Jelajahi Sumber

添加菜品标签,维护换桌功能

lutong 1 bulan lalu
induk
melakukan
ffd0ee092b

+ 8 - 2
alien-dining/src/main/java/shop/alien/dining/service/impl/CartServiceImpl.java

@@ -535,9 +535,15 @@ public class CartServiceImpl implements CartService {
 
             if (existingItem != null) {
                 // 合并数量
-                existingItem.setQuantity(existingItem.getQuantity() + fromItem.getQuantity());
+                int newQuantity = (existingItem.getQuantity() != null ? existingItem.getQuantity() : 0)
+                        + (fromItem.getQuantity() != null ? fromItem.getQuantity() : 0);
+                existingItem.setQuantity(newQuantity);
                 existingItem.setSubtotalAmount(existingItem.getUnitPrice()
-                        .multiply(BigDecimal.valueOf(existingItem.getQuantity())));
+                        .multiply(BigDecimal.valueOf(newQuantity)));
+                // 合并已下单数量(换桌后目标桌需保留两边的已下单数量)
+                int toLocked = existingItem.getLockedQuantity() != null ? existingItem.getLockedQuantity() : 0;
+                int fromLocked = fromItem.getLockedQuantity() != null ? fromItem.getLockedQuantity() : 0;
+                existingItem.setLockedQuantity(toLocked + fromLocked > 0 ? toLocked + fromLocked : null);
             } else {
                 mergedItems.add(fromItem);
             }

+ 46 - 2
alien-dining/src/main/java/shop/alien/dining/service/impl/DiningServiceImpl.java

@@ -333,6 +333,31 @@ public class DiningServiceImpl implements DiningService {
         // 获取购物车
         CartDTO cart = cartService.getCart(tableId);
 
+        // 为购物车项补全菜品标签(购物车从 DB 加载时可能无 tags)
+        List<CartItemDTO> items = cart.getItems();
+        if (items != null && !items.isEmpty()) {
+            Set<Integer> cuisineIds = items.stream()
+                    .map(CartItemDTO::getCuisineId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toSet());
+            if (!cuisineIds.isEmpty()) {
+                List<StoreCuisine> cuisines = storeCuisineMapper.selectBatchIds(new ArrayList<>(cuisineIds));
+                Map<Integer, String> tagsMap = new HashMap<>();
+                if (cuisines != null) {
+                    for (StoreCuisine c : cuisines) {
+                        if (c.getTags() != null) {
+                            tagsMap.put(c.getId(), c.getTags());
+                        }
+                    }
+                }
+                for (CartItemDTO item : items) {
+                    if (item.getCuisineId() != null && item.getTags() == null) {
+                        item.setTags(tagsMap.get(item.getCuisineId()));
+                    }
+                }
+            }
+        }
+
         // 检查订单锁定
         Integer lockUserId = orderLockService.checkOrderLock(tableId);
         boolean isLocked = lockUserId != null && !lockUserId.equals(userId);
@@ -342,7 +367,7 @@ public class DiningServiceImpl implements DiningService {
         vo.setTableNumber(pageInfo.getTableNumber());
         vo.setDinerCount(pageInfo.getDinerCount() != null ? pageInfo.getDinerCount() : dinerCount);
         // 联系电话和备注由前端传入,这里不设置默认值
-        vo.setItems(cart.getItems());
+        vo.setItems(items != null ? items : cart.getItems());
         vo.setTotalAmount(cart.getTotalAmount());
         vo.setIsLocked(isLocked);
         vo.setLockUserId(lockUserId);
@@ -516,7 +541,25 @@ public class DiningServiceImpl implements DiningService {
         detailWrapper.orderByDesc(shop.alien.entity.store.StoreOrderDetail::getCreatedTime);
         List<shop.alien.entity.store.StoreOrderDetail> details = storeOrderDetailMapper.selectList(detailWrapper);
 
-        // 转换为CartItemDTO
+        // 批量查询菜品标签
+        Set<Integer> cuisineIds = details.stream()
+                .map(shop.alien.entity.store.StoreOrderDetail::getCuisineId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Integer, String> cuisineIdToTags = new HashMap<>();
+        if (!cuisineIds.isEmpty()) {
+            List<StoreCuisine> cuisines = storeCuisineMapper.selectBatchIds(new ArrayList<>(cuisineIds));
+            if (cuisines != null) {
+                for (StoreCuisine c : cuisines) {
+                    if (c.getTags() != null) {
+                        cuisineIdToTags.put(c.getId(), c.getTags());
+                    }
+                }
+            }
+        }
+
+        // 转换为CartItemDTO(含菜品标签)
+        Map<Integer, String> finalTagsMap = cuisineIdToTags;
         List<shop.alien.entity.store.dto.CartItemDTO> items = details.stream().map(detail -> {
             shop.alien.entity.store.dto.CartItemDTO item = new shop.alien.entity.store.dto.CartItemDTO();
             item.setCuisineId(detail.getCuisineId());
@@ -529,6 +572,7 @@ public class DiningServiceImpl implements DiningService {
             item.setAddUserId(detail.getAddUserId());
             item.setAddUserPhone(detail.getAddUserPhone());
             item.setRemark(detail.getRemark());
+            item.setTags(detail.getCuisineId() != null ? finalTagsMap.get(detail.getCuisineId()) : null);
             return item;
         }).collect(Collectors.toList());
 

+ 89 - 9
alien-dining/src/main/java/shop/alien/dining/service/impl/StoreOrderServiceImpl.java

@@ -751,6 +751,24 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         Map<Integer, List<StoreOrderDetail>> detailsMap = allDetails.stream()
                 .collect(Collectors.groupingBy(StoreOrderDetail::getOrderId));
         
+        // 4.1 批量查询菜品标签(用于分页列表展示)
+        Set<Integer> cuisineIds = allDetails.stream()
+                .map(StoreOrderDetail::getCuisineId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Integer, String> cuisineIdToTags = new HashMap<>();
+        if (!cuisineIds.isEmpty()) {
+            List<StoreCuisine> cuisines = storeCuisineMapper.selectBatchIds(new ArrayList<>(cuisineIds));
+            if (cuisines != null) {
+                for (StoreCuisine c : cuisines) {
+                    if (c.getTags() != null) {
+                        cuisineIdToTags.put(c.getId(), c.getTags());
+                    }
+                }
+            }
+        }
+        Map<Integer, String> finalCuisineIdToTags = cuisineIdToTags;
+        
         // 5. 批量查询门店名称
         Set<Integer> storeIds = orders.stream().map(StoreOrder::getStoreId).filter(Objects::nonNull).collect(Collectors.toSet());
         Map<Integer, String> storeNameMap = new HashMap<>();
@@ -769,7 +787,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             vo.setOrder(order);
             vo.setStoreName(storeNameMap.getOrDefault(order.getStoreId(), ""));
             
-            // 获取该订单的菜品列表
+            // 获取该订单的菜品列表(含菜品标签)
             List<StoreOrderDetail> orderDetails = detailsMap.getOrDefault(order.getId(), new ArrayList<>());
             List<OrderCuisineItemVO> cuisineItems = orderDetails.stream()
                     .map(detail -> {
@@ -779,6 +797,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
                         item.setCuisineImage(detail.getCuisineImage());
                         item.setQuantity(detail.getQuantity());
                         item.setUnitPrice(detail.getUnitPrice());
+                        item.setTags(detail.getCuisineId() != null ? finalCuisineIdToTags.get(detail.getCuisineId()) : null);
                         return item;
                     })
                     .collect(Collectors.toList());
@@ -1292,6 +1311,24 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         Map<Integer, List<StoreOrderDetail>> detailsMap = allDetails.stream()
                 .collect(Collectors.groupingBy(StoreOrderDetail::getOrderId));
 
+        // 4.1 批量查询菜品标签(用于我的订单分页展示)
+        Set<Integer> cuisineIdsMy = allDetails.stream()
+                .map(StoreOrderDetail::getCuisineId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Integer, String> cuisineIdToTagsMy = new HashMap<>();
+        if (!cuisineIdsMy.isEmpty()) {
+            List<StoreCuisine> cuisinesMy = storeCuisineMapper.selectBatchIds(new ArrayList<>(cuisineIdsMy));
+            if (cuisinesMy != null) {
+                for (StoreCuisine c : cuisinesMy) {
+                    if (c.getTags() != null) {
+                        cuisineIdToTagsMy.put(c.getId(), c.getTags());
+                    }
+                }
+            }
+        }
+        Map<Integer, String> finalCuisineIdToTagsMy = cuisineIdToTagsMy;
+
         // 5. 批量查询门店名称
         Set<Integer> storeIds = orders.stream().map(StoreOrder::getStoreId).filter(Objects::nonNull).collect(Collectors.toSet());
         Map<Integer, String> storeNameMap = new HashMap<>();
@@ -1310,7 +1347,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             vo.setOrder(order);
             vo.setStoreName(storeNameMap.getOrDefault(order.getStoreId(), ""));
 
-            // 获取该订单的菜品列表
+            // 获取该订单的菜品列表(含菜品标签)
             List<StoreOrderDetail> orderDetails = detailsMap.getOrDefault(order.getId(), new ArrayList<>());
             List<OrderCuisineItemVO> cuisineItems = orderDetails.stream()
                     .map(detail -> {
@@ -1320,6 +1357,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
                         item.setCuisineImage(detail.getCuisineImage());
                         item.setQuantity(detail.getQuantity());
                         item.setUnitPrice(detail.getUnitPrice());
+                        item.setTags(detail.getCuisineId() != null ? finalCuisineIdToTagsMy.get(detail.getCuisineId()) : null);
                         return item;
                     })
                     .collect(Collectors.toList());
@@ -1478,6 +1516,23 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         Map<String, List<StoreOrderChangeLog>> batchMap = logs.stream()
                 .collect(Collectors.groupingBy(StoreOrderChangeLog::getBatchNo));
         
+        // 2.1 批量查询菜品标签(用于订单详情展示)
+        Set<Integer> cuisineIds = logs.stream()
+                .map(StoreOrderChangeLog::getCuisineId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Integer, String> cuisineIdToTags = new HashMap<>();
+        if (!cuisineIds.isEmpty()) {
+            List<StoreCuisine> cuisines = storeCuisineMapper.selectBatchIds(new ArrayList<>(cuisineIds));
+            if (cuisines != null) {
+                for (StoreCuisine c : cuisines) {
+                    if (c.getTags() != null) {
+                        cuisineIdToTags.put(c.getId(), c.getTags());
+                    }
+                }
+            }
+        }
+        
         // 3. 转换为批次VO列表
         List<OrderChangeLogBatchVO> batchList = new ArrayList<>();
         for (Map.Entry<String, List<StoreOrderChangeLog>> entry : batchMap.entrySet()) {
@@ -1512,7 +1567,8 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
             batchVO.setTotalAmountChange(totalAmountChange);
             batchVO.setItemCount(batchLogs.size());
             
-            // 转换为商品项VO列表
+            // 转换为商品项VO列表(含菜品标签)
+            Map<Integer, String> finalCuisineIdToTags = cuisineIdToTags;
             List<OrderChangeLogItemVO> items = batchLogs.stream().map(log -> {
                 OrderChangeLogItemVO itemVO = new OrderChangeLogItemVO();
                 itemVO.setCuisineId(log.getCuisineId());
@@ -1525,6 +1581,7 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
                 itemVO.setQuantityAfter(log.getQuantityAfter());
                 itemVO.setAmountChange(log.getAmountChange());
                 itemVO.setRemark(log.getRemark());
+                itemVO.setTags(log.getCuisineId() != null ? finalCuisineIdToTags.get(log.getCuisineId()) : null);
                 return itemVO;
             }).collect(Collectors.toList());
             
@@ -1835,28 +1892,51 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
     public shop.alien.entity.store.dto.CartDTO changeTable(Integer fromTableId, Integer toTableId, String changeReason, Integer userId) {
         log.info("换桌, fromTableId={}, toTableId={}, changeReason={}, userId={}", fromTableId, toTableId, changeReason, userId);
 
+        // 0. 校验:目标桌只能是空桌(空闲且无当前订单)
+        if (fromTableId.equals(toTableId)) {
+            throw new RuntimeException("原桌号与目标桌号不能相同");
+        }
+        StoreTable fromTable = storeTableMapper.selectById(fromTableId);
+        if (fromTable == null) {
+            throw new RuntimeException("原桌号不存在");
+        }
+        StoreTable toTable = storeTableMapper.selectById(toTableId);
+        if (toTable == null) {
+            throw new RuntimeException("目标桌号不存在");
+        }
+        if (!fromTable.getStoreId().equals(toTable.getStoreId())) {
+            throw new RuntimeException("原桌号与目标桌号须在同一门店");
+        }
+        // 空桌:状态为空闲(0)且无当前订单
+        boolean emptyStatus = (toTable.getStatus() == null || toTable.getStatus() == 0);
+        boolean noOrder = (toTable.getCurrentOrderId() == null);
+        if (!emptyStatus || !noOrder) {
+            throw new RuntimeException("只能换到空桌,请选择空闲且无订单的桌号");
+        }
+
         // 1. 迁移购物车
         shop.alien.entity.store.dto.CartDTO cart = cartService.migrateCart(fromTableId, toTableId);
 
         // 2. 迁移所有关联数据(订单、订单变更记录、优惠券使用记录等)
         migrateTableData(fromTableId, toTableId, userId);
 
-        // 3. 查询桌号信息
-        StoreTable fromTable = storeTableMapper.selectById(fromTableId);
-        StoreTable toTable = storeTableMapper.selectById(toTableId);
-
-        // 4. 记录换桌日志
+        // 3. 记录换桌日志(fromTable、toTable 已在步骤0中查询)
+        Date now = new Date();
         StoreTableLog tableLog = new StoreTableLog();
         tableLog.setStoreId(cart.getStoreId());
+        tableLog.setOrderId(fromTable.getCurrentOrderId()); // 有订单则记录,仅购物车换桌时为 null
         tableLog.setFromTableId(fromTableId);
         tableLog.setFromTableNumber(fromTable != null ? fromTable.getTableNumber() : null);
         tableLog.setToTableId(toTableId);
         tableLog.setToTableNumber(toTable != null ? toTable.getTableNumber() : null);
         tableLog.setChangeReason(changeReason);
         tableLog.setCreatedUserId(userId);
+        tableLog.setCreatedTime(now);
+        tableLog.setUpdatedTime(now);
+        tableLog.setUpdatedUserId(userId);
         storeTableLogMapper.insert(tableLog);
 
-        // 5. 推送购物车更新消息到新桌号
+        // 4. 推送购物车更新消息到新桌号
         sseService.pushCartUpdate(toTableId, cart);
 
         log.info("换桌完成, fromTableId={}, toTableId={}", fromTableId, toTableId);

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/OrderChangeLogItemVO.java

@@ -45,4 +45,7 @@ public class OrderChangeLogItemVO {
 
     @ApiModelProperty(value = "备注")
     private String remark;
+
+    @ApiModelProperty(value = "菜品标签(JSON数组,如:[\"招牌菜\",\"推荐\"])")
+    private String tags;
 }

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/OrderCuisineItemVO.java

@@ -30,4 +30,7 @@ public class OrderCuisineItemVO {
 
     @ApiModelProperty(value = "单价")
     private BigDecimal unitPrice;
+
+    @ApiModelProperty(value = "菜品标签(JSON数组,如:[\"招牌菜\",\"推荐\"])")
+    private String tags;
 }

+ 18 - 1
alien-store/src/main/java/shop/alien/store/controller/DiningServiceController.java

@@ -11,6 +11,7 @@ import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreTable;
 import shop.alien.entity.store.dto.CartDTO;
+import shop.alien.entity.store.dto.ChangeTableDTO;
 import shop.alien.entity.store.vo.*;
 import shop.alien.store.feign.DiningServiceFeign;
 
@@ -170,8 +171,24 @@ public class DiningServiceController {
         }
     }
 
-    @ApiOperation(value = "获取订单详情", notes = "根据订单ID获取订单详细信息")
+    @ApiOperation(value = "换桌", notes = "按桌号换桌,迁移购物车、未完成订单及关联数据。支持点餐未下单场景,只需传原桌号与目标桌号,无需订单号。")
     @ApiOperationSupport(order = 7)
+    @PostMapping("/order/change-table")
+    public R<CartDTO> changeTable(
+            HttpServletRequest request,
+            @ApiParam(value = "换桌参数(原桌号ID、目标桌号ID、换桌原因)", required = true) @RequestBody ChangeTableDTO dto) {
+        try {
+            String authorization = getAuthorization(request);
+            log.info("换桌: fromTableId={}, toTableId={}, changeReason={}", dto.getFromTableId(), dto.getToTableId(), dto.getChangeReason());
+            return diningServiceFeign.changeTable(authorization, dto);
+        } catch (Exception e) {
+            log.error("换桌失败: {}", e.getMessage(), e);
+            return R.fail("换桌失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation(value = "获取订单详情", notes = "根据订单ID获取订单详细信息")
+    @ApiOperationSupport(order = 8)
     @GetMapping("/order/{orderId}")
     public R<OrderDetailWithChangeLogVO> getOrderDetail(
             HttpServletRequest request,

+ 13 - 0
alien-store/src/main/java/shop/alien/store/feign/DiningServiceFeign.java

@@ -9,6 +9,7 @@ import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreTable;
 import shop.alien.entity.store.dto.CartDTO;
+import shop.alien.entity.store.dto.ChangeTableDTO;
 import shop.alien.entity.store.vo.*;
 
 import java.util.List;
@@ -126,6 +127,18 @@ public interface DiningServiceFeign {
             @PathVariable("tableId") Integer tableId);
 
     /**
+     * 换桌(迁移购物车、未完成订单及关联数据,支持点餐未下单场景,无需订单号)
+     *
+     * @param authorization 请求头 Authorization
+     * @param dto            原桌号ID、目标桌号ID、换桌原因
+     * @return R.data 为迁移后的购物车 CartDTO
+     */
+    @PostMapping("/store/order/change-table")
+    R<CartDTO> changeTable(
+            @RequestHeader(value = "Authorization", required = false) String authorization,
+            @RequestBody ChangeTableDTO dto);
+
+    /**
      * 获取订单详情
      *
      * @param authorization 请求头 Authorization