feat(ic): 实现盘点和销售出库的 MES 同步功能

- 新增 AfterApprovingSynchronizeRule 类用于盘点单审批后同步到 MES
- 新增 AfterSigningSynchronizeRule 类用于销售出库单签字后同步到 MES- 在 ApproveBP 和 SignBP 中集成同步规则
- 优化数据构建和响应处理逻辑,提高同步效率和可靠性
This commit is contained in:
张明 2025-05-15 14:12:41 +08:00
parent 0763417ca8
commit 841b210351
4 changed files with 459 additions and 0 deletions

View File

@ -0,0 +1,61 @@
package nc.bs.ic.m4c.sign;
import com.yonyou.cloud.ncc.plugin.entity.OperationInfo;
import nc.bs.ic.general.sign.ISignBP;
import nc.bs.ic.general.sign.ISignRuleProvider;
import nc.bs.ic.general.sign.SignBPTemplate;
import nc.bs.ic.m4c.base.BPPlugInPoint;
import nc.bs.ic.m4c.base.rule.SaleOutFillInvoiceNumRule;
import nc.bs.ic.m4c.sign.rule.*;
import nc.bs.ic.pub.util.SagasUtils;
import nc.bs.scmpub.rule.VOSagaFrozenValidateRule;
import nc.impl.pubapp.pattern.rule.processer.AroundProcesser;
import nc.itf.ic.m4c.compensate.ISaleOutSagasCompensate;
import nc.vo.ic.m4c.entity.SaleOutVO;
import nc.vo.ic.pub.util.VOEntityUtil;
import nc.vo.scmpub.res.billtype.ICBillType;
import nccloud.bs.ic.mobile.component.operation.rule.MobAfterSignMessageRule;
import nccloud.bs.ic.tms.kj.KJTMSConfirmAction;
import nccloud.vo.scmpub.tms.enumeration.BusiScene;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
public class SignBP implements ISignBP<SaleOutVO>, ISignRuleProvider<SaleOutVO> {
public SignBP() {
}
public SaleOutVO[] sign(SaleOutVO[] bills) {
SignBPTemplate<SaleOutVO> signBP = new SignBPTemplate(BPPlugInPoint.SignBP, this);
SagasUtils.frozenAndAddSaga(bills, ICBillType.SaleOut.getCode(), "1", (OperationInfo) null);
Map<String, Serializable> paramMap = new HashMap();
paramMap.put("actionname", "sign_4C");
paramMap.put("hid", VOEntityUtil.getPksFromAggVO(bills));
SagasUtils.compensate(paramMap, ISaleOutSagasCompensate.class);
SaleOutVO[] resultVOs = (SaleOutVO[]) signBP.sign(bills);
KJTMSConfirmAction action = new KJTMSConfirmAction(BusiScene.SODelivery);
action.confirm(resultVOs);
return resultVOs;
}
public void addAfterRule(SaleOutVO[] vos, AroundProcesser<SaleOutVO> processor) {
processor.addAfterRule(new PushSquareSignRule());
processor.addAfterRule(new AfterSignRuleForSquareiaProcess());
processor.addAfterRule(new AfterSignRuleForFinanceProcess());
processor.addAfterRule(new SaleOutFillInvoiceNumRule());
processor.addAfterRule(new AfterSignRuleForLiabilityProcess());
processor.addAfterRule(new SaleOutProceedsRule());
processor.addAfterRule(new AfterRedSignRuleTOArsubProcess());
processor.addAfterRule(new ArsubToVoucherRule());
processor.addAfterRule(new SaleOutProceedsRuleCG());
processor.addAfterRule(new MobAfterSignMessageRule());
// 销售出库 签字后 同步到MES金思维系统
processor.addAfterRule(new AfterSigningSynchronizeRule());
// 盘点审批后传MES
}
public void addBeforeRule(SaleOutVO[] vos, AroundProcesser<SaleOutVO> processor) {
processor.addBeforeRule(new VOSagaFrozenValidateRule(true));
}
}

View File

@ -0,0 +1,190 @@
package nc.bs.ic.m4c.sign.rule;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.yonyou.cloud.utils.StringUtils;
import nc.IHttpPostOtherSys;
import nc.bs.framework.common.NCLocator;
import nc.bs.logging.Log;
import nc.impl.pubapp.pattern.rule.IRule;
import nc.vo.ic.m4c.entity.SaleOutBodyVO;
import nc.vo.ic.m4c.entity.SaleOutHeadVO;
import nc.vo.ic.m4c.entity.SaleOutVO;
import nc.vo.pub.BusinessException;
import nc.vo.pub.lang.UFDate;
import java.text.SimpleDateFormat;
//销售出库签字后传MES金思维系统
public class AfterSigningSynchronizeRule implements IRule<SaleOutVO> {
private static final String SALE_OUT_URL = "/GTHINKING/AjaxService/N_MISPRO/SaleOrderOutbound.ashx/SaveData"; // 销售出库登记接口
private static final SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final String logginfo = "OALOG";
private static final Log obmlog = Log.getInstance(logginfo);
public AfterSigningSynchronizeRule() {
}
@Override
public void process(SaleOutVO[] saleOutVOS) {
try {
if (saleOutVOS == null || saleOutVOS.length == 0) {
return;
}
// 初始化HTTP请求工具类
IHttpPostOtherSys httpPostOtherSys = NCLocator.getInstance().lookup(IHttpPostOtherSys.class);
// 处理每个销售出库单
for (SaleOutVO saleOutVO : saleOutVOS) {
SaleOutHeadVO hvo = (SaleOutHeadVO) saleOutVO.getParentVO();
SaleOutBodyVO[] bvos = (SaleOutBodyVO[]) saleOutVO.getChildrenVO();
// 构建要发送的数据
JSONObject syncData = buildSyncData(hvo, bvos);
// 发送数据到金思维系统使用HttpPostOtherSysImpl处理网络请求
String mesResponse = httpPostOtherSys.callLE(SALE_OUT_URL, syncData);
obmlog.debug("AfterSigningSynchronizeRule-金思维系统响应: " + mesResponse);
// 解析响应处理结果
processResponse(hvo.getVbillcode(), mesResponse);
}
} catch (Exception e) {
obmlog.error("AfterSigningSynchronizeRule-处理异常:" + e.getMessage(), e);
}
}
/**
* 构建符合金思维系统接口规范的请求数据
* 按照NCC/YonBIP字段映射到金思维系统字段
*/
private JSONObject buildSyncData(SaleOutHeadVO hvo, SaleOutBodyVO[] bvos) {
JSONObject requestData = new JSONObject();
requestData.put("operation_type", "I");
JSONObject info = new JSONObject();
// 主表数据 - 根据字典字段映射
info.put("orderNo", hvo.getVbillcode()); // 提货单ID - 单据号(vbillcode)
// 日期格式转换
UFDate dbilldate = hvo.getDbilldate();
String billDateStr = dateTimeFormat.format(dbilldate.toDate());
info.put("orderDate", billDateStr); // 提单日期 - 单据日期(dbilldate)
info.put("planDate", billDateStr); // 计划日期 - 使用同样的单据日期
info.put("actureDate", billDateStr);
// 生成方式可能存储在自定义字段中
String genType = getStringValue(hvo.getVdef1());
info.put("genType", null); // 生成方式 - 默认N
info.put("type", "XSCK"); // 默认事务类型XSCK
info.put("departmentId", hvo.getCdptvid()); // 部门ID - 部门(cdptvid)
info.put("storeId", hvo.getCwarehouseid()); // 仓库ID - 仓库(cwarehouseid)
// 汇率默认为1
info.put("exRate", null);
info.put("sType", "N"); // 发出默认值
info.put("billing", "Y"); // 出具发票默认值
info.put("billingBasis", "S"); // 开票依据默认值
// 有效日期可能为空可能存储在其他字段或自定义字段中
String effDateStr = getStringValue(hvo.getVdef2());
if (StringUtils.isNotEmpty(effDateStr)) {
try {
UFDate effDate = new UFDate(effDateStr);
info.put("effDate", dateTimeFormat.format(effDate.toDate()));
} catch (Exception e) {
obmlog.error("解析有效日期出错: " + e.getMessage());
}
}
info.put("consignStoreId", null); // 寄售仓库ID
info.put("consignType", null); // 寄售事务类型
info.put("operatorNo", null); // 经办人工号 - 制单人(billmaker)
info.put("operatorName", null); // 经办人 - 可能是自定义字段
info.put("storeKeeper", hvo.getCwhsmanagerid()); // 保管员 - 库管员(cwhsmanagerid)
info.put("cwhsmanagerid", hvo.getCwhsmanagerid()); // 保管员ID - 库管员(cwhsmanagerid)
info.put("customId", hvo.getCcustomerid()); // 客户ID - 订单客户(ccustomerid)
info.put("mark", "Y"); // 生成标志默认值
info.put("remark", hvo.getVnote()); // 备注 - 备注(vnote)
// 构建details明细数组
JSONArray details = new JSONArray();
if (bvos != null) {
for (SaleOutBodyVO bvo : bvos) {
JSONObject detail = new JSONObject();
detail.put("orderNo", hvo.getVbillcode()); // 提货单ID - 单据号(vbillcode)
detail.put("sequenceNum", bvo.getCrowno());
// 来源单据信息 - 根据字典正确映射
detail.put("saleOrderNo", null); // SOID - 来源单据号(vsourcebillcode)
detail.put("saleSequenceNum", null); // SO序号 - 来源单据行号(vsourcerowno)
detail.put("allocationNum", null); // 分配号
// 物料相关 - 使用正确的字段名
detail.put("materialId", bvo.getCmaterialoid()); // 物料ID - 物料(cmaterialoid)
detail.put("unit", bvo.getCunitid()); // 计量单位 - 主单位(cunitid)
detail.put("productNum", null); // 制令号 - 可能是自定义字段
detail.put("storageId", bvo.getClocationid()); // 库位 - 货位(clocationid)
detail.put("batchNum", bvo.getVbatchcode()); // 物料批号 - 批次号(vbatchcode)
detail.put("scaleFactor", bvo.getVchangerate());
// 应发数量和实发数量
detail.put("issuedQty", bvo.getNshouldassistnum()); // 应发数量(nshouldassistnum)
detail.put("mIssuedQty", bvo.getNshouldnum()); // 主应发数量(nshouldnum)
detail.put("actQry", bvo.getNassistnum()); // 实发数量(nassistnum)
detail.put("mActQry", bvo.getNnum()); // 主实发数量(nnum)
detail.put("assistActQry", null); // 辅助实发数量(nassistnum)
// 客户信息
detail.put("customId", getStringValue(bvo.getCasscustid())); // 客户ID - 客户(casscustid)
// 供应商信息
detail.put("supplierId", getStringValue(bvo.getCvendorid())); // 供应商ID - 供应商(cvendorid)
detail.put("color", null);
// 生产日期
detail.put("manufactureDate", null); // 生产日期(dproducedate)
detail.put("properties", null);
detail.put("remark", bvo.getVnotebody()); // 备注 - 行备注(vnotebody)
details.add(detail);
}
}
info.put("details", details);
// 将info对象添加到请求数据中
requestData.put("info", info);
return requestData;
}
/**
* 安全获取字符串值防止空指针异常
*/
private String getStringValue(Object value) {
return value == null ? "" : value.toString();
}
/**
* 处理金思维系统响应
*/
private void processResponse(String vbillcode, String response) {
if (StringUtils.isEmpty(response)) {
obmlog.error("AfterSigningSynchronizeRule-响应为空,单据号: " + vbillcode);
return;
}
try {
JSONObject respObj = JSONObject.parseObject(response);
JSONObject result = respObj.getJSONObject("result");
if (result != null) {
boolean success = result.getBooleanValue("success");
String message = result.getString("message");
String errorMessage = result.getString("errorMessage");
String orderNo = result.getString("orderNo");
if (success) {
obmlog.info("AfterSigningSynchronizeRule-同步成功,单据号: " + vbillcode + ", 返回单号: " + orderNo + ", 消息: " + message);
} else {
obmlog.error("AfterSigningSynchronizeRule-同步失败,单据号: " + vbillcode + ", 错误: " + errorMessage);
throw new BusinessException("同步到金思维系统失败: " + errorMessage);
}
}
} catch (Exception e) {
obmlog.error("AfterSigningSynchronizeRule-处理响应异常: " + e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,32 @@
package nc.bs.ic.m4r.approve;
import nc.bs.ic.m4r.approve.rule.AfterApprovingSynchronizeRule;
import nc.bs.ic.m4r.approve.rule.PushInOutBills;
import nc.bs.ic.m4r.base.BPPluginPoint;
import nc.bs.ic.m4r.insert.rule.InvcountDataCheck;
import nc.bs.ic.pub.base.ICAroundProcesser;
import nc.bs.ic.special.approve.ApproveBPTemplate;
import nc.bs.ic.special.approve.IApproveBP;
import nc.bs.ic.special.approve.rule.SpecialAuditDataCheck;
import nc.bs.ic.special.base.IApproveRuleProvider;
import nc.bs.pub.compiler.AbstractCompiler2;
import nc.vo.ic.m4r.entity.InvCountBillVO;
public class ApproveBP implements IApproveBP<InvCountBillVO>, IApproveRuleProvider<InvCountBillVO> {
public ApproveBP() {
}
public InvCountBillVO[] approve(InvCountBillVO[] bills, AbstractCompiler2 script) {
return (InvCountBillVO[]) (new ApproveBPTemplate(BPPluginPoint.ApproveBP, this)).approve(bills, script);
}
public void addApproveAfterRule(ICAroundProcesser<InvCountBillVO> processor) {
processor.addAfterRule(new PushInOutBills());
// Å̵㣨ÉóÅúºó´«MES£©
processor.addAfterRule(new AfterApprovingSynchronizeRule());
}
public void addApproveBeforeRule(ICAroundProcesser<InvCountBillVO> processor) {
processor.replaceBeforeRuleAt(new InvcountDataCheck(), SpecialAuditDataCheck.class);
}
}

View File

@ -0,0 +1,176 @@
package nc.bs.ic.m4r.approve.rule;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.yonyou.cloud.utils.StringUtils;
import nc.IHttpPostOtherSys;
import nc.bs.framework.common.NCLocator;
import nc.bs.logging.Log;
import nc.impl.pubapp.pattern.rule.IRule;
import nc.vo.ic.m4r.entity.InvCountBillVO;
import nc.vo.ic.m4r.entity.InvCountHeaderVO;
import nc.vo.ic.m4r.entity.InvCountBodyVO;
import nc.vo.pub.BusinessException;
import nc.vo.pub.lang.UFDate;
import nc.vo.pub.lang.UFDouble;
import java.text.SimpleDateFormat;
// 盘点审批后传MES
public class AfterApprovingSynchronizeRule implements IRule<InvCountBillVO> {
private static final String INV_COUNT_URL = "/GTHINKING/AjaxService/N_MISPRO/InvCount.ashx/SaveData"; // 盘点单同步接口
private static final SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final String logginfo = "OALOG";
private static final Log obmlog = Log.getInstance(logginfo);
public AfterApprovingSynchronizeRule() {
}
@Override
public void process(InvCountBillVO[] invCountBillVOs) {
try {
if (invCountBillVOs == null || invCountBillVOs.length == 0) {
return;
}
// 初始化HTTP请求工具类
IHttpPostOtherSys httpPostOtherSys = NCLocator.getInstance().lookup(IHttpPostOtherSys.class);
// 处理每个盘点单
for (InvCountBillVO invCountBillVO : invCountBillVOs) {
InvCountHeaderVO hvo = invCountBillVO.getParentVO();
InvCountBodyVO[] bvos = invCountBillVO.getChildrenVO();
// 构建要发送的数据
JSONObject syncData = buildSyncData(hvo, bvos);
// 发送数据到金思维系统使用HttpPostOtherSysImpl处理网络请求
String mesResponse = httpPostOtherSys.callLE(INV_COUNT_URL, syncData);
obmlog.debug("AfterApprovingSynchronizeRule-金思维系统响应: " + mesResponse);
// 解析响应处理结果
processResponse(hvo.getVbillcode(), mesResponse);
}
} catch (Exception e) {
obmlog.error("AfterApprovingSynchronizeRule-处理异常:" + e.getMessage(), e);
}
}
/**
* 构建符合金思维系统接口规范的请求数据
* 按照接口文档要求构建JSON结构
*/
private JSONObject buildSyncData(InvCountHeaderVO hvo, InvCountBodyVO[] bvos) throws BusinessException {
JSONObject requestData = new JSONObject();
// 操作类型为新增
requestData.put("operation_type", "I");
// 构建info对象 - 按照接口要求设置必填字段
JSONObject info = new JSONObject();
// 必填字段
info.put("storeId", hvo.getCwarehouseid()); // 仓库ID(cwarehouseid) - 必填
info.put("departmentId", hvo.getCdptvid()); // 部门ID(cdptvid) - 必填
// 日期处理 - 盘点日期
UFDate dcountdate = hvo.getDcountdate();
if (dcountdate != null) {
info.put("date", dateTimeFormat.format(dcountdate.toDate())); // 盘点日期(dcountdate) - 必填
} else {
// 如果盘点日期为空使用单据日期
UFDate dbilldate = hvo.getDbilldate();
if (dbilldate != null) {
info.put("date", dateTimeFormat.format(dbilldate.toDate()));
} else {
// 接口要求此字段必填如果没有日期则使用当前日期
info.put("date", dateTimeFormat.format(new java.util.Date()));
}
}
// 其他主表字段
info.put("worker", getStringValue(hvo.getCountoperator())); // 盘点人(countoperator)
info.put("mark", "N"); // 生成标志默认为N
info.put("remark", getStringValue(hvo.getVnote())); // 备注(vnote)
// 构建details明细数组
JSONArray details = new JSONArray();
if (bvos != null) {
for (InvCountBodyVO bvo : bvos) {
JSONObject detail = new JSONObject();
// 必填字段
// 序号 - 转换为浮点数
detail.put("sequenceNum", bvo.getCrowno()); // 序号(crowno) - 必填
detail.put("materialId", getStringValue(bvo.getCmaterialvid())); // 物料ID(cmaterialvid) - 必填
detail.put("storageId", getStringValue(bvo.getClocationid())); // 库位(clocationid) - 必填
detail.put("batchNum", getStringValue(bvo.getVbatchcode())); // 物料批号(vbatchcode) - 必填
// 盘存数量 - 使用实盘主数量
UFDouble countNum = bvo.getNcountnum();
if (countNum != null) {
detail.put("panQty", countNum.doubleValue()); // 盘存数量(ncountnum) - 必填
} else {
throw new BusinessException("盘存数量不能为空");
}
detail.put("customId", getStringValue(bvo.getCasscustid())); // 客户ID
detail.put("supplierId", getStringValue(bvo.getCvendorid())); // 供应商ID
// 生产日期处理
detail.put("manufactureDate", null); // 如果没有传入null
detail.put("color", null);
// 包装信息可能来自自定义属性
detail.put("packLen", null);
detail.put("packSize", null);
// 备注
detail.put("remark", getStringValue(bvo.getVnotebody())); // 备注(vnotebody)
details.add(detail);
}
}
info.put("details", details);
// 将info对象添加到请求数据中
requestData.put("info", info);
return requestData;
}
/**
* 安全获取字符串值防止空指针异常
*/
private String getStringValue(Object value) {
return value == null ? "" : value.toString();
}
/**
* 处理金思维系统响应
*/
private void processResponse(String vbillcode, String response) {
if (StringUtils.isEmpty(response)) {
obmlog.error("AfterApprovingSynchronizeRule-响应为空,单据号: " + vbillcode);
return;
}
try {
JSONObject respObj = JSONObject.parseObject(response);
JSONObject result = respObj.getJSONObject("result");
if (result != null) {
boolean success = result.getBooleanValue("success");
String message = result.getString("message");
String errorMessage = result.getString("errorMessage");
String orderNo = result.getString("orderNo");
if (success) {
obmlog.info("AfterApprovingSynchronizeRule-同步成功,单据号: " + vbillcode + ", 返回单号: " + orderNo + ", 消息: " + message);
} else {
obmlog.error("AfterApprovingSynchronizeRule-同步失败,单据号: " + vbillcode + ", 错误: " + errorMessage);
throw new BusinessException("同步到金思维系统失败: " + errorMessage);
}
}
} catch (Exception e) {
obmlog.error("AfterApprovingSynchronizeRule-处理响应异常: " + e.getMessage(), e);
}
}
}