Fix #7: Lieferantenmanagement mit Dropdown-Auswahl an Belegen hinzufügen
This commit is contained in:
parent
7609d1e4e3
commit
29295989aa
13 changed files with 464 additions and 1 deletions
|
|
@ -37,6 +37,13 @@ $this->assertGreaterThan($min, $wert, 'Beschreibung');
|
|||
$this->assertNotEmpty($wert, 'Beschreibung');
|
||||
```
|
||||
|
||||
## Dokumentation aktuell halten
|
||||
|
||||
| Datei | Wann aktualisieren |
|
||||
|---|---|
|
||||
| `Changes.md` | Bei jedem Feature, Bugfix oder strukturellen Änderung (nicht bei reinen Test- oder Doku-Commits) |
|
||||
| `README.md` | Wenn sich der Funktionsumfang, Tech Stack oder die Installationsanleitung ändert |
|
||||
|
||||
## Commits
|
||||
```
|
||||
Fix #NR: Kurze Beschreibung der Änderung
|
||||
|
|
|
|||
18
Changes.md
18
Changes.md
|
|
@ -2,6 +2,24 @@
|
|||
|
||||
---
|
||||
|
||||
## 2026-03-26 – Lieferantenmanagement (#7)
|
||||
|
||||
### Neu
|
||||
- **Lieferantenverwaltung**: Neue Seite `/suppliers` zum Anlegen, Bearbeiten und Löschen von Lieferanten
|
||||
- **Lieferant am Beleg**: Im Metadaten-Formular der Belegdetailseite kann ein Lieferant per Dropdown/Freitexteingabe ausgewählt werden; unbekannte Namen werden beim Speichern automatisch angelegt
|
||||
- **Datenbank**: Tabelle `suppliers` (id, name) und Fremdschlüssel `supplier_id` in `belege`
|
||||
- **Navigation**: Neuer Menüpunkt "Lieferanten" im Bereich Belege & Buchungen
|
||||
|
||||
### Betrifft
|
||||
- Migration: `007_suppliers.sql`
|
||||
- Model: `Supplier.php` (neu), `Beleg.php` (JOIN + updateMeta)
|
||||
- Controller: `SupplierController.php` (neu), `BelegController.php`
|
||||
- Views: `suppliers/list.php`, `suppliers/form.php` (neu), `belege/detail.php`, `layout/nav.php`
|
||||
- Routes: `public/index.php`
|
||||
- Test: `08_LieferantTest.php`
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-26 – EÜR-Buchungsmodell (Soll/Haben → Einnahme/Ausgabe)
|
||||
|
||||
### Geändert
|
||||
|
|
|
|||
20
migrations/007_suppliers.sql
Normal file
20
migrations/007_suppliers.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
-- Steuerkram - Lieferantenmanagement
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- ============================================================
|
||||
-- Lieferanten
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS suppliers (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_supplier_name (name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Lieferant-Referenz in Belege
|
||||
ALTER TABLE belege
|
||||
ADD COLUMN supplier_id INT UNSIGNED NULL DEFAULT NULL AFTER belegtyp,
|
||||
ADD CONSTRAINT fk_belege_supplier
|
||||
FOREIGN KEY (supplier_id) REFERENCES suppliers (id) ON DELETE SET NULL;
|
||||
|
|
@ -14,6 +14,7 @@ use Steuerkram\Controllers\BelegController;
|
|||
use Steuerkram\Controllers\BookingController;
|
||||
use Steuerkram\Controllers\CustomerController;
|
||||
use Steuerkram\Controllers\InvoiceController;
|
||||
use Steuerkram\Controllers\SupplierController;
|
||||
use Steuerkram\Controllers\UstvaController;
|
||||
use Steuerkram\Controllers\EuerController;
|
||||
use Steuerkram\Controllers\SettingsController;
|
||||
|
|
@ -58,6 +59,15 @@ $router->post('/belege/{beleg_id}/bookings', fn($p) => (new BookingController())
|
|||
$router->post('/bookings/{id}/reverse', fn($p) => (new BookingController())->reverse($p));
|
||||
$router->post('/bookings/{id}/delete', fn($p) => (new BookingController())->destroy($p));
|
||||
|
||||
// ── Lieferanten ──────────────────────────────────────────────
|
||||
$router->get('/suppliers', fn($p) => (new SupplierController())->index($p));
|
||||
$router->get('/suppliers/new', fn($p) => (new SupplierController())->create($p));
|
||||
$router->post('/suppliers', fn($p) => (new SupplierController())->store($p));
|
||||
$router->get('/suppliers/search', fn($p) => (new SupplierController())->search($p));
|
||||
$router->get('/suppliers/{id}/edit', fn($p) => (new SupplierController())->edit($p));
|
||||
$router->post('/suppliers/{id}', fn($p) => (new SupplierController())->update($p));
|
||||
$router->post('/suppliers/{id}/delete', fn($p) => (new SupplierController())->destroy($p));
|
||||
|
||||
// ── Kunden ───────────────────────────────────────────────────
|
||||
$router->get('/customers', fn($p) => (new CustomerController())->index($p));
|
||||
$router->get('/customers/new', fn($p) => (new CustomerController())->create($p));
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ namespace Steuerkram\Controllers;
|
|||
use Steuerkram\Models\Beleg;
|
||||
use Steuerkram\Models\Booking;
|
||||
use Steuerkram\Models\Account;
|
||||
use Steuerkram\Models\Supplier;
|
||||
use Steuerkram\Config;
|
||||
use Steuerkram\View;
|
||||
|
||||
|
|
@ -140,12 +141,14 @@ class BelegController
|
|||
$bookings = Booking::forBeleg((int)$params['id']);
|
||||
$integrity = Beleg::verifyIntegrity((int)$params['id']);
|
||||
$accounts = Account::all(['aktiv' => 1]);
|
||||
$suppliers = Supplier::all();
|
||||
|
||||
$view = new View();
|
||||
$view->render('layout/base.php', [
|
||||
'pageTitle' => 'Beleg ' . $beleg['beleg_nr'],
|
||||
'content' => $this->renderView('belege/detail', [
|
||||
'beleg' => $beleg,
|
||||
'suppliers' => $suppliers,
|
||||
'bookings' => $bookings,
|
||||
'integrity' => $integrity,
|
||||
'accounts' => $accounts,
|
||||
|
|
@ -183,11 +186,19 @@ class BelegController
|
|||
return;
|
||||
}
|
||||
|
||||
// Lieferant: Name → ID (ggf. neu anlegen)
|
||||
$supplierId = null;
|
||||
$supplierName = trim($_POST['supplier_name'] ?? '');
|
||||
if ($supplierName !== '') {
|
||||
$supplierId = Supplier::findOrCreate($supplierName);
|
||||
}
|
||||
|
||||
Beleg::updateMeta($id, [
|
||||
'rechnungs_nr' => trim($_POST['rechnungs_nr'] ?? ''),
|
||||
'belegtyp' => $_POST['belegtyp'] ?? $beleg['belegtyp'],
|
||||
'beleg_datum' => $_POST['beleg_datum'] ?: null,
|
||||
'betrag_brutto' => ($_POST['betrag_brutto'] ?? '') !== '' ? $_POST['betrag_brutto'] : null,
|
||||
'supplier_id' => $supplierId,
|
||||
]);
|
||||
|
||||
if ($isAjax) {
|
||||
|
|
|
|||
144
src/Controllers/SupplierController.php
Normal file
144
src/Controllers/SupplierController.php
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Steuerkram\Controllers;
|
||||
|
||||
use Steuerkram\Models\Supplier;
|
||||
use Steuerkram\View;
|
||||
|
||||
class SupplierController
|
||||
{
|
||||
public function index(array $params): void
|
||||
{
|
||||
$suppliers = Supplier::all();
|
||||
|
||||
$view = new View();
|
||||
$view->render('layout/base.php', [
|
||||
'pageTitle' => 'Lieferanten',
|
||||
'content' => $this->renderView('suppliers/list', [
|
||||
'suppliers' => $suppliers,
|
||||
]),
|
||||
'topbarActions' => '<a href="/suppliers/new" class="btn btn-primary">+ Lieferant anlegen</a>',
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(array $params): void
|
||||
{
|
||||
$view = new View();
|
||||
$view->render('layout/base.php', [
|
||||
'pageTitle' => 'Lieferant anlegen',
|
||||
'content' => $this->renderView('suppliers/form', [
|
||||
'supplier' => [],
|
||||
'errors' => [],
|
||||
'action' => '/suppliers',
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(array $params): void
|
||||
{
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$errors = [];
|
||||
|
||||
if ($name === '') {
|
||||
$errors['name'] = 'Pflichtfeld';
|
||||
} elseif (Supplier::findByName($name)) {
|
||||
$errors['name'] = 'Dieser Lieferantenname existiert bereits.';
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
$view = new View();
|
||||
$view->render('layout/base.php', [
|
||||
'pageTitle' => 'Lieferant anlegen',
|
||||
'content' => $this->renderView('suppliers/form', [
|
||||
'supplier' => ['name' => $name],
|
||||
'errors' => $errors,
|
||||
'action' => '/suppliers',
|
||||
]),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
Supplier::create($name);
|
||||
View::redirect('/suppliers', 'Lieferant erfolgreich angelegt.');
|
||||
}
|
||||
|
||||
public function edit(array $params): void
|
||||
{
|
||||
$supplier = Supplier::find((int)$params['id']);
|
||||
if (!$supplier) { http_response_code(404); return; }
|
||||
|
||||
$view = new View();
|
||||
$view->render('layout/base.php', [
|
||||
'pageTitle' => 'Lieferant bearbeiten',
|
||||
'content' => $this->renderView('suppliers/form', [
|
||||
'supplier' => $supplier,
|
||||
'errors' => [],
|
||||
'action' => '/suppliers/' . $supplier['id'],
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(array $params): void
|
||||
{
|
||||
$id = (int)$params['id'];
|
||||
$supplier = Supplier::find($id);
|
||||
if (!$supplier) { http_response_code(404); return; }
|
||||
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$errors = [];
|
||||
|
||||
if ($name === '') {
|
||||
$errors['name'] = 'Pflichtfeld';
|
||||
} else {
|
||||
$existing = Supplier::findByName($name);
|
||||
if ($existing && (int)$existing['id'] !== $id) {
|
||||
$errors['name'] = 'Dieser Lieferantenname existiert bereits.';
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
$view = new View();
|
||||
$view->render('layout/base.php', [
|
||||
'pageTitle' => 'Lieferant bearbeiten',
|
||||
'content' => $this->renderView('suppliers/form', [
|
||||
'supplier' => array_merge($supplier, ['name' => $name]),
|
||||
'errors' => $errors,
|
||||
'action' => '/suppliers/' . $id,
|
||||
]),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
Supplier::update($id, $name);
|
||||
View::redirect('/suppliers', 'Lieferant aktualisiert.');
|
||||
}
|
||||
|
||||
public function destroy(array $params): void
|
||||
{
|
||||
if (!Supplier::delete((int)$params['id'])) {
|
||||
View::redirect('/suppliers', 'Lieferant kann nicht gelöscht werden (noch in Belegen verwendet).', 'error');
|
||||
return;
|
||||
}
|
||||
View::redirect('/suppliers', 'Lieferant gelöscht.');
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: Autocomplete-Liste aller Lieferanten als JSON.
|
||||
*/
|
||||
public function search(array $params): void
|
||||
{
|
||||
$suppliers = Supplier::all();
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(array_map(fn($s) => ['id' => $s['id'], 'name' => $s['name']], $suppliers));
|
||||
}
|
||||
|
||||
private function renderView(string $template, array $data): string
|
||||
{
|
||||
extract($data, EXTR_SKIP);
|
||||
ob_start();
|
||||
include dirname(__DIR__) . '/Views/' . $template . '.php';
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
|
@ -69,9 +69,11 @@ class Beleg
|
|||
(SELECT SUM(bk.betrag_brutto) FROM bookings bk
|
||||
WHERE bk.beleg_id = b.id AND bk.is_reversal = 0)
|
||||
) AS betrag_brutto,
|
||||
fy.year as fiscal_year_nr
|
||||
fy.year as fiscal_year_nr,
|
||||
s.name as supplier_name
|
||||
FROM belege b
|
||||
LEFT JOIN fiscal_years fy ON b.fiscal_year_id = fy.id
|
||||
LEFT JOIN suppliers s ON b.supplier_id = s.id
|
||||
WHERE b.id = ?',
|
||||
[$id]
|
||||
);
|
||||
|
|
@ -156,6 +158,7 @@ class Beleg
|
|||
belegtyp = ?,
|
||||
beleg_datum = ?,
|
||||
betrag_brutto = ?,
|
||||
supplier_id = ?,
|
||||
fiscal_year_id = COALESCE(?, fiscal_year_id),
|
||||
updated_at = NOW()
|
||||
WHERE id = ? AND locked = 0',
|
||||
|
|
@ -164,6 +167,7 @@ class Beleg
|
|||
$data['belegtyp'],
|
||||
$data['beleg_datum'] ?: null,
|
||||
$data['betrag_brutto'] !== null && $data['betrag_brutto'] !== '' ? (float)$data['betrag_brutto'] : null,
|
||||
isset($data['supplier_id']) && $data['supplier_id'] !== '' ? (int)$data['supplier_id'] : null,
|
||||
$fyId,
|
||||
$id,
|
||||
]
|
||||
|
|
|
|||
63
src/Models/Supplier.php
Normal file
63
src/Models/Supplier.php
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Steuerkram\Models;
|
||||
|
||||
use Steuerkram\Database;
|
||||
|
||||
class Supplier
|
||||
{
|
||||
public static function all(): array
|
||||
{
|
||||
return Database::fetchAll('SELECT * FROM suppliers ORDER BY name');
|
||||
}
|
||||
|
||||
public static function find(int $id): array|false
|
||||
{
|
||||
return Database::fetchOne('SELECT * FROM suppliers WHERE id = ?', [$id]);
|
||||
}
|
||||
|
||||
public static function findByName(string $name): array|false
|
||||
{
|
||||
return Database::fetchOne('SELECT * FROM suppliers WHERE name = ?', [trim($name)]);
|
||||
}
|
||||
|
||||
public static function create(string $name): int
|
||||
{
|
||||
Database::execute('INSERT INTO suppliers (name) VALUES (?)', [trim($name)]);
|
||||
return (int)Database::lastInsertId();
|
||||
}
|
||||
|
||||
public static function update(int $id, string $name): void
|
||||
{
|
||||
Database::execute('UPDATE suppliers SET name = ?, updated_at = NOW() WHERE id = ?', [trim($name), $id]);
|
||||
}
|
||||
|
||||
public static function delete(int $id): bool
|
||||
{
|
||||
// Prüfen ob Lieferant noch in Belegen verwendet wird
|
||||
$cnt = Database::fetchOne('SELECT COUNT(*) as cnt FROM belege WHERE supplier_id = ?', [$id]);
|
||||
if ($cnt && $cnt['cnt'] > 0) {
|
||||
return false;
|
||||
}
|
||||
Database::execute('DELETE FROM suppliers WHERE id = ?', [$id]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt ID eines vorhandenen Lieferanten zurück oder legt ihn neu an.
|
||||
*/
|
||||
public static function findOrCreate(string $name): int
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name === '') {
|
||||
throw new \InvalidArgumentException('Lieferantenname darf nicht leer sein.');
|
||||
}
|
||||
$existing = self::findByName($name);
|
||||
if ($existing) {
|
||||
return (int)$existing['id'];
|
||||
}
|
||||
return self::create($name);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ use Steuerkram\Models\Setting;
|
|||
$h = fn($v) => View::h($v);
|
||||
$fmt = fn($v) => View::formatMoney($v);
|
||||
$testMode = Setting::isTestMode();
|
||||
$suppliers = $suppliers ?? [];
|
||||
?>
|
||||
|
||||
<!-- Beleg-Info-Bar -->
|
||||
|
|
@ -23,6 +24,10 @@ $testMode = Setting::isTestMode();
|
|||
<span class="text-muted text-sm">Typ</span><br>
|
||||
<span class="badge badge-gray"><?= $h($beleg['belegtyp']) ?></span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted text-sm">Lieferant</span><br>
|
||||
<?= $beleg['supplier_name'] ? $h($beleg['supplier_name']) : '<span class="text-muted">–</span>' ?>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted text-sm">Belegdatum<?= !$beleg['locked'] ? ' <span style="opacity:.4;font-size:.7rem">✎</span>' : '' ?></span><br>
|
||||
<?php if (!$beleg['locked']): ?>
|
||||
|
|
@ -108,6 +113,19 @@ $testMode = Setting::isTestMode();
|
|||
<input type="number" name="betrag_brutto" step="0.01" min="0"
|
||||
value="<?= $h($beleg['betrag_brutto'] ?? '') ?>" placeholder="0.00">
|
||||
</div>
|
||||
<div class="form-group" style="margin:0;min-width:180px">
|
||||
<label style="font-size:.8rem">Lieferant</label>
|
||||
<input type="text" name="supplier_name" id="supplier_name_input"
|
||||
value="<?= $h($beleg['supplier_name'] ?? '') ?>"
|
||||
placeholder="Name eingeben oder wählen…"
|
||||
list="supplier-datalist"
|
||||
autocomplete="off">
|
||||
<datalist id="supplier-datalist">
|
||||
<?php foreach ($suppliers as $sup): ?>
|
||||
<option value="<?= $h($sup['name']) ?>">
|
||||
<?php endforeach; ?>
|
||||
</datalist>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm">Speichern</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm"
|
||||
onclick="document.getElementById('beleg-meta-form').style.display='none'">Abbrechen</button>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ function navActive(string $prefix, string $current): string {
|
|||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16M4 10h16M4 14h16M4 18h16"/></svg>
|
||||
Buchungsjournal
|
||||
</a>
|
||||
<a href="/suppliers" class="<?= navActive('/suppliers', $path) ?>">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 7H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/></svg>
|
||||
Lieferanten
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
24
src/Views/suppliers/form.php
Normal file
24
src/Views/suppliers/form.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php use Steuerkram\View; $h = fn($v) => View::h($v); ?>
|
||||
|
||||
<div class="card" style="max-width:480px">
|
||||
<div class="card-body">
|
||||
<form method="post" action="<?= $h($action) ?>">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Lieferantenname *</label>
|
||||
<input type="text" id="name" name="name"
|
||||
value="<?= $h($supplier['name'] ?? '') ?>"
|
||||
class="<?= isset($errors['name']) ? 'input-error' : '' ?>"
|
||||
required autofocus>
|
||||
<?php if (isset($errors['name'])): ?>
|
||||
<div class="error-text"><?= $h($errors['name']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:.75rem;margin-top:1rem">
|
||||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||||
<a href="/suppliers" class="btn btn-secondary">Abbrechen</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
45
src/Views/suppliers/list.php
Normal file
45
src/Views/suppliers/list.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php use Steuerkram\View; $h = fn($v) => View::h($v); ?>
|
||||
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<span class="text-muted text-sm"><?= count($suppliers) ?> Lieferanten</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Angelegt am</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($suppliers as $s): ?>
|
||||
<tr>
|
||||
<td class="td-mono text-sm"><?= $h($s['id']) ?></td>
|
||||
<td><?= $h($s['name']) ?></td>
|
||||
<td class="td-muted text-sm"><?= View::formatDate($s['created_at']) ?></td>
|
||||
<td>
|
||||
<div style="display:flex;gap:.5rem">
|
||||
<a href="/suppliers/<?= $h($s['id']) ?>/edit" class="btn btn-secondary btn-sm">Bearbeiten</a>
|
||||
<form method="post" action="/suppliers/<?= $h($s['id']) ?>/delete" style="margin:0"
|
||||
data-confirm="Lieferant "<?= $h($s['name']) ?>" wirklich löschen?">
|
||||
<button type="submit" class="btn btn-danger btn-sm">Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($suppliers)): ?>
|
||||
<tr><td colspan="4" class="text-center text-muted" style="padding:2rem">Noch keine Lieferanten angelegt.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
95
tests/08_LieferantTest.php
Normal file
95
tests/08_LieferantTest.php
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Test 8: Lieferantenmanagement
|
||||
*
|
||||
* Prüft, ob Lieferanten angelegt, gefunden, aktualisiert und
|
||||
* einem Beleg zugewiesen werden können.
|
||||
*/
|
||||
class LieferantTest extends TestCase
|
||||
{
|
||||
protected string $testName = 'Test 8: Lieferantenmanagement';
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
// 1. Lieferant anlegen
|
||||
$name = 'Testlieferant GmbH ' . uniqid();
|
||||
$id = \Steuerkram\Models\Supplier::create($name);
|
||||
$this->assertGreaterThan(0, $id, 'Lieferant wurde angelegt (ID > 0)');
|
||||
|
||||
// 2. find() liefert den angelegten Lieferanten
|
||||
$supplier = \Steuerkram\Models\Supplier::find($id);
|
||||
$this->assertNotEmpty($supplier, 'Supplier::find() liefert Datensatz');
|
||||
$this->assertEquals($name, $supplier['name'], 'Lieferantenname stimmt überein');
|
||||
|
||||
// 3. findByName() findet den Lieferanten
|
||||
$found = \Steuerkram\Models\Supplier::findByName($name);
|
||||
$this->assertNotEmpty($found, 'Supplier::findByName() findet den Lieferanten');
|
||||
$this->assertEquals($id, (int)$found['id'], 'ID stimmt überein');
|
||||
|
||||
// 4. all() enthält den Lieferanten
|
||||
$all = \Steuerkram\Models\Supplier::all();
|
||||
$inList = false;
|
||||
foreach ($all as $s) {
|
||||
if ((int)$s['id'] === $id) { $inList = true; break; }
|
||||
}
|
||||
$this->assert($inList, 'Lieferant erscheint in Supplier::all()');
|
||||
|
||||
// 5. findOrCreate() gibt vorhandenen zurück
|
||||
$id2 = \Steuerkram\Models\Supplier::findOrCreate($name);
|
||||
$this->assertEquals($id, $id2, 'findOrCreate() gibt vorhandene ID zurück');
|
||||
|
||||
// 6. findOrCreate() legt neuen an
|
||||
$newName = 'Neuer Lieferant ' . uniqid();
|
||||
$id3 = \Steuerkram\Models\Supplier::findOrCreate($newName);
|
||||
$this->assertGreaterThan(0, $id3, 'findOrCreate() legt neuen Lieferanten an');
|
||||
$this->assert($id3 !== $id, 'Neue ID ist verschieden von der ersten');
|
||||
|
||||
// 7. update()
|
||||
$updatedName = 'Umbenannt ' . uniqid();
|
||||
\Steuerkram\Models\Supplier::update($id, $updatedName);
|
||||
$updated = \Steuerkram\Models\Supplier::find($id);
|
||||
$this->assertEquals($updatedName, $updated['name'], 'Name nach update() korrekt geändert');
|
||||
|
||||
// 8. Lieferant einem Beleg zuweisen via Beleg::create() + updateMeta()
|
||||
$belegNr = 'TEST-SUP-' . uniqid();
|
||||
$datum = date('Y-m-d');
|
||||
$belegId = \Steuerkram\Models\Beleg::create([
|
||||
'beleg_nr' => $belegNr,
|
||||
'filename' => 'test-lieferant.pdf',
|
||||
'storage_path' => 'static/uploads/test/test-lieferant.pdf',
|
||||
'mime_type' => 'application/pdf',
|
||||
'file_hash' => hash('sha256', $belegNr),
|
||||
'beleg_datum' => $datum,
|
||||
'eingangs_datum' => $datum,
|
||||
'rechnungs_nr' => 'RE-SUP-001',
|
||||
'betrag_brutto' => 59.50,
|
||||
'belegtyp' => 'Eingangsrechnung',
|
||||
]);
|
||||
|
||||
\Steuerkram\Models\Beleg::updateMeta($belegId, [
|
||||
'rechnungs_nr' => 'RE-SUP-001',
|
||||
'belegtyp' => 'Eingangsrechnung',
|
||||
'beleg_datum' => $datum,
|
||||
'betrag_brutto' => 59.50,
|
||||
'supplier_id' => $id3,
|
||||
]);
|
||||
|
||||
$beleg = \Steuerkram\Models\Beleg::find($belegId);
|
||||
$this->assertEquals($id3, (int)$beleg['supplier_id'], 'supplier_id korrekt am Beleg gespeichert');
|
||||
$this->assertNotEmpty($beleg['supplier_name'], 'supplier_name via JOIN abrufbar');
|
||||
|
||||
// 9. Löschen: Lieferant der noch verwendet wird kann nicht gelöscht werden
|
||||
$deleted = \Steuerkram\Models\Supplier::delete($id3);
|
||||
$this->assert(!$deleted, 'Lieferant in Verwendung kann nicht gelöscht werden');
|
||||
|
||||
// 10. Nicht verwendeten Lieferanten löschen
|
||||
$unusedName = 'Ungenutzt ' . uniqid();
|
||||
$unusedId = \Steuerkram\Models\Supplier::create($unusedName);
|
||||
$deletedOk = \Steuerkram\Models\Supplier::delete($unusedId);
|
||||
$this->assert($deletedOk, 'Nicht verwendeter Lieferant kann gelöscht werden');
|
||||
$this->assert(!\Steuerkram\Models\Supplier::find($unusedId), 'Gelöschter Lieferant nicht mehr auffindbar');
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue