订单下单

This commit is contained in:
2025-02-21 18:37:58 +08:00
parent 16ef1edc6b
commit 03217ee2ab
21 changed files with 466 additions and 228 deletions

View File

@@ -15,5 +15,11 @@ import java.util.List;
public interface CashierCartMapper extends BaseMapper<CashierCart> {
List<OrderDetail> getCartByTableCode(String tableCode);
/**
*
* @param tableCode 桌码
* @param isUseVip 是否使用会员价
* @param placeNum 第几次下单
*/
List<OrderDetail> getCartByTableCode(String tableCode,Integer isUseVip,Integer placeNum);
}

View File

@@ -2,6 +2,9 @@ package com.czg.service.order.mapper;
import com.mybatisflex.core.BaseMapper;
import com.czg.order.entity.OrderDetail;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 订单详情 映射层。
@@ -10,5 +13,5 @@ import com.czg.order.entity.OrderDetail;
* @since 2025-02-13
*/
public interface OrderDetailMapper extends BaseMapper<OrderDetail> {
void createOrderDetails(@Param("orderId") Long orderId,@Param("orderDetails") List<OrderDetail> orderDetails);
}

View File

@@ -16,10 +16,10 @@ import java.util.List;
* @since 2025-02-20
*/
@Service
public class CashierCartServiceImpl extends ServiceImpl<CashierCartMapper, CashierCart> implements CashierCartService{
public class CashierCartServiceImpl extends ServiceImpl<CashierCartMapper, CashierCart> implements CashierCartService {
@Override
public List<OrderDetail> getCartByTableCode(String tableCode) {
return getMapper().getCartByTableCode(tableCode);
public List<OrderDetail> getCartByTableCode(String tableCode, Integer isUseVip, Integer placeNum) {
return getMapper().getCartByTableCode(tableCode, isUseVip, placeNum);
}
}

View File

@@ -6,6 +6,8 @@ import com.czg.order.service.OrderDetailService;
import com.czg.service.order.mapper.OrderDetailMapper;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 订单详情 服务层实现。
*
@@ -15,4 +17,8 @@ import org.springframework.stereotype.Service;
@Service
public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements OrderDetailService{
@Override
public void createOrderDetails(Long orderId, List<OrderDetail> orderDetails) {
getMapper().createOrderDetails(orderId, orderDetails);
}
}

View File

@@ -1,7 +1,9 @@
package com.czg.service.order.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
@@ -9,6 +11,7 @@ import com.czg.account.dto.shopuser.ShopUserMoneyEditDTO;
import com.czg.account.entity.*;
import com.czg.account.service.*;
import com.czg.config.RabbitPublisher;
import com.czg.config.RedisCst;
import com.czg.entity.notify.CzgPayNotifyDTO;
import com.czg.entity.notify.CzgRefundNotifyDTO;
import com.czg.enums.ShopUserFlowBizEnum;
@@ -23,8 +26,8 @@ import com.czg.order.service.OrderDetailService;
import com.czg.order.service.OrderInfoService;
import com.czg.order.service.OrderPaymentService;
import com.czg.order.vo.OrderDetailSmallVO;
import com.czg.order.vo.OrderInfoCreateVo;
import com.czg.order.vo.OrderInfoVo;
import com.czg.service.RedisService;
import com.czg.service.order.enums.OrderStatusEnums;
import com.czg.service.order.mapper.OrderInfoMapper;
import com.czg.utils.AssertUtil;
@@ -43,8 +46,9 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List;
import java.util.*;
/**
* 订单表 服务层实现。
@@ -58,27 +62,31 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
@Resource
private OrderDetailService orderDetailService;
@Resource
private OrderPaymentService paymentService;
@Resource
private RabbitPublisher rabbitPublisher;
@Resource
private CashierCartService cartService;
@Resource
private OrderDetailService detailService;
@DubboReference
private ShopTableService shopTableService;
@DubboReference
private ShopInfoService shopInfoService;
@DubboReference
private PointsBasicSettingService pointsBasicService;
@DubboReference
private ShopUserService shopUserService;
@DubboReference
private UserInfoService userInfoService;
@DubboReference
private ShopUserService shopUserService;
@DubboReference
private ShopTableService shopTableService;
@DubboReference
private ShopActivateService activateService;
@DubboReference
private PointsBasicSettingService pointsBasicService;
@DubboReference
private ShopActivateCouponRecordService couponRecordService;
@Resource
private RedisService redisService;
@Resource
private RabbitPublisher rabbitPublisher;
@Override
public Page<OrderInfoVo> getOrderByPage(OrderInfoQueryDTO param) {
@@ -114,13 +122,13 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
}
@Override
public OrderInfoCreateVo createOrder(OrderInfoAddDTO param) {
public OrderInfo createOrder(OrderInfoAddDTO param) {
ShopInfo shopInfo = shopInfoService.getById(param.getShopId());
AssertUtil.isNull(shopInfo, "生成订单失败,店铺信息不存在");
if (shopInfo.getIsTableFee().equals(0) && param.getSeatNum() == 0) {
throw new ValidateException("生成订单失败,请选择用餐人数后下单");
}
if (!shopInfo.getEatModel().contains(param.getEatModel())) {
if (!shopInfo.getEatModel().contains(param.getDineMode())) {
throw new ValidateException("生成订单失败,店铺不支持该用餐模式");
}
if ("afterPay".equals(param.getPayMode()) && !"restaurant".equals(shopInfo.getRegisterType())) {
@@ -130,7 +138,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
throw new ValidateException("生成订单失败,该店铺不支持使用会员价");
}
PointsBasicSetting pointSetting = pointsBasicService.getById(shopInfo.getId());
if (param.getPointsNum().compareTo(BigDecimal.ZERO) > 0 && !pointSetting.getEnableDeduction().equals(1)) {
if (param.getPointsNum() > 0 && !pointSetting.getEnableDeduction().equals(1)) {
throw new ValidateException("生成订单失败,该店铺未开启积分抵扣");
}
UserInfo userInfo = userInfoService.getById(param.getUserId());
@@ -141,11 +149,151 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
}
ShopTable table = shopTableService.getOneByTableCode(param.getShopId(), param.getTableCode());
AssertUtil.isNull(table, "生成订单失败,桌台信息不存在");
// List<CashierCart> cashierCarts = cartService.
if (param.getDiscountRatio().compareTo(BigDecimal.ZERO) <= 0 && param.getDiscountRatio().compareTo(BigDecimal.ONE) > 0) {
throw new ValidateException("生成订单失败,折扣比例不正确");
}
//商品券
Map<Long, Integer> prodCouponMap = new HashMap<>();
//满减券 满fullCouponAmount 减disCouponAmount
BigDecimal fullAmount = BigDecimal.ZERO;
BigDecimal discountAmount = BigDecimal.ZERO;
if (CollUtil.isNotEmpty(param.getCouponList())) {
//校验优惠券
List<ShopActivateCouponRecord> records = couponRecordService.list(QueryWrapper.create()
.where(ShopActivateCouponRecord::getId).in(param.getCouponList())
.and(ShopActivateCouponRecord::getStatus).eq("not_used"));
if (CollUtil.isEmpty(records)) {
throw new ValidateException("生成订单失败,优惠券信息不存在");
} else if (records.size() != param.getCouponList().size()) {
throw new ValidateException("生成订单失败,优惠券信息不正确");
}
boolean isFullMinus = false;
for (ShopActivateCouponRecord record : records) {
if (record.getType().equals(1)) {
if (isFullMinus) {
throw new ValidateException("生成订单失败,满减券仅可使用一张");
}
fullAmount = record.getFullAmount();
discountAmount = record.getDiscountAmount();
isFullMinus = true;
} else if (record.getType().equals(2)) {
prodCouponMap.compute(record.getProId(), (key, oldValue) -> oldValue == null ? 1 : oldValue + 1);
}
}
}
if (discountAmount.compareTo(param.getFullCouponDiscountAmount()) != 0) {
throw new ValidateException("生成订单失败,满减券减免金额不正确");
}
//获取商品信息 计算金额 需要传入优惠券 减去优惠券
List<OrderDetail> orderDetails = cartService.getCartByTableCode(table.getTableCode(), param.isVipPrice() ? 1 : 0, param.getPlaceNum());
//总打包费
BigDecimal packAmount = BigDecimal.ZERO;
//商品优惠券金额
BigDecimal prodCouponAmount = BigDecimal.ZERO;
//总商品支付金额 不包含打包费 用来计算后续
BigDecimal totalAmount = BigDecimal.ZERO;
processOrderDetails(orderDetails, prodCouponMap, prodCouponAmount, packAmount, totalAmount);
if (packAmount.compareTo(param.getPackFee()) != 0) {
throw new ValidateException("生成订单失败,打包费不正确");
}
if (prodCouponAmount.compareTo(param.getProductCouponDiscountAmount()) != 0) {
throw new ValidateException("生成订单失败,商品优惠券优惠金额不正确");
}
//折扣金额 如 9折 计算 为 订单金额*0.9 向上取整
BigDecimal newTotalAmount = BigDecimal.ZERO;
if (param.getDiscountRatio().compareTo(BigDecimal.ONE) != 0) {
newTotalAmount = totalAmount.multiply(param.getDiscountRatio()).setScale(2, RoundingMode.UP);
if (param.getDiscountAmount().compareTo(totalAmount.subtract(newTotalAmount)) != 0) {
throw new ValidateException("生成订单失败,折扣金额不正确");
}
}
//满减券 校验
if (newTotalAmount.compareTo(fullAmount) < 0) {
throw new ValidateException("生成订单失败,满减券不满足条件");
}
//减去满减优惠券
newTotalAmount = newTotalAmount.subtract(discountAmount);
//积分抵扣 金额范围校验 抵扣金额校验
if (param.getPointsNum() > 0) {
if (pointSetting.getMinPaymentAmount().compareTo(newTotalAmount) > 0) {
throw new ValidateException("生成订单失败,未满足积分抵扣最低门槛");
}
if (pointSetting.getMaxDeductionRatio().multiply(newTotalAmount).compareTo(param.getPointsDiscountAmount()) < 0) {
throw new ValidateException("生成订单失败,积分抵扣金额已超出最大抵扣金额");
}
BigDecimal pointAmount = new BigDecimal(param.getPointsNum()).divide(new BigDecimal(pointSetting.getEquivalentPoints()), 2, RoundingMode.DOWN);
if (pointAmount.compareTo(param.getPointsDiscountAmount()) != 0) {
throw new ValidateException("生成订单失败,积分抵扣金额不正确");
}
}
newTotalAmount = newTotalAmount.subtract(param.getPointsDiscountAmount());
//抹零
newTotalAmount = newTotalAmount.subtract(param.getRoundAmount());
//校验最终金额(订单金额 (扣除各类折扣)+打包费 餐位费)
newTotalAmount = newTotalAmount.add(param.getPackFee()).add(new BigDecimal(param.getSeatNum()).multiply(shopInfo.getTableFee()));
if (newTotalAmount.compareTo(param.getOrderAmount()) != 0) {
throw new ValidateException("生成订单失败,订单金额不正确");
}
//生成订单 //discount_info 所有折扣 几折 折扣金额 满减金额 优惠券金额 积分抵扣金额 抹零
OrderInfo orderInfo = initOrderInfo(param, shopInfo, table);
orderDetailService.createOrderDetails(orderInfo.getId(), orderDetails);
return orderInfo;
}
return null;
/**
* 填充 优惠券使用数量 以及 付款金额
*
* @param orderDetails 订单详情 需要回填 优惠券抵扣数量
* @param prodCouponMap 使用优惠券<商品id,数量>
* @param prodCouponAmount 商品券优惠金额 商品单价*优惠数量 的总和
* @param packAmount 打包费
* @param totalAmount 最终总金额(没加打包费 餐位费) 去除优惠券金额后的
*/
private void processOrderDetails(List<OrderDetail> orderDetails, Map<Long, Integer> prodCouponMap,
BigDecimal prodCouponAmount, BigDecimal packAmount, BigDecimal totalAmount) {
Map<Long, List<OrderDetail>> detailMap = new HashMap<>();
for (OrderDetail detail : orderDetails) {
detailMap.computeIfAbsent(detail.getProductId(), k -> new ArrayList<>()).add(detail);
if (detail.getPackNumber().compareTo(BigDecimal.ZERO) > 0 && detail.getPackAmount().compareTo(BigDecimal.ZERO) > 0) {
packAmount = packAmount.add(detail.getPackAmount().multiply(detail.getPackNumber()));
}
}
List<OrderDetail> resultList = new ArrayList<>();
for (Map.Entry<Long, List<OrderDetail>> entry : detailMap.entrySet()) {
Long key = entry.getKey();
List<OrderDetail> value = entry.getValue();
BigDecimal couponNum = BigDecimal.ZERO;
if (prodCouponMap.containsKey(key)) {
couponNum = new BigDecimal(prodCouponMap.get(key));
if (value.size() > 1) {
value.sort(Comparator.comparing(OrderDetail::getPrice));
}
}
for (OrderDetail orderDetail : value) {
if (couponNum.compareTo(BigDecimal.ZERO) > 0) {
if (couponNum.compareTo(orderDetail.getNum()) >= 0) {
orderDetail.setCouponNum(orderDetail.getNum());
orderDetail.setPayAmount(BigDecimal.ZERO);
couponNum = couponNum.subtract(orderDetail.getNum());
} else {
orderDetail.setCouponNum(couponNum);
orderDetail.setPayAmount((orderDetail.getNum().subtract(couponNum)).multiply(orderDetail.getPrice()));
couponNum = BigDecimal.ZERO;
}
prodCouponAmount = prodCouponAmount.add(orderDetail.getPrice().multiply(orderDetail.getCouponNum()));
} else {
orderDetail.setCouponNum(BigDecimal.ZERO);
orderDetail.setPayAmount(orderDetail.getNum().multiply(orderDetail.getPrice()));
}
totalAmount = totalAmount.add(orderDetail.getPayAmount());
resultList.add(orderDetail);
}
if (couponNum.compareTo(BigDecimal.ZERO) != 0) {
throw new ValidateException("生成订单失败,优惠券数量不正确");
}
}
orderDetails = resultList;
}
@Override
@@ -244,4 +392,91 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
//发送打票信息
rabbitPublisher.sendOrderPrintMsg(orderId.toString());
}
private OrderInfo initOrderInfo(OrderInfoAddDTO param, ShopInfo shopInfo, ShopTable table) {
OrderInfo orderInfo = new OrderInfo();
if (param.getOrderId() != null) {
orderInfo = getById(param.getOrderId());
if (!"unpaid".equals(orderInfo.getStatus())) {
throw new ValidateException("生成订单失败,订单已结束,请重新下单");
}
} else {
orderInfo.setOrderNo(param.getPlatformType() + IdUtil.getSnowflakeNextId());
orderInfo.setShopId(param.getShopId());
orderInfo.setUserId(param.getUserId());
orderInfo.setTableCode(table.getTableCode());
orderInfo.setTableName(table.getName());
orderInfo.setDineMode(param.getDineMode());
orderInfo.setSeatNum(param.getSeatNum());
orderInfo.setRefundAmount(BigDecimal.ZERO);
orderInfo.setPayAmount(BigDecimal.ZERO);
orderInfo.setOrderType(param.getOrderType());
orderInfo.setPlatformType(param.getPlatformType());
orderInfo.setDineMode(param.getDineMode());
orderInfo.setPayMode(param.getPayMode());
orderInfo.setStatus("unpaid");
orderInfo.setRefundAble(0);
orderInfo.setTradeDay(DateUtil.formatDate(new Date()));
orderInfo.setRemark(param.getRemark());
orderInfo.setStaffId(param.getStaffId());
orderInfo.setIsWaitCall(param.isWaitCall() ? 1 : 0);
orderInfo.setCreditBuyerId(param.getCreditBuyerId());
orderInfo.setIsDel(0);
//取餐码 多端一致
orderInfo.setTakeCode(getCode(shopInfo.getId()));
orderInfo.setSeatAmount(shopInfo.getTableFee().multiply(new BigDecimal(param.getSeatNum())));
}
orderInfo.setPlaceNum(param.getPlaceNum());
orderInfo.setPointsNum(orderInfo.getPointsNum() + param.getPointsNum());
orderInfo.setOriginAmount(orderInfo.getOriginAmount().add(param.getOriginAmount()));
orderInfo.setRoundAmount(orderInfo.getRoundAmount().add(param.getRoundAmount()));
orderInfo.setOrderAmount(orderInfo.getOrderAmount().add(param.getOrderAmount()));
orderInfo.setPointsDiscountAmount(orderInfo.getPointsDiscountAmount().add(param.getPointsDiscountAmount()));
orderInfo.setProductCouponDiscountAmount(orderInfo.getProductCouponDiscountAmount().add(param.getProductCouponDiscountAmount()));
orderInfo.setFullCouponDiscountAmount(orderInfo.getFullCouponDiscountAmount().add(param.getFullCouponDiscountAmount()));
orderInfo.setDiscountAmount(orderInfo.getDiscountAmount().add(param.getDiscountAmount()));
orderInfo.setPackFee(orderInfo.getPackFee().add(param.getPackFee()));
orderInfo.setDiscountRatio(orderInfo.getDiscountRatio() + param.getDiscountRatio());
//优惠券
orderInfo.setCouponInfoList(orderInfo.getCouponInfoList() + "," + JSONObject.toJSONString(param.getCouponList()));
//折扣信息
orderInfo.setDiscountInfo(buildDiscountInfo(orderInfo));
saveOrUpdate(orderInfo);
return orderInfo;
}
private String getCode(Long shopId) {
String key = RedisCst.SHOP_CODE + DateUtil.today() + ":" + shopId;
int count = 1;
if (redisService.hasKey(key)) {
count = Integer.parseInt(redisService.get(key).toString());
count++;
}
// 设置缓存并设置过期时间
redisService.set(key, count, 24 * 60 * 60);
return "#" + count;
}
private String buildDiscountInfo(OrderInfo orderInfo) {
JSONObject jsonObject = new JSONObject();
if (orderInfo.getProductCouponDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
jsonObject.put("商品券抵扣", orderInfo.getProductCouponDiscountAmount());
}
if (orderInfo.getDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
jsonObject.put("打折优惠", orderInfo.getDiscountAmount());
}
if (orderInfo.getFullCouponDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
jsonObject.put("满减券抵扣", orderInfo.getFullCouponDiscountAmount());
}
if (orderInfo.getPointsDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
jsonObject.put("积分抵扣", orderInfo.getPointsDiscountAmount());
}
if (orderInfo.getRoundAmount().compareTo(BigDecimal.ZERO) > 0) {
jsonObject.put("抹零", orderInfo.getPointsNum());
}
return jsonObject.toString();
}
}

View File

@@ -5,12 +5,44 @@
<mapper namespace="com.czg.service.order.mapper.CashierCartMapper">
<select id="getCartByTableCode" resultType="com.czg.order.entity.OrderDetail">
select cart.shop_id as shopId,
cart.product_id as productId,
pros.product_name as productName,
cart.sku_id as skuId,
skus.spec_info as skuName,
cart.number as number,
select cart.shop_id as shopId,
cart.number as number,
pros.pack_fee as packAmount,
cart.pack_number as packNumber,
cart.is_temporary as isTemporary,
cart.discount_sale_note as discountSaleNote,
cart.is_print as isPrint,
cart.is_wait_call as isWaitCall,
cart.pro_group_info as proGroupInfo,
cart.remark as remark,
cart.product_id as productId,
pros.cover_img as productImg,
pros.type as productType,
cart.sku_id as skuId,
skus.spec_info as skuName,
'wait-pay' as status,
#{placeNum} as placeNum,
#{dineMode} as dineMode,
case cart.is_temporary
when 1 then cart.product_name
else pros.name
end as productName,
case cart.is_gift
when 1 then 0
else
CASE
cart.is_temporary
WHEN 1 THEN
cart.discount_sale_amount
ELSE
IF(cart.discount_sale_amount > 0,
cart.discount_sale_amount,
CASE #{isUseVip}
WHEN 1 THEN skus.member_price
ELSE skus.sale_price END)
end
END as price
from tb_cashier_cart cart
left join tb_product pros on cart.product_id = pros.id
left join tb_prod_sku skus on cart.sku_id = skus.id

View File

@@ -4,4 +4,20 @@
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.czg.service.order.mapper.OrderDetailMapper">
<insert id="createOrderDetails">
INSERT INTO tb_order_detail(order_id, shop_id, product_id, product_img, product_name, product_type, sku_id,
sku_name, price, discount_amount, pack_amount, pay_amount, return_amount, num, pack_number, coupon_num,
return_num, refund_num, refund_no, discount_sale_note, status, place_num, is_temporary, is_print, is_wait_call,
pro_group_info, remark, refund_remark, create_time, update_time)
VALUES
<foreach collection="orderDetails" item="entity" separator=",">
(#{orderId}, #{entity.shopId}, #{entity.productId}, #{entity.productImg}, #{entity.productName},
#{entity.productType}, #{entity.skuId}, #{entity.skuName}, #{entity.price}, #{entity.discountAmount},
#{entity.packAmount}, #{entity.payAmount}, #{entity.returnAmount}, #{entity.num}, #{entity.packNumber},
#{entity.couponNum}, #{entity.returnNum}, #{entity.refundNum}, #{entity.refundNo},
#{entity.discountSaleNote}, #{entity.status}, #{entity.placeNum}, #{entity.isTemporary}, #{entity.isPrint},
#{entity.isWaitCall}, #{entity.proGroupInfo}, #{entity.remark}, #{entity.refundRemark},
#{entity.createTime}, #{entity.updateTime})
</foreach>
</insert>
</mapper>