From b6c66565b94afc19337066221ea6e26743f73e2c Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 11 Sep 2026 16:50:10 +0800 Subject: [PATCH] fix: unify same-carton order metrics --- .../InternalOrderCargoController.java | 6 +++-- .../com/baiyue/order/dto/OrderRequests.java | 2 +- .../MerchantInboundOrderListService.java | 9 +++++--- .../service/OrderApplicationService.java | 7 ++++-- .../service/OrderCargoPackageCounts.java | 13 +++++++++++ .../service/UnifiedOrderQueryService.java | 10 ++++++--- .../src/main/resources/openapi/order-v1.yaml | 2 +- .../service/OrderCargoPackageCountsTest.java | 11 ++++++++++ .../service/UnifiedOrderQueryServiceTest.java | 22 +++++++++++++++++++ scripts/admin-test-env-modules/orders.mjs | 4 ++-- 10 files changed, 72 insertions(+), 14 deletions(-) diff --git a/order/src/main/java/com/baiyue/order/controller/InternalOrderCargoController.java b/order/src/main/java/com/baiyue/order/controller/InternalOrderCargoController.java index 86aa868c8..a7d753780 100644 --- a/order/src/main/java/com/baiyue/order/controller/InternalOrderCargoController.java +++ b/order/src/main/java/com/baiyue/order/controller/InternalOrderCargoController.java @@ -140,8 +140,10 @@ public class InternalOrderCargoController { // 申报重量/体积必须属于当前订单。拆分子单未物化独立货物事实时不得复制根单整单总量。 List 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("、")))); diff --git a/order/src/main/java/com/baiyue/order/dto/OrderRequests.java b/order/src/main/java/com/baiyue/order/dto/OrderRequests.java index 13a22c321..48fa55833 100644 --- a/order/src/main/java/com/baiyue/order/dto/OrderRequests.java +++ b/order/src/main/java/com/baiyue/order/dto/OrderRequests.java @@ -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, diff --git a/order/src/main/java/com/baiyue/order/service/MerchantInboundOrderListService.java b/order/src/main/java/com/baiyue/order/service/MerchantInboundOrderListService.java index 94ed30daf..a827da1c5 100644 --- a/order/src/main/java/com/baiyue/order/service/MerchantInboundOrderListService.java +++ b/order/src/main/java/com/baiyue/order/service/MerchantInboundOrderListService.java @@ -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 cargoColumns = cargoes.stream() diff --git a/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java b/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java index d9c3d0825..8f2a76c8b 100644 --- a/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java +++ b/order/src/main/java/com/baiyue/order/service/OrderApplicationService.java @@ -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 的箱数、包装类型、箱规、重量和体积必须一致"); } } } diff --git a/order/src/main/java/com/baiyue/order/service/OrderCargoPackageCounts.java b/order/src/main/java/com/baiyue/order/service/OrderCargoPackageCounts.java index 555357944..df1403a16 100644 --- a/order/src/main/java/com/baiyue/order/service/OrderCargoPackageCounts.java +++ b/order/src/main/java/com/baiyue/order/service/OrderCargoPackageCounts.java @@ -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 cargoes, String fulfillmentType, + Function getter) { + Set countedGroups = new HashSet<>(); + List 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); + } } diff --git a/order/src/main/java/com/baiyue/order/service/UnifiedOrderQueryService.java b/order/src/main/java/com/baiyue/order/service/UnifiedOrderQueryService.java index e40838a64..4aa72693c 100644 --- a/order/src/main/java/com/baiyue/order/service/UnifiedOrderQueryService.java +++ b/order/src/main/java/com/baiyue/order/service/UnifiedOrderQueryService.java @@ -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 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; diff --git a/order/src/main/resources/openapi/order-v1.yaml b/order/src/main/resources/openapi/order-v1.yaml index 1096cd56b..ad2f90bfa 100644 --- a/order/src/main/resources/openapi/order-v1.yaml +++ b/order/src/main/resources/openapi/order-v1.yaml @@ -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: 总数量;提交时必填且必须等于每箱数量 × 箱数} diff --git a/order/src/test/java/com/baiyue/order/service/OrderCargoPackageCountsTest.java b/order/src/test/java/com/baiyue/order/service/OrderCargoPackageCountsTest.java index e05da0868..4226fca70 100644 --- a/order/src/test/java/com/baiyue/order/service/OrderCargoPackageCountsTest.java +++ b/order/src/test/java/com/baiyue/order/service/OrderCargoPackageCountsTest.java @@ -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) { diff --git a/order/src/test/java/com/baiyue/order/service/UnifiedOrderQueryServiceTest.java b/order/src/test/java/com/baiyue/order/service/UnifiedOrderQueryServiceTest.java index 7f90979c8..f502162aa 100644 --- a/order/src/test/java/com/baiyue/order/service/UnifiedOrderQueryServiceTest.java +++ b/order/src/test/java/com/baiyue/order/service/UnifiedOrderQueryServiceTest.java @@ -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); diff --git a/scripts/admin-test-env-modules/orders.mjs b/scripts/admin-test-env-modules/orders.mjs index 296027a1d..8850c800c 100644 --- a/scripts/admin-test-env-modules/orders.mjs +++ b/scripts/admin-test-env-modules/orders.mjs @@ -454,9 +454,9 @@ function buildPayload(context, profile, master, productImageFileId, customerAddr hsCode: '8518210000', customsHsCode: '8518210000', packageType: 'CARTON', packingGroupNo: dropship ? mixedPackingGroupNo : null, lengthCm: carton.lengthCm, widthCm: carton.widthCm, heightCm: carton.heightCm, - unitsPerPackage, packageCount, grossWeightKg: (10 + index) * packageCount, + unitsPerPackage, packageCount, grossWeightKg: (dropship ? 10 : 10 + index) * packageCount, volumeCbm: cartonVolumeCbm * packageCount, totalQuantity: unitsPerPackage * packageCount, - netWeightKg: (9 + index) * packageCount, + netWeightKg: (dropship ? 9 : 9 + index) * packageCount, unitDeclaredValue: 8.5, declaredValue: unitsPerPackage * packageCount * 8.5, currency: 'USD', material: '塑料 / Plastic', usage: '接口验收 / API acceptance', model: `MODEL-${box.boxType}`, brand: 'TEST-NO-LOGO',