Practical Tutorial: Offline Return & Exchange Query to Sales Return Order Sync
What This Strategy Solves (Scenario & Value)
In a real retail supply-chain integration project, we kept hitting the same problem: offline store returns and exchanges were finalized in the retail/OMS side, but the back-office ERP's sales return orders lagged—or simply never showed up. Finance reconciliation, refund clearing, and stock return all stalled as a result.
This strategy focuses on pulling settled return/exchange records from the retail source system incrementally by "last update time," transforming them through the Qeasy data integration platform, and writing them into the target ERP as sales return orders. It is not about bulk backfill; it is about a stable, settlement-driven incremental flow.
Data Flow & Field Mapping (Source → Middle Layer → Target)
The chain splits into three segments: retail source pull → Qeasy mapping & transformation → ERP write.
Key Field Mapping Table
| Business Meaning | Source Field (Retail) | Target Field (ERP) | Handling Notes |
|---|---|---|---|
| Document number | refund_no | FBillNo | Idempotency key, unique on source |
| Process status | process_status | (filter condition) | Only 90 (completed) is taken |
| Time window | start_time / end_time | FDate | Incremental by last update time |
| Time type | time_type | — | 0 = last update time, 1 = settle time |
| Shop number | shop_no | FSaleOrgId | Resolved via customer/shop mapping |
| Warehouse number | warehouse_no | FStockOrgId | Resolved via warehouse mapping |
| Document type | (fixed) | FBillTypeID | Hardcoded XSTHD01_SYS on target |
| Return customer | (source extension) | FRetcustId | Encoding mapping in middle layer |
Note that source start_time is typically written as {{LAST_SYNC_TIME|datetime}} and end_time as {{CURRENT_TIME|datetime}} in Qeasy—these are the hooks that make the incremental schedule work.
How to Configure in Qeasy
In the Qeasy data integration platform, this strategy is normally configured as a source-interface + write-interface pair.
- Source interface: type QUERY, method POST, API
wdt.refund.query. Hardcodeprocess_statusto90so only completed returns are pulled—do not let half-finished documents pollute the ERP. - Primary key & idempotency: set both
numberandidtorefund_no, and enableidCheckso the same document is never written twice. - Target interface: type EXECUTE, method POST, API
batchSave. HardcodeFBillTypeIDtoXSTHD01_SYS. - Organization field mapping: fields like
FSaleOrgIdandFStockOrgIdshould be resolved via_findCollectionagainst the ERP's organization master, not hardcoded. This is a common Qeasy customer pattern—centralized encoding mapping: every external code to internal ID mapping lives in a visual mapping table, so when stores or warehouses change, only one place needs updating. - Response & logging: enable
autoFillResponseso the target's returned id is auto-written back to the middle layer for easier troubleshooting.
Implementation Steps (Phased Scheduling)
Across several similar projects, the safe pattern is three phases:
- Incremental start point: before the first run, confirm with the business which timestamp serves as the historical starting point. Avoid dumping all history at once and overwhelming the ERP. A common choice is to set
start_timeto 00:00 on go-live day and let the incremental flow stabilize first. - Full-volume trigger: if historical data must be backfilled, run a separate full-volume task to write all settled historical documents once, then switch to incremental. Do not mix full and incremental in the same schedule—this is a trap we keep hitting.
- Schedule frequency: source cron
*/3 * * * *, target cron*/4 * * * *. The offsets are not arbitrary—source pulls every 3 minutes, target writes every 4 minutes; staggering prevents pulling and downstream writes from colliding. The incremental + full dual-track pattern common among Qeasy customers is exactly this kind of separation.
If the return document also contains line items (SKU, return qty, reason, etc.), go with header/body phased rollout: stabilize the header first, then configure a separate strategy for the body. It dramatically reduces debugging pain.
Pitfalls We Have Hit
- Forgot the status filter: without
process_status=90, "pending review" and "pending receipt" documents flow into the ERP and trigger mass reversals. The safe move is to hardcode the filter in the source interface and not rely on downstream cleaning. - Mixed time types: switching
time_typebetween 0 (last update) and 1 (settle time) makes the same document get pulled again at settlement. Pick one time dimension at the source and document it in the config. - Hardcoded organization mapping: writing
FSaleOrgIdas a fixed value works until store count grows. All organization-type fields must go through_findCollection. - Overlapping cron rhythms: source and target firing at the same minute spike ERP interfaces. Staggering by 1–4 minutes is a low-cost, high-return tweak.
- Wrong idempotency key: choosing a business field as the idempotency key causes duplicates once that field changes later. The source system number
refund_nois the safest key.
When It Applies and When It Doesn't
Applies: retailers with a standalone retail/OMS system, high return/exchange volume, and a back-office ERP that needs settled sales return orders for finance closing. Does not apply: pure online e-commerce returns (handled by online flows), very low document volume where automation is overkill, or scenarios requiring real-time return (this strategy is polling-based, minute-level latency, not seconds).