Miler app P0 + contract gaps found in the live audit: - GET /miler/bookings now returns consignmentid + consignmentstatus on every row (nullable), so the app can call deliver/skip/start-delivery straight from the list. /miler/assignments is the active-only queue, so this is the authoritative fix for stops that have moved onto the delivery leg. - GET /miler/bookings now returns sequencedat per row: non-null means the console/optimizer fixed this stop's order and the app follows step exactly; null means no route assigned and the app may fall back to nearest-first. - Route sequencing (internal/routing) now runs automatically after every assignment — customer auto-assign, express auto-assign, manual assign, and accept — via SequenceMilerStopsAsync (fire-and-forget, no-op below two active stops). Previously only hub batch-assign sequenced, so most riders saw step=0. - GET /admin/bookings now surfaces the live consignmentstatus alongside the frozen booking status, so a Converted_To_Consignment booking can still show Out_for_Delivery / Delivered instead of a generic "Active". Two-step hyperlocal flow (Arrived_At_Pickup, Collected_By_Miler, start-delivery) stays gated behind MILER_COLLECTED_STATE_ENABLED (default off) until the app ships; consignmentid/status, GET /miler/consignments/:id, stable error codes and Idempotency-Key handling are unconditional and safe on the current app. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WRaFH5hMRqmUQvVPQsyjZD
173 lines
11 KiB
Go
173 lines
11 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"`
|
|
// Consignmentstatus is not a column — it is filled in by handlers that want
|
|
// the live consignment status alongside the booking (e.g. the admin bookings
|
|
// list, so a "Converted_To_Consignment" booking can still show
|
|
// Out_for_Delivery / Delivered). omitempty keeps it out of every other
|
|
// PickupBooking response that does not populate it.
|
|
Consignmentstatus string `json:"consignmentstatus,omitempty" gorm:"-"`
|
|
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"
|
|
}
|