zapamietanie akcji
This commit is contained in:
+68
-79
@@ -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 entry_vat_rate, freeze_vat_action, global_vat_override, save_global_vat_override
|
||||
from .domain.costs import calculate_net_price_costs, calculate_net_price_comparison
|
||||
from .settlement import calculate as calculate_snapshot_settlement, freeze as freeze_settlement
|
||||
|
||||
main = Blueprint("main", __name__)
|
||||
FUEL_TYPES = ("PB95", "PB98", "DIESEL", "LPG")
|
||||
@@ -101,87 +101,75 @@ def parse_fuel_entry_form(entry):
|
||||
entry.wholesale_price = Decimal(wholesale_price) if wholesale_price else None
|
||||
entry.wholesale_source = (request.form.get("wholesale_source") or "").strip() or None
|
||||
settings = vehicle.company or (vehicle.owner.company if vehicle.owner else None) or CompanySettings.query.first()
|
||||
freeze_vat_action(entry, settings.vat_rate, previous_fueled_at)
|
||||
|
||||
# During manual editing the action snapshot is authoritative. This prevents
|
||||
# later global actions from changing historical settlements.
|
||||
if "vat_action_applied" in request.form or any(
|
||||
key in request.form for key in ("vat_action_name", "vat_action_rate", "vat_action_start", "vat_action_end")
|
||||
):
|
||||
applied = "vat_action_applied" in request.form
|
||||
entry.vat_action_frozen = True
|
||||
entry.vat_action_applied = applied
|
||||
if applied:
|
||||
action_name = (request.form.get("vat_action_name") or "").strip()
|
||||
action_rate_raw = (request.form.get("vat_action_rate") or "").strip()
|
||||
if not action_name:
|
||||
raise ValueError("Podaj nazwę akcji")
|
||||
if not action_rate_raw:
|
||||
raise ValueError("Podaj stawkę VAT akcji")
|
||||
action_rate = Decimal(action_rate_raw)
|
||||
if action_rate < 0 or action_rate > 100:
|
||||
raise ValueError("Stawka VAT akcji musi mieścić się w zakresie 0–100%")
|
||||
start_raw = (request.form.get("vat_action_start") or "").strip()
|
||||
end_raw = (request.form.get("vat_action_end") or "").strip()
|
||||
action_start = date.fromisoformat(start_raw) if start_raw else None
|
||||
action_end = date.fromisoformat(end_raw) if end_raw else None
|
||||
if action_start and action_end and action_start > action_end:
|
||||
raise ValueError("Data zakończenia akcji nie może być wcześniejsza niż data rozpoczęcia")
|
||||
fuel_day = fueled_at.date()
|
||||
if action_start and fuel_day < action_start:
|
||||
raise ValueError("Data tankowania jest wcześniejsza niż początek akcji")
|
||||
if action_end and fuel_day > action_end:
|
||||
raise ValueError("Data tankowania jest późniejsza niż koniec akcji")
|
||||
entry.vat_action_name = action_name
|
||||
entry.vat_action_rate = action_rate
|
||||
entry.vat_action_start = action_start
|
||||
entry.vat_action_end = action_end
|
||||
else:
|
||||
entry.vat_action_name = None
|
||||
entry.vat_action_rate = None
|
||||
entry.vat_action_start = None
|
||||
entry.vat_action_end = None
|
||||
else:
|
||||
freeze_vat_action(entry, settings.vat_rate, previous_fueled_at)
|
||||
|
||||
manual_keys = ("snapshot_vat_rate", "snapshot_vat_deduction_percent", "snapshot_discount_percent", "snapshot_surcharge_per_liter", "snapshot_uses_last_price")
|
||||
if any(key in request.form for key in manual_keys):
|
||||
vat_rate = Decimal(request.form.get("snapshot_vat_rate", ""))
|
||||
deduction = Decimal(request.form.get("snapshot_vat_deduction_percent", ""))
|
||||
discount = Decimal(request.form.get("snapshot_discount_percent", "0") or 0)
|
||||
surcharge = Decimal(request.form.get("snapshot_surcharge_per_liter", "0") or 0)
|
||||
if not (0 <= vat_rate <= 100 and 0 <= deduction <= 100 and 0 <= discount <= 100):
|
||||
raise ValueError("Stawki VAT, odliczenia i rabatu muszą mieścić się w zakresie 0–100%")
|
||||
if entry.vat_action_applied and entry.vat_action_rate is not None and vat_rate != Decimal(entry.vat_action_rate):
|
||||
raise ValueError("Stawka VAT rozliczenia musi być zgodna ze stawką zapisanej akcji")
|
||||
uses_last = "snapshot_uses_last_price" in request.form
|
||||
if uses_last and entry.wholesale_price is None:
|
||||
raise ValueError("Last Price wymaga zapisanej ceny hurtowej")
|
||||
conditions = {"vat_rate": vat_rate, "vat_deduction_percent": deduction, "uses_last_price": uses_last,
|
||||
"discount_percent": discount, "surcharge_per_liter": surcharge,
|
||||
"rule_source": (request.form.get("snapshot_rule_source") or "Ręczna korekta").strip()}
|
||||
freeze_settlement(entry, settings, conditions, source="Ręczna korekta w edycji")
|
||||
else:
|
||||
freeze_settlement(entry, settings, source="Automatycznie przy zapisie/edycji")
|
||||
return vehicle
|
||||
|
||||
|
||||
def calculate_entry_settlement(entry, fallback_settings=None):
|
||||
"""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 = entry_vat_rate(entry, entry_settings.vat_rate)
|
||||
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
|
||||
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:
|
||||
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
|
||||
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,
|
||||
"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)),
|
||||
}
|
||||
# Historyczne wyniki po zamrożeniu korzystają wyłącznie z migawki wpisu.
|
||||
if not entry.settlement_frozen:
|
||||
freeze_settlement(entry, fallback_settings, source="Migracja istniejącego wpisu", migration_note="Warunki odtworzono z ustawień dostępnych podczas migracji; wymagają weryfikacji.")
|
||||
return calculate_snapshot_settlement(entry, fallback_settings)
|
||||
|
||||
def accessible_vehicles():
|
||||
if current_user.role == "admin":
|
||||
@@ -320,8 +308,9 @@ def fuel():
|
||||
p = fetch_orlen_price(request.form["fuel_type"], fueled_at.date(), price_region); wholesale=p["price_per_liter"]; source=p["source"]
|
||||
except Exception: pass
|
||||
station_label = station_company.company_name if station_company else request.form.get("station")
|
||||
e = FuelEntry(vehicle_id=vehicle.id, user_id=current_user.id, fueled_at=fueled_at, liters=Decimal(request.form["liters"]), price_per_liter=Decimal(request.form["price_per_liter"]), fuel_type=request.form["fuel_type"], odometer=odometer, station=station_label, station_company_id=station_company.id if station_company else None, invoice_number=request.form.get("invoice_number"), used_fuel_card="used_fuel_card" in request.form, wholesale_price=wholesale, wholesale_source=source)
|
||||
e = FuelEntry(vehicle=vehicle, user_id=current_user.id, fueled_at=fueled_at, liters=Decimal(request.form["liters"]), price_per_liter=Decimal(request.form["price_per_liter"]), fuel_type=request.form["fuel_type"], odometer=odometer, station=station_label, station_company_id=station_company.id if station_company else None, invoice_number=request.form.get("invoice_number"), used_fuel_card="used_fuel_card" in request.form, wholesale_price=wholesale, wholesale_source=source)
|
||||
freeze_vat_action(e, settings.vat_rate)
|
||||
freeze_settlement(e, settings, source="Automatycznie przy dodaniu")
|
||||
vehicle.current_odometer = odometer; db.session.add(e); db.session.commit(); response = ok_response("Zapisano tankowanie", redirect=url_for("main.dashboard")); return response or redirect(url_for("main.dashboard"))
|
||||
allowed=list(settings.allowed_stations)
|
||||
company_favorites=list(settings.favorite_stations)
|
||||
|
||||
Reference in New Issue
Block a user