fix: per-site attribution needs its own column, not pickuplocationid

Caught by testing the previous commit against production: creating a booking
with a resolved site failed with

  pickupbookings_pickuplocationid_fkey
  FOREIGN KEY (pickuplocationid) REFERENCES appcustomerlocations(...)

pickuplocationid is the *customer's* saved address, a B2C concept. It never
referred to the client company's own kitchens or branches. The pre-existing
code that validated an incoming pickuplocationid against TenantLocation was
wrong on the same point and would have 500'd for any caller that used it — it
had simply never been called with a value.

Adds tenantlocationid to pickupbookings and consignments (nullable, indexed,
additive via AutoMigrate), carried across at pickup, and points the reporting
filter, the by_location breakdown and the Unattributed bucket at it.

The booking request accepts tenantlocationid, and still accepts
pickuplocationid as an alias so anything written against the earlier docs
starts working instead of failing.

Also gofmt on the two model files touched; booking.go was already failing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Suriya
2026-08-06 13:08:57 +05:30
parent 0c407e5b27
commit 90fa4fbb74
6 changed files with 108 additions and 70 deletions

View File

@@ -5,8 +5,8 @@ import (
)
type PickupBooking struct {
Bookingid int `json:"bookingid" gorm:"primaryKey;column:bookingid"`
Bookingno string `json:"bookingno" gorm:"column:bookingno;unique;not null"`
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
@@ -14,31 +14,39 @@ type PickupBooking struct {
// 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 *int `json:"pickuplocationid" gorm:"column:pickuplocationid"`
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"`
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"`
@@ -91,7 +99,7 @@ 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
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"`
@@ -129,8 +137,8 @@ type BookingVehicleRequirement struct {
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"`
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