// Ansichtslogik des Reiters „Aufträge und Rechnungen“ (rein, ohne React und ohne Appwrite; unter Node testbar). // Die API-Funktionen kommen als Argument. Der Container (AuftraegeUndRechnungen.jsx) verdrahtet diese Funktionen mit // useFinanceResource und den Abschnitten des Reiters. import { invoiceDoneMessage } from './financeView' export const KONTEN_FEHLER = 'Die Projektübersicht konnte nicht geladen werden.' export const AUFTRAEGE_FEHLER = 'Die Aufträge konnten nicht geladen werden.' const istObjekt = (wert) => wert !== null && typeof wert === 'object' && !Array.isArray(wert) const ohneProjektLeer = () => ({ berechnetCents: 0, bezahltCents: 0, anzahl: 0 }) /** * Lädt Projektkonten und, nur bei eingeschaltetem Schalter (`auftraegeAktiv === true`), die Aufträge des Kunden. * Scheitert nur listAuftraege, bleiben die Projektkonten und der Fehler steht eigens da. * Scheitert getProjektkonten, wird die Auftragsliste trotzdem geholt (der Server prüft den Schalter ohnehin und antwortet bei * „aus“ mit 404): kommt sie, bleibt der Abschnitt Aufträge bedienbar und der Fehler steht als `kontenFehler` daneben; * kommt sie nicht, geht der Fehler der Projektkonten weiter. * -> { konten, auftraege: Liste | null, auftraegeFehler: Text | '', kontenFehler?: Text (nur bei ausgefallenen Projektkonten) } */ export async function ladeAuftraegeAnsicht(kundeId, { getProjektkonten, listAuftraege }) { let antwort try { antwort = await getProjektkonten(kundeId) } catch (kontenErr) { let liste try { liste = await listAuftraege(kundeId) } catch { throw kontenErr } return { konten: {}, kontenFehler: (kontenErr && kontenErr.message) || KONTEN_FEHLER, auftraege: Array.isArray(liste?.auftraege) ? liste.auftraege : [], auftraegeFehler: '', } } const konten = istObjekt(antwort) ? antwort : {} if (konten.auftraegeAktiv !== true) return { konten, auftraege: null, auftraegeFehler: '' } try { const liste = await listAuftraege(kundeId) return { konten, auftraege: Array.isArray(liste?.auftraege) ? liste.auftraege : [], auftraegeFehler: '' } } catch (err) { return { konten, auftraege: null, auftraegeFehler: (err && err.message) || AUFTRAEGE_FEHLER } } } /** * Zustand der Ansicht aus dem Ergebnis von useFinanceResource ({ data, error }). * -> { projekteStatus: 'laden' | 'fehler' | 'bereit', fehler, nachladeFehler, * auftraegeAktiv, auftraegeStatus: 'aus' | 'bereit' | 'fehler', auftraegeFehler, * konten, ohneProjekt, stripeOk, kunde, auftraege } * Ohne Daten gibt es keinen Aufträge-Abschnitt (der Schalter ist noch unbekannt). Scheitert das Neuladen, nachdem Daten * da sind, bleiben die alten Daten stehen und die Meldung kommt als `nachladeFehler` daneben. Waren die Projektkonten * ausgefallen und die Aufträge trotzdem da (`data.kontenFehler`), ist `projekteStatus` „fehler“ und die Aufträge sind bereit. */ export function ansichtsZustand({ data, error } = {}) { if (!data) { return { projekteStatus: error ? 'fehler' : 'laden', fehler: error || '', nachladeFehler: '', auftraegeAktiv: false, auftraegeStatus: 'aus', auftraegeFehler: '', konten: [], ohneProjekt: ohneProjektLeer(), stripeOk: true, kunde: null, auftraege: [], } } const k = istObjekt(data.konten) ? data.konten : {} const kontenFehler = typeof data.kontenFehler === 'string' ? data.kontenFehler : '' // Mit ausgefallenen Projektkonten gilt der Schalter als an, wenn der Server die Auftragsliste geliefert hat const auftraegeAktiv = kontenFehler ? Array.isArray(data.auftraege) : k.auftraegeAktiv === true const auftraegeStatus = !auftraegeAktiv ? 'aus' : data.auftraegeFehler ? 'fehler' : 'bereit' return { projekteStatus: kontenFehler ? 'fehler' : 'bereit', fehler: kontenFehler ? error || kontenFehler : '', nachladeFehler: kontenFehler ? '' : error || '', auftraegeAktiv, auftraegeStatus, auftraegeFehler: auftraegeStatus === 'fehler' ? data.auftraegeFehler : '', konten: Array.isArray(k.konten) ? k.konten : [], ohneProjekt: istObjekt(k.ohneProjekt) ? k.ohneProjekt : ohneProjektLeer(), stripeOk: k.stripeOk !== false, kunde: istObjekt(k.kunde) ? k.kunde : null, auftraege: auftraegeAktiv && Array.isArray(data.auftraege) ? data.auftraege : [], } } // Wertebereich von customers.kundenart (V1); bewusst lokal, auftragView.js gehört dem Strang W2 const KUNDENARTEN = ['unternehmen', 'privat'] /** * Kundendokument mit der frischen Kundenart des Portals (getProjektkonten -> kunde.kundenart). Gilt nur ein gültiger Wert * („unternehmen“ oder „privat“), kommt eine Kopie mit diesem Wert zurück, das Dokument der Seite bleibt unverändert. * Sonst dieselbe Referenz (null bleibt null). So fragt der AB-Dialog nicht erneut, wenn die Kundenart eben gespeichert wurde * und die Kundenseite noch den alten Datensatz hält. */ export function kundeMitKundenart(customer, kundeInfo) { const art = istObjekt(kundeInfo) ? kundeInfo.kundenart : undefined if (!customer || !KUNDENARTEN.includes(art)) return customer return { ...customer, kundenart: art } } /** * Rückmeldung nach dem Anlegen einer Rechnung (wie InvoiceTable.handleCreated): `info.notice` (festgeschrieben, aber nicht * versendet) gewinnt mit seinem Ton, sonst Entwurf oder gesendet. `info` darf fehlen oder null sein. -> { tone, text } */ export function erstelltMeldung(rechnung, info) { const i = istObjekt(info) ? info : {} if (typeof i.notice === 'string' && i.notice) return { tone: i.tone || 'ok', text: i.notice } return { tone: 'ok', text: invoiceDoneMessage(i.senden ? 'create-send' : 'create-draft', rechnung) } } /** Rückmeldung nach dem Speichern oder Senden einer Auftragsbestätigung (onSaved des AB-Dialogs). -> { tone, text } */ export function auftragMeldung(auftrag) { const a = istObjekt(auftrag) ? auftrag : {} const name = a.nummer ? `Auftragsbestätigung ${a.nummer}` : 'Die Auftragsbestätigung' if (a.status === 'entwurf') return { tone: 'ok', text: 'Der Entwurf wurde gespeichert.' } if (a.mailStatus === 'fehler') { return { tone: 'warn', text: `${name} ist versendet, die Mail an den Kunden wurde aber nicht zugestellt. Bitte in der Liste erneut senden.` } } if (a.nummer) return { tone: 'ok', text: `${name} wurde${a.gesendetAn ? ` an ${a.gesendetAn}` : ''} gesendet.` } return { tone: 'ok', text: 'Die Auftragsbestätigung wurde gespeichert.' } }