[CLAUDE] Docs: chốt Session 19 — PE Section 5 V2 dynamic + Mig 26
Chunk D Docs cho Session 19 (PE Section 5 dynamic theo ApprovalWorkflowLevel): - docs/STATUS.md — Recently Done row Session 19 chi tiết + header narrative 25→26 mig + 58→59 tables + 5 spec Q&A + 4 chunk per-commit - docs/HANDOFF.md — TL;DR Session 19 đầy đủ (polish 3 button + Chunk A/B/C summary + Stats Δ) + 7 cảnh báo Session 20+. Giữ TL;DR S18 nguyên văn theo §6.5 (KHÔNG cắt narrative) - docs/changelog/migration-todos.md — Phase 9 Session 19 done block với 5 commit + Stats final + Defer Session 20+ (8 task pending) - docs/changelog/sessions/2026-05-09-0400-pe-section-5-v2-dynamic-mig26.md — Session log mới, full Q&A + Chunks + Bug fix + Stats cumulative - CLAUDE.md (root) — count 25→26 mig + 58→59 tables + Mig 26 description KHÔNG đụng (per §6.5 không cố sửa khi không cần): - rules.md / architecture.md / PROJECT-MAP.md / workflow-contract.md / forms-spec.md / database-guide.md - Skills (6) — drift defer cron audit 2026-06-01 - schema-diagram.md §16 PE Level Opinions V2 — defer cùng §17-21 cron audit 2026-06-01 Path filter docs-only → CI sẽ skip deploy (gotcha #41).
This commit is contained in:
130
docs/HANDOFF.md
130
docs/HANDOFF.md
@ -1,6 +1,134 @@
|
||||
# HANDOFF — Brief 5 phút cho session tiếp theo
|
||||
|
||||
**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).**)
|
||||
**Last updated:** 2026-05-09 (Session 19 — **🎯 PE Section 5 V2 dynamic theo ApprovalWorkflowLevel + Mig 26. 4 commit `873e7a1` → Chunk D. Section 5 hiện CỨNG 4 box (Mig 15 PheDuyet/CCM/MuaHàng/SmPm) → động theo workflow V2 đã pin: forEach Step → forEach Level → forEach NV → 1 OpinionBox. 5 spec chốt Q&A trước code: Q1=1B (Service auto sync khi duyệt, KHÔNG form rời), Q2=2A+Admin (NV chính chủ + Admin override với SignedByUserId track signer thật), Q3=V2 hết (V1 legacy fallback Mig 15 readOnly), Q4=4C+bonus (Phase=DaDuyet/TuChoi khoá; comment empty → "(duyệt — không ý kiến)"), Q5=5A (group Step + grid-cols-2 N approvers). Mig 26 `AddPeLevelOpinionsForV2` bảng mới UNIQUE (PEId, LevelId), FK Cascade Pe + Restrict Level. Service `ApproveV2Async` UPSERT khi NV duyệt. DTO 15 fields denorm StepOrder/LevelOrder/ApproverUserId/ApproverFullName cho FE render trực tiếp. FE `LevelOpinionsSectionV2` + `LevelOpinionBox` mirror 2 app. Polish 3 button đầu session: "Duyệt/Trả lại/Từ chối" rút gọn + emerald/amber/red + bold (`873e7a1`). 81 test pass (no change — UAT defer §7).**)
|
||||
|
||||
## TL;DR Session 19 — PE Section 5 V2 dynamic theo Workflow + Mig 26
|
||||
|
||||
User feedback Section 5 hiện CỨNG 4 box (Mig 15 PheDuyet/CCM/MuaHàng/SmPm từ Phase 8) → cần ĐỘNG theo Workflow V2 đã pin với phiếu. Spec rõ: forEach Step (Phòng) → forEach Level (Cấp) → forEach NV → 1 OpinionBox với ý kiến + tên người. "Bước 1 phòng A có 2 NV → 2 box ngang hàng".
|
||||
|
||||
User chốt 5 câu Q&A trước code (capture trong session log):
|
||||
- Q1=**1B**: Comment khi NV nhấn Duyệt trong Workflow Panel auto sync sang OpinionBox của NV đó (Section 5 read-only summary). KHÔNG có form input rời.
|
||||
- Q2=**2A+Admin**: Chỉ NV chính chủ duyệt được. Admin override → lưu SignedByUserId=Admin.Id, FE banner "Admin <name> duyệt thay" khi SignedByUserId !== Level.ApproverUserId.
|
||||
- Q3=**V2 hết**: Phiếu V1 legacy → fallback render Mig 15 4 box CỨNG readOnly cho data history (KHÔNG drop Mig 15 — giữ data cũ).
|
||||
- Q4=**4C + bonus**: Phase=DaDuyet/TuChoi → khoá hoàn toàn. Admin có quyền duyệt thay. Comment empty/whitespace → ghi "(duyệt — không ý kiến)" placeholder.
|
||||
- Q5=**5A**: Layout group theo Step (header "Bước N — Phòng X" badge emerald + hint số người duyệt) + grid-cols-2 cho N approvers (wrap nếu N>2).
|
||||
|
||||
### Polish 3 button Hành động (873e7a1) — đầu session
|
||||
|
||||
Session 18 turn cuối user đã review screenshot "Hành động: Trả lại / Hủy/Từ chối / Duyệt → Chờ CCM" — yêu cầu rút gọn label + 3 màu khác nhau + bold. PeWorkflowPanel.tsx (mirror 2 app):
|
||||
- Label: "← Trả lại (về Drafter sửa)" → **"← Trả lại"** | "✗ Hủy / Từ chối" → **"✗ Từ chối"** | "✓ Duyệt → Chờ CCM" → **"✓ Duyệt"**
|
||||
- Phase đích vẫn hiện qua tooltip title khi hover
|
||||
- 3 màu: Duyệt = emerald (positive) · Trả lại = amber (request changes) · Từ chối = red (terminal)
|
||||
- font-medium → font-bold
|
||||
|
||||
### Chunk A (`77a3058`) — Domain entity + EF + Mig 26
|
||||
|
||||
`PurchaseEvaluationLevelOpinion : AuditableEntity`:
|
||||
- (PEId, ApprovalWorkflowLevelId) UNIQUE composite
|
||||
- Comment nvarchar(2000)
|
||||
- SignedAt datetime2 (luôn có khi UPSERT)
|
||||
- SignedByUserId Guid (NV chính chủ HOẶC Admin override)
|
||||
- SignedByFullName nvarchar(200) — denorm tránh user xóa/đổi tên
|
||||
|
||||
EF: FK Cascade Pe + Restrict Level. SignedByUserId KHÔNG nav (denorm only).
|
||||
Migration 26 `AddPeLevelOpinionsForV2`: 1 CREATE TABLE + 2 FK + 2 index. 3-file rule commit đủ. Apply LocalDB SolutionErp_Dev OK (Mig 25 + 26 catchup).
|
||||
|
||||
Verify: dotnet build pass + dotnet test 81 pass.
|
||||
|
||||
### Chunk B (`90baa8e`) — Service V2 hook + DTO + GET include
|
||||
|
||||
Service `ApproveV2Async` sau khi log approval → UPSERT row LevelOpinion cho Cấp hiện tại:
|
||||
```csharp
|
||||
var matchingLevel = pendingLevelGroup
|
||||
.FirstOrDefault(l => actorUserId.HasValue && l.ApproverUserId == actorUserId.Value)
|
||||
?? pendingLevelGroup.First(); // Admin override fallback first
|
||||
var actorFullName = await ResolveActorFullNameAsync(actorUserId, isSystem, ct);
|
||||
var existingOpinion = await db.PurchaseEvaluationLevelOpinions
|
||||
.FirstOrDefaultAsync(o => o.PurchaseEvaluationId == evaluation.Id
|
||||
&& o.ApprovalWorkflowLevelId == matchingLevel.Id, ct);
|
||||
var normalizedComment = string.IsNullOrWhiteSpace(comment)
|
||||
? "(duyệt — không ý kiến)"
|
||||
: comment.Trim();
|
||||
// UPSERT: if existing → update; else → Add new
|
||||
```
|
||||
|
||||
Reject (Trả lại / Từ chối) KHÔNG sync. Multi-NV cùng Cấp OR-of-N: match level theo ApproverUserId (NV chính chủ). Admin = fallback first; FE banner "Admin duyệt thay".
|
||||
|
||||
Helper `ResolveActorFullNameAsync` lookup denorm SignedByFullName từ Users (fallback "(System)" / "(unknown)").
|
||||
|
||||
DTO `PurchaseEvaluationLevelOpinionDto` 15 fields:
|
||||
- LevelId, StepOrder, StepName, StepDepartmentId, StepDepartmentName
|
||||
- LevelOrder, LevelName, ApproverUserId, ApproverFullName
|
||||
- Comment, SignedAt, SignedByUserId, SignedByFullName
|
||||
|
||||
GET handler Include LevelOpinions + `BuildLevelOpinionsAsync` JOIN ApprovalWorkflows.Steps.Levels + Departments + Users → denorm DTO. Empty list cho phiếu V1 / V2 chưa có cấp duyệt → FE fallback message.
|
||||
|
||||
Verify: dotnet build pass + dotnet test 81 pass.
|
||||
|
||||
### Chunk C (`6e913b3`) — FE Section 5 V2 dynamic mirror 2 app
|
||||
|
||||
Type `PeLevelOpinion` (15 field) + `PeDetailBundle.levelOpinions[]`.
|
||||
|
||||
Section 5 PeDetailTabs conditional:
|
||||
```tsx
|
||||
{evaluation.approvalWorkflowId
|
||||
? <LevelOpinionsSectionV2 ev={evaluation} />
|
||||
: <DepartmentOpinionsSection ev={evaluation} readOnly={opinionsReadOnly} />}
|
||||
```
|
||||
|
||||
`LevelOpinionsSectionV2`:
|
||||
- Empty state khi `flow null` / `0 steps` → message "Workflow chưa cấu hình hoặc chưa có cấp duyệt nào"
|
||||
- forEach `step` → header "Bước N — <step.name>" + dept badge emerald + hint "(N người duyệt)" nếu totalApprovers > 1
|
||||
- Body grid-cols-2 cho `step.levels.flatMap(level => level.approvers.map(approver => <LevelOpinionBox/>))`
|
||||
- Lookup opinion theo (stepOrder, levelOrder, approverUserId) match levelOpinions[]
|
||||
|
||||
`LevelOpinionBox` read-only:
|
||||
- Title "Cấp N — <ApproverFullName>"
|
||||
- Badge amber "⚠ Admin <SignedByFullName> duyệt thay" khi `signedByUserId !== approverUserId`
|
||||
- Badge emerald "✓ Đã duyệt" khi opinion tồn tại
|
||||
- Empty: "— chưa duyệt" italic gray
|
||||
- Footer: timestamp signedAt format vi-VN
|
||||
|
||||
Workspace mode hint amber giữ "Ý kiến + chữ ký auto đồng bộ khi NV duyệt phiếu — vào menu Duyệt để ký."
|
||||
|
||||
Mirror fe-admin + fe-user (rule §3.9).
|
||||
|
||||
Verify: npm run build × 2 pass · 0 TS error.
|
||||
|
||||
### Stats Δ Session 19
|
||||
|
||||
| | Trước S19 | Sau S19 |
|
||||
|---|---:|---:|
|
||||
| Migrations | 25 | **26** (+1 Mig 26) |
|
||||
| DB tables | 58 | **59** (+1 PeLevelOpinions) |
|
||||
| API endpoints | ~141 | ~141 (no new — UPSERT auto qua Service hook) |
|
||||
| FE pages | 33 | 33 (modify existing only) |
|
||||
| Test pass | 81 | 81 (no change — UAT defer test §7) |
|
||||
| Gotchas | 44 | 44 |
|
||||
| Memory entries | 14 | 14 |
|
||||
| Skills | 6 | 6 |
|
||||
| Commits | (after S18) | **+4** (873e7a1 + 77a3058 + 90baa8e + 6e913b3 + Chunk D Docs) |
|
||||
|
||||
## ⚠️ Điều quan trọng cho Session 20+
|
||||
|
||||
1. **Test V2 Service wire mới (Chunk B Service hook)** — defer khi UAT user UAT confirm + có sample data Production. Domain test ApproveV2 + UPSERT opinion match logic + Admin override match firstLevel + comment empty placeholder.
|
||||
|
||||
2. **Drop Mig 15 cho V2 phiếu (cleanup sau UAT confirm)** — sau khi không còn phiếu V2 dùng `PurchaseEvaluationDepartmentOpinions` (tất cả phiếu V2 chỉ dùng Mig 26 LevelOpinions). Mig 27 cleanup drop bảng + entity. Phiếu V1 legacy giữ Mig 15. Hoặc giữ cả 2 để backward compat.
|
||||
|
||||
3. **Migrate phiếu V1 cũ sang V2 (data migration)** — admin tool chuyển `ApprovalWorkflowId` từ null → V2 workflow phù hợp + clear `WorkflowDefinitionId`. Hiện chưa làm (Q3 user nói chuyển V2 hết = phiếu MỚI dùng V2, phiếu V1 cũ giữ legacy không migrate — đơn giản hơn).
|
||||
|
||||
4. **Contract V2 wire (Mig 27 hoặc 28) + Section 5 dynamic Contract** — mirror PE Mig 26 pattern: thêm `Contract.ApprovalWorkflowId` + `CurrentApprovalLevelOrder` (Mig 27) + `ContractLevelOpinions` (Mig 28) + Service `ApproveV2Async` mirror PE + ContractDetailContent Section 5 V2. Audit-reuse pattern memory `feedback_audit_reuse_before_clone` áp dụng.
|
||||
|
||||
5. **Phân quyền strict V2** — vẫn loose UAT. Sau confirm V2 flow (S19 Section 5 + S18 polish OK):
|
||||
- List = Drafter + approver any-Step + Admin
|
||||
- Inbox = chỉ approver Cấp hiện tại (V2 đã đúng)
|
||||
- Detail = same as List
|
||||
|
||||
6. **schema-diagram §16 PE Level Opinions V2** — thêm khi Chunk D update. Mig 22-25 V2 schema vẫn defer cron audit 2026-06-01.
|
||||
|
||||
7. **Skill `ef-core-migration` frontmatter "21 migration" stale** (thực 26). Defer cron audit 2026-06-01.
|
||||
|
||||
---
|
||||
|
||||
## TL;DR Session 18 — PE V2 polish + Clone B + 4 bug fix UAT
|
||||
|
||||
|
||||
Reference in New Issue
Block a user