修复bug

This commit is contained in:
2026-08-04 12:00:38 +08:00
parent dc3fda34c9
commit 667e0be7bd
5 changed files with 137 additions and 12 deletions
@@ -44,10 +44,10 @@ public final class OrderRequests {
LocalDate expectedShipDate,
@Schema(description = "Customer remark / 客户备注", example = "Notify before pickup / 提货前联系", maxLength = 1000)
@Size(max = 1000) String customerRemark,
@Schema(description = "Sender information / 发货方信息", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull @Valid Party sender,
@Schema(description = "Receiver information / 收货方信息", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull @Valid Party receiver,
@Schema(description = "Sender information; required when pickupMethod is PICKUP / 发货方信息,取货方式为 PICKUP 时必填")
@Valid Party sender,
@Schema(description = "Receiver information; required for PRIVATE destination / 收货方信息,私人地址目的地时必填")
@Valid Party receiver,
@Schema(description = "Cargo lines / 货物明细", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty @Valid List<Cargo> cargoes,
@NotBlank @Pattern(regexp = "SELF_EXPRESS|PICKUP|SELF_TRUCK") String pickupMethod,
@@ -145,6 +145,12 @@ public class OrderApplicationService {
}
private void validatePrototypeFields(OrderRequests.SaveRequest request) {
if ("PICKUP".equals(request.pickupMethod()) && request.sender() == null) {
throw new BizException("上门提货时必须填写发货信息");
}
if ("PRIVATE".equals(request.destinationType()) && request.receiver() == null) {
throw new BizException("私人地址必须填写收货信息");
}
if ("FBA".equals(request.destinationType())) {
if (!StringUtils.hasText(request.amazonWarehouseCode()) || !StringUtils.hasText(request.shipmentId())
|| !StringUtils.hasText(request.referenceId())) {
@@ -168,6 +174,13 @@ public class OrderApplicationService {
}
var cargoes = repository.findCargo(tenantId, order.getId());
if (cargoes == null || cargoes.isEmpty()) throw new BizException("At least one cargo line is required");
var parties = repository.findParties(tenantId, order.getId());
if ("PICKUP".equals(order.getPickupMethod()) && !hasParty(parties, "SENDER")) {
throw new BizException("上门提货时必须填写发货信息");
}
if ("PRIVATE".equals(order.getDestinationType()) && !hasParty(parties, "RECEIVER")) {
throw new BizException("私人地址必须填写收货信息");
}
if ("FBA".equals(order.getDestinationType())) {
if (!StringUtils.hasText(order.getAmazonWarehouseCode()) || !StringUtils.hasText(order.getShipmentId())
|| !StringUtils.hasText(order.getReferenceId())) {
@@ -181,6 +194,11 @@ public class OrderApplicationService {
}
}
private boolean hasParty(List<OrderPartyEntity> parties, String partyType) {
return parties != null && parties.stream()
.anyMatch(party -> party != null && partyType.equals(party.getPartyType()));
}
private OrderEntity require(long tenantId, long id) {
OrderEntity order = repository.findOwned(tenantId, id);
if (order == null) throw new BizException(com.baiyue.common.core.api.ResultCode.NOT_FOUND, "Order not found");
@@ -272,7 +290,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.setCustomerId(snapshots.partner() == null ? null : snapshots.partner().id());
order.setCustomerName(snapshots.partner() == null ? request.customerName() : snapshots.partner().partnerName());
order.setTransportMode(request.transportMode()); order.setTradeTerm(request.tradeTerm());
order.setDestinationCountryCode(snapshots.destination() == null ? request.destinationCountryCode()
: snapshots.destination().warehouse().countryCode());
@@ -324,8 +343,8 @@ public class OrderApplicationService {
item.setProductUsage(cargo.usage()); item.setModel(cargo.model()); item.setBrand(cargo.brand());
item.setCreatedBy(userId); item.setUpdatedBy(userId); repository.insertCargo(item);
}
saveParty(tenantId, userId, orderId, "SENDER", request.sender());
saveParty(tenantId, userId, orderId, "RECEIVER", request.receiver());
if (request.sender() != null) saveParty(tenantId, userId, orderId, "SENDER", request.sender());
if (request.receiver() != null) saveParty(tenantId, userId, orderId, "RECEIVER", request.receiver());
}
private void saveParty(long tenantId, long userId, long orderId, String type, OrderRequests.Party party) {
OrderPartyEntity item = new OrderPartyEntity();
@@ -28,9 +28,8 @@ public class OrderMasterDataGuard {
this.warehouses=warehouses; this.partners=partners; this.token=token;
}
public Snapshots validate(long tenantId, OrderRequests.SaveRequest request) {
if (request.customerId() == null) throw new BizException(ResultCode.VALIDATION_FAILED, "必须选择往来单位");
try {
var partner=loadPartner(tenantId,request.customerId());
PartnerClient.Partner partner=request.customerId()==null ? null : loadPartner(tenantId,request.customerId());
var receiving=loadWarehouse(request.receivingWarehouseId(),"收货仓库");
requireWarehouseType(receiving, "DOMESTIC", "收货仓库");
if (receiving.addresses()==null || receiving.addresses().stream().noneMatch(Objects::nonNull))
@@ -62,8 +61,7 @@ public class OrderMasterDataGuard {
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 (partnerId!=null) loadPartner(tenantId,partnerId);
if (receivingWarehouseId==null) throw new BizException(ResultCode.NOT_FOUND,"收货仓库已停用或不可用");
WarehouseClient.Detail receiving=loadWarehouse(receivingWarehouseId,"收货仓库");
requireWarehouseType(receiving,"DOMESTIC","收货仓库");
@@ -6,6 +6,7 @@ import com.baiyue.order.client.PartnerClient;
import com.baiyue.order.client.WarehouseClient;
import com.baiyue.order.dto.OrderRequests;
import com.baiyue.order.entity.OrderEntity;
import com.baiyue.order.entity.OrderCargoEntity;
import com.baiyue.order.entity.OrderStatus;
import com.baiyue.order.repository.OrderRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -98,6 +99,68 @@ class OrderApplicationServiceStateTest {
verify(repository,never()).saveOrder(any());
}
@Test
void publicDestinationCanBeSavedWithoutPartnerSenderOrReceiver() {
OrderRepository repository = mock(OrderRepository.class);
OrderEligibilityGuard eligibility = mock(OrderEligibilityGuard.class);
OrderMasterDataGuard masterData = mock(OrderMasterDataGuard.class);
OrderRequests.SaveRequest request = request(null, "SELF_EXPRESS", null, null, "OVERSEAS");
when(masterData.validate(20002L, request)).thenReturn(snapshots(null));
when(repository.completeIdempotency(any(), anyLong())).thenReturn(1);
service(repository, eligibility, masterData)
.createDraft(20002L, 2L, request, "public-destination-no-parties");
verify(repository).insertOrder(argThat(order -> order.getCustomerId() == null
&& "当前企业".equals(order.getCustomerName())));
verify(repository, never()).insertParty(any());
}
@Test
void pickupStillRequiresSender() {
OrderRepository repository = mock(OrderRepository.class);
OrderMasterDataGuard masterData = mock(OrderMasterDataGuard.class);
OrderRequests.SaveRequest request = request(null, "PICKUP", null, null, "OVERSEAS");
assertThatThrownBy(() -> service(repository, mock(OrderEligibilityGuard.class), masterData)
.createDraft(20002L, 2L, request, "pickup-without-sender"))
.isInstanceOf(BizException.class)
.hasMessageContaining("上门提货时必须填写发货信息");
verifyNoInteractions(masterData);
}
@Test
void privateDestinationStillRequiresReceiver() {
OrderRepository repository = mock(OrderRepository.class);
OrderMasterDataGuard masterData = mock(OrderMasterDataGuard.class);
OrderRequests.SaveRequest request = request(null, "SELF_EXPRESS", null, null, "PRIVATE");
assertThatThrownBy(() -> service(repository, mock(OrderEligibilityGuard.class), masterData)
.createDraft(20002L, 2L, request, "private-without-receiver"))
.isInstanceOf(BizException.class)
.hasMessageContaining("私人地址必须填写收货信息");
verifyNoInteractions(masterData);
}
@Test
void submittingPersistedPickupDraftRequiresSenderSnapshot() {
OrderRepository repository = mock(OrderRepository.class);
OrderEntity order = persistedDraft("PICKUP", "OVERSEAS");
OrderCargoEntity cargo = new OrderCargoEntity();
when(repository.findOwned(20002L, 1L)).thenReturn(order);
when(repository.findCargo(20002L, 1L)).thenReturn(List.of(cargo));
when(repository.findParties(20002L, 1L)).thenReturn(List.of());
assertThatThrownBy(() -> service(repository, mock(OrderEligibilityGuard.class), mock(OrderMasterDataGuard.class))
.transit(20002L, 2L, "u", 1L, OrderStatus.SUBMITTED, null))
.isInstanceOf(BizException.class)
.hasMessageContaining("上门提货时必须填写发货信息");
verify(repository, never()).saveOrder(any());
}
private OrderApplicationService service(OrderRepository repository, OrderEligibilityGuard eligibility,
OrderMasterDataGuard masterData) {
OrderApplicationService service = new OrderApplicationService();
@@ -121,12 +184,44 @@ class OrderApplicationServiceStateTest {
"BUY_DECLARATION", "LCL", false, "OVERSEAS", null, null, null);
}
private OrderRequests.SaveRequest request(Long customerId, String pickupMethod,
OrderRequests.Party sender, OrderRequests.Party receiver,
String destinationType) {
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(
customerId, "当前企业", "SEA", null, "US", null, "目的仓", null, null,
sender, receiver, List.of(cargo), pickupMethod, 8L, "收货仓", "CH", "渠道",
"BUY_DECLARATION", "LCL", false, destinationType, null, null, null);
}
private OrderMasterDataGuard.Snapshots snapshots() {
var partner = new PartnerClient.Partner(7L, 10001L, "客户", "ACTIVE", "SHIPPER", List.of("SHIPPER"));
return snapshots(new PartnerClient.Partner(7L, 10001L, "客户", "ACTIVE", "SHIPPER", List.of("SHIPPER")));
}
private OrderMasterDataGuard.Snapshots snapshots(PartnerClient.Partner partner) {
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));
return new OrderMasterDataGuard.Snapshots(partner, receiving, null);
}
private OrderEntity persistedDraft(String pickupMethod, String destinationType) {
OrderEntity order = new OrderEntity();
order.setId(1L);
order.setTenantId(20002L);
order.setStatus(OrderStatus.DRAFT);
order.setPickupMethod(pickupMethod);
order.setReceivingWarehouseId(8L);
order.setReceivingWarehouseName("收货仓");
order.setChannelCode("CH");
order.setChannelName("渠道");
order.setTaxMode("BUY_DECLARATION");
order.setCustomsInspection(false);
order.setDestinationType(destinationType);
return order;
}
}
@@ -168,6 +168,19 @@ class OrderMasterDataGuardTest {
verify(warehouses).active("token",PUBLIC_TENANT_ID,8);
}
@Test
void orderWithoutPartnerUsesPublicWarehouseWithoutPartnerLookup() {
WarehouseClient warehouses=mock(WarehouseClient.class); PartnerClient partners=mock(PartnerClient.class);
when(warehouses.active("token",PUBLIC_TENANT_ID,8)).thenReturn(ApiResponse.success(
warehouse(8L,"DOMESTIC",true)));
var result=new OrderMasterDataGuard(warehouses,partners,"token")
.validate(20002,request(null,8L));
assertThat(result.partner()).isNull();
verifyNoInteractions(partners);
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(