← Code-Übersicht

InvoiceRecord.php

Pfad: src/Domain/Rechnung/InvoiceRecord.php

Ext: php

Größe: 6299 Bytes

Geändert: 2026-07-16 16:57:10+02

<?php
declare(strict_types=1);

namespace Demo\Domain\Rechnung;

/**
 * Unveraenderlicher, serialisierbarer Verarbeitungsstand einer Rechnung.
 */
final class InvoiceRecord
{
    /** @param list<array<string,mixed>> $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 ?string $netto = null,
        public readonly ?string $steuer = null,
        public readonly ?string $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 = []
    ) {
    }

    /** Erzeugt den initialen Datensatz. */
    public static function fromSourceInvoice(SourceInvoice $source): self
    {
        return new self($source->sourceId, $source->rechnungsschluessel(), InvoiceStatus::DISCOVERED, sourceUrl: $source->downloadUrl, invoiceDate: $source->invoiceDateHint, invoiceNumber: $source->invoiceNumberHint, vendorName: $source->vendorName);
    }

    /** @param array<string,mixed> $data Persistierte Daten. */
    public static function fromArray(array $data): self
    {
        $decimal = static fn (mixed $v): ?string => $v === null ? null : Amount::decimal(is_string($v) || is_int($v) || is_float($v) ? $v : '0');
        return new self((string) ($data['quelleId'] ?? ''), (string) ($data['rechnungsschluessel'] ?? ''), (string) ($data['status'] ?? InvoiceStatus::NEW), self::nullable($data['originalPfad'] ?? null), self::nullable($data['downloadAt'] ?? null), self::nullable($data['payloadSha256'] ?? null), self::nullable($data['sourceUrl'] ?? null), self::nullable($data['invoiceDate'] ?? null), self::nullable($data['invoiceNumber'] ?? null), self::nullable($data['vendorName'] ?? null), self::nullable($data['vendorAddressSignature'] ?? null), $decimal($data['netto'] ?? null), $decimal($data['steuer'] ?? null), $decimal($data['brutto'] ?? null), self::nullable($data['currency'] ?? null), self::nullable($data['lexofficeContactId'] ?? null), self::nullable($data['kontaktStatus'] ?? null), self::nullable($data['lexofficeVoucherId'] ?? null), self::nullable($data['buchungsStatus'] ?? null), isset($data['extractionConfidence']) ? (float) $data['extractionConfidence'] : null, (int) ($data['retryAttempts'] ?? 0), self::nullable($data['lastAttemptAt'] ?? null), is_array($data['errors'] ?? null) ? array_values($data['errors']) : []);
    }

    /** Liefert eine Kopie mit neuem Status. */
    public function withStatus(string $status): self { return $this->copy(['status' => $status]); }

    /** Liefert eine Kopie mit Downloadnachweis. */
    public function withDownload(string $targetPath, string $sha256, string $downloadAt): self { return $this->copy(['status' => InvoiceStatus::DOWNLOADED, 'originalPfad' => $targetPath, 'payloadSha256' => $sha256, 'downloadAt' => $downloadAt]); }

    /** Liefert eine Kopie mit fachlich extrahierten Daten. */
    public function withExtraction(ExtractedInvoiceData $e): self
    {
        $key = self::identity($e->vendorName, $e->invoiceNumber, $e->invoiceDate);
        return $this->copy(['rechnungsschluessel' => $key, 'status' => InvoiceStatus::EXTRACTED, 'invoiceDate' => $e->invoiceDate, 'invoiceNumber' => $e->invoiceNumber, 'vendorName' => $e->vendorName, 'vendorAddressSignature' => $e->vendorAddressSignature, 'netto' => $e->amount->netto, 'steuer' => $e->amount->steuer, 'brutto' => $e->amount->brutto, 'currency' => $e->amount->währung, 'extractionConfidence' => $e->extractionConfidence]);
    }

    /** Liefert eine Kopie mit Kontakt. */
    public function withContact(string $kontaktId, string $kontaktStatus): self { return $this->copy(['status' => InvoiceStatus::CONTACT_RESOLVED, 'lexofficeContactId' => $kontaktId, 'kontaktStatus' => $kontaktStatus]); }

    /** Liefert eine Kopie mit Buchungsnachweis. */
    public function withPosting(PostingResult $posting): self { return $this->copy(['status' => InvoiceStatus::POSTED, 'lexofficeVoucherId' => $posting->voucherId, 'buchungsStatus' => $posting->postStatus]); }

    /** Liefert eine Kopie mit Fehlerhistorie. */
    public function withError(string $status, string $code, string $step, string $message, string $zeit): self
    {
        $errors = $this->errors;
        $errors[] = compact('code', 'step', 'message', 'zeit');
        return $this->copy(['status' => $status, 'lastAttemptAt' => $zeit, 'errors' => $errors]);
    }

    /** Liefert eine Kopie mit Retryzaehler. */
    public function withRetry(int $attempts, string $zeit): self { return $this->copy(['retryAttempts' => $attempts, 'lastAttemptAt' => $zeit]); }

    /** @return array<string,mixed> Persistenzdarstellung. */
    public function toArray(): array { return get_object_vars($this); }

    /** @param array<string,mixed> $changes Aenderungen. */
    private function copy(array $changes): self { return self::fromArray([...$this->toArray(), ...$changes]); }

    /** Baut die kanonische Fachidentitaet. */
    private static function identity(string $vendor, string $number, string $date): string
    {
        $name = preg_replace('/[^a-z0-9]+/', '', strtolower($vendor)) ?: 'unknown';
        return $name . '-' . strtoupper(preg_replace('/\s+/', '', $number) ?? '') . '-' . $date;
    }

    /** Normalisiert nullable Persistenzwerte. */
    private static function nullable(mixed $value): ?string { return $value === null ? null : (string) $value; }
}

Frühere Versionen

Version Zeitpunkt Operation
19 2026-07-16 19:01:40.812288+02 UPDATE