wal: flush 20260729T2046
This commit is contained in:
@ -16,7 +16,8 @@ chain:
|
||||
- [x] commit `a8027c0` (30 file +8.987/−22, F-0 gate ĐẠT) + PUSH origin/main
|
||||
- [x] **W1 ĐÓNG — cicd VERDICT=PASS 6/6** (Run #427 5m40s · 566 · Mig 69 prod 96 bảng · menu 142 GIỮ · marker 3-tầng · đo-CỜ 13/13/13/2 + control-âm Khkk_G1 + log `0 added + 91 upgraded` khớp dự-đoán · label type-10 VN sống). Synthesis `runs/2026-07-29-S161-khkk-w1-schema/w1-synthesis.md`. Tồn: setup WF tay trước W5 · UAT non-Admin (nv.test KHÔNG tồn tại prod) · 51-bis GẤP (cicd _INDEX 99,2%/167B) · F-4
|
||||
- [x] gate 5-anchor W2 PASS 5/5 (A4 line-drift content-found; A5 KeHoachKyKet∈All:180 THẬT; phân xử lệch: mã sinh @CREATE per spec, entity-comment W1 sai → worker sửa)
|
||||
- [!] wf: hmw W2 đợt-1 `wf_6f0aad26-229` ĐANG CHẠY — 2 lane {implementer-backend: mega-file ContractSigningPlanFeatures CHỈ-CRUD (cấm copy phần duyệt, bẫy flatten :427-439) + Controller authz-2-tầng ≥4 policy · implementer-frontend: pages/khkk 3-page ×2 app + 4-place re-point 6 leaf khỏi coming-soon + SHA-pair} — run-folder `runs/2026-07-29-S161-khkk-w2-crud/` (taskList 6 stage)
|
||||
- [x] wf W2 đợt-1 `wf_6f0aad26-229`: 2/2 lane chết #53 NHƯNG sub-md cứu trọn (BE 16.5KB — 15 endpoint policy 15/15 + 4 quyết D1-D4 lead duyệt cả 4 · FE 4 file SHA-pair) → lead solo đuôi FE 4-place (WfView 2-app-2-đường + whitelist matrix +10) → **build BE 0W/0E + FE ×2 PASS + coming-soon 0/0**; harvest 2 diary xong
|
||||
- [!] wf: W2 đợt-2 `wf_7490179a-70b` test-specialist ĐANG CHẠY (2 test PIN + 2-4 test acceptance, sàn ≥568) → về thì: lead suite + reviewer → commit/push → cicd
|
||||
- [ ] đợt-2 SAU khi wf về: test-specialist + database-agent (song song) → lead acceptance §③-B (build·test·sqlcmd 96·Down sạch·đo CỜ) → reviewer → commit/push → cicd-monitor 3-chân-kiềng
|
||||
- [ ] setup thủ công TRƯỚC W5 (SAU W1 vì type=10 cần mig 69): dựng workflow type=3 + type=10 3-trạm qua Designer (seed chỉ 1 trạm)
|
||||
- [ ] housekeeping @pause/closeout kế: nén MIND-1 5.447B (trần `_mind` 83,2% — MIND-5 chắc vượt) · re-carry 2 ý D rơi (vá-GỐC-Phase-3.5-refresh {mới-nêu} CÒN SỐNG + khuôn-5-đợt→sổ bền {gần-chốt}) · STATUS:6 bổ arc dry-run S160 (mù có lý do — land 15:27 trước pipeline) · hạ nhãn DR/WF-3-trạm đã-định-đoạt · E đếm 7 sub-file (không 6) · mở lại slot-45 trần `_mind` (tiền-đề lật: file SỐNG 83,2%) · quyết bookkeeping OVERDUE:light (cặp H24 chạy @S159 bookend mà `last_audit` đứng 27)
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
# Implementer-Backend Agent — Persistent Memory
|
||||
|
||||
- **S161-W2 (07-29) KHKK CRUD mega-file + Controller `[hmw wf_6f0aad26-229 · chết #53 SAU §7 (trước §8 build-log) NHƯNG sub-md 16.5KB ghi-từng-mục TRỌN + build lead 0W/0E 2s incremental = lane đã build xanh; entry on-behalf]`:** `ContractSigningPlanFeatures.cs` (Create 4-rào-fail-sớm + auto-Lines snapshot + UpdateDraft null-safe 3/3 #73 + Get/List/inbox/deleted + Dossier + Attachments-mọi-phase + Delete allow-list M6 + Picker) + `ContractSigningPlansController` 15 action **policy 15/15 kể cả Read tường minh** (#82). 4 QUYẾT khai-để-lead-phân-xử (lead duyệt cả 4): D1 `PeId` giữ nguyên spec (hợp-đồng 2-lane, đổi = gãy body JSON) · D2 `DrafterUserId=currentUser` KHÔNG copy pe.Drafter (Guid?→non-null = Guid.Empty hỏng quyền) · D3 IDOR mình-soạn ∪ có-chân-duyệt (khuôn Proposal 0-rào là khuôn HỞ — bài 8b S155; nới-phòng = 1 vế 1 chỗ) · D4 ProposedAmount mồi=PeReferenceAmount (W2 0 command sửa line). Bẫy né được ghi lại: **(1) codegen gọi TRƯỚC `Add(plan)`** — generator SaveChanges nội bộ trong tx riêng, Add trước = flush phiếu dở (S88 self-committing); **(2) SUM quote so `w.Id` (row PeSupplier) KHÔNG `w.SupplierId`** (nhầm = cộng tiền NCC khác); **(3) `Remove(plan)` TRƯỚC `Add(changelog)`** — CascadeDeleteTiming.Immediate detach entity Added (bug T26 S155); **(4) subquery `Any` lồng 2 tầng trong Where vỡ dịch SQL CHỈ LÚC CHẠY** → dùng hình dạng join :185-189; **(5) tự mở lại 5 anchor + grep-cùng-lớp ra 3 site comment sai khi lead giao 1** (S122). Tag `[s161-w2, khkk-crud, 4-quyet-khai-lead-phan-xu, codegen-truoc-add, wid-vs-supplierid, remove-truoc-changelog]`
|
||||
|
||||
- **S161 (07-29) W1 KHKK schema Mig 69 `[hmw wf_7d6fb81b-640 + wf_f8c806d2-5a3 · CẢ 2 lượt chết #53 (×2 cùng task) NHƯNG lượt-2 sub-md 6.2KB ghi-TỪNG-MỤC-trong-lúc-làm cứu TRỌN narrative + code đĩa sống 100%; lead verify build 0W/0E + solo đuôi; entry on-behalf]`:** Module `Domain/ContractSigningPlans/` 7 entity + 3 enum (Phase 1/2/3/98/99 · DossierItemKind/Status trong DossierItem.cs, start=1) + `ApplicableType.ContractSigningPlan=10` append-only + 7 Configuration + DbSets ×2 file + **Mig 69 `20260729122015_AddContractSigningPlans`** (7 CreateTable · 0 ALTER · Down 7 DropTable · UNIQUE filtered `(PlanId,SupplierId) WHERE IsDeleted=0` #57 · MaKeHoach unique filter NOT-NULL · FK 6-con Cascade 1-hop + Plan→AWF Restrict — **0 multiple-cascade-path by design**: Attachment.DossierItemId chỉ HasIndex KHÔNG FK) · apply 2 DB **0 drift** (khác S133) · sys.tables 89→96 · MenuKeys.All+=KeHoachKyKet:179. Lead solo đuôi: seeder `SeedKeHoachKyKetAccessAsync` (khuôn S118 + MỞ RỘNG vế CanDelete Drafter/Admin, UPGRADE-if-exists F-S1, call SAU revoke) + `ContractSigningPlanCodeGenerator` (KHKK/{YYYY}/{Seq:D3}, SERIALIZABLE, `WorkflowAppCodeSequences` dùng chung ns Domain.Office) + DI + 3 site AWV2Admin (count/retained/orphan — danh sách kết ở VehicleBooking, ItTicket KHÔNG có LevelOpinions). Learned: (1) **ghi-sub-md-TỪNG-MỤC = thứ duy nhất sống khi return chết** — lượt-1 dồn cuối mất trắng narrative, lượt-2 ghi từng mục giữ đủ grep-proof; (2) audit-first §0 bảng đo trước khi làm tiếp = relaunch-cắt-gọt chuẩn; (3) Changelog/Approval = BaseEntity KHÔNG HasQueryFilter (parity PE) — schema-test phải assert theo THỰC TẾ entity không đoán. Tag `[s161, w1-khkk-mig69, sub-md-tung-muc-cuu-narrative, 53-x2-cung-task, cascade-1-hop, upgrade-if-exists-candelete]`
|
||||
|
||||
- **S153 (07-26) arc ctx-adopt T1 + T3 (2 lượt hmw đợt-1) `[lead seed on-behalf @closeout M9; T1 return mất schema-fail → derive-from-body; T3 return CLEAN]`:** **T1 máy `mind-check`** — EXTEND `scripts/session_ctx.py` (+528 dòng, ADD-only: `_TS_KEY_RE` module-level canonical + subcommand + parser + dispatch; 2 subcommand cũ BẤT ĐỘNG). 7 phép + 2 phụ · 4 mức {đạt·trượt·cờ·bỏ-qua-có-khai} · draft/closed/`--root`/`--session` PIN · exit {0,1,3}. **9 quyết-định thiết-kế có lý-do đo được**, đắt nhất: **D1 config đọc LAZY** — chỉ đọc `memory-budget.json` khi CÓ tệp để đo, vì 2 dòng acceptance chỉ đồng-thời đúng theo thứ tự đó ("không có gì để đo thì không đọc trần; có thứ để đo thì TỪ CHỐI đoán trần") · D3 marker VẮNG ⇒ quét TRỌN + CỜ (hướng hỏng NGHIÊM: tệp thiếu marker = tệp sai khuôn, phải lộ) · D4 rào-3 in pattern-name + `file:line` **KHÔNG in snippet** (gate chống rò không được tự rò ra log) · D6 `_ascii_safe()` backslashreplace (console cp1252 gặp diacritic ⇒ `UnicodeEncodeError` = máy chết GIỮA gate).
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
# Implementer-Frontend Agent — Persistent Memory
|
||||
|
||||
- **S161-W2 (07-29) KHKK 3 page ×2 app `[hmw wf_6f0aad26-229 · chết #53 sau MUC-1+cp (4-place dở) → lead solo đuôi {Layout ×2 re-point + fe-admin App routes + matrix whitelist +10} · build ×2 PASS + SHA-pair 4/4 + coming-soon 0/0]`:** `types/khkk.ts` + 3 page `pages/khkk/` viết fe-user trước rồi cp (SHA-pair 4/4). Types theo ENTITY (BE lane song song chưa land Application lúc recon — luật đề bài "ưu tiên shape entity" ăn); header types khai HỢP-ĐỒNG FE↔BE 12 endpoint để BE đối chiếu 1 chỗ. Chip lọc List đổi STATE không ghi URL (bug UAT S155 menu-sáng-nhầm). Create: dropdown workflow đọc `{types:[...]}` KHÔNG phải flat array (khuôn ProposalCreatePage:59-69). Detail: Lines READ-ONLY (W2 không endpoint sửa line — KHÔNG bịa UI). **WfView 2 app 2 ĐƯỜNG (lead chốt @đuôi):** fe-user → alias route `/khkk/workflow-matrix?type=10` tái dùng `WorkflowMatrixViewPage` (whitelist :56 nay `1|2|3|10`) · fe-admin → Designer deep-link `/system/approval-workflows-v2/ContractSigningPlan` (admin CỐ Ý không có matrix — chốt 07-27; "Luồng duyệt" admin = dựng quy trình). menuKeys.ts place-3 SKIP-có-khai: pages 0 usage MenuKeys/usePermission (khớp khuôn Proposal) — entry-không-consumer = drift. Root `KeHoachKyKet` staticMap fe-user /dashboard→`/khkk/list`. Tag `[s161-w2, khkk-3-page, types-theo-entity, wfview-2-app-2-duong, alias-route-khong-de-page, place3-skip-khong-consumer]`
|
||||
|
||||
- **S161 (07-29) W1 KHKK Designer type-10 fe-admin `[hmw wf_7d6fb81b-640 lane-2 · return CLEAN · lead verify: build tươi PASS + disk khớp từng site; diagnostics IDE giữa-agent = stale (#68) đĩa thắng]`:** 1 file `ApprovalWorkflowsV2Page.tsx` (+67/−9): `TYPE_INT_CONTRACT_SIGNING_PLAN=10` (:147) → `TYPE_CODE_TO_INT:162` (deep-link `/ContractSigningPlan`, route sẵn `App.tsx:76`) + `DEFAULT_CODE_BY_TYPE:170` `'QT-KHKK-V2-001'` (suy diễn khuôn — spec KHÔNG chốt, đổi 1 dòng) + rào 2 checkbox early-finalize **2 LỚP**: render (`disabled` + `checked` ép false + tooltip dùng chung `KHKK_EARLY_FINALIZE_NOTE` :1284-1330, className ternary chuỗi ĐẦY ĐỦ Pattern-14) **VÀ** payload (`levelFlagsPayload(e, lockEarlyFinalize)` :247 ép false :254-255 — 2/2 call-site PUT :783 + POST :815) — chỉ disable ô = containment GIẢ vì `copyFromDefinition()` vẫn tuồn `true` vào state→BE. Cân 409: PUT echo-so-BẰNG (`AWV2AdminFeatures:580-585`) KHÔNG nổ vì type-10 mọi bản đều POST-ép-false + cloneFrom cùng-type. **2 tiền-đề đề bài SAI, lane tự bẻ (mirror bài S160):** (i) FE KHÔNG có bảng nhãn ApplicableType — nhãn từ BE `applicableTypeLabel`; (ii) khuôn "entry type=9" không tồn tại (`TYPE_CODE_TO_INT` dừng ở 3) → mirror `Contract:3`. **Cờ đỏ báo lead:** type-10 (và cả type-3 xưa nay) KHÔNG có đường sidebar — allowlist `Layout.tsx:162-168` chỉ có 2 key `AwV2_` PE; admin vào bằng URL trực tiếp; BE seed leaf mới thì PHẢI nới `Layout.tsx:165` cùng lượt. Tag `[s161, w1-designer-type10, two-layer-containment-render-payload, fe-khong-label-map, tien-de-de-bai-sai-tu-be, stale-diag-68]`
|
||||
|
||||
- **S153 (07-26) arc ctx-adopt T2 + T5 (2 lượt hmw) `[lead seed on-behalf @closeout M9; T2 return mất schema-fail (#53 +1) → derive-from-body, template VẪN LAND 8.819B; T5 return CLEAN]`:** **T2 template** `.claude/templates/session-mind-template.md` — 3 marker (RULES-START/END · MIND-TOP) · **0 dòng key `ts:`** (ts sống trong HEADING sau em-dash ⇒ không phạm rào-2) · 6 hit `rào-` (3 rào × tên+lý-do) · chiều chèn **NGƯỢC** `_context` khai TO trong khối luật · placeholder `<k>` trong schema-comment chống parser false-match (mirror trick template `_context:32`) · kiểm `_SECRET_RES` để ví-dụ rào-3 không tự khớp mẫu (secrets-sweep rglob TRỌN session-dir, KHÔNG biết enclosure).
|
||||
|
||||
@ -8,9 +8,9 @@
|
||||
|
||||
| # | Stage | Vai / cách | Trạng thái |
|
||||
|---|---|---|---|
|
||||
| 1 | Đợt-1a BE: mega-file `ContractSigningPlanFeatures.cs` (Create+auto-Lines+MaKeHoach@CREATE · UpdateDraft · Get/List/pendingMe+inbox-precompute · Dossier UPSERT/DELETE · Attachments · DELETE allow-list · picker PE-chờ-lập-KH) + `ContractSigningPlansController` authz 2-tầng per-action | hmw `implementer-backend` | [ ] |
|
||||
| 2 | Đợt-1b FE: `pages/khkk/` 3 page + types ×2 app + 4-place mirror (routes + staticMap re-point 6 leaf khỏi coming-soon + menuKeys) + build ×2 + SHA-pair | hmw `implementer-frontend` | [ ] |
|
||||
| 3 | Đợt-2: test-specialist (≥2 test PIN: `ContractSigningPlansController_EveryWriteEndpoint_HasAuthorizePolicy` + `CreateContractSigningPlan_FromNonApprovedPe_Throws`) | hmw `test-specialist` | [ ] |
|
||||
| 1 | Đợt-1a BE: mega-file `ContractSigningPlanFeatures.cs` (Create+auto-Lines+MaKeHoach@CREATE · UpdateDraft · Get/List/pendingMe+inbox-precompute · Dossier UPSERT/DELETE · Attachments · DELETE allow-list · picker PE-chờ-lập-KH) + `ContractSigningPlansController` authz 2-tầng per-action | hmw `implementer-backend` | [x] `wf_6f0aad26-229` lane-0 chết #53 sau §7 NHƯNG sub-md **16.5KB ghi-từng-mục TRỌN** (tự mở 5 anchor + grep-cùng-lớp 3 site comment + 15 endpoint policy 15/15 + **4 quyết D1-D4 khai-lead-phân-xử → lead DUYỆT cả 4**) · build lead 0W/0E |
|
||||
| 2 | Đợt-1b FE: `pages/khkk/` 3 page + types ×2 app + 4-place mirror (routes + staticMap re-point 6 leaf khỏi coming-soon + menuKeys) + build ×2 + SHA-pair | hmw `implementer-frontend` | [x] lane-1 chết #53 sau MUC-1+cp (SHA-pair 4/4) → **lead solo đuôi**: Layout ×2 re-point 6 leaf (WfView 2 app 2 đường: user=alias matrix?type=10 whitelist+10 · admin=Designer deep-link) + fe-admin App routes + root KeHoachKyKet→/khkk/list; menuKeys place-3 SKIP-có-khai (0 consumer). Build ×2 PASS + coming-soon **0/0** |
|
||||
| 3 | Đợt-2: test-specialist (≥2 test PIN: `ContractSigningPlansController_EveryWriteEndpoint_HasAuthorizePolicy` + `CreateContractSigningPlan_FromNonApprovedPe_Throws`) | hmw `test-specialist` | [!] `wf_7490179a-70b` ĐANG CHẠY nền |
|
||||
| 4 | Lead: build ×2 + suite + acceptance §③-B sweep | lead | [ ] |
|
||||
| 5 | Reviewer adversarial (CRUD endpoints POST/PUT/DELETE — bắt buộc) | `reviewer` | [ ] |
|
||||
| 6 | Commit + push + cicd (3-chân-kiềng + 2-bearer NẾU có tài khoản non-Admin — nv.test KHÔNG tồn tại prod, xem w1-synthesis tồn-2) | lead + `cicd-monitor` | [ ] |
|
||||
|
||||
@ -0,0 +1,174 @@
|
||||
# sub-implementer-backend-0 — W2 KHKK BE (CRUD nháp + căn cứ b.8-9)
|
||||
|
||||
> Run: `2026-07-29-S161-khkk-w2-crud` · vai: implementer-backend · lượt 0
|
||||
> Luật: ghi TỪNG MỤC ngay sau khi làm xong mục đó (W1: 5/7 lượt chết #53 — file này là thứ duy nhất sống).
|
||||
|
||||
---
|
||||
|
||||
## §0 — AUDIT TRƯỚC KHI VIẾT (đọc đĩa, không đoán)
|
||||
|
||||
Mọi số dưới đây đo từ đĩa lúc bắt đầu lượt (HEAD `77c723b`, W1 đã land ở `a8027c0`).
|
||||
|
||||
### 0.1 — 5 anchor spec §③-A: TỰ MỞ LẠI (không tin gate của lead)
|
||||
|
||||
| # | Anchor | Đo được | Ghi chú |
|
||||
|---|---|---|---|
|
||||
| A1 | `Application/Office/ProposalFeatures.cs:427-439` | ✔ SỐNG — `allLevels = wf.Steps.OrderBy(s=>s.Order).SelectMany(s=>s.Levels...)` rồi `ElementAtOrDefault(CurrentApprovalLevelOrder-1)` | 🔴 BẪY: flatten toàn workflow ⇒ mọi Level thành 1 chuỗi tuần tự = OR-of-N biến thành AND. ĐỌC ĐỂ NÉ. W2 KHÔNG copy (phần duyệt = W3) |
|
||||
| A2 | `PurchaseEvaluations/CreateContractFromEvaluationFeatures.cs:53-62` | ✔ guard `pe.Phase != DaDuyet → ConflictException` (`:53-54`) + `winners = pe.Suppliers.Where(s => s.IsWinner)` (`:56`) + `winners.Count == 0 → Conflict` (`:57-58`) | phép tính tiền ở `:88-90` |
|
||||
| A3 | `ProposalFeatures.cs:258-267` | ✔ guard workflow-type TRỌN 2 nhánh: `wfType is null → NotFoundException` (`:264-265`) + `wfType != ProposalGeneral → ConflictException` (`:266-267`) | copy CẢ nhánh NotFound |
|
||||
| A4 | `MenuKeys.cs:42` const + `:180` vào `All` | ✔ `KeHoachKyKet = "KeHoachKyKet"` (`:42`), có mặt trong `All` (`:180`) | ⇒ 4 policy `KeHoachKyKet.{Read,Create,Update,Delete}` sinh runtime (Program.cs nhân `All × Actions`) |
|
||||
| A5 | `ApprovalWorkflow.cs` enum | ✔ `ContractSigningPlan = 10` (append-only, đúng slot) | dùng cho guard type |
|
||||
|
||||
### 0.2 — Nền W1 đang có trên đĩa (7 entity + 7 config + DbSet + codegen)
|
||||
|
||||
- Entity: `Domain/ContractSigningPlans/{ContractSigningPlan, ContractSigningPlanLine, ...DossierItem, ...Attachment, ...LevelOpinion, ...Changelog, ...Approval}.cs` + `ContractSigningPlanPhase.cs`.
|
||||
- Phase enum: `DangSoanThao=1 · ChoDuyet=2 · DaDuyet=3 · TraLai=98 · TuChoi=99` (`ContractSigningPlanPhase.cs:8-15`).
|
||||
- DbSet ×7: `IApplicationDbContext.cs:149-155`.
|
||||
- Codegen: `Application/ContractSigningPlans/Services/IContractSigningPlanCodeGenerator.cs` — `Task<string> GenerateAsync(CancellationToken)`; DI đã đăng ký `Infrastructure/DependencyInjection.cs:42`.
|
||||
|
||||
### 0.3 — MaxLength THẬT (EF = source of truth, KHÔNG trust spec — bài S35)
|
||||
|
||||
Đo từ Configurations (không đọc spec):
|
||||
|
||||
| Field | EF config | file:line |
|
||||
|---|---|---|
|
||||
| `Plan.MaKeHoach` | 50 | `ContractSigningPlanConfiguration.cs:19` |
|
||||
| `Plan.HoSoLink` | 1000 | `:21` |
|
||||
| `Plan.GhiChu` | 2000 | `:22` |
|
||||
| `Line.Note` | 1000 | `ContractSigningPlanLineConfiguration.cs:20` |
|
||||
| `DossierItem.Name` | 500 **required** | `ContractSigningPlanDossierItemConfiguration.cs:18` |
|
||||
| `DossierItem.TvgsName` | 200 | `:19` |
|
||||
| `DossierItem.Note` | 1000 | `:20` |
|
||||
| `Attachment.FileName` | 500 | `ContractSigningPlanAttachmentConfiguration.cs:24` |
|
||||
| `Attachment.StoragePath` | 1000 | `:25` |
|
||||
| `Attachment.ContentType` | 200 | `:26` |
|
||||
| `Attachment.Note` | 1000 | `:28` |
|
||||
|
||||
### 0.4 — HasQueryFilter: KHKK CÓ (khác HRM), trừ 2 bảng audit
|
||||
|
||||
`grep -c HasQueryFilter` trên 7 config KHKK: có ở `ContractSigningPlan/Line/DossierItem/Attachment` (`:42/:33/:30/:38`).
|
||||
`Changelog` + `Approval` = `BaseEntity` (không `IsDeleted`) ⇒ không có filter. `LevelOpinion` = `AuditableEntity` nhưng **CỐ Ý KHÔNG filter** (`ContractSigningPlanLevelOpinion.cs:18-20` — site dọn chữ ký mồ côi phải thấy row soft-deleted).
|
||||
|
||||
⇒ Hệ quả cho W2: list/detail **KHÔNG cần** `.Where(!IsDeleted)` thủ công (filter tự chạy); ngược lại màn "đã xóa" BẮT BUỘC `IgnoreQueryFilters().Where(x => x.IsDeleted)` — luật `ContractFeatures.cs:356-360` (Ignore GỠ filter chứ không ĐẢO).
|
||||
|
||||
### 0.5 — LỆCH SPEC vs COMMENT W1 (lead đã phân xử: SPEC THẮNG) + 2 site cùng-lớp lead CHƯA nêu
|
||||
|
||||
Lead giao sửa 1 chỗ. Grep cùng-lớp ra **3** chỗ khai cùng một điều sai ("gen lúc Submit"):
|
||||
|
||||
| # | Site | Nguyên văn | Lead giao? |
|
||||
|---|---|---|---|
|
||||
| 1 | `Domain/ContractSigningPlans/ContractSigningPlan.cs:17` | `// "KHKK/2026/001" — gen atomic lúc Submit (W2)` | ✔ CÓ |
|
||||
| 2 | `Application/ContractSigningPlans/Services/IContractSigningPlanCodeGenerator.cs:9` | `// Caller: W2 Submit (gen lúc trình duyệt, KHÔNG lúc tạo nháp — mirror quyết spec cũ)` | ✗ KHÔNG |
|
||||
| 3 | `Infrastructure/.../ContractSigningPlanConfiguration.cs:24` | `// Mã phiếu UNIQUE khi đã gen (gen lúc Submit ⇒ nháp còn null)` | ✗ KHÔNG |
|
||||
|
||||
Xử: sửa cả 3 (comment-only, 0 thay đổi hành vi) — theo bài `feedback_root_cause_over_symptom` "được chỉ 1 chỗ ⇒ PHẢI grep cùng-lớp". Index filtered `[MaKeHoach] IS NOT NULL` GIỮ NGUYÊN (vẫn đúng: gen-lúc-create thì cột luôn NOT NULL, filter chỉ rộng hơn cần thiết chứ không sai).
|
||||
|
||||
**ĐÃ LÀM** — 3 Edit comment-only:
|
||||
- `ContractSigningPlan.cs:17-22` (5 dòng comment mới + prop giữ nguyên chữ ký `public string? MaKeHoach`)
|
||||
- `IContractSigningPlanCodeGenerator.cs:9-11`
|
||||
- `ContractSigningPlanConfiguration.cs:24-27`
|
||||
|
||||
---
|
||||
|
||||
## §1 — CREATE (`CreateContractSigningPlanCommand`)
|
||||
|
||||
File: `src/Backend/SolutionErp.Application/ContractSigningPlans/ContractSigningPlanFeatures.cs` (Region 1).
|
||||
|
||||
**Chữ ký:** `CreateContractSigningPlanCommand(Guid PeId, Guid ApprovalWorkflowId, string? GhiChu = null) : IRequest<CreateContractSigningPlanResult>`
|
||||
→ `CreateContractSigningPlanResult(Guid Id, string MaKeHoach, int LineCount)`.
|
||||
|
||||
🔴 Tên `PeId` (KHÔNG `PurchaseEvaluationId` theo convention repo) là CỐ Ý: spec §②-2 viết đúng chữ đó, lane FE dựng body JSON từ CÙNG bản spec ⇒ đổi tên = gãy tích hợp 2 lane (`camelCase` body sẽ là `peId`). Đã ghi lý do vào `<remarks>` ngay trên record.
|
||||
|
||||
**4 rào theo đúng thứ tự** (fail sớm, rào rẻ trước):
|
||||
1. Phase PE `!= DaDuyet` → `ConflictException` — mirror `CreateContractFromEvaluationFeatures.cs:53-54`.
|
||||
2. `winners = pe.Suppliers.Where(s => s.IsWinner)`, rỗng → Conflict — mirror `:56-58`.
|
||||
3. Đã có kế hoạch SỐNG (`AnyAsync(p.PurchaseEvaluationId == pe.Id && p.Phase != TuChoi)`) → Conflict 409. `TuChoi` không tính ⇒ lập lại được; phiếu xoá mềm cũng không tính (global filter tự loại) ⇒ acceptance "DELETE → tạo lại → 201" đi qua rào này.
|
||||
4. Workflow type — copy TRỌN 2 nhánh Proposal `:264-267` vào helper `ContractSigningPlanScope.EnsureWorkflowTypeAsync` (dùng lại ở Update). NotFound giữ nguyên là `NotFoundException`, sai loại là `ConflictException`.
|
||||
|
||||
**Codegen:** `await codeGen.GenerateAsync(ct)` gọi **TRƯỚC** `db.ContractSigningPlans.Add(plan)`.
|
||||
Lý do đo được: `ContractSigningPlanCodeGenerator.cs:23-43` mở transaction SERIALIZABLE **rồi gọi `db.SaveChangesAsync` nội bộ** ⇒ nếu phiếu đã nằm trong change-tracker, lượt lưu đó flush phiếu DỞ (chưa có mã) trong transaction của codegen (bài S88 self-committing codegen). Add + SaveChanges đúng 1 lần ở cuối.
|
||||
|
||||
**Lines auto-sinh:** 1 dòng / winner, `PeReferenceAmount = SUM(Quote.ThanhTien WHERE IsSelected && q.PurchaseEvaluationSupplierId == w.Id)` — copy `:88-90`.
|
||||
🔴 So với `w.Id` (Id của ROW `PurchaseEvaluationSuppliers`), KHÔNG phải `w.SupplierId` (Id master) — nhầm 2 cái này là cộng nhầm tiền của NCC khác. Snapshot 1 lần, không đọc live (cùng triết lý freeze NS Mig 67).
|
||||
|
||||
**Denorm:** `ProjectId`/`DepartmentId` ← PE. `DrafterUserId` ← **currentUser** (KHÔNG phải `pe.DrafterUserId`) — xem §6 DECISION-2.
|
||||
|
||||
**Changelog:** 1 entry `Header/Insert/PhaseAtChange=DangSoanThao` + `ContextNote = "Nguồn: phiếu Duyệt NCC {MaPhieu} ({Id})"`.
|
||||
|
||||
## §2 — UPDATE nháp (`UpdateContractSigningPlanDraftCommand`)
|
||||
|
||||
`(Guid Id, string? GhiChu = null, string? HoSoLink = null, Guid? ApprovalWorkflowId = null)`.
|
||||
- Guard qua `ContractSigningPlanScope.EnsureDraftEditable(plan, currentUser)`: (a) owner ∥ Admin → `ForbiddenException`; (b) phase ∈ {DangSoanThao, TraLai} → `ConflictException`.
|
||||
- **Null-safe 3/3 field** (#73/S42): `null` = client không gửi ⇒ GIỮ; `""` = chủ động xoá ⇒ set null (khuôn `HoSoLink` `PurchaseEvaluationFeatures.cs:285-286`). Không field nào absolute-set.
|
||||
- Re-pin workflow: **guard type LẠI** (admin có thể đổi loại quy trình giữa chừng) — cùng helper §1 rào 4.
|
||||
|
||||
## §3 — GET detail / LIST / INBOX / ĐÃ XOÁ
|
||||
|
||||
**`GetContractSigningPlanQuery`** — Include `Lines + DossierItems + Attachments + LevelOpinions` (khuôn Proposal `:159-162`) + tra thêm: PE (MaPhieu/TenGoiThau), Project (Name/Code), Department, Drafter, **tên NCC 1 lượt** (`Suppliers` dictionary dùng chung Lines + DossierItems), **workflow tree** (Steps→Levels + tên người duyệt) và metadata Cấp cho `LevelOpinions` (StepOrder/StepName/LevelOrder/ApproverUserId) — mirror `ProposalFeatures.cs:182-217` nhưng KHÔNG dùng `SelectMany` flatten (chỉ dựng `Dictionary<levelId, meta>`).
|
||||
|
||||
**`ListContractSigningPlansQuery(Phase?, ProjectId?, PurchaseEvaluationId?, PendingMe) : PagedRequest`**
|
||||
- Không cần `.Where(!IsDeleted)` thủ công — 4 bảng KHKK CÓ global filter (§0.4).
|
||||
- `PendingMe=true` → giao với `ResolveV2InboxIdsAsync` (non-admin) hoặc `AdminInboxPhases` (admin).
|
||||
- **IDOR scope** non-admin — xem §6 DECISION-3.
|
||||
|
||||
**`GetMyContractSigningPlanInboxQuery`** — admin: phase-list TƯỜNG MINH `AdminInboxPhases = [ChoDuyet]` (mirror PE `:731-742`, nháp KHÔNG vào inbox theo S89); non-admin: `ResolveV2InboxIdsAsync` mirror `PurchaseEvaluationFeatures.cs:817-852` — in-memory join vì `CurrentWorkflowStepIndex` là INDEX 0-based vào list Steps đã sort, không dịch thẳng SQL được; so Cấp bằng GIÁ TRỊ `Level.Order` + `Any(...)` ⇒ **giữ OR-of-N**. W2 luôn trả rỗng (chưa có phiếu ChoDuyet vì transition = W3) — wire sẵn, W3 không phải sửa list/inbox.
|
||||
|
||||
**`ListDeletedContractSigningPlansQuery`** — `IgnoreQueryFilters().Where(x => x.IsDeleted)` theo LUẬT `ContractFeatures.cs:356-360` (Ignore **GỠ** filter chứ không ĐẢO) + cùng vị ngữ IDOR như list sống.
|
||||
|
||||
Projection dùng chung 3 query qua `ListContractSigningPlansQueryHandler.ProjectPagedAsync` (1 hình dạng DTO, không lệch).
|
||||
|
||||
## §4 — Dossier items (căn cứ b.8-9) + Attachments
|
||||
|
||||
- `UpsertContractSigningPlanDossierItemCommand(PlanId, Id?, Kind, SupplierId?, Name, Status, TvgsName?, TvgsResultAt?, Note?)` → `Guid`. `Id == null` ⇒ Insert, có ⇒ Update (verify item thuộc ĐÚNG phiếu). Guard = `EnsureDraftEditable` ⇒ **chỉ {DangSoanThao, TraLai}**.
|
||||
- `DeleteContractSigningPlanDossierItemCommand` — `Remove()` = XOÁ MỀM (`AuditingInterceptor.cs:54-63` đổi Deleted→Modified + set IsDeleted/DeletedAt/DeletedBy) + changelog.
|
||||
- **Attachments MỞ MỌI PHASE** (triết lý PE S147): người duyệt phải bổ sung được scan kết quả TVGS ngay lúc đang duyệt ⇒ 3 handler upload/download/delete KHÔNG gọi `EnsureDraftEditable`. Chỉ CĂN CỨ mới khoá theo phase.
|
||||
- Upload: `IFileStorage` + path `contract-signing-plans/{planId}/{attId}_{safeName}` + sanitize (mirror PE `:115-123`). `DossierItemId` phải thuộc đúng phiếu — **handler tự kiểm** vì cột đó CỐ Ý không có FK vật lý (tránh multiple cascade path, `ContractSigningPlanAttachment.cs:14-19`) ⇒ DB không kiểm hộ.
|
||||
- Validator MaxLength lấy từ EF KHKK (FileName **500** / ContentType 200 / Note 1000) — KHÔNG copy số của PE (255/100/500); PE hẹp hơn vì là bản cũ.
|
||||
|
||||
## §5 — DELETE phiếu + PICKER
|
||||
|
||||
**`DeleteContractSigningPlanCommand(Id, Reason?)`** — owner ∥ Admin; allow-list **{DangSoanThao, TuChoi}** (M6: thiếu đường này thì lỡ tạo nhầm 1 kế hoạch là PE khoá 409 vĩnh viễn).
|
||||
🔴 Thứ tự `db.ContractSigningPlans.Remove(plan)` **TRƯỚC** `Add(changelog)` — bug T26 S155: EF chạy cascade client-side NGAY tại `Remove()` (`CascadeDeleteTiming.Immediate`), changelog đang ở state `Added` sẽ bị DETACH ⇒ SaveChanges ghi 0 row, IM LẶNG. (Ở dossier-item/attachment thì changelog trỏ PLAN chứ không trỏ thứ bị Remove nên không dính, vẫn giữ cùng thứ tự cho nhất quán.)
|
||||
|
||||
**`ListApprovedPeAwaitingPlanQuery`** — mirror `CreateContractFromEvaluationFeatures.cs:162-174`, đổi vị ngữ: bỏ `e.ContractId == null` (không liên quan GĐ2), thêm `có ≥1 IsWinner` + `chưa có plan sống` ⇒ picker khớp ĐÚNG 2 rào của Create, không mời user bấm vào thứ chắc chắn 409.
|
||||
⚠️ `WinnerQuoteTotal` viết lại sau lần build đầu: bỏ `Any(...)` lồng trong `Where` của subquery (sâu 2 tầng, vỡ dịch SQL chỉ nổ LÚC CHẠY chứ không nổ lúc build) → dùng hình dạng subquery-có-join đã chạy được ở `:185-189`.
|
||||
|
||||
## §6 — 4 QUYẾT ĐỊNH lệch/ngoài chữ của lệnh (khai để lead phân xử)
|
||||
|
||||
| # | Chỗ | Lệnh/spec nói | Tôi làm | Vì sao |
|
||||
|---|---|---|---|---|
|
||||
| D1 | tên tham số Create | spec + lệnh viết `PeId` | `PeId` (trái convention repo `PurchaseEvaluationId`) | spec = hợp đồng chung 2 lane; FE đọc CÙNG spec ⇒ đổi tên là gãy body JSON |
|
||||
| D2 | `DrafterUserId` | lệnh gộp "DepartmentId/DrafterUserId/ProjectId denorm từ PE" | `= currentUser.UserId` (chỉ Project+Dept mới denorm) | (a) entity khai "BCH soạn kế hoạch" `ContractSigningPlan.cs:24` — người soạn PE (QS) ≠ người lập kế hoạch (BCH); (b) khuôn Proposal `:276` + PE `:147` đều lấy currentUser; (c) `pe.DrafterUserId` là `Guid?` còn cột này non-null ⇒ copy sang cho phiếu PE cũ thiếu drafter sẽ ra `Guid.Empty` = hỏng cả quyền sửa lẫn scope list |
|
||||
| D3 | IDOR scope list | lệnh không nói | non-admin thấy: phiếu MÌNH soạn ∪ phiếu đã rời nháp thuộc quy trình mình có chân duyệt | bài 8b S155 "handler MỚI mặc định KHÔNG có rào nào"; khuôn Proposal (0 rào) là khuôn HỞ. Bỏ vế eligible-phase-theo-role vì module này KHÔNG có bản đồ role→phase. **Nới cho cả phòng = thêm 1 vế `DepartmentId` ở đúng 1 chỗ** (comment đã chỉ chỗ), áp cả list sống lẫn màn xoá |
|
||||
| D4 | `ProposedAmount` lúc tạo | không nói | mồi `= PeReferenceAmount` | W2 KHÔNG có command sửa dòng giá (spec §②-2 không liệt kê) ⇒ để 0 là cả phiếu 0 và không ai sửa được. Mồi = giá đã duyệt ở PE = trường hợp thường gặp |
|
||||
|
||||
**Cố ý KHÔNG làm (ngoài spec, để W3):** command sửa Line (ProposedAmount/Note) · mọi transition (submit/approve/reject/return) · `SlaDeadline` để null (cột là display-only, không job — `ContractSigningPlan.cs:34-37`) · xoá phiếu đang duyệt (cần rào đúng-lượt như `PeSoftDeleteFeatures`).
|
||||
|
||||
## §7 — Endpoint + authz (14 action, per-action policy 14/14)
|
||||
|
||||
Route gốc `api/contract-signing-plans`, class `[Authorize(Policy = "KeHoachKyKet.Read")]`.
|
||||
|
||||
| Verb | Path | Policy |
|
||||
|---|---|---|
|
||||
| GET | `/` | `KeHoachKyKet.Read` |
|
||||
| GET | `/inbox` | Read |
|
||||
| GET | `/deleted` | Read |
|
||||
| GET | `/approved-pe-awaiting-plan` | Read |
|
||||
| GET | `/{id}` | Read |
|
||||
| POST | `/` | **Create** |
|
||||
| PUT | `/{id}` | **Update** |
|
||||
| DELETE | `/{id}` | **Delete** |
|
||||
| POST | `/{id}/dossier-items` | **Update** |
|
||||
| PUT | `/{id}/dossier-items/{itemId}` | **Update** |
|
||||
| DELETE | `/{id}/dossier-items/{itemId}` | **Update** |
|
||||
| POST | `/{id}/attachments` | **Update** |
|
||||
| GET | `/{id}/attachments/{attId}/download` | Read |
|
||||
| GET | `/{id}/attachments/{attId}/view` | Read |
|
||||
| DELETE | `/{id}/attachments/{attId}` | **Update** |
|
||||
|
||||
(15 dòng bảng = 15 action; xem số đo grep ở §8.)
|
||||
Ghi Read tường minh cả trên action chỉ-đọc: `[Authorize]` trần / để trống = lỗ hổng (gotcha #82 — ẩn menu KHÔNG đóng API). Dossier-item + attachment dùng **Update** chứ không Create: "tạo" ở module này = tạo PHIẾU.
|
||||
Endpoint dossier-item verify `id == cmd.ContractSigningPlanId` → `BadRequest` (Pattern 12-ter).
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
# sub-implementer-frontend-1 — W2 KHKK FE 3 page x 2 app (S161)
|
||||
|
||||
> Lane: FE-only. KHONG cham src/Backend (chi Read entity de suy DTO shape). KHONG commit.
|
||||
> Ghi TUNG MUC ngay sau khi lam xong muc do (chong #53).
|
||||
|
||||
## MUC 0 — Recon (DONE)
|
||||
|
||||
Khuon nguon doc that (khong doan):
|
||||
- `fe-user/src/pages/office/ProposalsListPage.tsx` 252 dong / `ProposalCreatePage.tsx` 245 / `ProposalDetailPage.tsx` 386 / `fe-user/src/types/proposal.ts` 95.
|
||||
SHA256 = IDENTICAL voi fe-admin counterpart cho ca 4 file (do bang `sha256sum`):
|
||||
- Create `fb852a8df3191aa8696468ee6c762c21710f662db146ab678933c57a7373464b`
|
||||
- Detail `a550a5da29729d0d44981627bf8a6934255ac02c80477545f8c4eff393e83f81`
|
||||
- List `780060dc1c4a9f08387e7ad35737846e38aff92191ad9fe2a7e8f2fa303f5af6`
|
||||
- types `95607052ff1138f21d266d7fe66d45bab180c97270030f939be1ffcb681fdeda`
|
||||
- Entity BE W1 (`src/Backend/SolutionErp.Domain/ContractSigningPlans/*.cs`, 8 file) = nguon shape cho types:
|
||||
`ContractSigningPlan` (MaKeHoach, PurchaseEvaluationId, ProjectId, DepartmentId, DrafterUserId, Phase,
|
||||
ApprovalWorkflowId, CurrentWorkflowStepIndex, CurrentApprovalLevelOrder, SlaDeadline, HoSoLink, GhiChu)
|
||||
· `Line` (SupplierId, PeReferenceAmount, ProposedAmount, ApprovedAmount?, ContractId?, Note?)
|
||||
· `DossierItem` (Kind 1/2/99, SupplierId?, Name, Status 1..4, TvgsName?, TvgsResultAt?, Note?)
|
||||
· `Attachment` (FileName, StoragePath, FileSize, ContentType, Purpose 1/2/99, Note?, DossierItemId?)
|
||||
· `Phase` enum 1/2/3/98/99.
|
||||
- BE lane CHUA land file Application/Controller luc toi recon (`ls src/Backend/SolutionErp.Application/ContractSigningPlans/` = chi co `Services/` cua W1)
|
||||
=> types FE viet theo ENTITY (dung luat de bai: "uu tien shape ENTITY").
|
||||
- Layout staticMap truoc khi sua: fe-user `Layout.tsx:59-62,79-80` (6 leaf Khkk_* -> /coming-soon),
|
||||
fe-admin `Layout.tsx:34-39` (6 leaf -> /coming-soon).
|
||||
- `Khkk_*` KHONG ton tai trong `fe-{user,admin}/src/lib/menuKeys.ts` (grep 0 hit) => phai them (place 3).
|
||||
- Pe_WfView hien tai: fe-user `Layout.tsx:159` -> `/purchase-evaluations/workflow-matrix?type=N`
|
||||
(page `fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx`, whitelist type `1|2|3` o `:60`).
|
||||
fe-admin CO Y KHONG co Pe_WfView (`Layout.tsx:125-129` lead chot 2026-07-27 — khong co matrix page ben admin).
|
||||
- fe-admin Designer da mo type-10: `ApprovalWorkflowsV2Page.tsx:147` `TYPE_INT_CONTRACT_SIGNING_PLAN = 10`,
|
||||
`:162` `ContractSigningPlan: 10` (deep-link), route `fe-admin/src/App.tsx:76` `/system/approval-workflows-v2/:typeCode`.
|
||||
- Upload multipart khuon: `fe-user/src/components/pe/PeDetailTabs.tsx:3323-3331` (FormData + header multipart)
|
||||
+ download blob `:3352-3360`.
|
||||
|
||||
## MUC 1 — 4 file MOI ben fe-user (DONE, viet truoc roi cp sang fe-admin)
|
||||
|
||||
- `fe-user/src/types/khkk.ts` — enum-as-const 4 bo (Phase 1/2/3/98/99 · DossierItemKind 1/2/99 ·
|
||||
DossierItemStatus 1..4 · AttachmentPurpose 1/2/99) + badge full-class-literal (Pattern 14) +
|
||||
DTO (List/Line/DossierItem/Attachment/LevelOpinion/Detail) + input (Create/UpdateDraft/UpsertDossier)
|
||||
+ `KhkkPeCandidateDto` (CO Y la TAP CON cua `PurchaseEvaluationListItemDto` BE tra — field thua bo qua runtime)
|
||||
+ `PagedResult<T>`. Header file KHAI HOP DONG FE<->BE 12 endpoint (de BE lane doi chieu 1 cho).
|
||||
- `fe-user/src/pages/khkk/KhkkListPage.tsx` — 6 KpiCard loc phase + o tim + bang 7 cot + phan trang;
|
||||
`?filter=` map ten-phase -> int (`FILTER_TO_PHASE`), `?view=deleted` -> endpoint rieng
|
||||
`/contract-signing-plans/deleted` (mirror PE S155) + an nut tao + row khong click.
|
||||
🔴 Chip loc chi doi STATE (khong ghi URL) => menu khong sang nham (bug UAT S155).
|
||||
- `fe-user/src/pages/khkk/KhkkCreatePage.tsx` — picker PE nguon (list nut chon, hien maPhieu +
|
||||
tenGoiThau + du an/hang muc + chip winner names) + dropdown workflow `applicableType=10` loc
|
||||
`isUserSelectable` (khuon ProposalCreatePage:59-69, endpoint tra `{types:[{applicableType,history}]}`
|
||||
KHONG phai flat array) + GhiChu; POST -> navigate `/khkk/{id}`. FE KHONG gui lines (BE sinh tu winner).
|
||||
- `fe-user/src/pages/khkk/KhkkDetailPage.tsx` — 4 section: Thong tin (sua GhiChu/HoSoLink khi
|
||||
DangSoanThao|TraLai) · bang Lines per-NCC READ-ONLY (PeReferenceAmount/Proposed/Approved formatVnd —
|
||||
W2 khong co endpoint sua line nen KHONG bia) · bang DossierItems chip 4 muc + upsert/delete dialog
|
||||
(chi mo khi draft) · Attachments upload multi/download blob/delete (mo MOI phase).
|
||||
🔴 KHONG panel trinh/duyet + KHONG banner cap duyet = W3.
|
||||
Reference in New Issue
Block a user