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 fc5317d..91a60a2 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 @@ -95,7 +95,7 @@ public BudgetProgressResponseDto getBudgetProgress(int year, int month, LocalDat // 예정되어 있는 고정 지출 조회 (아직 발생하지 않은) long pendingExpenseAmount = Optional.ofNullable(scheduledTransactionService.getTotalPendingScheduledTransactionAmountByMonth(user, startOfMonth, endOfMonth)).orElse(0L); // 현재까지 발생한 지출 조회 - long totalExpenseAmount = Optional.ofNullable(expenseService.getTotalExpenseAmountForMonth(user, startOfMonth, endOfMonth)).orElse(0L); + long totalExpenseAmount = Optional.ofNullable(expenseService.getTotalExpenseAmountForPeriod(user, startOfMonth, endOfMonth)).orElse(0L); // 남은 예산 계산 long remainingBudgetAmount = Math.max(monthlyBudgetAmount - pendingExpenseAmount - totalExpenseAmount, 0); // 음수 방지 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 ba1b622..5ee96ad 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 @@ -5,6 +5,8 @@ import org.springframework.transaction.annotation.Transactional; import tamtam.mooney.domain.budget.entity.MonthlyBudget; import tamtam.mooney.domain.budget.repository.MonthlyBudgetRepository; +import tamtam.mooney.domain.transaction.repository.TransactionRepository; +import tamtam.mooney.domain.transaction.service.ExpenseService; import tamtam.mooney.domain.user.dto.UserHomeWeeklyBudgetDto; import tamtam.mooney.domain.user.entity.User; import tamtam.mooney.global.exception.CustomException; @@ -12,11 +14,15 @@ import java.time.LocalDate; +import static java.lang.Math.max; + @Service @Transactional @RequiredArgsConstructor public class MonthlyBudgetService { private final MonthlyBudgetRepository monthlyBudgetRepository; + private final ExpenseService expenseService; + private final TransactionRepository transactionRepository; @Transactional(readOnly = true) public MonthlyBudget getMonthlyBudget(User user, LocalDate startOfMonth) { @@ -26,6 +32,45 @@ public MonthlyBudget getMonthlyBudget(User user, LocalDate startOfMonth) { @Transactional(readOnly = true) public UserHomeWeeklyBudgetDto getWeeklyBudgetInfo(User user, LocalDate today) { - return null; + LocalDate startOfMonth = today.withDayOfMonth(1); + LocalDate endOfMonth = startOfMonth.withDayOfMonth(startOfMonth.lengthOfMonth()); + MonthlyBudget monthlyBudget = getMonthlyBudget(user, startOfMonth); + + // 주차 범위 계산 (월~일) + // 진짜 endOfWeek는 일요일이 아니라 이번 달의 마지막 날을 초과하지 않는 범위 + LocalDate startOfWeek = today.with(java.time.DayOfWeek.MONDAY); + LocalDate rawEndOfWeek = today.with(java.time.DayOfWeek.SUNDAY); + LocalDate endOfWeek = rawEndOfWeek.isAfter(endOfMonth) ? endOfMonth : rawEndOfWeek; + + // 이번 주 일 수 / 남은 일 수 = 이번 주가 남은 예산에서 차지하는 비율 + int remainingDays = Math.max(today.lengthOfMonth() - today.getDayOfMonth() + 1, 1); + + // 이번주 지출 + long thisMonthSpentAmount = expenseService.getTotalExpenseAmountForPeriod(user, startOfMonth, endOfMonth); + // 남은 이번달 예산 + long remainingMonthlyBudget = monthlyBudget.getAmount() - thisMonthSpentAmount; + + // 💡 핵심: 남은 예산 중 이번 주가 차지하는 비율만큼만 예산 할당 + int totalWeekDays = (int) (endOfWeek.toEpochDay() - startOfWeek.toEpochDay() + 1); + long thisWeekBudgetAmount = remainingMonthlyBudget * totalWeekDays / remainingDays; + + // 사용액 추정 = (이번주 지출 / 오늘까지 경과 일수) * (월요일~오늘 해당하는 일수) + long thisWeekSpentAmount = expenseService.getTotalExpenseAmountForPeriod(user, startOfWeek, endOfWeek); + + // 예정 지출 추정 = (전체 예정 지출 / 총일수) * (오늘~일요일 일수) + 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); + + return UserHomeWeeklyBudgetDto.builder() + .remainingBudgetAmount(thisWeekRemainingBudgetAmount) // 이번 주 남은 예산 (오늘~일요일) + .dailyBudgetAmount(dailyBudgetAmount) // 이번 주 예산 중 하루 예산 (오늘~일요일) + .budgetUsagePercentage(budgetUsagePercentage) // 예산 퍼센트 (85 등) + .totalBudgetAmount(thisWeekBudgetAmount) // 이번 주 예산이 원래 얼마였는지 + .spentAmount(thisWeekSpentAmount) // 이번주 예산 중에서 현재까지 사용한 금액 (이번주 월요일~오늘) + .scheduledExpenseAmount(scheduledExpenseAmount) // 예정된 지출 (이번주 오늘~일요일 사이) + .build(); } } diff --git a/mooney/src/main/java/tamtam/mooney/domain/transaction/repository/TransactionRepository.java b/mooney/src/main/java/tamtam/mooney/domain/transaction/repository/TransactionRepository.java index d32fa0d..fc93e6b 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/transaction/repository/TransactionRepository.java +++ b/mooney/src/main/java/tamtam/mooney/domain/transaction/repository/TransactionRepository.java @@ -3,7 +3,6 @@ import io.lettuce.core.dynamic.annotation.Param; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import reactor.core.publisher.Mono; import tamtam.mooney.domain.enums.ExpenseCategory; import tamtam.mooney.domain.transaction.entity.Transaction; import tamtam.mooney.domain.user.entity.User; @@ -17,8 +16,8 @@ public interface TransactionRepository extends JpaRepository { // 특정 월의 총 지출 금액 - @Query("SELECT COALESCE(SUM(t.amount), 0) FROM Transaction t WHERE TYPE(t) = Expense AND t.user = :user AND t.transactionTime BETWEEN :startOfMonth AND :endOfMonth") - Long getTotalExpenseAmountForMonth(User user, LocalDateTime startOfMonth, LocalDateTime endOfMonth); + @Query("SELECT COALESCE(SUM(t.amount), 0) FROM Transaction t WHERE TYPE(t) = Expense AND t.user = :user AND t.transactionTime BETWEEN :startDateTime AND :endDateTime") + Long getTotalExpenseAmountForPeriod(User user, LocalDateTime startDateTime, LocalDateTime endDateTime); // 특정 월의 총 수입, 지출 금액 @Query(""" @@ -179,6 +178,5 @@ List> findWeeklyAggregatedTransactions(@Param("userId") Long @Param("validCategories") List validCategories, @Param("startDate") LocalDateTime startDate); - } 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 0fb90b1..a138253 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 @@ -3,7 +3,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import tamtam.mooney.domain.mission.entity.Mission; import tamtam.mooney.domain.mission.repository.MissionRepository; import tamtam.mooney.domain.mission.service.MissionService; import tamtam.mooney.domain.transaction.dto.ExpenseAddRequestDto; @@ -15,7 +14,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.List; @Service @Transactional @@ -55,9 +53,9 @@ public String createExpense(ExpenseAddRequestDto request) { } @Transactional(readOnly = true) - public Long getTotalExpenseAmountForMonth(User user, LocalDate startDate, LocalDate endDate) { + public Long getTotalExpenseAmountForPeriod(User user, LocalDate startDate, LocalDate endDate) { LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(23, 59, 59); - return transactionRepository.getTotalExpenseAmountForMonth(user, startDateTime, endDateTime); + return transactionRepository.getTotalExpenseAmountForPeriod(user, startDateTime, endDateTime); } }