* * GPL v3 (siehe COPYING). */ /** * \file htdocs/custom/mahnung/class/actions_mahnung.class.php * \ingroup mahnung * \brief Hook-Klasse: Tab "Mahnungen" + Button "Mahnung erstellen" auf Rechnungs-Karte. * * Wird in modMahnung registriert via module_parts['hooks'] = ['data' => ['invoicecard']]. */ require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnung.class.php'; class ActionsMahnung { /** @var array */ public $errors = array(); /** @var string */ public $resprints = ''; /** * Hook addMoreActionsButtons: Button "Mahnung erstellen" im Header der Rechnungs-Karte. * * @param array $parameters * @param CommonObject $object Facture * @param string $action * @param HookManager $hookmanager * @return int 0 = weiter, 1 = überschreiben */ public function addMoreActionsButtons($parameters, &$object, &$action, $hookmanager) { global $user, $langs; $contexts = explode(':', $parameters['context'] ?? ''); if (!in_array('invoicecard', $contexts, true)) { return 0; } if (empty($object->id) || empty($object->socid)) { return 0; } // Nur für normale Kundenrechnungen if (!isset($object->type) || (int) $object->type !== 0) { return 0; } $paye = (int) ($object->paye ?? 0); $statut = (int) ($object->statut ?? ($object->status ?? 0)); // Button nur bei FREIGEGEBENER (statut >= 1) und NOCH OFFENER (paye === 0) Rechnung. // Entwurf, bezahlt, storniert, abandoned → gar kein Button. if ($statut < 1 || $paye !== 0) { return 0; } if (!$user->hasRight('mahnung', 'write')) { return 0; } $langs->load('mahnung@mahnung'); $dateLim = !empty($object->date_lim_reglement) ? (int) $object->date_lim_reglement : 0; $ueberfaellig = ($dateLim > 0 && $dateLim < dol_now()); $label = $langs->trans('MahnungErstellen'); if ($ueberfaellig) { $url = DOL_URL_ROOT.'/custom/mahnung/list.php?mainmenu=billing&leftmenu=mahnung&mode=vorschlag&search_socid='.((int) $object->socid); $btn = dolGetButtonAction($label, '', 'default', $url, 'btn-mahnung-create', 1); } else { $attr = array('title' => $langs->trans('MahnungKeineUeberfaelligen')); $btn = dolGetButtonAction($label, '', 'default', '#', 'btn-mahnung-create', 0, array('attr' => $attr)); } // Der Hook läuft VOR den Standard-Buttons → der Mahnung-Button würde sonst ganz // am Anfang der .tabsAction-Div stehen. Eddy will ihn aber bei den letzten // "Verwaltungs"-Buttons (Löschen / Auf anderen Kunden übertragen — typisch // butActionRefused weil disabled bei freigegebener Rechnung). // JS verschiebt den Button per insertBefore VOR den ersten .butActionRefused — // fällt zurück auf appendTo wenn keiner existiert. $js = ""; // WICHTIG: addMoreActionsButtons braucht direkten print, nicht $this->resprints. // card.php wertet zwar $reshook aus, druckt aber $hookmanager->resPrint an dieser // Stelle NICHT — anders als bei formObjectOptions. print $btn.$js; return 0; } /** * Hook completeTabsHead: Tab "Mahnungen (n)" sowohl auf Rechnungs- als * auch auf Kundenkarte einblenden. * * @param array $parameters * @param CommonObject $object Facture oder Societe * @param string $action * @param HookManager $hookmanager * @return int 0 = weiter */ public function completeTabsHead($parameters, &$object, &$action, $hookmanager) { global $langs, $db; $contexts = explode(':', $parameters['context'] ?? ''); $onInvoice = in_array('invoicecard', $contexts, true); $onThirdparty = in_array('thirdpartycard', $contexts, true); if (!$onInvoice && !$onThirdparty) { return 0; } if (empty($object->id)) { return 0; } if (!isset($parameters['head']) || !is_array($parameters['head'])) { return 0; } // complete_head_from_modules() wird pro Karte mehrfach aufgerufen // (mode='add' für 'core' UND 'external', plus mode='remove'). // Genau einmal feuern: nur add/external. if (($parameters['mode'] ?? '') !== 'add') { return 0; } if (($parameters['filterorigmodule'] ?? '') !== 'external') { return 0; } $langs->load('mahnung@mahnung'); if ($onInvoice) { $count = $this->countMahnungen($db, '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?mainmenu=billing&leftmenu=mahnung&mode=archiv&search_socid='.((int) $object->id); } $head = &$parameters['head']; $pos = count($head); $head[$pos][0] = $tabUrl; $head[$pos][1] = $langs->trans('MahnungMenu').($count > 0 ? ' '.$count.'' : ''); $head[$pos][2] = 'mahnung'; return 0; } /** * @param DoliDB $db * @param string $col 'fk_facture' | 'fk_soc' * @param int $id * @return int */ private function countMahnungen($db, $col, $id) { if (!in_array($col, array('fk_facture', 'fk_soc'), true)) { return 0; } $sql = "SELECT COUNT(*) AS n FROM ".MAIN_DB_PREFIX."mahnung_mahnung"; $sql .= " WHERE ".$col." = ".((int) $id); $sql .= " AND status NOT IN (".Mahnung::STATUS_STORNIERT.")"; $resql = $db->query($sql); if (!$resql) { return 0; } $obj = $db->fetch_object($resql); $count = (int) $obj->n; $db->free($resql); return $count; } /** * Hook tabContentViewThirdparty: rendert eine prominente Bonitäts-Warnbox auf * der Kundenkarte, wenn für diesen Kunden uneinbringliche Forderungen existieren * (fk_statut=3 abandoned + close_code='badcustomer'). * * @param array $parameters * @param CommonObject $object Societe * @param string $action * @param HookManager $hookmanager * @return int 0 */ public function tabContentViewThirdparty($parameters, &$object, &$action, $hookmanager) { global $langs, $db; if (empty($object->id)) { return 0; } $langs->load('mahnung@mahnung'); $info = $this->getBonitaetsInfo($db, (int) $object->id); if (empty($info) || $info['anzahl'] <= 0) { return 0; } // Rote Warnbox direkt unter dem Banner — vor dem Standard-Content print '
'; print '
'; print img_picto('', 'warning', 'class="pictofixedwidth"'); print dol_escape_htmltag($langs->trans('MahnungBonitaetWarnung')).'
'; print ''; print ''; print ''; if (!empty($info['letztes_datum'])) { print ''; } print '
'.((int) $info['anzahl']).' '.dol_escape_htmltag($langs->trans($info['anzahl'] === 1 ? 'MahnungBonitaetRechnungSing' : 'MahnungBonitaetRechnungPlur')).''.price($info['summe']).' '.dol_escape_htmltag($langs->trans('MahnungBonitaetGesamtAusfall')).''.dol_escape_htmltag($langs->trans('MahnungBonitaetLetzteAm')).' '.dol_print_date($info['letztes_datum'], 'day').'
'; // Link zur Detail-Liste (gefilterter Mahnung-Archiv-View) $listUrl = DOL_URL_ROOT.'/compta/facture/list.php?socid='.((int) $object->id).'&search_status=3'; print '
'.dol_escape_htmltag($langs->trans('MahnungBonitaetAusfaelleAnzeigen')).' →
'; print '
'; return 0; } /** * Hook addMoreActionsButtons: Warning-Banner bei der Aktions-Leiste falls * der Kunde Forderungsausfälle hat. Wird sowohl bei Auftrag (ordercard) als * auch bei Rechnung (invoicecard) gezeigt. * * Ergänzt addMoreActionsButtons (Mahnung-erstellen-Button) — wird vor * jenem Block ausgegeben. * * @param array $parameters * @param CommonObject $object * @param string $action * @param HookManager $hookmanager * @return int */ public function formObjectOptions($parameters, &$object, &$action, $hookmanager) { global $langs, $db; $contexts = explode(':', $parameters['context'] ?? ''); if (!in_array('invoicecard', $contexts, true) && !in_array('ordercard', $contexts, true)) { return 0; } if (empty($object->socid) && empty($object->fk_soc)) { return 0; } $socid = (int) ($object->socid ?? $object->fk_soc); if ($socid <= 0) { return 0; } $langs->load('mahnung@mahnung'); $info = $this->getBonitaetsInfo($db, $socid); if (empty($info) || $info['anzahl'] <= 0) { return 0; } // Kompakte Warn-Zeile — unaufdringlich, aber sichtbar print '
'; print img_picto('', 'warning', 'class="pictofixedwidth"'); print ''.dol_escape_htmltag($langs->trans('MahnungBonitaetWarnungKurz')).': '; print sprintf( $langs->trans('MahnungBonitaetKundeHat'), (int) $info['anzahl'], price($info['summe']) ); print '
'; return 0; } /** * Liefert Anzahl, Summe und Datum der letzten als 'badcustomer' abandonierten * Rechnungen für einen Kunden. * * @param DoliDB $db * @param int $socid * @return array{anzahl:int, summe:float, letztes_datum:int|null}|null */ private function getBonitaetsInfo($db, $socid) { if ($socid <= 0) { return null; } $sql = "SELECT COUNT(*) AS nb, COALESCE(SUM(total_ttc), 0) AS summe, MAX(date_closing) AS letztes_datum"; $sql .= " FROM ".MAIN_DB_PREFIX."facture"; $sql .= " WHERE fk_soc = ".((int) $socid); $sql .= " AND fk_statut = 3"; $sql .= " AND close_code = 'badcustomer'"; $sql .= " AND entity IN (".getEntity('facture').")"; $resql = $db->query($sql); if (!$resql) { return null; } $obj = $db->fetch_object($resql); $db->free($resql); return array( 'anzahl' => (int) $obj->nb, 'summe' => (float) $obj->summe, 'letztes_datum' => !empty($obj->letztes_datum) ? $db->jdate($obj->letztes_datum) : null, ); } }