[CLAUDE] Docs: S85 closeout — CV PRO batch + clear-data + 6 UAT round + 2 root-cause + 5 SQL-fix
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m51s

STATUS/HANDOFF prepend S85 (Mig 57 · 88 tables · 374 test · gotcha 73 · bundle admin aKuNip19/user BUsL0HeQ Run #341). gotchas +2: #72 DateTime serialize no-Z -> FE +7h (UtcDateTimeJsonConverter UTC-with-Z) · #73 UPDATE absolute-set clears field on FE-omit (bug-class S42, null-safe guard). + session log + scripts/s85-wipe-uat-data.sql (prod UAT wipe, backup-first, executed) + agent-memory residuals (inv-codebase PE-recon, test-specialist baseline 374).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-23 15:52:46 +07:00
parent 5349623347
commit d4f60212a5
7 changed files with 205 additions and 6 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,73 @@
# Session 85 — 2026-06-23 — PE UAT anh Kiệt FDC CỰC LỚN: CV PRO 6-fix + clear-data + 6 vòng UAT + 2 ROOT-CAUSE + 5 SQL-fix
> **~10 deploy prod-verified #334→#341 (anh Kiệt FDC + Tra Sol + long.chau UAT realtime, HMW-mode ON, ~12 spawn).** Anh `/session-start` → forward Zalo + file Excel `Cần chỉnh sửa CV PRO.xlsx` liên-tục realtime → `/session-end`.
> State THẬT cuối: **Mig 57 · 88 bảng · 374 test · gotcha 73 · menu 54 · bundle admin `aKuNip19`/user `BUsL0HeQ` (Run #341, FE frozen #339+#341 BE-only).**
## Đợt 1 — Batch CV PRO 6-fix (Run #334 `5a61254`)
File `Cần chỉnh sửa CV PRO.xlsx` = **8 ảnh chú-thích nhúng** (zip xl/media/, KHÔNG phải bảng công thức) — đọc qua skill xlsx + giải-nén + Read ảnh. 6 fix → AskUserQuestion chốt 3 nhóm mơ-hồ:
- **C1** ẩn "c.Giá chào thầu" + "d.Bảng so sánh giá" khỏi form TẠO (giữ ở Detail).
- **C3** ô8 "Giá trị TH dự kiến còn lại" pre-fill từ dòng7 (Ngân sách còn lại).
- **C4a** **live-recompute**`VndInlineEdit +onLiveChange`, `PeBudgetSummaryTable draftRow3/draftRow8` → dòng 5/6/7/9 + % nhảy NGAY khi gõ ô3/8 (chưa cần Lưu).
- **C4b** So-sánh=0 → "Bằng ngân sách"/"—" (chỉ khi base>0).
- **C5** hủy/đổi NCC winner — BE `SelectWinnerBody(Guid?)` + Command/Handler clear-branch (null=clear bỏ-validate-list, no-migration vì SelectedSupplierId đã Guid?) + FE dropdown ""→null + nút ✓ toggle.
- **C7** nút "Toàn màn hình" preview file báo giá (overlay inset-0 + Esc).
Test +10 (`PeSelectWinnerClearTests`) → 356→366. FE 3 file ×2 app SHA-identical (diff-confirm rồi cp).
## Đợt 2 — Clear sạch UAT data (`scripts/s85-wipe-uat-data.sql`)
anh Kiệt "Clear sạch giúp anh nhé. Clear truoc em, xong anh check." Quy-trình destructive-prod chuẩn S59: recon → AskUserQuestion chốt scope → script → chạy → verify exact-count.
- **Recon prod:** 43 PE + 28 PeWorkItemBudgets + 1 HĐ + 32 notif + 43 Suppliers (22 gốc ≤06-09 + 21 test ≥06-11).
- anh chốt: **xóa budgets (28)** + **xóa 21 NCC-test** (CreatedAt≥06-11, mốc cắt sạch 06-10 trống).
- Script: **BACKUP bước 0** (registry backup-dir vì D:\ không tồn-tại VPS) + `SET QUOTED_IDENTIFIER ON` (filtered-index Mig19/20) + `SET XACT_ABORT ON` + `-b` (backup-fail→abort-trước-wipe). Verify exact: PE/budgets/contracts/notif=0, Suppliers=22, reset mã→A/001. DemoSeed:Disabled gate → app-recycle KHÔNG resurrect.
- Orphan upload files **defer** (path CWD-relative chưa pin, vô-hình UAT).
## Đợt 3-6 — 6 vòng UAT (#335→#338)
- **R1 #335 `24f0083`:** D1 ẩn mục 3/4/5 (gỡ FormRow/LockedHint/Lock unused) + D5 auto-chọn quy trình khi 1 option (prod PE type-1 có đúng 1 selectable WF).
- **R2 #336 `a5e936f`:** D2 drafter tự-gỡ cờ GẤP — BE restructure SET/UNSET (GẮN role-only; GỠ +drafter clear-CẢ-2) + FE `isDrafter` canToggle. D3 ngân-sách-kỳ-này tự-nhảy từ "Ban hành lần đầu" (`proInitialAmount`) — FE display + FE/BE submit-guard fallback `budgetPeriodAmount ?? proInitialAmount`. test +5 (`PeUrgentToggleAuthzTests` drafter-self-unset) → 366→371.
- **R3 #337 `e3e40ae`:** ràng tạo-phiếu (hint đỏ "thiếu: Quy trình/Hạng mục/Dự án", anh "không chọn quy trình thì ko cho tạo phiếu") + D4 checklist xanh✓/đỏ✗ trường bắt-buộc gửi-duyệt (mirror missingForApproval). [JSX fragment fix: chèn nhầm conditional trùng → bọc `<>`.]
- **R4 #338 `34218f0`:** checklist +"Quy trình duyệt" (sót → toàn-xanh-nhưng-submit-chặn misleading). [R4 auto-assign-at-submit thử → REVERT vì phá 2 test S83 + nới block ngược "ràng".]
## Đợt 7-8 — 2 ROOT-CAUSE (whack-a-mole → fix gốc)
**(R5 #339 `1263c0d`) Workflow MẤT khi bấm Sửa = bug-class S42** — gotcha #73.
- long.chau + anh Kiệt vẫn kẹt "chưa chọn quy trình" DÙ SQL-fix 5 phiếu. Query prod: 2 phiếu workflow-less MỚI (A/005 14:54, A/010 15:08) tạo SAU SQL-fix → KHÔNG phải point-in-time, là systemic.
- Truy: form Sửa header → `PUT /purchase-evaluations/{id}` (PeHeaderForm:112) KHÔNG gửi `approvalWorkflowId``UpdatePeDraftHandler:282` absolute-set `= null` = CLEAR. Field SÓT khỏi pattern null-safe mà BudgetPeriodAmount/WorkItemId cạnh đã có (S42).
- Fix: `if (request.ApprovalWorkflowId is not null) entity.ApprovalWorkflowId = ...`. Test +3 (`PeUpdateDraftWorkflowPreserveTests`) → 371→374.
**(R6 #341 `49ac086`) "Mới tạo báo 7h trước" = TZ +7h** — gotcha #72.
- timestamps UTC nhưng EF đọc `DateTimeKind.Unspecified` → System.Text.Json serialize KHÔNG 'Z' → FE `new Date(naive)`=local → lệch +7h.
- Fix: global `UtcDateTimeJsonConverter` (AddControllers.AddJsonOptions) ép UTC-with-Z `.ToString("O")`. cicd verify API trả `createdAt:"…3154440Z"`. (= ops "tzutil UTC+7" pending S58, fix tầng-serialize chuẩn hơn đổi giờ máy-chủ.)
**5 SQL-fix** (2 đợt 3+2 phiếu workflow-less): `UPDATE PurchaseEvaluations SET ApprovalWorkflowId='E08E3DA6…' WHERE ApprovalWorkflowId IS NULL AND Type=1` (QT-DN-V2-001). Bridge tới khi R5 deploy.
## Cách chạy + spawn-record (§L.b(d) — ~12 spawn)
| Sub | Task | Nấc | Evidence |
|---|---|---|---|
| tooling-auditor (H1) | session-start RE-REPORT 4-mặt | verified | 0 new-alloc, ef-core/skills-README stale-cite defer-monthly |
| harvest-curator (H2) | (skip — em-main proxy harvest closeout) | n-a | — |
| implementer-backend | C5 BE select-winner Guid? clear | verified | build slnx 0-err, Run #334 |
| implementer-frontend | (em-main did mirror) | n-a | — |
| test-specialist ×3 | C5 +10 · urgent-authz +5 · workflow-preserve +3 | verified | 356→374, 0 fail |
| reviewer ×2 | R2-batch + R5-batch | partial | truncated #53 → em-main self-gate (memory-lesson) |
| cicd-monitor ×7 | #334/#335/#336/#337/#338/#339/#341 | verified | 7/7 PASS, ship-proof Last-Modified BE-only |
| investigator-codebase | recon D2-GẤP-authz + D5-WF + validators | verified | found create-validator + IsUserSelectable prod=1 |
⚠️ **Residual-write reconcile (B3):** investigator-codebase + test-specialist ×2 tự ghi MEMORY (residual) — em-main review git-diff = legit findings, KEEP.
## §L closeout
- **§L.a RCA:** 2 production-bug = lỗi-KÉP → 2-fix mỗi cái: R5 (code null-safe + test regression #73) · R6 (code converter + gotcha-guard #72). AS-* không thêm class mới.
- **§L.b(c)** archive-gate A7 GATE PASS 217/217 · test-specialist 28.4KB WATCH strike-1 (value-protect gotcha#/guard KEEP) · sleep-check 5d<7 skip.
- **§L.b(h)** User-Mark: 4 Active-High hiển-thị, 0 governance-decision MỚI (product session) n-a.
- **Lesson NEW `feedback_root_cause_over_symptom`:** bug lặp-lại + band-aid (SQL/tzutil) không dứt truy field/case LỆCH-quy-ước, đưa vào pattern đã-đúng = fix 1 chỗ hết tái-diễn.
## 🔴 NEXT
- **Anh:** restart CLI (carry budget v3 runtime + lệnh `/user-mark-*` + cadence). Báo anh Kiệt/long.chau Ctrl+F5.
- **Em (carry):** curate test-specialist 28.4KB WATCH (value-protect KEEP) · monthly 2026-07-01 (doc-cite ef-core/skills-README Mig57 · STATUS/HANDOFF re-tier · **SignalR TZ-check** nếu realtime-push lệch · **edit-form workflow-selector** defer = đóng dead-end "mở Sửa chọn quy trình" form chưa ô · fix L1-essentials `vrapp` password prefix-thừa per cicd-flag).
- **Pending product:** Ngưỡng giá CEO (Mig 54) Designer UAT · "C" chuyển phiếudự án chờ spec.
- **Ops:** email typo anh Chương · 5 real-staff pw · gán CNTT.

View File

@ -1246,6 +1246,30 @@ for h in resp.points: # ← .points không phải iterable trực tiếp
---
### 72. DateTime serialize KHÔNG có 'Z' (EF đọc `Unspecified` kind) → FE `new Date()` hiểu là local → lệch +7h (Session 85)
**Triệu chứng:** anh Kiệt "mới tạo phiếu báo cách 7h trước" relative-time thông-báo (`Date.now() - new Date(createdAt).getTime()`) lệch đúng +7h (VN UTC+7).
**Cơ chế:** timestamps lưu UTC (`DateTime.UtcNow`) nhưng EF Core đọc SQL Server `datetime2` về `DateTimeKind.Unspecified`. `AddControllers()` mặc-định (KHÔNG JSON config) System.Text.Json serialize DateTime Unspecified-kind **KHÔNG có hậu-tố 'Z'** (`"2026-06-23T08:20:00"`). FE `new Date(naive)` hiểu chuỗi-không-TZ **giờ LOCAL** nhưng giá-trị UTC lệch +7h. Build PASS + test xanh (serialization không unit-test) CÂM.
**Guard:** global `JsonConverter<DateTime>` ép UTC-with-Z: `Kind==Unspecified ? SpecifyKind(Utc) : ToUniversalTime()` `.ToString("O")`. Đăng-ký `AddControllers().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new UtcDateTimeJsonConverter()))`. System.Text.Json tự áp cho `DateTime?` (Nullable non-null). **Verify:** curl authed endpoint field `createdAt` JSON 'Z'. Lưu-UTC + convert-on-display (FE) CHUẨN KHÔNG band-aid `tzutil` đổi giờ máy-chủ (ảnh-hưởng GETUTCDATE + lệch khác). SignalR realtime-push dùng serializer RIÊNG (`AddJsonProtocol`) nếu giờ realtime còn lệch thì áp converter tương-tự.
**References:** `src/Backend/SolutionErp.Api/Serialization/UtcDateTimeJsonConverter.cs` · `Program.cs` AddControllers · `fe-user/src/components/NotificationBell.tsx:23`.
---
### 73. UPDATE-handler absolute-set field → CLEAR khi FE KHÔNG gửi field (bug-class S42 recurrence) (Session 85)
**Triệu chứng:** phiếu tạo Quy trình duyệt bấm "Sửa" (đổi tên/dự án) MẤT workflow kẹt submit "chưa chọn quy trình duyệt" (**whack-a-mole**: SQL-fix xong, ai bấm Sửa lại rụng tiếp). long.chau + anh Kiệt + nhiều account.
**Cơ chế:** `UpdatePeDraftHandler` absolute-set `entity.ApprovalWorkflowId = request.ApprovalWorkflowId`. Form Sửa (PeHeaderForm) KHÔNG gửi `approvalWorkflowId` command bind `null` (default `Guid? = null`) handler set `= null` = **CLEAR**. Field SÓT khỏi pattern null-safe (`if (request.X is not null)`) `BudgetPeriodAmount`/`ExpectedRemainingAmount`/`WorkItemId` NGAY DƯỚI đã (comment "client không gửi GIỮ giá trị " bug-class S42). 1 field lọt khỏi quy-ước đã-thiết-lập.
**Guard:** MỌI field optional trong UPDATE-handler PHẢI null-safe `if (request.X is not null) entity.X = request.X` (preserve khi không gửi); CHỈ field text-clearable CỐ-Ý (TenGoiThau/DiaDiem/MoTa) mới absolute-set. Khi thêm field vào UPDATE command **đối-chiếu pattern null-safe của field cạnh**. Triệu-chứng "vá-điểm xong TÁI-DIỄN" = nghi field-clear-on-omit, truy root KHÔNG vá-data lặp (root-cause-over-symptom).
**References:** `src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs:~282` (UpdatePeDraftHandler) · `fe-user/src/components/pe/PeHeaderForm.tsx:112` (PUT no-workflow) · bug-class S42 (BudgetPeriodAmount null-safe S61) · `tests/.../PeUpdateDraftWorkflowPreserveTests.cs`.
---
## Checklist debug bug mới
1. Build pass không? fail check using + package version compat