fix: 분석화면 총지출 금액 불일치 및 다국어 지원 개선

- 월별지출현황과 총지출요약 카드의 7월 금액 불일치 수정
- 현재 월은 currentPrice 사용, 과거 월은 이벤트 기간 체크
- 월별 차트 라벨 다국어 지원 추가 (한/영/일/중)
- 홈/분석 화면 총지출 금액 통일을 위한 통화 변환 로직 모듈화
- ExchangeRateService에 convertBetweenCurrencies 메서드 추가
- CurrencyUtil 및 SubscriptionProvider 리팩토링

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-07-17 00:07:24 +09:00
parent 46883f7314
commit 91bc91383b
3 changed files with 121 additions and 97 deletions

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:uuid/uuid.dart';
import 'package:intl/intl.dart';
import '../models/subscription_model.dart';
import '../services/notification_service.dart';
import '../services/exchange_rate_service.dart';
@@ -258,37 +259,24 @@ class SubscriptionProvider extends ChangeNotifier {
? CurrencyUtil.getDefaultCurrency(locale)
: 'KRW'; // 기본값
debugPrint('[calculateTotalExpense] 계산 시작 - 타겟 통화: $targetCurrency');
double total = 0.0;
for (final subscription in _subscriptions) {
final currentPrice = subscription.currentPrice;
debugPrint('[calculateTotalExpense] ${subscription.serviceName}: '
'${currentPrice} ${subscription.currency} (이벤트 적용: ${subscription.isCurrentlyInEvent})');
final converted = await ExchangeRateService().convertBetweenCurrencies(
currentPrice,
subscription.currency,
targetCurrency,
);
if (subscription.currency == targetCurrency) {
// 이미 타겟 통화인 경우
total += currentPrice;
} else if (subscription.currency == 'USD') {
// USD를 타겟 통화로 변환
final converted = await ExchangeRateService().convertUsdToTarget(currentPrice, targetCurrency);
total += converted ?? currentPrice;
} else if (targetCurrency == 'USD') {
// 타겟이 USD인 경우 다른 통화를 USD로 변환
final converted = await ExchangeRateService().convertTargetToUsd(currentPrice, subscription.currency);
total += converted ?? currentPrice;
} else {
// USD를 거쳐서 변환 (예: KRW → USD → JPY)
// 1단계: 구독 통화를 USD로 변환
final usdAmount = await ExchangeRateService().convertTargetToUsd(currentPrice, subscription.currency);
if (usdAmount != null) {
// 2단계: USD를 타겟 통화로 변환
final converted = await ExchangeRateService().convertUsdToTarget(usdAmount, targetCurrency);
total += converted ?? currentPrice;
} else {
// 변환 실패 시 원래 값 사용
total += currentPrice;
}
}
}
debugPrint('[calculateTotalExpense] 총 지출 계산 완료: $total $targetCurrency');
return total;
}
@@ -307,8 +295,31 @@ class SubscriptionProvider extends ChangeNotifier {
final month = DateTime(now.year, now.month - i, 1);
double monthTotal = 0.0;
// 현재 월인지 확인
final isCurrentMonth = (month.year == now.year && month.month == now.month);
if (isCurrentMonth) {
debugPrint('[getMonthlyExpenseData] 현재 월(${month.year}-${month.month}) 계산 중...');
}
// 해당 월에 활성화된 구독 계산
for (final subscription in _subscriptions) {
if (isCurrentMonth) {
// 현재 월인 경우: 모든 활성 구독 포함 (calculateTotalExpense와 동일하게)
final cost = subscription.currentPrice;
debugPrint('[getMonthlyExpenseData] 현재 월 - ${subscription.serviceName}: '
'${cost} ${subscription.currency} (이벤트 적용: ${subscription.isCurrentlyInEvent})');
// 통화 변환
final converted = await ExchangeRateService().convertBetweenCurrencies(
cost,
subscription.currency,
targetCurrency,
);
monthTotal += converted ?? cost;
} else {
// 과거 월인 경우: 기존 로직 유지
// 구독이 해당 월에 활성화되어 있었는지 확인
final subscriptionStartDate = subscription.nextBillingDate.subtract(
Duration(days: _getBillingCycleDays(subscription.billingCycle)),
@@ -318,45 +329,38 @@ class SubscriptionProvider extends ChangeNotifier {
subscription.nextBillingDate.isAfter(month)) {
// 해당 월의 비용 계산 (이벤트 가격 고려)
double cost;
if (subscription.isEventActive &&
subscription.eventStartDate != null &&
subscription.eventEndDate != null &&
month.isAfter(subscription.eventStartDate!) &&
month.isBefore(subscription.eventEndDate!)) {
// 이벤트 기간과 해당 월이 겹치는지 확인
subscription.eventStartDate!.isBefore(DateTime(month.year, month.month + 1, 1)) &&
subscription.eventEndDate!.isAfter(month)) {
cost = subscription.eventPrice ?? subscription.monthlyCost;
} else {
cost = subscription.monthlyCost;
}
// 통화 변환
if (subscription.currency == targetCurrency) {
monthTotal += cost;
} else if (subscription.currency == 'USD') {
final converted = await ExchangeRateService().convertUsdToTarget(cost, targetCurrency);
final converted = await ExchangeRateService().convertBetweenCurrencies(
cost,
subscription.currency,
targetCurrency,
);
monthTotal += converted ?? cost;
} else if (targetCurrency == 'USD') {
final converted = await ExchangeRateService().convertTargetToUsd(cost, subscription.currency);
monthTotal += converted ?? cost;
} else {
// USD를 거쳐서 변환 (예: KRW → USD → JPY)
// 1단계: 구독 통화를 USD로 변환
final usdAmount = await ExchangeRateService().convertTargetToUsd(cost, subscription.currency);
if (usdAmount != null) {
// 2단계: USD를 타겟 통화로 변환
final converted = await ExchangeRateService().convertUsdToTarget(usdAmount, targetCurrency);
monthTotal += converted ?? cost;
} else {
// 변환 실패 시 원래 값 사용
monthTotal += cost;
}
}
}
if (isCurrentMonth) {
debugPrint('[getMonthlyExpenseData] 현재 월(${_getMonthLabel(month, locale ?? 'en')}) 총 지출: $monthTotal $targetCurrency');
}
monthlyData.add({
'month': month,
'totalExpense': monthTotal,
'monthName': _getMonthLabel(month),
'monthName': _getMonthLabel(month, locale ?? 'en'),
});
}
@@ -386,12 +390,20 @@ class SubscriptionProvider extends ChangeNotifier {
}
/// 월 라벨을 생성합니다.
String _getMonthLabel(DateTime month) {
final months = [
'1월', '2월', '3월', '4월', '5월', '6월',
'7월', '8월', '9월', '10월', '11월', '12월'
];
return months[month.month - 1];
String _getMonthLabel(DateTime month, String locale) {
if (locale == 'ko') {
// 한국어는 기존 형식 유지 (1월, 2월 등)
return '${month.month}';
} else if (locale == 'ja') {
// 일본어
return '${month.month}';
} else if (locale == 'zh') {
// 중국어
return '${month.month}';
} else {
// 영어 및 기타 언어는 약식 월 이름 사용
return DateFormat('MMM', locale).format(month);
}
}
/// categoryId가 없는 기존 구독들에 대해 자동으로 카테고리 할당

View File

@@ -117,22 +117,13 @@ class CurrencyUtil {
for (var subscription in subscriptions) {
final price = subscription.currentPrice;
if (subscription.currency == defaultCurrency) {
// 기본 통화면 그대로 합산
total += price;
} else if (subscription.currency == 'USD') {
// USD면 기본 통화로 변환
final converted = await _exchangeRateService.convertUsdToTarget(price, defaultCurrency);
if (converted != null) {
total += converted;
}
} else if (defaultCurrency == 'USD') {
// 기본 통화가 USD인 경우 다른 통화를 USD로 변환
final converted = await _exchangeRateService.convertTargetToUsd(price, subscription.currency);
if (converted != null) {
total += converted;
}
}
final converted = await _exchangeRateService.convertBetweenCurrencies(
price,
subscription.currency,
defaultCurrency,
);
total += converted ?? price;
}
return total;
@@ -188,20 +179,13 @@ class CurrencyUtil {
if (subscription.isCurrentlyInEvent) {
final savings = subscription.eventSavings;
if (subscription.currency == defaultCurrency) {
total += savings;
} else if (subscription.currency == 'USD') {
final converted = await _exchangeRateService.convertUsdToTarget(savings, defaultCurrency);
if (converted != null) {
total += converted;
}
} else if (defaultCurrency == 'USD') {
// 기본 통화가 USD인 경우 다른 통화를 USD로 변환
final converted = await _exchangeRateService.convertTargetToUsd(savings, subscription.currency);
if (converted != null) {
total += converted;
}
}
final converted = await _exchangeRateService.convertBetweenCurrencies(
savings,
subscription.currency,
defaultCurrency,
);
total += converted ?? savings;
}
}

View File

@@ -116,6 +116,34 @@ class ExchangeRateService {
}
}
/// 두 통화 간 변환을 수행합니다. (USD를 거쳐서 변환)
Future<double?> convertBetweenCurrencies(
double amount,
String fromCurrency,
String toCurrency
) async {
if (fromCurrency == toCurrency) {
return amount;
}
// fromCurrency → USD → toCurrency
double? usdAmount;
if (fromCurrency == 'USD') {
usdAmount = amount;
} else {
usdAmount = await convertTargetToUsd(amount, fromCurrency);
}
if (usdAmount == null) return null;
if (toCurrency == 'USD') {
return usdAmount;
} else {
return await convertUsdToTarget(usdAmount, toCurrency);
}
}
/// 현재 환율 정보를 포맷팅하여 텍스트로 반환합니다.
Future<String> getFormattedExchangeRateInfo() async {
final rate = await getUsdToKrwRate();