Files
certain/web/src/format.js
T
GuidoandClaude Opus 4.8 8ed4bf5254 feat: Phase 2 – Budget-Engine + Direktzugriff (http://IP:Port)
Budget:
- Modelle + Migration 0003: budget_template_lines, planned_income_lines,
  budget_periods, budget_period_lines.
- services/budget: Perioden-Grenzen (einstellbarer Starttag, Label nach Endmonat),
  Standard-Vorlage vs. Monats-Override (Snapshot-on-Customize), Soll/Ist inkl.
  budgetierter Sparen-Transfers, geplante Einnahmen (Soll/Ist).
- routers/budgets: Vorlage, geplante Einnahmen (CRUD), Monats-Soll/Ist,
  Monats-Override, Zurücksetzen. pytest: Perioden, Soll/Ist, Override/Reset, Einnahmen.
- Frontend: Budget-Seite (Monat + Standard-Budget), Fortschrittsbalken grün/rot,
  Monats-Navigation, editierbare Soll-Beträge; Sparen-Option im Transfer-Formular.

Deployment-Fix (Nutzer-Anforderung: http://IP:Port muss funktionieren):
- Dienst bindet an APP_BIND_HOST=0.0.0.0 (direkt im LAN erreichbar).
- APP_COOKIE_SECURE=false als Standard (sonst kein Login über reines HTTP);
  für reinen HTTPS-Betrieb umstellbar. Doku + install.sh entsprechend angepasst.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-19 19:16:27 +02:00

68 lines
2.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Formatierung von Beträgen, Zahlen und Daten – währungs-/gebietsspezifisch.
// EUR im deutschen Format (1.234,56 €), CHF im Schweizer Format (CHF 1'234.56).
// Datum als TT.MM.JJJJ. Beträge liegen intern als Ganzzahl in Cent/Rappen vor.
export function moneyLocale(currency) {
return currency === "CHF" ? "de-CH" : "de-DE";
}
/** Formatiert einen Cent-Betrag als Währungstext (z. B. 123456 -> "1.234,56 €"). */
export function formatMoney(cents, currency = "EUR") {
const value = (Number(cents) || 0) / 100;
return new Intl.NumberFormat(moneyLocale(currency), {
style: "currency",
currency,
}).format(value);
}
/** Formatiert einen Cent-Betrag ohne Währungssymbol (für Eingaben/Tabellen). */
export function formatAmount(cents, currency = "EUR") {
const value = (Number(cents) || 0) / 100;
return new Intl.NumberFormat(moneyLocale(currency), {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(value);
}
/** ISO-Datum (YYYY-MM-DD oder ISO-Zeitstempel) als TT.MM.JJJJ. */
export function formatDate(iso, locale = "de") {
if (!iso) return "";
const d = new Date(String(iso).length <= 10 ? `${iso}T00:00:00` : iso);
if (Number.isNaN(d.getTime())) return String(iso);
return new Intl.DateTimeFormat(locale === "en" ? "en-GB" : "de-DE").format(d);
}
/** Eingabetext ("12,34" oder "12.34") in Ganzzahl-Cent; null bei leer/ungültig. */
export function parseAmountToCents(str) {
if (str == null) return null;
const cleaned = String(str).trim().replace(/\s/g, "").replace(",", ".");
if (cleaned === "") return null;
const value = Number.parseFloat(cleaned);
if (Number.isNaN(value)) return null;
return Math.round(value * 100);
}
/** Cent-Betrag als bearbeitbarer Eingabetext ("1234" -> "12,34"). */
export function centsToInput(cents) {
if (cents == null) return "";
return (cents / 100).toFixed(2).replace(".", ",");
}
/** Heutiges Datum als YYYY-MM-DD (lokal). */
export function todayIso() {
const d = new Date();
const z = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${z(d.getMonth() + 1)}-${z(d.getDate())}`;
}
/** Monats-Label "2026-09" -> "September 2026". */
export function formatMonthLabel(label, locale = "de") {
if (!label) return "";
const [y, m] = label.split("-").map(Number);
const d = new Date(y, (m || 1) - 1, 1);
return new Intl.DateTimeFormat(locale === "en" ? "en-GB" : "de-DE", {
month: "long",
year: "numeric",
}).format(d);
}