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

# Purchase Orders

> Purchase order lines (header + line combined, Katana-compatible). Ingest: POST /api/ingest/purchase_order_lines.

## What it is

Purchase order lines — header and line combined in one row (`order_no` + `sku`), Katana-compatible. Each row is one article on one supplier order, with ordered vs. received quantities, expected arrival, price, and a full provenance trail (who created it — user or agent — and who approved it).

The **status** machine is `draft` → `pending_approval` → `ordered` → `partially_received` → `received` (or `cancelled`). Katana mapping: `draft`→`DRAFT`, `ordered`→`NOT_RECEIVED`, `partially_received`→`PARTIALLY_RECEIVED`, `received`→`RECEIVED` (`pending_approval` is a Concierca addition, exported as `DRAFT`). Open quantity is always computed as `quantity_ordered − quantity_received`, never stored.

Open lines feed the [Procurement Queue](/for-agents/data-model/procurement-queue): `open_purchase_order_quantity` and arrival dates there are aggregated from lines in `ordered` / `partially_received`. A unique index on `recommendation_key` over open lines is the physical double-order lock — one queue recommendation can never spawn two open orders.

## How it gets data

Three paths write here: the procurement agent creates draft lines from queue recommendations, admins edit in the app, and external systems (e.g. an existing PO list) push through the [Ingest API](/for-agents/ingest):

```http theme={null}
POST /api/ingest/purchase_order_lines
Authorization: Bearer pk_...
Content-Type: application/json
```

* One JSON record per request; unknown fields are dropped.
* **Idempotent upsert** on `(tenant_id, external_id)` — use a stable per-line id (e.g. `PO-2026-014-1`).
* `tenant_id` / `workspace_id` come from the API key context, never from the body.
* `product_name`, `supplier_name`, `variant_external_id`, `product_external_id` are filled by a database trigger from the SKU — don't send or maintain them by hand.

Minimal example payload:

```json theme={null}
{
  "external_id": "PO-2026-014-1",
  "order_no": "PO-2026-014",
  "sku": "6304",
  "status": "ordered",
  "quantity_ordered": 20,
  "quantity_received": 0,
  "expected_arrival_date": "2026-10-01",
  "price_per_unit": 89.50,
  "currency": "USD"
}
```

## Fields

### Order line

| Field                   | Label               | Type     | Writable | Description                                                                                                                                              |
| ----------------------- | ------------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `external_id`           | External ID         | text     | ✓        | Stable line id — the upsert key for ingest                                                                                                               |
| `order_no`              | Order No            | text     | ✓        | Purchase order number — the header shared by all lines of one supplier order                                                                             |
| `sku`                   | SKU (code only)     | text     | ✓        | Article code being ordered. The trigger resolves it to the variant identity                                                                              |
| `status`                | Status              | text     | ✓        | `draft` / `pending_approval` / `ordered` / `partially_received` / `received` / `cancelled`. Only `ordered` and `partially_received` count as open supply |
| `quantity_ordered`      | Qty Ordered         | number   | ✓        | Units ordered. Open quantity = `quantity_ordered − quantity_received`, computed on read                                                                  |
| `quantity_received`     | Qty Received        | number   | ✓        | Units received so far; drives `partially_received` → `received`                                                                                          |
| `expected_arrival_date` | Expected Arrival    | date     | ✓        | When the line is expected to arrive — feeds the queue's arrival planning                                                                                 |
| `price_per_unit`        | Price/Unit          | number   | ✓        | Buying price per unit (Katana `price_per_unit`)                                                                                                          |
| `currency`              | Currency            | text     | ✓        | Buying currency                                                                                                                                          |
| `note`                  | Note                | text     | ✓        | Free-text note on the line                                                                                                                               |
| `supplier_id`           | Supplier (internal) | text     | ✓        | Supplier reference — joins to `suppliers.id`                                                                                                             |
| `product_id`            | Product (internal)  | text     | ✓        | Product reference — joins to `products.id`                                                                                                               |
| `ordered_at`            | —                   | datetime | API only | When the order was actually placed with the supplier                                                                                                     |
| `received_at`           | —                   | datetime | API only | When the line was fully received                                                                                                                         |
| `location_id`           | —                   | uuid     | API only | Destination location/warehouse reference, for multi-location setups                                                                                      |

### Snapshot columns (trigger-filled)

| Field                 | Label              | Type | Writable | Description                                                          |
| --------------------- | ------------------ | ---- | -------- | -------------------------------------------------------------------- |
| `product_name`        | Product            | text | trigger  | Product title at order time — do not maintain by hand                |
| `supplier_name`       | Supplier           | text | trigger  | Supplier name at order time — do not maintain by hand                |
| `variant_external_id` | Variant ID         | text | trigger  | Shopify variant gid — the article's unique identity (the SKU is not) |
| `product_external_id` | Shopify Product ID | text | trigger  | Shopify product gid (parent product of the variant)                  |

### Provenance, approval & external sync

| Field                 | Label    | Type     | Writable  | Description                                                                                                                |
| --------------------- | -------- | -------- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `recommendation_key`  | Rec. Key | text     | read-only | Links the line to the queue recommendation that spawned it. Unique while the line is open — the physical double-order lock |
| `created_by_type`     | —        | text     | API only  | Who created the line: user or agent                                                                                        |
| `created_by_user_id`  | —        | uuid     | API only  | Creating user, when created by a human                                                                                     |
| `created_by_agent_id` | —        | uuid     | API only  | Creating agent, when created by the procurement agent                                                                      |
| `created_by_run_id`   | —        | text     | API only  | Agent run that created the line — full trace back to the reasoning                                                         |
| `approved_by`         | —        | uuid     | API only  | User who approved the line (`pending_approval` → `ordered`)                                                                |
| `approved_at`         | —        | datetime | API only  | When it was approved                                                                                                       |
| `external_system`     | —        | text     | API only  | External system this line is synced with (e.g. katana)                                                                     |
| `external_po_id`      | —        | text     | API only  | Purchase order id in that external system                                                                                  |
| `external_line_id`    | —        | text     | API only  | Line id in that external system                                                                                            |

✓ = editable in the app, settable on create, and accepted on ingest. "trigger" = computed by the database on write. "API only" = real database column not registered as a visible app column — readable via query, maintained by the system or specialized flows.

<Note>
  Like every workspace table, purchase order lines also carry the system columns `id`, `tenant_id`, `workspace_id`, `created_at`, and `updated_at` — set automatically, never sent by integrations.
</Note>

## Query it

```json theme={null}
{
  "method": "tools/call",
  "params": {
    "name": "data_query",
    "arguments": {
      "view_slug": "purchase_order_lines",
      "filters": { "status": "ordered" },
      "select_fields": ["order_no", "sku", "quantity_ordered", "quantity_received", "expected_arrival_date"],
      "sort_by": "expected_arrival_date",
      "sort_desc": false,
      "limit": 50
    }
  }
}
```
