- filemtime() warf Fehler wenn CSS-Datei nicht existiert - Alle Update 1.2.0 Felder werden jetzt bei Aktivierung geprüft/angelegt - SQL-Update-Skript erweitert Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
535 lines
20 KiB
PHP
535 lines
20 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.
|
|
*/
|
|
|
|
/**
|
|
* \defgroup stundenzettel Module Stundenzettel
|
|
* \brief Stundenzettel-Verwaltung für Aufträge
|
|
*/
|
|
|
|
/**
|
|
* \file core/modules/modStundenzettel.class.php
|
|
* \ingroup stundenzettel
|
|
* \brief Modulbeschreibung und Setup
|
|
*/
|
|
|
|
include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php';
|
|
|
|
/**
|
|
* Class modStundenzettel
|
|
*/
|
|
class modStundenzettel extends DolibarrModules
|
|
{
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param DoliDB $db Database handler
|
|
*/
|
|
public function __construct($db)
|
|
{
|
|
global $langs, $conf;
|
|
|
|
$this->db = $db;
|
|
|
|
// Modul-ID (muss eindeutig sein)
|
|
$this->numero = 500200;
|
|
|
|
// Familie/Kategorie
|
|
$this->family = "crm";
|
|
|
|
// Position im Menü (zwischen Einkauf=40 und Rechnung=50)
|
|
$this->module_position = '45';
|
|
|
|
// Modulname
|
|
$this->name = preg_replace('/^mod/i', '', get_class($this));
|
|
|
|
// Beschreibung
|
|
$this->description = "Stundenzettel-Verwaltung für Aufträge - Dokumentation von Arbeitszeiten und Materialverbrauch";
|
|
$this->descriptionlong = "Verwaltet Stundenzettel für Kundenaufträge. Ermöglicht die Dokumentation von Arbeitszeiten, verbrauchten Materialien und Notizen. Integration mit SubtotalTitle für Produktgruppen-Unterstützung.";
|
|
|
|
// Version
|
|
$this->version = '1.2.0';
|
|
|
|
// Autor
|
|
$this->editor_name = 'Data IT Solution';
|
|
$this->editor_url = 'https://data-it-solution.de';
|
|
|
|
// Konstanten
|
|
$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
|
|
|
|
// Pfade
|
|
$this->special = 0;
|
|
$this->picto = 'clock';
|
|
|
|
// Abhängigkeiten
|
|
$this->depends = array('modCommande'); // Aufträge erforderlich
|
|
$this->requiredby = array();
|
|
$this->conflictwith = array();
|
|
|
|
// PHP-Version
|
|
$this->phpmin = array(7, 4);
|
|
|
|
// Dolibarr-Version
|
|
$this->need_dolibarr_version = array(16, 0);
|
|
|
|
// Daten-Verzeichnisse
|
|
$this->dirs = array('/stundenzettel/temp');
|
|
|
|
// Konfiguration
|
|
$this->config_page_url = array("setup.php@stundenzettel");
|
|
|
|
// Konstanten
|
|
$this->const = array(
|
|
0 => array(
|
|
'STUNDENZETTEL_ADDON',
|
|
'chaine',
|
|
'mod_stundenzettel_standard',
|
|
'Nummernkreis für Stundenzettel',
|
|
0,
|
|
'current',
|
|
1
|
|
),
|
|
1 => array(
|
|
'STUNDENZETTEL_TIME_INPUT_MODE',
|
|
'chaine',
|
|
'dropdown',
|
|
'Zeiteingabe-Modus: dropdown (15-Min-Takt) oder text (freie Eingabe)',
|
|
0,
|
|
'current',
|
|
1
|
|
),
|
|
);
|
|
|
|
// Tabs - Tab im Auftrag (order = commande)
|
|
$this->tabs = array(
|
|
'order:+stundenzettel:Stundenzettel:stundenzettel@stundenzettel:$user->hasRight("stundenzettel","read"):/custom/stundenzettel/stundenzettel_commande.php?id=__ID__&tab=products&noredirect=1'
|
|
);
|
|
|
|
// Boxen/Widgets
|
|
$this->boxes = array(
|
|
0 => array(
|
|
'file' => 'box_stundenzettel_recent@stundenzettel',
|
|
'note' => 'Zuletzt bearbeitete Stundenzettel',
|
|
'enabledbydefaulton' => 'Home'
|
|
),
|
|
1 => array(
|
|
'file' => 'box_stundenzettel_open@stundenzettel',
|
|
'note' => 'Offene Stundenzettel',
|
|
'enabledbydefaulton' => 'Home'
|
|
),
|
|
);
|
|
|
|
// Cronjobs
|
|
$this->cronjobs = array();
|
|
|
|
// Berechtigungen
|
|
$this->rights = array();
|
|
$this->rights_class = 'stundenzettel';
|
|
|
|
$r = 0;
|
|
|
|
// Lesen
|
|
$this->rights[$r][0] = $this->numero + $r;
|
|
$this->rights[$r][1] = 'Stundenzettel lesen';
|
|
$this->rights[$r][3] = 0;
|
|
$this->rights[$r][4] = 'read';
|
|
$this->rights[$r][5] = '';
|
|
$r++;
|
|
|
|
// Erstellen
|
|
$this->rights[$r][0] = $this->numero + $r;
|
|
$this->rights[$r][1] = 'Stundenzettel erstellen';
|
|
$this->rights[$r][3] = 0;
|
|
$this->rights[$r][4] = 'write';
|
|
$this->rights[$r][5] = '';
|
|
$r++;
|
|
|
|
// Freigeben
|
|
$this->rights[$r][0] = $this->numero + $r;
|
|
$this->rights[$r][1] = 'Stundenzettel freigeben';
|
|
$this->rights[$r][3] = 0;
|
|
$this->rights[$r][4] = 'validate';
|
|
$this->rights[$r][5] = '';
|
|
$r++;
|
|
|
|
// Löschen
|
|
$this->rights[$r][0] = $this->numero + $r;
|
|
$this->rights[$r][1] = 'Stundenzettel löschen';
|
|
$this->rights[$r][3] = 0;
|
|
$this->rights[$r][4] = 'delete';
|
|
$this->rights[$r][5] = '';
|
|
$r++;
|
|
|
|
// Hauptmenü
|
|
$this->menu = array();
|
|
$r = 0;
|
|
|
|
// Top-Menü
|
|
$this->menu[$r] = array(
|
|
'fk_menu' => '',
|
|
'type' => 'top',
|
|
'titre' => 'Stundenzettel',
|
|
'prefix' => img_picto('', 'clock', 'class="pictofixedwidth"'),
|
|
'mainmenu' => 'stundenzettel',
|
|
'leftmenu' => '',
|
|
'url' => '/stundenzettel/index.php',
|
|
'langs' => 'stundenzettel@stundenzettel',
|
|
'position' => 45,
|
|
'enabled' => '$conf->stundenzettel->enabled',
|
|
'perms' => '$user->hasRight("stundenzettel", "read")',
|
|
'target' => '',
|
|
'user' => 0
|
|
);
|
|
$r++;
|
|
|
|
// Linkes Menü - Übersicht
|
|
$this->menu[$r] = array(
|
|
'fk_menu' => 'fk_mainmenu=stundenzettel',
|
|
'type' => 'left',
|
|
'titre' => 'Übersicht',
|
|
'prefix' => img_picto('', 'home', 'class="pictofixedwidth"'),
|
|
'mainmenu' => 'stundenzettel',
|
|
'leftmenu' => 'stundenzettel_index',
|
|
'url' => '/stundenzettel/index.php',
|
|
'langs' => 'stundenzettel@stundenzettel',
|
|
'position' => 100,
|
|
'enabled' => '$conf->stundenzettel->enabled',
|
|
'perms' => '$user->hasRight("stundenzettel", "read")',
|
|
'target' => '',
|
|
'user' => 0
|
|
);
|
|
$r++;
|
|
|
|
// Linkes Menü - Liste
|
|
$this->menu[$r] = array(
|
|
'fk_menu' => 'fk_mainmenu=stundenzettel',
|
|
'type' => 'left',
|
|
'titre' => 'Alle Stundenzettel',
|
|
'prefix' => img_picto('', 'list', 'class="pictofixedwidth"'),
|
|
'mainmenu' => 'stundenzettel',
|
|
'leftmenu' => 'stundenzettel_list',
|
|
'url' => '/stundenzettel/list.php',
|
|
'langs' => 'stundenzettel@stundenzettel',
|
|
'position' => 110,
|
|
'enabled' => '$conf->stundenzettel->enabled',
|
|
'perms' => '$user->hasRight("stundenzettel", "read")',
|
|
'target' => '',
|
|
'user' => 0
|
|
);
|
|
$r++;
|
|
|
|
// Linkes Menü - Neuer Stundenzettel
|
|
$this->menu[$r] = array(
|
|
'fk_menu' => 'fk_mainmenu=stundenzettel',
|
|
'type' => 'left',
|
|
'titre' => 'Neuer Stundenzettel',
|
|
'prefix' => img_picto('', 'add', 'class="pictofixedwidth"'),
|
|
'mainmenu' => 'stundenzettel',
|
|
'leftmenu' => 'stundenzettel_new',
|
|
'url' => '/stundenzettel/card.php?action=create',
|
|
'langs' => 'stundenzettel@stundenzettel',
|
|
'position' => 120,
|
|
'enabled' => '$conf->stundenzettel->enabled',
|
|
'perms' => '$user->hasRight("stundenzettel", "write")',
|
|
'target' => '',
|
|
'user' => 0
|
|
);
|
|
$r++;
|
|
|
|
// Linkes Menü - Offene Stundenzettel (Entwürfe)
|
|
$this->menu[$r] = array(
|
|
'fk_menu' => 'fk_mainmenu=stundenzettel',
|
|
'type' => 'left',
|
|
'titre' => 'Offene Stundenzettel',
|
|
'prefix' => img_picto('', 'statut0', 'class="pictofixedwidth"'),
|
|
'mainmenu' => 'stundenzettel',
|
|
'leftmenu' => 'stundenzettel_open',
|
|
'url' => '/stundenzettel/list.php?search_status=0',
|
|
'langs' => 'stundenzettel@stundenzettel',
|
|
'position' => 130,
|
|
'enabled' => '$conf->stundenzettel->enabled',
|
|
'perms' => '$user->hasRight("stundenzettel", "read")',
|
|
'target' => '',
|
|
'user' => 0
|
|
);
|
|
$r++;
|
|
|
|
// Linkes Menü - Meine Stundenzettel
|
|
$this->menu[$r] = array(
|
|
'fk_menu' => 'fk_mainmenu=stundenzettel',
|
|
'type' => 'left',
|
|
'titre' => 'Meine Stundenzettel',
|
|
'prefix' => img_picto('', 'user', 'class="pictofixedwidth"'),
|
|
'mainmenu' => 'stundenzettel',
|
|
'leftmenu' => 'stundenzettel_my',
|
|
'url' => '/stundenzettel/list.php?search_author=__USER_ID__',
|
|
'langs' => 'stundenzettel@stundenzettel',
|
|
'position' => 140,
|
|
'enabled' => '$conf->stundenzettel->enabled',
|
|
'perms' => '$user->hasRight("stundenzettel", "read")',
|
|
'target' => '',
|
|
'user' => 0
|
|
);
|
|
$r++;
|
|
|
|
// Linkes Menü - Freigegebene Stundenzettel
|
|
$this->menu[$r] = array(
|
|
'fk_menu' => 'fk_mainmenu=stundenzettel',
|
|
'type' => 'left',
|
|
'titre' => 'Freigegeben',
|
|
'prefix' => img_picto('', 'statut4', 'class="pictofixedwidth"'),
|
|
'mainmenu' => 'stundenzettel',
|
|
'leftmenu' => 'stundenzettel_validated',
|
|
'url' => '/stundenzettel/list.php?search_status=1',
|
|
'langs' => 'stundenzettel@stundenzettel',
|
|
'position' => 150,
|
|
'enabled' => '$conf->stundenzettel->enabled',
|
|
'perms' => '$user->hasRight("stundenzettel", "read")',
|
|
'target' => '',
|
|
'user' => 0
|
|
);
|
|
$r++;
|
|
}
|
|
|
|
/**
|
|
* Funktion beim Aktivieren des Moduls
|
|
*
|
|
* @param string $options Options when enabling module
|
|
* @return int 1 if OK, 0 if KO
|
|
*/
|
|
public function init($options = '')
|
|
{
|
|
global $conf;
|
|
|
|
$result = $this->_load_tables('/stundenzettel/sql/');
|
|
if ($result < 0) {
|
|
return -1;
|
|
}
|
|
|
|
// Extrafeld "Auftragsbeschreibung" für Aufträge anlegen
|
|
$this->createExtraFieldOrderDescription();
|
|
|
|
// View für Dienstleistungen erstellen (für sellist-Filter)
|
|
$this->createServicesView();
|
|
|
|
// Extrafeld "Standard-Leistung" für Kunden anlegen
|
|
$this->createExtraFieldDefaultService();
|
|
|
|
// Stundenpreis-Felder hinzufügen (Update 1.2.0)
|
|
$this->addHourlyRateFields();
|
|
|
|
$sql = array();
|
|
|
|
return $this->_init($sql, $options);
|
|
}
|
|
|
|
/**
|
|
* Erstellt das Extrafeld "Auftragsbeschreibung" für Aufträge (commande)
|
|
*
|
|
* @return int 1 if created or exists, -1 if error
|
|
*/
|
|
private function createExtraFieldOrderDescription()
|
|
{
|
|
global $langs;
|
|
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
|
|
|
|
$extrafields = new ExtraFields($this->db);
|
|
|
|
// Prüfen ob Extrafeld bereits existiert
|
|
$extrafields->fetch_name_optionals_label('commande');
|
|
|
|
if (!isset($extrafields->attributes['commande']['label']['auftragsbeschreibung'])) {
|
|
// Extrafeld anlegen: Textarea für Auftragsbeschreibung
|
|
$result = $extrafields->addExtraField(
|
|
'auftragsbeschreibung', // attrname - Feldname
|
|
'Auftragsbeschreibung', // label - Anzeigename
|
|
'text', // type - Feldtyp (text = Textarea)
|
|
100, // pos - Position
|
|
'', // size - Größe (leer für text)
|
|
'commande', // elementtype - Objekttyp
|
|
0, // unique
|
|
0, // required
|
|
'', // default_value
|
|
array('options' => array()), // param
|
|
1, // alwayseditable
|
|
'', // perms
|
|
1, // list - In Liste anzeigen
|
|
'', // ishidden
|
|
0, // computed
|
|
'', // entity
|
|
'', // langfile
|
|
'', // enabled
|
|
0, // totalizable
|
|
0, // printable
|
|
array('css' => '', 'cssview' => '', 'csslist' => '') // moreparams
|
|
);
|
|
|
|
if ($result < 0) {
|
|
dol_syslog("modStundenzettel::createExtraFieldOrderDescription Error creating extrafield: ".$extrafields->error, LOG_ERR);
|
|
return -1;
|
|
}
|
|
|
|
dol_syslog("modStundenzettel::createExtraFieldOrderDescription Extrafield 'auftragsbeschreibung' created successfully", LOG_DEBUG);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Erstellt eine View für Dienstleistungen (fk_product_type = 1)
|
|
* Diese View wird für das sellist-Extrafeld verwendet
|
|
*
|
|
* @return int 1 if created, -1 if error
|
|
*/
|
|
private function createServicesView()
|
|
{
|
|
$sql = "CREATE OR REPLACE VIEW ".MAIN_DB_PREFIX."product_services AS
|
|
SELECT rowid, ref, label, description, fk_product_type, entity, tosell, tobuy
|
|
FROM ".MAIN_DB_PREFIX."product
|
|
WHERE fk_product_type = 1";
|
|
|
|
$resql = $this->db->query($sql);
|
|
if (!$resql) {
|
|
dol_syslog("modStundenzettel::createServicesView Error: ".$this->db->lasterror(), LOG_ERR);
|
|
return -1;
|
|
}
|
|
|
|
dol_syslog("modStundenzettel::createServicesView View created successfully", LOG_DEBUG);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Erstellt das Extrafeld "Standard-Leistung" für Kunden (societe)
|
|
*
|
|
* @return int 1 if created or exists, -1 if error
|
|
*/
|
|
private function createExtraFieldDefaultService()
|
|
{
|
|
global $langs;
|
|
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
|
|
|
|
$extrafields = new ExtraFields($this->db);
|
|
|
|
// Prüfen ob Extrafeld bereits existiert
|
|
$extrafields->fetch_name_optionals_label('societe');
|
|
|
|
if (!isset($extrafields->attributes['societe']['label']['stundenzettel_default_service'])) {
|
|
// Extrafeld anlegen: sellist mit View für nur Dienstleistungen
|
|
$result = $extrafields->addExtraField(
|
|
'stundenzettel_default_service', // attrname - Feldname
|
|
'Standard-Leistung (Stundenzettel)', // label - Anzeigename
|
|
'sellist', // type - Feldtyp (sellist = SQL-basierte Auswahlliste)
|
|
200, // pos - Position
|
|
'', // size - Größe
|
|
'societe', // elementtype - Objekttyp (Kunden/Lieferanten)
|
|
0, // unique
|
|
0, // required
|
|
'', // default_value
|
|
array('options' => array('product_services:label:rowid' => null)), // param - View mit nur Dienstleistungen
|
|
1, // alwayseditable
|
|
'', // perms
|
|
1, // list - In Liste anzeigen
|
|
'', // ishidden
|
|
0, // computed
|
|
'', // entity
|
|
'', // langfile
|
|
'$conf->stundenzettel->enabled', // enabled - nur wenn Modul aktiv
|
|
0, // totalizable
|
|
0, // printable
|
|
array('css' => '', 'cssview' => '', 'csslist' => '') // moreparams
|
|
);
|
|
|
|
if ($result < 0) {
|
|
dol_syslog("modStundenzettel::createExtraFieldDefaultService Error creating extrafield: ".$extrafields->error, LOG_ERR);
|
|
return -1;
|
|
}
|
|
|
|
dol_syslog("modStundenzettel::createExtraFieldDefaultService Extrafield 'stundenzettel_default_service' created successfully", LOG_DEBUG);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Fügt die neuen Felder für Update 1.2.0 hinzu
|
|
* Wird bei jeder Modulaktivierung ausgeführt - IF NOT EXISTS verhindert Fehler
|
|
*
|
|
* @return int 1 if OK, -1 if error
|
|
*/
|
|
private function addHourlyRateFields()
|
|
{
|
|
// 1. Spalte fk_product zur Leistungstabelle hinzufügen falls nicht vorhanden
|
|
$sql1 = "ALTER TABLE ".MAIN_DB_PREFIX."stundenzettel_leistung
|
|
ADD COLUMN IF NOT EXISTS fk_product INTEGER DEFAULT NULL
|
|
COMMENT 'Verknüpfung zur Leistungsposition (Dienstleistung)'";
|
|
|
|
$resql1 = $this->db->query($sql1);
|
|
if (!$resql1) {
|
|
// Fallback für ältere MySQL-Versionen ohne IF NOT EXISTS
|
|
$sql1b = "SHOW COLUMNS FROM ".MAIN_DB_PREFIX."stundenzettel_leistung LIKE 'fk_product'";
|
|
$resql1b = $this->db->query($sql1b);
|
|
if ($resql1b && $this->db->num_rows($resql1b) == 0) {
|
|
$sql1c = "ALTER TABLE ".MAIN_DB_PREFIX."stundenzettel_leistung
|
|
ADD COLUMN fk_product INTEGER DEFAULT NULL";
|
|
$this->db->query($sql1c);
|
|
}
|
|
}
|
|
|
|
// 2. Spalte hourly_rate zur Haupttabelle hinzufügen falls nicht vorhanden
|
|
$sql2 = "ALTER TABLE ".MAIN_DB_PREFIX."stundenzettel
|
|
ADD COLUMN IF NOT EXISTS hourly_rate DOUBLE(24,8) DEFAULT NULL
|
|
COMMENT 'Stundenpreis (NULL = Standard verwenden)'";
|
|
|
|
$resql2 = $this->db->query($sql2);
|
|
if (!$resql2) {
|
|
// Fallback für ältere MySQL-Versionen ohne IF NOT EXISTS
|
|
$sql2b = "SHOW COLUMNS FROM ".MAIN_DB_PREFIX."stundenzettel LIKE 'hourly_rate'";
|
|
$resql2b = $this->db->query($sql2b);
|
|
if ($resql2b && $this->db->num_rows($resql2b) == 0) {
|
|
$sql2c = "ALTER TABLE ".MAIN_DB_PREFIX."stundenzettel
|
|
ADD COLUMN hourly_rate DOUBLE(24,8) DEFAULT NULL";
|
|
$this->db->query($sql2c);
|
|
}
|
|
}
|
|
|
|
// 3. Spalte hourly_rate_is_custom zur Haupttabelle hinzufügen falls nicht vorhanden
|
|
$sql3 = "ALTER TABLE ".MAIN_DB_PREFIX."stundenzettel
|
|
ADD COLUMN IF NOT EXISTS hourly_rate_is_custom TINYINT DEFAULT 0 NOT NULL
|
|
COMMENT '1 = manuell geändert'";
|
|
|
|
$resql3 = $this->db->query($sql3);
|
|
if (!$resql3) {
|
|
// Fallback für ältere MySQL-Versionen ohne IF NOT EXISTS
|
|
$sql3b = "SHOW COLUMNS FROM ".MAIN_DB_PREFIX."stundenzettel LIKE 'hourly_rate_is_custom'";
|
|
$resql3b = $this->db->query($sql3b);
|
|
if ($resql3b && $this->db->num_rows($resql3b) == 0) {
|
|
$sql3c = "ALTER TABLE ".MAIN_DB_PREFIX."stundenzettel
|
|
ADD COLUMN hourly_rate_is_custom TINYINT DEFAULT 0 NOT NULL";
|
|
$this->db->query($sql3c);
|
|
}
|
|
}
|
|
|
|
dol_syslog("modStundenzettel::addHourlyRateFields All 1.2.0 fields checked/added", LOG_DEBUG);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Funktion beim Deaktivieren des Moduls
|
|
*
|
|
* @param string $options Options when disabling module
|
|
* @return int 1 if OK, 0 if KO
|
|
*/
|
|
public function remove($options = '')
|
|
{
|
|
$sql = array();
|
|
|
|
return $this->_remove($sql, $options);
|
|
}
|
|
}
|