updates on the api endpoints on the customer page and more
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"doormile/db"
|
||||
"doormile/dto"
|
||||
"doormile/internal/assignment"
|
||||
"doormile/internal/legs"
|
||||
"doormile/internal/notify"
|
||||
"doormile/internal/routing"
|
||||
"doormile/models"
|
||||
@@ -957,10 +958,7 @@ func BookingPaymentCollect(c *fiber.Ctx) error {
|
||||
// final-mile delivery. Pincodes shorter than 3 characters are treated as
|
||||
// unknown rather than matching, so bad data falls back to the safe hub route.
|
||||
func isHyperlocal(pickupPincode, deliveryPincode string) bool {
|
||||
if len(pickupPincode) < 3 || len(deliveryPincode) < 3 {
|
||||
return false
|
||||
}
|
||||
return pickupPincode[:3] == deliveryPincode[:3]
|
||||
return legs.SamePostalArea(pickupPincode, deliveryPincode)
|
||||
}
|
||||
|
||||
// maxHyperlocalKM bounds the straight-line pickup→delivery distance under which
|
||||
@@ -969,7 +967,7 @@ func isHyperlocal(pickupPincode, deliveryPincode string) bool {
|
||||
// created kitchen→customer bookings (e.g. DailyGrubs) frequently carry accurate
|
||||
// coordinates but no delivery pincode, and must not be wrongly routed through a
|
||||
// hub. When both pincodes are present the prefix rule still wins.
|
||||
const maxHyperlocalKM = 30.0
|
||||
const maxHyperlocalKM = legs.MaxHyperlocalKM
|
||||
|
||||
// isHyperlocalBooking decides whether a booking can skip the hub and be carried
|
||||
// straight to the final mile. It prefers the pincode-prefix rule (isHyperlocal)
|
||||
@@ -977,18 +975,7 @@ const maxHyperlocalKM = 30.0
|
||||
// between the pickup and delivery coordinates when a pincode is missing — so a
|
||||
// same-area booking whose address carried no pincode isn't sent to a hub.
|
||||
func isHyperlocalBooking(pickupPincode, deliveryPincode string, pLat, pLng, dLat, dLng float64) bool {
|
||||
// Both pincodes present: the prefix rule decides definitively (a matching
|
||||
// pincode is hyperlocal, a differing one is genuinely inter-area — don't let
|
||||
// distance override that).
|
||||
if len(pickupPincode) >= 3 && len(deliveryPincode) >= 3 {
|
||||
return isHyperlocal(pickupPincode, deliveryPincode)
|
||||
}
|
||||
// A pincode is missing: fall back to straight-line distance when we have both
|
||||
// coordinates.
|
||||
if pLat != 0 && pLng != 0 && dLat != 0 && dLng != 0 {
|
||||
return haversineKM(pLat, pLng, dLat, dLng) <= maxHyperlocalKM
|
||||
}
|
||||
return false
|
||||
return legs.IsHyperlocal(pickupPincode, deliveryPincode, pLat, pLng, dLat, dLng)
|
||||
}
|
||||
|
||||
// collectedStateEnabled gates the two-step hyperlocal delivery flow
|
||||
@@ -1068,15 +1055,29 @@ func BookingPickupComplete(c *fiber.Ctx) error {
|
||||
|
||||
trackingNo := generateTrackingNo()
|
||||
|
||||
// The base this parcel belongs to. Backend decides — the app is told where to
|
||||
// go and never picks a base itself. resolveHandoverHub prefers the base the
|
||||
// booking was routed to, then the collecting rider's own base (the previous
|
||||
// behaviour, and still the operational default), then the nearest active base
|
||||
// to the pickup point — that last step replaces a fallback that took whichever
|
||||
// hub row happened to come back first.
|
||||
handoverHub := resolveHandoverHub(&booking, profile.Hubid)
|
||||
var defaultHubID *int
|
||||
if profile.Hubid != nil {
|
||||
defaultHubID = profile.Hubid
|
||||
if handoverHub != nil {
|
||||
defaultHubID = &handoverHub.Hubid
|
||||
} else {
|
||||
utils.Warn("BookingPickupComplete: miler has no assigned hub, falling back to first hub row", "miler_user_id", milerUserID, "booking_id", bookingID)
|
||||
var hub models.Hub
|
||||
if tx.First(&hub).Error == nil {
|
||||
defaultHubID = &hub.Hubid
|
||||
}
|
||||
utils.Warn("BookingPickupComplete: no base could be resolved for this pickup", "miler_user_id", milerUserID, "booking_id", bookingID)
|
||||
}
|
||||
|
||||
// A hub-routed parcel: with the hub-handover flow ON it stops at Created —
|
||||
// collected, in the rider's hands, on its way to a base — and only reaches
|
||||
// Inwarded_at_Hub when the handover is actually recorded. With it OFF
|
||||
// (default, and what the current app expects) it is marked Inwarded_at_Hub
|
||||
// here, which is not where the parcel physically is but is what the current
|
||||
// app and the console's inbound views read.
|
||||
consignmentStatus := constants.ConsignmentInwardedAtHub
|
||||
if hubHandoverEnabled() {
|
||||
consignmentStatus = constants.ConsignmentCreated
|
||||
}
|
||||
|
||||
// Hyperlocal shortcut: pickup and delivery in the same postal area mean no
|
||||
@@ -1088,7 +1089,6 @@ func BookingPickupComplete(c *fiber.Ctx) error {
|
||||
// to Out_for_Delivery, which lets the console tell "collected" from "actively
|
||||
// delivering". With it OFF (default, and what the current app expects) it goes
|
||||
// straight to Out_for_Delivery exactly as before.
|
||||
consignmentStatus := constants.ConsignmentInwardedAtHub
|
||||
if isHyperlocalBooking(booking.Pickuppincode, booking.Deliverypincode,
|
||||
booking.Pickuplatitude, booking.Pickuplongitude,
|
||||
booking.Deliverylatitude, booking.Deliverylongitude) {
|
||||
@@ -1138,6 +1138,14 @@ func BookingPickupComplete(c *fiber.Ctx) error {
|
||||
Currenthubid: defaultHubID,
|
||||
}
|
||||
|
||||
// Under the compatibility flow the parcel is treated as received at the base
|
||||
// the moment it is collected, so the received-at fact is stamped here too —
|
||||
// otherwise every parcel inwarded this way would have a null handover time
|
||||
// and base reconciliation would have nothing to compare against.
|
||||
if consignmentStatus == constants.ConsignmentInwardedAtHub {
|
||||
consignment.Inwardedat = &now
|
||||
}
|
||||
|
||||
var payment models.BookingPayment
|
||||
if tx.Where("bookingid = ?", bookingID).First(&payment).Error == nil {
|
||||
if payment.Paymentstatus == constants.PaymentStatusPaid {
|
||||
@@ -1188,9 +1196,31 @@ func BookingPickupComplete(c *fiber.Ctx) error {
|
||||
// they stay Picked_Up and out of the assignment pool until they finish. A
|
||||
// hub-routed parcel was dropped at the hub, so the rider frees up.
|
||||
postPickupAvailability := constants.MilerAvailable
|
||||
if consignmentStatus == constants.ConsignmentCollectedByMiler {
|
||||
if consignmentStatus == constants.ConsignmentCollectedByMiler ||
|
||||
consignmentStatus == constants.ConsignmentCreated {
|
||||
// Created here means hub-routed and still in the rider's hands: they are
|
||||
// carrying it to a base, so they are not free yet.
|
||||
postPickupAvailability = constants.MilerPickedUp
|
||||
}
|
||||
|
||||
// A parcel that is already inwarded at the base ends this rider's leg, so the
|
||||
// assignment closes with it. Without this the assignment stayed open forever
|
||||
// on the compatibility flow and the rider could not go off duty — MilerEndDuty
|
||||
// refuses while any assignment is still Assigned/Accepted. On the handover
|
||||
// flow the assignment stays open on purpose and closes at inward-at-hub.
|
||||
if consignmentStatus == constants.ConsignmentInwardedAtHub {
|
||||
if err := tx.Model(&models.BookingAssignment{}).
|
||||
Where("bookingid = ? AND mileruserid = ? AND assignmentstatus IN ?",
|
||||
bookingID, milerUserID,
|
||||
[]string{constants.AssignmentAssigned, constants.AssignmentAccepted}).
|
||||
Updates(map[string]interface{}{
|
||||
"assignmentstatus": constants.AssignmentCompleted,
|
||||
"completedat": now,
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return utils.Internal(c, "failed to close assignment")
|
||||
}
|
||||
}
|
||||
if err := tx.Model(&models.MilerProfile{}).Where("userid = ?", milerUserID).
|
||||
Update("availabilitystatus", postPickupAvailability).Error; err != nil {
|
||||
tx.Rollback()
|
||||
@@ -1221,30 +1251,28 @@ func BookingPickupComplete(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
return utils.OK(c, fiber.Map{
|
||||
// next_action says what the rider does next; next_hub says where. An
|
||||
// inward_at_hub with no base named leaves a rider holding a parcel with
|
||||
// nowhere to take it, so the two travel together — and next_hub carries all
|
||||
// six fields, because the coordinates are the only thing that can drive
|
||||
// Navigate and the address and pincode are what the rider reads at the gate.
|
||||
//
|
||||
// consignment_id is always present: the delivery leg is keyed on it, and
|
||||
// without it the app cannot name the parcel it is about to act on.
|
||||
resp := fiber.Map{
|
||||
"tracking_no": trackingNo,
|
||||
"consignment_id": consignment.Consignmentid,
|
||||
"consignmentstatus": consignment.Status,
|
||||
"status": consignment.Status,
|
||||
"booking_no": booking.Bookingno,
|
||||
"booking_status": booking.Status,
|
||||
"next_action": pickupNextAction(consignment.Status),
|
||||
})
|
||||
}
|
||||
|
||||
// pickupNextAction tells the app what the rider does next after a pickup, so it
|
||||
// doesn't have to encode the hub-vs-hyperlocal branch itself:
|
||||
// - Collected_By_Miler → tap start-delivery (collected-state flow on)
|
||||
// - Out_for_Delivery → deliver directly (hyperlocal, collected-state off)
|
||||
// - Inwarded_at_Hub → handed to the hub, done for this rider
|
||||
func pickupNextAction(consignmentStatus string) string {
|
||||
switch consignmentStatus {
|
||||
case constants.ConsignmentCollectedByMiler:
|
||||
return "start_delivery"
|
||||
case constants.ConsignmentOutForDelivery:
|
||||
return "deliver"
|
||||
default:
|
||||
return "handed_to_hub"
|
||||
"next_action": nextActionForConsignment(consignment.Status),
|
||||
}
|
||||
if consignment.Status == constants.ConsignmentCreated ||
|
||||
consignment.Status == constants.ConsignmentInwardedAtHub {
|
||||
resp["next_hub"] = renderBase(handoverHub)
|
||||
}
|
||||
return utils.OK(c, resp)
|
||||
}
|
||||
|
||||
func BookingVehicleRequiredEscalate(c *fiber.Ctx) error {
|
||||
|
||||
Reference in New Issue
Block a user