* * GPL v3 (siehe COPYING). */ /** * \file htdocs/custom/mahnung/ajax/createmahnung.php * \ingroup mahnung * \brief AJAX-Endpoint: Mahnung(en) zu Rechnung(en) erzeugen + PDF generieren. * * Akzeptiert sowohl klassische Form-POSTs (Browser-Submit aus list.php) * als auch AJAX-Calls. Antwortet je nach Accept-Header HTML-Redirect * oder JSON. * * Ist die Zielstufe eine kostenlose Zahlungserinnerung (ist_erinnerung = 1), * wird KEIN Mahn-PDF erzeugt: Anhang ist später die unveränderte * Original-Rechnung, der Versand läuft per E-Mail über ajax/sendmail.php und * ausschließlich nach ausdrücklicher Bestätigung. * * Hat der Kunde keine (gültige) E-Mail-Adresse, wird die Erinnerung trotzdem * angelegt — die Rechnung darf nicht aus dem Mahnlauf fallen — und in der Antwort * unter "ohne_email" namentlich gemeldet (Adresse nachtragen oder per Post senden). * * POST: * - facture_ids[] Array Rechnungs-IDs (oder einzelne facture_id) * - stufe Optional: Stufe erzwingen (sonst Vorschlag-Logik). * '0' ist ein gültiger Wert, '' bedeutet "nicht gesetzt". * - token CSRF */ if (!defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1'); if (!defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1'); ob_start(); require_once $_SERVER['DOCUMENT_ROOT'].'/main.inc.php'; require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnung.class.php'; require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnungstufe.class.php'; require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnungvorschlag.class.php'; require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/lib/mahnung_anlage.lib.php'; require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/core/modules/modMahnung.class.php'; global $db, $user, $langs, $conf; $langs->loadLangs(array('mahnung@mahnung')); // Schema nach einem reinen Datei-Deploy nachziehen. Kritisch hier: ohne die // Spalte kosten_vorstufen schlägt Mahnung::create() mit "Unknown column" fehl. modMahnung::ensureSchema($db); /** * @param bool $success * @param string $message * @param array $extra */ function respond($success, $message, $extra = array()) { $wantsJson = false; if (!empty($_SERVER['HTTP_ACCEPT']) && stripos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) { $wantsJson = true; } if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') { $wantsJson = true; } while (ob_get_level() > 0) { ob_end_clean(); } if ($wantsJson) { header('Content-Type: application/json; charset=utf-8'); echo json_encode(array_merge(array('success' => (bool) $success, 'message' => $message), $extra)); exit; } // Klassischer Submit -> Redirect mit Flash-Message. // // Wurde GENAU EIN Vorgang erzeugt, geht es direkt auf dessen Karte: der Vorgang // muss ohnehin bearbeitet werden (Versand anstoßen, Texte prüfen), und in der // Vorschlagsliste taucht die Rechnung jetzt gar nicht mehr auf — die Wartefrist // zur Folgestufe läuft. Ohne diesen Sprung wäre die frisch angelegte Mahnung nur // über den Umweg Archiv auffindbar. if (function_exists('setEventMessages')) { setEventMessages($message, null, $success ? 'mesgs' : 'errors'); } if ($success && !empty($extra['single_id']) && (int) $extra['single_id'] > 0) { header('Location: '.DOL_URL_ROOT.'/custom/mahnung/card.php?id='.((int) $extra['single_id'])); exit; } header('Location: '.DOL_URL_ROOT.'/custom/mahnung/list.php?mainmenu=billing&leftmenu=mahnung&mode=vorschlag'); exit; } // 1) CSRF $postedToken = GETPOST('token', 'alphanohtml'); if (empty($postedToken) || empty($_SESSION['newtoken']) || $postedToken !== $_SESSION['newtoken']) { respond(false, $langs->trans('MahnungCsrfFehler'), array('code' => 'csrf')); } // 2) Permission if (!$user->hasRight('mahnung', 'write')) { respond(false, $langs->transnoentities('NotEnoughPermissions') ?: $langs->trans('MahnungNichtBerechtigt'), array('code' => 'forbidden')); } // 3) Input $factureIds = GETPOST('facture_ids', 'array:int'); if (empty($factureIds)) { $single = GETPOSTINT('facture_id'); if (!empty($single)) { $factureIds = array($single); } } $factureIds = array_values(array_unique(array_map('intval', $factureIds))); $factureIds = array_filter($factureIds, function ($v) { return $v > 0; }); if (empty($factureIds)) { respond(false, $langs->trans('MahnungKeineRechnungenAusgewaehlt'), array('code' => 'noinput')); } $service = new MahnungVorschlag($db); // 4) Verarbeitung — Vorschläge einmal holen, dann pro Rechnung Mahnung erzeugen. // Bewusst VOR der Stufen-Validierung: liefert die Stufenkonfiguration einen // Fehler, soll dieser gemeldet werden und nicht "Stufe ungültig". $vorschlaege = mahnungVorschlagsIndex($service); if ($vorschlaege === false) { respond(false, mahnungVorschlagFehlertext($service), array('code' => 'vorschlagfehler')); } // Zielstufe erzwingen? 0 ist gültig (Zahlungserinnerung), '' heißt "nicht gesetzt". $forceStufe = mahnungGetForceStufe($service); if ($forceStufe === MAHNUNG_STUFE_UNGUELTIG) { respond(false, $langs->trans('MahnungStufeUngueltig'), array('code' => 'badstufe')); } $basiszins = (float) getDolGlobalString('MAHNUNG_BASISZINS', '1.27'); $created = 0; $createdErinnerung = 0; $skipped = 0; $failed = array(); // IDs der tatsächlich angelegten Vorgänge — bei genau einem wird direkt auf dessen // Karte weitergeleitet (siehe respond()). $createdIds = array(); // Angelegte Zahlungserinnerungen, die mangels (gültiger) E-Mail-Adresse nicht // automatisch versendet werden können — siehe Kommentar in der Schleife. $ohneEmail = array(); foreach ($factureIds as $fid) { if (!isset($vorschlaege[$fid])) { // Keine offene Mahnungs-Empfehlung — z.B. weil die Wartefrist noch läuft $skipped++; continue; } $row = $vorschlaege[$fid]; $stufeNr = ($forceStufe !== MAHNUNG_STUFE_NICHT_GESETZT) ? $forceStufe : (isset($row['vorgeschlagene_stufe']) ? (int) $row['vorgeschlagene_stufe'] : null); if ($stufeNr === null) { $skipped++; continue; } $stufe = $service->getStufe($stufeNr); if ($stufe === null) { $failed[] = $langs->trans('MahnungStufeNichtKonfiguriert', $fid, $stufeNr); continue; } $mahnung = mahnungBaueVorgang($db, $row, $stufe, $basiszins); $newId = $mahnung->create($user); if ($newId <= 0) { $failed[] = 'Rechnung #'.$fid.': '.$mahnung->error; continue; } if ($stufe->istErinnerung()) { // Kostenlose Zahlungserinnerung: kein Mahn-PDF. Versendet wird die // Original-Rechnung per E-Mail, erst nach Bestätigung auf der Karte. $createdErinnerung++; $created++; // Ohne (gültige) E-Mail-Adresse ist die Erinnerung per Mail nicht zustellbar — // ajax/sendmail.php bricht dann mit MahnungKundeKeineEmail ab. Trotzdem wird sie // angelegt und NICHT abgewiesen: die Erinnerung ist in der Regel die kleinste // aktive Stufe, ein Abweisen würde die Rechnung dauerhaft aus dem Mahnlauf // werfen (ohne Vorstufe schlägt der Vorschlag immer wieder dieselbe Erinnerung // vor, die immer wieder scheitert — eine echte Mahnstufe würde nie erreicht). // Angelegt läuft die Wartefrist zur Folgestufe dagegen ab date_mahnung weiter // (MahnungVorschlag::ermittleZielstufe() nutzt date_versand nur als Vorzug), // die Rechnung erreicht also Stufe 1 auch ohne Mailadresse. // Damit die Erinnerung nicht still liegen bleibt, wird sie hier namentlich // gemeldet: Adresse nachtragen oder das Schreiben per Post rausschicken. $kundenMail = trim((string) (isset($row['soc_email']) ? $row['soc_email'] : '')); if ($kundenMail === '' || !isValidEmail($kundenMail)) { $refText = trim((string) (isset($row['facture_ref']) ? $row['facture_ref'] : '')); if ($refText === '') { $refText = '#'.((int) $fid); } $kundeText = trim((string) (isset($row['soc_nom']) ? $row['soc_nom'] : '')); $ohneEmail[] = ($kundeText !== '') ? $refText.' ('.$kundeText.')' : $refText; } $createdIds[] = (int) $newId; continue; } $docResult = $mahnung->generateDocument('', $langs); if ($docResult <= 0) { $failed[] = 'Rechnung #'.$fid.' (Mahnung '.$mahnung->ref.'): '.$langs->trans('MahnungDokumentFehler').' '.$mahnung->error; continue; } $created++; $createdIds[] = (int) $newId; } $msg = $langs->trans('MahnungMahnungErstellt', $created); if ($createdErinnerung > 0) { $msg .= $langs->trans('MahnungErinnerungenErstelltHinweis', $createdErinnerung); } if ($skipped > 0) { $msg .= $langs->trans('MahnungUebersprungen2', $skipped); } // Nicht versendbare Erinnerungen deutlich benennen — sonst wartet Eddy auf einen // Mailversand, der nie kommt. trans() escapt die eingesetzten Kundennamen selbst, // deshalb hier KEIN zusätzliches dol_escape_htmltag() (sonst doppeltes Encoding). if (!empty($ohneEmail)) { $msg .= $langs->trans('MahnungErinnerungOhneEmailHinweis', count($ohneEmail), implode(', ', $ohneEmail)); } if (!empty($failed)) { $msg .= $langs->trans('MahnungFehlerLabel', implode(' | ', $failed)); respond(false, $msg, array('created' => $created, 'erinnerungen' => $createdErinnerung, 'ohne_email' => $ohneEmail, 'failed' => $failed)); } // single_id nur bei GENAU einem angelegten Vorgang — dann springt respond() direkt // auf dessen Karte, weil er ohnehin bearbeitet werden muss. respond(true, $msg, array( 'created' => $created, 'erinnerungen' => $createdErinnerung, 'ohne_email' => $ohneEmail, 'skipped' => $skipped, 'created_ids' => $createdIds, 'single_id' => (count($createdIds) === 1) ? $createdIds[0] : 0, ));