19 Commits

Author SHA1 Message Date
adee20f7c3 Upload files to "src/main/java/com/example/nto/dto" 2025-12-08 12:08:16 +00:00
4304812e5f Upload files to "src/main/java/com/example/nto/dto" 2025-12-08 12:08:05 +00:00
2a9f262b52 Delete src/main/java/com/example/nto/EmployeeInfoResponseDto.java 2025-12-08 12:07:47 +00:00
5bdfb19fd8 Delete src/main/java/com/example/nto/BookingShortDto.java 2025-12-08 12:07:43 +00:00
9b9cd08836 Delete src/main/java/com/example/nto/BookingRequestDto.java 2025-12-08 12:07:38 +00:00
ba04d412e8 Upload files to "src/main/java/com/example/nto" 2025-12-08 12:07:31 +00:00
14f176557e Update src/main/java/com/example/nto/App.java 2025-12-08 12:06:16 +00:00
ffe6d20c58 Update src/main/java/com/example/nto/service/EmployeeService.java 2025-12-08 12:06:06 +00:00
2b83847947 Update src/main/java/com/example/nto/service/BookingService.java 2025-12-08 12:05:57 +00:00
b513cfb282 Update src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java 2025-12-08 12:05:48 +00:00
4cd71ec9ad Update src/main/java/com/example/nto/service/impl/BookingServiceImpl.java 2025-12-08 12:05:39 +00:00
2e2dc5cd1f Update src/main/java/com/example/nto/repository/PlaceRepository.java 2025-12-08 12:05:23 +00:00
f8a1da5df9 Update src/main/java/com/example/nto/repository/EmployeeRepository.java 2025-12-08 12:05:13 +00:00
f870b7991a Update src/main/java/com/example/nto/repository/BookingRepository.java 2025-12-08 12:05:01 +00:00
f5c5f5050e Update src/main/java/com/example/nto/entity/Place.java 2025-12-08 12:04:50 +00:00
98897bc809 Update src/main/java/com/example/nto/entity/Employee.java 2025-12-08 12:04:33 +00:00
631dfbd67d Update src/main/java/com/example/nto/entity/Booking.java 2025-12-08 12:04:19 +00:00
de79bac801 Update src/main/java/com/example/nto/controller/EmployeeController.java 2025-12-08 12:04:05 +00:00
d6553f2888 Update src/main/java/com/example/nto/controller/BookingController.java 2025-12-08 12:03:44 +00:00
16 changed files with 419 additions and 91 deletions

View File

@@ -1,12 +1,11 @@
package com.example.nto; package com.example.nto;
/** import org.springframework.boot.SpringApplication;
* TODO: ДОРАБОТАТЬ в рамках задания import org.springframework.boot.autoconfigure.SpringBootApplication;
* =================================
* МОЖНО: Добавлять методы, аннотации, зависимости @SpringBootApplication
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
public class App { public class App {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(App.class, args);
} }
} }

View File

@@ -1,10 +1,170 @@
package com.example.nto.controller; package com.example.nto.controller;
/** import com.example.nto.dto.BookingRequestDto;
* TODO: ДОРАБОТАТЬ в рамках задания import com.example.nto.dto.BookingShortDto;
* ================================= import com.example.nto.dto.EmployeeInfoResponseDto;
* МОЖНО: Добавлять методы, аннотации, зависимости import com.example.nto.entity.Booking;
* НЕЛЬЗЯ: Изменять название класса и пакета import com.example.nto.entity.Employee;
*/ import com.example.nto.entity.Place;
import com.example.nto.repository.PlaceRepository;
import com.example.nto.service.BookingService;
import com.example.nto.service.EmployeeService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class BookingController { public class BookingController {
private final EmployeeService employeeService;
private final BookingService bookingService;
private final PlaceRepository placeRepository;
/**
* GET api/{code}/info
*
* Ответ (пример из README):
* {
* "name": "Иванов Петр Федорович",
* "photoUrl": "https://...",
* "booking": {
* "2025-01-05": {"id":1,"place":"102"},
* "2025-01-09": {"id":3,"place":"Зона 51. 50"}
* }
* }
*
* 400 что-то пошло не так
* 401 кода не существует
* 200 ОК
*/
@GetMapping("/{code}/info")
public ResponseEntity<?> getInfo(@PathVariable("code") String code) {
if (code == null || code.isBlank()) {
return ResponseEntity.badRequest().build();
}
Optional<Employee> employeeOpt = employeeService.findByCode(code);
if (employeeOpt.isEmpty()) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
Employee employee = employeeOpt.get();
Map<LocalDate, Booking> bookings = bookingService.getBookingsByEmployee(employee);
Map<String, BookingShortDto> bookingMap = new LinkedHashMap<>();
bookings.forEach((date, booking) -> bookingMap.put(
date.toString(),
new BookingShortDto(
booking.getPlace().getId(),
booking.getPlace().getPlace()
)
));
EmployeeInfoResponseDto response = new EmployeeInfoResponseDto(
employee.getName(),
employee.getPhotoUrl(),
bookingMap
);
return ResponseEntity.ok(response);
}
/**
* GET api/{code}/booking
*
* Ответ (пример из README):
* {
* "2025-01-05": [{"id":1,"place":"102"}, {"id":2,"place":"209.13"}],
* "2025-01-06": [{"id":3,"place":"Зона 51. 50"}],
* ...
* }
*
* 400 что-то пошло не так
* 401 кода не существует
* 200 ОК
*/
@GetMapping("/{code}/booking")
public ResponseEntity<?> getAvailable(@PathVariable("code") String code) {
if (code == null || code.isBlank()) {
return ResponseEntity.badRequest().build();
}
if (!employeeService.existsByCode(code)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
Map<LocalDate, List<Place>> available = bookingService.getAvailablePlaces();
Map<String, List<BookingShortDto>> body = new LinkedHashMap<>();
available.forEach((date, places) -> body.put(
date.toString(),
places.stream()
.map(place -> new BookingShortDto(place.getId(), place.getPlace()))
.collect(Collectors.toList())
));
return ResponseEntity.ok(body);
}
/**
* POST api/{code}/book
*
* Тело:
* {
* "date": "2025-01-05",
* "placeId": 1
* }
*
* 400 что-то пошло не так (неправильный формат даты, нет placeId и т.п.)
* 401 кода не существует
* 409 место уже занято или у сотрудника уже есть бронь на эту дату
* 201 бронирование успешно создано
*/
@PostMapping("/{code}/book")
public ResponseEntity<?> createBooking(@PathVariable("code") String code,
@RequestBody BookingRequestDto request) {
if (code == null || code.isBlank()) {
return ResponseEntity.badRequest().build();
}
Optional<Employee> employeeOpt = employeeService.findByCode(code);
if (employeeOpt.isEmpty()) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
if (request == null || request.date() == null || request.placeId() == null) {
return ResponseEntity.badRequest().build();
}
LocalDate date;
try {
date = LocalDate.parse(request.date());
} catch (DateTimeParseException ex) {
return ResponseEntity.badRequest().build();
}
Optional<Place> placeOpt = placeRepository.findById(request.placeId());
if (placeOpt.isEmpty()) {
return ResponseEntity.badRequest().build();
}
Employee employee = employeeOpt.get();
Place place = placeOpt.get();
if (bookingService.existsBookingForEmployeeOnDate(employee, date)
|| bookingService.isPlaceBookedOnDate(place, date)) {
return ResponseEntity.status(HttpStatus.CONFLICT).build(); // 409
}
bookingService.createBooking(employee, date, place);
return ResponseEntity.status(HttpStatus.CREATED).build(); // 201
}
} }

View File

@@ -1,10 +1,37 @@
package com.example.nto.controller; package com.example.nto.controller;
/** import com.example.nto.service.EmployeeService;
* TODO: ДОРАБОТАТЬ в рамках задания import lombok.RequiredArgsConstructor;
* ================================= import org.springframework.http.HttpStatus;
* МОЖНО: Добавлять методы, аннотации, зависимости import org.springframework.http.ResponseEntity;
* НЕЛЬЗЯ: Изменять название класса и пакета import org.springframework.web.bind.annotation.*;
*/
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class EmployeeController { public class EmployeeController {
private final EmployeeService employeeService;
/**
* GET api/{code}/auth
*
* 400 что-то пошло не так (пустой код и т.п.)
* 401 кода не существует
* 200 код существует
*/
@GetMapping("/{code}/auth")
public ResponseEntity<Void> auth(@PathVariable("code") String code) {
if (code == null || code.isBlank()) {
return ResponseEntity.badRequest().build(); // 400
}
boolean exists = employeeService.existsByCode(code);
if (!exists) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); // 401
}
return ResponseEntity.ok().build(); // 200
}
} }

View File

@@ -0,0 +1,11 @@
package com.example.nto.dto;
/**
* Тело запроса для POST /api/{code}/book
* {
* "date": "2025-01-05",
* "placeId": 1
* }
*/
public record BookingRequestDto(String date, Long placeId) {
}

View File

@@ -0,0 +1,4 @@
package com.example.nto.dto;
public record BookingShortDto(long id, String place) {
}

View File

@@ -0,0 +1,10 @@
package com.example.nto.dto;
import java.util.Map;
public record EmployeeInfoResponseDto(
String name,
String photoUrl,
Map<String, BookingShortDto> booking
) {
}

View File

@@ -1,8 +1,6 @@
package com.example.nto.entity; package com.example.nto.entity;
import jakarta.persistence.FetchType; import jakarta.persistence.*;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@@ -11,25 +9,26 @@ import lombok.NoArgsConstructor;
import java.time.LocalDate; import java.time.LocalDate;
/** @Entity
* TODO: ДОРАБОТАТЬ в рамках задания @Table(name = "booking")
* =================================
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
@Data @Data
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class Booking { public class Booking {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id; private long id;
@Column(name = "date")
private LocalDate date; private LocalDate date;
@ManyToOne(targetEntity = Place.class, fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY, targetEntity = Place.class)
@JoinColumn(name = "place_id") @JoinColumn(name = "place_id", nullable = false)
private Place place; private Place place;
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Employee.class)
@JoinColumn(name = "employee_id", nullable = false)
private Employee employee; private Employee employee;
} }

View File

@@ -1,5 +1,6 @@
package com.example.nto.entity; package com.example.nto.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
@@ -8,27 +9,26 @@ import lombok.NoArgsConstructor;
import java.util.List; import java.util.List;
@Entity
/** @Table(name = "employee")
* TODO: ДОРАБОТАТЬ в рамках задания
* =================================
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
@Data @Data
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class Employee { public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id; private long id;
private String name; private String name;
private String code; private String code;
@Column(name = "photo_url")
private String photoUrl; private String photoUrl;
@OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JsonIgnore
private List<Booking> bookingList; private List<Booking> bookingList;
} }

View File

@@ -1,20 +1,13 @@
package com.example.nto.entity; package com.example.nto.entity;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.*;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@Entity
/** @Table(name = "place")
* TODO: ДОРАБОТАТЬ в рамках задания
* =================================
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
@Data @Data
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@@ -25,5 +18,6 @@ public class Place {
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private long id; private long id;
@Column(name = "place_name")
private String place; private String place;
} }

View File

@@ -1,10 +1,26 @@
package com.example.nto.repository; package com.example.nto.repository;
/** import com.example.nto.entity.Booking;
* TODO: ДОРАБОТАТЬ в рамках задания import com.example.nto.entity.Employee;
* ================================= import com.example.nto.entity.Place;
* МОЖНО: Добавлять методы, аннотации, зависимости import org.springframework.data.jpa.repository.JpaRepository;
* НЕЛЬЗЯ: Изменять название класса и пакета import org.springframework.stereotype.Repository;
*/
public interface BookingRepository { import java.time.LocalDate;
import java.util.List;
@Repository
public interface BookingRepository extends JpaRepository<Booking, Long> {
// Все брони сотрудника
List<Booking> findAllByEmployee(Employee employee);
// Все брони на дату
List<Booking> findAllByDate(LocalDate date);
// Есть ли бронь у сотрудника на эту дату
boolean existsByDateAndEmployee(LocalDate date, Employee employee);
// Занято ли место на эту дату
boolean existsByDateAndPlace(LocalDate date, Place place);
} }

View File

@@ -1,10 +1,16 @@
package com.example.nto.repository; package com.example.nto.repository;
/** import com.example.nto.entity.Employee;
* TODO: ДОРАБОТАТЬ в рамках задания import org.springframework.data.jpa.repository.JpaRepository;
* ================================= import org.springframework.stereotype.Repository;
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета import java.util.Optional;
*/
public interface EmployeeRepository {
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
Optional<Employee> findByCode(String code);
boolean existsByCode(String code);
} }

View File

@@ -1,10 +1,10 @@
package com.example.nto.repository; package com.example.nto.repository;
/** import com.example.nto.entity.Place;
* TODO: ДОРАБОТАТЬ в рамках задания import org.springframework.data.jpa.repository.JpaRepository;
* ================================= import org.springframework.stereotype.Repository;
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/ @Repository
public interface PlaceRepository { public interface PlaceRepository extends JpaRepository<Place, Long> {
} }

View File

@@ -1,10 +1,22 @@
package com.example.nto.service; package com.example.nto.service;
/** import com.example.nto.entity.Booking;
* TODO: ДОРАБОТАТЬ в рамках задания import com.example.nto.entity.Employee;
* ================================= import com.example.nto.entity.Place;
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета import java.time.LocalDate;
*/ import java.util.List;
import java.util.Map;
public interface BookingService { public interface BookingService {
Map<LocalDate, Booking> getBookingsByEmployee(Employee employee);
Map<LocalDate, List<Place>> getAvailablePlaces();
boolean existsBookingForEmployeeOnDate(Employee employee, LocalDate date);
boolean isPlaceBookedOnDate(Place place, LocalDate date);
Booking createBooking(Employee employee, LocalDate date, Place place);
} }

View File

@@ -1,10 +1,13 @@
package com.example.nto.service; package com.example.nto.service;
/** import com.example.nto.entity.Employee;
* TODO: ДОРАБОТАТЬ в рамках задания
* ================================= import java.util.Optional;
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
public interface EmployeeService { public interface EmployeeService {
boolean existsByCode(String code);
Optional<Employee> findByCode(String code);
} }

View File

@@ -1,12 +1,85 @@
package com.example.nto.service.impl; package com.example.nto.service.impl;
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.PlaceRepository;
import com.example.nto.service.BookingService; import com.example.nto.service.BookingService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/** import java.time.LocalDate;
* TODO: ДОРАБОТАТЬ в рамках задания import java.util.*;
* ================================= import java.util.stream.Collectors;
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета @Service
*/ @RequiredArgsConstructor
public class BookingServiceImpl implements BookingService { public class BookingServiceImpl implements BookingService {
private final BookingRepository bookingRepository;
private final PlaceRepository placeRepository;
@Value("${booking.days-ahead}")
private int daysAhead;
@Override
public Map<LocalDate, Booking> getBookingsByEmployee(Employee employee) {
List<Booking> bookings = bookingRepository.findAllByEmployee(employee);
return bookings.stream()
.collect(Collectors.toMap(
Booking::getDate,
b -> b,
(b1, b2) -> b1,
TreeMap::new
));
}
@Override
public Map<LocalDate, List<Place>> getAvailablePlaces() {
LocalDate start = LocalDate.now();
List<Place> allPlaces = placeRepository.findAll();
Map<LocalDate, List<Place>> result = new LinkedHashMap<>();
for (int i = 0; i <= daysAhead; i++) {
LocalDate date = start.plusDays(i);
List<Booking> bookingsForDate = bookingRepository.findAllByDate(date);
Set<Long> busyPlaceIds = bookingsForDate.stream()
.map(b -> b.getPlace().getId())
.collect(Collectors.toSet());
List<Place> available = allPlaces.stream()
.filter(p -> !busyPlaceIds.contains(p.getId()))
.collect(Collectors.toList());
result.put(date, available);
}
return result;
}
@Override
public boolean existsBookingForEmployeeOnDate(Employee employee, LocalDate date) {
return bookingRepository.findAllByDate(date).stream()
.anyMatch(b -> b.getEmployee() != null && b.getEmployee().getId() == employee.getId());
}
@Override
public boolean isPlaceBookedOnDate(Place place, LocalDate date) {
return bookingRepository.existsByDateAndPlace(date, place);
}
@Override
public Booking createBooking(Employee employee, LocalDate date, Place place) {
Booking booking = Booking.builder()
.date(date)
.employee(employee)
.place(place)
.build();
return bookingRepository.save(booking);
}
} }

View File

@@ -1,12 +1,26 @@
package com.example.nto.service.impl; package com.example.nto.service.impl;
import com.example.nto.entity.Employee;
import com.example.nto.repository.EmployeeRepository;
import com.example.nto.service.EmployeeService; import com.example.nto.service.EmployeeService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/** import java.util.Optional;
* TODO: ДОРАБОТАТЬ в рамках задания
* ================================= @Service
* МОЖНО: Добавлять методы, аннотации, зависимости @RequiredArgsConstructor
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
public class EmployeeServiceImpl implements EmployeeService { public class EmployeeServiceImpl implements EmployeeService {
private final EmployeeRepository employeeRepository;
@Override
public boolean existsByCode(String code) {
return employeeRepository.existsByCode(code);
}
@Override
public Optional<Employee> findByCode(String code) {
return employeeRepository.findByCode(code);
}
} }