n8n Automation

Automatizarea Managementului de Inventar cu n8n

Petru Constantin
--10 min lectura
#n8n#inventory-management#automation#supply-chain#ecommerce

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 →

Ai nevoie de ajutor cu conformitatea EU AI Act sau securitatea AI?

Programeaza o consultatie gratuita de 30 de minute. Fara obligatii.

Programeaza un Apel

Weekly AI Security & Automation Digest

Get the latest on AI Security, workflow automation, secure integrations, and custom platform development delivered weekly.

No spam. Unsubscribe anytime.