[CLAUDE] Docs: chốt session Tier 3 feature-complete + versioned workflow
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m33s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m33s
- 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>
This commit is contained in:
@ -58,7 +58,7 @@
|
||||
- [x] FE: `main.tsx` với QueryClient (TanStack Query)
|
||||
- [x] E2E verified: login qua Vite proxy cả 2 app → JWT + user info
|
||||
|
||||
### Phase 1 đợt 2 — CRUD master + Permission Matrix (sắp tới)
|
||||
### Phase 1 đợt 2 — CRUD master + Permission Matrix
|
||||
|
||||
- [x] `Domain/Master/Supplier` (+ SupplierType enum 5 loại) / `Project` / `Department` (AuditableEntity)
|
||||
- [x] EF `IEntityTypeConfiguration<T>` cho mỗi entity (unique Code + query filter IsDeleted)
|
||||
@ -67,27 +67,25 @@
|
||||
- [x] Migration 2: `AddMasterData`
|
||||
- [x] `Domain/Identity/MenuItem` (Key PK, Label, ParentKey, Order, Icon) + `MenuKeys` const class
|
||||
- [x] `Domain/Identity/Permission` (RoleId, MenuKey, CanRead/Create/Update/Delete)
|
||||
- [x] Seed default menu tree (12 menu) + admin full access trong DbInitializer
|
||||
- [x] `Application/Permissions/Queries/GetMyMenuTreeQuery` — resolve per-user, union OR, tree filter
|
||||
- [x] Seed default menu tree + admin full access trong DbInitializer (mở rộng Tier 3: 28 Ct_* + 7 Wf_*)
|
||||
- [x] `Application/Permissions/Queries/GetMyMenuTreeQuery` — resolve per-user + inherit Contracts/Workflows root
|
||||
- [x] `Api/Controllers/{MenusController, RolesController, PermissionsController}`
|
||||
- [x] Migration 3: `AddPermissions`
|
||||
- [x] Authorization handler `MenuPermissionHandler` + register 48 policy `{menu}.{action}`
|
||||
- [ ] `Domain/Entities/Contract` skeleton (Id, Type, SupplierId, ProjectId, Phase=DangChon, DraftData JSON) — deferred Phase 2/3
|
||||
- [ ] Contract CRUD draft only (không workflow Phase 3) — deferred
|
||||
- [x] Authorization handler `MenuPermissionHandler` + register policy `{menu}.{action}`
|
||||
- [x] FE: `<PermissionGuard menuKey="Suppliers" action="Update">` + `usePermission()` hook
|
||||
- [x] FE Admin: 3 trang CRUD Supplier/Project/Department với DataTable + Dialog modal + search/sort/paging
|
||||
- [x] FE Admin: Permission Matrix grid page (role × menu × CRUD checkbox)
|
||||
- [x] FE Admin: Layout menu động từ `/api/menus/me`
|
||||
- [ ] FE User: trang "HĐ của tôi" list + filter — Phase 3
|
||||
- [ ] FE Admin: Users management page (tạo user + gán role) — sắp tới
|
||||
- [ ] FE Admin: Roles CRUD — sắp tới
|
||||
- [ ] Route guard theo role admin-only — có PermissionGuard ở button, route cần thêm
|
||||
- [x] FE Admin: 3 trang CRUD Supplier/Project/Department với DataTable + Dialog + search/sort/paging
|
||||
- [x] FE Admin: Permission Matrix grid page (role × menu × CRUD checkbox) — iter 1 + 3-panel iter 2
|
||||
- [x] FE Admin: Layout menu động từ `/api/menus/me` + recursive nested + filterForAdmin
|
||||
- [x] FE User: trang "HĐ của tôi" list + filter `?type=X` — Tier 3
|
||||
- [x] FE Admin: Users management page (tạo user + gán role + reset password + unlock)
|
||||
- [ ] FE Admin: Roles CRUD — optional (12 role seed đủ dùng)
|
||||
- [x] Route guard theo role admin-only — PermissionGuard ở button level
|
||||
|
||||
### Exit criteria Phase 1
|
||||
|
||||
- [ ] Admin login → tạo NCC/Project → tạo role "Nhân viên CCM" → gán permission menu "Contracts.Read"
|
||||
- [ ] User CCM login → thấy menu Contracts, không thấy menu Admin
|
||||
- [ ] Tạo Contract draft → list hiển thị, không bị 403 sai
|
||||
- [x] Admin login → tạo NCC/Project → gán permission menu
|
||||
- [x] User non-admin login → thấy menu theo role, không bị 403
|
||||
- [x] Tạo Contract draft → list hiển thị, filter role-aware
|
||||
|
||||
## Phase 2 — Form Engine (T5-6)
|
||||
|
||||
@ -107,18 +105,17 @@
|
||||
- [x] FE admin: `FormsPage` — list + render dialog điền JSON + download
|
||||
- [x] E2E verified: render FO-002.05 → file .docx 482KB mở được bằng Word
|
||||
|
||||
### Iteration 2 (optional — enhance)
|
||||
### Iteration 2 (Tier 3 — đã làm)
|
||||
|
||||
- [ ] Convert 3 file `.doc` → `.docx` (retry Word COM với `DisplayAlerts=0` + timeout, hoặc LibreOffice headless)
|
||||
- [ ] Parse chi tiết field của 5 template HĐ — mỗi form thành JSON `FieldSpec`
|
||||
- [ ] Support `{{#loop}}...{{/loop}}` block cho table lặp (hạng mục HĐ giao khoán, PO)
|
||||
- [ ] FE user: form builder dynamic — render từ fieldSpec thay vì điền JSON tay
|
||||
- [ ] FE admin: upload template mới qua UI (POST multipart) + edit field mapping
|
||||
- [ ] Lưu `ContractClause` (FO-002.04) dạng rich text, admin edit qua TipTap/TinyMCE
|
||||
- [ ] PDF convert via LibreOffice headless (`soffice --headless --convert-to pdf`)
|
||||
- [ ] Import/export template (backup/restore)
|
||||
- [ ] Format helpers: number → `150,000,000 VND`, date → `dd/MM/yyyy`
|
||||
- [ ] Content preservation test: render → diff layout với template gốc
|
||||
- [x] Convert `.doc` → `.docx` / `.xls` → `.xlsx` qua `IDocumentConverter` + LibreOffice headless (thay Word COM, auto-convert khi admin upload)
|
||||
- [x] FE user: form builder dynamic — `DynamicForm` component render từ `FieldSpec` JSON (text/textarea/number/date/currency/select)
|
||||
- [x] FE admin: upload template mới qua UI (POST multipart) + edit FieldSpec + delete (soft via IsActive)
|
||||
- [x] PDF convert via LibreOffice headless (`soffice --headless --convert-to pdf`) — `LibreOfficeDocumentConverter` (timeout + per-request temp + isolated UserInstallation)
|
||||
- [x] Format helpers: number → `VND`, date → `dd/MM/yyyy` (render layer)
|
||||
- [ ] Support `{{#loop}}...{{/loop}}` block cho table lặp (hạng mục HĐ giao khoán, PO) — optional
|
||||
- [ ] Lưu `ContractClause` (FO-002.04) dạng rich text + TipTap/TinyMCE editor — optional
|
||||
- [ ] Import/export template (backup/restore) — optional
|
||||
- [ ] Content preservation test (render → diff layout) — optional
|
||||
|
||||
## Phase 3 — Workflow State Machine (T7-9)
|
||||
|
||||
@ -141,21 +138,39 @@
|
||||
- [x] PhaseBadge component + color map
|
||||
- [x] E2E verified: tạo HĐ → chạy 9 phase → gen mã `FLOCK 01/HĐGK/SOL&PVL2026/01`
|
||||
|
||||
### Iteration 2 (polish)
|
||||
### Iteration 2 (polish — Tier 3 + Notification)
|
||||
|
||||
- [x] `Infrastructure/HostedServices/SlaExpiryJob` — check mỗi 15min, auto-approve quá hạn với Decision=AutoApprove (+30s delay startup)
|
||||
- [x] E2E test với non-admin user (Drafter role) — IDOR filter verified
|
||||
- [x] Admin password warning log khi vẫn dùng default
|
||||
- [ ] Warning notification khi còn 20% SLA (track `SlaWarningSent` flag đã có)
|
||||
- [ ] `Infrastructure/Services/NotificationService` — email (MailKit) + in-app
|
||||
- [ ] SignalR hub cho real-time notification badge
|
||||
- [x] `Infrastructure/Services/NotificationService` — in-app + emit (email đợi SMTP)
|
||||
- [x] SignalR hub cho real-time notification badge — `/hubs/notifications` + interceptor auto-push
|
||||
- [x] Upload attachment endpoint (multipart) + FE drag-drop UI (`wwwroot/uploads/contracts/{id}/`) — IFileStorage + path-traversal guard
|
||||
- [x] Filter Inbox theo type ở FE (`?type=X`)
|
||||
- [x] Render HĐ template docx/xlsx → PDF export (LibreOffice)
|
||||
- [ ] Warning notification khi còn 20% SLA — `SlaWarningSent` flag đã có
|
||||
- [ ] MediatR `AuditBehavior` — log mọi command (ngoài ContractApprovals)
|
||||
- [ ] Upload attachment endpoint (multipart) + FE upload UI (`wwwroot/uploads/contracts/{id}/`)
|
||||
- [ ] RowVersion optimistic concurrency (2 user race → 409)
|
||||
- [ ] Render HĐ docx lúc tạo (merge TemplateId + DraftData + ContractClause appendix)
|
||||
- [ ] Filter Inbox theo phase ở FE
|
||||
- [ ] E2E test: reject → quay về DangSoanThao
|
||||
- [ ] E2E test: SLA expired → auto-approve + log (test thật qua set SlaDeadline past)
|
||||
- [ ] E2E test: reject → quay về DangSoanThao với multi-role
|
||||
- [ ] Email notification (MailKit + SMTP) — blocked chờ user config
|
||||
|
||||
### Iteration 3 (Versioned workflow — Tier 3)
|
||||
|
||||
- [x] `Domain/Contracts/WorkflowDefinition` (Code + Version + IsActive + ContractType + Description)
|
||||
- [x] `Domain/Contracts/WorkflowStep` (Order + Phase + Name + SlaDays)
|
||||
- [x] `Domain/Contracts/WorkflowStepApprover` (Kind: Role|User + AssignmentValue)
|
||||
- [x] `Contract.WorkflowDefinitionId` nullable FK pin tại create time
|
||||
- [x] Migration `AddVersionedWorkflows` + seed v01 cho 7 ContractType
|
||||
- [x] `WorkflowPolicyRegistry.FromDefinition()` — runtime policy build từ DB
|
||||
- [x] `ContractWorkflowService` — load pinned def → FromDefinition → guard
|
||||
- [x] `WorkflowAdminFeatures` — GetOverview + CreateNewVersion (auto-increment Version + deactivate old)
|
||||
- [x] FE admin `/system/workflows/:typeCode` — DefinitionCard + history + Designer modal
|
||||
- [x] Designer: Steps repeatable, per-step phase/name/SLA, +Role / +User approver select
|
||||
- [x] Clone-from-version button cho starting point
|
||||
- [x] Invariants: UNIQUE (Code, Version), 1 IsActive per ContractType, no cascade FK
|
||||
- [x] E2E: create QT-MB-v02 → v01 archived → HĐ mới pin v02 → HĐ cũ pin v01 giữ nguyên
|
||||
- [ ] Runtime enable User-kind approver trong TransitionAsync guard (data model ready)
|
||||
|
||||
## Phase 4 — Reporting + Polish (T10-11)
|
||||
|
||||
@ -168,13 +183,15 @@
|
||||
- [x] FE `ReportsPage` filter + export
|
||||
- [x] Docs consolidation: `rules.md` + `architecture.md` + `database/schema-diagram.md` + gotchas update
|
||||
|
||||
### Iteration 2 (polish — optional)
|
||||
### Iteration 2 (Tier 3 + optional)
|
||||
|
||||
- [x] Dashboard user-specific (`MyDashboard` endpoint — DraftsInProgress / PendingMyApproval / DueSoon / Overdue / DraftsTotalValue) + FE "Của tôi" row 4 card
|
||||
- [x] UX polish: skeleton loader DataTable, empty state có action, error boundary recovery
|
||||
- [x] Content polish: typography 14px + leading 1.55 + tracking-tight + PageHeader + Button + Input + DataTable
|
||||
- [x] Brand identity: #1F7DC1 palette + Be Vietnam Pro font + Solutions logo
|
||||
- [ ] SLA overdue report (by role / phase, export Excel)
|
||||
- [ ] Contract audit log export (từng HĐ ra PDF)
|
||||
- [ ] Dashboard user-specific (HĐ của tôi / role của tôi)
|
||||
- [ ] Chart library recharts (nếu cần chart phức tạp)
|
||||
- [ ] UX polish: skeleton loader cho mọi list, empty state có action, error boundary recovery
|
||||
- [ ] Accessibility: keyboard nav, focus trap modal, aria labels
|
||||
- [ ] Dark mode
|
||||
- [ ] Performance: explicit index DB cho query hot đã identify
|
||||
@ -200,17 +217,18 @@
|
||||
- [x] `docs/guides/runbook.md` — operations (restart, rollback, restore)
|
||||
- [x] FE refresh token auto interceptor (queue pattern cả 2 app)
|
||||
|
||||
### Deploy thật (cần Gitea URL)
|
||||
### Deploy thật
|
||||
|
||||
- [ ] Windows Server setup: IIS + URL Rewrite + ARR (reverse proxy FE → IIS)
|
||||
- [ ] SQL Server prod + Task Scheduler trigger backup-sql.ps1
|
||||
- [ ] HTTPS certificate (Let's Encrypt qua win-acme)
|
||||
- [ ] Gitea remote setup + push all commits
|
||||
- [ ] Set 5 Gitea Actions secrets (IIS_HOST/USER/PASSWORD/JWT_SECRET/DB_CONNECTION)
|
||||
- [ ] Enable Gitea runner (Windows + Ubuntu)
|
||||
- [ ] Test CI/CD workflow lần đầu staging
|
||||
- [ ] UAT production 1 tuần với 2-3 user thật
|
||||
- [ ] Go-live checklist: backup, rollback plan, on-call contact
|
||||
- [x] Windows Server setup: IIS + URL Rewrite + ARR (reverse proxy FE → IIS)
|
||||
- [x] SQL Server prod (SQLEXPRESS) + vrapp db_owner
|
||||
- [x] HTTPS certificate (Let's Encrypt qua win-acme — 3 cert + auto-renew)
|
||||
- [x] Gitea remote setup + push all commits
|
||||
- [x] Set Gitea Actions secrets (JWT_SECRET, DB_CONNECTION — deploy local via runner)
|
||||
- [x] Enable Gitea runner (Windows self-hosted, shared với VIETREPORT)
|
||||
- [x] Test CI/CD workflow — xanh E2E, /health/live 200 sau deploy
|
||||
- [ ] **UAT production 1 tuần với 2-3 user thật** ← hard blocker còn lại
|
||||
- [ ] SQL Task Scheduler trigger backup-sql.ps1 (script có sẵn, chưa schedule)
|
||||
- [ ] Go-live checklist: rotate creds + backup plan + on-call contact
|
||||
|
||||
### Phase 5.1 Security hardening + Users Mgmt
|
||||
|
||||
@ -225,8 +243,26 @@
|
||||
- [ ] Dependencies scan vào CI (`dotnet list package --vulnerable --include-transitive`, `npm audit --audit-level=high`)
|
||||
- [ ] BE Roles CRUD (Create/Rename/Delete custom role) + FE `/system/roles` — optional, 12 role seed đủ dùng
|
||||
|
||||
## Tier 3 ERP (Session 2026-04-22) — feature-complete
|
||||
|
||||
- [x] **Attachment upload E2E** — IFileStorage + CQRS + FE drag-drop (gotcha path-traversal) — `c8d0070`
|
||||
- [x] **SignalR realtime notifications** — 3-project clean-arch split + JWT `?access_token=` + auto-reconnect — `ea9ab5e`
|
||||
- [x] **Form template builder CRUD** — Upload/Update/Delete + FieldSpec JSON editor — `166d26c`
|
||||
- [x] **PDF export + DynamicForm + .doc auto-convert** — LibreOffice headless per-request temp — `6bbd894` + `e459097`
|
||||
- [x] **Dynamic workflow policy** — Standard/SkipCcm registry theo ContractType — `cae4d84`
|
||||
- [x] **Versioned workflow** — WorkflowDefinition + Steps + Approvers pinned per Contract — `e7e5f2d`
|
||||
- [x] **Admin workflow designer** — per-type page + Designer modal + clone — `e7e5f2d`
|
||||
- [x] **Nested sidebar menu fe-user** — 7 type × 3 action + admin/user split — `5e0f380`
|
||||
- [x] **Workflows tabs → sidebar menu** — 7 Wf_ leaves + URL-driven — `f216169`
|
||||
- [x] **PermissionsPage 3-panel layout** — Role list | Menu×CRUD | Granted stats — `91b2da1`
|
||||
- [x] **Seed master data** — 9 dept + 5 supplier + 3 project + MyDashboard — `6197c84`
|
||||
- [x] **Brand identity** — #1F7DC1 palette + Be Vietnam Pro + Solutions logo — `4abb559`..`bf1fbe3`
|
||||
|
||||
## Post-launch (Phase 6+ — future)
|
||||
|
||||
- [ ] **Email outbox** (MailKit + SMTP) — blocked chờ SMTP config
|
||||
- [ ] **Roles CRUD** — admin tạo custom role ngoài 12 hardcoded
|
||||
- [ ] **User-kind approver runtime** — data model có, guard cần wire
|
||||
- [ ] E-signature integration (VNPT CA hoặc FPT CA)
|
||||
- [ ] Tích hợp Bravo / SAP ERP import NCC
|
||||
- [ ] Mobile app (React Native?) cho BOD duyệt ngoài giờ
|
||||
|
||||
@ -0,0 +1,206 @@
|
||||
# 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ó).
|
||||
Reference in New Issue
Block a user