From d15398fafedab766839955d3c1eef827eb1bd566 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Thu, 7 May 2026 15:38:46 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20Domain+FE:=20PE=20th=C3=AAm=20phase?= =?UTF-8?q?=20TraLai=20+=20pencil=20always=20visible=20+=20edit=20gating?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User feedback 2026-05-07: 1. Pencil edit icon LUÔN hiện (không chỉ hover) trong workspace Panel 1 2. Pencil sáng (active brand-color) khi phase editable, xám/disabled khi không 3. Click pencil khi sáng → row sáng + auto-open edit toàn bộ (header + detail) 4. Thêm phase mới "Trả lại" — approver gửi về Drafter sửa (vs Từ chối terminal) 5. Edit chỉ cho 2 trạng thái: Đang soạn thảo + Trả lại (Từ chối + Đã gửi duyệt + Đã duyệt → không edit/thao tác gì) Implementation: ~ Domain/PurchaseEvaluations/PurchaseEvaluationPhase.cs + TraLai = 98 (giữa DaDuyet=7 và TuChoi=99) Comment ghi rõ "approver trả về Drafter sửa, vẫn cho edit, khác TuChoi" ~ types/purchaseEvaluation.ts (× 2 app) + PurchaseEvaluationPhase enum: TraLai = 98 + PurchaseEvaluationPhaseLabel/Color cho TraLai (yellow) + isEditablePhase(phase) helper: true cho DangSoanThao + TraLai + PeDisplayStatus thêm "TraLai" (separate, không gộp DaGuiDuyet) + getPeDisplayStatus map TraLai → "Trả lại" badge yellow ~ components/pe/PeListPanel.tsx (× 2 app) - Pencil icon: bỏ opacity-0 hover-only → LUÔN visible - editable=isEditablePhase(p.phase): bright text-brand-600 + cursor-pointer - !editable: text-slate-300 + cursor-not-allowed + onClick guard ignored - title tooltip rõ ràng "đã gửi duyệt / đã duyệt / từ chối — không sửa được" - Bỏ forcedPhase prop → editableOnly prop (filter client-side cả 2 phase DangSoanThao + TraLai vì BE chưa support multi-phase param) - Khi editableOnly: hiển thị "Lọc cố định: Bản nháp + Trả lại" indicator ~ components/pe/PeDetailTabs.tsx (× 2 app) - Header bar: isDraft → canEditPhase = isEditablePhase(phase) - InfoTab: canEdit = !readOnly && isEditablePhase - BudgetFieldRow: canEdit = !readOnly && isEditablePhase → Đồng nghĩa Drafter sửa được phiếu Trả lại sau approver send back ~ pages/pe/PurchaseEvaluationWorkspacePage.tsx (× 2 app) - PeListPanel forcedPhase=DangSoanThao → editableOnly - Bỏ import PurchaseEvaluationPhase Workflow service BE chưa wire transition → TraLai (defer — user sẽ thêm button "Trả lại" trong PeWorkflowPanel duyệt sau, hoặc dùng API PATCH manual). Phase TraLai chỉ là enum value sẵn sàng FE hiển thị + BE ánh xạ HasConversion. UAT mode: skip verify, push ngay. Co-Authored-By: Claude Opus 4.7 (1M context) --- fe-admin/src/components/pe/PeDetailTabs.tsx | 30 ++++----- fe-admin/src/components/pe/PeListPanel.tsx | 63 ++++++++++++------- .../pe/PurchaseEvaluationWorkspacePage.tsx | 3 +- fe-admin/src/types/purchaseEvaluation.ts | 16 ++++- fe-user/src/components/pe/PeDetailTabs.tsx | 30 ++++----- fe-user/src/components/pe/PeListPanel.tsx | 63 ++++++++++++------- .../pe/PurchaseEvaluationWorkspacePage.tsx | 3 +- fe-user/src/types/purchaseEvaluation.ts | 16 ++++- .../PurchaseEvaluationPhase.cs | 3 +- 9 files changed, 146 insertions(+), 81 deletions(-) diff --git a/fe-admin/src/components/pe/PeDetailTabs.tsx b/fe-admin/src/components/pe/PeDetailTabs.tsx index 0ee33ad..4746bcd 100644 --- a/fe-admin/src/components/pe/PeDetailTabs.tsx +++ b/fe-admin/src/components/pe/PeDetailTabs.tsx @@ -27,6 +27,7 @@ import { PurchaseEvaluationPhaseLabel, PurchaseEvaluationTypeLabel, getPeDisplayStatus, + isEditablePhase, type PeAttachment, type PeChangelog, type PeDepartmentOpinion, @@ -69,7 +70,9 @@ export function PeDetailTabs({ autoEditHeader?: boolean }) { const navigate = useNavigate() - const isDraft = evaluation.phase === PurchaseEvaluationPhase.DangSoanThao + // isDraft renamed → canEditPhase: bao gồm cả TraLai (per user 2026-05-07). + // Header bar action buttons (Sửa header / Xóa) hiện khi phase editable + !readOnly. + const canEditPhase = isEditablePhase(evaluation.phase) const opinionsReadOnly = readOnly || mode === 'workspace' return ( @@ -107,7 +110,7 @@ export function PeDetailTabs({
- {isDraft && !readOnly && ( + {canEditPhase && !readOnly && ( <>
- {list.data?.total ?? 0} + {rows.length}
@@ -100,17 +103,17 @@ export function PeListPanel({ className="pl-8" />
- {forcedPhase === undefined ? ( + {editableOnly ? ( +
+ Lọc cố định: Bản nháp + Trả lại (chỉ phiếu sửa được) +
+ ) : ( - ) : ( -
- Lọc cố định: {PeDisplayStatusLabel[getPeDisplayStatus(forcedPhase)]} -
)} @@ -179,16 +182,30 @@ export function PeListPanel({
✓ Đã tạo HĐ
)} - {/* Edit pencil — visible on hover (chỉ khi onEditClick được truyền) */} - {onEditClick && ( - - )} + {/* Edit pencil — LUÔN visible (user 2026-05-07). + Bright/active khi phase editable (DangSoanThao + TraLai). + Dim/disabled khi phase không edit được (Đã gửi duyệt / Đã duyệt + / Từ chối) — click không có tác dụng. */} + {onEditClick && (() => { + const editable = isEditablePhase(p.phase) + return ( + + ) + })()} ))} diff --git a/fe-admin/src/pages/pe/PurchaseEvaluationWorkspacePage.tsx b/fe-admin/src/pages/pe/PurchaseEvaluationWorkspacePage.tsx index 4870150..ed4ac91 100644 --- a/fe-admin/src/pages/pe/PurchaseEvaluationWorkspacePage.tsx +++ b/fe-admin/src/pages/pe/PurchaseEvaluationWorkspacePage.tsx @@ -20,7 +20,6 @@ import { PeWorkspaceCreateView } from '@/components/pe/PeWorkspaceCreateView' import { api } from '@/lib/api' import { getErrorMessage } from '@/lib/apiError' import { - PurchaseEvaluationPhase, PurchaseEvaluationType, PurchaseEvaluationTypeLabel, type PeDetailBundle, @@ -93,7 +92,7 @@ export function PurchaseEvaluationWorkspacePage() { showCreateButton onCreate={() => setParams({ mode: 'new', id: null, editHeader: null })} onEditClick={id => setParams({ id, mode: null, editHeader: '1' })} - forcedPhase={PurchaseEvaluationPhase.DangSoanThao} + editableOnly /> {/* Panel 2: Empty | Header form | Detail tabs (workspace mode) */} diff --git a/fe-admin/src/types/purchaseEvaluation.ts b/fe-admin/src/types/purchaseEvaluation.ts index 400ba6a..149b05f 100644 --- a/fe-admin/src/types/purchaseEvaluation.ts +++ b/fe-admin/src/types/purchaseEvaluation.ts @@ -24,7 +24,8 @@ export const PurchaseEvaluationPhase = { ChoCEODuyetPA: 5, ChoCEODuyetNCC: 6, DaDuyet: 7, - TuChoi: 99, + TraLai: 98, // approver trả về Drafter sửa — vẫn cho edit + TuChoi: 99, // terminal từ chối — KHÔNG edit } as const export type PurchaseEvaluationPhase = typeof PurchaseEvaluationPhase[keyof typeof PurchaseEvaluationPhase] @@ -36,6 +37,7 @@ export const PurchaseEvaluationPhaseLabel: Record = { 5: 'Chờ CEO duyệt PA', 6: 'Chờ CEO duyệt NCC', 7: 'Đã duyệt', + 98: 'Trả lại', 99: 'Từ chối', } @@ -47,9 +49,17 @@ export const PurchaseEvaluationPhaseColor: Record = { 5: 'bg-fuchsia-100 text-fuchsia-700', 6: 'bg-pink-100 text-pink-700', 7: 'bg-emerald-100 text-emerald-700', + 98: 'bg-yellow-100 text-yellow-800', 99: 'bg-red-100 text-red-700', } +// Phase nào được phép edit phiếu (Drafter sửa header + detail). +// User 2026-05-07: chỉ Đang soạn thảo + Trả lại (Từ chối là terminal, không edit). +export function isEditablePhase(phase: number): boolean { + return phase === PurchaseEvaluationPhase.DangSoanThao + || phase === PurchaseEvaluationPhase.TraLai +} + // Display status meta — gom các phase chi tiết thành 4 nhóm hiển thị end-user // friendly. Workflow timeline + workflow service vẫn dùng phase chi tiết. // User 2026-05-07 chỉnh: @@ -60,6 +70,7 @@ export const PurchaseEvaluationPhaseColor: Record = { export const PeDisplayStatus = { BanNhap: 'BanNhap', DaGuiDuyet: 'DaGuiDuyet', + TraLai: 'TraLai', DaDuyet: 'DaDuyet', TuChoi: 'TuChoi', } as const @@ -68,6 +79,7 @@ export type PeDisplayStatus = typeof PeDisplayStatus[keyof typeof PeDisplayStatu export const PeDisplayStatusLabel: Record = { BanNhap: 'Bản nháp', DaGuiDuyet: 'Đã gửi duyệt', + TraLai: 'Trả lại', DaDuyet: 'Đã duyệt', TuChoi: 'Từ chối', } @@ -75,6 +87,7 @@ export const PeDisplayStatusLabel: Record = { export const PeDisplayStatusColor: Record = { BanNhap: 'bg-slate-100 text-slate-700', DaGuiDuyet: 'bg-amber-100 text-amber-700', + TraLai: 'bg-yellow-100 text-yellow-800', DaDuyet: 'bg-emerald-100 text-emerald-700', TuChoi: 'bg-red-100 text-red-700', } @@ -82,6 +95,7 @@ export const PeDisplayStatusColor: Record = { export function getPeDisplayStatus(phase: number): PeDisplayStatus { if (phase === PurchaseEvaluationPhase.DangSoanThao) return PeDisplayStatus.BanNhap if (phase === PurchaseEvaluationPhase.DaDuyet) return PeDisplayStatus.DaDuyet + if (phase === PurchaseEvaluationPhase.TraLai) return PeDisplayStatus.TraLai if (phase === PurchaseEvaluationPhase.TuChoi) return PeDisplayStatus.TuChoi return PeDisplayStatus.DaGuiDuyet } diff --git a/fe-user/src/components/pe/PeDetailTabs.tsx b/fe-user/src/components/pe/PeDetailTabs.tsx index 0ee33ad..4746bcd 100644 --- a/fe-user/src/components/pe/PeDetailTabs.tsx +++ b/fe-user/src/components/pe/PeDetailTabs.tsx @@ -27,6 +27,7 @@ import { PurchaseEvaluationPhaseLabel, PurchaseEvaluationTypeLabel, getPeDisplayStatus, + isEditablePhase, type PeAttachment, type PeChangelog, type PeDepartmentOpinion, @@ -69,7 +70,9 @@ export function PeDetailTabs({ autoEditHeader?: boolean }) { const navigate = useNavigate() - const isDraft = evaluation.phase === PurchaseEvaluationPhase.DangSoanThao + // isDraft renamed → canEditPhase: bao gồm cả TraLai (per user 2026-05-07). + // Header bar action buttons (Sửa header / Xóa) hiện khi phase editable + !readOnly. + const canEditPhase = isEditablePhase(evaluation.phase) const opinionsReadOnly = readOnly || mode === 'workspace' return ( @@ -107,7 +110,7 @@ export function PeDetailTabs({
- {isDraft && !readOnly && ( + {canEditPhase && !readOnly && ( <>
- {list.data?.total ?? 0} + {rows.length}
@@ -100,17 +103,17 @@ export function PeListPanel({ className="pl-8" />
- {forcedPhase === undefined ? ( + {editableOnly ? ( +
+ Lọc cố định: Bản nháp + Trả lại (chỉ phiếu sửa được) +
+ ) : ( - ) : ( -
- Lọc cố định: {PeDisplayStatusLabel[getPeDisplayStatus(forcedPhase)]} -
)} @@ -179,16 +182,30 @@ export function PeListPanel({
✓ Đã tạo HĐ
)} - {/* Edit pencil — visible on hover (chỉ khi onEditClick được truyền) */} - {onEditClick && ( - - )} + {/* Edit pencil — LUÔN visible (user 2026-05-07). + Bright/active khi phase editable (DangSoanThao + TraLai). + Dim/disabled khi phase không edit được (Đã gửi duyệt / Đã duyệt + / Từ chối) — click không có tác dụng. */} + {onEditClick && (() => { + const editable = isEditablePhase(p.phase) + return ( + + ) + })()} ))} diff --git a/fe-user/src/pages/pe/PurchaseEvaluationWorkspacePage.tsx b/fe-user/src/pages/pe/PurchaseEvaluationWorkspacePage.tsx index 4870150..ed4ac91 100644 --- a/fe-user/src/pages/pe/PurchaseEvaluationWorkspacePage.tsx +++ b/fe-user/src/pages/pe/PurchaseEvaluationWorkspacePage.tsx @@ -20,7 +20,6 @@ import { PeWorkspaceCreateView } from '@/components/pe/PeWorkspaceCreateView' import { api } from '@/lib/api' import { getErrorMessage } from '@/lib/apiError' import { - PurchaseEvaluationPhase, PurchaseEvaluationType, PurchaseEvaluationTypeLabel, type PeDetailBundle, @@ -93,7 +92,7 @@ export function PurchaseEvaluationWorkspacePage() { showCreateButton onCreate={() => setParams({ mode: 'new', id: null, editHeader: null })} onEditClick={id => setParams({ id, mode: null, editHeader: '1' })} - forcedPhase={PurchaseEvaluationPhase.DangSoanThao} + editableOnly /> {/* Panel 2: Empty | Header form | Detail tabs (workspace mode) */} diff --git a/fe-user/src/types/purchaseEvaluation.ts b/fe-user/src/types/purchaseEvaluation.ts index 400ba6a..149b05f 100644 --- a/fe-user/src/types/purchaseEvaluation.ts +++ b/fe-user/src/types/purchaseEvaluation.ts @@ -24,7 +24,8 @@ export const PurchaseEvaluationPhase = { ChoCEODuyetPA: 5, ChoCEODuyetNCC: 6, DaDuyet: 7, - TuChoi: 99, + TraLai: 98, // approver trả về Drafter sửa — vẫn cho edit + TuChoi: 99, // terminal từ chối — KHÔNG edit } as const export type PurchaseEvaluationPhase = typeof PurchaseEvaluationPhase[keyof typeof PurchaseEvaluationPhase] @@ -36,6 +37,7 @@ export const PurchaseEvaluationPhaseLabel: Record = { 5: 'Chờ CEO duyệt PA', 6: 'Chờ CEO duyệt NCC', 7: 'Đã duyệt', + 98: 'Trả lại', 99: 'Từ chối', } @@ -47,9 +49,17 @@ export const PurchaseEvaluationPhaseColor: Record = { 5: 'bg-fuchsia-100 text-fuchsia-700', 6: 'bg-pink-100 text-pink-700', 7: 'bg-emerald-100 text-emerald-700', + 98: 'bg-yellow-100 text-yellow-800', 99: 'bg-red-100 text-red-700', } +// Phase nào được phép edit phiếu (Drafter sửa header + detail). +// User 2026-05-07: chỉ Đang soạn thảo + Trả lại (Từ chối là terminal, không edit). +export function isEditablePhase(phase: number): boolean { + return phase === PurchaseEvaluationPhase.DangSoanThao + || phase === PurchaseEvaluationPhase.TraLai +} + // Display status meta — gom các phase chi tiết thành 4 nhóm hiển thị end-user // friendly. Workflow timeline + workflow service vẫn dùng phase chi tiết. // User 2026-05-07 chỉnh: @@ -60,6 +70,7 @@ export const PurchaseEvaluationPhaseColor: Record = { export const PeDisplayStatus = { BanNhap: 'BanNhap', DaGuiDuyet: 'DaGuiDuyet', + TraLai: 'TraLai', DaDuyet: 'DaDuyet', TuChoi: 'TuChoi', } as const @@ -68,6 +79,7 @@ export type PeDisplayStatus = typeof PeDisplayStatus[keyof typeof PeDisplayStatu export const PeDisplayStatusLabel: Record = { BanNhap: 'Bản nháp', DaGuiDuyet: 'Đã gửi duyệt', + TraLai: 'Trả lại', DaDuyet: 'Đã duyệt', TuChoi: 'Từ chối', } @@ -75,6 +87,7 @@ export const PeDisplayStatusLabel: Record = { export const PeDisplayStatusColor: Record = { BanNhap: 'bg-slate-100 text-slate-700', DaGuiDuyet: 'bg-amber-100 text-amber-700', + TraLai: 'bg-yellow-100 text-yellow-800', DaDuyet: 'bg-emerald-100 text-emerald-700', TuChoi: 'bg-red-100 text-red-700', } @@ -82,6 +95,7 @@ export const PeDisplayStatusColor: Record = { export function getPeDisplayStatus(phase: number): PeDisplayStatus { if (phase === PurchaseEvaluationPhase.DangSoanThao) return PeDisplayStatus.BanNhap if (phase === PurchaseEvaluationPhase.DaDuyet) return PeDisplayStatus.DaDuyet + if (phase === PurchaseEvaluationPhase.TraLai) return PeDisplayStatus.TraLai if (phase === PurchaseEvaluationPhase.TuChoi) return PeDisplayStatus.TuChoi return PeDisplayStatus.DaGuiDuyet } diff --git a/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationPhase.cs b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationPhase.cs index bc6dcd0..f18dd54 100644 --- a/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationPhase.cs +++ b/src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluationPhase.cs @@ -16,5 +16,6 @@ public enum PurchaseEvaluationPhase ChoCEODuyetPA = 5, // chỉ B (duyệt phương án trước) ChoCEODuyetNCC = 6, // chung cả A & B — duyệt chọn đơn vị DaDuyet = 7, // terminal thành công - TuChoi = 99, // terminal từ chối + TraLai = 98, // approver trả về cho Drafter sửa (vẫn cho edit, khác TuChoi) + TuChoi = 99, // terminal từ chối — KHÔNG cho edit/thao tác }