target->active) { return; } $record = InvoiceExport::query()->firstOrNew([ 'invoice_id' => $this->invoice->id, 'export_target_id' => $this->target->id, ]); // Already there. A retry after a timeout that in fact succeeded must // not write it a second time. if ($record->exists && $record->exported_at !== null) { return; } $record->attempts = (int) $record->attempts + 1; $record->save(); $archive->store($this->invoice, $this->target); $record->forceFill(['exported_at' => now(), 'error' => null])->save(); } public function failed(?Throwable $e): void { // Against the pair, not the invoice: "on the office NAS, not on the // off-site box" is the thing somebody needs to be told. InvoiceExport::query()->updateOrCreate( ['invoice_id' => $this->invoice->id, 'export_target_id' => $this->target->id], ['error' => mb_substr((string) $e?->getMessage(), 0, 250)], ); } }