sample_production_reception.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. """生产部门样本接收 API"""
  2. from typing import Optional
  3. from fastapi import APIRouter, Depends, Query
  4. from pydantic import BaseModel, ConfigDict, Field
  5. from sqlalchemy.orm import Session
  6. from app.core.database import get_db
  7. from app.core.menu_guard import require_any_menu_code, require_menu_code
  8. from app.core.response import error_response, success_response
  9. from app.core.utils import parse_datetime
  10. from app.crud import sample_production_reception as prod_rec_svc
  11. from app.crud import sample_reception as sr_svc
  12. from app.models.models import SysUser
  13. router = APIRouter(prefix="/web/admin", tags=["生产管理"])
  14. PRODUCTION_RECEPTION_READ_MENU_CODES = (
  15. "production:work-order:list",
  16. "production:my-task:list",
  17. "biz:sampleReception:list",
  18. "production:sample-reception-completion:list",
  19. )
  20. PRODUCTION_RECEPTION_WRITE_MENU_CODES = (
  21. "production:work-order:list",
  22. "production:my-task:list",
  23. )
  24. def _get_tenant_no(db: Session, user: dict) -> str:
  25. user_id = user.get("user_id")
  26. if not user_id:
  27. raise PermissionError("用户身份信息缺失,无法确定租户")
  28. sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
  29. if not sys_user or not sys_user.tenant_no:
  30. raise PermissionError("用户租户信息缺失")
  31. return sys_user.tenant_no
  32. def _get_current_user_id(user: dict) -> str:
  33. user_id = user.get("user_id")
  34. if not user_id:
  35. raise PermissionError("用户身份信息缺失")
  36. return str(user_id)
  37. class ProductionReceptionBody(BaseModel):
  38. model_config = ConfigDict(populate_by_name=True)
  39. reception_id: Optional[str] = Field(None, alias="receptionId")
  40. sample_code: Optional[str] = Field(None, alias="sampleCode")
  41. source_type: Optional[str] = Field("cell_storage", alias="sourceType")
  42. sample_type_name: Optional[str] = Field(None, alias="sampleTypeName")
  43. handler_user_id: Optional[str] = Field(None, alias="handlerUserId")
  44. handler_name: Optional[str] = Field(None, alias="handlerName")
  45. process_time: str = Field(..., alias="processTime")
  46. original_blood_weight_g: Optional[float] = Field(None, alias="originalBloodWeightG")
  47. hes_volume_ml: Optional[float] = Field(None, alias="hesVolumeMl")
  48. blood_type: Optional[str] = Field(None, alias="bloodType")
  49. infectious_four_test_result: Optional[str] = Field(None, alias="infectiousFourTestResult")
  50. enriched_blood_weight_g: Optional[float] = Field(None, alias="enrichedBloodWeightG")
  51. cryopreservation_volume_ml: Optional[float] = Field(None, alias="cryopreservationVolumeMl")
  52. inoculation_amount: Optional[float] = Field(None, alias="inoculationAmount")
  53. cryopreservation_time: Optional[str] = Field(None, alias="cryopreservationTime")
  54. storage_total_tubes: Optional[int] = Field(None, alias="storageTotalTubes")
  55. available_cell_count_1e7: Optional[float] = Field(None, alias="availableCellCount1e7")
  56. original_blood_volume_ml: Optional[float] = Field(None, alias="originalBloodVolumeMl")
  57. nucleated_cell_count_1e8: Optional[float] = Field(None, alias="nucleatedCellCount1e8")
  58. remark: Optional[str] = None
  59. def _body_to_payload(body: ProductionReceptionBody) -> dict:
  60. cryo_time = parse_datetime(body.cryopreservation_time) if body.cryopreservation_time else None
  61. process_time = parse_datetime(body.process_time)
  62. if not process_time:
  63. raise ValueError("处理时间格式无效")
  64. return {
  65. "handlerUserId": body.handler_user_id,
  66. "handlerName": body.handler_name,
  67. "processTime": process_time,
  68. "sampleTypeName": body.sample_type_name,
  69. "originalBloodWeightG": body.original_blood_weight_g,
  70. "hesVolumeMl": body.hes_volume_ml,
  71. "bloodType": body.blood_type,
  72. "infectiousFourTestResult": body.infectious_four_test_result,
  73. "enrichedBloodWeightG": body.enriched_blood_weight_g,
  74. "cryopreservationVolumeMl": body.cryopreservation_volume_ml,
  75. "inoculationAmount": body.inoculation_amount,
  76. "cryopreservationTime": cryo_time,
  77. "storageTotalTubes": body.storage_total_tubes,
  78. "availableCellCount1e7": body.available_cell_count_1e7,
  79. "originalBloodVolumeMl": body.original_blood_volume_ml,
  80. "nucleatedCellCount1e8": body.nucleated_cell_count_1e8,
  81. "remark": body.remark,
  82. }
  83. def _resolve_reception_id(
  84. db: Session,
  85. *,
  86. tenant_no: str,
  87. reception_id: Optional[str],
  88. sample_code: Optional[str],
  89. source_type: Optional[str],
  90. ) -> str:
  91. rid = (reception_id or "").strip()
  92. if rid:
  93. return rid
  94. code = (sample_code or "").strip()
  95. if not code:
  96. raise ValueError("请提供 receptionId 或 sampleCode")
  97. result = sr_svc.get_reception_by_sample_code(
  98. db, sample_code=code, source_type=source_type, tenant_no=tenant_no
  99. )
  100. found = (result.get("receptionId") or "").strip()
  101. if not found:
  102. raise ValueError("请先完成质检样本接收登记")
  103. return found
  104. @router.get("/production-sample-receptions/by-sample-code")
  105. async def get_production_reception_by_sample_code(
  106. sampleCode: str = Query(..., alias="sampleCode"),
  107. sourceType: Optional[str] = Query(None, alias="sourceType"),
  108. db: Session = Depends(get_db),
  109. user: dict = Depends(require_any_menu_code(*PRODUCTION_RECEPTION_READ_MENU_CODES)),
  110. ):
  111. try:
  112. tenant_no = _get_tenant_no(db, user)
  113. base = sr_svc.get_reception_by_sample_code(
  114. db, sample_code=sampleCode, source_type=sourceType, tenant_no=tenant_no
  115. )
  116. reception_id = base.get("receptionId")
  117. qc_reception = None
  118. production_reception = None
  119. if reception_id:
  120. qc_reception = sr_svc.get_reception_by_id(db, reception_id, tenant_no)
  121. production_reception = prod_rec_svc.get_by_reception_id(db, reception_id, tenant_no)
  122. return success_response(
  123. {
  124. "receptionId": reception_id,
  125. "sampleInfo": base.get("sampleInfo"),
  126. "qcReception": qc_reception,
  127. "productionReception": production_reception,
  128. }
  129. )
  130. except PermissionError as e:
  131. return error_response(403, str(e))
  132. except Exception as e:
  133. return error_response(400, str(e))
  134. @router.get("/production-sample-receptions/{row_id}")
  135. async def get_production_reception_detail(
  136. row_id: str,
  137. db: Session = Depends(get_db),
  138. user: dict = Depends(require_any_menu_code(*PRODUCTION_RECEPTION_READ_MENU_CODES)),
  139. ):
  140. try:
  141. tenant_no = _get_tenant_no(db, user)
  142. item = prod_rec_svc.get_by_id(db, row_id, tenant_no)
  143. if not item:
  144. return error_response(404, "生产接收记录不存在")
  145. return success_response(item)
  146. except PermissionError as e:
  147. return error_response(403, str(e))
  148. except Exception as e:
  149. return error_response(400, str(e))
  150. @router.post("/production-sample-receptions")
  151. async def post_production_reception(
  152. body: ProductionReceptionBody,
  153. db: Session = Depends(get_db),
  154. user: dict = Depends(require_any_menu_code(*PRODUCTION_RECEPTION_WRITE_MENU_CODES)),
  155. ):
  156. try:
  157. tenant_no = _get_tenant_no(db, user)
  158. user_id = _get_current_user_id(user)
  159. sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
  160. dept_id = (sys_user.dept_id or "") if sys_user else ""
  161. reception_id = _resolve_reception_id(
  162. db,
  163. tenant_no=tenant_no,
  164. reception_id=body.reception_id,
  165. sample_code=body.sample_code,
  166. source_type=body.source_type,
  167. )
  168. payload = _body_to_payload(body)
  169. type_name = (body.sample_type_name or "").strip()
  170. if not type_name:
  171. sample_info = sr_svc.get_reception_by_sample_code(
  172. db,
  173. sample_code=(body.sample_code or "").strip(),
  174. source_type=body.source_type,
  175. tenant_no=tenant_no,
  176. ).get("sampleInfo")
  177. if isinstance(sample_info, dict):
  178. type_name = str(sample_info.get("sampleTypeName") or "")
  179. obj = prod_rec_svc.create_production_reception(
  180. db,
  181. reception_id=reception_id,
  182. tenant_no=tenant_no,
  183. user_id=user_id,
  184. create_dept_id=dept_id,
  185. data=payload,
  186. sample_type_name=type_name,
  187. )
  188. return success_response(prod_rec_svc.get_by_id(db, obj.id, tenant_no))
  189. except PermissionError as e:
  190. return error_response(403, str(e))
  191. except ValueError as e:
  192. return error_response(400, str(e))
  193. except Exception as e:
  194. return error_response(400, str(e))
  195. @router.put("/production-sample-receptions/{row_id}")
  196. async def put_production_reception(
  197. row_id: str,
  198. body: ProductionReceptionBody,
  199. db: Session = Depends(get_db),
  200. user: dict = Depends(require_any_menu_code(*PRODUCTION_RECEPTION_WRITE_MENU_CODES)),
  201. ):
  202. try:
  203. tenant_no = _get_tenant_no(db, user)
  204. user_id = _get_current_user_id(user)
  205. payload = _body_to_payload(body)
  206. obj = prod_rec_svc.update_production_reception(
  207. db,
  208. row_id=row_id,
  209. tenant_no=tenant_no,
  210. user_id=user_id,
  211. data=payload,
  212. )
  213. return success_response(prod_rec_svc.get_by_id(db, obj.id, tenant_no))
  214. except PermissionError as e:
  215. return error_response(403, str(e))
  216. except ValueError as e:
  217. return error_response(400, str(e))
  218. except Exception as e:
  219. return error_response(400, str(e))