Sync Kingdee Cloud Customer Master Data to MySQL: A Single-Strategy Practical Guide
What This Strategy Solves
In one real project, a retail enterprise used Kingdee Cloud as the ERP source of truth for master data, while downstream BI and the business middle platform needed a flattened customer master table for profiling and store-level analytics. The Kingdee BD_Customer interface returns a structure of "base data IDs plus nested objects," which is nearly unusable if pushed directly into BI. This strategy does only one thing: incrementally pull Kingdee customers filtered by FModifyDate into the MySQL table tp_jd_customer, isolate organization TP000, and flatten the nested objects for country, region, province, and city.
Data Flow and Field Mapping
Flow: Kingdee Cloud (executeBillQuery, FormId=BD_Customer) → Qeasy Data Integration Platform → MySQL (tp_jd_customer).
Key field mapping:
| Target Field | Source Field | Mapping Type | Notes |
|---|---|---|---|
| FCUSTID | FCUSTID | DIRECT | Primary key, used for upsert |
| FNumber | FNumber | DIRECT | Customer code, business key |
| FName | FName | DIRECT | Customer name |
| FCOUNTRY_FNumber | FCOUNTRY.FNumber | TRANSFORM | Pre-extracted at source, flattened |
| FCOUNTRY_FName | FCOUNTRY.FDataValue | TRANSFORM | Same as above |
| FPROVINCE_FNumber | FPROVINCE.FNumber | TRANSFORM | Province code |
| FCITY_FName | FCITY.FDataValue | TRANSFORM | City name |
| FCustType_FName | FCustTypeId.FDataValue | TRANSFORM | Customer category name |
| FGroup_FNumber | FGroup.FNumber | TRANSFORM | Customer group code |
| FUseOrgId | FUseOrgId.FNumber | TRANSFORM | Use organization, also the filter condition |
| FModifyDate | FModifyDate | DIRECT | Incremental cursor |
Note that FCustTypeId in the source carries an Id suffix, while the target is named FCustType_. Mixing these up is a common pitfall.
How to Configure It on Qeasy
The source is Kingdee Cloud's WebAPI (executeBillQuery), and the target is MySQL's SQL executor (batchexecute). On the Qeasy Data Integration Platform, there are four typical configuration points:
- In the source request configuration, use dot-path syntax such as
{{FCOUNTRY.FNumber}}and{{FCustTypeId.FDataValue}}to let Kingdee flatten the nested objects at the API layer, so the target table does not need any extra expressions. - Write the filter as
FModifyDate>='{{LAST_SYNC_TIME|datetime}}' and FUseOrgId.fnumber='TP000'. Put the incremental cursor and organization isolation on the source side to minimize network round-trips. - On the target side, use
REPLACE INTO tp_jd_customer (...) VALUES (...), with the primary keyFCUSTIDandidCheckenabled, for a natural upsert. - Cap each batch at 200 rows; anything more gets paginated.
Implementation Steps
We usually follow four steps on the customer site, in strict order:
- Step 1: Build the full baseline. Set the
FModifyDatestarting point to a very early date (e.g., 2000-01-01), run it once manually, and confirm that the target row count matches the Kingdee customer count after filtering byTP000. The output of this step is a full baseline. - Step 2: Turn on incremental sync. Switch
LAST_SYNC_TIMEto be automatically maintained by the platform. The source runs on*/7 * * * *(every 7 minutes), and the target runs on3-59/7 * * * *(every 7 minutes, offset 3 minutes from the source). Pull first, write later, to keep the timing stable. - Step 3: Reconciliation and monitoring. Run a daily morning reconciliation: Kingdee customer count for
TP000vs row count intp_jd_customer. Alert if the difference exceeds a threshold. - Step 4: Exception fallback. If a customer is disabled and then re-enabled in Kingdee,
FModifyDatedoes not refresh. Add a separate sync for freeze/forbid status changes; do not rely solely on the modification date.
Pitfall Recap
- Forgetting to flatten nested objects. Dumping raw Kingdee JSON into MySQL leaves BI queries with no value for
FCOUNTRY.FNumber. The safe approach is to pre-extract at the source request configuration, so the target only receives flattened fields. - Wrong field name mapping.
FCustTypeId(source) maps toFCustType_FName/FCustType_FNumber(target). It differs by anIdand an extra underscore. A typical mistake is to copy the source name directly, which leaves the whole column NULL. - Inverted organization filter.
FUseOrgId.fnumberis the use organization, whileFCreateOrgIdis the create organization. A multi-org customer can exist in both. A wrong filter leads to "exists in Kingdee, missing in MySQL." - Incremental cursor not updated. If the scheduler uses a global timestamp, concurrent strategies will overwrite each other. On Qeasy, it is recommended to maintain a separate
LAST_SYNC_TIMEfor each strategy in the strategy metadata, not a shared global variable. - Confusing
idCheckandidsemantics. The sourceidis set toFCUSTIDwithidCheck=false(becauseFCUSTIDmay be 0 in the Kingdee response). The targetidis set toidwithidCheck=true, relying on the MySQL primary key constraint for deduplication. The two sides have different semantics, do not mix them up.
Suitable and Unsuitable Scenarios
Suitable: ERP master data is centrally pushed to a data warehouse or middle platform, with a flattened structure, high-frequency incremental updates, and multi-organization isolation.
Unsuitable: when cross-system lookup is needed to enrich customer fields (in that case, add _findCollection on the source side), or when handling complex multi-currency, multi-language customer models. In the latter case, split into multiple strategies and land them separately.