Manufacturing and Production API Quickstart Guide
Welcome to the Stateset Manufacturing and Production Quickstart Guide. This document provides developers and technical users with instructions for utilizing the Stateset API to manage core manufacturing processes. Learn how to configure your environment, model production data (Products, BOMs), plan and execute production runs (Work Orders, Manufacturing Orders), and manage associated inventory movements.
Following this guide will enable you to integrate Stateset into your manufacturing workflows, enhancing operational efficiency, data accuracy, and cost control.
Table of Contents
- Introduction
- Prerequisites
- Getting Started: SDK Setup
- Core Manufacturing Concepts
- API Workflow: End-to-End Manufacturing Process
- Optimizing Manufacturing Processes with API Data
- Real-Time Monitoring and System Integration
- Error Handling and Logging Strategies
- Troubleshooting Common Manufacturing API Issues
- Support Resources
- Conclusion
Introduction
Stateset offers a comprehensive API designed to manage the complexities of modern manufacturing operations. This guide focuses on the practical application of the API for common workflows, including defining manufacturable items, planning production schedules, tracking execution progress, and maintaining accurate inventory records. By leveraging the Stateset API, you can automate processes, improve data visibility, and enable data-driven optimization.
Key Learning Objectives:
- Configure the Stateset Node.js SDK for secure API interaction.
- Understand fundamental manufacturing entities within Stateset (Products, BOMs, WOs, MOs, Inventory).
- Utilize API endpoints to create, update, and manage manufacturing data throughout the production lifecycle.
- Identify opportunities to use API data for process optimization (e.g., OEE, waste analysis).
- Implement robust error handling and logging for reliable integration.
Prerequisites
- Node.js (version 16 or higher recommended).
- An active Stateset account and a generated API Key with appropriate permissions for manufacturing resources.
- Basic understanding of Javascript (
async
/await
), REST APIs, and JSON data structures.
- Familiarity with core manufacturing concepts (BOMs, Work Orders, Inventory Management).
Getting Started: SDK Setup
Ensure your development environment is configured to interact with the Stateset API.
1. Install the Stateset Node.js SDK
Add the official SDK package to your Node.js project using npm or yarn.
npm install stateset-node
# or
# yarn add stateset-node
Store your Stateset API key securely, preferably using environment variables. Avoid embedding keys directly in source code.
# Example for bash/zsh shells. Adapt for your environment (e.g., .env file, secrets manager).
export STATESET_API_KEY='your_api_key_here'
3. Initialize the Stateset Client
Instantiate the SDK client in your application code, providing the API key.
import { stateset } from 'stateset-node';
import winston from 'winston';
// Configure structured logging
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'manufacturing.log' }),
new winston.transports.Console()
]
});
// Ensure the environment variable is loaded (e.g., using dotenv)
if (!process.env.STATESET_API_KEY) {
throw new Error("STATESET_API_KEY environment variable is not set.");
}
// Initialize the client
const client = new stateset(process.env.STATESET_API_KEY);
// Optional: Verify API connectivity on startup
async function verifyApiConnection() {
try {
const health = await client.system.healthCheck();
logger.info('StateSet API connection verified', { status: health.status, timestamp: new Date().toISOString() });
if (health.status !== 'OK') {
logger.warn('API health check returned non-OK status', { health });
}
} catch (error) {
logger.error('Failed to connect to StateSet API', { error: error.message, stack: error.stack });
// Consider application-specific error handling (e.g., retry, exit)
}
}
verifyApiConnection();
Core Manufacturing Concepts
Understanding these entities within the Stateset context is crucial for effective API usage.
- Product: Represents a distinct item that can be manufactured or sold (e.g., finished good, subassembly). Identified by attributes like
sku
, name
.
- Bill of Materials (BOM): Defines the “recipe” for manufacturing a specific
Product
. Lists required components
(other Products or raw materials identified by item_id
) and their quantity
and unit
of measure. Often version-controlled.
- Work Order (WO): An authorization to produce a specific
quantity
of a Product
by a certain date. References the relevant BOM
and specifies production details like priority
and target site
. Acts as the high-level production plan.
- Manufacturing Order (MO): Represents the execution of a
Work Order
or a portion of it. Tracks the actual production run, including start/end times, status
(e.g., planned
, in_progress
, completed
), resources used, and materials consumed. It’s the operational record of production. Often linked back to the source Work Order
via work_order_id
.
- Picks: The operational task of retrieving specific
inventory_item_id
s (representing actual stock) from storage locations to fulfill the material requirements of an MO
. Tracks requested vs. picked quantities.
- Inventory (
inventory_item_id
vs item_id
/part_number
):
item_id
or part_number
: Represents the type of item (e.g., ‘RESISTOR-10K’). Corresponds to a Product
or raw material definition.
inventory_item_id
(or similar concept): Represents a specific batch/lot/instance of that item in stock, often with its own location, quantity, and cost layer. Picks operate on these specific inventory units. Inventory movements update the quantity of specific inventory_item_id
s.
- Cycle Counts: Periodic verification of physical inventory quantities against system records (
inventory
resource) to ensure data accuracy.
- Waste & Scrap: Records materials consumed during production that did not become part of the finished good, enabling analysis of production efficiency.
- Machines: Represents production equipment. Tracking usage (
logRuntime
) and Maintenance
is vital for OEE and reliability.
- Kitting: Process of pre-assembling components into a single kit (
item_id
) for easier consumption during final assembly. Managed via BOMs and Inventory.
API Workflow: End-to-End Manufacturing Process
This section demonstrates using the Stateset API to manage a typical manufacturing flow.
Step 1: Defining Products and Bills of Materials (BOMs)
Establish the foundation by defining what you manufacture and how.
A. Create a Product
Define the item to be manufactured.
/**
* Creates a new Product definition in Stateset.
* @param {object} productData - Attributes for the new product.
* @returns {Promise<object>} The newly created Product object.
*/
async function createProduct(productData) {
try {
console.log('Creating product:', productData.sku);
const newProduct = await client.product.create({
name: productData.name,
description: productData.description,
sku: productData.sku,
unit_of_measure: productData.unit_of_measure,
type: productData.type,
category: productData.category,
sub_category: productData.sub_category,
brand: productData.brand,
model_number: productData.model_number,
serial_number: productData.serial_number,
weight: productData.weight,
weight_unit: productData.weight_unit,
dimensions: productData.dimensions,
dimensions_unit: productData.dimensions_unit,
color: productData.color,
material: productData.material
});
console.log(`Product created successfully: ID=${newProduct.id}, SKU=${newProduct.sku}`);
return newProduct;
} catch (error) {
console.error(`Error creating product ${productData.sku}:`, error.response ? error.response.data : error.message);
throw error; // Re-throw for upstream handling
}
}
// Example Usage:
const widgetProductData = {
name: 'Standard Widget',
description: 'A reliable widget for general use.',
sku: 'SW-100',
unit_of_measure: 'pcs',
type: 'manufactured',
category: 'Electronics',
sub_category: 'Widgets',
brand: 'Acme',
model_number: 'SW-100',
serial_number: '1234567890',
};
const product = await createProduct(widgetProductData);
Purpose: Records the master data for the manufacturable item. The sku
or id
will be used to link BOMs and Orders.
B. Create a Bill of Materials (BOM)
Define the components required to make the Product.
/**
* Creates a Bill of Materials (BOM) for a specific Product.
* @param {object} bomData - Attributes for the new BOM.
* @param {string} bomData.productId - ID of the Product this BOM is for.
* @param {Array<object>} bomData.components - Array of { item_id, quantity, unit }.
* @returns {Promise<object>} The newly created BOM object.
*/
async function createBOM(bomData) {
try {
console.log(`Creating BOM for Product ID: ${bomData.productId}`);
const newBOM = await client.billofmaterials.create({
name: bomData.name || `BOM for Product ${bomData.productId}`,
description: bomData.description,
product_id: bomData.productId,
version: bomData.version || '1.0', // Implement versioning as needed
components: bomData.components,
is_active: bomData.is_active || true,
effective_date: bomData.effective_date || new Date().toISOString(),
expiration_date: bomData.expiration_date || null,
is_active: bomData.is_active || true,
// Add other fields: isActive, effective_date, etc.
});
console.log(`BOM created successfully: ID=${newBOM.id}, Name=${newBOM.name}`);
return newBOM;
} catch (error) {
console.error(`Error creating BOM for Product ID ${bomData.productId}:`, error.response ? error.response.data : error.message);
throw error;
}
}
// Example Usage (using 'product' from previous step):
const widgetBOMData = {
productId: product.id,
name: 'Standard Widget BOM v1.0',
description: 'Components for SW-100',
version: '1.0',
components: [
{ item_id: 'COMP-A', quantity: 2, unit: 'pcs' }, // Assumes COMP-A exists as a Product/Item
{ item_id: 'COMP-B', quantity: 50, unit: 'g' } // Assumes COMP-B exists as a Product/Item
]
};
const bom = await createBOM(widgetBOMData);
Purpose: Defines the structure and material requirements for manufacturing. The item_id
for components refers to other Product
SKUs or raw material identifiers.
Step 2: Planning Production (Work Orders)
Authorize and schedule production runs.
A. Create a Work Order
Generate an order to produce a specific quantity of a Product.
/**
* Creates a Work Order to plan the production of a Product.
* @param {object} woData - Attributes for the new Work Order.
* @param {string} woData.bomId - ID of the BOM to use for this production run.
* @param {string} woData.productId - ID of the product being manufactured.
* @param {number} woData.quantity - Target quantity to produce.
* @returns {Promise<object>} The newly created Work Order object.
*/
async function createWorkOrder(woData) {
try {
console.log(`Creating Work Order for Product ID: ${woData.productId}, Qty: ${woData.quantity}`);
const newWorkOrder = await client.workorder.create({
type: woData.type || 'production', // 'production', 'rework', etc.
status: woData.status || 'planned', // 'planned', 'released', 'in_progress', 'completed'
priority: woData.priority || 'medium',
number: woData.number || `WO-${Date.now()}`, // Ensure uniqueness
site: woData.site, // Manufacturing location identifier
product_id: woData.productId, // What is being made
quantity: woData.quantity, // How many
due_date: woData.dueDate, // ISO 8601 string
bill_of_material_id: woData.bomId, // Which recipe to use
is_active: woData.is_active || true,
effective_date: woData.effective_date || new Date().toISOString(),
expiration_date: woData.expiration_date || null,
work_order_line_items: woData.work_order_line_items || []
});
console.log(`Work Order created successfully: ID=${newWorkOrder.id}, Number=${newWorkOrder.number}`);
return newWorkOrder;
} catch (error) {
console.error(`Error creating Work Order for Product ID ${woData.productId}:`, error.response ? error.response.data : error.message);
throw error;
}
}
// Example Usage (using 'product' and 'bom' from previous steps):
const widgetWOData = {
productId: product.id,
bomId: bom.id,
quantity: 500,
dueDate: new Date(Date.now() + 14 * 86400000).toISOString(), // Due in 14 days
site: 'FACTORY-A',
number: 'WO-SW100-500'
};
const workOrder = await createWorkOrder(widgetWOData);
Purpose: Schedules production, reserves capacity (implicitly), and provides the basis for execution tracking via Manufacturing Orders.
Step 3: Executing Production (Manufacturing Orders & Activities)
Track the actual manufacturing process based on the planned Work Order.
A. Create a Manufacturing Order
Initiate the tracking for the actual production run, linking it to the Work Order.
/**
* Creates a Manufacturing Order to track the execution of a Work Order.
* @param {object} moData - Attributes for the new Manufacturing Order.
* @param {string} moData.workOrderId - ID of the parent Work Order.
* @returns {Promise<object>} The newly created Manufacturing Order object.
*/
async function createManufacturingOrder(moData) {
try {
console.log(`Creating Manufacturing Order linked to WO ID: ${moData.workOrderId}`);
// Often MO details are derived from the WO initially
const parentWO = await client.workorder.get(moData.workOrderId); // Fetch WO details if needed
const newMO = await client.manufacturingorder.create({
type: moData.type || parentWO.type || 'production',
status: moData.status || 'released', // Often starts as 'released' or 'in_progress'
priority: moData.priority || parentWO.priority || 'medium',
number: moData.number || `MO-${parentWO.number || Date.now()}`, // Ensure uniqueness
site: moData.site || parentWO.site,
work_order_id: moData.workOrderId,
product_id: parentWO.product_id, // Inherited from WO
quantity_planned: parentWO.quantity, // Inherited from WO
manufacturing_order_line_items: moData.manufacturing_order_line_items || [],
is_active: moData.is_active || true,
effective_date: moData.effective_date || new Date().toISOString(),
expiration_date: moData.expiration_date || null,
actual_start_date: new Date().toISOString(), // Can set start time immediately
});
console.log(`Manufacturing Order created successfully: ID=${newMO.id}, Number=${newMO.number}`);
return newMO;
} catch (error) {
console.error(`Error creating Manufacturing Order for WO ID ${moData.workOrderId}:`, error.response ? error.response.data : error.message);
throw error;
}
}
// Example Usage (using 'workOrder' from previous step):
const widgetMOData = {
workOrderId: workOrder.id,
status: 'released' // Ready to start picking/production
};
const manufacturingOrder = await createManufacturingOrder(widgetMOData);
Purpose: Creates the operational record for tracking progress, resource consumption, and output related to the planned Work Order.
B. Track Production Activities (Machine Time, Waste)
Record resource usage and material losses during the MO execution.
/**
* Logs runtime for a specific machine against a Manufacturing Order.
* @param {string} machineId - The ID of the machine used.
* @param {string} moId - The ID of the Manufacturing Order.
* @param {object} runtimeData - { start_time, end_time, cycle_count (optional) }.
* @returns {Promise<object>} The created machine runtime log entry.
*/
async function logMachineRuntime(machineId, moId, runtimeData) {
try {
console.log(`Logging runtime for Machine ID: ${machineId} on MO ID: ${moId}`);
// Assuming a dedicated endpoint exists like 'client.machines.logRuntime' or similar
// This is a hypothetical structure; adapt to the actual SDK method.
const runtimeLog = await client.machines.logRuntime(machineId, { // Adapt SDK call
manufacturing_order_id: moId,
start_time: runtimeData.start_time, // ISO 8601 string
end_time: runtimeData.end_time, // ISO 8601 string
is_active: runtimeData.is_active || true,
});
console.log(`Machine runtime logged successfully: Log ID=${runtimeLog.id}`);
return runtimeLog;
} catch (error) {
console.error(`Error logging runtime for Machine ${machineId} on MO ${moId}:`, error.response ? error.response.data : error.message);
throw error;
}
}
/**
* Records waste or scrap generated during a Manufacturing Order.
* @param {string} moId - The ID of the Manufacturing Order.
* @param {object} wasteData - { item_id, quantity, unit, reason, type ('waste'/'scrap') }.
* @returns {Promise<object>} The created waste/scrap record.
*/
async function recordWaste(moId, wasteData) {
try {
console.log(`Recording ${wasteData.type} for MO ID: ${moId}, Item: ${wasteData.item_id}, Qty: ${wasteData.quantity}`);
// Assuming an endpoint like 'client.wasteAndScrap.create' exists
const wasteRecord = await client.wasteAndScrap.create({ // Adapt SDK call
manufacturing_order_id: moId,
item_id: wasteData.item_id, // The component part number that was wasted
quantity: wasteData.quantity,
unit: wasteData.unit,
type: wasteData.type, // 'waste', 'scrap'
reason: wasteData.reason,
// Optional fields: recorded_by, timestamp, location_id
});
console.log(`${wasteData.type} recorded successfully: Record ID=${wasteRecord.id}`);
// IMPORTANT: This record typically needs a corresponding INVENTORY adjustment
// to decrease the quantity of the wasted item_id.
// await adjustInventoryForWaste(wasteData.item_id, wasteData.quantity, wasteData.unit, moId);
return wasteRecord;
} catch (error) {
console.error(`Error recording ${wasteData.type} for MO ID ${moId}:`, error.response ? error.response.data : error.message);
throw error;
}
}
// Example Usage:
const startTime = new Date();
const endTime = new Date(startTime.getTime() + 2 * 3600000); // 2 hours later
await logMachineRuntime('MACHINE-01', manufacturingOrder.id, {
start_time: startTime.toISOString(),
end_time: endTime.toISOString()
});
await recordWaste(manufacturingOrder.id, {
item_id: 'COMP-A', // Wasted component
quantity: 5,
unit: 'pcs',
type: 'scrap',
reason: 'Damaged during handling'
});
Purpose: Captures critical operational data for performance analysis (OEE, yield) and cost tracking. Note the importance of linking waste records to actual inventory adjustments.
C. Complete the Manufacturing Order
Mark the production run as finished and record the output quantity.
/**
* Updates the status of a Manufacturing Order, typically to 'completed'.
* Records the actual quantity produced.
* @param {string} moId - The ID of the Manufacturing Order to complete.
* @param {number} quantityCompleted - The actual quantity of the Product produced.
* @returns {Promise<object>} The updated Manufacturing Order object.
*/
async function completeManufacturingOrder(moId, quantityCompleted) {
try {
console.log(`Completing Manufacturing Order ID: ${moId}, Qty Produced: ${quantityCompleted}`);
const updatedMO = await client.manufacturingorder.update(moId, {
status: 'completed',
actual_end_date: new Date().toISOString(),
quantity_completed: quantityCompleted,
// Potentially update line items with actual consumption if tracked there
});
console.log(`Manufacturing Order ${moId} completed successfully.`);
// IMPORTANT: MO completion usually triggers Finished Goods Inventory receipt.
// await receiveFinishedGoods(updatedMO.product_id, quantityCompleted, moId);
return updatedMO;
} catch (error) {
console.error(`Error completing Manufacturing Order ${moId}:`, error.response ? error.response.data : error.message);
throw error;
}
}
// Example Usage:
const actualQuantityProduced = 495; // Perhaps 5 units were scrapped
const completedMO = await completeManufacturingOrder(manufacturingOrder.id, actualQuantityProduced);
Purpose: Finalizes the production run record, capturing the yield. This event typically triggers the creation of Finished Goods inventory.
Step 4: Managing Inventory Movements (Picks, Consumption, Receipts)
Accurately track materials moving into, through, and out of production.
A. Create and Complete Picks
Manage the process of retrieving components from inventory for the MO.
/**
* Creates a Pick task for required inventory items for an MO.
* @param {string} moId - The Manufacturing Order needing materials.
* @param {Array<object>} itemsToPick - Array of { inventory_item_id, quantity, unit }.
* @returns {Promise<object>} The created Pick task object.
*/
async function createPickTask(moId, itemsToPick) {
try {
console.log(`Creating Pick Task for MO ID: ${moId}`);
// Assumes an endpoint like 'client.picks.create' exists
const newPick = await client.picks.create({ // Adapt SDK call
manufacturing_order_id: moId,
status: 'pending', // 'pending', 'in_progress', 'completed', 'cancelled'
// Optional: picker_id, requested_date, priority
items: itemsToPick.map(item => ({
inventory_item_id: item.inventory_item_id, // Specific stock ID
quantity_requested: item.quantity,
unit: item.unit
}))
});
console.log(`Pick Task created successfully: ID=${newPick.id}`);
return newPick;
} catch (error) {
console.error(`Error creating Pick Task for MO ID ${moId}:`, error.response ? error.response.data : error.message);
throw error;
}
}
/**
* Marks a Pick task as completed, recording actual picked quantities.
* @param {string} pickId - The ID of the Pick task to complete.
* @param {Array<object>} itemsPicked - Array of { inventory_item_id, quantity_picked }.
* @returns {Promise<object>} The updated Pick task object.
*/
async function completePickTask(pickId, itemsPicked) {
try {
console.log(`Completing Pick Task ID: ${pickId}`);
// Assumes an endpoint like 'client.picks.complete' or 'client.picks.update' exists
const completedPick = await client.picks.update(pickId, { // Adapt SDK call
status: 'completed',
completed_date: new Date().toISOString(),
// Update line items with actual picked quantities
items: itemsPicked.map(item => ({
inventory_item_id: item.inventory_item_id,
quantity_picked: item.quantity_picked
}))
});
console.log(`Pick Task ${pickId} completed successfully.`);
for (const item of itemsPicked) {
await adjustInventoryForPick(item.inventory_item_id, item.quantity_picked);
}
return completedPick;
} catch (error) {
console.error(`Error completing Pick Task ${pickId}:`, error.response ? error.response.data : error.message);
throw error;
}
}
// Example Usage:
const pickItems = [
{ inventory_item_id: 'INV-CA-001', quantity: 2, unit: 'pcs' },
{ inventory_item_id: 'INV-CB-005', quantity: 50, unit: 'g' }
];
const pickTask = await createPickTask(manufacturingOrder.id, pickItems);
// Later, when picked: Assume only 48g of COMP-B was available/picked
const pickedQuantities = [
{ inventory_item_id: 'INV-CA-001', quantity_picked: 2 },
{ inventory_item_id: 'INV-CB-005', quantity_picked: 48 }
];
const completedPick = await completePickTask(pickTask.id, pickedQuantities);
Purpose: Manages the physical movement of materials from storage to the production floor. Crucially links to inventory deduction. The distinction between inventory_item_id
(specific stock) and item_id
(general part number) is vital here.
B. Record Finished Goods Receipt (Implied/Separate Step)
Increase inventory for the product manufactured upon MO completion. This might be an automatic side-effect of completeManufacturingOrder
or require a separate inventory transaction.
/**
* Creates an inventory movement record for receiving finished goods.
* Often triggered after MO completion.
* @param {string} productId - The Product ID (SKU) of the item produced.
* @param {number} quantity - The quantity produced and received into stock.
* @param {string} sourceMoId - The Manufacturing Order ID that produced these goods.
* @returns {Promise<object>} The created inventory movement record.
*/
async function receiveFinishedGoods(productId, quantity, sourceMoId) {
try {
console.log(`Receiving Finished Goods: Product ID=${productId}, Qty=${quantity} from MO ID=${sourceMoId}`);
// Assumes a general inventory adjustment/movement endpoint
const inventoryReceipt = await client.inventory.create({ // Adapt SDK call
part_number: productId, // Or item_id
quantity: quantity, // Positive quantity for receipt
type: 'FG_RECEIPT', // Categorize the movement type
source_document_id: sourceMoId,
source_document_type: 'ManufacturingOrder',
// Important: Determine the UNIT COST of these finished goods
// This involves summing material, labor, overhead costs from the MO.
// unit_cost: calculateFinishedGoodsCost(sourceMoId), // Requires separate calculation
location_id: 'FG-WAREHOUSE', // Target location
// Add lot/batch number if applicable
});
console.log(`Finished Goods received into inventory: Movement ID=${inventoryReceipt.id}`);
return inventoryReceipt;
} catch (error) {
console.error(`Error receiving Finished Goods for Product ${productId} from MO ${sourceMoId}:`, error.response ? error.response.data : error.message);
throw error;
}
}
// Example Usage (called after completeManufacturingOrder):
await receiveFinishedGoods(completedMO.product_id, completedMO.quantity_completed, completedMO.id);
Purpose: Updates inventory levels to reflect newly manufactured stock, making it available for sale or further processes. Calculating the correct unit_cost
for these finished goods is a critical accounting step often involving cost roll-ups from the MO.
Step 5: Maintaining Inventory Accuracy (Cycle Counts)
Ensure inventory data remains accurate through regular checks.
A. Schedule and Record Cycle Counts
Initiate and record the results of periodic inventory counts.
/**
* Schedules a Cycle Count task for specific items or locations.
* @param {object} countData - { scheduled_date, location_id, item_ids (optional) }.
* @returns {Promise<object>} The created Cycle Count task object.
*/
async function scheduleCycleCount(countData) {
try {
console.log(`Scheduling Cycle Count for Location: ${countData.location_id}`);
// Assumes an endpoint like 'client.cycleCounts.create' exists
const newCycleCount = await client.cycleCounts.create({ // Adapt SDK call
scheduled_date: countData.scheduled_date, // ISO 8601 string
location_id: countData.location_id, // ID of the warehouse area being counted
status: 'scheduled', // 'scheduled', 'in_progress', 'completed'
// Optionally specify items or count everything in the location
items_to_count: countData.item_ids || [], // Array of part_numbers/item_ids
});
console.log(`Cycle Count scheduled successfully: ID=${newCycleCount.id}`);
return newCycleCount;
} catch (error) {
console.error(`Error scheduling Cycle Count for Location ${countData.location_id}:`, error.response ? error.response.data : error.message);
throw error;
}
}
/**
* Records the results of a completed Cycle Count, highlighting discrepancies.
* @param {string} countId - The ID of the Cycle Count task.
* @param {Array<object>} countResults - Array of { inventory_item_id, counted_quantity }.
* @returns {Promise<object>} The updated Cycle Count task object.
*/
async function recordCycleCountResults(countId, countResults) {
try {
console.log(`Recording results for Cycle Count ID: ${countId}`);
// Assumes an endpoint like 'client.cycleCounts.complete' or 'update'
const completedCount = await client.cycleCounts.update(countId, { // Adapt SDK call
status: 'completed',
completion_date: new Date().toISOString(),
results: countResults.map(result => ({
inventory_item_id: result.inventory_item_id, // Specific stock ID counted
counted_quantity: result.counted_quantity,
// The system should calculate discrepancy based on current inventory qty
system_quantity: fetchSystemQuantity(result.inventory_item_id),
discrepancy: counted_quantity - system_quantity
}))
});
console.log(`Cycle Count ${countId} results recorded.`);
// IMPORTANT: Discrepancies found during cycle counts require investigation
// and subsequent INVENTORY ADJUSTMENTS to correct system quantities.
processCycleCountDiscrepancies(completedCount.results);
return completedCount;
} catch (error) {
console.error(`Error recording results for Cycle Count ${countId}:`, error.response ? error.response.data : error.message);
throw error;
}
}
// Example Usage:
const tomorrow = new Date(Date.now() + 86400000);
const countTask = await scheduleCycleCount({
scheduled_date: tomorrow.toISOString(),
location_id: 'BIN-A1',
item_ids: ['COMP-A', 'COMP-B'] // Count only these items in this location
});
// After counting:
const results = [
{ inventory_item_id: 'INV-CA-001', counted_quantity: 98 }, // System thought 100?
{ inventory_item_id: 'INV-CB-005', counted_quantity: 50 } // Matches system?
];
await recordCycleCountResults(countTask.id, results);
Purpose: Provides a mechanism for systematic inventory verification, identifying discrepancies that require adjustments to maintain accurate stock levels crucial for planning and fulfillment.
Optimizing Manufacturing Processes with API Data
The data captured via the Stateset API is invaluable for process improvement. Analyze this data to:
- Calculate Overall Equipment Effectiveness (OEE): Use machine runtime logs (
logMachineRuntime
), MO planned vs. actual times, and MO quantity_completed
vs. quantity_planned
(considering scrap) to measure Availability, Performance, and Quality.
- Availability = Actual Runtime / Planned Production Time
- Performance = (Ideal Cycle Time * Total Pieces Produced) / Actual Runtime
- Quality = Good Pieces (Completed Qty - Scrap Qty) / Total Pieces Produced
- OEE = Availability * Performance * Quality
- Analyze Waste and Scrap: Aggregate
wasteAndScrap
records by reason code, item, machine, or operator to identify root causes of material loss and target areas for process improvement or training.
- Optimize Production Scheduling: Analyze historical MO completion times, lead times derived from WO/MO dates, and resource (machine/labor) utilization data to improve future scheduling accuracy and resource allocation.
- Implement Predictive Maintenance: Analyze machine runtime hours, cycle counts, and potentially sensor data (if integrated) to predict maintenance needs before failures occur, reducing unplanned downtime.
- Refine BOM Accuracy: Compare actual component consumption (derived from completed Picks and adjusted for scrap) against theoretical BOM quantities to identify inaccuracies in the Bill of Materials.
// Conceptual OEE Calculation Snippet (Requires fetching related data)
async function calculateOEEForMO(moId) {
try {
const mo = await client.manufacturingorder.get(moId);
if (!mo || mo.status !== 'completed') {
throw new Error(`Manufacturing Order ${moId} not found or not completed.`);
}
// --- Data Gathering (Requires specific queries/logic) ---
// 1. Planned Time: Derive from MO schedule, associated WO, or standard process times.
const plannedProductionTimeHrs = 8; // Placeholder
// 2. Actual Runtime: Sum machine runtime logs linked to this MO.
const machineLogs = await client.machines.listLogs({ manufacturing_order_id: moId });
const actualRuntimeHrs = machineLogs.reduce((sum, log) => sum + calculateDurationHrs(log.start_time, log.end_time), 0);
const actualRuntimeHrs = 7.5; // Placeholder
// 3. Ideal Cycle Time: From Product/Process definition (units per hour).
const idealCycleTimeHrsPerUnit = 0.01; // Placeholder (e.g., 100 units/hr = 0.01 hrs/unit)
// 4. Total Pieces Produced: Sum of good pieces and scrap for this MO.
const scrapRecords = await client.wasteAndScrap.list({ manufacturing_order_id: moId });
const totalScrapQty = scrapRecords.reduce((sum, record) => sum + record.quantity, 0);
const totalScrapQty = 5; // Placeholder
const goodPieces = mo.quantity_completed;
const totalPieces = goodPieces + totalScrapQty;
if (plannedProductionTimeHrs <= 0 || actualRuntimeHrs <= 0 || totalPieces <= 0) {
console.warn(`Cannot calculate OEE for MO ${moId} due to zero values in time or quantity.`);
return null;
}
// --- Calculation ---
const availability = actualRuntimeHrs / plannedProductionTimeHrs;
const performance = (idealCycleTimeHrsPerUnit * totalPieces) / actualRuntimeHrs;
const quality = goodPieces / totalPieces;
const oee = availability * performance * quality;
console.log(`OEE Calculation for MO ${moId}:`);
console.log(` Availability: ${(availability * 100).toFixed(1)}%`);
console.log(` Performance: ${(performance * 100).toFixed(1)}%`);
console.log(` Quality: ${(quality * 100).toFixed(1)}%`);
console.log(` OEE: ${(oee * 100).toFixed(1)}%`);
return { availability, performance, quality, oee };
} catch (error) {
console.error(`Error calculating OEE for MO ${moId}:`, error.message);
return null;
}
}
// Placeholder helper function
function calculateDurationHrs(startISO, endISO) {
return (new Date(endISO) - new Date(startISO)) / (1000 * 60 * 60);
}
// Example Usage:
await calculateOEEForMO(completedMO.id);
Note: The OEE calculation requires careful data gathering from multiple related records. The example provides the structure but relies on placeholders for data fetching logic.
Real-Time Monitoring and System Integration
Leverage real-time data flow for operational visibility and system synchronization.
- Webhooks: Configure Stateset Webhooks to receive real-time notifications for critical manufacturing events. Examples:
manufacturingorder.completed
: Trigger downstream processes like FG inventory receipt, shipping notifications, or ERP updates.
inventory.quantity.low
: Alert purchasing or planning when component stock drops below a threshold.
pick.completed
: Update shop floor dashboards or trigger material movement confirmations.
machine.status.changed
: Monitor equipment state changes for immediate visibility.
- Dashboarding: Feed API data (MO status, queue lengths, OEE metrics, waste levels) into Business Intelligence (BI) tools or custom dashboards for real-time operational monitoring by supervisors and managers.
- ERP/MES Integration: Synchronize Stateset data (inventory levels, WO/MO status, costs) with your primary ERP or Manufacturing Execution System (MES) to maintain data consistency across platforms. Use the API for bi-directional updates where appropriate.
Error Handling and Logging Strategies
Implement robust error handling and logging for reliable manufacturing system integration.
- Specific API Error Handling: Catch errors from SDK calls. Inspect
error.response.status
(HTTP status code) and error.response.data
(API error details) to determine the cause (e.g., 400 Bad Request, 401 Unauthorized, 404 Not Found, 429 Rate Limited, 5xx Server Error).
- Input Validation: Validate data before making API calls to prevent unnecessary errors (check required fields, data types, formats).
- Retry Logic: Implement exponential backoff strategies for transient errors (e.g., 429, 503). Do not retry client errors (4xx) without correcting the request.
- Idempotency: For critical operations like creating orders or inventory movements, utilize idempotency keys if supported by the API, or implement application-level checks to prevent duplicate transactions.
- Centralized Logging: Log detailed information for both successful operations and errors (timestamp, operation name, input identifiers, outcome, error message, stack trace, API response details) to a dedicated logging service (e.g., Datadog, Splunk, ELK Stack) for monitoring, alerting, and debugging.
- Transactionality (Application Level): For multi-step workflows (e.g., completing MO -> receiving FG inventory), consider application-level patterns (like sagas or compensating transactions) to handle failures gracefully if the API doesn’t support atomic transactions across multiple calls.
// Enhanced Error Handling Wrapper Example (from previous guide, still applicable)
async function safeApiOperation(operation, description) {
try {
console.log(`Attempting operation: ${description}`);
const result = await operation();
// Log success with key identifiers if needed
// externalLogger.info(`Operation successful: ${description}`, { resultId: result?.id });
return result;
} catch (error) {
const timestamp = new Date().toISOString();
let errorDetails = {
message: error.message,
stack: error.stack?.substring(0, 1000), // Limit stack trace length
description: description,
timestamp: timestamp
};
if (error.response) {
errorDetails.apiStatus = error.response.status;
errorDetails.apiResponse = error.response.data;
} else {
errorDetails.type = 'Application Error';
}
console.error(`Error during operation: ${description}`, errorDetails);
// Log structured error to external system
externalLogger.error('Manufacturing Process Error', errorDetails);
// Optional: Notify team on critical errors
if (errorDetails.apiStatus >= 500 || !errorDetails.apiStatus) {
notifyTeam(`Critical error in ${description}: ${error.message}`);
}
// Re-throw for workflow control or return specific error indicator
throw new Error(`Operation failed: ${description}. Status: ${errorDetails.apiStatus || 'N/A'}. Message: ${errorDetails.message}`);
}
}
// Usage Example:
try {
const product = await safeApiOperation(
() => createProduct({ name: '...', sku: '...' }),
'Create Product SW-101'
);
// ... other operations
} catch (workflowError) {
console.error("Manufacturing workflow step failed:", workflowError.message);
// Implement workflow recovery or termination logic
}
Troubleshooting Common Manufacturing API Issues
Address frequent problems encountered when integrating manufacturing workflows.
- Authentication Errors (401/403):
- Solution: Verify API key validity, environment variable loading, and key permissions for manufacturing-related resources (
product
, workorder
, inventory
, etc.).
- Not Found Errors (404):
- Solution: Double-check IDs used in requests (
productId
, bomId
, woId
, moId
, inventory_item_id
). Ensure the referenced resource exists and hasn’t been deleted. Check for typos.
- Validation Errors (400):
- Solution: Examine
error.response.data
for details. Confirm required fields are present, data types match API expectations (number vs. string), dates are in ISO 8601 format, and enum values (status
, type
) are valid. Check quantity/unit consistency.
- Inventory Discrepancies:
- Solution: Audit the workflow logic: Are picks correctly deducting inventory? Is waste recording linked to an inventory adjustment? Are FG receipts correctly adding inventory? Use Cycle Counts to identify and investigate differences. Ensure correct
inventory_item_id
s are used.
- Incorrect Production Costs:
- Solution: Verify component costs used in calculations. Ensure labor and overhead tracking/allocation logic is correct. Check BOM accuracy. Confirm waste quantities are factored in appropriately.
- Race Conditions/Concurrency Issues:
- Solution: If multiple processes might update the same resource (e.g., inventory quantity), use optimistic locking mechanisms (if supported by API via ETags/versions) or structure workflows to minimize concurrent updates on the same item. Ensure idempotency for creation events.
Support Resources
Consult these resources for additional help and information:
Conclusion
This quickstart guide has provided a comprehensive walkthrough of managing manufacturing and production processes using the Stateset API. You have learned how to set up your environment, define products and BOMs, plan and execute production via Work Orders and Manufacturing Orders, track crucial activities like picks and machine usage, manage inventory movements, and leverage the captured data for optimization.
By implementing these workflows and adhering to best practices for error handling and monitoring, you can build robust, efficient, and data-driven manufacturing operations powered by Stateset. Remember to adapt the specific costing, inventory adjustment, and optimization logic to your company’s unique requirements and accounting practices. Utilize the available support resources for any further assistance needed.