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)
## Support Resources
If you encounter issues or have questions, here are some resources that can help you:
* **Stateset Documentation:** [https://docs.stateset.io](https://docs.stateset.io)
* **Community Forum:** [https://community.stateset.io](https://community.stateset.io)
* **Email Support:** [support@stateset.com](mailto:support@stateset.com)
## Complete Order Lifecycle Implementation
Here's a production-ready implementation that ties everything together:
```javascript
import { StateSetClient } from 'stateset-node';
import { EventEmitter } from 'events';
class OrderLifecycleManager extends EventEmitter {
constructor(apiKey) {
super();
this.client = new StateSetClient({ apiKey });
this.setupEventHandlers();
}
/**
* Process a complete order from creation to delivery
*/
async processOrder(orderData) {
const orderLog = {
orderId: null,
steps: [],
errors: [],
startTime: Date.now()
};
try {
// Step 1: Validate and create order
this.emit('step:start', 'order_creation');
const order = await this.createOrderWithValidation(orderData);
orderLog.orderId = order.id;
orderLog.steps.push({ step: 'created', timestamp: Date.now() });
// Step 2: Check inventory and allocate
this.emit('step:start', 'inventory_check');
const allocation = await this.allocateInventoryWithFallback(order);
orderLog.steps.push({ step: 'allocated', timestamp: Date.now() });
// Step 3: Route to optimal fulfillment center
this.emit('step:start', 'routing');
const routing = await this.routeOrderIntelligently(order, allocation);
orderLog.steps.push({ step: 'routed', timestamp: Date.now() });
// Step 4: Create fulfillment order
this.emit('step:start', 'fulfillment');
const fulfillment = await this.createFulfillmentWithRetry(order, routing);
orderLog.steps.push({ step: 'fulfillment_created', timestamp: Date.now() });
// Step 5: Generate shipping label
this.emit('step:start', 'shipping');
const shipping = await this.generateShippingLabel(fulfillment);
orderLog.steps.push({ step: 'shipped', timestamp: Date.now() });
// Step 6: Notify customer
this.emit('step:start', 'notification');
await this.sendCustomerNotifications(order, shipping);
orderLog.steps.push({ step: 'customer_notified', timestamp: Date.now() });
// Success!
orderLog.completionTime = Date.now();
orderLog.duration = orderLog.completionTime - orderLog.startTime;
this.emit('order:complete', orderLog);
return {
success: true,
order,
fulfillment,
shipping,
log: orderLog
};
} catch (error) {
orderLog.errors.push({
step: this.currentStep,
error: error.message,
timestamp: Date.now()
});
// Attempt recovery
await this.handleOrderError(orderLog, error);
this.emit('order:failed', orderLog);
throw error;
}
}
async createOrderWithValidation(orderData) {
// Validate order data
const validation = this.validateOrderData(orderData);
if (!validation.valid) {
throw new Error(`Order validation failed: ${validation.errors.join(', ')}`);
}
// Check for duplicate orders
const isDuplicate = await this.checkDuplicateOrder(orderData);
if (isDuplicate) {
throw new Error('Duplicate order detected');
}
// Create order with idempotency key
const order = await this.client.orders.create({
...orderData,
idempotency_key: this.generateIdempotencyKey(orderData)
});
return order;
}
async allocateInventoryWithFallback(order) {
try {
// Try primary allocation
return await this.client.inventory.allocate({
order_id: order.id,
items: order.items
});
} catch (error) {
if (error.code === 'INSUFFICIENT_INVENTORY') {
// Try split fulfillment
return await this.attemptSplitFulfillment(order);
}
throw error;
}
}
async routeOrderIntelligently(order, allocation) {
// Get all potential fulfillment locations
const locations = await this.client.locations.list({
type: 'fulfillment',
active: true
});
// Score each location
const scoredLocations = await Promise.all(
locations.map(async (location) => {
const score = await this.scoreLocation(location, order, allocation);
return { location, score };
})
);
// Select optimal location
const optimal = scoredLocations.reduce((best, current) =>
current.score > best.score ? current : best
);
// Update order with routing
await this.client.orders.update(order.id, {
fulfillment_location: optimal.location.id,
routing_score: optimal.score
});
return optimal;
}
async createFulfillmentWithRetry(order, routing) {
let attempts = 0;
const maxAttempts = 3;
while (attempts < maxAttempts) {
try {
return await this.client.fulfillment.create({
order_id: order.id,
location_id: routing.location.id,
priority: this.calculatePriority(order)
});
} catch (error) {
attempts++;
if (attempts >= maxAttempts) throw error;
// Wait with exponential backoff
await this.sleep(Math.pow(2, attempts) * 1000);
}
}
}
async generateShippingLabel(fulfillment) {
// Select best carrier
const carrier = await this.selectOptimalCarrier(fulfillment);
// Generate label
const label = await this.client.shipping.createLabel({
fulfillment_id: fulfillment.id,
carrier: carrier.id,
service: carrier.recommended_service,
insurance: fulfillment.total_value > 100
});
return label;
}
async sendCustomerNotifications(order, shipping) {
const notifications = [];
// Email notification
notifications.push(
this.client.notifications.send({
type: 'email',
to: order.customer_email,
template: 'order_shipped',
data: {
order_number: order.number,
tracking_number: shipping.tracking_number,
tracking_url: shipping.tracking_url,
estimated_delivery: shipping.estimated_delivery
}
})
);
// SMS if opted in
if (order.customer_phone && order.sms_opt_in) {
notifications.push(
this.client.notifications.send({
type: 'sms',
to: order.customer_phone,
message: `Your order ${order.number} has shipped! Track: ${shipping.tracking_url}`
})
);
}
await Promise.all(notifications);
}
setupEventHandlers() {
this.on('step:start', (step) => {
this.currentStep = step;
console.log(`📋 Starting: ${step}`);
});
this.on('order:complete', (log) => {
console.log(`✅ Order ${log.orderId} completed in ${log.duration}ms`);
});
this.on('order:failed', (log) => {
console.error(`❌ Order ${log.orderId} failed:`, log.errors);
});
}
// Helper methods
validateOrderData(data) {
const errors = [];
if (!data.customer_email) errors.push('Missing customer email');
if (!data.items || data.items.length === 0) errors.push('No items in order');
if (!data.shipping_address) errors.push('Missing shipping address');
// Validate each item
data.items?.forEach((item, index) => {
if (!item.sku) errors.push(`Item ${index}: missing SKU`);
if (!item.quantity || item.quantity < 1) errors.push(`Item ${index}: invalid quantity`);
if (!item.price || item.price < 0) errors.push(`Item ${index}: invalid price`);
});
return {
valid: errors.length === 0,
errors
};
}
generateIdempotencyKey(orderData) {
return `${orderData.source}-${orderData.external_id}-${Date.now()}`;
}
async checkDuplicateOrder(orderData) {
if (!orderData.external_id) return false;
const existing = await this.client.orders.list({
external_id: orderData.external_id,
created_after: new Date(Date.now() - 24 * 60 * 60 * 1000) // Last 24 hours
});
return existing.length > 0;
}
calculatePriority(order) {
// Priority based on shipping method, customer tier, etc.
if (order.shipping_method === 'express') return 'high';
if (order.customer_tier === 'vip') return 'high';
if (order.total_value > 500) return 'medium';
return 'normal';
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage example
const orderManager = new OrderLifecycleManager(process.env.STATESET_API_KEY);
// Process an order
const result = await orderManager.processOrder({
source: 'website',
external_id: 'WEB-12345',
customer_email: 'customer@example.com',
customer_phone: '+1234567890',
sms_opt_in: true,
shipping_method: 'standard',
items: [
{
sku: 'WIDGET-001',
quantity: 2,
price: 29.99,
name: 'Premium Widget'
}
],
shipping_address: {
name: 'John Doe',
street1: '123 Main St',
city: 'San Francisco',
state: 'CA',
zip: '94105',
country: 'US'
},
billing_address: {
// Same as shipping or different
}
});
console.log('Order processed:', result);