Automatizarea managementului de inventar elimina urmarirea manuala, previne rupturile de stoc si optimizeaza comenzile. Acest ghid iti arata cum sa construiesti workflow-uri complete de inventar cu n8n.
Monitorizarea Nivelurilor de Stoc
Monitorizeaza nivelurile de inventar din toate depozitele:
// Stock Monitor Node - JavaScript Code
const inventoryData = $input.all();
const thresholds = $workflow.staticData.thresholds || {};
// Praguri implicite
const defaultReorderPoint = 10;
const defaultSafetyStock = 5;
function analyzeStockLevels(items) {
const alerts = [];
const summaries = [];
for (const item of items) {
const sku = item.json.sku;
const currentStock = item.json.quantity;
const warehouse = item.json.warehouse;
// Obtine pragurile specifice produsului sau foloseste implicite
const reorderPoint = thresholds[sku]?.reorderPoint || defaultReorderPoint;
const safetyStock = thresholds[sku]?.safetyStock || defaultSafetyStock;
// Calculeaza zilele de inventar
const avgDailySales = item.json.avgDailySales || 1;
const daysOfInventory = Math.floor(currentStock / avgDailySales);
// Determina starea stocului
let status, priority;
if (currentStock <= 0) {
status = 'out_of_stock';
priority = 'critical';
} else if (currentStock <= safetyStock) {
status = 'critical_low';
priority = 'high';
} else if (currentStock <= reorderPoint) {
status = 'low';
priority = 'medium';
} else {
status = 'adequate';
priority = 'low';
}
const summary = {
sku,
name: item.json.name,
warehouse,
currentStock,
reorderPoint,
safetyStock,
daysOfInventory,
status,
priority,
recommendedOrderQty: status !== 'adequate' ?
calculateReorderQuantity(item.json) : 0
};
summaries.push(summary);
// Genereaza alerte pentru stoc scazut
if (status !== 'adequate') {
alerts.push({
type: 'stock_alert',
priority,
sku,
name: item.json.name,
warehouse,
currentStock,
status,
message: `${item.json.name} (${sku}) este ${status.replace('_', ' ')} la ${warehouse}`,
recommendedAction: getRecommendedAction(status, summary)
});
}
}
return { summaries, alerts };
}
function calculateReorderQuantity(item) {
const leadTime = item.leadTimeDays || 7;
const avgDailySales = item.avgDailySales || 1;
const safetyStock = thresholds[item.sku]?.safetyStock || defaultSafetyStock;
// Cantitate Economica de Comanda (simplificata)
const demandDuringLeadTime = avgDailySales * leadTime;
const reorderQty = Math.ceil(demandDuringLeadTime + safetyStock - item.quantity);
// Rotunjeste in sus la cantitatea minima de comanda
const minOrderQty = item.minOrderQuantity || 1;
return Math.ceil(reorderQty / minOrderQty) * minOrderQty;
}
function getRecommendedAction(status, summary) {
switch (status) {
case 'out_of_stock':
return `URGENT: Plaseaza comanda de urgenta pentru ${summary.recommendedOrderQty} unitati imediat`;
case 'critical_low':
return `Plaseaza comanda pentru ${summary.recommendedOrderQty} unitati in 24 de ore`;
case 'low':
return `Programeaza reaprovizionare pentru ${summary.recommendedOrderQty} unitati`;
default:
return 'Nicio actiune necesara';
}
}
const { summaries, alerts } = analyzeStockLevels(inventoryData);
// Stocheaza sumarele pentru dashboard
$workflow.staticData.lastStockSummary = summaries;
// Returneaza alertele pentru procesare ulterioara
return alerts.map(alert => ({ json: alert }));Workflow de Reaprovizionare Automata
Automatizeaza crearea comenzilor de aprovizionare:
// Reorder Generator Node
const stockAlerts = $input.all().filter(
item => item.json.priority === 'critical' || item.json.priority === 'high'
);
// Grupeaza alertele dupa furnizor
const ordersBySupplier = {};
for (const alert of stockAlerts) {
const sku = alert.json.sku;
// Cauta informatiile furnizorului
const supplierInfo = await getSupplierForSKU(sku);
if (!supplierInfo) {
console.log(`Niciun furnizor gasit pentru SKU: ${sku}`);
continue;
}
const supplierId = supplierInfo.id;
if (!ordersBySupplier[supplierId]) {
ordersBySupplier[supplierId] = {
supplier: supplierInfo,
items: [],
totalValue: 0,
urgency: 'normal'
};
}
const orderItem = {
sku,
name: alert.json.name,
quantity: alert.json.recommendedOrderQty || calculateOrderQty(sku),
unitPrice: supplierInfo.pricing[sku] || 0,
warehouse: alert.json.warehouse
};
orderItem.lineTotal = orderItem.quantity * orderItem.unitPrice;
ordersBySupplier[supplierId].items.push(orderItem);
ordersBySupplier[supplierId].totalValue += orderItem.lineTotal;
// Actualizeaza urgenta pe baza prioritatii
if (alert.json.priority === 'critical') {
ordersBySupplier[supplierId].urgency = 'urgent';
}
}
async function getSupplierForSKU(sku) {
// Aceasta ar interoga baza de date a furnizorilor
const supplierMapping = $workflow.staticData.supplierMapping || {};
const supplierId = supplierMapping[sku];
if (!supplierId) return null;
// Obtine detaliile furnizorului
return {
id: supplierId,
name: `Supplier ${supplierId}`,
email: `orders@supplier${supplierId}.com`,
leadTime: 7,
minOrderValue: 100,
pricing: {}
};
}
function calculateOrderQty(sku) {
// Calcul implicit al cantitatii de comanda
return 50;
}
// Genereaza comenzi de aprovizionare
const purchaseOrders = [];
for (const [supplierId, order] of Object.entries(ordersBySupplier)) {
// Verifica valoarea minima a comenzii
if (order.totalValue < order.supplier.minOrderValue) {
console.log(`Comanda pentru ${order.supplier.name} sub minim: $${order.totalValue}`);
// Optional adauga produse suplimentare sau omite
}
const po = {
poNumber: generatePONumber(),
supplier: order.supplier,
items: order.items,
totalValue: order.totalValue,
urgency: order.urgency,
expectedDelivery: calculateExpectedDelivery(order.supplier.leadTime, order.urgency),
status: 'pending_approval',
createdAt: new Date().toISOString()
};
purchaseOrders.push(po);
}
function generatePONumber() {
const date = new Date();
const dateStr = date.toISOString().slice(0, 10).replace(/-/g, '');
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
return `PO-${dateStr}-${random}`;
}
function calculateExpectedDelivery(leadTime, urgency) {
const multiplier = urgency === 'urgent' ? 0.7 : 1;
const days = Math.ceil(leadTime * multiplier);
const delivery = new Date();
delivery.setDate(delivery.getDate() + days);
return delivery.toISOString().slice(0, 10);
}
return purchaseOrders.map(po => ({ json: po }));Integrarea cu Furnizorii
Conecteaza-te cu sistemele furnizorilor:
// Supplier API Integration Node
async function submitPurchaseOrder(po) {
const supplier = po.supplier;
const endpoint = supplier.apiEndpoint || `https://api.supplier.com/orders`;
// Formateaza comanda pentru API-ul furnizorului
const supplierOrder = {
customer_id: $env.COMPANY_ID,
reference: po.poNumber,
delivery_address: getWarehouseAddress(po.items[0].warehouse),
items: po.items.map(item => ({
sku: supplier.skuMapping?.[item.sku] || item.sku,
quantity: item.quantity,
requested_price: item.unitPrice
})),
requested_delivery: po.expectedDelivery,
priority: po.urgency === 'urgent' ? 'express' : 'standard',
notes: po.urgency === 'urgent' ? 'COMANDA URGENTA - Expediere daca e posibil' : ''
};
try {
const response = await $http.request({
method: 'POST',
url: endpoint,
headers: {
'Authorization': `Bearer ${supplier.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(supplierOrder)
});
const result = JSON.parse(response.body);
return {
success: true,
poNumber: po.poNumber,
supplierOrderId: result.order_id,
confirmedDelivery: result.estimated_delivery,
status: result.status
};
} catch (error) {
return {
success: false,
poNumber: po.poNumber,
error: error.message,
fallbackAction: 'email'
};
}
}
function getWarehouseAddress(warehouseCode) {
const addresses = {
'WH-EAST': {
name: 'Depozit Coasta de Est',
street: '123 Industrial Blvd',
city: 'Newark',
state: 'NJ',
zip: '07101',
country: 'US'
},
'WH-WEST': {
name: 'Depozit Coasta de Vest',
street: '456 Distribution Way',
city: 'Los Angeles',
state: 'CA',
zip: '90001',
country: 'US'
}
};
return addresses[warehouseCode] || addresses['WH-EAST'];
}
// Proceseaza fiecare comanda de aprovizionare
const purchaseOrders = $input.all();
const results = [];
for (const item of purchaseOrders) {
const po = item.json;
if (po.supplier.hasApiIntegration) {
const result = await submitPurchaseOrder(po);
results.push(result);
} else {
// Fallback pe email
results.push({
success: true,
poNumber: po.poNumber,
method: 'email',
status: 'email_queued'
});
}
}
return results.map(r => ({ json: r }));Previziunea Cererii
Prezice nevoile viitoare de inventar:
// Demand Forecasting Node
function forecastDemand(salesHistory, daysToForecast = 30) {
// Medie mobila simpla cu ajustare de trend
const recentSales = salesHistory.slice(-90); // Ultimele 90 de zile
// Calculeaza mediile zilnice dupa ziua saptamanii
const byDayOfWeek = [0, 0, 0, 0, 0, 0, 0].map(() => []);
recentSales.forEach(day => {
const date = new Date(day.date);
byDayOfWeek[date.getDay()].push(day.quantity);
});
const avgByDay = byDayOfWeek.map(days =>
days.length > 0 ? days.reduce((a, b) => a + b, 0) / days.length : 0
);
// Calculeaza trendul (regresie liniara simpla)
const n = recentSales.length;
let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
recentSales.forEach((day, i) => {
sumX += i;
sumY += day.quantity;
sumXY += i * day.quantity;
sumX2 += i * i;
});
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
const intercept = (sumY - slope * sumX) / n;
// Genereaza previziunea
const forecast = [];
const startDate = new Date();
for (let i = 0; i < daysToForecast; i++) {
const forecastDate = new Date(startDate);
forecastDate.setDate(forecastDate.getDate() + i);
const dayOfWeek = forecastDate.getDay();
const trendValue = intercept + slope * (n + i);
const seasonalFactor = avgByDay[dayOfWeek] / (sumY / n);
// Combina trendul si sezonalitatea
let predicted = Math.max(0, trendValue * seasonalFactor);
forecast.push({
date: forecastDate.toISOString().slice(0, 10),
dayOfWeek: ['Dum', 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sam'][dayOfWeek],
predictedDemand: Math.round(predicted),
confidence: calculateConfidence(recentSales)
});
}
// Calculeaza totalurile
const totalForecast = forecast.reduce((sum, day) => sum + day.predictedDemand, 0);
return {
forecast,
summary: {
totalPredictedDemand: totalForecast,
avgDailyDemand: Math.round(totalForecast / daysToForecast),
trend: slope > 0.1 ? 'in crestere' : slope < -0.1 ? 'in scadere' : 'stabil',
confidence: calculateConfidence(recentSales)
}
};
}
function calculateConfidence(history) {
// Pe baza calitatii datelor si variantei
if (history.length < 30) return 'low';
if (history.length < 60) return 'medium';
// Calculeaza coeficientul de variatie
const values = history.map(h => h.quantity);
const mean = values.reduce((a, b) => a + b, 0) / values.length;
const variance = values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / values.length;
const cv = Math.sqrt(variance) / mean;
if (cv < 0.3) return 'high';
if (cv < 0.5) return 'medium';
return 'low';
}
function calculateReorderRecommendation(sku, currentStock, forecast, leadTime) {
// Calculeaza cererea in timpul lead time
const demandDuringLeadTime = forecast.forecast
.slice(0, leadTime)
.reduce((sum, day) => sum + day.predictedDemand, 0);
// Stoc de siguranta (2 saptamani de cerere medie)
const safetyStock = forecast.summary.avgDailyDemand * 14;
// Punct de reaprovizionare
const reorderPoint = demandDuringLeadTime + safetyStock;
// Zile pana la ruptura de stoc
const daysUntilStockout = Math.floor(currentStock / forecast.summary.avgDailyDemand);
return {
sku,
currentStock,
reorderPoint: Math.round(reorderPoint),
daysUntilStockout,
safetyStock: Math.round(safetyStock),
recommendedOrderDate: calculateOrderDate(daysUntilStockout, leadTime),
recommendedQuantity: Math.round(demandDuringLeadTime + safetyStock),
urgency: daysUntilStockout <= leadTime ? 'high' :
daysUntilStockout <= leadTime * 1.5 ? 'medium' : 'low'
};
}
function calculateOrderDate(daysUntilStockout, leadTime) {
const orderLeadDays = daysUntilStockout - leadTime - 3; // 3 zile buffer
const orderDate = new Date();
orderDate.setDate(orderDate.getDate() + Math.max(0, orderLeadDays));
return orderDate.toISOString().slice(0, 10);
}
// Proceseaza articolele din inventar cu istoricul vanzarilor
const inventoryItems = $input.all();
const recommendations = [];
for (const item of inventoryItems) {
const salesHistory = item.json.salesHistory || [];
const leadTime = item.json.leadTimeDays || 7;
const forecast = forecastDemand(salesHistory, 30);
const recommendation = calculateReorderRecommendation(
item.json.sku,
item.json.currentStock,
forecast,
leadTime
);
recommendations.push({
sku: item.json.sku,
name: item.json.name,
forecast: forecast.summary,
recommendation
});
}
return recommendations.map(r => ({ json: r }));Transfer de Inventar Multi-Locatie
Echilibreaza inventarul intre depozite:
// Inventory Transfer Optimizer Node
function optimizeInventoryTransfers(inventoryByLocation) {
const transfers = [];
// Grupeaza dupa SKU
const skuLocations = {};
for (const item of inventoryByLocation) {
const sku = item.sku;
if (!skuLocations[sku]) {
skuLocations[sku] = [];
}
skuLocations[sku].push(item);
}
// Analizeaza fiecare SKU
for (const [sku, locations] of Object.entries(skuLocations)) {
// Gaseste locatiile cu exces si deficit
const excessLocations = locations.filter(l =>
l.quantity > l.reorderPoint * 2 && l.daysOfInventory > 60
);
const shortageLocations = locations.filter(l =>
l.quantity < l.reorderPoint && l.daysOfInventory < 14
);
if (excessLocations.length === 0 || shortageLocations.length === 0) {
continue;
}
// Calculeaza transferurile optime
for (const shortage of shortageLocations) {
const needed = shortage.reorderPoint - shortage.quantity;
for (const excess of excessLocations) {
const available = excess.quantity - excess.reorderPoint;
if (available <= 0) continue;
const transferQty = Math.min(needed, available);
// Verifica daca transferul are sens economic
const transferCost = calculateTransferCost(excess.warehouse, shortage.warehouse, transferQty);
const stockoutCost = calculateStockoutCost(shortage, transferQty);
if (transferCost < stockoutCost * 0.5) { // Transfera daca costul < 50% din costul rupturii
transfers.push({
sku,
name: locations[0].name,
fromWarehouse: excess.warehouse,
toWarehouse: shortage.warehouse,
quantity: transferQty,
transferCost,
estimatedSavings: stockoutCost - transferCost,
priority: shortage.daysOfInventory < 7 ? 'high' : 'medium',
reason: `Echilibrare inventar - ${shortage.warehouse} are ${shortage.daysOfInventory} zile de aprovizionare`
});
excess.quantity -= transferQty;
shortage.quantity += transferQty;
}
}
}
}
return transfers;
}
function calculateTransferCost(fromWarehouse, toWarehouse, quantity) {
// Calcul simplificat al costului
const distanceMatrix = {
'WH-EAST': { 'WH-WEST': 2500, 'WH-CENTRAL': 1000 },
'WH-WEST': { 'WH-EAST': 2500, 'WH-CENTRAL': 1500 },
'WH-CENTRAL': { 'WH-EAST': 1000, 'WH-WEST': 1500 }
};
const distance = distanceMatrix[fromWarehouse]?.[toWarehouse] || 1000;
const costPerMile = 0.02;
const handlingCost = 5 + (quantity * 0.50);
return distance * costPerMile + handlingCost;
}
function calculateStockoutCost(location, quantity) {
const avgSalePrice = location.avgSalePrice || 50;
const marginPercent = location.marginPercent || 0.3;
const lostSalesFactor = 1.5; // Include pierderea de goodwill
return quantity * avgSalePrice * marginPercent * lostSalesFactor;
}
// Proceseaza datele de inventar
const inventoryData = $input.all().map(i => i.json);
const transfers = optimizeInventoryTransfers(inventoryData);
// Sorteaza dupa prioritate si economii
transfers.sort((a, b) => {
if (a.priority !== b.priority) {
return a.priority === 'high' ? -1 : 1;
}
return b.estimatedSavings - a.estimatedSavings;
});
return transfers.map(t => ({ json: t }));Concluzie
Automatizarea managementului de inventar cu n8n elimina urmarirea manuala si previne rupturile de stoc costisitoare. Implementeaza monitorizarea stocurilor pentru vizibilitate in timp real, automatizeaza reaprovizionarea pe baza previziunilor de cerere si integreaza-te cu sistemele furnizorilor pentru aprovizionare eficienta. Foloseste optimizarea multi-locatie pentru a echilibra inventarul eficient. Incepe cu alerte de baza pentru stoc si extinde catre previziunea cererii si integrarea cu furnizorii pe masura ce automatizarea ta se maturizeaza.
Sistemul tau AI e conform cu EU AI Act? Evaluare gratuita de risc - afla in 2 minute →