All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Im Bericht-Modul-Admin oben prominent: - Großer 'PWA öffnen'-Button mit Direktlink auf /custom/baustelle/ - 'QR-Code anzeigen'-Button mit qrcodejs (Inline-Render auf Klick) - Code-Block mit der vollen URL zum Kopieren - Sektion 'REST-API Status' listet alle Endpoints für die Doku Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> [deploy]
235 lines
12 KiB
PHP
235 lines
12 KiB
PHP
<?php
|
|
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
|
* GPL v3+
|
|
*
|
|
* Admin-Setup für das Bericht-Modul.
|
|
* Verwaltung von ODT-Templates + globalen Konstanten.
|
|
*/
|
|
|
|
$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/admin.lib.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
|
require_once __DIR__.'/../lib/bericht.lib.php';
|
|
|
|
if (!$user->admin && !$user->hasRight('bericht', 'admin')) accessforbidden();
|
|
|
|
$langs->loadLangs(array("admin", "bericht@bericht"));
|
|
|
|
$action = GETPOST('action', 'alpha');
|
|
|
|
$templates_dir = DOL_DATA_ROOT.'/bericht/templates';
|
|
if (!is_dir($templates_dir)) {
|
|
dol_mkdir($templates_dir);
|
|
}
|
|
|
|
// --- Aktionen ---
|
|
if ($action === 'upload_template' && !empty($_FILES['template_file']['name'])) {
|
|
$name = dol_sanitizeFileName($_FILES['template_file']['name']);
|
|
if (!preg_match('/\.odt$/i', $name)) {
|
|
setEventMessages('Nur .odt-Dateien erlaubt', null, 'errors');
|
|
} else {
|
|
$dest = $templates_dir.'/'.$name;
|
|
if (move_uploaded_file($_FILES['template_file']['tmp_name'], $dest)) {
|
|
setEventMessages($langs->trans("BerichtSetupTemplateUploaded"), null, 'mesgs');
|
|
} else {
|
|
setEventMessages('Upload fehlgeschlagen', null, 'errors');
|
|
}
|
|
}
|
|
header("Location: ".$_SERVER['PHP_SELF']);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'delete_template') {
|
|
$name = dol_sanitizeFileName(GETPOST('name', 'alphanohtml'));
|
|
$path = $templates_dir.'/'.$name;
|
|
if ($name && file_exists($path)) {
|
|
@unlink($path);
|
|
setEventMessages($langs->trans("BerichtSetupTemplateDeleted"), null, 'mesgs');
|
|
}
|
|
header("Location: ".$_SERVER['PHP_SELF']);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'save_const') {
|
|
$consts = array(
|
|
'BERICHT_DEFAULT_TEMPLATE' => GETPOST('default_template', 'alphanohtml'),
|
|
'BERICHT_TAB_ON_INVOICE' => GETPOST('tab_invoice', 'int') ? '1' : '0',
|
|
'BERICHT_TAB_ON_ORDER' => GETPOST('tab_order', 'int') ? '1' : '0',
|
|
'BERICHT_TAB_ON_PROPAL' => GETPOST('tab_propal', 'int') ? '1' : '0',
|
|
'BERICHT_BURN_ANNOTATIONS' => GETPOST('burn', 'int') ? '1' : '0',
|
|
'BERICHT_LIBREOFFICE_BIN' => GETPOST('lobin', 'alphanohtml'),
|
|
);
|
|
foreach ($consts as $k => $v) {
|
|
dolibarr_set_const($db, $k, $v, 'chaine', 0, '', $conf->entity);
|
|
}
|
|
setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
|
|
header("Location: ".$_SERVER['PHP_SELF']);
|
|
exit;
|
|
}
|
|
|
|
// --- Anzeige ---
|
|
llxHeader('', $langs->trans("BerichtSetup"));
|
|
|
|
$linkback = '<a href="'.DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1">'.$langs->trans("BackToModuleList").'</a>';
|
|
print load_fiche_titre($langs->trans("BerichtSetup"), $linkback, 'title_setup');
|
|
|
|
print '<span class="opacitymedium">'.$langs->trans("BerichtSetupDescription").'</span><br><br>';
|
|
|
|
// --- PWA-Link prominent oben ---
|
|
$pwa_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https://' : 'http://').$_SERVER['HTTP_HOST'].'/custom/baustelle/';
|
|
print '<div class="bericht-setup-section" style="background:linear-gradient(135deg,#1e3a5f,#2a5080);border:1px solid #4080c0;">';
|
|
print '<h3 style="color:#fff;">📱 Baustelle PWA</h3>';
|
|
print '<p style="color:#e0e8f0;">Mobile Progressive Web App für die Baustelle. Installierbar auf Handy oder Tablet, funktioniert offline.</p>';
|
|
print '<p style="color:#e0e8f0;font-size:13px;"><strong>Funktionen:</strong> Auftragsliste, Foto-Aufnahme direkt aus der Kamera, automatische Synchronisierung bei Verbindung, Multi-User-Filter pro angemeldetem User</p>';
|
|
print '<div style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:12px;">';
|
|
print ' <a href="'.dol_escape_htmltag($pwa_url).'" target="_blank" class="butAction" style="background:#5cb85c;color:#fff;border:none;padding:10px 20px;border-radius:6px;text-decoration:none;font-weight:600;">🚀 PWA öffnen</a>';
|
|
print ' <button type="button" id="btn-show-pwa-qr" class="butAction" style="background:#fff;color:#2a5080;border:none;padding:10px 20px;border-radius:6px;font-weight:600;cursor:pointer;">📱 QR-Code anzeigen</button>';
|
|
print ' <code style="background:rgba(0,0,0,0.3);padding:8px 12px;border-radius:4px;color:#e0e8f0;font-size:12px;">'.dol_escape_htmltag($pwa_url).'</code>';
|
|
print '</div>';
|
|
print '<div id="pwa-qr-area" style="display:none;text-align:center;margin-top:16px;background:#fff;padding:16px;border-radius:8px;"><div id="pwa-qr"></div><p style="color:#222;font-size:12px;margin:8px 0 0;">Mit dem Handy scannen → "Zum Home-Screen hinzufügen"</p></div>';
|
|
print '</div>';
|
|
|
|
// API-Status
|
|
print '<div class="bericht-setup-section">';
|
|
print '<h3>🔌 REST-API Status</h3>';
|
|
$api_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https://' : 'http://').$_SERVER['HTTP_HOST'].'/custom/bericht/api';
|
|
print '<p>Endpoint: <code>'.dol_escape_htmltag($api_url).'</code></p>';
|
|
print '<table class="noborder">';
|
|
print '<tr><td><code>POST '.dol_escape_htmltag($api_url).'/auth.php</code></td><td>Login → JWT</td></tr>';
|
|
print '<tr><td><code>GET '.dol_escape_htmltag($api_url).'/orders.php</code></td><td>Aufträge des Users</td></tr>';
|
|
print '<tr><td><code>GET '.dol_escape_htmltag($api_url).'/orders.php?id=X</code></td><td>Auftrags-Detail</td></tr>';
|
|
print '<tr><td><code>POST '.dol_escape_htmltag($api_url).'/orders.php?id=X&action=upload_photo</code></td><td>Foto hochladen</td></tr>';
|
|
print '<tr><td><code>GET '.dol_escape_htmltag($api_url).'/reports.php?id=X</code></td><td>Bericht-Detail</td></tr>';
|
|
print '</table>';
|
|
print '<p class="opacitymedium small">JWT-Token sind 7 Tage gültig. Multi-User über fk_user_author/valid/modif gefiltert.</p>';
|
|
print '</div>';
|
|
|
|
// --- Templates ---
|
|
print '<div class="bericht-setup-section">';
|
|
print '<h3>'.$langs->trans("BerichtSetupTemplates").'</h3>';
|
|
print '<p class="opacitymedium">'.$langs->trans("BerichtSetupTemplatesDesc").'</p>';
|
|
|
|
$templates = bericht_list_templates();
|
|
if (empty($templates)) {
|
|
print '<div class="opacitymedium">'.$langs->trans("BerichtSetupNoTemplates").'</div>';
|
|
} else {
|
|
print '<table class="noborder centpercent">';
|
|
print '<tr class="liste_titre"><th>'.$langs->trans("File").'</th><th>'.$langs->trans("Size").'</th><th class="right">'.$langs->trans("Action").'</th></tr>';
|
|
foreach ($templates as $tpl) {
|
|
$path = $templates_dir.'/'.$tpl;
|
|
print '<tr class="oddeven">';
|
|
print '<td>📄 '.dol_escape_htmltag($tpl).'</td>';
|
|
print '<td>'.dol_print_size(filesize($path)).'</td>';
|
|
print '<td class="right">';
|
|
print '<a href="?action=delete_template&name='.urlencode($tpl).'&token='.newToken().'" '
|
|
.'onclick="return confirm(\'Vorlage löschen?\')" class="button-small">'.$langs->trans("Delete").'</a>';
|
|
print '</td>';
|
|
print '</tr>';
|
|
}
|
|
print '</table>';
|
|
}
|
|
|
|
print '<br>';
|
|
print '<form method="post" enctype="multipart/form-data">';
|
|
print '<input type="hidden" name="token" value="'.newToken().'">';
|
|
print '<input type="hidden" name="action" value="upload_template">';
|
|
print '<label class="butAction" for="template_file">📤 '.$langs->trans("BerichtSetupUploadTemplate").'</label>';
|
|
print '<input type="file" id="template_file" name="template_file" accept=".odt" onchange="this.form.submit()" style="display:none">';
|
|
print '</form>';
|
|
|
|
print '</div><br>';
|
|
|
|
// --- Platzhalter-Hinweis ---
|
|
print '<div class="bericht-setup-section">';
|
|
print '<h3>'.$langs->trans("BerichtPlaceholdersTitle").'</h3>';
|
|
print '<table class="noborder centpercent" style="max-width:700px">';
|
|
$placeholders = array(
|
|
'{auftragsnummer}' => 'Aus extrafield options_auftragsnummer der Rechnung',
|
|
'{angebotsnummer}' => 'Aus extrafield options_angebotsnummer',
|
|
'{rechnungsnummer}' => 'Rechnungsnummer (ref)',
|
|
'{kunde_name}' => 'Name des Kunden (Société)',
|
|
'{kunde_adresse}' => 'Adresse des Kunden (mehrzeilig)',
|
|
'{datum}' => 'Heutiges Datum',
|
|
'{beschreibung}' => 'Auftragsbeschreibung (extrafield)',
|
|
'{hinweis}' => 'Hinweis (extrafield)',
|
|
'{bericht_titel}' => 'Titel des Berichts',
|
|
'{ersteller}' => 'Login-Name des Erstellers',
|
|
);
|
|
foreach ($placeholders as $k => $desc) {
|
|
print '<tr class="oddeven"><td><code>'.$k.'</code></td><td>'.$desc.'</td></tr>';
|
|
}
|
|
print '</table>';
|
|
print '</div><br>';
|
|
|
|
// --- Konstanten ---
|
|
print '<div class="bericht-setup-section">';
|
|
print '<h3>'.$langs->trans("BerichtSetupOptions").'</h3>';
|
|
print '<form method="post">';
|
|
print '<input type="hidden" name="token" value="'.newToken().'">';
|
|
print '<input type="hidden" name="action" value="save_const">';
|
|
print '<table class="noborder centpercent">';
|
|
|
|
print '<tr class="oddeven"><td class="titlefield">'.$langs->trans("BerichtSetupDefaultTemplate").'</td><td>';
|
|
print '<select name="default_template">';
|
|
print '<option value="">— '.$langs->trans("BerichtNoTemplate").' —</option>';
|
|
$current_default = getDolGlobalString('BERICHT_DEFAULT_TEMPLATE', '');
|
|
foreach ($templates as $tpl) {
|
|
$sel = ($tpl === $current_default) ? ' selected' : '';
|
|
print '<option value="'.dol_escape_htmltag($tpl).'"'.$sel.'>'.dol_escape_htmltag($tpl).'</option>';
|
|
}
|
|
print '</select></td></tr>';
|
|
|
|
$cb = function ($name, $key, $default) {
|
|
$v = getDolGlobalString($key, $default) ? 'checked' : '';
|
|
return '<input type="checkbox" name="'.$name.'" value="1" '.$v.'>';
|
|
};
|
|
|
|
print '<tr class="oddeven"><td>'.$langs->trans("BerichtSetupTabInvoice").'</td><td>'.$cb('tab_invoice', 'BERICHT_TAB_ON_INVOICE', '1').'</td></tr>';
|
|
print '<tr class="oddeven"><td>'.$langs->trans("BerichtSetupTabOrder").'</td><td>'.$cb('tab_order', 'BERICHT_TAB_ON_ORDER', '1').'</td></tr>';
|
|
print '<tr class="oddeven"><td>'.$langs->trans("BerichtSetupTabPropal").'</td><td>'.$cb('tab_propal', 'BERICHT_TAB_ON_PROPAL', '1').'</td></tr>';
|
|
print '<tr class="oddeven"><td>'.$langs->trans("BerichtSetupBurnAnnotations").'</td><td>'.$cb('burn', 'BERICHT_BURN_ANNOTATIONS', '1').'</td></tr>';
|
|
|
|
print '<tr class="oddeven"><td>'.$langs->trans("BerichtSetupLibreOfficeBin").'</td><td>';
|
|
print '<input type="text" name="lobin" value="'.dol_escape_htmltag(getDolGlobalString('BERICHT_LIBREOFFICE_BIN', '/usr/bin/libreoffice')).'" size="40">';
|
|
print '</td></tr>';
|
|
|
|
print '</table>';
|
|
print '<br><button type="submit" class="butAction">'.$langs->trans("Save").'</button>';
|
|
print '</form>';
|
|
print '</div>';
|
|
|
|
// QR-Code-Lib + kleines Init-Script für PWA-QR
|
|
print '<script src="'.dol_buildpath('/bericht/js/lib/qrcode.min.js', 1).'"></script>';
|
|
print '<script>
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
var btn = document.getElementById("btn-show-pwa-qr");
|
|
if (!btn) return;
|
|
var area = document.getElementById("pwa-qr-area");
|
|
var container = document.getElementById("pwa-qr");
|
|
var url = '.json_encode($pwa_url).';
|
|
var rendered = false;
|
|
btn.addEventListener("click", function () {
|
|
if (area.style.display === "none") {
|
|
area.style.display = "";
|
|
if (!rendered && typeof QRCode !== "undefined") {
|
|
new QRCode(container, { text: url, width: 220, height: 220, correctLevel: QRCode.CorrectLevel.M });
|
|
rendered = true;
|
|
}
|
|
} else {
|
|
area.style.display = "none";
|
|
}
|
|
});
|
|
});
|
|
</script>';
|
|
|
|
llxFooter();
|
|
$db->close();
|