Files
solution-erp/.claude/workflows/runs/2026-08-05-S175-duan-budget-column/spec-hang-muc-nhieu-dong-va-cot-du-an-05-08-2026.md
pqhuy1987 b811e1125d
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 6m32s
[CLAUDE] Contract: KHKK them N hang muc (gia+ghi chu) + gop muc 3-4 file dinh kem (0-mig)
Nguon: /fable-real investigator-codebase (wf_a574cff1-6b9, 1 lane, 169K tok).
Spec 3-muc: runs/2026-08-05-S175-duan-budget-column/spec-hang-muc-nhieu-dong-va-cot-du-an-05-08-2026.md
Anh chot: "Chi them hang muc -> nhieu hang muc -> gia va ghi chu, vay thoi" + "file 3+4 ->
Hop thanh file dinh kem nhu cua Duyet NCC nhe".

=== 1. THEM N HANG MUC / NCC (0 MIGRATION) ===
🔴 Cau hoi TIEN da co dap an: KHONG co luat chia — NGUOI NHAP TAY tung dong.
Truoc do lead dung lai vi hoi "tach N hang muc thi chia tien NCC the nao"; anh tra loi khong
theo tien, nhap tay. Het chan.

BE — 0 migration (unique 3 cot Plan x Supplier x CatalogEntry co tu Mig 71; may DA rong,
lan nay chi bung UI — dung khuon "may rong, UI hep" anh chot S164):
· UpdateContractSigningPlanLineCommand +ProposedAmount? +Note?, CatalogEntryId -> NULLABLE.
  Null-safe khuon UpdateDraftCommand:559-563 => call-site FE cu (gui dung 1 field) KHONG phai
  sua dong nao.
· AddContractSigningPlanLineCommand (MOI) + route POST {id}/lines.
  🔴 SupplierId PHAI thuoc phieu — NCC den tu winner phieu Duyet NCC; de NCC moi o day = ky
    voi nha thau chua ai chon. Khong thuoc => 409.
  🔴 CatalogEntryId BAT BUOC: unique index CO Y khong loc "IS NOT NULL"
    (ContractSigningPlanLineConfiguration.cs:35-38) => them dong NULL se dung dong goc.
  🔴 PeReferenceAmount = 0, KHONG copy so tham chieu cua NCC. Tham chieu la tong bao gia DUOC
    CHON cua CA NCC, da nam tron o dong goc (Features.cs:512-515). Lap lai o dong them tay =
    DEM DOI tien tham chieu du an. No cung la DAU phan biet dong-goc/dong-them-tay.
· DeleteContractSigningPlanLineCommand (MOI) + route DELETE {id}/lines/{lineId}.
  🔴 CHI xoa duoc dong THEM TAY (PeReferenceAmount == 0). Dong goc mang snapshot tham chieu —
    xoa la mat so goc + mat dau vet NCC tung trung thau.
· Bat TRUNG cap (Plan, Supplier, CatalogEntry) TRUOC SaveChanges => 409 doc duoc, thay vi 500
  unique-violation. Ap cho CA duong them LAN duong doi hang muc (NCC nay co N dong => doi
  trung hang muc dong khac cung NCC la co that).
· Rao ContractId nay che CA TIEN, khong chi hang muc: ProposedAmount la so di vao HD.
· Changelog ghi cap CU->MOI cho tien ("gia de xuat 1.000 -> 2.000 d") — day la tien, nguoi
  duyet phai truy duoc ai doi tu bao nhieu sang bao nhieu, khong chi biet "co sua".
· 3 route deu policy KeHoachKyKet.Update (gotcha #85 — dung key cua endpoint; "tao" o module
  nay nghia la tao PHIEU nen POST line van la Update).

FE: cot GIA DE XUAT + GHI CHU nhap duoc inline (chi Nhap/Tra lai, chi dong chua co HD), luu
onBlur va CHI khi so doi that. Nut "+ Them hang muc" liet ke dung NCC da co tren phieu. Dong
them tay hien "—" o cot tham chieu (khong phai "0 d" — de khong doc nham la tham chieu bang 0).

=== 2. GOP MUC 3 + 4 -> 1 KHOI "FILE DINH KEM" ===
Khuon dong file be tu PE (PeDetailTabs.tsx:3382-3418): 1 danh sach PHANG, moi dong co badge
cho biet file thuoc can cu nao (hoac "Ho so phieu" neu o muc phieu), + eye xem truoc + download.
🔴 Bang can cu 5 cot GO khoi man nhung MAY GIU NGUYEN: entity KhkkDossierItem + 3 endpoint +
changelog con nguyen; kind/status/TVGS/ghi-chu van sua qua nut ✎ (mo dung dialog cu, du 5
truong) va van hien duoi dang badge. Go luon entity de "cho gon giao dien" = mat du-lieu BE
dang ghi va nguoi duyet dang doc.
Can cu DA KHAI ma chua co file van hien 1 dong (dashed) — khong thi khai xong no bien mat va
nguoi dung tuong bam hut.

=== 3. ENGINE BAC 2 GIA-THUYET CUA LEAD (ghi lai vi la SAI CUA LEAD) ===
(a) Lead: "cot DU AN la DI-CHUNG module Budget cu bi Mig 50 rut ruot" — SAI. Cot sinh o
    e33481e 19-06 (Mig 56), SAU Mig 50 (13-06) 6 ngay, va CUNG COMMIT voi comment y-dinh
    PeWorkItemBudget.cs:19 "DU AN: hien thi FE-only (chua wire BE — sau moi co NGUOI DU AN
    NHAP)" => CHO-CHUA-SAN co chu dich, khong phai xac chet.
(b) Lead do "≥3 cho hardcode —" — THIEU. That = 11 o/app (Block A 3 + Block B 8), mirror 2 app.
(c) 🔑 PHAT HIEN: Project.BudgetTotal DA TON TAI tu Mig 2 (day-1), wired tron 2 dau (BE CRUD
    ProjectFeatures.cs + FE cot "Ngan sach" ProjectsPage.tsx:117 + form :205). Chi chua noi vao
    ma tran PE, va DU-LIEU RONG: Dev 61/69 du an NULL (8 cai co so deu la demo-seed gated-OFF
    prod). Rat co the CHINH LA thu anh noi "ben muc ngan sach co muc du an chua dien vao" —
    lop "co-che dung, du-lieu khong co" lan thu 7.

=== CON NO ===
🔴 Cot DU AN (11 o) CHUA LAND — cho anh chot 3 cau trong spec §③: (1) Sigma KHKK do vao O NAO
trong 11 o; (2) phieu DaDuyet thi cot DU AN live hay dong bang; (3) sua thang ProposedAmount
(delta tu-audit = Proposed − PeReference) du chua hay can field "dieu chinh" rieng.
🔴 CHUA soi mat prod 375/1366/1440 (5 lane NO-SCREENSHOT, gotcha #3).
🔸 Test-before cho lop luy-ke la BAT BUOC khi land cot DU AN (financial-critical, rules §7) —
  lan nay chua cham luy-ke nen chua no test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 20:54:15 +07:00

87 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SPEC — Thêm N hạng mục (giá + ghi chú) & cột DỰ ÁN trong ma trận ngân sách (05-08-2026)
> Nguồn: `/fable-real investigator-codebase` single deep-pass (`wf_a574cff1-6b9`, 1 lane, 169K tok, 38 tool-use, ~12).
> Engine **propose-only**; file này **LEAD ghi SAU khi verify** (H21 honest-note (c)).
---
## ① TÍNH-NĂNG / ĐỀ-BÀI
**Anh chốt (verbatim, tin nhắn cuối):** *"Chỉ thêm hạng mục → nhiều hạng mục → giá và ghi chú, vậy thôi"*
(kèm ảnh khoanh đỏ mục **2. Giá đề xuất theo NCC trúng thầu**, mũi tên chỉ cột **HẠNG MỤC**).
**Phạm vi lượt này = A.** Mục B ghi lại để không rơi, **chưa land**.
**A. Dòng hạng mục (LÀM NGAY):** 1 NCC được **N dòng hạng mục**, mỗi dòng **nhập tay giá + ghi chú**.
🔑 Câu này **mở khoá bế tắc trước đó**: lead từng dừng vì hỏi *"tách N hạng mục thì CHIA tiền NCC thế nào"*.
Câu trả lời là **không có luật chia — người nhập tay từng dòng**. Hết chặn.
**B. Cột DỰ ÁN (CHƯA LÀM — chờ anh chốt 3 câu ở §③):** 11 ô `—` trong ma trận ngân sách của phiếu Duyệt NCC,
nuôi bằng Σ các dòng hạng mục bên KHKK.
### 🔴 3 đính-chính engine bắt được — lead ghi lại vì 2 cái đầu là SAI CỦA LEAD
| # | Lead nói | Sự thật đo được | Nguồn |
|---|---|---|---|
| a | *"cột DỰ ÁN là DI-CHỨNG module Budget cũ bị Mig 50 rút ruột"* | **SAI.** Cột sinh ở `e33481e` **19-06** (S76/Mig 56) — **SAU** Mig 50 (13-06) **6 ngày**, và **cùng commit** với comment ý-định *"DỰ ÁN: hiển thị FE-only (chưa wire BE — sau mới có NGƯỜI DỰ ÁN NHẬP)"***CHỖ-CHỪA-SẴN có chủ đích**, không phải xác chết | `PeWorkItemBudget.cs:19` + git `e33481e` |
| b | *"cột DỰ ÁN hardcode `—` ở ≥3 chỗ"* | **THIẾU.** Thật = **11 ô/app**: Block A `PeDetailTabs.tsx:1415·1426·1447` + Block B `:1535·1589·1599·1619·1629·1639·1664·1677`; fe-user mirror y hệt | pattern-count 19/19 cả 2 app |
| c | *(lead không biết)* | 🔑 **Tầng NS cấp dự-án ĐÃ CÓ MỘT NỬA**: `Project.BudgetTotal` sinh từ **Mig 2 — day-1**, wired TRỌN 2 đầu (BE CRUD `ProjectFeatures.cs:18·82·107·129·154` + FE cột **"Ngân sách"** `ProjectsPage.tsx:117` + form `:205`). Chỉ chưa nối vào ma trận PE, và **DỮ-LIỆU RỖNG: 61/69 dự án NULL**, 8 cái có số đều là demo-seed gated-OFF-prod | `Domain/Master/Project.cs:12` · `DbInitializer.cs:3212-3219` |
🔴 **(c) rất có thể CHÍNH LÀ thứ anh nói** — *"bên mục ngân sách có mục dự án chưa điền vào"*.
Ô đó **có thật, nhập được, và đang trống** ở 61/69 dự án. Đây là lớp **"cơ-chế đúng, dữ-liệu không có"** lần thứ 7.
🔸 Kết-luận hệ-quả của lead (*"phải dựng một tầng, không phải vá 1 cột FE"*) **vẫn đứng** — chỉ tiền-đề đổ.
---
## ② CÁCH IMPLEMENT
### A — Dòng hạng mục N-dòng + giá + ghi chú (lượt này)
**BE — 0 migration** (unique `(PlanId, SupplierId, CatalogEntryId)` lọc `IsDeleted=0` đã có từ Mig 71):
| # | Việc | Ghi chú |
|---|---|---|
| A1 | `UpdateContractSigningPlanLineCommand` +`ProposedAmount?` +`Note?`, đổi `CatalogEntryId`**nullable** | Null-safe khuôn `UpdateDraftCommand:559-563`: `null` = không gửi ⇒ giữ nguyên. Nullable ⇒ **call-site FE cũ không phải sửa dòng nào** |
| A2 | `AddContractSigningPlanLineCommand(PlanId, SupplierId, CatalogEntryId, ProposedAmount, Note)` | 🔴 `SupplierId` **PHẢI ∈ NCC đã có trên phiếu** — NCC đến từ winner của PE, không được đẻ NCC mới ở đây. 🔴 `CatalogEntryId` **BẮT BUỘC**: unique index **cố ý không lọc NULL** ⇒ thêm dòng NULL sẽ đụng dòng gốc |
| A3 | `DeleteContractSigningPlanLineCommand(PlanId, LineId)` | 🔴 **CHỈ xoá được dòng THÊM TAY** (`PeReferenceAmount == 0`). Dòng gốc sinh từ PE mang snapshot tham chiếu — xoá là mất số gốc |
| A4 | 3 route: `POST {id}/lines` · `DELETE {id}/lines/{lineId}` · mở rộng body `PUT {id}/lines/{lineId}` | policy `KeHoachKyKet.Update` (gotcha #85: đúng key endpoint) |
🔴 **`PeReferenceAmount` của dòng THÊM TAY = 0, KHÔNG copy số NCC.**
Tham chiếu là Σ báo giá được chọn của **cả NCC**, đã nằm trọn ở dòng gốc; lặp lại là **đếm đôi tiền dự án**.
**FE (mirror 2 app):** mục 2 thêm nút `+ Thêm hạng mục` theo NCC · ô nhập **giá đề xuất** + **ghi chú** inline
(chỉ phase Nháp/Trả lại) · nút xoá cho dòng thêm tay.
### B — Cột DỰ ÁN (CHƯA LÀM)
Phương án engine chốt = **live-derive, 0 migration, 0 endpoint mới**: JOIN `Lines → Plan WHERE Plan.ProjectId = pe.ProjectId`,
2 tầng mirror S134 (CHÍNH-XÁC = Σ `ApprovedAmount` plan `DaDuyet` ⟂ TẠM-TÍNH = Σ `ProposedAmount` plan đang mở).
Đã BÁC: ledger-table riêng (+1 mig, số derive được lại đi lưu) · cột `DuAn*` trên `PeWorkItemBudgets`
(**sai trục khoá** — bảng per-`WorkItemId` mà chứa số per-`Project`; và `CatalogEntryId` SP-002 **không map được** `WorkItemId`).
🔴 **Rào tiền phải xử khi land B:** nhánh **frozen** `PurchaseEvaluationFeatures.cs:946-959` dựng DTO **bằng tay**
quên field mới thì phiếu `DaDuyet` hiện DỰ ÁN = 0 **câm lặng** (đúng lớp *"vắng mặt trông giống ổn"*).
---
## ③ CHECKLIST — acceptance ĐO ĐƯỢC
| # | Deliverable | Acceptance |
|---|---|---|
| 1 | A1 land | `grep -c 'ProposedAmount' ContractSigningPlanFeatures.cs` tăng; call-site FE cũ (`catalogEntryId` đơn lẻ) vẫn build |
| 2 | A2 rào NCC | POST với `supplierId` KHÔNG thuộc phiếu → **409**, message nêu rõ |
| 3 | A2 rào trùng | POST trùng cặp (Plan, Supplier, CatalogEntry) → **409** chứ không phải 500 unique-violation |
| 4 | A3 rào dòng gốc | DELETE dòng có `PeReferenceAmount != 0`**409** |
| 5 | Tiền không đếm đôi | Dòng thêm tay có `PeReferenceAmount == 0` — verify bằng đọc DB/DTO sau khi tạo |
| 6 | Mirror | `sha256sum` từng cặp file FE 2 app — **khớp 100%** |
| 7 | Build | `npm run build` ×2 app + `dotnet build` — 0 error. 🔴 Đo **NỘI DUNG** bundle, đừng tin thời-gian build |
| 8 | Deploy | bundle prod 2 vế: chuỗi mới **có** ∧ chuỗi cũ **vắng**, kèm control-dương. Dùng `grep -o \| wc -l`, KHÔNG `grep -c` (bundle 1 mega-dòng) |
| 9 | 🔴 Mắt anh | Soi thật **375 / 1366 / 1440** trên prod — mọi kết luận giao diện tới giờ là ĐỌC MÃ |
### 🔴 3 câu treo — cần anh chốt TRƯỚC khi land mục B
1. **Σ KHKK đổ vào Ô NÀO** trong 11 ô? (engine đề xuất: hàng **5 lũy kế** Block B, 2 tầng chính-xác + tạm-tính khuôn amber S134; còn hàng **NS full** Block A đọc `Project.BudgetTotal`)
2. **Phiếu đã duyệt**: cột DỰ ÁN **live** hay **đóng băng** theo snapshot?
3. Sửa thẳng `ProposedAmount` (delta tự-audit = `ProposedAmount PeReferenceAmount`) là đủ, hay cần field **"điều chỉnh"** riêng?