71 lines
2.3 KiB
PHP
71 lines
2.3 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Models\ExportTarget;
|
|
use App\Models\Invoice;
|
|
use App\Models\InvoiceExport;
|
|
use App\Services\Billing\InvoiceArchive;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Queue\Queueable;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Put one invoice on ONE destination, and keep trying if that one is away.
|
|
*
|
|
* One job per pair rather than one per invoice: the reason for having a second
|
|
* destination is that the first can fail, and a job that writes to both would
|
|
* retry the one that worked every time the other did not — and would report a
|
|
* single outcome for two different things.
|
|
*
|
|
* Queued rather than done during the request that issued the invoice: a network
|
|
* mount can block for its whole timeout, and an invoice must not fail to be
|
|
* issued because a NAS in an office is rebooting.
|
|
*/
|
|
class ArchiveInvoice implements ShouldQueue
|
|
{
|
|
use Queueable;
|
|
|
|
public int $tries = 5;
|
|
|
|
/** A destination that is gone is usually gone for a while. */
|
|
public array $backoff = [60, 300, 900, 3600];
|
|
|
|
public function __construct(public Invoice $invoice, public ExportTarget $target) {}
|
|
|
|
public function handle(InvoiceArchive $archive): void
|
|
{
|
|
if (! $this->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)],
|
|
);
|
|
}
|
|
}
|