[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:
@ -81,15 +81,18 @@ public record PurchaseEvaluationChangelogDto(
|
|||||||
// Mig 29 (S21 t5) — Approver options của slot Level hiện tại (per-NV).
|
// 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
|
// 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.
|
// 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 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(
|
public record ApprovalWorkflowOptionsDto(
|
||||||
bool AllowReturnOneLevel,
|
bool AllowReturnOneLevel,
|
||||||
bool AllowReturnOneStep,
|
bool AllowReturnOneStep,
|
||||||
bool AllowReturnToAssignee,
|
bool AllowReturnToAssignee,
|
||||||
bool AllowReturnToDrafter,
|
bool AllowReturnToDrafter,
|
||||||
bool AllowApproverEditDetails,
|
bool AllowApproverEditDetails,
|
||||||
bool AllowApproverEditBudget);
|
bool AllowApproverEditBudget,
|
||||||
|
bool AllowApproverSkipToFinal);
|
||||||
|
|
||||||
public record PurchaseEvaluationWorkflowSummaryDto(
|
public record PurchaseEvaluationWorkflowSummaryDto(
|
||||||
string PolicyName,
|
string PolicyName,
|
||||||
@ -207,14 +210,12 @@ public record PurchaseEvaluationDetailBundleDto(
|
|||||||
string? ApprovalWorkflowCode,
|
string? ApprovalWorkflowCode,
|
||||||
string? ApprovalWorkflowName,
|
string? ApprovalWorkflowName,
|
||||||
int? ApprovalWorkflowVersion,
|
int? ApprovalWorkflowVersion,
|
||||||
// Mig 29 (S21 t5) — 5 Allow* options của Cấp hiện tại (per-NV slot). Null
|
// Mig 29 (S21 t5) + Mig 30 (S22+5) + Mig 31 (S23 t1) — 7 Allow* options
|
||||||
// nếu V1 legacy hoặc không ChoDuyet. FE render Trả lại dropdown + Edit
|
// của Cấp hiện tại (per-NV slot). Null nếu V1 legacy hoặc không ChoDuyet.
|
||||||
// Section 2 conditional. Field rename "WorkflowOptions" → "CurrentLevelOptions"
|
// FE render Trả lại dropdown + Edit Section 2 + Section 4 ngân sách +
|
||||||
// để rõ semantic per-slot không phải workflow-wide.
|
// 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,
|
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,
|
PurchaseEvaluationCurrentApprovalDto? CurrentApproval,
|
||||||
PurchaseEvaluationApprovalFlowDto? ApprovalFlow,
|
PurchaseEvaluationApprovalFlowDto? ApprovalFlow,
|
||||||
List<PurchaseEvaluationSupplierDto> Suppliers,
|
List<PurchaseEvaluationSupplierDto> Suppliers,
|
||||||
|
|||||||
@ -728,11 +728,6 @@ public class GetPurchaseEvaluationQueryHandler(
|
|||||||
// hiển thị FE detail card "QT-DN-V2-001 - Tên (v01)").
|
// 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
|
// 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.
|
// 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;
|
string? awCode = null, awName = null;
|
||||||
int? awVersion = null;
|
int? awVersion = null;
|
||||||
@ -751,8 +746,9 @@ public class GetPurchaseEvaluationQueryHandler(
|
|||||||
awName = aw.Name;
|
awName = aw.Name;
|
||||||
awVersion = aw.Version;
|
awVersion = aw.Version;
|
||||||
|
|
||||||
// Mig 29 (S21 t5) — Resolve Cấp hiện tại + populate 5 Allow* flag
|
// Mig 29 (S21 t5) + Mig 30 (S22+5) + Mig 31 (S23 t1) — Resolve
|
||||||
// của slot Approver đang duyệt. Null nếu pointer chưa init.
|
// 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
|
if (e.CurrentWorkflowStepIndex is int curStepIdx
|
||||||
&& curStepIdx >= 0 && curStepIdx < aw.Steps.Count
|
&& curStepIdx >= 0 && curStepIdx < aw.Steps.Count
|
||||||
&& e.CurrentApprovalLevelOrder is int curLevelOrder)
|
&& e.CurrentApprovalLevelOrder is int curLevelOrder)
|
||||||
@ -767,7 +763,8 @@ public class GetPurchaseEvaluationQueryHandler(
|
|||||||
curLevel.AllowReturnToAssignee,
|
curLevel.AllowReturnToAssignee,
|
||||||
curLevel.AllowReturnToDrafter,
|
curLevel.AllowReturnToDrafter,
|
||||||
curLevel.AllowApproverEditDetails,
|
curLevel.AllowApproverEditDetails,
|
||||||
curLevel.AllowApproverEditBudget);
|
curLevel.AllowApproverEditBudget,
|
||||||
|
curLevel.AllowApproverSkipToFinal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -890,8 +887,6 @@ public class GetPurchaseEvaluationQueryHandler(
|
|||||||
e.BudgetId, budgetSummary,
|
e.BudgetId, budgetSummary,
|
||||||
e.BudgetManualName, e.BudgetManualAmount,
|
e.BudgetManualName, e.BudgetManualAmount,
|
||||||
e.ApprovalWorkflowId, awCode, awName, awVersion, currentLevelOptions,
|
e.ApprovalWorkflowId, awCode, awName, awVersion, currentLevelOptions,
|
||||||
// Mig 29 (S21 t5) — F2 drafter flag từ User entity
|
|
||||||
drafterAllowSkipToFinal,
|
|
||||||
currentApproval, approvalFlow,
|
currentApproval, approvalFlow,
|
||||||
e.Suppliers
|
e.Suppliers
|
||||||
.OrderBy(s => s.Order)
|
.OrderBy(s => s.Order)
|
||||||
|
|||||||
@ -8,13 +8,15 @@ public interface IPurchaseEvaluationWorkflowService
|
|||||||
// Kiểm tra + thực hiện transition. Throw ForbiddenException nếu không hợp lệ.
|
// Kiểm tra + thực hiện transition. Throw ForbiddenException nếu không hợp lệ.
|
||||||
// Tự tạo PurchaseEvaluationApproval + update Phase + SlaDeadline.
|
// 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.
|
// - returnMode: mode Trả lại (F1). Null = default Drafter behavior khi Reject+TraLai.
|
||||||
// OneLevel/OneStep/Assignee → giữ Phase=ChoDuyet, lùi pointer (peer review).
|
// 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.
|
// - 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
|
// - skipToFinal: F2 Approver during ChoDuyet duyệt thẳng Cấp cuối → set Phase=DaDuyet
|
||||||
// = max Step + max Level. Workflow phải AllowDrafterSkipToFinal=true.
|
// 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(
|
Task TransitionAsync(
|
||||||
PurchaseEvaluation evaluation,
|
PurchaseEvaluation evaluation,
|
||||||
PurchaseEvaluationPhase targetPhase,
|
PurchaseEvaluationPhase targetPhase,
|
||||||
|
|||||||
@ -118,13 +118,10 @@ public class PurchaseEvaluationWorkflowService(
|
|||||||
}
|
}
|
||||||
evaluation.Phase = PurchaseEvaluationPhase.ChoDuyet;
|
evaluation.Phase = PurchaseEvaluationPhase.ChoDuyet;
|
||||||
|
|
||||||
// Mig 31 (S23 t1 Plan K Chunk A) — F2 Drafter SUBMIT skipToFinal branch
|
// Mig 31 (S23 t1 Plan K) — F2 Drafter-skip-from-Nháp semantic deprecated.
|
||||||
// REMOVED stub. Semantic refactor: F2 cũ Drafter-skip-from-Nháp → mới
|
// skipToFinal param 8th repurpose sang Approver scope ChoDuyet (xem
|
||||||
// Approver-skip-during-ChoDuyet (storage move sang
|
// ApproveV2Async branch). Drafter SUBMIT chạy normal init pointer Step 0
|
||||||
// ApprovalWorkflowLevels.AllowApproverSkipToFinal per-slot Approver).
|
// Cấp 1, ignore skipToFinal flag.
|
||||||
// TransitionAsync `bool skipToFinal` 8th param KEPT cho K2 sẽ repurpose
|
|
||||||
// to APPROVE STEP branch (line ~393-525 V2 path).
|
|
||||||
// K2 sẽ add Approver F2 branch trong APPROVE STEP (line ~393-525)
|
|
||||||
evaluation.CurrentWorkflowStepIndex = 0;
|
evaluation.CurrentWorkflowStepIndex = 0;
|
||||||
// Chỉ init levelOrder=1 nếu pin schema V2 (ApprovalWorkflowId set).
|
// Chỉ init levelOrder=1 nếu pin schema V2 (ApprovalWorkflowId set).
|
||||||
evaluation.CurrentApprovalLevelOrder = evaluation.ApprovalWorkflowId is not null ? 1 : null;
|
evaluation.CurrentApprovalLevelOrder = evaluation.ApprovalWorkflowId is not null ? 1 : null;
|
||||||
@ -139,12 +136,17 @@ public class PurchaseEvaluationWorkflowService(
|
|||||||
{
|
{
|
||||||
// Branch: V2 schema mới (ApprovalWorkflowId pin) hay V1 legacy
|
// Branch: V2 schema mới (ApprovalWorkflowId pin) hay V1 legacy
|
||||||
// (WorkflowDefinitionId pin Mig 21).
|
// (WorkflowDefinitionId pin Mig 21).
|
||||||
|
// Mig 31 (S23 t1 Plan K) — skipToFinal repurpose Approver scope ChoDuyet.
|
||||||
|
// V2 path nhận flag, V1 legacy throw nếu non-admin gọi skipToFinal=true.
|
||||||
if (evaluation.ApprovalWorkflowId is Guid awId)
|
if (evaluation.ApprovalWorkflowId is Guid awId)
|
||||||
{
|
{
|
||||||
await ApproveV2Async(evaluation, awId, actorUserId, actorRoles, isAdmin, isSystem, comment, ct);
|
await ApproveV2Async(evaluation, awId, actorUserId, actorRoles, isAdmin, isSystem, comment, skipToFinal, ct);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (skipToFinal && !isAdmin && !isSystem)
|
||||||
|
throw new ConflictException(
|
||||||
|
"skipToFinal chỉ hỗ trợ phiếu V2 (ApprovalWorkflowsV2). Phiếu V1 legacy không có per-Approver-slot flag.");
|
||||||
await ApproveV1LegacyAsync(evaluation, actorUserId, actorRoles, isAdmin, isSystem, comment, ct);
|
await ApproveV1LegacyAsync(evaluation, actorUserId, actorRoles, isAdmin, isSystem, comment, ct);
|
||||||
}
|
}
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
@ -364,6 +366,8 @@ public class PurchaseEvaluationWorkflowService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ===== V2 schema (Mig 22-24) — iterate ApprovalWorkflowSteps + Levels =====
|
// ===== V2 schema (Mig 22-24) — iterate ApprovalWorkflowSteps + Levels =====
|
||||||
|
// Mig 31 (S23 t1 Plan K) — `skipToFinal` 8th param: F2 Approver scope ChoDuyet.
|
||||||
|
// Admin opt-in flag per slot tại matchingLevel.AllowApproverSkipToFinal.
|
||||||
private async Task ApproveV2Async(
|
private async Task ApproveV2Async(
|
||||||
PurchaseEvaluation evaluation,
|
PurchaseEvaluation evaluation,
|
||||||
Guid awId,
|
Guid awId,
|
||||||
@ -372,6 +376,7 @@ public class PurchaseEvaluationWorkflowService(
|
|||||||
bool isAdmin,
|
bool isAdmin,
|
||||||
bool isSystem,
|
bool isSystem,
|
||||||
string? comment,
|
string? comment,
|
||||||
|
bool skipToFinal,
|
||||||
CancellationToken ct)
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
var aw = await db.ApprovalWorkflows.AsNoTracking()
|
var aw = await db.ApprovalWorkflows.AsNoTracking()
|
||||||
@ -462,6 +467,38 @@ public class PurchaseEvaluationWorkflowService(
|
|||||||
existingOpinion.SignedByFullName = actorFullName;
|
existingOpinion.SignedByFullName = actorFullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mig 31 (S23 t1 Plan K) — F2 Approver scope ChoDuyet: duyệt thẳng Cấp cuối.
|
||||||
|
// Admin opt-in per slot tại matchingLevel.AllowApproverSkipToFinal. Khi
|
||||||
|
// Approver tick checkbox "Duyệt thẳng Cấp cuối" trong Workspace + admin
|
||||||
|
// đã enable flag cho slot này → bỏ qua mọi Bước/Cấp trung gian còn lại,
|
||||||
|
// set Phase=DaDuyet terminal trực tiếp. Mirror F3+F4 admin opt-in per-
|
||||||
|
// Approver-slot pattern (Mig 29 + Mig 30) reinforced 3× cumulative.
|
||||||
|
// Non-admin + flag off → ConflictException. Admin bypass flag.
|
||||||
|
if (skipToFinal)
|
||||||
|
{
|
||||||
|
if (!isAdmin && !isSystem && !matchingLevel.AllowApproverSkipToFinal)
|
||||||
|
{
|
||||||
|
throw new ConflictException(
|
||||||
|
$"Cấp Approver hiện tại (Bước {currentIdx + 1} Cấp {currentLevelOrder}) " +
|
||||||
|
"chưa được phép duyệt thẳng Cấp cuối. Admin phải tick checkbox " +
|
||||||
|
"'Duyệt thẳng Cấp cuối' trong Workflow Designer cho slot này.");
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluation.Phase = PurchaseEvaluationPhase.DaDuyet;
|
||||||
|
evaluation.CurrentWorkflowStepIndex = null;
|
||||||
|
evaluation.CurrentApprovalLevelOrder = null;
|
||||||
|
evaluation.SlaDeadline = null;
|
||||||
|
await LogTransitionAsync(
|
||||||
|
evaluation,
|
||||||
|
PurchaseEvaluationPhase.ChoDuyet,
|
||||||
|
PurchaseEvaluationPhase.DaDuyet,
|
||||||
|
actorUserId,
|
||||||
|
ApprovalDecision.Approve,
|
||||||
|
$"[Approver duyệt thẳng Cấp cuối — Bước {currentIdx + 1} Cấp {currentLevelOrder} → DaDuyet] {comment ?? ""}".Trim(),
|
||||||
|
ct);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Advance: nếu còn cấp tiếp trong Step → levelOrder++; else → next Step + level 1
|
// Advance: nếu còn cấp tiếp trong Step → levelOrder++; else → next Step + level 1
|
||||||
if (currentLevelOrder < maxLevelOrder)
|
if (currentLevelOrder < maxLevelOrder)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user