fix(order): align FBA submit and fee confirmation

This commit is contained in:
2026-09-14 17:19:06 +08:00
parent e0af45db02
commit 8a1e587ee4
13 changed files with 103 additions and 12 deletions
@@ -38,7 +38,7 @@ public interface FirstlegUnifiedOrderClient {
record BatchRequest(Long tenantId, List<Long> orderIds) {}
record DetailRequest(long tenantId, long orderId) {}
record Summary(Inbound inbound, String mainStatus, String nextAction,
record Summary(Inbound inbound, String mainStatus, String nextAction, Boolean customerFeeConfirmationRequired,
Preload preload, Loading loading, Delivery delivery) {}
record Detail(Summary summary, List<Timeline> timeline) {}
record Inbound(Long id, Long tenantId, Long orderId, String orderNo, String inboundCode,
@@ -13,6 +13,7 @@ public final class UnifiedOrderResponse {
public record Row(
Long orderId, Integer orderVersion, Long tenantId, String orderNo, String mainStatus, String nextAction,
Boolean customerFeeConfirmationRequired,
OrderRelationSummary relation,
OrderStatus internalOrderStatus,
Long customerId, String customerName, String channelCode, String channelName, Boolean channelChanged,
@@ -348,11 +348,11 @@ public class OrderApplicationService {
"FBA orders require Amazon warehouse code, Shipment ID and Reference ID");
}
for (var cargo : cargoes) {
if (!StringUtils.hasText(cargo.getSkuNo()) || !StringUtils.hasText(cargo.getUpcNo())
if (!StringUtils.hasText(cargo.getSkuNo())
|| !StringUtils.hasText(cargo.getShipmentId())
|| !StringUtils.hasText(cargo.getReferenceId())) {
throw new BizException(ResultCode.VALIDATION_FAILED,
"FBA cargo requires SKU NO., UPC NO., Shipment ID and Reference ID.");
"FBA cargo requires SKU NO., Shipment ID and Reference ID.");
}
}
}
@@ -387,10 +387,10 @@ public class OrderApplicationService {
"FBA orders require Amazon warehouse code, Shipment ID and Reference ID");
}
for (var cargo : request.cargoes()) {
if (!StringUtils.hasText(cargo.skuNo()) || !StringUtils.hasText(cargo.upcNo())
if (!StringUtils.hasText(cargo.skuNo())
|| !StringUtils.hasText(cargo.shipmentId()) || !StringUtils.hasText(cargo.referenceId())) {
throw new BizException(ResultCode.VALIDATION_FAILED,
"FBA cargo requires SKU NO., UPC NO., Shipment ID and Reference ID.");
"FBA cargo requires SKU NO., Shipment ID and Reference ID.");
}
}
}
@@ -316,6 +316,7 @@ public class UnifiedOrderQueryService {
String mainStatus = orderMainStatus(order.getStatus());
return new UnifiedOrderResponse.Row(order.getId(), order.getVersion(), order.getTenantId(), order.getOrderNo(), mainStatus,
order.getStatus() == OrderStatus.SUBMITTED ? "APPROVE_ORDER" : null,
false,
relation(order, childCounts),
order.getStatus(), order.getCustomerId(), order.getCustomerName(),
order.getChannelCode(), order.getChannelName(), Boolean.TRUE.equals(order.getChannelChanged()), order.getReceivingWarehouseId(), order.getReceivingWarehouseName(),
@@ -337,7 +338,7 @@ public class UnifiedOrderQueryService {
String mainStatus = pendingReview ? "PENDING_REVIEW" : summary.mainStatus();
String nextAction = pendingReview ? "APPROVE_ORDER" : summary.nextAction();
return new UnifiedOrderResponse.Row(order.getId(), order.getVersion(), order.getTenantId(), order.getOrderNo(),
mainStatus, nextAction, relation(order, childCounts), order.getStatus(),
mainStatus, nextAction, Boolean.TRUE.equals(summary.customerFeeConfirmationRequired()), relation(order, childCounts), order.getStatus(),
order.getCustomerId(), order.getCustomerName(),
order.getChannelCode(), order.getChannelName(), Boolean.TRUE.equals(order.getChannelChanged()), order.getReceivingWarehouseId(), order.getReceivingWarehouseName(),
order.getDestinationWarehouseId(), order.getDestinationWarehouseName(), inbound.id(), inbound.version(), inbound.inboundCode(),
@@ -366,7 +367,7 @@ public class UnifiedOrderQueryService {
UnifiedOrderResponse.CoLoadGroupSummary coLoadGroup) {
var inbound = summary.inbound();
return new UnifiedOrderResponse.Row(inbound.orderId(), order == null ? null : order.getVersion(), inbound.tenantId(), inbound.orderNo(),
summary.mainStatus(), summary.nextAction(), relation(order, childCounts),
summary.mainStatus(), summary.nextAction(), Boolean.TRUE.equals(summary.customerFeeConfirmationRequired()), relation(order, childCounts),
parseOrderStatus(inbound.orderStatusSnapshot()),
inbound.customerId(), inbound.customerName(), inbound.channelCode(), inbound.channelName(),
order != null && Boolean.TRUE.equals(order.getChannelChanged()), inbound.warehouseId(),
@@ -893,6 +893,7 @@ components:
orderNo: {type: string}
mainStatus: {$ref: '#/components/schemas/UnifiedMainStatus'}
nextAction: {type: string, nullable: true}
customerFeeConfirmationRequired: {type: boolean, description: Firstleg 已向客户发送最新费用且客户尚未确认时为 true}
relation: {$ref: '#/components/schemas/UnifiedOrderRelation'}
internalOrderStatus: {type: string, enum: [DRAFT, SUBMITTED, APPROVED, ACCEPTED, REJECTED, CANCELLED, PROCESSING, COMPLETED], nullable: true}
customerId: {type: integer, format: int64, nullable: true}
@@ -1289,7 +1290,7 @@ components:
dangerousGoods: {type: boolean}
woodenProduct: {type: boolean, description: 木制品权威判定快照;为 true 时订单 customsInspection 必须为 true}
description: >-
原型货物明细字段。FBA 货物的 skuNo、upcNo、shipmentId、referenceId 由服务端按每条货物校验;
原型货物明细字段。FBA 货物的 skuNo、shipmentId、referenceId 由服务端按每条货物校验,upcNo 选填;
productImageFileId 必须属于当前登录客户。一件代发中,相同 packingGroupNo 的多条 SKU 行属于同一批物理箱,物理箱数只计算一次。
FirstLegOrderSummary:
type: object
@@ -306,6 +306,30 @@ class OrderApplicationServiceStateTest {
verify(repository).saveOrder(20002L, order);
}
@Test
void submittingFbaDraftKeepsUpcOptional() {
OrderRepository repository = mock(OrderRepository.class);
OrderEntity order = persistedDraft("SELF_EXPRESS", "FBA");
order.setAmazonWarehouseCode("GEU2");
order.setShipmentId("FBA19PS4C6SP");
order.setReferenceId("2F6MA5QD");
OrderCargoEntity cargo = completeCargo();
cargo.setSkuNo("Shoes");
cargo.setShipmentId("FBA19PS4C6SP");
cargo.setReferenceId("2F6MA5QD");
cargo.setUpcNo(null);
when(repository.findOwned(20002L, 1L)).thenReturn(order);
when(repository.findCargo(20002L, 1L)).thenReturn(List.of(cargo));
when(repository.findParties(20002L, 1L)).thenReturn(List.of());
when(repository.saveOrder(20002L, order)).thenReturn(1);
service(repository, mock(OrderEligibilityGuard.class), mock(OrderMasterDataGuard.class))
.transit(20002L, 2L, "u", 1L, OrderStatus.SUBMITTED, null);
assertThat(order.getStatus()).isEqualTo(OrderStatus.SUBMITTED);
verify(repository).saveOrder(20002L, order);
}
@Test
void submittingDraftPreparesInboundForWarehouseMeasurementBeforeReview() {
OrderRepository repository = mock(OrderRepository.class);
@@ -683,6 +683,7 @@ class UnifiedOrderQueryServiceTest {
assertThat(result.summary().mainStatus()).isEqualTo("LOADED");
assertThat(result.summary().internalOrderStatus()).isEqualTo(OrderStatus.APPROVED);
assertThat(result.summary().inboundId()).isEqualTo(88L);
assertThat(result.summary().customerFeeConfirmationRequired()).isTrue();
assertThat(result.summary().holdFlag()).isTrue();
assertThat(result.summary().currentHoldReason()).isEqualTo("等待客户补充资料");
ArgumentCaptor<FirstlegUnifiedOrderClient.DetailRequest> captor =
@@ -934,7 +935,7 @@ class UnifiedOrderQueryServiceTest {
when(inbound.chargeableWeightKg()).thenReturn(new BigDecimal("125.500"));
when(inbound.createdAt()).thenReturn(LocalDateTime.of(2026, 8, 6, 10, 0));
when(inbound.updatedAt()).thenReturn(LocalDateTime.of(2026, 8, 6, 11, 0));
return new FirstlegUnifiedOrderClient.Summary(inbound, status, action,
return new FirstlegUnifiedOrderClient.Summary(inbound, status, action, true,
new FirstlegUnifiedOrderClient.Preload("PENDING", 1, 2, null),
new FirstlegUnifiedOrderClient.Loading("IN_PROGRESS", 3, 3, 10, 5, 1, "SEAL-1", null), null);
}