[CLAUDE] Infra: PE workflow service N-stage logic + Migration 19 unique filter (Chunk C)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m2s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m2s
Migration 19 `AlterPeDeptApprovalsUniqueFilteredForInnerSteps` — fix
UNIQUE constraint conflict khi N-stage có nhiều inner step cùng dept:
- Drop UX_PEDeptApprovals_PE_Phase_Dept_Stage (Mig 16)
- Recreate filtered: UNIQUE (PEId, Phase, Dept, Stage) WHERE InnerStepId IS NULL
(legacy 2-stage rows giữ nguyên invariant)
- New filtered: UNIQUE (PEId, Phase, InnerStepId) WHERE InnerStepId IS NOT NULL
(N-stage 1 row per inner step per phase)
PurchaseEvaluationWorkflowService.TransitionAsync refactor:
- Load definition with InnerSteps eager (.Include ThenInclude)
- Reject branch: clear N-stage approval rows tại fromPhase (resume sẽ
approve lại từ inner step đầu — clean state)
- Department approval block split:
* hasInnerSteps=true → N-stage logic:
- Yêu cầu actor có DepartmentId + PositionLevel set (else throw 403)
- Match firstPending inner step (Order asc, IsRequired only):
same dept AND (exact PositionLevel OR canBypass + level ≥ pending)
- exact match: upsert 1 row (Stage=Confirm, InnerStepId=that)
- bypass: batch upsert NV+PP+TP cùng dept ≤ actor level (audit
IsBypassed=true cho cấp dưới skip)
- Recheck stillPending → BLOCK transition + log Approval/Changelog
"đã duyệt cấp X (còn Y cấp pending)"
- All done → fall through phase transition
* hasInnerSteps=false → Legacy 2-stage (Mig 16) — giữ nguyên logic
NV.Review/TPB.Confirm + InnerStepId=null filter
Backward compat: workflow cũ (no InnerSteps configured) chạy đúng logic
2-stage Mig 16. Data legacy InnerStepId=null vẫn match unique cũ qua
filtered index. Tests 83 pass — no regression.
Verify:
- dotnet build SolutionErp.slnx 0 error
- dotnet ef database update LocalDB applied Mig 19
- dotnet test 83 pass
Pending Chunk D: Tests N-stage workflow (~6-7 test mới: sequential pass /
bypass cùng dept / reject reset / resume jump-back / legacy fallback).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -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)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,50 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AlterPeDeptApprovalsUniqueFilteredForInnerSteps : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "UX_PEDeptApprovals_PE_Phase_Dept_Stage",
|
||||
table: "PurchaseEvaluationDepartmentApprovals");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_PEDeptApprovals_PE_Phase_Dept_Stage",
|
||||
table: "PurchaseEvaluationDepartmentApprovals",
|
||||
columns: new[] { "PurchaseEvaluationId", "PhaseAtApproval", "DepartmentId", "Stage" },
|
||||
unique: true,
|
||||
filter: "[InnerStepId] IS NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_PEDeptApprovals_PE_Phase_InnerStep",
|
||||
table: "PurchaseEvaluationDepartmentApprovals",
|
||||
columns: new[] { "PurchaseEvaluationId", "PhaseAtApproval", "InnerStepId" },
|
||||
unique: true,
|
||||
filter: "[InnerStepId] IS NOT NULL");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "UX_PEDeptApprovals_PE_Phase_Dept_Stage",
|
||||
table: "PurchaseEvaluationDepartmentApprovals");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "UX_PEDeptApprovals_PE_Phase_InnerStep",
|
||||
table: "PurchaseEvaluationDepartmentApprovals");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_PEDeptApprovals_PE_Phase_Dept_Stage",
|
||||
table: "PurchaseEvaluationDepartmentApprovals",
|
||||
columns: new[] { "PurchaseEvaluationId", "PhaseAtApproval", "DepartmentId", "Stage" },
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2693,9 +2693,15 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("PurchaseEvaluationId");
|
||||
|
||||
b.HasIndex("PurchaseEvaluationId", "PhaseAtApproval", "InnerStepId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_PEDeptApprovals_PE_Phase_InnerStep")
|
||||
.HasFilter("[InnerStepId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("PurchaseEvaluationId", "PhaseAtApproval", "DepartmentId", "Stage")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_PEDeptApprovals_PE_Phase_Dept_Stage");
|
||||
.HasDatabaseName("UX_PEDeptApprovals_PE_Phase_Dept_Stage")
|
||||
.HasFilter("[InnerStepId] IS NULL");
|
||||
|
||||
b.ToTable("PurchaseEvaluationDepartmentApprovals", (string)null);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user