Files
doormile_backend/models/booking.go

271 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"`
// Pickupsourcetype says what KIND of place this booking is collected from —
// one of constants.PickupSource*. It is stored on the booking row, not looked
// up from a location master, because a customer-door pickup has no location
// id at all: only the row can tell "no location because it is a front door"
// apart from "no location because nobody filled it in". Empty on rows written
// before this column existed; derivePickupSourceType classifies those from
// what they do carry, so the API never returns a blank type.
Pickupsourcetype string `json:"pickup_source_type" gorm:"column:pickupsourcetype;size:20"`
// Pickuphubid is set only when Pickupsourcetype is "hub" — the base the
// parcel is collected FROM (Base → Customer). It is a separate column from
// Nearesthubid, which is the base a parcel is routed TO. Conflating them
// would make a base-origin booking look like a base-destination one.
Pickuphubid *int `json:"pickuphubid" gorm:"column:pickuphubid;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"`
// Arrival trace: written by the reached action when the rider is at the
// pickup point. Nullable — a booking that was never reached has no arrival.
// arrivallatitude/longitude are the rider's actual GPS at the moment they
// tapped "arrived", kept as a durable record separate from live telemetry.
// JSON name is reachedat (not arrivedat): the miler app, the /reached
// response and the Admin Console all key off reachedat — the console derives
// its "Arrived" display from Pickup_Scheduled + reachedat != null. The DB
// column stays arrivedat.
Arrivedat *time.Time `json:"reachedat" gorm:"column:arrivedat"`
Arrivallatitude *float64 `json:"arrivallatitude" gorm:"column:arrivallatitude"`
Arrivallongitude *float64 `json:"arrivallongitude" gorm:"column:arrivallongitude"`
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:"-"`
// Trackingno is not a column either — it lives on the consignment, which only
// exists once the parcel is collected. It is filled in by handlers that serve
// a customer, because without it the customer app has no way to reach
// GET /customer/track/:trackingno at all: a booking is addressed by id, a
// shipment by tracking number, and nothing joined the two.
Trackingno string `json:"trackingno,omitempty" gorm:"-"`
// Destinationcount and Totalpackagecount are not columns either. A
// customer-app pickup is ONE booking carrying N destinations, and the admin
// list has to be able to say "3 destinations · 4 packages" without shipping
// the whole destination array on every row — the console drains up to 12
// pages of 100 bookings and does not use the array in the list. Filled in by
// the admin bookings list from one grouped query per page (see
// applyDestinationCounts). Deliberately NOT omitempty: a console-created
// booking has no bookingdestinations rows at all and must report 0, which
// the console reads as "no destinations recorded" — omitting the field would
// make it indistinguishable from a single-destination booking.
Destinationcount int `json:"destinationcount" gorm:"-"`
Totalpackagecount int `json:"totalpackagecount" gorm:"-"`
Createdat time.Time `json:"createdat" gorm:"column:createdat;default:CURRENT_TIMESTAMP"`
Updatedat time.Time `json:"updatedat" gorm:"column:updatedat;default:CURRENT_TIMESTAMP"`
// ── Customer-app (doormile_cx) columns ──────────────────────────────────
// All additive and nullable/zero-valued, so every existing row and every
// console-created booking stays valid without a backfill.
// Slotid is the pickup window the customer chose, as the opaque id the app
// sent back ("slot_20260905_t1"). Preferredpickupfrom/to carry the same
// window as real times for the assignment engine; this keeps the customer's
// own choice recorded verbatim, so a slot definition edited later cannot
// silently rewrite what they picked.
Slotid string `json:"slotid" gorm:"column:slotid;size:40"`
// Customerstage is the operational stage the customer app renders, one of
// the nine keys in constants.CxStage*. Distinct from Status, which is the
// booking lifecycle the console and the miler app read: Status stops moving
// at Converted_To_Consignment while the parcels keep going, and the two
// vocabularies are not a renaming of each other. Derived from the same
// writes, stored so a read is one row rather than a replay of the history.
Customerstage string `json:"customerstage" gorm:"column:customerstage;size:24"`
// Customerstatus is active / completed / cancelled. Derived, but sent
// explicitly — making the client infer it from a stage is how two surfaces
// end up disagreeing about whether an order is finished.
Customerstatus string `json:"customerstatus" gorm:"column:customerstatus;size:16"`
// The estimate range the customer was actually shown at booking time, in
// whole rupees. Kept forever, including after settlement: the receipt
// renders amountPaid − estimate min as the weight adjustment, and a price
// dispute needs the number that was on screen, not a re-run of today's
// pricing rules.
Estimateminrupees int `json:"estimateminrupees" gorm:"column:estimateminrupees;default:0"`
Estimatemaxrupees int `json:"estimatemaxrupees" gorm:"column:estimatemaxrupees;default:0"`
// Routekm is the pickup→destination distance the estimate was priced on;
// it drives the route outline and the receipt.
Routekm float64 `json:"routekm" gorm:"column:routekm;default:0"`
// Pickuptitle/Pickupsub are the two-line pickup label the customer picked
// from the place search ("12 Nehru Street" / "Gandhipuram, Coimbatore
// 641012"). Pickupaddress remains the single flat string the rest of the
// system uses; these keep the split the app renders without it having to
// re-parse one back into two.
Pickuptitle string `json:"pickuptitle" gorm:"column:pickuptitle;size:64"`
Pickupsub string `json:"pickupsub" gorm:"column:pickupsub"`
// Cancelreason is free text or one of the app's five presets. Nullable in
// spirit — an empty string means the customer gave no reason, which is
// allowed.
Cancelreason string `json:"cancelreason" gorm:"column:cancelreason;size:120"`
// Relations
Parcels []BookingParcel `json:"parcels" gorm:"foreignKey:Bookingid"`
ServiceOptions []BookingServiceOption `json:"serviceoptions" gorm:"foreignKey:Bookingid"`
Payments []BookingPayment `json:"payments" gorm:"foreignKey:Bookingid"`
Destinations []BookingDestination `json:"destinations" 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"`
// Bookingdestinationid says which destination this package is going to.
// Null on console-created and pre-existing bookings, which have exactly one
// delivery address and therefore no ambiguity. On a customer-app booking it
// is what lets the miler weigh three packages for Chennai and one for
// Kochi and have each weight settle against the right order — without it,
// a multi-destination pickup has one pile of parcels and no way to say
// which parcel belongs to which tracking number.
Bookingdestinationid *int `json:"bookingdestinationid" gorm:"column:bookingdestinationid;index"`
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"
}