7.1 KiB
K2-BE: IN-PROGRESS — build 0W/0E? chưa đo · test chưa chạy
sub-implbe-k2 — K2 BACKEND (S164, wave 2/8, SP-002 phiếu mang hạng-mục + nhóm)
Luật đan-xen (bài K1): làm mục nào GHI NGAY mục đó. Dòng-1 file = verdict, cập nhật cuối. Nguồn: spec-cum1 (12 vá) + spec-tổng-quát (QĐ-1/2/9 + OG-1/7) + nền
sub-invest-fable-b2-cum1.md§K2.
§0 Tiền đề — git status TRƯỚC mọi Write (bài K1 sự-cố suýt-đè)
git status --porcelain@start:sub-implbe-k2.mdKHÔNG tồn tại (run-folder có k1/implfe/test/reviewer/cicd, không có k2) ⇒ Write mới AN TOÀN.- Baseline nhận từ lead: Mig 70 land (
50e6d8c), suite 598/0 (45D+553I), MenuKeys.All=56, policies=224. - Nhánh:
main.
§0-bis Anchor re-verify 5/5 (lead giao @S165 — ĐO LẠI, không tin sổ)
| Anchor lead giao | Đo thật | Khớp |
|---|---|---|
Features.cs:342-347 rào hasLivePlan |
:342 var hasLivePlan = await db.ContractSigningPlans → :347 message |
✅ |
Features.cs:622-626 ListContractSigningPlansQuery |
:622 public record ListContractSigningPlansQuery( → :626 bool PendingMe = false) : PagedRequest |
✅ |
Features.cs:1201-1202 picker ApprovedPeAwaitingPlan |
:1201-1202 && !db.ContractSigningPlans.Any(pl => ... pl.Phase != TuChoi) |
✅ |
Line.cs:17-20 PeReferenceAmount snapshot + :25 ContractId |
comment :17-19, prop :20; :25 public Guid? ContractId |
✅ |
WorkflowService ApplyApprovedValuesOnFinalize ??= ~:301-305 |
:301 signature, :303 foreach, :304 line.ApprovedAmount ??= line.ProposedAmount |
✅ |
Bonus đo thêm (không ai giao, cần cho mục e): KHÔNG tồn tại đường ghi Line nào ngoài auto-gen :389
(grep -rn "\.Lines" src/Backend = 10 hit, chỉ 1 hit ghi). Hệ quả nặng → xem §e.
§a Mig 71 AddKhkkApprovalGroupCatalogAndFinalizeRuntime — DONE
File 3/3 (gotcha #17):
src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime.cs…20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime.Designer.cs…/ApplicationDbContextModelSnapshot.cs(M)
6 item trong Up() — 0 RenameColumn, 0 CreateTable:
| # | Lệnh | Dòng |
|---|---|---|
| 1 | AddColumn<int> ApprovalGroup (ContractSigningPlans) + backfill |
:52-58 + :69 |
| 2 | AddColumn<bool> EndedByLevelFinalize default false |
:71-76 |
| 3 | AddColumn<Guid> CatalogEntryId nullable (Lines) — loose-Guid, 0 FK |
:78-82 |
| 4 | AddColumn<string> TenHangMuc nvarchar(500) nullable |
:84-89 |
| 5 | DropIndex unique cũ …_ContractSigningPlanId_SupplierId → CreateIndex UNIQUE (Plan,Supplier,CatalogEntry) filter [IsDeleted] = 0 |
:44-46 + :101-106 |
| 6 | +2 IX tra cứu: (ApprovalGroup, Phase) header · CatalogEntryId dòng |
:91-99 |
Backfill (task yêu cầu Sql UPDATE trong Up): :69 migrationBuilder.Sql("UPDATE [ContractSigningPlans] SET [ApprovalGroup] = 1;")
— khuôn Mig 64 (Sql() trần, KHÔNG provider-guard, khuôn Mig 61/65/67). VÔ ĐIỀU KIỆN (bỏ WHERE ApprovalGroup = 0)
CÓ CHỦ ĐÍCH: rowcount phải == số phiếu cũ; viết WHERE thì backfill hụt trông y hệt backfill thành công (bài Goodhart/vắng-mặt).
Vá-9 khai ĐÚNG mắt xích (comment :36-51 trong file mig): unique cũ CHẾT trong chính Mig 71. Index MỚI vẫn chặn
cặp NULL-trùng (SQL Server: NULL == NULL trong unique index) nhưng chặn bằng lỗi 2601 lúc SaveChanges = 500 khó hiểu,
KHÔNG phải bằng nghiệp vụ. Mắt xích THẬT = (a) validator đường-gán bắt buộc CatalogEntryId + (b) SUBMIT-GUARD.
Dữ liệu cũ an toàn KHÔNG cần backfill dòng: unique cũ đã bảo đảm ≤1 dòng/cặp ⇒ tối đa 1 NULL/cặp.
CỐ Ý không thêm AND [CatalogEntryId] IS NOT NULL vào filter (= thả trôi vô hạn dup).
Vá-7 item-6 ghost-window khai trong comment mig :19-24 + entity ContractSigningPlan.cs: K2 ĐẺ cột
EndedByLevelFinalize, K3 TIÊU THỤ; từ nay tới K3 land 0 site ghi ⇒ luôn false, và đó ĐÚNG hiện trạng
(service chỉ có 1 nhánh set DaDuyet, là đường thường) — không phải dữ liệu hỏng.
Apply 2 DB (feedback_designtime_runtime_db):
- Design:
Applying migration '20260731085624_…'→ OK - Dev: drift committed-chưa-apply — Dev đang ở Mig 69, thiếu Mig 70 (K1 land nhưng chưa apply local)
⇒ catch-up 2 mig 1 lượt:
Applying '20260731072441_AddContractCatalogEntries'+Applying '20260731085624_…'→ OK
Đo sau apply (sqlcmd, cả 2 DB giống hệt):
tables=97 · lastmig=20260731085624_AddKhkk… · cols=ApprovalGroup,EndedByLevelFinalize
linecols=CatalogEntryId,TenHangMuc
uniqueIX=PK_ContractSigningPlanLines | filter=(none)
uniqueIX=IX_ContractSigningPlanLines_ContractSigningPlanId_SupplierId_CatalogEntryId | filter=([IsDeleted]=(0))
⇒ unique cũ (2 cột) KHÔNG còn trong sys.indexes = index swap có thật, không phải thêm chồng.
Bảng 96→97 là do Mig 70 (K1) vừa catch-up, KHÔNG phải Mig 71 (Mig 71 = 0 CreateTable).
Σ tiền TRƯỚC == SAU (chứng 2 tầng, khai ĐỘ MẠNH thật):
- Tầng CẤU TRÚC (mạnh):
grep -c "PeReferenceAmount\|ProposedAmount\|ApprovedAmount"trong file mig = 0 ⇒ bất-biến đúng theo cấu trúc, không phụ thuộc phép đo. - Tầng PHÉP ĐO (yếu, khai thẳng): Dev & Design đều 0 dòng KHKK ⇒
SUM(PeReferenceAmount)= NULL/tập RỖNG cả trước lẫn sau. Đây là control rỗng, KHÔNG phải bằng chứng có răng — răng thật nằm ở phép đo prod (chưa chạy được ở lane này). Ghi đúng độ mạnh thay vì dán nhãn "PASS" (bài "trích rỗng đọc thành sạch"). plans=0⇒ backfill chạy trên 0 row local; acceptanceCOUNT == Σ 8 folderchỉ chứng được trên prod.
§b Entity + EF Configuration khớp mig — DONE
src/Backend/SolutionErp.Domain/ContractSigningPlans/ContractSigningPlan.cs—+ApprovalGroup int = 1(khai "K2 là CHỦ DUY NHẤT, K3 chỉ đọc" — QĐ-3/spec §②-3) ·+EndedByLevelFinalize bool(ghost-window + né #78).src/Backend/SolutionErp.Domain/ContractSigningPlans/ContractSigningPlanLine.cs—+CatalogEntryId Guid?(loose-Guid, khuônPE.WorkItemIdMig 49 + chínhSupplierIdcùng file) ·+TenHangMuc string?(denorm, khuônSignedByFullNameMig 26).…/Configurations/ContractSigningPlanConfiguration.cs—+HasIndex(ApprovalGroup, Phase). 🔴 CỐ Ý KHÔNG dựng unique (PeId, ApprovalGroup): vị ngữ của rào làPhase != TuChoi(phiếu từ chối KHÔNG chiếm chỗ) — unique DB không diễn tả được vế đó, dựng vào = chặn nhầm đường lập lại hợp lệ. Rào sống ở handler.…/Configurations/ContractSigningPlanLineConfiguration.cs—TenHangMuc.HasMaxLength(500)(==ContractCatalogEntry.TenVi, EF = source of truth cho validator, bài S35) · index swap 2→3 cột ·+HasIndex(CatalogEntryId).
(mục c-h ghi tiếp bên dưới)