// frontend/src/api/services/inventory.service.ts /** * Inventory Service * Handles inventory management, stock tracking, and product operations */ import { apiClient } from '../client'; // ========== TYPES AND INTERFACES ========== export type ProductType = 'ingredient' | 'finished_product'; export type UnitOfMeasure = | 'kilograms' | 'grams' | 'liters' | 'milliliters' | 'units' | 'pieces' | 'dozens' | 'boxes'; export type IngredientCategory = | 'flour' | 'yeast' | 'dairy' | 'eggs' | 'sugar' | 'fats' | 'salt' | 'spices' | 'additives' | 'packaging'; export type ProductCategory = | 'bread' | 'croissants' | 'pastries' | 'cakes' | 'cookies' | 'muffins' | 'sandwiches' | 'beverages' | 'other_products'; export type StockMovementType = | 'purchase' | 'consumption' | 'adjustment' | 'waste' | 'transfer' | 'return'; export interface InventoryItem { id: string; tenant_id: string; name: string; product_type: ProductType; category: IngredientCategory | ProductCategory; unit_of_measure: UnitOfMeasure; estimated_shelf_life_days?: number; requires_refrigeration: boolean; requires_freezing: boolean; is_seasonal: boolean; minimum_stock_level?: number; maximum_stock_level?: number; reorder_point?: number; supplier?: string; notes?: string; barcode?: string; sku?: string; cost_per_unit?: number; is_active: boolean; created_at: string; updated_at: string; // Computed fields current_stock?: StockLevel; low_stock_alert?: boolean; expiring_soon_alert?: boolean; recent_movements?: StockMovement[]; } export interface StockLevel { item_id: string; current_quantity: number; available_quantity: number; reserved_quantity: number; unit_of_measure: UnitOfMeasure; value_estimate?: number; last_updated: string; // Batch information batches?: StockBatch[]; oldest_batch_date?: string; newest_batch_date?: string; } export interface StockBatch { id: string; item_id: string; batch_number?: string; quantity: number; unit_cost?: number; purchase_date?: string; expiration_date?: string; supplier?: string; notes?: string; is_expired: boolean; days_until_expiration?: number; } export interface StockMovement { id: string; item_id: string; movement_type: StockMovementType; quantity: number; unit_cost?: number; total_cost?: number; batch_id?: string; reference_id?: string; notes?: string; movement_date: string; created_by: string; created_at: string; // Related data item_name?: string; batch_info?: StockBatch; } export interface StockAlert { id: string; item_id: string; alert_type: 'low_stock' | 'expired' | 'expiring_soon' | 'overstock'; severity: 'low' | 'medium' | 'high' | 'critical'; message: string; threshold_value?: number; current_value?: number; is_acknowledged: boolean; created_at: string; acknowledged_at?: string; acknowledged_by?: string; // Related data item?: InventoryItem; } // ========== REQUEST/RESPONSE TYPES ========== export interface CreateInventoryItemRequest { name: string; product_type: ProductType; category: IngredientCategory | ProductCategory; unit_of_measure: UnitOfMeasure; estimated_shelf_life_days?: number; requires_refrigeration?: boolean; requires_freezing?: boolean; is_seasonal?: boolean; minimum_stock_level?: number; maximum_stock_level?: number; reorder_point?: number; supplier?: string; notes?: string; barcode?: string; cost_per_unit?: number; } export interface UpdateInventoryItemRequest extends Partial { is_active?: boolean; } export interface StockAdjustmentRequest { movement_type: StockMovementType; quantity: number; unit_cost?: number; batch_number?: string; expiration_date?: string; supplier?: string; notes?: string; } export interface InventorySearchParams { search?: string; product_type?: ProductType; category?: string; is_active?: boolean; low_stock_only?: boolean; expiring_soon_only?: boolean; page?: number; limit?: number; sort_by?: 'name' | 'category' | 'stock_level' | 'last_movement' | 'created_at'; sort_order?: 'asc' | 'desc'; } export interface StockMovementSearchParams { item_id?: string; movement_type?: StockMovementType; date_from?: string; date_to?: string; page?: number; limit?: number; } export interface InventoryDashboardData { total_items: number; total_value: number; low_stock_count: number; expiring_soon_count: number; recent_movements: StockMovement[]; top_items_by_value: InventoryItem[]; category_breakdown: { category: string; count: number; value: number; }[]; movement_trends: { date: string; purchases: number; consumption: number; waste: number; }[]; } export interface PaginatedResponse { items: T[]; total: number; page: number; limit: number; total_pages: number; } // ========== INVENTORY SERVICE CLASS ========== export class InventoryService { private baseEndpoint = '/api/v1'; // ========== INVENTORY ITEMS ========== /** * Get inventory items with filtering and pagination */ async getInventoryItems( tenantId: string, params?: InventorySearchParams ): Promise> { const searchParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { searchParams.append(key, value.toString()); } }); } const query = searchParams.toString(); const url = `${this.baseEndpoint}/tenants/${tenantId}/inventory/items${query ? `?${query}` : ''}`; return apiClient.get(url); } /** * Get single inventory item by ID */ async getInventoryItem(tenantId: string, itemId: string): Promise { return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/items/${itemId}`); } /** * Create new inventory item */ async createInventoryItem( tenantId: string, data: CreateInventoryItemRequest ): Promise { return apiClient.post(`${this.baseEndpoint}/tenants/${tenantId}/inventory/items`, data); } /** * Update existing inventory item */ async updateInventoryItem( tenantId: string, itemId: string, data: UpdateInventoryItemRequest ): Promise { return apiClient.put(`${this.baseEndpoint}/tenants/${tenantId}/inventory/items/${itemId}`, data); } /** * Delete inventory item (soft delete) */ async deleteInventoryItem(tenantId: string, itemId: string): Promise { return apiClient.delete(`${this.baseEndpoint}/tenants/${tenantId}/inventory/items/${itemId}`); } /** * Bulk update inventory items */ async bulkUpdateInventoryItems( 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`, { updates }); } // ========== STOCK MANAGEMENT ========== /** * Get current stock level for an item */ async getStockLevel(tenantId: string, itemId: string): Promise { return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/stock/${itemId}`); } /** * Get stock levels for all items */ async getAllStockLevels(tenantId: string): Promise { return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/stock`); } /** * Adjust stock level (purchase, consumption, waste, etc.) */ async adjustStock( tenantId: string, itemId: string, adjustment: StockAdjustmentRequest ): Promise { return apiClient.post( `${this.baseEndpoint}/tenants/${tenantId}/inventory/stock/${itemId}/adjust`, adjustment ); } /** * Bulk stock adjustments */ async bulkAdjustStock( tenantId: string, adjustments: { item_id: string; adjustment: StockAdjustmentRequest }[] ): Promise<{ success: number; failed: number; movements: StockMovement[]; errors: string[] }> { return apiClient.post(`${this.baseEndpoint}/tenants/${tenantId}/inventory/stock/bulk-adjust`, { adjustments }); } /** * Get stock movements with filtering */ async getStockMovements( tenantId: string, params?: StockMovementSearchParams ): Promise> { const searchParams = new URLSearchParams(); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { searchParams.append(key, value.toString()); } }); } const query = searchParams.toString(); const url = `${this.baseEndpoint}/tenants/${tenantId}/inventory/movements${query ? `?${query}` : ''}`; return apiClient.get(url); } // ========== ALERTS ========== /** * Get current stock alerts */ async getStockAlerts(tenantId: string): Promise { return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/alerts`); } /** * Acknowledge alert */ async acknowledgeAlert(tenantId: string, alertId: string): Promise { return apiClient.post(`${this.baseEndpoint}/tenants/${tenantId}/inventory/alerts/${alertId}/acknowledge`); } /** * Bulk acknowledge alerts */ async bulkAcknowledgeAlerts(tenantId: string, alertIds: string[]): Promise { return apiClient.post(`${this.baseEndpoint}/tenants/${tenantId}/inventory/alerts/bulk-acknowledge`, { alert_ids: alertIds }); } // ========== DASHBOARD & ANALYTICS ========== /** * Get inventory dashboard data */ async getDashboardData(tenantId: string): Promise { return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/dashboard`); } /** * Get inventory value report */ async getInventoryValue(tenantId: string): Promise<{ total_value: number; by_category: { category: string; value: number; percentage: number }[]; by_product_type: { type: ProductType; value: number; percentage: number }[]; }> { return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/value`); } /** * Get low stock report */ async getLowStockReport(tenantId: string): Promise<{ items: InventoryItem[]; total_affected: number; estimated_loss: number; }> { return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/reports/low-stock`); } /** * Get expiring items report */ async getExpiringItemsReport(tenantId: string, days?: number): Promise<{ items: (InventoryItem & { batches: StockBatch[] })[]; total_affected: number; estimated_loss: number; }> { const params = days ? `?days=${days}` : ''; return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/reports/expiring${params}`); } // ========== IMPORT/EXPORT ========== /** * Export inventory data to CSV */ async exportInventory(tenantId: string, format: 'csv' | 'excel' = 'csv'): Promise { const response = await apiClient.getRaw( `${this.baseEndpoint}/tenants/${tenantId}/inventory/export?format=${format}` ); return response.blob(); } /** * Import inventory from file */ async importInventory(tenantId: string, file: File): Promise<{ success: number; failed: number; errors: string[]; created_items: InventoryItem[]; }> { const formData = new FormData(); formData.append('file', file); return apiClient.post(`${this.baseEndpoint}/tenants/${tenantId}/inventory/import`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); } // ========== SEARCH & SUGGESTIONS ========== /** * Search inventory items with autocomplete */ async searchItems(tenantId: string, query: string, limit = 10): Promise { return apiClient.get( `${this.baseEndpoint}/tenants/${tenantId}/inventory/search?q=${encodeURIComponent(query)}&limit=${limit}` ); } /** * Get category suggestions based on product type */ async getCategorySuggestions(productType: ProductType): Promise { return apiClient.get(`${this.baseEndpoint}/inventory/categories?type=${productType}`); } /** * Get supplier suggestions */ async getSupplierSuggestions(tenantId: string): Promise { return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/suppliers`); } } export const inventoryService = new InventoryService();