Fix: Menü-Navigation, GlobalNotify-URL, Dokument-Upload + -Löschen [deploy]
All checks were successful
Deploy mahnung / deploy (push) Successful in 13s
All checks were successful
Deploy mahnung / deploy (push) Successful in 13s
- GlobalNotify-URL: relativer Pfad statt absoluter (DOL_MAIN_URL_ROOT ohne Protokoll führte zu kaputten Seiten-URLs im Browser); buildAbsoluteUrl() prüft jetzt auf fehlendes http(s):// - Menü-Navigation: mainmenu=billing&leftmenu=mahnung in allen list.php-Links ergänzt (createmahnung.php Redirect, Filter-Form, Hook-Links, Cron-Pfade) - card.php Dokumente: Lupe direkt neben Dateinamen, Löschen-Button pro Datei - card.php Upload: actions_linkedfiles.inc.php eingebunden (vor llxHeader), upload_dir korrekt gesetzt — showdocuments() zeigt jetzt Upload-Formular - Redundante Variablen-Definitionen (mahnungSafeRef, files.lib require) entfernt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5e91c87c99
commit
80d92042bc
8 changed files with 103 additions and 37 deletions
|
|
@ -64,7 +64,7 @@ function respond($success, $message, $extra = array())
|
|||
if (function_exists('setEventMessages')) {
|
||||
setEventMessages($message, null, $success ? 'mesgs' : 'errors');
|
||||
}
|
||||
header('Location: '.DOL_URL_ROOT.'/custom/mahnung/list.php?mode=vorschlag');
|
||||
header('Location: '.DOL_URL_ROOT.'/custom/mahnung/list.php?mainmenu=billing&leftmenu=mahnung&mode=vorschlag');
|
||||
exit;
|
||||
}
|
||||
|
||||
|
|
|
|||
80
card.php
80
card.php
|
|
@ -79,6 +79,30 @@ if ($action === 'regenerate_pdf' && $user->hasRight('mahnung', 'write')) {
|
|||
exit;
|
||||
}
|
||||
|
||||
// Generiertes Dokument loeschen
|
||||
if ($action === 'delete_doc' && $user->hasRight('mahnung', 'write')) {
|
||||
$docfile = GETPOST('file', 'alphanohtml');
|
||||
if (!empty($docfile) && !empty($mahnung->fk_facture)) {
|
||||
$facTmp = new Facture($db);
|
||||
if ($facTmp->fetch((int) $mahnung->fk_facture) > 0) {
|
||||
$baseDir = !empty($conf->facture->multidir_output[$facTmp->entity])
|
||||
? $conf->facture->multidir_output[$facTmp->entity]
|
||||
: $conf->facture->dir_output;
|
||||
$fullpath = $baseDir.'/'.dol_sanitizeFileName($facTmp->ref).'/'.dol_sanitizeFileName($docfile);
|
||||
// Sicherstellen dass die Datei zur Mahnung gehoert (Ref im Dateinamen)
|
||||
if (file_exists($fullpath) && strpos($docfile, dol_sanitizeFileName($mahnung->ref)) !== false) {
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
||||
dol_delete_file($fullpath);
|
||||
setEventMessages($langs->trans('FileWasRemoved'), null, 'mesgs');
|
||||
} else {
|
||||
setEventMessages('Datei nicht gefunden oder nicht zugehoerig', null, 'errors');
|
||||
}
|
||||
}
|
||||
}
|
||||
header('Location: '.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Versand-Daten speichern (Datum, Weg, optional Tracking)
|
||||
if ($action === 'set_versand' && $user->hasRight('mahnung', 'write')) {
|
||||
$y = GETPOSTINT('versand_year');
|
||||
|
|
@ -156,13 +180,8 @@ if ($action === 'dismiss_tracking' && $user->hasRight('mahnung', 'write')) {
|
|||
// Belege scannen: pdftotext + Pattern-Matching
|
||||
if ($action === 'scan_belege' && $user->hasRight('mahnung', 'write')) {
|
||||
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnungtrackingpattern.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
||||
|
||||
$mahnungSafeRef = dol_sanitizeFileName($mahnung->ref);
|
||||
$scanDir = (!empty($conf->mahnung->multidir_output[$mahnung->entity])
|
||||
? $conf->mahnung->multidir_output[$mahnung->entity]
|
||||
: ($conf->mahnung->dir_output ?? (DOL_DATA_ROOT.'/mahnung')))
|
||||
.'/'.$mahnungSafeRef;
|
||||
$scanDir = $upload_dir;
|
||||
|
||||
$patternService = new MahnungTrackingPattern($db);
|
||||
$suggestions = array();
|
||||
|
|
@ -225,6 +244,20 @@ if ($action === 'clear_versand' && $user->hasRight('mahnung', 'write')) {
|
|||
exit;
|
||||
}
|
||||
|
||||
// Upload-Verzeichnis fuer Sendebelege (muss VOR llxHeader stehen fuer actions_linkedfiles)
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
||||
$mahnungSafeRef = dol_sanitizeFileName($mahnung->ref);
|
||||
$upload_dir = (!empty($conf->mahnung->multidir_output[$mahnung->entity])
|
||||
? $conf->mahnung->multidir_output[$mahnung->entity]
|
||||
: $conf->mahnung->dir_output ?? (DOL_DATA_ROOT.'/mahnung'))
|
||||
.'/'.$mahnungSafeRef;
|
||||
if (!is_dir($upload_dir)) {
|
||||
dol_mkdir($upload_dir);
|
||||
}
|
||||
$permissiontoadd = $user->hasRight('mahnung', 'write');
|
||||
$permissiontodelete = $user->hasRight('mahnung', 'write');
|
||||
include DOL_DOCUMENT_ROOT.'/core/actions_linkedfiles.inc.php';
|
||||
|
||||
llxHeader('', $langs->trans('MahnungRef').' '.$mahnung->ref);
|
||||
|
||||
print load_fiche_titre($langs->trans('MahnungRef').' '.$mahnung->ref, '', 'fa-envelope-open-text');
|
||||
|
|
@ -265,7 +298,6 @@ print '<br>';
|
|||
print load_fiche_titre($langs->trans('Documents'), '', 'fa-file');
|
||||
|
||||
// Dokumente im Rechnungsordner suchen die zur Mahnung gehoeren
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
||||
$docDir = '';
|
||||
if ($facture->id > 0) {
|
||||
$docDir = !empty($conf->facture->multidir_output[$facture->entity])
|
||||
|
|
@ -274,16 +306,16 @@ if ($facture->id > 0) {
|
|||
$docDir .= '/'.dol_sanitizeFileName($facture->ref);
|
||||
}
|
||||
|
||||
$mahnungRef = dol_sanitizeFileName($mahnung->ref);
|
||||
$fileList = array();
|
||||
if (!empty($docDir) && is_dir($docDir)) {
|
||||
$allFiles = dol_dir_list($docDir, 'files', 0, preg_quote($mahnungRef, '/'));
|
||||
$allFiles = dol_dir_list($docDir, 'files', 0, preg_quote($mahnungSafeRef, '/'));
|
||||
foreach ($allFiles as $f) {
|
||||
$fileList[] = $f;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($fileList)) {
|
||||
$canDeleteDoc = $user->hasRight('mahnung', 'write');
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th>'.$langs->trans('Document').'</th>';
|
||||
|
|
@ -295,30 +327,36 @@ if (!empty($fileList)) {
|
|||
$fname = $f['name'];
|
||||
$relativePath = dol_sanitizeFileName($facture->ref).'/'.$fname;
|
||||
$dlUrl = DOL_URL_ROOT.'/document.php?modulepart=facture&file='.urlencode($relativePath);
|
||||
$viewUrl = $dlUrl.'&attachment=0'; // Inline-Ansicht (keine Download-Erzwingung)
|
||||
$viewUrl = $dlUrl.'&attachment=0';
|
||||
$ext = strtolower(pathinfo($fname, PATHINFO_EXTENSION));
|
||||
$icon = ($ext === 'pdf') ? 'pdf' : (($ext === 'odt') ? 'ooffice' : 'generic');
|
||||
$filesize = !empty($f['size']) ? $f['size'] : filesize($f['fullname']);
|
||||
$filedate = !empty($f['date']) ? $f['date'] : filemtime($f['fullname']);
|
||||
|
||||
print '<tr class="oddeven">';
|
||||
// Dateiname mit Icon
|
||||
// Dateiname mit Icon + Lupe direkt daneben
|
||||
print '<td class="nowraponall">';
|
||||
print '<a href="'.$dlUrl.'">';
|
||||
print img_picto('', $icon, 'class="pictofixedwidth"');
|
||||
print dol_escape_htmltag($fname);
|
||||
print '</a>';
|
||||
if ($ext === 'pdf') {
|
||||
print ' <a href="'.$viewUrl.'" target="_blank" title="'.dol_escape_htmltag($langs->trans('Preview')).'">'.img_picto($langs->trans('Preview'), 'search').'</a>';
|
||||
}
|
||||
print '</td>';
|
||||
// Groesse
|
||||
print '<td class="right nowraponall">'.dol_print_size($filesize, 0, 0).'</td>';
|
||||
// Datum
|
||||
print '<td class="center nowraponall">'.dol_print_date($filedate, 'dayhour').'</td>';
|
||||
// Aktionen: Vorschau + Download
|
||||
// Aktionen: Download + Loeschen
|
||||
print '<td class="right nowraponall">';
|
||||
if ($ext === 'pdf') {
|
||||
print '<a href="'.$viewUrl.'" target="_blank" title="'.dol_escape_htmltag($langs->trans('Preview')).'">'.img_picto($langs->trans('Preview'), 'search', 'class="pictofixedwidth"').'</a> ';
|
||||
print '<a href="'.$dlUrl.'">'.img_picto($langs->trans('Download'), 'download').'</a>';
|
||||
if ($canDeleteDoc) {
|
||||
$delUrl = $_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id).'&action=delete_doc&file='.urlencode($fname).'&token='.newToken();
|
||||
print ' <a href="'.$delUrl.'" onclick="return confirm(\''.dol_escape_js($langs->trans('MahnungDokumentLoeschenConfirm', $fname)).'\');">';
|
||||
print img_picto($langs->trans('Delete'), 'delete');
|
||||
print '</a>';
|
||||
}
|
||||
print '<a href="'.$dlUrl.'">'.img_picto($langs->trans('Download'), 'download', 'class="pictofixedwidth"').'</a>';
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
}
|
||||
|
|
@ -473,21 +511,11 @@ if ($canWrite) {
|
|||
print '</div>';
|
||||
}
|
||||
|
||||
$mahnungSafeRef = dol_sanitizeFileName($mahnung->ref);
|
||||
$mahnungFileDir = (!empty($conf->mahnung->multidir_output[$mahnung->entity])
|
||||
? $conf->mahnung->multidir_output[$mahnung->entity]
|
||||
: $conf->mahnung->dir_output ?? (DOL_DATA_ROOT.'/mahnung'))
|
||||
.'/'.$mahnungSafeRef;
|
||||
// Verzeichnis bei Bedarf anlegen, damit FormFile->showdocuments() das Upload-Formular zeigt
|
||||
if (!is_dir($mahnungFileDir)) {
|
||||
dol_mkdir($mahnungFileDir);
|
||||
}
|
||||
|
||||
$urlSelf = $_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id);
|
||||
$formfile->showdocuments(
|
||||
'mahnung', // $modulepart
|
||||
$mahnungSafeRef, // $modulesubdir
|
||||
$mahnungFileDir, // $filedir
|
||||
$upload_dir, // $filedir
|
||||
$urlSelf, // $urlsource
|
||||
0, // $genallowed (kein PDF-Gen-Button hier)
|
||||
(int) $canWrite, // $delallowed
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class ActionsMahnung
|
|||
$label = $langs->trans('MahnungErstellen');
|
||||
|
||||
if ($ueberfaellig) {
|
||||
$url = DOL_URL_ROOT.'/custom/mahnung/list.php?mode=vorschlag&search_socid='.((int) $object->socid);
|
||||
$url = DOL_URL_ROOT.'/custom/mahnung/list.php?mainmenu=billing&leftmenu=mahnung&mode=vorschlag&search_socid='.((int) $object->socid);
|
||||
print dolGetButtonAction($label, '', 'default', $url, 'btn-mahnung-create', 1);
|
||||
} else {
|
||||
$attr = array('title' => $langs->trans('MahnungKeineUeberfaelligen'));
|
||||
|
|
@ -114,10 +114,10 @@ class ActionsMahnung
|
|||
|
||||
if ($onInvoice) {
|
||||
$count = $this->countMahnungen($db, 'fk_facture', (int) $object->id);
|
||||
$tabUrl = DOL_URL_ROOT.'/custom/mahnung/list.php?mode=archiv&fk_facture='.((int) $object->id);
|
||||
$tabUrl = DOL_URL_ROOT.'/custom/mahnung/list.php?mainmenu=billing&leftmenu=mahnung&mode=archiv&fk_facture='.((int) $object->id);
|
||||
} else {
|
||||
$count = $this->countMahnungen($db, 'fk_soc', (int) $object->id);
|
||||
$tabUrl = DOL_URL_ROOT.'/custom/mahnung/list.php?mode=archiv&search_socid='.((int) $object->id);
|
||||
$tabUrl = DOL_URL_ROOT.'/custom/mahnung/list.php?mainmenu=billing&leftmenu=mahnung&mode=archiv&search_socid='.((int) $object->id);
|
||||
}
|
||||
|
||||
$head = &$parameters['head'];
|
||||
|
|
|
|||
|
|
@ -74,7 +74,8 @@ class MahnungCron
|
|||
}
|
||||
|
||||
$dolUrl = trim((string) getDolGlobalString('MAIN_INFO_SOCIETE_NOM', ''));
|
||||
$listUrl = self::buildAbsoluteUrl('/custom/mahnung/list.php?mode=vorschlag');
|
||||
$relPath = '/custom/mahnung/list.php?mainmenu=billing&leftmenu=mahnung&mode=vorschlag';
|
||||
$absUrl = self::buildAbsoluteUrl($relPath);
|
||||
|
||||
$title = 'Mahnwesen: '.$count.' offene Vorschlaege';
|
||||
$message = "Stufe 1 (Erinnerung): {$counts[1]}\n";
|
||||
|
|
@ -82,9 +83,10 @@ class MahnungCron
|
|||
$message .= "Stufe 3 (Letzte Mahnung): {$counts[3]}\n";
|
||||
$message .= 'Offener Betrag: '.number_format($summe, 2, ',', '.').' EUR';
|
||||
|
||||
MahnungNtfy::send($title, $message, $listUrl, array('envelope_with_arrow', 'warning'));
|
||||
MahnungNtfy::send($title, $message, $absUrl, array('envelope_with_arrow', 'warning'));
|
||||
|
||||
// Optional: GlobalNotify-Badge ins Dolibarr-UI (wenn Modul aktiv)
|
||||
// Relativer Pfad — wird im Browser-Kontext korrekt aufgeloest
|
||||
if (isModEnabled('globalnotify') && class_exists('GlobalNotify') === false) {
|
||||
$gnPath = DOL_DOCUMENT_ROOT.'/custom/globalnotify/class/globalnotify.class.php';
|
||||
if (file_exists($gnPath)) {
|
||||
|
|
@ -96,7 +98,7 @@ class MahnungCron
|
|||
'mahnung',
|
||||
'Mahnwesen: '.$count.' Vorschlaege',
|
||||
$message,
|
||||
$listUrl,
|
||||
$relPath,
|
||||
'Vorschlagsliste oeffnen'
|
||||
);
|
||||
}
|
||||
|
|
@ -176,7 +178,8 @@ class MahnungCron
|
|||
return 0;
|
||||
}
|
||||
|
||||
$listUrl = self::buildAbsoluteUrl('/custom/mahnung/list.php?mode=archiv');
|
||||
$relPath = '/custom/mahnung/list.php?mainmenu=billing&leftmenu=mahnung&mode=archiv';
|
||||
$absUrl = self::buildAbsoluteUrl($relPath);
|
||||
$title = 'Mahnwesen: '.count($pending).' Mahnung(en) unversendet';
|
||||
$lines = array();
|
||||
foreach ($pending as $p) {
|
||||
|
|
@ -191,9 +194,9 @@ class MahnungCron
|
|||
}
|
||||
$message = implode("\n", $lines);
|
||||
|
||||
MahnungNtfy::send($title, $message, $listUrl, array('envelope_with_arrow', 'warning'));
|
||||
MahnungNtfy::send($title, $message, $absUrl, array('envelope_with_arrow', 'warning'));
|
||||
|
||||
// Optional: GlobalNotify-Badge
|
||||
// Optional: GlobalNotify-Badge — relativer Pfad fuer Browser-Kontext
|
||||
if (isModEnabled('globalnotify') && !class_exists('GlobalNotify')) {
|
||||
$gnPath = DOL_DOCUMENT_ROOT.'/custom/globalnotify/class/globalnotify.class.php';
|
||||
if (file_exists($gnPath)) {
|
||||
|
|
@ -205,7 +208,7 @@ class MahnungCron
|
|||
'mahnung_versand',
|
||||
$title,
|
||||
$message,
|
||||
$listUrl,
|
||||
$relPath,
|
||||
'Archiv oeffnen'
|
||||
);
|
||||
}
|
||||
|
|
@ -230,6 +233,10 @@ class MahnungCron
|
|||
if (empty($base)) {
|
||||
return $relPath;
|
||||
}
|
||||
// Protokoll sicherstellen — ohne Protokoll wird die URL im Browser als relativ interpretiert
|
||||
if (!preg_match('/^https?:\/\//', $base)) {
|
||||
$base = 'http://'.$base;
|
||||
}
|
||||
return rtrim($base, '/').$relPath;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,14 @@ class InterfaceMahnungTriggers extends DolibarrTriggers
|
|||
}
|
||||
}
|
||||
$this->db->free($resql);
|
||||
|
||||
// Wenn Mahnungen erledigt wurden: pruefen ob noch offene uebrig sind.
|
||||
// Falls nicht, GlobalNotify-Badge raeumen.
|
||||
if ($count > 0 && !$this->hatOffeneMahnungen()) {
|
||||
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnungcron.class.php';
|
||||
MahnungCron::clearGlobalNotify();
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
|
|
@ -128,6 +136,25 @@ class InterfaceMahnungTriggers extends DolibarrTriggers
|
|||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prueft ob noch offene Mahnvorgaenge existieren (Status weder erledigt noch storniert).
|
||||
*
|
||||
* @return bool true wenn mindestens eine offene Mahnung existiert
|
||||
*/
|
||||
private function hatOffeneMahnungen()
|
||||
{
|
||||
$sql = "SELECT COUNT(*) AS nb FROM ".MAIN_DB_PREFIX."mahnung_mahnung";
|
||||
$sql .= " WHERE status NOT IN (".Mahnung::STATUS_ERLEDIGT.", ".Mahnung::STATUS_STORNIERT.")";
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if (!$resql) {
|
||||
return true; // Im Zweifel Badge stehen lassen
|
||||
}
|
||||
$obj = $this->db->fetch_object($resql);
|
||||
$this->db->free($resql);
|
||||
return (int) $obj->nb > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $factureId
|
||||
* @return bool
|
||||
|
|
|
|||
|
|
@ -211,3 +211,4 @@ MahnungDokumentModelle = Dokumentenmodelle
|
|||
MahnungPdfStandard = Standard-PDF (DIN 5008)
|
||||
MahnungGenerate = Dokument generieren
|
||||
NoDocuments = Keine Dokumente vorhanden.
|
||||
MahnungDokumentLoeschenConfirm = Dokument '%s' wirklich loeschen?
|
||||
|
|
|
|||
|
|
@ -199,3 +199,4 @@ MahnungCronBuildVorschlag = Dunning — build proposal list
|
|||
MahnungCronBuildVorschlagDesc = Daily scan for overdue invoices, sends a Ntfy push with the count of new proposals.
|
||||
MahnungCronVersandReminder = Dunning — shipment reminder (unsent dunnings)
|
||||
MahnungCronVersandReminderDesc = Daily check for dunnings in status ERSTELLT that have not been sent for more than N days (MAHNUNG_VERSAND_REMINDER_DAYS, default 2).
|
||||
MahnungDokumentLoeschenConfirm = Really delete document '%s'?
|
||||
|
|
|
|||
2
list.php
2
list.php
|
|
@ -93,6 +93,8 @@ print load_fiche_titre(
|
|||
|
||||
// --- Filter-Form ---
|
||||
print '<form method="GET" action="'.$_SERVER['PHP_SELF'].'">';
|
||||
print '<input type="hidden" name="mainmenu" value="billing">';
|
||||
print '<input type="hidden" name="leftmenu" value="mahnung">';
|
||||
print '<input type="hidden" name="mode" value="'.dol_escape_htmltag($mode).'">';
|
||||
print '<table class="noborder centpercent"><tr class="liste_titre">';
|
||||
print '<th>'.$langs->trans('MahnungStufe').'</th>';
|
||||
|
|
|
|||
Loading…
Reference in a new issue