From 0f7901c19f11d19afc92f3f20665c8f819c657ae Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Thu, 7 May 2026 12:34:00 +0700 Subject: [PATCH] [CLAUDE] App: PE + Contract Create/Update commands + DTO add manual budget fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chunk 2/5 — wire 2 field mới (BudgetManualName + BudgetManualAmount) qua tất cả CQRS commands + handlers + DTOs cho cả PE và HĐ. Mirror logic per Q3 user. Files sửa: ~ Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs - CreatePurchaseEvaluationCommand record +2 param - Validator: MaximumLength(200) + GreaterThanOrEqualTo(0) - Handler: wire entity - UpdatePurchaseEvaluationDraftCommand record +2 param + handler wire - GetPurchaseEvaluationQuery → DTO mapping +2 field ~ Application/PurchaseEvaluations/Dtos/PurchaseEvaluationDtos.cs - PurchaseEvaluationDetailBundleDto +2 field (BudgetManualName/Amount) ~ Application/PurchaseEvaluations/CreateContractFromEvaluationFeatures.cs - Carry forward pe.BudgetManualName/Amount → contract khi gen HĐ từ phiếu ~ Application/Contracts/ContractFeatures.cs - CreateContractCommand record +2 param + Validator + Handler - UpdateContractDraftCommand record +2 param + Handler (diff log thêm 2 field) - ContractDetailDto mapping +2 field ~ Application/Contracts/Dtos/ContractDtos.cs - ContractDetailDto +2 field Validation Q2 chốt: cả 2 cùng null OK. Manual amount có thể null hoặc >= 0. KHÔNG XOR với BudgetId (BE prefer link BudgetId nếu set, manual fallback only). Controllers KHÔNG đụng (FromBody bind JSON → record record optional fields gen auto null cho legacy callers — backward compat). Verify: dotnet build pass · dotnet test SolutionErp.slnx 83 pass. Next: Chunk 3 FE-Admin toggle + 2 fields PeHeaderForm + ContractHeaderForm. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Contracts/ContractFeatures.cs | 19 +++++++++++++++++-- .../Contracts/Dtos/ContractDtos.cs | 2 ++ .../CreateContractFromEvaluationFeatures.cs | 2 ++ .../Dtos/PurchaseEvaluationDtos.cs | 2 ++ .../PurchaseEvaluationFeatures.cs | 15 +++++++++++++-- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/Backend/SolutionErp.Application/Contracts/ContractFeatures.cs b/src/Backend/SolutionErp.Application/Contracts/ContractFeatures.cs index 5d8274a..f7273e0 100644 --- a/src/Backend/SolutionErp.Application/Contracts/ContractFeatures.cs +++ b/src/Backend/SolutionErp.Application/Contracts/ContractFeatures.cs @@ -25,7 +25,9 @@ public record CreateContractCommand( string? NoiDung, bool BypassProcurementAndCCM, string? DraftData, - Guid? BudgetId) : IRequest; + Guid? BudgetId, + string? BudgetManualName, + decimal? BudgetManualAmount) : IRequest; public class CreateContractCommandValidator : AbstractValidator { @@ -37,6 +39,8 @@ public class CreateContractCommandValidator : AbstractValidator x.GiaTri).GreaterThanOrEqualTo(0); RuleFor(x => x.TenHopDong).MaximumLength(500); RuleFor(x => x.NoiDung).MaximumLength(2000); + RuleFor(x => x.BudgetManualName).MaximumLength(200); + RuleFor(x => x.BudgetManualAmount).GreaterThanOrEqualTo(0).When(x => x.BudgetManualAmount.HasValue); } } @@ -87,6 +91,8 @@ public class CreateContractCommandHandler( BypassProcurementAndCCM = request.BypassProcurementAndCCM, DraftData = request.DraftData, BudgetId = request.BudgetId, + BudgetManualName = request.BudgetManualName, + BudgetManualAmount = request.BudgetManualAmount, WorkflowDefinitionId = activeWfId, SlaDeadline = DateTime.UtcNow.Add(workflow.GetPhaseSla(ContractPhase.DangSoanThao) ?? TimeSpan.FromDays(7)), }; @@ -121,7 +127,9 @@ public record UpdateContractDraftCommand( string? NoiDung, Guid? TemplateId, string? DraftData, - Guid? BudgetId) : IRequest; + Guid? BudgetId, + string? BudgetManualName, + decimal? BudgetManualAmount) : IRequest; public class UpdateContractDraftCommandHandler( IApplicationDbContext db, @@ -159,6 +167,10 @@ public class UpdateContractDraftCommandHandler( changes.Add(new { Field = "TemplateId", Old = entity.TemplateId, New = request.TemplateId }); if (entity.BudgetId != request.BudgetId) changes.Add(new { Field = "BudgetId", Old = entity.BudgetId, New = request.BudgetId }); + if (entity.BudgetManualName != request.BudgetManualName) + changes.Add(new { Field = "BudgetManualName", Old = entity.BudgetManualName, New = request.BudgetManualName }); + if (entity.BudgetManualAmount != request.BudgetManualAmount) + changes.Add(new { Field = "BudgetManualAmount", Old = entity.BudgetManualAmount, New = request.BudgetManualAmount }); entity.GiaTri = request.GiaTri; entity.TenHopDong = request.TenHopDong; @@ -166,6 +178,8 @@ public class UpdateContractDraftCommandHandler( entity.TemplateId = request.TemplateId; entity.DraftData = request.DraftData; entity.BudgetId = request.BudgetId; + entity.BudgetManualName = request.BudgetManualName; + entity.BudgetManualAmount = request.BudgetManualAmount; if (changes.Count > 0) { @@ -471,6 +485,7 @@ public class GetContractQueryHandler( c.TemplateId, c.GiaTri, c.BypassProcurementAndCCM, c.SlaDeadline, c.DraftData, c.CreatedAt, c.UpdatedAt, c.BudgetId, budgetSummary, + c.BudgetManualName, c.BudgetManualAmount, c.Approvals .OrderBy(a => a.ApprovedAt) .Select(a => new ContractApprovalDto( diff --git a/src/Backend/SolutionErp.Application/Contracts/Dtos/ContractDtos.cs b/src/Backend/SolutionErp.Application/Contracts/Dtos/ContractDtos.cs index b335361..1a36413 100644 --- a/src/Backend/SolutionErp.Application/Contracts/Dtos/ContractDtos.cs +++ b/src/Backend/SolutionErp.Application/Contracts/Dtos/ContractDtos.cs @@ -41,6 +41,8 @@ public record ContractDetailDto( DateTime? UpdatedAt, Guid? BudgetId, BudgetSummaryDto? Budget, + string? BudgetManualName, + decimal? BudgetManualAmount, List Approvals, List Comments, List Attachments, diff --git a/src/Backend/SolutionErp.Application/PurchaseEvaluations/CreateContractFromEvaluationFeatures.cs b/src/Backend/SolutionErp.Application/PurchaseEvaluations/CreateContractFromEvaluationFeatures.cs index d573f69..9526afc 100644 --- a/src/Backend/SolutionErp.Application/PurchaseEvaluations/CreateContractFromEvaluationFeatures.cs +++ b/src/Backend/SolutionErp.Application/PurchaseEvaluations/CreateContractFromEvaluationFeatures.cs @@ -80,6 +80,8 @@ public class CreateContractFromEvaluationCommandHandler( BypassProcurementAndCCM = request.BypassProcurementAndCCM, DraftData = pe.PaymentTerms, // carry forward payment terms BudgetId = pe.BudgetId, // carry forward Budget link nếu PE đã link + BudgetManualName = pe.BudgetManualName, // carry forward manual budget (Mig 17) + BudgetManualAmount = pe.BudgetManualAmount, WorkflowDefinitionId = activeWfId, SlaDeadline = DateTime.UtcNow.Add( workflow.GetPhaseSla(ContractPhase.DangSoanThao) ?? TimeSpan.FromDays(7)), diff --git a/src/Backend/SolutionErp.Application/PurchaseEvaluations/Dtos/PurchaseEvaluationDtos.cs b/src/Backend/SolutionErp.Application/PurchaseEvaluations/Dtos/PurchaseEvaluationDtos.cs index dbb4bcd..502ed28 100644 --- a/src/Backend/SolutionErp.Application/PurchaseEvaluations/Dtos/PurchaseEvaluationDtos.cs +++ b/src/Backend/SolutionErp.Application/PurchaseEvaluations/Dtos/PurchaseEvaluationDtos.cs @@ -127,6 +127,8 @@ public record PurchaseEvaluationDetailBundleDto( DateTime? UpdatedAt, Guid? BudgetId, BudgetSummaryDto? Budget, + string? BudgetManualName, + decimal? BudgetManualAmount, List Suppliers, List Details, List Approvals, diff --git a/src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs b/src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs index eed9496..a5fa8eb 100644 --- a/src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs +++ b/src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs @@ -23,7 +23,9 @@ public record CreatePurchaseEvaluationCommand( string? DiaDiem, string? MoTa, string? PaymentTerms, - Guid? BudgetId) : IRequest; + Guid? BudgetId, + string? BudgetManualName, + decimal? BudgetManualAmount) : IRequest; public class CreatePurchaseEvaluationCommandValidator : AbstractValidator { @@ -34,6 +36,8 @@ public class CreatePurchaseEvaluationCommandValidator : AbstractValidator x.ProjectId).NotEmpty(); RuleFor(x => x.DiaDiem).MaximumLength(500); RuleFor(x => x.MoTa).MaximumLength(2000); + RuleFor(x => x.BudgetManualName).MaximumLength(200); + RuleFor(x => x.BudgetManualAmount).GreaterThanOrEqualTo(0).When(x => x.BudgetManualAmount.HasValue); } } @@ -79,6 +83,8 @@ public class CreatePurchaseEvaluationCommandHandler( WorkflowDefinitionId = activeWfId, PaymentTerms = request.PaymentTerms, BudgetId = request.BudgetId, + BudgetManualName = request.BudgetManualName, + BudgetManualAmount = request.BudgetManualAmount, SlaDeadline = DateTime.UtcNow.Add( workflow.GetPhaseSla(PurchaseEvaluationPhase.DangSoanThao) ?? TimeSpan.FromDays(3)), }; @@ -112,7 +118,9 @@ public record UpdatePurchaseEvaluationDraftCommand( string? DiaDiem, string? MoTa, string? PaymentTerms, - Guid? BudgetId) : IRequest; + Guid? BudgetId, + string? BudgetManualName, + decimal? BudgetManualAmount) : IRequest; public class UpdatePurchaseEvaluationDraftCommandHandler( IApplicationDbContext db, @@ -143,6 +151,8 @@ public class UpdatePurchaseEvaluationDraftCommandHandler( entity.MoTa = request.MoTa; entity.PaymentTerms = request.PaymentTerms; entity.BudgetId = request.BudgetId; + entity.BudgetManualName = request.BudgetManualName; + entity.BudgetManualAmount = request.BudgetManualAmount; db.PurchaseEvaluationChangelogs.Add(new PurchaseEvaluationChangelog { @@ -408,6 +418,7 @@ public class GetPurchaseEvaluationQueryHandler( e.ContractId, e.PaymentTerms, e.SlaDeadline, e.CreatedAt, e.UpdatedAt, e.BudgetId, budgetSummary, + e.BudgetManualName, e.BudgetManualAmount, e.Suppliers .OrderBy(s => s.Order) .Select(s => new PurchaseEvaluationSupplierDto(