[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: -9dee00dChunk A — BE auto-seed Hạng mục mặc định + FE reorder section -2bba851Chunk B — Nested grid HangMucCard, NCC expand inline + drop SuppliersTab -f2f01f4Chunk 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:
171
docs/HANDOFF.md
171
docs/HANDOFF.md
@ -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 change — UAT 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` cũ
|
||||
- `✓` 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user