Writing Back DingTalk Production Approval Status to a CRM Production Order: A Single-Strategy Tutorial
What This Strategy Solves
At a manufacturing customer, production-approval workflows run in DingTalk. Once an approval completes, the business expects the production-order status in the CRM to automatically flip from "pending" to "completed." It sounds like changing a single field, but in practice DingTalk's approval-instance callbacks are often delayed and the form-control field names are messy, so manual syncing drops entries. This single strategy exists for one job: DingTalk production-approval outcome → CRM production-order status.
Data Flow and Field Mapping
The flow is DingTalk → Qeasy → CRM. The source calls topapi/processinstance/get to pull approval instances; the target calls /v1/invoices/order/push to push the status. The middleware does two things: normalize the cryptic TextNote_* control fields from DingTalk, and reverse-lookup order_id in the CRM by order number.
| Role | Key Field | Notes |
|---|---|---|
| Source – DingTalk | 流水号 (serial number) | Approval-instance unique key, used for deduplication |
| Source – DingTalk | TextNote_IQXZ8ZWDA0G0 / TextNote_16OTUV6D29UK0 | Business fields on the form, must be normalized in the middleware |
| Source – DingTalk | 发起部门 / 下单品牌所属部门 | Used for permission and ownership checks |
| Middleware | 订单编号 (normalized) | Bridge to the CRM side |
| Target – CRM | order_id | Reverse-looked-up from order number; required on update |
| Target – CRM | status | A fixed constant for the "completed" state |
One important point: order_id is not carried directly from the source. It is produced by a _findCollection reverse-lookup keyed on the order number — and this is the single most failure-prone part of the strategy.
How to Configure It on Qeasy
We built this on the Qeasy Data Integration Platform during on-site work. The configuration has three blocks:
- Source collector: WebAPI type, endpoint
topapi/processinstance/get, method POST, effect=QUERY. Response fields are expanded via_autoFillResponseso everyTextNote_*,发起部门, and下单品牌所属部门is surfaced — otherwise downstream mapping will silently miss fields. - Target executor: EXECUTE type, endpoint
/v1/invoices/order/push.order_idis bound through_findCollectionto an order-mapping collection usingwhere order_no={{订单编号}};statusis a fixed constant — the numeric value that represents "completed." This reflects a common pattern among Qeasy customers: centralized code mapping, so magic numbers like status codes don't drift across scripts. - Scheduling: the source runs every 10 minutes (
0-59/10 7-22 * * *); the target is offset by one minute (1-59/10 7-22 * * *) so both ends never contend for the same row.
Implementation Steps
- Incremental starting point: do not scan historical approval instances on the first run. Configure the incremental cursor to only fetch approvals that are newly created after the start time and already in the "completed" state. Historical data is backfilled by a separate one-shot job — otherwise the database takes a hit on day one.
- Full-trigger rollout: once the strategy has run stably for two or three cycles, manually trigger a one-off full re-scan to backfill any "completed" approvals that were missed. Run it once and stop — do not put it into the regular schedule.
- Schedule frequency: every 10 minutes during business hours; lower to 30 minutes or pause at night. Approval callbacks themselves have minute-level latency; polling more aggressively just wastes quota.
- Post-launch: for one week, sample 10 records per day and compare DingTalk's approval state with the CRM's order state to confirm consistency. Also watch the target endpoint's failure rate — most failures come from
order_idlookups that return empty, which we cover in the lessons section.
Lessons from the Field
- Typical mistake: pushing the raw
TextNote_*field straight into the CRM as the order number. Control-field names can be renamed by form admins at any time. Business fields must be normalized in the middleware first; the safe approach is to reference a dedicated "approval field → business field" mapping table. order_idlookup returns empty: the order number on the approval form and the CRM'sorder_nodiffer in format — case, surrounding whitespace, prefixes — so_findCollectionfinds nothing. The safe approach is to add atrim+ case-normalize step in the middleware, and to fall back to fuzzy matching when needed.- Duplicate writes: the same approval instance is fetched by multiple scheduling cycles, the target endpoint has no idempotency check, and the CRM gets hit repeatedly. The mitigation is incremental and full-scan on separate tracks: incrementals rely on the dedup key (serial number + completed state) inside the scheduling window, while full-scans rely on the target endpoint's idempotent field.
- Hard-coded status values: the numeric code for "completed" can differ across tenants and business lines. Don't scatter it across multiple strategies. Keep it in a centralized code-mapping table so that switching business lines later only requires one change.
- Approval-callback latency: DingTalk's approval-completed callbacks are commonly delayed by 5–10 minutes. If the strategy only listens to the live callback, data will be lost. This is precisely why the source uses polling rather than relying solely on callbacks — polling catches up delayed records.
When to Use It and When Not To
Use it for write-backs in a single business line, with stable approval-form fields and a small enum of status values — for example, production-approval completed, contract-approval completed. Don't use it when the approval form changes frequently, when multiple business lines share the same approval template, or when the target-side state machine is more complex than a single "completed" value (e.g., "partially completed," "rejected" each require their own write-back). In those cases, split into multiple strategies instead of stuffing everything into one.