Finanzen: Steuerkarte mit Orientierungshinweis und neuer Anteilsformulierung, Ladetext auf Deutsch

TaxCard zeigt am Ende immer "Orientierungswert, ersetzt keine Steuerberatung."; der
Anteil heisst "<x> % der Grenze von 25.000 €" statt "<x> % der 25.000-€-Grenze".
AdminRoute zeigt "Wird geladen …" statt "Loading...".

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-01 15:21:53 +00:00
1 parent 0b1101d384
commit e9cfae28ee
5 files changed
+36 -6

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ export default function AdminRoute({ children }) {
return ( return (
<div className="text-center p-4" style={{ marginTop: 80 }}> <div className="text-center p-4" style={{ marginTop: 80 }}>
<div className="spinner"></div> <div className="spinner"></div>
<p>Loading...</p> <p>Wird geladen …</p>
</div> </div>
) )
} }
+3
View File
@@ -5,6 +5,7 @@ import { taxView } from '../../lib/financeView'
* Block 4: „Steuer“. Rücklage (Betrag und woraus sie sich ergibt), Kleinunternehmer-Zähler gegen die Grenzen, * Block 4: „Steuer“. Rücklage (Betrag und woraus sie sich ergibt), Kleinunternehmer-Zähler gegen die Grenzen,
* Vorjahr (nur wenn eingetragen), Gewerbesteuer-Hinweis (nur wenn nötig) und die nächste ESt-Vorauszahlung * Vorjahr (nur wenn eingetragen), Gewerbesteuer-Hinweis (nur wenn nötig) und die nächste ESt-Vorauszahlung
* (nur wenn eingestellt). Ist der Zähler unbekannt (Stripe nicht erreichbar), steht das als Text da, nie eine 0. * (nur wenn eingestellt). Ist der Zähler unbekannt (Stripe nicht erreichbar), steht das als Text da, nie eine 0.
* Am Ende steht immer der Hinweis „Orientierungswert, ersetzt keine Steuerberatung.“
*/ */
export default function TaxCard({ steuer }) { export default function TaxCard({ steuer }) {
const v = taxView(steuer) const v = taxView(steuer)
@@ -44,6 +45,8 @@ export default function TaxCard({ steuer }) {
<div className="fin-fact-sub">am {v.prepayment.dateText}</div> <div className="fin-fact-sub">am {v.prepayment.dateText}</div>
</div> </div>
)} )}
<p className="fin-hint fin-tax-note">{v.noteText}</p>
</div> </div>
</Card> </Card>
) )
+6 -2
View File
@@ -292,10 +292,13 @@ const KLEIN_LIMIT_CENTS = 2_500_000
const KLEIN_HARD_LIMIT_CENTS = 10_000_000 const KLEIN_HARD_LIMIT_CENTS = 10_000_000
const GEWERBE_ALLOWANCE_CENTS = 2_450_000 const GEWERBE_ALLOWANCE_CENTS = 2_450_000
/** Dauerhafter Hinweis unter den Steuerwerten: die Zahlen sind Näherungen. */
export const TAX_NOTE = 'Orientierungswert, ersetzt keine Steuerberatung.'
/** /**
* Texte der Steuerkarte aus `steuer`: * Texte der Steuerkarte aus `steuer`:
* { reserve: { amountText, basisText }, small: { known, counterText, shareText, limitText, hardText, previousText }, * { reserve: { amountText, basisText }, small: { known, counterText, shareText, limitText, hardText, previousText },
* gewerbeText, prepayment: { dateText, amountText } | null }. * gewerbeText, prepayment: { dateText, amountText } | null, noteText }.
* Ist der Kleinunternehmer-Zähler null (Stripe nicht erreichbar), steht „unbekannt“ statt einer Zahl. * Ist der Kleinunternehmer-Zähler null (Stripe nicht erreichbar), steht „unbekannt“ statt einer Zahl.
*/ */
export function taxView(steuer) { export function taxView(steuer) {
@@ -331,7 +334,7 @@ export function taxView(steuer) {
counterText: known ? `${euro(k.jahrCents)} von ${euroWhole(limit)}${year !== null ? ` (${year})` : ''}` : 'unbekannt (Stripe nicht erreichbar)', counterText: known ? `${euro(k.jahrCents)} von ${euroWhole(limit)}${year !== null ? ` (${year})` : ''}` : 'unbekannt (Stripe nicht erreichbar)',
shareText: shareText:
known && isNum(k.anteilFolgejahr) known && isNum(k.anteilFolgejahr)
? `${percentDe(k.anteilFolgejahr * 100, 1)} % der ${euroWhole(limit).replace(' €', '-€')}-Grenze` ? `${percentDe(k.anteilFolgejahr * 100, 1)} % der Grenze von ${euroWhole(limit)}`
: null, : null,
limitText: `Über ${euroWhole(limit)} im Jahr${year !== null ? ` ${year}` : ''}: ab ${ limitText: `Über ${euroWhole(limit)} im Jahr${year !== null ? ` ${year}` : ''}: ab ${
year !== null ? year + 1 : 'dem Folgejahr' year !== null ? year + 1 : 'dem Folgejahr'
@@ -344,6 +347,7 @@ export function taxView(steuer) {
? `Der Gewinn${year !== null ? ` ${year}` : ''} liegt über dem Gewerbesteuer-Freibetrag von ${euroWhole(GEWERBE_ALLOWANCE_CENTS)} (natürliche Personen und Personengesellschaften, § 11 GewStG). Auf den übersteigenden Gewerbeertrag kann Gewerbesteuer anfallen.` ? `Der Gewinn${year !== null ? ` ${year}` : ''} liegt über dem Gewerbesteuer-Freibetrag von ${euroWhole(GEWERBE_ALLOWANCE_CENTS)} (natürliche Personen und Personengesellschaften, § 11 GewStG). Auf den übersteigenden Gewerbeertrag kann Gewerbesteuer anfallen.`
: null, : null,
prepayment, prepayment,
noteText: TAX_NOTE,
} }
} }
+14
View File
@@ -0,0 +1,14 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')
const source = readFileSync(path.join(root, 'src/components/AdminRoute.jsx'), 'utf8')
// AdminRoute braucht React und den Auth-Kontext (nicht unter Node ladbar): der Ladetext wird am Quelltext geprüft
test('AdminRoute: Ladetext auf Deutsch, kein englisches „Loading...“', () => {
assert.match(source, /<p>Wird geladen …<\/p>/)
assert.doesNotMatch(source, /Loading/)
})
+12 -3
View File
@@ -20,6 +20,7 @@ const {
forecastRows, forecastRows,
forecastNotes, forecastNotes,
taxView, taxView,
TAX_NOTE,
aboView, aboView,
productLabel, productLabel,
aboStatus, aboStatus,
@@ -453,17 +454,25 @@ test('taxView: Kleinunternehmer-Zähler „<Betrag> von 25.000 € (2026)“ und
const v = taxView(steuer()) const v = taxView(steuer())
assert.equal(v.small.known, true) assert.equal(v.small.known, true)
assert.equal(v.small.counterText, '80,00 € von 25.000 € (2026)') assert.equal(v.small.counterText, '80,00 € von 25.000 € (2026)')
assert.equal(v.small.shareText, '0,3 % der 25.000-€-Grenze') assert.equal(v.small.shareText, '0,3 % der Grenze von 25.000 €')
assert.equal(v.small.limitText, 'Über 25.000 € im Jahr 2026: ab 2027 keine Kleinunternehmerregelung.') assert.equal(v.small.limitText, 'Über 25.000 € im Jahr 2026: ab 2027 keine Kleinunternehmerregelung.')
assert.equal( assert.equal(
v.small.hardText, v.small.hardText,
'Grenze 100.000 €: Ab dem Umsatz, der diese Grenze übersteigt, gilt schon im laufenden Jahr die Regelbesteuerung (Umsatzsteuer).' 'Grenze 100.000 €: Ab dem Umsatz, der diese Grenze übersteigt, gilt schon im laufenden Jahr die Regelbesteuerung (Umsatzsteuer).'
) )
assert.equal(v.small.previousText, null) assert.equal(v.small.previousText, null)
assert.equal(taxView(steuer({}, { anteilFolgejahr: 0.704 })).small.shareText, '70,4 % der 25.000-€-Grenze') assert.equal(taxView(steuer({}, { anteilFolgejahr: 0.704 })).small.shareText, '70,4 % der Grenze von 25.000 €')
assert.equal(taxView(steuer({}, { jahrCents: 0, anteilFolgejahr: 0 })).small.counterText, '0,00 € von 25.000 € (2026)') assert.equal(taxView(steuer({}, { jahrCents: 0, anteilFolgejahr: 0 })).small.counterText, '0,00 € von 25.000 € (2026)')
}) })
test('taxView: Orientierungshinweis steht immer dabei, Anteil ohne „-€-Grenze“', () => {
assert.equal(TAX_NOTE, 'Orientierungswert, ersetzt keine Steuerberatung.')
assert.equal(taxView(steuer()).noteText, TAX_NOTE)
assert.equal(taxView(null).noteText, TAX_NOTE)
assert.equal(taxView(steuer({}, { jahrCents: null, anteilFolgejahr: null })).noteText, TAX_NOTE)
for (const t of [taxView(steuer()), taxView(steuer({}, { anteilFolgejahr: 1.2 }))]) assert.doesNotMatch(t.small.shareText, /-€-Grenze/)
})
test('taxView: Stripe nicht erreichbar -> unbekannt statt 0', () => { test('taxView: Stripe nicht erreichbar -> unbekannt statt 0', () => {
const v = taxView(steuer({}, { jahrCents: null, anteilFolgejahr: null })) const v = taxView(steuer({}, { jahrCents: null, anteilFolgejahr: null }))
assert.equal(v.small.known, false) assert.equal(v.small.known, false)
@@ -492,7 +501,7 @@ test('taxView: Vorjahr nur wenn gesetzt, über der Grenze mit Hinweis', () => {
test('taxView: Grenzen kommen aus dem Server (Rückfall nur ohne Wert), Jahr fehlt -> Folgejahr', () => { test('taxView: Grenzen kommen aus dem Server (Rückfall nur ohne Wert), Jahr fehlt -> Folgejahr', () => {
const custom = taxView(steuer({ jahr: 2030 }, { grenzeFolgejahrCents: 3000000, grenzeHartCents: 12000000, anteilFolgejahr: 0.5, vorjahrCents: 3000001 })) const custom = taxView(steuer({ jahr: 2030 }, { grenzeFolgejahrCents: 3000000, grenzeHartCents: 12000000, anteilFolgejahr: 0.5, vorjahrCents: 3000001 }))
assert.equal(custom.small.counterText, '80,00 € von 30.000 € (2030)') assert.equal(custom.small.counterText, '80,00 € von 30.000 € (2030)')
assert.equal(custom.small.shareText, '50 % der 30.000-€-Grenze') assert.equal(custom.small.shareText, '50 % der Grenze von 30.000 €')
assert.equal(custom.small.limitText, 'Über 30.000 € im Jahr 2030: ab 2031 keine Kleinunternehmerregelung.') assert.equal(custom.small.limitText, 'Über 30.000 € im Jahr 2030: ab 2031 keine Kleinunternehmerregelung.')
assert.match(custom.small.hardText, /^Grenze 120\.000 €: Ab dem Umsatz, der diese Grenze übersteigt, gilt schon im laufenden Jahr die Regelbesteuerung \(Umsatzsteuer\)\.$/) assert.match(custom.small.hardText, /^Grenze 120\.000 €: Ab dem Umsatz, der diese Grenze übersteigt, gilt schon im laufenden Jahr die Regelbesteuerung \(Umsatzsteuer\)\.$/)
assert.equal(custom.small.previousText, 'Vorjahr: 30.000,01 € (über 30.000 €: im laufenden Jahr keine Kleinunternehmerregelung)') assert.equal(custom.small.previousText, 'Vorjahr: 30.000,01 € (über 30.000 €: im laufenden Jahr keine Kleinunternehmerregelung)')