deliveries
This commit is contained in:
91
src/api/deliveries.ts
Normal file
91
src/api/deliveries.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { api, WEB } from './client';
|
||||
import type { RiderInfo } from './types';
|
||||
import type { DeliveryDraft } from '@/features/store-admin/assignDelivery';
|
||||
|
||||
/**
|
||||
* Deliveries — creating them, moving them along, and finding a rider.
|
||||
*
|
||||
* Separate from `insights.ts`, which only READS deliveries. The split is the
|
||||
* same one the backend makes: `getdeliveries` answers "what is out there", and
|
||||
* these three change it.
|
||||
*/
|
||||
|
||||
export interface RiderQuery {
|
||||
/**
|
||||
* The delivery region. This is the scope that works.
|
||||
*
|
||||
* `tenantid` is also accepted and returns nothing: the filter is
|
||||
* `app_users.tenantid`, which is not set on rider accounts. Verified against
|
||||
* production — `?tenantid=1135` gives an empty list while `?applocationid=1`
|
||||
* gives the rider working that tenant's shops. Passing the tenant would have
|
||||
* produced an empty picker with no error to explain it.
|
||||
*/
|
||||
applocationid: number;
|
||||
}
|
||||
|
||||
export const deliveriesApi = {
|
||||
/**
|
||||
* Riders on duty right now.
|
||||
*
|
||||
* "On duty" is the backend's word, not a filter added here: the query wants
|
||||
* `app_userpools.onduty = 1` and a `riderlogs` row stamped today with
|
||||
* `logstatus = 0`. So this list empties overnight and refills as riders clock
|
||||
* on, and an empty answer means nobody has started their shift — not that
|
||||
* the shop has no riders. The picker has to say which.
|
||||
*/
|
||||
riders: (query: RiderQuery) =>
|
||||
api.list<RiderInfo>(`${WEB}/partners/getriders`, { applocationid: query.applocationid }),
|
||||
|
||||
/**
|
||||
* Hand orders to a rider.
|
||||
*
|
||||
* An array, always, because that is what the endpoint takes and because one
|
||||
* call is one transaction: each row inserts a `deliveries` row, copies it to
|
||||
* `deliveryqueues` for the rider's app, and moves the parent order's status.
|
||||
* Verified with three orders in a single call — three deliveries, three
|
||||
* queue rows, nothing duplicated.
|
||||
*
|
||||
* The response carries no ids, only a message, so callers refetch rather
|
||||
* than patching a row in place.
|
||||
*/
|
||||
assign: (rows: DeliveryDraft[]) =>
|
||||
api.post<unknown>(`${WEB}/deliveries/createdeliveries`, rows),
|
||||
|
||||
/**
|
||||
* Move a delivery along its ladder, or hand it to a different rider.
|
||||
*
|
||||
* `deliveryid` is the only field the backend insists on — it finds the row
|
||||
* with it and derives the parent order from that rather than trusting the
|
||||
* caller's `orderheaderid`, which is why a partial payload is safe here.
|
||||
*
|
||||
* Note what the backend does NOT do: `picked` updates the delivery and stops
|
||||
* there, leaving the order at its previous status. Only pending, delivered
|
||||
* and cancelled are mirrored onto the order.
|
||||
*/
|
||||
update: (body: UpdateDelivery) =>
|
||||
api.put<unknown>(`${WEB}/deliveries/updatedelivery`, body),
|
||||
};
|
||||
|
||||
/**
|
||||
* The delivery lifecycle, lowercase, as `deliveries.orderstatus` stores it.
|
||||
*
|
||||
* `skipped` is real and reachable — the rider got there and nobody was in —
|
||||
* but it is written by the rider's app, not from here, so it is not offered.
|
||||
*/
|
||||
export const DELIVERY_STEPS = ['pending', 'accepted', 'arrived', 'picked', 'active', 'delivered'] as const;
|
||||
export type DeliveryStep = (typeof DELIVERY_STEPS)[number];
|
||||
|
||||
export interface UpdateDelivery {
|
||||
deliveryid: number;
|
||||
orderstatus: string;
|
||||
/** Sent when known so the backend does not have to look it up. */
|
||||
orderheaderid?: number;
|
||||
/** Set to move the job to a different rider. */
|
||||
userid?: number;
|
||||
assigntime?: string;
|
||||
starttime?: string;
|
||||
arrivaltime?: string;
|
||||
pickuptime?: string;
|
||||
deliverytime?: string;
|
||||
canceltime?: string;
|
||||
}
|
||||
@@ -441,7 +441,14 @@ export interface OrderRow {
|
||||
pickupcontactno?: string;
|
||||
pickupaddress?: string;
|
||||
pickupsuburb?: string;
|
||||
/** The delivery half. Blank on an order nobody has been assigned to. */
|
||||
/**
|
||||
* The delivery half. Blank on an order nobody has been assigned to.
|
||||
*
|
||||
* `deliveryid` is the one to read for "has a rider been assigned yet" — it is
|
||||
* 0 until `createdeliveries` runs, and it is 0 on every unassigned row in
|
||||
* production. `rider` looks like the same test and is not: it is filled from
|
||||
* a join and is empty on rows that DO have a delivery.
|
||||
*/
|
||||
deliveryid?: number;
|
||||
rider?: string;
|
||||
ridercontactno?: string;
|
||||
@@ -450,6 +457,77 @@ export interface OrderRow {
|
||||
pickuptime?: string;
|
||||
deliverytime?: string;
|
||||
canceltime?: string;
|
||||
|
||||
/*
|
||||
* The ids a delivery row has to be built from.
|
||||
*
|
||||
* All of these are on the wire and none were typed, because nothing read
|
||||
* them until assignment existed. `createdeliveries` copies them onto the
|
||||
* `deliveries` row, and the reads that follow join on them — so an order
|
||||
* that arrives without one produces a delivery that cannot be seen. See
|
||||
* `assignDelivery.ts` for which of them are load-bearing.
|
||||
*/
|
||||
applocationid?: number;
|
||||
partnerid?: number;
|
||||
configid?: number;
|
||||
moduleid?: number;
|
||||
categoryid?: number;
|
||||
subcategoryid?: number;
|
||||
/** Who placed the order. Distinct from `deliverycustomerid`, which is 0. */
|
||||
customerid?: number;
|
||||
deliverycustomerid?: number;
|
||||
deliverylocationid?: number;
|
||||
pickuplocationid?: number;
|
||||
pickuplat?: string;
|
||||
pickuplong?: string;
|
||||
deliverylat?: string;
|
||||
deliverylong?: string;
|
||||
droplat?: string;
|
||||
droplon?: string;
|
||||
kms?: string;
|
||||
/**
|
||||
* Empty on every production order, so it CANNOT be used to tell a delivery
|
||||
* order from a collection. Typed only so nobody reaches for it and quietly
|
||||
* filters the whole list away — the drop address is the real test.
|
||||
*/
|
||||
deliverytype?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* One rider, from `GET /partners/getriders` (`models.RiderInfo`).
|
||||
*
|
||||
* Only riders who are ON DUTY TODAY appear. The query requires a `riderlogs`
|
||||
* row stamped today with `logstatus = 0` and `app_userpools.onduty = 1`, so an
|
||||
* empty list means "nobody has clocked on", not "this shop has no riders" —
|
||||
* a distinction the picker has to make, or an operator spends the morning
|
||||
* wondering why the fleet vanished.
|
||||
*
|
||||
* `password` is deliberately absent for the same reason it is on `Staff`: the
|
||||
* endpoint returns it in clear, and not typing it is what stops a cell
|
||||
* rendering one.
|
||||
*/
|
||||
export interface RiderInfo {
|
||||
userid: number;
|
||||
firstname?: string;
|
||||
lastname?: string;
|
||||
fullname?: string;
|
||||
contactno?: string;
|
||||
partnerid?: number;
|
||||
applocationid?: number;
|
||||
applocation?: string;
|
||||
vehiclename?: string;
|
||||
vehicleno?: string;
|
||||
licenseno?: string;
|
||||
shiftid?: number;
|
||||
/** Shift window, as `HH:MM:SS`. */
|
||||
starttime?: string;
|
||||
endtime?: string;
|
||||
/** Today's log. `logstatus` 0 is on duty. */
|
||||
logdate?: string;
|
||||
login?: string;
|
||||
logout?: string;
|
||||
logstatus?: number;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user