Architecture
1. System Context (C4 L1)
2. Container View (C4 L2)
3. Component View (C4 L3) — Phân lớp nội bộ
| Layer | Trách nhiệm |
|---|---|
| Components | Kafka producer/consumer, WebSocket emitter, asset ngân hàng VN |
| Controllers | Auth + cổng Casbin + map DTO; action tùy chỉnh phiếu; scope tài khoản theo merchant |
| Worker | FinanceWorkerService — map sự kiện inbound thành lời gọi issueDirect |
| Core services | FinanceVoucherService (động cơ hạch toán), FinanceAccountService, PaymentIntegrationService |
| Repositories | Truy vấn Drizzle, soft-delete, lưu balance/posting-sequence (tất cả từ @nx/core) |
4. Chỉ mục State Machine
| Entity | Trạng thái | Sơ đồ |
|---|---|---|
FinanceVoucher | DRAFT, ISSUED, VOIDED | → jump |
FinanceTransaction | PENDING, COMPLETED, CANCELLED | → jump |
FinanceAccount | ACTIVATED, DEACTIVATED, ARCHIVED | → jump |
FinanceVoucher
| Từ | Sự kiện | Đến | Guards |
|---|---|---|---|
— | createDraft | DRAFT | nguồn manual; dòng đệm trong metadata.draftLines |
— | issueDirect | ISSUED | dòng post ngay; replay idempotent trên dedup key |
DRAFT | approve (issueDraft) | ISSUED | draft phải có ≥1 dòng; nâng draft, gán số |
DRAFT | deleteDraft | removed | chỉ khi đang DRAFT |
ISSUED | void | VOIDED | không phải COGS/INVENTORY_ADJUSTMENT; opening-balance chỉ khi không có hoạt động sau; phát phiếu đối ứng |
FinanceTransaction (dòng ledger)
Trên thực tế các dòng phiếu được tạo trực tiếp
COMPLETED.PENDING/CANCELLEDtồn tại trong enum cho các luồng tương lai.
FinanceAccount
5. Kịch bản Runtime
5.1 Thanh toán bán hàng → phiếu RECEIPT
| Bước | Chi tiết |
|---|---|
| 1-2 | Idempotency qua sourceEventUid = attempt.uid (dedup theo từng sự kiện SALE_ORDER) |
| 3 | RECEIPT ⇒ hướng dòng suy ra DEBIT; balance tăng |
| 4 | Toàn bộ hạch toán là một DB transaction với SELECT … FOR UPDATE trên tài khoản |
5.2 Đơn mua đã nhận → phiếu PAYMENT (với leg tài sản)
5.3 Tồn kho xuất bán → hạch toán COGS
5.4 Merchant CDC → đối soát tài khoản + onboarding
6. Mối quan tâm xuyên suốt
| Mối quan tâm | Cách service này xử lý |
|---|---|
| AuthN | JWT (Issuer = identity), JWKS verify mỗi request (VerifierApplication) |
| AuthZ | Casbin; list/count tài khoản lọc theo GROUP policy từng merchant (assertMerchantAccess trên route single-entity) |
| i18n | Cột jsonb, dạng { default, en, vi } (tên account/category, party/reason phiếu) |
| Logging | Key-value có cấu trúc (key: %s); @logged() trên handler worker |
| Idempotency | Dedup phiếu: partial unique index theo từng-source (merchantId, type, sourceType, sourceId) và theo từng-event (sourceType, sourceEventUid); issueDirect replay phiếu hiện có khi trúng |
| Toàn vẹn hạch toán | postingSequenceLastValue tăng nghiêm ngặt theo tài khoản; balance tính trong-tx với float(_, 4); dòng tài khoản khóa FOR UPDATE |
| Soft-delete | SoftDeletableRepository (deletedAt) |
| IDs | Snowflake qua IdGenerator, worker 4 |