Files
shanxia2.0/backend/tests/test_pl_vehicle_fuel_summary.py

208 lines
8.1 KiB
Python

"""HTML 车辆卡片的真实汇总口径,使用 SQLite 验证查询与返回模型。"""
from datetime import date
from decimal import Decimal
from types import SimpleNamespace
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from app.models.pl_models import PlVehicle, PlVehicleFuelRegister, PlPersonnel, PlVehiclePersonnel
from app.services import pl_vehicle_fuel_service as service
@pytest.fixture
def summary_db(monkeypatch):
class FixedDate(date):
@classmethod
def today(cls):
return cls(2026, 1, 26)
monkeypatch.setattr(service, "date", FixedDate)
engine = create_engine("sqlite://")
PlVehicle.__table__.create(engine)
PlVehicleFuelRegister.__table__.create(engine)
PlPersonnel.__table__.create(engine)
PlVehiclePersonnel.__table__.create(engine)
with Session(engine) as db:
yield db
engine.dispose()
def vehicle(db, plate="皖H57837", **kwargs):
values = dict(id=plate, license_plate=plate, vehicle_type="self", tenant_no="t1",
vehicle_status="available", create_by="u1")
values.update(kwargs)
db.add(PlVehicle(**values))
def fuel(db, day, liters, km, **kwargs):
key = str(db.query(PlVehicleFuelRegister).count())
values = dict(id=key, register_no=key, license_plate="皖H57837", tenant_no="t1",
fuel_category="gas_station", register_date=date.fromisoformat(day),
registrant_id="u1", fuel_liters=Decimal(str(liters)),
odometer_km=Decimal(str(km)), create_by="u1")
values.update(kwargs)
db.add(PlVehicleFuelRegister(**values))
db.flush()
def summary(db):
db.flush()
user = SimpleNamespace(to_user_info=lambda: {"role": "super_admin"})
return service.list_plate_month_summary(db, "t1", user)
def test_summary_includes_html_metrics_and_previous_quarter_across_year(summary_db):
db = summary_db
vehicle(db)
fuel(db, "2025-09-30", 10, 1000)
fuel(db, "2025-10-15", 100, 1500)
fuel(db, "2025-11-30", 100, 2000)
fuel(db, "2025-12-31", 200, 3000)
fuel(db, "2026-01-10", 120, 3400)
fuel(db, "2026-01-20", 180, 4000)
result = summary(db)
item = result.items[0].model_dump(by_alias=True)
assert (result.month, result.quarter) == ("2026-01", "2026-Q1")
assert item["liters"] == Decimal("300")
assert item["prevLiters"] == Decimal("200")
assert item["runKm"] == Decimal("600")
assert item["prevRunKm"] == Decimal("400")
assert item["monthRateL100"] == Decimal("50")
assert item["quarterRateL100"] == Decimal("50")
assert item["prevMonthRateL100"] == Decimal("50")
assert item["prevQuarterRateL100"] == Decimal("21.05")
def test_all_self_vehicles_visible_even_without_calculable_rate(summary_db):
db = summary_db
vehicle(db)
vehicle(db, "无记录车辆", vehicle_status="maintaining")
vehicle(db, "承运商车辆", vehicle_type="carrier")
vehicle(db, "其他租户", tenant_no="t2")
vehicle(db, "删除车辆", del_flag=1)
vehicle(db, "叉车")
vehicle(db, " 油桶 ")
fuel(db, "2026-01-10", 120, 3400)
items = {item.license_plate: item.model_dump(by_alias=True) for item in summary(db).items}
assert set(items) == {"皖H57837", "无记录车辆"}
assert items["皖H57837"]["liters"] == 120
assert items["皖H57837"]["monthRateL100"] is None
assert items["无记录车辆"]["liters"] == 0
assert items["无记录车辆"]["runKm"] is None
assert items["无记录车辆"]["monthMileageStatus"] == "insufficient"
def test_missing_boundary_month_falls_back_inside_period(summary_db):
db = summary_db
vehicle(db)
fuel(db, "2025-08-30", 10, 100)
# 缺失 9 月,不得把 8 月的里程当作上季度起点。
fuel(db, "2025-10-15", 100, 1000)
fuel(db, "2025-12-31", 100, 2000)
fuel(db, "2026-01-20", 100, 2500)
item = summary(db).items[0].model_dump(by_alias=True)
assert item["prevQuarterRateL100"] == Decimal("13.33")
# 上月只有一笔,但本月首笔可作为终点。
assert item["prevMonthRateL100"] == 20
def test_summary_excludes_invalid_records_and_applies_data_scope(summary_db, monkeypatch):
db = summary_db
vehicle(db)
vehicle(db, "权限外车辆", create_by="u2")
fuel(db, "2025-12-31", 100, 1000)
fuel(db, "2026-01-10", 100, 1500)
fuel(db, "2026-01-11", 900, 1600, status="cancelled")
fuel(db, "2026-01-12", 900, 1700, del_flag=1)
fuel(db, "2026-01-13", 900, 1800, tenant_no="t2")
fuel(db, "2026-01-14", 900, 1900, create_by="u2")
scoped_models = []
def scope(query, model, *args):
scoped_models.append(model)
return query.filter(model.create_by == "u1")
monkeypatch.setattr(service, "apply_pl_scope", scope)
items = summary(db).items
assert len(items) == 1
assert items[0].model_dump(by_alias=True)["liters"] == 100
assert items[0].month_rate_l100 is None
assert set(scoped_models) == {PlVehicle, PlVehicleFuelRegister}
def test_next_quarter_reading_is_loaded_without_adding_next_period_fuel(summary_db, monkeypatch):
class DecemberDate(date):
@classmethod
def today(cls):
return cls(2025, 12, 26)
monkeypatch.setattr(service, "date", DecemberDate)
db = summary_db
vehicle(db)
fuel(db, "2025-10-05", 100, 1000)
fuel(db, "2025-11-05", 100, 1500)
fuel(db, "2025-12-05", 100, 2000)
fuel(db, "2025-12-20", 200, 2500)
fuel(db, "2026-01-01", 999, 2700, status="cancelled")
fuel(db, "2026-01-02", 999, 2800, del_flag=1)
fuel(db, "2026-01-03", 999, 2900, tenant_no="t2")
fuel(db, "2026-01-05", 900, 3000)
fuel(db, "2026-01-10", 900, 3500)
item = summary(db).items[0].model_dump(by_alias=True)
assert item["liters"] == 300
assert item["runKm"] == 1000
assert item["monthRateL100"] == 30
assert item["quarterRateL100"] == 25
assert item["last3MonthsRateL100"] == 25
assert item["monthMileageStatus"] == "finalized"
assert item["quarterMileageStatus"] == "finalized"
def test_current_month_and_quarter_are_estimated_until_next_period(summary_db):
db = summary_db
vehicle(db)
fuel(db, "2026-01-01", 100, 1000)
fuel(db, "2026-01-20", 200, 1500)
item = summary(db).items[0].model_dump(by_alias=True)
assert item["runKm"] == 500
assert item["monthMileageStatus"] == "estimated"
assert item["quarterMileageStatus"] == "estimated"
fuel(db, "2026-02-02", 999, 2000)
item = summary(db).items[0].model_dump(by_alias=True)
assert item["runKm"] == 1000
assert item["monthMileageStatus"] == "finalized"
assert item["quarterMileageStatus"] == "estimated"
def test_rollback_exposes_status_and_no_rate(summary_db):
db = summary_db
vehicle(db)
fuel(db, "2026-01-01", 100, 1000)
fuel(db, "2026-01-20", 200, 900)
item = summary(db).items[0].model_dump(by_alias=True)
assert item["runKm"] is None
assert item["monthMileageStatus"] == "abnormal"
assert item["monthRateL100"] is None
assert item["quarterRateL100"] is None
def test_driver_name_uses_current_vehicle_personnel_association(summary_db):
db = summary_db
vehicle(db, default_driver_name="旧司机姓名")
vehicle(db, "未配置司机")
db.add_all([
PlPersonnel(id="driver", personnel_name="当前司机", tenant_no="t1"),
PlPersonnel(id="escort", personnel_name="押运员", tenant_no="t1"),
PlPersonnel(id="deleted", personnel_name="已删除司机", tenant_no="t1", del_flag=1),
PlVehiclePersonnel(id="driver-link", vehicle_id="皖H57837", personnel_id="driver",
role="driver", is_default=1, tenant_no="t1"),
PlVehiclePersonnel(id="escort-link", vehicle_id="皖H57837", personnel_id="escort",
role="escort", is_default=1, tenant_no="t1"),
PlVehiclePersonnel(id="deleted-link", vehicle_id="皖H57837", personnel_id="deleted",
role="driver", is_default=1, tenant_no="t1"),
])
items = {item.license_plate: item for item in summary(db).items}
assert items["皖H57837"].default_driver_name == "当前司机"
assert items["未配置司机"].default_driver_name is None