--- 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ó 21 migration sẵn (Init → RefactorWorkflowToFlatModel). Snapshot + Designer + Migration 3-file rule bắt buộc commit đủ. when-to-use: - "thêm migration" - "EF Core migration" - "dotnet ef migrations add" - "revert migration" - "schema DB update" - "DbContext change" - "snapshot lỗi" - "DesignTimeDbContextFactory" --- # EF Core Migration — SOLUTION_ERP > **Context:** .NET 10 + EF Core 10 + SQL Server. DbContext: `ApplicationDbContext` ở `Infrastructure/Persistence/`. Startup: `SolutionErp.Api`. ## Migration history (21 migration hiện có) | # | Name | Tables added / changed | |---|---|---| | 1 | `Init` | 7 Identity tables (AspNetUsers/Roles/UserRoles/...) | | 2 | `AddMasterData` | Suppliers, Projects, Departments | | 3 | `AddPermissions` | MenuItems, Permissions | | 4 | `AddForms` | ContractTemplates, ContractClauses | | 5 | `AddContractsWorkflow` | Contracts, ContractApprovals, ContractComments, ContractAttachments, ContractCodeSequences | | 6 | `AddNotifications` | Notifications | | 7 | `AddWorkflowTypeAssignments` | WorkflowTypeAssignments (legacy admin override) | | 8 | `AddVersionedWorkflows` | WorkflowDefinitions, WorkflowSteps, WorkflowStepApprovers + `Contracts.WorkflowDefinitionId` FK | | 9 | `AddContractDetailsAndChangelog` | 7 per-type Details (ThauPhu/GiaoKhoan/NhaCungCap/DichVu/MuaBan/NguyenTacNcc/NguyenTacDv) + ContractChangelogs | | 10 | `AddMasterCatalogs` | UnitsOfMeasure, MaterialItems, ServiceItems, WorkItems | | 11 | `AddRoleShortNameAndUserDepartment` | +Role.ShortName + User.DepartmentId/Position (cột thêm, không bảng mới) | | **12** | **`AddPurchaseEvaluations`** | **10 bảng module Duyệt NCC: PurchaseEvaluations + Suppliers + Details + Quotes + Approvals + Changelogs + Attachments + WorkflowDefinitions/Steps/StepApprovers** | | **13** | **`AddPurchaseEvaluationCodeSequences`** | **1 bảng `PurchaseEvaluationCodeSequences` (Prefix PK, LastSeq) — atomic sequence cho MaPhieu format `PE/{YYYY}/{A\|B}/{Seq:D3}`** | | **14** | **`AddBudgets`** | **4 bảng module Ngân sách: Budgets + BudgetDetails + BudgetApprovals + BudgetChangelogs. + nullable FK index `Contracts.BudgetId` & `PurchaseEvaluations.BudgetId`** | | **15** | **`AddPurchaseEvaluationDepartmentOpinions`** | **1 bảng `PurchaseEvaluationDepartmentOpinions` (UNIQUE PEId+Kind, max 1 row mỗi PeDepartmentKind per phiếu — Phê duyệt/Ccm/MuaHang/SmPm)** | | **16** | **`AddTwoStageDeptApprovalAndSmartReject`** | **3 bảng `Contract/PurchaseEvaluation/Budget DepartmentApprovals` (UNIQUE TargetId+Phase+Dept+Stage cho 2-stage NV.Review → TPB.Confirm per phòng × phase) + 4 ALTER (`Users.CanBypassReview` bit cho NV bypass + 3 `RejectedFromPhase` int cho smart reject jump-back). Phase 9 — đóng bug "NV duyệt được hết phase" anh Kiệt (FDC) báo. Logic 2-stage trong PurchaseEvaluationWorkflowService chỉ áp PE; HĐ + Budget defer.** | | **17** | **`AddManualBudgetFieldsToPeAndContract`** | **4 ALTER (PE + HĐ × `BudgetManualName` nvarchar(200) + `BudgetManualAmount` decimal(18,2)) — manual budget fallback khi user không link Budget entity approved. KHÔNG XOR với BudgetId, cả 2 cùng null OK. Carry-forward `pe.BudgetManualName/Amount → contract` ở `CreateContractFromEvaluation`. Phase 9 — Session 11 (2026-05-07).** | | **18** | **`AddPeWorkflowInnerStepsAndPositionLevel`** | **N-stage workflow PE — 1 CREATE TABLE `PurchaseEvaluationWorkflowStepInnerSteps` (Order, DepartmentId, PositionLevel, Name, SlaDays, IsRequired) + 2 ALTER (`Users.PositionLevel int?` 1=NV/2=PP/3=TP + `PEDeptApproval.InnerStepId Guid?`) + 3 IX + FK Cascade Step / Restrict Dept+InnerStep. Phase 9+ — Session 12 (2026-05-07).** | | **19** | **`AlterPeDeptApprovalsUniqueFilteredForInnerSteps`** | **Filtered unique split: drop UNIQUE cũ Mig 16 → 2 filtered: legacy `WHERE InnerStepId IS NULL` (Stage Review/Confirm) + N-stage `WHERE InnerStepId IS NOT NULL` (per inner step). Tránh conflict 2 inner step cùng dept Stage=Confirm. Session 12.** | | **20** | **`AddContractWorkflowInnerStepsAndAlterDeptApprovalUnique`** | **N-stage workflow Contract mirror PE Mig 18+19 — GỘP 1 migration: CREATE TABLE `WorkflowStepInnerSteps` + ALTER `ContractDeptApproval.InnerStepId` + DropIndex old + Recreate filtered legacy/N-stage + 3 IX + FK. Session 13 (2026-05-07).** | | **21** | **`RefactorWorkflowToFlatModel`** | **🎯 DRASTIC REFACTOR (Session 16, 2026-05-08): bỏ phase enum legacy, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking. Workflow flat (Phòng × Cấp + Approvers). 4 ALTER (PE/Contract +CurrentWorkflowStepIndex/RejectedAtStepIndex) + 2 ALTER (WorkflowStep +DepartmentId/PositionLevel PE+Contract) + DROP TABLE × 2 (PE+Contract WorkflowStepInnerSteps Mig 18+20) + DROP COLUMN InnerStepId × 2 + DROP filtered indexes Mig 19/20 + RESTORE simple unique non-filtered × 2.** | Total: **55 bảng** dbo + `__EFMigrationsHistory` (Mig 21 drop 2 InnerStep tables, restore Mig 16 simple unique). Xem `docs/database/schema-diagram.md` ERD đầy đủ. ## N-stage workflow pattern (Mig 18-20 — Session 12-13) Architecture decision đáng ghi cho session sau: **Pattern:** Mỗi WorkflowStep cha (= 1 phase) cấu hình động chuỗi InnerSteps con theo Department × PositionLevel với Order sequential. Approver cần khớp DeptId + PositionLevel + Order tiếp theo chưa duyệt. CanBypassReview cho user level cao hơn skip cấp dưới cùng dept. **Per-module migration** (PE first, mirror sang Contract sau): - PE: 2 migration tách (Mig 18 schema + Mig 19 alter index) — vì index issue phát hiện sau khi schema đã commit - Contract: 1 migration GỘP (Mig 20) — gộp CREATE TABLE + ALTER + DropIndex + Recreate filtered. Cleaner cho mirror. **Filtered unique trick:** legacy 2-stage rows (Mig 16, Stage=Review/Confirm) + N-stage rows (per InnerStep) cùng table `*DepartmentApprovals` → cần unique split: - `UX_*_Phase_Dept_Stage WHERE InnerStepId IS NULL` (legacy, prevent duplicate Review/Confirm cùng phase × dept) - `UX_*_Phase_InnerStep WHERE InnerStepId IS NOT NULL` (N-stage, prevent duplicate per inner step) **Backward compat 100%:** workflow no InnerSteps configured → service fallback legacy 2-stage logic Mig 16. Data legacy InnerStepId=null vẫn enforce unique cũ qua filtered index. **Budget defer:** Budget chưa có versioned WorkflowDefinition entity (hardcoded `BudgetPolicy.Default`). Để mirror N-stage Budget cần migration `AddBudgetVersionedWorkflow` trước (4 bảng + ALTER `Budget.WorkflowDefinitionId?`). Defer cho user quyết riêng. **Phase 7 pending:** - `AddPePaymentTermFields` — tách `PurchaseEvaluations.PaymentTerms` JSON thành 6 column riêng (Session 11+++++ đã thay UI Workspace `