[CLAUDE] Contract: KHKK 3-panel mirror Duyet NCC (GD2 het lech kien-truc) + 4 mitigation cuong-hanh

Dua man Ke hoach Ky ket HD ve dung lop kien-truc ma GD1 (PE) va GD3 (HD) dang
dung: 1 man 3-panel h-[calc(100vh-4rem)] grid [400px_1fr_360px], thay cho
list 2-panel + detail trang rieng co workflow xep chong. GD2 la mat-xich lech
duy nhat trong 4 giai-doan truoc dot nay.

BE (7/7):
- DetailDto +5 field: WorkflowVersion / EndedByLevelFinalize / FinalizeStepName
  / FinalizeLevelName / Approvals. 2 field Finalize* KHONG phai field append —
  ContractSigningPlan chi co co bool, phai DAN XUAT (lam in-memory tu cay
  workflow da nap, thay vi port 3 subquery cua PE).
- ContractSigningPlanApprovalDto de moi (truoc do 0 hit toan repo).
- StepDto +DepartmentName (nuoi chip phong panel 3 + header nhom Section 5).
- GET {id}/changelogs — policy KeHoachKyKet.Read.
- POST {id}/approval-attachments + enum +ApprovalAttachment=3 (append-only).
- approvals[] loc bo Decision=Pending de so (N) cung nghia voi (N) cua PE.
- KHONG chep khuon authz cua PurchaseEvaluationsController: file do la mot lo
  gotcha #82 (class [Authorize] tran + changelogs khong policy).

FE (x2 app, SHA256 byte-identical 8/8):
- components/khkk/{KhkkListPanel,KhkkDetailContent,KhkkWorkflowPanel}.tsx;
  KhkkWorkflowPanel doi tu pages/ sang components/.
- Panel 2: 5 Section danh so, Section 5 "Y kien cap duyet" duoi cung — khoi nay
  DOI TU panel 3 sang panel 2 (owner khoanh do trong anh); luoi 2 cot + meta
  "x/y cap da duyet · N NV tham gia" thay list phang 1 cot.
- Panel 3: banner ket-thuc-som, chip ten phong, badge Duyet thay CEO, lam mo
  buoc sau finalize, khoi File dinh kem khi duyet, Lich su duyet, Lich su
  thay doi. Chua chon phieu -> hop dashed y PE (QD owner).

4 mitigation cuong-hanh (owner chot panel 1 = PipelineTreePanel; chon HINH
khong phai chap-nhan VO):
- M-1 fallback !canPe -> danh-sach KHKK phang. Khong co no, user chi co quyen
  KHKK (vd nguoi duyet nhom N5) MAT TRANG danh-sach du API van cho doc —
  display-layer khoa authz-layer dang mo, mat con lai cua #82.
- M-2 onOpen thanh callback tu host; mac-dinh giu navigate cu => 0 hoi-quy
  trang PE/GD3.
- B1 stage2Content di CAP voi stage2Count qua conditional-spread:
  khong truyen => stage2 van nhan groups => trang PE GIU NGUYEN 8 ngan N1-N8.
- R-11 search/loc di server-side, KHONG loc-tren-cay; giu dong canh-bao
  "Cay tai 200 phieu moi nhat".

Chong hoi-quy da do: build 3/3 PASS · acceptance tinh 20/20 x 2 app ·
mirror SHA256 8/8 · 0 hit path cam (components|pages)/(pe|contracts) ·
KHONG ghi filter/view/group vao URL (chong tai-sinh bug S155 sidebar sang
nham leaf) · giu set-state-in-render :97-101 (dung khuon React, refactor
thanh useEffect se tai-hien va-5).

Con no, khai thang:
- E-7 bang "cot nao chet, chuyen di dau" (8 cot + 6 KpiCard -> card 400px)
  THIEU: lane FE chet o luot tra ve, ly-do bo cot mat theo. Phai soi diff
  bang mat truoc UAT, neu khong dung ca M-7 mat-chuc-nang-im-lang.
- Test 643/644. 1 FLAKY Create_DifferentEvaluationType_DoesNotAffect_Other
  (full-suite 2->1->1, chay rieng 0/0/0; PeWorkflowAdminTests tao
  new SqliteDbFixture() rieng x6 test). Baseline tren HEAD sach CHUA do
  (git worktree fail duong-dan Dropbox) => CHUA duoc noi "flake co san".
- 5 acceptance RUNTIME cho UAT: approvals co dong Tra lai + ten nguoi (DB dev
  0 hang KHKK) · UAT fe-user:8080 an-danh · deep-link fe-admin:8082 (menu
  KHKK bi an trong ADMIN_HIDDEN_KEYS) · negative-control go fallback canPe ·
  khoi file tu-an khi rong.
- P3-12 4 return-mode = HOAN, khong phai buoc-khac: tien-de "KHKK 3 tram ngan"
  do lai la SAI (DbInitializer:554 = 3 Buoc x <=2 Cap, seed tu ghi "sao tu
  workflow Duyet NCC prod").

Spec + 6 artifact phan-bien: .claude/workflows/runs/2026-08-04-S171-khkk-ui-mirror-pe/

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-08-04 19:48:38 +07:00
parent f151320780
commit 998ea559f3
21 changed files with 4581 additions and 3179 deletions

View File

@ -13,7 +13,7 @@ import { useQuery } from '@tanstack/react-query'
import { ListTree, Search } from 'lucide-react'
import { api } from '@/lib/api'
import { cn } from '@/lib/cn'
import { usePipelineStages } from '@/hooks/usePipelineStages'
import { usePipelineStages, type BuildStagesOptions } from '@/hooks/usePipelineStages'
import { PipelineStageFolders, type PipelineStageNo } from '@/components/pipeline/PipelineStageFolders'
import type { Paged } from '@/types/master'
import type { PeListItem } from '@/types/purchaseEvaluation'
@ -87,9 +87,14 @@ export function PipelineTreePanel({
currentStage,
className,
bodyMaxHClass,
stage2Options,
}: {
currentStage: PipelineStageNo
className?: string
// [S171] Trang KHKK (GĐ2) tự vẽ thân folder GĐ2 = card phiếu của chính nó, và tự bắt
// sự kiện mở phiếu (M-2: chọn vào panel 2 thay vì rời trang). Không truyền ⇒ cây giữ
// NGUYÊN hành vi cũ (8 ngăn nhóm + navigate) ở trang Duyệt NCC / GĐ3 / GĐ4.
stage2Options?: Pick<BuildStagesOptions, 'stage2Content' | 'stage2Count' | 'onOpenKhkk'>
// [S168 GĐ3-D gate F-1] Host 3-panel (Panel-3 TỰ CUỘN) truyền class trần riêng cho THÂN
// cây (vd `lg:max-h-none` — panel giữ đúng 1 thanh cuộn; max-h mặc định 100vh16rem là
// hiệu-chỉnh cho trang giấy-cuộn, vượt chỗ Panel-3 /inbox ~26px ⇒ 2 cuộn lồng nhau).
@ -306,7 +311,7 @@ export function PipelineTreePanel({
className="ml-3 border-l border-slate-200"
currentStage={currentStage}
hideEmpty={hideEmpty}
stages={buildStages(wg.items)}
stages={buildStages(wg.items, stage2Options ?? {})}
/>
</details>
)