同步使用的函数示例

[TOC]

同步前、中、后函数需要统一设置入参 ,类型为Map,参数名为syncArg。

返回值也必须是map.

1.syncArg的属性

syncDataId 同步数据id

sourceTenantId 源企业id

sourceObjectApiName 源对象apiName

sourceEventType 源事件类型, 1是 "新增", 2是“更新”

destTenantId 目标企业id

destObjectApiName 目标对象apiName

destEventType 目标事件类型, 1是 "新增", 2是“更新”

objectData 主对象数据, 可以通过字段APIname获取字段的值。

details 从对象数据, 可以通过字段APIname获取字段的值。

说明: 同步前的函数,objectData ,details 携带的是源对象数据。且不支持修改目标对象数据。

同步中objectData ,details 携带的是源对象数据, 且支持修改目标对象数据。

同步后objectData ,details 携带的是目标对象数据。

调用目标系统写接口之前执行 同步中函数,调用写接口结束后执行 同步后函数。

从对象数据要是新增事件才有数据。

2.同步前函数

在同步前自定义函数做数据过滤

即函中返回结果中包含"isExec":"false",即该次同步将不再往后执行,目标数据将不会做出相应的更改。 测试场景:在自定义函数返回的Map中加入"isExec":"false",或"isExec":false目标数据将不会发生更改。

Map map = ["details":syncArg.details, "objectData":syncArg.objectData, "isExec":"false" ];
return map;

3.同步中函数

在同步中自定义函数中,先修改数据,再把修改的数据会写入目标系统

log.info(http://log.info/)("事件类型:" + syncArg.destEventType);
log.info(http://log.info/)(syncArg.objectData);
log.info(http://log.info/)(syncArg.details);
Map objectData = syncArg.objectData as Map;

objectData.name= "自定义函数修改了该字段"

Map map = ["details":syncArg.details, "objectData":syncArg.objectData];
log.info(map);
return map;

4.同步后函数

K3Cloud订单从CRM同步到ERP后回填订单id和编码

使用场景:如题

示例函数:

//指定对象,订单
def sourceObjectApiName = 'SalesOrderObj'
//回填ERPId字段
def erpIdF = "field_86Xfn__c"
//回填ERP编码字段
def erpNoF = "field_K4xdf__c"
def destEventType = syncArg.destEventType
def completeDataWriteResult = syncArg.completeDataWriteResult as Map
if( syncArg.sourceObjectApiName!= sourceObjectApiName||
    completeDataWriteResult.destEventType!=1||
    !completeDataWriteResult.success
    ){
      log.info("不处理")
  //以下情况直接返回
  //不是处理订单
  //不是新增
  //没成功
  return syncArg;
}
log.info("开始回填id")
String sourceDataId = syncArg.sourceDataId
def writeResult = completeDataWriteResult.writeResult as Map
String destDataId = writeResult.destDataId
def split = destDataId.split("#",  2)
def upArg = [(erpIdF):split[0],(erpNoF):split[1]]
log.info(upArg)
def upR =  Fx.object.update(sourceObjectApiName,  sourceDataId, upArg )
log.info("回填结果:"+upR)

5.转义自定义函数(访问ERP接口做格式转换)

ERP系统原生API,如果不是标准的开放接口,没有预置到集成平台中,则需要通过转义自定义函数去调用ERP的接口,再转为标准API。

image-20210812161533464

如果要做条件过滤,不能放在转义自定义函数中,数据范围设置能满足的用数据范围,不满足的用同步前函数。

image-20210813162653063

image-20210813160901639

参数的类型必须是Map, 名字固定为 syncArg。每个接口的参数,可以通过log.info("请求参数:"+syncArg); 打印出来观察。

queryMasterBatch 获取系统传递的参数

//调用参数:
log.info("请求参数:"+syncArg);
Integer offset=syncArg["objectData"]==null?0:(Integer)syncArg["objectData"]["offset"];
Integer limit=syncArg["objectData"]==null?10:(Integer)syncArg["objectData"]["limit"];
Long startTime=syncArg["objectData"]==null?0:(Long)syncArg["objectData"]["startTime"];
Long endTime=syncArg["objectData"]==null?0:(Long)syncArg["objectData"]["endTime"];
queryMasterById 获取系统传递的参数

//调用参数:
log.info("请求参数:"+syncArg);
String dataId=syncArg["objectData"]["dataId"];
create 获取系统传递的参数

//获取主对象数据

Map masterFieldVal=syncArg["objectData"]["masterFieldVal"] as Map;

6.异步推送接口通过自定义函数转义

接口URL:https://www.fxiaoke.com/erp/syncdata/open/objdata/asyncpush 使用场景:客户推送的数据格式跟平台要求的数据格式不一致,需要通过自定义函数将推送的数据格式转成平台要求的数据格式

推送执行逻辑:外部接口主动调用集成平台的推送接口把数据推送的到集成平台的缓存里。平台会异步的从缓存表拉取数据,如果写入缓存表失败则直接返回报错信息,如果是从缓存拉取数据后处理失败需要到集成平台的数据维护里面查看数据错误原因。

请求方式:POST

请求 Header 参数:

字段 说明
token 请求认证字符串【请联系纷享研发侧提供】
tenantId 请联系纷享研发侧提供
dataCenterId 单账套不需要推送该字段,多账套必须推送并填写;
objectApiName ERP 侧真实的对象名称数据同步设置->ERP 对象设置->ERP 对象编码可以找到
version v1
operationType 3 作废,其他状态不需要推送该字段;

推送步骤:

  1. 创建erp对象

    image-20211102164426503

  2. 创建同步策略

    image-20211102192240916

  3. 增加推送的自定义函数(不管非标还是标准推送,只要走推送接口就需要增加这个函数)

log.info("请求参数:"+Fx.json.toJson(syncArg))
String dataStr=syncArg["objectData"]["pushData"];
Map pushDataMap=Fx.json.parse(dataStr);

List<Map> pushDatas=[];
if( pushDataMap["data"] instanceof List){//
    /*推送一个数据示例数据
    {
      "data": {
          "number": "A2001",
          "lrap_days": "2",
          "name": "测试产品41"
      }
    }
    */
    pushDatas=pushDataMap["data"] as List
}else if( pushDataMap["data"] instanceof Map ){
    /*//推送多个数据示例数据
    {
        "data": [
            {
                "number": "A2001",
                "lrap_days": "2",
                "name": "测试产品41"
            }
        ]
    }
    */
    pushDatas.add(pushDataMap["data"] as Map)
}

List resultList=[]
pushDatas.each{
    map->    
    Map dataMap=map as Map;
    String id=map["code"];//获取主对象中哪个字段可以标识唯一值
    dataMap.put("id",id);//很重要必须有这个key和值
    Map data=["masterFieldVal":dataMap]//转成平台的格式
    resultList.add(data)
}
return ["dataList":resultList];//返回的是数组类型的

推送失败示例

image-20211102191125968

image-20211102192931588

推送成功

image-20211118173650751

image-20211102193121883

7.场景函数

根据CRM数据ID触发CRM往ERP数据同步

使用场景:在CRM列表、详情页配置按钮,触发数据同步。可用于解决CRM数据不方便修改或刷数据需求。

示例函数:

//必填,CRM对象apiName
def crmObjApiName = context.data.object_describe_api_name;
//必填,CRM数据Id
def crmDataId = context.data._id
//选填,ERP中间对象apiName,不指定会同步所有策略,填写则只同步到指定目标ERP对象的策略
def erpObjApiName = null //"BD_Customer.BillHead"
def type = "manualSyncCrm2Erp"
def params = ["crmObjectApiName":crmObjApiName,
    "crmDataId":crmDataId,
    "erpObjectApiName":erpObjApiName]
def arg = ["type":type,
    "params":Fx.json.toJson(params)]
def ret = Fx.proxy.callAPI("erp.syncData.executeCustomFunction", [:], arg)
//同步结果处理逻辑:
log.info(ret)

根据CRM数据ID触发ERP往CRM数据同步

//必填,CRM对象apiName
def crmObjApiName = context.data.object_describe_api_name;
//必填,CRM数据Id
def crmDataId = context.data._id
//必填,ERP中间对象apiName
def erpObjApiName = "BD_Customer.BillHead"
//该类型用于触发ERP往CRM数据同步,使用crm数据id
def type = "manualSyncErpDataByCrmDataId"
def params = ["crmObjectApiName":crmObjApiName,
    "crmDataId":crmDataId,
    "erpObjectApiName":erpObjApiName]
def arg = ["type":type,
    "params":Fx.json.toJson(params)]
def ret = Fx.proxy.callAPI("erp.syncData.executeCustomFunction", [:], arg)
//同步结果处理逻辑:
log.info(ret)

调用金蝶云星空的webapi

image-20210813110218312

//下面的代码片段,演示在纷享的CRM中如何调用webapi。

// K3Cloud地址 
String baseUrl = "http://172.31.100.60/K3Cloud/" 
//url接口变量 
String auth = "Kingdee.BOS.WebApi.ServicesStub.AuthService.ValidateUser.common.kdsvc" 
String query = "Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.ExecuteBillQuery.common.kdsvc"
String save = "Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.Save.common.kdsvc" 
String submit = "Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.Submit.common.kdsvc" 
String audit = "Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.Audit.common.kdsvc" 
//登录信息 
List param = ["5ec229fad54306", "ces", "888888", 2052] 
//请求参数体 Map body = ["parameters": param] 
Map headers = ["Content-Type": "application/json; charset=utf-8"] 
Map content = [:] 
//登录连接 
def (Boolean error, HttpResult data, String errorMessage) = Fx.http.post(baseUrl + auth, headers, body) 
Map cookieHeader = [:] 
if (!error) 
{ 
    if (data.statusCode == 200) 
    { 
        content = json.parse(data.content as String) 
        String logInType = content."LoginResultType" 
        if (logInType == "1" || logInType == "-5") 
        { 
            log.info("登录成功,content:" + content) 

        } else { 
            log.info("登录失败,content:" + content)
        } 

    } else { 
        log.info("请求失败,HttpResult:" + data)
    } 

} else { 
    log.info("请求错误,errorMessage:" + errorMessage) 

}
//单据查询示例(查询库存) 
body.put("parameters", [ [ "FormId" : "STK_Inventory", 
"FieldKeys" : "FID,FStockId.FNumber,FMaterialId.FNumber,FMaterialId.FName,FLot.FNumber,FLot,FS tockLocId,FBaseQty,FBaseAVBQty,FUpdateTIme", 
"FilterString": "FMaterialId.FNumber='CH4453'", 
"OrderString" : "", 
"TopRowCount" : "0", "StartRow" : "0", "Limit" : "0" ] ])
(error, data, errorMessage) = Fx.http.post(baseUrl + query, headers, body) 
log.info(data) 

/*---------------------------创建订单示例---------------------------------------*/ 
//从crm获取订单数据 
Map salesOrderMap = context.data log.info("订单:"+json.toJson(salesOrderMap)) 
//获取产品数据(一个) 
//订单产品 
Map salesProductMap = ((List)context.details.SalesOrderProductObj)[0] 
log.info("订单产品:"+salesProductMap) 
//产品 Map productData 
(error, productData, errorMessage) = object.findById("ProductObj", salesProductMap.product_id as String) log.info("product:"+productData) 
// 新增订单 
Map model = [:] 
//单据类型,必填 
model.put("FBillTypeID", ["FNumber": "XSDD01_SYS"]) 
//销售组织,必填 
model.put("FSaleOrgId", ["FNumber": "003"]) 
//客户,必填 
model.put("FCustId", ["FNumber": "4326rtyu"]) 
//日期,必填 
model.put("FDate", "2020-05-22 00:00:00") 
//销售员,必填 
model.put("FSalerId", ["FNumber": "88888"]) 
model.put("FSaleDeptId", ["FNumber": ""]) 
//k3cloud编码从crm订单同步过去 
model.put("FBillNo ", salesOrderMap.name) //订单明细
Map material = [:] 
//物料编码,必填,物料需分配到对应组织 
material.put("FMaterialId", ["FNumber": productData.product_code]) 
//销售单位,必填 
material.put("FUnitID", ["FNumber": "Pcs"]) 
//销售数量 
material.put("FQty", salesProductMap.quantity) 
//含税单价 
material.put("FTaxPrice", salesProductMap.sales_price) 
//订单明细,必填 
model.put("FSaleOrderEntry", [material]) 
//财务信息 
Map finance = [:] 
//结算币别,必填 
finance.put("FSettleCurrId", ["FNumber": "PRE001"]) 
finance.put("FExchangeTypeId", ["FNumber": "HLTX01_SYS"]) 
finance.put("FExchangeRate", 1) 
model.put("FSaleOrderFinance",finance) 
body.put("parameters",["SAL_SaleOrder", json.toJson(["Model":model])]) 
log.info(json.toJson(model)) 
(error, data, errorMessage) = Fx.http.post(baseUrl + save, headers, body) 
log.info(data) 
//提交 
body.put("parameters",["SAL_SaleOrder", ["Numbers": [salesOrderMap.name]]]) 
(error, data, errorMessage) = Fx.http.post(baseUrl + submit, headers, body) log.info(data) 
//审核 
(error, data, errorMessage) = Fx.http.post(baseUrl + audit, headers, body) 
log.info(data) 
//这里只是示例,请按照当前自定义函数要求的实际返回类型 进行返回。
return "111"

通过CRM自定义函数操作中间表

Map header=[:]
//创建数据映射
Map param1=["ployDetailId":"155bd981457343f291e0edc13776217f",//策略明细id(见下图),如果策略被删除重新建了,这里需要改动
"sourceObjectApiName":"AccountObj",//源对象apiName,如果对象apiName变了,这里要改动
"destObjectApiName":"BD_Customer.BillHead",//目标对象apiName,如果对象apiName变了,这里要改动
"sourceDataId":"sourceDataId123",//源对象数据id
"destDataId":"destDataId123666",//目标对象数据id
"sourceDataName":"sourceDataName3666",//源对象数据name属性
"destDataName":"destDataName66",//目标对象数据name属性
"remark":"remark1341"];//备注
def result1=Fx.proxy.callAPI("erp.syncData.createSyncDataMapping",header,param1);
//[false, HttpResult(statusCode=200, content={"errCode":"s106240000","errMsg":"成功"}, bytes=null), ]   s106240000成功,其他失败
log.info(result1)

//根据源对象数据id更新目标对象数据id
Map param2=["sourceObjectApiName":"AccountObj",//源对象apiName,如果对象apiName变了,这里要改动
"destObjectApiName":"BD_Customer.BillHead",//目标对象apiName,如果对象apiName变了,这里要改动
"sourceDataId":"sourceDataId123",//源对象数据id
"destDataId":"destDataId123666"]//目标对象数据id
def result2=Fx.proxy.callAPI("erp.syncData.updateSyncDataMapping",header,param2);
//[false, HttpResult(statusCode=200, content={"errCode":"s106240000","errMsg":"成功"}, bytes=null), ]   s106240000成功,其他失败
log.info(result2)

//查询源对象数据id是否存在映射关系
Map param3=["sourceObjectApiName":"AccountObj",//源对象apiName,如果对象apiName变了,这里要改动
"destObjectApiName":"BD_Customer.BillHead",//目标对象apiName,如果对象apiName变了,这里要改动
"sourceDataId":["sourceDataId123"]]//源对象数据ids,List
def result3=Fx.proxy.callAPI("erp.syncData.getSyncDataMappingBySourceDataId",header,param3);
// [false, HttpResult(statusCode=200, content={"data":{"sourceDataId123":{"sourceDataId":"sourceDataId123","isCreated":true,"destDataId":"destDataId123666","sourceDataName":"sourceDataName1233","updateTime":1611047455451,"lastSyncStatus":6,"destDataName":"destDataName123","destTenantId":"81138","sourceObjectApiName":"AccountObj","destObjectApiName":"BD_Customer.BillHead","sourceTenantId":"81138","statusName":"新增成功","id":"aa46ed320312476485e932a1ca4b4263","lastSyncDataId":"92c86fb175254e54b990bd86b6ce1145","status":1}},"errCode":"s106240000","errMsg":"成功"}, bytes=null), ]
//s106240000成功,其他失败
//data是一个Map,存储着存在映射关系的数据,key是源数据id,value是已存在的对应关系,
log.info(result3)

抓取策略明细ID ployDetailId

getployid

8.标准API接口说明

8.1 asyncpush推送数据接口说明

asyncpush是异步执行的接口,接口返回成功,只是表示接口已经成功到达集成平台,后面同步过程是异步批量处理数据的。

除非对时效性有很高的要求,否则请采用查询接口 queryMasterBatch 返回数据。

使用查询接口返回数据的好处是,容错性好,刷历史数据,重新同步这些费时费力的操作都有成熟的工具支撑,而不再需要客户方IT人员干预。

推送参考文档:集成平台接口开发指引

8.2 create

CRM->ERP创建数据时,调用该接口。

该接口要求在返回值中带上 ERP主键,ERP主键不能异步返回。

如果ERP上该接口不能在30s内返回,则ERP侧需要进行判重处理。在CRM上用同样的数据进行重试的时候,要返回上一次成功创建的结果。

参数说明 { "objAPIName":"ERP对象APIName", "masterFieldVal":"主数据", "detailFieldVals":"从数据列表" }

返回值说明: { "code": "错误返回码", "message": "错误提示语", "data": { "masterDataId": "主数据主键", "detailDataIds": { "每个明细对象的数据 主键列表" } } }

8.3 update

CRM->ERP更新主数据时,调用该接口。 主从数据一起覆盖更新目标数据。

参数 { "objAPIName":"ERP对象APIName", "masterFieldVal":"主数据", "detailFieldVals":"从数据列表" }

返回: { "code": "错误返回码", "message": "错误提示语", "data": { "masterDataId": "主数据主键", "detailDataIds": { "每个明细对象的数据 主键列表" } } }

8.4 queryMasterBatch

ERP->CRM 增量数据查询接口,集成平台定时轮询,调用该接口获取增量变化的数据。

参数说明:

objAPIName:ERP对象APIName

startTime:数据变更的开始时间(unix时间戳,单位毫秒)

endTime:数据变更的结束时间(unix时间戳,单位毫秒)

includeDetail:返回结果是否包含从数据

offset:获取记录的偏移

limit:当前请求记录条数

返回值说明:

{ "code": "错误返回码", "message": "错误提示语", "data": { "totalNum": "总记录数", "dataList": [{ "objAPIName": "主对象名", "masterFieldVal": {主对象数据}, "detailFieldVals": { "从对象1": [{从对象1数据列表}], "从对象2": [{从对象2数据列表}] } }, { "objAPIName": "主对象名", "masterFieldVal": {主对象数据}, "detailFieldVals": { "从对象1": [{从对象1数据列表}], "从对象2": [{从对象2数据列表}] } }] } }

该时间片轮询终止说明:

(1)返回的数据条数为0。比如6分钟的数据,分10页返回,如果查询第2页时,返回0条数据,则从第3页开始往后不会再轮询了。会继续轮询下一个6分钟的数据。

(2)ERP侧接口报错,跳过本时间片轮询。会继续轮询下一个时间片。

8.5 queryMasterById

ERP->CRM 通过主键ID获取数据。重新同步时会调用该接口获取最新的数据。

参数说明:

objAPIName:ERP对象APIName

dataId:数据主键

includeDetail: 返回结果是否包含从数据

返回值说明:

{ "code": "错误返回码", "message": "错误提示语", "data": { "objAPIName": "ERP对象APIName", "masterFieldVal": {主对象数据}, "detailFieldVals": { "从对象APIName": [{从对象数据列表}] } } }

8.6 invalid

CRM->ERP 作废主数据时,调用该接口。

参数说明:

{ "objAPIName": "ERP主对象APIName", "masterFieldVal": { "_id": " ERP主对象数据主键"
} }

返回结果说明:

{

"errCode": "错误返回码",

"errMsg": "错误提示语"

}

results matching ""

    No results matching ""