main #10

Closed
student-20690 wants to merge 26 commits from (deleted):main into main
7 changed files with 160 additions and 49 deletions
Showing only changes of commit 7a0e8b70db - Show all commits

View File

@@ -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();
}
public void setDate(LocalDate date) { } catch (EmployeeNotFoundException e) {
this.date = date; // 401 - кода не существует
} return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
public long getPlaceId() { } catch (BookingConflictException e) {
return placeId; // 409 - конфликт (уже забронировано)
} return ResponseEntity.status(HttpStatus.CONFLICT).build();
public void setPlaceId(long placeId) { } catch (InvalidBookingException e) {
this.placeId = placeId; // 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;
}
} }

View File

@@ -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;
}
} }

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
} }

View File

@@ -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);
}
}
} }