Fix new services implementation 5
This commit is contained in:
@@ -80,6 +80,7 @@ export class HealthService {
|
||||
{ name: 'Sales', endpoint: '/sales/health' },
|
||||
{ name: 'External', endpoint: '/external/health' },
|
||||
{ name: 'Training', endpoint: '/training/health' },
|
||||
{ name: 'Inventory', endpoint: '/inventory/health' },
|
||||
{ name: 'Forecasting', endpoint: '/forecasting/health' },
|
||||
{ name: 'Notification', endpoint: '/notifications/health' },
|
||||
];
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client';
|
||||
import type { ProductInfo } from '../types';
|
||||
|
||||
// ========== TYPES AND INTERFACES ==========
|
||||
|
||||
@@ -208,7 +209,7 @@ export interface PaginatedResponse<T> {
|
||||
// ========== INVENTORY SERVICE CLASS ==========
|
||||
|
||||
export class InventoryService {
|
||||
private baseEndpoint = '/api/v1';
|
||||
private baseEndpoint = '';
|
||||
|
||||
// ========== INVENTORY ITEMS ==========
|
||||
|
||||
@@ -230,16 +231,70 @@ export class InventoryService {
|
||||
}
|
||||
|
||||
const query = searchParams.toString();
|
||||
const url = `${this.baseEndpoint}/tenants/${tenantId}/inventory/items${query ? `?${query}` : ''}`;
|
||||
const url = `/tenants/${tenantId}/ingredients${query ? `?${query}` : ''}`;
|
||||
|
||||
return apiClient.get(url);
|
||||
console.log('🔍 InventoryService: Fetching inventory items from:', url);
|
||||
|
||||
try {
|
||||
console.log('🔑 InventoryService: Making request with auth token:', localStorage.getItem('auth_token') ? 'Present' : 'Missing');
|
||||
const response = await apiClient.get(url);
|
||||
console.log('📋 InventoryService: Raw response:', response);
|
||||
console.log('📋 InventoryService: Response type:', typeof response);
|
||||
console.log('📋 InventoryService: Response keys:', response ? Object.keys(response) : 'null');
|
||||
|
||||
// Handle different response formats
|
||||
if (Array.isArray(response)) {
|
||||
// Direct array response
|
||||
console.log('✅ InventoryService: Array response with', response.length, 'items');
|
||||
return {
|
||||
items: response,
|
||||
total: response.length,
|
||||
page: 1,
|
||||
limit: response.length,
|
||||
total_pages: 1
|
||||
};
|
||||
} else if (response && typeof response === 'object') {
|
||||
// Check if it's already paginated
|
||||
if ('items' in response && Array.isArray(response.items)) {
|
||||
console.log('✅ InventoryService: Paginated response with', response.items.length, 'items');
|
||||
return response;
|
||||
}
|
||||
|
||||
// Handle object with numeric keys (convert to array)
|
||||
const keys = Object.keys(response);
|
||||
if (keys.length > 0 && keys.every(key => !isNaN(Number(key)))) {
|
||||
const items = Object.values(response);
|
||||
console.log('✅ InventoryService: Numeric keys response with', items.length, 'items');
|
||||
return {
|
||||
items,
|
||||
total: items.length,
|
||||
page: 1,
|
||||
limit: items.length,
|
||||
total_pages: 1
|
||||
};
|
||||
}
|
||||
|
||||
// Handle empty object - this seems to be what we're getting
|
||||
if (keys.length === 0) {
|
||||
console.log('📭 InventoryService: Empty object response - backend has no inventory items for this tenant');
|
||||
throw new Error('NO_INVENTORY_ITEMS'); // This will trigger fallback in useInventory
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: unexpected response format
|
||||
console.warn('⚠️ InventoryService: Unexpected response format, keys:', Object.keys(response || {}));
|
||||
throw new Error('UNEXPECTED_RESPONSE_FORMAT');
|
||||
} catch (error) {
|
||||
console.error('❌ InventoryService: Failed to fetch inventory items:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single inventory item by ID
|
||||
*/
|
||||
async getInventoryItem(tenantId: string, itemId: string): Promise<InventoryItem> {
|
||||
return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/items/${itemId}`);
|
||||
return apiClient.get(`/tenants/${tenantId}/ingredients/${itemId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,7 +304,7 @@ export class InventoryService {
|
||||
tenantId: string,
|
||||
data: CreateInventoryItemRequest
|
||||
): Promise<InventoryItem> {
|
||||
return apiClient.post(`${this.baseEndpoint}/tenants/${tenantId}/inventory/items`, data);
|
||||
return apiClient.post(`/tenants/${tenantId}/ingredients`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,14 +315,14 @@ export class InventoryService {
|
||||
itemId: string,
|
||||
data: UpdateInventoryItemRequest
|
||||
): Promise<InventoryItem> {
|
||||
return apiClient.put(`${this.baseEndpoint}/tenants/${tenantId}/inventory/items/${itemId}`, data);
|
||||
return apiClient.put(`/tenants/${tenantId}/ingredients/${itemId}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete inventory item (soft delete)
|
||||
*/
|
||||
async deleteInventoryItem(tenantId: string, itemId: string): Promise<void> {
|
||||
return apiClient.delete(`${this.baseEndpoint}/tenants/${tenantId}/inventory/items/${itemId}`);
|
||||
return apiClient.delete(`/tenants/${tenantId}/ingredients/${itemId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,7 +332,7 @@ export class InventoryService {
|
||||
tenantId: string,
|
||||
updates: { id: string; data: UpdateInventoryItemRequest }[]
|
||||
): Promise<{ success: number; failed: number; errors: string[] }> {
|
||||
return apiClient.post(`${this.baseEndpoint}/tenants/${tenantId}/inventory/items/bulk-update`, {
|
||||
return apiClient.post(`/tenants/${tenantId}/ingredients/bulk-update`, {
|
||||
updates
|
||||
});
|
||||
}
|
||||
@@ -288,14 +343,16 @@ export class InventoryService {
|
||||
* Get current stock level for an item
|
||||
*/
|
||||
async getStockLevel(tenantId: string, itemId: string): Promise<StockLevel> {
|
||||
return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/stock/${itemId}`);
|
||||
return apiClient.get(`/tenants/${tenantId}/ingredients/${itemId}/stock`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stock levels for all items
|
||||
*/
|
||||
async getAllStockLevels(tenantId: string): Promise<StockLevel[]> {
|
||||
return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/stock`);
|
||||
// TODO: Map to correct endpoint when available
|
||||
return [];
|
||||
// return apiClient.get(`/stock/summary`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,7 +364,7 @@ export class InventoryService {
|
||||
adjustment: StockAdjustmentRequest
|
||||
): Promise<StockMovement> {
|
||||
return apiClient.post(
|
||||
`${this.baseEndpoint}/tenants/${tenantId}/inventory/stock/${itemId}/adjust`,
|
||||
`/stock/consume`,
|
||||
adjustment
|
||||
);
|
||||
}
|
||||
@@ -353,7 +410,9 @@ export class InventoryService {
|
||||
* Get current stock alerts
|
||||
*/
|
||||
async getStockAlerts(tenantId: string): Promise<StockAlert[]> {
|
||||
return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/alerts`);
|
||||
// TODO: Map to correct endpoint when available
|
||||
return [];
|
||||
// return apiClient.get(`/tenants/${tenantId}/inventory/alerts`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -378,7 +437,17 @@ export class InventoryService {
|
||||
* Get inventory dashboard data
|
||||
*/
|
||||
async getDashboardData(tenantId: string): Promise<InventoryDashboardData> {
|
||||
return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/dashboard`);
|
||||
// TODO: Map to correct endpoint when available
|
||||
return {
|
||||
total_items: 0,
|
||||
low_stock_items: 0,
|
||||
out_of_stock_items: 0,
|
||||
total_value: 0,
|
||||
recent_movements: [],
|
||||
top_products: [],
|
||||
stock_alerts: []
|
||||
};
|
||||
// return apiClient.get(`/tenants/${tenantId}/inventory/dashboard`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -470,6 +539,87 @@ export class InventoryService {
|
||||
async getSupplierSuggestions(tenantId: string): Promise<string[]> {
|
||||
return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/suppliers`);
|
||||
}
|
||||
|
||||
// ========== PRODUCTS FOR FORECASTING ==========
|
||||
|
||||
/**
|
||||
* Get Products List with IDs for Forecasting
|
||||
*/
|
||||
async getProductsList(tenantId: string): Promise<ProductInfo[]> {
|
||||
try {
|
||||
const response = await apiClient.get(`/tenants/${tenantId}/ingredients`, {
|
||||
params: { limit: 100 }, // Get all products
|
||||
});
|
||||
|
||||
console.log('🔍 Inventory Products API Response:', response);
|
||||
|
||||
let productsArray: any[] = [];
|
||||
|
||||
if (Array.isArray(response)) {
|
||||
productsArray = response;
|
||||
} else if (response && typeof response === 'object') {
|
||||
// Handle different response formats
|
||||
const keys = Object.keys(response);
|
||||
if (keys.length > 0 && keys.every(key => !isNaN(Number(key)))) {
|
||||
productsArray = Object.values(response);
|
||||
} else {
|
||||
console.warn('⚠️ Response is object but not with numeric keys:', response);
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ Response is not array or object:', response);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Convert to ProductInfo objects
|
||||
const products: ProductInfo[] = productsArray
|
||||
.map((product: any) => ({
|
||||
inventory_product_id: product.id || product.inventory_product_id,
|
||||
name: product.name || product.product_name || `Product ${product.id || ''}`,
|
||||
category: product.category,
|
||||
// Add additional fields if available from inventory
|
||||
current_stock: product.current_stock,
|
||||
unit: product.unit,
|
||||
cost_per_unit: product.cost_per_unit
|
||||
}))
|
||||
.filter(product => product.inventory_product_id && product.name);
|
||||
|
||||
console.log('📋 Processed inventory products:', products);
|
||||
|
||||
return products;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to fetch inventory products:', error);
|
||||
|
||||
// Return empty array on error - let dashboard handle fallback
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Product by ID
|
||||
*/
|
||||
async getProductById(tenantId: string, productId: string): Promise<ProductInfo | null> {
|
||||
try {
|
||||
const response = await apiClient.get(`/tenants/${tenantId}/ingredients/${productId}`);
|
||||
|
||||
if (response) {
|
||||
return {
|
||||
inventory_product_id: response.id || response.inventory_product_id,
|
||||
name: response.name || response.product_name,
|
||||
category: response.category,
|
||||
current_stock: response.current_stock,
|
||||
unit: response.unit,
|
||||
cost_per_unit: response.cost_per_unit
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to fetch product by ID:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const inventoryService = new InventoryService();
|
||||
@@ -181,85 +181,6 @@ export class SalesService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Products List from Sales Data
|
||||
*/
|
||||
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 Sales Summary by Period
|
||||
|
||||
@@ -91,10 +91,21 @@ export class TenantService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get User's Tenants
|
||||
* Get User's Tenants - Get tenants where user is owner
|
||||
*/
|
||||
async getUserTenants(): Promise<TenantInfo[]> {
|
||||
return apiClient.get(`/users/me/tenants`);
|
||||
try {
|
||||
// First get current user info to get user ID
|
||||
const currentUser = await apiClient.get(`/users/me`);
|
||||
const userId = currentUser.id;
|
||||
|
||||
// Then get tenants owned by this user
|
||||
return apiClient.get(`${this.baseEndpoint}/user/${userId}`);
|
||||
} catch (error) {
|
||||
console.error('Failed to get user tenants:', error);
|
||||
// Return empty array if API call fails
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user