CluPilotCloud/app/Livewire/Admin/EditInvoiceSeries.php

124 lines
4.1 KiB
PHP

<?php
namespace App\Livewire\Admin;
use App\Models\InvoiceSeries;
use Illuminate\Validation\Rule;
use LivewireUI\Modal\ModalComponent;
/**
* Editing a Rechnungskreis, with the parts that cannot move held still.
*
* The same shape as EditDatacenter and for the same reason: some fields stop
* being editable once something depends on them. Here the something is a
* document in somebody's accounts.
*
* The prefix is part of every number already issued. Changing it from RE to AR
* does not rename anything — it leaves a series whose documents say RE-2026-…
* and whose next document says AR-2026-…, with no record that they belong
* together. Free while the series is empty, which is the case that matters: a
* prefix chosen wrongly on the day the shop opens.
*
* The counter may be raised but never lowered. Lowering it re-issues a number
* that is already printed on a document somebody holds, and gapless-and-
* ascending is a legal requirement rather than a preference.
*/
class EditInvoiceSeries extends ModalComponent
{
public string $uuid = '';
public string $name = '';
public string $prefix = '';
public int $digits = 4;
public bool $yearlyReset = true;
public int $nextNumber = 1;
public bool $active = true;
/** How many documents this series has already issued. */
public int $issued = 0;
/** The lowest number this counter may still be set to. */
public int $floor = 1;
public function mount(string $uuid): void
{
$this->authorize('site.manage'); // modals are reachable without the route middleware
$series = InvoiceSeries::query()->where('uuid', $uuid)->firstOrFail();
$this->uuid = $uuid;
$this->name = $series->name;
$this->prefix = $series->prefix;
$this->digits = (int) $series->digits;
$this->yearlyReset = (bool) $series->yearly_reset;
$this->nextNumber = (int) $series->next_number;
$this->active = (bool) $series->active;
$this->issued = $series->invoices()->count();
$this->floor = (int) $series->next_number;
}
public function save()
{
$this->authorize('site.manage');
$series = InvoiceSeries::query()->where('uuid', $this->uuid)->firstOrFail();
// Recomputed from the database, never read back from the properties the
// browser returns: these two rules are what keep already-issued numbers
// from being re-used, and a forged field must not lift either.
$issued = $series->invoices()->count();
$floor = (int) $series->next_number;
$rules = [
'name' => 'required|string|max:120',
'digits' => 'required|integer|min:3|max:10',
'active' => 'boolean',
'nextNumber' => ['required', 'integer', 'min:'.$floor],
];
if ($issued === 0) {
$rules['prefix'] = [
'required', 'string', 'max:12', 'regex:/^[A-Z0-9-]+$/',
Rule::unique('invoice_series', 'prefix')->ignore($series->id),
];
// Only while nothing has been issued: switching an established
// series between yearly and continuous puts a gap in one of them.
$rules['yearlyReset'] = 'boolean';
}
if ($issued === 0) {
$this->prefix = strtoupper(trim($this->prefix));
} else {
$this->prefix = $series->prefix;
$this->yearlyReset = (bool) $series->yearly_reset;
}
$data = $this->validate($rules);
$series->update([
'name' => $data['name'],
'digits' => $data['digits'],
'next_number' => $data['nextNumber'],
'active' => $data['active'],
...($issued === 0 ? [
'prefix' => $data['prefix'],
'yearly_reset' => $data['yearlyReset'],
] : []),
]);
$this->dispatch('notify', message: __('finance.series_saved'));
return $this->redirectRoute('admin.finance', navigate: true);
}
public function render()
{
return view('livewire.admin.edit-invoice-series');
}
}