[CLAUDE] FE-PE: S21 t5 Chunk C — eOffice read currentLevelOptions + drafterAllowSkipToFinal (per-NV) mirror 2 app
Types refactor `fe-{admin,user}/src/types/purchaseEvaluation.ts`:
- `ApprovalWorkflowOptions` REMOVE allowDrafterSkipToFinal (F2 đã move per-User).
Còn 5 flag (F1 4 mode + F3 EditDetails).
- `PeDetailBundle`:
- RENAME `workflowOptions` → `currentLevelOptions` (clearer semantic per-slot)
- ADD `drafterAllowSkipToFinal: boolean` (BE resolve từ DrafterUserId → User entity)
PeWorkflowPanel.tsx (mirror 2 app):
- RENAME local var `wfOptions` → `levelOptions`
- READ `evaluation.currentLevelOptions` (Cấp hiện tại)
- 4 mode radio render conditional theo levelOptions.allowReturnXxx (unchanged
logic, just rename source)
PeDetailTabs.tsx (mirror 2 app):
- F3 approverEditMode: READ `evaluation.currentLevelOptions?.allowApproverEditDetails`
thay vì workflowOptions.allowApproverEditDetails (semantic per-NV slot)
- F2 allowSkipToFinal: READ `evaluation.drafterAllowSkipToFinal` thay vì
workflowOptions.allowDrafterSkipToFinal (semantic per-Drafter user)
Backward compat verified:
- Phiếu cũ trước Mig 29 vẫn return currentLevelOptions populated (BE backfill
Mig 29 đã copy 5 Allow* per Level)
- drafterAllowSkipToFinal: BE backfill chỉ TRUE cho user từng Drafter PE link
workflow.AllowDrafterSkipToFinal=true (preserve admin config S21 t4)
- Phiếu V1 legacy: currentLevelOptions=null → FE fallback chỉ Drafter mode
Verify:
- npm run build × 2 app pass (fe-user 450ms + fe-admin 439ms, cache hot)
- 0 TS6 err, warning chunk size pre-existing
Pending Chunk D: Docs (schema-diagram §14 update + STATUS + HANDOFF + session log).
Note: User Management page chưa có F2 checkbox UX (defer commit sau khi admin
UAT request — BE field đã có, FE chỉ cần thêm 1 toggle vào UserEdit dialog).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -108,16 +108,17 @@ export function PeDetailTabs({
|
||||
const actorMatchesLevel = isAdmin
|
||||
|| (currentUser?.id != null && v2Approvers.some(a => a.userId === currentUser.id))
|
||||
const approverEditMode = evaluation.phase === PurchaseEvaluationPhase.ChoDuyet
|
||||
&& (evaluation.workflowOptions?.allowApproverEditDetails ?? false)
|
||||
// Mig 29 (S21 t5) — read F3 từ currentLevelOptions (per-NV slot)
|
||||
&& (evaluation.currentLevelOptions?.allowApproverEditDetails ?? false)
|
||||
&& actorMatchesLevel
|
||||
const itemsReadOnly = readOnly && !approverEditMode
|
||||
|
||||
// "Lưu & Gửi Duyệt" workspace mode (user 2026-05-07): trigger transition
|
||||
// sang phase tiếp theo (= Đã gửi duyệt). nextPhases[0] thường là ChoPurchasing
|
||||
// (skip TuChoi). Sau success → toast + invalidate + onBack đóng workspace.
|
||||
// Mig 28 (S21 t4) — F2: Drafter skip thẳng Cấp cuối. Workflow phải bật flag.
|
||||
// Mig 29 (S21 t5) — F2: per-Drafter user flag (User Management page).
|
||||
const [skipToFinal, setSkipToFinal] = useState(false)
|
||||
const allowSkipToFinal = evaluation.workflowOptions?.allowDrafterSkipToFinal ?? false
|
||||
const allowSkipToFinal = evaluation.drafterAllowSkipToFinal ?? false
|
||||
|
||||
const submitForApproval = useMutation({
|
||||
mutationFn: async (opts: { skipToFinal: boolean }) => {
|
||||
|
||||
@ -41,8 +41,8 @@ export function PeWorkflowPanel({
|
||||
const { user: currentUser } = useAuth()
|
||||
const isAdmin = currentUser?.roles?.includes('Admin') ?? false
|
||||
|
||||
// Mig 28 — F1 workflow options. Null nếu V1 legacy → fallback chỉ "Trả về Drafter".
|
||||
const wfOptions = evaluation.workflowOptions
|
||||
// Mig 29 (S21 t5) — F1 options per-Level (Cấp Approver hiện tại).
|
||||
const levelOptions = evaluation.currentLevelOptions
|
||||
// List approvers đã ký (cho mode Assignee dropdown pick)
|
||||
const signedApprovers = (evaluation.levelOpinions ?? [])
|
||||
.map(o => ({ userId: o.approverUserId, fullName: o.approverFullName ?? 'NV' }))
|
||||
@ -307,15 +307,15 @@ export function PeWorkflowPanel({
|
||||
<>
|
||||
{/* Mig 28 (S21 t4) — F1 mode picker khi Trả lại. Show modes
|
||||
enabled per workflow.options. Default Drafter (S17 fallback). */}
|
||||
{(wfOptions?.allowReturnOneLevel
|
||||
|| wfOptions?.allowReturnOneStep
|
||||
|| wfOptions?.allowReturnToAssignee
|
||||
|| wfOptions?.allowReturnToDrafter
|
||||
|| !wfOptions) && (
|
||||
{(levelOptions?.allowReturnOneLevel
|
||||
|| levelOptions?.allowReturnOneStep
|
||||
|| levelOptions?.allowReturnToAssignee
|
||||
|| levelOptions?.allowReturnToDrafter
|
||||
|| !levelOptions) && (
|
||||
<div className="mb-3 space-y-1.5">
|
||||
<Label className="text-[12px]">Chọn cách Trả lại</Label>
|
||||
<div className="space-y-1">
|
||||
{(wfOptions?.allowReturnOneLevel) && (
|
||||
{(levelOptions?.allowReturnOneLevel) && (
|
||||
<label className="flex items-start gap-2 rounded border border-amber-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
||||
<input
|
||||
type="radio"
|
||||
@ -329,7 +329,7 @@ export function PeWorkflowPanel({
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
{(wfOptions?.allowReturnOneStep) && (
|
||||
{(levelOptions?.allowReturnOneStep) && (
|
||||
<label className="flex items-start gap-2 rounded border border-amber-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
||||
<input
|
||||
type="radio"
|
||||
@ -343,7 +343,7 @@ export function PeWorkflowPanel({
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
{(wfOptions?.allowReturnToAssignee) && (
|
||||
{(levelOptions?.allowReturnToAssignee) && (
|
||||
<label className="flex items-start gap-2 rounded border border-amber-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
||||
<input
|
||||
type="radio"
|
||||
@ -369,7 +369,7 @@ export function PeWorkflowPanel({
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
{(wfOptions?.allowReturnToDrafter !== false) && (
|
||||
{(levelOptions?.allowReturnToDrafter !== false) && (
|
||||
<label className="flex items-start gap-2 rounded border border-amber-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
||||
<input
|
||||
type="radio"
|
||||
|
||||
@ -347,13 +347,13 @@ export type PeDepartmentApproval = {
|
||||
isBypassed: boolean
|
||||
}
|
||||
|
||||
// Mig 28 (S21 t4) — 6 advanced options của workflow pin.
|
||||
// Mig 29 (S21 t5) — 5 Allow* options của Cấp Approver hiện tại (per-NV slot).
|
||||
// F2 (Drafter skip) MOVED sang per-User (xem PeDetailBundle.drafterAllowSkipToFinal).
|
||||
export type ApprovalWorkflowOptions = {
|
||||
allowReturnOneLevel: boolean
|
||||
allowReturnOneStep: boolean
|
||||
allowReturnToAssignee: boolean
|
||||
allowReturnToDrafter: boolean
|
||||
allowDrafterSkipToFinal: boolean
|
||||
allowApproverEditDetails: boolean
|
||||
}
|
||||
|
||||
@ -397,8 +397,10 @@ export type PeDetailBundle = {
|
||||
approvalWorkflowCode: string | null
|
||||
approvalWorkflowName: string | null
|
||||
approvalWorkflowVersion: number | null
|
||||
// Mig 28 (S21 t4) — 6 advanced options của workflow pin. Null nếu V1 legacy.
|
||||
workflowOptions: ApprovalWorkflowOptions | null
|
||||
// Mig 29 (S21 t5) — 5 Allow* options của Cấp hiện tại (per-NV slot).
|
||||
currentLevelOptions: ApprovalWorkflowOptions | null
|
||||
// Mig 29 — F2 per-Drafter flag
|
||||
drafterAllowSkipToFinal: boolean
|
||||
// Mig 24 — info Bước/Cấp đang chờ duyệt (chỉ populate khi pin V2 + Phase=ChoDuyet)
|
||||
currentApproval: PeCurrentApproval | null
|
||||
// Mig 24 — full flow snapshot (Bước → Cấp → NV) với Status per level
|
||||
|
||||
@ -110,15 +110,16 @@ export function PeDetailTabs({
|
||||
const actorMatchesLevel = isAdmin
|
||||
|| (currentUser?.id != null && v2Approvers.some(a => a.userId === currentUser.id))
|
||||
const approverEditMode = evaluation.phase === PurchaseEvaluationPhase.ChoDuyet
|
||||
&& (evaluation.workflowOptions?.allowApproverEditDetails ?? false)
|
||||
// Mig 29 (S21 t5) — read F3 từ currentLevelOptions (per-NV slot)
|
||||
&& (evaluation.currentLevelOptions?.allowApproverEditDetails ?? false)
|
||||
&& actorMatchesLevel
|
||||
// itemsReadOnly = readOnly trừ khi approver mode F3 mở
|
||||
const itemsReadOnly = readOnly && !approverEditMode
|
||||
|
||||
// Mig 28 (S21 t4) — F2: Drafter skip thẳng Cấp cuối. Workflow phải bật flag.
|
||||
// Default false (gửi tuần tự như cũ). Sync state với confirm dialog handler.
|
||||
// Mig 29 (S21 t5) — F2: per-Drafter user flag (KHÔNG còn workflow-level).
|
||||
// Admin cấu hình ở User Management page → BE resolve qua DrafterUserId.
|
||||
const [skipToFinal, setSkipToFinal] = useState(false)
|
||||
const allowSkipToFinal = evaluation.workflowOptions?.allowDrafterSkipToFinal ?? false
|
||||
const allowSkipToFinal = evaluation.drafterAllowSkipToFinal ?? false
|
||||
|
||||
// "Lưu & Gửi Duyệt" workspace mode (user 2026-05-07): trigger transition
|
||||
// sang phase tiếp theo (= Đã gửi duyệt). nextPhases[0] thường là ChoPurchasing
|
||||
|
||||
@ -41,8 +41,9 @@ export function PeWorkflowPanel({
|
||||
const { user: currentUser } = useAuth()
|
||||
const isAdmin = currentUser?.roles?.includes('Admin') ?? false
|
||||
|
||||
// Mig 28 — F1 workflow options. Null nếu V1 legacy → fallback chỉ "Trả về Drafter".
|
||||
const wfOptions = evaluation.workflowOptions
|
||||
// Mig 29 (S21 t5) — F1 options per-Level (Cấp Approver hiện tại). Null nếu
|
||||
// V1 legacy hoặc pointer chưa init → fallback chỉ "Trả về Drafter".
|
||||
const levelOptions = evaluation.currentLevelOptions
|
||||
// List approvers đã ký (cho mode Assignee dropdown pick)
|
||||
const signedApprovers = (evaluation.levelOpinions ?? [])
|
||||
.map(o => ({ userId: o.approverUserId, fullName: o.approverFullName ?? 'NV' }))
|
||||
@ -302,15 +303,15 @@ export function PeWorkflowPanel({
|
||||
<>
|
||||
{/* Mig 28 (S21 t4) — F1 mode picker khi Trả lại. Show modes
|
||||
enabled per workflow.options. Default Drafter (S17 fallback). */}
|
||||
{(wfOptions?.allowReturnOneLevel
|
||||
|| wfOptions?.allowReturnOneStep
|
||||
|| wfOptions?.allowReturnToAssignee
|
||||
|| wfOptions?.allowReturnToDrafter
|
||||
|| !wfOptions) && (
|
||||
{(levelOptions?.allowReturnOneLevel
|
||||
|| levelOptions?.allowReturnOneStep
|
||||
|| levelOptions?.allowReturnToAssignee
|
||||
|| levelOptions?.allowReturnToDrafter
|
||||
|| !levelOptions) && (
|
||||
<div className="mb-3 space-y-1.5">
|
||||
<Label className="text-[12px]">Chọn cách Trả lại</Label>
|
||||
<div className="space-y-1">
|
||||
{(wfOptions?.allowReturnOneLevel) && (
|
||||
{(levelOptions?.allowReturnOneLevel) && (
|
||||
<label className="flex items-start gap-2 rounded border border-amber-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
||||
<input
|
||||
type="radio"
|
||||
@ -324,7 +325,7 @@ export function PeWorkflowPanel({
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
{(wfOptions?.allowReturnOneStep) && (
|
||||
{(levelOptions?.allowReturnOneStep) && (
|
||||
<label className="flex items-start gap-2 rounded border border-amber-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
||||
<input
|
||||
type="radio"
|
||||
@ -338,7 +339,7 @@ export function PeWorkflowPanel({
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
{(wfOptions?.allowReturnToAssignee) && (
|
||||
{(levelOptions?.allowReturnToAssignee) && (
|
||||
<label className="flex items-start gap-2 rounded border border-amber-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
||||
<input
|
||||
type="radio"
|
||||
@ -364,7 +365,7 @@ export function PeWorkflowPanel({
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
{(wfOptions?.allowReturnToDrafter !== false) && (
|
||||
{(levelOptions?.allowReturnToDrafter !== false) && (
|
||||
<label className="flex items-start gap-2 rounded border border-amber-200 bg-white px-2 py-1.5 text-[12px] hover:bg-amber-50/40">
|
||||
<input
|
||||
type="radio"
|
||||
|
||||
@ -344,13 +344,14 @@ export type PeDepartmentApproval = {
|
||||
isBypassed: boolean
|
||||
}
|
||||
|
||||
// Mig 28 (S21 t4) — 6 advanced options của workflow pin.
|
||||
// Mig 29 (S21 t5) — 5 Allow* options của Cấp Approver hiện tại (per-NV slot).
|
||||
// FE filter Trả lại dropdown + Edit Section 2 enabled theo flag của slot này.
|
||||
// F2 (Drafter skip) MOVED sang per-User (xem PeDetailBundle.drafterAllowSkipToFinal).
|
||||
export type ApprovalWorkflowOptions = {
|
||||
allowReturnOneLevel: boolean
|
||||
allowReturnOneStep: boolean
|
||||
allowReturnToAssignee: boolean
|
||||
allowReturnToDrafter: boolean
|
||||
allowDrafterSkipToFinal: boolean
|
||||
allowApproverEditDetails: boolean
|
||||
}
|
||||
|
||||
@ -394,9 +395,13 @@ export type PeDetailBundle = {
|
||||
approvalWorkflowCode: string | null
|
||||
approvalWorkflowName: string | null
|
||||
approvalWorkflowVersion: number | null
|
||||
// Mig 28 (S21 t4) — 6 advanced options của workflow pin. Null nếu V1 legacy.
|
||||
// FE filter Trả lại dropdown + Skip submit + Edit Section 2 conditional.
|
||||
workflowOptions: ApprovalWorkflowOptions | null
|
||||
// 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 pointer chưa init. FE render Trả lại dropdown + Edit
|
||||
// Section 2 conditional theo flag của slot Approver đang duyệt.
|
||||
currentLevelOptions: ApprovalWorkflowOptions | null
|
||||
// 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".
|
||||
drafterAllowSkipToFinal: boolean
|
||||
// Mig 24 — info Bước/Cấp đang chờ duyệt (chỉ populate khi pin V2 + Phase=ChoDuyet)
|
||||
currentApproval: PeCurrentApproval | null
|
||||
// Mig 24 — full flow snapshot (Bước → Cấp → NV) với Status per level
|
||||
|
||||
Reference in New Issue
Block a user