diff --git a/src/main/java/com/core/linkup/club/clubnotice/entity/ClubNotice.java b/src/main/java/com/core/linkup/club/clubnotice/entity/ClubNotice.java index 376c1bb..42db8cf 100644 --- a/src/main/java/com/core/linkup/club/clubnotice/entity/ClubNotice.java +++ b/src/main/java/com/core/linkup/club/clubnotice/entity/ClubNotice.java @@ -21,7 +21,7 @@ public class ClubNotice extends BaseEntity { // board -> notice private Long memberId; private String title; private String content; - + @Enumerated(EnumType.STRING) private NotificationType type; //게시판 or 공지 } diff --git a/src/main/java/com/core/linkup/club/clubnotice/repository/ClubNoticeCustomRepositoryImpl.java b/src/main/java/com/core/linkup/club/clubnotice/repository/ClubNoticeCustomRepositoryImpl.java index 0f3fd5d..1bc99bf 100644 --- a/src/main/java/com/core/linkup/club/clubnotice/repository/ClubNoticeCustomRepositoryImpl.java +++ b/src/main/java/com/core/linkup/club/clubnotice/repository/ClubNoticeCustomRepositoryImpl.java @@ -31,7 +31,7 @@ public Page findAllNotice(Long clubId, Pageable pageable) { List clubNoticeList = queryFactory.selectFrom(clubNotice) .where(clubNotice.clubId.eq(clubId) - .and(clubNotice.type.eq(NotificationType.valueOf("NOTICE")))) + .and(clubNotice.type.eq(NotificationType.NOTICE))) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); @@ -54,7 +54,7 @@ public ClubNoticeResponse findNotice(Long clubId, Long noticeId) { ClubNotice notice = queryFactory.selectFrom(clubNotice) .where(clubNotice.clubId.eq(clubId) .and(clubNotice.id.eq(noticeId)) - .and(clubNotice.type.eq(NotificationType.valueOf("notice")))) + .and(clubNotice.type.eq(NotificationType.NOTICE))) .fetchOne(); if (notice == null) { @@ -70,14 +70,14 @@ public Page findAllBoard(Long clubId, Pageable pageable) { List clubNoticeList = queryFactory.selectFrom(clubNotice) .where(clubNotice.clubId.eq(clubId) - .and(clubNotice.type.eq(NotificationType.valueOf("BOARD")))) + .and(clubNotice.type.eq(NotificationType.BOARD))) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); long total = queryFactory.selectFrom(clubNotice) .where(clubNotice.clubId.eq(clubId) - .and(clubNotice.type.eq(NotificationType.valueOf("BOARD")))) + .and(clubNotice.type.eq(NotificationType.BOARD))) .fetchCount(); return new PageImpl<>(clubNoticeList, pageable, total); @@ -90,7 +90,7 @@ public Optional findBoard(Long clubId, Long noticeId) { ClubNotice notice = queryFactory.selectFrom(clubNotice) .where(clubNotice.clubId.eq(clubId) .and(clubNotice.id.eq(noticeId)) - .and(clubNotice.type.eq(NotificationType.valueOf("BOARD")))) + .and(clubNotice.type.eq(NotificationType.BOARD))) .fetchOne(); return Optional.ofNullable(notice); diff --git a/src/main/java/com/core/linkup/common/config/SecurityConfig.java b/src/main/java/com/core/linkup/common/config/SecurityConfig.java index 03f378d..11bd077 100644 --- a/src/main/java/com/core/linkup/common/config/SecurityConfig.java +++ b/src/main/java/com/core/linkup/common/config/SecurityConfig.java @@ -55,8 +55,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // 카테고리 조회 "/api/v1/category/*", - //소모임 - "/api/v1/club/**").permitAll() + //소모임 - 비로그인 범위 + "/api/v1/club/search").permitAll() .anyRequest().authenticated()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) diff --git a/src/main/java/com/core/linkup/common/exception/GlobalExceptionHandler.java b/src/main/java/com/core/linkup/common/exception/GlobalExceptionHandler.java index 9a971e9..b98c500 100644 --- a/src/main/java/com/core/linkup/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/core/linkup/common/exception/GlobalExceptionHandler.java @@ -2,6 +2,7 @@ import com.core.linkup.common.response.BaseResponse; import com.core.linkup.common.response.BaseResponseStatus; +import jakarta.persistence.OptimisticLockException; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -22,6 +23,17 @@ public ResponseEntity> exception(BaseException exception) { .body(BaseResponse.response(exception.getStatus())); } + @ExceptionHandler(value = OptimisticLockException.class) + public ResponseEntity> optimisticLockException(OptimisticLockException exception) { + log.warn("Optimistic Lock: {}\nClass: {}\nCause: {}", exception.getMessage(), + exception.getEntity().getClass().getName(), + exception.getCause().getMessage()); + + return ResponseEntity + .badRequest() + .body(BaseResponse.response(BaseResponseStatus.CONCURRENCY_CONFLICT)); + } + // 커스텀으로 처리되지 않은 예외 처리 @ExceptionHandler(value = Exception.class) public ResponseEntity> exception(Exception exception) { diff --git a/src/main/java/com/core/linkup/common/response/BaseResponseStatus.java b/src/main/java/com/core/linkup/common/response/BaseResponseStatus.java index c9605ae..342af64 100644 --- a/src/main/java/com/core/linkup/common/response/BaseResponseStatus.java +++ b/src/main/java/com/core/linkup/common/response/BaseResponseStatus.java @@ -15,20 +15,23 @@ public enum BaseResponseStatus { EMAIL_ERROR(false, OK.value(), "메일이 도착하지 않았다면 다시 시도해주세요."), - - // TODO: 개발 진행에 따라 추가될 에정 - EXPIRED_TOKEN(false, UNAUTHORIZED.value(), "만료된 토큰입니다."), INVALID_TOKEN(false, UNAUTHORIZED.value(), "잘못된 토큰입니다."), TOKEN_NOT_FOUND(false, UNAUTHORIZED.value(), "토큰이 존재하지 않습니다."), + INVALID_MEMBER(false, UNAUTHORIZED.value(), "로그인된 사용자와 일치하지 않습니다."), + REQUIRES_CONSENT(false, BAD_REQUEST.value(), "개인정보 수집에 동의해주세요."), DUPLICATE_EMAIL(false, BAD_REQUEST.value(), "이미 가입된 이메일입니다."), UNVERIFIED_EMAIL(false, BAD_REQUEST.value(), "인증되지 않은 이메일입니다."), DUPLICATE_USERNAME(false, BAD_REQUEST.value(), "이미 사용중인 닉네임입니다."), INVALID_PASSWORD(false, BAD_REQUEST.value(), "아이디나 비밀번호가 일치하지 않습니다."), INVALID_AUTHCODE(false, BAD_REQUEST.value(), "인증에 실패했습니다."), - AUTHCODE_ISSUE_ERROR(false, INTERNAL_SERVER_ERROR.value(), "인증코드 발급에 실패했습니다."), + INVALID_RESERVATION_DATE(false, BAD_REQUEST.value(), "예약 날짜가 옳지 않습니다."), + + CONCURRENCY_CONFLICT(false, BAD_REQUEST.value(), "예약 정보가 수정되었습니다. 새로고침 후 다시 시도해주세요."), + + INVALID_REQUEST(false, BAD_REQUEST.value(), "잘못된 요청입니다."), UNREGISTERD_MEMBER(false, NOT_FOUND.value(), "존재하지 않는 사용자입니다."), DOES_NOT_EXIST(false, NOT_FOUND.value(), "요청한 데이터가 존재하지 않습니다."), @@ -48,8 +51,7 @@ public enum BaseResponseStatus { INVALID_MEMBERSHIP(false, NOT_FOUND.value(),"멤버십을 먼저 구매 후에 이용해 주세요"), DUPLICATE_CLUB_LIKE(false, BAD_REQUEST.value(), "이미 좋아요를 눌렀습니다"), - INVALID_REQUEST(false, INTERNAL_SERVER_ERROR.value(), "잘못된 요청입니다."), - INVALID_MEMBER(false, INTERNAL_SERVER_ERROR.value(), "로그인된 사용자와 일치하지 않습니다."), + AUTHCODE_ISSUE_ERROR(false, INTERNAL_SERVER_ERROR.value(), "인증코드 발급에 실패했습니다."), SERVER_ERROR(false, INTERNAL_SERVER_ERROR.value(), "예상하지 못한 서버 에러가 발생했습니다."); diff --git a/src/main/java/com/core/linkup/reservation/reservation/controller/ReservationController.java b/src/main/java/com/core/linkup/reservation/reservation/controller/ReservationController.java index 5691947..a1af2e4 100644 --- a/src/main/java/com/core/linkup/reservation/reservation/controller/ReservationController.java +++ b/src/main/java/com/core/linkup/reservation/reservation/controller/ReservationController.java @@ -1,6 +1,7 @@ package com.core.linkup.reservation.reservation.controller; import com.core.linkup.common.response.BaseResponse; +import com.core.linkup.reservation.reservation.response.MainPageReservationResponse; import com.core.linkup.reservation.reservation.response.MembershipResponse; import com.core.linkup.reservation.reservation.response.SeatSpaceResponse; import com.core.linkup.reservation.reservation.service.MembershipReservationService; @@ -11,6 +12,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import java.time.LocalDate; import java.util.List; @Slf4j @@ -43,4 +45,17 @@ public BaseResponse> getAvailableSeats( return BaseResponse.response( reservationService.getAvailableSeatSpaces(officeId, type, start, startTime, end, endTime)); } + + @GetMapping // (뒤에 yyyy-mm-dd) + public BaseResponse> getMainPageReservations( + @AuthenticationPrincipal MemberDetails memberDetails, + @RequestParam(name = "date", required = false) LocalDate date) { + + if (date == null) { + date = LocalDate.now(); + } + return BaseResponse.response( + membershipReservationService.getAllReservationsOnDate(memberDetails.getMember(), date)); + + } } diff --git a/src/main/java/com/core/linkup/reservation/reservation/converter/ReservationConverter.java b/src/main/java/com/core/linkup/reservation/reservation/converter/ReservationConverter.java index 4321d26..fe0be41 100644 --- a/src/main/java/com/core/linkup/reservation/reservation/converter/ReservationConverter.java +++ b/src/main/java/com/core/linkup/reservation/reservation/converter/ReservationConverter.java @@ -36,13 +36,9 @@ public Reservation toReservationEntity(ReservationRequest request, BaseMembershipEntity membership, SeatSpace seatSpace){ ReservationType reservationType = ReservationType.fromKor(request.getType()); - - LocalDate startDate = LocalDate.parse(request.getStartDate()); - LocalDate endDate = LocalDate.parse(request.getEndDate()); + List dates = getLocalDateTime(request); if (membership.getClass().equals(IndividualMembership.class)) { - List dates = getLocalDateTime(request); - return Reservation.builder() .type(reservationType) .startDate(dates.get(0)) @@ -53,7 +49,6 @@ public Reservation toReservationEntity(ReservationRequest request, .seatId(seatSpace.getId()) .build(); } else { - List dates = getLocalDateTime(request); return Reservation.builder() .type(reservationType) .startDate(dates.get(0)) @@ -118,16 +113,6 @@ public ReservationResponse toReservationResponse( .build(); } - // 좌석 응답 - public SeatSpaceResponse toSeatSpaceResponse(SeatSpace seatSpace, boolean isReserved){ - return SeatSpaceResponse.builder() - .id(seatSpace.getId()) - .type(seatSpace.getType().getTypeName()) - .code(seatSpace.getCode()) - .isAvailable(isReserved) - .build(); - } - // 예약 수정 public Reservation updateOriginalDesignatedReservation(ReservationRequest request, Reservation originalReservation){ return originalReservation.toBuilder() @@ -135,29 +120,44 @@ public Reservation updateOriginalDesignatedReservation(ReservationRequest reques .build(); } + // 에약 수정 public Reservation updateReservation(ReservationRequest request, Reservation originalReservation){ - // 자율 좌석 변경 - if (request.getType().equals(ReservationType.TEMPORARY_SEAT.getName())) { - return originalReservation.toBuilder() - .seatId(request.getSeatId()) - .build(); // 공간 변경 - } else { - LocalDateTime startDate = LocalDateTime.of( - LocalDate.parse(request.getStartDate()), - LocalTime.parse(request.getStartTime())); - LocalDateTime endDate = LocalDateTime.of( - LocalDate.parse(request.getEndDate()), - LocalTime.parse(request.getEndTime())); + if (request.getType().equals(ReservationType.SPACE.getName())) { + List dates = getLocalDateTime(request); + LocalDateTime startDate = dates.get(0); + LocalDateTime endDate = dates.get(1); return originalReservation.toBuilder() .startDate(startDate) .endDate(endDate) .seatId(request.getSeatId()) .price(request.getPrice()) .build(); + } else { + // 자율 좌석 변경 + return originalReservation.toBuilder() + .seatId(request.getSeatId()) + .build(); } } + public MainPageReservationResponse toMainPageReservationResponse( + BaseMembershipEntity membership, Reservation reservation, SeatSpace seatSpace){ + return MainPageReservationResponse.builder() + .membershipType(membership.getType().getName()) + .officeId(membership.getId()) + .location(membership.getLocation()) + .reservationId(reservation.getId()) + .reservationType(reservation.getType().getName()) + .startDate(reservation.getStartDate().toLocalDate()) + .startTime(reservation.getStartDate().toLocalTime()) + .endDate(reservation.getEndDate().toLocalDate()) + .endTime(reservation.getEndDate().toLocalTime()) + .seatType(seatSpace.getType().getTypeName()) + .seatCode(seatSpace.getCode()) + .build(); + } + public ReservationResponse emptyReservationResponse(){ return ReservationResponse.builder().build(); } diff --git a/src/main/java/com/core/linkup/reservation/reservation/entity/Reservation.java b/src/main/java/com/core/linkup/reservation/reservation/entity/Reservation.java index 974a5a8..d777321 100644 --- a/src/main/java/com/core/linkup/reservation/reservation/entity/Reservation.java +++ b/src/main/java/com/core/linkup/reservation/reservation/entity/Reservation.java @@ -30,4 +30,7 @@ public class Reservation extends BaseEntity { private Long companyMembershipId; private Long individualMembershipId; private Long seatId; + + @Version + private int version; } diff --git a/src/main/java/com/core/linkup/reservation/reservation/entity/enums/ReservationType.java b/src/main/java/com/core/linkup/reservation/reservation/entity/enums/ReservationType.java index 8b215a4..acb81c6 100644 --- a/src/main/java/com/core/linkup/reservation/reservation/entity/enums/ReservationType.java +++ b/src/main/java/com/core/linkup/reservation/reservation/entity/enums/ReservationType.java @@ -7,6 +7,7 @@ @Getter public enum ReservationType { + COMPANY_DESIGNATED_SEAT("기업 지정석"), DESIGNATED_SEAT("지정석"), TEMPORARY_SEAT("자율 좌석"), SPACE("공간"); @@ -20,4 +21,4 @@ public static ReservationType fromKor(String name) { } throw new IllegalArgumentException("No matching occupation type for: " + name); } -} +} \ No newline at end of file diff --git a/src/main/java/com/core/linkup/reservation/reservation/repository/ReservationRepositoryCustom.java b/src/main/java/com/core/linkup/reservation/reservation/repository/ReservationRepositoryCustom.java index 5bb3a17..59c2a5a 100644 --- a/src/main/java/com/core/linkup/reservation/reservation/repository/ReservationRepositoryCustom.java +++ b/src/main/java/com/core/linkup/reservation/reservation/repository/ReservationRepositoryCustom.java @@ -2,8 +2,11 @@ import com.core.linkup.office.entity.SeatSpace; +import com.core.linkup.office.entity.enums.SeatSpaceType; +import com.core.linkup.reservation.reservation.entity.Reservation; import com.querydsl.core.Tuple; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -12,8 +15,10 @@ public interface ReservationRepositoryCustom { // 사용자의 전체 개인 멤버십/예약/좌석 조회 List findAllIndividualMembershipAndReservationsAndSeatByMemberId(Long memberId); - // 사용자의 해당 지점 전체 개인 멤버십/예약/좌석 조회 - List findAllReservationsAndSeatAndIndividualMembershipByMemberIdAndOfficeId(Long memberId, Long officeId); + // 사용자의 개인 멤버십 특정 날짜 예약 조회 + List findAllReservationsAndSeatForIndividualMembershipByMemberIdAndDate(Long memberId, LocalDate date); + + List findAllReservationsAndSeatForCompanyMembershipByMemberIdAndDate(Long memberId, LocalDate date); // 특정 개인 멤버십의 전체 예약/좌석 List findAllReservationAndSeatByIndividualMembershipId(Long membershipId, Long memberId); @@ -29,4 +34,8 @@ public interface ReservationRepositoryCustom { // 잔여 좌석 조회 List findAllSeatSpacesByOfficeIdAndType(Long officeId, String type, LocalDateTime start, LocalDateTime end); + + // 잔여 공간 조회 + List findAllReservationsBySeatIdAndDateAndType(Long seatId, LocalDateTime startDate, SeatSpaceType type); + } diff --git a/src/main/java/com/core/linkup/reservation/reservation/repository/ReservationRepositoryImpl.java b/src/main/java/com/core/linkup/reservation/reservation/repository/ReservationRepositoryImpl.java index f7022be..5494826 100644 --- a/src/main/java/com/core/linkup/reservation/reservation/repository/ReservationRepositoryImpl.java +++ b/src/main/java/com/core/linkup/reservation/reservation/repository/ReservationRepositoryImpl.java @@ -1,20 +1,23 @@ package com.core.linkup.reservation.reservation.repository; import com.core.linkup.member.entity.QMember; -import com.core.linkup.office.entity.QOfficeBuilding; import com.core.linkup.office.entity.QSeatSpace; import com.core.linkup.office.entity.SeatSpace; import com.core.linkup.office.entity.enums.SeatSpaceType; import com.core.linkup.reservation.membership.company.entity.QCompanyMembership; import com.core.linkup.reservation.membership.individual.entity.QIndividualMembership; import com.core.linkup.reservation.reservation.entity.QReservation; +import com.core.linkup.reservation.reservation.entity.Reservation; import com.core.linkup.reservation.reservation.entity.enums.ReservationStatus; import com.querydsl.core.Tuple; +import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.List; @Repository @@ -38,21 +41,47 @@ public List findAllIndividualMembershipAndReservationsAndSeatByMemberId(L .fetch(); } - // 해당 지점 개인 멤버십 예약 전체 조회 @Override - public List findAllReservationsAndSeatAndIndividualMembershipByMemberIdAndOfficeId( - Long memberId, Long officeId){ - QOfficeBuilding qOfficeBuilding = QOfficeBuilding.officeBuilding; + public List findAllReservationsAndSeatForIndividualMembershipByMemberIdAndDate( + Long memberId, LocalDate date){ QIndividualMembership qIndividualMembership = QIndividualMembership.individualMembership; QReservation qReservation = QReservation.reservation; QSeatSpace qSeatSpace = QSeatSpace.seatSpace; + LocalDateTime startOfDay = date.atStartOfDay(); + LocalDateTime endOfDay = date.atTime(23, 59, 59); + return jpaQueryFactory.select(qIndividualMembership, qReservation, qSeatSpace) .from(qIndividualMembership) .join(qReservation).on(qIndividualMembership.id.eq(qReservation.individualMembershipId)) .join(qSeatSpace).on(qReservation.seatId.eq(qSeatSpace.id)) - .where(qIndividualMembership.memberId.eq(memberId)) - .where(qIndividualMembership.location.eq(qOfficeBuilding.location)) + .where(qIndividualMembership.memberId.eq(memberId) + .and(qReservation.startDate.loe(endOfDay)) + .and(qReservation.endDate.goe(startOfDay)) + .and(qReservation.status.ne(ReservationStatus.CANCELED))) + .fetch(); + } + + @Override + public List findAllReservationsAndSeatForCompanyMembershipByMemberIdAndDate( + Long memberId, LocalDate date){ + QCompanyMembership qCompanyMembership = QCompanyMembership.companyMembership; + QReservation qReservation = QReservation.reservation; + QSeatSpace qSeatSpace = QSeatSpace.seatSpace; + QMember qMember = QMember.member; + + LocalDateTime startOfDay = date.atStartOfDay(); + LocalDateTime endOfDay = date.atTime(23, 59, 59); + + return jpaQueryFactory.select(qCompanyMembership, qReservation, qSeatSpace) + .from(qCompanyMembership) + .join(qMember).on(qCompanyMembership.id.eq(qMember.companyMembershipId)) + .join(qReservation).on(qCompanyMembership.id.eq(qReservation.companyMembershipId)) + .join(qSeatSpace).on(qReservation.seatId.eq(qSeatSpace.id)) + .where(qMember.id.eq(memberId) + .and(qReservation.startDate.loe(endOfDay)) + .and(qReservation.endDate.goe(startOfDay)) + .and(qReservation.status.ne(ReservationStatus.CANCELED))) .fetch(); } @@ -153,5 +182,25 @@ public List findAllSeatSpacesByOfficeIdAndType( .fetch(); } + // 잔여 공간 조회 + @Override + public List findAllReservationsBySeatIdAndDateAndType( + Long seatId, LocalDateTime startDate, SeatSpaceType type) { + + QReservation qReservation = QReservation.reservation; + QSeatSpace qSeatSpace = QSeatSpace.seatSpace; + + BooleanExpression datePredicate = qReservation.startDate.between( + startDate.toLocalDate().atStartOfDay(), + startDate.toLocalDate().atTime(LocalTime.MAX) + ); + + return jpaQueryFactory.selectFrom(qReservation) + .join(qSeatSpace).on(qReservation.seatId.eq(qSeatSpace.id)) + .where(qReservation.seatId.eq(seatId) + .and(datePredicate) + .and(qSeatSpace.type.eq(type))) + .fetch(); + } } diff --git a/src/main/java/com/core/linkup/reservation/reservation/response/MainPageReservationResponse.java b/src/main/java/com/core/linkup/reservation/reservation/response/MainPageReservationResponse.java new file mode 100644 index 0000000..7c03739 --- /dev/null +++ b/src/main/java/com/core/linkup/reservation/reservation/response/MainPageReservationResponse.java @@ -0,0 +1,25 @@ +package com.core.linkup.reservation.reservation.response; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDate; +import java.time.LocalTime; + +@Builder +@Getter +public class MainPageReservationResponse { + private String membershipType; + private Long officeId; + private String location; + private Long reservationId; + private String reservationType; + private LocalDate startDate; + private LocalTime startTime; + private LocalDate endDate; + private LocalTime endTime; + // private String status; + + private String seatType; + private String seatCode; +} diff --git a/src/main/java/com/core/linkup/reservation/reservation/response/SeatSpaceResponse.java b/src/main/java/com/core/linkup/reservation/reservation/response/SeatSpaceResponse.java index 877d476..08cc996 100644 --- a/src/main/java/com/core/linkup/reservation/reservation/response/SeatSpaceResponse.java +++ b/src/main/java/com/core/linkup/reservation/reservation/response/SeatSpaceResponse.java @@ -3,6 +3,8 @@ import lombok.Builder; import lombok.Getter; +import java.util.List; + @Builder @Getter public class SeatSpaceResponse { @@ -10,4 +12,6 @@ public class SeatSpaceResponse { private String type; private String code; private boolean isAvailable; + private List am; + private List pm; } diff --git a/src/main/java/com/core/linkup/reservation/reservation/service/CompanyMembershipReservationService.java b/src/main/java/com/core/linkup/reservation/reservation/service/CompanyMembershipReservationService.java index 92578c2..bfb31e0 100644 --- a/src/main/java/com/core/linkup/reservation/reservation/service/CompanyMembershipReservationService.java +++ b/src/main/java/com/core/linkup/reservation/reservation/service/CompanyMembershipReservationService.java @@ -3,7 +3,6 @@ import com.core.linkup.common.exception.BaseException; import com.core.linkup.common.response.BaseResponseStatus; import com.core.linkup.member.entity.Member; -import com.core.linkup.office.repository.SeatSpaceRepository; import com.core.linkup.reservation.membership.company.converter.CompanyMembershipConverter; import com.core.linkup.reservation.membership.company.entity.CompanyMembership; import com.core.linkup.reservation.membership.company.repository.CompanyMembershipRepository; @@ -14,14 +13,13 @@ import com.core.linkup.reservation.reservation.repository.ReservationRepository; import com.core.linkup.reservation.reservation.request.CompanyMembershipRegistrationRequest; import com.core.linkup.reservation.reservation.request.ReservationRequest; -import com.core.linkup.reservation.reservation.response.CompanyMembershipRegistrationResponse; -import com.core.linkup.reservation.reservation.response.MembershipReservationListResponse; -import com.core.linkup.reservation.reservation.response.MembershipResponse; -import com.core.linkup.reservation.reservation.response.ReservationResponse; +import com.core.linkup.reservation.reservation.response.*; +import com.querydsl.core.Tuple; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.time.LocalDate; import java.util.List; import java.util.Optional; @@ -31,7 +29,6 @@ public class CompanyMembershipReservationService { private final CompanyMembershipRepository companyMembershipRepository; - private final SeatSpaceRepository seatSpaceRepository; private final ReservationRepository reservationRepository; private final CompanyMembershipService companyMembershipService; @@ -46,7 +43,6 @@ public CompanyMembershipRegistrationResponse registerCompanyMembership(CompanyMe } // 사용자의 단일 기업 멤버십 - // null이면 404 public MembershipResponse getCompanyMembership(Member member) { if (member.getCompanyMembershipId()==null){ // return reservationConverter.emptyMembershipResponse(); @@ -66,7 +62,7 @@ public MembershipReservationListResponse getReservationsForCompanyMembership(Mem companyMembershipRepository.findById(member.getCompanyMembershipId()); if (companyMembership.isPresent()) { List reservationResponses = - reservationService.getReservationResponses(member, companyMembership.get()); + reservationService.getReservationResponsesWithMembership(member, companyMembership.get()); return reservationConverter.toMembershipReservationListResponse( companyMembershipConverter.toMembershipResponse(companyMembership.get()), @@ -110,8 +106,6 @@ public ReservationResponse updateReservation(ReservationRequest request, // (삭제) 개별 예약 삭제 public boolean deleteReservationForCompanyMembership(Member member, Long membershipId, Long reservationId) { Reservation reservation = reservationRepository.findFirstById(reservationId); - CompanyMembership companyMembership = - companyMembershipRepository.findFirstById(membershipId); if (member.getCompanyMembershipId().equals(membershipId)){ reservation.setStatus(ReservationStatus.CANCELED); return true; @@ -119,4 +113,13 @@ public boolean deleteReservationForCompanyMembership(Member member, Long members throw new BaseException(BaseResponseStatus.INVALID_REQUEST); } } + + // 입력된 날짜의 예약 반환 + public List getReservationsForCompanyMembershipOnDate( + Member member, LocalDate date) { + List tuples = + reservationRepository.findAllReservationsAndSeatForCompanyMembershipByMemberIdAndDate( + member.getId(), date); + return reservationService.getMainPageReservationResponseFromTuple(tuples); + } } diff --git a/src/main/java/com/core/linkup/reservation/reservation/service/IndividualMembershipReservationService.java b/src/main/java/com/core/linkup/reservation/reservation/service/IndividualMembershipReservationService.java index 4bf87b9..474c6a3 100644 --- a/src/main/java/com/core/linkup/reservation/reservation/service/IndividualMembershipReservationService.java +++ b/src/main/java/com/core/linkup/reservation/reservation/service/IndividualMembershipReservationService.java @@ -3,9 +3,7 @@ import com.core.linkup.common.exception.BaseException; import com.core.linkup.common.response.BaseResponseStatus; import com.core.linkup.member.entity.Member; -import com.core.linkup.office.entity.OfficeBuilding; import com.core.linkup.office.entity.SeatSpace; -import com.core.linkup.office.repository.OfficeRepository; import com.core.linkup.office.repository.SeatSpaceRepository; import com.core.linkup.reservation.membership.individual.converter.IndividualMembershipConverter; import com.core.linkup.reservation.membership.individual.entity.IndividualMembership; @@ -17,14 +15,17 @@ import com.core.linkup.reservation.reservation.repository.ReservationRepository; import com.core.linkup.reservation.reservation.request.IndividualMembershipRegistrationRequest; import com.core.linkup.reservation.reservation.request.ReservationRequest; +import com.core.linkup.reservation.reservation.response.MainPageReservationResponse; import com.core.linkup.reservation.reservation.response.MembershipReservationListResponse; import com.core.linkup.reservation.reservation.response.MembershipResponse; import com.core.linkup.reservation.reservation.response.ReservationResponse; +import com.querydsl.core.Tuple; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -34,12 +35,12 @@ public class IndividualMembershipReservationService { private final SeatSpaceRepository seatSpaceRepository; - private final OfficeRepository officeRepository; private final ReservationRepository reservationRepository; private final IndividualMembershipRepository individualMembershipRepository; private final IndividualMembershipService individualMembershipService; private final ReservationService reservationService; + private final ReservationValidationService reservationValidationService; private final ReservationConverter reservationConverter; private final IndividualMembershipConverter individualMembershipConverter; @@ -48,7 +49,7 @@ public class IndividualMembershipReservationService { @Transactional public MembershipReservationListResponse registerIndividualMembership( IndividualMembershipRegistrationRequest requests, Member member, Long officeId) { - validateOfficeLocation(requests, officeId); + reservationValidationService.validateOfficeLocation(requests, officeId); IndividualMembership membership = individualMembershipService.saveIndividualMembership(requests.getMembership(), member); List reservationResponses = @@ -58,14 +59,6 @@ public MembershipReservationListResponse registerIndividualMembership( reservationResponses); } - // (검증) 개인 멤버십 요청의 지점과 전달받은 지점 일치하는지 검증 - private void validateOfficeLocation(IndividualMembershipRegistrationRequest requests, Long officeId) { - OfficeBuilding officeBuilding = officeRepository.findFirstById(officeId); - if (!requests.getMembership().getLocation().equals(officeBuilding.getLocation())) { - throw new BaseException(BaseResponseStatus.INVALID_OFFICE_LOCATION); - } - } - // (생성) 개인 멤버십 예약 추가 @Transactional public MembershipReservationListResponse addIndividualReservations( @@ -115,7 +108,7 @@ public List getAllIndividualMembershipsAndRes return memberships.stream() .map(membership -> { List reservationResponses = - reservationService.getReservationResponses(member, membership); + reservationService.getReservationResponsesWithMembership(member, membership); MembershipResponse membershipResponse = individualMembershipConverter.toMembershipResponse(membership); return reservationConverter.toMembershipReservationListResponse( @@ -130,7 +123,7 @@ public List getReservationsForIndividualMembership( Member member, Long individualMembershipId){ IndividualMembership individualMembership = individualMembershipRepository.findFirstById(individualMembershipId); - return reservationService.getReservationResponses(member, individualMembership); + return reservationService.getReservationResponsesWithMembership(member, individualMembership); } // (조회) 개별 예약 조회 @@ -167,8 +160,16 @@ public boolean deleteReservationForIndividualMembership(Member member, Long memb reservation.setStatus(ReservationStatus.CANCELED); return true; } else { - throw new BaseException(BaseResponseStatus.INVALID_REQUEST); + return false; } } + // 해당 날짜에 있는 예약 전부 반환 + public List getReservationsForIndividualMembershipOnDate( + Member member, LocalDate date){ + List tuples = reservationRepository.findAllReservationsAndSeatForIndividualMembershipByMemberIdAndDate( + member.getId(), date); + return reservationService.getMainPageReservationResponseFromTuple(tuples); + } + } diff --git a/src/main/java/com/core/linkup/reservation/reservation/service/MembershipReservationService.java b/src/main/java/com/core/linkup/reservation/reservation/service/MembershipReservationService.java index 17d67e5..ee23d7b 100644 --- a/src/main/java/com/core/linkup/reservation/reservation/service/MembershipReservationService.java +++ b/src/main/java/com/core/linkup/reservation/reservation/service/MembershipReservationService.java @@ -1,12 +1,14 @@ package com.core.linkup.reservation.reservation.service; import com.core.linkup.member.entity.Member; -import com.core.linkup.reservation.reservation.response.MembershipReservationListResponse; +import com.core.linkup.reservation.reservation.response.MainPageReservationResponse; import com.core.linkup.reservation.reservation.response.MembershipResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; @Slf4j @@ -17,17 +19,6 @@ public class MembershipReservationService { private final CompanyMembershipReservationService companyMembershipReservationService; private final IndividualMembershipReservationService individualMembershipReservationService; - public List getAllMyMembershipsAndReservations(Member member){ - List individualResponses = - individualMembershipReservationService.getAllIndividualMembershipsAndReservations( - member); - MembershipReservationListResponse companyResponse = - companyMembershipReservationService.getReservationsForCompanyMembership(member); - - individualResponses.add(companyResponse); - return individualResponses; - } - // 모든 멤버십 목록 public List getAllMyMemberships(Member member) { List individualMembershipResponses = @@ -42,4 +33,16 @@ public List getAllMyMemberships(Member member) { } } + // 해당 날짜로 된 예약 목록 + public List getAllReservationsOnDate( + Member member, LocalDate date) { + List individualReservations = + individualMembershipReservationService.getReservationsForIndividualMembershipOnDate(member, date); + List companyReservations = + companyMembershipReservationService.getReservationsForCompanyMembershipOnDate(member, date); + List responses = new ArrayList<>(individualReservations); + responses.addAll(companyReservations); + + return responses; + } } diff --git a/src/main/java/com/core/linkup/reservation/reservation/service/ReservationService.java b/src/main/java/com/core/linkup/reservation/reservation/service/ReservationService.java index 0e3cbb1..5f1df53 100644 --- a/src/main/java/com/core/linkup/reservation/reservation/service/ReservationService.java +++ b/src/main/java/com/core/linkup/reservation/reservation/service/ReservationService.java @@ -13,6 +13,7 @@ import com.core.linkup.reservation.reservation.entity.enums.ReservationType; import com.core.linkup.reservation.reservation.repository.ReservationRepository; import com.core.linkup.reservation.reservation.request.ReservationRequest; +import com.core.linkup.reservation.reservation.response.MainPageReservationResponse; import com.core.linkup.reservation.reservation.response.ReservationResponse; import com.core.linkup.reservation.reservation.response.SeatSpaceResponse; import com.querydsl.core.Tuple; @@ -20,10 +21,14 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Slf4j @Service @@ -43,7 +48,7 @@ public Reservation saveReservation(ReservationRequest request, } // (조회, 응답 생성) 예약 튜플을 응답 형태로 변환 - public List getReservationResponses(Member member, BaseMembershipEntity membership) { + public List getReservationResponsesWithMembership(Member member, BaseMembershipEntity membership) { if (membership instanceof IndividualMembership){ List tuples = reservationRepository.findAllReservationAndSeatByIndividualMembershipId( @@ -66,6 +71,21 @@ private List getReservationResponsesFromTuple(List t .toList(); } + public List getMainPageReservationResponseFromTuple(List tuples) { + return !tuples.isEmpty() ? + tuples.stream() + .map(tuple -> { + BaseMembershipEntity membership = tuple.get(0, IndividualMembership.class); + Reservation reservation = tuple.get(1, Reservation.class); + SeatSpace seatSpace = tuple.get(2, SeatSpace.class); + return reservationConverter.toMainPageReservationResponse( + membership, reservation, seatSpace); + }) + .toList() : + new ArrayList<>(); + } + + // 예약 저장 후 응답으로 변환 public List createReservationResponses( List requests, BaseMembershipEntity membership) { return requests.stream() @@ -102,10 +122,15 @@ public ReservationResponse getReservationResponseForMembership( } } + // 예약 타입에 따라 예약 수정 + // 기업 지정석 || 지정석 : 기존의 예약 종료일 오늘로 변환 후 상태 CANCELED로 수정, 새로운 예약 객체 생성 + // 자율좌석 || 공간 : 자율좌석은 좌석만 변경, 공간은 시간까지 변경 public ReservationResponse updateReservationByType(ReservationRequest request, Reservation oldReservation, BaseMembershipEntity membership){ - if (oldReservation.getType().equals(ReservationType.DESIGNATED_SEAT)){ + if (oldReservation.getType().equals(ReservationType.DESIGNATED_SEAT) + || oldReservation.getType().equals(ReservationType.COMPANY_DESIGNATED_SEAT)){ + // 기업 지정석이나 지정석 Reservation updatedReservation = reservationConverter.updateOriginalDesignatedReservation(request, oldReservation); reservationRepository.save(updatedReservation); @@ -113,6 +138,7 @@ public ReservationResponse updateReservationByType(ReservationRequest request, SeatSpace seatSpace = seatSpaceRepository.findFirstById(newReservation.getSeatId()); return reservationConverter.toReservationResponse(newReservation, seatSpace); } else { + // 자율 좌석이나 공간 Reservation updatedReservation = reservationConverter.updateReservation( request, oldReservation); reservationRepository.save(updatedReservation); @@ -145,23 +171,82 @@ public List getAvailableSeatSpaces( private List getSeatSpacesFromDate( Long officeId, String type, LocalDateTime startDate, LocalDateTime endDate) { - List allSeatSpaces = - seatSpaceRepository.findAllByOfficeIdAndType(officeId, SeatSpaceType.fromKor(type)); - List availableSeatSpaces = - reservationRepository.findAllSeatSpacesByOfficeIdAndType( - officeId, String.valueOf(SeatSpaceType.fromKor(type)), startDate, endDate); - - // 전체 좌석 리스트와 잔여 좌석 리스트 - // 잔여 좌석 리스트에 있으면 해당 좌석 true - return allSeatSpaces.stream().map( - seatSpace -> { - return SeatSpaceResponse.builder() + + if (type.equals(SeatSpaceType.CONF4.getTypeName()) + || type.equals(SeatSpaceType.CONF8.getTypeName()) + || type.equals(SeatSpaceType.CONFERENCE_ROOM.getTypeName()) + || type.equals(SeatSpaceType.STUDIO.getTypeName())){ + + List responses = new ArrayList<>(); + // 해당 건물의 공간 조회 + List allSeatSpaces = + seatSpaceRepository.findAllByOfficeIdAndType(officeId, SeatSpaceType.fromKor(type)); + + for (SeatSpace seatSpace : allSeatSpaces){ + // 한 공간에 대한 특정 날짜의 예약들 + List reservations = + reservationRepository.findAllReservationsBySeatIdAndDateAndType( + seatSpace.getId(), startDate, SeatSpaceType.fromKor(type)); + + List reservedTimes = reservations.stream() + .flatMap(reservation -> { + LocalTime startTime = reservation.getStartDate().toLocalTime(); + LocalTime endTime = reservation.getEndDate().toLocalTime(); + return Stream.iterate(startTime, time -> time.plusMinutes(30)) + .limit(Duration.between(startTime, endTime).toMinutes() / 30); + }).toList(); + + List am = new ArrayList<>(); + List pm = new ArrayList<>(); + LocalTime time = LocalTime.of(8,0); + + while (time.isBefore(LocalTime.of(21,30))){ + if (!reservedTimes.contains(time)){ + if (time.isBefore(LocalTime.NOON)){ + am.add(time.toString()); + } else { + pm.add(time.toString()); + } + } + time = time.plusMinutes(30); + + } + if (!reservedTimes.contains(LocalTime.of(21,30))){ + pm.add(LocalTime.of(21,30).toString()); + } + + SeatSpaceResponse response = SeatSpaceResponse.builder() + .id(seatSpace.getId()) + .type(seatSpace.getType().getTypeName()) + .code(seatSpace.getCode()) + .isAvailable(true) + .am(am) + .pm(pm) + .build(); + + responses.add(response); + } + + return responses; + + } else { + // 좌석 + List allSeatSpaces = + seatSpaceRepository.findAllByOfficeIdAndType(officeId, SeatSpaceType.fromKor(type)); + List availableSeatSpaces = + reservationRepository.findAllSeatSpacesByOfficeIdAndType( + officeId, String.valueOf(SeatSpaceType.fromKor(type)), startDate, endDate); + + // 전체 좌석 리스트와 잔여 좌석 리스트 + // 잔여 좌석 리스트에 있으면 해당 좌석 true + return allSeatSpaces.stream().map( + seatSpace -> SeatSpaceResponse.builder() .id(seatSpace.getId()) .code(seatSpace.getCode()) .type(seatSpace.getType().getTypeName()) .isAvailable(availableSeatSpaces.contains(seatSpace)) - .build(); - } - ).toList(); + .build() + ).toList(); + } } } \ No newline at end of file diff --git a/src/main/java/com/core/linkup/reservation/reservation/service/ReservationValidationService.java b/src/main/java/com/core/linkup/reservation/reservation/service/ReservationValidationService.java index 2b94ef9..08c388f 100644 --- a/src/main/java/com/core/linkup/reservation/reservation/service/ReservationValidationService.java +++ b/src/main/java/com/core/linkup/reservation/reservation/service/ReservationValidationService.java @@ -1,18 +1,20 @@ package com.core.linkup.reservation.reservation.service; -import com.core.linkup.reservation.membership.individual.request.IndividualMembershipRequest; +import com.core.linkup.common.exception.BaseException; +import com.core.linkup.common.response.BaseResponseStatus; +import com.core.linkup.office.entity.OfficeBuilding; +import com.core.linkup.office.repository.OfficeRepository; import com.core.linkup.reservation.reservation.converter.ReservationConverter; import com.core.linkup.reservation.reservation.repository.ReservationRepository; +import com.core.linkup.reservation.reservation.request.IndividualMembershipRegistrationRequest; import com.core.linkup.reservation.reservation.request.ReservationRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.time.Duration; -import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; -import java.util.ArrayList; import java.util.List; @Slf4j @@ -20,12 +22,19 @@ @RequiredArgsConstructor public class ReservationValidationService { - private final ReservationRepository reservationRepository; + private final OfficeRepository officeRepository; private final ReservationConverter reservationConverter; + // (검증) 개인 멤버십 요청의 지점과 전달받은 지점 일치하는지 검증 + public void validateOfficeLocation(IndividualMembershipRegistrationRequest requests, Long officeId) { + OfficeBuilding officeBuilding = officeRepository.findFirstById(officeId); + if (!requests.getMembership().getLocation().equals(officeBuilding.getLocation())) { + throw new BaseException(BaseResponseStatus.INVALID_OFFICE_LOCATION); + } + } // 예약 날짜 검증 - public boolean validateReservationDate(ReservationRequest request){ + public void validateReservationDate(ReservationRequest request){ // 검증 통과 x 사례 // 1) 시작일자보다 종료일자가 빠른 경우 // 2) 종료일자가 현재 일자보다 빠른 경우 @@ -34,9 +43,11 @@ public boolean validateReservationDate(ReservationRequest request){ LocalDateTime startDate = dates.get(0); LocalDateTime endDate = dates.get(1); - return !startDate.isBefore(endDate) + if (!startDate.isBefore(endDate) || endDate.isBefore(LocalDateTime.now()) - || startDate.isBefore(LocalDateTime.now()); + || startDate.isBefore(LocalDateTime.now())){ + throw new BaseException(BaseResponseStatus.INVALID_RESERVATION_DATE); + } } // 공간 예약 최대 시간 제한 (2시간) @@ -60,4 +71,7 @@ public boolean validateSpaceReservationTime(ReservationRequest request){ // public boolean validateReservationPrice(ReservationRequest request){ // // } + + // 기업 문의 가격 검증 + } diff --git a/src/main/java/com/core/linkup/security/jwt/JwtAuthenticationFilter.java b/src/main/java/com/core/linkup/security/jwt/JwtAuthenticationFilter.java index 06f90c1..45ac197 100644 --- a/src/main/java/com/core/linkup/security/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/core/linkup/security/jwt/JwtAuthenticationFilter.java @@ -87,6 +87,7 @@ public boolean excludeUrls(HttpServletRequest request, request.getRequestURI().equals("/api/v1/member/token")|| request.getRequestURI().equals("/api/v1/reservation/company")|| request.getRequestURI().contains("category")|| + request.getRequestURI().equals("/api/v1/club/search")|| request.getRequestURI().contains("/api/v1/office") ){ filterChain.doFilter(request, response);