---
name: s4hana-create-pir
description: Create Purchasing Info Records (PIRs) in SAP S/4HANA Cloud Public or on-prem private edition via OData V2 A_PurchasingInfoRecord deep-insert at API_INFORECORD_PROCESS_SRV. Use whenever the user wants to create, post, add, generate, seed, onboard, or maintain purchasing info records — phrases like "create PIRs for supplier X", "add info records", "seed PIR demo data", "maintain prices for these materials", "onboard supplier-material pricing", "make me a few PIRs". Handles header + org-plant deep-insert in one POST, master-data lookup (eligible materials with ProcurementType=F, suppliers with PurchOrg setup), idempotent bulk batches, and known constraints (ProcurementType F only, supplier must have A_SupplierPurchasingOrg row for the target PurchOrg, StandardPurchaseOrderQuantity mandatory, PurchasingInfoRecordCategory lives on org-plant entity NOT header). Do NOT use for PIR price-condition updates (separate entity), PIR deletion, or category 1/2 (subcontracting/consignment) PIRs (untested).
---

# s4hana-create-pir

Create Purchasing Info Records on SAP S/4HANA Cloud Public. **Verified production-ready against `my438741.s4hana.cloud.sap` 2026-05-12 (PIR 5300000130 created via API; 119 prior bulk-migrated PIRs from 2026-05-05).**

## When to trigger
Verbs: create / post / add / generate / seed / maintain / onboard
Objects: PIR(s), purchasing info record(s), info record(s), supplier-material pricing
Counts: 1 to ~200 records.

## Hard rules (never violate)
1. Always do a 1-record live POST as a probe before bulk (≥3 records).
2. Never invent IDs — verify Supplier exists on the target PurchOrg, Material has `ProcurementType=F`, both exist in the target Plant.
3. If a required field has no sensible default and the user didn't specify → **ask once**, then auto-pick.
4. Scripts go in `<cwd>/.s4hana-tmp/create-pirs-<YYYYMMDD-HHMM>/`. Never commit, never modify `.env`.
5. POSTs are sequential with 200ms delay; halt on 3 consecutive failures.

## Endpoint
- Service path: `/sap/opu/odata/sap/API_INFORECORD_PROCESS_SRV`
- Entity sets: `A_PurchasingInfoRecord`, `A_PurgInfoRecdOrgPlantData`
- Method: POST (deep-insert with `to_PurgInfoRecdOrgPlantData`)
- Communication scenario: `SAP_COM_0102` — "Purchasing Info Record Integration"

## Phases

## Phase 0 — Setup check (always do this first)

Before any other action, run the credential-presence check documented in [`shared/setup-check.md`](../../shared/setup-check.md). In short:

1. Is there a `.env` in the user's working directory, OR are `SAP_HOST` / `SAP_AUTH_MODE` already exported in shell? If yes, skip and proceed to Phase 1.
2. If neither, find the plugin's `.env.example` (typically under `~/.claude/plugins/`), offer to copy it to the user's `./.env`, append `.env` to `.gitignore` if present, and **wait** for the user to fill in values before running the main task.

### Phase 1 — Parse & gather input

Per PIR required:

| Field | Notes |
|---|---|
| Supplier | Must exist in target AND have an `A_SupplierPurchasingOrg` row for the target PurchOrg, or you'll get `06/321: Supplier does not exist in purchasing organization` |
| Material | Must have `ProcurementType=F` (external) for the target plant. FERT materials with `ProcurementType=E` (in-house) fail with `ME/092` |
| NetPriceAmount + Currency | Item price |
| StandardPurchaseOrderQuantity | **Required** — `ME/083` fires if missing |

If the user said "auto" or "random", pull eligible pairs:
- Materials: `GET A_ProductPlant?$filter=Plant eq 'X' and ProcurementType eq 'F'`
- Suppliers: `GET A_SupplierPurchasingOrg?$filter=PurchasingOrganization eq 'X'`
- Pair them deterministically (FNV-1a hash for reproducibility)

### Phase 2 — Tenant defaults
Cache via tenant memory. For `my438741`:

| Setting | Value |
|---|---|
| PurchOrg | `1010` |
| Plant | `1010` |
| PurchasingGroup | `001` |
| Currency | `EUR` |
| `PurchasingInfoRecordCategory` | `0` (standard external) |
| Minimum order qty default | `1` |
| Standard order qty default | `10` |
| Planned delivery (days) | `7` |

### Phase 3 — Build payload

**Verified working envelope:**

```json
{
  "Supplier": "10100088",
  "Material": "HB01",
  "BaseUnit": "PC",
  "PurgDocOrderQuantityUnit": "PC",
  "to_PurgInfoRecdOrgPlantData": [
    {
      "PurchasingOrganization": "1010",
      "Plant": "1010",
      "PurchasingInfoRecordCategory": "0",
      "PurchasingGroup": "001",
      "Currency": "EUR",
      "NetPriceAmount": "50.00",
      "MinimumPurchaseOrderQuantity": "1",
      "StandardPurchaseOrderQuantity": "10",
      "MaterialPlannedDeliveryDurn": "7"
    }
  ]
}
```

**Critical structure points:**
- **Header has NO `PurchasingOrganization`, `PurchasingInfoRecordCategory`, `PurchasingInfoRecordType`** — those live on the org-plant child entity. Putting them on header gets rejected.
- **`Currency` (NOT `PurchaseOrderCurrency`)** on org-plant entity. Common mistake — different naming than the supplier-purchorg entity.
- **`PurchasingInfoRecordCategory='0'`** is the standard (external) category. Categories 1 (subcontracting), 2 (consignment), 3 (pipeline) untested on this tenant.
- **`StandardPurchaseOrderQuantity` is required** — even though SAP docs sometimes list it as optional. `ME/083` fires without it.

### Phase 4 — CSRF + POST
Standard OData V2 CSRF flow — see `../s4hana-create-po/references/csrf-flow.md`. PIR ID is auto-assigned in the `5300000xxx` range. Response includes `PurchasingInfoRecord` and `PurchasingInfoRecordUUID`.

**Side-effect to expect:** PIR creation auto-generates pricing condition records (PIR-driven condition tables) — `search_pricing_conditions`, `get_pricing_condition`, and `get_product_pricing` MCP tools will start returning rows after PIR creation.

### Phase 5 — Verify
```
GET /A_PurchasingInfoRecord('5300000xxx')?$expand=to_PurgInfoRecdOrgPlantData
```
Confirm both header and org-plant rows are present.

## Cross-tenant migration notes
- Source PIR IDs do NOT carry forward — Cloud auto-assigns.
- For pair (supplier, material): both must exist in target with their PurchOrg/plant relationships established. If migrating from a source with different supplier/material pools, hash-pair them deterministically.
- 2026-05-05 migration: 109 PIRs created in one batch by hash-pairing 81+ SUPL suppliers against ~100 ProcurementType=F materials at plant 1010. Side-effect: 210 pricing condition records auto-generated.

## Known error catalog

| Code | Cause | Fix |
|---|---|---|
| `ME/083` | "Enter G/L Account" OR "Enter Standard Order Quantity" | The PIR variant: add `StandardPurchaseOrderQuantity` to org-plant entity |
| `ME/092` | Procurement type doesn't match (material is in-house `E`, but PIR needs `F` external) | Filter materials to `ProcurementType=F` only |
| `06/321` | Supplier does not exist in purchasing organization | Extend supplier with `A_SupplierPurchasingOrg` row for the target PurchOrg first |
| `Property X is invalid` | Header has fields that belong on org-plant (e.g. `PurchasingInfoRecordCategory`) | Move them into `to_PurgInfoRecdOrgPlantData` |
| `MM_PUR_PIR/xxx` generic | Check `innererror.errordetails` | Specific field will be named |

## Output structure
```
<cwd>/.s4hana-tmp/create-pirs-<YYYYMMDD-HHMM>/
├── eligible-pairs.json      # (supplier, material) candidates after filtering
├── payloads.json
├── create-log.jsonl
├── results.json
└── verify-sample.json
```

## Reference files
- `references/envelope-template.md` — full schema with all optional fields
- `references/known-quirks.md` — header vs org-plant fields, ProcurementType filter, supplier-purchorg requirement, side-effects
- `scripts/bulk-pir-poster.mjs` — reference implementation
