[CLAUDE] PurchaseEvaluation: Chunk B — Mig 31 K2 Approver F2 branch APPROVE STEP + DTO refactor

Service ApproveV2Async +skipToFinal 8th param. APPROVE STEP branch sau UPSERT
PEL opinion: check admin OR matchingLevel.AllowApproverSkipToFinal → set
Phase=DaDuyet terminal directly, clear pointer + SLA, audit "[Approver duyệt
thẳng Cấp cuối — Bước X Cấp Y → DaDuyet]". Non-admin + flag off → ConflictException.

ApproveV1LegacyAsync: throw nếu skipToFinal=true non-admin (V1 legacy không
hỗ trợ per-Approver-slot flag).

Caller TransitionAsync line ~144 pass skipToFinal vào ApproveV2Async.
Drafter SUBMIT branch ignore skipToFinal (K1 đã remove F2 Drafter semantic
stub) — Mig 31 marker comment cleanup.

DTO ApprovalWorkflowOptionsDto +bool AllowApproverSkipToFinal (7th field).
DTO PurchaseEvaluationDetailBundleDto -DrafterAllowSkipToFinal field.
GetPe handler populate 7 Allow* từ curLevel (Mig 29+30+31 cumulative).
Sentinel `var drafterAllowSkipToFinal = false;` cleanup từ K1.

IPurchaseEvaluationWorkflowService.cs comment skipToFinal semantic refactor:
Drafter from Nháp → Approver during ChoDuyet skip thẳng Cấp cuối.

Pattern reusable: feedback_per_nv_permission_scope.md reinforced 3× cumulative
(Mig 29 F1+F3 + Mig 30 F4 + Mig 31 F2).

Verify:
- dotnet build production projects clean (0 err, 2 warnings pre-existing DocxRenderer)
- Test fail at K1 expected (test file references removed prop, K7 sẽ fix)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-14 23:08:11 +07:00
parent db6625304a
commit 364aef63fd
4 changed files with 66 additions and 31 deletions

View File

@ -81,15 +81,18 @@ public record PurchaseEvaluationChangelogDto(
// Mig 29 (S21 t5) — Approver options của slot Level hiện tại (per-NV).
// FE eOffice filter Trả lại dropdown + Edit Section 2 enabled theo flag của
// Cấp hiện tại NV đang duyệt. Null nếu phiếu V1 legacy hoặc không ChoDuyet.
// F2 (Drafter skip) đã move sang `PeDetailBundleDto.DrafterAllowSkipToFinal`.
// Mig 30 (S22+5) — F4 +AllowApproverEditBudget cho Section "Điều chỉnh ngân sách".
// Mig 31 (S23 t1) — F2 refactor sang Approver scope ChoDuyet: +AllowApproverSkipToFinal
// cho phép Approver duyệt thẳng Cấp cuối (admin opt-in per slot). Storage cũ
// Users.AllowDrafterSkipToFinal đã drop, semantic Drafter-from-Nháp deprecated.
public record ApprovalWorkflowOptionsDto(
bool AllowReturnOneLevel,
bool AllowReturnOneStep,
bool AllowReturnToAssignee,
bool AllowReturnToDrafter,
bool AllowApproverEditDetails,
bool AllowApproverEditBudget);
bool AllowApproverEditBudget,
bool AllowApproverSkipToFinal);
public record PurchaseEvaluationWorkflowSummaryDto(
string PolicyName,
@ -207,14 +210,12 @@ public record PurchaseEvaluationDetailBundleDto(
string? ApprovalWorkflowCode,
string? ApprovalWorkflowName,
int? ApprovalWorkflowVersion,
// Mig 29 (S21 t5) — 5 Allow* options của Cấp hiện tại (per-NV slot). Null
// nếu V1 legacy hoặc không ChoDuyet. FE render Trả lại dropdown + Edit
// Section 2 conditional. Field rename "WorkflowOptions" → "CurrentLevelOptions"
// để rõ semantic per-slot không phải workflow-wide.
// Mig 29 (S21 t5) + Mig 30 (S22+5) + Mig 31 (S23 t1)7 Allow* options
// của Cấp hiện tại (per-NV slot). Null nếu V1 legacy hoặc không ChoDuyet.
// FE render Trả lại dropdown + Edit Section 2 + Section 4 ngân sách +
// Duyệt thẳng Cấp cuối conditional. Field rename "WorkflowOptions" →
// "CurrentLevelOptions" để rõ semantic per-slot không phải workflow-wide.
ApprovalWorkflowOptionsDto? CurrentLevelOptions,
// Mig 29 — F2 per-Drafter: cờ AllowDrafterSkipToFinal của Drafter user pin
// phiếu. Workspace conditional render checkbox "Gửi thẳng Cấp cuối".
bool DrafterAllowSkipToFinal,
PurchaseEvaluationCurrentApprovalDto? CurrentApproval,
PurchaseEvaluationApprovalFlowDto? ApprovalFlow,
List<PurchaseEvaluationSupplierDto> Suppliers,

View File

@ -728,11 +728,6 @@ public class GetPurchaseEvaluationQueryHandler(
// hiển thị FE detail card "QT-DN-V2-001 - Tên (v01)").
// Mig 24 — populate CurrentApproval (cấp hiện tại) + ApprovalFlow (full
// Bước/Cấp tree với Status) cho FE render flow vertical thay phase cards.
// Mig 31 (S23 t1 K1) — F2 storage moved to ApprovalWorkflowLevels.AllowApproverSkipToFinal
// (per-Approver slot). Drafter flag retired. DTO field kept transiently (K2 sẽ refactor
// FE Workspace remove checkbox "Gửi thẳng Cấp cuối" Drafter mode + Approve panel hiện
// checkbox dynamic theo Level.AllowApproverSkipToFinal). Sentinel false.
var drafterAllowSkipToFinal = false;
string? awCode = null, awName = null;
int? awVersion = null;
@ -751,8 +746,9 @@ public class GetPurchaseEvaluationQueryHandler(
awName = aw.Name;
awVersion = aw.Version;
// Mig 29 (S21 t5) — Resolve Cấp hiện tại + populate 5 Allow* flag
// của slot Approver đang duyệt. Null nếu pointer chưa init.
// Mig 29 (S21 t5) + Mig 30 (S22+5) + Mig 31 (S23 t1) — Resolve
// Cấp hiện tại + populate 7 Allow* flag của slot Approver đang
// duyệt. Null nếu pointer chưa init.
if (e.CurrentWorkflowStepIndex is int curStepIdx
&& curStepIdx >= 0 && curStepIdx < aw.Steps.Count
&& e.CurrentApprovalLevelOrder is int curLevelOrder)
@ -767,7 +763,8 @@ public class GetPurchaseEvaluationQueryHandler(
curLevel.AllowReturnToAssignee,
curLevel.AllowReturnToDrafter,
curLevel.AllowApproverEditDetails,
curLevel.AllowApproverEditBudget);
curLevel.AllowApproverEditBudget,
curLevel.AllowApproverSkipToFinal);
}
}
@ -890,8 +887,6 @@ public class GetPurchaseEvaluationQueryHandler(
e.BudgetId, budgetSummary,
e.BudgetManualName, e.BudgetManualAmount,
e.ApprovalWorkflowId, awCode, awName, awVersion, currentLevelOptions,
// Mig 29 (S21 t5) — F2 drafter flag từ User entity
drafterAllowSkipToFinal,
currentApproval, approvalFlow,
e.Suppliers
.OrderBy(s => s.Order)

View File

@ -8,13 +8,15 @@ public interface IPurchaseEvaluationWorkflowService
// Kiểm tra + thực hiện transition. Throw ForbiddenException nếu không hợp lệ.
// Tự tạo PurchaseEvaluationApproval + update Phase + SlaDeadline.
//
// Optional params Mig 28 (S21 t4 — F1+F2 advanced workflow options):
// Optional params Mig 28-31 (S21-S23 advanced workflow options per-NV slot):
// - returnMode: mode Trả lại (F1). Null = default Drafter behavior khi Reject+TraLai.
// OneLevel/OneStep/Assignee → giữ Phase=ChoDuyet, lùi pointer (peer review).
// Drafter → Phase=TraLai clear pointer như S17.
// Drafter → Phase=TraLai clear pointer như S17. Flag check tại level.Allow*.
// - returnTargetUserId: required khi returnMode=Assignee — pick từ list NV đã duyệt.
// - skipToFinal: F2 Drafter trình duyệt → skip mọi Bước/Cấp trung gian, set pointer
// = max Step + max Level. Workflow phải AllowDrafterSkipToFinal=true.
// - skipToFinal: F2 Approver during ChoDuyet duyệt thẳng Cấp cuối → set Phase=DaDuyet
// terminal trực tiếp, clear pointer. Mig 31 (S23 t1) refactor sang Approver scope:
// matchingLevel.AllowApproverSkipToFinal phải true (admin opt-in per slot).
// Semantic cũ Drafter-from-Nháp đã deprecated, storage Users.AllowDrafterSkipToFinal dropped.
Task TransitionAsync(
PurchaseEvaluation evaluation,
PurchaseEvaluationPhase targetPhase,