تخطَّ إلى المحتوى

Go-live checklist

هذا المحتوى غير متوفر بلغتك بعد.

Run this before an integration goes live. It proves the six things that break real integrations: the first load, an incremental run, a re-run that books nothing twice, a lost answer, a rejection and its re-send, and recovery after the cursor is lost.

Each step says what to do in the ERP extension and which call proves it. The calls are written with curl so the same checks can be made by hand; your extension makes them itself.

Terminal window
SLFLO_BASE=https://api.slflo.com/api/integration/v1 # or your own copy's URL
SLFLO_TOKEN=sfi_live_… # your sandbox token
auth=(-H "Authorization: Bearer $SLFLO_TOKEN")

These run in bash or zsh. -g stops curl from reading the brackets in filter[…] as a glob.

Terminal window
curl -s "${auth[@]}" "$SLFLO_BASE/me"

Pass: data.company.code is the sandbox company (t-sandbox, or the code we sent with the token), data.scopes holds every scope your jobs use, and data.client.token_prefix starts sfi_live_ (sfi_test_ on a copy of Slflo you run yourself). Note rate_limit_per_minute and set the extension’s throttle just under it.

Terminal window
curl -s "$SLFLO_BASE/openapi.json" | head -c 200 # public, no token

Pass: the document loads; your contracts match components.schemas (OrderDto, InvoiceDto, PaymentDto, ReturnDto) — see the API reference.

The sandbox company is in ERP mode API, which order acks need (otherwise 409 ERP_MODE_NOT_API, see ERP mode). Its day’s two orders are already delivered (an ack on them answers 409 ORDER_NOT_AWAITING_ACK), so step 1 acks its invoices, payments and return; the first order ack comes with step 2’s new order.

In the extension: clear staging, set every watermark to 0, run the pull job for orders, invoices, payments and returns, then the booking job.

Terminal window
v=0
while :; do
page=$(curl -sg "${auth[@]}" "$SLFLO_BASE/orders?filter[version_after]=$v&per_page=100")
echo "$page" | jq -r '.data[] | "\(.id) \(.version) \(.number)"'
[ "$(echo "$page" | jq .meta.has_more)" = true ] || break
v=$(echo "$page" | jq .meta.next_version_after)
done

Pass:

  • Staging holds every order the loop printed, keyed (id, version, line_number); the watermark equals the last page’s next_version_after.
  • The ERP has one sales order per Slflo order, with CustomersOrderReference = the order’s id, customer taken from customer.external_ref.
  • Every booked order was acked: curl -sg "${auth[@]}" "$SLFLO_BASE/orders?filter[ack]=pending" returns only orders the extension deliberately did not book.
  • The same holds for /invoices, /payments and /returns, each with its own watermark (or its own filter[seq_after] counter, starting from GET /invoices/cursor etc.).

Have a new pre-sale order placed on the sandbox (ask us; on your own copy, book one on the demo phone or in the console), then run the jobs again.

Terminal window
curl -sg "${auth[@]}" "$SLFLO_BASE/orders?filter[version_after]=$v" | jq '.data | map(.id)'

Pass: only the new order (and any order changed since) comes back; one new sales order in the ERP; the watermark moves on.

Run the pull and booking jobs a second time without any change in Slflo, then once more from an older watermark (e.g. half of the current one).

Pass:

  • The re-read rows are rejected by the staging unique index or skipped as already staged.
  • No second sales order, journal or credit note appears in the ERP.
  • Repeating an ack with its original Idempotency-Key answers the first answer again:
Terminal window
curl -si "${auth[@]}" -H "Idempotency-Key: SO-0012345" -H "Content-Type: application/json" \
-d '{"outcome":"ACCEPTED","external_ref":"SO-0012345","external_number":"SO-0012345"}' \
"$SLFLO_BASE/orders/<order id>/ack" | grep -i idempotent-replayed # Idempotent-Replayed: true

Simulate a timeout after the ERP created the sales order but before it stored or sent anything (kill the job, or drop the network, between “create SO” and “ack”).

Run the jobs again. Pass:

  • Find-before-create finds the existing sales order by CustomersOrderReference and does not create a second one.
  • The ack is sent (again) and lands once. Check from Slflo’s side:
Terminal window
curl -s "${auth[@]}" "$SLFLO_BASE/lookup?entity=order&id=<order id>"
# → "external_ref": "SO-0012345", "exists": true
curl -sg "${auth[@]}" "$SLFLO_BASE/orders?filter[external_ref]=SO-0012345" | jq '.data | length' # 1

Do the same for a master-data push: send PUT /products/ITEM-1001 twice with the same body, each time with a new Idempotency-Key (under the same key the second call only replays the first answer). Pass: the second answers "result": "unchanged" and no duplicate product exists (GET /lookup?entity=product&external_ref=ITEM-1001 gives one id).

Make one order fail in the ERP (a customer on hold, say), so the extension acks it:

Terminal window
curl -s "${auth[@]}" -H "Idempotency-Key: reject-<order id>" -H "Content-Type: application/json" \
-d '{"outcome":"REJECTED","reason_code":"CUSTOMER_ON_HOLD","message":"Customer CUST-0042 is on hold in D365"}' \
"$SLFLO_BASE/orders/<order id>/ack"

Pass: the ack answers "status": "ERP_FAILED", and the office sees your message on the order page. Fix the cause in the ERP, then have Send to ERP again pressed in the console (on the hosted sandbox, ask us). Pass:

Terminal window
curl -sg "${auth[@]}" "$SLFLO_BASE/orders?filter[ack]=pending&filter[id]=<order id>" | jq '.data | length' # 1

The next run books it and acks ACCEPTED; GET /lookup?entity=order&id=<order id> shows the SO number.

6. Cursor lost, recovered by filter[ack]=pending

Section titled “6. Cursor lost, recovered by filter[ack]=pending”

Simulate a restored ERP database: purge staging and set the order watermark back to 0 — or, to be harsher, to the current high_water so the cursor skips everything.

Run the extension in its recovery mode (pull filter[ack]=pending instead of the cursor):

Terminal window
curl -sg "${auth[@]}" "$SLFLO_BASE/orders?filter[ack]=pending&per_page=500" | jq '.data | map(.id)'
curl -sg "${auth[@]}" "$SLFLO_BASE/payments?filter[ack]=pending&per_page=500" | jq '.data | map(.id)'

Pass: exactly the documents the ERP has not booked are returned; booking them creates no duplicates (find-before-create); after the run every filter[ack]=pending list is empty, and the normal cursor resumes from high_water.

Also: have a payment reversed or a cheque bounced after it was acked (on the hosted sandbox, ask us). Pass: the payment appears in filter[ack]=pending again with a reversal, and the extension reverses its journal.

Terminal window
curl -s "${auth[@]}" -X PUT -H "Idempotency-Key: ITEM-1001@7" -H "Content-Type: application/json" \
-d '{"name":{"en":"Cola 330ml"},"base_unit_ref":"PCS","active":true,"source_version":"7"}' "$SLFLO_BASE/products/ITEM-1001"
curl -s "${auth[@]}" -X PUT -H "Idempotency-Key: ITEM-1001@6" -H "Content-Type: application/json" \
-d '{"name":{"en":"Cola"},"base_unit_ref":"PCS","active":true,"source_version":"6"}' "$SLFLO_BASE/products/ITEM-1001"

PCS is the sandbox’s piece unit; keys are case-sensitive, so pcs would answer 422 UNKNOWN_REFERENCE until you push PUT /units/pcs.

Pass: the first is created (or updated/unchanged), the second stale and nothing changes. A nightly batch through POST /batch/products with one bad item reports that item ok: false with UNKNOWN_REFERENCE and applies the rest. A full re-run of the nightly job answers unchanged for every item.

  • Make a call fail with 401 while a job runs. On the hosted sandbox, don’t revoke the shared token: switch the job’s setting to a wrong token (401 TOKEN_INVALID); on your own copy you can revoke one in the console (401 TOKEN_REVOKED). Pass: the job stops and alerts, and it does not retry in a loop.
  • Exceed the rate limit (lower the throttle or run two jobs). Pass: on 429 the client waits Retry-After seconds and continues; nothing is marked failed.
  • A 500 INTERNAL (ask the Slflo team to simulate one) is retried with back-off and logged with its request_id.
Step Result Notes / request ids
0. Settings
1. First load
2. Incremental run
3. Re-run
4. Lost answer
5. Rejection and re-send
6. Cursor reset
7. Master data out
8. Failure policy

When every row passes: the company’s administrator issues a token on the production company (Settings → Integrations → New client), you ask us to set its ERP mode to API (see ERP mode), put the token in the ERP settings, and schedule the jobs.