Files
shanxia2.0/backend/app/services/pl_vehicle_fuel_service.py
T
2026-09-18 12:01:13 +08:00

342 lines
11 KiB
Python

"""车辆加油登记业务逻辑"""
from __future__ import annotations
from datetime import date, datetime
from typing import Optional
from fastapi import HTTPException
from sqlalchemy import and_, func, or_
from sqlalchemy.orm import Session
from app.core.db_auth import DbUserContext
from app.core.utils import generate_uuid
from app.models.models import SysUser
from app.models.pl_models import PlVehicle, PlVehicleFuelRegister
from app.schemas.pl.vehicle_fuel import (
VehicleFuelRegisterCreate,
VehicleFuelRegisterRead,
VehicleFuelRegisterUpdate,
calc_fuel_tons,
)
from app.services.pl_data_scope import apply_pl_scope, assert_pl_row_in_scope
def _generate_register_no(db: Session) -> str:
prefix = f"SXJY{date.today():%Y%m%d}"
last = (
db.query(PlVehicleFuelRegister)
.filter(PlVehicleFuelRegister.register_no.like(f"{prefix}%"))
.order_by(PlVehicleFuelRegister.register_no.desc())
.first()
)
seq = int(last.register_no[-6:]) + 1 if last else 1
return f"{prefix}{seq:06d}"
def _resolve_user_name(db: Session, user_id: Optional[str]) -> Optional[str]:
if not user_id:
return None
row = (
db.query(SysUser)
.filter(SysUser.id == user_id, SysUser.del_flag == 0)
.first()
)
return row.nick_name or row.user_no if row else None
def _assert_vehicle_exists(db: Session, tenant_no: str, license_plate: str) -> None:
exists = (
db.query(PlVehicle.id)
.filter(
PlVehicle.tenant_no == tenant_no,
PlVehicle.license_plate == license_plate,
PlVehicle.del_flag == 0,
)
.first()
)
if not exists:
raise HTTPException(status_code=400, detail="车牌号不存在")
def _resolve_registrant_id(
db: Session,
tenant_no: str,
registrant_id: Optional[str],
user: DbUserContext,
*,
force_self: bool = False,
) -> str:
target = user.id if force_self else ((registrant_id or "").strip() or user.id)
row = (
db.query(SysUser.id)
.filter(
SysUser.id == target,
SysUser.tenant_no == tenant_no,
SysUser.del_flag == 0,
SysUser.status == "1",
)
.first()
)
if not row:
raise HTTPException(status_code=400, detail="登记人不存在或已停用")
return target
def _get_row(
db: Session, tenant_no: str, register_id: str, user: DbUserContext
) -> PlVehicleFuelRegister:
row = (
db.query(PlVehicleFuelRegister)
.filter(
PlVehicleFuelRegister.id == register_id,
PlVehicleFuelRegister.tenant_no == tenant_no,
PlVehicleFuelRegister.del_flag == 0,
)
.first()
)
if not row:
raise HTTPException(status_code=404, detail="加油登记不存在")
assert_pl_row_in_scope(db, row, PlVehicleFuelRegister, user, tenant_no)
return row
def _compute_seq_no(db: Session, tenant_no: str, row: PlVehicleFuelRegister) -> int:
"""租户内含软删:按 create_time ASC, id ASC 的 1-based 序号。"""
count = (
db.query(func.count(PlVehicleFuelRegister.id))
.filter(
PlVehicleFuelRegister.tenant_no == tenant_no,
or_(
PlVehicleFuelRegister.create_time < row.create_time,
and_(
PlVehicleFuelRegister.create_time == row.create_time,
PlVehicleFuelRegister.id <= row.id,
),
),
)
.scalar()
)
return int(count or 0)
def _build_seq_no_map(db: Session, tenant_no: str) -> dict[str, int]:
"""一次拉取租户内全部 id(含软删)映射序号,供列表避免 N+1。"""
rows = (
db.query(PlVehicleFuelRegister.id)
.filter(PlVehicleFuelRegister.tenant_no == tenant_no)
.order_by(
PlVehicleFuelRegister.create_time.asc(),
PlVehicleFuelRegister.id.asc(),
)
.all()
)
return {row_id: idx + 1 for idx, (row_id,) in enumerate(rows)}
def to_read(
row: PlVehicleFuelRegister,
db: Session,
*,
seq_no: Optional[int] = None,
) -> VehicleFuelRegisterRead:
resolved = seq_no if seq_no is not None else _compute_seq_no(db, row.tenant_no, row)
return VehicleFuelRegisterRead(
id=row.id,
seqNo=resolved,
registerNo=row.register_no,
fuelCategory=row.fuel_category,
registerDate=row.register_date,
registrantId=row.registrant_id,
registrantName=_resolve_user_name(db, row.registrant_id),
licensePlate=row.license_plate,
odometerKm=row.odometer_km,
fuelLiters=row.fuel_liters,
fuelTons=row.fuel_tons if row.fuel_tons is not None else calc_fuel_tons(row.fuel_liters),
attachmentUrls=row.attachment_urls,
status=row.status,
remark=row.remark,
createTime=row.create_time,
updateTime=row.update_time,
)
def list_registers(
db: Session,
tenant_no: str,
user: DbUserContext,
*,
q: Optional[str] = None,
register_no: Optional[str] = None,
license_plate: Optional[str] = None,
fuel_category: Optional[str] = None,
fuel_category_in: Optional[str] = None,
status: Optional[str] = None,
status_in: Optional[str] = None,
register_date_from: Optional[date] = None,
register_date_to: Optional[date] = None,
registrant_id: Optional[str] = None,
mine_only: bool = False,
ids: Optional[list[str]] = None,
page: int = 1,
page_size: int = 20,
max_page_size: Optional[int] = None,
) -> tuple[list[VehicleFuelRegisterRead], int]:
query = db.query(PlVehicleFuelRegister).filter(
PlVehicleFuelRegister.tenant_no == tenant_no,
PlVehicleFuelRegister.del_flag == 0,
)
if mine_only:
query = query.filter(PlVehicleFuelRegister.registrant_id == user.id)
elif registrant_id:
query = query.filter(PlVehicleFuelRegister.registrant_id == registrant_id)
if q:
query = query.filter(
or_(
PlVehicleFuelRegister.register_no.contains(q),
PlVehicleFuelRegister.license_plate.contains(q),
)
)
if register_no:
query = query.filter(PlVehicleFuelRegister.register_no.contains(register_no))
if license_plate:
query = query.filter(PlVehicleFuelRegister.license_plate.contains(license_plate))
if fuel_category:
query = query.filter(PlVehicleFuelRegister.fuel_category == fuel_category)
if fuel_category_in:
values = [v.strip() for v in str(fuel_category_in).split(",") if v.strip()]
if values:
query = query.filter(PlVehicleFuelRegister.fuel_category.in_(values))
if status:
query = query.filter(PlVehicleFuelRegister.status == status)
if status_in:
values = [v.strip() for v in str(status_in).split(",") if v.strip()]
if values:
query = query.filter(PlVehicleFuelRegister.status.in_(values))
if register_date_from:
query = query.filter(PlVehicleFuelRegister.register_date >= register_date_from)
if register_date_to:
query = query.filter(PlVehicleFuelRegister.register_date <= register_date_to)
if ids:
query = query.filter(PlVehicleFuelRegister.id.in_(ids))
query = apply_pl_scope(query, PlVehicleFuelRegister, user, db, tenant_no)
total = query.count()
page = max(1, page)
cap = int(max_page_size) if max_page_size else 500
page_size = min(max(1, page_size), cap)
rows = (
# 与 seqNo 倒序一致:序号大的在前(create_time ASC,id ASC → seqNo;列表取反)
query.order_by(
PlVehicleFuelRegister.create_time.desc(),
PlVehicleFuelRegister.id.desc(),
)
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
seq_map = _build_seq_no_map(db, tenant_no)
return [to_read(row, db, seq_no=seq_map.get(row.id)) for row in rows], total
def get_register(
db: Session, tenant_no: str, user: DbUserContext, register_id: str
) -> VehicleFuelRegisterRead:
return to_read(_get_row(db, tenant_no, register_id, user), db)
def create_register(
db: Session,
tenant_no: str,
user: DbUserContext,
payload: VehicleFuelRegisterCreate,
*,
force_self: bool = False,
) -> VehicleFuelRegisterRead:
license_plate = payload.license_plate.strip()
_assert_vehicle_exists(db, tenant_no, license_plate)
registrant_id = _resolve_registrant_id(
db, tenant_no, payload.registrant_id, user, force_self=force_self
)
now = datetime.now()
row = PlVehicleFuelRegister(
id=generate_uuid(),
register_no=_generate_register_no(db),
fuel_category=payload.fuel_category,
register_date=payload.register_date,
registrant_id=registrant_id,
license_plate=license_plate,
odometer_km=payload.odometer_km,
fuel_liters=payload.fuel_liters,
fuel_tons=calc_fuel_tons(payload.fuel_liters),
attachment_urls=payload.attachment_urls,
status="normal",
tenant_no=tenant_no,
remark=payload.remark,
create_by=user.id,
create_dept_id=user.dept_id or "",
create_time=now,
update_by=user.id,
update_time=now,
del_flag=0,
)
db.add(row)
db.commit()
db.refresh(row)
return to_read(row, db)
def update_register(
db: Session,
tenant_no: str,
user: DbUserContext,
register_id: str,
payload: VehicleFuelRegisterUpdate,
*,
force_self: bool = False,
) -> VehicleFuelRegisterRead:
row = _get_row(db, tenant_no, register_id, user)
if row.status != "normal":
raise HTTPException(status_code=400, detail="仅正常状态的登记可编辑")
if force_self and row.registrant_id != user.id:
raise HTTPException(status_code=403, detail="只能编辑本人登记")
license_plate = payload.license_plate.strip()
_assert_vehicle_exists(db, tenant_no, license_plate)
row.fuel_category = payload.fuel_category
row.register_date = payload.register_date
row.registrant_id = _resolve_registrant_id(
db, tenant_no, payload.registrant_id, user, force_self=force_self
)
row.license_plate = license_plate
row.odometer_km = payload.odometer_km
row.fuel_liters = payload.fuel_liters
row.fuel_tons = calc_fuel_tons(payload.fuel_liters)
row.attachment_urls = payload.attachment_urls
row.remark = payload.remark
row.update_by = user.id
row.update_time = datetime.now()
db.commit()
db.refresh(row)
return to_read(row, db)
def cancel_register(
db: Session,
tenant_no: str,
user: DbUserContext,
register_id: str,
*,
force_self: bool = False,
) -> VehicleFuelRegisterRead:
row = _get_row(db, tenant_no, register_id, user)
if row.status != "normal":
raise HTTPException(status_code=400, detail="仅正常状态的登记可作废")
if force_self and row.registrant_id != user.id:
raise HTTPException(status_code=403, detail="只能作废本人登记")
row.status = "cancelled"
row.update_by = user.id
row.update_time = datetime.now()
db.commit()
db.refresh(row)
return to_read(row, db)