From 7a0e8b70dbd5a1cbd696a56e3247db5e4588ef12 Mon Sep 17 00:00:00 2001 From: lynxwq2 Date: Fri, 5 Dec 2025 15:17:31 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BF=D0=BE=D1=87=D1=82=D0=B8=20=D1=80=D0=BE?= =?UTF-8?q?=D0=B1=D0=B8=D1=82(400=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nto/controller/BookingController.java | 56 +++++++++--- .../java/com/example/nto/entity/Booking.java | 28 ++++++ .../exception/BookingConflictException.java | 11 +++ .../exception/EmployeeNotFoundException.java | 12 +++ .../exception/InvalidBookingException.java | 11 +++ .../nto/repository/BookingRepository.java | 6 +- .../nto/service/impl/BookingServiceImpl.java | 85 ++++++++++++------- 7 files changed, 160 insertions(+), 49 deletions(-) create mode 100644 src/main/java/com/example/nto/exception/BookingConflictException.java create mode 100644 src/main/java/com/example/nto/exception/EmployeeNotFoundException.java create mode 100644 src/main/java/com/example/nto/exception/InvalidBookingException.java diff --git a/src/main/java/com/example/nto/controller/BookingController.java b/src/main/java/com/example/nto/controller/BookingController.java index 14a46c7..cc1f76f 100644 --- a/src/main/java/com/example/nto/controller/BookingController.java +++ b/src/main/java/com/example/nto/controller/BookingController.java @@ -1,10 +1,14 @@ package com.example.nto.controller; 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 org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.ErrorResponse; import org.springframework.web.bind.annotation.*; import java.time.LocalDate; @@ -45,24 +49,48 @@ public class BookingController { } } - public static class CreateBookingRequest { - private LocalDate date; - private long placeId; + @PostMapping("/{code}/book") + public ResponseEntity createBooking( + @PathVariable String code, + @RequestBody Booking.CreateBookingRequest request + ) { + try { + Booking booking = bookingService.createBooking( + code, + request.getDate(), + request.getPlaceId() + ); - public LocalDate getDate() { - return date; - } + // Возвращаем только статус 201 без тела + return ResponseEntity.status(HttpStatus.CREATED).build(); - public void setDate(LocalDate date) { - this.date = date; - } + } catch (EmployeeNotFoundException e) { + // 401 - кода не существует + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - public long getPlaceId() { - return placeId; - } + } catch (BookingConflictException e) { + // 409 - конфликт (уже забронировано) + return ResponseEntity.status(HttpStatus.CONFLICT).build(); - public void setPlaceId(long placeId) { - this.placeId = placeId; + } catch (InvalidBookingException e) { + // 400 - невалидные данные + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + + } catch (Exception e) { + // 500 - внутренняя ошибка сервера + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } + + private Booking.BookingResponse convertToResponse(Booking booking) { + 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; + } } + diff --git a/src/main/java/com/example/nto/entity/Booking.java b/src/main/java/com/example/nto/entity/Booking.java index 5eb54d9..0c15862 100644 --- a/src/main/java/com/example/nto/entity/Booking.java +++ b/src/main/java/com/example/nto/entity/Booking.java @@ -1,6 +1,7 @@ package com.example.nto.entity; import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.AllArgsConstructor; @@ -45,4 +46,31 @@ public class Booking { private long id; 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; + } } diff --git a/src/main/java/com/example/nto/exception/BookingConflictException.java b/src/main/java/com/example/nto/exception/BookingConflictException.java new file mode 100644 index 0000000..f3e8c74 --- /dev/null +++ b/src/main/java/com/example/nto/exception/BookingConflictException.java @@ -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); + } +} diff --git a/src/main/java/com/example/nto/exception/EmployeeNotFoundException.java b/src/main/java/com/example/nto/exception/EmployeeNotFoundException.java new file mode 100644 index 0000000..586473e --- /dev/null +++ b/src/main/java/com/example/nto/exception/EmployeeNotFoundException.java @@ -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); + } +} + diff --git a/src/main/java/com/example/nto/exception/InvalidBookingException.java b/src/main/java/com/example/nto/exception/InvalidBookingException.java new file mode 100644 index 0000000..4fe16b2 --- /dev/null +++ b/src/main/java/com/example/nto/exception/InvalidBookingException.java @@ -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); + } +} diff --git a/src/main/java/com/example/nto/repository/BookingRepository.java b/src/main/java/com/example/nto/repository/BookingRepository.java index d88e7e3..4345e9c 100644 --- a/src/main/java/com/example/nto/repository/BookingRepository.java +++ b/src/main/java/com/example/nto/repository/BookingRepository.java @@ -23,9 +23,7 @@ public interface BookingRepository extends JpaRepository { Optional findById(Long id); - Optional findByDateAndPlace_Id(LocalDate date, Long placeId); - - List findByDateAndEmployee_Code(LocalDate date, String employeeCode); - List findByDateInAndEmployee_Code(List dates, String employeeCode); + Optional findByDateAndPlace_Id(LocalDate date, long placeId); + List findByDateAndEmployee_Code(LocalDate date, String employeeCode); } diff --git a/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java b/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java index cbaef40..00daa58 100644 --- a/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java +++ b/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java @@ -1,8 +1,12 @@ package com.example.nto.service.impl; import com.example.nto.entity.Booking; +import com.example.nto.entity.Booking.AvailablePlaceDto; import com.example.nto.entity.Employee; 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.EmployeeRepository; 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.stereotype.Component; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.*; @@ -49,7 +54,7 @@ public class BookingServiceImpl implements BookingService { public boolean isEmployeeExists(String code) { return employeeRepository.findByCode(code).isPresent(); } - public Map> getAvailablePlaces(String employeeCode) { + public Map> getAvailablePlaces(String employeeCode) { // 1. Получаем сотрудника (для контекста) Employee employee = employeeRepository.findByCode(employeeCode) .orElseThrow(() -> new RuntimeException("Employee not found")); @@ -74,7 +79,7 @@ public class BookingServiceImpl implements BookingService { .collect(Collectors.groupingBy(Booking::getDate)); // 6. Формируем итоговый ответ - Map> result = new HashMap<>(); + Map> result = new HashMap<>(); for (LocalDate date : targetDates) { String dateKey = date.toString(); // Формат: "yyyy-MM-dd" @@ -88,10 +93,10 @@ public class BookingServiceImpl implements BookingService { .collect(Collectors.toSet()); // Фильтруем свободные места - List available = allPlaces.stream() + List available = allPlaces.stream() .filter(place -> !occupiedPlaceIds.contains(place.getId())) .map(place -> { - Booking.AvailablePlaceDto dto = new Booking.AvailablePlaceDto(); + AvailablePlaceDto dto = new AvailablePlaceDto(); dto.setId(place.getId()); dto.setPlace(place.getPlace()); return dto; @@ -104,49 +109,67 @@ public class BookingServiceImpl implements BookingService { return result; } @Override + @Transactional public Booking createBooking(String employeeCode, LocalDate date, long placeId) { - // Проверяем сотрудника + // 1. Проверяем существование сотрудника (401 если нет) 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) - .orElseThrow(() -> new RuntimeException("Place not found with id: " + placeId)); + .orElseThrow(() -> new InvalidBookingException("Place with id " + placeId + " not found")); - // Проверяем, что дата в пределах 3 дней от сегодня - LocalDate today = LocalDate.now(); - LocalDate maxDate = today.plusDays(3); + // 3. Валидация даты (400 если невалидно) + validateBookingDate(date); - if (date.isBefore(today)) { - throw new RuntimeException("Cannot book in the past. Date: " + date); - } + // 4. Проверяем, не занято ли место (409 если занято) + checkPlaceAvailability(date, placeId); - if (date.isAfter(maxDate)) { - throw new RuntimeException("Can only book up to 3 days in advance. Date: " + date); - } + // 5. Проверяем, нет ли уже брони у сотрудника на эту дату (409 если есть) + checkEmployeeBookingConflict(employeeCode, date); - // Проверяем, не занято ли уже это место на эту дату - Optional existingPlaceBooking = bookingRepository.findByDateAndPlace_Id(date, placeId); - if (existingPlaceBooking.isPresent()) { - throw new RuntimeException("Place " + placeId + " is already booked for " + date); - } - - // Проверяем, нет ли у сотрудника уже брони на эту дату - List employeeBookings = bookingRepository.findByDateAndEmployee_Code(date, employeeCode); - if (!employeeBookings.isEmpty()) { - throw new RuntimeException("Employee already has a booking on " + date); - } - - // Создаем новое бронирование + // 6. Создаем и сохраняем бронирование Booking booking = Booking.builder() .date(date) .place(place) .employee(employee) .build(); - // Сохраняем в базу 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 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 employeeBookingsOnDate = bookingRepository.findByDateAndEmployee_Code(date, employeeCode); + if (!employeeBookingsOnDate.isEmpty()) { + throw new BookingConflictException("Employee already has a booking on " + date); + } + } + + }