Add new API in the frontend

This commit is contained in:
Urtzi Alfaro
2025-09-11 18:21:32 +02:00
parent 523b926854
commit 55f31a3630
16 changed files with 2719 additions and 1806 deletions

View File

@@ -0,0 +1,655 @@
/**
* POS React Query hooks
* Provides data fetching and mutation hooks for POS operations
*/
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
import { posService } from '../services/pos';
import type {
POSConfiguration,
POSTransaction,
POSSyncLog,
POSWebhookLog,
GetPOSConfigurationsRequest,
GetPOSConfigurationsResponse,
CreatePOSConfigurationRequest,
CreatePOSConfigurationResponse,
GetPOSConfigurationRequest,
GetPOSConfigurationResponse,
UpdatePOSConfigurationRequest,
UpdatePOSConfigurationResponse,
DeletePOSConfigurationRequest,
DeletePOSConfigurationResponse,
TestPOSConnectionRequest,
TestPOSConnectionResponse,
GetSupportedPOSSystemsResponse,
POSSystem,
} from '../types/pos';
import { ApiError } from '../client';
// ============================================================================
// QUERY KEYS
// ============================================================================
export const posKeys = {
all: ['pos'] as const,
// Configurations
configurations: () => [...posKeys.all, 'configurations'] as const,
configurationsList: (tenantId: string, filters?: { pos_system?: POSSystem; is_active?: boolean }) =>
[...posKeys.configurations(), 'list', tenantId, filters] as const,
configuration: (tenantId: string, configId: string) =>
[...posKeys.configurations(), 'detail', tenantId, configId] as const,
// Supported Systems
supportedSystems: () => [...posKeys.all, 'supported-systems'] as const,
// Transactions
transactions: () => [...posKeys.all, 'transactions'] as const,
transactionsList: (tenantId: string, filters?: any) =>
[...posKeys.transactions(), 'list', tenantId, filters] as const,
transaction: (tenantId: string, transactionId: string) =>
[...posKeys.transactions(), 'detail', tenantId, transactionId] as const,
// Sync Logs
syncLogs: () => [...posKeys.all, 'sync-logs'] as const,
syncLogsList: (tenantId: string, filters?: any) =>
[...posKeys.syncLogs(), 'list', tenantId, filters] as const,
// Webhook Logs
webhookLogs: () => [...posKeys.all, 'webhook-logs'] as const,
webhookLogsList: (tenantId: string, filters?: any) =>
[...posKeys.webhookLogs(), 'list', tenantId, filters] as const,
} as const;
// ============================================================================
// CONFIGURATION QUERIES
// ============================================================================
/**
* Get POS configurations for a tenant
*/
export const usePOSConfigurations = (
params: GetPOSConfigurationsRequest,
options?: Omit<UseQueryOptions<GetPOSConfigurationsResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<GetPOSConfigurationsResponse, ApiError>({
queryKey: posKeys.configurationsList(params.tenant_id, {
pos_system: params.pos_system,
is_active: params.is_active
}),
queryFn: () => posService.getPOSConfigurations(params),
enabled: !!params.tenant_id,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
};
/**
* Get a specific POS configuration
*/
export const usePOSConfiguration = (
params: GetPOSConfigurationRequest,
options?: Omit<UseQueryOptions<GetPOSConfigurationResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<GetPOSConfigurationResponse, ApiError>({
queryKey: posKeys.configuration(params.tenant_id, params.config_id),
queryFn: () => posService.getPOSConfiguration(params),
enabled: !!params.tenant_id && !!params.config_id,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
/**
* Get supported POS systems
*/
export const useSupportedPOSSystems = (
options?: Omit<UseQueryOptions<GetSupportedPOSSystemsResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<GetSupportedPOSSystemsResponse, ApiError>({
queryKey: posKeys.supportedSystems(),
queryFn: () => posService.getSupportedPOSSystems(),
staleTime: 30 * 60 * 1000, // 30 minutes - this data rarely changes
...options,
});
};
// ============================================================================
// CONFIGURATION MUTATIONS
// ============================================================================
/**
* Create a new POS configuration
*/
export const useCreatePOSConfiguration = (
options?: UseMutationOptions<CreatePOSConfigurationResponse, ApiError, CreatePOSConfigurationRequest>
) => {
const queryClient = useQueryClient();
return useMutation<CreatePOSConfigurationResponse, ApiError, CreatePOSConfigurationRequest>({
mutationFn: (params) => posService.createPOSConfiguration(params),
onSuccess: (data, variables) => {
// Invalidate and refetch configurations list
queryClient.invalidateQueries({
queryKey: posKeys.configurationsList(variables.tenant_id)
});
// If we have the created configuration, add it to the cache
if (data.configuration) {
queryClient.setQueryData(
posKeys.configuration(variables.tenant_id, data.id),
{ configuration: data.configuration }
);
}
},
...options,
});
};
/**
* Update a POS configuration
*/
export const useUpdatePOSConfiguration = (
options?: UseMutationOptions<UpdatePOSConfigurationResponse, ApiError, UpdatePOSConfigurationRequest>
) => {
const queryClient = useQueryClient();
return useMutation<UpdatePOSConfigurationResponse, ApiError, UpdatePOSConfigurationRequest>({
mutationFn: (params) => posService.updatePOSConfiguration(params),
onSuccess: (data, variables) => {
// Invalidate and refetch configurations list
queryClient.invalidateQueries({
queryKey: posKeys.configurationsList(variables.tenant_id)
});
// Update the specific configuration cache if we have the updated data
if (data.configuration) {
queryClient.setQueryData(
posKeys.configuration(variables.tenant_id, variables.config_id),
{ configuration: data.configuration }
);
} else {
// Invalidate the specific configuration to refetch
queryClient.invalidateQueries({
queryKey: posKeys.configuration(variables.tenant_id, variables.config_id)
});
}
},
...options,
});
};
/**
* Delete a POS configuration
*/
export const useDeletePOSConfiguration = (
options?: UseMutationOptions<DeletePOSConfigurationResponse, ApiError, DeletePOSConfigurationRequest>
) => {
const queryClient = useQueryClient();
return useMutation<DeletePOSConfigurationResponse, ApiError, DeletePOSConfigurationRequest>({
mutationFn: (params) => posService.deletePOSConfiguration(params),
onSuccess: (data, variables) => {
// Remove from configurations list cache
queryClient.invalidateQueries({
queryKey: posKeys.configurationsList(variables.tenant_id)
});
// Remove the specific configuration from cache
queryClient.removeQueries({
queryKey: posKeys.configuration(variables.tenant_id, variables.config_id)
});
},
...options,
});
};
/**
* Test POS connection
*/
export const useTestPOSConnection = (
options?: UseMutationOptions<TestPOSConnectionResponse, ApiError, TestPOSConnectionRequest>
) => {
const queryClient = useQueryClient();
return useMutation<TestPOSConnectionResponse, ApiError, TestPOSConnectionRequest>({
mutationFn: (params) => posService.testPOSConnection(params),
onSuccess: (data, variables) => {
// Invalidate the configurations list to refresh connection status
queryClient.invalidateQueries({
queryKey: posKeys.configurationsList(variables.tenant_id)
});
// Invalidate the specific configuration to refresh connection status
queryClient.invalidateQueries({
queryKey: posKeys.configuration(variables.tenant_id, variables.config_id)
});
},
...options,
});
};
// ============================================================================
// TRANSACTION QUERIES
// ============================================================================
/**
* Get POS transactions for a tenant (Updated to match backend)
*/
export const usePOSTransactions = (
params: {
tenant_id: string;
pos_system?: string;
start_date?: string;
end_date?: string;
status?: string;
is_synced?: boolean;
limit?: number;
offset?: number;
},
options?: Omit<UseQueryOptions<{
transactions: POSTransaction[];
total: number;
has_more: boolean;
summary: {
total_amount: number;
transaction_count: number;
sync_status: {
synced: number;
pending: number;
failed: number;
};
};
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: posKeys.transactionsList(params.tenant_id, params),
queryFn: () => posService.getPOSTransactions(params),
enabled: !!params.tenant_id,
staleTime: 30 * 1000, // 30 seconds - transaction data should be fresh
...options,
});
};
/**
* Get a specific POS transaction
*/
export const usePOSTransaction = (
params: {
tenant_id: string;
transaction_id: string;
},
options?: Omit<UseQueryOptions<{
transaction: POSTransaction;
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: posKeys.transaction(params.tenant_id, params.transaction_id),
queryFn: () => posService.getPOSTransaction(params),
enabled: !!params.tenant_id && !!params.transaction_id,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
// ============================================================================
// SYNC OPERATIONS
// ============================================================================
/**
* Trigger manual sync
*/
export const useTriggerManualSync = (
options?: UseMutationOptions<
{
sync_id: string;
message: string;
status: string;
sync_type: string;
data_types: string[];
estimated_duration: string;
},
ApiError,
{
tenant_id: string;
config_id: string;
sync_type?: 'full' | 'incremental';
data_types?: string[];
from_date?: string;
to_date?: string;
}
>
) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (params) => posService.triggerManualSync(params),
onSuccess: (data, variables) => {
// Invalidate sync logs to show the new sync
queryClient.invalidateQueries({
queryKey: posKeys.syncLogsList(variables.tenant_id)
});
// Invalidate configurations to update last sync info
queryClient.invalidateQueries({
queryKey: posKeys.configurationsList(variables.tenant_id)
});
},
...options,
});
};
/**
* Get sync status for a configuration
*/
export const usePOSSyncStatus = (
params: {
tenant_id: string;
config_id: string;
limit?: number;
},
options?: Omit<UseQueryOptions<{
current_sync: any;
last_successful_sync: any;
recent_syncs: any[];
sync_health: {
status: string;
success_rate: number;
average_duration_minutes: number;
last_error?: string;
};
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...posKeys.configurations(), 'sync-status', params.tenant_id, params.config_id],
queryFn: () => posService.getSyncStatus(params),
enabled: !!params.tenant_id && !!params.config_id,
staleTime: 30 * 1000, // 30 seconds - sync status should be fresh
...options,
});
};
/**
* Get detailed sync logs for a configuration
*/
export const useDetailedSyncLogs = (
params: {
tenant_id: string;
config_id: string;
limit?: number;
offset?: number;
status?: string;
sync_type?: string;
data_type?: string;
},
options?: Omit<UseQueryOptions<{
logs: any[];
total: number;
has_more: boolean;
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...posKeys.syncLogs(), 'detailed', params.tenant_id, params.config_id, params],
queryFn: () => posService.getDetailedSyncLogs(params),
enabled: !!params.tenant_id && !!params.config_id,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
/**
* Sync single transaction
*/
export const useSyncSingleTransaction = (
options?: UseMutationOptions<
{ message: string; transaction_id: string; sync_status: string; sales_record_id: string },
ApiError,
{ tenant_id: string; transaction_id: string; force?: boolean }
>
) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (params) => posService.syncSingleTransaction(params),
onSuccess: (data, variables) => {
// Invalidate transactions list to update sync status
queryClient.invalidateQueries({
queryKey: posKeys.transactionsList(variables.tenant_id)
});
// Invalidate specific transaction
queryClient.invalidateQueries({
queryKey: posKeys.transaction(variables.tenant_id, variables.transaction_id)
});
},
...options,
});
};
/**
* Get sync performance analytics
*/
export const usePOSSyncAnalytics = (
params: {
tenant_id: string;
days?: number;
},
options?: Omit<UseQueryOptions<{
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[];
};
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...posKeys.all, 'analytics', params.tenant_id, params.days],
queryFn: () => posService.getSyncAnalytics(params),
enabled: !!params.tenant_id,
staleTime: 5 * 60 * 1000, // 5 minutes - analytics don't change frequently
...options,
});
};
/**
* Resync failed transactions
*/
export const useResyncFailedTransactions = (
options?: UseMutationOptions<
{ message: string; job_id: string; scope: string; estimated_transactions: number },
ApiError,
{ tenant_id: string; days_back?: number }
>
) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (params) => posService.resyncFailedTransactions(params),
onSuccess: (data, variables) => {
// Invalidate sync logs and analytics
queryClient.invalidateQueries({
queryKey: posKeys.syncLogsList(variables.tenant_id)
});
queryClient.invalidateQueries({
queryKey: [...posKeys.all, 'analytics', variables.tenant_id]
});
},
...options,
});
};
/**
* Get sync logs
*/
export const usePOSSyncLogs = (
params: {
tenant_id: string;
config_id?: string;
status?: string;
limit?: number;
offset?: number;
},
options?: Omit<UseQueryOptions<{
sync_logs: POSSyncLog[];
total: number;
has_more: boolean;
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: posKeys.syncLogsList(params.tenant_id, params),
queryFn: () => posService.getSyncLogs(params),
enabled: !!params.tenant_id,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
// ============================================================================
// WEBHOOK LOGS
// ============================================================================
/**
* Get webhook logs
*/
export const usePOSWebhookLogs = (
params: {
tenant_id: string;
pos_system?: POSSystem;
status?: string;
limit?: number;
offset?: number;
},
options?: Omit<UseQueryOptions<{
webhook_logs: POSWebhookLog[];
total: number;
has_more: boolean;
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: posKeys.webhookLogsList(params.tenant_id, params),
queryFn: () => posService.getWebhookLogs(params),
enabled: !!params.tenant_id,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
/**
* Get webhook status for a POS system
*/
export const useWebhookStatus = (
pos_system: POSSystem,
options?: Omit<UseQueryOptions<{
pos_system: string;
status: string;
endpoint: string;
supported_events: {
events: string[];
format: string;
authentication: string;
};
last_received?: string;
total_received: number;
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...posKeys.webhookLogs(), 'status', pos_system],
queryFn: () => posService.getWebhookStatus(pos_system),
enabled: !!pos_system,
staleTime: 5 * 60 * 1000, // 5 minutes - webhook status doesn't change often
...options,
});
};
// ============================================================================
// UTILITY HOOKS
// ============================================================================
/**
* Hook to get POS service utility functions
*/
export const usePOSUtils = () => {
return {
formatPrice: posService.formatPrice,
getPOSSystemDisplayName: posService.getPOSSystemDisplayName,
getConnectionStatusColor: posService.getConnectionStatusColor,
getSyncStatusColor: posService.getSyncStatusColor,
formatSyncInterval: posService.formatSyncInterval,
validateCredentials: posService.validateCredentials,
};
};
// ============================================================================
// COMPOSITE HOOKS (Convenience)
// ============================================================================
/**
* Hook that combines configurations and supported systems for the configuration UI
*/
export const usePOSConfigurationData = (tenantId: string) => {
const configurationsQuery = usePOSConfigurations(
{ tenant_id: tenantId },
{ enabled: !!tenantId }
);
const supportedSystemsQuery = useSupportedPOSSystems();
return {
configurations: configurationsQuery.data?.configurations || [],
supportedSystems: supportedSystemsQuery.data?.systems || [],
isLoading: configurationsQuery.isLoading || supportedSystemsQuery.isLoading,
error: configurationsQuery.error || supportedSystemsQuery.error,
refetch: () => {
configurationsQuery.refetch();
supportedSystemsQuery.refetch();
},
};
};
/**
* Hook for POS configuration management with all CRUD operations
*/
export const usePOSConfigurationManager = (tenantId: string) => {
const utils = usePOSUtils();
const createMutation = useCreatePOSConfiguration();
const updateMutation = useUpdatePOSConfiguration();
const deleteMutation = useDeletePOSConfiguration();
const testConnectionMutation = useTestPOSConnection();
return {
// Utility functions
...utils,
// Mutations
createConfiguration: createMutation.mutateAsync,
updateConfiguration: updateMutation.mutateAsync,
deleteConfiguration: deleteMutation.mutateAsync,
testConnection: testConnectionMutation.mutateAsync,
// Mutation states
isCreating: createMutation.isPending,
isUpdating: updateMutation.isPending,
isDeleting: deleteMutation.isPending,
isTesting: testConnectionMutation.isPending,
// Errors
createError: createMutation.error,
updateError: updateMutation.error,
deleteError: deleteMutation.error,
testError: testConnectionMutation.error,
};
};

View File

@@ -27,6 +27,7 @@ export { suppliersService } from './services/suppliers';
export { OrdersService } from './services/orders';
export { forecastingService } from './services/forecasting';
export { productionService } from './services/production';
export { posService } from './services/pos';
// Types - Auth
export type {
@@ -344,6 +345,33 @@ export {
ProductionPriorityEnum,
} from './types/production';
// Types - POS
export type {
POSConfiguration,
POSTransaction,
POSTransactionItem,
POSWebhookLog,
POSSyncLog,
POSSystemInfo,
POSProviderConfig,
POSCredentialsField,
GetPOSConfigurationsRequest,
GetPOSConfigurationsResponse,
CreatePOSConfigurationRequest,
CreatePOSConfigurationResponse,
UpdatePOSConfigurationRequest,
UpdatePOSConfigurationResponse,
TestPOSConnectionRequest,
TestPOSConnectionResponse,
POSSyncSettings,
SyncHealth,
SyncAnalytics,
TransactionSummary,
WebhookStatus,
POSSystem,
POSEnvironment,
} from './types/pos';
// Hooks - Auth
export {
useAuthProfile,
@@ -632,6 +660,32 @@ export {
productionKeys,
} from './hooks/production';
// Hooks - POS
export {
usePOSConfigurations,
usePOSConfiguration,
useSupportedPOSSystems,
useCreatePOSConfiguration,
useUpdatePOSConfiguration,
useDeletePOSConfiguration,
useTestPOSConnection,
usePOSTransactions,
usePOSTransaction,
useTriggerManualSync,
usePOSSyncStatus,
useDetailedSyncLogs,
useSyncSingleTransaction,
usePOSSyncAnalytics,
useResyncFailedTransactions,
usePOSSyncLogs,
usePOSWebhookLogs,
useWebhookStatus,
usePOSUtils,
usePOSConfigurationData,
usePOSConfigurationManager,
posKeys,
} from './hooks/pos';
// Query Key Factories (for advanced usage)
export {
authKeys,
@@ -650,4 +704,5 @@ export {
dataImportKeys,
forecastingKeys,
productionKeys,
posKeys,
};

View File

@@ -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(

View 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;

View File

@@ -0,0 +1,697 @@
/**
* POS API Types
* Based on services/pos/app/models/ backend implementation
*/
// ============================================================================
// CORE POS TYPES
// ============================================================================
export type POSSystem = 'square' | 'toast' | 'lightspeed';
export type POSEnvironment = 'sandbox' | 'production';
export type POSTransactionType = 'sale' | 'refund' | 'void' | 'exchange';
export type POSTransactionStatus = 'completed' | 'pending' | 'failed' | 'refunded' | 'voided';
export type POSPaymentMethod = 'card' | 'cash' | 'digital_wallet' | 'other';
export type POSPaymentStatus = 'paid' | 'pending' | 'failed';
export type POSOrderType = 'dine_in' | 'takeout' | 'delivery' | 'pickup';
export type POSSyncStatus = 'success' | 'failed' | 'partial';
export type POSHealthStatus = 'healthy' | 'unhealthy' | 'unknown';
export type POSSyncType = 'full' | 'incremental' | 'manual' | 'webhook_triggered';
export type POSSyncDirection = 'inbound' | 'outbound' | 'bidirectional';
export type POSDataType = 'transactions' | 'products' | 'customers' | 'orders';
export type POSWebhookStatus = 'received' | 'processing' | 'processed' | 'failed';
// ============================================================================
// POS CONFIGURATION
// ============================================================================
export interface POSConfiguration {
// Primary identifiers
id: string;
tenant_id: string;
// POS Provider Information
pos_system: POSSystem;
provider_name: string;
// Configuration Status
is_active: boolean;
is_connected: boolean;
// Authentication & Credentials (encrypted)
encrypted_credentials?: string;
webhook_url?: string;
webhook_secret?: string;
// Provider-specific Settings
environment: POSEnvironment;
location_id?: string;
merchant_id?: string;
// Sync Configuration
sync_enabled: boolean;
sync_interval_minutes: string;
auto_sync_products: boolean;
auto_sync_transactions: boolean;
// Last Sync Information
last_sync_at?: string;
last_successful_sync_at?: string;
last_sync_status?: POSSyncStatus;
last_sync_message?: string;
// Provider-specific Configuration (JSON)
provider_settings?: Record<string, any>;
// Connection Health
last_health_check_at?: string;
health_status: POSHealthStatus;
health_message?: string;
// Timestamps
created_at: string;
updated_at: string;
// Metadata
created_by?: string;
notes?: string;
}
// ============================================================================
// POS TRANSACTIONS
// ============================================================================
export interface POSTransaction {
// Primary identifiers
id: string;
tenant_id: string;
pos_config_id: string;
// POS Provider Information
pos_system: POSSystem;
external_transaction_id: string;
external_order_id?: string;
// Transaction Details
transaction_type: POSTransactionType;
status: POSTransactionStatus;
// Financial Information
subtotal: number;
tax_amount: number;
tip_amount: number;
discount_amount: number;
total_amount: number;
currency: string;
// Payment Information
payment_method?: POSPaymentMethod;
payment_status?: POSPaymentStatus;
// Transaction Timing
transaction_date: string;
pos_created_at: string;
pos_updated_at?: string;
// Location & Staff
location_id?: string;
location_name?: string;
staff_id?: string;
staff_name?: string;
// Customer Information
customer_id?: string;
customer_email?: string;
customer_phone?: string;
// Order Context
order_type?: POSOrderType;
table_number?: string;
receipt_number?: string;
// Sync Status
is_synced_to_sales: boolean;
sales_record_id?: string;
sync_attempted_at?: string;
sync_completed_at?: string;
sync_error?: string;
sync_retry_count: number;
// Raw Data
raw_data?: Record<string, any>;
// Processing Status
is_processed: boolean;
processing_error?: string;
// Duplicate Detection
is_duplicate: boolean;
duplicate_of?: string;
// Timestamps
created_at: string;
updated_at: string;
// Related Items
items?: POSTransactionItem[];
}
export interface POSTransactionItem {
// Primary identifiers
id: string;
transaction_id: string;
tenant_id: string;
// POS Item Information
external_item_id?: string;
sku?: string;
// Product Details
product_name: string;
product_category?: string;
product_subcategory?: string;
// Quantity & Pricing
quantity: number;
unit_price: number;
total_price: number;
// Discounts & Modifiers
discount_amount: number;
tax_amount: number;
// Modifiers (e.g., extra shot, no foam for coffee)
modifiers?: Record<string, any>;
// Inventory Mapping
inventory_product_id?: string;
is_mapped_to_inventory: boolean;
// Sync Status
is_synced_to_sales: boolean;
sync_error?: string;
// Raw Data
raw_data?: Record<string, any>;
// Timestamps
created_at: string;
updated_at: string;
}
// ============================================================================
// POS WEBHOOK LOGS
// ============================================================================
export interface POSWebhookLog {
// Primary identifiers
id: string;
tenant_id?: string;
// POS Provider Information
pos_system: POSSystem;
webhook_type: string;
// Request Information
method: string;
url_path: string;
query_params?: Record<string, any>;
headers?: Record<string, any>;
// Payload
raw_payload: string;
payload_size: number;
content_type?: string;
// Security
signature?: string;
is_signature_valid?: boolean;
source_ip?: string;
// Processing Status
status: POSWebhookStatus;
processing_started_at?: string;
processing_completed_at?: string;
processing_duration_ms?: number;
// Error Handling
error_message?: string;
error_code?: string;
retry_count: number;
max_retries: number;
// Response Information
response_status_code?: number;
response_body?: string;
response_sent_at?: string;
// Event Metadata
event_id?: string;
event_timestamp?: string;
sequence_number?: number;
// Business Data References
transaction_id?: string;
order_id?: string;
customer_id?: string;
// Internal References
created_transaction_id?: string;
updated_transaction_id?: string;
// Duplicate Detection
is_duplicate: boolean;
duplicate_of?: string;
// Processing Priority
priority: string;
// Debugging Information
user_agent?: string;
forwarded_for?: string;
request_id?: string;
// Timestamps
received_at: string;
created_at: string;
updated_at: string;
}
// ============================================================================
// POS SYNC LOGS
// ============================================================================
export interface POSSyncLog {
// Primary identifiers
id: string;
tenant_id: string;
pos_config_id: string;
// Sync Operation Details
sync_type: POSSyncType;
sync_direction: POSSyncDirection;
data_type: POSDataType;
// POS Provider Information
pos_system: POSSystem;
// Sync Status
status: string; // started, in_progress, completed, failed, cancelled
// Timing Information
started_at: string;
completed_at?: string;
duration_seconds?: number;
// Date Range for Sync
sync_from_date?: string;
sync_to_date?: string;
// Statistics
records_requested: number;
records_processed: number;
records_created: number;
records_updated: number;
records_skipped: number;
records_failed: number;
// API Usage Statistics
api_calls_made: number;
api_rate_limit_hits: number;
total_api_time_ms: number;
// Error Information
error_message?: string;
error_code?: string;
error_details?: Record<string, any>;
// Retry Information
retry_attempt: number;
max_retries: number;
parent_sync_id?: string;
// Configuration Snapshot
sync_configuration?: Record<string, any>;
// Progress Tracking
current_page?: number;
total_pages?: number;
current_batch?: number;
total_batches?: number;
progress_percentage?: number;
// Data Quality
validation_errors?: Record<string, any>[];
data_quality_score?: number;
// Performance Metrics
memory_usage_mb?: number;
cpu_usage_percentage?: number;
network_bytes_received?: number;
network_bytes_sent?: number;
// Business Impact
revenue_synced?: number;
transactions_synced: number;
// Trigger Information
triggered_by?: string; // system, user, webhook, schedule
triggered_by_user_id?: string;
trigger_details?: Record<string, any>;
// External References
external_batch_id?: string;
webhook_log_id?: string;
// Timestamps
created_at: string;
updated_at: string;
// Metadata
notes?: string;
tags?: string[];
}
// ============================================================================
// SUPPORTED POS SYSTEMS
// ============================================================================
export interface POSSystemInfo {
id: POSSystem;
name: string;
description: string;
features: string[];
supported_regions: string[];
}
export interface POSCredentialsField {
field: string;
label: string;
type: 'text' | 'password' | 'url' | 'select';
placeholder?: string;
required: boolean;
help_text?: string;
options?: { value: string; label: string }[];
}
export interface POSProviderConfig {
id: POSSystem;
name: string;
logo: string;
description: string;
features: string[];
required_fields: POSCredentialsField[];
}
// ============================================================================
// SYNC ANALYTICS & PERFORMANCE TYPES
// ============================================================================
export interface SyncHealth {
status: string;
success_rate: number;
average_duration_minutes: number;
last_error?: string;
}
export interface SyncFrequency {
daily_average: number;
peak_day?: string;
peak_count: number;
}
export interface ErrorAnalysis {
common_errors: any[];
error_trends: any[];
}
export interface SyncAnalytics {
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: SyncFrequency;
error_analysis: ErrorAnalysis;
}
export interface TransactionSummary {
total_amount: number;
transaction_count: number;
sync_status: {
synced: number;
pending: number;
failed: number;
};
}
// ============================================================================
// WEBHOOK TYPES
// ============================================================================
export interface WebhookSupportedEvents {
events: string[];
format: string;
authentication: string;
}
export interface WebhookStatus {
pos_system: string;
status: string;
endpoint: string;
supported_events: WebhookSupportedEvents;
last_received?: string;
total_received: number;
}
// ============================================================================
// BASE POS CLIENT TYPES (matching backend integrations)
// ============================================================================
export interface POSCredentialsData {
pos_system: POSSystem;
environment: POSEnvironment;
api_key?: string;
api_secret?: string;
access_token?: string;
application_id?: string;
merchant_id?: string;
location_id?: string;
webhook_secret?: string;
additional_params?: Record<string, any>;
}
export interface ClientPOSTransaction {
external_id: string;
transaction_type: string;
status: string;
total_amount: number;
subtotal: number;
tax_amount: number;
tip_amount: number;
discount_amount: number;
currency: string;
transaction_date: string;
payment_method?: string;
payment_status?: string;
location_id?: string;
location_name?: string;
staff_id?: string;
staff_name?: string;
customer_id?: string;
customer_email?: string;
order_type?: string;
table_number?: string;
receipt_number?: string;
external_order_id?: string;
items: ClientPOSTransactionItem[];
raw_data: Record<string, any>;
}
export interface ClientPOSTransactionItem {
external_id?: string;
sku?: string;
name: string;
category?: string;
quantity: number;
unit_price: number;
total_price: number;
discount_amount: number;
tax_amount: number;
modifiers?: Record<string, any>;
raw_data?: Record<string, any>;
}
export interface ClientPOSProduct {
external_id: string;
name: string;
sku?: string;
category?: string;
subcategory?: string;
price: number;
description?: string;
is_active: boolean;
raw_data: Record<string, any>;
}
export interface SyncResult {
success: boolean;
records_processed: number;
records_created: number;
records_updated: number;
records_skipped: number;
records_failed: number;
errors: string[];
warnings: string[];
duration_seconds: number;
api_calls_made: number;
}
// ============================================================================
// API REQUEST/RESPONSE TYPES
// ============================================================================
// GET /tenants/{tenant_id}/pos/configurations
export interface GetPOSConfigurationsRequest {
tenant_id: string;
pos_system?: POSSystem;
is_active?: boolean;
}
export interface GetPOSConfigurationsResponse {
configurations: POSConfiguration[];
total: number;
supported_systems: POSSystem[];
}
// POST /tenants/{tenant_id}/pos/configurations
export interface CreatePOSConfigurationRequest {
tenant_id: string;
pos_system: POSSystem;
provider_name: string;
environment: POSEnvironment;
credentials: Record<string, any>;
sync_settings?: {
auto_sync_enabled: boolean;
sync_interval_minutes: number;
sync_sales: boolean;
sync_inventory: boolean;
sync_customers: boolean;
};
webhook_url?: string;
location_id?: string;
merchant_id?: string;
notes?: string;
}
export interface CreatePOSConfigurationResponse {
message: string;
id: string;
configuration?: POSConfiguration;
}
// GET /tenants/{tenant_id}/pos/configurations/{config_id}
export interface GetPOSConfigurationRequest {
tenant_id: string;
config_id: string;
}
export interface GetPOSConfigurationResponse {
configuration: POSConfiguration;
}
// PUT /tenants/{tenant_id}/pos/configurations/{config_id}
export interface UpdatePOSConfigurationRequest {
tenant_id: string;
config_id: string;
provider_name?: string;
credentials?: Record<string, any>;
sync_settings?: {
auto_sync_enabled?: boolean;
sync_interval_minutes?: number;
sync_sales?: boolean;
sync_inventory?: boolean;
sync_customers?: boolean;
};
webhook_url?: string;
location_id?: string;
merchant_id?: string;
notes?: string;
is_active?: boolean;
}
export interface UpdatePOSConfigurationResponse {
message: string;
configuration?: POSConfiguration;
}
// DELETE /tenants/{tenant_id}/pos/configurations/{config_id}
export interface DeletePOSConfigurationRequest {
tenant_id: string;
config_id: string;
}
export interface DeletePOSConfigurationResponse {
message: string;
success: boolean;
}
// POST /tenants/{tenant_id}/pos/configurations/{config_id}/test-connection
export interface TestPOSConnectionRequest {
tenant_id: string;
config_id: string;
}
export interface TestPOSConnectionResponse {
success: boolean;
message: string;
tested_at: string;
}
// GET /pos/supported-systems
export interface GetSupportedPOSSystemsResponse {
systems: POSSystemInfo[];
}
// ============================================================================
// UTILITY TYPES
// ============================================================================
export interface POSApiError {
message: string;
status?: number;
code?: string;
details?: any;
}
export interface POSSyncSettings {
auto_sync_enabled: boolean;
sync_interval_minutes: number;
sync_sales: boolean;
sync_inventory: boolean;
sync_customers: boolean;
}
export interface POSConnectionTestResult {
success: boolean;
message: string;
tested_at: string;
}
// For backward compatibility with existing BakeryConfigPage
export type { POSProviderConfig, POSSyncSettings as SyncSettings };