Skip to content

Commit

Permalink
Merge pull request #115 from 4bujak-4bujak/feature/reservation
Browse files Browse the repository at this point in the history
feat: 리차징룸 예약 관련 기능 구현
  • Loading branch information
zoomin3022 authored Jun 8, 2024
2 parents 6c8d6bc + 98f6e7f commit 11e0e92
Show file tree
Hide file tree
Showing 14 changed files with 301 additions and 21 deletions.
6 changes: 6 additions & 0 deletions src/main/java/com/example/sabujak/common/dto/ToastType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.sabujak.common.dto;

public enum ToastType {
OVERLAPPING_MEETING_ROOM_EXISTS,
OVERLAPPING_RECHARGING_ROOM_EXISTS
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,33 @@ public ResponseEntity<Response<Void>> cancelMeetingRoomReservation(@Authenticati
return ResponseEntity.ok(Response.success(null));
}

@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "예약 성공", content = @Content(schema = @Schema(implementation = Response.class))),
@ApiResponse(responseCode = "404", description = "예약 실패", content = @Content(schema = @Schema(implementation = Response.class)))})
@Operation(summary = "리차징룸 예약", description = "리차징룸을 30분동안 예약")
@Parameters({
@Parameter(name = "access", hidden = true)
})
@PostMapping("/recharging-rooms")
public ResponseEntity<Response<Void>> reserveRechargingRoom(@AuthenticationPrincipal AuthRequestDto.Access access,
@Valid @RequestBody ReservationRequestDto.RechargingRoomDto rechargingRoomDto) {
reservationService.reserveRechargingRoom(access.getEmail(), rechargingRoomDto);
return ResponseEntity.ok(Response.success(null));
}

@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "검증 성공", content = @Content(schema = @Schema(implementation = Response.class))),
@ApiResponse(responseCode = "404", description = "검증 실패", content = @Content(schema = @Schema(implementation = Response.class)))})
@Operation(summary = "리차징룸 예약 중복 검증", description = "선택한 시간에 이미 리차징룸이나 미팅룸을 예약 했는지 검증")
@Parameters({
@Parameter(name = "access", hidden = true)
})
@GetMapping("/recharging-rooms/check-overlap")
public ResponseEntity<Response<ReservationResponseDto.CheckRechargingRoomOverlap>> checkRechargingRoomOverlap(@AuthenticationPrincipal AuthRequestDto.Access access,
@RequestParam LocalDateTime startAt) {
return ResponseEntity.ok(Response.success(reservationService.checkRechargingRoomOverlap(access.getEmail(), startAt)));
}

@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "예약 성공", content = @Content(schema = @Schema(implementation = Response.class))),
@ApiResponse(responseCode = "404", description = "예약 실패", content = @Content(schema = @Schema(implementation = Response.class)))})
Expand All @@ -151,9 +178,9 @@ public ResponseEntity<Response<Void>> reserveFocusDesk(@AuthenticationPrincipal
@Parameter(name = "access", hidden = true)
})
@GetMapping("/focus-desks/check-overlap/{focusDeskId}")
public ResponseEntity<Response<ReservationResponseDto.CheckOverlap>> checkOverlap(@AuthenticationPrincipal AuthRequestDto.Access access,
@PathVariable Long focusDeskId) {
return ResponseEntity.ok(Response.success(reservationService.checkOverlap(access.getEmail(), focusDeskId)));
public ResponseEntity<Response<ReservationResponseDto.CheckFocusDeskOverlap>> checkFocusDeskOverlap(@AuthenticationPrincipal AuthRequestDto.Access access,
@PathVariable Long focusDeskId) {
return ResponseEntity.ok(Response.success(reservationService.checkFocusDeskOverlap(access.getEmail(), focusDeskId)));
}

@ApiResponses(value = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.example.sabujak.reservation.entity.Reservation;
import com.example.sabujak.space.entity.FocusDesk;
import com.example.sabujak.space.entity.MeetingRoom;
import com.example.sabujak.space.entity.RechargingRoom;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
Expand Down Expand Up @@ -48,4 +49,21 @@ public Reservation toReservationEntity(FocusDesk focusDesk, LocalDateTime startA
return reservation;
}
}

public record RechargingRoomDto(
@Positive
Long rechargingRoomId,

@Future
LocalDateTime startAt) {

public Reservation toReservationEntity(RechargingRoom rechargingRoom, LocalDateTime startAt, Member member) {
LocalDateTime endAt = startAt.plusMinutes(30);

Reservation reservation = Reservation.createReservation("리차징룸", startAt, endAt, rechargingRoom);
reservation.addMemberReservation(member, MemberReservationType.REPRESENTATIVE);

return reservation;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.sabujak.reservation.dto.response;

import com.example.sabujak.common.dto.ToastType;
import com.example.sabujak.member.entity.Member;
import lombok.Getter;

Expand All @@ -10,7 +11,10 @@

public class ReservationResponseDto {

public record CheckOverlap(Boolean alreadyUsing) {
public record CheckFocusDeskOverlap(Boolean alreadyUsing) {
}

public record CheckRechargingRoomOverlap(ToastType toastType) {
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ public enum ReservationErrorCode implements ErrorCode {
NOT_RESERVED_BY_MEMBER(BAD_REQUEST, "9-004", "해당 회원이 예약한 내역이 아닙니다."),
RESERVATION_NOT_EXISTS(NOT_FOUND, "9-005", "존재하지 않는 예약입니다"),
ALREADY_ENDED_RESERVATION(BAD_REQUEST, "9-006", "이미 종료된 예약입니다."),
ALREADY_CANCELED_RESERVATION(BAD_REQUEST, "9-007", "이미 취소한 예약입니다.");
ALREADY_CANCELED_RESERVATION(BAD_REQUEST, "9-007", "이미 취소한 예약입니다."),
PARTICIPANTS_OVERLAPPING_MEETINGROOM_EXISTS(BAD_REQUEST, "9-008", "참여자의 겹치는 미팅룸 예약이 존재합니다."),
OVERLAPPING_RECHARGING_ROOM_EXISTS(BAD_REQUEST, "9-009", "리차징룸 예약이 겹칩니다.");

private final HttpStatus httpStatus;
private final String customCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.example.sabujak.member.entity.Member;
import com.example.sabujak.reservation.entity.Reservation;
import com.example.sabujak.space.entity.RechargingRoom;

import java.time.LocalDateTime;
import java.util.List;
Expand All @@ -16,11 +17,15 @@ public interface ReservationRepositoryCustom {
List<Reservation> findOverlappingRechargingRoomReservationInMembers(List<Member> members, LocalDateTime startAt, LocalDateTime endAt);



List<Reservation> findTodayFocusDeskReservationOrderByTime(Member member, LocalDateTime startAt);

List<Reservation> findReservationsWithDuration(Member member, LocalDateTime now, int durationStart, int durationEnd);

List<Reservation> findReservationsToday(Member member, LocalDateTime now);
Integer countTodayReservation(Member member, LocalDateTime now);

List<Reservation> findAllByRechargingRoomListAndStartTimes(List<RechargingRoom> rechargingRooms, LocalDateTime startAt, LocalDateTime endAt);

boolean existsOverlappingRechargingRoomReservationByStartAt(Member member, LocalDateTime startAt);
boolean existsOverlappingMeetingRoomReservationsByStartAt(Member member, LocalDateTime startAt);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.example.sabujak.member.entity.Member;
import com.example.sabujak.reservation.entity.Reservation;
import com.example.sabujak.reservation.entity.ReservationStatus;
import com.example.sabujak.space.entity.RechargingRoom;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
Expand Down Expand Up @@ -165,4 +166,39 @@ public Integer countTodayReservation(Member member, LocalDateTime now) {
reservation.reservationStartDateTime.between(startAt, endAt))
.fetchFirst());
}

@Override
public List<Reservation> findAllByRechargingRoomListAndStartTimes(List<RechargingRoom> rechargingRooms, LocalDateTime startAt, LocalDateTime endAt) {
return queryFactory.selectFrom(reservation)
.join(reservation.space, space)
.where(space.in(rechargingRooms),
reservation.reservationStartDateTime.between(startAt, endAt))
.fetch();
}

@Override
public boolean existsOverlappingRechargingRoomReservationByStartAt(Member member, LocalDateTime startAt) {
return queryFactory.selectOne()
.from(reservation)
.join(reservation.memberReservations, memberReservation)
.join(reservation.space, space)
.where(memberReservation.member.eq(member),
memberReservation.memberReservationStatus.eq(ReservationStatus.ACCEPTED),
space.dtype.eq("RechargingRoom"),
reservation.reservationStartDateTime.eq(startAt))
.fetchFirst() != null;
}

@Override
public boolean existsOverlappingMeetingRoomReservationsByStartAt(Member member, LocalDateTime startAt) {
return queryFactory.selectOne()
.from(reservation)
.join(reservation.memberReservations, memberReservation)
.join(reservation.space, space)
.where(memberReservation.member.eq(member),
memberReservation.memberReservationStatus.eq(ReservationStatus.ACCEPTED),
space.dtype.eq("MeetingRoom"),
reservation.reservationStartDateTime.eq(startAt))
.fetchFirst() != null;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.sabujak.reservation.service;

import com.example.sabujak.common.dto.ToastType;
import com.example.sabujak.member.entity.Member;
import com.example.sabujak.member.repository.MemberRepository;
import com.example.sabujak.reservation.dto.FindMeetingRoomEntryNotificationMembersEvent;
Expand All @@ -17,8 +18,10 @@
import com.example.sabujak.security.exception.AuthException;
import com.example.sabujak.space.entity.FocusDesk;
import com.example.sabujak.space.entity.MeetingRoom;
import com.example.sabujak.space.entity.RechargingRoom;
import com.example.sabujak.space.exception.meetingroom.SpaceException;
import com.example.sabujak.space.repository.FocusDeskRepository;
import com.example.sabujak.space.repository.RechargingRoomRepository;
import com.example.sabujak.space.repository.meetingroom.MeetingRoomRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -49,6 +52,7 @@ public class ReservationService {
private final ReservationRepository reservationRepository;
private final MeetingRoomRepository meetingRoomRepository;
private final FocusDeskRepository focusDeskRepository;
private final RechargingRoomRepository rechargingRoomRepository;
private final MemberReservationRepository memberReservationRepository;

private final ApplicationEventPublisher publisher;
Expand Down Expand Up @@ -88,7 +92,7 @@ public void reserveMeetingRoom(String email, ReservationRequestDto.MeetingRoomDt
}
//참여자 미팅룸 예약 검증
else if (verifyOverlappingMeetingRoom(participants, meetingRoomDto.startAt(), meetingRoomDto.endAt())) {
throw new ReservationException(REPRESENTATIVE_OVERLAPPING_MEETINGROOM_EXISTS);
throw new ReservationException(PARTICIPANTS_OVERLAPPING_MEETINGROOM_EXISTS);
}

// 대표자 및 참여자 리차징룸 중복 예약 처리
Expand Down Expand Up @@ -213,7 +217,7 @@ public void endUseFocusDesk(String email, Long spaceId) {
focusDesk.changeCanReserve(true);
}

public ReservationResponseDto.CheckOverlap checkOverlap(String email, Long focusDeskId) {
public ReservationResponseDto.CheckFocusDeskOverlap checkFocusDeskOverlap(String email, Long focusDeskId) {

LocalDateTime now = LocalDateTime.now();

Expand All @@ -228,7 +232,7 @@ public ReservationResponseDto.CheckOverlap checkOverlap(String email, Long focus

// 당일 예약한게 없으면 사용중인 좌석이 없음
if (todayReservations.isEmpty()) {
return new ReservationResponseDto.CheckOverlap(false);
return new ReservationResponseDto.CheckFocusDeskOverlap(false);
}

// 당일 예약 중 가장 최근 좌석을 찾아서
Expand All @@ -237,10 +241,10 @@ public ReservationResponseDto.CheckOverlap checkOverlap(String email, Long focus

// 가장 최근 좌석을 종료하지 않았고 해당 좌석이 예약 종료된 상태가 아니면 사용중인 좌석이 있음
if (todayLatestReservation.getReservationEndDateTime().isAfter(now) && !todayLatestFocusDesk.isCanReserve()) {
return new ReservationResponseDto.CheckOverlap(true);
return new ReservationResponseDto.CheckFocusDeskOverlap(true);
}

return new ReservationResponseDto.CheckOverlap(false);
return new ReservationResponseDto.CheckFocusDeskOverlap(false);
}

public ReservationHistoryResponse.TodayReservationCount getTodayReservationCount(String email) {
Expand Down Expand Up @@ -415,6 +419,53 @@ public void cancelMeetingRoom(String email, Long reservationId) {
}
}

@Transactional
public void reserveRechargingRoom(String email, ReservationRequestDto.RechargingRoomDto rechargingRoomDto) {
final Member member = memberRepository.findByMemberEmail(email)
.orElseThrow(() -> new AuthException(ACCOUNT_NOT_EXISTS));

final RechargingRoom rechargingRoom = rechargingRoomRepository.findById(rechargingRoomDto.rechargingRoomId())
.orElseThrow(() -> new SpaceException(MEETING_ROOM_NOT_FOUND));

//리차징룸 예약 중복 검증
if (verifyOverlappingRechargingRoom(member, rechargingRoomDto.startAt())) {
throw new ReservationException(OVERLAPPING_RECHARGING_ROOM_EXISTS);
}

Reservation reservation = rechargingRoomDto.toReservationEntity(rechargingRoom, rechargingRoomDto.startAt(), member);

reservationRepository.save(reservation);
}

private boolean verifyOverlappingRechargingRoom(Member member, LocalDateTime startAt) {
if (reservationRepository.existsOverlappingRechargingRoomReservationByStartAt(member, startAt)) {
return true;
}
return false;
}

public ReservationResponseDto.CheckRechargingRoomOverlap checkRechargingRoomOverlap(String email, LocalDateTime startAt) {

final Member member = memberRepository.findByMemberEmail(email)
.orElseThrow(() -> new AuthException(ACCOUNT_NOT_EXISTS));


// 해당 회원이 해당 시간에 예약한 미팅룸이 있는지 확인
if (verifyOverlappingMeetingRoom(member, startAt)) {
return new ReservationResponseDto.CheckRechargingRoomOverlap(ToastType.OVERLAPPING_MEETING_ROOM_EXISTS);
} else if (verifyOverlappingRechargingRoom(member, startAt)) {
return new ReservationResponseDto.CheckRechargingRoomOverlap(ToastType.OVERLAPPING_RECHARGING_ROOM_EXISTS);
}
return null;
}

private boolean verifyOverlappingMeetingRoom(Member member, LocalDateTime startAt) {
if (reservationRepository.existsOverlappingMeetingRoomReservationsByStartAt(member, startAt)) {
return true;
}
return false;
}

private ReserveMeetingRoomEvent createReserveMeetingRoomEvent(Reservation reservation, MeetingRoom meetingRoom, List<Member> participants, List<Member> cancelers) {
Long reservationId = reservation.getReservationId();
LocalDateTime reservationDate = reservation.getReservationStartDateTime();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.example.sabujak.space.dto.SpaceCountResponseDto;
import com.example.sabujak.space.dto.response.FocusDeskResponseDto;
import com.example.sabujak.space.dto.response.MeetingRoomResponseDto;
import com.example.sabujak.space.dto.response.RechargingRoomResponseDto;
import com.example.sabujak.space.entity.MeetingRoomType;
import com.example.sabujak.space.service.SpaceService;
import io.swagger.v3.oas.annotations.Operation;
Expand Down Expand Up @@ -98,6 +99,19 @@ public ResponseEntity<Response<FocusDeskResponseDto.AvailableSeatCountInformatio
public ResponseEntity<Response<List<FocusDeskResponseDto.FocusDeskForList>>> getFocusDeskList(@PathVariable Long branchId) {
return ResponseEntity.ok(Response.success(spaceService.getFocusDeskList(branchId)));
}

@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = Response.class))),
@ApiResponse(responseCode = "404", description = "조회 실패", content = @Content(schema = @Schema(implementation = Response.class)))})
@Operation(summary = "리차징룸 리스트", description = "리차징룸 리스트를 30분 단위 5개 시간을 예약 가능 불가능을 표시하여 조회")
@Parameters({
@Parameter(name = "branchId", description = "지점 Id", example = "1")
})
@GetMapping("/recharging-rooms/{branchId}")
public ResponseEntity<Response<List<RechargingRoomResponseDto.RechargingRoomForList>>> getRechargingRoomList(@PathVariable Long branchId) {
return ResponseEntity.ok(Response.success(spaceService.getRechargingRoomList(branchId)));
}

@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회 성공", content = @Content(schema = @Schema(implementation = Response.class))),
@ApiResponse(responseCode = "404", description = "조회 실패", content = @Content(schema = @Schema(implementation = Response.class)))})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.sabujak.space.dto.response;

import com.example.sabujak.branch.entity.Branch;
import com.example.sabujak.common.dto.ToastType;
import com.example.sabujak.space.entity.MeetingRoom;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand All @@ -14,10 +15,6 @@ public class MeetingRoomResponseDto {

@Getter
public static class MeetingRoomList {
public enum ToastType {
OVERLAPPING_MEETING_ROOM_EXISTS,
OVERLAPPING_RECHARGING_ROOM_EXISTS
}

private List<MeetingRoomForList> meetingRoomForListList = new ArrayList<>();
private ToastType toastType;
Expand Down
Loading

0 comments on commit 11e0e92

Please sign in to comment.