Finanzen: Offen zeigt auch Ueberfaellige, Rechnungsliste laedt nach Zuordnen neu

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-01 15:12:18 +00:00
1 parent 1feeb18c08
commit 24a3cc41ef
6 files changed
+149 -29

No files matched your search

+54 -9
View File
@@ -406,16 +406,53 @@ export function aboView(abos) {
// --- Rechnungen (Block 6) ----------------------------------------------------------------------------------
/** Filter über der Rechnungsliste (Schlüssel = `status` der API); Standard ist „Offen“. */
/**
* Filter über der Rechnungsliste. `statuses` sind die API-Status, die der Filter abruft: „Offen“ zeigt offene UND
* überfällige Rechnungen (zwei Abrufe, zusammengeführt), alle anderen genau ihren Status. Standard ist „Offen“.
*/
export const INVOICE_FILTERS = [
{ key: 'offen', label: 'Offen' },
{ key: 'ueberfaellig', label: 'Überfällig' },
{ key: 'bezahlt', label: 'Bezahlt' },
{ key: 'entwurf', label: 'Entwürfe' },
{ key: 'alle', label: 'Alle' },
{ key: 'offen', label: 'Offen', statuses: ['offen', 'ueberfaellig'] },
{ key: 'ueberfaellig', label: 'Überfällig', statuses: ['ueberfaellig'] },
{ key: 'bezahlt', label: 'Bezahlt', statuses: ['bezahlt'] },
{ key: 'entwurf', label: 'Entwürfe', statuses: ['entwurf'] },
{ key: 'alle', label: 'Alle', statuses: ['alle'] },
]
export const DEFAULT_INVOICE_FILTER = 'offen'
/** API-Status, die ein Filter abruft (unbekannter Filter = alle). */
export function invoiceFilterStatuses(filterKey) {
const filter = INVOICE_FILTERS.find((f) => f.key === filterKey)
return filter ? [...filter.statuses] : ['alle']
}
/**
* Mehrere Antwortlisten (je Status eine) zu einer Liste: neueste zuerst nach Rechnungsdatum, gleiche Tage behalten
* die Reihenfolge der Listen; eine Rechnung, die in mehreren vorkommt, nur einmal.
*/
export function mergeInvoiceLists(lists) {
const seen = new Set()
const all = []
for (const list of Array.isArray(lists) ? lists : []) {
for (const inv of Array.isArray(list) ? list : []) {
if (!inv || typeof inv !== 'object') continue
if (inv.id) {
if (seen.has(inv.id)) continue
seen.add(inv.id)
}
all.push(inv)
}
}
return all
.map((inv, index) => ({ inv, index }))
.sort((a, b) => {
const da = text(a.inv.createdDate)
const db = text(b.inv.createdDate)
if (da === db) return a.index - b.index
return da < db ? 1 : -1
})
.map((x) => x.inv)
}
const obj = (v) => (v && typeof v === 'object' ? v : {})
const objects = (list) => (Array.isArray(list) ? list.filter((x) => x && typeof x === 'object') : [])
const OPEN_INVOICE = new Set(['offen', 'ueberfaellig'])
@@ -790,6 +827,7 @@ export function transactionRows(buchungen) {
// --- Zuordnen (Dialog) -------------------------------------------------------------------------------------
const NOT_OPEN_ANYMORE = 'Diese Rechnung ist nicht mehr offen.'
export const PENDING_ASSIGN_HINT = 'Vorgemerkte Buchungen können erst nach der Buchung einer Rechnung zugeordnet werden.'
/** Bestätigungstext vor dem Bezahlt-Markieren (nicht umkehrbar). */
@@ -809,8 +847,10 @@ const fixedCostLabel = (f) => `${text(f.name) || 'Fixkosten'} · ${euro(f.amount
* - Eingang: Vorschläge des Servers, andere offene Rechnungen, Kategorien (auch „Einnahme ohne Stripe-Rechnung“)
* - Ausgang: Fixkosten (aktive), Privat/Umbuchung, Sonstiges
* Rechnungen, deren offener Betrag vom Betrag der Buchung abweicht, sind nicht wählbar (Teilzahlungen in Stripe).
* invoicesLoaded = die offenen Rechnungen wurden frisch und ohne Fehler geladen: ein Vorschlag, dessen Rechnung nicht
* (mehr) darin steht, ist dann nicht wählbar („Diese Rechnung ist nicht mehr offen.“).
*/
export function assignModel(tx, { invoices = [], fixedCosts = [] } = {}) {
export function assignModel(tx, { invoices = [], fixedCosts = [], invoicesLoaded = false } = {}) {
const t = obj(tx)
const amount = t.amountCents
const incoming = isNum(amount) && amount > 0
@@ -868,6 +908,7 @@ export function assignModel(tx, { invoices = [], fixedCosts = [] } = {}) {
} else if (s.kind === 'invoice' && text(s.id) && !seen.has(s.id)) {
seen.add(s.id)
const known = open.find((inv) => inv.id === s.id)
const gone = invoicesLoaded && !known
const differs = s.amountDiffers === true || (known ? known.remainingCents !== amount : false)
const number = text(s.number) || text(known?.number)
model.suggestions.push({
@@ -876,8 +917,12 @@ export function assignModel(tx, { invoices = [], fixedCosts = [] } = {}) {
label: `Rechnung ${number || s.id}${known ? ` · ${text(known.customerName) || text(known.customerEmail)}` : ''}`,
detail: [text(s.reason), known ? `${euro(known.remainingCents)} offen` : ''].filter(Boolean).join(' · '),
confidence: s.confidence,
disabled: differs,
disabledReason: differs ? 'Betrag weicht ab, Teilzahlungen bitte direkt in Stripe erfassen' : '',
disabled: gone || differs,
disabledReason: gone
? NOT_OPEN_ANYMORE
: differs
? 'Betrag weicht ab, Teilzahlungen bitte direkt in Stripe erfassen'
: '',
number,
body: { invoiceId: s.id },
})