[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
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:
29
src/Backend/SolutionErp.Domain/Budgets/Budget.cs
Normal file
29
src/Backend/SolutionErp.Domain/Budgets/Budget.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using SolutionErp.Domain.Common;
|
||||
|
||||
namespace SolutionErp.Domain.Budgets;
|
||||
|
||||
// Aggregate root quản lý ngân sách. Gắn với Project (required), reference
|
||||
// từ PurchaseEvaluation + Contract (cả 2 nullable FK Budget.Id).
|
||||
//
|
||||
// Workflow đơn giản 3-step: Drafter → CCM → CEO. Pattern hardcoded trong
|
||||
// BudgetPolicy (chưa versioned, tương lai có thể thêm BudgetWorkflowDefinition
|
||||
// nếu cần admin config).
|
||||
public class Budget : AuditableEntity
|
||||
{
|
||||
public string? MaNganSach { get; set; } // Auto-gen NS-YYYYMM-XXXX
|
||||
public string TenNganSach { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public int NamNganSach { get; set; } // Năm áp dụng (vd 2026)
|
||||
public Guid ProjectId { get; set; } // FK Projects (required)
|
||||
public Guid? DepartmentId { get; set; }
|
||||
public Guid? DrafterUserId { get; set; }
|
||||
|
||||
public BudgetPhase Phase { get; set; } = BudgetPhase.DangSoanThao;
|
||||
public decimal TongNganSach { get; set; } // Tổng = sum BudgetDetails.ThanhTien (computed)
|
||||
public DateTime? SlaDeadline { get; set; }
|
||||
public bool SlaWarningSent { get; set; }
|
||||
|
||||
public List<BudgetDetail> Details { get; set; } = new();
|
||||
public List<BudgetApproval> Approvals { get; set; } = new();
|
||||
public List<BudgetChangelog> Changelogs { get; set; } = new();
|
||||
}
|
||||
17
src/Backend/SolutionErp.Domain/Budgets/BudgetApproval.cs
Normal file
17
src/Backend/SolutionErp.Domain/Budgets/BudgetApproval.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using SolutionErp.Domain.Common;
|
||||
using SolutionErp.Domain.Contracts; // reuse ApprovalDecision
|
||||
|
||||
namespace SolutionErp.Domain.Budgets;
|
||||
|
||||
public class BudgetApproval : BaseEntity
|
||||
{
|
||||
public Guid BudgetId { get; set; }
|
||||
public BudgetPhase FromPhase { get; set; }
|
||||
public BudgetPhase 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 Budget? Budget { get; set; }
|
||||
}
|
||||
28
src/Backend/SolutionErp.Domain/Budgets/BudgetChangelog.cs
Normal file
28
src/Backend/SolutionErp.Domain/Budgets/BudgetChangelog.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using SolutionErp.Domain.Common;
|
||||
using SolutionErp.Domain.Contracts; // reuse ChangelogAction
|
||||
|
||||
namespace SolutionErp.Domain.Budgets;
|
||||
|
||||
// Audit log unified cho mọi thay đổi trên ngân sách.
|
||||
public class BudgetChangelog : BaseEntity
|
||||
{
|
||||
public Guid BudgetId { get; set; }
|
||||
public Budget? Budget { get; set; }
|
||||
|
||||
public BudgetEntityType EntityType { get; set; }
|
||||
public Guid? EntityId { get; set; }
|
||||
public ChangelogAction Action { get; set; }
|
||||
public BudgetPhase? 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 BudgetEntityType
|
||||
{
|
||||
Header = 1,
|
||||
Detail = 2,
|
||||
Workflow = 3,
|
||||
}
|
||||
22
src/Backend/SolutionErp.Domain/Budgets/BudgetDetail.cs
Normal file
22
src/Backend/SolutionErp.Domain/Budgets/BudgetDetail.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using SolutionErp.Domain.Common;
|
||||
|
||||
namespace SolutionErp.Domain.Budgets;
|
||||
|
||||
// Chi tiết ngân sách — pattern flat row giống PurchaseEvaluationDetail.
|
||||
// Group A.I/A.II/... cho hạng mục cha, NoiDung cho item con.
|
||||
public class BudgetDetail : BaseEntity
|
||||
{
|
||||
public Guid BudgetId { get; set; }
|
||||
public string GroupCode { get; set; } = string.Empty; // "A.I", "A.II", ...
|
||||
public string GroupName { get; set; } = string.Empty; // "Bê tông", "Phụ gia", ...
|
||||
public string? ItemCode { get; set; }
|
||||
public string NoiDung { get; set; } = string.Empty;
|
||||
public string? DonViTinh { get; set; }
|
||||
public decimal KhoiLuong { get; set; }
|
||||
public decimal DonGia { get; set; }
|
||||
public decimal ThanhTien { get; set; } // = KhoiLuong × DonGia (hoặc nhập tay)
|
||||
public int Order { get; set; }
|
||||
public string? GhiChu { get; set; }
|
||||
|
||||
public Budget? Budget { get; set; }
|
||||
}
|
||||
14
src/Backend/SolutionErp.Domain/Budgets/BudgetPhase.cs
Normal file
14
src/Backend/SolutionErp.Domain/Budgets/BudgetPhase.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace SolutionErp.Domain.Budgets;
|
||||
|
||||
// State machine ngân sách — đơn giản 3 bước duyệt + 2 terminal.
|
||||
// DangSoanThao → ChoCCM → ChoCEO → DaDuyet
|
||||
// Bất kỳ phase duyệt → DangSoanThao (reject)
|
||||
// DangSoanThao → TuChoi
|
||||
public enum BudgetPhase
|
||||
{
|
||||
DangSoanThao = 1,
|
||||
ChoCCM = 2,
|
||||
ChoCEO = 3,
|
||||
DaDuyet = 4,
|
||||
TuChoi = 99,
|
||||
}
|
||||
63
src/Backend/SolutionErp.Domain/Budgets/BudgetPolicy.cs
Normal file
63
src/Backend/SolutionErp.Domain/Budgets/BudgetPolicy.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using SolutionErp.Domain.Identity;
|
||||
|
||||
namespace SolutionErp.Domain.Budgets;
|
||||
|
||||
// Policy hardcoded đơn giản — chưa versioned (theo user "tạm thời simple
|
||||
// default"). Tương lai nếu admin cần config qua UI: thêm BudgetWorkflow
|
||||
// Definition tables tương tự PE workflow.
|
||||
public sealed record BudgetPolicy(
|
||||
string Name,
|
||||
string Description,
|
||||
IReadOnlyDictionary<(BudgetPhase From, BudgetPhase To), string[]> Transitions,
|
||||
IReadOnlyDictionary<BudgetPhase, TimeSpan?> PhaseSla,
|
||||
IReadOnlyList<BudgetPhase> ActivePhases)
|
||||
{
|
||||
public bool HasPhase(BudgetPhase phase) => ActivePhases.Contains(phase);
|
||||
|
||||
public bool IsTransitionAllowed(
|
||||
BudgetPhase from, BudgetPhase to,
|
||||
IReadOnlyList<string> actorRoles)
|
||||
{
|
||||
if (!Transitions.TryGetValue((from, to), out var roles)) return false;
|
||||
return actorRoles.Any(r => roles.Contains(r));
|
||||
}
|
||||
|
||||
public IReadOnlyList<BudgetPhase> NextPhasesFrom(BudgetPhase from) =>
|
||||
Transitions.Keys.Where(k => k.From == from).Select(k => k.To).Distinct().ToList();
|
||||
}
|
||||
|
||||
public static class BudgetPolicies
|
||||
{
|
||||
private static readonly Dictionary<BudgetPhase, TimeSpan?> DefaultSla = new()
|
||||
{
|
||||
[BudgetPhase.DangSoanThao] = TimeSpan.FromDays(5),
|
||||
[BudgetPhase.ChoCCM] = TimeSpan.FromDays(3),
|
||||
[BudgetPhase.ChoCEO] = TimeSpan.FromDays(2),
|
||||
[BudgetPhase.DaDuyet] = null,
|
||||
[BudgetPhase.TuChoi] = null,
|
||||
};
|
||||
|
||||
public static readonly BudgetPolicy Default = new(
|
||||
Name: "Default",
|
||||
Description: "Quy trình ngân sách 3-step (Drafter → CCM → CEO).",
|
||||
Transitions: new Dictionary<(BudgetPhase, BudgetPhase), string[]>
|
||||
{
|
||||
[(BudgetPhase.DangSoanThao, BudgetPhase.ChoCCM)] = [AppRoles.Drafter, AppRoles.DeptManager],
|
||||
[(BudgetPhase.DangSoanThao, BudgetPhase.TuChoi)] = [AppRoles.Drafter, AppRoles.DeptManager],
|
||||
|
||||
[(BudgetPhase.ChoCCM, BudgetPhase.ChoCEO)] = [AppRoles.CostControl],
|
||||
[(BudgetPhase.ChoCCM, BudgetPhase.DangSoanThao)] = [AppRoles.CostControl],
|
||||
|
||||
[(BudgetPhase.ChoCEO, BudgetPhase.DaDuyet)] = [AppRoles.Director, AppRoles.AuthorizedSigner],
|
||||
[(BudgetPhase.ChoCEO, BudgetPhase.DangSoanThao)] = [AppRoles.Director, AppRoles.AuthorizedSigner],
|
||||
},
|
||||
PhaseSla: DefaultSla,
|
||||
ActivePhases:
|
||||
[
|
||||
BudgetPhase.DangSoanThao,
|
||||
BudgetPhase.ChoCCM,
|
||||
BudgetPhase.ChoCEO,
|
||||
BudgetPhase.DaDuyet,
|
||||
BudgetPhase.TuChoi,
|
||||
]);
|
||||
}
|
||||
@ -23,6 +23,7 @@ public class Contract : AuditableEntity
|
||||
public DateTime? SlaDeadline { get; set; } // Hết hạn phase hiện tại
|
||||
public string? DraftData { get; set; } // JSON field values (render template)
|
||||
public bool SlaWarningSent { get; set; } // Flag để không gửi warning 2 lần
|
||||
public Guid? BudgetId { get; set; } // Reference Budget (Phase 7) — đối chiếu chi phí HĐ vs ngân sách
|
||||
|
||||
public List<ContractApproval> Approvals { get; set; } = new();
|
||||
public List<ContractComment> Comments { get; set; } = new();
|
||||
|
||||
@ -51,6 +51,15 @@ public static class MenuKeys
|
||||
public const string PurchaseEvaluations = "PurchaseEvaluations"; // root group
|
||||
public const string PeWorkflows = "PeWorkflows"; // workflow admin root
|
||||
|
||||
// ============================================================
|
||||
// Module Ngân sách (Phase 7) — 4 bảng quản lý ngân sách dự án/gói thầu.
|
||||
// 1 root + 3 leaf action (Danh sách / Thao tác / Duyệt).
|
||||
// ============================================================
|
||||
public const string Budgets = "Budgets";
|
||||
public const string BudgetList = "Bg_List";
|
||||
public const string BudgetCreate = "Bg_Create";
|
||||
public const string BudgetPending = "Bg_Pending";
|
||||
|
||||
public static readonly string[] PurchaseEvaluationTypeCodes =
|
||||
["DuyetNcc", "DuyetNccPhuongAn"];
|
||||
|
||||
@ -70,6 +79,7 @@ public static class MenuKeys
|
||||
Catalogs, CatalogUnits, CatalogMaterials, CatalogServices, CatalogWorkItems,
|
||||
Contracts, Forms, Reports,
|
||||
PurchaseEvaluations,
|
||||
Budgets, BudgetList, BudgetCreate, BudgetPending,
|
||||
System, Users, Roles, Permissions, Workflows, PeWorkflows,
|
||||
];
|
||||
|
||||
|
||||
@ -26,6 +26,7 @@ public class PurchaseEvaluation : AuditableEntity
|
||||
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 Guid? BudgetId { get; set; } // FK Budget (Phase 7) — đối chiếu báo giá vs ngân sách gói thầu
|
||||
|
||||
public List<PurchaseEvaluationSupplier> Suppliers { get; set; } = new();
|
||||
public List<PurchaseEvaluationDetail> Details { get; set; } = new();
|
||||
|
||||
Reference in New Issue
Block a user