diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/FoodTruck.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/FoodTruck.java index a2d8caf9..54c82a01 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/domain/FoodTruck.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/FoodTruck.java @@ -8,6 +8,9 @@ import konkuk.chacall.global.common.domain.BaseEntity; import lombok.*; +import java.util.List; +import java.util.stream.Collectors; + @Builder @Entity @Table(name = "food_trucks") @@ -70,7 +73,18 @@ public class FoodTruck extends BaseEntity { @JoinColumn(name = "user_id", nullable = false) private User owner; + public boolean isOwnedBy(Long ownerId) { + return this.getOwner().getUserId().equals(ownerId); + } + public void updateAverageRating(double rating) { ratingInfo.updateAverageRating(rating); } + + // 푸드트럭의 호출 가능 지역을 반환해주는 메서드 + public String getServiceAreas(List serviceAreaList) { + return serviceAreaList.stream() + .map(serviceArea -> serviceArea.getRegion().getFullName()) + .collect(Collectors.joining(", ")); + } } diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/AvailableDateRepository.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/AvailableDateRepository.java new file mode 100644 index 00000000..4e7a1dce --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/AvailableDateRepository.java @@ -0,0 +1,14 @@ +package konkuk.chacall.domain.foodtruck.domain.repository; + +import konkuk.chacall.domain.foodtruck.domain.AvailableDate; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface AvailableDateRepository extends JpaRepository { + + @Modifying + @Query("DELETE FROM AvailableDate ad WHERE ad.foodTruck.foodTruckId = :foodTruckId") + void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId); +} 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 929dfe17..d5790c87 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 @@ -1,7 +1,18 @@ package konkuk.chacall.domain.foodtruck.domain.repository; import konkuk.chacall.domain.foodtruck.domain.FoodTruck; +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 org.springframework.data.repository.query.Param; public interface FoodTruckRepository extends JpaRepository { + + @Query("SELECT ft FROM FoodTruck ft " + + "WHERE ft.owner.userId = :ownerId " + + "AND ft.foodTruckId < :lastCursor " + + "ORDER BY ft.foodTruckId DESC") + Slice findByOwnerUserIdWithCursor(@Param("ownerId") Long ownerId, @Param("lastCursor") Long lastCursor, Pageable pageable); + } 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 new file mode 100644 index 00000000..5904a5d8 --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckServiceAreaRepository.java @@ -0,0 +1,21 @@ +package konkuk.chacall.domain.foodtruck.domain.repository; + +import konkuk.chacall.domain.foodtruck.domain.FoodTruckServiceArea; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface FoodTruckServiceAreaRepository extends JpaRepository { + + @Query("SELECT ftsa FROM FoodTruckServiceArea ftsa " + + "JOIN FETCH ftsa.region r " + + "WHERE ftsa.foodTruck.foodTruckId IN :foodTruckIds") + List findAllWithRegionByFoodTruckIdIn(@Param("foodTruckIds") List foodTruckIds); + + @Modifying + @Query("DELETE FROM FoodTruckServiceArea ftsa WHERE ftsa.foodTruck.foodTruckId = :foodTruckId") + void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId); +} diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/MenuRepository.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/MenuRepository.java new file mode 100644 index 00000000..53405dee --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/MenuRepository.java @@ -0,0 +1,14 @@ +package konkuk.chacall.domain.foodtruck.domain.repository; + +import konkuk.chacall.domain.foodtruck.domain.Menu; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface MenuRepository extends JpaRepository { + + @Modifying + @Query("DELETE FROM Menu m WHERE m.foodTruck.foodTruckId = :foodTruckId") + void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId); +} 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 b58f0257..492518ac 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 @@ -5,7 +5,9 @@ import konkuk.chacall.domain.user.domain.model.User; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; import java.util.Optional; @@ -19,4 +21,8 @@ public interface RatingRepository extends JpaRepository { "WHERE r.member = :member " + "AND r.isRated = false") List findAllByMemberAndIsRatedFalse(User member); + + @Modifying + @Query("DELETE FROM Rating r WHERE r.foodTruck.foodTruckId = :foodTruckId") + void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId); } diff --git a/src/main/java/konkuk/chacall/domain/member/domain/repository/SavedFoodTruckRepository.java b/src/main/java/konkuk/chacall/domain/member/domain/repository/SavedFoodTruckRepository.java index a1c8ed23..08482943 100644 --- a/src/main/java/konkuk/chacall/domain/member/domain/repository/SavedFoodTruckRepository.java +++ b/src/main/java/konkuk/chacall/domain/member/domain/repository/SavedFoodTruckRepository.java @@ -7,6 +7,7 @@ import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -27,4 +28,8 @@ Slice findMemberSavedFoodTruckWithCursor( @Param("member") User member, @Param("lastCursor") Long lastCursor, Pageable pageable); + + @Modifying + @Query("DELETE FROM SavedFoodTruck sft WHERE sft.foodTruck.foodTruckId = :foodTruckId") + void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId); } diff --git a/src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java b/src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java index b5cbb3af..fac3b2d8 100644 --- a/src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java +++ b/src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java @@ -1,19 +1,18 @@ package konkuk.chacall.domain.owner.application; -import konkuk.chacall.domain.owner.application.bankAccount.BankAccountService; -import konkuk.chacall.domain.owner.application.chatTemplate.ChatTemplateService; +import konkuk.chacall.domain.owner.application.bankaccount.BankAccountService; +import konkuk.chacall.domain.owner.application.chattemplate.ChatTemplateService; +import konkuk.chacall.domain.owner.application.myfoodtruck.MyFoodTruckService; import konkuk.chacall.domain.owner.application.reservation.OwnerReservationService; import konkuk.chacall.domain.owner.application.validator.OwnerValidator; import konkuk.chacall.domain.owner.presentation.dto.request.*; -import konkuk.chacall.domain.owner.presentation.dto.response.BankAccountResponse; -import konkuk.chacall.domain.owner.presentation.dto.response.ChatTemplateResponse; -import konkuk.chacall.domain.owner.presentation.dto.response.OwnerReservationDetailResponse; -import konkuk.chacall.domain.owner.presentation.dto.response.OwnerReservationHistoryResponse; +import konkuk.chacall.domain.owner.presentation.dto.response.*; import konkuk.chacall.domain.user.domain.model.User; import konkuk.chacall.global.common.dto.CursorPagingResponse; import konkuk.chacall.global.common.dto.CursorPagingRequest; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -24,10 +23,12 @@ public class OwnerService { private final BankAccountService bankAccountService; private final ChatTemplateService chatTemplateService; private final OwnerReservationService ownerReservationService; + private final MyFoodTruckService myFoodTruckService; // 파사드에서 사장님 검증을 거침으로써 서비스 로직에서는 사장님 검증에 신경쓰지 않도록 책임 분리 private final OwnerValidator ownerValidator; + @Transactional public void registerBankAccount(RegisterBankAccountRequest request, Long ownerId) { // 사장님인지 먼저 검증 User owner = ownerValidator.validateAndGetOwner(ownerId); @@ -36,6 +37,7 @@ public void registerBankAccount(RegisterBankAccountRequest request, Long ownerId bankAccountService.registerBankAccount(request, owner); } + @Transactional(readOnly = true) public BankAccountResponse getBankAccount(Long ownerId) { // 사장님인지 먼저 검증 ownerValidator.validateAndGetOwner(ownerId); @@ -44,6 +46,7 @@ public BankAccountResponse getBankAccount(Long ownerId) { return bankAccountService.getBankAccount(ownerId); } + @Transactional public void updateBankAccount(Long ownerId, Long bankAccountId, UpdateBankAccountRequest request) { // 사장님인지 먼저 검증 ownerValidator.validateAndGetOwner(ownerId); @@ -52,6 +55,7 @@ public void updateBankAccount(Long ownerId, Long bankAccountId, UpdateBankAccoun bankAccountService.updateBankAccount(ownerId, bankAccountId, request); } + @Transactional public void deleteBankAccount(Long ownerId, Long bankAccountId) { // 사장님인지 먼저 검증 ownerValidator.validateAndGetOwner(ownerId); @@ -60,7 +64,7 @@ public void deleteBankAccount(Long ownerId, Long bankAccountId) { bankAccountService.deleteBankAccount(ownerId, bankAccountId); } - + @Transactional public void registerChatTemplate(RegisterChatTemplateRequest request, Long ownerId) { // 사장님인지 먼저 검증 User owner = ownerValidator.validateAndGetOwner(ownerId); @@ -69,6 +73,7 @@ public void registerChatTemplate(RegisterChatTemplateRequest request, Long owner chatTemplateService.registerChatTemplate(request, owner); } + @Transactional(readOnly = true) public List getChatTemplates(Long ownerId) { // 사장님인지 먼저 검증 ownerValidator.validateAndGetOwner(ownerId); @@ -77,6 +82,7 @@ public List getChatTemplates(Long ownerId) { return chatTemplateService.getChatTemplates(ownerId); } + @Transactional public void updateChatTemplate(UpdateChatTemplateRequest request, Long ownerId, Long chatTemplateId) { // 사장님인지 먼저 검증 ownerValidator.validateAndGetOwner(ownerId); @@ -85,6 +91,7 @@ public void updateChatTemplate(UpdateChatTemplateRequest request, Long ownerId, chatTemplateService.updateChatTemplate(request, chatTemplateId); } + @Transactional public void deleteChatTemplate(Long ownerId, Long chatTemplateId) { // 사장님인지 먼저 검증 ownerValidator.validateAndGetOwner(ownerId); @@ -93,6 +100,7 @@ public void deleteChatTemplate(Long ownerId, Long chatTemplateId) { chatTemplateService.deleteChatTemplate(chatTemplateId); } + @Transactional(readOnly = true) public CursorPagingResponse getOwnerReservations(GetReservationHistoryRequest request, Long ownerId) { // 사장님인지 먼저 검증 ownerValidator.validateAndGetOwner(ownerId); @@ -102,6 +110,7 @@ public CursorPagingResponse getOwnerReservation return ownerReservationService.getOwnerReservations(ownerId, request.status(), cursorPagingRequest.cursor(), cursorPagingRequest.size()); } + @Transactional(readOnly = true) public OwnerReservationDetailResponse getReservationDetail(Long ownerId, Long reservationId) { // 사장님인지 먼저 검증 ownerValidator.validateAndGetOwner(ownerId); @@ -109,4 +118,22 @@ public OwnerReservationDetailResponse getReservationDetail(Long ownerId, Long re // 사장님 예약 내역 상세 조회 로직 호출 return ownerReservationService.getReservationDetail(ownerId, reservationId); } + + @Transactional(readOnly = true) + public CursorPagingResponse getMyFoodTrucks(CursorPagingRequest request, Long ownerId) { + // 사장님인지 먼저 검증 + ownerValidator.validateAndGetOwner(ownerId); + + // 사장님 - 나의 푸드트럭 목록 조회 로직 호출 + return myFoodTruckService.getMyFoodTrucks(request, ownerId); + } + + @Transactional + public void deleteMyFoodTruck(Long ownerId, Long foodTruckId) { + // 사장님인지 먼저 검증 + ownerValidator.validateAndGetOwner(ownerId); + + // 사장님 - 나의 푸드트럭 삭제 로직 호출 + myFoodTruckService.deleteMyFoodTruck(ownerId, foodTruckId); + } } diff --git a/src/main/java/konkuk/chacall/domain/owner/application/bankAccount/BankAccountService.java b/src/main/java/konkuk/chacall/domain/owner/application/bankaccount/BankAccountService.java similarity index 92% rename from src/main/java/konkuk/chacall/domain/owner/application/bankAccount/BankAccountService.java rename to src/main/java/konkuk/chacall/domain/owner/application/bankaccount/BankAccountService.java index c37955e6..6720a726 100644 --- a/src/main/java/konkuk/chacall/domain/owner/application/bankAccount/BankAccountService.java +++ b/src/main/java/konkuk/chacall/domain/owner/application/bankaccount/BankAccountService.java @@ -1,4 +1,4 @@ -package konkuk.chacall.domain.owner.application.bankAccount; +package konkuk.chacall.domain.owner.application.bankaccount; import konkuk.chacall.domain.owner.domain.model.BankAccount; import konkuk.chacall.domain.owner.domain.repository.BankAccountRepository; @@ -7,23 +7,19 @@ import konkuk.chacall.domain.owner.presentation.dto.response.BankAccountResponse; import konkuk.chacall.domain.user.domain.model.User; import konkuk.chacall.global.common.exception.BusinessException; -import konkuk.chacall.global.common.exception.DomainRuleException; import konkuk.chacall.global.common.exception.EntityNotFoundException; import konkuk.chacall.global.common.exception.code.ErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @Service @RequiredArgsConstructor -@Transactional(readOnly = true) public class BankAccountService { private final BankAccountRepository bankAccountRepository; - @Transactional public void registerBankAccount(RegisterBankAccountRequest request, User owner) { // 해당 유저의 계좌가 이미 있는지 확인 @@ -52,7 +48,6 @@ public BankAccountResponse getBankAccount(Long ownerId) { } - @Transactional public void updateBankAccount(Long ownerId, Long bankAccountId, UpdateBankAccountRequest request) { // 수정할 계좌를 찾고 요청자가 실제 소유주인지 검증 BankAccount bankAccount = findBankAccountAndVerifyOwner(ownerId, bankAccountId); @@ -72,7 +67,6 @@ public void updateBankAccount(Long ownerId, Long bankAccountId, UpdateBankAccoun ); } - @Transactional public void deleteBankAccount(Long ownerId, Long bankAccountId) { // 삭제할 계좌를 찾고, 요청자가 실제 소유주인지 검증 BankAccount bankAccount = findBankAccountAndVerifyOwner(ownerId, bankAccountId); diff --git a/src/main/java/konkuk/chacall/domain/owner/application/chatTemplate/ChatTemplateService.java b/src/main/java/konkuk/chacall/domain/owner/application/chattemplate/ChatTemplateService.java similarity index 87% rename from src/main/java/konkuk/chacall/domain/owner/application/chatTemplate/ChatTemplateService.java rename to src/main/java/konkuk/chacall/domain/owner/application/chattemplate/ChatTemplateService.java index fa31a9ae..9e99d1ab 100644 --- a/src/main/java/konkuk/chacall/domain/owner/application/chatTemplate/ChatTemplateService.java +++ b/src/main/java/konkuk/chacall/domain/owner/application/chattemplate/ChatTemplateService.java @@ -1,4 +1,4 @@ -package konkuk.chacall.domain.owner.application.chatTemplate; +package konkuk.chacall.domain.owner.application.chattemplate; import konkuk.chacall.domain.owner.domain.model.ChatTemplate; import konkuk.chacall.domain.owner.domain.repository.ChatTemplateRepository; @@ -6,23 +6,19 @@ import konkuk.chacall.domain.owner.presentation.dto.request.UpdateChatTemplateRequest; import konkuk.chacall.domain.owner.presentation.dto.response.ChatTemplateResponse; import konkuk.chacall.domain.user.domain.model.User; -import konkuk.chacall.global.common.exception.BusinessException; import konkuk.chacall.global.common.exception.EntityNotFoundException; import konkuk.chacall.global.common.exception.code.ErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.List; @RequiredArgsConstructor @Service -@Transactional(readOnly = true) public class ChatTemplateService { private final ChatTemplateRepository chatTemplateRepository; - @Transactional public void registerChatTemplate(RegisterChatTemplateRequest request, User owner) { ChatTemplate chatTemplate = ChatTemplate.of(request.content(), owner); @@ -37,7 +33,6 @@ public List getChatTemplates(Long ownerId) { .toList(); } - @Transactional public void updateChatTemplate(UpdateChatTemplateRequest request, Long chatTemplateId) { ChatTemplate chatTemplate = chatTemplateRepository.findById(chatTemplateId) .orElseThrow(() -> new EntityNotFoundException(ErrorCode.CHAT_TEMPLATE_NOT_FOUND)); @@ -47,7 +42,6 @@ public void updateChatTemplate(UpdateChatTemplateRequest request, Long chatTempl ); } - @Transactional public void deleteChatTemplate(Long chatTemplateId) { if(!chatTemplateRepository.existsById(chatTemplateId)) { throw new EntityNotFoundException(ErrorCode.CHAT_TEMPLATE_NOT_FOUND); diff --git a/src/main/java/konkuk/chacall/domain/owner/application/myfoodtruck/MyFoodTruckService.java b/src/main/java/konkuk/chacall/domain/owner/application/myfoodtruck/MyFoodTruckService.java new file mode 100644 index 00000000..f615d1db --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/owner/application/myfoodtruck/MyFoodTruckService.java @@ -0,0 +1,116 @@ +package konkuk.chacall.domain.owner.application.myfoodtruck; + +import konkuk.chacall.domain.foodtruck.domain.FoodTruck; +import konkuk.chacall.domain.foodtruck.domain.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.domain.repository.MenuRepository; +import konkuk.chacall.domain.member.domain.repository.RatingRepository; +import konkuk.chacall.domain.member.domain.repository.SavedFoodTruckRepository; +import konkuk.chacall.domain.owner.presentation.dto.response.MyFoodTruckResponse; +import konkuk.chacall.domain.reservation.domain.repository.ReservationRepository; +import konkuk.chacall.global.common.dto.CursorPagingRequest; +import konkuk.chacall.global.common.dto.CursorPagingResponse; +import konkuk.chacall.global.common.exception.BusinessException; +import konkuk.chacall.global.common.exception.EntityNotFoundException; +import konkuk.chacall.global.common.exception.code.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Service +public class MyFoodTruckService { + + private final FoodTruckRepository foodTruckRepository; + private final FoodTruckServiceAreaRepository foodTruckServiceAreaRepository; + private final MenuRepository menuRepository; + private final ReservationRepository reservationRepository; + private final SavedFoodTruckRepository savedFoodTruckRepository; + private final AvailableDateRepository availableDateRepository; + private final RatingRepository ratingRepository; + + public CursorPagingResponse getMyFoodTrucks(CursorPagingRequest request, Long ownerId) { + // 1. 커서 기반으로 푸드트럭 Slice 조회 + Slice foodTruckSlice = findFoodTrucks(ownerId, request.cursor(), request.size()); + List foodTrucks = foodTruckSlice.getContent(); + + // 2. 호출 가능 지역 정보 Map 조회 + Map> serviceAreaMap = getServiceAreaMap(foodTrucks); + + // 3. DTO 리스트 생성 + List responses = mapToMyFoodTruckResponse(foodTrucks, serviceAreaMap); + + return CursorPagingResponse.of(responses, MyFoodTruckResponse::foodTruckId, foodTruckSlice.hasNext()); + } + + public void deleteMyFoodTruck(Long ownerId, Long foodTruckId) { + // 푸드트럭 조회 및 소유권 확인 + FoodTruck foodTruck = foodTruckRepository.findById(foodTruckId) + .orElseThrow(() -> new EntityNotFoundException(ErrorCode.FOOD_TRUCK_NOT_FOUND)); + + if (!foodTruck.isOwnedBy(ownerId)) { + throw new BusinessException(ErrorCode.FOOD_TRUCK_NOT_OWNED); + } + + // 푸드트럭 호출 가능 지역 삭제 + foodTruckServiceAreaRepository.deleteAllByFoodTruckId(foodTruckId); + + // 푸드트럭 메뉴 삭제 + menuRepository.deleteAllByFoodTruckId(foodTruckId); + + // 푸드트럭 가능한 일정대 삭제 + availableDateRepository.deleteAllByFoodTruckId(foodTruckId); + + // 푸드트럭 관련 평점 삭제 + ratingRepository.deleteAllByFoodTruckId(foodTruckId); + + // 푸드트럭 관련 예약 삭제 + reservationRepository.deleteAllByFoodTruckId(foodTruckId); + + // 푸드트럭 저장한 푸드트럭 삭제 + savedFoodTruckRepository.deleteAllByFoodTruckId(foodTruckId); + + // 푸드트럭 삭제 + foodTruckRepository.delete(foodTruck); + } + + /** + * 커서 기반으로 페이징된 푸드트럭 목록 조회 + */ + private Slice findFoodTrucks(Long ownerId, Long lastCursor, int pageSize) { + return foodTruckRepository.findByOwnerUserIdWithCursor(ownerId, lastCursor, PageRequest.of(0, pageSize)); + } + + /** + * 푸드트럭 목록에 포함된 호출 가능 지역 정보 Map 조회 + */ + private Map> getServiceAreaMap(List foodTrucks) { + List foodTruckIds = foodTrucks.stream() + .map(FoodTruck::getFoodTruckId) + .toList(); + + List serviceAreas = foodTruckServiceAreaRepository + .findAllWithRegionByFoodTruckIdIn(foodTruckIds); + + return serviceAreas.stream() + .collect(Collectors.groupingBy(sa -> sa.getFoodTruck().getFoodTruckId())); + } + + private List mapToMyFoodTruckResponse(List foodTrucks, Map> serviceAreaMap) { + return foodTrucks.stream() + .map(foodTruck -> { + List serviceAreas = serviceAreaMap.getOrDefault(foodTruck.getFoodTruckId(), List.of()); + return MyFoodTruckResponse.of(foodTruck, serviceAreas); + }) + .toList(); + } + + +} diff --git a/src/main/java/konkuk/chacall/domain/owner/application/reservation/OwnerReservationService.java b/src/main/java/konkuk/chacall/domain/owner/application/reservation/OwnerReservationService.java index dd486185..64187cfd 100644 --- a/src/main/java/konkuk/chacall/domain/owner/application/reservation/OwnerReservationService.java +++ b/src/main/java/konkuk/chacall/domain/owner/application/reservation/OwnerReservationService.java @@ -17,7 +17,6 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Map; @@ -25,7 +24,6 @@ @RequiredArgsConstructor @Service -@Transactional(readOnly = true) public class OwnerReservationService { private final ReservationRepository reservationRepository; @@ -50,7 +48,7 @@ public OwnerReservationDetailResponse getReservationDetail(Long ownerId, Long re Reservation reservation = reservationRepository.findByIdWithDetails(reservationId) .orElseThrow(() -> new EntityNotFoundException(ErrorCode.RESERVATION_NOT_FOUND)); - if(!reservation.isOwnedBy(ownerId)) { + if(!reservation.isForFoodTruckOwnedBy(ownerId)) { throw new BusinessException(ErrorCode.RESERVATION_NOT_OWNED); } diff --git a/src/main/java/konkuk/chacall/domain/owner/presentation/OwnerController.java b/src/main/java/konkuk/chacall/domain/owner/presentation/OwnerController.java index 9ab34159..0301e611 100644 --- a/src/main/java/konkuk/chacall/domain/owner/presentation/OwnerController.java +++ b/src/main/java/konkuk/chacall/domain/owner/presentation/OwnerController.java @@ -6,13 +6,11 @@ import jakarta.validation.Valid; import konkuk.chacall.domain.owner.application.OwnerService; import konkuk.chacall.domain.owner.presentation.dto.request.*; -import konkuk.chacall.domain.owner.presentation.dto.response.BankAccountResponse; -import konkuk.chacall.domain.owner.presentation.dto.response.OwnerReservationDetailResponse; -import konkuk.chacall.domain.owner.presentation.dto.response.OwnerReservationHistoryResponse; +import konkuk.chacall.domain.owner.presentation.dto.response.*; import konkuk.chacall.global.common.annotation.ExceptionDescription; import konkuk.chacall.global.common.annotation.UserId; -import konkuk.chacall.domain.owner.presentation.dto.response.ChatTemplateResponse; import konkuk.chacall.global.common.dto.BaseResponse; +import konkuk.chacall.global.common.dto.CursorPagingRequest; import konkuk.chacall.global.common.dto.CursorPagingResponse; import konkuk.chacall.global.common.swagger.SwaggerResponseDescription; import lombok.RequiredArgsConstructor; @@ -170,4 +168,31 @@ public BaseResponse getReservationDetail( ownerId, reservationId)); } + + @Operation( + summary = "나의 푸드트럭 목록 조회 (무한 스크롤)", + description = "사장님 - 나의 푸드트럭 목록을 조회합니다.") + @ExceptionDescription(SwaggerResponseDescription.OWNER_GET_FOOD_TRUCK) + @GetMapping("/me/food-trucks") + public BaseResponse> getMyFoodTrucks( + @Valid @ParameterObject final CursorPagingRequest request, + @Parameter(hidden = true) @UserId final Long ownerId + ) { + return BaseResponse.ok(ownerService.getMyFoodTrucks( + request, + ownerId)); + } + + @Operation( + summary = "나의 푸드트럭 삭제", + description = "사장님 - 푸드트럭을 삭제합니다." + ) + @ExceptionDescription(SwaggerResponseDescription.OWNER_DELETE_FOOD_TRUCK) + @DeleteMapping("/me/food-trucks/{foodTruckId}") + public BaseResponse deleteFoodTruck ( + @PathVariable final Long foodTruckId, + @Parameter(hidden = true) @UserId final Long ownerId) { + ownerService.deleteMyFoodTruck(ownerId, foodTruckId); + return BaseResponse.ok(null); + } } 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 new file mode 100644 index 00000000..e1e71312 --- /dev/null +++ b/src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckResponse.java @@ -0,0 +1,43 @@ +package konkuk.chacall.domain.owner.presentation.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import konkuk.chacall.domain.foodtruck.domain.FoodTruck; +import konkuk.chacall.domain.foodtruck.domain.FoodTruckServiceArea; + +import java.util.List; +import java.util.stream.Collectors; + +public record MyFoodTruckResponse( + @Schema(description = "푸드트럭 식별자", example = "1") + Long foodTruckId, + @Schema(description = "푸드트럭 이미지", example = "image.png") + String imageUrl, + @Schema(description = "푸드트럭 이름", example = "차콜 푸드트럭") + String name, + @Schema(description = "푸드트럭 설명", example = "저희 푸드트럭은 10년간 이어져온...") + String description, + @Schema(description = "운영 가능 시간대", example = "09:00 ~ 21:00") + String activeTime, + @Schema(description = "호출 가능 지역", example = "서울 전체, 경기도 수원시 영통구, 인천 계양구") + String serviceArea +) { + /** + * FoodTruck 엔티티와 연관된 FoodTruckServiceArea 리스트를 사용하여 DTO 를 생성 + * @param foodTruck 푸드트럭 엔티티 + * @param serviceAreas 해당 푸드트럭의 서비스 가능 지역 엔티티 리스트 + * @return MyFoodTruckResponse DTO + */ + public static MyFoodTruckResponse of(FoodTruck foodTruck, List serviceAreas) { + // 대표 이미지 + String mainImageUrl = foodTruck.getFoodTruckPhotoList().getMainPhotoUrl(); + + return new MyFoodTruckResponse( + foodTruck.getFoodTruckId(), + mainImageUrl, + foodTruck.getName(), + foodTruck.getDescription(), + foodTruck.getActiveTime(), + foodTruck.getServiceAreas(serviceAreas) + ); + } +} 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 e6cddf1a..79b7cd02 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 @@ -9,10 +9,10 @@ public record OwnerReservationDetailResponse( @Schema(description = "상대방(손님)의 프로필 이미지 URL", example = "https://image.url/path/profile.jpg") - String userProfileImage, + String profileImage, @Schema(description = "상대방의 이름 또는 닉네임", example = "김차콜") - String username, + String name, @Schema(description = "예약 주소", example = "서울 광진구 화양동 123-45") String address, 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 3ad17d2a..508afb60 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 @@ -10,14 +10,16 @@ public record OwnerReservationHistoryResponse( @Schema(description = "예약 내역 식별자", example = "1") Long reservationId, @Schema(description = "유저(고객) 프로필 이미지", example = "http://image.png") - String userProfileImage, + String profileImage, @Schema(description = "유저(고객) 이름", example = "홍길동") - String username, + String name, @Schema(description = "예약 주소", example = "서울 광진구 화양동") String address, @Schema(description = "예약 날짜 및 운영 시간 정보 리스트 (최대 2개)", example = "[\"2025-09-20 13시~19시\", \"2025-09-21 13시~19시\"]") - List dateTimeInfos + List dateTimeInfos, + @Schema(description = "푸드트럭 이름", example = "차콜 푸드트럭") + String foodTruckName ) { public static OwnerReservationHistoryResponse of(Reservation reservation, User member) { @@ -28,7 +30,8 @@ public static OwnerReservationHistoryResponse of(Reservation reservation, User m member.getProfileImageUrl(), member.getName(), reservation.getReservationInfo().getReservationAddress(), - dateTimeList + dateTimeList, + reservation.getFoodTruck().getName() ); } } diff --git a/src/main/java/konkuk/chacall/domain/region/domain/Region.java b/src/main/java/konkuk/chacall/domain/region/domain/Region.java index 54cebea8..64aaea37 100644 --- a/src/main/java/konkuk/chacall/domain/region/domain/Region.java +++ b/src/main/java/konkuk/chacall/domain/region/domain/Region.java @@ -2,7 +2,9 @@ import jakarta.persistence.*; import konkuk.chacall.global.common.domain.BaseEntity; +import lombok.Getter; +@Getter @Entity @Table(name = "regions") public class Region extends BaseEntity { diff --git a/src/main/java/konkuk/chacall/domain/reservation/domain/model/Reservation.java b/src/main/java/konkuk/chacall/domain/reservation/domain/model/Reservation.java index 39b059f5..b2666e96 100644 --- a/src/main/java/konkuk/chacall/domain/reservation/domain/model/Reservation.java +++ b/src/main/java/konkuk/chacall/domain/reservation/domain/model/Reservation.java @@ -6,9 +6,7 @@ import konkuk.chacall.domain.reservation.domain.value.ReservationStatus; import konkuk.chacall.domain.user.domain.model.User; import konkuk.chacall.global.common.domain.BaseEntity; -import konkuk.chacall.global.common.exception.BusinessException; import konkuk.chacall.global.common.exception.DomainRuleException; -import konkuk.chacall.global.common.exception.code.ErrorCode; import lombok.*; import static konkuk.chacall.global.common.exception.code.ErrorCode.*; @@ -45,8 +43,8 @@ public class Reservation extends BaseEntity { @JoinColumn(name = "food_truck_id", nullable = false) private FoodTruck foodTruck; - public boolean isOwnedBy(Long ownerId) { - return foodTruck.getOwner().getUserId().equals(ownerId); + public boolean isForFoodTruckOwnedBy(Long ownerId) { + return foodTruck.isOwnedBy(ownerId); } public boolean isReservedBy(Long userId) { diff --git a/src/main/java/konkuk/chacall/domain/reservation/domain/repository/ReservationRepository.java b/src/main/java/konkuk/chacall/domain/reservation/domain/repository/ReservationRepository.java index 4d7992d3..5c58649f 100644 --- a/src/main/java/konkuk/chacall/domain/reservation/domain/repository/ReservationRepository.java +++ b/src/main/java/konkuk/chacall/domain/reservation/domain/repository/ReservationRepository.java @@ -6,6 +6,7 @@ import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -24,7 +25,6 @@ Slice findOwnerReservationsByStatusWithCursor( @Param("lastCursor") Long lastCursor, Pageable pageable); - @Query("SELECT r FROM Reservation r " + "JOIN FETCH r.member m " + "JOIN FETCH r.foodTruck ft " + @@ -32,7 +32,6 @@ Slice findOwnerReservationsByStatusWithCursor( "WHERE r.reservationId = :reservationId") Optional findByIdWithDetails(@Param("reservationId") Long reservationId); - @EntityGraph(attributePaths = {"foodTruck"}) @Query("SELECT r FROM Reservation r " + "WHERE r.member.userId = :memberId " + @@ -44,4 +43,8 @@ Slice findMemberReservationsByStatusWithCursor( @Param("status") ReservationStatus status, @Param("lastCursor") Long lastCursor, Pageable pageable); + + @Modifying + @Query("DELETE FROM Reservation r WHERE r.foodTruck.foodTruckId = :foodTruckId") + void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId); } 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 e79a05e1..a6c49f92 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 @@ -69,6 +69,7 @@ public enum ErrorCode implements ResponseCode { * FoodTruck */ FOOD_TRUCK_NOT_FOUND(HttpStatus.NOT_FOUND, 110001, "푸드트럭을 찾을 수 없습니다."), + FOOD_TRUCK_NOT_OWNED(HttpStatus.FORBIDDEN, 110002, "해당 푸드트럭의 소유자가 아닙니다."), /** 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 229c8346..b2578bbf 100644 --- a/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java +++ b/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java @@ -81,6 +81,15 @@ public enum SwaggerResponseDescription { USER_FORBIDDEN, RESERVATION_NOT_FOUND ))), + OWNER_GET_FOOD_TRUCK(new LinkedHashSet<>(Set.of( + USER_NOT_FOUND, + USER_FORBIDDEN + ))), + OWNER_DELETE_FOOD_TRUCK(new LinkedHashSet<>(Set.of( + USER_NOT_FOUND, + USER_FORBIDDEN, + FOOD_TRUCK_NOT_FOUND + ))), // Member