Files
bakery-ia/frontend/src/api/services/inventory.service.ts

474 lines
12 KiB
TypeScript
Raw Normal View History

// 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;
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<CreateInventoryItemRequest> {
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<T> {
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<PaginatedResponse<InventoryItem>> {
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<InventoryItem> {
return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/items/${itemId}`);
}
/**
* Create new inventory item
*/
async createInventoryItem(
tenantId: string,
data: CreateInventoryItemRequest
): Promise<InventoryItem> {
return apiClient.post(`${this.baseEndpoint}/tenants/${tenantId}/inventory/items`, data);
}
/**
* Update existing inventory item
*/
async updateInventoryItem(
tenantId: string,
itemId: string,
data: UpdateInventoryItemRequest
): Promise<InventoryItem> {
return apiClient.put(`${this.baseEndpoint}/tenants/${tenantId}/inventory/items/${itemId}`, data);
}
/**
* Delete inventory item (soft delete)
*/
async deleteInventoryItem(tenantId: string, itemId: string): Promise<void> {
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<StockLevel> {
return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/stock/${itemId}`);
}
/**
* Get stock levels for all items
*/
async getAllStockLevels(tenantId: string): Promise<StockLevel[]> {
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<StockMovement> {
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<PaginatedResponse<StockMovement>> {
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<StockAlert[]> {
return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/alerts`);
}
/**
* Acknowledge alert
*/
async acknowledgeAlert(tenantId: string, alertId: string): Promise<void> {
return apiClient.post(`${this.baseEndpoint}/tenants/${tenantId}/inventory/alerts/${alertId}/acknowledge`);
}
/**
* Bulk acknowledge alerts
*/
async bulkAcknowledgeAlerts(tenantId: string, alertIds: string[]): Promise<void> {
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<InventoryDashboardData> {
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<Blob> {
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<InventoryItem[]> {
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<string[]> {
return apiClient.get(`${this.baseEndpoint}/inventory/categories?type=${productType}`);
}
/**
* Get supplier suggestions
*/
async getSupplierSuggestions(tenantId: string): Promise<string[]> {
return apiClient.get(`${this.baseEndpoint}/tenants/${tenantId}/inventory/suppliers`);
}
}
export const inventoryService = new InventoryService();