Syncing Sales Orders from Xiaoman OKKICRM to KIS Private Cloud: A Single-Strategy Tutorial
What This Strategy Solves
Sales orders are among the most sensitive business documents. In one retail engagement, sales reps enter and approve orders in Xiaoman OKKICRM on the CRM side, while the ERP side (KIS Private Cloud) needs the same orders for inventory pre-allocation and downstream fulfillment. Once encodings, semantics, or time windows drift between the two, finance and warehouse figures stop reconciling within three months. We use the Qeasy data integration platform to consolidate this pipeline into a single observable, replayable, phased sync strategy whose sole job is: sales orders in Xiaoman land in KIS via the target API, scoped by a business time window, with no duplicates and no losses.
Data Flow and Field Mapping
The overall flow is Xiaoman OKKICRM (source) → Qeasy middle layer (orchestration + mapping + idempotency) → KIS Private Cloud (target). The source uses a GET query API, the target uses a POST write API, and authentication is handled by each system's own credentials — the middle layer only orchestrates.
Key field mapping (excerpted from production configuration):
| Dimension | Source (Xiaoman) | Middle Layer | Target (KIS) |
|---|---|---|---|
| Business order no. | order_no (idempotency key) | Pass-through | Object.id (reconciliation key) |
| Line item primary key | order_id | Compose line number | Object.entries[].id |
| Query time window | start_time / end_time | Take LAST_SYNC_TIME / CURRENT_TIME | — |
| Pagination | start_index, count=10 | Loop, aggregate, then dispatch | — |
| Soft-delete filter | removed=0 | Default: only non-deleted; =1 goes to compensation channel | — |
| Write endpoint | — | — | POST /koas/app007099/api/salesorder/create |
| Account-set isolation | — | — | AccountDB carried as a request field |
We centralize encoding mapping: a Xiaoman product/customer code → KIS code mapping table lives in Qeasy's Mapping Center. At execution time, the strategy looks up by primary key instead of embedding mapping logic in every request. Later, when either side changes its encoding, you change one table.
How to Configure on Qeasy
In the Qeasy console, this strategy is configured in four blocks:
- Source connector: Choose Xiaoman OKKICRM, supply app credentials and the query API
/v1/invoices/order/list, method=GET, pagination fieldsstart_index/count. - Target connector: Choose KIS Private Cloud, endpoint
/koas/app007099/api/salesorder/create, method=POST, configureAccountDBas a "fixed parameter + variable concat" so it is not repeated on every request. - Field mapping: Map source flat fields into the target's nested
Object. The header/body split is the critical part — header fields go toObject.*, line items go toObject.entries[], and in the mapping panel enable "array expansion". - Filter and idempotency: Enable
idCheckon the source side; the middle layer deduplicates onorder_id; before write, do an existence check on the target side using the business order numberidto avoid duplicates.
Implementation Steps
We split the schedule into three phases — incremental seed → full reconciliation → steady-state incremental:
- Phase 1: Incremental seed (cold start). Trigger one manual run in Qeasy, initialize
LAST_SYNC_TIMEto the required historical seed (e.g., 00:00 on go-live day), and pour in a batch of historical orders. The goal of this phase is to flatten history in one shot, not to let it mix with the daily schedule. - Phase 2: Full reconciliation (validation). Run a full sync during off-peak hours and reconcile against KIS manually. We once hit a phantom case where "both sides matched but line counts differed by one" — the root cause was the source collapsing merged rows into one while the target required per-SKU expansion. This phase is a regression test for encoding mapping and line-split rules, and should not be skipped.
- Phase 3: Steady-state incremental (routine). Source schedule
0-59/20 7-22 * * *(every 20 minutes during business hours); target writes staggered at2-59/45 7-22 * * *(starting 2 minutes past the hour, every 45 minutes). The stagger prevents both ends from contending for the same network segment and target connection pool when pulling and writing simultaneously.
Pitfalls and Lessons
- Do not flatten header and body into one mapping sheet. Early on we spread all fields flat and ended up only capturing the first line item. The safe approach is two sub-sheets: header 1:1, body 1:N, explicitly assembled in the middle layer as
Object.entries[]. - Do not hard-code the system clock for the time window.
start_time/end_timemust use Qeasy'sLAST_SYNC_TIME/CURRENT_TIMEvariables, calibrated to the source's timezone — otherwise you lose or duplicate orders across day boundaries. - The default
removed=0is a double-edged sword. Daily jobs only see non-deleted orders, but if a "delete-then-recreate with the same order number" event happens inside the sync window, incremental will miss it. Open a compensation channel: run a low-frequency job that pullsremoved=1and does diff reconciliation. - Do not be greedy with page
count. The source's default iscount=10. In practice, beyond a threshold the source sporadically times out — the middle layer must keep retries and a resume point (continuing from the laststart_index). idCheckon the target side is the last gate. Even if the middle layer already deduplicates, the target API's idempotency key (id) must be re-checked before write; otherwise, concurrent runs produce duplicate orders.
When to Use and When Not to Use
Use when: orders are created in a CRM/e-commerce front end and fulfilled by an ERP back end, you need a "trusted order flow" between them, and both sides allow time-window querying plus idempotent writes. Do not use when: the source offers no time-window query (only full scans), the target has no idempotency key, or order volume is so small that a middle layer is overkill — in those cases, a direct API connection is enough, and introducing Qeasy only enlarges the failure surface.