package com.example.nto.service.impl; import com.example.nto.controller.dto.BookingCreateDto; import com.example.nto.controller.dto.PlaceDto; import com.example.nto.entity.Booking; import com.example.nto.entity.Employee; import com.example.nto.entity.Place; import com.example.nto.exception.BookingAlreadyExistsException; import com.example.nto.exception.EmployeeNotFoundException; import com.example.nto.exception.PlaceNotFoundException; import com.example.nto.repository.BookingRepository; import com.example.nto.repository.EmployeeRepository; import com.example.nto.repository.PlaceRepository; import com.example.nto.service.BookingService; import com.example.nto.service.EmployeeService; import java.time.LocalDate; import java.time.ZoneId; import java.util.*; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class BookingServiceImpl implements BookingService { private final BookingRepository bookingRepository; private final EmployeeRepository employeeRepository; private final PlaceRepository placeRepository; private final EmployeeService employeeService; @Value("${booking.days-ahead}") private int daysAhead; @Override @Transactional(readOnly = true) public Map> getFreePlace(String code) { employeeService.auth(code); List allPlaces = placeRepository.findAll(); LocalDate today = LocalDate.now(ZoneId.systemDefault()); LocalDate end = today.plusDays(daysAhead); List bookings = bookingRepository.findByDateBetween(today, end); Map> busyByDate = bookings.stream() .collect( Collectors.groupingBy( Booking::getDate, Collectors.mapping( b -> b.getPlace().getId(), Collectors.toSet()))); Map> result = new LinkedHashMap<>(); for (int i = 0; i <= daysAhead; i++) { LocalDate currentDate = today.plusDays(i); Set busyPlaces = busyByDate.getOrDefault(currentDate, Collections.emptySet()); List freePlaces = allPlaces.stream() .filter(place -> !busyPlaces.contains(place.getId())) .map(place -> new PlaceDto(place.getId(), place.getPlace())) .toList(); result.put(currentDate, freePlaces); } return result; } @Override @Transactional public Booking create(String code, BookingCreateDto bookingCreateDto) { LocalDate date = bookingCreateDto.getDate(); LocalDate today = LocalDate.now(ZoneId.systemDefault()); if (date.isBefore(today) || date.isAfter(today.plusDays(daysAhead))) { throw new IllegalArgumentException("Date is out of booking window"); } Employee employee = employeeRepository .findByCode(code) .orElseThrow( () -> new EmployeeNotFoundException( "Employee with " + code + " code not found!")); long placeId = bookingCreateDto.getPlaceId(); Place place = placeRepository .findById(placeId) .orElseThrow( () -> new PlaceNotFoundException( "Place with " + placeId + " id not found!")); if (bookingRepository.findByDateAndPlace(date, place).isPresent()) { throw new BookingAlreadyExistsException("Booking already exists"); } if (bookingRepository.findByDateAndEmployee(date, employee).isPresent()) { throw new BookingAlreadyExistsException( "This employee already has another booking on " + date); } Booking booking = Booking.builder().date(date).employee(employee).place(place).build(); return bookingRepository.save(booking); } }