sample_code.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. """样本编码池 CRUD(biz_sample_code)"""
  2. from __future__ import annotations
  3. import math
  4. from datetime import datetime
  5. from typing import Any, Dict, List, Optional, Tuple
  6. from sqlalchemy import and_, func, or_
  7. from sqlalchemy.orm import Session
  8. from app.core.utils import generate_uuid
  9. from app.models.models import BizSampleCode, SysUser
  10. def _resolve_issued_user_display_name(
  11. db: Session,
  12. user_id: Optional[str],
  13. fallback_name: Optional[str] = None,
  14. ) -> Optional[str]:
  15. name = (fallback_name or "").strip()
  16. if name:
  17. return name
  18. uid = (user_id or "").strip()
  19. if not uid:
  20. return None
  21. u = db.query(SysUser).filter(SysUser.id == uid, SysUser.del_flag == 0).first()
  22. if not u:
  23. return None
  24. return (u.nick_name or u.user_no or "").strip() or None
  25. def _parse_issued_time(value: Optional[str]) -> Optional[datetime]:
  26. raw = (value or "").strip()
  27. if not raw:
  28. return None
  29. for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M"):
  30. try:
  31. return datetime.strptime(raw, fmt)
  32. except ValueError:
  33. continue
  34. return None
  35. def _row_to_dict(r: BizSampleCode) -> Dict[str, Any]:
  36. return {
  37. "id": r.id,
  38. "sampleCode": r.sample_code,
  39. "sampleCodeType": r.sample_code_type or "",
  40. "issued": int(r.issued or 0),
  41. "issuedUserId": r.issued_user_id or "",
  42. "issuedUserName": r.issued_user_name or "",
  43. "issuedTime": r.issued_time.strftime("%Y-%m-%d %H:%M:%S") if r.issued_time else "",
  44. "tenantNo": r.tenant_no,
  45. "orderNum": r.order_num if r.order_num is not None else 0,
  46. "remark": r.remark or "",
  47. "createTime": r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "",
  48. "updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "",
  49. }
  50. def sample_code_exists(db: Session, code: str, tenant_no: str, exclude_id: Optional[str] = None) -> bool:
  51. v = (code or "").strip()
  52. if not v:
  53. return False
  54. q = db.query(BizSampleCode).filter(
  55. and_(BizSampleCode.sample_code == v, BizSampleCode.tenant_no == tenant_no, BizSampleCode.del_flag == 0)
  56. )
  57. if exclude_id:
  58. q = q.filter(BizSampleCode.id != exclude_id)
  59. return q.first() is not None
  60. def list_sample_codes(
  61. db: Session,
  62. *,
  63. page: int = 1,
  64. page_size: int = 10,
  65. keyword: Optional[str] = None,
  66. tenant_no: Optional[str] = None,
  67. ) -> Tuple[List[Dict[str, Any]], int]:
  68. def _filtered():
  69. q0 = db.query(BizSampleCode).filter(BizSampleCode.del_flag == 0)
  70. if tenant_no:
  71. q0 = q0.filter(BizSampleCode.tenant_no == tenant_no)
  72. kw = (keyword or "").strip()
  73. if kw:
  74. like = f"%{kw}%"
  75. q0 = q0.filter(or_(BizSampleCode.sample_code.like(like), BizSampleCode.remark.like(like)))
  76. return q0
  77. total = _filtered().order_by(None).with_entities(func.count(BizSampleCode.id)).scalar() or 0
  78. page = max(1, page)
  79. page_size = min(max(1, page_size), 500)
  80. rows = (
  81. _filtered()
  82. .order_by(BizSampleCode.create_time.desc())
  83. .offset((page - 1) * page_size)
  84. .limit(page_size)
  85. .all()
  86. )
  87. return [_row_to_dict(x) for x in rows], int(total)
  88. def get_sample_code(db: Session, row_id: str, tenant_no: Optional[str] = None) -> Optional[BizSampleCode]:
  89. q = db.query(BizSampleCode).filter(and_(BizSampleCode.id == row_id, BizSampleCode.del_flag == 0))
  90. if tenant_no:
  91. q = q.filter(BizSampleCode.tenant_no == tenant_no)
  92. return q.first()
  93. def create_sample_code(
  94. db: Session,
  95. *,
  96. sample_code: str,
  97. sample_code_type: Optional[str] = None,
  98. issued: int = 0,
  99. tenant_no: str = "shanxia2.0",
  100. order_num: Optional[int] = None,
  101. remark: Optional[str] = None,
  102. user_id: Optional[str] = None,
  103. issued_user_id: Optional[str] = None,
  104. issued_user_name: Optional[str] = None,
  105. issued_time: Optional[str] = None,
  106. ) -> BizSampleCode:
  107. sc = (sample_code or "").strip()
  108. if not sc:
  109. raise ValueError("样本编码不能为空")
  110. if sample_code_exists(db, sc, tenant_no):
  111. raise ValueError("样本编码已存在")
  112. code_type = (sample_code_type or "").strip() or None
  113. if code_type and code_type not in ("zxbm", "qdbm"):
  114. raise ValueError("样本编码类型无效,仅支持 zxbm/qdbm")
  115. is_issued = 1 if issued else 0
  116. issue_uid: Optional[str] = None
  117. issue_uname: Optional[str] = None
  118. issue_time: Optional[datetime] = None
  119. if is_issued:
  120. issue_uid = (issued_user_id or "").strip() or None
  121. if not issue_uid:
  122. raise ValueError("请选择下发用户")
  123. issue_uname = _resolve_issued_user_display_name(db, issue_uid, issued_user_name)
  124. issue_time = _parse_issued_time(issued_time) or datetime.now()
  125. obj = BizSampleCode(
  126. id=generate_uuid(),
  127. sample_code=sc,
  128. sample_code_type=code_type,
  129. issued=is_issued,
  130. issued_user_id=issue_uid,
  131. issued_user_name=issue_uname,
  132. issued_time=issue_time,
  133. tenant_no=tenant_no,
  134. order_num=order_num if order_num is not None else 0,
  135. remark=(remark or "").strip() or None,
  136. create_by=user_id,
  137. create_dept_id="",
  138. create_time=datetime.now(),
  139. update_by=user_id,
  140. update_time=datetime.now(),
  141. del_flag=0,
  142. )
  143. db.add(obj)
  144. db.commit()
  145. db.refresh(obj)
  146. return obj
  147. def update_sample_code(
  148. db: Session,
  149. row_id: str,
  150. *,
  151. sample_code: Optional[str] = None,
  152. issued: Optional[int] = None,
  153. order_num: Optional[int] = None,
  154. remark: Optional[str] = None,
  155. user_id: Optional[str] = None,
  156. tenant_no: Optional[str] = None,
  157. issued_user_id: Optional[str] = None,
  158. issued_user_name: Optional[str] = None,
  159. issued_time: Optional[str] = None,
  160. ) -> BizSampleCode:
  161. obj = get_sample_code(db, row_id, tenant_no=tenant_no)
  162. if not obj:
  163. raise ValueError("记录不存在")
  164. if sample_code is not None:
  165. sc = sample_code.strip()
  166. if not sc:
  167. raise ValueError("样本编码不能为空")
  168. if sc != obj.sample_code and sample_code_exists(db, sc, obj.tenant_no, exclude_id=row_id):
  169. raise ValueError("样本编码已存在")
  170. obj.sample_code = sc
  171. if issued is not None:
  172. obj.issued = 1 if issued else 0
  173. if not obj.issued:
  174. obj.issued_user_id = None
  175. obj.issued_user_name = None
  176. obj.issued_time = None
  177. if order_num is not None:
  178. obj.order_num = order_num
  179. if remark is not None:
  180. obj.remark = remark.strip() or None
  181. if issued_user_id is not None:
  182. obj.issued_user_id = issued_user_id or None
  183. if issued_user_name is not None:
  184. obj.issued_user_name = issued_user_name or None
  185. if obj.issued == 1 and obj.issued_user_id and not (obj.issued_user_name or "").strip():
  186. obj.issued_user_name = _resolve_issued_user_display_name(db, obj.issued_user_id)
  187. if issued_time is not None:
  188. obj.issued_time = _parse_issued_time(issued_time) if (issued_time or "").strip() else None
  189. if obj.issued == 1 and obj.issued_user_id and not obj.issued_time:
  190. obj.issued_time = datetime.now()
  191. obj.update_by = user_id
  192. obj.update_time = datetime.now()
  193. db.add(obj)
  194. db.commit()
  195. db.refresh(obj)
  196. return obj
  197. def soft_delete_sample_code(db: Session, row_id: str, tenant_no: Optional[str] = None) -> None:
  198. obj = get_sample_code(db, row_id, tenant_no=tenant_no)
  199. if not obj:
  200. raise ValueError("记录不存在")
  201. obj.del_flag = 1
  202. obj.update_time = datetime.now()
  203. db.add(obj)
  204. db.commit()
  205. def batch_generate_sample_codes(
  206. db: Session,
  207. *,
  208. sample_code_type: str,
  209. middle: str,
  210. seq_start: int,
  211. seq_end: int,
  212. digit_count: int = 6,
  213. tenant_no: str = "shanxia2.0",
  214. user_id: Optional[str] = None,
  215. ) -> Dict[str, Any]:
  216. """批量生成样本编码: 类型前缀 + 中间段 + 补零序号"""
  217. t = (sample_code_type or "").strip()
  218. if t not in ("zxbm", "qdbm"):
  219. raise ValueError("样本编码类型无效,仅支持 zxbm/qdbm")
  220. m = (middle or "").strip()
  221. if not m:
  222. raise ValueError("中间段不能为空")
  223. if seq_start < 0 or seq_end < 0:
  224. raise ValueError("序号不能为负数")
  225. if seq_start > seq_end:
  226. raise ValueError("开始序号不能大于结束序号")
  227. if digit_count < 1:
  228. raise ValueError("序号位数不能小于1")
  229. count = seq_end - seq_start + 1
  230. if count > 10000:
  231. raise ValueError("单次批量生成不能超过10000条")
  232. prefix_map = {"zxbm": "ZKZX", "qdbm": "ZKQD"}
  233. prefix = prefix_map[t]
  234. generated_codes = []
  235. for i in range(seq_start, seq_end + 1):
  236. seq_str = str(i).zfill(digit_count)
  237. full_code = f"{prefix}{m}{seq_str}"
  238. generated_codes.append(full_code)
  239. existing = set(
  240. row.sample_code
  241. for row in db.query(BizSampleCode.sample_code)
  242. .filter(
  243. BizSampleCode.tenant_no == tenant_no,
  244. BizSampleCode.del_flag == 0,
  245. BizSampleCode.sample_code.in_(generated_codes),
  246. )
  247. .all()
  248. )
  249. now = datetime.now()
  250. created = []
  251. skipped = []
  252. for code in generated_codes:
  253. if code in existing:
  254. skipped.append(code)
  255. continue
  256. obj = BizSampleCode(
  257. id=generate_uuid(),
  258. sample_code=code,
  259. sample_code_type=t,
  260. issued=0,
  261. tenant_no=tenant_no,
  262. order_num=0,
  263. remark=None,
  264. create_by=user_id,
  265. create_dept_id="",
  266. create_time=now,
  267. update_by=user_id,
  268. update_time=now,
  269. del_flag=0,
  270. )
  271. created.append(obj)
  272. if created:
  273. db.bulk_save_objects(created)
  274. db.commit()
  275. return {
  276. "created": len(created),
  277. "skipped": len(skipped),
  278. "skippedCodes": skipped,
  279. }
  280. def batch_issue_sample_codes(
  281. db: Session,
  282. *,
  283. ids: List[str],
  284. issued_user_id: str,
  285. issued_user_name: Optional[str] = None,
  286. tenant_no: Optional[str] = None,
  287. user_id: Optional[str] = None,
  288. ) -> Dict[str, Any]:
  289. """批量下发样本编码"""
  290. if not ids:
  291. raise ValueError("请选择要下发的样本编码")
  292. uid = (issued_user_id or "").strip()
  293. if not uid:
  294. raise ValueError("请选择下发用户")
  295. now = datetime.now()
  296. issued_count = 0
  297. already_issued = []
  298. for row_id in ids:
  299. obj = get_sample_code(db, row_id, tenant_no=tenant_no)
  300. if not obj:
  301. continue
  302. if obj.issued == 1:
  303. already_issued.append(obj.sample_code)
  304. continue
  305. obj.issued = 1
  306. obj.issued_user_id = uid
  307. obj.issued_user_name = (issued_user_name or "").strip() or None
  308. obj.issued_time = now
  309. obj.update_by = user_id
  310. obj.update_time = now
  311. db.add(obj)
  312. issued_count += 1
  313. if issued_count > 0:
  314. db.commit()
  315. return {
  316. "issuedCount": issued_count,
  317. "alreadyIssued": already_issued,
  318. }
  319. def batch_revoke_sample_codes(
  320. db: Session,
  321. *,
  322. ids: List[str],
  323. tenant_no: Optional[str] = None,
  324. user_id: Optional[str] = None,
  325. ) -> Dict[str, Any]:
  326. if not ids:
  327. raise ValueError("请选择要收回的样本编码")
  328. now = datetime.now()
  329. revoked_count = 0
  330. not_issued = []
  331. for row_id in ids:
  332. obj = get_sample_code(db, row_id, tenant_no=tenant_no)
  333. if not obj:
  334. continue
  335. if obj.issued != 1:
  336. not_issued.append(obj.sample_code)
  337. continue
  338. obj.issued = 0
  339. obj.issued_user_id = None
  340. obj.issued_user_name = None
  341. obj.issued_time = None
  342. obj.update_by = user_id
  343. obj.update_time = now
  344. db.add(obj)
  345. revoked_count += 1
  346. if revoked_count > 0:
  347. db.commit()
  348. return {
  349. "revokedCount": revoked_count,
  350. "notIssued": not_issued,
  351. }
  352. def total_pages(total: int, page_size: int) -> int:
  353. if total <= 0:
  354. return 0
  355. return int(math.ceil(total / float(page_size)))