WFDASH-WEBAPP/docs/api-reference/orders-create.md
Dave cfd3e3e6be feat(quotes): add person_hint for customer resolution in forwarded emails
Introduces the `person_hint` field to the `EmailIntakePayload`, allowing the system to resolve the actual customer when a rep forwards an email. The `findOrCreateCustomer` function now prioritizes `person_hint` over the sender's email, enabling a more accurate attribution of quotes. This change includes a case-insensitive substring match with a token-drop fallback for multi-token names, ensuring better matching against existing customer records.

Additionally, new unit tests have been added to validate this functionality, covering various scenarios including token-drop behavior and ensuring the correct customer is linked when a rep forwards an RFQ.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 11:11:44 +02:00

4.9 KiB

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

{
  "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

{
  "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

{ "error": "customer_id is required" }
{ "error": "items must be a non-empty array" }

404 — not found

{ "error": "Requested resource not found..." }

customer_id or company_id doesn't resolve in PB.

500 — server / PB write failure

{ "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:

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.