[CLAUDE] Docs: chốt Session 20 — PE Detail UI restructure 3 yêu cầu UX (Chunk D)

Wrap-up docs cho 3 chunk code đã push:
- 9dee00d Chunk A — BE auto-seed Hạng mục mặc định + FE reorder section
- 2bba851 Chunk B — Nested grid HangMucCard, NCC expand inline + drop SuppliersTab
- f2f01f4 Chunk C — Section Ý kiến gộp đồng cấp cùng Phòng (1 box/Step, chỉ hiện signed)

Files updated:
- docs/STATUS.md — Last updated + Recently Done row S20 trên cùng (giữ S19 nguyên văn §6.5)
- docs/HANDOFF.md — Last updated + TL;DR Session 20 section ở đầu + pending S21+ + hard blocker ops (giữ S19 nguyên văn §6.5)
- docs/changelog/migration-todos.md — Phase 9 Session 20 done section + 9 defer item S21+ (giữ S19 nguyên văn §6.5)
- docs/changelog/sessions/2026-05-11-1100-pe-ui-restructure-s20.md (NEW) — session log

KHÔNG đụng rules/architecture/PROJECT-MAP/workflow-contract/forms-spec/database-guide/
schema-diagram/CLAUDE.md per §6.5 (S20 không thêm migration / gotcha mới, drift unchanged từ S19 → defer cron audit 2026-06-01).

Path filter CI sẽ skip (docs-only commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-11 10:12:13 +07:00
parent f2f01f4765
commit f8e5675edf
4 changed files with 404 additions and 2 deletions

View File

@ -1,6 +1,175 @@
# HANDOFF — Brief 5 phút cho session tiếp theo
**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 changeUAT defer §7).**)
**Last updated:** 2026-05-11 (Session 20**🎯 PE Detail UI restructure 3 yêu cầu UX user (4 chunk `9dee00d``2bba851``f2f01f4`→Docs). FE-only restructure (1 hook BE nhẹ auto-seed Detail). Q1=a giữ Section "Chọn NCC TP" riêng / Q2=a NCC shared + 1 hạng mục demo / Q3=a chỉ hiện NV đã ký / Q4 public luôn (skip dotnet test, vẫn npm build × 2 app mỗi chunk vì rename/remove function). Chunk A: BE CreatePE handler thêm 1 PurchaseEvaluationDetail mặc định (NoiDung=TenGoiThau, ThanhTienNganSach=Budget.TongNganSach||BudgetManualAmount||0) + FE reorder section Hạng mục lên #2. Chunk B: ItemsTab restructure list HangMucCard 1 card / hạng mục với expand panel chứa NCC inline table 8 cột (NCC/Liên hệ/Điều khoản TT/File báo giá/ĐG chưa VAT/ĐG có VAT/Thành tiền/Action). Click cell quote → QuoteDialog reuse. Drop SuppliersTab function ~134 LOC dead code, giữ 2 dialog + SupplierAttachmentsCell. Section 4 NCC tham gia gộp vào Section 2 → 4 section final (Thông tin/Hạng mục nested/Chọn NCC TP thắng thầu/Ý kiến). Chunk C: LevelOpinionsSectionV2 forEach step → 1 StepOpinionsBox (replace grid-cols-2 N approvers). Header "Bước N — Tên" + dept badge + "X/Y đã duyệt" counter. Body filter signed opinions sort levelOrder asc + signedAt asc → StepOpinionEntry per signed (tên + Cấp badge + admin override badge + timestamp + comment). NV chưa duyệt KHÔNG hiển thị. KHÔNG đụng Mig 26 schema. Drop LevelOpinionBox function. 81 test pass unchanged (UAT defer).**)
## TL;DR Session 20 — PE Detail UI restructure 3 yêu cầu user UX
User UAT live feedback: "Logic khá OK rồi, điều chỉnh giao diện chỗ Duyệt NCC 1 tý". 3 yêu cầu cụ thể:
1. Hạng mục đưa lên phía trên + auto-tạo 1 hạng mục từ gói thầu (tên = TenGoiThau, giá trị = ngân sách)
2. NCC expand dưới hạng mục (tầng 1 = hạng mục, tầng 2 = NCC, thông tin nhập trên grid)
3. Section Ý kiến: gộp comment đồng cấp cùng Phòng → 1 ô / bước (chỉ hiện NV đã duyệt)
### Q&A clarify trước code (chốt scope)
- **Q1=a**: Giữ Section "Chọn NCC TP thắng thầu" riêng (rõ UX, không gộp dropdown winner vào nested grid)
- **Q2=a**: NCC shared cross-hạng mục (như schema PE.Suppliers hiện tại) — "nhưng hiện chỉ cần 1 hạng mục trước tiên" → đơn giản scope Chunk B
- **Q3=a**: CHỈ hiển thị NV đã ký (KHÔNG show placeholder "— chưa duyệt")
- **Q4 public luôn demo**: Phase 9 UAT iteration skip `dotnet test` mỗi chunk, vẫn chạy `npm run build` × 2 app mỗi chunk (rule UAT skip verify exception cho rename/remove function — đã catch TS6133 SuppliersTab + SupplierAttachmentsCell)
### Chunk A (`9dee00d`) — BE auto-seed Hạng mục + FE reorder section
**BE — `PurchaseEvaluationFeatures.cs` `CreatePurchaseEvaluationCommandHandler`:**
```csharp
var defaultBudgetValue = linkedBudgetTotal ?? request.BudgetManualAmount ?? 0m;
var defaultDetail = new PurchaseEvaluationDetail
{
PurchaseEvaluationId = entity.Id,
GroupCode = "01",
GroupName = "Hạng mục chính",
NoiDung = request.TenGoiThau,
DonViTinh = "gói",
KhoiLuongNganSach = 1m,
KhoiLuongThiCong = 1m,
DonGiaNganSach = defaultBudgetValue,
ThanhTienNganSach = defaultBudgetValue,
Order = 1,
};
db.PurchaseEvaluationDetails.Add(defaultDetail);
// + Changelog Insert audit
```
`linkedBudgetTotal` mới: nếu PE link Budget, fetch `Budget.TongNganSach` (computed sum BudgetDetails). Nếu không link, fall back `BudgetManualAmount`. Nếu cả 2 null → 0.
**FE — Reorder section** (mirror fe-admin + fe-user, Chunk A intermediate state):
1.Thông tin / **2.Hạng mục (lên #2)** / 3.Chọn NCC / 4.NCC tham gia / 5.Ý kiến.
Verify: `dotnet build SolutionErp.slnx` 0 warn / 0 err.
### Chunk B (`2bba851`) — Nested grid Hạng mục → NCC expand
Restructure `ItemsTab` thành list `HangMucCard` (1 card / 1 hạng mục, expanded=true mặc định cho 1 hạng mục demo).
**HangMucCard structure:**
```
┌──────────────────────────────────────────┐
│ ▼ 01 · Tên hạng mục KL ĐG TT NS │ ← Header row
│ ──────────────────────────────────────── │
│ NCC tham gia (3) [+ Thêm NCC] │ ← Sub-header
│ ┌──────────────────────────────────────┐ │
│ │ NCC │ Liên hệ │ ĐK │ File │ giá ... │ │ ← Inline NCC table
│ ├──────────────────────────────────────┤ │
│ │ NCC X │ ... │ ... │ ... │ ... │ │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────┘
```
Header card: GroupCode + NoiDung + 3 stat (KL/ĐG ngân sách/TT ngân sách) + NS link Δ (nếu có Budget link) + Pencil/Trash actions + ▼/▶ toggle expand. Expand body: NCC inline table 8 cột (NCC / Liên hệ / Điều khoản TT / **File báo giá** / ĐG chưa VAT / ĐG có VAT / Thành tiền / Action).
**Tương tác:**
- Click cell quote (chưa VAT / có VAT / Thành tiền) → mở `QuoteDialog` cũ (reuse)
- `+ Thêm NCC` button trong expand panel → `AddSupplierDialog` cũ (reuse)
- `✏` icon mỗi NCC row → `EditSupplierDialog`
- `✓` icon → `setWinner` mutation, row + cell ăn theo màu emerald
- `🗑` icon disabled khi NCC là winner hoặc đã có quote (giữ logic cũ)
- `SupplierAttachmentsCell` nhúng vào cell "File báo giá" — full CRUD upload/download/delete file
**Drop dead code:**
- Function `SuppliersTab` xóa hoàn toàn (~134 LOC) — replace bằng `HangMucCard` expand panel
- Bỏ Section 4 "NCC tham gia" cũ trong main render PeDetailTabs (gộp vào Section 2)
**Section layout cuối** (4 section):
1. Thông tin gói thầu
2. Hạng mục + Báo giá NCC (nested expand)
3. Chọn NCC / TP thắng thầu
4. Ý kiến cấp duyệt
Verify: `npm run build` × 2 app pass (sau khi catch TS6133 SuppliersTab unused → drop + SupplierAttachmentsCell unused → restore vào cột "File báo giá").
### Chunk C (`f2f01f4`) — Section Ý kiến gộp đồng cấp cùng Phòng
**FE-only mirror 2 app**. KHÔNG đụng Mig 26 schema (vẫn UPSERT 1 row / Level trong `PurchaseEvaluationLevelOpinions` qua `ApproveV2Async` Service). Chỉ thay đổi render layer.
**Trước (S19 LevelOpinionsSectionV2):**
```
forEach step:
div.grid-cols-2:
forEach level:
forEach approver:
<LevelOpinionBox /> (1 box / NV)
- Cấp N — Tên NV
- "Đã duyệt" emerald badge or "— chưa duyệt" italic gray
- comment text
- admin override badge nếu signedBy !== approver
```
**Sau (Chunk C):**
```
forEach step:
<StepOpinionsBox stepOrder stepName departmentName totalApprovers opinions={stepOpinions} />
- Header: "Bước N — Tên" + dept badge emerald + "X/Y đã duyệt" counter
- Body:
- empty → "— Chưa có ý kiến duyệt." italic gray
- else → list <StepOpinionEntry opinion /> per signed opinion
(sort levelOrder asc, signedAt asc)
```
`StepOpinionEntry`:
- Header trái: ApproverFullName + "Cấp N" badge slate + admin override badge amber nếu có
- Header phải: emerald rounded-full timestamp "✓ DD/MM/YYYY HH:mm"
- Body: comment text whitespace-pre-wrap
NV chưa duyệt KHÔNG hiển thị (Q3=a) — chỉ 1 box / Step thay vì N box / NV như cũ.
**Drop dead code:**
- Function `LevelOpinionBox` xóa (~50 LOC) — replace bằng `StepOpinionsBox` + `StepOpinionEntry`
Verify: `npm run build` × 2 app pass.
### Pending Session 21+ (carry over từ HANDOFF S19 + còn nguyên)
1. **Test V2 Service wire** (Chunk B Service hook S19 + Section gộp Chunk C S20) — defer chờ UAT user confirm + có sample data Production. Domain test ApproveV2 + UPSERT opinion match logic + Admin override match firstLevel + comment empty placeholder + render gộp Step.
2. **Test regression B4 silent 403 S18** (HIGH §7 priority — vi phạm rule "test-before bug fix") — per-action `[Authorize(Policy=...)]` ApprovalWorkflowsV2Controller.
3. **Test Mig 25 PATCH `/user-selectable`** endpoint (admin scope hẹp, MED).
4. **🎯 Contract V2 wire (Mig 27/28 mirror PE pattern)** — biggest pending Plan. Audit-reuse memory áp dụng:
- Mig 27: `Contract.ApprovalWorkflowId` Guid? + `CurrentApprovalLevelOrder` int?
- Mig 28: `ContractLevelOpinions` mirror PE Mig 26 (UNIQUE composite, FK Cascade/Restrict)
- `ContractWorkflowService.ApproveV2Async` mirror PE branch
- `ContractCreatePage` Workspace Select V2 (validate ApplicableType=Contract=3)
- Pin V2 mặc định cho ContractType (admin Designer)
- `ContractDetailContent` Section "Ý kiến cấp duyệt" V2 dynamic (mirror S20 Chunk C — 1 box / Step)
5. **Phân quyền strict V2** — vẫn loose UAT (mọi authenticated user 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 S18 có thể relax
6. **Drop legacy V1 cleanup** sau khi không còn phiếu pin `WorkflowDefinitionId`:
- Drop tables `WorkflowDefinitions` + `WorkflowSteps` + `WorkflowStepApprovers` + PE versions
- Mig 29+ cleanup drop column `RejectedAtStepIndex` + `RejectedFromPhase` deprecated S17
- Drop `ApproveV1LegacyAsync` branch trong Service
7. **Drop Mig 15 cho V2 phiếu** sau UAT confirm — Mig 30 cleanup drop bảng `PurchaseEvaluationDepartmentOpinions` + entity. Hoặc giữ cả 2 backward compat (Q3 user chốt phiếu MỚI dùng V2, V1 cũ giữ legacy không migrate).
8. **schema-diagram §16 PE Level Opinions V2** + §17-21 Mig 18-21 — defer cron audit 2026-06-01.
9. **Skill `ef-core-migration`** frontmatter "21 migration" stale (thực 26) — defer cron audit 2026-06-01.
### Hard blockers ops (carry over từ Session 19)
- UAT thật 1 tuần với 2-3 user (Drafter/CCM/BOD)
- SMTP config → Email outbox (BLOCKED chờ user cấp host/user/pass)
- Rotate creds (admin + 30 demo + SA + vrapp + JWT secret + Gitea runner token)
- Schedule SQL backup daily — `scripts/backup-sql.ps1` chưa schedule Task Scheduler
- Remove binding cũ `.huypham.vn` sau verify stable
- win-acme scheduled task "unhealthy" — auto-renew fix trước 2026-06-18
### Audit định kỳ
- Lần gần nhất: 2026-05-04 (manual trễ 4 ngày) — log `docs/changelog/skill-audit-2026-05.md`
- Lần kế: **2026-06-01** combined audit (skill + doc drift). Drift hiện tại unchanged từ S19 (S20 không thêm migration / gotcha mới):
- `ef-core-migration` "21 migration" stale (thực 26)
- `dependency-audit-erp` count gotcha 41 stale (thực 44)
- `schema-diagram` §16 PE Level Opinions V2 + §17-21 Mig 18-21 pending
- Cron Claude SDK KHÔNG fit monthly (auto-expire 7d, memory `feedback_cron_monthly_limitation`) — manual trigger khi đến ngày hoặc user nói "audit MD" / "kiểm tra docs"
## TL;DR Session 19 — PE Section 5 V2 dynamic theo Workflow + Mig 26