Steuerkram/src/Models/BankSource.php
fds2610-bot b42758eb6e Feature: Drag-and-Drop Reihenfolge der Direktverbindungs-Kacheln
- Migration 071 ergänzt bank_sources.sort_order
- Drag-Handle (⠿) oben links jeder Kachel
- Gezogene Kachel hängt am Mauszeiger, Ursprung mit lila Rahmen
- Blauer Strich im Gap markiert die Einfügeposition
- Reihenfolge wird per AJAX nach /bank/direkt/_order persistiert
2026-05-22 09:24:56 +02:00

151 lines
5.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace Steuerkram\Models;
use Steuerkram\Database;
use Steuerkram\Services\Crypto;
/**
* Einheitliche Tabelle für alle Bank-Importquellen:
* - type='csv' – CSV-Importformat (Spaltenmapping, Trennzeichen, Templates)
* - type='api' – Direktverbindung über REST/NVP API (PayPal)
* - type='fints' – Direktverbindung über FinTS (Comdirect, DKB, …)
*
* Beim Lesen werden die verschlüsselten Spalten (client_id_enc, secret_enc, pin_enc)
* automatisch zu Klartext-Schlüsseln (client_id, secret, pin) entschlüsselt.
* Außerdem wird "name" als Alias für "bezeichnung" gesetzt, damit der bestehende
* BankImportParser-Code ($format['name'] etc.) ohne Anpassung funktioniert.
*/
class BankSource
{
public static function all(?string $type = null): array
{
$sql = 'SELECT bs.*, bp.name AS provider_name, bp.verbindungstyp, bp.default_fints_url
FROM bank_sources bs
LEFT JOIN bank_providers bp ON bp.id = bs.provider_id';
$params = [];
if ($type !== null) {
$sql .= ' WHERE bs.type = ?';
$params[] = $type;
}
$sql .= ' ORDER BY bs.sort_order, bs.type, bs.bezeichnung';
$rows = Database::fetchAll($sql, $params);
return array_map([self::class, 'hydrate'], $rows);
}
public static function find(int $id): ?array
{
$row = Database::fetchOne(
'SELECT bs.*, bp.name AS provider_name, bp.verbindungstyp, bp.default_fints_url
FROM bank_sources bs
LEFT JOIN bank_providers bp ON bp.id = bs.provider_id
WHERE bs.id = ?',
[$id]
);
return $row ? self::hydrate($row) : null;
}
public static function create(array $data): int
{
$type = $data['type'] ?? 'csv';
$row = self::dehydrate($data);
$cols = array_keys($row);
$sql = 'INSERT INTO bank_sources (' . implode(',', array_map(fn($c) => "`$c`", $cols)) . ')
VALUES (' . implode(',', array_fill(0, count($cols), '?')) . ')';
Database::execute($sql, array_values($row));
return (int)Database::lastInsertId();
}
public static function update(int $id, array $data): void
{
$row = self::dehydrate($data);
if (empty($row)) {
return;
}
$sets = implode(', ', array_map(fn($c) => "`$c` = ?", array_keys($row)));
$params = array_values($row);
$params[] = $id;
Database::execute("UPDATE bank_sources SET $sets WHERE id = ?", $params);
}
public static function updateField(int $id, string $field, mixed $value): void
{
if (in_array($field, ['client_id', 'secret', 'pin'], true)) {
$field = $field . '_enc';
$value = $value !== null && $value !== '' ? Crypto::encrypt((string)$value) : null;
}
Database::execute("UPDATE bank_sources SET `$field` = ? WHERE id = ?", [$value, $id]);
}
public static function delete(int $id): void
{
Database::execute('DELETE FROM bank_sources WHERE id = ?', [$id]);
}
public static function updateLastFetched(int $id): void
{
Database::execute('UPDATE bank_sources SET last_fetched_at = NOW() WHERE id = ?', [$id]);
}
/** Aktualisiert sort_order anhand der übergebenen ID-Reihenfolge (Position 0..N). */
public static function updateOrder(array $orderedIds): void
{
foreach (array_values($orderedIds) as $pos => $id) {
$id = (int)$id;
if ($id <= 0) continue;
Database::execute(
'UPDATE bank_sources SET sort_order = ? WHERE id = ?',
[$pos, $id]
);
}
}
private static function hydrate(array $row): array
{
$row['name'] = $row['bezeichnung'];
$row['client_id'] = Crypto::decrypt((string)($row['client_id_enc'] ?? ''));
$row['secret'] = Crypto::decrypt((string)($row['secret_enc'] ?? ''));
$row['pin'] = Crypto::decrypt((string)($row['pin_enc'] ?? ''));
return $row;
}
/**
* Überführt ein Eingabe-Array in die DB-Spalten:
* - client_id/secret/pin → *_enc (verschlüsselt)
* - nur erlaubte Spalten werden zurückgegeben
*/
private static function dehydrate(array $data): array
{
$allowed = [
'type', 'provider_id', 'bezeichnung', 'kuerzel', 'farbe',
'skip_rows', 'has_header_row', 'delimiter', 'enclosure',
'col_buchungstag', 'col_wertstellung', 'col_vorgang', 'col_buchungstext', 'col_wert',
'col_zahlungspflichtiger', 'col_zahlungsempfaenger', 'col_verwendungszweck',
'date_format', 'decimal_separator', 'thousands_separator',
'buchungstext_template_debit', 'buchungstext_template_credit',
'sandbox', 'last_fetched_at',
'app_name', 'client_id_enc', 'secret_enc',
'blz', 'fints_url', 'benutzerkennung', 'pin_enc', 'account_filter',
];
$out = [];
foreach ($allowed as $col) {
if (array_key_exists($col, $data)) {
$out[$col] = $data[$col];
}
}
// Klartext-Felder verschlüsseln
foreach (['client_id' => 'client_id_enc', 'secret' => 'secret_enc', 'pin' => 'pin_enc'] as $plain => $enc) {
if (array_key_exists($plain, $data)) {
$val = $data[$plain];
$out[$enc] = ($val === null || $val === '') ? null : Crypto::encrypt((string)$val);
}
}
return $out;
}
}