Files
my-bar/src/main/java/ru/kayashov/bar/service/CocktailService.java

494 lines
20 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<CocktailForListResponseDto> getMenu(CocktailFilterRequestDto dto) {
return mapper.cocktailsToDtoList(criteria(dto), dto.getAll());
}
private List<CocktailEntity> criteria(CocktailFilterRequestDto dto) {
Visitor visitor = visitorService.getCurrentVisitor();
Session session = entityManager.unwrap(Session.class);
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<CocktailEntity> criteriaQuery = cb.createQuery(CocktailEntity.class);
Root<CocktailEntity> root = criteriaQuery.from(CocktailEntity.class);
List<Predicate> predicates = new ArrayList<>();
if (!dto.getAll()) {
Long barId = visitor.getResidents().stream()
.filter(BarResident::getActive)
.map(BarResident::getBar)
.map(BarEntity::getId)
.toList()
.get(0);
List<Long> cocktailIds = getAllowedCocktailIds(barId);
Predicate pr = root.get("id").in(cocktailIds);
predicates.add(pr);
criteriaQuery.distinct(true);
List<Long> stopListIds = getStopListIds(visitor);
predicates.add(cb.not(root.get("id").in(stopListIds)));
}
if (!dto.getSearch().isEmpty()) {
String[] search = dto.getSearch().split(" ");
List<Predicate> in = new ArrayList<>();
Join<CocktailEntity, ReceiptEntity> 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<Long> 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<CocktailEntity, TagEntity> tagJoin = root.join("tags", JoinType.LEFT);
predicates.add(tagJoin.get("name").in(dto.getTags()));
}
if (dto.getInMenu() != null) {
List<Long> 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<CocktailEntity> query = session.createQuery(criteriaQuery);
query.setFirstResult(dto.getPage() * dto.getSize());
query.setMaxResults(dto.getSize());
List<CocktailEntity> cocktailEntities = query.getResultList();
log.info("Найдено {} коктейлей", cocktailEntities.size());
// return Collections.emptyList();
return cocktailEntities;
}
private List<Long> 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<Long> getAllowedCocktailIds(Long barId) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> query = cb.createQuery(Long.class);
Root<ReceiptEntity> receiptRoot = query.from(ReceiptEntity.class);
Join<ReceiptEntity, IngredientEntity> ingredientJoin = receiptRoot.join("ingredient", JoinType.LEFT);
Join<IngredientEntity, BarIngredientStorage> barIngredientStorageJoin = ingredientJoin.join("barIngredients", JoinType.LEFT);
// Внешний подзапрос с NOT EXISTS
Subquery<Long> subquery = query.subquery(Long.class);
Root<ReceiptEntity> receiptSubRoot = subquery.from(ReceiptEntity.class);
Join<ReceiptEntity, IngredientEntity> ingredientSubJoin = receiptSubRoot.join("ingredient", JoinType.LEFT);
// Внутренний подзапрос с NOT EXISTS
Subquery<Long> innerSubquery = subquery.subquery(Long.class);
Root<BarIngredientStorage> 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<Rating> 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<Rating> 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<ReceiptEntity> old, List<ReceiptResponseDto> 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<TagEntity> findTags(String tagString) {
if (tagString == null || tagString.isEmpty()) {
return new ArrayList<>();
}
List<TagEntity> 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<StopList> 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<ReceiptResponseDto> getReceipts(Long id) {
return null;
}
public List<CocktailSimpleResponseDto> getSimple() {
return repository.findAll().stream()
.map(CocktailSimpleResponseDto::mapToDto)
.toList();
}
public String savePhoto(MultipartFile file) throws IOException {
File folder = new File(photoFolder);
List<File> 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<File> 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<CocktailForIngredientModalDto> findByIngredient(Long id) {
return ingredientRepository.findById(id).orElseThrow(RuntimeException::new)
.getReceipts()
.stream()
.map(ReceiptEntity::getCocktail)
.distinct()
.map(mapper::cocktailToIngredientDtoList)
.toList();
}
}