qc.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967
  1. """质量检测 — 第三方/实验室主数据、质检工单、质检任务 API"""
  2. from typing import Any, Dict, List, 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_admin_or_super_admin, require_menu_code
  8. from app.core.response import error_response, success_response
  9. from app.crud import qc_lab_test_item as lab_svc
  10. from app.crud import qc_item_param as param_svc
  11. from app.crud import qc_task_result as result_svc
  12. from app.crud import qc_task as task_svc
  13. from app.crud import qc_third_party_company as tpc_svc
  14. from app.crud import qc_third_party_item as tpi_svc
  15. from app.crud import qc_material as mat_svc
  16. from app.crud import qc_material_usage as mu_svc
  17. from app.crud import qc_work_order as wo_svc
  18. from app.crud.collection_protocol import total_pages
  19. from app.api.sample_reception import _get_current_user_id, _get_tenant_no, _page_payload
  20. router = APIRouter(prefix="/web/admin", tags=["质量检测"])
  21. # ---------- 第三方检测公司 ----------
  22. class ThirdPartyCompanyBody(BaseModel):
  23. model_config = ConfigDict(populate_by_name=True)
  24. company_code: str = Field(..., alias="companyCode")
  25. company_name: str = Field(..., alias="companyName")
  26. contact_name: Optional[str] = Field(None, alias="contactName")
  27. contact_phone: Optional[str] = Field(None, alias="contactPhone")
  28. address: Optional[str] = None
  29. invoice_title: Optional[str] = Field(None, alias="invoiceTitle")
  30. tax_id: Optional[str] = Field(None, alias="taxId")
  31. bank_name: Optional[str] = Field(None, alias="bankName")
  32. bank_account: Optional[str] = Field(None, alias="bankAccount")
  33. status: str = "1"
  34. order_num: Optional[int] = Field(None, alias="orderNum")
  35. remark: Optional[str] = None
  36. class ThirdPartyCompanyUpdateBody(BaseModel):
  37. model_config = ConfigDict(populate_by_name=True)
  38. company_code: Optional[str] = Field(None, alias="companyCode")
  39. company_name: Optional[str] = Field(None, alias="companyName")
  40. contact_name: Optional[str] = Field(None, alias="contactName")
  41. contact_phone: Optional[str] = Field(None, alias="contactPhone")
  42. address: Optional[str] = None
  43. invoice_title: Optional[str] = Field(None, alias="invoiceTitle")
  44. tax_id: Optional[str] = Field(None, alias="taxId")
  45. bank_name: Optional[str] = Field(None, alias="bankName")
  46. bank_account: Optional[str] = Field(None, alias="bankAccount")
  47. status: Optional[str] = None
  48. order_num: Optional[int] = Field(None, alias="orderNum")
  49. remark: Optional[str] = None
  50. @router.get("/qc-third-party-companies")
  51. async def list_qc_third_party_companies(
  52. page: int = Query(1, ge=1),
  53. pageSize: int = Query(10, ge=1, le=100),
  54. keyword: Optional[str] = None,
  55. status: Optional[str] = None,
  56. db: Session = Depends(get_db),
  57. user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
  58. ):
  59. try:
  60. tenant_no = _get_tenant_no(db, user)
  61. items, total = tpc_svc.list_companies(
  62. db, page=page, page_size=pageSize, keyword=keyword, status=status, tenant_no=tenant_no
  63. )
  64. return success_response(_page_payload(items, total, page, pageSize))
  65. except PermissionError as e:
  66. return error_response(str(e), code=403)
  67. except Exception as e:
  68. return error_response(str(e))
  69. @router.get("/qc-third-party-companies/options")
  70. async def list_qc_third_party_company_options(
  71. db: Session = Depends(get_db),
  72. user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
  73. ):
  74. try:
  75. tenant_no = _get_tenant_no(db, user)
  76. return success_response(tpc_svc.list_company_options(db, tenant_no))
  77. except PermissionError as e:
  78. return error_response(str(e), code=403)
  79. except Exception as e:
  80. return error_response(str(e))
  81. @router.post("/qc-third-party-companies")
  82. async def create_qc_third_party_company(
  83. body: ThirdPartyCompanyBody,
  84. db: Session = Depends(get_db),
  85. user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
  86. ):
  87. try:
  88. tenant_no = _get_tenant_no(db, user)
  89. obj = tpc_svc.create_company(
  90. db,
  91. company_code=body.company_code,
  92. company_name=body.company_name,
  93. contact_name=body.contact_name,
  94. contact_phone=body.contact_phone,
  95. address=body.address,
  96. invoice_title=body.invoice_title,
  97. tax_id=body.tax_id,
  98. bank_name=body.bank_name,
  99. bank_account=body.bank_account,
  100. status=body.status,
  101. tenant_no=tenant_no,
  102. order_num=body.order_num,
  103. remark=body.remark,
  104. user_id=_get_current_user_id(user),
  105. )
  106. return success_response(tpc_svc._row_to_dict(obj), message="创建成功")
  107. except PermissionError as e:
  108. return error_response(str(e), code=403)
  109. except ValueError as e:
  110. return error_response(str(e))
  111. except Exception as e:
  112. return error_response(str(e))
  113. @router.put("/qc-third-party-companies/{company_id}")
  114. async def update_qc_third_party_company(
  115. company_id: str,
  116. body: ThirdPartyCompanyUpdateBody,
  117. db: Session = Depends(get_db),
  118. user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
  119. ):
  120. try:
  121. tenant_no = _get_tenant_no(db, user)
  122. obj = tpc_svc.update_company(
  123. db,
  124. company_id,
  125. company_code=body.company_code,
  126. company_name=body.company_name,
  127. contact_name=body.contact_name,
  128. contact_phone=body.contact_phone,
  129. address=body.address,
  130. invoice_title=body.invoice_title,
  131. tax_id=body.tax_id,
  132. bank_name=body.bank_name,
  133. bank_account=body.bank_account,
  134. status=body.status,
  135. order_num=body.order_num,
  136. remark=body.remark,
  137. user_id=_get_current_user_id(user),
  138. tenant_no=tenant_no,
  139. )
  140. return success_response(tpc_svc._row_to_dict(obj), message="更新成功")
  141. except PermissionError as e:
  142. return error_response(str(e), code=403)
  143. except ValueError as e:
  144. return error_response(str(e))
  145. except Exception as e:
  146. return error_response(str(e))
  147. @router.delete("/qc-third-party-companies/{company_id}")
  148. async def delete_qc_third_party_company(
  149. company_id: str,
  150. db: Session = Depends(get_db),
  151. user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
  152. ):
  153. try:
  154. tenant_no = _get_tenant_no(db, user)
  155. tpc_svc.soft_delete_company(db, company_id, tenant_no=tenant_no)
  156. return success_response(None, message="删除成功")
  157. except PermissionError as e:
  158. return error_response(str(e), code=403)
  159. except ValueError as e:
  160. return error_response(str(e))
  161. except Exception as e:
  162. return error_response(str(e))
  163. # ---------- 第三方检测项目 ----------
  164. class ThirdPartyItemBody(BaseModel):
  165. model_config = ConfigDict(populate_by_name=True)
  166. company_id: str = Field(..., alias="companyId")
  167. item_code: str = Field(..., alias="itemCode")
  168. item_name: str = Field(..., alias="itemName")
  169. fee_amount: float = Field(..., alias="feeAmount")
  170. status: str = "1"
  171. order_num: Optional[int] = Field(None, alias="orderNum")
  172. remark: Optional[str] = None
  173. class ThirdPartyItemUpdateBody(BaseModel):
  174. model_config = ConfigDict(populate_by_name=True)
  175. company_id: Optional[str] = Field(None, alias="companyId")
  176. item_code: Optional[str] = Field(None, alias="itemCode")
  177. item_name: Optional[str] = Field(None, alias="itemName")
  178. fee_amount: Optional[float] = Field(None, alias="feeAmount")
  179. status: Optional[str] = None
  180. order_num: Optional[int] = Field(None, alias="orderNum")
  181. remark: Optional[str] = None
  182. @router.get("/qc-third-party-items")
  183. async def list_qc_third_party_items(
  184. page: int = Query(1, ge=1),
  185. pageSize: int = Query(10, ge=1, le=100),
  186. keyword: Optional[str] = None,
  187. status: Optional[str] = None,
  188. companyId: Optional[str] = None,
  189. db: Session = Depends(get_db),
  190. user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
  191. ):
  192. try:
  193. tenant_no = _get_tenant_no(db, user)
  194. items, total = tpi_svc.list_items(
  195. db,
  196. page=page,
  197. page_size=pageSize,
  198. keyword=keyword,
  199. status=status,
  200. company_id=companyId,
  201. tenant_no=tenant_no,
  202. )
  203. return success_response(_page_payload(items, total, page, pageSize))
  204. except PermissionError as e:
  205. return error_response(str(e), code=403)
  206. except Exception as e:
  207. return error_response(str(e))
  208. @router.get("/qc-third-party-items/active")
  209. async def list_qc_third_party_items_active(
  210. companyId: Optional[str] = None,
  211. db: Session = Depends(get_db),
  212. user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
  213. ):
  214. try:
  215. tenant_no = _get_tenant_no(db, user)
  216. return success_response(tpi_svc.list_active_items(db, tenant_no, company_id=companyId))
  217. except PermissionError as e:
  218. return error_response(str(e), code=403)
  219. except Exception as e:
  220. return error_response(str(e))
  221. @router.post("/qc-third-party-items")
  222. async def create_qc_third_party_item(
  223. body: ThirdPartyItemBody,
  224. db: Session = Depends(get_db),
  225. user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
  226. ):
  227. try:
  228. tenant_no = _get_tenant_no(db, user)
  229. obj = tpi_svc.create_item(
  230. db,
  231. company_id=body.company_id,
  232. item_code=body.item_code,
  233. item_name=body.item_name,
  234. fee_amount=body.fee_amount,
  235. status=body.status,
  236. tenant_no=tenant_no,
  237. order_num=body.order_num,
  238. remark=body.remark,
  239. user_id=_get_current_user_id(user),
  240. )
  241. return success_response(tpi_svc._row_to_dict(obj, ""), message="创建成功")
  242. except PermissionError as e:
  243. return error_response(str(e), code=403)
  244. except ValueError as e:
  245. return error_response(str(e))
  246. except Exception as e:
  247. return error_response(str(e))
  248. @router.put("/qc-third-party-items/{item_id}")
  249. async def update_qc_third_party_item(
  250. item_id: str,
  251. body: ThirdPartyItemUpdateBody,
  252. db: Session = Depends(get_db),
  253. user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
  254. ):
  255. try:
  256. tenant_no = _get_tenant_no(db, user)
  257. obj = tpi_svc.update_item(
  258. db,
  259. item_id,
  260. company_id=body.company_id,
  261. item_code=body.item_code,
  262. item_name=body.item_name,
  263. fee_amount=body.fee_amount,
  264. status=body.status,
  265. order_num=body.order_num,
  266. remark=body.remark,
  267. user_id=_get_current_user_id(user),
  268. tenant_no=tenant_no,
  269. )
  270. return success_response(tpi_svc._row_to_dict(obj, ""), message="更新成功")
  271. except PermissionError as e:
  272. return error_response(str(e), code=403)
  273. except ValueError as e:
  274. return error_response(str(e))
  275. except Exception as e:
  276. return error_response(str(e))
  277. @router.delete("/qc-third-party-items/{item_id}")
  278. async def delete_qc_third_party_item(
  279. item_id: str,
  280. db: Session = Depends(get_db),
  281. user: dict = Depends(require_menu_code("biz:qcThirdPartyCompany:list")),
  282. ):
  283. try:
  284. tenant_no = _get_tenant_no(db, user)
  285. tpi_svc.soft_delete_item(db, item_id, tenant_no=tenant_no)
  286. return success_response(None, message="删除成功")
  287. except PermissionError as e:
  288. return error_response(str(e), code=403)
  289. except ValueError as e:
  290. return error_response(str(e))
  291. except Exception as e:
  292. return error_response(str(e))
  293. # ---------- 实验室自检项目 ----------
  294. class LabTestItemBody(BaseModel):
  295. model_config = ConfigDict(populate_by_name=True)
  296. item_code: str = Field(..., alias="itemCode")
  297. item_name: str = Field(..., alias="itemName")
  298. description: Optional[str] = None
  299. status: str = "1"
  300. order_num: Optional[int] = Field(None, alias="orderNum")
  301. remark: Optional[str] = None
  302. class LabTestItemUpdateBody(BaseModel):
  303. model_config = ConfigDict(populate_by_name=True)
  304. item_code: Optional[str] = Field(None, alias="itemCode")
  305. item_name: Optional[str] = Field(None, alias="itemName")
  306. description: Optional[str] = None
  307. status: Optional[str] = None
  308. order_num: Optional[int] = Field(None, alias="orderNum")
  309. remark: Optional[str] = None
  310. # ---------- 检测项目参数模板 ----------
  311. class ItemParamBody(BaseModel):
  312. model_config = ConfigDict(populate_by_name=True)
  313. params: List[Dict[str, Any]] = Field(default_factory=list, alias="params")
  314. class TaskResultBody(BaseModel):
  315. model_config = ConfigDict(populate_by_name=True)
  316. results: List[Dict[str, Any]] = Field(default_factory=list, alias="results")
  317. class TaskCompleteWithResultBody(BaseModel):
  318. model_config = ConfigDict(populate_by_name=True)
  319. conclusion: str = Field(..., alias="conclusion")
  320. results: List[Dict[str, Any]] = Field(default_factory=list, alias="results")
  321. remark: Optional[str] = Field(None, alias="remark")
  322. third_party_attachments: Optional[str] = Field(None, alias="thirdPartyAttachments")
  323. material_usages: Optional[List[Dict[str, Any]]] = Field(None, alias="materialUsages")
  324. @router.get("/qc-lab-test-items")
  325. async def list_qc_lab_test_items(
  326. page: int = Query(1, ge=1),
  327. pageSize: int = Query(10, ge=1, le=100),
  328. keyword: Optional[str] = None,
  329. status: Optional[str] = None,
  330. db: Session = Depends(get_db),
  331. user: dict = Depends(require_menu_code("biz:qcLabTestItem:list")),
  332. ):
  333. try:
  334. tenant_no = _get_tenant_no(db, user)
  335. items, total = lab_svc.list_lab_items(
  336. db, page=page, page_size=pageSize, keyword=keyword, status=status, tenant_no=tenant_no
  337. )
  338. return success_response(_page_payload(items, total, page, pageSize))
  339. except PermissionError as e:
  340. return error_response(str(e), code=403)
  341. except Exception as e:
  342. return error_response(str(e))
  343. @router.get("/qc-lab-test-items/active")
  344. async def list_qc_lab_test_items_active(
  345. db: Session = Depends(get_db),
  346. user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
  347. ):
  348. try:
  349. tenant_no = _get_tenant_no(db, user)
  350. return success_response(lab_svc.list_active_lab_items(db, tenant_no))
  351. except PermissionError as e:
  352. return error_response(str(e), code=403)
  353. except Exception as e:
  354. return error_response(str(e))
  355. @router.post("/qc-lab-test-items")
  356. async def create_qc_lab_test_item(
  357. body: LabTestItemBody,
  358. db: Session = Depends(get_db),
  359. user: dict = Depends(require_menu_code("biz:qcLabTestItem:list")),
  360. ):
  361. try:
  362. tenant_no = _get_tenant_no(db, user)
  363. obj = lab_svc.create_lab_item(
  364. db,
  365. item_code=body.item_code,
  366. item_name=body.item_name,
  367. description=body.description,
  368. status=body.status,
  369. tenant_no=tenant_no,
  370. order_num=body.order_num,
  371. remark=body.remark,
  372. user_id=_get_current_user_id(user),
  373. )
  374. return success_response(lab_svc._row_to_dict(obj), message="创建成功")
  375. except PermissionError as e:
  376. return error_response(str(e), code=403)
  377. except ValueError as e:
  378. return error_response(str(e))
  379. except Exception as e:
  380. return error_response(str(e))
  381. @router.put("/qc-lab-test-items/{item_id}")
  382. async def update_qc_lab_test_item(
  383. item_id: str,
  384. body: LabTestItemUpdateBody,
  385. db: Session = Depends(get_db),
  386. user: dict = Depends(require_menu_code("biz:qcLabTestItem:list")),
  387. ):
  388. try:
  389. tenant_no = _get_tenant_no(db, user)
  390. obj = lab_svc.update_lab_item(
  391. db,
  392. item_id,
  393. item_code=body.item_code,
  394. item_name=body.item_name,
  395. description=body.description,
  396. status=body.status,
  397. order_num=body.order_num,
  398. remark=body.remark,
  399. user_id=_get_current_user_id(user),
  400. tenant_no=tenant_no,
  401. )
  402. return success_response(lab_svc._row_to_dict(obj), message="更新成功")
  403. except PermissionError as e:
  404. return error_response(str(e), code=403)
  405. except ValueError as e:
  406. return error_response(str(e))
  407. except Exception as e:
  408. return error_response(str(e))
  409. @router.delete("/qc-lab-test-items/{item_id}")
  410. async def delete_qc_lab_test_item(
  411. item_id: str,
  412. db: Session = Depends(get_db),
  413. user: dict = Depends(require_menu_code("biz:qcLabTestItem:list")),
  414. ):
  415. try:
  416. tenant_no = _get_tenant_no(db, user)
  417. lab_svc.soft_delete_lab_item(db, item_id, tenant_no=tenant_no)
  418. return success_response(None, message="删除成功")
  419. except PermissionError as e:
  420. return error_response(str(e), code=403)
  421. except ValueError as e:
  422. return error_response(str(e))
  423. except Exception as e:
  424. return error_response(str(e))
  425. # ---------- 检测项目参数模板 ----------
  426. @router.get("/qc-item-params")
  427. async def list_item_params(
  428. itemSource: str = Query(...),
  429. itemId: str = Query(...),
  430. db: Session = Depends(get_db),
  431. user: dict = Depends(require_menu_code("biz:qcLabTestItem:list")),
  432. ):
  433. try:
  434. tenant_no = _get_tenant_no(db, user)
  435. return success_response(param_svc.list_params_by_item(db, itemSource, itemId, tenant_no))
  436. except PermissionError as e:
  437. return error_response(str(e), code=403)
  438. except Exception as e:
  439. return error_response(str(e))
  440. @router.put("/qc-item-params")
  441. async def save_item_params(
  442. body: ItemParamBody,
  443. itemSource: str = Query(...),
  444. itemId: str = Query(...),
  445. db: Session = Depends(get_db),
  446. user: dict = Depends(require_menu_code("biz:qcLabTestItem:list")),
  447. ):
  448. try:
  449. tenant_no = _get_tenant_no(db, user)
  450. return success_response(
  451. param_svc.save_params_for_item(db, itemSource, itemId, body.params, tenant_no, _get_current_user_id(user)),
  452. message="保存成功",
  453. )
  454. except PermissionError as e:
  455. return error_response(str(e), code=403)
  456. except Exception as e:
  457. return error_response(str(e))
  458. # ---------- 质检工单 ----------
  459. class WorkOrderConfigureBody(BaseModel):
  460. model_config = ConfigDict(populate_by_name=True)
  461. has_lab: bool = Field(False, alias="hasLab")
  462. has_third_party: bool = Field(False, alias="hasThirdParty")
  463. lab_item_ids: List[str] = Field(default_factory=list, alias="labItemIds")
  464. third_party_item_ids: List[str] = Field(default_factory=list, alias="thirdPartyItemIds")
  465. @router.get("/qc-work-orders")
  466. async def list_qc_work_orders(
  467. page: int = Query(1, ge=1),
  468. pageSize: int = Query(10, ge=1, le=100),
  469. keyword: Optional[str] = None,
  470. workOrderStatus: Optional[str] = None,
  471. db: Session = Depends(get_db),
  472. user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
  473. ):
  474. try:
  475. tenant_no = _get_tenant_no(db, user)
  476. items, total = wo_svc.list_work_orders(
  477. db,
  478. page=page,
  479. page_size=pageSize,
  480. keyword=keyword,
  481. work_order_status=workOrderStatus,
  482. tenant_no=tenant_no,
  483. )
  484. return success_response(_page_payload(items, total, page, pageSize))
  485. except PermissionError as e:
  486. return error_response(str(e), code=403)
  487. except Exception as e:
  488. return error_response(str(e))
  489. @router.get("/qc-work-orders/{work_order_id}")
  490. async def get_qc_work_order_detail(
  491. work_order_id: str,
  492. db: Session = Depends(get_db),
  493. user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
  494. ):
  495. try:
  496. tenant_no = _get_tenant_no(db, user)
  497. data = wo_svc.get_work_order_detail(db, work_order_id, tenant_no)
  498. return success_response(data)
  499. except PermissionError as e:
  500. return error_response(str(e), code=403)
  501. except ValueError as e:
  502. return error_response(str(e))
  503. except Exception as e:
  504. return error_response(str(e))
  505. @router.post("/qc-work-orders/{work_order_id}/configure")
  506. async def configure_qc_work_order(
  507. work_order_id: str,
  508. body: WorkOrderConfigureBody,
  509. db: Session = Depends(get_db),
  510. user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
  511. ):
  512. try:
  513. tenant_no = _get_tenant_no(db, user)
  514. obj = wo_svc.configure_work_order(
  515. db,
  516. work_order_id,
  517. has_lab=body.has_lab,
  518. has_third_party=body.has_third_party,
  519. lab_item_ids=body.lab_item_ids,
  520. third_party_item_ids=body.third_party_item_ids,
  521. tenant_no=tenant_no,
  522. user_id=_get_current_user_id(user),
  523. )
  524. return success_response(wo_svc._wo_to_dict(obj), message="配置成功")
  525. except PermissionError as e:
  526. return error_response(str(e), code=403)
  527. except ValueError as e:
  528. return error_response(str(e))
  529. except Exception as e:
  530. return error_response(str(e))
  531. # ---------- 质检任务 ----------
  532. class TaskAttachmentsBody(BaseModel):
  533. model_config = ConfigDict(populate_by_name=True)
  534. third_party_attachments: str = Field("", alias="thirdPartyAttachments")
  535. @router.get("/qc-tasks")
  536. async def list_qc_tasks(
  537. page: int = Query(1, ge=1),
  538. pageSize: int = Query(10, ge=1, le=100),
  539. keyword: Optional[str] = None,
  540. taskType: Optional[str] = None,
  541. taskStatus: Optional[str] = None,
  542. workOrderId: Optional[str] = None,
  543. qcBizType: Optional[str] = None,
  544. db: Session = Depends(get_db),
  545. user: dict = Depends(require_menu_code("biz:qcTask:list")),
  546. ):
  547. try:
  548. tenant_no = _get_tenant_no(db, user)
  549. items, total = task_svc.list_tasks(
  550. db,
  551. page=page,
  552. page_size=pageSize,
  553. keyword=keyword,
  554. task_type=taskType,
  555. task_status=taskStatus,
  556. work_order_id=workOrderId,
  557. qc_biz_type=qcBizType,
  558. tenant_no=tenant_no,
  559. )
  560. return success_response(_page_payload(items, total, page, pageSize))
  561. except PermissionError as e:
  562. return error_response(str(e), code=403)
  563. except Exception as e:
  564. return error_response(str(e))
  565. @router.get("/qc-tasks/{task_id}")
  566. async def get_qc_task_detail(
  567. task_id: str,
  568. db: Session = Depends(get_db),
  569. user: dict = Depends(require_menu_code("biz:qcTask:list")),
  570. ):
  571. try:
  572. tenant_no = _get_tenant_no(db, user)
  573. data = task_svc.get_task_detail(db, task_id, tenant_no)
  574. return success_response(data)
  575. except PermissionError as e:
  576. return error_response(str(e), code=403)
  577. except ValueError as e:
  578. return error_response(str(e))
  579. except Exception as e:
  580. return error_response(str(e))
  581. @router.post("/qc-tasks/{task_id}/start")
  582. async def start_qc_task(
  583. task_id: str,
  584. db: Session = Depends(get_db),
  585. user: dict = Depends(require_menu_code("biz:qcTask:list")),
  586. ):
  587. try:
  588. tenant_no = _get_tenant_no(db, user)
  589. obj = task_svc.start_task(db, task_id, tenant_no=tenant_no, user_id=_get_current_user_id(user))
  590. return success_response(task_svc._task_to_dict(obj), message="已开始")
  591. except PermissionError as e:
  592. return error_response(str(e), code=403)
  593. except ValueError as e:
  594. return error_response(str(e))
  595. except Exception as e:
  596. return error_response(str(e))
  597. @router.post("/qc-tasks/{task_id}/complete")
  598. async def complete_qc_task(
  599. task_id: str,
  600. db: Session = Depends(get_db),
  601. user: dict = Depends(require_menu_code("biz:qcTask:list")),
  602. ):
  603. try:
  604. tenant_no = _get_tenant_no(db, user)
  605. obj = task_svc.complete_task(
  606. db, task_id, tenant_no=tenant_no, user_id=_get_current_user_id(user)
  607. )
  608. return success_response(task_svc._task_to_dict(obj), message="已完成")
  609. except PermissionError as e:
  610. return error_response(str(e), code=403)
  611. except ValueError as e:
  612. return error_response(str(e))
  613. except Exception as e:
  614. return error_response(str(e))
  615. @router.get("/qc-tasks/{task_id}/results")
  616. async def get_task_results(
  617. task_id: str,
  618. db: Session = Depends(get_db),
  619. user: dict = Depends(require_menu_code("biz:qcTask:list")),
  620. ):
  621. try:
  622. tenant_no = _get_tenant_no(db, user)
  623. return success_response(result_svc.list_results_by_task(db, task_id, tenant_no))
  624. except PermissionError as e:
  625. return error_response(str(e), code=403)
  626. except Exception as e:
  627. return error_response(str(e))
  628. @router.post("/qc-tasks/{task_id}/complete-with-result")
  629. async def complete_task_with_result(
  630. task_id: str,
  631. body: TaskCompleteWithResultBody,
  632. db: Session = Depends(get_db),
  633. user: dict = Depends(require_menu_code("biz:qcTask:list")),
  634. ):
  635. try:
  636. tenant_no = _get_tenant_no(db, user)
  637. user_id = _get_current_user_id(user)
  638. t = task_svc.get_task(db, task_id, tenant_no=tenant_no)
  639. if not t:
  640. return error_response("任务不存在")
  641. was_done = (t.task_status or "").strip() == "ywc"
  642. if body.results:
  643. result_svc.save_results_for_task(db, task_id, body.results, tenant_no, user_id)
  644. concl = (body.conclusion or "").strip()
  645. if concl not in ("tg", "btg"):
  646. return error_response("请选择检测结论(通过/不通过)")
  647. if body.third_party_attachments is not None:
  648. task_svc.update_task_attachments(
  649. db, task_id, attachments=body.third_party_attachments,
  650. tenant_no=tenant_no, user_id=user_id,
  651. )
  652. # 物料扣减(可选,仅当传了 materialUsages 时)
  653. if body.material_usages:
  654. task_obj_pre = task_svc.get_task(db, task_id, tenant_no=tenant_no)
  655. task_no = task_obj_pre.task_no if task_obj_pre else ""
  656. mu_svc.deduct_for_task(
  657. db,
  658. task_id,
  659. task_no=task_no,
  660. material_usages=body.material_usages,
  661. tenant_no=tenant_no,
  662. user_id=user_id,
  663. )
  664. obj = task_svc.apply_task_result(
  665. db,
  666. task_id,
  667. tenant_no=tenant_no,
  668. user_id=user_id,
  669. conclusion=concl,
  670. remark=body.remark,
  671. )
  672. return success_response(
  673. task_svc._task_to_dict(obj, work_order_no="", db=db, tenant_no=tenant_no),
  674. message="已保存" if was_done else "已完成",
  675. )
  676. except PermissionError as e:
  677. return error_response(str(e), code=403)
  678. except ValueError as e:
  679. return error_response(str(e))
  680. except Exception as e:
  681. return error_response(str(e))
  682. @router.put("/qc-tasks/{task_id}/attachments")
  683. async def update_qc_task_attachments(
  684. task_id: str,
  685. body: TaskAttachmentsBody,
  686. db: Session = Depends(get_db),
  687. user: dict = Depends(require_menu_code("biz:qcTask:list")),
  688. ):
  689. try:
  690. tenant_no = _get_tenant_no(db, user)
  691. obj = task_svc.update_task_attachments(
  692. db,
  693. task_id,
  694. attachments=body.third_party_attachments,
  695. tenant_no=tenant_no,
  696. user_id=_get_current_user_id(user),
  697. )
  698. return success_response(task_svc._task_to_dict(obj), message="已保存")
  699. except PermissionError as e:
  700. return error_response(str(e), code=403)
  701. except ValueError as e:
  702. return error_response(str(e))
  703. except Exception as e:
  704. return error_response(str(e))
  705. # ---------- 质检物料库 ----------
  706. class QcMaterialBody(BaseModel):
  707. model_config = ConfigDict(populate_by_name=True)
  708. material_code: str = Field(..., alias="materialCode")
  709. material_name: str = Field(..., alias="materialName")
  710. spec_model: Optional[str] = Field(None, alias="specModel")
  711. unit: Optional[str] = None
  712. warning_qty: Optional[float] = Field(0, alias="warningQty")
  713. status: str = "1"
  714. remark: Optional[str] = None
  715. class QcMaterialUpdateBody(BaseModel):
  716. model_config = ConfigDict(populate_by_name=True)
  717. material_code: Optional[str] = Field(None, alias="materialCode")
  718. material_name: Optional[str] = Field(None, alias="materialName")
  719. spec_model: Optional[str] = Field(None, alias="specModel")
  720. unit: Optional[str] = None
  721. warning_qty: Optional[float] = Field(None, alias="warningQty")
  722. status: Optional[str] = None
  723. remark: Optional[str] = None
  724. class StockInBody(BaseModel):
  725. model_config = ConfigDict(populate_by_name=True)
  726. qty: float = Field(..., gt=0)
  727. remark: Optional[str] = None
  728. @router.get("/qc-materials")
  729. async def list_qc_materials(
  730. page: int = Query(1, ge=1),
  731. pageSize: int = Query(10, ge=1, le=100),
  732. keyword: Optional[str] = None,
  733. status: Optional[str] = None,
  734. db: Session = Depends(get_db),
  735. user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
  736. ):
  737. try:
  738. tenant_no = _get_tenant_no(db, user)
  739. items, total = mat_svc.list_materials(
  740. db, page=page, page_size=pageSize, keyword=keyword, status=status, tenant_no=tenant_no,
  741. )
  742. return success_response(_page_payload(items, total, page, pageSize))
  743. except PermissionError as e:
  744. return error_response(str(e), code=403)
  745. except Exception as e:
  746. return error_response(str(e))
  747. @router.get("/qc-materials/options")
  748. async def qc_material_options(
  749. db: Session = Depends(get_db),
  750. user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
  751. ):
  752. try:
  753. tenant_no = _get_tenant_no(db, user)
  754. return success_response(mat_svc.options_list(db, tenant_no=tenant_no))
  755. except Exception as e:
  756. return error_response(str(e))
  757. @router.post("/qc-materials")
  758. async def create_qc_material(
  759. body: QcMaterialBody,
  760. db: Session = Depends(get_db),
  761. user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
  762. ):
  763. try:
  764. tenant_no = _get_tenant_no(db, user)
  765. user_id = _get_current_user_id(user)
  766. obj = mat_svc.create_material(db, body.model_dump(by_alias=True, exclude_none=True), tenant_no=tenant_no, user_id=user_id)
  767. return success_response(obj, message="创建成功")
  768. except ValueError as e:
  769. return error_response(str(e))
  770. except Exception as e:
  771. return error_response(str(e))
  772. @router.put("/qc-materials/{material_id}")
  773. async def update_qc_material(
  774. material_id: str,
  775. body: QcMaterialUpdateBody,
  776. db: Session = Depends(get_db),
  777. user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
  778. ):
  779. try:
  780. tenant_no = _get_tenant_no(db, user)
  781. user_id = _get_current_user_id(user)
  782. obj = mat_svc.update_material(db, material_id, body.model_dump(by_alias=True, exclude_none=True), tenant_no=tenant_no, user_id=user_id)
  783. return success_response(obj, message="更新成功")
  784. except ValueError as e:
  785. return error_response(str(e))
  786. except Exception as e:
  787. return error_response(str(e))
  788. @router.delete("/qc-materials/{material_id}")
  789. async def delete_qc_material(
  790. material_id: str,
  791. db: Session = Depends(get_db),
  792. user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
  793. ):
  794. try:
  795. tenant_no = _get_tenant_no(db, user)
  796. user_id = _get_current_user_id(user)
  797. mat_svc.soft_delete_material(db, material_id, tenant_no=tenant_no, user_id=user_id)
  798. return success_response(None, message="删除成功")
  799. except ValueError as e:
  800. return error_response(str(e))
  801. except Exception as e:
  802. return error_response(str(e))
  803. @router.post("/qc-materials/{material_id}/stock-in")
  804. async def stock_in_qc_material(
  805. material_id: str,
  806. body: StockInBody,
  807. db: Session = Depends(get_db),
  808. user: dict = Depends(require_menu_code("biz:qcMaterial:list")),
  809. ):
  810. try:
  811. tenant_no = _get_tenant_no(db, user)
  812. user_id = _get_current_user_id(user)
  813. obj = mat_svc.stock_in(db, material_id, qty=body.qty, remark=body.remark, tenant_no=tenant_no, user_id=user_id)
  814. return success_response(obj, message="入库成功")
  815. except ValueError as e:
  816. return error_response(str(e))
  817. except Exception as e:
  818. return error_response(str(e))
  819. # ---------- 物料变动记录 ----------
  820. @router.get("/qc-material-usages")
  821. async def list_qc_material_usages(
  822. page: int = Query(1, ge=1),
  823. pageSize: int = Query(10, ge=1, le=100),
  824. materialId: Optional[str] = None,
  825. changeType: Optional[str] = None,
  826. startTime: Optional[str] = None,
  827. endTime: Optional[str] = None,
  828. db: Session = Depends(get_db),
  829. user: dict = Depends(require_menu_code("biz:qcMaterialUsage:list")),
  830. ):
  831. try:
  832. tenant_no = _get_tenant_no(db, user)
  833. items, total = mu_svc.list_usages(
  834. db, tenant_no, page=page, page_size=pageSize,
  835. material_id=materialId, change_type=changeType,
  836. start_time=startTime, end_time=endTime,
  837. )
  838. return success_response(_page_payload(items, total, page, pageSize))
  839. except PermissionError as e:
  840. return error_response(str(e), code=403)
  841. except Exception as e:
  842. return error_response(str(e))