package ru.kayashov.bar.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hibernate.Session; import org.hibernate.query.Query; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import ru.kayashov.bar.controller.IngredientController; import ru.kayashov.bar.controller.dto.cocktail.CocktailFilterRequestDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailForIngredientModalDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailForListResponseDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailSimpleResponseDto; import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto; import ru.kayashov.bar.mapper.CocktailMapper; import ru.kayashov.bar.model.entity.AlcoholicEntity; import ru.kayashov.bar.model.entity.BarEntity; import ru.kayashov.bar.model.entity.BarIngredientStorage; import ru.kayashov.bar.model.entity.BarResident; import ru.kayashov.bar.model.entity.CategoryEntity; import ru.kayashov.bar.model.entity.CocktailEntity; import ru.kayashov.bar.model.entity.GlassEntity; import ru.kayashov.bar.model.entity.IngredientEntity; import ru.kayashov.bar.model.entity.Rating; import ru.kayashov.bar.model.entity.ReceiptEntity; import ru.kayashov.bar.model.entity.StopList; import ru.kayashov.bar.model.entity.TagEntity; import ru.kayashov.bar.model.entity.Visitor; import ru.kayashov.bar.repository.AlcoholicRepository; import ru.kayashov.bar.repository.CategoryRepository; import ru.kayashov.bar.repository.CocktailRepository; import ru.kayashov.bar.repository.GlassRepository; import ru.kayashov.bar.repository.IngredientRepository; import ru.kayashov.bar.repository.RatingRepository; import ru.kayashov.bar.repository.ReceiptRepository; import ru.kayashov.bar.repository.StopListRepository; import ru.kayashov.bar.repository.TagRepository; import ru.kayashov.bar.repository.VisitorsRepository; import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.Order; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.persistence.criteria.Subquery; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; @Slf4j @Service @RequiredArgsConstructor public class CocktailService { @Value("${cocktail.photo.path}") private String photoFolder; private final StopListRepository stopListRepository; private final CocktailMapper mapper; private final CocktailRepository repository; private final AlcoholicRepository alcoholicRepository; private final CategoryRepository categoryRepository; private final GlassRepository glassRepository; private final TagRepository tagRepository; private final ReceiptRepository receiptRepository; private final IngredientRepository ingredientRepository; private final VisitorsRepository visitorsRepository; private final RatingRepository ratingRepository; private final VisitorService visitorService; private final EntityManager entityManager; /** * Вывод меню бара */ @Transactional public List getMenu(CocktailFilterRequestDto dto) { return mapper.cocktailsToDtoList(criteria(dto), dto.getAll()); } private List criteria(CocktailFilterRequestDto dto) { Visitor visitor = visitorService.getCurrentVisitor(); Session session = entityManager.unwrap(Session.class); CriteriaBuilder cb = session.getCriteriaBuilder(); CriteriaQuery criteriaQuery = cb.createQuery(CocktailEntity.class); Root root = criteriaQuery.from(CocktailEntity.class); List predicates = new ArrayList<>(); if (!dto.getAll()) { Long barId = visitor.getResidents().stream() .filter(BarResident::getActive) .map(BarResident::getBar) .map(BarEntity::getId) .toList() .get(0); List cocktailIds = getAllowedCocktailIds(barId); Predicate pr = root.get("id").in(cocktailIds); predicates.add(pr); criteriaQuery.distinct(true); List stopListIds = getStopListIds(visitor); predicates.add(cb.not(root.get("id").in(stopListIds))); } if (!dto.getSearch().isEmpty()) { String[] search = dto.getSearch().split(" "); List in = new ArrayList<>(); Join receiptJoin = root.join("receipt", JoinType.LEFT); for (String s : search) { in.add(cb.like(cb.lower(root.get("name")), "%" + s.toLowerCase() + "%")); in.add(cb.like(cb.lower(receiptJoin.get("ingredient").get("name")), "%" + s.toLowerCase() + "%")); } predicates.add(cb.or(in.toArray(new Predicate[0]))); } if (dto.getOnlyFavourite()) { List favouriteCocktailsId = visitor.getRating().stream() .filter(Rating::isFavorite) .map(Rating::getCocktail) .map(CocktailEntity::getId) .toList(); predicates.add(root.get("id").in(favouriteCocktailsId)); } if (dto.getGlass() != null && !dto.getGlass().isEmpty()) { predicates.add(root.get("glassEntity").get("name").in(dto.getGlass())); } if (dto.getCategory() != null && !dto.getCategory().isEmpty()) { predicates.add(root.get("categoryEntity").get("name").in(dto.getCategory())); } if (dto.getAlcohol() != null && !dto.getAlcohol().isEmpty()) { predicates.add(root.get("alcoholicEntity").get("name").in(dto.getAlcohol())); } if (!dto.getTags().isEmpty()) { Join tagJoin = root.join("tags", JoinType.LEFT); predicates.add(tagJoin.get("name").in(dto.getTags())); } if (dto.getInMenu() != null) { List stopListIds = getStopListIds(visitor); switch (dto.getInMenu()) { case "Есть в меню" -> predicates.add(cb.not(root.get("id").in(stopListIds))); case "Нет в меню" -> predicates.add(root.get("id").in(stopListIds)); } } if (dto.getICount() != null) { log.info("iii"); } // // if (!dto.getSortField().equals("name") || !dto.getSortOrder().equals("asc")) { // cb.asc(root.get("name")); //// query.orderBy(cb.asc(root.get("name").get(dto.getSortField()))); // } else { // criteriaQuery.orderBy(cb.asc(root.get("name"))); // } //todo: доделать другие виды сортировки Order order; switch (dto.getSort()) { case NAME_ASC -> order = cb.asc(root.get("name")); case NAME_DESC -> order = cb.desc(root.get("name")); default -> order = cb.asc(root.get("name")); } criteriaQuery.where(predicates.toArray(new Predicate[0])) .orderBy(order); Query query = session.createQuery(criteriaQuery); query.setFirstResult(dto.getPage() * dto.getSize()); query.setMaxResults(dto.getSize()); List cocktailEntities = query.getResultList(); log.info("Найдено {} коктейлей", cocktailEntities.size()); // return Collections.emptyList(); return cocktailEntities; } private List getStopListIds(Visitor visitor) { return visitor.getResidents() .stream() .filter(BarResident::getActive) .map(BarResident::getBar) .map(BarEntity::getStops) .flatMap(List::stream) .map(StopList::getCocktail) .map(CocktailEntity::getId) .toList(); } private List getAllowedCocktailIds(Long barId) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery query = cb.createQuery(Long.class); Root receiptRoot = query.from(ReceiptEntity.class); Join ingredientJoin = receiptRoot.join("ingredient", JoinType.LEFT); Join barIngredientStorageJoin = ingredientJoin.join("barIngredients", JoinType.LEFT); // Внешний подзапрос с NOT EXISTS Subquery subquery = query.subquery(Long.class); Root receiptSubRoot = subquery.from(ReceiptEntity.class); Join ingredientSubJoin = receiptSubRoot.join("ingredient", JoinType.LEFT); // Внутренний подзапрос с NOT EXISTS Subquery innerSubquery = subquery.subquery(Long.class); Root barIngredientStorageInnerRoot = innerSubquery.from(BarIngredientStorage.class); // Условия внутреннего подзапроса innerSubquery.select(barIngredientStorageInnerRoot.get("id")) .where( cb.equal(barIngredientStorageInnerRoot.get("ingredient"), ingredientSubJoin.get("id")), cb.equal(barIngredientStorageInnerRoot.get("bar").get("id"), barId) ); // Условия внешнего подзапроса subquery.select(receiptSubRoot.get("id")) .where( cb.equal(receiptSubRoot.get("cocktail").get("id"), receiptRoot.get("cocktail").get("id")), cb.not(cb.exists(innerSubquery)) ); // Основной запрос query.select(receiptRoot.get("cocktail").get("id")) .distinct(true) .where( cb.equal(barIngredientStorageJoin.get("bar").get("id"), barId), cb.not(cb.exists(subquery)) ); return entityManager.createQuery(query).getResultList(); } public CocktailForListResponseDto findById(Long id) { CocktailEntity cocktail = repository.findById(id).orElseThrow(); return mapper.cocktailToFullDto(cocktail); } @Transactional public void edit(CocktailForListResponseDto dto) { CocktailEntity cocktail; if (dto.getId() == null) { if (dto.getName() == null) { throw new RuntimeException("Коктейль не может быть без названия"); } cocktail = new CocktailEntity(); cocktail.setReceipt(new ArrayList<>()); } else { cocktail = repository.findById(dto.getId()) .orElseThrow(() -> new RuntimeException("Не удалось найти коктейль с id " + dto.getId())); } CategoryEntity category = categoryRepository.findByNameIgnoreCase(dto.getCategory()) .orElseThrow(() -> new RuntimeException("Не удалось найти категорию " + dto.getCategory())); GlassEntity glassEntity = glassRepository.findByNameIgnoreCase(dto.getGlass()) .orElseThrow(() -> new RuntimeException("Не удалось найти посуду" + dto.getGlass())); AlcoholicEntity alcoholicEntity = alcoholicRepository.findByName(dto.getAlcoholic()) .orElseThrow(() -> new RuntimeException("Не удалось найти алкогольность" + dto.getAlcoholic())); cocktail.setName(dto.getName()); cocktail.setInstructions(dto.getInstructions()); cocktail.setImage(dto.getImage()); cocktail.setVideo(cocktail.getVideo()); cocktail.setCategoryEntity(category); cocktail.setGlassEntity(glassEntity); cocktail.setAlcoholicEntity(alcoholicEntity); cocktail.setTags(findTags(dto.getTags())); cocktail.setRating(cocktail.getRating()); repository.save(cocktail); editCocktailReceipts(cocktail.getReceipt(), dto.getReceipt(), cocktail); } public void editFavourite(Long cocktailId, Long visitorId, boolean put) { Visitor visitor = visitorsRepository.findById(visitorId).orElseThrow(); Optional ratingOpt = ratingRepository.findRatingByCocktailIdAndVisitorId(cocktailId, visitorId); Rating rating; if (put) { CocktailEntity cocktail = repository.findById(cocktailId) .orElseThrow(); if (ratingOpt.isEmpty()) { rating = new Rating(); rating.setCocktail(cocktail); rating.setVisitor(visitor); } else { rating = ratingOpt.get(); } rating.setFavorite(true); ratingRepository.save(rating); } else { if (ratingOpt.isPresent()) { rating = ratingOpt.get(); rating.setFavorite(false); ratingRepository.save(rating); } } } public void setRating(Long cocktailId, Integer rating) { Long id = ((Visitor) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId(); Visitor visitor = visitorsRepository.findById(id) .orElseThrow(); Rating rate; Optional rateOpt = ratingRepository.findRatingByCocktailIdAndVisitorId(cocktailId, id); if (rateOpt.isEmpty()) { rate = new Rating(); rate.setVisitor(visitor); rate.setCocktail(repository.findById(cocktailId).orElseThrow()); rate.setFavorite(false); } else { rate = rateOpt.get(); } rate.setRating(rating); ratingRepository.save(rate); } private void editCocktailReceipts(List old, List actual, CocktailEntity cocktail) { for (ReceiptResponseDto receipt : actual) { if (receipt.getId() == null) { createNewReceipt(receipt, cocktail); continue; } old.stream().filter(r -> receipt.getId().equals(r.getId())) .findFirst() .ifPresent(r -> equalReceipt(r, receipt)); old.removeIf(r -> r.getId().equals(receipt.getId())); } old.forEach(r -> receiptRepository.deleteById(r.getId())); } private void equalReceipt(ReceiptEntity old, ReceiptResponseDto actual) { boolean needSave = false; if (!actual.getIngredient().getId().equals(old.getIngredient().getId())) { IngredientEntity ingredient = ingredientRepository.findById(actual.getIngredient().getId()).orElseThrow(); old.setIngredient(ingredient); needSave = true; } if (old.getUnit() == null || actual.getUnit() == null || !Objects.equals(old.getUnit().getId(), actual.getUnit().getId())) { old.setUnit(actual.getUnit()); needSave = true; } if (!Objects.equals(old.getCount(), actual.getCount())) { old.setCount(actual.getCount()); needSave = true; } if (needSave) { receiptRepository.save(old); } } private void createNewReceipt(ReceiptResponseDto receipt, CocktailEntity cocktail) { ReceiptEntity receiptEntity = new ReceiptEntity(); IngredientEntity ingredient = ingredientRepository.findById(receipt.getIngredient().getId()).orElseThrow(); receiptEntity.setIngredient(ingredient); receiptEntity.setCount(receipt.getCount()); receiptEntity.setUnit(receipt.getUnit()); // receiptEntity.setMeasure(receipt.getMeasure()); receiptEntity.setCocktail(cocktail); receiptRepository.save(receiptEntity); } private List findTags(String tagString) { if (tagString == null || tagString.isEmpty()) { return new ArrayList<>(); } List tags = new ArrayList<>(); for (String name : tagString.split(",")) { if (name.isEmpty()) { continue; } TagEntity tagEntity = tagRepository.findByName(name) .orElseThrow(() -> new RuntimeException("Не удалось найти тег " + name)); tags.add(tagEntity); } return tags; } public void inMenuEdit(Long id, Boolean value) { Visitor visitor = visitorService.getCurrentVisitor(); CocktailEntity entity = repository.findById(id) .orElseThrow(() -> new RuntimeException("Не удалось найти коктейль с id " + id)); BarEntity bar = visitor.getResidents().stream() .filter(BarResident::getActive) .map(BarResident::getBar) .toList() .get(0); Optional stop = bar.getStops().stream() .filter(s -> Objects.equals(s.getCocktail().getId(), id)) .findFirst(); if (value && stop.isPresent()) { stopListRepository.deleteById(stop.get().getId()); return; } if (!value && stop.isEmpty()) { StopList stopList = new StopList(); stopList.setCocktail(entity); stopList.setBar(bar); stopListRepository.save(stopList); } } public CocktailModalDto getForModal(Long id) { CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new); return mapper.cocktailToModalDto(cocktail); } public List getReceipts(Long id) { return null; } public List getSimple() { return repository.findAll().stream() .map(CocktailSimpleResponseDto::mapToDto) .toList(); } public String savePhoto(MultipartFile file) throws IOException { File folder = new File(photoFolder); List files = Arrays.asList(Objects.requireNonNull(folder.listFiles())); String fileName = getPhotoPath(files, file.getOriginalFilename()); String fullName = photoFolder + "/" + fileName; file.transferTo(new File(fullName)); log.info("сохранено фото {}", fileName); return "/assets/cocktails/" + fileName; } private String getPhotoPath(List files, String originalName) { if (files.stream().map(File::getName).anyMatch(name -> name.equals(originalName))) { String[] split = originalName.split("\\."); String name = concatName(split) + "_c." + split[split.length - 1]; return getPhotoPath(files, name); } return originalName; } private String concatName(String[] name) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < name.length - 1; i++) { sb.append(name[i]); } return sb.toString(); } public void delete(Long id) { CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new); log.info("Удален коктейль {}", cocktail); repository.delete(cocktail); } public String findInstructions(Long id) { CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new); return cocktail.getInstructions(); } public List findByIngredient(Long id) { return ingredientRepository.findById(id).orElseThrow(RuntimeException::new) .getReceipts() .stream() .map(ReceiptEntity::getCocktail) .distinct() .map(mapper::cocktailToIngredientDtoList) .toList(); } }