Thiết Kế Module Nền Tảng Cốt Lõi
1. Tổng Quan
Phần tiêu đề “1. Tổng Quan”Module Nền tảng Cốt lõi là nền tảng của BPO ERP. Mọi module khác phụ thuộc vào nó. Nó cung cấp:
- Vòng đời tenant — cấp, cấu hình, tạm ngừng, offboard
- Quản lý người dùng — đăng ký, xác thực, hồ sơ, chính sách mật khẩu
- Ủy quyền — quản lý vai trò và quyền (mô hình được định nghĩa trong Authorization.md)
- Ghi nhật ký kiểm toán — bản ghi bất biến của tất cả thay đổi dữ liệu trên mọi module
- Cấu hình hệ thống — cài đặt theo tenant và mặc định nền tảng
- Hạ tầng công việc nền — lập lịch, thử lại và giám sát
Module này phát hành trong Giai đoạn 1. Các thành phần của nó là điều kiện tiên quyết cho mọi module Giai đoạn 1 khác.
2. Vòng Đời Tenant
Phần tiêu đề “2. Vòng Đời Tenant”Trạng thái
Phần tiêu đề “Trạng thái”Provisioning → Active → Suspended → Offboarded| Trạng thái | Mô tả |
|---|---|
Provisioning | Bản ghi tenant đã tạo; schema đã seed; chưa cho phép người dùng tenant truy cập |
Active | Trạng thái hoạt động bình thường |
Suspended | Truy cập bị chặn (không thanh toán, vi phạm chính sách); dữ liệu được giữ; có thể kích hoạt lại |
Offboarded | Tenant đã chấm dứt hợp đồng; dữ liệu đã xuất và lên lịch xóa sau thời gian lưu trữ |
Các bước cấp phát
Phần tiêu đề “Các bước cấp phát”Khi Quản trị viên Hệ thống tạo một tenant mới, nền tảng thực hiện các bước sau theo thứ tự trong một giao dịch duy nhất:
- Chèn hàng vào bảng
TenantsvớiStatus = 'Provisioning' - Seed Hệ thống Tài khoản VAS (
VasChartOfAccounts) cho tenant sử dụng mẫu Thông tư 200 hoặc Thông tư 133 tùy theo quy mô doanh nghiệp đã khai báo của tenant - Seed Hệ thống Tài khoản IFRS (
ChartOfAccounts) với mẫu khởi đầu tiêu chuẩn - Seed các
TenantRolestích hợp sẵn (TenantAdministrator,Accountant,Purchasing,Sales,Viewer) vàTenantRolePermissionsmặc định của chúng - Tạo các hàng
FiscalPeriodban đầu cho năm tài chính hiện tại - Seed hàng
TenantEInvoiceConfigmặc định (rỗng — quản trị viên tenant phải hoàn tất cài đặt hóa đơn điện tử) - Đặt
Status = 'Active' - Gửi email onboarding đến địa chỉ email quản trị viên tenant được chỉ định với hướng dẫn đăng nhập
Nếu bất kỳ bước nào thất bại, giao dịch được rollback và bản ghi tenant một phần bị xóa. Quản trị viên Hệ thống được thông báo về thất bại với chi tiết lỗi.
Mô hình dữ liệu Tenant
Phần tiêu đề “Mô hình dữ liệu Tenant”Tenants (cấp nền tảng, không áp dụng RLS)──────────────────────────────────────────────────────TenantId UNIQUEIDENTIFIER PKTenantName NVARCHAR(200) NOT NULLTaxCode NVARCHAR(20) NOT NULL -- Mã số thuế doanh nghiệp Việt NamLegalName NVARCHAR(500) NOT NULL -- Tên pháp nhân đầy đủ (Tiếng Việt)EnterpriseSize NVARCHAR(20) NOT NULL -- 'Small' (C133) hoặc 'Medium_Large' (C200)FunctionalCurrency CHAR(3) NOT NULL DEFAULT 'VND'FiscalYearStart NVARCHAR(5) NOT NULL -- 'MM-DD', ví dụ: '01-01' cho ngày 1/1Status NVARCHAR(20) NOT NULL -- 'Provisioning' | 'Active' | 'Suspended' | 'Offboarded'CreatedAt DATETIMEOFFSET NOT NULLSuspendedAt DATETIMEOFFSET NULLOffboardedAt DATETIMEOFFSET NULLTenantSettings (có phạm vi tenant, áp dụng RLS)──────────────────────────────────────────────────────SettingId UNIQUEIDENTIFIER PKTenantId UNIQUEIDENTIFIER NOT NULL (RLS-enforced)SettingKey NVARCHAR(100) NOT NULLSettingValue NVARCHAR(MAX) NOT NULLUNIQUE (TenantId, SettingKey)3. Quản Lý Người Dùng
Phần tiêu đề “3. Quản Lý Người Dùng”Người dùng xác thực qua ASP.NET Core Identity. Một tài khoản người dùng tồn tại ở cấp nền tảng (AspNetUsers) và có thể được liên kết với một hoặc nhiều tenant qua các hàng TenantUsers (dành cho tư vấn viên hoặc quản trị viên phục vụ nhiều công ty).
Xác Thực — Ưu Tiên Cookie
Phần tiêu đề “Xác Thực — Ưu Tiên Cookie”Frontend React được đồng host với ASP.NET Core API (phục vụ từ cùng một origin). Vì lý do này, xác thực cookie ASP.NET Core Identity là mặc định cho phiên web.
- Cookie là
HttpOnly,SecurevàSameSite=Strict— không thể truy cập từ JavaScript. - Không lưu token trong
localStoragehoặcsessionStorageđược yêu cầu hoặc được phép cho web client. TenantIdđược lưu dưới dạng claim trong cookie xác thực form được mã hóa (qua ASP.NET Core Data Protection). Nó được đặt khi đăng nhập và xác thực lại ở mỗi yêu cầu.- Bảo vệ CSRF được cung cấp bởi chính sách cookie
SameSite=Strict. Token chống giả mạo được sử dụng cho các endpoint thay đổi trạng thái như một lớp bổ sung.
Một endpoint JWT/Bearer (/auth/token) có sẵn song song với xác thực cookie cho các trường hợp sử dụng không thể dùng cookie: ứng dụng di động gốc Giai đoạn 3 và bất kỳ tích hợp API bên thứ ba nào. Thực thi chính sách RBAC giống hệt cho cả hai lược đồ xác thực — cấu trúc claims giống nhau bất kể phương thức vận chuyển nào.
Tạo Người Dùng
Phần tiêu đề “Tạo Người Dùng”Người dùng tenant được cấp phát bởi quản trị viên tenant (hoặc Quản trị viên Hệ thống). Hai chế độ tạo được hỗ trợ:
Mật khẩu do admin đặt (mặc định):
- Quản trị viên tenant tạo người dùng trong UI quản trị: email, tên hiển thị, gán vai trò, mật khẩu ban đầu.
- Nền tảng tạo hàng
AspNetUsersvới mật khẩu do admin đặt và các hàngTenantUsers+TenantUserRoles. - Người dùng ngay lập tức hoạt động và có thể đăng nhập.
Luồng mời (tùy chọn):
- Quản trị viên tenant tạo người dùng mà không đặt mật khẩu; nền tảng gửi email mời với liên kết kích hoạt có thời hạn (48 giờ).
- Người dùng theo liên kết và đặt mật khẩu của họ.
- Liên kết mời hết hạn có thể được gửi lại bởi quản trị viên tenant.
Quản trị viên tenant chọn chế độ nào cho từng người dùng. Cả hai chế độ đều tạo ra tài khoản người dùng hoạt động giống hệt nhau.
Tự đăng ký (người dùng tự tạo tài khoản không có sự tham gia của admin) không được hỗ trợ.
Chính Sách Mật Khẩu
Phần tiêu đề “Chính Sách Mật Khẩu”Mặc định ASP.NET Core Identity, được thực thi:
- Tối thiểu 8 ký tự
- Ít nhất một chữ hoa, một chữ thường, một chữ số, một ký tự đặc biệt
- Mật khẩu không được tái sử dụng (nhớ 5 lần gần nhất)
- Tài khoản bị khóa 15 phút sau 5 lần đăng nhập thất bại liên tiếp
MFA (TOTP) được hoãn đến Giai đoạn 2.
Quản Lý Phiên
Phần tiêu đề “Quản Lý Phiên”- Phiên xác thực cookie: hết hạn sau 8 giờ không hoạt động (sliding expiry); hết hạn tuyệt đối 24 giờ
- Khi đổi mật khẩu hoặc vô hiệu hóa tài khoản: cookie phiên của người dùng bị vô hiệu hóa ngay lập tức qua thu hồi key ASP.NET Core Data Protection hoặc danh sách vô hiệu hóa phiên phía server
- Token làm mới JWT (cho endpoint
/auth/token): hết hạn 7 ngày, lưu phía server trongUserRefreshTokens, có thể vô hiệu hóa từng cái hoặc tất cả cùng lúc
4. Nhật Ký Kiểm Toán
Phần tiêu đề “4. Nhật Ký Kiểm Toán”Nguyên Tắc Thiết Kế
Phần tiêu đề “Nguyên Tắc Thiết Kế”- Chỉ thêm (append-only). Nhật ký kiểm toán không bao giờ được CẬP NHẬT hoặc XÓA qua mã ứng dụng.
- Bất biến được đảm bảo bởi cơ sở dữ liệu. Người dùng cơ sở dữ liệu ứng dụng không có đặc quyền
UPDATEhoặcDELETEtrên bảngAuditLog. - Bao phủ mọi thay đổi. Mọi
INSERT,UPDATEvàDELETEtrên bảng có phạm vi tenant phải tạo ra một bản ghi kiểm toán. - Ghi giá trị trước/sau. Giá trị cũ (trước) và giá trị mới (sau) được lưu dưới dạng JSON cho mọi thay đổi.
Triển Khai
Phần tiêu đề “Triển Khai”Các mục kiểm toán được ghi bởi EF Core SaveChanges interceptor (AuditInterceptor) chạy trước khi giao dịch commit. Interceptor:
- Kiểm tra
ChangeTrackercho tất cả entity đã sửa đổi - Với mỗi entity đã sửa, ghi lại:
TenantId,UserId,TableName,RecordId,ChangeType(Insert/Update/Delete),OldValues(JSON),NewValues(JSON),Timestamp(UTC) - Ghi tất cả hàng kiểm toán trong cùng giao dịch với các thay đổi
Interceptor chạy trong cùng giao dịch — hoặc tất cả các thay đổi và bản ghi kiểm toán của chúng được commit cùng nhau, hoặc không có gì cả.
Schema Nhật Ký Kiểm Toán
Phần tiêu đề “Schema Nhật Ký Kiểm Toán”AuditLog (có phạm vi tenant, áp dụng RLS, chỉ INSERT cho người dùng ứng dụng)──────────────────────────────────────────────────────AuditId UNIQUEIDENTIFIER PKTenantId UNIQUEIDENTIFIER NOT NULL (RLS-enforced)UserId UNIQUEIDENTIFIER NULL FK → AspNetUsers (NULL cho system/background job)TableName NVARCHAR(100) NOT NULLRecordId UNIQUEIDENTIFIER NOT NULLChangeType NVARCHAR(10) NOT NULL -- 'Insert' | 'Update' | 'Delete'OldValues NVARCHAR(MAX) NULL -- JSON; NULL cho InsertNewValues NVARCHAR(MAX) NULL -- JSON; NULL cho DeleteTimestamp DATETIMEOFFSET NOT NULLPermissionCode NVARCHAR(100) NULL -- quyền được sử dụng cho thay đổi nàyRequestId UNIQUEIDENTIFIER NULL -- liên kết tất cả thay đổi trong một HTTP requestJobName NVARCHAR(100) NULL -- điền cho background job; NULL cho user requestRequestId liên kết tất cả thay đổi từ một lệnh gọi API để có thể theo dõi. Background job đặt UserId = NULL và điền JobName.
Truy Vấn
Phần tiêu đề “Truy Vấn”Quản trị viên tenant có thể lọc nhật ký kiểm toán theo: khoảng thời gian, người dùng, loại bảng/entity, ID bản ghi và loại thay đổi. Giao diện mặc định hiển thị 90 ngày gần nhất. Lịch sử kiểm toán đầy đủ có sẵn trong thời gian lưu trữ 10 năm.
Thời Hạn Lưu Trữ
Phần tiêu đề “Thời Hạn Lưu Trữ”Bản ghi kiểm toán được lưu 10 năm theo Luật Kế toán Việt Nam (Điều 40). Lưu trữ tự động (sang cold storage nén) sau 2 năm giữ cho bảng AuditLog chính hoạt động hiệu quả. Bản ghi đã lưu trữ vẫn có thể truy vấn qua UI quản trị.
5. Hạ Tầng Công Việc Nền
Phần tiêu đề “5. Hạ Tầng Công Việc Nền”Lựa Chọn
Phần tiêu đề “Lựa Chọn”ASP.NET Core hosted services với Hangfire (SQL Server backing store). Lý do:
- SQL Server đã là cơ sở dữ liệu chính — không cần hạ tầng bổ sung
- Hangfire cung cấp web dashboard để giám sát công việc, thử lại và lịch sử
- Thư viện được hỗ trợ tốt với tích hợp EF Core và ASP.NET Core
- Đơn giản để vận hành cho đội ngũ ≤5 kỹ sư
Phân Loại Công Việc
Phần tiêu đề “Phân Loại Công Việc”| Loại | Ví dụ | Lịch trình |
|---|---|---|
| Xử lý cuối kỳ | Xác thực đóng kỳ tài chính, điều chỉnh cuối kỳ VAS | Theo yêu cầu (kích hoạt bởi người dùng) |
| Kiểm tra hóa đơn điện tử | Kiểm tra hóa đơn Submitted đang chờ | Mỗi 5 phút |
| Tạo báo cáo | Tạo trước báo cáo pháp định theo lịch | Có thể cấu hình theo tenant (ví dụ: hàng tháng) |
| Đồng bộ tỷ giá | Lấy tỷ giá VND mới nhất từ nguồn NHNN | Hàng ngày |
| Lưu trữ kiểm toán | Chuyển bản ghi kiểm toán cũ hơn 2 năm sang archive | Hàng tháng |
| Xuất dữ liệu | Xuất dữ liệu tenant để offboard | Theo yêu cầu |
Cô Lập Công Việc
Phần tiêu đề “Cô Lập Công Việc”Mỗi lần chạy công việc được giới hạn trong một tenant (khi áp dụng). Công việc resolve TenantId từ tham số riêng của nó — không bao giờ từ context môi trường. TenantId được đặt trong SESSION_CONTEXT cho kết nối cơ sở dữ liệu dùng trong công việc, đảm bảo RLS hoạt động bình thường. Công việc toàn nền tảng (ví dụ: đồng bộ tỷ giá) dùng PlatformAdminDbContext.
Chính Sách Thử Lại
Phần tiêu đề “Chính Sách Thử Lại”| Loại công việc | Số lần thử tối đa | Khoảng thời gian thử lại |
|---|---|---|
| Kiểm tra hóa đơn điện tử | Thử liên tục | 5 phút |
| Tạo báo cáo | 3 lần thử | 5 phút, 15 phút, 30 phút |
| Đồng bộ tỷ giá | 5 lần thử | 10 phút (theo cấp số nhân) |
| Xử lý cuối kỳ | Không tự động thử lại (cần người dùng kích hoạt lại) | N/A |
Công việc hết lần thử được chuyển sang trạng thái dead-letter. Quản trị viên Hệ thống và (khi liên quan đến tenant) quản trị viên tenant được thông báo. Công việc không bị bỏ qua im lặng.
6. Cấu Hình Hệ Thống
Phần tiêu đề “6. Cấu Hình Hệ Thống”Mặc Định Nền Tảng
Phần tiêu đề “Mặc Định Nền Tảng”Mặc định toàn nền tảng được lưu trong bảng PlatformSettings (không áp dụng RLS):
PlatformSettings (cấp nền tảng, không áp dụng RLS)──────────────────────────────────────────────────────SettingKey NVARCHAR(100) PKSettingValue NVARCHAR(MAX) NOT NULLDescription NVARCHAR(500) NULLLastUpdatedAt DATETIMEOFFSET NOT NULLLastUpdatedBy NVARCHAR(200) NOT NULLVí dụ: timeout phiên cookie, ghi đè chính sách mật khẩu, URL nguồn tỷ giá, thời gian lưu trữ kiểm toán.
Ghi Đè Theo Tenant
Phần tiêu đề “Ghi Đè Theo Tenant”Tenant có thể ghi đè một tập hợp con các mặc định nền tảng thông qua TenantSettings. ITenantSettingsService resolve giá trị theo thứ tự: ghi đè tenant → mặc định nền tảng → mặc định code.
7. Phụ Thuộc Module
Phần tiêu đề “7. Phụ Thuộc Module”Các module khác phụ thuộc vào Nền tảng Cốt lõi như sau:
| Module | Phụ thuộc Nền tảng Cốt lõi |
|---|---|
| Tất cả module | ITenantContext (TenantId), thực thi RBAC, Audit interceptor, cột CustomData |
| Kế toán | FiscalPeriods, ChartOfAccounts, VasChartOfAccounts, ExchangeRates |
| Tất cả module | Domain event bus (cho hook customization engine Giai đoạn 2) |
| Hóa đơn điện tử | TenantEInvoiceConfigs, EInvoiceProviders |
| Công cụ Tùy chỉnh (Giai đoạn 2) | CustomFieldDefinitions, ValidationRules, WorkflowDefinitions |
Nền tảng Cốt lõi phải là module đầu tiên được tích hợp và kiểm thử. Không module nào khác được phát hành cho đến khi các điều kiện sau được xác minh:
- Cấp phát tenant tạo ra một tenant được cô lập hoàn toàn với dữ liệu seed đúng
- Các kiểm tra cô lập xuyên tenant đạt (xem multitenancy.md §Kiểm tra)
- Audit interceptor ghi lại tất cả thay đổi trên tất cả bảng entity cốt lõi
- Phiên xác thực cookie và thực thi quyền hoạt động end-to-end cho tất cả vai trò tích hợp sẵn
- Job scheduler nền khởi động, chạy một công việc test và ghi lại kết quả
8. Vấn Đề Còn Mở
Phần tiêu đề “8. Vấn Đề Còn Mở”-
UX người dùng đa tenant — Người dùng có quyền truy cập nhiều tenant cần màn hình chọn tenant sau khi đăng nhập. Luồng và UI chưa được thiết kế.
-
MFA — Xác thực đa yếu tố dựa trên TOTP được hoãn đến Giai đoạn 2.
TenantSettingsnên bao gồm keymfa.requiredcó thể bật theo tenant khi MFA được triển khai. -
Xuất dữ liệu khi offboard — Định dạng và nội dung xuất dữ liệu tenant (cho tuân thủ PDPD khi có yêu cầu xóa dữ liệu) cần được định nghĩa trước khi có khách hàng nào offboard. Điều này nên được thiết kế trước khi go-live dù chưa có tenant nào chấm dứt.
-
Nguồn tỷ giá — NHNN (Ngân hàng Nhà nước Việt Nam) cung cấp tỷ giá hàng ngày. URL feed và định dạng chính xác phải được xác nhận. Tỷ giá VCB (Vietcombank) được các kế toán Việt Nam sử dụng phổ biến như một lựa chọn thay thế; trường
ExchangeRates.Sourcehỗ trợ ghi lại nguồn nào đã được sử dụng. -
Kiểm soát truy cập Hangfire dashboard — Web dashboard của Hangfire phải được giới hạn chỉ Quản trị viên Hệ thống và không được truy cập trên các endpoint công khai trong môi trường production.