protocol_status_display.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. """协议状态展示(含作废类型后缀)。"""
  2. from __future__ import annotations
  3. from typing import Dict, Optional
  4. from sqlalchemy.orm import Session
  5. from app.models.models import SysDictData
  6. VOID_TYPE_DICT = "xyzflx"
  7. # 协议/捐赠单无效态(与样本接收、生产工单列表过滤口径一致)
  8. INACTIVE_PROTOCOL_STATUSES = ("yzf", "sdfq")
  9. _PROTOCOL_STATUS_LABELS = {
  10. "dsx": "待生效",
  11. "sxz": "生效中",
  12. "ygq": "已过期",
  13. "yzf": "已作废",
  14. "sdfq": "属地废弃",
  15. "zky": "转科研",
  16. }
  17. _DONATION_STATUS_LABELS = {
  18. "dsx": "待生效",
  19. "sxz": "生效中",
  20. "ygq": "已过期",
  21. "yzf": "已作废",
  22. "sdfq": "属地废弃",
  23. "zckc": "转细胞存储",
  24. }
  25. _VOID_TYPE_FALLBACK_LABELS = {
  26. "khhc": "客户毁存",
  27. "jcbhg": "检测不合格",
  28. }
  29. # 非法来源状态视为终态,不允许再转出
  30. _PROTOCOL_STATUS_TRANSITIONS = {
  31. "dsx": ("sxz", "yzf", "sdfq"),
  32. "sxz": ("ygq", "yzf", "zky", "sdfq"),
  33. }
  34. _DONATION_STATUS_TRANSITIONS = {
  35. "dsx": ("sxz", "yzf", "sdfq"),
  36. "sxz": ("ygq", "yzf", "zckc", "sdfq"),
  37. }
  38. def resolve_void_type_label(
  39. db: Optional[Session],
  40. tenant_no: Optional[str],
  41. void_type: Optional[str],
  42. ) -> str:
  43. code = (void_type or "").strip()
  44. if not code:
  45. return ""
  46. if db is not None and tenant_no:
  47. row = (
  48. db.query(SysDictData.dict_label)
  49. .filter(
  50. SysDictData.dict_type == VOID_TYPE_DICT,
  51. SysDictData.dict_value == code,
  52. SysDictData.tenant_no == tenant_no,
  53. SysDictData.del_flag == 0,
  54. )
  55. .first()
  56. )
  57. if row and (row.dict_label or "").strip():
  58. return (row.dict_label or "").strip()
  59. return _VOID_TYPE_FALLBACK_LABELS.get(code, code)
  60. def status_transition_denied_message(current: str, target: str, *, kind: str = "protocol") -> str:
  61. """状态流转被拒绝时的提示,状态码展示为中文名称。"""
  62. current_label = format_protocol_status_label(current, kind=kind)
  63. target_label = format_protocol_status_label(target, kind=kind)
  64. return f"状态{current_label}不可转为{target_label}"
  65. def assert_protocol_status_transition(current: str, target: str, *, kind: str = "protocol") -> None:
  66. cur = (current or "").strip()
  67. nxt = (target or "").strip()
  68. if cur == nxt:
  69. return
  70. table = _DONATION_STATUS_TRANSITIONS if kind == "donation" else _PROTOCOL_STATUS_TRANSITIONS
  71. if nxt not in table.get(cur, ()):
  72. raise ValueError(status_transition_denied_message(cur, nxt, kind=kind))
  73. def format_protocol_status_label(
  74. status: str,
  75. void_type: Optional[str] = None,
  76. *,
  77. kind: str = "protocol",
  78. db: Optional[Session] = None,
  79. tenant_no: Optional[str] = None,
  80. ) -> str:
  81. st = (status or "").strip()
  82. labels = _DONATION_STATUS_LABELS if kind == "donation" else _PROTOCOL_STATUS_LABELS
  83. base = labels.get(st, st)
  84. if st != "yzf":
  85. return base
  86. vt_label = resolve_void_type_label(db, tenant_no, void_type)
  87. if vt_label:
  88. return f"{base}({vt_label})"
  89. return base
  90. def build_void_type_label_map(
  91. db: Session,
  92. tenant_no: str,
  93. codes: list[str],
  94. ) -> Dict[str, str]:
  95. normalized = list({(c or "").strip() for c in codes if (c or "").strip()})
  96. if not normalized:
  97. return {}
  98. rows = (
  99. db.query(SysDictData.dict_value, SysDictData.dict_label)
  100. .filter(
  101. SysDictData.dict_type == VOID_TYPE_DICT,
  102. SysDictData.dict_value.in_(normalized),
  103. SysDictData.tenant_no == tenant_no,
  104. SysDictData.del_flag == 0,
  105. )
  106. .all()
  107. )
  108. out = {r.dict_value: (r.dict_label or "").strip() for r in rows if (r.dict_value or "").strip()}
  109. for code in normalized:
  110. out.setdefault(code, _VOID_TYPE_FALLBACK_LABELS.get(code, code))
  111. return out