117 lines
5.5 KiB
Go
117 lines
5.5 KiB
Go
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"
|
|
}
|