← Code-Übersicht

InvoiceRecord.php

Pfad: src/Domain/Rechnung/InvoiceRecord.php

Ext: php

Größe: 17114 Bytes

Geändert: 2026-07-16T14:10:33+02:00

Frühere Version vom 2026-07-16T14:10:33+02:00 · zur aktuellen Fassung

<?php
declare(strict_types=1);

namespace Demo\Domain\Rechnung;

/**
 * Laufzeitobjekt pro Rechnung mit Idempotenzstatus.
 */
final class InvoiceRecord
{
    /**
     * @param string      $quelleId               Quell-ID.
     * @param string      $rechnungsschluessel    Idempotent-Schluessel.
     * @param string      $status                 Prozessstatus.
     * @param string|null $originalPfad           Lokaler PDF-Pfad.
     * @param string|null $downloadAt             Download-Zeitstempel.
     * @param string|null $payloadSha256          Hash des PDF.
     * @param string|null $sourceUrl              Quell-URL.
     * @param string|null $invoiceDate            Rechnungsdatum.
     * @param string|null $invoiceNumber          Rechnungsnummer.
     * @param string|null $vendorName             Lieferant.
     * @param string|null $vendorAddressSignature Adresse/Lieferantensignatur.
     * @param float|null  $netto                  Netto.
     * @param float|null  $steuer                 Steuer.
     * @param float|null  $brutto                 Brutto.
     * @param string|null $currency               Waehrung.
     * @param string|null $lexofficeContactId     Kontakt-ID aus Buha.
     * @param string|null $kontaktStatus          Kontakt-Status.
     * @param string|null $lexofficeVoucherId     Voucher-ID aus Buha.
     * @param string|null $buchungsStatus         Buha-Status.
     * @param float|null  $extractionConfidence   Qualität der Extraktion.
     * @param int         $retryAttempts          Wiederholungsversuchszahl.
     * @param string|null $lastAttemptAt          Zeit letzter Versuch.
     * @param array       $errors                 Fehlerhistorie.
     */
    public function __construct(
        public readonly string $quelleId,
        public readonly string $rechnungsschluessel,
        public readonly string $status,
        public readonly ?string $originalPfad = null,
        public readonly ?string $downloadAt = null,
        public readonly ?string $payloadSha256 = null,
        public readonly ?string $sourceUrl = null,
        public readonly ?string $invoiceDate = null,
        public readonly ?string $invoiceNumber = null,
        public readonly ?string $vendorName = null,
        public readonly ?string $vendorAddressSignature = null,
        public readonly ?float $netto = null,
        public readonly ?float $steuer = null,
        public readonly ?float $brutto = null,
        public readonly ?string $currency = null,
        public readonly ?string $lexofficeContactId = null,
        public readonly ?string $kontaktStatus = null,
        public readonly ?string $lexofficeVoucherId = null,
        public readonly ?string $buchungsStatus = null,
        public readonly ?float $extractionConfidence = null,
        public readonly int $retryAttempts = 0,
        public readonly ?string $lastAttemptAt = null,
        public readonly array $errors = []
    ) {
    }

    /**
     * @param SourceInvoice $source Invoice aus der Quelle.
     * @return InvoiceRecord Rohzustand fuer neue Quelleinträge.
     */
    public static function fromSourceInvoice(SourceInvoice $source): self
    {
        return new self(
            quelleId: $source->sourceId,
            rechnungsschluessel: $source->rechnungsschluessel(),
            status: InvoiceStatus::DISCOVERED,
            sourceUrl: $source->downloadUrl,
            invoiceDate: $source->invoiceDateHint,
            invoiceNumber: $source->invoiceNumberHint,
            vendorName: $source->vendorName
        );
    }

    /**
     * @param array<string,mixed> $data Serialisierte Daten.
     * @return InvoiceRecord Hydriertes Objekt.
     */
    public static function fromArray(array $data): self
    {
        return new self(
            quelleId: (string) ($data['quelleId'] ?? ''),
            rechnungsschluessel: (string) ($data['rechnungsschluessel'] ?? ''),
            status: (string) ($data['status'] ?? InvoiceStatus::NEW),
            originalPfad: isset($data['originalPfad']) ? (string) $data['originalPfad'] : null,
            downloadAt: isset($data['downloadAt']) ? (string) $data['downloadAt'] : null,
            payloadSha256: isset($data['payloadSha256']) ? (string) $data['payloadSha256'] : null,
            sourceUrl: isset($data['sourceUrl']) ? (string) $data['sourceUrl'] : null,
            invoiceDate: isset($data['invoiceDate']) ? (string) $data['invoiceDate'] : null,
            invoiceNumber: isset($data['invoiceNumber']) ? (string) $data['invoiceNumber'] : null,
            vendorName: isset($data['vendorName']) ? (string) $data['vendorName'] : null,
            vendorAddressSignature: isset($data['vendorAddressSignature']) ? (string) $data['vendorAddressSignature'] : null,
            netto: isset($data['netto']) ? (float) $data['netto'] : null,
            steuer: isset($data['steuer']) ? (float) $data['steuer'] : null,
            brutto: isset($data['brutto']) ? (float) $data['brutto'] : null,
            currency: isset($data['currency']) ? (string) $data['currency'] : null,
            lexofficeContactId: isset($data['lexofficeContactId']) ? (string) $data['lexofficeContactId'] : null,
            kontaktStatus: isset($data['kontaktStatus']) ? (string) $data['kontaktStatus'] : null,
            lexofficeVoucherId: isset($data['lexofficeVoucherId']) ? (string) $data['lexofficeVoucherId'] : null,
            buchungsStatus: isset($data['buchungsStatus']) ? (string) $data['buchungsStatus'] : null,
            extractionConfidence: isset($data['extractionConfidence']) ? (float) $data['extractionConfidence'] : null,
            retryAttempts: isset($data['retryAttempts']) ? (int) $data['retryAttempts'] : 0,
            lastAttemptAt: isset($data['lastAttemptAt']) ? (string) $data['lastAttemptAt'] : null,
            errors: is_array($data['errors'] ?? null) ? (array) $data['errors'] : []
        );
    }

    /**
     * @param string $status Neuer Status.
     * @return self Geaenderte Kopie.
     */
    public function withStatus(string $status): self
    {
        return new self(
            quelleId: $this->quelleId,
            rechnungsschluessel: $this->rechnungsschluessel,
            status: $status,
            originalPfad: $this->originalPfad,
            downloadAt: $this->downloadAt,
            payloadSha256: $this->payloadSha256,
            sourceUrl: $this->sourceUrl,
            invoiceDate: $this->invoiceDate,
            invoiceNumber: $this->invoiceNumber,
            vendorName: $this->vendorName,
            vendorAddressSignature: $this->vendorAddressSignature,
            netto: $this->netto,
            steuer: $this->steuer,
            brutto: $this->brutto,
            currency: $this->currency,
            lexofficeContactId: $this->lexofficeContactId,
            kontaktStatus: $this->kontaktStatus,
            lexofficeVoucherId: $this->lexofficeVoucherId,
            buchungsStatus: $this->buchungsStatus,
            extractionConfidence: $this->extractionConfidence,
            retryAttempts: $this->retryAttempts,
            lastAttemptAt: $this->lastAttemptAt,
            errors: $this->errors
        );
    }

    /**
     * @param string $targetPath Lokaler Ablagepfad.
     * @param string $sha256     Dateihash.
     * @param string $downloadAt Zeitpunkt.
     * @return self Aktualisierte Kopie.
     */
    public function withDownload(string $targetPath, string $sha256, string $downloadAt): self
    {
        return new self(
            quelleId: $this->quelleId,
            rechnungsschluessel: $this->rechnungsschluessel,
            status: InvoiceStatus::DOWNLOADED,
            originalPfad: $targetPath,
            downloadAt: $downloadAt,
            payloadSha256: $sha256,
            sourceUrl: $this->sourceUrl,
            invoiceDate: $this->invoiceDate,
            invoiceNumber: $this->invoiceNumber,
            vendorName: $this->vendorName,
            vendorAddressSignature: $this->vendorAddressSignature,
            netto: $this->netto,
            steuer: $this->steuer,
            brutto: $this->brutto,
            currency: $this->currency,
            lexofficeContactId: $this->lexofficeContactId,
            kontaktStatus: $this->kontaktStatus,
            lexofficeVoucherId: $this->lexofficeVoucherId,
            buchungsStatus: $this->buchungsStatus,
            extractionConfidence: $this->extractionConfidence,
            retryAttempts: $this->retryAttempts,
            lastAttemptAt: $this->lastAttemptAt,
            errors: $this->errors
        );
    }

    /**
     * @param ExtractedInvoiceData $extracted Extragierte Werte.
     * @return self Kopie mit extrahierten Feldern.
     */
    public function withExtraction(ExtractedInvoiceData $extracted): self
    {
        return new self(
            quelleId: $this->quelleId,
            rechnungsschluessel: $this->rechnungsschluessel,
            status: InvoiceStatus::EXTRACTED,
            originalPfad: $this->originalPfad,
            downloadAt: $this->downloadAt,
            payloadSha256: $this->payloadSha256,
            sourceUrl: $this->sourceUrl,
            invoiceDate: $extracted->invoiceDate,
            invoiceNumber: $extracted->invoiceNumber,
            vendorName: $extracted->vendorName,
            vendorAddressSignature: $extracted->vendorAddressSignature,
            netto: $extracted->amount->netto,
            steuer: $extracted->amount->steuer,
            brutto: $extracted->amount->brutto,
            currency: $extracted->amount->währung,
            lexofficeContactId: $this->lexofficeContactId,
            kontaktStatus: $this->kontaktStatus,
            lexofficeVoucherId: $this->lexofficeVoucherId,
            buchungsStatus: $this->buchungsStatus,
            extractionConfidence: $extracted->extractionConfidence,
            retryAttempts: $this->retryAttempts,
            lastAttemptAt: $this->lastAttemptAt,
            errors: $this->errors
        );
    }

    /**
     * @param string $kontaktId     Kontakt-ID aus Buha.
     * @param string $kontaktStatus Kontaktlogischer Status.
     * @return self Kopie mit Kontaktinformationen.
     */
    public function withContact(string $kontaktId, string $kontaktStatus): self
    {
        return new self(
            quelleId: $this->quelleId,
            rechnungsschluessel: $this->rechnungsschluessel,
            status: InvoiceStatus::CONTACT_RESOLVED,
            originalPfad: $this->originalPfad,
            downloadAt: $this->downloadAt,
            payloadSha256: $this->payloadSha256,
            sourceUrl: $this->sourceUrl,
            invoiceDate: $this->invoiceDate,
            invoiceNumber: $this->invoiceNumber,
            vendorName: $this->vendorName,
            vendorAddressSignature: $this->vendorAddressSignature,
            netto: $this->netto,
            steuer: $this->steuer,
            brutto: $this->brutto,
            currency: $this->currency,
            lexofficeContactId: $kontaktId,
            kontaktStatus: $kontaktStatus,
            lexofficeVoucherId: $this->lexofficeVoucherId,
            buchungsStatus: $this->buchungsStatus,
            extractionConfidence: $this->extractionConfidence,
            retryAttempts: $this->retryAttempts,
            lastAttemptAt: $this->lastAttemptAt,
            errors: $this->errors
        );
    }

    /**
     * @param PostingResult $posting Ergebnis der Buchung.
     * @return self Kopie mit Buchungsdaten.
     */
    public function withPosting(PostingResult $posting): self
    {
        return new self(
            quelleId: $this->quelleId,
            rechnungsschluessel: $this->rechnungsschluessel,
            status: InvoiceStatus::POSTED,
            originalPfad: $this->originalPfad,
            downloadAt: $this->downloadAt,
            payloadSha256: $this->payloadSha256,
            sourceUrl: $this->sourceUrl,
            invoiceDate: $this->invoiceDate,
            invoiceNumber: $this->invoiceNumber,
            vendorName: $this->vendorName,
            vendorAddressSignature: $this->vendorAddressSignature,
            netto: $this->netto,
            steuer: $this->steuer,
            brutto: $this->brutto,
            currency: $this->currency,
            lexofficeContactId: $this->lexofficeContactId,
            kontaktStatus: $this->kontaktStatus,
            lexofficeVoucherId: $posting->voucherId,
            buchungsStatus: $posting->postStatus,
            extractionConfidence: $this->extractionConfidence,
            retryAttempts: $this->retryAttempts,
            lastAttemptAt: $this->lastAttemptAt,
            errors: $this->errors
        );
    }

    /**
     * @param string      $status   Status fuer den Fehler.
     * @param string      $code     Fehlercode.
     * @param string      $step     Schrittbezeichnung.
     * @param string      $message  Meldung.
     * @param string      $zeit     Zeitpunkt.
     * @return self Kopie mit Fehlerartefakt.
     */
    public function withError(string $status, string $code, string $step, string $message, string $zeit): self
    {
        $errors = $this->errors;
        $errors[] = [
            'status' => $status,
            'code' => $code,
            'step' => $step,
            'message' => $message,
            'occurredAt' => $zeit,
        ];

        return new self(
            quelleId: $this->quelleId,
            rechnungsschluessel: $this->rechnungsschluessel,
            status: $status,
            originalPfad: $this->originalPfad,
            downloadAt: $this->downloadAt,
            payloadSha256: $this->payloadSha256,
            sourceUrl: $this->sourceUrl,
            invoiceDate: $this->invoiceDate,
            invoiceNumber: $this->invoiceNumber,
            vendorName: $this->vendorName,
            vendorAddressSignature: $this->vendorAddressSignature,
            netto: $this->netto,
            steuer: $this->steuer,
            brutto: $this->brutto,
            currency: $this->currency,
            lexofficeContactId: $this->lexofficeContactId,
            kontaktStatus: $this->kontaktStatus,
            lexofficeVoucherId: $this->lexofficeVoucherId,
            buchungsStatus: $this->buchungsStatus,
            extractionConfidence: $this->extractionConfidence,
            retryAttempts: $this->retryAttempts,
            lastAttemptAt: $this->lastAttemptAt,
            errors: $errors
        );
    }

    /**
     * @param int    $attempts Anzahl Versuche.
     * @param string $zeit     Zeitstempel.
     * @return self Kopie mit aktualisierten Retry-Hinweisen.
     */
    public function withRetry(int $attempts, string $zeit): self
    {
        return new self(
            quelleId: $this->quelleId,
            rechnungsschluessel: $this->rechnungsschluessel,
            status: $this->status,
            originalPfad: $this->originalPfad,
            downloadAt: $this->downloadAt,
            payloadSha256: $this->payloadSha256,
            sourceUrl: $this->sourceUrl,
            invoiceDate: $this->invoiceDate,
            invoiceNumber: $this->invoiceNumber,
            vendorName: $this->vendorName,
            vendorAddressSignature: $this->vendorAddressSignature,
            netto: $this->netto,
            steuer: $this->steuer,
            brutto: $this->brutto,
            currency: $this->currency,
            lexofficeContactId: $this->lexofficeContactId,
            kontaktStatus: $this->kontaktStatus,
            lexofficeVoucherId: $this->lexofficeVoucherId,
            buchungsStatus: $this->buchungsStatus,
            extractionConfidence: $this->extractionConfidence,
            retryAttempts: $attempts,
            lastAttemptAt: $zeit,
            errors: $this->errors
        );
    }

    /**
     * @return array<string,mixed> Serialisiertes Objekt.
     */
    public function toArray(): array
    {
        return [
            'quelleId' => $this->quelleId,
            'rechnungsschluessel' => $this->rechnungsschluessel,
            'status' => $this->status,
            'originalPfad' => $this->originalPfad,
            'downloadAt' => $this->downloadAt,
            'payloadSha256' => $this->payloadSha256,
            'sourceUrl' => $this->sourceUrl,
            'invoiceDate' => $this->invoiceDate,
            'invoiceNumber' => $this->invoiceNumber,
            'vendorName' => $this->vendorName,
            'vendorAddressSignature' => $this->vendorAddressSignature,
            'netto' => $this->netto,
            'steuer' => $this->steuer,
            'brutto' => $this->brutto,
            'currency' => $this->currency,
            'lexofficeContactId' => $this->lexofficeContactId,
            'kontaktStatus' => $this->kontaktStatus,
            'lexofficeVoucherId' => $this->lexofficeVoucherId,
            'buchungsStatus' => $this->buchungsStatus,
            'extractionConfidence' => $this->extractionConfidence,
            'retryAttempts' => $this->retryAttempts,
            'lastAttemptAt' => $this->lastAttemptAt,
            'errors' => $this->errors,
        ];
    }
}