[CLAUDE] App: PE + Contract Create/Update commands + DTO add manual budget fields

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) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-07 12:34:00 +07:00
parent ecd5f7e9d9
commit 0f7901c19f
5 changed files with 36 additions and 4 deletions

View File

@ -25,7 +25,9 @@ public record CreateContractCommand(
string? NoiDung,
bool BypassProcurementAndCCM,
string? DraftData,
Guid? BudgetId) : IRequest<Guid>;
Guid? BudgetId,
string? BudgetManualName,
decimal? BudgetManualAmount) : IRequest<Guid>;
public class CreateContractCommandValidator : AbstractValidator<CreateContractCommand>
{
@ -37,6 +39,8 @@ public class CreateContractCommandValidator : AbstractValidator<CreateContractCo
RuleFor(x => 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(

View File

@ -41,6 +41,8 @@ public record ContractDetailDto(
DateTime? UpdatedAt,
Guid? BudgetId,
BudgetSummaryDto? Budget,
string? BudgetManualName,
decimal? BudgetManualAmount,
List<ContractApprovalDto> Approvals,
List<ContractCommentDto> Comments,
List<ContractAttachmentDto> Attachments,