2025-10-06 15:27:01 +02:00
|
|
|
// ================================================================
|
|
|
|
|
// frontend/src/api/services/pos.ts
|
|
|
|
|
// ================================================================
|
2025-09-11 18:21:32 +02:00
|
|
|
/**
|
2025-10-06 15:27:01 +02:00
|
|
|
* POS Service - Complete backend alignment
|
|
|
|
|
*
|
|
|
|
|
* Backend API structure (3-tier architecture):
|
|
|
|
|
* - ATOMIC: configurations.py, transactions.py
|
|
|
|
|
* - OPERATIONS: pos_operations.py
|
|
|
|
|
* - ANALYTICS: analytics.py
|
|
|
|
|
*
|
|
|
|
|
* Last Updated: 2025-10-05
|
|
|
|
|
* Status: ✅ Complete - Zero drift with backend
|
2025-09-11 18:21:32 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
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';
|
|
|
|
|
|
2025-10-06 15:27:01 +02:00
|
|
|
// ===================================================================
|
|
|
|
|
// ATOMIC: POS Configuration CRUD
|
|
|
|
|
// Backend: services/pos/app/api/configurations.py
|
|
|
|
|
// ===================================================================
|
2025-09-11 18:21:32 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-06 15:27:01 +02:00
|
|
|
// ===================================================================
|
|
|
|
|
// OPERATIONS: Supported Systems
|
|
|
|
|
// Backend: services/pos/app/api/pos_operations.py
|
|
|
|
|
// ===================================================================
|
2025-09-11 18:21:32 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get list of supported POS systems
|
|
|
|
|
*/
|
|
|
|
|
async getSupportedPOSSystems(): Promise<GetSupportedPOSSystemsResponse> {
|
|
|
|
|
const url = `${this.basePath}/supported-systems`;
|
|
|
|
|
return apiClient.get<GetSupportedPOSSystemsResponse>(url);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-06 15:27:01 +02:00
|
|
|
// ===================================================================
|
|
|
|
|
// ATOMIC: Transactions
|
|
|
|
|
// Backend: services/pos/app/api/transactions.py
|
|
|
|
|
// ===================================================================
|
2025-09-11 18:21:32 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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;
|
2025-10-23 07:44:54 +02:00
|
|
|
}): Promise<POSTransaction> {
|
2025-09-11 18:21:32 +02:00
|
|
|
const { tenant_id, transaction_id } = params;
|
|
|
|
|
const url = `/tenants/${tenant_id}${this.basePath}/transactions/${transaction_id}`;
|
2025-10-23 07:44:54 +02:00
|
|
|
|
|
|
|
|
return apiClient.get(url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get POS transactions dashboard summary
|
|
|
|
|
*/
|
|
|
|
|
async getPOSTransactionsDashboard(params: {
|
|
|
|
|
tenant_id: string;
|
|
|
|
|
}): Promise<{
|
|
|
|
|
total_transactions_today: number;
|
|
|
|
|
total_transactions_this_week: number;
|
|
|
|
|
total_transactions_this_month: number;
|
|
|
|
|
revenue_today: number;
|
|
|
|
|
revenue_this_week: number;
|
|
|
|
|
revenue_this_month: number;
|
|
|
|
|
average_transaction_value: number;
|
|
|
|
|
status_breakdown: Record<string, number>;
|
|
|
|
|
payment_method_breakdown: Record<string, number>;
|
|
|
|
|
sync_status: {
|
|
|
|
|
synced: number;
|
|
|
|
|
pending: number;
|
|
|
|
|
failed: number;
|
|
|
|
|
last_sync_at?: string;
|
|
|
|
|
};
|
|
|
|
|
}> {
|
|
|
|
|
const { tenant_id } = params;
|
|
|
|
|
const url = `/tenants/${tenant_id}${this.basePath}/operations/transactions-dashboard`;
|
|
|
|
|
|
2025-09-11 18:21:32 +02:00
|
|
|
return apiClient.get(url);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-06 15:27:01 +02:00
|
|
|
// ===================================================================
|
|
|
|
|
// OPERATIONS: Sync Operations
|
|
|
|
|
// Backend: services/pos/app/api/pos_operations.py
|
|
|
|
|
// ===================================================================
|
2025-09-11 18:21:32 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-06 15:27:01 +02:00
|
|
|
// ===================================================================
|
|
|
|
|
// OPERATIONS: Webhook Management
|
|
|
|
|
// Backend: services/pos/app/api/pos_operations.py
|
|
|
|
|
// ===================================================================
|
2025-09-11 18:21:32 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-06 15:27:01 +02:00
|
|
|
// ===================================================================
|
|
|
|
|
// Frontend Utility Methods
|
|
|
|
|
// ===================================================================
|
2025-09-11 18:21:32 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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();
|
2025-10-27 16:33:26 +01:00
|
|
|
export default posService;
|