[CLAUDE] Domain+App+Infra: Plan B Chunk C — Mig 33 ContractLevelOpinions cookie-cutter mirror PE Mig 26
- Domain/Contracts/ContractLevelOpinion.cs (NEW entity mirror PE — AuditableEntity, 4 field core + 2 nav) - Domain/Contracts/Contract.cs (+LevelOpinions nav collection) - Migrations/20260522052240_AddContractLevelOpinions.cs (3-file rule: .cs + .Designer.cs + Snapshot) - Configurations/ContractLevelOpinionConfiguration.cs (NEW separate file, mirror PE pattern) - IApplicationDbContext.cs + ApplicationDbContext.cs (+DbSet<ContractLevelOpinion>) UNIQUE composite (ContractId, ApprovalWorkflowLevelId) — 1 row per HĐ × Level. FK Cascade Contract + Restrict ApprovalWorkflowLevel. SignedByUserId KHÔNG nav (denorm SignedByFullName tránh cascade khi xoá user). Mirror PE Mig 26 pattern (S19 2026-05-09) EXACT — UPSERT row khi Approver duyệt qua Service ApproveV2Async (Plan B Chunk B em main138469dđã có TODO marker). Em main sẽ add UPSERT block sau Chunk C done (Chunk D). Verify: - dotnet build PASS 0 err (2 pre-existing warn DocxRenderer unrelated) - dotnet ef database update PASS (Mig 33 applied SolutionErp_Dev + _Design) - dotnet test 111/111 PASS (58 Domain + 53 Infra — no regression) Plan B chain (6 chunks): - A158898e8✅ ContractApprovalWorkflowV2 entity scaffold - A2a85e437✅ Contract.ApprovalWorkflowId + ContractConfiguration FK - B138469d✅ ContractWorkflowService ApproveV2Async skeleton + TODO LevelOpinion UPSERT - C (this) ✅ ContractLevelOpinions entity + Mig 33 + config + DbSet - D FE Workspace V2 (Implementer, pending) - E FE Section 5 V2 (Implementer, pending) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -37,6 +37,8 @@ public class ApplicationDbContext
|
||||
public DbSet<ContractCodeSequence> ContractCodeSequences => Set<ContractCodeSequence>();
|
||||
public DbSet<ContractChangelog> ContractChangelogs => Set<ContractChangelog>();
|
||||
public DbSet<ContractDepartmentApproval> ContractDepartmentApprovals => Set<ContractDepartmentApproval>();
|
||||
// Plan B Chunk C (S29 — Mig 33) — Ý kiến cấp duyệt V2 cho HĐ. Mirror PE Mig 26.
|
||||
public DbSet<ContractLevelOpinion> ContractLevelOpinions => Set<ContractLevelOpinion>();
|
||||
public DbSet<Notification> Notifications => Set<Notification>();
|
||||
public DbSet<WorkflowTypeAssignment> WorkflowTypeAssignments => Set<WorkflowTypeAssignment>();
|
||||
public DbSet<WorkflowDefinition> WorkflowDefinitions => Set<WorkflowDefinition>();
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.Contracts;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
// Plan B Chunk C (S29 — Mig 33, 2026-05-22) — cookie-cutter mirror
|
||||
// PurchaseEvaluationLevelOpinionConfiguration (Mig 26 S19).
|
||||
//
|
||||
// UPSERT auto sync từ ContractWorkflowService.ApproveV2Async (Plan B Chunk D em
|
||||
// main wire). UNIQUE (ContractId, ApprovalWorkflowLevelId) đảm bảo 1 row/level/HĐ.
|
||||
// FK Cascade Contract (xoá HĐ → xoá opinions),
|
||||
// FK Restrict Level (admin xoá Level chặn nếu opinion tồn tại — bảo vệ data).
|
||||
// SignedByUserId KHÔNG nav (tránh cascade khi xoá user; denorm SignedByFullName).
|
||||
public class ContractLevelOpinionConfiguration : IEntityTypeConfiguration<ContractLevelOpinion>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ContractLevelOpinion> e)
|
||||
{
|
||||
e.ToTable("ContractLevelOpinions");
|
||||
|
||||
e.Property(x => x.Comment).HasMaxLength(2000);
|
||||
e.Property(x => x.SignedByFullName).HasMaxLength(200).IsRequired();
|
||||
|
||||
e.HasOne(x => x.Contract)
|
||||
.WithMany(c => c.LevelOpinions)
|
||||
.HasForeignKey(x => x.ContractId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
e.HasOne(x => x.Level)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.ApprovalWorkflowLevelId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
e.HasIndex(x => new { x.ContractId, x.ApprovalWorkflowLevelId }).IsUnique();
|
||||
e.HasIndex(x => x.ApprovalWorkflowLevelId);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddContractLevelOpinions : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ContractLevelOpinions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ContractId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ApprovalWorkflowLevelId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Comment = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
|
||||
SignedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
SignedByUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
SignedByFullName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, 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_ContractLevelOpinions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ContractLevelOpinions_ApprovalWorkflowLevels_ApprovalWorkflowLevelId",
|
||||
column: x => x.ApprovalWorkflowLevelId,
|
||||
principalTable: "ApprovalWorkflowLevels",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_ContractLevelOpinions_Contracts_ContractId",
|
||||
column: x => x.ContractId,
|
||||
principalTable: "Contracts",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractLevelOpinions_ApprovalWorkflowLevelId",
|
||||
table: "ContractLevelOpinions",
|
||||
column: "ApprovalWorkflowLevelId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractLevelOpinions_ContractId_ApprovalWorkflowLevelId",
|
||||
table: "ContractLevelOpinions",
|
||||
columns: new[] { "ContractId", "ApprovalWorkflowLevelId" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ContractLevelOpinions");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1044,6 +1044,64 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.ToTable("ContractDepartmentApprovals", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractLevelOpinion", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ApprovalWorkflowLevelId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
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<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime>("SignedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("SignedByFullName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<Guid>("SignedByUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApprovalWorkflowLevelId");
|
||||
|
||||
b.HasIndex("ContractId", "ApprovalWorkflowLevelId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ContractLevelOpinions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.DichVuDetail", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -3550,6 +3608,25 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.Navigation("Contract");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractLevelOpinion", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflowLevel", "Level")
|
||||
.WithMany()
|
||||
.HasForeignKey("ApprovalWorkflowLevelId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract")
|
||||
.WithMany("LevelOpinions")
|
||||
.HasForeignKey("ContractId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Contract");
|
||||
|
||||
b.Navigation("Level");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.DichVuDetail", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract")
|
||||
@ -3882,6 +3959,8 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.Navigation("GiaoKhoanDetails");
|
||||
|
||||
b.Navigation("LevelOpinions");
|
||||
|
||||
b.Navigation("MuaBanDetails");
|
||||
|
||||
b.Navigation("NguyenTacDvDetails");
|
||||
|
||||
Reference in New Issue
Block a user