Bläddra i källkod

优惠券使用

skyline 2 år sedan
förälder
incheckning
d0cdc291b3

+ 4 - 1
entity/src/main/java/com/kym/entity/common/Queues.java

@@ -1,5 +1,8 @@
 package com.kym.entity.common;
 
+/**
+ * 队列
+ */
 public class Queues {
-    public static final String DELAY_QUEUE = "delay.user.coupon.queue";
+    public static final String DELAY_USER_COUPON_QUEUE = "delay.user.coupon.queue";
 }

+ 64 - 0
entity/src/main/java/com/kym/entity/miniapp/OrderCoupon.java

@@ -0,0 +1,64 @@
+package com.kym.entity.miniapp;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.kym.entity.BaseEntity;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+/**
+ * <p>
+ * 订单优惠券关联表
+ * </p>
+ *
+ * @author skyline
+ * @since 2024-06-02
+ */
+@Getter
+@Setter
+@TableName("t_order_coupon")
+@Accessors(chain = true)
+public class OrderCoupon extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 优惠券id
+     */
+    private Long couponId;
+
+    /**
+     * 主活动id
+     */
+    private Long activityId;
+
+    /**
+     * 用户优惠券id
+     */
+    private Long userCouponId;
+
+    /**
+     * 用户id
+     */
+    private Long userId;
+
+    /**
+     * 充电订单号
+     */
+    private String startChargeSeq;
+
+    /**
+     * 券种:Discount折扣券、FullDiscount满减券
+     */
+    private String couponType;
+
+    /**
+     * 折扣:100代表无折扣,75代表75折;折扣金额(分)
+     */
+    private Integer discount;
+
+    /**
+     * 总优惠金额(分)
+     */
+    private Integer discountAmount;
+}

+ 5 - 0
entity/src/main/java/com/kym/entity/miniapp/UserCoupon.java

@@ -22,8 +22,13 @@ import java.time.LocalDateTime;
 @Accessors(chain = true)
 public class UserCoupon extends BaseEntity {
 
+
+
     private static final long serialVersionUID = 1L;
 
+    public final static int STATUS_无效 = 0;
+    public final static int STATUS_有效 = 1;
+
     /**
      * 优惠券id
      */

+ 2 - 0
entity/src/main/java/com/kym/entity/miniapp/vo/UserVo.java

@@ -3,6 +3,7 @@ package com.kym.entity.miniapp.vo;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.kym.entity.admin.RechargeRights;
 import com.kym.entity.admin.vo.ActivityVo;
+import com.kym.entity.miniapp.UserCoupon;
 import com.kym.entity.miniapp.UserRechargeRights;
 import lombok.Data;
 import lombok.experimental.Accessors;
@@ -33,5 +34,6 @@ public class UserVo implements Serializable {
     public String vin;
     public List<ActivityVo> activityList;
     public List<UserRechargeRights> userRechargeRightsList;
+    public List<UserCoupon> userCouponList;
 
 }

+ 16 - 0
mapper/src/main/java/com/kym/mapper/miniapp/OrderCouponMapper.java

@@ -0,0 +1,16 @@
+package com.kym.mapper.miniapp;
+
+import com.kym.entity.miniapp.OrderCoupon;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 订单优惠券关联表 Mapper 接口
+ * </p>
+ *
+ * @author skyline
+ * @since 2024-06-02
+ */
+public interface OrderCouponMapper extends BaseMapper<OrderCoupon> {
+
+}

+ 25 - 0
mapper/src/main/resources/mappers/miniapp/OrderCouponMapper.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.kym.mapper.miniapp.OrderCouponMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.kym.entity.miniapp.OrderCoupon">
+        <result column="id" property="id" />
+        <result column="coupon_id" property="couponId" />
+        <result column="activity_id" property="activityId" />
+        <result column="user_coupon_id" property="userCouponId" />
+        <result column="user_id" property="userId" />
+        <result column="start_charge_seq" property="startChargeSeq" />
+        <result column="coupon_type" property="couponType" />
+        <result column="discount" property="discount" />
+        <result column="discount_amount" property="discountAmount" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id,coupon_id, activity_id, user_coupon_id, user_id, start_charge_seq, coupon_type, discount, discount_amount,create_time,update_time
+    </sql>
+
+</mapper>

+ 2 - 1
miniapp/src/main/java/com/kym/miniapp/controller/ChargerController.java

@@ -94,11 +94,12 @@ public class ChargerController {
     @GetMapping("/startCharge/{connectorId}")
     R<?> startCharge(@PathVariable("connectorId") String connectorId,
                      @RequestParam(value = "rechargeRightsId", required = false) Long userRechargeRightsId,
+                     @RequestParam(value = "rechargeRightsId", required = false) Long userCouponId,
                      @RequestParam(value = "isBooking", defaultValue = "false") Boolean isBooking,
                      @RequestParam(value = "startTime", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
                      @RequestParam(value = "endTime", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
         var userId = StpUtil.getLoginIdAsLong();
-        return R.success(chargeService.queryStartCharge(userId, connectorId, userRechargeRightsId, isBooking, startTime, endTime));
+        return R.success(chargeService.queryStartCharge(userId, connectorId, userRechargeRightsId, userCouponId, isBooking, startTime, endTime));
     }
 
     @ApiLog("取消预约充电")

+ 18 - 0
miniapp/src/main/java/com/kym/miniapp/controller/OrderCouponController.java

@@ -0,0 +1,18 @@
+package com.kym.miniapp.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 订单优惠券关联表 前端控制器
+ * </p>
+ *
+ * @author skyline
+ * @since 2024-06-02
+ */
+@RestController
+@RequestMapping("/order-coupon")
+public class OrderCouponController {
+
+}

+ 1 - 1
miniapp/src/main/java/com/kym/miniapp/jobs/StartChargeDelayJob.java

@@ -91,7 +91,7 @@ public class StartChargeDelayJob implements DelayService<DelayChargeOrder> {
                     log.info("出队预约充电订单:{},队列剩余:{}", delayedItem.data.getStartChargeSeq(), START_DELAY_QUEUE.size());
                     // 启动充电
                     var order = delayedItem.data;
-                    chargeService.queryStartCharge(order.getUserId(), order.getConnectorId(), null, false, null, null);
+                    chargeService.queryStartCharge(order.getUserId(), order.getConnectorId(), null, null,false, null, null);
                     log.info("预约充电启动成功:用户:{},订单号:{},预约启动时间:{}", order.getUserId(), order.getStartChargeSeq(), order.getStartTime());
                     // 线程休眠250ms
                     Thread.sleep(250);

+ 215 - 101
service/src/main/java/com/kym/service/enplus/impl/EnNotifyServiceImpl.java

@@ -7,14 +7,12 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.kym.common.cache.ConnectorStatusCache;
 import com.kym.common.config.RedisDBChangeUtil;
 import com.kym.entity.admin.ConnectorInfo;
+import com.kym.entity.admin.Coupon;
 import com.kym.entity.admin.EquipmentInfo;
 import com.kym.entity.admin.MonitorLog;
 import com.kym.entity.common.RedisKeys;
 import com.kym.entity.enplus.EnConnectorStatusInfo;
-import com.kym.entity.miniapp.ChargeOrder;
-import com.kym.entity.miniapp.OrderRechargeRights;
-import com.kym.entity.miniapp.UserRechargeRights;
-import com.kym.entity.miniapp.WalletDetail;
+import com.kym.entity.miniapp.*;
 import com.kym.service.admin.ConnectorInfoService;
 import com.kym.service.admin.EquipmentInfoService;
 import com.kym.service.admin.MonitorLogService;
@@ -65,6 +63,10 @@ public class EnNotifyServiceImpl implements EnNotifyService {
 
     private final OrderRechargeRightsService orderRechargeRightsService;
 
+    private final OrderCouponService orderCouponService;
+
+    private final UserCouponService userCouponService;
+
     private final RedisDBChangeUtil redisDBChangeUtil;
 
     @Value("${kym.notify-email}")
@@ -73,7 +75,7 @@ public class EnNotifyServiceImpl implements EnNotifyService {
     public EnNotifyServiceImpl(EnPlusService enPlusService, ChargeOrderService chargeOrderService,
                                ChargeService chargeService, AccountService accountService, WalletDetailService walletDetailService,
                                MonitorLogService monitorLogService, EquipmentInfoService equipmentInfoService,
-                               ConnectorInfoService connectorInfoService, UserRechargeRightsService userRechargeRightsService, OrderRechargeRightsService orderRechargeRightsService, RedisDBChangeUtil redisDBChangeUtil) {
+                               ConnectorInfoService connectorInfoService, UserRechargeRightsService userRechargeRightsService, OrderRechargeRightsService orderRechargeRightsService, OrderCouponService orderCouponService, UserCouponService userCouponService, RedisDBChangeUtil redisDBChangeUtil) {
         this.enPlusService = enPlusService;
         this.chargeOrderService = chargeOrderService;
         this.chargeService = chargeService;
@@ -84,6 +86,8 @@ public class EnNotifyServiceImpl implements EnNotifyService {
         this.connectorInfoService = connectorInfoService;
         this.userRechargeRightsService = userRechargeRightsService;
         this.orderRechargeRightsService = orderRechargeRightsService;
+        this.orderCouponService = orderCouponService;
+        this.userCouponService = userCouponService;
         this.redisDBChangeUtil = redisDBChangeUtil;
     }
 
@@ -264,7 +268,7 @@ public class EnNotifyServiceImpl implements EnNotifyService {
     }
 
     /**
-     * 推送充电订单信息
+     * 推送充电订单信息(订单结算)
      *
      * @param json
      * @return
@@ -297,103 +301,16 @@ public class EnNotifyServiceImpl implements EnNotifyService {
                     // 实付金额初始化为订单总金额
                     .setPayAmount(chargeOrder.getTotalMoney());
 
-            // 获取订单权益,计算订单优惠金额,实付金额,服务费优惠金额
-            var orderRechargeRights = orderRechargeRightsService.lambdaQuery()
-                    .eq(OrderRechargeRights::getStartChargeSeq, chargeOrder.getStartChargeSeq())
-                    .eq(OrderRechargeRights::getUserId, chargeOrder.getUserId())
-                    .one();
-            if (orderRechargeRights != null) {
-                // 获取折扣,计算优惠
-                var discount = orderRechargeRights.getDiscount();
-                var serviceMoneyDiscount = (int) (chargeOrder.getServiceMoney() * (BigDecimal.valueOf(1).subtract(BigDecimal.valueOf(discount).divide(BigDecimal.valueOf(100)))).doubleValue()); // 只保留到分,分以下不进行四舍五入
-                chargeOrder
-                        .setPayAmount(chargeOrder.getTotalMoney() - serviceMoneyDiscount)
-                        .setDiscountAmount(serviceMoneyDiscount)
-                        .setServiceMoneyDiscount(serviceMoneyDiscount);
-
-                orderRechargeRights.setDiscountAmount(serviceMoneyDiscount);
-
-                // 用户充值权益
-                var userRechargeRights = userRechargeRightsService.lambdaQuery()
-                        .eq(UserRechargeRights::getId, orderRechargeRights.getUserRightsId())
-                        .eq(UserRechargeRights::getUserId, chargeOrder.getUserId())
-                        .eq(UserRechargeRights::getRightsId, orderRechargeRights.getRightsId())
-                        .one();
-
-                // 用户当前权益金余额是否足够支付本次充电费用
-                if (userRechargeRights.getStatus() == UserRechargeRights.STATUS_有效) {
-                    if (chargeOrder.getPayAmount() <= userRechargeRights.getRightsBalance()) {
-                        userRechargeRights
-                                // 用户权益包余额扣减当前订单实付金额
-                                .setRightsBalance(userRechargeRights.getRightsBalance() - chargeOrder.getPayAmount())
-                                .setDiscountAmount(userRechargeRights.getDiscountAmount() + serviceMoneyDiscount);
-
-                        // 账户设置优惠不可退金额
-                        account.setDiscountAmount(account.getDiscountAmount() + serviceMoneyDiscount);
-                        if (chargeOrder.getPayAmount().intValue() == userRechargeRights.getRightsBalance().intValue()) {
-                            userRechargeRights.setStatus(UserRechargeRights.STATUS_无效);
-                        }
-                    } else {
-                        // 不足(用户当前权益金余额是否足够支付本次充电费用)
-                        // 如果优惠后的实付金额大于权益金余额,那么实际优惠金额需要通过权益金余额反推
-                        // 通过权益金余额,反推最多扣减多少服务费
-                        // rightBalance = elecMoney + serviceMoney * (1-discount)
-                        // 订单中elecMoney与serviceMoney的比值是一定的
-                        var ratio = BigDecimal.valueOf(chargeOrder.getElecMoney()).divide(BigDecimal.valueOf(chargeOrder.getServiceMoney()), 2, RoundingMode.HALF_UP);
-                        // (ratio * realServiceMoney) + (realServiceMoney * (100 - discount)/100) = rightBalance
-                        var discountRate = BigDecimal.valueOf(discount).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
-                        var userRightBalance = BigDecimal.valueOf(userRechargeRights.getRightsBalance());
-                        // 真实可以覆盖的服务费金额
-                        var realServiceMoney = userRightBalance.divide(ratio.add(BigDecimal.valueOf(1)).subtract(discountRate), 2, RoundingMode.HALF_UP);
-                        // 实际优惠金额 = 实际可以覆盖的服务费金额 * 折扣率
-                        var realDiscountAmount = realServiceMoney.multiply(BigDecimal.valueOf(1).subtract(discountRate)).intValue();
-                        chargeOrder
-                                // 实付金额
-                                .setPayAmount(chargeOrder.getTotalMoney() - realDiscountAmount)
-                                .setDiscountAmount(realDiscountAmount)
-                                .setServiceMoneyDiscount(realDiscountAmount);
-                        // 余额扣完,总优惠金额加上最后一次真实优惠金额,此条用户权益结束,状态设置为无效
-                        userRechargeRights
-                                .setRightsBalance(0)
-                                .setDiscountAmount(userRechargeRights.getDiscountAmount() + realDiscountAmount)
-                                .setStatus(UserRechargeRights.STATUS_无效);
-
-                        orderRechargeRights.setDiscountAmount(realDiscountAmount);
-                        // 此权益包消耗完毕,账户设置优惠不可退金额
-                        account.setDiscountAmount(account.getDiscountAmount() + realDiscountAmount);
-                    }
-                }
-
-                // 更新订单权益的最终优惠金额
-                orderRechargeRightsService.updateById(orderRechargeRights);
-                // 更新用户权益金等
-                userRechargeRightsService.updateById(userRechargeRights);
-            }
+            // 结束时间
+            var endTime = LocalDateTime.parse(data.getString("EndTime"), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
 
-            // 优惠券(将来如果有多种活动形式,进行链式处理)
+            // todo 判断适用哪种优惠
 
-            // 订单成功
-            chargeOrder.setOrderStatus(ChargeOrder.ORDER_STATUS_成功);
-            // 充电结束
-            chargeOrder.setChargeStatus(ChargeOrder.CHARGE_STATUS_已结束);
-            chargeOrderService.updateById(chargeOrder);
-
-            // 账户扣费
-            account.setBalance(account.getBalance() - chargeOrder.getPayAmount());
-            accountService.updateById(account);
-
-            // 记录资金流水
-            var walletDetail = new WalletDetail();
-            walletDetail.setUserId(chargeOrder.getUserId());
-            walletDetail.setOrderNo(startChargeSeq);
-            // 消费
-            walletDetail.setType(WalletDetail.TYPE_消费);
-            walletDetail.setAmount(chargeOrder.getPayAmount());
-            walletDetail.setTransactionTime(LocalDateTime.parse(data.getString("EndTime"), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
-            // 已确认
-            walletDetail.setStatus(WalletDetail.STATUS_已确认);
-            walletDetailService.save(walletDetail);
+            // 处理充值权益优惠逻辑
+            handleRechargeRights(chargeOrder, account, endTime);
 
+            // 处理优惠券优惠逻辑
+            handleCoupon(chargeOrder, account, endTime);
         }
         return """
                 {
@@ -403,4 +320,201 @@ public class EnNotifyServiceImpl implements EnNotifyService {
                 }
                 """.formatted(startChargeSeq, chargeOrder.getConnectorId(), 0);
     }
-}
+
+    private void handleCoupon(ChargeOrder chargeOrder, Account account, LocalDateTime endTime) {
+        try {
+            if (chargeOrder.getServiceMoney() <= 0) {
+                LOGGER.info("订单:{} 服务费不合法", chargeOrder.getStartChargeSeq());
+                return; // 服务费不合法,直接返回
+            }
+
+            // 获取订单优惠券
+            var orderCoupon = orderCouponService.lambdaQuery()
+                    .eq(OrderCoupon::getStartChargeSeq, chargeOrder.getStartChargeSeq())
+                    .eq(OrderCoupon::getUserId, chargeOrder.getUserId())
+                    .one();
+            if (orderCoupon == null) {
+                return; // 没有优惠券,直接返回
+            }
+
+            // 查询用户优惠券信息
+            var userCoupon = userCouponService.lambdaQuery()
+                    .eq(UserCoupon::getId, orderCoupon.getCouponId()) // 假设COUPON_ID是通用的,否则需要根据类型查询不同的字段
+                    .eq(UserCoupon::getUserId, chargeOrder.getUserId())
+                    .one();
+            if (userCoupon == null) {
+                return; // 没有找到用户优惠券,直接返回
+            }
+
+            // 检查是否达到使用门槛
+            if (chargeOrder.getServiceMoney() < userCoupon.getMinServiceMoney()) {
+                LOGGER.info("订单:{}优惠券{}使用失败:服务费未达到优惠券使用门槛", chargeOrder.getStartChargeSeq(), userCoupon.getCouponId());
+                return;
+            }
+
+            // 根据优惠券类型处理
+            processCouponDiscount(chargeOrder, userCoupon, orderCoupon.getCouponType());
+
+            // 执行账户扣费等操作
+            deductions(chargeOrder, account, endTime);
+        } catch (Exception e) {
+            LOGGER.error("处理优惠券失败", e);
+            // 可以添加一些错误处理逻辑,比如重试机制、发送告警等
+        }
+    }
+
+    /**
+     * 处理优惠券优惠逻辑
+     *
+     * @param chargeOrder
+     * @param userCoupon
+     * @param couponType
+     */
+    private void processCouponDiscount(ChargeOrder chargeOrder, UserCoupon userCoupon, String couponType) {
+        int discountAmount;
+        switch (couponType) {
+            case Coupon.COUPON_TYPE_折扣券:
+                discountAmount = chargeOrder.getServiceMoney() * (100 - userCoupon.getDiscount()) / 100;
+                updateOrderAndCoupon(chargeOrder, userCoupon, discountAmount);
+                break;
+            case Coupon.COUPON_TYPE_满减券:
+                discountAmount = chargeOrder.getServiceMoney() - userCoupon.getDiscount();
+                updateOrderAndCoupon(chargeOrder, userCoupon, discountAmount);
+                break;
+        }
+    }
+
+    /**
+     * 更新订单和优惠券信息
+     *
+     * @param chargeOrder
+     * @param userCoupon
+     * @param discountAmount
+     */
+    private void updateOrderAndCoupon(ChargeOrder chargeOrder, UserCoupon userCoupon, int discountAmount) {
+        chargeOrder.setDiscountAmount(discountAmount);
+        chargeOrder.setServiceMoneyDiscount(discountAmount);
+        chargeOrderService.updateById(chargeOrder);
+        userCoupon.setDiscount(discountAmount);
+        userCouponService.updateById(userCoupon);
+    }
+
+
+    /**
+     * 处理订单权益
+     *
+     * @param chargeOrder
+     * @param account
+     * @param endTime
+     */
+    private void handleRechargeRights(ChargeOrder chargeOrder, Account account, LocalDateTime endTime) {
+        // 获取订单权益,计算订单优惠金额,实付金额,服务费优惠金额
+        var orderRechargeRights = orderRechargeRightsService.lambdaQuery()
+                .eq(OrderRechargeRights::getStartChargeSeq, chargeOrder.getStartChargeSeq())
+                .eq(OrderRechargeRights::getUserId, chargeOrder.getUserId())
+                .one();
+        if (orderRechargeRights != null) {
+            // 获取折扣,计算优惠
+            var discount = orderRechargeRights.getDiscount();
+            // 服务费优惠金额只保留到分,分以下不进行四舍五入
+            var serviceMoneyDiscount = (int) (chargeOrder.getServiceMoney() * (BigDecimal.valueOf(1).subtract(BigDecimal.valueOf(discount).divide(BigDecimal.valueOf(100)))).doubleValue());
+            chargeOrder
+                    .setPayAmount(chargeOrder.getTotalMoney() - serviceMoneyDiscount)
+                    .setDiscountAmount(serviceMoneyDiscount)
+                    .setServiceMoneyDiscount(serviceMoneyDiscount);
+
+            orderRechargeRights.setDiscountAmount(serviceMoneyDiscount);
+
+            // 用户充值权益
+            var userRechargeRights = userRechargeRightsService.lambdaQuery()
+                    .eq(UserRechargeRights::getId, orderRechargeRights.getUserRightsId())
+                    .eq(UserRechargeRights::getUserId, chargeOrder.getUserId())
+                    .eq(UserRechargeRights::getRightsId, orderRechargeRights.getRightsId())
+                    .one();
+
+            // 用户当前权益金余额是否足够支付本次充电费用
+            if (userRechargeRights.getStatus() == UserRechargeRights.STATUS_有效) {
+                if (chargeOrder.getPayAmount() <= userRechargeRights.getRightsBalance()) {
+                    userRechargeRights
+                            // 用户权益包余额扣减当前订单实付金额
+                            .setRightsBalance(userRechargeRights.getRightsBalance() - chargeOrder.getPayAmount())
+                            .setDiscountAmount(userRechargeRights.getDiscountAmount() + serviceMoneyDiscount);
+
+                    // 账户设置优惠不可退金额
+                    account.setDiscountAmount(account.getDiscountAmount() + serviceMoneyDiscount);
+                    if (chargeOrder.getPayAmount().intValue() == userRechargeRights.getRightsBalance().intValue()) {
+                        userRechargeRights.setStatus(UserRechargeRights.STATUS_无效);
+                    }
+                } else {
+                    // 不足(用户当前权益金余额是否足够支付本次充电费用)
+                    // 如果优惠后的实付金额大于权益金余额,那么实际优惠金额需要通过权益金余额反推
+                    // 通过权益金余额,反推最多扣减多少服务费
+                    // rightBalance = elecMoney + serviceMoney * (1-discount)
+                    // 订单中elecMoney与serviceMoney的比值是一定的
+                    var ratio = BigDecimal.valueOf(chargeOrder.getElecMoney()).divide(BigDecimal.valueOf(chargeOrder.getServiceMoney()), 2, RoundingMode.HALF_UP);
+                    // (ratio * realServiceMoney) + (realServiceMoney * (100 - discount)/100) = rightBalance
+                    var discountRate = BigDecimal.valueOf(discount).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
+                    var userRightBalance = BigDecimal.valueOf(userRechargeRights.getRightsBalance());
+                    // 真实可以覆盖的服务费金额
+                    var realServiceMoney = userRightBalance.divide(ratio.add(BigDecimal.valueOf(1)).subtract(discountRate), 2, RoundingMode.HALF_UP);
+                    // 实际优惠金额 = 实际可以覆盖的服务费金额 * 折扣率
+                    var realDiscountAmount = realServiceMoney.multiply(BigDecimal.valueOf(1).subtract(discountRate)).intValue();
+                    chargeOrder
+                            // 实付金额
+                            .setPayAmount(chargeOrder.getTotalMoney() - realDiscountAmount)
+                            .setDiscountAmount(realDiscountAmount)
+                            .setServiceMoneyDiscount(realDiscountAmount);
+                    // 余额扣完,总优惠金额加上最后一次真实优惠金额,此条用户权益结束,状态设置为无效
+                    userRechargeRights
+                            .setRightsBalance(0)
+                            .setDiscountAmount(userRechargeRights.getDiscountAmount() + realDiscountAmount)
+                            .setStatus(UserRechargeRights.STATUS_无效);
+
+                    orderRechargeRights.setDiscountAmount(realDiscountAmount);
+                    // 此权益包消耗完毕,账户设置优惠不可退金额
+                    account.setDiscountAmount(account.getDiscountAmount() + realDiscountAmount);
+                }
+            }
+
+            // 更新订单权益的最终优惠金额
+            orderRechargeRightsService.updateById(orderRechargeRights);
+            // 更新用户权益金等
+            userRechargeRightsService.updateById(userRechargeRights);
+
+            // 执行账户扣费等操作
+            deductions(chargeOrder, account, endTime);
+        }
+    }
+
+    /**
+     * 账户扣费等操作
+     *
+     * @param chargeOrder
+     * @param account
+     * @param endTime
+     */
+    private void deductions(ChargeOrder chargeOrder, Account account, LocalDateTime endTime) {
+        // 订单成功
+        chargeOrder.setOrderStatus(ChargeOrder.ORDER_STATUS_成功);
+        // 充电结束
+        chargeOrder.setChargeStatus(ChargeOrder.CHARGE_STATUS_已结束);
+        chargeOrderService.updateById(chargeOrder);
+
+        // 账户扣费
+        account.setBalance(account.getBalance() - chargeOrder.getPayAmount());
+        accountService.updateById(account);
+
+        // 记录资金流水
+        var walletDetail = new WalletDetail();
+        walletDetail.setUserId(chargeOrder.getUserId());
+        walletDetail.setOrderNo(chargeOrder.getStartChargeSeq());
+        // 消费
+        walletDetail.setType(WalletDetail.TYPE_消费);
+        walletDetail.setAmount(chargeOrder.getPayAmount());
+        walletDetail.setTransactionTime(endTime);
+        // 已确认
+        walletDetail.setStatus(WalletDetail.STATUS_已确认);
+        walletDetailService.save(walletDetail);
+
+    }
+}

+ 1 - 1
service/src/main/java/com/kym/service/miniapp/ChargeService.java

@@ -17,7 +17,7 @@ public interface ChargeService {
     void cancelBookingByConnector(String connectorId);
 
 
-    Map<String, String> queryStartCharge(Long userId, String connectorId, Long userRechargeRightsId, Boolean isBooking, LocalDateTime startTime, LocalDateTime endTime);
+    Map<String, String> queryStartCharge(Long userId, String connectorId, Long userRechargeRightsId, Long userCouponId,Boolean isBooking, LocalDateTime startTime, LocalDateTime endTime);
 
     void updateEquipmentAndConnectorStatus(String connectorId);
 

+ 16 - 0
service/src/main/java/com/kym/service/miniapp/OrderCouponService.java

@@ -0,0 +1,16 @@
+package com.kym.service.miniapp;
+
+import com.kym.entity.miniapp.OrderCoupon;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 订单优惠券关联表 服务类
+ * </p>
+ *
+ * @author skyline
+ * @since 2024-06-02
+ */
+public interface OrderCouponService extends IService<OrderCoupon> {
+
+}

+ 25 - 7
service/src/main/java/com/kym/service/miniapp/impl/ChargeServiceImpl.java

@@ -14,10 +14,7 @@ import com.kym.common.utils.OrderUtils;
 import com.kym.entity.admin.ConnectorInfo;
 import com.kym.entity.admin.EquipmentInfo;
 import com.kym.entity.enplus.response.EnBusinessPolicy;
-import com.kym.entity.miniapp.Account;
-import com.kym.entity.miniapp.ChargeOrder;
-import com.kym.entity.miniapp.OrderRechargeRights;
-import com.kym.entity.miniapp.UserRechargeRights;
+import com.kym.entity.miniapp.*;
 import com.kym.entity.miniapp.delay.DelayChargeOrder;
 import com.kym.service.admin.ConnectorInfoService;
 import com.kym.service.admin.EquipmentInfoService;
@@ -59,6 +56,9 @@ public class ChargeServiceImpl implements ChargeService {
 
     private final OrderRechargeRightsService orderRechargeRightsService;
     private final UserRechargeRightsService userRechargeRightsService;
+    private final UserCouponService userCouponService;
+
+    private final OrderCouponService orderCouponService;
 
     private final AccountService accountService;
 
@@ -71,7 +71,7 @@ public class ChargeServiceImpl implements ChargeService {
 
     public ChargeServiceImpl(EquipmentRelationService equipmentRelationService, EquipmentInfoService equipmentInfoService,
                              ConnectorInfoService connectorInfoService, ChargeOrderService chargeOrderService,
-                             OrderRechargeRightsService orderRechargeRightsService, UserRechargeRightsService userRechargeRightsService,
+                             OrderRechargeRightsService orderRechargeRightsService, UserRechargeRightsService userRechargeRightsService, UserCouponService userCouponService, OrderCouponService orderCouponService,
                              AccountService accountService, EnPlusService enPlusService, EnPlusConfig enPlusConfig,
                              @Qualifier("StartChargeDelayJob") @Lazy DelayService<DelayChargeOrder> startDelayService,
                              @Qualifier("StopChargeDelayJob") @Lazy DelayService<DelayChargeOrder> stopDelayService) {
@@ -81,6 +81,8 @@ public class ChargeServiceImpl implements ChargeService {
         this.chargeOrderService = chargeOrderService;
         this.orderRechargeRightsService = orderRechargeRightsService;
         this.userRechargeRightsService = userRechargeRightsService;
+        this.userCouponService = userCouponService;
+        this.orderCouponService = orderCouponService;
         this.accountService = accountService;
         this.enPlusService = enPlusService;
         this.enPlusConfig = enPlusConfig;
@@ -99,7 +101,7 @@ public class ChargeServiceImpl implements ChargeService {
     public Map<String, String> immediatelyCharge(String connectorId) {
         var userId = StpUtil.getLoginIdAsLong();
         cancelBooking();
-        return queryStartCharge(userId, connectorId, null, false, null, null);
+        return queryStartCharge(userId, connectorId, null, null, false, null, null);
     }
 
     /**
@@ -195,7 +197,7 @@ public class ChargeServiceImpl implements ChargeService {
      */
     @Override
     @DSTransactional(rollbackFor = Exception.class)
-    public Map<String, String> queryStartCharge(Long userId, String connectorId, Long userRechargeRightsId, Boolean isBooking, LocalDateTime startTime, LocalDateTime endTime) {
+    public Map<String, String> queryStartCharge(Long userId, String connectorId, Long userRechargeRightsId, Long userCouponId, Boolean isBooking, LocalDateTime startTime, LocalDateTime endTime) {
         var connectorId2StationId = getConnectorIdAndStationId(connectorId);
         connectorId = connectorId2StationId.get("connectorId");
         var stationId = connectorId2StationId.get("stationId");
@@ -237,6 +239,22 @@ public class ChargeServiceImpl implements ChargeService {
                     orderRechargeRightsService.save(orderRechargeRights);
                 }
             }
+
+            // 优惠券
+            if (!CommUtil.isEmptyOrNull(userCouponId)) {
+                var userCoupon = userCouponService.lambdaQuery().eq(UserCoupon::getUserId, userId).eq(UserCoupon::getId, userCouponId).one();
+                if (userCoupon != null) {
+                    var orderCoupon = new OrderCoupon()
+                            .setUserId(userId)
+                            .setStartChargeSeq(startChargeSeq)
+                            .setCouponType(userCoupon.getCouponType())
+                            .setActivityId(userCoupon.getActivityId())
+                            .setCouponId(userCoupon.getCouponId())
+                            .setUserCouponId(userCoupon.getId())
+                            .setDiscount(userCoupon.getDiscount());
+                    orderCouponService.save(orderCoupon);
+                }
+            }
         }
 
         // 如果是预约订单,则将订单放入预约充电延迟队列

+ 20 - 0
service/src/main/java/com/kym/service/miniapp/impl/OrderCouponServiceImpl.java

@@ -0,0 +1,20 @@
+package com.kym.service.miniapp.impl;
+
+import com.kym.entity.miniapp.OrderCoupon;
+import com.kym.mapper.miniapp.OrderCouponMapper;
+import com.kym.service.miniapp.OrderCouponService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 订单优惠券关联表 服务实现类
+ * </p>
+ *
+ * @author skyline
+ * @since 2024-06-02
+ */
+@Service
+public class OrderCouponServiceImpl extends ServiceImpl<OrderCouponMapper, OrderCoupon> implements OrderCouponService {
+
+}

+ 13 - 5
service/src/main/java/com/kym/service/miniapp/impl/UserServiceImpl.java

@@ -64,9 +64,11 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
     private final ActivityService activityService;
     private final BannerService bannerService;
 
+    private final UserCouponService userCouponService;
+
     public UserServiceImpl(WxConfig wxConfig, AccountService accountService, RefundLogService refundLogService,
                            CarsService carsService, UserRechargeRightsService userRechargeRightsService,
-                           RechargeRightsService rechargeRightsService, @Lazy ActivityService activityService, BannerService bannerService) {
+                           RechargeRightsService rechargeRightsService, @Lazy ActivityService activityService, BannerService bannerService, UserCouponService userCouponService) {
         this.wxConfig = wxConfig;
         this.accountService = accountService;
         this.refundLogService = refundLogService;
@@ -75,6 +77,7 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
         this.rechargeRightsService = rechargeRightsService;
         this.activityService = activityService;
         this.bannerService = bannerService;
+        this.userCouponService = userCouponService;
     }
 
 
@@ -145,13 +148,12 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
             userVo.setDefaultPlateNo(car.getPlateNo());
             userVo.setVin(car.getVin());
         }
-        // 当前用户有效的充电权益/优惠券
+        // 当前用户有效的充电权益
         var userRechargeRight = userRechargeRightsService.lambdaQuery()
                 .eq(UserRechargeRights::getUserId, userId).eq(UserRechargeRights::getStatus, UserRechargeRights.STATUS_有效)
                 .orderByAsc(UserRechargeRights::getEndTime).list();
-
+        userVo.setUserRechargeRightsList(userRechargeRight);
         // 可以参加的活动-充值权益
-        // 手动切换数据源
         DynamicDataSourceContextHolder.push("db-admin");
         var bannerList = bannerService.list();
         var rechargeRight = rechargeRightsService.lambdaQuery().eq(RechargeRights::getStatus, Activity.STATUS_进行中).list();
@@ -165,7 +167,13 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
             return vo;
         }).toList();
         userVo.setActivityList(voList);
-        return userVo.setUserRechargeRightsList(userRechargeRight);
+
+        // 当前用户有效的优惠券列表
+        var usesrCouponList = userCouponService.lambdaQuery()
+                .eq(UserCoupon::getUserId, userId).eq(UserCoupon::getStatus, UserCoupon.STATUS_有效).orderByAsc(UserCoupon::getEndTime).list();
+        userVo.setUserCouponList(usesrCouponList);
+
+        return userVo;
     }
 
 

+ 9 - 2
service/src/main/java/com/kym/service/queue/config/QueueConfig.java → service/src/main/java/com/kym/service/queue/config/RabbitConfig.java

@@ -8,6 +8,7 @@ import org.springframework.amqp.core.BindingBuilder;
 import org.springframework.amqp.core.CustomExchange;
 import org.springframework.amqp.core.Queue;
 import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
+import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -16,7 +17,7 @@ import java.util.Map;
 
 
 @Configuration
-public class QueueConfig {
+public class RabbitConfig {
 
     /**
      * 延迟交换机的类型
@@ -25,6 +26,7 @@ public class QueueConfig {
 
     /**
      * 指定批处理MessageListenerConverter(这里必须设置)
+     *
      * @param containerFactory
      * @return
      */
@@ -34,6 +36,11 @@ public class QueueConfig {
         return "cusSimpleRabbitListenerContainerFactory";
     }
 
+    @Bean
+    public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
+        return new Jackson2JsonMessageConverter();
+    }
+
     /**
      * 延迟队列
      *
@@ -41,7 +48,7 @@ public class QueueConfig {
      */
     @Bean(name = "delayQueue")
     public Queue delayQueue() {
-        return new Queue(Queues.DELAY_QUEUE, true, false, false);
+        return new Queue(Queues.DELAY_USER_COUPON_QUEUE, true, false, false);
     }
 
 

+ 19 - 15
service/src/main/java/com/kym/service/queue/consumer/UserCouponConsumer.java

@@ -1,6 +1,9 @@
 package com.kym.service.queue.consumer;
 
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.dynamic.datasource.annotation.DS;
 import com.kym.entity.common.Queues;
+import com.kym.entity.miniapp.UserCoupon;
 import com.kym.service.miniapp.UserCouponService;
 import com.rabbitmq.client.Channel;
 import lombok.extern.slf4j.Slf4j;
@@ -11,10 +14,13 @@ import org.springframework.stereotype.Component;
 
 import java.io.IOException;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
 
 
+/**
+ * 用户优惠券消费者
+ *
+ * @author skyline
+ */
 @Slf4j
 @Component
 public class UserCouponConsumer {
@@ -26,23 +32,21 @@ public class UserCouponConsumer {
     }
 
     @RabbitHandler
-    @RabbitListener(queues = Queues.DELAY_QUEUE)
+    @RabbitListener(queues = Queues.DELAY_USER_COUPON_QUEUE)
+    @DS("db-miniapp")
     public void userCouponHandler(List<Message> messages, Channel channel) {
-        log.info("userCouponHandler收到:{}", messages);
-
-        final List<String> messageList = messages.stream()
-                .map(each -> new String(each.getBody()))
-                .toList();
-        log.info("[onMessage] size:{}", messages.size());
-
         try {
-            for (Message message : messages) {
-                // todo 业务逻辑处理
-                log.info("消息:{}", new String(message.getBody()));
+            List<UserCoupon> userCouponList = messages.stream()
+                    .map(item -> JSONObject.parseObject(new String(item.getBody()), UserCoupon.class)).toList();
 
+            // 存入 t_user_coupon
+            userCouponService.saveBatch(userCouponList);
 
-                channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
-                log.info("批次处理完毕.........");
+            // todo 推送用户收到优惠券
+
+            // 消息逐个ack
+            for (Message message : messages) {
+                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
             }
         } catch (IOException e) {
             e.printStackTrace();