494 lines
20 KiB
Java
494 lines
20 KiB
Java
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();
|
||
}
|
||
}
|