[CLAUDE] Domain+Infra: Migration 16 — 2-stage dept approval + smart reject schema

Chunk A của feature 2-stage department approval (Phase 9). 3 ràng buộc gộp
1 migration để rollback atomic.

Schema changes:

1. Smart reject (3 ALTER):
   - Contracts.RejectedFromPhase int NULL
   - PurchaseEvaluations.RejectedFromPhase int NULL
   - Budgets.RejectedFromPhase int NULL
   Lưu phase nguồn khi reject để Drafter trình lại quay về đúng phase
   (skip phase trung gian đã duyệt) thay vì đi tuần tự từ DangSoanThao.

2. Bypass per-user (1 ALTER):
   - Users.CanBypassReview bit NOT NULL DEFAULT 0
   Khi true → NV được duyệt thay TPB (skip Stage Review, đẩy thẳng
   Stage Confirm). Audit qua DepartmentApproval.IsBypassed=true.

3. 2-stage dept approval (3 CREATE TABLE):
   - ContractDepartmentApprovals
   - PurchaseEvaluationDepartmentApprovals
   - BudgetDepartmentApprovals

   Schema (chung):
   - Id, *Id FK Cascade, PhaseAtApproval int, DepartmentId FK
   - Stage enum (1=Review NV, 2=Confirm TPB)
   - ApproverUserId, ApproverRoleSnapshot, Comment, ApprovedAt
   - IsBypassed bit (mark NV bypass)
   - AuditableEntity (CreatedAt/By/UpdatedAt/By/IsDeleted/...)

   Indexes:
   - UNIQUE (TargetId, PhaseAtApproval, DepartmentId, Stage)
   - Single: TargetId, DepartmentId, ApproverUserId

Files:
- Domain: 4 entity update (Contract/PE/Budget/User add field) + 1 enum mới
  ApprovalStage + 3 entity DepartmentApproval mới
- Infrastructure: 3 EntityConfiguration update + 1 file mới
  DepartmentApprovalsConfiguration với 3 config classes
- IApplicationDbContext: thêm 3 DbSet
- ApplicationDbContext: thêm 3 DbSet
- Migration 16: 3 file (.cs + Designer.cs + Snapshot.cs) — 3-file rule

Verify:
- Build pass (2 warning DocxRenderer cũ, không liên quan)
- 77 unit test pass (54 Domain + 23 Infra) — Domain policy chưa update,
  test pass nguyên không regression

Note: KHÔNG khác PurchaseEvaluationDepartmentOpinion (Migration 15) —
Opinion là sign-off block "Ý kiến 4 phòng ban" trên header phiếu.
DepartmentApproval mới là 2-stage approval workflow per phase.

Tổng sau Migration 16: 55 bảng (52+3), 16 migration. Chunk B-E sẽ implement
Application + FE + Tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-04 12:11:53 +07:00
parent dfb43fcbc6
commit 5c200978cb
17 changed files with 4266 additions and 0 deletions

View File

@ -35,6 +35,7 @@ public class ApplicationDbContext
public DbSet<ContractAttachment> ContractAttachments => Set<ContractAttachment>();
public DbSet<ContractCodeSequence> ContractCodeSequences => Set<ContractCodeSequence>();
public DbSet<ContractChangelog> ContractChangelogs => Set<ContractChangelog>();
public DbSet<ContractDepartmentApproval> ContractDepartmentApprovals => Set<ContractDepartmentApproval>();
public DbSet<Notification> Notifications => Set<Notification>();
public DbSet<WorkflowTypeAssignment> WorkflowTypeAssignments => Set<WorkflowTypeAssignment>();
public DbSet<WorkflowDefinition> WorkflowDefinitions => Set<WorkflowDefinition>();
@ -60,12 +61,14 @@ public class ApplicationDbContext
public DbSet<PurchaseEvaluationWorkflowStepApprover> PurchaseEvaluationWorkflowStepApprovers => Set<PurchaseEvaluationWorkflowStepApprover>();
public DbSet<PurchaseEvaluationCodeSequence> PurchaseEvaluationCodeSequences => Set<PurchaseEvaluationCodeSequence>();
public DbSet<PurchaseEvaluationDepartmentOpinion> PurchaseEvaluationDepartmentOpinions => Set<PurchaseEvaluationDepartmentOpinion>();
public DbSet<PurchaseEvaluationDepartmentApproval> PurchaseEvaluationDepartmentApprovals => Set<PurchaseEvaluationDepartmentApproval>();
// Module Ngân sách (Phase 7) — 4 bảng: Budget header + Details + Approvals + Changelogs.
public DbSet<Budget> Budgets => Set<Budget>();
public DbSet<BudgetDetail> BudgetDetails => Set<BudgetDetail>();
public DbSet<BudgetApproval> BudgetApprovals => Set<BudgetApproval>();
public DbSet<BudgetChangelog> BudgetChangelogs => Set<BudgetChangelog>();
public DbSet<BudgetDepartmentApproval> BudgetDepartmentApprovals => Set<BudgetDepartmentApproval>();
protected override void OnModelCreating(ModelBuilder builder)
{

View File

@ -15,6 +15,7 @@ public class BudgetConfiguration : IEntityTypeConfiguration<Budget>
b.Property(x => x.TenNganSach).HasMaxLength(500).IsRequired();
b.Property(x => x.Description).HasMaxLength(2000);
b.Property(x => x.Phase).HasConversion<int>();
b.Property(x => x.RejectedFromPhase).HasConversion<int?>();
b.Property(x => x.TongNganSach).HasPrecision(18, 2);
b.HasIndex(x => x.MaNganSach).IsUnique().HasFilter("[MaNganSach] IS NOT NULL");

View File

@ -14,6 +14,7 @@ public class ContractConfiguration : IEntityTypeConfiguration<Contract>
b.Property(x => x.MaHopDong).HasMaxLength(100);
b.Property(x => x.Type).HasConversion<int>();
b.Property(x => x.Phase).HasConversion<int>();
b.Property(x => x.RejectedFromPhase).HasConversion<int?>();
b.Property(x => x.GiaTri).HasPrecision(18, 2);
b.Property(x => x.TenHopDong).HasMaxLength(500);
b.Property(x => x.NoiDung).HasMaxLength(2000);

View File

@ -0,0 +1,94 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using SolutionErp.Domain.Budgets;
using SolutionErp.Domain.Contracts;
using SolutionErp.Domain.PurchaseEvaluations;
namespace SolutionErp.Infrastructure.Persistence.Configurations;
// 3 bảng *DepartmentApprovals (Phase 9 — Migration 16) — 2-stage approval per
// phase × phòng ban cho HĐ / PE / Budget.
//
// UNIQUE (TargetId, PhaseAtApproval, DepartmentId, Stage) — 1 phase × 1 phòng
// × 1 stage = 1 row duy nhất. UPDATE in-place khi user đổi ý → audit qua
// *Changelog.
//
// FK Cascade theo target (xóa HĐ/PE/Budget → xóa approval rows).
public class ContractDepartmentApprovalConfiguration
: IEntityTypeConfiguration<ContractDepartmentApproval>
{
public void Configure(EntityTypeBuilder<ContractDepartmentApproval> b)
{
b.ToTable("ContractDepartmentApprovals");
b.HasKey(x => x.Id);
b.Property(x => x.Stage).HasConversion<int>();
b.Property(x => x.ApproverRoleSnapshot).HasMaxLength(100);
b.Property(x => x.Comment).HasMaxLength(1000);
b.HasIndex(x => new { x.ContractId, x.PhaseAtApproval, x.DepartmentId, x.Stage })
.IsUnique()
.HasDatabaseName("UX_ContractDeptApprovals_Contract_Phase_Dept_Stage");
b.HasIndex(x => x.ContractId);
b.HasIndex(x => x.DepartmentId);
b.HasIndex(x => x.ApproverUserId);
b.HasOne(x => x.Contract)
.WithMany(c => c.DepartmentApprovals)
.HasForeignKey(x => x.ContractId)
.OnDelete(DeleteBehavior.Cascade);
}
}
public class PurchaseEvaluationDepartmentApprovalConfiguration
: IEntityTypeConfiguration<PurchaseEvaluationDepartmentApproval>
{
public void Configure(EntityTypeBuilder<PurchaseEvaluationDepartmentApproval> b)
{
b.ToTable("PurchaseEvaluationDepartmentApprovals");
b.HasKey(x => x.Id);
b.Property(x => x.Stage).HasConversion<int>();
b.Property(x => x.ApproverRoleSnapshot).HasMaxLength(100);
b.Property(x => x.Comment).HasMaxLength(1000);
b.HasIndex(x => new { x.PurchaseEvaluationId, x.PhaseAtApproval, x.DepartmentId, x.Stage })
.IsUnique()
.HasDatabaseName("UX_PEDeptApprovals_PE_Phase_Dept_Stage");
b.HasIndex(x => x.PurchaseEvaluationId);
b.HasIndex(x => x.DepartmentId);
b.HasIndex(x => x.ApproverUserId);
b.HasOne(x => x.PurchaseEvaluation)
.WithMany(c => c.DepartmentApprovals)
.HasForeignKey(x => x.PurchaseEvaluationId)
.OnDelete(DeleteBehavior.Cascade);
}
}
public class BudgetDepartmentApprovalConfiguration
: IEntityTypeConfiguration<BudgetDepartmentApproval>
{
public void Configure(EntityTypeBuilder<BudgetDepartmentApproval> b)
{
b.ToTable("BudgetDepartmentApprovals");
b.HasKey(x => x.Id);
b.Property(x => x.Stage).HasConversion<int>();
b.Property(x => x.ApproverRoleSnapshot).HasMaxLength(100);
b.Property(x => x.Comment).HasMaxLength(1000);
b.HasIndex(x => new { x.BudgetId, x.PhaseAtApproval, x.DepartmentId, x.Stage })
.IsUnique()
.HasDatabaseName("UX_BudgetDeptApprovals_Budget_Phase_Dept_Stage");
b.HasIndex(x => x.BudgetId);
b.HasIndex(x => x.DepartmentId);
b.HasIndex(x => x.ApproverUserId);
b.HasOne(x => x.Budget)
.WithMany(c => c.DepartmentApprovals)
.HasForeignKey(x => x.BudgetId)
.OnDelete(DeleteBehavior.Cascade);
}
}

View File

@ -14,6 +14,7 @@ public class PurchaseEvaluationConfiguration : IEntityTypeConfiguration<Purchase
b.Property(x => x.MaPhieu).HasMaxLength(100);
b.Property(x => x.Type).HasConversion<int>();
b.Property(x => x.Phase).HasConversion<int>();
b.Property(x => x.RejectedFromPhase).HasConversion<int?>();
b.Property(x => x.TenGoiThau).HasMaxLength(500).IsRequired();
b.Property(x => x.DiaDiem).HasMaxLength(500);
b.Property(x => x.MoTa).HasMaxLength(2000);

View File

@ -0,0 +1,231 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SolutionErp.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddTwoStageDeptApprovalAndSmartReject : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "CanBypassReview",
table: "Users",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<int>(
name: "RejectedFromPhase",
table: "PurchaseEvaluations",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "RejectedFromPhase",
table: "Contracts",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "RejectedFromPhase",
table: "Budgets",
type: "int",
nullable: true);
migrationBuilder.CreateTable(
name: "BudgetDepartmentApprovals",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
BudgetId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
PhaseAtApproval = table.Column<int>(type: "int", nullable: false),
DepartmentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Stage = table.Column<int>(type: "int", nullable: false),
ApproverUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ApproverRoleSnapshot = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
Comment = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
ApprovedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
IsBypassed = table.Column<bool>(type: "bit", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
DeletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
DeletedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_BudgetDepartmentApprovals", x => x.Id);
table.ForeignKey(
name: "FK_BudgetDepartmentApprovals_Budgets_BudgetId",
column: x => x.BudgetId,
principalTable: "Budgets",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ContractDepartmentApprovals",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ContractId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
PhaseAtApproval = table.Column<int>(type: "int", nullable: false),
DepartmentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Stage = table.Column<int>(type: "int", nullable: false),
ApproverUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ApproverRoleSnapshot = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
Comment = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
ApprovedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
IsBypassed = table.Column<bool>(type: "bit", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
DeletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
DeletedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ContractDepartmentApprovals", x => x.Id);
table.ForeignKey(
name: "FK_ContractDepartmentApprovals_Contracts_ContractId",
column: x => x.ContractId,
principalTable: "Contracts",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "PurchaseEvaluationDepartmentApprovals",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
PurchaseEvaluationId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
PhaseAtApproval = table.Column<int>(type: "int", nullable: false),
DepartmentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Stage = table.Column<int>(type: "int", nullable: false),
ApproverUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ApproverRoleSnapshot = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
Comment = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
ApprovedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
IsBypassed = table.Column<bool>(type: "bit", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
DeletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
DeletedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PurchaseEvaluationDepartmentApprovals", x => x.Id);
table.ForeignKey(
name: "FK_PurchaseEvaluationDepartmentApprovals_PurchaseEvaluations_PurchaseEvaluationId",
column: x => x.PurchaseEvaluationId,
principalTable: "PurchaseEvaluations",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_BudgetDepartmentApprovals_ApproverUserId",
table: "BudgetDepartmentApprovals",
column: "ApproverUserId");
migrationBuilder.CreateIndex(
name: "IX_BudgetDepartmentApprovals_BudgetId",
table: "BudgetDepartmentApprovals",
column: "BudgetId");
migrationBuilder.CreateIndex(
name: "IX_BudgetDepartmentApprovals_DepartmentId",
table: "BudgetDepartmentApprovals",
column: "DepartmentId");
migrationBuilder.CreateIndex(
name: "UX_BudgetDeptApprovals_Budget_Phase_Dept_Stage",
table: "BudgetDepartmentApprovals",
columns: new[] { "BudgetId", "PhaseAtApproval", "DepartmentId", "Stage" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ContractDepartmentApprovals_ApproverUserId",
table: "ContractDepartmentApprovals",
column: "ApproverUserId");
migrationBuilder.CreateIndex(
name: "IX_ContractDepartmentApprovals_ContractId",
table: "ContractDepartmentApprovals",
column: "ContractId");
migrationBuilder.CreateIndex(
name: "IX_ContractDepartmentApprovals_DepartmentId",
table: "ContractDepartmentApprovals",
column: "DepartmentId");
migrationBuilder.CreateIndex(
name: "UX_ContractDeptApprovals_Contract_Phase_Dept_Stage",
table: "ContractDepartmentApprovals",
columns: new[] { "ContractId", "PhaseAtApproval", "DepartmentId", "Stage" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_PurchaseEvaluationDepartmentApprovals_ApproverUserId",
table: "PurchaseEvaluationDepartmentApprovals",
column: "ApproverUserId");
migrationBuilder.CreateIndex(
name: "IX_PurchaseEvaluationDepartmentApprovals_DepartmentId",
table: "PurchaseEvaluationDepartmentApprovals",
column: "DepartmentId");
migrationBuilder.CreateIndex(
name: "IX_PurchaseEvaluationDepartmentApprovals_PurchaseEvaluationId",
table: "PurchaseEvaluationDepartmentApprovals",
column: "PurchaseEvaluationId");
migrationBuilder.CreateIndex(
name: "UX_PEDeptApprovals_PE_Phase_Dept_Stage",
table: "PurchaseEvaluationDepartmentApprovals",
columns: new[] { "PurchaseEvaluationId", "PhaseAtApproval", "DepartmentId", "Stage" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "BudgetDepartmentApprovals");
migrationBuilder.DropTable(
name: "ContractDepartmentApprovals");
migrationBuilder.DropTable(
name: "PurchaseEvaluationDepartmentApprovals");
migrationBuilder.DropColumn(
name: "CanBypassReview",
table: "Users");
migrationBuilder.DropColumn(
name: "RejectedFromPhase",
table: "PurchaseEvaluations");
migrationBuilder.DropColumn(
name: "RejectedFromPhase",
table: "Contracts");
migrationBuilder.DropColumn(
name: "RejectedFromPhase",
table: "Budgets");
}
}
}

View File

@ -169,6 +169,9 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Property<Guid>("ProjectId")
.HasColumnType("uniqueidentifier");
b.Property<int?>("RejectedFromPhase")
.HasColumnType("int");
b.Property<DateTime?>("SlaDeadline")
.HasColumnType("datetime2");
@ -314,6 +317,77 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.ToTable("BudgetChangelogs", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetDepartmentApproval", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("ApprovedAt")
.HasColumnType("datetime2");
b.Property<string>("ApproverRoleSnapshot")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<Guid>("ApproverUserId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("BudgetId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Comment")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uniqueidentifier");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("DepartmentId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsBypassed")
.HasColumnType("bit");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<int>("PhaseAtApproval")
.HasColumnType("int");
b.Property<int>("Stage")
.HasColumnType("int");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("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<Guid>("Id")
@ -438,6 +512,9 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Property<Guid>("ProjectId")
.HasColumnType("uniqueidentifier");
b.Property<int?>("RejectedFromPhase")
.HasColumnType("int");
b.Property<DateTime?>("SlaDeadline")
.HasColumnType("datetime2");
@ -701,6 +778,77 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.ToTable("ContractComments", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractDepartmentApproval", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("ApprovedAt")
.HasColumnType("datetime2");
b.Property<string>("ApproverRoleSnapshot")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<Guid>("ApproverUserId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Comment")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<Guid>("ContractId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uniqueidentifier");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("DepartmentId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsBypassed")
.HasColumnType("bit");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<int>("PhaseAtApproval")
.HasColumnType("int");
b.Property<int>("Stage")
.HasColumnType("int");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("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<Guid>("Id")
@ -1598,6 +1746,9 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<bool>("CanBypassReview")
.HasColumnType("bit");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
@ -2222,6 +2373,9 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Property<Guid>("ProjectId")
.HasColumnType("uniqueidentifier");
b.Property<int?>("RejectedFromPhase")
.HasColumnType("int");
b.Property<Guid?>("SelectedSupplierId")
.HasColumnType("uniqueidentifier");
@ -2451,6 +2605,77 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.ToTable("PurchaseEvaluationCodeSequences", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationDepartmentApproval", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("ApprovedAt")
.HasColumnType("datetime2");
b.Property<string>("ApproverRoleSnapshot")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<Guid>("ApproverUserId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Comment")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uniqueidentifier");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("DepartmentId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsBypassed")
.HasColumnType("bit");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<int>("PhaseAtApproval")
.HasColumnType("int");
b.Property<Guid>("PurchaseEvaluationId")
.HasColumnType("uniqueidentifier");
b.Property<int>("Stage")
.HasColumnType("int");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("ApproverUserId");
b.HasIndex("DepartmentId");
b.HasIndex("PurchaseEvaluationId");
b.HasIndex("PurchaseEvaluationId", "PhaseAtApproval", "DepartmentId", "Stage")
.IsUnique()
.HasDatabaseName("UX_PEDeptApprovals_PE_Phase_Dept_Stage");
b.ToTable("PurchaseEvaluationDepartmentApprovals", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationDepartmentOpinion", b =>
{
b.Property<Guid>("Id")
@ -2904,6 +3129,17 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Navigation("Budget");
});
modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetDepartmentApproval", b =>
{
b.HasOne("SolutionErp.Domain.Budgets.Budget", "Budget")
.WithMany("DepartmentApprovals")
.HasForeignKey("BudgetId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Budget");
});
modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetDetail", b =>
{
b.HasOne("SolutionErp.Domain.Budgets.Budget", "Budget")
@ -2959,6 +3195,17 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Navigation("Contract");
});
modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractDepartmentApproval", b =>
{
b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract")
.WithMany("DepartmentApprovals")
.HasForeignKey("ContractId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Contract");
});
modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.DichVuDetail", b =>
{
b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract")
@ -3128,6 +3375,17 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Navigation("PurchaseEvaluation");
});
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationDepartmentApproval", b =>
{
b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", "PurchaseEvaluation")
.WithMany("DepartmentApprovals")
.HasForeignKey("PurchaseEvaluationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("PurchaseEvaluation");
});
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationDepartmentOpinion", b =>
{
b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", "PurchaseEvaluation")
@ -3212,6 +3470,8 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Navigation("Changelogs");
b.Navigation("DepartmentApprovals");
b.Navigation("Details");
});
@ -3225,6 +3485,8 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Navigation("Comments");
b.Navigation("DepartmentApprovals");
b.Navigation("DichVuDetails");
b.Navigation("GiaoKhoanDetails");
@ -3265,6 +3527,8 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Navigation("Changelogs");
b.Navigation("DepartmentApprovals");
b.Navigation("DepartmentOpinions");
b.Navigation("Details");