<!-- SKILL-VERSION: library/purchase-order-v1 3a76850@2026-10-04T07:41:12+10:00 -->
> **Skill version:** `3a76850@2026-10-04T07:41:12+10:00` (`library/purchase-order-v1`).
> If you have a cached copy of this skill with a different version token,
> discard it and use this copy. Re-fetch this URL at the start of each
> session to detect updates.

# makesPDF — Purchase Order skill `purchase-order-v1`

> Version: `purchase-order-v1`. **Immutable** — this file never changes
> after release. Fixes land in `purchase-order-v2`, served alongside
> this file. If a newer version exists, it is noted at the top of this
> file; this URL keeps serving regardless.

> **Note to AI agents:** this document is served over HTTP at
> `https://makespdf.com/skills/library/purchase-order-v1.md` (or
> `http://localhost:8788/skills/library/purchase-order-v1.md` in dev).
> It is **not a local file.** Re-fetch it with a web fetch / HTTP tool
> — do **not** call your filesystem `Read` tool with `/skills/...`.
> Paths that begin with `/api/v1/` or `/skills/` below are URL paths
> relative to the makespdf origin, not filesystem paths.

Read [`/skills/pdf-template-author.md`](https://makespdf.com/skills/pdf-template-author.md)
first for the builder DSL, style properties, and the preview/render loop.
This file is the purchase-order-specific companion: which fields to use,
which library template to start from, and a worked fetch, fill, render
example.

A purchase order is a formal request to buy goods/services — issued
**by** the buyer **to** the vendor. Direction matters for the field
mapping below: your company is the buyer (issuer), the supplier is the
seller. For proposed pricing before a sale, use the quote skill; for a
bill after delivery, use the invoice skill.

## Canonical fields (field registry)

Source: `docs/field-registry.md` and
`apps/web/src/utils/field-registry.ts` (`documentTypes` containing
`"purchase-order"`). Use these canonical ids when naming data keys. The
`Aliases` column lists common incoming names the registry already
recognises — prefer the canonical id for new data.

### Document identity

| Canonical id    | Label         | Type / format | Common aliases                |
| --------------- | ------------- | ------------- | ----------------------------- |
| `po-number`     | PO Number     | string        | pono, ordernumber, orderid    |
| `document-date` | Date          | date          | date, orderdate, podate       |
| `delivery-date` | Delivery Date | date          | shipdate, expecteddelivery    |
| `payment-terms` | Payment Terms | string        | paymentterms, terms, netterms |
| `currency`      | Currency      | code          | currencycode, cur             |
| `status`        | Status        | string        | status, orderstatus           |

### Buyer (issuer — your company)

| Canonical id      | Label           | Type / format | Common aliases                           |
| ----------------- | --------------- | ------------- | ---------------------------------------- |
| `buyer-name`      | Client Name     | string        | customername, client                     |
| `buyer-address`   | Client Address  | string        | customeraddress, billingaddress          |
| `buyer-contact`   | Contact Name    | string        | contactname, attn, attention             |
| `buyer-email`     | Client Email    | email         | customeremail, contactemail              |
| `buyer-phone`     | Client Phone    | phone         | customerphone, contactphone              |
| `ship-to-address` | Ship To Address | string        | shipto, shippingaddress, deliveryaddress |

### Seller (vendor — the supplier)

| Canonical id     | Label           | Type / format | Common aliases                        |
| ---------------- | --------------- | ------------- | ------------------------------------- |
| `seller-name`    | Company Name    | string        | companyname, vendorname, suppliername |
| `seller-address` | Company Address | string        | companyaddress, vendoraddress         |
| `seller-phone`   | Phone           | phone         | phone, mobile, tel                    |
| `seller-email`   | Email           | email         | email, companyemail                   |
| `seller-tax-id`  | Tax ID          | tax-id        | gstin, vat, ein, abn                  |
| `seller-logo`    | Logo            | url           | logo, companylogo                     |

### Line items

| Canonical id       | Label       | Type / format | Common aliases                   |
| ------------------ | ----------- | ------------- | -------------------------------- |
| `item-description` | Description | string        | description, itemname, product   |
| `item-quantity`    | Quantity    | integer       | qty, quantity, count             |
| `item-unit-price`  | Unit Price  | currency      | unitprice, price, rate           |
| `item-unit`        | Unit        | code          | uom, unitofmeasure               |
| `item-line-total`  | Line Total  | currency      | linetotal, amount, total         |
| `item-sku`         | SKU         | code          | sku, itemcode, partnumber, hsncd |

### Totals

| Canonical id    | Label      | Type / format | Common aliases          |
| --------------- | ---------- | ------------- | ----------------------- |
| `subtotal`      | Subtotal   | currency      | subtotal, netamount     |
| `tax-amount`    | Tax Amount | currency      | tax, vat, gst, salestax |
| `shipping-cost` | Shipping   | currency      | shipping, freight       |
| `total`         | Total      | currency      | grandtotal, ordertotal  |

### Notes

| Canonical id           | Label                | Type / format | Common aliases                     |
| ---------------------- | -------------------- | ------------- | ---------------------------------- |
| `notes`                | Notes                | string        | notes, memo, remarks               |
| `terms-and-conditions` | Terms & Conditions   | string        | terms, tandcs                      |
| `special-instructions` | Special Instructions | string        | instructions, deliveryinstructions |
| `acceptance-signature` | Acceptance Signature | string        | signature, signedby, approvedby    |

## Library template

There is no curated purchase-order template in the library yet, so start
from **`quote-classic`** (`GET /api/v1/templates/quote-classic`) — it
has the closest structure (parties, line-item table, totals, terms,
signature block) — and adapt it with the starter below, which swaps the
parties into buyer-issuer / vendor roles and uses the PO canonical
fields above.

## Worked example: fetch, adapt, render

All calls need `Authorization: Bearer $MAKESPDF_API_KEY`.

**1. Fetch the closest base template.**

```sh
curl -s -H "Authorization: Bearer $MAKESPDF_API_KEY" \
  "https://makespdf.com/api/v1/templates/quote-classic"
# -> { templateId, name, dsl, sampleData, ... } — structural starting point
```

**2. Adapt.** The starter below is a complete, validated PO template.
Copy it, keep its `{{variables}}` keyed to the canonical ids above, and
replace `sampleData` with the real order:

```js
const cols = [
  ["SKU", "auto"],
  ["Description", "1fr"],
  ["Qty", "auto", "right"],
  ["Unit Price", "auto", "right"],
  ["Amount", "auto", "right"],
];

const template = doc(
  {
    size: "A4",
    title: "Purchase Order {{poNumber}}",
    author: "{{buyer.name}}",
    subject: "Purchase order {{poNumber}} for {{seller.name}}",
    keywords: ["purchase-order", "{{buyer.name}}"],
  },
  minHdr("Purchase Order", "{{buyer.name}}"),
  lvGrid([
    ["PO #:", "{{poNumber}}"],
    ["Date:", "{{date}}"],
    ["Delivery:", "{{deliveryDate}}"],
  ]),
  gap(8),
  addrs(
    { label: "Buyer (issuer)", lines: ["{{buyer.name}}", "{{buyer.address}}", "{{buyer.email}}"] },
    { label: "Vendor", lines: ["{{seller.name}}", "{{seller.address}}", "{{seller.email}}"] }
  ),
  gap(8),
  s({ "font-size": 9, color: "#333" }, "Ship to: {{shipTo}}"),
  gap(8),
  table(cols, "item in items", [
    ["{{item.sku}}", "auto"],
    ["{{item.description}}", "1fr"],
    ["{{item.qty}}", "auto", "right"],
    ["{{item.unitPrice | currency}}", "auto", "right"],
    ["{{item.total | currency}}", "auto", "right"],
  ]),
  totals(
    [
      ["Subtotal", "{{subtotal | currency}}"],
      ["Tax", "{{taxAmount | currency}}"],
      ["Shipping", "{{shipping | currency}}"],
      ["Total", "{{total | currency}}", true],
    ],
    cols
  ),
  terms("Terms & Conditions", "{{terms}}"),
  sigBlock(),
  ftrPages("{{buyer.name}}")
);

const sampleData = {
  poNumber: "PO-2026-0184",
  date: "2026-09-01",
  deliveryDate: "2026-09-30",
  paymentTerms: "Net 30",
  currency: "USD",
  buyer: {
    name: "Harbor Foods Co-op",
    address: "44 Pier Street, Portland, ME 04101",
    email: "purchasing@harborfoods.example.com",
  },
  seller: {
    name: "Northline Networks LLC",
    address: "900 Summit Avenue, Seattle, WA 98101",
    email: "sales@northline.example.com",
  },
  shipTo: "Harbor Foods warehouse, 44 Pier Street, Portland, ME 04101",
  items: [
    { sku: "AP-420X", description: "Wi-Fi 6E access point", qty: 12, unitPrice: 410, total: 4920 },
    { sku: "SVC-SURV", description: "Site survey (full day)", qty: 1, unitPrice: 950, total: 950 },
  ],
  subtotal: 5870,
  taxAmount: 440.25,
  shipping: 120,
  total: 6430.25,
  terms: "Delivery FOB destination. Net 30 from acceptance.",
};
```

**3. Validate, save, render.** Pre-flight the adapted DSL (free), save
it, then render production output (billed, 1 credit per 10 pages):

```sh
curl -s -X POST https://makespdf.com/api/v1/preview/validate \
  -H "Authorization: Bearer $MAKESPDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"dsl": "...", "data": {...}}'
# -> { valid, issues, summary }

curl -s -X POST https://makespdf.com/api/v1/templates \
  -H "Authorization: Bearer $MAKESPDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"dsl": "...", "name": "PO-2026-0184"}'
# -> { templateId, ... }

curl -s -X POST https://makespdf.com/api/v1/render \
  -H "Authorization: Bearer $MAKESPDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"templateId": "<templateId>", "data": {...}}' -o po.pdf
```

## Traps

- Keep the issuer/vendor roles straight: `buyer-*` is your company,
  `seller-*` is the supplier. Swapping them produces a quote with the
  wrong letterhead.
- `ship-to-address` is load-bearing on a PO — it is the delivery
  location, often different from the buyer's billing address.
- Numeric table columns stay `auto`/`1fr`, never fixed `%` widths on
  Qty / Unit Price / Amount.
