diff --git a/event/event.go b/event/event.go index 35bf269..4165b59 100644 --- a/event/event.go +++ b/event/event.go @@ -64,9 +64,15 @@ const ( // InvoiceIssued is emitted when an order is invoiced (pay-later placement or // manual invoice issuance). Payload is an order.InvoiceIssued (platform/order). InvoiceIssued Type = "invoice.issued" - // InvoicePaid is emitted when an invoiced order payment status crosses to paid. - // Payload is an order.InvoicePaid (platform/order). + // InvoicePaid is emitted for EVERY registered payment on an invoiced order + // (each event = one payment fact with its own amount/paidAt; the ledger + // books 1930→1510 per event). Payload is an order.InvoicePaid (platform/order). InvoicePaid Type = "invoice.paid" + // InvoiceCredited is emitted when a credit note is issued against an issued + // invoice. Payload is an order.InvoiceCredited (platform/order): the credited + // amount + per-rate VAT split, which the ledger books as the reversal + // voucher (1510 C / 304x D / 261x D per rate). + InvoiceCredited Type = "invoice.credited" // Analytics (tracking). Replaces the integer enum 0–6 that finder and diff --git a/order/order.go b/order/order.go index d171e76..1d93de7 100644 --- a/order/order.go +++ b/order/order.go @@ -59,16 +59,28 @@ func Decode(raw []byte) (Created, error) { return c, err } +// VatBucket is one VAT rate's split on an issued invoice: the net basis and +// the VAT amount, both in minor units. RateBp is basis points (2500 = 25 %). +// Summing ExVat+Vat across buckets equals AmountIncVat — the ledger reactor +// books the invoice from these (revenue accounts per rate, e.g. 3041/3042/3043 +// and 2611/2621/2631 in BAS), so it needs the split, not just the total. +type VatBucket struct { + RateBp int `json:"rate_bp"` + ExVat int64 `json:"ex_vat"` + Vat int64 `json:"vat"` +} + // InvoiceIssued is the payload of an event.InvoiceIssued envelope. type InvoiceIssued struct { - OrderID string `json:"order_id"` - InvoiceNumber string `json:"invoice_number"` - CompanyID string `json:"company_id,omitempty"` - CustomerEmail string `json:"customer_email,omitempty"` - AmountIncVat int64 `json:"amount_inc_vat"` - DueDate string `json:"due_date"` - OCRReference string `json:"ocr_reference"` - IssuedAtMs int64 `json:"issued_at_ms"` + OrderID string `json:"order_id"` + InvoiceNumber string `json:"invoice_number"` + CompanyID string `json:"company_id,omitempty"` + CustomerEmail string `json:"customer_email,omitempty"` + AmountIncVat int64 `json:"amount_inc_vat"` + VatBuckets []VatBucket `json:"vat_buckets,omitempty"` + DueDate string `json:"due_date"` + OCRReference string `json:"ocr_reference"` + IssuedAtMs int64 `json:"issued_at_ms"` } func (i InvoiceIssued) Encode() ([]byte, error) { return json.Marshal(i) } @@ -86,6 +98,10 @@ type InvoicePaid struct { CompanyID string `json:"company_id,omitempty"` AmountPaid int64 `json:"amount_paid"` PaidAtMs int64 `json:"paid_at_ms"` + // OcrReference lets the ledger settle the exact receivable (the payment's + // 1510 credit references the invoice's OCR) without depending on the + // invoice.issued event having been booked first. + OcrReference string `json:"ocr_reference,omitempty"` } func (i InvoicePaid) Encode() ([]byte, error) { return json.Marshal(i) } @@ -96,3 +112,28 @@ func DecodeInvoicePaid(raw []byte) (InvoicePaid, error) { return i, err } +// InvoiceCredited is the payload of an event.InvoiceCredited envelope: a +// credit note was issued against an issued invoice. The credited amount and +// per-rate VAT split mirror the credited lines (line total × qty / line +// quantity, VAT proportionally — the same math as the order grain's +// InvoiceCreditedAmount). The ledger books the reversal voucher +// (1510 C / 304x D / 261x D per rate), idempotent per credit number. +type InvoiceCredited struct { + OrderID string `json:"order_id"` + InvoiceNumber string `json:"invoice_number"` // the original invoice + CreditNumber string `json:"credit_number"` // from the same gap-free series + CompanyID string `json:"company_id,omitempty"` + OcrReference string `json:"ocr_reference,omitempty"` + AmountIncVat int64 `json:"amount_inc_vat"` + VatBuckets []VatBucket `json:"vat_buckets,omitempty"` + CreditedAtMs int64 `json:"credited_at_ms"` +} + +func (c InvoiceCredited) Encode() ([]byte, error) { return json.Marshal(c) } + +func DecodeInvoiceCredited(raw []byte) (InvoiceCredited, error) { + var c InvoiceCredited + err := json.Unmarshal(raw, &c) + return c, err +} +