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

# Purchase Orders Imports

> Import purchase orders and their lines from a single denormalized CSV.

## Overview

The Purchase Orders importer creates or updates Purchase Orders **and** their Purchase Order Lines from a single CSV. The CSV is denormalized: one row per PO line, with the PO header columns repeated across every row that belongs to the same PO.

Example: two POs, three lines.

| po\_number | supplier | currency | part\_number | quantity | cost   |
| ---------- | -------- | -------- | ------------ | -------- | ------ |
| PO-001     | Acme     | USD      | WIDGET-A     | 10       | 5.00   |
| PO-001     | Acme     | USD      | WIDGET-B     | 5        | 12.00  |
| PO-002     | Globex   | EUR      | GADGET-X     | 1        | 100.00 |

Rows sharing the same `po_number` are merged into a single PO header. If a header column is included in your CSV, it must be populated on **every row of that PO** and all values must match. Any blank cell or any value that disagrees with another row of the same PO fails the import with a per-conflict error before any data is written.

## Header columns

These describe the PO itself. If you include a header column in your CSV, it must be present and identical on every row that shares a `po_number` — otherwise the import fails.

| Column                 | Required | Description                                                                                                   |
| ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `po_number`            | Yes      | PO identifier. The grouping key.                                                                              |
| `original_po_number`   | No       | Existing PO number when renaming. **Lookup column** — an empty cell means "no rename," not `""`.              |
| `po_description`       | No       | Free-text PO description.                                                                                     |
| `supplier`             | No       | Supplier name. Must match an existing supplier.                                                               |
| `ship_to_location`     | No       | Ship-to location name. Must match an existing location.                                                       |
| `bill_to_location`     | No       | Bill-to location name. Must match an existing location.                                                       |
| `currency`             | No       | Currency type (e.g. `USD`, `EUR`). Must match an existing currency.                                           |
| `ordered_at`           | No       | Order date. Parsed flexibly (any pandas-recognized date format).                                              |
| `assigned_to`          | No       | Email of the user the PO is assigned to. Must match an existing user.                                         |
| `po_intent_option`     | No       | Intent option value at the PO level. Prefixed with `po_` to disambiguate from the line-level `intent_option`. |
| `po_labels`            | No       | Semicolon-delimited list of label values to attach to the PO.                                                 |
| `terms_and_conditions` | No       | Title of a Requirement with type `TERMS_AND_CONDITIONS`. Overrides the org's default.                         |
| `fees`                 | No       | Semicolon-delimited list of fee records. See [Fees](#fees) below.                                             |

## Line columns

These describe a single PO line. Every row contributes one line if any line column is populated.

| Column                   | Required | Description                                                                                                                                     |
| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `part_number`            | No       | Part number for the line. Combined with `revision` to look up the part.                                                                         |
| `revision`               | No       | Part revision. If omitted, the latest revision of `part_number` is used.                                                                        |
| `description`            | No       | Line description.                                                                                                                               |
| `quantity`               | No       | Line quantity. Must be non-negative.                                                                                                            |
| `cost`                   | No       | Line unit cost. Must be non-negative.                                                                                                           |
| `need_date`              | No       | Date needed. Parsed flexibly.                                                                                                                   |
| `estimated_arrival_date` | No       | Estimated arrival date. Parsed flexibly.                                                                                                        |
| `paid`                   | No       | Marks the line as paid. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)).                           |
| `percentage_fee_exempt`  | No       | Excludes this line from percentage-based PO fees. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
| `labels`                 | No       | Semicolon-delimited list of label values to attach to the line.                                                                                 |
| `quality_clauses`        | No       | Semicolon-delimited list of quality clause titles. Must match Requirements with `requirement_type = QUALITY_CLAUSE`.                            |
| `intent_option`          | No       | Intent option value at the line level.                                                                                                          |

<Info>
  A row that populates only header columns (no `part_number` or other line column) contributes to the PO header but does not create a line. Use this to create or update a PO without lines.
</Info>

## Fees

The `fees` column accepts a semicolon-delimited list of fee records. Each record uses two bracketed fields after the fee name:

```
Name[value][type];Name[value][type]
```

| Field   | Notes                                        |
| ------- | -------------------------------------------- |
| `Name`  | Required. Must be unique within the PO.      |
| `value` | Required. Must parse as a number.            |
| `type`  | Required. One of `currency` or `percentage`. |

Example: `Shipping[50.00][currency];VAT[8.5][percentage]`

<Warning>
  Fee names must be unique within a PO — the database enforces this with a unique constraint on `(purchase_order_id, name)`. Duplicate fee names in the same row's `fees` cell are rejected at validation.
</Warning>

## Custom attributes

Any column not in the standard set above is treated as a custom attribute. The importer routes each column to either PO-level or PO-line-level attributes based on which table defines it in your organization's attribute configuration:

* A column matching a PO custom attribute is attached to the PO.
* A column matching a PO-line custom attribute is attached to the line.
* A column matching **both** is **ambiguous** and rejected — rename one side to disambiguate.
* A column matching neither is rejected as unrecognized.

Matching is case- and format-sensitive, so a CSV header `BatchID` matches an attribute keyed `BatchID` only.

## Header conflicts

Header-column conflicts are collected across all POs and reported together, so you can fix everything in one pass.

## Related pages

For general import behavior, empty cell handling, and error reporting, see [Importers](/administration/ion-importers).
