|
|
@@ -1,12 +1,20 @@
|
|
|
package com.mdframe.forge.business.core.hotel.order.service.impl;
|
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import com.mdframe.forge.business.core.hotel.constant.HotelConfigKeys;
|
|
|
import com.mdframe.forge.business.core.hotel.domain.HotelDish;
|
|
|
+import com.mdframe.forge.business.core.hotel.domain.HotelDishAddition;
|
|
|
+import com.mdframe.forge.business.core.hotel.domain.HotelDishSpecOption;
|
|
|
import com.mdframe.forge.business.core.hotel.domain.HotelRoom;
|
|
|
import com.mdframe.forge.business.core.hotel.domain.HotelRoomStay;
|
|
|
+import com.mdframe.forge.business.core.hotel.mapper.HotelConfigMapper;
|
|
|
+import com.mdframe.forge.business.core.hotel.mapper.HotelDishAdditionMapper;
|
|
|
import com.mdframe.forge.business.core.hotel.mapper.HotelDishMapper;
|
|
|
+import com.mdframe.forge.business.core.hotel.mapper.HotelDishSpecOptionMapper;
|
|
|
import com.mdframe.forge.business.core.hotel.mapper.HotelRoomMapper;
|
|
|
import com.mdframe.forge.business.core.hotel.mapper.HotelRoomStayMapper;
|
|
|
import com.mdframe.forge.business.core.hotel.order.constant.HotelOrderConstants;
|
|
|
@@ -33,10 +41,12 @@ import org.springframework.util.StringUtils;
|
|
|
import java.math.BigDecimal;
|
|
|
import java.text.SimpleDateFormat;
|
|
|
import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
import java.util.Calendar;
|
|
|
import java.util.Date;
|
|
|
import java.util.List;
|
|
|
import java.util.Objects;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
* 酒店订单服务实现。
|
|
|
@@ -55,11 +65,20 @@ public class HotelOrderServiceImpl implements HotelOrderService {
|
|
|
private HotelDishMapper dishMapper;
|
|
|
|
|
|
@Autowired
|
|
|
+ private HotelDishSpecOptionMapper specOptionMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private HotelDishAdditionMapper additionMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
private HotelRoomMapper roomMapper;
|
|
|
|
|
|
@Autowired
|
|
|
private HotelRoomStayMapper roomStayMapper;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private HotelConfigMapper configMapper;
|
|
|
+
|
|
|
/**
|
|
|
* 支付服务(单向依赖:HotelPayService 不注入本服务,不构成循环)。
|
|
|
* 用于拒单/退单流转时自动发起退款。
|
|
|
@@ -146,7 +165,9 @@ public class HotelOrderServiceImpl implements HotelOrderService {
|
|
|
}
|
|
|
order.setContactName(dto.getContactName());
|
|
|
order.setContactPhone(dto.getContactPhone());
|
|
|
- order.setDeliveryFee(dto.getDeliveryFee() != null ? dto.getDeliveryFee() : BigDecimal.ZERO);
|
|
|
+ // 配送费一律取服务端配置,不接受前端传值:本接口免登录,
|
|
|
+ // 前端可传负数抵扣菜金或传巨额篡改订单金额
|
|
|
+ order.setDeliveryFee(resolveDeliveryFee(tenantId));
|
|
|
order.setDeliveryTimeType(dto.getDeliveryTimeType());
|
|
|
// 前端传 HH:mm 字符串(如"18:30"),拼接今天日期转为 Date 存入数据库
|
|
|
if (StringUtils.hasText(dto.getCustomDeliveryTime())) {
|
|
|
@@ -163,7 +184,7 @@ public class HotelOrderServiceImpl implements HotelOrderService {
|
|
|
order.setPayExpireTime(addMinutes(new Date(), HotelOrderConstants.PAY_TIMEOUT_MINUTES));
|
|
|
order.setTenantId(tenantId);
|
|
|
|
|
|
- // 计算总金额(从数据库查菜品单价,不信任前端传的价格)
|
|
|
+ // 计算总金额(从数据库查菜品单价+规格加价+加料加价,不信任前端传的价格)
|
|
|
BigDecimal totalAmount = BigDecimal.ZERO;
|
|
|
for (HotelOrderItem item : dto.getItems()) {
|
|
|
BigDecimal unitPrice = BigDecimal.ZERO;
|
|
|
@@ -173,6 +194,29 @@ public class HotelOrderServiceImpl implements HotelOrderService {
|
|
|
unitPrice = dish.getPrice();
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // 加上规格选项加价(按ID从数据库查 priceExtra)
|
|
|
+ if (StringUtils.hasText(item.getSpecOptionIds())) {
|
|
|
+ List<Long> specOptionIdList = parseIdList(item.getSpecOptionIds());
|
|
|
+ for (Long optId : specOptionIdList) {
|
|
|
+ HotelDishSpecOption opt = specOptionMapper.selectById(optId);
|
|
|
+ if (opt != null && opt.getPriceExtra() != null) {
|
|
|
+ unitPrice = unitPrice.add(opt.getPriceExtra());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加上加料加价(按ID从数据库查 extraPrice)
|
|
|
+ if (StringUtils.hasText(item.getAdditionIds())) {
|
|
|
+ List<Long> additionIdList = parseIdList(item.getAdditionIds());
|
|
|
+ for (Long addId : additionIdList) {
|
|
|
+ HotelDishAddition add = additionMapper.selectById(addId);
|
|
|
+ if (add != null && add.getExtraPrice() != null) {
|
|
|
+ unitPrice = unitPrice.add(add.getExtraPrice());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
int quantity = item.getQuantity() != null ? item.getQuantity() : 0;
|
|
|
BigDecimal subtotal = unitPrice.multiply(new BigDecimal(quantity));
|
|
|
item.setUnitPrice(unitPrice);
|
|
|
@@ -185,9 +229,10 @@ public class HotelOrderServiceImpl implements HotelOrderService {
|
|
|
orderMapper.insert(order);
|
|
|
log.info("创建订单(待支付): {}, 房间: {}, 金额: {}, 超时: {}", order.getOrderNo(), order.getRoomNo(), totalAmount, order.getPayExpireTime());
|
|
|
|
|
|
- // 保存订单明细
|
|
|
+ // 保存订单明细(自定义 XML batchInsert 不走 MP 的 ASSIGN_ID,需手动生成雪花 ID)
|
|
|
List<HotelOrderItem> itemsToInsert = new ArrayList<>();
|
|
|
for (HotelOrderItem item : dto.getItems()) {
|
|
|
+ item.setId(IdWorker.getId());
|
|
|
item.setOrderId(order.getId());
|
|
|
item.setTenantId(tenantId);
|
|
|
item.setDelFlag(0L);
|
|
|
@@ -236,18 +281,23 @@ public class HotelOrderServiceImpl implements HotelOrderService {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- @Transactional(rollbackFor = Exception.class)
|
|
|
public void orderReject(Long id, String reason) {
|
|
|
HotelOrder order = requireOrder(id);
|
|
|
if (order.getStatus() != HotelOrderConstants.STATUS_PLACED) {
|
|
|
throw new BusinessException("只有待接单的订单才能拒单");
|
|
|
}
|
|
|
- order.setStatus(HotelOrderConstants.STATUS_REJECTED);
|
|
|
- order.setRejectReason(reason);
|
|
|
- order.setRefundTime(new Date());
|
|
|
- orderMapper.updateById(order);
|
|
|
- // 拒单触发点:已支付订单自动全额退款(退款失败不影响拒单流转,标记重试)
|
|
|
- tryAutoRefund(order);
|
|
|
+ // 先退款再落终态:退款失败会抛出异常,订单保持"待接单",前台可稍后重试
|
|
|
+ refundBeforeClose(order);
|
|
|
+ Date now = new Date();
|
|
|
+ // 退款成功后 refund_status/pay_status/refund_amount/refund_trade_no 已由退款服务写库,
|
|
|
+ // 此处必须用 UpdateWrapper 精确更新,禁止 updateById(order) 用内存旧值覆盖回去
|
|
|
+ UpdateWrapper<HotelOrder> updateWrapper = new UpdateWrapper<HotelOrder>();
|
|
|
+ updateWrapper.eq("id", order.getId())
|
|
|
+ .set("status", HotelOrderConstants.STATUS_REJECTED)
|
|
|
+ .set("reject_reason", reason)
|
|
|
+ .set("refund_time", now)
|
|
|
+ .set("update_time", now);
|
|
|
+ orderMapper.update(null, updateWrapper);
|
|
|
log.info("订单拒单: {}", order.getOrderNo());
|
|
|
}
|
|
|
|
|
|
@@ -310,29 +360,37 @@ public class HotelOrderServiceImpl implements HotelOrderService {
|
|
|
// ==================== 退单相关 ====================
|
|
|
|
|
|
@Override
|
|
|
- @Transactional(rollbackFor = Exception.class)
|
|
|
public void customerRefund(Long id, String reason) {
|
|
|
HotelOrder order = requireOrder(id);
|
|
|
int status = order.getStatus();
|
|
|
|
|
|
if (status == HotelOrderConstants.STATUS_PENDING_PAY) {
|
|
|
- // 待支付:直接取消订单
|
|
|
- order.setStatus(HotelOrderConstants.STATUS_PAY_TIMEOUT);
|
|
|
- order.setPayStatus(HotelOrderConstants.PAY_STATUS_TIMEOUT);
|
|
|
- order.setRefundReason(reason);
|
|
|
- order.setRefundInitiator(HotelOrderConstants.REFUND_INITIATOR_CUSTOMER);
|
|
|
- order.setRefundTime(new Date());
|
|
|
- orderMapper.updateById(order);
|
|
|
+ // 待支付:无款可退,语义是「取消订单」而不是「退单」。
|
|
|
+ // 置 11已取消(与前台取消、退房联动保持一致),pay_status 保持 0(从未支付),
|
|
|
+ // 不写 refund_time(未发生资金流转);
|
|
|
+ // 旧的 10超时取消 属于错误语义:10 专属于超时任务,人为取消用 10
|
|
|
+ // 会让顾客看到「支付超时已取消」,与前端提示的「订单已取消」不一致
|
|
|
+ UpdateWrapper<HotelOrder> cancelWrapper = new UpdateWrapper<HotelOrder>();
|
|
|
+ cancelWrapper.eq("id", order.getId())
|
|
|
+ .set("status", HotelOrderConstants.STATUS_CANCELLED)
|
|
|
+ .set("refund_reason", reason)
|
|
|
+ .set("refund_initiator", HotelOrderConstants.REFUND_INITIATOR_CUSTOMER)
|
|
|
+ .set("update_time", new Date());
|
|
|
+ orderMapper.update(null, cancelWrapper);
|
|
|
log.info("客户取消待支付订单: {}", order.getOrderNo());
|
|
|
} else if (status == HotelOrderConstants.STATUS_PLACED) {
|
|
|
- // 未接单:直接退单(此时订单已支付,需同步退款)
|
|
|
- order.setStatus(HotelOrderConstants.STATUS_REFUNDED);
|
|
|
- order.setRefundReason(reason);
|
|
|
- order.setRefundInitiator(HotelOrderConstants.REFUND_INITIATOR_CUSTOMER);
|
|
|
- order.setRefundTime(new Date());
|
|
|
- orderMapper.updateById(order);
|
|
|
- // 顾客自助退单触发点:待接单订单已支付,自动全额退款
|
|
|
- tryAutoRefund(order);
|
|
|
+ // 未接单:订单已支付,必须先退款成功才允许置为已退单;
|
|
|
+ // 退款失败抛出异常,订单保持"待接单",避免钱没退回却显示已退单
|
|
|
+ refundBeforeClose(order);
|
|
|
+ Date now = new Date();
|
|
|
+ UpdateWrapper<HotelOrder> updateWrapper = new UpdateWrapper<HotelOrder>();
|
|
|
+ updateWrapper.eq("id", order.getId())
|
|
|
+ .set("status", HotelOrderConstants.STATUS_REFUNDED)
|
|
|
+ .set("refund_reason", reason)
|
|
|
+ .set("refund_initiator", HotelOrderConstants.REFUND_INITIATOR_CUSTOMER)
|
|
|
+ .set("refund_time", now)
|
|
|
+ .set("update_time", now);
|
|
|
+ orderMapper.update(null, updateWrapper);
|
|
|
log.info("客户自助退单: {}", order.getOrderNo());
|
|
|
} else if (status == HotelOrderConstants.STATUS_ACCEPTED) {
|
|
|
// 已接单:进入退单审核
|
|
|
@@ -364,51 +422,82 @@ public class HotelOrderServiceImpl implements HotelOrderService {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- @Transactional(rollbackFor = Exception.class)
|
|
|
public void hotelRefund(Long id, String reason) {
|
|
|
HotelOrder order = requireOrder(id);
|
|
|
- if (order.getStatus() == HotelOrderConstants.STATUS_COMPLETED
|
|
|
- || order.getStatus() == HotelOrderConstants.STATUS_REFUNDED) {
|
|
|
- throw new BusinessException("已完成或已退单的订单不能退单");
|
|
|
- }
|
|
|
- order.setStatus(HotelOrderConstants.STATUS_REFUNDED);
|
|
|
- order.setRefundReason(reason);
|
|
|
- order.setRefundInitiator(HotelOrderConstants.REFUND_INITIATOR_HOTEL_DESK);
|
|
|
- order.setRefundTime(new Date());
|
|
|
- orderMapper.updateById(order);
|
|
|
- // 前台退单触发点:已支付订单(接单~配送中)自动全额退款;
|
|
|
- // 待支付订单无资金可退,tryAutoRefund 内部会跳过
|
|
|
- tryAutoRefund(order);
|
|
|
+ int status = order.getStatus();
|
|
|
+ // 终态订单一律不得再退单:否则已拒单/已取消的订单会被错误改写成已退单
|
|
|
+ if (status == HotelOrderConstants.STATUS_COMPLETED
|
|
|
+ || status == HotelOrderConstants.STATUS_REJECTED
|
|
|
+ || status == HotelOrderConstants.STATUS_REFUNDED
|
|
|
+ || status == HotelOrderConstants.STATUS_PAY_TIMEOUT
|
|
|
+ || status == HotelOrderConstants.STATUS_CANCELLED) {
|
|
|
+ throw new BusinessException("当前订单状态不支持退单");
|
|
|
+ }
|
|
|
+
|
|
|
+ Date now = new Date();
|
|
|
+ if (status == HotelOrderConstants.STATUS_PENDING_PAY) {
|
|
|
+ // 待支付订单无款可退,语义是「取消订单」而不是「退单」:
|
|
|
+ // 置 11已取消,pay_status 保持 0(从未支付),不写 refund_time(未发生资金流转),
|
|
|
+ // 与退房联动 cancelPendingByStayId 的写法保持一致
|
|
|
+ UpdateWrapper<HotelOrder> cancelWrapper = new UpdateWrapper<HotelOrder>();
|
|
|
+ cancelWrapper.eq("id", order.getId())
|
|
|
+ .set("status", HotelOrderConstants.STATUS_CANCELLED)
|
|
|
+ .set("refund_reason", reason)
|
|
|
+ .set("refund_initiator", HotelOrderConstants.REFUND_INITIATOR_HOTEL_DESK)
|
|
|
+ .set("update_time", now);
|
|
|
+ orderMapper.update(null, cancelWrapper);
|
|
|
+ log.info("前台取消待支付订单: {}", order.getOrderNo());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 已支付订单(待接单~配送中、退单待审核)必须先退款成功才允许置为已退单;
|
|
|
+ // 退款失败会抛出异常,订单保持原状态,前台可用手动退款按钮重试
|
|
|
+ refundBeforeClose(order);
|
|
|
+ UpdateWrapper<HotelOrder> updateWrapper = new UpdateWrapper<HotelOrder>();
|
|
|
+ updateWrapper.eq("id", order.getId())
|
|
|
+ .set("status", HotelOrderConstants.STATUS_REFUNDED)
|
|
|
+ .set("refund_reason", reason)
|
|
|
+ .set("refund_initiator", HotelOrderConstants.REFUND_INITIATOR_HOTEL_DESK)
|
|
|
+ .set("refund_time", now)
|
|
|
+ .set("update_time", now);
|
|
|
+ orderMapper.update(null, updateWrapper);
|
|
|
log.info("前台退单: {}", order.getOrderNo());
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- @Transactional(rollbackFor = Exception.class)
|
|
|
public void approveRefund(Long id, String reason) {
|
|
|
HotelOrder order = requireOrder(id);
|
|
|
if (order.getStatus() != HotelOrderConstants.STATUS_REFUND_REQUESTED) {
|
|
|
throw new BusinessException("只有退单待审核状态的订单才能审核");
|
|
|
}
|
|
|
- order.setStatus(HotelOrderConstants.STATUS_REFUNDED);
|
|
|
- order.setRefundTime(new Date());
|
|
|
- orderMapper.updateById(order);
|
|
|
- // 退单审核通过触发点:顾客已支付的订单自动全额退款
|
|
|
- tryAutoRefund(order);
|
|
|
+ // 同意退单必须先把钱退成功,否则订单保持"退单待审核",
|
|
|
+ // 前台可修正问题后重新同意,或用手动退款按钮重试
|
|
|
+ refundBeforeClose(order);
|
|
|
+ Date now = new Date();
|
|
|
+ UpdateWrapper<HotelOrder> updateWrapper = new UpdateWrapper<HotelOrder>();
|
|
|
+ updateWrapper.eq("id", order.getId())
|
|
|
+ .set("status", HotelOrderConstants.STATUS_REFUNDED)
|
|
|
+ .set("refund_time", now)
|
|
|
+ .set("update_time", now);
|
|
|
+ orderMapper.update(null, updateWrapper);
|
|
|
log.info("退单审核通过: {}", order.getOrderNo());
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- @Transactional(rollbackFor = Exception.class)
|
|
|
public void rejectRefund(Long id, String reason) {
|
|
|
HotelOrder order = requireOrder(id);
|
|
|
if (order.getStatus() != HotelOrderConstants.STATUS_REFUND_REQUESTED) {
|
|
|
throw new BusinessException("只有退单待审核状态的订单才能审核");
|
|
|
}
|
|
|
- // 驳回后恢复为已接单状态
|
|
|
- order.setStatus(HotelOrderConstants.STATUS_ACCEPTED);
|
|
|
- order.setRefundReason(null);
|
|
|
- order.setRefundInitiator(null);
|
|
|
- orderMapper.updateById(order);
|
|
|
+ // 驳回后恢复为已接单状态;退单原因/发起方需要真正清空,
|
|
|
+ // updateById 的 NOT_NULL 策略会忽略 null 字段导致清不掉,改用 UpdateWrapper
|
|
|
+ UpdateWrapper<HotelOrder> updateWrapper = new UpdateWrapper<HotelOrder>();
|
|
|
+ updateWrapper.eq("id", order.getId())
|
|
|
+ .set("status", HotelOrderConstants.STATUS_ACCEPTED)
|
|
|
+ .set("refund_reason", null)
|
|
|
+ .set("refund_initiator", null)
|
|
|
+ .set("update_time", new Date());
|
|
|
+ orderMapper.update(null, updateWrapper);
|
|
|
log.info("退单审核驳回: {}", order.getOrderNo());
|
|
|
}
|
|
|
|
|
|
@@ -438,23 +527,33 @@ public class HotelOrderServiceImpl implements HotelOrderService {
|
|
|
// ==================== 私有方法 ====================
|
|
|
|
|
|
/**
|
|
|
- * 已支付订单流转到拒单/退单终态时自动发起全额退款。
|
|
|
+ * 订单流转拒单/退单终态之前的退款动作。
|
|
|
+ * <p>
|
|
|
+ * 与旧的"先改状态再退款并吞掉异常"相反:退款失败必须抛出异常阻断状态流转,
|
|
|
+ * 让订单保持原状态(待接单/退单待审核等),前台可用手动退款按钮重试,
|
|
|
+ * 否则会出现"钱没退回,订单却显示已退单"的资损与客诉。
|
|
|
* <p>
|
|
|
- * 退款失败不阻断状态流转(退款服务内部已将 refund_status 置为2可重试),
|
|
|
- * 前台可通过手动退款按钮重新发起。
|
|
|
+ * 本方法及其调用方均不加 {@code @Transactional}:退款服务需要把失败标记
|
|
|
+ * (refund_status=2)在抛异常后依然落库,外层事务会把它一起回滚掉。
|
|
|
*
|
|
|
- * @param order 订单(状态已更新为拒单/退单)
|
|
|
+ * @param order 订单(状态尚未变更)
|
|
|
*/
|
|
|
- private void tryAutoRefund(HotelOrder order) {
|
|
|
- // 仅已支付订单需要退款;未支付/已退款的直接跳过
|
|
|
+ private void refundBeforeClose(HotelOrder order) {
|
|
|
+ // 仅已支付订单需要退款;未支付/支付超时的直接跳过
|
|
|
if (order.getPayStatus() == null || order.getPayStatus() != HotelOrderConstants.PAY_STATUS_SUCCESS) {
|
|
|
return;
|
|
|
}
|
|
|
+ // 已退款成功(例如先前手动退款重试成功)不再重复发起,避免支付宝重复退款报错
|
|
|
+ if (order.getRefundStatus() != null
|
|
|
+ && order.getRefundStatus() == HotelOrderConstants.REFUND_STATUS_SUCCESS) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
try {
|
|
|
payService.refundOrder(order.getId());
|
|
|
} catch (Exception e) {
|
|
|
- log.error("拒单/退单自动退款失败,请通过手动退款重试: orderId={}, orderNo={}",
|
|
|
+ log.error("退款失败,订单保持原状态待重试: orderId={}, orderNo={}",
|
|
|
order.getId(), order.getOrderNo(), e);
|
|
|
+ throw new BusinessException(HotelOrderConstants.REFUND_FAILED_MSG_PREFIX + ":" + e.getMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -571,4 +670,63 @@ public class HotelOrderServiceImpl implements HotelOrderService {
|
|
|
cal.add(Calendar.MINUTE, minutes);
|
|
|
return cal.getTime();
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从服务端配置解析配送费。
|
|
|
+ * <p>
|
|
|
+ * 取值优先级:hotel_config 的 delivery_fee -> 缺省 0。配置行缺失或值非法时
|
|
|
+ * 按 0 处理并告警,与未引入配置前的实际行为(H5 恒传 0)保持一致,
|
|
|
+ * 不会因为一次误配置直接阻断下单。负数归零、超上限截断,防误配置放大成异常订单金额。
|
|
|
+ *
|
|
|
+ * @param tenantId 租户ID
|
|
|
+ * @return 非负配送费
|
|
|
+ */
|
|
|
+ private BigDecimal resolveDeliveryFee(Long tenantId) {
|
|
|
+ String raw = configMapper.selectValueByKey(tenantId, HotelConfigKeys.DELIVERY_FEE);
|
|
|
+ if (!StringUtils.hasText(raw)) {
|
|
|
+ log.warn("hotel_config 缺少 {} 配置(tenantId={}),本单配送费按 0 处理", HotelConfigKeys.DELIVERY_FEE, tenantId);
|
|
|
+ return HotelConfigKeys.DELIVERY_FEE_DEFAULT;
|
|
|
+ }
|
|
|
+ BigDecimal fee;
|
|
|
+ try {
|
|
|
+ fee = new BigDecimal(raw.trim());
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ log.warn("hotel_config 的 {} 值非法: {}(tenantId={}),本单配送费按 0 处理",
|
|
|
+ HotelConfigKeys.DELIVERY_FEE, raw, tenantId);
|
|
|
+ return HotelConfigKeys.DELIVERY_FEE_DEFAULT;
|
|
|
+ }
|
|
|
+ if (fee.signum() < 0) {
|
|
|
+ log.warn("hotel_config 的 {} 为负数: {}(tenantId={}),已归零", HotelConfigKeys.DELIVERY_FEE, raw, tenantId);
|
|
|
+ return HotelConfigKeys.DELIVERY_FEE_DEFAULT;
|
|
|
+ }
|
|
|
+ if (fee.compareTo(HotelConfigKeys.DELIVERY_FEE_MAX) > 0) {
|
|
|
+ log.warn("hotel_config 的 {} 超出上限: {}(tenantId={}),已按上限 {} 截断",
|
|
|
+ HotelConfigKeys.DELIVERY_FEE, raw, tenantId, HotelConfigKeys.DELIVERY_FEE_MAX);
|
|
|
+ return HotelConfigKeys.DELIVERY_FEE_MAX;
|
|
|
+ }
|
|
|
+ return fee;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析逗号分隔的ID列表字符串为Long列表。
|
|
|
+ * 忽略空值和非法数字,防止前端传入脏数据导致异常。
|
|
|
+ */
|
|
|
+ private List<Long> parseIdList(String idStr) {
|
|
|
+ if (!StringUtils.hasText(idStr)) {
|
|
|
+ return List.of();
|
|
|
+ }
|
|
|
+ return Arrays.stream(idStr.split(","))
|
|
|
+ .map(String::trim)
|
|
|
+ .filter(s -> !s.isEmpty())
|
|
|
+ .map(s -> {
|
|
|
+ try {
|
|
|
+ return Long.parseLong(s);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ log.warn("解析ID列表跳过非法值: {}", s);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
}
|