[CLAUDE] Domain+Infra: Migration 18 PE workflow inner steps + User.PositionLevel (Chunk A)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled
N-stage workflow approval — mỗi WorkflowStep cha (= 1 phase) cấu hình
được chuỗi InnerSteps con theo Department × PositionLevel với Order
sequential. Phase 9+ feature, mở rộng từ 2-stage Mig 16.
Schema:
- enum PositionLevel { NhanVien=1, PhoPhong=2, TruongPhong=3 } (Domain/Identity)
- ALTER Users + PositionLevel int? NULL (admin/system user vẫn null)
- CREATE TABLE PurchaseEvaluationWorkflowStepInnerSteps:
Id PK, PurchaseEvaluationWorkflowStepId FK Cascade,
Order int, DepartmentId FK Restrict, PositionLevel int,
Name nvarchar(200), SlaDays int?, IsRequired bit
- ALTER PurchaseEvaluationDepartmentApprovals + InnerStepId Guid? FK Restrict
(null cho data legacy 2-stage Review/Confirm Mig 16)
Backward compat: step KHÔNG có InnerSteps → service fallback logic
2-stage Stage=Review|Confirm cũ (Chunk C). Data Mig 16 hiện có giữ
nguyên, InnerStepId=null.
Verify:
- dotnet build SolutionErp.slnx pass (0 error, 2 pre-existing warning DocxRenderer)
- dotnet ef database update LocalDB applied OK
- dotnet test SolutionErp.slnx 83 pass (54 Domain + 29 Infra) — no regression
- 3-file rule: Migration.cs + Designer.cs + Snapshot updated
Pending Chunk B: Application CQRS — extend CreatePeWorkflowDefinitionCommand
với InnerSteps DTO + UpdateUserPositionLevelCommand.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -59,6 +59,7 @@ public class ApplicationDbContext
|
||||
public DbSet<PurchaseEvaluationWorkflowDefinition> PurchaseEvaluationWorkflowDefinitions => Set<PurchaseEvaluationWorkflowDefinition>();
|
||||
public DbSet<PurchaseEvaluationWorkflowStep> PurchaseEvaluationWorkflowSteps => Set<PurchaseEvaluationWorkflowStep>();
|
||||
public DbSet<PurchaseEvaluationWorkflowStepApprover> PurchaseEvaluationWorkflowStepApprovers => Set<PurchaseEvaluationWorkflowStepApprover>();
|
||||
public DbSet<PurchaseEvaluationWorkflowStepInnerStep> PurchaseEvaluationWorkflowStepInnerSteps => Set<PurchaseEvaluationWorkflowStepInnerStep>();
|
||||
public DbSet<PurchaseEvaluationCodeSequence> PurchaseEvaluationCodeSequences => Set<PurchaseEvaluationCodeSequence>();
|
||||
public DbSet<PurchaseEvaluationDepartmentOpinion> PurchaseEvaluationDepartmentOpinions => Set<PurchaseEvaluationDepartmentOpinion>();
|
||||
public DbSet<PurchaseEvaluationDepartmentApproval> PurchaseEvaluationDepartmentApprovals => Set<PurchaseEvaluationDepartmentApproval>();
|
||||
|
||||
@ -59,11 +59,19 @@ public class PurchaseEvaluationDepartmentApprovalConfiguration
|
||||
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)
|
||||
.HasForeignKey(x => x.PurchaseEvaluationId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// FK InnerStepId nullable — Restrict (không xóa InnerStep nếu còn approval row).
|
||||
// Cấu hình không nav để giữ nhẹ entity (1 chiều, query qua join nếu cần).
|
||||
b.HasOne<PurchaseEvaluationWorkflowStepInnerStep>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.InnerStepId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -209,6 +209,37 @@ public class PurchaseEvaluationWorkflowStepApproverConfiguration : IEntityTypeCo
|
||||
}
|
||||
}
|
||||
|
||||
// Inner step (Mig 18) — N-stage approval cấu hình động trong cùng 1 phase.
|
||||
// FK Cascade từ Step cha. Index theo (StepId, Order) cho query ordered list.
|
||||
// Index riêng DepartmentId để lookup khi service compute next pending sub-step.
|
||||
public class PurchaseEvaluationWorkflowStepInnerStepConfiguration
|
||||
: IEntityTypeConfiguration<PurchaseEvaluationWorkflowStepInnerStep>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PurchaseEvaluationWorkflowStepInnerStep> e)
|
||||
{
|
||||
e.ToTable("PurchaseEvaluationWorkflowStepInnerSteps");
|
||||
e.HasKey(x => x.Id);
|
||||
|
||||
e.Property(x => x.PositionLevel).HasConversion<int>();
|
||||
e.Property(x => x.Name).HasMaxLength(200);
|
||||
|
||||
e.HasOne(x => x.Step)
|
||||
.WithMany(s => s.InnerSteps)
|
||||
.HasForeignKey(x => x.PurchaseEvaluationWorkflowStepId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// FK Department — Restrict (không xóa dept nếu còn inner step assigned).
|
||||
// Không cấu hình nav trên Department để tránh circular collection bloat.
|
||||
e.HasOne<SolutionErp.Domain.Master.Department>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.DepartmentId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
e.HasIndex(x => new { x.PurchaseEvaluationWorkflowStepId, x.Order });
|
||||
e.HasIndex(x => x.DepartmentId);
|
||||
}
|
||||
}
|
||||
|
||||
// Mirror ContractCodeSequenceConfiguration — Prefix là PK, atomic UPDATE qua
|
||||
// SERIALIZABLE transaction trong PurchaseEvaluationCodeGenerator.
|
||||
public class PurchaseEvaluationCodeSequenceConfiguration
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPeWorkflowInnerStepsAndPositionLevel : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PositionLevel",
|
||||
table: "Users",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "InnerStepId",
|
||||
table: "PurchaseEvaluationDepartmentApprovals",
|
||||
type: "uniqueidentifier",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PurchaseEvaluationWorkflowStepInnerSteps",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
PurchaseEvaluationWorkflowStepId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Order = table.Column<int>(type: "int", nullable: false),
|
||||
DepartmentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
PositionLevel = table.Column<int>(type: "int", nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||
SlaDays = table.Column<int>(type: "int", nullable: true),
|
||||
IsRequired = 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)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PurchaseEvaluationWorkflowStepInnerSteps", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseEvaluationWorkflowStepInnerSteps_Departments_DepartmentId",
|
||||
column: x => x.DepartmentId,
|
||||
principalTable: "Departments",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_PurchaseEvaluationWorkflowStepInnerSteps_PurchaseEvaluationWorkflowSteps_PurchaseEvaluationWorkflowStepId",
|
||||
column: x => x.PurchaseEvaluationWorkflowStepId,
|
||||
principalTable: "PurchaseEvaluationWorkflowSteps",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseEvaluationDepartmentApprovals_InnerStepId",
|
||||
table: "PurchaseEvaluationDepartmentApprovals",
|
||||
column: "InnerStepId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseEvaluationWorkflowStepInnerSteps_DepartmentId",
|
||||
table: "PurchaseEvaluationWorkflowStepInnerSteps",
|
||||
column: "DepartmentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseEvaluationWorkflowStepInnerSteps_PurchaseEvaluationWorkflowStepId_Order",
|
||||
table: "PurchaseEvaluationWorkflowStepInnerSteps",
|
||||
columns: new[] { "PurchaseEvaluationWorkflowStepId", "Order" });
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_PurchaseEvaluationDepartmentApprovals_PurchaseEvaluationWorkflowStepInnerSteps_InnerStepId",
|
||||
table: "PurchaseEvaluationDepartmentApprovals",
|
||||
column: "InnerStepId",
|
||||
principalTable: "PurchaseEvaluationWorkflowStepInnerSteps",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_PurchaseEvaluationDepartmentApprovals_PurchaseEvaluationWorkflowStepInnerSteps_InnerStepId",
|
||||
table: "PurchaseEvaluationDepartmentApprovals");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PurchaseEvaluationWorkflowStepInnerSteps");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_PurchaseEvaluationDepartmentApprovals_InnerStepId",
|
||||
table: "PurchaseEvaluationDepartmentApprovals");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PositionLevel",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "InnerStepId",
|
||||
table: "PurchaseEvaluationDepartmentApprovals");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1809,6 +1809,9 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<int?>("PositionLevel")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("RefreshToken")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
@ -2656,6 +2659,9 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.Property<Guid>("DepartmentId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("InnerStepId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsBypassed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
@ -2683,6 +2689,8 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("DepartmentId");
|
||||
|
||||
b.HasIndex("InnerStepId");
|
||||
|
||||
b.HasIndex("PurchaseEvaluationId");
|
||||
|
||||
b.HasIndex("PurchaseEvaluationId", "PhaseAtApproval", "DepartmentId", "Stage")
|
||||
@ -3072,6 +3080,55 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.ToTable("PurchaseEvaluationWorkflowStepApprovers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStepInnerStep", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("DepartmentId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsRequired")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PositionLevel")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("PurchaseEvaluationWorkflowStepId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int?>("SlaDays")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DepartmentId");
|
||||
|
||||
b.HasIndex("PurchaseEvaluationWorkflowStepId", "Order");
|
||||
|
||||
b.ToTable("PurchaseEvaluationWorkflowStepInnerSteps", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Identity.Role", null)
|
||||
@ -3393,6 +3450,11 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationDepartmentApproval", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStepInnerStep", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("InnerStepId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", "PurchaseEvaluation")
|
||||
.WithMany("DepartmentApprovals")
|
||||
.HasForeignKey("PurchaseEvaluationId")
|
||||
@ -3480,6 +3542,23 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.Navigation("Step");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStepInnerStep", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Master.Department", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("DepartmentId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStep", "Step")
|
||||
.WithMany("InnerSteps")
|
||||
.HasForeignKey("PurchaseEvaluationWorkflowStepId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Step");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Budgets.Budget", b =>
|
||||
{
|
||||
b.Navigation("Approvals");
|
||||
@ -3567,6 +3646,8 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStep", b =>
|
||||
{
|
||||
b.Navigation("Approvers");
|
||||
|
||||
b.Navigation("InnerSteps");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user