Steuerkram/tests/78_NummernkreisFreigabeTest.php

167 lines
6.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
/**
* Test 78: Freigabe der zuletzt vergebenen Nummer beim Löschen.
*
* Wird eine Rechnung oder ein Beleg gelöscht, gibt der jeweilige Nummernkreis
* die Nummer wieder frei – aber nur, wenn es die zuletzt vergebene war. Sonst
* hinterließe jeder Löschvorgang (z.B. ein E2E-Testlauf) eine unumkehrbare
* Lücke im Nummernkreis.
*
* Gegenprobe: Wird eine ältere Nummer gelöscht, bleibt der Zähler stehen –
* andernfalls würde eine bereits vergebene Nummer ein zweites Mal ausgegeben.
*/
class NummernkreisFreigabeTest extends TestCase
{
protected string $testName = 'Test 78: Nummernkreis gibt die zuletzt vergebene Nummer beim Löschen frei';
public function run(): void
{
$this->rechnungsnummerWirdFreigegeben();
$this->aeltereRechnungsnummerBleibtVerbraucht();
$this->belegnummerWirdFreigegeben();
}
// ── Rechnungen ──────────────────────────────────────────────────────────
private function rechnungsnummerWirdFreigegeben(): void
{
$vorher = $this->invoiceNrNext();
$invoiceId = $this->createRechnung('Nummernkreis-Freigabe');
$nr = \Steuerkram\Models\Invoice::finalize($invoiceId);
$this->assertNotEmpty($nr, 'Finalisieren vergibt eine Rechnungsnummer');
$this->assertEquals($vorher + 1, $this->invoiceNrNext(), 'Zähler steht nach dem Finalisieren eins weiter');
\Steuerkram\Models\Invoice::delete($invoiceId);
$this->assertEquals(
$vorher,
$this->invoiceNrNext(),
'Nach dem Löschen steht der Zähler wieder auf dem Ausgangswert'
);
// Die freigegebene Nummer muss erneut vergeben werden – sonst entstünde
// trotz zurückgesetztem Zähler eine andere Nummer.
$zweiteId = $this->createRechnung('Nummernkreis-Wiedervergabe');
$this->assertEquals($nr, \Steuerkram\Models\Invoice::finalize($zweiteId), 'Die freigegebene Nummer wird erneut vergeben');
\Steuerkram\Models\Invoice::delete($zweiteId);
}
private function aeltereRechnungsnummerBleibtVerbraucht(): void
{
$ersteId = $this->createRechnung('Nummernkreis-Alt');
\Steuerkram\Models\Invoice::finalize($ersteId);
$zweiteId = $this->createRechnung('Nummernkreis-Neu');
$zweiteNr = \Steuerkram\Models\Invoice::finalize($zweiteId);
$standMitBeiden = $this->invoiceNrNext();
// Die ältere der beiden Rechnungen löschen: ihre Nummer darf nicht
// freigegeben werden, sie liegt vor der bereits vergebenen $zweiteNr.
\Steuerkram\Models\Invoice::delete($ersteId);
$this->assertEquals(
$standMitBeiden,
$this->invoiceNrNext(),
'Das Löschen einer älteren Rechnung lässt den Zähler unverändert'
);
// Jetzt die jüngste löschen – deren Nummer wird freigegeben.
\Steuerkram\Models\Invoice::delete($zweiteId);
$this->assertEquals(
$standMitBeiden - 1,
$this->invoiceNrNext(),
'Das Löschen der jüngsten Rechnung gibt deren Nummer frei'
);
$dritteId = $this->createRechnung('Nummernkreis-Nachrueck');
$this->assertEquals(
$zweiteNr,
\Steuerkram\Models\Invoice::finalize($dritteId),
'Die freigegebene Nummer der jüngsten Rechnung rückt nach'
);
\Steuerkram\Models\Invoice::delete($dritteId);
}
// ── Belege ──────────────────────────────────────────────────────────────
private function belegnummerWirdFreigegeben(): void
{
$vorher = $this->belegNrNext();
$ersterId = $this->createBeleg('Nummernkreis-Beleg-1');
$zweiterId = $this->createBeleg('Nummernkreis-Beleg-2');
$this->assertEquals($vorher + 2, $this->belegNrNext(), 'Zwei Belege verbrauchen zwei Nummern');
// Älterer Beleg: keine Freigabe.
$this->assert(\Steuerkram\Models\Beleg::delete($ersterId), 'Älterer Beleg ist löschbar');
$this->assertEquals(
$vorher + 2,
$this->belegNrNext(),
'Das Löschen eines älteren Belegs lässt den Zähler unverändert'
);
// Jüngster Beleg: Nummer wird frei.
$this->assert(\Steuerkram\Models\Beleg::delete($zweiterId), 'Jüngster Beleg ist löschbar');
$this->assertEquals(
$vorher + 1,
$this->belegNrNext(),
'Das Löschen des jüngsten Belegs gibt dessen Nummer frei'
);
}
// ── Hilfsfunktionen ─────────────────────────────────────────────────────
private function invoiceNrNext(): int
{
$row = \Steuerkram\Database::fetchOne('SELECT invoice_nr_next FROM company_settings WHERE id=1');
return (int)($row['invoice_nr_next'] ?? 0);
}
private function belegNrNext(): int
{
$row = \Steuerkram\Database::fetchOne('SELECT beleg_nr_next FROM company_settings WHERE id=1');
return (int)($row['beleg_nr_next'] ?? 0);
}
private function createRechnung(string $marker): int
{
$customerId = \Steuerkram\Models\Customer::create([
'nachname' => 'Nummernkreis',
'firma' => $marker . ' GmbH',
'strasse' => 'Zählerweg 1',
'plz' => '12345',
'ort' => 'Nummernstadt',
'land' => 'DE',
'payment_days' => 14,
]);
return \Steuerkram\Models\Invoice::create([
'customer_id' => $customerId,
'betreff' => $marker,
'rechnungs_datum' => date('Y-m-d'),
'lines' => [
['bezeichnung' => 'Leistung', 'menge' => 1, 'einheit' => 'Std.', 'einzelpreis' => 100.00, 'ust_satz' => 19.0],
],
]);
}
/** Legt einen Beleg ohne Datei an (storage_path zeigt bewusst ins Leere). */
private function createBeleg(string $marker): int
{
$belegNr = \Steuerkram\Models\Beleg::generateBelegNr();
return \Steuerkram\Models\Beleg::create([
'beleg_nr' => $belegNr,
'filename' => $marker . '.pdf',
'storage_path' => date('Y') . '/' . $marker . '/original.pdf',
'mime_type' => 'application/pdf',
'file_hash' => hash('sha256', $belegNr . $marker),
'belegtyp' => 'Eingangsrechnung',
'beleg_datum' => date('Y-m-d'),
'notes' => $marker,
]);
}
}