From 10ddc8761b860f7df7d66c0c55c1942481c6094f Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Fri, 15 May 2026 01:39:21 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20FE-Admin=20FE-User:=20Chunk=20L2=20?= =?UTF-8?q?=E2=80=94=20Fix=20F4=20BudgetAdjustSection=20bypass=20readOnly?= =?UTF-8?q?=20khi=20Approver=20scope=20(menu=20Duy=E1=BB=87t)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bro UAT S23 t2 catch: "Đã stick cho edit trong luồng duyệt nhưng trong menu duyệt -> vẫn không edit đc ngân sách". Investigator audit root cause: - BudgetAdjustSection canAdjust = !readOnly && (...) — `!readOnly` short-circuit block F4 logic - Menu Duyệt route truyền readOnly=true xuống PeDetailTabs → button "Điều chỉnh" hidden dù admin đã tick AllowApproverEditBudget cho slot + actor match - F3 wire ItemsTab ĐÚNG via `itemsReadOnly = readOnly && !approverEditMode` pattern bypass — F4 không follow same pattern Refactor canAdjust × 2 app (rule §3.9 mirror): ``` - canAdjust = !readOnly && (isAdmin || (isDrafter && isDrafterPhase) || isApproverChoDuyet) + canAdjust = isAdmin + || (!readOnly && isDrafter && isDrafterPhase) + || isApproverChoDuyet ``` → F4 Approver scope (Mig 30) BYPASS readOnly: - Admin: bypass readOnly (full quyền) - Drafter (Nháp/TraLai): chỉ Workspace (readOnly=false) - Approver ChoDuyet + flag tick + actor match: bypass readOnly → button "Điều chỉnh" visible trong menu Duyệt Mirror F3 pattern (itemsReadOnly line 118). F4 wire S22+5 ban đầu miss BYPASS pattern — fixed S23 t2. Verify: - npm run build × 2 app pass (0 TS err, bundle hash rotated) - Bro UAT verify: tick F4 → vào menu Duyệt → click "Điều chỉnh ngân sách" → modal open editable Pattern lesson saved memory: per-NV admin opt-in flag wire RULE — FE bypass readOnly khi flag tick + actor match + phase match (mirror F3 itemsReadOnly). F4 BudgetAdjustSection retroactive fix. Co-Authored-By: Claude Opus 4.7 (1M context) --- fe-admin/src/components/pe/PeDetailTabs.tsx | 8 +++++++- fe-user/src/components/pe/PeDetailTabs.tsx | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/fe-admin/src/components/pe/PeDetailTabs.tsx b/fe-admin/src/components/pe/PeDetailTabs.tsx index 92875ec..9167056 100644 --- a/fe-admin/src/components/pe/PeDetailTabs.tsx +++ b/fe-admin/src/components/pe/PeDetailTabs.tsx @@ -966,7 +966,13 @@ function BudgetAdjustSection({ ev, readOnly }: { ev: PeDetailBundle; readOnly: b && actorInCurrentLevel && approverEditBudgetAllowed - const canAdjust = !readOnly && (isAdmin || (isDrafter && isDrafterPhase) || isApproverChoDuyet) + // S23 t2 bug fix: F4 Approver scope BYPASS readOnly (mirror F3 itemsReadOnly + // pattern). Khi admin tick AllowApproverEditBudget cho slot + actor match + + // Phase=ChoDuyet → button "Điều chỉnh" enable trong menu Duyệt (readOnly=true) + // dù chế độ chỉ-đọc. Drafter + Admin vẫn cần !readOnly (chỉ active từ Workspace). + const canAdjust = isAdmin + || (!readOnly && isDrafter && isDrafterPhase) + || isApproverChoDuyet const initialManual = (ev.budgetManualName !== null || ev.budgetManualAmount !== null) && !ev.budgetId const [manualMode, setManualMode] = useState(initialManual) diff --git a/fe-user/src/components/pe/PeDetailTabs.tsx b/fe-user/src/components/pe/PeDetailTabs.tsx index dec220d..2bb6a9c 100644 --- a/fe-user/src/components/pe/PeDetailTabs.tsx +++ b/fe-user/src/components/pe/PeDetailTabs.tsx @@ -970,7 +970,13 @@ function BudgetAdjustSection({ ev, readOnly }: { ev: PeDetailBundle; readOnly: b && actorInCurrentLevel && approverEditBudgetAllowed - const canAdjust = !readOnly && (isAdmin || (isDrafter && isDrafterPhase) || isApproverChoDuyet) + // S23 t2 bug fix: F4 Approver scope BYPASS readOnly (mirror F3 itemsReadOnly + // pattern line 118). Khi admin tick AllowApproverEditBudget cho slot + actor + // match + Phase=ChoDuyet → button "Điều chỉnh" enable trong menu Duyệt (readOnly=true) + // dù chế độ chỉ-đọc. Drafter + Admin vẫn cần !readOnly (chỉ active từ Workspace). + const canAdjust = isAdmin + || (!readOnly && isDrafter && isDrafterPhase) + || isApproverChoDuyet const initialManual = (ev.budgetManualName !== null || ev.budgetManualAmount !== null) && !ev.budgetId const [manualMode, setManualMode] = useState(initialManual)