|
|
@@ -9,16 +9,21 @@ import org.springframework.stereotype.Service;
|
|
|
import shop.alien.entity.store.StoreCuisine;
|
|
|
import shop.alien.entity.store.StoreCuisineCategory;
|
|
|
import shop.alien.entity.store.StoreInfo;
|
|
|
+import shop.alien.entity.store.StorePrice;
|
|
|
+import shop.alien.entity.store.constants.StoreMenuType;
|
|
|
import shop.alien.entity.store.StoreProductDiscountRule;
|
|
|
import shop.alien.entity.store.StoreTable;
|
|
|
import shop.alien.entity.store.dto.StoreInfoWithHomepageCuisinesDTO;
|
|
|
-import shop.alien.entity.store.vo.CategoryWithCuisinesVO;
|
|
|
+import shop.alien.entity.store.vo.CategoryMenuGroupVO;
|
|
|
import shop.alien.entity.store.vo.StoreCuisineWithPricesVO;
|
|
|
+import shop.alien.entity.store.vo.StorePriceWithPricesVO;
|
|
|
import shop.alien.mapper.StoreCuisineCategoryMapper;
|
|
|
import shop.alien.mapper.StoreCuisineMapper;
|
|
|
import shop.alien.mapper.StoreInfoMapper;
|
|
|
+import shop.alien.mapper.StorePriceMapper;
|
|
|
import shop.alien.mapper.StoreProductDiscountRuleMapper;
|
|
|
import shop.alien.mapper.StoreTableMapper;
|
|
|
+import shop.alien.dining.constants.OrderMenuConstants;
|
|
|
import shop.alien.dining.service.StoreInfoService;
|
|
|
import shop.alien.dining.support.ProductDiscountPricingSupport;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
@@ -48,6 +53,7 @@ public class StoreInfoServiceImpl implements StoreInfoService {
|
|
|
private final StoreTableMapper storeTableMapper;
|
|
|
private final StoreCuisineCategoryMapper storeCuisineCategoryMapper;
|
|
|
private final StoreCuisineMapper storeCuisineMapper;
|
|
|
+ private final StorePriceMapper storePriceMapper;
|
|
|
private final StoreInfoMapper storeInfoMapper;
|
|
|
private final StoreProductDiscountRuleMapper storeProductDiscountRuleMapper;
|
|
|
|
|
|
@@ -64,15 +70,17 @@ public class StoreInfoServiceImpl implements StoreInfoService {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public List<StoreCuisineCategory> getCategoriesByStoreId(Integer storeId) {
|
|
|
- log.info("根据门店ID查询菜品种类列表, storeId={}", storeId);
|
|
|
-
|
|
|
+ public List<StoreCuisineCategory> getCategoriesByStoreId(Integer storeId, Integer menuType) {
|
|
|
+ int mt = StoreMenuType.normalizeOrCuisine(menuType);
|
|
|
+ log.info("根据门店ID查询分类列表, storeId={}, menuType={}", storeId, mt);
|
|
|
+
|
|
|
LambdaQueryWrapper<StoreCuisineCategory> wrapper = new LambdaQueryWrapper<>();
|
|
|
wrapper.eq(StoreCuisineCategory::getStoreId, storeId);
|
|
|
+ wrapper.eq(StoreCuisineCategory::getMenuType, mt);
|
|
|
wrapper.eq(StoreCuisineCategory::getDeleteFlag, 0);
|
|
|
- wrapper.eq(StoreCuisineCategory::getStatus, 1); // 只查询启用的分类
|
|
|
- wrapper.orderByAsc(StoreCuisineCategory::getSort); // 按排序字段排序
|
|
|
-
|
|
|
+ wrapper.eq(StoreCuisineCategory::getStatus, 1);
|
|
|
+ wrapper.orderByAsc(StoreCuisineCategory::getSort);
|
|
|
+
|
|
|
return storeCuisineCategoryMapper.selectList(wrapper);
|
|
|
}
|
|
|
|
|
|
@@ -86,8 +94,12 @@ public class StoreInfoServiceImpl implements StoreInfoService {
|
|
|
log.warn("菜品种类不存在, categoryId={}", categoryId);
|
|
|
return new java.util.ArrayList<>();
|
|
|
}
|
|
|
-
|
|
|
- // 查询该门店下所有上架的菜品(与 getCategoriesWithCuisinesByStoreId 中 cuisines 查询条件、排序一致)
|
|
|
+ if (!StoreMenuType.isCuisine(category.getMenuType())) {
|
|
|
+ log.warn("该分类非美食分类,不返回 store_cuisine 列表, categoryId={}, menuType={}", categoryId, category.getMenuType());
|
|
|
+ return new java.util.ArrayList<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询该门店下所有上架的菜品(与 getCategoriesWithMenuGroupsByStoreId(menuType=1) 中 cuisines 查询条件、排序一致)
|
|
|
LambdaQueryWrapper<StoreCuisine> wrapper = new LambdaQueryWrapper<>();
|
|
|
wrapper.eq(StoreCuisine::getStoreId, category.getStoreId());
|
|
|
wrapper.eq(StoreCuisine::getDeleteFlag, 0);
|
|
|
@@ -119,13 +131,20 @@ public class StoreInfoServiceImpl implements StoreInfoService {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public List<CategoryWithCuisinesVO> getCategoriesWithCuisinesByStoreId(Integer storeId, String keyword) {
|
|
|
- log.info("根据门店ID查询菜品种类及下属菜品, storeId={}, keyword={}", storeId, keyword);
|
|
|
+ public List<CategoryMenuGroupVO> getCategoriesWithMenuGroupsByStoreId(Integer storeId, String keyword, Integer menuType) {
|
|
|
+ int mt = StoreMenuType.normalizeOrCuisine(menuType);
|
|
|
+ log.info("根据门店ID查询分类树, storeId={}, keyword={}, menuType={}", storeId, keyword, mt);
|
|
|
+ if (mt == StoreMenuType.GENERIC_PRICE) {
|
|
|
+ return buildGenericCategoryMenuGroups(storeId, keyword);
|
|
|
+ }
|
|
|
+ return buildCuisineCategoryMenuGroups(storeId, keyword);
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<CategoryMenuGroupVO> buildCuisineCategoryMenuGroups(Integer storeId, String keyword) {
|
|
|
List<StoreCuisineCategory> categories = getCategoriesByStoreId(storeId);
|
|
|
if (categories == null || categories.isEmpty()) {
|
|
|
return new ArrayList<>();
|
|
|
}
|
|
|
- // 与 getCuisinesByCategoryId 相同的查询条件与排序,保证 cuisines 字段与 /store/info/cuisines 一致
|
|
|
LambdaQueryWrapper<StoreCuisine> cuisineWrapper = new LambdaQueryWrapper<>();
|
|
|
cuisineWrapper.eq(StoreCuisine::getStoreId, storeId);
|
|
|
cuisineWrapper.eq(StoreCuisine::getDeleteFlag, 0);
|
|
|
@@ -148,7 +167,7 @@ public class StoreInfoServiceImpl implements StoreInfoService {
|
|
|
}
|
|
|
Map<Integer, BigDecimal> discountedByProductId = buildDiscountedPriceMap(storeId, flatForPricing);
|
|
|
|
|
|
- List<CategoryWithCuisinesVO> result = new ArrayList<>();
|
|
|
+ List<CategoryMenuGroupVO> result = new ArrayList<>();
|
|
|
for (StoreCuisineCategory category : categories) {
|
|
|
Integer categoryId = category.getId();
|
|
|
List<StoreCuisineWithPricesVO> cuisines = allCuisines.stream()
|
|
|
@@ -156,7 +175,47 @@ public class StoreInfoServiceImpl implements StoreInfoService {
|
|
|
.filter(c -> !filterByName || (c.getName() != null && c.getName().toLowerCase().contains(keywordLower)))
|
|
|
.map(c -> toCuisineWithPricesVo(c, discountedByProductId))
|
|
|
.collect(Collectors.toList());
|
|
|
- result.add(new CategoryWithCuisinesVO(category, cuisines));
|
|
|
+ result.add(new CategoryMenuGroupVO(category, cuisines, null));
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<CategoryMenuGroupVO> buildGenericCategoryMenuGroups(Integer storeId, String keyword) {
|
|
|
+ List<StoreCuisineCategory> categories = getCategoriesByStoreId(storeId, StoreMenuType.GENERIC_PRICE);
|
|
|
+ if (categories == null || categories.isEmpty()) {
|
|
|
+ return new ArrayList<>();
|
|
|
+ }
|
|
|
+ LambdaQueryWrapper<StorePrice> priceWrapper = new LambdaQueryWrapper<>();
|
|
|
+ priceWrapper.eq(StorePrice::getStoreId, storeId);
|
|
|
+ priceWrapper.eq(StorePrice::getShelfStatus, 1);
|
|
|
+ priceWrapper.eq(StorePrice::getStatus, 1);
|
|
|
+ priceWrapper.orderByAsc(StorePrice::getId);
|
|
|
+ List<StorePrice> allPrices = storePriceMapper.selectList(priceWrapper);
|
|
|
+ if (allPrices == null) {
|
|
|
+ allPrices = new ArrayList<>();
|
|
|
+ }
|
|
|
+ boolean filterByName = StringUtils.isNotBlank(keyword);
|
|
|
+ String keywordLower = filterByName ? keyword.trim().toLowerCase() : null;
|
|
|
+
|
|
|
+ List<StorePrice> flatForPricing = new ArrayList<>();
|
|
|
+ for (StoreCuisineCategory category : categories) {
|
|
|
+ Integer categoryId = category.getId();
|
|
|
+ allPrices.stream()
|
|
|
+ .filter(p -> priceBelongsToCategory(p, categoryId))
|
|
|
+ .filter(p -> !filterByName || (p.getName() != null && p.getName().toLowerCase().contains(keywordLower)))
|
|
|
+ .forEach(flatForPricing::add);
|
|
|
+ }
|
|
|
+ Map<Integer, BigDecimal> discountedByProductId = buildDiscountedPriceMapForGeneric(storeId, flatForPricing);
|
|
|
+
|
|
|
+ List<CategoryMenuGroupVO> result = new ArrayList<>();
|
|
|
+ for (StoreCuisineCategory category : categories) {
|
|
|
+ Integer categoryId = category.getId();
|
|
|
+ List<StorePriceWithPricesVO> prices = allPrices.stream()
|
|
|
+ .filter(p -> priceBelongsToCategory(p, categoryId))
|
|
|
+ .filter(p -> !filterByName || (p.getName() != null && p.getName().toLowerCase().contains(keywordLower)))
|
|
|
+ .map(p -> toPriceWithPricesVo(p, discountedByProductId))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ result.add(new CategoryMenuGroupVO(category, null, prices));
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
@@ -195,6 +254,39 @@ public class StoreInfoServiceImpl implements StoreInfoService {
|
|
|
rules);
|
|
|
}
|
|
|
|
|
|
+ /** 通用价目:仅 rule_product_type=2 的规则参与计价 */
|
|
|
+ private Map<Integer, BigDecimal> buildDiscountedPriceMapForGeneric(Integer storeId, List<StorePrice> visiblePrices) {
|
|
|
+ if (visiblePrices == null || visiblePrices.isEmpty()) {
|
|
|
+ return new HashMap<>();
|
|
|
+ }
|
|
|
+ Set<Integer> idSet = new HashSet<>();
|
|
|
+ Map<Integer, BigDecimal> basePriceByProduct = new HashMap<>();
|
|
|
+ for (StorePrice p : visiblePrices) {
|
|
|
+ if (p.getId() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (idSet.add(p.getId())) {
|
|
|
+ BigDecimal base = p.getTotalPrice() != null ? p.getTotalPrice() : BigDecimal.ZERO;
|
|
|
+ basePriceByProduct.put(p.getId(), base);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ List<Integer> productIds = new ArrayList<>(idSet);
|
|
|
+ if (productIds.isEmpty()) {
|
|
|
+ return new HashMap<>();
|
|
|
+ }
|
|
|
+ List<StoreProductDiscountRule> rules = storeProductDiscountRuleMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<StoreProductDiscountRule>()
|
|
|
+ .eq(StoreProductDiscountRule::getStoreId, storeId)
|
|
|
+ .eq(StoreProductDiscountRule::getStatus, 1)
|
|
|
+ .eq(StoreProductDiscountRule::getRuleProductType, OrderMenuConstants.RULE_PRODUCT_GENERIC_PRICE)
|
|
|
+ .in(StoreProductDiscountRule::getProductId, productIds));
|
|
|
+ return ProductDiscountPricingSupport.resolveDiscountedPrices(
|
|
|
+ productIds,
|
|
|
+ basePriceByProduct,
|
|
|
+ LocalDateTime.now(),
|
|
|
+ rules);
|
|
|
+ }
|
|
|
+
|
|
|
private static StoreCuisineWithPricesVO toCuisineWithPricesVo(StoreCuisine cuisine, Map<Integer, BigDecimal> discountedByProductId) {
|
|
|
Map<Integer, BigDecimal> disc = discountedByProductId == null ? Collections.emptyMap() : discountedByProductId;
|
|
|
StoreCuisineWithPricesVO vo = new StoreCuisineWithPricesVO();
|
|
|
@@ -208,9 +300,29 @@ public class StoreInfoServiceImpl implements StoreInfoService {
|
|
|
return vo;
|
|
|
}
|
|
|
|
|
|
+ private static StorePriceWithPricesVO toPriceWithPricesVo(StorePrice price, Map<Integer, BigDecimal> discountedByProductId) {
|
|
|
+ Map<Integer, BigDecimal> disc = discountedByProductId == null ? Collections.emptyMap() : discountedByProductId;
|
|
|
+ StorePriceWithPricesVO vo = new StorePriceWithPricesVO();
|
|
|
+ BeanUtils.copyProperties(price, vo);
|
|
|
+ BigDecimal original = price.getTotalPrice() != null ? price.getTotalPrice() : BigDecimal.ZERO;
|
|
|
+ vo.setOriginalPrice(original);
|
|
|
+ boolean hasRule = price.getId() != null && disc.containsKey(price.getId());
|
|
|
+ BigDecimal current = hasRule ? disc.get(price.getId()) : original;
|
|
|
+ vo.setCurrentPrice(current);
|
|
|
+ vo.setHasActiveDiscount(hasRule);
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
private boolean belongsToCategory(StoreCuisine cuisine, Integer categoryId) {
|
|
|
- String categoryIdsStr = cuisine.getCategoryIds();
|
|
|
- if (StringUtils.isBlank(categoryIdsStr)) {
|
|
|
+ return categoryJsonContains(cuisine.getCategoryIds(), categoryId);
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean priceBelongsToCategory(StorePrice price, Integer categoryId) {
|
|
|
+ return categoryJsonContains(price.getCategoryIds(), categoryId);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static boolean categoryJsonContains(String categoryIdsStr, Integer categoryId) {
|
|
|
+ if (StringUtils.isBlank(categoryIdsStr) || categoryId == null) {
|
|
|
return false;
|
|
|
}
|
|
|
try {
|
|
|
@@ -233,6 +345,10 @@ public class StoreInfoServiceImpl implements StoreInfoService {
|
|
|
log.warn("删除菜品种类失败:分类不存在, categoryId={}", categoryId);
|
|
|
return false;
|
|
|
}
|
|
|
+ if (!StoreMenuType.isCuisine(category.getMenuType())) {
|
|
|
+ log.warn("删除菜品种类失败:仅支持美食分类,通用价目分类请在商家端维护, categoryId={}", categoryId);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
Integer storeId = category.getStoreId();
|
|
|
// 1. 解除绑定:将该分类ID从所有关联菜品的 category_ids 中移除,价目表菜品其它字段不变
|
|
|
LambdaQueryWrapper<StoreCuisine> wrapper = new LambdaQueryWrapper<>();
|