Improve the frontend 2

This commit is contained in:
Urtzi Alfaro
2025-10-29 06:58:05 +01:00
parent 858d985c92
commit 36217a2729
98 changed files with 6652 additions and 4230 deletions

View File

@@ -10,7 +10,7 @@ import { inventoryService } from '../services/inventory';
import { getAlertAnalytics } from '../services/alert_analytics';
import { getSustainabilityWidgetData } from '../services/sustainability';
import { ApiError } from '../client/apiClient';
import type { InventoryDashboardSummary } from '../types/dashboard';
import type { InventoryDashboardSummary } from '../types/inventory';
import type { AlertAnalytics } from '../services/alert_analytics';
import type { SalesAnalytics } from '../types/sales';
import type { OrdersDashboardSummary } from '../types/orders';
@@ -106,25 +106,12 @@ function calculateTrend(current: number, previous: number): number {
}
/**
* Calculate today's sales from sales records
* Calculate today's sales from sales records (REMOVED - Professional/Enterprise tier feature)
* Basic tier users don't get sales analytics on dashboard
*/
function calculateTodaySales(salesData?: SalesAnalytics): { amount: number; trend: number; productsSold: number; productsTrend: number } {
if (!salesData) {
return { amount: 0, trend: 0, productsSold: 0, productsTrend: 0 };
}
// Sales data should have today's revenue and comparison
const todayRevenue = salesData.total_revenue || 0;
const previousRevenue = salesData.previous_period_revenue || 0;
const todayUnits = salesData.total_units_sold || 0;
const previousUnits = salesData.previous_period_units_sold || 0;
return {
amount: todayRevenue,
trend: calculateTrend(todayRevenue, previousRevenue),
productsSold: todayUnits,
productsTrend: calculateTrend(todayUnits, previousUnits),
};
function calculateTodaySales(): { amount: number; trend: number; productsSold: number; productsTrend: number } {
// Return zero values - sales analytics not available for basic tier
return { amount: 0, trend: 0, productsSold: 0, productsTrend: 0 };
}
/**
@@ -135,27 +122,27 @@ function calculateOrdersMetrics(ordersData?: OrdersDashboardSummary): { pending:
return { pending: 0, today: 0, trend: 0 };
}
const pendingCount = ordersData.pending_orders_count || 0;
const todayCount = ordersData.orders_today_count || 0;
const yesterdayCount = ordersData.orders_yesterday_count || 0;
const pendingCount = ordersData.pending_orders || 0;
const todayCount = ordersData.total_orders_today || 0;
return {
pending: pendingCount,
today: todayCount,
trend: calculateTrend(todayCount, yesterdayCount),
trend: 0, // Trend calculation removed - needs historical data
};
}
/**
* Aggregate dashboard data from all services
* NOTE: Sales analytics removed - Professional/Enterprise tier feature
*/
function aggregateDashboardStats(data: AggregatedDashboardData): DashboardStats {
const sales = calculateTodaySales(data.sales);
const sales = calculateTodaySales(); // Returns zeros for basic tier
const orders = calculateOrdersMetrics(data.orders);
const criticalStockCount =
(data.inventory?.low_stock_count || 0) +
(data.inventory?.out_of_stock_count || 0);
(data.inventory?.low_stock_items || 0) +
(data.inventory?.out_of_stock_items || 0);
return {
// Alerts
@@ -167,20 +154,20 @@ function aggregateDashboardStats(data: AggregatedDashboardData): DashboardStats
ordersToday: orders.today,
ordersTrend: orders.trend,
// Sales
salesToday: sales.amount,
salesTrend: sales.trend,
salesCurrency: '€', // Default to EUR for bakery
// Sales (REMOVED - not available for basic tier)
salesToday: 0,
salesTrend: 0,
salesCurrency: '€',
// Inventory
criticalStock: criticalStockCount,
lowStockCount: data.inventory?.low_stock_count || 0,
outOfStockCount: data.inventory?.out_of_stock_count || 0,
expiringSoon: data.inventory?.expiring_soon_count || 0,
lowStockCount: data.inventory?.low_stock_items || 0,
outOfStockCount: data.inventory?.out_of_stock_items || 0,
expiringSoon: data.inventory?.expiring_soon_items || 0,
// Products
productsSoldToday: sales.productsSold,
productsSoldTrend: sales.productsTrend,
// Products (REMOVED - not available for basic tier)
productsSoldToday: 0,
productsSoldTrend: 0,
// Sustainability
wasteReductionPercentage: data.sustainability?.waste_reduction_percentage,
@@ -209,17 +196,13 @@ export const useDashboardStats = (
return useQuery<DashboardStats, ApiError>({
queryKey: dashboardKeys.stats(tenantId),
queryFn: async () => {
// Fetch all data in parallel
const [alertsData, ordersData, salesData, inventoryData, sustainabilityData] = await Promise.allSettled([
// Fetch all data in parallel (REMOVED sales analytics - Professional/Enterprise tier only)
const [alertsData, ordersData, inventoryData, sustainabilityData] = await Promise.allSettled([
getAlertAnalytics(tenantId, 7),
// Note: OrdersService methods are static
import('../services/orders').then(({ OrdersService }) =>
OrdersService.getDashboardSummary(tenantId)
),
// Fetch today's sales with comparison to yesterday
import('../services/sales').then(({ salesService }) =>
salesService.getSalesAnalytics(tenantId, todayStr, todayStr)
),
inventoryService.getDashboardSummary(tenantId),
getSustainabilityWidgetData(tenantId, 30), // 30 days for monthly savings
]);
@@ -228,7 +211,7 @@ export const useDashboardStats = (
const aggregatedData: AggregatedDashboardData = {
alerts: alertsData.status === 'fulfilled' ? alertsData.value : undefined,
orders: ordersData.status === 'fulfilled' ? ordersData.value : undefined,
sales: salesData.status === 'fulfilled' ? salesData.value : undefined,
sales: undefined, // REMOVED - Professional/Enterprise tier only
inventory: inventoryData.status === 'fulfilled' ? inventoryData.value : undefined,
sustainability: sustainabilityData.status === 'fulfilled' ? sustainabilityData.value : undefined,
};
@@ -240,9 +223,6 @@ export const useDashboardStats = (
if (ordersData.status === 'rejected') {
console.warn('[Dashboard] Failed to fetch orders:', ordersData.reason);
}
if (salesData.status === 'rejected') {
console.warn('[Dashboard] Failed to fetch sales:', salesData.reason);
}
if (inventoryData.status === 'rejected') {
console.warn('[Dashboard] Failed to fetch inventory:', inventoryData.reason);
}

View File

@@ -6,7 +6,7 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'react-hot-toast';
import { equipmentService } from '../services/equipment';
import type { Equipment } from '../types/equipment';
import type { Equipment, EquipmentDeletionSummary } from '../types/equipment';
// Query Keys
export const equipmentKeys = {
@@ -114,7 +114,7 @@ export function useUpdateEquipment(tenantId: string) {
}
/**
* Hook to delete equipment
* Hook to delete equipment (soft delete)
*/
export function useDeleteEquipment(tenantId: string) {
const queryClient = useQueryClient();
@@ -139,3 +139,46 @@ export function useDeleteEquipment(tenantId: string) {
},
});
}
/**
* Hook to hard delete equipment (permanent deletion)
*/
export function useHardDeleteEquipment(tenantId: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (equipmentId: string) =>
equipmentService.hardDeleteEquipment(tenantId, equipmentId),
onSuccess: (_, equipmentId) => {
// Remove from cache
queryClient.removeQueries({
queryKey: equipmentKeys.detail(tenantId, equipmentId)
});
// Invalidate lists to refresh
queryClient.invalidateQueries({ queryKey: equipmentKeys.lists() });
toast.success('Equipment permanently deleted');
},
onError: (error: any) => {
console.error('Error hard deleting equipment:', error);
toast.error(error.response?.data?.detail || 'Error permanently deleting equipment');
},
});
}
/**
* Hook to get equipment deletion summary
*/
export function useEquipmentDeletionSummary(
tenantId: string,
equipmentId: string,
options?: { enabled?: boolean }
) {
return useQuery({
queryKey: [...equipmentKeys.detail(tenantId, equipmentId), 'deletion-summary'],
queryFn: () => equipmentService.getEquipmentDeletionSummary(tenantId, equipmentId),
enabled: !!tenantId && !!equipmentId && (options?.enabled ?? true),
staleTime: 0, // Always fetch fresh data for dependency checks
});
}

View File

@@ -2,7 +2,7 @@
* Subscription hook for checking plan features and limits
*/
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect, useCallback, useRef } from 'react';
import { subscriptionService } from '../services/subscription';
import {
SUBSCRIPTION_TIERS,
@@ -10,6 +10,7 @@ import {
} from '../types/subscription';
import { useCurrentTenant } from '../../stores';
import { useAuthUser } from '../../stores/auth.store';
import { useSubscriptionEvents } from '../../contexts/SubscriptionEventsContext';
export interface SubscriptionFeature {
hasFeature: boolean;
@@ -40,9 +41,10 @@ export const useSubscription = () => {
loading: true,
});
const currentTenant = useCurrentTenant();
const currentTenant = useCurrentTenant();
const user = useAuthUser();
const tenantId = currentTenant?.id || user?.tenant_id;
const { notifySubscriptionChanged } = useSubscriptionEvents();
// Load subscription data
const loadSubscriptionData = useCallback(async () => {
@@ -62,6 +64,9 @@ export const useSubscription = () => {
features: usageSummary.usage || {},
loading: false,
});
// Notify subscribers that subscription data has changed
notifySubscriptionChanged();
} catch (error) {
console.error('Error loading subscription data:', error);
setSubscriptionInfo(prev => ({
@@ -70,7 +75,7 @@ export const useSubscription = () => {
error: 'Failed to load subscription data'
}));
}
}, [tenantId]);
}, [tenantId, notifySubscriptionChanged]);
useEffect(() => {
loadSubscriptionData();
@@ -177,4 +182,4 @@ export const useSubscription = () => {
};
};
export default useSubscription;
export default useSubscription;

View File

@@ -26,6 +26,9 @@ import type {
DeliveryReceiptConfirmation,
DeliverySearchParams,
PerformanceMetric,
SupplierPriceListCreate,
SupplierPriceListUpdate,
SupplierPriceListResponse,
} from '../types/suppliers';
// Query Keys Factory
@@ -228,6 +231,37 @@ export const useDelivery = (
});
};
// Supplier Price List Queries
export const useSupplierPriceLists = (
tenantId: string,
supplierId: string,
isActive: boolean = true,
options?: Omit<UseQueryOptions<SupplierPriceListResponse[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<SupplierPriceListResponse[], ApiError>({
queryKey: [...suppliersKeys.suppliers.detail(tenantId, supplierId), 'price-lists', isActive],
queryFn: () => suppliersService.getSupplierPriceLists(tenantId, supplierId, isActive),
enabled: !!tenantId && !!supplierId,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
export const useSupplierPriceList = (
tenantId: string,
supplierId: string,
priceListId: string,
options?: Omit<UseQueryOptions<SupplierPriceListResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<SupplierPriceListResponse, ApiError>({
queryKey: [...suppliersKeys.suppliers.detail(tenantId, supplierId), 'price-list', priceListId],
queryFn: () => suppliersService.getSupplierPriceList(tenantId, supplierId, priceListId),
enabled: !!tenantId && !!supplierId && !!priceListId,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
// Performance Queries
export const useSupplierPerformanceMetrics = (
tenantId: string,
@@ -236,7 +270,7 @@ export const useSupplierPerformanceMetrics = (
) => {
return useQuery<PerformanceMetric[], ApiError>({
queryKey: suppliersKeys.performance.metrics(tenantId, supplierId),
queryFn: () => suppliersService.getPerformanceMetrics(tenantId, supplierId),
queryFn: () => suppliersService.getSupplierPerformanceMetrics(tenantId, supplierId),
enabled: !!tenantId && !!supplierId,
staleTime: 10 * 60 * 1000, // 10 minutes
...options,
@@ -245,13 +279,13 @@ export const useSupplierPerformanceMetrics = (
export const usePerformanceAlerts = (
tenantId: string,
supplierId: string,
supplierId?: string,
options?: Omit<UseQueryOptions<any[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<any[], ApiError>({
queryKey: suppliersKeys.performance.alerts(tenantId, supplierId),
queryFn: () => suppliersService.evaluatePerformanceAlerts(tenantId, supplierId),
enabled: !!tenantId && !!supplierId,
queryFn: () => suppliersService.getPerformanceAlerts(tenantId, supplierId),
enabled: !!tenantId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
@@ -607,12 +641,108 @@ export const useConfirmDeliveryReceipt = (
});
};
// Supplier Price List Mutations
export const useCreateSupplierPriceList = (
options?: UseMutationOptions<
SupplierPriceListResponse,
ApiError,
{ tenantId: string; supplierId: string; priceListData: SupplierPriceListCreate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
SupplierPriceListResponse,
ApiError,
{ tenantId: string; supplierId: string; priceListData: SupplierPriceListCreate }
>({
mutationFn: ({ tenantId, supplierId, priceListData }) =>
suppliersService.createSupplierPriceList(tenantId, supplierId, priceListData),
onSuccess: (data, { tenantId, supplierId }) => {
// Add to cache
queryClient.setQueryData(
[...suppliersKeys.suppliers.detail(tenantId, supplierId), 'price-list', data.id],
data
);
// Invalidate price lists
queryClient.invalidateQueries({
queryKey: [...suppliersKeys.suppliers.detail(tenantId, supplierId), 'price-lists']
});
},
...options,
});
};
export const useUpdateSupplierPriceList = (
options?: UseMutationOptions<
SupplierPriceListResponse,
ApiError,
{ tenantId: string; supplierId: string; priceListId: string; priceListData: SupplierPriceListUpdate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
SupplierPriceListResponse,
ApiError,
{ tenantId: string; supplierId: string; priceListId: string; priceListData: SupplierPriceListUpdate }
>({
mutationFn: ({ tenantId, supplierId, priceListId, priceListData }) =>
suppliersService.updateSupplierPriceList(tenantId, supplierId, priceListId, priceListData),
onSuccess: (data, { tenantId, supplierId, priceListId }) => {
// Update cache
queryClient.setQueryData(
[...suppliersKeys.suppliers.detail(tenantId, supplierId), 'price-list', priceListId],
data
);
// Invalidate price lists
queryClient.invalidateQueries({
queryKey: [...suppliersKeys.suppliers.detail(tenantId, supplierId), 'price-lists']
});
},
...options,
});
};
export const useDeleteSupplierPriceList = (
options?: UseMutationOptions<
{ message: string },
ApiError,
{ tenantId: string; supplierId: string; priceListId: string }
>
) => {
const queryClient = useQueryClient();
return useMutation<
{ message: string },
ApiError,
{ tenantId: string; supplierId: string; priceListId: string }
>({
mutationFn: ({ tenantId, supplierId, priceListId }) =>
suppliersService.deleteSupplierPriceList(tenantId, supplierId, priceListId),
onSuccess: (_, { tenantId, supplierId, priceListId }) => {
// Remove from cache
queryClient.removeQueries({
queryKey: [...suppliersKeys.suppliers.detail(tenantId, supplierId), 'price-list', priceListId]
});
// Invalidate price lists
queryClient.invalidateQueries({
queryKey: [...suppliersKeys.suppliers.detail(tenantId, supplierId), 'price-lists']
});
},
...options,
});
};
// Performance Mutations
export const useCalculateSupplierPerformance = (
options?: UseMutationOptions<
{ message: string; calculation_id: string },
ApiError,
{ tenantId: string; supplierId: string; request?: PerformanceCalculationRequest }
{ tenantId: string; supplierId: string; request?: any }
>
) => {
const queryClient = useQueryClient();
@@ -620,7 +750,7 @@ export const useCalculateSupplierPerformance = (
return useMutation<
{ message: string; calculation_id: string },
ApiError,
{ tenantId: string; supplierId: string; request?: PerformanceCalculationRequest }
{ tenantId: string; supplierId: string; request?: any }
>({
mutationFn: ({ tenantId, supplierId, request }) =>
suppliersService.calculateSupplierPerformance(tenantId, supplierId, request),
@@ -641,7 +771,7 @@ export const useEvaluatePerformanceAlerts = (
options?: UseMutationOptions<
{ alerts_generated: number; message: string },
ApiError,
{ tenantId: string }
{ tenantId: string; supplierId?: string }
>
) => {
const queryClient = useQueryClient();
@@ -649,7 +779,7 @@ export const useEvaluatePerformanceAlerts = (
return useMutation<
{ alerts_generated: number; message: string },
ApiError,
{ tenantId: string }
{ tenantId: string; supplierId?: string }
>({
mutationFn: ({ tenantId, supplierId }) => suppliersService.evaluatePerformanceAlerts(tenantId, supplierId),
onSuccess: (_, { tenantId }) => {
@@ -677,11 +807,7 @@ export const useActiveSuppliersCount = (tenantId: string) => {
return statistics?.active_suppliers || 0;
};
export const usePendingOrdersCount = (supplierId?: string) => {
const { data: orders } = usePurchaseOrders({
supplier_id: supplierId,
status: 'pending_approval',
limit: 1000
});
return orders?.data?.length || 0;
};
export const usePendingOrdersCount = (queryParams?: PurchaseOrderSearchParams) => {
const { data: orders } = usePurchaseOrders('', queryParams);
return orders?.length || 0;
};

View File

@@ -9,7 +9,8 @@ import type {
EquipmentCreate,
EquipmentUpdate,
EquipmentResponse,
EquipmentListResponse
EquipmentListResponse,
EquipmentDeletionSummary
} from '../types/equipment';
class EquipmentService {
@@ -163,7 +164,7 @@ class EquipmentService {
}
/**
* Delete an equipment item
* Delete an equipment item (soft delete)
*/
async deleteEquipment(tenantId: string, equipmentId: string): Promise<void> {
await apiClient.delete(
@@ -173,6 +174,34 @@ class EquipmentService {
}
);
}
/**
* Permanently delete an equipment item (hard delete)
*/
async hardDeleteEquipment(tenantId: string, equipmentId: string): Promise<void> {
await apiClient.delete(
`${this.baseURL}/${tenantId}/production/equipment/${equipmentId}?permanent=true`,
{
headers: { 'X-Tenant-ID': tenantId }
}
);
}
/**
* Get deletion summary for an equipment item
*/
async getEquipmentDeletionSummary(
tenantId: string,
equipmentId: string
): Promise<EquipmentDeletionSummary> {
const data: EquipmentDeletionSummary = await apiClient.get(
`${this.baseURL}/${tenantId}/production/equipment/${equipmentId}/deletion-summary`,
{
headers: { 'X-Tenant-ID': tenantId }
}
);
return data;
}
}
export const equipmentService = new EquipmentService();

View File

@@ -20,25 +20,25 @@ import type {
SupplierResponse,
SupplierSummary,
SupplierApproval,
SupplierQueryParams,
SupplierSearchParams,
SupplierStatistics,
SupplierDeletionSummary,
TopSuppliersResponse,
SupplierResponse as SupplierResponse_,
PurchaseOrderCreate,
PurchaseOrderUpdate,
PurchaseOrderResponse,
PurchaseOrderApproval,
PurchaseOrderQueryParams,
PurchaseOrderSearchParams,
DeliveryCreate,
DeliveryUpdate,
DeliveryResponse,
DeliveryReceiptConfirmation,
DeliveryQueryParams,
PerformanceCalculationRequest,
PerformanceMetrics,
DeliverySearchParams,
PerformanceMetric,
PerformanceAlert,
PaginatedResponse,
ApiResponse,
SupplierPriceListCreate,
SupplierPriceListUpdate,
SupplierPriceListResponse
} from '../types/suppliers';
class SuppliersService {
@@ -59,10 +59,71 @@ class SuppliersService {
);
}
// ===================================================================
// ATOMIC: Supplier Price Lists CRUD
// Backend: services/suppliers/app/api/suppliers.py (price list endpoints)
// ===================================================================
async getSupplierPriceLists(
tenantId: string,
supplierId: string,
isActive: boolean = true
): Promise<SupplierPriceListResponse[]> {
const params = new URLSearchParams();
params.append('is_active', isActive.toString());
return apiClient.get<SupplierPriceListResponse[]>(
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}/price-lists?${params.toString()}`
);
}
async getSupplierPriceList(
tenantId: string,
supplierId: string,
priceListId: string
): Promise<SupplierPriceListResponse> {
return apiClient.get<SupplierPriceListResponse>(
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}/price-lists/${priceListId}`
);
}
async createSupplierPriceList(
tenantId: string,
supplierId: string,
priceListData: SupplierPriceListCreate
): Promise<SupplierPriceListResponse> {
return apiClient.post<SupplierPriceListResponse>(
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}/price-lists`,
priceListData
);
}
async updateSupplierPriceList(
tenantId: string,
supplierId: string,
priceListId: string,
priceListData: SupplierPriceListUpdate
): Promise<SupplierPriceListResponse> {
return apiClient.put<SupplierPriceListResponse>(
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}/price-lists/${priceListId}`,
priceListData
);
}
async deleteSupplierPriceList(
tenantId: string,
supplierId: string,
priceListId: string
): Promise<{ message: string }> {
return apiClient.delete<{ message: string }>(
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}/price-lists/${priceListId}`
);
}
async getSuppliers(
tenantId: string,
queryParams?: SupplierQueryParams
): Promise<PaginatedResponse<SupplierSummary>> {
queryParams?: SupplierSearchParams
): Promise<SupplierSummary[]> {
const params = new URLSearchParams();
if (queryParams?.search_term) params.append('search_term', queryParams.search_term);
@@ -70,11 +131,9 @@ class SuppliersService {
if (queryParams?.status) params.append('status', queryParams.status);
if (queryParams?.limit) params.append('limit', queryParams.limit.toString());
if (queryParams?.offset) params.append('offset', queryParams.offset.toString());
if (queryParams?.sort_by) params.append('sort_by', queryParams.sort_by);
if (queryParams?.sort_order) params.append('sort_order', queryParams.sort_order);
const queryString = params.toString() ? `?${params.toString()}` : '';
return apiClient.get<PaginatedResponse<SupplierSummary>>(
return apiClient.get<SupplierSummary[]>(
`${this.baseUrl}/${tenantId}/suppliers${queryString}`
);
}
@@ -142,10 +201,10 @@ class SuppliersService {
);
}
async getPurchaseOrders(
async getPurchaseOrders(
tenantId: string,
queryParams?: PurchaseOrderQueryParams
): Promise<PaginatedResponse<PurchaseOrderResponse>> {
queryParams?: PurchaseOrderSearchParams
): Promise<PurchaseOrderResponse[]> {
const params = new URLSearchParams();
if (queryParams?.supplier_id) params.append('supplier_id', queryParams.supplier_id);
@@ -155,11 +214,9 @@ class SuppliersService {
if (queryParams?.date_to) params.append('date_to', queryParams.date_to);
if (queryParams?.limit) params.append('limit', queryParams.limit.toString());
if (queryParams?.offset) params.append('offset', queryParams.offset.toString());
if (queryParams?.sort_by) params.append('sort_by', queryParams.sort_by);
if (queryParams?.sort_order) params.append('sort_order', queryParams.sort_order);
const queryString = params.toString() ? `?${params.toString()}` : '';
return apiClient.get<PaginatedResponse<PurchaseOrderResponse>>(
return apiClient.get<PurchaseOrderResponse[]>(
`${this.baseUrl}/${tenantId}/suppliers/purchase-orders${queryString}`
);
}
@@ -209,8 +266,8 @@ class SuppliersService {
async getDeliveries(
tenantId: string,
queryParams?: DeliveryQueryParams
): Promise<PaginatedResponse<DeliveryResponse>> {
queryParams?: DeliverySearchParams
): Promise<DeliveryResponse[]> {
const params = new URLSearchParams();
if (queryParams?.supplier_id) params.append('supplier_id', queryParams.supplier_id);
@@ -226,11 +283,9 @@ class SuppliersService {
}
if (queryParams?.limit) params.append('limit', queryParams.limit.toString());
if (queryParams?.offset) params.append('offset', queryParams.offset.toString());
if (queryParams?.sort_by) params.append('sort_by', queryParams.sort_by);
if (queryParams?.sort_order) params.append('sort_order', queryParams.sort_order);
const queryString = params.toString() ? `?${params.toString()}` : '';
return apiClient.get<PaginatedResponse<DeliveryResponse>>(
return apiClient.get<DeliveryResponse[]>(
`${this.baseUrl}/${tenantId}/suppliers/deliveries${queryString}`
);
}
@@ -276,8 +331,8 @@ class SuppliersService {
async getActiveSuppliers(
tenantId: string,
queryParams?: Omit<SupplierQueryParams, 'status'>
): Promise<PaginatedResponse<SupplierSummary>> {
queryParams?: SupplierSearchParams
): Promise<SupplierSummary[]> {
const params = new URLSearchParams();
if (queryParams?.search_term) params.append('search_term', queryParams.search_term);
if (queryParams?.supplier_type) params.append('supplier_type', queryParams.supplier_type);
@@ -285,10 +340,10 @@ class SuppliersService {
if (queryParams?.offset) params.append('offset', queryParams.offset.toString());
const queryString = params.toString() ? `?${params.toString()}` : '';
return apiClient.get<PaginatedResponse<SupplierSummary>>(
return apiClient.get<SupplierSummary[]>(
`${this.baseUrl}/${tenantId}/suppliers/operations/active${queryString}`
);
}
}
async getTopSuppliers(tenantId: string): Promise<TopSuppliersResponse> {
return apiClient.get<TopSuppliersResponse>(
@@ -356,11 +411,11 @@ class SuppliersService {
async getSupplierPerformanceMetrics(
tenantId: string,
supplierId: string
): Promise<PerformanceMetrics> {
return apiClient.get<PerformanceMetrics>(
): Promise<PerformanceMetric[]> {
return apiClient.get<PerformanceMetric[]>(
`${this.baseUrl}/${tenantId}/suppliers/analytics/performance/${supplierId}/metrics`
);
}
}
async evaluatePerformanceAlerts(
tenantId: string

View File

@@ -138,4 +138,15 @@ export interface EquipmentListResponse {
total_count: number;
page: number;
page_size: number;
}
export interface EquipmentDeletionSummary {
can_delete: boolean;
warnings: string[];
production_batches_count: number;
maintenance_records_count: number;
temperature_logs_count: number;
equipment_name?: string;
equipment_type?: string;
equipment_location?: string;
}

View File

@@ -22,7 +22,7 @@ export enum SupplierType {
export enum SupplierStatus {
ACTIVE = 'active',
INACTIVE = 'inactive',
INACTIVE = 'inactive',
PENDING_APPROVAL = 'pending_approval',
SUSPENDED = 'suspended',
BLACKLISTED = 'blacklisted'
@@ -114,7 +114,7 @@ export enum AlertType {
export enum AlertStatus {
ACTIVE = 'ACTIVE',
ACKNOWLEDGED = 'ACKNOWLEDGED',
ACKNOWLEDGED = 'ACKNOWLEDGED',
IN_PROGRESS = 'IN_PROGRESS',
RESOLVED = 'RESOLVED',
DISMISSED = 'DISMISSED'
@@ -139,6 +139,71 @@ export enum PerformancePeriod {
YEARLY = 'YEARLY'
}
// ===== SUPPLIER PRICE LIST SCHEMAS =====
export interface SupplierPriceListCreate {
inventory_product_id: string;
product_code?: string | null; // max_length=100
unit_price: number; // gt=0
unit_of_measure: string; // max_length=20
minimum_order_quantity?: number | null; // ge=1
price_per_unit: number; // gt=0
tier_pricing?: Record<string, any> | null; // [{quantity: 100, price: 2.50}, ...]
effective_date?: string; // Default: now()
expiry_date?: string | null;
is_active?: boolean; // Default: true
brand?: string | null; // max_length=100
packaging_size?: string | null; // max_length=50
origin_country?: string | null; // max_length=100
shelf_life_days?: number | null;
storage_requirements?: string | null;
quality_specs?: Record<string, any> | null;
allergens?: Record<string, any> | null;
}
export interface SupplierPriceListUpdate {
unit_price?: number | null; // gt=0
unit_of_measure?: string | null; // max_length=20
minimum_order_quantity?: number | null; // ge=1
tier_pricing?: Record<string, any> | null;
effective_date?: string | null;
expiry_date?: string | null;
is_active?: boolean | null;
brand?: string | null;
packaging_size?: string | null;
origin_country?: string | null;
shelf_life_days?: number | null;
storage_requirements?: string | null;
quality_specs?: Record<string, any> | null;
allergens?: Record<string, any> | null;
}
export interface SupplierPriceListResponse {
id: string;
tenant_id: string;
supplier_id: string;
inventory_product_id: string;
product_code: string | null;
unit_price: number;
unit_of_measure: string;
minimum_order_quantity: number | null;
price_per_unit: number;
tier_pricing: Record<string, any> | null;
effective_date: string;
expiry_date: string | null;
is_active: boolean;
brand: string | null;
packaging_size: string | null;
origin_country: string | null;
shelf_life_days: number | null;
storage_requirements: string | null;
quality_specs: Record<string, any> | null;
allergens: Record<string, any> | null;
created_at: string;
updated_at: string;
created_by: string;
updated_by: string;
}
// ===== SUPPLIER SCHEMAS =====
// Mirror: SupplierCreate from suppliers.py:23
@@ -222,7 +287,7 @@ export interface SupplierApproval {
// Mirror: SupplierResponse from suppliers.py:102
export interface SupplierResponse {
id: string;
id: string;
tenant_id: string;
name: string;
supplier_code: string | null;
@@ -245,7 +310,7 @@ export interface SupplierResponse {
country: string | null;
// Business terms
payment_terms: PaymentTerms;
payment_terms: PaymentTerms;
credit_limit: number | null;
currency: string;
standard_lead_time: number;
@@ -253,7 +318,7 @@ export interface SupplierResponse {
delivery_area: string | null;
// Performance metrics
quality_rating: number | null;
quality_rating: number | null;
delivery_rating: number | null;
total_orders: number;
total_amount: number;
@@ -264,7 +329,7 @@ export interface SupplierResponse {
rejection_reason: string | null;
// Additional information
notes: string | null;
notes: string | null;
certifications: Record<string, any> | null;
business_hours: Record<string, any> | null;
specializations: Record<string, any> | null;
@@ -346,12 +411,12 @@ export interface PurchaseOrderItemResponse {
// Mirror: PurchaseOrderCreate from suppliers.py (inferred)
export interface PurchaseOrderCreate {
supplier_id: string;
items: PurchaseOrderItemCreate[]; // min_items=1
items: PurchaseOrderItemCreate[]; // min_items=1
// Order details
reference_number?: string | null; // max_length=100
priority?: string; // Default: "normal", max_length=20
required_delivery_date?: string | null;
required_delivery_date?: string | null;
// Delivery info
delivery_address?: string | null;
@@ -360,12 +425,12 @@ export interface PurchaseOrderCreate {
delivery_phone?: string | null; // max_length=30
// Financial (all default=0, ge=0)
tax_amount?: number;
tax_amount?: number;
shipping_cost?: number;
discount_amount?: number;
// Additional
notes?: string | null;
notes?: string | null;
internal_notes?: string | null;
terms_and_conditions?: string | null;
}
@@ -376,7 +441,7 @@ export interface PurchaseOrderUpdate {
priority?: string | null;
required_delivery_date?: string | null;
estimated_delivery_date?: string | null;
supplier_reference?: string | null; // max_length=100
supplier_reference?: string | null; // max_length=100
delivery_address?: string | null;
delivery_instructions?: string | null;
delivery_contact?: string | null;
@@ -411,25 +476,25 @@ export interface PurchaseOrderResponse {
order_date: string;
reference_number: string | null;
priority: string;
required_delivery_date: string | null;
required_delivery_date: string | null;
estimated_delivery_date: string | null;
// Financial
subtotal: number;
tax_amount: number;
tax_amount: number;
shipping_cost: number;
discount_amount: number;
total_amount: number;
currency: string;
// Delivery
delivery_address: string | null;
delivery_address: string | null;
delivery_instructions: string | null;
delivery_contact: string | null;
delivery_phone: string | null;
// Approval
requires_approval: boolean;
requires_approval: boolean;
approved_by: string | null;
approved_at: string | null;
rejection_reason: string | null;
@@ -440,12 +505,12 @@ export interface PurchaseOrderResponse {
supplier_reference: string | null;
// Additional
notes: string | null;
notes: string | null;
internal_notes: string | null;
terms_and_conditions: string | null;
// Audit
created_at: string;
created_at: string;
updated_at: string;
created_by: string;
updated_by: string;
@@ -516,7 +581,7 @@ export interface DeliveryCreate {
delivery_phone?: string | null; // max_length=30
// Tracking
carrier_name?: string | null; // max_length=200
carrier_name?: string | null; // max_length=200
tracking_number?: string | null; // max_length=100
// Additional
@@ -525,7 +590,7 @@ export interface DeliveryCreate {
// Mirror: DeliveryUpdate from suppliers.py (inferred)
export interface DeliveryUpdate {
supplier_delivery_note?: string | null;
supplier_delivery_note?: string | null;
scheduled_date?: string | null;
estimated_arrival?: string | null;
actual_arrival?: string | null;
@@ -565,25 +630,25 @@ export interface DeliveryResponse {
status: DeliveryStatus;
// Timing
scheduled_date: string | null;
scheduled_date: string | null;
estimated_arrival: string | null;
actual_arrival: string | null;
actual_arrival: string | null;
completed_at: string | null;
// Delivery info
supplier_delivery_note: string | null;
supplier_delivery_note: string | null;
delivery_address: string | null;
delivery_contact: string | null;
delivery_phone: string | null;
// Tracking
carrier_name: string | null;
carrier_name: string | null;
tracking_number: string | null;
// Quality
inspection_passed: boolean | null;
inspection_notes: string | null;
quality_issues: Record<string, any> | null;
quality_issues: Record<string, any> | null;
// Receipt
received_by: string | null;
@@ -594,7 +659,7 @@ export interface DeliveryResponse {
photos: Record<string, any> | null;
// Audit
created_at: string;
created_at: string;
updated_at: string;
created_by: string;
@@ -624,7 +689,7 @@ export interface DeliverySummary {
export interface PerformanceMetricCreate {
supplier_id: string;
metric_type: PerformanceMetricType;
metric_type: PerformanceMetricType;
period: PerformancePeriod;
period_start: string;
period_end: string;
@@ -651,21 +716,21 @@ export interface PerformanceMetric extends PerformanceMetricCreate {
tenant_id: string;
previous_value: number | null;
trend_direction: string | null; // improving, declining, stable
trend_percentage: number | null;
trend_percentage: number | null;
calculated_at: string;
}
// Mirror: AlertCreate from performance.py
export interface AlertCreate {
supplier_id: string;
alert_type: AlertType;
alert_type: AlertType;
severity: AlertSeverity;
title: string; // max_length=255
message: string;
description?: string | null;
// Context
trigger_value?: number | null;
trigger_value?: number | null;
threshold_value?: number | null;
metric_type?: PerformanceMetricType | null;
@@ -693,7 +758,7 @@ export interface Alert extends Omit<AlertCreate, 'auto_resolve'> {
resolved_at: string | null;
resolved_by: string | null;
actions_taken: Array<Record<string, any>> | null;
resolution_notes: string | null;
resolution_notes: string | null;
escalated: boolean;
escalated_at: string | null;
notification_sent: boolean;
@@ -759,7 +824,7 @@ export interface SupplierStatistics {
active_suppliers: number;
pending_suppliers: number;
avg_quality_rating: number;
avg_delivery_rating: number;
avg_delivery_rating: number;
total_spend: number;
}
@@ -800,12 +865,12 @@ export interface PerformanceDashboardSummary {
average_quality_rate: number;
total_active_alerts: number;
critical_alerts: number;
high_priority_alerts: number;
high_priority_alerts: number;
recent_scorecards_generated: number;
cost_savings_this_month: number;
performance_trend: string;
delivery_trend: string;
quality_trend: string;
quality_trend: string;
detected_business_model: string;
model_confidence: number;
business_model_metrics: Record<string, any>;
@@ -958,7 +1023,7 @@ export interface ExportDataResponse {
export interface SupplierDeletionSummary {
supplier_name: string;
deleted_price_lists: number;
deleted_quality_reviews: number;
deleted_quality_reviews: number;
deleted_performance_metrics: number;
deleted_alerts: number;
deleted_scorecards: number;

View File

@@ -110,8 +110,8 @@ export interface TenantSubscriptionUpdate {
// ================================================================
/**
* Tenant response schema - FIXED VERSION with owner_id
* Backend: TenantResponse in schemas/tenants.py (lines 55-82)
* Tenant response schema - Updated with subscription_plan
* Backend: TenantResponse in schemas/tenants.py (lines 59-87)
*/
export interface TenantResponse {
id: string;
@@ -124,11 +124,15 @@ export interface TenantResponse {
postal_code: string;
phone?: string | null;
is_active: boolean;
subscription_tier: string;
subscription_plan?: string | null; // Populated from subscription relationship
ml_model_trained: boolean;
last_training_date?: string | null; // ISO datetime string
owner_id: string; // ✅ REQUIRED field - fixes type error
owner_id: string; // ✅ REQUIRED field
created_at: string; // ISO datetime string
// Backward compatibility
/** @deprecated Use subscription_plan instead */
subscription_tier?: string;
}
/**