updates on the ai agents and time series prediction and updates on the api to
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"doormile/constants"
|
||||
"doormile/db"
|
||||
"doormile/models"
|
||||
"doormile/utils"
|
||||
@@ -48,6 +49,10 @@ type onboardClientRequest struct {
|
||||
Password string `json:"password"`
|
||||
Applocationid int `json:"applocationid"`
|
||||
Requiredeliveryotp bool `json:"requiredeliveryotp"`
|
||||
// Deliverycategory is what the client ships. Required: it drives pricing
|
||||
// AND whether reverse logistics applies, and guessing it for them means
|
||||
// guessing whether their parcels can come back.
|
||||
Deliverycategory string `json:"deliverycategory"`
|
||||
// The client's main address (flat in the JSON). Saved as their primary
|
||||
// tenantlocations row, which is what a client login's zone list and the
|
||||
// order form's pickup "Business Hub" read. A client onboarded without one
|
||||
@@ -147,6 +152,17 @@ func (r *onboardClientRequest) validate() string {
|
||||
if r.Applocationid <= 0 {
|
||||
return "choose the client's operating city"
|
||||
}
|
||||
// Required, not defaulted. The category drives pricing AND whether this
|
||||
// client's parcels can be returned at all — defaulting it to General would
|
||||
// quietly give a food client a reverse-logistics path that makes no sense
|
||||
// for what they ship, and nobody would be asked.
|
||||
r.Deliverycategory = strings.TrimSpace(r.Deliverycategory)
|
||||
if r.Deliverycategory == "" {
|
||||
return "choose what this client delivers"
|
||||
}
|
||||
if !constants.DeliveryCategories[r.Deliverycategory] {
|
||||
return "that is not a delivery category we price for"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -225,6 +241,12 @@ func OnboardClient(c *fiber.Ctx) error {
|
||||
Primarycontact: req.Phone,
|
||||
Status: "Active",
|
||||
Requiredeliveryotp: req.Requiredeliveryotp,
|
||||
Deliverycategory: req.Deliverycategory,
|
||||
// Defaulted from the category, not asked for separately. The
|
||||
// operator answers the question they can answer ("what do they
|
||||
// ship?"); the consequence follows. It stays editable afterwards
|
||||
// for the cases the category cannot express.
|
||||
Reverselogisticsenabled: boolPtr(constants.ReverseLogisticsAllowed(req.Deliverycategory)),
|
||||
}
|
||||
if err := tx.Create(&tenant).Error; err != nil {
|
||||
return err
|
||||
@@ -316,17 +338,23 @@ func OnboardClient(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
type onboardedClient struct {
|
||||
Authid uint64 `json:"authid"`
|
||||
Tenantid int `json:"tenantid"`
|
||||
Tenantname string `json:"tenantname"`
|
||||
Primaryemail string `json:"primaryemail"`
|
||||
Primarycontact string `json:"primarycontact"`
|
||||
Status string `json:"status"`
|
||||
Requiredeliveryotp bool `json:"requiredeliveryotp"`
|
||||
Contactname string `json:"contactname"`
|
||||
Loginemail string `json:"loginemail"`
|
||||
Loginrole string `json:"loginrole"`
|
||||
Logincreatedat *time.Time `json:"logincreatedat"`
|
||||
Authid uint64 `json:"authid"`
|
||||
Tenantid int `json:"tenantid"`
|
||||
Tenantname string `json:"tenantname"`
|
||||
Primaryemail string `json:"primaryemail"`
|
||||
Primarycontact string `json:"primarycontact"`
|
||||
Status string `json:"status"`
|
||||
Requiredeliveryotp bool `json:"requiredeliveryotp"`
|
||||
// What the client ships, and whether their parcels can come back. The
|
||||
// console's edit dialog seeds its category picker from these — without
|
||||
// them it showed "Not recorded" for every client, including ones that
|
||||
// have a category.
|
||||
Deliverycategory string `json:"deliverycategory"`
|
||||
Reverselogisticsenabled bool `json:"reverselogisticsenabled"`
|
||||
Contactname string `json:"contactname"`
|
||||
Loginemail string `json:"loginemail"`
|
||||
Loginrole string `json:"loginrole"`
|
||||
Logincreatedat *time.Time `json:"logincreatedat"`
|
||||
// The client's main address (primary location); empty for a client
|
||||
// onboarded before addresses were collected.
|
||||
Address string `json:"address"`
|
||||
@@ -355,7 +383,10 @@ func GetOnboardedClients(c *fiber.Ctx) error {
|
||||
var rows []onboardedClientRow
|
||||
err := db.DB.Table("doormile_auth AS a").
|
||||
Select(`a.id AS authid, t.tenantid, t.tenantname, t.primaryemail, t.primarycontact, t.status,
|
||||
t.requiredeliveryotp, COALESCE(u.authname, '') AS contactname,
|
||||
t.requiredeliveryotp,
|
||||
COALESCE(t.deliverycategory, '') AS deliverycategory,
|
||||
COALESCE(t.reverselogisticsenabled, true) AS reverselogisticsenabled,
|
||||
COALESCE(u.authname, '') AS contactname,
|
||||
a.email AS loginemail, a.role AS loginrole,
|
||||
a.created_at AS authcreatedat, t.createdat AS tenantcreatedat,
|
||||
COALESCE(l.address, '') AS address, COALESCE(l.city, '') AS city, COALESCE(l.state, '') AS state,
|
||||
@@ -386,24 +417,28 @@ func GetOnboardedClients(c *fiber.Ctx) error {
|
||||
// unexported type, which once left every field but the dates empty (and every
|
||||
// authid 0). TestOnboardedClientRowMapsEveryColumn guards this.
|
||||
type onboardedClientRow struct {
|
||||
Authid uint64 `gorm:"column:authid"`
|
||||
Tenantid int `gorm:"column:tenantid"`
|
||||
Tenantname string `gorm:"column:tenantname"`
|
||||
Primaryemail string `gorm:"column:primaryemail"`
|
||||
Primarycontact string `gorm:"column:primarycontact"`
|
||||
Status string `gorm:"column:status"`
|
||||
Requiredeliveryotp bool `gorm:"column:requiredeliveryotp"`
|
||||
Contactname string `gorm:"column:contactname"`
|
||||
Loginemail string `gorm:"column:loginemail"`
|
||||
Loginrole string `gorm:"column:loginrole"`
|
||||
Authcreatedat *time.Time `gorm:"column:authcreatedat"`
|
||||
Tenantcreatedat *time.Time `gorm:"column:tenantcreatedat"`
|
||||
Address string `gorm:"column:address"`
|
||||
City string `gorm:"column:city"`
|
||||
State string `gorm:"column:state"`
|
||||
Pincode string `gorm:"column:pincode"`
|
||||
Latitude float64 `gorm:"column:latitude"`
|
||||
Longitude float64 `gorm:"column:longitude"`
|
||||
Authid uint64 `gorm:"column:authid"`
|
||||
Tenantid int `gorm:"column:tenantid"`
|
||||
Tenantname string `gorm:"column:tenantname"`
|
||||
Primaryemail string `gorm:"column:primaryemail"`
|
||||
Primarycontact string `gorm:"column:primarycontact"`
|
||||
Status string `gorm:"column:status"`
|
||||
Requiredeliveryotp bool `gorm:"column:requiredeliveryotp"`
|
||||
Deliverycategory string `gorm:"column:deliverycategory"`
|
||||
// Scanned as a plain bool from a COALESCE, so a client predating the
|
||||
// field reads as true — the same meaning Tenant.ReturnsEnabled() gives nil.
|
||||
Reverselogisticsenabled bool `gorm:"column:reverselogisticsenabled"`
|
||||
Contactname string `gorm:"column:contactname"`
|
||||
Loginemail string `gorm:"column:loginemail"`
|
||||
Loginrole string `gorm:"column:loginrole"`
|
||||
Authcreatedat *time.Time `gorm:"column:authcreatedat"`
|
||||
Tenantcreatedat *time.Time `gorm:"column:tenantcreatedat"`
|
||||
Address string `gorm:"column:address"`
|
||||
City string `gorm:"column:city"`
|
||||
State string `gorm:"column:state"`
|
||||
Pincode string `gorm:"column:pincode"`
|
||||
Latitude float64 `gorm:"column:latitude"`
|
||||
Longitude float64 `gorm:"column:longitude"`
|
||||
}
|
||||
|
||||
func (r onboardedClientRow) toClient() onboardedClient {
|
||||
@@ -419,8 +454,10 @@ func (r onboardedClientRow) toClient() onboardedClient {
|
||||
return onboardedClient{
|
||||
Authid: r.Authid, Tenantid: r.Tenantid, Tenantname: r.Tenantname,
|
||||
Primaryemail: r.Primaryemail, Primarycontact: r.Primarycontact, Status: r.Status,
|
||||
Requiredeliveryotp: r.Requiredeliveryotp, Contactname: r.Contactname,
|
||||
Loginemail: r.Loginemail, Loginrole: r.Loginrole, Logincreatedat: created,
|
||||
Requiredeliveryotp: r.Requiredeliveryotp,
|
||||
Deliverycategory: r.Deliverycategory, Reverselogisticsenabled: r.Reverselogisticsenabled,
|
||||
Contactname: r.Contactname,
|
||||
Loginemail: r.Loginemail, Loginrole: r.Loginrole, Logincreatedat: created,
|
||||
Address: r.Address, City: r.City, State: r.State, Pincode: r.Pincode,
|
||||
Latitude: r.Latitude, Longitude: r.Longitude,
|
||||
}
|
||||
@@ -443,7 +480,17 @@ type updateClientRequest struct {
|
||||
Phone *string `json:"phone"`
|
||||
Status *string `json:"status"`
|
||||
Requiredeliveryotp *bool `json:"requiredeliveryotp"`
|
||||
Password *string `json:"password"` // optional reset; empty = unchanged
|
||||
// Deliverycategory changes what the client ships. Omitted leaves it alone,
|
||||
// so an edit that only touches the phone number cannot blank it — and an
|
||||
// older client with no category recorded stays editable without being
|
||||
// forced to pick one mid-edit.
|
||||
Deliverycategory *string `json:"deliverycategory"`
|
||||
// Reverselogisticsenabled overrides what the category implies — a
|
||||
// clearance line that is final sale, or a caterer who takes back
|
||||
// equipment. Omitted keeps the stored value, EXCEPT when the category
|
||||
// changes, which re-derives it (see below).
|
||||
Reverselogisticsenabled *bool `json:"reverselogisticsenabled"`
|
||||
Password *string `json:"password"` // optional reset; empty = unchanged
|
||||
// Location replaces the client's main address (primary location), or
|
||||
// creates it for a client onboarded before addresses were collected.
|
||||
Location *clientAddress `json:"location"`
|
||||
@@ -495,6 +542,18 @@ func UpdateOnboardedClient(c *fiber.Ctx) error {
|
||||
// phone the caller is actually changing is validated.
|
||||
check.Phone = "9000000000"
|
||||
}
|
||||
// Only a category the caller is actually changing is validated — the same
|
||||
// rule the phone above follows. Seeding from the stored value keeps an
|
||||
// edit that does not mention the category working, including for clients
|
||||
// onboarded before the field existed (empty, which validate() would
|
||||
// otherwise refuse).
|
||||
if req.Deliverycategory != nil {
|
||||
check.Deliverycategory = strings.TrimSpace(*req.Deliverycategory)
|
||||
} else if tenant.Deliverycategory != "" {
|
||||
check.Deliverycategory = tenant.Deliverycategory
|
||||
} else {
|
||||
check.Deliverycategory = constants.CategoryDefault
|
||||
}
|
||||
newPassword := ""
|
||||
if req.Password != nil && *req.Password != "" {
|
||||
newPassword = *req.Password
|
||||
@@ -558,6 +617,21 @@ func UpdateOnboardedClient(c *fiber.Ctx) error {
|
||||
if req.Requiredeliveryotp != nil {
|
||||
tenantUpdates["requiredeliveryotp"] = *req.Requiredeliveryotp
|
||||
}
|
||||
// Changing WHAT a client ships re-derives whether their parcels can be
|
||||
// returned — otherwise switching a client to Food would leave returns
|
||||
// quietly enabled for a category where a returned parcel can only be
|
||||
// thrown away.
|
||||
//
|
||||
// An explicit reverselogisticsenabled in the same request still wins:
|
||||
// that is the override for the cases a category cannot express (a
|
||||
// final-sale clearance line, a caterer who takes back equipment).
|
||||
if req.Deliverycategory != nil && check.Deliverycategory != tenant.Deliverycategory {
|
||||
tenantUpdates["deliverycategory"] = check.Deliverycategory
|
||||
tenantUpdates["reverselogisticsenabled"] = constants.ReverseLogisticsAllowed(check.Deliverycategory)
|
||||
}
|
||||
if req.Reverselogisticsenabled != nil {
|
||||
tenantUpdates["reverselogisticsenabled"] = *req.Reverselogisticsenabled
|
||||
}
|
||||
if err := tx.Model(&models.Tenant{}).Where("tenantid = ?", tenant.Tenantid).Updates(tenantUpdates).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -714,3 +788,7 @@ func GetOnboardingCities(c *fiber.Ctx) error {
|
||||
}
|
||||
return utils.List(c, cities, int64(len(cities)))
|
||||
}
|
||||
|
||||
// boolPtr is for the Tenant.Reverselogisticsenabled pointer: a non-nil false
|
||||
// must reach the database, which a plain bool would not (see the field).
|
||||
func boolPtr(b bool) *bool { return &b }
|
||||
|
||||
Reference in New Issue
Block a user