Add new API in the frontend
This commit is contained in:
655
frontend/src/api/hooks/pos.ts
Normal file
655
frontend/src/api/hooks/pos.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
697
frontend/src/api/types/pos.ts
Normal file
697
frontend/src/api/types/pos.ts
Normal 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 };
|
||||
Reference in New Issue
Block a user