fix w kalkulacji last price
This commit is contained in:
+87
-62
@@ -12,7 +12,7 @@ from .services import calculate_costs, fetch_orlen_price, fetch_orlen_range, inv
|
||||
from .station_catalog import sync_station_catalog
|
||||
from . import THEMES
|
||||
from .vat import effective_vat_rate, global_vat_override, save_global_vat_override
|
||||
from .domain.costs import calculate_net_price_costs
|
||||
from .domain.costs import calculate_net_price_costs, calculate_net_price_comparison
|
||||
|
||||
main = Blueprint("main", __name__)
|
||||
FUEL_TYPES = ("PB95", "PB98", "DIESEL", "LPG")
|
||||
@@ -103,7 +103,12 @@ def parse_fuel_entry_form(entry):
|
||||
|
||||
|
||||
def calculate_entry_settlement(entry, fallback_settings=None):
|
||||
"""Calculate retail, invoice and effective company cost for one fuel entry."""
|
||||
"""Calculate retail, settlement and company costs for one fuel entry.
|
||||
|
||||
Commercial saving is always calculated net-to-net: pump gross price is first
|
||||
converted to net and then compared with Last Price net. VAT is reported
|
||||
separately and never counted as a Last Price saving.
|
||||
"""
|
||||
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
|
||||
@@ -112,35 +117,64 @@ def calculate_entry_settlement(entry, fallback_settings=None):
|
||||
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)
|
||||
policy = entry.vehicle.fuel_card_policy if entry.used_fuel_card else None
|
||||
uses_last_price = bool(
|
||||
entry.wholesale_price is not None and (
|
||||
(rule and rule.use_orlen_last_price) or (not rule and policy and policy.use_orlen_last_price)
|
||||
)
|
||||
)
|
||||
|
||||
if uses_last_price:
|
||||
settlement_net = Decimal(entry.wholesale_price)
|
||||
else:
|
||||
settlement_net = retail_price / (Decimal("1") + Decimal(str(vat_rate)) / Decimal("100"))
|
||||
|
||||
# A station-specific card rule has priority. The vehicle policy is a safe
|
||||
# fallback for cards/stations without a dedicated rule.
|
||||
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)
|
||||
discount_percent = Decimal(rule.discount_net_percent or 0)
|
||||
surcharge_per_liter = Decimal(rule.surcharge_net_per_liter or 0)
|
||||
elif policy:
|
||||
discount_percent = Decimal(policy.discount_percent or 0)
|
||||
surcharge_per_liter = Decimal(policy.surcharge_per_liter or 0)
|
||||
else:
|
||||
discount_percent = Decimal("0")
|
||||
surcharge_per_liter = Decimal("0")
|
||||
discount_percent = min(max(discount_percent, Decimal("0")), Decimal("100"))
|
||||
settlement_net *= Decimal("1") - discount_percent / Decimal("100")
|
||||
settlement_net += surcharge_per_liter
|
||||
settlement_net = max(settlement_net, Decimal("0"))
|
||||
|
||||
price_costs = calculate_net_price_costs(settlement_net, vat_rate, vat_deduction_percent)
|
||||
liters = Decimal(entry.liters)
|
||||
comparison = calculate_net_price_comparison(retail_price, settlement_net, liters, vat_rate)
|
||||
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")
|
||||
saving_net = comparison["saving_net_total"] if uses_last_price else Decimal("0")
|
||||
saving_net_per_liter = comparison["saving_net_per_liter"] if uses_last_price else Decimal("0")
|
||||
gross_saving = max(retail_gross - invoice_gross, Decimal("0"))
|
||||
gross_saving_per_liter = max(retail_price - price_costs["invoice_gross"], Decimal("0"))
|
||||
return {
|
||||
"liters": liters,
|
||||
"retail_price": retail_price,
|
||||
"retail_net_price": comparison["retail_net_price"],
|
||||
"settlement_net": settlement_net,
|
||||
"invoice_price": price_costs["invoice_gross"],
|
||||
"effective_price": price_costs["effective_cost"],
|
||||
"retail_gross": retail_gross,
|
||||
"retail_net_total": comparison["retail_net_total"],
|
||||
"settlement_net_total": comparison["settlement_net_total"],
|
||||
"invoice_gross": invoice_gross,
|
||||
"deductible_vat": deductible_vat,
|
||||
"effective_cost": effective_cost,
|
||||
"last_price_saving": last_price_saving,
|
||||
"saving_net": saving_net,
|
||||
"saving_net_per_liter": saving_net_per_liter,
|
||||
"gross_saving": gross_saving,
|
||||
"gross_saving_per_liter": gross_saving_per_liter,
|
||||
"discount_percent": discount_percent,
|
||||
"surcharge_per_liter": surcharge_per_liter,
|
||||
"uses_last_price": uses_last_price,
|
||||
"vat_rate": Decimal(str(vat_rate)),
|
||||
"vat_deduction_percent": Decimal(str(vat_deduction_percent)),
|
||||
@@ -163,57 +197,40 @@ def dashboard():
|
||||
try: year, mon = map(int, month.split("-"))
|
||||
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 []
|
||||
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 = {}
|
||||
zero = Decimal("0")
|
||||
totals = {"retail_gross": zero, "retail_net": zero, "settlement_net": zero, "invoice_gross": zero, "gross_saving": zero, "deductible_vat": zero, "final_cost": zero, "liters": zero, "saving_net": zero, "saving_base_net": zero}
|
||||
by_vehicle = defaultdict(lambda: {"retail_gross": 0, "invoice_gross": 0, "gross_saving": 0, "retail_net": 0, "settlement_net": 0, "saving_net": 0, "effective": 0, "liters": 0})
|
||||
invoices = defaultdict(lambda: {"gross": 0, "payable": 0, "count": 0}); settlements = {}
|
||||
for e in entries:
|
||||
row = calculate_entry_settlement(e, settings)
|
||||
totals["retail_gross"] += row["retail_gross"]
|
||||
totals["retail_net"] += row["retail_net_total"]
|
||||
totals["settlement_net"] += row["settlement_net_total"]
|
||||
totals["invoice_gross"] += row["invoice_gross"]
|
||||
totals["gross_saving"] += row["gross_saving"]
|
||||
totals["deductible_vat"] += row["deductible_vat"]
|
||||
totals["final_cost"] += row["effective_cost"]
|
||||
totals["liters"] += row["liters"]
|
||||
totals["saving_net"] += row["saving_net"]
|
||||
if row["uses_last_price"]:
|
||||
totals["saving_base_net"] += row["retail_net_total"]
|
||||
settlements[e.id] = {k: float(v) if isinstance(v, Decimal) else v for k, v in row.items()}
|
||||
bucket = by_vehicle[e.vehicle.name]
|
||||
bucket["retail_gross"] += float(row["retail_gross"])
|
||||
bucket["retail_net"] += float(row["retail_net_total"])
|
||||
bucket["settlement_net"] += float(row["settlement_net_total"])
|
||||
bucket["saving_net"] += float(row["saving_net"])
|
||||
bucket["invoice_gross"] += float(row["invoice_gross"])
|
||||
bucket["gross_saving"] += float(row["gross_saving"])
|
||||
bucket["effective"] += float(row["effective_cost"])
|
||||
bucket["liters"] += float(row["liters"])
|
||||
entry_settings = e.vehicle.company or settings
|
||||
vat_rate = effective_vat_rate(entry_settings.vat_rate, e.fueled_at)
|
||||
vat_deduction_percent = entry_settings.vat_deduction_percent
|
||||
normal_price = float(e.price_per_liter)
|
||||
rule = FuelCardStationRule.query.filter_by(fuel_card_id=e.fuel_card_id, station_company_id=e.station_company_id).first() if e.used_fuel_card and e.fuel_card_id and e.station_company_id else None
|
||||
uses_station_last_price = bool(rule and rule.use_orlen_last_price and e.wholesale_price is not None)
|
||||
|
||||
# Last price is a NET station price. Discounts and surcharges are also net.
|
||||
if uses_station_last_price:
|
||||
settlement_net = Decimal(e.wholesale_price)
|
||||
else:
|
||||
settlement_net = Decimal(str(normal_price)) / (Decimal("1") + Decimal(str(vat_rate)) / Decimal("100"))
|
||||
if rule:
|
||||
settlement_net = 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(e.liters)
|
||||
invoice_gross = price_costs["invoice_gross"] * liters
|
||||
deductible_vat = price_costs["deductible_vat"] * liters
|
||||
effective_gross = price_costs["effective_cost"] * liters
|
||||
|
||||
totals["gross"] += invoice_gross
|
||||
totals["deductible_vat"] += deductible_vat
|
||||
totals["final_cost"] += effective_gross
|
||||
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] = {
|
||||
"normal_price": normal_price,
|
||||
"net_price": float(settlement_net),
|
||||
"invoice_price": float(price_costs["invoice_gross"]),
|
||||
"payable_price": float(price_costs["effective_cost"]),
|
||||
"normal_gross": e.gross,
|
||||
"invoice_gross": float(invoice_gross),
|
||||
"payable_gross": float(effective_gross),
|
||||
"uses_last_price": uses_station_last_price,
|
||||
"last_price_saving": float(last_price_saving),
|
||||
"vat_rate": float(vat_rate),
|
||||
"vat_deduction_percent": float(vat_deduction_percent),
|
||||
}
|
||||
by_vehicle[e.vehicle.name]["gross"] += float(invoice_gross); by_vehicle[e.vehicle.name]["payable"] += float(effective_gross); by_vehicle[e.vehicle.name]["liters"] += float(liters)
|
||||
if e.used_fuel_card:
|
||||
key = invoice_period(e.fueled_at, settings.invoice_split_day) if split_enabled else "Cały miesiąc"
|
||||
key = invoice_period(e.fueled_at, entry_settings.invoice_split_day) if split_enabled else "Cały miesiąc"
|
||||
else:
|
||||
key = "Poza kartą"
|
||||
invoices[key]["gross"] += float(invoice_gross); invoices[key]["payable"] += float(effective_gross); invoices[key]["count"] += 1
|
||||
invoices[key]["gross"] += float(row["invoice_gross"]); invoices[key]["payable"] += float(row["effective_cost"]); invoices[key]["count"] += 1
|
||||
totals["saving_percent"] = (totals["saving_net"] / totals["saving_base_net"] * Decimal("100")) if totals["saving_base_net"] else zero
|
||||
return render_template("dashboard.html", vehicles=vehicles, entries=entries, totals=totals, month=month, by_vehicle=dict(by_vehicle), invoices=dict(invoices), settings=settings, split_enabled=split_enabled, settlements=settlements, vat_override=global_vat_override())
|
||||
|
||||
@main.route("/vehicles", methods=["GET", "POST"])
|
||||
@@ -554,32 +571,40 @@ def admin_reports():
|
||||
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})
|
||||
totals = {"retail_gross": zero, "retail_net": zero, "settlement_net": zero, "invoice": zero, "gross_saving": zero, "effective": zero, "vat_deducted": zero, "saving_net": zero, "saving_base_net": zero, "liters": zero, "count": 0, "last_price_count": 0}
|
||||
by_month = defaultdict(lambda: {"retail_gross": 0.0, "invoice": 0.0, "gross_saving": 0.0, "retail_net": 0.0, "settlement_net": 0.0, "effective": 0.0, "saving_net": 0.0, "liters": 0.0, "count": 0})
|
||||
by_vehicle = defaultdict(lambda: {"retail_gross": 0.0, "invoice": 0.0, "gross_saving": 0.0, "retail_net": 0.0, "settlement_net": 0.0, "effective": 0.0, "saving_net": 0.0, "liters": 0.0, "count": 0})
|
||||
by_company = defaultdict(lambda: {"retail_gross": 0.0, "invoice": 0.0, "gross_saving": 0.0, "retail_net": 0.0, "settlement_net": 0.0, "effective": 0.0, "saving_net": 0.0, "liters": 0.0, "count": 0})
|
||||
|
||||
for entry in entries:
|
||||
row = calculate_entry_settlement(entry)
|
||||
totals["retail"] += row["retail_gross"]
|
||||
totals["retail_gross"] += row["retail_gross"]
|
||||
totals["retail_net"] += row["retail_net_total"]
|
||||
totals["settlement_net"] += row["settlement_net_total"]
|
||||
totals["invoice"] += row["invoice_gross"]
|
||||
totals["gross_saving"] += row["gross_saving"]
|
||||
totals["effective"] += row["effective_cost"]
|
||||
totals["vat_deducted"] += row["deductible_vat"]
|
||||
totals["saving"] += row["last_price_saving"]
|
||||
totals["saving_net"] += row["saving_net"]
|
||||
if row["uses_last_price"]:
|
||||
totals["saving_base_net"] += row["retail_net_total"]
|
||||
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]["retail_gross"] += float(row["retail_gross"])
|
||||
bucket[key]["retail_net"] += float(row["retail_net_total"])
|
||||
bucket[key]["settlement_net"] += float(row["settlement_net_total"])
|
||||
bucket[key]["invoice"] += float(row["invoice_gross"])
|
||||
bucket[key]["gross_saving"] += float(row["gross_saving"])
|
||||
bucket[key]["effective"] += float(row["effective_cost"])
|
||||
bucket[key]["saving"] += float(row["last_price_saving"])
|
||||
bucket[key]["saving_net"] += float(row["saving_net"])
|
||||
bucket[key]["liters"] += float(row["liters"])
|
||||
bucket[key]["count"] += 1
|
||||
|
||||
totals["saving_percent"] = (totals["saving"] / totals["retail"] * Decimal("100")) if totals["retail"] else zero
|
||||
totals["saving_percent"] = (totals["saving_net"] / totals["saving_base_net"] * Decimal("100")) if totals["saving_base_net"] 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,
|
||||
|
||||
Reference in New Issue
Block a user