Inventory Management Quickstart Guide

Learn how to build a sophisticated inventory management system using the Stateset API. This guide covers everything from basic stock tracking to advanced demand forecasting and multi-location inventory optimization.

Table of Contents

  1. Introduction
  2. Core Concepts
  3. Setting Up Your Environment
  4. Advanced API Usage
  5. Inventory Optimization Strategies
  6. Error Handling and Logging
  7. Webhooks and Real-time Updates
  8. Performance Optimization
  9. Security Best Practices
  10. Troubleshooting

Introduction

Stateset One provides a powerful REST and GraphQL API for advanced Inventory Management. This guide will dive deep into the intricacies of the Inventory module, exploring advanced features and best practices for efficient inventory management.

Key Objects in the Inventory Module:

  • Inventory Items
  • Packing Lists
  • Packing List Items
  • Shipments
  • Orders

Core Concepts

Before we dive into the implementation, let’s review some core concepts and challenges in inventory management:

Inventory Management Processes:

  1. Receiving Inventory
  2. Picking Inventory
  3. Shipping Inventory
  4. Adjusting Inventory
  5. Cycle Counting
  6. Inventory Forecasting

Common Challenges:

  • Multi-warehouse management
  • Just-in-time (JIT) inventory
  • Demand forecasting
  • Stock-out prevention
  • Overstock mitigation
  • Inventory shrinkage
  • Supplier management

Stateset’s Solutions:

  • Real-time inventory tracking
  • Multi-location support
  • Advanced forecasting algorithms
  • Automated reorder points
  • Supplier performance metrics
  • Integration with ERP and e-commerce platforms

Prerequisites

Before you begin, ensure you have:

  • A Stateset account (Sign up here)
  • API credentials from Stateset Cloud Console
  • Node.js 16+ installed
  • Basic understanding of inventory management concepts
  • Access to your warehouse/fulfillment systems (if integrating)

Setting Up Your Environment

1

Install Dependencies

Install the required packages:

npm install stateset-node node-cron lodash moment
# or
yarn add stateset-node node-cron lodash moment
2

Configure Environment

Create your environment configuration:

# .env file
STATESET_API_KEY=your_api_key_here
STATESET_ENVIRONMENT=production

# Warehouse Configuration
DEFAULT_LOCATION=warehouse_001
REORDER_NOTIFICATION_EMAIL=ops@yourcompany.com

# Integration Settings
ERP_SYNC_ENABLED=true
SHOPIFY_STORE_URL=your-store.myshopify.com
3

Initialize Inventory Manager

Create a robust inventory management client:

import { StateSetClient } from 'stateset-node';
import cron from 'node-cron';
import _ from 'lodash';
import moment from 'moment';

class InventoryManager {
  constructor(apiKey) {
    this.client = new StateSetClient({ 
      apiKey,
      timeout: 30000,
      maxRetries: 3
    });
    
    this.cache = new Map();
    this.setupAutomation();
  }
  
  async initialize() {
    try {
      await this.client.health.check();
      console.log('✅ Inventory Manager initialized');
      
      // Set up scheduled tasks
      this.scheduleInventoryTasks();
      
      return true;
    } catch (error) {
      console.error('❌ Initialization failed:', error.message);
      throw error;
    }
  }
  
  setupAutomation() {
    // Auto-reorder monitoring
    cron.schedule('0 */6 * * *', () => {
      this.checkReorderPoints();
    });
    
    // Inventory sync with external systems
    cron.schedule('*/15 * * * *', () => {
      this.syncInventoryLevels();
    });
    
    // Daily inventory reports
    cron.schedule('0 9 * * *', () => {
      this.generateDailyReport();
    });
  }
}

// Initialize the manager
const inventoryManager = new InventoryManager(process.env.STATESET_API_KEY);
await inventoryManager.initialize();

Advanced API Usage

Creating and Managing Inventory Items

// Create a new Inventory Item
const newItem = await client.inventoryitems.create({
  upc: '123456789012',
  sku: 'WIDGET-001',
  name: 'Super Widget',
  description: 'A high-quality widget',
  category: 'Electronics',
  subcategory: 'Gadgets',
  price: 29.99,
  cost: 15.00,
  weight: 0.5,
  dimensions: { length: 5, width: 3, height: 2 },
  reorder_point: 100,
  lead_time: 14 // days
});

// Update an Inventory Item
const updatedItem = await client.inventoryitems.update('ii_ODkRWQtx9NVsRX', {
  price: 34.99,
  reorder_point: 150
});

// Get Inventory Item details
const itemDetails = await client.inventoryitems.get('ii_ODkRWQtx9NVsRX');

// List Inventory Items with pagination and filtering
const inventoryList = await client.inventoryitems.list({
  limit: 100,
  offset: 0,
  category: 'Electronics',
  in_stock: true
});

Managing Packing Lists and Items

// Create a Packing List
const packingList = await client.packinglists.create({
  supplier_id: 'sup_123456',
  expected_delivery_date: '2024-10-01',
  status: 'pending'
});

// Add items to the Packing List
const packingListItem = await client.packinglistitems.create({
  packing_list_id: packingList.id,
  inventory_item_id: 'ii_ODkRWQtx9NVsRX',
  quantity: 500
});

// Update Packing List status
const updatedPackingList = await client.packinglists.update(packingList.id, {
  status: 'in_transit'
});

// Process arrived Packing List
async function processArrivedPackingList(packingListId) {
  const packingList = await client.packinglists.get(packingListId);
  const items = await client.packinglistitems.list({ packing_list_id: packingListId });

  for (const item of items) {
    await updateInventoryCounts(item);
  }

  await client.packinglists.update(packingListId, { status: 'received' });
}

async function updateInventoryCounts(packingListItem) {
  const inventoryItem = await client.inventoryitems.get(packingListItem.inventory_item_id);
  
  const updatedInventory = await client.inventoryitems.update(inventoryItem.id, {
    incoming: inventoryItem.incoming - packingListItem.quantity,
    available: inventoryItem.available + packingListItem.quantity,
    warehouse: inventoryItem.warehouse + packingListItem.quantity
  });

  await client.packinglistitems.update(packingListItem.id, { arrived: true });
}

Inventory Optimization Strategies

  1. Implement ABC Analysis: Categorize inventory items based on their value and turnover rate.
async function performABCAnalysis() {
  const items = await client.inventoryitems.list({ limit: 1000 });
  const totalValue = items.reduce((sum, item) => sum + item.price * item.available, 0);

  const categorizedItems = items.map(item => ({
    ...item,
    value: item.price * item.available,
    percentageOfTotal: (item.price * item.available) / totalValue * 100
  })).sort((a, b) => b.value - a.value);

  let cumulativePercentage = 0;
  const abcCategories = categorizedItems.map(item => {
    cumulativePercentage += item.percentageOfTotal;
    if (cumulativePercentage <= 80) return { ...item, category: 'A' };
    if (cumulativePercentage <= 95) return { ...item, category: 'B' };
    return { ...item, category: 'C' };
  });

  // Update items with their ABC category
  for (const item of abcCategories) {
    await client.inventoryitems.update(item.id, { abc_category: item.category });
  }
}
  1. Implement Economic Order Quantity (EOQ): Calculate the optimal order quantity to minimize total inventory costs.
function calculateEOQ(demand, orderCost, holdingCost) {
  return Math.sqrt((2 * demand * orderCost) / holdingCost);
}

async function updateEOQForItems() {
  const items = await client.inventoryitems.list({ limit: 1000 });
  
  for (const item of items) {
    const annualDemand = await getAnnualDemand(item.id);
    const orderCost = 50; // Assume $50 per order
    const holdingCost = item.price * 0.2; // Assume 20% of item price as annual holding cost
    
    const eoq = calculateEOQ(annualDemand, orderCost, holdingCost);
    
    await client.inventoryitems.update(item.id, { economic_order_quantity: Math.round(eoq) });
  }
}

Error Handling and Logging

Implement robust error handling and logging to ensure smooth operation of your inventory management system.

async function safeInventoryOperation(operation) {
  try {
    const result = await operation();
    console.log(`Operation successful: ${JSON.stringify(result)}`);
    return result;
  } catch (error) {
    console.error(`Error in inventory operation: ${error.message}`);
    // Log to external logging service
    await logToExternalService({
      level: 'error',
      message: error.message,
      stack: error.stack,
      timestamp: new Date().toISOString()
    });
    throw error;
  }
}

// Usage
const newItem = await safeInventoryOperation(() => 
  client.inventoryitems.create({
    upc: '123456789012',
    name: 'Super Widget'
  })
);

Webhooks and Real-time Updates

Configure webhooks to receive real-time updates about inventory changes.

  1. Set up a webhook endpoint in your application.
  2. Register the webhook in the Stateset Console.
  3. Process incoming webhook events:
import express from 'express';
const app = express();

app.post('/webhook/inventory', express.json(), (req, res) => {
  const event = req.body;

  switch (event.type) {
    case 'inventory.updated':
      handleInventoryUpdate(event.data);
      break;
    case 'packing_list.received':
      handlePackingListReceived(event.data);
      break;
    // Handle other event types
  }

  res.sendStatus(200);
});

function handleInventoryUpdate(data) {
  // Update local cache, notify relevant systems, etc.
}

function handlePackingListReceived(data) {
  // Trigger inventory count updates, notify warehouse staff, etc.
}

Performance Optimization

  1. Implement caching for frequently accessed inventory data.
  2. Use bulk operations for updating multiple items.
  3. Implement pagination for large data sets.
import NodeCache from 'node-cache';

const cache = new NodeCache({ stdTTL: 600 }); // Cache for 10 minutes

async function getInventoryItem(id) {
  const cachedItem = cache.get(id);
  if (cachedItem) return cachedItem;

  const item = await client.inventoryitems.get(id);
  cache.set(id, item);
  return item;
}

async function bulkUpdateInventory(updates) {
  const bulkOperations = updates.map(update => ({
    id: update.id,
    changes: { quantity: update.newQuantity }
  }));

  return await client.inventoryitems.bulkUpdate(bulkOperations);
}

Security Best Practices

  1. Use environment variables for API keys.
  2. Implement API request signing for added security.
  3. Use HTTPS for all API communications.
  4. Implement proper access controls and user permissions in your application.

Real-World Inventory Scenarios

Scenario 1: Multi-Location Inventory Management

Manage inventory across multiple warehouses and retail locations:

class MultiLocationInventoryManager extends InventoryManager {
  constructor(apiKey) {
    super(apiKey);
    this.locations = new Map();
  }
  
  async setupLocations() {
    const locations = await this.client.locations.list();
    locations.forEach(location => {
      this.locations.set(location.id, {
        ...location,
        inventory: new Map(),
        reorderRules: new Map()
      });
    });
  }
  
  /**
   * Real-time inventory allocation across locations
   */
  async allocateInventoryOptimally(orderItems, customerLocation) {
    const allocations = [];
    
    for (const item of orderItems) {
      const allocation = await this.findBestAllocation(item, customerLocation);
      allocations.push(allocation);
    }
    
    return allocations;
  }
  
  async findBestAllocation(item, customerLocation) {
    // Get inventory levels across all locations
    const inventoryLevels = await this.getInventoryLevels(item.sku);
    
    // Calculate shipping costs and delivery times
    const locationScores = await Promise.all(
      Array.from(this.locations.keys()).map(async (locationId) => {
        const available = inventoryLevels.get(locationId) || 0;
        
        if (available < item.quantity) {
          return { locationId, score: 0, available: 0 };
        }
        
        const shippingCost = await this.calculateShippingCost(locationId, customerLocation);
        const deliveryTime = await this.estimateDeliveryTime(locationId, customerLocation);
        const distanceScore = this.calculateDistanceScore(locationId, customerLocation);
        
        // Weighted scoring: 40% cost, 40% speed, 20% distance
        const score = (
          (1 - shippingCost / 100) * 0.4 +
          (1 - deliveryTime / 7) * 0.4 +
          distanceScore * 0.2
        );
        
        return {
          locationId,
          score,
          available,
          shippingCost,
          deliveryTime
        };
      })
    );
    
    // Select the best location
    const bestLocation = locationScores
      .filter(loc => loc.available >= item.quantity)
      .sort((a, b) => b.score - a.score)[0];
    
    if (!bestLocation) {
      // Try split allocation
      return await this.attemptSplitAllocation(item, locationScores);
    }
    
    // Reserve inventory
    await this.reserveInventory(bestLocation.locationId, item.sku, item.quantity);
    
    return {
      sku: item.sku,
      quantity: item.quantity,
      location: bestLocation.locationId,
      estimated_cost: bestLocation.shippingCost,
      estimated_delivery: bestLocation.deliveryTime
    };
  }
  
  async attemptSplitAllocation(item, locationScores) {
    const allocations = [];
    let remainingQuantity = item.quantity;
    
    // Sort by score and try to fulfill from multiple locations
    const sortedLocations = locationScores
      .filter(loc => loc.available > 0)
      .sort((a, b) => b.score - a.score);
    
    for (const location of sortedLocations) {
      if (remainingQuantity <= 0) break;
      
      const allocateQuantity = Math.min(remainingQuantity, location.available);
      
      await this.reserveInventory(location.locationId, item.sku, allocateQuantity);
      
      allocations.push({
        sku: item.sku,
        quantity: allocateQuantity,
        location: location.locationId,
        estimated_cost: location.shippingCost,
        estimated_delivery: location.deliveryTime
      });
      
      remainingQuantity -= allocateQuantity;
    }
    
    if (remainingQuantity > 0) {
      throw new Error(`Insufficient inventory for ${item.sku}. Need ${remainingQuantity} more units.`);
    }
    
    return allocations;
  }
}

Scenario 2: Demand Forecasting & Auto-Replenishment

Implement intelligent demand forecasting and automated reordering:

class DemandForecastingManager {
  constructor(inventoryManager) {
    this.inventory = inventoryManager;
    this.historicalData = new Map();
    this.forecastModels = new Map();
  }
  
  /**
   * Machine learning-based demand forecasting
   */
  async generateDemandForecast(sku, forecastPeriodDays = 30) {
    // Collect historical sales data
    const salesHistory = await this.getSalesHistory(sku, 365); // Last year
    const seasonalFactors = await this.calculateSeasonalFactors(sku);
    const trendAnalysis = this.analyzeTrend(salesHistory);
    
    // Apply different forecasting models
    const forecasts = {
      movingAverage: this.calculateMovingAverage(salesHistory, 30),
      exponentialSmoothing: this.calculateExponentialSmoothing(salesHistory),
      linearRegression: this.calculateLinearRegression(salesHistory),
      seasonal: this.calculateSeasonalForecast(salesHistory, seasonalFactors)
    };
    
    // Ensemble forecast (weighted average of models)
    const weights = {
      movingAverage: 0.2,
      exponentialSmoothing: 0.3,
      linearRegression: 0.25,
      seasonal: 0.25
    };
    
    const ensembleForecast = Object.keys(forecasts).reduce((total, model) => {
      return total + (forecasts[model] * weights[model]);
    }, 0);
    
    // Apply trend and seasonal adjustments
    const adjustedForecast = ensembleForecast * trendAnalysis.factor * seasonalFactors.current;
    
    // Calculate forecast confidence interval
    const confidence = this.calculateConfidenceInterval(salesHistory, adjustedForecast);
    
    return {
      sku,
      forecastPeriodDays,
      predictedDemand: Math.round(adjustedForecast),
      confidence,
      models: forecasts,
      trend: trendAnalysis,
      seasonalFactor: seasonalFactors.current,
      generatedAt: new Date()
    };
  }
  
  /**
   * Automated reorder point calculation
   */
  async calculateOptimalReorderPoint(sku) {
    const forecast = await this.generateDemandForecast(sku, 30);
    const leadTime = await this.getSupplierLeadTime(sku);
    const serviceLevel = 0.95; // 95% service level
    
    // Lead time demand
    const leadTimeDemand = (forecast.predictedDemand / 30) * leadTime;
    
    // Safety stock calculation
    const demandVariability = this.calculateDemandVariability(sku);
    const leadTimeVariability = await this.getLeadTimeVariability(sku);
    
    const safetyStock = this.calculateSafetyStock(
      serviceLevel,
      demandVariability,
      leadTimeVariability,
      leadTime
    );
    
    const reorderPoint = leadTimeDemand + safetyStock;
    
    // Update inventory item with new reorder point
    await this.inventory.client.inventory.update(sku, {
      reorder_point: Math.ceil(reorderPoint),
      safety_stock: Math.ceil(safetyStock),
      lead_time_days: leadTime,
      last_forecast_update: new Date()
    });
    
    return {
      sku,
      reorderPoint: Math.ceil(reorderPoint),
      safetyStock: Math.ceil(safetyStock),
      leadTimeDemand: Math.ceil(leadTimeDemand),
      forecast
    };
  }
  
  /**
   * Auto-replenishment workflow
   */
  async executeAutoReplenishment() {
    const itemsNeedingReorder = await this.inventory.client.inventory.list({
      available_quantity_lte: 'reorder_point',
      auto_reorder_enabled: true
    });
    
    const replenishmentOrders = [];
    
    for (const item of itemsNeedingReorder) {
      try {
        const reorderCalculation = await this.calculateOptimalOrderQuantity(item.sku);
        
        // Create purchase order
        const purchaseOrder = await this.createPurchaseOrder({
          supplier_id: item.primary_supplier_id,
          items: [{
            sku: item.sku,
            quantity: reorderCalculation.orderQuantity,
            unit_cost: item.unit_cost
          }],
          requested_delivery_date: moment().add(item.lead_time_days, 'days').toDate(),
          priority: this.calculateOrderPriority(item, reorderCalculation)
        });
        
        replenishmentOrders.push(purchaseOrder);
        
        // Update inventory to reflect incoming stock
        await this.inventory.client.inventory.update(item.sku, {
          incoming_quantity: item.incoming_quantity + reorderCalculation.orderQuantity,
          last_reorder_date: new Date(),
          next_reorder_check: moment().add(7, 'days').toDate()
        });
        
        // Log the auto-replenishment action
        await this.logReplenishmentAction(item.sku, reorderCalculation, purchaseOrder);
        
      } catch (error) {
        console.error(`Auto-replenishment failed for ${item.sku}:`, error.message);
        await this.notifyReplenishmentFailure(item.sku, error);
      }
    }
    
    // Send summary report
    if (replenishmentOrders.length > 0) {
      await this.sendReplenishmentSummary(replenishmentOrders);
    }
    
    return replenishmentOrders;
  }
  
  calculateOptimalOrderQuantity(sku) {
    // Economic Order Quantity (EOQ) calculation
    const annualDemand = this.getAnnualDemand(sku);
    const orderingCost = this.getOrderingCost(sku);
    const holdingCost = this.getHoldingCost(sku);
    
    const eoq = Math.sqrt((2 * annualDemand * orderingCost) / holdingCost);
    
    // Consider supplier constraints
    const supplierConstraints = this.getSupplierConstraints(sku);
    const finalQuantity = this.applySupplierConstraints(eoq, supplierConstraints);
    
    return {
      economicOrderQuantity: Math.ceil(eoq),
      orderQuantity: finalQuantity,
      annualDemand,
      totalCost: this.calculateTotalCost(finalQuantity, annualDemand, orderingCost, holdingCost)
    };
  }
}

Scenario 3: Inventory Cycle Counting & Accuracy

Implement automated cycle counting and inventory accuracy tracking:

class InventoryAccuracyManager {
  constructor(inventoryManager) {
    this.inventory = inventoryManager;
    this.cycleCountSchedule = new Map();
  }
  
  /**
   * Generate cycle count schedule based on ABC analysis
   */
  async generateCycleCountSchedule() {
    const items = await this.inventory.client.inventory.list();
    const abcClassification = await this.performABCAnalysis(items);
    
    const schedule = new Map();
    
    // A items: Count monthly
    // B items: Count quarterly  
    // C items: Count annually
    const frequencies = {
      A: 30,  // days
      B: 90,
      C: 365
    };
    
    abcClassification.forEach((classification, sku) => {
      const frequency = frequencies[classification.category];
      const nextCountDate = moment().add(frequency, 'days').toDate();
      
      schedule.set(sku, {
        category: classification.category,
        frequency,
        nextCountDate,
        priority: classification.category === 'A' ? 'high' : 
                  classification.category === 'B' ? 'medium' : 'low'
      });
    });
    
    this.cycleCountSchedule = schedule;
    return schedule;
  }
  
  /**
   * Execute cycle count for specific items
   */
  async executeCycleCount(skuList, countedBy) {
    const cycleCountResults = [];
    
    for (const sku of skuList) {
      try {
        // Get current system quantity
        const inventoryItem = await this.inventory.client.inventory.get(sku);
        const systemQuantity = inventoryItem.available_quantity;
        
        // Initiate count (in real implementation, this would interface with warehouse systems)
        const countResult = await this.initiatePhysicalCount(sku);
        
        const variance = countResult.physicalQuantity - systemQuantity;
        const variancePercentage = Math.abs(variance) / systemQuantity * 100;
        
        const result = {
          sku,
          systemQuantity,
          physicalQuantity: countResult.physicalQuantity,
          variance,
          variancePercentage,
          countDate: new Date(),
          countedBy,
          location: countResult.location,
          status: this.determineVarianceStatus(variancePercentage)
        };
        
        // Update system if variance is within acceptable range
        if (variancePercentage <= 2) { // 2% tolerance
          await this.adjustInventoryQuantity(sku, variance, `Cycle count adjustment - ${result.status}`);
          result.adjusted = true;
        } else {
          // Flag for investigation
          await this.flagForInvestigation(sku, result);
          result.adjusted = false;
        }
        
        cycleCountResults.push(result);
        
        // Update next count date
        const scheduleItem = this.cycleCountSchedule.get(sku);
        if (scheduleItem) {
          scheduleItem.nextCountDate = moment().add(scheduleItem.frequency, 'days').toDate();
          scheduleItem.lastCountDate = new Date();
        }
        
      } catch (error) {
        console.error(`Cycle count failed for ${sku}:`, error.message);
        cycleCountResults.push({
          sku,
          error: error.message,
          status: 'failed'
        });
      }
    }
    
    // Generate cycle count report
    await this.generateCycleCountReport(cycleCountResults);
    
    return cycleCountResults;
  }
  
  /**
   * Track inventory accuracy metrics
   */
  async calculateInventoryAccuracyMetrics(period = 30) {
    const startDate = moment().subtract(period, 'days').toDate();
    
    // Get all cycle counts in the period
    const cycleCounts = await this.inventory.client.cycleCounts.list({
      date_after: startDate
    });
    
    const metrics = {
      totalCounts: cycleCounts.length,
      accuratecounts: 0,
      totalVariance: 0,
      totalValue: 0,
      byCategory: { A: {}, B: {}, C: {} },
      byLocation: new Map(),
      trends: []
    };
    
    cycleCounts.forEach(count => {
      const isAccurate = Math.abs(count.variancePercentage) <= 2;
      if (isAccurate) metrics.accurateCount++;
      
      metrics.totalVariance += Math.abs(count.variance);
      metrics.totalValue += count.systemQuantity * count.unitCost;
      
      // Track by ABC category
      const category = this.getItemCategory(count.sku);
      if (!metrics.byCategory[category].count) {
        metrics.byCategory[category] = { count: 0, accurate: 0 };
      }
      metrics.byCategory[category].count++;
      if (isAccurate) metrics.byCategory[category].accurate++;
      
      // Track by location
      if (!metrics.byLocation.has(count.location)) {
        metrics.byLocation.set(count.location, { count: 0, accurate: 0 });
      }
      const locationMetric = metrics.byLocation.get(count.location);
      locationMetric.count++;
      if (isAccurate) locationMetric.accurate++;
    });
    
    // Calculate accuracy percentages
    metrics.overallAccuracy = (metrics.accurateCount / metrics.totalCounts) * 100;
    
    Object.keys(metrics.byCategory).forEach(category => {
      const catMetric = metrics.byCategory[category];
      if (catMetric.count > 0) {
        catMetric.accuracy = (catMetric.accurate / catMetric.count) * 100;
      }
    });
    
    metrics.byLocation.forEach((locationMetric, location) => {
      locationMetric.accuracy = (locationMetric.accurate / locationMetric.count) * 100;
    });
    
    return metrics;
  }
}

Scenario 4: Seasonal Inventory Planning

Handle seasonal demand patterns and inventory planning:

class SeasonalInventoryPlanner {
  constructor(inventoryManager) {
    this.inventory = inventoryManager;
    this.seasonalPatterns = new Map();
  }
  
  /**
   * Analyze historical seasonal patterns
   */
  async analyzeSeasonalPatterns(sku, yearsOfHistory = 3) {
    const salesData = await this.getSalesData(sku, yearsOfHistory);
    
    // Group by month
    const monthlyData = _.groupBy(salesData, sale => moment(sale.date).month());
    
    // Calculate seasonal indices
    const seasonalIndices = {};
    const averageMonthlySales = _.mean(Object.values(monthlyData).map(month => _.sumBy(month, 'quantity')));
    
    Object.keys(monthlyData).forEach(month => {
      const monthSales = _.sumBy(monthlyData[month], 'quantity');
      seasonalIndices[month] = monthSales / averageMonthlySales;
    });
    
    // Identify peak seasons
    const peakSeasons = Object.keys(seasonalIndices)
      .filter(month => seasonalIndices[month] > 1.2)
      .map(month => ({
        month: parseInt(month),
        monthName: moment().month(month).format('MMMM'),
        index: seasonalIndices[month]
      }));
    
    const pattern = {
      sku,
      seasonalIndices,
      peakSeasons,
      isHighlySeasonal: Math.max(...Object.values(seasonalIndices)) > 1.5,
      volatility: this.calculateVolatility(Object.values(seasonalIndices))
    };
    
    this.seasonalPatterns.set(sku, pattern);
    return pattern;
  }
  
  /**
   * Generate seasonal inventory plan
   */
  async generateSeasonalPlan(sku, planningHorizon = 12) {
    const pattern = await this.analyzeSeasonalPatterns(sku);
    const baseForecast = await this.inventory.demandForecasting.generateDemandForecast(sku, 30);
    
    const monthlyPlan = [];
    const currentMonth = moment().month();
    
    for (let i = 0; i < planningHorizon; i++) {
      const planMonth = (currentMonth + i) % 12;
      const seasonalIndex = pattern.seasonalIndices[planMonth] || 1;
      
      const adjustedForecast = baseForecast.predictedDemand * seasonalIndex;
      const leadTime = await this.inventory.getSupplierLeadTime(sku);
      
      // Calculate when to place order for this month's demand
      const orderDate = moment().add(i, 'months').subtract(leadTime, 'days');
      
      monthlyPlan.push({
        month: planMonth,
        monthName: moment().month(planMonth).format('MMMM'),
        year: moment().add(i, 'months').year(),
        forecastDemand: Math.round(adjustedForecast),
        seasonalIndex,
        recommendedOrderQuantity: this.calculateSeasonalOrderQuantity(sku, adjustedForecast, seasonalIndex),
        recommendedOrderDate: orderDate.toDate(),
        estimatedInventoryLevel: 0 // To be calculated
      });
    }
    
    // Calculate running inventory levels
    let runningInventory = await this.getCurrentInventoryLevel(sku);
    
    monthlyPlan.forEach((month, index) => {
      if (moment(month.recommendedOrderDate).isBefore(moment().add(index, 'months'))) {
        runningInventory += month.recommendedOrderQuantity;
      }
      runningInventory -= month.forecastDemand;
      month.estimatedInventoryLevel = runningInventory;
      
      // Flag potential stockouts
      if (runningInventory < 0) {
        month.stockoutRisk = true;
        month.stockoutQuantity = Math.abs(runningInventory);
      }
    });
    
    return {
      sku,
      planningHorizon,
      seasonalPattern: pattern,
      monthlyPlan,
      summary: {
        totalDemand: _.sumBy(monthlyPlan, 'forecastDemand'),
        totalOrders: _.sumBy(monthlyPlan, 'recommendedOrderQuantity'),
        stockoutRiskMonths: monthlyPlan.filter(m => m.stockoutRisk).length
      }
    };
  }
  
  /**
   * Pre-season inventory buildup
   */
  async executePreSeasonBuildup(sku, targetMonth, bufferWeeks = 4) {
    const seasonalPlan = await this.generateSeasonalPlan(sku);
    const targetMonthPlan = seasonalPlan.monthlyPlan.find(m => m.month === targetMonth);
    
    if (!targetMonthPlan) {
      throw new Error(`No plan found for month ${targetMonth}`);
    }
    
    const buildupStartDate = moment().month(targetMonth).subtract(bufferWeeks, 'weeks');
    const currentInventory = await this.getCurrentInventoryLevel(sku);
    
    // Calculate required buildup quantity
    const peakDemand = targetMonthPlan.forecastDemand;
    const requiredInventory = peakDemand * 1.2; // 20% buffer
    const buildupRequired = Math.max(0, requiredInventory - currentInventory);
    
    if (buildupRequired > 0) {
      // Create advance purchase order
      const purchaseOrder = await this.createAdvancePurchaseOrder({
        sku,
        quantity: buildupRequired,
        requestedDeliveryDate: buildupStartDate.toDate(),
        reason: `Pre-season buildup for ${targetMonthPlan.monthName}`,
        priority: 'high'
      });
      
      // Update seasonal plan tracking
      await this.updateSeasonalPlanExecution(sku, targetMonth, {
        buildupOrderId: purchaseOrder.id,
        buildupQuantity: buildupRequired,
        buildupDate: new Date()
      });
      
      return {
        buildupRequired,
        purchaseOrder,
        estimatedReadiness: buildupStartDate.toDate()
      };
    }
    
    return {
      buildupRequired: 0,
      message: 'Sufficient inventory for peak season'
    };
  }
}

Advanced Testing Examples

Create comprehensive tests for your inventory management system:

import { describe, it, beforeEach, afterEach } from 'mocha';
import { expect } from 'chai';
import sinon from 'sinon';

describe('Inventory Management System', () => {
  let inventoryManager;
  let mockClient;
  
  beforeEach(async () => {
    mockClient = {
      inventory: {
        get: sinon.stub(),
        update: sinon.stub(),
        list: sinon.stub()
      },
      locations: {
        list: sinon.stub()
      }
    };
    
    inventoryManager = new MultiLocationInventoryManager('test-key');
    inventoryManager.client = mockClient;
  });
  
  describe('Multi-location allocation', () => {
    it('should allocate from closest location when sufficient inventory', async () => {
      // Setup test data
      mockClient.inventory.list.resolves([
        { location_id: 'warehouse_1', sku: 'TEST-001', available_quantity: 100 },
        { location_id: 'warehouse_2', sku: 'TEST-001', available_quantity: 50 }
      ]);
      
      const orderItem = { sku: 'TEST-001', quantity: 10 };
      const customerLocation = { lat: 40.7128, lng: -74.0060 }; // NYC
      
      const allocation = await inventoryManager.findBestAllocation(orderItem, customerLocation);
      
      expect(allocation).to.have.property('sku', 'TEST-001');
      expect(allocation).to.have.property('quantity', 10);
      expect(allocation).to.have.property('location');
    });
    
    it('should split allocation across multiple locations when needed', async () => {
      mockClient.inventory.list.resolves([
        { location_id: 'warehouse_1', sku: 'TEST-001', available_quantity: 5 },
        { location_id: 'warehouse_2', sku: 'TEST-001', available_quantity: 8 }
      ]);
      
      const orderItem = { sku: 'TEST-001', quantity: 10 };
      const customerLocation = { lat: 40.7128, lng: -74.0060 };
      
      const allocation = await inventoryManager.attemptSplitAllocation(orderItem, [
        { locationId: 'warehouse_1', available: 5, score: 0.8 },
        { locationId: 'warehouse_2', available: 8, score: 0.7 }
      ]);
      
      expect(allocation).to.be.an('array');
      expect(allocation).to.have.length(2);
      expect(allocation.reduce((sum, alloc) => sum + alloc.quantity, 0)).to.equal(10);
    });
  });
  
  describe('Demand forecasting', () => {
    it('should generate accurate demand forecast using multiple models', async () => {
      const demandForecaster = new DemandForecastingManager(inventoryManager);
      
      // Mock historical sales data
      sinon.stub(demandForecaster, 'getSalesHistory').resolves([
        { date: '2024-01-01', quantity: 100 },
        { date: '2024-01-02', quantity: 110 },
        { date: '2024-01-03', quantity: 95 }
        // ... more data
      ]);
      
      const forecast = await demandForecaster.generateDemandForecast('TEST-001', 30);
      
      expect(forecast).to.have.property('sku', 'TEST-001');
      expect(forecast).to.have.property('predictedDemand');
      expect(forecast.predictedDemand).to.be.a('number');
      expect(forecast).to.have.property('confidence');
      expect(forecast.confidence).to.be.within(0, 1);
    });
  });
  
  describe('Cycle counting', () => {
    it('should adjust inventory when variance is within tolerance', async () => {
      const accuracyManager = new InventoryAccuracyManager(inventoryManager);
      
      mockClient.inventory.get.resolves({
        sku: 'TEST-001',
        available_quantity: 100
      });
      
      sinon.stub(accuracyManager, 'initiatePhysicalCount').resolves({
        physicalQuantity: 98,
        location: 'warehouse_1'
      });
      
      const results = await accuracyManager.executeCycleCount(['TEST-001'], 'test-user');
      
      expect(results).to.have.length(1);
      expect(results[0]).to.have.property('adjusted', true);
      expect(results[0].variance).to.equal(-2);
      expect(results[0].variancePercentage).to.equal(2);
    });
  });
});

// Performance testing
describe('Performance Tests', () => {
  it('should handle bulk inventory updates efficiently', async () => {
    const startTime = Date.now();
    
    const updates = Array.from({ length: 1000 }, (_, i) => ({
      sku: `TEST-${i.toString().padStart(3, '0')}`,
      quantity: Math.floor(Math.random() * 100)
    }));
    
    await inventoryManager.bulkUpdateInventory(updates);
    
    const endTime = Date.now();
    const duration = endTime - startTime;
    
    expect(duration).to.be.below(5000); // Should complete within 5 seconds
  });
});

Best Practices Summary

  1. Real-time Tracking: Use webhooks for immediate inventory updates
  2. Demand Forecasting: Implement multiple forecasting models for accuracy
  3. Multi-location Optimization: Consider shipping costs and delivery times
  4. Automated Reordering: Set up intelligent reorder points based on demand patterns
  5. Cycle Counting: Regular accuracy checks with ABC classification
  6. Seasonal Planning: Prepare for demand fluctuations in advance
  7. Performance Monitoring: Track key metrics like turnover and accuracy
  8. Error Handling: Implement robust error recovery and alerting
  9. Testing: Comprehensive unit and integration tests
  10. Security: Secure API keys and implement proper access controls

Troubleshooting

Next Steps

Conclusion

You now have a comprehensive inventory management system that handles:

  • ✅ Multi-location inventory optimization
  • ✅ Intelligent demand forecasting
  • ✅ Automated replenishment
  • ✅ Cycle counting and accuracy tracking
  • ✅ Seasonal planning and preparation
  • ✅ Real-time monitoring and alerts

This system provides the foundation for scalable, efficient inventory operations that can adapt to your business needs and grow with your company.