From 4b29d007164391dd3a492cb3002f0e2779290dd8 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Wed, 13 May 2026 09:43:20 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20FE-PE:=20Chunk=20B=20=E2=80=94=20Fix?= =?UTF-8?q?=20button=20"Tr=E1=BA=A3=20l=E1=BA=A1i"=20g=E1=BB=ADi=20decisio?= =?UTF-8?q?n=3DApprove=20thay=20v=C3=AC=20Reject=20(gotcha=20#45)=20mirror?= =?UTF-8?q?=202=20app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug pattern: button "← Trả lại" trong PeWorkflowPanel.tsx hiển thị đúng label (L205-207 isSendBack include TraLai) NHƯNG payload `isReject` (L64-66) thiếu nhánh TraLai → gửi decision=1 (Approve) thay vì 2 (Reject) khi target=TraLai (98). BE Service vào APPROVE STEP → ApproveV2Async UPSERT opinion "đã duyệt" + advance Cấp tiếp theo. User UAT thấy: "Trả về nhưng hệ thống vẫn duyệt". Inconsistency thứ 2: dialog `isSendBack` (L247-248) cũng thiếu nhánh TraLai → dialog title fallback `✓ Duyệt → Trả lại` + KHÔNG hiển thị amber warning. Fix 3 chỗ × 2 app (fe-user + fe-admin, rule §3.9 mirror): 1. `isReject` payload — thêm nhánh `target=TraLai && phase!=TraLai` 2. dialog `isSendBack` — thêm nhánh TraLai + guard phase != TraLai 3. Comments document context bug + cross-ref BE guard Chunk A Sync với BE guard (Chunk A `de00887` `PurchaseEvaluationWorkflowService.cs`): - BE throw ConflictException khi target ∈ {TraLai, TuChoi} && decision != Reject - 2 phía cùng đúng → no payload mismatch Verify: - npm run build × 2 app pass (fe-user 17.91s, fe-admin 6.71s, 0 TS6 err) - Warning chunk size pre-existing (NOT introduced) Pending Chunk C: docs gotcha #45 + STATUS + HANDOFF + session log. Co-Authored-By: Claude Opus 4.7 (1M context) --- fe-admin/src/components/pe/PeWorkflowPanel.tsx | 16 ++++++++++++++-- fe-user/src/components/pe/PeWorkflowPanel.tsx | 16 ++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/fe-admin/src/components/pe/PeWorkflowPanel.tsx b/fe-admin/src/components/pe/PeWorkflowPanel.tsx index c11e298..09bf34e 100644 --- a/fe-admin/src/components/pe/PeWorkflowPanel.tsx +++ b/fe-admin/src/components/pe/PeWorkflowPanel.tsx @@ -63,11 +63,18 @@ export function PeWorkflowPanel({ mutationFn: async () => { // Decision = Reject (2) khi: // - target = TuChoi (huỷ phiếu) - // - target = DangSoanThao từ phase trung gian (= Trả lại — smart reject Mig 16 + // - target = DangSoanThao từ phase trung gian (= Trả lại legacy Mig 16 // set RejectedFromPhase + clear N-stage rows + Drafter resume jump-back) + // - target = TraLai (98) từ phase trung gian — Session 17 spec mới: Trả + // lại là Phase RIÊNG (gotcha #45 — thiếu nhánh này gây "Trả về nhưng + // hệ thống vẫn duyệt" do BE nhận decision=Approve → ApproveV2Async). + // BE có guard mirror trong PurchaseEvaluationWorkflowService.TransitionAsync + // throw ConflictException nếu payload mismatch — phải sync 2 phía. const isReject = target === PurchaseEvaluationPhase.TuChoi || (target === PurchaseEvaluationPhase.DangSoanThao && evaluation.phase !== PurchaseEvaluationPhase.DangSoanThao) + || (target === PurchaseEvaluationPhase.TraLai + && evaluation.phase !== PurchaseEvaluationPhase.TraLai) return api.post(`/purchase-evaluations/${evaluation.id}/transitions`, { targetPhase: target, decision: isReject ? 2 : 1, @@ -250,8 +257,13 @@ export function PeWorkflowPanel({ {target !== null && (() => { const isCancel = target === PurchaseEvaluationPhase.TuChoi - const isSendBack = target === PurchaseEvaluationPhase.DangSoanThao + // isSendBack sync với button label + payload isReject (gotcha #45). + // Include cả DangSoanThao (legacy Mig 16) lẫn TraLai (Session 17 spec) + // — cả 2 là Trả lại Drafter sửa. + const isSendBack = (target === PurchaseEvaluationPhase.DangSoanThao + || target === PurchaseEvaluationPhase.TraLai) && evaluation.phase !== PurchaseEvaluationPhase.DangSoanThao + && evaluation.phase !== PurchaseEvaluationPhase.TraLai const dialogTitle = isCancel ? '✗ Từ chối phiếu (khoá hoàn toàn)' : isSendBack diff --git a/fe-user/src/components/pe/PeWorkflowPanel.tsx b/fe-user/src/components/pe/PeWorkflowPanel.tsx index 3612929..32e3fbe 100644 --- a/fe-user/src/components/pe/PeWorkflowPanel.tsx +++ b/fe-user/src/components/pe/PeWorkflowPanel.tsx @@ -59,11 +59,18 @@ export function PeWorkflowPanel({ mutationFn: async () => { // Decision = Reject (2) khi: // - target = TuChoi (huỷ phiếu) - // - target = DangSoanThao từ phase trung gian (= Trả lại — smart reject Mig 16 + // - target = DangSoanThao từ phase trung gian (= Trả lại legacy Mig 16 // set RejectedFromPhase + clear N-stage rows + Drafter resume jump-back) + // - target = TraLai (98) từ phase trung gian — Session 17 spec mới: Trả + // lại là Phase RIÊNG (gotcha #45 — thiếu nhánh này gây "Trả về nhưng + // hệ thống vẫn duyệt" do BE nhận decision=Approve → ApproveV2Async). + // BE có guard mirror trong PurchaseEvaluationWorkflowService.TransitionAsync + // throw ConflictException nếu payload mismatch — phải sync 2 phía. const isReject = target === PurchaseEvaluationPhase.TuChoi || (target === PurchaseEvaluationPhase.DangSoanThao && evaluation.phase !== PurchaseEvaluationPhase.DangSoanThao) + || (target === PurchaseEvaluationPhase.TraLai + && evaluation.phase !== PurchaseEvaluationPhase.TraLai) return api.post(`/purchase-evaluations/${evaluation.id}/transitions`, { targetPhase: target, decision: isReject ? 2 : 1, @@ -244,8 +251,13 @@ export function PeWorkflowPanel({ {target !== null && (() => { const isCancel = target === PurchaseEvaluationPhase.TuChoi - const isSendBack = target === PurchaseEvaluationPhase.DangSoanThao + // isSendBack sync với button label L205-207 + payload isReject L64-68 + // (gotcha #45). Include cả DangSoanThao (legacy Mig 16) lẫn TraLai + // (Session 17 spec) — cả 2 là Trả lại Drafter sửa. + const isSendBack = (target === PurchaseEvaluationPhase.DangSoanThao + || target === PurchaseEvaluationPhase.TraLai) && evaluation.phase !== PurchaseEvaluationPhase.DangSoanThao + && evaluation.phase !== PurchaseEvaluationPhase.TraLai const dialogTitle = isCancel ? '✗ Từ chối phiếu (khoá hoàn toàn)' : isSendBack