@withpotter/lib 0.13.0-next.103 → 0.13.0-next.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin-pos-api.d.ts +23 -1
- package/dist/admin-pos-api.js +24 -0
- package/dist/admin-pos-api.js.map +1 -1
- package/dist/admin-pos-olive.d.ts +44 -0
- package/dist/admin-pos-olive.js +93 -0
- package/dist/admin-pos-olive.js.map +1 -0
- package/dist/admin-pos-types.d.ts +43 -0
- package/package.json +1 -1
package/dist/admin-pos-api.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ShiftFilters, type TerminalFilters } from './admin-pos-query';
|
|
2
|
-
import type { PosPage, PosShift, PosSummary, PosTerminal, PosTerminalDetail, PosTerminalHistory, PosWindow, TenantPos } from './admin-pos-types';
|
|
2
|
+
import type { PosPage, PosPaymentsPage, PosRosterEntry, PosTillRequest, PosShift, PosSummary, PosTerminal, PosTerminalDetail, PosTerminalHistory, PosWindow, TenantPos } from './admin-pos-types';
|
|
3
3
|
export type TerminalAct = 'suspend' | 'resume' | 'revoke';
|
|
4
4
|
export declare const adminPosApi: {
|
|
5
5
|
summary: (w?: PosWindow) => Promise<PosSummary>;
|
|
@@ -9,6 +9,28 @@ export declare const adminPosApi: {
|
|
|
9
9
|
history: (terminalId: string) => Promise<PosTerminalHistory>;
|
|
10
10
|
shifts: (filters?: ShiftFilters) => Promise<PosPage<PosShift>>;
|
|
11
11
|
tenant: (tenantId: string, w?: PosWindow) => Promise<TenantPos>;
|
|
12
|
+
/** A business's whole till roster, removed people marked. */
|
|
13
|
+
roster: (tenantId: string) => Promise<PosRosterEntry[]>;
|
|
14
|
+
/** A business's requests for terminals, newest first. */
|
|
15
|
+
tillRequests: (tenantId: string) => Promise<PosTillRequest[]>;
|
|
16
|
+
/** Card payments, newest first, one business when given; `cursor` is the last page's `nextCursor`. */
|
|
17
|
+
payments: (filters?: {
|
|
18
|
+
tenantId?: string;
|
|
19
|
+
deviceId?: string;
|
|
20
|
+
cursor?: string;
|
|
21
|
+
limit?: number;
|
|
22
|
+
}) => Promise<PosPaymentsPage>;
|
|
23
|
+
/**
|
|
24
|
+
* What the business sees from Olive, read live under its own Olive login. Read-only; each call is
|
|
25
|
+
* written to the admin audit log. The bodies are Olive's own, so they go through `admin-pos-olive`.
|
|
26
|
+
* A 400 says the business has not connected Olive.
|
|
27
|
+
*/
|
|
28
|
+
olive: {
|
|
29
|
+
account: (tenantId: string) => Promise<unknown>;
|
|
30
|
+
accountTransactions: (tenantId: string, query?: Record<string, string>) => Promise<unknown>;
|
|
31
|
+
outlets: (tenantId: string) => Promise<unknown>;
|
|
32
|
+
outletTerminals: (tenantId: string, outletId: string) => Promise<unknown>;
|
|
33
|
+
};
|
|
12
34
|
/** 409 when the terminal's status no longer allows it. */
|
|
13
35
|
setTerminalStatus: (terminalId: string, act: TerminalAct, reason: string) => Promise<PosTerminalDetail>;
|
|
14
36
|
/** Signs the shift out at its till. 409 when it is no longer open. */
|
package/dist/admin-pos-api.js
CHANGED
|
@@ -8,6 +8,7 @@ const BASE = "/platform-admin/v1/pos";
|
|
|
8
8
|
const id = (value) => encodeURIComponent(value);
|
|
9
9
|
const post = (path, body) => apiFetch(path, { method: "POST", body: JSON.stringify(body) });
|
|
10
10
|
const windowQuery = (w = {}) => toQuery({ from: w.from, to: w.to });
|
|
11
|
+
const tenantBase = (tenantId) => `/platform-admin/v1/tenants/${id(tenantId)}/pos`;
|
|
11
12
|
const adminPosApi = {
|
|
12
13
|
summary: (w) => apiFetch(`${BASE}/summary${windowQuery(w)}`),
|
|
13
14
|
terminals: (filters) => apiFetch(
|
|
@@ -22,6 +23,29 @@ const adminPosApi = {
|
|
|
22
23
|
tenant: (tenantId, w) => apiFetch(
|
|
23
24
|
`/platform-admin/v1/tenants/${id(tenantId)}/pos${windowQuery(w)}`
|
|
24
25
|
),
|
|
26
|
+
/** A business's whole till roster, removed people marked. */
|
|
27
|
+
roster: (tenantId) => apiFetch(`${tenantBase(tenantId)}/operators`),
|
|
28
|
+
/** A business's requests for terminals, newest first. */
|
|
29
|
+
tillRequests: (tenantId) => apiFetch(
|
|
30
|
+
`/platform-admin/v1/till-requests${toQuery({ tenantId })}`
|
|
31
|
+
),
|
|
32
|
+
/** Card payments, newest first, one business when given; `cursor` is the last page's `nextCursor`. */
|
|
33
|
+
payments: (filters = {}) => apiFetch(`${BASE}/payments${toQuery(filters)}`),
|
|
34
|
+
/**
|
|
35
|
+
* What the business sees from Olive, read live under its own Olive login. Read-only; each call is
|
|
36
|
+
* written to the admin audit log. The bodies are Olive's own, so they go through `admin-pos-olive`.
|
|
37
|
+
* A 400 says the business has not connected Olive.
|
|
38
|
+
*/
|
|
39
|
+
olive: {
|
|
40
|
+
account: (tenantId) => apiFetch(`${tenantBase(tenantId)}/olive/account`),
|
|
41
|
+
accountTransactions: (tenantId, query = {}) => apiFetch(
|
|
42
|
+
`${tenantBase(tenantId)}/olive/account/transactions${toQuery(query)}`
|
|
43
|
+
),
|
|
44
|
+
outlets: (tenantId) => apiFetch(`${tenantBase(tenantId)}/olive/outlets`),
|
|
45
|
+
outletTerminals: (tenantId, outletId) => apiFetch(
|
|
46
|
+
`${tenantBase(tenantId)}/olive/outlets/${id(outletId)}/terminals`
|
|
47
|
+
)
|
|
48
|
+
},
|
|
25
49
|
/** 409 when the terminal's status no longer allows it. */
|
|
26
50
|
setTerminalStatus: (terminalId, act, reason) => post(`${BASE}/terminals/${id(terminalId)}/${act}`, {
|
|
27
51
|
reason
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/admin-pos-api.ts"],"sourcesContent":["import { apiFetch } from './api';\nimport {\n shiftsQuery,\n terminalsQuery,\n toQuery,\n type ShiftFilters,\n type TerminalFilters,\n} from './admin-pos-query';\nimport type {\n PosPage,\n PosShift,\n PosSummary,\n PosTerminal,\n PosTerminalDetail,\n PosTerminalHistory,\n PosWindow,\n TenantPos,\n} from './admin-pos-types';\n\n/**\n * Terminals and till shifts across every business, as the platform desk\n * works them. Same `apiFetch` and cookie session as the rest of the console;\n * reads need `pos.read`, every action `pos.manage` and a reason. Shapes:\n * `admin-pos-types.ts`.\n */\n\nconst BASE = '/platform-admin/v1/pos';\n\nconst id = (value: string) => encodeURIComponent(value);\n\nconst post = <T>(path: string, body: unknown) =>\n apiFetch<T>(path, { method: 'POST', body: JSON.stringify(body) });\n\nconst windowQuery = (w: PosWindow = {}) => toQuery({ from: w.from, to: w.to });\n\nexport type TerminalAct = 'suspend' | 'resume' | 'revoke';\n\nexport const adminPosApi = {\n summary: (w?: PosWindow) =>\n apiFetch<PosSummary>(`${BASE}/summary${windowQuery(w)}`),\n\n terminals: (filters?: TerminalFilters) =>\n apiFetch<PosPage<PosTerminal>>(\n `${BASE}/terminals${terminalsQuery(filters)}`,\n ),\n\n /** 404 when there is no such terminal. */\n terminal: (terminalId: string, w?: PosWindow) =>\n apiFetch<PosTerminalDetail>(\n `${BASE}/terminals/${id(terminalId)}${windowQuery(w)}`,\n ),\n\n history: (terminalId: string) =>\n apiFetch<PosTerminalHistory>(`${BASE}/terminals/${id(terminalId)}/history`),\n\n shifts: (filters?: ShiftFilters) =>\n apiFetch<PosPage<PosShift>>(`${BASE}/shifts${shiftsQuery(filters)}`),\n\n tenant: (tenantId: string, w?: PosWindow) =>\n apiFetch<TenantPos>(\n `/platform-admin/v1/tenants/${id(tenantId)}/pos${windowQuery(w)}`,\n ),\n\n /** 409 when the terminal's status no longer allows it. */\n setTerminalStatus: (terminalId: string, act: TerminalAct, reason: string) =>\n post<PosTerminalDetail>(`${BASE}/terminals/${id(terminalId)}/${act}`, {\n reason,\n }),\n\n /** Signs the shift out at its till. 409 when it is no longer open. */\n closeShift: (shiftId: string, reason: string) =>\n post<PosShift>(`${BASE}/shifts/${id(shiftId)}/close`, { reason }),\n};\n"],"mappings":"AAAA,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;
|
|
1
|
+
{"version":3,"sources":["../src/admin-pos-api.ts"],"sourcesContent":["import { apiFetch } from './api';\nimport {\n shiftsQuery,\n terminalsQuery,\n toQuery,\n type ShiftFilters,\n type TerminalFilters,\n} from './admin-pos-query';\nimport type {\n PosPage,\n PosPaymentsPage,\n PosRosterEntry,\n PosTillRequest,\n PosShift,\n PosSummary,\n PosTerminal,\n PosTerminalDetail,\n PosTerminalHistory,\n PosWindow,\n TenantPos,\n} from './admin-pos-types';\n\n/**\n * Terminals and till shifts across every business, as the platform desk\n * works them. Same `apiFetch` and cookie session as the rest of the console;\n * reads need `pos.read`, every action `pos.manage` and a reason. Shapes:\n * `admin-pos-types.ts`.\n */\n\nconst BASE = '/platform-admin/v1/pos';\n\nconst id = (value: string) => encodeURIComponent(value);\n\nconst post = <T>(path: string, body: unknown) =>\n apiFetch<T>(path, { method: 'POST', body: JSON.stringify(body) });\n\nconst windowQuery = (w: PosWindow = {}) => toQuery({ from: w.from, to: w.to });\n\nconst tenantBase = (tenantId: string) =>\n `/platform-admin/v1/tenants/${id(tenantId)}/pos`;\n\nexport type TerminalAct = 'suspend' | 'resume' | 'revoke';\n\nexport const adminPosApi = {\n summary: (w?: PosWindow) =>\n apiFetch<PosSummary>(`${BASE}/summary${windowQuery(w)}`),\n\n terminals: (filters?: TerminalFilters) =>\n apiFetch<PosPage<PosTerminal>>(\n `${BASE}/terminals${terminalsQuery(filters)}`,\n ),\n\n /** 404 when there is no such terminal. */\n terminal: (terminalId: string, w?: PosWindow) =>\n apiFetch<PosTerminalDetail>(\n `${BASE}/terminals/${id(terminalId)}${windowQuery(w)}`,\n ),\n\n history: (terminalId: string) =>\n apiFetch<PosTerminalHistory>(`${BASE}/terminals/${id(terminalId)}/history`),\n\n shifts: (filters?: ShiftFilters) =>\n apiFetch<PosPage<PosShift>>(`${BASE}/shifts${shiftsQuery(filters)}`),\n\n tenant: (tenantId: string, w?: PosWindow) =>\n apiFetch<TenantPos>(\n `/platform-admin/v1/tenants/${id(tenantId)}/pos${windowQuery(w)}`,\n ),\n\n /** A business's whole till roster, removed people marked. */\n roster: (tenantId: string) =>\n apiFetch<PosRosterEntry[]>(`${tenantBase(tenantId)}/operators`),\n\n /** A business's requests for terminals, newest first. */\n tillRequests: (tenantId: string) =>\n apiFetch<PosTillRequest[]>(\n `/platform-admin/v1/till-requests${toQuery({ tenantId })}`,\n ),\n\n /** Card payments, newest first, one business when given; `cursor` is the last page's `nextCursor`. */\n payments: (\n filters: {\n tenantId?: string;\n deviceId?: string;\n cursor?: string;\n limit?: number;\n } = {},\n ) => apiFetch<PosPaymentsPage>(`${BASE}/payments${toQuery(filters)}`),\n\n /**\n * What the business sees from Olive, read live under its own Olive login. Read-only; each call is\n * written to the admin audit log. The bodies are Olive's own, so they go through `admin-pos-olive`.\n * A 400 says the business has not connected Olive.\n */\n olive: {\n account: (tenantId: string) =>\n apiFetch<unknown>(`${tenantBase(tenantId)}/olive/account`),\n accountTransactions: (\n tenantId: string,\n query: Record<string, string> = {},\n ) =>\n apiFetch<unknown>(\n `${tenantBase(tenantId)}/olive/account/transactions${toQuery(query)}`,\n ),\n outlets: (tenantId: string) =>\n apiFetch<unknown>(`${tenantBase(tenantId)}/olive/outlets`),\n outletTerminals: (tenantId: string, outletId: string) =>\n apiFetch<unknown>(\n `${tenantBase(tenantId)}/olive/outlets/${id(outletId)}/terminals`,\n ),\n },\n\n /** 409 when the terminal's status no longer allows it. */\n setTerminalStatus: (terminalId: string, act: TerminalAct, reason: string) =>\n post<PosTerminalDetail>(`${BASE}/terminals/${id(terminalId)}/${act}`, {\n reason,\n }),\n\n /** Signs the shift out at its till. 409 when it is no longer open. */\n closeShift: (shiftId: string, reason: string) =>\n post<PosShift>(`${BASE}/shifts/${id(shiftId)}/close`, { reason }),\n};\n"],"mappings":"AAAA,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAsBP,MAAM,OAAO;AAEb,MAAM,KAAK,CAAC,UAAkB,mBAAmB,KAAK;AAEtD,MAAM,OAAO,CAAI,MAAc,SAC7B,SAAY,MAAM,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,IAAI,EAAE,CAAC;AAElE,MAAM,cAAc,CAAC,IAAe,CAAC,MAAM,QAAQ,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,GAAG,CAAC;AAE7E,MAAM,aAAa,CAAC,aAClB,8BAA8B,GAAG,QAAQ,CAAC;AAIrC,MAAM,cAAc;AAAA,EACzB,SAAS,CAAC,MACR,SAAqB,GAAG,IAAI,WAAW,YAAY,CAAC,CAAC,EAAE;AAAA,EAEzD,WAAW,CAAC,YACV;AAAA,IACE,GAAG,IAAI,aAAa,eAAe,OAAO,CAAC;AAAA,EAC7C;AAAA;AAAA,EAGF,UAAU,CAAC,YAAoB,MAC7B;AAAA,IACE,GAAG,IAAI,cAAc,GAAG,UAAU,CAAC,GAAG,YAAY,CAAC,CAAC;AAAA,EACtD;AAAA,EAEF,SAAS,CAAC,eACR,SAA6B,GAAG,IAAI,cAAc,GAAG,UAAU,CAAC,UAAU;AAAA,EAE5E,QAAQ,CAAC,YACP,SAA4B,GAAG,IAAI,UAAU,YAAY,OAAO,CAAC,EAAE;AAAA,EAErE,QAAQ,CAAC,UAAkB,MACzB;AAAA,IACE,8BAA8B,GAAG,QAAQ,CAAC,OAAO,YAAY,CAAC,CAAC;AAAA,EACjE;AAAA;AAAA,EAGF,QAAQ,CAAC,aACP,SAA2B,GAAG,WAAW,QAAQ,CAAC,YAAY;AAAA;AAAA,EAGhE,cAAc,CAAC,aACb;AAAA,IACE,mCAAmC,QAAQ,EAAE,SAAS,CAAC,CAAC;AAAA,EAC1D;AAAA;AAAA,EAGF,UAAU,CACR,UAKI,CAAC,MACF,SAA0B,GAAG,IAAI,YAAY,QAAQ,OAAO,CAAC,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOpE,OAAO;AAAA,IACL,SAAS,CAAC,aACR,SAAkB,GAAG,WAAW,QAAQ,CAAC,gBAAgB;AAAA,IAC3D,qBAAqB,CACnB,UACA,QAAgC,CAAC,MAEjC;AAAA,MACE,GAAG,WAAW,QAAQ,CAAC,8BAA8B,QAAQ,KAAK,CAAC;AAAA,IACrE;AAAA,IACF,SAAS,CAAC,aACR,SAAkB,GAAG,WAAW,QAAQ,CAAC,gBAAgB;AAAA,IAC3D,iBAAiB,CAAC,UAAkB,aAClC;AAAA,MACE,GAAG,WAAW,QAAQ,CAAC,kBAAkB,GAAG,QAAQ,CAAC;AAAA,IACvD;AAAA,EACJ;AAAA;AAAA,EAGA,mBAAmB,CAAC,YAAoB,KAAkB,WACxD,KAAwB,GAAG,IAAI,cAAc,GAAG,UAAU,CAAC,IAAI,GAAG,IAAI;AAAA,IACpE;AAAA,EACF,CAAC;AAAA;AAAA,EAGH,YAAY,CAAC,SAAiB,WAC5B,KAAe,GAAG,IAAI,WAAW,GAAG,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC;AACpE;","names":[]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Olive's merchant API answers in its own shapes, which Box does not own, so these read them the way the
|
|
3
|
+
* merchant's own POS page does: tolerant of a `data` wrapper, of a list under one of several names, and of
|
|
4
|
+
* a field under one of several keys. Pure, so the guesses are tested rather than discovered on a live account.
|
|
5
|
+
*/
|
|
6
|
+
export type OliveResource = Record<string, unknown>;
|
|
7
|
+
/** The object an answer carries: its `data` when that is an object, else the answer itself. */
|
|
8
|
+
export declare function oliveObject(body: unknown): OliveResource;
|
|
9
|
+
/** The list an answer carries, wherever Olive put it; empty when there is none. */
|
|
10
|
+
export declare function oliveList(body: unknown): OliveResource[];
|
|
11
|
+
/** The first of these keys that holds text or a number, as text; null when none does. */
|
|
12
|
+
export declare function oliveText(r: OliveResource, ...keys: string[]): string | null;
|
|
13
|
+
export interface OliveOutlet {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
address: string | null;
|
|
17
|
+
contact: string | null;
|
|
18
|
+
terminalCount: number | null;
|
|
19
|
+
active: boolean;
|
|
20
|
+
createdAt: string | null;
|
|
21
|
+
}
|
|
22
|
+
/** An outlet row; an entry with no id is left out, as the merchant's page does. */
|
|
23
|
+
export declare function oliveOutlets(body: unknown): OliveOutlet[];
|
|
24
|
+
export interface OliveBalance {
|
|
25
|
+
/** The balance as Olive sent it, in kobo (minor units); null when the answer has none. */
|
|
26
|
+
balanceMinor: number | null;
|
|
27
|
+
accountNumber: string | null;
|
|
28
|
+
accountName: string | null;
|
|
29
|
+
}
|
|
30
|
+
export declare function oliveBalance(body: unknown): OliveBalance;
|
|
31
|
+
/** Whether a ledger line is money in or out, by the words Olive uses; null when it says neither. */
|
|
32
|
+
export declare function directionOf(t: OliveResource): 'in' | 'out' | null;
|
|
33
|
+
export interface OliveTransaction {
|
|
34
|
+
id: string;
|
|
35
|
+
at: string | null;
|
|
36
|
+
summary: string;
|
|
37
|
+
/** Kobo (minor units), as Olive sends it. */
|
|
38
|
+
amountMinor: number | null;
|
|
39
|
+
direction: 'in' | 'out' | null;
|
|
40
|
+
status: string | null;
|
|
41
|
+
reference: string | null;
|
|
42
|
+
}
|
|
43
|
+
/** Account ledger lines, in the order Olive sent them. Amounts are kobo. */
|
|
44
|
+
export declare function oliveTransactions(body: unknown): OliveTransaction[];
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const isObject = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
|
|
2
|
+
const LIST_KEYS = [
|
|
3
|
+
"items",
|
|
4
|
+
"results",
|
|
5
|
+
"transactions",
|
|
6
|
+
"branches",
|
|
7
|
+
"terminals",
|
|
8
|
+
"data"
|
|
9
|
+
];
|
|
10
|
+
function oliveObject(body) {
|
|
11
|
+
if (!isObject(body)) return {};
|
|
12
|
+
return isObject(body.data) ? body.data : body;
|
|
13
|
+
}
|
|
14
|
+
function oliveList(body) {
|
|
15
|
+
if (Array.isArray(body)) return body.filter(isObject);
|
|
16
|
+
const root = oliveObject(body);
|
|
17
|
+
for (const key of LIST_KEYS) {
|
|
18
|
+
const found = root[key];
|
|
19
|
+
if (Array.isArray(found)) return found.filter(isObject);
|
|
20
|
+
}
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
function oliveText(r, ...keys) {
|
|
24
|
+
for (const key of keys) {
|
|
25
|
+
const v = r[key];
|
|
26
|
+
if (typeof v === "number") return String(v);
|
|
27
|
+
if (typeof v === "string" && v.trim()) return v.trim();
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function oliveOutlets(body) {
|
|
32
|
+
return oliveList(body).flatMap((o, i) => {
|
|
33
|
+
const id = oliveText(o, "branchId", "id");
|
|
34
|
+
if (!id) return [];
|
|
35
|
+
const contact = [
|
|
36
|
+
oliveText(o, "contactFullName"),
|
|
37
|
+
oliveText(o, "contactNumber")
|
|
38
|
+
].filter(Boolean).join(" \xB7 ");
|
|
39
|
+
const count = Number(o.terminalCount);
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
id,
|
|
43
|
+
name: oliveText(o, "name", "branchName", "outletName", "label") ?? `Outlet ${i + 1}`,
|
|
44
|
+
address: oliveText(o, "address"),
|
|
45
|
+
contact: contact || null,
|
|
46
|
+
terminalCount: o.terminalCount == null || !Number.isFinite(count) ? null : count,
|
|
47
|
+
active: o.isActive !== false,
|
|
48
|
+
createdAt: oliveText(o, "createdAt")
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function oliveBalance(body) {
|
|
54
|
+
const a = oliveObject(body);
|
|
55
|
+
const raw = a.availableBalance ?? a.balance;
|
|
56
|
+
const n = typeof raw === "string" && raw.trim() === "" ? Number.NaN : Number(raw);
|
|
57
|
+
return {
|
|
58
|
+
balanceMinor: raw == null || !Number.isFinite(n) ? null : n,
|
|
59
|
+
accountNumber: oliveText(a, "accountNumber"),
|
|
60
|
+
accountName: oliveText(a, "accountName", "name")
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function directionOf(t) {
|
|
64
|
+
const raw = (oliveText(t, "direction", "type", "transactionType", "entryType") ?? "").toLowerCase();
|
|
65
|
+
if (/\b(credit|inflow|deposit|received|collection)\b/.test(raw)) return "in";
|
|
66
|
+
if (/\b(debit|outflow|withdrawal|payout|transfer)\b/.test(raw)) return "out";
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
function oliveTransactions(body) {
|
|
70
|
+
return oliveList(body).map((t, i) => {
|
|
71
|
+
const raw = t.amount ?? t.transactionAmount;
|
|
72
|
+
const n = Number(raw);
|
|
73
|
+
return {
|
|
74
|
+
id: oliveText(t, "id", "transactionId", "reference") ?? `row-${i}`,
|
|
75
|
+
at: oliveText(t, "createdAt", "transactionDate", "date"),
|
|
76
|
+
summary: oliveText(t, "narration", "description", "type") ?? "Transaction",
|
|
77
|
+
amountMinor: raw == null || raw === "" || !Number.isFinite(n) ? null : n,
|
|
78
|
+
direction: directionOf(t),
|
|
79
|
+
status: oliveText(t, "status"),
|
|
80
|
+
reference: oliveText(t, "reference", "transactionReference")
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
directionOf,
|
|
86
|
+
oliveBalance,
|
|
87
|
+
oliveList,
|
|
88
|
+
oliveObject,
|
|
89
|
+
oliveOutlets,
|
|
90
|
+
oliveText,
|
|
91
|
+
oliveTransactions
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=admin-pos-olive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/admin-pos-olive.ts"],"sourcesContent":["/**\n * Olive's merchant API answers in its own shapes, which Box does not own, so these read them the way the\n * merchant's own POS page does: tolerant of a `data` wrapper, of a list under one of several names, and of\n * a field under one of several keys. Pure, so the guesses are tested rather than discovered on a live account.\n */\n\nexport type OliveResource = Record<string, unknown>;\n\nconst isObject = (v: unknown): v is OliveResource =>\n Boolean(v) && typeof v === 'object' && !Array.isArray(v);\n\nconst LIST_KEYS = [\n 'items',\n 'results',\n 'transactions',\n 'branches',\n 'terminals',\n 'data',\n];\n\n/** The object an answer carries: its `data` when that is an object, else the answer itself. */\nexport function oliveObject(body: unknown): OliveResource {\n if (!isObject(body)) return {};\n\n return isObject(body.data) ? body.data : body;\n}\n\n/** The list an answer carries, wherever Olive put it; empty when there is none. */\nexport function oliveList(body: unknown): OliveResource[] {\n if (Array.isArray(body)) return body.filter(isObject);\n\n const root = oliveObject(body);\n\n for (const key of LIST_KEYS) {\n const found = root[key];\n\n if (Array.isArray(found)) return found.filter(isObject);\n }\n\n return [];\n}\n\n/** The first of these keys that holds text or a number, as text; null when none does. */\nexport function oliveText(r: OliveResource, ...keys: string[]): string | null {\n for (const key of keys) {\n const v = r[key];\n\n if (typeof v === 'number') return String(v);\n\n if (typeof v === 'string' && v.trim()) return v.trim();\n }\n\n return null;\n}\n\nexport interface OliveOutlet {\n id: string;\n name: string;\n address: string | null;\n contact: string | null;\n terminalCount: number | null;\n active: boolean;\n createdAt: string | null;\n}\n\n/** An outlet row; an entry with no id is left out, as the merchant's page does. */\nexport function oliveOutlets(body: unknown): OliveOutlet[] {\n return oliveList(body).flatMap((o, i) => {\n const id = oliveText(o, 'branchId', 'id');\n\n if (!id) return [];\n\n const contact = [\n oliveText(o, 'contactFullName'),\n oliveText(o, 'contactNumber'),\n ]\n .filter(Boolean)\n .join(' · ');\n const count = Number(o.terminalCount);\n\n return [\n {\n id,\n name:\n oliveText(o, 'name', 'branchName', 'outletName', 'label') ??\n `Outlet ${i + 1}`,\n address: oliveText(o, 'address'),\n contact: contact || null,\n terminalCount:\n o.terminalCount == null || !Number.isFinite(count) ? null : count,\n active: o.isActive !== false,\n createdAt: oliveText(o, 'createdAt'),\n },\n ];\n });\n}\n\nexport interface OliveBalance {\n /** The balance as Olive sent it, in kobo (minor units); null when the answer has none. */\n balanceMinor: number | null;\n accountNumber: string | null;\n accountName: string | null;\n}\n\nexport function oliveBalance(body: unknown): OliveBalance {\n const a = oliveObject(body);\n const raw = a.availableBalance ?? a.balance;\n const n =\n typeof raw === 'string' && raw.trim() === '' ? Number.NaN : Number(raw);\n\n return {\n balanceMinor: raw == null || !Number.isFinite(n) ? null : n,\n accountNumber: oliveText(a, 'accountNumber'),\n accountName: oliveText(a, 'accountName', 'name'),\n };\n}\n\n/** Whether a ledger line is money in or out, by the words Olive uses; null when it says neither. */\nexport function directionOf(t: OliveResource): 'in' | 'out' | null {\n const raw = (\n oliveText(t, 'direction', 'type', 'transactionType', 'entryType') ?? ''\n ).toLowerCase();\n\n if (/\\b(credit|inflow|deposit|received|collection)\\b/.test(raw)) return 'in';\n\n if (/\\b(debit|outflow|withdrawal|payout|transfer)\\b/.test(raw)) return 'out';\n\n return null;\n}\n\nexport interface OliveTransaction {\n id: string;\n at: string | null;\n summary: string;\n /** Kobo (minor units), as Olive sends it. */\n amountMinor: number | null;\n direction: 'in' | 'out' | null;\n status: string | null;\n reference: string | null;\n}\n\n/** Account ledger lines, in the order Olive sent them. Amounts are kobo. */\nexport function oliveTransactions(body: unknown): OliveTransaction[] {\n return oliveList(body).map((t, i) => {\n const raw = t.amount ?? t.transactionAmount;\n const n = Number(raw);\n\n return {\n id: oliveText(t, 'id', 'transactionId', 'reference') ?? `row-${i}`,\n at: oliveText(t, 'createdAt', 'transactionDate', 'date'),\n summary:\n oliveText(t, 'narration', 'description', 'type') ?? 'Transaction',\n amountMinor: raw == null || raw === '' || !Number.isFinite(n) ? null : n,\n direction: directionOf(t),\n status: oliveText(t, 'status'),\n reference: oliveText(t, 'reference', 'transactionReference'),\n };\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -120,3 +120,46 @@ export interface PosWindow {
|
|
|
120
120
|
from?: string;
|
|
121
121
|
to?: string;
|
|
122
122
|
}
|
|
123
|
+
/** One person on a business's till roster (`GET /tenants/:id/pos/operators`). */
|
|
124
|
+
export interface PosRosterEntry {
|
|
125
|
+
id: string;
|
|
126
|
+
/** The name they sign in with at the till. */
|
|
127
|
+
signIn: string;
|
|
128
|
+
name: string;
|
|
129
|
+
role: string;
|
|
130
|
+
active: boolean;
|
|
131
|
+
shiftLimitMinutes: number;
|
|
132
|
+
updatedAt: string;
|
|
133
|
+
}
|
|
134
|
+
export type TillRequestStatus = 'pending' | 'approved' | 'rejected' | 'fulfilled';
|
|
135
|
+
/** A request for terminals (`GET /till-requests?tenantId=`). */
|
|
136
|
+
export interface PosTillRequest {
|
|
137
|
+
id: string;
|
|
138
|
+
tenantId: string;
|
|
139
|
+
quantity: number;
|
|
140
|
+
note: string | null;
|
|
141
|
+
status: TillRequestStatus;
|
|
142
|
+
decidedBy: string | null;
|
|
143
|
+
decisionNote: string | null;
|
|
144
|
+
decidedAt: string | null;
|
|
145
|
+
/** The system that decides it (`olive`), or null when an admin does. */
|
|
146
|
+
source: string | null;
|
|
147
|
+
createdAt: string;
|
|
148
|
+
}
|
|
149
|
+
/** One card payment (`GET /pos/payments`). `amountMinor` is in the currency's minor units. */
|
|
150
|
+
export interface PosPayment {
|
|
151
|
+
id: string;
|
|
152
|
+
tenantId: string;
|
|
153
|
+
deviceId: string | null;
|
|
154
|
+
deviceLabel: string | null;
|
|
155
|
+
direction: 'in' | 'out';
|
|
156
|
+
amountMinor: string;
|
|
157
|
+
currency: string;
|
|
158
|
+
status: string;
|
|
159
|
+
reference: string | null;
|
|
160
|
+
at: string;
|
|
161
|
+
}
|
|
162
|
+
export interface PosPaymentsPage {
|
|
163
|
+
rows: PosPayment[];
|
|
164
|
+
nextCursor: string | null;
|
|
165
|
+
}
|
package/package.json
CHANGED