Răsfoiți Sursa

提交原矿物流合同

chenxiaofei 1 lună în urmă
părinte
comite
e43ee9a61d

+ 6 - 1
sckw-modules/sckw-transport/pom.xml

@@ -152,6 +152,11 @@
             <groupId>org.assertj</groupId>
             <artifactId>assertj-core</artifactId>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
 
         <dependency>
             <groupId>com.sckw</groupId>
@@ -179,4 +184,4 @@
             </plugin>
         </plugins>
     </build>
-</project>
+</project>

+ 51 - 11
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/dashboard/RealtimeSalesVolumeService.java

@@ -1,15 +1,18 @@
 package com.sckw.transport.service.dashboard;
 
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.sckw.core.common.enums.enums.DictEnum;
 import com.sckw.core.model.constant.Global;
 import com.sckw.core.model.enums.CarWaybillV1Enum;
 import com.sckw.core.utils.CollectionUtils;
 import com.sckw.transport.api.model.vo.RealtimeSalesGoodsSeriesVo;
 import com.sckw.transport.api.model.vo.RealtimeSalesVolumeVo;
+import com.sckw.transport.model.KwtLogisticsOrder;
 import com.sckw.transport.model.KwtLogisticsOrderGoods;
 import com.sckw.transport.model.KwtWaybillOrder;
 import com.sckw.transport.model.KwtWaybillOrderSubtask;
 import com.sckw.transport.repository.KwtLogisticsOrderGoodsRepository;
+import com.sckw.transport.repository.KwtLogisticsOrderRepository;
 import com.sckw.transport.repository.KwtWaybillOrderRepository;
 import com.sckw.transport.repository.KwtWaybillOrderSubtaskRepository;
 import lombok.RequiredArgsConstructor;
@@ -68,6 +71,7 @@ public class RealtimeSalesVolumeService {
     private final KwtWaybillOrderRepository waybillOrderRepository;
     private final KwtWaybillOrderSubtaskRepository waybillOrderSubtaskRepository;
     private final KwtLogisticsOrderGoodsRepository logisticsOrderGoodsRepository;
+    private final KwtLogisticsOrderRepository logisticsOrderRepository;
 
     /**
      * 构建实时销量视图对象
@@ -117,13 +121,16 @@ public class RealtimeSalesVolumeService {
         // 5. 提取运单ID和物流订单ID集合,用于后续关联查询
         Set<Long> wIds = waybills.stream().map(KwtWaybillOrder::getId).filter(Objects::nonNull).collect(Collectors.toSet());
         Set<Long> lOrderIds = waybills.stream().map(KwtWaybillOrder::getLOrderId).filter(Objects::nonNull).collect(Collectors.toSet());
+        // 按物流订单计费方式决定净重取装货量还是卸货量,避免实时销量口径与结算口径不一致。
+        Map<Long, String> billingModeByLogOrder = billingModeByLogOrder(lOrderIds);
 
         // 6. 查询运单子任务,并计算每个运单的净重总和
         Map<Long, BigDecimal> netByWaybill = netWeightByWaybill(
                 waybillOrderSubtaskRepository.list(
                         Wrappers.<KwtWaybillOrderSubtask>lambdaQuery()
                                 .in(KwtWaybillOrderSubtask::getWOrderId, wIds)
-                                .eq(KwtWaybillOrderSubtask::getDelFlag, Global.NO)));
+                                .eq(KwtWaybillOrderSubtask::getDelFlag, Global.NO)),
+                billingModeByLogOrder);
         log.debug("计算得到 {} 个运单的净重数据", netByWaybill.size());
 
         // 7. 查询物流订单货物信息,并选取每个物流订单的主要商品
@@ -233,6 +240,29 @@ public class RealtimeSalesVolumeService {
         return result;
     }
 
+    /**
+     * 查询物流订单计费方式映射。
+     *
+     * @param lOrderIds 物流订单ID集合
+     * @return 物流订单ID -> 计费方式
+     */
+    private Map<Long, String> billingModeByLogOrder(Set<Long> lOrderIds) {
+        if (CollectionUtils.isEmpty(lOrderIds)) {
+            log.debug("物流订单ID集合为空,跳过计费方式查询。");
+            return Map.of();
+        }
+        List<KwtLogisticsOrder> logisticsOrders = logisticsOrderRepository.queryByLogOrderIds(lOrderIds);
+        if (CollectionUtils.isEmpty(logisticsOrders)) {
+            log.warn("未查询到物流订单计费方式,lOrderIds={}", lOrderIds);
+            return Map.of();
+        }
+        Map<Long, String> result = logisticsOrders.stream()
+                .filter(order -> order != null && order.getId() != null && order.getBillingMode() != null)
+                .collect(Collectors.toMap(KwtLogisticsOrder::getId, KwtLogisticsOrder::getBillingMode, (left, right) -> left));
+        log.debug("物流订单计费方式映射构建完成,共 {} 条。", result.size());
+        return result;
+    }
+
     /**
      * 获取运单的完成时间
      * <p>
@@ -257,15 +287,16 @@ public class RealtimeSalesVolumeService {
      * @param subtasks 运单子任务列表
      * @return 运单ID -> 净重总和的映射
      */
-    private static Map<Long, BigDecimal> netWeightByWaybill(List<KwtWaybillOrderSubtask> subtasks) {
+    private static Map<Long, BigDecimal> netWeightByWaybill(List<KwtWaybillOrderSubtask> subtasks, Map<Long, String> billingModeByLogOrder) {
         if (CollectionUtils.isEmpty(subtasks)) {
             return Map.of();
         }
         
         Map<Long, BigDecimal> result = subtasks.stream()
+                .filter(st -> st != null && st.getWOrderId() != null)
                 .collect(Collectors.groupingBy(
                         KwtWaybillOrderSubtask::getWOrderId,
-                        Collectors.mapping(RealtimeSalesVolumeService::subtaskNetTon,
+                        Collectors.mapping(st -> subtaskNetTon(st, billingModeByLogOrder.get(st.getLOrderId())),
                                 Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
         
         log.debug("从 {} 条子任务记录中聚合出 {} 个运单的净重。", subtasks.size(), result.size());
@@ -275,22 +306,31 @@ public class RealtimeSalesVolumeService {
     /**
      * 计算单个子任务的净重
      * <p>
-     * 优先使用卸货量 (unloadAmount),如果不存在或<=0,则尝试使用装货量 (loadAmount)
+     * billingMode=1 按装货量计算,billingMode=2 按卸货量计算,其它缺失或异常值默认按卸货量计算
      *
      * @param st 子任务对象
+     * @param billingMode 物流订单计费方式
      * @return 净重(吨),默认为0
      */
-    private static BigDecimal subtaskNetTon(KwtWaybillOrderSubtask st) {
+    static BigDecimal subtaskNetTon(KwtWaybillOrderSubtask st, String billingMode) {
         if (st == null) {
             return BigDecimal.ZERO;
         }
-        
-        // 尝试获取卸货量,若无效则获取装货量
+        if (DictEnum.CHARGING_TYPE_1.getValue().equals(billingMode)) {
+            return validAmountOrZero(st.getLoadAmount());
+        }
+        return validAmountOrZero(st.getUnloadAmount());
+    }
 
-        return Stream.of(st.getUnloadAmount(), st.getLoadAmount())
-                .filter(Objects::nonNull)
-                .filter(a -> a.compareTo(BigDecimal.ZERO) > 0) // 只取正数
-                .findFirst()
+    /**
+     * 过滤无效重量,避免空指针和负数污染统计结果。
+     *
+     * @param amount 原始重量
+     * @return 有效重量;为空或小于等于0时返回0
+     */
+    private static BigDecimal validAmountOrZero(BigDecimal amount) {
+        return Optional.ofNullable(amount)
+                .filter(value -> value.compareTo(BigDecimal.ZERO) > 0)
                 .orElse(BigDecimal.ZERO);
     }
 

+ 65 - 0
sckw-modules/sckw-transport/src/test/java/com/sckw/transport/service/dashboard/RealtimeSalesVolumeServiceTest.java

@@ -0,0 +1,65 @@
+package com.sckw.transport.service.dashboard;
+
+import com.sckw.core.common.enums.enums.DictEnum;
+import com.sckw.transport.model.KwtWaybillOrderSubtask;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+
+/**
+ * 实时销量服务单元测试。
+ */
+public class RealtimeSalesVolumeServiceTest {
+
+    /**
+     * 按装货量计费时,应取子任务装货量作为净重。
+     */
+    @Test
+    public void subtaskNetTonShouldUseLoadAmountWhenBillingByLoadAmount() {
+        KwtWaybillOrderSubtask subtask = buildSubtask("10.50", "9.25");
+
+        BigDecimal actual = RealtimeSalesVolumeService.subtaskNetTon(subtask, DictEnum.CHARGING_TYPE_1.getValue());
+
+        Assert.assertEquals(new BigDecimal("10.50"), actual);
+    }
+
+    /**
+     * 按卸货量计费时,应取子任务卸货量作为净重。
+     */
+    @Test
+    public void subtaskNetTonShouldUseUnloadAmountWhenBillingByUnloadAmount() {
+        KwtWaybillOrderSubtask subtask = buildSubtask("10.50", "9.25");
+
+        BigDecimal actual = RealtimeSalesVolumeService.subtaskNetTon(subtask, DictEnum.CHARGING_TYPE_2.getValue());
+
+        Assert.assertEquals(new BigDecimal("9.25"), actual);
+    }
+
+    /**
+     * 计费方式缺失时保持默认卸货量口径,空值或非正数返回0。
+     */
+    @Test
+    public void subtaskNetTonShouldDefaultToUnloadAmountAndIgnoreInvalidAmount() {
+        KwtWaybillOrderSubtask subtask = buildSubtask("10.50", "-1.00");
+
+        BigDecimal actual = RealtimeSalesVolumeService.subtaskNetTon(subtask, null);
+
+        Assert.assertEquals(BigDecimal.ZERO, actual);
+        Assert.assertEquals(BigDecimal.ZERO, RealtimeSalesVolumeService.subtaskNetTon(null, DictEnum.CHARGING_TYPE_1.getValue()));
+    }
+
+    /**
+     * 构造子任务测试数据。
+     *
+     * @param loadAmount   装货量
+     * @param unloadAmount 卸货量
+     * @return 子任务对象
+     */
+    private KwtWaybillOrderSubtask buildSubtask(String loadAmount, String unloadAmount) {
+        KwtWaybillOrderSubtask subtask = new KwtWaybillOrderSubtask();
+        subtask.setLoadAmount(new BigDecimal(loadAmount));
+        subtask.setUnloadAmount(new BigDecimal(unloadAmount));
+        return subtask;
+    }
+}