diff --git a/src/main/java/com/sumte/guesthouse/controller/GuesthouseController.java b/src/main/java/com/sumte/guesthouse/controller/GuesthouseController.java index 9775b2d..b46bebe 100644 --- a/src/main/java/com/sumte/guesthouse/controller/GuesthouseController.java +++ b/src/main/java/com/sumte/guesthouse/controller/GuesthouseController.java @@ -1,8 +1,11 @@ package com.sumte.guesthouse.controller; +import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -71,7 +74,37 @@ public ApiResponse updateGuesthouse( @RequestBody @Valid GuesthouseRequestDTO.Update dto) { guesthouseCommandService.updateGuesthouse(guesthouseId, dto); return ApiResponse.successWithNoData(); + } + + // @Operation(summary = "홈 화면 게스트하우스 목록 조회 (광고 우선)", description = "게스트하우스 목록을 보여줍니다") + // @GetMapping("/home") + // public ApiResponse> getGuesthousesForHome( + // @ParameterObject + // @PageableDefault(size = 10) Pageable pageable) { + // return ApiResponse.success(guesthouseQueryService.getGuesthousesForHome(pageable)); + // } + + @GetMapping("/home") + public ResponseEntity>> getGuesthousesForHome( + @ParameterObject + @PageableDefault(size = 10) Pageable pageable) { + Slice data = guesthouseQueryService.getGuesthousesForHome(pageable); + ApiResponse> response = ApiResponse.success(data); + return ResponseEntity.ok(response); + } + @PatchMapping("/{guesthouseId}/advertisement/on") + @Operation(summary = "게스트하우스 광고 설정", description = "해당 게스트하우스를 광고 상태로 설정합니다.") + public ApiResponse activateAdvertisement(@PathVariable Long guesthouseId) { + guesthouseCommandService.activateAdvertisement(guesthouseId); + return ApiResponse.successWithNoData(); + } + + @PatchMapping("/{guesthouseId}/advertisement/off") + @Operation(summary = "게스트하우스 광고 해제", description = "해당 게스트하우스를 광고 상태에서 해제합니다.") + public ApiResponse deactivateAdvertisement(@PathVariable Long guesthouseId) { + guesthouseCommandService.deactivateAdvertisement(guesthouseId); + return ApiResponse.successWithNoData(); } @GetMapping("/{guesthouseId}") @@ -102,4 +135,4 @@ public ResponseEntity>> searchGuesthouse( } -} +} \ No newline at end of file diff --git a/src/main/java/com/sumte/guesthouse/converter/GuesthouseConverter.java b/src/main/java/com/sumte/guesthouse/converter/GuesthouseConverter.java index a12717e..9f5ce62 100644 --- a/src/main/java/com/sumte/guesthouse/converter/GuesthouseConverter.java +++ b/src/main/java/com/sumte/guesthouse/converter/GuesthouseConverter.java @@ -6,6 +6,7 @@ import com.sumte.guesthouse.dto.GuesthouseRequestDTO; import com.sumte.guesthouse.dto.GuesthouseResponseDTO; +import com.sumte.guesthouse.entity.AdType; import com.sumte.guesthouse.entity.Guesthouse; import lombok.RequiredArgsConstructor; @@ -36,4 +37,24 @@ public GuesthouseResponseDTO.Update toUpdateResponseDTO(Guesthouse guesthouse, L .optionServices(optionServices) .build(); } + + public GuesthouseResponseDTO.HomeSummary toHomeSummary( + Guesthouse guesthouse, + Double avgScore, + int reviewCount, + String checkInTime, + Long minPrice + ) { + return GuesthouseResponseDTO.HomeSummary.builder() + .guestHouseId(guesthouse.getId()) + .name(guesthouse.getName()) + .addressRegion(guesthouse.getAddressRegion()) + .imageUrl(guesthouse.getImageUrl()) + .averageScore(avgScore) + .reviewCount(reviewCount) + .checkInTime(checkInTime) + .minPrice(minPrice) + .isAd(guesthouse.getAdvertisement() == AdType.AD) + .build(); + } } diff --git a/src/main/java/com/sumte/guesthouse/dto/GuesthouseResponseDTO.java b/src/main/java/com/sumte/guesthouse/dto/GuesthouseResponseDTO.java index d4b0b2d..3df64ac 100644 --- a/src/main/java/com/sumte/guesthouse/dto/GuesthouseResponseDTO.java +++ b/src/main/java/com/sumte/guesthouse/dto/GuesthouseResponseDTO.java @@ -18,6 +18,7 @@ public class GuesthouseResponseDTO { public static class Register { Long id; String name; + String addressRegion; } @Builder @@ -62,4 +63,20 @@ public static class GetHouseResponse { } + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HomeSummary { + private Long guestHouseId; + private String name; + private String addressRegion; + private String imageUrl; + private Double averageScore; + private int reviewCount; + private String checkInTime; + private Long minPrice; + private boolean isAd; + } + } diff --git a/src/main/java/com/sumte/guesthouse/entity/Guesthouse.java b/src/main/java/com/sumte/guesthouse/entity/Guesthouse.java index e3046dc..5d0692f 100644 --- a/src/main/java/com/sumte/guesthouse/entity/Guesthouse.java +++ b/src/main/java/com/sumte/guesthouse/entity/Guesthouse.java @@ -71,4 +71,12 @@ public void setInformation(String information) { this.information = information; } + public void activateAd() { + this.advertisement = AdType.AD; + } + + public void deactivateAd() { + this.advertisement = AdType.NON_AD; + } + } diff --git a/src/main/java/com/sumte/guesthouse/repository/GuesthouseRepository.java b/src/main/java/com/sumte/guesthouse/repository/GuesthouseRepository.java index 49ec874..eb0c592 100644 --- a/src/main/java/com/sumte/guesthouse/repository/GuesthouseRepository.java +++ b/src/main/java/com/sumte/guesthouse/repository/GuesthouseRepository.java @@ -2,7 +2,10 @@ import java.util.Optional; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import com.sumte.guesthouse.entity.Guesthouse; @@ -10,4 +13,17 @@ public interface GuesthouseRepository extends JpaRepository { Guesthouse findByNameAndAddressDetail(String name, String addressDetails); Optional findById(Long id); + + @Query(""" + SELECT g FROM Guesthouse g + LEFT JOIN Review r ON r.room.guesthouse.id = g.id + GROUP BY g.id + ORDER BY + CASE WHEN g.advertisement = 'AD' THEN 0 ELSE 1 END, + COUNT(r) DESC, + COALESCE(AVG(r.score), 0) DESC + """) + + //리뷰는 Page로 했는데 Slice로 수정해도 괜찮을거 같음 (확인 -> 전체 요소수는 필요없으니) + Slice findAllOrderedForHome(Pageable pageable); } diff --git a/src/main/java/com/sumte/guesthouse/service/GuesthouseCommandService.java b/src/main/java/com/sumte/guesthouse/service/GuesthouseCommandService.java index 24ec22c..dbe7197 100644 --- a/src/main/java/com/sumte/guesthouse/service/GuesthouseCommandService.java +++ b/src/main/java/com/sumte/guesthouse/service/GuesthouseCommandService.java @@ -12,4 +12,10 @@ public interface GuesthouseCommandService { GuesthouseResponseDTO.Update updateGuesthouse(Long id, GuesthouseRequestDTO.Update dto); GuesthouseResponseDTO.delete deleteGuesthouse(Long guesthouseId); + + @Transactional + void activateAdvertisement(Long guesthouseId); + + @Transactional + void deactivateAdvertisement(Long guesthouseId); } diff --git a/src/main/java/com/sumte/guesthouse/service/GuesthouseCommandServiceImpl.java b/src/main/java/com/sumte/guesthouse/service/GuesthouseCommandServiceImpl.java index 228a34e..04512f2 100644 --- a/src/main/java/com/sumte/guesthouse/service/GuesthouseCommandServiceImpl.java +++ b/src/main/java/com/sumte/guesthouse/service/GuesthouseCommandServiceImpl.java @@ -154,4 +154,20 @@ public GuesthouseResponseDTO.delete deleteGuesthouse(Long guesthouseId) { } + @Override + @Transactional + public void activateAdvertisement(Long guesthouseId) { + Guesthouse guesthouse = guesthouseRepository.findById(guesthouseId) + .orElseThrow(() -> new SumteException(CommonErrorCode.NOT_EXIST)); + guesthouse.activateAd(); + } + + @Override + @Transactional + public void deactivateAdvertisement(Long guesthouseId) { + Guesthouse guesthouse = guesthouseRepository.findById(guesthouseId) + .orElseThrow(() -> new SumteException(CommonErrorCode.NOT_EXIST)); + guesthouse.deactivateAd(); + } + } diff --git a/src/main/java/com/sumte/guesthouse/service/GuesthouseQueryService.java b/src/main/java/com/sumte/guesthouse/service/GuesthouseQueryService.java index 609b0a7..8ccbbca 100644 --- a/src/main/java/com/sumte/guesthouse/service/GuesthouseQueryService.java +++ b/src/main/java/com/sumte/guesthouse/service/GuesthouseQueryService.java @@ -2,6 +2,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import com.sumte.guesthouse.dto.GuesthousePreviewDTO; import com.sumte.guesthouse.dto.GuesthouseResponseDTO; @@ -12,4 +13,5 @@ public interface GuesthouseQueryService { Page getFilteredGuesthouse(GuesthouseSearchRequestDTO dto, Pageable pageable); + Slice getGuesthousesForHome(Pageable pageable); } diff --git a/src/main/java/com/sumte/guesthouse/service/GuesthouseQueryServiceImpl.java b/src/main/java/com/sumte/guesthouse/service/GuesthouseQueryServiceImpl.java index 598363f..f8040d8 100644 --- a/src/main/java/com/sumte/guesthouse/service/GuesthouseQueryServiceImpl.java +++ b/src/main/java/com/sumte/guesthouse/service/GuesthouseQueryServiceImpl.java @@ -1,14 +1,17 @@ package com.sumte.guesthouse.service; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import com.sumte.apiPayload.code.error.CommonErrorCode; import com.sumte.apiPayload.exception.SumteException; +import com.sumte.guesthouse.converter.GuesthouseConverter; import com.sumte.guesthouse.dto.GuesthousePreviewDTO; import com.sumte.guesthouse.dto.GuesthouseResponseDTO; import com.sumte.guesthouse.dto.GuesthouseSearchRequestDTO; @@ -17,7 +20,9 @@ import com.sumte.guesthouse.repository.GuesthouseRepository; import com.sumte.guesthouse.repository.GuesthouseRepositoryCustom; import com.sumte.guesthouse.repository.GuesthouseTargetAudienceRepository; +import com.sumte.review.repository.ReviewRepository; import com.sumte.room.dto.RoomResponseDTO; +import com.sumte.room.repository.RoomRepository; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; @@ -25,9 +30,14 @@ @Service @RequiredArgsConstructor public class GuesthouseQueryServiceImpl implements GuesthouseQueryService { + private final GuesthouseRepository guesthouseRepository; + private final RoomRepository roomRepository; + private final ReviewRepository reviewRepository; + private final GuesthouseConverter guesthouseConverter; private final GuesthouseTargetAudienceRepository guesthouseTargetAudienceRepository; private final GuesthouseOptionServicesRepository guesthouseOptionServicesRepository; + private final GuesthouseRepositoryCustom guesthouseRepositoryCustom; @Override @Transactional @@ -67,13 +77,23 @@ public GuesthouseResponseDTO.GetHouseResponse getHouseById(Long id) { .build(); } - private final GuesthouseRepositoryCustom guesthouseRepositoryCustom; + @Override + @Transactional + public Slice getGuesthousesForHome(Pageable pageable) { + Slice guesthouses = guesthouseRepository.findAllOrderedForHome(pageable); + + return guesthouses.map(gh -> guesthouseConverter.toHomeSummary( + gh, + Optional.ofNullable(reviewRepository.findAverageScoreByGuesthouseId(gh.getId())).orElse(0.0), + reviewRepository.countByGuesthouseId(gh.getId()), + Optional.ofNullable(roomRepository.findEarliestCheckinByGuesthouseId(gh.getId())).orElse("00:00"), + Optional.ofNullable(roomRepository.findMinPriceByGuesthouseId(gh.getId())).orElse(0L) + )); + } @Override @Transactional public Page getFilteredGuesthouse(GuesthouseSearchRequestDTO dto, Pageable pageable) { - return guesthouseRepositoryCustom.searchFiltered(dto, pageable); } - -} +} \ No newline at end of file diff --git a/src/main/java/com/sumte/review/repository/ReviewRepository.java b/src/main/java/com/sumte/review/repository/ReviewRepository.java index 6491f6c..214bd84 100644 --- a/src/main/java/com/sumte/review/repository/ReviewRepository.java +++ b/src/main/java/com/sumte/review/repository/ReviewRepository.java @@ -5,6 +5,8 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import com.sumte.review.entity.Review; @@ -19,5 +21,13 @@ public interface ReviewRepository extends JpaRepository { //내가 작성한 리뷰 조회 (단순 내가 작성한 리뷰만 조회하는거라 넣었는데 다시 확인(중복) ) Page findAllByUserId(Long userId, Pageable pageable); + //홈화면 + @Query("SELECT AVG(r.score) FROM Review r WHERE r.room.guesthouse.id = :guesthouseId") + Double findAverageScoreByGuesthouseId(@Param("guesthouseId") Long guesthouseId); + + @Query("SELECT COUNT(r) FROM Review r WHERE r.room.guesthouse.id = :guesthouseId") + int countByGuesthouseId(@Param("guesthouseId") Long guesthouseId); + // int countByRoomGuesthouseId(Long guesthouseId); + boolean existsByUserIdAndRoomGuesthouseId(Long userId, Long roomGuesthouseId); } \ No newline at end of file diff --git a/src/main/java/com/sumte/room/controller/RoomController.java b/src/main/java/com/sumte/room/controller/RoomController.java index d61fc63..db8be29 100644 --- a/src/main/java/com/sumte/room/controller/RoomController.java +++ b/src/main/java/com/sumte/room/controller/RoomController.java @@ -1,5 +1,9 @@ package com.sumte.room.controller; +import java.time.LocalDate; +import java.util.List; + +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -8,6 +12,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.sumte.apiPayload.ApiResponse; @@ -86,4 +91,17 @@ public ResponseEntity> getRoom( return ResponseEntity.ok(ApiResponse.success(result)); } + @GetMapping("/guesthouse/{guesthouseId}/rooms") + // @Operation(summary = "특정 게스트하우스의 객실 목록 조회", description = "선택한 날짜 기준 예약 가능한 객실만 필터링하거나 전체 보여줄 수 있습니다.") + @Operation( + summary = "특정 게스트하우스의 객실 목록 조회", description = "선택한 날짜 기준으로 예약 가능한 객실만 필터링하거나 전체를 조회할 수 있습니다.\n각 방마다 예약 가능 여부가 포함되어 응답됩니다." + ) + public ApiResponse> getRoomsByGuesthouse( + @PathVariable Long guesthouseId, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate + ) { + return ApiResponse.success(roomQueryService.getRoomsByGuesthouse(guesthouseId, startDate, endDate)); + } + } diff --git a/src/main/java/com/sumte/room/converter/RoomConverter.java b/src/main/java/com/sumte/room/converter/RoomConverter.java index 5caccae..e8df474 100644 --- a/src/main/java/com/sumte/room/converter/RoomConverter.java +++ b/src/main/java/com/sumte/room/converter/RoomConverter.java @@ -37,4 +37,18 @@ public RoomResponseDTO.Updated toUpdateEntity(Room room) { .build(); } + public RoomResponseDTO.RoomSummary toRoomSummary(Room room, boolean isReservable) { + return RoomResponseDTO.RoomSummary.builder() + .id(room.getId()) + .name(room.getName()) + .price(room.getPrice()) + .imageUrl(room.getImageUrl()) + .standardCount(room.getStandardCount()) + .totalCount(room.getTotalCount()) + .checkin(room.getCheckin()) + .checkout(room.getCheckout()) + .isReservable(isReservable) + .build(); + } + } diff --git a/src/main/java/com/sumte/room/dto/RoomResponseDTO.java b/src/main/java/com/sumte/room/dto/RoomResponseDTO.java index 8c0d02a..9009378 100644 --- a/src/main/java/com/sumte/room/dto/RoomResponseDTO.java +++ b/src/main/java/com/sumte/room/dto/RoomResponseDTO.java @@ -49,4 +49,20 @@ public static class GetRoomResponse { String imageUrl; } + @NoArgsConstructor + @Getter + @AllArgsConstructor + @Builder + public static class RoomSummary { + private Long id; + private String name; + private Long price; + private String imageUrl; + private Long standardCount; + private Long totalCount; + private LocalTime checkin; + private LocalTime checkout; + private boolean isReservable; + } + } diff --git a/src/main/java/com/sumte/room/repository/RoomRepository.java b/src/main/java/com/sumte/room/repository/RoomRepository.java index 2b1ff83..bb6336e 100644 --- a/src/main/java/com/sumte/room/repository/RoomRepository.java +++ b/src/main/java/com/sumte/room/repository/RoomRepository.java @@ -1,13 +1,28 @@ package com.sumte.room.repository; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import com.sumte.room.entity.Room; public interface RoomRepository extends JpaRepository { - Optional findRoomByGuesthouseIdAndName(Long guesthouseId, String name); + Optional findRoomByGuesthouseIdAndName(Long guesthouseId, String name); + + Optional findById(Long roomId); + + //홈화면 + @Query("SELECT MIN(r.price) FROM Room r WHERE r.guesthouse.id = :guesthouseId") + Long findMinPriceByGuesthouseId(@Param("guesthouseId") Long guesthouseId); + + @Query("SELECT MIN(FUNCTION('TIME_FORMAT', r.checkin, '%H:%i')) " + + "FROM Room r WHERE r.guesthouse.id = :guesthouseId") + String findEarliestCheckinByGuesthouseId(@Param("guesthouseId") Long guesthouseId); + + //객실 조회 + List findAllByGuesthouseId(Long guesthouseId); - Optional findById(Long roomId); } \ No newline at end of file diff --git a/src/main/java/com/sumte/room/service/RoomQueryService.java b/src/main/java/com/sumte/room/service/RoomQueryService.java index 0346d09..afbba98 100644 --- a/src/main/java/com/sumte/room/service/RoomQueryService.java +++ b/src/main/java/com/sumte/room/service/RoomQueryService.java @@ -1,7 +1,11 @@ package com.sumte.room.service; +import java.time.LocalDate; +import java.util.List; + import com.sumte.room.dto.RoomResponseDTO; public interface RoomQueryService { + List getRoomsByGuesthouse(Long guesthouseId, LocalDate startDate, LocalDate endDate); RoomResponseDTO.GetRoomResponse getRoomById(Long roomId); } diff --git a/src/main/java/com/sumte/room/service/RoomQueryServiceImpl.java b/src/main/java/com/sumte/room/service/RoomQueryServiceImpl.java index 2799508..cbc5f55 100644 --- a/src/main/java/com/sumte/room/service/RoomQueryServiceImpl.java +++ b/src/main/java/com/sumte/room/service/RoomQueryServiceImpl.java @@ -1,9 +1,15 @@ package com.sumte.room.service; +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; + import org.springframework.stereotype.Service; import com.sumte.apiPayload.code.error.CommonErrorCode; import com.sumte.apiPayload.exception.SumteException; +import com.sumte.reservation.repository.ReservationRepository; +import com.sumte.room.converter.RoomConverter; import com.sumte.room.dto.RoomResponseDTO; import com.sumte.room.entity.Room; import com.sumte.room.repository.RoomRepository; @@ -16,6 +22,8 @@ public class RoomQueryServiceImpl implements RoomQueryService { private final RoomRepository roomRepository; + private final RoomConverter roomConverter; + private final ReservationRepository reservationRepository; @Override @Transactional @@ -37,4 +45,15 @@ public RoomResponseDTO.GetRoomResponse getRoomById(Long roomId) { .build(); } + public List getRoomsByGuesthouse(Long guesthouseId, LocalDate startDate, + LocalDate endDate) { + List rooms = roomRepository.findAllByGuesthouseId(guesthouseId); + return rooms.stream() + .map(room -> { + boolean isReserved = reservationRepository.existsOverlappingReservation(room, startDate, endDate); + return roomConverter.toRoomSummary(room, !isReserved); + }) + .collect(Collectors.toList()); + } + }