Redesign theo yêu cầu user: 3 panel vertical đồng thời trên cùng 1
màn hình (không modal/dialog popup).
Layout grid lg:grid-cols-[280px_1fr_300px]:
Panel 1 — Vai trò (trái, 280px):
Danh sách roles click-to-select với active highlight (brand-50 bg +
ring-brand-200 + check icon). Đếm số roles ở header.
Panel 2 — Quyền theo menu (giữa, flex):
Tìm menu inline header + sticky thead. Click vai trò → lọc menu
instant. Column toggle header (tick toàn cột) + per-cell checkbox.
Hover brand-tinted. Menu key hiện mono nhỏ dưới label.
Panel 3 — Tổng quan (phải, 300px):
Vai trò đang chọn + số quyền (progress bar brand) + chi tiết từng
CRUD (Xem/Tạo/Sửa/Xóa) với badge color-coded (slate/emerald/amber/
red) + count "X / Y menus" + tip helper cuối.
Bỏ dialog select + 3-col grid filter ở đầu (thay bằng 3 panel), giữ
logic mutation/toggle/column nguyên.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User request: 7 tab trong /system/workflows thành menu items riêng.
Domain:
- MenuKeys.WorkflowTypeLeaf(code) helper — `Wf_<TypeCode>` pattern
Infrastructure (DbInitializer):
- Seed 7 leaves dưới Workflows group (order 95..101), label matches
ContractType (HĐ Thầu phụ / Giao khoán / NCC / Dịch vụ / Mua bán /
Nguyên tắc NCC / Nguyên tắc Dịch vụ). Idempotent.
Application (GetMyMenuTreeQuery):
- Generalized inherit-perm logic: descendants of Contracts AND Workflows
inherit parent CanRead flag. Single Workflows.Read grant → all 7
Wf_* leaves visible; no per-leaf permission rows needed.
FE Layout (admin):
- resolvePath: Wf_<Code> → /system/workflows/<code>. Ct_* still hidden
on admin side.
FE App.tsx:
- New route /system/workflows/:typeCode?
FE WorkflowsPage:
- Removed horizontal tab bar; type selection now comes từ URL param.
- Landing view (no param): 3-col grid card per type với active version
badge — so admin có visual overview khi click top-level Workflows
group without selecting a type.
- TYPE_CODE_TO_INT map drives URL→int conversion.
Result: click `Quy trình HĐ > HĐ Mua bán` trong sidebar → opens
/system/workflows/MuaBan directly với designer scoped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User yêu cầu: mỗi loại HĐ có quy trình riêng với admin add roles + users
vào từng bước. Khi tạo version mới → HĐ tương lai chạy theo, HĐ cũ giữ
version cũ.
Domain:
- WorkflowDefinition (Code + Version + ContractType + IsActive + Steps)
- WorkflowStep (Order + Phase + Name + SlaDays + Approvers)
- WorkflowStepApprover (Kind: Role/User + AssignmentValue)
- Contract.WorkflowDefinitionId — pinned at creation
- WorkflowPolicyRegistry.FromDefinition() — build runtime policy từ DB
Infrastructure:
- EF config + migration AddVersionedWorkflows (3 table mới)
- DbInitializer.SeedWorkflowDefinitionsAsync: v01 per 7 ContractType,
steps sinh từ hardcoded WorkflowPolicies (Role approvers).
- ContractWorkflowService.TransitionAsync: load pinned WorkflowDefinition
→ FromDefinition(), fallback cho HĐ cũ không có pin.
Application:
- CreateContractCommand pin WorkflowDefinitionId = active version cho type
- ContractFeatures.Get(id): load pinned def cho workflow summary
- WorkflowAdminFeatures: GetWorkflowAdminOverviewQuery (7 types + active
+ history + ContractsUsingCount), CreateWorkflowDefinitionCommand
(validate payload, auto-increment version, deactivate old).
Api:
- GET /api/workflows trả overview
- POST /api/workflows tạo version mới (deactivate old)
FE /system/workflows:
- Tabs per 7 ContractType, mỗi tab hiện active version + lịch sử
- DefinitionCard: steps với badge role/user + SLA + archived indicator
hiện "N HĐ còn chạy" cho version cũ
- WorkflowDesigner modal: form code/name/desc + danh sách steps
(phase/name/SLA) + approvers (+ Role hoặc + User). Drop step ok.
Clone từ version hiện tại để tạo v02 có điểm start sensible.
- Amber banner: HĐ cũ không bị ảnh hưởng khi tạo version mới
Invariants được giữ:
- Unique (Code, Version) index
- Chỉ 1 version IsActive per ContractType tại 1 thời điểm
- Set default sẽ auto xóa override → respect legacy override table
- Role-kind approvers drive transition guards; User-kind fallback
DeptManager role cho v1 (user-level targeting = iteration 2)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User request: mỗi loại HĐ có menu riêng với 3 action Danh sách /
Thao tác / Duyệt.
Sidebar giờ 3-level under "Hợp đồng":
Hợp đồng (group, expandable)
├── HĐ Thầu phụ (sub-group)
│ ├── Danh sách → /contracts?type=1
│ ├── Thao tác → /contracts/new?type=1
│ └── Duyệt → /contracts?type=1&pendingMe=1
├── HĐ Giao khoán (sub-group)
├── HĐ NCC / Dịch vụ / Mua bán / Nguyên tắc NCC / Nguyên tắc DV
└── ... (7 types × 4 = 28 new menu items)
BE:
- MenuKeys.cs: ContractTypeCodes array + helpers ContractTypeGroup/
List/Create/Pending → key format Ct_<TypeCode>[_<Action>]
- DbInitializer.SeedMenuTreeAsync: loop seeds 28 entries under Contracts
- GetMyMenuTreeQuery.BuildChildren: descendants of `Contracts` inherit
parent permission (avoid adding 28 rows to Permissions table per role)
FE:
- Layout.tsx recursive: MenuNodeRenderer dispatches group vs leaf by
depth; nested groups collapsed by default (top-level expanded).
Deeper levels get smaller padding/text + left border guide.
- Pattern-based resolvePath: Ct_<Type>_<Action> → URL with query.
- Contract type code → int map (matches Domain ContractType enum).
- ContractsListPage reads ?type + ?pendingMe, filters client-side.
Header title + description reflect active filter. "← Tất cả loại"
quick-reset button.
- ContractCreatePage new cho admin (copy từ fe-user), pre-select type
từ ?type URL param.
- App.tsx route /contracts/new → ContractCreatePage.
Pure navigation UX; no new permissions needed. Admin + any role with
Contracts.Read see full menu; leaves click-through to filtered views.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Đọc QT-TP-NCC.docx: quy trình 9 bước chỉ áp dụng cho Thầu phụ/NCC/Tổ đội.
Dịch vụ/Mua bán/Nguyên tắc bypass CCM. Thay hardcoded dict bằng policy
registry.
Domain — WorkflowPolicy.cs:
- Record WorkflowPolicy { Name, Description, Transitions, PhaseSla,
ActivePhases } — pure data, testable.
- WorkflowPolicies.Standard: 9-phase full (Thầu phụ/Giao khoán/NCC)
- WorkflowPolicies.SkipCcm: 7-phase (Dịch vụ/Mua bán/Nguyên tắc)
- WorkflowPolicyRegistry.For(type) map ContractType → policy
- WorkflowPolicyRegistry.ForContract(c) override nếu BypassProcurement
AndCCM=true (instance-level escape hatch)
Infrastructure — ContractWorkflowService:
- Xóa hardcoded Transitions/PhaseSla dicts → load từ policy.ForContract
- TransitionAsync: validate qua policy.Transitions thay vì dict local
- Error message include policy.Name để debug dễ hơn
- GetPhaseSla trả SLA từ Standard policy (fallback — SLA hiện tại giống
nhau giữa 2 policy)
Application — ContractDetailDto:
- Field mới `Workflow: WorkflowSummaryDto { PolicyName, Description,
ActivePhases, NextPhases }` — FE dùng để render nút chuyển phase
dynamic + timeline card.
- BuildWorkflowSummary helper trong ContractFeatures.
FE (both apps):
- Type WorkflowSummary + ContractDetail.workflow
- ContractDetailPage xóa hardcoded NEXT_PHASES — dùng
c.workflow.nextPhases từ BE (single source of truth)
- WorkflowSummaryCard: timeline của ActivePhases với check/current/
future states + policy name/description ở header
- Card hiển thị trong sidebar, phía trên "Lịch sử duyệt"
Docs:
- gotchas.md #21 marked RESOLVED (NEXT_PHASES sync không còn cần)
Foundation: sau này admin có thể edit policy qua UI khi chuyển sang DB-
backed policy — nhưng API contract (WorkflowSummaryDto) đã stable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Admin giờ có thể quản lý template HĐ hoàn toàn qua UI — không cần dev
đụng vào file system hay seed data.
BE (FormFeatures.cs + FormsController.cs):
- UploadContractTemplateCommand (multipart): validate FormCode
(regex [A-Za-z0-9._-]+, unique), file <= 10MB, ext .docx/.xlsx,
FieldSpec phải là JSON hợp lệ hoặc null. Ghi file vào
wwwroot/templates/{formCode}_{guid:N}.{ext} để tránh collision
+ path traversal.
- UpdateContractTemplateCommand: sửa metadata + FieldSpec + IsActive
(không đụng file — chỉ DB).
- DeleteContractTemplateCommand: soft delete qua IsActive=false
(historical contracts ref template này vẫn resolve).
- Endpoints: POST /api/forms/templates (multipart),
PUT /api/forms/templates/{id}, DELETE /api/forms/templates/{id}.
RequestSizeLimit 12MB (validator caps 10MB).
FE (FormsPage.tsx admin):
- PageHeader action button "Upload template" mở dialog mới
- Row actions: Download (render existing), Pencil (edit), Trash (xóa
confirm) thay vì chỉ có 1 nút Render — row hover reveals clearly
- Upload dialog: file picker với file: pseudo-element brand styled,
FormCode (required, font-mono), Tên, Loại HĐ select, Mô tả,
FieldSpec JSON textarea với placeholder example
- Edit dialog: same fields minus file (FormCode disabled, edit chỉ
cập nhật metadata), có checkbox Kích hoạt
- Shared form submit handler — same dialog cho upload (__new) + edit
Foundation sẵn cho form builder thật (render UI từ FieldSpec JSON
đang là text field — iteration sau sẽ parse + render form dynamic).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pipeline:
- Pixel-crop chữ 'S' script từ logo.png (x=0..86, y=0..100 — trước
underline + tagline)
- Scale lên 2x thành 172x200 trên canvas 256x256 transparent,
center có padding ~12%
- Save thành mark.png (6.8KB, transparent bg)
- Embed base64 vào favicon.svg với nền trắng rounded-r-48 (contrast
với chữ S xanh brand) — scale mọi size
- index.html thêm apple-touch-icon + alternate PNG cho browsers
không support SVG favicon
Kết quả: favicon giờ là glyph 'S' script thật của Solutions, không
phải font-rendered text nữa. Contrast trắng-xanh dễ nhận ra ở size nhỏ.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lấy logo gốc từ template docx (SOL-CCM-FO-002.05) và brand color
exact pixel-sampled #1F7DC1 từ chữ "Solutions".
Thay đổi:
- logo.png (407x145, từ header docx) đặt vào /public cả 2 app
- favicon.svg: "S" trắng trên nền vuông brand blue bo góc
- index.css: palette brand-50..900 generate quanh #1F7DC1 + accent
red-500/600 cho ® mark + font Be Vietnam Pro (Google Fonts,
designed cho tiếng Việt, diacritics đẹp) với fallback Inter
+ JetBrains Mono cho font-mono + tùy chỉnh scrollbar
- Layout sidebar: logo.png 32px + "Admin"/"ERP" subtitle (thay
text "SOLUTION ERP" đơn điệu)
- LoginPage: gradient background brand-50 + 2 decorative orbs
blur, rounded-2xl card + backdrop-blur, big logo 56px + subtitle
tracking-[0.2em]
- index.html: lang="vi", title "Solutions ERP · Admin" / "Solutions
ERP", theme-color #1F7DC1 cho mobile address bar, preconnect
fonts.gstatic.com để load Google Fonts nhanh hơn
Tất cả màu hardcoded trong component đã dùng `brand-600` → tự
map sang palette mới, không cần đổi logic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Domain:
- Notification entity + NotificationType enum (stable ints)
- Nullable RefId cho correlation (contract, user, ...)
Infrastructure:
- NotificationConfiguration: bảng Notifications, index theo (UserId, ReadAt)
- NotificationService: ghi vào DbContext, không SaveChanges (để caller quyết
định unit-of-work — đảm bảo atomic với domain mutation)
- EF migration AddNotifications
Application:
- INotificationService (Notify + NotifyMany)
- CQRS: ListMyNotifications / GetMyUnreadCount / MarkRead / MarkAllRead
Api:
- NotificationsController: GET /api/notifications + unread-count + mark-read
Integration:
- ContractWorkflowService emit notification tới Drafter khi HĐ chuyển phase
(skip nếu actor chính là Drafter). Title + type theo phase đích:
DaPhatHanh → ContractPublished, TuChoi → ContractRejected, khác →
ContractPhaseTransition.
FE:
- Both NotificationBell (admin + user) dùng /api/notifications thật
(thay cho derived-from-inbox MVP trước đó). 30s refetch, click mark-read,
'Đọc hết' bulk action.
Foundation sẵn cho SignalR push + email outbox sau này — chỉ cần mở rộng
NotificationService mà không đổi caller.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Kiến trúc Layout giờ tách thành [sidebar] [topbar + content], foundation
để scale thêm module trong tương lai (HR, Accounting, Inventory...).
TopBar: title placeholder + NotificationBell + UserMenu (initials avatar
+ role badges + logout). UserMenu thay cho bottom-of-sidebar (cleaner).
NotificationBell:
- fe-admin: cảnh báo SLA (HĐ quá/sắp quá hạn, 24h window)
- fe-user: hộp thư chờ xử lý (items trong /contracts/inbox)
- refetchInterval: 60s
- Placeholder cho SignalR/email notifications thật sẽ thay bằng
/api/notifications endpoint ở Tier 3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CICD: check app pool state before Stop-WebAppPool (idempotent).
FE: new SlaTimer component with color-coded countdown (emerald/amber/red)
and progress bar. Two variants:
- inline: used in list tables (Inbox, Contracts list x2, MyContracts)
- full: used in ContractDetail card with progress bar + deadline timestamp
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>