All checks were successful
Deploy bericht / deploy (push) Successful in 6s
- Neuer API-Endpoint api/shipments.php: Liste Lieferungen zu Auftrag, PDF-Stream, confirm (Unterschrift stempeln)
- ODT-Hook actions_bericht.class.php: ersetzt {signature} Platzhalter via odfphp->setImage, setzt {signer_name}/{signed_at}/{gps}
- Backup-Roundtrip: generateDocument-Backup → signed.pdf erzeugen → Original wiederherstellen
- JWT-Fallback in _jwt.php: ?jwt= Query-Param für <img>/<object> ohne Authorization-Header
- Admin: BERICHT_SIGNATURE_IMAGE_RATIO Feld, Toggle BERICHT_TAB_ON_SHIPMENT, Signature-Box-Editor
- DB: llx_bericht_signature_box für pro-Template mm-Box-Geometrie
- element_type='shipment' in modBericht + lib/bericht.lib.php
- element_element Richtung: commande=source, shipping=target (fk_target=expedition_id)
- DOL_DATA_ROOT-Auflösung für EXPEDITION_ADDON_PDF_ODT_PATH
- Sprachen: de_DE + en_US mit neuen Schlüsseln für Signatur-Workflow
[deploy]
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
257 lines
13 KiB
PHP
257 lines
13 KiB
PHP
<?php
|
||
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||
* GPL v3+ — siehe COPYING
|
||
*/
|
||
|
||
/**
|
||
* \defgroup bericht Module Bericht
|
||
* \brief Arbeitsberichte aus Anhängen erstellen, im Browser annotieren und an Rechnungen anhängen.
|
||
* \file htdocs/bericht/core/modules/modBericht.class.php
|
||
*/
|
||
include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php';
|
||
|
||
class modBericht extends DolibarrModules
|
||
{
|
||
public function __construct($db)
|
||
{
|
||
global $conf, $langs;
|
||
$this->db = $db;
|
||
|
||
$this->numero = 500033; // Frei (500021 kollidiert mit BankImport, siehe llx_rights_def)
|
||
$this->rights_class = 'bericht';
|
||
$this->family = "other";
|
||
$this->module_position = '90';
|
||
$this->name = preg_replace('/^mod/i', '', get_class($this));
|
||
$this->description = "Arbeitsberichte aus Rechnungs-Anhängen erstellen, im Browser annotieren und als PDF an die Rechnung anhängen.";
|
||
$this->descriptionlong = "Fügt Rechnungen, Aufträgen und Angeboten einen Reiter 'Bericht' hinzu. Anhänge auswählen, im Browser mit Pfeilen/Kreisen/Rechtecken/Text annotieren, Seiten verwalten, Deckblatt aus ODT-Vorlage einfügen und als PDF unter Verknüpfte Dokumente speichern.";
|
||
|
||
$this->editor_name = 'Alles Watt läuft';
|
||
$this->editor_url = '';
|
||
$this->version = '1.1.0';
|
||
$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
|
||
$this->picto = 'fa-file-pdf';
|
||
|
||
$this->module_parts = array(
|
||
'triggers' => 0,
|
||
'login' => 0,
|
||
'substitutions' => 0,
|
||
'menus' => 0,
|
||
'tpl' => 0,
|
||
'barcode' => 0,
|
||
'models' => 0,
|
||
'printing' => 0,
|
||
'theme' => 0,
|
||
'css' => array('/bericht/css/bericht.css'),
|
||
'js' => array(),
|
||
'hooks' => array('data' => array('odtgeneration')),
|
||
'moduleforexternal' => 0,
|
||
);
|
||
|
||
// Datenverzeichnisse
|
||
$this->dirs = array(
|
||
"/bericht/temp",
|
||
"/bericht/templates",
|
||
"/bericht/work",
|
||
);
|
||
|
||
// Konfigurationsseite im Admin-Bereich
|
||
$this->config_page_url = array("setup.php@bericht");
|
||
|
||
$this->hidden = false;
|
||
$this->depends = array();
|
||
$this->requiredby = array();
|
||
$this->conflictwith = array();
|
||
$this->langfiles = array("bericht@bericht");
|
||
$this->phpmin = array(7, 4);
|
||
$this->need_dolibarr_version = array(19, 0);
|
||
$this->need_javascript_ajax = 1;
|
||
|
||
// Konstanten beim Aktivieren anlegen
|
||
$this->const = array(
|
||
0 => array('BERICHT_DEFAULT_TEMPLATE', 'chaine', '', 'Standard ODT-Template für Deckblatt', 0, 'current', 0),
|
||
1 => array('BERICHT_TAB_ON_INVOICE', 'chaine', '1', 'Reiter Bericht auf Rechnungen anzeigen', 0, 'current', 0),
|
||
2 => array('BERICHT_TAB_ON_ORDER', 'chaine', '1', 'Reiter Bericht auf Aufträgen anzeigen', 0, 'current', 0),
|
||
3 => array('BERICHT_TAB_ON_PROPAL', 'chaine', '1', 'Reiter Bericht auf Angeboten anzeigen', 0, 'current', 0),
|
||
4 => array('BERICHT_BURN_ANNOTATIONS', 'chaine', '1', 'Annotationen beim Export ins PDF einbrennen', 0, 'current', 0),
|
||
5 => array('BERICHT_LIBREOFFICE_BIN', 'chaine', '/usr/bin/libreoffice', 'Pfad zu LibreOffice für ODT→PDF Konvertierung', 0, 'current', 0),
|
||
6 => array('BERICHT_TAB_ON_THIRDPARTY', 'chaine', '1', 'Reiter Berichte auf Kundenkarten (read-only Übersicht)', 0, 'current', 0),
|
||
7 => array('BERICHT_TAB_ON_SHIPMENT', 'chaine', '1', 'Reiter Bericht auf Lieferungen anzeigen', 0, 'current', 0),
|
||
8 => array('BERICHT_SIGNATURE_BOX_DEFAULT', 'chaine',
|
||
'{"page":"last","x_mm":120,"y_mm":230,"w_mm":70,"h_mm":35,"label":"Unterschrift Kunde"}',
|
||
'Standard-Geometrie der Unterschriftsbox auf Lieferschein-PDFs (JSON)', 0, 'current', 0),
|
||
9 => array('BERICHT_SIGNATURE_IMAGE_RATIO', 'chaine', '0.35',
|
||
'Groessen-Faktor fuer Signatur im ODT (0.3 ergibt ca. 6×3 cm bei 800×400 Pixel)', 0, 'current', 0),
|
||
);
|
||
|
||
// Tabs werden über den Hook (actions_bericht.class.php → addMoreActionsButtons / completeTabsHead)
|
||
// dynamisch hinzugefügt, weil wir die Sichtbarkeit pro Element-Typ über Konstanten steuern wollen.
|
||
// Statisch geht aber auch — sicherer und einfacher:
|
||
$this->tabs = array(
|
||
'invoice:+bericht:Bericht:bericht@bericht:$user->hasRight("bericht","read"):/custom/bericht/bericht_card.php?id=__ID__&element=invoice',
|
||
'order:+bericht:Bericht:bericht@bericht:$user->hasRight("bericht","read"):/custom/bericht/bericht_card.php?id=__ID__&element=order',
|
||
'propal:+bericht:Bericht:bericht@bericht:$user->hasRight("bericht","read"):/custom/bericht/bericht_card.php?id=__ID__&element=propal',
|
||
'thirdparty:+bericht:Berichte:bericht@bericht:$user->hasRight("bericht","read"):/custom/bericht/bericht_thirdparty.php?socid=__ID__',
|
||
'shipping:+bericht:Bericht:bericht@bericht:$user->hasRight("bericht","read"):/custom/bericht/bericht_card.php?id=__ID__&element=shipment',
|
||
);
|
||
|
||
$this->dictionaries = array();
|
||
$this->boxes = array();
|
||
// Kein Cronjob — Cleanup expired Upload-Tokens passiert on-demand
|
||
// beim Anlegen eines neuen Tokens (siehe BerichtUploadToken::create()).
|
||
$this->cronjobs = array();
|
||
|
||
// Rechte — wie Stundenzettel: [4]=perms, [5]=subperms (leer)
|
||
$this->rights = array();
|
||
$r = 0;
|
||
|
||
$this->rights[$r][0] = $this->numero + $r;
|
||
$this->rights[$r][1] = 'Berichte lesen';
|
||
$this->rights[$r][3] = 1; // Standard aktiviert
|
||
$this->rights[$r][4] = 'read';
|
||
$this->rights[$r][5] = '';
|
||
$r++;
|
||
|
||
$this->rights[$r][0] = $this->numero + $r;
|
||
$this->rights[$r][1] = 'Berichte erstellen und bearbeiten';
|
||
$this->rights[$r][3] = 1;
|
||
$this->rights[$r][4] = 'write';
|
||
$this->rights[$r][5] = '';
|
||
$r++;
|
||
|
||
$this->rights[$r][0] = $this->numero + $r;
|
||
$this->rights[$r][1] = 'Berichte löschen';
|
||
$this->rights[$r][3] = 1;
|
||
$this->rights[$r][4] = 'delete';
|
||
$this->rights[$r][5] = '';
|
||
$r++;
|
||
|
||
$this->rights[$r][0] = $this->numero + $r;
|
||
$this->rights[$r][1] = 'Modul Bericht administrieren (Templates verwalten)';
|
||
$this->rights[$r][3] = 0;
|
||
$this->rights[$r][4] = 'admin';
|
||
$this->rights[$r][5] = '';
|
||
$r++;
|
||
|
||
// Kein Menü — Berichte werden über Tabs auf Rechnungen/Aufträgen/Angeboten aufgerufen
|
||
$this->menu = array();
|
||
}
|
||
|
||
/**
|
||
* Beim Aktivieren ausgeführt: SQL laden, Verzeichnisse anlegen,
|
||
* vorhandene Extrafields auf llx_facture_extrafields prüfen und ggf. anlegen.
|
||
*/
|
||
public function init($options = '')
|
||
{
|
||
global $conf, $langs;
|
||
|
||
// SQL-Tabellen anlegen
|
||
$result = $this->_load_tables('/bericht/sql/');
|
||
if ($result < 0) {
|
||
return -1;
|
||
}
|
||
|
||
// Migrationen für bestehende Tabellen
|
||
$migrations = array(
|
||
// Phase 1.3: Seitenformat
|
||
"ALTER TABLE ".$this->db->prefix()."bericht ADD COLUMN page_format VARCHAR(8) DEFAULT 'A4'",
|
||
"ALTER TABLE ".$this->db->prefix()."bericht ADD COLUMN page_orientation VARCHAR(8) DEFAULT 'P'",
|
||
// Phase 1.4: Layout für mehrere Bilder pro Seite
|
||
"ALTER TABLE ".$this->db->prefix()."bericht_page ADD COLUMN layout VARCHAR(16) DEFAULT 'single'",
|
||
// Phase 1.5: Bildgröße/-position
|
||
"ALTER TABLE ".$this->db->prefix()."bericht_page ADD COLUMN image_scale FLOAT DEFAULT 1.0",
|
||
"ALTER TABLE ".$this->db->prefix()."bericht_page ADD COLUMN image_align VARCHAR(16) DEFAULT 'fit'",
|
||
// Phase 5.5: Bericht-Vorlagen
|
||
"ALTER TABLE ".$this->db->prefix()."bericht ADD COLUMN is_template TINYINT(1) DEFAULT 0",
|
||
"ALTER TABLE ".$this->db->prefix()."bericht ADD COLUMN template_label VARCHAR(255) DEFAULT NULL",
|
||
// Phase C: Titel pro Seite (Zwischentitel)
|
||
"ALTER TABLE ".$this->db->prefix()."bericht_page ADD COLUMN title VARCHAR(255) DEFAULT NULL",
|
||
// Phase 5.3: Versionierung
|
||
"ALTER TABLE ".$this->db->prefix()."bericht ADD COLUMN version INT DEFAULT 1",
|
||
"ALTER TABLE ".$this->db->prefix()."bericht ADD COLUMN fk_bericht_parent INT DEFAULT NULL",
|
||
// Phase 6: Composite-PNG (Client-WYSIWYG). Wenn gesetzt, wird die Seite
|
||
// komplett aus diesem einen PNG gerendert statt aus source_path + fabric_json.
|
||
// Der Editor rendert sein Fabric-Canvas bei jedem Save zu einem PNG und
|
||
// lädt es hoch — damit ist PDF-Output identisch mit Editor-Anzeige.
|
||
"ALTER TABLE ".$this->db->prefix()."bericht_page ADD COLUMN composite_path VARCHAR(512) DEFAULT NULL",
|
||
// Foto-Upload entkoppeln: Token an Dolibarr-Objekt statt an Bericht binden
|
||
"ALTER TABLE ".$this->db->prefix()."bericht_upload_token ADD COLUMN fk_element INTEGER NOT NULL DEFAULT 0",
|
||
"ALTER TABLE ".$this->db->prefix()."bericht_upload_token ADD COLUMN element_type VARCHAR(32) NOT NULL DEFAULT 'order'",
|
||
"UPDATE ".$this->db->prefix()."bericht_upload_token SET fk_element = fk_bericht, element_type = 'order' WHERE fk_element = 0 AND fk_bericht > 0",
|
||
// Phase 1.8: Lieferschein-Bestaetigung (PWA) - Signatur-Box-Geometrie pro PDF-Template
|
||
"CREATE TABLE IF NOT EXISTS ".$this->db->prefix()."bericht_signature_box ("
|
||
."rowid INT AUTO_INCREMENT PRIMARY KEY,"
|
||
."entity INT NOT NULL DEFAULT 1,"
|
||
."template_name VARCHAR(64) NOT NULL,"
|
||
."page VARCHAR(8) NOT NULL DEFAULT 'last',"
|
||
."x_mm DECIMAL(7,2) NOT NULL,"
|
||
."y_mm DECIMAL(7,2) NOT NULL,"
|
||
."w_mm DECIMAL(7,2) NOT NULL,"
|
||
."h_mm DECIMAL(7,2) NOT NULL,"
|
||
."label VARCHAR(128) DEFAULT 'Unterschrift Kunde',"
|
||
."fk_user_modif INT DEFAULT NULL,"
|
||
."tms TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL,"
|
||
."UNIQUE KEY idx_bsb_template (entity, template_name)"
|
||
.") ENGINE=innodb",
|
||
// Phase 5.9: Materialliste pro Auftrag
|
||
"CREATE TABLE IF NOT EXISTS ".$this->db->prefix()."bericht_material ("
|
||
."rowid INT AUTO_INCREMENT PRIMARY KEY,"
|
||
."element_type VARCHAR(32) NOT NULL,"
|
||
."fk_element INT NOT NULL,"
|
||
."label VARCHAR(255) NOT NULL,"
|
||
."qty FLOAT DEFAULT 1,"
|
||
."unit VARCHAR(16) DEFAULT 'Stk',"
|
||
."note TEXT DEFAULT NULL,"
|
||
."fk_user_creat INT NOT NULL,"
|
||
."datec DATETIME NOT NULL,"
|
||
."INDEX idx_bm_element (element_type, fk_element)"
|
||
.") ENGINE=innodb",
|
||
);
|
||
foreach ($migrations as $sql) {
|
||
// Errors ignorieren — Spalten existieren ggf. schon
|
||
$this->db->query($sql, 1);
|
||
}
|
||
|
||
// Extrafields auf facture sicherstellen — vorhandene werden NICHT angefasst
|
||
require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
|
||
$extrafields = new ExtraFields($this->db);
|
||
|
||
$fields = array(
|
||
'auftragsnummer' => array('label' => 'Auftragsnummer', 'type' => 'varchar', 'size' => 255, 'pos' => 100),
|
||
'angebotsnummer' => array('label' => 'Angebotsnummer', 'type' => 'varchar', 'size' => 255, 'pos' => 101),
|
||
'rechnungsnummer' => array('label' => 'Rechnungsnummer', 'type' => 'varchar', 'size' => 255, 'pos' => 102),
|
||
'beschreibung' => array('label' => 'Auftragsbeschreibung', 'type' => 'text', 'size' => 2000, 'pos' => 103),
|
||
'hinweis' => array('label' => 'Hinweis', 'type' => 'varchar', 'size' => 255, 'pos' => 104),
|
||
);
|
||
|
||
foreach ($fields as $name => $def) {
|
||
// Existiert das Feld bereits? → nicht überschreiben
|
||
$check = $this->db->query("SELECT rowid FROM ".$this->db->prefix()."extrafields"
|
||
." WHERE name = '".$this->db->escape($name)."'"
|
||
." AND elementtype = 'facture'");
|
||
if ($check && $this->db->num_rows($check) > 0) {
|
||
continue;
|
||
}
|
||
$extrafields->addExtraField(
|
||
$name,
|
||
$def['label'],
|
||
$def['type'],
|
||
$def['pos'],
|
||
$def['size'],
|
||
'facture',
|
||
0, 0, '', '', 1, '', 0, 0, '', '', 'bericht@bericht', '1'
|
||
);
|
||
}
|
||
|
||
$sql = array();
|
||
return $this->_init($sql, $options);
|
||
}
|
||
|
||
/**
|
||
* Beim Deaktivieren: Konstanten/Permissions entfernen, Daten und Extrafields BLEIBEN erhalten.
|
||
*/
|
||
public function remove($options = '')
|
||
{
|
||
$sql = array();
|
||
return $this->_remove($sql, $options);
|
||
}
|
||
}
|