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:
@@ -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})');
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
final converted = await ExchangeRateService().convertBetweenCurrencies(
|
||||
currentPrice,
|
||||
subscription.currency,
|
||||
targetCurrency,
|
||||
);
|
||||
|
||||
total += converted ?? currentPrice;
|
||||
}
|
||||
|
||||
debugPrint('[calculateTotalExpense] 총 지출 계산 완료: $total $targetCurrency');
|
||||
return total;
|
||||
}
|
||||
|
||||
@@ -307,56 +295,72 @@ 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) {
|
||||
// 구독이 해당 월에 활성화되어 있었는지 확인
|
||||
final subscriptionStartDate = subscription.nextBillingDate.subtract(
|
||||
Duration(days: _getBillingCycleDays(subscription.billingCycle)),
|
||||
);
|
||||
|
||||
if (subscriptionStartDate.isBefore(DateTime(month.year, month.month + 1, 1)) &&
|
||||
subscription.nextBillingDate.isAfter(month)) {
|
||||
// 해당 월의 비용 계산 (이벤트 가격 고려)
|
||||
double cost;
|
||||
if (subscription.isEventActive &&
|
||||
subscription.eventStartDate != null &&
|
||||
subscription.eventEndDate != null &&
|
||||
month.isAfter(subscription.eventStartDate!) &&
|
||||
month.isBefore(subscription.eventEndDate!)) {
|
||||
cost = subscription.eventPrice ?? subscription.monthlyCost;
|
||||
} else {
|
||||
cost = subscription.monthlyCost;
|
||||
}
|
||||
if (isCurrentMonth) {
|
||||
// 현재 월인 경우: 모든 활성 구독 포함 (calculateTotalExpense와 동일하게)
|
||||
final cost = subscription.currentPrice;
|
||||
debugPrint('[getMonthlyExpenseData] 현재 월 - ${subscription.serviceName}: '
|
||||
'${cost} ${subscription.currency} (이벤트 적용: ${subscription.isCurrentlyInEvent})');
|
||||
|
||||
// 통화 변환
|
||||
if (subscription.currency == targetCurrency) {
|
||||
monthTotal += cost;
|
||||
} else if (subscription.currency == 'USD') {
|
||||
final converted = await ExchangeRateService().convertUsdToTarget(cost, 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;
|
||||
final converted = await ExchangeRateService().convertBetweenCurrencies(
|
||||
cost,
|
||||
subscription.currency,
|
||||
targetCurrency,
|
||||
);
|
||||
|
||||
monthTotal += converted ?? cost;
|
||||
} else {
|
||||
// 과거 월인 경우: 기존 로직 유지
|
||||
// 구독이 해당 월에 활성화되어 있었는지 확인
|
||||
final subscriptionStartDate = subscription.nextBillingDate.subtract(
|
||||
Duration(days: _getBillingCycleDays(subscription.billingCycle)),
|
||||
);
|
||||
|
||||
if (subscriptionStartDate.isBefore(DateTime(month.year, month.month + 1, 1)) &&
|
||||
subscription.nextBillingDate.isAfter(month)) {
|
||||
// 해당 월의 비용 계산 (이벤트 가격 고려)
|
||||
double cost;
|
||||
|
||||
if (subscription.isEventActive &&
|
||||
subscription.eventStartDate != null &&
|
||||
subscription.eventEndDate != null &&
|
||||
// 이벤트 기간과 해당 월이 겹치는지 확인
|
||||
subscription.eventStartDate!.isBefore(DateTime(month.year, month.month + 1, 1)) &&
|
||||
subscription.eventEndDate!.isAfter(month)) {
|
||||
cost = subscription.eventPrice ?? subscription.monthlyCost;
|
||||
} else {
|
||||
// 변환 실패 시 원래 값 사용
|
||||
monthTotal += cost;
|
||||
cost = subscription.monthlyCost;
|
||||
}
|
||||
|
||||
// 통화 변환
|
||||
final converted = await ExchangeRateService().convertBetweenCurrencies(
|
||||
cost,
|
||||
subscription.currency,
|
||||
targetCurrency,
|
||||
);
|
||||
|
||||
monthTotal += converted ?? 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가 없는 기존 구독들에 대해 자동으로 카테고리 할당
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user