# sub-implementer-backend-0 — W1 KHKK lane BE (RELAUNCH-CẮT-GỌT, tiếp lane chết #53) > Ghi TỪNG MỤC ngay sau khi xong mục đó (lane trước chết trước khi kịp ghi). > Run: `2026-07-29-S161-khkk-w1-schema` · spec `spec-wave-w1-schema-designer-29-07-2026.md` §② 2→9. ## §0 — AUDIT mục "✅ ĐÃ XONG" (trước khi làm tiếp) | Vật | Đo | Verdict | |---|---|---| | `Domain/ContractSigningPlans/` | 8 file (7 entity + `ContractSigningPlanPhase.cs`) | PASS | | `ContractSigningPlanPhase` | `:10-14` = DangSoanThao=1 / ChoDuyet=2 / DaDuyet=3 / TraLai=98 / TuChoi=99 | PASS khớp spec `:16` | | `DossierItemKind` | `ContractSigningPlanDossierItem.cs:7-12` = MauVatLieu=1/Shopdrawing=2/Khac=99 | PASS | | `DossierItemStatus` | `ContractSigningPlanDossierItem.cs:17-23` = ChuaNop=1..TvgsBac=4 (KHÔNG 0) | PASS — vá C2 | | `ApprovalWorkflowApplicableType.ContractSigningPlan = 10` | `ApprovalWorkflow.cs:66` (append-only, sau `TravelRequest = 9`) | PASS | | Header +`DepartmentId Guid?` (C5) | `ContractSigningPlan.cs:21` | PASS | | Lines +`ContractId Guid?` (C6) | `ContractSigningPlanLine.cs:25` | PASS | | Changelog ĐỦ 4 cột (C1) | `ContractSigningPlanChangelog.cs:29,30,34,36` EntityType/EntityId/UserName/FieldChangesJson | PASS | | Configuration đã có | `ContractSigningPlanConfiguration` + `...Line` + `...DossierItem` + `...LevelOpinion` = 4/7 | đúng như brief | | UNIQUE filtered Lines | `ContractSigningPlanLineConfiguration.cs:22-24` `(PlanId,SupplierId)` + `HasFilter("[IsDeleted] = 0")` | PASS gotcha #57 | | UNIQUE LevelOpinions | `ContractSigningPlanLevelOpinionConfiguration.cs:36` `(PlanId,LevelId)` IsUnique KHÔNG filter | PASS (parity 7 twin, UPSERT) | → 0 lệch spec ⇒ KHÔNG sửa gì phần đã xong. ## MỤC 1 — 3 Configuration còn lại ✅ 3 file MỚI (khuôn = 4 config đã có + twin PE `PurchaseEvaluationConfiguration.cs:156-210`): 1. `Configurations/ContractSigningPlanAttachmentConfiguration.cs` - FileName 500 IsRequired · StoragePath 1000 IsRequired · ContentType 200 IsRequired (spec `:134-137`; RỘNG hơn PE 255/500/100 — khuôn `ProposalAttachmentConfiguration.cs:15-17`) · Note 1000 · Purpose `HasConversion()` - FK **Cascade** Plan; `ContractSigningPlanDossierItemId` chỉ `HasIndex` (KHÔNG FK — tránh MULTIPLE CASCADE PATHS) - `HasQueryFilter(!IsDeleted)` — AuditableEntity 2. `Configurations/ContractSigningPlanChangelogConfiguration.cs` - Summary **1000** (spec, PE để 500) · ContextNote 2000 · UserName 200 · FieldChangesJson `nvarchar(max)` - IX `(PlanId, CreatedAt)` + `(PlanId, EntityType)` (index twin C1) - BaseEntity ⇒ **KHÔNG** HasQueryFilter (parity PE Changelog) 3. `Configurations/ContractSigningPlanApprovalConfiguration.cs` - Comment 1000 · From/ToPhase/Decision `HasConversion()` - IX `(PlanId, ApprovedAt)` (twin PE `:168`) + IX `ApprovalWorkflowLevelId` - `ApprovalWorkflowLevelId` loose-Guid KHÔNG FK (nếu Restrict → admin xoá Level nổ 500, vì 3 site dọn dẹp chỉ biết họ `*LevelOpinions`) - BaseEntity ⇒ KHÔNG HasQueryFilter ⚠️ Cả 7 bảng con đều FK Cascade 1-hop từ Plan ⇒ 0 multiple-cascade-path. ## MỤC 2 — DbSets ×7 ✅ - `Application/Common/Interfaces/IApplicationDbContext.cs:3` +`using SolutionErp.Domain.ContractSigningPlans;` · `:144-153` block 7 DbSet + chú thích "mã KHKK qua `WorkflowAppCodeSequences` (:136) DÙNG CHUNG, KHÔNG bảng sequence riêng". - `Infrastructure/Persistence/ApplicationDbContext.cs:5` +using · `:129-136` 7 `public DbSet<...> => Set<...>()`. - Config tự nạp qua `ApplyConfigurationsFromAssembly` (`ApplicationDbContext.cs` cuối `OnModelCreating`) — 0 dòng đăng ký tay. ## MỤC 4 (làm sớm, trước build) — MenuKeys ✅ `Domain/Identity/MenuKeys.cs:179` — thêm ĐÚNG 1 dòng `KeHoachKyKet,` vào `All` (const sẵn `:41`, KHÔNG key mới). ⇒ `Program.cs` foreach `All × Actions` tự sinh 4 policy `KeHoachKyKet.{Read,Create,Update,Delete}`. ## Build sau mục 1+2+4 `dotnet build SolutionErp.slnx` → **Build succeeded. 0 Warning(s) / 0 Error(s)** (56.77s, 6 project). ## MỤC 3 — Mig 69 `AddContractSigningPlans` ✅ Lệnh: `dotnet ef migrations add AddContractSigningPlans --project src/Backend/SolutionErp.Infrastructure --startup-project src/Backend/SolutionErp.Api --output-dir Persistence/Migrations` **3-file rule (gotcha #17) — `git status` chứng:** - `?? 20260729122015_AddContractSigningPlans.cs` (22.059 B) - `?? 20260729122015_AddContractSigningPlans.Designer.cs` (264.515 B) - ` M ApplicationDbContextModelSnapshot.cs` **Nội dung đo (grep, KHÔNG đọc-mắt):** - `grep -c CreateTable` = **7** · Down = **7 DropTable** (`:350-368`) · **0** `AddColumn/DropColumn/AlterColumn/Sql/RenameColumn` ⇒ 0 ALTER bảng cũ. - UNIQUE filtered Lines `:301-306`: `(ContractSigningPlanId, SupplierId)` `unique: true, filter: "[IsDeleted] = 0"` ✅ gotcha #57 - UNIQUE LevelOpinions `:287-291`: `(ContractSigningPlanId, ApprovalWorkflowLevelId)` `unique: true` (KHÔNG filter — đúng parity twin) - UNIQUE MaKeHoach `:325-330`: filter `[MaKeHoach] IS NOT NULL` - FK: Plan→ApprovalWorkflows **Restrict** `:47` · LevelOpinions→ApprovalWorkflowLevels **Restrict** `:201` · 6 con→Plan **Cascade** (`:76,108,139,171,207,238`) - maxLength khớp spec `:134-137` 100%: MaKeHoach 50 · GhiChu/ContextNote 2000 · Name 500 · Note 1000 · TvgsName 200 · Summary 1000 · SignedByFullName 200 NOT NULL · FileName 500 / StoragePath 1000 / ContentType 200 · Comment(LevelOpinion) 2000 / Comment(Approval) 1000 · HoSoLink 1000. Chỉ `FieldChangesJson` = `nvarchar(max)` `:124` (cố ý, JSON tự do). **Apply 2 DB:** - `migrations list --connection …SolutionErp_Dev` TRƯỚC apply: chỉ **1 Pending** = Mig 69 ⇒ **0 drift** (khác S133 phải catch-up). - Dev: `Applying migration '20260729122015_AddContractSigningPlans'. Done.` - Design (default factory): `Applying migration '20260729122015_AddContractSigningPlans'. Done.` - `sqlcmd` Dev: `sys.tables WHERE is_ms_shipped=0` = **96** (89→96, +7) · `LIKE 'ContractSigningPlan%'` = **7**.