Finance — Khái niệm cốt lõi
Core objects
| Khái niệm | Nghĩa |
|---|---|
| Billing | Aggregate gom nghĩa vụ tính tiền từ booking/subscription; sở hữu payment option, split context, dữ liệu sinh invoice. |
| BillingPeriod | Kỳ tài chính hoặc giai đoạn dùng để nhóm line, nhất là subscription billing schedule. |
| Invoice | Chứng từ phải thu gửi customer/account; có header, line, tax/discount, status và scheduler xử lý. |
| InvoiceLine | Dòng phí cụ thể: session, add-on, discount, adjustment hoặc metadata liên quan booking. |
| CreditNote | Chứng từ điều chỉnh ngược invoice khi giảm tiền, hủy hoặc chỉnh booking sau khi invoice đã phát hành/paid. |
| FinanceTransaction | Projection cho query invoice/credit-note/payment trạng thái hiện tại; giúp list/search nhanh và ổn định. |
| InvoiceScheduler | Job/scheduler phát hành hoặc xử lý invoice ở thời điểm phù hợp. |
| Preview Billing Difference | Read-only computation cho edit booking: tính invoice/credit-note dự kiến mà không persist. |
| Accounting Provider | Nhà cung cấp kế toán như Xero; nhận invoice/credit-note/payment sync từ AIMY. |
Billing vs Invoice
Billing là context tính tiền; Invoice là chứng từ customer thấy, finance vận hành. Một billing có thể tạo nhiều invoice khi split rule tách theo org, attendee, category hoặc kỳ.
Invoice vs FinanceTransaction
Invoice là record nghiệp vụ gốc. FinanceTransaction là projection/read model cho search, list, sync booking number, allocation, trạng thái tổng hợp. Lệch giữa hai bên → source of truth vẫn là record gốc; projection cần refresh hoặc reconcile.
Credit note
Credit note xuất hiện khi edit/cancel giảm nghĩa vụ tài chính sau khi invoice phát hành hoặc paid. Không phải "xóa invoice"; là chứng từ điều chỉnh giữ lịch sử tài chính audit được.
Billing generation — cách sinh chứng từ
Billing chứa nghĩa vụ tính tiền; billing generation là pipeline biến booking/enrollment đã confirm thành chứng từ (Invoice, CreditNote). Nó tách 3 lớp (invariant INV-BILLING-CORE-001):
| Lớp | Làm gì |
|---|---|
| Prepare | Gom booking/order/line/extra/discount/split thành input chuẩn. |
| Calculation core | Tính invoice, line, tax, discount, split group, total, credit-note impact — chỉ trong memory, không persist. |
| Persistence | Lưu invoice/credit note, finance log, scheduler, SaveChangesAsync. |
Preview Billing Diff và real confirm dùng chung core → projected output khớp real output. Quy tắc xương sống (mỗi rule kèm ví dụ):
- Gate confirm — confirm chỉ chốt sau khi billing regeneration thành công; billing fail → confirm fail, không ghi side effect (INV-CONFIRM-BILLING-001).
Ví dụ thực tế: Phụ huynh confirm booking Pay Now nhưng cấu hình DD hỏng → booking không sang
Approved, trả failure → admin sửa cấu hình rồi confirm lại. - Frequency override — Pay Now →
One_Off, Direct Debit →WeeklyquaResolveBillingFrequencyOverride; real path và preview dùng chung helper này.Ví dụ thực tế: Khách chọn Direct Debit khi checkout → invoice tách theo tuần để DD thu đúng kỳ.
- Deposit — confirm theo deposit vẫn sinh invoice full amount; settlement nằm ngoài billing generation (INV-DEP-015).
Ví dụ thực tế: Khách đặt cọc 20% → invoice vẫn ghi đủ toàn bộ; phần cọc Payment xử lý, không sửa invoice.
- Approve / Skip subscription — approve sinh invoice qua
UpdateBillingByIdAsync; skip với invoice đã Paid/Sent/Synced thì phát credit note, chưa phát hành thì regenerate.Ví dụ thực tế: Admin approve kỳ Tháng 5 → tạo TermBooking + invoice + log Approved; skip kỳ đã thu → credit note đảo.
- Price line bắt buộc — subscription generation cần
SubscriptionPriceId+ ≥1 price line active; thiếu → fail trước persist, không fallback từ schedule amount (INV-SUB-BM-04).Ví dụ thực tế: Gói Weekly chưa khai price line → generate fail với structured validation; khai xong mới ra invoice, không bao giờ sinh hóa đơn trống.
Invoice email
Invoice email là đường gửi invoice cho customer qua Notification template, sẵn có bất kể accounting provider (Xero hay AIMY Accounting) (INV-INVOICE-EMAIL-001).
- Composer — mở trước khi schedule; hiển thị số lượng invoice, recipient, From, Email Reply To, Subject, Body; Subject/Body chỉnh được trước Send (INV-INVOICE-EMAIL-002).
- Single & batch — batch nhiều site → mỗi site một editor riêng theo template + Email Reply To của site; cancel/close composer → không schedule gì.
- Không PDF attachment — email không đính kèm invoice PDF; không có control "Include Invoice PDF", legacy payload cũng không khôi phục được attachment (INV-INVOICE-EMAIL-003).
- Placeholders —
,,; link HTTPS, dùng opaque guid/token, không lộ numeric id (INV-INVOICE-EMAIL-005). - Customer link — View/Download yêu cầu authenticate + authorize theo account/BU; unauthorized trả not-found/forbidden, không lộ metadata (INV-INVOICE-LINK-001).
Ví dụ thực tế: Admin mở invoice AIN-0042 Approved trong BU Xero, chọn Email → composer hiện template site, chỉnh Subject/Body rồi Send → email không PDF; customer đăng nhập mới mở link View/Download.
Invoice reminders
Reminder là nhắc nợ tự động gửi qua đúng pipeline Invoice Email, cấu hình theo business unit.
| Quy tắc | Nội dung |
|---|---|
| Giới hạn | ≤3 rule active/BU; add/edit/delete; disable giữ rule, chặn run mới |
| Loại rule | OnInvoiceDate (no offset), BeforeDueDate(N), AfterDueDate(N) với offset 0–365 |
| Trùng target | 2 rule không trùng effective target (kể cả 0 days before/0 days after) |
| Target date | Invoice.Date; DueDate ∓ N theo local calendar/timezone BU; không backfill trước ngày enable |
| Eligibility | active, cùng BU, AmountDue > 0, không trong DD collection flow, LockTypeId null |
| Template | render theo Invoice Email template site tại thời điểm gửi; không lưu sender/subject/attachment riêng |
| Idempotent | 1 logical send per (invoice, rule); Invoice.Sent chỉ true sau khi NotificationJob tạo |
Ví dụ thực tế: Admin tạo rule
AfterDueDate(7); invoice AIN-0031 còn nợ, không trong DD collection flow → evaluator chạy due+7 theo giờ BU → gửi reminder bằng template Invoice Email hiện tại, ghi 1 entry history trên Account + Invoice Transaction.
Invoice fully-paid notification
Invoice_Fully_Paid là EntityEvent chuyên biệt phát khi invoice chuyển sang FinanceStatus.Paid (INV-INVOICE-PAID-NOTIF-001).
- Trigger — mọi path set
Invoice.StatusId = Paid(payment hoặc credit-note allocation làmAmountDue = 0) phát event sau commit, chỉ khi có transition thật. - Ngoại lệ — invoice
TotalAmount = 0đánh Paid vì không có gì thu → không phát; invoice import/sync sẵn Paid → không phát. - Hint — event mang
NotificationEventTypeId = PaymentSuccessConfirmation(810); worker tạo NotificationJob qua hint path, không gọi trực tiếpCreateNotificationJobAsync(INV-INVOICE-PAID-NOTIF-002). - Template — default từ NotificationOption, site overwrite qua NotificationSetting theo
OrgId; email disabled ở site → không gửi (INV-INVOICE-PAID-NOTIF-003). - Recipient — primary contact của account; thiếu email → job complete không delivery, không throw (INV-INVOICE-PAID-NOTIF-004).
Ví dụ thực tế: Phụ huynh trả nốt số dư invoice AIN-0027 →
AmountDue = 0→ commit rồi publishInvoice_Fully_Paid→ worker tạo PaymentSuccessConfirmation job; email về primary contact nếu site bật kênh email.
Invoice viewed tracking
Invoice.Viewed (boolean) lưu trạng thái customer đã truy cập invoice thành công (INV-INVOICE-VIEW-001).
- Mặc định
false; chỉ settruekhi customer authenticated + authorized và View/Download PDF thành công (INV-INVOICE-VIEW-002). - Monotonic — không reset bởi resend email, edit, paid hay sync; truy cập lặp idempotent.
- Không set — admin mở Invoice Details, email schedule/delivery, request unauthorized, hoặc PDF fail.
- Hiển thị — Invoice Details API trả
Viewed; Booking Manager chỉ hiện tagViewedkhi state true (INV-INVOICE-VIEW-003).
Ví dụ thực tế: Customer mở link View Invoice AIN-0012 lần đầu → set
Viewed = true→ admin thấy tagViewed; resend email không mất tag.
Invoice lock & tag "Processing"
Invoice đang trong luồng xử lý payment có LockTypeId != null được đánh dấu "processing" trong Invoice Manager.
- List — dưới Invoice Number hiện tag
ProcessingkhiLockTypeId != null; không thay thế hay mutate status (INV-INV-MGR-LOCK-001). - Hover — tooltip đúng nội dung
Payment in processing(INV-INV-MGR-LOCK-002). - Details — title hiện
Payment in processingsau Invoice Status;Add PaymentvàApply Creditvisible nhưng disabled (INV-INV-MGR-LOCK-003/005). - Display-only — tag không tạo invoice status mới, không đổi payment/lock lifecycle (INV-INV-MGR-LOCK-004).
Ví dụ thực tế: Payment đang xử lý, invoice AIN-0090 có
LockTypeId != null→ list hiện tag Processing dưới số hóa đơn; details thấyAdd Payment/Apply Creditdisabled nhưng trạng thái invoice vẫn nguyên.
Ownership ngắn gọn
| Owner | Sở hữu |
|---|---|
| Billing | Payment option trên billing, split context, dữ liệu chuẩn bị sinh invoice. |
| Invoice | Header/line/status/lock của invoice. |
| CreditNote | Header/line/allocation của credit note. |
| FinanceTransaction | Projection/search state, có thể refresh từ nguồn gốc. |
| Payment | Thu tiền và provider transaction; không sở hữu invoice lifecycle. |