Commit 5055a480 5055a48013de5b5cccc21f9d81a0ed6a16dc78d6 by 沈秋雨

fix(review): 调整审查领取超时时间并优化状态处理逻辑或回滚

1 parent 46f11430
...@@ -29,7 +29,7 @@ DASHBOARD = ROOT / "l2_review_dashboard.html" ...@@ -29,7 +29,7 @@ DASHBOARD = ROOT / "l2_review_dashboard.html"
29 REPORT_DIR.mkdir(parents=True, exist_ok=True) 29 REPORT_DIR.mkdir(parents=True, exist_ok=True)
30 GROUP_INDEX_CACHE: dict[tuple[str, int, int, str], list[dict[str, object]]] = {} 30 GROUP_INDEX_CACHE: dict[tuple[str, int, int, str], list[dict[str, object]]] = {}
31 load_dotenv(ROOT / ".env") 31 load_dotenv(ROOT / ".env")
32 REVIEW_CLAIM_TTL_SECONDS = max(60, int(os.getenv("REVIEW_CLAIM_TTL_SECONDS", "600"))) 32 REVIEW_CLAIM_TTL_SECONDS = max(60, int(os.getenv("REVIEW_CLAIM_TTL_SECONDS", "1800")))
33 REVIEW_ACCESS_CODE = os.getenv("REVIEW_ACCESS_CODE", "") 33 REVIEW_ACCESS_CODE = os.getenv("REVIEW_ACCESS_CODE", "")
34 REVIEW_SESSION_TTL_SECONDS = max(300, int(os.getenv("REVIEW_SESSION_TTL_SECONDS", "43200"))) 34 REVIEW_SESSION_TTL_SECONDS = max(300, int(os.getenv("REVIEW_SESSION_TTL_SECONDS", "43200")))
35 REVIEW_SESSIONS: dict[str, tuple[str, float]] = {} 35 REVIEW_SESSIONS: dict[str, tuple[str, float]] = {}
...@@ -578,7 +578,7 @@ def _claim_staging_review(staging_id: int, reviewer: str, client_token: str) -> ...@@ -578,7 +578,7 @@ def _claim_staging_review(staging_id: int, reviewer: str, client_token: str) ->
578 review_claim_expires_at = DATE_ADD(NOW(), INTERVAL %s SECOND), 578 review_claim_expires_at = DATE_ADD(NOW(), INTERVAL %s SECOND),
579 review_version = review_version + 1 579 review_version = review_version + 1
580 WHERE staging_id = %s 580 WHERE staging_id = %s
581 AND staging_status NOT IN ('imported', 'deleted') 581 AND staging_status <> 'imported'
582 AND ( 582 AND (
583 biz_review_status IN ('pending', 'unsure') 583 biz_review_status IN ('pending', 'unsure')
584 OR biz_review_status = 'not_required' 584 OR biz_review_status = 'not_required'
...@@ -599,8 +599,6 @@ def _claim_staging_review(staging_id: int, reviewer: str, client_token: str) -> ...@@ -599,8 +599,6 @@ def _claim_staging_review(staging_id: int, reviewer: str, client_token: str) ->
599 message = f"找不到 staging_id={staging_id},请刷新页面后重试" 599 message = f"找不到 staging_id={staging_id},请刷新页面后重试"
600 elif current.get("staging_status") == "imported": 600 elif current.get("staging_status") == "imported":
601 message = "该记录已经入库,只能浏览,不能再领取审核" 601 message = "该记录已经入库,只能浏览,不能再领取审核"
602 elif current.get("staging_status") == "deleted":
603 message = "该记录已被删除,不能领取审核"
604 elif current.get("review_claimed_by"): 602 elif current.get("review_claimed_by"):
605 message = f"该记录已由 {current['review_claimed_by']} 领取" 603 message = f"该记录已由 {current['review_claimed_by']} 领取"
606 else: 604 else:
......
...@@ -96,6 +96,27 @@ def test_stale_review_returns_conflict_without_overwrite(): ...@@ -96,6 +96,27 @@ def test_stale_review_returns_conflict_without_overwrite():
96 assert not conn.committed 96 assert not conn.committed
97 97
98 98
99 def test_pending_review_restores_a_deleted_staging_row():
100 snapshot = {
101 "staging_id": 42,
102 "biz_review_status": "pending",
103 "staging_status": "staged",
104 "review_version": 6,
105 }
106 cursor = FakeCursor(update_rowcount=1, snapshot=snapshot)
107 conn = FakeConnection(cursor)
108
109 with patch.object(dashboard, "_target_conn", return_value=conn):
110 dashboard._update_staging_review(
111 42, "pending", "", "alice", 5, "alice-token"
112 )
113
114 update_sql, _params = cursor.executions[0]
115 assert "staging_status = CASE WHEN staging_status = 'deleted' THEN 'staged'" in update_sql
116 assert "biz_review_status = 'pending'" in update_sql
117 assert conn.committed
118
119
99 def test_claim_conflict_exposes_current_owner(): 120 def test_claim_conflict_exposes_current_owner():
100 snapshot = { 121 snapshot = {
101 "staging_id": 42, 122 "staging_id": 42,
...@@ -115,6 +136,38 @@ def test_claim_conflict_exposes_current_owner(): ...@@ -115,6 +136,38 @@ def test_claim_conflict_exposes_current_owner():
115 assert conn.rolled_back 136 assert conn.rolled_back
116 137
117 138
139 def test_deleted_review_can_be_claimed_for_undo_but_imported_review_cannot():
140 snapshot = {
141 "staging_id": 42,
142 "biz_review_status": "deleted",
143 "staging_status": "deleted",
144 "review_version": 4,
145 }
146 cursor = FakeCursor(update_rowcount=1, snapshot=snapshot)
147 conn = FakeConnection(cursor)
148
149 with patch.object(dashboard, "_target_conn", return_value=conn):
150 dashboard._claim_staging_review(42, "alice", "alice-token")
151
152 claim_sql, _params = cursor.executions[0]
153 assert "staging_status <> 'imported'" in claim_sql
154 assert "staging_status NOT IN ('imported', 'deleted')" not in claim_sql
155 assert "biz_review_status IN ('approved_import', 'rejected_duplicate', 'deleted')" in claim_sql
156 assert conn.committed
157
158 imported_cursor = FakeCursor(
159 update_rowcount=0,
160 snapshot={"staging_id": 43, "staging_status": "imported"},
161 )
162 imported_conn = FakeConnection(imported_cursor)
163 with patch.object(dashboard, "_target_conn", return_value=imported_conn):
164 with pytest.raises(dashboard.ReviewConflictError, match="已经入库"):
165 dashboard._claim_staging_review(43, "alice", "alice-token")
166
167 assert imported_conn.rolled_back
168 assert not imported_conn.committed
169
170
118 def test_heartbeat_does_not_increment_review_version(): 171 def test_heartbeat_does_not_increment_review_version():
119 snapshot = {"staging_id": 42, "review_version": 5} 172 snapshot = {"staging_id": 42, "review_version": 5}
120 cursor = FakeCursor(update_rowcount=1, snapshot=snapshot) 173 cursor = FakeCursor(update_rowcount=1, snapshot=snapshot)
......