mahnung/card.php
Eduard Wisch 10cf41a687
All checks were successful
Deploy mahnung / deploy (push) Successful in 14s
i18n: Alle Texte über $langs->trans() — ~100 neue Sprachschlüssel de_DE + en_US [deploy]
Umlaute in allen lang-Dateien korrigiert. Alle hardcodierten deutschen Strings
in 22 PHP-Dateien durch $langs->trans('Key') ersetzt. Neue Schlüssel für
Cron-Meldungen, Dokument-Aktionen, Bonität, Vorschlag-Status, Template-Vars u.a.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-13 16:25:50 +02:00

649 lines
28 KiB
PHP

<?php
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
/**
* \file mahnung/card.php
* \ingroup mahnung
* \brief Detailansicht eines einzelnen Mahnvorgangs.
*/
$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 && file_exists("../main.inc.php")) {
$res = @include "../main.inc.php";
}
if (!$res) {
die("Include of main fails");
}
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnung.class.php';
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
global $langs, $user, $db;
$langs->loadLangs(array('mahnung@mahnung', 'companies', 'bills'));
if (!$user->hasRight('mahnung', 'read')) {
accessforbidden();
}
$id = GETPOSTINT('id');
$action = GETPOST('action', 'aZ09');
$mahnung = new Mahnung($db);
if ($mahnung->fetch($id) <= 0) {
dol_print_error($db, $langs->trans('MahnungNichtGefunden', $id));
exit;
}
// Stornieren
if ($action === 'storno' && $user->hasRight('mahnung', 'delete')) {
$mahnung->status = Mahnung::STATUS_STORNIERT;
if ($mahnung->update($user) > 0) {
setEventMessages($langs->trans('MahnungStornieren').' OK', null, 'mesgs');
header('Location: '.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id));
exit;
}
setEventMessages($mahnung->error, null, 'errors');
}
// Dokument neu generieren
if ($action === 'regenerate_pdf' && $user->hasRight('mahnung', 'write')) {
$selectedModel = GETPOST('model', 'alphanohtml');
$result = $mahnung->generateDocument($selectedModel, $langs);
if ($result > 0) {
setEventMessages($langs->trans('MahnungDokumentErstellt'), null, 'mesgs');
} else {
setEventMessages($langs->trans('MahnungDokumentFehler').': '.$mahnung->error, null, 'errors');
}
header('Location: '.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id));
exit;
}
// Generiertes Dokument löschen
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($langs->trans('MahnungDateiNichtGefunden'), 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');
$m = GETPOSTINT('versand_month');
$d = GETPOSTINT('versand_day');
$dateVersand = ($y && $m && $d) ? dol_mktime(12, 0, 0, $m, $d, $y) : dol_now();
$weg = GETPOST('versandweg', 'aZ09');
$trackNr = trim((string) GETPOST('tracking_nr', 'alphanohtml'));
$trackProv = GETPOST('tracking_provider', 'aZ09');
if (empty($trackProv) && $trackNr !== '') {
$trackProv = Mahnung::defaultProviderForWeg($weg);
}
if ($mahnung->setVersand($user, $dateVersand, $weg, $trackNr ?: null, $trackProv ?: null) > 0) {
setEventMessages($langs->trans('MahnungVersandGespeichert'), null, 'mesgs');
} else {
setEventMessages($mahnung->error ?: $langs->trans('MahnungFehlerSpeichern'), null, 'errors');
}
header('Location: '.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id));
exit;
}
// Rechnung als uneinbringlich klassifizieren (close_code='badcustomer')
// — endgültiger Schritt nach erfolglosem Mahnverfahren / Vollstreckung.
if ($action === 'confirm_uneinbringlich' && $user->hasRight('mahnung', 'delete')) {
require_once DOL_DOCUMENT_ROOT.'/core/class/commoninvoice.class.php';
$note = trim((string) GETPOST('uneinbringlich_note', 'nohtml'));
if ($note === '') {
$note = $langs->trans('MahnungVerfahrenErfolglos').' '.dol_print_date(dol_now(), 'day');
}
$facReload = new Facture($db);
if ($facReload->fetch((int) $mahnung->fk_facture) > 0) {
// Rechnung auf STATUS_ABANDONED (3) + close_code='badcustomer' setzen
$ret = $facReload->setCanceled($user, CommonInvoice::CLOSECODE_BADDEBT, $note);
if ($ret > 0) {
// Mahnung mitstornieren — Vorgang ist damit fachlich abgeschlossen
$mahnung->status = Mahnung::STATUS_STORNIERT;
$mahnung->note_private = trim(($mahnung->note_private ? $mahnung->note_private."\n\n" : '').$langs->trans('MahnungUneinbringlichKlassifiziert').' ('.dol_print_date(dol_now(), 'day').'): '.$note);
$mahnung->update($user);
setEventMessages($langs->trans('MahnungUneinbringlichErfolg'), null, 'mesgs');
} else {
setEventMessages($facReload->error ?: $langs->trans('MahnungFehlerKlassifizieren'), null, 'errors');
}
} else {
setEventMessages($langs->trans('MahnungRechnungNichtLadbar'), null, 'errors');
}
header('Location: '.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id));
exit;
}
// Sendungsnummer aus erkanntem Vorschlag übernehmen
if ($action === 'apply_tracking' && $user->hasRight('mahnung', 'write')) {
$nr = trim((string) GETPOST('nr', 'alphanohtml'));
$prov = GETPOST('provider', 'aZ09');
if ($nr !== '' && $prov !== '') {
$mahnung->tracking_nr = $nr;
$mahnung->tracking_provider = $prov;
if ($mahnung->update($user) > 0) {
setEventMessages($langs->trans('MahnungTrackingUebernommen'), null, 'mesgs');
}
}
// Vorschlag aus Session entfernen
unset($_SESSION['mahnung_tracking_suggestions_'.((int) $mahnung->id)]);
header('Location: '.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id));
exit;
}
// Vorschläge verwerfen
if ($action === 'dismiss_tracking' && $user->hasRight('mahnung', 'write')) {
unset($_SESSION['mahnung_tracking_suggestions_'.((int) $mahnung->id)]);
header('Location: '.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id));
exit;
}
// Belege scannen: pdftotext + Pattern-Matching
if ($action === 'scan_belege' && $user->hasRight('mahnung', 'write')) {
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnungtrackingpattern.class.php';
$scanDir = $upload_dir;
$patternService = new MahnungTrackingPattern($db);
$suggestions = array();
$pdftotextAvailable = null; // einmalig prüfen
if (is_dir($scanDir)) {
foreach (dol_dir_list($scanDir, 'files', 0) as $file) {
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$text = '';
if ($ext === 'txt' || $ext === 'html' || $ext === 'htm') {
$text = @file_get_contents($file['fullname']);
if ($ext === 'html' || $ext === 'htm') {
$text = strip_tags((string) $text);
}
} elseif ($ext === 'pdf') {
if ($pdftotextAvailable === null) {
$check = @shell_exec('command -v pdftotext 2>/dev/null');
$pdftotextAvailable = !empty(trim((string) $check));
}
if ($pdftotextAvailable) {
$cmd = 'pdftotext -layout '.escapeshellarg($file['fullname']).' - 2>/dev/null';
$text = (string) @shell_exec($cmd);
}
}
if ($text === '') {
continue;
}
$hit = $patternService->detectFromText($text);
if ($hit !== null) {
$suggestions[] = array(
'file' => $file['name'],
'provider' => $hit['provider'],
'nr' => $hit['nr'],
'url' => $hit['url'],
'label' => $hit['label'],
);
}
}
}
$_SESSION['mahnung_tracking_suggestions_'.((int) $mahnung->id)] = $suggestions;
if ($pdftotextAvailable === false) {
setEventMessages($langs->trans('MahnungPdftotextMissing'), null, 'warnings');
}
header('Location: '.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id));
exit;
}
// Versand-Daten zurücksetzen (z.B. Korrekturmöglichkeit)
if ($action === 'clear_versand' && $user->hasRight('mahnung', 'write')) {
$mahnung->date_versand = null;
$mahnung->versandweg = null;
$mahnung->tracking_nr = null;
$mahnung->tracking_provider = null;
// Status nicht zurückdrehen — nur Daten löschen
if ($mahnung->update($user) > 0) {
setEventMessages($langs->trans('MahnungVersandGeleert'), null, 'mesgs');
}
header('Location: '.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id));
exit;
}
// Upload-Verzeichnis für Sendebelege (muss VOR llxHeader stehen für 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');
print '<table class="border centpercent">';
print '<tr><td class="titlefield">'.$langs->trans('MahnungRef').'</td><td>'.dol_escape_htmltag($mahnung->ref).'</td></tr>';
print '<tr><td>'.$langs->trans('MahnungStufe').'</td><td>'.((int) $mahnung->stufe).'</td></tr>';
print '<tr><td>'.$langs->trans('MahnungDatum').'</td><td>'.dol_print_date($mahnung->date_mahnung, 'day').'</td></tr>';
print '<tr><td>'.$langs->trans('MahnungFaelligkeitAlt').'</td><td>'.dol_print_date($mahnung->date_lim_reglement_alt, 'day').'</td></tr>';
print '<tr><td>'.$langs->trans('MahnungFaelligkeitNeu').'</td><td>'.dol_print_date($mahnung->date_lim_reglement_neu, 'day').'</td></tr>';
// Rechnung
$facture = new Facture($db);
if ($facture->fetch((int) $mahnung->fk_facture) > 0) {
print '<tr><td>'.$langs->trans('MahnungRechnung').'</td>';
print '<td><a href="'.DOL_URL_ROOT.'/compta/facture/card.php?id='.((int) $facture->id).'">'.dol_escape_htmltag($facture->ref).'</a></td></tr>';
}
// Kunde
$societe = new Societe($db);
if ($societe->fetch((int) $mahnung->fk_soc) > 0) {
print '<tr><td>'.$langs->trans('MahnungKunde').'</td>';
print '<td><a href="'.DOL_URL_ROOT.'/societe/card.php?socid='.((int) $societe->id).'">'.dol_escape_htmltag($societe->name).'</a> ('.dol_escape_htmltag((string) $mahnung->customertype).')</td></tr>';
}
print '<tr><td>'.$langs->trans('MahnungBetragOffen').'</td><td>'.price($mahnung->betrag_offen).'</td></tr>';
print '<tr><td>'.$langs->trans('MahnungGebuehr').'</td><td>'.price($mahnung->mahngebuehr).'</td></tr>';
if ((float) $mahnung->pauschale_b2b > 0) {
print '<tr><td>'.$langs->trans('MahnungPauschaleB2B').'</td><td>'.price($mahnung->pauschale_b2b).'</td></tr>';
}
print '<tr><td>'.$langs->trans('MahnungVerzugszinsen').'</td><td>'.price($mahnung->verzugszinsen).' (Basiszins '.number_format((float) $mahnung->basiszins_snapshot, 2, ',', '.').' %)</td></tr>';
print '<tr><td>'.$langs->trans('MahnungSumme').'</td><td><strong>'.price($mahnung->summe_mahnung).'</strong></td></tr>';
print '<tr><td>'.$langs->trans('Status').'</td><td>'.dol_escape_htmltag($mahnung->getStatusLabel()).'</td></tr>';
print '</table>';
// --- Generierte Dokumente (wie bei Rechnungen) ---
print '<br>';
print load_fiche_titre($langs->trans('Documents'), '', 'fa-file');
// Dokumente im Rechnungsordner suchen die zur Mahnung gehoeren
$docDir = '';
if ($facture->id > 0) {
$docDir = !empty($conf->facture->multidir_output[$facture->entity])
? $conf->facture->multidir_output[$facture->entity]
: $conf->facture->dir_output;
$docDir .= '/'.dol_sanitizeFileName($facture->ref);
}
$fileList = array();
if (!empty($docDir) && is_dir($docDir)) {
$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>';
print '<th class="right">'.$langs->trans('Size').'</th>';
print '<th class="center">'.$langs->trans('Date').'</th>';
print '<th class="center"></th>';
print '</tr>';
foreach ($fileList as $f) {
$fname = $f['name'];
$relativePath = dol_sanitizeFileName($facture->ref).'/'.$fname;
$dlUrl = DOL_URL_ROOT.'/document.php?modulepart=facture&file='.urlencode($relativePath);
$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 + 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: Download + Löschen
print '<td class="right nowraponall">';
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 '</td>';
print '</tr>';
}
print '</table>';
} else {
print '<div class="opacitymedium">'.$langs->trans('NoDocuments').'</div>';
}
// Aktionen
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
$form = new Form($db);
$formfile = new FormFile($db);
// --- Versand & Belege ---
print '<br>';
print load_fiche_titre($langs->trans('MahnungVersandBelege'), '', 'fa-truck');
// Versandwege (Dropdown-Optionen, Label kommt aus Lang-File MahnungVersandweg*)
$versandwege = array(
'post' => $langs->trans('MahnungVersandwegPost'),
'einschreiben' => $langs->trans('MahnungVersandwegEinschreiben'),
'dhl' => $langs->trans('MahnungVersandwegDhl'),
'dpd' => $langs->trans('MahnungVersandwegDpd'),
'hermes' => $langs->trans('MahnungVersandwegHermes'),
'ups' => $langs->trans('MahnungVersandwegUps'),
'fax' => $langs->trans('MahnungVersandwegFax'),
'email' => $langs->trans('MahnungVersandwegEmail'),
'persoenlich' => $langs->trans('MahnungVersandwegPersoenlich'),
'eigen' => $langs->trans('MahnungVersandwegEigen'),
);
$editVersand = ($action === 'edit_versand') || empty($mahnung->date_versand);
$canWrite = $user->hasRight('mahnung', 'write');
if (!empty($mahnung->date_versand) && $action !== 'edit_versand') {
// Anzeige der bereits erfassten Versanddaten
print '<table class="border centpercent">';
print '<tr><td class="titlefield">'.$langs->trans('MahnungVersanddatum').'</td><td>'.dol_print_date($mahnung->date_versand, 'day').'</td></tr>';
print '<tr><td>'.$langs->trans('MahnungVersandweg').'</td><td>'
.($mahnung->versandweg && isset($versandwege[$mahnung->versandweg]) ? dol_escape_htmltag($versandwege[$mahnung->versandweg]) : dol_escape_htmltag((string) $mahnung->versandweg))
.'</td></tr>';
if (!empty($mahnung->tracking_nr)) {
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnungtrackingpattern.class.php';
$trackUrl = (new MahnungTrackingPattern($db))->urlFor((string) $mahnung->tracking_provider, (string) $mahnung->tracking_nr);
print '<tr><td>'.$langs->trans('MahnungTrackingNr').'</td><td>';
print '<code>'.dol_escape_htmltag($mahnung->tracking_nr).'</code>';
if (!empty($trackUrl)) {
print ' <a href="'.dol_escape_htmltag($trackUrl).'" target="_blank" rel="noopener" class="butAction" style="margin-left:8px;">';
print img_picto('', 'fa-external-link-alt', 'class="pictofixedwidth"');
print dol_escape_htmltag($langs->trans('MahnungSendungVerfolgen')).'</a>';
}
print '</td></tr>';
}
print '</table>';
if ($canWrite) {
print '<div style="margin-top:8px;">';
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id).'&action=edit_versand">'.img_picto('', 'edit').' '.dol_escape_htmltag($langs->trans('MahnungVersandBearbeiten')).'</a> ';
print '<a class="butActionDelete" href="'.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id).'&action=clear_versand&token='.newToken().'" onclick="return confirm(\''.dol_escape_js($langs->trans('MahnungVersandLeerenConfirm')).'\');">'.dol_escape_htmltag($langs->trans('MahnungVersandLeeren')).'</a>';
print '</div>';
}
} elseif ($canWrite) {
// Versand-Formular (Erfassung oder Bearbeitung)
$dateInit = !empty($mahnung->date_versand) ? $mahnung->date_versand : dol_now();
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id).'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="set_versand">';
print '<table class="border centpercent">';
// Versanddatum
print '<tr><td class="titlefield">'.$langs->trans('MahnungVersanddatum').'</td><td>';
print $form->selectDate($dateInit, 'versand_', 0, 0, 0, '', 1, 0);
print '</td></tr>';
// Versandweg
print '<tr><td>'.$langs->trans('MahnungVersandweg').'</td><td>';
print '<select name="versandweg" id="versandweg">';
foreach ($versandwege as $k => $label) {
print '<option value="'.dol_escape_htmltag($k).'"'.((string) $mahnung->versandweg === $k ? ' selected' : '').'>'.dol_escape_htmltag($label).'</option>';
}
print '</select>';
print '</td></tr>';
// Tracking-Nr
print '<tr><td>'.$langs->trans('MahnungTrackingNr').'</td><td>';
print '<input type="text" name="tracking_nr" value="'.dol_escape_htmltag((string) $mahnung->tracking_nr).'" size="30" placeholder="'.dol_escape_htmltag($langs->trans('MahnungTrackingNrHint')).'">';
print ' <span class="opacitymedium">('.dol_escape_htmltag($langs->trans('MahnungTrackingProviderAuto')).')</span>';
print '</td></tr>';
// Optional: Tracking-Provider override
print '<tr><td>'.$langs->trans('MahnungTrackingProvider').'</td><td>';
print '<select name="tracking_provider">';
print '<option value="">'.dol_escape_htmltag($langs->trans('MahnungTrackingProviderAuto')).'</option>';
foreach (array('dhl' => 'DHL', 'dpag' => 'Deutsche Post', 'dpd' => 'DPD', 'hermes' => 'Hermes', 'ups' => 'UPS') as $k => $label) {
print '<option value="'.dol_escape_htmltag($k).'"'.((string) $mahnung->tracking_provider === $k ? ' selected' : '').'>'.$label.'</option>';
}
print '</select>';
print '</td></tr>';
print '</table>';
print '<div style="margin-top:8px;">';
print '<button type="submit" class="button">'.dol_escape_htmltag($langs->trans('Save')).'</button> ';
if (!empty($mahnung->date_versand)) {
print '<a class="butActionRefused" href="'.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id).'">'.dol_escape_htmltag($langs->trans('Cancel')).'</a>';
}
print '</div>';
print '</form>';
}
// --- Sendebelege (Beleg-Upload via Dolibarr-Standard) ---
print '<br><h3>'.$langs->trans('MahnungSendebelege').'</h3>';
print '<div class="opacitymedium" style="margin-bottom:8px;">'.$langs->trans('MahnungSendebelegeHint').'</div>';
// Tracking-Vorschläge aus Session-Flash (vom Scan) anzeigen
$suggKey = 'mahnung_tracking_suggestions_'.((int) $mahnung->id);
$suggestions = $_SESSION[$suggKey] ?? null;
if (is_array($suggestions) && !empty($suggestions)) {
print '<div class="info" style="padding:8px; margin-bottom:8px; border-left:3px solid #2a8;">';
print '<strong>'.$langs->trans('MahnungTrackingErkannt').'</strong> <span class="opacitymedium">('.count($suggestions).')</span>';
print '<table class="noborder" style="margin-top:6px;">';
foreach ($suggestions as $sg) {
print '<tr>';
print '<td class="nowrap">'.img_picto('', 'pdf', 'class="pictofixedwidth"').dol_escape_htmltag($sg['file']).'</td>';
print '<td>'.dol_escape_htmltag($sg['label']).'</td>';
print '<td><code><strong>'.dol_escape_htmltag($sg['nr']).'</strong></code></td>';
print '<td><a href="'.dol_escape_htmltag($sg['url']).'" target="_blank" rel="noopener" class="opacitymedium">'.img_picto('', 'fa-external-link-alt').'</a></td>';
print '<td>';
if ($canWrite) {
$applyUrl = $_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id).'&action=apply_tracking'
.'&nr='.urlencode((string) $sg['nr'])
.'&provider='.urlencode((string) $sg['provider'])
.'&token='.newToken();
print '<a class="button smallpaddingimp" href="'.$applyUrl.'">'.dol_escape_htmltag($langs->trans('MahnungTrackingUebernehmen')).'</a>';
}
print '</td>';
print '</tr>';
}
print '</table>';
if ($canWrite) {
print '<div style="margin-top:6px;"><a class="opacitymedium" href="'.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id).'&action=dismiss_tracking&token='.newToken().'">'.dol_escape_htmltag($langs->trans('MahnungTrackingVerwerfen')).'</a></div>';
}
print '</div>';
}
// Scan-Button (Belege durchsuchen)
if ($canWrite) {
print '<div style="margin-bottom:8px;">';
print '<a class="button smallpaddingimp" href="'.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id).'&action=scan_belege&token='.newToken().'">';
print img_picto('', 'fa-search', 'class="pictofixedwidth"').dol_escape_htmltag($langs->trans('MahnungBelegeScannen'));
print '</a> ';
print '<span class="opacitymedium small">'.$langs->trans('MahnungBelegeScannenHint').'</span>';
print '</div>';
}
$urlSelf = $_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id);
// Upload-Formular (Durchsuchen + Upload-Button)
$formfile->form_attach_new_file(
$urlSelf,
'', // title
0, // addcancel
0, // sectionid
(int) $canWrite, // perm
50, // size
$mahnung, // object
'', // options
1, // useajax
'', // savingdocmask
0, // linkfiles
'formuserfile', // htmlname
'', // accept
'', // sectiondir
0, // usewithoutform
0, // capture
0 // disablemulti
);
// Dateiliste der bereits hochgeladenen Belege
$formfile->showdocuments(
'mahnung', // $modulepart
$mahnungSafeRef, // $modulesubdir
$upload_dir, // $filedir
$urlSelf, // $urlsource
0, // $genallowed (kein PDF-Gen-Button hier)
(int) $canWrite, // $delallowed
'', // $modelselected
0, // $allowgenifempty
0, // $forcenomultilang
0, // $iconPDF
0, // $notused
0, // $noform
'', // $param
'', // $title
'', // $buttonlabel
'', // $codelang
'', // $morepicto
$mahnung, // $object
1 // $hideifempty (Liste nur zeigen wenn Dateien vorhanden)
);
if ($mahnung->status !== Mahnung::STATUS_STORNIERT && $user->hasRight('mahnung', 'delete')) {
if ($action === 'confirm_storno') {
print $form->formconfirm(
$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id),
$langs->trans('MahnungStornieren'),
$langs->trans('MahnungStornieren').' — '.dol_escape_htmltag($mahnung->ref).'?',
'storno',
'',
0,
1
);
}
if ($action === 'ask_uneinbringlich') {
// Bestätigungsdialog mit Eingabefeld für Begründung
$formquestion = array(
array(
'type' => 'textarea',
'name' => 'uneinbringlich_note',
'label' => $langs->trans('MahnungUneinbringlichBegruendung'),
'value' => $langs->trans('MahnungVerfahrenErfolglos').' '.dol_print_date(dol_now(), 'day'),
'morecss' => 'minwidth500 quatrevingtpercent',
),
);
print $form->formconfirm(
$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id),
$langs->trans('MahnungUneinbringlichTitel'),
$langs->trans('MahnungUneinbringlichWarnung').' '.dol_escape_htmltag($mahnung->ref).'?',
'confirm_uneinbringlich',
$formquestion,
0,
1,
300
);
}
}
print '<br><div class="tabsAction">';
if ($user->hasRight('mahnung', 'write')) {
// Modellauswahl
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/core/modules/mahnung/modules_mahnung.php';
$modellist = ModelePDFMahnung::liste_modeles($db);
$defaultModel = getDolGlobalString('MAHNUNG_ADDON_PDF', 'standard_mahnung');
if (is_array($modellist) && count($modellist) > 1) {
print '<select name="model" id="selectmodel" style="margin-right: 5px;">';
foreach ($modellist as $k => $v) {
print '<option value="'.dol_escape_htmltag($k).'"'.($k === $defaultModel ? ' selected' : '').'>'.dol_escape_htmltag($v).'</option>';
}
print '</select>';
}
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id).'&action=regenerate_pdf&token='.newToken().'" onclick="if(document.getElementById(\'selectmodel\')){this.href+=\'&model=\'+document.getElementById(\'selectmodel\').value;}">';
print $langs->trans('MahnungGenerate');
print '</a> ';
}
// "Als uneinbringlich klassifizieren" — nur für Stufe-3-Mahnungen mit
// Status >= ERSTELLT (also nicht im Entwurf) und nicht bereits storniert.
// Setzt die Rechnung auf STATUS_ABANDONED mit close_code='badcustomer'.
if ($mahnung->status !== Mahnung::STATUS_STORNIERT
&& (int) $mahnung->stufe === 3
&& (int) $mahnung->status >= Mahnung::STATUS_ERSTELLT
&& $user->hasRight('mahnung', 'delete')
) {
// Prüfen ob Rechnung schon abandoned ist — dann Button verstecken
$facStatus = 0;
$facRes = $db->query("SELECT fk_statut, close_code FROM ".MAIN_DB_PREFIX."facture WHERE rowid = ".((int) $mahnung->fk_facture));
if ($facRes && ($facObj = $db->fetch_object($facRes))) {
$facStatus = (int) $facObj->fk_statut;
$db->free($facRes);
}
if ($facStatus !== 3) {
print '<a class="butActionDelete" href="'.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id).'&action=ask_uneinbringlich" title="'.dol_escape_htmltag($langs->trans('MahnungUneinbringlichHint')).'">';
print img_picto('', 'warning', 'class="pictofixedwidth"');
print $langs->trans('MahnungUneinbringlich');
print '</a>';
} else {
// Rechnung ist schon als abandoned markiert — Info anzeigen
print '<span class="opacitymedium" style="margin-left:8px;">'.dol_escape_htmltag($langs->trans('MahnungRechnungBereitsUneinbringlich')).'</span>';
}
}
if ($mahnung->status !== Mahnung::STATUS_STORNIERT && $user->hasRight('mahnung', 'delete')) {
print '<a class="butActionDelete" href="'.$_SERVER['PHP_SELF'].'?id='.((int) $mahnung->id).'&action=confirm_storno">';
print $langs->trans('MahnungStornieren');
print '</a>';
}
print '</div>';
llxFooter();
$db->close();