From 0d3c0cf16b27e001885d27ac09d126ecaa62b9a5 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Wed, 24 Jun 2026 17:09:07 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20PurchaseEvaluation:=20t=C3=A1ch=20kh?= =?UTF-8?q?=E1=BB=91i=20n=E1=BB=95i=20b=E1=BA=ADt=20"4.=20Th=C3=B4ng=20tin?= =?UTF-8?q?=20ch=E1=BB=8Dn=20th=E1=BA=A7u"=20(anh=20Ki=E1=BB=87t=20FDC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tách c/d/e (Giá chào thầu + Đề xuất PRO/CCM + Bảng so sánh + Link hồ sơ) khỏi Section 3 thành khối NỔI BẬT riêng "4. Thông tin chọn thầu" — header xanh #1F7DC1 giống bảng ngân sách "giá trị thực hiện". Đánh số lại 1->5: - 1. Tên NCC/TP + Giá trị (chào thầu): winner "mang từ trên xuống", hiển-thị nổi bật (Section 3 giữ ô CHỌN NccSelectorRow; multi-winner -> tên cách dấu phẩy) - 2-3. Giá đề xuất PRO/CCM (SuggestedPriceRows) · 4. Bảng so sánh giá · 5. Link hồ sơ - Ý kiến cấp duyệt: Section 4 -> 5. FE-only (PeDetailTabs ChonNccSection + new ThongTinChonThauSection), 2 app SHA-mirror, build sạch. Mockup anh Kiệt duyệt trước khi build. Co-Authored-By: Claude Opus 4.8 --- fe-admin/src/components/pe/PeDetailTabs.tsx | 116 ++++++++++++-------- fe-user/src/components/pe/PeDetailTabs.tsx | 116 ++++++++++++-------- 2 files changed, 138 insertions(+), 94 deletions(-) diff --git a/fe-admin/src/components/pe/PeDetailTabs.tsx b/fe-admin/src/components/pe/PeDetailTabs.tsx index 8a994c5..88b8962 100644 --- a/fe-admin/src/components/pe/PeDetailTabs.tsx +++ b/fe-admin/src/components/pe/PeDetailTabs.tsx @@ -402,7 +402,12 @@ export function PeDetailTabs({
-
+ {/* [anh Kiệt FDC S87+] Khối NỔI BẬT "4. Thông tin chọn thầu" — header xanh + giống bảng "giá trị thực hiện". Gom winner + c/d/e tách khỏi Section 3. */} +
+ +
+
{mode === 'workspace' && (
Ý kiến + chữ ký auto đồng bộ khi NV duyệt phiếu — vào menu “Duyệt” để ký. @@ -1742,7 +1747,7 @@ function SuggestedPriceRows({ ev, readOnly }: { ev: PeDetailBundle; readOnly: bo return (
- Giá đề xuất (ngoài giá chào thầu) + 2 – 3. Giá đề xuất từ PRO / CCM (ngoài giá chào thầu)
{/* PRO — Giá Min / Giá Max */} @@ -1895,17 +1900,9 @@ function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly const canCreateContract = !readOnly && ev.phase === PurchaseEvaluationPhase.DaDuyet && !ev.contractId && ev.suppliers.some(s => s.isWinner) const [createOpen, setCreateOpen] = useState(false) - // c. Giá chào thầu = sum quotes của NCC được chọn (winner). Dùng helper - // module-level (computeGiaChaoThau) — cùng predicate với pre-check gửi duyệt. - const hasWinner = ev.suppliers.some(s => s.isWinner) // [Mig 58] multi-winner - const giaChaoThau = computeGiaChaoThau(ev) - - // d. Bản so sánh — attachments supplier-row null, NHƯNG loại file "đính kèm khi - // duyệt" (S78 — purpose=ApprovalAttachment cũng supplierId=null) khỏi section này. - const banSoSanhAttachments = ev.attachments.filter( - a => a.purchaseEvaluationSupplierId === null - && a.purpose !== PeAttachmentPurpose.ApprovalAttachment, - ) + // [anh Kiệt FDC S87+] c/d/e (Giá chào thầu + Đề xuất PRO/CCM + Bảng so sánh + Link hồ + // sơ) + hiển-thị winner ĐÃ TÁCH sang khối nổi bật "4. Thông tin chọn thầu" + // (ThongTinChonThauSection). Section 3 này GIỮ: a. ô CHỌN winner + b. bảng ngân sách + tạo HĐ. return (
@@ -1913,39 +1910,8 @@ function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly {/* b. TỔNG HỢP NGÂN SÁCH TRÌNH KÝ (S61 — Excel anh Kiệt). Thay BudgetFieldRow + BudgetAdjustSection cũ (module Budget bỏ hẳn). */} - — (chọn NCC/TP ở (a) trước) - ) : giaChaoThau === 0 ? ( - — (chưa nhập báo giá ở Section 4) - ) : ( - {giaChaoThau!.toLocaleString('vi-VN')} đ - ) - } - /> - {/* [Mig 54 2026-06-18 — anh Kiệt FDC] Giá đề xuất PRO (Min/Max) + CCM — - NGOÀI giá NCC. Role-gate qua canEditPro/CcmSuggestedPrice (mirror budget, - KHÔNG ràng phase). Khi DaDuyet + approvedPriceAmount → show giá chốt + nguồn. */} - -
-
- d. Bảng so sánh giá -
- -
-
-
- - {/* e. Link hồ sơ (anh Kiệt FDC) — 1 hyperlink tới thư mục hồ sơ trên NAS công ty. - Read-only: render thẻ bấm-mở (target=_blank). Editable: Input dán URL + - nút Lưu (PUT /purchase-evaluations/:id echo field bắt buộc + hoSoLink). */} - + {/* [anh Kiệt FDC S87+] c/d/e + hiển-thị winner ĐÃ chuyển sang khối nổi bật + "4. Thông tin chọn thầu" (ThongTinChonThauSection — render ở parent, sau Section 3). */} {ev.paymentTerms && ( {ev.paymentTerms}} /> @@ -1974,6 +1940,62 @@ function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly ) } +// ===== Section 4 — Thông tin chọn thầu (anh Kiệt FDC S87+) ===== +// Tách c/d/e khỏi Section 3 thành khối NỔI BẬT riêng (header xanh #1F7DC1 giống bảng +// ngân sách "giá trị thực hiện"). Đánh số 1→5: 1.Tên NCC/TP+giá trị · 2-3.Đề xuất +// PRO/CCM · 4.Bảng so sánh giá · 5.Link hồ sơ. Tên đơn vị "mang từ trên xuống" — Section 3 +// giữ ô CHỌN (NccSelectorRow), khối này HIỂN THỊ kết quả (multi-winner → tên cách dấu phẩy). +function ThongTinChonThauSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) { + const winners = ev.suppliers.filter(s => s.isWinner) + const hasWinner = winners.length > 0 + const giaChaoThau = computeGiaChaoThau(ev) + const banSoSanhAttachments = ev.attachments.filter( + a => a.purchaseEvaluationSupplierId === null + && a.purpose !== PeAttachmentPurpose.ApprovalAttachment, + ) + return ( +
+
+ Thông tin chọn thầu +
+
+ {/* 1. Tên NCC/TP + Giá trị (chào thầu) — kéo từ trên xuống, nổi bật */} +
+
+ 1 + Tên NCC / TP được chọn +
+
+ {hasWinner + ?
{winners.map(s => s.supplierName).join(', ')}
+ :
— (chọn đơn vị ở mục 3 trước)
} +
+ Giá trị (chào thầu): + {!hasWinner + ? + : giaChaoThau === 0 + ? — (chưa nhập báo giá) + : {giaChaoThau!.toLocaleString('vi-VN')} đ} +
+
+
+ + {/* 2 – 3. Giá đề xuất PRO / CCM (ngoài giá chào thầu) */} + + + {/* 4. Bảng so sánh giá */} +
+
4. Bảng so sánh giá
+ +
+ + {/* 5. Link hồ sơ */} + +
+
+ ) +} + // e. Link hồ sơ — 1 cột HoSoLink (string? nullable) trỏ thư mục hồ sơ NAS. // Read-only: thẻ
bấm-mở. Editable (phiếu DangSoanThao/TraLai + !readOnly): // Input dán URL + nút Lưu. Save = PUT /purchase-evaluations/:id echo field bắt @@ -2008,7 +2030,7 @@ function HoSoLinkRow({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: return (
- e. Link hồ sơ + 5. Link hồ sơ
{canEdit ? (
diff --git a/fe-user/src/components/pe/PeDetailTabs.tsx b/fe-user/src/components/pe/PeDetailTabs.tsx index 8a994c5..88b8962 100644 --- a/fe-user/src/components/pe/PeDetailTabs.tsx +++ b/fe-user/src/components/pe/PeDetailTabs.tsx @@ -402,7 +402,12 @@ export function PeDetailTabs({
-
+ {/* [anh Kiệt FDC S87+] Khối NỔI BẬT "4. Thông tin chọn thầu" — header xanh + giống bảng "giá trị thực hiện". Gom winner + c/d/e tách khỏi Section 3. */} +
+ +
+
{mode === 'workspace' && (
Ý kiến + chữ ký auto đồng bộ khi NV duyệt phiếu — vào menu “Duyệt” để ký. @@ -1742,7 +1747,7 @@ function SuggestedPriceRows({ ev, readOnly }: { ev: PeDetailBundle; readOnly: bo return (
- Giá đề xuất (ngoài giá chào thầu) + 2 – 3. Giá đề xuất từ PRO / CCM (ngoài giá chào thầu)
{/* PRO — Giá Min / Giá Max */} @@ -1895,17 +1900,9 @@ function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly const canCreateContract = !readOnly && ev.phase === PurchaseEvaluationPhase.DaDuyet && !ev.contractId && ev.suppliers.some(s => s.isWinner) const [createOpen, setCreateOpen] = useState(false) - // c. Giá chào thầu = sum quotes của NCC được chọn (winner). Dùng helper - // module-level (computeGiaChaoThau) — cùng predicate với pre-check gửi duyệt. - const hasWinner = ev.suppliers.some(s => s.isWinner) // [Mig 58] multi-winner - const giaChaoThau = computeGiaChaoThau(ev) - - // d. Bản so sánh — attachments supplier-row null, NHƯNG loại file "đính kèm khi - // duyệt" (S78 — purpose=ApprovalAttachment cũng supplierId=null) khỏi section này. - const banSoSanhAttachments = ev.attachments.filter( - a => a.purchaseEvaluationSupplierId === null - && a.purpose !== PeAttachmentPurpose.ApprovalAttachment, - ) + // [anh Kiệt FDC S87+] c/d/e (Giá chào thầu + Đề xuất PRO/CCM + Bảng so sánh + Link hồ + // sơ) + hiển-thị winner ĐÃ TÁCH sang khối nổi bật "4. Thông tin chọn thầu" + // (ThongTinChonThauSection). Section 3 này GIỮ: a. ô CHỌN winner + b. bảng ngân sách + tạo HĐ. return (
@@ -1913,39 +1910,8 @@ function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly {/* b. TỔNG HỢP NGÂN SÁCH TRÌNH KÝ (S61 — Excel anh Kiệt). Thay BudgetFieldRow + BudgetAdjustSection cũ (module Budget bỏ hẳn). */} - — (chọn NCC/TP ở (a) trước) - ) : giaChaoThau === 0 ? ( - — (chưa nhập báo giá ở Section 4) - ) : ( - {giaChaoThau!.toLocaleString('vi-VN')} đ - ) - } - /> - {/* [Mig 54 2026-06-18 — anh Kiệt FDC] Giá đề xuất PRO (Min/Max) + CCM — - NGOÀI giá NCC. Role-gate qua canEditPro/CcmSuggestedPrice (mirror budget, - KHÔNG ràng phase). Khi DaDuyet + approvedPriceAmount → show giá chốt + nguồn. */} - -
-
- d. Bảng so sánh giá -
- -
-
-
- - {/* e. Link hồ sơ (anh Kiệt FDC) — 1 hyperlink tới thư mục hồ sơ trên NAS công ty. - Read-only: render thẻ bấm-mở (target=_blank). Editable: Input dán URL + - nút Lưu (PUT /purchase-evaluations/:id echo field bắt buộc + hoSoLink). */} - + {/* [anh Kiệt FDC S87+] c/d/e + hiển-thị winner ĐÃ chuyển sang khối nổi bật + "4. Thông tin chọn thầu" (ThongTinChonThauSection — render ở parent, sau Section 3). */} {ev.paymentTerms && ( {ev.paymentTerms}} /> @@ -1974,6 +1940,62 @@ function ChonNccSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly ) } +// ===== Section 4 — Thông tin chọn thầu (anh Kiệt FDC S87+) ===== +// Tách c/d/e khỏi Section 3 thành khối NỔI BẬT riêng (header xanh #1F7DC1 giống bảng +// ngân sách "giá trị thực hiện"). Đánh số 1→5: 1.Tên NCC/TP+giá trị · 2-3.Đề xuất +// PRO/CCM · 4.Bảng so sánh giá · 5.Link hồ sơ. Tên đơn vị "mang từ trên xuống" — Section 3 +// giữ ô CHỌN (NccSelectorRow), khối này HIỂN THỊ kết quả (multi-winner → tên cách dấu phẩy). +function ThongTinChonThauSection({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: boolean }) { + const winners = ev.suppliers.filter(s => s.isWinner) + const hasWinner = winners.length > 0 + const giaChaoThau = computeGiaChaoThau(ev) + const banSoSanhAttachments = ev.attachments.filter( + a => a.purchaseEvaluationSupplierId === null + && a.purpose !== PeAttachmentPurpose.ApprovalAttachment, + ) + return ( +
+
+ Thông tin chọn thầu +
+
+ {/* 1. Tên NCC/TP + Giá trị (chào thầu) — kéo từ trên xuống, nổi bật */} +
+
+ 1 + Tên NCC / TP được chọn +
+
+ {hasWinner + ?
{winners.map(s => s.supplierName).join(', ')}
+ :
— (chọn đơn vị ở mục 3 trước)
} +
+ Giá trị (chào thầu): + {!hasWinner + ? + : giaChaoThau === 0 + ? — (chưa nhập báo giá) + : {giaChaoThau!.toLocaleString('vi-VN')} đ} +
+
+
+ + {/* 2 – 3. Giá đề xuất PRO / CCM (ngoài giá chào thầu) */} + + + {/* 4. Bảng so sánh giá */} +
+
4. Bảng so sánh giá
+ +
+ + {/* 5. Link hồ sơ */} + +
+
+ ) +} + // e. Link hồ sơ — 1 cột HoSoLink (string? nullable) trỏ thư mục hồ sơ NAS. // Read-only: thẻ
bấm-mở. Editable (phiếu DangSoanThao/TraLai + !readOnly): // Input dán URL + nút Lưu. Save = PUT /purchase-evaluations/:id echo field bắt @@ -2008,7 +2030,7 @@ function HoSoLinkRow({ ev, readOnly = false }: { ev: PeDetailBundle; readOnly?: return (
- e. Link hồ sơ + 5. Link hồ sơ
{canEdit ? (