# Sandvik SAP Ariba cXML Integration

## Phase 1 — Current Scope

### Convert files done

| # | Direction | Document | cXML Type | Convert File |
|---|-----------|----------|-----------|--------------|
| 1 | **IN** | Purchase Order | `OrderRequest` | `order-sales-xml-convert.json` |
| 2 | **OUT** | Order Confirmation | `ConfirmationRequest` | `order-confirmation-xml-convert.json` |
| 3 | **OUT** | Ship Notice / ASN | `ShipNoticeRequest` | `order-ship_notice-xml-convert.json` |
| 4 | **OUT** | Invoice | `InvoiceDetailRequest` | `order-invoice-xml-convert.json` |

### Not yet implemented

| Document | Direction | Why not | When |
|----------|-----------|---------|------|
| Goods Receipt | IN | Sandvik sends after receiving goods | Phase 2 |
| OC Approval | IN | Auto-approved if within tolerance | Phase 2 |

### Data flow

```mermaid
graph LR
    S[Sandvik SAP S/4HANA] -->|OrderRequest cXML| BN[SAP Business Network]
    BN -->|HTTPS POST| MG[Managed Gateway]
    MG -->|POST /rest/nc/upload/cxml| NC[NC cxml worker]
    NC -->|parseMultipart if multipart/mixed| XML[Extract XML part]
    XML -->|order-sales-xml-convert| ORDER[NC Sales Order]
    ORDER -->|order-confirmation-xml-convert| OC[ConfirmationRequest .xml]
    ORDER -->|order-ship_notice-xml-convert| ASN[ShipNoticeRequest .xml]
    ORDER -->|order-invoice-xml-convert| INV[InvoiceDetailRequest .xml]
    OC -->|sendCxml + client cert| MG
    ASN -->|sendCxml + client cert| MG
    INV -->|sendCxml + client cert| MG
    MG -->|forward| BN
    BN -->|cXML| S
```

### Transport details

SAP Ariba Managed Gateway sends POST requests with `Transfer-Encoding: chunked` and `Content-Type: multipart/mixed` (XML + attachments). The NC REST layer de-chunks the body before passing to the cXML worker, then `parseMultipart` extracts the XML part and attachments.

| Header | Value | Notes |
|--------|-------|-------|
| `Content-Type` | `multipart/mixed; boundary="..."` | XML + attachments, or `text/xml` for plain cXML |
| `Transfer-Encoding` | `chunked` | SAP Ariba uses chunked transfer encoding |
| `Authorization` | `Basic ...` | Caddy IP allowlisting + SharedSecret auth |

### PO → OC cycle

1. Sandvik PO arrives via SAP Managed Gateway → saved to `import/sales-order/`
2. `order-sales-xml-convert.json` parses PO → NC sales order + order rows
3. User reviews order, adjusts delivery dates/quantities if needed
4. User triggers export → `order-confirmation-xml-convert.json` generates ConfirmationRequest
5. NC sends OC to SAP Managed Gateway via `sendCxml()` with client certificate
6. If OC has deviations outside tolerance → Sandvik approval flow (ApprovalRequest IN)
7. If OC within tolerance → no further action, Sandvik accepts automatically

### Control Keys — must be imported from PO

Every PO line has `<ControlKeys>` that tell the supplier what is required:

```xml
<ControlKeys>
  <OCInstruction value="allowed">...</OCInstruction>
  <ASNInstruction value="notAllowed">...</ASNInstruction>
  <InvoiceInstruction value="isNotERS"/>
</ControlKeys>
```

From real PO 4500038285 (our actual test PO):

| Control Key | Value | Meaning |
|-------------|-------|---------|
| Order Confirmation | `allowed` | Must send OC — no tolerance for price (0.00%) |
| Ship Notice | `notAllowed` | Do NOT send ASN |
| Invoice | `isNotERS` | Standalone invoice (not Evaluated Receipt Settlement) |

**These must be stored in NC order row `json_data`** so the system knows per-line requirements:

| Field | cXML Source | Stored in |
|-------|------------|-----------|
| `json_data.confirmation_instruction` | `ControlKeys/OCInstruction/@value` | order row |
| `json_data.ship_notice_instruction` | `ControlKeys/ASNInstruction/@value` | order row |
| `json_data.invoice_instruction` | `ControlKeys/salesInstruction/@value` or `InvoiceInstruction/@value` | order row |

### Real PO example — 4500038285 (our test PO)

From Sandvik Mining & Construction, sent 26 Nov 2025:

```text
PO: 4500038285, Version: 1, Amount: 121.42 EUR
From: Sandvik Mining & Construction Logistics Ltd (Ireland)
To: SUOMEN TEKNIIKKAPALVELU OY - TEST (Multia, Finland)
Payment: 75 days due net
Company Code: 1861, Purchase Group: 102, Purchase Org: 1400
Customer VAT: IE6374158E, Supplier VAT: FI27405518
Transport: FCA (Free Carrier), Location: FI-Multia
Ship To: Eindhoven SMCL (HUB), Netherlands, Code: DIC1

Line 10: THREAD PIECE, Part 55179607, Qty 5 EA, 9.35 EUR, Need 17 Dec 2025
  Control: OC=allowed, ASN=notAllowed, Invoice=isNotERS
  Tax: NN 0.00%
  Classification: ERPCommodityCode=74906, Description="Ug TH DSP - Other"
  External Line Number: 00010

Line 20: LAMP BRACKET, Part BG00427147, Qty 1 EA, 74.67 EUR, Need 24 Dec 2025
  Control: OC=allowed, ASN=notAllowed, Invoice=isNotERS
  Tax: NN 0.00%
  Classification: ERPCommodityCode=74405, Description="Sfce THDSP-Cnsum-veh"
  External Line Number: 00020
```

Key observations from real PO:

- Sandvik part number is in `BuyerPartID` (e.g. 55179607, BG00427147)
- `SupplierPartID` may or may not be provided
- `Ship Notice: not allowed` — our current POs do NOT require ASN
- All lines have tax category `NN` (zero-rated, 0.00%)
- Each line has `ERPCommodityCode` and `ERPCommodityCodeDescription` in Classification
- `Estimated days for inspection: 1` and `Receiving Type: 4` in Other Information
- PO header text may include criticality: Normal, Rush, or Emergency
- Invoice address: <payables_ie@sandvik.com>

### `order-sales-xml-convert.json` — ControlKeys

ControlKeys use **field mappings** to store each PO line's requirements into `json_data`:

```text
OCInstruction/@value     → ordr.json_data.confirmation_instruction (text, "allowed"/"notAllowed")
ASNInstruction/@value    → ordr.json_data.ship_notice_instruction (text, "allowed"/"notAllowed")
InvoiceInstruction/@value → ordr.json_data.invoice_instruction (text, "isNotERS"/"isERS"/"notAllowed")
```

Also stored from PO line data:

```text
ScheduleLine/@lineNumber → ordr.json_data.schedule_line_number
BatchInfo/@requiresBatch → ordr.json_data.requires_batch
```

---

## 1. Overview

Integration between Sandvik (SAP Ariba / S4HANA) and Tekpal (NC backend) using cXML protocol.

- **Protocol**: cXML v1.2.063+ over HTTPS
- **Transport**: SAP Managed Gateway for Spend and Network (Supplier Managed Gateway)
- **Documents OUT**: Order Confirmation (phase 1), Ship Notice (phase 2), Invoice (later)
- **Documents IN**: Purchase Order (phase 1), PO Change, Goods Receipt, OC Approval (phase 2)
- **Reference**: cXML Reference Guide v1.2.068 (644 pages)
- **DTDs**: Fulfill.dtd (OC/ASN), InvoiceDetail.dtd (invoices), cXML.dtd (orders)

Document upload/download and transport details are in `cxml-file-move.md`. This document covers only the cXML structures, field mappings, and convert definitions.

## 2. cXML Protocol

- cXML = Commerce eXtensible Markup Language, XML-based B2B protocol
- Request-Response model over HTTP/HTTPS POST
- Document structure: `cXML` envelope containing `Header` + `Request`
- `Header`: From, To, Sender credentials (NetworkID domain)
- All element names start uppercase, attributes start lowercase
- `payloadID` = unique document identifier (timestamp-based, generated per document)
- `timestamp` = ISO 8601 format: `2024-11-14T03:20:46-08:00`
- `deploymentMode` = "test" or "production"
- `xml:lang` = language code on root element ("en-GB", "en-US")
- Date format in NC: `%Y-%m-%dT%H:%M:%S%z2` (colon in timezone offset, e.g. +02:00)
- DTD validation recommended for all documents

## 3. Network Identity

### Sandvik (Buyer)

| Credential | Value | Used in |
|------------|-------|---------|
| NetworkID (Test) | AN11167947425-T | From in PO, To in OC/ASN/Invoice |
| NetworkID (Prod) | AN11167947425 | Production identifier |
| SystemID | S4QCLNT400 or S5QCLNT400 | From in PO (additional Credential) |
| AribaNetworkUserId | <sysadmin@ariba.com> | Sender in PO |

### Tekpal (Supplier)

| Credential | Value | Used in |
|------------|-------|---------|
| NetworkID | AN01000000001-T | Sender identity in OC/ASN/Invoice |
| NetworkID (From) | AN10000000001-T | From in OC/ASN/Invoice (per Sandvik guidelines) |
| NetworkID (alt) | AN11236273760-T | To in PO (order-sales-xml-convert) |
| UserAgent | SCMSupplier | OC/ASN/Invoice responses |

### Header patterns

**PO (IN from Sandvik):**

```xml
<From>
	<Credential domain="NetworkID"><Identity>AN11167947425-T</Identity></Credential>
	<Credential domain="SystemID"><Identity>S4QCLNT400</Identity></Credential>
</From>
<To>
	<Credential domain="NetworkID"><Identity>AN11236273760-T</Identity></Credential>
</To>
<Sender>
	<Credential domain="AribaNetworkUserId"><Identity>sysadmin@ariba.com</Identity></Credential>
	<UserAgent>Ariba Supplier</UserAgent>
</Sender>
```

**OC/ASN/Invoice (OUT to Sandvik):**

```xml
<From>
	<Credential domain="NetworkID"><Identity>AN10000000001-T</Identity></Credential>
</From>
<To>
	<Credential domain="NetworkID"><Identity>AN11167947425-T</Identity></Credential>
</To>
<Sender>
	<Credential domain="NetworkID"><Identity>AN01000000001-T</Identity></Credential>
	<UserAgent>SCMSupplier</UserAgent>
</Sender>
```

## 4. Document Types

### 4.1 Purchase Order (OrderRequest) — IN

DTD: cXML.dtd
Convert: `order-sales-xml-convert.json` (1162 lines)

```text
cXML @payloadID @timestamp @version @xml:lang
  Header
  Request @deploymentMode
    OrderRequest
      OrderRequestHeader @type="new"|"update" @orderDate @orderID @orderType @orderVersion
        Total > Money @currency
        ShipTo > Address @addressID @isoCountryCode
        BillTo > Address
        BusinessPartner @role="soldTo"
        LegalEntity > IdReference @domain="CompanyCode"
        OrganizationalUnit > IdReference @domain="PurchasingOrganization"|"PurchasingGroup"
        PaymentTerm @payInNumberOfDays
        Contact @role="supplierCorporate"|"purchasingAgent"
        Comments
        TermsOfDelivery
        OrderRequestHeaderIndustry > ExternalDocumentType
        Extrinsic: CompanyCode, PurchaseGroup, PurchaseOrganization, buyerVatID, supplierVatID
      ItemOut @quantity @requestedDeliveryDate @lineNumber [field=ordr]
        ItemID > SupplierPartID, BuyerPartID
        ItemDetail > UnitPrice, Description, UnitOfMeasure, PriceBasisQuantity, Classification
        ControlKeys > OCInstruction, ASNInstruction, InvoiceInstruction
        ScheduleLine @quantity @requestedDeliveryDate
        ItemOutIndustry > BatchInfo @requiresBatch
```

Key PO fields:

| cXML | NC field | Notes |
|------|----------|-------|
| OrderRequestHeader/@orderID | ord.company_reference_number | SAP PO number (4500035561) |
| ItemOut/@lineNumber | ordr.json_data.customer_row_number | SAP line number (00010, 00020) |
| BuyerPartID | ordr.product_id | Sandvik part number |
| SupplierPartID | ordr.json_data.supplier_product_code | Supplier part number |
| OCInstruction @value="allowed" | ordr.json_data.confirmation_instruction | OC is required for this PO |
| InvoiceInstruction @value="isNotERS" | ordr.json_data.invoice_instruction | Standalone invoice, not ERS |
| BatchInfo @requiresBatch="yes" | ordr.json_data.requires_batch | Batch tracking required |

PO types:

- `type="new"` — new purchase order
- `type="update"` — PO change (orderVersion incremented)

### 4.2 Order Confirmation (ConfirmationRequest) — OUT

DTD: Fulfill.dtd
Convert: `order-confirmation-xml-convert.json` (250 lines)

**Sandvik-specific** format — differs significantly from generic cXML examples.

```text
cXML @payloadID @timestamp @xml:lang
  Header (Supplier → Sandvik)
  Request @deploymentMode
    ConfirmationRequest
      ConfirmationHeader @noticeDate @type="detail" @operation="new"|"update" @confirmID
      OrderReference @orderID > DocumentReference @payloadID="" (from_static)
      ConfirmationItem @lineNumber @quantity [field=ordr]
        UnitOfMeasure
        ConfirmationStatus @deliveryDate @type="accept" @quantity
          UnitOfMeasure
          ItemIn @lineNumber @quantity
            ItemID > SupplierPartID, SupplierPartAuxiliaryID, BuyerPartID
            ItemDetail
              UnitPrice > Money @currency
              Description @xml:lang
              UnitOfMeasure
              PriceBasisQuantity @conversionFactor="1" @quantity="1" > UnitOfMeasure
              Classification @domain="ERPCommodityCode" (attribute_match)
              Classification @domain="ERPCommodityCodeDescription" (attribute_match)
```

Static export values use `from_static` (not `text` which is import example only):

| Element | `from_static` value | Notes |
|---------|---------------------|-------|
| Header/From/Identity | `AN10000000001-T` | Supplier ANID (per Sandvik guidelines) |
| Header/To/Identity | `AN11167947425-T` | Sandvik ANID |
| Header/Sender/Identity | `AN01000000001-T` | Supplier sender ANID |
| Header/Sender/UserAgent | `SCMSupplier` | |
| DocumentReference | `""` | Required by DTD, must have `from_static` to generate |
| PriceBasisQuantity @quantity | `1` | Static per Sandvik spec |
| SupplierPartAuxiliaryID | `""` (empty) | |

Same-tag elements with `attribute_match`:

| Tag | `attribute_match` | Why |
|-----|-------------------|-----|
| Classification | `{ "domain": "ERPCommodityCode" }` | Distinguish two Classification elements |
| Classification | `{ "domain": "ERPCommodityCodeDescription" }` | Distinguish two Classification elements |

Sandvik differences from generic cXML:

| Feature | Sandvik | Generic cXML |
|---------|---------|-------------|
| Header type | always "detail" | "accept", "reject", "detail" |
| Date attribute | deliveryDate | shipmentDate |
| ItemIn/ItemDetail | full detail inside ConfirmationStatus | simple UnitPrice only |
| PriceBasisQuantity | required with conversionFactor | optional |
| Classification | ERPCommodityCode + Description | not used |
| Multiple status per item | supported (split deliveries) | single status |
| SupplierPartID | can be empty | usually populated |

ConfirmationStatus types per line:

- `accept` — confirm quantity and date
- `reject` — reject line
- `backordered` — partial, rest backordered

Split deliveries: One ConfirmationItem can have multiple ConfirmationStatus elements with different deliveryDate and quantity (e.g. 5 on Dec 13, 5 on Jan 31).

#### OC new vs update — full document always

Every ConfirmationRequest sends **all lines with full detail**. There is no delta mode — SAP Ariba compares the OC against the PO to detect deviations automatically.

The only difference between a new OC and an update OC is the `operation` attribute:

| What | `operation="new"` | `operation="update"` |
|------|-------------------|----------------------|
| When | First confirmation | Changed delivery date, price or quantity |
| `confirmID` | New ID (e.g. "OC1") | Same ID as original OC |
| Lines sent | All lines | All lines (unchanged lines too) |
| Structure | Full ItemIn/ItemDetail | Full ItemIn/ItemDetail (identical) |

Example — PO line 10 deliveryDate changed from Dec 13 to Jan 15:

```text
NEW:   ConfirmationHeader operation="new"    confirmID="OC1"
       Line 10: deliveryDate="2024-12-13" qty=10 price=11266.36
       Line 20: deliveryDate="2024-12-13" qty=5  price=100.00  + deliveryDate="2025-01-31" qty=5
       Line 30: deliveryDate="2024-12-13" qty=10 price=11266.36

UPDATE: ConfirmationHeader operation="update" confirmID="OC1"  ← same ID
       Line 10: deliveryDate="2025-01-15" qty=10 price=11266.36  ← changed date
       Line 20: deliveryDate="2024-12-13" qty=5  price=100.00  + deliveryDate="2025-01-31" qty=5  ← resent unchanged
       Line 30: deliveryDate="2024-12-13" qty=10 price=11266.36  ← resent unchanged
```

**Implication**: The convert JSON generates the same full XML every time. The `operation` value is the only variable — controlled from NC order data (e.g. first export = "new", subsequent export = "update").

### 4.3 Ship Notice / ASN (ShipNoticeRequest) — OUT

DTD: Fulfill.dtd
Convert: `order-ship_notice-xml-convert.json` (160 lines)

```text
cXML @payloadID @timestamp
  Header (Supplier → Sandvik)
  Request
    ShipNoticeRequest
      ShipNoticeHeader @shipmentID @noticeDate @shipmentDate @operation="new"
      ShipControl
        CarrierIdentifier @domain="SCAC" [field=ord.json_data.carrier_code]
        ShipmentIdentifier [field=ord.json_data.tracking_number]
      ShipNoticePortion
        OrderReference @orderID
        ShipNoticeItem @quantity @lineNumber [field=ordr]
          ItemID > SupplierPartID
          UnitOfMeasure
```

ASN specifics:

- `shipmentID` — unique shipment identifier
- `ShipControl` — carrier and tracking info
- One `ShipNoticePortion` per PO reference
- Operations: "new", "update", "delete"

### 4.4 Invoice (InvoiceDetailRequest) — OUT

DTD: InvoiceDetail.dtd
Convert: `order-invoice-xml-convert.json` (311 lines)

No Sandvik invoice example available — follows standard cXML InvoiceDetail.dtd with Sandvik header pattern.

```text
cXML @payloadID @timestamp @version @xml:lang
  Header (Supplier → Sandvik)
  Request @deploymentMode
    InvoiceDetailRequest
      InvoiceDetailRequestHeader @purpose @operation @invoiceDate @invoiceID
      InvoiceDetailOrder
        InvoiceDetailOrderInfo > OrderReference @orderID > DocumentReference
        InvoiceDetailItem @quantity @invoiceLineNumber [field=ordr]
          UnitOfMeasure
          UnitPrice > Money @currency
          PriceBasisQuantity @conversionFactor @quantity > UnitOfMeasure
          ItemID > SupplierPartID, BuyerPartID
          Description @xml:lang
          SubtotalAmount > Money @currency
      InvoiceDetailSummary
        SubtotalAmount > Money
        Tax > Money, TaxDetail @category="NN" @purpose="tax" > TaxableAmount, TaxAmount
        GrossAmount > Money
        NetAmount > Money
        DueAmount > Money
```

Invoice purposes:

- `standard` — normal invoice
- `creditMemo` — credit note
- `debitMemo` — debit note

### 4.5 Goods Receipt (ReceiptRequest) — IN (future)

DTD: Fulfill.dtd
Reference: `Sandvik-example-cXML/goods-receipt/goods-receipt.xml`

```text
ReceiptRequest
  ReceiptRequestHeader @receiptDate @operation="new" @receiptID
  ReceiptOrder
    ReceiptOrderInfo > OrderIDInfo @orderID @orderDate
    ReceiptItem @quantity @receiptLineNumber @type="received"
      ReceiptItemReference @lineNumber > ItemID, ShipNoticeIDInfo
      UnitRate > Money, UnitOfMeasure
      ReceivedAmount > Money
```

Sent by Sandvik after receiving goods. References original PO and ASN. May need separate convert file.

### 4.6 OC Approval (ApprovalRequest) — IN (future)

DTD: cXML.dtd
Reference: `Sandvik-example-cXML/oc-approval/oc-approval.xml`

```text
ApprovalRequest
  ApprovalRequestHeader @creationDate @type="orderConfirmation"
    DocumentReference @payloadID
    cXMLAttachment > Attachment > URL
    Extrinsic @name="Ariba.orderPayloadId"
  AcceptanceItem @lineNumber @acceptanceStatus="approved"|"rejected"
```

Sent by Sandvik after reviewing supplier's OC. Contains approval/rejection per line.

### 4.7 PO Change (OrderRequest type="update") — IN

Same structure as PO but `type="update"` and `orderVersion` incremented. Handled by existing `order-sales-xml-convert.json`.

## 5. NC Data Model

### 5.1 Order Fields (table/external/cxml/order/order.json)

Regular fields used by cXML convert files:

| Field | Type | cXML Mapping | Direction |
|-------|------|-------------|-----------|
| order_id | varchar(40) | InvoiceDetailRequestHeader/@invoiceID | OUT |
| company_id | varchar(40) | BillTo/Address/@addressID | IN |
| company_reference_number | varchar(80) | OrderReference/@orderID | IN/OUT |
| confirmation_date | date | ConfirmationHeader/@noticeDate | OUT |
| currency_id | varchar(20) | Money/@currency | IN/OUT |
| info | text | Comments | IN |
| order_date | date | OrderRequestHeader/@orderDate, ShipNoticeHeader/@noticeDate | IN/OUT |
| price_with_vat | double | GrossAmount, DueAmount | OUT |
| price_without_vat | double | SubtotalAmount, NetAmount | OUT |
| term_of_delivery | varchar(120) | TransportTerms | IN |
| terms_of_payment_id | varchar(40) | PaymentTerm/@payInNumberOfDays | IN |
| vat_price | double | Tax/Money | OUT |
| way_of_delivery | varchar(120) | ShippingPaymentMethod | IN |
| your_reference | varchar(40) | Contact role="purchasingAgent" | IN |

Extra fields (`json_data`) in order.json:

| Field | Type | cXML Mapping | Direction |
|-------|------|-------------|-----------|
| json_data.mark | text | Comments (index field) | IN |
| json_data.confirm_id | text | ConfirmationHeader/@confirmID | OUT |
| json_data.order_action | text | OrderRequestHeader/@type | IN |
| json_data.order_type | text | OrderRequestHeader/@orderType | IN |
| json_data.order_version | integer | OrderRequestHeader/@orderVersion | IN |
| json_data.company_code | text | Extrinsic CompanyCode | IN |
| json_data.purchase_group | text | Extrinsic PurchaseGroup, OrganizationalUnit PurchasingGroup | IN |
| json_data.purchase_organization | text | Extrinsic PurchaseOrganization, OrganizationalUnit PurchasingOrganization | IN |
| json_data.ship_to_code | text | ShipTo/Address/@addressID | IN |
| json_data.storage_location_id | text | IdReference domain="stockLocationID" | IN |
| json_data.invoicing_allowed | text | Extrinsic Ariba.invoicingAllowed | IN |
| json_data.party_additional_id | text | Extrinsic partyAdditionalID | IN |
| json_data.payment_term_description | text | Extrinsic AribaNetwork.PaymentTermsExplanation | IN |
| json_data.buyer_email | text | Contact role="purchasingAgent" Email | IN |
| json_data.buyer_name | text | OrganizationalUnit PurchasingGroup Description | IN |
| json_data.document_type | text | ExternalDocumentType/@documentType | IN |
| json_data.shipping_date | date | ShipNoticeHeader/@shipmentDate | OUT |
| json_data.shipment_id | text | ShipNoticeHeader/@shipmentID | OUT |
| json_data.carrier_code | text | CarrierIdentifier domain="SCAC" | OUT |
| json_data.tracking_number | text | ShipmentIdentifier | OUT |

`json_data.confirm_operation` is referenced as a column in `column/order-sales.json` but is **not set** by any convert file.

### 5.2 Company Fields (table/local/company/company.json) — via `co.` prefix

Accessed through `ord.company_id` → `co.` prefix in convert files.

| Field | Type | cXML Mapping | Direction |
|-------|------|-------------|-----------|
| co.json_data.vat_number | varchar(50) | Extrinsic buyerVatID | IN |

### 5.3 Order Row Fields (table/external/cxml/order/order_row.json)

Regular fields used by cXML convert files:

| Field | Type | cXML Mapping | Direction |
|-------|------|-------------|-----------|
| product_id | varchar(80) | BuyerPartID (IN), SupplierPartID (OC/ASN/Invoice OUT) | IN/OUT |
| delivery_amount | double | ShipNoticeItem/@quantity, InvoiceDetailItem/@quantity | OUT |
| delivery_date | date | ConfirmationStatus/@deliveryDate | IN/OUT |
| description | text | Description | IN/OUT |
| order_amount | double | ItemOut/@quantity, ConfirmationItem/@quantity | IN/OUT |
| unit | varchar(20) | UnitOfMeasure | IN/OUT |
| unit_price | double | UnitPrice/Money | IN/OUT |
| vat_price | double | Tax/Money | IN/OUT |
| price_without_vat | double | SubtotalAmount/Money | OUT |

Extra fields (`json_data`) in order_row.json:

| Field | Type | cXML Mapping | Direction |
|-------|------|-------------|-----------|
| json_data.customer_row_number | varchar(20) | ItemOut/@lineNumber, ConfirmationItem/@lineNumber, ShipNoticeItem/@lineNumber, InvoiceDetailItem/@invoiceLineNumber | IN/OUT |
| json_data.supplier_product_code | varchar(100) | BuyerPartID (OC/Invoice OUT), SupplierPartID (IN) | IN/OUT |
| json_data.conversion_factor | double | PriceBasisQuantity/@conversionFactor | IN |
| json_data.commodity_code | text | Classification domain="ERPCommodityCode" | IN |
| json_data.commodity_description | text | Classification domain="ERPCommodityCodeDescription" | IN |
| json_data.planned_acceptance_days | integer | PlannedAcceptanceDays | IN |
| json_data.receiving_type | integer | Extrinsic ReceivingType | IN |
| json_data.tax_category | text | TaxDetail/@category | IN |
| json_data.buyer_planner_code | text | Contact role="BuyerPlannerCode" IdReference | IN |
| json_data.buyer_planner_name | text | Contact role="BuyerPlannerCode" Name | IN |
| json_data.confirmation_instruction | text | ControlKeys/OCInstruction/@value | IN |
| json_data.ship_notice_instruction | text | ControlKeys/ASNInstruction/@value | IN |
| json_data.invoice_instruction | text | ControlKeys/InvoiceInstruction/@value | IN |
| json_data.schedule_line_number | text | ScheduleLine/@lineNumber | IN |
| json_data.requires_batch | text | BatchInfo/@requiresBatch | IN |

#### Control Keys — stored from PO import

Every PO line has `<ControlKeys>` that determine what the supplier must send back:

```text
PO line ControlKeys:
  OCInstruction @value="allowed"|"notAllowed"
  ASNInstruction @value="allowed"|"notAllowed"
  InvoiceInstruction @value="isNotERS"|"isERS"|"notAllowed"
```

Our real POs (4500038285) show: `OC=allowed, ASN=notAllowed, Invoice=isNotERS`.
This means: **must send OC, do NOT send ASN, invoice is standalone (not ERS)**.

The `order-sales-xml-convert.json` stores these as **field mappings** to `ordr.json_data.*` so each PO line's requirements are preserved.

### 5.4 Field definitions

All cXML-specific `json_data` extra_fields are defined in:

- Order: `nc-plugin/preference/table/external/cxml/order/order.json` → `extra_field` (20 fields)
- Order row: `nc-plugin/preference/table/external/cxml/order/order_row.json` → `extra_field` (15 fields)

## 6. Convert Files

All in `save/` directory. Each convert JSON handles both IN and OUT directions.

### All convert files

| File | Document | Lines | Direction |
|------|----------|-------|-----------|
| `order-sales-xml-convert.json` | OrderRequest | 1162 | IN |
| `order-confirmation-xml-convert.json` | ConfirmationRequest | 250 | OUT |
| `order-ship_notice-xml-convert.json` | ShipNoticeRequest | 160 | OUT |
| `order-invoice-xml-convert.json` | InvoiceDetailRequest | 312 | OUT |

### Convert JSON format

```json
{
	"name": "path/to/file.json",
	"line_break": "\n",
	"indent": "\t",
	"main_data_tag": "ord",
	"charset": "UTF-8",
	"decimal_separator": ".",
	"create_empty_tag": true,
	"xml_header": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
	"namespace": "",
	"convert": [{ "field": "ord", "tag": "cXML", ... }]
}
```

Key patterns:

- `main_data_tag: "ord"` — order table
- `"field": "ordr"` — repeating order row element
- `"data_type": "date"` with `"format": "%Y-%m-%dT%H:%M:%S%z2"` — date fields
- `"text": "value"` — import example value (not used in export)
- `"from_static": "value"` — static value for export output
- `"field": "ord.field_name"` — dynamic field reference
- `"organization_id": { "4d": { "field": "..." } }` — per-organization field override
- `"create_empty_tag": true` — output empty XML tags

## 7. XSL Preview Files

XSLT stylesheets that render cXML documents as HTML for preview/display. Located in `xml/` directory.

| File | Document | Lines | Purpose |
|------|----------|-------|---------|
| `order-sales.xsl` | Purchase Order (IN) | 271 | Renders OrderRequest as readable HTML |
| `order-confirmation.xsl` | Order Confirmation (OUT) | 190 | Renders ConfirmationRequest as readable HTML |

Both use XSLT 1.0 with shared visual style:

- System font stack (-apple-system, Segoe UI, Roboto, sans-serif), 14px base size
- 3-column header grid with bordered boxes (Order, Ship To, Bill To / Confirmation, Order Ref, Network)
- Line items table with dark header, alternating row backgrounds
- Badge components for operation type (`NEW` green, `UPDATE` yellow) and status (`ACCEPT`, `REJECT`, `BACKORDER`)
- Right-aligned numeric columns (Qty, Price, Line Total)
- Footer with totals and payload/timestamp metadata
- `domainLabel` template to translate IdReference domain codes to human-readable labels

### order-sales.xsl — PO preview

Displays incoming Purchase Order with:

- **Header**: Order ID, Date, Type, Action, Version, Ship To (address + IdReference), Bill To, Network credentials
- **Organization**: Company Code, Purchasing Org, Agent (purchasingAgent name + email), Purchasing Group
- **Delivery**: TermsOfDeliveryCode, ShippingPaymentMethod, TransportTerms
- **Extra**: Payment terms, VAT IDs, invoicing allowed, available amount, document type
- **Line items**: Line number, extLineNumber, BuyerPartID, SupplierPartID, description, qty, unit, delivery date, unit price, line total, tax (category + %), control keys (OC/ASN/Invoice/Batch), commodity code, planner info, schedule line
- **Totals**: Subtotal + Tax = Grand Total

### order-confirmation.xsl — OC preview

Displays outgoing Order Confirmation with:

- **Header**: Notice date, Type, Operation (NEW/UPDATE badge), Confirm ID, deployment mode
- **Order reference**: Order ID, Payload ID, IdReference fields
- **Line items**: Line number, Status (ACCEPT/REJECT/BACKORDER badge), qty, unit, delivery date, SupplierPartID, BuyerPartID, description, unit price, line total, commodity code
- **Totals**: Recursive `calcTotal` template sums qty × unit price across all ConfirmationStatus elements

## 8. Units of Measure

| cXML Code | Description |
|-----------|-------------|
| EA | Each (pieces) |
| PCE | Pieces (alternative) |
| PK | Pack |

Reference: <https://help.sap.com/docs/business-network-for-procurement/catalog-format-reference/units-of-measure-for-catalog-items>

## 9. Document Flow

```mermaid
graph LR
    S4[SAP S/4HANA] -->|OrderRequest| BN[SAP Business Network]
    BN -->|cXML HTTPS| MG[Managed Gateway]
    MG -->|POST| NC[NC cxml worker]
    NC -->|import| ORD[NC Sales Order]
    ORD -->|export OC| MG
    ORD -->|export ASN| MG
    ORD -->|export Invoice| MG
    MG -->|forward| BN
    BN -->|cXML| S4
    S4 -->|Goods Receipt| BN
    S4 -->|OC Approval| BN
    BN -->|ReceiptRequest/ApprovalRequest| MG
```

## 10. Test Environment

- SAP Business Network test account active (Oct 2025)
- Managed Gateway activated, integration project created
- Test and Production project connections created
- `deploymentMode="test"` in all documents
- SAP IP whitelist: 155.56.128.0/17, 130.214.160.64/28, 130.214.160.80/29
- Document Validator Tool available in Managed Gateway
- Transaction Tracker available for document status monitoring

### Contacts

| Role | Name | Email |
|------|------|-------|
| SAP Ariba Seller Integrator | Lale Agajeva | <lale.agajeva@sap.com> |
| Sandvik Buyer | Fionna Langan | <fionna.langan@sandvik.com> |
| Sandvik IT | Vipin K S | <vipin.k_s@sandvik.com> |
| Tekpal | Lari Kytölä | <lari.kytola@suomentekniikkapalvelu.fi> |

## 11. Example Files

### Sandvik-specific (primary reference)

Path: `Sandvik-SAP-edi/Sandvik-example-cXML/`

| File | Type | Description |
|------|------|-------------|
| oc-update/oc-update-new.xml | ConfirmationRequest | 3 lines, split delivery on line 20 |
| oc-update/oc-update-update.xml | ConfirmationRequest | Update with changed dates |
| po-oc/po-oc-confirmation.xml | ConfirmationRequest | With IdReference supplierReference |
| asn/asn.xml | ShipNoticeRequest | Basic ASN with contacts |
| asn/asn-update.xml | ShipNoticeRequest | Update with 3 items |
| asn/asn-summary.xml | ShipNoticeRequest | 2 ShipNoticePortion (2 orders) |
| asn/asn-cancel.xml | ShipNoticeRequest | Delete operation |
| goods-receipt/goods-receipt.xml | ReceiptRequest | Goods receipt from Sandvik |
| oc-approval/oc-approval.xml | ApprovalRequest | OC approval |
| po/po.xml | OrderRequest | Standard PO with 2 items |
| po/po-example1.xml | OrderRequest | PO 4500035561 (test file) |
| po/po-change-update.xml | OrderRequest | PO update (orderVersion=2) |
| po-oc/po-oc.xml | OrderRequest | PO with OCInstruction="allowed" |

### Generic cXML (secondary reference)

Path: `Sandvik-SAP-edi/docs/Fulfill/Examples/`

| File | Description |
|------|-------------|
| 1a-ConfirmationRequest.xml | Simple accept |
| 1b-ConfirmationRequest.xml | Update with exception |
| 2-ConfirmationRequest.xml | Detailed accept/reject per line |
| 3-ConfirmationRequest.xml | Reject all |
| 4-ShipNoticeRequest.xml | Ship notice |

## 12. DTD Versions

| Version | Seen in |
|---------|---------|
| 1.2.060 | PO examples |
| 1.2.061 | po-oc-confirmation.xml, goods-receipt.xml, OC Fulfill.dtd |
| 1.2.063 | oc-update-new.xml, asn.xml, asn-summary.xml |
| 1.2.064 | oc-update-update.xml, asn-update.xml, oc-approval.xml |
| 1.2.066 | order-sales-xml-convert.json |
| 1.2.067 | Real test POs (4500038277-4500038288) |

DTD URL pattern: `http://xml.cxml.org/schemas/cXML/{version}/{DTD}`

| DTD | Documents |
|-----|-----------|
| cXML.dtd | Orders, OC Approval |
| Fulfill.dtd | OC, ASN, Goods Receipt |
| InvoiceDetail.dtd | Invoices |

## 13. Sandvik Business Rules

Source: Sandvik Seller Integration Guidelines v1.6 (25.2.2025) — Direct Materials, Supply Chain Collaboration

### 12.1 Scope

- **Entity**: Sandvik Mining & Construction Logistics Ltd — Parts and Services division
- **Prod ANID**: AN11167947425
- **Test ANID**: AN11167947425-T
- **PO number range**: starts with 45, Document type: NB
- **Invoice**: WIP — later deployment, no defined timeline

### 12.2 PO Types

| PO Type | Supported |
|---------|-----------|
| New Direct PO | Yes |
| Change Direct PO | Yes — offline alignment with supplier |
| Cancel Direct PO | Yes |
| PO with attachments | No |
| Free text (lines) Direct PO | No |
| Mixed items PO | No |
| Shipping on line level | No |
| Service PO | No |
| Legacy Orders (Cut-Over) | Yes |
| Blanket POs | No |
| Drop-ship POs | No |

### 12.3 Control Keys

Sandvik has three Confirmation Control Keys:

| Key | Description | ASN |
|-----|-------------|-----|
| Z001 | Inbound from ASN Receipt | Required |
| Z002 | Inbound without ASN | Not required |
| Z003 | No inbound | Not required |

**Integrated suppliers always get Z001** — ASN is mandatory for all POs.

Control keys override transaction rules for the same topic.

### 12.4 Tolerances

**Order Confirmation tolerances** (Supplier Group transaction rules):

- Date (upper/lower): 999 days
- Quantity over delivery (upper): 0%
- Quantity under delivery (upper): 99%

**OC Price tolerances** (from PO ControlKeys):

```xml
<OCInstruction value="allowed">
  <Lower><Tolerances><PriceTolerance><Percentage percent="0.00"/></PriceTolerance></Tolerances></Lower>
  <Upper><Tolerances><PriceTolerance><Percentage percent="0.00"/></PriceTolerance></Tolerances></Upper>
</OCInstruction>
```

Price tolerance = 0.00% — any price deviation triggers approval flow.

**ASN tolerances** (Supplier Group transaction rules):

- Quantity over delivery: 0%
- Quantity under delivery: 99%
- Date: 999 days

**ASN tolerances** (from PO):

```xml
<ASNInstruction value="allowed">
  <Lower><Tolerances><TimeTolerance limit="999" type="days"/></Tolerances></Lower>
  <Upper><Tolerances><TimeTolerance limit="999" type="days"/></Tolerances></Upper>
</ASNInstruction>
```

### 12.5 Order Confirmation Rules

- **Line-level confirmation mandatory** — Sandvik PO line reference required
- **Full PO rejection**: NOT allowed — must contact Sandvik buyer
- **PO line-item rejection**: NOT allowed — must contact Sandvik buyer
- **Changes/deviations allowed for**: delivery date, price, quantity
- **Partial confirmation** (subset of lines): Yes
- **Split quantity** (e.g. qty 5 of 10): Yes

| OC Attribute | Required | Notes |
|-------------|----------|-------|
| Unit price | Yes | |
| Unit of measure | Yes | Must match PO UoM — cannot change |
| Delivery date | Yes | Mandatory, may differ from PO |
| Estimated shipping date | No | |
| Comments | No | |
| Sandvik Part ID (BuyerPartID) | Yes | Must match PO |
| Supplier/Buyer Part ID changes | Not allowed | |
| Attachments | No | |
| Price corrections | Yes | Within tolerance only |

### 12.6 OC Approval Flow

All deviations outside tolerance trigger the Approval Flow.

| OC Scenario | Triggers Approval? | Approval sent to supplier? | Impact on PO? |
|-------------|-------------------|---------------------------|---------------|
| Full acceptance as-is | No | No | No |
| Qty/date deviations within tolerance | No | No | No |
| Qty/price deviations outside tolerance | **Yes** | Qty over delivery only | Yes — change PO (version 2) or PO cancel |
| Full rejection | Not in scope | Not in scope | Not in scope |

Key behaviors:

- Quantity over delivery deviations: Sandvik buyer approves in Supply Chain Monitor → S/4HANA issues change PO (version 2)
- Price deviations: NOT approved in SAP Business Network — Sandvik Sourcing reviews, issues PO change if accepted, or PO cancel if rejected
- Supplier can set up email notifications for approval status changes: Account → Settings → Network Notifications → Approval
- Price deviations rejected → PO cancelation after offline agreement

### 12.7 Ship Notice (ASN) Rules

ASN is **mandatory** for integrated suppliers (Z001 control key).

| ASN Type | Supported |
|----------|-----------|
| Full ASN | Yes |
| Partial ASN (subset of PO lines) | Yes |
| Partial ASN (partial quantity of line) | Yes |
| Summary ASN (multiple POs) | Yes |
| Multiple materials in one ASN | Yes |
| Change ASN | Yes |
| Cancel ASN | Yes |

| ASN Field | Required | Notes |
|-----------|----------|-------|
| Ship Notice ID | Mandatory | |
| Ship Notice Date | Mandatory | |
| Delivery date | Mandatory | May differ from PO within tolerance |
| Ship From / Ship To | Mandatory | |
| Actual shipping date | Optional | |
| PO reference number | Mandatory | |
| PO line reference number | Mandatory | |
| Carrier Name | Optional | |
| Unit of Measure | Mandatory | Must match PO UoM |
| Unit price | Optional | |
| Sandvik Part ID | Mandatory | Must match PO |
| Comments | Not required | |
| Batch IDs | No | |
| Handling units | No | |
| Serial number | No | |
| Country of origin | No | |
| Supplier lot ID | No | |

### 12.8 Goods Receipt

Sandvik issues Goods Receipts to suppliers. Suppliers can access GRs on the portal and set up notifications.

### 12.9 PO Criticality

PO header Comments may contain criticality:

- Normal
- Rush
- Emergency

Example: `<Comments xml:lang="EN">Criticality: Emergency</Comments>`

### 12.10 UoM Standard

All transactions must use **United Nations Units of Measure (UNUOM)** codes.
Reference: <http://www.unece.org/cefact/codesfortrade/codes_index.html>
Example: PCE for pieces (not EA). Align with Sandvik contact for master data UoM per material.

## 14. Implementation Notes

- All Sandvik POs have `OCInstruction value="allowed"` — OC is always required
- `InvoiceInstruction value="isNotERS"` — standalone invoice, not Evaluated Receipt Settlement
- `BatchInfo requiresBatch="yes"` — batch tracking required on all items
- `SupplierPartID` can be empty when supplier uses Sandvik's BuyerPartID
- Split deliveries: multiple ConfirmationStatus per ConfirmationItem
- ASN `shipNoticeLineNumber` is sequential across all ShipNoticePortion elements
- Tax category "NN" used in Sandvik examples (zero-rated/no tax)
- Date format uses `%z2` for colon in timezone offset (e.g. +02:00 not +0200)
- PO routing: New PO → Managed Gateway, Change PO → email (to be changed)
- Invoice is WIP — no current timeline for Sandvik deployment
- `json_data.confirm_operation` is referenced as a display column in `column/order-sales.json` but is **not set** by any convert file — dead reference
- `from_static` must be used instead of `text` for static values in export convert files — `text` is import example only
- `BuyerPartID` export uses `ordr.json_data.supplier_product_code` (original SAP part code preserved from import)
- `SupplierPartID` export uses `ordr.product_id` (our product code, set after import)
- `ConfirmationHeader` contains `IdReference domain="supplierReference"` per Sandvik guidelines
- `PriceBasisQuantity @quantity` is static `"1"` (not order quantity) per Sandvik sample
- `Classification` fields use `field` for dynamic value + `from_static` for fallback
- `DocumentReference` needs `from_static: ""` to force generation (DTD requires it)
- `Classification` elements need `attribute_match` to distinguish same-tag elements during import validation
- `ConfirmationItem` attributes (`lineNumber`, `quantity`) show "error, field does not exist" if `ordr.json_data.customer_row_number` or `ordr.order_amount` are not populated in data — re-import PO after column definitions are updated
- `ConfirmationHeader @noticeDate` shows 1970 date if `ord.confirmation_date` is not set — must be populated before export
