Finance — Kiến trúc & dữ liệu
Luồng chính
Pipeline billing generation
Billing generation chạy theo 3 lớp tách biệt (invariant INV-BILLING-CORE-001):
- Trigger: real confirm, subscription approve/skip, regenerate khi edit booking đã confirm.
- Gate:
UpdateBillingByIdAsynchoặc equivalent gate là điều kiện bắt buộc trước khi chốt booking (INV-CONFIRM-BILLING-001). - Frequency override:
ResolveBillingFrequencyOverridemap Pay Now →One_Off, Direct Debit →Weekly; real path và preview dùng chung helper, preview không mutate billing row. - Deposit: confirm theo deposit vẫn sinh invoice full amount; settlement nằm ngoài billing generation (INV-DEP-015).
- Subscription: approve/skip sinh/đảo invoice qua service backend; price line active là điều kiện bắt buộc để persist invoice (INV-SUB-BM-04).
Ranh giới quan trọng
| Ranh giới | Quy tắc |
|---|---|
| Billing calculation core | Có thể trả invoice/credit-note projection trong memory, nhưng không gọi EF persistence hoặc scheduler. |
| Persistence boundary | Chỉ boundary persist thật mới được add invoice/credit note, finance log, scheduler và SaveChangesAsync. |
| Preview Billing Diff | Read-only; không ghi invoice, scheduler, finance log hoặc projection. |
| Payment domain | Đọc invoice/payment option để thu tiền, nhưng trạng thái chứng từ tài chính vẫn thuộc Finance. |
| Accounting sync | Sync provider là side effect; record Finance trong AIMY vẫn là nguồn nghiệp vụ nội bộ. |
Luồng gửi invoice email
- Single vs batch — batch một site dùng template site đó; nhiều site → editor riêng từng site, payload chỉ chứa invoice của đúng site (INV-INVOICE-EMAIL-002).
- Provider-independent — Xero hay AIMY Accounting đều cho phép invoice email (INV-INVOICE-EMAIL-001).
- Placeholder render —
,,resolve lúc generate; placeholder chưa resolve không tồn tại trong email gửi đi (INV-INVOICE-EMAIL-005).
Ví dụ thực tế: Admin chọn 5 invoice từ 2 site rồi Email → composer hiện 2 editor theo site → send từng payload đúng site; customer nhận link phải đăng nhập mới mở.
Luồng invoice reminder scheduling
- Evaluator hằng ngày — chạy theo local calendar của BU; tính target
Invoice.Date/DueDate ∓ N; không backfill ngày trước khi enable rule (INV-INVOICE-REMINDER-002). - Eligibility re-check — ngay trước khi tạo notification: active,
AmountDue > 0, không trong DD collection flow,LockTypeIdnull; mỗi exclusion ghi skip reason có cấu trúc, không setInvoice.Sent(INV-INVOICE-REMINDER-003). - Idempotent send — claim theo
(Invoice.Guid, rule ID)với uniqueness guard; worker chạy trùng chỉ một worker tạo logical send (INV-INVOICE-REMINDER-005). - History — 1 entry trên Invoice Transaction + Account, correlation với OutgoingMessage; retry không tạo entry trùng (INV-INVOICE-REMINDER-006).
Ví dụ thực tế: Rule
BeforeDueDate(7), hóa đơn đến hạn 20/07 → ngày 13/07 (giờ BU) evaluator chọn invoice còn nợ, không DD/lock → tạo 1 reminder send + 1 entry history; worker trùng không gửi lần 2.
Đồng bộ projection FinanceTransaction qua worker
Booking numbers và credit-note allocation được đồng bộ qua finance-transaction-queue, không dùng trigger SQL.
Sync_Invoice_BookingNumbers— enqueue khiTermBookingInvoiceLoginsert/update/deactivate; worker tínhBookingNumbersdạngBK-{id}(status 150/200/250) hoặcREQ-{id}, rebuildSearchKeytheo formulaNaN; idempotent, trigger cũ drop sau khi worker verified.- Credit-note allocation —
HandleFinanceTransactionxử lýAllocate_CreditNote/Deallocate_CreditNote: load CreditNote fresh từ DB, cập nhậtAmountDue/AmountCredited/AmountPaid/StatusId; CreditNote không tồn tại → log lỗi; không có FT row → skip, không tạo mới. - Projection refresh —
ActionTypeId = Refresh_FinanceTransaction_Projection,EntityGuid = FinanceTransaction.Guid, RowKeyfinance-transaction-projection:{Guid}; dedup trước dispatch quaTux.Workers.EventScheduler; latest source state wins.
Ví dụ thực tế: Confirm booking tạo
TermBookingInvoiceLogmới → enqueueSync_Invoice_BookingNumbers→ worker ghiBookingNumbers = "BK-1234", rebuild SearchKey; Invoice Manager hiển thị Booking ID khớp association active.
Dữ liệu thường gặp
| Nhóm | Ví dụ |
|---|---|
| Billing | Billing, billing period, payment option, split rule, currency setting. |
| Invoice | Invoice, InvoiceLine, invoice scheduler, invoice lock/processing state. |
| Credit note | CreditNote, credit-note line, allocation/link về invoice. |
| Projection | Finance transaction search key, booking number sync, projection refresh state. |
| Email/reminder | Notification template, Invoice_Fully_Paid event/hint, reminder rule, Viewed/Sent flag. |
| Settings | Finance/accounting settings, invoice templates, Xero provider config. |
| Subsidy licence | Subsidy licence (SWN/Program ID) gắn site qua Licence.OrgId. |
Drift cần nhớ
- Projection có thể cần refresh khi booking number, credit-note allocation hoặc invoice status thay đổi.
- Invoice lock/processing giúp tránh thao tác admin đụng vào chứng từ đang chạy job.
- Preview Billing Diff dùng chung logic tính toán nhưng phải giữ zero side effect.
BookingNumberstrống trong projection không chứng minh invoice không có booking — đối chiếu source association trước (invoice-manager-booking-id-integrity).