diff --git a/ajax/createmahnung.php b/ajax/createmahnung.php index 6e312aa..6fbb68c 100644 --- a/ajax/createmahnung.php +++ b/ajax/createmahnung.php @@ -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; } diff --git a/card.php b/card.php index 65b1a0f..9ab46f3 100644 --- a/card.php +++ b/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 '
'; 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 ''; print ''; print ''; @@ -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 ''; - // Dateiname mit Icon + // Dateiname mit Icon + Lupe direkt daneben print ''; // Groesse print ''; // Datum print ''; - // Aktionen: Vorschau + Download + // Aktionen: Download + Loeschen print ''; print ''; } @@ -473,21 +511,11 @@ if ($canWrite) { print ''; } -$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 diff --git a/class/actions_mahnung.class.php b/class/actions_mahnung.class.php index 1422d55..b96fe50 100644 --- a/class/actions_mahnung.class.php +++ b/class/actions_mahnung.class.php @@ -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']; diff --git a/class/mahnungcron.class.php b/class/mahnungcron.class.php index d83b1c5..733ae58 100644 --- a/class/mahnungcron.class.php +++ b/class/mahnungcron.class.php @@ -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; } } diff --git a/core/triggers/interface_99_modMahnung_MahnungTriggers.class.php b/core/triggers/interface_99_modMahnung_MahnungTriggers.class.php index 81cac88..2a0bd00 100644 --- a/core/triggers/interface_99_modMahnung_MahnungTriggers.class.php +++ b/core/triggers/interface_99_modMahnung_MahnungTriggers.class.php @@ -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 diff --git a/langs/de_DE/mahnung.lang b/langs/de_DE/mahnung.lang index 3cc95dc..007701a 100644 --- a/langs/de_DE/mahnung.lang +++ b/langs/de_DE/mahnung.lang @@ -211,3 +211,4 @@ MahnungDokumentModelle = Dokumentenmodelle MahnungPdfStandard = Standard-PDF (DIN 5008) MahnungGenerate = Dokument generieren NoDocuments = Keine Dokumente vorhanden. +MahnungDokumentLoeschenConfirm = Dokument '%s' wirklich loeschen? diff --git a/langs/en_US/mahnung.lang b/langs/en_US/mahnung.lang index 29c7f6c..0bc2550 100644 --- a/langs/en_US/mahnung.lang +++ b/langs/en_US/mahnung.lang @@ -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'? diff --git a/list.php b/list.php index e4d96ea..208b272 100644 --- a/list.php +++ b/list.php @@ -93,6 +93,8 @@ print load_fiche_titre( // --- Filter-Form --- print ''; +print ''; +print ''; print ''; print '
'.$langs->trans('Document').'
'; print ''; print img_picto('', $icon, 'class="pictofixedwidth"'); print dol_escape_htmltag($fname); print ''; + if ($ext === 'pdf') { + print ' '.img_picto($langs->trans('Preview'), 'search').''; + } print ''.dol_print_size($filesize, 0, 0).''.dol_print_date($filedate, 'dayhour').''; - if ($ext === 'pdf') { - print ''.img_picto($langs->trans('Preview'), 'search', 'class="pictofixedwidth"').' '; + print ''.img_picto($langs->trans('Download'), 'download').''; + if ($canDeleteDoc) { + $delUrl = $_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id).'&action=delete_doc&file='.urlencode($fname).'&token='.newToken(); + print ' '; + print img_picto($langs->trans('Delete'), 'delete'); + print ''; } - print ''.img_picto($langs->trans('Download'), 'download', 'class="pictofixedwidth"').''; print '
'; print '';
'.$langs->trans('MahnungStufe').'