package models import "time" // Tenant represents the pre-existing 'tenants' table type Tenant struct { Tenantid int `json:"tenantid" gorm:"primaryKey;column:tenantid"` Tenantname string `json:"tenantname" gorm:"column:tenantname"` Primaryemail string `json:"primaryemail" gorm:"column:primaryemail"` Primarycontact string `json:"primarycontact" gorm:"column:primarycontact"` Status string `json:"status" gorm:"column:status;default:Active"` // Requiredeliveryotp decides whether the receiver must read a code back to // the rider. Worth the friction for a courier handing over a valuable // parcel; not for a food order, where it just slows every drop down. // Defaults off: turning it on platform-wide would block deliveries for // clients whose customer app has no way to show the code yet. Requiredeliveryotp bool `json:"requiredeliveryotp" gorm:"column:requiredeliveryotp;default:false"` // Deliverycategory is WHAT this client ships, from the same vocabulary // pricing uses (constants.DeliveryCategories) — a tenant whose category is // not a pricing category cannot be priced, so the two are one list. // // It decides whether reverse logistics applies: a returned meal is waste, // not inventory, so Food clients get no RTO path. See // constants.ReverseLogisticsAllowed for the reasoning. // // Empty on every tenant onboarded before this field existed, and empty // must keep behaving as it always did — which is why // ReverseLogisticsAllowed treats an unknown category as returnable rather // than defaulting the new flag to off. Deliverycategory string `json:"deliverycategory" gorm:"column:deliverycategory;size:32"` // Reverselogisticsenabled is the OPERATIONAL consequence, stored rather // than derived on every read. // // Two reasons it is its own column. First, an operator may need to turn // returns off for a non-Food client (a clearance line that is final sale) // or on for a Food client (a caterer who takes back equipment), and a // derived value cannot be overridden. Second, deriving it would mean that // editing a client's category silently changes whether live parcels can be // returned — a stored flag makes that an explicit second decision. // A POINTER, deliberately, and this is load-bearing. // // GORM omits a zero-value field from an INSERT when the tag declares a // default — so a plain `bool` set to false was silently dropped and the // column default (true) applied, creating Food clients with reverse // logistics ENABLED: the exact case this feature exists to prevent, // failing silently. A test caught it. // // Dropping the default instead is worse: ADD COLUMN ... NOT NULL with no // default fails outright on a populated tenants table. // // A pointer gives both. nil means "not stated" and the column default // (true) applies — which is what every row predating this field wants. A // non-nil false is written as false, because GORM never omits a non-nil // pointer. Read it through ReturnsEnabled(), never directly. Reverselogisticsenabled *bool `json:"reverselogisticsenabled" gorm:"column:reverselogisticsenabled;default:true"` Createdat time.Time `json:"createdat" gorm:"column:createdat;default:CURRENT_TIMESTAMP"` Updatedat time.Time `json:"updatedat" gorm:"column:updatedat;default:CURRENT_TIMESTAMP"` } // ReturnsEnabled is the one way to read Reverselogisticsenabled. // // nil means the client predates the field, and those clients were all using // returns — so nil is TRUE. Reading the pointer directly invites a nil deref // or, worse, treating "not stated" as "disabled" and silently withdrawing a // capability a client is already using. func (t Tenant) ReturnsEnabled() bool { return t.Reverselogisticsenabled == nil || *t.Reverselogisticsenabled } func (Tenant) TableName() string { return "tenants" } // Customer represents the pre-existing 'customers' table type Customer struct { Customerid int `json:"customerid" gorm:"primaryKey;column:customerid"` Firstname string `json:"firstname" gorm:"column:firstname"` Lastname string `json:"lastname" gorm:"column:lastname"` Contactno string `json:"contactno" gorm:"column:contactno"` Email string `json:"email" gorm:"column:email"` Status int `json:"status" gorm:"column:status"` Createdat time.Time `json:"createdat" gorm:"column:createdat"` Updatedat time.Time `json:"updatedat" gorm:"column:updatedat"` } func (Customer) TableName() string { return "customers" } // CustomerLocation represents the pre-existing 'customerlocations' table type CustomerLocation struct { Locationid int `json:"locationid" gorm:"primaryKey;column:locationid"` Customerid int `json:"customerid" gorm:"column:customerid"` Address string `json:"address" gorm:"column:address"` City string `json:"city" gorm:"column:city"` State string `json:"state" gorm:"column:state"` Postcode string `json:"postcode" gorm:"column:postcode"` Latitude string `json:"latitude" gorm:"column:latitude"` Longitude string `json:"longitude" gorm:"column:longitude"` Status int `json:"status" gorm:"column:status"` } func (CustomerLocation) TableName() string { return "customerlocations" } // AppLocation represents the pre-existing 'applocations' table type AppLocation struct { Applocationid int `json:"applocationid" gorm:"primaryKey;column:applocationid"` Applocationname string `json:"applocationname" gorm:"column:applocationname"` Status string `json:"status" gorm:"column:status"` } func (AppLocation) TableName() string { return "applocations" }