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

# Supplier & Purchase Order Quickstart

> Create a supplier and purchase order, verify approval transitions, and distinguish local records from supplier delivery.

Create a supplier and a purchase order for **ten mugs at a unit cost of five**. Submit it for
approval, approve it, and record it as sent. Check the stored state after each step and verify
that invalid transitions leave the record unchanged.

This walkthrough uses **`@stateset/embedded` 1.35.1** and a temporary local database. It needs no
API key and makes no supplier deliveries.

<Note>
  `purchaseOrders.send` records the PO's `sent` state. It does not email the supplier or submit
  an EDI document. Keep delivery evidence from your actual connector separate from this state.
</Note>

## Prerequisites

Use Node.js **20.20.0+** and npm **10+**:

```bash theme={null}
mkdir stateset-purchasing-example
cd stateset-purchasing-example
npm init -y
npm install @stateset/embedded@1.35.1
```

For native-module installation problems, see [SDK installation](/guides/sdk-installation).

## Run the complete example

Save this as `purchasing-demo.mjs`:

```javascript theme={null}
import assert from 'node:assert/strict';
import { Commerce } from '@stateset/embedded';

const commerce = new Commerce(':memory:');
const supplier = await commerce.purchaseOrders.createSupplier({
  name: 'Example supplier',
  supplierCode: 'DEMO-SUPPLIER',
  email: 'purchasing@example.com',
});
const storedSupplier = await commerce.purchaseOrders.getSupplier(supplier.id);
assert.equal(storedSupplier.supplierCode, 'DEMO-SUPPLIER');
assert.equal(storedSupplier.isActive, true);
console.log('Supplier created and read back: DEMO-SUPPLIER');

const input = {
  supplierId: supplier.id,
  items: [{ sku: 'MUG-001', name: 'Ceramic mug', quantity: 10, unitCost: 5 }],
  notes: 'Local onboarding example; no supplier delivery',
};
const po = await commerce.purchaseOrders.create(input);
assert.equal(po.supplierId, supplier.id);
assert.equal(po.status, 'draft');
assert.equal(po.subtotalExact, '50');
assert.equal(po.totalExact, '50');
console.log('Created: draft, total=50');

await assert.rejects(
  () => commerce.purchaseOrders.send(po.id),
  { code: 'CONFLICT', message: /from 'draft' to 'sent'/ },
);
assert.equal((await commerce.purchaseOrders.get(po.id)).status, 'draft');
console.log('Sending a draft rejected; stored state is still draft');

const submitted = await commerce.purchaseOrders.submit(po.id);
assert.equal(submitted.status, 'pending_approval');
assert.equal((await commerce.purchaseOrders.get(po.id)).status, 'pending_approval');
console.log('Submitted: pending_approval');

// Demonstration identity only; enforce approval permissions in your application.
const approved = await commerce.purchaseOrders.approve(po.id, 'demo-approver');
assert.equal(approved.status, 'approved');
assert.equal((await commerce.purchaseOrders.get(po.id)).status, 'approved');
console.log('Approved: approved');

const sent = await commerce.purchaseOrders.send(po.id);
assert.equal(sent.status, 'sent');
const stored = await commerce.purchaseOrders.get(po.id);
assert.equal(stored.status, 'sent');
assert.equal(stored.totalExact, '50');
console.log('Recorded as sent: sent; supplier delivery not performed');

const abandoned = await commerce.purchaseOrders.create(input);
await commerce.purchaseOrders.cancel(abandoned.id);
await assert.rejects(
  () => commerce.purchaseOrders.submit(abandoned.id),
  { code: 'CONFLICT', message: /from 'cancelled' to 'pending_approval'/ },
);
assert.equal((await commerce.purchaseOrders.get(abandoned.id)).status, 'cancelled');
assert.equal(await commerce.purchaseOrders.count(), 2);
console.log('Cancelled PO rejects submission; stored state is still cancelled');
```

Run it:

```bash theme={null}
node purchasing-demo.mjs
```

**Success:** all assertions pass and the output is:

```text theme={null}
Supplier created and read back: DEMO-SUPPLIER
Created: draft, total=50
Sending a draft rejected; stored state is still draft
Submitted: pending_approval
Approved: approved
Recorded as sent: sent; supplier delivery not performed
Cancelled PO rejects submission; stored state is still cancelled
```

## Understand the records and approval boundary

| Value | Meaning in this example |
| - | - |
| `supplier.id` | Returned supplier identity used to create the PO |
| `supplierCode` | A business identifier to retain in your external-system mapping |
| `po.id` | Returned PO identity used for reads and transitions |
| `po.poNumber` | Generated PO number; retain it alongside the ID for operator lookup |
| `subtotalExact`, `totalExact` | Decimal strings returned by the engine; prefer them over the deprecated floating-point output fields |
| `approvedBy` argument | Approver identity recorded by the operation; your application must decide who is allowed to approve |

The demonstrated path is `draft` → `pending_approval` → `approved` → `sent`. A draft cannot
skip directly to sent. The separate cancellation case shows that a cancelled PO cannot be
resubmitted through this path.

The published Node input accepts numeric `quantity` and `unitCost`. This example uses whole
numbers to make the total easy to verify. It does not expose a currency field in
`CreatePurchaseOrderInput`; do not add an invented `currency` or `unitCostExact` parameter.
Confirm currency and rounding requirements for the interface you choose before using it for
real procurement.

The returned `PurchaseOrderOutput` in this binding contains summary fields, not a PO line-item
array or approval history. Retain your submitted line data and approval evidence in the owning
system; do not write code that assumes `po.items` or `po.approvedBy` is present.

## Connect purchasing to real operations

Before adapting this example, decide which system owns supplier identities, commercial terms,
approval policy, and supplier delivery. The [connection guide](/guides/connect-your-operation)
provides a mapping and task-brief template.

1. Resolve the supplier using a stored mapping. Creating a supplier on every run is suitable
   for this fresh demonstration database, not a synchronization strategy.
2. Retain an external procurement-request identity and its returned PO ID. The published
   `create` signature has no idempotency-key parameter; a lost response is not a reason to
   create another PO without reconciliation.
3. Enforce approval permissions and amount limits before calling `approve`. A caller-supplied
   name does not prove the approver's identity or authority.
4. Use your configured connector to deliver the approved document. Record its delivery ID,
   outcome, and supplier acknowledgement separately; reconcile partial failures before retrying.
5. Track the inbound goods and verify inventory through your receiving process. A PO marked
   sent does not prove supplier acceptance, receipt of goods, or available stock.

The [warehouse receiving quickstart](/guides/warehouse-quickstart) demonstrates a separate
inbound shipment. It does not automatically link that shipment to this PO. Keep the relationship
explicit in your integration, or use the receipt-from-PO operation supported by your chosen
interface as described in [receiving and put-away](/guides/commerce/receiving-and-putaway).

## Give an agent a purchasing task

Start with a read-only request containing the actual PO ID: report its supplier ID, PO number,
status, and exact total, with the tool results supporting the answer. Ask it to report supplier
delivery and approval evidence as unknown unless those records are available.

For a write task, specify the permitted transition, the approval policy, the authorized identity,
and the procurement-request identity. A draft-creation task does not authorize approval or
supplier delivery. Discover the installed server's tool schemas instead of guessing MCP tool
names from these Node methods. See the [agent operating procedure](/getting-started-for-ai-agents).

After a failed or interrupted call, read the exact PO before deciding whether further action is
needed. Do not reinterpret every `CONFLICT` as success; the example's conflicts are rejected
transitions. See [error handling and retries](/guides/error-handling-best-practices).

## Troubleshooting

| Symptom | Check or next step |
| - | - |
| Sending a draft returns `CONFLICT` | Inspect its state and complete the configured approval process |
| Cancelled PO cannot be submitted | Reconcile the cancelled request before considering a replacement |
| Supplier receives nothing after `send` | Check the external delivery connector and its evidence |
| `po.items` is undefined | This binding returns a PO summary; use a supported detail surface for line read-back |
| Duplicate POs after a timeout | Reconcile the original procurement request and saved PO mapping before creating again |
| Data disappears after exit | `:memory:` is temporary; use a file-backed database for persistent application state |

## Next steps

* [Warehouse receiving](/guides/warehouse-quickstart): count expected and received units separately from inventory.
* [Your first operation](/guides/first-operation): connect a persistent local fixture to an agent.
* [Integration test plan](/guides/comprehensive-testing-guide): verify persistence and recovery behavior.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.