* * 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/class/mahnung.class.php * \ingroup mahnung * \brief CRUD-Klasse fuer Mahnvorgaenge (llx_mahnung_mahnung). */ require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; /** * Klasse Mahnung — repraesentiert einen Mahnvorgang zu einer Kundenrechnung. */ class Mahnung extends CommonObject { const STATUS_ENTWURF = 0; const STATUS_ERSTELLT = 1; const STATUS_VERSENDET = 2; const STATUS_ERLEDIGT = 3; const STATUS_STORNIERT = 9; const VERSAND_PDF = 'pdf'; const VERSAND_MAIL = 'mail'; const VERSAND_DRUCK = 'druck'; const VERSAND_NONE = 'none'; const KUNDENTYP_B2C = 'B2C'; const KUNDENTYP_B2B = 'B2B'; /** @var string */ public $element = 'mahnung'; /** @var string */ public $table_element = 'mahnung_mahnung'; /** @var int */ public $entity; /** @var string */ public $ref; /** @var int */ public $fk_facture; /** @var int */ public $fk_soc; /** @var int 1, 2, 3 */ public $stufe; /** @var int Unix-Zeit */ public $date_mahnung; /** @var int Unix-Zeit */ public $date_lim_reglement_alt; /** @var int Unix-Zeit */ public $date_lim_reglement_neu; /** @var float */ public $betrag_offen = 0; /** @var float */ public $mahngebuehr = 0; /** @var float */ public $pauschale_b2b = 0; /** @var float */ public $verzugszinsen = 0; /** @var float */ public $summe_mahnung = 0; /** @var string pdf|mail|druck|none */ public $versandart = self::VERSAND_PDF; /** @var string B2C|B2B */ public $customertype; /** @var float */ public $basiszins_snapshot; /** @var string */ public $pdf_path; /** @var string */ public $note_private; /** @var int 0..9 */ public $status = self::STATUS_ENTWURF; /** @var int Unix-Zeit */ public $datec; /** @var int Unix-Zeit */ public $tms; /** @var int */ public $fk_user_creat; /** @var int */ public $fk_user_modif; /** * @param DoliDB $db Datenbank-Handler */ public function __construct($db) { global $conf; $this->db = $db; $this->entity = $conf->entity; } /** * Naechste freie Mahnungs-Referenz fuer das aktuelle Jahr. * Format: MAHN- * * @return string */ public function getNextRef() { $year = (int) dol_print_date(dol_now(), '%Y'); $prefix = 'MAHN'.$year.'-'; $sql = "SELECT MAX(CAST(SUBSTRING(ref, ".(strlen($prefix) + 1).") AS UNSIGNED)) AS maxnum"; $sql .= " FROM ".MAIN_DB_PREFIX."mahnung_mahnung"; $sql .= " WHERE entity = ".((int) $this->entity); $sql .= " AND ref LIKE '".$this->db->escape($prefix)."%'"; $resql = $this->db->query($sql); $next = 1; if ($resql) { $obj = $this->db->fetch_object($resql); $next = ((int) $obj->maxnum) + 1; $this->db->free($resql); } return $prefix.str_pad((string) $next, 4, '0', STR_PAD_LEFT); } /** * Mahnvorgang anlegen. * * @param User $user Anlegender User * @return int <0 bei Fehler, sonst neue rowid */ public function create($user) { $now = dol_now(); if (empty($this->ref)) { $this->ref = $this->getNextRef(); } $this->db->begin(); $sql = "INSERT INTO ".MAIN_DB_PREFIX."mahnung_mahnung ("; $sql .= "entity, ref, fk_facture, fk_soc, stufe, date_mahnung,"; $sql .= " date_lim_reglement_alt, date_lim_reglement_neu,"; $sql .= " betrag_offen, mahngebuehr, pauschale_b2b, verzugszinsen, summe_mahnung,"; $sql .= " versandart, customertype, basiszins_snapshot, pdf_path, note_private,"; $sql .= " status, datec, fk_user_creat"; $sql .= ") VALUES ("; $sql .= ((int) $this->entity).","; $sql .= "'".$this->db->escape($this->ref)."',"; $sql .= ((int) $this->fk_facture).","; $sql .= ((int) $this->fk_soc).","; $sql .= ((int) $this->stufe).","; $sql .= "'".$this->db->idate($this->date_mahnung ?: $now)."',"; $sql .= ($this->date_lim_reglement_alt ? "'".$this->db->idate($this->date_lim_reglement_alt)."'" : "NULL").","; $sql .= ($this->date_lim_reglement_neu ? "'".$this->db->idate($this->date_lim_reglement_neu)."'" : "NULL").","; $sql .= ((float) $this->betrag_offen).","; $sql .= ((float) $this->mahngebuehr).","; $sql .= ((float) $this->pauschale_b2b).","; $sql .= ((float) $this->verzugszinsen).","; $sql .= ((float) $this->summe_mahnung).","; $sql .= "'".$this->db->escape($this->versandart ?: self::VERSAND_PDF)."',"; $sql .= ($this->customertype ? "'".$this->db->escape($this->customertype)."'" : "NULL").","; $sql .= ($this->basiszins_snapshot !== null ? ((float) $this->basiszins_snapshot) : "NULL").","; $sql .= ($this->pdf_path ? "'".$this->db->escape($this->pdf_path)."'" : "NULL").","; $sql .= ($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL").","; $sql .= ((int) $this->status).","; $sql .= "'".$this->db->idate($now)."',"; $sql .= ((int) $user->id); $sql .= ")"; dol_syslog(get_class($this).'::create', LOG_DEBUG); $resql = $this->db->query($sql); if (!$resql) { $this->error = $this->db->lasterror(); $this->db->rollback(); return -1; } $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'mahnung_mahnung'); $this->datec = $now; $this->fk_user_creat = $user->id; $this->db->commit(); return $this->id; } /** * @param int $id rowid * @return int -1 Fehler, 0 nicht gefunden, >0 OK */ public function fetch($id) { $sql = "SELECT t.* FROM ".MAIN_DB_PREFIX."mahnung_mahnung as t"; $sql .= " WHERE t.rowid = ".((int) $id); $resql = $this->db->query($sql); if (!$resql) { $this->error = $this->db->lasterror(); return -1; } if (!$this->db->num_rows($resql)) { $this->db->free($resql); return 0; } $obj = $this->db->fetch_object($resql); $this->id = $obj->rowid; $this->entity = $obj->entity; $this->ref = $obj->ref; $this->fk_facture = $obj->fk_facture; $this->fk_soc = $obj->fk_soc; $this->stufe = $obj->stufe; $this->date_mahnung = $this->db->jdate($obj->date_mahnung); $this->date_lim_reglement_alt = $this->db->jdate($obj->date_lim_reglement_alt); $this->date_lim_reglement_neu = $this->db->jdate($obj->date_lim_reglement_neu); $this->betrag_offen = $obj->betrag_offen; $this->mahngebuehr = $obj->mahngebuehr; $this->pauschale_b2b = $obj->pauschale_b2b; $this->verzugszinsen = $obj->verzugszinsen; $this->summe_mahnung = $obj->summe_mahnung; $this->versandart = $obj->versandart; $this->customertype = $obj->customertype; $this->basiszins_snapshot = $obj->basiszins_snapshot; $this->pdf_path = $obj->pdf_path; $this->note_private = $obj->note_private; $this->status = (int) $obj->status; $this->datec = $this->db->jdate($obj->datec); $this->tms = $this->db->jdate($obj->tms); $this->fk_user_creat = $obj->fk_user_creat; $this->fk_user_modif = $obj->fk_user_modif; $this->db->free($resql); return 1; } /** * Mehrere Mahnungen laden. * * @param string $sortfield * @param string $sortorder * @param int $limit * @param int $offset * @param array $filter Schluessel: fk_facture, fk_soc, stufe, status, ref_like * @param string $mode 'list' | 'count' * @return Mahnung[]|int Liste, Anzahl oder -1 bei Fehler */ public function fetchAll($sortfield = 'date_mahnung', $sortorder = 'DESC', $limit = 0, $offset = 0, $filter = array(), $mode = 'list') { $sql = "SELECT t.rowid FROM ".MAIN_DB_PREFIX."mahnung_mahnung as t"; $sql .= " WHERE t.entity = ".((int) $this->entity); if (!empty($filter['fk_facture'])) { $sql .= " AND t.fk_facture = ".((int) $filter['fk_facture']); } if (!empty($filter['fk_soc'])) { $sql .= " AND t.fk_soc = ".((int) $filter['fk_soc']); } if (isset($filter['stufe']) && $filter['stufe'] !== '') { $sql .= " AND t.stufe = ".((int) $filter['stufe']); } if (isset($filter['status']) && $filter['status'] !== '') { $sql .= " AND t.status = ".((int) $filter['status']); } if (!empty($filter['ref_like'])) { $sql .= " AND t.ref LIKE '%".$this->db->escape($filter['ref_like'])."%'"; } if ($mode === 'count') { $sqlcount = preg_replace('/SELECT t\.rowid/', 'SELECT COUNT(*) as total', $sql); $resqlcount = $this->db->query($sqlcount); if (!$resqlcount) { return -1; } $objcount = $this->db->fetch_object($resqlcount); $this->db->free($resqlcount); return (int) $objcount->total; } $sql .= $this->db->order($sortfield, $sortorder); if ($limit > 0) { $sql .= $this->db->plimit($limit, $offset); } $resql = $this->db->query($sql); if (!$resql) { $this->error = $this->db->lasterror(); return -1; } $result = array(); while ($obj = $this->db->fetch_object($resql)) { $m = new self($this->db); $m->fetch($obj->rowid); $result[] = $m; } $this->db->free($resql); return $result; } /** * Mahnvorgang aktualisieren. * * @param User $user Bearbeitender User * @return int <0 bei Fehler, sonst id */ public function update($user) { if (empty($this->id)) { $this->error = 'Mahnung::update — id missing'; return -1; } $this->db->begin(); $sql = "UPDATE ".MAIN_DB_PREFIX."mahnung_mahnung SET"; $sql .= " stufe = ".((int) $this->stufe); $sql .= ", date_mahnung = '".$this->db->idate($this->date_mahnung ?: dol_now())."'"; $sql .= ", date_lim_reglement_alt = ".($this->date_lim_reglement_alt ? "'".$this->db->idate($this->date_lim_reglement_alt)."'" : "NULL"); $sql .= ", date_lim_reglement_neu = ".($this->date_lim_reglement_neu ? "'".$this->db->idate($this->date_lim_reglement_neu)."'" : "NULL"); $sql .= ", betrag_offen = ".((float) $this->betrag_offen); $sql .= ", mahngebuehr = ".((float) $this->mahngebuehr); $sql .= ", pauschale_b2b = ".((float) $this->pauschale_b2b); $sql .= ", verzugszinsen = ".((float) $this->verzugszinsen); $sql .= ", summe_mahnung = ".((float) $this->summe_mahnung); $sql .= ", versandart = '".$this->db->escape($this->versandart)."'"; $sql .= ", customertype = ".($this->customertype ? "'".$this->db->escape($this->customertype)."'" : "NULL"); $sql .= ", basiszins_snapshot = ".($this->basiszins_snapshot !== null ? ((float) $this->basiszins_snapshot) : "NULL"); $sql .= ", pdf_path = ".($this->pdf_path ? "'".$this->db->escape($this->pdf_path)."'" : "NULL"); $sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL"); $sql .= ", status = ".((int) $this->status); $sql .= ", fk_user_modif = ".((int) $user->id); $sql .= " WHERE rowid = ".((int) $this->id); dol_syslog(get_class($this).'::update', LOG_DEBUG); $resql = $this->db->query($sql); if (!$resql) { $this->error = $this->db->lasterror(); $this->db->rollback(); return -1; } $this->fk_user_modif = $user->id; $this->db->commit(); return $this->id; } /** * Mahnvorgang loeschen. Verknuepftes PDF wird mitgeloescht falls vorhanden. * * @param User $user Loeschender User * @return int <0 bei Fehler, sonst 1 */ public function delete($user) { $this->db->begin(); if ($this->pdf_path && file_exists($this->pdf_path)) { @unlink($this->pdf_path); } $sql = "DELETE FROM ".MAIN_DB_PREFIX."mahnung_mahnung WHERE rowid = ".((int) $this->id); dol_syslog(get_class($this).'::delete', LOG_DEBUG); $resql = $this->db->query($sql); if (!$resql) { $this->error = $this->db->lasterror(); $this->db->rollback(); return -1; } $this->db->commit(); return 1; } /** * Status auf "erledigt" setzen (Trigger nach Zahlungseingang). * * @param User $user * @return int <0 bei Fehler, sonst 1 */ public function setErledigt($user) { $this->status = self::STATUS_ERLEDIGT; $res = $this->update($user); return $res > 0 ? 1 : -1; } /** * Letzten Mahnvorgang zu einer Rechnung holen (hoechste Stufe, neuestes Datum). * * @param int $factureId * @return Mahnung|null */ public function fetchLastByFacture($factureId) { $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."mahnung_mahnung"; $sql .= " WHERE fk_facture = ".((int) $factureId); $sql .= " AND entity = ".((int) $this->entity); $sql .= " AND status NOT IN (".self::STATUS_STORNIERT.")"; $sql .= " ORDER BY stufe DESC, date_mahnung DESC, rowid DESC"; $sql .= " LIMIT 1"; $resql = $this->db->query($sql); if (!$resql || !$this->db->num_rows($resql)) { return null; } $obj = $this->db->fetch_object($resql); $this->db->free($resql); $m = new self($this->db); if ($m->fetch($obj->rowid) > 0) { return $m; } return null; } /** * Verzugszinsen tagesgenau nach BGB §288 berechnen. * Formel: zinsen = betrag_offen * (basiszins + aufschlag) / 100 * tage / 365 * * @param float $betragOffen * @param int $tageVerzug * @param string $kundentyp B2C|B2B * @param float $basiszins in Prozent (z.B. 1.27) * @param float|null $zinssatzOverride Override aus Stufen-Konfig (Prozent) * @return float Zinsen in EUR (gerundet 2 Nachkomma) */ public static function berechneVerzugszinsen($betragOffen, $tageVerzug, $kundentyp, $basiszins, $zinssatzOverride = null) { $tage = max(0, (int) $tageVerzug); if ($tage <= 0 || $betragOffen <= 0) { return 0.0; } if ($zinssatzOverride !== null) { $satz = (float) $zinssatzOverride; } else { $aufschlag = ($kundentyp === self::KUNDENTYP_B2B) ? (float) getDolGlobalString('MAHNUNG_AUFSCHLAG_B2B', '9.0') : (float) getDolGlobalString('MAHNUNG_AUFSCHLAG_B2C', '5.0'); $satz = (float) $basiszins + $aufschlag; } $zinsen = ((float) $betragOffen) * $satz / 100.0 * $tage / 365.0; return round($zinsen, 2); } /** * Setzt $summe_mahnung = betrag_offen + mahngebuehr + pauschale_b2b + verzugszinsen. * * @return float Neue Summe */ public function rechneSumme() { $this->summe_mahnung = round( (float) $this->betrag_offen + (float) $this->mahngebuehr + (float) $this->pauschale_b2b + (float) $this->verzugszinsen, 2 ); return $this->summe_mahnung; } /** * Lokalisiertes Status-Label. * * @param int|null $status Override (sonst $this->status) * @return string */ public function getStatusLabel($status = null) { global $langs; $s = $status ?? $this->status; switch ((int) $s) { case self::STATUS_ENTWURF: return $langs->trans('MahnungStatusEntwurf'); case self::STATUS_ERSTELLT: return $langs->trans('MahnungStatusErstellt'); case self::STATUS_VERSENDET: return $langs->trans('MahnungStatusVersendet'); case self::STATUS_ERLEDIGT: return $langs->trans('MahnungStatusErledigt'); case self::STATUS_STORNIERT: return $langs->trans('MahnungStatusStorniert'); default: return (string) $s; } } }