diff --git a/src/main/java/com/budwk/app/sys/models/Sys_user.java b/src/main/java/com/budwk/app/sys/models/Sys_user.java index 20985e24..37753d57 100644 --- a/src/main/java/com/budwk/app/sys/models/Sys_user.java +++ b/src/main/java/com/budwk/app/sys/models/Sys_user.java @@ -411,6 +411,15 @@ public class Sys_user extends BaseModel implements Serializable { @ColDefine(type = ColType.VARCHAR, width = 50) private String openId; + @Column("virtual_id") + @Comment("虚拟用户ID") + @ColDefine(type = ColType.VARCHAR, width = 64) + private String virtualId; + + @Column("virtual_org_id") + @Comment("虚拟组织ID") + @ColDefine(type = ColType.VARCHAR, width = 255) + private String virtualOrgId; /** * 基金会员 diff --git a/src/main/java/com/budwk/app/sys/services/SysFileService.java b/src/main/java/com/budwk/app/sys/services/SysFileService.java index 9976d46b..88f07f0f 100644 --- a/src/main/java/com/budwk/app/sys/services/SysFileService.java +++ b/src/main/java/com/budwk/app/sys/services/SysFileService.java @@ -25,6 +25,8 @@ public interface SysFileService extends BaseService { */ String uploadReturnUrl(String engine, TempFile file); + String uploadReturnUrl(String engine, String fileName, byte[] bytes); + /** * 分页 */ diff --git a/src/main/java/com/budwk/app/sys/services/impl/SysFileServiceImpl.java b/src/main/java/com/budwk/app/sys/services/impl/SysFileServiceImpl.java index df36d760..4f730181 100644 --- a/src/main/java/com/budwk/app/sys/services/impl/SysFileServiceImpl.java +++ b/src/main/java/com/budwk/app/sys/services/impl/SysFileServiceImpl.java @@ -69,6 +69,11 @@ public class SysFileServiceImpl extends BaseServiceImpl implements Sys return this.storageFile(engine, file, false); } + @Override + public String uploadReturnUrl(String engine, String fileName, byte[] bytes) { + return this.storageFile(engine, fileName, bytes, false); + } + @Override public Pagination page(Sys_file file) { return null; diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/controller/PointsMallInvoiceController.java b/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/controller/PointsMallInvoiceController.java index f32cee59..b1962558 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/controller/PointsMallInvoiceController.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/controller/PointsMallInvoiceController.java @@ -37,13 +37,34 @@ public class PointsMallInvoiceController { @At @SaCheckPermission("points.mall.invoice") public Result infoPage(PointsMallInvoiceInfoPageParam param) { - return Result.success(pointsMallInvoiceService.infoPage(param)); + try { + return Result.success(pointsMallInvoiceService.infoPage(param)); + } catch (IllegalArgumentException e) { + return Result.error(e.getMessage()); + } } @At @SaCheckPermission("points.mall.invoice") public Result infoDetail(@Param("id") String id) { - return Result.success(pointsMallInvoiceService.infoDetail(id)); + try { + return Result.success(pointsMallInvoiceService.infoDetail(id)); + } catch (IllegalArgumentException e) { + return Result.error(e.getMessage()); + } + } + + @At + @org.nutz.mvc.annotation.POST + @SLog(tag = "积分商城发票管理", msg = "重新获取发票") + @SaCheckPermission("points.mall.invoice") + public Result retryInvoice(@Param("mainId") String mainId) { + try { + pointsMallInvoiceService.retryInvoice(mainId); + return Result.success(); + } catch (IllegalArgumentException e) { + return Result.error(e.getMessage()); + } } @At diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/models/PointsMallInvoiceInfo.java b/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/models/PointsMallInvoiceInfo.java index 45922f11..45fee015 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/models/PointsMallInvoiceInfo.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/models/PointsMallInvoiceInfo.java @@ -16,6 +16,9 @@ import java.util.Date; @Comment("积分商城发票信息") public class PointsMallInvoiceInfo extends BaseModel implements Serializable { + // 详情实时读取供应商 PDF 生成结果:1 成功,2 失败,不以本地文件地址推断状态。 + private Integer status; + @Name @Column @Comment("ID") diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/param/PointsMallInvoiceApplyParam.java b/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/param/PointsMallInvoiceApplyParam.java index 62c63f96..e59cdee1 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/param/PointsMallInvoiceApplyParam.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/param/PointsMallInvoiceApplyParam.java @@ -7,11 +7,13 @@ public class PointsMallInvoiceApplyParam { private String supplierId; private String orderIds; + private String queryOrderId; + private String userId; private String startDate; private String endDate; private String invoiceDate; private String invoiceTitle; - private String invoiceCode; + private String taxIdNumber; private String invoiceAddress; private String invoiceContact; private Integer invoiceType; diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/service/PointsMallInvoiceService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/service/PointsMallInvoiceService.java index 49d8e144..cd09dda3 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/service/PointsMallInvoiceService.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/service/PointsMallInvoiceService.java @@ -3,6 +3,9 @@ package com.budwk.app.zhgh.pointsmall.invoice.service; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.service.BaseService; import com.budwk.app.zhgh.pointsmall.invoice.models.PointsMallInvoiceMain; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderEventDTO; +import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult; +import com.budwk.app.zhgh.pointsmall.invoice.models.PointsMallInvoiceInfo; import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceApplyParam; import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceInfoPageParam; import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceMainPageParam; @@ -15,11 +18,19 @@ import java.util.List; public interface PointsMallInvoiceService extends BaseService { + /** 同步供应商发票信息及本地文件。 */ + MallInboundResult invoiceInfo(MallOrderEventDTO dto); + + /** 同步发票,可指定是否重新生成失败发票。 */ + MallInboundResult invoiceInfo(MallOrderEventDTO dto, boolean retry); + Pagination mainPage(PointsMallInvoiceMainPageParam param); Pagination infoPage(PointsMallInvoiceInfoPageParam param); - Record infoDetail(String id); + PointsMallInvoiceInfo infoDetail(String id); + + void retryInvoice(String mainId); Pagination orderPage(PointsMallInvoiceOrderPageParam param); diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/service/impl/PointsMallInvoiceServiceImpl.java b/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/service/impl/PointsMallInvoiceServiceImpl.java index 2ea00da8..c107e12f 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/service/impl/PointsMallInvoiceServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/invoice/service/impl/PointsMallInvoiceServiceImpl.java @@ -6,39 +6,207 @@ import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.service.impl.BaseServiceImpl; -import com.budwk.app.zhgh.pointsmall.invoice.models.PointsMallInvoiceInfo; import com.budwk.app.zhgh.pointsmall.invoice.models.PointsMallInvoiceMain; import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceApplyParam; import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceInfoPageParam; import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceMainPageParam; import com.budwk.app.zhgh.pointsmall.invoice.param.PointsMallInvoiceOrderPageParam; import com.budwk.app.zhgh.pointsmall.invoice.service.PointsMallInvoiceService; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallInvoiceApplyDTO; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.InvoiceInfoDTO; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.QueryInvoiceInfoDTO; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.GetInvoiceInfoDTO; +import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallOutboundResult; +import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallInvoiceApplyOutboundService; +import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallInvoiceInfoQueryService; import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSub; import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier; import org.nutz.dao.Chain; +import org.nutz.trans.Atom; 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 org.nutz.trans.Trans; import java.math.BigDecimal; +import com.budwk.app.zhgh.pointsmall.invoice.models.PointsMallInvoiceInfo; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderEventDTO; +import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult; +import com.budwk.app.sys.enums.SysFileEngineTypeEnum; +import com.budwk.app.sys.services.SysFileService; +import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallPictureOutboundResult; +import lombok.extern.slf4j.Slf4j; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +@Slf4j @IocBean(args = {"refer:dao"}) public class PointsMallInvoiceServiceImpl extends BaseServiceImpl implements PointsMallInvoiceService { + @Inject + private MallInvoiceApplyOutboundService mallInvoiceApplyOutboundService; + @Inject + private SysFileService sysFileService; + @Inject + private MallInvoiceInfoQueryService mallInvoiceInfoQueryService; + public PointsMallInvoiceServiceImpl(Dao dao) { super(dao); } + /** 处理供应商发票信息通知,查询发票并同步文件和开票结果。 */ + @Override + public MallInboundResult invoiceInfo(MallOrderEventDTO dto) { + return invoiceInfo(dto, false); + } + + /** retry=true 时允许供应商重新生成失败发票,后续复用相同的同步流程。 */ + @Override + public MallInboundResult invoiceInfo(MallOrderEventDTO dto, boolean retry) { + String settlementId = dto.getOrderId(); + if (StrUtil.hasBlank(dto.getSupplierId(), settlementId)) { + return MallInboundResult.fail(400, "供应商和结算单号不能为空"); + } + QueryInvoiceInfoDTO query = new QueryInvoiceInfoDTO(); + query.setSupplierId(dto.getSupplierId()); + query.setSettlementId(settlementId); + query.setRetry(retry); + GetInvoiceInfoDTO result; + try { + result = mallInvoiceInfoQueryService.queryInvoicesInfo(query); + } catch (Exception e) { + log.error("调用供应商获取发票信息失败,supplierId={},settlementId={}", dto.getSupplierId(), settlementId, e); + return MallInboundResult.fail(-1, "获取供应商发票信息失败"); + } + + PointsMallInvoiceMain main = dao().fetch(PointsMallInvoiceMain.class, + Cnd.where("settlementId", "=", settlementId).and("delFlag", "=", false)); + if (main == null) { + return MallInboundResult.fail(404, "开票申请记录不存在"); + } + if (result == null || !settlementId.equals(result.getSettlementId())) { + return MallInboundResult.fail(502, "供应商返回的发票结算单不匹配"); + } + Integer success = result.getBSuccess() == null ? 0 : result.getBSuccess(); + if (success == 0) { + if (result.getInvoiceInfos() == null || result.getInvoiceInfos().isEmpty() || StrUtil.isBlank(result.getSucOrderIds())) { + return MallInboundResult.fail(502, "供应商开票结果缺少发票明细或成功订单"); + } + try { + List fileIds = result.getInvoiceInfos().stream().map(InvoiceInfoDTO::getInvoiceId).collect(Collectors.toList()); + if (fileIds.stream().anyMatch(StrUtil::isBlank)) { + throw new IllegalArgumentException("供应商发票文件标识为空"); + } + MallPictureOutboundResult files = mallInvoiceInfoQueryService.queryInvoiceFiles(dto.getSupplierId(), settlementId, fileIds); + for (InvoiceInfoDTO item : result.getInvoiceInfos()) { + if (item.getStatus() != null && item.getStatus() != 1) { + throw new IllegalArgumentException("供应商发票尚未生成成功"); + } + String base64 = files.getImageEncodeMap() == null ? null : files.getImageEncodeMap().get(item.getInvoiceId()); + if (StrUtil.isBlank(base64)) { + throw new IllegalArgumentException("未获取到发票文件Base64:" + item.getInvoiceId()); + } + if (base64.startsWith("data:")) { + base64 = base64.substring(base64.indexOf(',') + 1); + } + byte[] bytes = Base64.getDecoder().decode(base64.replaceAll("\\s", "")); + if (bytes.length == 0) { + throw new IllegalArgumentException("发票文件内容为空"); + } + String fileType = StrUtil.blankToDefault(files.getFileType(), StrUtil.blankToDefault(item.getFileType(), "PDF")); + if (!fileType.matches("(?i)pdf|jpg|jpeg|png")) { + throw new IllegalArgumentException("不支持的发票文件类型:" + fileType); + } + String localUrl = sysFileService.uploadReturnUrl(SysFileEngineTypeEnum.MINIO.getValue(), + "invoice-" + java.util.UUID.randomUUID() + "." + fileType.toLowerCase(java.util.Locale.ROOT), bytes); + item.setUrl(localUrl); + item.setImageEncode(localUrl); + item.setFileType(fileType); + } + } catch (Exception e) { + log.error("发票文件同步失败,settlementId={}", settlementId, e); + return MallInboundResult.fail(502, "发票文件同步失败:" + e.getMessage()); + } + } + Trans.exec((Atom) () -> { + main.setBSuccess(success); + main.setSucOrderIds(result.getSucOrderIds()); + main.setFailOrderIds(result.getFailOrderIds()); + main.setFailMsg(result.getFailMsg()); + if (result.getInvoiceInfos() != null) { + for (InvoiceInfoDTO item : result.getInvoiceInfos()) { + PointsMallInvoiceInfo info = dao().fetch(PointsMallInvoiceInfo.class, + Cnd.where("mainId", "=", main.getId()).and("invoiceId", "=", item.getInvoiceId()).and("delFlag", "=", false)); + if (info == null) { + info = new PointsMallInvoiceInfo(); + info.setMainId(main.getId()); + info.setInvoiceId(item.getInvoiceId()); + } + info.setInvoiceCode(item.getInvoiceCode()); + info.setInvoiceDate(item.getInvoiceDate() == null ? null : DateUtil.parse(item.getInvoiceDate().toString())); + info.setInvoiceNakeAmount(item.getInvoiceNakeAmount()); + info.setInvoiceTaxRate(item.getInvoiceTaxRate()); + info.setInvoiceTaxAmount(item.getInvoiceTaxAmount()); + info.setInvoiceAmount(item.getInvoiceAmount()); + info.setInvoiceType(item.getInvoiceType()); + info.setUrl(item.getUrl()); + info.setImageEncode(item.getImageEncode()); + info.setFileType(item.getFileType()); + info.setCheckCode(item.getCheckCode()); + info.setInvoiceAddress(item.getInvoiceAddress()); + info.setRemark(item.getRemark()); + if (success != 0) { + info.setUrl(""); + info.setImageEncode(""); + } + if (StrUtil.isBlank(info.getId())) { + dao().insert(info); + } else { + dao().updateIgnoreNull(info); + } + } + } + + List successOrderIds = splitInvoiceOrderIds(result.getSucOrderIds()); + List failOrderIds = splitInvoiceOrderIds(result.getFailOrderIds()); + if (!successOrderIds.isEmpty()) { + dao().update(PointsMallOrderSub.class, Chain.make("invoiceStatus", 1), Cnd.where("supplierId", "=", dto.getSupplierId()) + .and("orderId", "in", successOrderIds).and("delFlag", "=", false)); + } + if (!failOrderIds.isEmpty()) { + dao().update(PointsMallOrderSub.class, Chain.make("invoiceStatus", 0), Cnd.where("supplierId", "=", dto.getSupplierId()) + .and("orderId", "in", failOrderIds).and("delFlag", "=", false)); + } + dao().updateIgnoreNull(main); + }); + log.info("供应商发票信息更新完成,supplierId={},settlementId={}", dto.getSupplierId(), settlementId); + return MallInboundResult.success(); + } + + /** 拆分开票结果中的订单号,去除空项和重复项,保留原有顺序。 */ + private List splitInvoiceOrderIds(String value) { + if (StrUtil.isBlank(value)) { + return Collections.emptyList(); + } + return Arrays.stream(value.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .distinct() + .collect(Collectors.toList()); + } + @Override public Pagination mainPage(PointsMallInvoiceMainPageParam param) { Cnd cnd = Cnd.where("delFlag", "=", false); if (StrUtil.isNotBlank(param.getSettlementId())) { - cnd.and("settlementId", "like", "%" + param.getSettlementId() + "%"); + cnd.and("settlementId", "=", param.getSettlementId()); } if (param.getBSuccess() != null) { cnd.and("bSuccess", "=", param.getBSuccess()); @@ -58,15 +226,32 @@ public class PointsMallInvoiceServiceImpl extends BaseServiceImpl infoPage(PointsMallInvoiceInfoPageParam param) { + if (StrUtil.isNotBlank(param.getMainId()) && dao().count(PointsMallInvoiceInfo.class, + Cnd.where("mainId", "=", param.getMainId()).and("delFlag", "=", false)) == 0) { + PointsMallInvoiceMain main = fetch(param.getMainId()); + PointsMallOrderSub order = main == null ? null : dao().fetch(PointsMallOrderSub.class, + Cnd.where("invoiceCode", "=", main.getSettlementId()).and("delFlag", "=", false)); + if (order != null) { + MallOrderEventDTO event = new MallOrderEventDTO(); + event.setSupplierId(order.getSupplierId()); + event.setOrderId(main.getSettlementId()); + MallInboundResult result = invoiceInfo(event); + if (result.getResultCode() != 0) { + dao().update(PointsMallInvoiceMain.class, Chain.make("bSuccess", 1).add("failMsg", result.getMessage()), + Cnd.where("id", "=", main.getId())); + throw new IllegalArgumentException(result.getMessage()); + } + } + } Cnd cnd = Cnd.where("delFlag", "=", false); if (StrUtil.isNotBlank(param.getMainId())) { cnd.and("mainId", "=", param.getMainId()); } if (StrUtil.isNotBlank(param.getInvoiceId())) { - cnd.and("invoiceId", "like", "%" + param.getInvoiceId() + "%"); + cnd.and("invoiceId", "=", param.getInvoiceId()); } if (StrUtil.isNotBlank(param.getInvoiceCode())) { - cnd.and("invoiceCode", "like", "%" + param.getInvoiceCode() + "%"); + cnd.and("invoiceCode", "=", param.getInvoiceCode()); } if (StrUtil.isNotBlank(param.getInvoiceDate())) { cnd.and("invoiceDate", "=", DateUtil.parse(param.getInvoiceDate())); @@ -81,13 +266,61 @@ public class PointsMallInvoiceServiceImpl extends BaseServiceImpl info.getInvoiceId().equals(item.getInvoiceId())).findFirst() + .orElseThrow(() -> new IllegalArgumentException("供应商未返回对应发票详情")); + info.setStatus(remote.getStatus()); + info.setRemark(remote.getRemark()); + if (!Integer.valueOf(1).equals(remote.getStatus())) { + info.setUrl(""); + info.setImageEncode(""); + } + return info; + } + + private MallOrderEventDTO invoiceEvent(String mainId) { + PointsMallInvoiceMain main = StrUtil.isBlank(mainId) ? null : dao().fetch(PointsMallInvoiceMain.class, + Cnd.where("id", "=", mainId).and("delFlag", "=", false)); + if (main == null) { + throw new IllegalArgumentException("开票申请记录不存在"); + } + PointsMallOrderSub order = dao().fetch(PointsMallOrderSub.class, + Cnd.where("invoiceCode", "=", main.getSettlementId()).and("delFlag", "=", false)); + if (order == null) { + throw new IllegalArgumentException("未找到结算单对应的供应商订单"); + } + MallOrderEventDTO event = new MallOrderEventDTO(); + event.setSupplierId(order.getSupplierId()); + event.setOrderId(main.getSettlementId()); + return event; + } + + @Override + public void retryInvoice(String mainId) { + MallInboundResult result = invoiceInfo(invoiceEvent(mainId), true); + if (result.getResultCode() != 0) { + throw new IllegalArgumentException(result.getMessage()); + } + PointsMallInvoiceMain main = fetch(mainId); + if (!Integer.valueOf(0).equals(main.getBSuccess())) { + throw new IllegalArgumentException(StrUtil.blankToDefault(main.getFailMsg(), "供应商发票生成失败")); + } } @Override @@ -117,7 +350,19 @@ public class PointsMallInvoiceServiceImpl extends BaseServiceImpl orders = dao().query(PointsMallOrderSub.class, invoiceOrderCnd(query, "")); + Cnd cnd = invoiceOrderCnd(query, ""); + if (StrUtil.isNotBlank(param.getQueryOrderId())) { + List queryOrderIds = StrUtil.splitTrim(param.getQueryOrderId(), ","); + if (queryOrderIds.size() > 1) { + cnd.and("orderId", "in", queryOrderIds); + } else { + cnd.and("orderId", "like", "%" + param.getQueryOrderId() + "%"); + } + } + if (StrUtil.isNotBlank(param.getUserId())) { + cnd.and("userId", "like", "%" + param.getUserId() + "%"); + } + List orders = dao().query(PointsMallOrderSub.class, cnd); if (CollUtil.isEmpty(orders)) { throw new IllegalArgumentException("无符合条件的订单数据"); } @@ -126,30 +371,69 @@ public class PointsMallInvoiceServiceImpl extends BaseServiceImpl { + dao().update(PointsMallInvoiceInfo.class, Chain.make("delFlag", true), + Cnd.where("mainId", "=", invoice.getId())); + dao().update(PointsMallInvoiceMain.class, Chain.make("delFlag", true), + Cnd.where("id", "=", invoice.getId())); + dao().update(PointsMallOrderSub.class, Chain.make("invoiceStatus", 0).add("invoiceCode", ""), + Cnd.where("supplierId", "=", param.getSupplierId()).and("orderId", "in", orderIds) + .and("invoiceCode", "=", settlementId).and("invoiceStatus", "in", new Integer[]{0, 2})); + }); + throw new IllegalArgumentException(result.getMessage()); + } + invoice.setBSuccess(1); + invoice.setSucOrderIds(""); + invoice.setFailOrderIds(String.join(",", orderIds)); + invoice.setFailMsg(result == null ? "供应商开票申请无响应" : result.getMessage()); + updateIgnoreNull(invoice); + dao().update(PointsMallOrderSub.class, Chain.make("invoiceStatus", 0), + Cnd.where("supplierId", "=", param.getSupplierId()).and("orderId", "in", orderIds) + .and("invoiceCode", "=", settlementId).and("invoiceStatus", "=", 2)); + throw new IllegalArgumentException(invoice.getFailMsg()); + } - dao().update(PointsMallOrderSub.class, Chain.make("invoiceStatus", 2), Cnd.where("supplierId", "=", param.getSupplierId()).and("orderId", "in", orderIds)); return "开票申请已生成,结算单号:" + settlementId; } @@ -170,11 +454,7 @@ public class PointsMallInvoiceServiceImpl extends BaseServiceImpl orderIds = StrUtil.splitTrim(param.getOrderId(), ","); - if (orderIds.size() > 1) { - cnd.and(field(alias, "orderId"), "in", orderIds); - } else { - cnd.and(field(alias, "orderId"), "like", "%" + param.getOrderId() + "%"); - } + cnd.and(field(alias, "orderId"), "in", orderIds); } if (StrUtil.isNotBlank(param.getStartDate())) { cnd.and(field(alias, "orderCompleteTime"), ">=", DateUtil.beginOfDay(DateUtil.parse(param.getStartDate()))); @@ -196,16 +476,16 @@ public class PointsMallInvoiceServiceImpl extends BaseServiceImpl orderProInfos; +} diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/QueryInvoiceInfoDTO.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/QueryInvoiceInfoDTO.java index 293d8696..d77a32e0 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/QueryInvoiceInfoDTO.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/QueryInvoiceInfoDTO.java @@ -4,6 +4,7 @@ import lombok.Data; @Data public class QueryInvoiceInfoDTO { + private Boolean retry; private String supplierId; private String token; private String settlementId; diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/QueryReconciliationInfoDTO.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/QueryReconciliationInfoDTO.java new file mode 100644 index 00000000..bdf89d61 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/QueryReconciliationInfoDTO.java @@ -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; +} diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/ReconciliationResultNoticeDTO.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/ReconciliationResultNoticeDTO.java new file mode 100644 index 00000000..0063bff9 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/ReconciliationResultNoticeDTO.java @@ -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 reconciliationResults; +} diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/ReconciliationResultOrderDTO.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/ReconciliationResultOrderDTO.java new file mode 100644 index 00000000..4333ce88 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/ReconciliationResultOrderDTO.java @@ -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; +} diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/UnReconciledMsgNoticeDTO.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/UnReconciledMsgNoticeDTO.java index d2e26696..5b5b27d8 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/UnReconciledMsgNoticeDTO.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/dto/UnReconciledMsgNoticeDTO.java @@ -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; diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallAbolishSubOrderEventHandler.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallAbolishSubOrderEventHandler.java index b4b8854c..a2fac5fc 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallAbolishSubOrderEventHandler.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallAbolishSubOrderEventHandler.java @@ -3,7 +3,16 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderEventDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.event.MallOrderEventHandler; import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult; -import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallInboundEventService; +import com.budwk.app.zhgh.pointsmall.mallbridge.service.support.AbstractMallBridgeSupport; +import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSub; +import lombok.extern.slf4j.Slf4j; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.trans.Atom; +import org.nutz.trans.Trans; +import java.util.Date; +import com.budwk.app.zhgh.pointsmall.points.service.PointsMallPointsLockService; +import java.math.BigDecimal; import com.budwk.app.zhgh.pointsmall.order.enums.MallOrderStatusEnum; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; @@ -12,19 +21,44 @@ import org.nutz.ioc.loader.annotation.IocBean; * MallAbolishSubOrderEventHandler * orderType=8: 废除子订单事件处理器。 */ -@IocBean -public class MallAbolishSubOrderEventHandler implements MallOrderEventHandler { +@Slf4j +@IocBean(args = {"refer:dao"}) +public class MallAbolishSubOrderEventHandler extends AbstractMallBridgeSupport implements MallOrderEventHandler { @Inject - private MallInboundEventService inboundEventService; + private PointsMallPointsLockService pointsMallPointsLockService; + + public MallAbolishSubOrderEventHandler(Dao dao) { + super(dao); + } @Override public int getType() { return 8; } + /** + * 废除指定子订单,在同一事务中更新状态并释放或退回积分。 + */ @Override public MallInboundResult handle(MallOrderEventDTO dto) { - return inboundEventService.updateSubOrderStatus(dto, MallOrderStatusEnum.ABOLISH); + Integer state = MallOrderStatusEnum.ABOLISH.getCode(); + PointsMallOrderSub sub = dao.fetch(PointsMallOrderSub.class, Cnd.where("supplierId", "=", dto.getSupplierId()) + .and("orderId", "=", dto.getOrderId()).and("delFlag", "=", false)); + if (sub == null) { + return MallInboundResult.fail(404, "订单不存在"); + } + // 已废除的重复通知不再处理,避免重复执行积分释放或退回。 + if (state.equals(sub.getOrderState())) { + log.info("积分商城子单状态已是目标状态,跳过重复处理,supplierId={},orderId={},state={}", dto.getSupplierId(), dto.getOrderId(), state); + return MallInboundResult.success(); + } + Trans.exec((Atom) () -> { + sub.setOrderState(state); + sub.setOrderUpdateTime(new Date()); + dao.updateIgnoreNull(sub); + }); + log.info("积分商城子单状态更新完成,supplierId={},orderId={},state={}", dto.getSupplierId(), dto.getOrderId(), state); + return MallInboundResult.success(); } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallCancelPayedMainOrderEventHandler.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallCancelPayedMainOrderEventHandler.java index 3bd3653a..ec76d571 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallCancelPayedMainOrderEventHandler.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallCancelPayedMainOrderEventHandler.java @@ -3,7 +3,18 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderEventDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.event.MallOrderEventHandler; import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult; -import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallInboundEventService; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.zhgh.pointsmall.mallbridge.service.support.AbstractMallBridgeSupport; +import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderMain; +import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSub; +import com.budwk.app.zhgh.pointsmall.points.service.PointsMallPointsLockService; +import lombok.extern.slf4j.Slf4j; +import org.nutz.dao.Chain; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.trans.Atom; +import org.nutz.trans.Trans; +import java.util.Date; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; @@ -11,19 +22,50 @@ import org.nutz.ioc.loader.annotation.IocBean; * MallCancelPayedMainOrderEventHandler * orderType=7: 已支付未拆单的整个父单取消。 */ -@IocBean -public class MallCancelPayedMainOrderEventHandler implements MallOrderEventHandler { +@Slf4j +@IocBean(args = {"refer:dao"}) +public class MallCancelPayedMainOrderEventHandler extends AbstractMallBridgeSupport implements MallOrderEventHandler { @Inject - private MallInboundEventService inboundEventService; + private PointsMallPointsLockService pointsMallPointsLockService; + + public MallCancelPayedMainOrderEventHandler(Dao dao) { + super(dao); + } @Override public int getType() { return 7; } + /** + * 处理已支付未拆单主单取消通知,沿用原 paidNotSplit=true 分支的校验与积分处理。 + */ @Override public MallInboundResult handle(MallOrderEventDTO dto) { - return inboundEventService.cancelMainOrder(dto, true); + // 优先使用 mainOrderId,未提供时使用事件中的 orderId 查询主订单。 + String mainOrderId = StrUtil.blankToDefault(dto.getMainOrderId(), dto.getOrderId()); + if (StrUtil.isBlank(mainOrderId)) { + return MallInboundResult.fail(400, "主订单信息不能为空"); + } + PointsMallOrderMain main = dao.fetch(PointsMallOrderMain.class, Cnd.where("supplierId", "=", dto.getSupplierId()) + .and("orderId", "=", mainOrderId).and("delFlag", "=", false)); + if (main == null) { + return MallInboundResult.fail(404, "主订单不存在"); + } + Date now = new Date(); + // 主子订单状态更新与主单积分锁释放在同一事务内完成。 + Trans.exec((Atom) () -> { + main.setOrderState(ORDER_CANCELLED); + main.setOrderUpdateTime(now); + dao.updateIgnoreNull(main); + // 同步取消该供应商、该主订单下尚未删除的子订单,保持原处理范围。 + dao.update(PointsMallOrderSub.class, Chain.make("orderState", ORDER_CANCELLED).add("orderUpdateTime", now), + Cnd.where("supplierId", "=", dto.getSupplierId()).and("mainOrderId", "=", mainOrderId).and("delFlag", "=", false)); + // 使用主订单归属信息释放主单积分锁。 + pointsMallPointsLockService.releaseMain(main.getUserId(), main.getSupplierId(), main.getOrderId()); + }); + log.info("积分商城主单取消完成,supplierId={},mainOrderId={},paidNotSplit={}", dto.getSupplierId(), mainOrderId, true); + return MallInboundResult.success(); } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallCancelPendingOrderEventHandler.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallCancelPendingOrderEventHandler.java index 8d38ae87..68f72dfb 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallCancelPendingOrderEventHandler.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallCancelPendingOrderEventHandler.java @@ -3,7 +3,18 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderEventDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.event.MallOrderEventHandler; import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult; -import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallInboundEventService; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.zhgh.pointsmall.mallbridge.service.support.AbstractMallBridgeSupport; +import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderMain; +import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSub; +import com.budwk.app.zhgh.pointsmall.points.service.PointsMallPointsLockService; +import lombok.extern.slf4j.Slf4j; +import org.nutz.dao.Chain; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.trans.Atom; +import org.nutz.trans.Trans; +import java.util.Date; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; @@ -11,19 +22,54 @@ import org.nutz.ioc.loader.annotation.IocBean; * MallCancelPendingOrderEventHandler * orderType=1: 取消待支付主单事件处理器。 */ -@IocBean -public class MallCancelPendingOrderEventHandler implements MallOrderEventHandler { +@Slf4j +@IocBean(args = {"refer:dao"}) +public class MallCancelPendingOrderEventHandler extends AbstractMallBridgeSupport implements MallOrderEventHandler { @Inject - private MallInboundEventService inboundEventService; + private PointsMallPointsLockService pointsMallPointsLockService; + + public MallCancelPendingOrderEventHandler(Dao dao) { + super(dao); + } @Override public int getType() { return 1; } + /** + * 处理待支付主单取消通知,校验待支付状态后取消订单并释放主单积分锁。 + */ @Override public MallInboundResult handle(MallOrderEventDTO dto) { - return inboundEventService.cancelMainOrder(dto, false); + // 优先使用 mainOrderId,未提供时使用事件中的 orderId 查询主订单。 + String mainOrderId = StrUtil.blankToDefault(dto.getMainOrderId(), dto.getOrderId()); + if (StrUtil.isBlank(mainOrderId)) { + return MallInboundResult.fail(400, "主订单信息不能为空"); + } + PointsMallOrderMain main = dao.fetch(PointsMallOrderMain.class, Cnd.where("supplierId", "=", dto.getSupplierId()) + .and("orderId", "=", mainOrderId).and("delFlag", "=", false)); + if (main == null) { + return MallInboundResult.fail(404, "主订单不存在"); + } + // 待支付取消事件仅允许取消尚未支付的主订单。 + if (!Integer.valueOf(ORDER_PENDING_PAY).equals(main.getOrderState())) { + return MallInboundResult.fail(400, "只能取消未支付主单"); + } + Date now = new Date(); + // 主子订单状态更新与主单积分锁释放在同一事务内完成。 + Trans.exec((Atom) () -> { + main.setOrderState(ORDER_CANCELLED); + main.setOrderUpdateTime(now); + dao.updateIgnoreNull(main); + // 同步取消该供应商、该主订单下尚未删除的子订单,保持原处理范围。 + dao.update(PointsMallOrderSub.class, Chain.make("orderState", ORDER_CANCELLED).add("orderUpdateTime", now), + Cnd.where("supplierId", "=", dto.getSupplierId()).and("mainOrderId", "=", mainOrderId).and("delFlag", "=", false)); + // 使用主订单归属信息释放主单积分锁。 + pointsMallPointsLockService.releaseMain(main.getUserId(), main.getSupplierId(), main.getOrderId()); + }); + log.info("积分商城主单取消完成,supplierId={},mainOrderId={},paidNotSplit={}", dto.getSupplierId(), mainOrderId, false); + return MallInboundResult.success(); } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallCancelSubOrderEventHandler.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallCancelSubOrderEventHandler.java index a30bbc09..784207d4 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallCancelSubOrderEventHandler.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallCancelSubOrderEventHandler.java @@ -1,6 +1,8 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderMessageDTO; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import com.budwk.app.sys.models.Sys_user; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderEventDTO; @@ -61,7 +63,7 @@ public class MallCancelSubOrderEventHandler implements MallOrderEventHandler { if (dto == null || StrUtil.hasBlank(dto.getSupplierId(), dto.getOrderId(), dto.getUserId())) { return MallInboundResult.fail(400, "供应商、订单、用户不能为空"); } - // userId 兼容系统用户ID和工号,后续消息推送需要使用用户信息。 + // 供应商传入 userId 为虚拟用户ID,转换为系统用户后用于消息推送。 Sys_user user = fetchUser(dto.getUserId()); if (user == null) { return MallInboundResult.fail(-1, "用户不存在"); @@ -102,7 +104,10 @@ public class MallCancelSubOrderEventHandler implements MallOrderEventHandler { pointsMallOrderService.updateMainOrderStatus(sub.getMainOrderId(), sub.getSupplierId()); }); try { - mallCallPMessagePushService.pushMessageToGlobal(StrUtil.blankToDefault(user.getUsername(), user.getLoginname()), sub); + MallOrderMessageDTO message = BeanUtil.copyProperties(sub, MallOrderMessageDTO.class); + message.setOperationTime(StrUtil.isBlank(dto.getOperationTime()) ? null : DateUtil.parse(dto.getOperationTime().replace("T", " "))); + message.setUserName(StrUtil.blankToDefault(user.getUsername(), user.getLoginname())); + mallCallPMessagePushService.pushMessageToGlobal("66666666", message); } catch (Exception e) { log.error("9.2.1 子单取消,callP消息推送:{},{}", dto, e.getMessage()); } @@ -110,14 +115,8 @@ public class MallCancelSubOrderEventHandler implements MallOrderEventHandler { return MallInboundResult.success(); } - /** - * 入参 userId 可能是系统用户ID或工号,按当前项目用户表兼容查询。 - */ - private Sys_user fetchUser(String userId) { - Sys_user user = dao.fetch(Sys_user.class, Cnd.where("id", "=", userId).and("disabled", "=", false).and("delFlag", "=", false)); - if (user != null) { - return user; - } - return dao.fetch(Sys_user.class, Cnd.where("loginname", "=", userId).and("disabled", "=", false).and("delFlag", "=", false)); + private Sys_user fetchUser(String virtualId) { + return dao.fetch(Sys_user.class, Cnd.where("virtual_id", "=", virtualId) + .and("disabled", "=", false).and("delFlag", "=", false)); } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallConfirmReceiptEventHandler.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallConfirmReceiptEventHandler.java index 246f65fd..42fbfeba 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallConfirmReceiptEventHandler.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallConfirmReceiptEventHandler.java @@ -1,9 +1,26 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderMessageDTO; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.sys.models.Sys_user; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderEventDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.event.MallOrderEventHandler; import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult; -import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallInboundEventService; +import com.budwk.app.zhgh.pointsmall.mallbridge.service.support.AbstractMallBridgeSupport; +import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSub; +import lombok.extern.slf4j.Slf4j; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.trans.Atom; +import org.nutz.trans.Trans; +import java.util.Date; +import cn.hutool.json.JSONUtil; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderQueryDTO; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.OrderInfoDTO; +import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallOutboundResult; +import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallOrderQueryOutboundService; +import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallCallPMessagePushService; import com.budwk.app.zhgh.pointsmall.order.enums.MallOrderStatusEnum; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; @@ -16,19 +33,103 @@ import org.nutz.ioc.loader.annotation.IocBean; *

注意:供应商侧“确认收货”对应 zhgh/callP 侧“已妥投(4)”, * 不等同于 zhgh/callP 用户确认收货后的“已完成(5)”。

*/ -@IocBean -public class MallConfirmReceiptEventHandler implements MallOrderEventHandler { +@Slf4j +@IocBean(args = {"refer:dao"}) +public class MallConfirmReceiptEventHandler extends AbstractMallBridgeSupport implements MallOrderEventHandler { @Inject - private MallInboundEventService inboundEventService; + private MallOrderQueryOutboundService mallOrderQueryOutboundService; + @Inject + private MallCallPMessagePushService mallCallPMessagePushService; + + public MallConfirmReceiptEventHandler(Dao dao) { + super(dao); + } @Override public int getType() { return 6; } + /** + * 处理供应商确认收货通知,仅更新为已妥投,不执行用户确认完成后的积分扣减。 + */ @Override public MallInboundResult handle(MallOrderEventDTO dto) { - return inboundEventService.updateSubOrderStatus(dto, MallOrderStatusEnum.RECEIVED); + Integer state = MallOrderStatusEnum.RECEIVED.getCode(); + PointsMallOrderSub sub = dao.fetch(PointsMallOrderSub.class, Cnd.where("supplierId", "=", dto.getSupplierId()) + .and("orderId", "=", dto.getOrderId()).and("delFlag", "=", false)); + if (sub == null) { + return MallInboundResult.fail(404, "订单不存在"); + } + // 已妥投的重复通知直接返回,保留原处理的幂等行为。 + if (state.equals(sub.getOrderState())) { + log.info("积分商城子单状态已是目标状态,跳过重复处理,supplierId={},orderId={},state={}", dto.getSupplierId(), dto.getOrderId(), state); + return MallInboundResult.success(); + } + Trans.exec((Atom) () -> { + // 补查供应商订单详情;查询失败时沿用原逻辑,仍更新本地妥投状态。 + fillReceivedSubOrder(dto, sub); + sub.setOrderState(state); + // 优先保留订单详情中的时间,缺失时使用当前时间。 + if (sub.getOrderUpdateTime() == null) { + sub.setOrderUpdateTime(new Date()); + } + if (sub.getOrderFinishTime() == null) { + sub.setOrderFinishTime(new Date()); + } + dao.updateIgnoreNull(sub); + }); + // 妥投状态保存成功后发送提醒,消息异常不回滚订单处理。 + try { + // 按子订单所属用户读取姓名,显式传入妥投提醒的 userName 模板变量。 + Sys_user user = dao.fetch(Sys_user.class, Cnd.where("id", "=", sub.getUserId()).or("loginname", "=", sub.getUserId())); + if (user == null) { + throw new IllegalArgumentException("订单所属用户不存在,无法生成妥投提醒"); + } + MallOrderMessageDTO message = BeanUtil.copyProperties(sub, MallOrderMessageDTO.class); + message.setOperationTime(parseDate(dto.getOperationTime())); + message.setUserName(StrUtil.blankToDefault(user.getUsername(), user.getLoginname())); + mallCallPMessagePushService.pushMessageToGlobal("22222222", message); + } catch (Exception e) { + log.error("订单类型为:6(确认收货)时,全局消息推送失败:{}", dto, e); + } + log.info("积分商城子单状态更新完成,supplierId={},orderId={},state={}", dto.getSupplierId(), dto.getOrderId(), state); + return MallInboundResult.success(); + } + + /** + * 订单类型为:6(确认收货)时,调用 9.2.2 获取订单详情接口,补充子单妥投相关信息。 + * + *

供应商侧确认收货只代表电商处已收货,对 zhgh/callP 侧应更新为已妥投(4), + * 等待 callP 用户侧确认收货后才进入已完成(5)。

+ */ + private void fillReceivedSubOrder(MallOrderEventDTO dto, PointsMallOrderSub sub) { + if (mallOrderQueryOutboundService == null) { + return; + } + MallOrderQueryDTO queryDTO = new MallOrderQueryDTO(); + queryDTO.setSupplierId(dto.getSupplierId()); + queryDTO.setOrderId(dto.getOrderId()); + queryDTO.setOrderType(2); + log.info("订单类型为:6(确认收货)时,调用9.2.2获取订单详情接口:{}", queryDTO); + try { + MallOutboundResult result = mallOrderQueryOutboundService.queryOrderInfo(queryDTO); + if (result == null || result.getResultCode() != 0 || result.getOrderInfo() == null || result.getOrderInfo().isEmpty()) { + log.error("订单类型为:6(确认收货)时,调用9.2.2 获取订单详情失败:{},{}", queryDTO, result == null ? null : result.getMessage()); + return; + } + OrderInfoDTO orderInfo = result.getOrderInfo().get(0); + sub.setOrderCreateTime(parseDate(orderInfo.getCreateTime())); + sub.setOrderUpdateTime(parseDate(orderInfo.getUpdateTime())); + sub.setOrderFinishTime(parseDate(orderInfo.getOrderFinishTime())); + sub.setDeliveryTime(parseDate(orderInfo.getDeliveryTime())); + sub.setDeliveryStatus(parseInteger(orderInfo.getDeliveryStatus())); + if (orderInfo.getOrderProInfos() != null) { + sub.setExtJson(JSONUtil.toJsonStr(orderInfo.getOrderProInfos())); + } + } catch (Exception e) { + log.error("订单类型为:6(确认收货)时,调用9.2.2 获取订单详情异常:{},{}", queryDTO, e.getMessage()); + } } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallInvoiceInfoEventHandler.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallInvoiceInfoEventHandler.java index 4a51cf59..2012194a 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallInvoiceInfoEventHandler.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallInvoiceInfoEventHandler.java @@ -3,7 +3,7 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderEventDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.event.MallOrderEventHandler; import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult; -import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallInboundEventService; +import com.budwk.app.zhgh.pointsmall.invoice.service.PointsMallInvoiceService; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; @@ -15,7 +15,7 @@ import org.nutz.ioc.loader.annotation.IocBean; public class MallInvoiceInfoEventHandler implements MallOrderEventHandler { @Inject - private MallInboundEventService inboundEventService; + private PointsMallInvoiceService pointsMallInvoiceService; @Override public int getType() { @@ -24,6 +24,6 @@ public class MallInvoiceInfoEventHandler implements MallOrderEventHandler { @Override public MallInboundResult handle(MallOrderEventDTO dto) { - return inboundEventService.invoiceInfo(dto); + return pointsMallInvoiceService.invoiceInfo(dto); } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallShipOrderEventHandler.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallShipOrderEventHandler.java index f89becef..fd5f40c8 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallShipOrderEventHandler.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallShipOrderEventHandler.java @@ -3,27 +3,116 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderEventDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.event.MallOrderEventHandler; import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult; -import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallInboundEventService; -import org.nutz.ioc.loader.annotation.Inject; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.sys.models.Sys_user; +import com.budwk.app.zhgh.pointsmall.mallbridge.service.support.AbstractMallBridgeSupport; +import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderMain; +import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSub; +import lombok.extern.slf4j.Slf4j; +import org.nutz.dao.Chain; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.trans.Atom; +import org.nutz.trans.Trans; +import java.util.Date; +import java.util.List; import org.nutz.ioc.loader.annotation.IocBean; /** * MallShipOrderEventHandler * orderType=99: 供应商已发货 / 更新发货状态。 + * 在处理器内完成订单校验及主子订单物流信息更新,不再回调事件分发服务。 + * 本事件仅更新发货状态和物流信息,不修改订单业务状态 orderState。 */ -@IocBean -public class MallShipOrderEventHandler implements MallOrderEventHandler { +@Slf4j +@IocBean(args = {"refer:dao"}) +public class MallShipOrderEventHandler extends AbstractMallBridgeSupport implements MallOrderEventHandler { - @Inject - private MallInboundEventService inboundEventService; + public MallShipOrderEventHandler(Dao dao) { + super(dao); + } @Override public int getType() { return 99; } + /** + * 处理供应商发货通知,在同一事务中更新匹配的子订单及一个主订单。 + * + * @param dto 发货事件,orderId 为英文逗号分隔的子订单号,userId 为可选的用户虚拟 ID + * @return 参数或用户映射无效时返回 400,无匹配子订单时返回 404,更新完成返回成功 + */ @Override public MallInboundResult handle(MallOrderEventDTO dto) { - return inboundEventService.updateDeliveryStatus(dto); + if (StrUtil.isBlank(dto.getSupplierId()) || StrUtil.isBlank(dto.getOrderId())) { + return MallInboundResult.fail(400, "供应商或订单不能为空"); + } + + // 按供应商和明确的子订单号限定更新范围,支持一次通知多个子订单。 + List orderIds = splitIds(dto.getOrderId()); + if (orderIds.isEmpty()) { + return MallInboundResult.fail(400, "订单信息不能为空"); + } + + // 未提供有效发货时间时,使用当前处理时间。 + Date deliveryTime = parseDate(dto.getDeliveryTime()); + if (deliveryTime == null) { + deliveryTime = new Date(); + } + + Cnd subCnd = Cnd.where("supplierId", "=", dto.getSupplierId()) + .and("orderId", "in", orderIds) + .and("delFlag", "=", false); + if (StrUtil.isNotBlank(dto.getUserId())) { + // 供应商传入虚拟 ID,转换为本地用户 ID 后参与订单归属查询。 + Sys_user user = fetchUser(dto.getUserId()); + if (user == null) { + return MallInboundResult.fail(400, "用户ID未配置或不存在"); + } + subCnd.and("userId", "=", user.getId()); + } + + List subs = dao.query(PointsMallOrderSub.class, subCnd); + if (subs.isEmpty()) { + return MallInboundResult.fail(404, "订单不存在"); + } + + Date finalDeliveryTime = deliveryTime; + // 已发货事件固定写入 deliveryStatus=1,不使用通知中的状态值覆盖该语义。 + Integer deliveredStatus = 1; + Trans.exec((Atom) () -> { + // 物流单号和承运商仅在非空时更新,避免空字段覆盖已有物流信息。 + Chain subChain = Chain.make("deliveryStatus", deliveredStatus) + .add("deliveryTime", finalDeliveryTime); + if (StrUtil.isNotBlank(dto.getLogisticsOrderId())) { + subChain.add("logisticsOrderId", dto.getLogisticsOrderId()); + } + if (StrUtil.isNotBlank(dto.getCrrgBsnNm())) { + subChain.add("crrgBsnNm", dto.getCrrgBsnNm()); + } + dao.update(PointsMallOrderSub.class, subChain, subCnd); + + // 优先使用通知中的主订单号,未提供时取首个匹配子订单的主订单号。 + // 此处只同步该主订单的物流信息,不检查其余子订单是否全部发货。 + String mainOrderId = StrUtil.blankToDefault(dto.getMainOrderId(), subs.get(0).getMainOrderId()); + if (StrUtil.isNotBlank(mainOrderId)) { + // 主订单的发货状态字段为字符串,子订单字段为整数。 + Chain mainChain = Chain.make("deliveryStatus", String.valueOf(deliveredStatus)) + .add("deliveryTime", finalDeliveryTime); + if (StrUtil.isNotBlank(dto.getLogisticsOrderId())) { + mainChain.add("logisticsOrderId", dto.getLogisticsOrderId()); + } + if (StrUtil.isNotBlank(dto.getCrrgBsnNm())) { + mainChain.add("crrgBsnNm", dto.getCrrgBsnNm()); + } + dao.update(PointsMallOrderMain.class, mainChain, Cnd.where("supplierId", "=", dto.getSupplierId()) + .and("orderId", "=", mainOrderId).and("delFlag", "=", false)); + } + }); + + log.info("供应商已发货事件处理完成,supplierId={},mainOrderId={},orderIds={},deliveryStatus={}", + dto.getSupplierId(), dto.getMainOrderId(), orderIds, deliveredStatus); + return MallInboundResult.success(); } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallSplitSubOrderEventHandler.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallSplitSubOrderEventHandler.java index 159a30df..01ef8cde 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallSplitSubOrderEventHandler.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallSplitSubOrderEventHandler.java @@ -94,7 +94,7 @@ public class MallSplitSubOrderEventHandler implements MallOrderEventHandler { // 调用外部服务获取子订单信息 MallOutboundResult result = mallOrderQueryOutboundService.orderQuery(queryDTO); if (result == null || result.getResultCode() != 0) { - throw new RuntimeException(result == null ? "获取订单详情返回为空" : result.getMessage()); + return MallInboundResult.fail(result == null ? 502 : result.getResultCode(), result == null ? "获取订单详情返回为空" : result.getMessage()); } orderInfoList = result.getOrderInfo(); } catch (Exception e) { diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallSubOrderReturnEventHandler.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallSubOrderReturnEventHandler.java index 2410de4d..27c1dec3 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallSubOrderReturnEventHandler.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/event/handlers/MallSubOrderReturnEventHandler.java @@ -1,4 +1,5 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderMessageDTO; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; @@ -148,13 +149,15 @@ public class MallSubOrderReturnEventHandler extends AbstractMallBridgeSupport im }); try { // callP消息推送 - PointsMallOrderSub notifySub = BeanUtil.copyProperties(sub, PointsMallOrderSub.class); + MallOrderMessageDTO notifySub = BeanUtil.copyProperties(sub, MallOrderMessageDTO.class); + notifySub.setOperationTime(parseDate(dto.getOperationTime())); notifySub.setOrderId(dto.getOrderId()); notifySub.setPointsPrice(refund); notifySub.setTotalPrice(orderInfo.getTotalPrice()); notifySub.setWPayOrAPay(orderInfo.getWPayOrAPay()); notifySub.setExtJson(orderExchange.getExtJson()); - mallCallPMessagePushService.pushMessageToGlobal(StrUtil.blankToDefault(user.getUsername(), user.getLoginname()), notifySub); + notifySub.setUserName(StrUtil.blankToDefault(user.getUsername(), user.getLoginname())); + mallCallPMessagePushService.pushMessageToGlobal("66666666", notifySub); } catch (Exception e) { log.error("9.2.1 子单取消 或 退货,callP消息推送: {}, {}", dto, e.getMessage()); } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/model/MallOutboundPageResult.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/model/MallOutboundPageResult.java new file mode 100644 index 00000000..2d98eaa8 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/model/MallOutboundPageResult.java @@ -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 orderInfos; + + public static MallOutboundPageResult success() { + MallOutboundPageResult r = new MallOutboundPageResult(); + r.resultCode = 0; + r.message = "成功"; + r.orderInfos = null; + return r; + } + + public static MallOutboundPageResult success(List 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; + } +} diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallCallPMessagePushService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallCallPMessagePushService.java index 41716465..7477a774 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallCallPMessagePushService.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallCallPMessagePushService.java @@ -8,8 +8,7 @@ import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageSendService import com.budwk.app.zhgh.pointsmall.mallbridge.dto.OrderProInfoDTO; import com.budwk.app.zhgh.pointsmall.message.models.PointsMallMessage; import com.budwk.app.zhgh.pointsmall.message.models.PointsMallMessageTemplate; -import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderMain; -import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSub; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderMessageDTO; import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier; import lombok.extern.slf4j.Slf4j; import org.nutz.dao.Cnd; @@ -39,123 +38,111 @@ public class MallCallPMessagePushService { } /** - * 支付成功callP消息推送。 - * - * @param mainOrder 订单信息 + * 统一订单消息入口:11111111 支付成功,22222222 妥投提醒,66666666 积分退回。 + * 主单、子单均转换为 MallOrderMessageDTO,退货传入退货单号及本次退款积分。 + * 已成功消息不重复发送;失败记录可在再次调用时重用。 + * synchronized 防止当前服务实例内的并发通知重复创建消息。 */ - public void pushMessageToGlobal(PointsMallOrderMain mainOrder) { - PointsMallMessageTemplate template = dao.fetch(PointsMallMessageTemplate.class, Cnd.where("id", "=", "11111111").and("delFlag", "=", false)); - if (template == null) { - log.info("支付成功推送消息失败,未配置消息模板"); + public synchronized void pushMessageToGlobal(String templateId, MallOrderMessageDTO order) { + if (order == null || StrUtil.hasBlank(templateId, order.getSupplierId(), order.getUserId(), order.getOrderId())) { + throw new IllegalArgumentException("消息模板、供应商、用户和订单不能为空"); + } + if ("66666666".equals(templateId) && nvl(order.getPointsPrice()).signum() <= 0) { return; } - Sys_user user = dao.fetch(Sys_user.class, Cnd.where("id", "=", mainOrder.getUserId()).or("loginname", "=", mainOrder.getUserId())); - PointsMallSupplier supplier = dao.fetch(PointsMallSupplier.class, Cnd.where("supplierId", "=", mainOrder.getSupplierId()).and("delFlag", "=", false)); - String supplierName = supplier == null || StrUtil.isBlank(supplier.getSupplierName()) ? mainOrder.getSupplierId() : supplier.getSupplierName(); - // 消息唯一标识 模板id+供应商+订单号 - String uniqueNo = template.getId().concat("-").concat(mainOrder.getSupplierId()).concat("-").concat(mainOrder.getOrderId()); - int count = dao.count(PointsMallMessage.class, Cnd.where("uniqueNo", "=", uniqueNo).and("delFlag", "=", false)); - if (count > 0) { - log.info("9.2.14支付结果通知接口-支付成功2,消息重复推送:{}", uniqueNo); - return; - } - - StringBuilder sb = new StringBuilder(); - sb.append(" 商品列表:").append(System.lineSeparator()); - List productList = StrUtil.isBlank(mainOrder.getExtJson()) - ? Collections.emptyList() - : JSONUtil.toList(mainOrder.getExtJson(), OrderProInfoDTO.class); - productList.forEach(product -> - sb.append(" 商品名称:").append(product.getName()).append(",商品价格:").append(formatMoney(product.getPrice())).append("元").append(System.lineSeparator()) - ); - // 模板变量替换数据 - Map paramMap = new HashMap<>(); - paramMap.put("#{userName}", user == null || StrUtil.isBlank(user.getUsername()) ? mainOrder.getUserId() : user.getUsername()); - paramMap.put("#{orderFinishTime}", DateUtil.formatDateTime(mainOrder.getOrderCreateTime())); - paramMap.put("#{pointPrice}", nvl(mainOrder.getPointsPrice()).toPlainString()); - paramMap.put("#{supplierName}", supplierName); - paramMap.put("#{totalPrice}", nvl(mainOrder.getTotalPrice()).toPlainString()); - paramMap.put("#{payPrice}", nvl(mainOrder.getWPayOrAPay()).toPlainString()); - paramMap.put("#{productList}", sb.toString()); - - // 模板转换成具体的消息 - String content = template.getContent(); - for (Map.Entry entry : paramMap.entrySet()) { - content = content.replace(entry.getKey(), entry.getValue()); - } - // 生成记录 - PointsMallMessage message = new PointsMallMessage(); - message.setUniqueNo(uniqueNo); - message.setPushType("CALLP"); - message.setTemplateId(template.getId()); - message.setTouser(mainOrder.getUserId()); - message.setContent(content); - message.setContentLink(template.getContentLink()); - message.setPlanPushTime(new Date()); - // 状态设置为已推送 - message.setStatus(1); - dao.insert(message); - - globalMessageSendService.sendLocalSystemMessage(template.getName(), content, 1, Collections.singletonList(mainOrder.getUserId()), null); - } - - /** - * callP消息推送。 - * - * @param userName 用户姓名 - * @param subOrder 订单信息 - */ - public void pushMessageToGlobal(String userName, PointsMallOrderSub subOrder) { - PointsMallMessageTemplate template = dao.fetch(PointsMallMessageTemplate.class, Cnd.where("id", "=", "22222222").and("delFlag", "=", false)); - if (template == null) { - log.info("子单取消后推送消息失败,未配置消息模板"); - return; - } - PointsMallSupplier supplier = dao.fetch(PointsMallSupplier.class, Cnd.where("supplierId", "=", subOrder.getSupplierId()).and("delFlag", "=", false)); - String supplierName = supplier == null || StrUtil.isBlank(supplier.getSupplierName()) ? subOrder.getSupplierId() : supplier.getSupplierName(); - + String userId = order.getUserId(); Date now = new Date(); - Date finishTime = subOrder.getOrderFinishTime() == null ? now : subOrder.getOrderFinishTime(); - long day = DateUtil.betweenDay(DateUtil.beginOfDay(finishTime), DateUtil.beginOfDay(now), false); - String uniqueNo = template.getId().concat("-").concat(subOrder.getSupplierId()).concat("-") - .concat(subOrder.getOrderId()).concat("-").concat(String.valueOf(day)); - - StringBuilder sb = new StringBuilder(); - sb.append("商品列表: ").append(System.lineSeparator()); - List productList = StrUtil.isBlank(subOrder.getExtJson()) - ? Collections.emptyList() - : JSONUtil.toList(subOrder.getExtJson(), OrderProInfoDTO.class); - productList.forEach(product -> - sb.append(" 商品名称: ").append(product.getName()).append(",商品价格:").append(formatMoney(product.getPrice())).append("元").append(System.lineSeparator()) - ); - - Map paramMap = new HashMap<>(); - paramMap.put("#{userName}", userName); - paramMap.put("#{orderFinishTime}", DateUtil.formatDateTime(finishTime)); - paramMap.put("#{pointPrice}", nvl(subOrder.getPointsPrice()).toPlainString()); - paramMap.put("#{supplierName}", supplierName); - paramMap.put("#{totalPrice}", nvl(subOrder.getTotalPrice()).toPlainString()); - paramMap.put("#{payPrice}", nvl(subOrder.getWPayOrAPay()).toPlainString()); - paramMap.put("#{productList}", sb.toString()); - + Date finishTime = "11111111".equals(templateId) ? order.getOrderCreateTime() + : order.getOrderFinishTime() == null ? now : order.getOrderFinishTime(); + Date planPushTime = now; + String uniqueNo = templateId + "-" + order.getSupplierId() + "-" + order.getOrderId(); + if ("22222222".equals(templateId)) { + // 妥投提醒按自然日去重;取消、退货按业务订单号去重。 + if (DateUtil.beginOfDay(finishTime).after(DateUtil.beginOfDay(now))) { + return; + } + long day = DateUtil.betweenDay(DateUtil.beginOfDay(finishTime), DateUtil.beginOfDay(now), false); + uniqueNo += "-" + day; + planPushTime = DateUtil.offsetDay(finishTime, (int) day); + } + PointsMallMessageTemplate template = dao.fetch(PointsMallMessageTemplate.class, + Cnd.where("id", "=", templateId).and("delFlag", "=", false)); + if (template == null || StrUtil.isBlank(template.getContent())) { + log.info("订单消息推送失败,未配置消息模板或模板内容为空,templateId={}", templateId); + return; + } + PointsMallMessage message = dao.fetch(PointsMallMessage.class, + Cnd.where("uniqueNo", "=", uniqueNo).and("delFlag", "=", false)); + if (message != null && Integer.valueOf(1).equals(message.getStatus())) { + log.info("订单消息已推送,跳过重复通知:{}", uniqueNo); + return; + } + Map params = orderParams(order.getSupplierId(), userId, order.getExtJson(), + "11111111".equals(templateId), order.getTotalPrice()); + if (StrUtil.isNotBlank(order.getUserName())) { + params.put("#{userName}", order.getUserName()); + } + params.put("#{orderId}", order.getOrderId()); + params.put("#{orderFinishTime}", finishTime == null ? "" : DateUtil.formatDateTime(finishTime)); + params.put("#{operationTime}", order.getOperationTime() == null ? "" : DateUtil.formatDateTime(order.getOperationTime())); + params.put("#{pointPrice}", nvl(order.getPointsPrice()).toPlainString()); + params.put("#{totalPrice}", nvl(order.getTotalPrice()).toPlainString()); + params.put("#{payPrice}", nvl(order.getWPayOrAPay()).toPlainString()); String content = template.getContent(); - for (Map.Entry entry : paramMap.entrySet()) { - content = content.replace(entry.getKey(), entry.getValue()); + for (Map.Entry entry : params.entrySet()) { + content = content.replace(entry.getKey(), StrUtil.blankToDefault(entry.getValue(), "")); + } + boolean create = message == null; + if (create) { + message = new PointsMallMessage(); + message.setUniqueNo(uniqueNo); } - - Date planPushTime = DateUtil.offsetDay(finishTime, (int) day); - PointsMallMessage message = new PointsMallMessage(); - message.setUniqueNo(uniqueNo); message.setPushType("CALLP"); - message.setTemplateId(template.getId()); - message.setTouser(subOrder.getUserId()); + message.setTemplateId(templateId); + message.setTouser(userId); message.setContent(content); message.setContentLink(template.getContentLink()); message.setPlanPushTime(planPushTime); message.setStatus(0); - dao.insert(message); + if (create) { + dao.insert(message); + } else { + dao.updateIgnoreNull(message); + } + try { + globalMessageSendService.sendLocalSystemMessage(template.getName(), content, 1, Collections.singletonList(userId), null); + message.setStatus(1); + dao.updateIgnoreNull(message); + } catch (Exception e) { + message.setStatus(2); + dao.updateIgnoreNull(message); + throw new IllegalStateException("订单消息推送失败:" + uniqueNo, e); + } + } - globalMessageSendService.sendLocalSystemMessage(template.getName(), content, 1, Collections.singletonList(subOrder.getUserId()), null); + /** 公共模板变量:用户姓名、供应商名称和订单商品列表。 */ + private Map orderParams(String supplierId, String userId, String extJson, + boolean paymentMessage, BigDecimal totalPrice) { + Sys_user user = dao.fetch(Sys_user.class, Cnd.where("id", "=", userId).or("loginname", "=", userId)); + PointsMallSupplier supplier = dao.fetch(PointsMallSupplier.class, Cnd.where("supplierId", "=", supplierId).and("delFlag", "=", false)); + Map params = new HashMap<>(); + params.put("#{userName}", user == null ? userId : StrUtil.blankToDefault(user.getUsername(), userId)); + params.put("#{supplierName}", supplier == null ? supplierId : StrUtil.blankToDefault(supplier.getSupplierName(), supplierId)); + StringBuilder sb = new StringBuilder(); + sb.append(paymentMessage ? "商品列表:" : "商品列表: ").append(System.lineSeparator()); + List products = StrUtil.isBlank(extJson) || "null".equalsIgnoreCase(extJson.trim()) + ? Collections.emptyList() : JSONUtil.toList(extJson, OrderProInfoDTO.class); + if (paymentMessage) { + products.forEach(product -> sb.append("商品名称:").append(product.getName()) + .append(" * ").append(product.getNumber() == null ? "-" : product.getNumber()) + .append(",商品单价:").append(formatMoney(product.getPrice())).append("元").append(System.lineSeparator())); + sb.append(System.lineSeparator()).append("订单合计:¥").append(formatMoney(totalPrice)); + } else { + products.forEach(product -> sb.append(" 商品名称: ").append(product.getName()) + .append(",商品价格:").append(formatMoney(product.getPrice())).append("元").append(System.lineSeparator())); + } + params.put("#{productList}", sb.toString()); + return params; } private String formatMoney(BigDecimal value) { diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundEventService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundEventService.java index 71b23123..7be394ac 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundEventService.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundEventService.java @@ -1,11 +1,6 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.service; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONUtil; -import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderQueryDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderEventDTO; -import com.budwk.app.zhgh.pointsmall.mallbridge.dto.OrderInfoDTO; -import com.budwk.app.zhgh.pointsmall.mallbridge.dto.OrderProInfoDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.event.MallOrderEventHandler; import com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers.MallAbolishSubOrderEventHandler; import com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers.MallCancelPayedMainOrderEventHandler; @@ -18,28 +13,15 @@ import com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers.MallShipOrderEven import com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers.MallSplitSubOrderEventHandler; import com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers.MallSubOrderReturnEventHandler; import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult; -import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallOutboundResult; import com.budwk.app.zhgh.pointsmall.mallbridge.service.support.AbstractMallBridgeSupport; -import com.budwk.app.zhgh.pointsmall.order.enums.MallOrderStatusEnum; -import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderMain; -import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSub; import com.budwk.app.zhgh.pointsmall.points.service.PointsMallPointsLockService; import lombok.extern.slf4j.Slf4j; -import org.nutz.dao.Chain; -import org.nutz.dao.Cnd; import org.nutz.dao.Dao; 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.math.BigDecimal; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; import java.util.HashMap; -import java.util.List; import java.util.Map; +import com.budwk.app.sys.services.SysFileService; /** * 商城桥接订单事件入站服务。 @@ -53,6 +35,10 @@ public class MallInboundEventService extends AbstractMallBridgeSupport { @Inject private MallOrderQueryOutboundService mallOrderQueryOutboundService; @Inject + private MallInvoiceInfoQueryService mallInvoiceInfoQueryService; + @Inject + private SysFileService sysFileService; + @Inject private MallCancelPendingOrderEventHandler mallCancelPendingOrderEventHandler; @Inject private MallSplitSubOrderEventHandler mallSplitSubOrderEventHandler; @@ -117,307 +103,4 @@ public class MallInboundEventService extends AbstractMallBridgeSupport { handlerRegistry.put(handler.getType(), handler); } } - - public MallInboundResult cancelMainOrder(MallOrderEventDTO dto, boolean paidNotSplit) { - String mainOrderId = StrUtil.blankToDefault(dto.getMainOrderId(), dto.getOrderId()); - if (StrUtil.isBlank(mainOrderId)) { - return MallInboundResult.fail(400, "主订单信息不能为空"); - } - PointsMallOrderMain main = dao.fetch(PointsMallOrderMain.class, Cnd.where("supplierId", "=", dto.getSupplierId()) - .and("orderId", "=", mainOrderId).and("delFlag", "=", false)); - if (main == null) { - return MallInboundResult.fail(404, "主订单不存在"); - } - if (!paidNotSplit && !Integer.valueOf(ORDER_PENDING_PAY).equals(main.getOrderState())) { - return MallInboundResult.fail(400, "只能取消未支付主单"); - } - Date now = new Date(); - Trans.exec((Atom) () -> { - main.setOrderState(ORDER_CANCELLED); - main.setOrderUpdateTime(now); - dao.updateIgnoreNull(main); - dao.update(PointsMallOrderSub.class, Chain.make("orderState", ORDER_CANCELLED).add("orderUpdateTime", now), - Cnd.where("supplierId", "=", dto.getSupplierId()).and("mainOrderId", "=", mainOrderId).and("delFlag", "=", false)); - pointsMallPointsLockService.releaseMain(main.getUserId(), main.getSupplierId(), main.getOrderId()); - }); - log.info("积分商城主单取消完成,supplierId={},mainOrderId={},paidNotSplit={}", dto.getSupplierId(), mainOrderId, paidNotSplit); - return MallInboundResult.success(); - } - - public MallInboundResult saveOrderSub(MallOrderEventDTO dto) { - PointsMallOrderMain main = dao.fetch(PointsMallOrderMain.class, Cnd.where("supplierId", "=", dto.getSupplierId()) - .and("orderId", "=", dto.getMainOrderId()).and("delFlag", "=", false)); - if (main == null) { - return MallInboundResult.fail(404, "主订单不存在"); - } - List subOrderIds = splitIds(dto.getOrderId()); - if (mallOrderQueryOutboundService != null) { - try { - return splitSubOrderByQuery(dto, main, subOrderIds); - } catch (Exception e) { - log.error("split sub order by query failed, fallback to local split logic, supplierId={}, mainOrderId={}, orderId={}", - dto.getSupplierId(), dto.getMainOrderId(), dto.getOrderId(), e); - } - } - if (subOrderIds.isEmpty()) { - return MallInboundResult.fail(400, "子订单信息不能为空"); - } - List products = StrUtil.isBlank(main.getExtJson()) || "null".equalsIgnoreCase(main.getExtJson()) - ? Collections.emptyList() - : JSONUtil.toList(main.getExtJson(), OrderProInfoDTO.class); - List subLocks = new ArrayList<>(); - Trans.exec((Atom) () -> { - for (String subOrderId : subOrderIds) { - PointsMallOrderSub sub = dao.fetch(PointsMallOrderSub.class, Cnd.where("orderId", "=", subOrderId) - .and("supplierId", "=", dto.getSupplierId()).and("delFlag", "=", false)); - if (sub == null) { - sub = toSub(main, subOrderId); - sub.setOrderState(ORDER_IN_PROGRESS); - dao.insert(sub); - insertProducts(subOrderId, products); - } else { - sub.setOrderState(ORDER_IN_PROGRESS); - sub.setOrderUpdateTime(new Date()); - dao.updateIgnoreNull(sub); - } - subLocks.add(sub); - } - pointsMallPointsLockService.createSubLocks(main, subLocks); - }); - log.info("积分商城拆单完成,supplierId={},mainOrderId={},subOrderCount={}", dto.getSupplierId(), dto.getMainOrderId(), subOrderIds.size()); - return MallInboundResult.success(); - } - - private MallInboundResult splitSubOrderByQuery(MallOrderEventDTO dto, PointsMallOrderMain main, List subOrderIds) { - log.info("9.2.1 生成订单信息推送-订单状态为:2(拆单子单)开始执行:{}", dto); - if (subOrderIds.isEmpty()) { - return MallInboundResult.fail(400, "缺少拆单信息"); - } - log.info("9.2.1 生成订单信息推送-订单状态为:2(拆单子单)调用外部服务获取子单信息:{}", dto); - List orderInfoList; - try { - orderInfoList = getOrderInfoList(dto); - } catch (Exception e) { - log.error("9.2.1 生成订单信息推送-订单状态为:2(拆单子单),调用9.2.2获取订单详情接口异常:{}", e.getMessage(), e); - return MallInboundResult.fail(-1, "调用9.2.2获取订单详情接口异常,异常订单:" + subOrderIds); - } - if (orderInfoList == null || orderInfoList.isEmpty()) { - return MallInboundResult.fail(404, "子单不存在"); - } - - List subLocks = new ArrayList<>(); - Trans.exec((Atom) () -> { - for (OrderInfoDTO info : orderInfoList) { - PointsMallOrderSub sub = dao.fetch(PointsMallOrderSub.class, Cnd.where("orderId", "=", info.getOrderId()) - .and("supplierId", "=", dto.getSupplierId()).and("delFlag", "=", false)); - if (sub == null) { - sub = toSub(main, info); - sub.setOrderState(ORDER_IN_PROGRESS); - dao.insert(sub); - insertProducts(info.getOrderId(), info.getOrderProInfos() == null ? Collections.emptyList() : info.getOrderProInfos()); - } else { - log.info("9.2.1 生成订单信息推送-订单状态为:2(拆单子单)子单已存在:{}", info); - sub.setOrderState(ORDER_IN_PROGRESS); - sub.setOrderUpdateTime(new Date()); - dao.updateIgnoreNull(sub); - } - subLocks.add(sub); - } - pointsMallPointsLockService.createSubLocks(main, subLocks); - }); - log.info("9.2.1 生成订单信息推送-订单状态为:2(拆单子单)结束,supplierId={},mainOrderId={},subOrderCount={}", - dto.getSupplierId(), dto.getMainOrderId(), orderInfoList.size()); - return MallInboundResult.success(); - } - - private List getOrderInfoList(MallOrderEventDTO dto) { - MallOrderQueryDTO queryDTO = new MallOrderQueryDTO(); - queryDTO.setSupplierId(dto.getSupplierId()); - queryDTO.setOrderId(dto.getOrderId()); - queryDTO.setOrderType(dto.getOrderType()); - MallOutboundResult result = mallOrderQueryOutboundService.orderQuery(queryDTO); - if (result == null || result.getResultCode() != 0) { - throw new RuntimeException(result == null ? "获取订单详情返回为空" : result.getMessage()); - } - return result.getOrderInfo(); - } - - private PointsMallOrderSub toSub(PointsMallOrderMain main, OrderInfoDTO info) { - PointsMallOrderSub sub = toSub(main, info.getOrderId()); - sub.setOrderNumberId(info.getOrderNumberId()); - sub.setAplName(StrUtil.blankToDefault(info.getAplName(), main.getAplName())); - sub.setOrderCreateTime(parseDate(info.getCreateTime())); - sub.setOrderUpdateTime(parseDate(info.getUpdateTime())); - sub.setDeliveryTime(parseDate(info.getDeliveryTime())); - sub.setOrderFinishTime(parseDate(info.getOrderFinishTime())); - sub.setOrderCompleteTime(parseDate(info.getCompleteTime())); - sub.setDeliveryStatus(parseInteger(info.getDeliveryStatus())); - sub.setFreightCost(nvl(info.getFreightCost())); - sub.setWPayOrAPayFreightCost(nvl(info.getWPayOrAPayFreightCost())); - sub.setRefund(nvl(info.getRefund())); - sub.setWPayOrAPayRefund(nvl(info.getWPayOrAPayRefund())); - sub.setLogisticsOrderId(info.getLogisticsOrderId()); - sub.setCrrgBsnNm(info.getCrrgBsnNm()); - sub.setTotalPrice(nvl(info.getTotalPrice())); - sub.setWPayOrAPay(nvl(info.getWPayOrAPay())); - sub.setPointsPrice(nvl(info.getPointsPrice())); - sub.setInvoiceCode(info.getInvoiceCode()); - sub.setExtJson(JSONUtil.toJsonStr(info.getOrderProInfos() == null ? Collections.emptyList() : info.getOrderProInfos())); - sub.setReconciliationStatus(0); - sub.setInvoiceStatus(0); - return sub; - } - - /** - * 更新子单状态。 - * - *

订单类型为:6(确认收货)时,供应商侧“确认收货”对应 zhgh/callP 侧“已妥投(4)”, - * 需重新调用 9.2.2 获取订单详情接口,补充确认收货时间、发货时间、商品信息等字段。

- * - * @param dto 请求参数 - * @param statusEnum zhgh/callP 侧订单状态枚举 - * @return 操作结果 - */ - public MallInboundResult updateSubOrderStatus(MallOrderEventDTO dto, MallOrderStatusEnum statusEnum) { - Integer state = statusEnum.getCode(); - PointsMallOrderSub sub = dao.fetch(PointsMallOrderSub.class, Cnd.where("supplierId", "=", dto.getSupplierId()) - .and("orderId", "=", dto.getOrderId()).and("delFlag", "=", false)); - if (sub == null) { - return MallInboundResult.fail(404, "订单不存在"); - } - if (Integer.valueOf(state).equals(sub.getOrderState())) { - log.info("积分商城子单状态已是目标状态,跳过重复处理,supplierId={},orderId={},state={}", dto.getSupplierId(), dto.getOrderId(), state); - return MallInboundResult.success(); - } - Trans.exec((Atom) () -> { - if (statusEnum == MallOrderStatusEnum.RECEIVED) { - fillReceivedSubOrder(dto, sub); - } - sub.setOrderState(state); - if (statusEnum != MallOrderStatusEnum.RECEIVED || sub.getOrderUpdateTime() == null) { - sub.setOrderUpdateTime(new Date()); - } - if (statusEnum == MallOrderStatusEnum.RECEIVED && sub.getOrderFinishTime() == null) { - sub.setOrderFinishTime(new Date()); - } - if (statusEnum == MallOrderStatusEnum.COMPLETED) { - sub.setOrderCompleteTime(new Date()); - } - dao.updateIgnoreNull(sub); - if (statusEnum == MallOrderStatusEnum.CANCELLED || statusEnum == MallOrderStatusEnum.REFUNDED || statusEnum == MallOrderStatusEnum.ABOLISH) { - pointsMallPointsLockService.releaseOrReturnPoints(sub, state, nvl(sub.getRefund(), BigDecimal.ZERO)); - } - if (statusEnum == MallOrderStatusEnum.COMPLETED) { - pointsMallPointsLockService.deductCompletedSub(sub); - } - }); - log.info("积分商城子单状态更新完成,supplierId={},orderId={},state={}", dto.getSupplierId(), dto.getOrderId(), state); - return MallInboundResult.success(); - } - - /** - * 订单类型为:6(确认收货)时,调用 9.2.2 获取订单详情接口,补充子单妥投相关信息。 - * - *

供应商侧确认收货只代表电商处已收货,对 zhgh/callP 侧应更新为已妥投(4), - * 等待 callP 用户侧确认收货后才进入已完成(5)。

- */ - private void fillReceivedSubOrder(MallOrderEventDTO dto, PointsMallOrderSub sub) { - if (mallOrderQueryOutboundService == null) { - return; - } - MallOrderQueryDTO queryDTO = new MallOrderQueryDTO(); - queryDTO.setSupplierId(dto.getSupplierId()); - queryDTO.setOrderId(dto.getOrderId()); - queryDTO.setOrderType(2); - log.info("订单类型为:6(确认收货)时,调用9.2.2获取订单详情接口:{}", queryDTO); - try { - MallOutboundResult result = mallOrderQueryOutboundService.queryOrderInfo(queryDTO); - if (result == null || result.getResultCode() != 0 || result.getOrderInfo() == null || result.getOrderInfo().isEmpty()) { - log.error("订单类型为:6(确认收货)时,调用9.2.2 获取订单详情失败:{},{}", queryDTO, result == null ? null : result.getMessage()); - return; - } - OrderInfoDTO orderInfo = result.getOrderInfo().get(0); - sub.setOrderCreateTime(parseDate(orderInfo.getCreateTime())); - sub.setOrderUpdateTime(parseDate(orderInfo.getUpdateTime())); - sub.setOrderFinishTime(parseDate(orderInfo.getOrderFinishTime())); - sub.setDeliveryTime(parseDate(orderInfo.getDeliveryTime())); - sub.setDeliveryStatus(parseInteger(orderInfo.getDeliveryStatus())); - if (orderInfo.getOrderProInfos() != null) { - sub.setExtJson(JSONUtil.toJsonStr(orderInfo.getOrderProInfos())); - } - } catch (Exception e) { - log.error("订单类型为:6(确认收货)时,调用9.2.2 获取订单详情异常:{},{}", queryDTO, e.getMessage()); - } - } - - public MallInboundResult updateDeliveryStatus(MallOrderEventDTO dto) { - if (StrUtil.isBlank(dto.getSupplierId()) || StrUtil.isBlank(dto.getOrderId())) { - return MallInboundResult.fail(400, "供应商或订单不能为空"); - } - - List orderIds = splitIds(dto.getOrderId()); - if (orderIds.isEmpty()) { - return MallInboundResult.fail(400, "订单信息不能为空"); - } - - Date deliveryTime = parseDate(dto.getDeliveryTime()); - if (deliveryTime == null) { - deliveryTime = new Date(); - } - - Cnd subCnd = Cnd.where("supplierId", "=", dto.getSupplierId()) - .and("orderId", "in", orderIds) - .and("delFlag", "=", false); - if (StrUtil.isNotBlank(dto.getUserId())) { - subCnd.and("userId", "=", dto.getUserId()); - } - - List subs = dao.query(PointsMallOrderSub.class, subCnd); - if (subs.isEmpty()) { - return MallInboundResult.fail(404, "订单不存在"); - } - - Date finalDeliveryTime = deliveryTime; - Integer deliveredStatus = 1; - Trans.exec((Atom) () -> { - Chain subChain = Chain.make("deliveryStatus", deliveredStatus) - .add("deliveryTime", finalDeliveryTime); - if (StrUtil.isNotBlank(dto.getLogisticsOrderId())) { - subChain.add("logisticsOrderId", dto.getLogisticsOrderId()); - } - if (StrUtil.isNotBlank(dto.getCrrgBsnNm())) { - subChain.add("crrgBsnNm", dto.getCrrgBsnNm()); - } - dao.update(PointsMallOrderSub.class, subChain, subCnd); - - String mainOrderId = StrUtil.blankToDefault(dto.getMainOrderId(), subs.get(0).getMainOrderId()); - if (StrUtil.isNotBlank(mainOrderId)) { - Chain mainChain = Chain.make("deliveryStatus", String.valueOf(deliveredStatus)) - .add("deliveryTime", finalDeliveryTime); - if (StrUtil.isNotBlank(dto.getLogisticsOrderId())) { - mainChain.add("logisticsOrderId", dto.getLogisticsOrderId()); - } - if (StrUtil.isNotBlank(dto.getCrrgBsnNm())) { - mainChain.add("crrgBsnNm", dto.getCrrgBsnNm()); - } - dao.update(PointsMallOrderMain.class, mainChain, Cnd.where("supplierId", "=", dto.getSupplierId()) - .and("orderId", "=", mainOrderId).and("delFlag", "=", false)); - } - }); - - log.info("供应商已发货事件处理完成,supplierId={},mainOrderId={},orderIds={},deliveryStatus={}", - dto.getSupplierId(), dto.getMainOrderId(), orderIds, deliveredStatus); - return MallInboundResult.success(); - } - - public MallInboundResult invoiceInfo(MallOrderEventDTO dto) { - String mainOrderId = StrUtil.blankToDefault(dto.getMainOrderId(), dto.getOrderId()); - if (StrUtil.isBlank(mainOrderId)) { - return MallInboundResult.fail(400, "主订单信息不能为空"); - } - dao.update(PointsMallOrderSub.class, Chain.make("invoiceStatus", 1), Cnd.where("supplierId", "=", dto.getSupplierId()) - .and("mainOrderId", "=", mainOrderId).and("delFlag", "=", false)); - log.info("积分商城发票通知状态更新完成,supplierId={},mainOrderId={}", dto.getSupplierId(), mainOrderId); - return MallInboundResult.success(); - } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundPendingOrderService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundPendingOrderService.java index 28b0053d..1e60da15 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundPendingOrderService.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundPendingOrderService.java @@ -2,6 +2,7 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; +import com.budwk.app.sys.models.Sys_user; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallPendingOrderDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult; import com.budwk.app.zhgh.pointsmall.mallbridge.service.support.AbstractMallBridgeSupport; @@ -32,13 +33,17 @@ public class MallInboundPendingOrderService extends AbstractMallBridgeSupport { if (dto.getOrderInfo() == null || StrUtil.isBlank(dto.getOrderInfo().getOrderId())) { return MallInboundResult.fail(400, "订单信息不能为空"); } + Sys_user user = fetchUser(dto.getUserId()); + if (user == null) { + return MallInboundResult.fail(400, "用户虚拟ID未配置或不存在"); + } log.info("积分商城接收待支付订单开始,supplierId={},orderId={}", dto.getSupplierId(), dto.getOrderInfo().getOrderId()); if (dao.count(PointsMallOrderMain.class, Cnd.where("orderId", "=", dto.getOrderInfo().getOrderId()) .and("supplierId", "=", dto.getSupplierId()).and("delFlag", "=", false)) > 0) { return MallInboundResult.fail(-1, "订单已存在"); } Trans.exec((Atom) () -> { - PointsMallOrderMain main = toMain(dto.getSupplierId(), dto.getUserId(), dto.getOrderInfo(), ORDER_PENDING_PAY); + PointsMallOrderMain main = toMain(dto.getSupplierId(), user.getId(), dto.getOrderInfo(), ORDER_PENDING_PAY); dao.insert(main); // 新增主单历史记录 diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundPointsQueryService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundPointsQueryService.java index 822f1eef..125f1dc5 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundPointsQueryService.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundPointsQueryService.java @@ -1,9 +1,13 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.service; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.sys.models.Sys_user; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallPointQueryDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundPointsResult; +import com.budwk.app.zhgh.pointsmall.mallbridge.service.support.AbstractMallBridgeSupport; import com.budwk.app.zhgh.pointsmall.points.service.PointsMallUserPointsService; import lombok.extern.slf4j.Slf4j; +import org.nutz.dao.Dao; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; @@ -11,17 +15,28 @@ import org.nutz.ioc.loader.annotation.IocBean; * 商城桥接积分查询服务。 */ @Slf4j -@IocBean -public class MallInboundPointsQueryService { +@IocBean(args = {"refer:dao"}) +public class MallInboundPointsQueryService extends AbstractMallBridgeSupport { @Inject private PointsMallUserPointsService pointsMallUserPointsService; + public MallInboundPointsQueryService(Dao dao) { + super(dao); + } + /** * 查询用户可用积分。 */ public MallInboundPointsResult queryPoints(MallPointQueryDTO dto) { log.info("积分商城查询用户积分,supplierId={},userId={}", dto == null ? null : dto.getSupplierId(), dto == null ? null : dto.getUserId()); - return MallInboundPointsResult.success(pointsMallUserPointsService.availablePoints(dto.getUserId())); + if (dto == null || StrUtil.isBlank(dto.getUserId())) { + return MallInboundPointsResult.fail(400, "用户虚拟ID不能为空"); + } + Sys_user user = fetchUser(dto.getUserId()); + if (user == null) { + return MallInboundPointsResult.fail(400, "用户虚拟ID未配置或不存在"); + } + return MallInboundPointsResult.success(pointsMallUserPointsService.availablePoints(user.getId())); } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundPurchaseResultService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundPurchaseResultService.java index e0121300..c5e0364c 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundPurchaseResultService.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInboundPurchaseResultService.java @@ -1,4 +1,5 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.service; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderMessageDTO; import cn.hutool.core.bean.BeanUtil; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallPurchaseResultDTO; @@ -58,7 +59,7 @@ public class MallInboundPurchaseResultService { } try { // callP消息推送 - mallCallPMessagePushService.pushMessageToGlobal(orderMain); + mallCallPMessagePushService.pushMessageToGlobal("11111111", BeanUtil.copyProperties(orderMain, MallOrderMessageDTO.class)); } catch (Exception e) { log.error("9.2.14 支付结果通知接口,callP消息推送:{},{}", dto, e.getMessage()); } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInvoiceApplyOutboundService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInvoiceApplyOutboundService.java index 84b0c0e9..1ac134dd 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInvoiceApplyOutboundService.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInvoiceApplyOutboundService.java @@ -1,24 +1,22 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.service; -import cn.hutool.core.convert.Convert; +import cn.hutool.http.HttpUtil; import cn.hutool.core.util.StrUtil; -import com.budwk.app.zhgh.pointsmall.invoice.models.PointsMallInvoiceInfo; -import com.budwk.app.zhgh.pointsmall.invoice.models.PointsMallInvoiceMain; +import cn.hutool.json.JSONUtil; +import com.budwk.app.zhgh.pointsmall.mallbridge.util.MallBridgeResponseUtil; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallInvoiceApplyDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallOutboundResult; import com.budwk.app.zhgh.pointsmall.mallbridge.service.support.AbstractMallBridgeSupport; -import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSub; +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.Chain; 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.math.BigDecimal; -import java.util.List; -import java.util.Objects; +import java.util.HashMap; /** * 商城桥接开票申请出站服务。 @@ -27,73 +25,61 @@ import java.util.Objects; @IocBean(args = {"refer:dao"}) public class MallInvoiceApplyOutboundService extends AbstractMallBridgeSupport { + @Inject + private PropertiesProxy conf; + public MallInvoiceApplyOutboundService(Dao dao) { super(dao); } /** - * 接收开票申请并保存开票记录。 + * 向供应商发起开票申请。 */ public MallOutboundResult applyInvoice(MallInvoiceApplyDTO dto) { if (dto == null || StrUtil.hasBlank(dto.getSupplierId(), dto.getSettlementId(), dto.getOrderIds())) { return MallOutboundResult.fail(400, "开票申请参数不能为空"); } - List orderIds = splitIds(dto.getOrderIds()); - log.info("积分商城开票申请开始,supplierId={},settlementId={},orderCount={}", dto.getSupplierId(), dto.getSettlementId(), orderIds.size()); - List orders = dao.query(PointsMallOrderSub.class, Cnd.where("supplierId", "=", dto.getSupplierId()) - .and("orderId", "in", orderIds).and("delFlag", "=", false)); - if (orders.isEmpty()) { - return MallOutboundResult.fail(404, "未找到可开票订单"); + String clientBAB7ServerUrl = conf.get("spdb.bab7.url", ""); + if (StrUtil.isBlank(clientBAB7ServerUrl)) { + return MallOutboundResult.fail(500, "未配置 BAB7 服务地址"); } - if (dto.getOrderNum() != null && dto.getOrderNum() != orders.size()) { - return MallOutboundResult.fail(400, "订单数量与开票申请不一致"); + PointsMallSupplier supplier = dao.fetch(PointsMallSupplier.class, + Cnd.where("supplierId", "=", dto.getSupplierId()).and("delFlag", "=", false)); + if (supplier == null) { + return MallOutboundResult.fail(404, "未找到对应供应商信息"); } - BigDecimal total = orders.stream().map(PointsMallOrderSub::getTotalPrice).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add); - if (dto.getOrderTotalPrice() != null && total.compareTo(dto.getOrderTotalPrice()) != 0) { - return MallOutboundResult.fail(400, "订单金额与开票申请不一致"); + if (StrUtil.isBlank(supplier.getSupplierApiUrl())) { + return MallOutboundResult.fail(404, "供应商地址未配置"); } - Trans.exec((Atom) () -> saveInvoiceApply(dto, orderIds, total)); - log.info("积分商城开票申请完成,supplierId={},settlementId={}", dto.getSupplierId(), dto.getSettlementId()); - return MallOutboundResult.success(); - } + long timestamp = System.currentTimeMillis(); + dto.setToken(MallBridgeCryptoUtil.generateSign(supplier.getSupplierId(), supplier.getClientId(), timestamp)); + dto.setTimestamp(timestamp); - private void saveInvoiceApply(MallInvoiceApplyDTO dto, List orderIds, BigDecimal total) { - PointsMallInvoiceMain main = dao.fetch(PointsMallInvoiceMain.class, Cnd.where("settlementId", "=", dto.getSettlementId()).and("delFlag", "=", false)); - if (main == null) { - main = new PointsMallInvoiceMain(); - main.setSettlementId(dto.getSettlementId()); - main.setBSuccess(0); - main.setSucOrderIds(String.join(",", orderIds)); - main.setFailOrderIds(""); - main.setFailMsg(""); - dao.insert(main); - } else { - main.setBSuccess(0); - main.setSucOrderIds(String.join(",", orderIds)); - main.setFailOrderIds(""); - main.setFailMsg(""); - dao.updateIgnoreNull(main); + String keyHex = conf.get("sm4cbc.keyHex", ""); + String ivHex = conf.get("sm4cbc.ivHex", ""); + String encrypt; + try { + encrypt = MallBridgeCryptoUtil.sm4CbcEncrypt(JSONUtil.toJsonStr(dto), keyHex, ivHex); + } catch (Exception e) { + log.error("9.2.5 发票开具申请接口请求参数 SM4CBC 加密出错", e); + return MallOutboundResult.fail(500, "发票开具申请参数加密失败"); } - PointsMallInvoiceInfo info = new PointsMallInvoiceInfo(); - info.setMainId(main.getId()); - info.setInvoiceId(dto.getSettlementId()); - info.setInvoiceCode(dto.getInvoiceCode()); - info.setInvoiceDate(parseDate(dto.getInvoiceDate())); - info.setInvoiceNakeAmount(nvl(dto.getInvoiceNakedPrice(), total)); - info.setInvoiceTaxAmount(nvl(dto.getInvoiceTaxPrice(), BigDecimal.ZERO)); - info.setInvoiceAmount(total); - info.setInvoiceType(Convert.toInt(dto.getInvoiceType(), null)); - info.setInvoiceAddress(dto.getInvoiceAddress()); - info.setInvoiceContact(dto.getInvoiceContact()); - info.setInvoiceContent(dto.getInvoiceContent()); - info.setInvoiceBank(dto.getBankName()); - info.setInvoiceAccount(dto.getBankAccount()); - info.setTaxIdNumber(dto.getInvoiceCode()); - info.setRemark(dto.getRemark()); - dao.insert(info); + HashMap bodyMap = new HashMap<>(); + bodyMap.put("Address4", supplier.getSupplierApiUrl() + "/supplierOpenApi/pointOrder/applyForInvoices"); + bodyMap.put("BussDealMd", "P"); + bodyMap.put("SroNo", supplier.getSroNo()); + bodyMap.put("RsrvFld1", supplier.getClientId()); + bodyMap.put("AplParmObjct", encrypt); - dao.update(PointsMallOrderSub.class, Chain.make("invoiceStatus", 2).add("invoiceCode", dto.getSettlementId()), - Cnd.where("supplierId", "=", dto.getSupplierId()).and("orderId", "in", orderIds)); + String response = HttpUtil.createPost(clientBAB7ServerUrl) + .contentType("application/json;charset=UTF-8") + .body(JSONUtil.toJsonStr(bodyMap)) + .execute() + .body(); + if (StrUtil.isBlank(response)) { + return MallOutboundResult.fail(502, "供应商开票申请无响应"); + } + return JSONUtil.toBean(MallBridgeResponseUtil.parse(response), MallOutboundResult.class); } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInvoiceInfoQueryService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInvoiceInfoQueryService.java index 656571f0..ed95e8af 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInvoiceInfoQueryService.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallInvoiceInfoQueryService.java @@ -1,19 +1,26 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.service; import cn.hutool.core.util.StrUtil; -import com.budwk.app.zhgh.pointsmall.invoice.models.PointsMallInvoiceInfo; -import com.budwk.app.zhgh.pointsmall.invoice.models.PointsMallInvoiceMain; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.budwk.app.zhgh.pointsmall.mallbridge.util.MallBridgeResponseUtil; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.GetInvoiceInfoDTO; -import com.budwk.app.zhgh.pointsmall.mallbridge.dto.InvoiceInfoDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.QueryInvoiceInfoDTO; 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.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; import java.util.List; -import java.util.stream.Collectors; +import com.budwk.app.zhgh.pointsmall.mallbridge.dto.PictureQueryDTO; +import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallPictureOutboundResult; /** * 商城桥接发票信息查询服务。 @@ -22,10 +29,51 @@ import java.util.stream.Collectors; @IocBean(args = {"refer:dao"}) public class MallInvoiceInfoQueryService extends AbstractMallBridgeSupport { + @Inject + private PropertiesProxy conf; + public MallInvoiceInfoQueryService(Dao dao) { super(dao); } + public MallPictureOutboundResult queryInvoiceFiles(String supplierId, String settlementId, List fileIds) { + return queryFiles(supplierId, settlementId, "2", fileIds); + } + + public MallPictureOutboundResult queryFiles(String supplierId, String idNumber, String queryType, List fileIds) { + PointsMallSupplier supplier = dao.fetch(PointsMallSupplier.class, + Cnd.where("supplierId", "=", supplierId).and("delFlag", "=", false)); + if (supplier == null || StrUtil.isBlank(supplier.getSupplierApiUrl())) { + throw new IllegalArgumentException("供应商或供应商接口地址未配置"); + } + String apiUrl = conf.get("spdb.bab7.url", ""); + if (StrUtil.isBlank(apiUrl)) { + throw new IllegalArgumentException("未配置 BAB7 服务地址"); + } + PictureQueryDTO dto = new PictureQueryDTO(); + long timestamp = System.currentTimeMillis(); + dto.setSupplierId(supplierId); + dto.setToken(MallBridgeCryptoUtil.generateSign(supplierId, supplier.getClientId(), timestamp)); + dto.setTimestamp(timestamp); + dto.setQueryType(queryType); + dto.setIdNumber(idNumber); + dto.setFileIdList(fileIds); + HashMap body = new HashMap<>(); + body.put("Address4", supplier.getSupplierApiUrl() + "/supplierOpenApi/pointOrder/queryFileBase64Info"); + body.put("BussDealMd", "P"); + body.put("SroNo", supplier.getSroNo()); + body.put("RsrvFld1", supplier.getClientId()); + body.put("AplParmObjct", MallBridgeCryptoUtil.sm4CbcEncrypt(JSONUtil.toJsonStr(dto), + conf.get("sm4cbc.keyHex", ""), conf.get("sm4cbc.ivHex", ""))); + String response = HttpUtil.createPost(apiUrl).contentType("application/json;charset=UTF-8") + .body(JSONUtil.toJsonStr(body)).execute().body(); + JSONObject payload = MallBridgeResponseUtil.parse(response); + if (payload.getInt("resultCode", -1) != 0) { + throw new IllegalArgumentException(StrUtil.blankToDefault(payload.getStr("message"), "获取供应商文件失败")); + } + return JSONUtil.toBean(payload, MallPictureOutboundResult.class); + } + /** * 查询指定结算单的发票信息。 */ @@ -33,21 +81,57 @@ public class MallInvoiceInfoQueryService extends AbstractMallBridgeSupport { if (dto == null || StrUtil.hasBlank(dto.getSupplierId(), dto.getSettlementId())) { throw new IllegalArgumentException("发票查询参数不能为空"); } - log.info("积分商城查询发票信息,supplierId={},settlementId={}", dto.getSupplierId(), dto.getSettlementId()); - PointsMallInvoiceMain main = dao.fetch(PointsMallInvoiceMain.class, Cnd.where("settlementId", "=", dto.getSettlementId()) - .and("delFlag", "=", false)); - if (main == null) { - throw new IllegalArgumentException("发票信息不存在"); + String clientBAB7ServerUrl = conf.get("spdb.bab7.url", ""); + if (StrUtil.isBlank(clientBAB7ServerUrl)) { + throw new IllegalArgumentException("未配置 BAB7 服务地址"); } - List invoiceInfos = dao.query(PointsMallInvoiceInfo.class, Cnd.where("mainId", "=", main.getId()).and("delFlag", "=", false)) - .stream().map(this::toInvoiceInfoDTO).collect(Collectors.toList()); - GetInvoiceInfoDTO result = new GetInvoiceInfoDTO(); - result.setBSuccess(main.getBSuccess()); - result.setSettlementId(main.getSettlementId()); - result.setSucOrderIds(main.getSucOrderIds()); - result.setFailOrderIds(main.getFailOrderIds()); - result.setFailMsg(main.getFailMsg()); - result.setInvoiceInfos(invoiceInfos); - return result; + PointsMallSupplier supplier = dao.fetch(PointsMallSupplier.class, + Cnd.where("supplierId", "=", dto.getSupplierId()).and("delFlag", "=", false)); + if (supplier == null) { + throw new IllegalArgumentException("未找到对应供应商信息"); + } + if (StrUtil.isBlank(supplier.getSupplierApiUrl())) { + throw new IllegalArgumentException("供应商地址未配置"); + } + + long timestamp = System.currentTimeMillis(); + dto.setToken(MallBridgeCryptoUtil.generateSign(supplier.getSupplierId(), supplier.getClientId(), timestamp)); + dto.setTimestamp(timestamp); + String encrypt; + try { + encrypt = MallBridgeCryptoUtil.sm4CbcEncrypt( + JSONUtil.toJsonStr(dto), + conf.get("sm4cbc.keyHex", ""), + conf.get("sm4cbc.ivHex", "") + ); + } catch (Exception e) { + log.error("9.2.10 发票获取接口请求参数 SM4CBC 加密出错", e); + throw new IllegalArgumentException("发票获取请求参数加密失败"); + } + + HashMap bodyMap = new HashMap<>(); + bodyMap.put("Address4", supplier.getSupplierApiUrl() + "/supplierOpenApi/pointOrder/queryInvoicesInfo"); + 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(); + if (StrUtil.isBlank(response)) { + throw new IllegalArgumentException("没有查询到发票信息"); + } + JSONObject responseJson = MallBridgeResponseUtil.parse(response); + if (responseJson.getInt("resultCode", -1) != 0) { + throw new IllegalArgumentException(StrUtil.blankToDefault(responseJson.getStr("message"), "获取发票信息失败")); + } + JSONObject invoiceInfo = responseJson.getJSONObject("getInvoiceInfo"); + if (invoiceInfo == null) { + throw new IllegalArgumentException("没有查询到发票信息"); + } + return JSONUtil.toBean(invoiceInfo, GetInvoiceInfoDTO.class); } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallOrderQueryOutboundService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallOrderQueryOutboundService.java index d6a079a2..f437aeff 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallOrderQueryOutboundService.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallOrderQueryOutboundService.java @@ -2,6 +2,7 @@ package com.budwk.app.zhgh.pointsmall.mallbridge.service; 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.MallOrderQueryDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallOutboundResult; @@ -85,6 +86,31 @@ public class MallOrderQueryOutboundService { .execute() .body(); log.info("9.2.2 获取订单详情调用结束,返回报文:{}", response); - return JSONUtil.toBean(response, MallOutboundResult.class); + JSONObject payload = JSONUtil.parseObj(response); + if (!payload.containsKey("resultCode")) { + JSONObject data = payload.getJSONObject("data"); + JSONObject body = data == null ? null : data.getJSONObject("Body"); + if (body == null) { + return MallOutboundResult.fail(502, "获取订单详情返回报文格式异常"); + } + JSONObject header = body.getJSONObject("RspSvcHeader"); + if (header == null || !"000000000000".equals(header.getStr("ReturnCode"))) { + return MallOutboundResult.fail(502, header == null ? "BAB7返回服务头缺失" : + StrUtil.blankToDefault(header.getStr("ReturnMsg"), "BAB7获取订单详情失败")); + } + JSONObject serviceBody = body.getJSONObject("RspSvcBody"); + if (serviceBody == null || !"200".equals(serviceBody.getStr("ReturnStCd"))) { + return MallOutboundResult.fail(502, "BAB7调用供应商订单详情接口失败"); + } + String content = serviceBody.getStr("ReturnCntnt"); + if (StrUtil.isBlank(content)) { + return MallOutboundResult.fail(502, "供应商订单详情返回内容为空"); + } + payload = JSONUtil.parseObj(content); + } + if (payload.getInt("resultCode") == null) { + return MallOutboundResult.fail(502, "供应商订单详情返回状态缺失"); + } + return JSONUtil.toBean(payload, MallOutboundResult.class); } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallReconciliationInfoQueryService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallReconciliationInfoQueryService.java new file mode 100644 index 00000000..70891998 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallReconciliationInfoQueryService.java @@ -0,0 +1,185 @@ +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 queryReconciliationInfo(QueryReconciliationInfoDTO dto) { + // 从供应商获取对账订单信息 + return getOrderInfoFromSupplier(dto); + } + + private List 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 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(); + log.info("通过对账接口获取对账订单数据,响应结果:{}", response); + MallOutboundPageResult mallOutboundPageResult = parseReconciliationResponse(response); + if (CollUtil.isEmpty(mallOutboundPageResult.getOrderInfos())) { + return Collections.emptyList(); + } + + List 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 = parseReconciliationResponse(response); + log.info("9.2.4 对账接口-分页循环拉取,返回报文:{}", mallOutboundPageResult); + if (CollUtil.isEmpty(mallOutboundPageResult.getOrderInfos())) { + throw new IllegalArgumentException("供应商对账分页数据为空,页码:" + dto.getPage()); + } + orderInfoList.addAll(mallOutboundPageResult.getOrderInfos()); + page++; + } + + log.info("9.2.4 通过对接接口从供应商获取对账订单数据调用结束,返回报文:{}", orderInfoList); + return orderInfoList; + } + + /** + * 解析 BAB7 包装或供应商直接返回的对账报文,并校验查询结果。 + */ + private MallOutboundPageResult parseReconciliationResponse(String response) { + if (StrUtil.isBlank(response)) { + throw new IllegalArgumentException("获取对账信息返回报文为空"); + } + JSONObject payload = JSONUtil.parseObj(response); + if (!payload.containsKey("resultCode")) { + JSONObject data = payload.getJSONObject("data"); + JSONObject body = data == null ? null : data.getJSONObject("Body"); + if (body == null) { + throw new IllegalArgumentException("获取对账信息返回报文格式异常"); + } + JSONObject header = body.getJSONObject("RspSvcHeader"); + if (header == null || !"000000000000".equals(header.getStr("ReturnCode"))) { + throw new IllegalArgumentException(header == null ? "BAB7返回服务头缺失" : + StrUtil.blankToDefault(header.getStr("ReturnMsg"), "BAB7获取对账信息失败")); + } + JSONObject serviceBody = body.getJSONObject("RspSvcBody"); + if (serviceBody == null || !"200".equals(serviceBody.getStr("ReturnStCd"))) { + throw new IllegalArgumentException("BAB7调用供应商对账接口失败"); + } + String content = serviceBody.getStr("ReturnCntnt"); + if (StrUtil.isBlank(content)) { + throw new IllegalArgumentException("供应商对账接口返回内容为空"); + } + payload = JSONUtil.parseObj(content); + } + Integer resultCode = payload.getInt("resultCode"); + if (resultCode == null) { + throw new IllegalArgumentException("供应商对账接口返回状态缺失"); + } + if (resultCode != 0) { + throw new IllegalArgumentException(StrUtil.blankToDefault(payload.getStr("message"), "供应商对账查询失败")); + } + MallOutboundPageResult result = JSONUtil.toBean(payload, MallOutboundPageResult.class); + if (CollUtil.isNotEmpty(result.getOrderInfos()) && (result.getTotalPage() == null || result.getTotalPage() < 1)) { + throw new IllegalArgumentException("供应商对账接口返回总页数无效"); + } + return result; + } + + /** + * 组装请求报文 + * + * @param dto 请求参数 + * @param supplier 供应商信息 + * @return 对账接口请求入参 + */ + private HashMap 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 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; + } +} diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallReconciliationResultNoticeService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallReconciliationResultNoticeService.java new file mode 100644 index 00000000..c3442eab --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallReconciliationResultNoticeService.java @@ -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 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); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallUnReconciledMsgNoticeService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallUnReconciledMsgNoticeService.java index 7bee8b24..e03c0ede 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallUnReconciledMsgNoticeService.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/MallUnReconciledMsgNoticeService.java @@ -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 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); + } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/support/AbstractMallBridgeSupport.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/support/AbstractMallBridgeSupport.java index c1f928bb..b9c2b7ce 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/support/AbstractMallBridgeSupport.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/service/support/AbstractMallBridgeSupport.java @@ -34,43 +34,105 @@ import java.util.stream.Collectors; *

只放通用转换和常量,具体接口业务放在各自服务中。

*/ public abstract class AbstractMallBridgeSupport { + /** + * 访问令牌有效期,单位为秒(1 小时)。 + */ protected static final long ACCESS_EXPIRE_SECONDS = 3600; + /** + * 刷新令牌有效期,单位为秒(7 天)。 + */ protected static final long REFRESH_EXPIRE_SECONDS = 86400L * 7; + /** + * 订单状态:待支付。 + */ protected static final int ORDER_PENDING_PAY = 0; + /** + * 订单状态:已支付。 + */ protected static final int ORDER_FINISH_PAY = 1; + /** + * 订单状态:处理中。 + */ protected static final int ORDER_IN_PROGRESS = 2; + /** + * 订单状态:已取消。 + */ protected static final int ORDER_CANCELLED = 3; + /** + * 订单状态:已收货。 + */ protected static final int ORDER_RECEIVED = 4; + /** + * 订单状态:已完成。 + */ protected static final int ORDER_COMPLETED = 5; + /** + * 订单状态:退换货。 + */ protected static final int ORDER_EXCHANGE = 6; + /** + * 订单状态:已退款。 + */ protected static final int ORDER_REFUNDED = 7; + /** + * 订单状态:已作废。 + */ protected static final int ORDER_ABOLISH = 8; + /** + * 供桥接服务查询和保存用户、订单、商品等数据的 DAO。 + */ protected final Dao dao; + /** + * 初始化桥接公共支撑类,复用业务服务传入的 DAO。 + */ protected AbstractMallBridgeSupport(Dao dao) { this.dao = dao; } + /** + * 检查订单事件及供应商、订单、主订单、用户标识是否齐全;不校验对应数据是否存在。 + */ protected boolean hasRequiredOrderEventFields(MallOrderEventDTO dto) { return dto != null && !StrUtil.hasBlank(dto.getSupplierId(), dto.getOrderId(), dto.getMainOrderId(), dto.getUserId()); } - protected Sys_user fetchUser(String userId) { - Sys_user user = dao.fetch(Sys_user.class, Cnd.where("id", "=", userId).and("disabled", "=", false).and("delFlag", "=", false)); - if (user != null) { - return user; + /** + * 根据对外用户虚拟 ID 查询未禁用、未删除的系统用户。 + * + * @param virtualId 供应商接口传入的用户虚拟 ID,不是系统用户主键 + * @return 符合条件的用户;虚拟 ID 为空或用户不存在时返回 null + */ + protected Sys_user fetchUser(String virtualId) { + if (StrUtil.isBlank(virtualId)) { + return null; } - return dao.fetch(Sys_user.class, Cnd.where("loginname", "=", userId).and("disabled", "=", false).and("delFlag", "=", false)); + return dao.fetch(Sys_user.class, Cnd.where("virtual_id", "=", virtualId) + .and("disabled", "=", false).and("delFlag", "=", false)); } + /** + * 将退换货事件和详情转换为退换货记录,并从原子单补充商品图片及文件信息。 + * + * @param dto 订单事件,其中 userId 为用户虚拟 ID,保存时转换为系统用户主键 + * @param orderInfo 供应商返回的退换货单详情 + * @param sub 原子单,提供商品扩展信息 + * @param orderStatus 待保存的退换货订单状态 + * @return 尚未入库的退换货记录 + * @throws IllegalArgumentException 虚拟 ID 对应的有效用户不存在时抛出 + */ protected PointsMallOrderExchange toExchange(MallOrderEventDTO dto, OrderInfoDTO orderInfo, PointsMallOrderSub sub, Integer orderStatus) { + Sys_user user = fetchUser(dto.getUserId()); + if (user == null) { + throw new IllegalArgumentException("用户虚拟ID未配置或不存在"); + } PointsMallOrderExchange exchange = new PointsMallOrderExchange(); exchange.setOrderMainId(dto.getMainOrderId()); exchange.setOrderId(dto.getOrderId()); exchange.setSupplierId(dto.getSupplierId()); - exchange.setUserId(dto.getUserId()); + exchange.setUserId(user.getId()); exchange.setAplName(orderInfo.getAplName()); exchange.setOrderCreateTime(parseDate(orderInfo.getCreateTime())); exchange.setOrderUpdateTime(parseDate(orderInfo.getUpdateTime())); @@ -90,6 +152,9 @@ public abstract class AbstractMallBridgeSupport { return exchange; } + /** + * 按 SKU 将原子单的图片及文件信息覆盖到传入商品列表中;原子单重复 SKU 取首项,传入列表为空时返回空列表。 + */ protected List mergeProductInfo(List products, String subExtJson) { if (products == null || products.isEmpty()) { return Collections.emptyList(); @@ -111,6 +176,15 @@ public abstract class AbstractMallBridgeSupport { return products; } + /** + * 将供应商订单详情转换为主订单,金额为空时按零处理,现金支付标记初始化为 0。 + * + * @param supplierId 供应商标识 + * @param userId 已由虚拟 ID 查得的系统用户主键 + * @param orderInfo 供应商返回的订单详情 + * @param state 主订单状态 + * @return 尚未入库的主订单 + */ protected PointsMallOrderMain toMain(String supplierId, String userId, OrderInfoDTO orderInfo, Integer state) { PointsMallOrderMain main = new PointsMallOrderMain(); main.setOrderId(orderInfo.getOrderId()); @@ -138,6 +212,9 @@ public abstract class AbstractMallBridgeSupport { return main; } + /** + * 从主订单复制信息生成子订单;子订单号为空时使用主订单号,更新时间取当前时间,对账和发票状态初始化为 0。 + */ protected PointsMallOrderSub toSub(PointsMallOrderMain main, String subOrderId) { PointsMallOrderSub sub = new PointsMallOrderSub(); sub.setOrderId(StrUtil.blankToDefault(subOrderId, main.getOrderId())); @@ -167,6 +244,9 @@ public abstract class AbstractMallBridgeSupport { return sub; } + /** + * 将商品逐条保存到指定子订单,仅保存图片列表中的首张图片;商品列表为 null 时不处理。 + */ protected void insertProducts(String orderSubId, List products) { if (products == null) { return; @@ -184,6 +264,9 @@ public abstract class AbstractMallBridgeSupport { } } + /** + * 将查询记录转换为对外订单详情,统一日期和金额格式,并读取订单商品信息。 + */ protected OrderInfoDTO toOrderInfo(Record record) { OrderInfoDTO dto = new OrderInfoDTO(); dto.setOrderId(record.getString("orderId")); @@ -211,6 +294,9 @@ public abstract class AbstractMallBridgeSupport { return dto; } + /** + * 优先读取订单下未删除的子单商品;无商品记录时从 extJson 解析,扩展信息为空或为 null 字符串时返回空列表。 + */ protected List readProducts(String orderId, String extJson) { List products = dao.query(PointsMallOrderSubProduct.class, Cnd.where("orderSubId", "=", orderId).and("delFlag", "=", false)); if (!products.isEmpty()) { @@ -222,6 +308,9 @@ public abstract class AbstractMallBridgeSupport { return JSONUtil.toList(extJson, OrderProInfoDTO.class); } + /** + * 将子单商品转换为对外商品信息,单张图片包装为列表,并以 SKU 和图片地址填充文件标识及路径。 + */ protected OrderProInfoDTO toProductDTO(PointsMallOrderSubProduct product) { OrderProInfoDTO dto = new OrderProInfoDTO(); dto.setSku(product.getSku()); @@ -235,6 +324,9 @@ public abstract class AbstractMallBridgeSupport { return dto; } + /** + * 将发票记录转换为对外发票信息,并将开票时间转换为本地日期。 + */ protected InvoiceInfoDTO toInvoiceInfoDTO(PointsMallInvoiceInfo info) { InvoiceInfoDTO dto = new InvoiceInfoDTO(); dto.setInvoiceId(info.getInvoiceId()); @@ -253,6 +345,9 @@ public abstract class AbstractMallBridgeSupport { return dto; } + /** + * 解析日期字符串,将日期时间分隔符 T 替换为空格;空白值返回 null,非法日期由日期工具抛出异常。 + */ protected Date parseDate(String value) { if (StrUtil.isBlank(value)) { return null; @@ -260,6 +355,9 @@ public abstract class AbstractMallBridgeSupport { return DateUtil.parse(value.replace("T", " ")); } + /** + * 将日期值统一格式化为 yyyy-MM-dd HH:mm:ss;null 值返回 null。 + */ protected String formatDate(Object value) { if (value == null) { return null; @@ -267,10 +365,16 @@ public abstract class AbstractMallBridgeSupport { return DateUtil.format(Convert.toDate(value), "yyyy-MM-dd HH:mm:ss"); } + /** + * 使用系统默认时区将时间转换为日期;null 值返回 null。 + */ protected LocalDate toLocalDate(Date date) { return date == null ? null : date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); } + /** + * 拆分逗号分隔的标识,去除首尾空白、空项和重复项,保留首次出现的顺序;空白值返回空列表。 + */ protected List splitIds(String value) { if (StrUtil.isBlank(value)) { return Collections.emptyList(); @@ -282,18 +386,30 @@ public abstract class AbstractMallBridgeSupport { .collect(Collectors.toList()); } + /** + * 金额为 null 时返回零,否则保留原值。 + */ protected BigDecimal nvl(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; } + /** + * 金额为 null 时返回指定默认值,否则保留原值。 + */ protected BigDecimal nvl(BigDecimal value, BigDecimal defaultValue) { return value == null ? defaultValue : value; } + /** + * 将对象转换为金额,无法转换或值为 null 时使用零作为默认值。 + */ protected BigDecimal decimal(Object value) { return Convert.toBigDecimal(value, BigDecimal.ZERO); } + /** + * 将字符串转换为整数;空白值或无法转换的值返回 null。 + */ protected Integer parseInteger(String value) { if (StrUtil.isBlank(value)) { return null; diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/util/MallBridgeResponseUtil.java b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/util/MallBridgeResponseUtil.java new file mode 100644 index 00000000..3d1c66c1 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/mallbridge/util/MallBridgeResponseUtil.java @@ -0,0 +1,37 @@ +package com.budwk.app.zhgh.pointsmall.mallbridge.util; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; + +/** 提取供应商业务响应,兼容 BAB7 包装报文。 */ +public class MallBridgeResponseUtil { + public static JSONObject parse(String response) { + if (StrUtil.isBlank(response)) { + throw new IllegalArgumentException("供应商接口返回为空"); + } + JSONObject payload = JSONUtil.parseObj(response); + if (!payload.containsKey("resultCode")) { + JSONObject data = payload.getJSONObject("data"); + JSONObject body = data == null ? null : data.getJSONObject("Body"); + JSONObject header = body == null ? null : body.getJSONObject("RspSvcHeader"); + if (header == null || !"000000000000".equals(header.getStr("ReturnCode"))) { + throw new IllegalArgumentException(header == null ? "BAB7返回报文格式异常" : + StrUtil.blankToDefault(header.getStr("ReturnMsg"), "BAB7调用失败")); + } + JSONObject serviceBody = body.getJSONObject("RspSvcBody"); + if (serviceBody == null || !"200".equals(serviceBody.getStr("ReturnStCd"))) { + throw new IllegalArgumentException("BAB7调用供应商接口失败"); + } + String content = serviceBody.getStr("ReturnCntnt"); + if (StrUtil.isBlank(content)) { + throw new IllegalArgumentException("供应商业务响应为空"); + } + payload = JSONUtil.parseObj(content); + } + if (payload.getInt("resultCode") == null) { + throw new IllegalArgumentException("供应商业务响应缺少结果码"); + } + return payload; + } +} diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/order/service/impl/PointsMallOrderSubHistoryServiceImpl.java b/src/main/java/com/budwk/app/zhgh/pointsmall/order/service/impl/PointsMallOrderSubHistoryServiceImpl.java index a2e9eef2..dad2fd63 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/order/service/impl/PointsMallOrderSubHistoryServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/order/service/impl/PointsMallOrderSubHistoryServiceImpl.java @@ -2,33 +2,98 @@ package com.budwk.app.zhgh.pointsmall.order.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileTypeUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.sys.enums.SysFileEngineTypeEnum; +import com.budwk.app.sys.services.SysFileService; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallOrderEventDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.OrderInfoDTO; import com.budwk.app.zhgh.pointsmall.mallbridge.dto.OrderProInfoDTO; +import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallPictureOutboundResult; +import com.budwk.app.zhgh.pointsmall.mallbridge.service.MallInvoiceInfoQueryService; import com.budwk.app.zhgh.pointsmall.order.enums.MallOrderStatusEnum; import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSub; import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSubHistory; import com.budwk.app.zhgh.pointsmall.order.result.SubOrderBuildResult; import com.budwk.app.zhgh.pointsmall.order.service.PointsMallOrderSubHistoryService; import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import java.util.Collections; import java.util.List; +import java.io.ByteArrayInputStream; +import java.util.Base64; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; @IocBean(args = {"refer:dao"}) public class PointsMallOrderSubHistoryServiceImpl extends BaseServiceImpl implements PointsMallOrderSubHistoryService { + @Inject + private MallInvoiceInfoQueryService mallInvoiceInfoQueryService; + @Inject + private SysFileService sysFileService; + public PointsMallOrderSubHistoryServiceImpl(Dao dao) { super(dao); } @Override public List getPictureBase64(String supplierId, String orderId, String queryType, List orderProInfo) { - return CollUtil.isEmpty(orderProInfo) ? Collections.emptyList() : orderProInfo; + if (CollUtil.isEmpty(orderProInfo)) { + return Collections.emptyList(); + } + List fileIds = orderProInfo.stream().map(OrderProInfoDTO::getSku) + .filter(StrUtil::isNotBlank).distinct().collect(Collectors.toList()); + if (fileIds.isEmpty()) { + throw new IllegalArgumentException("商品SKU为空,无法获取商品图片"); + } + try { + // 供应商按子订单和 SKU 返回图片 Base64,订单只保存工会文件服务地址。 + MallPictureOutboundResult result = mallInvoiceInfoQueryService.queryFiles(supplierId, orderId, queryType, fileIds); + Map localUrls = new LinkedHashMap<>(); + for (OrderProInfoDTO product : orderProInfo) { + String sku = product.getSku(); + if (StrUtil.isBlank(sku)) { + throw new IllegalArgumentException("商品SKU为空,无法获取商品图片"); + } + String localUrl = localUrls.get(sku); + if (localUrl == null) { + String base64 = result.getImageEncodeMap() == null ? null : result.getImageEncodeMap().get(sku); + if (StrUtil.isBlank(base64)) { + if (CollUtil.isEmpty(product.getPicInfo()) && StrUtil.isBlank(product.getFilePath()) && StrUtil.isBlank(product.getFileId())) { + continue; + } + throw new IllegalArgumentException("未获取到商品图片Base64:" + sku); + } + if (base64.startsWith("data:")) { + base64 = base64.substring(base64.indexOf(',') + 1); + } + byte[] bytes = Base64.getDecoder().decode(base64.replaceAll("\\s", "")); + String fileType = FileTypeUtil.getType(new ByteArrayInputStream(bytes)); + if (bytes.length == 0 || fileType == null || !fileType.matches("(?i)jpg|jpeg|png|gif|bmp|webp")) { + throw new IllegalArgumentException("商品图片内容为空或格式不支持:" + sku); + } + localUrl = sysFileService.uploadReturnUrl(SysFileEngineTypeEnum.MINIO.getValue(), + "product-" + UUID.randomUUID() + "." + fileType.toLowerCase(java.util.Locale.ROOT), bytes); + if (StrUtil.isBlank(localUrl)) { + throw new IllegalArgumentException("商品图片上传失败:" + sku); + } + localUrls.put(sku, localUrl); + } + product.setPicInfo(Collections.singletonList(localUrl)); + product.setFilePath(localUrl); + product.setFileId(localUrl); + } + return orderProInfo; + } catch (Exception e) { + throw new IllegalArgumentException("商品图片同步失败:" + e.getMessage(), e); + } } @Override diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/controller/PointsMallReconciliationController.java b/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/controller/PointsMallReconciliationController.java index 6a1bb191..fde95910 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/controller/PointsMallReconciliationController.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/controller/PointsMallReconciliationController.java @@ -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 diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/param/PointsMallReconciliationParam.java b/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/param/PointsMallReconciliationParam.java index 04c3164a..8a81f2a4 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/param/PointsMallReconciliationParam.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/param/PointsMallReconciliationParam.java @@ -8,6 +8,10 @@ public class PointsMallReconciliationParam { private String supplierId; private Integer reconciliationType; private String orderId; + private String queryOrderId; + private String userId; + private Integer reconciliationStatus; + private Integer invoiceStatus; private String startDate; private String endDate; } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/service/PointsMallReconciliationService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/service/PointsMallReconciliationService.java index a1817522..547252e0 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/service/PointsMallReconciliationService.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/service/PointsMallReconciliationService.java @@ -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 recordPage(PointsMallReconciliationRecordPageParam param); diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/service/impl/PointsMallReconciliationServiceImpl.java b/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/service/impl/PointsMallReconciliationServiceImpl.java index 8ba61444..cd8c9854 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/service/impl/PointsMallReconciliationServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliation/service/impl/PointsMallReconciliationServiceImpl.java @@ -2,35 +2,66 @@ 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.sys.models.Sys_user; +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 implements PointsMallReconciliationService { + @Inject + private MallReconciliationInfoQueryService mallReconciliationInfoQueryService; + + @Inject + private MallUnReconciledMsgNoticeService unReconciledMsgNoticeService; + + @Inject + private MallReconciliationResultNoticeService reconciliationResultNoticeService; + public PointsMallReconciliationServiceImpl(Dao dao) { super(dao); } @@ -53,37 +84,148 @@ public class PointsMallReconciliationServiceImpl extends BaseServiceImpl orders = dao().query(PointsMallOrderSub.class, reconciliationCnd(param, "")); - if (CollUtil.isEmpty(orders)) { - return "未查询到待对账订单"; + public Result executeReconciliation(PointsMallReconciliationParam reconciliationBO) { + try { + checkReconciliationParam(reconciliationBO); + } catch (IllegalArgumentException e) { + return Result.error(e.getMessage()); + } + Integer reconciliationType = reconciliationBO.getReconciliationType(); + + // 从积分商城订单表查询对账订单 + List orderInfoList = dao().query(PointsMallOrderSub.class, reconciliationCnd(reconciliationBO, "")); + if (CollUtil.isEmpty(orderInfoList)) { + return Result.error("积分商城没有查询到对账订单数据"); + } + Set orderIdList = orderInfoList.stream().map(PointsMallOrderSub::getOrderId).collect(Collectors.toSet()); + if (StrUtil.isNotBlank(reconciliationBO.getOrderId()) + && !orderIdList.containsAll(StrUtil.splitTrim(reconciliationBO.getOrderId(), ","))) { + return Result.error("部分勾选订单已不符合对账条件,请重新查询后选择"); } - String batchNo = DateUtil.format(new Date(), "yyyyMMddHHmmss") + IdUtil.fastSimpleUUID().substring(0, 6); - int success = 0; - int exception = 0; - List records = new ArrayList<>(); + QueryReconciliationInfoDTO queryReconciliationInfoDTO = new QueryReconciliationInfoDTO(); + queryReconciliationInfoDTO.setSupplierId(reconciliationBO.getSupplierId()); + queryReconciliationInfoDTO.setReconciliationType(reconciliationType); + queryReconciliationInfoDTO.setOrderId(String.join(",", orderIdList)); + queryReconciliationInfoDTO.setStartDate(reconciliationBO.getStartDate()); + queryReconciliationInfoDTO.setEndDate(reconciliationBO.getEndDate()); + queryReconciliationInfoDTO.setPage(1); + queryReconciliationInfoDTO.setPageSize(100); + // 通过对账接口从供应商拉取对账订单数据 + List result = mallReconciliationInfoQueryService.queryReconciliationInfo(queryReconciliationInfoDTO); + if (CollUtil.isEmpty(result)) { + return Result.error("供应商侧没有拉取到对账订单数据"); + } + result = result.stream().filter(o -> orderIdList.contains(o.getOrderId())).collect(Collectors.toList()); + String supplierId = reconciliationBO.getSupplierId(); + result.forEach(o -> o.setSupplierId(supplierId)); + // 根据供应商ID + 订单ID进行分组 + Map> supplierOrderList = result.stream() + .collect(Collectors.groupingBy(o -> String.join("-", o.getSupplierId(), o.getOrderId()))); - 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())); + // 根据供应商ID + 订单ID进行分组 + Map> pfOrderList = orderInfoList.stream() + .collect(Collectors.groupingBy(o -> String.join("-", o.getSupplierId(), o.getOrderId()))); + + Set pfKeySet = pfOrderList.keySet(); + Set supplierKeySet = supplierOrderList.keySet(); + // 浦发商城和供应商都存在的订单 + Set sameKeySet = pfKeySet.stream().filter(supplierKeySet::contains).collect(Collectors.toSet()); + // 1.浦发商城和供应商两边的《供应商ID + 订单ID》可以匹配上 + List reconciliationList1 = getReconciliationResult(sameKeySet, pfOrderList, supplierOrderList); + if (CollUtil.isNotEmpty(reconciliationList1)) { + List 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 reconciliationSuccessList = reconciliationList1.stream() + .filter(o -> o.getDiffType() == 0).collect(Collectors.toList()); + if (CollUtil.isNotEmpty(reconciliationSuccessList)) { + // 更新子订单状态为对账完成 + subOrderReconciliationUpdate(reconciliationSuccessList, 1); } - records.add(record); } - dao().insert(records); - return "对账完成,批次号:" + batchNo + ",成功:" + success + ",异常:" + exception; + // 2.积分商城已确认收货,对账接口未返回 + List 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 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()); + if (StrUtil.isNotBlank(o.getUserId())) { + Sys_user user = dao().fetch(Sys_user.class, o.getUserId()); + if (user == null || StrUtil.isBlank(user.getVirtualId())) { + throw new IllegalArgumentException("用户虚拟ID未配置或不存在"); + } + resultOrderDTO.setUserId(user.getVirtualId()); + } + 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 @@ -93,21 +235,21 @@ public class PointsMallReconciliationServiceImpl extends BaseServiceImpl 1) { - cnd.and(field(alias, "orderId"), "in", orderIds); - } else { - cnd.and(field(alias, "orderId"), "like", "%" + param.getOrderId() + "%"); - } + cnd.and(field(alias, "orderId"), "in", StrUtil.splitTrim(param.getOrderId(), ",")); } - addDateRange(cnd, alias, param.getStartDate(), param.getEndDate()); return cnd; } @@ -190,56 +334,233 @@ public class PointsMallReconciliationServiceImpl extends BaseServiceImpl 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 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 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 orderNotReturn(Set pfKeySet, Set supplierKeySet, + Map> pfOrderList) { + List reconciliationList = new ArrayList<>(); + pfKeySet.stream().filter(key -> !supplierKeySet.contains(key)).forEach(key -> { + List 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 orderNotExist(Set supplierKeySet, Set pfKeySet, + Map> supplierOrderList) { + List reconciliationList = new ArrayList<>(); + supplierKeySet.stream().filter(key -> !pfKeySet.contains(key)).forEach(key -> { + List 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 getReconciliationResult(Set sameKeySet, + Map> pfOrderList, + Map> supplierOrderList) { + List reconciliationList = new ArrayList<>(); + sameKeySet.forEach(key -> { + List pOrderList = pfOrderList.get(key); + List 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 pfOrderProList = JSONUtil.toList(productInfo, OrderProInfoDTO.class); + // 处理商品名称、价格、税率的小数位(不参与比较的字段) + handleField(pfOrderProList); + + // 从对账接口获取的订单商品 + OrderInfoByReconciliationDTO supplierOrder = sOrderList.get(0); + List 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 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是否完全一致,不考虑顺序 + * + * @param list1 集合1 + * @param list2 集合2 + * @return 是否相同 + */ + private boolean compareProduct(List list1, List 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; } } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliationrecord/service/PointsMallReconciliationRecordService.java b/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliationrecord/service/PointsMallReconciliationRecordService.java index 41d80b63..9e49563b 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliationrecord/service/PointsMallReconciliationRecordService.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliationrecord/service/PointsMallReconciliationRecordService.java @@ -5,13 +5,13 @@ import com.budwk.app.base.service.BaseService; import com.budwk.app.zhgh.pointsmall.reconciliationrecord.models.PointsMallReconciliationRecord; import com.budwk.app.zhgh.pointsmall.reconciliationrecord.param.PointsMallReconciliationRecordPageParam; import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier; -import org.nutz.dao.entity.Record; import java.util.List; +import java.util.Map; public interface PointsMallReconciliationRecordService extends BaseService { - Pagination page(PointsMallReconciliationRecordPageParam param); + Pagination> page(PointsMallReconciliationRecordPageParam param); List listSuppliers(); } diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliationrecord/service/impl/PointsMallReconciliationRecordServiceImpl.java b/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliationrecord/service/impl/PointsMallReconciliationRecordServiceImpl.java index 405b3a43..debad93e 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliationrecord/service/impl/PointsMallReconciliationRecordServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/reconciliationrecord/service/impl/PointsMallReconciliationRecordServiceImpl.java @@ -3,6 +3,7 @@ package com.budwk.app.zhgh.pointsmall.reconciliationrecord.service.impl; import cn.hutool.core.util.StrUtil; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.sys.models.Sys_user; import com.budwk.app.zhgh.pointsmall.reconciliationrecord.models.PointsMallReconciliationRecord; import com.budwk.app.zhgh.pointsmall.reconciliationrecord.param.PointsMallReconciliationRecordPageParam; import com.budwk.app.zhgh.pointsmall.reconciliationrecord.service.PointsMallReconciliationRecordService; @@ -10,11 +11,14 @@ import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier; 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.IocBean; import java.util.List; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; @IocBean(args = {"refer:dao"}) public class PointsMallReconciliationRecordServiceImpl extends BaseServiceImpl implements PointsMallReconciliationRecordService { @@ -24,30 +28,46 @@ public class PointsMallReconciliationRecordServiceImpl extends BaseServiceImpl

page(PointsMallReconciliationRecordPageParam param) { + public Pagination> page(PointsMallReconciliationRecordPageParam param) { Cnd cnd = Cnd.where("r.delFlag", "=", false); if (StrUtil.isNotBlank(param.getSupplierId())) { cnd.and("r.supplierId", "=", param.getSupplierId()); } if (StrUtil.isNotBlank(param.getOrderId())) { - cnd.and("r.orderId", "like", "%" + param.getOrderId() + "%"); + cnd.and("r.orderId", "=", param.getOrderId()); } if (StrUtil.isNotBlank(param.getUserId())) { - cnd.and("r.userId", "like", "%" + param.getUserId() + "%"); + List users = dao().query(Sys_user.class, Cnd.where("loginname", "like", "%" + param.getUserId().trim() + "%") + .and("delFlag", "=", false)); + if (users.isEmpty()) { + return new Pagination<>(param.getPageNumber(), param.getPageSize(), 0, Collections.emptyList()); + } + cnd.and("r.userId", "in", users.stream().map(Sys_user::getId).collect(Collectors.toList())); } if (param.getDiffType() != null) { cnd.and("r.diffType", "=", param.getDiffType()); } if (StrUtil.isNotBlank(param.getBatchNo())) { - cnd.and("r.batchNo", "like", "%" + param.getBatchNo() + "%"); + cnd.and("r.batchNo", "=", param.getBatchNo()); } if (param.getNoticeStatus() != null) { cnd.and("r.noticeStatus", "=", param.getNoticeStatus()); } - cnd.desc("r.createdAt"); + cnd.desc("r.batchNo").desc("r.createdAt"); Sql sql = Sqls.create("select r.*, s.supplierName from points_mall_reconciliation_record r left join points_mall_supplier s on r.supplierId = s.supplierId and s.delFlag = 0 $condition"); sql.setCondition(cnd); - return listPageMap(param.getPageNumber(), param.getPageSize(), sql); + Pagination> page = listPageMap(param.getPageNumber(), param.getPageSize(), sql); + if (page.getList() != null && !page.getList().isEmpty()) { + List userIds = page.getList().stream().map(record -> (String) record.get("userId")) + .filter(StrUtil::isNotBlank).distinct().collect(Collectors.toList()); + Map loginNames = new HashMap<>(); + if (!userIds.isEmpty()) { + List users = dao().query(Sys_user.class, Cnd.where("id", "in", userIds).and("delFlag", "=", false)); + users.forEach(user -> loginNames.put(user.getId(), user.getLoginname())); + } + page.getList().forEach(record -> record.put("loginname", loginNames.get(record.get("userId")))); + } + return page; } @Override diff --git a/src/main/java/com/budwk/app/zhgh/pointsmall/supplier/service/impl/PointsMallSupplierServiceImpl.java b/src/main/java/com/budwk/app/zhgh/pointsmall/supplier/service/impl/PointsMallSupplierServiceImpl.java index 01371ac7..adb8a578 100644 --- a/src/main/java/com/budwk/app/zhgh/pointsmall/supplier/service/impl/PointsMallSupplierServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/pointsmall/supplier/service/impl/PointsMallSupplierServiceImpl.java @@ -1,23 +1,36 @@ package com.budwk.app.zhgh.pointsmall.supplier.service.impl; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.budwk.app.base.exception.BaseException; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.sys.models.Sys_user; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier; import com.budwk.app.zhgh.pointsmall.supplier.param.PointsMallSupplierPageParam; import com.budwk.app.zhgh.pointsmall.supplier.service.PointsMallSupplierService; import org.nutz.dao.Chain; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; +import org.nutz.integration.jedis.RedisService; +import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; @IocBean(args = {"refer:dao"}) public class PointsMallSupplierServiceImpl extends BaseServiceImpl implements PointsMallSupplierService { + @Inject + private RedisService redisService; + public PointsMallSupplierServiceImpl(Dao dao) { super(dao); } @@ -52,7 +65,32 @@ public class PointsMallSupplierServiceImpl extends BaseServiceImpl list = dao().query(PointsMallSupplier.class, cnd); + if (Objects.equals(1, mallType)) { + Sys_user user = dao().fetch(Sys_user.class, SecurityUtil.getUserId()); + if (user == null || StrUtil.isBlank(user.getVirtualId())) { + throw new BaseException("用户不存在"); + } + for (PointsMallSupplier supplier : list) { + supplier.setSupplierUrl(getSupplierIndexUrl(user, supplier)); + } + } + return list; + } + + private String getSupplierIndexUrl(Sys_user user, PointsMallSupplier supplier) { + String supplierUrl = supplier.getSupplierUrl(); + if (StrUtil.isBlank(supplierUrl) || !Objects.equals(1, supplier.getMallType())) { + return supplierUrl; + } + Map params = new HashMap<>(); + params.put("userId", user.getVirtualId()); + if ("10101013".equals(supplier.getSupplierId())) { + params.put("orgId", user.getVirtualOrgId()); + } + String keyword = IdUtil.fastSimpleUUID(); + redisService.setex("supplier-session:" + keyword, 300, JSONUtil.toJsonStr(params)); + return supplierUrl.concat(keyword); } @Override diff --git a/src/main/resources/db/points-mall.sql b/src/main/resources/db/points-mall.sql index d1982fff..afb83e9b 100644 --- a/src/main/resources/db/points-mall.sql +++ b/src/main/resources/db/points-mall.sql @@ -510,3 +510,10 @@ CREATE TABLE IF NOT EXISTS `points_mall_schedule_user_points_record` ( KEY `idx_points_mall_schedule_user_points_user` (`userId`), KEY `idx_points_mall_schedule_user_points_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分商城定时发放积分记录'; + +ALTER TABLE sys_user + ADD COLUMN virtual_id VARCHAR(64) NULL COMMENT '虚拟用户ID,积分商城向供应商传递的用户标识', + ADD COLUMN virtual_org_id VARCHAR(255) NULL COMMENT '虚拟组织ID'; + +UPDATE sys_user SET virtual_id = MD5(CONCAT(id, '_', loginName, '_', salt)) +where id is not null and loginName is not null and salt is not null; \ No newline at end of file diff --git a/src/main/resources/views/platform/zhgh/points-mall/invoice/index.html b/src/main/resources/views/platform/zhgh/points-mall/invoice/index.html index 63be5be5..38b92b18 100644 --- a/src/main/resources/views/platform/zhgh/points-mall/invoice/index.html +++ b/src/main/resources/views/platform/zhgh/points-mall/invoice/index.html @@ -14,19 +14,11 @@ layout("/layouts/platform.html"){ - - - - - - - 开票申请 - 可开票订单 @@ -36,13 +28,11 @@ layout("/layouts/platform.html"){ {{ invoiceResultName(row.bSuccess) }} - - - - + @@ -58,120 +48,25 @@ layout("/layouts/platform.html"){ > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ orderTotalPoints }} - 计算 - - - - 取消 - 提交申请 - - - - - - - - - - - - - - - - - - - - - - - - - 查询 - 重置 - 用于开票 - - - - - + + + - - - - - - + + + + + + + + + 总消耗积分:{{ orderTotalPoints }} 关闭 - - - - - - - - - - - - - 查询 - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - + 重新获取发票 关闭 + + + + {{ detailData.mainId || '-' }} + {{ detailData.invoiceId || '-' }} + {{ detailData.invoiceCode || '-' }} + {{ detailData.invoiceDate || '-' }} + + {{ Number(detailData.invoiceType) === 9 ? '全电发票(增值税专用发票)' : Number(detailData.invoiceType) === 10 ? '全电发票(增值税普通发票)' : invoiceTypeName(detailData.invoiceType) }} + + {{ Number(detailData.status) === 1 ? '成功' : Number(detailData.status) === 2 ? '失败' : '未知' }} + ¥{{ Number(detailData.invoiceNakeAmount || 0).toFixed(2) }} + {{ detailData.invoiceTaxRate == null ? '-' : (Number(detailData.invoiceTaxRate) * 100).toFixed(2) + '%' }} + ¥{{ Number(detailData.invoiceTaxAmount || 0).toFixed(2) }} + ¥{{ Number(detailData.invoiceAmount || 0).toFixed(2) }} + {{ detailData.remark }} + + + 暂无地址 + + + + + 重新获取发票 + 关闭 + + + +

+ +
+ 关闭 + diff --git a/src/main/resources/views/platform/zhgh/points-mall/reconciliation/index.html b/src/main/resources/views/platform/zhgh/points-mall/reconciliation/index.html index e0ee1f7d..103e55c4 100644 --- a/src/main/resources/views/platform/zhgh/points-mall/reconciliation/index.html +++ b/src/main/resources/views/platform/zhgh/points-mall/reconciliation/index.html @@ -3,44 +3,74 @@ layout("/layouts/platform.html"){ #-->
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 查询 + 重置 + 对账 + 开票申请 + + + + - - 对账 - 对账记录 - - + + + @@ -87,39 +117,46 @@ layout("/layouts/platform.html"){ - - - - - - - - - - - - - - - - - - - - - - - {{ reconcilePointsTotal }} - - - + + + {{ reconcilePointsTotal }} + + 取消 - 计算金额 确认对账 + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 确认开票 + + + @@ -209,6 +246,10 @@ layout("/layouts/platform.html"){ supplierList: [], tableData: [], tableLoading: false, + selections: [], + lastQuery: null, + reconcileSelectionCount: 0, + hasSearched: false, pageForm: { pageNumber: 1, pageSize: 10, @@ -218,6 +259,7 @@ layout("/layouts/platform.html"){ userId: null, startDate: null, endDate: null, + reconciliationType: null, reconciliationStatus: null, invoiceStatus: null }, @@ -232,6 +274,38 @@ layout("/layouts/platform.html"){ startDate: null, endDate: null }, + applyDialogVisible: false, + applyLoading: false, + applyScope: {}, + applySelectionCount: 0, + applyForm: { + invoiceDate: [new Date().getFullYear(), String(new Date().getMonth() + 1).padStart(2, "0"), String(new Date().getDate()).padStart(2, "0")].join("-"), + invoiceTitle: "测试工会(开发测试)", + taxIdNumber: "91320100MA00000000", + invoiceAddress: "江苏省南京市测试路1号(开发测试)", + invoiceContact: "13800000000", + invoiceType: 10 + }, + applyRules: { + invoiceDate: [{ required: true, message: "请选择开票日期", trigger: "change" }], + invoiceTitle: [{ required: true, message: "请输入发票抬头", trigger: "blur" }], + taxIdNumber: [{ required: true, message: "请输入纳税人识别号", trigger: "blur" }], + invoiceAddress: [{ required: true, message: "请输入开户地址", trigger: "blur" }], + invoiceContact: [ + { required: true, message: "请输入联系电话", trigger: "blur" }, + { + validator(rule, value, callback) { + if (value && !/^1[3-9]\d{9}$/.test(value)) { + callback(new Error("请输入正确的手机号码")) + } else { + callback() + } + }, + trigger: "blur" + } + ], + invoiceType: [{ required: true, message: "请选择发票类型", trigger: "change" }] + }, recordDialogVisible: false, recordLoading: false, recordList: [], @@ -279,14 +353,41 @@ layout("/layouts/platform.html"){ return index + 1 + (this.recordForm.pageNumber - 1) * this.recordForm.pageSize }, doSearch() { + this.hasSearched = true + this.selections = [] this.pageForm.pageNumber = 1 this.pageData() }, + resetSearch() { + this.pageForm = { + pageNumber: 1, + pageSize: 10, + totalCount: 0, + supplierId: null, + orderId: null, + userId: null, + startDate: null, + endDate: null, + reconciliationType: null, + reconciliationStatus: null, + invoiceStatus: null + } + this.hasSearched = false + this.selections = [] + this.pageData() + }, + selectionChange(rows) { + this.selections = rows || [] + }, async pageData() { this.tableLoading = true - const resp = await this.$axios.post(loc() + "/pageData", this.pageForm) + const query = Object.assign({}, this.pageForm) + const resp = await this.$axios.post(loc() + "/pageData", query) this.tableLoading = false if (resp.code === 0) { + this.lastQuery = query + this.selections = [] + if (this.$refs.tableRef) this.$refs.tableRef.clearSelection() this.tableData = resp.data.list || [] this.pageForm.totalCount = resp.data.totalCount || 0 } else { @@ -308,52 +409,134 @@ layout("/layouts/platform.html"){ this.supplierList = resp.data || [] } }, - openReconcileDialog() { - this.reconcileForm = { - supplierId: this.pageForm.supplierId, - reconciliationType: this.pageForm.reconciliationStatus === 2 ? 2 : 1, - orderId: this.pageForm.orderId, - startDate: this.pageForm.startDate, - endDate: this.pageForm.endDate - } - this.reconcilePointsTotal = 0 - this.reconcileDialogVisible = true - this.loadPointsTotal() - }, - async loadPointsTotal() { - if (!this.reconcileForm.supplierId || !this.reconcileForm.startDate || !this.reconcileForm.endDate || !this.reconcileForm.reconciliationType) { - this.notifyWarning("请先选择供应商、对账类型和对账日期") + async openReconcileDialog() { + if (this.tableLoading || !this.lastQuery) { + this.notifyWarning("请先查询订单列表") return } + const query = this.lastQuery + this.reconcileSelectionCount = this.selections.length + this.reconcileForm = { + supplierId: query.supplierId, + reconciliationType: query.reconciliationType, + orderId: this.selections.map(item => item.orderId).join(",") || null, + queryOrderId: query.orderId, + userId: query.userId, + reconciliationStatus: query.reconciliationStatus, + invoiceStatus: query.invoiceStatus, + startDate: query.startDate, + endDate: query.endDate + } + this.reconcilePointsTotal = 0 + if (!this.reconciliationParamsComplete()) { + this.notifyWarning("请选择供应商、对账类型和对账日期,并查询订单列表") + return + } + if (await this.loadPointsTotal()) { + this.reconcileDialogVisible = true + } + }, + reconciliationParamsComplete() { + if (!this.reconcileForm.supplierId || !this.reconcileForm.reconciliationType) { + return false + } + if (!this.reconcileForm.startDate || !this.reconcileForm.endDate) { + return false + } + return true + }, + async loadPointsTotal() { + if (!this.reconciliationParamsComplete()) { + return false + } this.pointsTotalLoading = true - const resp = await this.$axios.post(loc() + "/pointsTotal", this.reconcileForm) - this.pointsTotalLoading = false - if (resp.code === 0) { - this.reconcilePointsTotal = resp.data || 0 - } else { + try { + const resp = await this.$axios.post(loc() + "/pointsTotal", this.reconcileForm) + if (resp.code === 0) { + this.reconcilePointsTotal = resp.data || 0 + return true + } this.notifyWarning(resp.msg) + return false + } finally { + this.pointsTotalLoading = false } }, async executeReconciliation() { - if (!this.reconcileForm.supplierId || !this.reconcileForm.startDate || !this.reconcileForm.endDate || !this.reconcileForm.reconciliationType) { - this.notifyWarning("请先选择供应商、对账类型和对账日期") + if (!this.reconciliationParamsComplete()) { + this.notifyWarning("请先选择供应商、对账类型和对账日期,并查询订单列表") return } this.executeLoading = true - const resp = await this.$axios.post(loc() + "/execute", this.reconcileForm) - this.executeLoading = false - if (resp.code === 0) { - this.notifySuccess(resp.data || "对账完成") - this.reconcileDialogVisible = false - this.pageData() - } else { - this.notifyWarning(resp.msg) + try { + const resp = await this.$axios.post(loc() + "/execute", this.reconcileForm) + if (resp.code === 0) { + this.notifySuccess(resp.data || "对账完成") + this.reconcileDialogVisible = false + this.pageData() + } else { + this.notifyWarning(resp.msg) + } + } finally { + this.executeLoading = false } }, + openApplyDialog() { + if (this.tableLoading || !this.lastQuery) { + this.notifyWarning("请先查询订单列表") + return + } + const query = this.lastQuery + if (!query.supplierId || !query.startDate || !query.endDate || query.reconciliationStatus !== 1 || query.invoiceStatus !== 0) { + this.notifyWarning("请查询对账成功且未开票的订单") + return + } + this.applySelectionCount = this.selections.length + this.applyScope = { + supplierId: query.supplierId, + startDate: query.startDate, + endDate: query.endDate, + queryOrderId: query.orderId, + userId: query.userId, + orderIds: this.selections.map(item => item.orderId).join(",") || null + } + this.applyForm = { + invoiceDate: [new Date().getFullYear(), String(new Date().getMonth() + 1).padStart(2, "0"), String(new Date().getDate()).padStart(2, "0")].join("-"), + invoiceTitle: "测试工会(开发测试)", + taxIdNumber: "91320100MA00000000", + invoiceAddress: "江苏省南京市测试路1号(开发测试)", + invoiceContact: "13800000000", + invoiceType: 10 + } + this.applyDialogVisible = true + }, + submitApply() { + this.$refs.applyFormRef.validate(async (valid) => { + if (!valid || this.applyLoading) { + return + } + this.applyLoading = true + try { + const params = Object.assign({}, this.applyForm, this.applyScope) + const resp = await this.$axios.post("/platform/zhgh/points-mall/invoice/applyInvoice", params) + if (resp.code === 0) { + this.notifySuccess(resp.data || "开票申请成功") + this.applyDialogVisible = false + this.selections = [] + this.$refs.tableRef.clearSelection() + this.pageData() + } else { + this.notifyWarning(resp.msg) + } + } finally { + this.applyLoading = false + } + }) + }, openRecordDialog() { this.recordDialogVisible = true this.recordForm.supplierId = this.pageForm.supplierId - this.recordForm.orderId = this.pageForm.orderId + this.recordForm.orderId = null this.recordPageData() }, recordSearch() { diff --git a/src/main/resources/views/platform/zhgh/points-mall/reconciliationRecord/index.html b/src/main/resources/views/platform/zhgh/points-mall/reconciliationRecord/index.html index b8f85a22..aac191ce 100644 --- a/src/main/resources/views/platform/zhgh/points-mall/reconciliationRecord/index.html +++ b/src/main/resources/views/platform/zhgh/points-mall/reconciliationRecord/index.html @@ -13,25 +13,6 @@ layout("/layouts/platform.html"){ - - - - - - - - - - - - - - - - - - - @@ -39,11 +20,10 @@ layout("/layouts/platform.html"){ - - + @@ -57,18 +37,7 @@ layout("/layouts/platform.html"){ {{ diffTypeName(row.diffType) }} - - - - - - - {{ currentRecord.batchNo || "-" }} {{ currentRecord.orderId || "-" }} {{ currentRecord.supplierName || currentRecord.supplierId || "-" }} - {{ currentRecord.userId || "-" }} + {{ currentRecord.loginname || "-" }} {{ currentRecord.pTotalPrice || "0" }} {{ currentRecord.sTotalPrice || "0" }} {{ currentRecord.pCashAmount || "0" }} @@ -121,11 +90,7 @@ layout("/layouts/platform.html"){ pageSize: 10, totalCount: 0, supplierId: null, - orderId: null, - userId: null, - diffType: null, - batchNo: null, - noticeStatus: null + orderId: null }, detailDialogVisible: false, currentRecord: null diff --git a/src/main/resources/views/platform/zhghh5/points-mall/index.html b/src/main/resources/views/platform/zhghh5/points-mall/index.html index b58408fb..77bcb11f 100644 --- a/src/main/resources/views/platform/zhghh5/points-mall/index.html +++ b/src/main/resources/views/platform/zhghh5/points-mall/index.html @@ -146,36 +146,12 @@ layout("/layouts/platform_h5.html"){ this.userPoints = resp.data || 0 } }, - async openSupplier(item) { + openSupplier(item) { if (!item.supplierUrl) { this.$toast("暂无跳转地址") return } - const userId = this.$store && this.$store.state.user ? this.$store.state.user.id : "" - if (!userId) { - this.$toast("获取用户信息失败,请重新登录后再试") - return - } - window.location.href = this.withSupplierQuery(item.supplierUrl, { - userId: userId - }) - }, - withSupplierQuery(url, params) { - const query = Object.keys(params) - .filter((key) => params[key] !== undefined && params[key] !== null && params[key] !== "") - .map((key) => encodeURIComponent(key) + "=" + encodeURIComponent(params[key])) - .join("&") - if (!query) { - return url - } - const hashIndex = url.indexOf("#") - if (hashIndex > -1) { - const base = url.slice(0, hashIndex) - const hash = url.slice(hashIndex) - const joiner = hash.indexOf("?") > -1 ? "&" : "?" - return base + hash + joiner + query - } - return url + (url.indexOf("?") > -1 ? "&" : "?") + query + window.location.href = item.supplierUrl }, onImgError(e) { e.target.src = this.defaultLogo diff --git a/src/main/resources/views/platform/zhghh5/points-mall/order/index.html b/src/main/resources/views/platform/zhghh5/points-mall/order/index.html index 90947f73..fcfd3d48 100644 --- a/src/main/resources/views/platform/zhghh5/points-mall/order/index.html +++ b/src/main/resources/views/platform/zhghh5/points-mall/order/index.html @@ -257,6 +257,7 @@ layout("/layouts/platform_h5.html"){ return item ? item.class : "gray" }, getOrderStatusItem(value) { + if (Number(value) === 1) return { code: 1, name: "已支付", class: "blue" } const list = this.normalTabList.concat(this.specialTabList) return list.find((tab) => Number(tab.code) === Number(value)) },