Fix new services implementation 10

This commit is contained in:
Urtzi Alfaro
2025-08-16 08:22:51 +02:00
parent 9de0f9943c
commit 119beb541f
5 changed files with 68 additions and 63 deletions

View File

@@ -548,7 +548,10 @@ export class InventoryService {
async getProductsList(tenantId: string): Promise<ProductInfo[]> { async getProductsList(tenantId: string): Promise<ProductInfo[]> {
try { try {
const response = await apiClient.get(`/tenants/${tenantId}/ingredients`, { 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); console.log('🔍 Inventory Products API Response:', response);

View File

@@ -90,31 +90,7 @@ export const useDashboard = () => {
try { try {
// 1. Get available products from inventory service // 1. Get available products from inventory service
let products: ProductInfo[] = []; const products = await getProductsList(tenantId);
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' }
];
}
// 2. Get weather data (Madrid coordinates) // 2. Get weather data (Madrid coordinates)
let weather = null; let weather = null;
try { try {
@@ -227,33 +203,12 @@ export const useDashboard = () => {
console.error('Error loading dashboard data:', error); console.error('Error loading dashboard data:', error);
setError(error instanceof Error ? error.message : 'Failed to load dashboard data'); setError(error instanceof Error ? error.message : 'Failed to load dashboard data');
// Set fallback data // Set empty fallback data on error
setDashboardData({ setDashboardData({
weather: { weather: null,
temperature: 18, todayForecasts: [],
description: 'Parcialmente nublado', metrics: null,
precipitation: 0 products: []
},
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' }
]
}); });
} finally { } finally {
setIsLoading(false); setIsLoading(false);

View File

@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { TrendingUp, TrendingDown, Calendar, Cloud, AlertTriangle, Info, RefreshCw } from 'lucide-react'; import { TrendingUp, TrendingDown, Calendar, Cloud, AlertTriangle, Info, RefreshCw } from 'lucide-react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { useForecast } from '../../api/hooks/useForecast'; import { useForecast } from '../../api/hooks/useForecast';
import { useInventory } from '../../api/hooks/useInventory'; import { useInventoryProducts } from '../../api/hooks/useInventory';
import { useTenantId } from '../../hooks/useTenantId'; import { useTenantId } from '../../hooks/useTenantId';
import type { ForecastResponse } from '../../api/types/forecasting'; import type { ForecastResponse } from '../../api/types/forecasting';
@@ -30,6 +30,7 @@ const ForecastPage: React.FC = () => {
const [forecastData, setForecastData] = useState<ForecastData[]>([]); const [forecastData, setForecastData] = useState<ForecastData[]>([]);
const [weatherAlert, setWeatherAlert] = useState<WeatherAlert | null>(null); const [weatherAlert, setWeatherAlert] = useState<WeatherAlert | null>(null);
const [isGenerating, setIsGenerating] = useState(false); const [isGenerating, setIsGenerating] = useState(false);
const [inventoryItems, setInventoryItems] = useState<any[]>([]);
// Hooks // Hooks
const { tenantId } = useTenantId(); const { tenantId } = useTenantId();
@@ -43,10 +44,10 @@ const ForecastPage: React.FC = () => {
exportForecasts exportForecasts
} = useForecast(); } = useForecast();
const { const {
items: inventoryItems, getProductsList,
isLoading: inventoryLoading, isLoading: inventoryLoading,
loadItems error: inventoryError
} = useInventory(false); // Disable auto-load, we'll load manually } = useInventoryProducts();
// Debug logging // Debug logging
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
@@ -81,10 +82,37 @@ const ForecastPage: React.FC = () => {
// Load inventory items on component mount // Load inventory items on component mount
useEffect(() => { useEffect(() => {
const loadProducts = async () => {
if (tenantId) { if (tenantId) {
loadItems(); 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
})));
} }
}, [tenantId, loadItems]); } 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 // Transform API forecasts to our local format
const transformForecastResponse = (forecast: ForecastResponse): ForecastData => { const transformForecastResponse = (forecast: ForecastResponse): ForecastData => {
@@ -142,8 +170,8 @@ const ForecastPage: React.FC = () => {
setIsGenerating(true); setIsGenerating(true);
try { try {
// Generate forecasts for top 3 products for the next 7 days // Generate forecasts for products with trained models only
const productsToForecast = inventoryItems.slice(0, 3); const productsToForecast = inventoryItems;
const chartData = []; const chartData = [];
// Generate data for the next 7 days // Generate data for the next 7 days

View File

@@ -151,6 +151,7 @@ async def list_ingredients(
skip: int = Query(0, ge=0, description="Number of records to skip"), 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"), limit: int = Query(100, ge=1, le=1000, description="Number of records to return"),
category: Optional[str] = Query(None, description="Filter by category"), 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_active: Optional[bool] = Query(None, description="Filter by active status"),
is_low_stock: Optional[bool] = Query(None, description="Filter by low stock 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"), needs_reorder: Optional[bool] = Query(None, description="Filter by reorder needed"),
@@ -171,6 +172,8 @@ async def list_ingredients(
filters = {} filters = {}
if category: if category:
filters['category'] = category filters['category'] = category
if product_type:
filters['product_type'] = product_type
if is_active is not None: if is_active is not None:
filters['is_active'] = is_active filters['is_active'] = is_active
if is_low_stock is not None: if is_low_stock is not None:

View File

@@ -101,6 +101,22 @@ class IngredientRepository(BaseRepository[Ingredient, IngredientCreate, Ingredie
if filters: if filters:
if filters.get('category'): if filters.get('category'):
query_filters['category'] = filters['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: if filters.get('is_active') is not None:
query_filters['is_active'] = filters['is_active'] query_filters['is_active'] = filters['is_active']
if filters.get('is_perishable') is not None: if filters.get('is_perishable') is not None: