[CLAUDE] Docs: chốt Session 18 wrap-up — PE V2 polish + Clone B + Mig 25 IsUserSelectable + 4 bug fix UAT

Session 18 (16:56 → 19:45, 7 commit `aaa1c6c` → `32a8d4d`):
- B1 Pe Duyệt filter cứng "Đã gửi duyệt"
- B2 HistoryTab filter Trả lại / Gửi lại
- B3 Clone V2 cho B (DuyetNccPhuongAn) — audit reuse pattern
- B4 Fix silent 403 ApprovalWorkflowsV2Controller
- B5 Fix sidebar highlight queryMatches transient keys
- B6 Mig 25 IsUserSelectable + Designer pin toggle + bỏ "(clone)" + Workspace filter
- B7 Cleanup orphan zip files

Updates:
- STATUS — header 24→25 mig + 43→44 gotcha + 1 row Recently Done top + session log link
- HANDOFF — TL;DR S18 đầy đủ + cảnh báo S19+ (giữ S17 narrative §6.5)
- CLAUDE.md root — count 25 mig + Mig 25 description block
- schema-diagram §14 — heading 22→25 + cột IsUserSelectable + filter logic section + Pending S19+ Mig 26/27
- gotchas — +#44 silent 403 + checklist debug 21
- migration-todos — Phase 9 S18 done section
- session log mới đầy đủ E2E narrative

Stats: 25 mig, 58 tables, ~141 endpoints, 81 test pass (no change), 44 gotcha, 14 memory entries, 6 skill, 7 commit S18.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-08 19:56:42 +07:00
parent 32a8d4db0b
commit daad79d282
7 changed files with 511 additions and 10 deletions

View File

@ -1,6 +1,162 @@
# HANDOFF — Brief 5 phút cho session tiếp theo
**Last updated:** 2026-05-08 (Session 17 wrap-up — **🎯 PE Workflow V2 schema + Service WIRE end-to-end DONE. 13 commit `c847dc0``de0f38d`. Mig 22-24. State machine 5 trạng thái. Service PE iterate Steps/Levels match `ApproverUserId`. Designer max 3 cấp × N NV/cấp. Panel 3 flow render thực tế. Test prod cleaned + user `nv.test@solutions.com.vn` tạo. 81 test pass. Contract V2 wire DEFER session sau.**)
**Last updated:** 2026-05-08 19:45 (Session 18 wrap-up — **🎯 PE V2 polish + Clone B (DuyetNccPhuongAn) + 4 bug fix UAT + Mig 25 IsUserSelectable. 7 commit `aaa1c6c``32a8d4d`. Audit reuse pattern (memory `feedback_audit_reuse_before_clone`): clone B chỉ 3 file ~60 LOC vì schema chung qua ApplicableType discriminator. Bug silent 403 từ class-level Authorize policy quá strict — Drafter không list workflow để pick, Workspace dropdown empty không warning. Fix: class-level `[Authorize]` only, GET endpoint không cần `Workflows.Read`. Bug sidebar highlight mất khi click row do queryMatches exact-set vs URL có `id` transient → strip TRANSIENT_QUERY_KEYS trước compare. Mig 25 ALTER ApprovalWorkflows +IsUserSelectable bit (admin pin/unpin per version, multi-select, độc lập IsActive). Designer +badge "Cho user chọn" + button Ghim/Bỏ ghim. Workspace filter only IsUserSelectable. Bỏ "(clone)" auto-suffix khi clone version. Pe Duyệt: bỏ dropdown trạng thái + filter cứng "Đã gửi duyệt". Lịch sử thay đổi: chỉ events Trả lại / Gửi lại / sửa khi phase=TraLai (BE keep audit, FE filter). 81 test pass (no change — UAT defer test §7). 44 gotcha (+1 silent 403).**)
## TL;DR Session 18 — PE V2 polish + Clone B + 4 bug fix UAT
User UAT live tiếp Session 17, 7 batch nhỏ + 1 feature lớn (Mig 25). Áp memory `feedback_uat_skip_verify` (skip dotnet test mỗi chunk, push ngay) + lesson `0ae3fe2`: rename/remove → BẮT BUỘC `npm run build`.
### B1 (`aaa1c6c`) — Pe Duyệt filter cứng "Đã gửi duyệt"
User: "Duyệt bỏ cái trạng thái đi, chỉ load những trạng thái 'Đã gửi duyệt' là đc."
- Bỏ dropdown "Tất cả trạng thái" khỏi UI khi `pendingMe=true`, thay bằng hint amber "Lọc cố định: Đã gửi duyệt (phiếu đang chờ duyệt)"
- Filter cứng client-side: `getPeDisplayStatus(p.phase) === DaGuiDuyet` — loại Nháp/Trả lại/Đã duyệt/Từ chối
- Header count dùng `rows.length` khi `pendingMe` (inbox không paged)
- Workaround BE `/inbox` loose UAT có thể trả phiếu Nháp (phân quyền strict V2 pending Session 19+)
- Mirror fe-admin + fe-user `PurchaseEvaluationsListPage.tsx`
### B2 (`917446d`) — HistoryTab filter Trả lại / Gửi duyệt lại
User: "Lịch sử thay đổi: chỉ bắt các dòng thay đổi khi trả lại và gửi duyệt lại thôi nhé, không cần bắt trạng thái duyệt và các thay đổi trước khi trả lại."
- FE filter trong `PeDetailTabs.HistoryTab`, BE giữ audit data đầy đủ (reversible nếu user đổi ý / cần audit trail compliance)
- Logic giữ:
- Workflow transition về TraLai (`phaseAtChange === 98`)
- Workflow transition từ TraLai (summary chứa `"TraLai →"`)
- Mọi thay đổi nội dung (Header/Detail/Supplier/Quote/Attachment) khi `phaseAtChange === 98`
- Bỏ: workflow Approve cùng cấp (Cấp 1→2→DaDuyet), sửa khi phase=Nháp/ChoDuyet ban đầu
- Empty state: "Chưa có lịch sử trả lại / gửi duyệt lại"
### B3 (`937eb24`) — Clone V2 cho B (DuyetNccPhuongAn)
User: "Quy trình chọn thầu phụ - NCC → Duyệt NCC đúng. Plan kế hoạch clone toàn bộ updates sang Duyệt NCC và Giải pháp."
Audit reuse trước thay vì duplicate. Phát hiện 80% đã chung:
- Schema V2 (Mig 22-24) qua `ApplicableType` enum
- BE Service `ApproveV2Async` không hardcode type
- App CQRS / API `/approval-workflows-v2?applicableType=N` dynamic
- FE Designer `ApprovalWorkflowsV2Page``TYPE_CODE_TO_INT` cả 3 type
- Layout regex `^AwV2_(.+)$` match dynamic typeCode
- App.tsx route `/system/approval-workflows-v2/:typeCode` dynamic
Chỉ thiếu cho B: **menu key + sample seed** (3 file ~60 LOC).
- `MenuKeys.cs` +const `ApprovalWorkflowDuyetNccPhuongAnV2 = "AwV2_DuyetNccPhuongAn"` + add vào `All[]`
- `DbInitializer.SeedMenusAsync` +leaf "Duyệt NCC và Giải pháp (Mới)" dưới root ApprovalWorkflowsV2 (Order=2 cạnh leaf A Order=1)
- `DbInitializer +SeedSampleApprovalWorkflowsV2Async` (idempotent — skip nếu admin đã tạo workflow B nào, hoặc thiếu test user `nv.test`/Phòng CCM): seed `QT-DN-PA-V2-001 v01` 1 Bước Phòng CCM × 1 Cấp NV test
- `fe-admin/lib/menuKeys.ts` +`AwV2_DuyetNccPhuongAn`
KHÔNG migration / Service / Designer page mới. Memory `feedback_audit_reuse_before_clone.md` capture pattern.
User feedback "OK khá tốt, 1 phát chạy luôn :))" sau verify → confirm approach.
### B4 (`f77ea38`) — Fix silent 403 ApprovalWorkflowsV2Controller
Triệu chứng: Drafter `nv.test` Workspace tạo phiếu B → dropdown "Quy trình duyệt" empty mặc dù Admin Designer thấy 2 version (v01 sample + v02 admin clone).
Root cause: Class-level `[Authorize(Policy = "Workflows.Read")]` → non-admin role 403 Forbidden khi GET `/api/approval-workflows-v2`. TanStack Query catch error không hiện UI → dropdown rỗng silent.
Fix:
- Class-level đổi `[Authorize]` only (any authenticated user)
- GET endpoint inherit class policy — Drafter list workflow để pick read-only, không nhạy cảm
- POST + DELETE giữ `[Authorize(Policy = "Workflows.Create")]` admin-only Designer
Pattern reusable cho Contract V2 Mig 26 sau.
### B5 (`a9c0857`) — Fix sidebar highlight queryMatches transient keys
Triệu chứng: Ở leaf "Danh sách" `/purchase-evaluations?type=1`, click chọn 1 phiếu → URL thành `?type=1&id=abc` → leaf bị mất highlight box (gotcha #34 cũ tái phát theo cách khác).
Root cause: `queryMatches` exact-set equality — target `{type}` (1 key) vs current `{type, id}` (2 keys) length mismatch → no match → leaf unhighlight.
Fix: `TRANSIENT_QUERY_KEYS = {id, q, editHeader, page, phase, awId}` — strip trước khi compare. Mọi key navigation identity (`type`, `pendingMe`, `mode`) check exact-set như cũ.
Edge cases verified:
| URL hiện tại | Target leaf | Match |
|---|---|---|
| `?type=1&id=abc` | Danh sách `?type=1` | ✓ giữ highlight |
| `?type=1&pendingMe=1` | Danh sách `?type=1` | ✗ distinct (không cross-highlight Pending) |
| `?type=1&phase=10` | Danh sách `?type=1` | ✓ giữ highlight (filter dropdown) |
| `?type=1&pendingMe=1&awId=xyz` | Duyệt `?type=1&pendingMe=1` | ✓ giữ highlight |
Mirror fe-admin + fe-user `Layout.tsx`.
### B6 (`2a53107`) — Mig 25 IsUserSelectable + Designer pin toggle + bỏ "(clone)"
User feedback xem Admin Designer: "Bỏ chữ Clone đi nhé, ghi v02, v03... là đủ rồi. Thêm cho tao nút stick để chọn các quy trình nào mà User đc select bên ngoài khi tạo phiếu."
**Bỏ "(clone)":** Designer auto-fill `name = cloneFrom.name` (bỏ ` (clone)` suffix). Version số đã đủ phân biệt.
**Pin toggle "Cho user chọn":**
- **Migration 25** `AddIsUserSelectableToApprovalWorkflows`:
```sql
ALTER TABLE ApprovalWorkflows ADD IsUserSelectable bit NOT NULL DEFAULT 0;
-- Backfill (giữ behavior cũ — active workflows vẫn pickable):
UPDATE ApprovalWorkflows SET IsUserSelectable = 1 WHERE IsActive = 1;
```
- **Domain** `ApprovalWorkflow.IsUserSelectable` — independent với `IsActive`, multiple versions có thể cùng selectable (admin có thể "ghim" nhiều version cho user pick).
- **App CQRS:**
- `AwDefinitionDto` +field `IsUserSelectable`
- `CreateAwDefinitionCommand` Handler set default `true` cho version mới (mirror IsActive default)
- New `SetAwUserSelectableCommand(Guid Id, bool IsUserSelectable)` + Handler — toggle
- **API** `PATCH /api/approval-workflows-v2/{id}/user-selectable` policy `Workflows.Create` (admin only)
- **DbInitializer** `SeedSampleApprovalWorkflowsV2Async` +`IsUserSelectable = true`
- **FE Designer** (`fe-admin/ApprovalWorkflowsV2Page.tsx`):
- `DefinitionDto` +`isUserSelectable`
- Badge amber "📌 Cho user chọn" cạnh badge IsActive/Archived khi `isUserSelectable === true`
- Button "📌 Ghim cho user / 🚫 Bỏ ghim" trong action group + mutation `toggleSelectable` (call PATCH endpoint, invalidate query)
- **FE Workspace** (cả fe-admin + fe-user `PeWorkspaceCreateView.tsx`):
- approvalWorkflows query `.filter(w => w.isUserSelectable)` — chỉ workflows admin đã ghim hiện trong dropdown user
### B7 (`32a8d4d`) — Cleanup orphan zip files
`.claude.zip + docs.zip` từ harness session start lỡ tay vào `git add -A` ở B6 commit. Untrack + add `*.zip` rule `.gitignore`.
### Stats Δ Session 18
| | Trước S18 | Sau S18 |
|---|---:|---:|
| Migrations | 24 | **25** (+1) |
| DB tables | 58 | 58 (Mig 25 chỉ ALTER cột) |
| API endpoints | ~140 | **~141** (+1 PATCH user-selectable) |
| FE pages | 33 | 33 (modify existing only) |
| Test pass | 81 | 81 (no change — UAT feature defer test §7) |
| Gotchas | 43 | **44** (+1 silent 403) |
| Memory entries | 13 | **14** (+1 audit reuse pattern) |
| Skills | 6 | 6 (no add) |
| Commits | (after S17) | **+7** |
## ⚠️ Điều quan trọng cho Session 19+
1. **Contract V2 wire (Mig 26) — pending dedicated session.** Pattern audit-reuse áp dụng: phần lớn đã chung. Mirror PE pattern:
- Thêm `Contract.ApprovalWorkflowId` + `CurrentApprovalLevelOrder` (Mig 26)
- `ContractWorkflowService.ApproveV2Async` mirror PE pattern
- `ContractCreatePage` Workspace Select V2
- Pin V2 mặc định cho ContractType
- Permission GET endpoint đã permissive (Session 18 fix), không cần đụng
2. **Phân quyền strict V2 V2** — hiện loose UAT (mọi authenticated thấy mọi phiếu V2). Sau confirm flow:
- List = Drafter + approver any-Step + Admin
- Inbox = chỉ approver Cấp hiện tại (V2 đã đúng — `ResolveV2InboxIdsAsync`)
- Detail = same as List
- Cũng giải quyết được bug "/inbox loose trả phiếu Nháp" — sau khi strict, B1 FE filter có thể relax nếu BE đã filter đúng
3. **Drop legacy V1 cleanup** sau khi không còn phiếu pin `WorkflowDefinitionId` (V1):
- Drop tables `WorkflowDefinitions` + `WorkflowSteps` + `WorkflowStepApprovers` + PE versions
- Mig 27 cleanup drop column `RejectedAtStepIndex` + `RejectedFromPhase` deprecated S17
- Drop `ApproveV1LegacyAsync` branch trong Service
4. **Test V2 wire** (defer khi UAT confirm + có sample data) — Domain test `ApproveV2Async` match logic + transient TraLai entry → Cấp 1 reset.
5. **`feedback_audit_reuse_before_clone` memory** — áp dụng cho mọi "clone X sang Y" / "thêm type Z mới" sau này. List "đã chung" vs "còn thiếu" trước khi propose plan.
6. **Sample seed B sample** chạy với check `hasAnyB` — sau UAT có thể remove sample seed (admin đã tạo workflow thật). Hoặc giữ làm fallback. Idempotent skip nếu admin có workflow B → không clobber.
---
## TL;DR Session 17 — PE V2 schema end-to-end
## TL;DR Session 17 — PE V2 schema end-to-end