All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Dolibarr-Modul für Arbeitsberichte aus Rechnungs-Anhängen mit Browser-PDF-Editor. - Reiter "Bericht" auf Rechnungen, Aufträgen und Angeboten - Anhänge-Browser inkl. verknüpfter Objekte (Auftrag → Rechnung) - PDF.js + Fabric.js Browser-Editor: Pfeile, Kreise, Rechtecke, Freihand, Text - SortableJS Seiten-Verwaltung mit Drag&Drop - ODT-Deckblatt mit Platzhaltern, Templates im Admin verwaltbar - TCPDF + FPDI Finalisierung mit eingebrannten Annotationen - ECM-Verknüpfung: PDF erscheint unter Verknüpfte Dokumente - Auftragsnummer aus existierendem Extrafield options_auftragsnummer - Mehrere Berichte pro Dokument - Beim Aktivieren werden vorhandene Extrafields nicht überschrieben Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
178 lines
5.8 KiB
PHP
178 lines
5.8 KiB
PHP
<?php
|
|
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
|
* GPL v3+
|
|
*/
|
|
|
|
/**
|
|
* Hilfs-Funktionen für das Bericht-Modul.
|
|
*/
|
|
|
|
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
|
|
|
/**
|
|
* Erzeugt die Tab-Header-Leiste für die Editor-Seite (Übersicht / Editor).
|
|
*
|
|
* @param Bericht $object
|
|
* @return array
|
|
*/
|
|
function bericht_prepare_head($object)
|
|
{
|
|
global $langs, $conf;
|
|
$langs->load("bericht@bericht");
|
|
|
|
$h = 0;
|
|
$head = array();
|
|
|
|
$head[$h][0] = dol_buildpath('/bericht/bericht_card.php', 1).'?id='.$object->id;
|
|
$head[$h][1] = $langs->trans("BerichtEditor");
|
|
$head[$h][2] = 'editor';
|
|
$h++;
|
|
|
|
return $head;
|
|
}
|
|
|
|
/**
|
|
* Lädt das Parent-Objekt anhand des element_type.
|
|
*
|
|
* @return CommonObject|null
|
|
*/
|
|
function bericht_fetch_parent(DoliDB $db, $element_type, $id)
|
|
{
|
|
if ($element_type === 'invoice') {
|
|
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
|
|
$o = new Facture($db);
|
|
} elseif ($element_type === 'order') {
|
|
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
|
|
$o = new Commande($db);
|
|
} elseif ($element_type === 'propal') {
|
|
require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
|
|
$o = new Propal($db);
|
|
} else {
|
|
return null;
|
|
}
|
|
if ($o->fetch($id) <= 0) return null;
|
|
$o->fetch_thirdparty();
|
|
if (method_exists($o, 'fetch_optionals')) {
|
|
$o->fetch_optionals();
|
|
}
|
|
return $o;
|
|
}
|
|
|
|
/**
|
|
* Mappt einen element_type-Code auf den Dolibarr-internen Element-Namen
|
|
* für das Verzeichnis der Anhänge (multidir_output).
|
|
*/
|
|
function bericht_element_to_dir_key($element_type)
|
|
{
|
|
return array(
|
|
'invoice' => 'facture',
|
|
'order' => 'commande',
|
|
'propal' => 'propal',
|
|
)[$element_type] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Sammelt alle Anhänge eines Parent-Objekts UND der direkt verknüpften Objekte
|
|
* (z. B. der Auftrag/Angebot, die mit dieser Rechnung verknüpft sind).
|
|
*
|
|
* @return array Liste mit ['source','source_id','source_ref','filename','fullpath','relpath','size','mime','date']
|
|
*/
|
|
function bericht_collect_attachments(DoliDB $db, CommonObject $parent, $element_type)
|
|
{
|
|
global $conf;
|
|
|
|
$result = array();
|
|
|
|
// 1) Eigene Anhänge
|
|
$dir_key = bericht_element_to_dir_key($element_type);
|
|
if ($dir_key && !empty($conf->{$dir_key}->multidir_output[$parent->entity])) {
|
|
$upload_dir = $conf->{$dir_key}->multidir_output[$parent->entity].'/'.dol_sanitizeFileName($parent->ref);
|
|
if (is_dir($upload_dir)) {
|
|
$files = dol_dir_list($upload_dir, 'files', 1, '', '(\.meta|_preview.*\.png|thumbs)$');
|
|
foreach ($files as $f) {
|
|
$result[] = array(
|
|
'source' => $element_type,
|
|
'source_id' => $parent->id,
|
|
'source_ref' => $parent->ref,
|
|
'filename' => $f['name'],
|
|
'fullpath' => $f['fullname'],
|
|
'relpath' => str_replace(DOL_DATA_ROOT.'/', '', $f['fullname']),
|
|
'size' => $f['size'],
|
|
'mime' => dol_mimetype($f['name']),
|
|
'date' => $f['date'],
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2) Verknüpfte Objekte: order, propal — auch deren Anhänge anbieten
|
|
$parent->fetchObjectLinked();
|
|
$linked_types = array('commande' => 'order', 'propal' => 'propal', 'facture' => 'invoice');
|
|
foreach ($linked_types as $linked_dolibarr_type => $linked_module_type) {
|
|
if (empty($parent->linkedObjects[$linked_dolibarr_type])) continue;
|
|
foreach ($parent->linkedObjects[$linked_dolibarr_type] as $lobj) {
|
|
if (empty($conf->{$linked_dolibarr_type}->multidir_output[$lobj->entity])) continue;
|
|
$linked_dir = $conf->{$linked_dolibarr_type}->multidir_output[$lobj->entity].'/'.dol_sanitizeFileName($lobj->ref);
|
|
if (!is_dir($linked_dir)) continue;
|
|
$files = dol_dir_list($linked_dir, 'files', 1, '', '(\.meta|_preview.*\.png|thumbs)$');
|
|
foreach ($files as $f) {
|
|
$result[] = array(
|
|
'source' => $linked_module_type,
|
|
'source_id' => $lobj->id,
|
|
'source_ref' => $lobj->ref,
|
|
'filename' => $f['name'],
|
|
'fullpath' => $f['fullname'],
|
|
'relpath' => str_replace(DOL_DATA_ROOT.'/', '', $f['fullname']),
|
|
'size' => $f['size'],
|
|
'mime' => dol_mimetype($f['name']),
|
|
'date' => $f['date'],
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Liest die Auftragsnummer aus dem Parent-Objekt.
|
|
* Reihenfolge: extrafield 'auftragsnummer' → ref_client → ref
|
|
*/
|
|
function bericht_get_auftragsnummer(CommonObject $parent)
|
|
{
|
|
if (!empty($parent->array_options['options_auftragsnummer'])) {
|
|
return $parent->array_options['options_auftragsnummer'];
|
|
}
|
|
if (!empty($parent->ref_client)) {
|
|
return $parent->ref_client;
|
|
}
|
|
return $parent->ref;
|
|
}
|
|
|
|
/**
|
|
* Listet alle ODT-Templates im Templates-Verzeichnis auf.
|
|
*
|
|
* @return string[] Dateinamen
|
|
*/
|
|
function bericht_list_templates()
|
|
{
|
|
$dir = DOL_DATA_ROOT.'/bericht/templates';
|
|
if (!is_dir($dir)) return array();
|
|
$files = scandir($dir);
|
|
return array_values(array_filter($files, function ($f) {
|
|
return preg_match('/\.odt$/i', $f);
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Sicherer absolute-Pfad-Resolver für Dateien unterhalb DOL_DATA_ROOT.
|
|
* Verhindert Path-Traversal.
|
|
*/
|
|
function bericht_resolve_data_path($relpath)
|
|
{
|
|
$base = realpath(DOL_DATA_ROOT);
|
|
$full = realpath($base.'/'.$relpath);
|
|
if ($full === false) return null;
|
|
if (strpos($full, $base) !== 0) return null;
|
|
return $full;
|
|
}
|