From b275893c8f16e12e088b4073ef76636658945cf2 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Mon, 10 Aug 2026 19:46:33 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20Contract:=20W5+W6FE=20card=20Ngu?= =?UTF-8?q?=E1=BB=93n-g=E1=BB=91c=20KHKK=20+=20panel=20duy=E1=BB=87t=202-n?= =?UTF-8?q?=C3=BAt=20mirror=20KhkkWorkflowPanel=20(dual-render=20V1)=20+?= =?UTF-8?q?=20Designer=20type-3=20family-slice=20+=20ru=E1=BB=99t=20pre-fi?= =?UTF-8?q?ll=20=C4=91o-khai?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .../contracts/ContractDetailContent.tsx | 150 ++++++- .../contracts/ContractWorkflowPanel.tsx | 396 ++++++++++++++++++ fe-admin/src/lib/contractWorkflowFamily.ts | 80 ++++ .../pages/contracts/ContractCreatePage.tsx | 56 ++- .../pages/system/ApprovalWorkflowsV2Page.tsx | 106 +++-- .../contracts/ContractDetailContent.tsx | 150 ++++++- .../contracts/ContractWorkflowPanel.tsx | 396 ++++++++++++++++++ .../pages/contracts/ContractCreatePage.tsx | 56 ++- 8 files changed, 1354 insertions(+), 36 deletions(-) create mode 100644 fe-admin/src/components/contracts/ContractWorkflowPanel.tsx create mode 100644 fe-admin/src/lib/contractWorkflowFamily.ts create mode 100644 fe-user/src/components/contracts/ContractWorkflowPanel.tsx diff --git a/fe-admin/src/components/contracts/ContractDetailContent.tsx b/fe-admin/src/components/contracts/ContractDetailContent.tsx index cd5ce88..42009bd 100644 --- a/fe-admin/src/components/contracts/ContractDetailContent.tsx +++ b/fe-admin/src/components/contracts/ContractDetailContent.tsx @@ -4,12 +4,16 @@ // approval history live separately in WorkflowHistoryPanel (Panel 3). import { useState, type FormEvent } from 'react' import { useMutation, useQueryClient } from '@tanstack/react-query' -import { ArrowLeft, CheckCircle2, MessageSquare, XCircle, ListChecks } from 'lucide-react' +import { Link } from 'react-router-dom' +import { ArrowLeft, CheckCircle2, MessageSquare, XCircle, ListChecks, GitBranch } from 'lucide-react' import { toast } from 'sonner' import { PhaseBadge } from '@/components/PhaseBadge' import { SlaTimer } from '@/components/SlaTimer' import { ContractAttachmentsSection } from '@/components/ContractAttachmentsSection' import { ContractDetailsTab } from '@/components/contracts/ContractDetailsTab' +// [W6 S187] Màn duyệt V2-trạm — CHỈ dùng cho HĐ đã pin `approvalWorkflowId` (xem khối +// dual-render ở dưới). HĐ V1 legacy không import nhánh này lúc chạy. +import { ContractWorkflowPanel } from '@/components/contracts/ContractWorkflowPanel' import { Button } from '@/components/ui/Button' import { Select } from '@/components/ui/Select' import { Textarea } from '@/components/ui/Textarea' @@ -23,6 +27,10 @@ import { type ContractDetail, } from '@/types/contracts' import { ContractTypeLabel } from '@/types/forms' +// [W5 S187] Nhãn 8 nhóm duyệt KHKK — IMPORT, KHÔNG chép chuỗi. Map này là single-source +// (`types/khkk.ts:140-149`, đã đối chiếu từng ký tự với nhãn BE seed @K4a); đẻ map thứ 2 ở +// đây là phá đúng cái acceptance nó phục vụ. +import { KHKK_APPROVAL_GROUP_LABELS } from '@/types/khkk' const fmt = (s: string) => new Date(s).toLocaleString('vi-VN') const fmtMoney = (v: number) => v.toLocaleString('vi-VN') + ' VND' @@ -71,6 +79,34 @@ export function ContractDetailContent({ const availableTargets = c.workflow?.nextPhases ?? [] + // ===== [W6 S187 · YC-023 GĐ3] DUAL-RENDER V2-trạm ⟂ V1-legacy ========================== + // 🔴 `approvalWorkflowId != null` là ĐÚNG cờ phân nhánh — cùng cờ mà BE dùng để chọn đường + // duyệt (`ContractWorkflowService.cs:111` `if (contract.ApprovalWorkflowId is Guid awId)`) + // VÀ cùng cờ mục "Ý kiến cấp duyệt (Quy trình V2)" bên dưới đã dùng từ S29. Một cờ, ba nơi + // đọc, không đẻ khái niệm "HĐ mới" thứ hai. + // • V2 ⇒ `ContractWorkflowPanel` (sơ đồ Bước→Cấp + banner lượt-ai + 2 nút Duyệt/Trả lại, + // có gating theo Cấp đang chờ). + // • V1 legacy (không pin — 7 HĐ prod chạy nhánh này, `ContractWorkflowService.cs:110`) + // ⇒ GIỮ NGUYÊN 2 nút "Yêu cầu sửa"/"Duyệt → tiếp" ở header + Dialog chọn phase. + // 2 khối là THAY THẾ nhau, không chồng: để cả hai thì cùng một HĐ V2 có 2 đường bấm duyệt + // (đường header không nói được đang ở Bước/Cấp nào, cũng không gating theo người) — mâu + // thuẫn hiển thị chứ không phải tiện thêm. + const isV2 = c.approvalWorkflowId != null + + // ===== [W5 S187 · YC-023 GĐ3] NGUỒN GỐC KHKK (GĐ2) ===================================== + // 🔴 Điều kiện render là `!= null` (LOOSE — bắt cả `undefined`) CÓ CHỦ ĐÍCH: `types/contracts.ts:174-182` + // khai 2 nghĩa KHÁC NHAU — `undefined` = payload/cache TanStack CHƯA có W2; `null` = BE nói + // "HĐ này không có nguồn KHKK". Cả 2 đều KHÔNG có gì để vẽ, nhưng KHÔNG được suy `undefined` + // thành "HĐ tạo tay" ở bất kỳ chỗ nào khác. + // Nguồn dữ liệu: BE reverse-join `ContractSigningPlanLines WHERE ContractId = id` + // (`ContractFeatures.cs:720-748`) — `Contract` KHÔNG có con-trỏ ngược sang kế hoạch. + const src = c.source + const srcGroup = src?.approvalGroup ?? null + // Nhãn nhóm: tra map single-source. Truthy-check theo đúng khuôn `khkkGroupMenuLabel` + // (`types/khkk.ts:159-162`) vì `Record` trả `string` cho MỌI số — nhóm lạ + // (ngoài 1..8, vd dữ liệu SQL thô) phải hiện "chưa có trong danh mục" thay vì ô trống CÂM. + const srcGroupLabel = srcGroup != null ? KHKK_APPROVAL_GROUP_LABELS[srcGroup] : undefined + function openAction(decisionType: number) { const targets = c.workflow?.nextPhases ?? [] const defaultTarget = decisionType === ApprovalDecision.Reject @@ -102,7 +138,9 @@ export function ContractDetailContent({ - {availableTargets.length > 0 && ( + {/* [W6 S187] `!isV2` = nửa V1 của dual-render. HĐ pin quy trình V2 thao tác duyệt ở + `ContractWorkflowPanel` bên dưới (đúng Bước/Cấp + đúng người), không ở đây. */} + {!isV2 && availableTargets.length > 0 && (
+ + + + +
+ + )} + + {action !== null && ( + setAction(null)} + title={action === 'approve' ? '✓ Duyệt hợp đồng' : '← Trả lại người soạn sửa'} + footer={<> + + + } + > + {action === 'return' && ( +
+ HĐ về trạng thái “Trả lại”. Người soạn sửa xong gửi lại thì quy trình chạy + từ Bước 1 · Cấp 1. +
+ )} + +