// Package order is the platform's shared order seam: the exported wire shape of // the `order.created` domain event (B4.1). It is the contract between the order // service (producer) and every reactor — inventory commit, fulfillment // allocation, loyalty, ERP sync — none of which the order service knows about. // // Like platform/catalog and platform/inventory, it holds only the wire value // types + encode/decode, and imports only the stdlib. // // `order.created` is a FACT, not a request: by the time it is emitted the order // is placed and paid. Reactors must be idempotent (the transport is // at-least-once) and must not assume they can refuse it — the sale already // happened. Oversell on commit is reconciled downstream (backorder), not // prevented here. package order import "encoding/json" // Line is one ordered line item, reduced to what reactors need: the SKU, how // many units sold, the inventory location to commit against, and whether the // item is drop-ship (fulfilled by the supplier, never decremented from local // stock). Pricing/tax live in the order grain, not on this seam. type Line struct { SKU string `json:"sku"` Quantity int `json:"quantity"` // Location is the inventory location / store id to decrement. Empty means // "use the order's Country" (central stock) — see Created.Country. Location string `json:"location,omitempty"` // DropShip marks items fulfilled directly by the supplier. The inventory // reactor must skip the stock decrement for these lines. DropShip bool `json:"drop_ship,omitempty"` } // Created is the payload of an event.OrderCreated envelope. // // Country is the default inventory location for the commit (central stock). A // Line may override it with its own Location (store id); when a line's Location // is empty the commit falls back to Country. See docs/inventory-shape.md. type Created struct { OrderID string `json:"order_id"` OrderReference string `json:"order_reference,omitempty"` CartID string `json:"cart_id,omitempty"` Country string `json:"country"` Currency string `json:"currency,omitempty"` CustomerEmail string `json:"customer_email,omitempty"` // CustomerID is the customer profile ID for identity linking (B0.4). Reactors // (e.g. profile service) use this to link the order to the customer profile. CustomerID string `json:"customer_id,omitempty"` Lines []Line `json:"lines"` PlacedAtMs int64 `json:"placed_at_ms,omitempty"` } // Encode serialises the payload for event.Event.Payload. func (c Created) Encode() ([]byte, error) { return json.Marshal(c) } // Decode parses an order.created payload — the consumer side of Encode. func Decode(raw []byte) (Created, error) { var c Created err := json.Unmarshal(raw, &c) 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"` 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) } func DecodeInvoiceIssued(raw []byte) (InvoiceIssued, error) { var i InvoiceIssued err := json.Unmarshal(raw, &i) return i, err } // InvoicePaid is the payload of an event.InvoicePaid envelope. type InvoicePaid struct { OrderID string `json:"order_id"` InvoiceNumber string `json:"invoice_number"` 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) } func DecodeInvoicePaid(raw []byte) (InvoicePaid, error) { var i InvoicePaid err := json.Unmarshal(raw, &i) 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 }