MES与畅捷通T+无缝集成, 破解业财数据割离难题

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_ITEMhandler.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

您是开发者或技术伙伴?​