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.