优化
This commit is contained in:
@@ -10,11 +10,13 @@ import com.baiyue.order.dto.MerchantBatchDraftDtos;
|
||||
import com.baiyue.order.dto.MerchantInboundOrderQuery;
|
||||
import com.baiyue.order.dto.MerchantInboundOrderRow;
|
||||
import com.baiyue.order.dto.InboundCodePolicyResponse;
|
||||
import com.baiyue.order.dto.InboundCodeAssociationSummary;
|
||||
import com.baiyue.order.entity.OrderStatus;
|
||||
import com.baiyue.order.service.OrderApplicationService;
|
||||
import com.baiyue.order.service.OrderScopeGuard;
|
||||
import com.baiyue.order.service.MerchantInboundOrderListService;
|
||||
import com.baiyue.order.service.MerchantBatchDraftService;
|
||||
import com.baiyue.order.service.InboundCodeAssociationService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
@@ -50,6 +52,8 @@ public class MerchantFirstLegOrderController {
|
||||
private MerchantInboundOrderListService inboundOrderListService;
|
||||
@Resource
|
||||
private MerchantBatchDraftService batchDraftService;
|
||||
@Resource
|
||||
private InboundCodeAssociationService inboundCodeAssociations;
|
||||
|
||||
@GetMapping("/inbound-code-policy")
|
||||
@PreAuthorize("hasAnyAuthority('merchant:order:list', 'merchant:order:create', 'merchant:order:edit')")
|
||||
@@ -57,6 +61,17 @@ public class MerchantFirstLegOrderController {
|
||||
return ApiResponse.success(service.inboundCodePolicy());
|
||||
}
|
||||
|
||||
@GetMapping("/inbound-code-associations/{inboundCode}")
|
||||
@PreAuthorize("hasAuthority('merchant:order:list')")
|
||||
@Operation(summary = "按入仓码查询本客户订单基础关联",
|
||||
description = "客户范围只取登录态;其他客户的入仓码按不存在处理。")
|
||||
public ApiResponse<InboundCodeAssociationSummary> inboundCodeAssociation(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String inboundCode) {
|
||||
return ApiResponse.success(inboundCodeAssociations.findForCustomer(
|
||||
OrderScopeGuard.merchant(user), inboundCode));
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "ORDER_CREATE", resourceType = "FIRST_LEG_ORDER", description = "Create draft / 新建草稿")
|
||||
@Operation(summary = "Create draft / 新建草稿", description = "Creates a first-leg draft order in the authenticated merchant tenant. / 在当前认证商户租户下创建头程订单草稿。")
|
||||
@ApiResponses({
|
||||
|
||||
@@ -9,12 +9,14 @@ import com.baiyue.order.dto.BatchOrderReviewResponse;
|
||||
import com.baiyue.order.dto.OrderDetailResponse;
|
||||
import com.baiyue.order.dto.OrderRequests;
|
||||
import com.baiyue.order.dto.InboundCodePolicyResponse;
|
||||
import com.baiyue.order.dto.InboundCodeAssociationSummary;
|
||||
import com.baiyue.order.dto.TenantOrderCountResponse;
|
||||
import com.baiyue.order.dto.TenantOrderStatisticsResponse;
|
||||
import com.baiyue.order.entity.OrderEntity;
|
||||
import com.baiyue.order.entity.OrderStatus;
|
||||
import com.baiyue.order.service.OrderApplicationService;
|
||||
import com.baiyue.order.service.OrderScopeGuard;
|
||||
import com.baiyue.order.service.InboundCodeAssociationService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
@@ -47,6 +49,8 @@ import java.util.List;
|
||||
public class PlatformFirstLegOrderController {
|
||||
@Resource
|
||||
private OrderApplicationService service;
|
||||
@Resource
|
||||
private InboundCodeAssociationService inboundCodeAssociations;
|
||||
|
||||
@GetMapping("/inbound-code-policy")
|
||||
@PreAuthorize("hasAnyAuthority('platform:order:list', 'platform:order:create', 'platform:order:edit', 'platform:order:submit')")
|
||||
@@ -54,6 +58,17 @@ public class PlatformFirstLegOrderController {
|
||||
return ApiResponse.success(service.inboundCodePolicy());
|
||||
}
|
||||
|
||||
@GetMapping("/inbound-code-associations/{inboundCode}")
|
||||
@PreAuthorize("hasAuthority('platform:order:list')")
|
||||
@Operation(summary = "按入仓码查询订单基础关联",
|
||||
description = "入仓码和订单号是主要展示键;Long ID 仅供路由关联。")
|
||||
public ApiResponse<InboundCodeAssociationSummary> inboundCodeAssociation(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String inboundCode) {
|
||||
OrderScopeGuard.platform(user);
|
||||
return ApiResponse.success(inboundCodeAssociations.findForPlatform(inboundCode));
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "ORDER_CREATE", resourceType = "FIRST_LEG_ORDER", description = "平台代客创建草稿")
|
||||
@Operation(summary = "平台代客创建草稿", description = "在明确指定的目标租户内创建头程订单草稿,必须携带幂等键。")
|
||||
@ApiResponses({
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.baiyue.order.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/** 以全局唯一入仓码定位的订单基础关联摘要;技术 ID 统一使用字符串。 */
|
||||
public record InboundCodeAssociationSummary(
|
||||
@Schema(description = "主要展示键:全局唯一入仓码") String inboundCode,
|
||||
@Schema(description = "主要展示键:订单业务号") String orderNo,
|
||||
@Schema(description = "订单交易状态") String orderStatus,
|
||||
@Schema(description = "订单内部 ID,仅供路由关联") String orderId,
|
||||
@Schema(description = "客户 ID,字符串避免前端精度损失") String customerId,
|
||||
String customerName,
|
||||
String destinationType,
|
||||
String destinationCountryCode,
|
||||
@Schema(description = "目的仓内部 ID,仅供路由关联") String destinationWarehouseId,
|
||||
@Schema(description = "目的仓业务编码;FBA 场景为 Amazon 仓码") String destinationWarehouseCode,
|
||||
String destinationWarehouseName
|
||||
) {
|
||||
}
|
||||
@@ -121,6 +121,21 @@ public class OrderRepository {
|
||||
.eq(OrderEntity::getId, orderId));
|
||||
}
|
||||
|
||||
/** 平台按数据库全局唯一键查询,不接受客户筛选参数。 */
|
||||
public OrderEntity findByInboundCode(String inboundCode) {
|
||||
return orderMapper.selectOne(new LambdaQueryWrapper<OrderEntity>()
|
||||
.eq(OrderEntity::getInboundCode, inboundCode)
|
||||
.eq(OrderEntity::getDeleted, false));
|
||||
}
|
||||
|
||||
/** 客户查询必须在 SQL 条件中同时绑定登录客户与入仓码。 */
|
||||
public OrderEntity findOwnedByInboundCode(long tenantId, String inboundCode) {
|
||||
return orderMapper.selectOne(new LambdaQueryWrapper<OrderEntity>()
|
||||
.eq(OrderEntity::getTenantId, tenantId)
|
||||
.eq(OrderEntity::getInboundCode, inboundCode)
|
||||
.eq(OrderEntity::getDeleted, false));
|
||||
}
|
||||
|
||||
public boolean inboundCodeExists(String inboundCode, Long excludedOrderId) {
|
||||
if (!StringUtils.hasText(inboundCode)) return false;
|
||||
return orderMapper.selectCount(new LambdaQueryWrapper<OrderEntity>()
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.baiyue.order.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.order.dto.InboundCodeAssociationSummary;
|
||||
import com.baiyue.order.entity.OrderEntity;
|
||||
import com.baiyue.order.repository.OrderRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** 从 Order 权威快照按入仓码读取只读基础关联。 */
|
||||
@Service
|
||||
public class InboundCodeAssociationService {
|
||||
private static final Pattern INBOUND_CODE = Pattern.compile("[A-Za-z0-9._:-]{1,40}");
|
||||
|
||||
private final OrderRepository repository;
|
||||
|
||||
public InboundCodeAssociationService(OrderRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public InboundCodeAssociationSummary findForPlatform(String inboundCode) {
|
||||
String normalized = normalize(inboundCode);
|
||||
return summary(require(repository.findByInboundCode(normalized)), normalized);
|
||||
}
|
||||
|
||||
public InboundCodeAssociationSummary findForCustomer(long tenantId, String inboundCode) {
|
||||
if (tenantId <= 0) throw new BizException(ResultCode.FORBIDDEN, "Customer scope is required");
|
||||
String normalized = normalize(inboundCode);
|
||||
return summary(require(repository.findOwnedByInboundCode(tenantId, normalized)), normalized);
|
||||
}
|
||||
|
||||
private String normalize(String value) {
|
||||
String normalized = value == null ? "" : value.trim();
|
||||
if (!INBOUND_CODE.matcher(normalized).matches()) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "入仓码格式无效");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private OrderEntity require(OrderEntity order) {
|
||||
if (order == null) throw new BizException(ResultCode.NOT_FOUND, "入仓码关联订单不存在");
|
||||
return order;
|
||||
}
|
||||
|
||||
private InboundCodeAssociationSummary summary(OrderEntity order, String inboundCode) {
|
||||
return new InboundCodeAssociationSummary(
|
||||
inboundCode,
|
||||
order.getOrderNo(),
|
||||
order.getStatus() == null ? null : order.getStatus().name(),
|
||||
string(order.getId()),
|
||||
string(order.getCustomerId()),
|
||||
order.getCustomerName(),
|
||||
order.getDestinationType(),
|
||||
order.getDestinationCountryCode(),
|
||||
string(order.getDestinationWarehouseId()),
|
||||
order.getAmazonWarehouseCode(),
|
||||
order.getDestinationWarehouseName());
|
||||
}
|
||||
|
||||
private String string(Long value) {
|
||||
return value == null ? null : value.toString();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ openapi: 3.0.3
|
||||
info:
|
||||
title: 柏悦头程订单 API
|
||||
description: 业务成功和失败均使用 HTTP 200;仅响应体 code 为字符串 "200" 时表示成功,其他 code 表示失败。
|
||||
version: 1.6.0
|
||||
version: 1.7.0
|
||||
security:
|
||||
- bearerAuth: []
|
||||
paths:
|
||||
@@ -21,6 +21,21 @@ paths:
|
||||
code: {type: string, example: '200'}
|
||||
data: {$ref: '#/components/schemas/InboundCodePolicy'}
|
||||
'403': {description: 需要平台订单列表、创建、编辑或提交权限之一}
|
||||
/api/platform/v1/orders/first-leg/inbound-code-associations/{inboundCode}:
|
||||
get:
|
||||
summary: 平台按全局入仓码查询订单基础关联
|
||||
description: >-
|
||||
需要 platform:order:list。入仓码和订单业务号是主要展示键;Long ID 均以字符串返回,
|
||||
只供页面路由和后续接口关联。查询直接使用 Order 全局唯一入仓码事实,不跨库读取 Firstleg。
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/InboundCodePath'
|
||||
responses:
|
||||
'200':
|
||||
description: 查询成功或业务失败,按响应体 code 判断
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/InboundCodeAssociationEnvelope'}
|
||||
'403': {description: 需要平台身份和 platform:order:list 权限}
|
||||
/api/platform/v1/orders:
|
||||
get:
|
||||
summary: 平台统一订单列表
|
||||
@@ -187,6 +202,21 @@ paths:
|
||||
schema: {$ref: '#/components/schemas/FirstLegOrderDraft'}
|
||||
responses:
|
||||
'200': {description: 成功或业务失败,按响应体 code 判断}
|
||||
/api/merchant/v1/orders/first-leg/inbound-code-associations/{inboundCode}:
|
||||
get:
|
||||
summary: 客户按入仓码查询本客户订单基础关联
|
||||
description: >-
|
||||
需要 merchant:order:list。客户范围仅取登录态,不接受客户 ID 参数;其他客户的入仓码统一按不存在处理。
|
||||
入仓码和订单业务号是主要展示键,Long ID 只供路由关联。
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/InboundCodePath'
|
||||
responses:
|
||||
'200':
|
||||
description: 查询成功、不存在或其他业务失败,按响应体 code 判断
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/InboundCodeAssociationEnvelope'}
|
||||
'403': {description: 需要客户身份和 merchant:order:list 权限}
|
||||
/api/merchant/v1/orders/first-leg/batch:
|
||||
post:
|
||||
summary: 批量创建头程订单草稿
|
||||
@@ -671,6 +701,27 @@ components:
|
||||
properties:
|
||||
mode: {type: string, enum: [AUTO, LEGACY_MANUAL]}
|
||||
manualEntryRequired: {type: boolean}
|
||||
InboundCodeAssociationEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
code: {type: string, example: '200'}
|
||||
message: {type: string}
|
||||
data: {$ref: '#/components/schemas/InboundCodeAssociationSummary'}
|
||||
InboundCodeAssociationSummary:
|
||||
type: object
|
||||
required: [inboundCode, orderNo, orderStatus, orderId, customerId, customerName, destinationType]
|
||||
properties:
|
||||
inboundCode: {type: string, maxLength: 40, pattern: '^[A-Za-z0-9._:-]+$', description: 主要展示键:全局唯一入仓码}
|
||||
orderNo: {type: string, description: 主要展示键:订单业务号}
|
||||
orderStatus: {type: string, description: Order 权威交易状态}
|
||||
orderId: {type: string, pattern: '^[1-9][0-9]*$', description: 内部 ID,仅供路由关联}
|
||||
customerId: {type: string, pattern: '^[1-9][0-9]*$', description: 客户 ID,字符串避免精度损失}
|
||||
customerName: {type: string}
|
||||
destinationType: {type: string}
|
||||
destinationCountryCode: {type: string, nullable: true}
|
||||
destinationWarehouseId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: 内部 ID,仅供路由关联}
|
||||
destinationWarehouseCode: {type: string, nullable: true, description: 目的仓业务编码;FBA 场景为 Amazon 仓码}
|
||||
destinationWarehouseName: {type: string, nullable: true}
|
||||
FirstLegBatchDraftItem:
|
||||
type: object
|
||||
required: [clientRowId, draft]
|
||||
@@ -778,6 +829,12 @@ components:
|
||||
packageCount: {type: integer}
|
||||
loadingPlanId: {type: string}
|
||||
parameters:
|
||||
InboundCodePath:
|
||||
name: inboundCode
|
||||
in: path
|
||||
required: true
|
||||
schema: {type: string, minLength: 1, maxLength: 40, pattern: '^[A-Za-z0-9._:-]+$'}
|
||||
description: 全局唯一业务入仓码
|
||||
InternalToken:
|
||||
name: X-Internal-Service-Token
|
||||
in: header
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.baiyue.order.controller;
|
||||
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.order.dto.InboundCodeAssociationSummary;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class InboundCodeAssociationContractTest {
|
||||
@Test
|
||||
void platformAndCustomerEndpointsUseListPermissionAndSameSummary() throws Exception {
|
||||
assertEndpoint(PlatformFirstLegOrderController.class, "platform:order:list");
|
||||
assertEndpoint(MerchantFirstLegOrderController.class, "merchant:order:list");
|
||||
}
|
||||
|
||||
@Test
|
||||
void openApiMakesBusinessKeysPrimaryAndRouteIdsStrings() throws Exception {
|
||||
String openapi = new ClassPathResource("openapi/order-v1.yaml")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(openapi)
|
||||
.contains("/api/platform/v1/orders/first-leg/inbound-code-associations/{inboundCode}:")
|
||||
.contains("/api/merchant/v1/orders/first-leg/inbound-code-associations/{inboundCode}:")
|
||||
.contains("InboundCodeAssociationSummary:")
|
||||
.contains("入仓码和订单业务号是主要展示键")
|
||||
.contains("orderId: {type: string")
|
||||
.contains("customerId: {type: string")
|
||||
.contains("destinationWarehouseId: {type: string")
|
||||
.contains("其他客户的入仓码统一按不存在处理")
|
||||
.doesNotContain("tenantId, in: query, required: true, description: 入仓码关联查询");
|
||||
}
|
||||
|
||||
private void assertEndpoint(Class<?> controller, String authority) throws Exception {
|
||||
var method = controller.getDeclaredMethod(
|
||||
"inboundCodeAssociation", AuthenticatedUser.class, String.class);
|
||||
assertThat(method.getAnnotation(GetMapping.class).value())
|
||||
.containsExactly("/inbound-code-associations/{inboundCode}");
|
||||
assertThat(method.getAnnotation(PreAuthorize.class).value())
|
||||
.isEqualTo("hasAuthority('" + authority + "')");
|
||||
assertThat(method.getGenericReturnType().getTypeName())
|
||||
.contains(InboundCodeAssociationSummary.class.getName());
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ class OrderControllerPermissionTest {
|
||||
AuthenticatedUser.class, MerchantInboundOrderQuery.class);
|
||||
assertAuthority(MerchantFirstLegOrderController.class, "detail", "hasAuthority('merchant:order:view')",
|
||||
AuthenticatedUser.class, long.class);
|
||||
assertAuthority(MerchantFirstLegOrderController.class, "inboundCodeAssociation",
|
||||
"hasAuthority('merchant:order:list')", AuthenticatedUser.class, String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -31,6 +33,8 @@ class OrderControllerPermissionTest {
|
||||
AuthenticatedUser.class, long.class, long.class);
|
||||
assertAuthority(PlatformFirstLegOrderController.class, "tenantCounts", "hasAuthority('platform:order:list')",
|
||||
AuthenticatedUser.class, List.class);
|
||||
assertAuthority(PlatformFirstLegOrderController.class, "inboundCodeAssociation",
|
||||
"hasAuthority('platform:order:list')", AuthenticatedUser.class, String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.baiyue.order.dto.BatchOrderReviewResponse;
|
||||
import com.baiyue.order.dto.OrderRequests;
|
||||
import com.baiyue.order.entity.OrderStatus;
|
||||
import com.baiyue.order.service.OrderApplicationService;
|
||||
import com.baiyue.order.service.InboundCodeAssociationService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
@@ -26,6 +27,7 @@ import static org.mockito.Mockito.verifyNoInteractions;
|
||||
@SpringJUnitConfig(PlatformFirstLegOrderController.class)
|
||||
class PlatformOrderPageControllerTest {
|
||||
@MockBean private OrderApplicationService service;
|
||||
@MockBean private InboundCodeAssociationService inboundCodeAssociations;
|
||||
@Resource private PlatformFirstLegOrderController controller;
|
||||
private final OrderRequests.QueryRequest query = new OrderRequests.QueryRequest(
|
||||
null, null, null, null, null, null, 1, 20);
|
||||
|
||||
+30
@@ -126,6 +126,36 @@ class OrderRepositoryIsolationUpdateTest {
|
||||
.contains("FIRST_LEG_ORDER", "PENDING");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
void customerInboundCodeLookupBindsCustomerAndGlobalBusinessKey() {
|
||||
OrderMapper mapper = mock(OrderMapper.class);
|
||||
OrderRepository repository = repository("orderMapper", mapper);
|
||||
|
||||
repository.findOwnedByInboundCode(11L, "ZBYHK2608300001");
|
||||
|
||||
ArgumentCaptor<Wrapper<OrderEntity>> wrapper = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(mapper).selectOne(wrapper.capture());
|
||||
assertThat(wrapper.getValue().getSqlSegment()).contains("tenant_id", "inbound_code", "deleted");
|
||||
assertThat(params(wrapper.getValue()).values()).contains(11L, "ZBYHK2608300001", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
void platformInboundCodeLookupUsesGlobalUniqueBusinessKeyWithoutCustomerPredicate() {
|
||||
OrderMapper mapper = mock(OrderMapper.class);
|
||||
OrderRepository repository = repository("orderMapper", mapper);
|
||||
|
||||
repository.findByInboundCode("ZBYHK2608300001");
|
||||
|
||||
ArgumentCaptor<Wrapper<OrderEntity>> wrapper = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(mapper).selectOne(wrapper.capture());
|
||||
assertThat(wrapper.getValue().getSqlSegment())
|
||||
.contains("inbound_code", "deleted")
|
||||
.doesNotContain("tenant_id");
|
||||
assertThat(params(wrapper.getValue()).values()).contains("ZBYHK2608300001", false);
|
||||
}
|
||||
|
||||
private static <T> void initialize(Class<T> entityType, String resource) {
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), resource), entityType);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.baiyue.order.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.order.entity.OrderEntity;
|
||||
import com.baiyue.order.entity.OrderStatus;
|
||||
import com.baiyue.order.repository.OrderRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
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.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class InboundCodeAssociationServiceTest {
|
||||
private final OrderRepository repository = mock(OrderRepository.class);
|
||||
private final InboundCodeAssociationService service = new InboundCodeAssociationService(repository);
|
||||
|
||||
@Test
|
||||
void platformReadsGlobalBusinessKeyAndReturnsStringRouteIds() {
|
||||
when(repository.findByInboundCode("ZBYHK2608300001")).thenReturn(order());
|
||||
|
||||
var summary = service.findForPlatform(" ZBYHK2608300001 ");
|
||||
|
||||
assertThat(summary.inboundCode()).isEqualTo("ZBYHK2608300001");
|
||||
assertThat(summary.orderNo()).isEqualTo("FL202608300001");
|
||||
assertThat(summary.orderStatus()).isEqualTo("APPROVED");
|
||||
assertThat(summary.orderId()).isEqualTo("9007199254740993");
|
||||
assertThat(summary.customerId()).isEqualTo("9007199254740995");
|
||||
assertThat(summary.customerName()).isEqualTo("客户甲");
|
||||
assertThat(summary.destinationType()).isEqualTo("FBA");
|
||||
assertThat(summary.destinationCountryCode()).isEqualTo("US");
|
||||
assertThat(summary.destinationWarehouseId()).isEqualTo("9007199254740997");
|
||||
assertThat(summary.destinationWarehouseCode()).isEqualTo("ONT8");
|
||||
assertThat(summary.destinationWarehouseName()).isEqualTo("ONT8 仓");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customerQueryAlwaysUsesAuthenticatedCustomerScope() {
|
||||
when(repository.findOwnedByInboundCode(10001L, "ZBYHK2608300001")).thenReturn(order());
|
||||
|
||||
service.findForCustomer(10001L, "ZBYHK2608300001");
|
||||
|
||||
verify(repository).findOwnedByInboundCode(10001L, "ZBYHK2608300001");
|
||||
verify(repository, never()).findByInboundCode("ZBYHK2608300001");
|
||||
}
|
||||
|
||||
@Test
|
||||
void anotherCustomersCodeIsIndistinguishableFromMissingCode() {
|
||||
when(repository.findOwnedByInboundCode(10001L, "ZBYHK2608300001")).thenReturn(null);
|
||||
|
||||
assertThatThrownBy(() -> service.findForCustomer(10001L, "ZBYHK2608300001"))
|
||||
.isInstanceOfSatisfying(BizException.class,
|
||||
error -> assertThat(error.getResultCode()).isEqualTo(ResultCode.NOT_FOUND))
|
||||
.hasMessage("入仓码关联订单不存在");
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidInboundCodeIsRejectedBeforeRepositoryAccess() {
|
||||
assertThatThrownBy(() -> service.findForPlatform("bad/code"))
|
||||
.isInstanceOfSatisfying(BizException.class,
|
||||
error -> assertThat(error.getResultCode()).isEqualTo(ResultCode.BAD_REQUEST));
|
||||
verifyNoInteractions(repository);
|
||||
}
|
||||
|
||||
private OrderEntity order() {
|
||||
OrderEntity order = new OrderEntity();
|
||||
order.setId(9007199254740993L);
|
||||
order.setOrderNo("FL202608300001");
|
||||
order.setStatus(OrderStatus.APPROVED);
|
||||
order.setCustomerId(9007199254740995L);
|
||||
order.setCustomerName("客户甲");
|
||||
order.setDestinationType("FBA");
|
||||
order.setDestinationCountryCode("US");
|
||||
order.setDestinationWarehouseId(9007199254740997L);
|
||||
order.setAmazonWarehouseCode("ONT8");
|
||||
order.setDestinationWarehouseName("ONT8 仓");
|
||||
return order;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user