[CLAUDE] Infra: gotcha #57 EXT Master filtered-unique Department/Supplier/Project (Mig 47)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m15s

Extend gotcha #57 filtered-unique fix to 3 Master catalogs (4th/5th/6th cumulative after Holiday Mig 43 S45 + HRM x3 Mig 45 S51).

Root cause: app-level dup-check db.X.AnyAsync(Code==req) runs through HasQueryFilter(!IsDeleted) so it ignores soft-deleted rows, but the bare .IsUnique() DB index counted them -> admin delete+re-add same Code = reachable 500. Fix aligns index with query filter via .HasFilter("[IsDeleted] = 0").

- Department/Project/Supplier Configuration: unique Code index + .HasFilter (Supplier Type index untouched)
- Mig 47 FilterMasterCatalogUniqueIndexesByIsDeleted (Up: 3x DropIndex+CreateIndex filtered; Down reversible)
- test-before MasterCatalogFilteredUniqueTests (3 RED->GREEN, delete+re-add same Code)
- Tests 200 -> 203 (58 Domain + 145 Infra)

Pipeline: test-specialist -> implementer-backend -> reviewer (PASS, 0 issues).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-08 14:28:04 +07:00
parent f440c194a8
commit 44b9e542fb
7 changed files with 6745 additions and 6 deletions

View File

@ -15,7 +15,7 @@ public class DepartmentConfiguration : IEntityTypeConfiguration<Department>
b.Property(x => x.Name).HasMaxLength(200).IsRequired();
b.Property(x => x.Note).HasMaxLength(1000);
b.HasIndex(x => x.Code).IsUnique();
b.HasIndex(x => x.Code).IsUnique().HasFilter("[IsDeleted] = 0"); // Mig 47 (gotcha #57 EXT) — soft-deleted slot reusable, khớp HasQueryFilter !IsDeleted app-check
b.HasQueryFilter(x => !x.IsDeleted);
}

View File

@ -16,7 +16,7 @@ public class ProjectConfiguration : IEntityTypeConfiguration<Project>
b.Property(x => x.BudgetTotal).HasPrecision(18, 2);
b.Property(x => x.Note).HasMaxLength(1000);
b.HasIndex(x => x.Code).IsUnique();
b.HasIndex(x => x.Code).IsUnique().HasFilter("[IsDeleted] = 0"); // Mig 47 (gotcha #57 EXT) — soft-deleted slot reusable, khớp HasQueryFilter !IsDeleted app-check
b.HasQueryFilter(x => !x.IsDeleted);
}

View File

@ -21,7 +21,7 @@ public class SupplierConfiguration : IEntityTypeConfiguration<Supplier>
b.Property(x => x.ContactPerson).HasMaxLength(200);
b.Property(x => x.Note).HasMaxLength(1000);
b.HasIndex(x => x.Code).IsUnique();
b.HasIndex(x => x.Code).IsUnique().HasFilter("[IsDeleted] = 0"); // Mig 47 (gotcha #57 EXT) — soft-deleted slot reusable, khớp HasQueryFilter !IsDeleted app-check
b.HasIndex(x => x.Type);
b.HasQueryFilter(x => !x.IsDeleted);

View File

@ -0,0 +1,81 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SolutionErp.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class FilterMasterCatalogUniqueIndexesByIsDeleted : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Suppliers_Code",
table: "Suppliers");
migrationBuilder.DropIndex(
name: "IX_Projects_Code",
table: "Projects");
migrationBuilder.DropIndex(
name: "IX_Departments_Code",
table: "Departments");
migrationBuilder.CreateIndex(
name: "IX_Suppliers_Code",
table: "Suppliers",
column: "Code",
unique: true,
filter: "[IsDeleted] = 0");
migrationBuilder.CreateIndex(
name: "IX_Projects_Code",
table: "Projects",
column: "Code",
unique: true,
filter: "[IsDeleted] = 0");
migrationBuilder.CreateIndex(
name: "IX_Departments_Code",
table: "Departments",
column: "Code",
unique: true,
filter: "[IsDeleted] = 0");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Suppliers_Code",
table: "Suppliers");
migrationBuilder.DropIndex(
name: "IX_Projects_Code",
table: "Projects");
migrationBuilder.DropIndex(
name: "IX_Departments_Code",
table: "Departments");
migrationBuilder.CreateIndex(
name: "IX_Suppliers_Code",
table: "Suppliers",
column: "Code",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Projects_Code",
table: "Projects",
column: "Code",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Departments_Code",
table: "Departments",
column: "Code",
unique: true);
}
}
}

View File

@ -3448,7 +3448,8 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.HasKey("Id");
b.HasIndex("Code")
.IsUnique();
.IsUnique()
.HasFilter("[IsDeleted] = 0");
b.ToTable("Departments", (string)null);
});
@ -3510,7 +3511,8 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.HasKey("Id");
b.HasIndex("Code")
.IsUnique();
.IsUnique()
.HasFilter("[IsDeleted] = 0");
b.ToTable("Projects", (string)null);
});
@ -3582,7 +3584,8 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.HasKey("Id");
b.HasIndex("Code")
.IsUnique();
.IsUnique()
.HasFilter("[IsDeleted] = 0");
b.HasIndex("Type");