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,
];
}
}