basic_info.py 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196
  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 import or_
  6. from sqlalchemy.orm import Session
  7. from app.core.data_scope import apply_data_scope
  8. from app.core.database import get_db
  9. from app.core.menu_guard import require_admin_or_super_admin, require_menu_code
  10. from app.core.response import error_response, success_response
  11. from app.crud import cell_sample_type as cell_type_svc
  12. from app.crud import collection_region as region_svc
  13. from app.crud import collection_protocol as collection_protocol_svc
  14. from app.crud import collection_protocol_no as protocol_no_svc
  15. from app.crud import customer as customer_svc
  16. from app.crud import sample_code as sample_code_svc
  17. from app.models.models import (
  18. BizCellSampleType,
  19. BizCollectionProtocolNo,
  20. BizCollectionRegion,
  21. BizCustomer,
  22. BizSampleCode,
  23. SysUser,
  24. )
  25. from app.services.customer_relations import build_customer_relations
  26. router = APIRouter(prefix="/web/admin", tags=["基础信息管理"])
  27. def _get_tenant_no(db: Session, user: dict) -> str:
  28. user_id = user.get("user_id")
  29. if not user_id:
  30. raise PermissionError("用户身份信息缺失,无法确定租户")
  31. sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
  32. if not sys_user or not sys_user.tenant_no:
  33. raise PermissionError("用户租户信息缺失")
  34. return sys_user.tenant_no
  35. # ---------------------------------------------------------------------------
  36. # 客户
  37. # ---------------------------------------------------------------------------
  38. class CustomerCreateBody(BaseModel):
  39. model_config = ConfigDict(populate_by_name=True)
  40. customer_name: str = Field(..., alias="customerName")
  41. sex: Optional[str] = Field("2", alias="sex")
  42. id_card_no: Optional[str] = Field(None, alias="idCardNo")
  43. age: Optional[int] = None
  44. birth_ym: Optional[str] = Field(None, alias="birthYm")
  45. ethnic: Optional[str] = None
  46. email: Optional[str] = None
  47. address: Optional[str] = None
  48. phone_no: Optional[str] = Field(None, alias="phoneNo")
  49. customer_tags: Optional[str] = Field(None, alias="customerTags")
  50. order_num: Optional[int] = Field(0, alias="orderNum")
  51. remark: Optional[str] = None
  52. class CustomerUpdateBody(BaseModel):
  53. model_config = ConfigDict(populate_by_name=True)
  54. customer_name: Optional[str] = Field(None, alias="customerName")
  55. sex: Optional[str] = None
  56. id_card_no: Optional[str] = Field(None, alias="idCardNo")
  57. age: Optional[int] = None
  58. birth_ym: Optional[str] = Field(None, alias="birthYm")
  59. ethnic: Optional[str] = None
  60. email: Optional[str] = None
  61. address: Optional[str] = None
  62. phone_no: Optional[str] = Field(None, alias="phoneNo")
  63. customer_tags: Optional[str] = Field(None, alias="customerTags")
  64. order_num: Optional[int] = Field(None, alias="orderNum")
  65. remark: Optional[str] = None
  66. def _normalize_date_bound(value: str, *, end_of_day: bool = False) -> str:
  67. val = (value or "").strip()
  68. if len(val) == 10:
  69. return f"{val} 23:59:59" if end_of_day else f"{val} 00:00:00"
  70. return val
  71. @router.get("/customers")
  72. async def list_customers(
  73. page: int = Query(1, ge=1),
  74. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  75. keyword: Optional[str] = None,
  76. createTimeStart: Optional[str] = Query(None, alias="createTimeStart"),
  77. createTimeEnd: Optional[str] = Query(None, alias="createTimeEnd"),
  78. user: dict = Depends(require_menu_code("biz:customer:list")),
  79. db: Session = Depends(get_db),
  80. ):
  81. tenant_no = _get_tenant_no(db, user)
  82. query = db.query(BizCustomer).filter(
  83. BizCustomer.del_flag == 0,
  84. BizCustomer.tenant_no == tenant_no,
  85. )
  86. if keyword:
  87. kw = keyword.strip()
  88. if kw:
  89. like = f"%{kw}%"
  90. query = query.filter(
  91. or_(
  92. BizCustomer.customer_name.like(like),
  93. BizCustomer.phone_no.like(like),
  94. BizCustomer.id_card_no.like(like),
  95. BizCustomer.customer_tags.like(like),
  96. )
  97. )
  98. if createTimeStart:
  99. query = query.filter(
  100. BizCustomer.create_time >= _normalize_date_bound(createTimeStart)
  101. )
  102. if createTimeEnd:
  103. query = query.filter(
  104. BizCustomer.create_time <= _normalize_date_bound(createTimeEnd, end_of_day=True)
  105. )
  106. query = apply_data_scope(query, BizCustomer, user, db, tenant_no)
  107. total = query.count()
  108. items = query.order_by(BizCustomer.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
  109. result_list = [customer_svc._row_to_dict(r) for r in items]
  110. tp = customer_svc.total_pages(total, pageSize)
  111. return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  112. @router.get("/customers/{customer_id}/relations")
  113. async def get_customer_relations(
  114. customer_id: str,
  115. user: dict = Depends(require_menu_code("biz:customer:list")),
  116. db: Session = Depends(get_db),
  117. ):
  118. tenant_no = _get_tenant_no(db, user)
  119. data = build_customer_relations(db, customer_id, tenant_no)
  120. if not data:
  121. return error_response(404, "客户不存在")
  122. return success_response(data)
  123. @router.post("/customers")
  124. async def create_customer(
  125. body: CustomerCreateBody,
  126. user: dict = Depends(require_admin_or_super_admin),
  127. db: Session = Depends(get_db),
  128. ):
  129. try:
  130. tenant_no = _get_tenant_no(db, user)
  131. obj = customer_svc.create_customer(
  132. db,
  133. customer_name=body.customer_name,
  134. sex=body.sex or "2",
  135. id_card_no=body.id_card_no,
  136. age=body.age,
  137. birth_ym=body.birth_ym,
  138. ethnic=body.ethnic,
  139. email=body.email,
  140. address=body.address,
  141. phone_no=body.phone_no,
  142. customer_tags=body.customer_tags,
  143. tenant_no=tenant_no,
  144. order_num=body.order_num,
  145. remark=body.remark,
  146. user_id=user.get("user_id"),
  147. )
  148. return success_response(customer_svc._row_to_dict(obj), message="创建成功")
  149. except ValueError as e:
  150. return error_response(400, str(e))
  151. @router.put("/customers/{customer_id}")
  152. async def update_customer(
  153. customer_id: str,
  154. body: CustomerUpdateBody,
  155. user: dict = Depends(require_admin_or_super_admin),
  156. db: Session = Depends(get_db),
  157. ):
  158. try:
  159. tenant_no = _get_tenant_no(db, user)
  160. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  161. obj = customer_svc.update_customer(
  162. db,
  163. customer_id,
  164. customer_name=payload.get("customer_name"),
  165. sex=payload.get("sex"),
  166. id_card_no=payload.get("id_card_no"),
  167. age=payload.get("age"),
  168. birth_ym=payload.get("birth_ym"),
  169. ethnic=payload.get("ethnic"),
  170. email=payload.get("email"),
  171. address=payload.get("address"),
  172. phone_no=payload.get("phone_no"),
  173. customer_tags=payload.get("customer_tags"),
  174. order_num=payload.get("order_num"),
  175. remark=payload.get("remark"),
  176. user_id=user.get("user_id"),
  177. tenant_no=tenant_no,
  178. )
  179. return success_response(customer_svc._row_to_dict(obj), message="更新成功")
  180. except ValueError as e:
  181. return error_response(400, str(e))
  182. @router.delete("/customers/{customer_id}")
  183. async def delete_customer(
  184. customer_id: str,
  185. user: dict = Depends(require_admin_or_super_admin),
  186. db: Session = Depends(get_db),
  187. ):
  188. try:
  189. tenant_no = _get_tenant_no(db, user)
  190. customer_svc.soft_delete_customer(db, customer_id, tenant_no=tenant_no)
  191. return success_response(None, message="删除成功")
  192. except ValueError as e:
  193. return error_response(400, str(e))
  194. # ---------------------------------------------------------------------------
  195. # 细胞样本类型
  196. # ---------------------------------------------------------------------------
  197. class CellSampleTypeCreateBody(BaseModel):
  198. model_config = ConfigDict(populate_by_name=True)
  199. type_code: str = Field(..., alias="typeCode")
  200. type_name: str = Field(..., alias="typeName")
  201. type_logo: Optional[str] = Field(None, alias="typeLogo")
  202. storage_years: Optional[int] = Field(None, alias="storageYears")
  203. storage_price: Optional[float] = Field(None, alias="storagePrice")
  204. sample_code_filter: Optional[str] = Field(None, alias="sampleCodeFilter")
  205. order_num: Optional[int] = Field(0, alias="orderNum")
  206. remark: Optional[str] = None
  207. class CellSampleTypeUpdateBody(BaseModel):
  208. model_config = ConfigDict(populate_by_name=True)
  209. type_code: Optional[str] = Field(None, alias="typeCode")
  210. type_name: Optional[str] = Field(None, alias="typeName")
  211. type_logo: Optional[str] = Field(None, alias="typeLogo")
  212. storage_years: Optional[int] = Field(None, alias="storageYears")
  213. storage_price: Optional[float] = Field(None, alias="storagePrice")
  214. sample_code_filter: Optional[str] = Field(None, alias="sampleCodeFilter")
  215. order_num: Optional[int] = Field(None, alias="orderNum")
  216. remark: Optional[str] = None
  217. @router.get("/cell-sample-types")
  218. async def list_cell_sample_types(
  219. page: int = Query(1, ge=1),
  220. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  221. keyword: Optional[str] = None,
  222. user: dict = Depends(require_menu_code("biz:cellSampleType:list")),
  223. db: Session = Depends(get_db),
  224. ):
  225. tenant_no = _get_tenant_no(db, user)
  226. query = db.query(BizCellSampleType).filter(
  227. BizCellSampleType.del_flag == 0,
  228. BizCellSampleType.tenant_no == tenant_no,
  229. )
  230. if keyword:
  231. kw = keyword.strip()
  232. if kw:
  233. like = f"%{kw}%"
  234. query = query.filter(
  235. or_(
  236. BizCellSampleType.type_code.like(like),
  237. BizCellSampleType.type_name.like(like),
  238. )
  239. )
  240. query = apply_data_scope(query, BizCellSampleType, user, db, tenant_no)
  241. total = query.count()
  242. items = query.order_by(BizCellSampleType.order_num.asc(), BizCellSampleType.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
  243. result_list = [cell_type_svc._row_to_dict(r) for r in items]
  244. tp = cell_type_svc.total_pages(total, pageSize)
  245. return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  246. @router.post("/cell-sample-types")
  247. async def create_cell_sample_type(
  248. body: CellSampleTypeCreateBody,
  249. user: dict = Depends(require_admin_or_super_admin),
  250. db: Session = Depends(get_db),
  251. ):
  252. try:
  253. tenant_no = _get_tenant_no(db, user)
  254. obj = cell_type_svc.create_cell_sample_type(
  255. db,
  256. type_code=body.type_code,
  257. type_name=body.type_name,
  258. type_logo=body.type_logo,
  259. storage_years=body.storage_years,
  260. storage_price=body.storage_price,
  261. sample_code_filter=body.sample_code_filter,
  262. tenant_no=tenant_no,
  263. order_num=body.order_num,
  264. remark=body.remark,
  265. user_id=user.get("user_id"),
  266. )
  267. return success_response(cell_type_svc._row_to_dict(obj), message="创建成功")
  268. except ValueError as e:
  269. return error_response(400, str(e))
  270. @router.put("/cell-sample-types/{row_id}")
  271. async def update_cell_sample_type(
  272. row_id: str,
  273. body: CellSampleTypeUpdateBody,
  274. user: dict = Depends(require_admin_or_super_admin),
  275. db: Session = Depends(get_db),
  276. ):
  277. try:
  278. tenant_no = _get_tenant_no(db, user)
  279. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  280. obj = cell_type_svc.update_cell_sample_type(
  281. db,
  282. row_id,
  283. type_code=payload.get("type_code"),
  284. type_name=payload.get("type_name"),
  285. type_logo=payload.get("type_logo"),
  286. storage_years=payload.get("storage_years"),
  287. storage_price=payload.get("storage_price"),
  288. sample_code_filter=payload.get("sample_code_filter"),
  289. order_num=payload.get("order_num"),
  290. remark=payload.get("remark"),
  291. user_id=user.get("user_id"),
  292. tenant_no=tenant_no,
  293. )
  294. return success_response(cell_type_svc._row_to_dict(obj), message="更新成功")
  295. except ValueError as e:
  296. return error_response(400, str(e))
  297. @router.delete("/cell-sample-types/{row_id}")
  298. async def delete_cell_sample_type(
  299. row_id: str,
  300. user: dict = Depends(require_admin_or_super_admin),
  301. db: Session = Depends(get_db),
  302. ):
  303. try:
  304. tenant_no = _get_tenant_no(db, user)
  305. cell_type_svc.soft_delete_cell_sample_type(db, row_id, tenant_no=tenant_no)
  306. return success_response(None, message="删除成功")
  307. except ValueError as e:
  308. return error_response(400, str(e))
  309. # ---------------------------------------------------------------------------
  310. # 采集地区
  311. # ---------------------------------------------------------------------------
  312. class CollectionRegionCreateBody(BaseModel):
  313. model_config = ConfigDict(populate_by_name=True)
  314. region_name: str = Field(..., alias="regionName")
  315. order_num: Optional[int] = Field(0, alias="orderNum")
  316. remark: Optional[str] = None
  317. class CollectionRegionUpdateBody(BaseModel):
  318. model_config = ConfigDict(populate_by_name=True)
  319. region_name: Optional[str] = Field(None, alias="regionName")
  320. order_num: Optional[int] = Field(None, alias="orderNum")
  321. remark: Optional[str] = None
  322. @router.get("/collection-regions")
  323. async def list_collection_regions(
  324. page: int = Query(1, ge=1),
  325. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  326. keyword: Optional[str] = None,
  327. user: dict = Depends(require_menu_code("biz:collectionRegion:list")),
  328. db: Session = Depends(get_db),
  329. ):
  330. tenant_no = _get_tenant_no(db, user)
  331. query = db.query(BizCollectionRegion).filter(
  332. BizCollectionRegion.del_flag == 0,
  333. BizCollectionRegion.tenant_no == tenant_no,
  334. )
  335. if keyword:
  336. query = query.filter(BizCollectionRegion.region_name.like(f"%{keyword}%"))
  337. query = apply_data_scope(query, BizCollectionRegion, user, db, tenant_no)
  338. total = query.count()
  339. items = query.order_by(BizCollectionRegion.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
  340. region_ids = [r.id for r in items]
  341. hospital_map = region_svc._hospital_names_by_region_ids(db, region_ids)
  342. staff_map = region_svc._staff_names_by_region_ids(db, region_ids)
  343. result_list = [
  344. region_svc._region_to_dict(
  345. r,
  346. hospital_names=hospital_map.get(r.id, []),
  347. staff_names=staff_map.get(r.id, ([], []))[0],
  348. leader_names=staff_map.get(r.id, ([], []))[1],
  349. )
  350. for r in items
  351. ]
  352. tp = region_svc.total_pages(total, pageSize)
  353. return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  354. @router.post("/collection-regions")
  355. async def create_collection_region(
  356. body: CollectionRegionCreateBody,
  357. user: dict = Depends(require_admin_or_super_admin),
  358. db: Session = Depends(get_db),
  359. ):
  360. try:
  361. tenant_no = _get_tenant_no(db, user)
  362. obj = region_svc.create_collection_region(
  363. db,
  364. region_name=body.region_name,
  365. tenant_no=tenant_no,
  366. order_num=body.order_num,
  367. remark=body.remark,
  368. user_id=user.get("user_id"),
  369. )
  370. return success_response(region_svc._region_to_dict(obj), message="创建成功")
  371. except ValueError as e:
  372. return error_response(400, str(e))
  373. @router.put("/collection-regions/{region_id}")
  374. async def update_collection_region(
  375. region_id: str,
  376. body: CollectionRegionUpdateBody,
  377. user: dict = Depends(require_admin_or_super_admin),
  378. db: Session = Depends(get_db),
  379. ):
  380. try:
  381. tenant_no = _get_tenant_no(db, user)
  382. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  383. obj = region_svc.update_collection_region(
  384. db,
  385. region_id,
  386. region_name=payload.get("region_name"),
  387. order_num=payload.get("order_num"),
  388. remark=payload.get("remark"),
  389. user_id=user.get("user_id"),
  390. tenant_no=tenant_no,
  391. )
  392. return success_response(region_svc._region_to_dict(obj), message="更新成功")
  393. except ValueError as e:
  394. return error_response(400, str(e))
  395. @router.delete("/collection-regions/{region_id}")
  396. async def delete_collection_region(
  397. region_id: str,
  398. user: dict = Depends(require_admin_or_super_admin),
  399. db: Session = Depends(get_db),
  400. ):
  401. try:
  402. tenant_no = _get_tenant_no(db, user)
  403. region_svc.soft_delete_collection_region(db, region_id, tenant_no=tenant_no)
  404. return success_response(None, message="删除成功")
  405. except ValueError as e:
  406. return error_response(400, str(e))
  407. @router.get("/collection-regions/by-staff-user/{user_id}")
  408. async def api_list_regions_by_staff_user(
  409. user_id: str,
  410. user: dict = Depends(require_menu_code("biz:collectionRegion:list")),
  411. db: Session = Depends(get_db),
  412. ):
  413. tenant_no = _get_tenant_no(db, user)
  414. items = region_svc.list_regions_by_staff_user(db, user_id=user_id, tenant_no=tenant_no)
  415. return success_response({"list": items})
  416. # ---------------------------------------------------------------------------
  417. # 采集地区 — 下属医院
  418. # ---------------------------------------------------------------------------
  419. class HospitalCreateBody(BaseModel):
  420. model_config = ConfigDict(populate_by_name=True)
  421. hospital_name: str = Field(..., alias="hospitalName")
  422. order_num: Optional[int] = Field(0, alias="orderNum")
  423. remark: Optional[str] = None
  424. class HospitalUpdateBody(BaseModel):
  425. model_config = ConfigDict(populate_by_name=True)
  426. hospital_name: Optional[str] = Field(None, alias="hospitalName")
  427. order_num: Optional[int] = Field(None, alias="orderNum")
  428. remark: Optional[str] = None
  429. @router.get("/collection-hospitals/search")
  430. async def search_collection_hospitals(
  431. keyword: str = Query(..., min_length=1),
  432. limit: int = Query(20, ge=1, le=50),
  433. user: dict = Depends(require_menu_code("biz:collectionRegion:list")),
  434. db: Session = Depends(get_db),
  435. ):
  436. tenant_no = _get_tenant_no(db, user)
  437. items = region_svc.search_hospitals_by_keyword(db, keyword=keyword, tenant_no=tenant_no, limit=limit)
  438. return success_response({"list": items})
  439. @router.get("/collection-regions/{region_id}/hospitals")
  440. async def list_hospitals(
  441. region_id: str,
  442. page: int = Query(1, ge=1),
  443. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  444. keyword: Optional[str] = None,
  445. user: dict = Depends(require_menu_code("biz:collectionRegion:list")),
  446. db: Session = Depends(get_db),
  447. ):
  448. try:
  449. tenant_no = _get_tenant_no(db, user)
  450. items, total = region_svc.list_hospitals(
  451. db, region_id=region_id, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no
  452. )
  453. tp = region_svc.total_pages(total, pageSize)
  454. return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  455. except ValueError as e:
  456. return error_response(400, str(e))
  457. @router.post("/collection-regions/{region_id}/hospitals")
  458. async def create_hospital(
  459. region_id: str,
  460. body: HospitalCreateBody,
  461. user: dict = Depends(require_admin_or_super_admin),
  462. db: Session = Depends(get_db),
  463. ):
  464. try:
  465. tenant_no = _get_tenant_no(db, user)
  466. obj = region_svc.create_hospital(
  467. db,
  468. region_id=region_id,
  469. hospital_name=body.hospital_name,
  470. tenant_no=tenant_no,
  471. order_num=body.order_num,
  472. remark=body.remark,
  473. user_id=user.get("user_id"),
  474. )
  475. return success_response(region_svc._hospital_to_dict(obj), message="创建成功")
  476. except ValueError as e:
  477. return error_response(400, str(e))
  478. @router.put("/collection-regions/{region_id}/hospitals/{hospital_id}")
  479. async def update_hospital(
  480. region_id: str,
  481. hospital_id: str,
  482. body: HospitalUpdateBody,
  483. user: dict = Depends(require_admin_or_super_admin),
  484. db: Session = Depends(get_db),
  485. ):
  486. try:
  487. tenant_no = _get_tenant_no(db, user)
  488. h = region_svc.get_hospital(db, hospital_id, tenant_no=tenant_no)
  489. if not h or h.region_id != region_id:
  490. return error_response(400, "医院不存在")
  491. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  492. obj = region_svc.update_hospital(
  493. db,
  494. hospital_id,
  495. hospital_name=payload.get("hospital_name"),
  496. order_num=payload.get("order_num"),
  497. remark=payload.get("remark"),
  498. user_id=user.get("user_id"),
  499. tenant_no=tenant_no,
  500. )
  501. return success_response(region_svc._hospital_to_dict(obj), message="更新成功")
  502. except ValueError as e:
  503. return error_response(400, str(e))
  504. @router.delete("/collection-regions/{region_id}/hospitals/{hospital_id}")
  505. async def delete_hospital(
  506. region_id: str,
  507. hospital_id: str,
  508. user: dict = Depends(require_admin_or_super_admin),
  509. db: Session = Depends(get_db),
  510. ):
  511. try:
  512. tenant_no = _get_tenant_no(db, user)
  513. h = region_svc.get_hospital(db, hospital_id, tenant_no=tenant_no)
  514. if not h or h.region_id != region_id:
  515. return error_response(400, "医院不存在")
  516. region_svc.soft_delete_hospital(db, hospital_id, tenant_no=tenant_no)
  517. return success_response(None, message="删除成功")
  518. except ValueError as e:
  519. return error_response(400, str(e))
  520. # ---------------------------------------------------------------------------
  521. # 采集地区 — 工作人员
  522. # ---------------------------------------------------------------------------
  523. class RegionStaffCreateBody(BaseModel):
  524. model_config = ConfigDict(populate_by_name=True)
  525. user_id: str = Field(..., alias="userId")
  526. is_leader: Optional[bool] = Field(False, alias="isLeader")
  527. order_num: Optional[int] = Field(0, alias="orderNum")
  528. remark: Optional[str] = None
  529. class RegionStaffUpdateBody(BaseModel):
  530. model_config = ConfigDict(populate_by_name=True)
  531. is_leader: Optional[bool] = Field(None, alias="isLeader")
  532. order_num: Optional[int] = Field(None, alias="orderNum")
  533. remark: Optional[str] = None
  534. @router.get("/collection-regions/{region_id}/staff")
  535. async def list_region_staff(
  536. region_id: str,
  537. page: int = Query(1, ge=1),
  538. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  539. user: dict = Depends(require_menu_code("biz:collectionRegion:list")),
  540. db: Session = Depends(get_db),
  541. ):
  542. try:
  543. tenant_no = _get_tenant_no(db, user)
  544. items, total = region_svc.list_staff(
  545. db, region_id=region_id, page=page, page_size=pageSize, tenant_no=tenant_no
  546. )
  547. tp = region_svc.total_pages(total, pageSize)
  548. return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  549. except ValueError as e:
  550. return error_response(400, str(e))
  551. @router.post("/collection-regions/{region_id}/staff")
  552. async def create_region_staff(
  553. region_id: str,
  554. body: RegionStaffCreateBody,
  555. user: dict = Depends(require_admin_or_super_admin),
  556. db: Session = Depends(get_db),
  557. ):
  558. try:
  559. tenant_no = _get_tenant_no(db, user)
  560. obj = region_svc.create_staff(
  561. db,
  562. region_id=region_id,
  563. user_id=body.user_id,
  564. is_leader=bool(body.is_leader),
  565. tenant_no=tenant_no,
  566. order_num=body.order_num,
  567. remark=body.remark,
  568. user_id_actor=user.get("user_id"),
  569. )
  570. # enrich response with user display fields
  571. items, _ = region_svc.list_staff(db, region_id=region_id, page=1, page_size=100, tenant_no=tenant_no)
  572. matched = next((x for x in items if x["id"] == obj.id), region_svc._staff_to_dict(obj))
  573. return success_response(matched, message="创建成功")
  574. except ValueError as e:
  575. return error_response(400, str(e))
  576. @router.put("/collection-regions/{region_id}/staff/{staff_id}")
  577. async def update_region_staff(
  578. region_id: str,
  579. staff_id: str,
  580. body: RegionStaffUpdateBody,
  581. user: dict = Depends(require_admin_or_super_admin),
  582. db: Session = Depends(get_db),
  583. ):
  584. try:
  585. tenant_no = _get_tenant_no(db, user)
  586. s = region_svc.get_staff(db, staff_id, tenant_no=tenant_no)
  587. if not s or s.region_id != region_id:
  588. return error_response(400, "绑定记录不存在")
  589. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  590. obj = region_svc.update_staff(
  591. db,
  592. staff_id,
  593. is_leader=payload.get("is_leader"),
  594. order_num=payload.get("order_num"),
  595. remark=payload.get("remark"),
  596. user_id=user.get("user_id"),
  597. tenant_no=tenant_no,
  598. )
  599. items, _ = region_svc.list_staff(db, region_id=region_id, page=1, page_size=100, tenant_no=tenant_no)
  600. matched = next((x for x in items if x["id"] == obj.id), region_svc._staff_to_dict(obj))
  601. return success_response(matched, message="更新成功")
  602. except ValueError as e:
  603. return error_response(400, str(e))
  604. @router.delete("/collection-regions/{region_id}/staff/{staff_id}")
  605. async def delete_region_staff(
  606. region_id: str,
  607. staff_id: str,
  608. user: dict = Depends(require_admin_or_super_admin),
  609. db: Session = Depends(get_db),
  610. ):
  611. try:
  612. tenant_no = _get_tenant_no(db, user)
  613. s = region_svc.get_staff(db, staff_id, tenant_no=tenant_no)
  614. if not s or s.region_id != region_id:
  615. return error_response(400, "绑定记录不存在")
  616. region_svc.soft_delete_staff(db, staff_id, tenant_no=tenant_no)
  617. return success_response(None, message="删除成功")
  618. except ValueError as e:
  619. return error_response(400, str(e))
  620. # ---------------------------------------------------------------------------
  621. # 采集协议号池
  622. # ---------------------------------------------------------------------------
  623. class ProtocolNoCreateBody(BaseModel):
  624. model_config = ConfigDict(populate_by_name=True)
  625. protocol_no: str = Field(..., alias="protocolNo")
  626. issued: int = 0
  627. order_num: Optional[int] = Field(0, alias="orderNum")
  628. remark: Optional[str] = None
  629. issued_user_id: Optional[str] = Field(None, alias="issuedUserId")
  630. issued_user_name: Optional[str] = Field(None, alias="issuedUserName")
  631. issued_time: Optional[str] = Field(None, alias="issuedTime")
  632. class ProtocolNoUpdateBody(BaseModel):
  633. model_config = ConfigDict(populate_by_name=True)
  634. protocol_no: Optional[str] = Field(None, alias="protocolNo")
  635. issued: Optional[int] = None
  636. order_num: Optional[int] = Field(None, alias="orderNum")
  637. remark: Optional[str] = None
  638. issued_user_id: Optional[str] = Field(None, alias="issuedUserId")
  639. issued_user_name: Optional[str] = Field(None, alias="issuedUserName")
  640. issued_time: Optional[str] = Field(None, alias="issuedTime")
  641. @router.get("/collection-protocol-nos")
  642. async def list_protocol_nos(
  643. page: int = Query(1, ge=1),
  644. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  645. keyword: Optional[str] = None,
  646. issued_user_id: Optional[str] = Query(None, alias="issuedUserId"),
  647. user: dict = Depends(require_menu_code("biz:collectionProtocolNo:list")),
  648. db: Session = Depends(get_db),
  649. ):
  650. tenant_no = _get_tenant_no(db, user)
  651. query = db.query(BizCollectionProtocolNo).filter(
  652. BizCollectionProtocolNo.del_flag == 0,
  653. BizCollectionProtocolNo.tenant_no == tenant_no,
  654. )
  655. kw = (keyword or "").strip()
  656. if kw:
  657. like = f"%{kw}%"
  658. query = query.filter(
  659. or_(
  660. BizCollectionProtocolNo.protocol_no.like(like),
  661. BizCollectionProtocolNo.remark.like(like),
  662. )
  663. )
  664. uid = (issued_user_id or "").strip()
  665. if uid:
  666. query = query.filter(BizCollectionProtocolNo.issued_user_id == uid)
  667. query = apply_data_scope(query, BizCollectionProtocolNo, user, db, tenant_no)
  668. total = query.count()
  669. items = query.order_by(BizCollectionProtocolNo.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
  670. result_list = [protocol_no_svc._row_to_dict(r) for r in items]
  671. protocol_no_svc.enrich_issued_user_names(db, result_list)
  672. used_nos = collection_protocol_svc.get_protocol_nos_used_in_protocols(
  673. db,
  674. tenant_no=tenant_no,
  675. protocol_nos=[row["protocolNo"] for row in result_list],
  676. )
  677. for row in result_list:
  678. row["usedInProtocol"] = row.get("protocolNo") in used_nos
  679. tp = protocol_no_svc.total_pages(total, pageSize)
  680. return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  681. class ProtocolNoBatchGenerateBody(BaseModel):
  682. model_config = ConfigDict(populate_by_name=True)
  683. prefix: str = Field("ZKZX", alias="prefix")
  684. middle: str = Field("34", alias="middle")
  685. seq_start: int = Field(..., alias="seqStart")
  686. seq_end: int = Field(..., alias="seqEnd")
  687. digit_count: int = Field(6, alias="digitCount")
  688. class ProtocolNoBatchIssueBody(BaseModel):
  689. model_config = ConfigDict(populate_by_name=True)
  690. ids: List[str] = Field(..., alias="ids")
  691. issued_user_id: str = Field(..., alias="issuedUserId")
  692. issued_user_name: Optional[str] = Field(None, alias="issuedUserName")
  693. @router.post("/collection-protocol-nos/batch-generate")
  694. async def batch_generate_protocol_nos(
  695. body: ProtocolNoBatchGenerateBody,
  696. user: dict = Depends(require_admin_or_super_admin),
  697. db: Session = Depends(get_db),
  698. ):
  699. try:
  700. tenant_no = _get_tenant_no(db, user)
  701. result = protocol_no_svc.batch_generate_protocol_nos(
  702. db,
  703. prefix=body.prefix,
  704. middle=body.middle,
  705. seq_start=body.seq_start,
  706. seq_end=body.seq_end,
  707. digit_count=body.digit_count,
  708. tenant_no=tenant_no,
  709. user_id=user.get("user_id"),
  710. )
  711. msg = f"成功生成 {result['created']} 条"
  712. if result['skipped'] > 0:
  713. msg += f",跳过已存在 {result['skipped']} 条"
  714. return success_response(result, message=msg)
  715. except ValueError as e:
  716. return error_response(400, str(e))
  717. @router.post("/collection-protocol-nos/batch-issue")
  718. async def batch_issue_protocol_nos(
  719. body: ProtocolNoBatchIssueBody,
  720. user: dict = Depends(require_admin_or_super_admin),
  721. db: Session = Depends(get_db),
  722. ):
  723. try:
  724. tenant_no = _get_tenant_no(db, user)
  725. result = protocol_no_svc.batch_issue_protocol_nos(
  726. db,
  727. ids=body.ids,
  728. issued_user_id=body.issued_user_id,
  729. issued_user_name=body.issued_user_name,
  730. tenant_no=tenant_no,
  731. user_id=user.get("user_id"),
  732. )
  733. msg = f"成功下发 {result['issuedCount']} 条"
  734. if result['alreadyIssued']:
  735. msg += f",{len(result['alreadyIssued'])} 条已下发被跳过"
  736. return success_response(result, message=msg)
  737. except ValueError as e:
  738. return error_response(400, str(e))
  739. class ProtocolNoBatchRevokeBody(BaseModel):
  740. model_config = ConfigDict(populate_by_name=True)
  741. ids: List[str] = Field(..., alias="ids")
  742. @router.post("/collection-protocol-nos/batch-revoke")
  743. async def batch_revoke_protocol_nos(
  744. body: ProtocolNoBatchRevokeBody,
  745. user: dict = Depends(require_admin_or_super_admin),
  746. db: Session = Depends(get_db),
  747. ):
  748. try:
  749. tenant_no = _get_tenant_no(db, user)
  750. result = protocol_no_svc.batch_revoke_protocol_nos(
  751. db,
  752. ids=body.ids,
  753. tenant_no=tenant_no,
  754. user_id=user.get("user_id"),
  755. )
  756. msg = f"成功收回 {result['revokedCount']} 条"
  757. if result["notIssued"]:
  758. msg += f",{len(result['notIssued'])} 条未下发已跳过"
  759. return success_response(result, message=msg)
  760. except ValueError as e:
  761. return error_response(400, str(e))
  762. @router.post("/collection-protocol-nos")
  763. async def create_protocol_no(
  764. body: ProtocolNoCreateBody,
  765. user: dict = Depends(require_admin_or_super_admin),
  766. db: Session = Depends(get_db),
  767. ):
  768. try:
  769. tenant_no = _get_tenant_no(db, user)
  770. obj = protocol_no_svc.create_protocol_no(
  771. db,
  772. protocol_no=body.protocol_no,
  773. issued=body.issued,
  774. tenant_no=tenant_no,
  775. order_num=body.order_num,
  776. remark=body.remark,
  777. user_id=user.get("user_id"),
  778. issued_user_id=body.issued_user_id,
  779. issued_user_name=body.issued_user_name,
  780. issued_time=body.issued_time,
  781. )
  782. row = protocol_no_svc._row_to_dict(obj)
  783. protocol_no_svc.enrich_issued_user_names(db, [row])
  784. return success_response(row, message="创建成功")
  785. except ValueError as e:
  786. return error_response(400, str(e))
  787. @router.put("/collection-protocol-nos/{row_id}")
  788. async def update_protocol_no(
  789. row_id: str,
  790. body: ProtocolNoUpdateBody,
  791. user: dict = Depends(require_admin_or_super_admin),
  792. db: Session = Depends(get_db),
  793. ):
  794. try:
  795. tenant_no = _get_tenant_no(db, user)
  796. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  797. obj = protocol_no_svc.update_protocol_no(
  798. db,
  799. row_id,
  800. protocol_no=payload.get("protocol_no"),
  801. issued=payload.get("issued"),
  802. order_num=payload.get("order_num"),
  803. remark=payload.get("remark"),
  804. user_id=user.get("user_id"),
  805. tenant_no=tenant_no,
  806. issued_user_id=payload.get("issued_user_id"),
  807. issued_user_name=payload.get("issued_user_name"),
  808. issued_time=payload.get("issued_time"),
  809. )
  810. return success_response(protocol_no_svc._row_to_dict(obj), message="更新成功")
  811. except ValueError as e:
  812. return error_response(400, str(e))
  813. @router.delete("/collection-protocol-nos/{row_id}")
  814. async def delete_protocol_no(
  815. row_id: str,
  816. user: dict = Depends(require_admin_or_super_admin),
  817. db: Session = Depends(get_db),
  818. ):
  819. try:
  820. tenant_no = _get_tenant_no(db, user)
  821. protocol_no_svc.soft_delete_protocol_no(db, row_id, tenant_no=tenant_no)
  822. return success_response(None, message="删除成功")
  823. except ValueError as e:
  824. return error_response(400, str(e))
  825. # ---------------------------------------------------------------------------
  826. # 样本编码池
  827. # ---------------------------------------------------------------------------
  828. class SampleCodeCreateBody(BaseModel):
  829. model_config = ConfigDict(populate_by_name=True)
  830. sample_code: str = Field(..., alias="sampleCode")
  831. sample_code_type: Optional[str] = Field(None, alias="sampleCodeType")
  832. issued: int = 0
  833. order_num: Optional[int] = Field(0, alias="orderNum")
  834. remark: Optional[str] = None
  835. issued_user_id: Optional[str] = Field(None, alias="issuedUserId")
  836. issued_user_name: Optional[str] = Field(None, alias="issuedUserName")
  837. issued_time: Optional[str] = Field(None, alias="issuedTime")
  838. class SampleCodeUpdateBody(BaseModel):
  839. model_config = ConfigDict(populate_by_name=True)
  840. sample_code: Optional[str] = Field(None, alias="sampleCode")
  841. issued: Optional[int] = None
  842. order_num: Optional[int] = Field(None, alias="orderNum")
  843. remark: Optional[str] = None
  844. issued_user_id: Optional[str] = Field(None, alias="issuedUserId")
  845. issued_user_name: Optional[str] = Field(None, alias="issuedUserName")
  846. issued_time: Optional[str] = Field(None, alias="issuedTime")
  847. class SampleCodeBatchGenerateBody(BaseModel):
  848. model_config = ConfigDict(populate_by_name=True)
  849. sample_code_type: str = Field(..., alias="sampleCodeType")
  850. middle: str = Field("34", alias="middle")
  851. seq_start: int = Field(..., alias="seqStart")
  852. seq_end: int = Field(..., alias="seqEnd")
  853. digit_count: int = Field(6, alias="digitCount")
  854. class SampleCodeBatchIssueBody(BaseModel):
  855. model_config = ConfigDict(populate_by_name=True)
  856. ids: List[str] = Field(..., alias="ids")
  857. issued_user_id: str = Field(..., alias="issuedUserId")
  858. issued_user_name: Optional[str] = Field(None, alias="issuedUserName")
  859. @router.post("/sample-codes/batch-generate")
  860. async def batch_generate_sample_codes(
  861. body: SampleCodeBatchGenerateBody,
  862. user: dict = Depends(require_admin_or_super_admin),
  863. db: Session = Depends(get_db),
  864. ):
  865. try:
  866. tenant_no = _get_tenant_no(db, user)
  867. result = sample_code_svc.batch_generate_sample_codes(
  868. db,
  869. sample_code_type=body.sample_code_type,
  870. middle=body.middle,
  871. seq_start=body.seq_start,
  872. seq_end=body.seq_end,
  873. digit_count=body.digit_count,
  874. tenant_no=tenant_no,
  875. user_id=user.get("user_id"),
  876. )
  877. msg = f"成功生成 {result['created']} 条"
  878. if result["skipped"] > 0:
  879. msg += f",跳过已存在 {result['skipped']} 条"
  880. return success_response(result, message=msg)
  881. except ValueError as e:
  882. return error_response(400, str(e))
  883. @router.post("/sample-codes/batch-issue")
  884. async def batch_issue_sample_codes(
  885. body: SampleCodeBatchIssueBody,
  886. user: dict = Depends(require_admin_or_super_admin),
  887. db: Session = Depends(get_db),
  888. ):
  889. try:
  890. tenant_no = _get_tenant_no(db, user)
  891. result = sample_code_svc.batch_issue_sample_codes(
  892. db,
  893. ids=body.ids,
  894. issued_user_id=body.issued_user_id,
  895. issued_user_name=body.issued_user_name,
  896. tenant_no=tenant_no,
  897. user_id=user.get("user_id"),
  898. )
  899. msg = f"成功下发 {result['issuedCount']} 条"
  900. if result["alreadyIssued"]:
  901. msg += f",{len(result['alreadyIssued'])} 条已下发被跳过"
  902. return success_response(result, message=msg)
  903. except ValueError as e:
  904. return error_response(400, str(e))
  905. class SampleCodeBatchRevokeBody(BaseModel):
  906. model_config = ConfigDict(populate_by_name=True)
  907. ids: List[str] = Field(..., alias="ids")
  908. @router.post("/sample-codes/batch-revoke")
  909. async def batch_revoke_sample_codes(
  910. body: SampleCodeBatchRevokeBody,
  911. user: dict = Depends(require_admin_or_super_admin),
  912. db: Session = Depends(get_db),
  913. ):
  914. try:
  915. tenant_no = _get_tenant_no(db, user)
  916. result = sample_code_svc.batch_revoke_sample_codes(
  917. db,
  918. ids=body.ids,
  919. tenant_no=tenant_no,
  920. user_id=user.get("user_id"),
  921. )
  922. msg = f"成功收回 {result['revokedCount']} 条"
  923. if result["notIssued"]:
  924. msg += f",{len(result['notIssued'])} 条未下发已跳过"
  925. return success_response(result, message=msg)
  926. except ValueError as e:
  927. return error_response(400, str(e))
  928. @router.get("/sample-codes")
  929. async def list_sample_codes(
  930. page: int = Query(1, ge=1),
  931. pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
  932. keyword: Optional[str] = None,
  933. sample_code_type: Optional[str] = Query(None, alias="sampleCodeType"),
  934. code_filter: Optional[str] = Query(None, alias="codeFilter"),
  935. issued_user_id: Optional[str] = Query(None, alias="issuedUserId"),
  936. user: dict = Depends(require_menu_code("biz:sampleCode:list")),
  937. db: Session = Depends(get_db),
  938. ):
  939. tenant_no = _get_tenant_no(db, user)
  940. query = db.query(BizSampleCode).filter(
  941. BizSampleCode.del_flag == 0,
  942. BizSampleCode.tenant_no == tenant_no,
  943. )
  944. kw = (keyword or "").strip()
  945. if kw:
  946. like = f"%{kw}%"
  947. query = query.filter(
  948. or_(
  949. BizSampleCode.sample_code.like(like),
  950. BizSampleCode.remark.like(like),
  951. )
  952. )
  953. code_type = (sample_code_type or "").strip()
  954. if code_type:
  955. query = query.filter(BizSampleCode.sample_code_type == code_type)
  956. cf = (code_filter or "").strip()
  957. if cf:
  958. query = query.filter(BizSampleCode.sample_code.like(f"%{cf}%"))
  959. uid = (issued_user_id or "").strip()
  960. if uid:
  961. query = query.filter(BizSampleCode.issued_user_id == uid)
  962. query = apply_data_scope(query, BizSampleCode, user, db, tenant_no)
  963. total = query.count()
  964. items = (
  965. query.order_by(BizSampleCode.create_time.desc(), BizSampleCode.sample_code.desc())
  966. .offset((page - 1) * pageSize)
  967. .limit(pageSize)
  968. .all()
  969. )
  970. result_list = [sample_code_svc._row_to_dict(r) for r in items]
  971. used_codes = collection_protocol_svc.get_sample_codes_used_in_protocol_samples(
  972. db,
  973. tenant_no=tenant_no,
  974. sample_codes=[row["sampleCode"] for row in result_list],
  975. )
  976. for row in result_list:
  977. row["usedInProtocol"] = row.get("sampleCode") in used_codes
  978. tp = sample_code_svc.total_pages(total, pageSize)
  979. return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
  980. @router.post("/sample-codes")
  981. async def create_sample_code(
  982. body: SampleCodeCreateBody,
  983. user: dict = Depends(require_admin_or_super_admin),
  984. db: Session = Depends(get_db),
  985. ):
  986. try:
  987. tenant_no = _get_tenant_no(db, user)
  988. obj = sample_code_svc.create_sample_code(
  989. db,
  990. sample_code=body.sample_code,
  991. sample_code_type=body.sample_code_type,
  992. issued=body.issued,
  993. tenant_no=tenant_no,
  994. order_num=body.order_num,
  995. remark=body.remark,
  996. user_id=user.get("user_id"),
  997. issued_user_id=body.issued_user_id,
  998. issued_user_name=body.issued_user_name,
  999. issued_time=body.issued_time,
  1000. )
  1001. return success_response(sample_code_svc._row_to_dict(obj), message="创建成功")
  1002. except ValueError as e:
  1003. return error_response(400, str(e))
  1004. @router.put("/sample-codes/{row_id}")
  1005. async def update_sample_code(
  1006. row_id: str,
  1007. body: SampleCodeUpdateBody,
  1008. user: dict = Depends(require_admin_or_super_admin),
  1009. db: Session = Depends(get_db),
  1010. ):
  1011. try:
  1012. tenant_no = _get_tenant_no(db, user)
  1013. payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
  1014. obj = sample_code_svc.update_sample_code(
  1015. db,
  1016. row_id,
  1017. sample_code=payload.get("sample_code"),
  1018. issued=payload.get("issued"),
  1019. order_num=payload.get("order_num"),
  1020. remark=payload.get("remark"),
  1021. user_id=user.get("user_id"),
  1022. tenant_no=tenant_no,
  1023. issued_user_id=payload.get("issued_user_id"),
  1024. issued_user_name=payload.get("issued_user_name"),
  1025. issued_time=payload.get("issued_time"),
  1026. )
  1027. return success_response(sample_code_svc._row_to_dict(obj), message="更新成功")
  1028. except ValueError as e:
  1029. return error_response(400, str(e))
  1030. @router.delete("/sample-codes/{row_id}")
  1031. async def delete_sample_code(
  1032. row_id: str,
  1033. user: dict = Depends(require_admin_or_super_admin),
  1034. db: Session = Depends(get_db),
  1035. ):
  1036. try:
  1037. tenant_no = _get_tenant_no(db, user)
  1038. sample_code_svc.soft_delete_sample_code(db, row_id, tenant_no=tenant_no)
  1039. return success_response(None, message="删除成功")
  1040. except ValueError as e:
  1041. return error_response(400, str(e))