Files
solution-erp/docs/changelog/sessions/2026-07-23-1730-S147-pe-attach-fix-project-dedup.md
pqhuy1987 5f6c3becfb [CLAUDE] Docs: S147 closeout — PE attach-fix (Trả lại) + project-dedup ship-prod
① nút đính kèm 'Bảng so sánh' gate drafter+phase (cả 2 app) · ② gom 3 cặp dự án trùng dấu-cách trên PROD (5 phiếu+5 NS repoint, 3 ẩn mềm) + vá gốc chuẩn hóa Code +4 test. CI #407 PASS, bundle rotate ×2 verify 2-nguồn. Session log + merge script (docs/changelog/data-fixes) + STATUS/HANDOFF + garble tally ×3 + harvest investigator/reviewer/cicd.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 17:37:59 +07:00

47 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# S147 — PE đính-kèm sau Trả-lại + gom dự án trùng (SHIP PROD)
**2026-07-23** · product/UAT session (khác chuỗi governance S135S146) · lead Opus 4.8 · `/tiep` (WAL trống, S146 đóng sạch) → 2 việc UAT anh giao.
## Bối cảnh vào phiên
`/tiep`: WAL chain rỗng nhưng chạy đủ Sàn-3 (5 tín-hiệu MẠNH). Tín-hiệu ⑤ fire (unpushed `wal:` chạm `.claude/sessions/session-1/closed.md`) NHƯNG ORPHAN-L cân sổ (pause=1/resume=1/closed=1) ⇒ L1(S146) đóng sạch, **không có mạch dở**. Rồi anh giao 2 việc + chỉ đạo **"invest + review chạy → sau đó fix"**.
## Việc ① — Bug đính kèm "Bảng so sánh" không sửa được khi phiếu bị Trả lại
**Triệu chứng (UAT, @Kenny/C Trà):** phiếu Duyệt NCC bị **Trả lại (TraLai=98)** → người soạn KHÔNG xóa file đính kèm sai + KHÔNG tải file mới được.
**Gốc (investigator + reviewer xác nhận):** KHÔNG phải phân quyền. Nút xóa/tải của `GeneralAttachmentsSection` (khối "Bảng so sánh giá", `purpose=ComparisonTable`) gate theo **`readOnly` của MÀN HÌNH**, không theo *trạng-thái + người-soạn*. Màn **"Danh sách"** mở phiếu `readOnly=true` cứng (`PurchaseEvaluationsListPage.tsx`) → ẩn nút ở **MỌI** phase, kể cả TraLai vốn ĐÃ nằm trong `isEditablePhase = {DangSoanThao, TraLai}`. Màn "Thao tác" (workspace) vốn chạy được (đó là cách gỡ tạm). **BE không hề chặn theo phase** — cố ý (S78 cần approver upload `ApprovalAttachment` lúc ChoDuyet).
**Fix (surgical, cả 2 app):** trong `ThongTinChonThauSection` (`PeDetailTabs.tsx`) tính `attachEditable = (isAdmin || isDrafter) && isEditablePhase(ev.phase)`, truyền `readOnly={!attachEditable}` **CHỈ** vào `GeneralAttachmentsSection` (`:2013`). **KHÔNG** đụng `readOnly` cấp `:421` (kẻo mở nhầm `HoSoLinkRow` + winner). Blast-radius = 1 hàm, 0 signature-change.
**3 bẫy reviewer bắt (đã né):** (1) `:421` bleed → HoSoLink; (2) `SupplierAttachmentsCell` gate `readOnly && !approverEditMode` — áp công-thức naive sẽ **vỡ Mig 28 F3** (approver sửa QuoteDocument lúc ChoDuyet) → **descope** (bug chỉ về comparison-doc); (3) BE guard naive vỡ **cả S78 lẫn Mig 28 F3****không thêm BE guard**. Test: FE-only, không có .NET test home → verify bằng `tsc -b` + `vite build` ×2 sạch.
## Việc ② — Gom dự án trùng (khác dấu-cách trong mã) + vá gốc
**Yêu cầu (UAT):** `FLOCK 01``FLOCK01`, `FLOCK 03``FLOCK03`, `CAL 01``CAL01` (cây "Duyệt NCC" hiện trùng do mã lệch 1 dấu cách).
**Gốc:** Project Create/Update lưu `Code` **thô** + kiểm-trùng **so-khớp-đúng-nguyên-văn** (`ProjectFeatures.cs` `x.Code == request.Code`) → "FLOCK 01" ≠ "FLOCK01" đều lọt. Seed `SeedRealMasterDataAsync` chỉ sinh mã **không-dấu-cách** (idempotency key exact-Code, ungated) ⇒ nguồn dấu-cách = nhập tay. Reviewer sửa investigator: refs là **loose-Guid (0 FK vật lý tới Projects)** — trỏ lại an toàn thứ tự nhưng **DB không có lưới an toàn** → phải verify 0-ref trước ẩn.
**Vá gốc (code, anh chốt "chỉ cắt đầu/cuối"):** LƯU `Code = request.Code.Trim()` (giữ mã như gõ), nhưng KIỂM-TRÙNG so trên `Code.Replace(" ","")` (bỏ-dấu-cách) → chặn tạo/sửa thành bản trùng chỉ khác khoảng trắng. **+4 test guard** `ProjectCodeNormalizationTests` (EF dịch `Replace` trên SQLite OK).
**Gom dữ liệu — Dev trước, prod sau:**
- Script `docs/changelog/data-fixes/2026-07-23-merge-duplicate-projects.sql` — idempotent, mỗi cặp 1 transaction, ABORT nếu >1 bản-giữ sống (reseed cycle), budget UNIQUE-collision → soft-delete, VERIFY 0 live-ref → else ROLLBACK. `SET QUOTED_IDENTIFIER ON; GO` baked-in (filtered-index DML bắt buộc, sqlcmd default OFF).
- **Dev:** cặp `FLOCK 01` (rỗng) ẩn mềm, idempotent re-run = SKIP.
- **PROD (qua `ssh vietreport-vps`, integrated auth `-E` → KHÔNG chạm password vrapp):** đo trước (read-only) = cả 3 cặp **live 2 bên, 0 HĐ, 1 bản-giữ/mã, 0 budget-collision** → chạy gộp: **5 phiếu + 5 ngân sách trỏ lại, 0 số bỏ, 3 bản dấu-cách ẩn mềm**. App-visible = 1 live row/mã. Đảo lại được (soft-delete). Verify em-main + kết-quả script **2-nguồn-khớp**.
| Mã giữ | Trước | Sau |
|---|---|---|
| CAL01 | 4 phiếu/2 NS | 5 phiếu/3 NS |
| FLOCK01 | 2 phiếu/2 NS | 4 phiếu/4 NS |
| FLOCK03 | 1 phiếu/1 NS | 3 phiếu/3 NS |
> ⚠️ Anh dặn: "sau này sẽ chạy tối ưu kỹ lại chỗ này" — đây là gom-sạch first-pass, chưa phải tối ưu cuối.
## Deploy
2 commit tách việc: `7d39d8d` (① FE 2-app) + `4510cea` (② BE + test). Squash 5 `wal:` local trước push (reset `origin/main`, 0 `wal:` lọt origin). **CI Run #407 SUCCESS 5m36s.** Verify (cicd-monitor + em-main curl **2-nguồn-khớp**): test gate 45D + **487I = 532** (was 483I → **+4**); bundle **fe-admin `DGPE6Nkb`→`B2j1LQ-v`** + **fe-user `DlTwAgJj`→`C3wbJegk`** (CSS frozen = logic-only); smoke api/admin/eoffice + /health = 200; **0 migration** (code-only, prod giữ Mig 67).
## Lessons
- **#53 return-garble ×3** (investigator ①-attachment + reviewer ②-merge + cicd-monitor) — tất cả memory-curation-fragment, tất cả recover **SendMessage-resume in-session** 3/3 sạch. → [[feedback_agent_return_garble_recover]] (tally ×25→×28).
- **Prod access:** S134 SSH flaky (reset trên reconnect nhanh) nhưng **one-shot non-interactive OK** + retry-loop; sqlcmd over SSH KHÔNG chết (S134 chỉ interactive); integrated auth `-E` (Administrator) né password; **QUOTED_IDENTIFIER ON bắt buộc cho DML trên bảng có filtered-index** (sqlcmd default OFF ≠ SSMS).
- **Reviewer-adversarial đáng giá:** self-review sẽ ship 3 regression (bleed + Mig28 + S78). Reviewer ② sửa cả claim "FK" của investigator (thật là loose-Guid).
## State
**Mig 67 · 89 bảng · 532 test (45D+487I) · gotcha 82 · menu 54 · bundle admin `B2j1LQ-v`/user `C3wbJegk` · prod dự án hết trùng (5 phiếu repoint, 3 ẩn mềm).**