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

10 KiB
Raw Blame History

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, CtCreate → /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ó).