This commit is contained in:
2026-09-01 12:00:46 +05:30
parent fe78c54eeb
commit a8e0dc069f
20 changed files with 1278 additions and 32 deletions

View File

@@ -21,7 +21,7 @@
* account ends up holding a role that matches nothing.
*/
import { api, MOB, WEB } from './client';
import { api, WEB } from './client';
import type { PosRole, PosUser, StaffInfo, StaffShift } from './types';
/* ── Back-office staff ───────────────────────────────────────────────────── */
@@ -57,15 +57,33 @@ export const staffApi = {
* back-office roles and most accounts carry an id absent from it, so any
* mapping written client-side is wrong."
*
* On the MOB prefix, and that is not a choice: `getstaffs` is registered on
* `/v1/mob/tenants` only (`tenantroutes.go:35`) and has no `/web` twin, so the
* path this used to call did not exist. The old console avoided the question
* by using `users/getallusers`, whose SQL selects no `rolename` at all — it
* had to map role ids client-side, which is the thing the backend warns
* against above.
* On WEB now. It used to be on MOB because `getstaffs` was registered under
* `/v1/mob/tenants` alone and had no `/web` twin — back-office staff were
* reachable only through the customer app's door, which is a large part of
* why this console never had a people screen. The twin now exists; the MOB
* registration is left in place in case something else calls it.
*
* Returns people with NO branch as well as people with one. That is the whole
* point: `locationid` 0 means hired and not yet placed, and the list is
* ordered to put them first, because they are the rows needing an action.
*/
list: (tenantid: number) =>
api.list<StaffInfo>(`${MOB}/tenants/getstaffs`, { tenantid }),
api.list<StaffInfo>(`${WEB}/tenants/getstaffs`, { tenantid }),
/**
* Put somebody at a branch, or take them off one.
*
* `unassign` is a separate flag rather than `locationid: 0`, deliberately. A
* body that lost the field, a form that posted a blank and a client that
* dropped it all arrive as 0 — so a zero alone must never mean "take them off
* their shop". The backend refuses it too; this mirrors the rule so the
* refusal is not a round trip.
*/
assign: (body: { tenantid: number; userid: number; locationid: number }) =>
api.put<unknown>(`${WEB}/tenants/assignstaff`, body),
unassign: (body: { tenantid: number; userid: number }) =>
api.put<unknown>(`${WEB}/tenants/assignstaff`, { ...body, unassign: true }),
create: (body: CreateStaffRequest) => api.post<StaffInfo>(`${WEB}/users/create`, body),

View File

@@ -43,6 +43,21 @@ export interface CreateBranchRequest {
deliveryradius?: number;
deliverymins?: number;
status?: string;
/**
* Who will run this outlet — an existing person, when one has been hired
* already.
*
* Omitted, the backend spawns a login named after the SHOP, on the shop's
* email address, one per outlet. That was the only option, and it is why two
* people at a counter shared a credential and nothing recorded which of them
* did anything.
*
* A branch must still arrive with SOMEBODY: name a person here, or give an
* `email` to spawn one from. The backend refuses a branch with neither,
* because an outlet nobody can sign in to is a dead end that shows up in
* every list and is noticed by whoever is standing in the shop.
*/
operatorid?: number;
}
export interface TenantListQuery {
@@ -110,7 +125,11 @@ export const tenantsApi = {
api.post<TenantInfo>(`${WEB}/tenants/createtenantuser`, toTenantBody(body)),
/**
* Commissions a branch and spawns its login (roleid 0, empty password).
* Commissions a branch, and gives it somebody to run it.
*
* Pass `operatorid` to place a person you have already hired. Without it the
* backend spawns a login named after the shop, as it always did — kept so
* nothing existing changes, but the named person is the better path.
*
* `createtenantlocation`, not `createlocation`: only this one returns the
* created row, and the new `locationid` is what a QR code and every
@@ -122,6 +141,48 @@ export const tenantsApi = {
updateBranch: (body: Partial<TenantLocation> & { locationid: number }) =>
api.put<TenantLocation>(`${WEB}/tenants/updatelocation`, body),
/**
* A merchant editing their own business record.
*
* The first write path `tenants` has ever had. Before it, everything about a
* shop — its name, its photograph, its licence, how to reach it — was set
* once at onboarding by a Nearle Admin and could never be changed by anyone.
*
* Only merchant-owned columns are written; the backend keeps the allowlist
* and ignores the rest, so `approved`, `status`, `partnerid` and the billing
* fields cannot be set from here even if a caller sends them. Anything
* omitted is left alone rather than blanked.
*/
updateProfile: (body: { tenantid: number } & Partial<TenantInfo>) =>
api.put<unknown>(`${WEB}/tenants/updatetenant`, body),
/**
* One business, by id — how a store login reads its own record.
*
* Not `listAll`. That is `getalltenants`, paginated over 262 merchants, so a
* shop on page two was simply absent and a profile screen built on it would
* show nothing for no visible reason.
*/
byId: (tenantid: number) => api.get<TenantInfo>(`${WEB}/tenants/gettenantinfo`, { tenantid }),
/**
* Somebody editing their own name, mobile or email.
*
* Not `users/update`. That one writes whatever struct it is handed and checks
* only the userid — no tenant, no guard on role or branch — so a self-service
* form built on it would let a branch user promote themselves or move shop.
* This is scoped to the caller's own account AND business, and writes
* identity fields only.
*/
updateOwnProfile: (body: {
userid: number;
tenantid: number;
firstname?: string;
lastname?: string;
contactno?: string;
email?: string;
}) => api.put<unknown>(`${WEB}/tenants/updateownprofile`, body),
};
/**

View File

@@ -93,6 +93,14 @@ export interface TenantInfo {
longitude?: string;
tenantimage?: string;
tenantinfo?: string;
/**
* The FSSAI or trade licence.
*
* Returned by the customer-facing tenant reads and displayed to shoppers,
* and across 200 merchants not one had it filled in. For a food business in
* India it is a display requirement, not a nicety.
*/
licenseno?: string;
partnerid?: number;
minorder?: number;
/** Misspelled on the wire — `applolcationid`, not `applocationid`. */