2 Комити 5e66fe0f54 ... 0c0503d576

Аутор SHA1 Порука Датум
  刘云鑫 0c0503d576 Merge remote-tracking branch 'origin/sit-new-checkstand' into sit-new-checkstand пре 2 недеља
  刘云鑫 baa79de9dd feat(store): 按分类查询可选菜品并回填优惠规则 пре 2 недеља

+ 27 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/CuisineSelectCategoryVo.java

@@ -0,0 +1,27 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 按菜品分类分组的列表(选择菜品弹层)
+ */
+@Data
+@ApiModel(value = "CuisineSelectCategoryVo", description = "按分类选择菜品-分类分组")
+public class CuisineSelectCategoryVo {
+
+    @ApiModelProperty("分类 id,未分类时为 0")
+    private Integer categoryId;
+
+    @ApiModelProperty("分类名称")
+    private String categoryName;
+
+    @ApiModelProperty("排序(门店内分类 sort,未分类置为较大值)")
+    private Integer sort;
+
+    @ApiModelProperty("该分类下的菜品列表")
+    private List<CuisineSelectDishVo> dishes;
+}

+ 48 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/CuisineSelectDishVo.java

@@ -0,0 +1,48 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 选择菜品弹层中的单行菜品(与 {@link CuisineSelectCategoryVo} 配套)
+ */
+@Data
+@ApiModel(value = "CuisineSelectDishVo", description = "按分类选择菜品-菜品项")
+public class CuisineSelectDishVo {
+
+    @ApiModelProperty("菜品 id(store_cuisine.id)")
+    private Integer id;
+
+    @ApiModelProperty("门店 id")
+    private Integer storeId;
+
+    @ApiModelProperty("菜品名称")
+    private String name;
+
+    @ApiModelProperty("总价")
+    private BigDecimal totalPrice;
+
+    @ApiModelProperty("美食类型: 1-单品,2-套餐")
+    private Integer cuisineType;
+
+    @ApiModelProperty("图片列表 JSON/URL")
+    private String images;
+
+    @ApiModelProperty("当前时刻是否存在生效中的菜品优惠(store_product_discount_rule)")
+    private Boolean hasActiveDiscount;
+
+    @ApiModelProperty("优惠类型: FREE / DISCOUNT")
+    private String discountType;
+
+    @ApiModelProperty("折扣比例(0-100)")
+    private BigDecimal discountRate;
+
+    @ApiModelProperty("当前生效规则名称")
+    private String discountRuleName;
+
+    @ApiModelProperty("按当前时间命中规则估算的优惠后价格")
+    private BigDecimal discountedPrice;
+}

+ 0 - 16
alien-entity/src/main/java/shop/alien/entity/store/vo/PriceListVo.java

@@ -112,20 +112,4 @@ public class PriceListVo {
     @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date updatedTime;
-
-    /** 以下字段由价目分页等接口按 {@code store_product_discount_rule} 与当前时间计算回填,非库表列 */
-    @ApiModelProperty(value = "当前时刻是否存在生效中的菜品优惠(store_product_discount_rule)")
-    private Boolean hasActiveDiscount;
-
-    @ApiModelProperty(value = "优惠类型: FREE / DISCOUNT")
-    private String discountType;
-
-    @ApiModelProperty(value = "折扣比例(0-100),DISCOUNT 时有值")
-    private BigDecimal discountRate;
-
-    @ApiModelProperty(value = "当前生效规则名称(最优价对应规则)")
-    private String discountRuleName;
-
-    @ApiModelProperty(value = "按当前时间命中规则估算的优惠后价格")
-    private BigDecimal discountedPrice;
 }

+ 115 - 4
alien-store/src/main/java/shop/alien/store/controller/StoreCuisineController.java

@@ -1,5 +1,6 @@
 package shop.alien.store.controller;
 
+import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -19,9 +20,13 @@ import shop.alien.entity.store.StorePrice;
 import shop.alien.entity.store.dto.CuisineComboDto;
 import shop.alien.entity.store.dto.CuisineTypeResponseDto;
 import shop.alien.entity.store.dto.TablewareFeeDto;
+import shop.alien.entity.store.StoreCuisineCategory;
+import shop.alien.entity.store.vo.CuisineSelectCategoryVo;
+import shop.alien.entity.store.vo.CuisineSelectDishVo;
 import shop.alien.entity.store.vo.PriceListVo;
 import shop.alien.mapper.StoreCuisineMapper;
 import shop.alien.store.annotation.TrackEvent;
+import shop.alien.store.service.StoreCuisineCategoryService;
 import shop.alien.store.service.StoreCuisineComboService;
 import shop.alien.store.service.StoreCuisineService;
 import shop.alien.store.service.StoreInfoService;
@@ -34,6 +39,9 @@ import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * 美食价目表
@@ -64,6 +72,8 @@ public class StoreCuisineController {
 
     private final StoreCuisineComboService storeCuisineComboService;
 
+    private final StoreCuisineCategoryService storeCuisineCategoryService;
+
     private final StoreProductDiscountService storeProductDiscountService;
 
     @ApiOperation("新增美食套餐或单品")
@@ -534,8 +544,6 @@ public class StoreCuisineController {
                     priceListVo.add(vo);
                 }
             }
-            // 回填 store_product_discount_rule:当前生效优惠与估算折后价(美食价目 productId=菜品 id)
-            storeProductDiscountService.fillActiveDiscountForPriceList(priceListVo);
             return R.data(priceListVo);
         }else{
             Page<StorePrice> page = new Page<>(pageNum, pageSize);
@@ -578,8 +586,6 @@ public class StoreCuisineController {
                     priceListVo.add(vo);
                 }
             }
-            // 同上,通用价目 productId=store_price.id
-            storeProductDiscountService.fillActiveDiscountForPriceList(priceListVo);
             return R.data(priceListVo);
         }
     }
@@ -654,6 +660,111 @@ public class StoreCuisineController {
         
         return R.data(storeInfo.getTablewareFee());
     }
+
+    @ApiOperation("选择菜品:按门店菜品分类分组,支持名称模糊;含当前生效优惠(store_product_discount_rule)")
+    @ApiOperationSupport(order = 11)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "name", value = "名称模糊搜索(可选)", dataType = "String", paramType = "query")
+    })
+    @GetMapping("/selectDishesByCategory")
+    public R<List<CuisineSelectCategoryVo>> selectDishesByCategory(
+            @RequestParam Integer storeId,
+            @RequestParam(required = false) String name) {
+        log.info("StoreCuisineController.selectDishesByCategory?storeId={},name={}", storeId, name);
+        if (storeId == null) {
+            return R.fail("门店ID不能为空");
+        }
+        LambdaQueryWrapper<StoreCuisine> qw = new LambdaQueryWrapper<>();
+        qw.eq(StoreCuisine::getStoreId, storeId)
+                .eq(StoreCuisine::getShelfStatus, 1)
+                .eq(StoreCuisine::getStatus, 1);
+        if (org.apache.commons.lang3.StringUtils.isNotBlank(name)) {
+            qw.like(StoreCuisine::getName, name.trim());
+        }
+        qw.orderByDesc(StoreCuisine::getCreatedTime);
+        List<StoreCuisine> cuisines = storeCuisineService.list(qw);
+        if (cuisines == null || cuisines.isEmpty()) {
+            return R.data(new ArrayList<>());
+        }
+        List<StoreCuisineCategory> categories = storeCuisineCategoryService.getCategoryList(storeId);
+        Set<Integer> activeCategoryIds = categories.stream()
+                .map(StoreCuisineCategory::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        List<CuisineSelectCategoryVo> groups = new ArrayList<>();
+        for (StoreCuisineCategory cat : categories) {
+            List<CuisineSelectDishVo> dishes = new ArrayList<>();
+            for (StoreCuisine c : cuisines) {
+                if (parseCategoryIdList(c.getCategoryIds()).contains(cat.getId())) {
+                    dishes.add(toSelectDishVo(c));
+                }
+            }
+            if (!dishes.isEmpty()) {
+                CuisineSelectCategoryVo g = new CuisineSelectCategoryVo();
+                g.setCategoryId(cat.getId());
+                g.setCategoryName(cat.getCategoryName());
+                g.setSort(cat.getSort());
+                g.setDishes(dishes);
+                groups.add(g);
+            }
+        }
+
+        List<CuisineSelectDishVo> uncategorized = new ArrayList<>();
+        for (StoreCuisine c : cuisines) {
+            List<Integer> cidList = parseCategoryIdList(c.getCategoryIds());
+            boolean placed = false;
+            for (Integer cid : cidList) {
+                if (activeCategoryIds.contains(cid)) {
+                    placed = true;
+                    break;
+                }
+            }
+            if (!placed) {
+                uncategorized.add(toSelectDishVo(c));
+            }
+        }
+        if (!uncategorized.isEmpty()) {
+            CuisineSelectCategoryVo g = new CuisineSelectCategoryVo();
+            g.setCategoryId(0);
+            g.setCategoryName("未分类");
+            g.setSort(Integer.MAX_VALUE);
+            g.setDishes(uncategorized);
+            groups.add(g);
+        }
+
+        List<CuisineSelectDishVo> flat = new ArrayList<>();
+        for (CuisineSelectCategoryVo g : groups) {
+            flat.addAll(g.getDishes());
+        }
+        storeProductDiscountService.fillActiveDiscountForSelectDishes(flat);
+        return R.data(groups);
+    }
+
+    /** 解析 store_cuisine.category_ids(JSON 数组) */
+    private static List<Integer> parseCategoryIdList(String categoryIdsJson) {
+        if (org.apache.commons.lang3.StringUtils.isBlank(categoryIdsJson)) {
+            return new ArrayList<>();
+        }
+        try {
+            List<Integer> ids = JSON.parseArray(categoryIdsJson, Integer.class);
+            return ids == null ? new ArrayList<>() : ids;
+        } catch (Exception e) {
+            return new ArrayList<>();
+        }
+    }
+
+    private static CuisineSelectDishVo toSelectDishVo(StoreCuisine c) {
+        CuisineSelectDishVo vo = new CuisineSelectDishVo();
+        vo.setId(c.getId());
+        vo.setStoreId(c.getStoreId());
+        vo.setName(c.getName());
+        vo.setTotalPrice(c.getTotalPrice());
+        vo.setCuisineType(c.getCuisineType());
+        vo.setImages(c.getImages());
+        return vo;
+    }
 }
 
 

+ 4 - 5
alien-store/src/main/java/shop/alien/store/service/StoreProductDiscountService.java

@@ -5,7 +5,7 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.store.vo.StoreProductDiscountRuleDetailVo;
 import shop.alien.entity.store.vo.StoreProductDiscountRuleListVo;
 import shop.alien.entity.store.dto.StoreProductDiscountRuleSaveDto;
-import shop.alien.entity.store.vo.PriceListVo;
+import shop.alien.entity.store.vo.CuisineSelectDishVo;
 
 import java.util.List;
 
@@ -31,11 +31,10 @@ public interface StoreProductDiscountService {
     int autoCloseExpiredCustomRules();
 
     /**
-     * 按门店批量查询 {@code store_product_discount_rule}(status=1),结合日期、星期、时段判断当前是否命中;
-     * 同一菜品多规则时取优惠后价最低的一条,写入 {@link PriceListVo} 的 hasActiveDiscount / discount* / discountedPrice。
+     * 回填 {@link CuisineSelectDishVo} 的优惠展示字段(规则同门店菜品优惠配置)。
      *
-     * @param items 已含 storeId、id(商品主键)、totalPrice 的价目
+     * @param items 已含 storeId、id、totalPrice 的行
      */
-    void fillActiveDiscountForPriceList(List<PriceListVo> items);
+    void fillActiveDiscountForSelectDishes(List<CuisineSelectDishVo> items);
 }
 

+ 12 - 12
alien-store/src/main/java/shop/alien/store/service/impl/StoreProductDiscountServiceImpl.java

@@ -12,7 +12,7 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.StoreProductDiscountRuleSaveDto;
 import shop.alien.entity.store.dto.StoreProductDiscountRuleSlotDto;
-import shop.alien.entity.store.vo.PriceListVo;
+import shop.alien.entity.store.vo.CuisineSelectDishVo;
 import shop.alien.entity.store.vo.StoreProductDiscountRuleDetailVo;
 import shop.alien.entity.store.vo.StoreProductDiscountRuleListVo;
 import shop.alien.mapper.StoreProductDiscountRuleMapper;
@@ -261,14 +261,14 @@ public class StoreProductDiscountServiceImpl implements StoreProductDiscountServ
 	}
 
 	/**
-	 * 按门店分组批量查规则,避免分页内逐条查库;先清空各行的优惠字段再回填命中项。
+	 * 按门店分组批量查规则;先清空各行的优惠字段再回填命中项。
 	 */
 	@Override
-	public void fillActiveDiscountForPriceList(List<PriceListVo> items) {
+	public void fillActiveDiscountForSelectDishes(List<CuisineSelectDishVo> items) {
 		if (items == null || items.isEmpty()) {
 			return;
 		}
-		for (PriceListVo vo : items) {
+		for (CuisineSelectDishVo vo : items) {
 			vo.setHasActiveDiscount(Boolean.FALSE);
 			vo.setDiscountType(null);
 			vo.setDiscountRate(null);
@@ -276,17 +276,17 @@ public class StoreProductDiscountServiceImpl implements StoreProductDiscountServ
 			vo.setDiscountedPrice(null);
 		}
 		LocalDateTime now = LocalDateTime.now();
-		Map<Integer, List<PriceListVo>> byStore = items.stream()
+		Map<Integer, List<CuisineSelectDishVo>> byStore = items.stream()
 				.filter(vo -> vo.getStoreId() != null && vo.getId() != null)
-				.collect(Collectors.groupingBy(PriceListVo::getStoreId));
-		for (Map.Entry<Integer, List<PriceListVo>> e : byStore.entrySet()) {
+				.collect(Collectors.groupingBy(CuisineSelectDishVo::getStoreId));
+		for (Map.Entry<Integer, List<CuisineSelectDishVo>> e : byStore.entrySet()) {
 			Integer storeId = e.getKey();
-			List<PriceListVo> row = e.getValue();
-			List<Integer> productIds = row.stream().map(PriceListVo::getId).distinct().collect(Collectors.toList());
+			List<CuisineSelectDishVo> row = e.getValue();
+			List<Integer> productIds = row.stream().map(CuisineSelectDishVo::getId).distinct().collect(Collectors.toList());
 			Map<Integer, java.math.BigDecimal> basePriceByProduct = row.stream()
-					.collect(Collectors.toMap(PriceListVo::getId, PriceListVo::getTotalPrice, (a, b) -> a));
+					.collect(Collectors.toMap(CuisineSelectDishVo::getId, CuisineSelectDishVo::getTotalPrice, (a, b) -> a));
 			Map<Integer, RulePick> picks = resolveBestActiveRules(storeId, productIds, basePriceByProduct, now);
-			for (PriceListVo vo : row) {
+			for (CuisineSelectDishVo vo : row) {
 				RulePick pick = picks.get(vo.getId());
 				if (pick == null || pick.head == null) {
 					continue;
@@ -300,7 +300,7 @@ public class StoreProductDiscountServiceImpl implements StoreProductDiscountServ
 		}
 	}
 
-	/* ---------------- 价目列表优惠回填(与 fillActiveDiscountForPriceList 配套) ---------------- */
+	/* ---------------- 菜品选择弹层优惠回填(与 fillActiveDiscountForSelectDishes 配套) ---------------- */
 
 	/** 命中规则的代表行(同组多行仅时段不同,取首行即可读类型/名称/折扣率) */
 	private static final class RulePick {