Sales Orders to Feishu Bitable: A Single-Strategy Integration Playbook on Qeasy
What This Strategy Solves
Sales orders are the source documents of any supply chain. Business teams usually want to see order details in their collaboration tool without logging into the ERP. Feishu Bitable is a natural fit for lightweight business ledgers, but ERP and Feishu don't talk natively. The Qeasy data integration platform extracts Kingdee Cloud sales orders in a flat structure and writes them into a designated Bitable, creating a "one table = one business dashboard" pattern. This strategy is ideal when order data needs to be shared (with appropriate masking) across sales, operations, and customer service teams.
Data Flow and Field Mapping (Source → Middle Layer → Target)
Data flow: Kingdee Cloud → Qeasy Integration Platform → Feishu Bitable. The source calls executeBillQuery on SAL_SaleOrder and returns flat results (one row = header + one line item). The middle layer is handled by Qeasy with mapping and transformation. The target calls Bitable's /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records to write single records.
| Layer | Key Fields | Transformation Notes |
|---|---|---|
| Source header | FBillNo, FID, FDocumentStatus, FSaleOrgId_FNumber, FCustId_FNumber, FDate | Document-level identifiers and business凭证 |
| Source line | FSaleOrderEntry_FEntryID, FMaterialId_Fnumber, FQty, FPrice, FTaxPrice, FAllAmount, FIsFree | Line-level primary key and price/tax info |
| Middle layer | FDocumentStatus (A/B/C/D/Z → Draft/Approving/Approved/Re-approving/Temp-saved), FIsFree (bool → Yes/No) | CASE conditional branches |
| Target Bitable | Order No, Entry ID, Customer Code, Material Code, Sales Qty, etc. | Field names must match Bitable columns |
Headers and line items land in the same row, no aggregation required at the integration layer. Each record's FSaleOrderEntry_FEntryID serves as the line-level primary key for deduplication and update decisions.
How to Configure in Qeasy
For the source, choose Kingdee Cloud, API executeBillQuery, Form ID SAL_SaleOrder, primary key FBillNo, line key FSaleOrderEntry_FEntryID. For the target, choose Feishu, the Bitable record write API, type RESTful, effect EXECUTE.
A common Qeasy customer pattern is "centralized code mapping": for master data fields like customer, sales org, sales department, material, and document type, pass the source code through unchanged—do not translate in the middle layer. If the Bitable needs to display the customer name or material spec, use _findCollection to look up master data and add a separate "display name" column, keeping the original code column for future reconciliation.
Date fields should consistently use transformations like {{FDate|datetime}} and {{FDeliveryDate|datetime}} to prevent Bitable from treating them as plain text. Status and free-item flags should be mapped to readable labels via CASE...END or _function conditional branches.
Implementation Steps
Phase one: define the incremental start point. The source filter should be FCreateDate>='{{LAST_SYNC_TIME|datetime}}', with the initial value set to the day before project go-live. Run a one-time full backfill of historical data first.
Phase two: trigger the full sync. In the test environment, first run a full sync without filter conditions to confirm Bitable capacity and field type matching—especially make sure number, date, and text types don't get mixed up in columns. Switch back to incremental filtering only after the full sync passes.
Phase three: schedule frequency. Source cron 0 */3 * * * (every 3 hours on the hour), target cron 10 */3 * * * (every 3 hours at 10 minutes past), staggered to prevent the write operation from being blocked by source extraction. For daily operations, just monitor Qeasy run logs and failed retries.
If business teams later demand higher real-time performance, this single strategy's frequency can be bumped to every 30 minutes—but first evaluate the source API rate limits.
Lessons Learned (Pitfalls)
- Misreading the flat structure:
executeBillQueryreturns a merged "header + one line item" structure, not a nested array. People who try to loop through header-line as two levels end up with the classic error of "line items triplicated." The safe approach is direct row-by-row mapping with no aggregation. - Missing line-level primary key: Using only
FBillNoas the primary key causes overwrite-and-loss when one order has multiple line items.FSaleOrderEntry_FEntryIDmust be synchronized to Bitable as the line-level unique identifier. - Mixing codes with display names: After swapping customer codes for customer names, you can't trace back to source codes. Once master data changes, Bitable can't be reconciled. Keep code and display name in separate columns; let the code column pass through transparently.
- Date type mismatch: Source timestamps as long integers written without
|datetimetransformation end up as text strings in Bitable and can't be filtered. - Spaces in Bitable field names: Source field names are normalized, but someone casually named a Bitable column "Document Number" with a space, and the mapping expression must preserve that exact space, otherwise the failure is silent. Standardize column names on the Bitable side up front.
Suitable and Unsuitable Scenarios
Suitable: sales teams needing lightweight dashboards, order data requiring masked cross-department sharing, business users who should not log directly into the ERP. Unsuitable: scenarios requiring strict transactional write-back to ERP, bidirectional order status sync, or high-precision numeric accounting that Bitable cannot support—the latter should use standard business system integration instead of Bitable.