Version 1.5: Dashboard-Widget und Browser-Benachrichtigungen
- Dashboard-Widget (Box) zeigt offene Zuordnungen auf der Dolibarr-Startseite - Browser-Benachrichtigungen: Automatische Push-Notifications wenn neue Zahlungseingänge per Cron-Import erkannt werden - AJAX-Endpoint checkpending.php für Notification-Polling (alle 5 Min.) - JavaScript läuft auf jeder Dolibarr-Seite und benachrichtigt bei neuen Buchungen mit Betrag und Anzahl - Klick auf Notification öffnet direkt die Zahlungsabgleich-Seite Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c1c06e19ae
commit
7f011424bb
6 changed files with 460 additions and 8 deletions
102
ajax/checkpending.php
Normal file
102
ajax/checkpending.php
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
<?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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX endpoint to check for pending bank transaction matches
|
||||||
|
* Used by browser notification system
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('NOTOKENRENEWAL')) {
|
||||||
|
define('NOTOKENRENEWAL', '1');
|
||||||
|
}
|
||||||
|
if (!defined('NOREQUIREMENU')) {
|
||||||
|
define('NOREQUIREMENU', '1');
|
||||||
|
}
|
||||||
|
if (!defined('NOREQUIREHTML')) {
|
||||||
|
define('NOREQUIREHTML', '1');
|
||||||
|
}
|
||||||
|
if (!defined('NOREQUIREAJAX')) {
|
||||||
|
define('NOREQUIREAJAX', '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Security check
|
||||||
|
if (!$user->hasRight('bankimport', 'read')) {
|
||||||
|
echo json_encode(array('error' => 'access_denied'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bankAccountId = getDolGlobalInt('BANKIMPORT_BANK_ACCOUNT_ID');
|
||||||
|
if (empty($bankAccountId)) {
|
||||||
|
echo json_encode(array('pending' => 0, 'incoming' => 0));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count new unmatched transactions (incoming payments = positive amount)
|
||||||
|
$sqlIncoming = "SELECT COUNT(*) as cnt, COALESCE(SUM(amount), 0) as total";
|
||||||
|
$sqlIncoming .= " FROM ".MAIN_DB_PREFIX."bankimport_transaction";
|
||||||
|
$sqlIncoming .= " WHERE entity IN (".getEntity('banktransaction').")";
|
||||||
|
$sqlIncoming .= " AND status = 0 AND amount > 0";
|
||||||
|
$resIncoming = $db->query($sqlIncoming);
|
||||||
|
$incoming = 0;
|
||||||
|
$incomingTotal = 0;
|
||||||
|
if ($resIncoming) {
|
||||||
|
$obj = $db->fetch_object($resIncoming);
|
||||||
|
$incoming = (int) $obj->cnt;
|
||||||
|
$incomingTotal = (float) $obj->total;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count all new transactions
|
||||||
|
$sqlAll = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."bankimport_transaction";
|
||||||
|
$sqlAll .= " WHERE entity IN (".getEntity('banktransaction').")";
|
||||||
|
$sqlAll .= " AND status = 0";
|
||||||
|
$resAll = $db->query($sqlAll);
|
||||||
|
$pending = 0;
|
||||||
|
if ($resAll) {
|
||||||
|
$obj = $db->fetch_object($resAll);
|
||||||
|
$pending = (int) $obj->cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(array(
|
||||||
|
'pending' => $pending,
|
||||||
|
'incoming' => $incoming,
|
||||||
|
'incoming_total' => $incomingTotal,
|
||||||
|
));
|
||||||
|
|
||||||
|
$db->close();
|
||||||
167
core/boxes/box_bankimport_pending.php
Normal file
167
core/boxes/box_bankimport_pending.php
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
<?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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
include_once DOL_DOCUMENT_ROOT.'/core/boxes/modules_boxes.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dashboard widget showing pending bank transaction matches
|
||||||
|
*/
|
||||||
|
class box_bankimport_pending extends ModeleBoxes
|
||||||
|
{
|
||||||
|
public $boxcode = "bankimport_pending";
|
||||||
|
public $boximg = "fa-money-check-alt";
|
||||||
|
public $boxlabel = "BoxBankImportPending";
|
||||||
|
public $depends = array("bankimport");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param DoliDB $db Database handler
|
||||||
|
* @param string $param More parameters
|
||||||
|
*/
|
||||||
|
public function __construct($db, $param = '')
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
$this->db = $db;
|
||||||
|
$this->hidden = !$user->hasRight('bankimport', 'read');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load data into info_box_contents array to show on dashboard
|
||||||
|
*
|
||||||
|
* @param int $max Maximum number of records to load
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function loadBox($max = 5)
|
||||||
|
{
|
||||||
|
global $user, $langs, $conf;
|
||||||
|
|
||||||
|
$langs->loadLangs(array("bankimport@bankimport", "banks"));
|
||||||
|
|
||||||
|
$this->max = $max;
|
||||||
|
|
||||||
|
// Box header
|
||||||
|
$this->info_box_head = array(
|
||||||
|
'text' => $langs->trans("BoxBankImportPending"),
|
||||||
|
'sublink' => dol_buildpath('/bankimport/confirm.php', 1).'?mainmenu=bank&leftmenu=bankimport',
|
||||||
|
'subtext' => $langs->trans("ReviewAndConfirm"),
|
||||||
|
'subpicto' => 'payment',
|
||||||
|
);
|
||||||
|
|
||||||
|
$line = 0;
|
||||||
|
$bankAccountId = getDolGlobalInt('BANKIMPORT_BANK_ACCOUNT_ID');
|
||||||
|
|
||||||
|
if (empty($bankAccountId)) {
|
||||||
|
// No bank account configured
|
||||||
|
$this->info_box_contents[$line][] = array(
|
||||||
|
'td' => 'class="center" colspan="4"',
|
||||||
|
'text' => img_warning().' '.$langs->trans("NoBankAccountConfigured"),
|
||||||
|
'url' => dol_buildpath('/bankimport/admin/setup.php', 1),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count new (unmatched) transactions
|
||||||
|
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."bankimport_transaction";
|
||||||
|
$sql .= " WHERE entity IN (".getEntity('banktransaction').")";
|
||||||
|
$sql .= " AND status = 0";
|
||||||
|
$resql = $this->db->query($sql);
|
||||||
|
$newCount = 0;
|
||||||
|
if ($resql) {
|
||||||
|
$obj = $this->db->fetch_object($resql);
|
||||||
|
$newCount = (int) $obj->cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($newCount > 0) {
|
||||||
|
// Summary line: X transactions pending
|
||||||
|
$this->info_box_contents[$line][] = array(
|
||||||
|
'td' => 'class="left" colspan="3"',
|
||||||
|
'text' => '<strong>'.$langs->trans("PendingPaymentMatches", $newCount).'</strong>',
|
||||||
|
'asis' => 1,
|
||||||
|
);
|
||||||
|
$this->info_box_contents[$line][] = array(
|
||||||
|
'td' => 'class="right"',
|
||||||
|
'text' => '<a class="butActionSmall" href="'.dol_buildpath('/bankimport/confirm.php', 1).'?mainmenu=bank&leftmenu=bankimport">'.$langs->trans("ReviewAndConfirm").'</a>',
|
||||||
|
'asis' => 1,
|
||||||
|
);
|
||||||
|
$line++;
|
||||||
|
|
||||||
|
// Show last few unmatched transactions
|
||||||
|
$sql2 = "SELECT t.rowid, t.date_trans, t.name, t.amount, t.currency";
|
||||||
|
$sql2 .= " FROM ".MAIN_DB_PREFIX."bankimport_transaction as t";
|
||||||
|
$sql2 .= " WHERE t.entity IN (".getEntity('banktransaction').")";
|
||||||
|
$sql2 .= " AND t.status = 0 AND t.amount > 0";
|
||||||
|
$sql2 .= " ORDER BY t.date_trans DESC";
|
||||||
|
$sql2 .= $this->db->plimit($max, 0);
|
||||||
|
|
||||||
|
$resql2 = $this->db->query($sql2);
|
||||||
|
if ($resql2) {
|
||||||
|
$num = $this->db->num_rows($resql2);
|
||||||
|
$i = 0;
|
||||||
|
while ($i < $num && $line < $max + 1) {
|
||||||
|
$obj2 = $this->db->fetch_object($resql2);
|
||||||
|
if (!$obj2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date
|
||||||
|
$this->info_box_contents[$line][] = array(
|
||||||
|
'td' => 'class="nowraponall"',
|
||||||
|
'text' => dol_print_date($this->db->jdate($obj2->date_trans), 'day'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Name
|
||||||
|
$this->info_box_contents[$line][] = array(
|
||||||
|
'td' => 'class="tdoverflowmax150"',
|
||||||
|
'text' => dol_trunc($obj2->name, 28),
|
||||||
|
'url' => dol_buildpath('/bankimport/card.php', 1).'?id='.$obj2->rowid.'&mainmenu=bank&leftmenu=bankimport',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Amount
|
||||||
|
$amountColor = $obj2->amount >= 0 ? 'color: green;' : 'color: red;';
|
||||||
|
$amountPrefix = $obj2->amount >= 0 ? '+' : '';
|
||||||
|
$this->info_box_contents[$line][] = array(
|
||||||
|
'td' => 'class="right nowraponall"',
|
||||||
|
'text' => '<span style="'.$amountColor.'">'.$amountPrefix.price($obj2->amount, 0, $langs, 1, -1, 2, $obj2->currency).'</span>',
|
||||||
|
'asis' => 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Status badge
|
||||||
|
$this->info_box_contents[$line][] = array(
|
||||||
|
'td' => 'class="right"',
|
||||||
|
'text' => '<span class="badge badge-status4 badge-status">'.$langs->trans("New").'</span>',
|
||||||
|
'asis' => 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
$line++;
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No pending transactions
|
||||||
|
$this->info_box_contents[$line][] = array(
|
||||||
|
'td' => 'class="center opacitymedium" colspan="4"',
|
||||||
|
'text' => $langs->trans("NoNewMatchesFound"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the box
|
||||||
|
*
|
||||||
|
* @param array|null $head Optional head array
|
||||||
|
* @param array|null $contents Optional contents array
|
||||||
|
* @param int $nooutput No print, return output
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function showBox($head = null, $contents = null, $nooutput = 0)
|
||||||
|
{
|
||||||
|
return parent::showBox($this->info_box_head, $this->info_box_contents, $nooutput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 = '1.4';
|
$this->version = '1.5';
|
||||||
// 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.js.php',
|
'/bankimport/js/bankimport_notify.js.php',
|
||||||
),
|
),
|
||||||
// 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 */
|
||||||
|
|
@ -255,12 +255,11 @@ class modBankImport extends DolibarrModules
|
||||||
// Add here list of php file(s) stored in bankimport/core/boxes that contains a class to show a widget.
|
// Add here list of php file(s) stored in bankimport/core/boxes that contains a class to show a widget.
|
||||||
/* BEGIN MODULEBUILDER WIDGETS */
|
/* BEGIN MODULEBUILDER WIDGETS */
|
||||||
$this->boxes = array(
|
$this->boxes = array(
|
||||||
// 0 => array(
|
0 => array(
|
||||||
// 'file' => 'bankimportwidget1.php@bankimport',
|
'file' => 'box_bankimport_pending.php@bankimport',
|
||||||
// 'note' => 'Widget provided by BankImport',
|
'note' => 'Pending bank transaction matches',
|
||||||
// 'enabledbydefaulton' => 'Home',
|
'enabledbydefaulton' => 'Home',
|
||||||
// ),
|
),
|
||||||
// ...
|
|
||||||
);
|
);
|
||||||
/* END MODULEBUILDER WIDGETS */
|
/* END MODULEBUILDER WIDGETS */
|
||||||
|
|
||||||
|
|
|
||||||
174
js/bankimport_notify.js.php
Normal file
174
js/bankimport_notify.js.php
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
<?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, '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
@ -307,3 +307,8 @@ Difference = Differenz
|
||||||
BankEntry = Bank-Eintrag
|
BankEntry = Bank-Eintrag
|
||||||
ReconciliationConfirmed = Zuordnung bestätigt und abgeglichen
|
ReconciliationConfirmed = Zuordnung bestätigt und abgeglichen
|
||||||
Confirm = Bestätigen
|
Confirm = Bestätigen
|
||||||
|
|
||||||
|
#
|
||||||
|
# Dashboard-Widget
|
||||||
|
#
|
||||||
|
BoxBankImportPending = Bankimport - Offene Zuordnungen
|
||||||
|
|
|
||||||
|
|
@ -203,3 +203,8 @@ Difference = Difference
|
||||||
BankEntry = Bank Entry
|
BankEntry = Bank Entry
|
||||||
ReconciliationConfirmed = Match confirmed and reconciled
|
ReconciliationConfirmed = Match confirmed and reconciled
|
||||||
Confirm = Confirm
|
Confirm = Confirm
|
||||||
|
|
||||||
|
#
|
||||||
|
# Dashboard Widget
|
||||||
|
#
|
||||||
|
BoxBankImportPending = BankImport - Pending Matches
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue