wal: flush 20260814T0402

This commit is contained in:
pqhuy1987
2026-08-14 04:02:16 +07:00
parent a326c85d36
commit 1e9df0c3f5
2 changed files with 103 additions and 25 deletions

View File

@ -4,7 +4,11 @@
> KHÔNG chấm thiết-kế schema (FK strategy / index perf / concurrency = `database-agent`).
> Địa-phận: `src/Backend/SolutionErp.Infrastructure/Persistence/**`.
## 0. Thước phạm-vi (untracked-aware)
**VERDICT: `G5-DB-STYLE: PASS-WITH-FINDINGS` — 3-file ĐỦ, 1 finding + 1 note out-of-diff.**
---
## 0. Thước phạm-vi (untracked-aware — bắt được migration mới)
```
{ git diff --name-only origin/main...HEAD
@ -13,18 +17,90 @@
} | sort -u | grep -i 'Persistence/'
```
(kết quả + đo đạc append phía dưới)
4 hit — đúng 4 file, không hơn:
## 1. 3-file rule
| file | trạng thái git |
|---|---|
| `Persistence/DbInitializer.cs` | ` M` (+45/6) |
| `Persistence/Migrations/20260813204332_AddContractSealedAt.cs` | `??` UNTRACKED |
| `Persistence/Migrations/20260813204332_AddContractSealedAt.Designer.cs` | `??` UNTRACKED |
| `Persistence/Migrations/ApplicationDbContextModelSnapshot.cs` | ` M` (+6/0) |
## 2. Tên migration
🔴 2/3 file migration là **UNTRACKED** ⇒ thước `git diff` đơn thuần sẽ **0 hit** — đúng cái bẫy S179. Vế `git ls-files -o` là thứ bắt được.
## 3. Cột SealedAt / SealedByUserId
`ContractConfiguration.cs` **KHÔNG** trong diff ⇒ `DraftData` (nơi chứa thân HĐ) đã có sẵn từ trước (`ContractConfiguration.cs:21` `HasColumnType("nvarchar(max)")`) ⇒ vế 2 của YC-032 **không nợ migration**. Đây là điểm CỘNG, không phải sót.
## 4. Down() reversible
## 1. 3-file rule — ĐỦ 3/3
## 5. DbInitializer seed
- `ls 20260813204332*` → 2 file (`.cs` 1134 B + `.Designer.cs` 268 739 B), cùng mtime `Aug 14 03:43`.
- `ApplicationDbContextModelSnapshot.cs` (file dùng chung) **CÓ đổi**: `git diff --stat` = `1 file changed, 6 insertions(+)`.
- grep `Sealed` trong snapshot → 2 hit: `:866 b.Property<DateTime?>("SealedAt")` · `:869 b.Property<Guid?>("SealedByUserId")`.
- 🔴 **CONTROL DƯƠNG** (chống 0-hit giả): `grep -c Sealed` trong Designer MỚI = **4**; trong Designer của Mig 73 (`20260812141501_...EndedByLevelFinalize.Designer.cs`) = **0** ⇒ thước sống, và vắng-mặt ở bản trước là vắng THẬT.
- Designer có attribute đúng id: `[Migration("20260813204332_AddContractSealedAt")]` (`...Designer.cs:15`).
## 6. has-pending-model-changes
## 2. Tên migration — KHỚP khuôn
## 7. VERDICT
`AddContractSealedAt` = `Add<Thing>` PascalCase, khuôn `Add<Entity><Feature>`.
**Neo bản ĐÃ APPLY PROD:** Mig 73 `20260812141501_AddContractEndedByLevelFinalize` — cùng bảng `Contracts`, cùng dạng `Add` + `Contract` + tên tính năng. Timestamp `20260813204332` > `20260812141501` ⇒ thứ tự apply đúng, không chèn ngược.
## 3. Cột `SealedAt` / `SealedByUserId` — KHỚP họ `<Verb>edAt` + `<Verb>edByUserId`
`Mig.cs:14-24`: `AddColumn<DateTime>("SealedAt", "Contracts", "datetime2", nullable: true)` + `AddColumn<Guid>("SealedByUserId", "Contracts", "uniqueidentifier", nullable: true)`.
| trục | bản mới | bản ĐÃ APPLY PROD (neo) | khớp? |
|---|---|---|---|
| hậu-tố `...ByUserId` | `SealedByUserId` | 11 cột toàn repo: `SignedByUserId` ×7, `ApprovedByUserId`, `BookedByUserId`, `UploadedByUserId` (snapshot `:692/:1245/:4304/:4334/:4776/…`) | ✅ |
| kiểu | `uniqueidentifier` + `datetime2` | `AddContractLevelOpinions.cs:22-23` (Mig 33) `SignedAt datetime2` + `SignedByUserId uniqueidentifier` | ✅ |
| nullable | cả 2 `null` | `ApprovedByUserId` `Guid?` (Mig 69 KHKK, snapshot `:422`) — mốc chưa xảy ra = null | ✅ |
| loose-Guid KHÔNG FK vật lý | không FK | Mig 69 `AddContractSigningPlans.cs`: grep `ByUserId``ForeignKey` = **0 hit**, mà CONTROL DƯƠNG cùng file = **8** `ForeignKey` ⇒ họ `*ByUserId` cố ý loose. Cùng lớp: `WorkItemId` (Mig 49), `Department.ParentId` (Mig 51) | ✅ |
| index | KHÔNG index | 1/11 cột `*ByUserId` có index (chỉ `BookedByUserId``MeetingBooking`, là khoá truy vấn). `SealedBy` là con-dấu audit, không phải khoá lọc ⇒ theo đa số 10/11 | ✅ |
| khai trong `Configurations/` | không khai | `ContractConfiguration.cs` chỉ khai maxlength/precision/enum-conversion/index; cột `Guid?`/`DateTime?` trần đi theo convention (`SlaDeadline`, `CurrentApprovalLevelOrder` cũng 0 dòng `Property`) ⇒ **không khai mới là đúng khuôn** | ✅ |
🔸 Không có `SealedByFullName` denorm (LevelOpinions có `SignedByFullName`) — **không tính lệch**: neo đúng của con-dấu-trên-chính-entity là bộ audit `BaseEntity.cs:7-9` (`CreatedBy`/`UpdatedBy` = Guid trần, không denorm tên), còn `SignedByFullName` thuộc bảng vệ-tinh in ra hộp ý-kiến.
## 4. `Down()` — reversible THẬT
`Mig.cs:28-37` = `DropColumn("SealedAt") + DropColumn("SealedByUserId")` — nghịch-đảo ĐÚNG của `Up`, không `Sql()` backfill nên không có vế data không đảo được.
**Neo:** Mig 73 `20260812141501_...cs:22-27` cùng hình-dạng `AddColumn → DropColumn`, không thừa không thiếu.
## 5. `has-pending-model-changes` — TỰ CHẠY, không tin lời lead
```
dotnet ef migrations has-pending-model-changes --project ...Infrastructure --startup-project ...Api
→ Build succeeded.
→ No changes have been made to the model since the last migration.
```
Khớp số lead khai. ⇒ snapshot ⟂ model đồng bộ, migration kế tiếp sẽ không sinh duplicate column (gotcha #17 không dính).
## 6. `DbInitializer.cs` — seed template + menu-row
**ĐÚNG khuôn (3 trục):**
- Menu-row 2 loại mới **tự sinh**, không hardcode: `MenuKeys.cs:44-45` `ContractTypeCodes = ["ThauPhu", "ThauPhuVatTu", "ThauPhuThietBi", "GiaoKhoan", …]` (chèn giữa ⇒ thứ tự 01 · 01A · 01B · 02…), tiêu thụ tại `DbInitializer.cs:2198 / :2218 / :2591 / :2627`.
- Nhãn đi đủ 3 nhà: `typeLabels` (`:2055-2056`), leaf `Hdc_*` (`:2382-2383`), group (`:2399-2400`).
- Vòng `reconciled` (`:3904-3921`) **idempotent + cùng khuôn `labelBackfill`** (`:2408-2422`): so-sánh → chỉ ghi khi lệch → đếm → `SaveChanges` chỉ khi >0 → log. Chạy lần 2 = 0 ghi. `existingCodes` chụp TRƯỚC vòng add (`:3872`) nên reconcile chỉ đụng row cũ, không đụng row vừa insert.
**🔴 FINDING F-1 — reconcile giành quyền ghi với admin UI trên 2 cột admin sửa được (seed-convention, không phải schema-design).**
`DbInitializer.cs:3913-3914`
```csharp
if (row.IsActive != shouldBeActive) { row.IsActive = shouldBeActive; reconciled++; }
if (row.ContractType != type) { row.ContractType = type; reconciled++; }
```
Comment ngay trên (`:3900-3902`) tự đặt luật: *"KHÔNG đụng `Name`/`Description` — admin có thể đã sửa tay qua UI, ghi đè là xoá công người dùng"*. Nhưng **cả 2 cột đang bị đụng ĐỀU do chính admin UI ghi**:
- `FormFeatures.cs:283` `entity.ContractType = request.ContractType;` (`UpdateContractTemplateCommandHandler`) — admin trỏ lại loại HĐ cho mẫu ⇒ **restart kế tiếp bị lật về hằng-số**.
- `FormFeatures.cs:307`**DELETE mẫu = `entity.IsActive = false`** (`DeleteContractTemplateCommandHandler`, comment *"simpler MVP: just flip IsActive"*). File `.docx` vẫn nằm trên đĩa ⇒ `shouldBeActive = true`**mẫu admin vừa xoá SỐNG LẠI mỗi lần khởi động API**.
Ungated: `SeedContractTemplatesAsync` gọi ở `DbInitializer.cs:119`, **NGOÀI** rào `if (!demoSeedDisabled)` (`:117`) ⇒ chạy mọi env kể cả prod. Đây đúng lớp bài học repo *"seeder ungated re-add mỗi lần restart"* (#75/#76) — luật của em: seed/backfill mới **phải khai rõ gate**.
Vá rẻ nhất, giữ nguyên ý đồ: chỉ reconcile đúng 2 `FormCode` đang re-trỏ (`002.02` / `002.03`), hoặc chỉ cho phép chiều `false → true` **một lần** khi file mới xuất hiện. Quyết ở lead/owner — em không sửa code.
**🔸 NOTE N-1 (NGOÀI diff, pre-existing = CHUẨN theo luật lõi, KHÔNG tính finding G5):** `ContractTemplateConfiguration.cs:24` `HasIndex(x => x.FormCode).IsUnique()` **không filter** `IsDeleted`, trong khi entity là soft-delete (`ContractTemplate : AuditableEntity` + `HasQueryFilter(x => !x.IsDeleted)` `:26`). Repo đã 6× vá đúng lớp này (Mig 43/45/47, gotcha #57). Phơi-nhiễm hiện **bằng 0** vì đường DELETE lật `IsActive` chứ không lật `IsDeleted` (`FormFeatures.cs:307`) ⇒ ghi lại để khỏi quên, KHÔNG chặn G5.
## 7. Neo THÁCH-CLEAN (cho các trục em tuyên khớp)
- `20260813204332_AddContractSealedAt.cs:14-18``20260812141501_AddContractEndedByLevelFinalize.cs:13-19` — cùng `AddColumn` trên bảng `Contracts`, cùng cặp `Up`/`Down` thuần, 0 `Sql()`.
- `20260813204332_AddContractSealedAt.cs:20-24``20260522052240_AddContractLevelOpinions.cs:23``SealedByUserId uniqueidentifier``SignedByUserId uniqueidentifier`, **cả hai KHÔNG FK vật lý**.
- `DbInitializer.cs:3904-3921``DbInitializer.cs:2408-2422` — cùng khuôn backfill hội-tụ (so-sánh → ghi-khi-lệch → đếm → save-if->0).
## 8. Ghi chú phép đo không lấy được
`sqlcmd -S .\SQLEXPRESS -d SolutionErp -E -Q "SELECT TOP 3 MigrationId FROM __EFMigrationsHistory"`**không chạy được ở máy này** (`Error Locating Server/Instance Specified` — instance prod nằm trên VPS). Không ảnh hưởng kết luận: mọi neo "đã apply prod" em dùng đều là migration cũ (26 / 33 / 49 / 51 / 69 / 73) đã có lịch sử prod-verified trong `CLAUDE.md` + skill `ef-core-migration`. Em KHÔNG ssh vào prod (read-only reviewer).

View File

@ -1,37 +1,39 @@
# sub-reviewer-5 — G5 vòng chấm cuối YC-032 (HĐ Thầu phụ tách 2 form)
# sub-reviewer-5 — G5 ADVERSARIAL GATE (YC-032, trước deploy prod)
> Vai: reviewer adversarial (read-only). Ngày 2026-08-13/14. Trần lượt ~40.
> Diện: 38 file sửa (+984/85) + 11 file/thư-mục mới (chưa `git add`).
> Nhiệm vụ: soi 6 trục, mỗi trục tự đo bằng lệnh, 0-hit kèm control-dương.
> Lượt CHẠY LẠI đã-cắt-gọt (ghi đè khung rỗng 880 B của lượt trước).
> Kỷ luật: mọi số từ lệnh vừa chạy · 0-hit kèm control-dương · append SAU MỖI trục, không gom cuối.
## 0. Trạng thái cây (đo)
## 0. Phạm vi đo (diff + untracked)
`git status --porcelain` = **49 dòng** (38 ` M` tracked + 11 `??` untracked).
`git diff --stat` (tracked only) = **38 file, +1009 / 92**.
🔴 11 mục untracked mà `git diff` thuần BỎ SÓT:
`fe-{admin,user}/src/components/contracts/ContractBodyView.tsx` · `fe-{admin,user}/src/lib/contractBody/` (thư mục) · `fe-{admin,user}/src/lib/contractDraftData.ts` · 2 file `.docx` template (002.02 / 002.03) · `20260813204332_AddContractSealedAt{.cs,.Designer.cs}` · `tests/.../ContractSealTests.cs`.
## 1. Trục 1 — Nhánh câm thứ 5 (switch/ternary nuốt 8/9)
(đang đo)
## 1. Trục 1nhánh câm còn sót (switch trên ContractType)
## 2. Trục 2Menu mất im lặng (map code↔int, staticMap `Hdc_*`)
(đang đo)
## 2. Trục 2menu biến mất im lặng (gotcha #50)
## 3. Trục 3`paymentTerms` có bị xoá không (3 đường ghi)
(đang đo)
## 3. Trục 3lược đồ DraftData, 3 đường ghi
## 4. Trục 4Khoá bản cứng có RĂNG thật (`SealedAt`)
(đang đo)
## 4. Trục 4khoá bản cứng có răng thật không
## 5. Trục 5Key ô trùng trong `body002_0*.ts`
(đang đo)
## 5. Trục 5key ô trùng trong body002_0*.ts
## 6. Trục 6Pre-fill khớp `slot.key` + N+1 query
(đang đo)
## 6. Trục 6 — pre-fill từ KHKK khớp key + N+1
## 7. VERDICT
(đang đo)
## 7. Bảng finding tổng hợp
(đang đo)
(chờ)