bericht/bericht_batch.php
Eduard Wisch dbddee7791
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
feat: Block C — Editor-Polish, Vorher/Nachher, Versionierung, Batch-Modus
Desktop-Editor Polish:
- Aktives Seiten-Thumbnail deutlich markiert (scale + shadow + blauer
  Label-Hintergrund)
- Titel-Feld pro Seite in der DB (llx_bericht_page.title)

Phase 5.2 Vorher/Nachher-Layout:
- Neuer layout-Typ 'before_after' in bericht_render_page_to_pdf
- Zwei Bilder nebeneinander, Labels 'Vorher' / 'Nachher', Titel oben
- 'VN'-Button in der Anhänge-Grid-Leiste
- create_grid_page akzeptiert before_after
- Layout-Dropdown hat before_after + title_only Option

Phase 5.2b Title-Only Seiten:
- Reine Titel/Zwischentitel-Seiten ohne Bild
- 32pt Titel zentriert, optional Notiz darunter
- bericht_render_page_to_pdf behandelt title_only separat

Phase 5.3 Bericht-Versionierung:
- Neue Spalten version + fk_bericht_parent in llx_bericht
- Bericht::duplicateAsNewVersion() kopiert alles inkl. Seiten
- '🔀 Neue Version'-Button im Editor-Footer
- Versions-Links in der Meta-Zeile (v1, v2, v3 …) mit Sprungmöglichkeit
- Alte Versionen bleiben unverändert erhalten

Phase 5.6 Batch-Modus:
- Neue Seite bericht_batch.php mit Filter (Datum von/bis, Suche)
- Checkbox-Liste aller finalisierten Berichte
- 'Ausgewählte als Sammel-PDF herunterladen' → FPDI merged alle
  final_pdf_path in ein neues PDF mit Inhaltsverzeichnis-Seite
- Link im Admin-Setup

api/pages.php:
- POST-Update akzeptiert jetzt auch title und layout zusätzlich zu
  note und rotation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 09:10:56 +02:00

173 lines
7.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/* Batch-Modus: mehrere Berichte zu einem Sammel-PDF zusammenführen.
* Nur finale Berichte mit existierendem final_pdf_path werden berücksichtigt.
*
* GET: filter per Datum-Von/Bis, Element-Typ, Kundensuche, Status
* POST: action=generate + ids[] → liefert ein zusammengesetztes PDF aus den ausgewählten
*/
$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 __DIR__.'/class/bericht.class.php';
require_once __DIR__.'/lib/bericht.lib.php';
if (!$user->hasRight('bericht', 'read')) accessforbidden();
$langs->loadLangs(array("bericht@bericht", "main"));
$action = GETPOST('action', 'alpha');
$datefrom = GETPOST('datefrom', 'alpha');
$dateto = GETPOST('dateto', 'alpha');
$q = GETPOST('q', 'alpha');
/* ---------- Batch-PDF generieren ---------- */
if ($action === 'generate') {
$ids = GETPOST('ids', 'array');
if (empty($ids)) {
setEventMessages('Keine Berichte ausgewählt', null, 'errors');
header("Location: ".$_SERVER['PHP_SELF']); exit;
}
// TCPDF + FPDI laden
$tcpdf_loaded = false;
foreach (array(
DOL_DOCUMENT_ROOT.'/includes/tecnickcom/tcpdf/tcpdf.php',
DOL_DOCUMENT_ROOT.'/includes/tcpdf/tcpdf.php',
) as $p) { if (file_exists($p)) { require_once $p; $tcpdf_loaded = true; break; } }
if (!$tcpdf_loaded) { http_response_code(500); exit('TCPDF fehlt'); }
$fpdi_loaded = false;
foreach (array(
DOL_DOCUMENT_ROOT.'/includes/setasign/vendor/setasign/fpdi/src/Tcpdf/Fpdi.php',
DOL_DOCUMENT_ROOT.'/includes/fpdi/src/Tcpdf/Fpdi.php',
) as $p) { if (file_exists($p)) { require_once $p; $fpdi_loaded = true; break; } }
if (!$fpdi_loaded) { http_response_code(500); exit('FPDI wird für Batch-Modus benötigt'); }
$pdf = new \setasign\Fpdi\Tcpdf\Fpdi('P', 'mm', 'A4', true, 'UTF-8', false);
$pdf->SetCreator('Dolibarr Bericht-Modul Batch');
$pdf->SetAuthor($user->getFullName($langs));
$pdf->SetTitle('Bericht-Sammlung '.dol_print_date(dol_now(), '%Y-%m-%d'));
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
// Inhaltsverzeichnis-Seite
$pdf->AddPage('P', 'A4');
$pdf->SetFont('helvetica', 'B', 18);
$pdf->Cell(0, 12, 'Bericht-Sammlung', 0, 1, 'C');
$pdf->SetFont('helvetica', '', 11);
$pdf->Cell(0, 8, 'Erstellt: '.dol_print_date(dol_now(), 'dayhour'), 0, 1, 'C');
$pdf->Ln(8);
$pdf->SetFont('helvetica', 'B', 13);
$pdf->Cell(0, 8, 'Enthaltene Berichte ('.count($ids).')', 0, 1, 'L');
$pdf->SetFont('helvetica', '', 10);
$berichte = array();
foreach ($ids as $id) {
$b = new Bericht($db);
if ($b->fetch((int) $id) > 0 && $b->status == Bericht::STATUS_FINAL && !empty($b->final_pdf_path)) {
$pp = bericht_resolve_data_path($b->final_pdf_path);
if ($pp && file_exists($pp)) {
$berichte[] = array('bericht' => $b, 'pdf' => $pp);
$pdf->Cell(0, 6, '• '.$b->ref.' '.($b->titel ?: '').' ('.$b->auftragsnummer.')', 0, 1, 'L');
}
}
}
if (empty($berichte)) {
exit('Keine gültigen (finalisierten) Berichte in der Auswahl');
}
// Jede Bericht-PDF als volle Seiten einbauen
foreach ($berichte as $entry) {
try {
$page_count = $pdf->setSourceFile($entry['pdf']);
for ($n = 1; $n <= $page_count; $n++) {
$tpl = $pdf->importPage($n);
$size = $pdf->getTemplateSize($tpl);
$pdf->AddPage($size['orientation'] ?? 'P', array($size['width'], $size['height']));
$pdf->useTemplate($tpl);
}
} catch (Throwable $e) { continue; }
}
$filename = 'Bericht-Sammlung_'.dol_print_date(dol_now(), '%Y%m%d_%H%M%S').'.pdf';
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="'.$filename.'"');
$pdf->Output($filename, 'D');
exit;
}
/* ---------- Listen-Ansicht ---------- */
llxHeader('', 'Bericht-Batch-Modus');
print load_fiche_titre('📦 Bericht-Batch — Mehrere Berichte zu einem PDF', '', 'bill');
// Filter
print '<form method="get" class="bericht-batch-filter" style="margin-bottom:16px;padding:12px;background:var(--colorbacktitle1,#f5f5f5);border-radius:6px;">';
print '<label>Von: <input type="date" name="datefrom" value="'.dol_escape_htmltag($datefrom).'"></label> &nbsp; ';
print '<label>Bis: <input type="date" name="dateto" value="'.dol_escape_htmltag($dateto).'"></label> &nbsp; ';
print '<label>Suche: <input type="text" name="q" value="'.dol_escape_htmltag($q).'" placeholder="Ref, Titel, Auftragsnr"></label> &nbsp; ';
print '<button type="submit" class="butAction">🔍 Filtern</button>';
print '</form>';
// Query: nur finale Berichte mit PDF, gefiltert
$where = "status = 1 AND final_pdf_path IS NOT NULL AND COALESCE(is_template,0) = 0 AND entity IN (".getEntity('bericht').")";
if ($datefrom) {
$where .= " AND datec >= '".$db->escape($datefrom." 00:00:00")."'";
}
if ($dateto) {
$where .= " AND datec <= '".$db->escape($dateto." 23:59:59")."'";
}
if ($q) {
$qe = $db->escape($q);
$where .= " AND (ref LIKE '%$qe%' OR titel LIKE '%$qe%' OR auftragsnummer LIKE '%$qe%')";
}
$sql = "SELECT rowid, ref, titel, auftragsnummer, element_type, fk_element, datec FROM ".$db->prefix()."bericht WHERE ".$where." ORDER BY datec DESC LIMIT 500";
$resq = $db->query($sql);
print '<form method="post" action="'.$_SERVER['PHP_SELF'].'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="generate">';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th><input type="checkbox" onclick="document.querySelectorAll(\'.bt-check\').forEach(c => c.checked = this.checked)"></th>';
print '<th>Ref</th>';
print '<th>Titel</th>';
print '<th>Auftragsnummer</th>';
print '<th>Datum</th>';
print '</tr>';
$count = 0;
if ($resq) {
while ($o = $db->fetch_object($resq)) {
print '<tr class="oddeven">';
print '<td><input type="checkbox" class="bt-check" name="ids[]" value="'.$o->rowid.'"></td>';
print '<td>'.dol_escape_htmltag($o->ref).'</td>';
print '<td>'.dol_escape_htmltag($o->titel).'</td>';
print '<td>'.dol_escape_htmltag($o->auftragsnummer).'</td>';
print '<td>'.dol_print_date($db->jdate($o->datec), 'dayhour').'</td>';
print '</tr>';
$count++;
}
}
print '</table>';
if ($count === 0) {
print '<p class="opacitymedium">Keine finalisierten Berichte gefunden.</p>';
} else {
print '<div style="margin-top:16px;text-align:right;">';
print '<button type="submit" class="butActionConfirm">📦 Ausgewählte als Sammel-PDF herunterladen</button>';
print '</div>';
}
print '</form>';
llxFooter();
$db->close();