fix: unify same-carton order metrics
This commit is contained in:
@@ -140,8 +140,10 @@ public class InternalOrderCargoController {
|
||||
// 申报重量/体积必须属于当前订单。拆分子单未物化独立货物事实时不得复制根单整单总量。
|
||||
List<OrderCargoEntity> cargoes = orders.findCargo(tenantId, orderId);
|
||||
return ApiResponse.success(new MeasurementDeclarationSnapshot(
|
||||
order.getReferenceId(), sum(cargoes, OrderCargoEntity::getGrossWeightKg),
|
||||
sum(cargoes, OrderCargoEntity::getVolumeCbm),
|
||||
order.getReferenceId(), OrderCargoPackageCounts.physicalMetric(
|
||||
cargoes, order.getFulfillmentType(), OrderCargoEntity::getGrossWeightKg),
|
||||
OrderCargoPackageCounts.physicalMetric(
|
||||
cargoes, order.getFulfillmentType(), OrderCargoEntity::getVolumeCbm),
|
||||
cargoes.stream().map(OrderCargoEntity::getCargoName).filter(Objects::nonNull)
|
||||
.map(String::trim).filter(value -> !value.isEmpty()).distinct()
|
||||
.collect(Collectors.joining("、"))));
|
||||
|
||||
@@ -229,7 +229,7 @@ public final class OrderRequests {
|
||||
@Schema(description = "Product image file ID / 产品图片文件ID") Long productImageFileId,
|
||||
@Schema(description = "Authoritative wood-product decision frozen with this cargo line / 木制品权威判定,随货物行冻结", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull Boolean woodenProduct,
|
||||
@Schema(description = "Mixed-carton group. Cargo lines with the same value share physical cartons / 混箱组合号;相同组合号的货物行属于同一批物理箱", maxLength = 32)
|
||||
@Schema(description = "Client-generated same-carton key. Lines with the same value share carton count, dimensions, weight and volume / 客户端添加同箱 SKU 时生成;同组物理箱字段只汇总一次", maxLength = 32)
|
||||
@Size(max = 32) String packingGroupNo
|
||||
) {
|
||||
public Cargo(String cargoName, String hsCode, String packageType, int packageCount,
|
||||
|
||||
@@ -107,9 +107,12 @@ public class MerchantInboundOrderListService {
|
||||
int packageCount = OrderCargoPackageCounts.physical(cargoes, order.getFulfillmentType());
|
||||
int totalQuantity = cargoes.stream().map(OrderCargoEntity::getTotalQuantity)
|
||||
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum();
|
||||
BigDecimal grossWeight = sum(cargoes, OrderCargoEntity::getGrossWeightKg);
|
||||
BigDecimal netWeight = sum(cargoes, OrderCargoEntity::getNetWeightKg);
|
||||
BigDecimal volume = sum(cargoes, OrderCargoEntity::getVolumeCbm);
|
||||
BigDecimal grossWeight = OrderCargoPackageCounts.physicalMetric(
|
||||
cargoes, order.getFulfillmentType(), OrderCargoEntity::getGrossWeightKg);
|
||||
BigDecimal netWeight = OrderCargoPackageCounts.physicalMetric(
|
||||
cargoes, order.getFulfillmentType(), OrderCargoEntity::getNetWeightKg);
|
||||
BigDecimal volume = OrderCargoPackageCounts.physicalMetric(
|
||||
cargoes, order.getFulfillmentType(), OrderCargoEntity::getVolumeCbm);
|
||||
BigDecimal declaredAmount = sum(cargoes, OrderCargoEntity::getDeclaredValue);
|
||||
|
||||
List<MerchantInboundOrderRow.CargoColumn> cargoColumns = cargoes.stream()
|
||||
|
||||
@@ -798,9 +798,12 @@ public class OrderApplicationService {
|
||||
|| !Objects.equals(first.packageType(), cargo.packageType())
|
||||
|| !sameDecimal(first.lengthCm(), cargo.lengthCm())
|
||||
|| !sameDecimal(first.widthCm(), cargo.widthCm())
|
||||
|| !sameDecimal(first.heightCm(), cargo.heightCm()))) {
|
||||
|| !sameDecimal(first.heightCm(), cargo.heightCm())
|
||||
|| !sameDecimal(first.grossWeightKg(), cargo.grossWeightKg())
|
||||
|| !sameDecimal(first.netWeightKg(), cargo.netWeightKg())
|
||||
|| !sameDecimal(first.volumeCbm(), cargo.volumeCbm()))) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"同一混箱组合的箱数、包装类型和箱规必须一致:" + group);
|
||||
"同箱 SKU 的箱数、包装类型、箱规、重量和体积必须一致");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.function.Function;
|
||||
|
||||
/** Calculates physical cartons without double-counting SKU lines in a mixed-carton group. */
|
||||
public final class OrderCargoPackageCounts {
|
||||
@@ -21,4 +23,15 @@ public final class OrderCargoPackageCounts {
|
||||
return countedGroups.add(group) ? count : 0;
|
||||
}).sum();
|
||||
}
|
||||
|
||||
/** Sums a physical-carton metric once per same-carton group. */
|
||||
public static BigDecimal physicalMetric(List<OrderCargoEntity> cargoes, String fulfillmentType,
|
||||
Function<OrderCargoEntity, BigDecimal> getter) {
|
||||
Set<String> countedGroups = new HashSet<>();
|
||||
List<BigDecimal> values = cargoes.stream().filter(Objects::nonNull).filter(cargo -> {
|
||||
String group = StringUtils.hasText(cargo.getPackingGroupNo()) ? cargo.getPackingGroupNo().trim() : null;
|
||||
return !"ONE_CLICK_DROPSHIP".equals(fulfillmentType) || group == null || countedGroups.add(group);
|
||||
}).map(getter).filter(Objects::nonNull).toList();
|
||||
return values.isEmpty() ? null : values.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,9 +508,12 @@ public class UnifiedOrderQueryService {
|
||||
Integer totalPackageCount = hasPackageCount
|
||||
? OrderCargoPackageCounts.physical(cargoes, fulfillmentType)
|
||||
: null;
|
||||
BigDecimal grossWeightKg = sum(cargoes, OrderCargoEntity::getGrossWeightKg);
|
||||
BigDecimal netWeightKg = sum(cargoes, OrderCargoEntity::getNetWeightKg);
|
||||
BigDecimal volumeCbm = sum(cargoes, OrderCargoEntity::getVolumeCbm);
|
||||
BigDecimal grossWeightKg = OrderCargoPackageCounts.physicalMetric(
|
||||
cargoes, fulfillmentType, OrderCargoEntity::getGrossWeightKg);
|
||||
BigDecimal netWeightKg = OrderCargoPackageCounts.physicalMetric(
|
||||
cargoes, fulfillmentType, OrderCargoEntity::getNetWeightKg);
|
||||
BigDecimal volumeCbm = OrderCargoPackageCounts.physicalMetric(
|
||||
cargoes, fulfillmentType, OrderCargoEntity::getVolumeCbm);
|
||||
return new CargoSummary(skuNos, shipmentIds, referenceIds, totalQuantity, totalPackageCount,
|
||||
grossWeightKg, netWeightKg, volumeCbm);
|
||||
}
|
||||
@@ -520,6 +523,7 @@ public class UnifiedOrderQueryService {
|
||||
List<BigDecimal> values = cargoes.stream().map(value).filter(Objects::nonNull).toList();
|
||||
return values.isEmpty() ? null : values.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
|
||||
}
|
||||
private String inboundMainStatus(String status) {
|
||||
if (status == null) return null;
|
||||
|
||||
@@ -1262,7 +1262,7 @@ components:
|
||||
heightCm: {type: number, minimum: 0, description: 箱规高(厘米)}
|
||||
unitsPerPackage: {type: integer, minimum: 1, description: 每箱数量;提交时必填}
|
||||
packageCount: {type: integer, minimum: 1, description: 箱数}
|
||||
packingGroupNo: {type: string, maxLength: 32, description: 一件代发混箱组合号;相同组合号的货物行共享同一批物理箱,组内箱数、包装类型和箱规必须一致}
|
||||
packingGroupNo: {type: string, maxLength: 32, description: 一件代发同箱关联键,由客户端在添加同箱 SKU 时生成;同组货物行共享物理箱,箱数、包装类型、箱规、重量和体积必须一致且汇总时只计算一次}
|
||||
grossWeightKg: {type: number, exclusiveMinimum: true, minimum: 0, description: 总毛重}
|
||||
volumeCbm: {type: number, exclusiveMinimum: true, minimum: 0, description: 总体积}
|
||||
totalQuantity: {type: integer, minimum: 1, description: 总数量;提交时必填且必须等于每箱数量 × 箱数}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baiyue.order.entity.OrderCargoEntity;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -18,6 +19,16 @@ class OrderCargoPackageCountsTest {
|
||||
List.of(skuA, skuB, separate), "ONE_CLICK_DROPSHIP")).isEqualTo(14);
|
||||
assertThat(OrderCargoPackageCounts.physical(
|
||||
List.of(skuA, skuB, separate), "STANDARD")).isEqualTo(24);
|
||||
|
||||
skuA.setGrossWeightKg(new BigDecimal("12.5"));
|
||||
skuB.setGrossWeightKg(new BigDecimal("12.5"));
|
||||
separate.setGrossWeightKg(new BigDecimal("3.0"));
|
||||
assertThat(OrderCargoPackageCounts.physicalMetric(
|
||||
List.of(skuA, skuB, separate), "ONE_CLICK_DROPSHIP", OrderCargoEntity::getGrossWeightKg))
|
||||
.isEqualByComparingTo("15.5");
|
||||
assertThat(OrderCargoPackageCounts.physicalMetric(
|
||||
List.of(skuA, skuB, separate), "STANDARD", OrderCargoEntity::getGrossWeightKg))
|
||||
.isEqualByComparingTo("28.0");
|
||||
}
|
||||
|
||||
private OrderCargoEntity cargo(int count, String group) {
|
||||
|
||||
@@ -131,6 +131,28 @@ class UnifiedOrderQueryServiceTest {
|
||||
verify(orders).findCargoByOrderIds(10001L, List.of(77L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dropshipMixedSkuRowsCountSharedCartonVolumeOnlyOnce() {
|
||||
UnifiedOrderQuery query = query(null);
|
||||
OrderEntity order = order(OrderStatus.APPROVED);
|
||||
order.setFulfillmentType("ONE_CLICK_DROPSHIP");
|
||||
OrderCargoEntity first = new OrderCargoEntity();
|
||||
first.setOrderId(77L); first.setPackageCount(10); first.setPackingGroupNo("PG-1");
|
||||
first.setVolumeCbm(new BigDecimal("0.375"));
|
||||
OrderCargoEntity second = new OrderCargoEntity();
|
||||
second.setOrderId(77L); second.setPackageCount(10); second.setPackingGroupNo("PG-1");
|
||||
second.setVolumeCbm(new BigDecimal("0.375"));
|
||||
when(orders.unifiedPage(eq(10001L), eq(query), any()))
|
||||
.thenReturn(new PageResult<>(List.of(order), 1, 20, 1, 1));
|
||||
when(firstleg.summaries(eq("internal-token"), any())).thenReturn(ApiResponse.success(List.of()));
|
||||
when(orders.findCargoByOrderIds(10001L, List.of(77L))).thenReturn(List.of(first, second));
|
||||
|
||||
assertThat(service.page(10001L, query).records()).singleElement().satisfies(row -> {
|
||||
assertThat(row.totalPackageCount()).isEqualTo(10);
|
||||
assertThat(row.expectedVolumeCbm()).isEqualByComparingTo("0.375");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void listAggregatesShipmentAndReferenceIdsFromAllCargoLines() {
|
||||
UnifiedOrderQuery query = query(null);
|
||||
|
||||
Reference in New Issue
Block a user