updates on the api endpoints on the customer page and more

This commit is contained in:
2026-09-02 16:32:53 +05:30
parent 8e2c484bcb
commit d12629a1e4
31 changed files with 3541 additions and 121 deletions

View File

@@ -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 {