[CLAUDE] Domain+Infra+App+FE: dynamic workflow policy per ContractType
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m42s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m42s
Đọc QT-TP-NCC.docx: quy trình 9 bước chỉ áp dụng cho Thầu phụ/NCC/Tổ đội.
Dịch vụ/Mua bán/Nguyên tắc bypass CCM. Thay hardcoded dict bằng policy
registry.
Domain — WorkflowPolicy.cs:
- Record WorkflowPolicy { Name, Description, Transitions, PhaseSla,
ActivePhases } — pure data, testable.
- WorkflowPolicies.Standard: 9-phase full (Thầu phụ/Giao khoán/NCC)
- WorkflowPolicies.SkipCcm: 7-phase (Dịch vụ/Mua bán/Nguyên tắc)
- WorkflowPolicyRegistry.For(type) map ContractType → policy
- WorkflowPolicyRegistry.ForContract(c) override nếu BypassProcurement
AndCCM=true (instance-level escape hatch)
Infrastructure — ContractWorkflowService:
- Xóa hardcoded Transitions/PhaseSla dicts → load từ policy.ForContract
- TransitionAsync: validate qua policy.Transitions thay vì dict local
- Error message include policy.Name để debug dễ hơn
- GetPhaseSla trả SLA từ Standard policy (fallback — SLA hiện tại giống
nhau giữa 2 policy)
Application — ContractDetailDto:
- Field mới `Workflow: WorkflowSummaryDto { PolicyName, Description,
ActivePhases, NextPhases }` — FE dùng để render nút chuyển phase
dynamic + timeline card.
- BuildWorkflowSummary helper trong ContractFeatures.
FE (both apps):
- Type WorkflowSummary + ContractDetail.workflow
- ContractDetailPage xóa hardcoded NEXT_PHASES — dùng
c.workflow.nextPhases từ BE (single source of truth)
- WorkflowSummaryCard: timeline của ActivePhases với check/current/
future states + policy name/description ở header
- Card hiển thị trong sidebar, phía trên "Lịch sử duyệt"
Docs:
- gotchas.md #21 marked RESOLVED (NEXT_PHASES sync không còn cần)
Foundation: sau này admin có thể edit policy qua UI khi chuyển sang DB-
backed policy — nhưng API contract (WorkflowSummaryDto) đã stable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -375,7 +375,20 @@ public class GetContractQueryHandler(
|
||||
.Select(att => new ContractAttachmentDto(
|
||||
att.Id, att.FileName, att.StoragePath, att.FileSize,
|
||||
att.ContentType, att.Purpose, att.Note, att.CreatedAt))
|
||||
.ToList());
|
||||
.ToList(),
|
||||
BuildWorkflowSummary(c));
|
||||
}
|
||||
|
||||
// FE uses this to render next-phase buttons dynamically — no more hardcoded
|
||||
// NEXT_PHASES map that silently drifts from the BE policy.
|
||||
private static WorkflowSummaryDto BuildWorkflowSummary(Contract c)
|
||||
{
|
||||
var policy = WorkflowPolicyRegistry.ForContract(c);
|
||||
return new WorkflowSummaryDto(
|
||||
PolicyName: policy.Name,
|
||||
PolicyDescription: policy.Description,
|
||||
ActivePhases: policy.ActivePhases.ToList(),
|
||||
NextPhases: policy.NextPhasesFrom(c.Phase).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -40,7 +40,16 @@ public record ContractDetailDto(
|
||||
DateTime? UpdatedAt,
|
||||
List<ContractApprovalDto> Approvals,
|
||||
List<ContractCommentDto> Comments,
|
||||
List<ContractAttachmentDto> Attachments);
|
||||
List<ContractAttachmentDto> Attachments,
|
||||
WorkflowSummaryDto Workflow);
|
||||
|
||||
// Policy snapshot for the FE — lets UI render next-phase buttons dynamically
|
||||
// without hardcoding the transition map (single source of truth in BE).
|
||||
public record WorkflowSummaryDto(
|
||||
string PolicyName,
|
||||
string PolicyDescription,
|
||||
List<ContractPhase> ActivePhases,
|
||||
List<ContractPhase> NextPhases);
|
||||
|
||||
public record ContractApprovalDto(
|
||||
Guid Id,
|
||||
|
||||
Reference in New Issue
Block a user