Rechnungs-Download und Buchhaltung
Frühere Version vom 2026-07-16T17:50:13.091757+02:00 · zur aktuellen Fassung
Rechnungs-Download und Buchhaltung
Orientierung für interessierte Laien
Der Prozess holt Rechnungen aus dem WEtell-Kundenkonto und übergibt sie an Lexoffice. WEtell ist die Quelle, Lexoffice das Buchhaltungsziel. Die PDF bleibt zusätzlich lokal als Originalbeleg erhalten.
Die Code-Links zeigen auf die jeweilige Datei in der `/code`-Ansicht. Die genannten Klassen und Methoden sind die darin enthaltenen Symbole. Die Architektur trennt fachliche Regeln von technischen Adaptern: Ein Use Case beschreibt, was fachlich geschieht; ein Port beschreibt eine benötigte Fähigkeit; ein Adapter verbindet diese Fähigkeit mit WEtell, Lexoffice, PDF-Werkzeugen oder Dateien.
1. Prozess starten und orchestrieren
`run-rechnung.php` lädt die Konfiguration für WEtell und Lexoffice, baut die Adapter zusammen und startet den zentralen Ingestions-Use-Case. Ein Use Case ist ein klar abgegrenzter Anwendungsvorgang. Das Ergebnis wird als JSON ausgegeben.
Relevante Symbole
- `run-rechnung.php` als Programmeinstieg: https://demo.karlkratz.com/code/105
- `IngestInvoicesUseCase::__construct`: https://demo.karlkratz.com/code/109
- `IngestInvoicesUseCase::execute`: https://demo.karlkratz.com/code/109
- `IngestResult::__construct`: https://demo.karlkratz.com/code/110
- `IngestResult::toArray`: https://demo.karlkratz.com/code/110
- `InvoiceProcessException::__construct`: https://demo.karlkratz.com/code/111
2. Bei WEtell anmelden und Rechnungen entdecken
Ziel dieses Teilprozesses ist eine belastbare Liste der Rechnungen, die WEtell für das angemeldete Kundenkonto bereitstellt. In diesem Abschnitt wird noch keine PDF gebucht und kein Lexoffice-Kontakt verändert. Es werden nur Zugang, Sitzung und Rechnungsmetadaten vorbereitet.
2.1 Lauf und Konfiguration vorbereiten
- `run-rechnung.php` erzeugt beziehungsweise übernimmt eine Lauf-ID. Diese ID verbindet Logeinträge und alle späteren Zustandsänderungen eines Durchlaufs.
- Die WEtell-Konfiguration wird aus der vorgesehenen Credential-Konfiguration geladen. Benutzername und Passwort werden nur zur Laufzeit verwendet.
- Die Konfiguration wird auf erforderliche Werte geprüft. Fehlende WEtell-Zugangsdaten führen zu einem kontrollierten Fehler, bevor ein Loginversuch startet.
- Die Zieladressen des Headless-Services, Zeitlimits und optionalen Fallbacks werden aus der Konfiguration übernommen.
- Der PHP-Prozess erstellt den fachlichen `WetellSourcePort`. Dadurch kennt der Use Case nur die Fähigkeiten `login`, `discoverInvoices` und später `downloadInvoicePdf`, nicht die konkrete Browsertechnik.
2.2 Zugangsmethode auswählen
Der bevorzugte Zugang ist der bereitgestellte Node.js-/Playwright-Service. Die PHP-Anwendung steuert den Browser nicht direkt, sondern spricht den Service über den `HeadlessWetellSourceAdapter` an. Dadurch bleibt die Browserinstanz von der Buchungslogik getrennt.
Wenn der Headless-Service nicht erreichbar ist oder ausdrücklich anders konfiguriert wurde, kann der `FallbackWetellSourceAdapter` auf den alternativen `HttpWetellSourceAdapter` zurückgreifen. Der Fallback ist eine technische Ausweichroute, keine zweite fachliche Verarbeitung. Es wird nur eine Quelle erfolgreich verwendet.
2.3 Headless-Aufruf absichern
- Der Adapter erstellt eine interne Aktion für `login` oder `discover`.
- Er übergibt nur die notwendigen Lauf- und Zugangsdaten an den lokalen Headless-Service.
- Der Service prüft die Anfrage und, sofern konfiguriert, ein gemeinsames Geheimnis zwischen PHP und Node.js.
- Der Service antwortet als JSON. HTTP-Fehler, Timeout oder ungültiges JSON werden nicht als erfolgreiche Anmeldung behandelt.
- Der Adapter wandelt die technische Antwort wieder in das fachliche Ergebnis des `WetellSourcePort` um.
2.4 Playwright-Sitzung herstellen
- Der Headless-Service öffnet einen isolierten Browser-Kontext.
- Der Kontext erhält die konfigurierten Laufzeitparameter, etwa User-Agent und Timeout.
- Eine neue Seite wird geöffnet und zur WEtell-Anmeldeseite navigiert.
- Die Seite wird auf den erwarteten DOM-Zustand geprüft. Ein unerwarteter Redirect oder eine nicht erreichbare Seite beendet den Schritt mit Fehler.
- Loginformular, Formularaktion und relevante Eingabefelder werden aus der Seite bestimmt, statt feste Sitzungswerte vorauszusetzen.
Die Sitzung bleibt innerhalb des aktuellen Dienstaufrufs gekapselt. Nach dem Ergebnis werden Seite, Kontext und Browserressourcen geschlossen, damit keine alte Sitzung unkontrolliert in den nächsten Lauf hineinwirkt.
2.5 Anmeldedaten übermitteln
- Benutzername und Passwort werden in das erkannte Formular eingesetzt.
- Das Formular wird an die erkannte WEtell-Aktion abgesendet.
- Der Service wartet auf den dokumentierten Ladezustand und berücksichtigt sowohl `domcontentloaded` als auch den Netzwerkzustand mit einem Timeout.
- Danach wird nicht nur der HTTP-Status bewertet. Die Seite wird zusätzlich inhaltlich darauf geprüft, ob sie als angemeldete Kontoseite erkennbar ist.
- Eine weiterhin sichtbare Loginseite, ein Fehlhinweis oder ein abweichender Zielzustand gilt als fehlgeschlagene Anmeldung.
Passwörter werden nicht in `rechnungen.json`, im Dokumentationsdatensatz oder in den fachlichen Rechnungsobjekten gespeichert.
2.6 Rechnungsübersicht öffnen
- Nach erfolgreicher Anmeldung navigiert der Service zur konfigurierten WEtell-Rechnungsübersicht.
- Die Seite wird geladen und als HTML-Dokument ausgelesen.
- Der Service sucht Rechnungszeilen, PDF-Links und die sichtbaren Begleitdaten im DOM.
- Für jede erkannte Zeile werden die verfügbaren Angaben wie Rechnungs-URL, Quell-ID, technische Dateibezeichnung sowie sichtbare Datums- oder Nummernhinweise zusammengeführt.
- Absolute und relative Links werden zu normalisierten URLs aufgelöst. Fehlende oder offensichtlich externe/ungültige Downloadziele werden nicht als gültige Rechnungsquelle ausgegeben.
2.7 Rechnungsobjekte erzeugen
Für jede erkannte Rechnung entsteht ein `SourceInvoice`. Dieses Objekt ist noch keine Buchung. Es beschreibt ausschließlich, was die Quelle WEtell geliefert hat:
- stabile Quell-ID zur Wiedererkennung,
- Download-URL,
- mögliche technische Rechnungsnummer,
- mögliches Rechnungsdatum,
- weitere sichtbare Hinweise aus der Rechnungsübersicht.
Die Hinweise aus der Übersicht sind vorläufig. Die verbindliche Rechnungsnummer und der verbindliche Lieferant werden erst später aus der tatsächlich heruntergeladenen PDF extrahiert.
2.8 Entdeckungsergebnis bewerten
- Die Liste wird an `DiscoverInvoicesUseCase::execute` zurückgegeben.
- Ein leerer, aber technisch erfolgreicher Rechnungsbestand bedeutet: WEtell ist erreichbar, es wurde aber keine Rechnung gefunden.
- Ein technischer Fehler bedeutet dagegen: Login, Navigation oder Extraktion konnte nicht zuverlässig abgeschlossen werden.
- Nur valide `SourceInvoice`-Objekte gelangen in die nächsten Schritte.
- Für jedes Objekt wird anschließend der Rechnungsschlüssel gebildet und gegen den lokalen Zustand geprüft. Die eigentliche Duplikatentscheidung erfolgt im zentralen Ingestions-Use-Case.
- Erst nach diesem Teilprozess darf `DownloadInvoiceUseCase` den PDF-Inhalt anfordern.
2.9 Fehlerbilder und Zustände
- `CREDENTIALS_FEHLEN`: erforderliche Zugangsdaten fehlen.
- `LOGIN_FEHLGESCHLAGEN`: Formular wurde abgesendet, aber kein angemeldeter Zustand erkannt.
- `HEADLESS_NICHT_ERREICHBAR`: der lokale Node.js-/Playwright-Service antwortet nicht rechtzeitig.
- `RECHNUNGSSEITE_FEHLER`: die erwartete Übersicht ist nicht lesbar oder nicht erreichbar.
- `KEINE_RECHNUNGEN`: technisch erfolgreicher Zugriff ohne gefundene Rechnungszeile.
- `QUELLE_UNVOLLSTAENDIG`: eine Zeile enthält keine verwertbare Quell-ID oder keinen gültigen Downloadverweis.
Bei Fehlern wird kein Download als erfolgreich markiert und es wird keine Lexoffice-Aktion ausgelöst. Die Fehlerzuordnung erfolgt über Lauf-ID, Schrittname und Logger.
Relevante Symbole
- `run-rechnung.php` als Programmeinstieg und Konfigurationsverdrahtung: https://demo.karlkratz.com/code/105
- `DiscoverInvoicesUseCase::__construct`: https://demo.karlkratz.com/code/106
- `DiscoverInvoicesUseCase::execute`: https://demo.karlkratz.com/code/106
- `IngestInvoicesUseCase::__construct`: https://demo.karlkratz.com/code/109
- `IngestInvoicesUseCase::execute`: https://demo.karlkratz.com/code/109
- `WetellSourcePort::login`: https://demo.karlkratz.com/code/128
- `WetellSourcePort::discoverInvoices`: https://demo.karlkratz.com/code/128
- `HeadlessWetellSourceAdapter::__construct`: https://demo.karlkratz.com/code/132
- `HeadlessWetellSourceAdapter::login`: https://demo.karlkratz.com/code/132
- `HeadlessWetellSourceAdapter::discoverInvoices`: https://demo.karlkratz.com/code/132
- `HeadlessWetellSourceAdapter::call`: https://demo.karlkratz.com/code/132
- `FallbackWetellSourceAdapter::__construct`: https://demo.karlkratz.com/code/131
- `FallbackWetellSourceAdapter::login`: https://demo.karlkratz.com/code/131
- `FallbackWetellSourceAdapter::discoverInvoices`: https://demo.karlkratz.com/code/131
- `HttpWetellSourceAdapter::__construct`: https://demo.karlkratz.com/code/133
- `HttpWetellSourceAdapter::login`: https://demo.karlkratz.com/code/133
- `HttpWetellSourceAdapter::discoverInvoices`: https://demo.karlkratz.com/code/133
- `HttpWetellSourceAdapter::request`: https://demo.karlkratz.com/code/133
- `HttpWetellSourceAdapter::extractLoginPayload`: https://demo.karlkratz.com/code/133
- `HttpWetellSourceAdapter::isLoginPage`: https://demo.karlkratz.com/code/133
- `HttpWetellSourceAdapter::extractFromHtml`: https://demo.karlkratz.com/code/133
- `HttpWetellSourceAdapter::fallbackRegexExtract`: https://demo.karlkratz.com/code/133
- `HttpWetellSourceAdapter::normalizeUrl`: https://demo.karlkratz.com/code/133
- `SourceInvoice::__construct`: https://demo.karlkratz.com/code/126
- `SourceInvoice::rechnungsschluessel`: https://demo.karlkratz.com/code/126
- `InvoiceLoggerPort::logStep`: https://demo.karlkratz.com/code/119
- `JsonLineLogger::logStep`: https://demo.karlkratz.com/code/135
- `InvoiceProcessException`: https://demo.karlkratz.com/code/111
- `InvoiceErrorCode`: https://demo.karlkratz.com/code/117
- `wetell-headless/server.js::withBrowser`: https://demo.karlkratz.com/code/142
- `wetell-headless/server.js::loginAndGetPage`: https://demo.karlkratz.com/code/142
- `wetell-headless/server.js::doLoginOnly`: https://demo.karlkratz.com/code/142
- `wetell-headless/server.js::doDiscover`: https://demo.karlkratz.com/code/142
- `wetell-headless/server.js::parseJsonBody`: https://demo.karlkratz.com/code/142
- `wetell-headless/server.js::sendJson`: https://demo.karlkratz.com/code/142
3. Rechnung herunterladen und Original speichern
Die gefundene Download-URL wird nicht ungeprüft als Beleg akzeptiert. Der Dateiinhalt wird geladen und als PDF validiert. Danach wird die Originaldatei datums- und lieferantenbezogen unter `rechnungen/originale/` gespeichert. Der SHA-256-Wert ist ein digitaler Fingerabdruck der gespeicherten Datei.
Relevante Symbole
- `DownloadInvoiceUseCase::__construct`: https://demo.karlkratz.com/code/107
- `DownloadInvoiceUseCase::execute`: https://demo.karlkratz.com/code/107
- `DownloadInvoiceUseCase::isPdf`: https://demo.karlkratz.com/code/107
- `WetellSourcePort::downloadInvoicePdf`: https://demo.karlkratz.com/code/128
- `HeadlessWetellSourceAdapter::downloadInvoicePdf`: https://demo.karlkratz.com/code/132
- `FallbackWetellSourceAdapter::downloadInvoicePdf`: https://demo.karlkratz.com/code/131
- `HttpWetellSourceAdapter::downloadInvoicePdf`: https://demo.karlkratz.com/code/133
- `InvoiceFileStoragePort::buildTargetPath`: https://demo.karlkratz.com/code/118
- `InvoiceFileStoragePort::persistDownloadedPdf`: https://demo.karlkratz.com/code/118
- `InvoiceFileStoragePort::sha256`: https://demo.karlkratz.com/code/118
- `DateBasedInvoiceStorage::__construct`: https://demo.karlkratz.com/code/129
- `DateBasedInvoiceStorage::buildTargetPath`: https://demo.karlkratz.com/code/129
- `DateBasedInvoiceStorage::persistDownloadedPdf`: https://demo.karlkratz.com/code/129
- `DateBasedInvoiceStorage::sha256`: https://demo.karlkratz.com/code/129
- `DateBasedInvoiceStorage::sanitize`: https://demo.karlkratz.com/code/129
- `DateBasedInvoiceStorage::normalizeDate`: https://demo.karlkratz.com/code/129
- `wetell-headless/server.js::doDownload`: https://demo.karlkratz.com/code/142
- `wetell-headless/server.js::cryptoHash`: https://demo.karlkratz.com/code/142
- `rechnungen/wetell-downloads.json`: https://demo.karlkratz.com/code/144
4. PDF-Inhalt extrahieren und fachlich prüfen
Nach dem Download wird die PDF gelesen. Zuerst wird der normale Textextraktor verwendet. Ist der Textlayer unbrauchbar oder fehlt er, greift der Fallback über OCR. OCR bedeutet Texterkennung aus dem Bild einer Seite.
Aus dem PDF werden Lieferant, Anschrift, echte Rechnungsnummer, Rechnungsdatum sowie Beträge gelesen. Die fachlichen Prüfungen stellen sicher, dass Pflichtfelder vorhanden sind und die Beträge plausibel zusammenpassen. Die Rechnungsnummer stammt aus dem PDF, nicht aus einer technischen Datei-ID.
Relevante Symbole
- `ExtractInvoiceDataUseCase::__construct`: https://demo.karlkratz.com/code/108
- `ExtractInvoiceDataUseCase::execute`: https://demo.karlkratz.com/code/108
- `TextExtractionPort::extract`: https://demo.karlkratz.com/code/127
- `FallbackTextExtractor::__construct`: https://demo.karlkratz.com/code/130
- `FallbackTextExtractor::extract`: https://demo.karlkratz.com/code/130
- `PdftotextExtractor::__construct`: https://demo.karlkratz.com/code/137
- `PdftotextExtractor::extract`: https://demo.karlkratz.com/code/137
- `PdftotextExtractor::parseText`: https://demo.karlkratz.com/code/137
- `PdftotextExtractor::extractPattern`: https://demo.karlkratz.com/code/137
- `PdftotextExtractor::extractAddress`: https://demo.karlkratz.com/code/137
- `TesseractInvoiceExtractor::__construct`: https://demo.karlkratz.com/code/139
- `TesseractInvoiceExtractor::extract`: https://demo.karlkratz.com/code/139
- `TesseractInvoiceExtractor::parseText`: https://demo.karlkratz.com/code/139
- `TesseractInvoiceExtractor::extractPattern`: https://demo.karlkratz.com/code/139
- `TesseractInvoiceExtractor::extractAddressLine`: https://demo.karlkratz.com/code/139
- `ExtractedInvoiceData::__construct`: https://demo.karlkratz.com/code/116
- `ExtractedInvoiceData::hasRequiredFields`: https://demo.karlkratz.com/code/116
- `ExtractedInvoiceData::hasPlausibleAmount`: https://demo.karlkratz.com/code/116
- `ExtractedInvoiceData::toArray`: https://demo.karlkratz.com/code/116
- `Amount::__construct`: https://demo.karlkratz.com/code/114
- `Amount::isConsistent`: https://demo.karlkratz.com/code/114
- `Amount::toArray`: https://demo.karlkratz.com/code/114
- `ClockPort::now`: https://demo.karlkratz.com/code/115
- `SystemClock::__construct`: https://demo.karlkratz.com/code/138
- `SystemClock::now`: https://demo.karlkratz.com/code/138
5. Duplikate und idempotente Wiederholung behandeln
Vor der Buchung wird geprüft, ob dieselbe Quellrechnung oder derselbe fachliche Rechnungsschlüssel schon bekannt ist. Ein zweiter Lauf mit derselben Rechnung soll nicht zu einem zweiten Lexoffice-Beleg führen. Das nennt man idempotentes Verhalten: Wiederholen ist sicher, weil das Ergebnis nicht doppelt angelegt wird.
Relevante Symbole
- `IngestInvoicesUseCase::execute`: https://demo.karlkratz.com/code/109
- `IngestInvoicesUseCase::shouldProcess`: https://demo.karlkratz.com/code/109
- `IngestInvoicesUseCase::deriveRunStatus`: https://demo.karlkratz.com/code/109
- `InvoiceState::__construct`: https://demo.karlkratz.com/code/121
- `InvoiceState::findBySourceId`: https://demo.karlkratz.com/code/121
- `InvoiceState::findByRechnungsschluessel`: https://demo.karlkratz.com/code/121
- `InvoiceState::withRecord`: https://demo.karlkratz.com/code/121
- `InvoiceState::all`: https://demo.karlkratz.com/code/121
- `InvoiceState::toArray`: https://demo.karlkratz.com/code/121
- `InvoiceStatePort::load`: https://demo.karlkratz.com/code/122
- `InvoiceStatePort::save`: https://demo.karlkratz.com/code/122
- `JsonInvoiceStateAdapter::__construct`: https://demo.karlkratz.com/code/134
- `JsonInvoiceStateAdapter::load`: https://demo.karlkratz.com/code/134
- `JsonInvoiceStateAdapter::save`: https://demo.karlkratz.com/code/134
- `SourceInvoice::rechnungsschluessel`: https://demo.karlkratz.com/code/126
- `InvoiceStatus`: https://demo.karlkratz.com/code/123
- `InvoiceErrorCode`: https://demo.karlkratz.com/code/117
- `InvoiceRecord::withStatus`: https://demo.karlkratz.com/code/120
- `InvoiceRecord::withRetry`: https://demo.karlkratz.com/code/120
- `InvoiceRecord::toArray`: https://demo.karlkratz.com/code/120
- `rechnungen/rechnungen.json`: https://demo.karlkratz.com/code/143
6. Lieferantenkontakt in Lexoffice finden oder anlegen
Der Lieferant wird nicht fest im Programm kodiert. Die extrahierten Daten werden verwendet, um in Lexoffice nach einem passenden Kontakt zu suchen. Wird einer gefunden, wird seine ID wiederverwendet. Andernfalls wird ein neuer Kontakt mit Name und Adresssignatur angelegt.
Relevante Symbole
- `ResolveVendorUseCase::__construct`: https://demo.karlkratz.com/code/113
- `ResolveVendorUseCase::execute`: https://demo.karlkratz.com/code/113
- `LexofficePort::findContactByName`: https://demo.karlkratz.com/code/124
- `LexofficePort::createContact`: https://demo.karlkratz.com/code/124
- `LexofficeApiAdapter::__construct`: https://demo.karlkratz.com/code/136
- `LexofficeApiAdapter::findContactByName`: https://demo.karlkratz.com/code/136
- `LexofficeApiAdapter::createContact`: https://demo.karlkratz.com/code/136
- `LexofficeApiAdapter::request`: https://demo.karlkratz.com/code/136
- `ExtractedInvoiceData::hasRequiredFields`: https://demo.karlkratz.com/code/116
- `InvoiceRecord::withContact`: https://demo.karlkratz.com/code/120
7. Rechnungsbeleg in Lexoffice anlegen
Mit der echten Rechnungsnummer, dem Rechnungsdatum, den extrahierten Beträgen und der Kontakt-ID wird der Lexoffice-Beleg erstellt. Danach wird die geprüfte lokale PDF als Anhang hochgeladen. Der Vorgang gilt erst als erfolgreich, wenn eine gültige Beleg-ID vorliegt und der Datei-Upload erfolgreich bestätigt wurde.
Relevante Symbole
- `PostInvoiceUseCase::__construct`: https://demo.karlkratz.com/code/112
- `PostInvoiceUseCase::execute`: https://demo.karlkratz.com/code/112
- `LexofficePort::postInvoice`: https://demo.karlkratz.com/code/124
- `LexofficeApiAdapter::postInvoice`: https://demo.karlkratz.com/code/136
- `LexofficeApiAdapter::request`: https://demo.karlkratz.com/code/136
- `LexofficeApiAdapter::uploadAttachment`: https://demo.karlkratz.com/code/136
- `PostingResult::__construct`: https://demo.karlkratz.com/code/125
- `PostingResult::toArray`: https://demo.karlkratz.com/code/125
- `InvoiceRecord::withPosting`: https://demo.karlkratz.com/code/120
- `ExtractedInvoiceData::toArray`: https://demo.karlkratz.com/code/116
- `Amount::toArray`: https://demo.karlkratz.com/code/114
8. Status und Protokoll schreiben
Jeder wichtige Schritt wird protokolliert. Der dauerhafte fachliche Zustand liegt in `rechnungen.json`; die einzelnen Logeinträge helfen bei der zeitlichen Nachvollziehbarkeit. Ein Status wie `POSTED` bedeutet, dass der Beleg erfolgreich verarbeitet wurde. Fehler und Wiederholungsversuche bleiben als Zustand erkennbar.
Relevante Symbole
- `InvoiceRecord::withDownload`: https://demo.karlkratz.com/code/120
- `InvoiceRecord::withExtraction`: https://demo.karlkratz.com/code/120
- `InvoiceRecord::withContact`: https://demo.karlkratz.com/code/120
- `InvoiceRecord::withPosting`: https://demo.karlkratz.com/code/120
- `InvoiceRecord::withError`: https://demo.karlkratz.com/code/120
- `InvoiceRecord::withRetry`: https://demo.karlkratz.com/code/120
- `InvoiceRecord::toArray`: https://demo.karlkratz.com/code/120
- `InvoiceState::withRecord`: https://demo.karlkratz.com/code/121
- `InvoiceState::toArray`: https://demo.karlkratz.com/code/121
- `JsonInvoiceStateAdapter::save`: https://demo.karlkratz.com/code/134
- `InvoiceLoggerPort::logStep`: https://demo.karlkratz.com/code/119
- `JsonLineLogger::__construct`: https://demo.karlkratz.com/code/135
- `JsonLineLogger::logStep`: https://demo.karlkratz.com/code/135
- `IngestResult::toArray`: https://demo.karlkratz.com/code/110
- `rechnungen/rechnungen.json`: https://demo.karlkratz.com/code/143
- `rechnungen/wetell-downloads.json`: https://demo.karlkratz.com/code/144
9. Fehler behandeln und sicher wiederholen
Ein Fehler bei Anmeldung, Entdeckung, Download, PDF-Auslesung, Kontaktabgleich oder Lexoffice-Buchung wird dem jeweiligen Schritt zugeordnet. Der Lauf wird nicht fälschlich als vollständig erfolgreich markiert. Bei einem erneuten Lauf entscheiden Status, Quell-ID, Rechnungsschlüssel und Hash erneut, ob eine Rechnung verarbeitet werden muss.
Relevante Symbole
- `InvoiceProcessException`: https://demo.karlkratz.com/code/111
- `InvoiceErrorCode`: https://demo.karlkratz.com/code/117
- `IngestInvoicesUseCase::execute`: https://demo.karlkratz.com/code/109
- `IngestInvoicesUseCase::shouldProcess`: https://demo.karlkratz.com/code/109
- `IngestInvoicesUseCase::deriveRunStatus`: https://demo.karlkratz.com/code/109
- `InvoiceRecord::withError`: https://demo.karlkratz.com/code/120
- `InvoiceRecord::withRetry`: https://demo.karlkratz.com/code/120
- `InvoiceState::findBySourceId`: https://demo.karlkratz.com/code/121
- `InvoiceState::findByRechnungsschluessel`: https://demo.karlkratz.com/code/121
- `JsonLineLogger::logStep`: https://demo.karlkratz.com/code/135
- `PostingResult::toArray`: https://demo.karlkratz.com/code/125
10. Nachweise des Gesamtergebnisses
Ein erfolgreicher Durchlauf hinterlässt drei zusammengehörige Nachweise:
- die geprüfte Original-PDF unter `rechnungen/originale/`,
- den lokalen Status in `rechnungen/rechnungen.json`,
- den Lexoffice-Kontakt und den Beleg mit PDF-Anhang.
Die fachliche Verarbeitung wird durch Application-Use-Cases gesteuert. Die technischen Details bleiben austauschbar: WEtell kann über Headless-Browser, HTTP oder Fallback angebunden werden; die PDF-Auslesung kann über `pdftotext` oder OCR erfolgen; Lexoffice wird über den API-Adapter angesprochen.
Relevante Symbole
- `run-rechnung.php`: https://demo.karlkratz.com/code/105
- `IngestInvoicesUseCase::execute`: https://demo.karlkratz.com/code/109
- `IngestResult::toArray`: https://demo.karlkratz.com/code/110
- `InvoiceRecord::toArray`: https://demo.karlkratz.com/code/120
- `JsonInvoiceStateAdapter::save`: https://demo.karlkratz.com/code/134
- `LexofficeApiAdapter::postInvoice`: https://demo.karlkratz.com/code/136
- `LexofficeApiAdapter::uploadAttachment`: https://demo.karlkratz.com/code/136
- `DateBasedInvoiceStorage::persistDownloadedPdf`: https://demo.karlkratz.com/code/129
- `rechnungen/rechnungen.json`: https://demo.karlkratz.com/code/143
- `rechnungen/wetell-downloads.json`: https://demo.karlkratz.com/code/144
- `wetell-headless/server.js`: https://demo.karlkratz.com/code/142