478 lines
22 KiB
PHP
478 lines
22 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use Steuerkram\Database;
|
||
use Steuerkram\Models\Account;
|
||
use Steuerkram\Models\BankSource;
|
||
use Steuerkram\Models\BankTransaction;
|
||
use Steuerkram\Models\Beleg;
|
||
use Steuerkram\Models\Booking;
|
||
use Steuerkram\Models\Customer;
|
||
use Steuerkram\Models\FiscalYear;
|
||
use Steuerkram\Models\Invoice;
|
||
use Steuerkram\Models\Supplier;
|
||
use Steuerkram\Models\UstvaSubmission;
|
||
|
||
/**
|
||
* Test 85: Aus Controllern verschobene Modell-Abfragen (Fix #156).
|
||
*
|
||
* 54 direkte Database::-Aufrufe in Controllern verletzten die Modellschicht
|
||
* und waren nur über einen vollständigen HTTP-Request testbar. Dieser Test
|
||
* deckt die dabei neu entstandenen bzw. konsolidierten Modellmethoden ab –
|
||
* mit Schwerpunkt auf der bislang völlig ungetesteten Kennzahlenberechnung
|
||
* Booking::dashboardKennzahlen(). Ein Regressionsschutz am Ende stellt
|
||
* sicher, dass kein Controller wieder direkt auf Database:: zugreift.
|
||
*/
|
||
class ModellAbfragenTest extends TestCase
|
||
{
|
||
protected string $testName = 'Test 85: Aus Controllern verschobene Modell-Abfragen (Fix #156)';
|
||
|
||
private int $zahlungsId;
|
||
private int $kategorieId;
|
||
private int $belegSeq = 0;
|
||
|
||
private ?int $offenerBelegId = null;
|
||
private ?int $recentBookingId = null;
|
||
|
||
public function run(): void
|
||
{
|
||
$this->zahlungsId = (int)$this->findAccountId('1200');
|
||
$this->kategorieId = (int)$this->findAccountId('8400');
|
||
|
||
if (!$this->zahlungsId || !$this->kategorieId) {
|
||
$this->assert(false, 'Konten 1200/8400 nicht gefunden – SKR03-Seed vorhanden?');
|
||
return;
|
||
}
|
||
|
||
$this->testDashboardKennzahlen();
|
||
$this->testRecent();
|
||
$this->testJournalForAccount();
|
||
$this->testFiscalYearRegisteredYears();
|
||
$this->testBewirtungssplit();
|
||
$this->testDeleteHard();
|
||
$this->testInvoiceQuerverweise();
|
||
$this->testInvoiceFinalizeWithBeleg();
|
||
$this->testBelegUpdates();
|
||
$this->testUstvaSubmission();
|
||
$this->testBankImportBatch();
|
||
$this->testKeineDirektenDatenbankaufrufeInControllern();
|
||
}
|
||
|
||
private function testDashboardKennzahlen(): void
|
||
{
|
||
$vorher = Booking::dashboardKennzahlen();
|
||
|
||
$customerId = Customer::create([
|
||
'nachname' => 'Kennzahlen',
|
||
'firma' => 'Kennzahlen GmbH',
|
||
'strasse' => 'Teststraße 1',
|
||
'plz' => '10115',
|
||
'ort' => 'Berlin',
|
||
'payment_days' => 14,
|
||
]);
|
||
$invoiceId = Invoice::create([
|
||
'customer_id' => $customerId,
|
||
'betreff' => 'Testrechnung Dashboard-Kennzahlen',
|
||
'rechnungs_datum' => date('Y-m-d'),
|
||
'lines' => [[
|
||
'bezeichnung' => 'Beratung',
|
||
'menge' => 1,
|
||
'einheit' => 'Std.',
|
||
'einzelpreis' => 500.00,
|
||
'ust_satz' => 19.0,
|
||
]],
|
||
]);
|
||
Invoice::finalize($invoiceId);
|
||
$invoice = Invoice::find($invoiceId);
|
||
|
||
$belegId = $this->newBeleg('OFFEN', $invoice['rechnungs_datum'], $invoice['rechnungs_nr'], (float)$invoice['betrag_brutto']);
|
||
Invoice::setBelegId($invoiceId, $belegId);
|
||
Invoice::markSent($invoiceId, 'Finalisiert');
|
||
|
||
// Zweiter, völlig unabhängiger Beleg ohne jede Buchung.
|
||
$ungebuchterBelegId = $this->newBeleg('UNGEBUCHT', date('Y-m-d'), null, 42.00);
|
||
|
||
$nachher = Booking::dashboardKennzahlen();
|
||
|
||
$this->assertEquals(
|
||
$vorher['offene_rechnungen'] + 1,
|
||
$nachher['offene_rechnungen'],
|
||
'dashboardKennzahlen(): offene_rechnungen zählt die neue unbezahlte Rechnung'
|
||
);
|
||
$this->assertEquals(
|
||
round($vorher['offene_rechnungen_sum'] + (float)$invoice['betrag_brutto'], 2),
|
||
round($nachher['offene_rechnungen_sum'], 2),
|
||
'dashboardKennzahlen(): offene_rechnungen_sum enthält den vollen Rechnungsbetrag (keine Zahlung erfasst)'
|
||
);
|
||
$this->assertEquals(
|
||
$vorher['ungebuchte_belege'] + 2,
|
||
$nachher['ungebuchte_belege'],
|
||
'dashboardKennzahlen(): ungebuchte_belege zählt Rechnungs- und Einzelbeleg (beide ohne Buchung)'
|
||
);
|
||
|
||
// Für nachfolgende Tests weiterreichen.
|
||
$this->offenerBelegId = $belegId;
|
||
}
|
||
|
||
private function testRecent(): void
|
||
{
|
||
// ust_satz 0, damit Booking::create() keine zusätzliche USt-Kindbuchung
|
||
// anlegt – sonst wäre nicht eindeutig, welche der beiden Zeilen jünger ist.
|
||
$bookingId = Booking::create([
|
||
'beleg_id' => $this->offenerBelegId,
|
||
'buchungs_datum' => date('Y-m-d'),
|
||
'buchungstyp' => 'Einnahme',
|
||
'kategorie_konto_id' => $this->kategorieId,
|
||
'zahlungskonto_id' => $this->zahlungsId,
|
||
'betrag_netto' => 100.00,
|
||
'ust_satz' => 0,
|
||
'buchungstext' => 'Test recent() Buchung',
|
||
]);
|
||
|
||
$recentOne = Booking::recent(1);
|
||
$this->assertEquals(1, count($recentOne), 'recent(1) liefert genau einen Datensatz');
|
||
$this->assertEquals($bookingId, (int)$recentOne[0]['id'], 'recent(1) liefert die zuletzt angelegte Buchung');
|
||
$this->assertNotEmpty($recentOne[0]['kategorie_nr'] ?? null, 'recent() liefert die Kategorie-Kontonummer mit');
|
||
$this->assertNotEmpty($recentOne[0]['beleg_nr'] ?? null, 'recent() liefert die Belegnummer mit');
|
||
|
||
$recentMany = Booking::recent(50);
|
||
$ids = array_column($recentMany, 'id');
|
||
$this->assert(in_array($bookingId, $ids, true), 'recent(50) enthält die soeben angelegte Buchung');
|
||
|
||
$this->recentBookingId = $bookingId;
|
||
}
|
||
|
||
private function testJournalForAccount(): void
|
||
{
|
||
$year = (int)date('Y');
|
||
|
||
$rows = Booking::journalForAccount($this->kategorieId, $year);
|
||
$ids = array_column($rows, 'id');
|
||
$this->assert(
|
||
in_array($this->recentBookingId, $ids, true),
|
||
'journalForAccount() findet die Buchung über das Kategorie-Konto'
|
||
);
|
||
|
||
$rowsZahlung = Booking::journalForAccount($this->zahlungsId, $year);
|
||
$idsZahlung = array_column($rowsZahlung, 'id');
|
||
$this->assert(
|
||
in_array($this->recentBookingId, $idsZahlung, true),
|
||
'journalForAccount() findet dieselbe Buchung auch über das Zahlungskonto'
|
||
);
|
||
|
||
$rowsAndererJahr = Booking::journalForAccount($this->kategorieId, $year - 50);
|
||
$idsAndererJahr = array_column($rowsAndererJahr, 'id');
|
||
$this->assert(
|
||
!in_array($this->recentBookingId, $idsAndererJahr, true),
|
||
'journalForAccount() filtert korrekt nach Geschäftsjahr'
|
||
);
|
||
}
|
||
|
||
private function testFiscalYearRegisteredYears(): void
|
||
{
|
||
$exotischesJahr = 2091;
|
||
FiscalYear::findOrCreateByYear($exotischesJahr);
|
||
|
||
$jahre = array_column(FiscalYear::registeredYears(), 'year');
|
||
$this->assert(
|
||
in_array($exotischesJahr, $jahre, true),
|
||
'registeredYears() liefert ein frisch angelegtes Geschäftsjahr'
|
||
);
|
||
}
|
||
|
||
private function testBewirtungssplit(): void
|
||
{
|
||
$belegId = $this->newBeleg('BEWIRTUNG', date('Y-m-d'), null, 119.00);
|
||
|
||
$data = [
|
||
'beleg_id' => $belegId,
|
||
'buchungs_datum' => date('Y-m-d'),
|
||
'buchungstyp' => 'Ausgabe',
|
||
'kategorie_konto_id' => (int)$this->findAccountId('4650'),
|
||
'zahlungskonto_id' => $this->zahlungsId,
|
||
'betrag_netto' => 100.00,
|
||
'ust_satz' => 19.0,
|
||
'buchungstext' => 'Testessen Bewirtung',
|
||
];
|
||
|
||
Booking::bucheBewirtungssplit($data, '4650');
|
||
|
||
$hauptbuchungen = array_values(array_filter(
|
||
Booking::forBeleg($belegId),
|
||
fn(array $b): bool => $b['parent_booking_id'] === null
|
||
));
|
||
$this->assertEquals(2, count($hauptbuchungen), 'bucheBewirtungssplit() legt zwei Hauptbuchungen an');
|
||
|
||
$konto4650Id = (int)$this->findAccountId('4650');
|
||
$konto4654Id = (int)$this->findAccountId('4654');
|
||
|
||
$b70 = null;
|
||
$b30 = null;
|
||
foreach ($hauptbuchungen as $b) {
|
||
if ((int)$b['kategorie_konto_id'] === $konto4650Id) $b70 = $b;
|
||
if ((int)$b['kategorie_konto_id'] === $konto4654Id) $b30 = $b;
|
||
}
|
||
|
||
$this->assertNotEmpty($b70, '70 %-Buchung liegt auf dem Bewirtungskonto 4650');
|
||
$this->assertNotEmpty($b30, '30 %-Buchung liegt auf dem Abzugskonto 4654');
|
||
$this->assertEquals(70.00, (float)($b70['betrag_netto'] ?? 0), '70 % Netto auf dem Bewirtungskonto');
|
||
$this->assertEquals(30.00, (float)($b30['betrag_netto'] ?? 0), '30 % Netto auf dem Abzugskonto');
|
||
}
|
||
|
||
private function testDeleteHard(): void
|
||
{
|
||
$belegId = $this->newBeleg('DELETEHARD', date('Y-m-d'), null, 119.00);
|
||
|
||
$bookingId = Booking::create([
|
||
'beleg_id' => $belegId,
|
||
'buchungs_datum' => date('Y-m-d'),
|
||
'buchungstyp' => 'Einnahme',
|
||
'kategorie_konto_id' => $this->kategorieId,
|
||
'zahlungskonto_id' => $this->zahlungsId,
|
||
'betrag_netto' => 100.00,
|
||
'ust_satz' => 19.0,
|
||
'buchungstext' => 'Test deleteHard() Buchung',
|
||
]);
|
||
|
||
// 19 % USt erzeugt automatisch eine Kindbuchung (USt-Zeile).
|
||
$kinder = Database::fetchAll('SELECT id FROM bookings WHERE parent_booking_id = ?', [$bookingId]);
|
||
$this->assertGreaterThan(0, count($kinder), 'Voraussetzung: Hauptbuchung hat eine Kindbuchung');
|
||
|
||
Booking::deleteHard($bookingId);
|
||
|
||
$this->assert(Booking::find($bookingId) === false, 'deleteHard() entfernt die Hauptbuchung');
|
||
$verbleibendeKinder = Database::fetchAll('SELECT id FROM bookings WHERE parent_booking_id = ?', [$bookingId]);
|
||
$this->assertEquals(0, count($verbleibendeKinder), 'deleteHard() entfernt auch die Kindbuchung');
|
||
}
|
||
|
||
private function testInvoiceQuerverweise(): void
|
||
{
|
||
$customerId = Customer::create([
|
||
'nachname' => 'Querverweis',
|
||
'firma' => 'Querverweis GmbH',
|
||
'strasse' => 'Teststraße 2',
|
||
'plz' => '10115',
|
||
'ort' => 'Berlin',
|
||
'payment_days' => 14,
|
||
]);
|
||
$invoiceId = Invoice::create([
|
||
'customer_id' => $customerId,
|
||
'betreff' => 'Testrechnung Querverweis',
|
||
'rechnungs_datum' => date('Y-m-d'),
|
||
'lines' => [[
|
||
'bezeichnung' => 'Beratung',
|
||
'menge' => 1,
|
||
'einheit' => 'Std.',
|
||
'einzelpreis' => 200.00,
|
||
'ust_satz' => 19.0,
|
||
]],
|
||
]);
|
||
Invoice::finalize($invoiceId);
|
||
$invoice = Invoice::find($invoiceId);
|
||
$belegId = $this->newBeleg('QUERVERWEIS', $invoice['rechnungs_datum'], $invoice['rechnungs_nr'], (float)$invoice['betrag_brutto']);
|
||
Invoice::setBelegId($invoiceId, $belegId);
|
||
|
||
$stornoId = Invoice::createCancellation($invoiceId);
|
||
|
||
$storno = Invoice::findStorno($invoiceId);
|
||
$this->assertEquals($stornoId, (int)($storno['id'] ?? 0), 'findStorno() liefert die Stornorechnung zur Originalrechnung');
|
||
|
||
$ref = Invoice::findReference($invoiceId);
|
||
$this->assertEquals($invoice['rechnungs_nr'], $ref['rechnungs_nr'] ?? null, 'findReference() liefert die Rechnungsnummer');
|
||
$this->assertEquals($invoice['rechnungs_datum'], $ref['rechnungs_datum'] ?? null, 'findReference() liefert das Rechnungsdatum');
|
||
|
||
// deleteLine()
|
||
$lineInvoiceId = Invoice::create([
|
||
'customer_id' => $customerId,
|
||
'betreff' => 'Testrechnung mit zwei Positionen',
|
||
'rechnungs_datum' => date('Y-m-d'),
|
||
'lines' => [
|
||
['bezeichnung' => 'Position A', 'menge' => 1, 'einheit' => 'Stk.', 'einzelpreis' => 10.00, 'ust_satz' => 19.0],
|
||
['bezeichnung' => 'Position B', 'menge' => 1, 'einheit' => 'Stk.', 'einzelpreis' => 20.00, 'ust_satz' => 19.0],
|
||
],
|
||
]);
|
||
$lines = Invoice::lines($lineInvoiceId);
|
||
$this->assertEquals(2, count($lines), 'Voraussetzung: Rechnung hat zwei Positionen');
|
||
Invoice::deleteLine($lineInvoiceId, (int)$lines[0]['id']);
|
||
$this->assertEquals(1, count(Invoice::lines($lineInvoiceId)), 'deleteLine() entfernt genau eine Position');
|
||
|
||
// forCustomer()
|
||
$kundenRechnungen = array_column(Invoice::forCustomer($customerId), 'id');
|
||
$this->assert(in_array($invoiceId, $kundenRechnungen, true), 'forCustomer() liefert die Rechnungen des Kunden');
|
||
|
||
// markSent()
|
||
$markInvoiceId = Invoice::create([
|
||
'customer_id' => $customerId,
|
||
'betreff' => 'Testrechnung markSent',
|
||
'rechnungs_datum' => date('Y-m-d'),
|
||
'lines' => [['bezeichnung' => 'Beratung', 'menge' => 1, 'einheit' => 'Std.', 'einzelpreis' => 50.00, 'ust_satz' => 19.0]],
|
||
]);
|
||
Invoice::finalize($markInvoiceId);
|
||
Invoice::markSent($markInvoiceId, 'Finalisiert');
|
||
$markInvoice = Invoice::find($markInvoiceId);
|
||
$this->assertEquals('Gesendet', $markInvoice['status'], 'markSent() setzt den Status bei der ersten Sendung auf Gesendet');
|
||
$this->assertNotEmpty($markInvoice['sent_at'], 'markSent() setzt sent_at');
|
||
|
||
$sentAtErsteSendung = $markInvoice['sent_at'];
|
||
Invoice::markSent($markInvoiceId, 'Gesendet');
|
||
$markInvoiceErneut = Invoice::find($markInvoiceId);
|
||
$this->assertEquals('Gesendet', $markInvoiceErneut['status'], 'markSent() ändert den Status beim erneuten Versand nicht');
|
||
$this->assertEquals($sentAtErsteSendung, $markInvoiceErneut['sent_at'], 'markSent() überschreibt sent_at beim erneuten Versand nicht');
|
||
}
|
||
|
||
private function testInvoiceFinalizeWithBeleg(): void
|
||
{
|
||
$customerId = Customer::create([
|
||
'nachname' => 'Finalisierung',
|
||
'firma' => 'Finalisierung GmbH',
|
||
'strasse' => 'Teststraße 3',
|
||
'plz' => '10115',
|
||
'ort' => 'Berlin',
|
||
'payment_days' => 14,
|
||
]);
|
||
|
||
$invoiceId = Invoice::create([
|
||
'customer_id' => $customerId,
|
||
'betreff' => 'Testrechnung finalizeWithBeleg',
|
||
'rechnungs_datum' => date('Y-m-d'),
|
||
'lines' => [['bezeichnung' => 'Beratung', 'menge' => 1, 'einheit' => 'Std.', 'einzelpreis' => 80.00, 'ust_satz' => 19.0]],
|
||
]);
|
||
|
||
[$nr, $belegNr] = Invoice::finalizeWithBeleg($invoiceId, function (array $invoice): string {
|
||
return 'TESTBELEG-' . $invoice['id'];
|
||
});
|
||
|
||
$this->assertNotEmpty($nr, 'finalizeWithBeleg() liefert die vergebene Rechnungsnummer');
|
||
$this->assertEquals('TESTBELEG-' . $invoiceId, $belegNr, 'finalizeWithBeleg() liefert den Rückgabewert des Beleg-Callbacks');
|
||
$this->assertEquals('Finalisiert', Invoice::find($invoiceId)['status'], 'Rechnung ist nach finalizeWithBeleg() finalisiert');
|
||
}
|
||
|
||
private function testBelegUpdates(): void
|
||
{
|
||
$belegId = $this->newBeleg('UPDATES', date('Y-m-d'), null, 50.00);
|
||
|
||
Beleg::updateBewirtung($belegId, 'Geschäftsessen', 'Person A, Person B');
|
||
$beleg = Beleg::find($belegId);
|
||
$this->assertEquals('Geschäftsessen', $beleg['bewirtungsgrund'], 'updateBewirtung() setzt bewirtungsgrund');
|
||
$this->assertEquals('Person A, Person B', $beleg['teilnehmerliste'], 'updateBewirtung() setzt teilnehmerliste');
|
||
|
||
Beleg::updateEigenanteil30($belegId, 1);
|
||
$this->assertEquals(1, (int)Beleg::find($belegId)['eigenanteil_30'], 'updateEigenanteil30() setzt das Flag');
|
||
|
||
// Beleg::find() überschreibt betrag_brutto mit einer SUM-Subquery über
|
||
// gebuchte Zahlungen (0 ohne Buchungen) – die rohe Spalte daher direkt prüfen.
|
||
Beleg::updateBetragBrutto($belegId, 123.45);
|
||
$rohwert = Database::fetchOne('SELECT betrag_brutto FROM belege WHERE id = ?', [$belegId]);
|
||
$this->assertEquals(123.45, (float)($rohwert['betrag_brutto'] ?? 0), 'updateBetragBrutto() aktualisiert den Betrag');
|
||
|
||
$hash = hash('sha256', 'test-inhalt-' . $belegId);
|
||
Beleg::updateStorage($belegId, '2026/TEST/original.pdf', 'Testdatei.pdf', $hash);
|
||
$belegNachUpdate = Beleg::find($belegId);
|
||
$this->assertEquals('2026/TEST/original.pdf', $belegNachUpdate['storage_path'], 'updateStorage() aktualisiert storage_path');
|
||
$this->assertEquals('Testdatei.pdf', $belegNachUpdate['filename'], 'updateStorage() aktualisiert filename');
|
||
$this->assertEquals($hash, $belegNachUpdate['file_hash'], 'updateStorage() aktualisiert file_hash');
|
||
|
||
$supplierId = Supplier::findOrCreate('Testlieferant Modellabfragen');
|
||
Beleg::setSupplierId($belegId, $supplierId);
|
||
$this->assertEquals($supplierId, (int)Beleg::find($belegId)['supplier_id'], 'setSupplierId() setzt den Lieferanten ohne locked-Prüfung');
|
||
}
|
||
|
||
private function testUstvaSubmission(): void
|
||
{
|
||
$year = 2077;
|
||
$month = 6;
|
||
|
||
$this->assert(UstvaSubmission::find($year, $month) === false, 'Vor dem Speichern existiert noch keine Übermittlung');
|
||
|
||
UstvaSubmission::upsert($year, $month, '<xml>erster-stand</xml>', ['81' => ['wert' => 100]]);
|
||
$ersterStand = UstvaSubmission::find($year, $month);
|
||
$this->assertEquals('<xml>erster-stand</xml>', $ersterStand['xml_content'], 'upsert() legt beim ersten Aufruf einen Datensatz an');
|
||
|
||
UstvaSubmission::upsert($year, $month, '<xml>zweiter-stand</xml>', ['81' => ['wert' => 200]]);
|
||
$zweiterStand = UstvaSubmission::find($year, $month);
|
||
$this->assertEquals('<xml>zweiter-stand</xml>', $zweiterStand['xml_content'], 'upsert() überschreibt beim zweiten Aufruf den Inhalt');
|
||
$this->assertEquals((int)$ersterStand['id'], (int)$zweiterStand['id'], 'upsert() legt beim zweiten Aufruf keinen zweiten Datensatz an');
|
||
|
||
$alleIds = array_column(UstvaSubmission::all(), 'id');
|
||
$this->assert(in_array((int)$zweiterStand['id'], $alleIds, true), 'all() enthält die gespeicherte Übermittlung');
|
||
}
|
||
|
||
private function testBankImportBatch(): void
|
||
{
|
||
$sourceId = BankSource::create([
|
||
'type' => 'csv',
|
||
'bezeichnung' => 'Test Modellabfragen CSV',
|
||
'kuerzel' => 'TMA',
|
||
'farbe' => '#123456',
|
||
'skip_rows' => 0,
|
||
'has_header_row' => 1,
|
||
'delimiter' => ';',
|
||
'enclosure' => '"',
|
||
'col_buchungstag' => 0,
|
||
'col_wertstellung' => 1,
|
||
'col_vorgang' => 2,
|
||
'col_buchungstext' => 3,
|
||
'col_wert' => 4,
|
||
]);
|
||
|
||
$rows = [
|
||
['buchungstag' => date('Y-m-d'), 'wertstellung' => date('Y-m-d'), 'vorgang' => 'Gutschrift', 'buchungstext' => 'Modellabfragen Testzeile 1', 'wert' => '10.00'],
|
||
['buchungstag' => date('Y-m-d'), 'wertstellung' => date('Y-m-d'), 'vorgang' => 'Gutschrift', 'buchungstext' => 'Modellabfragen Testzeile 2', 'wert' => '20.00'],
|
||
];
|
||
|
||
$batchId = BankTransaction::importBatch($sourceId, 'modellabfragen-test.csv', $rows);
|
||
$this->assertGreaterThan(0, $batchId, 'importBatch() legt einen Import-Batch an');
|
||
|
||
$importiert = Database::fetchAll('SELECT * FROM bank_transactions WHERE batch_id = ? ORDER BY id', [$batchId]);
|
||
$this->assertEquals(2, count($importiert), 'importBatch() legt beide Zeilen im selben Batch an');
|
||
$this->assertEquals('Modellabfragen Testzeile 1', $importiert[0]['buchungstext'] ?? null, 'Erste Zeile wird unverändert übernommen');
|
||
$this->assertEquals('20.00', $importiert[1]['wert'] ?? null, 'Zweite Zeile wird unverändert übernommen');
|
||
}
|
||
|
||
/**
|
||
* Regressionsschutz: kein Controller darf wieder direkt auf Database:: zugreifen.
|
||
*
|
||
* Ausnahme AssetController.php: dort verbleiben vier Aufrufe außerhalb des
|
||
* AfA-Umfangs von Fix #155 (Anlage-Verknüpfung einer Buchung, vorheriges
|
||
* AfA-Konto, harte Löschung im Testmodus) – laut Issue #156 nicht Teil
|
||
* dieses Tickets.
|
||
*/
|
||
private function testKeineDirektenDatenbankaufrufeInControllern(): void
|
||
{
|
||
$verzeichnis = dirname(__DIR__) . '/src/Controllers';
|
||
$ausnahmen = ['AssetController.php'];
|
||
|
||
$treffer = [];
|
||
foreach (glob($verzeichnis . '/*.php') ?: [] as $datei) {
|
||
if (in_array(basename($datei), $ausnahmen, true)) continue;
|
||
$inhalt = file_get_contents($datei);
|
||
if ($inhalt !== false && str_contains($inhalt, 'Database::')) {
|
||
$treffer[] = basename($datei);
|
||
}
|
||
}
|
||
|
||
$this->assert(
|
||
empty($treffer),
|
||
empty($treffer)
|
||
? 'Kein Controller (außer den dokumentierten Ausnahmen) ruft Database:: direkt auf'
|
||
: 'Unerwartete Database::-Aufrufe in: ' . implode(', ', $treffer)
|
||
);
|
||
}
|
||
|
||
private function newBeleg(string $suffix, string $belegDatum, ?string $rechnungsNr, float $betragBrutto): int
|
||
{
|
||
$this->belegSeq++;
|
||
return Beleg::create([
|
||
'beleg_nr' => Beleg::generateBelegNr(),
|
||
'filename' => "modellabfragen-{$suffix}-{$this->belegSeq}.pdf",
|
||
'storage_path' => '',
|
||
'mime_type' => 'application/pdf',
|
||
'file_hash' => hash('sha256', $suffix . $this->belegSeq),
|
||
'beleg_datum' => $belegDatum,
|
||
'rechnungs_nr' => $rechnungsNr,
|
||
'betrag_brutto' => $betragBrutto,
|
||
'belegtyp' => $rechnungsNr !== null ? 'Ausgangsrechnung' : 'Eingangsrechnung',
|
||
]);
|
||
}
|
||
}
|