From 91bc91383be0b2f39a6a87197a172eeefb27c014 Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Thu, 17 Jul 2025 00:07:24 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=B6=84=EC=84=9D=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=B4=9D=EC=A7=80=EC=B6=9C=20=EA=B8=88=EC=95=A1=20=EB=B6=88?= =?UTF-8?q?=EC=9D=BC=EC=B9=98=20=EB=B0=8F=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 월별지출현황과 총지출요약 카드의 7월 금액 불일치 수정 - 현재 월은 currentPrice 사용, 과거 월은 이벤트 기간 체크 - 월별 차트 라벨 다국어 지원 추가 (한/영/일/중) - 홈/분석 화면 총지출 금액 통일을 위한 통화 변환 로직 모듈화 - ExchangeRateService에 convertBetweenCurrencies 메서드 추가 - CurrencyUtil 및 SubscriptionProvider 리팩토링 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- lib/providers/subscription_provider.dart | 146 ++++++++++++----------- lib/services/currency_util.dart | 44 +++---- lib/services/exchange_rate_service.dart | 28 +++++ 3 files changed, 121 insertions(+), 97 deletions(-) diff --git a/lib/providers/subscription_provider.dart b/lib/providers/subscription_provider.dart index d712f19..339d32f 100644 --- a/lib/providers/subscription_provider.dart +++ b/lib/providers/subscription_provider.dart @@ -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가 없는 기존 구독들에 대해 자동으로 카테고리 할당 diff --git a/lib/services/currency_util.dart b/lib/services/currency_util.dart index f761504..1a38f52 100644 --- a/lib/services/currency_util.dart +++ b/lib/services/currency_util.dart @@ -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; } } diff --git a/lib/services/exchange_rate_service.dart b/lib/services/exchange_rate_service.dart index e0c6073..574da99 100644 --- a/lib/services/exchange_rate_service.dart +++ b/lib/services/exchange_rate_service.dart @@ -116,6 +116,34 @@ class ExchangeRateService { } } + /// 두 통화 간 변환을 수행합니다. (USD를 거쳐서 변환) + Future 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 getFormattedExchangeRateInfo() async { final rate = await getUsdToKrwRate();