189 lines
6.8 KiB
TypeScript
189 lines
6.8 KiB
TypeScript
/**
|
|
* Order, delivery and POS reads — the numbers behind store performance.
|
|
*
|
|
* Note the split: online orders come from `/web/orders/*` and counter sales
|
|
* from `/pos/*`, which is the ONLY part of the API carrying auth middleware.
|
|
* Any figure that blends the two is eventually consistent by construction, so
|
|
* screens that show one must also show its freshness.
|
|
*/
|
|
|
|
import { api, POS, WEB } from './client';
|
|
import type {
|
|
DeliveryRow,
|
|
DeliverySummary,
|
|
LocationOrderSummary,
|
|
OrderRow,
|
|
OrderSummary,
|
|
PosLocationHealth,
|
|
PosTerminalHealth,
|
|
PosSalesPage,
|
|
PosSalesSummary,
|
|
} from './types';
|
|
|
|
export interface DateRange {
|
|
fromdate?: string;
|
|
todate?: string;
|
|
}
|
|
|
|
export interface OrderQuery extends DateRange {
|
|
tenantid: number;
|
|
/** Omit for every branch of the tenant. */
|
|
locationid?: number;
|
|
status?: string;
|
|
keyword?: string;
|
|
pageno?: number;
|
|
pagesize?: number;
|
|
}
|
|
|
|
export const insightsApi = {
|
|
/**
|
|
* The order rows themselves.
|
|
*
|
|
* `/orders/tenant/getorders` rather than the bare `/orders/getorders`: the
|
|
* controller routes on which ids are present, and passing a tenant with no
|
|
* partner, customer or app-user reaches `GetTenantOrders`. Passing a
|
|
* `locationid` as well reaches `GetTenantLocationOrders`, which is the
|
|
* branch-scoped read — so one call covers both "all branches" and "one
|
|
* branch" by presence alone.
|
|
*
|
|
* `pageno` is 1-based here. The controller floors anything <= 0 to 1, so
|
|
* sending 0 silently gives page one rather than an error.
|
|
*/
|
|
orders: (query: OrderQuery) =>
|
|
api.list<OrderRow>(`${WEB}/orders/tenant/getorders`, {
|
|
tenantid: query.tenantid,
|
|
locationid: query.locationid,
|
|
status: query.status,
|
|
keyword: query.keyword,
|
|
fromdate: query.fromdate,
|
|
todate: query.todate,
|
|
pageno: query.pageno ?? 1,
|
|
pagesize: query.pagesize ?? 50,
|
|
}),
|
|
|
|
/**
|
|
* The delivery jobs.
|
|
*
|
|
* A separate read from the orders list, NOT a filter over it. The rows are a
|
|
* different struct with different fields — rider name, planned vs actual
|
|
* distance, rider charge vs job value, notes — and a different status ladder.
|
|
* Deriving deliveries from orders, which is what this page did first, loses
|
|
* every one of those.
|
|
*
|
|
* The controller 400s unless one of tenantid/partnerid/customerid/
|
|
* applocationid/userid/appuserid is present, so `tenantid` is required here.
|
|
*/
|
|
deliveries: (query: OrderQuery) =>
|
|
api.list<DeliveryRow>(`${WEB}/deliveries/getdeliveries`, {
|
|
tenantid: query.tenantid,
|
|
locationid: query.locationid,
|
|
status: query.status,
|
|
keyword: query.keyword,
|
|
fromdate: query.fromdate,
|
|
todate: query.todate,
|
|
pageno: query.pageno ?? 1,
|
|
pagesize: query.pagesize ?? 50,
|
|
}),
|
|
|
|
orderSummary: (tenantid: number, range: DateRange = {}) =>
|
|
api.get<OrderSummary>(`${WEB}/orders/getordersummary`, { tenantid, ...range }),
|
|
|
|
/** Per-branch order totals for one tenant. `tenantid` is required. */
|
|
locationSummary: (tenantid: number, range: DateRange = {}) =>
|
|
api.list<LocationOrderSummary>(`${WEB}/orders/getlocationsummary`, { tenantid, ...range }),
|
|
|
|
revenueSummary: (tenantid: number, range: DateRange = {}) =>
|
|
api.get<OrderSummary>(`${WEB}/orders/getrevenuesummary`, { tenantid, ...range }),
|
|
|
|
/**
|
|
* `granularity` is REQUIRED and was never sent, so this call answered 400
|
|
* every single time: "granularity query parameter is required (day, month,
|
|
* year)". Nothing renders it yet, which is the only reason it went unnoticed
|
|
* — the first screen to use it would have shown an error instead of a chart.
|
|
*
|
|
* Defaulted rather than made a required argument: a day-by-day series is what
|
|
* every caller of a dated range wants, and a parameter with one sensible
|
|
* answer should not be every caller's problem.
|
|
*/
|
|
timeSeries: (
|
|
tenantid: number,
|
|
range: DateRange = {},
|
|
granularity: 'day' | 'month' | 'year' = 'day',
|
|
) =>
|
|
api.get<Record<string, unknown>[]>(`${WEB}/orders/gettimeseries`, {
|
|
tenantid,
|
|
granularity,
|
|
...range,
|
|
}),
|
|
|
|
deliverySummary: (tenantid: number, range: DateRange = {}) =>
|
|
api.get<DeliverySummary>(`${WEB}/deliveries/deliverysummary`, { tenantid, ...range }),
|
|
|
|
/**
|
|
* Counter sales for ONE outlet.
|
|
*
|
|
* `locationid` is required and singular — there is no tenant-wide POS call,
|
|
* so a multi-branch view fans out one request per branch.
|
|
*/
|
|
posSales: (
|
|
locationid: number,
|
|
params: DateRange & {
|
|
pageno?: number;
|
|
pagesize?: number;
|
|
/** The three server-side filters the old console's bills tab offers. */
|
|
terminalid?: string;
|
|
cashiername?: string;
|
|
paymentmode?: string;
|
|
} = {},
|
|
) =>
|
|
api.get<PosSalesPage>(`${POS}/sales`, {
|
|
locationid,
|
|
pageno: params.pageno ?? 0,
|
|
pagesize: params.pagesize ?? 50,
|
|
fromdate: params.fromdate,
|
|
todate: params.todate,
|
|
terminalid: params.terminalid,
|
|
cashiername: params.cashiername,
|
|
paymentmode: params.paymentmode,
|
|
}),
|
|
|
|
/**
|
|
* One counter bill in full, with its lines.
|
|
*
|
|
* `reference` accepts the till's order UUID, the invoice number, or this
|
|
* backend's posorderid — a support call starts from whichever the person is
|
|
* looking at, so the endpoint takes all three.
|
|
*/
|
|
posSaleDetail: (locationid: number, reference: string) =>
|
|
api.get<unknown>(`${POS}/sales/detail`, { locationid, reference }),
|
|
|
|
/**
|
|
* Counter-sales totals for ONE outlet.
|
|
*
|
|
* The richest read in the API: bill count, gross, tax, discount, roundoff and
|
|
* average bill, already broken down by payment mode, by day and by terminal.
|
|
* Reports gets its offline half from this and nothing else.
|
|
*
|
|
* Kept apart from the order summaries above on purpose. These are `posorders`
|
|
* rows; those are `orders` rows. The two are never added together — see
|
|
* `store-admin-backend-gap.md` §3.1 for why that would double-count a branch
|
|
* that both runs a till and uploads a spreadsheet.
|
|
*/
|
|
posSalesSummary: (locationid: number, range: DateRange = {}) =>
|
|
api.get<PosSalesSummary>(`${POS}/sales/summary`, { locationid, ...range }),
|
|
|
|
/**
|
|
* Till presence for one outlet — how many are online, how many bills are stranded.
|
|
*
|
|
* Returns the terminal list, not the wrapper. The endpoint answers
|
|
* `{location_id, total, online, terminals}`; every caller wants `terminals`,
|
|
* and `summariseBranch` recomputes `online` from the heartbeats anyway
|
|
* because Fiesta's figure counts a stale till as present. Unwrapping here
|
|
* keeps that one shape fact in the API layer instead of on every page.
|
|
*/
|
|
posHealth: (locationid: number): Promise<PosTerminalHealth[]> =>
|
|
api
|
|
.get<PosLocationHealth>(`${POS}/health/location`, { location_id: locationid })
|
|
.then((health) => (Array.isArray(health?.terminals) ? health.terminals : [])),
|
|
};
|