feat: 积分商城-对账优化

This commit is contained in:
2026-09-01 16:07:41 +08:00
parent c84b387765
commit d2cb60f5cf
12 changed files with 856 additions and 83 deletions
@@ -0,0 +1,44 @@
package com.budwk.app.zhgh.pointsmall.mallbridge.dto;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class OrderInfoByReconciliationDTO {
/**
* 订单id
*/
private String orderId;
/**
* 供应商id
*/
private String supplierId;
/**
* 商品购买总价格(含税价)
*/
private BigDecimal totalPrice;
/**
* 订单消耗积分数,有退货需要计算
*/
private BigDecimal pointsPrice;
/**
* 订单微信支付或支付宝支付数目,有退货需要计算
*/
private BigDecimal wPayOrAPay;
/**
* 总价格(不含税)
*/
private BigDecimal invoiceNakedPrice;
/**
* 下单商品信息
*/
private List<OrderProInfoDTO> orderProInfos;
}
@@ -0,0 +1,28 @@
package com.budwk.app.zhgh.pointsmall.mallbridge.dto;
import lombok.Data;
@Data
public class QueryReconciliationInfoDTO {
private String supplierId;
private String token;
private Integer reconciliationType;
/**
* 订单ID
*/
private String orderId;
/**
* 格式为:yyyy-MM-dd(包含单日)
*/
private String startDate;
/**
* 格式为:yyyy-MM-dd(包含单日)
*/
private String endDate;
private Integer page;
private Integer pageSize;
private Long timestamp;
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.pointsmall.mallbridge.dto;
import lombok.Data;
import java.util.List;
@Data
public class ReconciliationResultNoticeDTO {
private String supplierId;
private String token;
private Long timestamp;
private String reconciliationBatchNo;
private String reconciliationTime;
private List<ReconciliationResultOrderDTO> reconciliationResults;
}
@@ -0,0 +1,22 @@
package com.budwk.app.zhgh.pointsmall.mallbridge.dto;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class ReconciliationResultOrderDTO {
private String orderId;
private String userId;
private Integer reconciliationStatus;
private Integer diffType;
private String diffReason;
private BigDecimal platformTotalPrice;
private BigDecimal platformCashAmount;
private BigDecimal platformPointsPrice;
private BigDecimal platformRefundPrice;
private BigDecimal supplierTotalPrice;
private BigDecimal supplierCashAmount;
private BigDecimal supplierPointsPrice;
private BigDecimal supplierRefundPrice;
}
@@ -7,6 +7,7 @@ public class UnReconciledMsgNoticeDTO {
private String uniqueSeqNo;
private String supplierId;
private String token;
private Long timestamp;
private String orderIds;
private String unReconciledMsg;
private String unReconciledType;
@@ -0,0 +1,55 @@
package com.budwk.app.zhgh.pointsmall.mallbridge.model;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.OrderInfoByReconciliationDTO;
import lombok.Data;
import java.util.List;
@Data
public class MallOutboundPageResult {
/**
* 0: 查询成功, 其他: 响应失败错误码
*/
private int resultCode;
/**
* 失败时, 返回失败原因
*/
private String message;
/**
* 总页
*/
private Integer totalPage;
/**
* 当前页
*/
private Integer currentPage;
//9.2.2 获取订单详情接口时不能为空
private List<OrderInfoByReconciliationDTO> orderInfos;
public static MallOutboundPageResult success() {
MallOutboundPageResult r = new MallOutboundPageResult();
r.resultCode = 0;
r.message = "成功";
r.orderInfos = null;
return r;
}
public static MallOutboundPageResult success(List<OrderInfoByReconciliationDTO> o) {
MallOutboundPageResult r = new MallOutboundPageResult();
r.resultCode = 0;
r.message = "成功";
r.orderInfos = o;
return r;
}
public static MallOutboundPageResult fail(int code, String message) {
MallOutboundPageResult r = new MallOutboundPageResult();
r.resultCode = code;
r.message = message;
return r;
}
}
@@ -0,0 +1,161 @@
package com.budwk.app.zhgh.pointsmall.mallbridge.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.OrderInfoByReconciliationDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.QueryReconciliationInfoDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallOutboundPageResult;
import com.budwk.app.zhgh.pointsmall.mallbridge.util.MallBridgeCryptoUtil;
import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
/**
* 获取对账信息
*/
@Slf4j
@IocBean(args = {"refer:dao"})
public class MallReconciliationInfoQueryService {
private final Dao dao;
@Inject
private PropertiesProxy conf;
public MallReconciliationInfoQueryService(Dao dao) {
this.dao = dao;
}
public List<OrderInfoByReconciliationDTO> queryReconciliationInfo(QueryReconciliationInfoDTO dto) {
// 从供应商获取对账订单信息
return getOrderInfoFromSupplier(dto);
}
private List<OrderInfoByReconciliationDTO> getOrderInfoFromSupplier(QueryReconciliationInfoDTO dto) {
log.info("9.2.4 对账接口执行开始:{}", dto);
String clientBAB7ServerUrl = conf.get("spdb.bab7.url", "");
if (StrUtil.isBlank(clientBAB7ServerUrl)) {
throw new IllegalArgumentException("未配置 BAB7 服务地址");
}
PointsMallSupplier supplier = dao.fetch(PointsMallSupplier.class,
Cnd.where("supplierId", "=", dto.getSupplierId()).and("delFlag", "=", false));
if (supplier == null) {
log.error("9.2.4 对账接口 未找到对应供应商信息");
throw new IllegalArgumentException("未找到对应供应商信息");
}
long timestamp = System.currentTimeMillis();
// 计算token
String token = MallBridgeCryptoUtil.generateSign(supplier.getSupplierId(), supplier.getClientId(), timestamp);
dto.setToken(token);
dto.setTimestamp(timestamp);
// 组装请求报文
HashMap<String, Object> bodyMap = buildReqParam(dto, supplier);
log.info("9.2.4 通过对接接口从供应商获取对账订单数据:{}", dto);
String response = HttpUtil.createPost(clientBAB7ServerUrl)
.contentType("application/json;charset=UTF-8")
.body(JSONUtil.toJsonStr(bodyMap))
.execute()
.body();
if (response == null) {
return Collections.emptyList();
}
JSONObject returnResult = JSONUtil.parseObj(response);
log.info("通过对账接口获取对账订单数据,响应结果:{}", returnResult);
if ("-1".equals(returnResult.getStr("resultCode"))) {
throw new IllegalArgumentException("查询对账信息异常:" + returnResult);
}
MallOutboundPageResult mallOutboundPageResult = JSONUtil.toBean(returnResult, MallOutboundPageResult.class);
if (mallOutboundPageResult == null) {
return Collections.emptyList();
}
// 错误码为0表示查询成功,不等于0表示查询失败
Integer resultCode = mallOutboundPageResult.getResultCode();
if (resultCode != 0) {
log.info("通过对账接口获取对账订单数据,查询失败:{}", dto);
}
if (CollUtil.isEmpty(mallOutboundPageResult.getOrderInfos())) {
return Collections.emptyList();
}
List<OrderInfoByReconciliationDTO> orderInfoList = new ArrayList<>(mallOutboundPageResult.getOrderInfos());
// 页码
Integer page = dto.getPage();
// 总页数
Integer totalPage = mallOutboundPageResult.getTotalPage();
// 当返回的总页数没有变化,以传入的page来循环拉取
while (page < totalPage) {
// 循环拉取page页的数据
dto.setPage(page + 1);
log.info("9.2.4 对账接口-分页循环拉取,入参:{}", dto);
bodyMap = buildReqParam(dto, supplier);
response = HttpUtil.createPost(clientBAB7ServerUrl)
.contentType("application/json;charset=UTF-8")
.body(JSONUtil.toJsonStr(bodyMap))
.execute()
.body();
mallOutboundPageResult = JSONUtil.toBean(response, MallOutboundPageResult.class);
log.info("9.2.4 对账接口-分页循环拉取,返回报文:{}", mallOutboundPageResult);
// 错误码为0表示查询成功,不等于0表示查询失败
if (mallOutboundPageResult.getResultCode() != 0) {
log.info("通过对账接口获取对账订单数据,查询失败:{}", dto);
}
orderInfoList.addAll(mallOutboundPageResult.getOrderInfos());
page++;
}
log.info("9.2.4 通过对接接口从供应商获取对账订单数据调用结束,返回报文:{}", orderInfoList);
return orderInfoList;
}
/**
* 组装请求报文
*
* @param dto 请求参数
* @param supplier 供应商信息
* @return 对账接口请求入参
*/
private HashMap<String, Object> buildReqParam(QueryReconciliationInfoDTO dto, PointsMallSupplier supplier) {
// 每个供应商的接口地址不一致,后续判断
String address4 = supplier.getSupplierApiUrl() + "/supplierOpenApi/pointOrder/queryReconciliationInfo";
String bussDealMd = "P";
String srvNo = supplier.getSroNo();
String clientId = supplier.getClientId();
// 参数(此处需要加密)
String params = JSONUtil.toJsonStr(dto);
log.info("9.2.4 对账接口-请求参数:{}", params);
String keyHex = conf.get("sm4cbc.keyHex", "");
String ivHex = conf.get("sm4cbc.ivHex", "");
String encrypt;
try {
encrypt = MallBridgeCryptoUtil.sm4CbcEncrypt(params, keyHex, ivHex);
} catch (Exception e) {
log.error("9.2.4 对账接口请求参数 SM4CBC 加密出错:{}", params, e);
throw new RuntimeException("9.2.4 对账接口请求参数 SM4CBC 加密出错");
}
HashMap<String, Object> bodyMap = new HashMap<>();
bodyMap.put("Address4", address4);
bodyMap.put("BussDealMd", bussDealMd);
bodyMap.put("SroNo", srvNo);
bodyMap.put("RsrvFld1", clientId);
bodyMap.put("AplParmObjct", encrypt);
return bodyMap;
}
}
@@ -0,0 +1,70 @@
package com.budwk.app.zhgh.pointsmall.mallbridge.service;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.ReconciliationResultNoticeDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult;
import com.budwk.app.zhgh.pointsmall.mallbridge.util.MallBridgeCryptoUtil;
import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.HashMap;
/**
* 对账结果通知服务
*/
@Slf4j
@IocBean(args = {"refer:dao"})
public class MallReconciliationResultNoticeService {
private final Dao dao;
@Inject
private PropertiesProxy conf;
public MallReconciliationResultNoticeService(Dao dao) {
this.dao = dao;
}
public MallInboundResult reconciliationResultNotice(ReconciliationResultNoticeDTO dto) {
String clientBAB7ServerUrl = conf.get("spdb.bab7.url", "");
String keyHex = conf.get("sm4cbc.keyHex", "");
String ivHex = conf.get("sm4cbc.ivHex", "");
if (StrUtil.isBlank(clientBAB7ServerUrl)) {
throw new IllegalArgumentException("未配置BAB7服务地址");
}
PointsMallSupplier supplier = dao.fetch(PointsMallSupplier.class,
Cnd.where("supplierId", "=", dto.getSupplierId()).and("delFlag", "=", false));
if (supplier == null || StrUtil.isBlank(supplier.getSupplierApiUrl())) {
throw new IllegalArgumentException("未找到供应商接口地址");
}
long timestamp = System.currentTimeMillis();
dto.setTimestamp(timestamp);
dto.setToken(MallBridgeCryptoUtil.generateSign(supplier.getSupplierId(), supplier.getClientId(), timestamp));
String params = JSONUtil.toJsonStr(dto);
String encrypt = MallBridgeCryptoUtil.sm4CbcEncrypt(params, keyHex, ivHex);
HashMap<String, Object> bodyMap = new HashMap<>();
bodyMap.put("Address4", supplier.getSupplierApiUrl() + "/supplierOpenApi/pointOrder/reconciliationResult");
bodyMap.put("BussDealMd", "P");
bodyMap.put("SroNo", supplier.getSroNo());
bodyMap.put("RsrvFld1", supplier.getClientId());
bodyMap.put("AplParmObjct", encrypt);
String response = HttpUtil.createPost(clientBAB7ServerUrl)
.contentType("application/json;charset=UTF-8")
.body(JSONUtil.toJsonStr(bodyMap))
.execute()
.body();
log.info("对账结果通知接口返回报文:{}", response);
return JSONUtil.toBean(response, MallInboundResult.class);
}
}
@@ -2,25 +2,36 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.service;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.UnReconciledMsgNoticeDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult;
import com.budwk.app.zhgh.pointsmall.mallbridge.service.support.AbstractMallBridgeSupport;
import com.budwk.app.zhgh.pointsmall.mallbridge.util.MallBridgeCryptoUtil;
import com.budwk.app.zhgh.pointsmall.reconciliation.models.PointsMallReconciliationRecord;
import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.trans.Atom;
import org.nutz.trans.Trans;
import java.util.HashMap;
import java.util.List;
/**
* 商城桥接对账异常通知服务
* 对账异常消息通知服务
*/
@Slf4j
@IocBean(args = {"refer:dao"})
public class MallUnReconciledMsgNoticeService extends AbstractMallBridgeSupport {
@Inject
private PropertiesProxy conf;
public MallUnReconciledMsgNoticeService(Dao dao) {
super(dao);
}
@@ -50,4 +61,61 @@ public class MallUnReconciledMsgNoticeService extends AbstractMallBridgeSupport
log.info("积分商城对账异常通知完成,supplierId={}orderCount={}", dto.getSupplierId(), orderIds.size());
return MallInboundResult.success();
}
public MallInboundResult unReconciledMsgNotice(UnReconciledMsgNoticeDTO dto) {
log.info("9.3.1 对账异常通知接口调用开始:{}", dto);
String clientBAB7ServerUrl = conf.get("spdb.bab7.url", "");
String keyHex = conf.get("sm4cbc.keyHex", "");
String ivHex = conf.get("sm4cbc.ivHex", "");
if (StrUtil.isBlank(clientBAB7ServerUrl)) {
throw new IllegalArgumentException("未配置BAB7服务地址");
}
// 查询供应商信息
PointsMallSupplier supplier = dao.fetch(PointsMallSupplier.class,
Cnd.where("supplierId", "=", dto.getSupplierId()).and("delFlag", "=", false));
if (supplier == null) {
log.error("9.3.1 对账异常通知接口 未找到对应供应商信息");
throw new IllegalArgumentException("未找到对应供应商信息");
}
if (StrUtil.isBlank(supplier.getSupplierApiUrl())) {
return MallInboundResult.fail(404, "供应商地址未配置");
}
// 组装请求地址
String address4 = supplier.getSupplierApiUrl() + "/supplierOpenApi/pointOrder/unReconciledMsg";
String bussDealMd = "P";
String sroNo = supplier.getSroNo();
String clientId = supplier.getClientId();
long timestamp = System.currentTimeMillis();
dto.setTimestamp(timestamp);
dto.setToken(MallBridgeCryptoUtil.generateSign(supplier.getSupplierId(), clientId, timestamp));
// 参数加密
String params = JSONUtil.toJsonStr(dto);
log.info("9.3.1 对账异常通知接口-请求参数:{}", params);
String encrypt;
try {
encrypt = MallBridgeCryptoUtil.sm4CbcEncrypt(params, keyHex, ivHex);
} catch (Exception e) {
log.error("9.3.1 对账异常通知接口参数加密出错:{}", params, e);
throw new IllegalArgumentException("参数加密失败");
}
// 构建请求体
HashMap<String, Object> bodyMap = new HashMap<>();
bodyMap.put("Address4", address4);
bodyMap.put("BussDealMd", bussDealMd);
bodyMap.put("SroNo", sroNo);
bodyMap.put("RsrvFld1", clientId);
bodyMap.put("AplParmObjct", encrypt);
// 发送请求
String response = HttpUtil.createPost(clientBAB7ServerUrl)
.contentType("application/json;charset=UTF-8")
.body(JSONUtil.toJsonStr(bodyMap))
.execute()
.body();
log.info("9.3.1 对账异常通知接口返回报文:{}", response);
return JSONUtil.toBean(response, MallInboundResult.class);
}
}
@@ -48,15 +48,11 @@ public class PointsMallReconciliationController {
}
}
@At
@At("/execute")
@SLog(tag = "积分商城对账管理", msg = "执行订单对账")
@SaCheckPermission("points.mall.reconciliation")
public Result execute(PointsMallReconciliationParam param) {
try {
return Result.success(pointsMallReconciliationService.execute(param));
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
return pointsMallReconciliationService.executeReconciliation(param);
}
@At
@@ -1,6 +1,7 @@
package com.budwk.app.zhgh.pointsmall.reconciliation.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.pointsmall.reconciliation.models.PointsMallReconciliationRecord;
import com.budwk.app.zhgh.pointsmall.reconciliation.param.PointsMallReconciliationOrderPageParam;
@@ -18,7 +19,7 @@ public interface PointsMallReconciliationService extends BaseService<PointsMallR
BigDecimal pointsTotal(PointsMallReconciliationParam param);
String execute(PointsMallReconciliationParam param);
Result executeReconciliation(PointsMallReconciliationParam param);
Pagination<Record> recordPage(PointsMallReconciliationRecordPageParam param);
@@ -2,35 +2,65 @@ package com.budwk.app.zhgh.pointsmall.reconciliation.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.OrderInfoByReconciliationDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.OrderProInfoDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.QueryReconciliationInfoDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.ReconciliationResultNoticeDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.ReconciliationResultOrderDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.UnReconciledMsgNoticeDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallReconciliationInfoQueryService;
import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallReconciliationResultNoticeService;
import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallUnReconciledMsgNoticeService;
import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSub;
import com.budwk.app.zhgh.pointsmall.reconciliation.models.PointsMallReconciliationOrder;
import com.budwk.app.zhgh.pointsmall.reconciliation.models.PointsMallReconciliationRecord;
import com.budwk.app.zhgh.pointsmall.reconciliation.param.PointsMallReconciliationOrderPageParam;
import com.budwk.app.zhgh.pointsmall.reconciliation.param.PointsMallReconciliationParam;
import com.budwk.app.zhgh.pointsmall.reconciliation.param.PointsMallReconciliationRecordPageParam;
import com.budwk.app.zhgh.pointsmall.reconciliation.service.PointsMallReconciliationService;
import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.entity.Record;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 对账功能
*/
@Slf4j
@IocBean(args = {"refer:dao"})
public class PointsMallReconciliationServiceImpl extends BaseServiceImpl<PointsMallReconciliationRecord> implements PointsMallReconciliationService {
@Inject
private MallReconciliationInfoQueryService mallReconciliationInfoQueryService;
@Inject
private MallUnReconciledMsgNoticeService unReconciledMsgNoticeService;
@Inject
private MallReconciliationResultNoticeService reconciliationResultNoticeService;
public PointsMallReconciliationServiceImpl(Dao dao) {
super(dao);
}
@@ -53,37 +83,145 @@ public class PointsMallReconciliationServiceImpl extends BaseServiceImpl<PointsM
return record == null || record.get("totalPoints") == null ? BigDecimal.ZERO : new BigDecimal(record.get("totalPoints").toString());
}
/**
* 执行对账
*
* @param reconciliationBO 入参
*/
@Override
public String execute(PointsMallReconciliationParam param) {
checkReconciliationParam(param);
List<PointsMallOrderSub> orders = dao().query(PointsMallOrderSub.class, reconciliationCnd(param, ""));
if (CollUtil.isEmpty(orders)) {
return "未查询到待对账订单";
}
String batchNo = DateUtil.format(new Date(), "yyyyMMddHHmmss") + IdUtil.fastSimpleUUID().substring(0, 6);
int success = 0;
int exception = 0;
List<PointsMallReconciliationRecord> records = new ArrayList<>();
for (PointsMallOrderSub order : orders) {
PointsMallReconciliationOrder supplierOrder = dao().fetch(PointsMallReconciliationOrder.class,
Cnd.where("orderId", "=", order.getOrderId())
.and("supplierId", "=", order.getSupplierId())
.and("delFlag", "=", false));
PointsMallReconciliationRecord record = buildRecord(order, supplierOrder, batchNo);
if (record.getDiffType() == null || record.getDiffType() == 0) {
success++;
dao().update(PointsMallOrderSub.class, Chain.make("reconciliationStatus", 1), Cnd.where("id", "=", order.getId()));
} else {
exception++;
dao().update(PointsMallOrderSub.class, Chain.make("reconciliationStatus", 2), Cnd.where("id", "=", order.getId()));
public Result executeReconciliation(PointsMallReconciliationParam reconciliationBO) {
Integer reconciliationType = reconciliationBO.getReconciliationType();
List<String> orderIdList = null;
// 校验参数
if (reconciliationType == 1) {
if (StrUtil.isBlank(reconciliationBO.getStartDate())) {
return Result.error("请选择开始日期");
}
records.add(record);
} else if (reconciliationType == 2) { // 历史异常订单对账
if (StrUtil.isBlank(reconciliationBO.getOrderId())) {
return Result.error("订单号不能为空");
}
orderIdList = StrUtil.splitTrim(reconciliationBO.getOrderId(), ",");
} else {
return Result.error("对账类型不正确");
}
dao().insert(records);
return "对账完成,批次号:" + batchNo + ",成功:" + success + ",异常:" + exception;
// 从浦发积分商城订单表查询对账订单
List<PointsMallOrderSub> orderInfoList = dao().query(PointsMallOrderSub.class, reconciliationCnd(reconciliationBO, ""));
if (CollUtil.isEmpty(orderInfoList)) {
return Result.error("浦发积分商城没有查询到对账订单数据");
}
QueryReconciliationInfoDTO queryReconciliationInfoDTO = new QueryReconciliationInfoDTO();
queryReconciliationInfoDTO.setSupplierId(reconciliationBO.getSupplierId());
queryReconciliationInfoDTO.setReconciliationType(reconciliationType);
queryReconciliationInfoDTO.setOrderId(orderIdList == null ? reconciliationBO.getOrderId() : String.join(",", orderIdList));
queryReconciliationInfoDTO.setStartDate(reconciliationBO.getStartDate());
queryReconciliationInfoDTO.setEndDate(reconciliationBO.getEndDate());
queryReconciliationInfoDTO.setPage(1);
queryReconciliationInfoDTO.setPageSize(100);
// 通过对账接口从供应商拉取对账订单数据
List<OrderInfoByReconciliationDTO> result = mallReconciliationInfoQueryService.queryReconciliationInfo(queryReconciliationInfoDTO);
if (CollUtil.isEmpty(result)) {
return Result.error("供应商侧没有拉取到对账订单数据");
}
String supplierId = reconciliationBO.getSupplierId();
result.forEach(o -> o.setSupplierId(supplierId));
// 根据供应商ID + 订单ID进行分组
Map<String, List<OrderInfoByReconciliationDTO>> supplierOrderList = result.stream()
.collect(Collectors.groupingBy(o -> String.join("-", o.getSupplierId(), o.getOrderId())));
// 根据供应商ID + 订单ID进行分组
Map<String, List<PointsMallOrderSub>> pfOrderList = orderInfoList.stream()
.collect(Collectors.groupingBy(o -> String.join("-", o.getSupplierId(), o.getOrderId())));
Set<String> pfKeySet = pfOrderList.keySet();
Set<String> supplierKeySet = supplierOrderList.keySet();
// 浦发商城和供应商都存在的订单
Set<String> sameKeySet = pfKeySet.stream().filter(supplierKeySet::contains).collect(Collectors.toSet());
// 1.浦发商城和供应商两边的《供应商ID + 订单ID》可以匹配上
List<PointsMallReconciliationRecord> reconciliationList1 = getReconciliationResult(sameKeySet, pfOrderList, supplierOrderList);
if (CollUtil.isNotEmpty(reconciliationList1)) {
List<PointsMallReconciliationRecord> reconciliationList0 = reconciliationList1.stream()
.filter(o -> o.getDiffType() == 3).collect(Collectors.toList());
if (CollUtil.isNotEmpty(reconciliationList0)) {
UnReconciledMsgNoticeDTO noticeDTO = getUnReconciledMsgNoticeDTO(reconciliationBO, reconciliationList0);
noticeDTO.setOrderIds(reconciliationList0.stream().map(PointsMallReconciliationRecord::getOrderId).collect(Collectors.joining(",")));
// 更新子订单状态为对账异常
subOrderReconciliationUpdate(reconciliationList0, 2);
// 对账异常结果通知接口
unReconciledMsgNoticeService.unReconciledMsgNotice(noticeDTO);
}
List<PointsMallReconciliationRecord> reconciliationSuccessList = reconciliationList1.stream()
.filter(o -> o.getDiffType() == 0).collect(Collectors.toList());
if (CollUtil.isNotEmpty(reconciliationSuccessList)) {
// 更新子订单状态为对账完成
subOrderReconciliationUpdate(reconciliationSuccessList, 1);
}
}
// 2.浦发积分商城已确认收货,对账接口未返回
List<PointsMallReconciliationRecord> reconciliationList2 = orderNotReturn(pfKeySet, supplierKeySet, pfOrderList);
if (CollUtil.isNotEmpty(reconciliationList2)) {
reconciliationList1.addAll(reconciliationList2);
UnReconciledMsgNoticeDTO noticeDTO = getUnReconciledMsgNoticeDTO(reconciliationBO, reconciliationList2);
noticeDTO.setOrderIds(reconciliationList2.stream().map(PointsMallReconciliationRecord::getOrderId).collect(Collectors.joining(",")));
// 更新子订单状态为对账异常
subOrderReconciliationUpdate(reconciliationList2, 2);
// 对账异常结果通知接口
unReconciledMsgNoticeService.unReconciledMsgNotice(noticeDTO);
}
// 3.浦发积分商城不存在此订单,无法对账
List<PointsMallReconciliationRecord> reconciliationList3 = orderNotExist(supplierKeySet, pfKeySet, supplierOrderList);
if (CollUtil.isNotEmpty(reconciliationList3)) {
reconciliationList1.addAll(reconciliationList3);
UnReconciledMsgNoticeDTO noticeDTO = getUnReconciledMsgNoticeDTO(reconciliationBO, reconciliationList3);
noticeDTO.setOrderIds(reconciliationList3.stream().map(PointsMallReconciliationRecord::getOrderId).collect(Collectors.joining(",")));
// 更新子订单状态为对账异常
// subOrderReconciliationUpdate(reconciliationList3, 2);
// 对账异常结果通知接口
unReconciledMsgNoticeService.unReconciledMsgNotice(noticeDTO);
}
if (CollUtil.isNotEmpty(reconciliationList1)) {
String startDate = reconciliationBO.getStartDate();
String endDate = reconciliationBO.getEndDate();
String startEndDate = StrUtil.blankToDefault(startDate, "history").replace("-", "")
+ StrUtil.blankToDefault(endDate, "").replace("-", "");
String loginName = StrUtil.blankToDefault(SecurityUtil.getUserId(), "console");
LocalDateTime now = LocalDateTime.now();
String yyyyMMddHHmm = now.format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
reconciliationList1.forEach(o ->
o.setBatchNo(String.format("%s-%s-%s", startEndDate, loginName, yyyyMMddHHmm)));
// 把对账结果写入数据库
dao().insert(reconciliationList1);
ReconciliationResultNoticeDTO resultNoticeDTO = new ReconciliationResultNoticeDTO();
resultNoticeDTO.setSupplierId(reconciliationBO.getSupplierId());
resultNoticeDTO.setReconciliationBatchNo(reconciliationList1.get(0).getBatchNo());
resultNoticeDTO.setReconciliationTime(now.toString());
resultNoticeDTO.setReconciliationResults(reconciliationList1.stream().map(o -> {
ReconciliationResultOrderDTO resultOrderDTO = new ReconciliationResultOrderDTO();
resultOrderDTO.setOrderId(o.getOrderId());
resultOrderDTO.setUserId(o.getUserId());
resultOrderDTO.setReconciliationStatus(o.getDiffType() == 0 ? 1 : 2);
resultOrderDTO.setDiffType(o.getDiffType());
resultOrderDTO.setDiffReason(o.getDiffReason());
resultOrderDTO.setPlatformTotalPrice(o.getPTotalPrice());
resultOrderDTO.setPlatformCashAmount(o.getPCashAmount());
resultOrderDTO.setPlatformPointsPrice(o.getPPointsPrice());
resultOrderDTO.setPlatformRefundPrice(o.getPRefund());
resultOrderDTO.setSupplierTotalPrice(o.getSTotalPrice());
resultOrderDTO.setSupplierCashAmount(o.getSCashAmount());
resultOrderDTO.setSupplierPointsPrice(o.getSPointsPrice());
resultOrderDTO.setSupplierRefundPrice(o.getSRefund());
return resultOrderDTO;
}).collect(Collectors.toList()));
reconciliationResultNoticeService.reconciliationResultNotice(resultNoticeDTO);
}
return Result.success();
}
@Override
@@ -192,54 +330,228 @@ public class PointsMallReconciliationServiceImpl extends BaseServiceImpl<PointsM
}
}
private PointsMallReconciliationRecord buildRecord(PointsMallOrderSub order, PointsMallReconciliationOrder supplierOrder, String batchNo) {
PointsMallReconciliationRecord record = new PointsMallReconciliationRecord();
record.setSupplierId(order.getSupplierId());
record.setOrderId(order.getOrderId());
record.setUserId(order.getUserId());
record.setPRefund(nullToZero(order.getRefund()));
record.setPTotalPrice(nullToZero(order.getTotalPrice()));
record.setPCashAmount(nullToZero(order.getWPayOrAPay()));
record.setPPointsPrice(nullToZero(order.getPointsPrice()));
record.setBatchNo(batchNo);
record.setNoticeStatus(0);
record.setPushTime(0);
if (supplierOrder == null) {
record.setSRefund(BigDecimal.ZERO);
record.setSTotalPrice(record.getPTotalPrice());
record.setSCashAmount(record.getPCashAmount());
record.setSPointsPrice(record.getPPointsPrice());
record.setDiffType(0);
record.setDiffReason("未获取到供应商侧对账订单,按平台订单确认");
return record;
}
record.setSRefund(BigDecimal.ZERO);
record.setSTotalPrice(nullToZero(supplierOrder.getTotalPrice()));
record.setSCashAmount(BigDecimal.ZERO);
record.setSPointsPrice(nullToZero(supplierOrder.getTotalPrice()));
List<String> reasons = new ArrayList<>();
if (!amountEquals(record.getPTotalPrice(), record.getSTotalPrice())) {
reasons.add("订单总金额不一致");
}
if (!amountEquals(record.getPPointsPrice(), record.getSPointsPrice())) {
reasons.add("积分金额不一致");
}
if (!Objects.equals(order.getUserId(), supplierOrder.getUserId())) {
reasons.add("用户ID不一致");
}
record.setDiffType(reasons.isEmpty() ? 0 : 1);
record.setDiffReason(reasons.isEmpty() ? "对账一致" : String.join("", reasons));
return record;
private void subOrderReconciliationUpdate(List<PointsMallReconciliationRecord> reconciliationList, Integer reconciliationStatus) {
// 更新子订单状态为对账异常
reconciliationList.forEach(o -> dao().update(PointsMallOrderSub.class,
Chain.make("reconciliationStatus", reconciliationStatus),
Cnd.where("orderId", "=", o.getOrderId()).and("supplierId", "=", o.getSupplierId())));
}
private BigDecimal nullToZero(BigDecimal value) {
return value == null ? BigDecimal.ZERO : value;
private UnReconciledMsgNoticeDTO getUnReconciledMsgNoticeDTO(PointsMallReconciliationParam reconciliationBO,
List<PointsMallReconciliationRecord> reconciliationList2) {
UnReconciledMsgNoticeDTO noticeDTO = new UnReconciledMsgNoticeDTO();
noticeDTO.setUniqueSeqNo("111");
noticeDTO.setToken("token");
noticeDTO.setSupplierId(reconciliationBO.getSupplierId());
noticeDTO.setStartDate(reconciliationBO.getStartDate());
noticeDTO.setEndDate(reconciliationBO.getEndDate());
PointsMallReconciliationRecord reconciliation = reconciliationList2.get(0);
noticeDTO.setUnReconciledType(reconciliation.getDiffType() + "");
noticeDTO.setUnReconciledMsg(reconciliation.getDiffReason());
return noticeDTO;
}
private boolean amountEquals(BigDecimal left, BigDecimal right) {
return nullToZero(left).compareTo(nullToZero(right)) == 0;
/**
* 浦发积分商城已确认收货,对账接口未返回
*
* @param pfKeySet 浦发key
* @param supplierKeySet 供应商key
* @param pfOrderList 浦发商城对账订单
* @return 异常订单
*/
private List<PointsMallReconciliationRecord> orderNotReturn(Set<String> pfKeySet, Set<String> supplierKeySet,
Map<String, List<PointsMallOrderSub>> pfOrderList) {
List<PointsMallReconciliationRecord> reconciliationList = new ArrayList<>();
pfKeySet.stream().filter(key -> !supplierKeySet.contains(key)).forEach(key -> {
List<PointsMallOrderSub> pfOrders = pfOrderList.get(key);
if (CollUtil.isNotEmpty(pfOrders)) {
reconciliationList.add(buildReconciliationRecord(null, pfOrders.get(0), 1, "浦发积分商城已确认收货,对账接口未返回"));
}
});
return reconciliationList;
}
/**
* 浦发积分商城不存在此订单,无法对账
*
* @param supplierKeySet 供应商key
* @param pfKeySet 浦发key
* @param supplierOrderList 供应商对账订单
* @return 异常订单
*/
private List<PointsMallReconciliationRecord> orderNotExist(Set<String> supplierKeySet, Set<String> pfKeySet,
Map<String, List<OrderInfoByReconciliationDTO>> supplierOrderList) {
List<PointsMallReconciliationRecord> reconciliationList = new ArrayList<>();
supplierKeySet.stream().filter(key -> !pfKeySet.contains(key)).forEach(key -> {
List<OrderInfoByReconciliationDTO> supplierOrders = supplierOrderList.get(key);
if (CollUtil.isNotEmpty(supplierOrders)) {
reconciliationList.add(buildReconciliationRecord(supplierOrders.get(0), null, 2, "浦发积分商城不存在此订单,无法对账"));
}
});
return reconciliationList;
}
/**
* 浦发商城和供应商两边的《供应商ID + 订单ID》可以匹配上
*
* @param sameKeySet 双方都存在的订单ID
* @param pfOrderList 浦发商城的订单
* @param supplierOrderList 供应商的订单
* @return 对账结果
*/
private List<PointsMallReconciliationRecord> getReconciliationResult(Set<String> sameKeySet,
Map<String, List<PointsMallOrderSub>> pfOrderList,
Map<String, List<OrderInfoByReconciliationDTO>> supplierOrderList) {
List<PointsMallReconciliationRecord> reconciliationList = new ArrayList<>();
sameKeySet.forEach(key -> {
List<PointsMallOrderSub> pOrderList = pfOrderList.get(key);
List<OrderInfoByReconciliationDTO> sOrderList = supplierOrderList.get(key);
BigDecimal pfPointSum = pOrderList.stream()
.map(o -> (o.getPointsPrice() == null ? BigDecimal.ZERO : o.getPointsPrice())
.subtract(o.getRefund() == null ? BigDecimal.ZERO : o.getRefund()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal supplierPointSum = sOrderList.stream()
.map(OrderInfoByReconciliationDTO::getTotalPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 根据供应商ID和订单ID比对积分总金额是否一致
if (pfPointSum.compareTo(supplierPointSum) != 0) {
reconciliationList.add(buildReconciliationRecord(sOrderList.get(0), pOrderList.get(0), 3, "订单内容不一致,订单金额不一致"));
return;
}
if (CollUtil.isNotEmpty(pOrderList)) {
OrderInfoByReconciliationDTO sOrder = sOrderList.get(0);
PointsMallOrderSub pfOrder = pOrderList.get(0);
PointsMallReconciliationRecord reconciliation = buildReconciliationRecord(sOrder, pfOrder, 0, "无差异");
// 比对商品明细的sku,商品数量,商品价格,商品税率是否一致
if (CollUtil.isNotEmpty(sOrderList)) {
String productInfo = pfOrder.getExtJson();
// 解析积分商品下的商品明细
List<OrderProInfoDTO> pfOrderProList = JSONUtil.toList(productInfo, OrderProInfoDTO.class);
// 处理商品名称、价格、税率的小数位(不参与比较的字段)
handleField(pfOrderProList);
// 从对账接口获取的订单商品
OrderInfoByReconciliationDTO supplierOrder = sOrderList.get(0);
List<OrderProInfoDTO> supplierOrderProList = supplierOrder.getOrderProInfos();
handleField(supplierOrderProList);
// 比较双方商品明细是否一致
if (!compareProduct(pfOrderProList, supplierOrderProList)) {
reconciliation.setDiffType(3);
reconciliation.setDiffReason("订单内容不一致,商品明细不一致");
}
}
reconciliationList.add(reconciliation);
}
});
return reconciliationList;
}
/**
* 处理商品价格和税率字段的小数位,保证对比的时候小数位一致
*
* @param productList 订单下的商品集合
*/
private void handleField(List<OrderProInfoDTO> productList) {
// 商品名称不参与比较,价格和税率小数点位数处理
productList.forEach(o -> {
o.setName(null);
if (o.getPrice() != null) {
o.setPrice(o.getPrice().setScale(2, RoundingMode.HALF_UP));
}
if (o.getTaxRate() != null) {
o.setTaxRate(o.getTaxRate().setScale(6, RoundingMode.HALF_UP));
}
});
}
/**
* 比较两个list<bean>是否完全一致,不考虑顺序
*
* @param list1 集合1
* @param list2 集合2
* @return 是否相同
*/
private boolean compareProduct(List<OrderProInfoDTO> list1, List<OrderProInfoDTO> list2) {
// 1. 如果两个列表是同一个引用,直接返回 true
if (list1 == list2) {
return true;
}
// 2. 如果其中有一个为 null,或者商品总数不一致,直接返回 false
if (list1 == null || list2 == null || list1.size() != list2.size()) {
return false;
}
// 3. 遍历第一个订单的商品,检查是否都能在第二个订单中找到匹配的商品
for (OrderProInfoDTO p1 : list1) {
boolean foundMatch = false;
for (OrderProInfoDTO p2 : list2) {
// 通过sku+number+price+taxRate相同判定为商品信息一致
if (Objects.equals(p1.getSku(), p2.getSku()) &&
Objects.equals(p1.getNumber(), p2.getNumber()) &&
compareBigDecimal(p1.getPrice(), p2.getPrice()) &&
compareBigDecimal(p1.getTaxRate(), p2.getTaxRate())) {
foundMatch = true;
break;
}
}
if (!foundMatch) {
return false;
}
}
return true;
}
private boolean compareBigDecimal(BigDecimal a, BigDecimal b) {
// 两者都为空,视为相等
if (a == null && b == null) {
return true;
}
// 只有一个为空,视为不相等
if (a == null || b == null) {
return false;
}
// 都不为空,使用compareTo 比较数值
return a.compareTo(b) == 0;
}
/**
* 订单积分对账数据赋值
*
* @param sOrder 对账接口返回的订单对账数据
* @param pOrder 子订单表的订单数据
* @param diffType 异常类型
* @param diffReason 异常原因
* @return 对账结果
*/
private PointsMallReconciliationRecord buildReconciliationRecord(OrderInfoByReconciliationDTO sOrder,
PointsMallOrderSub pOrder,
int diffType, String diffReason) {
PointsMallReconciliationRecord reconciliation = new PointsMallReconciliationRecord();
reconciliation.setDiffType(diffType);
reconciliation.setDiffReason(diffReason);
reconciliation.setNoticeStatus(0);
reconciliation.setPushTime(0);
if (sOrder != null) {
// 对账接口返回数据
reconciliation.setSupplierId(sOrder.getSupplierId());
reconciliation.setOrderId(sOrder.getOrderId());
reconciliation.setSTotalPrice(sOrder.getTotalPrice());
reconciliation.setSPointsPrice(sOrder.getPointsPrice());
reconciliation.setSCashAmount(sOrder.getWPayOrAPay());
}
if (pOrder != null) {
// 订单表里数据
reconciliation.setSupplierId(pOrder.getSupplierId());
reconciliation.setOrderId(pOrder.getOrderId());
reconciliation.setUserId(pOrder.getUserId());
reconciliation.setPTotalPrice(pOrder.getTotalPrice());
reconciliation.setPPointsPrice(pOrder.getPointsPrice());
reconciliation.setPCashAmount(pOrder.getWPayOrAPay());
reconciliation.setPRefund(pOrder.getRefund());
}
return reconciliation;
}
}