* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. */ /** * \file htdocs/custom/mahnung/lib/mahnung_anlage.lib.php * \ingroup mahnung * \brief Gemeinsame Anlage-Logik für Mahnvorgänge. * * Eingebunden von ajax/createmahnung.php (Einzel-/Massenanlage) und * ajax/sammelbrief.php (Anlage + gebündeltes Druck-PDF). Beide Endpunkte bauen * ihre Mahnvorgänge aus denselben Vorschlagszeilen. Gebühren, §288-Pauschale, * Verzugszinsen, Vorstufenkosten und der Sonderfall "kostenlose * Zahlungserinnerung" stehen deshalb genau einmal — hier. */ // Nicht direkt per URL aufrufbar — die Datei definiert ausschließlich Funktionen. if (!defined('DOL_VERSION')) { print 'Diese Datei kann nicht direkt aufgerufen werden.'; exit; } 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'; /** * Sentinel: der POST-Parameter "stufe" wurde nicht gesetzt. * Notwendig, weil 0 (Zahlungserinnerung) eine gültige Stufennummer ist und * eine Falsy-Prüfung (`$forceStufe ?: ...`) sie verschlucken würde. */ if (!defined('MAHNUNG_STUFE_NICHT_GESETZT')) { define('MAHNUNG_STUFE_NICHT_GESETZT', -1); } /** Sentinel: "stufe" wurde gesetzt, ist aber keine konfigurierte aktive Stufe. */ if (!defined('MAHNUNG_STUFE_UNGUELTIG')) { define('MAHNUNG_STUFE_UNGUELTIG', -2); } /** * Liest den optionalen POST-Parameter "stufe" (Zielstufe erzwingen). * * Der Wert wird bewusst als String geholt, damit '' (nicht gesetzt) von '0' * (Zahlungserinnerung) unterschieden werden kann. Validiert wird gegen die * tatsächlich konfigurierten aktiven Stufen — NICHT gegen einen festen * Bereich 1..3, denn Stufennummern sind frei wählbar. * * @param MahnungVorschlag $service Service, aus dem die aktiven Stufen kommen * @return int Stufennummer, MAHNUNG_STUFE_NICHT_GESETZT oder MAHNUNG_STUFE_UNGUELTIG */ function mahnungGetForceStufe($service) { $raw = GETPOST('stufe', 'alphanohtml'); if (is_array($raw)) { return MAHNUNG_STUFE_UNGUELTIG; } $raw = trim((string) $raw); if ($raw === '') { return MAHNUNG_STUFE_NICHT_GESETZT; } if (!preg_match('/^-?\d+$/', $raw)) { return MAHNUNG_STUFE_UNGUELTIG; } $nr = (int) $raw; if ($service->getStufe($nr) === null) { return MAHNUNG_STUFE_UNGUELTIG; } return $nr; } /** * Holt die Vorschlagszeilen EINMALIG und indiziert sie nach Rechnungs-ID. * * getVorschlaege() liest immer die komplette Liste überfälliger Rechnungen — * der Aufruf gehört deshalb vor die Schleife, nicht hinein. * * @param MahnungVorschlag $service * @param array $filter Filter für getVorschlaege() * @return array|false facture_id => Vorschlagszeile, false bei Fehler */ function mahnungVorschlagsIndex($service, array $filter = array()) { $rows = $service->getVorschlaege($filter); if ($rows === false) { return false; } if (!is_array($rows)) { return array(); } $index = array(); foreach ($rows as $r) { if (!isset($r['facture_id'])) { continue; } $index[(int) $r['facture_id']] = $r; } return $index; } /** * Fehlertext des Vorschlag-Service. isset()-Guard, damit die Funktion auch * ohne die Property $error keine PHP-Notice auslöst. * * @param MahnungVorschlag $service * @return string */ function mahnungVorschlagFehlertext($service) { global $langs; if (isset($service->error) && $service->error !== '') { return (string) $service->error; } return $langs->trans('MahnungVorschlagFehler'); } /** * Baut einen anlagefertigen (noch nicht gespeicherten) Mahnvorgang aus einer * Vorschlagszeile und der Zielstufe. * * Ist die Zielstufe eine Zahlungserinnerung (ist_erinnerung = 1), werden * Mahngebühr, §288-Pauschale, Verzugszinsen und Vorstufenkosten ZWINGEND auf 0 * gesetzt — unabhängig davon, was in der Stufen-Konfiguration steht. Die * Erinnerung kostet nichts, Versandart ist immer E-Mail und es entsteht kein * Mahn-PDF (pdf_path bleibt leer, Anhang ist die Original-Rechnung). * * Bei echten Mahnstufen werden die Gebühren/Pauschalen der Vorstufen derselben * Rechnung als kosten_vorstufen übernommen und fließen in rechneSumme() ein. * * @param DoliDB $db * @param array $row Zeile aus MahnungVorschlag::getVorschlaege() * @param MahnungStufe $stufe Zielstufe * @param float $basiszins Basiszinssatz in Prozent * @param string $versandart Versandart erzwingen ('' = Default der Stufe) * @return Mahnung */ function mahnungBaueVorgang($db, array $row, $stufe, $basiszins, $versandart = '') { $factureId = (int) $row['facture_id']; $now = dol_now(); $mahnung = new Mahnung($db); $mahnung->fk_facture = $factureId; $mahnung->fk_soc = (int) $row['soc_id']; $mahnung->stufe = (int) $stufe->stufe; $mahnung->date_mahnung = $now; $mahnung->date_lim_reglement_alt = $row['facture_date_lim_reglement']; $mahnung->date_lim_reglement_neu = dol_time_plus_duree($now, (int) $stufe->neue_frist_tage, 'd'); $mahnung->betrag_offen = (float) $row['betrag_offen']; $mahnung->customertype = $row['kundentyp']; $mahnung->basiszins_snapshot = $basiszins; if ($stufe->istErinnerung()) { // Kostenlose Zahlungserinnerung: keine Mahngebühr, keine Pauschale nach // §288 Abs. 5, keine Verzugszinsen, keine Vorstufenkosten. Bewusst hart // genullt und nicht aus der Stufen-Konfiguration übernommen. $mahnung->mahngebuehr = 0; $mahnung->pauschale_b2b = 0; $mahnung->verzugszinsen = 0; $mahnung->kosten_vorstufen = 0; $mahnung->versandart = Mahnung::VERSAND_MAIL; $mahnung->pdf_path = null; } else { $mahnung->versandart = $versandart ?: ($stufe->versandart_default ?: Mahnung::VERSAND_PDF); $mahnung->mahngebuehr = $stufe->getMahngebuehr($mahnung->customertype); // §288 Abs. 5 Pauschale: nur einmal pro Rechnung und nur B2B if ($mahnung->customertype === Mahnung::KUNDENTYP_B2B && (int) $stufe->pauschale_b2b_einmalig === 1 && !mahnungPauschaleBereitsAngewendet($db, $factureId)) { $mahnung->pauschale_b2b = (float) getDolGlobalString('MAHNUNG_PAUSCHALE_B2B', '40.00'); } $mahnung->verzugszinsen = Mahnung::berechneVerzugszinsen( $mahnung->betrag_offen, (int) $row['tage_verzug'], $mahnung->customertype, $basiszins, $stufe->getZinssatzOverride($mahnung->customertype) ); // Gebühren/Pauschalen der Vorstufen kumulieren (Zinsen bleiben außen vor, // die werden je Stufe tagesgenau neu gerechnet). $mahnung->kosten_vorstufen = $mahnung->summeVorstufenKosten($factureId); } $mahnung->rechneSumme(); $mahnung->status = Mahnung::STATUS_ERSTELLT; return $mahnung; } /** * Prüft, ob für eine Rechnung bereits in einer nicht stornierten Mahnung die * §288-Abs.-5-Pauschale berechnet wurde. * * @param DoliDB $db * @param int $factureId * @return bool */ function mahnungPauschaleBereitsAngewendet($db, $factureId) { $sql = "SELECT 1 FROM ".MAIN_DB_PREFIX."mahnung_mahnung"; $sql .= " WHERE fk_facture = ".((int) $factureId); $sql .= " AND entity IN (".getEntity('mahnung').")"; $sql .= " AND status <> ".((int) Mahnung::STATUS_STORNIERT); $sql .= " AND pauschale_b2b > 0"; $sql .= " LIMIT 1"; $resql = $db->query($sql); if (!$resql) { dol_syslog('mahnungPauschaleBereitsAngewendet SQL-Fehler: '.$db->lasterror(), LOG_ERR); return false; } $has = (bool) $db->num_rows($resql); $db->free($resql); return $has; }