[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
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:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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 Mig→57 · 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" mà form chưa có ô · 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ếu→dự án chờ spec.
|
||||
- **Ops:** email typo anh Chương · 5 real-staff pw · gán CNTT.
|
||||
@ -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 mà 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 là **giờ LOCAL** → nhưng giá-trị là 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 CÓ 'Z'. Lưu-UTC + convert-on-display (FE) là 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 CÓ 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 vá 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)`) mà `BudgetPeriodAmount`/`ExpectedRemainingAmount`/`WorkItemId` NGAY DƯỚI đã có (comment "client không gửi → GIỮ giá trị cũ" — 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
|
||||
|
||||
Reference in New Issue
Block a user