Assignment decided who carried a booking but never what order to run several of them in -- the one capability jupiter had that Doormile did not. It turns out we already own the solver: routes.workolik.com is a live in-house Route Optimization API backed by Valhalla road-network routing, not the paid third party I had assumed. This is a client for it, not a solver. internal/routing posts a rider's active stops and writes back step, previouskms, cumulativekms and ETA onto BookingAssignment. HubBatchAssign calls it after committing a batch, which is exactly the case it exists for: a rider used to walk away with several bookings and no order to run them in. GetMilerAssignments now returns sequenced stops in step order, falling back to newest-first for anything unsequenced. Contract discovered by probing the live service -- the OpenAPI schema types the body as a bare object array, so the field names are not documented anywhere. They are pickuplat/pickuplong/deliverylat/ deliverylong, NOT pickuplatitude/deliverylatitude. Sending the wrong names does not fail: it returns HTTP 200 with every coordinate defaulted to "0.0", no reordering and all distances zero. That trap is recorded in a comment so the next person does not lose an afternoon to it. Numeric fields come back inconsistently typed -- previouskms as a number, actualkms and eta as strings, some decimal -- so they are decoded loosely and coerced, with tests pinning the coercion. Steps for deliveryids we did not send are discarded rather than written, so an echoed or stale id cannot reorder another rider's work. Sequencing is best-effort throughout and runs after assignments commit. The optimizer is a separate service over the network; it being down must leave bookings assigned but unordered, never undo the batch. Step 0 means "not sequenced", not "first". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
167 lines
10 KiB
Go
167 lines
10 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
)
|
|
|
|
type PickupBooking struct {
|
|
Bookingid int `json:"bookingid" gorm:"primaryKey;column:bookingid"`
|
|
Bookingno string `json:"bookingno" gorm:"column:bookingno;unique;not null"`
|
|
// Tenantid identifies which client company this booking is for. Nil for
|
|
// direct B2C bookings (Bookingsource "Customer_App") that aren't attributed
|
|
// to a tenant yet — see CreateCustomerBooking. Required for CRM bookings
|
|
// (Bookingsource "CRM_Console"), since those are always made on behalf of
|
|
// a specific tenant. Propagated onto the resulting Consignment at pickup
|
|
// time in BookingPickupComplete, instead of inferring it from whichever
|
|
// miler happens to complete the pickup.
|
|
Tenantid *int `json:"tenantid" gorm:"column:tenantid;index"`
|
|
Appcustomerid int `json:"appcustomerid" gorm:"column:appcustomerid"`
|
|
// Pickuplocationid is the *customer's* saved address the parcel was collected
|
|
// from — it carries a foreign key to appcustomerlocations. It is a B2C
|
|
// concept and has nothing to do with the client company's own sites.
|
|
Pickuplocationid *int `json:"pickuplocationid" gorm:"column:pickuplocationid"`
|
|
// Tenantlocationid is the *client's* site: the kitchen, branch or depot the
|
|
// parcel came out of. Separate column because pickuplocationid points at a
|
|
// different table entirely; writing a tenantlocations id into it violates
|
|
// that foreign key. This is what per-site reporting groups by.
|
|
Tenantlocationid *int `json:"tenantlocationid" gorm:"column:tenantlocationid;index"`
|
|
Pickupaddress string `json:"pickupaddress" gorm:"column:pickupaddress;not null"`
|
|
Pickuppincode string `json:"pickuppincode" gorm:"column:pickuppincode;not null"`
|
|
Pickuplatitude float64 `json:"pickuplatitude" gorm:"column:pickuplatitude;not null"`
|
|
Pickuplongitude float64 `json:"pickuplongitude" gorm:"column:pickuplongitude;not null"`
|
|
Deliveryaddress string `json:"deliveryaddress" gorm:"column:deliveryaddress;not null"`
|
|
Deliverypincode string `json:"deliverypincode" gorm:"column:deliverypincode;not null"`
|
|
Deliverylatitude float64 `json:"deliverylatitude" gorm:"column:deliverylatitude;not null"`
|
|
Deliverylongitude float64 `json:"deliverylongitude" gorm:"column:deliverylongitude;not null"`
|
|
Deliverycity string `json:"deliverycity" gorm:"column:deliverycity"`
|
|
Nearesthubid *int `json:"nearesthubid" gorm:"column:nearesthubid"`
|
|
Bookingsource string `json:"bookingsource" gorm:"column:bookingsource;default:Customer_App"`
|
|
Providercompany string `json:"providercompany" gorm:"column:providercompany"`
|
|
Providerlocation string `json:"providerlocation" gorm:"column:providerlocation"`
|
|
Notes string `json:"notes" gorm:"column:notes"`
|
|
Status string `json:"status" gorm:"column:status;default:Created"` // Created, Miler_Assigned, Pickup_Scheduled, Picked_Up, Converted_To_Consignment, Cancelled
|
|
Preferredpickupfrom *time.Time `json:"preferredpickupfrom" gorm:"column:preferredpickupfrom"`
|
|
Preferredpickupto *time.Time `json:"preferredpickupto" gorm:"column:preferredpickupto"`
|
|
Assignedmileruserid *int `json:"assignedmileruserid" gorm:"column:assignedmileruserid"`
|
|
Consignmentid *int `json:"consignmentid" gorm:"column:consignmentid"`
|
|
Createdat time.Time `json:"createdat" gorm:"column:createdat;default:CURRENT_TIMESTAMP"`
|
|
Updatedat time.Time `json:"updatedat" gorm:"column:updatedat;default:CURRENT_TIMESTAMP"`
|
|
|
|
// Relations
|
|
Parcels []BookingParcel `json:"parcels" gorm:"foreignKey:Bookingid"`
|
|
ServiceOptions []BookingServiceOption `json:"serviceoptions" gorm:"foreignKey:Bookingid"`
|
|
Payments []BookingPayment `json:"payments" gorm:"foreignKey:Bookingid"`
|
|
}
|
|
|
|
func (PickupBooking) TableName() string {
|
|
return "pickupbookings"
|
|
}
|
|
|
|
type BookingParcel struct {
|
|
Bookingparcelid int `json:"bookingparcelid" gorm:"primaryKey;column:bookingparcelid"`
|
|
Bookingid int `json:"bookingid" gorm:"column:bookingid"`
|
|
Itemcategory string `json:"itemcategory" gorm:"column:itemcategory"`
|
|
Itemdescription string `json:"itemdescription" gorm:"column:itemdescription"`
|
|
Declaredvalue float64 `json:"declaredvalue" gorm:"column:declaredvalue"`
|
|
Weight float64 `json:"weight" gorm:"column:weight"`
|
|
Length float64 `json:"length" gorm:"column:length"`
|
|
Width float64 `json:"width" gorm:"column:width"`
|
|
Height float64 `json:"height" gorm:"column:height"`
|
|
Isfragile bool `json:"isfragile" gorm:"column:isfragile;default:false"`
|
|
Needsinsurance bool `json:"needsinsurance" gorm:"column:needsinsurance;default:false"`
|
|
Insuranceamount float64 `json:"insuranceamount" gorm:"column:insuranceamount;default:0.00"`
|
|
Requireslargevehicle bool `json:"requireslargevehicle" gorm:"column:requireslargevehicle;default:false"`
|
|
Suggestedvehicletype string `json:"suggestedvehicletype" gorm:"column:suggestedvehicletype"`
|
|
Createdat time.Time `json:"createdat" gorm:"column:createdat;default:CURRENT_TIMESTAMP"`
|
|
Updatedat time.Time `json:"updatedat" gorm:"column:updatedat;default:CURRENT_TIMESTAMP"`
|
|
}
|
|
|
|
func (BookingParcel) TableName() string {
|
|
return "bookingparcels"
|
|
}
|
|
|
|
type BookingServiceOption struct {
|
|
Bookingserviceid int `json:"bookingserviceid" gorm:"primaryKey;column:bookingserviceid"`
|
|
Bookingid int `json:"bookingid" gorm:"column:bookingid"`
|
|
Servicetype string `json:"servicetype" gorm:"column:servicetype"` // Normal, Fast, Superfast
|
|
Estimatedprice float64 `json:"estimatedprice" gorm:"column:estimatedprice"`
|
|
Estimateddeliveryat *time.Time `json:"estimateddeliveryat" gorm:"column:estimateddeliveryat"`
|
|
Sladueat *time.Time `json:"sladueat" gorm:"column:sladueat"`
|
|
Pricingid *int `json:"pricingid" gorm:"column:pricingid"`
|
|
Createdat time.Time `json:"createdat" gorm:"column:createdat;default:CURRENT_TIMESTAMP"`
|
|
}
|
|
|
|
func (BookingServiceOption) TableName() string {
|
|
return "bookingserviceoptions"
|
|
}
|
|
|
|
type BookingPayment struct {
|
|
Bookingpaymentid int `json:"bookingpaymentid" gorm:"primaryKey;column:bookingpaymentid"`
|
|
Bookingid int `json:"bookingid" gorm:"column:bookingid"`
|
|
Amount float64 `json:"amount" gorm:"column:amount;not null"`
|
|
Paymentmode string `json:"paymentmode" gorm:"column:paymentmode"` // Cash, UPI, Card, Wallet
|
|
Paymentstatus string `json:"paymentstatus" gorm:"column:paymentstatus;default:Pending"` // Pending, Paid, Failed, Refunded
|
|
Collectedbyuserid *int `json:"collectedbyuserid" gorm:"column:collectedbyuserid"`
|
|
Transactionref string `json:"transactionref" gorm:"column:transactionref"`
|
|
Paidat *time.Time `json:"paidat" gorm:"column:paidat"`
|
|
Createdat time.Time `json:"createdat" gorm:"column:createdat;default:CURRENT_TIMESTAMP"`
|
|
}
|
|
|
|
func (BookingPayment) TableName() string {
|
|
return "bookingpayments"
|
|
}
|
|
|
|
type BookingAssignment struct {
|
|
Bookingassignmentid int `json:"bookingassignmentid" gorm:"primaryKey;column:bookingassignmentid"`
|
|
Bookingid int `json:"bookingid" gorm:"column:bookingid"`
|
|
Mileruserid int `json:"mileruserid" gorm:"column:mileruserid"`
|
|
Assignedbyuserid *int `json:"assignedbyuserid" gorm:"column:assignedbyuserid"`
|
|
Assignmentstatus string `json:"assignmentstatus" gorm:"column:assignmentstatus;default:Assigned"` // Assigned, Accepted, Rejected, Reassigned, Completed, Cancelled
|
|
Assignedat time.Time `json:"assignedat" gorm:"column:assignedat;default:CURRENT_TIMESTAMP"`
|
|
Acceptedat *time.Time `json:"acceptedat" gorm:"column:acceptedat"`
|
|
Completedat *time.Time `json:"completedat" gorm:"column:completedat"`
|
|
Remarks string `json:"remarks" gorm:"column:remarks"`
|
|
AgentDecisionID *uint64 `json:"agent_decision_id,omitempty" gorm:"column:agent_decision_id"`
|
|
Riderkms float64 `json:"riderkms" gorm:"column:riderkms;default:0"`
|
|
Ridercharges float64 `json:"ridercharges" gorm:"column:ridercharges;default:0"`
|
|
Bonuspoints int `json:"bonuspoints" gorm:"column:bonuspoints;default:0"`
|
|
|
|
// Stop sequencing, written by internal/routing from the Route Optimization
|
|
// API's road-network ordering. Step is 1..N across a rider's currently
|
|
// active assignments — it says what order to run them in, which nothing in
|
|
// Doormile decided before: assignment picked *who*, never *in what order*.
|
|
//
|
|
// Step 0 means "not sequenced yet", not "first". A rider with a single stop
|
|
// is never sequenced, and sequencing is best-effort, so 0 is common and must
|
|
// not be read as a position.
|
|
Step int `json:"step" gorm:"column:step;default:0"`
|
|
Previouskms float64 `json:"previouskms" gorm:"column:previouskms;default:0"`
|
|
Cumulativekms float64 `json:"cumulativekms" gorm:"column:cumulativekms;default:0"`
|
|
Etaminutes int `json:"etaminutes" gorm:"column:etaminutes;default:0"`
|
|
Cumulativeeta int `json:"cumulativeeta" gorm:"column:cumulativeeta;default:0"`
|
|
Sequencedat *time.Time `json:"sequencedat" gorm:"column:sequencedat"`
|
|
}
|
|
|
|
func (BookingAssignment) TableName() string {
|
|
return "bookingassignments"
|
|
}
|
|
|
|
type BookingVehicleRequirement struct {
|
|
Requirementid int `json:"requirementid" gorm:"primaryKey;column:requirementid"`
|
|
Bookingid int `json:"bookingid" gorm:"column:bookingid"`
|
|
Requiredvehicletype string `json:"requiredvehicletype" gorm:"column:requiredvehicletype;not null"`
|
|
Reason string `json:"reason" gorm:"column:reason"`
|
|
Nearesthubid *int `json:"nearesthubid" gorm:"column:nearesthubid"`
|
|
Scheduledpickupfrom *time.Time `json:"scheduledpickupfrom" gorm:"column:scheduledpickupfrom"`
|
|
Scheduledpickupto *time.Time `json:"scheduledpickupto" gorm:"column:scheduledpickupto"`
|
|
Assignedvehicleid *int `json:"assignedvehicleid" gorm:"column:assignedvehicleid"`
|
|
Assigneddriveruserid *int `json:"assigneddriveruserid" gorm:"column:assigneddriveruserid"`
|
|
Status string `json:"status" gorm:"column:status;default:Required"` // Required, Scheduled, Assigned, Arrived, Picked_Up, Cancelled
|
|
Createdat time.Time `json:"createdat" gorm:"column:createdat;default:CURRENT_TIMESTAMP"`
|
|
Updatedat time.Time `json:"updatedat" gorm:"column:updatedat;default:CURRENT_TIMESTAMP"`
|
|
}
|
|
|
|
func (BookingVehicleRequirement) TableName() string {
|
|
return "bookingvehiclerequirements"
|
|
}
|