main #9

Closed
student-20690 wants to merge 26 commits from (deleted):main into main
16 changed files with 491 additions and 13 deletions

View File

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

View File

@@ -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<Map<String, List<Booking.AvailablePlaceDto>>> getAvailablePlaces(
@PathVariable String code
) {
try {
if (!bookingService.isEmployeeExists(code)) {
return ResponseEntity
.status(401).build();
}
Map<String, List<Booking.AvailablePlaceDto>> 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();
}
}
}

View File

@@ -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<Map<String, Object>> getEmployeeInfo(@PathVariable String code) {
Map<String, Object> response = employeeService.getEmployeeInfo(code);
return ResponseEntity.ok(response);
}
}

View File

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

View File

@@ -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<Booking> bookingList;
public Employee(long id, String name, String code, String photoUrl) {
this.id = id;
this.name = name;
this.code = code;
this.photoUrl = photoUrl;
}
}

View File

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

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

@@ -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<Booking, Long> {
List<Booking> findByDateIn(List<LocalDate> dates);
Optional<Booking> findById(Long id);
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,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<Employee, Long> {
Optional<Employee> findByCode(String code);
}

View File

@@ -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<Place, Long> {
}

View File

@@ -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<Booking> getAll();
Optional<Booking> getBookingById(Long id);
Map<String, List<Booking.AvailablePlaceDto>> getAvailablePlaces(String code);
boolean isEmployeeExists(String code);
Booking createBooking(String employeeCode, LocalDate date, long placeId);
}

View File

@@ -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<Employee> getAll();
Optional<Employee> getByCode(String code);
boolean isCodeValid(String code);
Map<String, Object> getEmployeeInfo(String code);
}

View File

@@ -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<Booking> getAll() {
return bookingRepository.findAll();
}
@Override
public Optional<Booking> 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<String, List<AvailablePlaceDto>> getAvailablePlaces(String employeeCode) {
Employee employee = employeeRepository.findByCode(employeeCode)
.orElseThrow(() -> new RuntimeException("Employee not found"));
LocalDate today = LocalDate.now();
List<LocalDate> targetDates = List.of(
today,
today.plusDays(1),
today.plusDays(2),
today.plusDays(3)
);
List<Place> allPlaces = placeRepository.findAll();
List<Booking> bookings = bookingRepository.findByDateIn(targetDates);
Map<LocalDate, List<Booking>> bookingsByDate = bookings.stream()
.collect(Collectors.groupingBy(Booking::getDate));
Map<String, List<AvailablePlaceDto>> result = new HashMap<>();
for (LocalDate date : targetDates) {
String dateKey = date.toString(); // Формат: "yyyy-MM-dd"
List<Booking> dayBookings = bookingsByDate.getOrDefault(date, List.of());
Set<Long> occupiedPlaceIds = dayBookings.stream()
.map(booking -> booking.getPlace().getId())
.collect(Collectors.toSet());
List<AvailablePlaceDto> 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<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);
}
}
}

View File

@@ -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<Employee> getAll() {
return employeeRepository.findAll();
}
@Override
public Optional<Employee> getByCode(String code) {
return employeeRepository.findByCode(code);
}
@Override
public boolean isCodeValid(String code){
return employeeRepository.findByCode(code).isPresent();
}
public Map<String, Object> getEmployeeInfo(String code) {
Employee employee = getByCode(code)
.orElseThrow(() -> new RuntimeException("Employee not found"));
Map<LocalDate, Map<String, Object>> bookingMap = employee.getBookingList().stream()
.collect(Collectors.toMap(
Booking::getDate,
booking -> {
Map<String, Object> info = new HashMap<>();
info.put("id", booking.getId());
info.put("place", booking.getPlace().getPlace());
return info;
}
));
Map<String, Object> result = new HashMap<>();
result.put("name", employee.getName());
result.put("photoUrl", employee.getPhotoUrl());
result.put("booking", bookingMap);
return result;
}
}