Distributed Order Management Quickstart Guide

Welcome to the Stateset One Distributed Order Management (DOM) Quickstart Guide! This guide will walk you through setting up your environment and using Stateset to manage your orders, inventory, and fulfillment processes across multiple sales channels and locations.

Imagine you’re an e-commerce business selling products through your online store, marketplaces, and physical locations. You need to efficiently manage orders, keep track of inventory, and make sure your customers get their products on time, no matter where they ordered them from. Stateset helps you achieve this with its powerful DOM features.

Table of Contents

  1. Introduction
  2. Getting Started
  3. Core Concepts of DOM
  4. API Walkthrough: End-to-End DOM Workflow
  5. Integration with Other Systems
  6. Real-Time Order Monitoring
  7. Error Handling and Logging
  8. Troubleshooting and Maintenance
  9. Support Resources
  10. Conclusion

Introduction

Distributed Order Management (DOM) is crucial for businesses managing orders across multiple channels and fulfillment locations. Stateset One provides a powerful API that simplifies DOM workflows, allowing you to manage your entire operation efficiently.

What You’ll Learn:

  • How to set up your Stateset environment and use the SDK.
  • Core DOM concepts, including order management, inventory control, and fulfillment.
  • How to use the Stateset API to manage your order process from start to finish.
  • Strategies for cross-channel synchronization and real-time monitoring.
  • How to handle errors and integrate with other systems.

Getting Started

Let’s get your environment ready.

1. Sign Up for Stateset One

2. Generate an API Key

  • After signing in, go to the Stateset Cloud Console to generate a new API key. This key will authenticate your API requests.

3. Install the Stateset Node.js SDK

Integrate Stateset’s SDK into your project to simplify API interactions.

Using npm:

npm install stateset-node

Using yarn:

yarn add stateset-node

4. Set Up Environment Variables

Store your API key securely using environment variables. (Note: This example is for bash shell, adapt as needed for your environment)

export STATESET_API_KEY=your_api_key_here

5. Initialize the Stateset Client

Initialize the Stateset client in your application using the generated API key. (Note: Requires Node.js 16 or higher)

import { stateset } from 'stateset-node';

// Initialize with your API key
const client = new stateset(process.env.STATESET_API_KEY);

// Verify connection
async function verifyConnection() {
  try {
    const status = await client.system.healthCheck();
    console.log('Connection status:', status);
  } catch (error) {
    console.error('Failed to connect:', error);
  }
}

verifyConnection();

Note: The above example uses async/await which requires a Javascript engine that supports it.


Core Concepts of DOM

Before diving into the API, it’s important to understand the key concepts behind a Distributed Order Management system. Here is a simplified view of the process:

  • Order Management: This encompasses the entire lifecycle of an order, from the moment a customer places it to its successful delivery. This includes capturing the order, routing, tracking status, and handling any modifications. Why is it important? Centralized order management improves efficiency by managing all orders in one platform. It also enhances the customer experience by providing accurate and timely information.
  • Inventory Management: This involves tracking and managing inventory levels across multiple locations. Accurate inventory data is key to ensuring products are available when they are needed, minimizing stockouts or overstocks. Why is it important? Proper inventory control is needed to fulfill orders and make sure your products are available. It also helps prevent delays in fulfillment.
  • Fulfillment Orchestration: This is the process of coordinating and executing the fulfillment of orders. It includes selecting the right fulfillment locations, picking and packing items, and shipping them to the customer. Why is it important? Proper fulfillment orchestration helps ensure products get to your customer quickly and efficiently.
  • Cross-Channel Synchronization: This ensures that order status and inventory levels are consistent across all sales channels. This will help create a seamless experience for the customers. Why is it important? Without it, inconsistencies will frustrate customers. For example, a customer could order an item online and find out it is out of stock at the physical store.
  • Reporting and Analytics: This provides insights into order trends, fulfillment performance, and inventory management. It enables data-driven decision-making and optimization of the overall process. Why is it important? Analysis of your data will allow you to make more informed decisions about your business.

Understanding these core components will be essential as we explore the Stateset API and its functionalities.


API Walkthrough: End-to-End DOM Workflow

Let’s dive into the end-to-end workflow with the Stateset API.

Workflow 1: Order Capture and Routing

In this section, we’ll cover capturing orders from various sources and intelligently routing them to the best fulfillment location.

Example 1: Capture an Order

async function captureOrder(orderData) {
    try {
      let order;
      switch(orderData.type) {
        case 'standard':
          order = await client.order.create({
            source: orderData.channel,
            channel_order_id: orderData.channelOrderId,
           customer: orderData.customer,
            line_items: orderData.lineItems,
            total_price: orderData.totalPrice,
             currency: orderData.currency,
           shipping_address: orderData.shippingAddress,
           status: 'received'
         });
         break;
       case 'pre-order':
          order = await createPreOrder(orderData);
          break;
        case 'backorder':
          order = await createBackOrder(orderData);
          break;
        case 'subscription':
           order = await createSubscriptionOrder(orderData);
         break;
        default:
          throw new Error('Unsupported order type');
      }
     console.log("Order Created:", order)
      return order;
    } catch (error) {
      console.error("Error capturing order:", error);
      throw error;
    }
}

const order = await captureOrder({
  type: 'standard',
  channel: 'web',
  channelOrderId: 'WEB-001',
  customer: { id: 'cust_001' },
   lineItems: [
    { product_id: 'prod_001', quantity: 2, price: 20, weight: 1, length: 5, width: 5, height: 5 },
    { product_id: 'prod_002', quantity: 1, price: 30, weight: 2, length: 10, width: 10, height: 10 }
    ],
  totalPrice: 70,
  currency: 'USD',
  shippingAddress: {
    street: '123 Main St',
    city: 'Anytown',
    state: 'CA',
    zip: '12345'
    }
})

This function demonstrates how to capture an order using the client.order.create() method. The order details include customer information, line items, shipping address, and the source channel. This captures data from different types of orders, and can be easily expanded to include different order scenarios.

Example 2: Route an Order

async function routeOrder(orderId) {
    try {
      const order = await client.order.get(orderId);
    const fulfillmentLocations = await client.location.list({ type: 'fulfillment' });

        if (!order || !fulfillmentLocations.length) {
           throw new Error('Invalid order or no fulfillment locations available');
        }

        // Placeholder: This is where you would use a machine learning algorithm to predict the best fulfillment location
       const mlPrediction = {
         confidence: 0.95,
         locationScores: fulfillmentLocations.reduce((acc, location) => {
         acc[location.id] = Math.random();
         return acc;
        }, {})
       }

        const inventoryLevels = await getInventoryLevels(order.line_items.map(item => item.product_id));
       const shippingRates = await getShippingRates(order);

      const optimalLocation = determineOptimalLocation(order, fulfillmentLocations, mlPrediction, shippingRates, inventoryLevels);

      if (!optimalLocation) {
       throw new Error('Unable to determine optimal fulfillment location');
    }
     const updatedOrder = await client.order.update(orderId, {
       assigned_location: optimalLocation.id,
        status: 'routed',
        routing_metadata: {
            prediction_confidence: mlPrediction.confidence,
            selected_shipping_rate: optimalLocation.selectedRate
          }
       });

    await logRoutingDecision(orderId, optimalLocation, mlPrediction);
    console.log("Order Routed:", updatedOrder);
    return {
      optimalLocation,
      updatedOrder
   };
  } catch (error) {
      console.error(`Error routing order ${orderId}:`, error);
    await client.order.update(orderId, { status: 'routing_failed' });
      throw error;
    }
}

function determineOptimalLocation(order, locations, mlPrediction, shippingRates, inventoryLevels) {
  const scoredLocations = locations.map(location => {
     const inventoryScore = calculateInventoryScore(location, order, inventoryLevels);
     const shippingScore = calculateShippingScore(location, shippingRates);
    const mlScore = mlPrediction.locationScores[location.id] || 0;
       const fulfillmentCost = estimateFulfillmentCost(location, order);

    const totalScore = (inventoryScore * 0.4) + (shippingScore * 0.3) + (mlScore * 0.2) - (fulfillmentCost * 0.1);

     return {
       ...location,
     score: totalScore,
        selectedRate: shippingRates[location.id].cheapestRate
      };
   });

   return scoredLocations.reduce((best, current) =>
      current.score > best.score ? current : best
    , scoredLocations[0]);
}

function calculateInventoryScore(location, order, inventoryLevels) {
    return order.line_items.reduce((score, item) => {
      const availability = inventoryLevels[location.id]?.[item.product_id] || 0;
      return score + (availability >= item.quantity ? 1 : 0);
   }, 0) / order.line_items.length;
}

function calculateShippingScore(location, shippingRates) {
  const locationRates = shippingRates[location.id];
   const cheapestRate = locationRates.cheapestRate;
  const fastestRate = locationRates.fastestRate;
   return 1 - (cheapestRate.price / fastestRate.price);
}

function estimateFulfillmentCost(location, order) {
    // Simplified cost estimation
  const baseCost = location.fulfillmentCostFactor || 1;
    return baseCost * order.line_items.reduce((total, item) => total + item.quantity, 0);
}

async function getShippingRates(order) {
  try {
      const carriers = await client.carrier.list();
    const fulfillmentLocations = await client.location.list({ type: 'fulfillment' });

     const rates = {};
    for (const location of fulfillmentLocations) {
     const locationRates = await Promise.all(carriers.map(carrier =>
        carrier.getRates({
            origin: location.address,
          destination: order.shipping_address,
          packages: convertOrderToPackages(order)
        })
      ));

      const flatRates = locationRates.flatMap(rate => rate);
     rates[location.id] = {
        allRates: flatRates,
        cheapestRate: flatRates.reduce((min, rate) => rate.price < min.price ? rate : min),
        fastestRate: flatRates.reduce((fastest, rate) => rate.deliveryDays < fastest.deliveryDays ? rate : fastest)
       };
      }

      return rates;
    } catch (error) {
     console.error("Error fetching shipping rates:", error);
        throw error
  }
}

function convertOrderToPackages(order) {
   // Simplified conversion - in reality, this would involve a more complex packing algorithm
   return [{
      weight: order.line_items.reduce((total, item) => total + (item.weight * item.quantity), 0),
     dimensions: {
       length: Math.max(...order.line_items.map(item => item.length)),
       width: Math.max(...order.line_items.map(item => item.width)),
        height: Math.max(...order.line_items.map(item => item.height))
     }
   }];
}

async function getInventoryLevels(productIds) {
    const inventoryRecords = await client.inventory.list({ product_id: { in: productIds } });
   return inventoryRecords.reduce((acc, record) => {
      acc[record.location_id] = acc[record.location_id] || {};
       acc[record.location_id][record.product_id] = record.available_quantity;
        return acc;
     }, {});
}

async function logRoutingDecision(orderId, optimalLocation, mlPrediction) {
  await client.log.create({
     type: 'order_routing',
      order_id: orderId,
    selected_location: optimalLocation.id,
    ml_confidence: mlPrediction.confidence,
     routing_score: optimalLocation.score
   });
}

// Example usage
const routedOrder = await routeOrder(order.id);
console.log("Routed Order:", routedOrder)

This function illustrates a more complex process, using a combination of inventory data, shipping rates, and an ML prediction (placeholder) to determine the optimal fulfillment location for the order. It includes placeholders for you to expand to include your own logic. This helps to understand the decision making process that goes into routing.

Workflow 2: Inventory Management and Allocation

This section focuses on managing inventory levels, allocating stock to orders, and rebalancing inventory across locations.

Example 1: Get a Global Inventory View

async function getGlobalInventory(productId) {
  try {
    const inventoryList = await client.inventory.list({ product_id: productId });
      const globalInventory = inventoryList.reduce((acc, inv) => {
      acc[inv.location_id] = {
        quantity: inv.quantity,
          type: inv.inventory_type, // 'owned', 'dropship', or 'jit'
        safetyStock: inv.safety_stock_level
      };
        return acc;
     }, {});
      console.log("Global Inventory:", globalInventory)
    return globalInventory;
  } catch (error) {
      console.error("Error fetching global inventory:", error);
      throw error;
  }
}

//Example of use
const globalInventory = await getGlobalInventory('prod_001')

This function retrieves a global view of inventory for a specific product across all locations, using the client.inventory.list() method. This will give you a snapshot of all the inventory, including the type and safety stock levels.

Example 2: Allocate Inventory for an Order

async function allocateInventory(orderId) {
   let allocatedItems = [];
  try {
   const order = await client.order.get(orderId);
   const allocationResults = await Promise.all(order.line_items.map(async (item) => {
     const allocation = await determineOptimalAllocation(item, order.assigned_location);
      return { item, allocation };
    }));

      for (const { item, allocation } of allocationResults) {
        for (const alloc of allocation) {
        if (alloc.quantity > 0) {
          await client.inventory.allocate({
           product_id: item.product_id,
           location_id: alloc.location_id,
           quantity: alloc.quantity,
            allocation_type: alloc.type,
            order_id: orderId,
            item_id: item.id
           });
        allocatedItems.push({
            item_id: item.id,
            product_id: item.product_id,
           allocated_quantity: alloc.quantity,
          location_id: alloc.location_id,
           allocation_type: alloc.type
           });
        }
       }
      }

    const fullyAllocated = allocatedItems.every(ai =>
        ai.allocated_quantity === order.line_items.find(li => li.id === ai.item_id).quantity
     );
    await client.order.update(orderId, {
        status: fullyAllocated ? 'fully_allocated' : 'partially_allocated',
      inventory_allocations: allocatedItems
     });

      await logAllocationResult(orderId, allocatedItems, fullyAllocated);
      console.log("Allocation Results:", { orderId, allocatedItems, fullyAllocated });
      return { orderId, allocatedItems, fullyAllocated };
  } catch (error) {
     console.error(`Error allocating inventory for order ${orderId}:`, error);
    await client.order.update(orderId, {
       status: 'allocation_failed',
        allocation_error: error.message
     });
    throw error;
  }
}

async function determineOptimalAllocation(item, preferredLocation) {
  const inventory = await getInventoryLevels(item.product_id);
  const reservations = await getActiveReservations(item.product_id);
  const safetyStockLevels = await getSafetyStockLevels(item.product_id);

  let remainingQuantity = item.quantity;
    let allocations = [];

   // Try to allocate from the preferred location first
    if (inventory[preferredLocation]) {
     const preferredAllocation = allocateFromLocation(
       preferredLocation,
     remainingQuantity,
      inventory[preferredLocation],
       reservations[preferredLocation],
       safetyStockLevels[preferredLocation]
   );
      if (preferredAllocation.quantity > 0) {
        allocations.push(preferredAllocation);
          remainingQuantity -= preferredAllocation.quantity;
    }
    }

    // If we couldn't fully allocate from the preferred location, try others
   if (remainingQuantity > 0) {
     for (const [locationId, quantity] of Object.entries(inventory)) {
     if (locationId === preferredLocation) continue;

       const allocation = allocateFromLocation(
          locationId,
          remainingQuantity,
          quantity,
           reservations[locationId],
         safetyStockLevels[locationId]
     );

    if (allocation.quantity > 0) {
       allocations.push(allocation);
     remainingQuantity -= allocation.quantity;
      if (remainingQuantity === 0) break;
       }
     }
  }

    // If we still couldn't allocate everything, create a backorder
   if (remainingQuantity > 0) {
      allocations.push({
        location_id: 'backorder',
          quantity: remainingQuantity,
          type: 'backorder'
       });
     }
  return allocations;
}

function allocateFromLocation(locationId, requiredQuantity, availableQuantity, reservations, safetyStock) {
  const effectiveQuantity = Math.max(availableQuantity - reservations - safetyStock, 0);
  const allocatedQuantity = Math.min(requiredQuantity, effectiveQuantity);

  return {
     location_id: locationId,
      quantity: allocatedQuantity,
      type: allocatedQuantity > 0 ? 'standard' : 'none'
   };
}

async function getActiveReservations(productId) {
    const reservations = await client.reservation.list({ product_id: productId, status: 'active' });
    return reservations.reduce((acc, reservation) => {
      acc[reservation.location_id] = (acc[reservation.location_id] || 0) + reservation.quantity;
      return acc;
    }, {});
}

async function getSafetyStockLevels(productId) {
  const safetyStockRecords = await client.safetyStock.list({ product_id: productId });
    return safetyStockRecords.reduce((acc, record) => {
      acc[record.location_id] = record.quantity;
       return acc;
   }, {});
}

async function logAllocationResult(orderId, allocatedItems, fullyAllocated) {
    await client.log.create({
      type: 'inventory_allocation',
      order_id: orderId,
       allocated_items: allocatedItems,
      fully_allocated: fullyAllocated,
      timestamp: new Date().toISOString()
     });
}

//Example of use
const allocatedOrder = await allocateInventory(order.id)
console.log("Allocated order:", allocatedOrder)

This code shows how inventory is allocated to an order, based on the preferred location and available inventory. This function also demonstrates how to allocate from other locations if the primary location does not have sufficient stock.

Example 3: Rebalance Inventory

async function rebalanceInventory() {
  const rebalancingLog = [];
  try {
    const inventory = await client.inventory.list();
    const locations = await client.location.list();
    const salesVelocity = await getSalesVelocityByLocation();
    const seasonalTrends = await getSeasonalTrends();
    const shippingCosts = await getShippingCostMatrix();
    const storageCapacity = await getStorageCapacityByLocation();

      const rebalancingPlan = generateRebalancingPlan(
          inventory,
        locations,
       salesVelocity,
        seasonalTrends,
          shippingCosts,
           storageCapacity
      );
    for (const transfer of rebalancingPlan) {
      try {
        await client.inventory.transfer(transfer);
          rebalancingLog.push({
              status: 'success',
            transfer: transfer
        });
       } catch (transferError) {
         console.error(`Error executing transfer:`, transferError);
           rebalancingLog.push({
            status: 'failed',
              transfer: transfer,
           error: transferError.message
        });
       }
      }
    const successfulTransfers = rebalancingLog.filter(log => log.status === 'success').length;
      console.log(`Inventory rebalancing completed. ${successfulTransfers}/${rebalancingPlan.length} transfers successful.`);

    await logRebalancingResult(rebalancingLog);

   return {
     totalTransfers: rebalancingPlan.length,
       successfulTransfers: successfulTransfers,
      rebalancingLog: rebalancingLog
    };
 } catch (error) {
      console.error("Error in inventory rebalancing process:", error);
    await logRebalancingError(error);
        throw error;
   }
}

function generateRebalancingPlan(inventory, locations, salesVelocity, seasonalTrends, shippingCosts, storageCapacity) {
    const rebalancingPlan = [];

   // Group inventory by product
   const productInventory = groupInventoryByProduct(inventory);

  for (const [productId, productLocations] of Object.entries(productInventory)) {
      const totalInventory = sum(Object.values(productLocations));
    const totalSalesVelocity = sum(Object.values(salesVelocity[productId] || {}));

    if (totalSalesVelocity === 0) continue; // Skip products with no sales

   const idealDistribution = calculateIdealDistribution(
        productId,
        totalInventory,
       locations,
      salesVelocity,
       seasonalTrends,
      storageCapacity
     );

      for (const [fromLocation, fromQuantity] of Object.entries(productLocations)) {
        const idealQuantity = idealDistribution[fromLocation] || 0;
        const difference = fromQuantity - idealQuantity;

      if (difference > 0) { // This location has excess inventory
        for (const [toLocation, toIdealQuantity] of Object.entries(idealDistribution)) {
         if (productLocations[toLocation] < toIdealQuantity) {
              const transferQuantity = Math.min(difference, toIdealQuantity - productLocations[toLocation]);
           if (transferQuantity > 0 && isTransferCostEffective(productId, fromLocation, toLocation, transferQuantity, shippingCosts)) {
           rebalancingPlan.push({
               product_id: productId,
                from_location: fromLocation,
                  to_location: toLocation,
                 quantity: transferQuantity
              });
           productLocations[fromLocation] -= transferQuantity;
            productLocations[toLocation] = (productLocations[toLocation] || 0) + transferQuantity;
            }
          }
         }
        }
     }
   }

  return rebalancingPlan;
}

function calculateIdealDistribution(productId, totalInventory, locations, salesVelocity, seasonalTrends, storageCapacity) {
  const distribution = {};
    const totalSalesVelocity = sum(Object.values(salesVelocity[productId] || {}));

  for (const location of locations) {
   const locationSalesVelocity = salesVelocity[productId]?.[location.id] || 0;
  const seasonalFactor = seasonalTrends[productId]?.[location.id] || 1;
    const adjustedSalesVelocity = locationSalesVelocity * seasonalFactor;

        let idealQuantity = Math.round((adjustedSalesVelocity / totalSalesVelocity) * totalInventory);
      idealQuantity = Math.min(idealQuantity, storageCapacity[location.id]); // Respect storage capacity

        distribution[location.id] = idealQuantity;
  }

  return distribution;
}

function isTransferCostEffective(productId, fromLocation, toLocation, quantity, shippingCosts) {
    const transferCost = shippingCosts[fromLocation]?.[toLocation] * quantity;
    const productValue = getProductValue(productId);
    const transferValueRatio = transferCost / (productValue * quantity);

   // Consider transfer cost-effective if it's less than 10% of the inventory value
    return transferValueRatio < 0.1;
}

function groupInventoryByProduct(inventory) {
    return inventory.reduce((acc, item) => {
       acc[item.product_id] = acc[item.product_id] || {};
     acc[item.product_id][item.location_id] = item.quantity;
        return acc;
   }, {});
}

async function getSalesVelocityByLocation() {
    // Placeholder: In a real implementation you'd use a caching layer
   const cacheKey = 'sales_velocity';
     // let salesVelocity = cache.get(cacheKey);

     //if (salesVelocity) {
   //   return salesVelocity;
     //}

    try {
     // Assume we're fetching data for the last 90 days
      const endDate = new Date();
        const startDate = new Date(endDate.getTime() - (90 * 24 * 60 * 60 * 1000));

     const salesData = await client.analytics.getSales({
          start_date: startDate.toISOString(),
           end_date: endDate.toISOString(),
          group_by: ['product_id', 'location_id']
       });

   let salesVelocity = salesData.reduce((acc, sale) => {
        acc[sale.product_id] = acc[sale.product_id] || {};
       acc[sale.product_id][sale.location_id] = sale.quantity / 90; // Daily sales velocity
            return acc;
   }, {});

     // cache.set(cacheKey, salesVelocity);
     return salesVelocity;
    } catch (error) {
       console.error('Error fetching sales velocity:', error);
      throw new Error('Failed to fetch sales velocity data');
   }
}

async function getSeasonalTrends() {
   // Placeholder: In a real implementation you'd use a caching layer
     const cacheKey = 'seasonal_trends';
     // let seasonalTrends = cache.get(cacheKey);

    //  if (seasonalTrends) {
       //   return seasonalTrends;
     //  }

    try {
        // Assume we're fetching seasonal data for the next 90 days
     const startDate = new Date();
        const endDate = new Date(startDate.getTime() + (90 * 24 * 60 * 60 * 1000));

        const trendsData = await client.analytics.getSeasonalTrends({
          start_date: startDate.toISOString(),
        end_date: endDate.toISOString(),
        group_by: ['product_id', 'location_id']
       });

        let seasonalTrends = trendsData.reduce((acc, trend) => {
         acc[trend.product_id] = acc[trend.product_id] || {};
         acc[trend.product_id][trend.location_id] = trend.seasonal_factor;
            return acc;
       }, {});

      // cache.set(cacheKey, seasonalTrends);
      return seasonalTrends;
    } catch (error) {
     console.error('Error fetching seasonal trends:', error);
        throw new Error('Failed to fetch seasonal trend data');
    }
}

async function getShippingCostMatrix() {
  // Placeholder: In a real implementation you'd use a caching layer
    const cacheKey = 'shipping_cost_matrix';
    // let shippingCostMatrix = cache.get(cacheKey);

   // if (shippingCostMatrix) {
       // return shippingCostMatrix;
    // }

    try {
    const locations = await client.location.list();
        let shippingCostMatrix = {};

     for (const fromLocation of locations) {
       shippingCostMatrix[fromLocation.id] = {};
            for (const toLocation of locations) {
                if (fromLocation.id !== toLocation.id) {
                  const shippingRate = await client.shipping.getRate({
                       from: fromLocation.id,
                        to: toLocation.id,
                        weight: 1, // Assume 1 kg as a base weight
                        volume: 1 // Assume 1 cubic meter as a base volume
                    });
                   shippingCostMatrix[fromLocation.id][toLocation.id] = shippingRate.cost;
           }
           }
      }

        //  cache.set(cacheKey, shippingCostMatrix);
    return shippingCostMatrix;
    } catch (error) {
     console.error('Error fetching shipping cost matrix:', error);
       throw new Error('Failed to fetch shipping cost data');
   }
}

async function getStorageCapacityByLocation() {
    // Placeholder: In a real implementation you'd use a caching layer
    const cacheKey = 'storage_capacity';
    //  let storageCapacity = cache.get(cacheKey);

    // if (storageCapacity) {
      //  return storageCapacity;
     //}

  try {
        const locations = await client.location.list();
      let storageCapacity = {};

        for (const location of locations) {
          const capacityData = await client.warehouse.getCapacity(location.id);
        storageCapacity[location.id] = capacityData.available_capacity;
       }
    // cache.set(cacheKey, storageCapacity);
    return storageCapacity;
    } catch (error) {
        console.error('Error fetching storage capacity:', error);
       throw new Error('Failed to fetch storage capacity data');
    }
}

function getProductValue(productId) {
    return new Promise((resolve, reject) => {
       // Placeholder: In a real implementation you'd use a caching layer
       const cacheKey = `product_value_${productId}`;
     // const cachedValue = cache.get(cacheKey);

        //if (cachedValue) {
          //  resolve(cachedValue);
      //  } else {
            client.product.get(productId)
            .then(product => {
                const value = product.cost || product.price || 0;
         //     cache.set(cacheKey, value);
                resolve(value);
        })
            .catch(error => {
               console.error(`Error fetching product value for ${productId}:`, error);
               reject(new Error(`Failed to fetch product value for ${productId}`));
      });
      //  }
  });
}

function sum(numbers) {
   return numbers.reduce((a, b) => a + b, 0);
}

async function logRebalancingResult(rebalancingLog) {
    await client.log.create({
     type: 'inventory_rebalancing',
      result: rebalancingLog,
       timestamp: new Date().toISOString()
    });
}

async function logRebalancingError(error) {
   await client.log.create({
       type: 'inventory_rebalancing_error',
     error: error.message,
        stack: error.stack,
       timestamp: new Date().toISOString()
    });
}
// Example of use
const rebalancedInventory = await rebalanceInventory();
console.log("Rebalanced Inventory:", rebalancedInventory)

This function shows how to rebalance your inventory across multiple locations using a combination of sales data, seasonal trends, and shipping costs. The code has placeholders to highlight where you would implement specific real-world data sources.

Workflow 3: Fulfillment

This section focuses on creating fulfillment orders and tracking their status.

Example 1: Create a Fulfillment Order

async function createFulfillmentOrder(orderId) {
   try {
        const order = await client.order.get(orderId);
       const fulfillmentCenter = await selectFulfillmentCenter(order);

      const fulfillmentOrder = await client.fulfillmentorder.create({
           order_id: orderId,
         location_id: fulfillmentCenter.id,
       line_items: order.line_items,
      picking_method: determineOptimalPickingMethod(order, fulfillmentCenter)
      });

        await client.order.update(orderId, { status: 'in_fulfillment' });
        
      console.log("Fulfillment Order Created:", fulfillmentOrder)
        return fulfillmentOrder;
    } catch (error) {
        console.error(`Error creating fulfillment order for ${orderId}:`, error);
        await client.order.update(orderId, { status: 'fulfillment_failed' });
        throw error;
    }
}

async function selectFulfillmentCenter(order) {
    // Placeholder: Implement more sophisticated logic to select the fulfillment location
    // based on inventory availability, order details, etc.

    const fulfillmentLocation = await client.location.get(order.assigned_location);

    if (!fulfillmentLocation) {
        throw new Error(`Fulfillment location ${order.assigned_location} not found`);
    }

    return fulfillmentLocation;
}

function determineOptimalPickingMethod(order, fulfillmentCenter) {
  // Placeholder: Implement logic to select picking method based on items, location, etc.
  // Can use zone-based picking, wave picking, single item picking, etc.

    if (fulfillmentCenter.picking_method === 'zone') {
        return 'zone-based';
    } else if (order.line_items.length > 5) {
      return 'wave';
  }
  return 'single-item';
}

// Example of use
const fulfillmentOrder = await createFulfillmentOrder(order.id);
console.log("Fulfillment order:", fulfillmentOrder);

This function demonstrates how to create a fulfillment order, selecting a fulfillment center and determining a picking method based on order details.

Example 2: Track Fulfillment Status

async function trackFulfillmentStatus(fulfillmentOrderId) {
    try {
      const fulfillmentOrder = await client.fulfillmentorder.get(fulfillmentOrderId);
      const status = fulfillmentOrder.status;
      console.log("Fulfillment Status:", { fulfillmentOrderId, status });
      return { fulfillmentOrderId, status };
   } catch (error) {
      console.error(`Error tracking fulfillment status for ${fulfillmentOrderId}:`, error);
    throw error;
   }
}

// Example of use
const fulfillmentStatus = await trackFulfillmentStatus(fulfillmentOrder.id);
console.log("Fulfillment status:", fulfillmentStatus);

This function retrieves the status of a fulfillment order using the client.fulfillmentorder.get() method, showcasing how to track the progress of fulfillment.

Example 3: Update Fulfillment Status

async function updateFulfillmentStatus(fulfillmentOrderId, newStatus, trackingNumber, carrier, estimatedDelivery) {
  try {
    const updatedFulfillmentOrder = await client.fulfillmentorder.update(fulfillmentOrderId, {
      status: newStatus,
      tracking_number: trackingNumber,
      carrier: carrier,
      estimated_delivery_date: estimatedDelivery
     });

    console.log("Fulfillment Status Updated:", updatedFulfillmentOrder);
     return updatedFulfillmentOrder;
  } catch (error) {
      console.error(`Error updating fulfillment status for ${fulfillmentOrderId}:`, error);
      throw error;
  }
}

// Example usage: Update the fulfillment status to "shipped"
const updatedStatus = await updateFulfillmentStatus(
    fulfillmentOrder.id,
    'shipped',
  'TRACK12345',
    'UPS',
  new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString()
  );
console.log("Updated status:", updatedStatus)

This example showcases how to update the status of a fulfillment order, which is essential for keeping track of order progress and providing updates to customers. This example also includes tracking information.

Workflow 4: Cross-Channel Synchronization

This section demonstrates how to synchronize order and inventory data across different channels.

Example 1: Sync Order Status Across Channels

async function syncOrderStatus(orderId, channel) {
    try {
    const order = await client.order.get(orderId);
    const channelStatus = mapStatesetOrderStatusToChannelStatus(order.status, channel);

    // Placeholder: Implement API call to update the order status in the specific channel
    console.log(`Updating order ${orderId} in channel ${channel} to status:`, channelStatus);
    // Example of updating status on a hypothetical channel API
    await updateOrderStatusOnChannel(orderId, channel, channelStatus);

    console.log(`Order status synced for order ${orderId} in channel ${channel}`);
    return { orderId, channel, channelStatus };
    } catch (error) {
      console.error(`Error syncing order status for ${orderId} in channel ${channel}:`, error);
      throw error;
  }
}

function mapStatesetOrderStatusToChannelStatus(statesetStatus, channel) {
    // Placeholder: Implement mapping logic between Stateset order statuses and channel specific statuses
  if (channel === 'web') {
        switch(statesetStatus) {
      case 'received':
        return 'pending';
      case 'routed':
      case 'fully_allocated':
      case 'partially_allocated':
      case 'in_fulfillment':
      case 'shipped':
        return 'processing';
       case 'delivered':
         return 'completed';
      case 'cancelled':
        return 'cancelled';
      default:
        return 'unknown';
       }
    } else if (channel === 'marketplace') {
      switch(statesetStatus) {
        case 'received':
         return 'pending';
       case 'routed':
        case 'fully_allocated':
       case 'partially_allocated':
       case 'in_fulfillment':
       case 'shipped':
        return 'shipped';
        case 'delivered':
          return 'delivered';
        case 'cancelled':
          return 'cancelled';
       default:
        return 'unknown';
        }
    }
    return 'unknown'; // Default status if channel is not known
}

async function updateOrderStatusOnChannel(orderId, channel, status) {
    // Placeholder: In a real implementation, this would make an API call
    // to update the order status on the channel, using the mapping logic above
  console.log(`Updated order ${orderId} status in ${channel} to ${status}`);
    // Simulate an API call
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log(`Order status on ${channel} updated to ${status}`)
            resolve({ status: 'success', orderId, channel, status});
        }, 500);
    });
}

// Example usage
const syncedStatus = await syncOrderStatus(order.id, 'web');
console.log("Synced status:", syncedStatus);

This function demonstrates how to synchronize order status from Stateset to an external sales channel, using a mapping function to translate between the two status types.

Example 2: Sync Inventory Levels Across Channels

async function syncInventoryLevels(productId) {
  try {
    const globalInventory = await getGlobalInventory(productId);

      for (const locationId of Object.keys(globalInventory)) {
         const channel = await getAssociatedChannel(locationId);
          const inventoryData = globalInventory[locationId]

        if (channel) {
            // Placeholder: Implement API call to update the inventory on specific channel
            console.log(`Updating inventory for ${productId} on channel ${channel} with:`, inventoryData);
             await updateInventoryOnChannel(productId, channel, inventoryData);
            console.log(`Inventory synced for ${productId} on channel ${channel}.`);
         } else {
            console.log(`No channel associated with location: ${locationId}`);
       }
     }
    return globalInventory;
  } catch (error) {
    console.error(`Error syncing inventory for product ${productId} across channels:`, error);
     throw error;
  }
}

async function getAssociatedChannel(locationId) {
 // Placeholder: Implement the logic to retrieve the channel based on the location.
    if (locationId === 'loc_001' || locationId === 'loc_002') {
        return 'web';
   } else if (locationId === 'loc_003') {
       return 'marketplace';
   }
    return null;
}

async function updateInventoryOnChannel(productId, channel, inventoryData) {
  // Placeholder: In a real implementation, this would make an API call
  // to update the inventory on the channel

    return new Promise((resolve) => {
        setTimeout(() => {
            console.log(`Updated inventory for ${productId} on ${channel} with:`, inventoryData)
            resolve({ status: 'success', productId, channel, inventoryData});
        }, 500);
    });
}


// Example usage
const syncedInventory = await syncInventoryLevels('prod_001');
console.log("Synced inventory:", syncedInventory)

This example illustrates how to sync inventory levels from Stateset to different sales channels, ensuring inventory data is consistent across all platforms.

Workflow 5: Reporting and Analytics

This section will demonstrate how to retrieve reports and analytics from the Stateset API.

Example 1: Get Sales Reports

async function getSalesReport(startDate, endDate) {
  try {
    const salesReport = await client.analytics.getSales({
      start_date: startDate.toISOString(),
        end_date: endDate.toISOString()
   });
     console.log("Sales Report:", salesReport);
    return salesReport;
  } catch (error) {
    console.error("Error fetching sales report:", error);
      throw error;
  }
}

// Example Usage
const salesReport = await getSalesReport(
    new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
    new Date()
   );
console.log("Sales report:", salesReport)

This function retrieves sales reports, providing insights into overall sales trends and performance.

Example 2: Get Inventory Reports

async function getInventoryReport() {
    try {
    const inventoryReport = await client.inventory.list();
    console.log("Inventory Report:", inventoryReport);
    return inventoryReport;
  } catch (error) {
      console.error("Error fetching inventory report:", error);
       throw error;
  }
}

// Example Usage
const inventoryReport = await getInventoryReport();
console.log("Inventory report:", inventoryReport)

This function retrieves inventory reports, allowing you to monitor inventory levels across different locations and products.

Example 3: Get Fulfillment Performance Reports

async function getFulfillmentPerformanceReport(startDate, endDate) {
  try {
    const fulfillmentReport = await client.analytics.getFulfillmentPerformance({
        start_date: startDate.toISOString(),
        end_date: endDate.toISOString()
    });
     console.log("Fulfillment Report:", fulfillmentReport);
    return fulfillmentReport;
    } catch (error) {
        console.error("Error fetching fulfillment performance report:", error);
      throw error;
  }
}

// Example Usage
const fulfillmentReport = await getFulfillmentPerformanceReport(
    new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
    new Date()
    );
console.log("Fulfillment Report:", fulfillmentReport)

This function retrieves fulfillment performance reports, providing data on the efficiency and speed of your fulfillment processes.


Integration with Other Systems

Stateset’s API is designed to be flexible and integrate seamlessly with your existing systems. You can integrate it with:

  • E-commerce platforms such as Shopify, Magento, and WooCommerce,
  • Marketplaces like Amazon, eBay, and Etsy,
  • Warehouse management systems (WMS) like NetSuite,
  • Enterprise resource planning (ERP) systems such as SAP and Oracle,
  • Carrier APIs from FedEx, UPS, and USPS, and more.

Implementing this will require you to build logic and data mappings between the systems based on your unique needs.

Real-Time Order Monitoring

Stateset’s API provides webhook functionality that can allow for you to receive real-time updates on order status, inventory changes, and fulfillment progress. The real-time capability allows for your systems to receive updates without having to poll for information.

Error Handling and Logging

Proper error handling and logging are essential for any robust application. Here’s how you can handle errors and log them:

  • Catch Errors: Implement try-catch blocks around all API calls to handle any errors that might occur.
  • Log Errors: Use Stateset’s Log API to record all errors for analysis and debugging. Include relevant context like the order ID, product ID, timestamp, and a detailed error message.
  • Retry Logic: Implement retry logic for transient errors, such as network issues or API timeouts. Use exponential backoff to avoid overwhelming the system.
  • Alerting: Set up alerts for critical errors. This will notify you when issues occur that require immediate attention.

Troubleshooting and Maintenance

  • API Documentation: Refer to the comprehensive Stateset API documentation for details on each endpoint and its parameters.
  • Logging: Review logs regularly to identify issues early. Log everything from API calls to any internal process issues.
  • Monitoring: Set up performance monitoring to ensure smooth operation. Watch for performance bottlenecks and make the required adjustments.
  • Maintenance: Plan regular maintenance and updates to keep your application running efficiently and securely.

Support Resources

If you encounter issues or have questions, here are some resources that can help you:

Conclusion

Congratulations on completing this quickstart guide! You’ve taken a major step in understanding and using Stateset One for Distributed Order Management. You now have the tools to manage orders, inventory, and fulfillment across multiple channels and locations using a robust API. Continue to explore the Stateset API documentation and other support resources to deepen your expertise.