User request: anh trỏ 3 subdomain mới về VPS IP 103.124.94.38:
- api.huypham.vn → api.solutions.com.vn
- admin.huypham.vn → admin.solutions.com.vn
- user.huypham.vn → eoffice.solutions.com.vn
Verified DNS: cả 3 resolve 103.124.94.38 ✓
Update 17 file repo:
FE (4): fe-admin/.env.production + fe-user/.env.production
(VITE_API_BASE_URL → https://api.solutions.com.vn)
fe-admin/src/lib/{api,realtime}.ts + fe-user equivalents (comment)
BE (1): appsettings.Production.json.example — CORS AllowedOrigins
CI/CD (1): .gitea/workflows/deploy.yml — smoke test URL
Scripts (3): setup-iis-sites (DomainApi/Admin/User), setup-ssl (3 host),
deploy-all (verify curls)
Docs (5): STATUS, HANDOFF, PROJECT-MAP, vps-setup, gotchas
Skill (1): iis-deploy-runbook — 3 site table + description
Email admin@huypham.vn giữ nguyên (Let's Encrypt contact — không phải
domain serve).
Thêm scripts/migrate-domains.ps1 — 1-shot VPS migration:
1. Pre-flight: resolve DNS 3 domain → verify IP VPS khớp
2. Add HTTP binding mới cho 3 IIS site (giữ binding cũ làm fallback)
3. Run win-acme xin 3 cert Let's Encrypt qua HTTP-01 challenge
(auto add HTTPS binding + http→https redirect)
4. Verify /health/live + /health/ready + 2 FE endpoint
5. (Optional -RemoveOld) xóa binding huypham.vn sau verify OK
Rollback: nếu fail, binding cũ vẫn active → site serve qua huypham.vn.
Anh chạy trên VPS:
cd C:\solution-erp\scripts ; .\migrate-domains.ps1
# Sau 1-2 ngày verify stable:
.\migrate-domains.ps1 -RemoveOld -SkipCert
13 KiB
PROJECT-MAP — Bản đồ toàn cảnh
Đọc file này nếu cần deep context (~15 phút). Nếu chỉ cần snapshot → đọc
STATUS.md.
Module map (hiện trạng post-Tier-3)
┌─────────────────────────────────────────────────────────────────┐
│ SOLUTION_ERP │
│ 🌐 Prod live: api.solutions.com.vn / admin.solutions.com.vn / eoffice.solutions.com.vn (HTTPS Let's Encrypt) │
└─────────────────────────────────────────────────────────────────┘
╔════════════════╗ ╔════════════════╗ ╔════════════════╗
║ IDENTITY ║ ║ DANH MỤC ║ ║ QUẢN LÝ HĐ ║
║ (Phase 1) ✅ ║ ║ (Phase 1) ✅ ║ ║ (Phase 1-3) ✅ ║
╠════════════════╣ ╠════════════════╣ ╠════════════════╣
║ User (+Mgmt) ║ ║ Supplier ║ ║ Contract ║
║ Role (12 seed) ║ ║ Project ║ ║ + WorkflowDefId║
║ Permission ║ ║ Department ║ ║ Approval ║
║ (3-panel UI) ║ ║ + seed demo ║ ║ Comment ║
║ MenuKey ║ ║ ContractClause ║ ║ Attachment ✅ ║
║ + nested tree ║ ║ ║ ║ (E2E upload) ║
╚════════════════╝ ╚════════════════╝ ╚════════════════╝
╔════════════════╗ ╔════════════════╗ ╔════════════════╗
║ FORM ENGINE ║ ║ WORKFLOW ║ ║ BÁO CÁO ║
║ (Phase 2) ✅ ║ ║ (Phase 3) ✅ ║ ║ (Phase 4) ✅ ║
╠════════════════╣ ╠════════════════╣ ╠════════════════╣
║ Template CRUD ║ ║ StateMachine ║ ║ Dashboard ║
║ DynamicForm ✅ ║ ║ Transition ║ ║ ExcelExport ║
║ (DOCX/XLSX) ║ ║ SlaTimer ║ ║ MyDashboard ✅ ║
║ FieldSpec JSON ║ ║ SlaExpiryJob ✅ ║ ║ (role-aware) ║
║ PDF export ✅ ║ ║ CodeGen RG-001 ║ ║ ║
║ (LibreOffice) ║ ║ **Versioned ✅**║ ║ ║
║ .doc auto-conv ║ ║ (admin design)║ ║ ║
╚════════════════╝ ╚════════════════╝ ╚════════════════╝
╔════════════════╗ ╔════════════════╗ ╔════════════════╗
║ NOTIFICATION ║ ║ ATTACHMENT ║ ║ BRANDING ║
║ (Tier 3) ✅ ║ ║ (Tier 3) ✅ ║ ║ (Tier 3) ✅ ║
╠════════════════╣ ╠════════════════╣ ╠════════════════╣
║ Notification ║ ║ IFileStorage ║ ║ #1F7DC1 palette║
║ SignalR Hub ║ ║ LocalFileStore ║ ║ Be Vietnam Pro ║
║ Auto-push ║ ║ Path traversal ║ ║ Solutions logo ║
║ interceptor ║ ║ guard ║ ║ ERP shell ║
║ Toast + Bell ║ ║ 3 endpoint ║ ║ (TopBar + Bell║
║ badge ║ ║ REST + FE ║ ║ + UserMenu) ║
║ Email (TODO) ║ ║ drag-drop ║ ║ ║
╚════════════════╝ ╚════════════════╝ ╚════════════════╝
╔════════════════════════════════════════════════════════════╗
║ INFRA / DEVOPS (Phase 5) ✅ ║
╠════════════════════════════════════════════════════════════╣
║ IIS 3 sites (Api/Admin/User) + URL Rewrite + ARR ║
║ win-acme 3 Let's Encrypt cert + auto-renew ║
║ Gitea Actions CI/CD (Windows self-hosted runner) ║
║ SQL Server 2019 SQLEXPRESS + scripts/backup-sql.ps1 ║
║ LibreOffice 25.8.6 headless (PDF/docx converter) ║
║ Health check /health/live + /health/ready ║
║ Serilog file rolling daily retention 30d ║
║ Security headers + HSTS + rate limit 300/min global ║
╚════════════════════════════════════════════════════════════╝
Domain entities chính (implemented)
User ────< UserRoles >──── Role ────< Permission (Role × MenuKey × CRUD)
MenuItem ─┬─ Parent-child tree (Contracts → Ct_MuaBan_* → ...)
├─ 28 Ct_* leaves (7 type × 3 action + 7 group)
└─ 7 Wf_* leaves for workflow admin
Supplier (NCC / NTP / TD / DVDV / CDT)
Project (Dự án)
Department (9 phòng từ QT docx)
Contract
├── Type: HĐTP | HĐGK | NCC | HĐDV | HĐ Mua bán | Nguyên tắc NCC | Nguyên tắc DV
├── Phase (9 state)
├── WorkflowDefinitionId (pinned policy at create-time)
├── Supplier, Project, Drafter, Template
├── MaHopDong (gen theo RG-001 khi DangDongDau)
├── SlaDeadline + SlaWarningSent flag
├── Approvals[] (history)
├── Comments[] (thread)
└── Attachments[] (scan + upload)
WorkflowDefinition (versioned per ContractType)
├── Code (QT-MB, QT-TP, ...) + Version (v01, v02, ...)
├── IsActive (max 1 per ContractType)
└── Steps[]
├── Order + Phase + Name + SlaDays
└── Approvers[] (Kind=Role|User + AssignmentValue)
WorkflowTypeAssignment (legacy admin override — fall back khi chưa có WorkflowDefinition)
ContractTemplate (FormCode + ContractType + FieldSpec JSON + StoragePath)
ContractClause (điều khoản chung FO-002.04)
ContractCodeSequence (Prefix → LastSeq, atomic gen)
Notification (RecipientUserId + Type + Title + Body + Link + IsRead)
API namespace (implemented)
/api/auth — login, refresh, me, logout
/api/users — CRUD + assign roles + reset password + unlock + toggle active
/api/roles — list (CRUD Create/Rename/Delete: TODO)
/api/menus — /me tree với inherit Contracts/Workflows
/api/suppliers — CRUD NCC
/api/projects — CRUD dự án
/api/departments — CRUD phòng ban
/api/permissions — get matrix + bulk upsert
/api/contracts — CRUD + query by phase/supplier/project/type + pendingMe
/api/contracts/inbox — HĐ chờ role tôi
/api/contracts/{id} — detail + pinned WorkflowDefinition policy
/api/contracts/{id}/transitions — state machine action (role guard + versioned policy)
/api/contracts/{id}/comments — thread góp ý
/api/contracts/{id}/attachments — upload (multipart) + download stream + delete
/api/forms — CRUD templates (upload/update/delete + render + PDF export)
/api/forms/templates/{id}/export-pdf — LibreOffice conversion
/api/workflows — admin GET overview + POST create new version
/api/workflows/{type} — per-type definition + history
/api/notifications — list + unread count + mark-read + mark-all-read
/api/reports/dashboard — KPI tổng hợp
/api/reports/my-dashboard — role-aware user-specific stats
/api/reports/export — Excel download
/hubs/notifications — SignalR hub (JWT qua ?access_token=)
/health/live + /health/ready — health check
FE screens (implemented)
fe-admin (:8082) — cho Admin + Role quản lý
/login/dashboard— MyDashboard row (4 card) + KPI + charts/master/suppliers|projects|departments— CRUD/system/users— Users Mgmt (create/reset/unlock/assign-roles/toggle-active)/system/permissions— 3-panel layout (Role list | Menu×CRUD matrix | Granted stats)/system/workflows— landing 7-card grid per ContractType/system/workflows/:typeCode— Definition card (active + history) + Designer modal/forms— list + upload + update + delete + render dialog (Form↔JSON toggle) + Tải PDF/contracts— list full + filter phase/supplier/project/type/contracts/new— Create (pre-select from?type=X)/contracts/:id— detail + timeline + action dialog + Attachments section + WorkflowSummaryCard/reports— filter + export Excel
Sidebar: nested menu + filterForAdmin hide Ct_*, keep Wf_* for admin
fe-user (:8080) — cho Drafter, TBP, PD/PM, BOD, CCM, HRA, ...
/login/inbox— HĐ chờ role tôi xử lý (lọc theo?type=X)/my-contracts— HĐ tôi đã tạo/tham gia (lọc theo?type=X)/contracts/new— tạo HĐ draft (pre-select type)/contracts/:id— detail + duyệt/comment + Attachments drag-drop
Sidebar: nested 3-level, filterForUser hide admin items, hiển thị 7 ContractType × 3 action
Flow chính (Phase 3 end-to-end, Tier 3 versioned)
Drafter (QS/NV.PB) ← pin WorkflowDefinitionId = v02 active
├─ [POST /api/contracts] tạo draft Phase=DangSoanThao, pin v02
├─ [POST /api/forms/templates/{id}/render] fill FieldSpec + preview
├─ (upload scan đính kèm qua /attachments)
├─ [POST /api/contracts/{id}/transitions] DangSoanThao → DangGopY
│ BE: load wfDef v02 → FromDefinition → Registry policy → guard (role + from/to)
│ → Notification push tới all PD/PM/PRO/CCM/FIN/ACT + SignalR toast
│
PD/PM/PRO/CCM/FIN/ACT (song song)
└─ [POST /api/contracts/{id}/comments] góp ý → Notification push Drafter
Drafter
├─ [PATCH /api/contracts/{id}] revise
├─ [POST .../transitions] DangGopY → DangDamPhan → DangInKy
│ (BypassProcurementAndCCM=true → skip CCM → DangInKy → DangTrinhKy)
│
NTP/NCC/TĐ (external — Drafter update thay)
└─ upload scan có chữ ký đối tác
Drafter → [transitions] → DangKiemTraCCM
CCM → [transitions] → DangTrinhKy
BOD → [transitions] → DangDongDau
└─ BE: ContractCodeGenerator SERIALIZABLE → gen MaHopDong RG-001
HRA → [transitions] → DaPhatHanh (final)
External dependencies (hiện trạng)
- SQL Server — prod SQLEXPRESS trên VPS, dev LocalDB hoặc Docker
- IIS — Windows Server (VPS shared VIETREPORT), URL Rewrite + ARR + WebSockets module
- Gitea — https://git.baocaogiaoduc.vn (self-hosted, shared runner)
- win-acme — Let's Encrypt auto-renew
- LibreOffice 25.8.6 — PDF / docx / xlsx conversion (soffice headless)
- DocumentFormat.OpenXml — render .docx (Phase 2)
- ClosedXML — render .xlsx + Excel export (Phase 4)
- MediatR 12.4.1 — CQRS mediator (pin, 14 breaking)
- @microsoft/signalr 8.0.7 — FE realtime client
- Be Vietnam Pro — Google Fonts (Vietnamese diacritics)
Non-goals (KHÔNG làm)
- ❌ Python AI service (user đã quyết gác vô thời hạn)
- ❌ Mobile app (React Native) — Phase 6+
- ❌ Multi-tenant (1 instance / 1 công ty)
- ❌ Tích hợp e-signature (VNPT/FPT CA) — Phase 6+
- ❌ Tích hợp SAP/Bravo ERP — Phase 6+
- ❌ Public API / external webhooks
Architectural wins (Tier 3)
-
Versioned workflow via Contract.WorkflowDefinitionId pin — zero-cost immutability guarantee. HĐ cũ protected from policy changes by REFERENCE (FK restrict), không phải snapshot copy.
-
Permission inheritance via menu ancestry — Contracts + Workflows roots inherit CanRead xuống descendant Ct_* / Wf_* nodes. Không cần per-leaf permission rows → Permissions table nhỏ gọn.
-
3-project clean-arch split cho cross-cutting services (SignalR realtime + LibreOffice converter):
- Abstraction ở Application (
IRealtimeNotifier,IDocumentConverter) - Implementation ở Api / Infrastructure
- Caller (Application handlers) KHÔNG depend transport / framework
- Abstraction ở Application (
-
SaveChangesInterceptor auto-push notifications —
NotificationPushInterceptorcapture Added Notifications ở SavingChanges, push viaIRealtimeNotifierở SavedChanges. Zero caller changes khi CQRS handler chỉdb.Notifications.Add(n). -
URL-driven admin UI (workflows per-type) — thay tabs bằng sidebar menu items + URL param. Linkable, bookmarkable, mỗi type có permission leaf riêng qua
Wf_<Code>.