1// Client-side API helper for secure server communication
2// Replaces direct Fieldpine API calls with server-side proxy calls
4interface ApiResponse<T = any> {
11 private baseUrl: string;
14 this.baseUrl = '/api';
17 // Generic request method for API calls
18 async request<T = any>(
20 options: RequestInit = {}
21 ): Promise<ApiResponse<T>> {
22 return this.requestInternal(endpoint, options);
25 // Generic GET method - convenience wrapper
26 async get<T = any>(endpoint: string): Promise<T> {
27 const response = await this.requestInternal<T>(endpoint);
28 // For backward compatibility, return just the data
29 // Check if response has the structure we expect
30 if (response && typeof response === 'object' && 'success' in response) {
36 private async requestInternal<T = any>(
38 options: RequestInit = {}
39 ): Promise<ApiResponse<T>> {
40 const url = `${this.baseUrl}${endpoint}`;
42 const defaultHeaders = {
43 'Content-Type': 'application/json',
47 const response = await fetch(url, {
53 credentials: 'same-origin', // Include cookies
56 // Handle 401 Unauthorized - redirect to login
57 if (response.status === 401) {
58 console.log('401 Unauthorized - redirecting to login');
59 // Use window.location for immediate redirect
60 if (typeof window !== 'undefined') {
61 window.location.href = '/pages/login';
65 error: 'Authentication required',
69 const result = await response.json();
72 throw new Error(result.error || `HTTP ${response.status}`);
77 console.error(`API request failed: ${endpoint}`, error);
80 error: error instanceof Error ? error.message : 'Unknown error',
86 async login(username: string, password: string, storeId?: string): Promise<ApiResponse<{ user: any; store?: any; redirect: string }>> {
87 return this.requestInternal('/auth/login', {
89 body: JSON.stringify({ username, password, storeId }),
93 async logout(): Promise<ApiResponse> {
94 return this.requestInternal('/auth/logout', { method: 'POST' });
98 async getProducts(params: {
105 source?: 'openapi' | 'elink'; // Optional source preference
106 } = {}): Promise<ApiResponse<any[]>> {
107 const query = new URLSearchParams(
108 Object.entries(params).reduce((acc, [key, value]) => {
109 if (value !== undefined && key !== 'source') {
110 acc[key] = value.toString();
113 }, {} as Record<string, string>)
116 // Determine which API version to use
117 const apiVersion = params.source || 'openapi'; // Default to OpenAPI
118 const endpoint = query ? `/v1/${apiVersion}/products?${query}` : `/v1/${apiVersion}/products`;
120 // Try primary choice first, then fallback
122 return await this.requestInternal(endpoint);
124 // Fallback to alternative API if primary fails
125 if (apiVersion === 'openapi') {
126 const fallbackEndpoint = query ? `/v1/elink/products?${query}` : '/v1/elink/products';
127 return this.requestInternal(fallbackEndpoint);
129 const fallbackEndpoint = query ? `/v1/openapi/products?${query}` : '/v1/openapi/products';
130 return this.requestInternal(fallbackEndpoint);
135 async getProductById(id: string): Promise<ApiResponse<any>> {
136 return this.requestInternal(`/products/${id}`);
139 async getProduct(id: string | number): Promise<ApiResponse<any>> {
140 return this.requestInternal(`/v1/openapi/products/${id}`);
144 async createSale(saleData: any): Promise<ApiResponse<any>> {
145 return this.requestInternal('/sales', {
147 body: JSON.stringify(saleData),
152 async getDashboard(): Promise<ApiResponse<any>> {
153 return this.requestInternal('/dashboard');
157 async getCustomers(params: {
160 type?: 'list' | 'single' | 'search';
162 source?: 'openapi' | 'elink'; // Optional source preference
163 } = {}): Promise<ApiResponse<any[]>> {
164 const query = new URLSearchParams(
165 Object.entries(params).reduce((acc, [key, value]) => {
166 if (value !== undefined && key !== 'source') {
167 acc[key] = value.toString();
170 }, {} as Record<string, string>)
173 // Determine which API version to use
174 const apiVersion = params.source || 'openapi'; // Default to OpenAPI
175 const endpoint = query ? `/v1/${apiVersion}/customers?${query}` : `/v1/${apiVersion}/customers`;
177 // Try primary choice first, then fallback
179 return await this.requestInternal(endpoint);
181 // Fallback to alternative API if primary fails
182 if (apiVersion === 'openapi') {
183 const fallbackEndpoint = query ? `/v1/elink/customers?${query}` : '/v1/elink/customers';
184 return this.requestInternal(fallbackEndpoint);
186 const fallbackEndpoint = query ? `/v1/openapi/customers?${query}` : '/v1/openapi/customers';
187 return this.requestInternal(fallbackEndpoint);
193 async getSuppliers(params: {
196 source?: 'openapi' | 'elink'; // Optional source preference
197 } = {}): Promise<ApiResponse<any[]>> {
198 const query = new URLSearchParams(
199 Object.entries(params).reduce((acc, [key, value]) => {
200 if (value !== undefined && key !== 'source') {
201 acc[key] = value.toString();
204 }, {} as Record<string, string>)
207 // Determine which API version to use
208 const apiVersion = params.source || 'openapi'; // Default to OpenAPI
209 const endpoint = query ? `/v1/${apiVersion}/suppliers?${query}` : `/v1/${apiVersion}/suppliers`;
211 return this.requestInternal(endpoint);
214 // OpenAPI Methods - Comprehensive implementation
216 // Products Search with Natural Language
217 async searchProducts(params: {
224 inputmethod?: string;
225 } = {}): Promise<ApiResponse<any[]>> {
226 const query = new URLSearchParams(
227 Object.entries(params).reduce((acc, [key, value]) => {
228 if (value !== undefined) acc[key] = value.toString();
230 }, {} as Record<string, string>)
232 return this.requestInternal(query ? `/v1/openapi/products-search?${query}` : '/v1/openapi/products-search');
236 async decodeBarcode(scan: string, geo?: string): Promise<ApiResponse<any>> {
237 const params = new URLSearchParams({ scan });
238 if (geo) params.append('geo', geo);
239 return this.requestInternal(`/v1/openapi/barcodes?${params.toString()}`);
243 async getWebGroups(): Promise<ApiResponse<any[]>> {
244 return this.requestInternal('/v1/openapi/webgroups');
248 async getReorderLevels(params: {
252 } = {}): Promise<ApiResponse<any[]>> {
253 const query = new URLSearchParams(
254 Object.entries(params).reduce((acc, [key, value]) => {
255 if (value !== undefined) acc[key] = value.toString();
257 }, {} as Record<string, string>)
259 return this.requestInternal(query ? `/v1/openapi/reorder-levels?${query}` : '/v1/openapi/reorder-levels');
262 async setReorderLevel(locid: number, pid: number, levels: {
263 ReorderLevel?: number;
265 }): Promise<ApiResponse<any>> {
266 return this.requestInternal(`/v1/openapi/reorder-levels/${locid}/${pid}`, {
268 body: JSON.stringify(levels)
272 async deleteReorderLevel(locid: number, pid: number): Promise<ApiResponse<any>> {
273 return this.requestInternal(`/v1/openapi/reorder-levels/${locid}/${pid}`, {
278 // Supplier Part Codes
279 async getSupplierPartCodes(params: {
283 } = {}): Promise<ApiResponse<any[]>> {
284 const query = new URLSearchParams(
285 Object.entries(params).reduce((acc, [key, value]) => {
286 if (value !== undefined) acc[key] = value.toString();
288 }, {} as Record<string, string>)
290 return this.requestInternal(query ? `/v1/openapi/supplier-part-codes?${query}` : '/v1/openapi/supplier-part-codes');
294 async getPurchaseOrders(params: {
298 } = {}): Promise<ApiResponse<any[]>> {
299 const query = new URLSearchParams(
300 Object.entries(params).reduce((acc, [key, value]) => {
301 if (value !== undefined) acc[key] = value.toString();
303 }, {} as Record<string, string>)
305 return this.requestInternal(query ? `/v1/openapi/purchase-orders?${query}` : '/v1/openapi/purchase-orders');
308 async createPurchaseOrder(order: any): Promise<ApiResponse<any>> {
309 return this.requestInternal('/v1/openapi/purchase-orders', {
311 body: JSON.stringify(order)
315 async parsePurchaseOrder(text: string): Promise<ApiResponse<any>> {
316 return this.requestInternal(`/v1/openapi/purchase-order/parse?text=${encodeURIComponent(text)}`);
319 // Stock2 - Stock Movements
320 async getStockMovements(params: {
326 } = {}): Promise<ApiResponse<any[]>> {
327 const query = new URLSearchParams(
328 Object.entries(params).reduce((acc, [key, value]) => {
329 if (value !== undefined) acc[key] = value.toString();
331 }, {} as Record<string, string>)
333 return this.requestInternal(query ? `/v1/openapi/stock2/movements?${query}` : '/v1/openapi/stock2/movements');
336 // Stock2 - Stock Counts
337 async getStockCounts(params: {
342 } = {}): Promise<ApiResponse<any[]>> {
343 const query = new URLSearchParams(
344 Object.entries(params).reduce((acc, [key, value]) => {
345 if (value !== undefined) acc[key] = value.toString();
347 }, {} as Record<string, string>)
349 return this.requestInternal(query ? `/v1/openapi/stock2/count?${query}` : '/v1/openapi/stock2/count');
352 async recordStockCount(count: any): Promise<ApiResponse<any>> {
353 return this.requestInternal('/v1/openapi/stock2/count', {
355 body: JSON.stringify(count)
359 async deleteStockCount(key: string): Promise<ApiResponse<any>> {
360 return this.requestInternal(`/v1/openapi/stock2/count/${key}`, {
365 async getStockCountSummary(params: {
369 } = {}): Promise<ApiResponse<any[]>> {
370 const query = new URLSearchParams(
371 Object.entries(params).reduce((acc, [key, value]) => {
372 if (value !== undefined) acc[key] = value.toString();
374 }, {} as Record<string, string>)
376 return this.requestInternal(query ? `/v1/openapi/stock2/count-summary?${query}` : '/v1/openapi/stock2/count-summary');
380 async getShelfVisits(params: {
385 } = {}): Promise<ApiResponse<any[]>> {
386 const query = new URLSearchParams(
387 Object.entries(params).reduce((acc, [key, value]) => {
388 if (value !== undefined) acc[key] = value.toString();
390 }, {} as Record<string, string>)
392 return this.requestInternal(query ? `/v1/openapi/shelf-visits?${query}` : '/v1/openapi/shelf-visits');
395 async recordShelfVisit(visit: any, params?: { CardNo?: string; Pass?: string }): Promise<ApiResponse<any>> {
396 const query = params ? new URLSearchParams(
397 Object.entries(params).reduce((acc, [key, value]) => {
398 if (value !== undefined) acc[key] = value;
400 }, {} as Record<string, string>)
402 return this.requestInternal(query ? `/v1/openapi/shelf-visits?${query}` : '/v1/openapi/shelf-visits', {
404 body: JSON.stringify(visit)
409 async getStocktakes(params: {
412 } = {}): Promise<ApiResponse<any[]>> {
413 const query = new URLSearchParams(
414 Object.entries(params).reduce((acc, [key, value]) => {
415 if (value !== undefined) acc[key] = value.toString();
417 }, {} as Record<string, string>)
419 return this.requestInternal(query ? `/v1/openapi/stocktakes?${query}` : '/v1/openapi/stocktakes');
422 async getBuckStocktakes(params: {
423 filter?: 'current' | 'complete' | 'all';
424 } = {}): Promise<ApiResponse<any>> {
425 const query = new URLSearchParams(
426 Object.entries(params).reduce((acc, [key, value]) => {
427 if (value !== undefined) acc[key] = value.toString();
429 }, {} as Record<string, string>)
431 return this.requestInternal(query ? `/v1/buck/stocktakes?${query}` : '/v1/buck/stocktakes');
434 async getBuckStocktakeStatus(id: number): Promise<ApiResponse<any>> {
435 return this.requestInternal(`/v1/buck/stocktake/status?id=${id}`);
439 async getStockTransfer(id: string, srcuid?: number): Promise<ApiResponse<any>> {
440 const query = srcuid ? `?srcuid=${srcuid}` : '';
441 return this.requestInternal(`/v1/openapi/stock-transfers/${id}${query}`);
444 async receiveStockTransfer(id: string, location?: number): Promise<ApiResponse<any>> {
445 const query = location ? `?location=${location}` : '';
446 return this.requestInternal(`/v1/openapi/stock-transfers/${id}/receive${query}`, {
452 async writeoffProduct(id: string, params: {
456 }): Promise<ApiResponse<any>> {
457 const query = new URLSearchParams(
458 Object.entries(params).reduce((acc, [key, value]) => {
459 if (value !== undefined) acc[key] = value.toString();
461 }, {} as Record<string, string>)
463 return this.requestInternal(`/v1/openapi/products/${id}/writeoff?${query}`, {
468 // Pricebook Specials
469 async getPricebookSpecials(params: {
473 } = {}): Promise<ApiResponse<any[]>> {
474 const query = new URLSearchParams(
475 Object.entries(params).reduce((acc, [key, value]) => {
476 if (value !== undefined) acc[key] = value.toString();
478 }, {} as Record<string, string>)
480 return this.requestInternal(query ? `/v1/openapi/pricebook-specials?${query}` : '/v1/openapi/pricebook-specials');
483 // Supplier Marketing Messages
484 async getSupplierMarketingMessages(params: {
489 } = {}): Promise<ApiResponse<any[]>> {
490 const query = new URLSearchParams(
491 Object.entries(params).reduce((acc, [key, value]) => {
492 if (value !== undefined) acc[key] = value.toString();
494 }, {} as Record<string, string>)
496 return this.requestInternal(query ? `/v1/openapi/supplier-marketing-messages?${query}` : '/v1/openapi/supplier-marketing-messages');
499 // Customer Accounts (uses elink/BUCK API)
500 async getAccounts(params: { type?: 'list' | 'summary' | 'search'; search?: string } = {}): Promise<ApiResponse<any[]>> {
501 const query = new URLSearchParams(
502 Object.entries(params).reduce((acc, [key, value]) => {
503 if (value !== undefined) acc[key] = value.toString();
505 }, {} as Record<string, string>)
507 return this.requestInternal(query ? `/v1/accounts?${query}` : '/v1/accounts');
510 async createOrUpdateAccount(data: any): Promise<ApiResponse<any>> {
511 return this.requestInternal('/v1/accounts', {
513 body: JSON.stringify({
514 action: data.id ? 'update' : 'create',
520 async getRecentAccountSales(): Promise<ApiResponse<any[]>> {
521 return this.requestInternal('/v1/accounts/sales');
524 async getAccountCustomers(accountId: number): Promise<ApiResponse<any[]>> {
525 return this.requestInternal(`/v1/accounts/customers?accountId=${accountId}`);
528 async linkCustomerToAccount(customerId: number, accountId: number): Promise<ApiResponse<any>> {
529 return this.requestInternal('/v1/accounts/customers', {
531 body: JSON.stringify({
539 async unlinkCustomerFromAccount(customerId: number): Promise<ApiResponse<any>> {
540 return this.requestInternal('/v1/accounts/customers', {
542 body: JSON.stringify({
551 async searchSales(params: {
555 inputMethod?: string;
556 }): Promise<ApiResponse<any>> {
557 const query = new URLSearchParams(
558 Object.entries(params).reduce((acc, [key, value]) => {
559 if (value !== undefined) acc[key] = value.toString();
561 }, {} as Record<string, string>)
563 return this.requestInternal(`/v1/sales/search?${query}`);
567 async getLocations(params: { source?: 'openapi' | 'elink'; want?: string } = {}): Promise<ApiResponse<any[]>> {
568 const query = new URLSearchParams(
569 Object.entries(params).reduce((acc, [key, value]) => {
570 if (value !== undefined) acc[key] = value.toString();
572 }, {} as Record<string, string>)
574 return this.requestInternal(query ? `/v1/locations?${query}` : '/v1/locations');
577 async saveLocation(data: {
585 }): Promise<ApiResponse<any>> {
586 return this.requestInternal('/v1/locations', {
588 body: JSON.stringify(data),
593 // async getDepartments(params: { want?: string } = {}): Promise<ApiResponse<any[]>> {
594 // const query = new URLSearchParams(
595 // Object.entries(params).reduce((acc, [key, value]) => {
596 // if (value !== undefined) acc[key] = value.toString();
598 // }, {} as Record<string, string>)
600 // return this.requestInternal(query ? `/v1/departments?${query}` : '/v1/departments');
604 async getPaymentTypes(params: { want?: string } = {}): Promise<ApiResponse<any[]>> {
605 const query = new URLSearchParams(
606 Object.entries(params).reduce((acc, [key, value]) => {
607 if (value !== undefined) acc[key] = value.toString();
609 }, {} as Record<string, string>)
611 return this.requestInternal(query ? `/v1/openapi/payment-types?${query}` : '/v1/openapi/payment-types');
615 async getDiscounts(): Promise<ApiResponse<any[]>> {
616 return this.requestInternal('/v1/discounts');
620 async lookupProductByBarcode(barcode: string): Promise<ApiResponse<any>> {
621 return this.requestInternal(`/v1/stocktake?barcode=${encodeURIComponent(barcode)}`);
624 async recordStocktakeCount(params: {
630 }): Promise<ApiResponse<any>> {
631 return this.requestInternal('/v1/stocktake', {
633 body: JSON.stringify(params)
637 // Account Financial Data (Aged Debtors, etc.)
638 async getAccountFinancialData(params: {
641 showZeroBalance?: boolean;
642 showRetired?: boolean;
643 } = {}): Promise<ApiResponse<any[]>> {
644 const query = new URLSearchParams(
645 Object.entries(params).reduce((acc, [key, value]) => {
646 if (value !== undefined) acc[key] = value.toString();
648 }, {} as Record<string, string>)
650 return this.requestInternal(query ? `/v1/accounts/financial?${query}` : '/v1/accounts/financial');
654 async getPaymentTypesList(): Promise<ApiResponse<any[]>> {
655 return this.requestInternal('/v1/payment-types');
659 async getPriceChanges(params: {
664 } = {}): Promise<ApiResponse<any[]>> {
665 const query = new URLSearchParams(
666 Object.entries(params).reduce((acc, [key, value]) => {
667 if (value !== undefined) acc[key] = value.toString();
669 }, {} as Record<string, string>)
671 return this.requestInternal(query ? `/v1/price-changes?${query}` : '/v1/price-changes');
675 async getSalesList(params: {
679 } = {}): Promise<ApiResponse<any>> {
680 const query = new URLSearchParams();
681 if (params.limit) query.append('limit', params.limit.toString());
682 if (params.fields) query.append('fields', params.fields);
683 if (params.filters) {
684 params.filters.forEach(filter => query.append('filter', filter));
686 return this.requestInternal(`/v1/sales/list?${query.toString()}`);
690 async getSales(params: {
696 } = {}): Promise<ApiResponse<any[]>> {
697 const query = new URLSearchParams(
698 Object.entries(params).reduce((acc, [key, value]) => {
699 if (value !== undefined) acc[key] = value.toString();
701 }, {} as Record<string, string>)
703 return this.requestInternal(query ? `/v1/sales?${query}` : '/v1/sales');
707 async getContactLogs(params: {
711 } = {}): Promise<ApiResponse<any[]>> {
712 const query = new URLSearchParams(
713 Object.entries(params).reduce((acc, [key, value]) => {
714 if (value !== undefined) acc[key] = value.toString();
716 }, {} as Record<string, string>)
718 return this.requestInternal(query ? `/v1/contact-logs?${query}` : '/v1/contact-logs');
721 async saveContactLog(params: {
726 }): Promise<ApiResponse<any>> {
727 return this.requestInternal('/v1/contact-logs', {
729 body: JSON.stringify(params)
733 // Customer Statistics and Analytics
734 async getCustomerStats(params: {
736 type?: 'product-performance' | 'brands';
738 }): Promise<ApiResponse<any>> {
739 const query = new URLSearchParams(
740 Object.entries(params).reduce((acc, [key, value]) => {
741 if (value !== undefined) acc[key] = value.toString();
743 }, {} as Record<string, string>)
745 return this.requestInternal(query ? `/v1/stats?${query}` : '/v1/stats');
748 async getDepartments(): Promise<ApiResponse<any[]>> {
749 return this.requestInternal('/v1/elink/departments');
752 async getDepartmentHierarchy(): Promise<ApiResponse<Record<number, number[]>>> {
753 return this.requestInternal('/v1/departments/hierarchy');
756 async saveDepartmentHierarchy(hierarchy: Record<number, number[]>): Promise<ApiResponse<any>> {
757 return this.requestInternal('/v1/departments/hierarchy', {
759 body: JSON.stringify({ hierarchy }),
764 async updateCustomer(
775 ): Promise<ApiResponse<any>> {
776 return this.requestInternal(`/v1/elink/customers/${customerId}`, {
778 body: JSON.stringify(data),
783 async getStaff(params: {
785 includeRights?: boolean;
787 } = {}): Promise<ApiResponse<any>> {
788 const query = new URLSearchParams(
789 Object.entries(params).reduce((acc, [key, value]) => {
790 if (value !== undefined) acc[key] = value.toString();
792 }, {} as Record<string, string>)
794 return this.requestInternal(query ? `/v1/elink/staff?${query}` : '/v1/elink/staff');
797 async getStaffUsage(params?: { staffId?: string; fromDate?: string; toDate?: string }): Promise<ApiResponse<any>> {
798 const queryParams = new URLSearchParams();
799 if (params?.staffId) queryParams.append('staffId', params.staffId);
800 if (params?.fromDate) queryParams.append('fromDate', params.fromDate);
801 if (params?.toDate) queryParams.append('toDate', params.toDate);
803 const queryString = queryParams.toString();
804 const endpoint = queryString ? `/v1/elink/staff/used?${queryString}` : '/v1/elink/staff/used';
806 return this.requestInternal(endpoint);
809 async getStaffUsageDetail(params: { staffId: string; fromDate?: string; toDate?: string }): Promise<ApiResponse<any>> {
810 const queryParams = new URLSearchParams();
811 queryParams.append('staffId', params.staffId);
812 if (params?.fromDate) queryParams.append('fromDate', params.fromDate);
813 if (params?.toDate) queryParams.append('toDate', params.toDate);
815 const queryString = queryParams.toString();
816 return this.requestInternal(`/v1/elink/staff/used/detail?${queryString}`);
819 async getStaffRights(): Promise<ApiResponse<any>> {
820 return this.requestInternal('/v1/elink/staff/rights');
823 async saveStaff(data: {
830 mainLocation?: string;
831 printedName?: string;
837 privateEmail?: string;
839 onlineLogin?: string;
840 onlinePassword?: string;
841 isCustomerRep?: boolean;
842 isAccountRep?: boolean;
843 remoteProfile?: number;
844 autoLoginFrom?: string;
847 }): Promise<ApiResponse<any>> {
848 return this.requestInternal('/v1/elink/staff', {
850 body: JSON.stringify(data),
854 // Employee Management
855 async getEmployees(params: {
857 } = {}): Promise<ApiResponse<any>> {
858 const query = new URLSearchParams(
859 Object.entries(params).reduce((acc, [key, value]) => {
860 if (value !== undefined) acc[key] = value.toString();
862 }, {} as Record<string, string>)
864 return this.requestInternal(query ? `/v1/elink/employees?${query}` : '/v1/elink/employees');
867 async saveEmployee(data: {
874 mainLocation?: string;
877 departmentId?: string;
880 }): Promise<ApiResponse<any>> {
881 return this.requestInternal('/v1/elink/employees', {
883 body: JSON.stringify(data),
887 async getTopology(): Promise<ApiResponse<any>> {
888 return this.requestInternal('/v1/elink/topology');
891 // Reports - Sales Detail (line-by-line)
892 async getSalesDetail(params: {
896 departmentId?: string;
898 } = {}): Promise<ApiResponse<any[]>> {
899 const query = new URLSearchParams(
900 Object.entries(params).reduce((acc, [key, value]) => {
901 if (value !== undefined) acc[key] = value.toString();
903 }, {} as Record<string, string>)
905 return this.requestInternal(query ? `/v1/reports/sales-detail?${query}` : '/v1/reports/sales-detail');
908 // Reports - Grouped Sales
909 async getSalesGrouped(params: {
912 groupBy?: 'product' | 'supplier' | 'department' | 'location' | 'teller';
914 } = {}): Promise<ApiResponse<any[]>> {
915 const query = new URLSearchParams(
916 Object.entries(params).reduce((acc, [key, value]) => {
917 if (value !== undefined) acc[key] = value.toString();
919 }, {} as Record<string, string>)
921 return this.requestInternal(query ? `/v1/reports/sales-grouped?${query}` : '/v1/reports/sales-grouped');
924 // Analytics - Pareto Analysis (80/20 rule)
925 async getParetoAnalysis(params: {
928 type?: 'basketrate' | 'grossmargin' | 'returns' | 'volume' | 'revenue';
930 } = {}): Promise<ApiResponse<any[]>> {
931 const query = new URLSearchParams(
932 Object.entries(params).reduce((acc, [key, value]) => {
933 if (value !== undefined) acc[key] = value.toString();
935 }, {} as Record<string, string>)
937 return this.requestInternal(query ? `/v1/analytics/pareto?${query}` : '/v1/analytics/pareto');
940 // Reports - Purchase Orders Summary
941 async getPurchaseOrdersSummary(params: {
944 status?: 'pending' | 'received' | 'cancelled';
947 } = {}): Promise<ApiResponse<any[]>> {
948 const query = new URLSearchParams(
949 Object.entries(params).reduce((acc, [key, value]) => {
950 if (value !== undefined) acc[key] = value.toString();
952 }, {} as Record<string, string>)
954 return this.requestInternal(query ? `/v1/reports/purchase-orders?${query}` : '/v1/reports/purchase-orders');
957 // Inventory - Goods Movements
958 async getGoodsMovements(params: {
961 type?: 'transfer' | 'adjustment' | 'writeoff' | 'stocktake' | 'return';
965 } = {}): Promise<ApiResponse<any[]>> {
966 const query = new URLSearchParams(
967 Object.entries(params).reduce((acc, [key, value]) => {
968 if (value !== undefined) acc[key] = value.toString();
970 }, {} as Record<string, string>)
972 return this.requestInternal(query ? `/v1/inventory/movements?${query}` : '/v1/inventory/movements');
975 // Analytics - Product Performance (Most critical - 432 uses!)
976 async getProductPerformance(params: {
979 groupBy?: 'pid' | 'product' | 'spid' | 'supplier' | 'dept' | 'department';
980 sortBy?: '1' | '2' | '3'; // 1=volume, 2=revenue, 3=profit
982 } = {}): Promise<ApiResponse<any[]>> {
983 const query = new URLSearchParams(
984 Object.entries(params).reduce((acc, [key, value]) => {
985 if (value !== undefined) acc[key] = value.toString();
987 }, {} as Record<string, string>)
989 return this.requestInternal(query ? `/v1/analytics/product-performance?${query}` : '/v1/analytics/product-performance');
992 // Configuration - Base Settings
993 async getBaseSettings(params: {
996 } = {}): Promise<ApiResponse<any[]>> {
997 const query = new URLSearchParams(
998 Object.entries(params).reduce((acc, [key, value]) => {
999 if (value !== undefined) acc[key] = value.toString();
1001 }, {} as Record<string, string>)
1003 return this.requestInternal(query ? `/v1/config/base-settings?${query}` : '/v1/config/base-settings');
1006 async updateBaseSetting(key: string, value: any): Promise<ApiResponse<any>> {
1007 return this.requestInternal('/v1/config/base-settings', {
1009 body: JSON.stringify({ key, value })
1013 // Reports - Sales Totals
1014 async getSalesTotals(params: {
1017 groupBy?: 'day' | 'week' | 'month' | 'location' | 'teller' | 'department';
1018 locationId?: string;
1020 sortBy?: 'date' | 'revenue' | 'units';
1021 } = {}): Promise<ApiResponse<any[]>> {
1022 const query = new URLSearchParams(
1023 Object.entries(params).reduce((acc, [key, value]) => {
1024 if (value !== undefined) acc[key] = value.toString();
1026 }, {} as Record<string, string>)
1028 return this.requestInternal(query ? `/v1/reports/sales-totals?${query}` : '/v1/reports/sales-totals');
1031 // Stats - Today's Statistics (for dashboard)
1032 async getStatsToday(params: {
1033 locationId?: string;
1034 } = {}): Promise<ApiResponse<any>> {
1035 const query = new URLSearchParams(
1036 Object.entries(params).reduce((acc, [key, value]) => {
1037 if (value !== undefined) acc[key] = value.toString();
1039 }, {} as Record<string, string>)
1041 return this.requestInternal(query ? `/v1/stats/today?${query}` : '/v1/stats/today');
1043 // BUCK Data - Loyalty Campaigns and Printer-Cartridge relationships
1044 async getBuckData(params: {
1049 }): Promise<ApiResponse<any>> {
1050 // Support loyalty campaigns
1051 if (params.table === 'retailmax.elink.loyalty.campaign') {
1052 return this.requestInternal('/v1/buck/loyalty/campaigns');
1054 // Support printer-cartridge relationships
1055 if (params.table === 'retailmax.elink.printercartridge') {
1056 const query = new URLSearchParams();
1057 if (params.want) query.append('want', params.want);
1058 if (params.filter) query.append('filter', params.filter);
1059 if (params.limit) query.append('limit', params.limit.toString());
1060 const queryString = query.toString();
1061 return this.requestInternal(queryString ? `/v1/elink/printer-cartridge?${queryString}` : '/v1/elink/printer-cartridge');
1063 throw new Error(`BUCK table ${params.table} not supported yet`);
1066export const apiClient = new ApiClient();
1067export type { ApiResponse };