* GPL v3+ * * Hook-Klasse fuer das Bericht-Modul. * Aktuell genutzt fuer: setImage('signature') beim ODT-Render von Lieferscheinen. */ class ActionsBericht { /** @var DoliDB */ private $db; /** * @param DoliDB $db Datenbank */ public function __construct($db) { $this->db = $db; } /** @var string */ public $error = ''; /** @var string[] */ public $errors = array(); /** @var array */ public $results = array(); /** * Hook fuer Dolibarrs ODT-Generation. Wird VOR dem saveToDisk aufgerufen. * Wenn das Objekt eine Expedition mit gesetzter $object->bericht_signature_png ist, * setzen wir das Bild im ODT-Platzhalter "signature" ein und befuellen Audit-Vars. * * Wichtig (KB #434): Bei MAIN_ODT_AS_PDF=libreoffice MUSS dieser Hook (nicht afterODTCreation) * verwendet werden — sonst ist das ODT bereits zu PDF konvertiert. * * @param array $parameters * @param object $object * @param string $action * @return int 0 = OK */ public function beforeODTSave($parameters, &$object, &$action) { dol_syslog('ActionsBericht::beforeODTSave aufgerufen', LOG_DEBUG); if (empty($parameters['odfHandler'])) { dol_syslog('ActionsBericht: kein odfHandler', LOG_DEBUG); return 0; } $odfHandler = $parameters['odfHandler']; $obj = isset($parameters['object']) ? $parameters['object'] : $object; if (!is_object($obj)) { dol_syslog('ActionsBericht: kein Objekt', LOG_DEBUG); return 0; } $cls = get_class($obj); $has = property_exists($obj, 'bericht_signature_png') ? 'yes' : 'no'; $png = $obj->bericht_signature_png ?? ''; dol_syslog('ActionsBericht: obj='.$cls.' bericht_signature_png prop='.$has.' val='.$png.' exists='.($png && file_exists($png) ? 'yes' : 'no'), LOG_DEBUG); // Nur greifen wenn shipments.php zuvor den Signatur-Pfad gesetzt hat if (empty($obj->bericht_signature_png) || !file_exists($obj->bericht_signature_png)) { dol_syslog('ActionsBericht: skip - kein signature_png', LOG_DEBUG); return 0; } // setImage: ersetzt {signature}-Text im ODT durch das PNG-draw-frame try { $ratio = (float) getDolGlobalString('BERICHT_SIGNATURE_IMAGE_RATIO', '0.35'); $odfHandler->setImage('signature', $obj->bericht_signature_png, $ratio); dol_syslog('ActionsBericht: setImage(signature) ok, ratio='.$ratio.' png='.$obj->bericht_signature_png, LOG_INFO); } catch (Throwable $e) { dol_syslog('ActionsBericht::beforeODTSave setImage(signature) failed: '.$e->getMessage(), LOG_WARNING); } // Zusatz-Variablen fuer Audit (optional im ODT als {signer_name} etc.) $meta = isset($obj->bericht_signature_meta) && is_array($obj->bericht_signature_meta) ? $obj->bericht_signature_meta : array(); $gps_str = ''; if (!empty($meta['gps_lat']) && !empty($meta['gps_lon'])) { $gps_str = sprintf('%.6f, %.6f', $meta['gps_lat'], $meta['gps_lon']); } $vars = array( 'signer_name' => (string) ($meta['signer_name'] ?? ''), 'signed_at' => (string) ($meta['signed_at'] ?? ''), 'gps' => $gps_str, ); foreach ($vars as $k => $v) { try { $odfHandler->setVars($k, $v, true, 'UTF-8'); } catch (Throwable $e) { /* Key muss nicht im ODT existieren */ } } return 0; } /** * Hook completeTabsHead: Reiter "Berichte" an der Kundenkarte — nur bei * vorhandenen Berichten. Ein fester Tab kann das nicht pruefen (verifCond() * laesst keine Abfrage zu). Die Leiste dort traegt ueber 20 Eintraege * (Stand 2026-09-07); auf Prod gibt es 9 Berichte bei einem Teil der Kunden. * Reiner Lesereiter — kein Anlegen-Weg, der verloren gehen koennte. * * @param array $parameters Hook-Parameter (context, head, mode, filterorigmodule) * @param object $object Societe * @param string $action Aktuelle Aktion * @param object $hookmanager Hook-Verwaltung * @return int 0 = weiter */ public function completeTabsHead($parameters, &$object, &$action, $hookmanager) { global $langs, $user; $contexts = explode(':', $parameters['context'] ?? ''); if (!in_array('thirdpartycard', $contexts, true)) { return 0; } if (empty($object->id) || !isset($parameters['head']) || !is_array($parameters['head'])) { return 0; } if (($parameters['mode'] ?? '') !== 'add' || ($parameters['filterorigmodule'] ?? '') !== 'external') { return 0; } if (!$user->hasRight('bericht', 'read')) { return 0; } $anzahl = $this->zaehleBerichte((int) $object->id); $head = &$parameters['head']; // Der frühere feste Tab steht bis zur Modul-Reaktivierung als // MAIN_MODULE_BERICHT_TABS_3 in der Datenbank — vorhandenen Eintrag // uebernehmen statt einen zweiten anzuhaengen, sonst waere der Reiter doppelt. $vorhanden = -1; foreach ($head as $i => $eintrag) { if (isset($eintrag[2]) && $eintrag[2] === 'bericht') { $vorhanden = $i; break; } } if ($anzahl < 1) { if ($vorhanden >= 0) { unset($head[$vorhanden]); $head = array_values($head); } return 0; } $langs->load('bericht@bericht'); $pos = ($vorhanden >= 0) ? $vorhanden : count($head); $head[$pos][0] = DOL_URL_ROOT.'/custom/bericht/bericht_thirdparty.php?socid='.((int) $object->id); $head[$pos][1] = $langs->trans('Berichte').' '.$anzahl.''; $head[$pos][2] = 'bericht'; return 0; } /** * Wie viele Berichte (an Auftrag, Rechnung oder Angebot) haengen an diesem Kunden? * Gleiche UNION-Abfrage wie in bericht_thirdparty.php. * * @param int $socId Kunde * @return int Anzahl */ private function zaehleBerichte($socId) { if ($socId <= 0) { return 0; } $sql = "SELECT COUNT(*) AS anzahl FROM ("; $sql .= "SELECT b.rowid FROM ".MAIN_DB_PREFIX."bericht b"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."commande c ON c.rowid = b.fk_element"; $sql .= " WHERE b.element_type = 'order' AND c.fk_soc = ".((int) $socId); $sql .= " UNION "; $sql .= "SELECT b.rowid FROM ".MAIN_DB_PREFIX."bericht b"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."facture f ON f.rowid = b.fk_element"; $sql .= " WHERE b.element_type = 'invoice' AND f.fk_soc = ".((int) $socId); $sql .= " UNION "; $sql .= "SELECT b.rowid FROM ".MAIN_DB_PREFIX."bericht b"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."propal p ON p.rowid = b.fk_element"; $sql .= " WHERE b.element_type = 'propal' AND p.fk_soc = ".((int) $socId); $sql .= ") AS alle"; $resql = $this->db->query($sql); if (!$resql) { return 0; } $obj = $this->db->fetch_object($resql); return $obj ? (int) $obj->anzahl : 0; } }