feat: Robustes Cron-System mit Monitoring (v2.7)
- Dediziertes Cron-Logging unter /documents/bankimport/logs/ - Shutdown Handler für fatale PHP-Fehler - Pause-Mechanismus nach 3 Fehlern (verhindert Bank-Sperrung) - Auth-Fehler-Erkennung für Authentifizierungsprobleme - Neue Admin-Seite: Cron-Monitor (Status, Logs, Pause/Resume) - CHANGELOG aktualisiert Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5264a2e91e
commit
f340ba2da5
18 changed files with 764 additions and 24 deletions
26
CHANGELOG.md
Normal file → Executable file
26
CHANGELOG.md
Normal file → Executable file
|
|
@ -2,6 +2,32 @@
|
|||
|
||||
Alle wesentlichen Änderungen an diesem Projekt werden in dieser Datei dokumentiert.
|
||||
|
||||
## [2.7] - 2026-02-23
|
||||
|
||||
### Hinzugefügt
|
||||
- **Dediziertes Cron-Logging**: Separate Log-Datei unter `/documents/bankimport/logs/cron_bankimport.log`
|
||||
- **Shutdown Handler**: Fängt fatale PHP-Fehler ab und protokolliert sie
|
||||
- **Pause-Mechanismus**: Cron pausiert automatisch nach 3 aufeinanderfolgenden Fehlern (60 Min)
|
||||
- **Auth-Fehler-Erkennung**: Erkennt Bank-Authentifizierungsfehler und pausiert um Kontosperrung zu vermeiden
|
||||
- **Cron-Monitor Admin-Seite**: Neue Seite unter Admin > BankImport > Cron-Monitor zeigt Status, Logs und ermöglicht Pause/Resume
|
||||
|
||||
### Verbessert
|
||||
- Robustere Fehlerbehandlung mit try/catch für alle Operationen
|
||||
- Detailliertes Logging mit Zeitstempeln und Elapsed-Time
|
||||
- Fehler-Zähler verhindert wiederholte fehlgeschlagene Versuche
|
||||
|
||||
## [2.6] - 2026-02-20
|
||||
|
||||
### Hinzugefügt
|
||||
- **Multi-Rechnungszahlungen**: Eine Bankbuchung kann jetzt mit mehreren Rechnungen verknüpft werden (Sammelzahlungen)
|
||||
- **Zahlungsverknüpfung aufheben**: Falsche Zuordnungen können über "Verknüpfung aufheben" korrigiert werden
|
||||
- **Detailansicht Verknüpfungen**: In der Buchungsdetailansicht werden verknüpfte Zahlungen, Rechnungen und Bank-Einträge angezeigt
|
||||
- **Bezahlte Rechnungen verknüpfen**: Bereits bezahlte Rechnungen können mit Bankbuchungen verknüpft werden (für nachträgliche Bank-Zuordnung)
|
||||
|
||||
### Verbessert
|
||||
- Bessere Anzeige von Multi-Invoice-Matches im Zahlungsabgleich
|
||||
- Flexible Rechnungsauswahl per Checkbox bei Sammelzahlungen
|
||||
|
||||
## [1.7] - 2026-02-20
|
||||
|
||||
### Hinzugefügt
|
||||
|
|
|
|||
347
admin/cronmonitor.php
Normal file
347
admin/cronmonitor.php
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file bankimport/admin/cronmonitor.php
|
||||
* \ingroup bankimport
|
||||
* \brief Cron job monitoring and log viewer
|
||||
*/
|
||||
|
||||
// Load Dolibarr environment
|
||||
$res = 0;
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
$j = strlen($tmp2) - 1;
|
||||
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
|
||||
$i--;
|
||||
$j--;
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
||||
$res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
|
||||
}
|
||||
if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) {
|
||||
$res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../main.inc.php")) {
|
||||
$res = @include "../../main.inc.php";
|
||||
}
|
||||
if (!$res && file_exists("../../../main.inc.php")) {
|
||||
$res = @include "../../../main.inc.php";
|
||||
}
|
||||
if (!$res) {
|
||||
die("Include of main fails");
|
||||
}
|
||||
|
||||
// Libraries
|
||||
require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php";
|
||||
require_once '../lib/bankimport.lib.php';
|
||||
dol_include_once('/bankimport/class/bankimportcron.class.php');
|
||||
|
||||
/**
|
||||
* @var Conf $conf
|
||||
* @var DoliDB $db
|
||||
* @var Translate $langs
|
||||
* @var User $user
|
||||
*/
|
||||
|
||||
// Translations
|
||||
$langs->loadLangs(array("admin", "bankimport@bankimport", "cron"));
|
||||
|
||||
// Parameters
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
$lines = GETPOSTINT('lines') ?: 100;
|
||||
|
||||
// Access control
|
||||
if (!$user->admin) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
if ($action == 'unpause') {
|
||||
$cron = new BankImportCron($db);
|
||||
$cron->unpauseCron();
|
||||
setEventMessages("Cron-Pause aufgehoben", null, 'mesgs');
|
||||
header("Location: ".$_SERVER["PHP_SELF"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'clearfailcount') {
|
||||
dolibarr_del_const($db, 'BANKIMPORT_CRON_FAIL_COUNT', $conf->entity);
|
||||
setEventMessages("Fehlerzähler zurückgesetzt", null, 'mesgs');
|
||||
header("Location: ".$_SERVER["PHP_SELF"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action == 'resetcronjob') {
|
||||
// Reset the cron job in llx_cronjob table
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX."cronjob SET processing = 0, datenextrun = NOW() WHERE label = 'BankImportAutoFetch' AND processing = 1";
|
||||
$resql = $db->query($sql);
|
||||
if ($resql && $db->affected_rows($resql) > 0) {
|
||||
setEventMessages("Hängenden Cron-Job zurückgesetzt", null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages("Kein hängender Job gefunden oder Fehler", null, 'warnings');
|
||||
}
|
||||
header("Location: ".$_SERVER["PHP_SELF"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$page_name = "Cron Monitor - BankImport";
|
||||
llxHeader('', $page_name, '', '', 0, 0, '', '', '', 'mod-bankimport page-admin-cronmonitor');
|
||||
|
||||
// Admin Tabs
|
||||
$head = bankimportAdminPrepareHead();
|
||||
print dol_get_fiche_head($head, 'cronmonitor', 'BankImport', -1, 'bank');
|
||||
|
||||
// Get cron status
|
||||
$cronStatus = BankImportCron::getCronStatus();
|
||||
|
||||
// Get Dolibarr cron job info
|
||||
$sql = "SELECT rowid, label, datenextrun, datelastrun, datelastresult, processing, lastoutput, lastresult, status
|
||||
FROM ".MAIN_DB_PREFIX."cronjob
|
||||
WHERE label = 'BankImportAutoFetch'";
|
||||
$resql = $db->query($sql);
|
||||
$cronJob = $resql ? $db->fetch_object($resql) : null;
|
||||
|
||||
print '<div class="div-table-responsive-no-min">';
|
||||
|
||||
// Status Overview
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre"><th colspan="2">Cron Status Übersicht</th></tr>';
|
||||
|
||||
// Enabled/Disabled
|
||||
print '<tr class="oddeven"><td width="300">Auto-Import aktiviert</td><td>';
|
||||
if ($cronStatus['enabled']) {
|
||||
print '<span class="badge badge-status4">Aktiviert</span>';
|
||||
} else {
|
||||
print '<span class="badge badge-status8">Deaktiviert</span>';
|
||||
}
|
||||
print '</td></tr>';
|
||||
|
||||
// Paused status
|
||||
print '<tr class="oddeven"><td>Pause-Status</td><td>';
|
||||
if ($cronStatus['paused']) {
|
||||
print '<span class="badge badge-status1" style="background-color: #ff9800;">PAUSIERT</span> ';
|
||||
print 'bis '.dol_print_date($cronStatus['paused_until'], 'dayhour');
|
||||
print '<br><strong>Grund:</strong> '.dol_escape_htmltag($cronStatus['pause_reason']);
|
||||
print '<br><a class="butAction" href="'.$_SERVER["PHP_SELF"].'?action=unpause">Pause aufheben</a>';
|
||||
} else {
|
||||
print '<span class="badge badge-status4">Aktiv</span>';
|
||||
}
|
||||
print '</td></tr>';
|
||||
|
||||
// Failure count
|
||||
print '<tr class="oddeven"><td>Fehlversuche in Folge</td><td>';
|
||||
$failCount = $cronStatus['fail_count'];
|
||||
if ($failCount >= 3) {
|
||||
print '<span class="badge badge-status8">'.$failCount.'</span> ';
|
||||
print '<span class="warning">(bei 3+ wird automatisch pausiert)</span> ';
|
||||
print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?action=clearfailcount">Zurücksetzen</a>';
|
||||
} elseif ($failCount > 0) {
|
||||
print '<span class="badge badge-status1">'.$failCount.'</span>';
|
||||
} else {
|
||||
print '<span class="badge badge-status4">0</span>';
|
||||
}
|
||||
print '</td></tr>';
|
||||
|
||||
// Last run status
|
||||
print '<tr class="oddeven"><td>Letzter Lauf</td><td>';
|
||||
if (!empty($cronStatus['last_run'])) {
|
||||
$lastRun = $cronStatus['last_run'];
|
||||
$statusClass = 'badge-status4';
|
||||
if ($lastRun['status'] == 'error') $statusClass = 'badge-status8';
|
||||
elseif ($lastRun['status'] == 'paused') $statusClass = 'badge-status1';
|
||||
elseif ($lastRun['status'] == 'running') $statusClass = 'badge-status6';
|
||||
|
||||
print '<span class="badge '.$statusClass.'">'.strtoupper($lastRun['status']).'</span> ';
|
||||
print dol_print_date($lastRun['timestamp'], 'dayhour');
|
||||
if (!empty($lastRun['duration'])) {
|
||||
print ' (Dauer: '.$lastRun['duration'].'s)';
|
||||
}
|
||||
if (!empty($lastRun['message'])) {
|
||||
print '<br><small>'.dol_escape_htmltag($lastRun['message']).'</small>';
|
||||
}
|
||||
} else {
|
||||
print '<span class="opacitymedium">Keine Daten</span>';
|
||||
}
|
||||
print '</td></tr>';
|
||||
|
||||
// Notification
|
||||
print '<tr class="oddeven"><td>Aktuelle Benachrichtigung</td><td>';
|
||||
if (!empty($cronStatus['notification'])) {
|
||||
$notif = $cronStatus['notification'];
|
||||
$notifLabels = array(
|
||||
'tan_required' => 'TAN erforderlich',
|
||||
'login_error' => 'Login-Fehler',
|
||||
'fetch_error' => 'Abruf-Fehler',
|
||||
'config_error' => 'Konfigurationsfehler',
|
||||
'session_expired' => 'Session abgelaufen',
|
||||
'error' => 'Allgemeiner Fehler',
|
||||
'paused' => 'Pausiert'
|
||||
);
|
||||
$label = $notifLabels[$notif['type']] ?? $notif['type'];
|
||||
print '<span class="badge badge-status1" style="background-color: #ff9800;">'.$label.'</span>';
|
||||
if (!empty($notif['date'])) {
|
||||
print ' seit '.dol_print_date($notif['date'], 'dayhour');
|
||||
}
|
||||
} else {
|
||||
print '<span class="badge badge-status4">Keine</span>';
|
||||
}
|
||||
print '</td></tr>';
|
||||
|
||||
print '</table>';
|
||||
|
||||
// Dolibarr Cron Job Status
|
||||
print '<br>';
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre"><th colspan="2">Dolibarr Cron-Job Status</th></tr>';
|
||||
|
||||
if ($cronJob) {
|
||||
print '<tr class="oddeven"><td width="300">Job Status</td><td>';
|
||||
if ($cronJob->processing) {
|
||||
print '<span class="badge badge-status1" style="background-color: #ff9800;">LÄUFT / HÄNGT</span> ';
|
||||
print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?action=resetcronjob">Job zurücksetzen</a>';
|
||||
} elseif ($cronJob->status == 1) {
|
||||
print '<span class="badge badge-status4">Bereit</span>';
|
||||
} else {
|
||||
print '<span class="badge badge-status5">Deaktiviert</span>';
|
||||
}
|
||||
print '</td></tr>';
|
||||
|
||||
print '<tr class="oddeven"><td>Nächster geplanter Lauf</td><td>'.dol_print_date($db->jdate($cronJob->datenextrun), 'dayhour').'</td></tr>';
|
||||
print '<tr class="oddeven"><td>Letzter Lauf (Start)</td><td>'.dol_print_date($db->jdate($cronJob->datelastrun), 'dayhour').'</td></tr>';
|
||||
print '<tr class="oddeven"><td>Letzter Lauf (Ende)</td><td>'.dol_print_date($db->jdate($cronJob->datelastresult), 'dayhour').'</td></tr>';
|
||||
|
||||
print '<tr class="oddeven"><td>Letztes Ergebnis</td><td>';
|
||||
if ($cronJob->lastresult === '0' || $cronJob->lastresult === 0) {
|
||||
print '<span class="badge badge-status4">OK</span>';
|
||||
} elseif (!empty($cronJob->lastresult)) {
|
||||
print '<span class="badge badge-status8">Fehler: '.$cronJob->lastresult.'</span>';
|
||||
} else {
|
||||
print '<span class="opacitymedium">-</span>';
|
||||
}
|
||||
print '</td></tr>';
|
||||
|
||||
print '<tr class="oddeven"><td>Letzte Ausgabe</td><td><pre style="margin:0; white-space: pre-wrap;">'.dol_escape_htmltag($cronJob->lastoutput ?: '-').'</pre></td></tr>';
|
||||
} else {
|
||||
print '<tr class="oddeven"><td colspan="2" class="center opacitymedium">Cron-Job nicht gefunden</td></tr>';
|
||||
}
|
||||
print '</table>';
|
||||
|
||||
// Log file viewer
|
||||
print '<br>';
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th>Cron Log-Datei</th>';
|
||||
print '<th class="right">';
|
||||
print '<form method="get" style="display:inline">';
|
||||
print 'Zeilen: <input type="number" name="lines" value="'.$lines.'" style="width:60px"> ';
|
||||
print '<input type="submit" class="button small" value="Aktualisieren">';
|
||||
print '</form>';
|
||||
print '</th>';
|
||||
print '</tr>';
|
||||
|
||||
$logFile = $conf->bankimport->dir_output.'/logs/cron_bankimport.log';
|
||||
print '<tr class="oddeven"><td colspan="2">';
|
||||
|
||||
if (file_exists($logFile)) {
|
||||
$fileSize = filesize($logFile);
|
||||
print '<small>Datei: '.$logFile.' ('.dol_print_size($fileSize, 1).')</small>';
|
||||
print '<pre style="background:#1e1e1e; color:#d4d4d4; padding:10px; max-height:500px; overflow:auto; font-size:12px; margin-top:5px;">';
|
||||
|
||||
// Read last N lines efficiently
|
||||
$logContent = '';
|
||||
$fp = fopen($logFile, 'r');
|
||||
if ($fp) {
|
||||
$buffer = array();
|
||||
while (!feof($fp)) {
|
||||
$line = fgets($fp);
|
||||
if ($line !== false) {
|
||||
$buffer[] = $line;
|
||||
if (count($buffer) > $lines) {
|
||||
array_shift($buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose($fp);
|
||||
$logContent = implode('', $buffer);
|
||||
}
|
||||
|
||||
// Syntax highlighting for log
|
||||
$logContent = htmlspecialchars($logContent);
|
||||
$logContent = preg_replace('/\[ERROR\]/', '<span style="color:#f44336">[ERROR]</span>', $logContent);
|
||||
$logContent = preg_replace('/\[WARNING\]/', '<span style="color:#ff9800">[WARNING]</span>', $logContent);
|
||||
$logContent = preg_replace('/\[INFO\]/', '<span style="color:#4caf50">[INFO]</span>', $logContent);
|
||||
$logContent = preg_replace('/\[DEBUG\]/', '<span style="color:#9e9e9e">[DEBUG]</span>', $logContent);
|
||||
$logContent = preg_replace('/========== CRON (START|END.*) ==========/', '<span style="color:#2196f3;font-weight:bold">========== CRON $1 ==========</span>', $logContent);
|
||||
|
||||
print $logContent;
|
||||
print '</pre>';
|
||||
} else {
|
||||
print '<span class="opacitymedium">Log-Datei existiert noch nicht: '.$logFile.'</span>';
|
||||
}
|
||||
|
||||
print '</td></tr>';
|
||||
print '</table>';
|
||||
|
||||
// All cron jobs overview
|
||||
print '<br>';
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th>Alle Modul Cron-Jobs</th>';
|
||||
print '<th>Status</th>';
|
||||
print '<th>Nächster Lauf</th>';
|
||||
print '<th>Letzter Lauf</th>';
|
||||
print '<th>Processing</th>';
|
||||
print '</tr>';
|
||||
|
||||
$sql = "SELECT rowid, label, datenextrun, datelastrun, processing, status
|
||||
FROM ".MAIN_DB_PREFIX."cronjob
|
||||
WHERE label LIKE '%Import%' OR label LIKE '%Bank%' OR label LIKE '%Zugferd%' OR label LIKE '%Dump%'
|
||||
ORDER BY label";
|
||||
$resql = $db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$obj->label.'</td>';
|
||||
print '<td>';
|
||||
if ($obj->status == 1) {
|
||||
print '<span class="badge badge-status4">Aktiv</span>';
|
||||
} else {
|
||||
print '<span class="badge badge-status5">Inaktiv</span>';
|
||||
}
|
||||
print '</td>';
|
||||
print '<td>'.dol_print_date($db->jdate($obj->datenextrun), 'dayhour').'</td>';
|
||||
print '<td>'.dol_print_date($db->jdate($obj->datelastrun), 'dayhour').'</td>';
|
||||
print '<td>';
|
||||
if ($obj->processing) {
|
||||
print '<span class="badge badge-status1" style="background-color:#ff9800">HÄNGT</span>';
|
||||
} else {
|
||||
print '<span class="badge badge-status4">OK</span>';
|
||||
}
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
}
|
||||
}
|
||||
print '</table>';
|
||||
|
||||
print dol_get_fiche_end();
|
||||
|
||||
llxFooter();
|
||||
$db->close();
|
||||
BIN
bin/module_bankimport-1.6.zip
Executable file
BIN
bin/module_bankimport-1.6.zip
Executable file
Binary file not shown.
BIN
bin/module_bankimport-1.7.zip
Executable file
BIN
bin/module_bankimport-1.7.zip
Executable file
Binary file not shown.
BIN
bin/module_bankimport-1.8.zip
Executable file
BIN
bin/module_bankimport-1.8.zip
Executable file
Binary file not shown.
BIN
bin/module_bankimport-1.9.zip
Executable file
BIN
bin/module_bankimport-1.9.zip
Executable file
Binary file not shown.
BIN
bin/module_bankimport-2.0.zip
Executable file
BIN
bin/module_bankimport-2.0.zip
Executable file
Binary file not shown.
BIN
bin/module_bankimport-2.1.zip
Executable file
BIN
bin/module_bankimport-2.1.zip
Executable file
Binary file not shown.
BIN
bin/module_bankimport-2.2.zip
Executable file
BIN
bin/module_bankimport-2.2.zip
Executable file
Binary file not shown.
BIN
bin/module_bankimport-2.3.zip
Executable file
BIN
bin/module_bankimport-2.3.zip
Executable file
Binary file not shown.
BIN
bin/module_bankimport-2.4.zip
Executable file
BIN
bin/module_bankimport-2.4.zip
Executable file
Binary file not shown.
BIN
bin/module_bankimport-2.5.zip
Executable file
BIN
bin/module_bankimport-2.5.zip
Executable file
Binary file not shown.
BIN
bin/module_bankimport-2.6.zip
Executable file
BIN
bin/module_bankimport-2.6.zip
Executable file
Binary file not shown.
|
|
@ -43,6 +43,16 @@ class BankImportCron
|
|||
*/
|
||||
public $output = '';
|
||||
|
||||
/**
|
||||
* @var string Path to cron log file
|
||||
*/
|
||||
private $cronLogFile = '';
|
||||
|
||||
/**
|
||||
* @var float Start time of cron execution
|
||||
*/
|
||||
private $startTime = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
|
@ -50,7 +60,63 @@ class BankImportCron
|
|||
*/
|
||||
public function __construct($db)
|
||||
{
|
||||
global $conf;
|
||||
$this->db = $db;
|
||||
|
||||
// Set up dedicated log file for cron jobs
|
||||
$logDir = $conf->bankimport->dir_output.'/logs';
|
||||
if (!is_dir($logDir)) {
|
||||
dol_mkdir($logDir);
|
||||
}
|
||||
$this->cronLogFile = $logDir.'/cron_bankimport.log';
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to dedicated cron log file
|
||||
*
|
||||
* @param string $message Log message
|
||||
* @param string $level Log level (INFO, WARNING, ERROR, DEBUG)
|
||||
* @return void
|
||||
*/
|
||||
private function cronLog($message, $level = 'INFO')
|
||||
{
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$elapsed = $this->startTime > 0 ? round(microtime(true) - $this->startTime, 2).'s' : '0s';
|
||||
$logLine = "[{$timestamp}] [{$level}] [{$elapsed}] {$message}\n";
|
||||
|
||||
// Write to dedicated log file
|
||||
@file_put_contents($this->cronLogFile, $logLine, FILE_APPEND | LOCK_EX);
|
||||
|
||||
// Also log to Dolibarr system log
|
||||
$dolLevel = LOG_INFO;
|
||||
if ($level === 'ERROR') $dolLevel = LOG_ERR;
|
||||
elseif ($level === 'WARNING') $dolLevel = LOG_WARNING;
|
||||
elseif ($level === 'DEBUG') $dolLevel = LOG_DEBUG;
|
||||
|
||||
dol_syslog("BankImportCron: ".$message, $dolLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record cron execution status to database for monitoring
|
||||
*
|
||||
* @param string $status Status (started, running, completed, error)
|
||||
* @param string $message Status message
|
||||
* @return void
|
||||
*/
|
||||
private function recordCronStatus($status, $message = '')
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$statusData = array(
|
||||
'status' => $status,
|
||||
'message' => $message,
|
||||
'timestamp' => time(),
|
||||
'duration' => $this->startTime > 0 ? round(microtime(true) - $this->startTime, 2) : 0,
|
||||
'memory' => memory_get_usage(true),
|
||||
'peak_memory' => memory_get_peak_usage(true)
|
||||
);
|
||||
|
||||
dolibarr_set_const($this->db, 'BANKIMPORT_CRON_STATUS', json_encode($statusData), 'chaine', 0, '', $conf->entity);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -63,33 +129,79 @@ class BankImportCron
|
|||
{
|
||||
global $conf, $langs, $user;
|
||||
|
||||
// Initialize timing
|
||||
$this->startTime = microtime(true);
|
||||
|
||||
// Register shutdown function to catch fatal errors
|
||||
register_shutdown_function(array($this, 'handleShutdown'));
|
||||
|
||||
$langs->load('bankimport@bankimport');
|
||||
|
||||
dol_syslog("BankImportCron::doAutoImport - Starting automatic import", LOG_INFO);
|
||||
$this->cronLog("========== CRON START ==========");
|
||||
$this->cronLog("PHP Version: ".PHP_VERSION.", Memory Limit: ".ini_get('memory_limit').", Max Execution Time: ".ini_get('max_execution_time'));
|
||||
$this->recordCronStatus('started', 'Cron job started');
|
||||
|
||||
// Check if automatic import is enabled
|
||||
if (!getDolGlobalInt('BANKIMPORT_AUTO_ENABLED')) {
|
||||
$this->output = $langs->trans('AutoImportDisabled');
|
||||
dol_syslog("BankImportCron::doAutoImport - Auto import is disabled", LOG_INFO);
|
||||
$this->cronLog("Auto import is disabled - exiting");
|
||||
$this->recordCronStatus('completed', 'Auto import disabled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// CHECK: Is cron paused due to errors? (to prevent bank account lockout)
|
||||
$pausedUntil = getDolGlobalInt('BANKIMPORT_CRON_PAUSED_UNTIL');
|
||||
if ($pausedUntil > 0 && $pausedUntil > time()) {
|
||||
$pauseReason = getDolGlobalString('BANKIMPORT_CRON_PAUSE_REASON');
|
||||
$remainingMinutes = ceil(($pausedUntil - time()) / 60);
|
||||
$this->output = "Cron pausiert für {$remainingMinutes} Minuten: {$pauseReason}";
|
||||
$this->cronLog("Cron is PAUSED until ".date('Y-m-d H:i:s', $pausedUntil)." - Reason: {$pauseReason}", 'WARNING');
|
||||
$this->recordCronStatus('paused', "Paused: {$pauseReason}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Clear pause if expired
|
||||
if ($pausedUntil > 0 && $pausedUntil <= time()) {
|
||||
$this->cronLog("Pause expired - resuming normal operation");
|
||||
dolibarr_del_const($this->db, 'BANKIMPORT_CRON_PAUSED_UNTIL', $conf->entity);
|
||||
dolibarr_del_const($this->db, 'BANKIMPORT_CRON_PAUSE_REASON', $conf->entity);
|
||||
dolibarr_del_const($this->db, 'BANKIMPORT_CRON_FAIL_COUNT', $conf->entity);
|
||||
}
|
||||
|
||||
// Check consecutive failure count
|
||||
$failCount = getDolGlobalInt('BANKIMPORT_CRON_FAIL_COUNT');
|
||||
if ($failCount >= 3) {
|
||||
// After 3 consecutive failures, pause for increasing time
|
||||
$pauseMinutes = min(60 * 24, 15 * pow(2, $failCount - 3)); // 15min, 30min, 1h, 2h, ... max 24h
|
||||
$this->pauseCron("Zu viele Fehlversuche ({$failCount}x) - Bitte manuell prüfen", $pauseMinutes);
|
||||
$this->output = "Automatisch pausiert nach {$failCount} Fehlversuchen";
|
||||
$this->cronLog("Auto-paused after {$failCount} failures for {$pauseMinutes} minutes", 'WARNING');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->cronLog("Initializing FinTS library");
|
||||
$this->recordCronStatus('running', 'Initializing FinTS');
|
||||
|
||||
// Initialize FinTS
|
||||
$fints = new BankImportFinTS($this->db);
|
||||
|
||||
if (!$fints->isConfigured()) {
|
||||
$this->error = $langs->trans('AutoImportNotConfigured');
|
||||
dol_syslog("BankImportCron::doAutoImport - FinTS not configured", LOG_WARNING);
|
||||
$this->cronLog("FinTS not configured", 'WARNING');
|
||||
$this->setNotification('config_error');
|
||||
$this->recordCronStatus('error', 'FinTS not configured');
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!$fints->isLibraryAvailable()) {
|
||||
$this->error = $langs->trans('FinTSLibraryNotFound');
|
||||
dol_syslog("BankImportCron::doAutoImport - Library not found", LOG_ERR);
|
||||
$this->cronLog("FinTS library not found", 'ERROR');
|
||||
$this->recordCronStatus('error', 'FinTS library not found');
|
||||
return -1;
|
||||
}
|
||||
|
||||
$this->cronLog("FinTS library loaded successfully");
|
||||
|
||||
// Check for stored session state (from previous successful TAN)
|
||||
$storedState = getDolGlobalString('BANKIMPORT_CRON_STATE');
|
||||
$sessionRestored = false;
|
||||
|
|
@ -97,39 +209,53 @@ class BankImportCron
|
|||
try {
|
||||
// Try to restore previous session
|
||||
if (!empty($storedState)) {
|
||||
dol_syslog("BankImportCron::doAutoImport - Attempting to restore previous session (state length: ".strlen($storedState).")", LOG_INFO);
|
||||
$this->cronLog("Attempting to restore previous session (state length: ".strlen($storedState).")");
|
||||
$this->recordCronStatus('running', 'Restoring session');
|
||||
$restoreResult = $fints->restore($storedState);
|
||||
if ($restoreResult < 0) {
|
||||
// Session expired, need fresh login
|
||||
dol_syslog("BankImportCron::doAutoImport - Session restore failed: ".$fints->error.", trying fresh login", LOG_WARNING);
|
||||
$this->cronLog("Session restore failed: ".$fints->error.", trying fresh login", 'WARNING');
|
||||
$storedState = '';
|
||||
// Clear stale session
|
||||
dolibarr_del_const($this->db, 'BANKIMPORT_CRON_STATE', $conf->entity);
|
||||
} else {
|
||||
dol_syslog("BankImportCron::doAutoImport - Session restored successfully", LOG_INFO);
|
||||
$this->cronLog("Session restored successfully");
|
||||
$sessionRestored = true;
|
||||
}
|
||||
} else {
|
||||
dol_syslog("BankImportCron::doAutoImport - No stored session found", LOG_INFO);
|
||||
$this->cronLog("No stored session found");
|
||||
}
|
||||
|
||||
// If no stored session or restore failed, try fresh login
|
||||
if (empty($storedState)) {
|
||||
dol_syslog("BankImportCron::doAutoImport - Attempting fresh login", LOG_INFO);
|
||||
$this->cronLog("Attempting fresh login to bank");
|
||||
$this->recordCronStatus('running', 'Logging in to bank');
|
||||
$loginResult = $fints->login();
|
||||
|
||||
if ($loginResult < 0) {
|
||||
$this->error = $langs->trans('LoginFailed').': '.$fints->error;
|
||||
dol_syslog("BankImportCron::doAutoImport - Login failed: ".$fints->error, LOG_ERR);
|
||||
$this->cronLog("Login failed: ".$fints->error, 'ERROR');
|
||||
$this->setNotification('login_error');
|
||||
$this->recordCronStatus('error', 'Login failed: '.$fints->error);
|
||||
|
||||
// Increment failure count and potentially pause to prevent lockout
|
||||
$this->incrementFailCount();
|
||||
|
||||
// Check if this is a critical auth error that should pause immediately
|
||||
if ($this->isAuthError($fints->error)) {
|
||||
$this->pauseCron("Bank-Login fehlgeschlagen - Zugangsdaten prüfen!", 60); // Pause 1 hour
|
||||
$this->cronLog("CRITICAL: Auth error detected - pausing cron for 1 hour to prevent lockout", 'ERROR');
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ($loginResult == 0) {
|
||||
// TAN required - can't proceed automatically
|
||||
$this->output = $langs->trans('TANRequired');
|
||||
dol_syslog("BankImportCron::doAutoImport - TAN required for login, cannot proceed automatically", LOG_WARNING);
|
||||
$this->cronLog("TAN required for login - cannot proceed automatically", 'WARNING');
|
||||
$this->setNotification('tan_required');
|
||||
$this->recordCronStatus('completed', 'TAN required - waiting for user');
|
||||
|
||||
// Store the state so user can complete TAN manually
|
||||
$state = $fints->persist();
|
||||
|
|
@ -137,10 +263,11 @@ class BankImportCron
|
|||
dolibarr_set_const($this->db, 'BANKIMPORT_CRON_PENDING_STATE', $state, 'chaine', 0, '', $conf->entity);
|
||||
dolibarr_set_const($this->db, 'BANKIMPORT_CRON_PENDING_ACTION', serialize($fints->getPendingAction()), 'chaine', 0, '', $conf->entity);
|
||||
}
|
||||
$this->cronLog("========== CRON END (TAN required) ==========");
|
||||
return 0; // Not an error, just can't proceed
|
||||
}
|
||||
|
||||
dol_syslog("BankImportCron::doAutoImport - Fresh login successful", LOG_INFO);
|
||||
$this->cronLog("Fresh login successful");
|
||||
}
|
||||
|
||||
// Login successful or session restored - fetch statements
|
||||
|
|
@ -148,7 +275,8 @@ class BankImportCron
|
|||
$dateFrom = strtotime("-{$daysToFetch} days");
|
||||
$dateTo = time();
|
||||
|
||||
dol_syslog("BankImportCron::doAutoImport - Fetching statements from ".date('Y-m-d', $dateFrom)." to ".date('Y-m-d', $dateTo)." ({$daysToFetch} days)", LOG_INFO);
|
||||
$this->cronLog("Fetching statements from ".date('Y-m-d', $dateFrom)." to ".date('Y-m-d', $dateTo)." ({$daysToFetch} days)");
|
||||
$this->recordCronStatus('running', 'Fetching bank statements');
|
||||
|
||||
$result = $fints->fetchStatements($dateFrom, $dateTo);
|
||||
|
||||
|
|
@ -157,16 +285,17 @@ class BankImportCron
|
|||
$txCount = count($result['transactions'] ?? array());
|
||||
$hasBalance = !empty($result['balance']);
|
||||
$isPartial = !empty($result['partial']);
|
||||
dol_syslog("BankImportCron::doAutoImport - fetchStatements returned array: transactions={$txCount}, hasBalance=".($hasBalance?'yes':'no').", partial=".($isPartial?'yes':'no'), LOG_INFO);
|
||||
$this->cronLog("fetchStatements returned: transactions={$txCount}, hasBalance=".($hasBalance?'yes':'no').", partial=".($isPartial?'yes':'no'));
|
||||
} else {
|
||||
dol_syslog("BankImportCron::doAutoImport - fetchStatements returned: ".var_export($result, true), LOG_INFO);
|
||||
$this->cronLog("fetchStatements returned: ".var_export($result, true));
|
||||
}
|
||||
|
||||
if ($result === 0) {
|
||||
// TAN required for statements
|
||||
$this->output = $langs->trans('TANRequired');
|
||||
dol_syslog("BankImportCron::doAutoImport - TAN required for statements", LOG_WARNING);
|
||||
$this->cronLog("TAN required for statements", 'WARNING');
|
||||
$this->setNotification('tan_required');
|
||||
$this->recordCronStatus('completed', 'TAN required for statements');
|
||||
|
||||
// Store state for manual completion
|
||||
$state = $fints->persist();
|
||||
|
|
@ -174,13 +303,15 @@ class BankImportCron
|
|||
dolibarr_set_const($this->db, 'BANKIMPORT_CRON_PENDING_STATE', $state, 'chaine', 0, '', $conf->entity);
|
||||
dolibarr_set_const($this->db, 'BANKIMPORT_CRON_PENDING_ACTION', serialize($fints->getPendingAction()), 'chaine', 0, '', $conf->entity);
|
||||
}
|
||||
$this->cronLog("========== CRON END (TAN required) ==========");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($result < 0) {
|
||||
$this->error = $langs->trans('FetchFailed').': '.$fints->error;
|
||||
dol_syslog("BankImportCron::doAutoImport - Fetch failed: ".$fints->error, LOG_ERR);
|
||||
$this->cronLog("Fetch failed: ".$fints->error, 'ERROR');
|
||||
$this->setNotification('fetch_error');
|
||||
$this->recordCronStatus('error', 'Fetch failed: '.$fints->error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -190,12 +321,12 @@ class BankImportCron
|
|||
$importedCount = 0;
|
||||
$skippedCount = 0;
|
||||
|
||||
dol_syslog("BankImportCron::doAutoImport - Bank returned {$fetchedCount} transactions", LOG_INFO);
|
||||
$this->cronLog("Bank returned {$fetchedCount} transactions");
|
||||
|
||||
// If restored session returned 0 transactions, the session might be stale
|
||||
// Try fresh login as fallback
|
||||
if ($fetchedCount == 0 && $sessionRestored) {
|
||||
dol_syslog("BankImportCron::doAutoImport - Restored session returned 0 transactions, clearing stale session", LOG_WARNING);
|
||||
$this->cronLog("Restored session returned 0 transactions - session might be stale", 'WARNING');
|
||||
dolibarr_del_const($this->db, 'BANKIMPORT_CRON_STATE', $conf->entity);
|
||||
|
||||
// Note: We don't retry here because a fresh login would require TAN
|
||||
|
|
@ -203,11 +334,15 @@ class BankImportCron
|
|||
$this->output = $langs->trans('AutoImportNoTransactions').' (Session abgelaufen - nächster Lauf erfordert TAN)';
|
||||
|
||||
$this->setNotification('session_expired');
|
||||
|
||||
$this->recordCronStatus('completed', 'Session expired - no transactions');
|
||||
$this->cronLog("========== CRON END (session expired) ==========");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!empty($transactions)) {
|
||||
$this->cronLog("Starting import of {$fetchedCount} transactions");
|
||||
$this->recordCronStatus('running', "Importing {$fetchedCount} transactions");
|
||||
|
||||
// Get a system user for the import
|
||||
$importUser = new User($this->db);
|
||||
$importUser->fetch(1); // Admin user
|
||||
|
|
@ -221,7 +356,7 @@ class BankImportCron
|
|||
$importedCount = $importResult['imported'] ?? 0;
|
||||
$skippedCount = $importResult['skipped'] ?? 0;
|
||||
|
||||
dol_syslog("BankImportCron::doAutoImport - Import result: imported={$importedCount}, skipped={$skippedCount}", LOG_INFO);
|
||||
$this->cronLog("Import result: imported={$importedCount}, skipped={$skippedCount}");
|
||||
}
|
||||
|
||||
// Update last fetch info
|
||||
|
|
@ -232,6 +367,7 @@ class BankImportCron
|
|||
$state = $fints->persist();
|
||||
if (!empty($state)) {
|
||||
dolibarr_set_const($this->db, 'BANKIMPORT_CRON_STATE', $state, 'chaine', 0, '', $conf->entity);
|
||||
$this->cronLog("Session state saved for next run");
|
||||
}
|
||||
|
||||
// Clear any pending state
|
||||
|
|
@ -251,20 +387,199 @@ class BankImportCron
|
|||
$this->output = $langs->trans('AutoImportNoTransactions');
|
||||
}
|
||||
|
||||
dol_syslog("BankImportCron::doAutoImport - Completed: imported={$importedCount}, skipped={$skippedCount}", LOG_INFO);
|
||||
// Reset failure count on success
|
||||
$this->resetFailCount();
|
||||
|
||||
$duration = round(microtime(true) - $this->startTime, 2);
|
||||
$this->cronLog("Completed successfully: imported={$importedCount}, skipped={$skippedCount}, duration={$duration}s");
|
||||
$this->recordCronStatus('completed', "Success: imported={$importedCount}, skipped={$skippedCount}");
|
||||
$this->cronLog("========== CRON END (success) ==========");
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->error = 'Exception: '.$e->getMessage();
|
||||
dol_syslog("BankImportCron::doAutoImport - Exception: ".$e->getMessage(), LOG_ERR);
|
||||
$this->cronLog("EXCEPTION: ".$e->getMessage()."\nStack trace:\n".$e->getTraceAsString(), 'ERROR');
|
||||
$this->setNotification('error');
|
||||
$this->recordCronStatus('error', 'Exception: '.$e->getMessage());
|
||||
$this->incrementFailCount();
|
||||
$this->cronLog("========== CRON END (exception) ==========");
|
||||
return -1;
|
||||
} catch (Throwable $t) {
|
||||
$this->error = 'Fatal error: '.$t->getMessage();
|
||||
$this->cronLog("FATAL ERROR: ".$t->getMessage()."\nStack trace:\n".$t->getTraceAsString(), 'ERROR');
|
||||
$this->setNotification('error');
|
||||
$this->recordCronStatus('error', 'Fatal: '.$t->getMessage());
|
||||
$this->incrementFailCount();
|
||||
$this->cronLog("========== CRON END (fatal error) ==========");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause cron execution for specified minutes
|
||||
*
|
||||
* @param string $reason Reason for pausing
|
||||
* @param int $minutes Minutes to pause
|
||||
* @return void
|
||||
*/
|
||||
private function pauseCron($reason, $minutes = 60)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$pauseUntil = time() + ($minutes * 60);
|
||||
dolibarr_set_const($this->db, 'BANKIMPORT_CRON_PAUSED_UNTIL', $pauseUntil, 'chaine', 0, '', $conf->entity);
|
||||
dolibarr_set_const($this->db, 'BANKIMPORT_CRON_PAUSE_REASON', $reason, 'chaine', 0, '', $conf->entity);
|
||||
|
||||
$this->cronLog("CRON PAUSED until ".date('Y-m-d H:i:s', $pauseUntil)." - Reason: {$reason}", 'WARNING');
|
||||
$this->setNotification('paused');
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment consecutive failure count
|
||||
*
|
||||
* @return int New failure count
|
||||
*/
|
||||
private function incrementFailCount()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$count = getDolGlobalInt('BANKIMPORT_CRON_FAIL_COUNT') + 1;
|
||||
dolibarr_set_const($this->db, 'BANKIMPORT_CRON_FAIL_COUNT', $count, 'chaine', 0, '', $conf->entity);
|
||||
$this->cronLog("Failure count incremented to {$count}");
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset failure count (on success)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function resetFailCount()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$oldCount = getDolGlobalInt('BANKIMPORT_CRON_FAIL_COUNT');
|
||||
if ($oldCount > 0) {
|
||||
dolibarr_del_const($this->db, 'BANKIMPORT_CRON_FAIL_COUNT', $conf->entity);
|
||||
$this->cronLog("Failure count reset (was {$oldCount})");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if error message indicates an authentication error
|
||||
* These errors should pause immediately to prevent account lockout
|
||||
*
|
||||
* @param string $error Error message
|
||||
* @return bool True if this is an auth-related error
|
||||
*/
|
||||
private function isAuthError($error)
|
||||
{
|
||||
$authKeywords = array(
|
||||
'authentication',
|
||||
'authentifizierung',
|
||||
'passwort',
|
||||
'password',
|
||||
'pin',
|
||||
'credentials',
|
||||
'zugangsdaten',
|
||||
'gesperrt',
|
||||
'locked',
|
||||
'blocked',
|
||||
'invalid user',
|
||||
'ungültiger benutzer',
|
||||
'zugang verweigert',
|
||||
'access denied',
|
||||
'not authorized',
|
||||
'nicht autorisiert'
|
||||
);
|
||||
|
||||
$errorLower = strtolower($error);
|
||||
foreach ($authKeywords as $keyword) {
|
||||
if (strpos($errorLower, $keyword) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually unpause the cron (called from admin interface)
|
||||
*
|
||||
* @return bool Success
|
||||
*/
|
||||
public function unpauseCron()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
dolibarr_del_const($this->db, 'BANKIMPORT_CRON_PAUSED_UNTIL', $conf->entity);
|
||||
dolibarr_del_const($this->db, 'BANKIMPORT_CRON_PAUSE_REASON', $conf->entity);
|
||||
dolibarr_del_const($this->db, 'BANKIMPORT_CRON_FAIL_COUNT', $conf->entity);
|
||||
$this->clearNotification();
|
||||
|
||||
$this->cronLog("Cron manually unpaused by admin");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current cron status for display
|
||||
*
|
||||
* @return array Status information
|
||||
*/
|
||||
public static function getCronStatus()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$status = array(
|
||||
'enabled' => getDolGlobalInt('BANKIMPORT_AUTO_ENABLED') > 0,
|
||||
'paused' => false,
|
||||
'paused_until' => 0,
|
||||
'pause_reason' => '',
|
||||
'fail_count' => getDolGlobalInt('BANKIMPORT_CRON_FAIL_COUNT'),
|
||||
'last_run' => null,
|
||||
'notification' => null
|
||||
);
|
||||
|
||||
// Check if paused
|
||||
$pausedUntil = getDolGlobalInt('BANKIMPORT_CRON_PAUSED_UNTIL');
|
||||
if ($pausedUntil > 0 && $pausedUntil > time()) {
|
||||
$status['paused'] = true;
|
||||
$status['paused_until'] = $pausedUntil;
|
||||
$status['pause_reason'] = getDolGlobalString('BANKIMPORT_CRON_PAUSE_REASON');
|
||||
}
|
||||
|
||||
// Get last run status
|
||||
$lastStatus = getDolGlobalString('BANKIMPORT_CRON_STATUS');
|
||||
if (!empty($lastStatus)) {
|
||||
$status['last_run'] = json_decode($lastStatus, true);
|
||||
}
|
||||
|
||||
// Get notification
|
||||
$status['notification'] = self::getNotification();
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown handler to catch fatal errors
|
||||
* Called automatically by PHP when script ends
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handleShutdown()
|
||||
{
|
||||
$error = error_get_last();
|
||||
if ($error !== null && in_array($error['type'], array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) {
|
||||
$message = "FATAL SHUTDOWN: {$error['message']} in {$error['file']}:{$error['line']}";
|
||||
$this->cronLog($message, 'ERROR');
|
||||
$this->recordCronStatus('error', $message);
|
||||
$this->cronLog("========== CRON END (fatal shutdown) ==========");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set notification flag for admin users
|
||||
* Also sends to GlobalNotify if available
|
||||
*
|
||||
* @param string $type Notification type (tan_required, error, etc.)
|
||||
* @return void
|
||||
|
|
@ -275,6 +590,47 @@ class BankImportCron
|
|||
|
||||
dolibarr_set_const($this->db, 'BANKIMPORT_NOTIFICATION', $type, 'chaine', 0, '', $conf->entity);
|
||||
dolibarr_set_const($this->db, 'BANKIMPORT_NOTIFICATION_DATE', time(), 'chaine', 0, '', $conf->entity);
|
||||
|
||||
// Send to GlobalNotify if module is enabled
|
||||
if (isModEnabled('globalnotify')) {
|
||||
dol_include_once('/globalnotify/class/globalnotify.class.php');
|
||||
if (class_exists('GlobalNotify')) {
|
||||
$messages = array(
|
||||
'tan_required' => array('TAN erforderlich', 'Bank-Login erfordert TAN-Bestätigung', 'action'),
|
||||
'login_error' => array('Login-Fehler', 'Bank-Login fehlgeschlagen - Zugangsdaten prüfen', 'error'),
|
||||
'fetch_error' => array('Abruf-Fehler', 'Kontoauszüge konnten nicht abgerufen werden', 'error'),
|
||||
'config_error' => array('Konfigurationsfehler', 'FinTS ist nicht korrekt konfiguriert', 'error'),
|
||||
'session_expired' => array('Session abgelaufen', 'Bank-Session ist abgelaufen, neuer Login erforderlich', 'warning'),
|
||||
'paused' => array('Cron pausiert', 'BankImport Cron wurde automatisch pausiert', 'warning'),
|
||||
'error' => array('Allgemeiner Fehler', 'Ein Fehler ist aufgetreten', 'error'),
|
||||
);
|
||||
|
||||
if (isset($messages[$type])) {
|
||||
$msg = $messages[$type];
|
||||
$actionUrl = dol_buildpath('/bankimport/admin/cronmonitor.php', 1);
|
||||
$actionLabel = 'Details anzeigen';
|
||||
|
||||
if ($type == 'tan_required') {
|
||||
$actionUrl = dol_buildpath('/bankimport/fetch.php', 1);
|
||||
$actionLabel = 'TAN eingeben';
|
||||
}
|
||||
|
||||
$notify = new GlobalNotify($this->db);
|
||||
$notifyType = $msg[2] == 'action' ? GlobalNotify::TYPE_ACTION :
|
||||
($msg[2] == 'error' ? GlobalNotify::TYPE_ERROR : GlobalNotify::TYPE_WARNING);
|
||||
|
||||
$notify->addNotification(
|
||||
'bankimport',
|
||||
$notifyType,
|
||||
$msg[0],
|
||||
$msg[1],
|
||||
$actionUrl,
|
||||
$actionLabel,
|
||||
$msg[2] == 'error' ? 10 : ($msg[2] == 'action' ? 9 : 7)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ class modBankImport extends DolibarrModules
|
|||
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@bankimport'
|
||||
|
||||
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||
$this->version = '2.6';
|
||||
$this->version = '2.7';
|
||||
// Url to the file with your last numberversion of this module
|
||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,17 @@ function bankimportAdminPrepareHead()
|
|||
$head[$h][2] = 'settings';
|
||||
$h++;
|
||||
|
||||
$head[$h][0] = dol_buildpath("/bankimport/admin/cronmonitor.php", 1);
|
||||
$head[$h][1] = $langs->trans("CronMonitor");
|
||||
// Add warning badge if there's a problem
|
||||
dol_include_once('/bankimport/class/bankimportcron.class.php');
|
||||
$cronStatus = BankImportCron::getCronStatus();
|
||||
if ($cronStatus['paused'] || $cronStatus['fail_count'] >= 3 || !empty($cronStatus['notification'])) {
|
||||
$head[$h][1] .= ' <span class="badge badge-warning">!</span>';
|
||||
}
|
||||
$head[$h][2] = 'cronmonitor';
|
||||
$h++;
|
||||
|
||||
/*
|
||||
$head[$h][0] = dol_buildpath("/bankimport/admin/myobject_extrafields.php", 1);
|
||||
$head[$h][1] = $langs->trans("ExtraFields");
|
||||
|
|
|
|||
0
repair.php
Normal file → Executable file
0
repair.php
Normal file → Executable file
0
sql/update_1.7.sql
Normal file → Executable file
0
sql/update_1.7.sql
Normal file → Executable file
Loading…
Reference in a new issue