n8n Automation

Inventory Management Automation with n8n

DeviDevs Team
10 min read
#n8n#inventory-management#automation#supply-chain#ecommerce

Inventory management automation eliminates manual tracking, prevents stockouts, and optimizes ordering. This guide shows you how to build comprehensive inventory workflows with n8n.

Stock Level Monitoring

Monitor inventory levels across warehouses:

// Stock Monitor Node - JavaScript Code
const inventoryData = $input.all();
const thresholds = $workflow.staticData.thresholds || {};
 
// Default thresholds
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;
 
    // Get item-specific thresholds or use defaults
    const reorderPoint = thresholds[sku]?.reorderPoint || defaultReorderPoint;
    const safetyStock = thresholds[sku]?.safetyStock || defaultSafetyStock;
 
    // Calculate days of inventory
    const avgDailySales = item.json.avgDailySales || 1;
    const daysOfInventory = Math.floor(currentStock / avgDailySales);
 
    // Determine stock status
    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);
 
    // Generate alerts for low stock
    if (status !== 'adequate') {
      alerts.push({
        type: 'stock_alert',
        priority,
        sku,
        name: item.json.name,
        warehouse,
        currentStock,
        status,
        message: `${item.json.name} (${sku}) is ${status.replace('_', ' ')} at ${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;
 
  // Economic Order Quantity (simplified)
  const demandDuringLeadTime = avgDailySales * leadTime;
  const reorderQty = Math.ceil(demandDuringLeadTime + safetyStock - item.quantity);
 
  // Round up to minimum order quantity
  const minOrderQty = item.minOrderQuantity || 1;
  return Math.ceil(reorderQty / minOrderQty) * minOrderQty;
}
 
function getRecommendedAction(status, summary) {
  switch (status) {
    case 'out_of_stock':
      return `URGENT: Place emergency order for ${summary.recommendedOrderQty} units immediately`;
    case 'critical_low':
      return `Place order for ${summary.recommendedOrderQty} units within 24 hours`;
    case 'low':
      return `Schedule reorder for ${summary.recommendedOrderQty} units`;
    default:
      return 'No action needed';
  }
}
 
const { summaries, alerts } = analyzeStockLevels(inventoryData);
 
// Store summaries for dashboard
$workflow.staticData.lastStockSummary = summaries;
 
// Return alerts for further processing
return alerts.map(alert => ({ json: alert }));

Automatic Reorder Workflow

Automate purchase order creation:

// Reorder Generator Node
const stockAlerts = $input.all().filter(
  item => item.json.priority === 'critical' || item.json.priority === 'high'
);
 
// Group alerts by supplier
const ordersBySupplier = {};
 
for (const alert of stockAlerts) {
  const sku = alert.json.sku;
 
  // Look up supplier information
  const supplierInfo = await getSupplierForSKU(sku);
 
  if (!supplierInfo) {
    console.log(`No supplier found for 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;
 
  // Update urgency based on priority
  if (alert.json.priority === 'critical') {
    ordersBySupplier[supplierId].urgency = 'urgent';
  }
}
 
async function getSupplierForSKU(sku) {
  // This would query your supplier database
  const supplierMapping = $workflow.staticData.supplierMapping || {};
  const supplierId = supplierMapping[sku];
 
  if (!supplierId) return null;
 
  // Fetch supplier details
  return {
    id: supplierId,
    name: `Supplier ${supplierId}`,
    email: `orders@supplier${supplierId}.com`,
    leadTime: 7,
    minOrderValue: 100,
    pricing: {}
  };
}
 
function calculateOrderQty(sku) {
  // Default order quantity calculation
  return 50;
}
 
// Generate purchase orders
const purchaseOrders = [];
 
for (const [supplierId, order] of Object.entries(ordersBySupplier)) {
  // Check minimum order value
  if (order.totalValue < order.supplier.minOrderValue) {
    console.log(`Order for ${order.supplier.name} below minimum: $${order.totalValue}`);
    // Optionally add filler items or skip
  }
 
  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 }));

Supplier Integration

Connect with supplier systems:

// Supplier API Integration Node
async function submitPurchaseOrder(po) {
  const supplier = po.supplier;
  const endpoint = supplier.apiEndpoint || `https://api.supplier.com/orders`;
 
  // Format order for supplier API
  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' ? 'URGENT ORDER - Expedite if possible' : ''
  };
 
  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: 'East Coast Warehouse',
      street: '123 Industrial Blvd',
      city: 'Newark',
      state: 'NJ',
      zip: '07101',
      country: 'US'
    },
    'WH-WEST': {
      name: 'West Coast Warehouse',
      street: '456 Distribution Way',
      city: 'Los Angeles',
      state: 'CA',
      zip: '90001',
      country: 'US'
    }
  };
  return addresses[warehouseCode] || addresses['WH-EAST'];
}
 
// Process each purchase order
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 to email
    results.push({
      success: true,
      poNumber: po.poNumber,
      method: 'email',
      status: 'email_queued'
    });
  }
}
 
return results.map(r => ({ json: r }));

Demand Forecasting

Predict future inventory needs:

// Demand Forecasting Node
function forecastDemand(salesHistory, daysToForecast = 30) {
  // Simple moving average with trend adjustment
  const recentSales = salesHistory.slice(-90); // Last 90 days
 
  // Calculate daily averages by day of week
  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
  );
 
  // Calculate trend (simple linear regression)
  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;
 
  // Generate forecast
  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);
 
    // Combine trend and seasonality
    let predicted = Math.max(0, trendValue * seasonalFactor);
 
    forecast.push({
      date: forecastDate.toISOString().slice(0, 10),
      dayOfWeek: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][dayOfWeek],
      predictedDemand: Math.round(predicted),
      confidence: calculateConfidence(recentSales)
    });
  }
 
  // Calculate totals
  const totalForecast = forecast.reduce((sum, day) => sum + day.predictedDemand, 0);
 
  return {
    forecast,
    summary: {
      totalPredictedDemand: totalForecast,
      avgDailyDemand: Math.round(totalForecast / daysToForecast),
      trend: slope > 0.1 ? 'increasing' : slope < -0.1 ? 'decreasing' : 'stable',
      confidence: calculateConfidence(recentSales)
    }
  };
}
 
function calculateConfidence(history) {
  // Based on data quality and variance
  if (history.length < 30) return 'low';
  if (history.length < 60) return 'medium';
 
  // Calculate coefficient of variation
  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) {
  // Calculate demand during lead time
  const demandDuringLeadTime = forecast.forecast
    .slice(0, leadTime)
    .reduce((sum, day) => sum + day.predictedDemand, 0);
 
  // Safety stock (2 weeks of average demand)
  const safetyStock = forecast.summary.avgDailyDemand * 14;
 
  // Reorder point
  const reorderPoint = demandDuringLeadTime + safetyStock;
 
  // Days until stockout
  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 day buffer
  const orderDate = new Date();
  orderDate.setDate(orderDate.getDate() + Math.max(0, orderLeadDays));
  return orderDate.toISOString().slice(0, 10);
}
 
// Process inventory items with sales history
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 }));

Multi-Location Inventory Transfer

Balance inventory across warehouses:

// Inventory Transfer Optimizer Node
function optimizeInventoryTransfers(inventoryByLocation) {
  const transfers = [];
 
  // Group by SKU
  const skuLocations = {};
  for (const item of inventoryByLocation) {
    const sku = item.sku;
    if (!skuLocations[sku]) {
      skuLocations[sku] = [];
    }
    skuLocations[sku].push(item);
  }
 
  // Analyze each SKU
  for (const [sku, locations] of Object.entries(skuLocations)) {
    // Find locations with excess and shortage
    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;
    }
 
    // Calculate optimal transfers
    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);
 
        // Check if transfer makes economic sense
        const transferCost = calculateTransferCost(excess.warehouse, shortage.warehouse, transferQty);
        const stockoutCost = calculateStockoutCost(shortage, transferQty);
 
        if (transferCost < stockoutCost * 0.5) { // Transfer if cost < 50% of stockout cost
          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: `Balance inventory - ${shortage.warehouse} has ${shortage.daysOfInventory} days supply`
          });
 
          excess.quantity -= transferQty;
          shortage.quantity += transferQty;
        }
      }
    }
  }
 
  return transfers;
}
 
function calculateTransferCost(fromWarehouse, toWarehouse, quantity) {
  // Simplified cost calculation
  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; // Including customer goodwill loss
 
  return quantity * avgSalePrice * marginPercent * lostSalesFactor;
}
 
// Process inventory data
const inventoryData = $input.all().map(i => i.json);
const transfers = optimizeInventoryTransfers(inventoryData);
 
// Sort by priority and savings
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 }));

Conclusion

Inventory management automation with n8n eliminates manual tracking and prevents costly stockouts. Implement stock monitoring for real-time visibility, automate reordering based on demand forecasts, and integrate with supplier systems for seamless procurement. Use multi-location optimization to balance inventory efficiently. Start with basic stock alerts and expand to include demand forecasting and supplier integration as your automation matures.

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.