diff --git a/src/main/java/com/offway/core/itinerary/controller/dto/CourseResponse.java b/src/main/java/com/offway/core/itinerary/controller/dto/CourseResponse.java index f54c33cd..28ecaae0 100644 --- a/src/main/java/com/offway/core/itinerary/controller/dto/CourseResponse.java +++ b/src/main/java/com/offway/core/itinerary/controller/dto/CourseResponse.java @@ -5,6 +5,7 @@ import com.offway.core.itinerary.domain.DaySchedule; import com.offway.core.itinerary.domain.Slot; import com.offway.core.itinerary.domain.SlotKind; +import com.offway.core.trip.domain.MapSearchLink; import com.offway.core.itinerary.service.dto.GeneratedCourse; import com.offway.core.itinerary.service.dto.SlotHours; import com.offway.core.policy.domain.PolicyType; @@ -266,6 +267,14 @@ public record Item( 알 수 있는데 그 데이터가 없어 붙이지 않는다 — 근거 없이 붙이면 사용자가 못 받는 할인을 기대하고 간다. 지역 단위 혜택은 코스 `benefits` 로 그대로 나간다.""", example = "숙박 할인", nullable = true) String benefit, + @Schema(description = """ + 지도 검색 링크(#236). **사진이 없는 슬롯에만** 실린다. + + 숙소는 89곳 중 45곳에서 사진 있는 후보가 2곳도 안 된다 — 인허가 데이터에 사진이 없고, + 공식 API 로 숙소 사진을 주는 곳은 유료뿐이다. 사진 없는 카드를 그대로 두는 대신 + 지도로 넘겨 위치·사진·리뷰를 거기서 보게 한다.""", + example = "https://map.naver.com/p/search/%EC%9D%98%EC%84%B1%EA%B5%B0+%EC%98%AC%EC%9D%B8%EB%AA%A8%ED%85%94", + nullable = true) String mapSearchUrl, double lat, double lng, int travelMinutes, @@ -290,6 +299,7 @@ static Item from(Slot slot, Integer distanceFromPrevMeters, String regionName, hours == null ? null : hours.restDate(), hours == null ? null : hours.displayStatus(), benefit, + mapSearchUrlFor(slot), slot.getLat(), slot.getLng(), slot.getTravelMinutesFromPrev(), @@ -388,4 +398,18 @@ private static Map slotBenefits(GeneratedCourse generated) { private static String benefitFor(Slot slot, Map slotBenefits) { return slotBenefits.get(slot.getKind()); } + + /** + * 사진이 없는 슬롯에만 지도 링크를 준다(#236). + * + *

사진이 있으면 카드가 이미 설 수 있어 링크가 군더더기다. 없을 때만 "여기서 보세요" 가 값어치를 갖는다. + * + *

숙소가 이 경우의 대부분이다 — 89곳 중 45곳에서 사진 있는 숙소가 2곳도 안 된다. + */ + private static String mapSearchUrlFor(Slot slot) { + if (slot.getImageUrl() != null && !slot.getImageUrl().isBlank()) { + return null; + } + return MapSearchLink.of(slot.getTitle(), slot.getAddress()).orElse(null); + } } diff --git a/src/test/java/com/offway/core/common/logging/ResponseLogSummaryTest.java b/src/test/java/com/offway/core/common/logging/ResponseLogSummaryTest.java index 89b95cc8..9fcf2008 100644 --- a/src/test/java/com/offway/core/common/logging/ResponseLogSummaryTest.java +++ b/src/test/java/com/offway/core/common/logging/ResponseLogSummaryTest.java @@ -32,7 +32,7 @@ class ResponseLogSummaryTest { @Test void 코스는_지역명과_규모를_낸다() { CourseResponse.Item item = new CourseResponse.Item( - 1, "MORNING", "SIGHT", "관광", "c1", "장소1", null, null, null, null, null, null, null, null, 37.5, 128.6, 0, null, "정선군"); + 1, "MORNING", "SIGHT", "관광", "c1", "장소1", null, null, null, null, null, null, null, null, null, 37.5, 128.6, 0, null, "정선군"); CourseResponse.Day day = new CourseResponse.Day(1, null, null, null, null, null, List.of(item)); CourseResponse response = new CourseResponse( 1L, 16, 3, null, "PACKED", "CAR", List.of(day), List.of(), null, null, null); diff --git a/src/test/java/com/offway/core/itinerary/controller/dto/SlotMapLinkTest.java b/src/test/java/com/offway/core/itinerary/controller/dto/SlotMapLinkTest.java new file mode 100644 index 00000000..72ec0fda --- /dev/null +++ b/src/test/java/com/offway/core/itinerary/controller/dto/SlotMapLinkTest.java @@ -0,0 +1,42 @@ +package com.offway.core.itinerary.controller.dto; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.offway.core.itinerary.domain.Slot; +import com.offway.core.itinerary.domain.SlotDisplay; +import com.offway.core.itinerary.domain.SlotKind; +import com.offway.core.itinerary.domain.TimeOfDay; +import org.junit.jupiter.api.Test; + +/** + * 사진 없는 슬롯에 지도 링크(#236). + * + *

숙소는 89곳 중 45곳에서 사진 있는 후보가 2곳도 안 된다. 인허가 데이터에 사진이 없고, 공식 API 로 + * 숙소 사진을 주는 곳은 유료뿐이다. 사진 없는 카드를 그대로 두는 대신 지도로 넘긴다. + */ +class SlotMapLinkTest { + + private static Slot slot(String imageUrl) { + return Slot.of(1, TimeOfDay.DINNER, SlotKind.STAY, "LIC-1", "올인모텔", 36.35, 128.69, 0, + new SlotDisplay(imageUrl, "경상북도 의성군 의성읍 후죽리 1", null, "054-1")); + } + + @Test + void 사진이_없으면_지도로_넘긴다() { + CourseResponse.Item item = CourseResponse.Item.from(slot(null), null, "의성군", null, null); + + assertNotNull(item.mapSearchUrl()); + assertTrue(item.mapSearchUrl().startsWith("https://map.naver.com/p/search/")); + } + + @Test + void 사진이_있으면_링크를_안_붙인다() { + // 카드가 이미 설 수 있어 링크가 군더더기다. + CourseResponse.Item item = + CourseResponse.Item.from(slot("http://img/1.jpg"), null, "의성군", null, null); + + assertNull(item.mapSearchUrl()); + } +}