package com.ruoyi.app.unifiedorder; import com.alibaba.fastjson2.JSONArray; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 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.mapper.PosOrderMapper; import com.ruoyi.system.mapper.PosStoreMapper; import com.ruoyi.system.mapper.flash.FlashDeliveryOrderMapper; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; /** * 统一订单列表查询:外卖与闪送两路各取 size+1 条,Java 按游标全序归并,读时聚合零写路径。 * 待付款与退款·售后 tab 闪送侧恒空,直接跳过闪送路;来源单选时跳过另一路。 * dinein/pickup 是外卖路按 pos_order.type 的子集筛选(堂食=2、自取=1、takeaway=0 纯外送), * 其游标编码相应为 dinein/pickup,同源翻页精确到 id 决胜。 */ @Service public class UnifiedOrderQueryService { /** 每页条数上限。 */ private static final int MAX_PAGE_SIZE = 50; /** unifiedStatus 的判定优先级:售后优先于状态,与 tab 互斥性一致。 */ private static final List STATUS_RESOLUTION_ORDER = List.of( UnifiedOrderTab.REFUND, UnifiedOrderTab.UNPAID, UnifiedOrderTab.ACTIVE, UnifiedOrderTab.COMPLETED, UnifiedOrderTab.CANCELLED); /** 闪送只可能命中这三个 tab。 */ private static final List FLASH_STATUS_RESOLUTION_ORDER = List.of( UnifiedOrderTab.ACTIVE, UnifiedOrderTab.COMPLETED, UnifiedOrderTab.CANCELLED); private final PosOrderMapper posOrderMapper; private final FlashDeliveryOrderMapper flashOrderMapper; private final PosStoreMapper posStoreMapper; public UnifiedOrderQueryService(PosOrderMapper posOrderMapper, FlashDeliveryOrderMapper flashOrderMapper, PosStoreMapper posStoreMapper) { this.posOrderMapper = posOrderMapper; this.flashOrderMapper = flashOrderMapper; this.posStoreMapper = posStoreMapper; } /** 查询统一列表:size 服务端钳制 1-50;cursor 为空表示首页。 */ public UnifiedOrderPageView query(Long userId, UnifiedOrderTab tab, OrderSource source, UnifiedCursor cursor, int size) { int limit = Math.max(1, Math.min(MAX_PAGE_SIZE, size)); List rows = new ArrayList<>(); if (source == null || source.queriesPosOrder()) { OrderSource takeawayTag = source == null ? OrderSource.TAKEAWAY : source; Page page = posOrderMapper.selectPage( new Page<>(1, limit + 1, false), takeawayWrapper(userId, tab, cursor, source)); for (PosOrder order : page.getRecords()) { rows.add(new Row(UnifiedCursor.of(safeTime(order.getCretim()), takeawayTag, order.getId()), order, null)); } } if (tab.queriesFlash() && (source == null || source == OrderSource.FLASH)) { Page page = flashOrderMapper.selectPage( new Page<>(1, limit + 1, false), flashWrapper(userId, tab, cursor)); for (FlashDeliveryOrder order : page.getRecords()) { rows.add(new Row(UnifiedCursor.of(safeTime(order.getCreateTime()), OrderSource.FLASH, order.getId()), null, order)); } } rows.sort(java.util.Comparator.comparing(Row::key, UnifiedCursor.DISPLAY_ORDER)); boolean hasMore = rows.size() > limit; List displayed = hasMore ? rows.subList(0, limit) : rows; UnifiedOrderPageView view = new UnifiedOrderPageView(); Map stores = storesOf(displayed); List items = new ArrayList<>(displayed.size()); for (Row row : displayed) { items.add(row.takeaway() != null ? takeawayItem(row.takeaway(), stores) : flashItem(row.flash(), userId)); } view.setList(items); view.setHasMore(hasMore); view.setNextCursor(hasMore && !displayed.isEmpty() ? displayed.get(displayed.size() - 1).key().encode() : null); return view; } /** 外卖路条件:用户 + 来源 type 过滤 + tab 谓词 + 游标推进,排序 cretim DESC, id DESC;source 为 all 时不限 type。 */ private LambdaQueryWrapper takeawayWrapper(Long userId, UnifiedOrderTab tab, UnifiedCursor cursor, OrderSource source) { LambdaQueryWrapper wrapper = tab.posOrderWrapper() .eq(PosOrder::getUserId, userId); if (source != null) { wrapper.eq(PosOrder::getType, source.posOrderType()); } wrapper.orderByDesc(PosOrder::getCretim) .orderByDesc(PosOrder::getId); // 游标推进按本路查询源判定:混排(source=all)按 takeaway,单选按所选源 OrderSource advanceSource = source == null ? OrderSource.TAKEAWAY : source; if (cursor != null) { Date time = new Date(cursor.getTimeMillis()); if (cursor.includesSameTimeAnyId(advanceSource)) { wrapper.le(PosOrder::getCretim, time); } else if (cursor.includesSameTimeLowerId(advanceSource)) { wrapper.and(w -> w.lt(PosOrder::getCretim, time) .or(x -> x.eq(PosOrder::getCretim, time).lt(PosOrder::getId, cursor.getOrderId()))); } else { wrapper.lt(PosOrder::getCretim, time); } } return wrapper; } /** 闪送路条件:寄件人或收件人 + tab 谓词 + 游标推进,排序 create_time DESC, id DESC。 */ private LambdaQueryWrapper flashWrapper(Long userId, UnifiedOrderTab tab, UnifiedCursor cursor) { LambdaQueryWrapper wrapper = tab.flashWrapper() .and(w -> w.eq(FlashDeliveryOrder::getUserId, userId) .or().eq(FlashDeliveryOrder::getReceiverUserId, userId)) .orderByDesc(FlashDeliveryOrder::getCreateTime) .orderByDesc(FlashDeliveryOrder::getId); if (cursor != null) { Date time = new Date(cursor.getTimeMillis()); if (cursor.includesSameTimeAnyId(OrderSource.FLASH)) { wrapper.le(FlashDeliveryOrder::getCreateTime, time); } else if (cursor.includesSameTimeLowerId(OrderSource.FLASH)) { wrapper.and(w -> w.lt(FlashDeliveryOrder::getCreateTime, time) .or(x -> x.eq(FlashDeliveryOrder::getCreateTime, time) .lt(FlashDeliveryOrder::getId, cursor.getOrderId()))); } else { wrapper.lt(FlashDeliveryOrder::getCreateTime, time); } } return wrapper; } /** 外卖条目按门店 ID 批量查 pos_store,一次回填店铺名称/头像。 */ private Map storesOf(List displayed) { Set mdIds = new HashSet<>(); for (Row row : displayed) { if (row.takeaway() != null && row.takeaway().getMdId() != null) { mdIds.add(row.takeaway().getMdId()); } } Map stores = new HashMap<>(); if (!mdIds.isEmpty()) { for (PosStore store : posStoreMapper.selectBatchIds(mdIds)) { if (store != null && store.getId() != null) { stores.put(store.getId().longValue(), store); } } } return stores; } private UnifiedOrderListItem takeawayItem(PosOrder order, Map stores) { UnifiedOrderListItem item = new UnifiedOrderListItem(); item.setSourceType("takeaway"); item.setRole("sender"); item.setOrderId(order.getId()); item.setOrderNo(order.getDdId()); item.setUnifiedStatus(resolveTakeawayStatus(order)); item.setCreateTime(order.getCretim() == null ? null : order.getCretim().getTime()); item.setAmount(order.getAmount() == null ? null : order.getAmount().longValue()); item.setHasAfterSale(order.getAfterSaleStatus() != null && order.getAfterSaleStatus() > 0); item.setType(order.getType()); item.setState(order.getState()); item.setDeliveryStatus(order.getDeliveryStatus()); item.setPayStatus(order.getPayStatus()); item.setPayType(order.getPayType()); item.setAfterSaleStatus(order.getAfterSaleStatus()); // 店铺已删或 mdId 缺失时回退订单创建时的快照 posName/logo PosStore store = order.getMdId() == null ? null : stores.get(order.getMdId()); item.setStoreName(store != null ? store.getPosName() : order.getPosName()); item.setStoreAvatar(store != null ? store.getLogo() : order.getLogo()); item.setGoodsCount(takeawayGoodsCount(order.getFood())); item.setCollectPayment(order.getCollectPayment()); return item; } /** 商品件数:food 快照数组各行 number 求和;food 为空或非法返回 null。 */ private Integer takeawayGoodsCount(String food) { if (food == null || food.isBlank()) { return null; } try { JSONArray rows = JSONArray.parseArray(food); int count = 0; for (int i = 0; i < rows.size(); i++) { Integer num = rows.getJSONObject(i).getInteger("number"); if (num != null) { count += num; } } return count; } catch (RuntimeException e) { return null; } } private UnifiedOrderListItem flashItem(FlashDeliveryOrder order, Long userId) { UnifiedOrderListItem item = new UnifiedOrderListItem(); item.setSourceType("flash"); item.setRole(userId != null && userId.equals(order.getUserId()) ? "sender" : "receiver"); item.setOrderId(order.getId()); item.setOrderNo(order.getOrderNo()); item.setUnifiedStatus(resolveFlashStatus(order)); item.setCreateTime(order.getCreateTime() == null ? null : order.getCreateTime().getTime()); item.setAmount(order.getAmount()); item.setCurrency(order.getCurrency()); item.setHasAfterSale(false); item.setStatus(order.getStatus()); item.setOrderVersion(order.getVersion()); item.setServiceType(order.getServiceType()); item.setDeliveryType(order.getDeliveryType()); item.setVehicleType(order.getVehicleType()); item.setDeliveryMode(order.getDeliveryMode()); item.setScheduledPickupStartAt(order.getScheduledPickupStartAt()); item.setScheduledPickupEndAt(order.getScheduledPickupEndAt()); item.setPickupAddress(order.getPickupAddress()); item.setPickupDetailAddress(order.getPickupAddressDetail()); item.setDeliveryAddress(order.getDeliveryAddress()); item.setDeliveryDetailAddress(order.getDeliveryAddressDetail()); item.setPackageType(order.getPackageType()); item.setQuantity(order.getQuantity()); item.setTipAmount(order.getTipAmount()); return item; } private String resolveTakeawayStatus(PosOrder order) { for (UnifiedOrderTab tab : STATUS_RESOLUTION_ORDER) { if (tab.matches(order)) { return tab.code(); } } return null; } private String resolveFlashStatus(FlashDeliveryOrder order) { for (UnifiedOrderTab tab : FLASH_STATUS_RESOLUTION_ORDER) { if (tab.matches(order)) { return tab.code(); } } return null; } private static long safeTime(Date time) { return time == null ? 0L : time.getTime(); } /** 归并中间行:游标键 + 单侧实体。 */ private record Row(UnifiedCursor key, PosOrder takeaway, FlashDeliveryOrder flash) { } }