diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/ordering/OrderingFoodSpuItemDTO.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/ordering/OrderingFoodSpuItemDTO.java index 81cde1e2c..94e614053 100644 --- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/ordering/OrderingFoodSpuItemDTO.java +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/ordering/OrderingFoodSpuItemDTO.java @@ -16,4 +16,7 @@ public class OrderingFoodSpuItemDTO { @NotBlank(message = "菜品名称不能为空") @Schema(description = "菜品名称") private String name; + + @Schema(description = "餐段序号(与 jobTime 解析的 segmentIndex 一致;不传则兼容旧版全餐段共用") + private Integer segmentIndex; } diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenOnlineSpuEntity.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenOnlineSpuEntity.java index becfb1dff..a505347a8 100644 --- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenOnlineSpuEntity.java +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenOnlineSpuEntity.java @@ -93,6 +93,7 @@ public class CanteenOnlineSpuEntity extends Model { * 择一/组合分组 id,与同组 SPU 共享 {@link CanteenOnlineSpuChoiceGroupEntity} 规则;null 表示不按组校验 */ @Schema(description = "择一/组合分组 id,同组商品一起参与 minKinds/maxKinds 等校验") + @TableField(updateStrategy = FieldStrategy.IGNORED) private Long choiceGroupId; /** diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/APPOnlineSpuVO.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/APPOnlineSpuVO.java index 0ffeaede7..086e1f395 100644 --- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/APPOnlineSpuVO.java +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/APPOnlineSpuVO.java @@ -54,6 +54,12 @@ public class APPOnlineSpuVO { @Schema(description="商品分类编号") private Long categoryId; + /** + * 分类餐段类型(来自在线分类 categoryType,逗号分隔:1早餐 2午餐 3晚餐) + */ + @Schema(description = "分类餐段类型") + private String categoryType; + /** * 商品封面图 */ diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenOnlineSpuController.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenOnlineSpuController.java index 978b2b813..bc05c92b0 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenOnlineSpuController.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenOnlineSpuController.java @@ -246,9 +246,10 @@ public class CanteenOnlineSpuController { @RequestParam(value = "maxPrice", required = false) @Parameter(description = "最高价(分)") Integer maxPrice, @RequestParam(value = "status", required = false) @Parameter(description = "0 上架 1 下架") Integer status, @RequestParam(value = "stockFilter", required = false) @Parameter(description = "in_stock | sold_out") String stockFilter, - @RequestParam(value = "priceOrder", required = false) @Parameter(description = "asc | desc") String priceOrder) { + @RequestParam(value = "priceOrder", required = false) @Parameter(description = "asc | desc") String priceOrder, + @RequestParam(value = "mealSegmentType", required = false) @Parameter(description = "餐段类型:1早餐 2午餐 3晚餐 4宵夜") Integer mealSegmentType) { return canteenOnlineSpuService.getCanteenOnlineSpuList(categoryId, merchantId, time, - minPrice, maxPrice, status, stockFilter, priceOrder); + minPrice, maxPrice, status, stockFilter, priceOrder, mealSegmentType); } diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenOnlineSpuService.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenOnlineSpuService.java index c68a86e4b..ef318afe1 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenOnlineSpuService.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenOnlineSpuService.java @@ -33,10 +33,11 @@ public interface CanteenOnlineSpuService extends IService> getCanteenOnlineSpuList(String categoryId, String merchantId, String time, Integer minPrice, Integer maxPrice, Integer status, - String stockFilter, String priceOrder); + String stockFilter, String priceOrder, Integer mealSegmentType); /** * 为 App 列表/详情填充 choiceGroup 规则对象(与 entities 按序一一对应) diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenOnlineSpuServiceImpl.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenOnlineSpuServiceImpl.java index fd56fb7ea..1f82c7fb0 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenOnlineSpuServiceImpl.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenOnlineSpuServiceImpl.java @@ -19,6 +19,7 @@ import com.bsd.pjcloud.canteen.mapper.CanteenOnlineCategoryMapper; import com.bsd.pjcloud.canteen.mapper.CanteenOnlineSpuMapper; import com.bsd.pjcloud.canteen.service.CanteenOnlineSpuChoiceGroupService; import com.bsd.pjcloud.canteen.service.CanteenOnlineSpuService; +import com.bsd.pjcloud.canteen.util.MealSegmentCategoryUtils; import com.bsd.pjcloud.canteen.vo.app.CanteenOnlineSpuAppVO; import com.bsd.pjcloud.canteen.vo.app.OnlineSpuChoiceGroupRuleVO; import com.bsd.pjcloud.canteen.vo.APPOnlineSpuVO; @@ -65,7 +66,8 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpl> getCanteenOnlineSpuList(String categoryId, String merchantId, String time, Integer minPrice, Integer maxPrice, Integer status, - String stockFilter, String priceOrder) { + String stockFilter, String priceOrder, + Integer mealSegmentType) { if (minPrice != null && maxPrice != null && minPrice > maxPrice) { return R.failed("价格区间无效:minPrice 不能大于 maxPrice"); } @@ -93,6 +95,16 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpl categoryWrapper = new LambdaQueryWrapper<>(); + categoryWrapper.eq(CanteenOnlineCategoryEntity::getMerchantId, merchantId); + List categories = canteenOnlineCategoryMapper.selectList(categoryWrapper); + List categoryIds = MealSegmentCategoryUtils.filterCategoryIdsByMealSegment(categories, mealSegmentType); + if (CollUtil.isEmpty(categoryIds)) { + return R.ok(Collections.emptyList()); + } + canteenOnlineSpuEntityLambdaQueryWrapper.in(CanteenOnlineSpuEntity::getCategoryId, categoryIds); + } List canteenSpuEntities = baseMapper.selectList(canteenOnlineSpuEntityLambdaQueryWrapper); //根据商户查询当前日期的每日菜谱,查询每日菜谱上架的菜的id,然后在从在线订餐的列表筛选出今天有上新的数据 @@ -112,6 +124,7 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpl bean = BeanUtils.toBean(finallyCanteenSpuEntities, APPOnlineSpuVO.class); + fillCategoryType(bean, finallyCanteenSpuEntities); for (int i = 0; i < bean.size(); i++) { Integer st = finallyCanteenSpuEntities.get(i).getStock(); bean.get(i).setSoldOut(st == null || st <= 0); @@ -149,6 +162,30 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpl vos, List entities) { + if (CollUtil.isEmpty(vos) || CollUtil.isEmpty(entities)) { + return; + } + Set categoryIds = entities.stream() + .map(CanteenOnlineSpuEntity::getCategoryId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (CollUtil.isEmpty(categoryIds)) { + return; + } + List categories = canteenOnlineCategoryMapper.selectBatchIds(categoryIds); + Map typeMap = categories == null ? Collections.emptyMap() : categories.stream() + .filter(Objects::nonNull) + .collect(Collectors.toMap(CanteenOnlineCategoryEntity::getId, + CanteenOnlineCategoryEntity::getCategoryType, (a, b) -> a)); + for (int i = 0; i < vos.size() && i < entities.size(); i++) { + Long cid = entities.get(i).getCategoryId(); + if (cid != null) { + vos.get(i).setCategoryType(typeMap.get(cid)); + } + } + } + private Map loadChoiceGroupRuleMap(Set groupIds) { if (CollUtil.isEmpty(groupIds)) { return Collections.emptyMap(); diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenOrderingFoodServiceImpl.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenOrderingFoodServiceImpl.java index 4db955cc9..fa125323d 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenOrderingFoodServiceImpl.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenOrderingFoodServiceImpl.java @@ -16,10 +16,15 @@ import com.bsd.pjcloud.canteen.dto.ordering.OrderingFoodSelectionDTO; import com.bsd.pjcloud.canteen.dto.ordering.OrderingFoodSelectionItemDTO; import com.bsd.pjcloud.canteen.dto.ordering.OrderingFoodSpuItemDTO; import com.bsd.pjcloud.canteen.dto.ordering.OrderingFoodSubmitDTO; +import com.bsd.pjcloud.canteen.entity.CanteenOnlineCategoryEntity; +import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuEntity; import com.bsd.pjcloud.canteen.entity.CanteenOrderingFoodEntity; +import com.bsd.pjcloud.canteen.mapper.CanteenOnlineCategoryMapper; +import com.bsd.pjcloud.canteen.mapper.CanteenOnlineSpuMapper; import com.bsd.pjcloud.canteen.mapper.CanteenOrderingFoodMapper; import com.bsd.pjcloud.canteen.service.CanteenOrderingFoodService; import com.bsd.pjcloud.canteen.util.MchJobTimeMealSegmentUtils; +import com.bsd.pjcloud.canteen.util.MealSegmentCategoryUtils; import com.bsd.pjcloud.canteen.vo.app.OrderingFoodAppVO; import com.bsd.pjcloud.canteen.vo.app.OrderingFoodMealSegmentVO; import com.bsd.pjcloud.canteen.vo.app.OrderingFoodStatsDayDetailVO; @@ -48,6 +53,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -73,6 +79,8 @@ public class CanteenOrderingFoodServiceImpl extends ServiceImpl> listMealSegments(String merchantId) { @@ -120,10 +128,6 @@ public class CanteenOrderingFoodServiceImpl extends ServiceImpl items = buildR.getData(); + String spuErr = validateSpuItems(dto.getSpuItems(), dto.getMerchantId(), jobTime, items); + if (spuErr != null) { + return R.failed(spuErr); + } String conflict = findSelectionConflict(login.getId(), dto.getMerchantId(), items); if (conflict != null) { return R.failed(conflict); @@ -617,12 +625,12 @@ public class CanteenOrderingFoodServiceImpl extends ServiceImpl dishes, List spuItems, - int people) { - if (CollUtil.isEmpty(spuItems)) { + int people, Integer segmentIndex) { + List applicable = filterSpuItemsForSegment(spuItems, segmentIndex); + if (CollUtil.isEmpty(applicable)) { return; } - for (OrderingFoodSpuItemDTO spu : spuItems) { + for (OrderingFoodSpuItemDTO spu : applicable) { if (spu == null || spu.getId() == null) { continue; } @@ -740,7 +749,7 @@ public class CanteenOrderingFoodServiceImpl extends ServiceImpl items) { + private String validateSpuItems(List items, String merchantId, String jobTime, + List selections) { for (OrderingFoodSpuItemDTO item : items) { if (item == null || item.getId() == null || StrUtil.isBlank(item.getName())) { return "报餐菜品信息不完整"; } } + boolean hasSegmentTag = items.stream().anyMatch(i -> i != null && i.getSegmentIndex() != null); + Set requiredSegments = selections.stream() + .map(OrderingFoodSelectionItemDTO::getSegmentIndex) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (hasSegmentTag) { + for (Integer segIdx : requiredSegments) { + boolean hasDish = items.stream() + .anyMatch(i -> i != null && Objects.equals(i.getSegmentIndex(), segIdx)); + if (!hasDish) { + OrderingFoodMealSegmentVO seg = MchJobTimeMealSegmentUtils.findSegment(jobTime, segIdx); + String segName = seg != null ? seg.getMealSegmentName() : ("餐段" + (segIdx + 1)); + return "请为「" + segName + "」选择菜品"; + } + } + } + Map categoryTypeMap = loadOnlineSpuCategoryTypeMap(items, merchantId); + for (OrderingFoodSpuItemDTO item : items) { + if (item == null || item.getId() == null) { + continue; + } + String categoryType = categoryTypeMap.get(item.getId()); + if (item.getSegmentIndex() != null) { + OrderingFoodMealSegmentVO seg = MchJobTimeMealSegmentUtils.findSegment(jobTime, item.getSegmentIndex()); + if (seg == null) { + return "菜品餐段无效"; + } + if (!MealSegmentCategoryUtils.matchesMealSegment(categoryType, seg.getMealSegmentType())) { + return "「" + item.getName() + "」不适用于「" + seg.getMealSegmentName() + "」"; + } + continue; + } + if (!hasSegmentTag && CollUtil.isNotEmpty(requiredSegments)) { + for (Integer segIdx : requiredSegments) { + OrderingFoodMealSegmentVO seg = MchJobTimeMealSegmentUtils.findSegment(jobTime, segIdx); + if (seg != null && !MealSegmentCategoryUtils.matchesMealSegment(categoryType, seg.getMealSegmentType())) { + return "「" + item.getName() + "」不适用于「" + seg.getMealSegmentName() + "」"; + } + } + } + } return null; } + private Map loadOnlineSpuCategoryTypeMap(List items, String merchantId) { + List spuIds = items.stream() + .filter(Objects::nonNull) + .map(OrderingFoodSpuItemDTO::getId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + if (CollUtil.isEmpty(spuIds)) { + return Collections.emptyMap(); + } + List spus = canteenOnlineSpuMapper.selectBatchIds(spuIds); + if (CollUtil.isEmpty(spus)) { + return Collections.emptyMap(); + } + Set categoryIds = spus.stream() + .filter(s -> s.getMerchantId() != null && String.valueOf(s.getMerchantId()).equals(String.valueOf(merchantId))) + .map(CanteenOnlineSpuEntity::getCategoryId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + Map categoryTypeById = Collections.emptyMap(); + if (CollUtil.isNotEmpty(categoryIds)) { + List categories = canteenOnlineCategoryMapper.selectBatchIds(categoryIds); + categoryTypeById = categories == null ? Collections.emptyMap() : categories.stream() + .filter(Objects::nonNull) + .collect(Collectors.toMap(CanteenOnlineCategoryEntity::getId, + CanteenOnlineCategoryEntity::getCategoryType, (a, b) -> a)); + } + Map result = new HashMap<>(); + for (CanteenOnlineSpuEntity spu : spus) { + if (spu.getId() == null) { + continue; + } + if (spu.getCategoryId() != null) { + result.put(spu.getId(), categoryTypeById.get(spu.getCategoryId())); + } + } + return result; + } + + private List filterSpuItemsForSegment(List spuItems, + Integer segmentIndex) { + if (CollUtil.isEmpty(spuItems)) { + return List.of(); + } + boolean hasSegmentTag = spuItems.stream().anyMatch(s -> s != null && s.getSegmentIndex() != null); + if (!hasSegmentTag) { + return spuItems; + } + return spuItems.stream() + .filter(s -> s != null && Objects.equals(s.getSegmentIndex(), segmentIndex)) + .collect(Collectors.toList()); + } + private String serializeSpuItems(List items) { if (CollUtil.isEmpty(items)) { return null; diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/util/MealSegmentCategoryUtils.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/util/MealSegmentCategoryUtils.java new file mode 100644 index 000000000..d3e62864b --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/util/MealSegmentCategoryUtils.java @@ -0,0 +1,50 @@ +package com.bsd.pjcloud.canteen.util; + +import cn.hutool.core.util.StrUtil; +import com.bsd.pjcloud.canteen.entity.CanteenOnlineCategoryEntity; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 在线分类 {@code categoryType} 与报餐餐段 {@code mealSegmentType} 的匹配工具。 + * categoryType 为逗号分隔,如 "1,2" 表示早餐+午餐;mealSegmentType:1早餐 2午餐 3晚餐 4宵夜。 + */ +public final class MealSegmentCategoryUtils { + + private MealSegmentCategoryUtils() { + } + + public static boolean matchesMealSegment(String categoryType, Integer mealSegmentType) { + if (mealSegmentType == null) { + return true; + } + if (StrUtil.isBlank(categoryType)) { + return true; + } + String typeStr = String.valueOf(mealSegmentType); + for (String part : categoryType.split(",")) { + if (typeStr.equals(StrUtil.trim(part))) { + return true; + } + } + return false; + } + + public static List filterCategoryIdsByMealSegment(List categories, + Integer mealSegmentType) { + if (categories == null || categories.isEmpty()) { + return Collections.emptyList(); + } + if (mealSegmentType == null) { + return categories.stream().map(CanteenOnlineCategoryEntity::getId).collect(Collectors.toList()); + } + return categories.stream() + .filter(c -> matchesMealSegment(c.getCategoryType(), mealSegmentType)) + .map(CanteenOnlineCategoryEntity::getId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } +}