|
|
@@ -0,0 +1,718 @@
|
|
|
+package shop.alien.storeplatform.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.ObjectUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import shop.alien.config.redis.BaseRedisService;
|
|
|
+import shop.alien.entity.result.R;
|
|
|
+import shop.alien.entity.store.*;
|
|
|
+import shop.alien.mapper.*;
|
|
|
+import shop.alien.storeplatform.service.CouponManageService;
|
|
|
+import shop.alien.util.common.constant.OrderStatusEnum;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.time.*;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.time.format.TextStyle;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * web端商户优惠券管理 Service实现
|
|
|
+ *
|
|
|
+ * @author ssk
|
|
|
+ * @since 2025-11-13
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class CouponManageServiceImpl implements CouponManageService {
|
|
|
+
|
|
|
+ private final OrderCouponMiddleMapper orderCouponMiddleMapper;
|
|
|
+ private final LifeUserOrderMapper lifeUserOrderMapper;
|
|
|
+ private final LifeCouponMapper lifeCouponMapper;
|
|
|
+ private final LifeGroupBuyMainMapper lifeGroupBuyMainMapper;
|
|
|
+ private final EssentialHolidayComparisonMapper essentialHolidayComparisonMapper;
|
|
|
+ private final BaseRedisService baseRedisService;
|
|
|
+ private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
|
|
|
+ private final StoreInfoMapper storeInfoMapper;
|
|
|
+ private final StoreIncomeDetailsRecordMapper storeIncomeDetailsRecordMapper;
|
|
|
+ private final StoreUserMapper storeUserMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 核销订单前效验
|
|
|
+ *
|
|
|
+ * @param orderCode 劵code
|
|
|
+ * @return 效验结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public R<String> verifyOrder(String orderCode) {
|
|
|
+ log.info("CouponManageServiceImpl.verifyOrder - 开始效验订单: orderCode={}", orderCode);
|
|
|
+
|
|
|
+ // 1. 查询订单券中间表
|
|
|
+ OrderCouponMiddle orderCouponMiddle = orderCouponMiddleMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<OrderCouponMiddle>()
|
|
|
+ .eq(OrderCouponMiddle::getCouponCode, orderCode)
|
|
|
+ );
|
|
|
+
|
|
|
+ // 2. 检查订单券是否存在且状态为待使用
|
|
|
+ if (orderCouponMiddle != null && orderCouponMiddle.getStatus() == 1) {
|
|
|
+ // 3. 查询用户订单
|
|
|
+ LifeUserOrder lifeUserOrder = lifeUserOrderMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<LifeUserOrder>()
|
|
|
+ .eq(LifeUserOrder::getId, orderCouponMiddle.getOrderId())
|
|
|
+ );
|
|
|
+
|
|
|
+ if (lifeUserOrder == null) {
|
|
|
+ log.warn("CouponManageServiceImpl.verifyOrder - 订单不存在: orderId={}", orderCouponMiddle.getOrderId());
|
|
|
+ return R.fail("订单不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 根据券类型进行不同的验证
|
|
|
+ // 类型为:1 代金券订单
|
|
|
+ if (lifeUserOrder.getCouponType() == 1) {
|
|
|
+ log.info("CouponManageServiceImpl.verifyOrder - 代金券验证: couponId={}", orderCouponMiddle.getCouponId());
|
|
|
+ return verifyCoupon(orderCouponMiddle);
|
|
|
+ }
|
|
|
+ // 类型为:2 团购订单
|
|
|
+ else if (lifeUserOrder.getCouponType() == 2) {
|
|
|
+ log.info("CouponManageServiceImpl.verifyOrder - 团购券验证: couponId={}", orderCouponMiddle.getCouponId());
|
|
|
+ return verifyGroupBuy(orderCouponMiddle);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ log.warn("CouponManageServiceImpl.verifyOrder - 该劵不是待使用状态: orderCode={}", orderCode);
|
|
|
+ return R.fail("该劵不是待使用状态");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 代金券验证
|
|
|
+ *
|
|
|
+ * @param orderCouponMiddle 订单券中间表
|
|
|
+ * @return 验证结果
|
|
|
+ */
|
|
|
+ private R<String> verifyCoupon(OrderCouponMiddle orderCouponMiddle) {
|
|
|
+ log.info("CouponManageServiceImpl.verifyCoupon - 开始代金券验证: couponId={}", orderCouponMiddle.getCouponId());
|
|
|
+
|
|
|
+ // 1. 查询代金券信息
|
|
|
+ LifeCoupon lifeCoupon = lifeCouponMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<LifeCoupon>()
|
|
|
+ .eq(LifeCoupon::getId, orderCouponMiddle.getCouponId())
|
|
|
+ );
|
|
|
+
|
|
|
+ if (lifeCoupon == null) {
|
|
|
+ return R.fail("代金券不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 验证有效期
|
|
|
+ R<String> validityResult = validateCouponValidity(lifeCoupon, orderCouponMiddle);
|
|
|
+ if (!validityResult.isSuccess()) {
|
|
|
+ return validityResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 验证不可用日期
|
|
|
+ R<String> unavailableDateResult = validateCouponUnavailableDate(lifeCoupon);
|
|
|
+ if (!unavailableDateResult.isSuccess()) {
|
|
|
+ return unavailableDateResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 验证使用时间段
|
|
|
+ R<String> useTimeResult = validateCouponUseTime(lifeCoupon);
|
|
|
+ if (!useTimeResult.isSuccess()) {
|
|
|
+ return useTimeResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 验证单次核销数量
|
|
|
+ R<String> singleUseResult = validateCouponSingleUse(lifeCoupon, orderCouponMiddle);
|
|
|
+ if (!singleUseResult.isSuccess()) {
|
|
|
+ return singleUseResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("CouponManageServiceImpl.verifyCoupon - 代金券验证通过: couponId={}", orderCouponMiddle.getCouponId());
|
|
|
+ return R.success("效验通过");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 团购券验证
|
|
|
+ *
|
|
|
+ * @param orderCouponMiddle 订单券中间表
|
|
|
+ * @return 验证结果
|
|
|
+ */
|
|
|
+ private R<String> verifyGroupBuy(OrderCouponMiddle orderCouponMiddle) {
|
|
|
+ log.info("CouponManageServiceImpl.verifyGroupBuy - 开始团购券验证: couponId={}", orderCouponMiddle.getCouponId());
|
|
|
+
|
|
|
+ // 1. 查询团购信息
|
|
|
+ LifeGroupBuyMain lifeGroupBuyMain = lifeGroupBuyMainMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<LifeGroupBuyMain>()
|
|
|
+ .eq(LifeGroupBuyMain::getId, orderCouponMiddle.getCouponId())
|
|
|
+ );
|
|
|
+
|
|
|
+ if (lifeGroupBuyMain == null) {
|
|
|
+ return R.fail("团购券不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 验证有效期
|
|
|
+ R<String> validityResult = validateGroupBuyValidity(lifeGroupBuyMain, orderCouponMiddle);
|
|
|
+ if (!validityResult.isSuccess()) {
|
|
|
+ return validityResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 验证不可用日期
|
|
|
+ R<String> unavailableDateResult = validateGroupBuyUnavailableDate(lifeGroupBuyMain);
|
|
|
+ if (!unavailableDateResult.isSuccess()) {
|
|
|
+ return unavailableDateResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("CouponManageServiceImpl.verifyGroupBuy - 团购券验证通过: couponId={}", orderCouponMiddle.getCouponId());
|
|
|
+ return R.success("效验通过");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证代金券有效期
|
|
|
+ */
|
|
|
+ private R<String> validateCouponValidity(LifeCoupon lifeCoupon, OrderCouponMiddle orderCouponMiddle) {
|
|
|
+ // 类型为:1 指定天数
|
|
|
+ if ("1".equals(lifeCoupon.getExpirationType())) {
|
|
|
+ LifeUserOrder lifeUserOrder = lifeUserOrderMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<LifeUserOrder>()
|
|
|
+ .eq(LifeUserOrder::getId, orderCouponMiddle.getOrderId())
|
|
|
+ );
|
|
|
+
|
|
|
+ if (lifeUserOrder == null || lifeUserOrder.getPayTime() == null) {
|
|
|
+ return R.fail("订单支付时间异常");
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDate localDate = lifeUserOrder.getPayTime().toInstant()
|
|
|
+ .atZone(ZoneId.systemDefault()).toLocalDate();
|
|
|
+ LocalDate validityPeriod = localDate.plusDays(lifeCoupon.getExpirationDate());
|
|
|
+ LocalDate nowDate = LocalDate.now();
|
|
|
+
|
|
|
+ if (nowDate.isAfter(validityPeriod)) {
|
|
|
+ log.warn("代金券已过期: couponId={}, validityPeriod={}, nowDate={}",
|
|
|
+ lifeCoupon.getId(), validityPeriod, nowDate);
|
|
|
+ return R.fail("该劵不在有效期内");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 类型为:2 指定时间段
|
|
|
+ else if ("2".equals(lifeCoupon.getExpirationType())) {
|
|
|
+ if (StringUtils.isEmpty(lifeCoupon.getValidityPeriod())) {
|
|
|
+ return R.fail("有效期配置异常");
|
|
|
+ }
|
|
|
+
|
|
|
+ String[] strings = lifeCoupon.getValidityPeriod().split(",");
|
|
|
+ if (strings.length < 2) {
|
|
|
+ return R.fail("有效期格式异常");
|
|
|
+ }
|
|
|
+
|
|
|
+ long start = Long.parseLong(strings[0]);
|
|
|
+ long end = Long.parseLong(strings[1]);
|
|
|
+ LocalDate startDate = Instant.ofEpochMilli(start)
|
|
|
+ .atZone(ZoneId.systemDefault()).toLocalDate();
|
|
|
+ LocalDate endDate = Instant.ofEpochMilli(end)
|
|
|
+ .atZone(ZoneId.systemDefault()).toLocalDate();
|
|
|
+ LocalDate nowDate = LocalDate.now();
|
|
|
+
|
|
|
+ if (nowDate.isAfter(endDate) || nowDate.isBefore(startDate)) {
|
|
|
+ log.warn("代金券不在有效期内: couponId={}, startDate={}, endDate={}, nowDate={}",
|
|
|
+ lifeCoupon.getId(), startDate, endDate, nowDate);
|
|
|
+ return R.fail("该劵不在有效期内");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return R.success("有效期验证通过");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证代金券不可用日期
|
|
|
+ */
|
|
|
+ private R<String> validateCouponUnavailableDate(LifeCoupon lifeCoupon) {
|
|
|
+ // 限制日期: 1234567;节日id
|
|
|
+ if ("2".equals(lifeCoupon.getUnusedType())) {
|
|
|
+ if (StringUtils.isEmpty(lifeCoupon.getUnavaiLableDate())) {
|
|
|
+ return R.success("无不可用日期限制");
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDate nowDate = LocalDate.now();
|
|
|
+ DayOfWeek dayOfWeek = nowDate.getDayOfWeek();
|
|
|
+ String week = dayOfWeek.getDisplayName(TextStyle.FULL, Locale.CHINA);
|
|
|
+
|
|
|
+ // 验证不可用星期
|
|
|
+ String beforeSemicolon = lifeCoupon.getUnavaiLableDate().split(";")[0];
|
|
|
+ if (StringUtils.isNotEmpty(beforeSemicolon)) {
|
|
|
+ List<String> collectUnavailableDate = Arrays.stream(beforeSemicolon.split(","))
|
|
|
+ .map(String::trim)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ boolean isExist = collectUnavailableDate.stream().anyMatch(s -> s.equals(week));
|
|
|
+ if (isExist) {
|
|
|
+ log.warn("代金券在不可用星期: couponId={}, week={}", lifeCoupon.getId(), week);
|
|
|
+ return R.fail("该劵在不可用日期内");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证不可用节日
|
|
|
+ String[] strings = lifeCoupon.getUnavaiLableDate().split(";");
|
|
|
+ if (strings.length > 1) {
|
|
|
+ String afterSemicolon = strings[1];
|
|
|
+ if (StringUtils.isNotEmpty(afterSemicolon)) {
|
|
|
+ List<String> collectUnavailableDate = Arrays.stream(afterSemicolon.split(","))
|
|
|
+ .map(String::trim)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ List<EssentialHolidayComparison> essentialHolidayComparisons = essentialHolidayComparisonMapper
|
|
|
+ .selectList(new LambdaQueryWrapper<EssentialHolidayComparison>()
|
|
|
+ .in(EssentialHolidayComparison::getId, collectUnavailableDate));
|
|
|
+ boolean isExist = essentialHolidayComparisons.stream()
|
|
|
+ .anyMatch(s -> !nowDate.isBefore(s.getStartTime().toInstant()
|
|
|
+ .atZone(ZoneId.systemDefault()).toLocalDate())
|
|
|
+ && !nowDate.isAfter(s.getEndTime().toInstant()
|
|
|
+ .atZone(ZoneId.systemDefault()).toLocalDate()));
|
|
|
+ if (isExist) {
|
|
|
+ log.warn("代金券在不可用节日: couponId={}, nowDate={}", lifeCoupon.getId(), nowDate);
|
|
|
+ return R.fail("该劵在不可用日期内");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 自定义不可用日期
|
|
|
+ else if ("3".equals(lifeCoupon.getUnusedType())) {
|
|
|
+ if (StringUtils.isNotEmpty(lifeCoupon.getUnavaiLableDate())) {
|
|
|
+ String[] customDate = lifeCoupon.getUnavaiLableDate().split(";");
|
|
|
+ boolean isExist = isCurrentDateInAnyRange(customDate);
|
|
|
+ if (isExist) {
|
|
|
+ log.warn("代金券在自定义不可用日期: couponId={}", lifeCoupon.getId());
|
|
|
+ return R.fail("该劵在不可用日期内");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return R.success("不可用日期验证通过");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证代金券使用时间段
|
|
|
+ */
|
|
|
+ private R<String> validateCouponUseTime(LifeCoupon lifeCoupon) {
|
|
|
+ if (StringUtils.isEmpty(lifeCoupon.getBuyUseStartTime())
|
|
|
+ || StringUtils.isEmpty(lifeCoupon.getBuyUseEndTime())) {
|
|
|
+ return R.success("无使用时间限制");
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ Integer buyUseStartTime = Integer.parseInt(lifeCoupon.getBuyUseStartTime());
|
|
|
+ Integer buyUseEndTime = Integer.parseInt(lifeCoupon.getBuyUseEndTime());
|
|
|
+
|
|
|
+ // 获取当前小时
|
|
|
+ LocalTime now = LocalTime.now();
|
|
|
+ int currentHour = now.getHour();
|
|
|
+
|
|
|
+ // 验证输入的小时是否有效
|
|
|
+ if (buyUseStartTime < 0 || buyUseStartTime > 24 || buyUseEndTime < 0 || buyUseEndTime > 24) {
|
|
|
+ return R.fail("小时必须在0-23之间");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理跨天的情况,例如22点到次日3点
|
|
|
+ if (buyUseStartTime >= buyUseEndTime) {
|
|
|
+ if (currentHour < buyUseStartTime && currentHour > buyUseEndTime) {
|
|
|
+ log.warn("代金券不在使用时间段(跨天): couponId={}, currentHour={}, startTime={}, endTime={}",
|
|
|
+ lifeCoupon.getId(), currentHour, buyUseStartTime, buyUseEndTime);
|
|
|
+ return R.fail("该劵不在有效期内");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (!(currentHour >= buyUseStartTime && currentHour <= buyUseEndTime)) {
|
|
|
+ log.warn("代金券不在使用时间段: couponId={}, currentHour={}, startTime={}, endTime={}",
|
|
|
+ lifeCoupon.getId(), currentHour, buyUseStartTime, buyUseEndTime);
|
|
|
+ return R.fail("该劵不在有效期内");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ log.error("使用时间格式异常: couponId={}, error={}", lifeCoupon.getId(), e.getMessage());
|
|
|
+ return R.fail("使用时间配置异常");
|
|
|
+ }
|
|
|
+
|
|
|
+ return R.success("使用时间验证通过");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证代金券单次核销数量
|
|
|
+ */
|
|
|
+ private R<String> validateCouponSingleUse(LifeCoupon lifeCoupon, OrderCouponMiddle orderCouponMiddle) {
|
|
|
+ if (StringUtils.isEmpty(lifeCoupon.getSingleCanUse()) || "0".equals(lifeCoupon.getSingleCanUse())) {
|
|
|
+ return R.success("无单次核销限制");
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取当天开始时间(00:00:00)
|
|
|
+ LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
|
|
|
+ // 获取当天结束时间(23:59:59.999)
|
|
|
+ LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
|
|
|
+
|
|
|
+ Integer orderNum = orderCouponMiddleMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<OrderCouponMiddle>()
|
|
|
+ .eq(OrderCouponMiddle::getOrderId, orderCouponMiddle.getOrderId())
|
|
|
+ .between(OrderCouponMiddle::getUsedTime, todayStart, todayEnd)
|
|
|
+ .eq(OrderCouponMiddle::getStatus, 2)
|
|
|
+ );
|
|
|
+
|
|
|
+ int maxSingleUse = Integer.parseInt(lifeCoupon.getSingleCanUse());
|
|
|
+ if (orderNum >= maxSingleUse) {
|
|
|
+ log.warn("超过今日单次核销数量: couponId={}, orderNum={}, maxSingleUse={}",
|
|
|
+ lifeCoupon.getId(), orderNum, maxSingleUse);
|
|
|
+ return R.fail("该订单已经超过今日单次核销数量");
|
|
|
+ }
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ log.error("单次核销数量格式异常: couponId={}, error={}", lifeCoupon.getId(), e.getMessage());
|
|
|
+ return R.fail("单次核销配置异常");
|
|
|
+ }
|
|
|
+
|
|
|
+ return R.success("单次核销验证通过");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证团购券有效期
|
|
|
+ */
|
|
|
+ private R<String> validateGroupBuyValidity(LifeGroupBuyMain lifeGroupBuyMain, OrderCouponMiddle orderCouponMiddle) {
|
|
|
+ // 类型为:0 指定天数
|
|
|
+ if (lifeGroupBuyMain.getEffectiveDateType() == 0) {
|
|
|
+ LifeUserOrder lifeUserOrder = lifeUserOrderMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<LifeUserOrder>()
|
|
|
+ .eq(LifeUserOrder::getId, orderCouponMiddle.getOrderId())
|
|
|
+ );
|
|
|
+
|
|
|
+ if (lifeUserOrder == null || lifeUserOrder.getPayTime() == null) {
|
|
|
+ return R.fail("订单支付时间异常");
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDate localDate = lifeUserOrder.getPayTime().toInstant()
|
|
|
+ .atZone(ZoneId.systemDefault()).toLocalDate();
|
|
|
+ LocalDate validityPeriod = localDate.plusDays(Long.parseLong(lifeGroupBuyMain.getEffectiveDateValue()));
|
|
|
+ LocalDate nowDate = LocalDate.now();
|
|
|
+
|
|
|
+ if (nowDate.isAfter(validityPeriod)) {
|
|
|
+ log.warn("团购券已过期: groupBuyId={}, validityPeriod={}, nowDate={}",
|
|
|
+ lifeGroupBuyMain.getId(), validityPeriod, nowDate);
|
|
|
+ return R.fail("该劵不在有效期内");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 类型为:1 指定时间段
|
|
|
+ else if (lifeGroupBuyMain.getEffectiveDateType() == 1) {
|
|
|
+ if (StringUtils.isEmpty(lifeGroupBuyMain.getEffectiveDateValue())) {
|
|
|
+ return R.fail("有效期配置异常");
|
|
|
+ }
|
|
|
+
|
|
|
+ String[] strings = lifeGroupBuyMain.getEffectiveDateValue().split(",");
|
|
|
+ if (strings.length < 2) {
|
|
|
+ return R.fail("有效期格式异常");
|
|
|
+ }
|
|
|
+
|
|
|
+ String startDate = strings[0];
|
|
|
+ String endDate = strings[1];
|
|
|
+ LocalDate localStartDate = LocalDate.parse(startDate);
|
|
|
+ LocalDate localEndDate = LocalDate.parse(endDate);
|
|
|
+ LocalDate nowDate = LocalDate.now();
|
|
|
+
|
|
|
+ if (nowDate.isAfter(localEndDate) || nowDate.isBefore(localStartDate)) {
|
|
|
+ log.warn("团购券不在有效期内: groupBuyId={}, startDate={}, endDate={}, nowDate={}",
|
|
|
+ lifeGroupBuyMain.getId(), localStartDate, localEndDate, nowDate);
|
|
|
+ return R.fail("该劵不在有效期内");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return R.success("有效期验证通过");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证团购券不可用日期
|
|
|
+ */
|
|
|
+ private R<String> validateGroupBuyUnavailableDate(LifeGroupBuyMain lifeGroupBuyMain) {
|
|
|
+ // 限制日期: 1234567;节日id
|
|
|
+ if (lifeGroupBuyMain.getDisableDateType() == 1) {
|
|
|
+ if (StringUtils.isEmpty(lifeGroupBuyMain.getDisableDateValue())) {
|
|
|
+ return R.success("无不可用日期限制");
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDate nowDate = LocalDate.now();
|
|
|
+ DayOfWeek dayOfWeek = nowDate.getDayOfWeek();
|
|
|
+ String week = dayOfWeek.getDisplayName(TextStyle.FULL, Locale.CHINA);
|
|
|
+
|
|
|
+ // 验证不可用星期
|
|
|
+ String beforeSemicolon = lifeGroupBuyMain.getDisableDateValue().split(";")[0];
|
|
|
+ if (StringUtils.isNotEmpty(beforeSemicolon)) {
|
|
|
+ List<String> collectUnavailableDate = Arrays.stream(beforeSemicolon.split(","))
|
|
|
+ .map(String::trim)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ boolean isExist = collectUnavailableDate.stream().anyMatch(s -> s.equals(week));
|
|
|
+ if (isExist) {
|
|
|
+ log.warn("团购券在不可用星期: groupBuyId={}, week={}", lifeGroupBuyMain.getId(), week);
|
|
|
+ return R.fail("该劵在不可用日期内");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证不可用节日
|
|
|
+ String[] strings = lifeGroupBuyMain.getDisableDateValue().split(";");
|
|
|
+ if (strings.length > 1) {
|
|
|
+ String afterSemicolon = strings[1];
|
|
|
+ if (StringUtils.isNotEmpty(afterSemicolon)) {
|
|
|
+ List<String> collectUnavailableDate = Arrays.stream(afterSemicolon.split(","))
|
|
|
+ .map(String::trim)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ List<EssentialHolidayComparison> essentialHolidayComparisons = essentialHolidayComparisonMapper
|
|
|
+ .selectList(new LambdaQueryWrapper<EssentialHolidayComparison>()
|
|
|
+ .in(EssentialHolidayComparison::getId, collectUnavailableDate));
|
|
|
+ boolean isExist = essentialHolidayComparisons.stream()
|
|
|
+ .anyMatch(s -> !nowDate.isBefore(s.getStartTime().toInstant()
|
|
|
+ .atZone(ZoneId.systemDefault()).toLocalDate())
|
|
|
+ && !nowDate.isAfter(s.getEndTime().toInstant()
|
|
|
+ .atZone(ZoneId.systemDefault()).toLocalDate()));
|
|
|
+ if (isExist) {
|
|
|
+ log.warn("团购券在不可用节日: groupBuyId={}, nowDate={}", lifeGroupBuyMain.getId(), nowDate);
|
|
|
+ return R.fail("该劵在不可用日期内");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 自定义不可用日期
|
|
|
+ else if (lifeGroupBuyMain.getDisableDateType() == 2) {
|
|
|
+ if (StringUtils.isNotEmpty(lifeGroupBuyMain.getDisableDateValue())) {
|
|
|
+ String[] customDate = lifeGroupBuyMain.getDisableDateValue().split(";");
|
|
|
+ boolean isExist = isCurrentDateInAnyRange(customDate);
|
|
|
+ if (isExist) {
|
|
|
+ log.warn("团购券在自定义不可用日期: groupBuyId={}", lifeGroupBuyMain.getId());
|
|
|
+ return R.fail("该劵在不可用日期内");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return R.success("不可用日期验证通过");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断当前日期是否在任意日期范围内
|
|
|
+ *
|
|
|
+ * @param dateRanges 日期范围数组
|
|
|
+ * @return true-在范围内,false-不在范围内
|
|
|
+ */
|
|
|
+ private static boolean isCurrentDateInAnyRange(String[] dateRanges) {
|
|
|
+ // 获取当前日期
|
|
|
+ LocalDate currentDate = LocalDate.now();
|
|
|
+ // 定义日期格式
|
|
|
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
+
|
|
|
+ // 遍历数组中的每一对日期范围
|
|
|
+ for (String range : dateRanges) {
|
|
|
+ // 分割每一对日期(开始日期和结束日期)
|
|
|
+ String[] dates = range.split(",");
|
|
|
+ if (dates.length != 2) {
|
|
|
+ // 格式不正确,跳过这一对
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ // 解析开始日期和结束日期
|
|
|
+ LocalDate startDate = LocalDate.parse(dates[0], formatter);
|
|
|
+ LocalDate endDate = LocalDate.parse(dates[1], formatter);
|
|
|
+
|
|
|
+ // 检查当前日期是否在范围内(包括开始和结束日期)
|
|
|
+ if (!currentDate.isBefore(startDate) && !currentDate.isAfter(endDate)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 日期解析错误,跳过这一对
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 没有任何一对日期范围包含当前日期
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 核销订单
|
|
|
+ *
|
|
|
+ * @param storeId 门店ID
|
|
|
+ * @param quanCode 券码
|
|
|
+ * @return 核销结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public Map<String, String> verifyCoupon(String storeId, String quanCode) {
|
|
|
+ log.info("CouponManageServiceImpl.verifyCoupon - 开始核销订单: storeId={}, quanCode={}", storeId, quanCode);
|
|
|
+
|
|
|
+ Map<String, String> resultMap = new HashMap<>();
|
|
|
+ String lockKey = "coupon:use:" + quanCode;
|
|
|
+
|
|
|
+ // 1. Redis分布式锁,防止重复核销
|
|
|
+ String lockValue = baseRedisService.getString(lockKey);
|
|
|
+ if (StringUtils.isNotEmpty(lockValue)) {
|
|
|
+ log.warn("CouponManageServiceImpl.verifyCoupon - 请勿重复核销: quanCode={}", quanCode);
|
|
|
+ resultMap.put("code", "false");
|
|
|
+ resultMap.put("message", "请勿重复核销");
|
|
|
+ return resultMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置锁
|
|
|
+ baseRedisService.setListRight(lockKey, quanCode);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 2. 查询订单券中间表(状态:待使用 或 退款失败)
|
|
|
+ OrderCouponMiddle orderCouponMiddle = orderCouponMiddleMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<OrderCouponMiddle>()
|
|
|
+ .eq(OrderCouponMiddle::getCouponCode, quanCode)
|
|
|
+ .in(OrderCouponMiddle::getStatus,
|
|
|
+ OrderStatusEnum.WAIT_USE.getStatus(),
|
|
|
+ OrderStatusEnum.REFUND_FAILED.getStatus())
|
|
|
+ );
|
|
|
+
|
|
|
+ if (orderCouponMiddle == null) {
|
|
|
+ log.warn("CouponManageServiceImpl.verifyCoupon - 券不存在或状态不正确: quanCode={}", quanCode);
|
|
|
+ resultMap.put("code", "false");
|
|
|
+ resultMap.put("message", "核销失败");
|
|
|
+ return resultMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 更新订单券状态为已使用
|
|
|
+ orderCouponMiddle.setStatus(OrderStatusEnum.USED.getStatus());
|
|
|
+ orderCouponMiddle.setUsedTime(new Date());
|
|
|
+ orderCouponMiddleMapper.updateById(orderCouponMiddle);
|
|
|
+
|
|
|
+ // 4. 查询用户订单
|
|
|
+ LifeUserOrder lifeUserOrder = lifeUserOrderMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<LifeUserOrder>()
|
|
|
+ .eq(LifeUserOrder::getId, orderCouponMiddle.getOrderId())
|
|
|
+ );
|
|
|
+
|
|
|
+ if (lifeUserOrder == null) {
|
|
|
+ log.error("CouponManageServiceImpl.verifyCoupon - 订单不存在: orderId={}", orderCouponMiddle.getOrderId());
|
|
|
+ resultMap.put("code", "false");
|
|
|
+ resultMap.put("message", "订单不存在");
|
|
|
+ return resultMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 检查是否使用了平台优惠券
|
|
|
+ Boolean platFormCoupon = false;
|
|
|
+ String quanId = lifeUserOrder.getQuanId();
|
|
|
+ if (StringUtils.isNotEmpty(quanId)) {
|
|
|
+ LifeDiscountCoupon lifeDiscountCoupon = lifeDiscountCouponMapper.selectById(quanId);
|
|
|
+ if (ObjectUtils.isNotEmpty(lifeDiscountCoupon) && lifeDiscountCoupon.getType() == 3) {
|
|
|
+ platFormCoupon = true;
|
|
|
+ log.info("CouponManageServiceImpl.verifyCoupon - 使用了平台优惠券: quanId={}", quanId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 查询该订单下的所有券
|
|
|
+ List<OrderCouponMiddle> couponMiddleList = orderCouponMiddleMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<OrderCouponMiddle>()
|
|
|
+ .eq(OrderCouponMiddle::getOrderId, lifeUserOrder.getId())
|
|
|
+ );
|
|
|
+
|
|
|
+ // 7. 检查是否所有券都已核销
|
|
|
+ boolean allUsed = couponMiddleList.stream()
|
|
|
+ .allMatch(str -> str.getStatus() == OrderStatusEnum.USED.getStatus());
|
|
|
+
|
|
|
+ // 筛选出未完成的券
|
|
|
+ List<OrderCouponMiddle> unfinishedList = couponMiddleList.stream()
|
|
|
+ .filter(x -> x.getStatus() == OrderStatusEnum.WAIT_USE.getStatus()
|
|
|
+ || x.getStatus() == OrderStatusEnum.REFUND_FAILED.getStatus())
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 8. 如果所有券都已核销,或者没有未完成的券,更新订单状态为已完成
|
|
|
+ if (allUsed || unfinishedList.size() == 0) {
|
|
|
+ lifeUserOrder.setStatus(OrderStatusEnum.COMPLETE.getStatus());
|
|
|
+ lifeUserOrder.setFinishTime(new Date());
|
|
|
+ lifeUserOrder.setCreatedTime(new Date());
|
|
|
+ lifeUserOrderMapper.updateById(lifeUserOrder);
|
|
|
+
|
|
|
+ log.info("CouponManageServiceImpl.verifyCoupon - 订单完成: orderId={}", lifeUserOrder.getId());
|
|
|
+ // 注:发放好友优惠券功能需要通过 Feign 调用 alien-store 服务实现
|
|
|
+ // 或在 web 端实现该功能
|
|
|
+ }
|
|
|
+
|
|
|
+ // 9. 查询门店信息(获取抽成比例)
|
|
|
+ StoreInfo storeInfo = storeInfoMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<StoreInfo>()
|
|
|
+ .eq(StoreInfo::getId, lifeUserOrder.getStoreId())
|
|
|
+ );
|
|
|
+
|
|
|
+ if (storeInfo == null) {
|
|
|
+ log.error("CouponManageServiceImpl.verifyCoupon - 门店不存在: storeId={}", lifeUserOrder.getStoreId());
|
|
|
+ resultMap.put("code", "false");
|
|
|
+ resultMap.put("message", "门店不存在");
|
|
|
+ return resultMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 10. 计算抽成和收入
|
|
|
+ // 抽成比例转换为小数
|
|
|
+ BigDecimal commissionRate = new BigDecimal(storeInfo.getCommissionRate())
|
|
|
+ .divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
|
|
|
+
|
|
|
+ // 计算抽成金额(单位:分)
|
|
|
+ BigDecimal commission = orderCouponMiddle.getPrice()
|
|
|
+ .multiply(new BigDecimal("100"))
|
|
|
+ .multiply(commissionRate)
|
|
|
+ .setScale(2, RoundingMode.HALF_UP);
|
|
|
+
|
|
|
+ // 计算实际收入(单位:分)
|
|
|
+ BigDecimal money;
|
|
|
+ if (platFormCoupon) {
|
|
|
+ // 如果使用了平台优惠券,需要加上平均优惠券价格
|
|
|
+ money = orderCouponMiddle.getPrice()
|
|
|
+ .add(orderCouponMiddle.getAvgDiscountCouponPrice())
|
|
|
+ .multiply(new BigDecimal("100"))
|
|
|
+ .subtract(commission);
|
|
|
+ log.info("CouponManageServiceImpl.verifyCoupon - 使用平台券计算: 原价={}, 优惠={}, 抽成={}, 收入={}",
|
|
|
+ orderCouponMiddle.getPrice(), orderCouponMiddle.getAvgDiscountCouponPrice(),
|
|
|
+ commission, money);
|
|
|
+ } else {
|
|
|
+ money = orderCouponMiddle.getPrice()
|
|
|
+ .multiply(new BigDecimal("100"))
|
|
|
+ .subtract(commission);
|
|
|
+ log.info("CouponManageServiceImpl.verifyCoupon - 普通计算: 原价={}, 抽成={}, 收入={}",
|
|
|
+ orderCouponMiddle.getPrice(), commission, money);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 11. 插入收入明细记录
|
|
|
+ StoreIncomeDetailsRecord record = new StoreIncomeDetailsRecord();
|
|
|
+ record.setStoreId(Integer.parseInt(storeId));
|
|
|
+ record.setUserOrderId(orderCouponMiddle.getId());
|
|
|
+ record.setIncomeType(lifeUserOrder.getCouponType());
|
|
|
+ record.setBusinessId(orderCouponMiddle.getCouponId());
|
|
|
+ record.setCommission(commission.intValue());
|
|
|
+ record.setMoney(money.intValue());
|
|
|
+ storeIncomeDetailsRecordMapper.insert(record);
|
|
|
+
|
|
|
+ log.info("CouponManageServiceImpl.verifyCoupon - 插入收入记录: storeId={}, money={}, commission={}",
|
|
|
+ storeId, money.intValue(), commission.intValue());
|
|
|
+
|
|
|
+ // 12. 更新店铺账户余额
|
|
|
+ UpdateWrapper<StoreUser> updateWrapper = new UpdateWrapper<>();
|
|
|
+ updateWrapper.eq("store_id", storeId);
|
|
|
+ updateWrapper.eq("delete_flag", 0);
|
|
|
+ updateWrapper.setSql("money = money + " + money.intValue());
|
|
|
+ int updateCount = storeUserMapper.update(null, updateWrapper);
|
|
|
+
|
|
|
+ log.info("CouponManageServiceImpl.verifyCoupon - 更新店铺余额: storeId={}, 增加金额={}, 影响行数={}",
|
|
|
+ storeId, money.intValue(), updateCount);
|
|
|
+
|
|
|
+ // 13. 返回成功结果
|
|
|
+ resultMap.put("code", "true");
|
|
|
+ resultMap.put("message", "核销成功");
|
|
|
+
|
|
|
+ log.info("CouponManageServiceImpl.verifyCoupon - 核销完成: quanCode={}", quanCode);
|
|
|
+ return resultMap;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("CouponManageServiceImpl.verifyCoupon - 核销异常: quanCode={}, error={}",
|
|
|
+ quanCode, e.getMessage(), e);
|
|
|
+ resultMap.put("code", "false");
|
|
|
+ resultMap.put("message", "核销失败:" + e.getMessage());
|
|
|
+ return resultMap;
|
|
|
+ } finally {
|
|
|
+ // 14. 释放分布式锁
|
|
|
+ baseRedisService.delete(lockKey);
|
|
|
+ log.debug("CouponManageServiceImpl.verifyCoupon - 释放锁: lockKey={}", lockKey);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|