[CLAUDE] Docs: S78 closeout — PE attach-file khi duyet (Run #330) + gotcha #71 + cicd flush

STATUS/HANDOFF tier +S78 (bundle CsJetgZH/BVS0ApIm, no migration, test 354 unchanged) + trim S75 row. gotcha #71 (them enum value vao entity dung-chung -> pollute UI/guard proxy-predicate supplierId===null). session log S78. cicd-monitor MEMORY self-flush Run #330. curate-debt carry: reviewer 45.2KB + cicd 38.8KB + inv 35.7KB keep-floor-hit (archive-gate A7 PASS 186/186). Docs-only -> CI skip.
This commit is contained in:
pqhuy1987
2026-06-19 19:32:41 +07:00
parent 7886fd03dd
commit f0e616fd5a
5 changed files with 79 additions and 8 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,34 @@
# Session 78 (2026-06-19) — PE: đính kèm file khi DUYỆT (UAT Tra Sol / 5 tester)
**Anh:** forward chat Zalo (Tra Sol + team — "5 tester đang testing kaka" 15:30) → *"CHỖ MÀN HÌNH duyệt — ĐỂ THÊM chỗ attach file… để khi họ duyệt mà muốn đính kèm cái file của mình lên vì có trường hợp thay đổi ko muốn trả lại"**"OK public và push luôn đi nhé"*`/session-end`.
## Yêu cầu
Ở modal Duyệt PE (popup "Duyệt → Đã gửi duyệt") thêm trường **ĐÍNH KÈM FILE** — người duyệt tải file của mình lên ngay lúc duyệt, thay vì phải Trả lại phiếu (tình huống có thay đổi nhỏ).
## Thiết kế (em-main solo — schema/UX decision)
- **Reuse hệ attachment có sẵn, MIGRATION-FREE:** thêm 1 enum `PurchaseEvaluationAttachmentPurpose.ApprovalAttachment = 5` (lưu int → 0 đổi schema). Endpoint `POST {id}/attachments` đã nhận sẵn `purpose`+`note`, validator `IsInEnum()`, controller `[Authorize]` + handler KHÔNG guard drafter-only → **approver upload được (no 403)**.
- **Upload-then-approve:** FE modal picker multi-file → bấm Xác nhận → upload từng file (purpose=5) TRƯỚC → rồi mới `/transitions` chuyển phase. File lỗi (định dạng/>20MB) → throw, KHÔNG duyệt.
- **Hiển thị:** mục "📎 File đính kèm khi duyệt" trong `PeWorkflowPanel` (download + preview qua `AttachmentPreviewDialog`), gắn CreatedBy + CreatedAt. KHÔNG lẫn vào "Bảng so sánh".
## Done (commit `7886fd0`, 7 file +313/14, Run #330 PASS)
- **BE (1 file):** enum `ApprovalAttachment=5``PurchaseEvaluationAttachment.cs`. No migration. Test **354 PASS** (unchanged). GET bundle project `e.Attachments` KHÔNG lọc purpose → file purpose=5 tới FE.
- **FE 2 app SHA-identical:** `PeWorkflowPanel.tsx` (picker + upload-before-transition + display section + close-reset) · `PeDetailTabs.tsx` (2 filter fix) · `types/purchaseEvaluation.ts` (enum + label "File khi duyệt").
- **Fix correctness (gotcha #71):** 2 filter dùng `supplierId===null` làm proxy "Bảng so sánh" → loại purpose=5 (tránh lẫn section + false-pass submit-guard "Chưa đính kèm Bảng so sánh").
## Cách chạy
em-main-led + self-gate (HMW-mode ON nhưng tight-coupling modal + go-live → `feedback_workflow_fanout_reliability`: em-main ≈ fan-out, tránh #53; reviewer over-cap 45KB → grep no-leak thay vai). **1 sub spawn = cicd-monitor** (verify Run #330). Verify chain: 3 build PASS + 354 test + SHA-parity identical (`diff` = 0) + grep no-leak (mọi `.purpose`/`supplierId===null` 2 app) + GET-bundle-projection check.
## 3 vấn đề em tự bắt khi build (review-trước-deploy)
1. **Authz** — đọc handler `UploadPurchaseEvaluationAttachmentCommandHandler` confirm KHÔNG guard drafter-only → approver upload được (no 403; verify TRƯỚC build per gotcha #44 silent-403 pattern).
2. **Filter pollution (#71)** — file-khi-duyệt (supplierId=null) lẫn "Bảng so sánh" + false-pass submit-guard → vá 2 filter ×2 app.
3. **Dialog mirror-truncate** — bản fe-admin em sót `</Dialog>` (new_string copy-truncate) → build fe-admin riêng BẮT → vá. **Bài học: build-verify TỪNG app, không tin "mirror identical".**
## Deploy (cicd-monitor PASS — Run #330 ~4m56s)
Test gate 354. Bundle ROTATE: admin **`BqKD3Y23 → CsJetgZH`** / user **`Cn-i349D → BVS0ApIm`** (+css). NO migration (Mig 57 latest, sys.tables 88). Smoke 4×200 + `POST .../attachments` 401-wired. Ship-proof byte-stable (admin 1,617,391b · user 1,521,772b, fetch#1==#2) + fake-hash control (gotcha #69 non-determ rotate).
## Lưu ý / NEXT
- **Minor (chấp nhận UAT):** upload-then-transition KHÔNG atomic — upload OK nhưng transition lỗi → file orphan gắn phiếu (hợp lệ, retry được).
- **NEXT UAT (anh/Tra Sol/5 tester, Ctrl+F5 lấy `CsJetgZH`/`BVS0ApIm`):** phiếu chờ duyệt → ✓ Duyệt → "+ Chọn file đính kèm" (multi) → Xác nhận → file ở mục "📎 File đính kèm khi duyệt" (download/preview). Chỉ hiện ở hành động Duyệt.
- **🔴 NEXT (em) — carry GẤP curate L1 over-cap (carry 6 session S72→S78):** reviewer **45.2KB** + cicd-monitor **38.8KB** + inv-codebase **35.7KB** = keep-floor-hit (manual SPLIT/condense, KHÔNG auto-drain; archive-gate `memory-archive-gate.ps1` A7 GATE PASS 186/186 — archive integrity OK, chỉ L1-hot truncate-on-inject) + FD 26.1KB / test-spec 27.7KB WATCH strike-1.
- **Ops giữ S58/S59:** tzutil VPS UTC+7 · anh Chương email typo · 5 real-staff pw `User@1234567` · gán CNTT. **Monthly audit 2026-07-01:** STATUS/HANDOFF re-tier · docs/CLAUDE deep-doc count-flush (Mig→57, test→354, gotcha→71) + schema-diagram §16+ Mig 32-57 ERD.
- **Pending product (carry):** "Ngưỡng giá CEO" Mig 54 Designer UAT · "C" chuyển phiếu→dự án chờ spec form.

View File

@ -1232,6 +1232,20 @@ for h in resp.points: # ← .points không phải iterable trực tiếp
---
### 71. Thêm enum value vào entity DÙNG-CHUNG → pollute UI/guard phân-loại theo PROXY-predicate (không phải enum) (Session 78)
**Triệu chứng:** Thêm `PurchaseEvaluationAttachmentPurpose.ApprovalAttachment=5` (file người duyệt đính kèm khi DUYỆT tái dùng entity `PurchaseEvaluationAttachment`, migration-free enum lưu int). File mới `PurchaseEvaluationSupplierId=null`. NHƯNG 2 chỗ FE dùng `supplierId === null` làm PROXY ngầm cho "Bảng so sánh": (1) `banSoSanhAttachments` file-khi-duyệt LẪN vào section Bảng so sánh; (2) submit-guard "Chưa đính kèm Bảng so sánh" file-khi-duyệt FALSE-PASS guard (cho gửi-duyệt chưa bảng so sánh thật, khi phiếu Trả-lại re-submit sẵn file vòng trước).
**Cơ chế:** Reuse-enum migration-free RẺ (0 schema change, tái dùng storage+endpoint+component), NHƯNG predicate phân-loại bằng FIELD-PROXY (`supplierId===null` = "không gắn NCC" "bảng so sánh") thay check enum tường minh giá-trị enum MỚI rơi nhầm bucket. Build PASS (TS không bắt logic-bucket) + test xanh (FE-filter no xUnit) CÂM.
**Guard:** Khi thêm enum value vào entity dùng-chung **grep MỌI predicate lọc bằng field-proxy** (không phải enum) liên-quan, loại-trừ value mới: `supplierId===null && purpose !== ApprovalAttachment`. Áp cả 2 app SHA-mirror. Kiểm thêm: per-supplier list (lọc `supplierId===s.id`) tự loại (value mới supplierId=null); GET bundle projection KHÔNG `.Where(purpose)` value mới tới được FE.
**Credit:** em-main self-review (grep toàn-bộ `.purpose` / `supplierId===null` 2 app) bắt TRƯỚC deploy thay vai reviewer (over-cap). Build fe-admin riêng cũng bắt mirror-truncate `</Dialog>` sót (new_string copy-truncate) **build-verify TỪNG app, KHÔNG tin "mirror identical"**.
**References:** `fe-*/src/components/pe/PeDetailTabs.tsx` (banSoSanhAttachments + missingForApproval) · `PeWorkflowPanel.tsx` (approvalAttachments) · `src/Backend/.../PurchaseEvaluations/PurchaseEvaluationAttachment.cs` enum · gotcha #70 (self-review bias).
---
## Checklist debug bug mới
1. Build pass không? fail check using + package version compat
@ -1267,3 +1281,4 @@ for h in resp.points: # ← .points không phải iterable trực tiếp
30. Nếu sub-agent WRITE truncate NGAY ĐẦU exploration phase (chưa write file, đọc > 4 reference) → heavy spec ~10K + context bloat. Mitigation: brief ≤ 8K + pre-supply reference snippet trong brief HOẶC em main solo nếu cần đọc > 4 reference file (#55)
31. Nếu `git commit -m` qua PS 5.1 báo `error: pathspec 'xxx' did not match` với message tiếng Việt có `"` → native-arg escaping vỡ tại quote kép → Write message ra file UTF-8 + `git commit -F <file>` (#59)
32. Nếu thao tác theo-email/code trên data prod (lock/seed/migrate) trả 0 row affected → DUMP bảng env đích trước khi nghi code — population Dev ≠ prod (seed silent-fail `IdentityResult` không throw) (#60)
33. Nếu thêm enum value vào entity DÙNG-CHUNG mà UI/guard phân-loại sai (file lẫn section / false-pass guard) → grep MỌI predicate field-proxy (`supplierId===null`...) loại value mới + build-verify TỪNG app riêng (#71)