All checks were successful
Deploy mahnung / deploy (push) Successful in 14s
tms war als reines TIMESTAMP angelegt -> unter explicit_defaults_for_timestamp NULL DEFAULT NULL, blieb daher bei jedem UPDATE leer. Jetzt Dolibarr-Standard DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP fuer mahnung/stufe/ trackingpattern. Neue idempotente Migration migrateTimestampSpalten() im init() befuellt Alt-NULLs aus datec und stellt die Spalte per ALTER TABLE um. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
447 lines
13 KiB
PHP
447 lines
13 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
/**
|
|
* \defgroup mahnung Modul Mahnwesen
|
|
* \brief Mahnwesen-Modul (3-stufig nach BGB §288)
|
|
* \file htdocs/custom/mahnung/core/modules/modMahnung.class.php
|
|
* \ingroup mahnung
|
|
*/
|
|
|
|
include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php';
|
|
|
|
/**
|
|
* Beschreibungs- und Aktivierungsklasse für Modul Mahnung
|
|
*/
|
|
class modMahnung extends DolibarrModules
|
|
{
|
|
/**
|
|
* @param DoliDB $db Datenbank-Handler
|
|
*/
|
|
public function __construct($db)
|
|
{
|
|
global $conf, $langs;
|
|
|
|
$this->db = $db;
|
|
|
|
// Eindeutige Modul-ID. 500034..500036 sind durch das Bericht-Modul belegt
|
|
// (numero=500033, dessen rights id-Range 500033..500036 abdeckt).
|
|
// 500037 ist durch Eplan belegt, daher 500038.
|
|
$this->numero = 500038;
|
|
|
|
// Schlüssel für Rechte und Menüs
|
|
$this->rights_class = 'mahnung';
|
|
|
|
$this->family = 'financial';
|
|
$this->module_position = '50';
|
|
|
|
$this->name = preg_replace('/^mod/i', '', get_class($this));
|
|
|
|
$this->description = 'MahnungDescription';
|
|
$this->descriptionlong = 'MahnungDescription';
|
|
|
|
$this->editor_name = 'Alles Watt läuft';
|
|
$this->editor_url = '';
|
|
|
|
$this->version = '0.2.0';
|
|
|
|
$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
|
|
|
|
// FontAwesome 5 Free (Dolibarr-Bundle, KB #435). 'fa-envelope-open-o' ist FA4-Notation
|
|
// und rendert in Dolibarr lautlos kein Glyph; 'fa-envelope-open-text' ist FA5-Free.
|
|
$this->picto = 'fa-envelope-open-text';
|
|
|
|
$this->module_parts = array(
|
|
'triggers' => 1,
|
|
'login' => 0,
|
|
'substitutions' => 0,
|
|
'menus' => 0,
|
|
'tpl' => 0,
|
|
'barcode' => 0,
|
|
'models' => 1,
|
|
'printing' => 0,
|
|
'theme' => 0,
|
|
'css' => array(),
|
|
'js' => array(),
|
|
// Hook-Klasse: class/actions_mahnung.class.php (Standard-Lookup-Pfad)
|
|
'hooks' => array(
|
|
'data' => array(
|
|
'invoicecard',
|
|
'thirdpartycard',
|
|
'ordercard',
|
|
),
|
|
'entity' => '0',
|
|
),
|
|
'moduleforexternal' => 0,
|
|
'websitetemplates' => 0,
|
|
'captcha' => 0,
|
|
);
|
|
|
|
// Datenverzeichnisse bei Modul-Aktivierung.
|
|
// $conf->mahnung->dir_output und multidir_output werden von Dolibarrs
|
|
// Conf-Klasse beim Bootstrap automatisch auf DOL_DATA_ROOT/mahnung gesetzt
|
|
// (siehe core/class/conf.class.php:744). Damit funktioniert FormFile->showdocuments('mahnung', ...)
|
|
// und document.php?modulepart=mahnung out-of-the-box.
|
|
$this->dirs = array('/mahnung', '/mahnung/temp');
|
|
|
|
// Konfigurationsseite
|
|
$this->config_page_url = array('setup.php@mahnung');
|
|
|
|
$this->hidden = getDolGlobalInt('MODULE_MAHNUNG_DISABLED');
|
|
$this->depends = array();
|
|
$this->requiredby = array();
|
|
$this->conflictwith = array();
|
|
|
|
$this->langfiles = array('mahnung@mahnung');
|
|
|
|
$this->phpmin = array(7, 4);
|
|
$this->need_dolibarr_version = array(19, -3);
|
|
$this->need_javascript_ajax = 1;
|
|
|
|
$this->warnings_activation = array();
|
|
$this->warnings_activation_ext = array();
|
|
|
|
// Modul-Konstanten
|
|
$this->const = array(
|
|
0 => array(
|
|
'MAHNUNG_BASISZINS',
|
|
'chaine',
|
|
'1.27',
|
|
'BGB-Basiszins in Prozent (manuell halbjährlich pflegen)',
|
|
0,
|
|
'allentities',
|
|
1,
|
|
),
|
|
1 => array(
|
|
'MAHNUNG_NTFY_TOPIC',
|
|
'chaine',
|
|
'vk-builds',
|
|
'Ntfy-Topic für Mahnungs-Benachrichtigungen',
|
|
0,
|
|
'current',
|
|
1,
|
|
),
|
|
2 => array(
|
|
'MAHNUNG_AUFSCHLAG_B2C',
|
|
'chaine',
|
|
'5.0',
|
|
'Verzugszins-Aufschlag B2C in Prozent (BGB §288 Abs. 1)',
|
|
0,
|
|
'allentities',
|
|
1,
|
|
),
|
|
3 => array(
|
|
'MAHNUNG_AUFSCHLAG_B2B',
|
|
'chaine',
|
|
'9.0',
|
|
'Verzugszins-Aufschlag B2B in Prozent (BGB §288 Abs. 2)',
|
|
0,
|
|
'allentities',
|
|
1,
|
|
),
|
|
4 => array(
|
|
'MAHNUNG_PAUSCHALE_B2B',
|
|
'chaine',
|
|
'40.00',
|
|
'Pauschale B2B nach BGB §288 Abs. 5 in EUR',
|
|
0,
|
|
'allentities',
|
|
1,
|
|
),
|
|
5 => array(
|
|
'MAHNUNG_ADDON_PDF',
|
|
'chaine',
|
|
'standard_mahnung',
|
|
'Standard-Dokumentenmodell für Mahnungen',
|
|
0,
|
|
'current',
|
|
1,
|
|
),
|
|
6 => array(
|
|
'MAHNUNG_ADDON_PDF_ODT_PATH',
|
|
'chaine',
|
|
'DOL_DATA_ROOT/doctemplates/mahnung',
|
|
'Verzeichnis für ODT-Templates',
|
|
0,
|
|
'current',
|
|
1,
|
|
),
|
|
);
|
|
|
|
if (!isModEnabled('mahnung')) {
|
|
$conf->mahnung = new stdClass();
|
|
$conf->mahnung->enabled = 0;
|
|
}
|
|
|
|
// Tabs auf bestehenden Karten (Phase 5: aktivieren)
|
|
$this->tabs = array();
|
|
|
|
$this->dictionaries = array();
|
|
|
|
$this->boxes = array(
|
|
0 => array(
|
|
'file' => 'box_mahnung_offen@mahnung',
|
|
'enabledbydefaulton' => 'Home',
|
|
),
|
|
);
|
|
|
|
// Cron-Job: Vorschlagsliste täglich 06:00
|
|
$this->cronjobs = array(
|
|
0 => array(
|
|
'label' => 'MahnungCronBuildVorschlag',
|
|
'jobtype' => 'method',
|
|
'class' => '/mahnung/class/mahnungcron.class.php',
|
|
'objectname' => 'MahnungCron',
|
|
'method' => 'buildVorschlagsliste',
|
|
'parameters' => '',
|
|
'comment' => 'MahnungCronCommentBuild',
|
|
'frequency' => 1,
|
|
'unitfrequency' => 86400,
|
|
'status' => 1,
|
|
'test' => 'isModEnabled("mahnung")',
|
|
'priority' => 50,
|
|
),
|
|
1 => array(
|
|
'label' => 'MahnungCronVersandReminder',
|
|
'jobtype' => 'method',
|
|
'class' => '/mahnung/class/mahnungcron.class.php',
|
|
'objectname' => 'MahnungCron',
|
|
'method' => 'versandReminder',
|
|
'parameters' => '',
|
|
'comment' => 'MahnungCronCommentReminder',
|
|
'frequency' => 1,
|
|
'unitfrequency' => 86400,
|
|
'status' => 1,
|
|
'test' => 'isModEnabled("mahnung")',
|
|
'priority' => 55,
|
|
),
|
|
);
|
|
|
|
// Berechtigungen
|
|
$this->rights = array();
|
|
$r = 0;
|
|
|
|
$this->rights[$r][0] = $this->numero.'01';
|
|
$this->rights[$r][1] = 'PermMahnungRead';
|
|
$this->rights[$r][2] = 'r';
|
|
$this->rights[$r][3] = 1;
|
|
$this->rights[$r][4] = 'read';
|
|
$r++;
|
|
|
|
$this->rights[$r][0] = $this->numero.'02';
|
|
$this->rights[$r][1] = 'PermMahnungWrite';
|
|
$this->rights[$r][2] = 'w';
|
|
$this->rights[$r][3] = 0;
|
|
$this->rights[$r][4] = 'write';
|
|
$r++;
|
|
|
|
$this->rights[$r][0] = $this->numero.'03';
|
|
$this->rights[$r][1] = 'PermMahnungSend';
|
|
$this->rights[$r][2] = 'w';
|
|
$this->rights[$r][3] = 0;
|
|
$this->rights[$r][4] = 'send';
|
|
$r++;
|
|
|
|
$this->rights[$r][0] = $this->numero.'04';
|
|
$this->rights[$r][1] = 'PermMahnungDelete';
|
|
$this->rights[$r][2] = 'd';
|
|
$this->rights[$r][3] = 0;
|
|
$this->rights[$r][4] = 'delete';
|
|
$r++;
|
|
|
|
$this->rights[$r][0] = $this->numero.'05';
|
|
$this->rights[$r][1] = 'PermMahnungSetup';
|
|
$this->rights[$r][2] = 'w';
|
|
$this->rights[$r][3] = 0;
|
|
$this->rights[$r][4] = 'setup';
|
|
$r++;
|
|
|
|
// Linkes Menue unter "Rechnungen" (mainmenu=billing)
|
|
$this->menu = array();
|
|
$r = 0;
|
|
$this->menu[$r++] = array(
|
|
'fk_menu' => 'fk_mainmenu=billing',
|
|
'type' => 'left',
|
|
'titre' => 'MahnungMenu',
|
|
'prefix' => img_picto('', 'fa-envelope-open-text', 'class="pictofixedwidth valignmiddle paddingright"'),
|
|
'mainmenu' => 'billing',
|
|
'leftmenu' => 'mahnung',
|
|
'url' => '/custom/mahnung/list.php?mainmenu=billing&leftmenu=mahnung',
|
|
'langs' => 'mahnung@mahnung',
|
|
'position' => 300,
|
|
'enabled' => 'isModEnabled("mahnung")',
|
|
'perms' => '$user->hasRight("mahnung", "read")',
|
|
'target' => '',
|
|
'user' => 2,
|
|
);
|
|
$this->menu[$r++] = array(
|
|
'fk_menu' => 'fk_mainmenu=billing,fk_leftmenu=mahnung',
|
|
'type' => 'left',
|
|
'titre' => 'MahnungVorschlagsliste',
|
|
'mainmenu' => 'billing',
|
|
'leftmenu' => 'mahnung_vorschlag',
|
|
'url' => '/custom/mahnung/list.php?mainmenu=billing&leftmenu=mahnung&mode=vorschlag',
|
|
'langs' => 'mahnung@mahnung',
|
|
'position' => 301,
|
|
'enabled' => 'isModEnabled("mahnung")',
|
|
'perms' => '$user->hasRight("mahnung", "read")',
|
|
'target' => '',
|
|
'user' => 2,
|
|
);
|
|
$this->menu[$r++] = array(
|
|
'fk_menu' => 'fk_mainmenu=billing,fk_leftmenu=mahnung',
|
|
'type' => 'left',
|
|
'titre' => 'MahnungArchiv',
|
|
'mainmenu' => 'billing',
|
|
'leftmenu' => 'mahnung_archiv',
|
|
'url' => '/custom/mahnung/list.php?mainmenu=billing&leftmenu=mahnung&mode=archiv',
|
|
'langs' => 'mahnung@mahnung',
|
|
'position' => 302,
|
|
'enabled' => 'isModEnabled("mahnung")',
|
|
'perms' => '$user->hasRight("mahnung", "read")',
|
|
'target' => '',
|
|
'user' => 2,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Aufruf bei Modul-Aktivierung: Tabellen anlegen, Konstanten/Rechte/Menüs schreiben.
|
|
*
|
|
* @param string $options Optionen ('', 'noboxes')
|
|
* @return int<-1,1> 1 = OK, <=0 = Fehler
|
|
*/
|
|
public function init($options = '')
|
|
{
|
|
global $conf;
|
|
|
|
// Tabellen anlegen aus sql/-Verzeichnis
|
|
$result = $this->_load_tables('/mahnung/sql/');
|
|
if ($result < 0) {
|
|
return -1;
|
|
}
|
|
|
|
// Migration: Versand-Felder ergänzen, falls Tabelle aus alter Version stammt
|
|
$this->migrateVersandFelder();
|
|
|
|
// Migration: tms-Spalten auf "ON UPDATE CURRENT_TIMESTAMP" umstellen
|
|
$this->migrateTimestampSpalten();
|
|
|
|
// Default-Tracking-Patterns seeden (idempotent — nur beim ersten Mal)
|
|
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnungtrackingpattern.class.php';
|
|
MahnungTrackingPattern::seedDefaults($this->db);
|
|
|
|
// Dokumentenmodelle registrieren
|
|
$sql = array();
|
|
$sql[] = "DELETE FROM ".MAIN_DB_PREFIX."document_model WHERE nom = 'standard_mahnung' AND type = 'mahnung' AND entity = ".((int) $conf->entity);
|
|
$sql[] = "INSERT INTO ".MAIN_DB_PREFIX."document_model (nom, type, entity, libelle, description) VALUES ('standard_mahnung', 'mahnung', ".((int) $conf->entity).", 'Standard PDF (DIN 5008)', NULL)";
|
|
$sql[] = "DELETE FROM ".MAIN_DB_PREFIX."document_model WHERE nom = 'generic_mahnung_odt' AND type = 'mahnung' AND entity = ".((int) $conf->entity);
|
|
$sql[] = "INSERT INTO ".MAIN_DB_PREFIX."document_model (nom, type, entity, libelle, description) VALUES ('generic_mahnung_odt', 'mahnung', ".((int) $conf->entity).", 'ODT templates', 'MAHNUNG_ADDON_PDF_ODT_PATH')";
|
|
|
|
// ODT-Template-Verzeichnis anlegen
|
|
$doctemplatedir = DOL_DATA_ROOT.'/doctemplates/mahnung';
|
|
dol_mkdir($doctemplatedir);
|
|
|
|
return $this->_init($sql, $options);
|
|
}
|
|
|
|
/**
|
|
* Aufruf bei Modul-Deaktivierung. Tabellen bleiben erhalten (Datensicherheit).
|
|
*
|
|
* @param string $options Optionen
|
|
* @return int<-1,1> 1 = OK, <=0 = Fehler
|
|
*/
|
|
public function remove($options = '')
|
|
{
|
|
$sql = array();
|
|
return $this->_remove($sql, $options);
|
|
}
|
|
|
|
/**
|
|
* Ergänzt Versand- und Tracking-Felder an llx_mahnung_mahnung, wenn sie
|
|
* in einer älteren Schema-Version noch fehlen. Idempotent — fehlende
|
|
* Spalten werden geprüft via SHOW COLUMNS und nur dann hinzugefügt.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function migrateVersandFelder()
|
|
{
|
|
global $db;
|
|
|
|
$alter = array();
|
|
$cols = array(
|
|
'date_versand' => "ADD COLUMN date_versand DATETIME NULL",
|
|
'versandweg' => "ADD COLUMN versandweg VARCHAR(30) NULL",
|
|
'tracking_nr' => "ADD COLUMN tracking_nr VARCHAR(50) NULL",
|
|
'tracking_provider' => "ADD COLUMN tracking_provider VARCHAR(20) NULL",
|
|
);
|
|
foreach ($cols as $col => $clause) {
|
|
$res = $db->query("SHOW COLUMNS FROM ".MAIN_DB_PREFIX."mahnung_mahnung LIKE '".$db->escape($col)."'");
|
|
if ($res && $db->num_rows($res) == 0) {
|
|
$alter[] = $clause;
|
|
}
|
|
if ($res) {
|
|
$db->free($res);
|
|
}
|
|
}
|
|
if (!empty($alter)) {
|
|
$db->query("ALTER TABLE ".MAIN_DB_PREFIX."mahnung_mahnung ".implode(', ', $alter));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stellt die tms-Spalten der Modul-Tabellen auf das Dolibarr-Standardverhalten
|
|
* "DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" um. Ältere Installs
|
|
* hatten tms als reines "TIMESTAMP", was unter explicit_defaults_for_timestamp
|
|
* als "NULL DEFAULT NULL" angelegt wurde — tms blieb dadurch bei jedem UPDATE leer.
|
|
*
|
|
* Idempotent: Tabellen, deren tms bereits ON UPDATE trägt, werden übersprungen.
|
|
* Bestehende NULL-Werte werden vor der NOT-NULL-Umstellung aus datec befüllt.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function migrateTimestampSpalten()
|
|
{
|
|
global $db;
|
|
|
|
$tables = array('mahnung_mahnung', 'mahnung_stufe', 'mahnung_trackingpattern');
|
|
foreach ($tables as $table) {
|
|
$full = MAIN_DB_PREFIX.$table;
|
|
|
|
// Aktuelle tms-Definition prüfen — Tabelle/Spalte fehlt -> überspringen
|
|
$res = $db->query("SHOW COLUMNS FROM ".$full." LIKE 'tms'");
|
|
if (!$res || $db->num_rows($res) == 0) {
|
|
if ($res) {
|
|
$db->free($res);
|
|
}
|
|
continue;
|
|
}
|
|
$col = $db->fetch_object($res);
|
|
$db->free($res);
|
|
|
|
// Bereits migriert (Extra enthält "on update ...") -> nichts zu tun
|
|
if (stripos((string) $col->Extra, 'on update') !== false) {
|
|
continue;
|
|
}
|
|
|
|
// Alt-Zeilen ohne tms aus dem Erstelldatum befüllen, damit die
|
|
// anschließende NOT-NULL-Umstellung keine 0000-Werte erzeugt.
|
|
$db->query("UPDATE ".$full." SET tms = COALESCE(datec, NOW()) WHERE tms IS NULL");
|
|
|
|
$db->query(
|
|
"ALTER TABLE ".$full." MODIFY COLUMN tms TIMESTAMP NOT NULL"
|
|
." DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
|
|
);
|
|
}
|
|
}
|
|
}
|