diff --git a/src/main/java/com/example/nto/App.java b/src/main/java/com/example/nto/App.java index e453f89..e5435c1 100644 --- a/src/main/java/com/example/nto/App.java +++ b/src/main/java/com/example/nto/App.java @@ -1,12 +1,17 @@ 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) { + SpringApplication.run(App.class, args); } } diff --git a/src/main/java/com/example/nto/controller/BookingController.java b/src/main/java/com/example/nto/controller/BookingController.java index 9885f84..068fd05 100644 --- a/src/main/java/com/example/nto/controller/BookingController.java +++ b/src/main/java/com/example/nto/controller/BookingController.java @@ -1,10 +1,83 @@ 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 com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +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; +import java.util.List; +import java.util.Map; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ +@RestController +@RequestMapping("/api") public class BookingController { + + private final BookingService bookingService; + + @Autowired + public BookingController(BookingService bookingService) { + this.bookingService = bookingService; + } + + @GetMapping("/{code}/booking") + public ResponseEntity>> getAvailablePlaces( + @PathVariable String code + ) { + try { + if (!bookingService.isEmployeeExists(code)) { + return ResponseEntity + .status(401).build(); + } + Map> response = bookingService.getAvailablePlaces(code); + + return ResponseEntity.ok(response); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + + } + } + @PostMapping("/{code}/book") + public ResponseEntity createBooking( + @PathVariable String code, + @RequestBody Booking.CreateBookingRequest request + ) { + try { + Booking booking = bookingService.createBooking(code, request.getDate(), request.getPlaceId()); + System.out.println("OK"); + return ResponseEntity.status(HttpStatus.CREATED).build(); + + } catch (EmployeeNotFoundException e) { + System.out.println("NOT"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } catch (BookingConflictException e) { + System.out.println("NOT1"); + return ResponseEntity.status(HttpStatus.CONFLICT).build(); + } catch (InvalidBookingException e) { + System.out.println("NOT2"); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + } catch (Exception e) { + System.out.println("NOT3"); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + } + + + + diff --git a/src/main/java/com/example/nto/controller/EmployeeController.java b/src/main/java/com/example/nto/controller/EmployeeController.java index 47658f9..4f157e7 100644 --- a/src/main/java/com/example/nto/controller/EmployeeController.java +++ b/src/main/java/com/example/nto/controller/EmployeeController.java @@ -1,10 +1,52 @@ package com.example.nto.controller; +import com.example.nto.entity.Employee; +import com.example.nto.repository.EmployeeRepository; +import com.example.nto.service.EmployeeService; +import com.example.nto.service.impl.EmployeeServiceImpl; +import org.hibernate.resource.beans.container.spi.BeanLifecycleStrategy; +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.net.http.HttpResponse; +import java.util.List; +import java.util.Map; +import java.util.Optional; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ +@RestController +@RequestMapping("api") public class EmployeeController { + + private final EmployeeService employeeService; + + public EmployeeController(EmployeeService employeeService) { + + this.employeeService = employeeService; + } + + @GetMapping("/{code}/auth") + public ResponseEntity Isauth(@PathVariable String code) { + if (employeeService.isCodeValid(code)) { + return ResponseEntity.ok().build(); + + } else if (!employeeService.isCodeValid(code)){ + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } else { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + } + } + + @GetMapping("/{code}/info") + public ResponseEntity> getEmployeeInfo(@PathVariable String code) { + Map response = employeeService.getEmployeeInfo(code); + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/com/example/nto/entity/Booking.java b/src/main/java/com/example/nto/entity/Booking.java index 21c1981..5bc8a84 100644 --- a/src/main/java/com/example/nto/entity/Booking.java +++ b/src/main/java/com/example/nto/entity/Booking.java @@ -1,8 +1,9 @@ package com.example.nto.entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -21,8 +22,10 @@ import java.time.LocalDate; @Builder @NoArgsConstructor @AllArgsConstructor +@Entity public class Booking { - + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private LocalDate date; @@ -31,5 +34,43 @@ public class Booking { @JoinColumn(name = "place_id") private Place place; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "employee_id") + @JsonBackReference private Employee employee; + + + + @Data + public static class AvailablePlaceDto { + private long id; + private String place; + } + @Data + public static class CreateBookingRequest { + @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate date; + private long placeId; + } + @Data + public static 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/entity/Employee.java b/src/main/java/com/example/nto/entity/Employee.java index a52102b..3ac4e3a 100644 --- a/src/main/java/com/example/nto/entity/Employee.java +++ b/src/main/java/com/example/nto/entity/Employee.java @@ -1,5 +1,7 @@ package com.example.nto.entity; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -19,8 +21,12 @@ import java.util.List; @Builder @NoArgsConstructor @AllArgsConstructor +@Entity +@JsonSerialize public class Employee { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String name; @@ -29,6 +35,16 @@ public class Employee { private String photoUrl; - @OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, fetch = FetchType.LAZY, + targetEntity = Booking.class) + @JsonManagedReference private List bookingList; + + public Employee(long id, String name, String code, String photoUrl) { + this.id = id; + this.name = name; + this.code = code; + this.photoUrl = photoUrl; + } + } diff --git a/src/main/java/com/example/nto/entity/Place.java b/src/main/java/com/example/nto/entity/Place.java index 00c253b..31b55fd 100644 --- a/src/main/java/com/example/nto/entity/Place.java +++ b/src/main/java/com/example/nto/entity/Place.java @@ -1,8 +1,6 @@ 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; @@ -18,12 +16,19 @@ import lombok.NoArgsConstructor; @Data @Builder @NoArgsConstructor -@AllArgsConstructor +@Entity public class Place { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") private long id; + + @Column(name = "place_name") private String place; + + public Place(long id, String place){ + this.id = id; + this.place = place; + } } 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 303bb54..4345e9c 100644 --- a/src/main/java/com/example/nto/repository/BookingRepository.java +++ b/src/main/java/com/example/nto/repository/BookingRepository.java @@ -1,10 +1,29 @@ package com.example.nto.repository; +import com.example.nto.entity.Booking; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Service; + +import java.sql.Date; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ -public interface BookingRepository { +@Service +@Repository +public interface BookingRepository extends JpaRepository { + List findByDateIn(List dates); + + Optional findById(Long id); + + 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/repository/EmployeeRepository.java b/src/main/java/com/example/nto/repository/EmployeeRepository.java index 210d29c..725ab28 100644 --- a/src/main/java/com/example/nto/repository/EmployeeRepository.java +++ b/src/main/java/com/example/nto/repository/EmployeeRepository.java @@ -1,10 +1,21 @@ package com.example.nto.repository; +import com.example.nto.entity.Employee; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Service; + +import java.util.Optional; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ -public interface EmployeeRepository { + +@Service +@Repository +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..638d706 100644 --- a/src/main/java/com/example/nto/repository/PlaceRepository.java +++ b/src/main/java/com/example/nto/repository/PlaceRepository.java @@ -1,10 +1,16 @@ package com.example.nto.repository; +import com.example.nto.entity.Booking; +import com.example.nto.entity.Place; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ -public interface PlaceRepository { +public interface PlaceRepository extends JpaRepository { } diff --git a/src/main/java/com/example/nto/service/BookingService.java b/src/main/java/com/example/nto/service/BookingService.java index 31ec148..7125167 100644 --- a/src/main/java/com/example/nto/service/BookingService.java +++ b/src/main/java/com/example/nto/service/BookingService.java @@ -1,5 +1,13 @@ package com.example.nto.service; +import com.example.nto.entity.Booking; +import com.example.nto.entity.Employee; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Optional; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= @@ -7,4 +15,12 @@ package com.example.nto.service; * НЕЛЬЗЯ: Изменять название класса и пакета */ public interface BookingService { + List getAll(); + Optional getBookingById(Long id); + + Map> getAvailablePlaces(String code); + + boolean isEmployeeExists(String code); + Booking createBooking(String employeeCode, LocalDate date, long placeId); + } diff --git a/src/main/java/com/example/nto/service/EmployeeService.java b/src/main/java/com/example/nto/service/EmployeeService.java index cccd209..32e6980 100644 --- a/src/main/java/com/example/nto/service/EmployeeService.java +++ b/src/main/java/com/example/nto/service/EmployeeService.java @@ -1,5 +1,11 @@ package com.example.nto.service; +import com.example.nto.entity.Employee; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= @@ -7,4 +13,12 @@ package com.example.nto.service; * НЕЛЬЗЯ: Изменять название класса и пакета */ public interface EmployeeService { + List getAll(); + + Optional getByCode(String code); + + + boolean isCodeValid(String code); + + Map getEmployeeInfo(String code); } 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 d24b244..27b21cc 100644 --- a/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java +++ b/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java @@ -1,6 +1,24 @@ 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; 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.*; +import java.util.stream.Collectors; /** * TODO: ДОРАБОТАТЬ в рамках задания @@ -8,5 +26,123 @@ import com.example.nto.service.BookingService; * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ +@Component +@Service public class BookingServiceImpl implements BookingService { + + private final BookingRepository bookingRepository; + private final PlaceRepository placeRepository; + private final EmployeeRepository employeeRepository; + + @Override + public List getAll() { + return bookingRepository.findAll(); + } + + @Override + public Optional getBookingById(Long id) { + return bookingRepository.findById(id); + } + + @Autowired + public BookingServiceImpl(BookingRepository bookingRepository, PlaceRepository placeRepository, + EmployeeRepository employeeRepository) { + this.bookingRepository = bookingRepository; + this.placeRepository = placeRepository; + this.employeeRepository = employeeRepository; + } + public boolean isEmployeeExists(String code) { + return employeeRepository.findByCode(code).isPresent(); + } + public Map> getAvailablePlaces(String employeeCode) { + Employee employee = employeeRepository.findByCode(employeeCode) + .orElseThrow(() -> new RuntimeException("Employee not found")); + LocalDate today = LocalDate.now(); + List targetDates = List.of( + today, + today.plusDays(1), + today.plusDays(2), + today.plusDays(3) + ); + List allPlaces = placeRepository.findAll(); + List bookings = bookingRepository.findByDateIn(targetDates); + Map> bookingsByDate = bookings.stream() + .collect(Collectors.groupingBy(Booking::getDate)); + Map> result = new HashMap<>(); + for (LocalDate date : targetDates) { + String dateKey = date.toString(); // Формат: "yyyy-MM-dd" + List dayBookings = bookingsByDate.getOrDefault(date, List.of()); + Set occupiedPlaceIds = dayBookings.stream() + .map(booking -> booking.getPlace().getId()) + .collect(Collectors.toSet()); + List available = allPlaces.stream() + .filter(place -> !occupiedPlaceIds.contains(place.getId())) + .map(place -> { + AvailablePlaceDto dto = new AvailablePlaceDto(); + dto.setId(place.getId()); + dto.setPlace(place.getPlace()); + return dto; + }) + .collect(Collectors.toList()); + result.put(dateKey, available); + } + + return result; + } + @Override + @Transactional + public Booking createBooking(String employeeCode, LocalDate date, long placeId) { + Employee employee = employeeRepository.findByCode(employeeCode) + .orElseThrow(() -> new EmployeeNotFoundException("Employee with code '" + employeeCode + "' not found")); + Place place = placeRepository.findById(placeId) + .orElseThrow(() -> new InvalidBookingException("Place with id " + placeId + " not found")); + validateBookingDate(date); + checkPlaceAvailability(date, placeId); + checkEmployeeBookingConflict(employeeCode, date); + 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); + } + } + + } + + + + + 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..4a4c7e3 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,20 @@ package com.example.nto.service.impl; +import com.example.nto.entity.Booking; +import com.example.nto.entity.Employee; +import com.example.nto.repository.EmployeeRepository; import com.example.nto.service.EmployeeService; +import jakarta.transaction.Transactional; +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; /** * TODO: ДОРАБОТАТЬ в рамках задания @@ -8,5 +22,51 @@ import com.example.nto.service.EmployeeService; * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ +@Component +@Service public class EmployeeServiceImpl implements EmployeeService { + private final EmployeeRepository employeeRepository; + + @Autowired + public EmployeeServiceImpl(EmployeeRepository employeeRepository) { + this.employeeRepository = employeeRepository; + } + + @Override + public List getAll() { + return employeeRepository.findAll(); + } + + @Override + public Optional getByCode(String code) { + return employeeRepository.findByCode(code); + } + + @Override + public boolean isCodeValid(String code){ + return employeeRepository.findByCode(code).isPresent(); + } + public Map getEmployeeInfo(String code) { + Employee employee = getByCode(code) + .orElseThrow(() -> new RuntimeException("Employee not found")); + + Map> bookingMap = employee.getBookingList().stream() + .collect(Collectors.toMap( + Booking::getDate, + booking -> { + Map info = new HashMap<>(); + info.put("id", booking.getId()); + info.put("place", booking.getPlace().getPlace()); + return info; + } + )); + + Map result = new HashMap<>(); + result.put("name", employee.getName()); + result.put("photoUrl", employee.getPhotoUrl()); + result.put("booking", bookingMap); + + return result; + } + }