WFDASH-WEBAPP/docs/api-reference/orders-create.md

164 lines
4.9 KiB
Markdown
Raw Permalink Normal View History

# WFDASH API — Create Order (POST /api/v1/orders/create)
> **One payload, one call.** The full order header + items array goes
> in a single request body. There is no `/api/v1/orders` POST —
> `/orders` is GET-only.
---
## Endpoint
```
POST /api/v1/orders/create
```
Bearer-token auth via `AGENT_API_KEY` header. **Hermes drops
`AGENT_API_KEY` on direct REST calls** — use the MCP wrapper
`mcp__wfdash-mcp__orders__create` with the same body shape.
`runtime = "nodejs"` (PocketBase admin auth requires Node).
---
## Request body
```json
{
"customer_id": "<15-char PB id>", // required
"company_id": "<15-char PB id>", // required
"notes": "string?", // order-level notes
"contact_name":"string?", // operator-readable contact
"description": "string?", // short description of the order
"email_message_id": "string?", // dedup key
"rep": "string?", // sales rep free-text
"items": [
{
"description": "string", // required, trimmed
"quantity": 1, // default 1; must be > 0 when supplied
"cost_price": 123.45,
"markup": 25,
"selling_price": 154.31, // optional
"sku": "string?",
"unit": "EA",
"supplier_id": "<15-char PB id?>",
"notes": "string?"
}
]
}
```
### Field rules
- **`customer_id`, `company_id`** — must resolve in PB. Free-text
names are **not** accepted on this endpoint (use the MCP `orders.create`
wrapper if you need name-resolution; the REST handler requires ids).
- **`items[]`** — non-empty, every item needs `description`.
- **`selling_price` auto-derive** — when `cost_price` and `markup` are
both supplied and `selling_price` is omitted, it auto-derives as
`cost_price * (1 + markup / 100)` rounded to 2 dp. Pass
`selling_price` explicitly to override.
- **`email_message_id`** — optional idempotency key. If an order with
the same `email_message_id` already exists, the response returns the
existing record with `duplicate: true` and HTTP **200** instead of
201. Safe to retry.
- **Per-row independence** — each item creates one `order_item` row
with `item_status = "pending"` and `drv_status = "unassigned"`. One
item's secondary-field patch failure does **not** abort the rest of
the call; failures land in `items_failed[]` with the partial-success
shape.
---
## Response
### 201 — created
```json
{
"order": { /* full PB order record */ },
"order_items_collection": "<relation-id>",
"items_created": [ { "id": "...", "description": "..." } ],
"items_failed": []
}
```
### 200 — duplicate (`email_message_id` matched)
Same shape as 201, plus `"duplicate": true`. Nothing new was written.
### 400 — validation failure
```json
{ "error": "customer_id is required" }
{ "error": "items must be a non-empty array" }
```
### 404 — not found
```json
{ "error": "Requested resource not found..." }
```
`customer_id` or `company_id` doesn't resolve in PB.
### 500 — server / PB write failure
```json
{ "error": "<PocketBase error message>" }
```
---
## Common workflows
### Walk-in / counter sale
No upstream quote. Customer walks up, picks items, you write the
order straight from the till:
```bash
curl -X POST https://wfdash.example/api/v1/orders/create \
-H "Authorization: Bearer $AGENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "<customer-id>",
"company_id": "<company-id>",
"notes": "Counter sale — 2026-08-27",
"items": [
{ "description": "SKF 6205 bearing", "quantity": 4,
"cost_price": 85, "markup": 30 }
]
}'
```
### Re-create from a quote
**Don't** use this endpoint — use
`POST /api/v1/orders/from-quote` (MCP: `orders.create_from_quote`).
It clones the matched quote into a fresh `ORD-YYYY-MM-NNN` order,
inheriting customer / company / rep / letterhead / line items.
### Append to an existing order
There **is** a separate endpoint:
`POST /api/v1/orders/{id}/items` (MCP: `order_items.create`). But it
**refuses with 409 `order_locked`** the moment any line on the order
is past `pending`. Use it only on fresh orders — for any order that
has a PO in flight, you must `orders.split` first.
---
## Pitfalls
- **Passing the order header without `items[]`** → 400. The handler
does not create empty orders.
- **Passing `quantity: 0` or negative** → rejected per item by the
service layer; the row lands in `items_failed[]` with
`validation_error`.
- **Forgetting to send `AGENT_API_KEY`** → 401 from
`assertAgentAuth`. From Hermes, use `mcp__wfdash-mcp__orders__create`
instead — the MCP layer injects the key.
- **Reusing `email_message_id` for genuinely different orders** →
the second call returns the first order with `duplicate: true`.
Don't reuse the key; generate a new one per logical order.