pl_dispatch_calendar_service.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. """手工派车 CRUD:租户、角色数据范围和关联主数据校验。"""
  2. import re
  3. from datetime import date
  4. from typing import Optional
  5. from fastapi import HTTPException
  6. from sqlalchemy.orm import Session
  7. from app.core.db_auth import DbUserContext
  8. from app.core.utils import generate_uuid
  9. from app.models.crm_models import CrmProduct
  10. from app.models.pl_dispatch_calendar import PlDispatchCalendar
  11. from app.models.pl_models import PlVehicle
  12. from app.schemas.pl.dispatch_calendar import CreateBody, UpdateBody
  13. from app.services.pl_data_scope import apply_pl_scope
  14. CALENDAR_KINDS = ("acid", "alkali", "other")
  15. def _calendar_kind(kind: Optional[str]) -> str:
  16. value = kind or "acid"
  17. if value not in CALENDAR_KINDS:
  18. raise HTTPException(status_code=422, detail="日历类型须为 acid、alkali 或 other")
  19. return value
  20. def _row_to_dict(row: PlDispatchCalendar) -> dict:
  21. return {
  22. "id": row.id,
  23. "dispatchDate": row.dispatch_date,
  24. "vehicleId": row.vehicle_id,
  25. "licensePlate": row.license_plate,
  26. "productId": row.product_id,
  27. "productName": row.product_name,
  28. "origin": row.origin,
  29. "destination": row.destination,
  30. "quantity": row.quantity,
  31. "calendarKind": row.calendar_kind or "acid",
  32. "remark": row.remark,
  33. "createTime": row.create_time,
  34. "updateTime": row.update_time,
  35. }
  36. def _query(db: Session, tenant_no: str, user: DbUserContext, model=PlDispatchCalendar):
  37. query = db.query(model).filter(model.tenant_no == tenant_no, model.del_flag == 0)
  38. return apply_pl_scope(query, model, user, db, tenant_no)
  39. def _month_bounds(month: str):
  40. if not re.fullmatch(r"[0-9]{4}-[0-9]{2}", month or ""):
  41. raise HTTPException(status_code=422, detail="月份须为 YYYY-MM")
  42. year, number = map(int, month.split("-"))
  43. if not 1900 <= year <= 2100 or not 1 <= number <= 12:
  44. raise HTTPException(status_code=422, detail="月份须在 1900-01 至 2100-12 范围内")
  45. return date(year, number, 1), date(year + (number == 12), number % 12 + 1, 1)
  46. def list_records(db: Session, tenant_no: str, user: DbUserContext, *, month: str,
  47. vehicle_id: Optional[str] = None, calendar_kind: Optional[str] = None,
  48. page: int = 1, page_size: int = 500) -> dict:
  49. start, end = _month_bounds(month)
  50. query = _query(db, tenant_no, user).filter(
  51. PlDispatchCalendar.dispatch_date >= start, PlDispatchCalendar.dispatch_date < end,
  52. PlDispatchCalendar.calendar_kind == _calendar_kind(calendar_kind),
  53. )
  54. if vehicle_id:
  55. query = query.filter(PlDispatchCalendar.vehicle_id == vehicle_id)
  56. total = query.count()
  57. rows = query.order_by(PlDispatchCalendar.create_time.desc(), PlDispatchCalendar.id.desc()).offset(
  58. (page - 1) * page_size
  59. ).limit(page_size).all()
  60. return {"list": [_row_to_dict(row) for row in rows], "total": total}
  61. def get_record(db: Session, tenant_no: str, user: DbUserContext, record_id: str) -> PlDispatchCalendar:
  62. row = _query(db, tenant_no, user).filter(PlDispatchCalendar.id == record_id).first()
  63. if row is None:
  64. raise HTTPException(status_code=404, detail="派车记录不存在或无权访问")
  65. return row
  66. def _resolve_snapshots(db: Session, tenant_no: str, user: DbUserContext, body: CreateBody) -> dict:
  67. vehicle = _query(db, tenant_no, user, PlVehicle).filter(PlVehicle.id == body.vehicle_id).first()
  68. if vehicle is None:
  69. raise HTTPException(status_code=422, detail="车辆不存在、已删除或无权访问,请重新选择")
  70. product = (
  71. _query(db, tenant_no, user, CrmProduct)
  72. .filter(CrmProduct.id == body.product_id, CrmProduct.status == "on")
  73. .first()
  74. )
  75. if product is None:
  76. raise HTTPException(status_code=422, detail="产品不存在、已下架、已删除或无权访问,请重新选择")
  77. return {"license_plate": vehicle.license_plate, "product_name": product.product_name}
  78. def create_record(db: Session, tenant_no: str, user: DbUserContext, body: CreateBody) -> PlDispatchCalendar:
  79. snapshots = _resolve_snapshots(db, tenant_no, user, body)
  80. row = PlDispatchCalendar(
  81. id=generate_uuid(), tenant_no=tenant_no, create_by=user.id,
  82. create_dept_id=user.dept_id or "", update_by=user.id,
  83. **body.model_dump(), **snapshots,
  84. )
  85. db.add(row)
  86. db.commit()
  87. db.refresh(row)
  88. return row
  89. def update_record(db: Session, tenant_no: str, user: DbUserContext, record_id: str,
  90. body: UpdateBody) -> PlDispatchCalendar:
  91. row = get_record(db, tenant_no, user, record_id)
  92. if body.calendar_kind != (row.calendar_kind or "acid"):
  93. raise HTTPException(status_code=422, detail="不能把派车计划改到另一本日历")
  94. values = {**body.model_dump(), **_resolve_snapshots(db, tenant_no, user, body)}
  95. for key, value in values.items():
  96. setattr(row, key, value)
  97. row.update_by = user.id
  98. db.commit()
  99. db.refresh(row)
  100. return row
  101. def delete_record(db: Session, tenant_no: str, user: DbUserContext, record_id: str) -> int:
  102. row = get_record(db, tenant_no, user, record_id)
  103. row.del_flag = 1
  104. row.update_by = user.id
  105. db.commit()
  106. return 1
  107. def _list_options(db: Session, tenant_no: str, user: DbUserContext, model, name_field,
  108. alias: str, q: Optional[str], page: int, page_size: int) -> dict:
  109. query = _query(db, tenant_no, user, model)
  110. keyword = (q or "").strip()
  111. if keyword:
  112. query = query.filter(name_field.contains(keyword, autoescape=True))
  113. total = query.count()
  114. rows = query.order_by(model.create_time.desc(), model.id.desc()).offset(
  115. (page - 1) * page_size
  116. ).limit(page_size).all()
  117. return {"list": [{"id": row.id, alias: getattr(row, name_field.key)} for row in rows], "total": total}
  118. def list_vehicles(db: Session, tenant_no: str, user: DbUserContext, *, q: Optional[str] = None,
  119. page: int = 1, page_size: int = 500) -> dict:
  120. return _list_options(db, tenant_no, user, PlVehicle, PlVehicle.license_plate,
  121. "licensePlate", q, page, page_size)
  122. def list_products(db: Session, tenant_no: str, user: DbUserContext, *, q: Optional[str] = None,
  123. page: int = 1, page_size: int = 500) -> dict:
  124. query = _query(db, tenant_no, user, CrmProduct).filter(CrmProduct.status == "on")
  125. keyword = (q or "").strip()
  126. if keyword:
  127. query = query.filter(CrmProduct.product_name.contains(keyword, autoescape=True))
  128. total = query.count()
  129. rows = query.order_by(CrmProduct.create_time.desc(), CrmProduct.id.desc()).offset(
  130. (page - 1) * page_size
  131. ).limit(page_size).all()
  132. return {"list": [{"id": row.id, "productName": row.product_name} for row in rows], "total": total}