purchase_cert.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. """易制毒购买证管理 API"""
  2. from typing import Any, Literal, Optional
  3. from fastapi import APIRouter, Depends, HTTPException, Query
  4. from pydantic import BaseModel, Field
  5. from sqlalchemy.orm import Session
  6. from app.core.database import get_db
  7. from app.core.db_auth import DbUserContext, get_db_current_user
  8. from app.core.tenant_context import get_tenant_no
  9. from app.models.pl_models import PlPlan, PlPlanProduct
  10. from app.schemas.pl.purchase_cert import (
  11. PlanProductCertLinksSaveBody,
  12. PurchaseCertCreate,
  13. PurchaseCertUpdate,
  14. TransportCertBody,
  15. )
  16. from app.services import pl_purchase_cert_service as svc
  17. from app.services.pl_plan_service import confirm_toxic_plan_effective
  18. from app.services.pl_purchase_cert_ocr_service import recognize_hazard_cert
  19. router = APIRouter(prefix="/purchase-cert", tags=["运输计划-购买证管理"])
  20. def _dept_id(user: DbUserContext) -> str:
  21. return getattr(user, "dept_id", None) or getattr(user, "create_dept_id", None) or ""
  22. class RecognizeHazardCertBody(BaseModel):
  23. kind: Literal["purchase", "transport"] = Field(..., description="purchase-购买证 transport-运输证")
  24. attachment_url: str = Field(..., alias="attachmentUrl")
  25. model_config = {"populate_by_name": True}
  26. @router.post("/recognize")
  27. def recognize_cert_image(
  28. body: RecognizeHazardCertBody,
  29. user: DbUserContext = Depends(get_db_current_user),
  30. ) -> dict[str, Any]:
  31. """DeepSeek 视觉识别购买证 / 运输证并返回可回填字段。"""
  32. _ = user
  33. return recognize_hazard_cert(kind=body.kind, attachment_url=body.attachment_url)
  34. @router.get("/stats")
  35. def purchase_cert_stats(
  36. db: Session = Depends(get_db),
  37. user: DbUserContext = Depends(get_db_current_user),
  38. ) -> dict[str, Any]:
  39. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  40. return svc.get_dashboard_stats(db, tenant_no=tenant_no)
  41. @router.get("/pending-plans")
  42. def pending_toxic_plans(
  43. db: Session = Depends(get_db),
  44. user: DbUserContext = Depends(get_db_current_user),
  45. ) -> dict[str, Any]:
  46. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  47. items = svc.list_pending_toxic_plans(db, tenant_no=tenant_no)
  48. return {"items": items, "list": items, "total": len(items)}
  49. @router.get("/")
  50. def list_purchase_certs(
  51. q: Optional[str] = Query(default=None),
  52. status: Optional[str] = Query(default=None),
  53. page: int = Query(default=1, ge=1),
  54. page_size: int = Query(default=20, ge=1, le=200, alias="pageSize"),
  55. db: Session = Depends(get_db),
  56. user: DbUserContext = Depends(get_db_current_user),
  57. ) -> dict[str, Any]:
  58. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  59. return svc.list_purchase_certs(
  60. db,
  61. tenant_no=tenant_no,
  62. q=q,
  63. status=status,
  64. page=page,
  65. page_size=page_size,
  66. )
  67. @router.get("/{cert_id}")
  68. def get_purchase_cert(
  69. cert_id: str,
  70. db: Session = Depends(get_db),
  71. user: DbUserContext = Depends(get_db_current_user),
  72. ) -> dict[str, Any]:
  73. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  74. return svc.get_purchase_cert_detail(db, tenant_no=tenant_no, cert_id=cert_id)
  75. @router.post("/")
  76. def create_purchase_cert(
  77. body: PurchaseCertCreate,
  78. db: Session = Depends(get_db),
  79. user: DbUserContext = Depends(get_db_current_user),
  80. ) -> dict[str, Any]:
  81. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  82. data = body.model_dump(exclude={"trans_list"}, exclude_none=True)
  83. transports = [t.model_dump(exclude_none=True) for t in (body.trans_list or [])]
  84. return svc.create_purchase_cert_with_transports(
  85. db,
  86. tenant_no=tenant_no,
  87. data=data,
  88. transports=transports,
  89. user_id=user.id,
  90. dept_id=_dept_id(user),
  91. )
  92. @router.put("/{cert_id}")
  93. def update_purchase_cert(
  94. cert_id: str,
  95. body: PurchaseCertUpdate,
  96. db: Session = Depends(get_db),
  97. user: DbUserContext = Depends(get_db_current_user),
  98. ) -> dict[str, Any]:
  99. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  100. data = body.model_dump(exclude_none=True)
  101. return svc.update_purchase_cert(
  102. db,
  103. tenant_no=tenant_no,
  104. cert_id=cert_id,
  105. data=data,
  106. user_id=user.id,
  107. )
  108. @router.delete("/{cert_id}")
  109. def delete_purchase_cert(
  110. cert_id: str,
  111. db: Session = Depends(get_db),
  112. user: DbUserContext = Depends(get_db_current_user),
  113. ) -> dict[str, str]:
  114. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  115. svc.soft_delete_purchase_cert(
  116. db, tenant_no=tenant_no, cert_id=cert_id, user_id=user.id
  117. )
  118. return {"message": "ok"}
  119. @router.post("/{cert_id}/transport-certs")
  120. def add_transport_cert(
  121. cert_id: str,
  122. body: TransportCertBody,
  123. db: Session = Depends(get_db),
  124. user: DbUserContext = Depends(get_db_current_user),
  125. ) -> dict[str, Any]:
  126. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  127. return svc.add_transport_cert(
  128. db,
  129. tenant_no=tenant_no,
  130. purchase_cert_id=cert_id,
  131. data=body.model_dump(exclude_none=True),
  132. user_id=user.id,
  133. dept_id=_dept_id(user),
  134. )
  135. @router.post("/links")
  136. def save_plan_product_links(
  137. body: PlanProductCertLinksSaveBody,
  138. db: Session = Depends(get_db),
  139. user: DbUserContext = Depends(get_db_current_user),
  140. ) -> dict[str, Any]:
  141. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  142. links = [lk.model_dump() for lk in body.links]
  143. items = svc.upsert_plan_product_links(
  144. db,
  145. tenant_no=tenant_no,
  146. plan_id=body.plan_id,
  147. links=links,
  148. user_id=user.id,
  149. dept_id=_dept_id(user),
  150. )
  151. return {"items": items, "total": len(items)}
  152. @router.post("/confirm/{plan_id}")
  153. def confirm_plan_with_certs(
  154. plan_id: str,
  155. db: Session = Depends(get_db),
  156. user: DbUserContext = Depends(get_db_current_user),
  157. ) -> dict[str, Any]:
  158. """校验关联 → 占用 → 确认生效。"""
  159. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  160. plan = (
  161. db.query(PlPlan)
  162. .filter(
  163. PlPlan.id == plan_id,
  164. PlPlan.tenant_no == tenant_no,
  165. PlPlan.del_flag == 0,
  166. )
  167. .first()
  168. )
  169. if not plan:
  170. raise HTTPException(status_code=404, detail="计划不存在")
  171. products = (
  172. db.query(PlPlanProduct)
  173. .filter(
  174. PlPlanProduct.plan_id == plan.id,
  175. PlPlanProduct.tenant_no == tenant_no,
  176. PlPlanProduct.del_flag == 0,
  177. )
  178. .order_by(PlPlanProduct.order_num)
  179. .all()
  180. )
  181. user_name = getattr(user, "nick_name", None) or getattr(user, "user_no", None)
  182. plan = confirm_toxic_plan_effective(
  183. db,
  184. tenant_no=tenant_no,
  185. plan=plan,
  186. products=products,
  187. user_id=user.id,
  188. user_name=user_name,
  189. dept_id=_dept_id(user),
  190. )
  191. return {
  192. "id": plan.id,
  193. "planNo": plan.plan_no,
  194. "followStatus": plan.follow_status,
  195. "hazardCertReady": int(plan.hazard_cert_ready or 0),
  196. }