Bladeren bron

商家端 预订加时 修改

qinxuyang 1 maand geleden
bovenliggende
commit
e74a402e29

+ 1 - 1
alien-store/src/main/java/shop/alien/store/controller/StoreReservationController.java

@@ -120,7 +120,7 @@ public class StoreReservationController {
     @ApiOperation("商家端加时")
     @ApiImplicitParams({
             @ApiImplicitParam(name = "reservationId", value = "预约ID", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "addTimeStart", value = "加时开始时间(HH:mm格式,如:13:00)", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "addTimeStart", value = "加时开始时间(yyyy-MM-dd HH:mm:ss格式,如:2026-03-11 13:00:00)", dataType = "String", paramType = "query", required = true),
             @ApiImplicitParam(name = "addTimeMinutes", value = "加时分钟数(必须大于0)", dataType = "Integer", paramType = "query", required = true)
     })
     @PostMapping("/addTime")

+ 19 - 13
alien-store/src/main/java/shop/alien/store/service/impl/StoreBookingTableServiceImpl.java

@@ -186,16 +186,17 @@ public class StoreBookingTableServiceImpl extends ServiceImpl<StoreBookingTableM
         String tableNumber = table.getTableNumber().trim();
         table.setTableNumber(tableNumber);
         
-        // 检查当前门店下是否已存在相同的桌号(不限制分类
+        // 检查同一分类下是否已存在相同的桌号(不同分类可以同名
         LambdaQueryWrapper<StoreBookingTable> checkWrapper = new LambdaQueryWrapper<>();
         checkWrapper.eq(StoreBookingTable::getStoreId, table.getStoreId())
+                .eq(StoreBookingTable::getCategoryId, table.getCategoryId())
                 .eq(StoreBookingTable::getTableNumber, tableNumber);
         // @TableLogic 会自动过滤已删除的记录(delete_flag=0)
         StoreBookingTable existingTable = this.getOne(checkWrapper);
         if (existingTable != null) {
-            log.warn("新增预订服务桌号失败:当前门店下桌号已存在,storeId={}, tableNumber={}", 
-                    table.getStoreId(), tableNumber);
-            throw new RuntimeException("该桌号已存在不能添加");
+            log.warn("新增预订服务桌号失败:同一分类下桌号已存在,storeId={}, categoryId={}, tableNumber={}", 
+                    table.getStoreId(), table.getCategoryId(), tableNumber);
+            throw new RuntimeException("该分类下桌号已存在不能添加");
         }
         
         table.setCreatedUserId(userId);
@@ -254,13 +255,14 @@ public class StoreBookingTableServiceImpl extends ServiceImpl<StoreBookingTableM
             throw new RuntimeException("本次批量添加的桌号列表中存在重复");
         }
         
-        // 检查当前门店下是否已存在相同的桌号(不限制分类
+        // 检查同一分类下是否已存在相同的桌号(不同分类可以同名
         List<String> tableNumbers = tables.stream()
                 .map(StoreBookingTable::getTableNumber)
                 .collect(Collectors.toList());
         
         LambdaQueryWrapper<StoreBookingTable> checkWrapper = new LambdaQueryWrapper<>();
         checkWrapper.eq(StoreBookingTable::getStoreId, storeId)
+                .eq(StoreBookingTable::getCategoryId, categoryId)
                 .in(StoreBookingTable::getTableNumber, tableNumbers);
         // @TableLogic 会自动过滤已删除的记录(delete_flag=0)
         List<StoreBookingTable> existingTables = this.list(checkWrapper);
@@ -270,12 +272,12 @@ public class StoreBookingTableServiceImpl extends ServiceImpl<StoreBookingTableM
                     .map(StoreBookingTable::getTableNumber)
                     .distinct()
                     .collect(Collectors.toList());
-            log.warn("批量新增预订服务桌号失败:当前门店下桌号已存在,storeId={}, existingNumbers={}", 
-                    storeId, existingNumbers);
+            log.warn("批量新增预订服务桌号失败:同一分类下桌号已存在,storeId={}, categoryId={}, existingNumbers={}", 
+                    storeId, categoryId, existingNumbers);
             if (existingNumbers.size() == 1) {
-                throw new RuntimeException("该桌号已存在不能添加");
+                throw new RuntimeException("该分类下桌号已存在不能添加");
             } else {
-                throw new RuntimeException("以下桌号已存在不能添加:" + String.join("、", existingNumbers));
+                throw new RuntimeException("该分类下以下桌号已存在不能添加:" + String.join("、", existingNumbers));
             }
         }
         
@@ -308,18 +310,22 @@ public class StoreBookingTableServiceImpl extends ServiceImpl<StoreBookingTableM
             String tableNumber = table.getTableNumber().trim();
             table.setTableNumber(tableNumber);
             
-            // 如果桌号有变化,检查当前门店下是否已存在相同的桌号(不限制分类,排除当前记录)
+            // 如果桌号有变化,检查同一分类下是否已存在相同的桌号(不同分类可以同名,排除当前记录)
             if (!tableNumber.equals(existingTable.getTableNumber())) {
+                // 如果分类ID也变化了,使用新的分类ID;否则使用原有的分类ID
+                Integer checkCategoryId = table.getCategoryId() != null ? table.getCategoryId() : existingTable.getCategoryId();
+                
                 LambdaQueryWrapper<StoreBookingTable> wrapper = new LambdaQueryWrapper<>();
                 wrapper.eq(StoreBookingTable::getStoreId, existingTable.getStoreId())
+                        .eq(StoreBookingTable::getCategoryId, checkCategoryId)
                         .eq(StoreBookingTable::getTableNumber, tableNumber)
                         .ne(StoreBookingTable::getId, table.getId());
                 // @TableLogic 会自动过滤已删除的记录(delete_flag=0)
                 StoreBookingTable duplicateTable = this.getOne(wrapper);
                 if (duplicateTable != null) {
-                    log.warn("更新预订服务桌号失败:当前门店下桌号已存在,storeId={}, tableNumber={}, id={}", 
-                            existingTable.getStoreId(), tableNumber, table.getId());
-                    throw new RuntimeException("该桌号已存在不能编辑");
+                    log.warn("更新预订服务桌号失败:同一分类下桌号已存在,storeId={}, categoryId={}, tableNumber={}, id={}", 
+                            existingTable.getStoreId(), checkCategoryId, tableNumber, table.getId());
+                    throw new RuntimeException("该分类下桌号已存在不能编辑");
                 }
             }
         }

+ 167 - 33
alien-store/src/main/java/shop/alien/store/service/impl/StoreReservationServiceImpl.java

@@ -26,7 +26,9 @@ import shop.alien.store.util.ali.AliSms;
 import com.alibaba.fastjson.JSONObject;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import shop.alien.entity.store.LifeNotice;
+import shop.alien.entity.store.LifeUser;
 import shop.alien.mapper.LifeNoticeMapper;
+import shop.alien.mapper.LifeUserMapper;
 import org.springframework.util.StringUtils;
 import javax.annotation.PostConstruct;
 import java.text.ParseException;
@@ -57,6 +59,7 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
     private final StoreBookingTableService storeBookingTableService;
     private final UserReservationTableMapper userReservationTableMapper;
     private final LifeNoticeMapper lifeNoticeMapper;
+    private final LifeUserMapper lifeUserMapper;
     private final MerchantPaymentStrategyFactory merchantPaymentStrategyFactory;
 
     /** 预约状态:已取消 */
@@ -261,7 +264,7 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
             }
             
             // 发送通知(和短信内容一样)
-            sendCancelReservationNotice(reservation, phone, noticeMessage);
+            sendCancelReservationNotice(reservation, noticeMessage);
         } catch (Exception e) {
             // 短信和通知发送失败不影响取消预约流程,只记录日志
             log.error("发送商家取消预约短信和通知异常,reservationId={}", reservation.getId(), e);
@@ -271,10 +274,31 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
     /**
      * 发送商家取消预约通知
      */
-    private void sendCancelReservationNotice(UserReservation reservation, String phone, String noticeMessage) {
+    private void sendCancelReservationNotice(UserReservation reservation, String noticeMessage) {
         try {
+            // 通过 userId 查询 life_user 表获取手机号
+            Integer userId = reservation.getUserId();
+            if (userId == null) {
+                log.warn("预约用户ID为空,无法发送通知,reservationId={}", reservation.getId());
+                return;
+            }
+            
+            // 查询用户信息
+            LifeUser lifeUser = lifeUserMapper.selectById(userId);
+            if (lifeUser == null) {
+                log.warn("未找到用户信息,无法发送通知,reservationId={}, userId={}", reservation.getId(), userId);
+                return;
+            }
+            
+            // 获取用户手机号
+            String userPhone = lifeUser.getUserPhone();
+            if (userPhone == null || userPhone.trim().isEmpty()) {
+                log.warn("用户手机号为空,无法发送通知,reservationId={}, userId={}", reservation.getId(), userId);
+                return;
+            }
+            
             // 构建receiverId:用户端使用 "user_" + 手机号
-            String receiverId = "user_" + phone;
+            String receiverId = "user_" + userPhone.trim();
             
             // 构建通知内容JSON
             JSONObject contextJson = new JSONObject();
@@ -295,10 +319,12 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
             // 保存通知到数据库
             lifeNoticeMapper.insert(lifeNotice);
             
-            log.info("商家取消预约通知发送成功,reservationId={}, receiverId={}", reservation.getId(), receiverId);
+            log.info("商家取消预约通知发送成功,reservationId={}, userId={}, receiverId={}", 
+                    reservation.getId(), userId, receiverId);
         } catch (Exception e) {
             // 通知发送失败不影响流程,只记录日志
-            log.error("发送商家取消预约通知异常,reservationId={}, phone={}", reservation.getId(), phone, e);
+            log.error("发送商家取消预约通知异常,reservationId={}, userId={}", 
+                    reservation.getId(), reservation.getUserId(), e);
         }
     }
 
@@ -329,7 +355,7 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
             }
 
             // 发送通知
-            sendDepositRefundNotice(reservation, userPhone, message);
+            sendDepositRefundNotice(reservation, message);
         } catch (Exception e) {
             log.error("发送订金退款短信和通知异常,reservationId={}", reservation.getId(), e);
             throw e;
@@ -340,13 +366,33 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
      * 发送订金退款通知
      *
      * @param reservation 预约信息
-     * @param phone 用户手机号
      * @param noticeMessage 通知内容
      */
-    private void sendDepositRefundNotice(UserReservation reservation, String phone, String noticeMessage) {
+    private void sendDepositRefundNotice(UserReservation reservation, String noticeMessage) {
         try {
+            // 通过 userId 查询 life_user 表获取手机号
+            Integer userId = reservation.getUserId();
+            if (userId == null) {
+                log.warn("预约用户ID为空,无法发送通知,reservationId={}", reservation.getId());
+                return;
+            }
+            
+            // 查询用户信息
+            LifeUser lifeUser = lifeUserMapper.selectById(userId);
+            if (lifeUser == null) {
+                log.warn("未找到用户信息,无法发送通知,reservationId={}, userId={}", reservation.getId(), userId);
+                return;
+            }
+            
+            // 获取用户手机号
+            String userPhone = lifeUser.getUserPhone();
+            if (userPhone == null || userPhone.trim().isEmpty()) {
+                log.warn("用户手机号为空,无法发送通知,reservationId={}, userId={}", reservation.getId(), userId);
+                return;
+            }
+            
             // 构建receiverId:用户端使用 "user_" + 手机号
-            String receiverId = "user_" + phone;
+            String receiverId = "user_" + userPhone.trim();
             
             // 构建通知内容JSON
             JSONObject contextJson = new JSONObject();
@@ -367,9 +413,11 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
             // 保存通知到数据库
             lifeNoticeMapper.insert(lifeNotice);
             
-            log.info("核销后订金退款通知发送成功,reservationId={}, receiverId={}", reservation.getId(), receiverId);
+            log.info("核销后订金退款通知发送成功,reservationId={}, userId={}, receiverId={}", 
+                    reservation.getId(), userId, receiverId);
         } catch (Exception e) {
-            log.error("发送订金退款通知异常,reservationId={}, phone={}", reservation.getId(), phone, e);
+            log.error("发送订金退款通知异常,reservationId={}, userId={}", 
+                    reservation.getId(), reservation.getUserId(), e);
             throw e;
         }
     }
@@ -483,9 +531,10 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
             throw new RuntimeException("加时分钟数必须大于0");
         }
 
-        // 验证时间格式 HH:mm
-        if (!addTimeStart.matches("^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$")) {
-            throw new RuntimeException("加时开始时间格式错误,应为HH:mm格式");
+        // 验证并标准化时间格式(支持 yyyy-MM-dd HH:mm:ss 和 yyyy-MM-dd HH:mm)
+        String normalizedAddTimeStart = normalizeDateTime(addTimeStart);
+        if (normalizedAddTimeStart == null) {
+            throw new RuntimeException("加时开始时间格式错误,应为yyyy-MM-dd HH:mm:ss或yyyy-MM-dd HH:mm格式,如:2026-03-11 13:00:00 或 2026-03-11 13:00");
         }
 
         // 查询预约信息
@@ -497,20 +546,31 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
         // 保存原结束时间用于日志
         String oldEndTime = reservation.getEndTime();
 
+        // 标准化预约的结束时间
+        String normalizedEndTime = normalizeDateTime(reservation.getEndTime());
+        if (normalizedEndTime == null) {
+            throw new RuntimeException("预约结束时间格式错误,无法进行加时操作");
+        }
+
         // 计算新的结束时间
-        // 如果加时开始时间在当前预订结束时间之内,新的结束时间 = 当前结束时间 + 加时分钟数
-        // 如果加时开始时间超过了当前预订结束时间,新的结束时间 = 加时开始时间 + 加时分钟数
+        // 无论加时开始时间是否超过预订结束时间,都从预订结束时间开始累加
+        // 如果加时开始时间 > 预订结束时间,中间的空挡时间也要累加进去
+        // 例如:预订结束时间 12:00,加时开始时间 13:00,加时30分钟
+        // 新结束时间 = 加时开始时间 + 加时分钟数 = 13:00 + 30分钟 = 13:30
+        // (这已经包含了空挡时间,因为是从加时开始时间开始计算的)
         String newEndTime;
-        if (compareTime(addTimeStart, reservation.getEndTime()) <= 0) {
-            // 加时开始时间在预订结束时间之内,从当前结束时间开始加时
-            newEndTime = calculateNewEndTime(reservation.getEndTime(), addTimeMinutes);
+        int timeComparison = compareTime(normalizedAddTimeStart, normalizedEndTime);
+        if (timeComparison <= 0) {
+            // 加时开始时间 <= 预订结束时间:新结束时间 = 预订结束时间 + 加时分钟数
+            newEndTime = calculateNewEndTime(normalizedEndTime, addTimeMinutes);
         } else {
-            // 加时开始时间超过了预订结束时间,从加时开始时间开始加时
-            newEndTime = calculateNewEndTime(addTimeStart, addTimeMinutes);
+            // 加时开始时间 > 预订结束时间:新结束时间 = 加时开始时间 + 加时分钟数
+            // (这已经包含了中间的空挡时间,因为是从加时开始时间开始累加的)
+            newEndTime = calculateNewEndTime(normalizedAddTimeStart, addTimeMinutes);
         }
 
         // 校验:不能超过下一个已确认预约的开始时间
-        validateAddTimeNotExceedNextReservation(reservation, addTimeStart, newEndTime);
+        validateAddTimeNotExceedNextReservation(reservation, normalizedAddTimeStart, newEndTime);
 
         // 更新预约结束时间
         reservation.setEndTime(newEndTime);
@@ -560,6 +620,13 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
             throw new RuntimeException("预约已过期,无法核销");
         }
 
+        if(order.getOrderCostType() == 1){
+            //调用退款
+            MerchantPaymentStrategy strategy = merchantPaymentStrategyFactory.getStrategy(order.getPaymentMethod());
+            strategy.refund(order.getStoreId(), order.getOutTradeNo(), order.getDepositAmount().toString(), "已扫码到店商家退款");
+
+        }
+
         // 更新预约状态为已到店(status = 2)
         reservation.setStatus(2); // 已到店
         reservation.setActualArrivalTime(new Date());
@@ -575,10 +642,6 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
             throw new RuntimeException("更新订单状态失败");
         }
 
-        //调用退款
-        MerchantPaymentStrategy strategy = merchantPaymentStrategyFactory.getStrategy(order.getPaymentMethod());
-        strategy.refund(order.getStoreId(), order.getOutTradeNo(), order.getDepositAmount().toString(), "已扫码到店商家退款");
-
         // 核销成功后,发送订金退款短信和通知
         try {
             sendDepositRefundSmsAndNotice(reservation, order);
@@ -588,6 +651,7 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
             // 短信和通知发送失败不影响核销流程,只记录日志
         }
 
+
         log.info("核销预约订单成功,verificationCode={}, reservationId={}, orderId={}",
                 verificationCode, reservation.getId(), order.getId());
         return true;
@@ -687,13 +751,57 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
     }
 
     /**
-     * 比较两个时间字符串(HH:mm格式)
+     * 标准化时间格式:将 yyyy-MM-dd HH:mm 或 yyyy-MM-dd HH:mm:ss 统一转换为 yyyy-MM-dd HH:mm:ss
+     * @param dateTime 时间字符串
+     * @return 标准化后的时间字符串(yyyy-MM-dd HH:mm:ss格式),如果格式不正确返回null
+     */
+    private String normalizeDateTime(String dateTime) {
+        if (dateTime == null || dateTime.trim().isEmpty()) {
+            return null;
+        }
+        
+        String trimmed = dateTime.trim();
+        
+        // 尝试解析 yyyy-MM-dd HH:mm:ss 格式
+        SimpleDateFormat formatWithSeconds = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        formatWithSeconds.setLenient(false);
+        try {
+            Date date = formatWithSeconds.parse(trimmed);
+            return formatWithSeconds.format(date);
+        } catch (ParseException e) {
+            // 继续尝试其他格式
+        }
+        
+        // 尝试解析 yyyy-MM-dd HH:mm 格式
+        SimpleDateFormat formatWithoutSeconds = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+        formatWithoutSeconds.setLenient(false);
+        try {
+            Date date = formatWithoutSeconds.parse(trimmed);
+            // 转换为 yyyy-MM-dd HH:mm:ss 格式(秒数补0)
+            return formatWithSeconds.format(date);
+        } catch (ParseException e) {
+            // 格式不正确
+            return null;
+        }
+    }
+
+    /**
+     * 比较两个时间字符串(支持 yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd HH:mm 格式)
      */
     private int compareTime(String time1, String time2) {
         try {
-            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
-            Date date1 = sdf.parse(time1);
-            Date date2 = sdf.parse(time2);
+            // 先标准化时间格式
+            String normalizedTime1 = normalizeDateTime(time1);
+            String normalizedTime2 = normalizeDateTime(time2);
+            
+            if (normalizedTime1 == null || normalizedTime2 == null) {
+                throw new RuntimeException("时间格式不正确,无法比较");
+            }
+            
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            sdf.setLenient(false);
+            Date date1 = sdf.parse(normalizedTime1);
+            Date date2 = sdf.parse(normalizedTime2);
             return date1.compareTo(date2);
         } catch (ParseException e) {
             log.error("比较时间失败,time1={}, time2={}", time1, time2, e);
@@ -703,17 +811,22 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
 
     /**
      * 计算新的结束时间:加时开始时间 + 加时分钟数
+     * 返回格式:yyyy-MM-dd HH:mm(不含秒)
      */
     private String calculateNewEndTime(String addTimeStart, Integer addTimeMinutes) {
         try {
-            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
-            Date startDate = sdf.parse(addTimeStart);
+            // 解析输入时间(支持 yyyy-MM-dd HH:mm:ss 格式)
+            SimpleDateFormat parseFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            parseFormat.setLenient(false);
+            Date startDate = parseFormat.parse(addTimeStart.trim());
 
             Calendar calendar = Calendar.getInstance();
             calendar.setTime(startDate);
             calendar.add(Calendar.MINUTE, addTimeMinutes);
 
-            return sdf.format(calendar.getTime());
+            // 返回格式:yyyy-MM-dd HH:mm(不含秒)
+            SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+            return outputFormat.format(calendar.getTime());
         } catch (ParseException e) {
             log.error("计算新的结束时间失败,addTimeStart={}, addTimeMinutes={}", addTimeStart, addTimeMinutes, e);
             throw new RuntimeException("时间计算失败:" + e.getMessage());
@@ -721,6 +834,27 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
     }
 
     /**
+     * 计算两个时间之间的分钟差(time2 - time1)
+     * @param time1 开始时间(yyyy-MM-dd HH:mm:ss格式)
+     * @param time2 结束时间(yyyy-MM-dd HH:mm:ss格式)
+     * @return 分钟差,如果time2 < time1则返回负数
+     */
+    private long calculateTimeGapInMinutes(String time1, String time2) {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            sdf.setLenient(false);
+            Date date1 = sdf.parse(time1.trim());
+            Date date2 = sdf.parse(time2.trim());
+            
+            long diffInMillis = date2.getTime() - date1.getTime();
+            return diffInMillis / (1000 * 60); // 转换为分钟
+        } catch (ParseException e) {
+            log.error("计算时间差失败,time1={}, time2={}", time1, time2, e);
+            throw new RuntimeException("计算时间差失败:" + e.getMessage());
+        }
+    }
+
+    /**
      * 校验预约是否过期
      * 判断当前时间是否超过预约日期 + 结束时间
      */