package models import "time" // Counter sales, stored at the fidelity the till actually rang them. // // Separate from `orders` on purpose. An app order and a counter bill are // different documents: a bill carries a cashier, a terminal, a rounding // adjustment, promo campaigns, loyalty movement and a payment split across // several tenders, none of which `orders` has anywhere to put. Forcing one into // the other's shape loses whichever fields do not fit, and the loss is silent. // // The cost of the split is that existing revenue queries do not see these rows // until they are extended to union them in — done in orderRepository's summary // queries, and the thing to remember when adding a new report. // // Stock is *not* separate: a counter sale writes the same productstocks "out" // rows an app order does, through the same helper. Two stock ledgers would mean // the catalogue pull sends a till figures that ignore its own sales. // PosOrders is one counter bill. type PosOrders struct { Posorderid int `json:"posorderid" gorm:"primaryKey;autoIncrement;column:posorderid"` // The UUID minted at the till. Globally unique by construction and the only // thing that identifies this bill, so it carries a unique index: delivery is // at-least-once and the same bill legitimately arrives more than once. Terminalorderid string `json:"terminalorderid" gorm:"column:terminalorderid;uniqueIndex;not null"` // Human-facing, and unique only per terminal — a till that was replaced // restarts its own series, so gaps are normal and duplicates across // terminals are expected. Invoicenumber string `json:"invoicenumber" gorm:"column:invoicenumber;index"` Tenantid int `json:"tenantid" gorm:"column:tenantid;index"` Locationid int `json:"locationid" gorm:"column:locationid;index"` // Which physical till, e.g. "T4A9". Free text: nothing keys on it, but a // support call starts with it. Terminalid string `json:"terminalid" gorm:"column:terminalid;index"` Cashiername string `json:"cashiername" gorm:"column:cashiername"` // Resolved against the customers table. Zero for a walk-in. Customerid int `json:"customerid" gorm:"column:customerid;index"` Customermobile string `json:"customermobile" gorm:"column:customermobile"` Customername string `json:"customername" gorm:"column:customername"` // When the sale was rung, not when it reached us — a till that was offline // for a day uploads bills whose Billedat is yesterday, and every daily // figure must use this rather than Receivedat. Billedat time.Time `json:"billedat" gorm:"column:billedat;index"` // YYYY-MM-DD of Billedat, denormalised so a day's takings are one indexed // equality match rather than a range scan with timezone arithmetic. Businessdate string `json:"businessdate" gorm:"column:businessdate;index"` Subtotal float64 `json:"subtotal" gorm:"column:subtotal"` Discount float64 `json:"discount" gorm:"column:discount"` Taxamount float64 `json:"taxamount" gorm:"column:taxamount"` // The paise adjustment printed on the bill. Kept because total is not // derivable from the other columns without it. Roundoff float64 `json:"roundoff" gorm:"column:roundoff"` // What the shopper actually paid. The figure every revenue report sums. Total float64 `json:"total" gorm:"column:total"` Pointsearned int `json:"pointsearned" gorm:"column:pointsearned"` Pointsredeemed int `json:"pointsredeemed" gorm:"column:pointsredeemed"` Itemcount int `json:"itemcount" gorm:"column:itemcount"` // The largest tender, for the common "how did they pay" grouping. Paymentmode string `json:"paymentmode" gorm:"column:paymentmode;index"` // The full split, verbatim. A bill can be part cash, part card, part // loyalty, and collapsing that to one mode would lose the reconciliation a // cashier settles their drawer against. Paymentsjson string `json:"paymentsjson" gorm:"column:paymentsjson;type:jsonb"` // Campaigns that fired, stored as amounts rather than rules — a bill read // back years later must show what was given, not what today's rules give. Promosjson string `json:"promosjson" gorm:"column:promosjson;type:jsonb"` // GST per slab, as printed on the tax invoice. Taxbreakdownjson string `json:"taxbreakdownjson" gorm:"column:taxbreakdownjson;type:jsonb"` // Which upload carried this bill, and when it landed. Kept for tracing a // terminal's complaint back to a specific batch. Batchid string `json:"batchid" gorm:"column:batchid;index"` Receivedat time.Time `json:"receivedat" gorm:"column:receivedat"` Created time.Time `json:"created" gorm:"column:created;autoCreateTime"` Updated time.Time `json:"updated" gorm:"column:updated;autoUpdateTime"` Items []PosOrderItems `json:"items" gorm:"-"` } func (PosOrders) TableName() string { return "pos_orders" } // PosOrderItems is one line of a counter bill. type PosOrderItems struct { Posorderitemid int `json:"posorderitemid" gorm:"primaryKey;autoIncrement;column:posorderitemid"` Posorderid int `json:"posorderid" gorm:"column:posorderid;index"` Tenantid int `json:"tenantid" gorm:"column:tenantid;index"` Locationid int `json:"locationid" gorm:"column:locationid;index"` Productid int `json:"productid" gorm:"column:productid;index"` // Snapshotted rather than joined. A product renamed or withdrawn next month // must not change what a bill from today says it sold. Productname string `json:"productname" gorm:"column:productname"` Barcode string `json:"barcode" gorm:"column:barcode"` Unitname string `json:"unitname" gorm:"column:unitname"` // Fractional: a counter sells 1.5 kg of onions. Note that productstocks // cannot represent that — see roundStockQty. Quantity float64 `json:"quantity" gorm:"column:quantity"` Unitprice float64 `json:"unitprice" gorm:"column:unitprice"` Discountamount float64 `json:"discountamount" gorm:"column:discountamount"` // Stored as a fraction (0.18), matching how the till holds it. Gstrate float64 `json:"gstrate" gorm:"column:gstrate"` Taxamount float64 `json:"taxamount" gorm:"column:taxamount"` // What this line contributed to the bill total, after its share of every // discount. The lines sum to the bill's Total less Roundoff. Linetotal float64 `json:"linetotal" gorm:"column:linetotal"` Created time.Time `json:"created" gorm:"column:created;autoCreateTime"` } func (PosOrderItems) TableName() string { return "pos_order_items" }