1. 方案场景
1.1 业务背景描述
某农业装备制造企业,拥有多品种、小批量的生产模式,业务环节复杂。车间生产执行依赖 新核云 MES 系统,财务核算和采购结算则基于 畅捷通 T+ ERP 系统。
1.2 客户痛点
效率低下: 车间物料员每日需将MES中的出入库记录手工录入T+系统,耗时耗力,且容易因疲劳而出错。
数据不准: 手工录入导致MES与T+库存数据长期不一致,财务月底核算成本时困难重重,对账差异排查往往需要跨部门耗费数天时间。
追溯困难: 生产执行与财务记账信息脱节,无法快速响应客户或内部的质量追溯需求,管理决策缺乏实时、准确的数据支撑。
1.3 目标愿景
企业希望打通两个核心系统,实现:
流程自动化: 车间作业完成,财务单据自动生成,彻底告别二次录入。
数据实时统一: 保障MES库存与T+库存账实相符,实时同步。
管理精细化: 构建完整的“生产-库存-财务”数据链条,提升整体运营效率。
2. 解决方案
2.1 方案总览
客户通过新核云开放平台,构建了一条从MES业务事件到T+单据生成的自动化数据流水线。下图展示了完整的业务流程(请点击图片进行放大):
触发: 员工在新核云MES中执行完工、出入库等操作。
传递: 新核云开放平台通过Webhook实时捕获业务事件。
转换与同步: 集成应用通过新核云开放平台将数据转换为T+API所需的格式,并自动调用接口,在T+中生成对应单据。
2.2 方案优势
业财一体,自动同步: 物料、备料出库、采购入库等10+类核心业务单据自动同步,业务发生即记账,从源头保障数据准确。
规则灵活,适配性强: 通过简单配置,即可指定同步仓库、工序等规则,轻松满足企业个性化管理需求。
投入降低,效率倍增: 基于开放API构建,无需昂贵复杂的传统中间件,实施快捷,大幅降低开发和维护成本。
安全可靠,性能稳定: 依托新核云开放平台的高可用架构,确保企业核心业务数据传输的稳定与安全。
3. 集成方式
3.1 核心集成点
本方案实现了以下关键业务的自动同步:
| MES业务单据 | T+生成单据 | 备注 |
|---|---|---|
| 物料 | 物料 | 仅同步允许采购的物料,上线初始先同步所有,后续只同步新增、修改的物料,删除不同步 |
| 备料出库/补料出库 | 材料出库单 | 仅同步出库仓库为指定仓库的单据,仓库通过cps表单由客户自行维护 |
| 还料入库 | 负数材料出库单 | 仅同步入库仓库为指定仓库的单据,仓库通过cps表单由客户自行维护 |
| 库存移库 | 调拨申请单 | 仅同步移出仓库为指定仓库的单据,仓库通过cps表单由客户自行维护 |
| 采购入库/采购检验二次入库 | 采购入库单 | 所有单据,无论物料属性,全部同步 |
| 采购退货出库 | 负数采购入库单 | 所有单据,无论物料属性,全部同步 |
| 生产委外备料出库 | 负数材料出库单 | 仅同步出入库明细中物料属性为原料的单据 |
| 生产委外入库/生产委外检验入库 | 采购入库单 | 仅同步指定工序单据,工序通过cps表单由客户自行维护 |
| 生产委外退货出库 | 采购入库单 | 仅同步指定工序单据,工序通过cps表单由客户自行维护 |
| 其他出库 | 材料出库单等 | 仅同步指定自定义库存申请类型,无需表单维护,不支持客户后续增加业务 |
| 其它入库 | 其他入库单等 | 仅同步指定自定义库存申请类型,无需表单维护,不支持客户后续增加业务 |
3.2 实现要点
事件驱动: 订阅新核云 MES 的 Webhook 事件,实时响应业务变化。
数据转换: 在开放平台侧完成数据格式、字段映射与业务逻辑转换。
接口调用: 通过畅捷通T+开放 API 创建最终单据。
稳健性设计: 采用 Token 动态认证与缓存机制,保证集成的长期稳定性。
3.3 集成步骤
该集成方案遵循事件驱动架构,通过新核云开放平台的 Webhook 功能触发,经由一个独立的集成应用(数据转换与路由中枢) 进行处理,最终调用畅捷通T+的 API 完成数据同步。
详细步骤
步骤 1: MES 业务操作触发
-
操作员在新核云 MES 中完成业务操作:例如,物料员执行完工、出入库、采购入库等操作。
-
MES 系统生成业务数据:操作完成后,新核云 MES 自动生成相应的业务数据(如库存变动记录、工单信息)。
-
事件检测:新核云开放平台的事件订阅模块实时检测到这些业务事件。
步骤 2: Webhook 事件推送
-
事件订阅配置:在新核云开放平台中,客户预先订阅相关 Webhook 事件(通过平台 UI 或 API 配置),指定事件类型和推送地址。
-
数据推送:当事件触发时,新核云开放平台通过 Webhook 向预设的集成应用地址推送事件详情和原始数据(JSON 格式),包括事件类型、时间戳、业务数据实体(如物料 ID、数量、仓库信息)。
步骤 3: 集成应用接收与解析
-
集成应用部署:客户创建并部署一个集成应用(作为数据转换与路由中枢),例如使用 Node.js 或 Python 编写的微服务,部署在云服务器或本地环境中。
-
接收数据:集成应用监听 Webhook 推送,接收并解析新核云发送的 JSON 数据,验证签名以确保数据安全。
-
日志记录:可选步骤,记录接收到的数据用于审计或调试。
-
租户隔离:通过
@RequestHeader("TENANT_ID")实现多租户路由(见代码handler.enable(tenantId)) -
事件分发:根据
businessType字段路由到不同处理器(如CREATE_ITEM→handler.createItem())
以下代码展示如何接收新核云 Webhook 推送的原始事件(@RequestBody String event):
@PostMapping("")
public ResponseEntity<OpenAPIResponse> consumeEvent(
@RequestHeader(name = "TENANT_ID", required = false) String tenantId,
@RequestBody String event // 接收新核云推送的原始JSON事件
) {
log.info("开始处理事件:tenantId:{} event:{}", tenantId, event);
JSONObject eventObj = JSON.parseObject(event);
BusinessType type = BusinessType.fromValue((Integer) eventObj.get("businessType"));
// 根据事件类型分发到不同处理器
switch (type) {
case CREATE_ITEM:
Event<ItemDTO> createItemEvent = this.getEvent(event, ItemDTO.class);
handler.createItem(createItemEvent.getData()); // 调用物料创建处理器
break;
case CREATE_INVENTORY_BATCH:
Event<InventoryBatch> inventoryBatchEvent = this.getEvent(event, InventoryBatch.class);
handler.inventoryBatch(inventoryBatchEvent.getData()); // 调用库存批次处理器
break;
// 其他事件类型(如出入库、采购等)...
}
}
以下代码展示如何将新核云推送的原始 JSON 字符串转换为结构化业务对象:
// 泛型方法:将原始JSON事件解析为强类型对象(如ItemDTO)
private <T> Event<T> getEvent(String eventStr, Class<T> clazz) throws Exception {
JavaType type = ObjectMapperUtils.objectMapperIgnoreUnknown.getTypeFactory()
.constructParametricType(Event.class, clazz);
return ObjectMapperUtils.objectMapperIgnoreUnknown.readValue(eventStr, type);
}
// 示例:解析物料创建事件
Event<ItemDTO> createItemEvent = this.getEvent(event, ItemDTO.class);
ItemDTO itemData = createItemEvent.getData(); // 获得结构化物料数据
步骤 4: 数据转换与映射
-
执行核心转换逻辑:
-
字段映射:将新核云 MES 数据字段映射到畅捷通 T+ API 所需的字段(例如,将 MES 的
material_code映射为 T+ 的InventoryCode)。 -
格式转换:调整数据格式(如日期时间格式、数值单位)、处理业务规则(如过滤特定仓库或工序的单据)。
-
业务逻辑处理:应用客户配置的规则(通过 CPS 表单或配置文件),例如只同步指定仓库的出库单或特定工序的委外单据。
-
-
构建 T+ API 请求:根据转换后的数据,构建符合畅捷通 T+ API 规范的请求体(通常为 JSON 或 XML)。
-
规则配置来源:
tBusinessCategory等参数来自客户在CPS表单配置的映射规则
以下是事件路由与业务分发示例代码:
@Override
public EventResult inventoryBatch(InventoryBatch inventoryBatch) throws Exception {
// 1. 解析操作类型
Integer operationTypeId = inventoryBatch.getOperationTypeId();
InventoryOperationTypeEnum operationType = InventoryOperationTypeEnum.parseById(operationTypeId);
// 2. 空记录检查
if (CollectionUtils.isEmpty(inventoryBatch.getRecords())) {
String message = tPlusService.buildInventoryErrorMessage(operationType);
return new EventResult(ResponseCodeEnum.SUCCESS, message);
}
// 3. 根据操作类型路由到不同T+单据处理
switch (operationType) {
// 出库类业务 → 创建材料出库单
case TAKE_OUT:
case SUPPLEMENT_OUT:
return tPlusService.createMaterialDispatch(inventoryBatch,
BusiTypeEnum.DIRECT_PICK, "211", false);
// 退料业务 → 创建负数材料出库单
case RETURN_IN:
return tPlusService.createMaterialDispatch(inventoryBatch,
BusiTypeEnum.DIRECT_RETURN, "211", true);
// 采购入库 → 创建采购入库单
case PURCHASE_IN:
case PURCHASE_INSPECTION_AFTER_IN:
return tPlusService.createPurchaseReceive(inventoryBatch,
BusiTypeEnum.PURCHASE_PICK, "101", false);
// 委外业务 → 创建采购入库单(委外场景)
case WORKSHOP_OUTSOURCE_IN:
case WORKSHOP_OUTSOURCE_QC_IN:
return tPlusService.createPurchaseReceive(inventoryBatch,
BusiTypeEnum.PURCHASE_PICK, "114", false);
// 反审核操作 → 删除对应单据
case TAKE_OUT_DE_AUDIT:
case SUPPLEMENT_OUT_DE_AUDIT:
return tPlusService.deleteMaterialDispatch(inventoryBatch);
// 其他业务类型处理...
default:
return new EventResult("无需处理");
}
}
以下是物料类型过滤逻辑:
@Override
public EventResult createMaterialRequirement(Event<MaterialRequirementInventoryTask> event) throws Exception {
// 1. 提取事件数据
MaterialRequirementInventoryTask data = event.getData();
// 2. 过滤非原材料物料
List<MaterialRequirementInventoryTask.MaterialRequirementInventoryTaskRecord> materialRecords =
data.getRecords().stream()
.filter(record ->
Objects.equals(ItemTypeEnum.MATERIALS.getName(),
record.getItemType() != null ?
record.getItemType().getName() : "")
).collect(Collectors.toList());
// 3. 空记录检查
if (CollectionUtils.isEmpty(materialRecords)) {
log.error("备料申请单中没有可用的原材料物料");
return new EventResult(ResponseCodeEnum.FORBIDDEN, "无有效原材料物料");
}
// 4. 调用下游服务
data.setRecords(materialRecords); // 重置为过滤后数据
String result = httpClients.post(TPlusUrlConstant.TIAN_REN_NONG_JI_URL,
new Event<>(event.getRequestId(), event.getBusinessType(), data));
return new EventResult(ResponseCodeEnum.SUCCESS, "");
}
以下是数据转换逻辑示例代码:
// 示例:创建材料出库单的数据转换
public EventResult createMaterialDispatch(InventoryBatch inventoryBatch, BusiTypeEnum busiType, String tBusinessCategory, Boolean isNegative) throws Exception {
// 将MES的库存记录转换为T+所需的明细行
List<MaterialDispatchCreateParam.RDRecordDetail> rdRecordDetails = inventoryBatch.getRecords().stream().map(v ->
MaterialDispatchCreateParam.RDRecordDetail.builder()
.inventory(TPlusCodeParam.builder().code(v.getItemCode()).build()) // 物料编码映射
.baseQuantity(isNegative ? v.getQuantity().negate().doubleValue() : v.getQuantity().doubleValue()) // 数量处理(支持负数)
.unit(TPlusCodeParam.builder().name(v.getItemUnit()).build()) // 单位映射
.build()
).collect(Collectors.toList());
// 构建T+ API请求体
MaterialDispatchCreateParam createParam = MaterialDispatchCreateParam.builder()
.code(inventoryBatch.getCode()) // 单据编号
.externalCode(inventoryBatch.getCode()) // 外部单号(与MES一致)
.voucherDate(DateTimeUtils.longToString(inventoryBatch.getOperationTime(), DateTimeUtils.YYYY_MM_DD)) // 日期格式转换
.voucherType(TPlusCodeParam.builder().code("ST1022").build()) // 单据类型编码(固定值)
.busiType(TPlusCodeParam.builder().code(busiType.getCode()).build()) // 业务类型编码
.rdStyle(TPlusCodeParam.builder().code(tBusinessCategory).build()) // 业务类别(客户配置)
.warehouse(TPlusCodeParam.builder().code(inventoryBatch.getRecords().get(0).getWarehouseCode()).build()) // 仓库编码
.rdRecordDetails(rdRecordDetails) // 明细行
.build();
// 调用T+ API(见后续API调用部分)
// ...
}
步骤 5: Token 认证与管理
- 获取认证 Token: 调用畅捷通T+认证接口(
/token),通过配置的refreshToken获取访问令牌,并缓存至本地。
以下是Token管理示例代码:
// Token获取与刷新
public String getOpenToken() throws Exception {
// 检查Token是否有效(未过期)
if (tPlusTokenDTO == null || !tPlusTokenDTO.isValid()) {
// 调用T+认证接口获取新Token
final String url = tianRenNongJiProperties.getTPlusHost() + TOKEN_GET;
Map<String, String> headerMap = Map.of(
"appKey", tianRenNongJiProperties.getTPlusAppKey(),
"appSecret", tianRenNongJiProperties.getTPlusAppSecret(),
"content-type", "application/json"
);
Map<String, String> query = Map.of("grantType", "refresh_token", "refreshToken", tianRenNongJiProperties.getRefreshToken());
// 发送HTTP请求
String result = httpClients.get(url, headerMap, query);
TPlusResultResponseDTO<TPlusTokenDTO> response = JacksonUtil.fromJson(result, new TypeReference<>() {});
// 处理响应
if (!"200".equals(response.getCode())) {
throw new Exception("获取Token失败: " + response.getMessage());
}
tPlusTokenDTO = response.getResult(); // 缓存新Token
tPlusTokenDTO.setCreateTime(System.currentTimeMillis());
}
return tPlusTokenDTO.getAccessToken();
}
步骤 6: 调用 T+ API
-
主动调用 T+ API:集成应用使用构建的请求体和缓存的 Token,调用畅捷通 T+ 的相关 API 端点(例如,对于材料出库单,调用
https://api.chanjet.com/t+/stock/out)。 -
错误处理:处理 API 调用可能出现的错误(如网络超时、参数错误),实现重试机制(最多 3 次重试)和异常告警(通过邮件或短信通知管理员)。
-
补充设计:集成应用需实现重试机制(如3次指数退避重试),应对网络抖动或T+系统短暂不可用。
以下是调用T+ API 示例代码:
// 通用API调用方法(支持泛型返回类型)
private <T, R> R postToTPlus(String apiUrl, T data, Class<R> clazz) throws Exception {
String openToken = this.getOpenToken(); // 获取Token
String url = tianRenNongJiProperties.getTPlusHost() + apiUrl;
// 构建请求头
Map<String, String> headerMap = Map.of(
"appKey", tianRenNongJiProperties.getTPlusAppKey(),
"appSecret", tianRenNongJiProperties.getTPlusAppSecret(),
"openToken", openToken,
"content-type", "application/json"
);
// 发送POST请求
log.info("请求T+ API: URL={}, Headers={}, Body={}", url, headerMap, JacksonUtil.toJson(data));
String result = httpClients.post(url, headerMap, Collections.emptyMap(), data);
log.info("T+ API响应: {}", result);
// 解析响应
return JacksonUtil.fromJson(result, clazz);
}
// 示例:创建材料出库单的API调用
TPlusRequestDtoParam requestDtoParam = TPlusRequestDtoParam.builder().dto(createParam).build();
TPlusDataResponseDTO response = postToTPlus(MATERIAL_DISPATCH_CREATE, requestDtoParam, TPlusDataResponseDTO.class);
if (response != null && !"0".equals(response.getCode())) {
throw new CommonAdapterException("创建失败: " + response.getMessage()); // 错误处理
}
步骤 7: T+ 系统处理与单据生成
-
T+ 接收请求:畅捷通 T+ 系统接收 API 请求,验证 Token 和参数有效性。
-
生成业务单据:T+ 根据请求数据自动生成对应的业务单据(如材料出库单、采购入库单),并更新库存和财务数据。
-
返回处理结果:T+ 向集成应用返回响应(包括成功状态、单据 ID 或错误信息)。
步骤 8: 集成应用处理结果
-
接收响应:集成应用解析 T+ 的响应,检查操作是否成功。
-
记录日志与告警:
-
成功时:记录同步日志(如单据 ID、同步时间),可用于后续查询或报表。
-
失败时:记录错误详情,除日志外,应补充说明告警通知方式(如集成钉钉机器人)。
-
-
结束流程:标记该事件处理完成,确保数据一致性。
以下是异常处理机制示例代码:
try {
// 事件处理逻辑(如调用handler.createItem())
} catch (NotNeedHandleException e) {
// 忽略无需处理的异常(如重复事件)
return ResponseEntity.ok(OpenAPIResponse.success(e.getMessage()));
} catch (Exception e) {
// 捕获其他异常(如网络错误、API调用失败)
log.error("event consume failed, event:{}", event, e);
// 根据异常类型返回错误状态
CommonAdapterRequestStatusEnum statusEnum;
if (e.getCause() instanceof ResourceAccessException) {
statusEnum = CommonAdapterRequestStatusEnum.NETWORK; // 网络异常
} else if (e.getCause() instanceof HttpClientErrorException.BadRequest) {
statusEnum = CommonAdapterRequestStatusEnum.BUSINESS; // 业务逻辑错误
} else {
statusEnum = CommonAdapterRequestStatusEnum.UNKNOWN; // 未知错误
}
// 返回错误响应(含错误日志和告警)
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(OpenAPIResponse.fail(statusEnum, e.getMessage()));
}
以下是业务逻辑层的错误处理:
try {
// 尝试创建单据
return createMaterialDispatch(inventoryBatch, busiType, tBusinessCategory, isNegative);
} catch (Exception e) {
// 记录详细错误日志
log.error("创建材料出库单失败: 单据编号={}, 错误原因={}", inventoryBatch.getCode(), e.getMessage(), e);
// 构建错误响应(供监控系统捕获)
return new EventResult(
ResponseCodeEnum.FAIL,
"同步失败: " + e.getMessage() // 可加入告警通知逻辑
);
}
步骤 9: 业财数据同步完成
-
自动同步成功:MES 中的业务操作实时反映在 T+ 系统中,实现库存、财务数据的自动同步。
-
持续监控:集成应用持续运行,监听新事件,确保流程的稳定性和实时性。
3.3 开发者资源
本方案为新核云开放平台的典型应用,您可参考以下资源快速启动:
4. 客户价值总结
该集成方案的成功实践,为企业系统协同提供了清晰路径:
对客户而言: 成功打通核心业务系统,构建了高效的自动化运营体系,是实现降本增效和精细化管理的关键一步。
对新核云开放平台而言: 验证了其作为企业应用连接器的强大能力,为更多生态集成提供了可复用的范本。
对行业而言: 为中小制造企业提供了低成本、高效益的业财一体化最佳实践。
5. 方案咨询
如果您也正在面临系统孤岛、数据割裂的挑战,新核云开放平台为您提供了成熟的解决方案。
您是业务负责人?
欢迎联系我们的解决方案专家,为您量身定制集成方案。
联系电话: 400-164-1521 | 邮箱: contact@xinheyun.com
您是开发者或技术伙伴?
立即免费注册为新核云开发者,探索开放API,快速启动您的集成项目!
