> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stateset.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Wholesale Quickstart

> Get started with StateSet's wholesale order management solution. Automate B2B order processing and integrate with SYSPRO ERP.

# 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](#1-introduction)
2. [Why Use StateSet for Wholesale Order Management?](#2-why-use-StateSet-for-wholesale-order-management)
3. [Understanding the Challenges](#3-understanding-the-challenges)
4. [StateSet's Solution](#4-StateSets-solution)
5. [Quickstart: Getting Started](#5-quickstart-getting-started)
6. [Core API Components](#6-core-api-components)
   * [6.1 GraphQL API](#61-graphql-api)
   * [6.2 SYSPRO API Helper](#62-syspro-api-helper)
7. [Integrating with SYSPRO ERP](#7-integrating-with-syspro-erp)
   * [7.1 Logging into SYSPRO](#71-logging-into-syspro)
   * [7.2 Checking Customer Status](#72-checking-customer-status)
   * [7.3 Checking Inventory Status](#73-checking-inventory-status)
   * [7.4 Sending Orders to SYSPRO](#74-sending-orders-to-syspro)
8. [Order Processing Workflow](#8-order-processing-workflow)
   * [8.1 Fetching and Initial Processing](#81-fetching-and-initial-processing)
   * [8.2 Splitting and Transforming Orders to XML](#82-splitting-and-transforming-orders-to-xml)
   * [8.3 Updating Order Status](#83-updating-order-status)
9. [Error Handling and Notifications](#9-error-handling-and-notifications)
10. [Support](#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.

<Steps>
  <Step title="Sign Up">
    Create your company's StateSet instance by signing up at [StateSet.com/sign-up](https://StateSet.com/sign-up).
  </Step>

  <Step title="Generate API key">
    Create a new API key in the StateSet Cloud Console at [cloud.StateSet.com/api-keys](https://cloud.StateSet.com/api-keys).
  </Step>

  <Step title="Set Up Your Environment">
    Ensure you have Node.js installed. Install the required npm package:

    ```bash theme={null}
    npm install StateSet-node
    ```
  </Step>

  <Step title="Clone Sample Project">
    Clone our sample project from [GitHub](https://github.com/StateSet/wholesale-order-management). *(Note: Replace with the correct repository link)*. This provides a working example to get you started quickly.
  </Step>

  <Step title="Configure Environment Variables">
    Create a `.env` file in the project's root directory and add your API keys and configurations:

    ```env theme={null}
    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
    ```
  </Step>
</Steps>

## 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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
async function sendEmail(emailContent) {
  try {
    await sgMail.send(emailContent);
    logger.info('Email sent successfully');
  } catch (error) {
    logger.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](mailto:support@StateSet.com) for any questions or implementation help.
