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

  1. Introduction
  2. Why Use StateSet for Wholesale Order Management?
  3. Understanding the Challenges
  4. StateSet’s Solution
  5. Quickstart: Getting Started
  6. Core API Components
  7. Integrating with SYSPRO ERP
  8. Order Processing Workflow
  9. Error Handling and Notifications
  10. 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.

1

Sign Up

Create your company’s StateSet instance by signing up at StateSet.io/signup.

2

Generate API Key

Create a new API Key in the StateSet Cloud Console at cloud.stateset.com/api-keys.

3

Set Up Your Environment

Ensure you have Node.js installed. Install the required npm package:

npm install stateset-node
4

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.

5

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 };
}

8.2 Splitting and Transforming Orders to XML

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.