basic_info.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920
  1. """基础信息管理 API(客户、样本类型、采集地区/医院、协议号池、样本编码池)"""
  2. from typing import Any, 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.data_scope import apply_data_scope
  7. from app.core.database import get_db
  8. from app.core.menu_guard import require_admin_or_super_admin, require_menu_code
  9. from app.core.response import error_response, success_response
  10. from app.crud import cell_sample_type as cell_type_svc
  11. from app.crud import collection_region as region_svc
  12. from app.crud import collection_protocol as collection_protocol_svc
  13. from app.crud import collection_protocol_no as protocol_no_svc
  14. from app.crud import customer as customer_svc
  15. from app.crud import sample_code as sample_code_svc
  16. from app.models.models import (
  17. BizCellSampleType,
  18. BizCollectionProtocolNo,
  19. BizCollectionRegion,
  20. BizCustomer,
  21. BizSampleCode,
  22. SysUser,
  23. )
  24. router = APIRouter(prefix="/web/admin", tags=["基础信息管理"])
  25. def _get_tenant_no(db: Session, user: dict) -> str:
  26. user_id = user.get("user_id")
  27. if not user_id:
  28. raise PermissionError("用户身份信息缺失,无法确定租户")
  29. sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
  30. if not sys_user or not sys_user.tenant_no:
  31. raise PermissionError("用户租户信息缺失")
  32. return sys_user.tenant_no
  33. # ---------------------------------------------------------------------------
  34. # 客户
  35. # ---------------------------------------------------------------------------
  36. class CustomerCreateBody(BaseModel):
  37. model_config = ConfigDict(populate_by_name=True)
  38. customer_name: str = Field(..., alias="customerName")
  39. sex: Optional[str] = Field("2", alias="sex")
  40. id_card_no: Optional[str] = Field(None, alias="idCardNo")
  41. age: Optional[int] = None
  42. phone_no: Optional[str] = Field(None, alias="phoneNo")
  43. customer_tags: Optional[str] = Field(None, alias="customerTags")
  44. order_num: Optional[int] = Field(0, alias="orderNum")
  45. remark: Optional[str] = None
  46. class CustomerUpdateBody(BaseModel):
  47. model_config = ConfigDict(populate_by_name=True)
  48. customer_name: Optional[str] = Field(None, alias="customerName")
  49. sex: Optional[str] = None
  50. id_card_no: Optional[str] = Field(None, alias="idCardNo")
  51. age: Optional[int] = None
  52. phone_no: Optional[str] = Field(None, alias="phoneNo")
  53. customer_tags: Optional[str] = Field(None, alias="customerTags")
  54. order_num: Optional[int] = Field(None, alias="orderNum")
  55. remark: Optional[str] = None
  56. @router.get("/customers")
  57. async def list_customers(
  58. page: int = Query(1, ge=1),
  59. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  60. keyword: Optional[str] = None,
  61. user: dict = Depends(require_menu_code("biz:customer:list")),
  62. db: Session = Depends(get_db),
  63. ):
  64. tenant_no = _get_tenant_no(db, user)
  65. query = db.query(BizCustomer).filter(
  66. BizCustomer.del_flag == 0,
  67. BizCustomer.tenant_no == tenant_no,
  68. )
  69. if keyword:
  70. query = query.filter(BizCustomer.customer_name.like(f"%{keyword}%"))
  71. query = apply_data_scope(query, BizCustomer, user, db, tenant_no)
  72. total = query.count()
  73. items = query.order_by(BizCustomer.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
  74. result_list = [customer_svc._row_to_dict(r) for r in items]
  75. tp = customer_svc.total_pages(total, pageSize)
  76. return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  77. @router.post("/customers")
  78. async def create_customer(
  79. body: CustomerCreateBody,
  80. user: dict = Depends(require_admin_or_super_admin),
  81. db: Session = Depends(get_db),
  82. ):
  83. try:
  84. tenant_no = _get_tenant_no(db, user)
  85. obj = customer_svc.create_customer(
  86. db,
  87. customer_name=body.customer_name,
  88. sex=body.sex or "2",
  89. id_card_no=body.id_card_no,
  90. age=body.age,
  91. phone_no=body.phone_no,
  92. customer_tags=body.customer_tags,
  93. tenant_no=tenant_no,
  94. order_num=body.order_num,
  95. remark=body.remark,
  96. user_id=user.get("user_id"),
  97. )
  98. return success_response(customer_svc._row_to_dict(obj), message="创建成功")
  99. except ValueError as e:
  100. return error_response(400, str(e))
  101. @router.put("/customers/{customer_id}")
  102. async def update_customer(
  103. customer_id: str,
  104. body: CustomerUpdateBody,
  105. user: dict = Depends(require_admin_or_super_admin),
  106. db: Session = Depends(get_db),
  107. ):
  108. try:
  109. tenant_no = _get_tenant_no(db, user)
  110. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  111. obj = customer_svc.update_customer(
  112. db,
  113. customer_id,
  114. customer_name=payload.get("customer_name"),
  115. sex=payload.get("sex"),
  116. id_card_no=payload.get("id_card_no"),
  117. age=payload.get("age"),
  118. phone_no=payload.get("phone_no"),
  119. customer_tags=payload.get("customer_tags"),
  120. order_num=payload.get("order_num"),
  121. remark=payload.get("remark"),
  122. user_id=user.get("user_id"),
  123. tenant_no=tenant_no,
  124. )
  125. return success_response(customer_svc._row_to_dict(obj), message="更新成功")
  126. except ValueError as e:
  127. return error_response(400, str(e))
  128. @router.delete("/customers/{customer_id}")
  129. async def delete_customer(
  130. customer_id: str,
  131. user: dict = Depends(require_admin_or_super_admin),
  132. db: Session = Depends(get_db),
  133. ):
  134. try:
  135. tenant_no = _get_tenant_no(db, user)
  136. customer_svc.soft_delete_customer(db, customer_id, tenant_no=tenant_no)
  137. return success_response(None, message="删除成功")
  138. except ValueError as e:
  139. return error_response(400, str(e))
  140. # ---------------------------------------------------------------------------
  141. # 细胞样本类型
  142. # ---------------------------------------------------------------------------
  143. class CellSampleTypeCreateBody(BaseModel):
  144. model_config = ConfigDict(populate_by_name=True)
  145. type_code: str = Field(..., alias="typeCode")
  146. type_name: str = Field(..., alias="typeName")
  147. storage_years: Optional[int] = Field(None, alias="storageYears")
  148. storage_price: Optional[float] = Field(None, alias="storagePrice")
  149. order_num: Optional[int] = Field(0, alias="orderNum")
  150. remark: Optional[str] = None
  151. class CellSampleTypeUpdateBody(BaseModel):
  152. model_config = ConfigDict(populate_by_name=True)
  153. type_code: Optional[str] = Field(None, alias="typeCode")
  154. type_name: Optional[str] = Field(None, alias="typeName")
  155. storage_years: Optional[int] = Field(None, alias="storageYears")
  156. storage_price: Optional[float] = Field(None, alias="storagePrice")
  157. order_num: Optional[int] = Field(None, alias="orderNum")
  158. remark: Optional[str] = None
  159. @router.get("/cell-sample-types")
  160. async def list_cell_sample_types(
  161. page: int = Query(1, ge=1),
  162. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  163. keyword: Optional[str] = None,
  164. user: dict = Depends(require_menu_code("biz:cellSampleType:list")),
  165. db: Session = Depends(get_db),
  166. ):
  167. tenant_no = _get_tenant_no(db, user)
  168. query = db.query(BizCellSampleType).filter(
  169. BizCellSampleType.del_flag == 0,
  170. BizCellSampleType.tenant_no == tenant_no,
  171. )
  172. if keyword:
  173. query = query.filter(BizCellSampleType.type_name.like(f"%{keyword}%"))
  174. query = apply_data_scope(query, BizCellSampleType, user, db, tenant_no)
  175. total = query.count()
  176. items = query.order_by(BizCellSampleType.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
  177. result_list = [cell_type_svc._row_to_dict(r) for r in items]
  178. tp = cell_type_svc.total_pages(total, pageSize)
  179. return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  180. @router.post("/cell-sample-types")
  181. async def create_cell_sample_type(
  182. body: CellSampleTypeCreateBody,
  183. user: dict = Depends(require_admin_or_super_admin),
  184. db: Session = Depends(get_db),
  185. ):
  186. try:
  187. tenant_no = _get_tenant_no(db, user)
  188. obj = cell_type_svc.create_cell_sample_type(
  189. db,
  190. type_code=body.type_code,
  191. type_name=body.type_name,
  192. storage_years=body.storage_years,
  193. storage_price=body.storage_price,
  194. tenant_no=tenant_no,
  195. order_num=body.order_num,
  196. remark=body.remark,
  197. user_id=user.get("user_id"),
  198. )
  199. return success_response(cell_type_svc._row_to_dict(obj), message="创建成功")
  200. except ValueError as e:
  201. return error_response(400, str(e))
  202. @router.put("/cell-sample-types/{row_id}")
  203. async def update_cell_sample_type(
  204. row_id: str,
  205. body: CellSampleTypeUpdateBody,
  206. user: dict = Depends(require_admin_or_super_admin),
  207. db: Session = Depends(get_db),
  208. ):
  209. try:
  210. tenant_no = _get_tenant_no(db, user)
  211. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  212. obj = cell_type_svc.update_cell_sample_type(
  213. db,
  214. row_id,
  215. type_code=payload.get("type_code"),
  216. type_name=payload.get("type_name"),
  217. storage_years=payload.get("storage_years"),
  218. storage_price=payload.get("storage_price"),
  219. order_num=payload.get("order_num"),
  220. remark=payload.get("remark"),
  221. user_id=user.get("user_id"),
  222. tenant_no=tenant_no,
  223. )
  224. return success_response(cell_type_svc._row_to_dict(obj), message="更新成功")
  225. except ValueError as e:
  226. return error_response(400, str(e))
  227. @router.delete("/cell-sample-types/{row_id}")
  228. async def delete_cell_sample_type(
  229. row_id: str,
  230. user: dict = Depends(require_admin_or_super_admin),
  231. db: Session = Depends(get_db),
  232. ):
  233. try:
  234. tenant_no = _get_tenant_no(db, user)
  235. cell_type_svc.soft_delete_cell_sample_type(db, row_id, tenant_no=tenant_no)
  236. return success_response(None, message="删除成功")
  237. except ValueError as e:
  238. return error_response(400, str(e))
  239. # ---------------------------------------------------------------------------
  240. # 采集地区
  241. # ---------------------------------------------------------------------------
  242. class CollectionRegionCreateBody(BaseModel):
  243. model_config = ConfigDict(populate_by_name=True)
  244. region_name: str = Field(..., alias="regionName")
  245. order_num: Optional[int] = Field(0, alias="orderNum")
  246. remark: Optional[str] = None
  247. class CollectionRegionUpdateBody(BaseModel):
  248. model_config = ConfigDict(populate_by_name=True)
  249. region_name: Optional[str] = Field(None, alias="regionName")
  250. order_num: Optional[int] = Field(None, alias="orderNum")
  251. remark: Optional[str] = None
  252. @router.get("/collection-regions")
  253. async def list_collection_regions(
  254. page: int = Query(1, ge=1),
  255. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  256. keyword: Optional[str] = None,
  257. user: dict = Depends(require_menu_code("biz:collectionRegion:list")),
  258. db: Session = Depends(get_db),
  259. ):
  260. tenant_no = _get_tenant_no(db, user)
  261. query = db.query(BizCollectionRegion).filter(
  262. BizCollectionRegion.del_flag == 0,
  263. BizCollectionRegion.tenant_no == tenant_no,
  264. )
  265. if keyword:
  266. query = query.filter(BizCollectionRegion.region_name.like(f"%{keyword}%"))
  267. query = apply_data_scope(query, BizCollectionRegion, user, db, tenant_no)
  268. total = query.count()
  269. items = query.order_by(BizCollectionRegion.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
  270. result_list = [region_svc._region_to_dict(r) for r in items]
  271. tp = region_svc.total_pages(total, pageSize)
  272. return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  273. @router.post("/collection-regions")
  274. async def create_collection_region(
  275. body: CollectionRegionCreateBody,
  276. user: dict = Depends(require_admin_or_super_admin),
  277. db: Session = Depends(get_db),
  278. ):
  279. try:
  280. tenant_no = _get_tenant_no(db, user)
  281. obj = region_svc.create_collection_region(
  282. db,
  283. region_name=body.region_name,
  284. tenant_no=tenant_no,
  285. order_num=body.order_num,
  286. remark=body.remark,
  287. user_id=user.get("user_id"),
  288. )
  289. return success_response(region_svc._region_to_dict(obj), message="创建成功")
  290. except ValueError as e:
  291. return error_response(400, str(e))
  292. @router.put("/collection-regions/{region_id}")
  293. async def update_collection_region(
  294. region_id: str,
  295. body: CollectionRegionUpdateBody,
  296. user: dict = Depends(require_admin_or_super_admin),
  297. db: Session = Depends(get_db),
  298. ):
  299. try:
  300. tenant_no = _get_tenant_no(db, user)
  301. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  302. obj = region_svc.update_collection_region(
  303. db,
  304. region_id,
  305. region_name=payload.get("region_name"),
  306. order_num=payload.get("order_num"),
  307. remark=payload.get("remark"),
  308. user_id=user.get("user_id"),
  309. tenant_no=tenant_no,
  310. )
  311. return success_response(region_svc._region_to_dict(obj), message="更新成功")
  312. except ValueError as e:
  313. return error_response(400, str(e))
  314. @router.delete("/collection-regions/{region_id}")
  315. async def delete_collection_region(
  316. region_id: str,
  317. user: dict = Depends(require_admin_or_super_admin),
  318. db: Session = Depends(get_db),
  319. ):
  320. try:
  321. tenant_no = _get_tenant_no(db, user)
  322. region_svc.soft_delete_collection_region(db, region_id, tenant_no=tenant_no)
  323. return success_response(None, message="删除成功")
  324. except ValueError as e:
  325. return error_response(400, str(e))
  326. # ---------------------------------------------------------------------------
  327. # 采集地区 — 下属医院
  328. # ---------------------------------------------------------------------------
  329. class HospitalCreateBody(BaseModel):
  330. model_config = ConfigDict(populate_by_name=True)
  331. hospital_name: str = Field(..., alias="hospitalName")
  332. order_num: Optional[int] = Field(0, alias="orderNum")
  333. remark: Optional[str] = None
  334. class HospitalUpdateBody(BaseModel):
  335. model_config = ConfigDict(populate_by_name=True)
  336. hospital_name: Optional[str] = Field(None, alias="hospitalName")
  337. order_num: Optional[int] = Field(None, alias="orderNum")
  338. remark: Optional[str] = None
  339. @router.get("/collection-regions/{region_id}/hospitals")
  340. async def list_hospitals(
  341. region_id: str,
  342. page: int = Query(1, ge=1),
  343. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  344. keyword: Optional[str] = None,
  345. user: dict = Depends(require_menu_code("biz:collectionRegion:list")),
  346. db: Session = Depends(get_db),
  347. ):
  348. try:
  349. tenant_no = _get_tenant_no(db, user)
  350. items, total = region_svc.list_hospitals(
  351. db, region_id=region_id, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no
  352. )
  353. tp = region_svc.total_pages(total, pageSize)
  354. return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  355. except ValueError as e:
  356. return error_response(400, str(e))
  357. @router.post("/collection-regions/{region_id}/hospitals")
  358. async def create_hospital(
  359. region_id: str,
  360. body: HospitalCreateBody,
  361. user: dict = Depends(require_admin_or_super_admin),
  362. db: Session = Depends(get_db),
  363. ):
  364. try:
  365. tenant_no = _get_tenant_no(db, user)
  366. obj = region_svc.create_hospital(
  367. db,
  368. region_id=region_id,
  369. hospital_name=body.hospital_name,
  370. tenant_no=tenant_no,
  371. order_num=body.order_num,
  372. remark=body.remark,
  373. user_id=user.get("user_id"),
  374. )
  375. return success_response(region_svc._hospital_to_dict(obj), message="创建成功")
  376. except ValueError as e:
  377. return error_response(400, str(e))
  378. @router.put("/collection-regions/{region_id}/hospitals/{hospital_id}")
  379. async def update_hospital(
  380. region_id: str,
  381. hospital_id: str,
  382. body: HospitalUpdateBody,
  383. user: dict = Depends(require_admin_or_super_admin),
  384. db: Session = Depends(get_db),
  385. ):
  386. try:
  387. tenant_no = _get_tenant_no(db, user)
  388. h = region_svc.get_hospital(db, hospital_id, tenant_no=tenant_no)
  389. if not h or h.region_id != region_id:
  390. return error_response(400, "医院不存在")
  391. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  392. obj = region_svc.update_hospital(
  393. db,
  394. hospital_id,
  395. hospital_name=payload.get("hospital_name"),
  396. order_num=payload.get("order_num"),
  397. remark=payload.get("remark"),
  398. user_id=user.get("user_id"),
  399. tenant_no=tenant_no,
  400. )
  401. return success_response(region_svc._hospital_to_dict(obj), message="更新成功")
  402. except ValueError as e:
  403. return error_response(400, str(e))
  404. @router.delete("/collection-regions/{region_id}/hospitals/{hospital_id}")
  405. async def delete_hospital(
  406. region_id: str,
  407. hospital_id: str,
  408. user: dict = Depends(require_admin_or_super_admin),
  409. db: Session = Depends(get_db),
  410. ):
  411. try:
  412. tenant_no = _get_tenant_no(db, user)
  413. h = region_svc.get_hospital(db, hospital_id, tenant_no=tenant_no)
  414. if not h or h.region_id != region_id:
  415. return error_response(400, "医院不存在")
  416. region_svc.soft_delete_hospital(db, hospital_id, tenant_no=tenant_no)
  417. return success_response(None, message="删除成功")
  418. except ValueError as e:
  419. return error_response(400, str(e))
  420. # ---------------------------------------------------------------------------
  421. # 采集协议号池
  422. # ---------------------------------------------------------------------------
  423. class ProtocolNoCreateBody(BaseModel):
  424. model_config = ConfigDict(populate_by_name=True)
  425. protocol_no: str = Field(..., alias="protocolNo")
  426. issued: int = 0
  427. order_num: Optional[int] = Field(0, alias="orderNum")
  428. remark: Optional[str] = None
  429. class ProtocolNoUpdateBody(BaseModel):
  430. model_config = ConfigDict(populate_by_name=True)
  431. protocol_no: Optional[str] = Field(None, alias="protocolNo")
  432. issued: Optional[int] = None
  433. order_num: Optional[int] = Field(None, alias="orderNum")
  434. remark: Optional[str] = None
  435. issued_user_id: Optional[str] = Field(None, alias="issuedUserId")
  436. issued_user_name: Optional[str] = Field(None, alias="issuedUserName")
  437. issued_time: Optional[str] = Field(None, alias="issuedTime")
  438. @router.get("/collection-protocol-nos")
  439. async def list_protocol_nos(
  440. page: int = Query(1, ge=1),
  441. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  442. keyword: Optional[str] = None,
  443. user: dict = Depends(require_menu_code("biz:collectionProtocolNo:list")),
  444. db: Session = Depends(get_db),
  445. ):
  446. tenant_no = _get_tenant_no(db, user)
  447. query = db.query(BizCollectionProtocolNo).filter(
  448. BizCollectionProtocolNo.del_flag == 0,
  449. BizCollectionProtocolNo.tenant_no == tenant_no,
  450. )
  451. if keyword:
  452. query = query.filter(BizCollectionProtocolNo.protocol_no.like(f"%{keyword}%"))
  453. query = apply_data_scope(query, BizCollectionProtocolNo, user, db, tenant_no)
  454. total = query.count()
  455. items = query.order_by(BizCollectionProtocolNo.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
  456. result_list = [protocol_no_svc._row_to_dict(r) for r in items]
  457. used_nos = collection_protocol_svc.get_protocol_nos_used_in_protocols(
  458. db,
  459. tenant_no=tenant_no,
  460. protocol_nos=[row["protocolNo"] for row in result_list],
  461. )
  462. for row in result_list:
  463. row["usedInProtocol"] = row.get("protocolNo") in used_nos
  464. tp = protocol_no_svc.total_pages(total, pageSize)
  465. return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  466. class ProtocolNoBatchGenerateBody(BaseModel):
  467. model_config = ConfigDict(populate_by_name=True)
  468. prefix: str = Field("ZKZX", alias="prefix")
  469. middle: str = Field("34", alias="middle")
  470. seq_start: int = Field(..., alias="seqStart")
  471. seq_end: int = Field(..., alias="seqEnd")
  472. digit_count: int = Field(6, alias="digitCount")
  473. class ProtocolNoBatchIssueBody(BaseModel):
  474. model_config = ConfigDict(populate_by_name=True)
  475. ids: List[str] = Field(..., alias="ids")
  476. issued_user_id: str = Field(..., alias="issuedUserId")
  477. issued_user_name: Optional[str] = Field(None, alias="issuedUserName")
  478. @router.post("/collection-protocol-nos/batch-generate")
  479. async def batch_generate_protocol_nos(
  480. body: ProtocolNoBatchGenerateBody,
  481. user: dict = Depends(require_admin_or_super_admin),
  482. db: Session = Depends(get_db),
  483. ):
  484. try:
  485. tenant_no = _get_tenant_no(db, user)
  486. result = protocol_no_svc.batch_generate_protocol_nos(
  487. db,
  488. prefix=body.prefix,
  489. middle=body.middle,
  490. seq_start=body.seq_start,
  491. seq_end=body.seq_end,
  492. digit_count=body.digit_count,
  493. tenant_no=tenant_no,
  494. user_id=user.get("user_id"),
  495. )
  496. msg = f"成功生成 {result['created']} 条"
  497. if result['skipped'] > 0:
  498. msg += f",跳过已存在 {result['skipped']} 条"
  499. return success_response(result, message=msg)
  500. except ValueError as e:
  501. return error_response(400, str(e))
  502. @router.post("/collection-protocol-nos/batch-issue")
  503. async def batch_issue_protocol_nos(
  504. body: ProtocolNoBatchIssueBody,
  505. user: dict = Depends(require_admin_or_super_admin),
  506. db: Session = Depends(get_db),
  507. ):
  508. try:
  509. tenant_no = _get_tenant_no(db, user)
  510. result = protocol_no_svc.batch_issue_protocol_nos(
  511. db,
  512. ids=body.ids,
  513. issued_user_id=body.issued_user_id,
  514. issued_user_name=body.issued_user_name,
  515. tenant_no=tenant_no,
  516. user_id=user.get("user_id"),
  517. )
  518. msg = f"成功下发 {result['issuedCount']} 条"
  519. if result['alreadyIssued']:
  520. msg += f",{len(result['alreadyIssued'])} 条已下发被跳过"
  521. return success_response(result, message=msg)
  522. except ValueError as e:
  523. return error_response(400, str(e))
  524. class ProtocolNoBatchRevokeBody(BaseModel):
  525. model_config = ConfigDict(populate_by_name=True)
  526. ids: List[str] = Field(..., alias="ids")
  527. @router.post("/collection-protocol-nos/batch-revoke")
  528. async def batch_revoke_protocol_nos(
  529. body: ProtocolNoBatchRevokeBody,
  530. user: dict = Depends(require_admin_or_super_admin),
  531. db: Session = Depends(get_db),
  532. ):
  533. try:
  534. tenant_no = _get_tenant_no(db, user)
  535. result = protocol_no_svc.batch_revoke_protocol_nos(
  536. db,
  537. ids=body.ids,
  538. tenant_no=tenant_no,
  539. user_id=user.get("user_id"),
  540. )
  541. msg = f"成功收回 {result['revokedCount']} 条"
  542. if result["notIssued"]:
  543. msg += f",{len(result['notIssued'])} 条未下发已跳过"
  544. return success_response(result, message=msg)
  545. except ValueError as e:
  546. return error_response(400, str(e))
  547. @router.post("/collection-protocol-nos")
  548. async def create_protocol_no(
  549. body: ProtocolNoCreateBody,
  550. user: dict = Depends(require_admin_or_super_admin),
  551. db: Session = Depends(get_db),
  552. ):
  553. try:
  554. tenant_no = _get_tenant_no(db, user)
  555. obj = protocol_no_svc.create_protocol_no(
  556. db,
  557. protocol_no=body.protocol_no,
  558. issued=body.issued,
  559. tenant_no=tenant_no,
  560. order_num=body.order_num,
  561. remark=body.remark,
  562. user_id=user.get("user_id"),
  563. )
  564. return success_response(protocol_no_svc._row_to_dict(obj), message="创建成功")
  565. except ValueError as e:
  566. return error_response(400, str(e))
  567. @router.put("/collection-protocol-nos/{row_id}")
  568. async def update_protocol_no(
  569. row_id: str,
  570. body: ProtocolNoUpdateBody,
  571. user: dict = Depends(require_admin_or_super_admin),
  572. db: Session = Depends(get_db),
  573. ):
  574. try:
  575. tenant_no = _get_tenant_no(db, user)
  576. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  577. obj = protocol_no_svc.update_protocol_no(
  578. db,
  579. row_id,
  580. protocol_no=payload.get("protocol_no"),
  581. issued=payload.get("issued"),
  582. order_num=payload.get("order_num"),
  583. remark=payload.get("remark"),
  584. user_id=user.get("user_id"),
  585. tenant_no=tenant_no,
  586. issued_user_id=payload.get("issued_user_id"),
  587. issued_user_name=payload.get("issued_user_name"),
  588. issued_time=payload.get("issued_time"),
  589. )
  590. return success_response(protocol_no_svc._row_to_dict(obj), message="更新成功")
  591. except ValueError as e:
  592. return error_response(400, str(e))
  593. @router.delete("/collection-protocol-nos/{row_id}")
  594. async def delete_protocol_no(
  595. row_id: str,
  596. user: dict = Depends(require_admin_or_super_admin),
  597. db: Session = Depends(get_db),
  598. ):
  599. try:
  600. tenant_no = _get_tenant_no(db, user)
  601. protocol_no_svc.soft_delete_protocol_no(db, row_id, tenant_no=tenant_no)
  602. return success_response(None, message="删除成功")
  603. except ValueError as e:
  604. return error_response(400, str(e))
  605. # ---------------------------------------------------------------------------
  606. # 样本编码池
  607. # ---------------------------------------------------------------------------
  608. class SampleCodeCreateBody(BaseModel):
  609. model_config = ConfigDict(populate_by_name=True)
  610. sample_code: str = Field(..., alias="sampleCode")
  611. issued: int = 0
  612. order_num: Optional[int] = Field(0, alias="orderNum")
  613. remark: Optional[str] = None
  614. class SampleCodeUpdateBody(BaseModel):
  615. model_config = ConfigDict(populate_by_name=True)
  616. sample_code: Optional[str] = Field(None, alias="sampleCode")
  617. issued: Optional[int] = None
  618. order_num: Optional[int] = Field(None, alias="orderNum")
  619. remark: Optional[str] = None
  620. issued_user_id: Optional[str] = Field(None, alias="issuedUserId")
  621. issued_user_name: Optional[str] = Field(None, alias="issuedUserName")
  622. issued_time: Optional[str] = Field(None, alias="issuedTime")
  623. class SampleCodeBatchGenerateBody(BaseModel):
  624. model_config = ConfigDict(populate_by_name=True)
  625. sample_code_type: str = Field(..., alias="sampleCodeType")
  626. middle: str = Field("34", alias="middle")
  627. seq_start: int = Field(..., alias="seqStart")
  628. seq_end: int = Field(..., alias="seqEnd")
  629. digit_count: int = Field(6, alias="digitCount")
  630. class SampleCodeBatchIssueBody(BaseModel):
  631. model_config = ConfigDict(populate_by_name=True)
  632. ids: List[str] = Field(..., alias="ids")
  633. issued_user_id: str = Field(..., alias="issuedUserId")
  634. issued_user_name: Optional[str] = Field(None, alias="issuedUserName")
  635. @router.post("/sample-codes/batch-generate")
  636. async def batch_generate_sample_codes(
  637. body: SampleCodeBatchGenerateBody,
  638. user: dict = Depends(require_admin_or_super_admin),
  639. db: Session = Depends(get_db),
  640. ):
  641. try:
  642. tenant_no = _get_tenant_no(db, user)
  643. result = sample_code_svc.batch_generate_sample_codes(
  644. db,
  645. sample_code_type=body.sample_code_type,
  646. middle=body.middle,
  647. seq_start=body.seq_start,
  648. seq_end=body.seq_end,
  649. digit_count=body.digit_count,
  650. tenant_no=tenant_no,
  651. user_id=user.get("user_id"),
  652. )
  653. msg = f"成功生成 {result['created']} 条"
  654. if result["skipped"] > 0:
  655. msg += f",跳过已存在 {result['skipped']} 条"
  656. return success_response(result, message=msg)
  657. except ValueError as e:
  658. return error_response(400, str(e))
  659. @router.post("/sample-codes/batch-issue")
  660. async def batch_issue_sample_codes(
  661. body: SampleCodeBatchIssueBody,
  662. user: dict = Depends(require_admin_or_super_admin),
  663. db: Session = Depends(get_db),
  664. ):
  665. try:
  666. tenant_no = _get_tenant_no(db, user)
  667. result = sample_code_svc.batch_issue_sample_codes(
  668. db,
  669. ids=body.ids,
  670. issued_user_id=body.issued_user_id,
  671. issued_user_name=body.issued_user_name,
  672. tenant_no=tenant_no,
  673. user_id=user.get("user_id"),
  674. )
  675. msg = f"成功下发 {result['issuedCount']} 条"
  676. if result["alreadyIssued"]:
  677. msg += f",{len(result['alreadyIssued'])} 条已下发被跳过"
  678. return success_response(result, message=msg)
  679. except ValueError as e:
  680. return error_response(400, str(e))
  681. class SampleCodeBatchRevokeBody(BaseModel):
  682. model_config = ConfigDict(populate_by_name=True)
  683. ids: List[str] = Field(..., alias="ids")
  684. @router.post("/sample-codes/batch-revoke")
  685. async def batch_revoke_sample_codes(
  686. body: SampleCodeBatchRevokeBody,
  687. user: dict = Depends(require_admin_or_super_admin),
  688. db: Session = Depends(get_db),
  689. ):
  690. try:
  691. tenant_no = _get_tenant_no(db, user)
  692. result = sample_code_svc.batch_revoke_sample_codes(
  693. db,
  694. ids=body.ids,
  695. tenant_no=tenant_no,
  696. user_id=user.get("user_id"),
  697. )
  698. msg = f"成功收回 {result['revokedCount']} 条"
  699. if result["notIssued"]:
  700. msg += f",{len(result['notIssued'])} 条未下发已跳过"
  701. return success_response(result, message=msg)
  702. except ValueError as e:
  703. return error_response(400, str(e))
  704. @router.get("/sample-codes")
  705. async def list_sample_codes(
  706. page: int = Query(1, ge=1),
  707. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  708. keyword: Optional[str] = None,
  709. user: dict = Depends(require_menu_code("biz:sampleCode:list")),
  710. db: Session = Depends(get_db),
  711. ):
  712. tenant_no = _get_tenant_no(db, user)
  713. query = db.query(BizSampleCode).filter(
  714. BizSampleCode.del_flag == 0,
  715. BizSampleCode.tenant_no == tenant_no,
  716. )
  717. if keyword:
  718. query = query.filter(BizSampleCode.sample_code.like(f"%{keyword}%"))
  719. query = apply_data_scope(query, BizSampleCode, user, db, tenant_no)
  720. total = query.count()
  721. items = query.order_by(BizSampleCode.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
  722. result_list = [sample_code_svc._row_to_dict(r) for r in items]
  723. used_codes = collection_protocol_svc.get_sample_codes_used_in_protocol_samples(
  724. db,
  725. tenant_no=tenant_no,
  726. sample_codes=[row["sampleCode"] for row in result_list],
  727. )
  728. for row in result_list:
  729. row["usedInProtocol"] = row.get("sampleCode") in used_codes
  730. tp = sample_code_svc.total_pages(total, pageSize)
  731. return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  732. @router.post("/sample-codes")
  733. async def create_sample_code(
  734. body: SampleCodeCreateBody,
  735. user: dict = Depends(require_admin_or_super_admin),
  736. db: Session = Depends(get_db),
  737. ):
  738. try:
  739. tenant_no = _get_tenant_no(db, user)
  740. obj = sample_code_svc.create_sample_code(
  741. db,
  742. sample_code=body.sample_code,
  743. issued=body.issued,
  744. tenant_no=tenant_no,
  745. order_num=body.order_num,
  746. remark=body.remark,
  747. user_id=user.get("user_id"),
  748. )
  749. return success_response(sample_code_svc._row_to_dict(obj), message="创建成功")
  750. except ValueError as e:
  751. return error_response(400, str(e))
  752. @router.put("/sample-codes/{row_id}")
  753. async def update_sample_code(
  754. row_id: str,
  755. body: SampleCodeUpdateBody,
  756. user: dict = Depends(require_admin_or_super_admin),
  757. db: Session = Depends(get_db),
  758. ):
  759. try:
  760. tenant_no = _get_tenant_no(db, user)
  761. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  762. obj = sample_code_svc.update_sample_code(
  763. db,
  764. row_id,
  765. sample_code=payload.get("sample_code"),
  766. issued=payload.get("issued"),
  767. order_num=payload.get("order_num"),
  768. remark=payload.get("remark"),
  769. user_id=user.get("user_id"),
  770. tenant_no=tenant_no,
  771. issued_user_id=payload.get("issued_user_id"),
  772. issued_user_name=payload.get("issued_user_name"),
  773. issued_time=payload.get("issued_time"),
  774. )
  775. return success_response(sample_code_svc._row_to_dict(obj), message="更新成功")
  776. except ValueError as e:
  777. return error_response(400, str(e))
  778. @router.delete("/sample-codes/{row_id}")
  779. async def delete_sample_code(
  780. row_id: str,
  781. user: dict = Depends(require_admin_or_super_admin),
  782. db: Session = Depends(get_db),
  783. ):
  784. try:
  785. tenant_no = _get_tenant_no(db, user)
  786. sample_code_svc.soft_delete_sample_code(db, row_id, tenant_no=tenant_no)
  787. return success_response(None, message="删除成功")
  788. except ValueError as e:
  789. return error_response(400, str(e))