From 2c6f0cabfb85acd50ef0bd316846cd33a55f4ed5 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Thu, 23 Apr 2026 16:37:55 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20Domain+Infra:=20PurchaseEvaluation?= =?UTF-8?q?=20module=20=E2=80=94=2010=20b=E1=BA=A3ng=20+=202=20workflow=20?= =?UTF-8?q?seed=20(migration=2012)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Module Duyệt NCC (tiền-HĐ): phiếu trình duyệt so sánh giá N NCC × M hạng mục trước khi ký HĐ. 2 quy trình: A DuyetNcc (3-step: Purchasing→CCM→CEO), B DuyetNccPhuongAn (5-step: Purchasing→DựÁn→CCM→CEO PA→CEO NCC). Domain (7 core + 3 workflow admin): - PurchaseEvaluation (header, AuditableEntity, pin WorkflowDefinitionId, SelectedSupplierId, PaymentTerms JSON, ContractId? FK kế thừa) - PurchaseEvaluationSupplier (N:M Phiếu × Supplier + contact + payment term) - PurchaseEvaluationDetail (hạng mục + ngân sách, group A.I/A.II/...) - PurchaseEvaluationQuote (báo giá per NCC per hạng mục + IsSelected) - PurchaseEvaluationApproval (workflow history, reuse ApprovalDecision) - PurchaseEvaluationChangelog (audit log, reuse ChangelogAction) - PurchaseEvaluationAttachment (file upload — báo giá NCC + spec...) - PurchaseEvaluationWorkflowDefinition/Step/StepApprover (config y như HĐ, tách table riêng vì Phase là PurchaseEvaluationPhase enum riêng) Policy: - PurchaseEvaluationPolicy record + PurchaseEvaluationPolicies.NccOnly/ NccWithPlan (default hardcoded) + FromDefinition(def) build runtime policy từ DB admin-authored. Default SLA: soạn 3d, step 1-2d, CEO 1d. EF: 10 configurations với index phase+isDeleted, SupplierId, ProjectId, SlaDeadline, WorkflowDefinitionId, ContractId. UX index (PeId, SupplierId) + (DetailId, SupplierId). HasQueryFilter soft delete cho header. Migration 12 AddPurchaseEvaluations tạo 10 bảng. Idempotent seed: - SeedMenuTreeAsync +13 menu item (Pe_* root + 2 group + 6 action leaf + PeWorkflows root + 2 admin leaf) - SeedPurchaseEvaluationWorkflowsAsync seed QT-DN-A-v01 + QT-DN-B-v01 --- .../Interfaces/IApplicationDbContext.cs | 13 + .../SolutionErp.Domain/Identity/MenuKeys.cs | 24 +- .../PurchaseEvaluations/PurchaseEvaluation.cs | 36 + .../PurchaseEvaluationApproval.cs | 18 + .../PurchaseEvaluationAttachment.cs | 25 + .../PurchaseEvaluationChangelog.cs | 33 + .../PurchaseEvaluationDetail.cs | 24 + .../PurchaseEvaluationPhase.cs | 20 + .../PurchaseEvaluationPolicy.cs | 203 ++ .../PurchaseEvaluationQuote.cs | 20 + .../PurchaseEvaluationSupplier.cs | 22 + .../PurchaseEvaluationType.cs | 10 + .../PurchaseEvaluationWorkflowDefinition.cs | 46 + .../Persistence/ApplicationDbContext.cs | 12 + .../PurchaseEvaluationConfiguration.cs | 205 ++ .../Persistence/DbInitializer.cs | 102 + ...3093608_AddPurchaseEvaluations.Designer.cs | 2901 +++++++++++++++++ .../20260423093608_AddPurchaseEvaluations.cs | 455 +++ .../ApplicationDbContextModelSnapshot.cs | 716 ++++ 19 files changed, 4884 insertions(+), 1 deletion(-) create mode 100644 src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluation.cs create mode 100644 src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationApproval.cs create mode 100644 src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationAttachment.cs create mode 100644 src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationChangelog.cs create mode 100644 src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationDetail.cs create mode 100644 src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationPhase.cs create mode 100644 src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationPolicy.cs create mode 100644 src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationQuote.cs create mode 100644 src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationSupplier.cs create mode 100644 src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationType.cs create mode 100644 src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationWorkflowDefinition.cs create mode 100644 src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/PurchaseEvaluationConfiguration.cs create mode 100644 src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260423093608_AddPurchaseEvaluations.Designer.cs create mode 100644 src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260423093608_AddPurchaseEvaluations.cs diff --git a/src/Backend/SolutionErp.Application/Common/Interfaces/IApplicationDbContext.cs b/src/Backend/SolutionErp.Application/Common/Interfaces/IApplicationDbContext.cs index 1d0a648..8b4d539 100644 --- a/src/Backend/SolutionErp.Application/Common/Interfaces/IApplicationDbContext.cs +++ b/src/Backend/SolutionErp.Application/Common/Interfaces/IApplicationDbContext.cs @@ -6,6 +6,7 @@ using SolutionErp.Domain.Identity; using SolutionErp.Domain.Master; using SolutionErp.Domain.Master.Catalogs; using SolutionErp.Domain.Notifications; +using SolutionErp.Domain.PurchaseEvaluations; namespace SolutionErp.Application.Common.Interfaces; @@ -44,5 +45,17 @@ public interface IApplicationDbContext DbSet NguyenTacNccDetails { get; } DbSet NguyenTacDvDetails { get; } + // Module Duyệt NCC (tiền-HĐ) — 7 core + 3 workflow config + DbSet PurchaseEvaluations { get; } + DbSet PurchaseEvaluationSuppliers { get; } + DbSet PurchaseEvaluationDetails { get; } + DbSet PurchaseEvaluationQuotes { get; } + DbSet PurchaseEvaluationApprovals { get; } + DbSet PurchaseEvaluationChangelogs { get; } + DbSet PurchaseEvaluationAttachments { get; } + DbSet PurchaseEvaluationWorkflowDefinitions { get; } + DbSet PurchaseEvaluationWorkflowSteps { get; } + DbSet PurchaseEvaluationWorkflowStepApprovers { get; } + Task SaveChangesAsync(CancellationToken cancellationToken = default); } diff --git a/src/Backend/SolutionErp.Domain/Identity/MenuKeys.cs b/src/Backend/SolutionErp.Domain/Identity/MenuKeys.cs index 2bd9b25..91c23fc 100644 --- a/src/Backend/SolutionErp.Domain/Identity/MenuKeys.cs +++ b/src/Backend/SolutionErp.Domain/Identity/MenuKeys.cs @@ -42,13 +42,35 @@ public static class MenuKeys // → mở /system/workflows/{typeCode} (filter theo type thay vì tab). public static string WorkflowTypeLeaf(string typeCode) => $"Wf_{typeCode}"; + // ============================================================ + // Module Duyệt NCC (tiền-HĐ) — Pe_* prefix. 2 EvaluationType: + // DuyetNcc (A, NccOnly 3-step) + DuyetNccPhuongAn (B, NccWithPlan 5-step). + // Mỗi type có 3 action leaf (Danh sách / Thao tác / Duyệt) + 1 group. + // Workflow admin cho PE ở /system/pe-workflows/:typeCode. + // ============================================================ + public const string PurchaseEvaluations = "PurchaseEvaluations"; // root group + public const string PeWorkflows = "PeWorkflows"; // workflow admin root + + public static readonly string[] PurchaseEvaluationTypeCodes = + ["DuyetNcc", "DuyetNccPhuongAn"]; + + public static string PurchaseEvaluationGroup(string typeCode) => $"Pe_{typeCode}"; + public static string PurchaseEvaluationList(string typeCode) => $"Pe_{typeCode}_List"; + public static string PurchaseEvaluationCreate(string typeCode) => $"Pe_{typeCode}_Create"; + public static string PurchaseEvaluationPending(string typeCode) => $"Pe_{typeCode}_Pending"; + + // Workflow admin leaf per PE type — dưới PeWorkflows, click leaf mở + // /system/pe-workflows/{typeCode} + public static string PeWorkflowTypeLeaf(string typeCode) => $"PeWf_{typeCode}"; + public static readonly string[] All = [ Dashboard, Master, Suppliers, Projects, Departments, Catalogs, CatalogUnits, CatalogMaterials, CatalogServices, CatalogWorkItems, Contracts, Forms, Reports, - System, Users, Roles, Permissions, Workflows, + PurchaseEvaluations, + System, Users, Roles, Permissions, Workflows, PeWorkflows, ]; public static readonly string[] Actions = ["Read", "Create", "Update", "Delete"]; diff --git a/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluation.cs b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluation.cs new file mode 100644 index 0000000..4c37055 --- /dev/null +++ b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluation.cs @@ -0,0 +1,36 @@ +using SolutionErp.Domain.Common; + +namespace SolutionErp.Domain.PurchaseEvaluations; + +// Aggregate root cho phiếu Duyệt NCC — module tiền-HĐ. +// Sau khi phê duyệt xong (Phase=DaDuyet) user click "Tạo HĐ từ phiếu" → +// kế thừa Details/Quotes sang Contract + ContractDetails. +public class PurchaseEvaluation : AuditableEntity +{ + public string? MaPhieu { get; set; } // Auto-gen khi create (format tính sau) + public PurchaseEvaluationType Type { get; set; } + public PurchaseEvaluationPhase Phase { get; set; } = PurchaseEvaluationPhase.DangSoanThao; + + public string TenGoiThau { get; set; } = string.Empty; // "Cung cấp bê tông" + public Guid ProjectId { get; set; } // Dự án (FK Projects) + public Guid? DepartmentId { get; set; } + public Guid? DrafterUserId { get; set; } // QS/NV.PB soạn + public string? DiaDiem { get; set; } // Lô K, KCN Lộc An... + public string? MoTa { get; set; } + + public Guid? WorkflowDefinitionId { get; set; } // Pinned at create — config y như HĐ + public DateTime? SlaDeadline { get; set; } + public bool SlaWarningSent { get; set; } + + public Guid? SelectedSupplierId { get; set; } // NCC thắng — null tới khi DaDuyet + public string? PaymentTerms { get; set; } // JSON {tamUng, thanhToanTam, quyetToan, baoHanh, hanMucCongNo, danhGia} + + public Guid? ContractId { get; set; } // FK Contracts — set khi user gen HĐ từ phiếu + + public List Suppliers { get; set; } = new(); + public List Details { get; set; } = new(); + public List Quotes { get; set; } = new(); + public List Approvals { get; set; } = new(); + public List Changelogs { get; set; } = new(); + public List Attachments { get; set; } = new(); +} diff --git a/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationApproval.cs b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationApproval.cs new file mode 100644 index 0000000..550cf05 --- /dev/null +++ b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationApproval.cs @@ -0,0 +1,18 @@ +using SolutionErp.Domain.Common; +using SolutionErp.Domain.Contracts; // reuse ApprovalDecision enum + +namespace SolutionErp.Domain.PurchaseEvaluations; + +// Lịch sử phê duyệt — giống ContractApproval pattern. +public class PurchaseEvaluationApproval : BaseEntity +{ + public Guid PurchaseEvaluationId { get; set; } + public PurchaseEvaluationPhase FromPhase { get; set; } + public PurchaseEvaluationPhase ToPhase { get; set; } + public Guid? ApproverUserId { get; set; } // null = system SLA auto + public ApprovalDecision Decision { get; set; } + public string? Comment { get; set; } + public DateTime ApprovedAt { get; set; } + + public PurchaseEvaluation? PurchaseEvaluation { get; set; } +} diff --git a/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationAttachment.cs b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationAttachment.cs new file mode 100644 index 0000000..cd61fc7 --- /dev/null +++ b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationAttachment.cs @@ -0,0 +1,25 @@ +using SolutionErp.Domain.Common; + +namespace SolutionErp.Domain.PurchaseEvaluations; + +public enum PurchaseEvaluationAttachmentPurpose +{ + QuoteDocument = 1, // File báo giá NCC gửi (PDF/xlsx) + RequirementSpec = 2, // Bản vẽ/yêu cầu kỹ thuật kèm theo + DecisionExport = 3, // Bản phiếu duyệt đã export + Other = 99, +} + +public class PurchaseEvaluationAttachment : BaseEntity +{ + public Guid PurchaseEvaluationId { get; set; } + public Guid? PurchaseEvaluationSupplierId { get; set; } // Null nếu không gắn với NCC cụ thể + public string FileName { get; set; } = string.Empty; + public string StoragePath { get; set; } = string.Empty; + public long FileSize { get; set; } + public string ContentType { get; set; } = string.Empty; + public PurchaseEvaluationAttachmentPurpose Purpose { get; set; } + public string? Note { get; set; } + + public PurchaseEvaluation? PurchaseEvaluation { get; set; } +} diff --git a/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationChangelog.cs b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationChangelog.cs new file mode 100644 index 0000000..dedfe3a --- /dev/null +++ b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationChangelog.cs @@ -0,0 +1,33 @@ +using SolutionErp.Domain.Common; +using SolutionErp.Domain.Contracts; // reuse ChangelogAction enum + +namespace SolutionErp.Domain.PurchaseEvaluations; + +// Audit log unified cho mọi thay đổi trên phiếu — Header / Supplier / Detail +// / Quote / Workflow / Attachment. Populate tương tự ContractChangelog qua +// IPurchaseEvaluationChangelogService. +public class PurchaseEvaluationChangelog : BaseEntity +{ + public Guid PurchaseEvaluationId { get; set; } + public PurchaseEvaluation? PurchaseEvaluation { get; set; } + + public PurchaseEvaluationEntityType EntityType { get; set; } + public Guid? EntityId { get; set; } + public ChangelogAction Action { get; set; } + public PurchaseEvaluationPhase? PhaseAtChange { get; set; } + public Guid? UserId { get; set; } + public string? UserName { get; set; } + public string? Summary { get; set; } + public string? FieldChangesJson { get; set; } + public string? ContextNote { get; set; } +} + +public enum PurchaseEvaluationEntityType +{ + Header = 1, + Supplier = 2, + Detail = 3, + Quote = 4, + Workflow = 5, + Attachment = 6, +} diff --git a/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationDetail.cs b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationDetail.cs new file mode 100644 index 0000000..d8e4f0e --- /dev/null +++ b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationDetail.cs @@ -0,0 +1,24 @@ +using SolutionErp.Domain.Common; + +namespace SolutionErp.Domain.PurchaseEvaluations; + +// Hạng mục so sánh giá + ngân sách (Excel III + "CHI TIẾT SO SÁNH"). +// Tách Quotes ra PurchaseEvaluationQuote để normalize N NCC × M hạng mục. +public class PurchaseEvaluationDetail : BaseEntity +{ + public Guid PurchaseEvaluationId { get; set; } + public string GroupCode { get; set; } = string.Empty; // "A.I", "A.II", "A.III", "A.IV" + public string GroupName { get; set; } = string.Empty; // "Bê tông", "Phụ gia", "Bơm bê tông", "Vận chuyển" + public string? ItemCode { get; set; } // "DMCCC0001" + public string NoiDung { get; set; } = string.Empty; // "Concrete M100" + public string? DonViTinh { get; set; } // "m3" + public decimal KhoiLuongNganSach { get; set; } + public decimal KhoiLuongThiCong { get; set; } + public decimal DonGiaNganSach { get; set; } // Chưa VAT + public decimal ThanhTienNganSach { get; set; } // = KL × Đơn giá (hoặc nhập tay) + public int Order { get; set; } + public string? GhiChu { get; set; } + + public PurchaseEvaluation? PurchaseEvaluation { get; set; } + public List Quotes { get; set; } = new(); +} diff --git a/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationPhase.cs b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationPhase.cs new file mode 100644 index 0000000..bc6dcd0 --- /dev/null +++ b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationPhase.cs @@ -0,0 +1,20 @@ +namespace SolutionErp.Domain.PurchaseEvaluations; + +// State machine cho phiếu Duyệt NCC (tiền-HĐ). 2 workflow khác nhau cùng +// share state space này — A (DuyetNcc) dùng subset, B (DuyetNccPhuongAn) +// dùng full. +// +// A: DangSoanThao → ChoPurchasing → ChoCCM → ChoCEODuyetNCC → DaDuyet +// B: DangSoanThao → ChoPurchasing → ChoDuAn → ChoCCM → ChoCEODuyetPA → ChoCEODuyetNCC → DaDuyet +// Cả 2: từ DangSoanThao có thể → TuChoi; từ mọi phase duyệt reject → DangSoanThao. +public enum PurchaseEvaluationPhase +{ + DangSoanThao = 1, + ChoPurchasing = 2, + ChoDuAn = 3, // chỉ B + ChoCCM = 4, + ChoCEODuyetPA = 5, // chỉ B (duyệt phương án trước) + ChoCEODuyetNCC = 6, // chung cả A & B — duyệt chọn đơn vị + DaDuyet = 7, // terminal thành công + TuChoi = 99, // terminal từ chối +} diff --git a/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationPolicy.cs b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationPolicy.cs new file mode 100644 index 0000000..5d4b2a0 --- /dev/null +++ b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationPolicy.cs @@ -0,0 +1,203 @@ +using SolutionErp.Domain.Contracts; // WorkflowApproverKind +using SolutionErp.Domain.Identity; + +namespace SolutionErp.Domain.PurchaseEvaluations; + +// Policy record cho phiếu Duyệt NCC — mirror WorkflowPolicy của HĐ nhưng +// dùng PurchaseEvaluationPhase enum. 2 default policy hardcoded (A/B) +// phục vụ seed + fallback khi admin chưa author định nghĩa DB. +public sealed record PurchaseEvaluationPolicy( + string Name, + string Description, + IReadOnlyDictionary<(PurchaseEvaluationPhase From, PurchaseEvaluationPhase To), string[]> Transitions, + IReadOnlyDictionary PhaseSla, + IReadOnlyList ActivePhases, + IReadOnlyDictionary<(PurchaseEvaluationPhase From, PurchaseEvaluationPhase To), string[]>? UserTransitions = null) +{ + public bool HasPhase(PurchaseEvaluationPhase phase) => ActivePhases.Contains(phase); + + public bool IsTransitionAllowed( + PurchaseEvaluationPhase from, PurchaseEvaluationPhase to, + IReadOnlyList actorRoles, Guid? actorUserId = null) + { + if (!Transitions.TryGetValue((from, to), out var roles)) return false; + if (actorRoles.Any(r => roles.Contains(r))) return true; + + if (actorUserId is null) return false; + if (UserTransitions is null) return false; + if (!UserTransitions.TryGetValue((from, to), out var userIds)) return false; + return userIds.Contains(actorUserId.Value.ToString()); + } + + public IReadOnlyList NextPhasesFrom(PurchaseEvaluationPhase from) => + Transitions.Keys.Where(k => k.From == from).Select(k => k.To).Distinct().ToList(); +} + +public static class PurchaseEvaluationPolicies +{ + private static readonly Dictionary DefaultSla = new() + { + [PurchaseEvaluationPhase.DangSoanThao] = TimeSpan.FromDays(3), + [PurchaseEvaluationPhase.ChoPurchasing] = TimeSpan.FromDays(2), + [PurchaseEvaluationPhase.ChoDuAn] = TimeSpan.FromDays(2), + [PurchaseEvaluationPhase.ChoCCM] = TimeSpan.FromDays(2), + [PurchaseEvaluationPhase.ChoCEODuyetPA] = TimeSpan.FromDays(1), + [PurchaseEvaluationPhase.ChoCEODuyetNCC] = TimeSpan.FromDays(1), + [PurchaseEvaluationPhase.DaDuyet] = null, + [PurchaseEvaluationPhase.TuChoi] = null, + }; + + // A — DuyetNcc (3 step thực + Drafter soạn): Drafter → Purchasing → CCM → CEO + public static readonly PurchaseEvaluationPolicy NccOnly = new( + Name: "NccOnly", + Description: "Duyệt NCC — 3 step (Purchasing → CCM → CEO). Không cần duyệt phương án.", + Transitions: new Dictionary<(PurchaseEvaluationPhase, PurchaseEvaluationPhase), string[]> + { + [(PurchaseEvaluationPhase.DangSoanThao, PurchaseEvaluationPhase.ChoPurchasing)] = [AppRoles.Drafter, AppRoles.DeptManager], + [(PurchaseEvaluationPhase.DangSoanThao, PurchaseEvaluationPhase.TuChoi)] = [AppRoles.Drafter, AppRoles.DeptManager], + + [(PurchaseEvaluationPhase.ChoPurchasing, PurchaseEvaluationPhase.ChoCCM)] = [AppRoles.Procurement], + [(PurchaseEvaluationPhase.ChoPurchasing, PurchaseEvaluationPhase.DangSoanThao)] = [AppRoles.Procurement], + + [(PurchaseEvaluationPhase.ChoCCM, PurchaseEvaluationPhase.ChoCEODuyetNCC)] = [AppRoles.CostControl], + [(PurchaseEvaluationPhase.ChoCCM, PurchaseEvaluationPhase.DangSoanThao)] = [AppRoles.CostControl], + + [(PurchaseEvaluationPhase.ChoCEODuyetNCC, PurchaseEvaluationPhase.DaDuyet)] = [AppRoles.Director, AppRoles.AuthorizedSigner], + [(PurchaseEvaluationPhase.ChoCEODuyetNCC, PurchaseEvaluationPhase.DangSoanThao)] = [AppRoles.Director, AppRoles.AuthorizedSigner], + }, + PhaseSla: DefaultSla, + ActivePhases: + [ + PurchaseEvaluationPhase.DangSoanThao, + PurchaseEvaluationPhase.ChoPurchasing, + PurchaseEvaluationPhase.ChoCCM, + PurchaseEvaluationPhase.ChoCEODuyetNCC, + PurchaseEvaluationPhase.DaDuyet, + PurchaseEvaluationPhase.TuChoi, + ]); + + // B — DuyetNccPhuongAn (5 step thực + Drafter): Drafter → Purchasing → Dự án → CCM → CEO(PA) → CEO(NCC) + public static readonly PurchaseEvaluationPolicy NccWithPlan = new( + Name: "NccWithPlan", + Description: "Duyệt NCC + Phương án — 5 step (Purchasing → Dự án → CCM → CEO duyệt PA → CEO duyệt NCC).", + Transitions: new Dictionary<(PurchaseEvaluationPhase, PurchaseEvaluationPhase), string[]> + { + [(PurchaseEvaluationPhase.DangSoanThao, PurchaseEvaluationPhase.ChoPurchasing)] = [AppRoles.Drafter, AppRoles.DeptManager], + [(PurchaseEvaluationPhase.DangSoanThao, PurchaseEvaluationPhase.TuChoi)] = [AppRoles.Drafter, AppRoles.DeptManager], + + [(PurchaseEvaluationPhase.ChoPurchasing, PurchaseEvaluationPhase.ChoDuAn)] = [AppRoles.Procurement], + [(PurchaseEvaluationPhase.ChoPurchasing, PurchaseEvaluationPhase.DangSoanThao)] = [AppRoles.Procurement], + + [(PurchaseEvaluationPhase.ChoDuAn, PurchaseEvaluationPhase.ChoCCM)] = [AppRoles.ProjectManager], + [(PurchaseEvaluationPhase.ChoDuAn, PurchaseEvaluationPhase.DangSoanThao)] = [AppRoles.ProjectManager], + + [(PurchaseEvaluationPhase.ChoCCM, PurchaseEvaluationPhase.ChoCEODuyetPA)] = [AppRoles.CostControl], + [(PurchaseEvaluationPhase.ChoCCM, PurchaseEvaluationPhase.DangSoanThao)] = [AppRoles.CostControl], + + [(PurchaseEvaluationPhase.ChoCEODuyetPA, PurchaseEvaluationPhase.ChoCEODuyetNCC)] = [AppRoles.Director, AppRoles.AuthorizedSigner], + [(PurchaseEvaluationPhase.ChoCEODuyetPA, PurchaseEvaluationPhase.DangSoanThao)] = [AppRoles.Director, AppRoles.AuthorizedSigner], + + [(PurchaseEvaluationPhase.ChoCEODuyetNCC, PurchaseEvaluationPhase.DaDuyet)] = [AppRoles.Director, AppRoles.AuthorizedSigner], + [(PurchaseEvaluationPhase.ChoCEODuyetNCC, PurchaseEvaluationPhase.DangSoanThao)] = [AppRoles.Director, AppRoles.AuthorizedSigner], + }, + PhaseSla: DefaultSla, + ActivePhases: + [ + PurchaseEvaluationPhase.DangSoanThao, + PurchaseEvaluationPhase.ChoPurchasing, + PurchaseEvaluationPhase.ChoDuAn, + PurchaseEvaluationPhase.ChoCCM, + PurchaseEvaluationPhase.ChoCEODuyetPA, + PurchaseEvaluationPhase.ChoCEODuyetNCC, + PurchaseEvaluationPhase.DaDuyet, + PurchaseEvaluationPhase.TuChoi, + ]); +} + +public static class PurchaseEvaluationPolicyRegistry +{ + public static readonly string[] AvailablePolicyNames = ["NccOnly", "NccWithPlan"]; + + public static PurchaseEvaluationPolicy ByName(string name) => name switch + { + "NccWithPlan" => PurchaseEvaluationPolicies.NccWithPlan, + _ => PurchaseEvaluationPolicies.NccOnly, + }; + + public static string DefaultPolicyNameFor(PurchaseEvaluationType type) => type switch + { + PurchaseEvaluationType.DuyetNccPhuongAn => "NccWithPlan", + _ => "NccOnly", + }; + + public static PurchaseEvaluationPolicy For(PurchaseEvaluationType type) => + ByName(DefaultPolicyNameFor(type)); + + public static PurchaseEvaluationPolicy ForEvaluation(PurchaseEvaluation ev) => + For(ev.Type); + + // Build policy from persisted admin-authored definition (mirror + // WorkflowPolicyRegistry.FromDefinition for HĐ). + public static PurchaseEvaluationPolicy FromDefinition(PurchaseEvaluationWorkflowDefinition def) + { + var steps = def.Steps.OrderBy(s => s.Order).ToList(); + var transitions = new Dictionary<(PurchaseEvaluationPhase From, PurchaseEvaluationPhase To), string[]>(); + var userTransitions = new Dictionary<(PurchaseEvaluationPhase From, PurchaseEvaluationPhase To), string[]>(); + var sla = new Dictionary(); + var activePhases = new List(); + + PurchaseEvaluationPhase? prev = null; + foreach (var s in steps) + { + activePhases.Add(s.Phase); + sla[s.Phase] = s.SlaDays is int d ? TimeSpan.FromDays(d) : null; + + var roles = s.Approvers + .Where(a => a.Kind == WorkflowApproverKind.Role) + .Select(a => a.AssignmentValue) + .Distinct() + .ToArray(); + var hasUserKind = s.Approvers.Any(a => a.Kind == WorkflowApproverKind.User); + if (roles.Length == 0 && !hasUserKind) roles = [AppRoles.DeptManager]; + + var userIds = s.Approvers + .Where(a => a.Kind == WorkflowApproverKind.User) + .Select(a => a.AssignmentValue) + .Distinct() + .ToArray(); + + if (prev is not null) + { + transitions[(prev.Value, s.Phase)] = roles; + if (userIds.Length > 0) userTransitions[(prev.Value, s.Phase)] = userIds; + + // Reject path back to Drafter (common pattern) + if (prev.Value != PurchaseEvaluationPhase.DangSoanThao && s.Phase != PurchaseEvaluationPhase.DangSoanThao) + { + transitions.TryAdd((s.Phase, PurchaseEvaluationPhase.DangSoanThao), roles); + if (userIds.Length > 0) + userTransitions.TryAdd((s.Phase, PurchaseEvaluationPhase.DangSoanThao), userIds); + } + } + prev = s.Phase; + } + // First step có thể reject to TuChoi + if (steps.Count > 0) + transitions.TryAdd((steps[0].Phase, PurchaseEvaluationPhase.TuChoi), + [AppRoles.Drafter, AppRoles.DeptManager]); + + // Terminal states always available + if (!activePhases.Contains(PurchaseEvaluationPhase.TuChoi)) + activePhases.Add(PurchaseEvaluationPhase.TuChoi); + if (!activePhases.Contains(PurchaseEvaluationPhase.DaDuyet)) + activePhases.Add(PurchaseEvaluationPhase.DaDuyet); + + return new PurchaseEvaluationPolicy( + Name: $"{def.Code}-v{def.Version:D2}", + Description: def.Description ?? def.Name, + Transitions: transitions, + PhaseSla: sla, + ActivePhases: activePhases, + UserTransitions: userTransitions.Count > 0 ? userTransitions : null); + } +} diff --git a/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationQuote.cs b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationQuote.cs new file mode 100644 index 0000000..8a781b2 --- /dev/null +++ b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationQuote.cs @@ -0,0 +1,20 @@ +using SolutionErp.Domain.Common; + +namespace SolutionErp.Domain.PurchaseEvaluations; + +// Báo giá N NCC × M hạng mục. 1 row = 1 NCC chào 1 hạng mục. +// IsSelected cho per-item winner (nếu user chọn mỗi hạng mục 1 NCC khác nhau); +// PurchaseEvaluation.SelectedSupplierId là winner tổng thể (trường hợp 1 NCC thắng toàn bộ). +public class PurchaseEvaluationQuote : BaseEntity +{ + public Guid PurchaseEvaluationDetailId { get; set; } + public Guid PurchaseEvaluationSupplierId { get; set; } // FK PurchaseEvaluationSuppliers + public decimal BgVat { get; set; } // Báo giá NCC gửi (đã VAT) + public decimal ChuaVat { get; set; } // Chưa VAT + public decimal ThanhTien { get; set; } // = KL × ChuaVat (tính sẵn) + public bool IsSelected { get; set; } // NCC được chọn cho hạng mục này + public string? Note { get; set; } + + public PurchaseEvaluationDetail? Detail { get; set; } + public PurchaseEvaluationSupplier? Supplier { get; set; } +} diff --git a/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationSupplier.cs b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationSupplier.cs new file mode 100644 index 0000000..b4db8f7 --- /dev/null +++ b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationSupplier.cs @@ -0,0 +1,22 @@ +using SolutionErp.Domain.Common; + +namespace SolutionErp.Domain.PurchaseEvaluations; + +// N:M giữa PurchaseEvaluation × Supplier (master). Lưu contact + payment +// term per NCC (file Excel section E + cột header "TGN-30 ngày" / +// "Tiến Phát" / ... ngay dưới bảng II). Note chứa yellow chip: +// "ĐÃ CHỐT SO SÁNH LẦN 1/2", "ĐÀM PHÁN THÊM". +public class PurchaseEvaluationSupplier : BaseEntity +{ + public Guid PurchaseEvaluationId { get; set; } + public Guid SupplierId { get; set; } // FK Suppliers master + public string? DisplayName { get; set; } // Override nếu khác Supplier.Name (vd kèm term: "TGN-30 ngày") + public string? ContactName { get; set; } + public string? ContactEmail { get; set; } + public string? ContactPhone { get; set; } + public string? PaymentTermText { get; set; } // Free text: "30 ngày", "45 ngày", "300tr" + public string? Note { get; set; } // Chip trạng thái so sánh + public int Order { get; set; } // Thứ tự cột trong bảng so sánh + + public PurchaseEvaluation? PurchaseEvaluation { get; set; } +} diff --git a/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationType.cs b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationType.cs new file mode 100644 index 0000000..ed34f17 --- /dev/null +++ b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationType.cs @@ -0,0 +1,10 @@ +namespace SolutionErp.Domain.PurchaseEvaluations; + +// 2 quy trình theo flowchart QT chọn NTP/NCC: +// A — NccOnly (3 step): Purchasing → CCM → CEO +// B — NccWithPlan (5 step): Purchasing → Dự án → CCM → CEO (PA) → CEO (NCC) +public enum PurchaseEvaluationType +{ + DuyetNcc = 1, // A + DuyetNccPhuongAn = 2, // B +} diff --git a/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationWorkflowDefinition.cs b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationWorkflowDefinition.cs new file mode 100644 index 0000000..f9ff5ce --- /dev/null +++ b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationWorkflowDefinition.cs @@ -0,0 +1,46 @@ +using SolutionErp.Domain.Common; +using SolutionErp.Domain.Contracts; // reuse WorkflowApproverKind + +namespace SolutionErp.Domain.PurchaseEvaluations; + +// Versioned workflow definition cho module Duyệt NCC — pattern giống HĐ +// nhưng tách table riêng vì Phase là PurchaseEvaluationPhase enum (không +// phải ContractPhase). Admin có UI /system/pe-workflows/:typeCode tương +// tự /system/workflows/:typeCode. +// +// Invariant: AT MOST ONE IsActive=true per PurchaseEvaluationType tại 1 +// thời điểm. PurchaseEvaluation.WorkflowDefinitionId pin tại create → +// phiếu cũ không bị ảnh hưởng khi admin active version mới. +public class PurchaseEvaluationWorkflowDefinition : BaseEntity +{ + public string Code { get; set; } = string.Empty; // "QT-DN-A" / "QT-DN-B" default + public int Version { get; set; } + public PurchaseEvaluationType EvaluationType { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public bool IsActive { get; set; } + public DateTime? ActivatedAt { get; set; } + + public List Steps { get; set; } = new(); +} + +public class PurchaseEvaluationWorkflowStep : BaseEntity +{ + public Guid PurchaseEvaluationWorkflowDefinitionId { get; set; } + public int Order { get; set; } + public PurchaseEvaluationPhase Phase { get; set; } + public string Name { get; set; } = string.Empty; + public int? SlaDays { get; set; } + + public PurchaseEvaluationWorkflowDefinition? Definition { get; set; } + public List Approvers { get; set; } = new(); +} + +public class PurchaseEvaluationWorkflowStepApprover : BaseEntity +{ + public Guid PurchaseEvaluationWorkflowStepId { get; set; } + public WorkflowApproverKind Kind { get; set; } // reuse Role/User enum từ Contract + public string AssignmentValue { get; set; } = string.Empty; + + public PurchaseEvaluationWorkflowStep? Step { get; set; } +} diff --git a/src/Backend/SolutionErp.Infrastructure/Persistence/ApplicationDbContext.cs b/src/Backend/SolutionErp.Infrastructure/Persistence/ApplicationDbContext.cs index 91563af..89aa543 100644 --- a/src/Backend/SolutionErp.Infrastructure/Persistence/ApplicationDbContext.cs +++ b/src/Backend/SolutionErp.Infrastructure/Persistence/ApplicationDbContext.cs @@ -8,6 +8,7 @@ using SolutionErp.Domain.Identity; using SolutionErp.Domain.Master; using SolutionErp.Domain.Master.Catalogs; using SolutionErp.Domain.Notifications; +using SolutionErp.Domain.PurchaseEvaluations; namespace SolutionErp.Infrastructure.Persistence; @@ -46,6 +47,17 @@ public class ApplicationDbContext public DbSet NguyenTacNccDetails => Set(); public DbSet NguyenTacDvDetails => Set(); + public DbSet PurchaseEvaluations => Set(); + public DbSet PurchaseEvaluationSuppliers => Set(); + public DbSet PurchaseEvaluationDetails => Set(); + public DbSet PurchaseEvaluationQuotes => Set(); + public DbSet PurchaseEvaluationApprovals => Set(); + public DbSet PurchaseEvaluationChangelogs => Set(); + public DbSet PurchaseEvaluationAttachments => Set(); + public DbSet PurchaseEvaluationWorkflowDefinitions => Set(); + public DbSet PurchaseEvaluationWorkflowSteps => Set(); + public DbSet PurchaseEvaluationWorkflowStepApprovers => Set(); + protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); diff --git a/src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/PurchaseEvaluationConfiguration.cs b/src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/PurchaseEvaluationConfiguration.cs new file mode 100644 index 0000000..57991a9 --- /dev/null +++ b/src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/PurchaseEvaluationConfiguration.cs @@ -0,0 +1,205 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using SolutionErp.Domain.PurchaseEvaluations; + +namespace SolutionErp.Infrastructure.Persistence.Configurations; + +public class PurchaseEvaluationConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder b) + { + b.ToTable("PurchaseEvaluations"); + b.HasKey(x => x.Id); + + b.Property(x => x.MaPhieu).HasMaxLength(100); + b.Property(x => x.Type).HasConversion(); + b.Property(x => x.Phase).HasConversion(); + b.Property(x => x.TenGoiThau).HasMaxLength(500).IsRequired(); + b.Property(x => x.DiaDiem).HasMaxLength(500); + b.Property(x => x.MoTa).HasMaxLength(2000); + b.Property(x => x.PaymentTerms).HasColumnType("nvarchar(max)"); + + b.HasIndex(x => x.MaPhieu).IsUnique().HasFilter("[MaPhieu] IS NOT NULL"); + b.HasIndex(x => new { x.Phase, x.IsDeleted }); + b.HasIndex(x => x.ProjectId); + b.HasIndex(x => x.SlaDeadline); + b.HasIndex(x => x.WorkflowDefinitionId); + b.HasIndex(x => x.ContractId); + + b.HasMany(x => x.Suppliers).WithOne(s => s.PurchaseEvaluation).HasForeignKey(s => s.PurchaseEvaluationId).OnDelete(DeleteBehavior.Cascade); + b.HasMany(x => x.Details).WithOne(d => d.PurchaseEvaluation).HasForeignKey(d => d.PurchaseEvaluationId).OnDelete(DeleteBehavior.Cascade); + b.HasMany(x => x.Approvals).WithOne(a => a.PurchaseEvaluation).HasForeignKey(a => a.PurchaseEvaluationId).OnDelete(DeleteBehavior.Cascade); + b.HasMany(x => x.Changelogs).WithOne(c => c.PurchaseEvaluation).HasForeignKey(c => c.PurchaseEvaluationId).OnDelete(DeleteBehavior.Cascade); + b.HasMany(x => x.Attachments).WithOne(a => a.PurchaseEvaluation).HasForeignKey(a => a.PurchaseEvaluationId).OnDelete(DeleteBehavior.Cascade); + // Quotes không FK trực tiếp tới PurchaseEvaluation (đi qua Detail) — + // nhưng collection navigation có nên cần config riêng bên dưới. + + b.HasQueryFilter(x => !x.IsDeleted); + } +} + +public class PurchaseEvaluationSupplierConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder b) + { + b.ToTable("PurchaseEvaluationSuppliers"); + b.HasKey(x => x.Id); + + b.Property(x => x.DisplayName).HasMaxLength(200); + b.Property(x => x.ContactName).HasMaxLength(200); + b.Property(x => x.ContactEmail).HasMaxLength(200); + b.Property(x => x.ContactPhone).HasMaxLength(50); + b.Property(x => x.PaymentTermText).HasMaxLength(200); + b.Property(x => x.Note).HasMaxLength(500); + + b.HasIndex(x => new { x.PurchaseEvaluationId, x.SupplierId }).IsUnique(); + b.HasIndex(x => x.SupplierId); + } +} + +public class PurchaseEvaluationDetailConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder b) + { + b.ToTable("PurchaseEvaluationDetails"); + b.HasKey(x => x.Id); + + b.Property(x => x.GroupCode).HasMaxLength(50).IsRequired(); + b.Property(x => x.GroupName).HasMaxLength(200).IsRequired(); + b.Property(x => x.ItemCode).HasMaxLength(100); + b.Property(x => x.NoiDung).HasMaxLength(500).IsRequired(); + b.Property(x => x.DonViTinh).HasMaxLength(50); + b.Property(x => x.GhiChu).HasMaxLength(1000); + b.Property(x => x.KhoiLuongNganSach).HasPrecision(18, 4); + b.Property(x => x.KhoiLuongThiCong).HasPrecision(18, 4); + b.Property(x => x.DonGiaNganSach).HasPrecision(18, 2); + b.Property(x => x.ThanhTienNganSach).HasPrecision(18, 2); + + b.HasIndex(x => new { x.PurchaseEvaluationId, x.Order }); + + b.HasMany(x => x.Quotes).WithOne(q => q.Detail).HasForeignKey(q => q.PurchaseEvaluationDetailId).OnDelete(DeleteBehavior.Cascade); + } +} + +public class PurchaseEvaluationQuoteConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder b) + { + b.ToTable("PurchaseEvaluationQuotes"); + b.HasKey(x => x.Id); + + b.Property(x => x.BgVat).HasPrecision(18, 2); + b.Property(x => x.ChuaVat).HasPrecision(18, 2); + b.Property(x => x.ThanhTien).HasPrecision(18, 2); + b.Property(x => x.Note).HasMaxLength(500); + + b.HasIndex(x => new { x.PurchaseEvaluationDetailId, x.PurchaseEvaluationSupplierId }).IsUnique(); + + // Quote → Supplier (restrict — không xóa Supplier-row khỏi phiếu nếu còn quote) + b.HasOne(x => x.Supplier) + .WithMany() + .HasForeignKey(x => x.PurchaseEvaluationSupplierId) + .OnDelete(DeleteBehavior.Restrict); + } +} + +public class PurchaseEvaluationApprovalConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder b) + { + b.ToTable("PurchaseEvaluationApprovals"); + b.HasKey(x => x.Id); + + b.Property(x => x.FromPhase).HasConversion(); + b.Property(x => x.ToPhase).HasConversion(); + b.Property(x => x.Decision).HasConversion(); + b.Property(x => x.Comment).HasMaxLength(1000); + + b.HasIndex(x => new { x.PurchaseEvaluationId, x.ApprovedAt }); + } +} + +public class PurchaseEvaluationChangelogConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder b) + { + b.ToTable("PurchaseEvaluationChangelogs"); + b.HasKey(x => x.Id); + + b.Property(x => x.EntityType).HasConversion(); + b.Property(x => x.Action).HasConversion(); + b.Property(x => x.PhaseAtChange).HasConversion(); + b.Property(x => x.UserName).HasMaxLength(200); + b.Property(x => x.Summary).HasMaxLength(500); + b.Property(x => x.ContextNote).HasMaxLength(2000); + b.Property(x => x.FieldChangesJson).HasColumnType("nvarchar(max)"); + + b.HasIndex(x => new { x.PurchaseEvaluationId, x.CreatedAt }); + b.HasIndex(x => new { x.PurchaseEvaluationId, x.EntityType }); + } +} + +public class PurchaseEvaluationAttachmentConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder b) + { + b.ToTable("PurchaseEvaluationAttachments"); + b.HasKey(x => x.Id); + + b.Property(x => x.FileName).HasMaxLength(255).IsRequired(); + b.Property(x => x.StoragePath).HasMaxLength(500).IsRequired(); + b.Property(x => x.ContentType).HasMaxLength(100).IsRequired(); + b.Property(x => x.Purpose).HasConversion(); + b.Property(x => x.Note).HasMaxLength(500); + + b.HasIndex(x => x.PurchaseEvaluationId); + b.HasIndex(x => x.PurchaseEvaluationSupplierId); + } +} + +public class PurchaseEvaluationWorkflowDefinitionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder e) + { + e.ToTable("PurchaseEvaluationWorkflowDefinitions"); + e.Property(x => x.Code).HasMaxLength(100).IsRequired(); + e.Property(x => x.Name).HasMaxLength(200).IsRequired(); + e.Property(x => x.Description).HasMaxLength(1000); + e.Property(x => x.EvaluationType).HasConversion(); + + e.HasIndex(x => new { x.Code, x.Version }).IsUnique(); + e.HasIndex(x => new { x.EvaluationType, x.IsActive }); + } +} + +public class PurchaseEvaluationWorkflowStepConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder e) + { + e.ToTable("PurchaseEvaluationWorkflowSteps"); + e.Property(x => x.Name).HasMaxLength(200).IsRequired(); + e.Property(x => x.Phase).HasConversion(); + + e.HasOne(x => x.Definition) + .WithMany(d => d.Steps) + .HasForeignKey(x => x.PurchaseEvaluationWorkflowDefinitionId) + .OnDelete(DeleteBehavior.Cascade); + + e.HasIndex(x => new { x.PurchaseEvaluationWorkflowDefinitionId, x.Order }); + } +} + +public class PurchaseEvaluationWorkflowStepApproverConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder e) + { + e.ToTable("PurchaseEvaluationWorkflowStepApprovers"); + e.Property(x => x.Kind).HasConversion(); + e.Property(x => x.AssignmentValue).HasMaxLength(100).IsRequired(); + + e.HasOne(x => x.Step) + .WithMany(s => s.Approvers) + .HasForeignKey(x => x.PurchaseEvaluationWorkflowStepId) + .OnDelete(DeleteBehavior.Cascade); + } +} diff --git a/src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs b/src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs index dd4fc9d..c87c758 100644 --- a/src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs +++ b/src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs @@ -8,6 +8,7 @@ using SolutionErp.Domain.Forms; using SolutionErp.Domain.Identity; using SolutionErp.Domain.Master; using SolutionErp.Domain.Master.Catalogs; +using SolutionErp.Domain.PurchaseEvaluations; namespace SolutionErp.Infrastructure.Persistence; @@ -37,6 +38,7 @@ public static class DbInitializer await SeedDemoMasterDataAsync(db, logger); await SeedContractTemplatesAsync(db, logger); await SeedWorkflowDefinitionsAsync(db, logger); + await SeedPurchaseEvaluationWorkflowsAsync(db, logger); await SeedCatalogsAsync(db, logger); // Backfill mã HĐ cho HĐ legacy chưa có (sau khi đổi policy gen-tại-create). @@ -317,6 +319,79 @@ public static class DbInitializer } } + // Seed default workflow v01 cho 2 PurchaseEvaluationType (A NccOnly / B + // NccWithPlan) — mirror SeedWorkflowDefinitionsAsync của HĐ. Admin có + // thể tạo version mới qua /system/pe-workflows/:typeCode designer. + private static async Task SeedPurchaseEvaluationWorkflowsAsync(ApplicationDbContext db, ILogger logger) + { + var typeLabels = new Dictionary + { + [PurchaseEvaluationType.DuyetNcc] = ("QT-DN-A", "Quy trình Duyệt NCC"), + [PurchaseEvaluationType.DuyetNccPhuongAn] = ("QT-DN-B", "Quy trình Duyệt NCC - Phương Án"), + }; + + var phaseNames = new Dictionary + { + [PurchaseEvaluationPhase.DangSoanThao] = "Soạn thảo", + [PurchaseEvaluationPhase.ChoPurchasing] = "Purchasing tổng hợp", + [PurchaseEvaluationPhase.ChoDuAn] = "Dự án kiểm tra", + [PurchaseEvaluationPhase.ChoCCM] = "CCM kiểm tra ngân sách", + [PurchaseEvaluationPhase.ChoCEODuyetPA] = "CEO duyệt phương án", + [PurchaseEvaluationPhase.ChoCEODuyetNCC] = "CEO duyệt chọn đơn vị", + [PurchaseEvaluationPhase.DaDuyet] = "Đã duyệt", + }; + + var added = 0; + foreach (var (type, info) in typeLabels) + { + var alreadyExists = await db.PurchaseEvaluationWorkflowDefinitions + .AnyAsync(w => w.EvaluationType == type); + if (alreadyExists) continue; + + var policy = PurchaseEvaluationPolicyRegistry.For(type); + var def = new PurchaseEvaluationWorkflowDefinition + { + Code = info.Code, + Version = 1, + EvaluationType = type, + Name = $"{info.Name} (v01)", + Description = policy.Description, + IsActive = true, + ActivatedAt = DateTime.UtcNow, + Steps = policy.ActivePhases + .Where(p => p != PurchaseEvaluationPhase.TuChoi && p != PurchaseEvaluationPhase.DaDuyet) + .Select((p, idx) => + { + var roles = policy.Transitions + .Where(t => t.Key.To == p) + .SelectMany(t => t.Value) + .Distinct() + .ToList(); + return new PurchaseEvaluationWorkflowStep + { + Order = idx + 1, + Phase = p, + Name = phaseNames.GetValueOrDefault(p, p.ToString()), + SlaDays = policy.PhaseSla.GetValueOrDefault(p) is TimeSpan s ? (int?)s.Days : null, + Approvers = roles.Select(r => new PurchaseEvaluationWorkflowStepApprover + { + Kind = WorkflowApproverKind.Role, + AssignmentValue = r, + }).ToList(), + }; + }) + .ToList(), + }; + db.PurchaseEvaluationWorkflowDefinitions.Add(def); + added++; + } + if (added > 0) + { + await db.SaveChangesAsync(); + logger.LogInformation("Seeded {Count} PE workflow definitions (v01)", added); + } + } + // Map ContractType → preferred supplier code (đa dạng dữ liệu demo theo // đúng business: ThauPhu/GiaoKhoan đi với NTP/TĐ, NCC/MuaBan/NTNcc đi // với NCC, DichVu/NTDv đi với DV). @@ -786,6 +861,9 @@ public static class DbInitializer (MenuKeys.Roles, "Vai trò", MenuKeys.System, 92, "Shield"), (MenuKeys.Permissions, "Phân quyền", MenuKeys.System, 93, "KeyRound"), (MenuKeys.Workflows, "Quy trình HĐ", MenuKeys.System, 94, "GitBranch"), + // Module Duyệt NCC (tiền-HĐ) + (MenuKeys.PurchaseEvaluations, "Quy trình chọn Thầu phụ - NCC", null, 25, "ClipboardCheck"), + (MenuKeys.PeWorkflows, "Quy trình Duyệt NCC", MenuKeys.System, 95, "GitCompareArrows"), }; // Per-type sub-menu under Contracts: 1 group + 3 leaves each @@ -809,6 +887,30 @@ public static class DbInitializer tree.Add((MenuKeys.WorkflowTypeLeaf(code), label, MenuKeys.Workflows, wfOrder++, "FileText")); } + // Pe_* group per PurchaseEvaluationType + 3 action leaves each + var peTypeLabels = new Dictionary + { + ["DuyetNcc"] = "Quy trình Duyệt NCC", + ["DuyetNccPhuongAn"] = "Quy trình Duyệt NCC - Phương Án", + }; + var peOrder = 1; + foreach (var code in MenuKeys.PurchaseEvaluationTypeCodes) + { + var label = peTypeLabels.GetValueOrDefault(code, code); + tree.Add((MenuKeys.PurchaseEvaluationGroup(code), label, MenuKeys.PurchaseEvaluations, peOrder++, "FileCheck")); + tree.Add((MenuKeys.PurchaseEvaluationList(code), "Danh sách", MenuKeys.PurchaseEvaluationGroup(code), peOrder++, "List")); + tree.Add((MenuKeys.PurchaseEvaluationCreate(code), "Thao tác", MenuKeys.PurchaseEvaluationGroup(code), peOrder++, "Plus")); + tree.Add((MenuKeys.PurchaseEvaluationPending(code),"Duyệt", MenuKeys.PurchaseEvaluationGroup(code), peOrder++, "CheckCircle2")); + } + + // PE workflow admin leaves dưới `PeWorkflows` + var peWfOrder = 96; + foreach (var code in MenuKeys.PurchaseEvaluationTypeCodes) + { + var label = peTypeLabels.GetValueOrDefault(code, code); + tree.Add((MenuKeys.PeWorkflowTypeLeaf(code), label, MenuKeys.PeWorkflows, peWfOrder++, "FileCheck")); + } + var existingKeys = await db.MenuItems.Select(m => m.Key).ToListAsync(); var added = 0; foreach (var (key, label, parent, o, icon) in tree) diff --git a/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260423093608_AddPurchaseEvaluations.Designer.cs b/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260423093608_AddPurchaseEvaluations.Designer.cs new file mode 100644 index 0000000..ed9cf28 --- /dev/null +++ b/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260423093608_AddPurchaseEvaluations.Designer.cs @@ -0,0 +1,2901 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SolutionErp.Infrastructure.Persistence; + +#nullable disable + +namespace SolutionErp.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260423093608_AddPurchaseEvaluations")] + partial class AddPurchaseEvaluations + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Contract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BypassProcurementAndCCM") + .HasColumnType("bit"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DepartmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("DraftData") + .HasColumnType("nvarchar(max)"); + + b.Property("DrafterUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("GiaTri") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("MaHopDong") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("NoiDung") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Phase") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("uniqueidentifier"); + + b.Property("SlaDeadline") + .HasColumnType("datetime2"); + + b.Property("SlaWarningSent") + .HasColumnType("bit"); + + b.Property("SupplierId") + .HasColumnType("uniqueidentifier"); + + b.Property("TemplateId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenHopDong") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("WorkflowDefinitionId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("MaHopDong") + .IsUnique() + .HasFilter("[MaHopDong] IS NOT NULL"); + + b.HasIndex("ProjectId"); + + b.HasIndex("SlaDeadline"); + + b.HasIndex("SupplierId"); + + b.HasIndex("Phase", "IsDeleted"); + + b.ToTable("Contracts", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractApproval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApprovedAt") + .HasColumnType("datetime2"); + + b.Property("ApproverUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("ContractId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Decision") + .HasColumnType("int"); + + b.Property("FromPhase") + .HasColumnType("int"); + + b.Property("ToPhase") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ContractId", "ApprovedAt"); + + b.ToTable("ContractApprovals", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ContractId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Purpose") + .HasColumnType("int"); + + b.Property("StoragePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ContractId"); + + b.ToTable("ContractAttachments", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractChangelog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("ContextNote") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ContractId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityType") + .HasColumnType("int"); + + b.Property("FieldChangesJson") + .HasColumnType("nvarchar(max)"); + + b.Property("PhaseAtChange") + .HasColumnType("int"); + + b.Property("Summary") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ContractId", "CreatedAt"); + + b.HasIndex("ContractId", "EntityType"); + + b.ToTable("ContractChangelogs", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractCodeSequence", b => + { + b.Property("Prefix") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("LastSeq") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Prefix"); + + b.ToTable("ContractCodeSequences", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ContractId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Phase") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ContractId", "CreatedAt"); + + b.ToTable("ContractComments", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.DichVuDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContractId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DenNgay") + .HasColumnType("datetime2"); + + b.Property("DonGia") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("DonViTinh") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("GhiChu") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("MaDichVu") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("MoTa") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("TenDichVu") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ThanhTien") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ThoiGian") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TuNgay") + .HasColumnType("datetime2"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ContractId", "Order"); + + b.ToTable("DichVuDetails", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.GiaoKhoanDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContractId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DonGia") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("DonViTinh") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("GhiChu") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("KhoiLuong") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("MaCongViec") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("TenCongViec") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ThanhTien") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ThoiGianHoanThanh") + .HasColumnType("datetime2"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("YeuCauKyThuat") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ContractId", "Order"); + + b.ToTable("GiaoKhoanDetails", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.MuaBanDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContractId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DonGia") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("DonViTinh") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("GhiChu") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("MaSP") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("MoTa") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("SoLuong") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TenSP") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ThanhTien") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ThueVAT") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("XuatXu") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("ContractId", "Order"); + + b.ToTable("MuaBanDetails", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.NguyenTacDvDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContractId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DonGiaToiDa") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("DonGiaToiThieu") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("DonViTinh") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("GhiChu") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("LoaiDichVu") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("PhamViDichVu") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("SLA") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("TenDichVu") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ThanhTien") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ContractId", "Order"); + + b.ToTable("NguyenTacDvDetails", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.NguyenTacNccDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContractId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DieuKienGiaoHang") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DieuKienThanhToan") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DonGiaToiDa") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("DonGiaToiThieu") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("DonViTinh") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("GhiChu") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("NhomSP") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("TenSP") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ThanhTien") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ContractId", "Order"); + + b.ToTable("NguyenTacNccDetails", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.NhaCungCapDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContractId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DonGia") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("DonViTinh") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("GhiChu") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("MaSP") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("SoLuong") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("TenSP") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ThanhTien") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ThoiGianGiao") + .HasColumnType("datetime2"); + + b.Property("ThongSoKyThuat") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("XuatXu") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("ContractId", "Order"); + + b.ToTable("NhaCungCapDetails", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.ThauPhuDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContractId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DonGia") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("DonViTinh") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("GhiChu") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("HangMuc") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("KhoiLuong") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("ThanhTien") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ThoiGianHoanThanh") + .HasColumnType("datetime2"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ContractId", "Order"); + + b.ToTable("ThauPhuDetails", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.WorkflowDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ActivatedAt") + .HasColumnType("datetime2"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ContractType") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Code", "Version") + .IsUnique(); + + b.HasIndex("ContractType", "IsActive"); + + b.ToTable("WorkflowDefinitions", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.WorkflowStep", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("Phase") + .HasColumnType("int"); + + b.Property("SlaDays") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("WorkflowDefinitionId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("WorkflowDefinitionId", "Order"); + + b.ToTable("WorkflowSteps", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.WorkflowStepApprover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AssignmentValue") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("WorkflowStepId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("WorkflowStepId"); + + b.ToTable("WorkflowStepApprovers", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.WorkflowTypeAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContractType") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("PolicyName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ContractType") + .IsUnique(); + + b.ToTable("WorkflowTypeAssignments", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Forms.ContractClause", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("ContractClauses", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Forms.ContractTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContractType") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FieldSpec") + .HasColumnType("nvarchar(max)"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FormCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Format") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("StoragePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ContractType"); + + b.HasIndex("FormCode") + .IsUnique(); + + b.ToTable("ContractTemplates", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Identity.MenuItem", b => + { + b.Property("Key") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Icon") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("ParentKey") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Key"); + + b.HasIndex("ParentKey"); + + b.ToTable("MenuItems", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Identity.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CanCreate") + .HasColumnType("bit"); + + b.Property("CanDelete") + .HasColumnType("bit"); + + b.Property("CanRead") + .HasColumnType("bit"); + + b.Property("CanUpdate") + .HasColumnType("bit"); + + b.Property("MenuKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("MenuKey"); + + b.HasIndex("RoleId", "MenuKey") + .IsUnique(); + + b.ToTable("Permissions", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Identity.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ShortName") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("Roles", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Identity.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DepartmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("Position") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RefreshToken") + .HasMaxLength(512) + .HasColumnType("nvarchar(512)"); + + b.Property("RefreshTokenExpiresAt") + .HasColumnType("datetime2"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Master.Catalogs.MaterialItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Category") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DefaultUnit") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("OriginCountry") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Specification") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Category"); + + b.HasIndex("Code") + .IsUnique() + .HasFilter("[IsDeleted] = 0"); + + b.ToTable("MaterialItems", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Master.Catalogs.ServiceItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Category") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DefaultUnit") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Category"); + + b.HasIndex("Code") + .IsUnique() + .HasFilter("[IsDeleted] = 0"); + + b.ToTable("ServiceItems", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Master.Catalogs.UnitOfMeasure", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique() + .HasFilter("[IsDeleted] = 0"); + + b.ToTable("UnitsOfMeasure", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Master.Catalogs.WorkItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Category") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DefaultUnit") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Category"); + + b.HasIndex("Code") + .IsUnique() + .HasFilter("[IsDeleted] = 0"); + + b.ToTable("WorkItems", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Master.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("ManagerUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Note") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("Departments", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Master.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BudgetTotal") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("ManagerUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Note") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("Projects", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Master.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ContactPerson") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Note") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("TaxCode") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("Type"); + + b.ToTable("Suppliers", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.Notifications.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Href") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ReadAt") + .HasColumnType("datetime2"); + + b.Property("RefId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId", "ReadAt"); + + b.ToTable("Notifications", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContractId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DepartmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("DiaDiem") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("DrafterUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("MaPhieu") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("MoTa") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("PaymentTerms") + .HasColumnType("nvarchar(max)"); + + b.Property("Phase") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("uniqueidentifier"); + + b.Property("SelectedSupplierId") + .HasColumnType("uniqueidentifier"); + + b.Property("SlaDeadline") + .HasColumnType("datetime2"); + + b.Property("SlaWarningSent") + .HasColumnType("bit"); + + b.Property("TenGoiThau") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("WorkflowDefinitionId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ContractId"); + + b.HasIndex("MaPhieu") + .IsUnique() + .HasFilter("[MaPhieu] IS NOT NULL"); + + b.HasIndex("ProjectId"); + + b.HasIndex("SlaDeadline"); + + b.HasIndex("WorkflowDefinitionId"); + + b.HasIndex("Phase", "IsDeleted"); + + b.ToTable("PurchaseEvaluations", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationApproval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApprovedAt") + .HasColumnType("datetime2"); + + b.Property("ApproverUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Decision") + .HasColumnType("int"); + + b.Property("FromPhase") + .HasColumnType("int"); + + b.Property("PurchaseEvaluationId") + .HasColumnType("uniqueidentifier"); + + b.Property("ToPhase") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationId", "ApprovedAt"); + + b.ToTable("PurchaseEvaluationApprovals", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("PurchaseEvaluationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PurchaseEvaluationSupplierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Purpose") + .HasColumnType("int"); + + b.Property("StoragePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationId"); + + b.HasIndex("PurchaseEvaluationSupplierId"); + + b.ToTable("PurchaseEvaluationAttachments", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationChangelog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("ContextNote") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityType") + .HasColumnType("int"); + + b.Property("FieldChangesJson") + .HasColumnType("nvarchar(max)"); + + b.Property("PhaseAtChange") + .HasColumnType("int"); + + b.Property("PurchaseEvaluationId") + .HasColumnType("uniqueidentifier"); + + b.Property("Summary") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationId", "CreatedAt"); + + b.HasIndex("PurchaseEvaluationId", "EntityType"); + + b.ToTable("PurchaseEvaluationChangelogs", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DonGiaNganSach") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("DonViTinh") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("GhiChu") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("GroupCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ItemCode") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("KhoiLuongNganSach") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("KhoiLuongThiCong") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NoiDung") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("PurchaseEvaluationId") + .HasColumnType("uniqueidentifier"); + + b.Property("ThanhTienNganSach") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationId", "Order"); + + b.ToTable("PurchaseEvaluationDetails", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationQuote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BgVat") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ChuaVat") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("IsSelected") + .HasColumnType("bit"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("PurchaseEvaluationDetailId") + .HasColumnType("uniqueidentifier"); + + b.Property("PurchaseEvaluationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PurchaseEvaluationSupplierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ThanhTien") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationId"); + + b.HasIndex("PurchaseEvaluationSupplierId"); + + b.HasIndex("PurchaseEvaluationDetailId", "PurchaseEvaluationSupplierId") + .IsUnique(); + + b.ToTable("PurchaseEvaluationQuotes", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationSupplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContactEmail") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ContactName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ContactPhone") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("PaymentTermText") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PurchaseEvaluationId") + .HasColumnType("uniqueidentifier"); + + b.Property("SupplierId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("SupplierId"); + + b.HasIndex("PurchaseEvaluationId", "SupplierId") + .IsUnique(); + + b.ToTable("PurchaseEvaluationSuppliers", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ActivatedAt") + .HasColumnType("datetime2"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EvaluationType") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Code", "Version") + .IsUnique(); + + b.HasIndex("EvaluationType", "IsActive"); + + b.ToTable("PurchaseEvaluationWorkflowDefinitions", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStep", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("Phase") + .HasColumnType("int"); + + b.Property("PurchaseEvaluationWorkflowDefinitionId") + .HasColumnType("uniqueidentifier"); + + b.Property("SlaDays") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationWorkflowDefinitionId", "Order"); + + b.ToTable("PurchaseEvaluationWorkflowSteps", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStepApprover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AssignmentValue") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .HasColumnType("int"); + + b.Property("PurchaseEvaluationWorkflowStepId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationWorkflowStepId"); + + b.ToTable("PurchaseEvaluationWorkflowStepApprovers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("SolutionErp.Domain.Identity.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("SolutionErp.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("SolutionErp.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("SolutionErp.Domain.Identity.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SolutionErp.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("SolutionErp.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractApproval", b => + { + b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract") + .WithMany("Approvals") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractAttachment", b => + { + b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract") + .WithMany("Attachments") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractChangelog", b => + { + b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract") + .WithMany("Changelogs") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.ContractComment", b => + { + b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract") + .WithMany("Comments") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.DichVuDetail", b => + { + b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract") + .WithMany("DichVuDetails") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.GiaoKhoanDetail", b => + { + b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract") + .WithMany("GiaoKhoanDetails") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.MuaBanDetail", b => + { + b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract") + .WithMany("MuaBanDetails") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.NguyenTacDvDetail", b => + { + b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract") + .WithMany("NguyenTacDvDetails") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.NguyenTacNccDetail", b => + { + b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract") + .WithMany("NguyenTacNccDetails") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.NhaCungCapDetail", b => + { + b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract") + .WithMany("NhaCungCapDetails") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Details.ThauPhuDetail", b => + { + b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract") + .WithMany("ThauPhuDetails") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.WorkflowStep", b => + { + b.HasOne("SolutionErp.Domain.Contracts.WorkflowDefinition", "WorkflowDefinition") + .WithMany("Steps") + .HasForeignKey("WorkflowDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WorkflowDefinition"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.WorkflowStepApprover", b => + { + b.HasOne("SolutionErp.Domain.Contracts.WorkflowStep", "Step") + .WithMany("Approvers") + .HasForeignKey("WorkflowStepId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Step"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Identity.MenuItem", b => + { + b.HasOne("SolutionErp.Domain.Identity.MenuItem", "Parent") + .WithMany("Children") + .HasForeignKey("ParentKey") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Identity.Permission", b => + { + b.HasOne("SolutionErp.Domain.Identity.MenuItem", "Menu") + .WithMany("Permissions") + .HasForeignKey("MenuKey") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SolutionErp.Domain.Identity.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Menu"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Identity.User", b => + { + b.HasOne("SolutionErp.Domain.Master.Department", null) + .WithMany() + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationApproval", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", "PurchaseEvaluation") + .WithMany("Approvals") + .HasForeignKey("PurchaseEvaluationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PurchaseEvaluation"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationAttachment", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", "PurchaseEvaluation") + .WithMany("Attachments") + .HasForeignKey("PurchaseEvaluationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PurchaseEvaluation"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationChangelog", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", "PurchaseEvaluation") + .WithMany("Changelogs") + .HasForeignKey("PurchaseEvaluationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PurchaseEvaluation"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationDetail", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", "PurchaseEvaluation") + .WithMany("Details") + .HasForeignKey("PurchaseEvaluationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PurchaseEvaluation"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationQuote", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationDetail", "Detail") + .WithMany("Quotes") + .HasForeignKey("PurchaseEvaluationDetailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", null) + .WithMany("Quotes") + .HasForeignKey("PurchaseEvaluationId"); + + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationSupplier", "Supplier") + .WithMany() + .HasForeignKey("PurchaseEvaluationSupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Detail"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationSupplier", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", "PurchaseEvaluation") + .WithMany("Suppliers") + .HasForeignKey("PurchaseEvaluationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PurchaseEvaluation"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStep", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowDefinition", "Definition") + .WithMany("Steps") + .HasForeignKey("PurchaseEvaluationWorkflowDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Definition"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStepApprover", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStep", "Step") + .WithMany("Approvers") + .HasForeignKey("PurchaseEvaluationWorkflowStepId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Step"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.Contract", b => + { + b.Navigation("Approvals"); + + b.Navigation("Attachments"); + + b.Navigation("Changelogs"); + + b.Navigation("Comments"); + + b.Navigation("DichVuDetails"); + + b.Navigation("GiaoKhoanDetails"); + + b.Navigation("MuaBanDetails"); + + b.Navigation("NguyenTacDvDetails"); + + b.Navigation("NguyenTacNccDetails"); + + b.Navigation("NhaCungCapDetails"); + + b.Navigation("ThauPhuDetails"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.WorkflowDefinition", b => + { + b.Navigation("Steps"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Contracts.WorkflowStep", b => + { + b.Navigation("Approvers"); + }); + + modelBuilder.Entity("SolutionErp.Domain.Identity.MenuItem", b => + { + b.Navigation("Children"); + + b.Navigation("Permissions"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", b => + { + b.Navigation("Approvals"); + + b.Navigation("Attachments"); + + b.Navigation("Changelogs"); + + b.Navigation("Details"); + + b.Navigation("Quotes"); + + b.Navigation("Suppliers"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationDetail", b => + { + b.Navigation("Quotes"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowDefinition", b => + { + b.Navigation("Steps"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStep", b => + { + b.Navigation("Approvers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260423093608_AddPurchaseEvaluations.cs b/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260423093608_AddPurchaseEvaluations.cs new file mode 100644 index 0000000..c6f4a52 --- /dev/null +++ b/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260423093608_AddPurchaseEvaluations.cs @@ -0,0 +1,455 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SolutionErp.Infrastructure.Persistence.Migrations +{ + /// + public partial class AddPurchaseEvaluations : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PurchaseEvaluations", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + MaPhieu = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + Type = table.Column(type: "int", nullable: false), + Phase = table.Column(type: "int", nullable: false), + TenGoiThau = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + ProjectId = table.Column(type: "uniqueidentifier", nullable: false), + DepartmentId = table.Column(type: "uniqueidentifier", nullable: true), + DrafterUserId = table.Column(type: "uniqueidentifier", nullable: true), + DiaDiem = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + MoTa = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), + WorkflowDefinitionId = table.Column(type: "uniqueidentifier", nullable: true), + SlaDeadline = table.Column(type: "datetime2", nullable: true), + SlaWarningSent = table.Column(type: "bit", nullable: false), + SelectedSupplierId = table.Column(type: "uniqueidentifier", nullable: true), + PaymentTerms = table.Column(type: "nvarchar(max)", nullable: true), + ContractId = table.Column(type: "uniqueidentifier", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: true), + CreatedBy = table.Column(type: "uniqueidentifier", nullable: true), + UpdatedBy = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false), + DeletedAt = table.Column(type: "datetime2", nullable: true), + DeletedBy = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PurchaseEvaluations", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PurchaseEvaluationWorkflowDefinitions", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Code = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Version = table.Column(type: "int", nullable: false), + EvaluationType = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + IsActive = table.Column(type: "bit", nullable: false), + ActivatedAt = table.Column(type: "datetime2", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: true), + CreatedBy = table.Column(type: "uniqueidentifier", nullable: true), + UpdatedBy = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PurchaseEvaluationWorkflowDefinitions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PurchaseEvaluationApprovals", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + PurchaseEvaluationId = table.Column(type: "uniqueidentifier", nullable: false), + FromPhase = table.Column(type: "int", nullable: false), + ToPhase = table.Column(type: "int", nullable: false), + ApproverUserId = table.Column(type: "uniqueidentifier", nullable: true), + Decision = table.Column(type: "int", nullable: false), + Comment = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + ApprovedAt = table.Column(type: "datetime2", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: true), + CreatedBy = table.Column(type: "uniqueidentifier", nullable: true), + UpdatedBy = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PurchaseEvaluationApprovals", x => x.Id); + table.ForeignKey( + name: "FK_PurchaseEvaluationApprovals_PurchaseEvaluations_PurchaseEvaluationId", + column: x => x.PurchaseEvaluationId, + principalTable: "PurchaseEvaluations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PurchaseEvaluationAttachments", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + PurchaseEvaluationId = table.Column(type: "uniqueidentifier", nullable: false), + PurchaseEvaluationSupplierId = table.Column(type: "uniqueidentifier", nullable: true), + FileName = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: false), + StoragePath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + FileSize = table.Column(type: "bigint", nullable: false), + ContentType = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Purpose = table.Column(type: "int", nullable: false), + Note = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: true), + CreatedBy = table.Column(type: "uniqueidentifier", nullable: true), + UpdatedBy = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PurchaseEvaluationAttachments", x => x.Id); + table.ForeignKey( + name: "FK_PurchaseEvaluationAttachments_PurchaseEvaluations_PurchaseEvaluationId", + column: x => x.PurchaseEvaluationId, + principalTable: "PurchaseEvaluations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PurchaseEvaluationChangelogs", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + PurchaseEvaluationId = table.Column(type: "uniqueidentifier", nullable: false), + EntityType = table.Column(type: "int", nullable: false), + EntityId = table.Column(type: "uniqueidentifier", nullable: true), + Action = table.Column(type: "int", nullable: false), + PhaseAtChange = table.Column(type: "int", nullable: true), + UserId = table.Column(type: "uniqueidentifier", nullable: true), + UserName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + Summary = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + FieldChangesJson = table.Column(type: "nvarchar(max)", nullable: true), + ContextNote = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: true), + CreatedBy = table.Column(type: "uniqueidentifier", nullable: true), + UpdatedBy = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PurchaseEvaluationChangelogs", x => x.Id); + table.ForeignKey( + name: "FK_PurchaseEvaluationChangelogs_PurchaseEvaluations_PurchaseEvaluationId", + column: x => x.PurchaseEvaluationId, + principalTable: "PurchaseEvaluations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PurchaseEvaluationDetails", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + PurchaseEvaluationId = table.Column(type: "uniqueidentifier", nullable: false), + GroupCode = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + GroupName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + ItemCode = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + NoiDung = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + DonViTinh = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + KhoiLuongNganSach = table.Column(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false), + KhoiLuongThiCong = table.Column(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false), + DonGiaNganSach = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), + ThanhTienNganSach = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), + Order = table.Column(type: "int", nullable: false), + GhiChu = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: true), + CreatedBy = table.Column(type: "uniqueidentifier", nullable: true), + UpdatedBy = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PurchaseEvaluationDetails", x => x.Id); + table.ForeignKey( + name: "FK_PurchaseEvaluationDetails_PurchaseEvaluations_PurchaseEvaluationId", + column: x => x.PurchaseEvaluationId, + principalTable: "PurchaseEvaluations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PurchaseEvaluationSuppliers", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + PurchaseEvaluationId = table.Column(type: "uniqueidentifier", nullable: false), + SupplierId = table.Column(type: "uniqueidentifier", nullable: false), + DisplayName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + ContactName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + ContactEmail = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + ContactPhone = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), + PaymentTermText = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + Note = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + Order = table.Column(type: "int", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: true), + CreatedBy = table.Column(type: "uniqueidentifier", nullable: true), + UpdatedBy = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PurchaseEvaluationSuppliers", x => x.Id); + table.ForeignKey( + name: "FK_PurchaseEvaluationSuppliers_PurchaseEvaluations_PurchaseEvaluationId", + column: x => x.PurchaseEvaluationId, + principalTable: "PurchaseEvaluations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PurchaseEvaluationWorkflowSteps", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + PurchaseEvaluationWorkflowDefinitionId = table.Column(type: "uniqueidentifier", nullable: false), + Order = table.Column(type: "int", nullable: false), + Phase = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + SlaDays = table.Column(type: "int", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: true), + CreatedBy = table.Column(type: "uniqueidentifier", nullable: true), + UpdatedBy = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PurchaseEvaluationWorkflowSteps", x => x.Id); + table.ForeignKey( + name: "FK_PurchaseEvaluationWorkflowSteps_PurchaseEvaluationWorkflowDefinitions_PurchaseEvaluationWorkflowDefinitionId", + column: x => x.PurchaseEvaluationWorkflowDefinitionId, + principalTable: "PurchaseEvaluationWorkflowDefinitions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PurchaseEvaluationQuotes", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + PurchaseEvaluationDetailId = table.Column(type: "uniqueidentifier", nullable: false), + PurchaseEvaluationSupplierId = table.Column(type: "uniqueidentifier", nullable: false), + BgVat = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), + ChuaVat = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), + ThanhTien = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), + IsSelected = table.Column(type: "bit", nullable: false), + Note = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + PurchaseEvaluationId = table.Column(type: "uniqueidentifier", nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: true), + CreatedBy = table.Column(type: "uniqueidentifier", nullable: true), + UpdatedBy = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PurchaseEvaluationQuotes", x => x.Id); + table.ForeignKey( + name: "FK_PurchaseEvaluationQuotes_PurchaseEvaluationDetails_PurchaseEvaluationDetailId", + column: x => x.PurchaseEvaluationDetailId, + principalTable: "PurchaseEvaluationDetails", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PurchaseEvaluationQuotes_PurchaseEvaluationSuppliers_PurchaseEvaluationSupplierId", + column: x => x.PurchaseEvaluationSupplierId, + principalTable: "PurchaseEvaluationSuppliers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_PurchaseEvaluationQuotes_PurchaseEvaluations_PurchaseEvaluationId", + column: x => x.PurchaseEvaluationId, + principalTable: "PurchaseEvaluations", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "PurchaseEvaluationWorkflowStepApprovers", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + PurchaseEvaluationWorkflowStepId = table.Column(type: "uniqueidentifier", nullable: false), + Kind = table.Column(type: "int", nullable: false), + AssignmentValue = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: true), + CreatedBy = table.Column(type: "uniqueidentifier", nullable: true), + UpdatedBy = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PurchaseEvaluationWorkflowStepApprovers", x => x.Id); + table.ForeignKey( + name: "FK_PurchaseEvaluationWorkflowStepApprovers_PurchaseEvaluationWorkflowSteps_PurchaseEvaluationWorkflowStepId", + column: x => x.PurchaseEvaluationWorkflowStepId, + principalTable: "PurchaseEvaluationWorkflowSteps", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationApprovals_PurchaseEvaluationId_ApprovedAt", + table: "PurchaseEvaluationApprovals", + columns: new[] { "PurchaseEvaluationId", "ApprovedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationAttachments_PurchaseEvaluationId", + table: "PurchaseEvaluationAttachments", + column: "PurchaseEvaluationId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationAttachments_PurchaseEvaluationSupplierId", + table: "PurchaseEvaluationAttachments", + column: "PurchaseEvaluationSupplierId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationChangelogs_PurchaseEvaluationId_CreatedAt", + table: "PurchaseEvaluationChangelogs", + columns: new[] { "PurchaseEvaluationId", "CreatedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationChangelogs_PurchaseEvaluationId_EntityType", + table: "PurchaseEvaluationChangelogs", + columns: new[] { "PurchaseEvaluationId", "EntityType" }); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationDetails_PurchaseEvaluationId_Order", + table: "PurchaseEvaluationDetails", + columns: new[] { "PurchaseEvaluationId", "Order" }); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationQuotes_PurchaseEvaluationDetailId_PurchaseEvaluationSupplierId", + table: "PurchaseEvaluationQuotes", + columns: new[] { "PurchaseEvaluationDetailId", "PurchaseEvaluationSupplierId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationQuotes_PurchaseEvaluationId", + table: "PurchaseEvaluationQuotes", + column: "PurchaseEvaluationId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationQuotes_PurchaseEvaluationSupplierId", + table: "PurchaseEvaluationQuotes", + column: "PurchaseEvaluationSupplierId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluations_ContractId", + table: "PurchaseEvaluations", + column: "ContractId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluations_MaPhieu", + table: "PurchaseEvaluations", + column: "MaPhieu", + unique: true, + filter: "[MaPhieu] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluations_Phase_IsDeleted", + table: "PurchaseEvaluations", + columns: new[] { "Phase", "IsDeleted" }); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluations_ProjectId", + table: "PurchaseEvaluations", + column: "ProjectId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluations_SlaDeadline", + table: "PurchaseEvaluations", + column: "SlaDeadline"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluations_WorkflowDefinitionId", + table: "PurchaseEvaluations", + column: "WorkflowDefinitionId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationSuppliers_PurchaseEvaluationId_SupplierId", + table: "PurchaseEvaluationSuppliers", + columns: new[] { "PurchaseEvaluationId", "SupplierId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationSuppliers_SupplierId", + table: "PurchaseEvaluationSuppliers", + column: "SupplierId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationWorkflowDefinitions_Code_Version", + table: "PurchaseEvaluationWorkflowDefinitions", + columns: new[] { "Code", "Version" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationWorkflowDefinitions_EvaluationType_IsActive", + table: "PurchaseEvaluationWorkflowDefinitions", + columns: new[] { "EvaluationType", "IsActive" }); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationWorkflowStepApprovers_PurchaseEvaluationWorkflowStepId", + table: "PurchaseEvaluationWorkflowStepApprovers", + column: "PurchaseEvaluationWorkflowStepId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseEvaluationWorkflowSteps_PurchaseEvaluationWorkflowDefinitionId_Order", + table: "PurchaseEvaluationWorkflowSteps", + columns: new[] { "PurchaseEvaluationWorkflowDefinitionId", "Order" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PurchaseEvaluationApprovals"); + + migrationBuilder.DropTable( + name: "PurchaseEvaluationAttachments"); + + migrationBuilder.DropTable( + name: "PurchaseEvaluationChangelogs"); + + migrationBuilder.DropTable( + name: "PurchaseEvaluationQuotes"); + + migrationBuilder.DropTable( + name: "PurchaseEvaluationWorkflowStepApprovers"); + + migrationBuilder.DropTable( + name: "PurchaseEvaluationDetails"); + + migrationBuilder.DropTable( + name: "PurchaseEvaluationSuppliers"); + + migrationBuilder.DropTable( + name: "PurchaseEvaluationWorkflowSteps"); + + migrationBuilder.DropTable( + name: "PurchaseEvaluations"); + + migrationBuilder.DropTable( + name: "PurchaseEvaluationWorkflowDefinitions"); + } + } +} diff --git a/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index 456b87d..a0eef73 100644 --- a/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1904,6 +1904,592 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations b.ToTable("Notifications", (string)null); }); + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContractId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletedAt") + .HasColumnType("datetime2"); + + b.Property("DeletedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DepartmentId") + .HasColumnType("uniqueidentifier"); + + b.Property("DiaDiem") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("DrafterUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("MaPhieu") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("MoTa") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("PaymentTerms") + .HasColumnType("nvarchar(max)"); + + b.Property("Phase") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("uniqueidentifier"); + + b.Property("SelectedSupplierId") + .HasColumnType("uniqueidentifier"); + + b.Property("SlaDeadline") + .HasColumnType("datetime2"); + + b.Property("SlaWarningSent") + .HasColumnType("bit"); + + b.Property("TenGoiThau") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("WorkflowDefinitionId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ContractId"); + + b.HasIndex("MaPhieu") + .IsUnique() + .HasFilter("[MaPhieu] IS NOT NULL"); + + b.HasIndex("ProjectId"); + + b.HasIndex("SlaDeadline"); + + b.HasIndex("WorkflowDefinitionId"); + + b.HasIndex("Phase", "IsDeleted"); + + b.ToTable("PurchaseEvaluations", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationApproval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApprovedAt") + .HasColumnType("datetime2"); + + b.Property("ApproverUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Decision") + .HasColumnType("int"); + + b.Property("FromPhase") + .HasColumnType("int"); + + b.Property("PurchaseEvaluationId") + .HasColumnType("uniqueidentifier"); + + b.Property("ToPhase") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationId", "ApprovedAt"); + + b.ToTable("PurchaseEvaluationApprovals", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("PurchaseEvaluationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PurchaseEvaluationSupplierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Purpose") + .HasColumnType("int"); + + b.Property("StoragePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationId"); + + b.HasIndex("PurchaseEvaluationSupplierId"); + + b.ToTable("PurchaseEvaluationAttachments", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationChangelog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("ContextNote") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityType") + .HasColumnType("int"); + + b.Property("FieldChangesJson") + .HasColumnType("nvarchar(max)"); + + b.Property("PhaseAtChange") + .HasColumnType("int"); + + b.Property("PurchaseEvaluationId") + .HasColumnType("uniqueidentifier"); + + b.Property("Summary") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationId", "CreatedAt"); + + b.HasIndex("PurchaseEvaluationId", "EntityType"); + + b.ToTable("PurchaseEvaluationChangelogs", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DonGiaNganSach") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("DonViTinh") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("GhiChu") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("GroupCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ItemCode") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("KhoiLuongNganSach") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("KhoiLuongThiCong") + .HasPrecision(18, 4) + .HasColumnType("decimal(18,4)"); + + b.Property("NoiDung") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("PurchaseEvaluationId") + .HasColumnType("uniqueidentifier"); + + b.Property("ThanhTienNganSach") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationId", "Order"); + + b.ToTable("PurchaseEvaluationDetails", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationQuote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BgVat") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ChuaVat") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("IsSelected") + .HasColumnType("bit"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("PurchaseEvaluationDetailId") + .HasColumnType("uniqueidentifier"); + + b.Property("PurchaseEvaluationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PurchaseEvaluationSupplierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ThanhTien") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationId"); + + b.HasIndex("PurchaseEvaluationSupplierId"); + + b.HasIndex("PurchaseEvaluationDetailId", "PurchaseEvaluationSupplierId") + .IsUnique(); + + b.ToTable("PurchaseEvaluationQuotes", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationSupplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContactEmail") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ContactName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ContactPhone") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("PaymentTermText") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PurchaseEvaluationId") + .HasColumnType("uniqueidentifier"); + + b.Property("SupplierId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("SupplierId"); + + b.HasIndex("PurchaseEvaluationId", "SupplierId") + .IsUnique(); + + b.ToTable("PurchaseEvaluationSuppliers", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ActivatedAt") + .HasColumnType("datetime2"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EvaluationType") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Code", "Version") + .IsUnique(); + + b.HasIndex("EvaluationType", "IsActive"); + + b.ToTable("PurchaseEvaluationWorkflowDefinitions", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStep", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("Phase") + .HasColumnType("int"); + + b.Property("PurchaseEvaluationWorkflowDefinitionId") + .HasColumnType("uniqueidentifier"); + + b.Property("SlaDays") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationWorkflowDefinitionId", "Order"); + + b.ToTable("PurchaseEvaluationWorkflowSteps", (string)null); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStepApprover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AssignmentValue") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("uniqueidentifier"); + + b.Property("Kind") + .HasColumnType("int"); + + b.Property("PurchaseEvaluationWorkflowStepId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedBy") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseEvaluationWorkflowStepId"); + + b.ToTable("PurchaseEvaluationWorkflowStepApprovers", (string)null); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("SolutionErp.Domain.Identity.Role", null) @@ -2135,6 +2721,106 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations .OnDelete(DeleteBehavior.Restrict); }); + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationApproval", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", "PurchaseEvaluation") + .WithMany("Approvals") + .HasForeignKey("PurchaseEvaluationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PurchaseEvaluation"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationAttachment", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", "PurchaseEvaluation") + .WithMany("Attachments") + .HasForeignKey("PurchaseEvaluationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PurchaseEvaluation"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationChangelog", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", "PurchaseEvaluation") + .WithMany("Changelogs") + .HasForeignKey("PurchaseEvaluationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PurchaseEvaluation"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationDetail", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", "PurchaseEvaluation") + .WithMany("Details") + .HasForeignKey("PurchaseEvaluationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PurchaseEvaluation"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationQuote", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationDetail", "Detail") + .WithMany("Quotes") + .HasForeignKey("PurchaseEvaluationDetailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", null) + .WithMany("Quotes") + .HasForeignKey("PurchaseEvaluationId"); + + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationSupplier", "Supplier") + .WithMany() + .HasForeignKey("PurchaseEvaluationSupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Detail"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationSupplier", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", "PurchaseEvaluation") + .WithMany("Suppliers") + .HasForeignKey("PurchaseEvaluationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PurchaseEvaluation"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStep", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowDefinition", "Definition") + .WithMany("Steps") + .HasForeignKey("PurchaseEvaluationWorkflowDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Definition"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStepApprover", b => + { + b.HasOne("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStep", "Step") + .WithMany("Approvers") + .HasForeignKey("PurchaseEvaluationWorkflowStepId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Step"); + }); + modelBuilder.Entity("SolutionErp.Domain.Contracts.Contract", b => { b.Navigation("Approvals"); @@ -2176,6 +2862,36 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations b.Navigation("Permissions"); }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", b => + { + b.Navigation("Approvals"); + + b.Navigation("Attachments"); + + b.Navigation("Changelogs"); + + b.Navigation("Details"); + + b.Navigation("Quotes"); + + b.Navigation("Suppliers"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationDetail", b => + { + b.Navigation("Quotes"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowDefinition", b => + { + b.Navigation("Steps"); + }); + + modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluationWorkflowStep", b => + { + b.Navigation("Approvers"); + }); #pragma warning restore 612, 618 } }