fix(order): align FBA submit and fee confirmation
This commit is contained in:
@@ -7,6 +7,7 @@ public record UnifiedInboundSummary(
|
|||||||
InboundOrderSummary inbound,
|
InboundOrderSummary inbound,
|
||||||
String mainStatus,
|
String mainStatus,
|
||||||
String nextAction,
|
String nextAction,
|
||||||
|
Boolean customerFeeConfirmationRequired,
|
||||||
PreloadSummary preload,
|
PreloadSummary preload,
|
||||||
LoadingSummary loading,
|
LoadingSummary loading,
|
||||||
DeliverySummary delivery
|
DeliverySummary delivery
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ import com.baiyue.firstleg.mapper.ContainerTierPricingMapper;
|
|||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/** FBA 计费仓储;平台基础资料列表可跨企业读取,其他业务操作强制带租户条件。 */
|
/** FBA 计费仓储;平台基础资料列表可跨企业读取,其他业务操作强制带租户条件。 */
|
||||||
@Repository
|
@Repository
|
||||||
@@ -250,6 +252,24 @@ public class FbaPricingRepository {
|
|||||||
.orderByDesc(InboundFeeEntity::getFeeVersion).last("LIMIT 1"));
|
.orderByDesc(InboundFeeEntity::getFeeVersion).last("LIMIT 1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<Long, Boolean> customerFeeConfirmationRequiredMap(
|
||||||
|
Long tenantId, Map<Long, Long> tenantByInbound) {
|
||||||
|
if (tenantByInbound == null || tenantByInbound.isEmpty()) return Map.of();
|
||||||
|
var query = new LambdaQueryWrapper<InboundFeeEntity>()
|
||||||
|
.in(InboundFeeEntity::getInboundId, tenantByInbound.keySet());
|
||||||
|
if (tenantId != null) query.eq(InboundFeeEntity::getTenantId, tenantId);
|
||||||
|
query.orderByDesc(InboundFeeEntity::getFeeVersion)
|
||||||
|
.orderByDesc(InboundFeeEntity::getId);
|
||||||
|
Map<Long, Boolean> result = new LinkedHashMap<>();
|
||||||
|
for (InboundFeeEntity fee : fees.selectList(query)) {
|
||||||
|
Long expectedTenantId = tenantByInbound.get(fee.getInboundId());
|
||||||
|
if (expectedTenantId == null || !expectedTenantId.equals(fee.getTenantId())) continue;
|
||||||
|
result.putIfAbsent(fee.getInboundId(),
|
||||||
|
"SENT".equals(fee.getFeeStatus()) && fee.getCustomerConfirmedAt() == null);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public InboundFeeEntity findLatestFeeForUpdate(long tenantId, long inboundId) {
|
public InboundFeeEntity findLatestFeeForUpdate(long tenantId, long inboundId) {
|
||||||
return fees.selectOne(new LambdaQueryWrapper<InboundFeeEntity>()
|
return fees.selectOne(new LambdaQueryWrapper<InboundFeeEntity>()
|
||||||
.eq(InboundFeeEntity::getTenantId, tenantId)
|
.eq(InboundFeeEntity::getTenantId, tenantId)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.baiyue.firstleg.dto.UnifiedInboundSummary;
|
|||||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||||
import com.baiyue.firstleg.dto.CurrentContainerTransport;
|
import com.baiyue.firstleg.dto.CurrentContainerTransport;
|
||||||
import com.baiyue.firstleg.loading.entity.CabinetLoadingEvidenceEntity;
|
import com.baiyue.firstleg.loading.entity.CabinetLoadingEvidenceEntity;
|
||||||
|
import com.baiyue.firstleg.repository.FbaPricingRepository;
|
||||||
import com.baiyue.firstleg.repository.InboundOrderRepository;
|
import com.baiyue.firstleg.repository.InboundOrderRepository;
|
||||||
import com.baiyue.firstleg.repository.UnifiedInboundReadRepository;
|
import com.baiyue.firstleg.repository.UnifiedInboundReadRepository;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
@@ -32,6 +33,7 @@ public class UnifiedInboundQueryService {
|
|||||||
private static final Set<String> REVIEW_STATUSES = Set.of("UNRECORDED", "PENDING", "APPROVED", "REJECTED");
|
private static final Set<String> REVIEW_STATUSES = Set.of("UNRECORDED", "PENDING", "APPROVED", "REJECTED");
|
||||||
|
|
||||||
@Resource private InboundOrderRepository inbounds;
|
@Resource private InboundOrderRepository inbounds;
|
||||||
|
@Resource private FbaPricingRepository pricing;
|
||||||
@Resource private UnifiedInboundReadRepository operations;
|
@Resource private UnifiedInboundReadRepository operations;
|
||||||
@Resource private ObjectMapper json;
|
@Resource private ObjectMapper json;
|
||||||
|
|
||||||
@@ -79,8 +81,10 @@ public class UnifiedInboundQueryService {
|
|||||||
}
|
}
|
||||||
CurrentContainerTransport transport = includePhaseThree
|
CurrentContainerTransport transport = includePhaseThree
|
||||||
? operations.findTransport(tenantId, inbound.getId()) : null;
|
? operations.findTransport(tenantId, inbound.getId()) : null;
|
||||||
|
boolean customerFeeConfirmationRequired = pricing.customerFeeConfirmationRequiredMap(
|
||||||
|
tenantId, Map.of(inbound.getId(), inbound.getTenantId())).getOrDefault(inbound.getId(), false);
|
||||||
return new UnifiedInboundDetail(summary(inbound, detail, preload, pick, evidence, deliveryAt,
|
return new UnifiedInboundDetail(summary(inbound, detail, preload, pick, evidence, deliveryAt,
|
||||||
transport, null, null), timeline);
|
transport, null, null, customerFeeConfirmationRequired), timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<UnifiedInboundSummary> summaries(List<InboundOrderEntity> records) {
|
private List<UnifiedInboundSummary> summaries(List<InboundOrderEntity> records) {
|
||||||
@@ -99,10 +103,12 @@ public class UnifiedInboundQueryService {
|
|||||||
records.forEach(inbound -> applyConfirmedPalletCount(inbound, confirmedPalletCounts));
|
records.forEach(inbound -> applyConfirmedPalletCount(inbound, confirmedPalletCounts));
|
||||||
var locationCodes = inbounds.locationCodesMap(null, owners);
|
var locationCodes = inbounds.locationCodesMap(null, owners);
|
||||||
var pickups = inbounds.pickupMap(null, owners);
|
var pickups = inbounds.pickupMap(null, owners);
|
||||||
|
var customerFeeConfirmations = pricing.customerFeeConfirmationRequiredMap(null, owners);
|
||||||
return records.stream().map(inbound -> summary(inbound, null, preloads.get(inbound.getId()),
|
return records.stream().map(inbound -> summary(inbound, null, preloads.get(inbound.getId()),
|
||||||
picks.get(inbound.getId()), evidences.getOrDefault(inbound.getId(), List.of()),
|
picks.get(inbound.getId()), evidences.getOrDefault(inbound.getId(), List.of()),
|
||||||
deliveries.get(inbound.getId()) == null ? null : deliveries.get(inbound.getId()).getOccurredAt(),
|
deliveries.get(inbound.getId()) == null ? null : deliveries.get(inbound.getId()).getOccurredAt(),
|
||||||
transports.get(inbound.getId()), locationCodes.get(inbound.getId()), pickups.get(inbound.getId()))).toList();
|
transports.get(inbound.getId()), locationCodes.get(inbound.getId()), pickups.get(inbound.getId()),
|
||||||
|
customerFeeConfirmations.getOrDefault(inbound.getId(), false))).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void applyConfirmedPalletCount(InboundOrderEntity inbound,
|
private static void applyConfirmedPalletCount(InboundOrderEntity inbound,
|
||||||
@@ -118,7 +124,8 @@ public class UnifiedInboundQueryService {
|
|||||||
com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity pick,
|
com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity pick,
|
||||||
List<CabinetLoadingEvidenceEntity> evidence, LocalDateTime deliveryAt,
|
List<CabinetLoadingEvidenceEntity> evidence, LocalDateTime deliveryAt,
|
||||||
CurrentContainerTransport transport, String locationCodes,
|
CurrentContainerTransport transport, String locationCodes,
|
||||||
com.baiyue.firstleg.entity.InboundPickupSnapshotEntity pickup) {
|
com.baiyue.firstleg.entity.InboundPickupSnapshotEntity pickup,
|
||||||
|
boolean customerFeeConfirmationRequired) {
|
||||||
String mainStatus = mainStatus(inbound.getProcessStatus());
|
String mainStatus = mainStatus(inbound.getProcessStatus());
|
||||||
List<CabinetLoadingEvidenceEntity> activeEvidence = evidence.stream()
|
List<CabinetLoadingEvidenceEntity> activeEvidence = evidence.stream()
|
||||||
.filter(item -> item.getStatus() == null || "ACTIVE".equals(item.getStatus())).toList();
|
.filter(item -> item.getStatus() == null || "ACTIVE".equals(item.getStatus())).toList();
|
||||||
@@ -143,6 +150,7 @@ public class UnifiedInboundQueryService {
|
|||||||
locationCodes == null && baseSummary != null ? baseSummary.locationCodes() : locationCodes);
|
locationCodes == null && baseSummary != null ? baseSummary.locationCodes() : locationCodes);
|
||||||
return new UnifiedInboundSummary(inboundSummary, mainStatus,
|
return new UnifiedInboundSummary(inboundSummary, mainStatus,
|
||||||
nextAction(mainStatus, inbound.getProcessStatus(), preload == null ? null : preload.getStatus()),
|
nextAction(mainStatus, inbound.getProcessStatus(), preload == null ? null : preload.getStatus()),
|
||||||
|
customerFeeConfirmationRequired,
|
||||||
preloadSummary, loadingSummary, deliverySummary);
|
preloadSummary, loadingSummary, deliverySummary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1573,6 +1573,7 @@ components:
|
|||||||
inbound: {$ref: '#/components/schemas/UnifiedInboundOrderSummary'}
|
inbound: {$ref: '#/components/schemas/UnifiedInboundOrderSummary'}
|
||||||
mainStatus: {$ref: '#/components/schemas/UnifiedMainStatus'}
|
mainStatus: {$ref: '#/components/schemas/UnifiedMainStatus'}
|
||||||
nextAction: {type: string, nullable: true}
|
nextAction: {type: string, nullable: true}
|
||||||
|
customerFeeConfirmationRequired: {type: boolean, description: 最新费用已发送给客户且尚未由客户确认}
|
||||||
preload: {$ref: '#/components/schemas/UnifiedPreloadSummary'}
|
preload: {$ref: '#/components/schemas/UnifiedPreloadSummary'}
|
||||||
loading: {$ref: '#/components/schemas/UnifiedLoadingSummary'}
|
loading: {$ref: '#/components/schemas/UnifiedLoadingSummary'}
|
||||||
delivery: {$ref: '#/components/schemas/UnifiedDeliverySummary'}
|
delivery: {$ref: '#/components/schemas/UnifiedDeliverySummary'}
|
||||||
|
|||||||
+27
@@ -10,6 +10,7 @@ import com.baiyue.firstleg.entity.CustomerChargeRuleEntity;
|
|||||||
import com.baiyue.firstleg.entity.CarrierFbaRateEntity;
|
import com.baiyue.firstleg.entity.CarrierFbaRateEntity;
|
||||||
import com.baiyue.firstleg.entity.InboundPricingIdempotencyEntity;
|
import com.baiyue.firstleg.entity.InboundPricingIdempotencyEntity;
|
||||||
import com.baiyue.firstleg.entity.InboundPricingEntity;
|
import com.baiyue.firstleg.entity.InboundPricingEntity;
|
||||||
|
import com.baiyue.firstleg.entity.InboundFeeEntity;
|
||||||
import com.baiyue.firstleg.mapper.CarrierFbaRateMapper;
|
import com.baiyue.firstleg.mapper.CarrierFbaRateMapper;
|
||||||
import com.baiyue.firstleg.mapper.CustomerChargeRuleMapper;
|
import com.baiyue.firstleg.mapper.CustomerChargeRuleMapper;
|
||||||
import com.baiyue.firstleg.mapper.ContainerTierPricingMapper;
|
import com.baiyue.firstleg.mapper.ContainerTierPricingMapper;
|
||||||
@@ -30,6 +31,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -58,6 +60,8 @@ class FbaPricingRepositoryRuleListTest {
|
|||||||
CarrierFbaRateEntity.class);
|
CarrierFbaRateEntity.class);
|
||||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "test"),
|
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "test"),
|
||||||
InboundPricingEntity.class);
|
InboundPricingEntity.class);
|
||||||
|
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "test"),
|
||||||
|
InboundFeeEntity.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -204,6 +208,29 @@ class FbaPricingRepositoryRuleListTest {
|
|||||||
.contains("List.of(\"FEE_CONFIRMING\", \"WAIT_PALLET\", \"WAIT_SPLIT\", \"LOADING\", \"LOADED\", \"DELIVERED\")");
|
.contains("List.of(\"FEE_CONFIRMING\", \"WAIT_PALLET\", \"WAIT_SPLIT\", \"LOADING\", \"LOADED\", \"DELIVERED\")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void customerFeeConfirmationIsRequiredOnlyForLatestUnconfirmedSentFee() {
|
||||||
|
InboundFeeEntity waiting = new InboundFeeEntity();
|
||||||
|
waiting.setId(12L);
|
||||||
|
waiting.setTenantId(10001L);
|
||||||
|
waiting.setInboundId(88L);
|
||||||
|
waiting.setFeeVersion(2);
|
||||||
|
waiting.setFeeStatus("SENT");
|
||||||
|
InboundFeeEntity confirmed = new InboundFeeEntity();
|
||||||
|
confirmed.setId(22L);
|
||||||
|
confirmed.setTenantId(10001L);
|
||||||
|
confirmed.setInboundId(99L);
|
||||||
|
confirmed.setFeeVersion(1);
|
||||||
|
confirmed.setFeeStatus("SENT");
|
||||||
|
confirmed.setCustomerConfirmedAt(java.time.LocalDateTime.now());
|
||||||
|
when(fees.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of(waiting, confirmed));
|
||||||
|
|
||||||
|
assertThat(repository.customerFeeConfirmationRequiredMap(
|
||||||
|
10001L, Map.of(88L, 10001L, 99L, 10001L)))
|
||||||
|
.containsEntry(88L, true)
|
||||||
|
.containsEntry(99L, false);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void dropshipInitialPricingPreservesWaitingForPalletization() {
|
void dropshipInitialPricingPreservesWaitingForPalletization() {
|
||||||
when(pricing.update(org.mockito.ArgumentMatchers.isNull(), org.mockito.ArgumentMatchers.any()))
|
when(pricing.update(org.mockito.ArgumentMatchers.isNull(), org.mockito.ArgumentMatchers.any()))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.baiyue.firstleg.dto.InboundOrderDetail;
|
|||||||
import com.baiyue.firstleg.dto.UnifiedInboundDetail;
|
import com.baiyue.firstleg.dto.UnifiedInboundDetail;
|
||||||
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
|
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
|
||||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||||
|
import com.baiyue.firstleg.repository.FbaPricingRepository;
|
||||||
import com.baiyue.firstleg.repository.InboundOrderRepository;
|
import com.baiyue.firstleg.repository.InboundOrderRepository;
|
||||||
import com.baiyue.firstleg.repository.UnifiedInboundReadRepository;
|
import com.baiyue.firstleg.repository.UnifiedInboundReadRepository;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -91,9 +92,11 @@ class UnifiedInboundQueryServiceTest {
|
|||||||
void phaseTwoDetailDoesNotQueryPhaseThreeTables(String processStatus) {
|
void phaseTwoDetailDoesNotQueryPhaseThreeTables(String processStatus) {
|
||||||
InboundOrderRepository inbounds = mock(InboundOrderRepository.class);
|
InboundOrderRepository inbounds = mock(InboundOrderRepository.class);
|
||||||
UnifiedInboundReadRepository operations = mock(UnifiedInboundReadRepository.class);
|
UnifiedInboundReadRepository operations = mock(UnifiedInboundReadRepository.class);
|
||||||
|
FbaPricingRepository pricing = mock(FbaPricingRepository.class);
|
||||||
UnifiedInboundQueryService service = new UnifiedInboundQueryService();
|
UnifiedInboundQueryService service = new UnifiedInboundQueryService();
|
||||||
ReflectionTestUtils.setField(service, "inbounds", inbounds);
|
ReflectionTestUtils.setField(service, "inbounds", inbounds);
|
||||||
ReflectionTestUtils.setField(service, "operations", operations);
|
ReflectionTestUtils.setField(service, "operations", operations);
|
||||||
|
ReflectionTestUtils.setField(service, "pricing", pricing);
|
||||||
|
|
||||||
InboundOrderEntity inbound = new InboundOrderEntity();
|
InboundOrderEntity inbound = new InboundOrderEntity();
|
||||||
inbound.setId(88L);
|
inbound.setId(88L);
|
||||||
@@ -103,10 +106,13 @@ class UnifiedInboundQueryServiceTest {
|
|||||||
InboundOrderDetail detail = new InboundOrderDetail(null, null, null, null, null, List.of());
|
InboundOrderDetail detail = new InboundOrderDetail(null, null, null, null, null, List.of());
|
||||||
when(inbounds.findByOrderId(10001L, 99L)).thenReturn(inbound);
|
when(inbounds.findByOrderId(10001L, 99L)).thenReturn(inbound);
|
||||||
when(inbounds.detail(10001L, inbound)).thenReturn(detail);
|
when(inbounds.detail(10001L, inbound)).thenReturn(detail);
|
||||||
|
when(pricing.customerFeeConfirmationRequiredMap(10001L, Map.of(88L, 10001L)))
|
||||||
|
.thenReturn(Map.of(88L, true));
|
||||||
|
|
||||||
UnifiedInboundDetail result = service.detail(10001L, 99L);
|
UnifiedInboundDetail result = service.detail(10001L, 99L);
|
||||||
|
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
|
assertThat(result.summary().customerFeeConfirmationRequired()).isTrue();
|
||||||
verifyNoInteractions(operations);
|
verifyNoInteractions(operations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public interface FirstlegUnifiedOrderClient {
|
|||||||
record BatchRequest(Long tenantId, List<Long> orderIds) {}
|
record BatchRequest(Long tenantId, List<Long> orderIds) {}
|
||||||
record DetailRequest(long tenantId, long orderId) {}
|
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) {}
|
Preload preload, Loading loading, Delivery delivery) {}
|
||||||
record Detail(Summary summary, List<Timeline> timeline) {}
|
record Detail(Summary summary, List<Timeline> timeline) {}
|
||||||
record Inbound(Long id, Long tenantId, Long orderId, String orderNo, String inboundCode,
|
record Inbound(Long id, Long tenantId, Long orderId, String orderNo, String inboundCode,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public final class UnifiedOrderResponse {
|
|||||||
|
|
||||||
public record Row(
|
public record Row(
|
||||||
Long orderId, Integer orderVersion, Long tenantId, String orderNo, String mainStatus, String nextAction,
|
Long orderId, Integer orderVersion, Long tenantId, String orderNo, String mainStatus, String nextAction,
|
||||||
|
Boolean customerFeeConfirmationRequired,
|
||||||
OrderRelationSummary relation,
|
OrderRelationSummary relation,
|
||||||
OrderStatus internalOrderStatus,
|
OrderStatus internalOrderStatus,
|
||||||
Long customerId, String customerName, String channelCode, String channelName, Boolean channelChanged,
|
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");
|
"FBA orders require Amazon warehouse code, Shipment ID and Reference ID");
|
||||||
}
|
}
|
||||||
for (var cargo : cargoes) {
|
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.getShipmentId())
|
||||||
|| !StringUtils.hasText(cargo.getReferenceId())) {
|
|| !StringUtils.hasText(cargo.getReferenceId())) {
|
||||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
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");
|
"FBA orders require Amazon warehouse code, Shipment ID and Reference ID");
|
||||||
}
|
}
|
||||||
for (var cargo : request.cargoes()) {
|
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())) {
|
|| !StringUtils.hasText(cargo.shipmentId()) || !StringUtils.hasText(cargo.referenceId())) {
|
||||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
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());
|
String mainStatus = orderMainStatus(order.getStatus());
|
||||||
return new UnifiedOrderResponse.Row(order.getId(), order.getVersion(), order.getTenantId(), order.getOrderNo(), mainStatus,
|
return new UnifiedOrderResponse.Row(order.getId(), order.getVersion(), order.getTenantId(), order.getOrderNo(), mainStatus,
|
||||||
order.getStatus() == OrderStatus.SUBMITTED ? "APPROVE_ORDER" : null,
|
order.getStatus() == OrderStatus.SUBMITTED ? "APPROVE_ORDER" : null,
|
||||||
|
false,
|
||||||
relation(order, childCounts),
|
relation(order, childCounts),
|
||||||
order.getStatus(), order.getCustomerId(), order.getCustomerName(),
|
order.getStatus(), order.getCustomerId(), order.getCustomerName(),
|
||||||
order.getChannelCode(), order.getChannelName(), Boolean.TRUE.equals(order.getChannelChanged()), order.getReceivingWarehouseId(), order.getReceivingWarehouseName(),
|
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 mainStatus = pendingReview ? "PENDING_REVIEW" : summary.mainStatus();
|
||||||
String nextAction = pendingReview ? "APPROVE_ORDER" : summary.nextAction();
|
String nextAction = pendingReview ? "APPROVE_ORDER" : summary.nextAction();
|
||||||
return new UnifiedOrderResponse.Row(order.getId(), order.getVersion(), order.getTenantId(), order.getOrderNo(),
|
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.getCustomerId(), order.getCustomerName(),
|
||||||
order.getChannelCode(), order.getChannelName(), Boolean.TRUE.equals(order.getChannelChanged()), order.getReceivingWarehouseId(), order.getReceivingWarehouseName(),
|
order.getChannelCode(), order.getChannelName(), Boolean.TRUE.equals(order.getChannelChanged()), order.getReceivingWarehouseId(), order.getReceivingWarehouseName(),
|
||||||
order.getDestinationWarehouseId(), order.getDestinationWarehouseName(), inbound.id(), inbound.version(), inbound.inboundCode(),
|
order.getDestinationWarehouseId(), order.getDestinationWarehouseName(), inbound.id(), inbound.version(), inbound.inboundCode(),
|
||||||
@@ -366,7 +367,7 @@ public class UnifiedOrderQueryService {
|
|||||||
UnifiedOrderResponse.CoLoadGroupSummary coLoadGroup) {
|
UnifiedOrderResponse.CoLoadGroupSummary coLoadGroup) {
|
||||||
var inbound = summary.inbound();
|
var inbound = summary.inbound();
|
||||||
return new UnifiedOrderResponse.Row(inbound.orderId(), order == null ? null : order.getVersion(), inbound.tenantId(), inbound.orderNo(),
|
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()),
|
parseOrderStatus(inbound.orderStatusSnapshot()),
|
||||||
inbound.customerId(), inbound.customerName(), inbound.channelCode(), inbound.channelName(),
|
inbound.customerId(), inbound.customerName(), inbound.channelCode(), inbound.channelName(),
|
||||||
order != null && Boolean.TRUE.equals(order.getChannelChanged()), inbound.warehouseId(),
|
order != null && Boolean.TRUE.equals(order.getChannelChanged()), inbound.warehouseId(),
|
||||||
|
|||||||
@@ -893,6 +893,7 @@ components:
|
|||||||
orderNo: {type: string}
|
orderNo: {type: string}
|
||||||
mainStatus: {$ref: '#/components/schemas/UnifiedMainStatus'}
|
mainStatus: {$ref: '#/components/schemas/UnifiedMainStatus'}
|
||||||
nextAction: {type: string, nullable: true}
|
nextAction: {type: string, nullable: true}
|
||||||
|
customerFeeConfirmationRequired: {type: boolean, description: Firstleg 已向客户发送最新费用且客户尚未确认时为 true}
|
||||||
relation: {$ref: '#/components/schemas/UnifiedOrderRelation'}
|
relation: {$ref: '#/components/schemas/UnifiedOrderRelation'}
|
||||||
internalOrderStatus: {type: string, enum: [DRAFT, SUBMITTED, APPROVED, ACCEPTED, REJECTED, CANCELLED, PROCESSING, COMPLETED], nullable: true}
|
internalOrderStatus: {type: string, enum: [DRAFT, SUBMITTED, APPROVED, ACCEPTED, REJECTED, CANCELLED, PROCESSING, COMPLETED], nullable: true}
|
||||||
customerId: {type: integer, format: int64, nullable: true}
|
customerId: {type: integer, format: int64, nullable: true}
|
||||||
@@ -1289,7 +1290,7 @@ components:
|
|||||||
dangerousGoods: {type: boolean}
|
dangerousGoods: {type: boolean}
|
||||||
woodenProduct: {type: boolean, description: 木制品权威判定快照;为 true 时订单 customsInspection 必须为 true}
|
woodenProduct: {type: boolean, description: 木制品权威判定快照;为 true 时订单 customsInspection 必须为 true}
|
||||||
description: >-
|
description: >-
|
||||||
原型货物明细字段。FBA 货物的 skuNo、upcNo、shipmentId、referenceId 由服务端按每条货物校验;
|
原型货物明细字段。FBA 货物的 skuNo、shipmentId、referenceId 由服务端按每条货物校验,upcNo 选填;
|
||||||
productImageFileId 必须属于当前登录客户。一件代发中,相同 packingGroupNo 的多条 SKU 行属于同一批物理箱,物理箱数只计算一次。
|
productImageFileId 必须属于当前登录客户。一件代发中,相同 packingGroupNo 的多条 SKU 行属于同一批物理箱,物理箱数只计算一次。
|
||||||
FirstLegOrderSummary:
|
FirstLegOrderSummary:
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
@@ -306,6 +306,30 @@ class OrderApplicationServiceStateTest {
|
|||||||
verify(repository).saveOrder(20002L, order);
|
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
|
@Test
|
||||||
void submittingDraftPreparesInboundForWarehouseMeasurementBeforeReview() {
|
void submittingDraftPreparesInboundForWarehouseMeasurementBeforeReview() {
|
||||||
OrderRepository repository = mock(OrderRepository.class);
|
OrderRepository repository = mock(OrderRepository.class);
|
||||||
|
|||||||
@@ -683,6 +683,7 @@ class UnifiedOrderQueryServiceTest {
|
|||||||
assertThat(result.summary().mainStatus()).isEqualTo("LOADED");
|
assertThat(result.summary().mainStatus()).isEqualTo("LOADED");
|
||||||
assertThat(result.summary().internalOrderStatus()).isEqualTo(OrderStatus.APPROVED);
|
assertThat(result.summary().internalOrderStatus()).isEqualTo(OrderStatus.APPROVED);
|
||||||
assertThat(result.summary().inboundId()).isEqualTo(88L);
|
assertThat(result.summary().inboundId()).isEqualTo(88L);
|
||||||
|
assertThat(result.summary().customerFeeConfirmationRequired()).isTrue();
|
||||||
assertThat(result.summary().holdFlag()).isTrue();
|
assertThat(result.summary().holdFlag()).isTrue();
|
||||||
assertThat(result.summary().currentHoldReason()).isEqualTo("等待客户补充资料");
|
assertThat(result.summary().currentHoldReason()).isEqualTo("等待客户补充资料");
|
||||||
ArgumentCaptor<FirstlegUnifiedOrderClient.DetailRequest> captor =
|
ArgumentCaptor<FirstlegUnifiedOrderClient.DetailRequest> captor =
|
||||||
@@ -934,7 +935,7 @@ class UnifiedOrderQueryServiceTest {
|
|||||||
when(inbound.chargeableWeightKg()).thenReturn(new BigDecimal("125.500"));
|
when(inbound.chargeableWeightKg()).thenReturn(new BigDecimal("125.500"));
|
||||||
when(inbound.createdAt()).thenReturn(LocalDateTime.of(2026, 8, 6, 10, 0));
|
when(inbound.createdAt()).thenReturn(LocalDateTime.of(2026, 8, 6, 10, 0));
|
||||||
when(inbound.updatedAt()).thenReturn(LocalDateTime.of(2026, 8, 6, 11, 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.Preload("PENDING", 1, 2, null),
|
||||||
new FirstlegUnifiedOrderClient.Loading("IN_PROGRESS", 3, 3, 10, 5, 1, "SEAL-1", null), null);
|
new FirstlegUnifiedOrderClient.Loading("IN_PROGRESS", 3, 3, 10, 5, 1, "SEAL-1", null), null);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user