/** * POS Service * Handles all POS configuration and management API calls * Based on services/pos/app/api/pos_config.py backend implementation */ import { apiClient } from '../client'; import type { POSConfiguration, POSTransaction, POSWebhookLog, POSSyncLog, POSSystemInfo, GetPOSConfigurationsRequest, GetPOSConfigurationsResponse, CreatePOSConfigurationRequest, CreatePOSConfigurationResponse, GetPOSConfigurationRequest, GetPOSConfigurationResponse, UpdatePOSConfigurationRequest, UpdatePOSConfigurationResponse, DeletePOSConfigurationRequest, DeletePOSConfigurationResponse, TestPOSConnectionRequest, TestPOSConnectionResponse, GetSupportedPOSSystemsResponse, POSSystem, } from '../types/pos'; export class POSService { private readonly basePath = '/pos'; // ============================================================================ // POS CONFIGURATIONS // ============================================================================ /** * Get POS configurations for a tenant */ async getPOSConfigurations(params: GetPOSConfigurationsRequest): Promise { const { tenant_id, pos_system, is_active } = params; const queryParams = new URLSearchParams(); if (pos_system) queryParams.append('pos_system', pos_system); if (is_active !== undefined) queryParams.append('is_active', is_active.toString()); const url = `/tenants/${tenant_id}${this.basePath}/configurations${queryParams.toString() ? `?${queryParams.toString()}` : ''}`; return apiClient.get(url); } /** * Create a new POS configuration */ async createPOSConfiguration(params: CreatePOSConfigurationRequest): Promise { const { tenant_id, ...configData } = params; const url = `/tenants/${tenant_id}${this.basePath}/configurations`; return apiClient.post(url, configData); } /** * Get a specific POS configuration */ async getPOSConfiguration(params: GetPOSConfigurationRequest): Promise { const { tenant_id, config_id } = params; const url = `/tenants/${tenant_id}${this.basePath}/configurations/${config_id}`; return apiClient.get(url); } /** * Update a POS configuration */ async updatePOSConfiguration(params: UpdatePOSConfigurationRequest): Promise { const { tenant_id, config_id, ...updateData } = params; const url = `/tenants/${tenant_id}${this.basePath}/configurations/${config_id}`; return apiClient.put(url, updateData); } /** * Delete a POS configuration */ async deletePOSConfiguration(params: DeletePOSConfigurationRequest): Promise { const { tenant_id, config_id } = params; const url = `/tenants/${tenant_id}${this.basePath}/configurations/${config_id}`; return apiClient.delete(url); } /** * Test connection to POS system */ async testPOSConnection(params: TestPOSConnectionRequest): Promise { const { tenant_id, config_id } = params; const url = `/tenants/${tenant_id}${this.basePath}/configurations/${config_id}/test-connection`; return apiClient.post(url); } // ============================================================================ // SUPPORTED SYSTEMS // ============================================================================ /** * Get list of supported POS systems */ async getSupportedPOSSystems(): Promise { const url = `${this.basePath}/supported-systems`; return apiClient.get(url); } // ============================================================================ // TRANSACTIONS (Future Implementation) // ============================================================================ /** * Get POS transactions for a tenant (Updated with backend structure) */ async getPOSTransactions(params: { tenant_id: string; pos_system?: string; start_date?: string; end_date?: string; status?: string; is_synced?: boolean; limit?: number; offset?: number; }): Promise<{ transactions: POSTransaction[]; total: number; has_more: boolean; summary: { total_amount: number; transaction_count: number; sync_status: { synced: number; pending: number; failed: number; }; }; }> { const { tenant_id, ...queryParams } = params; const searchParams = new URLSearchParams(); Object.entries(queryParams).forEach(([key, value]) => { if (value !== undefined) { searchParams.append(key, value.toString()); } }); const url = `/tenants/${tenant_id}${this.basePath}/transactions${searchParams.toString() ? `?${searchParams.toString()}` : ''}`; return apiClient.get(url); } /** * Sync a single transaction to sales service */ async syncSingleTransaction(params: { tenant_id: string; transaction_id: string; force?: boolean; }): Promise<{ message: string; transaction_id: string; sync_status: string; sales_record_id: string; }> { const { tenant_id, transaction_id, force } = params; const queryParams = new URLSearchParams(); if (force) queryParams.append('force', force.toString()); const url = `/tenants/${tenant_id}${this.basePath}/transactions/${transaction_id}/sync${queryParams.toString() ? `?${queryParams.toString()}` : ''}`; return apiClient.post(url); } /** * Get sync performance analytics */ async getSyncAnalytics(params: { tenant_id: string; days?: number; }): Promise<{ period_days: number; total_syncs: number; successful_syncs: number; failed_syncs: number; success_rate: number; average_duration_minutes: number; total_transactions_synced: number; total_revenue_synced: number; sync_frequency: { daily_average: number; peak_day?: string; peak_count: number; }; error_analysis: { common_errors: any[]; error_trends: any[]; }; }> { const { tenant_id, days } = params; const queryParams = new URLSearchParams(); if (days) queryParams.append('days', days.toString()); const url = `/tenants/${tenant_id}${this.basePath}/analytics/sync-performance${queryParams.toString() ? `?${queryParams.toString()}` : ''}`; return apiClient.get(url); } /** * Resync failed transactions */ async resyncFailedTransactions(params: { tenant_id: string; days_back?: number; }): Promise<{ message: string; job_id: string; scope: string; estimated_transactions: number; }> { const { tenant_id, days_back } = params; const queryParams = new URLSearchParams(); if (days_back) queryParams.append('days_back', days_back.toString()); const url = `/tenants/${tenant_id}${this.basePath}/data/resync${queryParams.toString() ? `?${queryParams.toString()}` : ''}`; return apiClient.post(url); } /** * Get a specific POS transaction */ async getPOSTransaction(params: { tenant_id: string; transaction_id: string; }): Promise<{ transaction: POSTransaction; }> { const { tenant_id, transaction_id } = params; const url = `/tenants/${tenant_id}${this.basePath}/transactions/${transaction_id}`; return apiClient.get(url); } // ============================================================================ // SYNC OPERATIONS (Future Implementation) // ============================================================================ /** * Trigger manual sync for a POS configuration */ async triggerManualSync(params: { tenant_id: string; config_id: string; sync_type?: 'full' | 'incremental'; data_types?: string[]; from_date?: string; to_date?: string; }): Promise<{ sync_id: string; message: string; status: string; sync_type: string; data_types: string[]; estimated_duration: string; }> { const { tenant_id, config_id, ...syncData } = params; const url = `/tenants/${tenant_id}${this.basePath}/configurations/${config_id}/sync`; return apiClient.post(url, syncData); } /** * Get sync status for a POS configuration */ async getSyncStatus(params: { tenant_id: string; config_id: string; limit?: number; }): Promise<{ current_sync: any; last_successful_sync: any; recent_syncs: any[]; sync_health: { status: string; success_rate: number; average_duration_minutes: number; last_error?: string; }; }> { const { tenant_id, config_id, limit } = params; const queryParams = new URLSearchParams(); if (limit) queryParams.append('limit', limit.toString()); const url = `/tenants/${tenant_id}${this.basePath}/configurations/${config_id}/sync/status${queryParams.toString() ? `?${queryParams.toString()}` : ''}`; return apiClient.get(url); } /** * Get detailed sync logs for a configuration */ async getDetailedSyncLogs(params: { tenant_id: string; config_id: string; limit?: number; offset?: number; status?: string; sync_type?: string; data_type?: string; }): Promise<{ logs: any[]; total: number; has_more: boolean; }> { const { tenant_id, config_id, ...queryParams } = params; const searchParams = new URLSearchParams(); Object.entries(queryParams).forEach(([key, value]) => { if (value !== undefined) { searchParams.append(key, value.toString()); } }); const url = `/tenants/${tenant_id}${this.basePath}/configurations/${config_id}/sync/logs${searchParams.toString() ? `?${searchParams.toString()}` : ''}`; return apiClient.get(url); } /** * Get sync logs for a POS configuration */ async getSyncLogs(params: { tenant_id: string; config_id?: string; status?: string; limit?: number; offset?: number; }): Promise<{ sync_logs: POSSyncLog[]; total: number; has_more: boolean; }> { const { tenant_id, ...queryParams } = params; const searchParams = new URLSearchParams(); Object.entries(queryParams).forEach(([key, value]) => { if (value !== undefined) { searchParams.append(key, value.toString()); } }); const url = `/tenants/${tenant_id}${this.basePath}/sync-logs${searchParams.toString() ? `?${searchParams.toString()}` : ''}`; return apiClient.get(url); } // ============================================================================ // WEBHOOKS // ============================================================================ /** * Get webhook logs */ async getWebhookLogs(params: { tenant_id: string; pos_system?: POSSystem; status?: string; limit?: number; offset?: number; }): Promise<{ webhook_logs: POSWebhookLog[]; total: number; has_more: boolean; }> { const { tenant_id, ...queryParams } = params; const searchParams = new URLSearchParams(); Object.entries(queryParams).forEach(([key, value]) => { if (value !== undefined) { searchParams.append(key, value.toString()); } }); const url = `/tenants/${tenant_id}${this.basePath}/webhook-logs${searchParams.toString() ? `?${searchParams.toString()}` : ''}`; return apiClient.get(url); } /** * Get webhook endpoint status for a POS system */ async getWebhookStatus(pos_system: POSSystem): Promise<{ pos_system: string; status: string; endpoint: string; supported_events: { events: string[]; format: string; authentication: string; }; last_received?: string; total_received: number; }> { const url = `/webhooks/${pos_system}/status`; return apiClient.get(url); } /** * Process webhook (typically called by POS systems, but useful for testing) */ async processWebhook(params: { pos_system: POSSystem; payload: any; signature?: string; headers?: Record; }): Promise<{ status: string; message?: string; success?: boolean; received?: boolean; }> { const { pos_system, payload, signature, headers = {} } = params; const requestHeaders: Record = { 'Content-Type': 'application/json', ...headers, }; if (signature) { requestHeaders['X-Webhook-Signature'] = signature; } const url = `/webhooks/${pos_system}`; // Note: This would typically be called by the POS system, not the frontend // This method is mainly for testing webhook processing return apiClient.post(url, payload); } // ============================================================================ // UTILITY METHODS // ============================================================================ /** * Format price for display */ formatPrice(amount: number, currency: string = 'EUR'): string { return new Intl.NumberFormat('es-ES', { style: 'currency', currency: currency, }).format(amount); } /** * Get POS system display name */ getPOSSystemDisplayName(posSystem: POSSystem): string { const systemNames: Record = { square: 'Square POS', toast: 'Toast POS', lightspeed: 'Lightspeed POS', }; return systemNames[posSystem] || posSystem; } /** * Get connection status color for UI */ getConnectionStatusColor(isConnected: boolean, healthStatus?: string): 'green' | 'yellow' | 'red' { if (!isConnected) return 'red'; if (healthStatus === 'healthy') return 'green'; if (healthStatus === 'unhealthy') return 'red'; return 'yellow'; // unknown status } /** * Get sync status color for UI */ getSyncStatusColor(status?: string): 'green' | 'yellow' | 'red' { switch (status) { case 'success': return 'green'; case 'failed': return 'red'; case 'partial': return 'yellow'; default: return 'yellow'; } } /** * Format sync interval for display */ formatSyncInterval(minutes: string): string { const mins = parseInt(minutes); if (mins < 60) { return `${mins} minutos`; } else if (mins < 1440) { const hours = Math.floor(mins / 60); return hours === 1 ? '1 hora' : `${hours} horas`; } else { const days = Math.floor(mins / 1440); return days === 1 ? '1 día' : `${days} días`; } } /** * Validate POS credentials based on system type */ validateCredentials(posSystem: POSSystem, credentials: Record): { isValid: boolean; errors: string[]; } { const errors: string[] = []; switch (posSystem) { case 'square': if (!credentials.application_id) errors.push('Application ID es requerido'); if (!credentials.access_token) errors.push('Access Token es requerido'); if (!credentials.location_id) errors.push('Location ID es requerido'); break; case 'toast': if (!credentials.api_key) errors.push('API Key es requerido'); if (!credentials.restaurant_guid) errors.push('Restaurant GUID es requerido'); if (!credentials.location_id) errors.push('Location ID es requerido'); break; case 'lightspeed': if (!credentials.api_key) errors.push('API Key es requerido'); if (!credentials.api_secret) errors.push('API Secret es requerido'); if (!credentials.account_id) errors.push('Account ID es requerido'); if (!credentials.shop_id) errors.push('Shop ID es requerido'); break; default: errors.push('Sistema POS no soportado'); } return { isValid: errors.length === 0, errors, }; } } // Export singleton instance export const posService = new POSService(); export default posService;