Compare commits
2 Commits
409a9676e8
...
10ddc8761b
| Author | SHA1 | Date | |
|---|---|---|---|
| 10ddc8761b | |||
| f3db9e6cc0 |
@ -966,7 +966,13 @@ function BudgetAdjustSection({ ev, readOnly }: { ev: PeDetailBundle; readOnly: b
|
|||||||
&& actorInCurrentLevel
|
&& actorInCurrentLevel
|
||||||
&& approverEditBudgetAllowed
|
&& approverEditBudgetAllowed
|
||||||
|
|
||||||
const canAdjust = !readOnly && (isAdmin || (isDrafter && isDrafterPhase) || isApproverChoDuyet)
|
// S23 t2 bug fix: F4 Approver scope BYPASS readOnly (mirror F3 itemsReadOnly
|
||||||
|
// pattern). Khi admin tick AllowApproverEditBudget cho slot + actor match +
|
||||||
|
// Phase=ChoDuyet → button "Điều chỉnh" enable trong menu Duyệt (readOnly=true)
|
||||||
|
// dù chế độ chỉ-đọc. Drafter + Admin vẫn cần !readOnly (chỉ active từ Workspace).
|
||||||
|
const canAdjust = isAdmin
|
||||||
|
|| (!readOnly && isDrafter && isDrafterPhase)
|
||||||
|
|| isApproverChoDuyet
|
||||||
|
|
||||||
const initialManual = (ev.budgetManualName !== null || ev.budgetManualAmount !== null) && !ev.budgetId
|
const initialManual = (ev.budgetManualName !== null || ev.budgetManualAmount !== null) && !ev.budgetId
|
||||||
const [manualMode, setManualMode] = useState(initialManual)
|
const [manualMode, setManualMode] = useState(initialManual)
|
||||||
|
|||||||
@ -418,7 +418,7 @@ export function PeWorkflowPanel({
|
|||||||
<span>
|
<span>
|
||||||
<span className="font-medium">Duyệt thẳng Cấp cuối (skip Bước/Cấp trung gian)</span>
|
<span className="font-medium">Duyệt thẳng Cấp cuối (skip Bước/Cấp trung gian)</span>
|
||||||
<span className="mt-0.5 block text-[11px] text-violet-700/80">
|
<span className="mt-0.5 block text-[11px] text-violet-700/80">
|
||||||
Phiếu sẽ tiến thẳng tới "Đã duyệt" (terminal) — bỏ qua mọi Cấp/Bước còn lại.
|
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.
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
@ -426,8 +426,8 @@ export function PeWorkflowPanel({
|
|||||||
)}
|
)}
|
||||||
{!isCancel && !isSendBack && skipToFinalApprover && (
|
{!isCancel && !isSendBack && skipToFinalApprover && (
|
||||||
<div className="mb-3 rounded border border-amber-300 bg-amber-50 px-3 py-2 text-[11px] text-amber-800">
|
<div className="mb-3 rounded border border-amber-300 bg-amber-50 px-3 py-2 text-[11px] text-amber-800">
|
||||||
⚠ Hành động KHÔNG quay lại được (trừ khi Drafter reset toàn bộ). Phiếu sẽ
|
⚠ Bỏ qua mọi Cấp/Bước trung gian, phiếu chuyển thẳng tới NV cuối. NV cuối
|
||||||
skip qua tất cả Cấp/Bước còn lại và chuyển thẳng "Đã duyệt".
|
vẫn phải ký duyệt thật để phiếu thành "Đã duyệt".
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Label>Ghi chú (tùy chọn)</Label>
|
<Label>Ghi chú (tùy chọn)</Label>
|
||||||
|
|||||||
@ -970,7 +970,13 @@ function BudgetAdjustSection({ ev, readOnly }: { ev: PeDetailBundle; readOnly: b
|
|||||||
&& actorInCurrentLevel
|
&& actorInCurrentLevel
|
||||||
&& approverEditBudgetAllowed
|
&& approverEditBudgetAllowed
|
||||||
|
|
||||||
const canAdjust = !readOnly && (isAdmin || (isDrafter && isDrafterPhase) || isApproverChoDuyet)
|
// S23 t2 bug fix: F4 Approver scope BYPASS readOnly (mirror F3 itemsReadOnly
|
||||||
|
// pattern line 118). Khi admin tick AllowApproverEditBudget cho slot + actor
|
||||||
|
// match + Phase=ChoDuyet → button "Điều chỉnh" enable trong menu Duyệt (readOnly=true)
|
||||||
|
// dù chế độ chỉ-đọc. Drafter + Admin vẫn cần !readOnly (chỉ active từ Workspace).
|
||||||
|
const canAdjust = isAdmin
|
||||||
|
|| (!readOnly && isDrafter && isDrafterPhase)
|
||||||
|
|| isApproverChoDuyet
|
||||||
|
|
||||||
const initialManual = (ev.budgetManualName !== null || ev.budgetManualAmount !== null) && !ev.budgetId
|
const initialManual = (ev.budgetManualName !== null || ev.budgetManualAmount !== null) && !ev.budgetId
|
||||||
const [manualMode, setManualMode] = useState(initialManual)
|
const [manualMode, setManualMode] = useState(initialManual)
|
||||||
|
|||||||
@ -415,7 +415,7 @@ export function PeWorkflowPanel({
|
|||||||
<span>
|
<span>
|
||||||
<span className="font-medium">Duyệt thẳng Cấp cuối (skip Bước/Cấp trung gian)</span>
|
<span className="font-medium">Duyệt thẳng Cấp cuối (skip Bước/Cấp trung gian)</span>
|
||||||
<span className="mt-0.5 block text-[11px] text-violet-700/80">
|
<span className="mt-0.5 block text-[11px] text-violet-700/80">
|
||||||
Phiếu sẽ tiến thẳng tới "Đã duyệt" (terminal) — bỏ qua mọi Cấp/Bước còn lại.
|
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.
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
@ -423,8 +423,8 @@ export function PeWorkflowPanel({
|
|||||||
)}
|
)}
|
||||||
{!isCancel && !isSendBack && skipToFinalApprover && (
|
{!isCancel && !isSendBack && skipToFinalApprover && (
|
||||||
<div className="mb-3 rounded border border-amber-300 bg-amber-50 px-3 py-2 text-[11px] text-amber-800">
|
<div className="mb-3 rounded border border-amber-300 bg-amber-50 px-3 py-2 text-[11px] text-amber-800">
|
||||||
⚠ Hành động KHÔNG quay lại được (trừ khi Drafter reset toàn bộ). Phiếu sẽ
|
⚠ Bỏ qua mọi Cấp/Bước trung gian, phiếu chuyển thẳng tới NV cuối. NV cuối
|
||||||
skip qua tất cả Cấp/Bước còn lại và chuyển thẳng "Đã duyệt".
|
vẫn phải ký duyệt thật để phiếu thành "Đã duyệt".
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Label>Ghi chú (tùy chọn)</Label>
|
<Label>Ghi chú (tùy chọn)</Label>
|
||||||
|
|||||||
@ -467,13 +467,19 @@ 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.
|
// Mig 31 (S23 t1 Plan K) — F2 Approver scope ChoDuyet: skip thẳng tới
|
||||||
// Admin opt-in per slot tại matchingLevel.AllowApproverSkipToFinal. Khi
|
// NV cuối (CEO / last approver). Admin opt-in per slot tại
|
||||||
// Approver tick checkbox "Duyệt thẳng Cấp cuối" trong Workspace + admin
|
// matchingLevel.AllowApproverSkipToFinal. Khi Approver tick checkbox
|
||||||
// đã enable flag cho slot này → bỏ qua mọi Bước/Cấp trung gian còn lại,
|
// "Duyệt thẳng Cấp cuối" + admin enable flag → bỏ qua mọi Bước/Cấp
|
||||||
// set Phase=DaDuyet terminal trực tiếp. Mirror F3+F4 admin opt-in per-
|
// TRUNG GIAN còn lại, advance pointer tới Bước cuối + Cấp cuối (max).
|
||||||
// Approver-slot pattern (Mig 29 + Mig 30) reinforced 3× cumulative.
|
// Phase GIỮ NGUYÊN ChoDuyet — NV cuối vẫn cần ký thật để tiến DaDuyet
|
||||||
// Non-admin + flag off → ConflictException. Admin bypass flag.
|
// (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 (skipToFinal)
|
||||||
{
|
{
|
||||||
if (!isAdmin && !isSystem && !matchingLevel.AllowApproverSkipToFinal)
|
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.");
|
"'Duyệt thẳng Cấp cuối' trong Workflow Designer cho slot này.");
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluation.Phase = PurchaseEvaluationPhase.DaDuyet;
|
// Resolve last Step + last Level (max LevelOrder trong Step cuối)
|
||||||
evaluation.CurrentWorkflowStepIndex = null;
|
var lastStepIdx = steps.Count - 1;
|
||||||
evaluation.CurrentApprovalLevelOrder = null;
|
var lastStep = steps[lastStepIdx];
|
||||||
evaluation.SlaDeadline = null;
|
var lastLevelGroups = lastStep.Levels.OrderBy(l => l.Order).GroupBy(l => l.Order).ToList();
|
||||||
await LogTransitionAsync(
|
var lastLevelMaxOrder = lastLevelGroups.Count == 0 ? 1 : lastLevelGroups.Max(g => g.Key);
|
||||||
evaluation,
|
|
||||||
PurchaseEvaluationPhase.ChoDuyet,
|
// Guard: nếu actor đã ở Cấp cuối Bước cuối thì skipToFinal = no-op
|
||||||
PurchaseEvaluationPhase.DaDuyet,
|
// → fall through advance logic bên dưới (normal advance → DaDuyet).
|
||||||
actorUserId,
|
if (currentIdx == lastStepIdx && currentLevelOrder == lastLevelMaxOrder)
|
||||||
ApprovalDecision.Approve,
|
{
|
||||||
$"[Approver duyệt thẳng Cấp cuối — Bước {currentIdx + 1} Cấp {currentLevelOrder} → DaDuyet] {comment ?? ""}".Trim(),
|
// No-op skip: actor đã ở slot cuối — fall through normal advance
|
||||||
ct);
|
// (sẽ hit branch `nextIdx >= steps.Count` → DaDuyet đúng).
|
||||||
return;
|
}
|
||||||
|
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
|
// Advance: nếu còn cấp tiếp trong Step → levelOrder++; else → next Step + level 1
|
||||||
|
|||||||
Reference in New Issue
Block a user