From f7bd97a83d059254729bf7ed048da3fdcadcb561 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Thu, 25 Jun 2026 14:39:08 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20FE-User=20=C2=B7=20FE-Admin:=20PE=20?= =?UTF-8?q?gi=C3=A1=20=C4=91=E1=BB=81=20xu=E1=BA=A5t=20PRO/CCM=20editable?= =?UTF-8?q?=20theo=20role=20(g=E1=BB=A1=20!readOnly=20over-gate)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ô "Giá đề xuất (PRO/CCM)" trong SuggestedPriceRows bị khoá thừa tầng !readOnly mà ô ngân sách song song (editable={bs.canEditCcm}) không có. Hệ quả: CCM (role CostControl) không nhập được giá đề xuất khi xem phiếu ở view readOnly=true (preview danh sách, hoặc màn Duyệt chưa tới lượt mình) — đúng báo cáo anh Kiệt FDC "PRO oki nhưng CCM không nhập". Gỡ !readOnly, gate thuần theo role-capability (ev.canEdit{Pro,Ccm}SuggestedPrice do BE tính). BE setter vẫn fail-closed theo role (ForbiddenException) nên gỡ gate FE = an toàn, khớp hành vi ô ngân sách + intent handler "KHÔNG ràng Phase, chỉnh như tài liệu sống". Bỏ luôn prop readOnly không dùng khỏi signature + call-site. 2 app SHA-mirror, build xanh. Co-Authored-By: Claude Opus 4.8 --- fe-admin/src/components/pe/PeDetailTabs.tsx | 13 +++++++++---- fe-user/src/components/pe/PeDetailTabs.tsx | 13 +++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/fe-admin/src/components/pe/PeDetailTabs.tsx b/fe-admin/src/components/pe/PeDetailTabs.tsx index 9029080..0d7cd0a 100644 --- a/fe-admin/src/components/pe/PeDetailTabs.tsx +++ b/fe-admin/src/components/pe/PeDetailTabs.tsx @@ -1700,7 +1700,7 @@ const APPROVED_PRICE_SOURCE_LABEL: Record = { ProMax: 'PRO Max', Ccm: 'CCM', } -function SuggestedPriceRows({ ev, readOnly }: { ev: PeDetailBundle; readOnly: boolean }) { +function SuggestedPriceRows({ ev }: { ev: PeDetailBundle }) { const qc = useQueryClient() const invalidate = () => { qc.invalidateQueries({ queryKey: ['pe-detail', ev.id] }) @@ -1722,8 +1722,13 @@ function SuggestedPriceRows({ ev, readOnly }: { ev: PeDetailBundle; readOnly: bo onError: e => toast.error(getErrorMessage(e)), }) - const canEditPro = !readOnly && ev.canEditProSuggestedPrice - const canEditCcm = !readOnly && ev.canEditCcmSuggestedPrice + // [S89 anh Kiệt FDC bug] Giá đề xuất PRO/CCM editable theo ROLE-capability đơn thuần + // (mirror ô ngân sách Block A/B dùng bs.canEditPro/canEditCcm — KHÔNG gắn !readOnly). + // BE setter fail-closed theo role (Forbidden) + "không ràng Phase, chỉnh như tài liệu + // sống" → CCM/PRO nhập được MỌI view (kể cả preview + Duyệt chưa-tới-lượt readOnly=true), + // không còn kẹt read-only khi xem phiếu ngoài đúng lượt duyệt của mình. + const canEditPro = ev.canEditProSuggestedPrice + const canEditCcm = ev.canEditCcmSuggestedPrice // Ghi chú PRO/CCM inline-edit state (Textarea). Echo cùng body absolute-set khi lưu giá. const [proNoteText, setProNoteText] = useState(ev.proSuggestedPriceNote ?? '') @@ -1981,7 +1986,7 @@ function ThongTinChonThauSection({ ev, readOnly = false }: { ev: PeDetailBundle; {/* 2 – 3. Giá đề xuất PRO / CCM (ngoài giá chào thầu) */} - + {/* 4. Bảng so sánh giá */}
diff --git a/fe-user/src/components/pe/PeDetailTabs.tsx b/fe-user/src/components/pe/PeDetailTabs.tsx index 9029080..0d7cd0a 100644 --- a/fe-user/src/components/pe/PeDetailTabs.tsx +++ b/fe-user/src/components/pe/PeDetailTabs.tsx @@ -1700,7 +1700,7 @@ const APPROVED_PRICE_SOURCE_LABEL: Record = { ProMax: 'PRO Max', Ccm: 'CCM', } -function SuggestedPriceRows({ ev, readOnly }: { ev: PeDetailBundle; readOnly: boolean }) { +function SuggestedPriceRows({ ev }: { ev: PeDetailBundle }) { const qc = useQueryClient() const invalidate = () => { qc.invalidateQueries({ queryKey: ['pe-detail', ev.id] }) @@ -1722,8 +1722,13 @@ function SuggestedPriceRows({ ev, readOnly }: { ev: PeDetailBundle; readOnly: bo onError: e => toast.error(getErrorMessage(e)), }) - const canEditPro = !readOnly && ev.canEditProSuggestedPrice - const canEditCcm = !readOnly && ev.canEditCcmSuggestedPrice + // [S89 anh Kiệt FDC bug] Giá đề xuất PRO/CCM editable theo ROLE-capability đơn thuần + // (mirror ô ngân sách Block A/B dùng bs.canEditPro/canEditCcm — KHÔNG gắn !readOnly). + // BE setter fail-closed theo role (Forbidden) + "không ràng Phase, chỉnh như tài liệu + // sống" → CCM/PRO nhập được MỌI view (kể cả preview + Duyệt chưa-tới-lượt readOnly=true), + // không còn kẹt read-only khi xem phiếu ngoài đúng lượt duyệt của mình. + const canEditPro = ev.canEditProSuggestedPrice + const canEditCcm = ev.canEditCcmSuggestedPrice // Ghi chú PRO/CCM inline-edit state (Textarea). Echo cùng body absolute-set khi lưu giá. const [proNoteText, setProNoteText] = useState(ev.proSuggestedPriceNote ?? '') @@ -1981,7 +1986,7 @@ function ThongTinChonThauSection({ ev, readOnly = false }: { ev: PeDetailBundle;
{/* 2 – 3. Giá đề xuất PRO / CCM (ngoài giá chào thầu) */} - + {/* 4. Bảng so sánh giá */}