Compare commits

2 Commits

Author SHA1 Message Date
cd2468d689 very important fix
Some checks failed
Android Test / validate-and-test (pull_request) Has been cancelled
2025-12-09 08:55:21 +03:00
b6d8d16a9a meaningful commit
Some checks failed
Android Test / validate-and-test (pull_request) Has been cancelled
2025-12-02 21:07:04 +03:00
14 changed files with 375 additions and 34 deletions

View File

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

View File

@@ -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<String> 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<UserInfoResponseDto> 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<Map<String, List<BookingDetailsDto>>> 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<String> 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);
}
}
}

View File

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

View File

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

View File

@@ -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<String, BookingDetailsDto> booking;
}

View File

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

View File

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

View File

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

View File

@@ -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<Booking, Long> {
Optional<Booking> findByDateAndPlaceId(LocalDate date, Long placeId);
List<Booking> findByDate(LocalDate date);
List<Booking> findByEmployee(Employee employee);
List<Booking> findByDateBetween(LocalDate startDate, LocalDate endDate);
}

View File

@@ -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<Employee, Long> {
Optional<Employee> findByCode(String code);
}

View File

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

View File

@@ -0,0 +1,9 @@
package com.example.nto.service;
public enum BookingResult {
SUCCESS,
USER_NOT_FOUND,
PLACE_NOT_FOUND,
ALREADY_BOOKED,
BAD_REQUEST
}

View File

@@ -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<UserInfoResponseDto> getUserInfoByCode(String authCode);
Optional<Map<String, List<BookingDetailsDto>>> getAvailableBookings(String authCode);
BookingResult createBooking(String authCode, CreateBookingRequestDto requestDto);
}

View File

@@ -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<UserInfoResponseDto> getUserInfoByCode(String authCode) {
Optional<Employee> employeeOpt = employeeRepository.findByCode(authCode);
if (employeeOpt.isEmpty()) {
return Optional.empty();
}
Employee employee = employeeOpt.get();
Map<String, BookingDetailsDto> 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<Map<String, List<BookingDetailsDto>>> getAvailableBookings(String authCode) {
if (!employeeRepository.findByCode(authCode).isPresent()) {
return Optional.empty();
}
LocalDate today = LocalDate.now();
LocalDate endDate = today.plusDays(3);
List<Place> allPlaces = placeRepository.findAll();
List<Booking> existingBookings = bookingRepository.findByDateBetween(today, endDate);
Map<LocalDate, Set<Long>> bookedPlaceIdsByDate = existingBookings.stream()
.collect(Collectors.groupingBy(
Booking::getDate,
Collectors.mapping(booking -> booking.getPlace().getId(), Collectors.toSet())
));
Map<String, List<BookingDetailsDto>> availableBookingsMap = new LinkedHashMap<>();
for (int i = 0; i < 4; i++) {
LocalDate currentDate = today.plusDays(i);
Set<Long> bookedIds = bookedPlaceIdsByDate.getOrDefault(currentDate, Collections.emptySet());
List<BookingDetailsDto> 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<Employee> employeeOpt = employeeRepository.findByCode(authCode);
if (employeeOpt.isEmpty()) {
return BookingResult.USER_NOT_FOUND;
}
Employee employee = employeeOpt.get();
Optional<Place> placeOpt = placeRepository.findById(requestDto.getPlaceId());
if (placeOpt.isEmpty()) {
return BookingResult.PLACE_NOT_FOUND;
}
Place place = placeOpt.get();
Optional<Booking> 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;
}
}