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/
This commit is contained in:
2026-10-09 19:24:47 +03:00
parent 9175d5c76b
commit d58547e8f8
6 changed files with 136 additions and 19 deletions
+25 -8
View File
@@ -6,6 +6,7 @@ from rich.table import Table
from domain import (
Account,
Asset,
CurrencyConverter,
ExchangeRate,
Liability,
RecurringCashflow,
@@ -132,7 +133,8 @@ def account_list() -> None:
table.add_column(t("table.accounts_config.col.currency"))
table.add_column(t("table.accounts_config.col.balance"), justify="right")
for a in model.accounts:
table.add_row(str(a.id)[:8], a.name, a.currency, f"{a.balance:,.2f}")
balance_str = CurrencyConverter.format_money(a.balance, a.currency)
table.add_row(str(a.id)[:8], a.name, a.currency, balance_str)
console.print(table)
@@ -182,8 +184,9 @@ def transaction_add(
model.transactions.append(txn)
save_model(model)
kind = t("cmd.config.transaction_add.kind.income") if amount >= 0 else t("cmd.config.transaction_add.kind.expense")
amount_str = CurrencyConverter.format_money(abs(amount), _account_currency(model, account_id))
ok = t("global.ok")
msg = t("cmd.config.transaction_add.ok", kind=kind, amount=abs(amount), account=account)
msg = t("cmd.config.transaction_add.ok", kind=kind, amount_str=amount_str, account=account)
console.print(f"[green]{ok}[/green] {msg}")
@@ -203,9 +206,12 @@ def transaction_list() -> None:
table.add_column(t("table.transactions.col.description"))
for tx in model.transactions:
account_name = _account_name(model, tx.account)
amount_str = CurrencyConverter.format_money(
tx.amount, _account_currency(model, tx.account)
)
table.add_row(
str(tx.id)[:8], tx.date, account_name, tx.category,
f"{tx.amount:,.2f}", tx.description,
amount_str, tx.description,
)
console.print(table)
@@ -245,8 +251,9 @@ def recurring_add(
)
model.recurring.append(rc)
save_model(model)
amount_str = CurrencyConverter.format_money(abs(amount), model.base_currency)
ok = t("global.ok")
msg = t("cmd.config.recurring_add.ok", amount=abs(amount), frequency=frequency)
msg = t("cmd.config.recurring_add.ok", amount_str=amount_str, frequency=frequency)
console.print(f"[green]{ok}[/green] {msg}")
@@ -265,7 +272,8 @@ def recurring_list() -> None:
table.add_column(t("table.recurring.col.amount"), justify="right")
table.add_column(t("table.recurring.col.category"))
for r in model.recurring:
row = (str(r.id)[:8], r.start_date, r.end_date, r.frequency, f"{r.amount:,.2f}", r.category)
amount_str = CurrencyConverter.format_money(r.amount, model.base_currency)
row = (str(r.id)[:8], r.start_date, r.end_date, r.frequency, amount_str, r.category)
table.add_row(*row)
console.print(table)
@@ -316,7 +324,8 @@ def asset_list() -> None:
table.add_column(t("table.assets.col.value"), justify="right")
table.add_column(t("table.assets.col.growth"), justify="right")
for a in model.assets:
table.add_row(str(a.id)[:8], a.name, f"{a.value:,.2f}", f"{a.growth_rate:.1f}%")
value_str = CurrencyConverter.format_money(a.value, model.base_currency)
table.add_row(str(a.id)[:8], a.name, value_str, f"{a.growth_rate:.1f}%")
console.print(table)
@@ -371,8 +380,9 @@ def liability_list() -> None:
table.add_column(t("table.liabilities.col.rate"), justify="right")
table.add_column(t("table.liabilities.col.payment"), justify="right")
for li in model.liabilities:
b, i, p = f"{li.balance:,.2f}", f"{li.interest:.1f}%", f"{li.payment:,.2f}"
row = (str(li.id)[:8], li.name, b, i, p)
balance_str = CurrencyConverter.format_money(li.balance, model.base_currency)
payment_str = CurrencyConverter.format_money(li.payment, model.base_currency)
row = (str(li.id)[:8], li.name, balance_str, f"{li.interest:.1f}%", payment_str)
table.add_row(*row)
console.print(table)
@@ -411,3 +421,10 @@ def _account_name(model, account_id: str) -> str:
if str(a.id) == account_id:
return a.name
return account_id[:8]
def _account_currency(model, account_id: str) -> str:
for a in model.accounts:
if str(a.id) == account_id:
return a.currency
return model.base_currency