ERP charge sync LIVE

Pull consolidated charges out of OpenIRIS and post them into SAP, Oracle Fusion, NetSuite or Workday as journal entries — with a dedup fence, because your ERP probably doesn't have one.

Scope required: billing:read (covers reading charges and recording exports).

The loop

  1. Pull new/changed charges since your last run.
  2. Transform each charge into your ERP's journal-entry shape (mapping below).
  3. Post it to the ERP.
  4. Acknowledge the export back to OpenIRIS with the ERP document number — this is your dedup fence and flips the charge's status to exported.

1. Pull — delta sync

GET /v1/charges?filter[created_at][gte]=2026-08-01T00:00:00Z&sort=created_at&limit=200

Page with links.next; persist the newest created_at you've seen as the next run's watermark. To pull only what's not yet posted, add filter[status]=invoiced (or unbilled, depending on where your institution draws the "ready" line).

Watermark on created_at for now. filter[updated_at][gte] exists, but historical rows are still being backfilled — until the changelog announces completion, updated_at-based delta sync under-reports. created_at is reliably populated on every row.

A charge

{
  "id": 98888864,
  "source": "booking",
  "source_id": 24722216,
  "booking_id": 3416659,
  "provider_id": 1300,
  "user_id": 20117,
  "group_id": 512,
  "currency": "EUR",
  "total": 215.0511,
  "is_waived": false,
  "invoice_id": 33,
  "status": "invoiced",
  "period_start": "2026-06-30T07:00:00Z",
  "period_end": "2026-06-30T09:00:00Z",
  "created_at": "2026-06-30T09:12:44.153Z",
  "links": { "self": "…/v1/charges/98888864" }
}

2. Transform — SAP Journal Entry mapping

Target Journal Entry Post (API_JOURNALENTRYITEMBASIC) — OpenIRIS charges are internal cost recovery, not supplier invoices. Oracle GL Journal Batch / NetSuite Journal Entry / Workday Manual Journal follow the same shape.

SAP fieldSourceNotes
CompanyCodeyour configMap provider → company code on your side
PostingDate / DocumentDateperiod_end or invoice_dateWatch fiscal-period cutoffs (below)
DocumentReferenceIDcharge id≤35 chars; your dedup key inside SAP
GLAccountyour mapping tableTypically resource/provider → GL on your side
CostCentercost_center.provider_code*≤10 alphanumeric — matches SAP's limit
Amount + currencytotal + currencyDebit user cost center, credit facility revenue GL

* the collapsed cost_center object is part of the proposed contract — until it ships, resolve codes via the cost-centers surface.

SAP has no native duplicate check on Journal Entry Post (SAP KBA 2817042). A retried job posts the same charge twice unless you fence it — that is exactly what step 4 is for.

Fiscal periods: SAP posting periods close monthly. A delayed export can land in a closed period — decide whether late charges post to the original period date or the run date, and be consistent.

3–4. Post, then acknowledge

After the ERP accepts the entry, record the acknowledgement (note the Idempotency-Key — required on all writes):

POST /v1/charges/98888864/exports
Idempotency-Key: 7f3a1c…
Content-Type: application/json

{ "external_system": "SAP", "external_id": "4900001234", "notes": "FY26 P06" }

GET /v1/charges/{id}/exports returns the full history — the audit answer to "where did this charge land, and when?". On your next pull, filter[status]=invoiced naturally excludes everything already exported.

Crash-safe ordering

Post to the ERP before acknowledging. If your job dies between the two, the next run re-posts the charge — and SAP-side dedup on DocumentReferenceID (the charge id you set in step 2) catches it. If you acknowledge first and die, the charge silently never reaches the ERP. Post → acknowledge, always in that order.