diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/category/service/CategoryService.java b/src/main/java/com/bbteam/budgetbuddies/domain/category/service/CategoryService.java index 92083193..0a75b0c9 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/category/service/CategoryService.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/category/service/CategoryService.java @@ -14,8 +14,10 @@ public interface CategoryService { List getUserCategories(Long userId); - Category handleCategoryChange(Expense expense, ExpenseUpdateRequestDto request, User user); - void deleteCategory(Long id, Long userId); + + Category getCategory(Long categoryId); + + List getUserCategoryList(Long userId); } diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/category/service/CategoryServiceImpl.java b/src/main/java/com/bbteam/budgetbuddies/domain/category/service/CategoryServiceImpl.java index af6ae372..7a010856 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/category/service/CategoryServiceImpl.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/category/service/CategoryServiceImpl.java @@ -5,7 +5,6 @@ import java.util.Optional; import java.util.stream.Collectors; -import com.bbteam.budgetbuddies.domain.expense.repository.ExpenseRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -16,9 +15,8 @@ import com.bbteam.budgetbuddies.domain.category.repository.CategoryRepository; import com.bbteam.budgetbuddies.domain.consumptiongoal.entity.ConsumptionGoal; import com.bbteam.budgetbuddies.domain.consumptiongoal.repository.ConsumptionGoalRepository; -import com.bbteam.budgetbuddies.domain.consumptiongoal.service.ConsumptionGoalService; -import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseUpdateRequestDto; import com.bbteam.budgetbuddies.domain.expense.entity.Expense; +import com.bbteam.budgetbuddies.domain.expense.repository.ExpenseRepository; import com.bbteam.budgetbuddies.domain.user.entity.User; import com.bbteam.budgetbuddies.domain.user.repository.UserRepository; @@ -30,7 +28,6 @@ public class CategoryServiceImpl implements CategoryService { private final CategoryRepository categoryRepository; - private final ConsumptionGoalService consumptionGoalService; private final UserRepository userRepository; private final CategoryConverter categoryConverter; private final ConsumptionGoalRepository consumptionGoalRepository; @@ -40,11 +37,11 @@ public class CategoryServiceImpl implements CategoryService { @Transactional public CategoryResponseDto createCategory(Long userId, CategoryRequestDto categoryRequestDto) { User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("Invalid user ID")); + .orElseThrow(() -> new IllegalArgumentException("Invalid user ID")); // 동일한 이름의 삭제된 카테고리가 존재하는지 확인 Optional existingCategory = categoryRepository.findByNameAndUserIdAndDeletedTrue( - categoryRequestDto.getName(), userId); + categoryRequestDto.getName(), userId); if (existingCategory.isPresent()) { // 삭제된 카테고리가 존재하면 복구 (deleted = false) @@ -54,7 +51,7 @@ public CategoryResponseDto createCategory(Long userId, CategoryRequestDto catego // 해당 카테고리의 삭제된 ConsumptionGoal도 복구 Optional existingConsumptionGoal = consumptionGoalRepository.findByUserAndCategoryAndDeletedTrue( - user, categoryToRestore); + user, categoryToRestore); if (existingConsumptionGoal.isPresent()) { ConsumptionGoal consumptionGoalToRestore = existingConsumptionGoal.get(); @@ -65,13 +62,13 @@ public CategoryResponseDto createCategory(Long userId, CategoryRequestDto catego } else { // ConsumptionGoal이 존재하지 않으면 새로 생성 ConsumptionGoal newConsumptionGoal = ConsumptionGoal.builder() - .user(user) - .category(categoryToRestore) - .goalMonth(LocalDate.now().withDayOfMonth(1)) // 현재 달로 목표 설정 - .consumeAmount(0L) - .goalAmount(0L) - .deleted(false) // 생성할 때 삭제 상태가 아니도록 - .build(); + .user(user) + .category(categoryToRestore) + .goalMonth(LocalDate.now().withDayOfMonth(1)) // 현재 달로 목표 설정 + .consumeAmount(0L) + .goalAmount(0L) + .deleted(false) // 생성할 때 삭제 상태가 아니도록 + .build(); consumptionGoalRepository.save(newConsumptionGoal); } @@ -83,13 +80,13 @@ public CategoryResponseDto createCategory(Long userId, CategoryRequestDto catego // 새로운 카테고리에 대한 ConsumptionGoal도 생성 ConsumptionGoal newConsumptionGoal = ConsumptionGoal.builder() - .user(user) - .category(newCategory) - .goalMonth(LocalDate.now().withDayOfMonth(1)) // 현재 달로 목표 설정 - .consumeAmount(0L) - .goalAmount(0L) - .deleted(false) // 생성할 때 삭제 상태가 아니도록 - .build(); + .user(user) + .category(newCategory) + .goalMonth(LocalDate.now().withDayOfMonth(1)) // 현재 달로 목표 설정 + .consumeAmount(0L) + .goalAmount(0L) + .deleted(false) // 생성할 때 삭제 상태가 아니도록 + .build(); consumptionGoalRepository.save(newConsumptionGoal); return categoryConverter.toCategoryResponseDto(newCategory); @@ -100,21 +97,15 @@ public CategoryResponseDto createCategory(Long userId, CategoryRequestDto catego public List getUserCategories(Long userId) { userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found with id: " + userId)); + .orElseThrow(() -> new IllegalArgumentException("User not found with id: " + userId)); List categories = categoryRepository.findUserCategoryByUserId(userId); return categories.stream().map(categoryConverter::toCategoryResponseDto).collect(Collectors.toList()); } @Override - @Transactional(readOnly = true) - public Category handleCategoryChange(Expense expense, ExpenseUpdateRequestDto request, User user) { - Category categoryToReplace = categoryRepository.findById(request.getCategoryId()) - .orElseThrow(() -> new IllegalArgumentException("Not found category")); - - consumptionGoalService.recalculateConsumptionAmount(expense, request, user); - - return categoryToReplace; + public Category getCategory(Long id) { + return categoryRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Not found category")); } @Override @@ -131,15 +122,15 @@ public void deleteCategory(Long categoryId, Long userId) { // 현재 월에 해당하는 삭제되지 않은 Expense 조회 (deleted = false) List currentMonthExpenses = expenseRepository.findByCategoryIdAndUserIdAndExpenseDateBetweenAndDeletedFalse( - categoryId, userId, startOfMonth.atStartOfDay(), endOfMonth.atTime(23, 59, 59)); + categoryId, userId, startOfMonth.atStartOfDay(), endOfMonth.atTime(23, 59, 59)); long totalAmount = currentMonthExpenses.stream() - .mapToLong(Expense::getAmount) - .sum(); + .mapToLong(Expense::getAmount) + .sum(); // category_id = 10(기타 카테고리)의 소비 목표 업데이트 (custom 카테고리 삭제로 인한 소비 내역은 삭제되지 않고 기타 카테고리로..) ConsumptionGoal goal = consumptionGoalRepository.findByCategoryIdAndUserId(10L, userId) - .orElseThrow(() -> new IllegalArgumentException("No consumption goal found for category_id 10.")); + .orElseThrow(() -> new IllegalArgumentException("No consumption goal found for category_id 10.")); goal.setConsumeAmount(goal.getConsumeAmount() + totalAmount); consumptionGoalRepository.save(goal); @@ -161,4 +152,8 @@ public void deleteCategory(Long categoryId, Long userId) { }); } + @Override + public List getUserCategoryList(Long userId) { + return categoryRepository.findUserCategoryByUserId(userId); + } } \ No newline at end of file diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/controller/ConsumptionGoalApi.java b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/controller/ConsumptionGoalApi.java index 69094dbc..09d38867 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/controller/ConsumptionGoalApi.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/controller/ConsumptionGoalApi.java @@ -14,6 +14,7 @@ import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.PeerInfoResponseDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.TopCategoryConsumptionDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.TopGoalCategoryResponseDto; +import com.bbteam.budgetbuddies.domain.user.dto.UserDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -53,10 +54,10 @@ ApiResponse> getAllConsumptionGoalCatego @Operation(summary = "[User] 소비 목표 조회", description = "date={yyyy-MM-dd} 형식의 query string을 통해서 사용자의 목표 달을 조회하는 API 입니다.") @Parameters({@Parameter(name = "date", description = "yyyy-MM-dd 형식으로 목표 달의 소비를 조회")}) - ApiResponse findUserConsumptionGoal(LocalDate date, Long userId); + ApiResponse findUserConsumptionGoal(LocalDate date, UserDto.AuthUserDto user); @Operation(summary = "[User] 이번 달 소비 목표 수정", description = "다른 달의 소비 목표를 업데이트하는 것은 불가능하고 오직 이번 달의 소비 목표만 업데이트 하는 API 입니다.") - ResponseEntity updateOrElseGenerateConsumptionGoal(Long userId, + ResponseEntity updateOrElseGenerateConsumptionGoal(UserDto.AuthUserDto user, ConsumptionGoalListRequestDto consumptionGoalListRequestDto); @Operation(summary = "[User] 또래들이 가장 많이한 소비 카테고리 조회 Top3", description = "특정 사용자의 또래 소비 카테고리별 소비 건 수을 조회하는 API 입니다.") diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/controller/ConsumptionGoalController.java b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/controller/ConsumptionGoalController.java index 4a75809d..b65b5904 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/controller/ConsumptionGoalController.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/controller/ConsumptionGoalController.java @@ -6,7 +6,6 @@ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -23,6 +22,8 @@ import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.TopCategoryConsumptionDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.TopGoalCategoryResponseDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.service.ConsumptionGoalService; +import com.bbteam.budgetbuddies.domain.user.dto.UserDto; +import com.bbteam.budgetbuddies.global.security.utils.AuthUser; import lombok.RequiredArgsConstructor; @@ -67,22 +68,21 @@ public ApiResponse getPeerInfo(@RequestParam(name = "userId return ApiResponse.onSuccess(response); } - @GetMapping("/{userId}") + @GetMapping() public ApiResponse findUserConsumptionGoal( - @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date, @PathVariable Long userId) { + @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date, @AuthUser UserDto.AuthUserDto user) { - ConsumptionGoalResponseListDto response = consumptionGoalService.findUserConsumptionGoalList(userId, date); - - return ApiResponse.onSuccess(response); + return ApiResponse.onSuccess(consumptionGoalService.findUserConsumptionGoalList(user.getId(), date)); } @Override - @PostMapping("/{userId}") - public ResponseEntity updateOrElseGenerateConsumptionGoal(@PathVariable Long userId, + @PostMapping() + public ResponseEntity updateOrElseGenerateConsumptionGoal( + @AuthUser UserDto.AuthUserDto user, @RequestBody ConsumptionGoalListRequestDto consumptionGoalListRequestDto) { return ResponseEntity.ok() - .body(consumptionGoalService.updateConsumptionGoals(userId, consumptionGoalListRequestDto)); + .body(consumptionGoalService.updateConsumptionGoals(user.getId(), consumptionGoalListRequestDto)); } @GetMapping("/categories/top-consumptions/top-3") diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/converter/ConsumptionGoalConverter.java b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/converter/ConsumptionGoalConverter.java index 5cd7a72a..44c0dd08 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/converter/ConsumptionGoalConverter.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/converter/ConsumptionGoalConverter.java @@ -1,11 +1,11 @@ package com.bbteam.budgetbuddies.domain.consumptiongoal.converter; import java.time.LocalDate; +import java.util.Comparator; import java.util.List; import org.springframework.stereotype.Component; -import com.bbteam.budgetbuddies.domain.category.entity.Category; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.ConsumptionAnalysisResponseDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.ConsumptionGoalResponseDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.ConsumptionGoalResponseListDto; @@ -17,15 +17,6 @@ @Component public class ConsumptionGoalConverter { - public ConsumptionGoalResponseDto toConsumptionGoalResponseDto(Category category) { - return ConsumptionGoalResponseDto.builder() - .categoryName(category.getName()) - .categoryId(category.getId()) - .goalAmount(0L) - .consumeAmount(0L) - .build(); - } - public ConsumptionGoalResponseDto toConsumptionGoalResponseDto(ConsumptionGoal consumptionGoal) { return ConsumptionGoalResponseDto.builder() .categoryName(consumptionGoal.getCategory().getName()) @@ -35,27 +26,24 @@ public ConsumptionGoalResponseDto toConsumptionGoalResponseDto(ConsumptionGoal c .build(); } - public ConsumptionGoalResponseDto toConsumptionGoalResponseDtoFromPreviousGoal(ConsumptionGoal consumptionGoal) { - return ConsumptionGoalResponseDto.builder() - .categoryName(consumptionGoal.getCategory().getName()) - .categoryId(consumptionGoal.getCategory().getId()) - .goalAmount(consumptionGoal.getGoalAmount()) - .consumeAmount(0L) - .build(); + public ConsumptionGoalResponseListDto toConsumptionGoalResponseListDto( + List consumptionGoalList, LocalDate goalMonth) { - } + List consumptionGoalResponseList = consumptionGoalList + .stream() + .map(this::toConsumptionGoalResponseDto) + .sorted(Comparator.comparingLong(ConsumptionGoalResponseDto::getRemainingBalance).reversed()) + .toList(); - public ConsumptionGoalResponseListDto toConsumptionGoalResponseListDto( - List consumptionGoalList, LocalDate goalMonth) { - Long totalGoalAmount = sumTotalGoalAmount(consumptionGoalList); - Long totalConsumptionAmount = sumTotalConsumptionAmount(consumptionGoalList); + Long totalGoalAmount = sumTotalGoalAmount(consumptionGoalResponseList); + Long totalConsumptionAmount = sumTotalConsumptionAmount(consumptionGoalResponseList); return ConsumptionGoalResponseListDto.builder() .goalMonth(goalMonth) .totalGoalAmount(totalGoalAmount) .totalConsumptionAmount(totalConsumptionAmount) .totalRemainingBalance(totalGoalAmount - totalConsumptionAmount) - .consumptionGoalList(consumptionGoalList) + .consumptionGoalList(consumptionGoalResponseList) .build(); } diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/entity/ConsumptionGoal.java b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/entity/ConsumptionGoal.java index 71d2accc..229f2df3 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/entity/ConsumptionGoal.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/entity/ConsumptionGoal.java @@ -48,7 +48,7 @@ public class ConsumptionGoal extends BaseEntity { @JoinColumn(name = "category_id") private Category category; - public void updateConsumeAmount(Long amount) { + public void addConsumeAmount(Long amount) { this.consumeAmount += amount; } diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/repository/ConsumptionGoalRepository.java b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/repository/ConsumptionGoalRepository.java index ad52c9c8..72284b9b 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/repository/ConsumptionGoalRepository.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/repository/ConsumptionGoalRepository.java @@ -25,8 +25,7 @@ public interface ConsumptionGoalRepository extends JpaRepository findConsumptionGoalByUserIdAndGoalMonth(Long userId, LocalDate goalMonth); - Optional findConsumptionGoalByUserAndCategoryAndGoalMonth(User user, Category category, - LocalDate goalMonth); + Optional findByUserAndCategoryAndGoalMonth(User user, Category category, LocalDate goalMonth); @Query("SELECT AVG(cg.consumeAmount) FROM ConsumptionGoal cg " + "JOIN cg.category c " + diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/service/ConsumptionGoalService.java b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/service/ConsumptionGoalService.java index de727f3d..aecf4416 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/service/ConsumptionGoalService.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/service/ConsumptionGoalService.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Service; +import com.bbteam.budgetbuddies.domain.category.entity.Category; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.AllConsumptionCategoryResponseDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.ConsumptionAnalysisResponseDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.ConsumptionGoalListRequestDto; @@ -13,8 +14,7 @@ import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.PeerInfoResponseDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.TopCategoryConsumptionDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.TopGoalCategoryResponseDto; -import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseUpdateRequestDto; -import com.bbteam.budgetbuddies.domain.expense.entity.Expense; +import com.bbteam.budgetbuddies.domain.consumptiongoal.entity.ConsumptionGoal; import com.bbteam.budgetbuddies.domain.user.entity.User; @Service @@ -35,7 +35,8 @@ ConsumptionGoalResponseListDto updateConsumptionGoals(Long userId, ConsumptionAnalysisResponseDto getTopCategoryAndConsumptionAmount(Long userId); - void recalculateConsumptionAmount(Expense expense, ExpenseUpdateRequestDto request, User user); + void recalculateConsumptionAmount(ConsumptionGoal beforeConsumptionGoal, Long beforeAmount, + ConsumptionGoal afterConsumptionGoal, Long afterAmount); void updateConsumeAmount(Long userId, Long categoryId, Long amount); @@ -52,4 +53,6 @@ List getAllConsumptionCategories(Long userId, MonthReportResponseDto getMonthReport(Long userId); String getConsumptionMention(Long userId); + + ConsumptionGoal getUserConsumptionGoal(User user, Category category, LocalDate goalDate); } diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/service/ConsumptionGoalServiceImpl.java b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/service/ConsumptionGoalServiceImpl.java index 73e1ba13..6bc6b4da 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/service/ConsumptionGoalServiceImpl.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/consumptiongoal/service/ConsumptionGoalServiceImpl.java @@ -25,6 +25,7 @@ import com.bbteam.budgetbuddies.domain.category.entity.Category; import com.bbteam.budgetbuddies.domain.category.repository.CategoryRepository; +import com.bbteam.budgetbuddies.domain.category.service.CategoryService; import com.bbteam.budgetbuddies.domain.consumptiongoal.converter.ConsumptionGoalConverter; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.AllConsumptionCategoryResponseDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.AvgConsumptionGoalDto; @@ -33,7 +34,6 @@ import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.ConsumptionAnalysisResponseDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.ConsumptionGoalListRequestDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.ConsumptionGoalRequestDto; -import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.ConsumptionGoalResponseDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.ConsumptionGoalResponseListDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.MonthReportResponseDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.MyConsumptionGoalDto; @@ -42,13 +42,12 @@ import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.TopGoalCategoryResponseDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.entity.ConsumptionGoal; import com.bbteam.budgetbuddies.domain.consumptiongoal.repository.ConsumptionGoalRepository; -import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseUpdateRequestDto; -import com.bbteam.budgetbuddies.domain.expense.entity.Expense; import com.bbteam.budgetbuddies.domain.expense.repository.ExpenseRepository; import com.bbteam.budgetbuddies.domain.gemini.service.GeminiService; import com.bbteam.budgetbuddies.domain.openai.service.OpenAiService; import com.bbteam.budgetbuddies.domain.user.entity.User; import com.bbteam.budgetbuddies.domain.user.repository.UserRepository; +import com.bbteam.budgetbuddies.domain.user.service.UserService; import com.bbteam.budgetbuddies.enums.Gender; import lombok.RequiredArgsConstructor; @@ -62,7 +61,9 @@ public class ConsumptionGoalServiceImpl implements ConsumptionGoalService { private final ConsumptionGoalRepository consumptionGoalRepository; private final ExpenseRepository expenseRepository; private final CategoryRepository categoryRepository; + private final CategoryService categoryService; private final UserRepository userRepository; + private final UserService userService; private final GeminiService geminiService; private final OpenAiService openAiService; @@ -465,140 +466,48 @@ private double calculateMedian(List values) { public ConsumptionGoalResponseListDto updateConsumptionGoals(Long userId, ConsumptionGoalListRequestDto consumptionGoalListRequestDto) { LocalDate thisMonth = LocalDate.now().withDayOfMonth(1); - User user = userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("Not found user")); + User user = userService.getUser(userId); List updatedConsumptionGoal = consumptionGoalListRequestDto.getConsumptionGoalList() .stream() .map(c -> updateConsumptionGoalWithRequestDto(user, c, thisMonth)) .toList(); - List response = consumptionGoalRepository.saveAll(updatedConsumptionGoal) - .stream() - .map(consumptionGoalConverter::toConsumptionGoalResponseDto) - .toList(); - - return consumptionGoalConverter.toConsumptionGoalResponseListDto(response, thisMonth); + return consumptionGoalConverter.toConsumptionGoalResponseListDto(updatedConsumptionGoal, thisMonth); } private ConsumptionGoal updateConsumptionGoalWithRequestDto(User user, ConsumptionGoalRequestDto consumptionGoalRequestDto, LocalDate goalMonth) { - Category category = categoryRepository.findById(consumptionGoalRequestDto.getCategoryId()) - .orElseThrow(() -> new IllegalArgumentException("Not found Category")); + Category category = categoryService.getCategory(consumptionGoalRequestDto.getCategoryId()); - ConsumptionGoal consumptionGoal = findOrElseGenerateConsumptionGoal(user, category, goalMonth); + ConsumptionGoal consumptionGoal = this.getUserConsumptionGoal(user, category, goalMonth); consumptionGoal.updateGoalAmount(consumptionGoalRequestDto.getGoalAmount()); return consumptionGoal; } - private ConsumptionGoal findOrElseGenerateConsumptionGoal(User user, Category category, LocalDate goalMonth) { - return consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth(user, category, goalMonth) - .orElseGet(() -> generateNewConsumptionGoal(user, category, goalMonth)); - } - - private ConsumptionGoal generateNewConsumptionGoal(User user, Category category, LocalDate goalMonth) { - return ConsumptionGoal.builder() - .goalMonth(goalMonth) - .user(user) - .category(category) - .consumeAmount(0L) - .goalAmount(0L) - .build(); - } - @Override - @Transactional(readOnly = true) + @Transactional public ConsumptionGoalResponseListDto findUserConsumptionGoalList(Long userId, LocalDate date) { LocalDate goalMonth = date.withDayOfMonth(1); - Map goalMap = initializeGoalMap(userId); - - updateGoalMapWithPrevious(userId, goalMonth, goalMap); - updateGoalMapWithCurrentMonth(userId, goalMonth, goalMap); + User user = userService.getUser(userId); + List categoryList = categoryService.getUserCategoryList(userId); - List consumptionGoalList = new ArrayList<>(goalMap.values()); - - return consumptionGoalConverter.toConsumptionGoalResponseListDto( - orderByRemainingBalanceDescending(consumptionGoalList), goalMonth); - } - - private Map initializeGoalMap(Long userId) { - return categoryRepository.findUserCategoryByUserId(userId) - .stream() - .collect(Collectors.toMap(Category::getId, consumptionGoalConverter::toConsumptionGoalResponseDto)); - } - - private void updateGoalMapWithPrevious(Long userId, LocalDate goalMonth, - Map goalMap) { - goalMap.keySet() - .stream() - .map(categoryId -> consumptionGoalRepository.findLatelyGoal(userId, categoryId, goalMonth.minusMonths(1))) - .filter(Optional::isPresent) - .map(Optional::get) - .map(consumptionGoalConverter::toConsumptionGoalResponseDtoFromPreviousGoal) - .forEach(goal -> goalMap.put(goal.getCategoryId(), goal)); - } - - private void updateGoalMapWithCurrentMonth(Long userId, LocalDate goalMonth, - Map goalMap) { - consumptionGoalRepository.findConsumptionGoalByUserIdAndGoalMonth(userId, goalMonth) + List thisMonthUserConsumptionGoal = categoryList .stream() - .map(consumptionGoalConverter::toConsumptionGoalResponseDto) - .forEach(goal -> goalMap.put(goal.getCategoryId(), goal)); - } - - private List orderByRemainingBalanceDescending( - List consumptionGoalList) { - return consumptionGoalList.stream() - .sorted(Comparator.comparingLong(ConsumptionGoalResponseDto::getRemainingBalance).reversed()) + .map(category -> this.getUserConsumptionGoal(user, category, goalMonth)) .toList(); + + return consumptionGoalConverter.toConsumptionGoalResponseListDto(thisMonthUserConsumptionGoal, goalMonth); } @Override @Transactional - public void recalculateConsumptionAmount(Expense expense, ExpenseUpdateRequestDto request, User user) { - restorePreviousGoalConsumptionAmount(expense, user); - calculatePresentGoalConsumptionAmount(request, user); - } - - private void restorePreviousGoalConsumptionAmount(Expense expense, User user) { - ConsumptionGoal previousConsumptionGoal = consumptionGoalRepository.findLatelyGoal(user.getId(), - expense.getCategory().getId(), expense.getExpenseDate().toLocalDate().withDayOfMonth(1)) - .orElseThrow(() -> new IllegalArgumentException("Not found consumptionGoal")); - - previousConsumptionGoal.restoreConsumeAmount(expense.getAmount()); - consumptionGoalRepository.save(previousConsumptionGoal); - } - - private void calculatePresentGoalConsumptionAmount(ExpenseUpdateRequestDto request, User user) { - Category categoryToReplace = categoryRepository.findById(request.getCategoryId()) - .orElseThrow(() -> new IllegalArgumentException("Not found category")); - - ConsumptionGoal consumptionGoal = consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth( - user, categoryToReplace, request.getExpenseDate().toLocalDate().withDayOfMonth(1)) - .orElseGet(() -> this.generateGoalByPreviousOrElseNew(user, categoryToReplace, - request.getExpenseDate().toLocalDate().withDayOfMonth(1))); - - consumptionGoal.updateConsumeAmount(request.getAmount()); - consumptionGoalRepository.save(consumptionGoal); - } - - private ConsumptionGoal generateGoalByPreviousOrElseNew(User user, Category category, LocalDate goalMonth) { - LocalDate previousMonth = goalMonth.minusMonths(1); - - return consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth(user, category, previousMonth) - .map(this::generateGoalByPrevious) - .orElseGet(() -> generateNewConsumptionGoal(user, category, goalMonth)); - } - - private ConsumptionGoal generateGoalByPrevious(ConsumptionGoal consumptionGoal) { - return ConsumptionGoal.builder() - .goalMonth(consumptionGoal.getGoalMonth().plusMonths(1)) - .user(consumptionGoal.getUser()) - .category(consumptionGoal.getCategory()) - .consumeAmount(0L) - .goalAmount(consumptionGoal.getGoalAmount()) - .build(); + public void recalculateConsumptionAmount(ConsumptionGoal beforeConsumptionGoal, Long beforeAmount, + ConsumptionGoal afterConsumptionGoal, Long afterAmount) { + beforeConsumptionGoal.restoreConsumeAmount(beforeAmount); + afterConsumptionGoal.addConsumeAmount(afterAmount); } @Override @@ -609,10 +518,10 @@ public void updateConsumeAmount(Long userId, Long categoryId, Long amount) { .orElseThrow(() -> new IllegalArgumentException("Not found Category")); LocalDate thisMonth = LocalDate.now().withDayOfMonth(1); - ConsumptionGoal consumptionGoal = consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth( + ConsumptionGoal consumptionGoal = consumptionGoalRepository.findByUserAndCategoryAndGoalMonth( user, category, thisMonth).orElseGet(() -> generateNewConsumptionGoal(user, category, thisMonth)); - consumptionGoal.updateConsumeAmount(amount); + consumptionGoal.addConsumeAmount(amount); consumptionGoalRepository.save(consumptionGoal); } @@ -624,7 +533,7 @@ public void decreaseConsumeAmount(Long userId, Long categoryId, Long amount, Loc .orElseThrow(() -> new IllegalArgumentException("Not found Category")); LocalDate goalMonth = expenseDate.withDayOfMonth(1); - ConsumptionGoal consumptionGoal = consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth( + ConsumptionGoal consumptionGoal = consumptionGoalRepository.findByUserAndCategoryAndGoalMonth( user, category, goalMonth).orElseThrow(() -> new IllegalArgumentException("Not found ConsumptionGoal")); consumptionGoal.decreaseConsumeAmount(amount); @@ -640,12 +549,12 @@ public void updateOrCreateDeletedConsumptionGoal(Long userId, Long categoryId, L .orElseThrow(() -> new IllegalArgumentException("Invalid category ID")); // 해당 월의 ConsumptionGoal이 존재하는지 확인 - Optional existingGoal = consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth( + Optional existingGoal = consumptionGoalRepository.findByUserAndCategoryAndGoalMonth( user, category, goalMonth); if (existingGoal.isPresent()) { // 존재하는 경우, consumeAmount 업데이트 ConsumptionGoal consumptionGoal = existingGoal.get(); - consumptionGoal.updateConsumeAmount(amount); + consumptionGoal.addConsumeAmount(amount); consumptionGoalRepository.save(consumptionGoal); } else { // 존재하지 않는 경우, 새로운 ConsumptionGoal을 생성 (이 때 목표 금액은 0) ConsumptionGoal newGoal = ConsumptionGoal.builder() @@ -656,7 +565,7 @@ public void updateOrCreateDeletedConsumptionGoal(Long userId, Long categoryId, L .goalAmount(0L) .build(); - newGoal.updateConsumeAmount(amount); // 신규 생성된 목표에 소비 금액 추가 + newGoal.addConsumeAmount(amount); // 신규 생성된 목표에 소비 금액 추가 consumptionGoalRepository.save(newGoal); } } @@ -785,7 +694,7 @@ private String getMainComment(List list) { } } Optional minCategory = categoryRepository.findById(minCategoryId); - + if (minCategory.isEmpty()) { throw new IllegalArgumentException("해당 카테고리를 찾을 수 없습니다."); } @@ -872,4 +781,41 @@ public String getConsumptionMention(Long userId) { // return geminiService.getContents(basePrompt); } + @Override + @Transactional + public ConsumptionGoal getUserConsumptionGoal(User user, Category category, LocalDate goalDate) { + LocalDate goalMonth = goalDate.withDayOfMonth(1); + + return consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, category, goalMonth) + .orElseGet(() -> this.generateGoalFromPreviousOrNew(user, category, goalMonth)); + } + + private ConsumptionGoal generateGoalFromPreviousOrNew(User user, Category category, LocalDate goalMonth) { + LocalDate previousMonth = goalMonth.minusMonths(1); + + return consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, category, previousMonth) + .map(this::generateGoalByPrevious) + .orElseGet(() -> generateNewConsumptionGoal(user, category, goalMonth)); + } + + private ConsumptionGoal generateGoalByPrevious(ConsumptionGoal consumptionGoal) { + return consumptionGoalRepository.save(ConsumptionGoal.builder() + .goalMonth(consumptionGoal.getGoalMonth().plusMonths(1)) + .user(consumptionGoal.getUser()) + .category(consumptionGoal.getCategory()) + .consumeAmount(0L) + .goalAmount(consumptionGoal.getGoalAmount()) + .build()); + } + + private ConsumptionGoal generateNewConsumptionGoal(User user, Category category, LocalDate goalMonth) { + return consumptionGoalRepository.save(ConsumptionGoal.builder() + .goalMonth(goalMonth) + .user(user) + .category(category) + .consumeAmount(0L) + .goalAmount(0L) + .build()); + } + } \ No newline at end of file diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/expense/controller/ExpenseApi.java b/src/main/java/com/bbteam/budgetbuddies/domain/expense/controller/ExpenseApi.java index 50a8cc9a..45856956 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/expense/controller/ExpenseApi.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/expense/controller/ExpenseApi.java @@ -2,20 +2,16 @@ import java.time.LocalDate; -import org.springframework.data.repository.query.Param; -import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; +import com.bbteam.budgetbuddies.domain.expense.dto.DetailExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseRequestDto; -import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseUpdateRequestDto; import com.bbteam.budgetbuddies.domain.expense.dto.MonthlyExpenseResponseDto; +import com.bbteam.budgetbuddies.domain.user.dto.UserDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -31,9 +27,9 @@ public interface ExpenseApi { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH003", description = "access 토큰을 주세요!", content = @Content(schema = @Schema(implementation = ApiResponse.class))), @ApiResponse(responseCode = "AUTH004", description = "access 토큰 만료", content = @Content(schema = @Schema(implementation = ApiResponse.class))), @ApiResponse(responseCode = "AUTH006", description = "access 토큰 모양이 이상함", content = @Content(schema = @Schema(implementation = ApiResponse.class)))}) - ResponseEntity createExpense( - @Parameter(description = "user_id") @PathVariable Long userId, - @Parameter(description = "category_id, amount, description, expenseDate") @RequestBody ExpenseRequestDto expenseRequestDto + ResponseEntity createExpense( + @Parameter(description = "user_id") @PathVariable Long userId, + @Parameter(description = "category_id, amount, description, expenseDate") @RequestBody ExpenseRequestDto expenseRequestDto ); @Operation(summary = "[User] 월별 소비 조회", description = "무한 스크롤을 통한 조회로 예상하여 Slice를 통해서 조회") @@ -43,8 +39,8 @@ ResponseEntity createExpense( @ApiResponse(responseCode = "AUTH004", description = "access 토큰 만료", content = @Content(schema = @Schema(implementation = ApiResponse.class))), @ApiResponse(responseCode = "AUTH006", description = "access 토큰 모양이 이상함", content = @Content(schema = @Schema(implementation = ApiResponse.class)))}) ResponseEntity findExpensesForMonth( - @PathVariable @Param("userId") Long userId, - @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date); + UserDto.AuthUserDto user, + LocalDate date); @Operation(summary = "[User] 단일 소비 조회하기", description = "queryParameter를 통해 소비 Id를 전달 받아서 응답값을 조회") @ApiResponses({ @@ -52,8 +48,7 @@ ResponseEntity findExpensesForMonth( @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH003", description = "access 토큰을 주세요!", content = @Content(schema = @Schema(implementation = ApiResponse.class))), @ApiResponse(responseCode = "AUTH004", description = "access 토큰 만료", content = @Content(schema = @Schema(implementation = ApiResponse.class))), @ApiResponse(responseCode = "AUTH006", description = "access 토큰 모양이 이상함", content = @Content(schema = @Schema(implementation = ApiResponse.class)))}) - @GetMapping("/{userId}/{expenseId}") - ResponseEntity findExpense(@Param("userId") Long userId, @Param("expenseId") Long expenseId); + ResponseEntity findExpense(UserDto.AuthUserDto user, Long expenseId); @Operation(summary = "[User] 단일 소비 업데이트하기", description = "소비 아이디와 카테고리 아이디, amount(소비 금액)을 body에 담아서 소비를 업데이트") @ApiResponses({ @@ -61,10 +56,7 @@ ResponseEntity findExpensesForMonth( @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH003", description = "access 토큰을 주세요!", content = @Content(schema = @Schema(implementation = ApiResponse.class))), @ApiResponse(responseCode = "AUTH004", description = "access 토큰 만료", content = @Content(schema = @Schema(implementation = ApiResponse.class))), @ApiResponse(responseCode = "AUTH006", description = "access 토큰 모양이 이상함", content = @Content(schema = @Schema(implementation = ApiResponse.class)))}) - @GetMapping("/{userId}/{expenseId}") - @PostMapping("/{userId}") - ResponseEntity updateExpense(@PathVariable @Param("userId") Long userId, - @RequestBody ExpenseUpdateRequestDto request); + ResponseEntity updateExpense(UserDto.AuthUserDto user, ExpenseUpdateRequestDto request); @Operation(summary = "[User] 소비 내역 삭제", description = "사용자가 소비 내역을 삭제합니다.") @ApiResponses({ diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/expense/controller/ExpenseController.java b/src/main/java/com/bbteam/budgetbuddies/domain/expense/controller/ExpenseController.java index c6499330..75329f71 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/expense/controller/ExpenseController.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/expense/controller/ExpenseController.java @@ -15,10 +15,12 @@ import org.springframework.web.bind.annotation.RestController; import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseRequestDto; -import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseResponseDto; +import com.bbteam.budgetbuddies.domain.expense.dto.DetailExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseUpdateRequestDto; import com.bbteam.budgetbuddies.domain.expense.dto.MonthlyExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.service.ExpenseService; +import com.bbteam.budgetbuddies.domain.user.dto.UserDto; +import com.bbteam.budgetbuddies.global.security.utils.AuthUser; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -31,41 +33,37 @@ public class ExpenseController implements ExpenseApi { @Override @PostMapping("/add/{userId}") - public ResponseEntity createExpense( - @Parameter(description = "user_id") @PathVariable Long userId, - @Parameter(description = "category_id, amount, description, expenseDate") @RequestBody ExpenseRequestDto expenseRequestDto) { - ExpenseResponseDto response = expenseService.createExpense(userId, expenseRequestDto); + public ResponseEntity createExpense( + @Parameter(description = "user_id") @PathVariable Long userId, + @Parameter(description = "category_id, amount, description, expenseDate") @RequestBody ExpenseRequestDto expenseRequestDto) { + DetailExpenseResponseDto response = expenseService.createExpense(userId, expenseRequestDto); return ResponseEntity.ok(response); } @Override - @GetMapping("/{userId}") - public ResponseEntity findExpensesForMonth( - @PathVariable @Param("userId") Long userId, + @GetMapping() + public ResponseEntity findExpensesForMonth(@AuthUser UserDto.AuthUserDto user, @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) { - return ResponseEntity.ok(expenseService.getMonthlyExpense(userId, date)); + return ResponseEntity.ok(expenseService.getMonthlyExpense(user.getId(), date)); } @Override - @GetMapping("/{userId}/{expenseId}") - public ResponseEntity findExpense(@PathVariable @Param("userId") Long userId, + @GetMapping("/{expenseId}") + public ResponseEntity findExpense(@AuthUser UserDto.AuthUserDto user, @PathVariable @Param("expenseId") Long expenseId) { - return ResponseEntity.ok(expenseService.findExpenseResponseFromUserIdAndExpenseId(userId, expenseId)); + return ResponseEntity.ok(expenseService.findDetailExpenseResponse(user.getId(), expenseId)); } @Override - @PostMapping("/{userId}") - public ResponseEntity updateExpense(@PathVariable @Param("userId") Long userId, + @PostMapping() + public ResponseEntity updateExpense(@AuthUser UserDto.AuthUserDto user, @RequestBody ExpenseUpdateRequestDto request) { - ExpenseResponseDto response = expenseService.updateExpense(userId, request); - return ResponseEntity.ok(response); + return ResponseEntity.ok(expenseService.updateExpense(user.getId(), request)); } @DeleteMapping("/delete/{expenseId}") - public ResponseEntity deleteExpense( - @Parameter(description = "expense_id") - @PathVariable Long expenseId) { + public ResponseEntity deleteExpense(@Parameter(description = "expense_id") @PathVariable Long expenseId) { expenseService.deleteExpense(expenseId); return ResponseEntity.ok("Successfully deleted expense!"); } diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/expense/converter/ExpenseConverter.java b/src/main/java/com/bbteam/budgetbuddies/domain/expense/converter/ExpenseConverter.java index 665849d1..3c639164 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/expense/converter/ExpenseConverter.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/expense/converter/ExpenseConverter.java @@ -13,7 +13,7 @@ import com.bbteam.budgetbuddies.domain.expense.dto.CompactExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.dto.DailyExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseRequestDto; -import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseResponseDto; +import com.bbteam.budgetbuddies.domain.expense.dto.DetailExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.dto.MonthlyExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.entity.Expense; import com.bbteam.budgetbuddies.domain.user.entity.User; @@ -31,10 +31,9 @@ public Expense toExpenseEntity(ExpenseRequestDto expenseRequestDto, User user, C .build(); } - public ExpenseResponseDto toExpenseResponseDto(Expense expense) { - return ExpenseResponseDto.builder() + public DetailExpenseResponseDto toDetailExpenseResponseDto(Expense expense) { + return DetailExpenseResponseDto.builder() .expenseId(expense.getId()) - .userId(expense.getUser().getId()) .categoryId(expense.getCategory().getId()) .categoryName(expense.getCategory().getName()) .amount(expense.getAmount()) diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/expense/dto/ExpenseResponseDto.java b/src/main/java/com/bbteam/budgetbuddies/domain/expense/dto/DetailExpenseResponseDto.java similarity index 91% rename from src/main/java/com/bbteam/budgetbuddies/domain/expense/dto/ExpenseResponseDto.java rename to src/main/java/com/bbteam/budgetbuddies/domain/expense/dto/DetailExpenseResponseDto.java index f0996be0..ef0fdef5 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/expense/dto/ExpenseResponseDto.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/expense/dto/DetailExpenseResponseDto.java @@ -13,9 +13,8 @@ @Builder @NoArgsConstructor @AllArgsConstructor -public class ExpenseResponseDto { +public class DetailExpenseResponseDto { private Long expenseId; - private Long userId; private Long categoryId; private String categoryName; private Long amount; diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/expense/dto/ExpenseUpdateRequestDto.java b/src/main/java/com/bbteam/budgetbuddies/domain/expense/dto/ExpenseUpdateRequestDto.java index 14312ca2..464cd098 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/expense/dto/ExpenseUpdateRequestDto.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/expense/dto/ExpenseUpdateRequestDto.java @@ -16,9 +16,11 @@ @Builder public class ExpenseUpdateRequestDto { private Long expenseId; + private Long categoryId; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime expenseDate; + private Long amount; } diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/expense/repository/ExpenseRepository.java b/src/main/java/com/bbteam/budgetbuddies/domain/expense/repository/ExpenseRepository.java index b55c55fd..62279b8b 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/expense/repository/ExpenseRepository.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/expense/repository/ExpenseRepository.java @@ -14,8 +14,8 @@ import com.bbteam.budgetbuddies.enums.Gender; public interface ExpenseRepository extends JpaRepository { - @Query("SELECT e FROM Expense e WHERE e.user = :user AND e.expenseDate BETWEEN :startDate AND :endDate ORDER BY e.expenseDate DESC") - List findAllByUserIdForPeriod(@Param("user") User user, @Param("startDate") LocalDateTime startDate, + @Query("SELECT e FROM Expense e WHERE e.user.id = :userId AND e.expenseDate >= :startDate AND e.expenseDate < :endDate ORDER BY e.expenseDate DESC") + List findAllByUserIdForPeriod(@Param("userId") Long userId, @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); List findByCategoryIdAndUserIdAndExpenseDateBetweenAndDeletedFalse(Long categoryId, Long userId, diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/expense/service/ExpenseService.java b/src/main/java/com/bbteam/budgetbuddies/domain/expense/service/ExpenseService.java index 67df0f7e..b14c8332 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/expense/service/ExpenseService.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/expense/service/ExpenseService.java @@ -3,18 +3,18 @@ import java.time.LocalDate; import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseRequestDto; -import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseResponseDto; +import com.bbteam.budgetbuddies.domain.expense.dto.DetailExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseUpdateRequestDto; import com.bbteam.budgetbuddies.domain.expense.dto.MonthlyExpenseResponseDto; public interface ExpenseService { - ExpenseResponseDto createExpense(Long userId, ExpenseRequestDto expenseRequestDto); + DetailExpenseResponseDto createExpense(Long userId, ExpenseRequestDto expenseRequestDto); MonthlyExpenseResponseDto getMonthlyExpense(Long userId, LocalDate localDate); - ExpenseResponseDto findExpenseResponseFromUserIdAndExpenseId(Long userId, Long expenseId); + DetailExpenseResponseDto findDetailExpenseResponse(Long userId, Long expenseId); - ExpenseResponseDto updateExpense(Long userId, ExpenseUpdateRequestDto request); + DetailExpenseResponseDto updateExpense(Long userId, ExpenseUpdateRequestDto request); void deleteExpense(Long expenseId); } diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/expense/service/ExpenseServiceImpl.java b/src/main/java/com/bbteam/budgetbuddies/domain/expense/service/ExpenseServiceImpl.java index a51df924..5d6bc445 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/expense/service/ExpenseServiceImpl.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/expense/service/ExpenseServiceImpl.java @@ -1,7 +1,6 @@ package com.bbteam.budgetbuddies.domain.expense.service; import java.time.LocalDate; -import java.time.LocalTime; import java.util.List; import org.springframework.stereotype.Service; @@ -10,16 +9,18 @@ import com.bbteam.budgetbuddies.domain.category.entity.Category; import com.bbteam.budgetbuddies.domain.category.repository.CategoryRepository; import com.bbteam.budgetbuddies.domain.category.service.CategoryService; +import com.bbteam.budgetbuddies.domain.consumptiongoal.entity.ConsumptionGoal; import com.bbteam.budgetbuddies.domain.consumptiongoal.service.ConsumptionGoalService; import com.bbteam.budgetbuddies.domain.expense.converter.ExpenseConverter; +import com.bbteam.budgetbuddies.domain.expense.dto.DetailExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseRequestDto; -import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseUpdateRequestDto; import com.bbteam.budgetbuddies.domain.expense.dto.MonthlyExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.entity.Expense; import com.bbteam.budgetbuddies.domain.expense.repository.ExpenseRepository; import com.bbteam.budgetbuddies.domain.user.entity.User; import com.bbteam.budgetbuddies.domain.user.repository.UserRepository; +import com.bbteam.budgetbuddies.domain.user.service.UserService; import lombok.RequiredArgsConstructor; @@ -29,13 +30,14 @@ public class ExpenseServiceImpl implements ExpenseService { private final ExpenseRepository expenseRepository; private final UserRepository userRepository; + private final UserService userService; private final CategoryRepository categoryRepository; private final CategoryService categoryService; private final ExpenseConverter expenseConverter; private final ConsumptionGoalService consumptionGoalService; @Override - public ExpenseResponseDto createExpense(Long userId, ExpenseRequestDto expenseRequestDto) { + public DetailExpenseResponseDto createExpense(Long userId, ExpenseRequestDto expenseRequestDto) { User user = userRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("Invalid user ID")); Category category = categoryRepository.findById(expenseRequestDto.getCategoryId()) @@ -73,14 +75,15 @@ else if (!category.getIsDefault() && category.getUser().getId().equals(userId)) if (expenseDateMonth.equals(currentMonth)) { // 현재 월의 소비 내역일 경우 ConsumptionGoal을 업데이트 - consumptionGoalService.updateConsumeAmount(userId, expenseRequestDto.getCategoryId(), expenseRequestDto.getAmount()); + consumptionGoalService.updateConsumeAmount(userId, expenseRequestDto.getCategoryId(), + expenseRequestDto.getAmount()); } -// else { -// // 과거 월의 소비 내역일 경우 해당 월의 ConsumptionGoal을 업데이트 또는 삭제 상태로 생성 -// consumptionGoalService.updateOrCreateDeletedConsumptionGoal(userId, expenseRequestDto.getCategoryId(), expenseDateMonth, expenseRequestDto.getAmount()); -// } + // else { + // // 과거 월의 소비 내역일 경우 해당 월의 ConsumptionGoal을 업데이트 또는 삭제 상태로 생성 + // consumptionGoalService.updateOrCreateDeletedConsumptionGoal(userId, expenseRequestDto.getCategoryId(), expenseDateMonth, expenseRequestDto.getAmount()); + // } - return expenseConverter.toExpenseResponseDto(expense); + return expenseConverter.toDetailExpenseResponseDto(expense); /* Case 1 결과) 해당 유저의 user_id + immutable 필드 중 하나의 조합으로 Expense 테이블에 저장 Case 2 결과) 내가 직접 생성한 카테고리 중 하나로 카테고리를 설정하여 Expense 테이블에 저장 @@ -91,7 +94,7 @@ else if (!category.getIsDefault() && category.getUser().getId().equals(userId)) @Transactional public void deleteExpense(Long expenseId) { Expense expense = expenseRepository.findById(expenseId) - .orElseThrow(() -> new IllegalArgumentException("Not found Expense")); + .orElseThrow(() -> new IllegalArgumentException("Not found Expense")); Long userId = expense.getUser().getId(); Long categoryId = expense.getCategory().getId(); @@ -113,45 +116,42 @@ public void deleteExpense(Long expenseId) { @Transactional(readOnly = true) public MonthlyExpenseResponseDto getMonthlyExpense(Long userId, LocalDate localDate) { LocalDate startOfMonth = localDate.withDayOfMonth(1); - LocalDate endOfMonth = localDate.withDayOfMonth(startOfMonth.lengthOfMonth()); + LocalDate nextMonth = startOfMonth.plusMonths(1L); - User user = userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("Invalid user ID")); - - List expenseSlice = expenseRepository.findAllByUserIdForPeriod(user, - startOfMonth.atStartOfDay(), endOfMonth.atTime(LocalTime.MAX)); + List expenseSlice = expenseRepository.findAllByUserIdForPeriod(userId, + startOfMonth.atStartOfDay(), nextMonth.atStartOfDay()); return expenseConverter.toMonthlyExpenseResponseDto(expenseSlice, startOfMonth); } @Override - public ExpenseResponseDto findExpenseResponseFromUserIdAndExpenseId(Long userId, Long expenseId) { - Expense expense = expenseRepository.findById(expenseId) - .orElseThrow(() -> new IllegalArgumentException("Not found expense")); - - checkUserAuthority(userId, expense); - - return expenseConverter.toExpenseResponseDto(expense); + public DetailExpenseResponseDto findDetailExpenseResponse(Long userId, Long expenseId) { + return expenseConverter.toDetailExpenseResponseDto(getExpense(expenseId)); } - private void checkUserAuthority(Long userId, Expense expense) { - if (!expense.getUser().getId().equals(userId)) - throw new IllegalArgumentException("Unauthorized user"); + private Expense getExpense(Long expenseId) { + return expenseRepository.findById(expenseId) + .orElseThrow(() -> new IllegalArgumentException("Not found expense")); } @Override @Transactional - public ExpenseResponseDto updateExpense(Long userId, ExpenseUpdateRequestDto request) { - Expense expense = expenseRepository.findById(request.getExpenseId()) - .orElseThrow(() -> new IllegalArgumentException("Not found expense")); + public DetailExpenseResponseDto updateExpense(Long userId, ExpenseUpdateRequestDto request) { + User user = userService.getUser(userId); + Expense expense = getExpense(request.getExpenseId()); + Category categoryToReplace = categoryService.getCategory(request.getCategoryId()); - User user = userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("Not found user")); - checkUserAuthority(userId, expense); + expense.updateExpenseFromRequest(request, categoryToReplace); - Category categoryToReplace = categoryService.handleCategoryChange(expense, request, user); + ConsumptionGoal beforeConsumptionGoal = consumptionGoalService.getUserConsumptionGoal( + user, expense.getCategory(), expense.getExpenseDate().toLocalDate().withDayOfMonth(1)); + ConsumptionGoal afterConsumptionGoal = consumptionGoalService.getUserConsumptionGoal( + user, categoryToReplace, request.getExpenseDate().toLocalDate().withDayOfMonth(1)); - expense.updateExpenseFromRequest(request, categoryToReplace); + consumptionGoalService.recalculateConsumptionAmount(beforeConsumptionGoal, expense.getAmount(), + afterConsumptionGoal, request.getAmount()); - return expenseConverter.toExpenseResponseDto(expenseRepository.save(expense)); + return expenseConverter.toDetailExpenseResponseDto(expenseRepository.save(expense)); } } diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/user/service/UserService.java b/src/main/java/com/bbteam/budgetbuddies/domain/user/service/UserService.java index 0cb29dba..ac0d046b 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/user/service/UserService.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/user/service/UserService.java @@ -1,6 +1,7 @@ package com.bbteam.budgetbuddies.domain.user.service; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.UserConsumptionGoalResponse; import com.bbteam.budgetbuddies.domain.user.dto.UserDto; +import com.bbteam.budgetbuddies.domain.user.entity.User; import java.util.List; @@ -14,4 +15,6 @@ public interface UserService { UserDto.ResponseUserDto modifyUser(Long userId, UserDto.ModifyUserDto dto); List findAll(); + + User getUser(Long userId); } diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/user/service/UserServiceImpl.java b/src/main/java/com/bbteam/budgetbuddies/domain/user/service/UserServiceImpl.java index 2ce9dbb1..6b9bb191 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/user/service/UserServiceImpl.java @@ -82,4 +82,9 @@ public List findAll() { .map(UserConverter::toDto) .toList(); } + + @Override + public User getUser(Long userId) { + return userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("Not found user")); + } } diff --git a/src/test/java/com/bbteam/budgetbuddies/domain/consumptiongoal/service/ConsumptionGoalServiceTest.java b/src/test/java/com/bbteam/budgetbuddies/domain/consumptiongoal/service/ConsumptionGoalServiceTest.java index 49346396..a010109f 100644 --- a/src/test/java/com/bbteam/budgetbuddies/domain/consumptiongoal/service/ConsumptionGoalServiceTest.java +++ b/src/test/java/com/bbteam/budgetbuddies/domain/consumptiongoal/service/ConsumptionGoalServiceTest.java @@ -12,13 +12,12 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; -import java.util.Random; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; +import org.mockito.AdditionalAnswers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; @@ -27,6 +26,7 @@ import com.bbteam.budgetbuddies.domain.category.entity.Category; import com.bbteam.budgetbuddies.domain.category.repository.CategoryRepository; +import com.bbteam.budgetbuddies.domain.category.service.CategoryService; import com.bbteam.budgetbuddies.domain.consumptiongoal.converter.ConsumptionGoalConverter; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.AllConsumptionCategoryResponseDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.AvgConsumptionGoalDto; @@ -41,20 +41,18 @@ import com.bbteam.budgetbuddies.domain.consumptiongoal.dto.TopCategoryConsumptionDto; import com.bbteam.budgetbuddies.domain.consumptiongoal.entity.ConsumptionGoal; import com.bbteam.budgetbuddies.domain.consumptiongoal.repository.ConsumptionGoalRepository; -import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseUpdateRequestDto; -import com.bbteam.budgetbuddies.domain.expense.entity.Expense; import com.bbteam.budgetbuddies.domain.expense.repository.ExpenseRepository; import com.bbteam.budgetbuddies.domain.user.entity.User; import com.bbteam.budgetbuddies.domain.user.repository.UserRepository; +import com.bbteam.budgetbuddies.domain.user.service.UserService; import com.bbteam.budgetbuddies.enums.Gender; @DisplayName("ConsumptionGoalImpl 서비스 테스트의 ") @ExtendWith(MockitoExtension.class) class ConsumptionGoalServiceTest { - private final LocalDate GOAL_MONTH = LocalDate.of(2024, 07, 01); private final LocalDate currentMonth = LocalDate.now().withDayOfMonth(1); private User user; - private LocalDate goalMonthRandomDay; + private LocalDate requestMonth; @InjectMocks private ConsumptionGoalServiceImpl consumptionGoalService; @Mock @@ -62,17 +60,19 @@ class ConsumptionGoalServiceTest { @Mock private CategoryRepository categoryRepository; @Mock + private CategoryService categoryService; + @Mock private UserRepository userRepository; @Mock + private UserService userService; + @Mock private ExpenseRepository expenseRepository; @Spy private ConsumptionGoalConverter consumptionGoalConverter; @BeforeEach void setUp() { - Random random = new Random(); - int randomDay = random.nextInt(30) + 1; - goalMonthRandomDay = LocalDate.of(GOAL_MONTH.getYear(), GOAL_MONTH.getMonth(), randomDay); + requestMonth = LocalDate.of(2024, 7, 15); user = Mockito.spy(User.builder() .email("email") @@ -84,26 +84,23 @@ void setUp() { } @Test - @DisplayName("findUserConsumptionGoal : 생성된 ConsumptionGoal이 없고 카테고리만 있는 경우 목표 금액, 소비 금액 0으로 초기화") - void findUserConsumptionGoal_onlyCategory() { + void 유저소비목표조회시_이번달_이전달_소비목표가_없는_경우_소비목표를_새로_생성() { // given Category defaultCategory = Mockito.spy(Category.builder().name("디폴트 카테고리").user(null).isDefault(true).build()); given(defaultCategory.getId()).willReturn(-1L); - Category userCategory = Mockito.spy(Category.builder().name("유저 카테고리").user(user).isDefault(false).build()); given(userCategory.getId()).willReturn(-2L); - List categoryList = List.of(defaultCategory, userCategory); + given(categoryService.getUserCategoryList(user.getId())).willReturn(List.of(defaultCategory, userCategory)); + given(consumptionGoalRepository.save(any(ConsumptionGoal.class))).will(AdditionalAnswers.returnsFirstArg()); List expected = List.of( new ConsumptionGoalResponseDto(defaultCategory.getName(), defaultCategory.getId(), 0L, 0L), new ConsumptionGoalResponseDto(userCategory.getName(), userCategory.getId(), 0L, 0L)); // when - when(categoryRepository.findUserCategoryByUserId(user.getId())).thenReturn(categoryList); - ConsumptionGoalResponseListDto result = consumptionGoalService.findUserConsumptionGoalList(user.getId(), - goalMonthRandomDay); + requestMonth); // then assertThat(result.getConsumptionGoalList()).usingRecursiveComparison().isEqualTo(expected); @@ -111,117 +108,105 @@ void findUserConsumptionGoal_onlyCategory() { } @Test - @DisplayName("findUserConsumptionGoal : 한달전 ConsumptionGoal만 있을 경우 한달전으로 초기화") - void findUserConsumptionGoal_previousMonth() { + void 소비목표조회시_지난달_소비목표만_존재할시_지난달_기준으로_소비목표를_생성() { // given Category defaultCategory = Mockito.spy(Category.builder().name("디폴트 카테고리").user(null).isDefault(true).build()); given(defaultCategory.getId()).willReturn(-1L); - Category userCategory = Mockito.spy(Category.builder().name("유저 카테고리").user(user).isDefault(false).build()); given(userCategory.getId()).willReturn(-2L); - given(categoryRepository.findUserCategoryByUserId(user.getId())).willReturn( - List.of(defaultCategory, userCategory)); + given(categoryService.getUserCategoryList(user.getId())).willReturn(List.of(defaultCategory, userCategory)); + given(userService.getUser(user.getId())).willReturn(user); ConsumptionGoal previousMonthDefaultCategoryGoal = ConsumptionGoal.builder() .goalAmount(1_000_000L) .consumeAmount(200_000L) .user(user) .category(defaultCategory) - .goalMonth(goalMonthRandomDay.minusMonths(1)) + .goalMonth(requestMonth.withDayOfMonth(1).minusMonths(1)) .build(); - Long previousMonthDefaultGoalRemainingBalance = - previousMonthDefaultCategoryGoal.getGoalAmount() - previousMonthDefaultCategoryGoal.getConsumeAmount(); + given(consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, defaultCategory, + requestMonth.withDayOfMonth(1))).willReturn(Optional.empty()); + given(consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, defaultCategory, + requestMonth.withDayOfMonth(1).minusMonths(1))).willReturn(Optional.of(previousMonthDefaultCategoryGoal)); ConsumptionGoal previousMonthUserCategoryGoal = ConsumptionGoal.builder() .goalAmount(1_000_000L) .consumeAmount(20_000L) .user(user) .category(userCategory) - .goalMonth(goalMonthRandomDay.minusMonths(1)) + .goalMonth(requestMonth.withDayOfMonth(1).minusMonths(1)) .build(); - Long previousMonthUseGoalRemainingBalance = - previousMonthUserCategoryGoal.getGoalAmount() - previousMonthUserCategoryGoal.getConsumeAmount(); + given(consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, userCategory, + requestMonth.withDayOfMonth(1))).willReturn(Optional.empty()); + given(consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, userCategory, + requestMonth.withDayOfMonth(1).minusMonths(1))).willReturn(Optional.of(previousMonthUserCategoryGoal)); - List previousGoalList = List.of(previousMonthDefaultCategoryGoal, - previousMonthUserCategoryGoal); - - List expected = List.of( - consumptionGoalConverter.toConsumptionGoalResponseDto(previousMonthUserCategoryGoal), - consumptionGoalConverter.toConsumptionGoalResponseDto(previousMonthDefaultCategoryGoal)); + given(consumptionGoalRepository.save(any(ConsumptionGoal.class))).will(AdditionalAnswers.returnsFirstArg()); // when - when(consumptionGoalRepository.findConsumptionGoalByUserIdAndGoalMonth(user.getId(), - GOAL_MONTH.minusMonths(1))).thenReturn(previousGoalList); - ConsumptionGoalResponseListDto result = consumptionGoalService.findUserConsumptionGoalList(user.getId(), - goalMonthRandomDay); + requestMonth); // then - assertThat(result.getConsumptionGoalList()).usingRecursiveComparison().isEqualTo(expected); - assertEquals(result.getTotalRemainingBalance(), - previousMonthDefaultGoalRemainingBalance + previousMonthUseGoalRemainingBalance); + assertThat(result.getTotalRemainingBalance()).isEqualTo(2_000_000L); + assertThat(result.getTotalConsumptionAmount()).isEqualTo(0L); } @Test - @DisplayName("findUserConsumptionGoal : 한달 전과 목표 달 ConsumptionGoal이 있을 경우 목표 달로 초기화") - void findUserConsumptionGoal_previousMonthAndGoalMonth() { + void 소비목표_조회시_이번달_소비목표가_존재하는_경우_이번달_소비목표를_반환() { // given + Category defaultCategory = Mockito.spy(Category.builder().name("디폴트 카테고리").user(null).isDefault(true).build()); + given(defaultCategory.getId()).willReturn(-1L); Category userCategory = Mockito.spy(Category.builder().name("유저 카테고리").user(user).isDefault(false).build()); given(userCategory.getId()).willReturn(-2L); - ConsumptionGoal previousMonthUserCategoryGoal = ConsumptionGoal.builder() + given(categoryService.getUserCategoryList(user.getId())).willReturn(List.of(defaultCategory, userCategory)); + given(userService.getUser(user.getId())).willReturn(user); + + ConsumptionGoal thisMonthDefaultCategoryGoal = ConsumptionGoal.builder() .goalAmount(1_000_000L) - .consumeAmount(20_000L) + .consumeAmount(200_000L) .user(user) - .category(userCategory) - .goalMonth(goalMonthRandomDay.minusMonths(1)) + .category(defaultCategory) + .goalMonth(requestMonth.withDayOfMonth(1)) .build(); + given(consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, defaultCategory, + requestMonth.withDayOfMonth(1))).willReturn(Optional.of(thisMonthDefaultCategoryGoal)); - ConsumptionGoal goalMonthUserCategoryGoal = ConsumptionGoal.builder() - .goalAmount(2_000_000L) - .consumeAmount(30_000L) + ConsumptionGoal thisMonthUserCategoryGoal = ConsumptionGoal.builder() + .goalAmount(1_000_000L) + .consumeAmount(20_000L) .user(user) .category(userCategory) - .goalMonth(goalMonthRandomDay) + .goalMonth(requestMonth.withDayOfMonth(1)) .build(); + given(consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, userCategory, + requestMonth.withDayOfMonth(1))).willReturn(Optional.of(thisMonthUserCategoryGoal)); // when - when(consumptionGoalRepository.findConsumptionGoalByUserIdAndGoalMonth(user.getId(), - GOAL_MONTH.minusMonths(1))).thenReturn(List.of(previousMonthUserCategoryGoal)); - - when(consumptionGoalRepository.findConsumptionGoalByUserIdAndGoalMonth(user.getId(), GOAL_MONTH)).thenReturn( - List.of(goalMonthUserCategoryGoal)); - ConsumptionGoalResponseListDto result = consumptionGoalService.findUserConsumptionGoalList(user.getId(), - goalMonthRandomDay); + requestMonth); // then - assertThat(result.getConsumptionGoalList()).usingRecursiveComparison() - .isEqualTo(List.of(consumptionGoalConverter.toConsumptionGoalResponseDto(goalMonthUserCategoryGoal))); + assertThat(result.getTotalRemainingBalance()).isEqualTo(1_780_000L); + assertThat(result.getTotalConsumptionAmount()).isEqualTo(220_000L); } @Test - @DisplayName("updateConsumptionGoal : 이번달 목표가 있는 경우(defaultCategory)와 목표가 없는 경우(userCategory)") - void updateConsumptionGoal_Success() { + void 소비목표_업데이트_성공() { // given - Long defaultGoalAmount = 100L; - Long userGoalAmount = 200L; + Long updateAmount = 100L; LocalDate thisMonth = LocalDate.now().withDayOfMonth(1); - given(userRepository.findById(user.getId())).willReturn(Optional.ofNullable(user)); + given(userService.getUser(user.getId())).willReturn(user); ConsumptionGoalListRequestDto request = new ConsumptionGoalListRequestDto( - List.of(new ConsumptionGoalRequestDto(-1L, defaultGoalAmount), - new ConsumptionGoalRequestDto(-2L, userGoalAmount))); + List.of(new ConsumptionGoalRequestDto(-1L, updateAmount))); Category defaultCategory = Mockito.spy(Category.builder().name("디폴트 카테고리").user(null).isDefault(true).build()); given(defaultCategory.getId()).willReturn(-1L); - given(categoryRepository.findById(defaultCategory.getId())).willReturn(Optional.of(defaultCategory)); - - Category userCategory = Mockito.spy(Category.builder().name("유저 카테고리").user(user).isDefault(false).build()); - given(userCategory.getId()).willReturn(-2L); - given(categoryRepository.findById(userCategory.getId())).willReturn(Optional.of(userCategory)); + given(categoryService.getCategory(defaultCategory.getId())).willReturn(defaultCategory); ConsumptionGoal defaultCategoryGoal = ConsumptionGoal.builder() .goalAmount(1_000_000L) @@ -230,27 +215,14 @@ void updateConsumptionGoal_Success() { .category(defaultCategory) .goalMonth(thisMonth) .build(); - given(consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth(user, defaultCategory, - thisMonth)).willReturn(Optional.ofNullable(defaultCategoryGoal)); - - given(consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth(user, userCategory, - thisMonth)).willReturn(Optional.ofNullable(null)); - - when(consumptionGoalRepository.saveAll(any())).thenAnswer(invocation -> { - List goalsToSave = invocation.getArgument(0); - return goalsToSave; - }); + given(consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, defaultCategory, thisMonth)).willReturn( + Optional.ofNullable(defaultCategoryGoal)); List expected = List.of(ConsumptionGoalResponseDto.builder() - .goalAmount(defaultGoalAmount) + .goalAmount(updateAmount) .consumeAmount(defaultCategoryGoal.getConsumeAmount()) .categoryName(defaultCategory.getName()) .categoryId(defaultCategory.getId()) - .build(), ConsumptionGoalResponseDto.builder() - .goalAmount(userGoalAmount) - .consumeAmount(0L) - .categoryName(userCategory.getName()) - .categoryId(userCategory.getId()) .build()); // when @@ -302,11 +274,7 @@ void getPeerInfo_UserNotFound() { @DisplayName("getTopCategoryAndConsumptionAmount : 가장 큰 계획 카테고리와 이번 주 소비 금액 조회 성공") void getTopCategoryAndConsumptionAmount_Success() { // given - Category defaultCategory = Mockito.spy(Category.builder() - .name("디폴트 카테고리") - .user(null) - .isDefault(true) - .build()); + Category defaultCategory = Mockito.spy(Category.builder().name("디폴트 카테고리").user(null).isDefault(true).build()); given(defaultCategory.getId()).willReturn(1L); LocalDate goalMonthRandomDay = LocalDate.now(); @@ -333,25 +301,21 @@ void getTopCategoryAndConsumptionAmount_Success() { .build(); List avgConsumptionGoalList = List.of( - new AvgConsumptionGoalDto(defaultCategory.getId(), 5000L) - ); + new AvgConsumptionGoalDto(defaultCategory.getId(), 5000L)); int peerAgeStart = 23; int peerAgeEnd = 25; Gender peerGender = Gender.MALE; given(userRepository.findById(user.getId())).willReturn(Optional.of(user)); - given(consumptionGoalRepository.findAvgGoalAmountByCategory( - peerAgeStart, peerAgeEnd, peerGender, currentMonth)) - .willReturn(avgConsumptionGoalList); + given(consumptionGoalRepository.findAvgGoalAmountByCategory(peerAgeStart, peerAgeEnd, peerGender, + currentMonth)).willReturn(avgConsumptionGoalList); - given(consumptionGoalRepository.findAvgConsumptionByCategoryIdAndCurrentWeek( - defaultCategory.getId(), startOfWeekDateTime, endOfWeekDateTime, - peerAgeStart, peerAgeEnd, peerGender)) - .willReturn(Optional.of(currentWeekConsumptionGoal.getConsumeAmount())); + given(consumptionGoalRepository.findAvgConsumptionByCategoryIdAndCurrentWeek(defaultCategory.getId(), + startOfWeekDateTime, endOfWeekDateTime, peerAgeStart, peerAgeEnd, peerGender)).willReturn( + Optional.of(currentWeekConsumptionGoal.getConsumeAmount())); - given(categoryRepository.findById(defaultCategory.getId())) - .willReturn(Optional.of(defaultCategory)); + given(categoryRepository.findById(defaultCategory.getId())).willReturn(Optional.of(defaultCategory)); // when ConsumptionAnalysisResponseDto result = consumptionGoalService.getTopCategoryAndConsumptionAmount(user.getId()); @@ -386,17 +350,17 @@ void getTopConsumptionCategories_Success() { String peerGender = "MALE"; given(userRepository.findById(user.getId())).willReturn(Optional.of(user)); - given(expenseRepository.findTopCategoriesByConsumptionCount(peerAgeStart, peerAgeEnd, - Gender.valueOf(peerGender), currentMonth.atStartOfDay())) - .willReturn(List.of(topConsumption1, topConsumption2, topConsumption3)); + given( + expenseRepository.findTopCategoriesByConsumptionCount(peerAgeStart, peerAgeEnd, Gender.valueOf(peerGender), + currentMonth.atStartOfDay())).willReturn(List.of(topConsumption1, topConsumption2, topConsumption3)); given(categoryRepository.findById(defaultCategory1.getId())).willReturn(Optional.of(defaultCategory1)); given(categoryRepository.findById(defaultCategory2.getId())).willReturn(Optional.of(defaultCategory2)); given(categoryRepository.findById(defaultCategory3.getId())).willReturn(Optional.of(defaultCategory3)); // when - List result = consumptionGoalService.getTopConsumptionCategories( - user.getId(), peerAgeStart, peerAgeEnd, peerGender); + List result = consumptionGoalService.getTopConsumptionCategories(user.getId(), + peerAgeStart, peerAgeEnd, peerGender); // then assertThat(result).hasSize(3); @@ -426,23 +390,19 @@ void getAllConsumptionGoalCategories_Success() { defaultCategory2.setName("디폴트 카테고리2"); defaultCategory2.setIsDefault(true); - List categoryAvgList = List.of( - new AvgConsumptionGoalDto(1L, 3000L), - new AvgConsumptionGoalDto(2L, 4000L) - ); + List categoryAvgList = List.of(new AvgConsumptionGoalDto(1L, 3000L), + new AvgConsumptionGoalDto(2L, 4000L)); - List myConsumptionAmountList = List.of( - new MyConsumptionGoalDto(1L, 5000L), - new MyConsumptionGoalDto(2L, 2000L) - ); + List myConsumptionAmountList = List.of(new MyConsumptionGoalDto(1L, 5000L), + new MyConsumptionGoalDto(2L, 2000L)); List defaultCategories = List.of(defaultCategory1, defaultCategory2); given(categoryRepository.findAllByIsDefaultTrue()).willReturn(defaultCategories); - given(consumptionGoalRepository.findAvgGoalAmountByCategory( - anyInt(), anyInt(), any(), any())).willReturn(categoryAvgList); - given(consumptionGoalRepository.findAllGoalAmountByUserId(user.getId(), currentMonth)) - .willReturn(myConsumptionAmountList); + given(consumptionGoalRepository.findAvgGoalAmountByCategory(anyInt(), anyInt(), any(), any())).willReturn( + categoryAvgList); + given(consumptionGoalRepository.findAllGoalAmountByUserId(user.getId(), currentMonth)).willReturn( + myConsumptionAmountList); given(userRepository.findById(user.getId())).willReturn(Optional.of(user)); // when @@ -478,21 +438,18 @@ void getAllConsumptionCategories_Success() { defaultCategory2.setName("디폴트 카테고리2"); defaultCategory2.setIsDefault(true); - List categoryAvgList = List.of( - new AvgConsumptionGoalDto(1L, 3000L), - new AvgConsumptionGoalDto(2L, 4000L) - ); + List categoryAvgList = List.of(new AvgConsumptionGoalDto(1L, 3000L), + new AvgConsumptionGoalDto(2L, 4000L)); - List myConsumptionAmountList = List.of( - new MyConsumptionGoalDto(1L, 5000L), - new MyConsumptionGoalDto(2L, 2000L) - ); + List myConsumptionAmountList = List.of(new MyConsumptionGoalDto(1L, 5000L), + new MyConsumptionGoalDto(2L, 2000L)); List defaultCategories = List.of(defaultCategory1, defaultCategory2); given(categoryRepository.findAllByIsDefaultTrue()).willReturn(defaultCategories); - given(consumptionGoalRepository.findAvgConsumptionAmountByCategory( - anyInt(), anyInt(), any(), any())).willReturn(categoryAvgList); + given( + consumptionGoalRepository.findAvgConsumptionAmountByCategory(anyInt(), anyInt(), any(), any())).willReturn( + categoryAvgList); given(userRepository.findById(user.getId())).willReturn(Optional.of(user)); // when @@ -515,166 +472,84 @@ void getAllConsumptionCategories_Success() { } @Test - @DisplayName("지난 달, 이번 달 소비 목표가 없는 카테고리에 대한 소비 업데이트를 진행하는 경우 새로운 소비 목표를 생성해 소비 금액을 갱신") - void recalculateConsumptionAmount_notExistPreviousMonthAndThisMonthGoal() { + void 이번달_사용자_소비목표_조회하기() { // given - Category existGoalCategory = Category.builder().name("유저 카테고리").user(user).isDefault(false).build(); - Category notExistGoalCategory = Mockito.spy(Category.builder().name("디폴트 카테고리").isDefault(true).build()); - given(notExistGoalCategory.getId()).willReturn(-1L); - - Expense expense = Mockito.spy( - Expense.builder().category(existGoalCategory).expenseDate(GOAL_MONTH.atStartOfDay()).amount(1000L).build()); - when(expense.getId()).thenReturn(-1L); - - ExpenseUpdateRequestDto request = ExpenseUpdateRequestDto.builder() - .amount(1000L) - .expenseId(expense.getId()) - .expenseDate(LocalDate.of(2024, 8, 7).atStartOfDay()) - .categoryId(notExistGoalCategory.getId()) - .build(); + Category category = Mockito.spy(Category.builder().name("TEST CATEGORY").user(user).isDefault(false).build()); + LocalDate goalMonth = LocalDate.of(2024, 7, 1); - ConsumptionGoal oldGoal = ConsumptionGoal.builder().consumeAmount(1000L).category(existGoalCategory).build(); - ConsumptionGoal expected = ConsumptionGoal.builder() - .goalMonth(LocalDate.of(2024, 8, 1)) - .goalAmount(0L) - .consumeAmount(1000L) - .category(notExistGoalCategory) + ConsumptionGoal userConsumptionGoal = ConsumptionGoal.builder() + .goalAmount(1_000_000L) + .consumeAmount(200_000L) .user(user) + .category(category) + .goalMonth(goalMonth) .build(); - // when - when(consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth(user, expense.getCategory(), - expense.getExpenseDate().toLocalDate().withDayOfMonth(1))).thenReturn(Optional.ofNullable(oldGoal)); - - when(categoryRepository.findById(request.getCategoryId())).thenReturn(Optional.of(notExistGoalCategory)); - when(consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth(user, notExistGoalCategory, - request.getExpenseDate().toLocalDate().withDayOfMonth(1))).thenReturn(Optional.empty()); - - when(consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth(user, notExistGoalCategory, - request.getExpenseDate().minusMonths(1).toLocalDate().withDayOfMonth(1))).thenReturn(Optional.empty()); + given(consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, category, goalMonth)).willReturn( + Optional.of(userConsumptionGoal)); - consumptionGoalService.recalculateConsumptionAmount(expense, request, user); - - ArgumentCaptor consumptionGoalCaptor = ArgumentCaptor.forClass(ConsumptionGoal.class); - verify(consumptionGoalRepository, times(2)).save(consumptionGoalCaptor.capture()); - - List savedConsumptionGoals = consumptionGoalCaptor.getAllValues(); + // when + ConsumptionGoal result = consumptionGoalService.getUserConsumptionGoal(user, category, goalMonth); // then - assertEquals(oldGoal.getConsumeAmount(), 0L); - assertThat(savedConsumptionGoals.get(1)).usingRecursiveComparison().isEqualTo(expected); + assertEquals(result, userConsumptionGoal); } @Test - @DisplayName("이번달 소비 목표가 없는 카테고리에 대한 소비 업데이트를 진행하는 경우 지난 달 소비 목표의 목표 금액을 복사한 이번 달 소비 목표를 생성해 소비 금액을 갱신") - void recalculateConsumptionAmount_notExistThisMonthGoal() { + void 이번달_소비목표가_없는경우_저번달_기준으로_소비목표_생성() { // given - Category existGoalCategory = Category.builder().name("유저 카테고리").user(user).isDefault(false).build(); - Category notExistThisMonthGoalCategory = Mockito.spy( - Category.builder().name("디폴트 카테고리").isDefault(true).build()); - given(notExistThisMonthGoalCategory.getId()).willReturn(-1L); - - Expense expense = Mockito.spy( - Expense.builder().category(existGoalCategory).expenseDate(GOAL_MONTH.atStartOfDay()).amount(1000L).build()); - when(expense.getId()).thenReturn(-1L); - - ExpenseUpdateRequestDto request = ExpenseUpdateRequestDto.builder() - .amount(1000L) - .expenseId(expense.getId()) - .expenseDate(LocalDate.of(2024, 8, 7).atStartOfDay()) - .categoryId(notExistThisMonthGoalCategory.getId()) - .build(); + Category category = Mockito.spy(Category.builder().name("TEST CATEGORY").user(user).isDefault(false).build()); + LocalDate goalMonth = LocalDate.of(2024, 7, 1); - ConsumptionGoal oldGoal = ConsumptionGoal.builder().consumeAmount(1000L).category(existGoalCategory).build(); - - ConsumptionGoal previousMonthGoal = ConsumptionGoal.builder() - .goalMonth(LocalDate.of(2024, 7, 1)) - .goalAmount(3000L) - .consumeAmount(3000L) - .category(notExistThisMonthGoalCategory) + ConsumptionGoal beforeUserConsumptionGoal = ConsumptionGoal.builder() + .goalAmount(1_000_000L) + .consumeAmount(200_000L) .user(user) + .category(category) + .goalMonth(LocalDate.of(2024, 6, 1)) .build(); + given(consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, category, goalMonth)).willReturn( + Optional.empty()); + given(consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, category, + goalMonth.minusMonths(1))).willReturn(Optional.of(beforeUserConsumptionGoal)); + given(consumptionGoalRepository.save(any(ConsumptionGoal.class))).will(AdditionalAnswers.returnsFirstArg()); ConsumptionGoal expected = ConsumptionGoal.builder() - .goalMonth(LocalDate.of(2024, 8, 1)) - .goalAmount(3000L) - .consumeAmount(1000L) - .category(notExistThisMonthGoalCategory) + .goalAmount(1_000_000L) + .consumeAmount(0L) .user(user) + .category(category) + .goalMonth(LocalDate.of(2024, 7, 1)) .build(); - // when - when(consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth(user, expense.getCategory(), - expense.getExpenseDate().toLocalDate().withDayOfMonth(1))).thenReturn(Optional.ofNullable(oldGoal)); - - when(categoryRepository.findById(request.getCategoryId())).thenReturn( - Optional.of(notExistThisMonthGoalCategory)); - when(consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth(user, - notExistThisMonthGoalCategory, request.getExpenseDate().toLocalDate().withDayOfMonth(1))).thenReturn( - Optional.empty()); - - when(consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth(user, - notExistThisMonthGoalCategory, - request.getExpenseDate().minusMonths(1).toLocalDate().withDayOfMonth(1))).thenReturn( - Optional.ofNullable(previousMonthGoal)); - - consumptionGoalService.recalculateConsumptionAmount(expense, request, user); - - ArgumentCaptor consumptionGoalCaptor = ArgumentCaptor.forClass(ConsumptionGoal.class); - verify(consumptionGoalRepository, times(2)).save(consumptionGoalCaptor.capture()); - - List savedConsumptionGoals = consumptionGoalCaptor.getAllValues(); + ConsumptionGoal result = consumptionGoalService.getUserConsumptionGoal(user, category, goalMonth); // then - assertEquals(oldGoal.getConsumeAmount(), 0L); - assertThat(savedConsumptionGoals.get(1)).usingRecursiveComparison().isEqualTo(expected); + assertThat(result).usingRecursiveComparison().isEqualTo(expected); } @Test - @DisplayName("이번달 소비 목표가 있는 경우 이번 달 소비 목표의 소비 금액을 갱신") - void recalculateConsumptionAmount_existThisMonthGoal() { + void 이번달_저번달_소비목표가_없는경우_소비목표를_새로_생성() { // given - Category existGoalCategory = Mockito.spy( - Category.builder().name("유저 카테고리").user(user).isDefault(false).build()); - given(existGoalCategory.getId()).willReturn(-1L); - - Expense expense = Mockito.spy( - Expense.builder().category(existGoalCategory).expenseDate(GOAL_MONTH.atStartOfDay()).amount(1000L).build()); - when(expense.getId()).thenReturn(-1L); - - ExpenseUpdateRequestDto request = ExpenseUpdateRequestDto.builder() - .amount(2000L) - .expenseId(expense.getId()) - .expenseDate(LocalDate.of(2024, 8, 7).atStartOfDay()) - .categoryId(existGoalCategory.getId()) - .build(); + Category category = Mockito.spy(Category.builder().name("TEST CATEGORY").user(user).isDefault(false).build()); + LocalDate goalMonth = LocalDate.of(2024, 7, 1); - ConsumptionGoal oldGoal = ConsumptionGoal.builder() - .goalMonth(LocalDate.of(2024, 8, 1)) - .goalAmount(3000L) - .consumeAmount(1000L) - .category(existGoalCategory) - .user(user) - .build(); + given(consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, category, goalMonth)).willReturn( + Optional.empty()); + given(consumptionGoalRepository.findByUserAndCategoryAndGoalMonth(user, category, + goalMonth.minusMonths(1))).willReturn(Optional.empty()); + given(consumptionGoalRepository.save(any(ConsumptionGoal.class))).will(AdditionalAnswers.returnsFirstArg()); ConsumptionGoal expected = ConsumptionGoal.builder() - .goalMonth(LocalDate.of(2024, 8, 1)) - .goalAmount(3000L) - .consumeAmount(2000L) - .category(existGoalCategory) + .goalAmount(0L) + .consumeAmount(0L) .user(user) + .category(category) + .goalMonth(LocalDate.of(2024, 7, 1)) .build(); - // when - when(consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth(user, expense.getCategory(), - expense.getExpenseDate().toLocalDate().withDayOfMonth(1))).thenReturn(Optional.ofNullable(oldGoal)); - - when(categoryRepository.findById(request.getCategoryId())).thenReturn(Optional.of(existGoalCategory)); - when(consumptionGoalRepository.findConsumptionGoalByUserAndCategoryAndGoalMonth(user, existGoalCategory, - request.getExpenseDate().toLocalDate().withDayOfMonth(1))).thenReturn(Optional.ofNullable(oldGoal)); - - consumptionGoalService.recalculateConsumptionAmount(expense, request, user); + ConsumptionGoal result = consumptionGoalService.getUserConsumptionGoal(user, category, goalMonth); // then - assertThat(oldGoal).usingRecursiveComparison().isEqualTo(expected); + assertThat(result).usingRecursiveComparison().isEqualTo(expected); } } \ No newline at end of file diff --git a/src/test/java/com/bbteam/budgetbuddies/domain/expense/repository/ExpenseRepositoryTest.java b/src/test/java/com/bbteam/budgetbuddies/domain/expense/repository/ExpenseRepositoryTest.java index c1b34ada..97806cd2 100644 --- a/src/test/java/com/bbteam/budgetbuddies/domain/expense/repository/ExpenseRepositoryTest.java +++ b/src/test/java/com/bbteam/budgetbuddies/domain/expense/repository/ExpenseRepositoryTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.*; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -30,59 +29,63 @@ class ExpenseRepositoryTest { private CategoryRepository categoryRepository; @Test - @DisplayName("findAllByUserIdFOrPeriod 성공") - void findAllByUserIdForPeriod_Success() { + void 월별_소비조회_월초_월말_조회_성공() { // given - User user = userRepository.save( - User.builder().email("email").age(24).name("name").phoneNumber("010-1234-5678").build()); + User user = userRepository.save(User.builder() + .email("email") + .age(24) + .name("test user") + .phoneNumber("010-1234-5678") + .mobileCarrier("TEST") + .build()); Category userCategory = categoryRepository.save( Category.builder().name("유저 카테고리").user(user).isDefault(false).build()); - LocalDate startDate = LocalDate.of(2024, 7, 1); - LocalDate endDate = startDate.withDayOfMonth(startDate.lengthOfMonth()); + LocalDate startMonth = LocalDate.of(2024, 7, 1); + LocalDate nextMonth = startMonth.plusMonths(1L); - List expected = setExpense(user, userCategory, startDate); + List expected = getExpectedExpense(user, userCategory); + setUnExpectedExpense(user, userCategory, nextMonth); // when - List result = expenseRepository.findAllByUserIdForPeriod(user, - startDate.atStartOfDay(), endDate.atStartOfDay()); + List result = expenseRepository.findAllByUserIdForPeriod(user.getId(), startMonth.atStartOfDay(), + nextMonth.atStartOfDay()); assertThat(result).usingRecursiveComparison().isEqualTo(expected); } - private List setExpense(User user, Category userCategory, LocalDate startDate) { - setUnexpectedExpense(user, userCategory, startDate); - - return setExpectedExpenseOrderByDateDesc(user, userCategory, startDate); - } - - private List setExpectedExpenseOrderByDateDesc(User user, Category userCategory, LocalDate startDate) { - List expenses = new ArrayList<>(); - - for (int i = startDate.lengthOfMonth(); i > startDate.lengthOfMonth() - 5; i--) { - Expense expense = Expense.builder() - .user(user) - .category(userCategory) - .amount(100000L * i) - .expenseDate(startDate.withDayOfMonth(i).atStartOfDay()) - .build(); - - expenses.add(expenseRepository.save(expense)); - } - return expenses; + private List getExpectedExpense(User user, Category userCategory) { + Expense monthOfStartExpense = Expense.builder() + .user(user) + .category(userCategory) + .amount(100000L) + .expenseDate(LocalDate.of(2024, 7, 1).atStartOfDay()) + .build(); + + Expense monthOfLastExpense = Expense.builder() + .user(user) + .category(userCategory) + .amount(100000L) + .expenseDate(LocalDate.of(2024, 7, 31).atTime(23, 59)) + .build(); + + return List.of(expenseRepository.save(monthOfLastExpense), expenseRepository.save(monthOfStartExpense)); } - private void setUnexpectedExpense(User user, Category userCategory, LocalDate startDate) { - for (int i = 1; i <= 5; i++) { - Expense expense = Expense.builder() - .user(user) - .category(userCategory) - .amount(100000L * i) - .expenseDate(startDate.withMonth(8).atStartOfDay()) - .build(); - - expenseRepository.save(expense); - } + private void setUnExpectedExpense(User user, Category userCategory, LocalDate nextMonth) { + expenseRepository.save(Expense.builder() + .user(user) + .category(userCategory) + .amount(100000L) + .expenseDate(LocalDate.of(2024, 8, 1).atStartOfDay()) + .build()); + + expenseRepository.save(Expense.builder() + .user(user) + .category(userCategory) + .amount(100000L) + .expenseDate(LocalDate.of(2024, 6, 30).atTime(23, 59)) + .build()); } } \ No newline at end of file diff --git a/src/test/java/com/bbteam/budgetbuddies/domain/expense/service/ExpenseServiceImplTest.java b/src/test/java/com/bbteam/budgetbuddies/domain/expense/service/ExpenseServiceImplTest.java index 2c7a8484..3de8bbed 100644 --- a/src/test/java/com/bbteam/budgetbuddies/domain/expense/service/ExpenseServiceImplTest.java +++ b/src/test/java/com/bbteam/budgetbuddies/domain/expense/service/ExpenseServiceImplTest.java @@ -24,7 +24,7 @@ import com.bbteam.budgetbuddies.domain.expense.converter.ExpenseConverter; import com.bbteam.budgetbuddies.domain.expense.dto.CompactExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.dto.DailyExpenseResponseDto; -import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseResponseDto; +import com.bbteam.budgetbuddies.domain.expense.dto.DetailExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.dto.MonthlyExpenseResponseDto; import com.bbteam.budgetbuddies.domain.expense.entity.Expense; import com.bbteam.budgetbuddies.domain.expense.repository.ExpenseRepository; @@ -54,11 +54,8 @@ void setUp() { } @Test - @DisplayName("월별 소비 조회 소비를 DailyExpenseResponseDto로 반환") - void getMonthlyExpense_Success() { + void 월별_소비목록_조회_소비일을_기준으로_소비를_반환_성공() { // given - given(userRepository.findById(user.getId())).willReturn(Optional.of(user)); - Category userCategory = Mockito.spy(Category.builder().build()); given(userCategory.getId()).willReturn(-1L); @@ -66,32 +63,10 @@ void getMonthlyExpense_Success() { List expenses = generateExpenseList(requestMonth, user, userCategory); - given(expenseRepository.findAllByUserIdForPeriod(any(User.class), any(LocalDateTime.class), + given(expenseRepository.findAllByUserIdForPeriod(anyLong(), any(LocalDateTime.class), any(LocalDateTime.class))).willReturn(expenses); - MonthlyExpenseResponseDto expected = MonthlyExpenseResponseDto.builder() - .expenseMonth(LocalDate.of(2024, 07, 01)) - .totalConsumptionAmount(300_000L) - .dailyExpenses(List.of(DailyExpenseResponseDto.builder() - .daysOfMonth(2) - .daysOfTheWeek("화요일") - .expenses(List.of(CompactExpenseResponseDto.builder() - .amount(200_000L) - .description("User 소비") - .expenseId(-2L) - .categoryId(userCategory.getId()) - .build())) - .build(), DailyExpenseResponseDto.builder() - .daysOfMonth(1) - .daysOfTheWeek("월요일") - .expenses(List.of(CompactExpenseResponseDto.builder() - .amount(100_000L) - .description("User 소비") - .expenseId(-1L) - .categoryId(userCategory.getId()) - .build())) - .build())) - .build(); + MonthlyExpenseResponseDto expected = getExpectedMonthlyExpense(userCategory); // when MonthlyExpenseResponseDto result = expenseService.getMonthlyExpense(user.getId(), requestMonth); @@ -100,6 +75,35 @@ void getMonthlyExpense_Success() { assertThat(result).usingRecursiveComparison().isEqualTo(expected); } + private MonthlyExpenseResponseDto getExpectedMonthlyExpense(Category userCategory) { + return MonthlyExpenseResponseDto.builder() + .expenseMonth(LocalDate.of(2024, 7, 1)) + .totalConsumptionAmount(300_000L) + .dailyExpenses( + List.of( + DailyExpenseResponseDto.builder() + .daysOfMonth(2) + .daysOfTheWeek("화요일") + .expenses(List.of(CompactExpenseResponseDto.builder() + .amount(200_000L) + .description("User 소비") + .expenseId(-2L) + .categoryId(userCategory.getId()) + .build())) + .build(), + DailyExpenseResponseDto.builder() + .daysOfMonth(1) + .daysOfTheWeek("월요일") + .expenses(List.of(CompactExpenseResponseDto.builder() + .amount(100_000L) + .description("User 소비") + .expenseId(-1L) + .categoryId(userCategory.getId()) + .build())) + .build())) + .build(); + } + private List generateExpenseList(LocalDate month, User user, Category userCategory) { Expense e1 = Mockito.spy(Expense.builder() .amount(100_000L) @@ -123,8 +127,7 @@ private List generateExpenseList(LocalDate month, User user, Category u } @Test - @DisplayName("findExpenseFromUserIdAndExpenseId : 성공") - void findExpenseResponseFromUserIdAndExpenseId_Success() { + void 소비상세조회_성공() { // given final Long expenseId = -1L; @@ -135,8 +138,7 @@ void findExpenseResponseFromUserIdAndExpenseId_Success() { given(expense.getId()).willReturn(expenseId); given(expenseRepository.findById(expense.getId())).willReturn(Optional.of(expense)); - ExpenseResponseDto expected = ExpenseResponseDto.builder() - .userId(user.getId()) + DetailExpenseResponseDto expected = DetailExpenseResponseDto.builder() .expenseId(-1L) .description("유저 소비") .categoryName("유저 카테고리") @@ -144,15 +146,14 @@ void findExpenseResponseFromUserIdAndExpenseId_Success() { .build(); // when - ExpenseResponseDto result = expenseService.findExpenseResponseFromUserIdAndExpenseId(user.getId(), expenseId); + DetailExpenseResponseDto result = expenseService.findDetailExpenseResponse(user.getId(), expenseId); // then assertThat(result).usingRecursiveComparison().isEqualTo(expected); } @Test - @DisplayName("findExpenseFromUserIdAndExpenseId : 소비 유저와 다른 유저로 인한 예외 반환") - void findExpenseResponseFromUserIdAndExpenseId_Fail() { + void 조회_권한이_없는_유저의_소비조회로_인한_예외_반환() { // given final Long expenseId = -1L; @@ -164,6 +165,6 @@ void findExpenseResponseFromUserIdAndExpenseId_Fail() { // then assertThrows(IllegalArgumentException.class, - () -> expenseService.findExpenseResponseFromUserIdAndExpenseId(-2L, expenseId)); + () -> expenseService.findDetailExpenseResponse(-2L, expenseId)); } } \ No newline at end of file