Files
bakery-ia/frontend/src/api/services/data.service.ts
2025-08-04 22:46:05 +02:00

383 lines
9.9 KiB
TypeScript

// frontend/src/api/services/data.service.ts
/**
* Data Management Service
* Handles sales data operations
*/
import { apiClient } from '../client';
import { RequestTimeouts } from '../client/config';
import type {
SalesData,
SalesValidationResult,
SalesDataQuery,
SalesDataImport,
SalesImportResult,
DashboardStats,
PaginatedResponse,
ActivityItem,
} from '../types';
export class DataService {
/**
* Upload Sales History File
*/
async uploadSalesHistory(
tenantId: string,
file: File,
additionalData?: Record<string, any>
): Promise<SalesImportResult> {
// Determine file format
const fileName = file.name.toLowerCase();
let fileFormat: string;
if (fileName.endsWith('.csv')) {
fileFormat = 'csv';
} else if (fileName.endsWith('.json')) {
fileFormat = 'json';
} else if (fileName.endsWith('.xlsx') || fileName.endsWith('.xls')) {
fileFormat = 'excel';
} else {
fileFormat = 'csv'; // Default fallback
}
const uploadData = {
file_format: fileFormat,
...additionalData,
};
return apiClient.upload(
`/tenants/${tenantId}/sales/import`,
file,
uploadData,
{
timeout: RequestTimeouts.LONG,
}
);
}
/**
* Validate Sales Data
*/
async validateSalesData(
tenantId: string,
file: File
): Promise<SalesValidationResult> {
const fileName = file.name.toLowerCase();
let fileFormat: string;
if (fileName.endsWith('.csv')) {
fileFormat = 'csv';
} else if (fileName.endsWith('.json')) {
fileFormat = 'json';
} else if (fileName.endsWith('.xlsx') || fileName.endsWith('.xls')) {
fileFormat = 'excel';
} else {
fileFormat = 'csv';
}
return apiClient.upload(
`/tenants/${tenantId}/sales/import/validate`,
file,
{
file_format: fileFormat,
validate_only: true,
source: 'onboarding_upload',
},
{
timeout: RequestTimeouts.MEDIUM,
}
);
}
/**
* Get Sales Data
*/
async getSalesData(
tenantId: string,
query?: SalesDataQuery
): Promise<PaginatedResponse<SalesData>> {
return apiClient.get(`/tenants/${tenantId}/sales`, { params: query });
}
/**
* Get Single Sales Record
*/
async getSalesRecord(tenantId: string, recordId: string): Promise<SalesData> {
return apiClient.get(`/tenants/${tenantId}/sales/${recordId}`);
}
/**
* Update Sales Record
*/
async updateSalesRecord(
tenantId: string,
recordId: string,
data: Partial<SalesData>
): Promise<SalesData> {
return apiClient.put(`/tenants/${tenantId}/sales/${recordId}`, data);
}
/**
* Delete Sales Record
*/
async deleteSalesRecord(tenantId: string, recordId: string): Promise<{ message: string }> {
return apiClient.delete(`/tenants/${tenantId}/sales/${recordId}`);
}
/**
* Get Dashboard Statistics
*/
async getDashboardStats(tenantId: string): Promise<DashboardStats> {
return apiClient.get(`/tenants/${tenantId}/sales/stats`);
}
/**
* Get Analytics Data
*/
async getAnalytics(
tenantId: string,
params?: {
start_date?: string;
end_date?: string;
product_names?: string[];
metrics?: string[];
}
): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/analytics`, { params });
}
/**
* Export Sales Data
*/
async exportSalesData(
tenantId: string,
format: 'csv' | 'excel' | 'json',
query?: SalesDataQuery
): Promise<Blob> {
const response = await apiClient.request(`/tenants/${tenantId}/sales/export`, {
method: 'GET',
params: { ...query, format },
headers: {
'Accept': format === 'csv' ? 'text/csv' :
format === 'excel' ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' :
'application/json',
},
});
return new Blob([response], {
type: format === 'csv' ? 'text/csv' :
format === 'excel' ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' :
'application/json',
});
}
/**
* Get Recent Activity
*/
async getRecentActivity(tenantId: string, limit?: number): Promise<ActivityItem[]> {
return apiClient.get(`/tenants/${tenantId}/activity`, {
params: { limit },
});
}
/**
* Get Products List from Sales Data
* This should be added to the DataService class
*/
async getProductsList(tenantId: string): Promise<string[]> {
try {
const response = await apiClient.get(`/tenants/${tenantId}/sales/products`);
console.log('🔍 Products API Response Analysis:');
console.log('- Type:', typeof response);
console.log('- Is Array:', Array.isArray(response));
console.log('- Keys:', Object.keys(response || {}));
console.log('- Response:', response);
let productsArray: any[] = [];
// ✅ FIX: Handle different response formats
if (Array.isArray(response)) {
// Standard array response
productsArray = response;
console.log('✅ Response is already an array');
} else if (response && typeof response === 'object') {
// Object with numeric keys - convert to array
const keys = Object.keys(response);
if (keys.length > 0 && keys.every(key => !isNaN(Number(key)))) {
// Object has numeric keys like {0: {...}, 1: {...}}
productsArray = Object.values(response);
console.log('✅ Converted object with numeric keys to array');
} else {
console.warn('⚠️ Response is object but not with numeric keys:', response);
return [];
}
} else {
console.warn('⚠️ Response is not array or object:', response);
return [];
}
console.log('📦 Products array:', productsArray);
// Extract product names from the array
const productNames = productsArray
.map((product: any) => {
if (typeof product === 'string') {
return product;
}
if (product && typeof product === 'object') {
return product.product_name ||
product.name ||
product.productName ||
null;
}
return null;
})
.filter(Boolean) // Remove null/undefined values
.filter((name: string) => name.trim().length > 0); // Remove empty strings
console.log('📋 Extracted product names:', productNames);
if (productNames.length === 0) {
console.warn('⚠️ No valid product names extracted from response');
}
return productNames;
} catch (error) {
console.error('❌ Failed to fetch products list:', error);
// Return fallback products for Madrid bakery
return [
'Croissants',
'Pan de molde',
'Baguettes',
'Café',
'Napolitanas',
'Pan integral',
'Magdalenas',
'Churros'
];
}
}
/**
* Get Current Weather Data
* This should be added to the DataService class
*/
async getCurrentWeather(
tenantId: string,
lat: number,
lon: number
): Promise<{
temperature: number;
description: string;
precipitation: number;
humidity?: number;
wind_speed?: number;
}> {
try {
// ✅ FIX 1: Correct endpoint path with tenant ID
const endpoint = `/tenants/${tenantId}/weather/current`;
// ✅ FIX 2: Correct parameter names (latitude/longitude, not lat/lon)
const response = await apiClient.get(endpoint, {
params: {
latitude: lat, // Backend expects 'latitude'
longitude: lon // Backend expects 'longitude'
}
});
// ✅ FIX 3: Handle the actual backend response structure
// Backend returns WeatherDataResponse:
// {
// "date": "2025-08-04T12:00:00Z",
// "temperature": 25.5,
// "precipitation": 0.0,
// "humidity": 65.0,
// "wind_speed": 10.2,
// "pressure": 1013.2,
// "description": "Partly cloudy",
// "source": "aemet"
// }
console.log('Weather API response:', response);
// Map backend response to expected frontend format
return {
temperature: response.temperature || 18,
description: response.description || 'Parcialmente nublado',
precipitation: response.precipitation || 0,
humidity: response.humidity || 65,
wind_speed: response.wind_speed || 10
};
} catch (error) {
console.error('Failed to fetch weather from backend:', error);
// Fallback weather for Madrid
return {
temperature: 18,
description: 'Parcialmente nublado',
precipitation: 0,
humidity: 65,
wind_speed: 10
};
}
}
/**
* Get Weather Forecast
* This should be added to the DataService class
*/
async getWeatherForecast(
lat: number,
lon: number,
days: number = 7
): Promise<any[]> {
return apiClient.get(`/data/weather/forecast`, {
params: { lat, lon, days }
});
}
/**
* Get Sales Summary by Period
* This should be added to the DataService class
*/
async getSalesSummary(
tenantId: string,
period: 'daily' | 'weekly' | 'monthly' = 'daily'
): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/sales/summary`, {
params: { period }
});
}
/**
* Get Sales Analytics
* This should be added to the DataService class
*/
async getSalesAnalytics(
tenantId: string,
startDate?: string,
endDate?: string
): Promise<{
total_revenue: number;
waste_reduction_percentage?: number;
forecast_accuracy?: number;
stockout_events?: number;
}> {
return apiClient.get(`/tenants/${tenantId}/sales/analytics`, {
params: {
start_date: startDate,
end_date: endDate
}
});
}
}
export const dataService = new DataService();