[CLAUDE] Contract: UAT S174 vong-3 — 5 chinh KHKK theo anh chot (FE-only, 0-mig)

1. Ho thu: GO `PipelineTreePanel` khoi `/inbox` — anh: "cho hop thu vut panel nay vao lam j,
   tap trung vao KHKK thoi". Ho thu = hang-doi-viec-cua-toi, cay toan trinh = ban-do-toan-he
   => sai the loai. Cho trong nay tra ve dung vai: cho quy trinh duyet cua HD dang chon.
   🔸 HAI trang GD3/GD4 con lai (`MyContractsPage` · `HardCopiesPage`) GIU NGUYEN — anh chi
   dich danh Ho thu, khong noi go het; khong tu suy rong. (Lineage: panel vao day @S168
   GD3-D theo owner slot (63) + "mo rong = ca 3 trang"; nay owner thu lai DUNG trang nay.)

2. Panel KHKK: BO nut "+ Them moi". Man nay KHONG co duong "tao tu so 0" — ke hoach LUON
   sinh tu mot phieu Duyet NCC, ma cac phieu du dieu kien da nam nguyen o tang tren => nut
   chi xoa form chu khong tao them duoc gi. Khac CO CHU DICH voi `PeListPanel`.

3. Tang duoi CHI giu ke hoach DANG XU LY — an phieu `DaDuyet` (anh: "da duyet bo di luon").
   🔴 KHAI GIOI-HAN: loc o FE vi endpoint chi nhan `phase` DON, khong co tham so "loai tru"
   => (a) KHONG dung `total` cua server lam so hien thi (da gom phieu DaDuyet) — dem tu mang
   da loc; (b) cua-so 30 phieu moi nhat co the TOAN DaDuyet => tang duoi rong du con phieu
   nhap cu hon. Chap nhan o UAT; muon chuan thi BE them tham so loai-tru.
   Doi luon dong chu "x/total" -> "Da an ke hoach Da duyet": ti so cu doc thanh "dang cat
   bot" trong khi that ra la "dang loc bo".

4. Nhom duyet + Quy trinh duyet GAN CUNG theo leaf menu — anh: "gan cung luon theo tung nhom
   ko cho User dieu chinh cho nay luon".
   - `approvalGroup` khong con la state, DAN XUAT thang tu `?group=n`. Bo luon khoi dong-bo
     `lastPresetGroup` + ham `firstFreeGroup()` (chung chi ton tai de tu nhay nhom = dung
     hanh-vi vua bi cam).
   - Quy trinh: match theo ma tu NGUON BE, khong suy tu man hinh — `DbInitializer.cs:609`
     `var code = $"KHKK-N{n}"`. Khong khop => CHAN tao + noi ro thieu ma nao, TUYET DOI
     khong tu roi ve quy trinh nhom khac (gan nham = phieu di sai duong duyet).
   - UI: hien gia-tri co dinh, CO Y khong dung `<Select disabled>` (o xam van trong nhu
     "bam duoc nhung dang khoa" va van trong tab-order).

5. Panel giua: GO Section 5 "Y kien cap duyet" (anh: "bo luon cai nay ra luon"). So-do
   Buoc/Cap + chu ky da song o Panel 3 => de ca hai la ke mot chuyen hai lan tren cung man.
   DTO giu nguyen (`levelOpinions`/`workflowSteps` panel 3 van dung) — chi bo cho VE.

Verify: build 2 app PASS; mirror SHA256 3/3 cap KHOP.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-08-05 15:50:17 +07:00
parent b7c6ee5a66
commit f86f30d291
7 changed files with 207 additions and 416 deletions

View File

@ -31,6 +31,7 @@ import { cn } from '@/lib/cn'
import {
KHKK_PHASE_BADGE,
KHKK_PHASE_LABELS,
KhkkPhase,
type KhkkListItemDto,
type KhkkPeCandidateDto,
type PagedResult,
@ -78,7 +79,6 @@ export function KhkkWorkListPanel({
onPickPlan,
selectedPeId,
selectedPlanId,
footer,
}: {
/** Bấm PE ở tầng trên → host nạp phiếu đó vào form tạo (Ở LẠI màn, không điều hướng). */
onPickPe: (peId: string) => void
@ -87,8 +87,6 @@ export function KhkkWorkListPanel({
onPickPlan: (planId: string) => void
selectedPeId?: string | null
selectedPlanId?: string | null
/** Nút "+ Thêm mới" dính đáy — mirror `PeListPanel` của màn Duyệt NCC — Thao tác. */
footer?: React.ReactNode
}) {
const { can } = usePermission()
// 🔴 ĐÚNG key policy của endpoint — KHÔNG dùng `Khkk_*` (đó là key MENU, tầng hiển thị).
@ -126,7 +124,14 @@ export function KhkkWorkListPanel({
else byProject.set(key, [pe])
}
const planItems = plans.data?.items ?? []
// [S174 owner] "đã duyệt bỏ đi luôn" — tầng dưới chỉ giữ kế hoạch CHƯA xong việc
// (nháp / chờ duyệt / trả lại / từ chối). Phiếu `DaDuyet` đã hết việc ở màn Thao tác.
// 🔴 KHAI GIỚI-HẠN (lọc ở FE): endpoint chỉ nhận `phase` ĐƠN, không có tham số "loại trừ"
// ⇒ (1) `plans.data.total` của server ĐÃ GỒM phiếu DaDuyet nên KHÔNG được dùng làm số
// hiển thị — đếm từ chính mảng đã lọc; (2) cửa-sổ 30 phiếu mới nhất có thể TOÀN DaDuyet,
// khi đó tầng dưới rỗng dù còn phiếu nháp cũ hơn. Chấp nhận ở UAT; muốn chuẩn thì BE thêm
// tham số loại-trừ rồi lọc SERVER-SIDE.
const planItems = (plans.data?.items ?? []).filter((k) => k.phase !== KhkkPhase.DaDuyet)
return (
<aside className="flex min-h-0 flex-col overflow-hidden border-r border-slate-200 bg-white">
@ -209,8 +214,9 @@ export function KhkkWorkListPanel({
<div className="shrink-0 border-t-2 border-slate-200" />
<Tier
title="Kế hoạch ký kết đã tạo"
count={plans.data?.total ?? null}
title="Kế hoạch ký kết đang xử lý"
// Đếm từ mảng ĐÃ LỌC, KHÔNG lấy `total` của server (xem khối lọc phía trên).
count={plans.data ? planItems.length : null}
accent="bg-violet-50 text-violet-600"
icon={<FileSignature className="h-3 w-3" />}
>
@ -228,7 +234,10 @@ export function KhkkWorkListPanel({
</div>
)}
{!plans.isLoading && !plans.isError && planItems.length === 0 && (
<p className="p-3 text-[11px] italic text-slate-400">Chưa kế hoạch kết nào.</p>
<p className="p-3 text-[11px] italic text-slate-400">
Không còn kế hoạch nào đang xử . Phiếu đã duyệt không hiện đây xem tại
menu Kế hoạch kết .
</p>
)}
<ul className="divide-y divide-slate-100">
{planItems.map((k) => (
@ -267,19 +276,23 @@ export function KhkkWorkListPanel({
</li>
))}
</ul>
{plans.data && plans.data.total > planItems.length && (
{/* 🔴 KHÔNG in "x/total" nữa: `total` gồm cả phiếu đã duyệt vừa bị lọc ⇒ tỉ số đó
sẽ đọc thành "đang cắt bớt" trong khi thật ra là "đang lọc bỏ". Nói đúng việc
đang làm, và chỉ nói khi thật sự có cái bị giấu. */}
{plans.data && plans.data.items.length > planItems.length && (
<p className="border-t border-slate-100 px-3 py-1.5 text-[10px] italic text-slate-400">
Hiện {planItems.length}/{plans.data.total} kế hoạch mới nhất xem đ menu Kế hoạch kết .
Đã n kế hoạch trạng thái Đã duyệt xem đ menu Kế hoạch kết .
</p>
)}
</Tier>
</>
)}
{/* Nút hành-động dính ĐÁY, ngoài vùng cuộn của 2 tầng — mirror "+ Thêm mới" của
`PeListPanel` (màn Duyệt NCC — Thao tác). Hiện KỂ CẢ khi thiếu quyền KHKK: cột
không dựng được thì vẫn phải còn đường bấm, không thì màn thành ngõ cụt. */}
{footer && <div className="shrink-0 border-t border-slate-200 p-2">{footer}</div>}
{/* 🧊 [S174] KHÔNG có nút "+ Thêm mới" ở đáy — anh chốt bỏ. Lý do đứng vững: màn này
KHÔNG có đường "tạo từ số 0". Một kế hoạch LUÔN sinh ra từ một phiếu Duyệt NCC, mà
các phiếu đủ điều kiện đã nằm nguyên ở tầng trên ⇒ nút chỉ xoá form chứ không tạo
thêm được gì. Đây là chỗ khác CÓ CHỦ ĐÍCH với `PeListPanel` (bên đó tạo phiếu PE
mới là việc có thật). Ai định thêm lại: phải trả lời được "thêm mới CÁI GÌ". */}
</aside>
)
}