Published at: 2025-10-30
4.1 [Function Module] Pre-Sync, Sync, and Post-Sync Functions
1. Pre-Sync Custom Function Execution with Data Filtering
When the function returns a result containing "isExec": "false", the synchronization process will terminate, and no changes will be made to the target data.
Test Scenario:
Add "isExec": "false" or "isExec": false to the Map returned by the custom function. The target data will remain unchanged.
Function Code:
java
Map map = ["details":syncArg.details, "objectData":syncArg.objectData, "isExec":"false"];
return map;
2. Custom Function Execution During Synchronization
After the function executes, the mapped values modified in the function will be updated, while the enterprise ID and object apiName remain unchanged.
Test Scenario:
- Only new data will include Sub-objects.
- During updates, mapped values for objects will be modified. Without specific Sub-object identification, updates will apply uniformly to both Primary and Sub-objects, renaming them according to the custom function’s assigned values.
Function Code:
java
log.info("Event type: " + syncArg.destEventType);
log.info(syncArg.objectData);
log.info(syncArg.details);
Map objectData = syncArg.objectData as Map;
objectData.name = "Modified by Custom Function";
Map map = ["details":syncArg.details, "objectData":syncArg.objectData];
log.info(map);
return map;
3. Post-Sync Custom Function Execution
After the function executes, the mapped values modified in the function will be updated, while the enterprise ID and object apiName remain unchanged.
Test Scenario:
Data synchronization proceeds unaffected, with data passed to the custom function for processing. Verification requires checking the Report.
Function Code:
java
Map map = ["details":context.details, "objectData":context.data, "afterSync": "yes001"];
return map;
Key Input Fields
(If unsure about input fields, first write the function and log the inputs. Note that inputs differ for create and update operations.)
Pre-Sync (After Data Validation):
Primary Input Fields:
json
{
"destObjectApiName": "", // Target object apiName
"sourceData": {"fieldApiName": "fieldValue"} // Object field info
}
Simple Example:
java
log.info(syncArg);
String destObjApiName = syncArg["destObjectApiName"] as String;
log.info("destObjectApiName: " + destObjApiName);
if ("object_xo21i__c" == destObjApiName) {
return syncArg;
}
Map objectData = syncArg["objectData"] as Map;
String customerCode = objectData["customerCodeHead"] as String;
syncArg["isExec"] = false; // Setting this to false filters out the data from synchronization
return syncArg;
During Sync (Before Writing to Target System):
Primary Input Fields:
json
{
"destDetailSyncDataIdAndDestDataMap": {"": {"fieldApiName": "fieldValue"}}, // Target Sub-object details
"destData": {"fieldApiName": "fieldValue"} // Target object field info
// For new Primary Objects, data is in `destData`; Sub-objects are in `destDetailSyncDataIdAndDestDataMap`.
// For updates, both Primary and Sub-object data reside in `destData` (updates are processed separately).
}
Simple Example:
java
// Modify the `customerShortName` field value
log.info(syncArg);
syncArg["objectData"]["customerShortName"] = "dddddd";
log.info(syncArg);
return syncArg;
Post-Sync (After Writing to Target System):
Primary Input Fields:
json
{
"sourceDataId": "5fead1146660700001170e3d", // Source data ID (only for CRM → ERP direction)
"sourceObjectApiName": "AccountObj", // Source object apiName
"completeDataWriteResult": { // Target data write results
"detailWriteResults": [], // Sub-object results
"errCode": 0,
"success": true,
"destEventType": 1,
"errMsg": "success",
"writeResult": { // Primary object result (errCode `s106240000` = success; others = failure)
"errCode": 5001,
"success": false,
"syncDataId": "3ab1c2c2ffe04111b3e713632d5a4f76",
"errMsg": "Preprocessing error: External HTTP call failed. Error: 100, SAP BP name already exists. ::errCode=s306240003",
"destDetailSyncDataIdAndDestDataMap": {}
}
},
"destObjectApiName": "AccountObj_1el03su6s", // Target object apiName
"objectData": { // Target data
"tenant_id": "706089", // Enterprise ID
"object_describe_api_name": "AccountObj_1el03su6s", // Object apiName
"_id": "5fead2696532bf0001e524e4"
},
"details": {} // Sub-object data
}
Simple Example:
java
// CRM → ERP: Write target object ID back to CRM source object
log.info(syncArg);
if (syncArg["objectData"]["_id"] != null && syncArg["objectData"]["_id"] != ""
&& syncArg["sourceDataId"] != null && syncArg["sourceDataId"] != "") {
String destDataId = syncArg["objectData"]["_id"] as String; // Target data ID
String sourceDataId = syncArg["sourceDataId"] as String; // Source data ID
String errCode = syncArg["completeDataWriteResult"]["writeResult"]["errCode"] as String;
log.info("--" + errCode);
if (errCode == "0") { // && destDataId.length() < 11
def (Boolean error, Map data, String errorMessage) = Fx.object.update("AccountObj", sourceDataId, ["field_b25i7__c": destDataId], true);
log.info(errorMessage);
}
}
return syncArg;
4. Accessing K3 WebAPI Data
```java
// Define request params: URL, username, password, data center
String url = “”; // e.g., http://jxsz.fortiddns.com:58000/k3cloud/
String userName = “”;
String passWord = “”;
String acctId = “”; // e.g., 62415a905fb572
// Request headers
Map headMap = [“Content-Type”: “application/json”];
// Kingdee K3C auth handling (requires URL, username, password, language, data center ID, method name)
String login = “Kingdee.BOS.WebApi.ServicesStub.AuthService.ValidateUser.common.kdsvc”;
// Query method
String queryMethod = “Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.ExecuteBillQuery.common.kdsvc”;
Map bodyMap1 = [:];
Map returnMap = [:];
bodyMap1.put(“lcid”, “2052”);
bodyMap1.put(“userName”, userName);
bodyMap1.put(“passWord”, passWord);
bodyMap1.put(“acctId”, acctId);
StringBody body = StringBody.builder().content(bodyMap1).build();
Request request = Request.builder()
.method(“POST”)
.url(url + login)
.timeout(7000)
.retryCount(0)
.header(“Content-Type”, “application/json”)
.body(body)
.build();
def (Boolean error1, HttpResult data1, String errorMessage1) = Fx.http.execute(request);
if (error1) {
log.info(“Login API error: “ + errorMessage1);
}
// Extract kdservice-sessionid and ASP.NET_SessionId from login response
Map headers = data1[“headers”] as Map;
if (headers == null) {
Fx.message.throwErrorMessage(“Login failed: “ + data1);
}
String Cookies = headers[“Cookies”] as String;
log.info(“Cookies: “ + Cookies);
String sessionid = Cookies.split(“;”)[0];
String NET_SessionId = Cookies.split(“;”)[1];
sessionid = sessionid.split(“=”)[1];
NET_SessionId = NET_SessionId.split(“=”)[1];
// Request body
Map bodyMap = [:];
Integer TopRowCount = 0;
String FieldKeys = “FCUSTID,FNumber”;
String FormId = “BD_Customer”;
String FilterString = “FNumber != ‘’”;
Integer Limit = 10;
Integer StartRow = 0;
// Build query params
Map data = [“TopRowCount”: TopRowCount, “FieldKeys”: FieldKeys, “FormId”: FormId, “FilterString”: FilterString, “Limit”: Limit, “StartRow”: StartRow];
bodyMap.put(“data”, data);
// Query data
StringBody body1 = StringBody.builder().content(bodyMap).build();
Request request1 = Request.builder()
.method(“POST”)
.url(url + queryMethod)
.timeout(7000)
.retryCount(0)
.header(“Content-Type”, “application/json”)
.header(“kdservice-sessionid”, sessionid)
.header(“ASP.NET_SessionId”, NET_SessionId)
.body(body1)
.build();
def (Boolean error, HttpResult Result, String Message) = Fx.http.execute(request1);
log.info(“Response data: “ + Result);
// Process data
String contentStr = Result[“content”] as String;
if (contentStr) {
// Handle query failure
if (contentStr.contains(“ResponseStatus”)) {
Fx.message.throwErrorMessage(contentStr);
}
// Process data if available
List contentList = Fx.json.parseList(contentStr);
log.info(“contentList: “ + contentList);
String[] keyList = FieldKeys.split(“,”);
contentList.each { item ->
List eachDataList = item as List;
log.info(“eachDataList: “ + eachDataList);
Map eachData = [:];
// Process each data entry
eachDataList.eachWithIndex { value, int i ->
eachData.put(keyList[i], value);
}
log.info(“eachData: “ + eachData);
}
}
```