44 KiB
sub-investigator-codebase-invest-s171 — KHKK UI mirror PE (fable-real LỆNH-A)
ENGINE-ĐẮT single top-model deep-pass. PROPOSE-ONLY. Mọi khẳng-định kèm
file:line. Đường-dẫn tương-đối gốcfe-admin/src/trừ khi ghi rõ khác. fe-user SHA256-identical (lead đã đo 4/4).
§0. Hai đính-chính vào Ground-truth của lead (đọc TRƯỚC §1)
Đ-1 — Panel 1 của PE KHÔNG phải PeListPanel. Lead ghi "PE panel 1/2/3 = PeListPanel · PeDetailTabs · PeWorkflowPanel". Đo lại: PurchaseEvaluationsListPage.tsx không import PeListPanel; panel 1 được viết INLINE trong chính file đó (:399-639). PeListPanel chỉ có một call-site duy nhất: pages/pe/PurchaseEvaluationWorkspacePage.tsx:84 (màn workspace 2-panel, grid-cols-[260px_1fr] :80).
⇒ Hệ quả cho §5: không có component list dùng-lại được để KHKK nhặt. Muốn "giống" panel 1 thì phải TÁCH nó ra trước, hoặc chép khuôn.
Đ-2 — Đã tồn tại một bản mirror PE 3-panel KHÁC, mới hơn: GĐ3 (Hợp đồng). pages/contracts/ContractsListPage.tsx:125 = grid flex-1 grid-cols-1 overflow-hidden lg:grid-cols-[340px_1fr_360px], header :106-123 sao y PE, Panel 2 = ContractDetailContent, Panel 3 = WorkflowHistoryPanel (components/contracts/), và Panel 3 khi chưa chọn thì render PipelineTreePanel currentStage={3} (:249).
⇒ Đây là tiền-lệ trong nhà, cùng chuỗi giai-đoạn, làm ở S168. KHKK là giai-đoạn NẰM GIỮA GĐ1 (PE, 3-panel) và GĐ3 (HĐ, 3-panel) mà lại là trang-giấy-cuộn ⇒ nó là mắt xích lệch duy nhất của cả 4 giai-đoạn. Đây là lập-luận mạnh nhất ủng-hộ đề-bài owner, và nó không nằm trong ảnh — owner nhìn thấy triệu-chứng, cái này là nguyên-nhân.
§1. Bản-đồ 3 panel PE (mẫu)
1.0 Khung trang (container)
| Hạng-mục | Sự-thật | file:line |
|---|---|---|
| Root | <div className="flex h-[calc(100vh-4rem)] flex-col"> — trang CHIẾM TRỌN viewport, KHÔNG scroll body |
pages/pe/PurchaseEvaluationsListPage.tsx:381 |
| Header | 1 dòng <header>: icon ClipboardCheck + h1 tiêu-đề động + badge đếm rounded-full bg-slate-100 |
:382-390 |
| Tiêu-đề động | headerTitle đổi theo deletedView/typeFilter/pendingMe |
:289-293 |
| Grid | grid flex-1 grid-cols-1 overflow-hidden lg:grid-cols-[400px_1fr_360px] |
:397 |
| URL-param lái toàn màn | type · pendingMe · deleted · q · phase · awId · id · expand |
:38-50 |
| 2 query nuôi 3 panel | list (['pe-list',{…}]) + detail (['pe-detail', selectedId], enabled: !!selectedId) |
:74-117 |
🔴 PE là một trang duy nhất; không có route detail riêng cho desktop. PurchaseEvaluationDetailPage (:782-807) chỉ là fallback fullpage mobile /purchase-evaluations/:id, và vẫn dựng đúng 2 component đó xếp dọc.
1.1 Panel 1 — LIST (trái, 400px)
<aside className="flex flex-col overflow-hidden border-r border-slate-200 bg-white"> :399
- Khối filter
space-y-2 border-b border-slate-200 p-3:400- Search icon-trong-ô:
Searchabsolute +<Input className="pl-8">, placeholder"Tìm mã / tên gói thầu / dự án…":401-409 Selectquy-trình duyệt — chỉ khipendingMe:413-422- Banner đỏ "Phiếu đã xóa"
:427-431/ banner amber "Lọc cố định: Đã gửi duyệt":432-435/Selecttrạng-thái:437-453(ba-ngả loại-trừ)
- Search icon-trong-ô:
- Vùng cuộn
flex-1 overflow-y-auto:457- Skeleton 6×
h-16 animate-pulse:458-464·EmptyState icon={ClipboardCheck}:465-469 - Cây 3 tầng
<details>:📅 Năm→📁 Dự án→🧱 Hạng mục:472-637; chevrongroup-open/year|proj|wi:rotate-90; badge đếm mỗi tầng; dựng ởyearGroups:220-262 - Tầng 4 =
<PipelineStageFolders currentStage={1} hideEmpty={filterActive} stages={buildStages(wg.items,{stage1Content:<ul>…</ul>})} />:524-625— 4 folder giai-đoạn; danh-sách phiếu bị RE-PARENT vào folder GĐ1 - Card phiếu
:530-621: hàng 1PeUrgentChips+PeFinalizeChip+ tên gói thầu + badge trạng-thái; hàng 2 mãfont-mono+ timestamp; hàng 3👤 drafter · dept+✓ HĐ - Nút
Maximize2"Xem mở rộng" absolute,opacity-0 … group-hover/row:opacity-100:607-620
- Skeleton 6×
- Persist mở/đóng:
localStoragekeype_list_expanded_projects_v3:269-287
1.2 Panel 2 — DETAIL (giữa, 1fr)
<main className="hidden overflow-y-auto bg-slate-50 p-6 lg:block"> :643
- Chưa chọn ⇒
EmptyState"Chọn phiếu ở danh sách":644-652(khớp Ảnh 1) detail.data && !isExpand⇒<PeDetailTabs evaluation onBack={closeDetail} onDelete readOnly={true} />:657-664
Ruột PeDetailTabs (components/pe/PeDetailTabs.tsx, 3613 dòng):
- Đầu phiếu:
<PePipelineStrip current={1} />:277— dải 4 giai-đoạn - Banner phiếu Trả-lại (readOnly)
:386-395 <div className="divide-y divide-slate-200">:397chứa 5 khối đánh số, đúng thứ-tự dọc:<Section title="1. Thông tin gói thầu">:4022. Hạng mục + Báo giá NCC (…):4053. Đơn vị NCC/TP được chọn:4184. Thông tin chọn thầu—<section>trần, header xanh:423-425<Section title="5. Ý kiến cấp duyệt (sign-off theo workflow)">:426-437→LevelOpinionsSectionV2khi cóapprovalWorkflowId, ngược lại fallback V1DepartmentOpinionsSection:434-436
<Section>=<section className="px-3 py-3 sm:px-5 sm:py-4">+h3 mb-3 text-xs font-semibold uppercase tracking-wide text-slate-500:514-523
Section 5 (khối owner khoanh đỏ ở Ảnh 3) — :666-774:
LevelOpinionsSectionV2lặpflow.steps; mỗi Step tínhtotalLevels·totalApprovers·signedLevels:680-700StepOpinionsBox:705-745: headerBước {n} — {name}uppercase bold + chipdepartmentNamenềnbg-emerald-50+ meta phảiml-auto{signedLevels}/{totalLevels} cấp đã duyệt · {totalApprovers} NV tham gia:718-733; rỗng ⇒— Chưa có ý kiến duyệt.:736; có ⇒grid grid-cols-1 gap-3 md:grid-cols-2:738StepOpinionEntry:747-774: cardrounded-lg border border-emerald-200 bg-white p-3;Cấp {n} — {tên}; badge⚠ Admin {X} duyệt thaykhisignedByUserId !== approverUserId:748,756-760; badge✓ Đã duyệt:762-764; bodywhitespace-pre-wrap:766; footer timestamp:769-771
1.3 Panel 3 — WORKFLOW (phải, 360px)
<aside className="hidden overflow-y-auto border-l border-slate-200 bg-white p-4 lg:block"> :668
- Chưa chọn ⇒ hộp
border-dashed+ iconX+ "Quy trình duyệt sẽ hiện khi chọn phiếu.":669-676(khớp Ảnh 1) - Có phiếu ⇒
<PeWorkflowPanel evaluation readOnly={!pendingMe} onApproved onDeleted />:677-684
Ruột PeWorkflowPanel (components/pe/PeWorkflowPanel.tsx, 1062 dòng) — thứ-tự dọc ĐÚNG như Ảnh 2:
h3 Quy trình duyệt+ meta{code} v{NN} · {name}:344-354- Banner kết-thúc-sớm
evaluation.endsBeforeCeo:360-377— 2 nhánh:DaDuyet⇒ "✅ Phiếu đã kết thúc tại {finalizeStepName} · {finalizeLevelName} — không qua CEO."; đang chạy ⇒ "⚑ Quy trình rút gọn…" - Sơ-đồ Bước → Cấp → NV
:381-478:step.statusdo BE precompute;isStepAfterFinalize(step.order)⇒opacity-50:393+ chipKết thúc tại {…}:411-418; chip tên phòngstep.departmentName:406-410; per-Cấp badge⚑ Duyệt thay CEOkhilv.allowApproverFinalize:441-448; NV liệt kê/-phân-cách:453-465+ badge✎ NS PRO/✎ NS CCM:458-463 - Fallback V1 "Phiếu này dùng quy trình cũ"
:481-485 - Banner "đến lượt bạn"
:489-508(emerald/amber theoactorInV2Level) - Banner S134 phiếu-trước-chưa-duyệt
:512-515 - 3 nút + nút XÓA (S155, CẠNH
next.map):564· dialog transition + 4 return-mode:633-707+ toggle skip-to-final:723-745+ CCM-done:749+ ô-tích level-finalize:769-791+ bộ chọn giá chốt:792+ upload file-khi-duyệt:821 DeptApprovalsSection(legacy 2-cấp phòng ban):920-924📎 File đính kèm khi duyệt:928-967—h3+ phụ-đề "File do người duyệt tải lên trong quá trình duyệt." + hàng file (Paperclip· tên ·fmtFileSize·Eyexem trước ·Download)Lịch sử duyệt (N)—<PeApprovalsSection ev>:969-971, định-nghĩa ởPeDetailTabs.tsx:778-785(export NGƯỢC về panel 3, importPeWorkflowPanel.tsx:29); ruộtApprovalsTab:3109-3219: mergeev.approvals+ synthetic-reject dựng lại từ changelog + dedupe 5s-bucket:3152-3192; mỗi entry = badge quyết-địnhdecisionBadge+ hintextractNextTargetHint+ timestamp +tên · ghi-chú:3197-3216Lịch sử thay đổi—<PeHistorySection ev>:973-975→HistoryTab:3222-3300(GET/purchase-evaluations/{id}/changelogs, filter TraLai/gửi-lại/ngân-sách)
1.4 Overlay "Xem mở rộng" (expand=1) — thành-phần THỨ TƯ của mẫu
:694-776. fixed inset-0 z-50, backdrop bg-slate-900/40, panel translate-x-full → translate-x-0 200ms, mount-then-animate 2×rAF :308-336, khoá body.overflow :340-349, Esc + focus-trap :352-378. Body = 2 cột lg:flex-row: <main> PeDetailTabs + <aside lg:w-[24rem] xl:w-[26rem]> PeWorkflowPanel :753-772.
⇒ Cùng 2 component, 3 khung khác nhau (inline · overlay · route mobile). Đây là bằng-chứng cho §5.
§2. Bản-đồ KHKK hiện-trạng
2.0 Khung — KHÁC LOẠI, không phải khác CSS
| Hạng-mục | KHKK | file:line |
|---|---|---|
| Root list | <div className="space-y-5"> — trang cuộn theo body |
pages/khkk/KhkkListPage.tsx:161 |
| Header | <PageHeader eyebrow="Giai đoạn 2" title subtitle icon accent actions> (khuôn PURO) |
:162-192 |
| Grid | grid gap-5 lg:grid-cols-[19rem_minmax(0,1fr)] xl:grid-cols-[21rem_minmax(0,1fr)] = 2 panel |
:197 |
| Panel trái | <PipelineTreePanel currentStage={2} /> — cây TOÀN TRÌNH điều-hướng, không chứa phiếu KHKK |
:198 |
| Panel phải | 6 KpiCard lọc :202-220 + card search :222-233 + bảng 8 cột :239-339 + phân trang :341-355 |
|
| Chọn phiếu | onClick={() => navigate('/khkk/'+k.id)} — rời trang |
:280 |
| Route | /khkk/list · /khkk/create · /khkk/:id |
App.tsx:107-110 |
| Detail | Trang riêng, KhkkWorkflowPanel render INLINE ngay dưới PageHeader, TRÊN mọi nội-dung |
pages/khkk/KhkkDetailPage.tsx:411 |
| Detail sections | 4 SectionCard rời: Thông tin :414 · Dòng giá per-NCC :505 · Căn cứ hồ sơ :626 · File đính kèm :721 |
2.1 Panel 1 (KHKK) — ĐANG SAI CHỖ, không phải "đang xấu"
- Ô panel-1 hiện bị
PipelineTreePanelchiếm. Đó là cây điều-hướng 4 giai-đoạn dựng từ chỉ-mục PE (components/pipeline/PipelineTreePanel.tsx:106-120, query['pipeline-pe-index'],pageSize 200), không phải danh-sách phiếu KHKK. - Danh-sách phiếu KHKK nằm ở bảng 8 cột bên phải — tức nội-dung thuộc panel 1 đang bị đặt vào ô panel 2.
- ⇒ KHKK VẮNG panel-1-theo-nghĩa-PE, và THỪA một widget khác nghiệp-vụ ở đúng chỗ đó.
2.2 Panel 2 (KHKK) — VẮNG ở màn list
Không ô nào ở /khkk/list hiển thị nội-dung phiếu. Nội-dung chỉ tồn-tại sau navigate('/khkk/:id').
2.3 Panel 3 (KHKK) — CÓ, nhưng ĐẶT SAI CHỖ và THIẾU 5 khối
pages/khkk/KhkkWorkflowPanel.tsx (491 dòng — 🔴 nằm trong pages/, không phải components/) đã mirror một phần PeWorkflowPanel:
| Khối (theo Ảnh 2) | KHKK | file:line |
|---|---|---|
Header Quy trình duyệt + meta |
✅ (thiếu v{NN}) |
:194-209 |
| Banner "đã kết thúc tại … không qua CEO" | ❌ VẮNG | — (§4 D-1) |
| Sơ-đồ Bước → Cấp ✓/●/○ | ✅ nhưng FE tự suy status | :106-120 · :213-266 |
| Chip tên phòng của Bước | ❌ VẮNG | types/khkk.ts:275-282 (chỉ departmentId) |
Badge ⚑ Duyệt thay CEO per-Cấp |
❌ VẮNG (cờ allowApproverFinalize ĐÃ CÓ trên DTO, chỉ dùng cho ô-tích) |
types/khkk.ts:272 · :143 |
| Làm mờ Bước/Cấp sau finalize | ❌ VẮNG | — |
Badge vai ✎ NS PRO/CCM |
❌ VẮNG | — |
📎 File đính kèm khi duyệt |
❌ VẮNG | — (§4 D-7) |
Lịch sử duyệt (N) |
❌ VẮNG | — (§4 D-2) |
Lịch sử thay đổi |
❌ VẮNG | — (§4 D-3) |
| Ý kiến cấp duyệt | ⚠️ CÓ NHƯNG SAI PANEL — list phẳng 1 cột trong panel duyệt | :376-412 |
| 3 nút + Dialog | ✅ | :299-374 · :415-488 |
| Ô-tích "Cấp này KẾT THÚC" (opt-in) | ✅ | :451-470 |
| 4 return-mode | ❌ cố-ý bỏ (W3 = TraLai toàn phần) | :6-8 · :290 |
| Bộ chọn giá chốt | ❌ cố-ý bỏ (BE chốt ở finalize choke-point) | :6-8 · :449-450 |
🔴 Delta owner nhìn thấy rõ nhất: khối "Ý kiến cấp duyệt" (Ảnh 3, khoanh đỏ) bên PE là Section 5 của panel 2, nhóm BƯỚC → PHÒNG, lưới 2 cột; bên KHKK nó là list phẳng nhét trong panel 3. Đây vừa sai panel, vừa sai hình.
§3. Bảng DELTA per-panel
Luật phán (hiến-pháp run): "hiện tại nó đang thế" KHÔNG phải lý-do ⇒ phán lại thành GIỐNG-được.
3.A Khung trang
| # | Hạng-mục | PE làm sao | KHKK làm sao | Phán | Lý-do |
|---|---|---|---|---|---|
| K1 | Chiều cao | flex h-[calc(100vh-4rem)] flex-col, không cuộn body (:381) |
space-y-5 giấy-cuộn (:161) |
GIỐNG-được | 3-panel bắt buộc phải chiếm-trọn-viewport, nếu không mỗi panel không có thanh cuộn riêng. GĐ3 đã làm y hệt (ContractsListPage.tsx:106) |
| K2 | Header | <header> 1 dòng: icon + h1 + badge đếm (:382-390) |
<PageHeader> eyebrow/subtitle/actions (:162-192) |
GIỐNG-được | Cùng chức-năng; GĐ1 và GĐ3 đều dùng header 1 dòng, chỉ GĐ2 lệch. Actions (nút "Lập kế hoạch") giữ ở góc phải header |
| K3 | Grid | [400px_1fr_360px] (:397) |
[19rem_minmax(0,1fr)] (:197) |
GIỐNG-được | Đề-bài trực-tiếp |
| K4 | Chọn phiếu | URL ?id= in-page, <lg mới đi overlay (:152-158) |
navigate('/khkk/:id') rời trang (:280) |
GIỐNG-được | GĐ3 giữ CẢ HAI: desktop ?id=, <lg navigate (ContractsListPage.tsx:90-96) — khuôn sẵn, chép được |
3.B Panel 1
| # | Hạng-mục | PE | KHKK | Phán | Lý-do |
|---|---|---|---|---|---|
| P1-1 | Nguồn cây | cây phiếu PE 3 tầng + folder GĐ (:472-637) |
PipelineTreePanel cây điều-hướng (:198) |
GIỐNG-được | Cùng bộ dựng cây Năm>Dự án>Hạng mục>4-folder. Chỉ cần đổ card phiếu KHKK vào content của folder GĐ2 y như PE đổ vào GĐ1 (§4 D-8) |
| P1-2 | Ô search | trong panel 1, icon-trong-ô (:401-409) |
card riêng giữa trang (:222-233) |
GIỐNG-được | Cùng chức-năng, khác chỗ đặt |
| P1-3 | Lọc trạng-thái | <Select> 1 dòng (:437-453) |
6 KpiCard hàng ngang (:202-220) |
GIỐNG-được | KpiCard đẹp nhưng ăn hết bề ngang — không sống nổi trong cột 400px. Owner đã nói "đồng nhất" ⇒ Select thắng |
| P1-4 | Lọc nhóm N1-N8 | không có (PE có awId dropdown quy-trình :413-422) |
đọc từ URL ?group= (:79-80), 8 leaf menu |
KHÁC-buộc-phải-khác | Nghiệp-vụ SP-002: 8 nhóm × 8 workflow KHKK-N{n} (types/khkk.ts:163), rào 409 ở BE (ContractSigningPlanFeatures.cs:420). PE không có trục này. 🔸 Nhưng CHỖ ĐẶT phải giống: dropdown trong khối filter panel 1, đúng ô mà PE đặt awId |
| P1-5 | Dòng phiếu | card 3 hàng + chip (:546-601) |
<tr> 8 cột (:277-337) |
GIỐNG-được | Bảng 8 cột không vừa cột 400px; card 3 hàng chứa đủ: mã · gói thầu · trạng thái · Σ tiền · người soạn · ngày |
| P1-6 | Phân trang | không (pageSize 50 :101) |
20/trang, nút Trước/Sau (:341-355) |
GIỐNG-được | Không phải "bỏ phân trang" — GĐ3 đã có phân trang compact NGAY TRONG panel 1 (ContractsListPage.tsx:200-222). Chép khuôn đó |
| P1-7 | Nút "Xem mở rộng" → overlay | có (:607-620 + :694-776) |
không có | GIỐNG-được | Ưu-tiên P2 (xem §7) — không chặn P1 |
| P1-8 | Persist mở/đóng cây | pe_list_expanded_projects_v3 (:269) |
pipeline_tree_expanded_v1 (PipelineTreePanel.tsx:21) |
GIỐNG-được | Đã có sẵn, chỉ cần giữ key riêng cho GĐ2 |
3.C Panel 2
| # | Hạng-mục | PE | KHKK | Phán | Lý-do |
|---|---|---|---|---|---|
| P2-1 | Dải 4 giai-đoạn đầu phiếu | <PePipelineStrip current={1} /> (PeDetailTabs.tsx:277) |
không có | GIỐNG-được | Component đã dùng-chung được (ComingSoonPage.tsx:33 truyền current động) ⇒ current={2} |
| P2-2 | Khung Section | divide-y divide-slate-200 + <Section> h3 uppercase tracking-wide đánh số (:397 · :514-523) |
4 SectionCard card-accent rời, không đánh số (:414 :505 :626 :721) |
GIỐNG-được | Đây đúng là "tương tự ⇒ giống nhau". Đổi SectionCard → Section đánh số + divide-y |
| P2-3 | Nội-dung các Section | 1 gói thầu · 2 hạng mục+báo giá NCC · 3 chọn NCC · 4 thông tin chọn thầu | Thông tin · Dòng giá per-NCC · Căn cứ hồ sơ b.8-9 (TVGS) · File đính kèm | KHÁC-buộc-phải-khác | Nghiệp-vụ GĐ2 ≠ GĐ1: KHKK không có báo-giá/so-sánh NCC (đã chốt ở GĐ1), có căn-cứ hồ-sơ TVGS (ContractSigningPlanDossierItem, types/khkk.ts:67-99) |
| P2-4 | Section CUỐI = Ý kiến cấp duyệt | Section 5, dưới cùng panel 2 (:426-437) |
khối nằm trong panel 3 (KhkkWorkflowPanel.tsx:376-412) |
GIỐNG-được 🔴 | Chính là ô owner khoanh đỏ Ảnh 3 — "content nhập phía trên, duyệt ghi chú phía dưới". Phải DỜI PANEL |
| P2-5 | Nhóm ý-kiến theo BƯỚC→PHÒNG + meta x/y cấp · N NV |
StepOpinionsBox (:705-745) |
list phẳng theo opinions[] |
GIỐNG-được | Chỉ cần workflowSteps (ĐÃ CÓ) + departmentName (§4 D-4) |
| P2-6 | Lưới 2 cột card ý-kiến | md:grid-cols-2 + card emerald + badge ✓ Đã duyệt + ⚠ … duyệt thay + placeholder (duyệt — không ý kiến) (:738-772) |
<li> 1 cột text-[11px] (đã có nhãn duyệt thay + placeholder) |
GIỐNG-được | Dữ-liệu đã đủ 100% (KhkkLevelOpinionDto types/khkk.ts:245-258 có approverUserId/signedByUserId/comment/signedAt) |
| P2-7 | Sửa nội-dung | panel 2 readOnly={true} cứng (:663); sửa ở workspace riêng (PurchaseEvaluationWorkspacePage) |
sửa inline ngay trong detail (nút Sửa/Lưu, KhkkDetailPage.tsx:422-427) |
KHÁC-buộc-phải-khác (có điều-kiện) | KHKK không có trang workspace. Ép readOnly mà chưa dựng workspace = cắt mất đường sửa duy nhất ⇒ mất chức-năng. 🔸 Điều-kiện: khi nào có workspace KHKK thì dòng này lật lại thành GIỐNG-được. Phải khai trong spec, đừng để thành di-sản im-lặng |
3.D Panel 3
| # | Hạng-mục | PE | KHKK | Phán | Lý-do |
|---|---|---|---|---|---|
| P3-1 | Meta workflow | {code} v{NN} · {name} (:348-353) |
{code} · {name} (:203-208) |
GIỐNG-được | Thiếu version (§4 D-5) |
| P3-2 | Banner kết-thúc-sớm | endsBeforeCeo 2 nhánh (:360-377) |
VẮNG | GIỐNG-được | Cơ-chế ĐÃ port ở K3 (cột EndedByLevelFinalize Mig 71), chỉ chưa expose (§4 D-1). Ảnh 2 có banner này ở vị-trí #2 |
| P3-3 | Sơ-đồ Bước→Cấp ✓/●/○ | BE precompute status |
FE tự suy (:106-120) |
ĐÃ GIỐNG (hình) | Khác ở tầng dữ-liệu, không khác ở tầng nhìn. Không ép BE precompute — rủi-ro > lợi |
| P3-4 | Chip tên phòng của Bước | có (:406-410) |
VẮNG | GIỐNG-được | §4 D-4 |
| P3-5 | Badge ⚑ Duyệt thay CEO per-Cấp |
có (:441-448) |
VẮNG | GIỐNG-được | 0 công BE — allowApproverFinalize đã nằm trên KhkkWorkflowLevelDto (types/khkk.ts:272) |
| P3-6 | Làm mờ Bước/Cấp sau finalize + chip Kết thúc tại … |
có (:393 :411-418 :425) |
VẮNG | GIỐNG-được | Suy được từ P3-5 + workflowSteps — 0 công BE |
| P3-7 | Badge ✎ NS PRO / ✎ NS CCM |
có (:458-463) |
VẮNG | KHÁC-buộc-phải-khác | Quyền nhập ngân sách PRO/CCM là khái-niệm riêng của PE (PeWorkItemBudgets, Mig 50). KHKK không có cột ngân sách per-role ⇒ badge sẽ vô nghĩa |
| P3-8 | 📎 File đính kèm khi duyệt |
có, tự-ẩn khi rỗng (:928-967) |
VẮNG | GIỐNG-được (có ngoặc) | Khối + hành-vi tự-ẩn = giống được ngay. 🔸 Nhưng CHO PHÉP người duyệt upload là quyết-định owner, không tự làm: cần thêm ApprovalAttachment vào enum purpose (§4 D-7). Khuyến-nghị: dựng khối, đọc purpose=Other, đợi owner chốt vế upload |
| P3-9 | Lịch sử duyệt (N) |
có (:969-971 → :3109-3219) |
VẮNG | GIỐNG-được | 🔴 Dữ-liệu ĐÃ ĐƯỢC GHI — ContractSigningPlanWorkflowService.cs:534. Chỉ chưa expose (§4 D-2) |
| P3-10 | Lịch sử thay đổi (changelog) |
có (:973-975 → :3222-3300) |
VẮNG | GIỐNG-được | 🔴 Changelog ĐÃ GHI (ContractSigningPlanWorkflowService.cs:547, và ≥8 chỗ trong Features.cs), chỉ thiếu endpoint đọc (§4 D-3) |
| P3-11 | 3 nút + dialog + ô ý-kiến | có | có (:299-374 :415-488) |
ĐÃ GIỐNG | Không đụng |
| P3-12 | 4 return-mode (Cấp / Bước / NV / Drafter) | có (:633-707) |
1 mode (về Bước 1·Cấp 1) (:290) |
KHÁC-buộc-phải-khác | Chuỗi KHKK là 3 trạm ngắn (PRO→CCM→CEO) — "lùi 1 cấp" gần trùng "về đầu". Thêm 4 mode = đổi state machine BE, ngoài phạm-vi UI. 🔸 Khai rõ, không giấu |
| P3-13 | Bộ chọn giá chốt khi duyệt | có (:792) |
không | KHÁC-buộc-phải-khác | KHKK chốt giá per-Line tại choke-point BE ApplyApprovedValuesOnFinalize (KhkkWorkflowPanel.tsx:449-450) — không có "1 giá" để người duyệt chọn |
| P3-14 | Nút XÓA phiếu trong panel duyệt | có, AllowApproverDelete Mig 68 (:564) |
có nhưng ở PageHeader (KhkkDetailPage.tsx:388-399) |
GIỐNG-được | Chỉ là dời vị-trí + dialog xác-nhận riêng |
| P3-15 | readOnly={!pendingMe} ẩn nút thao-tác ở view Danh sách |
có (:680) |
luôn hiện | GIỐNG-được | Cần khi panel 3 sống trong màn list |
| P3-16 | Empty-state panel 3 khi chưa chọn | hộp dashed + X + câu (:669-676) |
n/a | GIỐNG-được | Ảnh 1 có. 🔸 GĐ3 chọn cách khác: chưa chọn ⇒ nhét PipelineTreePanel vào panel 3 (ContractsListPage.tsx:249) — đây là lối thoát cho cây toàn-trình mà KHKK đang giữ ở panel 1 |
Tổng: 27 GIỐNG-được · 6 KHÁC-buộc-phải-khác · 2 ĐÃ GIỐNG (không việc).
§4. Data-gap — KHKK có đủ trường nuôi panel 3 + Section 5 không?
🔴 Kết-luận đầu tiên, quan trọng nhất: gần như toàn bộ dữ-liệu ĐÃ TỒN TẠI Ở DB VÀ ĐANG ĐƯỢC GHI. Khoảng trống nằm ở tầng đọc (DTO/endpoint), không phải tầng ghi. Đây là tin tốt: chi-phí BE nhỏ hơn nhiều so với ước-lượng "phải làm lại history".
| # | Thiếu cái gì | Trạng-thái thật ở BE | Ai cần | Việc phải làm |
|---|---|---|---|---|
| D-1 | Banner kết-thúc-sớm: endedByLevelFinalize + finalizeStepName + finalizeLevelName |
Cột EndedByLevelFinalize ĐÃ CÓ (Mig 71). ContractSigningPlanDetailDto (ContractSigningPlanFeatures.cs:145-181) KHÔNG expose — đã tự khai ở types/khkk.ts:299-300 ("cột Mig 71 nằm im chờ K3 tiêu thụ") |
P3-2 | +3 field vào DetailDto (append cuối, đúng convention :174-176) + 3 field vào KhkkDetailDto |
| D-2 | Lịch sử duyệt — danh-sách transition |
🔴 ĐANG GHI THẬT: ContractSigningPlanWorkflowService.cs:534 db.ContractSigningPlanApprovals.Add(new ContractSigningPlanApproval{FromPhase,ToPhase,Decision,Comment,ApprovalWorkflowLevelId,ApprovedByUserId,ApprovedAt}). Entity ContractSigningPlanApproval.cs:17-31. Bảng ContractSigningPlanApprovals + index (ContractSigningPlanId, ApprovedAt) (Mig 69 :247-248). DTO không có Approvals |
P3-9 | +List<ContractSigningPlanApprovalDto> Approvals vào DetailDto. Cẩn thận: entity chỉ có ApprovedByUserId, không có tên ⇒ projection phải join Users (BE đã có ResolveActorFullNameAsync, WorkflowService.cs:552) |
| D-3 | Lịch sử thay đổi — changelog |
🔴 ĐANG GHI THẬT ≥9 chỗ: WorkflowService.cs:547 + Features.cs:497,562,639,1078,1116,1224,1304. Có UserName đã denorm ⇒ không cần join. KHÔNG có endpoint đọc — ContractSigningPlansController.cs không có route changelogs nào |
P3-10 | +GET /contract-signing-plans/{id}/changelogs policy KeHoachKyKet.Read (mirror PE PeDetailTabs.tsx:3225) |
| D-4 | departmentName của Bước |
ContractSigningPlanWorkflowStepDto chỉ có DepartmentId — tự khai ở types/khkk.ts:275 ("KHÔNG có tên phòng") |
P2-5 và P3-4 (2 chỗ) | +DepartmentName vào StepDto (join Departments) |
| D-5 | workflowVersion |
DetailDto có WorkflowCode + WorkflowName, không có version (Features.cs:160-161) |
P3-1 | +1 field |
| D-6 | Tên người trong lịch-sử duyệt | (xem D-2) ApprovedByUserId không kèm tên |
P3-9 | Join hoặc denorm giống Changelog.UserName |
| D-7 | Purpose "file khi duyệt" | Enum ContractSigningPlanAttachmentPurpose chỉ có DossierScan=1 · PlanDocument=2 · Other=99 (ContractSigningPlanAttachment.cs:7-12) — không có giá-trị cho "file người duyệt tải lên" |
P3-8 | 🔸 Quyết-định owner: (a) dựng khối chỉ-đọc lọc purpose=Other — 0 công BE; (b) +ApprovalAttachment=3 + cho upload trong dialog duyệt — đổi enum + endpoint. Đừng tự chọn (b) |
| D-8 | stage2Content / stage2Count |
BuildStagesOptions (hooks/usePipelineStages.ts:163) chỉ có stage1Content/stage1Count; stage 2 dựng bằng leaves (link) :265. PipelineStageFolders đã hỗ-trợ content tổng-quát (PipelineStageFolders.tsx:88 + render :285-286) |
P1-1 | Mở rộng ĐỐI-XỨNG: stage2Content/stage2Count — sửa 1 hook, 0 đụng component trình-bày |
| D-9 | chưa đo được | Cách usePipelineStages nạp phiếu KHKK để dựng leaves stage-2 (:265-315) — tôi chưa đọc đoạn này. Nó quyết-định card KHKK trong folder GĐ2 lấy dữ-liệu từ đâu (query sẵn hay phải thêm) |
P1-1 | PHẢI đọc hooks/usePipelineStages.ts:150-320 trước khi code panel 1 |
Trả lời thẳng câu hỏi §4: workflow steps/levels ✅ đủ · ý-kiến per-cấp ✅ đủ (kể cả cờ "duyệt thay") · lịch-sử duyệt ⚠️ có ở DB, thiếu ở API · file-khi-duyệt ❌ chưa có khái-niệm.
§5. Kiến-trúc đề-xuất
Phương-án A — tạo components/khkk/ riêng, chép khuôn PE
- Làm:
mkdir components/khkk/→KhkkListPanel.tsx·KhkkDetailContent.tsx(+KhkkLevelOpinionsSection.tsx) · dờipages/khkk/KhkkWorkflowPanel.tsx→components/khkk/.KhkkListPagetrở thành host 3-panel. - Được: 0 rủi-ro cho PE (đang chạy prod, 45 phiếu) và GĐ3. Mỗi module giữ UX riêng — đúng nguyên-tắc đã ghi trong
CLAUDE.md("duplicate giữa 2 app là CÓ CHỦ ĐÍCH"). Diff dễ review. Mirror 2-app đơn-giản (copy nguyên file). - Mất: trùng-lặp thật. Sửa "cách hiển-thị ý-kiến cấp duyệt" sau này phải sửa ≥3 nơi (PE · KHKK · HĐ).
Phương-án B — tổng-quát-hoá component PE thành components/approval/ dùng chung
- Làm: trích
StepOpinionsBox/StepOpinionEntry(PeDetailTabs.tsx:705-774) và bộ sơ-đồ Bước→Cấp (PeWorkflowPanel.tsx:381-478) thành component generic nhậnsteps[] · opinions[] · finalizeInfo. - Được: 1 nguồn sự-thật cho 3 module (PE · KHKK · HĐ).
- Mất: 🔴 shape KHÁC NHAU THẬT. PE nhận
approvalFlowcóstatusdo BE precompute (PeWorkflowPanel.tsx:384); KHKK nhận cây thô, FE tự suy (KhkkWorkflowPanel.tsx:22-29,106-120). Generic hoá = phải hoặc (i) ép BE KHKK precompute, hoặc (ii) nhồi adapter 2 chiều. Cả hai đều đụng vào đường duyệt của PE đang chạy prod — rủi-ro không cân với lợi ích thẩm-mỹ. Thêm nữa PE có ≥6 nhánh riêng (NS PRO/CCM · price-picker · 4 return-mode · CCM-done · skip-to-final · DeptApprovals legacy) mà KHKK không có: generic sẽ đầyif (module === …).
🔴 CHỌN: Phương-án A, kèm 2 điều-kiện
- Điều-kiện 1 — lấy khuôn từ GĐ3, không phải từ PE.
ContractsListPage.tsx(255 dòng) đã là bản PE-3-panel đã được gọt gọn, làm ở S168, cùng chuỗi giai-đoạn, đã qua gate. Chép từ đó rẻ hơn và ít bẫy hơn chép từPurchaseEvaluationsListPage.tsx(807 dòng, mang theodeleted/pendingMe/awId/overlay/2 hệ<details>). - Điều-kiện 2 — nợ kỹ-thuật khai ngay, không giấu. Ghi vào đầu file
components/khkk/một dòng: "Khối ý-kiến cấp duyệt là bản thứ 3 của cùng một hình (PEPeDetailTabs.tsx:705-774· HĐWorkflowHistoryPanel.tsx). Gộp được khi và chỉ khi BE 3 module thống-nhất shapestatusprecompute." — biến trùng-lặp từ tai-nạn thành quyết-định có điều-kiện thoát.
Bố-cục đích (đề-xuất)
/khkk/list ← host 3-panel, khuôn ContractsListPage
├─ Panel 1 400px KhkkListPanel cây Năm>Dự án>Hạng mục>folder GĐ2 → card phiếu KHKK
│ + search + Select trạng-thái + Select nhóm N1-N8
├─ Panel 2 1fr KhkkDetailContent PePipelineStrip current={2}
│ Section 1 Thông tin kế hoạch
│ Section 2 Dòng giá per-NCC trúng thầu
│ Section 3 Căn cứ hồ sơ (b.8-9)
│ Section 4 File đính kèm
│ Section 5 Ý KIẾN CẤP DUYỆT ← Ảnh 3 khoanh đỏ
└─ Panel 3 360px KhkkWorkflowPanel chưa chọn → PipelineTreePanel currentStage={2}
(khuôn ContractsListPage.tsx:249)
đã chọn → 1 Quy trình duyệt + meta
2 Banner kết-thúc-sớm
3 Sơ-đồ Bước→Cấp (+chip phòng, +⚑, +mờ-sau-finalize)
4 Nút Duyệt/Trả lại/Từ chối + Dialog
5 📎 File đính kèm khi duyệt
6 Lịch sử duyệt (N)
7 Lịch sử thay đổi
/khkk/:id GIỮ NGUYÊN route (fallback <lg + deep-link + bookmark cũ) nhưng ruột đổi thành KhkkDetailContent + KhkkWorkflowPanel xếp dọc — đúng khuôn PurchaseEvaluationDetailPage (:798-805).
§6. Rủi-ro / bẫy
| # | Bẫy | Bằng-chứng | Phòng |
|---|---|---|---|
| R-1 🔴 | fe-admin KHÔNG hiển thị menu KHKK. ADMIN_HIDDEN_KEYS chứa 'KeHoachKyKet' (K6/QĐ7 S164) ⇒ toàn nhánh Khkk_* bị cắt khỏi sidebar admin. Cũng cắt PurchaseEvaluations và Contracts |
components/Layout.tsx:227-231 |
Ảnh owner gửi là fe-user, không phải fe-admin. ⇒ UAT phải nghiệm-thu trên fe-user:8080; mirror 2 app là BẮT BUỘC, không phải tuỳ chọn. Deep-link /khkk/list bên admin vẫn vào được (:212-216 — display-layer ≠ authz-layer, gotcha #82) |
| R-2 | gotcha #85 — dùng ĐÚNG key policy của endpoint. Mọi route KHKK gate KeHoachKyKet.{Read/Create/Update/Delete}; riêng bắc-cầu HĐ gate Contracts.Create |
ContractSigningPlansController.cs:29,37,87,96,118,223,244 · hooks/usePipelineStages.ts:11-17,57 |
Panel mới không OR key con (Khkk_List…). Nếu thêm endpoint changelogs (D-3) ⇒ [Authorize(Policy="KeHoachKyKet.Read")], không để [Authorize] trần |
| R-3 | Pattern 16-bis 4-place mirror. Menu-key mới/route mới phải có mặt ở: BE seed · Layout.tsx staticMap/regex · App.tsx route · resolvePath. Thiếu 1 ⇒ leaf DROP IM LẶNG (gotcha #50) |
Layout.tsx:37-45,181-188 · App.tsx:107-110 |
Việc này không đẻ menu-key mới (giữ /khkk/list) ⇒ rủi-ro thấp. Nhưng nếu ai thêm ?id= vào staticMap thì phải soát lại menu-highlight |
| R-4 🔴 | Chip lọc CỐ Ý không ghi URL. KhkkListPage.tsx:12-13,211-216 — ghi URL sẽ làm sidebar sáng nhầm leaf (bug UAT S155) |
:82-102 |
Panel 1 mới: q/phase ghi URL kiểu PE sẽ hồi-sinh bug S155. 🔸 Đề-xuất: ?id= ghi URL (cần bookmark) nhưng phase/group giữ ở lớp override (state), y như hiện tại. Đây là chỗ dễ làm hỏng nhất khi "chép khuôn PE cho giống" |
| R-5 | 8 nhóm dùng CHUNG 1 route ⇒ mọi thứ đọc từ URL phải đọc mỗi render, không useState khởi-tạo-một-lần (đi leaf→leaf không remount) |
:15-19,70-102 (vá-5 K4b) |
Host 3-panel mới phải giữ nguyên cơ-chế navKey/lastNavKey (:95-101), đừng viết lại bằng useEffect |
| R-6 | PipelineTreePanel currentStage + bodyMaxHClass. Trong host 3-panel (Panel-3 tự cuộn) phải truyền bodyMaxHClass="max-h-[28rem] lg:max-h-none" nếu không sẽ 2 thanh cuộn lồng nhau (~26px vượt) |
ContractsListPage.tsx:247-249 · PipelineTreePanel.tsx:93-97,221 |
Chép nguyên chuỗi đó. Nếu đặt cây ở panel 1 thì giữ mặc-định |
| R-7 | Mirror SHA256 2 app. 4 file KHKK + PipelineTreePanel + usePipelineStages đều khai "MIRROR SHA256 identical" |
KhkkListPage.tsx:3-4 · types/khkk.ts:2 · PipelineTreePanel.tsx:6 |
Acceptance §7 A-9: sha256 từng cặp, không "nhìn thấy giống" |
| R-8 | Không có phiếu V1-legacy ở KHKK. Module sinh 2026-07-29 (Mig 69), mọi phiếu đều V2 | types/khkk.ts:1 · KhkkWorkflowPanel.tsx:261-266 (chỉ fallback "chưa cấu hình Bước/Cấp") |
Không port nhánh fallback V1 của PE (PeDetailTabs.tsx:434-436, PeWorkflowPanel.tsx:481-485) — sẽ là code chết |
| R-9 | Route hiện có không được phá. /khkk/:id đang là đích của navigate ở list (:280), của nút "Danh sách" (KhkkDetailPage.tsx:400), và có thể của link ngoài |
App.tsx:110 |
Giữ route; chỉ đổi ruột. Thêm ?id= cho desktop, navigate cho <lg (khuôn ContractsListPage.tsx:90-96) |
| R-10 | budgetFrozen display-gate. Phiếu DaDuyet đọc ApprovedAmount, phiếu chưa duyệt đọc ProposedAmount |
types/khkk.ts:296-301 · Features.cs:177-181 |
Card panel 1 hiện Σ tiền phải theo đúng cờ này, nếu không sẽ nói 2 con số khác nhau cho cùng 1 phiếu (đúng lỗi vá-8 K2 đã sửa ở bảng — KhkkListPage.tsx:247-250) |
| R-11 | Cây tải tối đa 200 phiếu. PE_INDEX_PAGE_SIZE = 200, có dòng cảnh-báo khi chạm trần |
PipelineTreePanel.tsx:22,252-256 |
Nếu panel 1 dựng cây từ chỉ-mục PE thì phiếu KHKK của gói cũ hơn sẽ không xuất hiện. Phải giữ (và thấy) dòng cảnh-báo đó |
| R-12 | KhkkWorkflowPanel đang ở pages/, không phải components/ ⇒ import chéo pages→pages (KhkkDetailPage.tsx:41) |
KhkkDetailPage.tsx:41 |
Dời file = đổi import ở ≥2 nơi × 2 app. Việc nhỏ nhưng dễ sót ở app thứ hai |
§7. Checklist thực-thi ĐO ĐƯỢC
Mỗi mục có lệnh/grep kiểm được. Chạy tại repo-root. APP ∈ {fe-admin,fe-user} — mọi mục phải PASS cả hai.
Nhóm 0 — bắt buộc TRƯỚC khi viết dòng code đầu tiên
| # | Việc | Acceptance ĐO ĐƯỢC |
|---|---|---|
| A-0 | Đọc hooks/usePipelineStages.ts:150-320 để chốt nguồn dữ-liệu phiếu KHKK cho folder GĐ2 (§4 D-9) |
Ghi vào spec 1 dòng: query nào nuôi stage-2, có KhkkListItemDto sẵn hay phải thêm fetch. Không có dòng này ⇒ không được bắt đầu A-3 |
Nhóm 1 — BE (nhỏ, 1 lượt)
| # | Việc | Acceptance |
|---|---|---|
| A-1 | DetailDto +5 field: WorkflowVersion · EndedByLevelFinalize · FinalizeStepName · FinalizeLevelName · List<…ApprovalDto> Approvals; StepDto +DepartmentName |
grep -n "record ContractSigningPlanDetailDto" -A 70 src/Backend/SolutionErp.Application/ContractSigningPlans/ContractSigningPlanFeatures.cs → thấy đủ 5 tên; grep -c "DepartmentName" …WorkflowStepDto ≥1 |
| A-2 | GET /contract-signing-plans/{id}/changelogs policy KeHoachKyKet.Read |
grep -n "changelogs" src/Backend/SolutionErp.Api/Controllers/ContractSigningPlansController.cs → 1 hit, và dòng [Authorize(Policy = "KeHoachKyKet.Read")] đứng ngay trên (gotcha #85 / #82) |
| A-2b | Tên người trong Approvals không rỗng |
Gọi thật 1 phiếu đã duyệt: response approvals[].approvedByFullName ≠ null cho ≥1 phần tử. 🔴 Không nghiệm-thu bằng "code trông đúng" |
Nhóm 2 — FE cấu-trúc
| # | Việc | Acceptance |
|---|---|---|
| A-3 | $APP/src/components/khkk/ tồn-tại, chứa KhkkListPanel.tsx · KhkkDetailContent.tsx · KhkkWorkflowPanel.tsx (dời từ pages/) |
ls $APP/src/components/khkk/ → 3 file; grep -rn "pages/khkk/KhkkWorkflowPanel" $APP/src → 0 hit |
| A-4 | KhkkListPage là host 3-panel |
grep -n "lg:grid-cols-\[" $APP/src/pages/khkk/KhkkListPage.tsx → khớp ^.*lg:grid-cols-\[[0-9]+px_1fr_[0-9]+px\] ; và grep -c "h-\[calc(100vh-4rem)\]" = 1 |
| A-5 | Chọn phiếu = ?id= (desktop) / navigate (<lg), route /khkk/:id CÒN SỐNG |
grep -n "matchMedia('(min-width: 1024px)')" $APP/src/pages/khkk/KhkkListPage.tsx ≥1 · grep -n 'path="/khkk/:id"' $APP/src/App.tsx = 1 |
| A-6 | Bộ lọc trạng-thái/nhóm KHÔNG ghi vào URL (R-4) | grep -n "setSearchParams|setSp(" $APP/src/pages/khkk/KhkkListPage.tsx → chỉ xuất hiện trong hàm set id; 0 hit cho 'phase'/'group' bên trong lời gọi đó |
Nhóm 3 — FE nội-dung (từng khối của ảnh)
| # | Khối | Acceptance |
|---|---|---|
| A-7a | Panel 2 có PePipelineStrip current={2} |
grep -n "PePipelineStrip" $APP/src/components/khkk/KhkkDetailContent.tsx → 1 hit kèm current={2} |
| A-7b | Panel 2 có 5 Section đánh số, Section CUỐI = ý-kiến cấp duyệt | grep -oE 'title="[0-9]\. [^"]+"' $APP/src/components/khkk/KhkkDetailContent.tsx → 5 dòng, dòng cuối khớp ^title="5\. Ý kiến cấp duyệt |
| A-7c | Ý-kiến nhóm theo BƯỚC + lưới 2 cột + meta x/y cấp · N NV |
grep -n "md:grid-cols-2" …KhkkDetailContent.tsx ≥1 và grep -c "cấp đã duyệt" ≥1 và grep -c "NV tham gia" ≥1 |
| A-7d | Ý-kiến KHÔNG CÒN ở panel 3 | grep -c "Ý kiến cấp duyệt" $APP/src/components/khkk/KhkkWorkflowPanel.tsx = 0 |
| A-8a | Panel 3 có banner kết-thúc-sớm | grep -c "không qua CEO" …KhkkWorkflowPanel.tsx ≥1 |
| A-8b | Panel 3 có chip tên phòng + badge ⚑ Duyệt thay CEO + làm mờ sau finalize |
3 grep: departmentName ≥1 · Duyệt thay CEO ≥1 · opacity-50 ≥1 |
| A-8c | Panel 3 có 📎 File đính kèm khi duyệt |
grep -c "File đính kèm khi duyệt" ≥1 |
| A-8d | Panel 3 có Lịch sử duyệt (N) + Lịch sử thay đổi |
grep -c "Lịch sử duyệt" ≥1 và grep -c "Lịch sử thay đổi" ≥1 |
| A-8e | Panel 3 chưa chọn ⇒ PipelineTreePanel currentStage={2} bodyMaxHClass=… (R-6) |
grep -n "bodyMaxHClass" $APP/src/pages/khkk/KhkkListPage.tsx → 1 hit chứa lg:max-h-none |
Nhóm 4 — chống hồi-quy
| # | Việc | Acceptance |
|---|---|---|
| A-9 | Mirror 2 app byte-identical | Với mỗi file mới/sửa: sha256sum fe-admin/src/<p> = sha256sum fe-user/src/<p> → PASS 100%, không trừ file nào |
| A-10 | PE và HĐ không bị đụng | git diff --name-only → 0 đường-dẫn khớp components/pe/ · pages/pe/ · components/contracts/ · pages/contracts/. Nếu có ⇒ dừng, giải-trình |
| A-11 | Build + typecheck sạch | npm --prefix fe-admin run build và npm --prefix fe-user run build → exit 0. 🔴 Chỉ tin build chạy SAU khi mọi agent nền dừng (gotcha #68) |
| A-12 | Test BE không vỡ | dotnet test SolutionErp.slnx → 0 failed |
| A-13 | Quyền không bị nới im-lặng | grep -rn "Authorize" src/Backend/SolutionErp.Api/Controllers/ContractSigningPlansController.cs | grep -c "Policy" — số này phải ≥ số route, và 0 dòng [Authorize] trần (gotcha #82) |
| A-14 | Nghiệm-thu THẬT trên fe-user:8080 (R-1), không phải fe-admin | Chụp 3 màn khớp Ảnh 1/2/3; kiểm bằng cửa-sổ ẩn-danh (bài S96 #77: cache ≠ deploy) |
Nhóm 5 — chờ owner, KHÔNG tự làm
| # | Việc | Vì sao phải hỏi |
|---|---|---|
| A-15 | Cho phép người duyệt upload file khi duyệt (D-7, thêm ApprovalAttachment=3) |
Đổi enum + endpoint + quy-tắc lưu-trữ hồ-sơ. Ảnh 2 có khối này nhưng owner chưa nói KHKK cần upload |
| A-16 | Panel-2 chuyển sang readOnly cứng + dựng workspace KHKK riêng (P2-7) |
Làm nửa vời = cắt mất đường sửa duy nhất của phiếu Nháp |
| A-17 | Overlay "Xem mở rộng" cho KHKK (P1-7) | Chi-phí cao (focus-trap + 2×rAF + khoá scroll), owner chưa nêu. Đề-xuất P2 |
§8. Ba câu tôi muốn lead đọc kỹ nhất
- Không phải "sửa CSS", mà là "đưa GĐ2 về đúng lớp kiến-trúc mà GĐ1 và GĐ3 đang đứng". GĐ2 là mắt-xích lệch duy nhất trong 4 giai-đoạn (§0 Đ-2). Nói với owner theo cách này thì phạm-vi công-việc tự giải-thích.
- Dữ-liệu lịch-sử duyệt của KHKK ĐÃ ĐƯỢC GHI TỪ ĐẦU (
ContractSigningPlanWorkflowService.cs:534+:547) — chỉ chưa có cửa đọc. Đừng ước-lượng việc này như "phải xây lại history"; nó là ~5 field DTO + 1 endpoint. Đây là chỗ dễ ước-lượng sai đắt nhất theo cả hai chiều. - Chỗ dễ làm hỏng nhất khi "chép PE cho giống" là R-4 (ghi bộ lọc vào URL làm sidebar sáng nhầm — bug S155 đã đốt một lần) và R-1 (nghiệm-thu nhầm app). Cả hai đều không nhìn thấy được trong ảnh owner gửi.
END investigator-codebase-invest-s171 — VERDICT=27 giống-được / 6 buộc-khác / 8 data-gap