Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
use OpenNIT\Assets\Valuation;
/**
* Deckt Nutzungsdauer, Abschreibung und Restwert im Inventar ab.
*
* Drei Punkte sind hier bindend: Die drei Angaben lassen sich getrennt
* zuschalten (die Nutzungsdauer insbesondere ohne jeden Betrag), ohne
* Grundlage entsteht kein geschätzter Wert, und die Summe aus Restwert und
* bereits abgeschriebenem Anteil bleibt der Anschaffungswert.
*/
return static function (): void {
// ----------------------------------------------------------------
// Schalter
// ----------------------------------------------------------------
test('Ohne Einstellung ist keine der drei Angaben in Gebrauch', function () {
$v = Valuation::ausWerten([]);
assertFalse($v->nutzungsdauer);
assertFalse($v->abschreibung);
assertFalse($v->restwert);
assertFalse($v->aktiv());
assertFalse($v->brauchtNutzungsdauer());
assertFalse($v->brauchtAnschaffungswert());
});
test('Die Nutzungsdauer steht allein, ohne Anschaffungswert', function () {
$v = Valuation::ausWerten(['assets_useful_life_enabled' => '1']);
assertTrue($v->nutzungsdauer);
assertFalse($v->abschreibung);
assertFalse($v->restwert);
assertTrue($v->aktiv());
assertTrue($v->brauchtNutzungsdauer());
// Für eine reine Ersatzplanung wird kein Betrag verlangt.
assertFalse($v->brauchtAnschaffungswert());
});
test('Abschreibung und Restwert brauchen einander nicht', function () {
$nurAfa = Valuation::ausWerten(['assets_depreciation_enabled' => '1']);
assertTrue($nurAfa->abschreibung);
assertFalse($nurAfa->restwert);
assertTrue($nurAfa->brauchtAnschaffungswert());
assertTrue($nurAfa->brauchtNutzungsdauer());
$nurRest = Valuation::ausWerten(['assets_residual_value_enabled' => '1']);
assertTrue($nurRest->restwert);
assertFalse($nurRest->abschreibung);
assertTrue($nurRest->brauchtAnschaffungswert());
assertTrue($nurRest->brauchtNutzungsdauer());
});
test('Ein unbekannter Rechenweg fällt auf die monatsgenaue Methode zurück', function () {
assertSame(
Valuation::METHODE_MONATLICH,
Valuation::ausWerten(['assets_depreciation_method' => 'degressiv'])->methode
);
assertSame(
Valuation::METHODE_JAEHRLICH,
Valuation::ausWerten(['assets_depreciation_method' => 'yearly'])->methode
);
});
// ----------------------------------------------------------------
// Fehlende Grundlagen
// ----------------------------------------------------------------
$alle = new Valuation(true, true, true);
test('Ohne Kaufdatum bleibt alles leer', function () use ($alle) {
$w = $alle->berechnen(null, 1200.0, 36, '2026-09-17');
assertSame(null, $w['ende']);
assertSame(null, $w['restwert']);
assertSame(null, $w['abgeschrieben']);
// Der eingegebene Betrag wird trotzdem zurückgegeben.
assertSame(1200.0, $w['anschaffungswert']);
});
test('Ohne Nutzungsdauer bleibt alles leer', function () use ($alle) {
$w = $alle->berechnen('2024-03-15', 1200.0, null, '2026-09-17');
assertSame(null, $w['ende']);
assertSame(null, $w['restwert']);
});
test('Ohne Betrag entsteht trotzdem eine Frist', function () use ($alle) {
$w = $alle->berechnen('2024-03-15', null, 36, '2026-09-17');
assertSame('2027-03-15', $w['ende']);
assertSame(5, $w['monate_rest']);
// Ein Restwert ohne Anschaffungswert wäre geraten – er bleibt leer.
assertSame(null, $w['restwert']);
assertSame(null, $w['abgeschrieben']);
});
test('Eine unsinnige Nutzungsdauer wird abgewiesen', function () use ($alle) {
assertSame(null, $alle->berechnen('2024-03-15', 1200.0, 0, '2026-09-17')['ende']);
assertSame(null, $alle->berechnen('2024-03-15', 1200.0, -12, '2026-09-17')['ende']);
assertSame(null, $alle->berechnen('2024-03-15', 1200.0, Valuation::MAX_MONATE + 1, '2026-09-17')['ende']);
});
// ----------------------------------------------------------------
// Frist
// ----------------------------------------------------------------
test('Das Nutzungsende liegt die Nutzungsdauer nach dem Kaufdatum', function () use ($alle) {
assertSame('2027-03-15', $alle->berechnen('2024-03-15', null, 36, '2024-03-15')['ende']);
assertSame('2029-03-01', $alle->berechnen('2024-03-01', null, 60, '2024-03-01')['ende']);
});
test('Der Monatsletzte bleibt der Monatsletzte', function () use ($alle) {
// Der 31.01. plus einen Monat ist der 29.02. und nicht der 02.03.
assertSame('2024-02-29', $alle->berechnen('2024-01-31', null, 1, '2024-01-31')['ende']);
assertSame('2025-02-28', $alle->berechnen('2024-01-31', null, 13, '2024-01-31')['ende']);
});
test('Der Status folgt der Restlaufzeit', function () use ($alle) {
assertSame('aktiv', $alle->berechnen('2024-03-15', null, 36, '2025-01-01')['status']);
assertSame('laeuft_aus', $alle->berechnen('2024-03-15', null, 36, '2026-10-01')['status']);
assertSame('abgelaufen', $alle->berechnen('2024-03-15', null, 36, '2027-03-15')['status']);
assertSame('abgelaufen', $alle->berechnen('2024-03-15', null, 36, '2030-01-01')['status']);
});
test('Genutzte und restliche Monate ergeben zusammen die Nutzungsdauer', function () use ($alle) {
foreach (['2024-03-15', '2025-06-30', '2026-09-17', '2027-03-15', '2028-01-01'] as $tag) {
$w = $alle->berechnen('2024-03-15', null, 36, $tag);
assertSame(36, $w['monate_genutzt'] + $w['monate_rest'], 'Stichtag ' . $tag);
}
});
test('Ein künftiges Kaufdatum verbraucht noch nichts', function () use ($alle) {
$w = $alle->berechnen('2027-01-01', 1200.0, 36, '2026-09-17');
assertSame(0, $w['monate_genutzt']);
assertSame(0.0, $w['fortschritt']);
assertSame(0.0, $w['abgeschrieben']);
assertSame(1200.0, $w['restwert']);
});
// ----------------------------------------------------------------
// Beträge
// ----------------------------------------------------------------
test('Die monatsgenaue Abschreibung zählt den Anschaffungsmonat voll', function () use ($alle) {
// Anschaffung im März 2024, 36 Monate à 33,33 €.
assertSame(33.33, $alle->berechnen('2024-03-15', 1200.0, 36, '2024-03-15')['abgeschrieben']);
assertSame(66.67, $alle->berechnen('2024-03-15', 1200.0, 36, '2024-04-01')['abgeschrieben']);
assertSame(66.67, $alle->berechnen('2024-03-15', 1200.0, 36, '2024-04-30')['abgeschrieben']);
});
test('Volle Jahre buchen erst nach dem Jahrestag', function () {
$v = new Valuation(true, true, true, Valuation::METHODE_JAEHRLICH);
assertSame(0.0, $v->berechnen('2024-03-15', 1200.0, 36, '2025-03-14')['abgeschrieben']);
assertSame(400.0, $v->berechnen('2024-03-15', 1200.0, 36, '2025-03-15')['abgeschrieben']);
assertSame(800.0, $v->berechnen('2024-03-15', 1200.0, 36, '2026-09-17')['abgeschrieben']);
});
test('Restwert und abgeschriebener Anteil ergeben den Anschaffungswert', function () {
foreach ([Valuation::METHODE_MONATLICH, Valuation::METHODE_JAEHRLICH] as $methode) {
$v = new Valuation(true, true, true, $methode);
foreach (['2024-03-15', '2025-01-01', '2026-09-17', '2027-03-15'] as $tag) {
foreach ([1.0, 1200.0, 99999.99] as $wert) {
$w = $v->berechnen('2024-03-15', $wert, 36, $tag);
assertTrue(
abs(($w['restwert'] + $w['abgeschrieben']) - $wert) < 0.01,
"{$methode} / {$tag} / {$wert}"
);
}
}
}
});
test('Nach Ablauf der Nutzungsdauer steht der Restwert auf null', function () {
foreach ([Valuation::METHODE_MONATLICH, Valuation::METHODE_JAEHRLICH] as $methode) {
$v = new Valuation(true, true, true, $methode);
$w = $v->berechnen('2024-03-15', 1200.0, 36, '2030-01-01');
assertSame(0.0, $w['restwert'], $methode);
assertSame(1200.0, $w['abgeschrieben'], $methode);
}
});
test('Der Restwert fällt nie unter null', function () {
$v = new Valuation(true, true, true);
foreach (['2027-03-15', '2027-04-01', '2040-01-01'] as $tag) {
assertTrue($v->berechnen('2024-03-15', 1200.0, 36, $tag)['restwert'] >= 0.0, $tag);
}
});
test('Jahres- und Monatsbetrag passen zur Nutzungsdauer', function () use ($alle) {
$w = $alle->berechnen('2024-03-15', 1200.0, 36, '2024-03-15');
assertSame(33.33, $w['monatsbetrag']);
assertSame(400.0, $w['jahresbetrag']);
$kurz = $alle->berechnen('2024-03-15', 1200.0, 12, '2024-03-15');
assertSame(100.0, $kurz['monatsbetrag']);
assertSame(1200.0, $kurz['jahresbetrag']);
});
// ----------------------------------------------------------------
// Eingaben aus dem Formular
// ----------------------------------------------------------------
test('Beträge werden mit Komma wie mit Punkt gelesen', function () {
assertSame(1234.56, Valuation::wertAusEingabe('1234.56'));
assertSame(1234.56, Valuation::wertAusEingabe('1234,56'));
assertSame(1234.56, Valuation::wertAusEingabe('1.234,56'));
assertSame(1234.56, Valuation::wertAusEingabe(' 1 234,56 '));
assertSame(0.0, Valuation::wertAusEingabe('0'));
});
test('Unbrauchbare Beträge ergeben keinen Wert', function () {
assertSame(null, Valuation::wertAusEingabe(''));
assertSame(null, Valuation::wertAusEingabe(' '));
assertSame(null, Valuation::wertAusEingabe('teuer'));
assertSame(null, Valuation::wertAusEingabe('-5'));
assertSame(null, Valuation::wertAusEingabe(null));
assertSame(null, Valuation::wertAusEingabe(['1200']));
assertSame(null, Valuation::wertAusEingabe('99999999999999'));
});
test('Die Nutzungsdauer nimmt nur ganze Monate im erlaubten Bereich', function () {
assertSame(36, Valuation::monateAusEingabe('36'));
assertSame(1, Valuation::monateAusEingabe('1'));
assertSame(Valuation::MAX_MONATE, Valuation::monateAusEingabe((string)Valuation::MAX_MONATE));
assertSame(null, Valuation::monateAusEingabe('0'));
assertSame(null, Valuation::monateAusEingabe('-12'));
assertSame(null, Valuation::monateAusEingabe('36,5'));
assertSame(null, Valuation::monateAusEingabe(''));
assertSame(null, Valuation::monateAusEingabe(null));
assertSame(null, Valuation::monateAusEingabe((string)(Valuation::MAX_MONATE + 1)));
});
// ----------------------------------------------------------------
// Oberfläche
// ----------------------------------------------------------------
test('Die Geräteseite zeigt jede Angabe nur bei zugeschaltetem Schalter', function () {
$quelle = file_get_contents(__DIR__ . '/../templates/assets/show.php');
foreach (['Anschaffungswert', 'Nutzungsdauer', 'Abschreibung', 'Restwert'] as $beschriftung) {
// Die Beschriftung kann übersetzt sein – dann steht sie in einem te()-Aufruf.
assertTrue(
str_contains($quelle, '<div class="inv-detail-label">' . $beschriftung . '</div>')
|| str_contains($quelle, '<div class="inv-detail-label"><?= $this->te(\'' . $beschriftung . '\') ?></div>'),
'Zeile fehlt: ' . $beschriftung
);
}
// Jede der vier Zeilen hängt an einer eigenen Bedingung.
foreach (['$val->brauchtAnschaffungswert()', '$val->nutzungsdauer', '$val->abschreibung', '$val->restwert'] as $bedingung) {
assertTrue(str_contains($quelle, 'if ($val && ' . $bedingung . '):'), 'Bedingung fehlt: ' . $bedingung);
}
});
test('Die Einstellungsseite bietet für jeden Schalter ein Kästchen', function () {
$quelle = file_get_contents(__DIR__ . '/../templates/admin/assets/valuation.php');
foreach (Valuation::SCHALTER as $key) {
assertTrue(
str_contains($quelle, 'name="' . $key . '" value="1"'),
'Kästchen fehlt: ' . $key
);
}
assertTrue(str_contains($quelle, 'name="_csrf"'), 'CSRF-Feld fehlt');
});
test('Der Filter nach Nutzungsdauer greift nur bei zugeschalteter Angabe', function () {
$quelle = file_get_contents(__DIR__ . '/../src/Controllers/AssetController.php');
assertTrue(
str_contains($quelle, "if (\$this->valuation()->nutzungsdauer && \$lifecycle !== '')"),
'Der Filter ist nicht an den Schalter gebunden'
);
});
test('Ohne Zuschaltung schreibt das Formular keine Werte fort', function () {
$quelle = file_get_contents(__DIR__ . '/../src/Controllers/AssetController.php');
// Der Helfer liefert nur zugeschaltete Spalten; beide Schreibwege nutzen ihn.
assertTrue(str_contains($quelle, 'private function valuationFelder(array $data): array'));
assertTrue(str_contains($quelle, "\$this->db()->insert('assets', \$this->valuationFelder(\$data) + ["));
assertTrue(str_contains($quelle, "\$this->db()->update('assets', \$wertFelder + ["));
});
};