presign_order.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. """预签约订单 / 意向金转账 / 转正式 API"""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from typing import Any, Dict, List, Optional
  5. from fastapi import APIRouter, Depends, Query
  6. from pydantic import BaseModel, ConfigDict, Field, field_validator
  7. from sqlalchemy.orm import Session
  8. from app.core.database import get_db
  9. from app.core.data_scope import get_user_dept_id
  10. from app.core.menu_guard import require_admin_or_super_admin, require_strict_menu_code
  11. from app.core.response import error_response, success_response
  12. from app.crud import presign_order as po_svc
  13. from app.models.models import SysUser
  14. router = APIRouter(prefix="/web/admin", tags=["细胞存储-预签约订单"])
  15. PAYMENT_CONFIRM_MENU = "biz:cellStoragePaymentConfirm:list"
  16. def _get_tenant_no(db: Session, user: dict) -> str:
  17. user_id = user.get("user_id")
  18. if not user_id:
  19. raise PermissionError("用户身份信息缺失,无法确定租户")
  20. sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
  21. if not sys_user or not sys_user.tenant_no:
  22. raise PermissionError("用户租户信息缺失")
  23. return sys_user.tenant_no
  24. def _get_current_user_id(user: dict) -> str:
  25. user_id = user.get("user_id")
  26. if not user_id:
  27. raise PermissionError("用户身份信息缺失")
  28. return str(user_id)
  29. class PresignOrderBody(BaseModel):
  30. model_config = ConfigDict(extra="ignore", populate_by_name=True)
  31. order_date: Optional[str] = Field(None, alias="orderDate")
  32. protocol_no: Optional[str] = Field(None, alias="protocolNo")
  33. mother_name: Optional[str] = Field(None, alias="motherName")
  34. expected_delivery_date: Optional[str] = Field(None, alias="expectedDeliveryDate")
  35. region_id: Optional[str] = Field(None, alias="regionId")
  36. hospital_id: Optional[str] = Field(None, alias="hospitalId")
  37. sample_collect_time: Optional[str] = Field(None, alias="sampleCollectTime")
  38. pay_mode: Optional[str] = Field(None, alias="payMode")
  39. contract_amount: Optional[float] = Field(None, alias="contractAmount")
  40. collector_user_id: Optional[str] = Field(None, alias="collectorUserId")
  41. sales_user_id: Optional[str] = Field(None, alias="salesUserId")
  42. region_manager_user_id: Optional[str] = Field(None, alias="regionManagerUserId")
  43. order_status: Optional[str] = Field(None, alias="orderStatus")
  44. remark: Optional[str] = None
  45. sample_type_ids: Optional[List[str]] = Field(None, alias="sampleTypeIds")
  46. transfers: Optional[List[Dict[str, Any]]] = None
  47. @field_validator("order_date", "expected_delivery_date")
  48. @classmethod
  49. def validate_ymd(cls, value: Optional[str]) -> Optional[str]:
  50. if value in (None, ""):
  51. return value
  52. text = value.strip()
  53. if len(text) != 8 or not text.isdigit():
  54. raise ValueError("日期必须使用 YYYYMMDD 格式")
  55. try:
  56. datetime.strptime(text, "%Y%m%d")
  57. except ValueError as exc:
  58. raise ValueError("日期必须是有效的 YYYYMMDD") from exc
  59. return text
  60. class PresignTransferBody(BaseModel):
  61. model_config = ConfigDict(extra="ignore", populate_by_name=True)
  62. transfer_amount: Optional[float] = Field(None, alias="transferAmount")
  63. transfer_time: Optional[str] = Field(None, alias="transferTime")
  64. transfer_type: Optional[str] = Field(None, alias="transferType")
  65. transfer_attachments: Optional[str] = Field(None, alias="transferAttachments")
  66. remark: Optional[str] = None
  67. class FinanceConfirmBody(BaseModel):
  68. model_config = ConfigDict(extra="ignore", populate_by_name=True)
  69. finance_confirm_attachments: Optional[str] = Field(
  70. None, alias="financeConfirmAttachments"
  71. )
  72. finance_remark: Optional[str] = Field(None, alias="financeRemark")
  73. @router.get("/presign-orders")
  74. async def list_presign_orders(
  75. page: int = Query(1, ge=1),
  76. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  77. keyword: Optional[str] = None,
  78. orderStatus: Optional[str] = Query(None, alias="orderStatus"),
  79. user: dict = Depends(require_admin_or_super_admin),
  80. db: Session = Depends(get_db),
  81. ):
  82. try:
  83. tenant_no = _get_tenant_no(db, user)
  84. items, total = po_svc.list_presign_orders(
  85. db,
  86. tenant_no=tenant_no,
  87. page=page,
  88. page_size=pageSize,
  89. keyword=keyword,
  90. order_status=orderStatus,
  91. user_info=user,
  92. )
  93. return success_response(
  94. {"list": items, "total": total, "page": page, "pageSize": pageSize}
  95. )
  96. except PermissionError as e:
  97. return error_response(403, str(e))
  98. @router.get("/presign-orders/{order_id}")
  99. async def get_presign_order(
  100. order_id: str,
  101. user: dict = Depends(require_admin_or_super_admin),
  102. db: Session = Depends(get_db),
  103. ):
  104. try:
  105. tenant_no = _get_tenant_no(db, user)
  106. po_svc.assert_presign_data_access(db, order_id, tenant_no, user)
  107. data = po_svc.get_presign_order(db, order_id=order_id, tenant_no=tenant_no)
  108. if not data:
  109. return error_response(404, "预签约订单不存在")
  110. return success_response(data)
  111. except PermissionError as e:
  112. return error_response(403, str(e))
  113. @router.post("/presign-orders")
  114. async def create_presign_order(
  115. body: PresignOrderBody,
  116. user: dict = Depends(require_admin_or_super_admin),
  117. db: Session = Depends(get_db),
  118. ):
  119. try:
  120. tenant_no = _get_tenant_no(db, user)
  121. user_id = _get_current_user_id(user)
  122. dumped = body.model_dump(by_alias=False)
  123. obj = po_svc.create_presign_order(
  124. db,
  125. create_dept_id=get_user_dept_id(db, user_id) or "",
  126. tenant_no=tenant_no,
  127. user_id=user_id,
  128. fields=dumped,
  129. sample_type_ids=body.sample_type_ids,
  130. transfers=body.transfers,
  131. )
  132. return success_response(
  133. po_svc.order_to_dict(db, obj, tenant_no, with_children=True),
  134. message="创建成功",
  135. )
  136. except PermissionError as e:
  137. return error_response(403, str(e))
  138. except ValueError as e:
  139. return error_response(400, str(e))
  140. @router.put("/presign-orders/{order_id}")
  141. async def update_presign_order(
  142. order_id: str,
  143. body: PresignOrderBody,
  144. user: dict = Depends(require_admin_or_super_admin),
  145. db: Session = Depends(get_db),
  146. ):
  147. try:
  148. tenant_no = _get_tenant_no(db, user)
  149. po_svc.assert_presign_data_access(db, order_id, tenant_no, user)
  150. user_id = _get_current_user_id(user)
  151. dumped = body.model_dump(by_alias=False, exclude_unset=True)
  152. sample_type_ids = dumped.pop("sample_type_ids", None)
  153. dumped.pop("transfers", None)
  154. obj = po_svc.update_presign_order(
  155. db,
  156. create_dept_id=get_user_dept_id(db, user_id) or "",
  157. order_id=order_id,
  158. tenant_no=tenant_no,
  159. user_id=user_id,
  160. fields=dumped,
  161. sample_type_ids=sample_type_ids,
  162. )
  163. return success_response(
  164. po_svc.order_to_dict(db, obj, tenant_no, with_children=True),
  165. message="保存成功",
  166. )
  167. except PermissionError as e:
  168. return error_response(403, str(e))
  169. except ValueError as e:
  170. return error_response(400, str(e))
  171. @router.delete("/presign-orders/{order_id}")
  172. async def delete_presign_order(
  173. order_id: str,
  174. user: dict = Depends(require_admin_or_super_admin),
  175. db: Session = Depends(get_db),
  176. ):
  177. try:
  178. tenant_no = _get_tenant_no(db, user)
  179. po_svc.assert_presign_data_access(db, order_id, tenant_no, user)
  180. user_id = _get_current_user_id(user)
  181. po_svc.soft_delete_presign_order(
  182. db, order_id=order_id, tenant_no=tenant_no, user_id=user_id
  183. )
  184. return success_response(None, message="删除成功")
  185. except PermissionError as e:
  186. return error_response(403, str(e))
  187. except ValueError as e:
  188. return error_response(400, str(e))
  189. @router.post("/presign-orders/{order_id}/convert")
  190. async def convert_presign_order(
  191. order_id: str,
  192. user: dict = Depends(require_admin_or_super_admin),
  193. db: Session = Depends(get_db),
  194. ):
  195. try:
  196. tenant_no = _get_tenant_no(db, user)
  197. po_svc.assert_presign_data_access(db, order_id, tenant_no, user)
  198. user_id = _get_current_user_id(user)
  199. data = po_svc.convert_to_protocol(
  200. db, order_id=order_id, tenant_no=tenant_no, user_id=user_id,
  201. create_dept_id=get_user_dept_id(db, user_id) or "",
  202. )
  203. return success_response(data, message="已转正式协议")
  204. except PermissionError as e:
  205. return error_response(403, str(e))
  206. except ValueError as e:
  207. return error_response(400, str(e))
  208. @router.post("/presign-orders/{order_id}/transfers")
  209. async def create_presign_transfer(
  210. order_id: str,
  211. body: PresignTransferBody,
  212. user: dict = Depends(require_admin_or_super_admin),
  213. db: Session = Depends(get_db),
  214. ):
  215. try:
  216. tenant_no = _get_tenant_no(db, user)
  217. po_svc.assert_presign_data_access(db, order_id, tenant_no, user)
  218. user_id = _get_current_user_id(user)
  219. obj = po_svc.add_transfer(
  220. db,
  221. create_dept_id=get_user_dept_id(db, user_id) or "",
  222. presign_order_id=order_id,
  223. tenant_no=tenant_no,
  224. user_id=user_id,
  225. fields=body.model_dump(by_alias=False),
  226. )
  227. return success_response(
  228. po_svc._transfer_to_dict(db, obj, tenant_no), message="添加成功"
  229. )
  230. except PermissionError as e:
  231. return error_response(403, str(e))
  232. except ValueError as e:
  233. return error_response(400, str(e))
  234. @router.put("/presign-transfers/{transfer_id}")
  235. async def update_presign_transfer(
  236. transfer_id: str,
  237. body: PresignTransferBody,
  238. user: dict = Depends(require_admin_or_super_admin),
  239. db: Session = Depends(get_db),
  240. ):
  241. try:
  242. tenant_no = _get_tenant_no(db, user)
  243. po_svc.assert_presign_data_access(
  244. db, transfer_id, tenant_no, user, transfer=True
  245. )
  246. user_id = _get_current_user_id(user)
  247. obj = po_svc.update_transfer_draft(
  248. db,
  249. transfer_id=transfer_id,
  250. tenant_no=tenant_no,
  251. user_id=user_id,
  252. fields=body.model_dump(by_alias=False, exclude_unset=True),
  253. )
  254. return success_response(
  255. po_svc._transfer_to_dict(db, obj, tenant_no), message="保存成功"
  256. )
  257. except PermissionError as e:
  258. return error_response(403, str(e))
  259. except ValueError as e:
  260. return error_response(400, str(e))
  261. @router.post("/presign-transfers/{transfer_id}/submit-receipt-finance")
  262. async def submit_presign_transfer_finance(
  263. transfer_id: str,
  264. user: dict = Depends(require_admin_or_super_admin),
  265. db: Session = Depends(get_db),
  266. ):
  267. try:
  268. tenant_no = _get_tenant_no(db, user)
  269. po_svc.assert_presign_data_access(
  270. db, transfer_id, tenant_no, user, transfer=True
  271. )
  272. user_id = _get_current_user_id(user)
  273. obj = po_svc.submit_receipt_finance(
  274. db, transfer_id=transfer_id, tenant_no=tenant_no, user_id=user_id
  275. )
  276. return success_response(
  277. po_svc._transfer_to_dict(db, obj, tenant_no), message="已提交财务确认"
  278. )
  279. except PermissionError as e:
  280. return error_response(403, str(e))
  281. except ValueError as e:
  282. return error_response(400, str(e))
  283. @router.post("/presign-transfers/{transfer_id}/withdraw")
  284. async def withdraw_presign_transfer(
  285. transfer_id: str,
  286. user: dict = Depends(require_admin_or_super_admin),
  287. db: Session = Depends(get_db),
  288. ):
  289. try:
  290. tenant_no = _get_tenant_no(db, user)
  291. po_svc.assert_presign_data_access(
  292. db, transfer_id, tenant_no, user, transfer=True
  293. )
  294. user_id = _get_current_user_id(user)
  295. obj = po_svc.withdraw_receipt(
  296. db, transfer_id=transfer_id, tenant_no=tenant_no, user_id=user_id
  297. )
  298. return success_response(
  299. po_svc._transfer_to_dict(db, obj, tenant_no), message="已撤回"
  300. )
  301. except PermissionError as e:
  302. return error_response(403, str(e))
  303. except ValueError as e:
  304. return error_response(400, str(e))
  305. @router.get("/presign-transfers/receipt-confirm")
  306. async def list_presign_receipt_confirm(
  307. page: int = Query(1, ge=1),
  308. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  309. keyword: Optional[str] = None,
  310. user: dict = Depends(require_admin_or_super_admin),
  311. db: Session = Depends(get_db),
  312. ):
  313. try:
  314. tenant_no = _get_tenant_no(db, user)
  315. items, total = po_svc.list_presign_transfers_for_finance(
  316. db,
  317. pay_status="jfqrz",
  318. tenant_no=tenant_no,
  319. keyword=keyword,
  320. page=page,
  321. page_size=pageSize,
  322. user_info=user,
  323. )
  324. return success_response(
  325. {"list": items, "total": total, "page": page, "pageSize": pageSize}
  326. )
  327. except PermissionError as e:
  328. return error_response(403, str(e))
  329. @router.get("/presign-transfers/receipt-confirmed")
  330. async def list_presign_receipt_confirmed(
  331. page: int = Query(1, ge=1),
  332. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  333. keyword: Optional[str] = None,
  334. user: dict = Depends(require_admin_or_super_admin),
  335. db: Session = Depends(get_db),
  336. ):
  337. try:
  338. tenant_no = _get_tenant_no(db, user)
  339. items, total = po_svc.list_presign_transfers_for_finance(
  340. db,
  341. pay_status="yjf",
  342. tenant_no=tenant_no,
  343. keyword=keyword,
  344. page=page,
  345. page_size=pageSize,
  346. user_info=user,
  347. )
  348. return success_response(
  349. {"list": items, "total": total, "page": page, "pageSize": pageSize}
  350. )
  351. except PermissionError as e:
  352. return error_response(403, str(e))
  353. @router.post("/presign-transfers/{transfer_id}/receipt-confirm")
  354. async def confirm_presign_transfer(
  355. transfer_id: str,
  356. body: Optional[FinanceConfirmBody] = None,
  357. user: dict = Depends(require_strict_menu_code(PAYMENT_CONFIRM_MENU)),
  358. db: Session = Depends(get_db),
  359. ):
  360. try:
  361. tenant_no = _get_tenant_no(db, user)
  362. po_svc.assert_presign_data_access(
  363. db, transfer_id, tenant_no, user, transfer=True
  364. )
  365. user_id = _get_current_user_id(user)
  366. obj = po_svc.confirm_receipt(
  367. db,
  368. transfer_id=transfer_id,
  369. tenant_no=tenant_no,
  370. user_id=user_id,
  371. finance_confirm_attachments=body.finance_confirm_attachments if body else None,
  372. finance_remark=body.finance_remark if body else None,
  373. )
  374. return success_response(
  375. po_svc._transfer_to_dict(db, obj, tenant_no), message="确认收款成功"
  376. )
  377. except PermissionError as e:
  378. return error_response(403, str(e))
  379. except ValueError as e:
  380. return error_response(400, str(e))
  381. @router.post("/presign-transfers/{transfer_id}/receipt-reject")
  382. async def reject_presign_transfer(
  383. transfer_id: str,
  384. user: dict = Depends(require_strict_menu_code(PAYMENT_CONFIRM_MENU)),
  385. db: Session = Depends(get_db),
  386. ):
  387. try:
  388. tenant_no = _get_tenant_no(db, user)
  389. po_svc.assert_presign_data_access(
  390. db, transfer_id, tenant_no, user, transfer=True
  391. )
  392. user_id = _get_current_user_id(user)
  393. obj = po_svc.reject_receipt(
  394. db, transfer_id=transfer_id, tenant_no=tenant_no, user_id=user_id
  395. )
  396. return success_response(
  397. po_svc._transfer_to_dict(db, obj, tenant_no), message="已退回"
  398. )
  399. except PermissionError as e:
  400. return error_response(403, str(e))
  401. except ValueError as e:
  402. return error_response(400, str(e))