updates on the deliveries page and status updates as well

This commit is contained in:
2026-08-27 10:34:48 +05:30
parent cf4f4ecd9c
commit 4bcf604539
7 changed files with 256 additions and 158 deletions

View File

@@ -91,12 +91,20 @@ const BOOKING_STATUS_TO_DELIVERY_STATUS = {
out_for_delivery: 'active',
in_transit: 'active',
intransit: 'active',
inwarded_at_hub: 'active',
inwarded: 'active',
at_hub: 'active',
outwarded_from_hub: 'active',
in_transit_to_hub: 'active',
active: 'active',
delivered: 'delivered',
completed: 'delivered',
cancelled: 'cancelled',
canceled: 'cancelled',
skipped: 'skipped'
skipped: 'skipped',
rto: 'skipped',
returned: 'cancelled',
failed: 'cancelled'
};
// Exported (not just module-local) so anything else that needs to classify a
@@ -104,34 +112,69 @@ const BOOKING_STATUS_TO_DELIVERY_STATUS = {
// reuses this instead of growing a second copy of BOOKING_STATUS_TO_DELIVERY_STATUS
// that can drift from it.
// Supports both (status, reachedAt) and a booking/consignment row object.
export const mapBookingStatusToDeliveryStatus = (statusOrRow, maybeReachedAt) => {
if (statusOrRow && typeof statusOrRow === 'object') {
const b = statusOrRow;
const rawConsignmentStatus = b.consignmentstatus ?? b.consignment_status;
if (rawConsignmentStatus) {
return mapBookingStatusToDeliveryStatus(rawConsignmentStatus);
export const deriveDeliveryStatus = (bookingOrStatus, consignmentMapOrReachedAt) => {
if (!bookingOrStatus) return 'pending';
// If passed a raw status string
if (typeof bookingOrStatus === 'string') {
const raw = bookingOrStatus.trim().toLowerCase();
if (raw === 'pickup_scheduled') {
const isReached =
consignmentMapOrReachedAt != null &&
consignmentMapOrReachedAt !== false &&
consignmentMapOrReachedAt !== '';
return isReached ? 'arrived' : 'accepted';
}
const reached = b.reachedat ?? b.reached_at ?? b.reachedAt ?? b.reachedtime ?? b.reached_time;
const rawStatus = b.status ?? b.orderstatus ?? b.bookingstatus;
return mapBookingStatusToDeliveryStatus(rawStatus, reached);
return BOOKING_STATUS_TO_DELIVERY_STATUS[raw] || raw;
}
const raw = String(statusOrRow || '').trim().toLowerCase();
if (raw === 'pickup_scheduled') {
const isReached = maybeReachedAt != null && maybeReachedAt !== false && maybeReachedAt !== '';
// If passed a booking row object
const b = bookingOrStatus;
const rawBookingStatus = String(b.status ?? b.bookingstatus ?? b.orderstatus ?? '').trim().toLowerCase();
const cStatus = consignmentStatusFor(b, typeof consignmentMapOrReachedAt === 'object' && consignmentMapOrReachedAt instanceof Map ? consignmentMapOrReachedAt : undefined);
const reached = b.reachedat ?? b.reached_at ?? b.reachedAt ?? b.reachedtime ?? b.reached_time;
// If booking is Converted_To_Consignment, switch directly to consignmentstatus:
// Converted_To_Consignment is a handoff marker, not a display state.
if (rawBookingStatus === 'converted_to_consignment') {
if (cStatus) {
return mapBookingStatusToDeliveryStatus(cStatus);
}
return 'picked';
}
// If consignment status is present, it wins:
if (cStatus) {
return mapBookingStatusToDeliveryStatus(cStatus);
}
// Before pickup (booking lifecycle):
if (rawBookingStatus === 'pickup_scheduled') {
const isReached = reached != null && reached !== false && reached !== '';
return isReached ? 'arrived' : 'accepted';
}
return BOOKING_STATUS_TO_DELIVERY_STATUS[raw] || raw;
return mapBookingStatusToDeliveryStatus(rawBookingStatus, reached);
};
export const mapBookingStatusToDeliveryStatus = (statusOrRow, maybeReachedAt) => {
return deriveDeliveryStatus(statusOrRow, maybeReachedAt);
};
// A consignment's own status, when this booking has one and it looks like a
// status. `GET /admin/consignments` has no documented response schema, so this
// reads defensively: only a non-empty string on one of the plausible field
// names counts, and anything else returns undefined so the caller keeps using
// the booking's status.
// status. GetAdminBookings attaches consignmentstatus directly to each row,
// and GET /admin/consignments can also be joined.
const consignmentStatusFor = (booking, consignmentMap) => {
if (!booking?.consignmentid || !consignmentMap?.size) return undefined;
const record = consignmentMap.get(String(booking.consignmentid));
// 1. Direct field attached to booking row by GetAdminBookings
const attached = booking?.consignmentstatus ?? booking?.consignment_status;
if (typeof attached === 'string' && attached.trim()) {
return attached.trim();
}
// 2. Lookup in joined consignmentMap if available
const consignmentId = booking?.consignmentid ?? booking?.consignment_id;
if (!consignmentId || !consignmentMap?.size) return undefined;
const record = consignmentMap.get(String(consignmentId));
if (!record) return undefined;
const raw = record.status ?? record.consignmentstatus ?? record.currentstatus ?? record.consignment_status;
return typeof raw === 'string' && raw.trim() ? raw.trim() : undefined;
@@ -648,97 +691,113 @@ export const fetchDeliveries = async ({ pageParam = 1, queryKey }) => {
const milerMap = new Map((milers || []).map((m) => [m.userid ?? m.milerid, m]));
const tenantMap = new Map((tenants || []).map((t) => [t.tenantid, t]));
const dispatched = (bookings || []).filter((b) => b.assignedmileruserid || b.consignmentid);
const isDispatched = (b) => {
if (!b) return false;
const riderId = b.assignedmileruserid ?? b.assigned_miler_user_id ?? b.mileruserid ?? b.milerid ?? b.assignedmilerid;
const consignmentId = b.consignmentid ?? b.consignment_id;
const rawStatus = String(b.status || b.orderstatus || '').trim().toLowerCase();
if (riderId || consignmentId) return true;
if (rawStatus && !['pending_pickup', 'created', 'new', 'booked', 'order_placed', 'unassigned', ''].includes(rawStatus)) {
return true;
}
return false;
};
const dispatched = (bookings || []).filter(isDispatched);
const rows = dispatched.map((b) => {
const customer = customerMap.get(b.appcustomerid);
const miler = milerMap.get(b.assignedmileruserid);
const riderId = b.assignedmileruserid ?? b.assigned_miler_user_id ?? b.mileruserid ?? b.milerid ?? b.assignedmilerid;
const consignmentId = b.consignmentid ?? b.consignment_id;
const customerId = b.appcustomerid ?? b.customerid ?? b.id;
const customer = customerMap.get(customerId);
const miler = milerMap.get(riderId) || (riderId != null ? milerMap.get(Number(riderId)) : null);
const tenant = tenantMap.get(b.tenantid);
const charge = b.serviceoptions?.[0]?.estimatedprice;
const cStatus = consignmentStatusFor(b, consignmentMap);
const reached = b.reachedat ?? b.reached_at ?? b.reachedAt ?? b.reachedtime ?? b.reached_time;
const effectiveDeliveryStatus = cStatus
? mapBookingStatusToDeliveryStatus(cStatus)
: mapBookingStatusToDeliveryStatus(b.status, reached);
const cStatus = consignmentStatusFor(b, consignmentMap);
const reached = b.reachedat ?? b.reached_at ?? b.reachedAt ?? b.reachedtime ?? b.reached_time;
const effectiveDeliveryStatus = deriveDeliveryStatus(b, consignmentMap);
return {
orderheaderid: b.bookingid,
deliveryid: b.bookingid,
orderid: b.bookingno || `#${b.bookingid}`,
consignmentid: b.consignmentid,
tenantid: b.tenantid,
tenantname: tenant?.tenantname || '',
tenantsuburb: '',
applocation: '',
tenantadress: tenant?.primaryemail || '',
locationname: tenant?.tenantname || '',
locationsuburb: '',
// Was hardcoded '' — Dispatch.js's kitchen markers read this as the
// pickup business name (`o.pickupcustomer || o.kitchen_key || 'Unknown'`,
// Dispatch.js:1660), and no booking on this API carries a `kitchen_key`
// field at all, so every kitchen pin fell through to the literal string
// 'Unknown' — rendered as a "U" marker whose hover/popup then showed
// "Unknown". The tenant IS the kitchen for a B2B booking (same value
// already used for tenantname/locationname above), so reuse it here.
pickupcustomer: tenant?.tenantname || '',
pickupcontactno: '',
Pickupaddress: b.pickupaddress || '',
pickupaddress: b.pickupaddress || '',
pickuplocation: b.pickupaddress || '',
pickupsuburb: '',
deliverycustomer: customer?.firstname || customer?.name || (b.appcustomerid ? `Customer #${b.appcustomerid}` : ''),
deliverycontactno: customer?.phone || customer?.contactno || '',
deliveryaddress: b.deliveryaddress || '',
deliverylocation: b.deliveryaddress || '',
deliverysuburb: '',
ridername: miler?.displayname || (b.assignedmileruserid ? `Rider #${b.assignedmileruserid}` : ''),
userid: b.assignedmileruserid,
// GET /admin/milers/:id/notify (and block/assign-vehicle) key off
// milerprofileid, not the userid stored on the booking — confirmed
// live (a booking's assignedmileruserid matches a miler's `userid`
// field, which 404s against /admin/milers/:id; milerprofileid is the
// real primary key of that resource).
milerprofileid: miler?.milerprofileid,
ridercontact: miler?.phone || '',
expecteddeliverytime: b.serviceoptions?.[0]?.estimateddeliveryat,
// No route-plan data source (step order/transit time/cumulative km were
// computed by the old jupiter backend from the dispatch optimiser's
// output, not stored on a booking/consignment) — left undefined so the
// UI's own "—" fallbacks render instead of a fabricated number.
transitminutes: undefined,
cumulativekms: undefined,
step: undefined,
// No road-distance field on a booking — approximated as a straight
// line between pickup and delivery coordinates.
kms: haversineKm(b.pickuplatitude, b.pickuplongitude, b.deliverylatitude, b.deliverylongitude),
pickuplatitude: b.pickuplatitude,
pickuplongitude: b.pickuplongitude,
deliverylatitude: b.deliverylatitude,
deliverylongitude: b.deliverylongitude,
deliverycharges: charge,
deliveryamt: charge,
deliveryamount: charge,
Quantity: b.parcels?.length || 0,
quantity: b.parcels?.length || 0,
collectionamt: undefined,
notes: b.notes || '',
deliverytype: customer ? 'B' : 'C',
orderdate: b.createdat,
deliverydate: b.serviceoptions?.[0]?.estimateddeliveryat || b.updatedat,
reachedat: reached,
assigntime: b.updatedat,
// The consignment's status WINS when there is one (e.g. Collected_By_Miler -> Picked,
// Out_for_Delivery -> Active, Delivered -> Delivered).
// Otherwise derives from booking status + reachedat:
// Pickup_Scheduled + reachedat present -> Arrived
// Pickup_Scheduled + reachedat absent -> Accepted
orderstatus: effectiveDeliveryStatus,
consignmentstatus: cStatus,
statusfromconsignment: cStatus != null,
droplat: b.deliverylatitude,
droplon: b.deliverylongitude
};
});
return {
orderheaderid: b.bookingid ?? b.id,
deliveryid: b.bookingid ?? b.id,
bookingid: b.bookingid ?? b.id,
bookingno: b.bookingno,
orderid: b.bookingno || (b.bookingid ? `#${b.bookingid}` : (b.id ? `#${b.id}` : '')),
consignmentid: consignmentId,
tenantid: b.tenantid,
tenantname: tenant?.tenantname || '',
tenantsuburb: '',
applocation: '',
tenantadress: tenant?.primaryemail || '',
locationname: tenant?.tenantname || '',
locationsuburb: '',
// Was hardcoded '' — Dispatch.js's kitchen markers read this as the
// pickup business name (`o.pickupcustomer || o.kitchen_key || 'Unknown'`,
// Dispatch.js:1660), and no booking on this API carries a `kitchen_key`
// field at all, so every kitchen pin fell through to the literal string
// 'Unknown' — rendered as a "U" marker whose hover/popup then showed
// "Unknown". The tenant IS the kitchen for a B2B booking (same value
// already used for tenantname/locationname above), so reuse it here.
pickupcustomer: tenant?.tenantname || '',
pickupcontactno: '',
Pickupaddress: b.pickupaddress || '',
pickupaddress: b.pickupaddress || '',
pickuplocation: b.pickupaddress || '',
pickupsuburb: '',
deliverycustomer: customer?.firstname || customer?.name || (customerId ? `Customer #${customerId}` : ''),
deliverycontactno: customer?.phone || customer?.contactno || '',
deliveryaddress: b.deliveryaddress || '',
deliverylocation: b.deliveryaddress || '',
deliverysuburb: '',
ridername: miler?.displayname || miler?.name || (riderId ? `Rider #${riderId}` : ''),
userid: riderId,
// GET /admin/milers/:id/notify (and block/assign-vehicle) key off
// milerprofileid, not the userid stored on the booking — confirmed
// live (a booking's assignedmileruserid matches a miler's `userid`
// field, which 404s against /admin/milers/:id; milerprofileid is the
// real primary key of that resource).
milerprofileid: miler?.milerprofileid ?? miler?.id,
ridercontact: miler?.phone || miler?.contactno || '',
expecteddeliverytime: b.serviceoptions?.[0]?.estimateddeliveryat,
// No route-plan data source (step order/transit time/cumulative km were
// computed by the old jupiter backend from the dispatch optimiser's
// output, not stored on a booking/consignment) — left undefined so the
// UI's own "—" fallbacks render instead of a fabricated number.
transitminutes: undefined,
cumulativekms: undefined,
step: undefined,
// No road-distance field on a booking — approximated as a straight
// line between pickup and delivery coordinates.
kms: haversineKm(b.pickuplatitude, b.pickuplongitude, b.deliverylatitude, b.deliverylongitude),
pickuplatitude: b.pickuplatitude,
pickuplongitude: b.pickuplongitude,
deliverylatitude: b.deliverylatitude,
deliverylongitude: b.deliverylongitude,
deliverycharges: charge,
deliveryamt: charge,
deliveryamount: charge,
Quantity: b.parcels?.length || 0,
quantity: b.parcels?.length || 0,
collectionamt: undefined,
notes: b.notes || '',
deliverytype: customer ? 'B' : 'C',
orderdate: b.createdat,
deliverydate: b.serviceoptions?.[0]?.estimateddeliveryat || b.updatedat,
reachedat: reached,
assigntime: b.updatedat,
// The consignment's status WINS when there is one (e.g. Collected_By_Miler -> Picked,
// Out_for_Delivery -> Active, Delivered -> Delivered).
// If status == Converted_To_Consignment, switches to consignmentstatus.
// Otherwise derives from booking status + reachedat:
// Pickup_Scheduled + reachedat present -> Arrived
// Pickup_Scheduled + reachedat absent -> Accepted
orderstatus: effectiveDeliveryStatus,
consignmentstatus: cStatus,
statusfromconsignment: cStatus != null || String(b.status || '').toLowerCase() === 'converted_to_consignment',
droplat: b.deliverylatitude,
droplon: b.deliverylongitude
};
});
// Apply the requested date range to the booking's CREATION day. This has to
// agree with what the batch bucketing reads (Dispatch.js's
@@ -783,14 +842,21 @@ const getDeliveryStatusCounts = async () => {
const id = c?.consignmentid ?? c?.id;
if (id != null) consignmentMap.set(String(id), c);
});
const dispatched = (bookings || []).filter((b) => b.assignedmileruserid || b.consignmentid);
const isDispatched = (b) => {
if (!b) return false;
const riderId = b.assignedmileruserid ?? b.assigned_miler_user_id ?? b.mileruserid ?? b.milerid ?? b.assignedmilerid;
const consignmentId = b.consignmentid ?? b.consignment_id;
const rawStatus = String(b.status || b.orderstatus || '').trim().toLowerCase();
if (riderId || consignmentId) return true;
if (rawStatus && !['pending_pickup', 'created', 'new', 'booked', 'order_placed', 'unassigned', ''].includes(rawStatus)) {
return true;
}
return false;
};
const dispatched = (bookings || []).filter(isDispatched);
const counts = { total: dispatched.length };
dispatched.forEach((b) => {
const cStatus = consignmentStatusFor(b, consignmentMap);
const reached = b.reachedat ?? b.reached_at ?? b.reachedAt ?? b.reachedtime ?? b.reached_time;
const status = cStatus
? mapBookingStatusToDeliveryStatus(cStatus)
: mapBookingStatusToDeliveryStatus(b.status, reached);
const status = deriveDeliveryStatus(b, consignmentMap);
counts[status] = (counts[status] || 0) + 1;
});
return counts;