diff --git a/docs/gotchas.md b/docs/gotchas.md
index 396a5fc..bc6f062 100644
--- a/docs/gotchas.md
+++ b/docs/gotchas.md
@@ -1322,6 +1322,14 @@ for h in resp.points: # ← .points không phải iterable trực tiếp
**Fix (root-cause, Mig 60 `8037fa0`):** thêm field RUNTIME `EndedByLevelFinalize` (set true CHỈ ở nhánh level-finalize service) → `EndsBeforeCeo` cho phiếu DaDuyet đọc runtime thay config (ChoDuyet giữ heads-up config); FE gate làm-mờ theo `endsBeforeCeo`; backfill dò marker "Duyệt KẾT THÚC tại". prod-verified Run #482 (PE-detail endsBeforeCeo=False live). **Quy tắc:** flag hiển-thị/trạng-thái = "instance NÀY ĐÃ làm-gì" (RUNTIME) — KHÔNG "workflow CÓ THỂ làm-gì" (CONFIG); config-flag chỉ đúng khi instance không-bao-giờ-lệch-config → đổi default opt-in/opt-out làm lệch phổ-biến → soi lại mọi display suy-từ-config. Liên-quan `feedback_cardinality_change_grep_consumers` · `feedback_root_cause_over_symptom`.
+### 79. Backfill marker soi NHẦM bảng — transition-marker ở Changelogs KHÔNG phải Approvals (Session 97)
+
+**Triệu chứng:** Mig 60 backfill `EndedByLevelFinalize` (nhận diện phiếu finalize-sớm qua marker "[Duyệt KẾT THÚC tại...]") match **0 dòng** trên prod → phiếu finalize pre-Mig60 (PE/2026/A/005, A/008) giữ `=0` → hiển thị SAI "Đã được CEO duyệt" thay vì "Kết thúc tại CCM".
+
+**Cơ chế:** 2 bảng lịch-sử RIÊNG trong PE workflow: (1) `PurchaseEvaluationApprovals` = record per-approver (thêm ở main-flow `ApproveV2Async:745`, comment "[Bước X — Cấp Y] {user}"); (2) `PurchaseEvaluationChangelogs` = transition-log (`LogTransitionAsync:1073` "chỉ log Changelog ở đây", ContextNote = marker "[Duyệt KẾT THÚC tại...không trình CEO]"). Marker finalize CHỈ nằm ở Changelogs. Backfill query `Approvals.Comment LIKE '%KẾT THÚC%'` → sai bảng → 0 match. (Bonus: approval-record `ToPhase=evaluation.Phase` chụp TRƯỚC khi terminal-branch đổi Phase → MỌI approval-record `ToPhase=ChoDuyet`; terminal chỉ ghi Changelog `PhaseAtChange=DaDuyet` — đừng tìm `ToPhase=7` ở Approvals.)
+
+**Fix (Mig 61 `BackfillEndedByLevelFinalizeFromChangelog`):** query ĐÚNG `PurchaseEvaluationChangelogs.ContextNote LIKE N'%Duyệt KẾT THÚC tại%'` + `PhaseAtChange=7`. **Quy tắc:** trước khi viết backfill/query dựa "transition-marker", XÁC ĐỊNH bảng nào thực-sự lưu marker (đọc code writer) — PE có 2 bảng lịch sử tách vai (Approvals=ai-duyệt · Changelogs=chuyển-phase+marker). Verify marker-count trên prod TRƯỚC khi tin backfill. Liên-quan #78 · #64 (data-migrate Sql prod).
+
---
## Checklist debug bug mới
diff --git a/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260701142752_BackfillEndedByLevelFinalizeFromChangelog.Designer.cs b/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260701142752_BackfillEndedByLevelFinalizeFromChangelog.Designer.cs
new file mode 100644
index 0000000..c559f1f
--- /dev/null
+++ b/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260701142752_BackfillEndedByLevelFinalizeFromChangelog.Designer.cs
@@ -0,0 +1,6277 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using SolutionErp.Infrastructure.Persistence;
+
+#nullable disable
+
+namespace SolutionErp.Infrastructure.Persistence.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20260701142752_BackfillEndedByLevelFinalizeFromChangelog")]
+ partial class BackfillEndedByLevelFinalizeFromChangelog
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.6")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RoleId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("RoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("UserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderKey")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("UserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("RoleId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("UserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Value")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("UserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflow", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ActivatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("ApplicableType")
+ .HasColumnType("int");
+
+ b.Property("CeoApprovalThreshold")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Description")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("IsActive")
+ .HasColumnType("bit");
+
+ b.Property("IsUserSelectable")
+ .HasColumnType("bit");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Version")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApplicableType", "IsActive");
+
+ b.HasIndex("Code", "Version")
+ .IsUnique();
+
+ b.ToTable("ApprovalWorkflows", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflowLevel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("AllowApproverEditBudget")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false);
+
+ b.Property("AllowApproverEditDetails")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false);
+
+ b.Property("AllowApproverFinalize")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false);
+
+ b.Property("AllowApproverSkipToFinal")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false);
+
+ b.Property("AllowReturnOneLevel")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false);
+
+ b.Property("AllowReturnOneStep")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false);
+
+ b.Property("AllowReturnToAssignee")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false);
+
+ b.Property("AllowReturnToDrafter")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(true);
+
+ b.Property("ApprovalWorkflowStepId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ApproverUserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Name")
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApproverUserId");
+
+ b.HasIndex("ApprovalWorkflowStepId", "Order");
+
+ b.ToTable("ApprovalWorkflowLevels", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflowStep", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ApprovalWorkflowId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DepartmentId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DepartmentId");
+
+ b.HasIndex("ApprovalWorkflowId", "Order");
+
+ b.ToTable("ApprovalWorkflowSteps", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.Contract", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ApprovalWorkflowId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("BudgetManualAmount")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("BudgetManualName")
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("BypassProcurementAndCCM")
+ .HasColumnType("bit");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CurrentApprovalLevelOrder")
+ .HasColumnType("int");
+
+ b.Property("CurrentWorkflowStepIndex")
+ .HasColumnType("int");
+
+ b.Property("DeletedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("DeletedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DepartmentId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DraftData")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DrafterUserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("GiaTri")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("MaHopDong")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("NoiDung")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("Phase")
+ .HasColumnType("int");
+
+ b.Property("ProjectId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("RejectedAtStepIndex")
+ .HasColumnType("int");
+
+ b.Property("RejectedFromPhase")
+ .HasColumnType("int");
+
+ b.Property("SlaDeadline")
+ .HasColumnType("datetime2");
+
+ b.Property("SlaWarningSent")
+ .HasColumnType("bit");
+
+ b.Property("SupplierId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("TemplateId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("TenHopDong")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("Type")
+ .HasColumnType("int");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("WorkflowDefinitionId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApprovalWorkflowId");
+
+ b.HasIndex("MaHopDong")
+ .IsUnique()
+ .HasFilter("[MaHopDong] IS NOT NULL");
+
+ b.HasIndex("ProjectId");
+
+ b.HasIndex("SlaDeadline");
+
+ b.HasIndex("SupplierId");
+
+ b.HasIndex("Phase", "IsDeleted");
+
+ b.ToTable("Contracts", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractApproval", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ApprovedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("ApproverUserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Comment")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("ContractId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Decision")
+ .HasColumnType("int");
+
+ b.Property("FromPhase")
+ .HasColumnType("int");
+
+ b.Property("ToPhase")
+ .HasColumnType("int");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ContractId", "ApprovedAt");
+
+ b.ToTable("ContractApprovals", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractAttachment", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ContentType")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("ContractId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("FileName")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("nvarchar(255)");
+
+ b.Property("FileSize")
+ .HasColumnType("bigint");
+
+ b.Property("Note")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("Purpose")
+ .HasColumnType("int");
+
+ b.Property("StoragePath")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ContractId");
+
+ b.ToTable("ContractAttachments", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractChangelog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Action")
+ .HasColumnType("int");
+
+ b.Property("ContextNote")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("ContractId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("EntityId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("EntityType")
+ .HasColumnType("int");
+
+ b.Property("FieldChangesJson")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhaseAtChange")
+ .HasColumnType("int");
+
+ b.Property("Summary")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("UserName")
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ContractId", "CreatedAt");
+
+ b.HasIndex("ContractId", "EntityType");
+
+ b.ToTable("ContractChangelogs", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractCodeSequence", b =>
+ {
+ b.Property("Prefix")
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("LastSeq")
+ .HasColumnType("int");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.HasKey("Prefix");
+
+ b.ToTable("ContractCodeSequences", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractComment", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("ContractId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Phase")
+ .HasColumnType("int");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ContractId", "CreatedAt");
+
+ b.ToTable("ContractComments", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractDepartmentApproval", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ApprovedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("ApproverRoleSnapshot")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("ApproverUserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Comment")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("ContractId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DeletedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("DeletedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DepartmentId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("IsBypassed")
+ .HasColumnType("bit");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("PhaseAtApproval")
+ .HasColumnType("int");
+
+ b.Property("Stage")
+ .HasColumnType("int");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApproverUserId");
+
+ b.HasIndex("ContractId");
+
+ b.HasIndex("DepartmentId");
+
+ b.HasIndex("ContractId", "PhaseAtApproval", "DepartmentId", "Stage")
+ .IsUnique()
+ .HasDatabaseName("UX_ContractDeptApprovals_Contract_Phase_Dept_Stage");
+
+ b.ToTable("ContractDepartmentApprovals", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractLevelOpinion", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ApprovalWorkflowLevelId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Comment")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("ContractId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DeletedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("DeletedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("SignedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("SignedByFullName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("SignedByUserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApprovalWorkflowLevelId");
+
+ b.HasIndex("ContractId", "ApprovalWorkflowLevelId")
+ .IsUnique();
+
+ b.ToTable("ContractLevelOpinions", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.DichVuDetail", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ContractId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DenNgay")
+ .HasColumnType("datetime2");
+
+ b.Property("DonGia")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("DonViTinh")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("GhiChu")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("MaDichVu")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("MoTa")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("TenDichVu")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("ThanhTien")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("ThoiGian")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("TuNgay")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ContractId", "Order");
+
+ b.ToTable("DichVuDetails", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.GiaoKhoanDetail", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ContractId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DonGia")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("DonViTinh")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("GhiChu")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("KhoiLuong")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("MaCongViec")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("TenCongViec")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("ThanhTien")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("ThoiGianHoanThanh")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("YeuCauKyThuat")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ContractId", "Order");
+
+ b.ToTable("GiaoKhoanDetails", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.MuaBanDetail", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ContractId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DonGia")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("DonViTinh")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("GhiChu")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("MaSP")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("MoTa")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("SoLuong")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("TenSP")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("ThanhTien")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("ThueVAT")
+ .HasPrecision(5, 2)
+ .HasColumnType("decimal(5,2)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("XuatXu")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ContractId", "Order");
+
+ b.ToTable("MuaBanDetails", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.NguyenTacDvDetail", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ContractId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DonGiaToiDa")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("DonGiaToiThieu")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("DonViTinh")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("GhiChu")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("LoaiDichVu")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("PhamViDichVu")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("SLA")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("TenDichVu")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("ThanhTien")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ContractId", "Order");
+
+ b.ToTable("NguyenTacDvDetails", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.NguyenTacNccDetail", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ContractId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DieuKienGiaoHang")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("DieuKienThanhToan")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("DonGiaToiDa")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("DonGiaToiThieu")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("DonViTinh")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("GhiChu")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property