Compare commits
2 commits
f340ba2da5
...
6e4b5b0de2
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e4b5b0de2 | |||
| 717ae539d3 |
4 changed files with 122 additions and 176 deletions
23
CHANGELOG.md
23
CHANGELOG.md
|
|
@ -2,6 +2,29 @@
|
||||||
|
|
||||||
Alle wesentlichen Änderungen an diesem Projekt werden in dieser Datei dokumentiert.
|
Alle wesentlichen Änderungen an diesem Projekt werden in dieser Datei dokumentiert.
|
||||||
|
|
||||||
|
## [2.9] - 2026-02-23
|
||||||
|
|
||||||
|
### Entfernt
|
||||||
|
- **Browser Push-Notifications**: Entfernt zugunsten von GlobalNotify
|
||||||
|
- `bankimport_notify.js.php` gelöscht
|
||||||
|
- Benachrichtigungen laufen jetzt zentral über GlobalNotify-Widget
|
||||||
|
|
||||||
|
## [2.8] - 2026-02-23
|
||||||
|
|
||||||
|
### Hinzugefügt
|
||||||
|
- **GlobalNotify Integration**: Benachrichtigungen über das zentrale GlobalNotify-Modul
|
||||||
|
- TAN-Anforderung: Sofortige Benachrichtigung wenn Bank TAN verlangt
|
||||||
|
- Login-Fehler: Warnung bei fehlgeschlagenem Bank-Login
|
||||||
|
- Session abgelaufen: Info wenn neue Authentifizierung nötig
|
||||||
|
- Cron pausiert: Warnung wenn automatischer Import pausiert wurde
|
||||||
|
- **Zahlungsabgleich-Benachrichtigungen**:
|
||||||
|
- Info über neue importierte Bankbuchungen
|
||||||
|
- Aktion erforderlich wenn unzugeordnete Buchungen warten
|
||||||
|
- **Helper-Funktion**: `BankImportTransaction::notify()` für sichere GlobalNotify-Nutzung
|
||||||
|
|
||||||
|
### Hinweis
|
||||||
|
GlobalNotify ist optional. Ohne das Modul werden Benachrichtigungen ins Dolibarr-Log geschrieben.
|
||||||
|
|
||||||
## [2.7] - 2026-02-23
|
## [2.7] - 2026-02-23
|
||||||
|
|
||||||
### Hinzugefügt
|
### Hinzugefügt
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,59 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
|
||||||
*/
|
*/
|
||||||
class BankImportTransaction extends CommonObject
|
class BankImportTransaction extends CommonObject
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Send notification via GlobalNotify (if available)
|
||||||
|
*
|
||||||
|
* @param string $type 'error', 'warning', 'info', 'action'
|
||||||
|
* @param string $title Title
|
||||||
|
* @param string $message Message
|
||||||
|
* @param string $actionUrl URL for action button
|
||||||
|
* @param string $actionLabel Label for action button
|
||||||
|
* @return bool True if sent via GlobalNotify
|
||||||
|
*/
|
||||||
|
public static function notify($type, $title, $message, $actionUrl = '', $actionLabel = '')
|
||||||
|
{
|
||||||
|
global $db;
|
||||||
|
|
||||||
|
if (!isModEnabled('globalnotify')) {
|
||||||
|
dol_syslog("BankImport [{$type}]: {$title} - {$message}", LOG_WARNING);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classFile = dol_buildpath('/globalnotify/class/globalnotify.class.php', 0);
|
||||||
|
if (!file_exists($classFile)) {
|
||||||
|
dol_syslog("BankImport [{$type}]: {$title} - {$message}", LOG_WARNING);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once $classFile;
|
||||||
|
|
||||||
|
if (!class_exists('GlobalNotify')) {
|
||||||
|
dol_syslog("BankImport [{$type}]: {$title} - {$message}", LOG_WARNING);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch ($type) {
|
||||||
|
case 'error':
|
||||||
|
GlobalNotify::error('bankimport', $title, $message, $actionUrl, $actionLabel);
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
GlobalNotify::warning('bankimport', $title, $message, $actionUrl, $actionLabel);
|
||||||
|
break;
|
||||||
|
case 'action':
|
||||||
|
GlobalNotify::actionRequired('bankimport', $title, $message, $actionUrl, $actionLabel ?: 'Aktion erforderlich');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
GlobalNotify::info('bankimport', $title, $message, $actionUrl, $actionLabel);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
dol_syslog("GlobalNotify error: ".$e->getMessage(), LOG_ERR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string ID to identify managed object
|
* @var string ID to identify managed object
|
||||||
*/
|
*/
|
||||||
|
|
@ -528,6 +581,31 @@ class BankImportTransaction extends CommonObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send notification if new transactions were imported
|
||||||
|
if ($imported > 0) {
|
||||||
|
// Count unmatched transactions
|
||||||
|
$unmatchedCount = $this->countUnmatchedTransactions();
|
||||||
|
|
||||||
|
if ($unmatchedCount > 0) {
|
||||||
|
self::notify(
|
||||||
|
'action',
|
||||||
|
$imported.' neue Bankbuchungen',
|
||||||
|
"{$unmatchedCount} Buchungen warten auf Zuordnung zu Rechnungen",
|
||||||
|
dol_buildpath('/bankimport/list.php?status=0', 1),
|
||||||
|
'Zahlungsabgleich'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// All matched - just info
|
||||||
|
self::notify(
|
||||||
|
'info',
|
||||||
|
$imported.' Bankbuchungen importiert',
|
||||||
|
'Alle Buchungen wurden automatisch zugeordnet',
|
||||||
|
dol_buildpath('/bankimport/list.php', 1),
|
||||||
|
'Anzeigen'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'imported' => $imported,
|
'imported' => $imported,
|
||||||
'skipped' => $skipped,
|
'skipped' => $skipped,
|
||||||
|
|
@ -536,6 +614,25 @@ class BankImportTransaction extends CommonObject
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count unmatched/new transactions waiting for reconciliation
|
||||||
|
*
|
||||||
|
* @return int Number of unmatched transactions
|
||||||
|
*/
|
||||||
|
public function countUnmatchedTransactions()
|
||||||
|
{
|
||||||
|
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."bankimport_transaction";
|
||||||
|
$sql .= " WHERE status = ".self::STATUS_NEW;
|
||||||
|
$sql .= " AND entity = ".((int) $this->entity);
|
||||||
|
|
||||||
|
$resql = $this->db->query($sql);
|
||||||
|
if ($resql) {
|
||||||
|
$obj = $this->db->fetch_object($resql);
|
||||||
|
return (int) $obj->cnt;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch list of transactions with filters
|
* Fetch list of transactions with filters
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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'
|
$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'
|
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||||
$this->version = '2.7';
|
$this->version = '2.9';
|
||||||
// Url to the file with your last numberversion of this module
|
// Url to the file with your last numberversion of this module
|
||||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@ class modBankImport extends DolibarrModules
|
||||||
),
|
),
|
||||||
// Set this to relative path of js file if module must load a js on all pages
|
// Set this to relative path of js file if module must load a js on all pages
|
||||||
'js' => array(
|
'js' => array(
|
||||||
'/bankimport/js/bankimport_notify.js.php',
|
// Browser notifications removed - GlobalNotify handles this now
|
||||||
),
|
),
|
||||||
// Set here all hooks context managed by module. To find available hook context, make a "grep -r '>initHooks(' *" on source code. You can also set hook context to 'all'
|
// Set here all hooks context managed by module. To find available hook context, make a "grep -r '>initHooks(' *" on source code. You can also set hook context to 'all'
|
||||||
/* BEGIN MODULEBUILDER HOOKSCONTEXTS */
|
/* BEGIN MODULEBUILDER HOOKSCONTEXTS */
|
||||||
|
|
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
<?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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JavaScript for browser push notifications about incoming payments
|
|
||||||
* Loaded on every Dolibarr page via module_parts['js']
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Define MIME type
|
|
||||||
if (!defined('NOTOKENRENEWAL')) {
|
|
||||||
define('NOTOKENRENEWAL', '1');
|
|
||||||
}
|
|
||||||
if (!defined('NOREQUIREMENU')) {
|
|
||||||
define('NOREQUIREMENU', '1');
|
|
||||||
}
|
|
||||||
if (!defined('NOREQUIREHTML')) {
|
|
||||||
define('NOREQUIREHTML', '1');
|
|
||||||
}
|
|
||||||
if (!defined('NOREQUIREAJAX')) {
|
|
||||||
define('NOREQUIREAJAX', '1');
|
|
||||||
}
|
|
||||||
|
|
||||||
$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";
|
|
||||||
}
|
|
||||||
|
|
||||||
header('Content-Type: application/javascript; charset=UTF-8');
|
|
||||||
header('Cache-Control: max-age=3600');
|
|
||||||
|
|
||||||
if (!isModEnabled('bankimport') || empty($user->id) || !$user->hasRight('bankimport', 'read')) {
|
|
||||||
echo '/* bankimport: no access */';
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$checkUrl = dol_buildpath('/bankimport/ajax/checkpending.php', 1);
|
|
||||||
$confirmUrl = dol_buildpath('/bankimport/confirm.php', 1).'?mainmenu=bank&leftmenu=bankimport';
|
|
||||||
$checkInterval = 5 * 60 * 1000; // 5 Minuten
|
|
||||||
?>
|
|
||||||
(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var STORAGE_KEY = 'bankimport_last_pending';
|
|
||||||
var CHECK_URL = <?php echo json_encode($checkUrl); ?>;
|
|
||||||
var CONFIRM_URL = <?php echo json_encode($confirmUrl); ?>;
|
|
||||||
var CHECK_INTERVAL = <?php echo $checkInterval; ?>;
|
|
||||||
|
|
||||||
// Erst nach Seitenload starten
|
|
||||||
if (document.readyState === 'loading') {
|
|
||||||
document.addEventListener('DOMContentLoaded', init);
|
|
||||||
} else {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
// Berechtigung anfragen beim ersten Mal
|
|
||||||
if ('Notification' in window && Notification.permission === 'default') {
|
|
||||||
// Dezent um Berechtigung bitten - nicht sofort, sondern nach 10 Sekunden
|
|
||||||
setTimeout(function() {
|
|
||||||
Notification.requestPermission();
|
|
||||||
}, 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sofort prüfen
|
|
||||||
checkPending();
|
|
||||||
|
|
||||||
// Regelmäßig prüfen
|
|
||||||
setInterval(checkPending, CHECK_INTERVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkPending() {
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('GET', CHECK_URL, true);
|
|
||||||
xhr.timeout = 15000;
|
|
||||||
|
|
||||||
xhr.onload = function() {
|
|
||||||
if (xhr.status !== 200) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
var data = JSON.parse(xhr.responseText);
|
|
||||||
} catch(e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastKnown = parseInt(localStorage.getItem(STORAGE_KEY) || '0', 10);
|
|
||||||
var currentPending = data.pending || 0;
|
|
||||||
var incoming = data.incoming || 0;
|
|
||||||
var incomingTotal = data.incoming_total || 0;
|
|
||||||
|
|
||||||
// Neue Buchungen seit letztem Check?
|
|
||||||
if (currentPending > lastKnown && currentPending > 0) {
|
|
||||||
var newCount = currentPending - lastKnown;
|
|
||||||
|
|
||||||
if (incoming > 0) {
|
|
||||||
showNotification(
|
|
||||||
'Zahlungseingang',
|
|
||||||
incoming + ' Zahlungseingang' + (incoming > 1 ? 'e' : '') + ' (' + formatAmount(incomingTotal) + ' €)\nBestätigung erforderlich',
|
|
||||||
'incoming'
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
showNotification(
|
|
||||||
'Bankimport',
|
|
||||||
newCount + ' neue Buchung' + (newCount > 1 ? 'en' : '') + ' warten auf Zuordnung',
|
|
||||||
'pending'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aktuellen Stand merken
|
|
||||||
localStorage.setItem(STORAGE_KEY, currentPending.toString());
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onerror = function() {};
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showNotification(title, body, type) {
|
|
||||||
if (!('Notification' in window)) return;
|
|
||||||
if (Notification.permission !== 'granted') return;
|
|
||||||
|
|
||||||
var icon = type === 'incoming'
|
|
||||||
? '/theme/common/mime/money.png'
|
|
||||||
: '/theme/common/mime/doc.png';
|
|
||||||
|
|
||||||
var notification = new Notification(title, {
|
|
||||||
body: body,
|
|
||||||
icon: icon,
|
|
||||||
tag: 'bankimport-' + type,
|
|
||||||
requireInteraction: true
|
|
||||||
});
|
|
||||||
|
|
||||||
notification.onclick = function() {
|
|
||||||
window.focus();
|
|
||||||
window.location.href = CONFIRM_URL;
|
|
||||||
notification.close();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Nach 30 Sekunden automatisch schließen
|
|
||||||
setTimeout(function() {
|
|
||||||
notification.close();
|
|
||||||
}, 30000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatAmount(amount) {
|
|
||||||
return parseFloat(amount).toFixed(2).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
||||||
Loading…
Reference in a new issue