* GPL v3+ * * Editor-Seite des Bericht-Moduls. * Eingang über Tab "Bericht" auf Rechnung/Auftrag/Angebot. * * Aufruf: * /bericht/bericht_card.php?id=&element=invoice|order|propal * /bericht/bericht_card.php?berichtid= (direkter Aufruf eines bestehenden Berichts) */ // Standard-Dolibarr-Include-Kaskade (symlink-sicher) $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/functions2.lib.php'; require_once __DIR__.'/class/bericht.class.php'; require_once __DIR__.'/lib/bericht.lib.php'; if (!$user->hasRight('bericht', 'read')) accessforbidden(); $langs->loadLangs(array("bericht@bericht", "main", "other")); $id = GETPOSTINT('id'); $berichtid = GETPOSTINT('berichtid'); $element = GETPOST('element', 'alpha'); $action = GETPOST('action', 'alpha'); // Bericht laden bzw. Parent ermitteln $bericht = null; $parent = null; if ($berichtid > 0) { $bericht = new Bericht($db); if ($bericht->fetch($berichtid) <= 0) { accessforbidden('Bericht nicht gefunden'); } $element = $bericht->element_type; $id = $bericht->fk_element; } $parent = bericht_fetch_parent($db, $element, $id); if (!$parent) { setEventMessages($langs->trans("BerichtErrorNoParent"), null, 'errors'); llxHeader('', $langs->trans("Bericht")); print '
'.$langs->trans("BerichtErrorNoParent").'
'; llxFooter(); exit; } $auftragsnummer = bericht_get_auftragsnummer($parent); // Aktion: Bericht zur aktuellen Karte verknüpfen if ($action === 'link' && $berichtid > 0 && $user->hasRight('bericht', 'write')) { $b = new Bericht($db); if ($b->fetch($berichtid) > 0) { $b->linkToElement($element, $parent->id); setEventMessages('Bericht der Rechnung zugeordnet', null, 'mesgs'); } header("Location: ".$_SERVER['PHP_SELF'].'?id='.$parent->id.'&element='.$element); exit; } if ($action === 'unlink' && $berichtid > 0 && $user->hasRight('bericht', 'write')) { $b = new Bericht($db); if ($b->fetch($berichtid) > 0) { $b->unlinkFromElement($element, $parent->id); setEventMessages('Verknüpfung entfernt', null, 'mesgs'); } header("Location: ".$_SERVER['PHP_SELF'].'?id='.$parent->id.'&element='.$element); exit; } // Aktion: neuen Bericht anlegen (ggf. aus Vorlage) if ($action === 'create' && $user->hasRight('bericht', 'write')) { $tpl_id = GETPOSTINT('template_id'); if ($tpl_id > 0) { $new_id = Bericht::createFromTemplate($db, $user, $tpl_id, $element, $parent->id, $auftragsnummer); if ($new_id) { header("Location: ".$_SERVER['PHP_SELF'].'?berichtid='.$new_id); exit; } setEventMessages('Vorlage konnte nicht angewendet werden', null, 'errors'); } $b = new Bericht($db); $b->element_type = $element; $b->fk_element = $parent->id; $b->titel = GETPOST('titel', 'alphanohtml') ?: ('Bericht '.$auftragsnummer); $b->auftragsnummer = $auftragsnummer; $b->template_odt = getDolGlobalString('BERICHT_DEFAULT_TEMPLATE', ''); if ($b->create($user) > 0) { header("Location: ".$_SERVER['PHP_SELF'].'?berichtid='.$b->id); exit; } setEventMessages($b->error, $b->errors, 'errors'); } // Aktion: neue Version erstellen if ($action === 'new_version' && $berichtid > 0 && $user->hasRight('bericht', 'write')) { $new_id = $bericht->duplicateAsNewVersion($user); if ($new_id) { setEventMessages('Neue Version erstellt', null, 'mesgs'); header("Location: ".$_SERVER['PHP_SELF'].'?berichtid='.$new_id); exit; } setEventMessages('Versionierung fehlgeschlagen', null, 'errors'); } // Aktion: Bericht löschen if ($action === 'delete' && $berichtid > 0 && $user->hasRight('bericht', 'delete')) { if ($bericht->delete($user) > 0) { setEventMessages($langs->trans("BerichtDeleteSuccess"), null, 'mesgs'); // Zurück auf die Tab-Übersicht (gleiche URL ohne berichtid) header("Location: ".$_SERVER['PHP_SELF'].'?id='.$parent->id.'&element='.$element); exit; } setEventMessages($bericht->error, $bericht->errors, 'errors'); } /* * Anzeige */ $title = $langs->trans("Bericht").' — '.$parent->ref; llxHeader('', $title, '', '', 0, 0, array(), array(dol_buildpath('/bericht/css/imageviewer.css', 1)), '', 'mod-bericht page-bericht-card'); // Header des Parent-Objekts (Standard-Dolibarr-Tabs für Rechnung/Auftrag/Angebot) if ($element === 'invoice') { require_once DOL_DOCUMENT_ROOT.'/core/lib/invoice.lib.php'; $head = facture_prepare_head($parent); print dol_get_fiche_head($head, 'bericht', $langs->trans("Bill"), -1, 'bill'); } elseif ($element === 'order') { require_once DOL_DOCUMENT_ROOT.'/core/lib/order.lib.php'; $head = commande_prepare_head($parent); print dol_get_fiche_head($head, 'bericht', $langs->trans("CustomerOrder"), -1, 'order'); } elseif ($element === 'propal') { require_once DOL_DOCUMENT_ROOT.'/core/lib/propal.lib.php'; $head = propal_prepare_head($parent); print dol_get_fiche_head($head, 'bericht', $langs->trans("Proposal"), -1, 'propal'); } // Banner mit Parent-Infos $linkback = ''.$langs->trans("BackToList").''; dol_banner_tab($parent, 'ref', $linkback, 1, 'ref'); print dol_get_fiche_end(); print '
'; if (!$bericht) { /* * MODUS A: Übersicht * - Berichte direkt zugeordnet (element_type+fk_element) * - Berichte zusätzlich verknüpft (llx_element_element) * - NUR auf Rechnung: Berichte aus den verknüpften Aufträgen (read-only Sicht) */ $list_direct = Bericht::fetchAllForElement($db, $element, $parent->id); $list_linked = Bericht::fetchLinkedForElement($db, $element, $parent->id); // Aus verknüpften Aufträgen: nur wenn aktuelle Karte eine Rechnung ist $list_from_orders = array(); if ($element === 'invoice') { $parent->fetchObjectLinked(); if (!empty($parent->linkedObjects['commande'])) { foreach ($parent->linkedObjects['commande'] as $linked_order) { $more = Bericht::fetchAllForElement($db, 'order', $linked_order->id); foreach ($more as $b) { // Nicht doppelt zeigen wenn schon manuell verknüpft $b->_source_order_ref = $linked_order->ref; $b->_source_order_id = $linked_order->id; $list_from_orders[] = $b; } } } } // Set der bereits verknüpften IDs für Vergleich $linked_ids = array_map(function ($b) { return (int) $b->id; }, $list_linked); print '
'; print '
'; print '

'.$langs->trans("Berichte").'

'; if ($user->hasRight('bericht', 'write')) { $templates = Bericht::fetchAllTemplates($db); print '
'; print ''; print ''; print ''; print ''; if (!empty($templates)) { print ''; } print ''; print '
'; } print '
'; /* ----- Helper: Bericht-Tabelle rendern ----- */ $renderBerichtTable = function ($title, $items, $mode = 'direct') use ($langs, $user, $element, $parent) { if (empty($items)) return; print '

'.$title.' ('.count($items).')

'; print ''; print ''; print ''; print ''; if ($mode === 'from_order') print ''; print ''; print ''; print ''; print ''; foreach ($items as $b) { $url = $_SERVER['PHP_SELF'].'?berichtid='.$b->id; print ''; print ''; print ''; if ($mode === 'from_order') { print ''; } print ''; print ''; print ''; print ''; } print '
'.$langs->trans("Ref").''.$langs->trans("BerichtTitle").'Aus Auftrag'.$langs->trans("BerichtCreatedAt").''.$langs->trans("BerichtStatus").''.$langs->trans("Action").'
'.dol_escape_htmltag($b->ref).''.dol_escape_htmltag($b->titel).''.dol_escape_htmltag($b->_source_order_ref ?? '').''.dol_print_date($b->datec, 'dayhour').''.$b->getLibStatut().''; print ''.$langs->trans("Open").' '; if ($mode === 'from_order' && $user->hasRight('bericht', 'write')) { print '→ Übernehmen '; } if ($mode === 'linked' && $user->hasRight('bericht', 'write')) { print '⊗ Lösen '; } if ($mode !== 'from_order' && $user->hasRight('bericht', 'delete')) { print ''.$langs->trans("Delete").''; } print '
'; }; if (empty($list_direct) && empty($list_linked) && empty($list_from_orders)) { print '
'.$langs->trans("BerichtNoReports").'
'; } else { $renderBerichtTable('Berichte direkt zu dieser Karte', $list_direct, 'direct'); $renderBerichtTable('Zusätzlich verknüpfte Berichte', $list_linked, 'linked'); if ($element === 'invoice') { // Auftragsberichte filtern: solche die schon verknüpft sind, ausblenden $remaining = array_filter($list_from_orders, function ($b) use ($linked_ids) { return !in_array((int) $b->id, $linked_ids, true); }); $renderBerichtTable('Berichte aus verknüpften Aufträgen', $remaining, 'from_order'); } } print '
'; // Bestätigungsdialog für Lösch-Buttons (eigener Modal statt browser-confirm) print ''; } else { /* * MODUS B: Editor — bestehender Bericht wird bearbeitet */ $pages = BerichtPage::fetchAllForBericht($db, $bericht->id); $attachments = bericht_collect_attachments($db, $parent, $element); $templates = bericht_list_templates(); // Daten für JS bereitstellen $editor_config = array( 'berichtid' => (int) $bericht->id, 'element_id' => (int) $parent->id, 'element_type' => $element, 'token' => newToken(), 'urls' => array( 'save_annotations' => dol_buildpath('/bericht/ajax/save_annotations.php', 1), 'upload_extra' => dol_buildpath('/bericht/ajax/upload_extra.php', 1), 'add_attachment' => dol_buildpath('/bericht/ajax/add_attachment.php', 1), 'delete_page' => dol_buildpath('/bericht/ajax/delete_page.php', 1), 'reorder_pages' => dol_buildpath('/bericht/ajax/reorder_pages.php', 1), 'page_image' => dol_buildpath('/bericht/ajax/page_image.php', 1), 'generate_pdf' => dol_buildpath('/bericht/ajax/generate_pdf.php', 1), 'preview_pdf' => dol_buildpath('/bericht/ajax/preview_pdf.php', 1), 'delete_attachment'=> dol_buildpath('/bericht/ajax/delete_attachment.php', 1), 'save_meta' => dol_buildpath('/bericht/ajax/save_meta.php', 1), 'save_page_options'=> dol_buildpath('/bericht/ajax/save_page_options.php', 1), 'create_grid_page' => dol_buildpath('/bericht/ajax/create_grid_page.php', 1), 'set_slot_image' => dol_buildpath('/bericht/ajax/set_slot_image.php', 1), 'create_upload_token' => dol_buildpath('/bericht/ajax/create_upload_token.php', 1), 'list_pages' => dol_buildpath('/bericht/ajax/list_pages.php', 1), 'verify_signature' => dol_buildpath('/bericht/ajax/verify_signature.php', 1), 'save_as_template' => dol_buildpath('/bericht/ajax/save_as_template.php', 1), ), 'lang' => array( 'undo' => $langs->trans("BerichtUndo"), 'redo' => $langs->trans("BerichtRedo"), 'select' => $langs->trans("BerichtToolSelect"), 'draw' => $langs->trans("BerichtToolDraw"), 'rect' => $langs->trans("BerichtToolRect"), 'circle' => $langs->trans("BerichtToolCircle"), 'arrow' => $langs->trans("BerichtToolArrow"), 'text' => $langs->trans("BerichtToolText"), 'delete' => $langs->trans("BerichtToolDelete"), 'note_hint' => $langs->trans("BerichtNoteHint"), 'confirm_del' => $langs->trans("BerichtConfirmDelete"), ), ); print '
'; // Kopfzeile mit Bericht-Meta print '
'; print '
'; print ''; print ''; // Versionsliste derselben Kette ermitteln $chain_id = $bericht->fk_bericht_parent ?: $bericht->id; $version_links = ''; $vres = $db->query("SELECT rowid, version FROM ".$db->prefix()."bericht" ." WHERE rowid = ".((int) $chain_id)." OR fk_bericht_parent = ".((int) $chain_id) ." ORDER BY version ASC"); if ($vres) { $vs = array(); while ($vr = $db->fetch_object($vres)) { $active = ((int) $vr->rowid === (int) $bericht->id); $vs[] = 'v'.$vr->version.''; } if (count($vs) > 1) $version_links = '   Versionen: '.implode(' · ', $vs).''; } print ''; print ''; print ''; print ''; print ''; print ''; print ''; // Format und Orientation print ''; print ''; print '
'.$langs->trans("Ref").''.dol_escape_htmltag($bericht->ref).' v'.((int) $bericht->version).''.$version_links.''.$langs->trans("BerichtAuftragsnummer").''.dol_escape_htmltag($bericht->auftragsnummer).'
'.$langs->trans("BerichtTitle").''.$langs->trans("BerichtTemplate").'
Format'; print ''; print '   '; print ''; print 'Wird beim Finalisieren angewendet
'; print '
'; print '
'; // Hauptlayout: links Anhänge, Mitte Editor, rechts Seiten print '
'; // LINKS: Anhänge-Browser print ''; // MITTE: PDF.js + Fabric.js Editor print '
'; print '
'; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; // ====== Zweite Zeile ====== print '
'; // Text-Optionen print ''; print ''; print ''; print ''; print ''; print ''; print ''; // Zoom print ''; print '100%'; print ''; print ''; print ''; print ''; print ''; // ====== Dritte Zeile: Layout & Bildgröße der aktuellen Seite ====== print '
'; print ''; print ''; print ''; print '
'; print '
'; print ''; print ''; print '
'; print '
'; print ''; // DolEditor (CKEditor) — wird im PDF mit writeHTMLCell gerendert require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php'; $noteEditor = new DolEditor('page-note', '', '100%', 120, 'dolibarr_notes', 'In', false, true, true, 4, '100%'); $noteEditor->Create(0); print '
'; print '
'; // RECHTS: Seiten-Thumbnails print ''; print '
'; // .bericht-layout // Footer-Aktionen print '
'; print ''; print ''; print ''; print '🔀 Neue Version'; print ''; if ($user->hasRight('bericht', 'delete')) { print '🗑️ '.$langs->trans("Delete").''; } print '
'; print '
'; // .bericht-editor // PDF-Vorschau-Modal print ''; // QR-Code-Modal für Mobile-Upload print ''; // PDF.js + Fabric.js (lokal) print ''; print ''; print ''; print ''; print ''; print ''; print ''; } llxFooter(); $db->close();