pl_vehicle_fuel_card_service.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. """车油卡台账 CRUD,租户隔离、数据范围与软删除。"""
  2. from __future__ import annotations
  3. from typing import List, Optional
  4. from fastapi import HTTPException
  5. from sqlalchemy import or_
  6. from sqlalchemy.exc import IntegrityError
  7. from sqlalchemy.orm import Session
  8. from app.core.db_auth import DbUserContext
  9. from app.core.utils import generate_uuid
  10. from app.models.pl_vehicle_fuel_card import PlVehicleFuelCard
  11. from app.schemas.pl.vehicle_fuel_card import CreateBody, UpdateBody
  12. from app.services.pl_data_scope import apply_pl_scope
  13. from app.services.pl_vehicle_mine_vehicles import (
  14. license_plates_for_user_personnel,
  15. should_restrict_mine_vehicles,
  16. )
  17. def _row_to_dict(row: PlVehicleFuelCard) -> dict:
  18. return {
  19. "id": row.id,
  20. "licensePlate": row.license_plate,
  21. "cardNo": row.card_no,
  22. "remark": row.remark,
  23. "createTime": row.create_time,
  24. "updateTime": row.update_time,
  25. }
  26. def _query(db: Session, tenant_no: str, user: DbUserContext):
  27. query = db.query(PlVehicleFuelCard).filter(
  28. PlVehicleFuelCard.tenant_no == tenant_no,
  29. PlVehicleFuelCard.del_flag == 0,
  30. )
  31. return apply_pl_scope(query, PlVehicleFuelCard, user, db, tenant_no)
  32. def list_cards(db: Session, tenant_no: str, user: DbUserContext, *, q: Optional[str] = None,
  33. page: int = 1, page_size: int = 20) -> dict:
  34. query = _query(db, tenant_no, user)
  35. keyword = (q or "").strip()
  36. if keyword:
  37. query = query.filter(or_(
  38. PlVehicleFuelCard.license_plate.contains(keyword, autoescape=True),
  39. PlVehicleFuelCard.card_no.contains(keyword, autoescape=True),
  40. PlVehicleFuelCard.remark.contains(keyword, autoescape=True),
  41. ))
  42. total = query.count()
  43. rows = query.order_by(PlVehicleFuelCard.create_time.desc(), PlVehicleFuelCard.id.desc()).offset(
  44. (page - 1) * page_size
  45. ).limit(page_size).all()
  46. return {"list": [_row_to_dict(row) for row in rows], "total": total}
  47. def get_card(db: Session, tenant_no: str, user: DbUserContext, record_id: str) -> PlVehicleFuelCard:
  48. row = _query(db, tenant_no, user).filter(PlVehicleFuelCard.id == record_id).first()
  49. if not row:
  50. raise HTTPException(status_code=404, detail="车油卡信息不存在或无权访问")
  51. return row
  52. def _commit_binding(db: Session) -> None:
  53. try:
  54. db.commit()
  55. except IntegrityError:
  56. db.rollback()
  57. raise HTTPException(status_code=409, detail="该车牌或油卡卡号已存在有效绑定,请检查后重试")
  58. def create_card(db: Session, tenant_no: str, user: DbUserContext, body: CreateBody) -> PlVehicleFuelCard:
  59. row = PlVehicleFuelCard(
  60. id=generate_uuid(), tenant_no=tenant_no, create_by=user.id,
  61. create_dept_id=user.dept_id or "", update_by=user.id,
  62. **body.model_dump(),
  63. )
  64. db.add(row)
  65. _commit_binding(db)
  66. db.refresh(row)
  67. return row
  68. def update_card(db: Session, tenant_no: str, user: DbUserContext, record_id: str,
  69. body: UpdateBody) -> PlVehicleFuelCard:
  70. row = get_card(db, tenant_no, user, record_id)
  71. for key, value in body.model_dump().items():
  72. setattr(row, key, value)
  73. row.update_by = user.id
  74. _commit_binding(db)
  75. db.refresh(row)
  76. return row
  77. def delete_cards(db: Session, tenant_no: str, user: DbUserContext, ids: List[str]) -> int:
  78. unique_ids = set(ids)
  79. rows = _query(db, tenant_no, user).filter(PlVehicleFuelCard.id.in_(unique_ids)).with_for_update().all()
  80. if len(rows) != len(unique_ids):
  81. raise HTTPException(status_code=404, detail="部分车油卡信息不存在或无权访问,未删除任何记录")
  82. for row in rows:
  83. row.del_flag = 1
  84. row.update_by = user.id
  85. db.commit()
  86. return len(rows)
  87. def lookup_for_recharge(db: Session, tenant_no: str, user: DbUserContext, license_plate: str,
  88. *, mine_vehicles: bool = False) -> dict:
  89. """充值表单读取关联主数据:权限取决于车辆/单据访问权,而非台账创建人。
  90. 不开放台账列表或管理权限;精确车牌查询只返回卡号,不返回备注等资料。
  91. 小程序继续遵循本人挂靠车辆与管理员放宽规则。
  92. """
  93. from app.models.pl_models import PlVehicle, PlVehicleRechargeApply, PlVehicleRechargeRecord
  94. plate = license_plate.strip().upper()
  95. if should_restrict_mine_vehicles(db, tenant_no, user.id, mine_vehicles=mine_vehicles):
  96. permitted = plate in license_plates_for_user_personnel(db, tenant_no, user.id)
  97. elif mine_vehicles or user.role == "super_admin":
  98. # mineVehicles=true 能到达这里,表示 helper 已核实管理员角色;与充值列表一致。
  99. permitted = True
  100. else:
  101. permitted = False
  102. # 历史单据的车牌可能已从车辆档案移除,仍可读取其关联卡号。
  103. for model in (PlVehicle, PlVehicleRechargeApply, PlVehicleRechargeRecord):
  104. query = db.query(model).filter(
  105. model.tenant_no == tenant_no, model.del_flag == 0, model.license_plate == plate
  106. )
  107. if apply_pl_scope(query, model, user, db, tenant_no).first() is not None:
  108. permitted = True
  109. break
  110. if not permitted:
  111. raise HTTPException(status_code=403, detail="无权查询该车辆的油卡信息")
  112. row = db.query(PlVehicleFuelCard.card_no).filter(
  113. PlVehicleFuelCard.tenant_no == tenant_no,
  114. PlVehicleFuelCard.del_flag == 0,
  115. PlVehicleFuelCard.license_plate == plate,
  116. ).first()
  117. return {"licensePlate": plate, "cardNo": row[0] if row else None}
  118. def attach_recharge_list_bindings(db: Session, tenant_no: str, items: list) -> list:
  119. """为已完成业务权限过滤的当前页补充绑定卡号,单次查询且不覆盖历史发放卡号。"""
  120. plates = {item.license_plate for item in items if item.recharge_type == "fuel"}
  121. cards = dict(db.query(PlVehicleFuelCard.license_plate, PlVehicleFuelCard.card_no).filter(
  122. PlVehicleFuelCard.tenant_no == tenant_no,
  123. PlVehicleFuelCard.del_flag == 0,
  124. PlVehicleFuelCard.license_plate.in_(plates),
  125. ).all()) if plates else {}
  126. for item in items:
  127. item.bound_fuel_card_no = cards.get(item.license_plate) if item.recharge_type == "fuel" else None
  128. return items