'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'); } }