Просмотр исходного кода

feat: apply dual-gate storage release submit, auto store, and unstore.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 2 месяцев назад
Родитель
Сommit
6143a99133
1 измененных файлов с 130 добавлено и 18 удалено
  1. 130 18
      backend/app/crud/storage_release_review.py

+ 130 - 18
backend/app/crud/storage_release_review.py

@@ -419,6 +419,9 @@ def list_reviews(
     storage_status: Optional[str] = None,
     keyword: Optional[str] = None,
     review_phase: Optional[str] = None,
+    review_side: Optional[str] = None,
+    prod_review_conclusion: Optional[str] = None,
+    qc_review_conclusion: Optional[str] = None,
     qc_work_order_completed=None,
     ready_for_review_only=None,
     user_info=None,
@@ -431,18 +434,29 @@ def list_reviews(
     if rs:
         q0 = q0.filter(BizStorageReleaseReview.reception_source == rs)
     phase = normalize_review_phase(review_phase)
-    if phase == "pending":
-        q0 = q0.filter(
-            or_(
-                BizStorageReleaseReview.review_conclusion.is_(None),
-                BizStorageReleaseReview.review_conclusion == "",
-            )
+    side = normalize_review_side(review_side) or "overview"
+    if phase:
+        prod_blank = or_(
+            BizStorageReleaseReview.prod_review_conclusion.is_(None),
+            BizStorageReleaseReview.prod_review_conclusion == "",
         )
-    elif phase == "reviewed":
-        q0 = q0.filter(
-            BizStorageReleaseReview.review_conclusion.isnot(None),
-            BizStorageReleaseReview.review_conclusion != "",
+        qc_blank = or_(
+            BizStorageReleaseReview.qc_review_conclusion.is_(None),
+            BizStorageReleaseReview.qc_review_conclusion == "",
         )
+        if side == "production":
+            q0 = q0.filter(prod_blank if phase == "pending" else ~prod_blank)
+        elif side == "qc":
+            q0 = q0.filter(qc_blank if phase == "pending" else ~qc_blank)
+        else:
+            both_filled = and_(~prod_blank, ~qc_blank)
+            q0 = q0.filter(~both_filled if phase == "pending" else both_filled)
+    prc = (prod_review_conclusion or "").strip()
+    if prc:
+        q0 = q0.filter(BizStorageReleaseReview.prod_review_conclusion == prc)
+    qrc = (qc_review_conclusion or "").strip()
+    if qrc:
+        q0 = q0.filter(BizStorageReleaseReview.qc_review_conclusion == qrc)
     rc = (review_conclusion or "").strip()
     if rc:
         q0 = q0.filter(BizStorageReleaseReview.review_conclusion == rc)
@@ -944,6 +958,7 @@ def submit_review(
     db: Session,
     review_id: str,
     *,
+    review_side: str,
     review_conclusion: str,
     review_remark: Optional[str] = None,
     review_attachments: Optional[str] = None,
@@ -961,17 +976,50 @@ def submit_review(
     )
     if not rev:
         raise ValueError("审核记录不存在")
+    side = normalize_review_side(review_side)
+    if side not in ("production", "qc"):
+        raise ValueError("审核侧无效")
+    conclusion = (review_conclusion or "").strip()
+    if conclusion not in ("tg", "btg"):
+        raise ValueError("审核结论无效")
     user_name = _get_user_name(db, user_id)
-    rev.review_conclusion = review_conclusion
-    rev.review_remark = (review_remark or "").strip() or None
-    rev.review_attachments = (review_attachments or "").strip() or None
+    now = datetime.now()
+    remark = (review_remark or "").strip() or None
+    attachments = (review_attachments or "").strip() or None
+    if side == "production":
+        rev.prod_review_conclusion = conclusion
+        rev.prod_review_remark = remark
+        rev.prod_review_attachments = attachments
+        rev.prod_review_by = user_id
+        rev.prod_review_by_name = user_name
+        rev.prod_review_time = now
+    else:
+        rev.qc_review_conclusion = conclusion
+        rev.qc_review_remark = remark
+        rev.qc_review_attachments = attachments
+        rev.qc_review_by = user_id
+        rev.qc_review_by_name = user_name
+        rev.qc_review_time = now
+    agg = aggregate_review_conclusion(rev.prod_review_conclusion, rev.qc_review_conclusion)
+    rev.review_conclusion = agg
+    rev.review_remark = remark
+    rev.review_attachments = attachments
     rev.review_by = user_id
     rev.review_by_name = user_name
-    rev.review_time = datetime.now()
+    rev.review_time = now
     rev.update_by = user_id
-    rev.update_time = datetime.now()
+    rev.update_time = now
     db.add(rev)
     db.flush()
+    action = storage_transition(
+        rev.prod_review_conclusion, rev.qc_review_conclusion, rev.storage_status
+    )
+    if action == "store":
+        store_sample(db, review_id, tenant_no=tenant_no, user_id=user_id)
+        db.refresh(rev)
+    elif action == "unstore":
+        unstore_sample(db, review_id, tenant_no=tenant_no, user_id=user_id)
+        db.refresh(rev)
     return rev
 
 
@@ -979,6 +1027,7 @@ def batch_submit_review(
     db: Session,
     ids: List[str],
     *,
+    review_side: str,
     review_conclusion: str,
     review_remark: Optional[str] = None,
     review_attachments: Optional[str] = None,
@@ -992,6 +1041,7 @@ def batch_submit_review(
             submit_review(
                 db,
                 rid,
+                review_side=review_side,
                 review_conclusion=review_conclusion,
                 review_remark=review_remark,
                 review_attachments=review_attachments,
@@ -1022,8 +1072,8 @@ def store_sample(
     )
     if not rev:
         raise ValueError("审核记录不存在")
-    if rev.review_conclusion != "tg":
-        raise ValueError("只有审核通过的记录才能执行入库操作")
+    if aggregate_review_conclusion(rev.prod_review_conclusion, rev.qc_review_conclusion) != "tg":
+        raise ValueError("生产与质检均通过后才能入库")
     if rev.storage_status != "drk":
         raise ValueError("当前状态不允许入库操作")
     user_name = _get_user_name(db, user_id)
@@ -1058,6 +1108,54 @@ def store_sample(
     return rev
 
 
+def unstore_sample(
+    db: Session,
+    review_id: str,
+    *,
+    tenant_no: str,
+    user_id: str,
+) -> BizStorageReleaseReview:
+    rev = (
+        db.query(BizStorageReleaseReview)
+        .filter(
+            BizStorageReleaseReview.id == review_id,
+            BizStorageReleaseReview.tenant_no == tenant_no,
+            BizStorageReleaseReview.del_flag == 0,
+        )
+        .first()
+    )
+    if not rev:
+        raise ValueError("审核记录不存在")
+    if (rev.storage_status or "") != "yrk":
+        return rev
+    rev.storage_status = "drk"
+    rev.storage_by = None
+    rev.storage_by_name = None
+    rev.storage_time = None
+    rev.update_by = user_id
+    rev.update_time = datetime.now()
+    db.add(rev)
+    if (rev.reception_source or "cell_storage") == "cell_storage":
+        sample_code = (rev.sample_code or "").strip()
+        if sample_code:
+            ps = (
+                db.query(BizProtocolSample)
+                .filter(
+                    BizProtocolSample.sample_code == sample_code,
+                    BizProtocolSample.tenant_no == tenant_no,
+                    BizProtocolSample.del_flag == 0,
+                )
+                .first()
+            )
+            if ps and (ps.sample_status or "") == "yrk":
+                ps.sample_status = "jcz"
+                ps.storage_in_date = None
+                ps.update_time = datetime.now()
+                db.add(ps)
+    db.flush()
+    return rev
+
+
 def batch_store_samples(
     db: Session,
     ids: List[str],
@@ -1116,7 +1214,7 @@ def get_storage_release_map_for_samples(
         if not code:
             continue
         rev = code_to_review.get(code)
-        if not rev or not (rev.review_conclusion or "").strip():
+        if not rev:
             continue
         rc = rev.review_conclusion or ""
         out[sid] = {
@@ -1126,5 +1224,19 @@ def get_storage_release_map_for_samples(
             "reviewTime": _fmt_dt(rev.review_time),
             "reviewRemark": rev.review_remark or "",
             "reviewAttachments": rev.review_attachments or "",
+            "qcReviewConclusion": rev.qc_review_conclusion or "",
+            "qcReviewConclusionLabel": _REVIEW_CONCLUSION_LABELS.get(rev.qc_review_conclusion or "", rev.qc_review_conclusion or ""),
+            "qcReviewByName": rev.qc_review_by_name or "",
+            "qcReviewTime": _fmt_dt(rev.qc_review_time),
+            "qcReviewRemark": rev.qc_review_remark or "",
+            "qcReviewAttachments": rev.qc_review_attachments or "",
+            "prodReviewConclusion": rev.prod_review_conclusion or "",
+            "prodReviewConclusionLabel": _REVIEW_CONCLUSION_LABELS.get(rev.prod_review_conclusion or "", rev.prod_review_conclusion or ""),
+            "prodReviewByName": rev.prod_review_by_name or "",
+            "prodReviewTime": _fmt_dt(rev.prod_review_time),
+            "prodReviewRemark": rev.prod_review_remark or "",
+            "prodReviewAttachments": rev.prod_review_attachments or "",
+            "storageStatus": rev.storage_status or "drk",
+            "storageStatusLabel": _STORAGE_STATUS_LABELS.get(rev.storage_status or "drk", rev.storage_status or "drk"),
         }
     return out