updates on the ai agents and time series prediction and updates on the api to

This commit is contained in:
2026-10-09 13:50:30 +05:30
parent 29690c56f2
commit 153be40e5c
42 changed files with 4889 additions and 186 deletions

View File

@@ -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 }