Merge remote-tracking branch 'origin/master'
# Conflicts: # pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java
This commit is contained in:
commit
92cabb5796
4 files changed
+239
-80
No files matched your search
+41
-41
@@ -1,41 +1,41 @@
|
||||
package com.bsd.pjcloud.canteen.dto.report;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 包厢台账(汇总)行数据。
|
||||
* <p>
|
||||
* 导入已改为 {@code CanteenReportBoxExcelBinder} 按规范化表头名称映射,
|
||||
* 不再依赖 EasyExcel {@code @ExcelProperty}(多值 value 会被当成多级表头而非别名)。
|
||||
*/
|
||||
@Data
|
||||
public class CanteenReportBoxExcel {
|
||||
|
||||
private String monthText;
|
||||
|
||||
private String bizDate;
|
||||
|
||||
private String costCenter;
|
||||
|
||||
private String costCenterCode;
|
||||
|
||||
private String empNo;
|
||||
|
||||
private String empName;
|
||||
|
||||
private BigDecimal foodAmount;
|
||||
|
||||
private BigDecimal drinkAmount;
|
||||
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
private String legalEntity;
|
||||
|
||||
private String remark1;
|
||||
|
||||
private String remark2;
|
||||
|
||||
private String park;
|
||||
}
|
||||
package com.bsd.pjcloud.canteen.dto.report;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 包厢台账(汇总)行数据。
|
||||
* <p>
|
||||
* 导入已改为 {@code CanteenReportBoxExcelBinder} 按规范化表头名称映射,
|
||||
* 不再依赖 EasyExcel {@code @ExcelProperty}(多值 value 会被当成多级表头而非别名)。
|
||||
*/
|
||||
@Data
|
||||
public class CanteenReportBoxExcel {
|
||||
|
||||
private String monthText;
|
||||
|
||||
private String bizDate;
|
||||
|
||||
private String costCenter;
|
||||
|
||||
private String costCenterCode;
|
||||
|
||||
private String empNo;
|
||||
|
||||
private String empName;
|
||||
|
||||
private BigDecimal foodAmount;
|
||||
|
||||
private BigDecimal drinkAmount;
|
||||
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
private String legalEntity;
|
||||
|
||||
private String remark1;
|
||||
|
||||
private String remark2;
|
||||
|
||||
private String park;
|
||||
}
|
||||
+1
-1
@@ -45,7 +45,7 @@ class CanteenSpuServiceImplTest {
|
||||
valueOperations = mock(ValueOperations.class);
|
||||
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
CanteenSpuServiceImpl target = new CanteenSpuServiceImpl(redisTemplate, null, null, null, null, null,
|
||||
mock(CanteenMerchantScopeHelper.class));
|
||||
mock(CanteenMerchantScopeHelper.class), mock(CanteenDailyMenuStockSupport.class));
|
||||
ReflectionTestUtils.setField(target, "baseMapper", spuMapper);
|
||||
service = target;
|
||||
TransactionSynchronizationManager.initSynchronization();
|
||||
|
||||
+33
-32
@@ -2222,10 +2222,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
}
|
||||
for (Map.Entry<Long, Integer> entry : required.entrySet()) {
|
||||
DailyMenuAppVO row = menuBySpu.get(entry.getKey());
|
||||
// 线上剩余:优先 remainOnlineStock;兼容旧字段 stock/onlineStock(历史把剩余写在配额字段)
|
||||
int onlineRemain = row.getRemainOnlineStock() != null ? row.getRemainOnlineStock()
|
||||
: (row.getStock() != null ? row.getStock()
|
||||
: (row.getOnlineStock() == null ? 0 : row.getOnlineStock()));
|
||||
int onlineRemain = dailyOnlineRemain(row);
|
||||
if (onlineRemain < entry.getValue()) {
|
||||
throw new I18nBizException(ErrorCodes.HARD_PRODUCT_STOCK_LOW, row.getName());
|
||||
}
|
||||
@@ -4647,9 +4644,9 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
|
||||
HardParentCarEntity parentCar = hardCarService.getParentCarById(order.getParentCarId());
|
||||
boolean dailyMenu = isDailyMenuParentCar(parentCar);
|
||||
// App 预约(RESERVE)走线上 ONLINE;安卓机等线下订单走 OFFLINE。退款必须与下单同通道,否则剩余对不上。
|
||||
boolean onlineChannel = dailyMenu
|
||||
&& Objects.equals(order.getOrderType(), HardOrderTypeEnum.RESERVE.getType());
|
||||
// 每日菜谱分池路由:线上预定订单下单扣 ONLINE 池(updateDailyMenuReserveInventory),退款/重扣必须还同一池;
|
||||
// 机器收银等其他类型下单走 OFFLINE 池。不看订单类型会把预约单的线上销量错补到线下池。
|
||||
boolean onlineChannel = HardOrderTypeEnum.RESERVE.getType().equals(order.getOrderType());
|
||||
LocalDate mealDate = order.getApptUseDate();
|
||||
Integer hourType = parseHourType(order.getHourType());
|
||||
Map<Long, Integer> inventorySnapshot = new LinkedHashMap<>();
|
||||
@@ -4658,9 +4655,10 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
if (mealDate == null || hourType == null) {
|
||||
throw new I18nBizException(ErrorCodes.HARD_CANTEEN_SPU_BAD);
|
||||
}
|
||||
R<List<DailyMenuAppVO>> menuResult = onlineChannel
|
||||
? remoteDailyMenuService.listForOrderValidate(mealDate.toString(), hourType, order.getMerchantId())
|
||||
: remoteDailyMenuService.getCanteenList(mealDate.toString(), hourType, null, order.getMerchantId());
|
||||
// 库存恢复可能在自动退款定时任务等无用户凭证的上下文执行,必须走 @NoToken 内部菜谱接口;
|
||||
// getCanteenList 是 AD 终端登录态接口,任务线程调用会被 424 拒绝。
|
||||
R<List<DailyMenuAppVO>> menuResult = remoteDailyMenuService.listForOrderValidate(
|
||||
mealDate.toString(), hourType, order.getMerchantId());
|
||||
if (menuResult == null || !menuResult.isOk() || menuResult.getData() == null) {
|
||||
throw new I18nBizException(menuResult == null ? ErrorCodes.HARD_CANTEEN_STOCK_NO_RESP
|
||||
: ErrorCodes.HARD_CANTEEN_STOCK_QUERY_FAIL);
|
||||
@@ -4674,17 +4672,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
if (row == null) {
|
||||
throw new I18nBizException(ErrorCodes.HARD_CANTEEN_SPU_BAD);
|
||||
}
|
||||
int channelRemain;
|
||||
if (onlineChannel) {
|
||||
channelRemain = row.getRemainOnlineStock() != null ? row.getRemainOnlineStock()
|
||||
: (row.getStock() != null ? row.getStock()
|
||||
: (row.getOnlineStock() == null ? 0 : row.getOnlineStock()));
|
||||
}
|
||||
else {
|
||||
channelRemain = row.getRemainOfflineStock() != null ? row.getRemainOfflineStock()
|
||||
: (row.getStock() != null ? row.getStock()
|
||||
: (row.getOfflineStock() != null ? row.getOfflineStock() : 0));
|
||||
}
|
||||
int channelRemain = onlineChannel ? dailyOnlineRemain(row) : dailyOfflineRemain(row);
|
||||
if (entry.getValue() < 0 && channelRemain < Math.abs(entry.getValue())) {
|
||||
throw new I18nBizException(ErrorCodes.HARD_PRODUCT_STOCK_LOW, row.getName());
|
||||
}
|
||||
@@ -4731,10 +4719,10 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
: "HARD_DAILY_MENU_AFTER_SALE:" + afterSaleId;
|
||||
if (dailyMenu) {
|
||||
String stockChannel = onlineChannel ? "ONLINE" : "OFFLINE";
|
||||
registerCanteenDailyChannelInventoryRollback(stockChanges, inventorySnapshot, resolvedInventoryBizId,
|
||||
order.getMerchantId(), mealDate, hourType, stockChannel);
|
||||
applyCanteenDailyChannelInventoryChange(stockChanges, inventorySnapshot, resolvedInventoryBizId, "APPLY",
|
||||
order.getMerchantId(), mealDate, hourType, stockChannel);
|
||||
registerCanteenDailyChannelInventoryRollback(stockChannel, stockChanges, inventorySnapshot,
|
||||
resolvedInventoryBizId, order.getMerchantId(), mealDate, hourType);
|
||||
applyCanteenDailyChannelInventoryChange(stockChannel, stockChanges, inventorySnapshot,
|
||||
resolvedInventoryBizId, "APPLY", order.getMerchantId(), mealDate, hourType);
|
||||
}
|
||||
else {
|
||||
registerCanteenSpuInventoryRollback(stockChanges, inventorySnapshot, resolvedInventoryBizId);
|
||||
@@ -4812,8 +4800,8 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
}
|
||||
}
|
||||
|
||||
/** 每日菜谱分池:按 ONLINE/OFFLINE 改写 daily_menu.*_sold(配额不动)。 */
|
||||
private void applyCanteenDailyChannelInventoryChange(Map<Long, Integer> stockChanges,
|
||||
/** 每日菜谱分池幂等变更:ONLINE 改写线上池销量,OFFLINE 改写线下池销量,两池互不影响。 */
|
||||
private void applyCanteenDailyChannelInventoryChange(String stockChannel, Map<Long, Integer> stockChanges,
|
||||
Map<Long, Integer> inventorySnapshot, String inventoryBizId, String operation, Long merchantId,
|
||||
LocalDate mealDate, Integer hourType, String stockChannel) {
|
||||
CanteenSpuUpdateStockReqDTO dto = new CanteenSpuUpdateStockReqDTO();
|
||||
@@ -4842,7 +4830,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
}
|
||||
}
|
||||
|
||||
private void registerCanteenDailyChannelInventoryRollback(Map<Long, Integer> stockChanges,
|
||||
private void registerCanteenDailyChannelInventoryRollback(String stockChannel, Map<Long, Integer> stockChanges,
|
||||
Map<Long, Integer> inventorySnapshot, String inventoryBizId, Long merchantId, LocalDate mealDate,
|
||||
Integer hourType, String stockChannel) {
|
||||
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
@@ -4858,17 +4846,30 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
return;
|
||||
}
|
||||
try {
|
||||
applyCanteenDailyChannelInventoryChange(rollbackChanges, rollbackSnapshot, inventoryBizId,
|
||||
"ROLLBACK", merchantId, mealDate, hourType, channel);
|
||||
applyCanteenDailyChannelInventoryChange(stockChannel, rollbackChanges, rollbackSnapshot, inventoryBizId,
|
||||
"ROLLBACK", merchantId, mealDate, hourType);
|
||||
}
|
||||
catch (Exception compensationException) {
|
||||
log.error("|{}|每日菜谱{}库存回滚失败,inventoryBizId:|{}|", TAG, channel, inventoryBizId,
|
||||
compensationException);
|
||||
log.error("|{}|每日菜谱{}库存回滚失败,inventoryBizId:|{}|", TAG, stockChannel, inventoryBizId, compensationException);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 线上剩余:优先 remainOnlineStock;兼容旧字段 stock/onlineStock(历史把剩余写在配额字段)。 */
|
||||
private static int dailyOnlineRemain(DailyMenuAppVO row) {
|
||||
return row.getRemainOnlineStock() != null ? row.getRemainOnlineStock()
|
||||
: (row.getStock() != null ? row.getStock()
|
||||
: (row.getOnlineStock() == null ? 0 : row.getOnlineStock()));
|
||||
}
|
||||
|
||||
/** 线下剩余:优先 remainOfflineStock;兼容旧字段 stock/offlineStock。 */
|
||||
private static int dailyOfflineRemain(DailyMenuAppVO row) {
|
||||
return row.getRemainOfflineStock() != null ? row.getRemainOfflineStock()
|
||||
: (row.getStock() != null ? row.getStock()
|
||||
: (row.getOfflineStock() != null ? row.getOfflineStock() : 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void fastenerInventory(HardOrderEntity order, YesNoEnum isReduce) {
|
||||
|
||||
+164
-6
@@ -1,9 +1,12 @@
|
||||
package com.bsd.pjcloud.hard.service.impl.order;
|
||||
|
||||
import com.bsd.pjcloud.canteen.api.feign.RemoteCanteenSpuService;
|
||||
import com.bsd.pjcloud.canteen.api.feign.RemoteDailyMenuService;
|
||||
import com.bsd.pjcloud.canteen.dto.CanteenSpuUpdateStockReqDTO;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenSpuEntity;
|
||||
import com.bsd.pjcloud.canteen.vo.app.DailyMenuAppVO;
|
||||
import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum;
|
||||
import com.bsd.pjcloud.common.core.exception.ErrorCodes;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.hard.api.constant.HardProductSourceEnum;
|
||||
import com.bsd.pjcloud.hard.api.constant.HardTypeEnum;
|
||||
@@ -19,10 +22,12 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Answers.CALLS_REAL_METHODS;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -32,7 +37,11 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 机器收银食堂订单库存分流测试,确保 BELOW 订单使用 {@code canteen_spu} 原子库存接口。
|
||||
* 食堂订单库存分流测试:机器收银 BELOW 订单使用 {@code canteen_spu} 原子库存接口,
|
||||
* 每日菜谱订单按订单类型路由分池(RESERVE 走 ONLINE,其他类型走 OFFLINE)。
|
||||
* <p>
|
||||
* 直接调用五参 {@code fastenerInventory} 重载:四参重载经 getSelf() 需要 Spring 上下文,
|
||||
* 纯单测环境无法提供;异常文案由全局 i18n 处理器解析,此处只断言错误码。
|
||||
*/
|
||||
class HardOrderServiceImplInventoryTest {
|
||||
|
||||
@@ -42,6 +51,9 @@ class HardOrderServiceImplInventoryTest {
|
||||
/** 食堂基础菜品远程服务,负责查询权威库存并执行幂等扣减。 */
|
||||
private final RemoteCanteenSpuService remoteCanteenSpuService = mock(RemoteCanteenSpuService.class);
|
||||
|
||||
/** 每日菜谱远程服务,负责按日期餐段查询分池剩余。 */
|
||||
private final RemoteDailyMenuService remoteDailyMenuService = mock(RemoteDailyMenuService.class);
|
||||
|
||||
/** 被测订单服务,仅执行真实库存方法,其他依赖不参与本组测试。 */
|
||||
private final HardOrderServiceImpl service = mock(HardOrderServiceImpl.class, CALLS_REAL_METHODS);
|
||||
|
||||
@@ -52,6 +64,7 @@ class HardOrderServiceImplInventoryTest {
|
||||
void setUp() {
|
||||
ReflectionTestUtils.setField(service, "hardCarService", hardCarService);
|
||||
ReflectionTestUtils.setField(service, "remoteCanteenSpuService", remoteCanteenSpuService);
|
||||
ReflectionTestUtils.setField(service, "remoteDailyMenuService", remoteDailyMenuService);
|
||||
TransactionSynchronizationManager.initSynchronization();
|
||||
}
|
||||
|
||||
@@ -75,9 +88,9 @@ class HardOrderServiceImplInventoryTest {
|
||||
when(remoteCanteenSpuService.getInnerSpuListByIds(List.of(11L))).thenReturn(R.ok(List.of(product)));
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.fastenerInventory(cashierOrder(), null, YesNoEnum.YES, null));
|
||||
() -> service.fastenerInventory(cashierOrder(), null, YesNoEnum.YES, null, null));
|
||||
|
||||
assertEquals("测试菜品库存不足", exception.getMessage());
|
||||
assertEquals(ErrorCodes.HARD_PRODUCT_STOCK_LOW, exception.getMessage());
|
||||
verify(remoteCanteenSpuService, never()).updateSkuStockIdempotent(any());
|
||||
}
|
||||
|
||||
@@ -91,7 +104,7 @@ class HardOrderServiceImplInventoryTest {
|
||||
when(remoteCanteenSpuService.getInnerSpuListByIds(List.of(11L))).thenReturn(R.ok(List.of(product)));
|
||||
when(remoteCanteenSpuService.updateSkuStockIdempotent(any())).thenReturn(R.ok());
|
||||
|
||||
assertDoesNotThrow(() -> service.fastenerInventory(cashierOrder(), null, YesNoEnum.YES, null));
|
||||
assertDoesNotThrow(() -> service.fastenerInventory(cashierOrder(), null, YesNoEnum.YES, null, null));
|
||||
|
||||
ArgumentCaptor<CanteenSpuUpdateStockReqDTO> captor = ArgumentCaptor.forClass(CanteenSpuUpdateStockReqDTO.class);
|
||||
verify(remoteCanteenSpuService).updateSkuStockIdempotent(captor.capture());
|
||||
@@ -129,7 +142,7 @@ class HardOrderServiceImplInventoryTest {
|
||||
RuntimeException exception = assertThrows(RuntimeException.class, () -> ReflectionTestUtils.invokeMethod(
|
||||
service, "validateCanteenSpuOrderInventory", cashierOrder()));
|
||||
|
||||
assertEquals("测试菜品库存不足", exception.getMessage());
|
||||
assertEquals(ErrorCodes.HARD_PRODUCT_STOCK_LOW, exception.getMessage());
|
||||
verify(remoteCanteenSpuService, never()).updateSkuStockIdempotent(any());
|
||||
}
|
||||
|
||||
@@ -143,7 +156,7 @@ class HardOrderServiceImplInventoryTest {
|
||||
when(remoteCanteenSpuService.getInnerSpuListByIds(List.of(11L))).thenReturn(R.ok(List.of(product)));
|
||||
when(remoteCanteenSpuService.updateSkuStockIdempotent(any())).thenReturn(R.ok());
|
||||
|
||||
assertDoesNotThrow(() -> service.fastenerInventory(cashierOrder(), null, YesNoEnum.NO, 500L));
|
||||
assertDoesNotThrow(() -> service.fastenerInventory(cashierOrder(), null, YesNoEnum.NO, 500L, null));
|
||||
|
||||
ArgumentCaptor<CanteenSpuUpdateStockReqDTO> captor = ArgumentCaptor.forClass(CanteenSpuUpdateStockReqDTO.class);
|
||||
verify(remoteCanteenSpuService).updateSkuStockIdempotent(captor.capture());
|
||||
@@ -174,6 +187,96 @@ class HardOrderServiceImplInventoryTest {
|
||||
assertEquals(-2, request.getItems().get(0).getIncrCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证设备购买链路(/hard-api/ad/create-order 产生的 BELOW+CANTEEN_SPU 购物车)扣基础 canteen_spu.stock:
|
||||
* 请求不携带每日菜谱分池通道,也不查询每日菜谱接口——每日菜谱页面的线上/线下剩余不会变化。
|
||||
*/
|
||||
@Test
|
||||
void deviceCreateOrderDeductsBasicSpuStockWithoutDailyMenuPools() {
|
||||
prepareCart(1);
|
||||
CanteenSpuEntity product = productWithStock(9996);
|
||||
when(remoteCanteenSpuService.getInnerSpuListByIds(List.of(11L))).thenReturn(R.ok(List.of(product)));
|
||||
when(remoteCanteenSpuService.updateSkuStockIdempotent(any())).thenReturn(R.ok());
|
||||
|
||||
assertDoesNotThrow(() -> service.fastenerInventory(cashierOrder(), null, YesNoEnum.YES, null, null));
|
||||
|
||||
ArgumentCaptor<CanteenSpuUpdateStockReqDTO> captor = ArgumentCaptor.forClass(CanteenSpuUpdateStockReqDTO.class);
|
||||
verify(remoteCanteenSpuService).updateSkuStockIdempotent(captor.capture());
|
||||
CanteenSpuUpdateStockReqDTO request = captor.getValue();
|
||||
assertNull(request.getStockChannel());
|
||||
assertEquals(-1, request.getItems().get(0).getIncrCount());
|
||||
verify(remoteDailyMenuService, never()).listForOrderValidate(any(), any(), any());
|
||||
verify(remoteDailyMenuService, never()).getCanteenList(any(), any(), any(), any());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证线上预定每日菜谱订单退款恢复 ONLINE 池:下单扣的是 online_sold,退款必须还同一池,
|
||||
* 快照取线上剩余并携带日期餐段。
|
||||
*/
|
||||
@Test
|
||||
void reserveRefundRestoresDailyMenuOnlineStock() {
|
||||
prepareDailyMenuCart(2);
|
||||
when(remoteDailyMenuService.listForOrderValidate(any(), any(), any()))
|
||||
.thenReturn(R.ok(List.of(dailyMenuRow(8, 4))));
|
||||
when(remoteCanteenSpuService.updateSkuStockIdempotent(any())).thenReturn(R.ok());
|
||||
|
||||
assertDoesNotThrow(() -> service.fastenerInventory(reserveDailyMenuOrder(), null, YesNoEnum.NO, 500L, null));
|
||||
|
||||
ArgumentCaptor<CanteenSpuUpdateStockReqDTO> captor = ArgumentCaptor.forClass(CanteenSpuUpdateStockReqDTO.class);
|
||||
verify(remoteCanteenSpuService).updateSkuStockIdempotent(captor.capture());
|
||||
CanteenSpuUpdateStockReqDTO request = captor.getValue();
|
||||
assertEquals("APPLY", request.getOperation());
|
||||
assertEquals("ONLINE", request.getStockChannel());
|
||||
assertEquals("HARD_DAILY_MENU_AFTER_SALE:500", request.getBizId());
|
||||
assertEquals(300L, request.getMerchantId());
|
||||
CanteenSpuUpdateStockReqDTO.Item item = request.getItems().get(0);
|
||||
assertEquals(11L, item.getId());
|
||||
assertEquals(2, item.getIncrCount());
|
||||
assertEquals(8, item.getBeforeStock());
|
||||
assertEquals(LocalDate.of(2026, 9, 13), item.getMealDate());
|
||||
assertEquals(2, item.getHourType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证机器收银等其他类型的每日菜谱订单退款仍恢复 OFFLINE 池,快照取线下剩余。
|
||||
*/
|
||||
@Test
|
||||
void cashierDailyMenuRefundRestoresOfflineStock() {
|
||||
prepareDailyMenuCart(2);
|
||||
when(remoteDailyMenuService.listForOrderValidate(any(), any(), any()))
|
||||
.thenReturn(R.ok(List.of(dailyMenuRow(8, 4))));
|
||||
when(remoteCanteenSpuService.updateSkuStockIdempotent(any())).thenReturn(R.ok());
|
||||
|
||||
HardOrderEntity order = reserveDailyMenuOrder();
|
||||
order.setOrderType(HardOrderTypeEnum.BELOW.getType());
|
||||
assertDoesNotThrow(() -> service.fastenerInventory(order, null, YesNoEnum.NO, 500L, null));
|
||||
|
||||
ArgumentCaptor<CanteenSpuUpdateStockReqDTO> captor = ArgumentCaptor.forClass(CanteenSpuUpdateStockReqDTO.class);
|
||||
verify(remoteCanteenSpuService).updateSkuStockIdempotent(captor.capture());
|
||||
CanteenSpuUpdateStockReqDTO request = captor.getValue();
|
||||
assertEquals("OFFLINE", request.getStockChannel());
|
||||
assertEquals("HARD_DAILY_MENU_AFTER_SALE:500", request.getBizId());
|
||||
assertEquals(2, request.getItems().get(0).getIncrCount());
|
||||
assertEquals(4, request.getItems().get(0).getBeforeStock());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证线上预定重审重扣按线上剩余校验:线上不足即拒绝,即使线下剩余充足。
|
||||
*/
|
||||
@Test
|
||||
void reserveReoccupyRejectsInsufficientOnlineStock() {
|
||||
prepareDailyMenuCart(2);
|
||||
when(remoteDailyMenuService.listForOrderValidate(any(), any(), any()))
|
||||
.thenReturn(R.ok(List.of(dailyMenuRow(1, 4))));
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.fastenerInventory(reserveDailyMenuOrder(), null, YesNoEnum.YES, 500L,
|
||||
"HARD_AFTER_SALE_REVIEW_REOCCUPY:500:1"));
|
||||
|
||||
assertEquals(ErrorCodes.HARD_PRODUCT_STOCK_LOW, exception.getMessage());
|
||||
verify(remoteCanteenSpuService, never()).updateSkuStockIdempotent(any());
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备机器收银购物车来源和商品数量。
|
||||
*
|
||||
@@ -193,6 +296,44 @@ class HardOrderServiceImplInventoryTest {
|
||||
when(hardCarService.getByParentCarId(200L)).thenReturn(List.of(car));
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备每日菜谱购物车来源(cartScene 与商品来源标记与服务端保存逻辑一致)和商品数量。
|
||||
*
|
||||
* @param productCount 本次购买份数
|
||||
*/
|
||||
private void prepareDailyMenuCart(int productCount) {
|
||||
HardParentCarEntity.TakeOutSetting setting = new HardParentCarEntity.TakeOutSetting();
|
||||
setting.setCartScene("DAILY_MENU");
|
||||
HardParentCarEntity parentCar = HardParentCarEntity.builder()
|
||||
.id(200L)
|
||||
.productSource(HardProductSourceEnum.CANTEEN_DAILY_MENU.getType())
|
||||
.takeOutSettingJson(setting)
|
||||
.build();
|
||||
HardCarEntity car = HardCarEntity.builder()
|
||||
.parentCarId(200L)
|
||||
.productId(11L)
|
||||
.productCount(productCount)
|
||||
.build();
|
||||
when(hardCarService.getParentCarById(200L)).thenReturn(parentCar);
|
||||
when(hardCarService.getByParentCarId(200L)).thenReturn(List.of(car));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造指定线上/线下剩余的每日菜谱行。
|
||||
*
|
||||
* @param onlineRemain 线上池剩余
|
||||
* @param offlineRemain 线下池剩余
|
||||
* @return 用于分池校验的菜谱行
|
||||
*/
|
||||
private DailyMenuAppVO dailyMenuRow(int onlineRemain, int offlineRemain) {
|
||||
DailyMenuAppVO row = new DailyMenuAppVO();
|
||||
row.setSpuId(11L);
|
||||
row.setName("测试菜品");
|
||||
row.setRemainOnlineStock(onlineRemain);
|
||||
row.setRemainOfflineStock(offlineRemain);
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造指定权威库存的当前商户基础菜品。
|
||||
*
|
||||
@@ -223,4 +364,21 @@ class HardOrderServiceImplInventoryTest {
|
||||
.merchantId(300L)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造线上预定提交的每日菜谱订单。
|
||||
*
|
||||
* @return 关联每日菜谱购物车、固定日期餐段和商户的 RESERVE 订单
|
||||
*/
|
||||
private HardOrderEntity reserveDailyMenuOrder() {
|
||||
return HardOrderEntity.builder()
|
||||
.id(100L)
|
||||
.sysType(HardTypeEnum.CANTEEN.getType())
|
||||
.orderType(HardOrderTypeEnum.RESERVE.getType())
|
||||
.parentCarId(200L)
|
||||
.merchantId(300L)
|
||||
.apptUseDate(LocalDate.of(2026, 9, 13))
|
||||
.hourType("2")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user