rider partner page
This commit is contained in:
@@ -33,38 +33,59 @@ export const RIDER_MESSAGE = {
|
||||
: `${count} orders have been assigned to you. Kindly accept and process the deliveries.`,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Which fleet to ask for. One of these, in this order of preference.
|
||||
*
|
||||
* `getriders` scopes by applocation, partner or tenant. It used to be called
|
||||
* with the region ALONE, which asks "who is on duty in this city" — so a
|
||||
* merchant's assign picker offered every on-duty rider in Coimbatore, including
|
||||
* other merchants' own riders and every other partner's.
|
||||
*
|
||||
* Measured 2026-09-09: 118 riders across three regions, and 117 of them belong
|
||||
* to a delivery partner — 75 to partner 44 alone. Exactly one rider on the
|
||||
* platform is a merchant's own. So the region scope was not a harmless default;
|
||||
* it was the only thing holding the picker together while the two real scopes
|
||||
* went unused.
|
||||
*/
|
||||
export interface RiderQuery {
|
||||
/** The merchant's own riders — hired by them, working their branches. */
|
||||
tenantid?: number;
|
||||
/** A delivery partner's riders. One partner supplies many merchants. */
|
||||
partnerid?: number;
|
||||
/**
|
||||
* The delivery region, and for now the only scope that finds anybody.
|
||||
* The delivery region — a CITY, and the fallback for neither of the above.
|
||||
*
|
||||
* `tenantid` is accepted too and the query is sound — it just matches nothing
|
||||
* yet, because `app_users.tenantid` was never filled in for a rider. Riders
|
||||
* hired through this console DO carry one, so tenant scope starts working the
|
||||
* moment a merchant has their own.
|
||||
*
|
||||
* It is not the scope used here, and that is deliberate: production has 84
|
||||
* riders on applocation 1 and none of them has a tenant, so switching today
|
||||
* would empty the picker for everybody. Revisit once merchants have hired
|
||||
* their own — preferring tenant and falling back to region.
|
||||
*
|
||||
* Note what region means: a CITY. Until then an operator is offered every
|
||||
* on-duty rider in Coimbatore, including other merchants'.
|
||||
* Kept because a caller with no merchant in hand still has to ask something,
|
||||
* not because it is the right scope for an assign picker.
|
||||
*/
|
||||
applocationid: number;
|
||||
applocationid?: number;
|
||||
}
|
||||
|
||||
export const deliveriesApi = {
|
||||
/**
|
||||
* Riders on duty right now.
|
||||
* Riders on duty right now, for one OWNER.
|
||||
*
|
||||
* "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.
|
||||
*
|
||||
* ── Why this is scoped and used to not be ─────────────────────────────────
|
||||
*
|
||||
* It sent `applocationid` alone, which asks "who is on duty in this city" —
|
||||
* so a merchant's assign picker listed every on-duty rider in Coimbatore,
|
||||
* including other merchants' own riders and every partner's. Nobody hit it
|
||||
* because there is one rider on the platform. `getriders` scopes by
|
||||
* applocation, partner or tenant, in that order, so the caller names which
|
||||
* fleet it means and the region is only a fallback for neither.
|
||||
*/
|
||||
riders: (query: RiderQuery) =>
|
||||
api.list<RiderInfo>(`${WEB}/partners/getriders`, { applocationid: query.applocationid }),
|
||||
api.list<RiderInfo>(`${WEB}/partners/getriders`, {
|
||||
...(query.tenantid ? { tenantid: query.tenantid } : {}),
|
||||
...(query.partnerid ? { partnerid: query.partnerid } : {}),
|
||||
...(query.tenantid || query.partnerid ? {} : { applocationid: query.applocationid }),
|
||||
}),
|
||||
|
||||
/**
|
||||
* Hand orders to a rider.
|
||||
@@ -165,7 +186,18 @@ export interface NewRider {
|
||||
password?: string;
|
||||
/** The delivery region. Defaulted from the branch — see `RiderDrawer`. */
|
||||
applocationid: number;
|
||||
/**
|
||||
* Whose rider this is — one of these, never both.
|
||||
*
|
||||
* `tenantid` is a merchant's own rider; `partnerid` is a delivery partner's,
|
||||
* who serves several merchants and sits under no single one. The server
|
||||
* refuses neither and refuses both, so the two can never be confused
|
||||
* downstream in a directory or an assign picker.
|
||||
*/
|
||||
tenantid?: number;
|
||||
partnerid?: number;
|
||||
/** The branch an OWN rider works out of. Meaningless for a partner's. */
|
||||
locationid?: number;
|
||||
shiftid: number;
|
||||
identificationno?: string;
|
||||
vehiclename?: string;
|
||||
@@ -191,6 +223,9 @@ export interface RiderRosterRow {
|
||||
contactno?: string;
|
||||
email?: string;
|
||||
tenantid?: number;
|
||||
/** The branch an own rider works out of, and its name. */
|
||||
locationid?: number;
|
||||
locationname?: string;
|
||||
applocationid?: number;
|
||||
applocation?: string;
|
||||
partnerid?: number;
|
||||
@@ -213,9 +248,58 @@ export interface RiderRosterRow {
|
||||
export interface Partner {
|
||||
partnerid: number;
|
||||
partnername?: string;
|
||||
companyname?: string;
|
||||
applocationid?: number;
|
||||
primarycontact?: string;
|
||||
primaryemail?: string;
|
||||
contactno?: string;
|
||||
registrationno?: string;
|
||||
address?: string;
|
||||
suburb?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
/** One region a partner covers — a row of `partnerlocations`. */
|
||||
export interface PartnerLocation {
|
||||
partnerlocationid: number;
|
||||
partnerid: number;
|
||||
applocationid: number;
|
||||
applocation?: string;
|
||||
}
|
||||
|
||||
/** A delivery region. `applocationid=0` asks for all of them. */
|
||||
export interface AppLocation {
|
||||
applocationid: number;
|
||||
locationname?: string;
|
||||
}
|
||||
|
||||
/** Everything the console collects to onboard a delivery partner. */
|
||||
export interface NewPartner {
|
||||
partnerid?: number;
|
||||
partnername: string;
|
||||
companyname?: string;
|
||||
registrationno?: string;
|
||||
primarycontact: string;
|
||||
primaryemail?: string;
|
||||
contactno?: string;
|
||||
address?: string;
|
||||
suburb?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
postcode?: number;
|
||||
status?: string;
|
||||
/** The district they work — one, never a set. */
|
||||
applocationid: number;
|
||||
/**
|
||||
* The district by NAME, for one Nearle has not opened yet.
|
||||
*
|
||||
* Sending it opens the district: the server writes the `app_location` and
|
||||
* `app_locationconfig` rows every rider query joins through. Ignored when
|
||||
* `applocationid` is set, which is the ordinary case.
|
||||
*/
|
||||
district?: string;
|
||||
}
|
||||
|
||||
export interface RiderShift {
|
||||
@@ -238,10 +322,27 @@ export const ridersApi = {
|
||||
roster: (tenantid: number) =>
|
||||
api.list<RiderRosterRow>(`${WEB}/partners/getriderroster`, { tenantid }),
|
||||
|
||||
/** Hire one. `tenantid` travels as a param — the backend ignores it in the body. */
|
||||
/**
|
||||
* Hire one for a MERCHANT. `tenantid` travels as a param — the backend takes
|
||||
* the scope from there rather than trusting the body, so a store admin cannot
|
||||
* put a rider on another merchant's books by editing a payload.
|
||||
*/
|
||||
create: (tenantid: number, rider: NewRider) =>
|
||||
api.post<{ userid: number }>(`${WEB}/partners/createrider`, rider, { tenantid }),
|
||||
|
||||
/**
|
||||
* Hire one for a delivery PARTNER.
|
||||
*
|
||||
* Same endpoint, same rider — what differs is who they ride for. A partner
|
||||
* has no console of its own, so their riders are added by the platform.
|
||||
*/
|
||||
createForPartner: (partnerid: number, rider: NewRider) =>
|
||||
api.post<{ userid: number }>(`${WEB}/partners/createrider`, rider, { partnerid }),
|
||||
|
||||
/** A partner's riders, for the platform's directory. */
|
||||
partnerRoster: (partnerid: number) =>
|
||||
api.list<RiderRosterRow>(`${WEB}/partners/getriderroster`, { partnerid }),
|
||||
|
||||
update: (rider: NewRider & { userid: number }) =>
|
||||
api.put<unknown>(`${WEB}/partners/updaterider`, rider),
|
||||
|
||||
@@ -253,3 +354,38 @@ export const ridersApi = {
|
||||
partners: (applocationid: number) =>
|
||||
api.list<Partner>(`${WEB}/partners/getpartners`, { applocationid }),
|
||||
};
|
||||
|
||||
/**
|
||||
* Delivery partners — the companies that supply riders.
|
||||
*
|
||||
* A partner is onboarded by the platform and then ASSIGNED to merchants; a
|
||||
* merchant never creates one. That split is why `assign` lives on the tenant
|
||||
* API and not here, and why `partnerid` is kept out of the merchant-editable
|
||||
* profile allowlist on the server.
|
||||
*
|
||||
* One partner routinely serves many merchants: partner 44 supplies 48 of them
|
||||
* and partner 60 supplies 63, measured on 2026-09-09.
|
||||
*/
|
||||
export const partnersApi = {
|
||||
/** Every partner in a region. `applocationid` 0 is not accepted here. */
|
||||
list: (applocationid: number) =>
|
||||
api.list<Partner>(`${WEB}/partners/getpartners`, { applocationid }),
|
||||
|
||||
/** One partner, by id. */
|
||||
byId: (partnerid: number) =>
|
||||
api.list<Partner>(`${WEB}/partners/getpartners`, { partnerid }),
|
||||
|
||||
create: (partner: NewPartner) =>
|
||||
api.post<{ partnerid: number }>(`${WEB}/partners/createpartner`, partner),
|
||||
|
||||
/**
|
||||
* Edit a partner. Regions are REPLACED when sent and left alone when not, so
|
||||
* an edit that changes only a phone number cannot empty the list.
|
||||
*/
|
||||
update: (partner: NewPartner & { partnerid: number }) =>
|
||||
api.put<unknown>(`${WEB}/partners/updatepartner`, partner),
|
||||
|
||||
/** The regions one partner covers. */
|
||||
locations: (partnerid: number) =>
|
||||
api.list<PartnerLocation>(`${WEB}/partners/getpartnerlocations`, { partnerid }),
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Tenant and branch endpoints — the Nearle Admin's provisioning surface. */
|
||||
|
||||
import { api, WEB } from './client';
|
||||
import type { AppLocation } from './deliveries';
|
||||
import type { TenantInfo, TenantLocation } from './types';
|
||||
|
||||
/** Everything the tenant-onboarding form collects. */
|
||||
@@ -172,6 +173,19 @@ export const tenantsApi = {
|
||||
updateProfile: (body: { tenantid: number } & Partial<TenantInfo>) =>
|
||||
api.put<unknown>(`${WEB}/tenants/updatetenant`, body),
|
||||
|
||||
/**
|
||||
* Which delivery partner supplies this merchant's riders.
|
||||
*
|
||||
* Its own endpoint, not a field on `updateProfile`: `partnerid` is kept out
|
||||
* of the merchant-editable allowlist on purpose, because a merchant who could
|
||||
* set it would move themselves under another partner's riders and billing.
|
||||
*
|
||||
* `partnerid: 0` is a real instruction — it means "this merchant uses their
|
||||
* own riders" — and the server reads it as sent rather than as absent.
|
||||
*/
|
||||
assignPartner: (tenantid: number, partnerid: number) =>
|
||||
api.put<unknown>(`${WEB}/tenants/assignpartner`, { tenantid, partnerid }),
|
||||
|
||||
/**
|
||||
* One business, by id — how a store login reads its own record.
|
||||
*
|
||||
@@ -267,4 +281,14 @@ export const utilsApi = {
|
||||
* invisible to onboarding until someone edits the frontend.
|
||||
*/
|
||||
appCategories: () => api.list<AppCategory>(`${WEB}/utils/getappcategories`),
|
||||
|
||||
/**
|
||||
* The delivery regions — Coimbatore, Madurai, Nagercoil today.
|
||||
*
|
||||
* `applocationid` is REQUIRED by the handler and 0 is how you ask for all of
|
||||
* them; omitting it answers 400 "Invalid applocationid", which reads as a
|
||||
* broken request rather than a missing default.
|
||||
*/
|
||||
appLocations: (applocationid = 0) =>
|
||||
api.list<AppLocation>(`${WEB}/utils/getapplocations`, { applocationid }),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user