import type { Item } from '@sims/domain' import type { LineInput, TaxClassRow } from '@sims/billing-engine' /** POS ↔ store-server client. Same origin in production (served under /pos/). */ export interface BootstrapStore { id: string; name: string; storeCode: string; stateCode: string gstin: string; counterId: string; counterCode: string } export interface PosUser { id: string; name: string; role: string } export interface Bootstrap { store: BootstrapStore; users: PosUser[]; taxClasses: TaxClassRow[] } let token = '' async function call(path: string, init?: RequestInit): Promise { const res = await fetch(`/api${path}`, { ...init, headers: { 'content-type': 'application/json', ...(token !== '' ? { authorization: `Bearer ${token}` } : {}), }, }).catch(() => undefined) if (res === undefined) throw new Error('Store-server unreachable (E-3301)') const body = (await res.json().catch(() => ({}))) as T & { error?: string } if (!res.ok) throw new Error(body.error ?? `Server error HTTP ${res.status}`) return body } export const fetchBootstrap = (counter: string): Promise => call(`/bootstrap?counter=${encodeURIComponent(counter)}`) export async function pinLogin(userId: string, pin: string): Promise { const s = await call<{ token: string }>('/auth/pin', { method: 'POST', body: JSON.stringify({ userId, pin }), }) token = s.token } /** * In-flow supervisor gate (spec P0-2): verify a supervisor's PIN without touching * the module-level `token` — the cashier stays logged in and no session is created. * Distinct from `pinLogin` on purpose; reusing that would hijack the cashier's session. */ export const verifySupervisorPin = (userId: string, pin: string): Promise<{ ok: true; userId: string; displayName: string }> => call('/auth/verify-pin', { method: 'POST', body: JSON.stringify({ userId, pin }) }) // The counter caches the whole sellable catalog (no silent cap — R13); ?all=1 // returns every non-inactive item unpaginated. export const fetchItemsCache = (): Promise => call('/items?all=1') export const fetchStock = (): Promise<{ code: string; on_hand: number }[]> => call('/stock') export interface CommitResult { id: string; docNo: string; payablePaise: number } /** Supervisor-approved override, audited server-side in the commit transaction (spec P0-2). */ export interface BillOverride { itemId: string; kind: 'price' | 'qty' | 'discount'; before: number; after: number; approvedByUserId?: string } export const postBill = (body: { storeId: string; counterId: string; shiftId: string; customerId?: string businessDate: string; lines: LineInput[] payments: { mode: string; amountPaise: number }[] clientPayablePaise: number overrides?: BillOverride[] }): Promise => call('/bills', { method: 'POST', body: JSON.stringify(body) }) export const fetchBills = (date: string, counterId: string): Promise[]> => call(`/bills?date=${date}&counterId=${encodeURIComponent(counterId)}`) export const createDraftItem = (body: { code: string; name: string; hsn: string; taxClassCode: string unitCode: string; unitDecimals: number; salePricePaise: number barcode?: string; status: string }): Promise => call('/items', { method: 'POST', body: JSON.stringify(body) }) /** Attached-customer identity the counter needs: name plus B2B place-of-supply inputs. */ export interface PosCustomer { id: string; name: string; phone?: string; gstin?: string; stateCode?: string } export const findCustomers = (phone: string): Promise => call(`/parties?phone=${encodeURIComponent(phone)}`) export const createCustomer = (name: string, phone: string, gstin?: string): Promise => call('/parties', { method: 'POST', body: JSON.stringify({ name, phone, ...(gstin !== undefined && gstin !== '' ? { gstin } : {}) }), })