[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:
158
docs/HANDOFF.md
158
docs/HANDOFF.md
@ -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` có `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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user