[CLAUDE] FE-PE: Chunk B — Fix button "Trả lại" gửi decision=Approve thay vì Reject (gotcha #45) mirror 2 app
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) <noreply@anthropic.com>
This commit is contained in:
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user