From 6143a99133522e35bb84f33a10fd5cd52ab77b7e Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Wed, 19 Aug 2026 11:39:00 +0800 Subject: [PATCH] feat: apply dual-gate storage release submit, auto store, and unstore. Co-authored-by: Cursor --- backend/app/crud/storage_release_review.py | 148 ++++++++++++++++++--- 1 file changed, 130 insertions(+), 18 deletions(-) diff --git a/backend/app/crud/storage_release_review.py b/backend/app/crud/storage_release_review.py index 6a328ddc..2b2e3093 100644 --- a/backend/app/crud/storage_release_review.py +++ b/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