[CLAUDE] Domain+App+Api: Module Ngan sach (Budget) - 4 bang + workflow simple
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m11s

User request: 'Them cho tao 4 bang luu ve ngan sach: Header / Chi tiet
/ Quy trinh duyet / Lich su thay doi'.

Domain (5 file + 1 enum):
 - Budget (header) — Aggregate root, AuditableEntity. Field: MaNganSach,
   TenNganSach, Description, NamNganSach, ProjectId FK, DepartmentId?,
   DrafterUserId, Phase (BudgetPhase 5-state), TongNganSach (sum auto
   tu Details), SlaDeadline, SlaWarningSent.
 - BudgetDetail — flat row pattern (GroupCode/GroupName + Item +
   KhoiLuong/DonGia/ThanhTien). 18,4 precision KhoiLuong, 18,2 money.
 - BudgetApproval — workflow history (FromPhase/ToPhase/Decision/Comment)
 - BudgetChangelog — audit log unified (EntityType: Header/Detail/Workflow)
 - BudgetPhase enum 5 state: DangSoanThao(1) → ChoCCM(2) → ChoCEO(3) →
   DaDuyet(4) | TuChoi(99)
 - BudgetPolicy hardcoded (no versioned WF, simple default per user
   confirm 'tam thoi don gian'): Drafter/DeptManager → CCM → CEO/
   AuthorizedSigner. Reject path back to DangSoanThao.

Migration 14 AddBudgets:
 - 4 bang moi: Budgets + BudgetDetails + BudgetApprovals + BudgetChangelogs
 - Index: Phase+IsDeleted, ProjectId, NamNganSach, SlaDeadline,
   MaNganSach unique filtered. Cascade delete child.
 - +2 cot FK ngoai bang (per user 'lien ket ca 3'):
   * Contracts.BudgetId Guid? + index
   * PurchaseEvaluations.BudgetId Guid? + index
   Cho phep doi chieu chi phi HD/PE vs ngan sach goi thau.

Application CQRS (BudgetFeatures.cs ~340 line):
 - CreateBudget + UpdateBudgetDraft + TransitionBudget + ListBudgets
   (filter Phase/Project/Year + search + paging) + GetBudget bundle
   (Header + Details + Approvals + Workflow summary)
 - DeleteBudget (only DangSoanThao/TuChoi)
 - AddBudgetDetail + UpdateBudgetDetail + DeleteBudgetDetail (auto
   recompute TongNganSach = sum Details.ThanhTien)
 - ListBudgetChangelogs

Api: BudgetsController 11 endpoint REST /api/budgets:
 - GET /  /{id}  /{id}/changelogs
 - POST /  /{id}/transitions  /{id}/details
 - PUT /{id}  /{id}/details/{detailId}
 - DELETE /{id}  /{id}/details/{detailId}

DbContext + IApplicationDbContext: 4 DbSet new (Budgets/Details/
Approvals/Changelogs).

MenuKeys + DbInitializer: 4 menu key (Budgets root + Bg_List/Create/
Pending leaves) seed dau order=27 'Ngan sach' icon Wallet. Auto-grant
admin permission via SeedAdminPermissionsAsync (MenuKeys.All).

MaNganSach format don gian 'NS-YYYYMM-NNNN' Random.Shared (chua atomic
sequence - user said 'tam thoi chua co').

Workflow chua versioned, hardcode BudgetPolicy.Default. Tuong lai admin
config qua UI: them BudgetWorkflowDefinition tables tuong tu PE.
This commit is contained in:
pqhuy1987
2026-04-28 11:37:45 +07:00
parent 8097892d20
commit a05c57b081
21 changed files with 4632 additions and 0 deletions

View File

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Domain.Budgets;
using SolutionErp.Domain.Contracts;
using SolutionErp.Domain.Contracts.Details;
using SolutionErp.Domain.Forms;
@ -59,6 +60,12 @@ public class ApplicationDbContext
public DbSet<PurchaseEvaluationWorkflowStepApprover> PurchaseEvaluationWorkflowStepApprovers => Set<PurchaseEvaluationWorkflowStepApprover>();
public DbSet<PurchaseEvaluationCodeSequence> PurchaseEvaluationCodeSequences => Set<PurchaseEvaluationCodeSequence>();
// 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>();
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);

View File

@ -0,0 +1,89 @@
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.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 });
}
}

View File

@ -24,6 +24,7 @@ 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.HasMany(x => x.Approvals).WithOne(a => a.Contract).HasForeignKey(a => a.ContractId).OnDelete(DeleteBehavior.Cascade);
b.HasMany(x => x.Comments).WithOne(c => c.Contract).HasForeignKey(c => c.ContractId).OnDelete(DeleteBehavior.Cascade);

View File

@ -25,6 +25,7 @@ public class PurchaseEvaluationConfiguration : IEntityTypeConfiguration<Purchase
b.HasIndex(x => x.SlaDeadline);
b.HasIndex(x => x.WorkflowDefinitionId);
b.HasIndex(x => x.ContractId);
b.HasIndex(x => x.BudgetId);
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);

View File

@ -1308,6 +1308,11 @@ public static class DbInitializer
// 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"),
// 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"),
};
// Per-type sub-menu under Contracts: 1 group + 3 leaves each

View File

@ -0,0 +1,236 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SolutionErp.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddBudgets : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "BudgetId",
table: "PurchaseEvaluations",
type: "uniqueidentifier",
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),
MaNganSach = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
TenNganSach = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
Description = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
NamNganSach = table.Column<int>(type: "int", nullable: false),
ProjectId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
DepartmentId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DrafterUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Phase = table.Column<int>(type: "int", nullable: false),
TongNganSach = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
SlaDeadline = table.Column<DateTime>(type: "datetime2", nullable: true),
SlaWarningSent = table.Column<bool>(type: "bit", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
DeletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
DeletedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_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),
FromPhase = table.Column<int>(type: "int", nullable: false),
ToPhase = table.Column<int>(type: "int", nullable: false),
ApproverUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Decision = table.Column<int>(type: "int", nullable: false),
Comment = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
ApprovedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_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),
EntityType = table.Column<int>(type: "int", nullable: false),
EntityId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Action = table.Column<int>(type: "int", nullable: false),
PhaseAtChange = table.Column<int>(type: "int", nullable: true),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
UserName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
Summary = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
FieldChangesJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
ContextNote = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_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: "BudgetDetails",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
BudgetId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
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),
NoiDung = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
DonViTinh = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
KhoiLuong = table.Column<decimal>(type: "decimal(18,4)", precision: 18, scale: 4, nullable: false),
DonGia = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
ThanhTien = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
Order = table.Column<int>(type: "int", nullable: false),
GhiChu = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
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_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");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "BudgetApprovals");
migrationBuilder.DropTable(
name: "BudgetChangelogs");
migrationBuilder.DropTable(
name: "BudgetDetails");
migrationBuilder.DropTable(
name: "Budgets");
migrationBuilder.DropIndex(
name: "IX_PurchaseEvaluations_BudgetId",
table: "PurchaseEvaluations");
migrationBuilder.DropIndex(
name: "IX_Contracts_BudgetId",
table: "Contracts");
migrationBuilder.DropColumn(
name: "BudgetId",
table: "PurchaseEvaluations");
migrationBuilder.DropColumn(
name: "BudgetId",
table: "Contracts");
}
}
}

View File

@ -125,12 +125,274 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.ToTable("UserTokens", (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<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.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")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("BudgetId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("BypassProcurementAndCCM")
.HasColumnType("bit");
@ -206,6 +468,8 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.HasKey("Id");
b.HasIndex("BudgetId");
b.HasIndex("MaHopDong")
.IsUnique()
.HasFilter("[MaHopDong] IS NOT NULL");
@ -1910,6 +2174,9 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("BudgetId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("ContractId")
.HasColumnType("uniqueidentifier");
@ -1983,6 +2250,8 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.HasKey("Id");
b.HasIndex("BudgetId");
b.HasIndex("ContractId");
b.HasIndex("MaPhieu")
@ -2558,6 +2827,39 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
.IsRequired();
});
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.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.ContractApproval", b =>
{
b.HasOne("SolutionErp.Domain.Contracts.Contract", "Contract")
@ -2838,6 +3140,15 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Navigation("Step");
});
modelBuilder.Entity("SolutionErp.Domain.Budgets.Budget", b =>
{
b.Navigation("Approvals");
b.Navigation("Changelogs");
b.Navigation("Details");
});
modelBuilder.Entity("SolutionErp.Domain.Contracts.Contract", b =>
{
b.Navigation("Approvals");