StateSet Wholesale Order Management: Your Comprehensive Guide
This guide provides everything you need to start using StateSet’s powerful Wholesale Order Management API. Learn how to streamline your B2B order processing, from capturing email orders to integrating with SYSPRO ERP.
Table of Contents
- Introduction
- Why Use StateSet for Wholesale Order Management?
- Understanding the Challenges
- StateSet’s Solution
- Quickstart: Getting Started
- Core API Components
- Integrating with SYSPRO ERP
- Order Processing Workflow
- Error Handling and Notifications
- Support
1. Introduction
StateSet’s Wholesale Order Management API is a robust solution designed for businesses managing large-volume B2B orders. Our API automates critical processes, from extracting order data from emails to seamlessly integrating with ERP systems like SYSPRO. This guide will walk you through the key features and how to implement them.
2. Why Use StateSet for Wholesale Order Management?
StateSet provides a purpose-built solution to simplify and automate the complexities of wholesale ordering. By leveraging our platform, you can:
- Save Time: Automate tasks previously done manually.
- Reduce Errors: Eliminate manual data entry, reducing errors and improving data accuracy.
- Streamline Operations: Improve order flow from capture to fulfillment.
- Improve Efficiency: Manage wholesale orders efficiently and at scale.
- Integrate Seamlessly: Connect with critical ERP systems like SYSPRO.
3. Understanding the Challenges
Wholesale order management presents unique challenges compared to B2C sales. Some key hurdles include:
- Complex Pricing: Handling tiered pricing, bulk discounts, and customer-specific rates.
- Large Order Volumes: Managing orders with many line items and high quantities.
- Custom Terms: Accommodating varying customer payment, shipping, and other terms.
- ERP Integration: Ensuring smooth data exchange with your core ERP system.
- Inventory Management: Synchronizing inventory levels across wholesale and retail channels.
- Approval Workflows: Supporting multi-stage approvals before fulfilling orders.
4. StateSet’s Solution
StateSet’s Wholesale Order Management API addresses these challenges with powerful features:
- Automated Email Order Capture: Extracts order details from emails in various formats.
- Flexible Pricing: Handles complex pricing models including tiered and customer-specific rates.
- Bulk Order Processing: Efficiently processes orders with large numbers of items.
- Customizable Workflows: Allows custom approval processes and business rules.
- ERP Integration: Creates standardized XML output for easy integration with SYSPRO and similar systems.
- Inventory Synchronization: Offers real-time inventory updates across all channels.
5. Quickstart: Getting Started
Ready to begin? Follow these steps to set up and configure your StateSet integration.
Set Up Your Environment
Ensure you have Node.js installed. Install the required npm package:
npm install stateset-node
Clone Sample Project
Clone our sample project from GitHub. (Note: Replace with the correct repository link). This provides a working example to get you started quickly.
Configure Environment Variables
Create a .env
file in the project’s root directory and add your API keys and configurations:
STATESET_API_KEY=your_stateset_api_key
SENDGRID_API_KEY=your_sendgrid_api_key
SYSPRO_BASE_URL=your_syspro_base_url
SYSPRO_OPERATOR=your_syspro_operator
SYSPRO_COMPANY=your_syspro_company
SYSPRO_PASSWORD=your_syspro_password
6. Core API Components
Here’s an overview of the core API components you’ll use.
6.1 GraphQL API
StateSet utilizes a GraphQL API for interacting with wholesale orders and line items. Here are examples of the queries and mutations you will use:
6.1.1 Fetching a Wholesale Order
Use the GET_MY_WHOLESALE_ORDER
query to retrieve a wholesale order and its associated line items:
const GET_MY_WHOLESALE_ORDER = gql`
query getMyWholesaleOrders($id: uuid!) {
wholesale_orders(where: { id: { _eq: $id } }) {
id
order_number
customer_name
customer_number
delivery_date
created_date
}
wholesale_order_line_items(
where: {
wholesale_order_id: { _eq: $id }
_and: { include_in_export: { _eq: true } }
}
) {
id
product_id
product_name
quantity
unit
price_unit
product_class
}
}
`;
6.1.2 Updating a Wholesale Order
After processing, update the wholesale order’s status using the UPDATE_WHOLESALE_ORDER
mutation:
const UPDATE_WHOLESALE_ORDER = gql`
mutation updateWholesaleOrder(
$id: uuid!
$imported_status: String!
$imported_date: timestamptz!
) {
update_wholesale_orders(
where: { id: { _eq: $id } }
_set: { imported_status: $imported_status, imported_date: $imported_date }
) {
affected_rows
}
}
`;
6.2 SYSPRO API Helper
The following helper function is used to interact with the SYSPRO API:
async function callSysproApi({ method = 'GET', endpoint, sessionId = '', queryParams = '', xmlIn = '', headers = {}, body = null }) {
const baseUrl = process.env.SYSPRO_BASE_URL;
let url = `${baseUrl}${endpoint}`;
// Add 'UserId' parameter if 'sessionId' is provided
const params = [];
if (sessionId) {
params.push(`UserId=${sessionId}`);
}
if (queryParams) {
params.push(queryParams);
}
if (params.length > 0) {
url += `?${params.join('&')}`;
}
const response = await fetch(url, {
method,
headers: {
'Content-Type': 'application/xml',
'User-Agent': 'StateSet SYSPRO Client',
...headers,
},
body,
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.text();
}
7. Integrating with SYSPRO ERP
StateSet’s integration with SYSPRO allows for automated order processing and inventory management.
7.1 Logging into SYSPRO
Start by obtaining a session ID by logging into SYSPRO:
async function logonToSyspro() {
const url = `/Rest/Logon`;
const queryParams = `Operator=${process.env.SYSPRO_OPERATOR}&Company=${process.env.SYSPRO_COMPANY}&OperatorPassword=${encodeURIComponent(process.env.SYSPRO_PASSWORD)}&LanguageCode=ENG&LogLevel=2`;
const sessionId = await callSysproApi({ endpoint: url, queryParams });
return sessionId;
}
7.2 Checking Customer Status
Check if a customer is on hold within SYSPRO before placing an order:
async function checkCustomer(sessionId, customerNumber) {
const customerQueryXml = `
<?xml version="1.0" encoding="Windows-1252"?>
<Query xmlns:xsd="http://www.w3.org/2000/10/XMLSchema-instance" xsd:noNamespaceSchemaLocation="ARSQRY.XSD">
<Key>
<Customer><![CDATA[${customerNumber}]]></Customer>
</Key>
<Option>
<XslStylesheet/>
</Option>
</Query>`;
const endpoint = `/Rest/Query/Query`;
const queryParams = `&BusinessObject=ARSQRY&XmlIn=${encodeURIComponent(customerQueryXml)}`;
const data = await callSysproApi({ endpoint, sessionId, queryParams });
// Parse the XML response to check if the customer is on hold
}
7.3 Checking Inventory Status
Check if inventory items are on hold before fulfilling an order:
async function checkInventory(sessionId, stockCode) {
const inventoryQueryXml = `
<?xml version="1.0" encoding="Windows-1252"?>
<Query xmlns:xsd="http://www.w3.org/2000/10/XMLSchema-instance" xsd:noNamespaceSchemaLocation="INVQRY.XSD">
<Key>
<StockCode><![CDATA[${stockCode}]]></StockCode>
</Key>
<Option>
<XslStylesheet/>
</Option>
</Query>`;
const endpoint = `/Rest/Query/Query`;
const queryParams = `&BusinessObject=INVQRY&XmlIn=${encodeURIComponent(inventoryQueryXml)}`;
const data = await callSysproApi({ endpoint, sessionId, queryParams });
// Parse the XML response to check if the stock item is on hold
}
7.4 Sending Orders to SYSPRO
Send the transformed XML order data to SYSPRO to create a sales order:
async function sendToSyspro(sessionId, xmlIn) {
const xmlParameters = `<SalesOrders xsd:noNamespaceSchemaLocation="SORTOI.XSD" xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance">
<Parameters>
<InBoxMsgReqd>Y</InBoxMsgReqd>
<Process>IMPORT</Process>
<WarehouseListToUse>A1</WarehouseListToUse>
<AcceptEarlierShipDate>Y</AcceptEarlierShipDate>
<TypeOfOrder>PR</TypeOfOrder>
<AddAttachedServiceCharges>Y</AddAttachedServiceCharges>
<OrderStatus>1</OrderStatus>
<IgnoreWarnings>Y</IgnoreWarnings>
</Parameters>
</SalesOrders>`;
const endpoint = `/Rest/Transaction/Post`;
const queryParams = `&BusinessObject=SORTOI&XmlParameters=${encodeURIComponent(
xmlParameters
)}&XmlIn=${encodeURIComponent(xmlIn)}`;
const data = await callSysproApi({ endpoint, sessionId, queryParams });
return data;
}
8. Order Processing Workflow
This section details the flow of order processing within the StateSet system.
8.1 Fetching and Initial Processing
First, retrieve the wholesale order and validate its contents:
async function processOrder(order_id, customer_number, graphQLClient) {
const data = await graphQLClient.request(GET_MY_WHOLESALE_ORDER, { id: order_id });
if (data.wholesale_order_line_items.length === 0) {
return { skipped: true };
}
const sessionId = await logonToSyspro();
const customerCheck = await checkCustomer(sessionId, customer_number);
const lineItemsWithInventory = await Promise.all(
data.wholesale_order_line_items.map(async (item) => {
const onHold = await checkInventory(sessionId, item.product_id);
return { ...item, onHold };
})
);
return { lineItemsWithInventory };
}
The following functions allow you to prepare your order data for SYSPRO. First the items are split into produce and non-produce, and then transformed into the correct XML format
function splitOrder(lineItems) {
return lineItems.reduce(
(acc, lineItem) => {
if (lineItem.product_class === 'PRO') {
acc.produceLines.push(lineItem);
} else {
acc.otherLines.push(lineItem);
}
return acc;
},
{ produceLines: [], otherLines: [] }
);
}
function transformToXML(order, lineItems, version) {
const formatDate = (dateString) =>
dateString ? new Date(dateString).toISOString().split('T')[0] : '';
const formatTime = () => new Date().toTimeString().split(' ')[0].substring(0, 5);
const formatNumber = (value) => {
const num = Number(value);
return isNaN(num) ? '0.00' : num.toFixed(2);
};
const createXmlObj = (lines, suffix = '') => ({
TransmissionHeader: {
TransmissionReference: `${order.order_number}${suffix}-${version}` || '',
ReceiverCode: 'HO',
DatePrepared: formatDate(new Date().toISOString()),
TimePrepared: formatTime(),
},
Orders: {
OrderHeader: {
CustomerPoNumber: `${order.order_number}${suffix}-${version}` || '',
OrderActionType: 'A',
Customer: order.customer_number || '',
OrderDate: formatDate(order.created_date),
OrderType: 'DE',
RequestedShipDate: formatDate(order.delivery_date),
},
OrderDetails: {
StockLine: lines.map((item, index) => ({
CustomerPoLine: (index + 1).toString(),
LineActionType: 'A',
StockCode: item.product_id || '',
OrderQty: formatNumber(item.quantity),
OrderUom: item.unit,
PriceUom: item.price_unit,
})),
},
},
});
return {
otherLineXmlObj: createXmlObj(lineItems.filter(item => item.product_class !== 'PRO')),
produceLineXmlObj: createXmlObj(lineItems.filter(item => item.product_class === 'PRO'), '-PR'),
};
}
8.3 Updating Order Status
Finally, update the order status in StateSet after the order has been processed in SYSPRO:
const updatedWholesaleOrder = await graphQLClient.request(UPDATE_WHOLESALE_ORDER, {
id: order_id,
imported_status: 'IMPORTED',
imported_date: new Date().toISOString(),
});
9. Error Handling and Notifications
9.1 Error Handling
StateSet incorporates robust error handling to manage potential issues:
- Detailed Logging: Errors are logged with comprehensive information for debugging.
- Exception Management:
try-catch
blocks are used throughout the code.
9.2 Email Notifications
Email notifications can be sent using SendGrid, providing updates on the status of orders and any encountered issues:
async function sendEmail(emailContent) {
try {
await sgMail.send(emailContent);
console.log('Email sent successfully');
} catch (error) {
console.error('Error sending email:', error);
throw error;
}
}
Prepare the email content with attachments of the generated XMLs and SYSPRO responses, including any held items or warnings.
10. Support
Need further assistance? Contact StateSet support at support@stateset.com for any questions or implementation help.