diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java b/src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java index dd6203f5..332c5dc4 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java @@ -1,16 +1,10 @@ package konkuk.chacall.domain.foodtruck.application; import konkuk.chacall.domain.foodtruck.application.image.FoodTruckImageService; -import konkuk.chacall.domain.foodtruck.application.command.FoodTruckCommandService; +import konkuk.chacall.domain.foodtruck.application.info.FoodTruckInfoService; import konkuk.chacall.domain.foodtruck.application.menu.FoodTruckMenuService; -import konkuk.chacall.domain.foodtruck.presentation.dto.request.FoodTruckMenuRequest; -import konkuk.chacall.domain.foodtruck.presentation.dto.request.FoodTruckNameDuplicateCheckRequest; -import konkuk.chacall.domain.foodtruck.presentation.dto.request.FoodTruckSearchRequest; -import konkuk.chacall.domain.foodtruck.presentation.dto.request.ImageRequest; -import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckMenuResponse; -import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckNameDuplicateCheckResponse; -import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckResponse; -import konkuk.chacall.domain.foodtruck.presentation.dto.response.ImageResponse; +import konkuk.chacall.domain.foodtruck.presentation.dto.request.*; +import konkuk.chacall.domain.foodtruck.presentation.dto.response.*; import konkuk.chacall.domain.owner.application.validator.OwnerValidator; import konkuk.chacall.domain.user.domain.model.User; import konkuk.chacall.domain.member.application.validator.MemberValidator; @@ -24,25 +18,24 @@ @Transactional(readOnly = true) public class FoodTruckService { - private final FoodTruckCommandService foodTruckCommandService; + private final FoodTruckInfoService foodTruckInfoService; private final FoodTruckMenuService foodTruckMenuService; private final FoodTruckImageService foodTruckImageService; - private final MemberValidator memberValidator; private final OwnerValidator ownerValidator; public CursorPagingResponse getFoodTrucks(Long memberId, FoodTruckSearchRequest request) { memberValidator.validateAndGetMember(memberId); - return foodTruckCommandService.getFoodTrucks(memberId, request); + return foodTruckInfoService.getFoodTrucks(memberId, request); } public FoodTruckNameDuplicateCheckResponse isNameDuplicated(Long ownerId, FoodTruckNameDuplicateCheckRequest request) { ownerValidator.validateAndGetOwner(ownerId); return FoodTruckNameDuplicateCheckResponse.of( - foodTruckCommandService.isNameDuplicated(request.name())); + foodTruckInfoService.isNameDuplicated(request.name())); } public CursorPagingResponse getFoodTruckMenus(Long memberId, Long foodTruckId, FoodTruckMenuRequest request) { @@ -63,7 +56,18 @@ public ImageResponse createMenuImagePresignedUrl(ImageRequest request, Long owne return foodTruckImageService.createMenuImagePresignedUrl(request, owner); } + @Transactional + public FoodTruckIdResponse updateMyFoodTruckInfo(Long ownerId, Long foodTruckId, UpdateFoodTruckInfoRequest request) { + User owner = ownerValidator.validateAndGetOwner(ownerId); + return FoodTruckIdResponse.of( + foodTruckInfoService.updateMyFoodTruckInfo(owner, foodTruckId, request) + ); + } + public void deleteFoodTruckImagesFromS3(Long ownerId, Long foodTruckId, DeleteFoodTruckImagesRequest request) { + User owner = ownerValidator.validateAndGetOwner(ownerId); + foodTruckImageService.deleteFoodTruckImagesFromS3(owner, foodTruckId, request); + } } diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/application/command/FoodTruckCommandService.java b/src/main/java/konkuk/chacall/domain/foodtruck/application/command/FoodTruckCommandService.java deleted file mode 100644 index 426397c1..00000000 --- a/src/main/java/konkuk/chacall/domain/foodtruck/application/command/FoodTruckCommandService.java +++ /dev/null @@ -1,50 +0,0 @@ -package konkuk.chacall.domain.foodtruck.application.command; - -import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck; -import konkuk.chacall.domain.foodtruck.domain.repository.FoodTruckRepository; -import konkuk.chacall.domain.foodtruck.presentation.dto.request.FoodTruckSearchRequest; -import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckResponse; -import konkuk.chacall.domain.member.domain.repository.SavedFoodTruckRepository; -import konkuk.chacall.domain.user.domain.model.User; -import konkuk.chacall.global.common.dto.CursorPagingResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Slice; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.Set; - -@Service -@RequiredArgsConstructor -public class FoodTruckCommandService { - - private final FoodTruckRepository foodTruckRepository; - private final SavedFoodTruckRepository savedFoodTruckRepository; - - public CursorPagingResponse getFoodTrucks(Long memberId, FoodTruckSearchRequest request) { - - Slice foodTruckSlice = foodTruckRepository.getFoodTrucks(request); - List content = foodTruckSlice.getContent(); - - // 이 페이지의 트럭 ID 목록 - List foodTruckIds = content.stream() - .map(FoodTruck::getFoodTruckId) - .toList(); - - Set savedFoodTruckIds = foodTruckIds.isEmpty() ? Set.of() : savedFoodTruckRepository.findSavedTruckIdsIn(memberId, foodTruckIds); - - List foodTrucks = content.stream() - .map(ft -> FoodTruckResponse.of(ft, savedFoodTruckIds.contains(ft.getFoodTruckId()))) - .toList(); - - return CursorPagingResponse.of( - foodTrucks, - FoodTruckResponse::foodTruckId, - foodTruckSlice.hasNext() - ); - } - - public boolean isNameDuplicated(String name) { - return foodTruckRepository.existsByName(name); - } -} diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/application/image/FoodTruckImageService.java b/src/main/java/konkuk/chacall/domain/foodtruck/application/image/FoodTruckImageService.java index 21592650..5f94834a 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/application/image/FoodTruckImageService.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/application/image/FoodTruckImageService.java @@ -1,5 +1,8 @@ package konkuk.chacall.domain.foodtruck.application.image; +import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck; +import konkuk.chacall.domain.foodtruck.domain.repository.FoodTruckRepository; +import konkuk.chacall.domain.foodtruck.presentation.dto.request.DeleteFoodTruckImagesRequest; import konkuk.chacall.domain.foodtruck.presentation.dto.request.ImageRequest; import konkuk.chacall.domain.foodtruck.presentation.dto.response.ImageResponse; import konkuk.chacall.domain.user.domain.model.User; @@ -7,6 +10,7 @@ import konkuk.chacall.global.common.exception.code.ErrorCode; import konkuk.chacall.global.common.storage.S3Service; import konkuk.chacall.global.common.storage.util.AllowedFileExtension; +import konkuk.chacall.global.common.storage.util.CdnUrlResolver; import konkuk.chacall.global.common.storage.util.KeyUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -18,7 +22,9 @@ public class FoodTruckImageService { private final S3Service s3Service; + private final CdnUrlResolver cdnUrlResolver; private static final int MAX_FOOD_TRUCK_IMAGE_COUNT = 9; + private final FoodTruckRepository foodTruckRepository; // private static final int MAX_MENU_IMAGE_COUNT = 5; public ImageResponse createFoodTruckImagePresignedUrl(ImageRequest request, User owner) { @@ -65,4 +71,15 @@ public ImageResponse createMenuImagePresignedUrl(ImageRequest request, User owne return ImageResponse.of(imageInfos); } + + public void deleteFoodTruckImagesFromS3(User owner, Long foodTruckId, DeleteFoodTruckImagesRequest request) { + FoodTruck foodTruck = foodTruckRepository.findById(foodTruckId) + .orElseThrow(() -> new BusinessException(ErrorCode.FOOD_TRUCK_NOT_FOUND)); + + foodTruck.validateOwner(owner.getUserId()); + + request.imageUrls().stream() + .map(cdnUrlResolver::extractKeyFromUrl) + .forEach(s3Service::delete); + } } \ No newline at end of file diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/application/info/FoodTruckInfoService.java b/src/main/java/konkuk/chacall/domain/foodtruck/application/info/FoodTruckInfoService.java new file mode 100644 index 00000000..64c2fea1 --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/foodtruck/application/info/FoodTruckInfoService.java @@ -0,0 +1,136 @@ +package konkuk.chacall.domain.foodtruck.application.info; + +import konkuk.chacall.domain.foodtruck.domain.model.AvailableDate; +import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck; +import konkuk.chacall.domain.foodtruck.domain.model.FoodTruckServiceArea; +import konkuk.chacall.domain.foodtruck.domain.repository.AvailableDateRepository; +import konkuk.chacall.domain.foodtruck.domain.repository.FoodTruckRepository; +import konkuk.chacall.domain.foodtruck.domain.repository.FoodTruckServiceAreaRepository; +import konkuk.chacall.domain.foodtruck.presentation.dto.request.DateRangeRequest; +import konkuk.chacall.domain.foodtruck.presentation.dto.request.FoodTruckSearchRequest; +import konkuk.chacall.domain.foodtruck.presentation.dto.request.UpdateFoodTruckInfoRequest; +import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckResponse; +import konkuk.chacall.domain.member.domain.repository.SavedFoodTruckRepository; +import konkuk.chacall.domain.region.domain.model.Region; +import konkuk.chacall.domain.region.domain.repository.RegionRepository; +import konkuk.chacall.domain.user.domain.model.User; +import konkuk.chacall.global.common.dto.CursorPagingResponse; +import konkuk.chacall.global.common.exception.EntityNotFoundException; +import konkuk.chacall.global.common.exception.code.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class FoodTruckInfoService { + + private final FoodTruckRepository foodTruckRepository; + private final SavedFoodTruckRepository savedFoodTruckRepository; + private final FoodTruckServiceAreaRepository foodTruckServiceAreaRepository; + private final AvailableDateRepository availableDateRepository; + private final RegionRepository regionRepository; + + public CursorPagingResponse getFoodTrucks(Long memberId, FoodTruckSearchRequest request) { + + Slice foodTruckSlice = foodTruckRepository.getFoodTrucks(request); + List content = foodTruckSlice.getContent(); + + // 이 페이지의 트럭 ID 목록 + List foodTruckIds = content.stream() + .map(FoodTruck::getFoodTruckId) + .toList(); + + Set savedFoodTruckIds = foodTruckIds.isEmpty() ? Set.of() : savedFoodTruckRepository.findSavedTruckIdsIn(memberId, foodTruckIds); + + List foodTrucks = content.stream() + .map(ft -> FoodTruckResponse.of(ft, savedFoodTruckIds.contains(ft.getFoodTruckId()))) + .toList(); + + return CursorPagingResponse.of( + foodTrucks, + FoodTruckResponse::foodTruckId, + foodTruckSlice.hasNext() + ); + } + + public boolean isNameDuplicated(String name) { + return foodTruckRepository.existsByFoodTruckInfo_Name(name); + } + + public Long updateMyFoodTruckInfo(User owner, Long foodTruckId, UpdateFoodTruckInfoRequest request) { + FoodTruck foodTruck = foodTruckRepository.findById(foodTruckId) + .orElseThrow(() -> new EntityNotFoundException(ErrorCode.FOOD_TRUCK_NOT_FOUND)); + + // 푸드트럭 소유주/승인 상태 검증 + foodTruck.validateOwner(owner.getUserId()); + foodTruck.validateApprovedStatus(); + + // 푸드트럭 정보 업데이트 + foodTruck.updateFoodTruckInfo(request.name(), request.description(), request.phoneNumber(), request.activeTime(), request.timeDiscussRequired(), request.photoUrls(), + request.menuCategories(), request.availableQuantity(), request.needElectricity(), request.paymentMethod(), request.operatingInfo(), request.option()); + + // 푸드트럭 노출 상태 변경 허용 + foodTruck.permitChangeViewStatus(); + + // 서비스 가능 지역 동기화 + syncServiceAreas(foodTruck, request.foodTruckServiceAreas()); + + // 운영 가능 날짜 동기화 + syncAvailableDates(foodTruck, request.availableDates()); + + return foodTruck.getFoodTruckId(); + } + + private void syncAvailableDates(FoodTruck foodTruck, List dateRangeRequests) { + // 기존 운영 가능 날짜 삭제 + availableDateRepository.deleteAllByFoodTruckId(foodTruck.getFoodTruckId()); + + // 새로운 운영 가능 날짜 추가 + List newAvailableDates = dateRangeRequests.stream() + .map(request -> AvailableDate.createAvailableDate(request.startDate(), request.endDate(), foodTruck)) + .toList(); + + availableDateRepository.saveAll(newAvailableDates); + } + + private void syncServiceAreas(FoodTruck foodTruck, Set requestedRegionIds) { + Set currentRegionIds = foodTruckServiceAreaRepository.findAllByFoodTruckId(foodTruck.getFoodTruckId()).stream() + .map(FoodTruckServiceArea::getRegion) + .map(Region::getRegionId) + .collect(Collectors.toSet()); + + // 추가할 지역 + Set regionsToAdd = new HashSet<>(requestedRegionIds); + regionsToAdd.removeAll(currentRegionIds); // 현재 지역에서 없는 것들만 남김 + + // 제거할 지역 + Set regionsToRemove = new HashSet<>(currentRegionIds); + regionsToRemove.removeAll(requestedRegionIds); // 요청된 지역에서 없는 것들만 남김 + + // 추가 + if(!regionsToAdd.isEmpty()) { + List serviceAreasToAdd = regionsToAdd.stream() + .map(regionId -> FoodTruckServiceArea.createFoodTruckServiceArea(regionRepository.findById(regionId) + .orElseThrow(() -> new EntityNotFoundException(ErrorCode.REGION_NOT_FOUND)), foodTruck)) + .toList(); + + foodTruckServiceAreaRepository.saveAll(serviceAreasToAdd); + } + + // 제거 + if(!regionsToRemove.isEmpty()) { + List serviceAreasToRemove = regionsToRemove.stream() + .map(regionId -> foodTruckServiceAreaRepository.findByFoodTruckIdAndRegionId(foodTruck.getFoodTruckId(), regionId) + .orElseThrow(() -> new EntityNotFoundException(ErrorCode.FOOD_TRUCK_SERVICE_AREA_NOT_FOUND))) + .toList(); + + foodTruckServiceAreaRepository.deleteAll(serviceAreasToRemove); + } + } +} diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/AvailableDate.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/AvailableDate.java index ab3d7be0..aea4a7d8 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/AvailableDate.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/AvailableDate.java @@ -2,14 +2,14 @@ import jakarta.persistence.*; import konkuk.chacall.global.common.domain.BaseEntity; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import java.time.LocalDate; +@Builder @Entity @Table(name = "available_dates") +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class AvailableDate extends BaseEntity { @@ -29,4 +29,11 @@ public class AvailableDate extends BaseEntity { @JoinColumn(name = "food_truck_id", nullable = false) private FoodTruck foodTruck; + public static AvailableDate createAvailableDate(LocalDate startDate, LocalDate endDate, FoodTruck foodTruck) { + return AvailableDate.builder() + .startAt(startDate) + .endAt(endDate) + .foodTruck(foodTruck) + .build(); + } } diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java index 1731931f..2ba5bbb9 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java @@ -3,8 +3,6 @@ import jakarta.persistence.*; import konkuk.chacall.domain.foodtruck.domain.value.*; import konkuk.chacall.domain.user.domain.model.User; -import konkuk.chacall.global.common.converter.MenuCategoryListConverter; -import konkuk.chacall.global.common.converter.PhotoUrlListConverter; import konkuk.chacall.global.common.domain.BaseEntity; import konkuk.chacall.global.common.exception.DomainRuleException; import konkuk.chacall.global.common.exception.code.ErrorCode; @@ -26,39 +24,8 @@ public class FoodTruck extends BaseEntity { @Column(name = "food_truck_id", nullable = false) private Long foodTruckId; - @Column(nullable = false) - private String name; - - @Column(length = 300) - private String description; - - @Column(length = 15) - private String phoneNumber; - - private String activeTime; - - private Boolean timeDiscussRequired; - - @Convert(converter = PhotoUrlListConverter.class) - private PhotoUrlList foodTruckPhotoList; - - @Convert(converter = MenuCategoryListConverter.class) - private MenuCategoryList menuCategoryList; - - @Enumerated(EnumType.STRING) - private AvailableQuantity availableQuantity; - - @Enumerated(EnumType.STRING) - private NeedElectricity needElectricity; - - @Enumerated(EnumType.STRING) - private PaymentMethod paymentMethod; - - @Column(name = "operating_info", length = 800) - private String operatingInfo; - - @Column(name = "option", length = 800) - private String option; + @Embedded + private FoodTruckInfo foodTruckInfo; @Column(name = "rejection_reason", length = 30) private String rejectionReason; @@ -77,6 +44,11 @@ public class FoodTruck extends BaseEntity { @Column(nullable = false, length = 3) private FoodTruckViewedStatus foodTruckViewedStatus = FoodTruckViewedStatus.OFF; + //todo 컬럼 추가 후 기존 데이터 변경한 다음 다시 주석 해제할 예정 + @Builder.Default +// @Column(nullable = false) + private Boolean canChangeViewedStatus = false; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User owner; @@ -84,18 +56,7 @@ public class FoodTruck extends BaseEntity { public static FoodTruck createEmptyFoodTruck(User owner, String name) { return FoodTruck.builder() .owner(owner) - .name(name) - .description(null) - .phoneNumber(null) - .activeTime(null) - .timeDiscussRequired(null) - .foodTruckPhotoList(null) - .menuCategoryList(null) - .availableQuantity(null) - .needElectricity(null) - .paymentMethod(null) - .operatingInfo(null) - .option(null) + .foodTruckInfo(FoodTruckInfo.createEmptyFoodTruckInfo(name)) .rejectionReason(null) .foodTruckStatus(FoodTruckStatus.PENDING) .foodTruckViewedStatus(FoodTruckViewedStatus.OFF) @@ -113,6 +74,47 @@ public void validateOwner(Long ownerId) { } } + public void validateApprovedStatus() { + if (this.foodTruckStatus != FoodTruckStatus.APPROVED) { + throw new DomainRuleException(ErrorCode.FOOD_TRUCK_NOT_APPROVED); + } + } + + // 푸드트럭 노출 상태 변경 허용 (최소 1번의 정보 기입 후) + public void permitChangeViewStatus() { + this.canChangeViewedStatus = true; + } + + public void updateFoodTruckInfo( + String name, + String description, + String phoneNumber, + String activeTime, + Boolean timeDiscussRequired, + List photoUrls, + List menuCategories, + AvailableQuantity availableQuantity, + NeedElectricity needElectricity, + PaymentMethod paymentMethod, + String operatingInfo, + String option + ) { + this.foodTruckInfo.updateFoodTruckInfo( + name, + description, + phoneNumber, + activeTime, + timeDiscussRequired, + PhotoUrlList.of(photoUrls), + MenuCategoryList.of(menuCategories), + availableQuantity, + needElectricity, + paymentMethod, + operatingInfo, + option + ); + } + public void updateAverageRating(double rating) { ratingInfo.updateAverageRating(rating); } @@ -146,6 +148,11 @@ public void changeViewedStatus(FoodTruckViewedStatus targetViewedStatus) { throw new DomainRuleException(ErrorCode.INVALID_FOOD_TRUCK_STATUS_TRANSITION); } + if(!this.canChangeViewedStatus) { + throw new DomainRuleException(ErrorCode.FOOD_TRUCK_VIEWED_STATUS_CHANGE_NOT_PERMITTED, + new IllegalArgumentException("최소 1번의 정보 기입 후에만 푸드트럭 노출 상태를 변경할 수 있습니다.")); + } + this.foodTruckViewedStatus = targetViewedStatus; } } diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruckServiceArea.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruckServiceArea.java index f520c58c..3887ba3d 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruckServiceArea.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruckServiceArea.java @@ -3,12 +3,12 @@ import jakarta.persistence.*; import konkuk.chacall.domain.region.domain.model.Region; import konkuk.chacall.global.common.domain.BaseEntity; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; +@Builder @Entity @Table(name = "food_truck_service_areas") +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class FoodTruckServiceArea extends BaseEntity { @@ -25,4 +25,11 @@ public class FoodTruckServiceArea extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "food_truck_id", nullable = false) private FoodTruck foodTruck; + + public static FoodTruckServiceArea createFoodTruckServiceArea(Region region, FoodTruck foodTruck) { + return FoodTruckServiceArea.builder() + .region(region) + .foodTruck(foodTruck) + .build(); + } } diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/Menu.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/Menu.java index 9aad31e4..a987f514 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/Menu.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/Menu.java @@ -60,10 +60,6 @@ public void updateMenu(String name, Integer price, String description, String im this.imageUrl = imageUrl; } - public String parsingMenuPrice() { - return price + "원"; - } - public void changeViewedStatus(MenuViewedStatus targetViewedStatus) { if(this.menuViewedStatus == targetViewedStatus) { throw new DomainRuleException(ErrorCode.INVALID_MENU_STATUS_TRANSITION); diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckRepository.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckRepository.java index 22738f95..740d120f 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckRepository.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckRepository.java @@ -9,7 +9,6 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.Collection; import java.util.Optional; public interface FoodTruckRepository extends JpaRepository, FoodTruckSearchRepository { @@ -45,6 +44,6 @@ boolean existsByFoodTruckIdAndOwnerIdAndFoodTruckStatus( @Param("status") FoodTruckStatus status ); - boolean existsByName(String name); + boolean existsByFoodTruckInfo_Name(String name); } diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckServiceAreaRepository.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckServiceAreaRepository.java index dedc4294..1870005f 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckServiceAreaRepository.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckServiceAreaRepository.java @@ -7,6 +7,7 @@ import org.springframework.data.repository.query.Param; import java.util.List; +import java.util.Optional; public interface FoodTruckServiceAreaRepository extends JpaRepository { @@ -18,4 +19,14 @@ public interface FoodTruckServiceAreaRepository extends JpaRepository findAllByFoodTruckId(Long foodTruckId); + + + @Query("SELECT ftsa FROM FoodTruckServiceArea ftsa " + + "WHERE ftsa.foodTruck.foodTruckId = :foodTruckId " + + "AND ftsa.region.regionId = :regionId") + Optional findByFoodTruckIdAndRegionId(Long foodTruckId, Long regionId); } diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/infra/FoodTruckSearchRepositoryImpl.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/infra/FoodTruckSearchRepositoryImpl.java index 7043c8ed..fa2d53b2 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/infra/FoodTruckSearchRepositoryImpl.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/infra/FoodTruckSearchRepositoryImpl.java @@ -34,6 +34,7 @@ public FoodTruckSearchRepositoryImpl(JPAQueryFactory query) { @Override public Slice getFoodTrucks(FoodTruckSearchRequest request) { BooleanBuilder where = new BooleanBuilder(); + QFoodTruckInfo foodTruckInfo = foodTruck.foodTruckInfo; // 지역 - prefix if (request.regionCodes() != null && !request.regionCodes().isEmpty()) { @@ -74,7 +75,7 @@ public Slice getFoodTrucks(FoodTruckSearchRequest request) { // 수량 if (request.availableQuantity() != null) { - where.and(foodTruck.availableQuantity.in( + where.and(foodTruckInfo.availableQuantity.in( AvailableQuantity.acceptableFor(request.availableQuantity()) )); } @@ -85,7 +86,7 @@ public Slice getFoodTrucks(FoodTruckSearchRequest request) { for (MenuCategory category : request.categories()) { any.or(Expressions.booleanTemplate( "concat(',', {0}, ',') like concat('%,', {1}, ',%')", - foodTruck.menuCategoryList, + foodTruckInfo.menuCategoryList, Expressions.constant(category.getValue()) )); } @@ -94,12 +95,12 @@ public Slice getFoodTrucks(FoodTruckSearchRequest request) { // 전기 if(request.needElectricity() != null) { - where.and(foodTruck.needElectricity.eq(request.needElectricity())); + where.and(foodTruckInfo.needElectricity.eq(request.needElectricity())); } // 결제방법 if (request.paymentMethod() != null && request.paymentMethod() != PaymentMethod.ANY) { - where.and(foodTruck.paymentMethod.eq(request.paymentMethod())); + where.and(foodTruckInfo.paymentMethod.eq(request.paymentMethod())); } // 푸드트럭 상태 diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/AvailableQuantity.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/AvailableQuantity.java index 3a18c529..f2f45f52 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/AvailableQuantity.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/AvailableQuantity.java @@ -1,6 +1,9 @@ package konkuk.chacall.domain.foodtruck.domain.value; +import com.fasterxml.jackson.annotation.JsonCreator; import konkuk.chacall.global.common.dto.EnumValue; +import konkuk.chacall.global.common.exception.DomainRuleException; +import konkuk.chacall.global.common.exception.code.ErrorCode; import lombok.Getter; import java.util.EnumSet; @@ -11,6 +14,7 @@ public enum AvailableQuantity implements EnumValue { LESS_THAN_50("50인분 미만"), LESS_THAN_100("100인분 미만"), LESS_THAN_150("150인분 미만"), + LESS_THAN_200("200인분 미만"), MORE_THAN_200("200인분 이상"), NEED_DISCUSSION("논의 필요"); @@ -20,17 +24,29 @@ public enum AvailableQuantity implements EnumValue { this.value = value; } + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static AvailableQuantity from(String value) { + for (AvailableQuantity quantity : AvailableQuantity.values()) { + if (quantity.getValue().equals(value)) { + return quantity; + } + } + throw new DomainRuleException(ErrorCode.AVAILABLE_QUANTITY_MISMATCH); + } + // 요청된 수량 기준을 만족하는 트럭의 가능한 값 집합 public static EnumSet acceptableFor(AvailableQuantity req) { if (req == null) return EnumSet.noneOf(AvailableQuantity.class); return switch (req) { case LESS_THAN_50 -> - EnumSet.of(LESS_THAN_50, LESS_THAN_100, LESS_THAN_150, MORE_THAN_200, NEED_DISCUSSION); + EnumSet.of(LESS_THAN_50, LESS_THAN_100, LESS_THAN_150, LESS_THAN_200, MORE_THAN_200, NEED_DISCUSSION); case LESS_THAN_100 -> - EnumSet.of(LESS_THAN_100, LESS_THAN_150, MORE_THAN_200, NEED_DISCUSSION); + EnumSet.of(LESS_THAN_100, LESS_THAN_150, LESS_THAN_200, MORE_THAN_200, NEED_DISCUSSION); case LESS_THAN_150 -> - EnumSet.of(LESS_THAN_150, MORE_THAN_200, NEED_DISCUSSION); + EnumSet.of(LESS_THAN_150, LESS_THAN_200, MORE_THAN_200, NEED_DISCUSSION); + case LESS_THAN_200 -> + EnumSet.of(LESS_THAN_200, MORE_THAN_200, NEED_DISCUSSION); case MORE_THAN_200 -> EnumSet.of(MORE_THAN_200, NEED_DISCUSSION); case NEED_DISCUSSION -> diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/FoodTruckInfo.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/FoodTruckInfo.java new file mode 100644 index 00000000..189d61dd --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/FoodTruckInfo.java @@ -0,0 +1,85 @@ +package konkuk.chacall.domain.foodtruck.domain.value; + +import jakarta.persistence.*; +import konkuk.chacall.global.common.converter.MenuCategoryListConverter; +import konkuk.chacall.global.common.converter.PhotoUrlListConverter; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@Embeddable +public class FoodTruckInfo { + + @Column(nullable = false) + private String name; + + @Column(length = 300) + private String description; + + @Column(length = 15) + private String phoneNumber; + + private String activeTime; + + private Boolean timeDiscussRequired; + + @Convert(converter = PhotoUrlListConverter.class) + private PhotoUrlList foodTruckPhotoList; + + @Convert(converter = MenuCategoryListConverter.class) + private MenuCategoryList menuCategoryList; + + @Enumerated(EnumType.STRING) + private AvailableQuantity availableQuantity; + + @Enumerated(EnumType.STRING) + private NeedElectricity needElectricity; + + @Enumerated(EnumType.STRING) + private PaymentMethod paymentMethod; + + @Column(name = "operating_info", length = 800) + private String operatingInfo; + + @Column(name = "option", length = 800) + private String option; + + public static FoodTruckInfo createEmptyFoodTruckInfo(String name) { + return FoodTruckInfo.builder() + .name(name) + .build(); + } + + public void updateFoodTruckInfo( + String name, + String description, + String phoneNumber, + String activeTime, + Boolean timeDiscussRequired, + PhotoUrlList foodTruckPhotoList, + MenuCategoryList menuCategoryList, + AvailableQuantity availableQuantity, + NeedElectricity needElectricity, + PaymentMethod paymentMethod, + String operatingInfo, + String option + ) { + this.name = name; + this.description = description; + this.phoneNumber = phoneNumber; + this.activeTime = activeTime; + this.timeDiscussRequired = timeDiscussRequired; + this.foodTruckPhotoList = foodTruckPhotoList; + this.menuCategoryList = menuCategoryList; + this.availableQuantity = availableQuantity; + this.needElectricity = needElectricity; + this.paymentMethod = paymentMethod; + this.operatingInfo = operatingInfo; + this.option = option; + } +} diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/MenuCategory.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/MenuCategory.java index 2df7312b..994979f7 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/MenuCategory.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/MenuCategory.java @@ -1,6 +1,9 @@ package konkuk.chacall.domain.foodtruck.domain.value; +import com.fasterxml.jackson.annotation.JsonCreator; import konkuk.chacall.global.common.dto.EnumValue; +import konkuk.chacall.global.common.exception.DomainRuleException; +import konkuk.chacall.global.common.exception.code.ErrorCode; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -22,12 +25,13 @@ public enum MenuCategory implements EnumValue { private final String value; + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) public static MenuCategory from(String value) { for (MenuCategory category : MenuCategory.values()) { if (category.getValue().equals(value)) { return category; } } - throw new IllegalArgumentException("Unknown MenuCategory value: " + value); + throw new DomainRuleException(ErrorCode.MENU_CATEGORY_MISMATCH); } } diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/NeedElectricity.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/NeedElectricity.java index f6a9e3df..96a84dc0 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/NeedElectricity.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/NeedElectricity.java @@ -1,12 +1,15 @@ package konkuk.chacall.domain.foodtruck.domain.value; +import com.fasterxml.jackson.annotation.JsonCreator; import konkuk.chacall.global.common.dto.EnumValue; +import konkuk.chacall.global.common.exception.DomainRuleException; +import konkuk.chacall.global.common.exception.code.ErrorCode; import lombok.Getter; @Getter public enum NeedElectricity implements EnumValue { - REQUIRED("가능"), - NOT_REQUIRED("불가능"), + REQUIRED("필요"), + NOT_REQUIRED("불필요"), NEED_DISCUSSION("논의 필요"); private final String value; @@ -14,4 +17,14 @@ public enum NeedElectricity implements EnumValue { NeedElectricity(String value) { this.value = value; } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static NeedElectricity from(String value) { + for (NeedElectricity needElectricity : NeedElectricity.values()) { + if (needElectricity.getValue().equals(value)) { + return needElectricity; + } + } + throw new DomainRuleException(ErrorCode.NEED_ELECTRICITY_MISMATCH); + } } diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/PaymentMethod.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/PaymentMethod.java index 2e8aaa4c..f487a9e7 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/PaymentMethod.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/value/PaymentMethod.java @@ -1,6 +1,9 @@ package konkuk.chacall.domain.foodtruck.domain.value; +import com.fasterxml.jackson.annotation.JsonCreator; import konkuk.chacall.global.common.dto.EnumValue; +import konkuk.chacall.global.common.exception.DomainRuleException; +import konkuk.chacall.global.common.exception.code.ErrorCode; import lombok.Getter; @Getter @@ -14,4 +17,14 @@ public enum PaymentMethod implements EnumValue { PaymentMethod(String value) { this.value = value; } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static PaymentMethod from(String value) { + for (PaymentMethod method : PaymentMethod.values()) { + if (method.getValue().equals(value)) { + return method; + } + } + throw new DomainRuleException(ErrorCode.PAYMENT_METHOD_MISMATCH); + } } diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java index 72fcc10c..5180f96b 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java @@ -5,16 +5,8 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import konkuk.chacall.domain.foodtruck.application.FoodTruckService; -import konkuk.chacall.domain.foodtruck.presentation.dto.request.ImageRequest; -import konkuk.chacall.domain.foodtruck.presentation.dto.request.FoodTruckMenuRequest; -import konkuk.chacall.domain.foodtruck.presentation.dto.request.FoodTruckNameDuplicateCheckRequest; -import konkuk.chacall.domain.foodtruck.presentation.dto.request.FoodTruckSearchRequest; -import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckMenuResponse; -import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckNameDuplicateCheckResponse; -import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckResponse; -import konkuk.chacall.domain.foodtruck.presentation.dto.response.ImageResponse; -import konkuk.chacall.domain.owner.presentation.dto.request.MyFoodTruckMenuRequest; -import konkuk.chacall.domain.owner.presentation.dto.response.MyFoodTruckMenuResponse; +import konkuk.chacall.domain.foodtruck.presentation.dto.request.*; +import konkuk.chacall.domain.foodtruck.presentation.dto.response.*; import konkuk.chacall.global.common.annotation.ExceptionDescription; import konkuk.chacall.global.common.annotation.UserId; import konkuk.chacall.global.common.dto.BaseResponse; @@ -100,4 +92,35 @@ public BaseResponse> getFoodTruckMen @Parameter(hidden = true) @UserId final Long memberId) { return BaseResponse.ok(foodTruckService.getFoodTruckMenus(memberId, foodTruckId, request)); } + + @Operation( + summary = "나의 푸드트럭 정보 등록/수정", + description = "승인이 완료된 나의 푸드트럭 정보를 기입하거나 수정합니다." + ) + @ExceptionDescription(SwaggerResponseDescription.UPDATE_FOOD_TRUCK_INFO) + @PutMapping("{foodTruckId}") + public BaseResponse updateMyFoodTruckInfo( + @Parameter(description = "푸드트럭 ID", example = "1") @PathVariable final Long foodTruckId, + @Valid @RequestBody final UpdateFoodTruckInfoRequest request, + @Parameter(hidden = true) @UserId final Long ownerId + ) { + return BaseResponse.ok(foodTruckService.updateMyFoodTruckInfo(ownerId, foodTruckId, request)); + } + + @Operation( + summary = "S3에서 푸드트럭 이미지 객체 삭제", + description = "S3에서 푸드트럭/메뉴 이미지 객체를 삭제합니다. 사용자가 기존 푸드트럭/메뉴 이미지를 삭제했을 경우 호출해주세요." + ) + @ExceptionDescription(SwaggerResponseDescription.DELETE_FOOD_TRUCK_IMAGES) + @DeleteMapping("{foodTruckId}/images") + public BaseResponse deleteFoodTruckImagesFromS3( + @Parameter(description = "푸드트럭 ID", example = "1") @PathVariable final Long foodTruckId, + @Valid @RequestBody final DeleteFoodTruckImagesRequest request, + @Parameter(hidden = true) @UserId final Long ownerId + ) { + foodTruckService.deleteFoodTruckImagesFromS3(ownerId, foodTruckId, request); + return BaseResponse.ok(null); + } + + } diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/DeleteFoodTruckImagesRequest.java b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/DeleteFoodTruckImagesRequest.java new file mode 100644 index 00000000..7a522473 --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/DeleteFoodTruckImagesRequest.java @@ -0,0 +1,13 @@ +package konkuk.chacall.domain.foodtruck.presentation.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; + +import java.util.List; + +public record DeleteFoodTruckImagesRequest( + @Schema(description = "삭제할 이미지 URL 목록", example = "[\"https://example.com/image1.jpg\", \"https://example.com/image2.jpg\"]") + @Size(min = 1, message = "최소 1개의 이미지 URL을 제공해야 합니다.") + List imageUrls +) { +} diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/FoodTruckSearchRequest.java b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/FoodTruckSearchRequest.java index 484d58ba..469985b0 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/FoodTruckSearchRequest.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/FoodTruckSearchRequest.java @@ -39,6 +39,7 @@ public record FoodTruckSearchRequest( "50인분 미만", "100인분 미만", "150인분 미만", + "200인분 미만", "200인분 이상", "논의 필요" } @@ -59,7 +60,7 @@ public record FoodTruckSearchRequest( schema = @Schema( type = "string", example = "논의 필요", - allowableValues = {"가능", "불가능", "논의 필요"} + allowableValues = {"필요", "불필요", "논의 필요"} ) ) NeedElectricity needElectricity, diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/UpdateFoodTruckInfoRequest.java b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/UpdateFoodTruckInfoRequest.java new file mode 100644 index 00000000..bd503851 --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/UpdateFoodTruckInfoRequest.java @@ -0,0 +1,87 @@ +package konkuk.chacall.domain.foodtruck.presentation.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import konkuk.chacall.domain.foodtruck.domain.value.AvailableQuantity; +import konkuk.chacall.domain.foodtruck.domain.value.MenuCategory; +import konkuk.chacall.domain.foodtruck.domain.value.NeedElectricity; +import konkuk.chacall.domain.foodtruck.domain.value.PaymentMethod; + +import java.util.List; +import java.util.Set; + +public record UpdateFoodTruckInfoRequest ( + @Schema(description = "푸드트럭 이름", example = "맛있는 푸드트럭") + @NotBlank(message = "푸드트럭 이름은 필수 입력 값입니다.") + String name, + + @Schema(description = "푸드트럭 설명", example = "저희 푸드트럭은 신선한 재료로 만든 음식을 제공합니다.") + @NotBlank(message = "푸드트럭 설명은 필수 입력 값입니다.") + String description, + + @Schema(description = "푸드트럭 전화번호", example = "010-1234-5678") + @NotBlank(message = "푸드트럭 전화번호는 필수 입력 값입니다.") + String phoneNumber, + + @Schema(description = "운영 시간대 (형식: HH:MM-HH:MM)", example = "10:00-18:00") + @Pattern(regexp = "^(?:[01]\\d|2[0-3]):[0-5]\\d-(?:[01]\\d|2[0-3]):[0-5]\\d$", message = "운영 시간대는 'HH:MM-HH:MM' 형식이어야 합니다.") + @NotBlank(message = "운영 시간대는 필수 입력 값입니다.") + String activeTime, + + @Schema(description = "시간 상의 필요 여부", example = "true") + @NotNull(message = "시간 상의 필요 여부는 필수 입력 값입니다.") + Boolean timeDiscussRequired, + + @Schema(description = "푸드트럭 서비스 가능 지역 ID 리스트", example = "[1, 2, 3]") + @Size(min = 1, max = 10, message = "서비스 가능 지역은 최소 1개 최대 10개 선택 가능합니다.") + Set<@NotNull Long> foodTruckServiceAreas, + + @Schema(description = "메뉴 카테고리 리스트", example = "[\"한식\", \"분식\"]") + @Size(min = 1, max = 12, message = "메뉴 카테고리는 최소 1개 최대 12개 선택 가능합니다.") + List<@NotNull MenuCategory> menuCategories, + + @Schema(description = "제조 가능 수량", example = "100인분 미만", allowableValues = { + "50인분 미만", + "100인분 미만", + "150인분 미만", + "200인분 미만", + "200인분 이상", + "논의 필요" + }) + @NotNull(message = "제조 가능 수량은 필수 입력 값입니다.") + AvailableQuantity availableQuantity, + + @Schema(description = "전기 필요 여부", example = "필요", allowableValues = { + "필요", + "불필요", + "논의 필요" + }) + @NotNull(message = "전기 사용 여부는 필수 입력 값입니다.") + NeedElectricity needElectricity, + + @Schema(description = "결제 수단", example = "계좌이체", allowableValues = { + "무관", + "계좌이체", + "카드" + }) + @NotNull(message = "결제 수단은 필수 입력 값입니다.") + PaymentMethod paymentMethod, + + @Schema(description = "운영 가능 날짜 리스트", example = "[{\"startDate\":\"2024.10.01\",\"endDate\":\"2024.10.10\"}]") + @Size(min = 1, max = 4, message = "운영 가능 날짜는 최소 1개, 최대 4개까지 등록 가능합니다.") + List availableDates, + + @Schema(description = "푸드트럭 사진 URL 리스트", example = "[\"http://image1.png\", \"http://image2.png\"]") + @Size(min = 1, max = 9, message = "푸드트럭 사진은 최소 1개 최대 9개까지 등록 가능합니다.") + List<@NotBlank String> photoUrls, + + @Schema(description = "운영 정보", example = "맛있는 음식을 신속하게 제공합니다.") + String operatingInfo, + + @Schema(description = "기타 옵션", example = "추가 요청 사항이 있으면 기재해주세요.") + String option +) { +} diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckIdResponse.java b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckIdResponse.java new file mode 100644 index 00000000..9805e034 --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckIdResponse.java @@ -0,0 +1,9 @@ +package konkuk.chacall.domain.foodtruck.presentation.dto.response; + +public record FoodTruckIdResponse( + Long foodTruckId +) { + public static FoodTruckIdResponse of(Long foodTruckId) { + return new FoodTruckIdResponse(foodTruckId); + } +} diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckMenuResponse.java b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckMenuResponse.java index 2a0c9461..43975e50 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckMenuResponse.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckMenuResponse.java @@ -9,8 +9,8 @@ public record FoodTruckMenuResponse ( Long menuId, @Schema(description = "메뉴명", example = "크림파스타") String name, - @Schema(description = "가격", example = "12000원") - String price, + @Schema(description = "가격", example = "12000") + int price, @Schema(description = "설명", example = "진한 크림소스와 베이컨") String description, @Schema(description = "이미지 URL", example = "https://cdn.example.com/menus/101.jpg") @@ -21,7 +21,7 @@ public static FoodTruckMenuResponse from(Menu menu) { return new FoodTruckMenuResponse( menu.getMenuId(), menu.getName(), - menu.parsingMenuPrice(), + menu.getPrice(), menu.getDescription(), menu.getImageUrl() ); diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckResponse.java b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckResponse.java index 2f869c0f..da112ed5 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckResponse.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckResponse.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck; +import konkuk.chacall.domain.foodtruck.domain.value.FoodTruckInfo; import java.util.List; @@ -25,12 +26,13 @@ public record FoodTruckResponse( ) { public static FoodTruckResponse of(FoodTruck foodTruck, boolean isSaved) { + FoodTruckInfo foodTruckInfo = foodTruck.getFoodTruckInfo(); return new FoodTruckResponse( foodTruck.getFoodTruckId(), - foodTruck.getName(), - foodTruck.getFoodTruckPhotoList().getMainPhotoUrl(), // 대표 사진 (첫 번째 사진) - foodTruck.getDescription(), - foodTruck.getMenuCategoryList().getMenuCategoryLabelList(), + foodTruckInfo.getName(), + foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl(), // 대표 사진 (첫 번째 사진) + foodTruckInfo.getDescription(), + foodTruckInfo.getMenuCategoryList().getMenuCategoryLabelList(), foodTruck.getRatingInfo().getAverageRating(), foodTruck.getRatingInfo().getRatingCount(), isSaved diff --git a/src/main/java/konkuk/chacall/domain/member/application/reservation/MemberReservationService.java b/src/main/java/konkuk/chacall/domain/member/application/reservation/MemberReservationService.java index 3cbc1fd3..18b1e87b 100644 --- a/src/main/java/konkuk/chacall/domain/member/application/reservation/MemberReservationService.java +++ b/src/main/java/konkuk/chacall/domain/member/application/reservation/MemberReservationService.java @@ -1,6 +1,6 @@ package konkuk.chacall.domain.member.application.reservation; -import konkuk.chacall.domain.member.presentation.dto.request.GetReservationHistoryRequest; +import konkuk.chacall.domain.member.domain.repository.RatingRepository; import konkuk.chacall.domain.member.presentation.dto.response.MemberReservationDetailResponse; import konkuk.chacall.domain.member.presentation.dto.response.MemberReservationHistoryResponse; import konkuk.chacall.domain.reservation.domain.model.Reservation; @@ -22,6 +22,7 @@ public class MemberReservationService { private final ReservationRepository reservationRepository; + private final RatingRepository ratingRepository; public CursorPagingResponse getMemberReservations(User member, ReservationViewType viewType, Long lastCursor, int pageSize) { Slice memberReservationSlice = reservationRepository @@ -41,6 +42,8 @@ public MemberReservationDetailResponse getMemberReservationDetail(Long reservati reservation.validateReservedBy(member.getUserId()); - return MemberReservationDetailResponse.of(reservation, reservation.getFoodTruck()); + boolean reviewRequired = ratingRepository.existsByMemberAndReservation(member, reservation); + + return MemberReservationDetailResponse.of(reservation, reservation.getFoodTruck(), reviewRequired); } } diff --git a/src/main/java/konkuk/chacall/domain/member/domain/repository/RatingRepository.java b/src/main/java/konkuk/chacall/domain/member/domain/repository/RatingRepository.java index 5d80b4a7..dc259f4a 100644 --- a/src/main/java/konkuk/chacall/domain/member/domain/repository/RatingRepository.java +++ b/src/main/java/konkuk/chacall/domain/member/domain/repository/RatingRepository.java @@ -26,4 +26,6 @@ public interface RatingRepository extends JpaRepository { @Modifying @Query("DELETE FROM Rating r WHERE r.foodTruck.foodTruckId = :foodTruckId") void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId); + + boolean existsByMemberAndReservation(User member, Reservation reservation); } diff --git a/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationDetailResponse.java b/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationDetailResponse.java index b296eb2e..72d3ffcd 100644 --- a/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationDetailResponse.java +++ b/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationDetailResponse.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck; +import konkuk.chacall.domain.foodtruck.domain.value.FoodTruckInfo; import konkuk.chacall.domain.reservation.domain.model.Reservation; import konkuk.chacall.domain.reservation.domain.value.ReservationStatus; @@ -38,15 +39,18 @@ public record MemberReservationDetailResponse( example = "음식을 많이 주세요, 늦지말아주세요") String etcRequest, @Schema(description = "예약 상태", example = "예약 확정") - String reservationStatus + String reservationStatus, + @Schema(description = "리뷰 작성 필요 여부", example = "true") + boolean reviewRequired ) { - public static MemberReservationDetailResponse of(Reservation reservation, FoodTruck foodTruck) { + public static MemberReservationDetailResponse of(Reservation reservation, FoodTruck foodTruck, boolean reviewRequired) { List dateTimeList = reservation.getReservationInfo().getFormattedDateTimeInfos(); + FoodTruckInfo foodTruckInfo = foodTruck.getFoodTruckInfo(); return new MemberReservationDetailResponse( - foodTruck.getFoodTruckPhotoList().getMainPhotoUrl(), - foodTruck.getName(), + foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl(), + foodTruckInfo.getName(), reservation.getReservationInfo().getFullAddress(), dateTimeList, reservation.getReservationStatus() == ReservationStatus.CANCELLED ? // 예약 취소 상태이면 null 반환 @@ -55,7 +59,8 @@ public static MemberReservationDetailResponse of(Reservation reservation, FoodTr reservation.getReservationInfo().getDeposit(), reservation.getReservationInfo().parsingIsUserElectricity(), reservation.getReservationInfo().getEtcRequest(), - reservation.getReservationStatus().getValue() + reservation.getReservationStatus().getValue(), + reviewRequired ); } } diff --git a/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationHistoryResponse.java b/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationHistoryResponse.java index 2a2efa60..d88e08ab 100644 --- a/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationHistoryResponse.java +++ b/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationHistoryResponse.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck; +import konkuk.chacall.domain.foodtruck.domain.value.FoodTruckInfo; import konkuk.chacall.domain.reservation.domain.model.Reservation; import java.util.List; @@ -24,11 +25,12 @@ public record MemberReservationHistoryResponse( public static MemberReservationHistoryResponse of(Reservation reservation, FoodTruck foodTruck) { List dateTimeList = reservation.getReservationInfo().getFormattedDateTimeInfos(); + FoodTruckInfo foodTruckInfo = foodTruck.getFoodTruckInfo(); return new MemberReservationHistoryResponse( reservation.getReservationId(), - foodTruck.getFoodTruckPhotoList().getMainPhotoUrl(), - foodTruck.getName(), + foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl(), + foodTruckInfo.getName(), reservation.getReservationInfo().getFullAddress(), dateTimeList, reservation.getReservationStatus().getValue() diff --git a/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/ReservationForRatingResponse.java b/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/ReservationForRatingResponse.java index 717c0f83..2ce2e04e 100644 --- a/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/ReservationForRatingResponse.java +++ b/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/ReservationForRatingResponse.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck; +import konkuk.chacall.domain.foodtruck.domain.value.FoodTruckInfo; import konkuk.chacall.domain.reservation.domain.model.Reservation; import java.util.List; @@ -32,12 +33,13 @@ public record ReservationForRating( ) { public static ReservationForRating of(FoodTruck foodTruck, Reservation reservation) { List dateTimeList = reservation.getReservationInfo().getFormattedDateTimeInfos(); + FoodTruckInfo foodTruckInfo = foodTruck.getFoodTruckInfo(); return new ReservationForRating( reservation.getReservationId(), foodTruck.getFoodTruckId(), - foodTruck.getName(), - foodTruck.getFoodTruckPhotoList().getMainPhotoUrl(), // 대표 사진 (첫 번째 사진) + foodTruckInfo.getName(), + foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl(), // 대표 사진 (첫 번째 사진) reservation.getReservationInfo().getFullAddress(), dateTimeList ); diff --git a/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/SavedFoodTruckResponse.java b/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/SavedFoodTruckResponse.java index ca76ddfb..c3571214 100644 --- a/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/SavedFoodTruckResponse.java +++ b/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/SavedFoodTruckResponse.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck; +import konkuk.chacall.domain.foodtruck.domain.value.FoodTruckInfo; public record SavedFoodTruckResponse( @Schema(description = "푸드트럭 식별자", example = "1") @@ -18,11 +19,12 @@ public record SavedFoodTruckResponse( Integer ratingCount ) { public static SavedFoodTruckResponse of(FoodTruck foodTruck) { + FoodTruckInfo foodTruckInfo = foodTruck.getFoodTruckInfo(); return new SavedFoodTruckResponse( foodTruck.getFoodTruckId(), - foodTruck.getName(), - foodTruck.getFoodTruckPhotoList().getMainPhotoUrl(), // 대표 사진 (첫 번째 사진) - foodTruck.getDescription(), + foodTruckInfo.getName(), + foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl(), // 대표 사진 (첫 번째 사진) + foodTruckInfo.getDescription(), foodTruck.getRatingInfo().getAverageRating(), foodTruck.getRatingInfo().getRatingCount() ); diff --git a/src/main/java/konkuk/chacall/domain/owner/application/myfoodtruckmenu/MyFoodTruckMenuService.java b/src/main/java/konkuk/chacall/domain/owner/application/myfoodtruckmenu/MyFoodTruckMenuService.java index 064258cf..39b42491 100644 --- a/src/main/java/konkuk/chacall/domain/owner/application/myfoodtruckmenu/MyFoodTruckMenuService.java +++ b/src/main/java/konkuk/chacall/domain/owner/application/myfoodtruckmenu/MyFoodTruckMenuService.java @@ -114,12 +114,12 @@ public void deleteMenu(Long ownerId, Long foodTruckId, Long menuId) { Menu menu = menuRepository.findByMenuIdAndFoodTruckId(menuId, foodTruckId) .orElseThrow(() -> new BusinessException(ErrorCode.MENU_NOT_FOUND)); - String key = cdnUrlResolver.extractKeyFromUrl(menu.getImageUrl()); +// String key = cdnUrlResolver.extractKeyFromUrl(menu.getImageUrl()); - // 메뉴 이미지를 S3 에서 삭제 - if (key != null) { - s3Service.delete(key); - } +// // 메뉴 이미지를 S3 에서 삭제 +// if (key != null) { +// s3Service.delete(key); +// } menuRepository.delete(menu); } diff --git a/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckMenuResponse.java b/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckMenuResponse.java index 4ef86554..fa32e8b0 100644 --- a/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckMenuResponse.java +++ b/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckMenuResponse.java @@ -9,8 +9,8 @@ public record MyFoodTruckMenuResponse( Long menuId, @Schema(description = "메뉴명", example = "크림파스타") String name, - @Schema(description = "가격", example = "12000원") - String price, + @Schema(description = "가격", example = "12000") + int price, @Schema(description = "설명", example = "진한 크림소스와 베이컨") String description, @Schema(description = "이미지 URL", example = "https://cdn.example.com/menus/101.jpg") @@ -23,7 +23,7 @@ public static MyFoodTruckMenuResponse from(Menu menu) { return new MyFoodTruckMenuResponse( menu.getMenuId(), menu.getName(), - menu.parsingMenuPrice(), + menu.getPrice(), menu.getDescription(), menu.getImageUrl(), menu.getMenuViewedStatus().name() diff --git a/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckResponse.java b/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckResponse.java index 37515490..ae57baf0 100644 --- a/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckResponse.java +++ b/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckResponse.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck; import konkuk.chacall.domain.foodtruck.domain.model.FoodTruckServiceArea; +import konkuk.chacall.domain.foodtruck.domain.value.FoodTruckInfo; import java.util.List; @@ -29,15 +30,16 @@ public record MyFoodTruckResponse( * @return MyFoodTruckResponse DTO */ public static MyFoodTruckResponse of(FoodTruck foodTruck, List serviceAreas) { + FoodTruckInfo foodTruckInfo = foodTruck.getFoodTruckInfo(); // 대표 이미지 - String mainImageUrl = foodTruck.getFoodTruckPhotoList().getMainPhotoUrl(); + String mainImageUrl = foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl(); return new MyFoodTruckResponse( foodTruck.getFoodTruckId(), mainImageUrl, - foodTruck.getName(), - foodTruck.getDescription(), - foodTruck.getActiveTime(), + foodTruckInfo.getName(), + foodTruckInfo.getDescription(), + foodTruckInfo.getActiveTime(), foodTruck.getServiceAreas(serviceAreas), foodTruck.getFoodTruckViewedStatus().name() ); diff --git a/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationDetailResponse.java b/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationDetailResponse.java index e40d6704..2e473f6c 100644 --- a/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationDetailResponse.java +++ b/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationDetailResponse.java @@ -48,7 +48,7 @@ public static OwnerReservationDetailResponse of(Reservation reservation, User me // boolean 값을 화면에 표시할 문자열로 변환 return new OwnerReservationDetailResponse( - reservation.getFoodTruck().getName(), + reservation.getFoodTruck().getFoodTruckInfo().getName(), member.getProfileImageUrl(), member.getName(), reservation.getReservationInfo().getFullAddress(), diff --git a/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationHistoryResponse.java b/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationHistoryResponse.java index 5258e894..9af29547 100644 --- a/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationHistoryResponse.java +++ b/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationHistoryResponse.java @@ -33,7 +33,7 @@ public static OwnerReservationHistoryResponse of(Reservation reservation, User m member.getName(), reservation.getReservationInfo().getFullAddress(), dateTimeList, - reservation.getFoodTruck().getName(), + reservation.getFoodTruck().getFoodTruckInfo().getName(), reservation.getReservationStatus().getValue() ); } diff --git a/src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java b/src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java index da0c349e..9d4285c1 100644 --- a/src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java @@ -78,6 +78,11 @@ public enum ErrorCode implements ResponseCode { FOOD_TRUCK_NOT_OWNED(HttpStatus.FORBIDDEN, 110002, "해당 푸드트럭의 소유자가 아닙니다."), FOOD_TRUCK_NOT_APPROVED(HttpStatus.FORBIDDEN, 110003, "승인된 푸드트럭이 아닙니다."), INVALID_FOOD_TRUCK_STATUS_TRANSITION(HttpStatus.BAD_REQUEST, 110004, "허용되지 않은 푸드트럭 상태 변경입니다."), + FOOD_TRUCK_VIEWED_STATUS_CHANGE_NOT_PERMITTED(HttpStatus.BAD_REQUEST, 110005, "푸드트럭 노출 상태 변경이 허용되지 않았습니다."), + MENU_CATEGORY_MISMATCH(HttpStatus.BAD_REQUEST, 110006, "메뉴 카테고리 값이 올바르지 않습니다."), + AVAILABLE_QUANTITY_MISMATCH(HttpStatus.BAD_REQUEST, 110007, "제조 가능 수량 값이 올바르지 않습니다."), + NEED_ELECTRICITY_MISMATCH(HttpStatus.BAD_REQUEST, 110008, "전기 사용 여부 값이 올바르지 않습니다."), + PAYMENT_METHOD_MISMATCH(HttpStatus.BAD_REQUEST, 110009, "결제 수단 값이 올바르지 않습니다."), INVALID_IMAGE_COUNT(HttpStatus.BAD_REQUEST, 115001, "이미지 개수가 유효하지 않습니다."), INVALID_FILE_EXTENSION(HttpStatus.BAD_REQUEST, 115002, "허용되지 않는 파일 확장자입니다."), @@ -99,6 +104,11 @@ public enum ErrorCode implements ResponseCode { */ MENU_NOT_FOUND(HttpStatus.NOT_FOUND, 140001, "메뉴를 찾을 수 없습니다."), INVALID_MENU_STATUS_TRANSITION(HttpStatus.BAD_REQUEST, 140002, "허용되지 않은 메뉴 상태 변경입니다."), + + /** + * FoodTruckServiceArea + */ + FOOD_TRUCK_SERVICE_AREA_NOT_FOUND(HttpStatus.NOT_FOUND, 150001, "푸드트럭 서비스 지역을 찾을 수 없습니다.") ; private final HttpStatus httpStatus; diff --git a/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java b/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java index e2069c5f..11ddede5 100644 --- a/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java +++ b/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java @@ -94,7 +94,8 @@ public enum SwaggerResponseDescription { USER_NOT_FOUND, USER_FORBIDDEN, FOOD_TRUCK_NOT_APPROVED, - INVALID_FOOD_TRUCK_STATUS_TRANSITION + INVALID_FOOD_TRUCK_STATUS_TRANSITION, + FOOD_TRUCK_VIEWED_STATUS_CHANGE_NOT_PERMITTED ))), OWNER_GET_FOOD_TRUCK_MENUS(new LinkedHashSet<>(Set.of( USER_NOT_FOUND, @@ -220,6 +221,20 @@ public enum SwaggerResponseDescription { USER_FORBIDDEN, FOOD_TRUCK_NOT_FOUND ))), + UPDATE_FOOD_TRUCK_INFO(new LinkedHashSet<>(Set.of( + USER_NOT_FOUND, + USER_FORBIDDEN, + FOOD_TRUCK_NOT_FOUND, + FOOD_TRUCK_NOT_OWNED, + FOOD_TRUCK_NOT_APPROVED, + REGION_NOT_FOUND + ))), + DELETE_FOOD_TRUCK_IMAGES(new LinkedHashSet<>(Set.of( + USER_NOT_FOUND, + USER_FORBIDDEN, + FOOD_TRUCK_NOT_FOUND, + FOOD_TRUCK_NOT_OWNED + ))), // Default DEFAULT(new LinkedHashSet<>()) diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 6ff2425e..8f008935 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -10,7 +10,7 @@ spring: jpa: hibernate: - ddl-auto: create + ddl-auto: update show-sql: true properties: hibernate: