210 lines
7.6 KiB
PHP
210 lines
7.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Steuerkram\Models;
|
|
|
|
use Steuerkram\Database;
|
|
|
|
class Booking
|
|
{
|
|
public static function forBeleg(int $belegId): array
|
|
{
|
|
return Database::fetchAll(
|
|
'SELECT bk.*,
|
|
k.kontonummer as kategorie_nr, k.bezeichnung as kategorie_name,
|
|
z.kontonummer as zahlungs_nr, z.bezeichnung as zahlungs_name
|
|
FROM bookings bk
|
|
JOIN accounts k ON bk.kategorie_konto_id = k.id
|
|
JOIN accounts z ON bk.zahlungskonto_id = z.id
|
|
WHERE bk.beleg_id = ?
|
|
ORDER BY bk.buchungs_datum, bk.id',
|
|
[$belegId]
|
|
);
|
|
}
|
|
|
|
public static function journal(array $filter = [], int $page = 1, int $perPage = 50): array
|
|
{
|
|
$where = ['1=1'];
|
|
$params = [];
|
|
|
|
if (!empty($filter['year'])) {
|
|
$where[] = 'fy.year = ?';
|
|
$params[] = (int)$filter['year'];
|
|
}
|
|
if (!empty($filter['month'])) {
|
|
$where[] = 'MONTH(bk.buchungs_datum) = ?';
|
|
$params[] = (int)$filter['month'];
|
|
}
|
|
if (!empty($filter['konto'])) {
|
|
$where[] = '(bk.kategorie_konto_id = ? OR bk.zahlungskonto_id = ?)';
|
|
$params[] = (int)$filter['konto'];
|
|
$params[] = (int)$filter['konto'];
|
|
}
|
|
if (!empty($filter['search'])) {
|
|
$where[] = 'bk.buchungstext LIKE ?';
|
|
$params[] = '%' . $filter['search'] . '%';
|
|
}
|
|
|
|
$offset = ($page - 1) * $perPage;
|
|
$sql = 'SELECT bk.*,
|
|
k.kontonummer as kategorie_nr, k.bezeichnung as kategorie_name,
|
|
z.kontonummer as zahlungs_nr, z.bezeichnung as zahlungs_name,
|
|
bl.beleg_nr, fy.year as fiscal_year_nr
|
|
FROM bookings bk
|
|
JOIN accounts k ON bk.kategorie_konto_id = k.id
|
|
JOIN accounts z ON bk.zahlungskonto_id = z.id
|
|
JOIN belege bl ON bk.beleg_id = bl.id
|
|
JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id
|
|
WHERE ' . implode(' AND ', $where) . '
|
|
ORDER BY bk.buchungs_datum DESC, bk.id DESC
|
|
LIMIT ? OFFSET ?';
|
|
|
|
$params[] = $perPage;
|
|
$params[] = $offset;
|
|
|
|
$rows = Database::fetchAll($sql, $params);
|
|
|
|
$countSql = 'SELECT COUNT(*) as cnt FROM bookings bk
|
|
JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id
|
|
WHERE ' . implode(' AND ', $where);
|
|
$countParams = array_slice($params, 0, -2);
|
|
$total = (int)(Database::fetchOne($countSql, $countParams)['cnt'] ?? 0);
|
|
|
|
return ['rows' => $rows, 'total' => $total, 'pages' => (int)ceil($total / $perPage)];
|
|
}
|
|
|
|
public static function find(int $id): array|false
|
|
{
|
|
return Database::fetchOne('SELECT * FROM bookings WHERE id = ?', [$id]);
|
|
}
|
|
|
|
public static function create(array $data): int
|
|
{
|
|
// Determine fiscal year
|
|
$year = (int)date('Y', strtotime($data['buchungs_datum']));
|
|
$fy = FiscalYear::findOrCreateByYear($year);
|
|
|
|
if ($fy['locked']) {
|
|
throw new \RuntimeException("Geschäftsjahr {$year} ist gesperrt. Neue Buchungen nicht möglich.");
|
|
}
|
|
|
|
// Calculate amounts
|
|
$netto = round((float)$data['betrag_netto'], 2);
|
|
$ust = round((float)($data['ust_satz'] ?? 0) / 100 * $netto, 2);
|
|
$brutto = round($netto + $ust, 2);
|
|
|
|
$nr = self::generateBuchungsNr();
|
|
|
|
$steuerart = $data['steuerart'] ?? 'normal';
|
|
if (!in_array($steuerart, ['normal','ig_lieferung','export','ig_erwerb_19','ig_erwerb_7'], true)) {
|
|
$steuerart = 'normal';
|
|
}
|
|
|
|
Database::execute(
|
|
'INSERT INTO bookings (buchungs_nr, beleg_id, buchungs_datum, buchungstyp,
|
|
kategorie_konto_id, zahlungskonto_id,
|
|
betrag_netto, ust_satz, steuerart, betrag_ust, betrag_brutto, buchungstext, fiscal_year_id)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
[
|
|
$nr,
|
|
(int)$data['beleg_id'],
|
|
$data['buchungs_datum'],
|
|
$data['buchungstyp'],
|
|
(int)$data['kategorie_konto_id'],
|
|
(int)$data['zahlungskonto_id'],
|
|
$netto,
|
|
(float)($data['ust_satz'] ?? 0),
|
|
$steuerart,
|
|
$ust,
|
|
$brutto,
|
|
$data['buchungstext'],
|
|
$fy['id'],
|
|
]
|
|
);
|
|
|
|
$id = (int)Database::lastInsertId();
|
|
|
|
// Mark Beleg as booked
|
|
Database::execute('UPDATE belege SET booked=1, updated_at=NOW() WHERE id=?', [(int)$data['beleg_id']]);
|
|
|
|
return $id;
|
|
}
|
|
|
|
public static function reverse(int $id, string $reason): int
|
|
{
|
|
$orig = self::find($id);
|
|
if (!$orig) throw new \RuntimeException('Buchung nicht gefunden.');
|
|
if ($orig['is_reversal']) throw new \RuntimeException('Stornobuchungen können nicht storniert werden.');
|
|
|
|
$reversalData = [
|
|
'beleg_id' => $orig['beleg_id'],
|
|
'buchungs_datum' => date('Y-m-d'),
|
|
'buchungstyp' => $orig['buchungstyp'],
|
|
'kategorie_konto_id' => $orig['kategorie_konto_id'],
|
|
'zahlungskonto_id' => $orig['zahlungskonto_id'],
|
|
'betrag_netto' => $orig['betrag_netto'],
|
|
'ust_satz' => $orig['ust_satz'],
|
|
'buchungstext' => 'STORNO zu ' . $orig['buchungs_nr'] . ': ' . $reason,
|
|
];
|
|
|
|
$year = (int)date('Y', strtotime($reversalData['buchungs_datum']));
|
|
$fy = FiscalYear::findOrCreateByYear($year);
|
|
|
|
$nr = self::generateBuchungsNr();
|
|
|
|
Database::execute(
|
|
'INSERT INTO bookings (buchungs_nr, beleg_id, buchungs_datum, buchungstyp,
|
|
kategorie_konto_id, zahlungskonto_id,
|
|
betrag_netto, ust_satz, betrag_ust, betrag_brutto, buchungstext, is_reversal, fiscal_year_id)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)',
|
|
[
|
|
$nr,
|
|
$reversalData['beleg_id'],
|
|
$reversalData['buchungs_datum'],
|
|
$reversalData['buchungstyp'],
|
|
$reversalData['kategorie_konto_id'],
|
|
$reversalData['zahlungskonto_id'],
|
|
$orig['betrag_netto'],
|
|
$orig['ust_satz'],
|
|
$orig['betrag_ust'],
|
|
$orig['betrag_brutto'],
|
|
$reversalData['buchungstext'],
|
|
$fy['id'],
|
|
]
|
|
);
|
|
|
|
$reversalId = (int)Database::lastInsertId();
|
|
|
|
// Link original to reversal
|
|
Database::execute('UPDATE bookings SET reversed_by=? WHERE id=?', [$reversalId, $id]);
|
|
|
|
return $reversalId;
|
|
}
|
|
|
|
public static function generateBuchungsNr(): string
|
|
{
|
|
$year = date('Y');
|
|
$result = Database::fetchOne(
|
|
'SELECT COUNT(*) as cnt FROM bookings WHERE YEAR(buchungs_datum) = ?',
|
|
[$year]
|
|
);
|
|
$next = (int)($result['cnt'] ?? 0) + 1;
|
|
return sprintf('BU-%s-%06d', $year, $next);
|
|
}
|
|
|
|
public static function sumByYear(int $year): array
|
|
{
|
|
return Database::fetchAll(
|
|
'SELECT bk.buchungstyp, bk.kategorie_konto_id, bk.zahlungskonto_id,
|
|
SUM(bk.betrag_netto) as sum_netto,
|
|
SUM(bk.betrag_ust) as sum_ust,
|
|
SUM(bk.betrag_brutto) as sum_brutto
|
|
FROM bookings bk
|
|
JOIN fiscal_years fy ON bk.fiscal_year_id = fy.id
|
|
WHERE fy.year = ?
|
|
GROUP BY bk.buchungstyp, bk.kategorie_konto_id, bk.zahlungskonto_id',
|
|
[$year]
|
|
);
|
|
}
|
|
}
|