退款 扣除库存校验

This commit is contained in:
2026-04-10 16:11:43 +08:00
parent c4b93b6a5b
commit 0e600d76f8
8 changed files with 88 additions and 54 deletions

View File

@@ -2,7 +2,6 @@ package com.czg.mq;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.czg.config.RabbitConstants;
import com.czg.order.entity.MqLog;
@@ -71,10 +70,10 @@ public class OrderMqListener {
public void orderStockRecover(String orderId) {
long startTime = DateUtil.date().getTime();
log.info("接收到订单取消恢复库存消息:{}", orderId);
MqLog mqLog = new MqLog().setQueue(RabbitConstants.Queue.ORDER_CANCEL_QUEUE).setMsg(orderId).setType("orderStockRecover").setPlat("java.order").setCreateTime(DateUtil.date().toLocalDateTime());
try {
orderInfoRpcService.orderCancelCallback(Long.valueOf(orderId));
} catch (Exception e) {
MqLog mqLog = new MqLog().setQueue(RabbitConstants.Queue.ORDER_CANCEL_QUEUE).setMsg(orderId).setType("orderStockRecover").setPlat("java.order").setCreateTime(DateUtil.date().toLocalDateTime());
log.error("订单取消恢复库存失败", e);
String errorInfo = ExceptionUtil.stacktraceToString(e);
mqLog.setErrInfo(errorInfo);
@@ -88,11 +87,10 @@ public class OrderMqListener {
public void orderStockReturn(String jsonObjStr) {
long startTime = DateUtil.date().getTime();
log.info("接收到订单退款返还库存消息:{}", jsonObjStr);
MqLog mqLog = new MqLog().setQueue(RabbitConstants.Queue.ORDER_REFUND_QUEUE).setMsg(jsonObjStr).setType("orderStockReturn").setPlat("java.order").setCreateTime(DateUtil.date().toLocalDateTime());
try {
JSONObject data = JSON.parseObject(jsonObjStr);
orderInfoRpcService.orderRefundCallback(data);
orderInfoRpcService.orderRefundCallback(JSONObject.parseObject(jsonObjStr));
} catch (Exception e) {
MqLog mqLog = new MqLog().setQueue(RabbitConstants.Queue.ORDER_REFUND_QUEUE).setMsg(jsonObjStr).setType("orderStockReturn").setPlat("java.order").setCreateTime(DateUtil.date().toLocalDateTime());
log.error("订单退款返还库存失败", e);
String errorInfo = ExceptionUtil.stacktraceToString(e);
mqLog.setErrInfo(errorInfo);

View File

@@ -54,6 +54,10 @@ public class OrderInfoRefundDTO implements Serializable {
* 是否打印退菜/退款票
*/
private boolean print;
/**
* 是否退库存
*/
private boolean refundStock;
private String refundReason;

View File

@@ -2,6 +2,7 @@ package com.czg.order.service;
import cn.hutool.core.exceptions.ValidateException;
import com.alibaba.fastjson2.JSONObject;
import com.czg.account.entity.ShopInfo;
import com.czg.exception.CzgException;
import com.czg.exception.OrderCancelException;
import com.czg.exception.OrderValidateException;
@@ -21,6 +22,7 @@ import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* 订单表 服务层。
@@ -73,4 +75,9 @@ public interface OrderInfoCustomService {
Boolean upOrderDetail(Long shopId, OrderDetailStatusDTO detailStatusDTO);
/**
* 退单库存问题
*/
void refundStock(ShopInfo shopInfo, Long orderId, Map<Long,BigDecimal> products, boolean refundStock);
}

View File

@@ -1,9 +1,9 @@
package com.czg.product.service;
import com.czg.product.vo.ProductStockVO;
import com.czg.product.vo.ProductVO;
import java.util.List;
import java.util.Map;
/**
* 商品RPC远程调用服务接口
@@ -20,24 +20,23 @@ public interface ProductRpcService {
* @param orderId 订单ID
* @param dataList 库存扣减数据
*/
void paySuccessSubtractStock(Long shopId, Long orderId, List<Map<String, Object>> dataList);
void paySuccessSubtractStock(Long shopId, Long orderId, List<ProductStockVO> dataList);
/**
* 订单取消后恢复库存
*
* @param shopId 店铺id
* @param orderId 订单ID
* @param dataList 库存恢复数据
*/
void orderCancelRecoverStock(Long shopId, Long orderId, List<Map<String, Object>> dataList);
void orderCancelRecoverStock(Long shopId, Long orderId, List<ProductStockVO> list);
/**
* 订单退菜或退款后回退库存
* @param shopId 店铺id
* @param orderId 订单ID
* @param dataList 库存恢复数据
* @param list 库存恢复数据
*/
void orderRefundReturnStock(Long shopId, Long orderId, List<Map<String, Object>> dataList);
void orderRefundReturnStock(Long shopId, Long orderId, List<ProductStockVO> list);
List<ProductVO> listAndLowPrice(Long shopId, List<Long> productIds);

View File

@@ -43,8 +43,12 @@ import com.czg.order.enums.PayEnums;
import com.czg.order.service.*;
import com.czg.order.vo.*;
import com.czg.pay.PayNotifyRespDTO;
import com.czg.product.entity.Product;
import com.czg.product.entity.ShopProdCategory;
import com.czg.product.service.ProductRpcService;
import com.czg.product.service.ProductService;
import com.czg.product.service.ShopProdCategoryService;
import com.czg.product.vo.ProductStockVO;
import com.czg.resp.CzgResult;
import com.czg.sa.StpKit;
import com.czg.service.RedisService;
@@ -107,6 +111,8 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
@DubboReference
private ProductService productService;
@DubboReference
private ShopProdCategoryService shopProdCategoryService;
@DubboReference
private ProductRpcService productRpcService;
@DubboReference
private ShopInfoService shopInfoService;
@@ -1696,13 +1702,13 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
return true;
}
List<OrderDetail> details = orderDetailService.list(QueryWrapper.create().eq(OrderDetail::getOrderId, orderId).eq(OrderDetail::getPlaceNum, placeNum));
List<Map<String, Object>> dataList = new ArrayList<>();
List<ProductStockVO> listStock = new ArrayList<>();
for (OrderDetail detail : details) {
dataList.add(Map.of("productId", detail.getProductId(), "number", NumberUtil.sub(detail.getNum(), detail.getReturnNum())));
listStock.add(new ProductStockVO(detail.getProductId(), NumberUtil.sub(detail.getNum(), detail.getReturnNum())));
}
orderDetailService.remove(new QueryWrapper().eq(OrderDetail::getOrderId, orderId).eq(OrderDetail::getPlaceNum, placeNum));
if (CollUtil.isNotEmpty(dataList)) {
productRpcService.orderCancelRecoverStock(shopId, orderId, dataList);
if (CollUtil.isNotEmpty(listStock)) {
productRpcService.orderCancelRecoverStock(shopId, orderId, listStock);
}
List<OrderDetail> list = orderDetailService.queryChain().eq(OrderDetail::getOrderId, orderId).eq(OrderDetail::getShopId, shopId).list();
if (CollUtil.isEmpty(list)) {
@@ -1828,4 +1834,48 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
rabbitPublisher.sendOrderDetailStatusMsg(shopId.toString(), "bc");
return true;
}
/**
* 退单库存问题
* 1退菜退库存 2仅退菜不退库存 3每次询问-退菜后弹窗提示1
*/
@Override
public void refundStock(ShopInfo shopInfo, Long orderId, Map<Long, BigDecimal> products, boolean refundStock) {
List<Product> list = productService.list(new QueryWrapper().select(Product::getId, Product::getCategoryId, Product::getRefundMode)
.eq(Product::getShopId, shopInfo.getId())
.in(Product::getId, products.keySet()));
if (CollUtil.isEmpty(list)) {
return;
}
List<ProductStockVO> resultProduct = new ArrayList<>();
HashMap<Long, Integer> categoryMap = new HashMap<>();
for (Product product : list) {
Integer refundMode;
if (shopInfo.getRefundMode().equals(1)) {
//分类
if (!categoryMap.containsKey(product.getCategoryId())) {
refundMode = shopProdCategoryService.getOneAs(new QueryWrapper().select(ShopProdCategory::getRefundMode)
.eq(ShopProdCategory::getShopId, shopInfo.getId()).eq(ShopProdCategory::getId, product.getCategoryId()), Integer.class);
categoryMap.put(product.getCategoryId(), refundMode);
} else {
refundMode = categoryMap.get(product.getCategoryId());
}
} else if (shopInfo.getRefundMode().equals(2)) {
//单商品
refundMode = product.getRefundMode();
} else {
log.error("退菜模式错误 店铺{},退款模式{},商品Id{}分类Id{}", shopInfo.getShopName(), shopInfo.getRefundMode(), product.getId(), product.getCategoryId());
throw new CzgException("退菜模式错误");
}
if (refundMode.equals(1)) {
resultProduct.add(new ProductStockVO(product.getId(), products.get(product.getId())));
} else if (refundMode.equals(3) && refundStock) {
resultProduct.add(new ProductStockVO(product.getId(), products.get(product.getId())));
}
}
if (CollUtil.isNotEmpty(resultProduct)) {
rabbitPublisher.sendOrderRefundMsg(JSONObject.toJSONString(Map.of("orderId", orderId, "returnProList", resultProduct)));
}
}
}

View File

@@ -13,6 +13,7 @@ import com.czg.order.entity.OrderDetail;
import com.czg.order.entity.OrderInfo;
import com.czg.order.service.OrderInfoRpcService;
import com.czg.product.service.ProductRpcService;
import com.czg.product.vo.ProductStockVO;
import com.czg.service.market.mapper.OrderInfoMapper;
import com.czg.service.order.mapper.OrderDetailMapper;
import com.mybatisflex.core.query.QueryWrapper;
@@ -95,12 +96,12 @@ public class OrderInfoRpcServiceImpl implements OrderInfoRpcService {
}
Long shopId = orderInfo.getShopId();
// 封装扣减库存数据
List<Map<String, Object>> dataList = new ArrayList<>();
List<ProductStockVO> dataList = new ArrayList<>();
for (OrderDetail orderDetail : detailList) {
Long productId = orderDetail.getProductId();
BigDecimal num = orderDetail.getNum();
BigDecimal refundNum = orderDetail.getRefundNum();
dataList.add(Map.of("productId", productId, "number", NumberUtil.sub(num, refundNum)));
dataList.add(new ProductStockVO(productId, NumberUtil.sub(num, refundNum)));
}
try {
// 调用商品服务扣减库存
@@ -127,12 +128,12 @@ public class OrderInfoRpcServiceImpl implements OrderInfoRpcService {
throw new CzgException("该订单下不存在商品");
}
// 封装扣减库存数据
List<Map<String, Object>> dataList = new ArrayList<>();
List<ProductStockVO> dataList = new ArrayList<>();
for (OrderDetail orderDetail : detailList) {
Long productId = orderDetail.getProductId();
BigDecimal num = orderDetail.getNum();
BigDecimal refundNum = orderDetail.getRefundNum();
dataList.add(Map.of("productId", productId, "number", NumberUtil.sub(num, refundNum)));
dataList.add(new ProductStockVO(productId, NumberUtil.sub(num, refundNum)));
}
try {
// 调用商品服务扣减库存
@@ -145,7 +146,6 @@ public class OrderInfoRpcServiceImpl implements OrderInfoRpcService {
@Override
public void orderRefundCallback(JSONObject data) {
log.info(">>>>>>>>>>>>>>>>>:入参:{}", data.toJSONString());
Long orderId = data.getLong("orderId");
// 订单取消后商品库存恢复,耗材恢复,流水记录
OrderInfo orderInfo = orderInfoMapper.selectOneById(orderId);
@@ -157,13 +157,7 @@ public class OrderInfoRpcServiceImpl implements OrderInfoRpcService {
if (CollUtil.isEmpty(detailList)) {
throw new CzgException("该订单下不存在商品");
}
JSONObject obj = data.getJSONObject("returnProMap");
Set<String> keys = obj.keySet();
// 封装扣减库存数据
List<Map<String, Object>> dataList = new ArrayList<>();
for (String key : keys) {
dataList.add(Map.of("productId", key, "number", obj.getBigDecimal(key)));
}
List<ProductStockVO> dataList = data.getList("returnProList", ProductStockVO.class);
try {
// 调用商品服务回退库存
productRpcService.orderRefundReturnStock(orderInfo.getShopId(), orderId, dataList);

View File

@@ -1,13 +1,11 @@
package com.czg.service.order.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.MD5;
import com.alibaba.fastjson2.JSONObject;
import com.czg.PayCst;
import com.czg.account.dto.shopuser.ShopUserMoneyEditDTO;
import com.czg.account.entity.ShopInfo;
@@ -383,7 +381,7 @@ public class OrderPayServiceImpl implements OrderPayService {
}
boolean isFirstRefund = orderInfo.getRefundAmount().compareTo(BigDecimal.ZERO) == 0;
ShopInfo shopInfo = shopInfoService.getById(orderInfo.getShopId());
Map<String, BigDecimal> returnProMap = new HashMap<>();
Map<Long,BigDecimal> productStockMap = new HashMap<>();
boolean isPay = true;
String refPayOrderNo = "REFO" + IdUtil.getSnowflakeNextId();
if (orderInfo.getStatus().equals(OrderStatusEnums.UNPAID.getCode())) {
@@ -443,7 +441,7 @@ public class OrderPayServiceImpl implements OrderPayService {
}
orderDetailService.updateById(orderDetail);
if (orderDetail.getProductId() != null && orderDetail.getProductId() > 0) {
returnProMap.put(Convert.toStr(orderDetail.getProductId()), refundDetail.getNum());
productStockMap.put(orderDetail.getProductId(), refundDetail.getNum());
}
}
long count = orderDetailService.count(QueryWrapper.create().eq(OrderDetail::getOrderId, orderInfo.getId())
@@ -461,14 +459,14 @@ public class OrderPayServiceImpl implements OrderPayService {
for (OrderDetail orderDetail : orderDetails) {
if (isPay) {
if (orderDetail.getProductId() != null && orderDetail.getProductId() > 0) {
returnProMap.put(Convert.toStr(orderDetail.getProductId()), orderDetail.getNum().subtract(orderDetail.getReturnNum()).subtract(orderDetail.getRefundNum()));
productStockMap.put(orderDetail.getProductId(), orderDetail.getNum().subtract(orderDetail.getReturnNum()).subtract(orderDetail.getRefundNum()));
}
orderDetail.setReturnAmount(orderDetail.getPayAmount());
orderDetail.setRefundNum(orderDetail.getNum().subtract(orderDetail.getReturnNum()));
orderDetail.setStatus(OrderStatusEnums.REFUND.getCode());
} else {
if (orderDetail.getProductId() != null && orderDetail.getProductId() > 0) {
returnProMap.put(Convert.toStr(orderDetail.getProductId()), orderDetail.getNum().subtract(orderDetail.getReturnNum()));
productStockMap.put(orderDetail.getProductId(), orderDetail.getNum().subtract(orderDetail.getReturnNum()));
}
orderDetail.setReturnNum(orderDetail.getNum());
orderDetail.setStatus(OrderStatusEnums.CANCELLED.getCode());
@@ -519,9 +517,8 @@ public class OrderPayServiceImpl implements OrderPayService {
, param.getRefundReason(), orderInfo.getRefundType(), orderInfo, param.getRefundDetails());
}
//退款返还库存
if (!returnProMap.isEmpty()) {
//TODO 退款 商品退不退的 校验
rabbitPublisher.sendOrderRefundMsg(JSONObject.toJSONString(Map.of("orderId", orderInfo.getId(), "returnProMap", returnProMap)));
if (!productStockMap.isEmpty()) {
orderInfoCustomService.refundStock(shopInfo, orderInfo.getId(), productStockMap, param.isRefundStock());
}
refundOrderAfter(orderInfo.getId(), orderInfo.getShopId(), orderInfo.getUserId(), orderInfo.getOrderNo(),
orderInfo.getPointsNum(), isFirstRefund, orderInfo.getStatus().equals(OrderStatusEnums.REFUND.getCode()));

View File

@@ -1,7 +1,5 @@
package com.czg.service.product.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.czg.product.enums.InOutItemEnum;
import com.czg.product.enums.InOutTypeEnum;
import com.czg.product.service.ProductRpcService;
@@ -16,7 +14,6 @@ import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import static com.czg.constant.CacheConstant.ADMIN_CLIENT_PRODUCT_LIST;
@@ -39,32 +36,20 @@ public class ProductRpcServiceImpl implements ProductRpcService {
@Override
@Transactional(rollbackFor = Exception.class)
public void paySuccessSubtractStock(Long shopId, Long orderId, List<Map<String, Object>> dataList) {
List<ProductStockVO> list = BeanUtil.copyToList(dataList, ProductStockVO.class);
if (CollUtil.isEmpty(list)) {
return;
}
public void paySuccessSubtractStock(Long shopId, Long orderId, List<ProductStockVO> list) {
productService.consStockByProduct(shopId, InOutTypeEnum.OUT, InOutItemEnum.ORDER_OUT, list, orderId, "订单消费");
}
@Override
@Transactional(rollbackFor = Exception.class)
public void orderCancelRecoverStock(Long shopId, Long orderId, List<Map<String, Object>> dataList) {
List<ProductStockVO> list = BeanUtil.copyToList(dataList, ProductStockVO.class);
if (CollUtil.isEmpty(list)) {
return;
}
public void orderCancelRecoverStock(Long shopId, Long orderId, List<ProductStockVO> list) {
productService.consStockByProduct(shopId, InOutTypeEnum.IN, InOutItemEnum.ORDER_IN, list, orderId, "订单取消/过期返还库存");
}
@Override
@Transactional(rollbackFor = Exception.class)
public void orderRefundReturnStock(Long shopId, Long orderId, List<Map<String, Object>> dataList) {
List<ProductStockVO> list = BeanUtil.copyToList(dataList, ProductStockVO.class);
if (CollUtil.isEmpty(list)) {
return;
}
public void orderRefundReturnStock(Long shopId, Long orderId, List<ProductStockVO> list) {
productService.consStockByProduct(shopId, InOutTypeEnum.IN, InOutItemEnum.ORDER_IN, list, orderId, "订单退菜/退款返还库存");
}