Steuerkram/src/Models/Asset.php
2026-05-24 15:22:18 +02:00

257 lines
9.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Steuerkram\Models;
use Steuerkram\Database;
class Asset
{
/**
* Anlageliste für die EÜR: alle Assets, die im gegebenen Jahr aktiv sind,
* mit kumulierten AfA-Buchungen aus Vorjahren und dem laufenden Jahr.
*/
public static function anlageliste(int $year): array
{
$rows = Database::fetchAll(
"SELECT
a.*,
ac.kontonummer AS anlageart_kontonummer,
ac.bezeichnung AS anlageart_bezeichnung,
COALESCE(bk_prev.afa_vorjahre, 0) AS afa_vorjahre,
COALESCE(bk_curr.afa_jahr, 0) AS afa_jahr
FROM assets a
LEFT JOIN accounts ac ON a.account_id = ac.id
LEFT JOIN (
SELECT asset_id, SUM(betrag_netto) AS afa_vorjahre
FROM bookings
WHERE buchungstyp = 'AfA' AND afa_jahr < ?
GROUP BY asset_id
) bk_prev ON bk_prev.asset_id = a.id
LEFT JOIN (
SELECT asset_id, SUM(betrag_netto) AS afa_jahr
FROM bookings
WHERE buchungstyp = 'AfA' AND afa_jahr = ?
GROUP BY asset_id
) bk_curr ON bk_curr.asset_id = a.id
WHERE YEAR(a.anschaffungsdatum) <= ?
ORDER BY a.lfd_nr ASC, a.id ASC",
[$year, $year, $year]
);
$result = [];
foreach ($rows as $row) {
$basis = (float)$row['anschaffungskosten'] + (float)$row['zugaenge'] - (float)$row['abgaenge'];
$afaVorjahre = (float)$row['afa_vorjahre'];
$afaJahr = (float)$row['afa_jahr'];
$restbuchwertVorjahr = round($basis - $afaVorjahre, 2);
$restbuchwert = round($restbuchwertVorjahr - $afaJahr, 2);
$result[] = array_merge($row, [
'basis' => $basis,
'afa_vorjahre' => $afaVorjahre,
'afa_jahr' => $afaJahr,
'restbuchwert_vorjahr' => $restbuchwertVorjahr,
'restbuchwert' => $restbuchwert,
]);
}
return $result;
}
public static function all(?int $fiscalYearId = null): array
{
$where = '1=1';
$params = [];
if ($fiscalYearId !== null) {
$where = 'a.fiscal_year_id = ?';
$params[] = $fiscalYearId;
}
return Database::fetchAll(
"SELECT a.*, ac.kontonummer AS anlageart_kontonummer, ac.bezeichnung AS anlageart_bezeichnung
FROM assets a
LEFT JOIN accounts ac ON a.account_id = ac.id
WHERE $where
ORDER BY a.lfd_nr ASC, a.id ASC",
$params
);
}
public static function find(int $id): array|false
{
return Database::fetchOne(
'SELECT a.*, ac.kontonummer AS anlageart_kontonummer, ac.bezeichnung AS anlageart_bezeichnung
FROM assets a
LEFT JOIN accounts ac ON a.account_id = ac.id
WHERE a.id = ?',
[$id]
);
}
public static function anlagearten(): array
{
return Database::fetchAll(
"SELECT id, kontonummer, bezeichnung FROM accounts
WHERE kontenart = 'Anlagevermögen'
ORDER BY kontonummer ASC"
);
}
public static function nextLfdNr(): int
{
$row = Database::fetchOne('SELECT COALESCE(MAX(lfd_nr), 0) + 1 AS next FROM assets');
return (int)($row['next'] ?? 1);
}
public static function generateAnlageNr(): string
{
$settings = Database::fetchOne(
'SELECT anlage_nr_prefix, anlage_nr_next FROM company_settings WHERE id=1'
);
$prefix = $settings['anlage_nr_prefix'] ?? 'ANL';
$next = (int)($settings['anlage_nr_next'] ?? 1);
$year = date('Y');
$nr = sprintf('%s-%s-%04d', $prefix, $year, $next);
Database::execute(
'UPDATE company_settings SET anlage_nr_next = anlage_nr_next + 1 WHERE id=1'
);
return $nr;
}
public static function create(array $data): int
{
$fyId = null;
if (!empty($data['anschaffungsdatum'])) {
$year = (int)date('Y', strtotime($data['anschaffungsdatum']));
$fy = FiscalYear::findOrCreateByYear($year);
$fyId = $fy['id'];
}
$anlageNr = $data['anlage_nr'] ?? self::generateAnlageNr();
Database::execute(
'INSERT INTO assets
(lfd_nr, anlage_nr, bezeichnung, account_id, anschaffungsdatum, anschaffungskosten,
zugaenge, abgaenge, abschreibung_jahr, nutzungsdauer, automatisch_berechnet,
cent_im_ersten_jahr, notizen, fiscal_year_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[
$data['lfd_nr'] ?? self::nextLfdNr(),
$anlageNr,
$data['bezeichnung'],
isset($data['account_id']) && $data['account_id'] !== '' ? (int)$data['account_id'] : null,
$data['anschaffungsdatum'],
$data['anschaffungskosten'] ?? 0,
$data['zugaenge'] ?? 0,
$data['abgaenge'] ?? 0,
$data['abschreibung_jahr'] ?? 0,
isset($data['nutzungsdauer']) && $data['nutzungsdauer'] !== '' ? (int)$data['nutzungsdauer'] : null,
isset($data['automatisch_berechnet']) ? (int)(bool)$data['automatisch_berechnet'] : 1,
isset($data['cent_im_ersten_jahr']) ? (int)(bool)$data['cent_im_ersten_jahr'] : 0,
$data['notizen'] ?? null,
$fyId,
]
);
return (int)Database::lastInsertId();
}
public static function update(int $id, array $data): void
{
$fyId = null;
if (!empty($data['anschaffungsdatum'])) {
$year = (int)date('Y', strtotime($data['anschaffungsdatum']));
$fy = FiscalYear::findOrCreateByYear($year);
$fyId = $fy['id'];
}
Database::execute(
'UPDATE assets SET
bezeichnung = ?,
account_id = ?,
anschaffungsdatum = ?,
anschaffungskosten = ?,
zugaenge = ?,
abgaenge = ?,
abschreibung_jahr = ?,
nutzungsdauer = ?,
automatisch_berechnet = ?,
cent_im_ersten_jahr = ?,
notizen = ?,
fiscal_year_id = COALESCE(?, fiscal_year_id),
updated_at = NOW()
WHERE id = ?',
[
$data['bezeichnung'],
isset($data['account_id']) && $data['account_id'] !== '' ? (int)$data['account_id'] : null,
$data['anschaffungsdatum'],
$data['anschaffungskosten'] ?? 0,
$data['zugaenge'] ?? 0,
$data['abgaenge'] ?? 0,
$data['abschreibung_jahr'] ?? 0,
isset($data['nutzungsdauer']) && $data['nutzungsdauer'] !== '' ? (int)$data['nutzungsdauer'] : null,
isset($data['automatisch_berechnet']) ? (int)(bool)$data['automatisch_berechnet'] : 1,
isset($data['cent_im_ersten_jahr']) ? (int)(bool)$data['cent_im_ersten_jahr'] : 0,
$data['notizen'] ?? null,
$fyId,
$id,
]
);
}
public static function anlagekontoBookings(bool $nurUnzugeordnet = true): array
{
$extraWhere = $nurUnzugeordnet ? 'AND (b.asset_id IS NULL OR a.id IS NULL)' : '';
return Database::fetchAll(
"SELECT
b.id, b.buchungs_nr, b.buchungs_datum, b.buchungstext,
b.betrag_netto, b.betrag_brutto, b.is_reversal, b.reversed_by,
b.asset_id, b.beleg_id, b.kategorie_konto_id,
ac.kontonummer AS kategorie_kontonummer,
ac.bezeichnung AS kategorie_bezeichnung,
zk.kontonummer AS zahlungs_kontonummer,
zk.bezeichnung AS zahlungs_bezeichnung,
a.bezeichnung AS asset_bezeichnung,
a.anlage_nr AS asset_anlage_nr
FROM bookings b
JOIN accounts ac ON b.kategorie_konto_id = ac.id
AND ac.kontenart = 'Anlagevermögen'
JOIN accounts zk ON b.zahlungskonto_id = zk.id
LEFT JOIN assets a ON b.asset_id = a.id
WHERE b.buchungstyp != 'AfA'
AND b.is_reversal = 0
AND b.reversed_by IS NULL
$extraWhere
ORDER BY b.buchungs_datum DESC",
[]
);
}
/**
* Löscht ein Wirtschaftsgut inklusive AfA-Buchungen und Abschreibungstabelle.
* Gibt 'hat_buchungen' zurück, wenn noch reguläre Buchungen verknüpft sind.
*/
public static function delete(int $id): bool|string
{
$asset = self::find($id);
if (!$asset) return false;
$linked = Database::fetchOne(
"SELECT COUNT(*) AS cnt FROM bookings WHERE asset_id = ? AND buchungstyp != 'AfA'",
[$id]
);
if ($linked && (int)$linked['cnt'] > 0) {
return 'hat_buchungen';
}
Database::execute("DELETE FROM bookings WHERE asset_id = ? AND buchungstyp = 'AfA'", [$id]);
AssetDepreciation::deleteByAsset($id);
Database::execute('DELETE FROM assets WHERE id = ?', [$id]);
return true;
}
}