Finanzen: Vorschau, Steuer und Aufgaben genauer (Fixkosten ohne ESt, verspaetete Auszahlungen, Abos nicht ladbar)

M6: fixkosten30Cents zaehlt nur Fixkosten, die ESt-Vorauszahlung bleibt sicherer Posten.
M7: verspaetete Auszahlung (Ankunftstag vorbei, noch nicht bezahlt) kommt heute.
M8: ohne Stripe sind Kleinunternehmer-Zaehler und Anteil unbekannt (null), keine Warnung daraus.
M12: Verbindungsdokument ohne Sitzung zaehlt als keine Verbindung.
M13: Buchungen privat/fixkosten gehen nicht in die Fixkosten-Vorschlaege.
M14: Vorjahresumsatz ueber 25.000 EUR ist eine hohe Aufgabe.
M15: scheitert das Laden der Abos, meldet abos.fehler statt die ganze Uebersicht zu kippen.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-01 15:42:06 +00:00
1 parent 83ee9d5a25
commit 039fdcaf27
6 files changed
+295 -20

No files matched your search

+36 -6
View File
@@ -15,7 +15,8 @@ import { buildTodos } from './todos.js'
* Steuer, Abos und Altersgruppen der Rechnungen. Die Rechnen-Module (forecast, tax, todos, recurring) sind rein; hier
* werden nur die Daten geladen (Bank und Einstellungen aus Appwrite, Rechnungen und Auszahlungen von Stripe) und
* zusammengesetzt. Scheitert Stripe, rechnet die Übersicht mit leeren Rechnungs- und Auszahlungslisten weiter und
* meldet das in stand.stripe; Appwrite-Fehler gehen als Fehler an den Aufrufer.
* meldet das in stand.stripe; scheitert das Laden der Abos, meldet das abos.fehler. Andere Appwrite-Fehler gehen als Fehler
* an den Aufrufer.
*/
const SUBSCRIPTION_LIMIT = 500
@@ -75,6 +76,24 @@ async function loadStripe(now, waitMs) {
// --- Abos ---------------------------------------------------------------------------------------------------
// Abos aus Appwrite: scheitert das Laden, rechnet die Uebersicht ohne Abos weiter und die Seite zeigt "unbekannt"
const SUBSCRIPTIONS_FAILED = Object.freeze({
monatlichCents: null,
zahlendeKunden: null,
gefaehrdetCents: null,
liste: Object.freeze([]),
fehler: 'Abos konnten nicht geladen werden.',
})
async function loadSubscriptions() {
try {
return { ok: true, docs: await listDocuments(config.collections.subscriptions, [Query.limit(SUBSCRIPTION_LIMIT)]) }
} catch (err) {
logFailure('Abos laden', err)
return { ok: false, docs: [] }
}
}
/** Kundennamen (Firmenname vor Name) zu den Kunden-IDs; ein fehlender oder nicht lesbarer Kunde bekommt den Namen "". */
async function customerNames(ids) {
const names = new Map()
@@ -175,14 +194,17 @@ const isOpenIncoming = (tx) => tx.status === 'BOOK' && isCents(tx.amountCents) &
*/
export async function buildOverview({ now = new Date(), stripeWaitMs = STRIPE_WAIT_MS } = {}) {
const today = berlinDate(now)
const [connection, transactions, settings, fixedCosts, subscriptions, stripe] = await Promise.all([
const [storedConnection, transactions, settings, fixedCosts, loadedSubscriptions, stripe] = await Promise.all([
getConnection(),
listTransactions(),
getSettings(),
listFixedCosts(),
listDocuments(config.collections.subscriptions, [Query.limit(SUBSCRIPTION_LIMIT)]),
loadSubscriptions(),
loadStripe(now, stripeWaitMs),
])
// Ein Dokument ohne Sitzung (erste Freigabe begonnen, nie abgeschlossen) ist kein Bankzugang
const connection = trimmed(storedConnection?.sessionId) ? storedConnection : null
const subscriptions = loadedSubscriptions.docs
// Kundennamen nur für die Abos der Liste
const listed = subscriptions.filter((s) => LISTED_STATUSES.has(s.status))
@@ -202,7 +224,10 @@ export async function buildOverview({ now = new Date(), stripeWaitMs = STRIPE_WA
fixedCosts,
settings,
})
const tax = computeTax({ today, transactions, invoices: stripe.invoices, settings })
const computedTax = computeTax({ today, transactions, invoices: stripe.invoices, settings })
// Der Kleinunternehmer-Zaehler stuetzt sich auf die Zahltage der Stripe-Rechnungen: ohne Stripe waere er zu niedrig und
// wuerde Sicherheit vortaeuschen (die Seite zeigt "unbekannt"), eine Warnung daraus waere falsch
const tax = stripe.ok ? computedTax : { ...computedTax, kleinunternehmer: { ...computedTax.kleinunternehmer, jahrCents: null, anteilFolgejahr: null } }
// Zuordnungsvorschläge wie bei GET /buchungen, nur für gebuchte, offene Eingänge; ohne vollständige Stripe-Daten keine
const openIncoming = transactions.filter(isOpenIncoming)
@@ -214,8 +239,13 @@ export async function buildOverview({ now = new Date(), stripeWaitMs = STRIPE_WA
invoices: stripe.invoices,
transactions: todoTransactions,
subscriptions: named,
recurring: detectRecurring(transactions, { fixedCosts }),
// Was schon zugeordnet ist (privat, Fixkosten), ist kein Vorschlag mehr
recurring: detectRecurring(
transactions.filter((tx) => tx.category !== 'privat' && tx.category !== 'fixkosten'),
{ fixedCosts }
),
tax,
settings,
})
return {
@@ -236,7 +266,7 @@ export async function buildOverview({ now = new Date(), stripeWaitMs = STRIPE_WA
aufgaben,
vorschau: forecast,
steuer: tax,
abos: buildSubscriptions(named),
abos: loadedSubscriptions.ok ? buildSubscriptions(named) : SUBSCRIPTIONS_FAILED,
rechnungen: buildInvoiceSummary(stripe.invoices, today),
}
}