[CLAUDE] Docs: S113 closeout — Supplier import v2 SHIP prod (Run #497) + Mig 64/477 test/gotcha #80

- STATUS/HANDOFF: S113 mega-line + state (Mig 63->64, test 458->477, gotcha 79->80, prod 22->37 NCC, bundle acprMqfB/Ck3BTDdA Run #497)
- gotcha #80: ! null-forgiving tren external-parsed data -> NRE an (build 0/0 khong bat; chi test-before-critical bat)
- ef-core skill: Mig 64 row + pointer-flush (session-start H1 drift, kill recurring latest-Mig class)
- session log 2026-07-12-S113-supplier-import-v2.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-12 19:24:49 +07:00
parent 0efc22364b
commit ccdcbe79f0
6 changed files with 71 additions and 11 deletions

View File

@ -17,7 +17,7 @@ Skill này là tài liệu chuyên biệt để Claude (và developer khác) dù
| Skill | Mục đích | Trigger ví dụ | Trạng thái |
|---|---|---|---|
| `dependency-audit-erp` | Scan CVE NuGet + npm 2 FE, respect pin constraint (MediatR 12.4.1, Swashbuckle 6.9.0) | "npm audit", "dotnet vulnerable", "deps scan", "nâng cấp package" | ✅ New Tier 3 |
| `ef-core-migration` | Tạo/revert EF Core 10 migration, 3-file rule, DesignTimeDbContextFactory, migration history (mới nhất Mig 62; số → `docs/STATUS.md`) | "thêm migration", "EF migration", "schema update", "snapshot lỗi" | ✅ Updated S104 (Mig 62 ExpandSupplierFields Supplier 9→27 field; prev S98 Mig 60/61; số → `docs/STATUS.md`) |
| `ef-core-migration` | Tạo/revert EF Core 10 migration, 3-file rule, DesignTimeDbContextFactory, migration history (mới nhất → row cuối bảng skill; số → `docs/STATUS.md`) | "thêm migration", "EF migration", "schema update", "snapshot lỗi" | ✅ Updated S113 (mig-ref → pointer anti-drift B1; Mig 63 AddSupplierImportSourceFields row added; số → `docs/STATUS.md`) |
| `iis-deploy-runbook` | 3 IIS site + win-acme cert + gitea-runner + LibreOffice + debug 500/502/SignalR prod + **G-084 IPv4/IPv6 hardening** | "prod 500", "IIS fail", "cert hết hạn", "restart app pool", "deploy IIS", "port hijack" | ✅ Updated (G-084) |
## Format chuẩn 1 skill

View File

@ -1,6 +1,6 @@
---
name: ef-core-migration
description: Tạo/sửa/revert EF Core 10 migration cho SOLUTION_ERP. Dùng khi thêm entity mới, thay đổi schema, rollback migration, debug DesignTimeDbContextFactory fail. Đã có nhiều migration (số hiện tại → docs/STATUS.md canonical; mới nhất Mig 62 ExpandSupplierFields, S104). Snapshot + Designer + Migration 3-file rule bắt buộc commit đủ.
description: Tạo/sửa/revert EF Core 10 migration cho SOLUTION_ERP. Dùng khi thêm entity mới, thay đổi schema, rollback migration, debug DesignTimeDbContextFactory fail. Đã có nhiều migration (số hiện tại + mới nhất → docs/STATUS.md canonical + row cuối bảng history trong skill này — KHÔNG hardcode số ở đây, B1 anti-drift). Snapshot + Designer + Migration 3-file rule bắt buộc commit đủ.
when-to-use:
- "thêm migration"
- "EF Core migration"
@ -16,7 +16,7 @@ when-to-use:
> **Context:** .NET 10 + EF Core 10 + SQL Server. DbContext: `ApplicationDbContext` ở `Infrastructure/Persistence/`. Startup: `SolutionErp.Api`.
## Migration history (số hiện tại → `docs/STATUS.md` canonical; mới nhất Mig 62)
## Migration history (số hiện tại → `docs/STATUS.md` canonical; mới nhất = row cuối bảng)
| # | Name | Tables added / changed |
|---|---|---|
@ -82,8 +82,10 @@ when-to-use:
| **60** | **`AddPeEndedByLevelFinalize`** | **🎯 S97 PE +`EndedByLevelFinalize bit NOT NULL DEFAULT 0` cờ RUNTIME "phiếu ĐÃ kết thúc sớm bằng level-finalize" (set-true CHỈ nhánh finalize trong service); display `EndsBeforeCeo` cho phiếu DaDuyet đọc RUNTIME thay suy-CONFIG (gotcha #78 phiếu opt-out lên CEO hết báo sai "không qua CEO"). Kèm `Sql()` backfill marker soi NHẦM bảng `Approvals` match 0 (xem Mig 61). 1 AddColumn, no new table.** |
| **61** | **`BackfillEndedByLevelFinalizeFromChangelog`** | **🎯 S97 DATA-ONLY no-schema (đính chính Mig 60): backfill query ĐÚNG bảng `PurchaseEvaluationChangelogs.ContextNote LIKE N'%Duyệt KẾT THÚC tại%'` + PhaseAtChange=7. PE 2 bảng lịch-sử TÁCH VAI: `Approvals` = per-approver record vs `Changelogs.ContextNote` = transition+marker (`LogTransitionAsync`). Idempotent, SQL-Server-only, prod-verified A/005/A/008 flag 01 + A/010 control unchanged (gotcha #79 xác định đúng bảng-writer + verify marker-count prod TRƯỚC khi tin backfill; backfill=0-rows = cờ đỏ).** |
| **62** | **`ExpandSupplierFields`** | **🎯 S104 Supplier 927 field (Phase A của 30-col NCC Excel import, anh Kiệt FDC): 18 AddColumn nullable + 18 DropColumn Down + `SupplierType +CaHai=6` append-only + enum `SupplierStatus` mới + guarded un-cram `Sql()` (clear legacy crammed Note 4 seeded rows exact-match, reversible). **No new table (89 giữ — 18 AddColumn/0 CreateTable)**. Seed fill-nulls-if-exists 30-field TRUONGGIANG/TANPHU/TGN/DONGDUONG idempotent. Commit `778fc98` shipped UN-closed-out S104 docs-reconcile; prod-verified Run #488 PASS; 440 test #65-no-bite đổi chữ Create/UpdateSupplierCommand. Phase B (Excel-auto-map) defer.** |
| **63** | **`AddSupplierImportSourceFields`** | **🎯 S112 Supplier +`SourceUpdatedAt datetime2?` + `SourceUpdatedBy nvarchar(200)?` (import-provenance cột 29/30 file Excel "Database NCC" Phase B Excel-import Phase B). 2 AddColumn nullable + 2 DropColumn Down (reversible). **No new table (89 giữ)**. Prod-verified Run #496 PASS `__EFMigrationsHistory` top + 2/2-col present; commit `e100ef0`.** |
| **64** | **`AddSupplierPublishState`** | **🎯 S113 Supplier +`IsPublic` bit NOT NULL default 0 (cổng công-bố "thiếu NCC nháp/ẩn" R4): AddColumn + `Sql("UPDATE Suppliers SET IsPublic=1")` backfill 37/37 prodpublic + DropIndexCreateIndex đổi Code filtered-unique `WHERE [IsDeleted]=0 AND [Code]<>''` (cho phép nhiều nháp Code=''). **No new table (89 giữ)**. Reversible Down (⚠ edge: rollback sau khi 2 nháp Code='' sẽ kẹt CreateIndex filter ). Prod-verified Run #497; commit `5fa11b5`. Kèm gotcha #80 (null-Code NRE NewSupplier).** |
Total: **89 bảng** `sys.tables` (= 88 dbo-app + `__EFMigrationsHistory`; re-ground S97 cicd Run #482 LIVE số canonical `docs/STATUS.md`; last schema-changing Mig 50 net 9388-app, Mig 51-62 đều AddColumn/index/data-only/schema-expand không đổi số bảng). Xem `docs/database/schema-diagram.md` migration table + §11-15 module ERD 16+ Mig 27-62 chi tiết pending).
Total: **89 bảng** `sys.tables` (= 88 dbo-app + `__EFMigrationsHistory`; re-ground S97 cicd Run #482 LIVE số canonical `docs/STATUS.md`; last schema-changing Mig 50 net 9388-app, Mig 51-64 đều AddColumn/index/data-only/schema-expand không đổi số bảng). Xem `docs/database/schema-diagram.md` migration table + §11-15 module ERD 16+ Mig 27-64 chi tiết pending).
## N-stage workflow pattern (Mig 18-20 — Session 12-13)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,41 @@
# S113 — Supplier import v2 (SHIP PROD, Run #497)
**Date:** 2026-07-12 · **Owner:** anh Kiệt · **Lead:** Opus 4.8 (1M) Max · **Mode:** HMW-ON · **Commit:** `5fa11b5` (feature) + closeout.
## Đề bài (anh Kiệt — 7 yêu cầu R1-R7)
Nâng Supplier Excel-import (S112 Phase B) → v2: R1 đọc đúng file thật · R2 nút "Tải file mẫu" · R3 nhập Mã NCC thủ công sau upload · R4 thiếu Mã NCC→nháp/ẩn · R5 dedup theo MST · R6 không MST&không Mã NCC→không public · R7 badge Public/ẩn.
## Flow (invest → spec → decision → implement → test → review → deploy)
1. **Đọc file thật** `Data base - 1_7_2027.xlsx` (Sheet1, header ROW 4, 30 cột, MST=c12, TÊN VIẾT TẮT=c4→Code, 4 NCC). Phát hiện: file KHÔNG có cột "Mã NCC" → xác nhận R3 = user tự nhập.
2. **`/fable-clone investigator-codebase` invest ensemble** (`wf_e7896ca6-901`, 5 lăng-kính cùng vai): schema · import-service · fe-ux · visibility · dedup. 4/5 structured + lane visibility **#53-garble** → em-main grep recover.
3. **Spec 3-mục** (`runs/2026-07-12-S113-supplier-import-v2-invest/spec-...md`) + trích 30 token header THẬT byte-exact.
4. **Anh Kiệt 3 decision:** **D1=A** (Mã NCC = field Code hiện có) · **D2** (Code=cổng public; MST=cảnh-báo-mềm không chặn; existing-public giữ; import-scope) · **D3** (nới quyền: mọi user có quyền cập-nhật danh-mục = `Suppliers.Update`, không admin-only).
5. **HMW-Opus implement** (BE⟂FE disjoint parallel): implementer-backend + implementer-frontend.
6. **test-specialist** +19 test (477 PASS) → **reviewer** GO-WITH-ADJ (2 must-fix) → deploy.
## Ship
- **Mig 64 `AddSupplierPublishState`:** +IsPublic bit (cổng công-bố) + backfill 37/37 prod→public + filtered-unique đổi `[Code]<>''` (nhiều nháp) + no-new-table (89) + reversible Down.
- **BE:** dedup MST-primary+Code-backstop (chống 500) · re-bake 30-token LayoutValid · `PublishSupplierCommand` riêng (né #73) · `GET /import/template` ClosedXML · authz `Policy=Suppliers.Update`.
- **FE 2-app SHA-mirror:** badge Public/Nháp · nút Công bố · filter · nút Tải-file-mẫu · cột Mã editable · cảnh-báo-MST · picker PE+HĐ lọc `published=true`.
## 4 BUG caught + fixed PRE-PROD (giá trị multi-agent)
1. **#53-garble invest-visibility-lane** → em-main-solo grep recover (identify consumers).
2. **null-Code NRE `NewSupplier` (gotcha #80)** — test-specialist bắt: `Code=r.Code!.Trim()` NRE khi ô cột-4 trống (path R4 lõi, 500 cả batch); `!` che → build 0/0 không thấy. Fix `(r.Code??"").Trim()` + flip characterization-test.
3. **authz-403 Import-nút** — em-main verify-chéo: FE Import `action="Create"` ⧣ BE `Policy=Suppliers.Update` → khớp Update 2-app.
4. **draft-leak-picker + CreateSupplier-quên-IsPublic** — reviewer 2 must-fix: 4 picker (PeDetailTabs+ContractCreate ×2) chưa truyền `published=true` (draft lọt → mã HĐ hỏng) + `CreateSupplierCommand` không set IsPublic → NCC tạo tay thành nháp. Fix cả 2.
## cicd Run #497 PASS (5m14s)
Bundle admin `Cixlt_MA``acprMqfB` (CSS `CfunQHbd`) / user `BhBOG1dn``Ck3BTDdA` (CSS `DGqqHQXS`) rotate ×2 ship-proof · Mig 64 prod (history-top + IsPublic-col + 37/37-backfill + sys.tables 89) · byte-verify `Tải file mẫu`×1/`Đã công bố`×3/`Nháp (ẩn)`×4/`import/template`×1/`published`×5 PRESENT · smoke health/admin/eoffice 200 + `import/template`-401-live + `suppliers?published=true`-401-live + bogus-404-control.
## State
**Mig 64 · 89 bảng · 477 test (45D+432I) · gotcha 80 · menu 54 · bundle admin `acprMqfB`/user `Ck3BTDdA` (Run #497) · prod 37 NCC/17 user.**
## Lessons
- `!` null-forgiving trên external-parsed data = mìn (gotcha #80); build-0/0 không bắt, chỉ test-before-critical bắt.
- Verify-chéo BE⟷FE bắt authz-mismatch (build-pass ≠ contract-match); policy-name là string, compiler không check → verify Program.cs auto-reg.
- Cardinality (S87/S88): đổi visibility = grep MỌI consumer; reviewer bắt FE sót wiring dù spec đã flag (recover-lane).
- #53-garble ×2 → SendMessage-resume in-session finish sạch (1/3 lifetime) + em-main ground-truth qua git/grep (không tin self-review sau garble).
## NEXT
- **anh Kiệt (UAT):** test luồng end-to-end + upload file "Database NCC" ĐẦY ĐỦ (file test chỉ 4 dòng) xác nhận LayoutValid=true.
- **em:** OQ4 open (MST-A-Code-B silent-precedence — anh refine nếu muốn conflict-error) · nice-to-have reviewer (stale-comment service + Mig64-Down-edge + template-roundtrip-test) · monthly re-tier 2026-08-01.

View File

@ -1332,6 +1332,16 @@ for h in resp.points: # ← .points không phải iterable trực tiếp
---
### 80. `!` null-forgiving trên dữ-liệu-parse-ngoài → NRE ẩn (build 0/0 KHÔNG bắt) (Session 113)
**Triệu chứng:** Import Excel NCC v2 dòng thiếu NCC (ô cột-4 trống): preview classify New/draft KHÔNG lỗi + báo "sẽ lưu nháp", nhưng Confirm nổ `NullReferenceException` **500 CẢ batch** (all-or-nothing DB 0 ghi). Đúng path R4 lõi "thiếu NCC nháp" của feature.
**Cơ chế:** `SupplierExcelImportService.NewSupplier` gán `Code = r.Code!.Trim()`. Parser (`ReadRaw`) trả `null` cho ô trống (KHÔNG phải `""`). Toán tử `!` (null-forgiving) TẮT cảnh-báo nullable của compiler **build 0 warn / 0 err** mìn. Sibling `entity.IsPublic = !string.IsNullOrWhiteSpace(row.Code)` null-safe nhưng `NewSupplier` thì KHÔNG. Empty-string `""` chạy OK; chỉ `null` nổ FE round-trip ô blank thành JSON `null` (không phải `""`) reachable đường-thường.
**Fix:** `Code = (r.Code ?? string.Empty).Trim()` null + "" đồng nhất Code="" + IsPublic=false (nháp, ẩn khỏi filtered-unique `[Code]<>''`). **Quy tắc:** `!` null-forgiving trên dữ-liệu-parse-từ-ngoài (Excel/JSON/form-input) = MÌN dùng `?? default` chứ KHÔNG `!`. Build-0/0 KHÔNG bảo-đảm null-safety khi `!` che. Chỉ **test-before-critical** bắt được (test-specialist viết characterization-test reproduce NRE em-main fix prod flip test sang NotThrow+draft). Liên-quan #73 (absolute-set null-clobber).
---
## Checklist debug bug mới
1. Build pass không? fail check using + package version compat