main #9
@@ -1,10 +1,14 @@
|
|||||||
package com.example.nto.controller;
|
package com.example.nto.controller;
|
||||||
|
|
||||||
import com.example.nto.entity.Booking;
|
import com.example.nto.entity.Booking;
|
||||||
|
import com.example.nto.exception.BookingConflictException;
|
||||||
|
import com.example.nto.exception.EmployeeNotFoundException;
|
||||||
|
import com.example.nto.exception.InvalidBookingException;
|
||||||
import com.example.nto.service.BookingService;
|
import com.example.nto.service.BookingService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.ErrorResponse;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -45,24 +49,48 @@ public class BookingController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static class CreateBookingRequest {
|
@PostMapping("/{code}/book")
|
||||||
private LocalDate date;
|
public ResponseEntity<?> createBooking(
|
||||||
private long placeId;
|
@PathVariable String code,
|
||||||
|
@RequestBody Booking.CreateBookingRequest request
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
Booking booking = bookingService.createBooking(
|
||||||
|
code,
|
||||||
|
request.getDate(),
|
||||||
|
request.getPlaceId()
|
||||||
|
);
|
||||||
|
|
||||||
public LocalDate getDate() {
|
// Возвращаем только статус 201 без тела
|
||||||
return date;
|
return ResponseEntity.status(HttpStatus.CREATED).build();
|
||||||
|
|
||||||
|
} catch (EmployeeNotFoundException e) {
|
||||||
|
// 401 - кода не существует
|
||||||
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
|
||||||
|
|
||||||
|
} catch (BookingConflictException e) {
|
||||||
|
// 409 - конфликт (уже забронировано)
|
||||||
|
return ResponseEntity.status(HttpStatus.CONFLICT).build();
|
||||||
|
|
||||||
|
} catch (InvalidBookingException e) {
|
||||||
|
// 400 - невалидные данные
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 500 - внутренняя ошибка сервера
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDate(LocalDate date) {
|
private Booking.BookingResponse convertToResponse(Booking booking) {
|
||||||
this.date = date;
|
Booking.BookingResponse response = new Booking.BookingResponse();
|
||||||
|
response.setId(booking.getId());
|
||||||
|
response.setDate(booking.getDate());
|
||||||
|
response.setPlaceId(booking.getPlace().getId());
|
||||||
|
response.setPlaceName(booking.getPlace().getPlace());
|
||||||
|
response.setEmployeeId(booking.getEmployee().getId());
|
||||||
|
response.setEmployeeName(booking.getEmployee().getName());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getPlaceId() {
|
|
||||||
return placeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPlaceId(long placeId) {
|
|
||||||
this.placeId = placeId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.example.nto.entity;
|
package com.example.nto.entity;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonBackReference;
|
import com.fasterxml.jackson.annotation.JsonBackReference;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@@ -45,4 +46,31 @@ public class Booking {
|
|||||||
private long id;
|
private long id;
|
||||||
private String place;
|
private String place;
|
||||||
}
|
}
|
||||||
|
@Data
|
||||||
|
public class CreateBookingRequest {
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private LocalDate date;
|
||||||
|
private long placeId;
|
||||||
|
}
|
||||||
|
@Data
|
||||||
|
public class ErrorResponse {
|
||||||
|
private String error;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public ErrorResponse(String error, String message) {
|
||||||
|
this.error = error;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class BookingResponse {
|
||||||
|
private Long id;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private LocalDate date;
|
||||||
|
private Long placeId;
|
||||||
|
private String placeName;
|
||||||
|
private Long employeeId;
|
||||||
|
private String employeeName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.example.nto.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ResponseStatus(HttpStatus.CONFLICT)
|
||||||
|
public class BookingConflictException extends RuntimeException {
|
||||||
|
public BookingConflictException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.example.nto.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||||
|
public class EmployeeNotFoundException extends RuntimeException {
|
||||||
|
public EmployeeNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.example.nto.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public class InvalidBookingException extends RuntimeException {
|
||||||
|
public InvalidBookingException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,9 +23,7 @@ public interface BookingRepository extends JpaRepository<Booking, Long> {
|
|||||||
|
|
||||||
Optional<Booking> findById(Long id);
|
Optional<Booking> findById(Long id);
|
||||||
|
|
||||||
Optional<Booking> findByDateAndPlace_Id(LocalDate date, Long placeId);
|
|
||||||
|
|
||||||
List<Booking> findByDateAndEmployee_Code(LocalDate date, String employeeCode);
|
|
||||||
|
|
||||||
List<Booking> findByDateInAndEmployee_Code(List<LocalDate> dates, String employeeCode);
|
List<Booking> findByDateInAndEmployee_Code(List<LocalDate> dates, String employeeCode);
|
||||||
|
Optional<Booking> findByDateAndPlace_Id(LocalDate date, long placeId);
|
||||||
|
List<Booking> findByDateAndEmployee_Code(LocalDate date, String employeeCode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package com.example.nto.service.impl;
|
package com.example.nto.service.impl;
|
||||||
|
|
||||||
import com.example.nto.entity.Booking;
|
import com.example.nto.entity.Booking;
|
||||||
|
import com.example.nto.entity.Booking.AvailablePlaceDto;
|
||||||
import com.example.nto.entity.Employee;
|
import com.example.nto.entity.Employee;
|
||||||
import com.example.nto.entity.Place;
|
import com.example.nto.entity.Place;
|
||||||
|
import com.example.nto.exception.BookingConflictException;
|
||||||
|
import com.example.nto.exception.EmployeeNotFoundException;
|
||||||
|
import com.example.nto.exception.InvalidBookingException;
|
||||||
import com.example.nto.repository.BookingRepository;
|
import com.example.nto.repository.BookingRepository;
|
||||||
import com.example.nto.repository.EmployeeRepository;
|
import com.example.nto.repository.EmployeeRepository;
|
||||||
import com.example.nto.repository.PlaceRepository;
|
import com.example.nto.repository.PlaceRepository;
|
||||||
@@ -10,6 +14,7 @@ import com.example.nto.service.BookingService;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -49,7 +54,7 @@ public class BookingServiceImpl implements BookingService {
|
|||||||
public boolean isEmployeeExists(String code) {
|
public boolean isEmployeeExists(String code) {
|
||||||
return employeeRepository.findByCode(code).isPresent();
|
return employeeRepository.findByCode(code).isPresent();
|
||||||
}
|
}
|
||||||
public Map<String, List<Booking.AvailablePlaceDto>> getAvailablePlaces(String employeeCode) {
|
public Map<String, List<AvailablePlaceDto>> getAvailablePlaces(String employeeCode) {
|
||||||
// 1. Получаем сотрудника (для контекста)
|
// 1. Получаем сотрудника (для контекста)
|
||||||
Employee employee = employeeRepository.findByCode(employeeCode)
|
Employee employee = employeeRepository.findByCode(employeeCode)
|
||||||
.orElseThrow(() -> new RuntimeException("Employee not found"));
|
.orElseThrow(() -> new RuntimeException("Employee not found"));
|
||||||
@@ -74,7 +79,7 @@ public class BookingServiceImpl implements BookingService {
|
|||||||
.collect(Collectors.groupingBy(Booking::getDate));
|
.collect(Collectors.groupingBy(Booking::getDate));
|
||||||
|
|
||||||
// 6. Формируем итоговый ответ
|
// 6. Формируем итоговый ответ
|
||||||
Map<String, List<Booking.AvailablePlaceDto>> result = new HashMap<>();
|
Map<String, List<AvailablePlaceDto>> result = new HashMap<>();
|
||||||
|
|
||||||
for (LocalDate date : targetDates) {
|
for (LocalDate date : targetDates) {
|
||||||
String dateKey = date.toString(); // Формат: "yyyy-MM-dd"
|
String dateKey = date.toString(); // Формат: "yyyy-MM-dd"
|
||||||
@@ -88,10 +93,10 @@ public class BookingServiceImpl implements BookingService {
|
|||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
// Фильтруем свободные места
|
// Фильтруем свободные места
|
||||||
List<Booking.AvailablePlaceDto> available = allPlaces.stream()
|
List<AvailablePlaceDto> available = allPlaces.stream()
|
||||||
.filter(place -> !occupiedPlaceIds.contains(place.getId()))
|
.filter(place -> !occupiedPlaceIds.contains(place.getId()))
|
||||||
.map(place -> {
|
.map(place -> {
|
||||||
Booking.AvailablePlaceDto dto = new Booking.AvailablePlaceDto();
|
AvailablePlaceDto dto = new AvailablePlaceDto();
|
||||||
dto.setId(place.getId());
|
dto.setId(place.getId());
|
||||||
dto.setPlace(place.getPlace());
|
dto.setPlace(place.getPlace());
|
||||||
return dto;
|
return dto;
|
||||||
@@ -104,49 +109,67 @@ public class BookingServiceImpl implements BookingService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional
|
||||||
public Booking createBooking(String employeeCode, LocalDate date, long placeId) {
|
public Booking createBooking(String employeeCode, LocalDate date, long placeId) {
|
||||||
// Проверяем сотрудника
|
// 1. Проверяем существование сотрудника (401 если нет)
|
||||||
Employee employee = employeeRepository.findByCode(employeeCode)
|
Employee employee = employeeRepository.findByCode(employeeCode)
|
||||||
.orElseThrow(() -> new RuntimeException("Employee not found with code: " + employeeCode));
|
.orElseThrow(() -> new EmployeeNotFoundException("Employee with code '" + employeeCode + "' not found"));
|
||||||
|
|
||||||
// Проверяем место
|
// 2. Проверяем существование места (400 если нет)
|
||||||
Place place = placeRepository.findById(placeId)
|
Place place = placeRepository.findById(placeId)
|
||||||
.orElseThrow(() -> new RuntimeException("Place not found with id: " + placeId));
|
.orElseThrow(() -> new InvalidBookingException("Place with id " + placeId + " not found"));
|
||||||
|
|
||||||
// Проверяем, что дата в пределах 3 дней от сегодня
|
// 3. Валидация даты (400 если невалидно)
|
||||||
LocalDate today = LocalDate.now();
|
validateBookingDate(date);
|
||||||
LocalDate maxDate = today.plusDays(3);
|
|
||||||
|
|
||||||
if (date.isBefore(today)) {
|
// 4. Проверяем, не занято ли место (409 если занято)
|
||||||
throw new RuntimeException("Cannot book in the past. Date: " + date);
|
checkPlaceAvailability(date, placeId);
|
||||||
}
|
|
||||||
|
|
||||||
if (date.isAfter(maxDate)) {
|
// 5. Проверяем, нет ли уже брони у сотрудника на эту дату (409 если есть)
|
||||||
throw new RuntimeException("Can only book up to 3 days in advance. Date: " + date);
|
checkEmployeeBookingConflict(employeeCode, date);
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем, не занято ли уже это место на эту дату
|
// 6. Создаем и сохраняем бронирование
|
||||||
Optional<Booking> existingPlaceBooking = bookingRepository.findByDateAndPlace_Id(date, placeId);
|
|
||||||
if (existingPlaceBooking.isPresent()) {
|
|
||||||
throw new RuntimeException("Place " + placeId + " is already booked for " + date);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем, нет ли у сотрудника уже брони на эту дату
|
|
||||||
List<Booking> employeeBookings = bookingRepository.findByDateAndEmployee_Code(date, employeeCode);
|
|
||||||
if (!employeeBookings.isEmpty()) {
|
|
||||||
throw new RuntimeException("Employee already has a booking on " + date);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаем новое бронирование
|
|
||||||
Booking booking = Booking.builder()
|
Booking booking = Booking.builder()
|
||||||
.date(date)
|
.date(date)
|
||||||
.place(place)
|
.place(place)
|
||||||
.employee(employee)
|
.employee(employee)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Сохраняем в базу
|
|
||||||
return bookingRepository.save(booking);
|
return bookingRepository.save(booking);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateBookingDate(LocalDate date) {
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
LocalDate maxDate = today.plusDays(3);
|
||||||
|
|
||||||
|
if (date == null) {
|
||||||
|
throw new InvalidBookingException("Date is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (date.isBefore(today)) {
|
||||||
|
throw new InvalidBookingException("Cannot book in the past");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (date.isAfter(maxDate)) {
|
||||||
|
throw new InvalidBookingException("Can only book up to 3 days in advance");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPlaceAvailability(LocalDate date, long placeId) {
|
||||||
|
Optional<Booking> existingBooking = bookingRepository.findByDateAndPlace_Id(date, placeId);
|
||||||
|
if (existingBooking.isPresent()) {
|
||||||
|
throw new BookingConflictException("Place " + placeId + " is already booked for " + date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkEmployeeBookingConflict(String employeeCode, LocalDate date) {
|
||||||
|
List<Booking> employeeBookingsOnDate = bookingRepository.findByDateAndEmployee_Code(date, employeeCode);
|
||||||
|
if (!employeeBookingsOnDate.isEmpty()) {
|
||||||
|
throw new BookingConflictException("Employee already has a booking on " + date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user