Files
nifodea/infrastructure/cli/services.py
T
oqyude d58547e8f8 T8: ISO 4217 per-currency decimals + format_money()
- Add CURRENCY_DECIMALS dict (JPY=0, BHD/KWD/OMR=3, rest=2) and DEFAULT_CURRENCY_DECIMALS=2
- Add CurrencyConverter.format_money() staticmethod with ROUND_HALF_UP; symbol-prefix for known, code-prefix for unknown
- Replace 14 :,.2f literals across services.py / info.py / config_cmd.py with format_money() calls
- Refactor 2 i18n templates (transaction_add.ok, recurring_add.ok): {amount:.2f} -> {amount_str}; callers pass pre-formatted string
- New _account_currency() helper for transaction currency lookup
- Extend test_currency.py: 8 new tests (JPY/BHD/USD/CHF/negative/zero/float-int/lowercase)
- 75/75 tests pass; 0 :,.2f literals remain in infrastructure/cli/
2026-10-09 19:24:47 +03:00

65 lines
2.2 KiB
Python

"""Composition root + общие хелперы для CLI-команд."""
from __future__ import annotations
from decimal import Decimal
from rich.console import Console
from application.forecast import ForecastService
from application.scenarios import ScenarioService
from domain import CurrencyConverter, FinancialModel
from infrastructure.ai.assistant import AssistantService
from infrastructure.cli.paths import MODEL_PATH
from infrastructure.repositories import JsonFileRepository
console = Console()
_json_repo = JsonFileRepository()
def load_model() -> FinancialModel:
if MODEL_PATH.exists():
return _json_repo.load(MODEL_PATH)
return FinancialModel()
def save_model(model: FinancialModel) -> None:
_json_repo.save(model, MODEL_PATH)
def build_services(model: FinancialModel) -> tuple[ForecastService, ScenarioService, AssistantService, CurrencyConverter]:
"""Composition root: собрать граф сервисов с зависимостями."""
converter = CurrencyConverter(model.exchange_rates)
forecast_service = ForecastService(model)
scenario_service = ScenarioService(model)
assistant_service = AssistantService(
model, forecast_service=forecast_service, converter=converter
)
return forecast_service, scenario_service, assistant_service, converter
def resolve_currency(model: FinancialModel, currency: str | None) -> tuple[str, str]:
target = currency or model.base_currency
converter = CurrencyConverter(model.exchange_rates)
symbol = converter.get_symbol(target)
return target, symbol
def convert_value(
converter: CurrencyConverter, amount: Decimal, from_curr: str, to_curr: str
) -> Decimal:
if to_curr == from_curr:
return amount
return converter.convert(amount, from_curr, to_curr)
def fmt_amount(amount: Decimal | float, currency: str, symbol: str = "") -> str:
"""Format money via the canonical display rule.
`symbol` is retained as a positional parameter for backward compatibility with
existing callers (compare.py / forecast.py / scenario.py / whatif.py); the
symbol/layout decision is centralised in `CurrencyConverter.format_money`.
"""
return CurrencyConverter.format_money(amount, currency)