Files
bakery-ia/frontend/src/api/services/pos.ts

557 lines
16 KiB
TypeScript
Raw Normal View History

2025-09-11 18:21:32 +02:00
/**
* 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<GetPOSConfigurationsResponse> {
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<GetPOSConfigurationsResponse>(url);
}
/**
* Create a new POS configuration
*/
async createPOSConfiguration(params: CreatePOSConfigurationRequest): Promise<CreatePOSConfigurationResponse> {
const { tenant_id, ...configData } = params;
const url = `/tenants/${tenant_id}${this.basePath}/configurations`;
return apiClient.post<CreatePOSConfigurationResponse>(url, configData);
}
/**
* Get a specific POS configuration
*/
async getPOSConfiguration(params: GetPOSConfigurationRequest): Promise<GetPOSConfigurationResponse> {
const { tenant_id, config_id } = params;
const url = `/tenants/${tenant_id}${this.basePath}/configurations/${config_id}`;
return apiClient.get<GetPOSConfigurationResponse>(url);
}
/**
* Update a POS configuration
*/
async updatePOSConfiguration(params: UpdatePOSConfigurationRequest): Promise<UpdatePOSConfigurationResponse> {
const { tenant_id, config_id, ...updateData } = params;
const url = `/tenants/${tenant_id}${this.basePath}/configurations/${config_id}`;
return apiClient.put<UpdatePOSConfigurationResponse>(url, updateData);
}
/**
* Delete a POS configuration
*/
async deletePOSConfiguration(params: DeletePOSConfigurationRequest): Promise<DeletePOSConfigurationResponse> {
const { tenant_id, config_id } = params;
const url = `/tenants/${tenant_id}${this.basePath}/configurations/${config_id}`;
return apiClient.delete<DeletePOSConfigurationResponse>(url);
}
/**
* Test connection to POS system
*/
async testPOSConnection(params: TestPOSConnectionRequest): Promise<TestPOSConnectionResponse> {
const { tenant_id, config_id } = params;
const url = `/tenants/${tenant_id}${this.basePath}/configurations/${config_id}/test-connection`;
return apiClient.post<TestPOSConnectionResponse>(url);
}
// ============================================================================
// SUPPORTED SYSTEMS
// ============================================================================
/**
* Get list of supported POS systems
*/
async getSupportedPOSSystems(): Promise<GetSupportedPOSSystemsResponse> {
const url = `${this.basePath}/supported-systems`;
return apiClient.get<GetSupportedPOSSystemsResponse>(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<string, string>;
}): Promise<{
status: string;
message?: string;
success?: boolean;
received?: boolean;
}> {
const { pos_system, payload, signature, headers = {} } = params;
const requestHeaders: Record<string, string> = {
'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<POSSystem, string> = {
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<string, any>): {
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;