[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:
@ -25,7 +25,9 @@ public record CreateContractCommand(
|
|||||||
string? NoiDung,
|
string? NoiDung,
|
||||||
bool BypassProcurementAndCCM,
|
bool BypassProcurementAndCCM,
|
||||||
string? DraftData,
|
string? DraftData,
|
||||||
Guid? BudgetId) : IRequest<Guid>;
|
Guid? BudgetId,
|
||||||
|
string? BudgetManualName,
|
||||||
|
decimal? BudgetManualAmount) : IRequest<Guid>;
|
||||||
|
|
||||||
public class CreateContractCommandValidator : AbstractValidator<CreateContractCommand>
|
public class CreateContractCommandValidator : AbstractValidator<CreateContractCommand>
|
||||||
{
|
{
|
||||||
@ -37,6 +39,8 @@ public class CreateContractCommandValidator : AbstractValidator<CreateContractCo
|
|||||||
RuleFor(x => x.GiaTri).GreaterThanOrEqualTo(0);
|
RuleFor(x => x.GiaTri).GreaterThanOrEqualTo(0);
|
||||||
RuleFor(x => x.TenHopDong).MaximumLength(500);
|
RuleFor(x => x.TenHopDong).MaximumLength(500);
|
||||||
RuleFor(x => x.NoiDung).MaximumLength(2000);
|
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,
|
BypassProcurementAndCCM = request.BypassProcurementAndCCM,
|
||||||
DraftData = request.DraftData,
|
DraftData = request.DraftData,
|
||||||
BudgetId = request.BudgetId,
|
BudgetId = request.BudgetId,
|
||||||
|
BudgetManualName = request.BudgetManualName,
|
||||||
|
BudgetManualAmount = request.BudgetManualAmount,
|
||||||
WorkflowDefinitionId = activeWfId,
|
WorkflowDefinitionId = activeWfId,
|
||||||
SlaDeadline = DateTime.UtcNow.Add(workflow.GetPhaseSla(ContractPhase.DangSoanThao) ?? TimeSpan.FromDays(7)),
|
SlaDeadline = DateTime.UtcNow.Add(workflow.GetPhaseSla(ContractPhase.DangSoanThao) ?? TimeSpan.FromDays(7)),
|
||||||
};
|
};
|
||||||
@ -121,7 +127,9 @@ public record UpdateContractDraftCommand(
|
|||||||
string? NoiDung,
|
string? NoiDung,
|
||||||
Guid? TemplateId,
|
Guid? TemplateId,
|
||||||
string? DraftData,
|
string? DraftData,
|
||||||
Guid? BudgetId) : IRequest;
|
Guid? BudgetId,
|
||||||
|
string? BudgetManualName,
|
||||||
|
decimal? BudgetManualAmount) : IRequest;
|
||||||
|
|
||||||
public class UpdateContractDraftCommandHandler(
|
public class UpdateContractDraftCommandHandler(
|
||||||
IApplicationDbContext db,
|
IApplicationDbContext db,
|
||||||
@ -159,6 +167,10 @@ public class UpdateContractDraftCommandHandler(
|
|||||||
changes.Add(new { Field = "TemplateId", Old = entity.TemplateId, New = request.TemplateId });
|
changes.Add(new { Field = "TemplateId", Old = entity.TemplateId, New = request.TemplateId });
|
||||||
if (entity.BudgetId != request.BudgetId)
|
if (entity.BudgetId != request.BudgetId)
|
||||||
changes.Add(new { Field = "BudgetId", Old = entity.BudgetId, New = 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.GiaTri = request.GiaTri;
|
||||||
entity.TenHopDong = request.TenHopDong;
|
entity.TenHopDong = request.TenHopDong;
|
||||||
@ -166,6 +178,8 @@ public class UpdateContractDraftCommandHandler(
|
|||||||
entity.TemplateId = request.TemplateId;
|
entity.TemplateId = request.TemplateId;
|
||||||
entity.DraftData = request.DraftData;
|
entity.DraftData = request.DraftData;
|
||||||
entity.BudgetId = request.BudgetId;
|
entity.BudgetId = request.BudgetId;
|
||||||
|
entity.BudgetManualName = request.BudgetManualName;
|
||||||
|
entity.BudgetManualAmount = request.BudgetManualAmount;
|
||||||
|
|
||||||
if (changes.Count > 0)
|
if (changes.Count > 0)
|
||||||
{
|
{
|
||||||
@ -471,6 +485,7 @@ public class GetContractQueryHandler(
|
|||||||
c.TemplateId, c.GiaTri, c.BypassProcurementAndCCM, c.SlaDeadline, c.DraftData,
|
c.TemplateId, c.GiaTri, c.BypassProcurementAndCCM, c.SlaDeadline, c.DraftData,
|
||||||
c.CreatedAt, c.UpdatedAt,
|
c.CreatedAt, c.UpdatedAt,
|
||||||
c.BudgetId, budgetSummary,
|
c.BudgetId, budgetSummary,
|
||||||
|
c.BudgetManualName, c.BudgetManualAmount,
|
||||||
c.Approvals
|
c.Approvals
|
||||||
.OrderBy(a => a.ApprovedAt)
|
.OrderBy(a => a.ApprovedAt)
|
||||||
.Select(a => new ContractApprovalDto(
|
.Select(a => new ContractApprovalDto(
|
||||||
|
|||||||
@ -41,6 +41,8 @@ public record ContractDetailDto(
|
|||||||
DateTime? UpdatedAt,
|
DateTime? UpdatedAt,
|
||||||
Guid? BudgetId,
|
Guid? BudgetId,
|
||||||
BudgetSummaryDto? Budget,
|
BudgetSummaryDto? Budget,
|
||||||
|
string? BudgetManualName,
|
||||||
|
decimal? BudgetManualAmount,
|
||||||
List<ContractApprovalDto> Approvals,
|
List<ContractApprovalDto> Approvals,
|
||||||
List<ContractCommentDto> Comments,
|
List<ContractCommentDto> Comments,
|
||||||
List<ContractAttachmentDto> Attachments,
|
List<ContractAttachmentDto> Attachments,
|
||||||
|
|||||||
@ -80,6 +80,8 @@ public class CreateContractFromEvaluationCommandHandler(
|
|||||||
BypassProcurementAndCCM = request.BypassProcurementAndCCM,
|
BypassProcurementAndCCM = request.BypassProcurementAndCCM,
|
||||||
DraftData = pe.PaymentTerms, // carry forward payment terms
|
DraftData = pe.PaymentTerms, // carry forward payment terms
|
||||||
BudgetId = pe.BudgetId, // carry forward Budget link nếu PE đã link
|
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,
|
WorkflowDefinitionId = activeWfId,
|
||||||
SlaDeadline = DateTime.UtcNow.Add(
|
SlaDeadline = DateTime.UtcNow.Add(
|
||||||
workflow.GetPhaseSla(ContractPhase.DangSoanThao) ?? TimeSpan.FromDays(7)),
|
workflow.GetPhaseSla(ContractPhase.DangSoanThao) ?? TimeSpan.FromDays(7)),
|
||||||
|
|||||||
@ -127,6 +127,8 @@ public record PurchaseEvaluationDetailBundleDto(
|
|||||||
DateTime? UpdatedAt,
|
DateTime? UpdatedAt,
|
||||||
Guid? BudgetId,
|
Guid? BudgetId,
|
||||||
BudgetSummaryDto? Budget,
|
BudgetSummaryDto? Budget,
|
||||||
|
string? BudgetManualName,
|
||||||
|
decimal? BudgetManualAmount,
|
||||||
List<PurchaseEvaluationSupplierDto> Suppliers,
|
List<PurchaseEvaluationSupplierDto> Suppliers,
|
||||||
List<PurchaseEvaluationDetailDto> Details,
|
List<PurchaseEvaluationDetailDto> Details,
|
||||||
List<PurchaseEvaluationApprovalDto> Approvals,
|
List<PurchaseEvaluationApprovalDto> Approvals,
|
||||||
|
|||||||
@ -23,7 +23,9 @@ public record CreatePurchaseEvaluationCommand(
|
|||||||
string? DiaDiem,
|
string? DiaDiem,
|
||||||
string? MoTa,
|
string? MoTa,
|
||||||
string? PaymentTerms,
|
string? PaymentTerms,
|
||||||
Guid? BudgetId) : IRequest<Guid>;
|
Guid? BudgetId,
|
||||||
|
string? BudgetManualName,
|
||||||
|
decimal? BudgetManualAmount) : IRequest<Guid>;
|
||||||
|
|
||||||
public class CreatePurchaseEvaluationCommandValidator : AbstractValidator<CreatePurchaseEvaluationCommand>
|
public class CreatePurchaseEvaluationCommandValidator : AbstractValidator<CreatePurchaseEvaluationCommand>
|
||||||
{
|
{
|
||||||
@ -34,6 +36,8 @@ public class CreatePurchaseEvaluationCommandValidator : AbstractValidator<Create
|
|||||||
RuleFor(x => x.ProjectId).NotEmpty();
|
RuleFor(x => x.ProjectId).NotEmpty();
|
||||||
RuleFor(x => x.DiaDiem).MaximumLength(500);
|
RuleFor(x => x.DiaDiem).MaximumLength(500);
|
||||||
RuleFor(x => x.MoTa).MaximumLength(2000);
|
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,
|
WorkflowDefinitionId = activeWfId,
|
||||||
PaymentTerms = request.PaymentTerms,
|
PaymentTerms = request.PaymentTerms,
|
||||||
BudgetId = request.BudgetId,
|
BudgetId = request.BudgetId,
|
||||||
|
BudgetManualName = request.BudgetManualName,
|
||||||
|
BudgetManualAmount = request.BudgetManualAmount,
|
||||||
SlaDeadline = DateTime.UtcNow.Add(
|
SlaDeadline = DateTime.UtcNow.Add(
|
||||||
workflow.GetPhaseSla(PurchaseEvaluationPhase.DangSoanThao) ?? TimeSpan.FromDays(3)),
|
workflow.GetPhaseSla(PurchaseEvaluationPhase.DangSoanThao) ?? TimeSpan.FromDays(3)),
|
||||||
};
|
};
|
||||||
@ -112,7 +118,9 @@ public record UpdatePurchaseEvaluationDraftCommand(
|
|||||||
string? DiaDiem,
|
string? DiaDiem,
|
||||||
string? MoTa,
|
string? MoTa,
|
||||||
string? PaymentTerms,
|
string? PaymentTerms,
|
||||||
Guid? BudgetId) : IRequest;
|
Guid? BudgetId,
|
||||||
|
string? BudgetManualName,
|
||||||
|
decimal? BudgetManualAmount) : IRequest;
|
||||||
|
|
||||||
public class UpdatePurchaseEvaluationDraftCommandHandler(
|
public class UpdatePurchaseEvaluationDraftCommandHandler(
|
||||||
IApplicationDbContext db,
|
IApplicationDbContext db,
|
||||||
@ -143,6 +151,8 @@ public class UpdatePurchaseEvaluationDraftCommandHandler(
|
|||||||
entity.MoTa = request.MoTa;
|
entity.MoTa = request.MoTa;
|
||||||
entity.PaymentTerms = request.PaymentTerms;
|
entity.PaymentTerms = request.PaymentTerms;
|
||||||
entity.BudgetId = request.BudgetId;
|
entity.BudgetId = request.BudgetId;
|
||||||
|
entity.BudgetManualName = request.BudgetManualName;
|
||||||
|
entity.BudgetManualAmount = request.BudgetManualAmount;
|
||||||
|
|
||||||
db.PurchaseEvaluationChangelogs.Add(new PurchaseEvaluationChangelog
|
db.PurchaseEvaluationChangelogs.Add(new PurchaseEvaluationChangelog
|
||||||
{
|
{
|
||||||
@ -408,6 +418,7 @@ public class GetPurchaseEvaluationQueryHandler(
|
|||||||
e.ContractId,
|
e.ContractId,
|
||||||
e.PaymentTerms, e.SlaDeadline, e.CreatedAt, e.UpdatedAt,
|
e.PaymentTerms, e.SlaDeadline, e.CreatedAt, e.UpdatedAt,
|
||||||
e.BudgetId, budgetSummary,
|
e.BudgetId, budgetSummary,
|
||||||
|
e.BudgetManualName, e.BudgetManualAmount,
|
||||||
e.Suppliers
|
e.Suppliers
|
||||||
.OrderBy(s => s.Order)
|
.OrderBy(s => s.Order)
|
||||||
.Select(s => new PurchaseEvaluationSupplierDto(
|
.Select(s => new PurchaseEvaluationSupplierDto(
|
||||||
|
|||||||
Reference in New Issue
Block a user