diff --git a/src/main/java/com/example/nto/App.java b/src/main/java/com/example/nto/App.java index e453f89..8dc3056 100644 --- a/src/main/java/com/example/nto/App.java +++ b/src/main/java/com/example/nto/App.java @@ -1,12 +1,18 @@ package com.example.nto; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ +@SpringBootApplication public class App { public static void main(String[] args) { + // Trippi Troppi + SpringApplication.run(App.class, args); } } diff --git a/src/main/java/com/example/nto/controller/EmployeeController.java b/src/main/java/com/example/nto/controller/EmployeeController.java index 47658f9..e2045bb 100644 --- a/src/main/java/com/example/nto/controller/EmployeeController.java +++ b/src/main/java/com/example/nto/controller/EmployeeController.java @@ -1,10 +1,103 @@ package com.example.nto.controller; +import com.example.nto.dto.BookingDetailsDto; +import com.example.nto.dto.CreateBookingRequestDto; +import com.example.nto.dto.UserInfoResponseDto; +import com.example.nto.service.BookingResult; +import com.example.nto.service.EmployeeService; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ +@RestController +@RequestMapping("/api") public class EmployeeController { + private final EmployeeService employeeService; + + @Autowired + public EmployeeController(EmployeeService employeeService) { + this.employeeService = employeeService; + } + + @GetMapping("/{code}/auth") + public ResponseEntity checkAuthorization(@PathVariable("code") String authCode) { + if (authCode == null || authCode.isEmpty()) { + return new ResponseEntity<>("Код не может быть пустым", HttpStatus.BAD_REQUEST); + } + + boolean isValid = employeeService.isAuthCodeValid(authCode); + + if (isValid) { + return new ResponseEntity<>("Авторизация успешна. Можно пользоваться приложением.", HttpStatus.OK); + } else { + return new ResponseEntity<>("Указанный код не существует или недействителен.", HttpStatus.UNAUTHORIZED); + } + } + + @GetMapping("/{code}/info") + public ResponseEntity getUserInfo(@PathVariable("code") String authCode) { + if (authCode == null || authCode.isEmpty()) { + return new ResponseEntity<>("Код не может быть пустым", HttpStatus.BAD_REQUEST); + } + + Optional userInfoOpt = employeeService.getUserInfoByCode(authCode); + + if (userInfoOpt.isPresent()) { + return new ResponseEntity<>(userInfoOpt.get(), HttpStatus.OK); + } else { + return new ResponseEntity<>("Пользователь с таким кодом не найден.", HttpStatus.UNAUTHORIZED); + } + } + + @GetMapping("/{code}/booking") + public ResponseEntity getAvailableBookings(@PathVariable("code") String authCode) { + if (authCode == null || authCode.isEmpty()) { + return new ResponseEntity<>("Код не может быть пустым", HttpStatus.BAD_REQUEST); + } + + Optional>> availableBookings = employeeService.getAvailableBookings(authCode); + + if (availableBookings.isPresent()) { + return new ResponseEntity<>(availableBookings.get(), HttpStatus.OK); + } else { + return new ResponseEntity<>("Пользователь с таким кодом не найден или недействителен.", HttpStatus.UNAUTHORIZED); + } + } + + @PostMapping("/{code}/book") + public ResponseEntity bookPlace(@PathVariable("code") String authCode, + @Valid @RequestBody CreateBookingRequestDto requestDto) { + + if (authCode == null || authCode.isEmpty()) { + return new ResponseEntity<>("Код пользователя не может быть пустым.", HttpStatus.BAD_REQUEST); + } + + BookingResult result = employeeService.createBooking(authCode, requestDto); + + switch (result) { + case SUCCESS: + return new ResponseEntity<>("Бронирование успешно создано.", HttpStatus.CREATED); + case USER_NOT_FOUND: + return new ResponseEntity<>("Пользователь с таким кодом не существует.", HttpStatus.UNAUTHORIZED); + case PLACE_NOT_FOUND: + return new ResponseEntity<>("Указанное место для бронирования не найдено.", HttpStatus.BAD_REQUEST); + case ALREADY_BOOKED: + return new ResponseEntity<>("Данное место уже забронировано на указанную дату.", HttpStatus.CONFLICT); + case BAD_REQUEST: + default: + return new ResponseEntity<>("Некорректный запрос.", HttpStatus.BAD_REQUEST); + } + } } diff --git a/src/main/java/com/example/nto/dto/BookingDetailsDto.java b/src/main/java/com/example/nto/dto/BookingDetailsDto.java new file mode 100644 index 0000000..5156490 --- /dev/null +++ b/src/main/java/com/example/nto/dto/BookingDetailsDto.java @@ -0,0 +1,15 @@ +package com.example.nto.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/// :D + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class BookingDetailsDto { + private Long id; + private String place; +} \ No newline at end of file diff --git a/src/main/java/com/example/nto/dto/CreateBookingRequestDto.java b/src/main/java/com/example/nto/dto/CreateBookingRequestDto.java new file mode 100644 index 0000000..63104c8 --- /dev/null +++ b/src/main/java/com/example/nto/dto/CreateBookingRequestDto.java @@ -0,0 +1,21 @@ +package com.example.nto.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CreateBookingRequestDto { + + @NotNull(message = "Дата бронирования обязательна.") + @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate date; + + @NotNull(message = "ID места бронирования обязательно.") + private Long placeId; +} diff --git a/src/main/java/com/example/nto/dto/UserInfoResponseDto.java b/src/main/java/com/example/nto/dto/UserInfoResponseDto.java new file mode 100644 index 0000000..b56ea1d --- /dev/null +++ b/src/main/java/com/example/nto/dto/UserInfoResponseDto.java @@ -0,0 +1,17 @@ +package com.example.nto.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.util.Map; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserInfoResponseDto { + private String name; + private String photoUrl; + private Map booking; +} diff --git a/src/main/java/com/example/nto/entity/Booking.java b/src/main/java/com/example/nto/entity/Booking.java index 21c1981..0bf73eb 100644 --- a/src/main/java/com/example/nto/entity/Booking.java +++ b/src/main/java/com/example/nto/entity/Booking.java @@ -1,35 +1,38 @@ package com.example.nto.entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.time.LocalDate; // Убедитесь, что у вас правильный импорт даты -import java.time.LocalDate; - - -/** - * TODO: ДОРАБОТАТЬ в рамках задания - * ================================= - * МОЖНО: Добавлять методы, аннотации, зависимости - * НЕЛЬЗЯ: Изменять название класса и пакета - */ @Data @Builder @NoArgsConstructor @AllArgsConstructor +@Entity +@Table(name = "booking") public class Booking { - private long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name = "date") private LocalDate date; - @ManyToOne(targetEntity = Place.class, fetch = FetchType.LAZY) - @JoinColumn(name = "place_id") - private Place place; + @Column(name = "place_id", insertable = false, updatable = false) + private Long placeId; + @Column(name = "employee_id", insertable = false, updatable = false) + private Long employeeId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "employee_id", nullable = false) private Employee employee; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id", nullable = false) + private Place place; } diff --git a/src/main/java/com/example/nto/entity/Employee.java b/src/main/java/com/example/nto/entity/Employee.java index a52102b..068a3c8 100644 --- a/src/main/java/com/example/nto/entity/Employee.java +++ b/src/main/java/com/example/nto/entity/Employee.java @@ -19,14 +19,21 @@ import java.util.List; @Builder @NoArgsConstructor @AllArgsConstructor +@Entity +@Table(name = "employee") public class Employee { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; + @Column(name = "name") private String name; + @Column(name = "code") private String code; + @Column(name = "photo_url") private String photoUrl; @OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, fetch = FetchType.LAZY) diff --git a/src/main/java/com/example/nto/entity/Place.java b/src/main/java/com/example/nto/entity/Place.java index 00c253b..96b81d2 100644 --- a/src/main/java/com/example/nto/entity/Place.java +++ b/src/main/java/com/example/nto/entity/Place.java @@ -1,29 +1,27 @@ package com.example.nto.entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; +import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; - -/** - * TODO: ДОРАБОТАТЬ в рамках задания - * ================================= - * МОЖНО: Добавлять методы, аннотации, зависимости - * НЕЛЬЗЯ: Изменять название класса и пакета - */ @Data @Builder @NoArgsConstructor @AllArgsConstructor +@Entity +@Table(name = "place") public class Place { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private long id; + private Long id; - private String place; + @Column(name = "place_name") + private String placeName; + + @OneToMany(mappedBy = "place", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List bookingList; } diff --git a/src/main/java/com/example/nto/repository/BookingRepository.java b/src/main/java/com/example/nto/repository/BookingRepository.java index 303bb54..a9489b2 100644 --- a/src/main/java/com/example/nto/repository/BookingRepository.java +++ b/src/main/java/com/example/nto/repository/BookingRepository.java @@ -1,10 +1,23 @@ package com.example.nto.repository; +import com.example.nto.entity.Booking; +import com.example.nto.entity.Employee; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ -public interface BookingRepository { -} +public interface BookingRepository extends JpaRepository { + Optional findByDateAndPlaceId(LocalDate date, Long placeId); + List findByDate(LocalDate date); + List findByEmployee(Employee employee); + + List findByDateBetween(LocalDate startDate, LocalDate endDate); +} \ No newline at end of file diff --git a/src/main/java/com/example/nto/repository/EmployeeRepository.java b/src/main/java/com/example/nto/repository/EmployeeRepository.java index 210d29c..07150fb 100644 --- a/src/main/java/com/example/nto/repository/EmployeeRepository.java +++ b/src/main/java/com/example/nto/repository/EmployeeRepository.java @@ -1,10 +1,16 @@ package com.example.nto.repository; +import com.example.nto.entity.Employee; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ -public interface EmployeeRepository { +public interface EmployeeRepository extends JpaRepository { + Optional findByCode(String code); } diff --git a/src/main/java/com/example/nto/repository/PlaceRepository.java b/src/main/java/com/example/nto/repository/PlaceRepository.java index d3bea1d..735318f 100644 --- a/src/main/java/com/example/nto/repository/PlaceRepository.java +++ b/src/main/java/com/example/nto/repository/PlaceRepository.java @@ -1,10 +1,14 @@ package com.example.nto.repository; +import com.example.nto.entity.Place; +import org.springframework.data.jpa.repository.JpaRepository; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ -public interface PlaceRepository { -} +public interface PlaceRepository extends JpaRepository { + +} \ No newline at end of file diff --git a/src/main/java/com/example/nto/service/BookingResult.java b/src/main/java/com/example/nto/service/BookingResult.java new file mode 100644 index 0000000..f5a2c06 --- /dev/null +++ b/src/main/java/com/example/nto/service/BookingResult.java @@ -0,0 +1,9 @@ +package com.example.nto.service; + +public enum BookingResult { + SUCCESS, + USER_NOT_FOUND, + PLACE_NOT_FOUND, + ALREADY_BOOKED, + BAD_REQUEST +} diff --git a/src/main/java/com/example/nto/service/EmployeeService.java b/src/main/java/com/example/nto/service/EmployeeService.java index cccd209..6551a62 100644 --- a/src/main/java/com/example/nto/service/EmployeeService.java +++ b/src/main/java/com/example/nto/service/EmployeeService.java @@ -1,5 +1,13 @@ package com.example.nto.service; +import com.example.nto.dto.BookingDetailsDto; +import com.example.nto.dto.CreateBookingRequestDto; +import com.example.nto.dto.UserInfoResponseDto; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= @@ -7,4 +15,9 @@ package com.example.nto.service; * НЕЛЬЗЯ: Изменять название класса и пакета */ public interface EmployeeService { -} + boolean isAuthCodeValid(String authCode); + + Optional getUserInfoByCode(String authCode); + Optional>> getAvailableBookings(String authCode); + BookingResult createBooking(String authCode, CreateBookingRequestDto requestDto); +} \ No newline at end of file diff --git a/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java b/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java index f8125e5..ba3d59d 100644 --- a/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java +++ b/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java @@ -1,6 +1,24 @@ package com.example.nto.service.impl; +import com.example.nto.dto.BookingDetailsDto; +import com.example.nto.dto.CreateBookingRequestDto; +import com.example.nto.dto.UserInfoResponseDto; +import com.example.nto.entity.Booking; +import com.example.nto.entity.Employee; +import com.example.nto.entity.Place; +import com.example.nto.repository.BookingRepository; +import com.example.nto.repository.EmployeeRepository; +import com.example.nto.repository.PlaceRepository; +import com.example.nto.service.BookingResult; import com.example.nto.service.EmployeeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; /** * TODO: ДОРАБОТАТЬ в рамках задания @@ -8,5 +26,123 @@ import com.example.nto.service.EmployeeService; * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ +@Service public class EmployeeServiceImpl implements EmployeeService { + private final EmployeeRepository employeeRepository; + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private final PlaceRepository placeRepository; + private final BookingRepository bookingRepository; + + @Autowired + public EmployeeServiceImpl(EmployeeRepository employeeRepository, PlaceRepository placeRepository, BookingRepository bookingRepository) { + this.employeeRepository = employeeRepository; + this.placeRepository = placeRepository; + this.bookingRepository = bookingRepository; + } + + @Override + public boolean isAuthCodeValid(String authCode) { + return employeeRepository.findByCode(authCode).isPresent(); + } + + @Override + public Optional getUserInfoByCode(String authCode) { + Optional employeeOpt = employeeRepository.findByCode(authCode); + + if (employeeOpt.isEmpty()) { + return Optional.empty(); + } + + Employee employee = employeeOpt.get(); + + Map bookingMap = new HashMap<>(); + if (employee.getBookingList() != null) { + for (Booking booking : employee.getBookingList()) { + String dateKey = booking.getDate().format(DATE_FORMATTER); + String placeName = (booking.getPlace() != null) ? booking.getPlace().getPlaceName() : "N/A"; + + bookingMap.put(dateKey, new BookingDetailsDto( + booking.getId(), + placeName + )); + } + } + + UserInfoResponseDto dto = UserInfoResponseDto.builder() + .name(employee.getName()) + .photoUrl(employee.getPhotoUrl()) + .booking(bookingMap) + .build(); + + return Optional.of(dto); + } + + @Override + public Optional>> getAvailableBookings(String authCode) { + if (!employeeRepository.findByCode(authCode).isPresent()) { + return Optional.empty(); + } + + LocalDate today = LocalDate.now(); + LocalDate endDate = today.plusDays(3); + + List allPlaces = placeRepository.findAll(); + + List existingBookings = bookingRepository.findByDateBetween(today, endDate); + + Map> bookedPlaceIdsByDate = existingBookings.stream() + .collect(Collectors.groupingBy( + Booking::getDate, + Collectors.mapping(booking -> booking.getPlace().getId(), Collectors.toSet()) + )); + + Map> availableBookingsMap = new LinkedHashMap<>(); + + for (int i = 0; i < 4; i++) { + LocalDate currentDate = today.plusDays(i); + Set bookedIds = bookedPlaceIdsByDate.getOrDefault(currentDate, Collections.emptySet()); + + List availablePlaces = allPlaces.stream() + .filter(place -> !bookedIds.contains(place.getId())) + .map(place -> new BookingDetailsDto(place.getId(), place.getPlaceName())) + .collect(Collectors.toList()); + + availableBookingsMap.put(currentDate.format(DATE_FORMATTER), availablePlaces); + } + + return Optional.of(availableBookingsMap); + } + + @Override + @Transactional + public BookingResult createBooking(String authCode, CreateBookingRequestDto requestDto) { + Optional employeeOpt = employeeRepository.findByCode(authCode); + if (employeeOpt.isEmpty()) { + return BookingResult.USER_NOT_FOUND; + } + Employee employee = employeeOpt.get(); + + Optional placeOpt = placeRepository.findById(requestDto.getPlaceId()); + if (placeOpt.isEmpty()) { + return BookingResult.PLACE_NOT_FOUND; + } + Place place = placeOpt.get(); + + Optional existingBooking = bookingRepository + .findByDateAndPlaceId(requestDto.getDate(), requestDto.getPlaceId()); + + if (existingBooking.isPresent()) { + return BookingResult.ALREADY_BOOKED; + } + + Booking newBooking = Booking.builder() + .date(requestDto.getDate()) + .employee(employee) + .place(place) + .build(); + + bookingRepository.save(newBooking); + + return BookingResult.SUCCESS; + } }