106 lines
4.6 KiB
PHP
106 lines
4.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Test 27: Stornorechnung erstellen via Invoice::createCancellation().
|
|
*/
|
|
class StornorechnungTest extends TestCase
|
|
{
|
|
protected string $testName = 'Test 27: Stornorechnung erstellen';
|
|
|
|
public function run(): void
|
|
{
|
|
// Testkunden anlegen
|
|
$customerId = \Steuerkram\Models\Customer::create([
|
|
'nachname' => 'StornoTester',
|
|
'vorname' => 'Test',
|
|
'firma' => 'Storno GmbH',
|
|
'strasse' => 'Testweg 1',
|
|
'plz' => '10115',
|
|
'ort' => 'Berlin',
|
|
'land' => 'DE',
|
|
'payment_days' => 14,
|
|
]);
|
|
|
|
// Originalrechnung erstellen und finalisieren
|
|
$originalId = \Steuerkram\Models\Invoice::create([
|
|
'customer_id' => $customerId,
|
|
'betreff' => 'Testrechnung für Storno',
|
|
'rechnungs_datum' => date('Y-m-d'),
|
|
'lines' => [
|
|
[
|
|
'bezeichnung' => 'Beratung',
|
|
'menge' => 2,
|
|
'einheit' => 'Std.',
|
|
'einzelpreis' => 100.00,
|
|
'ust_satz' => 19.0,
|
|
],
|
|
[
|
|
'bezeichnung' => 'Reisekosten',
|
|
'menge' => 1,
|
|
'einheit' => 'Pauschal',
|
|
'einzelpreis' => 50.00,
|
|
'ust_satz' => 19.0,
|
|
],
|
|
],
|
|
]);
|
|
|
|
\Steuerkram\Models\Invoice::finalize($originalId);
|
|
$original = \Steuerkram\Models\Invoice::find($originalId);
|
|
|
|
$this->assertNotEmpty($original['rechnungs_nr'], 'Original hat Rechnungsnummer');
|
|
$this->assertEquals('Finalisiert', $original['status'], 'Original ist finalisiert');
|
|
$this->assertEquals(250.00, round((float)$original['betrag_netto'], 2), 'Original Nettobetrag korrekt');
|
|
|
|
// Stornorechnung erstellen
|
|
$stornoId = \Steuerkram\Models\Invoice::createCancellation($originalId);
|
|
$this->assertGreaterThan(0, $stornoId, 'createCancellation gibt gültige ID zurück');
|
|
|
|
// Stornorechnung prüfen
|
|
$storno = \Steuerkram\Models\Invoice::find($stornoId);
|
|
$this->assertNotEmpty($storno, 'Stornorechnung gefunden');
|
|
$this->assertEquals($originalId, (int)$storno['storno_of_id'], 'storno_of_id zeigt auf Original');
|
|
$this->assertEquals('Finalisiert', $storno['status'], 'Stornorechnung ist sofort finalisiert');
|
|
$this->assertNotEmpty($storno['rechnungs_nr'], 'Stornorechnung hat Rechnungsnummer');
|
|
$this->assert($original['rechnungs_nr'] !== $storno['rechnungs_nr'], 'Rechnungsnummern sind verschieden');
|
|
|
|
// Beträge sind negiert
|
|
$this->assertEquals(-250.00, round((float)$storno['betrag_netto'], 2), 'Nettobetrag negiert');
|
|
$this->assertEquals(-47.50, round((float)$storno['betrag_ust'], 2), 'USt-Betrag negiert');
|
|
$this->assertEquals(-297.50, round((float)$storno['betrag_brutto'], 2), 'Bruttobetrag negiert');
|
|
|
|
// Originalrechnung ist storniert
|
|
$originalNach = \Steuerkram\Models\Invoice::find($originalId);
|
|
$this->assertEquals('Storniert', $originalNach['status'], 'Originalrechnung hat Status Storniert');
|
|
|
|
// Positionen der Stornorechnung sind negiert
|
|
$stornoLines = \Steuerkram\Models\Invoice::lines($stornoId);
|
|
$this->assertEquals(2, count($stornoLines), 'Stornorechnung hat 2 Positionen');
|
|
foreach ($stornoLines as $line) {
|
|
if (($line['type'] ?? 'position') === 'position') {
|
|
$this->assert((float)$line['menge'] > 0, 'Menge bleibt positiv');
|
|
$this->assert((float)$line['einzelpreis'] < 0, 'Einzelpreis ist negativ');
|
|
}
|
|
}
|
|
|
|
// Stornobuchungen müssen angelegt worden sein (Original hatte beleg_id nach finalize)
|
|
$originalMitBeleg = \Steuerkram\Models\Invoice::find($originalId);
|
|
if (!empty($originalMitBeleg['beleg_id'])) {
|
|
$bookings = \Steuerkram\Database::fetchAll(
|
|
'SELECT id FROM bookings WHERE beleg_id = ? AND is_reversal = 1',
|
|
[(int)$originalMitBeleg['beleg_id']]
|
|
);
|
|
$this->assertGreaterThan(0, count($bookings), 'Stornobuchungen wurden angelegt');
|
|
}
|
|
|
|
// Erneutes Stornieren der Stornorechnung muss fehlschlagen
|
|
$exception = false;
|
|
try {
|
|
\Steuerkram\Models\Invoice::createCancellation($stornoId);
|
|
} catch (\RuntimeException $e) {
|
|
$exception = true;
|
|
}
|
|
$this->assert($exception, 'Stornorechnung kann nicht erneut storniert werden');
|
|
}
|
|
}
|