|
|
@@ -6,15 +6,25 @@ import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
+import shop.alien.entity.store.StoreBookingTable;
|
|
|
+import shop.alien.entity.store.StoreInfo;
|
|
|
import shop.alien.entity.store.UserReservation;
|
|
|
+import shop.alien.entity.store.UserReservationTable;
|
|
|
import shop.alien.entity.store.UserReservationOrder;
|
|
|
import shop.alien.entity.store.vo.StoreReservationListVo;
|
|
|
import shop.alien.mapper.StoreReservationMapper;
|
|
|
+import shop.alien.mapper.UserReservationTableMapper;
|
|
|
import shop.alien.store.config.BaseRedisService;
|
|
|
import shop.alien.store.listener.RedisKeyExpirationHandler;
|
|
|
+import shop.alien.store.service.StoreBookingTableService;
|
|
|
+import shop.alien.store.service.StoreInfoService;
|
|
|
import shop.alien.store.service.StoreReservationService;
|
|
|
import shop.alien.store.service.UserReservationOrderService;
|
|
|
+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.mapper.LifeNoticeMapper;
|
|
|
import javax.annotation.PostConstruct;
|
|
|
|
|
|
import java.text.ParseException;
|
|
|
@@ -22,6 +32,7 @@ import java.text.SimpleDateFormat;
|
|
|
import java.util.Calendar;
|
|
|
import java.util.Date;
|
|
|
import java.util.List;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
* 商家端预约管理 服务实现类
|
|
|
@@ -39,6 +50,11 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
|
|
|
private final BaseRedisService baseRedisService;
|
|
|
private final RedisKeyExpirationHandler expirationHandler;
|
|
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+ private final AliSms aliSms;
|
|
|
+ private final StoreInfoService storeInfoService;
|
|
|
+ private final StoreBookingTableService storeBookingTableService;
|
|
|
+ private final UserReservationTableMapper userReservationTableMapper;
|
|
|
+ private final LifeNoticeMapper lifeNoticeMapper;
|
|
|
|
|
|
/** 预约状态:已取消 */
|
|
|
private static final int STATUS_CANCELLED = 3;
|
|
|
@@ -68,8 +84,8 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public boolean cancelReservationByStore(Integer reservationId) {
|
|
|
- log.info("StoreReservationServiceImpl.cancelReservationByStore?reservationId={}", reservationId);
|
|
|
+ public boolean cancelReservationByStore(Integer reservationId, String cancelReason) {
|
|
|
+ log.info("StoreReservationServiceImpl.cancelReservationByStore?reservationId={}&cancelReason={}", reservationId, cancelReason);
|
|
|
|
|
|
if (reservationId == null) {
|
|
|
throw new RuntimeException("预约ID不能为空");
|
|
|
@@ -86,6 +102,15 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
|
|
|
throw new RuntimeException("预约已取消,不能重复取消");
|
|
|
}
|
|
|
|
|
|
+ // 校验取消原因(如果提供,限30字)
|
|
|
+ if (cancelReason != null && cancelReason.length() > 30) {
|
|
|
+ throw new RuntimeException("取消原因不能超过30字");
|
|
|
+ }
|
|
|
+ // 如果没有提供取消原因,使用默认值
|
|
|
+ if (cancelReason == null || cancelReason.trim().isEmpty()) {
|
|
|
+ cancelReason = "商家取消";
|
|
|
+ }
|
|
|
+
|
|
|
// 查询关联的订单信息
|
|
|
LambdaQueryWrapper<UserReservationOrder> orderWrapper = new LambdaQueryWrapper<>();
|
|
|
orderWrapper.eq(UserReservationOrder::getReservationId, reservationId);
|
|
|
@@ -99,6 +124,9 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
|
|
|
throw new RuntimeException("更新预约状态失败");
|
|
|
}
|
|
|
log.info("商家端取消预约成功(无订单),reservationId={}", reservationId);
|
|
|
+
|
|
|
+ // 发送短信通知
|
|
|
+ sendCancelReservationSms(reservation, cancelReason);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@@ -123,6 +151,9 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
|
|
|
}
|
|
|
|
|
|
log.info("商家端取消预约成功(免费订单),reservationId={}, orderId={}", reservationId, order.getId());
|
|
|
+
|
|
|
+ // 发送短信通知
|
|
|
+ sendCancelReservationSms(reservation, cancelReason);
|
|
|
return true;
|
|
|
} else if (orderCostType == 1) {
|
|
|
// 付费订单:功能预留(暂不更新状态,等待后续实现退款逻辑)
|
|
|
@@ -132,6 +163,138 @@ public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMap
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 发送商家取消预约短信通知
|
|
|
+ */
|
|
|
+ private void sendCancelReservationSms(UserReservation reservation, String cancelReason) {
|
|
|
+ try {
|
|
|
+ // 获取用户手机号
|
|
|
+ String phone = reservation.getReservationUserPhone();
|
|
|
+ if (phone == null || phone.trim().isEmpty()) {
|
|
|
+ log.warn("预约人电话为空,无法发送短信通知,reservationId={}", reservation.getId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询店铺信息
|
|
|
+ StoreInfo storeInfo = storeInfoService.getById(reservation.getStoreId());
|
|
|
+ String storeName = storeInfo != null ? storeInfo.getStoreName() : "未知店铺";
|
|
|
+ if (storeName == null || storeName.trim().isEmpty()) {
|
|
|
+ storeName = "未知店铺";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询桌号信息
|
|
|
+ LambdaQueryWrapper<UserReservationTable> tableWrapper = new LambdaQueryWrapper<>();
|
|
|
+ tableWrapper.eq(UserReservationTable::getReservationId, reservation.getId())
|
|
|
+ .eq(UserReservationTable::getDeleteFlag, 0)
|
|
|
+ .orderByAsc(UserReservationTable::getSort);
|
|
|
+ List<UserReservationTable> reservationTables = userReservationTableMapper.selectList(tableWrapper);
|
|
|
+
|
|
|
+ String tableNumber = "未知桌号";
|
|
|
+ if (reservationTables != null && !reservationTables.isEmpty()) {
|
|
|
+ // 获取所有桌号,用逗号分隔
|
|
|
+ List<String> tableNumbers = reservationTables.stream()
|
|
|
+ .map(rt -> {
|
|
|
+ StoreBookingTable table = storeBookingTableService.getById(rt.getTableId());
|
|
|
+ return table != null && table.getTableNumber() != null ? table.getTableNumber() : null;
|
|
|
+ })
|
|
|
+ .filter(tn -> tn != null && !tn.trim().isEmpty())
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ if (!tableNumbers.isEmpty()) {
|
|
|
+ tableNumber = String.join(",", tableNumbers);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 格式化预约时间:日期 + 时间(格式:2026-01-01 14:00)
|
|
|
+ String dateTime = formatReservationDateTime(reservation);
|
|
|
+
|
|
|
+ // 构建通知内容(和短信内容一样)
|
|
|
+ // 短信模板:您在${dateTime},预订了${storeName},的${number}桌位,已被商家取消,取消原因:${info},请您重新预订
|
|
|
+ String noticeMessage = String.format("您在%s,预订了%s,的%s桌位,已被商家取消,取消原因:%s,请您重新预订",
|
|
|
+ dateTime, storeName, tableNumber, cancelReason);
|
|
|
+
|
|
|
+ // 调用发送短信接口
|
|
|
+ Integer smsResult = aliSms.sendCancelReservationSms(phone, dateTime, storeName, tableNumber, cancelReason);
|
|
|
+ if (smsResult != null && smsResult == 1) {
|
|
|
+ log.info("商家取消预约短信发送成功,reservationId={}, phone={}", reservation.getId(), phone);
|
|
|
+ } else {
|
|
|
+ log.warn("商家取消预约短信发送失败,reservationId={}, phone={}", reservation.getId(), phone);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发送通知(和短信内容一样)
|
|
|
+ sendCancelReservationNotice(reservation, phone, noticeMessage);
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 短信和通知发送失败不影响取消预约流程,只记录日志
|
|
|
+ log.error("发送商家取消预约短信和通知异常,reservationId={}", reservation.getId(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送商家取消预约通知
|
|
|
+ */
|
|
|
+ private void sendCancelReservationNotice(UserReservation reservation, String phone, String noticeMessage) {
|
|
|
+ try {
|
|
|
+ // 构建receiverId:用户端使用 "user_" + 手机号
|
|
|
+ String receiverId = "user_" + phone;
|
|
|
+
|
|
|
+ // 构建通知内容JSON
|
|
|
+ JSONObject contextJson = new JSONObject();
|
|
|
+ contextJson.put("message", noticeMessage);
|
|
|
+ contextJson.put("reservationId", reservation.getId());
|
|
|
+ contextJson.put("reservationNo", reservation.getReservationNo());
|
|
|
+
|
|
|
+ // 创建通知记录
|
|
|
+ LifeNotice lifeNotice = new LifeNotice();
|
|
|
+ lifeNotice.setSenderId("system");
|
|
|
+ lifeNotice.setReceiverId(receiverId);
|
|
|
+ lifeNotice.setBusinessId(reservation.getId());
|
|
|
+ lifeNotice.setTitle("订单取消提醒");
|
|
|
+ lifeNotice.setContext(contextJson.toJSONString());
|
|
|
+ lifeNotice.setNoticeType(2); // 2-订单提醒
|
|
|
+ lifeNotice.setIsRead(0);
|
|
|
+
|
|
|
+ // 保存通知到数据库
|
|
|
+ lifeNoticeMapper.insert(lifeNotice);
|
|
|
+
|
|
|
+ log.info("商家取消预约通知发送成功,reservationId={}, receiverId={}", reservation.getId(), receiverId);
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 通知发送失败不影响流程,只记录日志
|
|
|
+ log.error("发送商家取消预约通知异常,reservationId={}, phone={}", reservation.getId(), phone, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化预约时间:日期 + 时间(格式:2026-01-01 14:00)
|
|
|
+ */
|
|
|
+ private String formatReservationDateTime(UserReservation reservation) {
|
|
|
+ try {
|
|
|
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
+
|
|
|
+ String dateStr = "";
|
|
|
+ if (reservation.getReservationDate() != null) {
|
|
|
+ dateStr = dateFormat.format(reservation.getReservationDate());
|
|
|
+ }
|
|
|
+
|
|
|
+ String timeStr = "";
|
|
|
+ if (reservation.getStartTime() != null && !reservation.getStartTime().trim().isEmpty()) {
|
|
|
+ timeStr = reservation.getStartTime();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dateStr.isEmpty() && timeStr.isEmpty()) {
|
|
|
+ return "未知时间";
|
|
|
+ } else if (dateStr.isEmpty()) {
|
|
|
+ return timeStr;
|
|
|
+ } else if (timeStr.isEmpty()) {
|
|
|
+ return dateStr;
|
|
|
+ } else {
|
|
|
+ return dateStr + " " + timeStr;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("格式化预约时间失败,reservationId={}", reservation.getId(), e);
|
|
|
+ return "未知时间";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public boolean deleteReservationByStore(Integer reservationId) {
|
|
|
log.info("StoreReservationServiceImpl.deleteReservationByStore?reservationId={}", reservationId);
|