Files
solution-erp/docs/changelog/sessions/2026-04-22-0300-tier3-feature-complete.md
pqhuy1987 fbca83264c
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m33s
[CLAUDE] Docs: chốt session Tier 3 feature-complete + versioned workflow
- Session log 2026-04-22-0300 (A→K): attachment, SignalR, form builder,
  PDF, dynamic + versioned workflow, nested menu, 3-panel permissions,
  seed master, brand identity, content polish, Gitea fix
- STATUS: Tier 3 feature-complete snapshot + cumulative stats (24 tables,
  ~50 endpoints, 8 migrations); next-up = UAT + Email SMTP (blocked) +
  rotate creds + SQL backup schedule
- HANDOFF: rewrite brief cho session mới — phase 5 prod done, Tier 3
  đóng gói, quick sanity-check 2 app, versioned workflow quick ref,
  file active hiện trạng, git state
- migration-todos: tick Tier 3 items (attachment/realtime/form builder/
  PDF/dynamic+versioned workflow/nested menu) + thêm iter-3 versioned
  workflow section + post-launch list
- schema-diagram: +5 table (Notifications, WorkflowTypeAssignments,
  WorkflowDefinitions, WorkflowSteps, WorkflowStepApprovers); indexes
  mới, cardinality FK restrict cho pinned policy, truy vấn tiêu biểu
- workflow-contract: +section 7bis resolution order, 7ter admin
  designer flow, updated data model + code pointers Tier 3
- PROJECT-MAP: module map post-Tier-3 (3 box mới Notification/
  Attachment/Branding + Infra/DevOps box), API namespace đầy đủ,
  architectural wins 5 điểm
- contract-workflow skill: versioned workflow section, policy
  resolution code snippet, admin designer flow, code pointers Tier 3,
  tier 4+ backlog
- gotchas +7 bẫy mới (#26-32): SignalR WebSocket headers, interceptor
  2-phase pattern, LibreOffice mirror 404, PS 5.1 UTF-16 GITHUB_PATH,
  PS 5.1 diacritics parse, Dialog size TS, NavLink end query-params

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:25:02 +07:00

207 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Session 2026-04-22 ~03:00 — Tier 3 feature-complete + versioned workflow
**Focus:** Hoàn thành toàn bộ Tier 3 ERP features, pivot workflow từ hardcoded
policy → versioned DB-backed designer, chia nested menu cho fe-user + admin
workflow management riêng.
Session kéo dài 2 phiên (21/04 chiều — 22/04 sáng), tổng ~20+ commit.
## Outcomes
### A. Attachment upload E2E ✓
- `IFileStorage` abstraction + `LocalFileStorage` (Application/Infra split,
path-traversal guard, CREATEDIRECTORY-if-missing).
- CQRS: Upload / Download / Delete, validation 20MB + MIME whitelist (pdf/doc
(x)/xls(x)/png/jpg/webp), sanitize filename.
- Endpoints: POST multipart / GET download stream / DELETE.
- FE `ContractAttachmentsSection` (both apps) — drag-drop, purpose selector,
icon-per-MIME, auth-blob download, confirm delete.
- Integrated vào ContractDetailPage cả 2 app.
### B. SignalR realtime notifications ✓
- Clean-arch 3-project split: `IRealtimeNotifier` (Application) +
`SignalRNotifier` (Api) + `NotificationPushInterceptor` (Infrastructure
SaveChanges hook). Zero caller changes — `db.Notifications.Add()` auto-push.
- Hub `/hubs/notifications` JWT via `?access_token=` query string (WebSocket
headers limit).
- FE `lib/realtime.ts` singleton connection + auto-reconnect backoff + stop
on logout. NotificationBell subscribe `notification-created` → toast +
invalidate query.
- IIS WebSocket module installed trên VPS.
### C. Form template builder CRUD + DynamicForm ✓
- BE: Upload / Update / Delete templates (multipart, FormCode regex + unique,
FieldSpec JSON validation). `.doc`/`.xls` auto-convert sang `.docx`/`.xlsx`
qua `IDocumentConverter` khi upload.
- FE admin FormsPage: upload dialog với file picker + FormCode + Loại HĐ +
FieldSpec JSON textarea. Row actions 3 nút (render / edit / delete).
- `DynamicForm` component: parse FieldSpec JSON (text/textarea/number/date/
currency/select), render form inputs. Render dialog có tab toggle Form ↔ JSON.
### D. PDF export (LibreOffice headless) ✓
- `IDocumentConverter` generalized (docx→pdf, doc→docx, xls→xlsx, etc).
- `LibreOfficeDocumentConverter` shells `soffice.exe --headless --convert-to`,
per-request temp workDir + isolated UserInstallation (concurrent-safe),
60s timeout, kill process tree.
- Endpoint: POST `/api/forms/templates/{id}/export-pdf` pipe render → PDF.
- FE Tải PDF button cạnh Tải file gốc trong render dialog.
- LibreOffice 25.8.6 installed trên VPS via `scripts/install-libreoffice.ps1`.
- E2E verified: PDF 488KB / 126 pages.
### E. Dynamic + versioned workflow per ContractType ✓
**Phase 1 — Dynamic policy selection:**
- `WorkflowPolicy` record (Domain) + registry với 2 policy: Standard (8 phase
full CCM) + SkipCcm (7 phase bỏ CCM). Map ContractType → policy theo QT docx.
- `ContractWorkflowService.ForContract()` dùng registry.
- FE xóa hardcoded `NEXT_PHASES`, dùng `contract.workflow.nextPhases` từ
`ContractDetailDto.Workflow`. `WorkflowSummaryCard` timeline visual.
- Admin `/system/workflows` page (Phase 1) với dropdown Standard/SkipCcm per
ContractType (DB override `WorkflowTypeAssignment`).
**Phase 2 — Versioned workflow (user request "Khi add quy trình mới → HĐ cũ
giữ quy trình cũ"):**
- 3 entities mới: `WorkflowDefinition` (Code+Version+IsActive+ContractType),
`WorkflowStep` (Order+Phase+Name+SlaDays), `WorkflowStepApprover`
(Kind: Role|User + AssignmentValue).
- `Contract.WorkflowDefinitionId` nullable FK — pinned at create time.
- Migration `AddVersionedWorkflows`. Seed v01 per 7 ContractType từ hardcoded
policies (Role approvers).
- `WorkflowPolicyRegistry.FromDefinition()` — build runtime policy từ
WorkflowDefinition's Steps. Role-based transitions derive từ Role-kind
approvers, User-kind fallback DeptManager (iteration 2 sẽ enable user-level).
- `ContractWorkflowService` + `ContractFeatures.Get()`: load pinned
WorkflowDefinition → FromDefinition → runtime policy.
- CreateContract pin `WorkflowDefinitionId = active version for type`.
- Admin UI `/system/workflows/:typeCode` (URL-driven, sidebar menu replaces
tabs):
- Landing: 3-col grid card per 7 type với active version badge
- Per-type page: DefinitionCard (active + history), "Archived · N HĐ còn
chạy" count, Designer modal cho create-new-version (code/name/desc,
repeatable steps, per-step approvers + Role hoặc + User select).
- Clone-from-version button cho starting point sensible.
- POST `/api/workflows` create-new-version: auto-increment Version, deactivate
old IsActive, atomic.
- Invariants:
- Unique (Code, Version)
- Chỉ 1 IsActive per ContractType tại 1 thời điểm
- HĐ cũ giữ version cũ (WorkflowDefinitionId pinned, not FK cascade)
- E2E verified: tạo QT-MB-v02 → v01 archived, HĐ mới type=5 pin v02
`policyName: "QT-MB-v02"`, 5 bước custom [2,3,7,8,9,99].
### F. Nested sidebar menu per ContractType (fe-user) ✓
- BE seed 7 type groups × 3 action leaves (28 entries) dưới `Contracts`:
- `Ct_<Code>` group + `Ct_<Code>_List/Create/Pending` leaves
- `GetMyMenuTreeQuery` generalized inherit-permission: descendants of
`Contracts` hoặc `Workflows` inherit parent CanRead (no per-leaf perm rows).
- fe-user Layout: recursive `MenuNodeRenderer` (top-level expanded, nested
collapsed). Ct_*_List → `/my-contracts?type=X`, Ct_*_Create →
`/contracts/new?type=X`, Ct_*_Pending → `/inbox?type=X`.
- MyContractsPage + InboxPage read `?type=X`, filter client-side.
- **Menu split**: admin hide `Ct_*`, user hide `Master/System/Forms/Reports`.
### G. Admin Workflows tabs → sidebar menu items ✓
- Seed 7 `Wf_<Code>` leaves dưới `Workflows` group.
- Layout resolvePath `Wf_<Code>``/system/workflows/<code>`.
- WorkflowsPage bỏ tab bar; URL param drives type selection. Landing 7-card
grid khi click top-level `Quy trình HĐ` without type.
- Inheritance: `Workflows.Read` perm → tất cả 7 leaves auto-visible.
### H. PermissionsPage 3-panel layout ✓
- Grid `lg:grid-cols-[280px_1fr_300px]`:
- Panel 1 (trái): Role list click-to-select với active ring-brand
- Panel 2 (giữa): Menu × CRUD matrix + sticky thead + search + column
bulk-toggle + row brand-tinted hover
- Panel 3 (phải): Granted progress bar + CRUD breakdown color-coded badges
(slate/emerald/amber/red) + Tip
### I. Seed master data + MyDashboard ✓
- DbInitializer: 9 departments từ QT docx (PM/QS/CCM/PRO/FIN/ACT/EQU/HRA/BOD),
5 demo suppliers (5 SupplierType), 3 demo projects. Idempotent.
- Endpoint `/api/reports/my-dashboard`: DraftsInProgress / PendingMyApproval /
DueSoon / Overdue / DraftsTotalValue.
- FE DashboardPage "Của tôi" row 4 card, hover-interactive, admin auto-hide
nếu tất cả = 0.
### J. Brand identity + content polish (earlier in session) ✓
- Solutions logo cropped (pixel-sampled #1F7DC1) + full palette brand-50..900
+ Be Vietnam Pro font.
- SlaTimer, InboxPage stat cards, DataTable skeleton, EmptyState.
- TopBar + NotificationBell + UserMenu (ERP shell).
### K. Gitea 500 fix (side-effect) ✓
- `Install-WindowsFeature Web-WebSockets` khóa section `<webSocket>`
applicationHost → all IIS sites with `<webSocket enabled="true">` sập.
- Fix: `appcmd unlock config -section:system.webServer/webSocket`.
- Documented as gotcha #25.
## Commits (chronological, partial)
```
Earlier (21/04):
c8d0070 — Attachment upload E2E
ea9ab5e — SignalR realtime E2E
166d26c — Form template builder CRUD
6bbd894 — PDF export (LibreOffice)
e459097 — DynamicForm + .doc auto-convert
cae4d84 — Dynamic workflow policy per ContractType
6197c84 — Seed master data + MyDashboard
48e91fe — Nested sidebar menu (admin)
5e0f380 — Menu split (admin hide, user show) + workflow config static
4abb559..bf1fbe3 — Brand identity (Solutions logo + palette + fonts)
346bd5d — Content polish (typography, PageHeader, Button, Input, DataTable)
290936a..2e43799 — Tier 1 UI (SlaTimer, Inbox stats, Skeleton, EmptyState)
2b6f91c — ERP shell (TopBar, NotificationBell, UserMenu)
6c0e206 — PermissionsPage iter 1 (search + stats + bulk toggle)
Today (22/04):
e7e5f2d — Versioned workflow entities + migration + designer
355bbe3 — Fix Dialog size TS (xl → lg)
f216169 — Workflows tabs → sidebar menu items
91b2da1 — PermissionsPage 3-panel layout
```
## Key architectural decisions
1. **WorkflowPolicy runtime build from WorkflowDefinition DB rows** (not stored
as JSON blob) — allows admin to edit steps/approvers granularly without
JSON parser UX.
2. **WorkflowDefinitionId pinned at contract create** — zero-cost immutability
guarantee. Old contracts protected from workflow changes by reference, not
by snapshot copy.
3. **Permission inheritance via menu ancestry** (Contracts / Workflows roots)
— keeps Permissions table small while supporting deep navigation menus.
4. **3-project clean-arch split for cross-cutting services** (realtime
notifications, document conversion) — each service has abstraction in
Application + implementation in Infra/Api.
5. **Role + User approvers per step** (data model) but only Role-kind drives
runtime guard v1 — user-level targeting deferred to iter 2.
## Runtime workflow resolution (critical path)
```
Contract.TransitionAsync:
if contract.WorkflowDefinitionId not null:
def = db.WorkflowDefinitions.Include(Steps.Approvers).First(wfId)
policy = WorkflowPolicyRegistry.FromDefinition(def)
elif admin has override in WorkflowTypeAssignments for contract.Type:
policy = Registry.ByName(override.PolicyName)
else:
policy = Registry.For(contract.Type) // hardcoded Standard/SkipCcm
if not policy.Transitions.HasKey((from, to)): throw Forbidden
if not actor.Roles.Any(r => allowed.Contains(r)): throw Forbidden
```
## Next session priority
1. **UAT với 2-3 user thật** (hard requirement từ roadmap Phase 5).
2. Roles CRUD — trường hợp admin muốn tạo custom role ngoài 12 hardcoded.
3. Email outbox (MailKit + SMTP) — BLOCKED on user providing SMTP config.
4. User-level approver targeting trong workflow runtime (data model có sẵn,
chỉ cần wire User-kind approvers vào TransitionAsync guard).
5. PermissionsPage: allow admin grant `Workflows.Read` cho non-admin role so
menu Wf_* visible.
6. Rotate credentials đã leak trong chat (SA, vrapp, JWT).
7. SQL backup daily Task Scheduler (script đã có).