[CLAUDE] PurchaseEvaluation: Chunk L1 — Fix F2 skipToFinal semantic: skip pointer tới NV cuối (KHÔNG terminate DaDuyet)
Bro UAT S23 t2 catch: Plan K K2 implement F2 SAI semantic — set Phase=DaDuyet terminal auto-approve. Bro intent: "Duyệt thẳng đến CEO, bỏ qua các bước khác chứ ko phải chuyển sang đã duyệt." Refactor Service.cs ApproveV2Async F2 branch: - Resolve lastStepIdx = steps.Count - 1, lastLevelMaxOrder = max(LevelOrder) trong Step cuối - Advance pointer: CurrentWorkflowStepIndex = lastStepIdx + CurrentApprovalLevelOrder = lastLevelMaxOrder - Phase GIỮ NGUYÊN ChoDuyet — NV cuối (CEO/last approver) vẫn cần ký thật để tiến DaDuyet - Audit log "Approver skip thẳng tới Bước X Cấp Y (NV cuối) — bỏ qua các Bước/Cấp trung gian" - Guard no-op: actor đã ở slot cuối → fall through advance logic (normal → DaDuyet) (KHÔNG double-advance khi skipToFinal=true ngay slot cuối) - Reset SLA 7d cho NV cuối nhận lại FE × 2 app PeWorkflowPanel.tsx (mirror rule §3.9): - Description text update: "Phiếu sẽ skip tới NV cuối (CEO/cấp ký cuối) — NV cuối vẫn cần duyệt thật để hoàn tất." - Amber warning update: "Bỏ qua mọi Cấp/Bước trung gian, phiếu chuyển thẳng tới NV cuối. NV cuối vẫn phải ký duyệt thật để phiếu thành 'Đã duyệt'." Verify: - dotnet build production projects clean (0 err, 2 pre-existing warn) - npm run build × 2 app pass Pattern lesson saved memory: Service skipToFinal semantic = advance pointer NOT terminate. K7 tests TODO update: 3 Approver F2 tests assert pointer moved to last slot, NOT Phase=DaDuyet. Defer test fix sau UAT confirm UX. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -467,13 +467,19 @@ public class PurchaseEvaluationWorkflowService(
|
||||
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.
|
||||
// Mig 31 (S23 t1 Plan K) — F2 Approver scope ChoDuyet: skip thẳng tới
|
||||
// NV cuối (CEO / last approver). Admin opt-in per slot tại
|
||||
// matchingLevel.AllowApproverSkipToFinal. Khi Approver tick checkbox
|
||||
// "Duyệt thẳng Cấp cuối" + admin enable flag → bỏ qua mọi Bước/Cấp
|
||||
// TRUNG GIAN còn lại, advance pointer tới Bước cuối + Cấp cuối (max).
|
||||
// Phase GIỮ NGUYÊN ChoDuyet — NV cuối vẫn cần ký thật để tiến DaDuyet
|
||||
// (KHÔNG auto-approve terminal). Mirror F3+F4 admin opt-in per-slot
|
||||
// pattern (Mig 29 + Mig 30) reinforced 3× cumulative. Non-admin + flag
|
||||
// off → ConflictException. Admin bypass flag.
|
||||
//
|
||||
// S23 t2 spec fix: bro UAT feedback Plan K K2 implement SAI semantic
|
||||
// (set Phase=DaDuyet terminal auto-approve), refactor sang advance
|
||||
// pointer tới Cấp cuối (CEO duyệt thật).
|
||||
if (skipToFinal)
|
||||
{
|
||||
if (!isAdmin && !isSystem && !matchingLevel.AllowApproverSkipToFinal)
|
||||
@ -484,19 +490,37 @@ public class PurchaseEvaluationWorkflowService(
|
||||
"'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;
|
||||
// Resolve last Step + last Level (max LevelOrder trong Step cuối)
|
||||
var lastStepIdx = steps.Count - 1;
|
||||
var lastStep = steps[lastStepIdx];
|
||||
var lastLevelGroups = lastStep.Levels.OrderBy(l => l.Order).GroupBy(l => l.Order).ToList();
|
||||
var lastLevelMaxOrder = lastLevelGroups.Count == 0 ? 1 : lastLevelGroups.Max(g => g.Key);
|
||||
|
||||
// Guard: nếu actor đã ở Cấp cuối Bước cuối thì skipToFinal = no-op
|
||||
// → fall through advance logic bên dưới (normal advance → DaDuyet).
|
||||
if (currentIdx == lastStepIdx && currentLevelOrder == lastLevelMaxOrder)
|
||||
{
|
||||
// No-op skip: actor đã ở slot cuối — fall through normal advance
|
||||
// (sẽ hit branch `nextIdx >= steps.Count` → DaDuyet đúng).
|
||||
}
|
||||
else
|
||||
{
|
||||
// Advance pointer tới Bước cuối + Cấp cuối. Phase giữ ChoDuyet.
|
||||
// CEO/NV cuối thấy phiếu đang chờ duyệt + opinion của actor vừa
|
||||
// ghi sẵn → duyệt cuối để approve DaDuyet thật.
|
||||
evaluation.CurrentWorkflowStepIndex = lastStepIdx;
|
||||
evaluation.CurrentApprovalLevelOrder = lastLevelMaxOrder;
|
||||
evaluation.SlaDeadline = dateTime.UtcNow.AddDays(7);
|
||||
await LogTransitionAsync(
|
||||
evaluation,
|
||||
PurchaseEvaluationPhase.ChoDuyet,
|
||||
PurchaseEvaluationPhase.ChoDuyet,
|
||||
actorUserId,
|
||||
ApprovalDecision.Approve,
|
||||
$"[Approver skip thẳng tới Bước {lastStepIdx + 1} Cấp {lastLevelMaxOrder} (NV cuối) — bỏ qua các Bước/Cấp trung gian] {comment ?? ""}".Trim(),
|
||||
ct);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Advance: nếu còn cấp tiếp trong Step → levelOrder++; else → next Step + level 1
|
||||
|
||||
Reference in New Issue
Block a user