feat: Sonderführungen aus dem Logbuch in SoFue2 nachtragen
RepoSoFue::ausLogbuch() sucht zu einem Logbuch-Eintrag der Art 'SF' den passenden Datensatz über DATE(wtermin) — bei mehreren Führungen an einem Tag gewinnt die zeitlich nächstgelegene — und setzt über das vorhandene updateAfter() stattgefunden, anzahl_echt, bezahlt und remarks. Aufgerufen wird das im Dispatcher nach dem Commit der Logbuch- Transaktion; die Methode wirft nie, sondern liefert einen Status, der als Feld 'sofue' in der Antwort zurückgeht. bezahlt wird wie im beoanswer-Formular geschrieben (Kasse 50€, Überweisung, Spendenkässle, keine). Da SoFue2 latin1 ist und remarks nur 100 Zeichen fasst, kürzt cp1252Safe() den Text und entfernt nicht darstellbare Zeichen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -588,6 +588,19 @@ class RepoSoFue
|
|||||||
{
|
{
|
||||||
return DB::one("SELECT * FROM " . TBL_SOFUE . " WHERE wtermin=? AND deleted=0", [$termin]);
|
return DB::one("SELECT * FROM " . TBL_SOFUE . " WHERE wtermin=? AND deleted=0", [$termin]);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Passende Führung zum Beginn eines Logbuch-Eintrags: gleiches Datum, zeitlich am
|
||||||
|
* nächsten. Der Logbuch-Beginn liegt typisch 0-30 Minuten vor dem wtermin.
|
||||||
|
*/
|
||||||
|
public static function findByLogbuchBeginn(string $beginn): ?array
|
||||||
|
{
|
||||||
|
return DB::one(
|
||||||
|
"SELECT id, wtermin FROM " . TBL_SOFUE
|
||||||
|
. " WHERE deleted=0 AND DATE(wtermin) = DATE(?)"
|
||||||
|
. " ORDER BY ABS(TIMESTAMPDIFF(MINUTE, ?, wtermin)) ASC, id ASC LIMIT 1",
|
||||||
|
[$beginn, $beginn]
|
||||||
|
);
|
||||||
|
}
|
||||||
public static function getAfterDate(string $date)
|
public static function getAfterDate(string $date)
|
||||||
{
|
{
|
||||||
// Accept YYYYMMDD or YYYY-MM-DD; compare by DATE(wtermin)
|
// Accept YYYYMMDD or YYYY-MM-DD; compare by DATE(wtermin)
|
||||||
@@ -709,6 +722,75 @@ class RepoSoFue
|
|||||||
{
|
{
|
||||||
return DB::exec("UPDATE " . TBL_SOFUE . " SET deleted=1 WHERE id=?", [$id]);
|
return DB::exec("UPDATE " . TBL_SOFUE . " SET deleted=1 WHERE id=?", [$id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spende aus dem Logbuch als bezahlt-Text — gleiche Schreibweise wie das
|
||||||
|
* beoanswer-Formular (beoanswer/src/components/LastButtons.jsx).
|
||||||
|
*/
|
||||||
|
private static function bezahltText(array $p): string
|
||||||
|
{
|
||||||
|
switch ($p['Spende'] ?? '') {
|
||||||
|
case 'bar':
|
||||||
|
// Ganze Beträge ohne Nachkommastellen ("Kasse 50€"), sonst mit Komma
|
||||||
|
// ("Kasse 25,50€") — passt zu den Bestandswerten und in VARCHAR(15).
|
||||||
|
$betrag = (float)($p['SpendeBetrag'] ?? 0);
|
||||||
|
$text = fmod($betrag, 1.0) === 0.0
|
||||||
|
? number_format($betrag, 0, ',', '')
|
||||||
|
: number_format($betrag, 2, ',', '');
|
||||||
|
return 'Kasse ' . $text . '€';
|
||||||
|
case 'ueberw': return 'Überweisung';
|
||||||
|
case 'kasse': return 'Spendenkässle';
|
||||||
|
case 'keine': return 'keine';
|
||||||
|
default: return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kürzt auf $max Zeichen und entfernt alles, was die latin1-Spalten von SoFue2
|
||||||
|
* nicht darstellen können (z. B. Emoji) — sonst scheitert der UPDATE im Strict Mode.
|
||||||
|
*/
|
||||||
|
private static function cp1252Safe(string $s, int $max): string
|
||||||
|
{
|
||||||
|
// 'none' lässt nicht abbildbare Zeichen (z. B. Emoji) ersatzlos entfallen,
|
||||||
|
// statt sie wie iconv//TRANSLIT durch '?' zu ersetzen.
|
||||||
|
$vorher = mb_substitute_character();
|
||||||
|
mb_substitute_character('none');
|
||||||
|
$cp = mb_convert_encoding($s, 'CP1252', 'UTF-8');
|
||||||
|
mb_substitute_character($vorher);
|
||||||
|
return mb_substr(mb_convert_encoding($cp, 'UTF-8', 'CP1252'), 0, $max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trägt eine im Logbuch erfasste Sonderführung im Register nach: stattgefunden,
|
||||||
|
* anzahl_echt, bezahlt und remarks. Der Datensatz wird über das Datum gefunden.
|
||||||
|
* Wirft nie — der Logbuch-Eintrag ist zu diesem Zeitpunkt bereits gespeichert.
|
||||||
|
*/
|
||||||
|
public static function ausLogbuch(array $p): array
|
||||||
|
{
|
||||||
|
if (($p['ArtFuehrung'] ?? '') !== 'SF') return ['status' => 'uebersprungen'];
|
||||||
|
$datum = substr((string)($p['Beginn'] ?? ''), 0, 10);
|
||||||
|
try {
|
||||||
|
$treffer = self::findByLogbuchBeginn((string)($p['Beginn'] ?? ''));
|
||||||
|
if (!$treffer) return ['status' => 'kein_termin', 'datum' => $datum];
|
||||||
|
|
||||||
|
self::updateAfter((int)$treffer['id'], [
|
||||||
|
'stattgefunden' => 1,
|
||||||
|
'besucher' => (int)($p['Besucher'] ?? 0),
|
||||||
|
'remark' => self::cp1252Safe((string)($p['Bemerkungen'] ?? ''), 100),
|
||||||
|
'bezahlt' => self::bezahltText($p),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => 'ok',
|
||||||
|
'id' => (int)$treffer['id'],
|
||||||
|
'wtermin' => $treffer['wtermin'],
|
||||||
|
'datum' => $datum,
|
||||||
|
];
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('SoFue-Rueckschreibung: ' . $e->getMessage());
|
||||||
|
return ['status' => 'fehler', 'datum' => $datum];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RepoStatistik
|
class RepoStatistik
|
||||||
@@ -1679,8 +1761,8 @@ class Commands
|
|||||||
'LB_GET_BEO_ADRESSEN' => 'Logbuch: BEOs mit Adressen (km-Berechnung)',
|
'LB_GET_BEO_ADRESSEN' => 'Logbuch: BEOs mit Adressen (km-Berechnung)',
|
||||||
'LB_UPDATE_BEO_KM' => 'Logbuch: km-Entfernung eines BEOs setzen',
|
'LB_UPDATE_BEO_KM' => 'Logbuch: km-Entfernung eines BEOs setzen',
|
||||||
'LB_LIST_LOGBUCH' => 'Logbuch: Einträge abrufen',
|
'LB_LIST_LOGBUCH' => 'Logbuch: Einträge abrufen',
|
||||||
'LB_CREATE_LOGBUCH' => 'Logbuch: Eintrag anlegen',
|
'LB_CREATE_LOGBUCH' => 'Logbuch: Eintrag anlegen (trägt Sonderführungen in SoFue2 nach)',
|
||||||
'LB_UPDATE_LOGBUCH' => 'Logbuch: Eintrag ändern',
|
'LB_UPDATE_LOGBUCH' => 'Logbuch: Eintrag ändern (trägt Sonderführungen in SoFue2 nach)',
|
||||||
'LB_DELETE_LOGBUCH' => 'Logbuch: Eintrag löschen',
|
'LB_DELETE_LOGBUCH' => 'Logbuch: Eintrag löschen',
|
||||||
'LB_GET_BEOS' => 'Logbuch: Guide-BEOs abrufen',
|
'LB_GET_BEOS' => 'Logbuch: Guide-BEOs abrufen',
|
||||||
'LB_GET_OBJEKTE' => 'Logbuch: Objekte (zuletzt genutzt)',
|
'LB_GET_OBJEKTE' => 'Logbuch: Objekte (zuletzt genutzt)',
|
||||||
@@ -2192,13 +2274,15 @@ Spendenbescheinigung: \t" . $info['spende'] . "
|
|||||||
respond(RepoLogbuch::listLogbuch($input));
|
respond(RepoLogbuch::listLogbuch($input));
|
||||||
case 'LB_CREATE_LOGBUCH':
|
case 'LB_CREATE_LOGBUCH':
|
||||||
$newId = RepoLogbuch::createLogbuch($input);
|
$newId = RepoLogbuch::createLogbuch($input);
|
||||||
respond(['id' => $newId], 201);
|
// Erst nach dem Commit: eine Sonderführung wird im Register nachgetragen,
|
||||||
|
// ein Problem dabei darf den Logbuch-Eintrag nicht zurückrollen.
|
||||||
|
respond(['id' => $newId, 'sofue' => RepoSoFue::ausLogbuch($input)], 201);
|
||||||
case 'LB_UPDATE_LOGBUCH':
|
case 'LB_UPDATE_LOGBUCH':
|
||||||
$id = (int)($input['id'] ?? respondError('id missing'));
|
$id = (int)($input['id'] ?? respondError('id missing'));
|
||||||
$userId = (int)($input['user_id'] ?? respondError('user_id missing'));
|
$userId = (int)($input['user_id'] ?? respondError('user_id missing'));
|
||||||
$userRole = (string)($input['user_role'] ?? '');
|
$userRole = (string)($input['user_role'] ?? '');
|
||||||
RepoLogbuch::updateLogbuch($id, $userId, $userRole, $input);
|
RepoLogbuch::updateLogbuch($id, $userId, $userRole, $input);
|
||||||
respond(['ok' => true]);
|
respond(['ok' => true, 'sofue' => RepoSoFue::ausLogbuch($input)]);
|
||||||
case 'LB_DELETE_LOGBUCH':
|
case 'LB_DELETE_LOGBUCH':
|
||||||
$id = (int)($input['id'] ?? respondError('id missing'));
|
$id = (int)($input['id'] ?? respondError('id missing'));
|
||||||
$userId = (int)($input['user_id'] ?? respondError('user_id missing'));
|
$userId = (int)($input['user_id'] ?? respondError('user_id missing'));
|
||||||
|
|||||||
Reference in New Issue
Block a user