updates on the api endpoints on the customer page and more
This commit is contained in:
@@ -2142,7 +2142,23 @@ type AdminBookingRequest struct {
|
||||
// callers written against the earlier docs. It is never stored as-is: the
|
||||
// column of that name foreign-keys to appcustomerlocations, not to a
|
||||
// client's sites.
|
||||
Pickuplocationid *int `json:"pickuplocationid"`
|
||||
Pickuplocationid *int `json:"pickuplocationid"`
|
||||
// PickupSourceType says what kind of place this booking is collected from —
|
||||
// one of constants.PickupSource*. Optional: left blank it is classified from
|
||||
// what the payload carries (a base id, a client site id, or neither), so
|
||||
// existing console callers keep working unchanged. Send it explicitly to
|
||||
// create a Base/Hub-origin booking.
|
||||
PickupSourceType string `json:"pickup_source_type"`
|
||||
// Pickuphubid names the base a Base → Customer booking is collected FROM.
|
||||
// Required when pickup_source_type is "hub"; supplying it is also enough on
|
||||
// its own, since a booking that names a base is a base-origin booking. The
|
||||
// base's own address, pincode and coordinates fill in whatever the caller
|
||||
// left blank, so the dispatch board never has to retype a gate address.
|
||||
Pickuphubid *int `json:"pickuphubid"`
|
||||
// Sourceid is accepted as an alias for whichever id the source type implies —
|
||||
// the app and the console have both used this spelling. With
|
||||
// pickup_source_type "hub" it is a base id; otherwise a client-site id.
|
||||
Sourceid *int `json:"sourceid"`
|
||||
Pickupaddress string `json:"pickupaddress"`
|
||||
Pickuppincode string `json:"pickuppincode"`
|
||||
Pickuplatitude float64 `json:"pickuplatitude"`
|
||||
@@ -2202,7 +2218,38 @@ func createExpressBooking(req AdminBookingRequest, autoAssign bool) (*models.Pic
|
||||
// against the earlier documentation. It is the wrong column: it foreign-keys
|
||||
// to appcustomerlocations, so a tenantlocations id in it fails the insert.
|
||||
// Both names resolve to Tenantlocationid.
|
||||
// A base-origin pickup (Base/Hub → Customer). The base is the actual place the
|
||||
// rider collects from, so its address and coordinates become the booking's
|
||||
// pickup point and the row records both the type and the base id — that pair
|
||||
// is what the rider app reads to title the stop as a Base rather than as the
|
||||
// rider's own office, and what the dispatch board reads back on the row.
|
||||
baseID := req.Pickuphubid
|
||||
if baseID == nil && strings.EqualFold(req.PickupSourceType, constants.PickupSourceHub) {
|
||||
baseID = req.Sourceid
|
||||
}
|
||||
if baseID != nil {
|
||||
var hub models.Hub
|
||||
if err := db.DB.Where("hubid = ? AND deletedat IS NULL", *baseID).First(&hub).Error; err != nil {
|
||||
return nil, &expressBookingValidationError{"pickuphubid does not match a known base"}
|
||||
}
|
||||
req.PickupSourceType = constants.PickupSourceHub
|
||||
req.Pickuphubid = &hub.Hubid
|
||||
if req.Pickupaddress == "" {
|
||||
req.Pickupaddress = hub.Address
|
||||
}
|
||||
if req.Pickuppincode == "" {
|
||||
req.Pickuppincode = hub.Pincode
|
||||
}
|
||||
if req.Pickuplatitude == 0 && req.Pickuplongitude == 0 {
|
||||
req.Pickuplatitude, req.Pickuplongitude = hub.Latitude, hub.Longitude
|
||||
}
|
||||
}
|
||||
|
||||
siteID := req.Tenantlocationid
|
||||
// sourceid doubles as the client-site id when the source is not a base.
|
||||
if siteID == nil && baseID == nil {
|
||||
siteID = req.Sourceid
|
||||
}
|
||||
if siteID == nil {
|
||||
siteID = req.Pickuplocationid
|
||||
}
|
||||
@@ -2238,10 +2285,34 @@ func createExpressBooking(req AdminBookingRequest, autoAssign bool) (*models.Pic
|
||||
// pickup actually is. Without this the field stays null — as it did on every
|
||||
// booking in the system — and per-site reporting has nothing to group by,
|
||||
// because the console sends a kitchen's address rather than its id.
|
||||
if req.Tenantlocationid == nil {
|
||||
if req.Tenantlocationid == nil && req.Pickuphubid == nil {
|
||||
req.Tenantlocationid = matchTenantLocation(req.Tenantid, req.Pickupaddress, req.Pickuplatitude, req.Pickuplongitude)
|
||||
}
|
||||
|
||||
// Classify the source once, here, rather than leaving every reader to guess.
|
||||
// A caller-supplied type wins as long as it is one we know; an unknown word is
|
||||
// dropped rather than stored, so the column never holds something the app has
|
||||
// no meaning for. A door pickup is recorded as "customer" explicitly — the
|
||||
// whole point of the column is that a blank cannot be told apart from an
|
||||
// address nobody filled in.
|
||||
switch {
|
||||
case req.Pickuphubid != nil:
|
||||
req.PickupSourceType = constants.PickupSourceHub
|
||||
case strings.EqualFold(req.PickupSourceType, constants.PickupSourceStore):
|
||||
req.PickupSourceType = constants.PickupSourceStore
|
||||
case strings.EqualFold(req.PickupSourceType, constants.PickupSourceCustomer):
|
||||
req.PickupSourceType = constants.PickupSourceCustomer
|
||||
case strings.EqualFold(req.PickupSourceType, constants.PickupSourceMerchant):
|
||||
// Honoured even with no site id attached. A merchant collection with no
|
||||
// configured location is still a shop, and telling the rider "customer
|
||||
// door" would send them looking for a person who is not there.
|
||||
req.PickupSourceType = constants.PickupSourceMerchant
|
||||
case req.Tenantlocationid != nil:
|
||||
req.PickupSourceType = constants.PickupSourceMerchant
|
||||
default:
|
||||
req.PickupSourceType = constants.PickupSourceCustomer
|
||||
}
|
||||
|
||||
tx := db.DB.Begin()
|
||||
|
||||
customerID := req.Appcustomerid
|
||||
@@ -2276,6 +2347,8 @@ func createExpressBooking(req AdminBookingRequest, autoAssign bool) (*models.Pic
|
||||
Appcustomerid: customerID,
|
||||
Pickuplocationid: req.Pickuplocationid,
|
||||
Tenantlocationid: req.Tenantlocationid,
|
||||
Pickupsourcetype: req.PickupSourceType,
|
||||
Pickuphubid: req.Pickuphubid,
|
||||
Pickupaddress: req.Pickupaddress,
|
||||
Pickuppincode: req.Pickuppincode,
|
||||
Pickuplatitude: req.Pickuplatitude,
|
||||
@@ -2525,7 +2598,65 @@ func GetAdminBookingDetails(c *fiber.Ctx) error {
|
||||
if err := q.First(&booking, id).Error; err != nil {
|
||||
return utils.NotFound(c, "booking not found")
|
||||
}
|
||||
return utils.OK(c, booking)
|
||||
|
||||
// The routing decision and the inputs it was made from, so a support call
|
||||
// about "why does this say handover instead of delivery" is a lookup rather
|
||||
// than a reconstruction. Everything here is derived from stored state — no
|
||||
// new columns, and it stays right if the routing rule changes, because it
|
||||
// reads the same helpers the pivot does.
|
||||
var customer models.AppCustomer
|
||||
db.DB.Where("appcustomerid = ?", booking.Appcustomerid).First(&customer)
|
||||
sourceType, sourceID, sourceName, sourceAddress := pickupSource(&booking,
|
||||
customer.Firstname+" "+customer.Lastname)
|
||||
|
||||
routing := fiber.Map{
|
||||
"pickup_source_type": sourceType,
|
||||
"pickup_source_id": sourceID,
|
||||
"pickup_source_name": sourceName,
|
||||
"from_address": sourceAddress,
|
||||
"from_pincode": booking.Pickuppincode,
|
||||
"to_address": booking.Deliveryaddress,
|
||||
"destination_pincode": booking.Deliverypincode,
|
||||
// hyperlocal: same postal area, so no base leg — the collecting rider
|
||||
// carries it to the receiver. Otherwise it goes through a base. This is the
|
||||
// decision pickup-complete makes, shown with the inputs it makes it from.
|
||||
"is_hyperlocal": isHyperlocalBooking(booking.Pickuppincode, booking.Deliverypincode,
|
||||
booking.Pickuplatitude, booking.Pickuplongitude,
|
||||
booking.Deliverylatitude, booking.Deliverylongitude),
|
||||
}
|
||||
|
||||
// Before pickup the decision has not been taken yet, so the routing result is
|
||||
// a projection; after pickup it is fact, read off the consignment.
|
||||
if booking.Consignmentid != nil {
|
||||
var cn models.Consignment
|
||||
if db.DB.First(&cn, *booking.Consignmentid).Error == nil {
|
||||
booking.Consignmentstatus = cn.Status
|
||||
routing["consignment_state"] = cn.Status
|
||||
routing["next_action"] = nextActionForConsignment(cn.Status)
|
||||
routing["next_hub"] = renderBase(loadHub(cn.Currenthubid))
|
||||
routing["inwardedat"] = cn.Inwardedat
|
||||
routing["decided"] = true
|
||||
}
|
||||
} else {
|
||||
routing["consignment_state"] = ""
|
||||
routing["next_action"] = constants.NextActionPickup
|
||||
routing["next_hub"] = nil
|
||||
routing["decided"] = false
|
||||
}
|
||||
|
||||
// routing rides alongside the booking's own fields rather than nesting them
|
||||
// under a new key — the console reads this response as a booking object today,
|
||||
// and moving those fields would break every screen that does.
|
||||
raw, err := json.Marshal(booking)
|
||||
if err != nil {
|
||||
return utils.OK(c, booking)
|
||||
}
|
||||
payload := map[string]interface{}{}
|
||||
if err := json.Unmarshal(raw, &payload); err != nil {
|
||||
return utils.OK(c, booking)
|
||||
}
|
||||
payload["routing"] = routing
|
||||
return utils.OK(c, payload)
|
||||
}
|
||||
|
||||
func AdminAssignMiler(c *fiber.Ctx) error {
|
||||
|
||||
Reference in New Issue
Block a user