diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/api/feign/RemoteCanteenSpuService.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/api/feign/RemoteCanteenSpuService.java index be6fef78e..c168bf846 100644 --- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/api/feign/RemoteCanteenSpuService.java +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/api/feign/RemoteCanteenSpuService.java @@ -105,6 +105,16 @@ public interface RemoteCanteenSpuService { @PostMapping("/canteenSpu/ad/getComboItemsBySpuIds") R>> getComboItemsBySpuIds(@RequestBody List spuIds); + /** + * 供 Hardware 异步打印任务等无用户上下文的服务间调用批量查询套餐组成。 + * + * @param spuIds 套餐 SPU 主键列表,空列表返回空映射 + * @return 套餐 SPU ID 与已按配置顺序排列的组成菜品映射 + */ + @PostMapping("/canteenSpu/inner/getComboItemsBySpuIds") + @NoToken + R>> getInnerComboItemsBySpuIds(@RequestBody List spuIds); + /** * 购物车效验库存 * @param spuId diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenSpuController.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenSpuController.java index 76c7227fb..402a2d399 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenSpuController.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenSpuController.java @@ -444,6 +444,18 @@ public class CanteenSpuController { return R.ok(canteenSpuService.loadComboItemVos(spuIds)); } + /** + * 为无用户令牌的 Hardware 异步打印任务提供套餐组成查询,复用管理端相同的数据装配逻辑。 + * + * @param spuIds 套餐 SPU 主键列表,空列表返回空映射 + * @return 套餐 SPU ID 与组成菜品列表的映射 + */ + @Inner + @PostMapping("/inner/getComboItemsBySpuIds") + public R>> getInnerComboItemsBySpuIds(@RequestBody List spuIds) { + return R.ok(canteenSpuService.loadComboItemVos(spuIds)); + } + @ApiCryptoAes @GetMapping("/app/checkSpuStock") diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardInvoiceServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardInvoiceServiceImpl.java index 007ea285d..1ef94e089 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardInvoiceServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/HardInvoiceServiceImpl.java @@ -799,20 +799,21 @@ public class HardInvoiceServiceImpl extends ServiceImpl loadProductMetadata(List cars) { List productIds = cars.stream().map(HardCarVo::getProductId).filter(Objects::nonNull).distinct().toList(); if (productIds.isEmpty()) return Map.of(); try { - R> result = remoteCanteenSpuService.getSpuListByIds(productIds); + R> result = remoteCanteenSpuService.getInnerSpuListByIds(productIds); if (result == null || !result.isOk() || result.getData() == null) return Map.of(); List comboIds = result.getData().stream() .filter(item -> item != null && Objects.equals(item.getSpuType(), 2)) .map(CanteenSpuEntity::getId).toList(); Map> comboItems = Map.of(); if (!comboIds.isEmpty()) { - R>> comboResult = remoteCanteenSpuService.getComboItemsBySpuIds(comboIds); + R>> comboResult = + remoteCanteenSpuService.getInnerComboItemsBySpuIds(comboIds); if (comboResult != null && comboResult.isOk() && comboResult.getData() != null) comboItems = comboResult.getData(); } Map metadata = new LinkedHashMap<>(); diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardInvoiceServiceImplTest.java b/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardInvoiceServiceImplTest.java index 475b703a0..a0786ee88 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardInvoiceServiceImplTest.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/HardInvoiceServiceImplTest.java @@ -7,6 +7,9 @@ import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto; import com.bsd.pjcloud.admin.api.dto.user.SysUserTeacherDto; import com.bsd.pjcloud.admin.api.vo.UserVO; +import com.bsd.pjcloud.canteen.api.feign.RemoteCanteenSpuService; +import com.bsd.pjcloud.canteen.entity.CanteenSpuEntity; +import com.bsd.pjcloud.canteen.vo.app.OnlineSpuComboItemVO; import com.bsd.pjcloud.hard.api.constant.HardPrintStatusEnum; import com.bsd.pjcloud.hard.api.constant.HardPrintTypeEnum; import com.bsd.pjcloud.hard.api.constant.HardTypeEnum; @@ -34,6 +37,7 @@ import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Answers.CALLS_REAL_METHODS; @@ -54,6 +58,9 @@ class HardInvoiceServiceImplTest { private final HardOrderService orderService = mock(HardOrderService.class); private final HardManagerService managerService = mock(HardManagerService.class); private final HardPrinterService printerService = mock(HardPrinterService.class); + + /** 无用户上下文的食堂商品内部查询客户端。 */ + private final RemoteCanteenSpuService remoteCanteenSpuService = mock(RemoteCanteenSpuService.class); private final ThreadPoolTaskExecutor taskExecutor = mock(ThreadPoolTaskExecutor.class); private final HardInvoiceServiceImpl service = mock(HardInvoiceServiceImpl.class, CALLS_REAL_METHODS); @@ -67,6 +74,7 @@ class HardInvoiceServiceImplTest { ReflectionTestUtils.setField(service, "orderService", orderService); ReflectionTestUtils.setField(service, "hardManagerService", managerService); ReflectionTestUtils.setField(service, "printerService", printerService); + ReflectionTestUtils.setField(service, "remoteCanteenSpuService", remoteCanteenSpuService); ReflectionTestUtils.setField(service, "hardTaskExecutor", taskExecutor); } @@ -242,6 +250,49 @@ class HardInvoiceServiceImplTest { assertEquals("三年级二班", fields.get("班级")); } + /** 兑换券异步生成必须使用无用户令牌的内部接口识别套餐及其组成,避免统一降级为单品。 */ + @Test + void voucherMetadataUsesInnerSpuAndComboQueries() { + HardCarVo car = new HardCarVo(); + car.setProductId(501L); + CanteenSpuEntity combo = new CanteenSpuEntity(); + combo.setId(501L); + combo.setSpuType(2); + OnlineSpuComboItemVO item = new OnlineSpuComboItemVO(); + item.setName("糖醋排骨"); + item.setQuantity(1); + when(remoteCanteenSpuService.getInnerSpuListByIds(List.of(501L))).thenReturn(R.ok(List.of(combo))); + when(remoteCanteenSpuService.getInnerComboItemsBySpuIds(List.of(501L))) + .thenReturn(R.ok(Map.of(501L, List.of(item)))); + + @SuppressWarnings("unchecked") + Map metadata = ReflectionTestUtils.invokeMethod(service, "loadProductMetadata", List.of(car)); + Object product = metadata.get(501L); + + assertNotNull(product); + assertTrue((Boolean) ReflectionTestUtils.invokeMethod(product, "combo")); + assertEquals(List.of(item), ReflectionTestUtils.invokeMethod(product, "items")); + verify(remoteCanteenSpuService).getInnerSpuListByIds(List.of(501L)); + verify(remoteCanteenSpuService).getInnerComboItemsBySpuIds(List.of(501L)); + verify(remoteCanteenSpuService, never()).getSpuListByIds(any()); + verify(remoteCanteenSpuService, never()).getComboItemsBySpuIds(any()); + } + + /** 商品内部查询暂时不可用时应保持原有降级策略,避免已完成核销因打印资料失败而回滚。 */ + @Test + void voucherMetadataFallsBackToSingleProductWhenInnerQueryFails() { + HardCarVo car = new HardCarVo(); + car.setProductId(501L); + when(remoteCanteenSpuService.getInnerSpuListByIds(List.of(501L))) + .thenThrow(new RuntimeException("canteen service unavailable")); + + @SuppressWarnings("unchecked") + Map metadata = ReflectionTestUtils.invokeMethod(service, "loadProductMetadata", List.of(car)); + + assertTrue(metadata.isEmpty()); + verify(remoteCanteenSpuService, never()).getInnerComboItemsBySpuIds(any()); + } + private static HardOrderEntity order() { return HardOrderEntity.builder().id(1L).hardId(10L).merchantId(30L).deptId(40L).tenantId(50L).build(); }