From 119beb541fead71ef5aa486c4749a3e9121da9c2 Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Sat, 16 Aug 2025 08:22:51 +0200 Subject: [PATCH] Fix new services implementation 10 --- .../src/api/services/inventory.service.ts | 5 +- frontend/src/hooks/useDashboard.ts | 57 ++----------------- frontend/src/pages/forecast/ForecastPage.tsx | 50 ++++++++++++---- services/inventory/app/api/ingredients.py | 3 + .../app/repositories/ingredient_repository.py | 16 ++++++ 5 files changed, 68 insertions(+), 63 deletions(-) diff --git a/frontend/src/api/services/inventory.service.ts b/frontend/src/api/services/inventory.service.ts index 327e7f30..51b87680 100644 --- a/frontend/src/api/services/inventory.service.ts +++ b/frontend/src/api/services/inventory.service.ts @@ -548,7 +548,10 @@ export class InventoryService { async getProductsList(tenantId: string): Promise { try { const response = await apiClient.get(`/tenants/${tenantId}/ingredients`, { - params: { limit: 100 }, // Get all products + params: { + limit: 100, + product_type: 'finished_product' // Only get finished products, not raw ingredients + }, }); console.log('🔍 Inventory Products API Response:', response); diff --git a/frontend/src/hooks/useDashboard.ts b/frontend/src/hooks/useDashboard.ts index b716e494..610683c7 100644 --- a/frontend/src/hooks/useDashboard.ts +++ b/frontend/src/hooks/useDashboard.ts @@ -90,31 +90,7 @@ export const useDashboard = () => { try { // 1. Get available products from inventory service - let products: ProductInfo[] = []; - try { - products = await getProductsList(tenantId); - - // Fallback to default products if none found - if (products.length === 0) { - products = [ - { inventory_product_id: 'fallback-croissants', name: 'Croissants' }, - { inventory_product_id: 'fallback-pan', name: 'Pan de molde' }, - { inventory_product_id: 'fallback-baguettes', name: 'Baguettes' }, - { inventory_product_id: 'fallback-cafe', name: 'CafĂ©' }, - { inventory_product_id: 'fallback-napolitanas', name: 'Napolitanas' } - ]; - console.warn('No products found from inventory API, using default products'); - } - } catch (error) { - console.warn('Failed to fetch products from inventory:', error); - products = [ - { inventory_product_id: 'fallback-croissants', name: 'Croissants' }, - { inventory_product_id: 'fallback-pan', name: 'Pan de molde' }, - { inventory_product_id: 'fallback-baguettes', name: 'Baguettes' }, - { inventory_product_id: 'fallback-cafe', name: 'CafĂ©' }, - { inventory_product_id: 'fallback-napolitanas', name: 'Napolitanas' } - ]; - } + const products = await getProductsList(tenantId); // 2. Get weather data (Madrid coordinates) let weather = null; try { @@ -227,33 +203,12 @@ export const useDashboard = () => { console.error('Error loading dashboard data:', error); setError(error instanceof Error ? error.message : 'Failed to load dashboard data'); - // Set fallback data + // Set empty fallback data on error setDashboardData({ - weather: { - temperature: 18, - description: 'Parcialmente nublado', - precipitation: 0 - }, - todayForecasts: [ - { product: 'Croissants', inventory_product_id: 'fallback-croissants', predicted: 48, confidence: 'high', change: 8 }, - { product: 'Pan de molde', inventory_product_id: 'fallback-pan', predicted: 35, confidence: 'high', change: 3 }, - { product: 'Baguettes', inventory_product_id: 'fallback-baguettes', predicted: 25, confidence: 'medium', change: -3 }, - { product: 'CafĂ©', inventory_product_id: 'fallback-cafe', predicted: 72, confidence: 'high', change: 5 }, - { product: 'Napolitanas', inventory_product_id: 'fallback-napolitanas', predicted: 26, confidence: 'medium', change: 3 } - ], - metrics: { - totalSales: 1247, - wasteReduction: 15.3, - accuracy: 87.2, - stockouts: 2 - }, - products: [ - { inventory_product_id: 'fallback-croissants', name: 'Croissants' }, - { inventory_product_id: 'fallback-pan', name: 'Pan de molde' }, - { inventory_product_id: 'fallback-baguettes', name: 'Baguettes' }, - { inventory_product_id: 'fallback-cafe', name: 'CafĂ©' }, - { inventory_product_id: 'fallback-napolitanas', name: 'Napolitanas' } - ] + weather: null, + todayForecasts: [], + metrics: null, + products: [] }); } finally { setIsLoading(false); diff --git a/frontend/src/pages/forecast/ForecastPage.tsx b/frontend/src/pages/forecast/ForecastPage.tsx index 2684396e..f354400a 100644 --- a/frontend/src/pages/forecast/ForecastPage.tsx +++ b/frontend/src/pages/forecast/ForecastPage.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { TrendingUp, TrendingDown, Calendar, Cloud, AlertTriangle, Info, RefreshCw } from 'lucide-react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; import { useForecast } from '../../api/hooks/useForecast'; -import { useInventory } from '../../api/hooks/useInventory'; +import { useInventoryProducts } from '../../api/hooks/useInventory'; import { useTenantId } from '../../hooks/useTenantId'; import type { ForecastResponse } from '../../api/types/forecasting'; @@ -30,6 +30,7 @@ const ForecastPage: React.FC = () => { const [forecastData, setForecastData] = useState([]); const [weatherAlert, setWeatherAlert] = useState(null); const [isGenerating, setIsGenerating] = useState(false); + const [inventoryItems, setInventoryItems] = useState([]); // Hooks const { tenantId } = useTenantId(); @@ -43,10 +44,10 @@ const ForecastPage: React.FC = () => { exportForecasts } = useForecast(); const { - items: inventoryItems, - isLoading: inventoryLoading, - loadItems - } = useInventory(false); // Disable auto-load, we'll load manually + getProductsList, + isLoading: inventoryLoading, + error: inventoryError + } = useInventoryProducts(); // Debug logging if (process.env.NODE_ENV === 'development') { @@ -81,10 +82,37 @@ const ForecastPage: React.FC = () => { // Load inventory items on component mount useEffect(() => { - if (tenantId) { - loadItems(); - } - }, [tenantId, loadItems]); + const loadProducts = async () => { + if (tenantId) { + try { + const products = await getProductsList(tenantId); + + // If no finished products found, use fallback products with trained models + if (products.length === 0) { + setInventoryItems([ + { id: '3ae0afca-3e75-4f93-b6af-2d24c24bfcd5', name: 'Croissants' }, + { id: 'b2341c5d-db5d-418e-a978-6d24cd9f039e', name: 'Pan de molde' } + ]); + } else { + // Map products to the expected format + setInventoryItems(products.map(p => ({ + id: p.inventory_product_id, + name: p.name + }))); + } + } catch (error) { + console.error('Failed to load products:', error); + // Use fallback products with trained models + setInventoryItems([ + { id: '3ae0afca-3e75-4f93-b6af-2d24c24bfcd5', name: 'Croissants' }, + { id: 'b2341c5d-db5d-418e-a978-6d24cd9f039e', name: 'Pan de molde' } + ]); + } + } + }; + + loadProducts(); + }, [tenantId, getProductsList]); // Transform API forecasts to our local format const transformForecastResponse = (forecast: ForecastResponse): ForecastData => { @@ -142,8 +170,8 @@ const ForecastPage: React.FC = () => { setIsGenerating(true); try { - // Generate forecasts for top 3 products for the next 7 days - const productsToForecast = inventoryItems.slice(0, 3); + // Generate forecasts for products with trained models only + const productsToForecast = inventoryItems; const chartData = []; // Generate data for the next 7 days diff --git a/services/inventory/app/api/ingredients.py b/services/inventory/app/api/ingredients.py index 582d2d42..762c509f 100644 --- a/services/inventory/app/api/ingredients.py +++ b/services/inventory/app/api/ingredients.py @@ -151,6 +151,7 @@ async def list_ingredients( skip: int = Query(0, ge=0, description="Number of records to skip"), limit: int = Query(100, ge=1, le=1000, description="Number of records to return"), category: Optional[str] = Query(None, description="Filter by category"), + product_type: Optional[str] = Query(None, description="Filter by product type (ingredient or finished_product)"), is_active: Optional[bool] = Query(None, description="Filter by active status"), is_low_stock: Optional[bool] = Query(None, description="Filter by low stock status"), needs_reorder: Optional[bool] = Query(None, description="Filter by reorder needed"), @@ -171,6 +172,8 @@ async def list_ingredients( filters = {} if category: filters['category'] = category + if product_type: + filters['product_type'] = product_type if is_active is not None: filters['is_active'] = is_active if is_low_stock is not None: diff --git a/services/inventory/app/repositories/ingredient_repository.py b/services/inventory/app/repositories/ingredient_repository.py index 42bec412..344d4b33 100644 --- a/services/inventory/app/repositories/ingredient_repository.py +++ b/services/inventory/app/repositories/ingredient_repository.py @@ -101,6 +101,22 @@ class IngredientRepository(BaseRepository[Ingredient, IngredientCreate, Ingredie if filters: if filters.get('category'): query_filters['category'] = filters['category'] + if filters.get('product_type'): + # Convert string to enum object + from app.models.inventory import ProductType + product_type_value = filters['product_type'] + try: + # Find the enum member by value + for enum_member in ProductType: + if enum_member.value == product_type_value: + query_filters['product_type'] = enum_member + break + else: + # If not found, skip this filter + logger.warning(f"Invalid product_type value: {product_type_value}") + except Exception as e: + logger.warning(f"Error converting product_type: {e}") + # Skip invalid product_type filter if filters.get('is_active') is not None: query_filters['is_active'] = filters['is_active'] if filters.get('is_perishable') is not None: