[CLAUDE] PurchaseEvaluation: ngan sach goi thau theo Excel anh Kiet - bang tong hop 2 block + nhap theo role PRO/CCM + xoa module Budget cu (Mig 50)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m31s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m31s
- Mig 50 ReplaceBudgetModuleWithPeWorkItemBudgets: bang moi PeWorkItemBudgets (1 record/cap Du an x Hang muc, UNIQUE filtered [IsDeleted]=0) + drop 5 bang Budget cu + PE/Contracts drop BudgetId + backfill BudgetManualAmount->BudgetPeriodAmount TRUOC DropColumn (phieu UAT giu so) + DELETE menu/permission Bg_* IN-list children-first
- BE: PUT {id}/budget/pro (role Procurement) + {id}/budget/ccm (role CostControl, Adjustment cho phep AM) fail-closed Forbidden-truoc-side-effect + EnsureTrackedAsync race-safe (catch unique -> re-fetch winner, loi khac rethrow) + auto-create record khi tao phieu + budgetSummary DTO (luy ke trinh-truoc/chon-thau-truoc/de-xuat-ky-nay + full fallback du-tru-PRO + canEdit flags) + submit-guard (3) doi predicate BudgetPeriodAmount -> "chua nhap Ngan sach ky nay" + PATCH budget-adjust absolute-set 2 field moi + Contract GIU BudgetManual* (HD nhap tay khong doi) + ke thua HD map BudgetPeriodAmount
- FE x2 app SHA256 identical: bang "TONG HOP NGAN SACH TRINH KY" block A (full dam + ban hanh + V0 hieu chinh + du tru PRO + ghi chu, editable theo canEditPro/canEditCcm) + block B 9 dong cong thuc Excel (5=1+3, 6=2+4, 7=full-5, 8 tu nhap default 7, 9=4+8) + to mau vuot ngan sach #C00000 / am do / red-soft row8>row7 + "Chua chon" khi count=0 + banner phieu chua gan Hang muc + o "Ngan sach ky nay" o create/header + XOA pages/components/types budgets + routes + menuKeys + Layout staticMap 4-place
- Tests: +22 PeWorkItemBudgetTests (auto-create x3, ensure/race x2, authz matrix PRO x5 + CCM x3, budgetSummary aggregates x5, adjust x4) - 14 BudgetPolicyTests xoa theo module - 1 test via-BudgetId -> 263 PASS (45 Domain + 218 Infra, 0 fail)
- database-agent advise adopted: khong FK vat ly PE/Contracts->Budgets (DropColumn khong can DropForeignKey) + DropIndex truoc DropColumn (SQL 5074) + IN-list thay LIKE Bg_% (underscore wildcard + miss root) + khong Serializable wrap (nested-tx conflict codegen)
- Reviewer PASS-with-minor 0 blocker (verdict-first survived); 2 minor da sua truoc commit (comment adjustMut absolute-set + dead key budgetId); note: F4 approver-edit-budget UI entry tam drafter-only, BE van cho approver scope - cho UAT anh Kiet
- Scaffold-bug caught: EF tu sinh RenameColumn BudgetManualAmount->ExpectedRemainingAmount (SAI semantics) -> thay bang Add+UPDATE+Drop
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Domain.ApprovalWorkflowsV2;
|
||||
using SolutionErp.Domain.Budgets;
|
||||
using SolutionErp.Domain.Contracts;
|
||||
using SolutionErp.Domain.Contracts.Details;
|
||||
using SolutionErp.Domain.Forms;
|
||||
@ -69,19 +68,14 @@ public class ApplicationDbContext
|
||||
public DbSet<PurchaseEvaluationDepartmentApproval> PurchaseEvaluationDepartmentApprovals => Set<PurchaseEvaluationDepartmentApproval>();
|
||||
// Mig 26 (Session 19) — Ý kiến cấp duyệt V2 dynamic
|
||||
public DbSet<PurchaseEvaluationLevelOpinion> PurchaseEvaluationLevelOpinions => Set<PurchaseEvaluationLevelOpinion>();
|
||||
// [S61 Mig 50] Ngân sách gói thầu per cặp (ProjectId, WorkItemId) — thay module Budget cũ.
|
||||
public DbSet<PeWorkItemBudget> PeWorkItemBudgets => Set<PeWorkItemBudget>();
|
||||
|
||||
// Quy trình duyệt mới (Mig 22 — Session 17): schema riêng UAT.
|
||||
public DbSet<ApprovalWorkflow> ApprovalWorkflows => Set<ApprovalWorkflow>();
|
||||
public DbSet<ApprovalWorkflowStep> ApprovalWorkflowSteps => Set<ApprovalWorkflowStep>();
|
||||
public DbSet<ApprovalWorkflowLevel> ApprovalWorkflowLevels => Set<ApprovalWorkflowLevel>();
|
||||
|
||||
// Module Ngân sách (Phase 7) — 4 bảng: Budget header + Details + Approvals + Changelogs.
|
||||
public DbSet<Budget> Budgets => Set<Budget>();
|
||||
public DbSet<BudgetDetail> BudgetDetails => Set<BudgetDetail>();
|
||||
public DbSet<BudgetApproval> BudgetApprovals => Set<BudgetApproval>();
|
||||
public DbSet<BudgetChangelog> BudgetChangelogs => Set<BudgetChangelog>();
|
||||
public DbSet<BudgetDepartmentApproval> BudgetDepartmentApprovals => Set<BudgetDepartmentApproval>();
|
||||
|
||||
// Phase 10.1 G-H1 (Mig 34 — S33) — Hồ sơ Nhân sự port từ NamGroup.
|
||||
public DbSet<EmployeeProfile> EmployeeProfiles => Set<EmployeeProfile>();
|
||||
public DbSet<EmployeeWorkHistory> EmployeeWorkHistories => Set<EmployeeWorkHistory>();
|
||||
|
||||
@ -1,90 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.Budgets;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
public class BudgetConfiguration : IEntityTypeConfiguration<Budget>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Budget> b)
|
||||
{
|
||||
b.ToTable("Budgets");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.MaNganSach).HasMaxLength(100);
|
||||
b.Property(x => x.TenNganSach).HasMaxLength(500).IsRequired();
|
||||
b.Property(x => x.Description).HasMaxLength(2000);
|
||||
b.Property(x => x.Phase).HasConversion<int>();
|
||||
b.Property(x => x.RejectedFromPhase).HasConversion<int?>();
|
||||
b.Property(x => x.TongNganSach).HasPrecision(18, 2);
|
||||
|
||||
b.HasIndex(x => x.MaNganSach).IsUnique().HasFilter("[MaNganSach] IS NOT NULL");
|
||||
b.HasIndex(x => new { x.Phase, x.IsDeleted });
|
||||
b.HasIndex(x => x.ProjectId);
|
||||
b.HasIndex(x => x.NamNganSach);
|
||||
b.HasIndex(x => x.SlaDeadline);
|
||||
|
||||
b.HasMany(x => x.Details).WithOne(d => d.Budget).HasForeignKey(d => d.BudgetId).OnDelete(DeleteBehavior.Cascade);
|
||||
b.HasMany(x => x.Approvals).WithOne(a => a.Budget).HasForeignKey(a => a.BudgetId).OnDelete(DeleteBehavior.Cascade);
|
||||
b.HasMany(x => x.Changelogs).WithOne(c => c.Budget).HasForeignKey(c => c.BudgetId).OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasQueryFilter(x => !x.IsDeleted);
|
||||
}
|
||||
}
|
||||
|
||||
public class BudgetDetailConfiguration : IEntityTypeConfiguration<BudgetDetail>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<BudgetDetail> b)
|
||||
{
|
||||
b.ToTable("BudgetDetails");
|
||||
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.KhoiLuong).HasPrecision(18, 4);
|
||||
b.Property(x => x.DonGia).HasPrecision(18, 2);
|
||||
b.Property(x => x.ThanhTien).HasPrecision(18, 2);
|
||||
|
||||
b.HasIndex(x => new { x.BudgetId, x.Order });
|
||||
}
|
||||
}
|
||||
|
||||
public class BudgetApprovalConfiguration : IEntityTypeConfiguration<BudgetApproval>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<BudgetApproval> b)
|
||||
{
|
||||
b.ToTable("BudgetApprovals");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.FromPhase).HasConversion<int>();
|
||||
b.Property(x => x.ToPhase).HasConversion<int>();
|
||||
b.Property(x => x.Decision).HasConversion<int>();
|
||||
b.Property(x => x.Comment).HasMaxLength(1000);
|
||||
|
||||
b.HasIndex(x => new { x.BudgetId, x.ApprovedAt });
|
||||
}
|
||||
}
|
||||
|
||||
public class BudgetChangelogConfiguration : IEntityTypeConfiguration<BudgetChangelog>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<BudgetChangelog> b)
|
||||
{
|
||||
b.ToTable("BudgetChangelogs");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.EntityType).HasConversion<int>();
|
||||
b.Property(x => x.Action).HasConversion<int>();
|
||||
b.Property(x => x.PhaseAtChange).HasConversion<int>();
|
||||
b.Property(x => x.UserName).HasMaxLength(200);
|
||||
b.Property(x => x.Summary).HasMaxLength(500);
|
||||
b.Property(x => x.ContextNote).HasMaxLength(2000);
|
||||
b.Property(x => x.FieldChangesJson).HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasIndex(x => new { x.BudgetId, x.CreatedAt });
|
||||
b.HasIndex(x => new { x.BudgetId, x.EntityType });
|
||||
}
|
||||
}
|
||||
@ -27,7 +27,6 @@ public class ContractConfiguration : IEntityTypeConfiguration<Contract>
|
||||
b.HasIndex(x => x.SupplierId);
|
||||
b.HasIndex(x => x.ProjectId);
|
||||
b.HasIndex(x => x.SlaDeadline);
|
||||
b.HasIndex(x => x.BudgetId);
|
||||
b.HasIndex(x => x.ApprovalWorkflowId);
|
||||
|
||||
// FK ApprovalWorkflowId Restrict (Plan B Chunk A2 — Mig 32 mirror PE Mig 23)
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.Budgets;
|
||||
using SolutionErp.Domain.Contracts;
|
||||
using SolutionErp.Domain.PurchaseEvaluations;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
// 3 bảng *DepartmentApprovals (Phase 9 — Migration 16) — 2-stage approval per
|
||||
// phase × phòng ban cho HĐ / PE / Budget.
|
||||
// 2 bảng *DepartmentApprovals (Phase 9 — Migration 16) — 2-stage approval per
|
||||
// phase × phòng ban cho HĐ / PE. (Budget variant XÓA cùng module Budget cũ —
|
||||
// S61 Mig 50.)
|
||||
//
|
||||
// UNIQUE (TargetId, PhaseAtApproval, DepartmentId, Stage) — 1 phase × 1 phòng
|
||||
// × 1 stage = 1 row duy nhất. UPDATE in-place khi user đổi ý → audit qua
|
||||
// *Changelog.
|
||||
//
|
||||
// FK Cascade theo target (xóa HĐ/PE/Budget → xóa approval rows).
|
||||
// FK Cascade theo target (xóa HĐ/PE → xóa approval rows).
|
||||
|
||||
public class ContractDepartmentApprovalConfiguration
|
||||
: IEntityTypeConfiguration<ContractDepartmentApproval>
|
||||
@ -70,29 +70,3 @@ public class PurchaseEvaluationDepartmentApprovalConfiguration
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
public class BudgetDepartmentApprovalConfiguration
|
||||
: IEntityTypeConfiguration<BudgetDepartmentApproval>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<BudgetDepartmentApproval> b)
|
||||
{
|
||||
b.ToTable("BudgetDepartmentApprovals");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Stage).HasConversion<int>();
|
||||
b.Property(x => x.ApproverRoleSnapshot).HasMaxLength(100);
|
||||
b.Property(x => x.Comment).HasMaxLength(1000);
|
||||
|
||||
b.HasIndex(x => new { x.BudgetId, x.PhaseAtApproval, x.DepartmentId, x.Stage })
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_BudgetDeptApprovals_Budget_Phase_Dept_Stage");
|
||||
b.HasIndex(x => x.BudgetId);
|
||||
b.HasIndex(x => x.DepartmentId);
|
||||
b.HasIndex(x => x.ApproverUserId);
|
||||
|
||||
b.HasOne(x => x.Budget)
|
||||
.WithMany(c => c.DepartmentApprovals)
|
||||
.HasForeignKey(x => x.BudgetId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.PurchaseEvaluations;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
// [S61 Mig 50] Ngân sách gói thầu per cặp (ProjectId, WorkItemId) — thay module
|
||||
// Budget cũ. Loose-Guid convention PE: KHÔNG FK vật lý, KHÔNG navigation.
|
||||
// UNIQUE composite FILTERED [IsDeleted] = 0 day-1 (gotcha #57 — soft-delete +
|
||||
// UNIQUE phải filter, filter string byte-for-byte khớp 13 index filtered hiện có).
|
||||
public class PeWorkItemBudgetConfiguration : IEntityTypeConfiguration<PeWorkItemBudget>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PeWorkItemBudget> b)
|
||||
{
|
||||
b.ToTable("PeWorkItemBudgets");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
// Precision match BudgetManualAmount cũ (18,2).
|
||||
b.Property(x => x.ProEstimateAmount).HasPrecision(18, 2);
|
||||
b.Property(x => x.InitialAmount).HasPrecision(18, 2);
|
||||
b.Property(x => x.AdjustmentAmount).HasPrecision(18, 2);
|
||||
b.Property(x => x.ProNote).HasMaxLength(1000);
|
||||
|
||||
b.HasIndex(x => new { x.ProjectId, x.WorkItemId })
|
||||
.IsUnique()
|
||||
.HasFilter("[IsDeleted] = 0");
|
||||
b.HasIndex(x => x.WorkItemId);
|
||||
|
||||
b.HasQueryFilter(x => !x.IsDeleted);
|
||||
}
|
||||
}
|
||||
@ -19,8 +19,9 @@ public class PurchaseEvaluationConfiguration : IEntityTypeConfiguration<Purchase
|
||||
b.Property(x => x.DiaDiem).HasMaxLength(500);
|
||||
b.Property(x => x.MoTa).HasMaxLength(2000);
|
||||
b.Property(x => x.PaymentTerms).HasColumnType("nvarchar(max)");
|
||||
b.Property(x => x.BudgetManualName).HasMaxLength(200);
|
||||
b.Property(x => x.BudgetManualAmount).HasPrecision(18, 2);
|
||||
// [S61 Mig 50] 2 cột ngân sách mới thay BudgetManual* — precision giữ (18,2).
|
||||
b.Property(x => x.BudgetPeriodAmount).HasPrecision(18, 2);
|
||||
b.Property(x => x.ExpectedRemainingAmount).HasPrecision(18, 2);
|
||||
|
||||
b.HasIndex(x => x.MaPhieu).IsUnique().HasFilter("[MaPhieu] IS NOT NULL");
|
||||
b.HasIndex(x => new { x.Phase, x.IsDeleted });
|
||||
@ -32,7 +33,6 @@ public class PurchaseEvaluationConfiguration : IEntityTypeConfiguration<Purchase
|
||||
b.HasIndex(x => x.WorkflowDefinitionId);
|
||||
b.HasIndex(x => x.ApprovalWorkflowId);
|
||||
b.HasIndex(x => x.ContractId);
|
||||
b.HasIndex(x => x.BudgetId);
|
||||
|
||||
// FK ApprovalWorkflowId Restrict (Mig 23 Session 17) — schema mới
|
||||
// ApprovalWorkflowsV2 pin lúc create. Restrict để KHÔNG xóa workflow
|
||||
|
||||
@ -1797,11 +1797,8 @@ public static class DbInitializer
|
||||
(MenuKeys.ApprovalWorkflowsV2, "Quy trình duyệt (Mới)", MenuKeys.System, 96, "Workflow"),
|
||||
(MenuKeys.ApprovalWorkflowDuyetNccV2, "Duyệt NCC (Mới)", MenuKeys.ApprovalWorkflowsV2, 1, "FileCheck"),
|
||||
(MenuKeys.ApprovalWorkflowDuyetNccPhuongAnV2, "Duyệt NCC và Giải pháp (Mới)", MenuKeys.ApprovalWorkflowsV2, 2, "FileCheck"),
|
||||
// Module Ngân sách (Phase 7)
|
||||
(MenuKeys.Budgets, "Ngân sách", null, 27, "Wallet"),
|
||||
(MenuKeys.BudgetList, "Danh sách", MenuKeys.Budgets, 1, "List"),
|
||||
(MenuKeys.BudgetCreate, "Thao tác", MenuKeys.Budgets, 2, "Plus"),
|
||||
(MenuKeys.BudgetPending, "Duyệt", MenuKeys.Budgets, 3, "CheckCircle2"),
|
||||
// [S61 Mig 50] Menu module Ngân sách cũ (Budgets + 3 leaf Bg_*) đã XÓA —
|
||||
// rows cũ trên DB cleanup qua SQL trong migration (idempotent DELETE).
|
||||
// Module Nhân sự (Phase 10.1 G-H1 — Mig 34 S33). Root operational HR.
|
||||
// [S57] "Cấu hình HRM" re-parent sang "Danh mục" (Master) — gom config 1 chỗ.
|
||||
// Hrm còn: Dashboard(1) → Hồ sơ(2), Dashboard đầu nhóm (khớp Puro).
|
||||
@ -2031,7 +2028,7 @@ public static class DbInitializer
|
||||
|
||||
// [S57] Mở quyền XEM (Read-only) cho TẤT CẢ role để mọi bộ phận review/góp ý
|
||||
// các module HRM + Văn phòng số + Danh mục (master). KHÔNG đụng Duyệt NCC
|
||||
// (Pe_*/PeWf_*/AwV2 — sắp go-live, giữ phân quyền cũ), Contracts/Budgets/System.
|
||||
// (Pe_*/PeWf_*/AwV2 — sắp go-live, giữ phân quyền cũ), Contracts/System.
|
||||
// [S58] Scope grant THU HẸP còn Master/Catalogs/Pe_* — xem note trong method.
|
||||
await SeedAllRolesReviewReadPermissionsAsync(db, roleManager, logger);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,419 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ReplaceBudgetModuleWithPeWorkItemBudgets : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
// [S61 Mig 50] Reorder TAY theo advise database-agent (scaffold mặc định
|
||||
// drop-trước-add; RIÊNG RenameColumn EF tự đoán BudgetManualAmount →
|
||||
// ExpectedRemainingAmount là SAI SEMANTICS — số nhập tay cũ phải migrate
|
||||
// sang BudgetPeriodAmount "NS kỳ này" row 3, không phải "dự kiến còn lại"
|
||||
// row 8): (1) AddColumn mới → (2) Sql UPDATE backfill (4 phiếu UAT thật
|
||||
// PE/2026/A/001+ GIỮ SỐ) → (3) DropIndex trước DropColumn (SQL 5074) →
|
||||
// (4) DropColumn cũ → (5) DropTable 5 bảng children-first (4 FK đều khai
|
||||
// trên child, CASCADE — không cần DropForeignKey riêng) → (6) CreateTable
|
||||
// mới → (7) Sql DELETE menu/permission (IN-list tường minh — LIKE 'Bg_%'
|
||||
// dính bẫy underscore-wildcard T-SQL + miss root 'Budgets').
|
||||
// Precedent data-migrate cùng shape: 20260513130144 (add→backfill→drop).
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// (1) 2 cột ngân sách mới trên PE
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "BudgetPeriodAmount",
|
||||
table: "PurchaseEvaluations",
|
||||
type: "decimal(18,2)",
|
||||
precision: 18,
|
||||
scale: 2,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "ExpectedRemainingAmount",
|
||||
table: "PurchaseEvaluations",
|
||||
type: "decimal(18,2)",
|
||||
precision: 18,
|
||||
scale: 2,
|
||||
nullable: true);
|
||||
|
||||
// (2) Backfill: số ngân sách nhập tay cũ = "NS kỳ này" của phiếu
|
||||
migrationBuilder.Sql(
|
||||
"UPDATE [PurchaseEvaluations] SET [BudgetPeriodAmount] = [BudgetManualAmount] WHERE [BudgetManualAmount] IS NOT NULL;");
|
||||
|
||||
// (3) DropIndex TRƯỚC DropColumn (SQL Server error 5074 nếu ngược)
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_PurchaseEvaluations_BudgetId",
|
||||
table: "PurchaseEvaluations");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Contracts_BudgetId",
|
||||
table: "Contracts");
|
||||
|
||||
// (4) Drop cột cũ (PE 3 cột; Contracts CHỈ BudgetId — BudgetManual*
|
||||
// của HĐ GIỮ NGUYÊN, anh Kiệt chỉ redesign ngân sách PE)
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BudgetId",
|
||||
table: "PurchaseEvaluations");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BudgetManualName",
|
||||
table: "PurchaseEvaluations");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BudgetManualAmount",
|
||||
table: "PurchaseEvaluations");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BudgetId",
|
||||
table: "Contracts");
|
||||
|
||||
// (5) Drop 5 bảng module Budget cũ — children-first
|
||||
migrationBuilder.DropTable(
|
||||
name: "BudgetApprovals");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "BudgetChangelogs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "BudgetDepartmentApprovals");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "BudgetDetails");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Budgets");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PeWorkItemBudgets",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ProjectId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
WorkItemId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ProEstimateAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true),
|
||||
ProNote = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
InitialAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true),
|
||||
AdjustmentAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
|
||||
DeletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
DeletedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PeWorkItemBudgets", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PeWorkItemBudgets_ProjectId_WorkItemId",
|
||||
table: "PeWorkItemBudgets",
|
||||
columns: new[] { "ProjectId", "WorkItemId" },
|
||||
unique: true,
|
||||
filter: "[IsDeleted] = 0");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PeWorkItemBudgets_WorkItemId",
|
||||
table: "PeWorkItemBudgets",
|
||||
column: "WorkItemId");
|
||||
|
||||
// (7) Dọn menu + permission module cũ. IN-list tường minh (advise
|
||||
// database-agent: Permissions key theo MenuKey string FK CASCADE →
|
||||
// MenuItems; MenuItems self-FK ParentKey RESTRICT → children TRƯỚC
|
||||
// root SAU). DELETE idempotent tự nhiên.
|
||||
migrationBuilder.Sql(
|
||||
"DELETE FROM [Permissions] WHERE [MenuKey] IN ('Budgets','Bg_List','Bg_Create','Bg_Pending');");
|
||||
migrationBuilder.Sql(
|
||||
"DELETE FROM [MenuItems] WHERE [Key] IN ('Bg_List','Bg_Create','Bg_Pending');");
|
||||
migrationBuilder.Sql(
|
||||
"DELETE FROM [MenuItems] WHERE [Key] = 'Budgets';");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
// [S61] Down tái tạo SCHEMA đầy đủ (5 bảng + FK + index + cột cũ) nhưng
|
||||
// KHÔNG khôi phục data Budget cũ + menu/permission rows đã DELETE —
|
||||
// (data-loss one-way chấp nhận, anh Kiệt chốt "xóa hẳn module").
|
||||
// BudgetPeriodAmount backfill ngược về BudgetManualAmount để số phiếu
|
||||
// không mất khi rollback.
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PeWorkItemBudgets");
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "BudgetManualAmount",
|
||||
table: "PurchaseEvaluations",
|
||||
type: "decimal(18,2)",
|
||||
precision: 18,
|
||||
scale: 2,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.Sql(
|
||||
"UPDATE [PurchaseEvaluations] SET [BudgetManualAmount] = [BudgetPeriodAmount] WHERE [BudgetPeriodAmount] IS NOT NULL;");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BudgetPeriodAmount",
|
||||
table: "PurchaseEvaluations");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ExpectedRemainingAmount",
|
||||
table: "PurchaseEvaluations");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "BudgetId",
|
||||
table: "PurchaseEvaluations",
|
||||
type: "uniqueidentifier",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "BudgetManualName",
|
||||
table: "PurchaseEvaluations",
|
||||
type: "nvarchar(200)",
|
||||
maxLength: 200,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "BudgetId",
|
||||
table: "Contracts",
|
||||
type: "uniqueidentifier",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Budgets",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
DeletedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DepartmentId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
Description = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
|
||||
DrafterUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
|
||||
MaNganSach = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
NamNganSach = table.Column<int>(type: "int", nullable: false),
|
||||
Phase = table.Column<int>(type: "int", nullable: false),
|
||||
ProjectId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
RejectedFromPhase = table.Column<int>(type: "int", nullable: true),
|
||||
SlaDeadline = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
SlaWarningSent = table.Column<bool>(type: "bit", nullable: false),
|
||||
TenNganSach = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||
TongNganSach = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Budgets", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "BudgetApprovals",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
BudgetId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ApprovedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
ApproverUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
Comment = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
Decision = table.Column<int>(type: "int", nullable: false),
|
||||
FromPhase = table.Column<int>(type: "int", nullable: false),
|
||||
ToPhase = table.Column<int>(type: "int", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_BudgetApprovals", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_BudgetApprovals_Budgets_BudgetId",
|
||||
column: x => x.BudgetId,
|
||||
principalTable: "Budgets",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "BudgetChangelogs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
BudgetId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Action = table.Column<int>(type: "int", nullable: false),
|
||||
ContextNote = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
EntityId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
EntityType = table.Column<int>(type: "int", nullable: false),
|
||||
FieldChangesJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
PhaseAtChange = table.Column<int>(type: "int", nullable: true),
|
||||
Summary = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
UserName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_BudgetChangelogs", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_BudgetChangelogs_Budgets_BudgetId",
|
||||
column: x => x.BudgetId,
|
||||
principalTable: "Budgets",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "BudgetDepartmentApprovals",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
BudgetId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ApprovedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
ApproverRoleSnapshot = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
ApproverUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Comment = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
DeletedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DepartmentId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
IsBypassed = table.Column<bool>(type: "bit", nullable: false),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
|
||||
PhaseAtApproval = table.Column<int>(type: "int", nullable: false),
|
||||
Stage = table.Column<int>(type: "int", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_BudgetDepartmentApprovals", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_BudgetDepartmentApprovals_Budgets_BudgetId",
|
||||
column: x => x.BudgetId,
|
||||
principalTable: "Budgets",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "BudgetDetails",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
BudgetId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DonGia = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
DonViTinh = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||
GhiChu = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
GroupCode = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
GroupName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||
ItemCode = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
KhoiLuong = table.Column<decimal>(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false),
|
||||
NoiDung = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||
Order = table.Column<int>(type: "int", nullable: false),
|
||||
ThanhTien = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_BudgetDetails", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_BudgetDetails_Budgets_BudgetId",
|
||||
column: x => x.BudgetId,
|
||||
principalTable: "Budgets",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PurchaseEvaluations_BudgetId",
|
||||
table: "PurchaseEvaluations",
|
||||
column: "BudgetId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Contracts_BudgetId",
|
||||
table: "Contracts",
|
||||
column: "BudgetId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BudgetApprovals_BudgetId_ApprovedAt",
|
||||
table: "BudgetApprovals",
|
||||
columns: new[] { "BudgetId", "ApprovedAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BudgetChangelogs_BudgetId_CreatedAt",
|
||||
table: "BudgetChangelogs",
|
||||
columns: new[] { "BudgetId", "CreatedAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BudgetChangelogs_BudgetId_EntityType",
|
||||
table: "BudgetChangelogs",
|
||||
columns: new[] { "BudgetId", "EntityType" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BudgetDepartmentApprovals_ApproverUserId",
|
||||
table: "BudgetDepartmentApprovals",
|
||||
column: "ApproverUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BudgetDepartmentApprovals_BudgetId",
|
||||
table: "BudgetDepartmentApprovals",
|
||||
column: "BudgetId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BudgetDepartmentApprovals_DepartmentId",
|
||||
table: "BudgetDepartmentApprovals",
|
||||
column: "DepartmentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_BudgetDeptApprovals_Budget_Phase_Dept_Stage",
|
||||
table: "BudgetDepartmentApprovals",
|
||||
columns: new[] { "BudgetId", "PhaseAtApproval", "DepartmentId", "Stage" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BudgetDetails_BudgetId_Order",
|
||||
table: "BudgetDetails",
|
||||
columns: new[] { "BudgetId", "Order" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Budgets_MaNganSach",
|
||||
table: "Budgets",
|
||||
column: "MaNganSach",
|
||||
unique: true,
|
||||
filter: "[MaNganSach] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Budgets_NamNganSach",
|
||||
table: "Budgets",
|
||||
column: "NamNganSach");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Budgets_Phase_IsDeleted",
|
||||
table: "Budgets",
|
||||
columns: new[] { "Phase", "IsDeleted" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Budgets_ProjectId",
|
||||
table: "Budgets",
|
||||
column: "ProjectId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Budgets_SlaDeadline",
|
||||
table: "Budgets",
|
||||
column: "SlaDeadline");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -298,339 +298,6 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.ToTable("ApprovalWorkflowSteps", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Budgets.Budget", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("DepartmentId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.Property<Guid?>("DrafterUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("MaNganSach")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("NamNganSach")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Phase")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int?>("RejectedFromPhase")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("SlaDeadline")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("SlaWarningSent")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("TenNganSach")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<decimal>("TongNganSach")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MaNganSach")
|
||||
.IsUnique()
|
||||
.HasFilter("[MaNganSach] IS NOT NULL");
|
||||
|
||||
b.HasIndex("NamNganSach");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.HasIndex("SlaDeadline");
|
||||
|
||||
b.HasIndex("Phase", "IsDeleted");
|
||||
|
||||
b.ToTable("Budgets", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetApproval", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("ApprovedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("ApproverUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("BudgetId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Decision")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("FromPhase")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ToPhase")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BudgetId", "ApprovedAt");
|
||||
|
||||
b.ToTable("BudgetApprovals", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetChangelog", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Action")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("BudgetId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("ContextNote")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("EntityId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("EntityType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("FieldChangesJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int?>("PhaseAtChange")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BudgetId", "CreatedAt");
|
||||
|
||||
b.HasIndex("BudgetId", "EntityType");
|
||||
|
||||
b.ToTable("BudgetChangelogs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetDepartmentApproval", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("ApprovedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("ApproverRoleSnapshot")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<Guid>("ApproverUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("BudgetId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("DepartmentId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsBypassed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int>("PhaseAtApproval")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Stage")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApproverUserId");
|
||||
|
||||
b.HasIndex("BudgetId");
|
||||
|
||||
b.HasIndex("DepartmentId");
|
||||
|
||||
b.HasIndex("BudgetId", "PhaseAtApproval", "DepartmentId", "Stage")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_BudgetDeptApprovals_Budget_Phase_Dept_Stage");
|
||||
|
||||
b.ToTable("BudgetDepartmentApprovals", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetDetail", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("BudgetId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<decimal>("DonGia")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<string>("DonViTinh")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("GhiChu")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<string>("GroupCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("GroupName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("ItemCode")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<decimal>("KhoiLuong")
|
||||
.HasPrecision(18, 4)
|
||||
.HasColumnType("decimal(18,4)");
|
||||
|
||||
b.Property<string>("NoiDung")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("ThanhTien")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BudgetId", "Order");
|
||||
|
||||
b.ToTable("BudgetDetails", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Contracts.Contract", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -640,9 +307,6 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.Property<Guid?>("ApprovalWorkflowId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("BudgetId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<decimal?>("BudgetManualAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
@ -740,8 +404,6 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("ApprovalWorkflowId");
|
||||
|
||||
b.HasIndex("BudgetId");
|
||||
|
||||
b.HasIndex("MaHopDong")
|
||||
.IsUnique()
|
||||
.HasFilter("[MaHopDong] IS NOT NULL");
|
||||
@ -4842,6 +4504,66 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.ToTable("WorkflowAppCodeSequences", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PeWorkItemBudget", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<decimal?>("AdjustmentAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<decimal?>("InitialAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<decimal?>("ProEstimateAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<string>("ProNote")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("WorkItemId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("WorkItemId");
|
||||
|
||||
b.HasIndex("ProjectId", "WorkItemId")
|
||||
.IsUnique()
|
||||
.HasFilter("[IsDeleted] = 0");
|
||||
|
||||
b.ToTable("PeWorkItemBudgets", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -4851,17 +4573,10 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.Property<Guid?>("ApprovalWorkflowId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("BudgetId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<decimal?>("BudgetManualAmount")
|
||||
b.Property<decimal?>("BudgetPeriodAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<string>("BudgetManualName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<Guid?>("ContractId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
@ -4893,6 +4608,10 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.Property<Guid?>("DrafterUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<decimal?>("ExpectedRemainingAmount")
|
||||
.HasPrecision(18, 2)
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
@ -4952,8 +4671,6 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("ApprovalWorkflowId");
|
||||
|
||||
b.HasIndex("BudgetId");
|
||||
|
||||
b.HasIndex("ContractId");
|
||||
|
||||
b.HasIndex("MaPhieu")
|
||||
@ -5756,50 +5473,6 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.Navigation("ApprovalWorkflow");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetApproval", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Budgets.Budget", "Budget")
|
||||
.WithMany("Approvals")
|
||||
.HasForeignKey("BudgetId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Budget");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetChangelog", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Budgets.Budget", "Budget")
|
||||
.WithMany("Changelogs")
|
||||
.HasForeignKey("BudgetId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Budget");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetDepartmentApproval", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Budgets.Budget", "Budget")
|
||||
.WithMany("DepartmentApprovals")
|
||||
.HasForeignKey("BudgetId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Budget");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Budgets.BudgetDetail", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.Budgets.Budget", "Budget")
|
||||
.WithMany("Details")
|
||||
.HasForeignKey("BudgetId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Budget");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Contracts.Contract", b =>
|
||||
{
|
||||
b.HasOne("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflow", null)
|
||||
@ -6392,17 +6065,6 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.Navigation("Levels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Budgets.Budget", b =>
|
||||
{
|
||||
b.Navigation("Approvals");
|
||||
|
||||
b.Navigation("Changelogs");
|
||||
|
||||
b.Navigation("DepartmentApprovals");
|
||||
|
||||
b.Navigation("Details");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Contracts.Contract", b =>
|
||||
{
|
||||
b.Navigation("Approvals");
|
||||
|
||||
@ -5,7 +5,6 @@ using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Application.Notifications;
|
||||
using SolutionErp.Application.PurchaseEvaluations.Services;
|
||||
using SolutionErp.Domain.ApprovalWorkflowsV2;
|
||||
using SolutionErp.Domain.Common;
|
||||
using SolutionErp.Domain.Contracts;
|
||||
using SolutionErp.Domain.Identity;
|
||||
using SolutionErp.Domain.Notifications;
|
||||
@ -193,10 +192,12 @@ public class PurchaseEvaluationWorkflowService(
|
||||
missing.Add("Đơn vị được chọn chưa có giá chào thầu");
|
||||
}
|
||||
}
|
||||
if (evaluation.BudgetId is null
|
||||
&& (evaluation.BudgetManualAmount is null || evaluation.BudgetManualAmount <= 0))
|
||||
// [S61 Mig 50] Schema ngân sách mới — điều kiện (3) = "Ngân sách - kỳ này"
|
||||
// (BudgetPeriodAmount, drafter nhập). FE PeDetailTabs missingForApproval
|
||||
// mirror CÙNG predicate (đổi đồng bộ 2 tầng).
|
||||
if (evaluation.BudgetPeriodAmount is null || evaluation.BudgetPeriodAmount <= 0)
|
||||
{
|
||||
missing.Add("chưa nhập Ngân sách");
|
||||
missing.Add("chưa nhập Ngân sách kỳ này");
|
||||
}
|
||||
var hasComparisonFile = await db.PurchaseEvaluationAttachments.AsNoTracking()
|
||||
.AnyAsync(a => a.PurchaseEvaluationId == evaluation.Id
|
||||
|
||||
Reference in New Issue
Block a user