[CLAUDE] Domain+App+Infra+Admin: W1 KHKK — Mig 69 ContractSigningPlans 7 bang + policy KeHoachKyKet + seeder UPGRADE-if-exists + CodeGen KHKK + Designer type-10
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m40s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m40s
- Domain/ContractSigningPlans: 7 entity + 3 enum (Phase 1/2/3/98/99; DossierItem Kind/Status start=1); ApplicableType += ContractSigningPlan=10 (slot 10, append-only) - Mig 69 AddContractSigningPlans: 7 CreateTable / 20 CreateIndex / 0 ALTER; UNIQUE filtered (PlanId,SupplierId) WHERE IsDeleted=0 (#57); FK 6 con Cascade 1-hop + 2 Restrict, 0 multiple-cascade-path; database-agent review PASS 8/8 + DB11 - MenuKeys.All += KeHoachKyKet (policy KeHoachKyKet.* x4 sinh runtime; menu-row prod KHONG doi — 142 giu); STATUS canonical 54->55 / 216->220 - DbInitializer.SeedKeHoachKyKetAccessAsync: UPGRADE-if-exists (F-S1 — khong insert-NO-OP) + vế CanDelete (Drafter/Admin), 13/13 role CanCreate+CanUpdate, chay SAU revoke; revoker khong phu Khkk_* (reviewer Truc-1 PASS) - ContractSigningPlanCodeGenerator: KHKK/{YYYY}/{Seq:D3} (owner OK @S161) qua WorkflowAppCodeSequences dung chung, SERIALIZABLE - AWV2AdminFeatures: 3 site count/retained/orphan + nhanh ContractSigningPlanLevelOpinions (chong no FK 500 khi xoa Level); +label VN type-10 (F-7); +ep false 2 co early-finalize server-side cho type-10 (F-8 — containment tang BE, FE da ep o levelFlagsPayload) - fe-admin Designer: deep-link type-10 + an 2 checkbox early-finalize (anh phan "PHA VO" S155) — containment 2 lop render+payload - tests: +4 (566 total, 0 fail) — schema-invariant 7 bang + codegen format (fault-inject 2 chieu chung co rang) Reviewer: PASS_WITH_FLAGS 10 issue -> 8 fixed cung commit, 2 giu trang thai (F-1 acceptance đo-CO cho prod; F-4 ke thua y PE prod) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@ -4,6 +4,7 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Application.Contracts.Services;
|
||||
using SolutionErp.Application.ContractSigningPlans.Services;
|
||||
using SolutionErp.Application.Forms.Services;
|
||||
using SolutionErp.Application.Hrm.Services;
|
||||
using SolutionErp.Application.Master.Suppliers.Import;
|
||||
@ -37,6 +38,8 @@ public static class DependencyInjection
|
||||
services.AddScoped<IContractWorkflowService, ContractWorkflowService>();
|
||||
services.AddScoped<IPurchaseEvaluationWorkflowService, PurchaseEvaluationWorkflowService>();
|
||||
services.AddScoped<IPurchaseEvaluationCodeGenerator, PurchaseEvaluationCodeGenerator>();
|
||||
// [W1 Mig 69 — S161] mã phiếu KHKK/{YYYY}/{Seq:D3} qua WorkflowAppCodeSequences dùng chung.
|
||||
services.AddScoped<IContractSigningPlanCodeGenerator, ContractSigningPlanCodeGenerator>();
|
||||
services.AddScoped<IEmployeeCodeGenerator, EmployeeCodeGenerator>();
|
||||
services.AddScoped<IContractExcelExporter, ContractExcelExporter>();
|
||||
services.AddScoped<IAttendanceReportExcelExporter, AttendanceReportExcelExporter>();
|
||||
|
||||
@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Domain.ApprovalWorkflowsV2;
|
||||
using SolutionErp.Domain.ContractSigningPlans;
|
||||
using SolutionErp.Domain.Contracts;
|
||||
using SolutionErp.Domain.Contracts.Details;
|
||||
using SolutionErp.Domain.Forms;
|
||||
@ -126,6 +127,15 @@ public class ApplicationDbContext
|
||||
// Phase 11 P11-B Wave 1 (Mig 42) — Số dư phép theo năm.
|
||||
public DbSet<LeaveBalance> LeaveBalances => Set<LeaveBalance>();
|
||||
|
||||
// [W1 Mig 69 — S161] GĐ2 "Kế hoạch ký kết HĐ" — 7 bảng (mirror IApplicationDbContext).
|
||||
public DbSet<ContractSigningPlan> ContractSigningPlans => Set<ContractSigningPlan>();
|
||||
public DbSet<ContractSigningPlanLine> ContractSigningPlanLines => Set<ContractSigningPlanLine>();
|
||||
public DbSet<ContractSigningPlanDossierItem> ContractSigningPlanDossierItems => Set<ContractSigningPlanDossierItem>();
|
||||
public DbSet<ContractSigningPlanLevelOpinion> ContractSigningPlanLevelOpinions => Set<ContractSigningPlanLevelOpinion>();
|
||||
public DbSet<ContractSigningPlanAttachment> ContractSigningPlanAttachments => Set<ContractSigningPlanAttachment>();
|
||||
public DbSet<ContractSigningPlanChangelog> ContractSigningPlanChangelogs => Set<ContractSigningPlanChangelog>();
|
||||
public DbSet<ContractSigningPlanApproval> ContractSigningPlanApprovals => Set<ContractSigningPlanApproval>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.ContractSigningPlans;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
// [W1 Mig 69 — S161] Lịch sử transition (1 row / lần chuyển phase).
|
||||
// Khuôn PurchaseEvaluationApprovalConfiguration (`PurchaseEvaluationConfiguration.cs:156-170`)
|
||||
// + index twin (PlanId, ApprovedAt).
|
||||
//
|
||||
// BaseEntity — append-only, KHÔNG soft-delete ⇒ KHÔNG HasQueryFilter (parity PE/Contract).
|
||||
// `ApprovalWorkflowLevelId` = loose-Guid KHÔNG FK vật lý: FK Restrict ở đây sẽ làm admin xoá
|
||||
// Level nổ 500 vì lịch sử duyệt, mà 3 site dọn dẹp trong ApprovalWorkflowV2AdminFeatures chỉ
|
||||
// biết họ bảng *LevelOpinions (không biết *Approvals) — xem chú thích trong entity.
|
||||
public class ContractSigningPlanApprovalConfiguration
|
||||
: IEntityTypeConfiguration<ContractSigningPlanApproval>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ContractSigningPlanApproval> b)
|
||||
{
|
||||
b.ToTable("ContractSigningPlanApprovals");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.FromPhase).HasConversion<int>();
|
||||
b.Property(x => x.ToPhase).HasConversion<int>();
|
||||
b.Property(x => x.Decision).HasConversion<int>();
|
||||
b.Property(x => x.Comment).HasMaxLength(1000);
|
||||
|
||||
b.HasIndex(x => new { x.ContractSigningPlanId, x.ApprovedAt });
|
||||
b.HasIndex(x => x.ApprovalWorkflowLevelId);
|
||||
|
||||
b.HasOne(x => x.ContractSigningPlan)
|
||||
.WithMany(p => p.Approvals)
|
||||
.HasForeignKey(x => x.ContractSigningPlanId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.ContractSigningPlans;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
// [W1 Mig 69 — S161] File đính kèm phiếu Kế hoạch ký kết.
|
||||
// Độ dài chuỗi theo spec §②-4 (`:134-137`): FileName 500 / StoragePath 1000 / ContentType 200
|
||||
// — khuôn ProposalAttachmentConfiguration.cs:15-17 (RỘNG hơn PE 255/500/100 vì PE là bản cũ).
|
||||
//
|
||||
// FK Cascade Plan = đường cascade DUY NHẤT. `ContractSigningPlanDossierItemId` CỐ Ý chỉ
|
||||
// HasIndex, KHÔNG FK vật lý — khuôn PurchaseEvaluationAttachmentConfiguration
|
||||
// (`PurchaseEvaluationConfiguration.cs:199-200` chỉ HasIndex cho SupplierId):
|
||||
// Plan→DossierItem→Attachment + Plan→Attachment cùng Cascade = MULTIPLE CASCADE PATHS,
|
||||
// SQL Server từ chối tạo bảng.
|
||||
public class ContractSigningPlanAttachmentConfiguration
|
||||
: IEntityTypeConfiguration<ContractSigningPlanAttachment>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ContractSigningPlanAttachment> b)
|
||||
{
|
||||
b.ToTable("ContractSigningPlanAttachments");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.FileName).HasMaxLength(500).IsRequired();
|
||||
b.Property(x => x.StoragePath).HasMaxLength(1000).IsRequired();
|
||||
b.Property(x => x.ContentType).HasMaxLength(200).IsRequired();
|
||||
b.Property(x => x.Purpose).HasConversion<int>();
|
||||
b.Property(x => x.Note).HasMaxLength(1000);
|
||||
|
||||
b.HasIndex(x => x.ContractSigningPlanId);
|
||||
b.HasIndex(x => x.ContractSigningPlanDossierItemId);
|
||||
|
||||
b.HasOne(x => x.ContractSigningPlan)
|
||||
.WithMany(p => p.Attachments)
|
||||
.HasForeignKey(x => x.ContractSigningPlanId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasQueryFilter(x => !x.IsDeleted);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.ContractSigningPlans;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
// [W1 Mig 69 — S161] Nhật ký thay đổi phiếu Kế hoạch ký kết — CÓ TỪ NGÀY 1
|
||||
// (tiền lệ PE Mig 60/61: thiếu changelog thì không backfill lại được lịch sử).
|
||||
//
|
||||
// Khuôn PurchaseEvaluationChangelogConfiguration (`PurchaseEvaluationConfiguration.cs:172-190`),
|
||||
// độ dài chuỗi theo spec §②-4: Summary 1000 (PE để 500) · ContextNote 2000 · UserName 200 ·
|
||||
// FieldChangesJson nvarchar(max) (JSON tự do, KHÔNG index).
|
||||
//
|
||||
// BaseEntity — KHÔNG soft-delete ⇒ KHÔNG HasQueryFilter (append-only audit, parity PE/Contract).
|
||||
// Index twin (PlanId, EntityType) = vế mà `LogTransitionAsync` copy sang cần để lọc theo phạm vi.
|
||||
public class ContractSigningPlanChangelogConfiguration
|
||||
: IEntityTypeConfiguration<ContractSigningPlanChangelog>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ContractSigningPlanChangelog> b)
|
||||
{
|
||||
b.ToTable("ContractSigningPlanChangelogs");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.EntityType).HasConversion<int>();
|
||||
b.Property(x => x.Action).HasConversion<int>();
|
||||
b.Property(x => x.PhaseAtChange).HasConversion<int>();
|
||||
b.Property(x => x.UserName).HasMaxLength(200);
|
||||
b.Property(x => x.Summary).HasMaxLength(1000);
|
||||
b.Property(x => x.ContextNote).HasMaxLength(2000);
|
||||
b.Property(x => x.FieldChangesJson).HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasIndex(x => new { x.ContractSigningPlanId, x.CreatedAt });
|
||||
b.HasIndex(x => new { x.ContractSigningPlanId, x.EntityType });
|
||||
|
||||
b.HasOne(x => x.ContractSigningPlan)
|
||||
.WithMany(p => p.Changelogs)
|
||||
.HasForeignKey(x => x.ContractSigningPlanId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.ContractSigningPlans;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
// [W1 Mig 69 — S161 2026-07-29] Header "Kế hoạch ký kết HĐ" (GĐ2).
|
||||
// Khuôn PurchaseEvaluationConfiguration.cs (HasQueryFilter :84 + FK Restrict workflow :70-73).
|
||||
//
|
||||
// loose-Guid KHÔNG FK vật lý: PurchaseEvaluationId / ProjectId / DepartmentId /
|
||||
// DrafterUserId — chỉ HasIndex (convention PE + Mig 49).
|
||||
public class ContractSigningPlanConfiguration : IEntityTypeConfiguration<ContractSigningPlan>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ContractSigningPlan> b)
|
||||
{
|
||||
b.ToTable("ContractSigningPlans");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.MaKeHoach).HasMaxLength(50); // "KHKK/2026/001"
|
||||
b.Property(x => x.Phase).HasConversion<int>();
|
||||
b.Property(x => x.HoSoLink).HasMaxLength(1000);
|
||||
b.Property(x => x.GhiChu).HasMaxLength(2000);
|
||||
|
||||
// Mã phiếu UNIQUE khi đã gen (gen lúc Submit ⇒ nháp còn null) — mirror
|
||||
// PurchaseEvaluationConfiguration.cs:56 / ProposalConfiguration.cs:23.
|
||||
b.HasIndex(x => x.MaKeHoach).IsUnique().HasFilter("[MaKeHoach] IS NOT NULL");
|
||||
|
||||
b.HasIndex(x => x.PurchaseEvaluationId);
|
||||
b.HasIndex(x => x.ProjectId);
|
||||
b.HasIndex(x => x.DepartmentId);
|
||||
b.HasIndex(x => x.DrafterUserId);
|
||||
b.HasIndex(x => new { x.Phase, x.IsDeleted });
|
||||
b.HasIndex(x => x.ApprovalWorkflowId);
|
||||
|
||||
// FK Restrict sang bảng V2 dùng chung: KHÔNG cho xoá quy trình khi còn phiếu pin
|
||||
// (mirror PE `PurchaseEvaluationConfiguration.cs:70-73`).
|
||||
b.HasOne<SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflow>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.ApprovalWorkflowId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasQueryFilter(x => !x.IsDeleted);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.ContractSigningPlans;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
// [W1 Mig 69 — S161] Danh mục căn cứ (b.8-9): mẫu vật liệu / shopdrawing + kết quả TVGS.
|
||||
public class ContractSigningPlanDossierItemConfiguration
|
||||
: IEntityTypeConfiguration<ContractSigningPlanDossierItem>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ContractSigningPlanDossierItem> b)
|
||||
{
|
||||
b.ToTable("ContractSigningPlanDossierItems");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Kind).HasConversion<int>();
|
||||
b.Property(x => x.Status).HasConversion<int>();
|
||||
b.Property(x => x.Name).HasMaxLength(500).IsRequired();
|
||||
b.Property(x => x.TvgsName).HasMaxLength(200);
|
||||
b.Property(x => x.Note).HasMaxLength(1000);
|
||||
|
||||
b.HasIndex(x => x.ContractSigningPlanId);
|
||||
b.HasIndex(x => x.SupplierId);
|
||||
|
||||
b.HasOne(x => x.ContractSigningPlan)
|
||||
.WithMany(p => p.DossierItems)
|
||||
.HasForeignKey(x => x.ContractSigningPlanId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasQueryFilter(x => !x.IsDeleted);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.ContractSigningPlans;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
// [W1 Mig 69 — S161] Cookie-cutter mirror ContractLevelOpinionConfiguration.cs (Mig 33)
|
||||
// — twin thứ 8 của họ `*LevelOpinion`.
|
||||
//
|
||||
// UNIQUE (PlanId, LevelId) = 1 row/level/phiếu; UPSERT (không soft-delete) ⇒ index
|
||||
// KHÔNG filter, ĐÚNG như 7 twin còn lại (khác Lines: ở đó có xoá-mềm-rồi-thêm-lại thật).
|
||||
// FK Cascade Plan + Restrict Level (bảo vệ chữ ký khi admin sửa quy trình).
|
||||
// CỐ Ý KHÔNG HasQueryFilter — xem chú thích trong entity (site dọn chữ ký MỒ CÔI phải
|
||||
// thấy được row soft-deleted, nếu không FK Restrict sẽ nổ 500 lúc admin xoá Cấp).
|
||||
public class ContractSigningPlanLevelOpinionConfiguration
|
||||
: IEntityTypeConfiguration<ContractSigningPlanLevelOpinion>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ContractSigningPlanLevelOpinion> e)
|
||||
{
|
||||
e.ToTable("ContractSigningPlanLevelOpinions");
|
||||
e.HasKey(x => x.Id);
|
||||
|
||||
e.Property(x => x.Comment).HasMaxLength(2000);
|
||||
e.Property(x => x.SignedByFullName).HasMaxLength(200).IsRequired();
|
||||
|
||||
e.HasOne(x => x.ContractSigningPlan)
|
||||
.WithMany(p => p.LevelOpinions)
|
||||
.HasForeignKey(x => x.ContractSigningPlanId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
e.HasOne(x => x.Level)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.ApprovalWorkflowLevelId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
e.HasIndex(x => new { x.ContractSigningPlanId, x.ApprovalWorkflowLevelId }).IsUnique();
|
||||
e.HasIndex(x => x.ApprovalWorkflowLevelId);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.ContractSigningPlans;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
// [W1 Mig 69 — S161] Dòng giá per-NCC.
|
||||
// 🔴 UNIQUE (PlanId, SupplierId) PHẢI filtered `WHERE [IsDeleted] = 0` (gotcha #57 —
|
||||
// 7× cumulative: Mig 43/45/47/64). Xoá mềm 1 NCC rồi thêm lại NCC đó = phải được.
|
||||
public class ContractSigningPlanLineConfiguration : IEntityTypeConfiguration<ContractSigningPlanLine>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ContractSigningPlanLine> b)
|
||||
{
|
||||
b.ToTable("ContractSigningPlanLines");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.PeReferenceAmount).HasPrecision(18, 2);
|
||||
b.Property(x => x.ProposedAmount).HasPrecision(18, 2);
|
||||
b.Property(x => x.ApprovedAmount).HasPrecision(18, 2);
|
||||
b.Property(x => x.Note).HasMaxLength(1000);
|
||||
|
||||
b.HasIndex(x => new { x.ContractSigningPlanId, x.SupplierId })
|
||||
.IsUnique()
|
||||
.HasFilter("[IsDeleted] = 0");
|
||||
b.HasIndex(x => x.SupplierId);
|
||||
b.HasIndex(x => x.ContractId);
|
||||
|
||||
b.HasOne(x => x.ContractSigningPlan)
|
||||
.WithMany(p => p.Lines)
|
||||
.HasForeignKey(x => x.ContractSigningPlanId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasQueryFilter(x => !x.IsDeleted);
|
||||
}
|
||||
}
|
||||
@ -1769,8 +1769,9 @@ public static class DbInitializer
|
||||
(MenuKeys.CatalogWorkItems,"Hạng mục công việc", MenuKeys.Catalogs, 4, "ListChecks"),
|
||||
// [S57] Order 30→31: nhường slot 30 cho nhóm "Cá nhân" (đứng ngay sau Văn phòng số = 29, mirror Puro).
|
||||
(MenuKeys.Contracts, "Duyệt hợp đồng", null, 31, "FileText"), // [S159] rename theo 4-GĐ (owner tab strip)
|
||||
// [S159] 2 root PLACEHOLDER GĐ2/GĐ4 — menu = 4 giai đoạn (owner). Ngoài MenuKeys.All,
|
||||
// FE staticMap trỏ /dashboard. Prod đã INSERT trực tiếp cùng ngày (idempotent ở đây).
|
||||
// [S159] 2 root PLACEHOLDER GĐ2/GĐ4 — menu = 4 giai đoạn (owner). FE staticMap trỏ
|
||||
// /dashboard. Prod đã INSERT trực tiếp cùng ngày (idempotent ở đây).
|
||||
// [S161 W1 — F-3] `KeHoachKyKet` nay ĐÃ VÀO MenuKeys.All (policy sống); `HopDongCung` vẫn ngoài.
|
||||
(MenuKeys.KeHoachKyKet, "Kế hoạch ký kết HĐ", null, 26, "FileSignature"),
|
||||
(MenuKeys.HopDongCung, "Hợp đồng cứng", null, 32, "Stamp"),
|
||||
// [S159-đợt3] skeleton menu GĐ2/GĐ4 (owner vẽ: "viết hoa + phân list như
|
||||
@ -2101,6 +2102,13 @@ public static class DbInitializer
|
||||
// MỌI role khác (seeder chỉ đụng role Procurement). Dashboard/Reports KHÔNG cấp (anh chốt bỏ).
|
||||
await SeedProcurementMasterAccessAsync(db, roleManager, logger);
|
||||
|
||||
// [W1 Mig 69 — S161 2026-07-29] GĐ2 "Kế hoạch ký kết HĐ" golive: nâng quyền GHI
|
||||
// (CanCreate+CanUpdate 13/13 role — O-A "hiển thị hết để góp ý"; CanDelete chỉ
|
||||
// Drafter+Admin) trên root KeHoachKyKet + 6 leaf Khkk_*. CHẠY SAU revoke để THẮNG.
|
||||
// 🔴 UPGRADE-IF-EXISTS bắt buộc (review F-S1 4-lane hội tụ): row đã seed CanRead-only
|
||||
// @S159 ⇒ nhánh insert skip-existing = NO-OP im lặng ⇒ 403 giết dry-run (gotcha #44).
|
||||
await SeedKeHoachKyKetAccessAsync(db, roleManager, logger);
|
||||
|
||||
// [S65 2026-06-16] CHẠY SAU revoke để THẮNG: mở lại quyền XEM "Hồ sơ Nhân sự"
|
||||
// (+ root nhóm "Nhân sự") cho MỌI role — anh chốt public module Nhân sự cho
|
||||
// user thường tra cứu hồ sơ. Dashboard NS (Hrm_Dashboard) + 6 catalog
|
||||
@ -2397,6 +2405,72 @@ public static class DbInitializer
|
||||
}
|
||||
}
|
||||
|
||||
// [W1 Mig 69 — S161 2026-07-29] GĐ2 "Kế hoạch ký kết HĐ" — nâng quyền GHI cho MỌI role
|
||||
// trên root KeHoachKyKet + 6 leaf Khkk_* (O-A anh chốt "hiển thị hết để góp ý" ⇒ 13/13
|
||||
// role CanCreate+CanUpdate; CanDelete CHỈ Drafter+Admin — spec W1 §②-8).
|
||||
// Khuôn = SeedProcurementMasterAccessAsync (S118) MỞ RỘNG thêm vế CanDelete (khuôn gốc
|
||||
// không có — chốt-cuối S160 dặn). UPGRADE-ONLY: row tồn tại → NÂNG cờ desired, KHÔNG hạ
|
||||
// cờ nào; row chưa có (fresh DB) → tạo đủ cờ. CẤM SQL tay (#84 — grant tay bị revoker lật).
|
||||
// Thu hồi khi cần: xóa call ở SeedAsync — revoke sẽ tự che lại lần seed kế.
|
||||
private static async Task SeedKeHoachKyKetAccessAsync(
|
||||
ApplicationDbContext db, RoleManager<Role> roleManager, ILogger logger)
|
||||
{
|
||||
// Root policy-key + 6 leaf menu-key (khớp bộ seed menu-row S159; Khkk_G1 = group
|
||||
// điều hướng, CanRead-all đã có từ nhánh read-only — KHÔNG cần cờ ghi).
|
||||
var keys = new[]
|
||||
{
|
||||
MenuKeys.KeHoachKyKet,
|
||||
"Khkk_WfView", "Khkk_List", "Khkk_Create", "Khkk_Pending",
|
||||
"Khkk_Approved", "Khkk_Deleted",
|
||||
};
|
||||
|
||||
var roles = await roleManager.Roles.ToListAsync();
|
||||
var existing = (await db.Permissions
|
||||
.Where(p => keys.Contains(p.MenuKey))
|
||||
.ToListAsync())
|
||||
.ToDictionary(p => (p.RoleId, p.MenuKey));
|
||||
|
||||
var added = 0;
|
||||
var upgraded = 0;
|
||||
foreach (var role in roles)
|
||||
{
|
||||
var allowDelete = role.Name == AppRoles.Admin || role.Name == AppRoles.Drafter;
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (existing.TryGetValue((role.Id, key), out var row))
|
||||
{
|
||||
var changed = false;
|
||||
if (!row.CanRead) { row.CanRead = true; changed = true; }
|
||||
if (!row.CanCreate) { row.CanCreate = true; changed = true; }
|
||||
if (!row.CanUpdate) { row.CanUpdate = true; changed = true; }
|
||||
if (allowDelete && !row.CanDelete) { row.CanDelete = true; changed = true; }
|
||||
if (changed) upgraded++;
|
||||
continue;
|
||||
}
|
||||
|
||||
db.Permissions.Add(new Permission
|
||||
{
|
||||
RoleId = role.Id,
|
||||
MenuKey = key,
|
||||
CanRead = true,
|
||||
CanCreate = true,
|
||||
CanUpdate = true,
|
||||
CanDelete = allowDelete,
|
||||
});
|
||||
added++;
|
||||
}
|
||||
}
|
||||
|
||||
if (added > 0 || upgraded > 0)
|
||||
{
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation(
|
||||
"Seeded KeHoachKyKet access: {Added} added + {Upgraded} upgraded " +
|
||||
"(W1 GĐ2 golive — 13/13 role C+U, Delete=Drafter/Admin; UPGRADE-if-exists F-S1)",
|
||||
added, upgraded);
|
||||
}
|
||||
}
|
||||
|
||||
// [S65 2026-06-16] Mở quyền XEM (Read-only) "Hồ sơ Nhân sự" cho MỌI role: anh chốt
|
||||
// public module Nhân sự (trọng tâm Hồ sơ NS) để user thường tra cứu hồ sơ nhân sự.
|
||||
// CHẠY SAU RevokeTemporarilyHiddenModulesAsync (SeedAsync) để THẮNG revoke: revoke
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,372 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddContractSigningPlans : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ContractSigningPlans",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
MaKeHoach = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||
PurchaseEvaluationId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ProjectId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
DepartmentId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DrafterUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Phase = table.Column<int>(type: "int", nullable: false),
|
||||
ApprovalWorkflowId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
CurrentWorkflowStepIndex = table.Column<int>(type: "int", nullable: true),
|
||||
CurrentApprovalLevelOrder = table.Column<int>(type: "int", nullable: true),
|
||||
SlaDeadline = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
HoSoLink = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
GhiChu = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
|
||||
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_ContractSigningPlans", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ContractSigningPlans_ApprovalWorkflows_ApprovalWorkflowId",
|
||||
column: x => x.ApprovalWorkflowId,
|
||||
principalTable: "ApprovalWorkflows",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ContractSigningPlanApprovals",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ContractSigningPlanId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
FromPhase = table.Column<int>(type: "int", nullable: false),
|
||||
ToPhase = table.Column<int>(type: "int", nullable: false),
|
||||
Decision = table.Column<int>(type: "int", nullable: false),
|
||||
Comment = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
ApprovalWorkflowLevelId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
ApprovedByUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
ApprovedAt = table.Column<DateTime>(type: "datetime2", 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_ContractSigningPlanApprovals", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ContractSigningPlanApprovals_ContractSigningPlans_ContractSigningPlanId",
|
||||
column: x => x.ContractSigningPlanId,
|
||||
principalTable: "ContractSigningPlans",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ContractSigningPlanAttachments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ContractSigningPlanId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ContractSigningPlanDossierItemId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
FileName = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||
StoragePath = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
|
||||
FileSize = table.Column<long>(type: "bigint", nullable: false),
|
||||
ContentType = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||
Purpose = table.Column<int>(type: "int", nullable: false),
|
||||
Note = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
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_ContractSigningPlanAttachments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ContractSigningPlanAttachments_ContractSigningPlans_ContractSigningPlanId",
|
||||
column: x => x.ContractSigningPlanId,
|
||||
principalTable: "ContractSigningPlans",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ContractSigningPlanChangelogs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ContractSigningPlanId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
EntityType = table.Column<int>(type: "int", nullable: false),
|
||||
EntityId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
Action = table.Column<int>(type: "int", nullable: false),
|
||||
PhaseAtChange = table.Column<int>(type: "int", nullable: true),
|
||||
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
UserName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||
Summary = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
FieldChangesJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
ContextNote = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
|
||||
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_ContractSigningPlanChangelogs", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ContractSigningPlanChangelogs_ContractSigningPlans_ContractSigningPlanId",
|
||||
column: x => x.ContractSigningPlanId,
|
||||
principalTable: "ContractSigningPlans",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ContractSigningPlanDossierItems",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ContractSigningPlanId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Kind = table.Column<int>(type: "int", nullable: false),
|
||||
SupplierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
Name = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||
Status = table.Column<int>(type: "int", nullable: false),
|
||||
TvgsName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||
TvgsResultAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
Note = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
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_ContractSigningPlanDossierItems", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ContractSigningPlanDossierItems_ContractSigningPlans_ContractSigningPlanId",
|
||||
column: x => x.ContractSigningPlanId,
|
||||
principalTable: "ContractSigningPlans",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ContractSigningPlanLevelOpinions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ContractSigningPlanId = 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_ContractSigningPlanLevelOpinions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ContractSigningPlanLevelOpinions_ApprovalWorkflowLevels_ApprovalWorkflowLevelId",
|
||||
column: x => x.ApprovalWorkflowLevelId,
|
||||
principalTable: "ApprovalWorkflowLevels",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_ContractSigningPlanLevelOpinions_ContractSigningPlans_ContractSigningPlanId",
|
||||
column: x => x.ContractSigningPlanId,
|
||||
principalTable: "ContractSigningPlans",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ContractSigningPlanLines",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ContractSigningPlanId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
SupplierId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
PeReferenceAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
ProposedAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
ApprovedAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true),
|
||||
ContractId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
Note = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
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_ContractSigningPlanLines", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ContractSigningPlanLines_ContractSigningPlans_ContractSigningPlanId",
|
||||
column: x => x.ContractSigningPlanId,
|
||||
principalTable: "ContractSigningPlans",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlanApprovals_ApprovalWorkflowLevelId",
|
||||
table: "ContractSigningPlanApprovals",
|
||||
column: "ApprovalWorkflowLevelId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlanApprovals_ContractSigningPlanId_ApprovedAt",
|
||||
table: "ContractSigningPlanApprovals",
|
||||
columns: new[] { "ContractSigningPlanId", "ApprovedAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlanAttachments_ContractSigningPlanDossierItemId",
|
||||
table: "ContractSigningPlanAttachments",
|
||||
column: "ContractSigningPlanDossierItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlanAttachments_ContractSigningPlanId",
|
||||
table: "ContractSigningPlanAttachments",
|
||||
column: "ContractSigningPlanId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlanChangelogs_ContractSigningPlanId_CreatedAt",
|
||||
table: "ContractSigningPlanChangelogs",
|
||||
columns: new[] { "ContractSigningPlanId", "CreatedAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlanChangelogs_ContractSigningPlanId_EntityType",
|
||||
table: "ContractSigningPlanChangelogs",
|
||||
columns: new[] { "ContractSigningPlanId", "EntityType" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlanDossierItems_ContractSigningPlanId",
|
||||
table: "ContractSigningPlanDossierItems",
|
||||
column: "ContractSigningPlanId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlanDossierItems_SupplierId",
|
||||
table: "ContractSigningPlanDossierItems",
|
||||
column: "SupplierId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlanLevelOpinions_ApprovalWorkflowLevelId",
|
||||
table: "ContractSigningPlanLevelOpinions",
|
||||
column: "ApprovalWorkflowLevelId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlanLevelOpinions_ContractSigningPlanId_ApprovalWorkflowLevelId",
|
||||
table: "ContractSigningPlanLevelOpinions",
|
||||
columns: new[] { "ContractSigningPlanId", "ApprovalWorkflowLevelId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlanLines_ContractId",
|
||||
table: "ContractSigningPlanLines",
|
||||
column: "ContractId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlanLines_ContractSigningPlanId_SupplierId",
|
||||
table: "ContractSigningPlanLines",
|
||||
columns: new[] { "ContractSigningPlanId", "SupplierId" },
|
||||
unique: true,
|
||||
filter: "[IsDeleted] = 0");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlanLines_SupplierId",
|
||||
table: "ContractSigningPlanLines",
|
||||
column: "SupplierId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlans_ApprovalWorkflowId",
|
||||
table: "ContractSigningPlans",
|
||||
column: "ApprovalWorkflowId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlans_DepartmentId",
|
||||
table: "ContractSigningPlans",
|
||||
column: "DepartmentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlans_DrafterUserId",
|
||||
table: "ContractSigningPlans",
|
||||
column: "DrafterUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlans_MaKeHoach",
|
||||
table: "ContractSigningPlans",
|
||||
column: "MaKeHoach",
|
||||
unique: true,
|
||||
filter: "[MaKeHoach] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlans_Phase_IsDeleted",
|
||||
table: "ContractSigningPlans",
|
||||
columns: new[] { "Phase", "IsDeleted" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlans_ProjectId",
|
||||
table: "ContractSigningPlans",
|
||||
column: "ProjectId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractSigningPlans_PurchaseEvaluationId",
|
||||
table: "ContractSigningPlans",
|
||||
column: "PurchaseEvaluationId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ContractSigningPlanApprovals");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ContractSigningPlanAttachments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ContractSigningPlanChangelogs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ContractSigningPlanDossierItems");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ContractSigningPlanLevelOpinions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ContractSigningPlanLines");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ContractSigningPlans");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -312,6 +312,459 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.ToTable("ApprovalWorkflowSteps", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlan", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("ApprovalWorkflowId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int?>("CurrentApprovalLevelOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("CurrentWorkflowStepIndex")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("DepartmentId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("DrafterUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("GhiChu")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.Property<string>("HoSoLink")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("MaKeHoach")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<int>("Phase")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("PurchaseEvaluationId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("SlaDeadline")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApprovalWorkflowId");
|
||||
|
||||
b.HasIndex("DepartmentId");
|
||||
|
||||
b.HasIndex("DrafterUserId");
|
||||
|
||||
b.HasIndex("MaKeHoach")
|
||||
.IsUnique()
|
||||
.HasFilter("[MaKeHoach] IS NOT NULL");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.HasIndex("PurchaseEvaluationId");
|
||||
|
||||
b.HasIndex("Phase", "IsDeleted");
|
||||
|
||||
b.ToTable("ContractSigningPlans", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlanApproval", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("ApprovalWorkflowLevelId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("ApprovedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("ApprovedByUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<Guid>("ContractSigningPlanId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Decision")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("FromPhase")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ToPhase")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApprovalWorkflowLevelId");
|
||||
|
||||
b.HasIndex("ContractSigningPlanId", "ApprovedAt");
|
||||
|
||||
b.ToTable("ContractSigningPlanApprovals", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlanAttachment", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<Guid?>("ContractSigningPlanDossierItemId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ContractSigningPlanId")
|
||||
.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<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<long>("FileSize")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Note")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<int>("Purpose")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("StoragePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContractSigningPlanDossierItemId");
|
||||
|
||||
b.HasIndex("ContractSigningPlanId");
|
||||
|
||||
b.ToTable("ContractSigningPlanAttachments", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlanChangelog", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Action")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContextNote")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.Property<Guid>("ContractSigningPlanId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("EntityId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("EntityType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("FieldChangesJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int?>("PhaseAtChange")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContractSigningPlanId", "CreatedAt");
|
||||
|
||||
b.HasIndex("ContractSigningPlanId", "EntityType");
|
||||
|
||||
b.ToTable("ContractSigningPlanChangelogs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlanDossierItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ContractSigningPlanId")
|
||||
.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<int>("Kind")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("Note")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid?>("SupplierId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("TvgsName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<DateTime?>("TvgsResultAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContractSigningPlanId");
|
||||
|
||||
b.HasIndex("SupplierId");
|
||||
|
||||
b.ToTable("ContractSigningPlanDossierItems", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlanLevelOpinion", 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>("ContractSigningPlanId")
|
||||
.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("ContractSigningPlanId", "ApprovalWorkflowLevelId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ContractSigningPlanLevelOpinions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlanLine", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<decimal?>("ApprovedAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<Guid?>("ContractId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ContractSigningPlanId")
|
||||
.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<string>("Note")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<decimal>("PeReferenceAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("ProposedAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<Guid>("SupplierId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ContractId");
|
||||
|
||||
b.HasIndex("SupplierId");
|
||||
|
||||
b.HasIndex("ContractSigningPlanId", "SupplierId")
|
||||
.IsUnique()
|
||||
.HasFilter("[IsDeleted] = 0");
|
||||
|
||||
b.ToTable("ContractSigningPlanLines", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Contracts.Contract", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -5683,6 +6136,88 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.Navigation("ApprovalWorkflow");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlan", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflow", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("ApprovalWorkflowId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlanApproval", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlan", "ContractSigningPlan")
|
||||
.WithMany("Approvals")
|
||||
.HasForeignKey("ContractSigningPlanId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContractSigningPlan");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlanAttachment", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlan", "ContractSigningPlan")
|
||||
.WithMany("Attachments")
|
||||
.HasForeignKey("ContractSigningPlanId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContractSigningPlan");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlanChangelog", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlan", "ContractSigningPlan")
|
||||
.WithMany("Changelogs")
|
||||
.HasForeignKey("ContractSigningPlanId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContractSigningPlan");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlanDossierItem", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlan", "ContractSigningPlan")
|
||||
.WithMany("DossierItems")
|
||||
.HasForeignKey("ContractSigningPlanId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContractSigningPlan");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlanLevelOpinion", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflowLevel", "Level")
|
||||
.WithMany()
|
||||
.HasForeignKey("ApprovalWorkflowLevelId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlan", "ContractSigningPlan")
|
||||
.WithMany("LevelOpinions")
|
||||
.HasForeignKey("ContractSigningPlanId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContractSigningPlan");
|
||||
|
||||
b.Navigation("Level");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlanLine", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlan", "ContractSigningPlan")
|
||||
.WithMany("Lines")
|
||||
.HasForeignKey("ContractSigningPlanId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ContractSigningPlan");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Contracts.Contract", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflow", null)
|
||||
@ -6275,6 +6810,21 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.Navigation("Levels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.ContractSigningPlans.ContractSigningPlan", b =>
|
||||
{
|
||||
b.Navigation("Approvals");
|
||||
|
||||
b.Navigation("Attachments");
|
||||
|
||||
b.Navigation("Changelogs");
|
||||
|
||||
b.Navigation("DossierItems");
|
||||
|
||||
b.Navigation("LevelOpinions");
|
||||
|
||||
b.Navigation("Lines");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Contracts.Contract", b =>
|
||||
{
|
||||
b.Navigation("Approvals");
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
using System.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Application.ContractSigningPlans.Services;
|
||||
using SolutionErp.Domain.Office;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Services;
|
||||
|
||||
// [W1 Mig 69 — S161 2026-07-29] Mã phiếu KHKK — format KHKK/{YYYY}/{Seq:D3} (anh OK @S161).
|
||||
// Mirror PurchaseEvaluationCodeGenerator (SERIALIZABLE + LastSeq += 1), khác 1 điểm:
|
||||
// sequence sống trong bảng WorkflowAppCodeSequences DÙNG CHUNG 4 module Office
|
||||
// (per-prefix row "KHKK/{YYYY}" — KHÔNG bảng sequence riêng, spec W1 §②-9).
|
||||
public class ContractSigningPlanCodeGenerator(IApplicationDbContext db, IDateTime dateTime)
|
||||
: IContractSigningPlanCodeGenerator
|
||||
{
|
||||
public async Task<string> GenerateAsync(CancellationToken ct = default)
|
||||
{
|
||||
var year = dateTime.UtcNow.Year;
|
||||
var prefix = $"KHKK/{year}";
|
||||
|
||||
// Transaction SERIALIZABLE chống race 2 người Submit cùng lúc (mirror PE codegen).
|
||||
var context = (DbContext)db;
|
||||
await using var tx = await context.Database
|
||||
.BeginTransactionAsync(IsolationLevel.Serializable, ct);
|
||||
try
|
||||
{
|
||||
var seq = await db.WorkflowAppCodeSequences
|
||||
.FirstOrDefaultAsync(s => s.Prefix == prefix, ct);
|
||||
if (seq is null)
|
||||
{
|
||||
seq = new WorkflowAppCodeSequence
|
||||
{
|
||||
Prefix = prefix, LastSeq = 1, UpdatedAt = dateTime.UtcNow,
|
||||
};
|
||||
db.WorkflowAppCodeSequences.Add(seq);
|
||||
}
|
||||
else
|
||||
{
|
||||
seq.LastSeq += 1;
|
||||
seq.UpdatedAt = dateTime.UtcNow;
|
||||
}
|
||||
await db.SaveChangesAsync(ct);
|
||||
await tx.CommitAsync(ct);
|
||||
return $"{prefix}/{seq.LastSeq:D3}";
|
||||
}
|
||||
catch
|
||||
{
|
||||
await tx.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user