edycja tankowań

This commit is contained in:
Mateusz Gruszczyński
2026-07-14 13:33:28 +02:00
parent 5980a61a7e
commit d2f0077e86
6 changed files with 166 additions and 2 deletions
+134 -1
View File
@@ -4,7 +4,7 @@ from decimal import Decimal
from functools import wraps
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, abort
from flask_login import login_required, current_user
from sqlalchemy import extract, or_
from sqlalchemy import extract, or_, and_
from sqlalchemy.exc import IntegrityError
from .extensions import db
from .models import User, Vehicle, FuelEntry, CompanySettings, AppSetting, OrlenPrice, FuelCardPolicy, FuelStationCompany, FuelStationPoint, FuelCard, FuelCardStationRule
@@ -43,6 +43,63 @@ def roles(*allowed):
return wrapped
return deco
def fuel_entry_accessible(entry):
if current_user.role == "admin":
return True
if current_user.role != "boss" or not current_user.company_id:
return False
company_id = entry.vehicle.company_id or (entry.vehicle.owner.company_id if entry.vehicle and entry.vehicle.owner else None)
return company_id == current_user.company_id
def recalculate_vehicle_odometer(vehicle):
latest = (db.session.query(db.func.max(FuelEntry.odometer))
.filter(FuelEntry.vehicle_id == vehicle.id)
.scalar())
vehicle.current_odometer = int(latest or 0)
def parse_fuel_entry_form(entry):
vehicle_id = request.form.get("vehicle_id", type=int)
vehicle = db.session.get(Vehicle, vehicle_id)
if not vehicle:
raise ValueError("Nie znaleziono pojazdu")
if current_user.role == "boss" and (vehicle.company_id or vehicle.owner.company_id) != current_user.company_id:
abort(403)
fuel_type = (request.form.get("fuel_type") or "").upper()
if fuel_type not in FUEL_TYPES:
raise ValueError("Nieprawidłowy rodzaj paliwa")
fueled_at = datetime.fromisoformat(request.form.get("fueled_at", ""))
liters = Decimal(request.form.get("liters", ""))
price = Decimal(request.form.get("price_per_liter", ""))
odometer = int(request.form.get("odometer", ""))
if liters <= 0:
raise ValueError("Liczba litrów musi być większa od zera")
if price < 0 or odometer < 0:
raise ValueError("Cena i stan licznika nie mogą być ujemne")
station_company_id = request.form.get("station_company_id", type=int)
station_company = db.session.get(FuelStationCompany, station_company_id) if station_company_id else None
fuel_card_id = request.form.get("fuel_card_id", type=int)
fuel_card = db.session.get(FuelCard, fuel_card_id) if fuel_card_id else None
company_id = vehicle.company_id or vehicle.owner.company_id
if fuel_card and fuel_card.company_id and fuel_card.company_id != company_id:
raise ValueError("Wybrana karta nie należy do firmy pojazdu")
entry.vehicle = vehicle
entry.fueled_at = fueled_at
entry.liters = liters
entry.price_per_liter = price
entry.fuel_type = fuel_type
entry.odometer = odometer
entry.station_company = station_company
entry.station = station_company.company_name if station_company else (request.form.get("station") or "").strip() or None
entry.invoice_number = (request.form.get("invoice_number") or "").strip() or None
entry.used_fuel_card = "used_fuel_card" in request.form
entry.fuel_card = fuel_card if entry.used_fuel_card else None
wholesale_price = (request.form.get("wholesale_price") or "").strip()
entry.wholesale_price = Decimal(wholesale_price) if wholesale_price else None
entry.wholesale_source = (request.form.get("wholesale_source") or "").strip() or None
return vehicle
def accessible_vehicles():
if current_user.role == "admin":
return Vehicle.query.order_by(Vehicle.name).all()
@@ -374,6 +431,82 @@ def orlen_sync():
except Exception as exc:
db.session.rollback(); return jsonify({"ok": False, "error": str(exc)}), 502
@main.get("/admin/fuel-entries")
@login_required
@roles("boss", "admin")
def admin_fuel_entries():
q = request.args.get("q", "").strip()
month = request.args.get("month", "").strip()
company_id = request.args.get("company_id", type=int)
page = request.args.get("page", 1, type=int)
query = FuelEntry.query.join(Vehicle).join(User, Vehicle.owner_id == User.id)
if current_user.role == "boss":
query = query.filter(or_(Vehicle.company_id == current_user.company_id, and_(Vehicle.company_id.is_(None), User.company_id == current_user.company_id)))
company_id = current_user.company_id
elif company_id:
query = query.filter(or_(Vehicle.company_id == company_id, and_(Vehicle.company_id.is_(None), User.company_id == company_id)))
if month:
try:
year, mon = map(int, month.split("-"))
query = query.filter(extract("year", FuelEntry.fueled_at) == year, extract("month", FuelEntry.fueled_at) == mon)
except ValueError:
month = ""
if q:
like = f"%{q}%"
query = query.outerjoin(FuelStationCompany, FuelEntry.station_company_id == FuelStationCompany.id).filter(or_(
Vehicle.name.ilike(like), Vehicle.registration.ilike(like), FuelEntry.station.ilike(like),
FuelEntry.invoice_number.ilike(like), User.name.ilike(like), User.email.ilike(like),
FuelStationCompany.company_name.ilike(like), FuelStationCompany.brand_name.ilike(like)
))
entries_page = query.order_by(FuelEntry.fueled_at.desc(), FuelEntry.id.desc()).paginate(page=page, per_page=25, error_out=False)
companies = CompanySettings.query.order_by(CompanySettings.name).all() if current_user.role == "admin" else [current_user.company]
vehicles_q = Vehicle.query.join(User, Vehicle.owner_id == User.id)
cards_q = FuelCard.query.filter_by(active=True)
if current_user.role == "boss":
vehicles_q = vehicles_q.filter(or_(Vehicle.company_id == current_user.company_id, and_(Vehicle.company_id.is_(None), User.company_id == current_user.company_id)))
cards_q = cards_q.filter_by(company_id=current_user.company_id)
elif company_id:
vehicles_q = vehicles_q.filter(or_(Vehicle.company_id == company_id, and_(Vehicle.company_id.is_(None), User.company_id == company_id)))
cards_q = cards_q.filter_by(company_id=company_id)
return render_template("admin_fuel_entries.html", entries_page=entries_page, q=q, month=month, company_id=company_id, companies=companies, vehicles=vehicles_q.order_by(Vehicle.name).all(), stations=FuelStationCompany.query.filter_by(active=True).order_by(FuelStationCompany.brand_name, FuelStationCompany.company_name).all(), fuel_cards=cards_q.order_by(FuelCard.name).all())
@main.put("/api/fuel-entries/<int:entry_id>")
@login_required
@roles("boss", "admin")
def update_fuel_entry(entry_id):
entry = FuelEntry.query.get_or_404(entry_id)
if not fuel_entry_accessible(entry):
abort(403)
old_vehicle = entry.vehicle
try:
new_vehicle = parse_fuel_entry_form(entry)
db.session.flush()
recalculate_vehicle_odometer(old_vehicle)
if new_vehicle.id != old_vehicle.id:
recalculate_vehicle_odometer(new_vehicle)
db.session.commit()
except (ValueError, ArithmeticError) as exc:
db.session.rollback()
return jsonify({"ok": False, "error": str(exc)}), 400
return jsonify({"ok": True, "message": "Zaktualizowano tankowanie"})
@main.delete("/api/fuel-entries/<int:entry_id>")
@login_required
@roles("boss", "admin")
def delete_fuel_entry(entry_id):
entry = FuelEntry.query.get_or_404(entry_id)
if not fuel_entry_accessible(entry):
abort(403)
vehicle = entry.vehicle
db.session.delete(entry)
db.session.flush()
recalculate_vehicle_odometer(vehicle)
db.session.commit()
return jsonify({"ok": True, "message": "Usunięto tankowanie"})
@main.route("/admin", methods=["GET","POST"])
@login_required
@roles("boss", "admin")