|
@@ -54,6 +54,8 @@ public class FlashDeliveryApplicationService {
|
|
|
/** 支付方式取值与主订单系统同域:4=现金、6=线下转账(027 线下转账支付)。 */
|
|
/** 支付方式取值与主订单系统同域:4=现金、6=线下转账(027 线下转账支付)。 */
|
|
|
private static final String PAY_TYPE_CASH = "4";
|
|
private static final String PAY_TYPE_CASH = "4";
|
|
|
private static final String PAY_TYPE_TRANSFER = "6";
|
|
private static final String PAY_TYPE_TRANSFER = "6";
|
|
|
|
|
+ /** 自动取消原因使用稳定代码,供不同端按各自语言展示而不是保存单一语言文本。 */
|
|
|
|
|
+ private static final String SCHEDULE_EXPIRED_REASON = "SCHEDULE_EXPIRED";
|
|
|
private static final DateTimeFormatter PRICING_TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm");
|
|
private static final DateTimeFormatter PRICING_TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm");
|
|
|
private static final ZoneId TAIPEI = ZoneId.of("Asia/Taipei");
|
|
private static final ZoneId TAIPEI = ZoneId.of("Asia/Taipei");
|
|
|
private static final int MAX_DISTANCE_METERS = 40_000;
|
|
private static final int MAX_DISTANCE_METERS = 40_000;
|
|
@@ -472,8 +474,10 @@ public class FlashDeliveryApplicationService {
|
|
|
FlashDeliveryOrder target = requireOrder(orderId);
|
|
FlashDeliveryOrder target = requireOrder(orderId);
|
|
|
// 列表过滤不能替代锁内校验,防止骑手绕过列表直接提交不同车型订单。
|
|
// 列表过滤不能替代锁内校验,防止骑手绕过列表直接提交不同车型订单。
|
|
|
if (!isRiderVehicleCompatible(rider, target)) throw fail("flash.delivery.rider.vehicle.not.match");
|
|
if (!isRiderVehicleCompatible(rider, target)) throw fail("flash.delivery.rider.vehicle.not.match");
|
|
|
- riderDeliveryExclusivityService.assertCanAcceptFlash(riderId, target.getDeliveryType());
|
|
|
|
|
Date now = new Date();
|
|
Date now = new Date();
|
|
|
|
|
+ // 预约单必须仍处在取件窗口内;即使骑手持有订单 ID,也不能绕过列表限制提前或逾期接单。
|
|
|
|
|
+ if (!isAvailableAt(target, now)) throw fail("flash.delivery.schedule.not.open");
|
|
|
|
|
+ riderDeliveryExclusivityService.assertCanAcceptFlash(riderId, target.getDeliveryType());
|
|
|
// Mapper 使用“待接单且 rider_id 为空”的条件更新,受影响行数为 1 才表示抢单成功。
|
|
// Mapper 使用“待接单且 rider_id 为空”的条件更新,受影响行数为 1 才表示抢单成功。
|
|
|
if (orderMapper.accept(orderId, riderId, now) != 1) {
|
|
if (orderMapper.accept(orderId, riderId, now) != 1) {
|
|
|
throw fail("flash.delivery.order.already.accepted");
|
|
throw fail("flash.delivery.order.already.accepted");
|
|
@@ -632,6 +636,19 @@ public class FlashDeliveryApplicationService {
|
|
|
return orderMapper.selectAutoCompletable(deadline, Math.min(Math.max(limit, 1), 500));
|
|
return orderMapper.selectAutoCompletable(deadline, Math.min(Math.max(limit, 1), 500));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /** 查询已经错过预约取件结束时间、但仍无人接单的订单,供 Quartz 任务分批处理。 */
|
|
|
|
|
+ public List<FlashDeliveryOrder> expiredScheduledCandidates(Date now, int limit) {
|
|
|
|
|
+ return orderMapper.selectExpiredScheduled(now, Math.min(Math.max(limit, 1), 500));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 原子取消过期预约单;条件更新失败表示订单已被抢、取消或状态已变化,无需重复写日志。 */
|
|
|
|
|
+ @Transactional
|
|
|
|
|
+ public boolean cancelExpiredScheduledOrder(Long orderId, Date now) {
|
|
|
|
|
+ if (orderId == null || now == null || orderMapper.cancelExpiredScheduled(orderId, now) != 1) return false;
|
|
|
|
|
+ writeLog(orderId, WAITING_ACCEPTANCE, CANCELLED, "SYSTEM", null, SCHEDULE_EXPIRED_REASON, now);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/** 报价核心:校验枚举、物品、地址与预约窗后,按时段命中运价并计算路线距离,产出报价上下文。 */
|
|
/** 报价核心:校验枚举、物品、地址与预约窗后,按时段命中运价并计算路线距离,产出报价上下文。 */
|
|
|
private QuoteContext calculateQuote(FlashDeliveryQuoteRequest request) {
|
|
private QuoteContext calculateQuote(FlashDeliveryQuoteRequest request) {
|
|
|
if (request == null) throw fail("flash.delivery.request.required");
|
|
if (request == null) throw fail("flash.delivery.request.required");
|
|
@@ -811,7 +828,15 @@ public class FlashDeliveryApplicationService {
|
|
|
if (userView) {
|
|
if (userView) {
|
|
|
view.setOrderVersion(order.getVersion());
|
|
view.setOrderVersion(order.getVersion());
|
|
|
view.setDeliveryPinCode(Boolean.TRUE.equals(order.getPinRequired()) ? order.getDeliveryPinCode() : null);
|
|
view.setDeliveryPinCode(Boolean.TRUE.equals(order.getPinRequired()) ? order.getDeliveryPinCode() : null);
|
|
|
- view.setRider(riderSummary(order));
|
|
|
|
|
|
|
+ // 接单后直接返回骑手完整用户资料,不做字段加工。
|
|
|
|
|
+ if (order.getRiderId() != null) {
|
|
|
|
|
+ view.setRider(userMapper.selectInfoUserByUserId(order.getRiderId()));
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (!hideSensitiveFields) {
|
|
|
|
|
+ // 接单后仅中单骑手可见寄件用户完整资料;抢单前脱敏视图不返回用户身份信息。
|
|
|
|
|
+ if (order.getUserId() != null) {
|
|
|
|
|
+ view.setUser(userMapper.selectInfoUserByUserId(order.getUserId()));
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
if (!hideSensitiveFields && order.getId() != null) {
|
|
if (!hideSensitiveFields && order.getId() != null) {
|
|
|
groupImageUrls(view, imageMapper.selectList(new QueryWrapper<FlashDeliveryOrderImage>()
|
|
groupImageUrls(view, imageMapper.selectList(new QueryWrapper<FlashDeliveryOrderImage>()
|
|
@@ -992,23 +1017,6 @@ public class FlashDeliveryApplicationService {
|
|
|
return view;
|
|
return view;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** 组装骑手公开资料;仅已接单或已取件状态附带骑手实时位置。 */
|
|
|
|
|
- private FlashDeliveryRiderSummaryView riderSummary(FlashDeliveryOrder order) {
|
|
|
|
|
- if (order.getRiderId() == null) return null;
|
|
|
|
|
- InfoUser rider = userMapper.selectInfoUserByUserId(order.getRiderId());
|
|
|
|
|
- if (rider == null) return null;
|
|
|
|
|
- FlashDeliveryRiderSummaryView view = new FlashDeliveryRiderSummaryView();
|
|
|
|
|
- view.setName(hasText(rider.getNickName()) ? rider.getNickName() : rider.getUserName());
|
|
|
|
|
- view.setAvatar(rider.getAvatar());
|
|
|
|
|
- view.setRating(rider.getStar());
|
|
|
|
|
- view.setImUserId(rider.getImUserId() == null ? null : String.valueOf(rider.getImUserId()));
|
|
|
|
|
- if (ACCEPTED.equals(order.getStatus()) || PICKED_UP.equals(order.getStatus())) {
|
|
|
|
|
- view.setLongitude(rider.getLongitude());
|
|
|
|
|
- view.setLatitude(rider.getLatitude());
|
|
|
|
|
- }
|
|
|
|
|
- return view;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
/** 把费用明细写入报价视图。 */
|
|
/** 把费用明细写入报价视图。 */
|
|
|
private void applyBreakdown(FlashDeliveryQuoteView view, FlashDeliveryPriceBreakdown breakdown) {
|
|
private void applyBreakdown(FlashDeliveryQuoteView view, FlashDeliveryPriceBreakdown breakdown) {
|
|
|
view.setBillableDistance(breakdown.getBillableDistance());
|
|
view.setBillableDistance(breakdown.getBillableDistance());
|
|
@@ -1214,7 +1222,8 @@ public class FlashDeliveryApplicationService {
|
|
|
query.eq("status", WAITING_ACCEPTANCE).isNull("rider_id")
|
|
query.eq("status", WAITING_ACCEPTANCE).isNull("rider_id")
|
|
|
.eq("vehicle_type", riderVehicleType)
|
|
.eq("vehicle_type", riderVehicleType)
|
|
|
.and(item -> item.eq("delivery_mode", "NOW")
|
|
.and(item -> item.eq("delivery_mode", "NOW")
|
|
|
- .or().le("scheduled_pickup_start_at", now));
|
|
|
|
|
|
|
+ .or(nested -> nested.le("scheduled_pickup_start_at", now)
|
|
|
|
|
+ .gt("scheduled_pickup_end_at", now)));
|
|
|
if (longitude != null) {
|
|
if (longitude != null) {
|
|
|
Integer limitKm = newTaskDistanceLimit();
|
|
Integer limitKm = newTaskDistanceLimit();
|
|
|
if (limitKm != null) {
|
|
if (limitKm != null) {
|
|
@@ -1317,12 +1326,14 @@ public class FlashDeliveryApplicationService {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** 判断订单当前是否开放抢单:立即单随时可抢,预约单到点开放。 */
|
|
|
|
|
|
|
+ /** 判断订单当前是否开放抢单:立即单随时可抢,预约单仅在开始(含)至结束(不含)之间可抢。 */
|
|
|
private boolean isAvailableAt(FlashDeliveryOrder order, Date now) {
|
|
private boolean isAvailableAt(FlashDeliveryOrder order, Date now) {
|
|
|
return "NOW".equals(order.getDeliveryMode())
|
|
return "NOW".equals(order.getDeliveryMode())
|
|
|
|| "SCHEDULED".equals(order.getDeliveryMode())
|
|
|| "SCHEDULED".equals(order.getDeliveryMode())
|
|
|
&& order.getScheduledPickupStartAt() != null
|
|
&& order.getScheduledPickupStartAt() != null
|
|
|
- && !order.getScheduledPickupStartAt().after(now);
|
|
|
|
|
|
|
+ && order.getScheduledPickupEndAt() != null
|
|
|
|
|
+ && !order.getScheduledPickupStartAt().after(now)
|
|
|
|
|
+ && order.getScheduledPickupEndAt().after(now);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void optionalEq(QueryWrapper<FlashDeliveryOrder> query, String column, String value) {
|
|
private void optionalEq(QueryWrapper<FlashDeliveryOrder> query, String column, String value) {
|