updates on the deliveries page and status updates as well
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user