优化
This commit is contained in:
@@ -12,7 +12,8 @@ public interface WarehouseClient {
|
||||
ApiResponse<Detail> active(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@PathVariable("tenantId") long tenantId, @PathVariable("id") long id);
|
||||
record Detail(Warehouse warehouse, List<Address> addresses, List<Capability> capabilities) { }
|
||||
record Warehouse(Long id, Long tenantId, String warehouseName, String status) { }
|
||||
record Warehouse(Long id, Long tenantId, String warehouseCode, String warehouseName,
|
||||
String warehouseType, String countryCode, String status) { }
|
||||
record Address(String countryCode, String stateProvince, String city, String district,
|
||||
String postalCode, String addressLine1, String addressLine2) { }
|
||||
record Capability(String capabilityCode, Boolean enabled, LocalTime cutoffTime) { }
|
||||
|
||||
@@ -81,7 +81,7 @@ public class OrderApplicationService {
|
||||
if (target == OrderStatus.SUBMITTED) {
|
||||
validatePersistedPrototypeFields(tenantId, order);
|
||||
masterDataGuard.validatePersisted(tenantId, order.getCustomerId(), order.getReceivingWarehouseId(),
|
||||
order.getDestinationWarehouseId());
|
||||
order.getDestinationWarehouseId(), order.getDestinationType());
|
||||
}
|
||||
if ((target == OrderStatus.REJECTED || target == OrderStatus.CANCELLED) && !StringUtils.hasText(reason)) {
|
||||
throw new BizException("Reason is required");
|
||||
@@ -274,7 +274,8 @@ public class OrderApplicationService {
|
||||
private void apply(OrderEntity order, OrderRequests.SaveRequest request, OrderMasterDataGuard.Snapshots snapshots) {
|
||||
order.setCustomerId(snapshots.partner().id()); order.setCustomerName(snapshots.partner().partnerName());
|
||||
order.setTransportMode(request.transportMode()); order.setTradeTerm(request.tradeTerm());
|
||||
order.setDestinationCountryCode(request.destinationCountryCode());
|
||||
order.setDestinationCountryCode(snapshots.destination() == null ? request.destinationCountryCode()
|
||||
: snapshots.destination().warehouse().countryCode());
|
||||
order.setDestinationWarehouseId(request.destinationWarehouseId());
|
||||
order.setDestinationWarehouseName(snapshots.destination() == null ? request.destinationWarehouseName()
|
||||
: snapshots.destination().warehouse().warehouseName());
|
||||
@@ -286,7 +287,9 @@ public class OrderApplicationService {
|
||||
order.setChannelCode(request.channelCode());
|
||||
order.setChannelName(request.channelName()); order.setTaxMode(request.taxMode());
|
||||
order.setContainerMode(request.containerMode()); order.setCustomsInspection(request.customsInspection());
|
||||
order.setDestinationType(request.destinationType()); order.setAmazonWarehouseCode(request.amazonWarehouseCode());
|
||||
order.setDestinationType(request.destinationType());
|
||||
order.setAmazonWarehouseCode("FBA".equals(request.destinationType()) && snapshots.destination() != null
|
||||
? snapshots.destination().warehouse().warehouseCode() : request.amazonWarehouseCode());
|
||||
order.setShipmentId(request.shipmentId()); order.setReferenceId(request.referenceId());
|
||||
}
|
||||
private String formatAddress(com.baiyue.order.client.WarehouseClient.Detail detail) {
|
||||
|
||||
@@ -21,6 +21,8 @@ public class OrderMasterDataGuard {
|
||||
private final WarehouseClient warehouses;
|
||||
private final PartnerClient partners;
|
||||
private final String token;
|
||||
@Value("${baiyue.warehouse.public-tenant-id:10001}")
|
||||
private long publicTenantId = 10001L;
|
||||
public OrderMasterDataGuard(WarehouseClient warehouses, PartnerClient partners,
|
||||
@Value("${baiyue.security.internal-service-token}") String token) {
|
||||
this.warehouses=warehouses; this.partners=partners; this.token=token;
|
||||
@@ -29,15 +31,24 @@ public class OrderMasterDataGuard {
|
||||
if (request.customerId() == null) throw new BizException(ResultCode.VALIDATION_FAILED, "必须选择往来单位");
|
||||
try {
|
||||
var partner=loadPartner(tenantId,request.customerId());
|
||||
var receiving=loadWarehouse(tenantId,request.receivingWarehouseId(),"收货仓库");
|
||||
var receiving=loadWarehouse(request.receivingWarehouseId(),"收货仓库");
|
||||
requireWarehouseType(receiving, "DOMESTIC", "收货仓库");
|
||||
if (receiving.addresses()==null || receiving.addresses().stream().noneMatch(Objects::nonNull))
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,"收货仓库未配置地址");
|
||||
if (receiving.capabilities()==null || receiving.capabilities().stream().noneMatch(c ->
|
||||
c != null && "RECEIVING".equals(c.capabilityCode()) && Boolean.TRUE.equals(c.enabled())))
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,"仓库未启用收货能力");
|
||||
WarehouseClient.Detail destination=null;
|
||||
if (request.destinationWarehouseId()!=null) {
|
||||
destination=loadWarehouse(tenantId,request.destinationWarehouseId(),"目的仓库");
|
||||
if ("PRIVATE".equals(request.destinationType())) {
|
||||
if (request.destinationWarehouseId()!=null) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,"私人地址不能引用公共仓库");
|
||||
}
|
||||
} else {
|
||||
if (request.destinationWarehouseId()==null) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,"请选择平台维护的目的仓库");
|
||||
}
|
||||
destination=loadWarehouse(request.destinationWarehouseId(),"目的仓库");
|
||||
requireWarehouseType(destination, request.destinationType(), "目的仓库");
|
||||
}
|
||||
return new Snapshots(partner,receiving,destination);
|
||||
} catch (FeignException exception) {
|
||||
@@ -48,13 +59,21 @@ public class OrderMasterDataGuard {
|
||||
throw downstreamFailure(exception);
|
||||
}
|
||||
}
|
||||
public void validatePersisted(long tenantId, Long partnerId, Long receivingWarehouseId, Long destinationWarehouseId) {
|
||||
public void validatePersisted(long tenantId, Long partnerId, Long receivingWarehouseId,
|
||||
Long destinationWarehouseId, String destinationType) {
|
||||
try {
|
||||
if (partnerId==null) throw new BizException(ResultCode.NOT_FOUND,"往来单位已停用或不可用");
|
||||
loadPartner(tenantId,partnerId);
|
||||
if (receivingWarehouseId==null) throw new BizException(ResultCode.NOT_FOUND,"收货仓库已停用或不可用");
|
||||
loadWarehouse(tenantId,receivingWarehouseId,"收货仓库");
|
||||
if (destinationWarehouseId!=null) loadWarehouse(tenantId,destinationWarehouseId,"目的仓库");
|
||||
WarehouseClient.Detail receiving=loadWarehouse(receivingWarehouseId,"收货仓库");
|
||||
requireWarehouseType(receiving,"DOMESTIC","收货仓库");
|
||||
if ("PRIVATE".equals(destinationType)) {
|
||||
if (destinationWarehouseId!=null) throw new BizException(ResultCode.VALIDATION_FAILED,"私人地址不能引用公共仓库");
|
||||
} else {
|
||||
if (destinationWarehouseId==null) throw new BizException(ResultCode.NOT_FOUND,"目的仓库已停用或不可用");
|
||||
WarehouseClient.Detail destination=loadWarehouse(destinationWarehouseId,"目的仓库");
|
||||
requireWarehouseType(destination,destinationType,"目的仓库");
|
||||
}
|
||||
} catch (FeignException exception) {
|
||||
throw downstreamFailure(exception);
|
||||
} catch (BizException exception) {
|
||||
@@ -82,18 +101,18 @@ public class OrderMasterDataGuard {
|
||||
if (!StringUtils.hasText(value.partnerName())) throw unavailable("往来单位");
|
||||
return value;
|
||||
}
|
||||
private WarehouseClient.Detail loadWarehouse(long tenantId,long id,String name) {
|
||||
private WarehouseClient.Detail loadWarehouse(long id,String name) {
|
||||
ApiResponse<WarehouseClient.Detail> response;
|
||||
try {
|
||||
response=warehouses.active(token,tenantId,id);
|
||||
response=warehouses.active(token,publicTenantId,id);
|
||||
} catch (FeignException.NotFound exception) {
|
||||
throw new BizException(ResultCode.NOT_FOUND,"\u6536\u8d27\u4ed3\u5e93\u4e0d\u5b58\u5728\u6216\u5df2\u505c\u7528");
|
||||
}
|
||||
requireSuccessful(response,name);
|
||||
if (response.data()==null || response.data().warehouse()==null) throw unavailable(name);
|
||||
var value=response.data();
|
||||
if (!Long.valueOf(tenantId).equals(value.warehouse().tenantId()))
|
||||
throw new BizException(ResultCode.FORBIDDEN,name+"不属于当前企业");
|
||||
if (!Long.valueOf(publicTenantId).equals(value.warehouse().tenantId()))
|
||||
throw new BizException(ResultCode.FORBIDDEN,name+"不属于公共仓库配置");
|
||||
if (!"ACTIVE".equals(value.warehouse().status()))
|
||||
throw new BizException(ResultCode.CONFLICT,name+"已停用");
|
||||
if (!Long.valueOf(id).equals(value.warehouse().id()) || !StringUtils.hasText(value.warehouse().warehouseName())) {
|
||||
@@ -101,6 +120,11 @@ public class OrderMasterDataGuard {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
private void requireWarehouseType(WarehouseClient.Detail detail, String expected, String name) {
|
||||
if (detail == null || detail.warehouse() == null || !expected.equals(detail.warehouse().warehouseType())) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,name+"类型必须为 "+expected);
|
||||
}
|
||||
}
|
||||
private BizException unavailable(String name) {
|
||||
return new BizException(ResultCode.SERVICE_UNAVAILABLE,name+"服务暂时不可用");
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ class OrderApplicationServiceStateTest {
|
||||
|
||||
private OrderMasterDataGuard.Snapshots snapshots() {
|
||||
var partner = new PartnerClient.Partner(7L, 10001L, "客户", "ACTIVE", "SHIPPER", List.of("SHIPPER"));
|
||||
var warehouse = new WarehouseClient.Warehouse(8L, 10001L, "收货仓", "ACTIVE");
|
||||
var warehouse = new WarehouseClient.Warehouse(8L, 10001L, "CN-SZX-01", "收货仓", "DOMESTIC", "CN", "ACTIVE");
|
||||
var address = new WarehouseClient.Address("CN", "广东", "深圳", "南山", "518000", "测试路1号", null);
|
||||
var capability = new WarehouseClient.Capability("RECEIVING", true, LocalTime.of(16, 0));
|
||||
var receiving = new WarehouseClient.Detail(warehouse, List.of(address), List.of(capability));
|
||||
|
||||
@@ -21,13 +21,15 @@ import static org.mockito.Mockito.*;
|
||||
|
||||
@DisplayName("订单主数据校验")
|
||||
class OrderMasterDataGuardTest {
|
||||
private static final long PUBLIC_TENANT_ID = 10001L;
|
||||
|
||||
@Test
|
||||
void validMasterDataReturnsTrustedServerSnapshot() {
|
||||
WarehouseClient warehouses=mock(WarehouseClient.class); PartnerClient partners=mock(PartnerClient.class);
|
||||
var partner=new PartnerClient.Partner(7L,10001L,"服务端客户名","ACTIVE","SHIPPER",List.of("SHIPPER"));
|
||||
var address=new WarehouseClient.Address("CN","广东省","深圳市","宝安区","518000","示例路1号",null);
|
||||
var capability=new WarehouseClient.Capability("RECEIVING",true,java.time.LocalTime.of(16,0));
|
||||
var wh=new WarehouseClient.Detail(new WarehouseClient.Warehouse(8L,10001L,"深圳仓","ACTIVE"),List.of(address),List.of(capability));
|
||||
var wh=new WarehouseClient.Detail(new WarehouseClient.Warehouse(8L,10001L,"CN-SZX-01","深圳仓","DOMESTIC","CN","ACTIVE"),List.of(address),List.of(capability));
|
||||
when(partners.active("token",10001,7)).thenReturn(ApiResponse.success(partner));
|
||||
when(warehouses.active("token",10001,8)).thenReturn(ApiResponse.success(wh));
|
||||
var result=new OrderMasterDataGuard(warehouses,partners,"token").validate(10001,request(7L,8L));
|
||||
@@ -58,7 +60,7 @@ class OrderMasterDataGuardTest {
|
||||
void masterDataResponseWithNullElementReturnsControlledBusinessError() {
|
||||
WarehouseClient warehouses=mock(WarehouseClient.class); PartnerClient partners=mock(PartnerClient.class);
|
||||
var partner=new PartnerClient.Partner(7L,10001L,"服务端客户名","ACTIVE","SHIPPER",List.of("SHIPPER"));
|
||||
var wh=new WarehouseClient.Detail(new WarehouseClient.Warehouse(8L,10001L,"深圳仓","ACTIVE"),
|
||||
var wh=new WarehouseClient.Detail(new WarehouseClient.Warehouse(8L,10001L,"CN-SZX-01","深圳仓","DOMESTIC","CN","ACTIVE"),
|
||||
Collections.singletonList(null), Collections.singletonList(null));
|
||||
when(partners.active("token",10001,7)).thenReturn(ApiResponse.success(partner));
|
||||
when(warehouses.active("token",10001,8)).thenReturn(ApiResponse.success(wh));
|
||||
@@ -153,6 +155,47 @@ class OrderMasterDataGuardTest {
|
||||
.isEqualTo(ResultCode.VALIDATION_FAILED);
|
||||
verifyNoInteractions(warehouses);
|
||||
}
|
||||
@Test
|
||||
void customerTenantCanUseDomesticWarehouseFromPublicTenant() {
|
||||
WarehouseClient warehouses=mock(WarehouseClient.class); PartnerClient partners=mock(PartnerClient.class);
|
||||
when(partners.active("token",20002,7)).thenReturn(ApiResponse.success(
|
||||
new PartnerClient.Partner(7L,20002L,"客户","ACTIVE","SHIPPER",List.of("SHIPPER"))));
|
||||
when(warehouses.active("token",PUBLIC_TENANT_ID,8)).thenReturn(ApiResponse.success(
|
||||
warehouse(8L,"DOMESTIC",true)));
|
||||
|
||||
new OrderMasterDataGuard(warehouses,partners,"token").validate(20002,request(7L,8L));
|
||||
|
||||
verify(warehouses).active("token",PUBLIC_TENANT_ID,8);
|
||||
}
|
||||
@Test
|
||||
void fbaDestinationMustReferenceMatchingPublicWarehouse() {
|
||||
WarehouseClient warehouses=mock(WarehouseClient.class); PartnerClient partners=mock(PartnerClient.class);
|
||||
when(partners.active("token",20002,7)).thenReturn(ApiResponse.success(
|
||||
new PartnerClient.Partner(7L,20002L,"客户","ACTIVE","SHIPPER",List.of("SHIPPER"))));
|
||||
when(warehouses.active("token",PUBLIC_TENANT_ID,8)).thenReturn(ApiResponse.success(
|
||||
warehouse(8L,"DOMESTIC",true)));
|
||||
when(warehouses.active("token",PUBLIC_TENANT_ID,9)).thenReturn(ApiResponse.success(
|
||||
warehouse(9L,"OVERSEAS",false)));
|
||||
|
||||
assertThatThrownBy(() -> new OrderMasterDataGuard(warehouses,partners,"token")
|
||||
.validate(20002,request(7L,8L,"FBA",9L)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("目的仓库类型必须为 FBA");
|
||||
}
|
||||
@Test
|
||||
void privateDestinationCannotReferencePublicWarehouse() {
|
||||
WarehouseClient warehouses=mock(WarehouseClient.class); PartnerClient partners=mock(PartnerClient.class);
|
||||
when(partners.active("token",20002,7)).thenReturn(ApiResponse.success(
|
||||
new PartnerClient.Partner(7L,20002L,"客户","ACTIVE","SHIPPER",List.of("SHIPPER"))));
|
||||
when(warehouses.active("token",PUBLIC_TENANT_ID,8)).thenReturn(ApiResponse.success(
|
||||
warehouse(8L,"DOMESTIC",true)));
|
||||
|
||||
assertThatThrownBy(() -> new OrderMasterDataGuard(warehouses,partners,"token")
|
||||
.validate(20002,request(7L,8L,"PRIVATE",9L)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("私人地址不能引用公共仓库");
|
||||
verify(warehouses,never()).active("token",PUBLIC_TENANT_ID,9);
|
||||
}
|
||||
private FeignException feign(int status) {
|
||||
Request request = Request.create(Request.HttpMethod.GET, "http://warehouse-service/internal/test",
|
||||
Map.of(), null, StandardCharsets.UTF_8, null);
|
||||
@@ -161,9 +204,20 @@ class OrderMasterDataGuardTest {
|
||||
return FeignException.errorStatus("masterData", response);
|
||||
}
|
||||
private OrderRequests.SaveRequest request(Long partner,Long warehouse) {
|
||||
return request(partner,warehouse,"PRIVATE",null);
|
||||
}
|
||||
private OrderRequests.SaveRequest request(Long partner,Long warehouse,String destinationType,Long destinationWarehouseId) {
|
||||
var party=new OrderRequests.Party("公司","联系人","13800138000",null,"CN",null,"深圳",null,"地址",null);
|
||||
var cargo=new OrderRequests.Cargo("货物",null,"CARTON",1,BigDecimal.ONE,BigDecimal.ONE,
|
||||
null,null,null,null,null,false,null,null,null,null,null,null,null,null,null,null,null,null,null);
|
||||
return new OrderRequests.SaveRequest(partner,"不可信客户名","SEA",null,"CN",null,"不可信目的仓",null,null,party,party,List.of(cargo),"PICKUP",warehouse,"不可信收货仓","CH","渠道","BUY_DECLARATION","LCL",false,"OVERSEAS",null,null,null);
|
||||
return new OrderRequests.SaveRequest(partner,"不可信客户名","SEA",null,"CN",destinationWarehouseId,"私人地址",null,null,party,party,List.of(cargo),"PICKUP",warehouse,"不可信收货仓","CH","渠道","BUY_DECLARATION","LCL",false,destinationType,null,null,null);
|
||||
}
|
||||
private WarehouseClient.Detail warehouse(long id,String type,boolean receiving) {
|
||||
var value=new WarehouseClient.Warehouse(id,PUBLIC_TENANT_ID,"WH-"+id,"仓库"+id,type,"CN","ACTIVE");
|
||||
var address=new WarehouseClient.Address("CN","广东省","深圳市",null,null,"示例地址",null);
|
||||
var capabilities=receiving
|
||||
? List.of(new WarehouseClient.Capability("RECEIVING",true,null))
|
||||
: List.<WarehouseClient.Capability>of();
|
||||
return new WarehouseClient.Detail(value,List.of(address),capabilities);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user