Add new API in the frontend
This commit is contained in:
@@ -62,11 +62,11 @@ export class AuthService {
|
||||
}
|
||||
|
||||
async getProfile(): Promise<UserResponse> {
|
||||
return apiClient.get<UserResponse>(`${this.baseUrl}/profile`);
|
||||
return apiClient.get<UserResponse>('/users/me');
|
||||
}
|
||||
|
||||
async updateProfile(updateData: UserUpdate): Promise<UserResponse> {
|
||||
return apiClient.put<UserResponse>(`${this.baseUrl}/profile`, updateData);
|
||||
return apiClient.put<UserResponse>('/users/me', updateData);
|
||||
}
|
||||
|
||||
async verifyEmail(
|
||||
|
||||
557
frontend/src/api/services/pos.ts
Normal file
557
frontend/src/api/services/pos.ts
Normal file
@@ -0,0 +1,557 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user