deliveries

This commit is contained in:
2026-09-03 19:13:04 +05:30
parent 25073659d8
commit 743aa93e20
11 changed files with 1787 additions and 10 deletions

91
src/api/deliveries.ts Normal file
View 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;
}

View File

@@ -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;
}
/**