All checks were successful
Deploy mahnung / deploy (push) Successful in 14s
Umlaute in allen lang-Dateien korrigiert. Alle hardcodierten deutschen Strings
in 22 PHP-Dateien durch $langs->trans('Key') ersetzt. Neue Schlüssel für
Cron-Meldungen, Dokument-Aktionen, Bonität, Vorschlag-Status, Template-Vars u.a.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
250 lines
7.3 KiB
PHP
250 lines
7.3 KiB
PHP
<?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, version 3.
|
|
*/
|
|
|
|
/**
|
|
* \file htdocs/custom/mahnung/class/mahnungcron.class.php
|
|
* \ingroup mahnung
|
|
* \brief Cron-Job: Vorschlagsliste überfälliger Rechnungen einsammeln,
|
|
* Ntfy-Push mit Kennzahl an Eddy.
|
|
*/
|
|
|
|
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnungvorschlag.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnungntfy.class.php';
|
|
|
|
class MahnungCron
|
|
{
|
|
/** @var DoliDB */
|
|
public $db;
|
|
|
|
/** @var string */
|
|
public $error = '';
|
|
|
|
/** @var string[] */
|
|
public $errors = array();
|
|
|
|
/** @var string */
|
|
public $output = '';
|
|
|
|
/** @var int|string */
|
|
public $lastresult = 0;
|
|
|
|
/**
|
|
* @param DoliDB $db
|
|
*/
|
|
public function __construct($db)
|
|
{
|
|
$this->db = $db;
|
|
}
|
|
|
|
/**
|
|
* Sucht überfällige Rechnungen, ermittelt Vorschläge je Stufe,
|
|
* sendet Ntfy-Push mit Anzahl je Stufe und Gesamtwert.
|
|
*
|
|
* @return int 0 bei Erfolg, < 0 bei Fehler
|
|
*/
|
|
public function buildVorschlagsliste()
|
|
{
|
|
global $conf;
|
|
|
|
$service = new MahnungVorschlag($this->db);
|
|
$vorschlaege = $service->getVorschlaege();
|
|
|
|
$count = count($vorschlaege);
|
|
$counts = array(1 => 0, 2 => 0, 3 => 0);
|
|
$summe = 0.0;
|
|
foreach ($vorschlaege as $v) {
|
|
$stufe = (int) $v['vorgeschlagene_stufe'];
|
|
if (isset($counts[$stufe])) {
|
|
$counts[$stufe]++;
|
|
}
|
|
$summe += (float) $v['betrag_offen'];
|
|
}
|
|
$summe = round($summe, 2);
|
|
|
|
if ($count === 0) {
|
|
// Alte Notifications räumen — es gibt nichts mehr zu tun
|
|
self::clearGlobalNotify();
|
|
global $langs;
|
|
$langs->load('mahnung@mahnung');
|
|
$this->output = $langs->trans('MahnungCronKeineUeberfaellige');
|
|
$this->lastresult = 0;
|
|
return 0;
|
|
}
|
|
|
|
global $langs;
|
|
$langs->load('mahnung@mahnung');
|
|
|
|
$dolUrl = trim((string) getDolGlobalString('MAIN_INFO_SOCIETE_NOM', ''));
|
|
$relPath = '/custom/mahnung/list.php?mainmenu=billing&leftmenu=mahnung&mode=vorschlag';
|
|
$absUrl = self::buildAbsoluteUrl($relPath);
|
|
|
|
$title = $langs->trans('MahnungCronOffeneVorschlaege', $count);
|
|
$message = $langs->trans('MahnungCronStufe1Erinnerung', $counts[1])."\n";
|
|
$message .= $langs->trans('MahnungCronStufe2Mahnung', $counts[2])."\n";
|
|
$message .= $langs->trans('MahnungCronStufe3LetzteMahnung', $counts[3])."\n";
|
|
$message .= $langs->trans('MahnungCronOffenerBetrag', number_format($summe, 2, ',', '.'));
|
|
|
|
MahnungNtfy::send($title, $message, $absUrl, array('envelope_with_arrow', 'warning'));
|
|
|
|
// Optional: GlobalNotify-Badge ins Dolibarr-UI (wenn Modul aktiv)
|
|
// Relativer Pfad — wird im Browser-Kontext korrekt aufgeloest
|
|
if (isModEnabled('globalnotify') && class_exists('GlobalNotify') === false) {
|
|
$gnPath = DOL_DOCUMENT_ROOT.'/custom/globalnotify/class/globalnotify.class.php';
|
|
if (file_exists($gnPath)) {
|
|
require_once $gnPath;
|
|
}
|
|
}
|
|
if (class_exists('GlobalNotify')) {
|
|
GlobalNotify::actionRequired(
|
|
'mahnung',
|
|
$langs->trans('MahnungCronVorschlaege', $count),
|
|
$message,
|
|
$relPath,
|
|
$langs->trans('MahnungCronVorschlagslisteOeffnen')
|
|
);
|
|
}
|
|
|
|
$this->output = $title.' — '.$message;
|
|
$this->lastresult = $count;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Räumt alle GlobalNotify-Notifications für das Mahnung-Modul auf.
|
|
* Wird aufgerufen wenn keine offenen Vorschläge mehr existieren.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function clearGlobalNotify()
|
|
{
|
|
if (!isModEnabled('globalnotify')) {
|
|
return;
|
|
}
|
|
if (!class_exists('GlobalNotify')) {
|
|
$gnPath = DOL_DOCUMENT_ROOT.'/custom/globalnotify/class/globalnotify.class.php';
|
|
if (!file_exists($gnPath)) {
|
|
return;
|
|
}
|
|
require_once $gnPath;
|
|
}
|
|
global $db;
|
|
$gn = new GlobalNotify($db);
|
|
$gn->clearModuleNotifications('mahnung');
|
|
}
|
|
|
|
/**
|
|
* Versand-Reminder: Mahnungen mit Status ERSTELLT, deren PDF schon
|
|
* laenger als N Tage erstellt wurde, aber noch nicht versendet ist,
|
|
* werden gesammelt und per Ntfy gepusht.
|
|
*
|
|
* Schwellenwert konfigurierbar via MAHNUNG_VERSAND_REMINDER_DAYS (Default 2).
|
|
*
|
|
* @return int 0 bei Erfolg, < 0 bei Fehler
|
|
*/
|
|
public function versandReminder()
|
|
{
|
|
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnung.class.php';
|
|
|
|
$tageSchwelle = (int) getDolGlobalString('MAHNUNG_VERSAND_REMINDER_DAYS', '2');
|
|
if ($tageSchwelle <= 0) {
|
|
$tageSchwelle = 2;
|
|
}
|
|
|
|
$sql = "SELECT m.rowid, m.ref, m.stufe, m.datec, m.tms, m.fk_facture, m.fk_soc,";
|
|
$sql .= " s.nom AS soc_nom, f.ref AS facture_ref";
|
|
$sql .= " FROM ".MAIN_DB_PREFIX."mahnung_mahnung as m";
|
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = m.fk_soc";
|
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as f ON f.rowid = m.fk_facture";
|
|
$sql .= " WHERE m.status = ".Mahnung::STATUS_ERSTELLT;
|
|
$sql .= " AND m.date_versand IS NULL";
|
|
$sql .= " AND m.datec < DATE_SUB(NOW(), INTERVAL ".$tageSchwelle." DAY)";
|
|
$sql .= " ORDER BY m.datec ASC";
|
|
|
|
$resql = $this->db->query($sql);
|
|
if (!$resql) {
|
|
$this->error = $this->db->lasterror();
|
|
$this->lastresult = -1;
|
|
return -1;
|
|
}
|
|
|
|
$pending = array();
|
|
while ($obj = $this->db->fetch_object($resql)) {
|
|
$pending[] = $obj;
|
|
}
|
|
$this->db->free($resql);
|
|
|
|
global $langs;
|
|
$langs->load('mahnung@mahnung');
|
|
|
|
if (empty($pending)) {
|
|
$this->output = $langs->trans('MahnungCronKeineUnversendet', $tageSchwelle);
|
|
$this->lastresult = 0;
|
|
return 0;
|
|
}
|
|
|
|
$relPath = '/custom/mahnung/list.php?mainmenu=billing&leftmenu=mahnung&mode=archiv';
|
|
$absUrl = self::buildAbsoluteUrl($relPath);
|
|
$title = $langs->trans('MahnungCronUnversendetTitel', count($pending));
|
|
$lines = array();
|
|
foreach ($pending as $p) {
|
|
$tage = (int) floor((time() - strtotime((string) $p->datec)) / 86400);
|
|
$lines[] = $langs->trans('MahnungCronStufeAlter', $p->ref, $p->stufe, $tage, $p->soc_nom);
|
|
}
|
|
// Auf 8 Zeilen kürzen, Rest als "+N weitere"
|
|
if (count($lines) > 8) {
|
|
$rest = count($lines) - 8;
|
|
$lines = array_slice($lines, 0, 8);
|
|
$lines[] = $langs->trans('MahnungCronWeitere', $rest);
|
|
}
|
|
$message = implode("\n", $lines);
|
|
|
|
MahnungNtfy::send($title, $message, $absUrl, array('envelope_with_arrow', 'warning'));
|
|
|
|
// Optional: GlobalNotify-Badge — relativer Pfad für Browser-Kontext
|
|
if (isModEnabled('globalnotify') && !class_exists('GlobalNotify')) {
|
|
$gnPath = DOL_DOCUMENT_ROOT.'/custom/globalnotify/class/globalnotify.class.php';
|
|
if (file_exists($gnPath)) {
|
|
require_once $gnPath;
|
|
}
|
|
}
|
|
if (class_exists('GlobalNotify')) {
|
|
GlobalNotify::actionRequired(
|
|
'mahnung_versand',
|
|
$title,
|
|
$message,
|
|
$relPath,
|
|
$langs->trans('MahnungCronArchivOeffnen')
|
|
);
|
|
}
|
|
|
|
$this->output = $langs->trans('MahnungCronEintraege', $title, count($pending));
|
|
$this->lastresult = count($pending);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Baut eine absolute URL aus einem relativen Pfad anhand der Dolibarr-URL-Konfig.
|
|
*
|
|
* @param string $relPath
|
|
* @return string
|
|
*/
|
|
private static function buildAbsoluteUrl($relPath)
|
|
{
|
|
$base = trim((string) getDolGlobalString('DOLIBARR_MAIN_URL_ROOT', ''));
|
|
if (empty($base) && defined('DOL_MAIN_URL_ROOT')) {
|
|
$base = DOL_MAIN_URL_ROOT;
|
|
}
|
|
if (empty($base)) {
|
|
return $relPath;
|
|
}
|
|
// Protokoll sicherstellen — ohne Protokoll wird die URL im Browser als relativ interpretiert
|
|
if (!preg_match('/^https?:\/\//', $base)) {
|
|
$base = 'http://'.$base;
|
|
}
|
|
return rtrim($base, '/').$relPath;
|
|
}
|
|
}
|