124 lines
4.1 KiB
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');
|
|
}
|
|
}
|