[CLAUDE] PE-Workflow: Chunk B — BE Service + handlers + DTOs (F1+F2+F3)

F1 — 4 mode Trả lại (Service.ApplyReturnModeAsync helper):
- WorkflowReturnMode enum (OneLevel / OneStep / Assignee / Drafter)
- OneLevel: lùi 1 Cấp trong cùng Step (peer review). Bước 1 Cấp 1 → fallback Drafter.
- OneStep: lùi sang Bước trước Cấp cuối. Bước 1 → fallback Drafter.
- Assignee: pick runtime → tìm Step+Level match ApproverUserId trong workflow.
- Drafter: Phase=TraLai clear pointer như S17 (backward compat).
- 3 mode đầu giữ Phase=ChoDuyet, reset SLA 7d. Mode Drafter clear SLA.
- Admin bypass workflow.Allow* flag check. Non-admin → throw ConflictException
  với message rõ "Workflow không bật mode X".

F2 — Drafter skipToFinal (extend DRAFTER trình branch):
- Workflow.AllowDrafterSkipToFinal=true required (non-admin)
- Set CurrentWorkflowStepIndex = Steps.Count-1 + CurrentApprovalLevelOrder = max Level
- Audit comment append "[Drafter gửi thẳng Cấp cuối]"

F3 — Approver edit Section 2 (Detail + NCC + Báo giá):
- New helper `EnsureEditableForDetailsAsync` (extend pattern PurchaseEvaluationDraftGuard):
  - Drafter scope: DangSoanThao OR TraLai (any role, Controller [Authorize] handles)
  - F3 Approver scope: ChoDuyet + workflow.AllowApproverEditDetails=true +
    actor.Id match CurrentLevel.ApproverUserId. Admin bypass flag check.
  - Throw ForbiddenException nếu approver Cấp khác nhau (rõ Bước/Cấp trong message).
- 8 handler switch helper + inject ICurrentUser khi cần:
  - Detail: Add (existing ICurrentUser) / Update + Delete (inject new)
  - Quote: Upsert + Delete (inject new)
  - Supplier: Add (existing) / Update + Delete (inject new + add guard, trước
    đây hoàn toàn KHÔNG có phase guard — bonus security fix)
- Audit: thêm changelog Update/Delete handler (trước đây silent). Khi phase=
  ChoDuyet append " [Approver edit khi đang duyệt]" cho lịch sử rõ ai sửa.

Extension Service `TransitionAsync` signature (backward compat — 3 optional
param thêm cuối + default null/false):
- WorkflowReturnMode? returnMode = null
- Guid? returnTargetUserId = null
- bool skipToFinal = false

TransitionPurchaseEvaluationCommand DTO + Validator + Handler — mirror signature.

DTO extensions:
- ApprovalWorkflowOptionsDto NEW sub-record (6 Allow* flag) cho FE filter
- PurchaseEvaluationDetailBundleDto + WorkflowOptions field (null nếu V1 legacy)
- GetPe handler populate awOptions từ ApprovalWorkflow entity load (Mig 23 path)
- AwDefinitionDto + 6 Allow* field (admin Designer GET overview)
- CreateAwDefinitionCommand + 6 Allow* param (admin Designer POST new version)
- Handler ToDto + entity new() — propagate Allow* end-to-end

Default backward compat: workflow cũ → AllowReturnToDrafter=true (Mig 28 DB
default), 5 flag còn lại false. Phiếu cũ V2 vẫn Trả lại Drafter như S17 sau
deploy — no breaking change.

Verify:
- dotnet build SolutionErp.slnx → 0 err, 2 warn pre-existing DocxRenderer
- 3 regression test gotcha #45 vẫn PASS (backward compat signature change)
- LocalDB Dev + Design đã apply Mig 28 (Chunk A)

Pending Chunk C: FE Admin Designer mirror 2 app (6 checkbox + DTO types).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-13 18:57:09 +07:00
parent 0294693a4a
commit c56024ba25
7 changed files with 478 additions and 29 deletions

View File

@ -46,6 +46,15 @@ public record AwDefinitionDto(
string? Description,
bool IsActive,
bool IsUserSelectable,
// Mig 28 (S21 t4) — 6 advanced options của workflow per version. Admin
// Designer tick stick → checkbox. FE eOffice render dropdown / Skip / Edit
// conditional theo flag tương ứng.
bool AllowReturnOneLevel,
bool AllowReturnOneStep,
bool AllowReturnToAssignee,
bool AllowReturnToDrafter,
bool AllowDrafterSkipToFinal,
bool AllowApproverEditDetails,
DateTime? ActivatedAt,
DateTime CreatedAt,
List<AwStepDto> Steps);
@ -128,6 +137,13 @@ public class GetAwAdminOverviewQueryHandler(
d.Description,
d.IsActive,
d.IsUserSelectable,
// Mig 28 — 6 Allow* flag
d.AllowReturnOneLevel,
d.AllowReturnOneStep,
d.AllowReturnToAssignee,
d.AllowReturnToDrafter,
d.AllowDrafterSkipToFinal,
d.AllowApproverEditDetails,
d.ActivatedAt,
d.CreatedAt,
d.Steps.OrderBy(s => s.Order).Select(s => new AwStepDto(
@ -178,7 +194,15 @@ public record CreateAwDefinitionCommand(
string Code,
string Name,
string? Description,
List<CreateAwStepInput> Steps) : IRequest<Guid>;
List<CreateAwStepInput> Steps,
// Mig 28 (S21 t4) — 6 Allow* options. Default = backward compat S17
// (chỉ Trả về Drafter enabled). Admin tick stick để mở mode khác.
bool AllowReturnOneLevel = false,
bool AllowReturnOneStep = false,
bool AllowReturnToAssignee = false,
bool AllowReturnToDrafter = true,
bool AllowDrafterSkipToFinal = false,
bool AllowApproverEditDetails = false) : IRequest<Guid>;
public class CreateAwDefinitionCommandValidator : AbstractValidator<CreateAwDefinitionCommand>
{
@ -271,6 +295,13 @@ public class CreateAwDefinitionCommandHandler(IApplicationDbContext db)
Description = request.Description,
IsActive = true,
IsUserSelectable = true, // Mig 25 — version mới mặc định cho user pick
// Mig 28 (S21 t4) — 6 Allow* options
AllowReturnOneLevel = request.AllowReturnOneLevel,
AllowReturnOneStep = request.AllowReturnOneStep,
AllowReturnToAssignee = request.AllowReturnToAssignee,
AllowReturnToDrafter = request.AllowReturnToDrafter,
AllowDrafterSkipToFinal = request.AllowDrafterSkipToFinal,
AllowApproverEditDetails = request.AllowApproverEditDetails,
ActivatedAt = DateTime.UtcNow,
Steps = request.Steps.OrderBy(s => s.Order)
.Select(s => new ApprovalWorkflowStep