Просмотр исходного кода

feat(闪送): 用户端订单列表返回v2多站点逐站信息

- 闪送专属列表卡片新增 stops:寄件人全站文字地址+备注+状态,收件人看他人订单仅本站地址,其余站只留类型/序号/状态(与详情口径一致)
- 收件人地址替换复用批量站点查询,消除逐单 N+1
- 统一订单列表闪送块补 stops 与双进度四计数,批量 IN 取站
- 新增 FlashDeliveryUserCardStops 统一两处列表的卡片脱敏口径
qmj 4 дней назад
Родитель
Сommit
829bd82cff

+ 2 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryUserOrderListView.java

@@ -67,4 +67,6 @@ public class FlashDeliveryUserOrderListView {
     private Integer deliveredStopCount;
     /** 任一站已取货即禁止用户自助取消,不能仅按订单主状态判断。 */
     private Boolean canUserCancel;
+    /** v2 多站点单的逐站列表(用户顺序);寄件人全站文字地址,收件人仅本站,其余站只留类型/序号/状态。 */
+    private java.util.List<FlashDeliveryStopView> stops;
 }

+ 10 - 3
ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationService.java

@@ -314,16 +314,23 @@ public class FlashDeliveryApplicationService {
             query.eq("user_id", userId);
         }
         query.orderByDesc("create_time");
-        return mapPage(orderMapper.selectPage(page(pageNum, pageSize), query), order -> {
+        IPage<FlashDeliveryOrder> orders = orderMapper.selectPage(page(pageNum, pageSize), query);
+        Map<Long, List<FlashDeliveryOrderStop>> stopRows = stopRowsFor(orders.getRecords());
+        return mapPage(orders, order -> {
             FlashDeliveryUserOrderListView view = userOrderListView(order);
+            List<FlashDeliveryOrderStop> rows = stopRows.get(order.getId());
             if ("receiver".equals(selectedRole) && order.getDeliveryStopCount() != null
-                    && !Objects.equals(userId, order.getUserId())) {
-                stops(order.getId()).stream().filter(s -> "DELIVERY".equals(s.getStopType())
+                    && !Objects.equals(userId, order.getUserId()) && rows != null) {
+                rows.stream().filter(s -> "DELIVERY".equals(s.getStopType())
                         && Objects.equals(userId, s.getReceiverUserId())).findFirst().ifPresent(own -> {
                     view.setDeliveryAddress(own.getAddress());
                     view.setDeliveryDetailAddress(own.getAddressDetail());
                 });
             }
+            // v2 多站点卡片逐站展示;寄件人全站文字地址,收件人仅本站,其余站只留类型/序号/状态。
+            if (order.getDeliveryStopCount() != null && rows != null && !rows.isEmpty()) {
+                view.setStops(FlashDeliveryUserCardStops.of(rows, userId, Objects.equals(userId, order.getUserId())));
+            }
             view.setCanUserCancel(Objects.equals(userId, order.getUserId()) && canUserCancel(order));
             return view;
         });

+ 29 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryUserCardStops.java

@@ -0,0 +1,29 @@
+package com.ruoyi.app.flashdelivery.service;
+
+import com.ruoyi.app.flashdelivery.dto.FlashDeliveryStopView;
+import com.ruoyi.system.domain.flash.FlashDeliveryOrderStop;
+import org.springframework.beans.BeanUtils;
+
+import java.util.List;
+import java.util.Objects;
+
+/** 用户端订单卡片逐站列表脱敏:寄件人返回全站文字地址;收件人看他人订单仅本站保留地址,其余站只留类型/序号/状态。 */
+public final class FlashDeliveryUserCardStops {
+    private FlashDeliveryUserCardStops() {}
+
+    public static List<FlashDeliveryStopView> of(List<FlashDeliveryOrderStop> rows, Long viewerId, boolean sender) {
+        return rows.stream().map(stop -> {
+            FlashDeliveryStopView view = new FlashDeliveryStopView();
+            BeanUtils.copyProperties(stop, view);
+            view.setDeliveryPinCode(null);
+            // 卡片不落联系人、电话、坐标与凭证;收件人视角他人的站连地址与备注也隐藏,与详情口径一致。
+            view.setName(null); view.setPhone(null);
+            view.setLongitude(null); view.setLatitude(null);
+            if (!sender && !("DELIVERY".equals(stop.getStopType()) && Objects.equals(viewerId, stop.getReceiverUserId()))) {
+                view.setAddress(null); view.setAddressDetail(null);
+                view.setHandoffMethod(null); view.setRemark(null);
+            }
+            return view;
+        }).toList();
+    }
+}

+ 33 - 3
ruoyi-admin/src/main/java/com/ruoyi/app/unifiedorder/UnifiedOrderQueryService.java

@@ -2,15 +2,19 @@ package com.ruoyi.app.unifiedorder;
 
 import com.alibaba.fastjson2.JSONArray;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.app.flashdelivery.service.FlashDeliveryUserCardStops;
 import com.ruoyi.app.unifiedorder.dto.UnifiedOrderListItem;
 import com.ruoyi.app.unifiedorder.dto.UnifiedOrderPageView;
 import com.ruoyi.system.domain.PosOrder;
 import com.ruoyi.system.domain.PosStore;
 import com.ruoyi.system.domain.flash.FlashDeliveryOrder;
+import com.ruoyi.system.domain.flash.FlashDeliveryOrderStop;
 import com.ruoyi.system.mapper.PosOrderMapper;
 import com.ruoyi.system.mapper.PosStoreMapper;
 import com.ruoyi.system.mapper.flash.FlashDeliveryOrderMapper;
+import com.ruoyi.system.mapper.flash.FlashDeliveryOrderStopMapper;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
@@ -21,6 +25,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * 统一订单列表查询:外卖与闪送两路各取 size+1 条,Java 按游标全序归并,读时聚合零写路径。
@@ -43,13 +48,16 @@ public class UnifiedOrderQueryService {
     private final PosOrderMapper posOrderMapper;
     private final FlashDeliveryOrderMapper flashOrderMapper;
     private final PosStoreMapper posStoreMapper;
+    private final FlashDeliveryOrderStopMapper flashStopMapper;
 
     public UnifiedOrderQueryService(PosOrderMapper posOrderMapper,
                                     FlashDeliveryOrderMapper flashOrderMapper,
-                                    PosStoreMapper posStoreMapper) {
+                                    PosStoreMapper posStoreMapper,
+                                    FlashDeliveryOrderStopMapper flashStopMapper) {
         this.posOrderMapper = posOrderMapper;
         this.flashOrderMapper = flashOrderMapper;
         this.posStoreMapper = posStoreMapper;
+        this.flashStopMapper = flashStopMapper;
     }
 
     /** 查询统一列表:size 服务端钳制 1-50;cursor 为空表示首页。 */
@@ -79,11 +87,12 @@ public class UnifiedOrderQueryService {
 
         UnifiedOrderPageView view = new UnifiedOrderPageView();
         Map<Long, PosStore> stores = storesOf(displayed);
+        Map<Long, List<FlashDeliveryOrderStop>> flashStops = flashStopRows(displayed);
         List<UnifiedOrderListItem> items = new ArrayList<>(displayed.size());
         for (Row row : displayed) {
             items.add(row.takeaway() != null
                     ? takeawayItem(row.takeaway(), stores)
-                    : flashItem(row.flash(), userId));
+                    : flashItem(row.flash(), userId, flashStops.get(row.flash().getId())));
         }
         view.setList(items);
         view.setHasMore(hasMore);
@@ -204,7 +213,18 @@ public class UnifiedOrderQueryService {
         }
     }
 
-    private UnifiedOrderListItem flashItem(FlashDeliveryOrder order, Long userId) {
+    /** 展示页闪送 v2 多站点单一次 IN 批量取站点,避免逐单 N+1;外卖路与 v1 闪送不查。 */
+    private Map<Long, List<FlashDeliveryOrderStop>> flashStopRows(List<Row> displayed) {
+        List<Long> orderIds = displayed.stream().map(Row::flash).filter(Objects::nonNull)
+                .filter(order -> order.getDeliveryStopCount() != null)
+                .map(FlashDeliveryOrder::getId).toList();
+        if (orderIds.isEmpty()) return Map.of();
+        return flashStopMapper.selectList(new QueryWrapper<FlashDeliveryOrderStop>()
+                        .in("order_id", orderIds).orderByAsc("stop_order", "id")).stream()
+                .collect(Collectors.groupingBy(FlashDeliveryOrderStop::getOrderId));
+    }
+
+    private UnifiedOrderListItem flashItem(FlashDeliveryOrder order, Long userId, List<FlashDeliveryOrderStop> stopRows) {
         UnifiedOrderListItem item = new UnifiedOrderListItem();
         item.setSourceType("flash");
         item.setRole(userId != null && userId.equals(order.getUserId()) ? "sender" : "receiver");
@@ -230,6 +250,16 @@ public class UnifiedOrderQueryService {
         item.setPackageType(order.getPackageType());
         item.setQuantity(order.getQuantity());
         item.setTipAmount(order.getTipAmount());
+        // v2 多站点单补双进度计数与逐站列表;卡片脱敏口径与闪送专属列表一致。
+        if (order.getDeliveryStopCount() != null) {
+            item.setPickupStopCount(order.getPickupStopCount());
+            item.setPickedUpStopCount(order.getPickedUpStopCount());
+            item.setDeliveryStopCount(order.getDeliveryStopCount());
+            item.setDeliveredStopCount(order.getDeliveredStopCount());
+            if (stopRows != null && !stopRows.isEmpty()) {
+                item.setStops(FlashDeliveryUserCardStops.of(stopRows, userId, Objects.equals(userId, order.getUserId())));
+            }
+        }
         return item;
     }
 

+ 11 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/unifiedorder/dto/UnifiedOrderListItem.java

@@ -2,7 +2,10 @@ package com.ruoyi.app.unifiedorder.dto;
 
 import lombok.Data;
 
+import com.ruoyi.app.flashdelivery.dto.FlashDeliveryStopView;
+
 import java.util.Date;
+import java.util.List;
 
 /**
  * 统一订单列表项:公共字段 + 外卖渲染块 + 闪送渲染块,前端按 sourceType 复用现有两种卡片。
@@ -86,4 +89,12 @@ public class UnifiedOrderListItem {
     private Integer quantity;
     /** 小费。 */
     private Long tipAmount;
+    /** 取货站总数及已取数;v2 多站点单返回,v1 闪送卡为空。 */
+    private Integer pickupStopCount;
+    private Integer pickedUpStopCount;
+    /** 收货站总数与已送达数;v2 多站点单返回,v1 闪送卡为空。 */
+    private Integer deliveryStopCount;
+    private Integer deliveredStopCount;
+    /** v2 逐站列表(用户顺序);寄件人全站文字地址,收件人看他人订单仅本站地址,其余站只留类型/序号/状态。 */
+    private List<FlashDeliveryStopView> stops;
 }

+ 27 - 0
ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryV2Test.java

@@ -389,6 +389,33 @@ class FlashDeliveryV2Test {
         assertEquals("second receiver floor", card.getDeliveryDetailAddress());
     }
 
+    @Test void userListCardsCarryStopsForSenderAndMaskOtherStopsForReceiver() {
+        Fixture f = ready(); receivers(f);
+        f.rows.get(0).setRemark("取月餅"); f.rows.get(3).setRemark("送前台");
+        var page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<FlashDeliveryOrder>(1, 10);
+        page.setRecords(List.of(f.order));
+        when(f.orders.selectPage(any(), any())).thenReturn(page);
+        // 寄件人卡片:全站文字地址与备注可见,联系人、电话、坐标仍不落卡片。
+        var senderCard = f.service.userOrders(7L, 1, 10, null).getRecords().get(0);
+        assertEquals(List.of("address 0", "address 1", "address 2", "address 3"),
+                senderCard.getStops().stream().map(FlashDeliveryStopView::getAddress).toList());
+        assertEquals(java.util.Arrays.asList("取月餅", null, null, "送前台"),
+                senderCard.getStops().stream().map(FlashDeliveryStopView::getRemark).toList());
+        assertEquals(List.of("PICKED_UP", "PICKED_UP", "PENDING", "PENDING"),
+                senderCard.getStops().stream().map(FlashDeliveryStopView::getStatus).toList());
+        assertNull(senderCard.getStops().get(0).getName());
+        assertNull(senderCard.getStops().get(0).getPhone());
+        assertNull(senderCard.getStops().get(0).getLongitude());
+        // 收件人卡片(32 号用户绑定第 4 站):仅本站保留地址与备注,其余站只留类型/序号/状态。
+        var receiverCard = f.service.userOrders(32L, 1, 10, "receiver").getRecords().get(0);
+        assertEquals(java.util.Arrays.asList(null, null, null, "address 3"),
+                receiverCard.getStops().stream().map(FlashDeliveryStopView::getAddress).toList());
+        assertEquals(java.util.Arrays.asList(null, null, null, "送前台"),
+                receiverCard.getStops().stream().map(FlashDeliveryStopView::getRemark).toList());
+        assertEquals(List.of("PICKUP", "PICKUP", "DELIVERY", "DELIVERY"),
+                receiverCard.getStops().stream().map(FlashDeliveryStopView::getStopType).toList());
+    }
+
     @Test void legacyDeliveryRejectsV2OrdersIncludingSingleDropoffBeforeAnyWrite() {
         for (int count : List.of(1, 2)) {
             Fixture f = ready(); if (count == 1) f.rows.remove(3); f.order.setDeliveryStopCount(count);

+ 39 - 1
ruoyi-admin/src/test/java/com/ruoyi/app/unifiedorder/UnifiedOrderQueryServiceTest.java

@@ -13,6 +13,8 @@ import com.ruoyi.system.domain.flash.FlashDeliveryStatus;
 import com.ruoyi.system.mapper.PosOrderMapper;
 import com.ruoyi.system.mapper.PosStoreMapper;
 import com.ruoyi.system.mapper.flash.FlashDeliveryOrderMapper;
+import com.ruoyi.system.mapper.flash.FlashDeliveryOrderStopMapper;
+import com.ruoyi.app.flashdelivery.dto.FlashDeliveryStopView;
 import org.apache.ibatis.builder.MapperBuilderAssistant;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -44,8 +46,9 @@ class UnifiedOrderQueryServiceTest {
     private final PosOrderMapper posOrderMapper = mock(PosOrderMapper.class);
     private final FlashDeliveryOrderMapper flashOrderMapper = mock(FlashDeliveryOrderMapper.class);
     private final PosStoreMapper posStoreMapper = mock(PosStoreMapper.class);
+    private final FlashDeliveryOrderStopMapper flashStopMapper = mock(FlashDeliveryOrderStopMapper.class);
     private final UnifiedOrderQueryService service =
-            new UnifiedOrderQueryService(posOrderMapper, flashOrderMapper, posStoreMapper);
+            new UnifiedOrderQueryService(posOrderMapper, flashOrderMapper, posStoreMapper, flashStopMapper);
 
     @BeforeAll
     static void initializeTableMetadata() {
@@ -55,6 +58,32 @@ class UnifiedOrderQueryServiceTest {
                 new MapperBuilderAssistant(new MybatisConfiguration(), ""), FlashDeliveryOrder.class);
     }
 
+    @Test
+    void flashMultiStopOrderCarriesCountsAndReceiverMaskedStops() {
+        FlashDeliveryOrder order = flash(31L, 3000L);
+        order.setUserId(7L);
+        order.setPickupStopCount(2); order.setPickedUpStopCount(1);
+        order.setDeliveryStopCount(2); order.setDeliveredStopCount(0);
+        doReturn(pageOf(List.of(order))).when(flashOrderMapper).selectPage(any(), any());
+        doReturn(pageOf(List.of())).when(posOrderMapper).selectPage(any(), any());
+        when(flashStopMapper.selectList(any())).thenReturn(java.util.Arrays.asList(
+                stopRow(101L, 0, "PICKUP", null), stopRow(102L, 1, "PICKUP", null),
+                stopRow(103L, 2, "DELIVERY", 7L), stopRow(104L, 3, "DELIVERY", USER_ID)));
+
+        UnifiedOrderPageView view = service.query(USER_ID, UnifiedOrderTab.ALL, null, null, 10);
+
+        UnifiedOrderListItem item = view.getList().get(0);
+        assertEquals("receiver", item.getRole());
+        assertEquals(2, item.getPickupStopCount()); assertEquals(1, item.getPickedUpStopCount());
+        assertEquals(2, item.getDeliveryStopCount()); assertEquals(0, item.getDeliveredStopCount());
+        // 查看人是第 4 站收件人:仅本站保留地址,其余站只留类型/序号/状态。
+        assertEquals(4, item.getStops().size());
+        assertEquals(java.util.Arrays.asList(null, null, null, "address 3"),
+                item.getStops().stream().map(FlashDeliveryStopView::getAddress).toList());
+        assertEquals(List.of("PICKUP", "PICKUP", "DELIVERY", "DELIVERY"),
+                item.getStops().stream().map(FlashDeliveryStopView::getStopType).toList());
+    }
+
     @Test
     void twoPathsMergeInDisplayOrderAndReturnExactlySize() {
         doReturn(pageOf(List.of(
@@ -421,6 +450,15 @@ class UnifiedOrderQueryServiceTest {
         return order;
     }
 
+    private static com.ruoyi.system.domain.flash.FlashDeliveryOrderStop stopRow(long id, int index, String type,
+                                                                                Long receiverUserId) {
+        com.ruoyi.system.domain.flash.FlashDeliveryOrderStop stop = new com.ruoyi.system.domain.flash.FlashDeliveryOrderStop();
+        stop.setId(id); stop.setOrderId(31L); stop.setStopType(type); stop.setStopOrder(index);
+        stop.setStatus("PENDING"); stop.setAddress("address " + index);
+        stop.setReceiverUserId(receiverUserId);
+        return stop;
+    }
+
     private static <T> Page<T> pageOf(List<T> records) {
         Page<T> page = new Page<>(1, Math.max(records.size(), 1), false);
         page.setRecords(new ArrayList<>(records));