mahnung/admin/setup.php
Eduard Wisch 10cf41a687
All checks were successful
Deploy mahnung / deploy (push) Successful in 14s
i18n: Alle Texte über $langs->trans() — ~100 neue Sprachschlüssel de_DE + en_US [deploy]
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>
2026-05-13 16:25:50 +02:00

431 lines
19 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 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
/**
* \file mahnung/admin/setup.php
* \ingroup mahnung
* \brief Setup: Mahnstufen, Basiszins, B2C/B2B-Aufschläge, Pauschale, Ntfy-Topic.
*/
$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");
}
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnung.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnungstufe.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/core/modules/mahnung/modules_mahnung.php';
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/core/modules/modMahnung.class.php';
global $langs, $user, $conf, $db;
$langs->loadLangs(array('admin', 'mahnung@mahnung'));
if (!$user->admin && !$user->hasRight('mahnung', 'setup')) {
accessforbidden();
}
// Schema-Migration bei jedem Setup-Aufruf (idempotent — fehlende Spalten ergänzen)
(new modMahnung($db))->migrateVersandFelder();
// Tracking-Pattern-Tabelle anlegen (falls noch nicht da) + Default-Patterns seeden
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnungtrackingpattern.class.php';
$check = $db->query("SHOW TABLES LIKE '".$db->escape(MAIN_DB_PREFIX.'mahnung_trackingpattern')."'");
if ($check && $db->num_rows($check) == 0) {
$db->query("CREATE TABLE ".MAIN_DB_PREFIX."mahnung_trackingpattern ("
." rowid INTEGER AUTO_INCREMENT PRIMARY KEY,"
." entity INTEGER DEFAULT 1 NOT NULL,"
." provider VARCHAR(20) NOT NULL,"
." label VARCHAR(80) NOT NULL,"
." regex VARCHAR(255) NOT NULL,"
." url_template VARCHAR(255) NOT NULL,"
." priority INTEGER DEFAULT 100,"
." active TINYINT DEFAULT 1,"
." datec DATETIME,"
." tms TIMESTAMP,"
." INDEX idx_trackingpattern_active (active, priority),"
." INDEX idx_trackingpattern_entity (entity)"
.") ENGINE=InnoDB");
}
if ($check) {
$db->free($check);
}
MahnungTrackingPattern::seedDefaults($db);
$action = GETPOST('action', 'aZ09');
// ---------------------------------------------------------------
// POST: Allgemeine Konstanten speichern
// ---------------------------------------------------------------
if ($action === 'save_consts' && $user->hasRight('mahnung', 'setup')) {
$basis = str_replace(',', '.', GETPOST('MAHNUNG_BASISZINS', 'alphanohtml'));
$b2c = str_replace(',', '.', GETPOST('MAHNUNG_AUFSCHLAG_B2C', 'alphanohtml'));
$b2b = str_replace(',', '.', GETPOST('MAHNUNG_AUFSCHLAG_B2B', 'alphanohtml'));
$pau = str_replace(',', '.', GETPOST('MAHNUNG_PAUSCHALE_B2B', 'alphanohtml'));
$topic = GETPOST('MAHNUNG_NTFY_TOPIC', 'alphanohtml');
dolibarr_set_const($db, 'MAHNUNG_BASISZINS', (string) (float) $basis, 'chaine', 0, '', 0);
dolibarr_set_const($db, 'MAHNUNG_AUFSCHLAG_B2C', (string) (float) $b2c, 'chaine', 0, '', 0);
dolibarr_set_const($db, 'MAHNUNG_AUFSCHLAG_B2B', (string) (float) $b2b, 'chaine', 0, '', 0);
dolibarr_set_const($db, 'MAHNUNG_PAUSCHALE_B2B', (string) (float) $pau, 'chaine', 0, '', 0);
dolibarr_set_const($db, 'MAHNUNG_NTFY_TOPIC', (string) $topic, 'chaine', 0, '', $conf->entity);
setEventMessages($langs->trans('MahnungSettingsSaved'), null, 'mesgs');
header('Location: '.$_SERVER['PHP_SELF']);
exit;
}
// ---------------------------------------------------------------
// POST: Stufen-Tabelle speichern (Bulk-Update aller 3 Stufen)
// ---------------------------------------------------------------
if ($action === 'save_stufen' && $user->hasRight('mahnung', 'setup')) {
$stufeObj = new MahnungStufe($db);
$alle = $stufeObj->fetchAllActive();
// Auch inaktive laden (active=0) — fetchAllActive filtert; hier inkl. inaktive:
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."mahnung_stufe WHERE entity = ".((int) $conf->entity)." ORDER BY stufe";
$resql = $db->query($sql);
$ids = array();
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
$ids[] = (int) $obj->rowid;
}
$db->free($resql);
}
$ok = true;
foreach ($ids as $id) {
$s = loadStufeById($db, $id, $conf->entity);
if (!$s) {
continue;
}
$prefix = 'stufe_'.$s->stufe.'_';
$s->label = GETPOST($prefix.'label', 'alphanohtml');
$s->frist_tage = (int) GETPOST($prefix.'frist_tage', 'int');
$s->neue_frist_tage = (int) GETPOST($prefix.'neue_frist_tage', 'int');
$s->mahngebuehr_b2c = (float) str_replace(',', '.', GETPOST($prefix.'mahngebuehr_b2c', 'alphanohtml'));
$s->mahngebuehr_b2b = (float) str_replace(',', '.', GETPOST($prefix.'mahngebuehr_b2b', 'alphanohtml'));
$s->pauschale_b2b_einmalig = GETPOSTISSET($prefix.'pauschale_b2b_einmalig') ? 1 : 0;
$ovB2c = trim((string) GETPOST($prefix.'zinssatz_b2c', 'alphanohtml'));
$ovB2b = trim((string) GETPOST($prefix.'zinssatz_b2b', 'alphanohtml'));
$s->zinssatz_b2c_uebersteuern = $ovB2c === '' ? null : (float) str_replace(',', '.', $ovB2c);
$s->zinssatz_b2b_uebersteuern = $ovB2b === '' ? null : (float) str_replace(',', '.', $ovB2b);
$s->versandart_default = GETPOST($prefix.'versandart', 'alphanohtml') ?: 'pdf';
$s->pdf_intro = GETPOST($prefix.'pdf_intro', 'restricthtml');
$s->email_subject = GETPOST($prefix.'email_subject', 'alphanohtml');
$s->email_body = GETPOST($prefix.'email_body', 'restricthtml');
$s->active = GETPOSTISSET($prefix.'active') ? 1 : 0;
if ($s->update($user) <= 0) {
$ok = false;
setEventMessages($s->error, null, 'errors');
}
}
if ($ok) {
setEventMessages($langs->trans('MahnungSettingsSaved'), null, 'mesgs');
header('Location: '.$_SERVER['PHP_SELF']);
exit;
}
}
/**
* Helfer: Stufe per rowid + entity laden (CRUD-Klasse hat nur fetchByStufe).
*
* @param DoliDB $db
* @param int $id
* @param int $entity
* @return MahnungStufe|null
*/
function loadStufeById($db, $id, $entity)
{
$sql = "SELECT t.* FROM ".MAIN_DB_PREFIX."mahnung_stufe as t";
$sql .= " WHERE t.rowid = ".((int) $id);
$sql .= " AND t.entity = ".((int) $entity);
$resql = $db->query($sql);
if (!$resql || !$db->num_rows($resql)) {
return null;
}
$obj = $db->fetch_object($resql);
$s = new MahnungStufe($db);
$s->id = (int) $obj->rowid;
$s->entity = (int) $obj->entity;
$s->stufe = (int) $obj->stufe;
$s->label = $obj->label;
$s->frist_tage = (int) $obj->frist_tage;
$s->neue_frist_tage = (int) $obj->neue_frist_tage;
$s->mahngebuehr_b2c = $obj->mahngebuehr_b2c;
$s->mahngebuehr_b2b = $obj->mahngebuehr_b2b;
$s->pauschale_b2b_einmalig = (int) $obj->pauschale_b2b_einmalig;
$s->zinssatz_b2c_uebersteuern = $obj->zinssatz_b2c_uebersteuern;
$s->zinssatz_b2b_uebersteuern = $obj->zinssatz_b2b_uebersteuern;
$s->versandart_default = $obj->versandart_default;
$s->email_subject = $obj->email_subject;
$s->email_body = $obj->email_body;
$s->pdf_intro = $obj->pdf_intro;
$s->active = (int) $obj->active;
$db->free($resql);
return $s;
}
// ---------------------------------------------------------------
// ODT-Upload / Template-Löschen (actions_setmoduleoptions.inc.php)
// Muss vor llxHeader() stehen, da es header()-Redirects macht.
// ---------------------------------------------------------------
include_once DOL_DOCUMENT_ROOT.'/core/actions_setmoduleoptions.inc.php';
// ---------------------------------------------------------------
// Dokumentenmodell-Aktionen (vor View, da header()-Redirect)
// ---------------------------------------------------------------
if ($action === 'setdoc' && $user->hasRight('mahnung', 'setup')) {
$newModel = GETPOST('value', 'alphanohtml');
if (!empty($newModel)) {
dolibarr_set_const($db, 'MAHNUNG_ADDON_PDF', $newModel, 'chaine', 0, '', $conf->entity);
}
header('Location: '.$_SERVER['PHP_SELF']);
exit;
}
if ($action === 'set' && $user->hasRight('mahnung', 'setup')) {
$newModel = GETPOST('value', 'alphanohtml');
$sql = "INSERT INTO ".MAIN_DB_PREFIX."document_model (nom, type, entity, libelle) VALUES ('".$db->escape($newModel)."', 'mahnung', ".((int) $conf->entity).", '".$db->escape($newModel)."')";
$db->query($sql);
header('Location: '.$_SERVER['PHP_SELF']);
exit;
}
if ($action === 'del' && $user->hasRight('mahnung', 'setup')) {
$newModel = GETPOST('value', 'alphanohtml');
$sql = "DELETE FROM ".MAIN_DB_PREFIX."document_model WHERE nom = '".$db->escape($newModel)."' AND type = 'mahnung' AND entity = ".((int) $conf->entity);
$db->query($sql);
if (getDolGlobalString('MAHNUNG_ADDON_PDF') === $newModel) {
dolibarr_del_const($db, 'MAHNUNG_ADDON_PDF', $conf->entity);
}
header('Location: '.$_SERVER['PHP_SELF']);
exit;
}
// ---------------------------------------------------------------
// View
// ---------------------------------------------------------------
llxHeader('', $langs->trans('MahnungSetupPage'));
$setupExtra = '<a href="tracking_patterns.php" class="button smallpaddingimp">'.dol_escape_htmltag($langs->trans('MahnungTrackingPatternsSetup')).'</a>';
print load_fiche_titre($langs->trans('MahnungSetupPage'), $setupExtra, 'fa-envelope-open-text');
print '<span class="opacitymedium">'.$langs->trans('MahnungSetupDescription').'</span><br><br>';
// --- Block: Konstanten -------------------------------------------------------
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="save_consts">';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre"><th colspan="2">'.$langs->trans('MahnungSetup').'</th></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungBasiszins').'</td>';
print '<td><input type="text" name="MAHNUNG_BASISZINS" size="8" value="'.dol_escape_htmltag(getDolGlobalString('MAHNUNG_BASISZINS', '1.27')).'"> %';
print ' <span class="opacitymedium">'.$langs->trans('MahnungBasiszinsHelp').'</span></td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungAufschlagB2C').'</td>';
print '<td><input type="text" name="MAHNUNG_AUFSCHLAG_B2C" size="8" value="'.dol_escape_htmltag(getDolGlobalString('MAHNUNG_AUFSCHLAG_B2C', '5.0')).'"> %</td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungAufschlagB2B').'</td>';
print '<td><input type="text" name="MAHNUNG_AUFSCHLAG_B2B" size="8" value="'.dol_escape_htmltag(getDolGlobalString('MAHNUNG_AUFSCHLAG_B2B', '9.0')).'"> %</td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungPauschaleB2BLabel').'</td>';
print '<td><input type="text" name="MAHNUNG_PAUSCHALE_B2B" size="8" value="'.dol_escape_htmltag(getDolGlobalString('MAHNUNG_PAUSCHALE_B2B', '40.00')).'"> EUR</td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungNtfyTopic').'</td>';
print '<td><input type="text" name="MAHNUNG_NTFY_TOPIC" size="40" value="'.dol_escape_htmltag(getDolGlobalString('MAHNUNG_NTFY_TOPIC', 'vk-builds')).'">';
print ' <span class="opacitymedium">'.$langs->trans('MahnungNtfyTopicHelp').'</span></td></tr>';
print '</table>';
print '<br><div class="center"><input type="submit" class="button" value="'.$langs->trans('Save').'"></div>';
print '</form>';
// --- Block: Stufen -----------------------------------------------------------
$stufeObj = new MahnungStufe($db);
$stufen = array();
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."mahnung_stufe WHERE entity = ".((int) $conf->entity)." ORDER BY stufe ASC";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
$s = loadStufeById($db, (int) $obj->rowid, (int) $conf->entity);
if ($s) {
$stufen[] = $s;
}
}
$db->free($resql);
}
print '<br><br>';
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="save_stufen">';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre"><th colspan="2">'.$langs->trans('MahnungStufe').'</th></tr>';
foreach ($stufen as $s) {
$prefix = 'stufe_'.$s->stufe.'_';
print '<tr class="liste_titre_filter"><th colspan="2">'.dol_escape_htmltag($langs->trans('MahnungStufe').' '.$s->stufe).' ';
print '<input type="checkbox" name="'.$prefix.'active" value="1"'.($s->active ? ' checked' : '').'> '.$langs->trans('Active');
print '</th></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungStufeLabel').'</td>';
print '<td><input type="text" name="'.$prefix.'label" size="40" value="'.dol_escape_htmltag($s->label).'"></td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungStufeFristTage').'</td>';
print '<td><input type="number" name="'.$prefix.'frist_tage" size="6" value="'.((int) $s->frist_tage).'"></td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungStufeNeueFristTage').'</td>';
print '<td><input type="number" name="'.$prefix.'neue_frist_tage" size="6" value="'.((int) $s->neue_frist_tage).'"></td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungStufeGebuehrB2C').'</td>';
print '<td><input type="text" name="'.$prefix.'mahngebuehr_b2c" size="8" value="'.((float) $s->mahngebuehr_b2c).'"> EUR</td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungStufeGebuehrB2B').'</td>';
print '<td><input type="text" name="'.$prefix.'mahngebuehr_b2b" size="8" value="'.((float) $s->mahngebuehr_b2b).'"> EUR</td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungPauschaleB2B').' (§288 Abs. 5)</td>';
print '<td><input type="checkbox" name="'.$prefix.'pauschale_b2b_einmalig" value="1"'.($s->pauschale_b2b_einmalig ? ' checked' : '').'></td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungStufeZinssatzB2C').'</td>';
print '<td><input type="text" name="'.$prefix.'zinssatz_b2c" size="8" value="'.($s->zinssatz_b2c_uebersteuern !== null ? (float) $s->zinssatz_b2c_uebersteuern : '').'"> %</td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungStufeZinssatzB2B').'</td>';
print '<td><input type="text" name="'.$prefix.'zinssatz_b2b" size="8" value="'.($s->zinssatz_b2b_uebersteuern !== null ? (float) $s->zinssatz_b2b_uebersteuern : '').'"> %</td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungStufeVersandartDefault').'</td>';
$va = $s->versandart_default ?: 'pdf';
print '<td><select name="'.$prefix.'versandart">';
foreach (array('pdf' => 'MahnungVersandPdf', 'mail' => 'MahnungVersandMail', 'druck' => 'MahnungVersandDruck', 'none' => 'MahnungVersandNone') as $v => $tx) {
print '<option value="'.$v.'"'.($va === $v ? ' selected' : '').'>'.$langs->trans($tx).'</option>';
}
print '</select></td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungStufePdfIntro').'</td>';
print '<td><textarea name="'.$prefix.'pdf_intro" cols="80" rows="3">'.dol_escape_htmltag($s->pdf_intro).'</textarea></td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungStufeEmailSubject').'</td>';
print '<td><input type="text" name="'.$prefix.'email_subject" size="80" value="'.dol_escape_htmltag($s->email_subject).'"></td></tr>';
print '<tr class="oddeven"><td>'.$langs->trans('MahnungStufeEmailBody').'</td>';
print '<td><textarea name="'.$prefix.'email_body" cols="80" rows="5">'.dol_escape_htmltag($s->email_body).'</textarea></td></tr>';
}
print '</table>';
print '<br><div class="center"><input type="submit" class="button" value="'.$langs->trans('Save').'"></div>';
print '</form>';
// --- Block: Dokumentenmodelle -----------------------------------------------------------
print '<br><br>';
print load_fiche_titre($langs->trans('MahnungDokumentModelle'), '<a href="templatevars.php" class="button smallpaddingimp">'.$langs->trans('MahnungSetupTemplateVars').'</a>', '');
// Dokumentenmodelle auflisten
$def = array();
$sql = "SELECT nom FROM ".MAIN_DB_PREFIX."document_model WHERE type = 'mahnung' AND entity = ".((int) $conf->entity);
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
$def[] = $obj->nom;
}
$db->free($resql);
}
// Verfügbare Modelle scannen (aus den doc/-Klassen)
$dirmodels = array(
DOL_DOCUMENT_ROOT.'/custom/mahnung/core/modules/mahnung/doc/',
);
$modellist = array();
foreach ($dirmodels as $dmod) {
$files = dol_dir_list($dmod, 'files', 0, '\.modules\.php$');
foreach ($files as $file) {
$classname = preg_replace('/\.modules\.php$/', '', $file['name']);
// Modellname ohne doc_/pdf_-Prefix (commonGenerateDocument fuegt den Prefix selbst hinzu)
$modelname = preg_replace('/^(doc|pdf)_/', '', $classname);
include_once $file['fullname'];
if (class_exists($classname)) {
$obj = new $classname($db);
$modellist[$modelname] = $obj;
}
}
}
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th>'.$langs->trans('Name').'</th>';
print '<th>'.$langs->trans('Description').'</th>';
print '<th class="center">'.$langs->trans('Status').'</th>';
print '<th class="center">'.$langs->trans('Default').'</th>';
print '<th></th>';
print '</tr>';
foreach ($modellist as $mname => $mobj) {
print '<tr class="oddeven">';
print '<td>'.dol_escape_htmltag($mobj->name).'</td>';
print '<td>'.dol_escape_htmltag($mobj->description ?? '').'</td>';
// Status (aktiviert/deaktiviert)
print '<td class="center">';
if (in_array($mname, $def)) {
print '<a class="reposition" href="'.$_SERVER['PHP_SELF'].'?action=del&token='.newToken().'&value='.urlencode($mname).'">';
print img_picto($langs->trans('Activated'), 'switch_on');
print '</a>';
} else {
print '<a class="reposition" href="'.$_SERVER['PHP_SELF'].'?action=set&token='.newToken().'&value='.urlencode($mname).'">';
print img_picto($langs->trans('Disabled'), 'switch_off');
print '</a>';
}
print '</td>';
// Default
print '<td class="center">';
if (getDolGlobalString('MAHNUNG_ADDON_PDF') === $mname) {
print img_picto($langs->trans('Default'), 'on');
} elseif (in_array($mname, $def)) {
print '<a class="reposition" href="'.$_SERVER['PHP_SELF'].'?action=setdoc&token='.newToken().'&value='.urlencode($mname).'">';
print img_picto($langs->trans('SetAsDefault'), 'off');
print '</a>';
}
print '</td>';
// Info (ODT: Upload-Formular)
print '<td>';
if (method_exists($mobj, 'info')) {
print $mobj->info($langs);
}
print '</td>';
print '</tr>';
}
print '</table>';
llxFooter();
$db->close();