fix:核销bug修改
This commit is contained in:
1 parent
a9a5a2f8d0
commit
a7c505e9fc
5 files changed
+58
-5
No files matched your search
+1
-1
@@ -475,7 +475,7 @@ public class HardApiController {
|
||||
@Operation(summary = "批量核销预约订单并合并打印兑换券")
|
||||
@Idempotent(expireTime = 1, key = "'/hard-api/ad/reservation-menu/cancel-batch:' + #dto.getOrderIds()", info = "正在核销中,请勿重复操作")
|
||||
public R<Boolean> cancelReservationMenuBatch(@Valid @RequestBody ReservationMenuBatchCancelDto dto,
|
||||
@RequestHeader(value = "DEVICE-ID", required = false) Long hardId) {
|
||||
@RequestHeader("DEVICE-ID") Long hardId) {
|
||||
return R.ok(reservationMenuQueryService.cancelBatch(dto, hardId));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ public interface ReservationMenuQueryService {
|
||||
* 在同一事务中批量核销同一识别用户的一组预约订单,并只创建一张兑换券打印任务。
|
||||
*
|
||||
* @param dto 批量订单主键参数
|
||||
* @param hardId 当前食堂或超市终端 ID,可为空以兼容历史调用
|
||||
* @param hardId 当前食堂或超市终端 ID,批量打印必须提供有效来源终端
|
||||
* @return 整批订单均核销成功时返回 {@code true}
|
||||
*/
|
||||
Boolean cancelBatch(ReservationMenuBatchCancelDto dto, Long hardId);
|
||||
|
||||
+21
@@ -142,6 +142,8 @@ public class ReservationMenuQueryServiceImpl implements ReservationMenuQueryServ
|
||||
}
|
||||
List<Long> orderIds = new LinkedHashSet<>(dto.getOrderIds()).stream().toList();
|
||||
if (orderIds.stream().anyMatch(Objects::isNull)) throw new RuntimeException("预约订单ID不能为空");
|
||||
if (orderIds.size() > 100) throw new RuntimeException("一次最多核销100笔预约订单");
|
||||
if (hardId == null) throw new RuntimeException("DEVICE-ID不能为空");
|
||||
|
||||
List<HardOrderEntity> orders = orderIds.stream().map(hardOrderService::getById).toList();
|
||||
HardOrderEntity first = orders.stream().findFirst().orElseThrow(() -> new RuntimeException("预约订单不存在"));
|
||||
@@ -183,6 +185,9 @@ public class ReservationMenuQueryServiceImpl implements ReservationMenuQueryServ
|
||||
throw new RuntimeException("该订单设备类型不支持预约核销");
|
||||
}
|
||||
resolveVerificationHardId(order, hardId);
|
||||
if (first == null) {
|
||||
validateBatchDevice(order, hardId);
|
||||
}
|
||||
if (first == null) return;
|
||||
if (!Objects.equals(first.getUserId(), order.getUserId())
|
||||
|| !Objects.equals(first.getMerchantId(), order.getMerchantId())
|
||||
@@ -220,6 +225,22 @@ public class ReservationMenuQueryServiceImpl implements ReservationMenuQueryServ
|
||||
return hard.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量核销专用的来源设备白名单校验;单笔旧接口保留原有商户归属兼容语义。
|
||||
*
|
||||
* @param order 批次订单
|
||||
* @param hardId 请求头来源设备 ID
|
||||
*/
|
||||
private void validateBatchDevice(HardOrderEntity order, Long hardId) {
|
||||
if (hardId == null) return;
|
||||
HardManagerEntity hard = hardManagerService.getById(hardId);
|
||||
HardTypeEnum hardType = hard == null ? null : HardTypeEnum.valueOfByType(hard.getType());
|
||||
if (hard == null || (hardType != HardTypeEnum.SUPERMARKET && hardType != HardTypeEnum.CANTEEN)
|
||||
|| !Objects.equals(hard.getMerchantId(), order.getMerchantId())) {
|
||||
throw new RuntimeException("当前设备不能核销该预约订单");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断预约订单退款聚合状态是否允许继续履约,历史空值按未售后兼容,未知状态按不可核销处理。
|
||||
*
|
||||
|
||||
+30
-2
@@ -3995,10 +3995,12 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
if (requestByOrderId.values().stream().anyMatch(dto -> !Objects.equals(sourceHardId, dto.getHardwareId()))) {
|
||||
throw new RuntimeException("批量核销必须使用同一来源设备");
|
||||
}
|
||||
if (sourceHardId == null) throw new RuntimeException("批量核销来源设备不能为空");
|
||||
|
||||
Long firstOrderId = requestByOrderId.keySet().stream().min(Long::compareTo).orElseThrow();
|
||||
HardOrderEntity firstSnapshot = baseMapper.selectById(firstOrderId);
|
||||
if (firstSnapshot == null) throw new I18nBizException(ErrorCodes.COMMON_ORDER_NOT_FOUND);
|
||||
validateReservationVerificationSource(firstSnapshot, sourceHardId);
|
||||
LockInfo verificationLock = acquireReservationVerificationLock(firstSnapshot, sourceHardId);
|
||||
boolean releaseByTransaction = false;
|
||||
List<HardOrderEntity> canceledOrders = new ArrayList<>();
|
||||
@@ -4075,8 +4077,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
}
|
||||
if (ObjectUtil.equals(order.getSysType(), HardTypeEnum.CANTEEN.getType())) {
|
||||
validateCanteenReservationBeforeCancel(order);
|
||||
} else if (hardCarService.getByOrderId(order.getId()).stream()
|
||||
.noneMatch(car -> resolveEffectiveProductCount(car) > 0)) {
|
||||
} else if (!hasReservationFulfillableProduct(order.getId())) {
|
||||
throw new I18nBizException(ErrorCodes.HARD_RESERVE_FULL_REFUND_NO_VERIFY);
|
||||
}
|
||||
if (sourceHardId != null && !Objects.equals(sourceHardId, order.getHardId())
|
||||
@@ -4093,6 +4094,33 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对订单服务内部调用再次校验来源设备类型和商户,避免绕过预约菜单服务时使用其他硬件类型核销。
|
||||
*
|
||||
* @param order 批次首订单快照
|
||||
* @param sourceHardId 来源业务终端 ID;为空表示历史无设备来源调用
|
||||
*/
|
||||
private void validateReservationVerificationSource(HardOrderEntity order, Long sourceHardId) {
|
||||
if (sourceHardId == null) return;
|
||||
HardManagerEntity source = hardManagerService.getById(sourceHardId);
|
||||
HardTypeEnum sourceType = source == null ? null : HardTypeEnum.valueOfByType(source.getType());
|
||||
if (source == null || (sourceType != HardTypeEnum.SUPERMARKET && sourceType != HardTypeEnum.CANTEEN)
|
||||
|| !Objects.equals(source.getMerchantId(), order.getMerchantId())) {
|
||||
throw new RuntimeException("当前设备不能核销该预约订单");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断预约订单是否仍有可履约商品,兼容历史商品查询返回空值的情况。
|
||||
*
|
||||
* @param orderId 待核销预约订单主键
|
||||
* @return 至少有一份剩余商品时返回 {@code true}
|
||||
*/
|
||||
private boolean hasReservationFulfillableProduct(Long orderId) {
|
||||
List<HardCarVo> cars = hardCarService.getByOrderId(orderId);
|
||||
return cars != null && cars.stream().anyMatch(car -> resolveEffectiveProductCount(car) > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按用户、商户、日期、餐段和来源设备串行化预约核销批次,避免共享核销时间误合并并发批次。
|
||||
*
|
||||
|
||||
+5
-1
@@ -16,6 +16,7 @@ import com.bsd.pjcloud.hard.service.HardOrderTotalService;
|
||||
import com.bsd.pjcloud.hard.service.HardPrinterService;
|
||||
import com.bsd.pjcloud.hard.service.HardTerminalStatusService;
|
||||
import com.bsd.pjcloud.hard.service.HardTerminalFaceService;
|
||||
import com.bsd.pjcloud.hard.service.HardTerminalPrintService;
|
||||
import com.bsd.pjcloud.hard.service.ReservationMenuQueryService;
|
||||
import com.bsd.pjcloud.hard.service.VerificationRecordService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -69,11 +70,14 @@ class HardApiControllerTest {
|
||||
/** 终端人脸删除服务,本组购物车测试不调用。 */
|
||||
private final HardTerminalFaceService hardTerminalFaceService = mock(HardTerminalFaceService.class);
|
||||
|
||||
/** 终端打印凭证服务,本组购物车测试不调用。 */
|
||||
private final HardTerminalPrintService hardTerminalPrintService = mock(HardTerminalPrintService.class);
|
||||
|
||||
/** 被测 Android 硬件接口控制器。 */
|
||||
private final HardApiController controller = new HardApiController(hardManagerService, hardInvoiceService,
|
||||
hardPrinterService, hardCarService, hardOrderService, hardOrderSyncService, hardApiService, hardOrderTotalService,
|
||||
reservationMenuQueryService, verificationRecordService, hardTerminalFaceService,
|
||||
hardTerminalStatusService);
|
||||
hardTerminalPrintService, hardTerminalStatusService);
|
||||
|
||||
/**
|
||||
* 验证食堂预约部分退款后数量和行金额按剩余份数投影,已全部退款商品不会返回消费机。
|
||||
|
||||
Reference in new issue
Block a user