Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public BudgetProgressResponseDto getBudgetProgress(int year, int month, LocalDat
List<CategoryBudgetProgressUnitDto> 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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<CategoryBudgetSimpleUnitDto> categoryBudgets) {
Set<ExpenseCategory> existingCategories = categoryBudgets.stream()
Expand Down Expand Up @@ -55,12 +60,12 @@ public List<CategoryBudgetProgressUnitDto> getCategoryBudgetProgresses(User user
List<CategoryBudget> budgets = findByMonthlyBudget(monthlyBudget);

// 특정 기간 동안의 모든 카테고리별 총 지출
Map<String, Long> totalExpensesByCategory = transactionService.mapTotalExpenseForAllCategories(user, startOfMonth);
Map<ExpenseCategory, Long> 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);

Expand All @@ -83,14 +88,14 @@ public List<CategoryBudgetPlanUnitDto> getCategoryBudgetPlans(User user, Monthly
List<CategoryBudget> budgets = findByMonthlyBudget(monthlyBudget);

// 지난달 모든 카테고리의 총 지출
Map<String, Long> lastMonthExpensesByCategory = transactionService.mapTotalExpenseForAllCategories(user, lastMonthStart);
Map<ExpenseCategory, Long> 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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) // 이번 주 남은 예산 (오늘~일요일)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<ChatMessage> chatRedisRepository;

// 채팅 저장
Expand Down Expand Up @@ -85,12 +87,12 @@ public List<ChatBudgetInfoDto> getCategoryBudgetRemainingAmount(User user) {
List<CategoryBudget> budgets = categoryBudgetService.findByMonthlyBudget(monthlyBudget);

// 특정 기간 동안의 모든 카테고리별 총 지출
Map<String, Long> totalExpensesByCategory = transactionService.mapTotalExpenseForAllCategories(user, startOfMonth);
Map<ExpenseCategory, Long> 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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -11,12 +12,12 @@
import java.util.Map;

public interface ExpenseRepository extends JpaRepository<Expense, Long> {
@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<String, Long> getTotalExpenseForAllCategories(
List<Object[]> getTotalExpenseForAllCategories(
@Param("user") User user,
@Param("startOfMonth") LocalDateTime startOfMonth,
@Param("endOfMonth") LocalDateTime endOfMonth
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
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;
import tamtam.mooney.domain.mission.service.MissionService;
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;
Expand Down Expand Up @@ -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<ExpenseCategory, Long> 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<Object[]>로 쿼리 결과 받기
List<Object[]> rawResults = expenseRepository.getTotalExpenseForAllCategories(user, startOfMonth, endOfMonth);

// 수동 변환: Map<ExpenseCategory, Long>
Map<ExpenseCategory, Long> 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;
}


}
Original file line number Diff line number Diff line change
@@ -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.*;
Expand All @@ -13,7 +14,6 @@
import tamtam.mooney.domain.user.service.UserService;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;

@Service
Expand Down Expand Up @@ -66,13 +66,6 @@ public MonthlyTransactionDayUnitDto getTransactionsByDate(LocalDate date) {
return MonthlyTransactionDayUnitDto.from(date, totalIncomeAmount, totalExpenseAmount, expenses, incomes);
}

@Transactional(readOnly = true)
public Map<String, Long> 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();
Expand Down
Loading