Fix new services implementation 10
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
if (tenantId) {
|
const loadProducts = async () => {
|
||||||
loadItems();
|
if (tenantId) {
|
||||||
}
|
try {
|
||||||
}, [tenantId, loadItems]);
|
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
|
// 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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user