200 lines
7.1 KiB
PHP
200 lines
7.1 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.*,
|
|
s.kontonummer as soll_nr, s.bezeichnung as soll_name,
|
|
h.kontonummer as haben_nr, h.bezeichnung as haben_name
|
|
FROM bookings bk
|
|
JOIN accounts s ON bk.soll_konto_id = s.id
|
|
JOIN accounts h ON bk.haben_konto_id = h.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.soll_konto_id = ? OR bk.haben_konto_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.*,
|
|
s.kontonummer as soll_nr, s.bezeichnung as soll_name,
|
|
h.kontonummer as haben_nr, h.bezeichnung as haben_name,
|
|
bl.beleg_nr, fy.year as fiscal_year_nr
|
|
FROM bookings bk
|
|
JOIN accounts s ON bk.soll_konto_id = s.id
|
|
JOIN accounts h ON bk.haben_konto_id = h.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();
|
|
|
|
Database::execute(
|
|
'INSERT INTO bookings (buchungs_nr, beleg_id, buchungs_datum, soll_konto_id, haben_konto_id,
|
|
betrag_netto, ust_satz, betrag_ust, betrag_brutto, buchungstext, fiscal_year_id)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
[
|
|
$nr,
|
|
(int)$data['beleg_id'],
|
|
$data['buchungs_datum'],
|
|
(int)$data['soll_konto_id'],
|
|
(int)$data['haben_konto_id'],
|
|
$netto,
|
|
(float)($data['ust_satz'] ?? 0),
|
|
$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.');
|
|
|
|
// Create reversal: swap Soll/Haben, same amounts
|
|
$reversalData = [
|
|
'beleg_id' => $orig['beleg_id'],
|
|
'buchungs_datum' => date('Y-m-d'),
|
|
'soll_konto_id' => $orig['haben_konto_id'],
|
|
'haben_konto_id' => $orig['soll_konto_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, soll_konto_id, haben_konto_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['soll_konto_id'],
|
|
$reversalData['haben_konto_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.soll_konto_id, bk.haben_konto_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.soll_konto_id, bk.haben_konto_id',
|
|
[$year]
|
|
);
|
|
}
|
|
}
|