63 lines
1.9 KiB
PHP
63 lines
1.9 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Models\Invoice;
|
|
use App\Services\Billing\InvoiceArchive;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Queue\Queueable;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Put one invoice on the archive, and keep trying if the archive is not there.
|
|
*
|
|
* Queued rather than done during the request that issued the invoice: a network
|
|
* mount can block for as long as its timeout allows, and an invoice must not
|
|
* fail to be issued because a NAS in an office is rebooting.
|
|
*
|
|
* Retries with a backoff and, when it finally gives up, records why on the
|
|
* invoice itself. An export that fails silently is the failure mode that
|
|
* matters here — nobody looks at an archive to check whether last Tuesday is
|
|
* in it, and the sweep needs something to find.
|
|
*/
|
|
class ArchiveInvoice implements ShouldQueue
|
|
{
|
|
use Queueable;
|
|
|
|
public int $tries = 5;
|
|
|
|
/** Minutes, roughly: a mount that is gone is usually gone for a while. */
|
|
public array $backoff = [60, 300, 900, 3600];
|
|
|
|
public function __construct(public Invoice $invoice) {}
|
|
|
|
public function handle(InvoiceArchive $archive): void
|
|
{
|
|
if (! InvoiceArchive::enabled()) {
|
|
return;
|
|
}
|
|
|
|
// Already there. A retry after a timeout that actually succeeded must
|
|
// not write it twice, and the file name is the same either way.
|
|
if ($this->invoice->exported_at !== null) {
|
|
return;
|
|
}
|
|
|
|
$archive->store($this->invoice);
|
|
|
|
$this->invoice->forceFill([
|
|
'exported_at' => now(),
|
|
'export_error' => null,
|
|
])->save();
|
|
}
|
|
|
|
public function failed(?Throwable $e): void
|
|
{
|
|
// On the invoice, not only in the log. The console shows it there, and
|
|
// a log line nobody reads is not a report.
|
|
$this->invoice->forceFill([
|
|
'export_error' => mb_substr((string) $e?->getMessage(), 0, 250),
|
|
])->save();
|
|
}
|
|
}
|