# Session 59 — 2026-06-11 (chiều→tối) — Wipe testing data + PE tree 4 tầng + 71 mã PMH + 6 vòng UAT feedback (10 đợt ship prod-verified)

> **10 commit code → 8 Gitea Run PASS + 2 cancelled-supersede-benign (#273→#282), tất cả prod-verified.** Anh: `/session-start` → forward Zalo anh Kiệt FDC (2 việc) → chốt tree follow-up → forward chị Trà Sol (dọn mã tự chế) → anh Kiệt chốt format mã → 6 vòng screenshot UAT feedback realtime (4 vòng cuối chen giữa session-end) → "Chốt làm đi nhé" (session-end).
> Bundle FINAL: admin **`B1DtNT9C`** / user **`D6uF3Mln`** (Run #282 `792c030`). Test **240/240** (verify local ×2 + CI gate ×8). HMW-mode ON nguyên session nhưng mọi việc chạy Agent-tool spawn lẻ (không Workflow fan-out — task vừa, em main solo + recon/cicd spawn).

## Đợt 1 — Wipe transactional testing data prod (anh Kiệt: "xoá các cái testing trước đi em") — `56882ac` → Run #273

- 🟦 investigator-codebase recon prod: chỉ 3 cụm test data — **10 PE** (A/031-040, cả 10 WorkItemId NULL) + child 252 rows · **7 Contracts `[DEMO]`** + 30 child · **64 Notifications**; mọi module khác 0. Workflows: 7 ghim active + 1 V2 cũ inactive. Uploads: 19 folder PE vs 10 PE (~10 orphan từ Plan R S23). Generator PE/Contract đều INSERT-if-missing → DELETE sequence = reset mã an toàn.
- Anh chốt (AskUserQuestion): **wipe cả 3 cụm + reset mã về 0**.
- `scripts/s59-wipe-testing-data.sql` (pattern Plan R S23, QUOTED_IDENTIFIER ON): DELETE PE (cascade, Quotes NO_ACTION pass nhờ cascade Details→Quotes) → Contracts → Notifications → 1 AwV2 inactive (`IsActive=0 AND IsUserSelectable=0`) → PeSeq+CtSeq. AFTER exact: PE/child/Ct/Notif = 0 · AwV2 7/7 active · KEEP Projects 70 / WorkItems 86 / Suppliers 22 / Users 55 / Templates 9 / WfDefV1 10. Uploads orphan rd+md lại (ssh từng lệnh — quoting 3 lớp vỡ tại `&`, tách lệnh đơn).
- **Run #273 PASS** ~3m34s: bundle ×2 rotate (`R9uGRxvw`/`DikfX1RD`) + **app-recycle KHÔNG resurrect** (DemoSeed:Disabled gate held — verify THẬT qua recycle, không trên giấy). Team vào testing chiều: phiếu thật đầu tiên ra đúng **PE/2026/A/001** (sequence reset đúng thiết kế).

## Đợt 2+3 — PE Panel 1 tree regroup (2 vòng theo chỉ đạo) — `56882ac` (v1) + `0eafcd3` → Run #274

- Anh Kiệt v1: "Tên dự án (2026) → Hạng mục công việc → Phiếu cần duyệt" (thứ tự label "Dự án - Năm" — mirror folder Outlook FDC). Ship trong `56882ac`: node gộp `(Dự án, Năm-tạo-phiếu)` label "Tên (Năm)" → Hạng mục (`workItemId` Mig 49 — list DTO đã có sẵn `workItemName` S57bis nên FE-only) → phiếu. Bỏ tầng NCC (vẫn ở card/detail).
- Anh hỏi lại "Dự án→Năm hay Năm→Dự án?" → chốt follow-up: **"Năm chứa Dự án, Dự án chứa Hạng mục"** → `0eafcd3`: tree 4 tầng 📅 **Năm (DESC, bg-slate-50) → 📁 Dự án (A-Z vi) → 🧱 Hạng mục (A-Z vi) → phiếu (createdAt DESC)**. `yearGroups` useMemo thay `projectGroups`, expand-state localStorage key bump v3.
- **Run #274 PASS** ~4m51s: bundle ×2 rotate (`DuU7OTym`/`DWyeTzf3`), PE wipe held.

## Đợt 4 — Dọn 15 mã hạng mục tự chế (chị Trà Sol: "xóa cái đám phần thô phần hoàn thiện… MÀ ANH TỰ ĐẺ RA") — `bbd1554` → Run #275

- Prod 86 = 71 real PMH + **15 demo seed cũ** (Phần thô 5 + Hoàn thiện 6 + Cơ điện 3 + Khác 1 — DAO-MONG, SON-NUOC, TRAT-TUONG…). Đối chiếu **71/71 real khớp từng dòng bảng PMH** anh paste (4 nhóm 16/30/9/16 — S55 nạp từ chính Excel này).
- Fix kép: (a) **GỠ HẲN block seed demo** khỏi `DbInitializer` (guard `!AnyAsync()` vốn chống resurrect khi bảng còn data, nhưng DB mới tinh sẽ đẻ lại — diệt gốc theo "không đẻ thêm mã của công ty khác vô"); (b) `scripts/s59-wipe-demo-workitems.sql` DELETE 15 codes prod + LocalDB Dev → 71.
- **Run #275 PASS** ~3m44s: bundle ×2 FROZEN (BE-only đúng) + **app-recycle giữ 71** (demo codes = 0, seed real idempotent add 0). INFO: PE = 1 (phiếu UAT thật A/001 — không phải resurrect).

## Đợt 5 — Rename 71 mã theo format PMH anh Kiệt chốt ("MÃ CV gồm chữ MEP-SUB-1 rồi tên 1 MEP Sub MEP (Full) — đúng kiểu vậy") — `c869d26` → Run #276

- Mapping: `VT-nn→MAT-n` · `TP-nn→SUB-n` · `MEP-0n→MEP-SUB-n` · `TB-nn→MEP-EQU-n`; Name = `"STT nhóm tên"` (`1 Mat Bê tông`, `1 MEP Sub MEP (Full)`…). Category 4 nhóm giữ.
- **Thứ tự sống còn (gotcha #62 NEW):** SQL UPDATE rename (GIỮ Id) chạy prod + Dev **TRƯỚC** push — seed per-code idempotent mà deploy trước sẽ INSERT 71 mã mới cạnh 71 cũ = 142. Run #276 verify **đúng 71**.
- **Encoding (gotcha #61 NEW):** sqlcmd đọc file UTF-8 tiếng Việt PHẢI `-f 65001`; console VPS mojibake = display-only — verify data thật qua LocalDB console (đúng dấu) + **API JSON prod** (login admin → GET /catalogs/work-items → `"2 MEP Sub Hệ thống trung thế"` nguyên vẹn).
- FE sort numeric-aware 3 chỗ ×2 app (mã không pad → string-sort xếp "MAT-10" trước "MAT-2"): 2 dropdown (`select:` sort `localeCompare(…, {numeric:true})`) + tree workItemName. Spec provenance `master-import-data.generated.md` sync 71 dòng + note mapping.
- **Run #276 PASS** ~4m33s: bundle ×2 rotate (`BBA0KSWu`/`DzdTI18G`), spot MEP-SUB-1 đúng.

## Đợt 6 — UAT feedback vòng 1 (4 điểm form tạo phiếu, screenshot 16:40) — `faed59f` → Run #277

- **NEW `ui/SearchableSelect.tsx`** (~140 LOC, không lib ngoài): combobox gõ-để-lọc, **fold bỏ dấu tiếng Việt** (NFD strip U+0300-036F + đ→d — gõ "be tong" trúng "Bê tông"), keyboard ↑↓/Enter/Esc, clear ✕, listbox absolute z-50, style mirror ui/Input density S55.
- Áp: **Hạng mục + Dự án** ở PeWorkspaceCreateView + PeHeaderForm (HeaderForm giữ placeholder "Giữ nguyên: …" cho phiếu cũ). **Auto Địa điểm**: chọn dự án tự điền từ `Project.Location` (S55), chỉ ghi đè khi user chưa gõ tay (track `lastAutoLoc` ref). **Điều khoản thanh toán nhập tay**: Input 1 dòng → Textarea 3 dòng (CreateView + PeDetailTabs inline-edit; render detail đã `whitespace-pre-wrap` sẵn từ trước).
- **Run #277 PASS** ~4m09s: bundle ×2 rotate (`ex7Tc92G`/`DzUeSk96`).

## Đợt 7 — UAT feedback vòng 2 (3 điểm, screenshot 16:52) — `9c330d2` → Run #278

- Anh chốt 2 quyết định (AskUserQuestion): **(1) ẨN CẢ Trả lại + Từ chối** khi người duyệt = người soạn (`drafterUserId === currentUser.id` — PeWorkflowPanel skip 2 nút trong map; hủy phiếu = nhờ cấp khác Từ chối / Xóa phiếu Nháp); **(2) CHO PHÉP quick-add NCC**: `SuppliersController` POST hạ `Roles=Admin,CatalogManager` → `[Authorize]` any-auth (PUT/DELETE giữ khóa S57) + AddSupplierDialog nút "+ NCC mới" form nhanh (Mã/Tên/Loại/SĐT/Email) → POST → invalidate + auto-select vào phiếu.
- NCC dropdown → SearchableSelect sort A-Z theo mã. **Upload nhiều file 1 lần**: input `multiple` + `onPick` async upload tuần tự `mutateAsync` từng file (×2 chỗ: báo giá per-NCC + bảng so sánh — replace_all 2 occurrences identical).
- Test 240/240 local TRƯỚC push (BE authz + seed đổi trong session). **Run #278 PASS** ~3m45s: bundle ×2 rotate (`BSh2fG2X`/`D22KfpPc`) + **authz probe live 4/4**: unauth POST **401** (mở quyền ≠ anonymous) · nv.test POST **201** (id tạo thật) · nv.test DELETE **403** (khóa giữ) · admin cleanup 204→404 (tombstone soft-delete by-design). Suppliers active 22 giữ.

## Đợt 8-10 — UAT vòng 3-6 realtime (chen giữa session-end, anh forward liên tục) — `f21c55d`/`69997da`/`80b64dd`/`792c030` → Run #279-cancelled/#280/#281-cancelled/#282 FINAL

- **Vòng 3 — "thêm file giao diện bị thay đổi không cân xứng" (`f21c55d`):** bảng NCC tham gia auto-layout → cell File (chip tên dài 8.2MB) phình, bóp dọc cột NCC. Fix `table-fixed` + width % từng cột (NCC 24/SĐT 9/Email 14/ĐKTT 14/File 22/Tiền 12/action w-10) + `min-w-[860px]` (hẹp → scroll ngang wrapper) + email span block truncate. Chip file truncate sẵn — kích hoạt khi cell khóa width.
- **Vòng 4 — "chỗ tên ngân sách bỏ đi nhé" (`69997da` #280):** bỏ ô "Tên (không bắt buộc)" ngân sách nhập tay (user không hiểu "ý nghĩa dự phòng là gì") — chỉ còn Số tiền. Payload `budgetManualName: null`; PeHeaderForm `hasManual` detect thêm theo amount (phiếu mới name-null vẫn nhận đúng manual mode). Tên cũ hiện read-only, về null khi lưu lần tới.
- **Vòng 5 — "điều khoản thanh toán bỏ nốt ra luôn tất cả các form" (`80b64dd`):** GỠ field phiếu-level khỏi CẢ 3 form (CreateView: Select preset + Textarea custom + PAYMENT_PRESETS/PAYMENT_CUSTOM/paymentMode drop, −142 LOC · HeaderForm + DetailTabs inline-edit: bỏ Textarea, state giữ → save giữ nguyên data cũ). GIỮ: cột "Điều khoản TT" per-NCC bảng so sánh (data từng NCC, khác field) + display read-only phiếu cũ.
- **Vòng 6 — "bỏ luôn cái nút thêm hạng mục" (`792c030` #282):** gỡ nút "+ Thêm hạng mục" ItemsTab — 1 phiếu = 1 hạng mục chọn từ header (S57bis/S58), hạng mục đầu auto-seed khi tạo. AddItemDialog giữ dead-code flip lại dễ.
- **2 run cancelled (#279, #281) = supersede-BENIGN** (Gitea concurrency-cancel khi push đè lúc UAT góp ý realtime ~1s gap): cicd ancestor-verify `git merge-base --is-ancestor` TRUE cả 2 — mọi fix preserved trong HEAD, ships qua run kế (#280, #282). Pattern lặp #385 S58 — cicd-monitor giờ xử lý chuỗi này thành thạo (con final-v2 tự phát hiện supersede + verify run MỚI thay vì báo fail).

## Closeout & lessons

- **0/14 spawn truncated** (H1×2 + H2×2 bootstrap/closeout + investigator recon + cicd ×9) — lần đầu sau nhiều session; verdict-FIRST + entry gọn + prompt scoped works.
- **gotcha #61** (sqlcmd -f 65001 + verify-qua-API-JSON) + **#62** (rename natural-key: DB-trước-code-sau) NEW → 62 total.
- Quy trình "destructive prod" giữ chuẩn: recon → AskUserQuestion chốt scope → script committed vào repo → chạy → verify exact-count → cicd re-verify sau app-recycle.
- cicd-monitor MEMORY ~56KB > cap 30KB (9 cicd-spawn/ngày, H2 đo 54KB giữa session + 2 entry cuối) → chore-flag curate L2 monthly 2026-07-01 (FIFO top vẫn inject OK). H2 GATE PASS 5/5 + H1 ALL-4-FRESH (cross-count verified); 2 fix theo H2 flag: UTC-annotate entry #275 + bundle status-line tự cập bởi cicd final-v2.
- Ops note benign: `GET /api/suppliers` trả page đầu (20) — đếm thật qua DB (22); không UI nào dùng sai.
- Pending (carry): test-after guard cho `LockDemoSampleUsersAsync` (S58) + asymmetric authz suppliers POST/PUT-DELETE (S59) → test-specialist khi UAT ổn; 4 ops của anh (tzutil / email anh Chương / báo password 5 staff / lock nv.cao-truong khi gán thật); PermissionGuard per-route khi golive HRM/Office; Phase 9 Ops; monthly audit 2026-07-01.
