fix: reserve stable inbound code for draft label printing
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
package com.baiyue.firstleg.client;
|
||||||
|
|
||||||
|
import com.baiyue.common.core.api.ApiResponse;
|
||||||
|
import com.baiyue.firstleg.dto.OrderApprovedInboundCommand;
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
|
|
||||||
|
@FeignClient(name = "order-service", contextId = "printableOrderInboundClient",
|
||||||
|
url = "${baiyue.clients.order.url:}", path = "/internal/order/v1/cargoes")
|
||||||
|
public interface PrintableOrderInboundClient {
|
||||||
|
@PostMapping("/{tenantId}/{orderId}/printable-inbound-snapshot")
|
||||||
|
ApiResponse<OrderApprovedInboundCommand> reserve(
|
||||||
|
@RequestHeader("X-Internal-Service-Token") String token,
|
||||||
|
@PathVariable long tenantId,
|
||||||
|
@PathVariable long orderId);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import com.baiyue.common.core.api.ApiResponse;
|
|||||||
import com.baiyue.common.core.api.ResultCode;
|
import com.baiyue.common.core.api.ResultCode;
|
||||||
import com.baiyue.common.core.exception.BizException;
|
import com.baiyue.common.core.exception.BizException;
|
||||||
import com.baiyue.firstleg.client.OrderCargoClient;
|
import com.baiyue.firstleg.client.OrderCargoClient;
|
||||||
|
import com.baiyue.firstleg.client.PrintableOrderInboundClient;
|
||||||
import com.baiyue.firstleg.dto.InboundCartonLabels;
|
import com.baiyue.firstleg.dto.InboundCartonLabels;
|
||||||
import com.baiyue.firstleg.entity.InboundCartonLabelEntity;
|
import com.baiyue.firstleg.entity.InboundCartonLabelEntity;
|
||||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||||
@@ -35,6 +36,7 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
|
|||||||
|
|
||||||
@Resource private InboundCartonLabelRepository repository;
|
@Resource private InboundCartonLabelRepository repository;
|
||||||
@Resource private OrderCargoClient orderCargoClient;
|
@Resource private OrderCargoClient orderCargoClient;
|
||||||
|
@Resource private PrintableOrderInboundClient printableOrderInboundClient;
|
||||||
@Resource private InboundOrderCreationService inboundOrderCreationService;
|
@Resource private InboundOrderCreationService inboundOrderCreationService;
|
||||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||||
|
|
||||||
@@ -101,7 +103,7 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
|
|||||||
private InboundOrderEntity prepareMissingInbound(long tenantId, long orderId) {
|
private InboundOrderEntity prepareMissingInbound(long tenantId, long orderId) {
|
||||||
try {
|
try {
|
||||||
ApiResponse<com.baiyue.firstleg.dto.OrderApprovedInboundCommand> response =
|
ApiResponse<com.baiyue.firstleg.dto.OrderApprovedInboundCommand> response =
|
||||||
orderCargoClient.inboundSnapshot(internalToken, tenantId, orderId);
|
printableOrderInboundClient.reserve(internalToken, tenantId, orderId);
|
||||||
if (response == null || !response.successful() || response.data() == null) {
|
if (response == null || !response.successful() || response.data() == null) {
|
||||||
throw new BizException(ResultCode.NOT_FOUND, "订单不存在或无法生成预入库单");
|
throw new BizException(ResultCode.NOT_FOUND, "订单不存在或无法生成预入库单");
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.service;
|
|||||||
import com.baiyue.common.core.api.ApiResponse;
|
import com.baiyue.common.core.api.ApiResponse;
|
||||||
import com.baiyue.common.core.exception.BizException;
|
import com.baiyue.common.core.exception.BizException;
|
||||||
import com.baiyue.firstleg.client.OrderCargoClient;
|
import com.baiyue.firstleg.client.OrderCargoClient;
|
||||||
|
import com.baiyue.firstleg.client.PrintableOrderInboundClient;
|
||||||
import com.baiyue.firstleg.entity.InboundCartonLabelEntity;
|
import com.baiyue.firstleg.entity.InboundCartonLabelEntity;
|
||||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||||
import com.baiyue.firstleg.repository.InboundCartonLabelRepository;
|
import com.baiyue.firstleg.repository.InboundCartonLabelRepository;
|
||||||
@@ -28,6 +29,7 @@ import static org.mockito.Mockito.when;
|
|||||||
class InboundCartonLabelServiceTest {
|
class InboundCartonLabelServiceTest {
|
||||||
@Mock private InboundCartonLabelRepository repository;
|
@Mock private InboundCartonLabelRepository repository;
|
||||||
@Mock private OrderCargoClient orderCargoClient;
|
@Mock private OrderCargoClient orderCargoClient;
|
||||||
|
@Mock private PrintableOrderInboundClient printableOrderInboundClient;
|
||||||
@Mock private InboundOrderCreationService inboundOrderCreationService;
|
@Mock private InboundOrderCreationService inboundOrderCreationService;
|
||||||
private InboundCartonLabelService service;
|
private InboundCartonLabelService service;
|
||||||
|
|
||||||
@@ -36,6 +38,7 @@ class InboundCartonLabelServiceTest {
|
|||||||
service = new InboundCartonLabelService();
|
service = new InboundCartonLabelService();
|
||||||
ReflectionTestUtils.setField(service, "repository", repository);
|
ReflectionTestUtils.setField(service, "repository", repository);
|
||||||
ReflectionTestUtils.setField(service, "orderCargoClient", orderCargoClient);
|
ReflectionTestUtils.setField(service, "orderCargoClient", orderCargoClient);
|
||||||
|
ReflectionTestUtils.setField(service, "printableOrderInboundClient", printableOrderInboundClient);
|
||||||
ReflectionTestUtils.setField(service, "inboundOrderCreationService", inboundOrderCreationService);
|
ReflectionTestUtils.setField(service, "inboundOrderCreationService", inboundOrderCreationService);
|
||||||
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
|
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
|
||||||
}
|
}
|
||||||
@@ -222,7 +225,7 @@ class InboundCartonLabelServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
void rejectsAnOrderWithoutAnOwnedPreparingInbound() {
|
void rejectsAnOrderWithoutAnOwnedPreparingInbound() {
|
||||||
when(repository.findByOrder(10001L, 60001L)).thenReturn(null);
|
when(repository.findByOrder(10001L, 60001L)).thenReturn(null);
|
||||||
when(orderCargoClient.inboundSnapshot("internal-token", 10001L, 60001L))
|
when(printableOrderInboundClient.reserve("internal-token", 10001L, 60001L))
|
||||||
.thenThrow(org.mockito.Mockito.mock(feign.FeignException.class));
|
.thenThrow(org.mockito.Mockito.mock(feign.FeignException.class));
|
||||||
|
|
||||||
assertThatThrownBy(() -> service.requireInboundIdByOrder(10001L, 60001L))
|
assertThatThrownBy(() -> service.requireInboundIdByOrder(10001L, 60001L))
|
||||||
@@ -239,7 +242,7 @@ class InboundCartonLabelServiceTest {
|
|||||||
null, null, null, 2);
|
null, null, null, 2);
|
||||||
InboundOrderEntity repaired = inbound();
|
InboundOrderEntity repaired = inbound();
|
||||||
when(repository.findByOrder(10001L, 60001L)).thenReturn(null, repaired);
|
when(repository.findByOrder(10001L, 60001L)).thenReturn(null, repaired);
|
||||||
when(orderCargoClient.inboundSnapshot("internal-token", 10001L, 60001L))
|
when(printableOrderInboundClient.reserve("internal-token", 10001L, 60001L))
|
||||||
.thenReturn(ApiResponse.success(snapshot));
|
.thenReturn(ApiResponse.success(snapshot));
|
||||||
|
|
||||||
assertThat(service.requireInboundIdByOrder(10001L, 60001L)).isEqualTo(70001L);
|
assertThat(service.requireInboundIdByOrder(10001L, 60001L)).isEqualTo(70001L);
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import com.baiyue.common.core.exception.BizException;
|
|||||||
import com.baiyue.order.entity.OrderCargoEntity;
|
import com.baiyue.order.entity.OrderCargoEntity;
|
||||||
import com.baiyue.order.repository.OrderRepository;
|
import com.baiyue.order.repository.OrderRepository;
|
||||||
import com.baiyue.order.service.OrderApplicationService;
|
import com.baiyue.order.service.OrderApplicationService;
|
||||||
|
import com.baiyue.order.service.PrintableInboundSnapshotService;
|
||||||
import com.baiyue.order.client.FirstlegInboundClient;
|
import com.baiyue.order.client.FirstlegInboundClient;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@@ -29,6 +31,7 @@ import java.util.stream.Collectors;
|
|||||||
@RequestMapping("/internal/order/v1/cargoes")
|
@RequestMapping("/internal/order/v1/cargoes")
|
||||||
public class InternalOrderCargoController {
|
public class InternalOrderCargoController {
|
||||||
@Resource private OrderRepository orders;
|
@Resource private OrderRepository orders;
|
||||||
|
@Resource private PrintableInboundSnapshotService printableInboundSnapshots;
|
||||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||||
private byte[] expectedToken;
|
private byte[] expectedToken;
|
||||||
|
|
||||||
@@ -98,6 +101,18 @@ public class InternalOrderCargoController {
|
|||||||
return ApiResponse.success(OrderApplicationService.inboundCommand(order, packageCount));
|
return ApiResponse.success(OrderApplicationService.inboundCommand(order, packageCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{tenantId}/{orderId}/printable-inbound-snapshot")
|
||||||
|
public ApiResponse<FirstlegInboundClient.OrderApprovedInboundCommand> printableInboundSnapshot(
|
||||||
|
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||||
|
@PathVariable long tenantId,
|
||||||
|
@PathVariable long orderId) {
|
||||||
|
requireToken(token);
|
||||||
|
if (tenantId <= 0 || orderId <= 0) {
|
||||||
|
throw new BizException(ResultCode.BAD_REQUEST, "租户和订单ID必须大于0");
|
||||||
|
}
|
||||||
|
return ApiResponse.success(printableInboundSnapshots.reserve(tenantId, orderId));
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/{tenantId}/{orderId}/measurement-declaration")
|
@GetMapping("/{tenantId}/{orderId}/measurement-declaration")
|
||||||
public ApiResponse<MeasurementDeclarationSnapshot> measurementDeclaration(
|
public ApiResponse<MeasurementDeclarationSnapshot> measurementDeclaration(
|
||||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||||
|
|||||||
@@ -54,6 +54,17 @@ public class OrderRepository {
|
|||||||
.eq(OrderEntity::getId, order.getId())
|
.eq(OrderEntity::getId, order.getId())
|
||||||
.eq(OrderEntity::getTenantId, tenantId));
|
.eq(OrderEntity::getTenantId, tenantId));
|
||||||
}
|
}
|
||||||
|
public int assignInboundCodeIfBlank(long tenantId, long orderId, String inboundCode) {
|
||||||
|
return orderMapper.update(null, new LambdaUpdateWrapper<OrderEntity>()
|
||||||
|
.eq(OrderEntity::getTenantId, tenantId)
|
||||||
|
.eq(OrderEntity::getId, orderId)
|
||||||
|
.eq(OrderEntity::getDeleted, false)
|
||||||
|
.and(wrapper -> wrapper.isNull(OrderEntity::getInboundCode)
|
||||||
|
.or().eq(OrderEntity::getInboundCode, ""))
|
||||||
|
.set(OrderEntity::getInboundCode, inboundCode)
|
||||||
|
.set(OrderEntity::getUpdatedAt, LocalDateTime.now())
|
||||||
|
.setSql("version = version + 1"));
|
||||||
|
}
|
||||||
public int updateDeclarationHeader(long tenantId, long orderId, int expectedVersion, long operatorId,
|
public int updateDeclarationHeader(long tenantId, long orderId, int expectedVersion, long operatorId,
|
||||||
String customerRemark, LocalDateTime expectedArrivalAt,
|
String customerRemark, LocalDateTime expectedArrivalAt,
|
||||||
java.math.BigDecimal estimatedVolumeCbm,
|
java.math.BigDecimal estimatedVolumeCbm,
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.baiyue.order.service;
|
||||||
|
|
||||||
|
import com.baiyue.common.core.api.ResultCode;
|
||||||
|
import com.baiyue.common.core.exception.BizException;
|
||||||
|
import com.baiyue.order.client.FirstlegInboundClient;
|
||||||
|
import com.baiyue.order.config.InboundCodeModeProvider;
|
||||||
|
import com.baiyue.order.entity.OrderCargoEntity;
|
||||||
|
import com.baiyue.order.entity.OrderEntity;
|
||||||
|
import com.baiyue.order.repository.OrderRepository;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/** 为草稿箱唛打印预留稳定入仓码;订单提交时继续复用该码。 */
|
||||||
|
@Service
|
||||||
|
public class PrintableInboundSnapshotService {
|
||||||
|
private final OrderRepository orders;
|
||||||
|
private final OrderPricingChannelGuard pricingChannels;
|
||||||
|
private final InboundCodeModeProvider inboundCodeMode;
|
||||||
|
private final OrderApplicationService orderApplication;
|
||||||
|
|
||||||
|
public PrintableInboundSnapshotService(OrderRepository orders,
|
||||||
|
OrderPricingChannelGuard pricingChannels,
|
||||||
|
InboundCodeModeProvider inboundCodeMode,
|
||||||
|
OrderApplicationService orderApplication) {
|
||||||
|
this.orders = orders;
|
||||||
|
this.pricingChannels = pricingChannels;
|
||||||
|
this.inboundCodeMode = inboundCodeMode;
|
||||||
|
this.orderApplication = orderApplication;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public FirstlegInboundClient.OrderApprovedInboundCommand reserve(long tenantId, long orderId) {
|
||||||
|
OrderEntity order = requireOwned(tenantId, orderId);
|
||||||
|
if (!StringUtils.hasText(order.getInboundCode())) {
|
||||||
|
if (inboundCodeMode.legacyManual()) {
|
||||||
|
throw new BizException(ResultCode.VALIDATION_FAILED, "当前系统使用旧系统入仓码,请先填写入仓码");
|
||||||
|
}
|
||||||
|
String prefix = pricingChannels.requireAvailable(tenantId, order.getReceivingWarehouseId(),
|
||||||
|
order.getChannelCode(), order.getDestinationType());
|
||||||
|
String allocated = orderApplication.nextInboundCode(prefix);
|
||||||
|
if (orders.assignInboundCodeIfBlank(tenantId, orderId, allocated) == 1) {
|
||||||
|
order.setInboundCode(allocated);
|
||||||
|
} else {
|
||||||
|
order = requireOwned(tenantId, orderId);
|
||||||
|
if (!StringUtils.hasText(order.getInboundCode())) {
|
||||||
|
throw new BizException(ResultCode.CONFLICT, "入仓码预留冲突,请刷新后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int packageCount = orders.findCargoInheritedByOrder(tenantId, orderId).stream()
|
||||||
|
.map(OrderCargoEntity::getPackageCount).filter(java.util.Objects::nonNull)
|
||||||
|
.mapToInt(Integer::intValue).sum();
|
||||||
|
return OrderApplicationService.inboundCommand(order, packageCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEntity requireOwned(long tenantId, long orderId) {
|
||||||
|
OrderEntity order = orders.findOwned(tenantId, orderId);
|
||||||
|
if (order == null) {
|
||||||
|
throw new BizException(ResultCode.NOT_FOUND, "订单不存在或不属于当前客户");
|
||||||
|
}
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,23 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema: {$ref: '#/components/schemas/CargoLabelBindingListEnvelope'}
|
schema: {$ref: '#/components/schemas/CargoLabelBindingListEnvelope'}
|
||||||
'403': {description: 内部服务令牌无效}
|
'403': {description: 内部服务令牌无效}
|
||||||
|
/internal/order/v1/cargoes/{tenantId}/{orderId}/printable-inbound-snapshot:
|
||||||
|
post:
|
||||||
|
security: []
|
||||||
|
summary: 为 Firstleg 箱唛打印读取并预留稳定入仓快照
|
||||||
|
description: >-
|
||||||
|
仅接受内部服务令牌。若 AUTO 模式草稿尚无入仓码,则按订单渠道原子预留全局唯一入仓码;
|
||||||
|
并发重放返回已胜出的同一码,后续订单提交继续复用,避免已打印二维码失效。
|
||||||
|
parameters:
|
||||||
|
- name: X-Internal-Service-Token
|
||||||
|
in: header
|
||||||
|
required: true
|
||||||
|
schema: {type: string}
|
||||||
|
- {name: tenantId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||||
|
- {name: orderId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||||
|
responses:
|
||||||
|
'200': {description: 返回带稳定入仓码及总箱数的订单快照}
|
||||||
|
'403': {description: 内部服务令牌无效}
|
||||||
/internal/order/v1/pending-review-orders:
|
/internal/order/v1/pending-review-orders:
|
||||||
get:
|
get:
|
||||||
security: []
|
security: []
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import com.baiyue.order.entity.OrderCargoEntity;
|
|||||||
import com.baiyue.order.entity.OrderEntity;
|
import com.baiyue.order.entity.OrderEntity;
|
||||||
import com.baiyue.order.entity.OrderInternalNoteEntity;
|
import com.baiyue.order.entity.OrderInternalNoteEntity;
|
||||||
import com.baiyue.order.repository.OrderRepository;
|
import com.baiyue.order.repository.OrderRepository;
|
||||||
|
import com.baiyue.order.service.OrderApplicationService;
|
||||||
|
import com.baiyue.order.service.PrintableInboundSnapshotService;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
@@ -23,11 +25,13 @@ import java.lang.reflect.Method;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
|
||||||
@SpringJUnitConfig(InternalOrderCargoController.class)
|
@SpringJUnitConfig(InternalOrderCargoController.class)
|
||||||
@TestPropertySource(properties = "baiyue.security.internal-service-token=internal-token")
|
@TestPropertySource(properties = "baiyue.security.internal-service-token=internal-token")
|
||||||
class InternalOrderCargoControllerTest {
|
class InternalOrderCargoControllerTest {
|
||||||
@MockBean private OrderRepository orders;
|
@MockBean private OrderRepository orders;
|
||||||
|
@MockBean private PrintableInboundSnapshotService printableInboundSnapshots;
|
||||||
@Resource private InternalOrderCargoController controller;
|
@Resource private InternalOrderCargoController controller;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -145,6 +149,24 @@ class InternalOrderCargoControllerTest {
|
|||||||
verify(orders).findOwned(11L, 21L);
|
verify(orders).findOwned(11L, 21L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reservesPrintableSnapshotThroughExplicitCommandRoute() throws Exception {
|
||||||
|
OrderEntity order = new OrderEntity();
|
||||||
|
order.setTenantId(11L); order.setId(21L); order.setDestinationType("FBA");
|
||||||
|
order.setTransportMode("SEA");
|
||||||
|
var snapshot = OrderApplicationService.inboundCommand(order, 3);
|
||||||
|
when(printableInboundSnapshots.reserve(11L, 21L)).thenReturn(snapshot);
|
||||||
|
|
||||||
|
var response = controller.printableInboundSnapshot("internal-token", 11L, 21L);
|
||||||
|
|
||||||
|
assertThat(response.data()).isSameAs(snapshot);
|
||||||
|
verify(printableInboundSnapshots).reserve(11L, 21L);
|
||||||
|
Method route = InternalOrderCargoController.class.getMethod(
|
||||||
|
"printableInboundSnapshot", String.class, long.class, long.class);
|
||||||
|
assertThat(route.getAnnotation(PostMapping.class).value())
|
||||||
|
.containsExactly("/{tenantId}/{orderId}/printable-inbound-snapshot");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void measurementDeclarationUsesOrderReferenceAndSumsCargoFacts() {
|
void measurementDeclarationUsesOrderReferenceAndSumsCargoFacts() {
|
||||||
OrderEntity order = new OrderEntity();
|
OrderEntity order = new OrderEntity();
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package com.baiyue.order.controller;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class PrintableInboundSnapshotOpenApiContractTest {
|
||||||
|
@Test
|
||||||
|
void documentsTheMutatingInternalCommandAsPost() throws Exception {
|
||||||
|
try (var stream = getClass().getResourceAsStream("/openapi/order-v1.yaml")) {
|
||||||
|
assertThat(stream).isNotNull();
|
||||||
|
String yaml = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
assertThat(yaml).contains(
|
||||||
|
"/internal/order/v1/cargoes/{tenantId}/{orderId}/printable-inbound-snapshot:",
|
||||||
|
"若 AUTO 模式草稿尚无入仓码,则按订单渠道原子预留全局唯一入仓码",
|
||||||
|
"后续订单提交继续复用");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
package com.baiyue.order.service;
|
||||||
|
|
||||||
|
import com.baiyue.common.core.exception.BizException;
|
||||||
|
import com.baiyue.order.config.InboundCodeModeProvider;
|
||||||
|
import com.baiyue.order.entity.OrderCargoEntity;
|
||||||
|
import com.baiyue.order.entity.OrderEntity;
|
||||||
|
import com.baiyue.order.repository.OrderRepository;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class PrintableInboundSnapshotServiceTest {
|
||||||
|
@Test
|
||||||
|
void reservesStableCodeForDraftAndReturnsPrintableSnapshot() {
|
||||||
|
OrderRepository orders = mock(OrderRepository.class);
|
||||||
|
OrderPricingChannelGuard pricing = mock(OrderPricingChannelGuard.class);
|
||||||
|
OrderApplicationService application = mock(OrderApplicationService.class);
|
||||||
|
OrderEntity order = draft();
|
||||||
|
OrderCargoEntity cargo = new OrderCargoEntity();
|
||||||
|
cargo.setPackageCount(3);
|
||||||
|
when(orders.findOwned(11L, 21L)).thenReturn(order);
|
||||||
|
when(pricing.requireAvailable(11L, 41L, "CH", "FBA")).thenReturn("PCHK");
|
||||||
|
when(application.nextInboundCode("PCHK")).thenReturn("PCHK2609080001");
|
||||||
|
when(orders.assignInboundCodeIfBlank(11L, 21L, "PCHK2609080001")).thenReturn(1);
|
||||||
|
when(orders.findCargoInheritedByOrder(11L, 21L)).thenReturn(List.of(cargo));
|
||||||
|
|
||||||
|
var result = service(orders, pricing, application, "AUTO").reserve(11L, 21L);
|
||||||
|
|
||||||
|
assertThat(result.inboundCode()).isEqualTo("PCHK2609080001");
|
||||||
|
assertThat(result.totalPackageCount()).isEqualTo(3);
|
||||||
|
verify(orders).assignInboundCodeIfBlank(11L, 21L, "PCHK2609080001");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reusesCodeAlreadyReservedByAConcurrentRequest() {
|
||||||
|
OrderRepository orders = mock(OrderRepository.class);
|
||||||
|
OrderPricingChannelGuard pricing = mock(OrderPricingChannelGuard.class);
|
||||||
|
OrderApplicationService application = mock(OrderApplicationService.class);
|
||||||
|
OrderEntity initial = draft();
|
||||||
|
OrderEntity winner = draft();
|
||||||
|
winner.setInboundCode("PCHK2609080002");
|
||||||
|
when(orders.findOwned(11L, 21L)).thenReturn(initial, winner);
|
||||||
|
when(pricing.requireAvailable(11L, 41L, "CH", "FBA")).thenReturn("PCHK");
|
||||||
|
when(application.nextInboundCode("PCHK")).thenReturn("PCHK2609080001");
|
||||||
|
when(orders.assignInboundCodeIfBlank(11L, 21L, "PCHK2609080001")).thenReturn(0);
|
||||||
|
when(orders.findCargoInheritedByOrder(11L, 21L)).thenReturn(List.of());
|
||||||
|
|
||||||
|
var result = service(orders, pricing, application, "AUTO").reserve(11L, 21L);
|
||||||
|
|
||||||
|
assertThat(result.inboundCode()).isEqualTo("PCHK2609080002");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void existingCodeDoesNotAllocateAnotherSequence() {
|
||||||
|
OrderRepository orders = mock(OrderRepository.class);
|
||||||
|
OrderPricingChannelGuard pricing = mock(OrderPricingChannelGuard.class);
|
||||||
|
OrderApplicationService application = mock(OrderApplicationService.class);
|
||||||
|
OrderEntity order = draft();
|
||||||
|
order.setInboundCode("PCHK2609080001");
|
||||||
|
when(orders.findOwned(11L, 21L)).thenReturn(order);
|
||||||
|
when(orders.findCargoInheritedByOrder(11L, 21L)).thenReturn(List.of());
|
||||||
|
|
||||||
|
var result = service(orders, pricing, application, "AUTO").reserve(11L, 21L);
|
||||||
|
|
||||||
|
assertThat(result.inboundCode()).isEqualTo("PCHK2609080001");
|
||||||
|
verify(application, never()).nextInboundCode(org.mockito.ArgumentMatchers.anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void legacyManualModeRejectsMissingCodeWithoutWriting() {
|
||||||
|
OrderRepository orders = mock(OrderRepository.class);
|
||||||
|
OrderPricingChannelGuard pricing = mock(OrderPricingChannelGuard.class);
|
||||||
|
OrderApplicationService application = mock(OrderApplicationService.class);
|
||||||
|
when(orders.findOwned(11L, 21L)).thenReturn(draft());
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> service(orders, pricing, application, "LEGACY_MANUAL").reserve(11L, 21L))
|
||||||
|
.isInstanceOf(BizException.class)
|
||||||
|
.hasMessage("当前系统使用旧系统入仓码,请先填写入仓码");
|
||||||
|
|
||||||
|
verify(orders, never()).assignInboundCodeIfBlank(
|
||||||
|
org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyLong(),
|
||||||
|
org.mockito.ArgumentMatchers.anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrintableInboundSnapshotService service(OrderRepository orders,
|
||||||
|
OrderPricingChannelGuard pricing,
|
||||||
|
OrderApplicationService application,
|
||||||
|
String mode) {
|
||||||
|
return new PrintableInboundSnapshotService(
|
||||||
|
orders, pricing, new InboundCodeModeProvider(mode), application);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEntity draft() {
|
||||||
|
OrderEntity order = new OrderEntity();
|
||||||
|
order.setId(21L);
|
||||||
|
order.setTenantId(11L);
|
||||||
|
order.setOrderNo("FL21");
|
||||||
|
order.setCustomerId(11L);
|
||||||
|
order.setReceivingWarehouseId(41L);
|
||||||
|
order.setChannelCode("CH");
|
||||||
|
order.setDestinationType("FBA");
|
||||||
|
order.setTransportMode("SEA");
|
||||||
|
order.setFulfillmentType("STANDARD");
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user