diff --git a/.claude/WAL.md b/.claude/WAL.md index b5ab979..44528ed 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -20,9 +20,12 @@ chain: + MenuKeys/All + CQRS ContractCatalogFeatures + controller (path api/catalogs/contract-catalog, authz mirror) + build 0W/0E + TEST 590/590 (lead chạy ×2 — lần-1 flaky 2-fail isolation, lần-2 SẠCH; chạy riêng class 11/11). Lane khai 2 quyết hợp lý (TenVi sạch · controller file-riêng path-spec). UNCOMMITTED -[!] wf: B4 K1-FE+K1-test wf_d894f6e6-018 ĐANG CHẠY (2 lane song song: impl-fe KIND_CONFIG contract-catalog - ×2 app + 4-chỗ-mirror · test-specialist 6 test T1-T6 [seed-idempotent-86 · phân-bố-8-nhóm · unique- - filtered · validator · soft-delete · key-∈-All]). 🔴 KHÔNG commit/push — OG-6 gate +[x] wf: B4 K1-FE ✅ (6 file/3-per-app đối xứng · KIND_CONFIG kind-5 · 4-chỗ-16-bis · SHA-pair d71a9e3b ×2 · + build ×2 PASS — lead RE-BUILD trọng-tài #68 lần-3: diagnostics IDE = stale, build thật XANH; lane khai + 2 khoảng-hở UX [3 filter BE chưa UI · tab hiện cả fe-user] chờ lead/anh, KHÔNG tự quyết — đúng luật). + Bonus finding: `columns` KIND_CONFIG = config CHẾT từ S29, kind-5 là lần đầu được nối render +[!] wf: B4 K1-test p2 wf_1fb9f309-a27 (relaunch — p1 chết sau đọc-contract): viết ContractCatalogTests 6-test + + chạy suite kỳ vọng 596. 🔴 KHÔNG commit — sau đó: reviewer-gate K1 → chờ OG-6 → commit-push K1 [ ] B4 tiếp: K1-FE (×2 app KIND_CONFIG) + K1-test → K2 → K3 → K4a... theo build-order; mỗi wave lead verify + reviewer-gate trước commit; PUSH K1 chờ anh soát OG-6 (bảng transcribe + 8 nhãn) [ ] K8 dry-run: gói test SOLUTION-PE-{n} + B0-form 14 người thật (fail-closed) + đo freeze 2-nhánh diff --git a/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k1.md b/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k1.md index 9faaabf..b62cfe9 100644 --- a/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k1.md +++ b/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k1.md @@ -1,7 +1,58 @@ # impl-fe K1 ## Tiến độ -- (ĐANG LÀM) Call-1: khung sub-file. Bước kế: đọc §K1 (d) FE + BE contract trên đĩa + khuôn KIND_CONFIG catalogs fe-user. +- Call-1: khung sub-file. OK. +- Đọc xong nguồn: + - `sub-invest-fable-b2-cum1.md:53-57` §K1.d — kind mới `contract-catalog` vào KIND_CONFIG ×2 app, endpoint `api/catalogs/contract-catalog`, route `:kind` sẵn ⇒ 0 route mới. + - BE THẬT: `src/Backend/SolutionErp.Api/Controllers/ContractCatalogController.cs:21` `[Route("api/catalogs/contract-catalog")]`, `[Authorize]` class + `[Authorize(Roles="Admin")]` 3 verb ghi (`:39,:47,:56`). List query `groupCode/approvalGroup/isActive/q` (`:27-30`). + - DTO `ContractCatalogFeatures.cs:27-38` = 11 field {Id, Code, TenVi, TenEn?, GroupCode, ApprovalGroup int, SignerRole, InitialsChain?, NduqNote?, IsActive, SortOrder}. Create/Update command = 10 field (không Id) `:96-106` / `:155-166`. `ApprovalGroup` InclusiveBetween(1,8) `:118`. PUT check `id != body.Id` → 400 ⇒ FE phải gửi kèm `id`. + - Khuôn FE: `fe-user/src/pages/master/CatalogsPage.tsx` 321 dòng — SHA **IDENTICAL** với fe-admin (`6f482614…`) ⇒ viết 1 lần rồi `cp`. + - Menu: BE `MenuKeys.cs:28` const `ContractCatalog` (VÀO `All` :173) + `DbInitializer` seed row `(ContractCatalog, "Danh mục Hợp đồng", parent Catalogs, order 5, icon "FileSpreadsheet")`. + - `Layout.tsx` + `menuKeys.ts` DIVERGE 2 app (SHA khác) ⇒ mirror TAY, không cp. +- Sửa `CatalogsPage.tsx` fe-user (kind thứ 5) → `cp` fe-admin → menuKeys ×2 → Layout staticMap ×2 → build ×2. XONG. + +## File tạo/sửa (6 file, 0 file mới — 3 file/app, đối xứng) + +| File | Δ | Nội dung | +|---|---|---| +| `fe-user/src/pages/master/CatalogsPage.tsx` | +201/−68 (321→454 dòng) | kind thứ 5 | +| `fe-admin/src/pages/master/CatalogsPage.tsx` | +201/−68 | `cp` y nguyên — SHA-pair | +| `fe-user/src/lib/menuKeys.ts` | +4 | `ContractCatalog: 'ContractCatalog'` | +| `fe-admin/src/lib/menuKeys.ts` | +4 | idem (2 file DIVERGE sẵn ⇒ edit tay, không cp) | +| `fe-user/src/components/Layout.tsx` | +4 | staticMap `ContractCatalog: '/master/catalogs/contract-catalog'` | +| `fe-admin/src/components/Layout.tsx` | +4 | idem | + +### Chi tiết `CatalogsPage.tsx` (khuôn KIND_CONFIG, ADD-KIND playbook) +1. `type Kind` += `'contract-catalog'` · `KINDS` += (sub-tab thứ 5 tự hiện). +2. `FieldDef` nới 3 thứ: `type` += `'number' | 'select'`, `options?: {value:number;label:string}[]`, `numeric?: boolean`. +3. `KIND_CONFIG['contract-catalog']` — icon `FileSpreadsheet` (KHỚP icon BE seed), 10 field ĐÚNG 10 field của `CreateContractCatalogEntryCommand`: code · groupCode · tenVi · tenEn · approvalGroup(select N1–N8, numeric) · sortOrder(number, numeric) · signerRole · initialsChain · nduqNote(textarea) · isActive(checkbox). 4 field `*` = 4 `NotEmpty` của validator BE (`ContractCatalogFeatures.cs:114-120`). +4. `columns` (trước giờ là config CHẾT, không ai đọc) → thành nguồn `` cho nhánh contract-catalog: 8 cột {Mã · Tên VN · Tên EN · Nhóm · Nhóm duyệt · Vai ký chính · Ký nháy · Trạng thái}. +5. `` nhánh riêng (4 kind cũ giữ NGUYÊN byte cell cũ) · `colSpan` hardcode 6 → `colCount` tính theo kind. +6. `CatalogRow.name` `string` → `string?` (contract-catalog dùng `tenVi`) + helper `rowLabel()` cho confirm xóa (trước là `row.name` → sẽ in "undefined"). +7. `ApprovalGroupBadge` — Pattern 14: 8 chuỗi class ĐẦY ĐỦ trong `APPROVAL_GROUP_BADGE`, cycle `(value-1) % 8`, KHÔNG interpolate. +8. `buildBody` — field `numeric` ép `Number()`, rỗng → **0 chứ không null** (BE `int` non-nullable, `null → int` là System.Text.Json ném). Nhánh cũ (`'' | undefined → null`) giữ nguyên cho field còn lại. +9. `openCreate` — select mặc định option đầu (N1) để không POST rỗng dính `InclusiveBetween(1,8)`. +10. `PageHeader description` đổi theo kind (giữ signature title/description, không thêm prop). + +### Pattern 16-bis — 4 chỗ +| # | Chỗ | Trạng thái | +|---|---|---| +| 1 | Page/types | ✅ `CatalogsPage.tsx` ×2 (0 file types mới — row là `Record`) | +| 2 | `App.tsx` Route | ✅ **KHÔNG cần** — `/master/catalogs/:kind` có sẵn (`fe-user/src/App.tsx:63`, `fe-admin/src/App.tsx:69`), kind mới đi chung | +| 3 | `lib/menuKeys.ts` | ✅ ×2 | +| 4 | `Layout.tsx` staticMap | ✅ ×2 — chỗ DỄ MISS (gotcha #50) | ## Kết quả build -- (ĐANG LÀM) +- `fe-user`: **PASS** — `tsc -b && vite build`, 1950 modules, `dist/assets/index-C5SIaqt5.js` 1,634.97 kB, ✓ built in 21.57s, **0 TS error**. +- `fe-admin`: **PASS** — `dist/assets/index-DLUxeB3d.js` 1,718.77 kB, ✓ built in 9.52s, **0 TS error**. +- Warning: `@import` phải đứng trước rule + chunk >500 kB + INEFFECTIVE_DYNAMIC_IMPORT `realtime.ts` — **pre-existing cả 3**, không phải do K1. +- SHA-pair `CatalogsPage.tsx`: `d71a9e3bb17b835b85b4f3467905af2ab414f52e3ec0c58e79d55ef57673985a` ×2 IDENTICAL (trước sửa: `6f482614…` ×2). +- Chứng Pattern 14 có răng (class KHÔNG bị purge) — grep CSS đã build: `bg-sky-50` `bg-violet-50` `bg-teal-50` `bg-orange-50` `border-indigo-200` `text-rose-700` có mặt ở CẢ `fe-user/dist/assets/index-BjUevXzu.css` LẪN `fe-admin/dist/assets/index-B62-9mVN.css`. +- `git diff --stat` đối xứng: 201/201 · 4/4 · 4/4 → 350 insert / 68 delete / 6 file. + +## Ngoài phạm vi (KHÔNG đụng — đúng lệnh) +- 0 file `src/Backend/**` (chỉ Read `ContractCatalogController.cs` + `ContractCatalogFeatures.cs` để lấy shape). +- 0 file pipeline/khkk (K4/K5). 0 commit, 0 push. +- KHÔNG sửa `agent-memory/*` (spawn-record trả về qua `memoryDelta`, em main harvest). + +END impl-fe-k1 — 6 file · build ×2 PASS diff --git a/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-test-k1.md b/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-test-k1.md index 41b090a..01d4361 100644 --- a/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-test-k1.md +++ b/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-test-k1.md @@ -1,7 +1,32 @@ # test K1 ## Tiến độ -- (ĐANG LÀM) call-1: khung sub-file đã ghi. Bước kế: đọc spec §K1 + code thật (entity/config/seeder/features). -## Kết quả -- (CHƯA ĐO) +### §0 Baseline ĐO TƯƠI trước khi viết (không tin số đề bài) — XONG +Lệnh: `dotnet test SolutionErp.slnx --nologo --verbosity minimal -p:BuildInParallel=false -maxcpucount:1` +``` +Domain: Failed 0, Passed 45, Total 45 +Infra : Failed 1, Passed 544, Total 545 +``` +⇒ **baseline 590 test = 589 PASS / 1 FAIL**. Đề bài ghi "590/590" — LỆCH 1. +🔴 **FAIL KHÔNG liên quan K1:** `Forms.DocxRendererTests.RenderAsync_UnknownPlaceholder_KeepsLiteralToken_NoCrash` +→ `System.OutOfMemoryException` tại `WordprocessingDocument.AddMainDocumentPart()` (DocxRendererTests.cs:26). +Nghi flake do áp lực RAM (fan-out nhiều agent chạy song song), KHÔNG phải regression code. +Sẽ đo lại RIÊNG test đó ở §3 để phân biệt flake ⟂ vỡ thật. + +### §1 Đọc nguồn — XONG +- `sub-invest-fable-b2-cum1.md:59-75` §K1.e (5 case) + §K1.f (checklist acceptance) +- Code THẬT (test theo CODE, không theo spec): + · `Domain/Master/Catalogs/ContractCatalogEntry.cs:13-25` — 10 cột + · `Persistence/Configurations/ContractCatalogEntryConfiguration.cs:27-29` — UNIQUE filtered `[IsDeleted] = 0` + index ApprovalGroup + query filter + · `DbInitializer.cs:3163-3324` `SeedContractCatalogAsync(ApplicationDbContext, ILogger)` **private static** → gọi qua REFLECTION (khuôn S67) + · `DbInitializer.cs:3293-3296` — `IgnoreQueryFilters()` khi dò existing (né #75/#76) + · `Application/Master/Catalogs/ContractCatalogFeatures.cs` — List/Get/Create/Update/Delete + 2 validator `InclusiveBetween(1,8)` + · `Api/Controllers/ContractCatalogController.cs:20-23` — `[Authorize]` class + `Roles="Admin"` cho 3 verb ghi (**0 policy key**) + · `MenuKeys.cs:28-32 + :173` — const `ContractCatalog` **VÀO `All`**; `DbInitializer.cs:1776` menu row parent `Catalogs` + +## PHẦN 2 (relaunch) + +### §2 Viết file test — ĐANG LÀM +Relaunch 14:5x. Lượt 1 chết sau §1 (contract đã note đủ → KHÔNG đọc lại code trừ khuôn fixture). +Kế hoạch: `tests/SolutionErp.Infrastructure.Tests/Services/ContractCatalogTests.cs` — 6 test T1..T6.