Merge branch 'currency-by-country'
Currency-aware formatting in the planner: UK → £, US → $, EU → € with locale-native thousands separators.
This commit is contained in:
@@ -23,19 +23,24 @@ def _detect_lang() -> str:
|
||||
|
||||
|
||||
def _fmt_currency(n) -> str:
|
||||
"""Format as EUR with German-style thousands separator (€50.000)."""
|
||||
"""Format currency using request-context symbol and locale style."""
|
||||
sym = getattr(g, "currency_sym", "\u20ac")
|
||||
eu_style = getattr(g, "currency_eu_style", True)
|
||||
n = round(float(n))
|
||||
s = f"{abs(n):,}".replace(",", ".")
|
||||
return f"-\u20ac{s}" if n < 0 else f"\u20ac{s}"
|
||||
s = f"{abs(n):,}"
|
||||
if eu_style:
|
||||
s = s.replace(",", ".")
|
||||
return f"-{sym}{s}" if n < 0 else f"{sym}{s}"
|
||||
|
||||
|
||||
def _fmt_k(n) -> str:
|
||||
"""Short currency: €50K, €1.2M, or full fmt_currency."""
|
||||
"""Short currency: €50K, €1.2M, or full _fmt_currency."""
|
||||
sym = getattr(g, "currency_sym", "\u20ac")
|
||||
n = float(n)
|
||||
if abs(n) >= 1_000_000:
|
||||
return f"\u20ac{n/1_000_000:.1f}M"
|
||||
return f"{sym}{n/1_000_000:.1f}M"
|
||||
if abs(n) >= 1_000:
|
||||
return f"\u20ac{n/1_000:.0f}K"
|
||||
return f"{sym}{n/1_000:.0f}K"
|
||||
return _fmt_currency(n)
|
||||
|
||||
|
||||
@@ -50,8 +55,10 @@ def _fmt_x(n) -> str:
|
||||
|
||||
|
||||
def _fmt_n(n) -> str:
|
||||
"""Format integer with German thousands separator: 1.234."""
|
||||
return f"{round(float(n)):,}".replace(",", ".")
|
||||
"""Format integer with locale-aware thousands separator: 1.234 or 1,234."""
|
||||
eu_style = getattr(g, "currency_eu_style", True)
|
||||
s = f"{round(float(n)):,}"
|
||||
return s.replace(",", ".") if eu_style else s
|
||||
|
||||
|
||||
def _tformat(s: str, **kwargs) -> str:
|
||||
|
||||
@@ -9,16 +9,20 @@ from pathlib import Path
|
||||
|
||||
from .core import fetch_one
|
||||
from .i18n import get_translations
|
||||
from .planner.calculator import calc, validate_state
|
||||
from .planner.calculator import COUNTRY_CURRENCY, CURRENCY_DEFAULT, calc, validate_state
|
||||
|
||||
TEMPLATE_DIR = Path(__file__).parent / "templates" / "businessplan"
|
||||
|
||||
|
||||
def _fmt_eur(n) -> str:
|
||||
"""Format number as EUR with thousands separator."""
|
||||
def _fmt_cur(n, sym: str = "\u20ac", eu_style: bool = True) -> str:
|
||||
"""Format number as currency with locale-aware thousands separator."""
|
||||
if n is None:
|
||||
return "-"
|
||||
return f"\u20ac{n:,.0f}"
|
||||
v = round(float(n))
|
||||
s = f"{abs(v):,}"
|
||||
if eu_style:
|
||||
s = s.replace(",", ".")
|
||||
return f"-{sym}{s}" if v < 0 else f"{sym}{s}"
|
||||
|
||||
|
||||
def _fmt_pct(n) -> str:
|
||||
@@ -44,16 +48,20 @@ def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
|
||||
s = state
|
||||
t = get_translations(language)
|
||||
|
||||
cur = COUNTRY_CURRENCY.get(s.get("country", "DE"), CURRENCY_DEFAULT)
|
||||
sym, eu_style = cur["sym"], cur["eu_style"]
|
||||
fmt = lambda n: _fmt_cur(n, sym, eu_style) # noqa: E731
|
||||
|
||||
venue_type = t["bp_indoor"] if s["venue"] == "indoor" else t["bp_outdoor"]
|
||||
own_type = t["bp_own"] if s["own"] == "buy" else t["bp_rent"]
|
||||
|
||||
payback_str = _fmt_months(d["paybackIdx"], t)
|
||||
irr_str = _fmt_pct(d["irr"])
|
||||
total_capex_str = _fmt_eur(d["capex"])
|
||||
equity_str = _fmt_eur(d["equity"])
|
||||
loan_str = _fmt_eur(d["loanAmount"])
|
||||
per_court_str = _fmt_eur(d["capexPerCourt"])
|
||||
per_sqm_str = _fmt_eur(d["capexPerSqm"])
|
||||
total_capex_str = fmt(d["capex"])
|
||||
equity_str = fmt(d["equity"])
|
||||
loan_str = fmt(d["loanAmount"])
|
||||
per_court_str = fmt(d["capexPerCourt"])
|
||||
per_sqm_str = fmt(d["capexPerSqm"])
|
||||
|
||||
sections = {
|
||||
"lang": language,
|
||||
@@ -70,8 +78,8 @@ def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
|
||||
"total_capex": total_capex_str,
|
||||
"equity": equity_str,
|
||||
"loan": loan_str,
|
||||
"y1_revenue": _fmt_eur(d["annuals"][0]["revenue"]) if d["annuals"] else "-",
|
||||
"y3_ebitda": _fmt_eur(d["stabEbitda"]),
|
||||
"y1_revenue": fmt(d["annuals"][0]["revenue"]) if d["annuals"] else "-",
|
||||
"y3_ebitda": fmt(d["stabEbitda"]),
|
||||
"irr": irr_str,
|
||||
"payback": payback_str,
|
||||
},
|
||||
@@ -79,7 +87,7 @@ def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
|
||||
# Investment Plan (CAPEX)
|
||||
"investment": {
|
||||
"heading": t["bp_investment"],
|
||||
"items": d["capexItems"],
|
||||
"items": [{**i, "formatted_amount": fmt(i["amount"])} for i in d["capexItems"]],
|
||||
"total": total_capex_str,
|
||||
"per_court": per_court_str,
|
||||
"per_sqm": per_sqm_str,
|
||||
@@ -88,20 +96,20 @@ def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
|
||||
# Operating Costs
|
||||
"operations": {
|
||||
"heading": t["bp_operations"],
|
||||
"items": d["opexItems"],
|
||||
"monthly_total": _fmt_eur(d["opex"]),
|
||||
"annual_total": _fmt_eur(d["annualOpex"]),
|
||||
"items": [{**i, "formatted_amount": fmt(i["amount"])} for i in d["opexItems"]],
|
||||
"monthly_total": fmt(d["opex"]),
|
||||
"annual_total": fmt(d["annualOpex"]),
|
||||
},
|
||||
|
||||
# Revenue Model
|
||||
"revenue": {
|
||||
"heading": t["bp_revenue"],
|
||||
"weighted_rate": _fmt_eur(d["weightedRate"]),
|
||||
"weighted_rate": fmt(d["weightedRate"]),
|
||||
"utilization": _fmt_pct(s["utilTarget"] / 100),
|
||||
"gross_monthly": _fmt_eur(d["grossRevMonth"]),
|
||||
"net_monthly": _fmt_eur(d["netRevMonth"]),
|
||||
"ebitda_monthly": _fmt_eur(d["ebitdaMonth"]),
|
||||
"net_cf_monthly": _fmt_eur(d["netCFMonth"]),
|
||||
"gross_monthly": fmt(d["grossRevMonth"]),
|
||||
"net_monthly": fmt(d["netRevMonth"]),
|
||||
"ebitda_monthly": fmt(d["ebitdaMonth"]),
|
||||
"net_cf_monthly": fmt(d["netCFMonth"]),
|
||||
},
|
||||
|
||||
# 5-Year P&L
|
||||
@@ -110,10 +118,10 @@ def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
|
||||
"years": [
|
||||
{
|
||||
"year": a["year"],
|
||||
"revenue": _fmt_eur(a["revenue"]),
|
||||
"ebitda": _fmt_eur(a["ebitda"]),
|
||||
"debt_service": _fmt_eur(a["ds"]),
|
||||
"net_cf": _fmt_eur(a["ncf"]),
|
||||
"revenue": fmt(a["revenue"]),
|
||||
"ebitda": fmt(a["ebitda"]),
|
||||
"debt_service": fmt(a["ds"]),
|
||||
"net_cf": fmt(a["ncf"]),
|
||||
}
|
||||
for a in d["annuals"]
|
||||
],
|
||||
@@ -127,8 +135,8 @@ def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
|
||||
"loan": loan_str,
|
||||
"interest_rate": f"{s['interestRate']}%",
|
||||
"term": t["bp_years"].format(n=s["loanTerm"]),
|
||||
"monthly_payment": _fmt_eur(d["monthlyPayment"]),
|
||||
"annual_debt_service": _fmt_eur(d["annualDebtService"]),
|
||||
"monthly_payment": fmt(d["monthlyPayment"]),
|
||||
"annual_debt_service": fmt(d["annualDebtService"]),
|
||||
"ltv": _fmt_pct(d["ltv"]),
|
||||
},
|
||||
|
||||
@@ -151,12 +159,12 @@ def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
|
||||
"months": [
|
||||
{
|
||||
"month": m["m"],
|
||||
"revenue": _fmt_eur(m["totalRev"]),
|
||||
"opex": _fmt_eur(abs(m["opex"])),
|
||||
"ebitda": _fmt_eur(m["ebitda"]),
|
||||
"debt": _fmt_eur(abs(m["loan"])),
|
||||
"ncf": _fmt_eur(m["ncf"]),
|
||||
"cumulative": _fmt_eur(m["cum"]),
|
||||
"revenue": fmt(m["totalRev"]),
|
||||
"opex": fmt(abs(m["opex"])),
|
||||
"ebitda": fmt(m["ebitda"]),
|
||||
"debt": fmt(abs(m["loan"])),
|
||||
"ncf": fmt(m["ncf"]),
|
||||
"cumulative": fmt(m["cum"]),
|
||||
}
|
||||
for m in d["months"][:12]
|
||||
],
|
||||
@@ -220,6 +228,7 @@ def get_plan_sections(state: dict, d: dict, language: str = "en") -> dict:
|
||||
"debt": t["bp_lbl_debt"],
|
||||
"cumulative": t["bp_lbl_cumulative"],
|
||||
"disclaimer": t["bp_lbl_disclaimer"],
|
||||
"currency_sym": sym,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -652,9 +652,9 @@
|
||||
"sl_sqm_sgl_hall": "Hallen-m² pro Einzelplatz",
|
||||
"sl_sqm_dbl_outdoor": "Grundstück-m² pro Doppelplatz",
|
||||
"sl_sqm_sgl_outdoor": "Grundstück-m² pro Einzelplatz",
|
||||
"sl_rate_peak": "Spitzenstundensatz (€)",
|
||||
"sl_rate_offpeak": "Nebenstundensatz (€)",
|
||||
"sl_rate_single": "Einzelplatz-Stundensatz (€)",
|
||||
"sl_rate_peak": "Spitzenstundensatz ({currency})",
|
||||
"sl_rate_offpeak": "Nebenstundensatz ({currency})",
|
||||
"sl_rate_single": "Einzelplatz-Stundensatz ({currency})",
|
||||
"sl_peak_pct": "Anteil Spitzenstunden",
|
||||
"sl_booking_fee": "Plattformprovision",
|
||||
"sl_util_target": "Ziel-Auslastung",
|
||||
@@ -668,9 +668,9 @@
|
||||
"sl_retail_rev": "Einzelhandel / Platz",
|
||||
"sl_court_cost_dbl": "Platzkosten — Doppel",
|
||||
"sl_court_cost_sgl": "Platzkosten — Einzel",
|
||||
"sl_hall_cost_sqm": "Hallenbau (€/m²)",
|
||||
"sl_foundation_sqm": "Fundament (€/m²)",
|
||||
"sl_land_price_sqm": "Grundstückspreis (€/m²)",
|
||||
"sl_hall_cost_sqm": "Hallenbau ({currency}/m²)",
|
||||
"sl_foundation_sqm": "Fundament ({currency}/m²)",
|
||||
"sl_land_price_sqm": "Grundstückspreis ({currency}/m²)",
|
||||
"sl_hvac": "Lüftung & Klimaanlage",
|
||||
"sl_electrical": "Elektro + Beleuchtung",
|
||||
"sl_sanitary": "Sanitär / Umkleide",
|
||||
@@ -680,7 +680,7 @@
|
||||
"sl_hvac_upgrade": "Lüftungsausbau",
|
||||
"sl_lighting_upgrade": "Beleuchtungsausbau",
|
||||
"sl_fitout": "Ausbau & Empfang",
|
||||
"sl_outdoor_foundation": "Beton (€/m²)",
|
||||
"sl_outdoor_foundation": "Beton ({currency}/m²)",
|
||||
"sl_outdoor_site_work": "Erschließung",
|
||||
"sl_outdoor_lighting": "Beleuchtung pro Platz",
|
||||
"sl_outdoor_fencing": "Einzäunung",
|
||||
@@ -688,17 +688,17 @@
|
||||
"sl_working_capital": "Betriebskapital",
|
||||
"sl_contingency": "Reserve",
|
||||
"sl_budget_target": "Dein Budgetziel",
|
||||
"sl_rent_sqm": "Miete (€/m²/Monat)",
|
||||
"sl_rent_sqm": "Miete ({currency}/m²/Monat)",
|
||||
"sl_outdoor_rent": "Monatliche Grundstücksmiete",
|
||||
"sl_property_tax": "Grundsteuer / Monat",
|
||||
"sl_insurance": "Versicherung (€/Monat)",
|
||||
"sl_electricity": "Strom (€/Monat)",
|
||||
"sl_heating": "Heizung (€/Monat)",
|
||||
"sl_water": "Wasser (€/Monat)",
|
||||
"sl_maintenance": "Wartung (€/Monat)",
|
||||
"sl_cleaning": "Reinigung (€/Monat)",
|
||||
"sl_marketing": "Marketing / Sonstiges (€/Monat)",
|
||||
"sl_staff": "Personal (€/Monat)",
|
||||
"sl_insurance": "Versicherung ({currency}/Monat)",
|
||||
"sl_electricity": "Strom ({currency}/Monat)",
|
||||
"sl_heating": "Heizung ({currency}/Monat)",
|
||||
"sl_water": "Wasser ({currency}/Monat)",
|
||||
"sl_maintenance": "Wartung ({currency}/Monat)",
|
||||
"sl_cleaning": "Reinigung ({currency}/Monat)",
|
||||
"sl_marketing": "Marketing / Sonstiges ({currency}/Monat)",
|
||||
"sl_staff": "Personal ({currency}/Monat)",
|
||||
"sl_loan_pct": "Fremdkapitalquote (LTC)",
|
||||
"sl_interest_rate": "Zinssatz",
|
||||
"sl_loan_term": "Kreditlaufzeit",
|
||||
|
||||
@@ -652,9 +652,9 @@
|
||||
"sl_sqm_sgl_hall": "Hall m² per Single Court",
|
||||
"sl_sqm_dbl_outdoor": "Land m² per Double Court",
|
||||
"sl_sqm_sgl_outdoor": "Land m² per Single Court",
|
||||
"sl_rate_peak": "Peak Hour Rate (€)",
|
||||
"sl_rate_offpeak": "Off-Peak Hour Rate (€)",
|
||||
"sl_rate_single": "Single Court Rate (€)",
|
||||
"sl_rate_peak": "Peak Hour Rate ({currency})",
|
||||
"sl_rate_offpeak": "Off-Peak Hour Rate ({currency})",
|
||||
"sl_rate_single": "Single Court Rate ({currency})",
|
||||
"sl_peak_pct": "Peak Hours Share",
|
||||
"sl_booking_fee": "Platform Fee",
|
||||
"sl_util_target": "Target Utilization",
|
||||
@@ -668,9 +668,9 @@
|
||||
"sl_retail_rev": "Retail / Court",
|
||||
"sl_court_cost_dbl": "Court Cost — Double",
|
||||
"sl_court_cost_sgl": "Court Cost — Single",
|
||||
"sl_hall_cost_sqm": "Hall Construction (€/m²)",
|
||||
"sl_foundation_sqm": "Foundation (€/m²)",
|
||||
"sl_land_price_sqm": "Land Price (€/m²)",
|
||||
"sl_hall_cost_sqm": "Hall Construction ({currency}/m²)",
|
||||
"sl_foundation_sqm": "Foundation ({currency}/m²)",
|
||||
"sl_land_price_sqm": "Land Price ({currency}/m²)",
|
||||
"sl_hvac": "HVAC System",
|
||||
"sl_electrical": "Electrical + Lighting",
|
||||
"sl_sanitary": "Sanitary / Changing",
|
||||
@@ -680,7 +680,7 @@
|
||||
"sl_hvac_upgrade": "HVAC Upgrade",
|
||||
"sl_lighting_upgrade": "Lighting Upgrade",
|
||||
"sl_fitout": "Fit-Out & Reception",
|
||||
"sl_outdoor_foundation": "Concrete (€/m²)",
|
||||
"sl_outdoor_foundation": "Concrete ({currency}/m²)",
|
||||
"sl_outdoor_site_work": "Site Work",
|
||||
"sl_outdoor_lighting": "Lighting per Court",
|
||||
"sl_outdoor_fencing": "Fencing",
|
||||
@@ -688,17 +688,17 @@
|
||||
"sl_working_capital": "Working Capital",
|
||||
"sl_contingency": "Contingency",
|
||||
"sl_budget_target": "Your Budget Target",
|
||||
"sl_rent_sqm": "Rent (€/m²/month)",
|
||||
"sl_rent_sqm": "Rent ({currency}/m²/month)",
|
||||
"sl_outdoor_rent": "Monthly Land Rent",
|
||||
"sl_property_tax": "Property Tax / month",
|
||||
"sl_insurance": "Insurance (€/mo)",
|
||||
"sl_electricity": "Electricity (€/mo)",
|
||||
"sl_heating": "Heating (€/mo)",
|
||||
"sl_water": "Water (€/mo)",
|
||||
"sl_maintenance": "Maintenance (€/mo)",
|
||||
"sl_cleaning": "Cleaning (€/mo)",
|
||||
"sl_marketing": "Marketing / Misc (€/mo)",
|
||||
"sl_staff": "Staff (€/mo)",
|
||||
"sl_insurance": "Insurance ({currency}/mo)",
|
||||
"sl_electricity": "Electricity ({currency}/mo)",
|
||||
"sl_heating": "Heating ({currency}/mo)",
|
||||
"sl_water": "Water ({currency}/mo)",
|
||||
"sl_maintenance": "Maintenance ({currency}/mo)",
|
||||
"sl_cleaning": "Cleaning ({currency}/mo)",
|
||||
"sl_marketing": "Marketing / Misc ({currency}/mo)",
|
||||
"sl_staff": "Staff ({currency}/mo)",
|
||||
"sl_loan_pct": "Loan-to-Cost (LTC)",
|
||||
"sl_interest_rate": "Interest Rate",
|
||||
"sl_loan_term": "Loan Term",
|
||||
|
||||
@@ -95,6 +95,20 @@ DEFAULTS = {
|
||||
}
|
||||
|
||||
|
||||
CURRENCY_DEFAULT = {"sym": "\u20ac", "eu_style": True}
|
||||
|
||||
COUNTRY_CURRENCY: dict[str, dict] = {
|
||||
"DE": CURRENCY_DEFAULT,
|
||||
"ES": CURRENCY_DEFAULT,
|
||||
"IT": CURRENCY_DEFAULT,
|
||||
"FR": CURRENCY_DEFAULT,
|
||||
"NL": CURRENCY_DEFAULT,
|
||||
"SE": CURRENCY_DEFAULT,
|
||||
"UK": {"sym": "\u00a3", "eu_style": False},
|
||||
"US": {"sym": "$", "eu_style": False},
|
||||
}
|
||||
|
||||
|
||||
def validate_state(s: dict) -> dict:
|
||||
"""Apply defaults and coerce types. Returns a clean copy."""
|
||||
out = {**DEFAULTS}
|
||||
@@ -155,6 +169,7 @@ def calc(s: dict, lang: str = "en") -> dict:
|
||||
derived-data dict (the `d` object from the JS version).
|
||||
"""
|
||||
names = get_calc_item_names(lang)
|
||||
sym = COUNTRY_CURRENCY.get(s.get("country", "DE"), CURRENCY_DEFAULT)["sym"]
|
||||
d: dict = {}
|
||||
is_in = s["venue"] == "indoor"
|
||||
is_buy = s["own"] == "buy"
|
||||
@@ -198,12 +213,12 @@ def calc(s: dict, lang: str = "en") -> dict:
|
||||
if is_in:
|
||||
if is_buy:
|
||||
ci(names["hall_construction"], d["hallSqm"] * s["hallCostSqm"],
|
||||
f"{d['hallSqm']}m\u00b2 \u00d7 \u20ac{s['hallCostSqm']}/m\u00b2")
|
||||
f"{d['hallSqm']}m\u00b2 \u00d7 {sym}{s['hallCostSqm']}/m\u00b2")
|
||||
ci(names["foundation"], d["hallSqm"] * s["foundationSqm"],
|
||||
f"{d['hallSqm']}m\u00b2 \u00d7 \u20ac{s['foundationSqm']}/m\u00b2")
|
||||
f"{d['hallSqm']}m\u00b2 \u00d7 {sym}{s['foundationSqm']}/m\u00b2")
|
||||
land_sqm = _round(d["hallSqm"] * 1.25)
|
||||
ci(names["land_purchase"], land_sqm * s["landPriceSqm"],
|
||||
f"{land_sqm}m\u00b2 \u00d7 \u20ac{s['landPriceSqm']}/m\u00b2")
|
||||
f"{land_sqm}m\u00b2 \u00d7 {sym}{s['landPriceSqm']}/m\u00b2")
|
||||
ci(names["transaction_costs"], _round(land_sqm * s["landPriceSqm"] * 0.1), "~10% of land")
|
||||
ci(names["hvac_system"], s["hvac"])
|
||||
ci(names["electrical_lighting"], s["electrical"] * light_mult)
|
||||
@@ -225,7 +240,7 @@ def calc(s: dict, lang: str = "en") -> dict:
|
||||
ci(names["permits_compliance"], s["permitsCompliance"])
|
||||
if is_buy:
|
||||
ci(names["land_purchase"], d["outdoorLandSqm"] * s["landPriceSqm"],
|
||||
f"{d['outdoorLandSqm']}m\u00b2 \u00d7 \u20ac{s['landPriceSqm']}/m\u00b2")
|
||||
f"{d['outdoorLandSqm']}m\u00b2 \u00d7 {sym}{s['landPriceSqm']}/m\u00b2")
|
||||
ci(names["transaction_costs"], _round(d["outdoorLandSqm"] * s["landPriceSqm"] * 0.1))
|
||||
|
||||
ci(names["equipment"], s["equipment"] + total_courts * 300)
|
||||
@@ -253,7 +268,7 @@ def calc(s: dict, lang: str = "en") -> dict:
|
||||
if is_in:
|
||||
rent_amount = _round(d["hallSqm"] * s["rentSqm"])
|
||||
oi(names["rent"], d["hallSqm"] * s["rentSqm"],
|
||||
f"{d['hallSqm']}m\u00b2 \u00d7 \u20ac{s['rentSqm']}/m\u00b2")
|
||||
f"{d['hallSqm']}m\u00b2 \u00d7 {sym}{s['rentSqm']}/m\u00b2")
|
||||
else:
|
||||
rent_amount = s["outdoorRent"]
|
||||
oi(names["rent"], s["outdoorRent"])
|
||||
|
||||
@@ -19,7 +19,7 @@ from ..core import (
|
||||
waitlist_gate,
|
||||
)
|
||||
from ..i18n import get_translations
|
||||
from .calculator import DEFAULTS, calc, validate_state
|
||||
from .calculator import COUNTRY_CURRENCY, CURRENCY_DEFAULT, DEFAULTS, calc, validate_state
|
||||
|
||||
bp = Blueprint(
|
||||
"planner",
|
||||
@@ -328,6 +328,9 @@ async def index():
|
||||
lang = g.get("lang", "en")
|
||||
d = calc(s, lang=lang)
|
||||
augment_d(d, s, lang)
|
||||
cur = COUNTRY_CURRENCY.get(s["country"], CURRENCY_DEFAULT)
|
||||
g.currency_sym = cur["sym"]
|
||||
g.currency_eu_style = cur["eu_style"]
|
||||
return await render_template(
|
||||
"planner.html",
|
||||
s=s,
|
||||
@@ -337,6 +340,7 @@ async def index():
|
||||
active_tab="capex",
|
||||
country_presets=COUNTRY_PRESETS,
|
||||
defaults=DEFAULTS,
|
||||
currency_sym=cur["sym"],
|
||||
)
|
||||
|
||||
|
||||
@@ -351,12 +355,16 @@ async def calculate():
|
||||
active_tab = form.get("activeTab", "capex")
|
||||
if active_tab not in {"capex", "operating", "cashflow", "returns", "metrics"}:
|
||||
active_tab = "capex"
|
||||
cur = COUNTRY_CURRENCY.get(s["country"], CURRENCY_DEFAULT)
|
||||
g.currency_sym = cur["sym"]
|
||||
g.currency_eu_style = cur["eu_style"]
|
||||
return await render_template(
|
||||
"partials/calculate_response.html",
|
||||
s=s,
|
||||
d=d,
|
||||
active_tab=active_tab,
|
||||
lang=lang,
|
||||
currency_sym=cur["sym"],
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
{% macro slider(name, label, min, max, step, value, tip='') %}
|
||||
<div class="slider-group">
|
||||
<label>
|
||||
<span class="slider-group__label">{{ label }}</span>{% if tip %}<span class="ti">i<span class="tp">{{ tip }}</span></span>{% endif %}
|
||||
<span class="slider-group__label">{{ label | tformat(currency=currency_sym) }}</span>{% if tip %}<span class="ti">i<span class="tp">{{ tip }}</span></span>{% endif %}
|
||||
</label>
|
||||
<div class="slider-combo">
|
||||
<input type="range" name="{{ name }}" min="{{ min }}" max="{{ max }}" step="{{ step }}" value="{{ value }}"
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
{% for item in s.investment.items %}
|
||||
<tr>
|
||||
<td>{{ item.name }}</td>
|
||||
<td style="text-align:right">€{{ "{:,.0f}".format(item.amount) }}</td>
|
||||
<td style="text-align:right">{{ item.formatted_amount }}</td>
|
||||
<td style="color:#94A3B8;font-size:8pt">{{ item.info }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -97,7 +97,7 @@
|
||||
{% for item in s.operations.items %}
|
||||
<tr>
|
||||
<td>{{ item.name }}</td>
|
||||
<td style="text-align:right">€{{ "{:,.0f}".format(item.amount) }}</td>
|
||||
<td style="text-align:right">{{ item.formatted_amount }}</td>
|
||||
<td style="color:#94A3B8;font-size:8pt">{{ item.info }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
Reference in New Issue
Block a user