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

# Warehouse Receiving Quickstart

> Track an inbound shipment, receive it in two parts, reject excess quantities, and verify what changes in the embedded engine.

Start warehouse onboarding with one measurable task: **record ten expected mugs, receive four,
then receive the remaining six**. Verify the shipment state after every change and reject an
attempt to receive more than expected.

This program uses **`@stateset/embedded` 1.35.1** in a fresh local database. No hosted API key,
carrier connection, or warehouse service is required.

<Note>
  In this version, `inboundShipments.receiveLine` updates the inbound shipment's received
  quantity and state. The program verifies that it **does not increase inventory on hand**.
  Posting sellable stock and assigning a warehouse location are separate integration work.
</Note>

## Prerequisites

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

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

See [SDK installation](/guides/sdk-installation) for native-module troubleshooting.

## Run the complete example

Save this as `receiving-demo.mjs`:

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

const commerce = new Commerce(':memory:');
assert.equal(await commerce.inboundShipments.isSupported(), true);
const supplier = await commerce.purchaseOrders.createSupplier({ name: 'Example supplier' });
const product = await commerce.products.create({ name: 'Ceramic mug', slug: 'ceramic-mug' });
const sku = 'MUG-001';
await commerce.inventory.createItem({ sku, name: 'Ceramic mug', initialQuantity: 0 });
const input = {
  supplierId: supplier.id,
  items: [{ productId: product.id, sku, quantityExpected: '10' }],
};
const shipment = await commerce.inboundShipments.create(input);
assert.equal(shipment.status, 'pending');
assert.equal(shipment.items[0].quantityReceived, '0');
const itemId = shipment.items[0].id;
console.log('Created: pending, received=0 of 10');

const inTransit = await commerce.inboundShipments.markInTransit(shipment.id);
assert.equal(inTransit.status, 'in_transit');
const arrived = await commerce.inboundShipments.markArrived(shipment.id);
assert.equal(arrived.status, 'arrived');
console.log('Arrival recorded: arrived');

const partial = await commerce.inboundShipments.receiveLine(shipment.id, itemId, '4');
assert.equal(partial.status, 'partially_received');
assert.equal(partial.items[0].quantityReceived, '4');
console.log('First receipt: partially_received, received=4 of 10');

await assert.rejects(
  () => commerce.inboundShipments.receiveLine(shipment.id, itemId, '7'),
  { code: 'VALIDATION', message: /would exceed the 10 expected/ },
);
const afterRejection = await commerce.inboundShipments.get(shipment.id);
assert.equal(afterRejection.status, 'partially_received');
assert.equal(afterRejection.items[0].quantityReceived, '4');
console.log('Excess receipt rejected; received quantity is still 4');

const received = await commerce.inboundShipments.receiveLine(shipment.id, itemId, '6');
assert.equal(received.status, 'received');
assert.equal(received.items[0].quantityReceived, '10');
assert.ok(received.receivedAt);
console.log('Final receipt: received, received=10 of 10');

const stock = await commerce.inventory.getStock(sku);
assert.deepEqual(
  [stock.totalOnHand, stock.totalAllocated, stock.totalAvailable],
  ['0', '0', '0'],
);
console.log('Inventory remains: on hand=0, allocated=0, available=0');

// A separate shipment demonstrates cancellation without changing the received one.
const abandoned = await commerce.inboundShipments.create({
  ...input,
  items: [{ ...input.items[0], quantityExpected: '2' }],
});
await commerce.inboundShipments.cancel(abandoned.id);
await assert.rejects(
  () => commerce.inboundShipments.receiveLine(abandoned.id, abandoned.items[0].id, '1'),
  { code: 'VALIDATION', message: /cancelled inbound shipment/ },
);
const cancelled = await commerce.inboundShipments.get(abandoned.id);
assert.equal(cancelled.status, 'cancelled');
assert.equal(cancelled.items[0].quantityReceived, '0');
console.log('Cancelled shipment rejects receipt; received quantity is still 0');
```

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

**Success:** all assertions pass and you see:

```text theme={null}
Created: pending, received=0 of 10
Arrival recorded: arrived
First receipt: partially_received, received=4 of 10
Excess receipt rejected; received quantity is still 4
Final receipt: received, received=10 of 10
Inventory remains: on hand=0, allocated=0, available=0
Cancelled shipment rejects receipt; received quantity is still 0
```

## Understand the identities and quantities

| Value | Source | Used for |
| - | - | - |
| Supplier ID | `purchaseOrders.createSupplier` | Linking the inbound shipment to its supplier |
| Product ID | `products.create` | Identifying the expected product |
| SKU | Your item identifier | Comparing shipment lines with inventory |
| Shipment ID | `inboundShipments.create` | Lifecycle changes and read-back |
| Shipment item ID | `shipment.items[0].id` | Receiving against that exact line |

Use the returned **shipment item ID**, not the product ID, as the second argument to
`receiveLine(shipmentId, itemId, quantity)`. Quantity inputs and received totals are decimal
strings. The received quantity is cumulative, but each call supplies an **additional quantity**:
four followed by six yields ten. Sending ten after the initial four would exceed the expectation.

`markArrived` records arrival; it does not count the contents. The partial and final receiving
calls record the quantities counted for the line. This example has one line; inspect all lines
when adapting it to a multi-item shipment.

## Handle retries and discrepancies

This method's published Node signature has no idempotency-key parameter. Do not treat an
identical `receiveLine` call as a read or an overwrite: it adds another quantity when accepted.
An excess-quantity guard is not duplicate detection; a repeated small receipt can still fit
within the expected total.

Keep a durable identity for each physical receiving event in your integration, together with
its shipment/line IDs, counted quantity, and outcome. If the call's result is lost, reconcile
the original event and stored received quantity before sending another delta. Coordinate writers
so two workers do not independently apply the same scan or delivery event.

A mismatch between expected and counted goods needs an explicit operational decision. Do not
change the count to make the shipment look complete. Report damaged, missing, or excess units
to the process that owns disposition and supplier follow-up.

## Keep receiving and stock posting separate

The final shipment is `received`, while `inventory.getStock` still reports zero. The example
has neither posted stock to a bin nor updated a purchase order, supplier bill, or external WMS.
Do not tell another agent that these units are sellable based only on the shipment status.

Choose the receiving and inventory process your system actually uses, map its record IDs, and
verify both outcomes. Simply following every receipt with `inventory.adjust` would introduce
a second write whose retries and failure recovery you must design; it is not an atomic
receiving transaction supplied by this example.

The [receiving and put-away guide](/guides/commerce/receiving-and-putaway) distinguishes these
records and points to the relevant contracts. The [inventory quickstart](/guides/inventory-quickstart)
shows stock adjustments and reservations separately.

## Give an agent a receiving task

For a read-only check, supply the actual shipment ID and ask the agent to report its status,
each line's expected/received quantities, and separately observed inventory. Have it identify
the tool results behind its answer. Do not infer stock availability from the receipt total.

For a write task, also supply the exact line ID, the **additional** quantity physically counted,
the receiving-event identity, and the configured authorization for that operation. Discover the
installed server's tool schema rather than translating these Node method names into guessed
MCP tool names. Use the [agent operating procedure](/getting-started-for-ai-agents).

## Troubleshooting

| Symptom | Check or next step |
| - | - |
| Backend support check fails | Verify the installed package/build exposes inbound shipments |
| Excess-quantity validation error | Compare cumulative received quantity plus the new delta with the expectation |
| Receipt rejected after cancellation | Read the shipment state; do not retry unchanged |
| Line lookup fails | Use the line ID belonging to this shipment, not a product or order item ID |
| Shipment is received but stock is zero | Expected here; verify the separate stock-posting process |
| Quantity doubled after retry | Reconcile duplicate receiving events before making another adjustment |
| Data disappears after exit | `:memory:` is temporary; choose a file-backed database for persistent application state |

## Next steps

* [Supplier and purchasing](/guides/supplier-quickstart): create a PO and verify its approval state.

* [Receiving and put-away](/guides/commerce/receiving-and-putaway): identify the next record and verification boundary.

* [Inventory](/guides/inventory-quickstart): check on-hand, allocated, and available quantities.

* [Connect your operation](/guides/connect-your-operation): map warehouse and external-system ownership.

* [Integration tests](/guides/api-testing): assert unchanged state after rejected writes.


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