feat: 已实现订单基本逻辑,到完成,却订单售后

This commit is contained in:
2026-07-30 15:55:00 +08:00
parent 1cbacf987d
commit 5de4d6f9f4
15 changed files with 781 additions and 176 deletions
@@ -50,8 +50,10 @@ public class SysV4MsgController {
m.title,
m.content,
m.type,
m.sendTime,
r.isRead,
r.readTime
r.readTime,
r.createdAt
FROM
global_message m
INNER JOIN global_message_receiver r ON m.id = r.messageId
@@ -69,7 +71,7 @@ public class SysV4MsgController {
cnd.andEX("m.type", "=", type);
cnd.desc("m.sendTime");
cnd.desc("m.sendTime").desc("r.createdAt");
sql.setCondition(cnd);
String title = "审批待办";
@@ -45,4 +45,24 @@ public class GlobalMessageSendRequest {
*/
private List<String> receiverIds;
/**
* 是否系统发送
*/
private Boolean systemSend;
/**
* 发送人ID
*/
private String senderId;
/**
* 发送人名称
*/
private String senderName;
/**
* 发送人工号
*/
private String senderLoginName;
}
@@ -97,6 +97,66 @@ public class GlobalMessageSendService {
sendMessage(request);
}
/**
* 发送系统消息(不依赖当前登录token)
*/
public void sendSystemMessage(String title, String content, Integer type, List<String> receiverIds, JSONObject config) {
GlobalMessageSendRequest request = new GlobalMessageSendRequest();
request.setTitle(title);
request.setContent(content);
request.setType(type);
request.setReceiverIds(receiverIds);
request.setConfig(config);
request.setSystemSend(true);
request.setSenderId("system");
request.setSenderName("系统");
request.setSenderLoginName("system");
sendMessage(request);
}
/**
* 发送本地系统消息(不依赖当前登录token)
*/
public void sendLocalSystemMessage(String title, String content, Integer type, List<String> receiverIds, JSONObject config) {
GlobalMessageSendRequest request = new GlobalMessageSendRequest();
request.setTitle(title);
request.setContent(content);
request.setType(type);
request.setReceiverIds(receiverIds);
request.setConfig(config);
request.setSystemSend(true);
request.setSenderId("system");
request.setSenderName("系统");
request.setSenderLoginName("system");
sendLocalMessage(request);
}
/**
* 发送本地消息(使用请求对象)
*/
public void sendLocalMessage(GlobalMessageSendRequest request) {
try {
log.info("开始发送本地全局消息,标题:{},接收人数量:{}", request.getTitle(),
request.getReceiverIds() != null ? request.getReceiverIds().size() : 0);
validateRequest(request);
applySystemSenderConfig(request);
if (localStrategy == null) {
throw new RuntimeException("本地消息发送策略未找到,系统无法正常工作");
}
localStrategy.send(request.getTitle(), request.getContent(), request.getType(),
request.getReceiverIds(), request.getConfig());
log.info("本地全局消息发送完成,标题:{}", request.getTitle());
} catch (Exception e) {
log.error("本地全局消息发送失败,标题:{},错误:{}", request.getTitle(), e.getMessage(), e);
throw new RuntimeException("本地消息发送失败:" + e.getMessage(), e);
}
}
/**
* 发送消息(使用请求对象)
*/
@@ -107,6 +167,7 @@ public class GlobalMessageSendService {
// 参数校验
validateRequest(request);
applySystemSenderConfig(request);
// 获取启用的策略
List<GlobalMessageSendStrategy> enabledStrategies = getEnabledStrategies();
@@ -161,6 +222,21 @@ public class GlobalMessageSendService {
}
}
private void applySystemSenderConfig(GlobalMessageSendRequest request) {
if (!Boolean.TRUE.equals(request.getSystemSend())) {
return;
}
JSONObject config = request.getConfig();
if (config == null) {
config = new JSONObject();
request.setConfig(config);
}
config.set("systemSend", true);
config.set("senderId", request.getSenderId());
config.set("senderName", request.getSenderName());
config.set("senderLoginName", request.getSenderLoginName());
}
/**
* 参数校验
*/
@@ -13,6 +13,7 @@ import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.Date;
import java.util.List;
/**
@@ -56,9 +57,15 @@ public class LocalMessageSendStrategy implements GlobalMessageSendStrategy {
globalMessage.setTitle(title);
globalMessage.setContent(content);
globalMessage.setType(type);
globalMessage.setSenderId(SecurityUtil.getUserId());
globalMessage.setSenderName(SecurityUtil.getUserUsername());
globalMessage.setSenderLoginName(SecurityUtil.getUserLoginname());
if (config != null && Boolean.TRUE.equals(config.getBool("systemSend"))) {
globalMessage.setSenderId(config.getStr("senderId"));
globalMessage.setSenderName(config.getStr("senderName"));
globalMessage.setSenderLoginName(config.getStr("senderLoginName"));
} else {
globalMessage.setSenderId(SecurityUtil.getUserId());
globalMessage.setSenderName(SecurityUtil.getUserUsername());
globalMessage.setSenderLoginName(SecurityUtil.getUserLoginname());
}
globalMessage.setStatus(1); // 草稿状态
globalMessage.setSendSuccess(false);
dao.insert(globalMessage);
@@ -77,6 +84,7 @@ public class LocalMessageSendStrategy implements GlobalMessageSendStrategy {
// 更新消息状态为已发送
globalMessage.setStatus(2);
globalMessage.setSendTime(new Date());
globalMessage.setSendSuccess(true);
globalMessage.setSendResult("本地消息发送成功");
dao.update(globalMessage);
@@ -10,5 +10,9 @@ public class MallOrderEventDTO {
private String orderId;
private String mainOrderId;
private String userId;
private Integer deliveryStatus;
private String deliveryTime;
private String logisticsOrderId;
private String crrgBsnNm;
private Long time;
}
@@ -0,0 +1,29 @@
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 org.nutz.ioc.loader.annotation.IocBean;
/**
* MallShipOrderEventHandler
* orderType=99: 供应商已发货 / 更新发货状态。
*/
@IocBean
public class MallShipOrderEventHandler implements MallOrderEventHandler {
@Inject
private MallInboundEventService inboundEventService;
@Override
public int getType() {
return 99;
}
@Override
public MallInboundResult handle(MallOrderEventDTO dto) {
return inboundEventService.updateDeliveryStatus(dto);
}
}
@@ -0,0 +1,32 @@
package com.budwk.app.zhgh.pointsmall.mallbridge.exception;
/**
* MallBridgeError
* 说明: 商城桥接的统一错误码枚举, 包含HTTP风格的resultCode与默认提示文案。
* 用于抛出MallBridgeException时传递标准化的错误信息。
*/
public enum MallBridgeError {
ERROR(-1, "校验失败"),
AUTH_FAIL(401, "鉴权失败"),
PARAM_ERROR(400, "参数错误"),
ORDER_EXISTS(409, "订单已存在"),
ORDER_NOT_EXISTS(410, "订单不存在"),
NOT_FOUND(404, "资源不存在"),
SERVER_ERROR(500, "服务器内部错误");
private final int code;
private final String message;
MallBridgeError(int code, String message) {
this.code = code;
this.message = message;
}
public int code() {
return code;
}
public String message() {
return message;
}
}
@@ -14,6 +14,7 @@ import com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers.MallCancelSubOrde
import com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers.MallConfirmReceiptEventHandler;
import com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers.MallExchangeSubEventHandler;
import com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers.MallInvoiceInfoEventHandler;
import com.budwk.app.zhgh.pointsmall.mallbridge.event.handlers.MallShipOrderEventHandler;
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;
@@ -69,6 +70,8 @@ public class MallInboundEventService extends AbstractMallBridgeSupport {
private MallAbolishSubOrderEventHandler mallAbolishSubOrderEventHandler;
@Inject
private MallInvoiceInfoEventHandler mallInvoiceInfoEventHandler;
@Inject
private MallShipOrderEventHandler mallShipOrderEventHandler;
public MallInboundEventService(Dao dao) {
super(dao);
@@ -105,6 +108,7 @@ public class MallInboundEventService extends AbstractMallBridgeSupport {
register(handlerRegistry, mallCancelPayedMainOrderEventHandler);
register(handlerRegistry, mallAbolishSubOrderEventHandler);
register(handlerRegistry, mallInvoiceInfoEventHandler);
register(handlerRegistry, mallShipOrderEventHandler);
return handlerRegistry;
}
@@ -346,6 +350,66 @@ public class MallInboundEventService extends AbstractMallBridgeSupport {
}
}
public MallInboundResult updateDeliveryStatus(MallOrderEventDTO dto) {
if (StrUtil.isBlank(dto.getSupplierId()) || StrUtil.isBlank(dto.getOrderId())) {
return MallInboundResult.fail(400, "供应商或订单不能为空");
}
List<String> 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<PointsMallOrderSub> 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)) {
@@ -1,10 +1,20 @@
package com.budwk.app.zhgh.pointsmall.mallbridge.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageSendService;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.MallPurchaseResultDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.OrderProInfoDTO;
import com.budwk.app.zhgh.pointsmall.mallbridge.exception.MallBridgeError;
import com.budwk.app.zhgh.pointsmall.mallbridge.model.MallInboundResult;
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.order.models.PointsMallOrderMainHistory;
import com.budwk.app.zhgh.pointsmall.points.service.PointsMallPointsLockService;
import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
@@ -14,10 +24,14 @@ import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.trans.Atom;
import org.nutz.trans.Trans;
import java.util.Date;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* 商城桥接购买结果通知服务。
* 9.2.14 支付结果通知接口
*/
@Slf4j
@IocBean(args = {"refer:dao"})
@@ -25,45 +39,105 @@ public class MallInboundPurchaseResultService {
private final Dao dao;
@Inject
private MallInboundConfirmPurchaseService mallInboundConfirmPurchaseService;
@Inject
private PointsMallPointsLockService pointsMallPointsLockService;
@Inject
private GlobalMessageSendService globalMessageSendService;
public MallInboundPurchaseResultService(Dao dao) {
this.dao = dao;
}
/**
* 接收购买结果。支付成功只同步现金支付状态;支付失败取消主单并释放锁定积分。
*/
public MallInboundResult purchaseResult(MallPurchaseResultDTO dto) {
log.info("积分商城购买结果通知,supplierId={}orderId={}purchaseResult={}", dto.getSupplierId(), dto.getOrderId(), dto.getPurchaseResult());
PointsMallOrderMain main = dao.fetch(PointsMallOrderMain.class, Cnd.where("supplierId", "=", dto.getSupplierId())
log.info("9.2.14 支付结果通知接口 调用开始:{}", dto);
// 查询订单是否存在
PointsMallOrderMain orderMain = dao.fetch(PointsMallOrderMain.class, Cnd.where("supplierId", "=", dto.getSupplierId())
.and("orderId", "=", dto.getOrderId()).and("delFlag", "=", false));
if (main == null) {
return MallInboundResult.fail(404, "订单不存在");
if (null == orderMain) {
return MallInboundResult.fail(MallBridgeError.ORDER_NOT_EXISTS.code(), "订单不存在");
}
if (Boolean.TRUE.equals(dto.getPurchaseResult())) {
Integer cashPaid = main.getWPayOrAPay() != null && main.getWPayOrAPay().signum() > 0 ? 2 : 0;
return mallInboundConfirmPurchaseService.updateMainOrderPay(dto.getSupplierId(), dto.getOrderId(), cashPaid,
dto.getTotalPrice(), dto.getWPayOrAPay(), dto.getPointsPrice());
// 支付结果
if (dto.getPurchaseResult()) {
log.info("9.2.14 支付结果通知接口,支付成功");
if (BigDecimal.ZERO.compareTo(orderMain.getWPayOrAPay()) != 0) {
dao.update(PointsMallOrderMain.class, Chain.make("cashPaid", 2), Cnd.where("id", "=", orderMain.getId()));
}
try {
// callP消息推送
pushMessageToCallP(orderMain);
} catch (Exception e) {
log.error("9.2.14 支付结果通知接口,callP消息推送:{},{}", dto, e.getMessage());
}
return MallInboundResult.success();
}
if (Objects.equals(orderMain.getOrderState(), MallOrderStatusEnum.CANCELLED.getCode())) {
return MallInboundResult.fail(MallBridgeError.PARAM_ERROR.code(), "订单已取消,请不要重复推送");
}
log.info("9.2.14 支付结果通知接口,支付失败,1.修改主订单支付状态为已取消 2.释放积分");
// 支付失败,修改主订单支付状态为已取消
Trans.exec((Atom) () -> {
dao.update(PointsMallOrderMain.class,
Chain.make("orderState", 3)
.add("cashPaid", main.getWPayOrAPay() != null && main.getWPayOrAPay().signum() > 0 ? 3 : main.getCashPaid())
.add("totalPrice", dto.getTotalPrice())
.add("pointsPrice", dto.getPointsPrice())
.add("wPayOrAPay", dto.getWPayOrAPay())
.add("orderUpdateTime", new Date()),
Cnd.where("id", "=", main.getId()));
dao.update(PointsMallOrderSub.class,
Chain.make("orderState", 3).add("orderUpdateTime", new Date()),
Cnd.where("supplierId", "=", dto.getSupplierId()).and("mainOrderId", "=", dto.getOrderId()).and("delFlag", "=", false));
pointsMallPointsLockService.releaseMain(main.getUserId(), main.getSupplierId(), main.getOrderId());
Chain mainOrderUpdate = Chain.make("totalPrice", dto.getTotalPrice())
.add("pointsPrice", dto.getPointsPrice())
.add("wPayOrAPay", dto.getWPayOrAPay())
.add("orderState", MallOrderStatusEnum.CANCELLED.getCode());
if (BigDecimal.ZERO.compareTo(orderMain.getWPayOrAPay()) != 0) {
// 取消现金支付
mainOrderUpdate.add("cashPaid", 3);
}
dao.update(PointsMallOrderMain.class, mainOrderUpdate, Cnd.where("id", "=", orderMain.getId()));
// 支付失败,释放积分
pointsMallPointsLockService.releaseMain(orderMain.getUserId(), orderMain.getSupplierId(), orderMain.getOrderId());
// 添加历史记录
PointsMallOrderMainHistory orderMainHistory = BeanUtil.copyProperties(orderMain, PointsMallOrderMainHistory.class);
// id 主键
orderMainHistory.setId(null);
log.info("9.2.14 支付结果通知接口,保存订单主表流水记录");
dao.insert(orderMainHistory);
});
log.info("积分商城购买失败处理完成,supplierId={}orderId={}", dto.getSupplierId(), dto.getOrderId());
log.info("9.2.14 支付结果通知接口 调用结束");
return MallInboundResult.success();
}
/**
* callP消息推送
*
* @param mainOrder 订单信息
*/
public void pushMessageToCallP(PointsMallOrderMain mainOrder) {
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();
StringBuilder sb = new StringBuilder();
sb.append("商品列表:<ol>");
List<OrderProInfoDTO> productList = StrUtil.isBlank(mainOrder.getExtJson())
? Collections.emptyList()
: JSONUtil.toList(mainOrder.getExtJson(), OrderProInfoDTO.class);
productList.forEach(product ->
sb.append("<li>商品名称:").append(product.getName()).append(",商品价格:").append(formatMoney(product.getPrice())).append("元</li>")
);
sb.append("</ol>");
String userName = user == null || StrUtil.isBlank(user.getUsername()) ? mainOrder.getUserId() : user.getUsername();
String content = "尊敬的" + userName + ",您的积分商城订单已支付成功。"
+ "<br>"
+ "订单时间:" + DateUtil.formatDateTime(mainOrder.getOrderCreateTime())
+ "<br>"
+ "消费积分:" + mainOrder.getPointsPrice()
+ "<br>"
+ "供应商:" + supplierName
+ "<br>"
+ "订单总金额:" + mainOrder.getTotalPrice()
+ "<br>"
+ "现金金额:" + mainOrder.getWPayOrAPay()
+ "<br>"
+ sb;
globalMessageSendService.sendLocalSystemMessage("积分商城支付成功", content, 1, Collections.singletonList(mainOrder.getUserId()), null);
}
private String formatMoney(BigDecimal value) {
return (value == null ? BigDecimal.ZERO : value).setScale(2, RoundingMode.HALF_UP).toPlainString();
}
}
@@ -0,0 +1,136 @@
package com.budwk.app.zhgh.pointsmall.order.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 商城主订单历史记录 当主单数据变化时 落库到历史表
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("points_mall_order_main_history")
@Comment("积分商城主订单历史记录")
public class PointsMallOrderMainHistory extends BaseModel implements Serializable {
@Name
@Column
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("主订单ID")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String orderId;
@Column
@Comment("供应商编码")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String supplierId;
@Column
@Comment("订单编号")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String orderNumberId;
@Column
@Comment("用户ID")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String userId;
@Column
@Comment("应用名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String aplName;
@Column
@Comment("订单创建时间")
private Date orderCreateTime;
@Column
@Comment("订单更新时间")
private Date orderUpdateTime;
@Column
@Comment("发货时间")
private Date deliveryTime;
@Column
@Comment("确认收货时间")
private Date orderFinishTime;
@Column
@Comment("完成时间")
private Date orderCompleteTime;
@Column
@Comment("发货状态")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String deliveryStatus;
@Column
@Comment("运费积分")
@ColDefine(type = ColType.FLOAT, width = 18, precision = 2)
private BigDecimal freightCost;
@Column
@Comment("退款积分")
@ColDefine(type = ColType.FLOAT, width = 18, precision = 2)
private BigDecimal refund;
@Column
@Comment("物流单号")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String logisticsOrderId;
@Column
@Comment("物流承运商")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String crrgBsnNm;
@Column
@Comment("订单状态")
private Integer orderState;
@Column
@Comment("订单总金额")
@ColDefine(type = ColType.FLOAT, width = 18, precision = 2)
private BigDecimal totalPrice;
@Column
@Comment("现金金额")
@ColDefine(type = ColType.FLOAT, width = 18, precision = 2)
private BigDecimal wPayOrAPay;
@Column
@Comment("消费积分")
@ColDefine(type = ColType.FLOAT, width = 18, precision = 2)
private BigDecimal pointsPrice;
@Column
@Comment("发票代码")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String invoiceCode;
@Column
@Comment("商品扩展信息JSON")
@ColDefine(type = ColType.TEXT)
private String extJson;
@Column
@Comment("现金支付状态")
private Integer cashPaid;
}
@@ -11,6 +11,7 @@ import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier;
import com.budwk.app.zhgh.pointsmall.order.param.PointsMallOrderPageParam;
import com.budwk.app.zhgh.pointsmall.order.vo.PointsMallMobileOrderVO;
import org.nutz.dao.entity.Record;
import org.nutz.lang.util.NutMap;
import java.util.List;
@@ -28,9 +29,9 @@ public interface PointsMallOrderService extends BaseService<PointsMallOrderMain>
List<Record> listExchange(String mainOrderId);
List<Record> listSubProducts(String orderId);
List<NutMap> listSubProducts(String orderId);
List<Record> listExchangeProducts(String orderId);
List<NutMap> listExchangeProducts(String orderId);
boolean updateSub(PointsMallOrderSub orderSub);
@@ -6,6 +6,7 @@ import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.pointsmall.mallbridge.dto.OrderProInfoDTO;
import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderExchange;
import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderMain;
import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderSub;
@@ -28,6 +29,7 @@ import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import org.nutz.trans.Atom;
import org.nutz.trans.Trans;
@@ -198,7 +200,26 @@ public class PointsMallOrderServiceImpl extends BaseServiceImpl<PointsMallOrderM
}
@Override
public List<Record> listSubProducts(String orderId) {
public List<NutMap> listSubProducts(String orderId) {
PointsMallOrderSub orderSub = dao().fetch(PointsMallOrderSub.class, Cnd.where("orderId", "=", orderId).and("delFlag", "=", false));
List<NutMap> extProducts = parseProductExtJson(orderId, orderSub == null ? null : orderSub.getExtJson());
if (!extProducts.isEmpty()) {
return extProducts;
}
return listProductTable(orderId);
}
@Override
public List<NutMap> listExchangeProducts(String orderId) {
PointsMallOrderExchange orderExchange = dao().fetch(PointsMallOrderExchange.class, Cnd.where("orderId", "=", orderId).and("delFlag", "=", false));
List<NutMap> extProducts = parseProductExtJson(orderId, orderExchange == null ? null : orderExchange.getExtJson());
if (!extProducts.isEmpty()) {
return extProducts;
}
return listProductTable(orderId);
}
private List<NutMap> listProductTable(String orderId) {
Cnd cnd = Cnd.where("delFlag", "=", false);
if (StrUtil.isNotBlank(orderId)) {
cnd.and("orderSubId", "=", orderId);
@@ -206,12 +227,43 @@ public class PointsMallOrderServiceImpl extends BaseServiceImpl<PointsMallOrderM
cnd.asc("createdAt");
Sql sql = Sqls.create("select * from points_mall_order_sub_product $condition");
sql.setCondition(cnd);
return list(sql);
return list(sql).stream().map(this::toProductRecord).collect(Collectors.toList());
}
@Override
public List<Record> listExchangeProducts(String orderId) {
return listSubProducts(orderId);
private List<NutMap> parseProductExtJson(String orderId, String extJson) {
if (StrUtil.isBlank(extJson) || "null".equalsIgnoreCase(extJson)) {
return Collections.emptyList();
}
try {
return JSONUtil.toList(extJson, OrderProInfoDTO.class).stream().map(product -> {
return NutMap.NEW()
.addv("orderId", orderId)
.addv("sku", product.getSku())
.addv("number", product.getNumber())
.addv("price", product.getPrice())
.addv("name", product.getName())
.addv("taxRate", product.getTaxRate())
.addv("picInfo", product.getPicInfo())
.addv("fileId", product.getFileId())
.addv("filePath", product.getFilePath());
}).collect(Collectors.toList());
} catch (Exception e) {
log.warn("解析订单商品扩展信息失败,orderId={}extJson={}", orderId, extJson, e);
return Collections.emptyList();
}
}
private NutMap toProductRecord(Record item) {
return NutMap.NEW()
.addv("orderId", item.getString("orderSubId"))
.addv("sku", item.getString("sku"))
.addv("number", item.get("number"))
.addv("price", item.get("price"))
.addv("name", item.getString("name"))
.addv("taxRate", item.get("taxRate"))
.addv("picInfo", item.getString("picInfo"))
.addv("fileId", item.getString("fileId"))
.addv("filePath", item.getString("filePath"));
}
@Override
+15 -1
View File
@@ -130,6 +130,20 @@ layout("/layouts/v4/baseLayout.html"){
color: #303133;
}
.detail-content {
white-space: pre-line;
}
.detail-content ol {
list-style-type: decimal;
padding-left: 22px;
margin: 6px 0;
}
.detail-content li {
display: list-item;
}
.pagination-container {
margin-top: 20px;
display: flex;
@@ -297,7 +311,7 @@ layout("/layouts/v4/baseLayout.html"){
<span>{{formatTime(currentMessage.createdAt)}}</span>
</div>
</div>
<div style="line-height: 1.8; font-size: 15px;" v-html="currentMessage?.globalMessage?.content"></div>
<div class="detail-content" style="line-height: 1.8; font-size: 15px;" v-html="currentMessage?.globalMessage?.content"></div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="detailVisible = false">关闭</el-button>
@@ -68,81 +68,222 @@ layout("/layouts/platform.html"){
></el-pagination>
</el-card>
<el-dialog :close-on-click-modal="false" :title="'子订单详情 - ' + currentMainOrderId" :visible.sync="subDialogVisible" top="5vh" width="88%">
<el-table :data="subOrderList" border size="small" style="width: 100%" v-loading="subLoading">
<el-table-column align="center" label="子订单号" min-width="170" prop="orderId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="订单编码" min-width="150" prop="orderNumberId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="供应商" min-width="130" prop="supplierName" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="工号" min-width="110" prop="userAccount"></el-table-column>
<el-table-column align="center" label="创建时间" min-width="160" prop="orderCreateTime"></el-table-column>
<el-table-column align="center" label="订单状态" min-width="100">
<template slot-scope="{row}">
<el-tag :type="orderStatusType(row.orderState)" size="mini">{{ orderStateName(row.orderState) }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="消费积分" min-width="100" prop="pointsPrice"></el-table-column>
<el-table-column align="center" label="物流单号" min-width="140" prop="logisticsOrderId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="发票代码" min-width="120" prop="invoiceCode" show-overflow-tooltip></el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="180">
<template slot-scope="{row}">
<el-button @click="openProducts(row, false)" size="mini">商品</el-button>
<el-button @click="openExchange(row)" size="mini" type="warning">售后</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="subPageForm.pageNumber"
:page-size="subPageForm.pageSize"
:total="subPageForm.totalCount"
@current-change="subPageChange"
layout="total, prev, pager, next"
style="margin-top: 16px; text-align: right"
></el-pagination>
<span slot="footer">
<el-button @click="subDialogVisible = false">关闭</el-button>
</span>
</el-dialog>
<el-dialog :close-on-click-modal="false" :title="productDialogTitle" :visible.sync="productDialogVisible" width="72%">
<el-table :data="productList" border size="small" v-loading="productLoading">
<el-table-column align="center" label="商品名称" min-width="220" prop="name" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="SKU" min-width="140" prop="sku" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="数量" width="90" prop="number"></el-table-column>
<el-table-column align="center" label="单价" width="110" prop="price"></el-table-column>
<el-table-column align="center" label="税率" width="110" prop="taxRate"></el-table-column>
<el-table-column align="center" label="图片信息" min-width="180" prop="picInfo" show-overflow-tooltip></el-table-column>
</el-table>
<span slot="footer">
<el-button @click="productDialogVisible = false">关闭</el-button>
</span>
</el-dialog>
<el-dialog :close-on-click-modal="false" :title="'售后单 - ' + currentSubOrderId" :visible.sync="exchangeDialogVisible" width="82%">
<el-table :data="exchangeList" border size="small" v-loading="exchangeLoading">
<el-table-column align="center" label="售后订单号" min-width="170" prop="orderId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="供应商" min-width="130" prop="supplierName" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="工号" min-width="110" prop="userAccount"></el-table-column>
<el-table-column align="center" label="创建时间" min-width="160" prop="orderCreateTime"></el-table-column>
<el-table-column align="center" label="状态" min-width="100">
<template slot-scope="{row}">
<el-tag :type="orderStatusType(row.orderState)" size="mini">{{ orderStateName(row.orderState) }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="退款" width="100" prop="refund"></el-table-column>
<el-table-column align="center" label="消费积分" width="110" prop="pointsPrice"></el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="100">
<template slot-scope="{row}">
<el-button @click="openProducts(row, true)" size="mini">商品</el-button>
</template>
</el-table-column>
</el-table>
<span slot="footer">
<el-button @click="exchangeDialogVisible = false">关闭</el-button>
</span>
</el-dialog>
<order-sub-list-dialog
:order-status-type="orderStatusType"
:order-state-name="orderStateName"
:visible.sync="subDialogVisible"
:main-order-id="currentMainOrderId"
></order-sub-list-dialog>
</div>
<script nonce="${cspNonce!}">
Vue.component("order-detail-dialog", {
props: {
visible: Boolean,
orderId: String
},
data() {
return {
loading: false,
productList: []
}
},
watch: {
visible(val) {
if (val) {
this.loadProducts()
}
},
orderId() {
if (this.visible) {
this.loadProducts()
}
}
},
methods: {
close() {
this.$emit("update:visible", false)
},
async loadProducts() {
if (!this.orderId) {
this.productList = []
return
}
this.loading = true
const resp = await this.$axios.post(loc() + "/productList", { orderId: this.orderId })
this.loading = false
if (resp.code === 0) {
this.productList = (resp.data || []).map((item) => Object.assign({}, item, {
orderId: this.orderId
}))
} else if (this.$message) {
this.$message.warning(resp.msg)
}
}
},
template: `
<el-dialog :close-on-click-modal="false" title="子订单商品明细" :visible="visible" @update:visible="$emit('update:visible', $event)" width="72%">
<el-table :data="productList" border size="small" v-loading="loading">
<el-table-column align="center" label="订单号" min-width="180" prop="orderId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="SKU" min-width="140" prop="sku" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="商品名称" min-width="220" prop="name" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="商品单价" width="110" prop="price"></el-table-column>
<el-table-column align="center" label="商品图片" width="120">
<template slot-scope="{row}">
<el-image
v-if="row.fileId"
:src="row.fileId"
style="width: 60px; height: 60px"
fit="contain"
preview-teleported
></el-image>
</template>
</el-table-column>
<el-table-column align="center" label="商品数量" width="110" prop="number"></el-table-column>
<el-table-column align="center" label="税率" width="110" prop="taxRate"></el-table-column>
</el-table>
<span slot="footer">
<el-button @click="close">关闭</el-button>
</span>
</el-dialog>
`
})
Vue.component("order-sub-list-dialog", {
props: {
visible: Boolean,
mainOrderId: String,
orderStateName: Function,
orderStatusType: Function
},
data() {
return {
loading: false,
subOrderList: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0
},
detailVisible: false,
detailOrderId: null
}
},
watch: {
visible(val) {
if (val) {
this.pageForm.pageNumber = 1
this.pageData()
}
},
mainOrderId() {
if (this.visible) {
this.pageForm.pageNumber = 1
this.pageData()
}
}
},
methods: {
close() {
this.$emit("update:visible", false)
},
async pageData() {
if (!this.mainOrderId) {
this.subOrderList = []
return
}
this.loading = true
const resp = await this.$axios.post(loc() + "/subPageData", {
pageNumber: this.pageForm.pageNumber,
pageSize: this.pageForm.pageSize,
mainOrderId: this.mainOrderId
})
this.loading = false
if (resp.code === 0) {
this.subOrderList = resp.data.list || []
this.pageForm.totalCount = resp.data.totalCount || 0
} else if (this.$message) {
this.$message.warning(resp.msg)
}
},
pageChange(val) {
this.pageForm.pageNumber = val
this.pageData()
},
deliveryStatusName(value) {
const map = {
0: "未发货",
1: "已发货"
}
return map[value] || "-"
},
deliveryStatusType(value) {
const map = {
0: "warning",
1: "success"
}
return map[value] || "info"
},
openDetails(row) {
this.detailOrderId = row.orderId
this.detailVisible = true
}
},
template: `
<div>
<el-dialog :close-on-click-modal="false" :title="'子订单详情 - ' + (mainOrderId || '')" :visible="visible" @update:visible="$emit('update:visible', $event)" top="5vh" width="88%">
<el-table :data="subOrderList" border size="small" style="width: 100%" v-loading="loading">
<el-table-column align="center" label="子订单号" min-width="170" prop="orderId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="订单编码" min-width="150" prop="orderNumberId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="供应商名称" min-width="150" prop="supplierName" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="应用名称" min-width="120" prop="aplName" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="创建时间" min-width="160" prop="orderCreateTime"></el-table-column>
<el-table-column align="center" label="订单状态" min-width="110">
<template slot-scope="{row}">
<el-tag :type="orderStatusType(row.orderState)" size="mini">{{ orderStateName(row.orderState) }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="订单总金额" min-width="120" prop="totalPrice"></el-table-column>
<el-table-column align="center" label="消费积分" min-width="110" prop="pointsPrice"></el-table-column>
<el-table-column align="center" label="供应商ID" min-width="120" prop="supplierId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="工号" min-width="120" prop="userAccount" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="更新时间" min-width="160" prop="orderUpdateTime"></el-table-column>
<el-table-column align="center" label="发货时间" min-width="160" prop="deliveryTime"></el-table-column>
<el-table-column align="center" label="确认收货时间" min-width="160" prop="orderFinishTime"></el-table-column>
<el-table-column align="center" label="完成时间" min-width="160" prop="orderCompleteTime"></el-table-column>
<el-table-column align="center" label="发货状态" min-width="100">
<template slot-scope="{row}">
<el-tag :type="deliveryStatusType(row.deliveryStatus)" size="mini">{{ deliveryStatusName(row.deliveryStatus) }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="运费金额" min-width="110" prop="freightCost"></el-table-column>
<el-table-column align="center" label="积分退回" min-width="110" prop="refund"></el-table-column>
<el-table-column align="center" label="物流单号" min-width="140" prop="logisticsOrderId" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="物流承运商" min-width="140" prop="crrgBsnNm" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="发票代码" min-width="120" prop="invoiceCode" show-overflow-tooltip></el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="90">
<template slot-scope="{row}">
<el-button @click="openDetails(row)" size="mini">详情</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="pageForm.pageNumber"
:page-size="pageForm.pageSize"
:total="pageForm.totalCount"
@current-change="pageChange"
layout="total, prev, pager, next"
style="margin-top: 16px; text-align: right"
></el-pagination>
<span slot="footer">
<el-button @click="close">关闭</el-button>
</span>
</el-dialog>
<order-detail-dialog :visible.sync="detailVisible" :order-id="detailOrderId"></order-detail-dialog>
</div>
`
})
new Vue({
el: "#app",
mixins: [initTableMixins],
@@ -162,22 +303,7 @@ layout("/layouts/platform.html"){
logisticsOrderId: null
},
subDialogVisible: false,
subLoading: false,
currentMainOrderId: null,
subOrderList: [],
subPageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0
},
productDialogVisible: false,
productDialogTitle: "商品明细",
productLoading: false,
productList: [],
exchangeDialogVisible: false,
exchangeLoading: false,
currentSubOrderId: null,
exchangeList: [],
orderStateOptions: [
{ value: "0", label: "待付款" },
{ value: "1", label: "已支付" },
@@ -227,52 +353,7 @@ layout("/layouts/platform.html"){
},
openSubOrders(row) {
this.currentMainOrderId = row.orderId
this.subPageForm.pageNumber = 1
this.subDialogVisible = true
this.subPageData()
},
async subPageData() {
this.subLoading = true
const resp = await this.$axios.post(loc() + "/subPageData", {
pageNumber: this.subPageForm.pageNumber,
pageSize: this.subPageForm.pageSize,
mainOrderId: this.currentMainOrderId
})
this.subLoading = false
if (resp.code === 0) {
this.subOrderList = resp.data.list || []
this.subPageForm.totalCount = resp.data.totalCount || 0
} else {
this.notifyWarning(resp.msg)
}
},
subPageChange(val) {
this.subPageForm.pageNumber = val
this.subPageData()
},
async openProducts(row, exchange) {
this.productDialogTitle = (exchange ? "售后单商品明细 - " : "子订单商品明细 - ") + row.orderId
this.productDialogVisible = true
this.productLoading = true
const resp = await this.$axios.post(loc() + (exchange ? "/exchangeProductList" : "/productList"), { orderId: row.orderId })
this.productLoading = false
if (resp.code === 0) {
this.productList = resp.data || []
} else {
this.notifyWarning(resp.msg)
}
},
async openExchange(row) {
this.currentSubOrderId = row.orderId
this.exchangeDialogVisible = true
this.exchangeLoading = true
const resp = await this.$axios.post(loc() + "/exchangeList", { mainOrderId: row.orderId })
this.exchangeLoading = false
if (resp.code === 0) {
this.exchangeList = resp.data || []
} else {
this.notifyWarning(resp.msg)
}
},
orderStateName(value) {
const item = this.orderStateOptions.find((v) => String(v.value) === String(value))
@@ -88,7 +88,7 @@ const msg= {
未读
</van-tag>
</div>
<div class="message-time">{{formatTime(message.createdAt)}}</div>
<div class="message-time">{{formatTime(message.sendTime || message.createdAt)}}</div>
</div>
<div class="message-content">{{getContentPreview(message.content)}}</div>
<div class="message-arrow">
@@ -122,7 +122,7 @@ const msg= {
<van-tag :type="currentMessage?.globalMessage?.type === 1 ? 'danger' : 'primary'">
{{getMessageTypeName(currentMessage?.globalMessage?.type)}}
</van-tag>
<span class="detail-time">{{formatTime(currentMessage.createdAt)}}</span>
<span class="detail-time">{{formatTime((currentMessage.globalMessage && currentMessage.globalMessage.sendTime) || currentMessage.createdAt)}}</span>
</div>
</div>
<div class="detail-content" v-html="currentMessage?.globalMessage?.content"></div>
@@ -315,6 +315,7 @@ const msg= {
// 格式化时间
formatTime(timestamp) {
if (!timestamp) return ''
return this.$moment(timestamp).format('MM-DD HH:mm')
}
},
@@ -442,6 +443,7 @@ const msg= {
line-height: 1.8;
font-size: 15px;
color: #323233;
white-space: pre-line;
}
/deep/ .detail-content img {
@@ -455,6 +457,16 @@ const msg= {
margin: 12px 0;
}
/deep/ .detail-content ol {
list-style-type: decimal;
padding-left: 22px;
margin: 6px 0;
}
/deep/ .detail-content li {
display: list-item;
}
/* 标签页样式优化 */
/deep/ .van-tabs__nav {
background-color: #fff;