dispatch page
This commit is contained in:
@@ -9,16 +9,16 @@
|
||||
* REST tab). This is the operational backend: order/delivery/location summaries,
|
||||
* the deliveries board, riders, stock statements, and customers.
|
||||
*
|
||||
* Requests go through the Vite dev proxy at `/fiesta/*`, which forwards to
|
||||
* `https://fiesta.nearle.app/*` (see vite.config.ts). Fiesta is CORS-enabled and
|
||||
* needs no auth header for these read endpoints.
|
||||
* Requests go directly to `https://fiesta.nearle.app/*` — Fiesta is CORS-enabled
|
||||
* and needs no auth header for these read endpoints, so no dev proxy is required.
|
||||
*
|
||||
* This sits alongside `./api` (the Hasura/workolik REST surface the dashboard
|
||||
* uses). Components should call the TanStack hooks in `./fiestaQueries`, not
|
||||
* these functions directly.
|
||||
*/
|
||||
|
||||
const FIESTA_BASE = '/fiesta/live/api/v1/web';
|
||||
const FIESTA_BASE = 'https://fiesta.nearle.app/live/api/v1/web';
|
||||
const FIESTA_MOB_BASE = 'https://fiesta.nearle.app/live/api/v1/mob';
|
||||
|
||||
/** Tenant / location scope shared by the merchant console (Ragul Stores, Coimbatore). */
|
||||
export const FIESTA_TENANT_ID = 1087;
|
||||
@@ -29,6 +29,19 @@ export const FIESTA_PRIMARY_LOCATION_ID = 1097;
|
||||
export type Row = Record<string, unknown>;
|
||||
type QueryParams = Record<string, string | number | undefined | null>;
|
||||
|
||||
/**
|
||||
* The exact payload the nearledaily consumer app expects when its in-app scanner
|
||||
* reads a store QR: a JSON object `{"tenantid":N,"locationid":N}`. The app parses
|
||||
* this and resolves the outlet from it.
|
||||
*
|
||||
* IMPORTANT: it must be this JSON shape, NOT a URL — the app rejects a URL with
|
||||
* "invalid QR code content". Keep it to exactly these two keys to match the app's
|
||||
* schema; extra keys risk strict-schema rejection on the app side.
|
||||
*/
|
||||
export function buildStoreQrPayload(opts: { tenantid: number; locationid: number }): string {
|
||||
return JSON.stringify({ tenantid: opts.tenantid, locationid: opts.locationid });
|
||||
}
|
||||
|
||||
async function fiestaGet<T = unknown>(endpoint: string, params: QueryParams = {}): Promise<T> {
|
||||
const qs = new URLSearchParams();
|
||||
Object.entries(params).forEach(([k, v]) => {
|
||||
@@ -112,13 +125,14 @@ export interface FiestaOrderSummary {
|
||||
tenantname?: string;
|
||||
}
|
||||
|
||||
/** /orders/getordersummary?tenantid=&fromdate=&todate= — flat order counts. */
|
||||
/** /orders/getordersummary?tenantid=&locationid=&fromdate=&todate= — flat order counts. */
|
||||
export async function getOrderSummary(
|
||||
tenantid: number,
|
||||
fromdate: string,
|
||||
todate: string,
|
||||
locationid?: number,
|
||||
): Promise<FiestaOrderSummary | null> {
|
||||
const row = firstRow<Row>(await fiestaGet('orders/getordersummary', { tenantid, fromdate, todate }));
|
||||
const row = firstRow<Row>(await fiestaGet('orders/getordersummary', { tenantid, locationid, fromdate, todate }));
|
||||
if (!row) return null;
|
||||
return {
|
||||
total: num(row.total),
|
||||
@@ -162,21 +176,27 @@ export async function getOrderInsight(tenantid: number): Promise<Row[]> {
|
||||
return toRows(await fiestaGet('orders/getorderinsight', { tenantid }));
|
||||
}
|
||||
|
||||
/** /orders/getorders?tenantid=&status=&fromdate=&todate=&pageno=&pagesize= — orders board. */
|
||||
/** /orders/getorders?tenantid=&locationid=&applocationid=&status=&fromdate=&todate=&keyword=&pageno=&pagesize= — orders board. */
|
||||
export async function getOrders(opts: {
|
||||
tenantid: number;
|
||||
status: string;
|
||||
fromdate: string;
|
||||
todate: string;
|
||||
locationid?: number;
|
||||
applocationid?: number;
|
||||
keyword?: string;
|
||||
pageno?: number;
|
||||
pagesize?: number;
|
||||
}): Promise<Row[]> {
|
||||
return toRows(
|
||||
await fiestaGet('orders/getorders', {
|
||||
tenantid: opts.tenantid,
|
||||
locationid: opts.locationid,
|
||||
applocationid: opts.applocationid,
|
||||
status: opts.status,
|
||||
fromdate: opts.fromdate,
|
||||
todate: opts.todate,
|
||||
keyword: opts.keyword,
|
||||
pageno: opts.pageno ?? 1,
|
||||
pagesize: opts.pagesize ?? 20,
|
||||
}),
|
||||
@@ -185,7 +205,15 @@ export async function getOrders(opts: {
|
||||
|
||||
/** /orders/getorderdetails?orderheaderid= — line items for a single order. */
|
||||
export async function getOrderDetails(orderheaderid: number | string): Promise<Row[]> {
|
||||
return toRows(await fiestaGet('orders/getorderdetails', { orderheaderid }));
|
||||
let cleanId = String(orderheaderid).trim();
|
||||
if (cleanId.toUpperCase().startsWith('DLV-')) {
|
||||
cleanId = cleanId.substring(4);
|
||||
}
|
||||
cleanId = cleanId.split('-')[0];
|
||||
const numericId = Number(cleanId);
|
||||
const finalId = Number.isInteger(numericId) && numericId > 0 ? numericId : orderheaderid;
|
||||
|
||||
return toRows(await fiestaGet('orders/getorderdetails', { orderheaderid: finalId }));
|
||||
}
|
||||
|
||||
/** /orders/getorders?customerid=&status=&pageno=&pagesize= — one customer's order history. */
|
||||
@@ -221,17 +249,19 @@ export interface FiestaDeliverySummary {
|
||||
cancelled: number;
|
||||
}
|
||||
|
||||
/** /deliveries/deliverysummary?tenantid=&applocationid=&fromdate=&todate= — dispatch counts. */
|
||||
/** /deliveries/deliverysummary?tenantid=&applocationid=&locationid=&fromdate=&todate= — dispatch counts. */
|
||||
export async function getDeliverySummary(opts: {
|
||||
tenantid: number;
|
||||
applocationid?: number;
|
||||
locationid?: number;
|
||||
fromdate: string;
|
||||
todate: string;
|
||||
}): Promise<FiestaDeliverySummary | null> {
|
||||
const row = firstRow<Row>(
|
||||
await fiestaGet('deliveries/deliverysummary', {
|
||||
tenantid: opts.tenantid,
|
||||
applocationid: opts.applocationid ?? FIESTA_APPLOCATION_ID,
|
||||
applocationid: opts.applocationid, // only sent when provided (no forced default)
|
||||
locationid: opts.locationid,
|
||||
fromdate: opts.fromdate,
|
||||
todate: opts.todate,
|
||||
}),
|
||||
@@ -250,19 +280,40 @@ export async function getDeliverySummary(opts: {
|
||||
};
|
||||
}
|
||||
|
||||
/** /deliveries/getdeliveries?tenantid=&fromdate=&todate= — the master deliveries board. */
|
||||
/** /deliveries/getdeliveries?tenantid=&applocationid=&locationid=&status=&fromdate=&todate=&keyword=&pageno=&pagesize= — the master deliveries board. */
|
||||
export async function getDeliveries(opts: {
|
||||
tenantid: number;
|
||||
fromdate: string;
|
||||
todate: string;
|
||||
status?: string;
|
||||
locationid?: number;
|
||||
applocationid?: number;
|
||||
keyword?: string;
|
||||
pageno?: number;
|
||||
pagesize?: number;
|
||||
}): Promise<Row[]> {
|
||||
return toRows(
|
||||
const rows = toRows(
|
||||
await fiestaGet('deliveries/getdeliveries', {
|
||||
tenantid: opts.tenantid,
|
||||
// NOTE: do NOT send `locationid` to getdeliveries — the backend's locationid
|
||||
// filter on THIS endpoint is broken: passing a real outlet id returns []
|
||||
// (it doesn't match against the row's own `locationid`), even though
|
||||
// deliverysummary honours the same id and the rows clearly carry it. So we
|
||||
// fetch tenant-wide here and scope by locationid client-side below; the KPI
|
||||
// strip (deliverysummary) keeps using the working server-side filter.
|
||||
applocationid: opts.applocationid,
|
||||
// The backend treats `status` as a LITERAL orderstatus filter — passing
|
||||
// 'all' matches nothing (returns []). Send empty to fetch every status and
|
||||
// let the board filter client-side by its status tabs.
|
||||
status: !opts.status || opts.status === 'all' ? '' : opts.status,
|
||||
fromdate: opts.fromdate,
|
||||
todate: opts.todate,
|
||||
keyword: opts.keyword,
|
||||
pageno: opts.pageno ?? 1,
|
||||
pagesize: opts.pagesize ?? 200,
|
||||
}),
|
||||
);
|
||||
return opts.locationid ? rows.filter((r) => num(r.locationid) === opts.locationid) : rows;
|
||||
}
|
||||
|
||||
/** /deliveries/getdeliveryinsight?tenantid= — daily delivery insight. */
|
||||
@@ -312,35 +363,214 @@ export async function getFleetSummary(opts: {
|
||||
);
|
||||
}
|
||||
|
||||
/** `YYYY-MM-DD HH:mm:ss` — the timestamp format the delivery endpoints expect. */
|
||||
function nowStamp(): string {
|
||||
const d = new Date();
|
||||
const p = (n: number) => String(n).padStart(2, '0');
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a delivery record from a getorders row for POST /createdeliveries. The
|
||||
* backend keys the order (orderheaderid) and the rider (userid), copies the
|
||||
* pickup/drop snapshot, and must carry the SAME tenant/partner/applocation as
|
||||
* the order so the assignment is valid. Order field names map 1:1 (Go's JSON
|
||||
* decode is case-insensitive, so `pickupaddress` satisfies `Pickupaddress`).
|
||||
*/
|
||||
function deliveryFromOrder(o: Row, userid: number, assigntime: string): Row {
|
||||
return {
|
||||
orderheaderid: num(o.orderheaderid),
|
||||
orderid: str(o.orderid),
|
||||
applocationid: num(o.applocationid),
|
||||
configid: num(o.configid) || 1,
|
||||
partnerid: num(o.partnerid),
|
||||
tenantid: num(o.tenantid),
|
||||
moduleid: num(o.moduleid),
|
||||
locationid: num(o.locationid),
|
||||
categoryid: num(o.categoryid),
|
||||
subcategoryid: num(o.subcategoryid),
|
||||
userid, // the assigned rider
|
||||
customerid: num(o.customerid),
|
||||
orderstatus: 'pending',
|
||||
assigntime,
|
||||
// The Orders API exposes the scheduled delivery date as `deliverytime` (there
|
||||
// is no `deliverydate` on an order row). Copy it through so the new delivery
|
||||
// lands in the Deliveries board's date window — falling back to the order date
|
||||
// and finally the assign timestamp so the row is never written date-less
|
||||
// (a date-less delivery is excluded by getdeliveries' from/to filter).
|
||||
deliverydate: str(o.deliverydate) || str(o.deliverytime) || str(o.orderdate) || assigntime,
|
||||
itemcount: num(o.itemcount),
|
||||
orderamount: num(o.orderamount) || num(o.deliveryamt),
|
||||
deliveryamt: num(o.deliveryamt),
|
||||
deliverycharges: num(o.deliverycharge) || num(o.deliverycharges),
|
||||
paymenttype: num(o.paymenttype),
|
||||
ordernotes: str(o.ordernotes),
|
||||
pickupcustomer: str(o.pickupcustomer) || str(o.tenantname),
|
||||
pickupcontactno: str(o.pickupcontactno),
|
||||
pickupaddress: str(o.pickupaddress),
|
||||
pickuplocationid: num(o.pickuplocationid),
|
||||
pickuplat: str(o.pickuplat),
|
||||
pickuplon: str(o.pickuplong) || str(o.pickuplon),
|
||||
deliverycustomerid: num(o.deliverycustomerid),
|
||||
deliverylocationid: num(o.deliverylocationid),
|
||||
deliverycustomer: str(o.deliverycustomer),
|
||||
deliverycontactno: str(o.deliverycontactno),
|
||||
deliveryaddress: str(o.deliveryaddress),
|
||||
deliverylat: str(o.deliverylat),
|
||||
deliverylong: str(o.deliverylong),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a rider to one or more orders — the CORRECT flow per the backend:
|
||||
* • orders with no delivery yet (`deliveryid == 0`, i.e. freshly created) →
|
||||
* POST /deliveries/createdeliveries (one batched call). This creates the
|
||||
* delivery, enqueues it, AND flips the order to `pending`.
|
||||
* • orders that already have a delivery → PUT /deliveries/updatedelivery to
|
||||
* re-point the rider.
|
||||
* The rider (userid) MUST belong to the same tenant/partner as the orders, or
|
||||
* the backend rejects the assignment — that scoping is enforced on the rider
|
||||
* list (see getRiders' partnerid).
|
||||
*/
|
||||
export async function assignRiderToOrders(
|
||||
userid: number,
|
||||
orders: Row[],
|
||||
): Promise<{ ok: number; failed: number; total: number }> {
|
||||
const assigntime = nowStamp();
|
||||
const toCreate = orders.filter((o) => !num(o.deliveryid));
|
||||
const toUpdate = orders.filter((o) => num(o.deliveryid));
|
||||
let ok = 0;
|
||||
let failed = 0;
|
||||
|
||||
if (toCreate.length) {
|
||||
try {
|
||||
await fiestaSend('deliveries/createdeliveries', 'POST', toCreate.map((o) => deliveryFromOrder(o, userid, assigntime)));
|
||||
ok += toCreate.length;
|
||||
} catch {
|
||||
failed += toCreate.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (toUpdate.length) {
|
||||
const results = await Promise.allSettled(
|
||||
toUpdate.map((o) =>
|
||||
fiestaSend('deliveries/updatedelivery', 'PUT', {
|
||||
userid,
|
||||
deliveryid: num(o.deliveryid),
|
||||
orderheaderid: num(o.orderheaderid),
|
||||
orderstatus: 'pending',
|
||||
assigntime,
|
||||
}),
|
||||
),
|
||||
);
|
||||
ok += results.filter((r) => r.status === 'fulfilled').length;
|
||||
failed += results.filter((r) => r.status === 'rejected').length;
|
||||
}
|
||||
|
||||
return { ok, failed, total: orders.length };
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// PARTNERS / RIDERS
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/** /partners/getriders?applocationid=&tenantid= — active rider fleet. */
|
||||
/**
|
||||
* /partners/getriders?applocationid=&tenantid=&partnerid= — active rider fleet.
|
||||
* Scoped by tenant AND partner: a rider belongs to one tenant/partner, so an
|
||||
* order can only be assigned to a rider sharing its partnerid. Passing the
|
||||
* order's partnerid keeps the assignable list correct (an out-of-tenant rider
|
||||
* simply won't appear, which is the intended guard).
|
||||
*/
|
||||
export async function getRiders(opts: {
|
||||
applocationid?: number;
|
||||
tenantid: number;
|
||||
partnerid?: number;
|
||||
}): Promise<Row[]> {
|
||||
return toRows(
|
||||
await fiestaGet('partners/getriders', {
|
||||
applocationid: opts.applocationid ?? FIESTA_APPLOCATION_ID,
|
||||
tenantid: opts.tenantid,
|
||||
partnerid: opts.partnerid,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/** /partners/getridershifts?applocationid= — rider shift records. */
|
||||
export async function getRiderShifts(applocationid: number = FIESTA_APPLOCATION_ID): Promise<Row[]> {
|
||||
return toRows(await fiestaGet('partners/getridershifts', { applocationid }));
|
||||
return toRows(await fiestaGet('partners/getridershifts/', { applocationid }));
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// TENANTS / CUSTOMERS
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/** /tenants/gettenantlocations?tenantid= — outlet locations for a tenant. */
|
||||
/**
|
||||
* Throwaway/test email providers. A location whose contact email is on one of
|
||||
* these is a sandbox record, never a real outlet — used to drop test data.
|
||||
*/
|
||||
const DISPOSABLE_EMAIL_DOMAINS = new Set([
|
||||
'mailinator.com',
|
||||
'mailinator.net',
|
||||
'example.com',
|
||||
'example.org',
|
||||
'test.com',
|
||||
'yopmail.com',
|
||||
'guerrillamail.com',
|
||||
'10minutemail.com',
|
||||
]);
|
||||
|
||||
/**
|
||||
* The tenant-locations endpoint for some tenants returns junk: the primary
|
||||
* outlet duplicated several times, plus orphan test records geocoded to random
|
||||
* countries (e.g. "Deborah Lara, Spain", "power, Ireland") with throwaway
|
||||
* emails. This strips both so the registry/inventory show only real outlets.
|
||||
*
|
||||
* The filter is self-calibrating (no hardcoded names/ids): it derives the
|
||||
* tenant's operating region from the most common state among its outlets, then
|
||||
* drops rows that either use a disposable email or sit outside that region. If
|
||||
* the region can't be established (no state data), nothing is region-filtered —
|
||||
* we'd rather show an extra row than hide a genuine outlet.
|
||||
*/
|
||||
export function cleanTenantLocations(rows: Row[]): Row[] {
|
||||
// 1. Dedupe by locationid — the API repeats the primary outlet.
|
||||
const seen = new Set<number>();
|
||||
const deduped = rows.filter((r) => {
|
||||
const id = num(r.locationid);
|
||||
if (!id || seen.has(id)) return false;
|
||||
seen.add(id);
|
||||
return true;
|
||||
});
|
||||
|
||||
// 2. Find the tenant's home region (plurality of `state`).
|
||||
const stateCounts = new Map<string, number>();
|
||||
for (const r of deduped) {
|
||||
const st = str(r.state).trim().toLowerCase();
|
||||
if (st) stateCounts.set(st, (stateCounts.get(st) ?? 0) + 1);
|
||||
}
|
||||
let homeState = '';
|
||||
let max = 0;
|
||||
for (const [st, c] of stateCounts) {
|
||||
if (c > max) {
|
||||
max = c;
|
||||
homeState = st;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Drop disposable-email rows and out-of-region rows.
|
||||
return deduped.filter((r) => {
|
||||
const emailDomain = (str(r.email).split('@')[1] ?? '').trim().toLowerCase();
|
||||
if (emailDomain && DISPOSABLE_EMAIL_DOMAINS.has(emailDomain)) return false;
|
||||
if (homeState) {
|
||||
const st = str(r.state).trim().toLowerCase();
|
||||
if (st && st !== homeState) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/** /tenants/gettenantlocations?tenantid= — outlet locations for a tenant (test rows stripped). */
|
||||
export async function getTenantLocations(tenantid: number): Promise<Row[]> {
|
||||
return toRows(await fiestaGet('tenants/gettenantlocations', { tenantid }));
|
||||
return cleanTenantLocations(toRows(await fiestaGet('tenants/gettenantlocations', { tenantid })));
|
||||
}
|
||||
|
||||
/** /tenants/getalltenants?applocationid=&status=&pageno=&pagesize= — active tenants. */
|
||||
@@ -360,7 +590,27 @@ export async function getAllTenants(opts: {
|
||||
);
|
||||
}
|
||||
|
||||
/** /customers/gettenantcustomers?tenantid=&locationid=&pageno=&pagesize=&keyword= */
|
||||
/**
|
||||
* Collapse the gettenantcustomers rows to one per customer. The endpoint returns
|
||||
* one row per saved DELIVERY ADDRESS (each carries its own deliverylocationid /
|
||||
* address), so a customer with several addresses repeats many times. Key by
|
||||
* customerid (fall back to contactno), preferring the row flagged primaryaddress;
|
||||
* rows with no identity at all are kept as-is so nothing is silently dropped.
|
||||
*/
|
||||
export function dedupeCustomers(rows: Row[]): Row[] {
|
||||
const byCustomer = new Map<string, Row>();
|
||||
for (const r of rows) {
|
||||
const cid = num(r.customerid);
|
||||
const key = cid ? `c${cid}` : (str(r.contactno) ? `p${str(r.contactno)}` : `x${byCustomer.size}`);
|
||||
const existing = byCustomer.get(key);
|
||||
if (!existing || (num(r.primaryaddress) && !num(existing.primaryaddress))) {
|
||||
byCustomer.set(key, r);
|
||||
}
|
||||
}
|
||||
return [...byCustomer.values()];
|
||||
}
|
||||
|
||||
/** /customers/gettenantcustomers?tenantid=&locationid=&pageno=&pagesize=&keyword= (deduped per customer). */
|
||||
export async function getTenantCustomers(opts: {
|
||||
tenantid: number;
|
||||
locationid: number;
|
||||
@@ -368,7 +618,7 @@ export async function getTenantCustomers(opts: {
|
||||
pageno?: number;
|
||||
pagesize?: number;
|
||||
}): Promise<Row[]> {
|
||||
return toRows(
|
||||
return dedupeCustomers(toRows(
|
||||
await fiestaGet('customers/gettenantcustomers', {
|
||||
tenantid: opts.tenantid,
|
||||
locationid: opts.locationid,
|
||||
@@ -376,7 +626,7 @@ export async function getTenantCustomers(opts: {
|
||||
pageno: opts.pageno ?? 1,
|
||||
pagesize: opts.pagesize ?? 20,
|
||||
}),
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
@@ -540,8 +790,13 @@ export interface CreateUserInput {
|
||||
lastname?: string;
|
||||
email: string;
|
||||
contactno: string;
|
||||
password: string;
|
||||
/** Optional — merchant_web's create form doesn't collect one. */
|
||||
password?: string;
|
||||
roleid: number;
|
||||
/** Role config (the selected role's configid) — matches merchant_web's create payload. */
|
||||
configid?: number;
|
||||
/** Business module id (merchant_web sends the logged-in user's; 0 when absent). */
|
||||
moduleid?: number;
|
||||
dialcode?: string;
|
||||
pin?: number;
|
||||
address?: string;
|
||||
@@ -549,6 +804,10 @@ export interface CreateUserInput {
|
||||
city?: string;
|
||||
state?: string;
|
||||
postcode?: string;
|
||||
latitude?: string;
|
||||
longitude?: string;
|
||||
/** Rider shift (only meaningful for rider-role users). */
|
||||
shiftid?: number;
|
||||
tenantid: number;
|
||||
locationid?: number;
|
||||
applocationid?: number;
|
||||
@@ -561,17 +820,22 @@ export async function createUser(input: CreateUserInput): Promise<Row> {
|
||||
authname: input.email,
|
||||
firstname: input.firstname,
|
||||
lastname: input.lastname ?? '',
|
||||
password: input.password,
|
||||
password: input.password ?? '',
|
||||
email: input.email,
|
||||
dialcode: input.dialcode ?? '+91',
|
||||
contactno: input.contactno,
|
||||
roleid: input.roleid,
|
||||
configid: input.configid ?? 15,
|
||||
moduleid: input.moduleid ?? 0,
|
||||
pin: input.pin ?? 0,
|
||||
address: input.address ?? '',
|
||||
suburb: input.suburb ?? '',
|
||||
city: input.city ?? '',
|
||||
state: input.state ?? '',
|
||||
postcode: input.postcode ?? '',
|
||||
latitude: input.latitude ?? '',
|
||||
longitude: input.longitude ?? '',
|
||||
shiftid: input.shiftid ?? 0,
|
||||
tenantid: input.tenantid,
|
||||
locationid: input.locationid ?? 0,
|
||||
applocationid: input.applocationid ?? FIESTA_APPLOCATION_ID,
|
||||
@@ -597,3 +861,56 @@ export interface UpdateUserInput {
|
||||
export async function updateUser(input: UpdateUserInput): Promise<Row> {
|
||||
return fiestaSend<Row>('users/update', 'PUT', input);
|
||||
}
|
||||
|
||||
export interface CreateTenantInput {
|
||||
tenantname: string;
|
||||
companyname: string;
|
||||
primarycontact: string;
|
||||
primaryemail: string;
|
||||
address?: string;
|
||||
suburb?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
postcode?: string;
|
||||
approved?: number;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
/** POST /tenants/createtenantuser — Onboard a new tenant and create their admin user. */
|
||||
export async function createTenantUser(input: CreateTenantInput): Promise<Row> {
|
||||
const res = await fetch(`${FIESTA_MOB_BASE}/tenants/createtenantuser`, {
|
||||
method: 'POST',
|
||||
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
const json = (await res.json().catch(() => null)) as { message?: string; status?: boolean } | null;
|
||||
if (!res.ok || (json && json.status === false)) {
|
||||
throw new Error(json?.message || `Tenant onboarding failed: ${res.status}`);
|
||||
}
|
||||
return json as Row;
|
||||
}
|
||||
|
||||
export interface CreateTenantLocationInput {
|
||||
tenantid: number;
|
||||
locationname: string;
|
||||
address?: string;
|
||||
suburb?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
postcode?: string;
|
||||
contactno?: string;
|
||||
email?: string;
|
||||
opentime?: string;
|
||||
closetime?: string;
|
||||
deliverymins?: number;
|
||||
deliveryradius?: number;
|
||||
latitude?: string;
|
||||
longitude?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
/** POST /tenants/createtenantlocation — Create a new store location under a tenant. */
|
||||
export async function createTenantLocation(input: CreateTenantLocationInput): Promise<Row> {
|
||||
return fiestaSend<Row>('tenants/createtenantlocation', 'POST', input);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user