[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

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