diff --git a/mooney/src/main/java/tamtam/mooney/domain/budget/service/BudgetService.java b/mooney/src/main/java/tamtam/mooney/domain/budget/service/BudgetService.java index 91a60a2..dbe9de4 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/budget/service/BudgetService.java +++ b/mooney/src/main/java/tamtam/mooney/domain/budget/service/BudgetService.java @@ -105,7 +105,7 @@ public BudgetProgressResponseDto getBudgetProgress(int year, int month, LocalDat List categoryBudgets = categoryBudgetService.getCategoryBudgetProgresses(user, monthlyBudget, startOfMonth); // 사용한 예산 비율 계산 - int budgetUsagePercentage = (int) ((totalExpenseAmount + pendingExpenseAmount) * 100 / monthlyBudgetAmount); + int budgetUsagePercentage = monthlyBudgetAmount > 0 ? (int) ((totalExpenseAmount + pendingExpenseAmount) * 100 / monthlyBudgetAmount) : 0; return BudgetProgressResponseDto.builder() .remainingBudgetAmount(remainingBudgetAmount) diff --git a/mooney/src/main/java/tamtam/mooney/domain/budget/service/CategoryBudgetService.java b/mooney/src/main/java/tamtam/mooney/domain/budget/service/CategoryBudgetService.java index a8fbdd4..2a7f90c 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/budget/service/CategoryBudgetService.java +++ b/mooney/src/main/java/tamtam/mooney/domain/budget/service/CategoryBudgetService.java @@ -1,6 +1,7 @@ package tamtam.mooney.domain.budget.service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import tamtam.mooney.domain.budget.dto.CategoryBudgetPlanUnitDto; @@ -10,8 +11,10 @@ import tamtam.mooney.domain.budget.entity.MonthlyBudget; import tamtam.mooney.domain.budget.repository.CategoryBudgetRepository; import tamtam.mooney.domain.enums.ExpenseCategory; +import tamtam.mooney.domain.transaction.service.ExpenseService; import tamtam.mooney.domain.transaction.service.TransactionService; import tamtam.mooney.domain.user.entity.User; +import tamtam.mooney.domain.user.service.UserService; import java.time.LocalDate; import java.util.Arrays; @@ -21,12 +24,14 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +@Slf4j @Service @Transactional @RequiredArgsConstructor public class CategoryBudgetService { private final CategoryBudgetRepository categoryBudgetRepository; private final TransactionService transactionService; + private final ExpenseService expenseService; public void saveCategoryBudgets(MonthlyBudget monthlyBudget, List categoryBudgets) { Set existingCategories = categoryBudgets.stream() @@ -55,12 +60,12 @@ public List getCategoryBudgetProgresses(User user List budgets = findByMonthlyBudget(monthlyBudget); // 특정 기간 동안의 모든 카테고리별 총 지출 - Map totalExpensesByCategory = transactionService.mapTotalExpenseForAllCategories(user, startOfMonth); + Map totalExpensesByCategory = expenseService.mapTotalExpenseForAllCategories(user, startOfMonth); // 각 카테고리의 실제 지출 계산 return budgets.stream() .map(cb -> { - Long spent = totalExpensesByCategory.getOrDefault(cb.getExpenseCategory().name(), 0L); + Long spent = totalExpensesByCategory.getOrDefault(cb.getExpenseCategory(), 0L); int spentPercentage = cb.getAmount() > 0 ? (int) ((spent * 100.0) / cb.getAmount()) : 0; long remaining = Math.max(cb.getAmount() - spent, 0); @@ -83,14 +88,14 @@ public List getCategoryBudgetPlans(User user, Monthly List budgets = findByMonthlyBudget(monthlyBudget); // 지난달 모든 카테고리의 총 지출 - Map lastMonthExpensesByCategory = transactionService.mapTotalExpenseForAllCategories(user, lastMonthStart); + Map lastMonthExpensesByCategory = expenseService.mapTotalExpenseForAllCategories(user, lastMonthStart); // 각 카테고리별 예산 계획 생성 return budgets.stream() .map(cb -> new CategoryBudgetPlanUnitDto( cb.getCategoryBudgetId(), cb.getExpenseCategory(), - lastMonthExpensesByCategory.getOrDefault(cb.getExpenseCategory().name(), 0L), + lastMonthExpensesByCategory.getOrDefault(cb.getExpenseCategory(), 0L), cb.getAmount() )).collect(Collectors.toList()); } diff --git a/mooney/src/main/java/tamtam/mooney/domain/budget/service/MonthlyBudgetService.java b/mooney/src/main/java/tamtam/mooney/domain/budget/service/MonthlyBudgetService.java index 5ee96ad..f258f4f 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/budget/service/MonthlyBudgetService.java +++ b/mooney/src/main/java/tamtam/mooney/domain/budget/service/MonthlyBudgetService.java @@ -52,7 +52,7 @@ public UserHomeWeeklyBudgetDto getWeeklyBudgetInfo(User user, LocalDate today) { // 💡 핵심: 남은 예산 중 이번 주가 차지하는 비율만큼만 예산 할당 int totalWeekDays = (int) (endOfWeek.toEpochDay() - startOfWeek.toEpochDay() + 1); - long thisWeekBudgetAmount = remainingMonthlyBudget * totalWeekDays / remainingDays; + long thisWeekBudgetAmount = remainingDays > 0 ? remainingMonthlyBudget * totalWeekDays / remainingDays : 0; // 사용액 추정 = (이번주 지출 / 오늘까지 경과 일수) * (월요일~오늘 해당하는 일수) long thisWeekSpentAmount = expenseService.getTotalExpenseAmountForPeriod(user, startOfWeek, endOfWeek); @@ -61,8 +61,12 @@ public UserHomeWeeklyBudgetDto getWeeklyBudgetInfo(User user, LocalDate today) { long scheduledExpenseAmount = 0L; // TODO: 추후 수정 long thisWeekRemainingBudgetAmount = max(thisWeekBudgetAmount - thisWeekSpentAmount - scheduledExpenseAmount, 0L); - long dailyBudgetAmount = max((thisWeekRemainingBudgetAmount - scheduledExpenseAmount) / totalWeekDays, 0L); - int budgetUsagePercentage = max((int)(thisWeekSpentAmount * 100 / thisWeekBudgetAmount), 0); + long dailyBudgetAmount = max(totalWeekDays > 0 ? (thisWeekRemainingBudgetAmount - scheduledExpenseAmount) / totalWeekDays : 0, 0L); + int budgetUsagePercentage = Math.max( + thisWeekBudgetAmount > 0 ? (int)(thisWeekSpentAmount * 100 / thisWeekBudgetAmount) : 0, + 0 + ); + return UserHomeWeeklyBudgetDto.builder() .remainingBudgetAmount(thisWeekRemainingBudgetAmount) // 이번 주 남은 예산 (오늘~일요일) diff --git a/mooney/src/main/java/tamtam/mooney/domain/chat/service/ChatService.java b/mooney/src/main/java/tamtam/mooney/domain/chat/service/ChatService.java index bdcd0b7..6170a7a 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/chat/service/ChatService.java +++ b/mooney/src/main/java/tamtam/mooney/domain/chat/service/ChatService.java @@ -13,6 +13,8 @@ import tamtam.mooney.domain.chat.dto.ChatMessage; import tamtam.mooney.domain.chat.dto.ChatRequestDto; import tamtam.mooney.domain.chat.dto.ChatResponseDto; +import tamtam.mooney.domain.enums.ExpenseCategory; +import tamtam.mooney.domain.transaction.service.ExpenseService; import tamtam.mooney.domain.transaction.service.TransactionService; import tamtam.mooney.domain.user.entity.User; import tamtam.mooney.domain.user.service.UserService; @@ -36,7 +38,7 @@ public class ChatService { private final OpenAIService openAIService; private final MonthlyBudgetService monthlyBudgetService; private final CategoryBudgetService categoryBudgetService; - private final TransactionService transactionService; + private final ExpenseService expenseService; private final GenericRedisRepository chatRedisRepository; // 채팅 저장 @@ -85,12 +87,12 @@ public List getCategoryBudgetRemainingAmount(User user) { List budgets = categoryBudgetService.findByMonthlyBudget(monthlyBudget); // 특정 기간 동안의 모든 카테고리별 총 지출 - Map totalExpensesByCategory = transactionService.mapTotalExpenseForAllCategories(user, startOfMonth); + Map totalExpensesByCategory = expenseService.mapTotalExpenseForAllCategories(user, startOfMonth); // 각 카테고리의 실제 지출 계산 return budgets.stream() .map(cb -> { - Long spent = totalExpensesByCategory.getOrDefault(cb.getExpenseCategory().name(), 0L); + Long spent = totalExpensesByCategory.getOrDefault(cb.getExpenseCategory(), 0L); long remaining = Math.max(cb.getAmount() - spent, 0); return new ChatBudgetInfoDto( diff --git a/mooney/src/main/java/tamtam/mooney/domain/transaction/repository/ExpenseRepository.java b/mooney/src/main/java/tamtam/mooney/domain/transaction/repository/ExpenseRepository.java index f5f6e09..c043a12 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/transaction/repository/ExpenseRepository.java +++ b/mooney/src/main/java/tamtam/mooney/domain/transaction/repository/ExpenseRepository.java @@ -3,6 +3,7 @@ import io.lettuce.core.dynamic.annotation.Param; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import tamtam.mooney.domain.enums.ExpenseCategory; import tamtam.mooney.domain.transaction.entity.Expense; import tamtam.mooney.domain.user.entity.User; @@ -11,12 +12,12 @@ import java.util.Map; public interface ExpenseRepository extends JpaRepository { - @Query("SELECT CAST(e.expenseCategory AS string), COALESCE(SUM(t.amount), 0) " + + @Query("SELECT e.expenseCategory, COALESCE(SUM(t.amount), 0) " + "FROM Expense e " + "JOIN Transaction t ON e.transactionId = t.transactionId " + "WHERE t.user = :user AND t.transactionTime BETWEEN :startOfMonth AND :endOfMonth " + "GROUP BY e.expenseCategory") - Map getTotalExpenseForAllCategories( + List getTotalExpenseForAllCategories( @Param("user") User user, @Param("startOfMonth") LocalDateTime startOfMonth, @Param("endOfMonth") LocalDateTime endOfMonth diff --git a/mooney/src/main/java/tamtam/mooney/domain/transaction/service/ExpenseService.java b/mooney/src/main/java/tamtam/mooney/domain/transaction/service/ExpenseService.java index a138253..2accc2a 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/transaction/service/ExpenseService.java +++ b/mooney/src/main/java/tamtam/mooney/domain/transaction/service/ExpenseService.java @@ -1,6 +1,7 @@ package tamtam.mooney.domain.transaction.service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import tamtam.mooney.domain.mission.repository.MissionRepository; @@ -8,19 +9,25 @@ import tamtam.mooney.domain.transaction.dto.ExpenseAddRequestDto; import tamtam.mooney.domain.transaction.entity.Expense; import tamtam.mooney.domain.enums.ExpenseCategory; +import tamtam.mooney.domain.transaction.repository.ExpenseRepository; import tamtam.mooney.domain.transaction.repository.TransactionRepository; import tamtam.mooney.domain.user.entity.User; import tamtam.mooney.domain.user.service.UserService; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +@Slf4j @Service @Transactional @RequiredArgsConstructor public class ExpenseService { private final TransactionRepository transactionRepository; + private final ExpenseRepository expenseRepository; private final UserService userService; // private final LlmCategoryClassifier llmCategoryClassifier; private final MissionRepository missionRepository; @@ -58,4 +65,31 @@ public Long getTotalExpenseAmountForPeriod(User user, LocalDate startDate, Local LocalDateTime endDateTime = endDate.atTime(23, 59, 59); return transactionRepository.getTotalExpenseAmountForPeriod(user, startDateTime, endDateTime); } + + @Transactional(readOnly = true) + public Map mapTotalExpenseForAllCategories(User user, LocalDate startDate) { + LocalDateTime startOfMonth = startDate.atStartOfDay(); + LocalDateTime endOfMonth = startDate.withDayOfMonth(startDate.lengthOfMonth()).atTime(23, 59, 59); + log.info("startOfMonth: " + startOfMonth + " / endOfMonth: " + endOfMonth); + + // List로 쿼리 결과 받기 + List rawResults = expenseRepository.getTotalExpenseForAllCategories(user, startOfMonth, endOfMonth); + + // 수동 변환: Map + Map result = new HashMap<>(); + for (Object[] row : rawResults) { + ExpenseCategory category = (ExpenseCategory) row[0]; + Long amount = (Long) row[1]; + result.put(category, amount); + } + + // 전체 결과 출력 + result.forEach((key, value) -> + log.info("카테고리: " + key.name() + " / 총 지출: " + value) + ); + + return result; + } + + } diff --git a/mooney/src/main/java/tamtam/mooney/domain/transaction/service/TransactionService.java b/mooney/src/main/java/tamtam/mooney/domain/transaction/service/TransactionService.java index 616cd51..614fb45 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/transaction/service/TransactionService.java +++ b/mooney/src/main/java/tamtam/mooney/domain/transaction/service/TransactionService.java @@ -1,6 +1,7 @@ package tamtam.mooney.domain.transaction.service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import tamtam.mooney.domain.transaction.dto.*; @@ -13,7 +14,6 @@ import tamtam.mooney.domain.user.service.UserService; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.*; @Service @@ -66,13 +66,6 @@ public MonthlyTransactionDayUnitDto getTransactionsByDate(LocalDate date) { return MonthlyTransactionDayUnitDto.from(date, totalIncomeAmount, totalExpenseAmount, expenses, incomes); } - @Transactional(readOnly = true) - public Map mapTotalExpenseForAllCategories(User user, LocalDate startDate) { - LocalDateTime startOfMonth = startDate.atStartOfDay(); - LocalDateTime endOfMonth = startDate.withDayOfMonth(startDate.lengthOfMonth()).atTime(23, 59, 59); - return expenseRepository.getTotalExpenseForAllCategories(user, startOfMonth, endOfMonth); - } - @Transactional(readOnly = true) public MonthlyTransactionResponseDto getTransactionsByMonth(int year, int month) { User user = userService.getCurrentUser();