/** * Hospitality Service API client — Torbat Food (real backend only). * Browser → /api/hospitality BFF; SSR → direct upstream :8009 */ import { getValidAccessToken, refreshSession } from "@/lib/auth"; import type { CapabilitiesResponse, HealthResponse, ListParams } from "@/modules/hospitality/types"; const HOSPITALITY_BASE = typeof window !== "undefined" ? "/api/hospitality" : process.env.HOSPITALITY_SERVICE_URL || process.env.NEXT_PUBLIC_HOSPITALITY_API_URL || "http://localhost:8009"; export class HospitalityApiError extends Error { constructor( public status: number, public code: string, message: string ) { super(message); this.name = "HospitalityApiError"; } } type RequestOptions = RequestInit & { tenantId: string; auth?: boolean }; async function request(path: string, options: RequestOptions): Promise { const { tenantId, auth = true, headers: customHeaders, ...init } = options; const headers = new Headers(customHeaders); if (!headers.has("Content-Type") && init.body) { headers.set("Content-Type", "application/json"); } headers.set("X-Tenant-ID", tenantId); if (auth) { const token = await getValidAccessToken(); if (token) headers.set("Authorization", `Bearer ${token}`); } const url = path.startsWith("http") ? path : `${HOSPITALITY_BASE.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`; let res: Response; try { res = await fetch(url, { ...init, headers }); } catch (err) { const detail = err instanceof Error ? err.message : "network_error"; throw new Error(`اتصال به سرویس تربت فود برقرار نشد (${url}) — ${detail}`); } if (res.status === 401 && auth) { const refreshed = await refreshSession({ force: true }); if (refreshed) { headers.set("Authorization", `Bearer ${refreshed}`); res = await fetch(url, { ...init, headers }); } } if (!res.ok) { const body = await res.json().catch(() => ({})); throw new HospitalityApiError( res.status, body?.error?.code || body?.detail || "unknown_error", body?.error?.message || body?.detail || res.statusText ); } if (res.status === 204) return undefined as T; return res.json() as Promise; } function qs(params?: Record) { if (!params) return ""; const sp = new URLSearchParams(); Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== "") sp.set(k, String(v)); }); const s = sp.toString(); return s ? `?${s}` : ""; } function action(tenantId: string, apiPath: string, body?: Record) { return request>(apiPath, { tenantId, method: "POST", body: body ? JSON.stringify(body) : undefined, }); } export const hospitalityApi = { health: (): Promise => fetch(`${HOSPITALITY_BASE}/health`).then((r) => r.json()), capabilities: (): Promise => fetch(`${HOSPITALITY_BASE}/capabilities`).then((r) => r.json()), venues: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/venues${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/venues/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/venues`, { tenantId, method: "POST", body: JSON.stringify(body), }), update: (tenantId: string, id: string, body: Record) => request>(`/api/v1/venues/${id}`, { tenantId, method: "PATCH", body: JSON.stringify(body), }), remove: (tenantId: string, id: string) => request>(`/api/v1/venues/${id}/delete`, { tenantId, method: "POST" }), }, branches: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/branches${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/branches/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/branches`, { tenantId, method: "POST", body: JSON.stringify(body), }), update: (tenantId: string, id: string, body: Record) => request>(`/api/v1/branches/${id}`, { tenantId, method: "PATCH", body: JSON.stringify(body), }), remove: (tenantId: string, id: string) => request>(`/api/v1/branches/${id}/delete`, { tenantId, method: "POST" }), }, diningAreas: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/dining-areas${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/dining-areas/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/dining-areas`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, tables: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/tables${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/tables/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/tables`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, menus: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/menus${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/menus/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/menus`, { tenantId, method: "POST", body: JSON.stringify(body), }), update: (tenantId: string, id: string, body: Record) => request>(`/api/v1/menus/${id}`, { tenantId, method: "PATCH", body: JSON.stringify(body), }), remove: (tenantId: string, id: string) => request>(`/api/v1/menus/${id}/delete`, { tenantId, method: "POST" }), }, menuCategories: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/menu-categories${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/menu-categories/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/menu-categories`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, menuItems: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/menu-items${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/menu-items/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/menu-items`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, allergens: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/allergens${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/allergens/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/allergens`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, modifierGroups: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/modifier-groups${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/modifier-groups/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/modifier-groups`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, modifierOptions: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/modifier-options${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/modifier-options/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/modifier-options`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, availabilityWindows: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/availability-windows${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/availability-windows/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/availability-windows`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, menuMediaRefs: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/menu-media-refs${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/menu-media-refs/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/menu-media-refs`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, qrCodes: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/qr-codes${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/qr-codes/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/qr-codes`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, qrMenuSessions: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/qr-menu-sessions${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/qr-menu-sessions/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/qr-menu-sessions`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, carts: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/carts${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/carts/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/carts`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, cartLines: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/cart-lines${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/cart-lines/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/cart-lines`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, posRegisters: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/pos-registers${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/pos-registers/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/pos-registers`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, posTicketLines: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/pos-ticket-lines${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/pos-ticket-lines/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/pos-ticket-lines`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, posPayments: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/pos-payments${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/pos-payments/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/pos-payments`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, posDiscounts: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/pos-discounts${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/pos-discounts/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/pos-discounts`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, posTaxLines: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/pos-tax-lines${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/pos-tax-lines/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/pos-tax-lines`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, posFloorPlans: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/pos-floor-plans${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/pos-floor-plans/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/pos-floor-plans`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, posStations: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/pos-stations${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/pos-stations/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/pos-stations`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, kitchenStations: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/kitchen-stations${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/kitchen-stations/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/kitchen-stations`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, connectorRegistrations: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/connector-registrations${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/connector-registrations/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/connector-registrations`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, connectorDispatches: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/connector-dispatches${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/connector-dispatches/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/connector-dispatches`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, analyticsReports: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/analytics-reports${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/analytics-reports/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/analytics-reports`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, bundleDefinitions: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/bundle-definitions${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/bundle-definitions/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/bundle-definitions`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, roles: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/roles${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/roles/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/roles`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, permissions: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/permissions${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/permissions/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/permissions`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, configurations: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/configurations${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/configurations/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/configurations`, { tenantId, method: "POST", body: JSON.stringify(body), }), update: (tenantId: string, id: string, body: Record) => request>(`/api/v1/configurations/${id}`, { tenantId, method: "PATCH", body: JSON.stringify(body), }), }, events: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/events${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/events/${id}`, { tenantId }), create: (tenantId: string, body: Record) => request>(`/api/v1/events`, { tenantId, method: "POST", body: JSON.stringify(body), }), }, tenantBundles: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/tenant-bundles${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/tenant-bundles/${id}`, { tenantId }), activate: (tenantId: string, body: Record) => action(tenantId, "/api/v1/tenant-bundles/activate", body), deactivate: (tenantId: string, id: string) => action(tenantId, `/api/v1/tenant-bundles/${id}/deactivate`), }, featureToggles: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/feature-toggles${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/feature-toggles/${id}`, { tenantId }), upsert: (tenantId: string, body: Record) => action(tenantId, "/api/v1/feature-toggles/upsert", body), }, menuItemModifiers: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/menu-item-modifiers${qs(params)}`, { tenantId }), link: (tenantId: string, body: Record) => action(tenantId, "/api/v1/menu-item-modifiers/link", body), }, menuItemAllergens: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/menu-item-allergens${qs(params)}`, { tenantId }), link: (tenantId: string, body: Record) => action(tenantId, "/api/v1/menu-item-allergens/link", body), }, menuLocalizations: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/menu-localizations${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/menu-localizations/${id}`, { tenantId }), upsert: (tenantId: string, body: Record) => action(tenantId, "/api/v1/menu-localizations/upsert", body), }, settings: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/settings${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/settings/${id}`, { tenantId }), upsert: (tenantId: string, body: Record) => action(tenantId, "/api/v1/settings/upsert", body), }, reservations: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/reservations${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/reservations/${id}`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/reservations", body), updateStatus: (tenantId: string, id: string, body: Record) => action(tenantId, `/api/v1/reservations/${id}/status`, body), }, tableAssignments: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/table-assignments${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/table-assignments/${id}`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/table-assignments", body), release: (tenantId: string, id: string) => action(tenantId, `/api/v1/table-assignments/${id}/release`), }, serviceRequests: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/service-requests${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/service-requests/${id}`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/service-requests", body), updateStatus: (tenantId: string, id: string, body: Record) => action(tenantId, `/api/v1/service-requests/${id}/status`, body), }, waitlist: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/waitlist${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/waitlist/${id}`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/waitlist", body), updateStatus: (tenantId: string, id: string, body: Record) => action(tenantId, `/api/v1/waitlist/${id}/status`, body), }, posShifts: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/pos-shifts${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/pos-shifts/${id}`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/pos-shifts", body), close: (tenantId: string, id: string) => action(tenantId, `/api/v1/pos-shifts/${id}/close`), }, posTickets: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/pos-tickets${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/pos-tickets/${id}`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/pos-tickets", body), void: (tenantId: string, id: string) => action(tenantId, `/api/v1/pos-tickets/${id}/void`), }, qrOrderingSessions: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/qr-ordering-sessions${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/qr-ordering-sessions/${id}`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/qr-ordering-sessions", body), submit: (tenantId: string, id: string) => action(tenantId, `/api/v1/qr-ordering-sessions/${id}/submit`), }, kitchenTickets: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/kitchen-tickets${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/kitchen-tickets/${id}`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/kitchen-tickets", body), updateStatus: (tenantId: string, id: string, body: Record) => action(tenantId, `/api/v1/kitchen-tickets/${id}/status`, body), }, kitchenTicketItems: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/kitchen-ticket-items${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/kitchen-ticket-items/${id}`, { tenantId }), create: (tenantId: string, body: Record) => action(tenantId, "/api/v1/kitchen-ticket-items", body), updateStatus: (tenantId: string, id: string, body: Record) => action(tenantId, `/api/v1/kitchen-ticket-items/${id}/status`, body), }, analyticsSnapshots: { list: (tenantId: string, params?: ListParams) => request[]>(`/api/v1/analytics-snapshots${qs(params)}`, { tenantId }), get: (tenantId: string, id: string) => request>(`/api/v1/analytics-snapshots/${id}`, { tenantId }), refresh: (tenantId: string, body: Record) => action(tenantId, "/api/v1/analytics-snapshots/refresh", body), }, };