271 lines
18 KiB
Go
271 lines
18 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"`
|
||
// 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"
|
||
}
|