diff --git a/src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/DepartmentApprovalsConfiguration.cs b/src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/DepartmentApprovalsConfiguration.cs
index cb7e696..82ce7d4 100644
--- a/src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/DepartmentApprovalsConfiguration.cs
+++ b/src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/DepartmentApprovalsConfiguration.cs
@@ -53,13 +53,24 @@ public class PurchaseEvaluationDepartmentApprovalConfiguration
b.Property(x => x.ApproverRoleSnapshot).HasMaxLength(100);
b.Property(x => x.Comment).HasMaxLength(1000);
+ // Legacy 2-stage rows (Mig 16): UNIQUE (PEId, Phase, Dept, Stage) chỉ áp
+ // khi InnerStepId IS NULL — tránh conflict với N-stage rows nhiều InnerStep
+ // cùng dept cùng Stage=Confirm.
b.HasIndex(x => new { x.PurchaseEvaluationId, x.PhaseAtApproval, x.DepartmentId, x.Stage })
.IsUnique()
+ .HasFilter("[InnerStepId] IS NULL")
.HasDatabaseName("UX_PEDeptApprovals_PE_Phase_Dept_Stage");
+
+ // N-stage rows (Mig 18+): UNIQUE (PEId, Phase, InnerStepId) — 1 approval row
+ // per (phase × inner step) khi InnerStepId IS NOT NULL.
+ b.HasIndex(x => new { x.PurchaseEvaluationId, x.PhaseAtApproval, x.InnerStepId })
+ .IsUnique()
+ .HasFilter("[InnerStepId] IS NOT NULL")
+ .HasDatabaseName("UX_PEDeptApprovals_PE_Phase_InnerStep");
+
b.HasIndex(x => x.PurchaseEvaluationId);
b.HasIndex(x => x.DepartmentId);
b.HasIndex(x => x.ApproverUserId);
- b.HasIndex(x => x.InnerStepId); // Mig 18 — query rows by sub-step
b.HasOne(x => x.PurchaseEvaluation)
.WithMany(c => c.DepartmentApprovals)
diff --git a/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260507111624_AlterPeDeptApprovalsUniqueFilteredForInnerSteps.Designer.cs b/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260507111624_AlterPeDeptApprovalsUniqueFilteredForInnerSteps.Designer.cs
new file mode 100644
index 0000000..d27c752
--- /dev/null
+++ b/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260507111624_AlterPeDeptApprovalsUniqueFilteredForInnerSteps.Designer.cs
@@ -0,0 +1,3664 @@
+//
+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("20260507111624_AlterPeDeptApprovalsUniqueFilteredForInnerSteps")]
+ partial class AlterPeDeptApprovalsUniqueFilteredForInnerSteps
+ {
+ ///
+ 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.Budgets.Budget", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .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("Description")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("DrafterUserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("IsDeleted")
+ .HasColumnType("bit");
+
+ b.Property("MaNganSach")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("NamNganSach")
+ .HasColumnType("int");
+
+ b.Property("Phase")
+ .HasColumnType("int");
+
+ b.Property("ProjectId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("RejectedFromPhase")
+ .HasColumnType("int");
+
+ b.Property("SlaDeadline")
+ .HasColumnType("datetime2");
+
+ b.Property("SlaWarningSent")
+ .HasColumnType("bit");
+
+ b.Property("TenNganSach")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("TongNganSach")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MaNganSach")
+ .IsUnique()
+ .HasFilter("[MaNganSach] IS NOT NULL");
+
+ b.HasIndex("NamNganSach");
+
+ b.HasIndex("ProjectId");
+
+ b.HasIndex("SlaDeadline");
+
+ b.HasIndex("Phase", "IsDeleted");
+
+ b.ToTable("Budgets", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetApproval", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ApprovedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("ApproverUserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("BudgetId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Comment")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ 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("BudgetId", "ApprovedAt");
+
+ b.ToTable("BudgetApprovals", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetChangelog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Action")
+ .HasColumnType("int");
+
+ b.Property("BudgetId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ContextNote")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ 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("BudgetId", "CreatedAt");
+
+ b.HasIndex("BudgetId", "EntityType");
+
+ b.ToTable("BudgetChangelogs", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetDepartmentApproval", 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("BudgetId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Comment")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ 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("BudgetId");
+
+ b.HasIndex("DepartmentId");
+
+ b.HasIndex("BudgetId", "PhaseAtApproval", "DepartmentId", "Stage")
+ .IsUnique()
+ .HasDatabaseName("UX_BudgetDeptApprovals_Budget_Phase_Dept_Stage");
+
+ b.ToTable("BudgetDepartmentApprovals", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetDetail", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("BudgetId")
+ .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")
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("GhiChu")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("GroupCode")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("GroupName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("ItemCode")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("KhoiLuong")
+ .HasPrecision(18, 4)
+ .HasColumnType("decimal(18,4)");
+
+ b.Property("NoiDung")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ 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("BudgetId", "Order");
+
+ b.ToTable("BudgetDetails", (string)null);
+ });
+
+ modelBuilder.Entity("SolutionErp.Domain.Contracts.Contract", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("BudgetId")
+ .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("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("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("BudgetId");
+
+ 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.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