diff --git a/fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx b/fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx index 211a868..fae853d 100644 --- a/fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx +++ b/fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx @@ -41,6 +41,12 @@ type LevelDto = { approverUserId: string approverUserName: string | null approverEmail: string | null + // Mig 29 (S21 t5) — 5 Allow* options per slot Approver + allowReturnOneLevel: boolean + allowReturnOneStep: boolean + allowReturnToAssignee: boolean + allowReturnToDrafter: boolean + allowApproverEditDetails: boolean } type StepDto = { id: string @@ -60,13 +66,9 @@ type DefinitionDto = { description: string | null isActive: boolean isUserSelectable: boolean // Mig 25 — admin toggle cho user pick - // Mig 28 (S21 t4) — 6 advanced options per workflow version - allowReturnOneLevel: boolean - allowReturnOneStep: boolean - allowReturnToAssignee: boolean - allowReturnToDrafter: boolean // default true backward compat S17 - allowDrafterSkipToFinal: boolean - allowApproverEditDetails: boolean + // Mig 29 (S21 t5) — 6 Allow* options MOVED: + // - 5 flag F1+F3 xuống per slot Level (xem LevelDto) + // - 1 flag F2 AllowDrafterSkipToFinal xuống per User (User Management) activatedAt: string | null createdAt: string steps: StepDto[] @@ -79,7 +81,17 @@ type TypeSummaryDto = { } type LevelOrder = 1 | 2 | 3 -type EditLevelEntry = { order: LevelOrder; approverUserId: string } +type EditLevelEntry = { + order: LevelOrder + approverUserId: string + // Mig 29 (S21 t5) — 5 Allow* per slot (default backward compat S17: chỉ + // AllowReturnToDrafter=true, 4 còn lại false). + allowReturnOneLevel: boolean + allowReturnOneStep: boolean + allowReturnToAssignee: boolean + allowReturnToDrafter: boolean + allowApproverEditDetails: boolean +} type EditStep = { name: string; departmentId: string | null; levelEntries: EditLevelEntry[] } type ApproverUser = { id: string; fullName: string; email: string; departmentId: string | null } @@ -110,16 +122,39 @@ function makeEmptyStep(stepNo: number, deptId: string | null = null): EditStep { } // Clone existing definition: filter Order ∈ {1,2,3}, drop entries vượt giới hạn. +// Mig 29 (S21 t5) — clone 5 Allow* per slot từ existing Level. function copyFromDefinition(d: DefinitionDto): EditStep[] { return d.steps.map(s => ({ name: s.name, departmentId: s.departmentId, levelEntries: s.levels .filter(l => l.order >= 1 && l.order <= MAX_LEVELS_PER_STEP) - .map(l => ({ order: l.order as LevelOrder, approverUserId: l.approverUserId })), + .map(l => ({ + order: l.order as LevelOrder, + approverUserId: l.approverUserId, + allowReturnOneLevel: l.allowReturnOneLevel ?? false, + allowReturnOneStep: l.allowReturnOneStep ?? false, + allowReturnToAssignee: l.allowReturnToAssignee ?? false, + allowReturnToDrafter: l.allowReturnToDrafter ?? true, + allowApproverEditDetails: l.allowApproverEditDetails ?? false, + })), })) } +// Mig 29 — Factory default cho entry mới (admin click "+ Thêm NV"). 5 flag +// default backward compat S17: chỉ AllowReturnToDrafter=true. +function makeDefaultLevelEntry(order: LevelOrder, approverUserId: string): EditLevelEntry { + return { + order, + approverUserId, + allowReturnOneLevel: false, + allowReturnOneStep: false, + allowReturnToAssignee: false, + allowReturnToDrafter: true, + allowApproverEditDetails: false, + } +} + // Filter NV theo Phòng. Nếu Phòng = null → fallback all (chưa chọn phòng). function usersForDept(all: ApproverUser[] | undefined, deptId: string | null): ApproverUser[] { if (!all) return [] @@ -452,14 +487,9 @@ function Designer({ const [description, setDescription] = useState(cloneFrom?.description ?? '') const [steps, setSteps] = useState(initialSteps) - // Mig 28 (S21 t4) — 6 advanced options. Default clone từ cloneFrom (giữ - // config version trước) hoặc backward compat S17 (chỉ Drafter mode). - const [allowReturnOneLevel, setAllowReturnOneLevel] = useState(cloneFrom?.allowReturnOneLevel ?? false) - const [allowReturnOneStep, setAllowReturnOneStep] = useState(cloneFrom?.allowReturnOneStep ?? false) - const [allowReturnToAssignee, setAllowReturnToAssignee] = useState(cloneFrom?.allowReturnToAssignee ?? false) - const [allowReturnToDrafter, setAllowReturnToDrafter] = useState(cloneFrom?.allowReturnToDrafter ?? true) - const [allowDrafterSkipToFinal, setAllowDrafterSkipToFinal] = useState(cloneFrom?.allowDrafterSkipToFinal ?? false) - const [allowApproverEditDetails, setAllowApproverEditDetails] = useState(cloneFrom?.allowApproverEditDetails ?? false) + // Mig 29 (S21 t5) — 6 Allow* options MOVED: + // - 5 flag F1+F3 xuống per Level slot (xem EditLevelEntry, render mỗi Level row) + // - 1 flag F2 AllowDrafterSkipToFinal xuống per User (User Management page) const usersList = useQuery({ queryKey: ['users-for-approver-v2'], @@ -513,19 +543,18 @@ function Designer({ departmentId: s.departmentId, // Mỗi entry → 1 Level row. Multiple rows cùng Order = same Cấp với // N approvers (BE iterate group by Order). + // Mig 29 (S21 t5) — 5 Allow* options per slot Approver. levels: s.levelEntries.map(e => ({ order: e.order, name: `Cấp ${e.order}`, approverUserId: e.approverUserId, + allowReturnOneLevel: e.allowReturnOneLevel, + allowReturnOneStep: e.allowReturnOneStep, + allowReturnToAssignee: e.allowReturnToAssignee, + allowReturnToDrafter: e.allowReturnToDrafter, + allowApproverEditDetails: e.allowApproverEditDetails, })), })), - // Mig 28 (S21 t4) — 6 advanced options - allowReturnOneLevel, - allowReturnOneStep, - allowReturnToAssignee, - allowReturnToDrafter, - allowDrafterSkipToFinal, - allowApproverEditDetails, }) }, onSuccess: () => { @@ -584,116 +613,14 @@ function Designer({ - {/* Mig 28 (S21 t4) — Section Cấu hình nâng cao (F1+F2+F3 advanced options). - 6 checkbox per workflow: 4 mode Trả lại + 1 Skip CEO + 1 Approver edit. */} -
- -

- Bật/tắt mode duyệt mở rộng cho workflow này. Mặc định chỉ "Trả về Người soạn thảo" enabled - (tương thích quy trình cũ). Các mode khác opt-in để audit nghiêm. -

- -
-
-
- Mode Trả lại (Approver chọn khi nhấn ← Trả lại) -
-
- - - - -
-
- -
-
- Drafter gửi duyệt (Workspace "Lưu & Gửi Duyệt") -
- -
- -
-
- Approver chỉnh sửa phiếu -
- -
-
+ {/* Mig 29 (S21 t5) — 6 Allow* options MOVED per-NV: + - 5 flag F1+F3 xuống mỗi Level row (xem level entry inline below). + - 1 flag F2 AllowDrafterSkipToFinal xuống Users page (System → Users). + Section "Cấu hình nâng cao" workflow-level cũ Mig 28 đã DROP. */} +
+ ⓘ Cấu hình quyền duyệt (Trả lại modes + Edit Section 2) đặt RIÊNG cho từng NV ở mỗi + Cấp dưới đây. F2 "Gửi thẳng Cấp cuối" (Drafter) cấu hình ở + User Management (mỗi NV global).
@@ -830,7 +757,7 @@ function Designer({ const firstUser = availableUsers[0] setSteps(steps.map((x, i) => i === idx - ? { ...x, levelEntries: [...x.levelEntries, { order, approverUserId: firstUser.id }] } + ? { ...x, levelEntries: [...x.levelEntries, makeDefaultLevelEntry(order, firstUser.id)] } : x, )) }} @@ -913,6 +840,81 @@ function Designer({
) })} + {/* Mig 29 (S21 t5) — 5 Allow* checkbox inline cho mỗi + NV entry. Mặc định AllowReturnToDrafter=true (S17 + backward compat). Admin tick mở mode khác per slot. */} + {entries.map((entry, ei) => { + const globalIdx = s.levelEntries.findIndex(x => x === entry) + const updateField = (field: keyof EditLevelEntry, value: boolean) => { + setSteps(steps.map((x, i) => + i === idx + ? { + ...x, + levelEntries: x.levelEntries.map((y, j) => + j === globalIdx ? { ...y, [field]: value } : y, + ), + } + : x, + )) + } + return ( +
+
+ Quyền duyệt NV #{ei + 1} +
+
+ + + + + +
+
+ ) + })}
)}