zestawienia
This commit is contained in:
+131
-1
@@ -101,6 +101,51 @@ def parse_fuel_entry_form(entry):
|
|||||||
entry.wholesale_source = (request.form.get("wholesale_source") or "").strip() or None
|
entry.wholesale_source = (request.form.get("wholesale_source") or "").strip() or None
|
||||||
return vehicle
|
return vehicle
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_entry_settlement(entry, fallback_settings=None):
|
||||||
|
"""Calculate retail, invoice and effective company cost for one fuel entry."""
|
||||||
|
entry_settings = entry.vehicle.company or (entry.vehicle.owner.company if entry.vehicle and entry.vehicle.owner else None) or fallback_settings or CompanySettings.query.first()
|
||||||
|
vat_rate = effective_vat_rate(entry_settings.vat_rate, entry.fueled_at)
|
||||||
|
vat_deduction_percent = entry_settings.vat_deduction_percent
|
||||||
|
retail_price = Decimal(entry.price_per_liter)
|
||||||
|
rule = FuelCardStationRule.query.filter_by(
|
||||||
|
fuel_card_id=entry.fuel_card_id,
|
||||||
|
station_company_id=entry.station_company_id,
|
||||||
|
).first() if entry.used_fuel_card and entry.fuel_card_id and entry.station_company_id else None
|
||||||
|
uses_last_price = bool(rule and rule.use_orlen_last_price and entry.wholesale_price is not None)
|
||||||
|
|
||||||
|
if uses_last_price:
|
||||||
|
settlement_net = Decimal(entry.wholesale_price)
|
||||||
|
else:
|
||||||
|
settlement_net = retail_price / (Decimal("1") + Decimal(str(vat_rate)) / Decimal("100"))
|
||||||
|
if rule:
|
||||||
|
settlement_net *= Decimal("1") - Decimal(rule.discount_net_percent or 0) / Decimal("100")
|
||||||
|
settlement_net += Decimal(rule.surcharge_net_per_liter or 0)
|
||||||
|
settlement_net = max(settlement_net, Decimal("0"))
|
||||||
|
|
||||||
|
price_costs = calculate_net_price_costs(settlement_net, vat_rate, vat_deduction_percent)
|
||||||
|
liters = Decimal(entry.liters)
|
||||||
|
retail_gross = retail_price * liters
|
||||||
|
invoice_gross = price_costs["invoice_gross"] * liters
|
||||||
|
deductible_vat = price_costs["deductible_vat"] * liters
|
||||||
|
effective_cost = price_costs["effective_cost"] * liters
|
||||||
|
last_price_saving = retail_gross - invoice_gross if uses_last_price else Decimal("0")
|
||||||
|
return {
|
||||||
|
"liters": liters,
|
||||||
|
"retail_price": retail_price,
|
||||||
|
"settlement_net": settlement_net,
|
||||||
|
"invoice_price": price_costs["invoice_gross"],
|
||||||
|
"effective_price": price_costs["effective_cost"],
|
||||||
|
"retail_gross": retail_gross,
|
||||||
|
"invoice_gross": invoice_gross,
|
||||||
|
"deductible_vat": deductible_vat,
|
||||||
|
"effective_cost": effective_cost,
|
||||||
|
"last_price_saving": last_price_saving,
|
||||||
|
"uses_last_price": uses_last_price,
|
||||||
|
"vat_rate": Decimal(str(vat_rate)),
|
||||||
|
"vat_deduction_percent": Decimal(str(vat_deduction_percent)),
|
||||||
|
}
|
||||||
|
|
||||||
def accessible_vehicles():
|
def accessible_vehicles():
|
||||||
if current_user.role == "admin":
|
if current_user.role == "admin":
|
||||||
return Vehicle.query.order_by(Vehicle.name).all()
|
return Vehicle.query.order_by(Vehicle.name).all()
|
||||||
@@ -118,7 +163,7 @@ def dashboard():
|
|||||||
try: year, mon = map(int, month.split("-"))
|
try: year, mon = map(int, month.split("-"))
|
||||||
except ValueError: year, mon = datetime.now().year, datetime.now().month; month=f"{year:04d}-{mon:02d}"
|
except ValueError: year, mon = datetime.now().year, datetime.now().month; month=f"{year:04d}-{mon:02d}"
|
||||||
entries = FuelEntry.query.filter(FuelEntry.vehicle_id.in_(ids), extract("year", FuelEntry.fueled_at)==year, extract("month", FuelEntry.fueled_at)==mon).order_by(FuelEntry.fueled_at.desc()).all() if ids else []
|
entries = FuelEntry.query.filter(FuelEntry.vehicle_id.in_(ids), extract("year", FuelEntry.fueled_at)==year, extract("month", FuelEntry.fueled_at)==mon).order_by(FuelEntry.fueled_at.desc()).all() if ids else []
|
||||||
totals = {"gross": Decimal("0"), "deductible_vat": Decimal("0"), "final_cost": Decimal("0"), "liters": Decimal("0")}
|
totals = {"gross": Decimal("0"), "deductible_vat": Decimal("0"), "final_cost": Decimal("0"), "liters": Decimal("0"), "last_price_saving": Decimal("0")}
|
||||||
by_vehicle = defaultdict(lambda: {"gross": 0, "payable": 0, "liters": 0}); invoices = defaultdict(lambda: {"gross": 0, "payable": 0, "count": 0}); settlements = {}
|
by_vehicle = defaultdict(lambda: {"gross": 0, "payable": 0, "liters": 0}); invoices = defaultdict(lambda: {"gross": 0, "payable": 0, "count": 0}); settlements = {}
|
||||||
for e in entries:
|
for e in entries:
|
||||||
entry_settings = e.vehicle.company or settings
|
entry_settings = e.vehicle.company or settings
|
||||||
@@ -148,6 +193,8 @@ def dashboard():
|
|||||||
totals["deductible_vat"] += deductible_vat
|
totals["deductible_vat"] += deductible_vat
|
||||||
totals["final_cost"] += effective_gross
|
totals["final_cost"] += effective_gross
|
||||||
totals["liters"] += liters
|
totals["liters"] += liters
|
||||||
|
last_price_saving = Decimal(str(e.gross)) - invoice_gross if uses_station_last_price else Decimal("0")
|
||||||
|
totals["last_price_saving"] += last_price_saving
|
||||||
settlements[e.id] = {
|
settlements[e.id] = {
|
||||||
"normal_price": normal_price,
|
"normal_price": normal_price,
|
||||||
"net_price": float(settlement_net),
|
"net_price": float(settlement_net),
|
||||||
@@ -157,6 +204,7 @@ def dashboard():
|
|||||||
"invoice_gross": float(invoice_gross),
|
"invoice_gross": float(invoice_gross),
|
||||||
"payable_gross": float(effective_gross),
|
"payable_gross": float(effective_gross),
|
||||||
"uses_last_price": uses_station_last_price,
|
"uses_last_price": uses_station_last_price,
|
||||||
|
"last_price_saving": float(last_price_saving),
|
||||||
"vat_rate": float(vat_rate),
|
"vat_rate": float(vat_rate),
|
||||||
"vat_deduction_percent": float(vat_deduction_percent),
|
"vat_deduction_percent": float(vat_deduction_percent),
|
||||||
}
|
}
|
||||||
@@ -456,6 +504,88 @@ def orlen_sync():
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@main.get("/admin/reports")
|
||||||
|
@login_required
|
||||||
|
@roles("boss", "admin")
|
||||||
|
def admin_reports():
|
||||||
|
today = date.today()
|
||||||
|
period = request.args.get("period", "month")
|
||||||
|
company_id = request.args.get("company_id", type=int)
|
||||||
|
month = request.args.get("month", today.strftime("%Y-%m"))
|
||||||
|
year = request.args.get("year", today.year, type=int)
|
||||||
|
date_from_raw = request.args.get("date_from", "")
|
||||||
|
date_to_raw = request.args.get("date_to", "")
|
||||||
|
|
||||||
|
if current_user.role == "boss":
|
||||||
|
company_id = current_user.company_id
|
||||||
|
companies = CompanySettings.query.order_by(CompanySettings.name).all() if current_user.role == "admin" else [current_user.company]
|
||||||
|
|
||||||
|
try:
|
||||||
|
if period == "year":
|
||||||
|
start_date, end_date = date(year, 1, 1), date(year, 12, 31)
|
||||||
|
elif period == "custom":
|
||||||
|
start_date = date.fromisoformat(date_from_raw)
|
||||||
|
end_date = date.fromisoformat(date_to_raw)
|
||||||
|
if end_date < start_date:
|
||||||
|
raise ValueError
|
||||||
|
else:
|
||||||
|
period = "month"
|
||||||
|
selected_year, selected_month = map(int, month.split("-"))
|
||||||
|
start_date = date(selected_year, selected_month, 1)
|
||||||
|
if selected_month == 12:
|
||||||
|
end_date = date(selected_year, 12, 31)
|
||||||
|
else:
|
||||||
|
end_date = date(selected_year, selected_month + 1, 1)
|
||||||
|
end_date = date.fromordinal(end_date.toordinal() - 1)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
period = "month"
|
||||||
|
month = today.strftime("%Y-%m")
|
||||||
|
start_date = date(today.year, today.month, 1)
|
||||||
|
next_month = date(today.year + (today.month == 12), 1 if today.month == 12 else today.month + 1, 1)
|
||||||
|
end_date = date.fromordinal(next_month.toordinal() - 1)
|
||||||
|
|
||||||
|
query = FuelEntry.query.join(Vehicle).join(User, Vehicle.owner_id == User.id).filter(
|
||||||
|
FuelEntry.fueled_at >= datetime.combine(start_date, datetime.min.time()),
|
||||||
|
FuelEntry.fueled_at < datetime.combine(date.fromordinal(end_date.toordinal() + 1), datetime.min.time()),
|
||||||
|
)
|
||||||
|
if company_id:
|
||||||
|
query = query.filter(or_(Vehicle.company_id == company_id, and_(Vehicle.company_id.is_(None), User.company_id == company_id)))
|
||||||
|
entries = query.order_by(FuelEntry.fueled_at.asc()).all()
|
||||||
|
|
||||||
|
zero = Decimal("0")
|
||||||
|
totals = {"retail": zero, "invoice": zero, "effective": zero, "vat_deducted": zero, "saving": zero, "liters": zero, "count": 0, "last_price_count": 0}
|
||||||
|
by_month = defaultdict(lambda: {"retail": 0.0, "invoice": 0.0, "effective": 0.0, "saving": 0.0, "liters": 0.0, "count": 0})
|
||||||
|
by_vehicle = defaultdict(lambda: {"retail": 0.0, "invoice": 0.0, "effective": 0.0, "saving": 0.0, "liters": 0.0, "count": 0})
|
||||||
|
by_company = defaultdict(lambda: {"retail": 0.0, "invoice": 0.0, "effective": 0.0, "saving": 0.0, "liters": 0.0, "count": 0})
|
||||||
|
|
||||||
|
for entry in entries:
|
||||||
|
row = calculate_entry_settlement(entry)
|
||||||
|
totals["retail"] += row["retail_gross"]
|
||||||
|
totals["invoice"] += row["invoice_gross"]
|
||||||
|
totals["effective"] += row["effective_cost"]
|
||||||
|
totals["vat_deducted"] += row["deductible_vat"]
|
||||||
|
totals["saving"] += row["last_price_saving"]
|
||||||
|
totals["liters"] += row["liters"]
|
||||||
|
totals["count"] += 1
|
||||||
|
totals["last_price_count"] += int(row["uses_last_price"])
|
||||||
|
company = entry.vehicle.company or entry.vehicle.owner.company
|
||||||
|
keys = ((by_month, entry.fueled_at.strftime("%Y-%m")), (by_vehicle, entry.vehicle.name), (by_company, company.name if company else "Bez firmy"))
|
||||||
|
for bucket, key in keys:
|
||||||
|
bucket[key]["retail"] += float(row["retail_gross"])
|
||||||
|
bucket[key]["invoice"] += float(row["invoice_gross"])
|
||||||
|
bucket[key]["effective"] += float(row["effective_cost"])
|
||||||
|
bucket[key]["saving"] += float(row["last_price_saving"])
|
||||||
|
bucket[key]["liters"] += float(row["liters"])
|
||||||
|
bucket[key]["count"] += 1
|
||||||
|
|
||||||
|
totals["saving_percent"] = (totals["saving"] / totals["retail"] * Decimal("100")) if totals["retail"] else zero
|
||||||
|
return render_template(
|
||||||
|
"admin_reports.html", companies=[c for c in companies if c], company_id=company_id, period=period, month=month, year=year,
|
||||||
|
date_from=date_from_raw, date_to=date_to_raw, start_date=start_date, end_date=end_date, totals=totals,
|
||||||
|
by_month=dict(sorted(by_month.items())), by_vehicle=dict(sorted(by_vehicle.items())), by_company=dict(sorted(by_company.items())),
|
||||||
|
)
|
||||||
|
|
||||||
@main.get("/admin/fuel-entries")
|
@main.get("/admin/fuel-entries")
|
||||||
@login_required
|
@login_required
|
||||||
@roles("boss", "admin")
|
@roles("boss", "admin")
|
||||||
|
|||||||
+35
-1
@@ -1 +1,35 @@
|
|||||||
(()=>{const F=FuelTrack;const text=()=>getComputedStyle(document.body).color;const grid=()=>getComputedStyle(document.documentElement).getPropertyValue('--bs-border-color').trim()||'rgba(0,0,0,.1)';function destroy(id){if(F.charts[id]){F.charts[id].destroy();delete F.charts[id]}}function empty(id,v){F.qs(`#${id}-empty`)?.classList.toggle('d-none',!v);F.qs(`#${id}`)?.classList.toggle('d-none',v)}F.renderBarChart=(id,data)=>{const el=F.qs(`#${id}`);if(!el)return;const labels=Object.keys(data||{});destroy(id);empty(id,!labels.length);if(!labels.length)return;F.charts[id]=new Chart(el,{type:'bar',data:{labels,datasets:[{label:'Cena detaliczna (zł)',data:labels.map(k=>data[k].gross)},{label:'Do zapłaty wg karty (zł)',data:labels.map(k=>data[k].payable)}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{labels:{color:text()}}},scales:{x:{ticks:{color:text()},grid:{color:grid()}},y:{beginAtZero:true,ticks:{color:text()},grid:{color:grid()}}}}})};F.renderMultiLineChart=(id,series,gross=false)=>{const el=F.qs(`#${id}`);if(!el)return;destroy(id);const keys=Object.keys(series||{}).filter(k=>(series[k]||[]).length);empty(id,!keys.length);if(!keys.length)return;const dates=[...new Set(keys.flatMap(k=>series[k].map(p=>p.date)))].sort();F.charts[id]=new Chart(el,{type:'line',data:{labels:dates,datasets:keys.map(k=>{const m=new Map(series[k].map(p=>[p.date,p.value]));return{label:`${k} ${gross?'brutto':'netto'} zł/l`,data:dates.map(d=>m.has(d)?m.get(d):null),spanGaps:true,tension:.2,pointRadius:1.5,borderWidth:2}})},options:{responsive:true,maintainAspectRatio:false,interaction:{mode:'index',intersect:false},plugins:{legend:{labels:{color:text()}}},scales:{x:{ticks:{color:text(),maxTicksLimit:12},grid:{color:grid()}},y:{ticks:{color:text()},grid:{color:grid()}}}}})}})();
|
(()=>{
|
||||||
|
const F=FuelTrack;
|
||||||
|
const text=()=>getComputedStyle(document.body).color;
|
||||||
|
const grid=()=>getComputedStyle(document.documentElement).getPropertyValue('--bs-border-color').trim()||'rgba(0,0,0,.1)';
|
||||||
|
function destroy(id){if(F.charts[id]){F.charts[id].destroy();delete F.charts[id]}}
|
||||||
|
function empty(id,value){F.qs(`#${id}-empty`)?.classList.toggle('d-none',!value);F.qs(`#${id}`)?.classList.toggle('d-none',value)}
|
||||||
|
const commonScales=()=>({x:{ticks:{color:text()},grid:{color:grid()}},y:{beginAtZero:true,ticks:{color:text()},grid:{color:grid()}}});
|
||||||
|
|
||||||
|
F.renderBarChart=(id,data)=>{
|
||||||
|
const el=F.qs(`#${id}`);if(!el)return;
|
||||||
|
const labels=Object.keys(data||{});destroy(id);empty(id,!labels.length);if(!labels.length)return;
|
||||||
|
F.charts[id]=new Chart(el,{type:'bar',data:{labels,datasets:[
|
||||||
|
{label:'Cena fakturowa (zł)',data:labels.map(k=>data[k].gross)},
|
||||||
|
{label:'Koszt po VAT (zł)',data:labels.map(k=>data[k].payable)}
|
||||||
|
]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{labels:{color:text()}}},scales:commonScales()}});
|
||||||
|
};
|
||||||
|
|
||||||
|
F.renderMultiLineChart=(id,series,gross=false)=>{
|
||||||
|
const el=F.qs(`#${id}`);if(!el)return;destroy(id);
|
||||||
|
const keys=Object.keys(series||{}).filter(k=>(series[k]||[]).length);empty(id,!keys.length);if(!keys.length)return;
|
||||||
|
const dates=[...new Set(keys.flatMap(k=>series[k].map(p=>p.date)))].sort();
|
||||||
|
F.charts[id]=new Chart(el,{type:'line',data:{labels:dates,datasets:keys.map(k=>{const m=new Map(series[k].map(p=>[p.date,p.value]));return{label:`${k} ${gross?'brutto':'netto'} zł/l`,data:dates.map(d=>m.has(d)?m.get(d):null),spanGaps:true,tension:.2,pointRadius:1.5,borderWidth:2}})},options:{responsive:true,maintainAspectRatio:false,interaction:{mode:'index',intersect:false},plugins:{legend:{labels:{color:text()}}},scales:{x:{ticks:{color:text(),maxTicksLimit:12},grid:{color:grid()}},y:{ticks:{color:text()},grid:{color:grid()}}}}});
|
||||||
|
};
|
||||||
|
|
||||||
|
F.renderCostReportChart=(id,data)=>{
|
||||||
|
const el=F.qs(`#${id}`);if(!el)return;
|
||||||
|
const labels=Object.keys(data||{});destroy(id);empty(id,!labels.length);if(!labels.length)return;
|
||||||
|
F.charts[id]=new Chart(el,{type:'bar',data:{labels,datasets:[
|
||||||
|
{label:'Detal z dystrybutora (zł)',data:labels.map(k=>data[k].retail)},
|
||||||
|
{label:'Faktura wg rozliczenia (zł)',data:labels.map(k=>data[k].invoice)},
|
||||||
|
{label:'Koszt po VAT (zł)',data:labels.map(k=>data[k].effective)},
|
||||||
|
{label:'Oszczędność Last Price (zł)',data:labels.map(k=>data[k].saving)}
|
||||||
|
]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{labels:{color:text()}}},scales:commonScales()}});
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|||||||
@@ -3,4 +3,5 @@
|
|||||||
{% if current_user.role=='admin' %}<li class="nav-item"><a class="nav-link {% if request.endpoint=='main.admin_application' %}active{% endif %}" href="{{url_for('main.admin_application')}}">Aplikacja</a></li>{% endif %}
|
{% if current_user.role=='admin' %}<li class="nav-item"><a class="nav-link {% if request.endpoint=='main.admin_application' %}active{% endif %}" href="{{url_for('main.admin_application')}}">Aplikacja</a></li>{% endif %}
|
||||||
<li class="nav-item"><a class="nav-link {% if request.endpoint=='main.admin_users' %}active{% endif %}" href="{{url_for('main.admin_users')}}">Użytkownicy</a></li>
|
<li class="nav-item"><a class="nav-link {% if request.endpoint=='main.admin_users' %}active{% endif %}" href="{{url_for('main.admin_users')}}">Użytkownicy</a></li>
|
||||||
<li class="nav-item"><a class="nav-link {% if request.endpoint=='main.admin_fuel_entries' %}active{% endif %}" href="{{url_for('main.admin_fuel_entries')}}">Tankowania</a></li>
|
<li class="nav-item"><a class="nav-link {% if request.endpoint=='main.admin_fuel_entries' %}active{% endif %}" href="{{url_for('main.admin_fuel_entries')}}">Tankowania</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link {% if request.endpoint=='main.admin_reports' %}active{% endif %}" href="{{url_for('main.admin_reports')}}">Zestawienia kosztów</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{% extends 'base.html' %}{% block content %}
|
||||||
|
<div class="mb-4"><h1 class="h2 mb-1">Zestawienia kosztów</h1><p class="text-body-secondary mb-0">Rzeczywiste koszty, VAT i oszczędność wynikająca z rozliczenia Last Price.</p></div>
|
||||||
|
{% include '_admin_nav.html' %}
|
||||||
|
<div class="card mb-4"><div class="card-body">
|
||||||
|
<form method="get" class="row g-3 ajax-nav-form" action="{{url_for('main.admin_reports')}}">
|
||||||
|
{% if current_user.role=='admin' %}<div class="col-lg-3"><label class="form-label">Firma</label><select class="form-select" name="company_id"><option value="">Wszystkie firmy</option>{% for c in companies %}<option value="{{c.id}}" {% if company_id==c.id %}selected{% endif %}>{{c.name}}</option>{% endfor %}</select></div>{% endif %}
|
||||||
|
<div class="col-lg-2"><label class="form-label">Zakres</label><select class="form-select" name="period" id="report-period"><option value="month" {% if period=='month' %}selected{% endif %}>Miesiąc</option><option value="year" {% if period=='year' %}selected{% endif %}>Rok</option><option value="custom" {% if period=='custom' %}selected{% endif %}>Zakres dat</option></select></div>
|
||||||
|
<div class="col-lg-2 report-filter report-month"><label class="form-label">Miesiąc</label><input class="form-control" type="month" name="month" value="{{month}}"></div>
|
||||||
|
<div class="col-lg-2 report-filter report-year"><label class="form-label">Rok</label><input class="form-control" type="number" min="2000" max="2100" name="year" value="{{year}}"></div>
|
||||||
|
<div class="col-lg-2 report-filter report-custom"><label class="form-label">Od</label><input class="form-control" type="date" name="date_from" value="{{date_from}}"></div>
|
||||||
|
<div class="col-lg-2 report-filter report-custom"><label class="form-label">Do</label><input class="form-control" type="date" name="date_to" value="{{date_to}}"></div>
|
||||||
|
<div class="col-lg-2 d-flex align-items-end"><button class="btn btn-primary w-100">Pokaż zestawienie</button></div>
|
||||||
|
</form></div></div>
|
||||||
|
<p class="small text-body-secondary">Okres: <strong>{{start_date.strftime('%d.%m.%Y')}}–{{end_date.strftime('%d.%m.%Y')}}</strong></p>
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-6 col-xl-2"><div class="card metric h-100"><div class="card-body"><small>Detal z dystrybutora</small><strong>{{'%.2f'|format(totals.retail)}} zł</strong></div></div></div>
|
||||||
|
<div class="col-6 col-xl-2"><div class="card metric h-100"><div class="card-body"><small>Faktury Last Price*</small><strong>{{'%.2f'|format(totals.invoice)}} zł</strong></div></div></div>
|
||||||
|
<div class="col-6 col-xl-2"><div class="card metric h-100"><div class="card-body"><small>Koszt po VAT</small><strong>{{'%.2f'|format(totals.effective)}} zł</strong></div></div></div>
|
||||||
|
<div class="col-6 col-xl-2"><div class="card metric h-100"><div class="card-body"><small>Odliczony VAT</small><strong>{{'%.2f'|format(totals.vat_deducted)}} zł</strong></div></div></div>
|
||||||
|
<div class="col-6 col-xl-2"><div class="card metric h-100"><div class="card-body"><small>Oszczędność Last Price</small><strong>{{'%.2f'|format(totals.saving)}} zł</strong><span>{{'%.2f'|format(totals.saving_percent)}}%</span></div></div></div>
|
||||||
|
<div class="col-6 col-xl-2"><div class="card metric h-100"><div class="card-body"><small>Tankowania / litry</small><strong>{{totals.count}} / {{'%.2f'|format(totals.liters)}} l</strong><span>Last Price: {{totals.last_price_count}}</span></div></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-info small"><strong>* Faktura Last Price</strong> oznacza kwotę obliczoną z ceny Last Price netto, powiększonej o stawkę VAT firmy. Oszczędność Last Price to różnica między wartością detaliczną z dystrybutora a fakturą Last Price brutto.</div>
|
||||||
|
<div class="card mb-4"><div class="card-body"><h2 class="h5">Koszty w czasie</h2><div class="chart-box"><canvas id="report-cost-chart"></canvas><div id="report-cost-chart-empty" class="chart-empty d-none">Brak danych dla wybranego okresu.</div></div></div></div>
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-xl-6"><div class="card h-100"><div class="card-body"><h2 class="h5">Według pojazdu</h2><div class="table-responsive"><table class="table table-hover align-middle"><thead><tr><th>Pojazd</th><th>Tank.</th><th>Litry</th><th>Detal</th><th>Faktura*</th><th>Koszt po VAT</th><th>Oszczędność</th></tr></thead><tbody>{% for name,row in by_vehicle.items() %}<tr><td><strong>{{name}}</strong></td><td>{{row.count}}</td><td>{{'%.2f'|format(row.liters)}}</td><td>{{'%.2f'|format(row.retail)}} zł</td><td>{{'%.2f'|format(row.invoice)}} zł</td><td>{{'%.2f'|format(row.effective)}} zł</td><td><strong>{{'%.2f'|format(row.saving)}} zł</strong></td></tr>{% else %}<tr><td colspan="7" class="text-center text-body-secondary">Brak danych.</td></tr>{% endfor %}</tbody></table></div></div></div></div>
|
||||||
|
<div class="col-xl-6"><div class="card h-100"><div class="card-body"><h2 class="h5">Według firmy</h2><div class="table-responsive"><table class="table table-hover align-middle"><thead><tr><th>Firma</th><th>Tank.</th><th>Litry</th><th>Detal</th><th>Faktura*</th><th>Koszt po VAT</th><th>Oszczędność</th></tr></thead><tbody>{% for name,row in by_company.items() %}<tr><td><strong>{{name}}</strong></td><td>{{row.count}}</td><td>{{'%.2f'|format(row.liters)}}</td><td>{{'%.2f'|format(row.retail)}} zł</td><td>{{'%.2f'|format(row.invoice)}} zł</td><td>{{'%.2f'|format(row.effective)}} zł</td><td><strong>{{'%.2f'|format(row.saving)}} zł</strong></td></tr>{% else %}<tr><td colspan="7" class="text-center text-body-secondary">Brak danych.</td></tr>{% endfor %}</tbody></table></div></div></div></div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}{% block scripts %}<script>
|
||||||
|
FuelTrack.renderCostReportChart('report-cost-chart', {{by_month|tojson}});
|
||||||
|
(()=>{const select=document.getElementById('report-period');const refresh=()=>{document.querySelectorAll('.report-filter').forEach(el=>el.classList.add('d-none'));document.querySelectorAll('.report-'+select.value).forEach(el=>el.classList.remove('d-none'));};select?.addEventListener('change',refresh);refresh();})();
|
||||||
|
</script>{% endblock %}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends 'base.html' %}{% block content %}
|
{% extends 'base.html' %}{% block content %}
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3"><div><h1 class="h2 mb-0">Podsumowanie kosztów</h1><small class="text-body-secondary">{{ settings.name }} · VAT do odliczenia {{ settings.vat_deduction_percent }}%</small></div><form action="{{url_for('main.dashboard')}}" class="ajax-nav-form"><input class="form-control" type="month" name="month" value="{{month}}"></form></div>
|
<div class="d-flex justify-content-between align-items-center mb-3"><div><h1 class="h2 mb-0">Podsumowanie kosztów</h1><small class="text-body-secondary">{{ settings.name }} · VAT do odliczenia {{ settings.vat_deduction_percent }}%</small></div><form action="{{url_for('main.dashboard')}}" class="ajax-nav-form"><input class="form-control" type="month" name="month" value="{{month}}"></form></div>
|
||||||
{% if vat_override.enabled %}<div class="alert alert-warning"><strong>{{ vat_override.name }}:</strong> globalny VAT {{ vat_override.rate }}%{% if vat_override.start %} od {{ vat_override.start.strftime('%d.%m.%Y') }}{% endif %}{% if vat_override.end %} do {{ vat_override.end.strftime('%d.%m.%Y') }}{% endif %}. Rozliczenia są liczone według daty tankowania.</div>{% endif %}
|
{% if vat_override.enabled %}<div class="alert alert-warning"><strong>{{ vat_override.name }}:</strong> globalny VAT {{ vat_override.rate }}%{% if vat_override.start %} od {{ vat_override.start.strftime('%d.%m.%Y') }}{% endif %}{% if vat_override.end %} do {{ vat_override.end.strftime('%d.%m.%Y') }}{% endif %}. Rozliczenia są liczone według daty tankowania.</div>{% endif %}
|
||||||
<div class="row g-3 mb-4"><div class="col-6 col-lg-3"><div class="card metric"><div class="card-body"><small>Brutto</small><strong>{{ '%.2f'|format(totals.gross) }} zł</strong></div></div></div><div class="col-6 col-lg-3"><div class="card metric"><div class="card-body"><small>VAT odliczony</small><strong>{{ '%.2f'|format(totals.deductible_vat) }} zł</strong></div></div></div><div class="col-6 col-lg-3"><div class="card metric"><div class="card-body"><small>Koszt po VAT</small><strong>{{ '%.2f'|format(totals.final_cost) }} zł</strong></div></div></div><div class="col-6 col-lg-3"><div class="card metric"><div class="card-body"><small>Litry</small><strong>{{ '%.2f'|format(totals.liters) }} l</strong></div></div></div></div>
|
<div class="row g-3 mb-4"><div class="col-6 col-lg"><div class="card metric"><div class="card-body"><small>Brutto</small><strong>{{ '%.2f'|format(totals.gross) }} zł</strong></div></div></div><div class="col-6 col-lg"><div class="card metric"><div class="card-body"><small>VAT odliczony</small><strong>{{ '%.2f'|format(totals.deductible_vat) }} zł</strong></div></div></div><div class="col-6 col-lg"><div class="card metric"><div class="card-body"><small>Koszt po VAT</small><strong>{{ '%.2f'|format(totals.final_cost) }} zł</strong></div></div></div><div class="col-6 col-lg"><div class="card metric"><div class="card-body"><small>Litry</small><strong>{{ '%.2f'|format(totals.liters) }} l</strong></div></div></div><div class="col-6 col-lg"><div class="card metric"><div class="card-body"><small>Oszczędność Last Price</small><strong>{{ '%.2f'|format(totals.last_price_saving) }} zł</strong></div></div></div></div>
|
||||||
<div class="row g-4"><div class="col-lg-7"><div class="card"><div class="card-body"><h2 class="h5">Koszt według pojazdu</h2><p class="small text-body-secondary">Cena fakturowa a rzeczywisty koszt po odliczeniu VAT.</p><div class="chart-box"><canvas id="cost-chart"></canvas><div id="cost-chart-empty" class="chart-empty d-none">Brak danych dla wybranego miesiąca.</div></div></div></div></div><div class="col-lg-5"><div class="card h-100"><div class="card-body"><h2 class="h5">Okresy faktur</h2><small class="text-body-secondary">{% if split_enabled %}Podział według dnia {{settings.invoice_split_day}}{% else %}Podział dzienny wyłączony{% endif %}</small><table class="table mt-2"><thead><tr><th>Okres</th><th>Pozycje</th><th>Detal</th><th>Koszt po VAT</th></tr></thead><tbody>{% for key,row in invoices.items() %}<tr><td>{{key}}</td><td>{{row.count}}</td><td>{{'%.2f'|format(row.gross)}} zł</td><td>{{'%.2f'|format(row.payable)}} zł</td></tr>{% else %}<tr><td colspan="4">Brak danych</td></tr>{% endfor %}</tbody></table></div></div></div></div>
|
<div class="row g-4"><div class="col-lg-7"><div class="card"><div class="card-body"><h2 class="h5">Koszt według pojazdu</h2><p class="small text-body-secondary">Cena fakturowa a rzeczywisty koszt po odliczeniu VAT.</p><div class="chart-box"><canvas id="cost-chart"></canvas><div id="cost-chart-empty" class="chart-empty d-none">Brak danych dla wybranego miesiąca.</div></div></div></div></div><div class="col-lg-5"><div class="card h-100"><div class="card-body"><h2 class="h5">Okresy faktur</h2><small class="text-body-secondary">{% if split_enabled %}Podział według dnia {{settings.invoice_split_day}}{% else %}Podział dzienny wyłączony{% endif %}</small><table class="table mt-2"><thead><tr><th>Okres</th><th>Pozycje</th><th>Faktura*</th><th>Koszt po VAT</th></tr></thead><tbody>{% for key,row in invoices.items() %}<tr><td>{{key}}</td><td>{{row.count}}</td><td>{{'%.2f'|format(row.gross)}} zł</td><td>{{'%.2f'|format(row.payable)}} zł</td></tr>{% else %}<tr><td colspan="4">Brak danych</td></tr>{% endfor %}</tbody></table><div class="small text-body-secondary">* Kwota faktury wynika z ceny Last Price netto + VAT firmy, gdy tankowanie podlega tej regule.</div></div></div></div>
|
||||||
<div class="card mt-4"><div class="card-body"><h2 class="h5">Tankowania</h2><div class="table-responsive"><table class="table table-hover align-middle"><thead><tr><th>Data</th><th>Auto</th><th>Stacja</th><th>Paliwo</th><th>Litry</th><th>Cena detal.</th><th>Last price netto</th><th>Koszt po VAT</th><th>VAT</th><th>Razem</th></tr></thead><tbody>{% for e in entries %}{% set s=settlements[e.id] %}<tr><td>{{e.fueled_at.strftime('%d.%m.%Y')}}</td><td>{{e.vehicle.name}}</td><td>{{e.station or '—'}}</td><td>{{e.fuel_type}}</td><td>{{e.liters}}</td><td>{{'%.4f'|format(s.normal_price)}} zł/l</td><td>{% if s.uses_last_price %}{{'%.4f'|format(s.net_price)}} zł/l{% elif e.wholesale_price %}{{'%.4f'|format(e.wholesale_price|float)}} zł/l{% else %}—{% endif %}</td><td>{% if s.uses_last_price %}<span class="badge text-bg-info">last price</span> {% endif %}<strong>{{'%.4f'|format(s.payable_price)}} zł/l</strong><br><small class="text-body-secondary">faktura {{'%.4f'|format(s.invoice_price)}} zł/l</small></td><td>{{'%.2f'|format(s.vat_rate)}}%<br><small class="text-body-secondary">odliczenie {{'%.0f'|format(s.vat_deduction_percent)}}%</small></td><td><span class="text-body-secondary text-decoration-line-through">{{'%.2f'|format(s.normal_gross)}} zł</span><br><strong>{{'%.2f'|format(s.payable_gross)}} zł</strong><br><small class="text-body-secondary">faktura {{'%.2f'|format(s.invoice_gross)}} zł</small></td></tr>{% else %}<tr><td colspan="10">Brak tankowań w tym miesiącu.</td></tr>{% endfor %}</tbody></table></div></div></div>
|
<div class="card mt-4"><div class="card-body"><h2 class="h5">Tankowania</h2><div class="table-responsive"><table class="table table-hover align-middle"><thead><tr><th>Data</th><th>Auto</th><th>Stacja</th><th>Paliwo</th><th>Litry</th><th>Cena detal.</th><th>Last price netto</th><th>Koszt po VAT</th><th>VAT</th><th>Oszczędność</th><th>Razem</th></tr></thead><tbody>{% for e in entries %}{% set s=settlements[e.id] %}<tr><td>{{e.fueled_at.strftime('%d.%m.%Y')}}</td><td>{{e.vehicle.name}}</td><td>{{e.station or '—'}}</td><td>{{e.fuel_type}}</td><td>{{e.liters}}</td><td>{{'%.4f'|format(s.normal_price)}} zł/l</td><td>{% if s.uses_last_price %}{{'%.4f'|format(s.net_price)}} zł/l{% elif e.wholesale_price %}{{'%.4f'|format(e.wholesale_price|float)}} zł/l{% else %}—{% endif %}</td><td>{% if s.uses_last_price %}<span class="badge text-bg-info">last price</span> {% endif %}<strong>{{'%.4f'|format(s.payable_price)}} zł/l</strong><br><small class="text-body-secondary">faktura {{'%.4f'|format(s.invoice_price)}} zł/l</small></td><td>{{'%.2f'|format(s.vat_rate)}}%<br><small class="text-body-secondary">odliczenie {{'%.0f'|format(s.vat_deduction_percent)}}%</small></td><td>{% if s.uses_last_price %}<strong>{{'%.2f'|format(s.last_price_saving)}} zł</strong><br><small class="text-body-secondary">dzięki Last Price</small>{% else %}—{% endif %}</td><td><span class="text-body-secondary text-decoration-line-through">{{'%.2f'|format(s.normal_gross)}} zł</span><br><strong>{{'%.2f'|format(s.payable_gross)}} zł</strong><br><small class="text-body-secondary">faktura {{'%.2f'|format(s.invoice_gross)}} zł</small></td></tr>{% else %}<tr><td colspan="11">Brak tankowań w tym miesiącu.</td></tr>{% endfor %}</tbody></table></div></div></div>
|
||||||
{% endblock %}{% block scripts %}<script>FuelTrack.renderBarChart('cost-chart', {{by_vehicle|tojson}});</script>{% endblock %}
|
{% endblock %}{% block scripts %}<script>FuelTrack.renderBarChart('cost-chart', {{by_vehicle|tojson}});</script>{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user