From 91c1ddd919b9c1edc915b231eef8f45bb2130ca1 Mon Sep 17 00:00:00 2001 From: data Date: Tue, 17 Mar 2026 14:41:22 +0100 Subject: [PATCH] feat: Add history/log tab to admin area - New admin/history.php with two sections: - Cronjob run history with parsed output (updated/skipped/errors) - Price changes from llx_product_price (price_label='Preisbot') - Filter by 30/90/365 days or all - Direct link to product price page - Add "Verlauf" tab to preisbotAdminPrepareHead() - Add translation keys for history page (de_DE + en_US) - Bump version to 1.3 Co-Authored-By: Claude Sonnet 4.6 --- admin/history.php | 230 +++++++++++++++++++++++++++++ core/modules/modPreisBot.class.php | 2 +- langs/de_DE/preisbot.lang | 18 +++ langs/en_US/preisbot.lang | 18 +++ lib/preisbot.lib.php | 5 + 5 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 admin/history.php diff --git a/admin/history.php b/admin/history.php new file mode 100644 index 0000000..58c3675 --- /dev/null +++ b/admin/history.php @@ -0,0 +1,230 @@ + + * + * 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 preisbot/admin/history.php + * \ingroup preisbot + * \brief History page: cronjob runs and price changes + */ + +$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 && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) { + $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php"; +} +if (!$res && file_exists("../../main.inc.php")) { + $res = @include "../../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.'/core/lib/admin.lib.php'; +require_once '../lib/preisbot.lib.php'; + +$langs->loadLangs(array("admin", "preisbot@preisbot")); + +if (!$user->admin) { + accessforbidden(); +} + +$filter = GETPOST('filter', 'aZ09'); +if (empty($filter)) { + $filter = '30'; +} + +/* + * View + */ + +$form = new Form($db); +$title = $langs->trans("PreisBotSetup"); + +llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-preisbot page-admin_history'); + +$linkback = ''.$langs->trans("BackToModuleList").''; +print load_fiche_titre($title, $linkback, 'title_setup'); + +$head = preisbotAdminPrepareHead(); +print dol_get_fiche_head($head, 'history', $title, -1, 'preisbot@preisbot'); + +// ─── Sektion 1: Cronjob-Läufe ──────────────────────────────────────────────── +print '
'; +print '

'.$langs->trans("PreisBotCronRuns").'

'; + +$sql = "SELECT rowid, label, status, processing, datelastrun, datelastresult, lastresult, lastoutput, datenextrun"; +$sql .= " FROM ".MAIN_DB_PREFIX."cronjob"; +$sql .= " WHERE module_name = 'preisbot'"; +$sql .= " ORDER BY datelastrun DESC"; + +$resql = $db->query($sql); +if ($resql && $db->num_rows($resql) > 0) { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + while ($obj = $db->fetch_object($resql)) { + // Parse Zusammenfassung aus lastoutput + $updated = '-'; + $skipped = '-'; + $errors = '-'; + if (!empty($obj->lastoutput)) { + if (preg_match('/Aktualisiert:\s*(\d+)/u', $obj->lastoutput, $m)) { + $updated = $m[1]; + } + if (preg_match('/Übersprungen:\s*(\d+)/u', $obj->lastoutput, $m)) { + $skipped = $m[1]; + } + if (preg_match('/Fehler:\s*(\d+)/u', $obj->lastoutput, $m)) { + $errors = $m[1]; + } + } + + // Status-Badge + if (!empty($obj->processing)) { + $statusLabel = ''.$langs->trans("Running").''; + } elseif (is_null($obj->datelastresult)) { + $statusLabel = ''.$langs->trans("Error").''; + } elseif ($obj->lastresult == 0) { + $statusLabel = ''.$langs->trans("OK").''; + } else { + $statusLabel = ''.$langs->trans("Error").''; + } + + // Kurzausgabe (erste 3 Zeilen) + $shortOutput = ''; + if (!empty($obj->lastoutput)) { + $lines = array_filter(explode("\n", trim($obj->lastoutput))); + $preview = array_slice(array_values($lines), 0, 3); + $shortOutput = ''.nl2br(dol_htmlentities(implode("\n", $preview))).''; + } + + $errorClass = ($errors !== '-' && $errors > 0) ? ' class="error"' : ''; + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + } + + print '
'.$langs->trans("Date").''.$langs->trans("Status").''.$langs->trans("PreisBotUpdated").''.$langs->trans("PreisBotSkipped").''.$langs->trans("Errors").''.$langs->trans("PreisBotLastOutput").'
'.dol_print_date($db->jdate($obj->datelastrun), 'dayhour').''.$statusLabel.''.($updated !== '-' ? $updated : '-').''.$skipped.''.$errors.''.$shortOutput.'
'; +} else { + print '
'.$langs->trans("PreisBotNoCronRuns").'
'; +} +print '
'; + +print '
'; + +// ─── Sektion 2: Preisänderungen ─────────────────────────────────────────────── +print '
'; +print '

'.$langs->trans("PreisBotPriceChanges").'

'; + +// Filter-Auswahl +print '
'; +print '
'; +print $langs->trans("Period").': '; +print ''; +print '
'; +print '
'; + +$sqlFilter = ''; +if ($filter > 0) { + $sqlFilter = " AND pp.date_price > DATE_SUB(NOW(), INTERVAL ".(int)$filter." DAY)"; +} + +$sql = "SELECT pp.rowid, pp.fk_product, pp.date_price, pp.price, pp.price_ttc, pp.tva_tx, pp.price_base_type,"; +$sql .= " p.ref, p.label as product_label,"; +$sql .= " (SELECT pp2.price FROM ".MAIN_DB_PREFIX."product_price pp2"; +$sql .= " WHERE pp2.fk_product = pp.fk_product AND pp2.date_price < pp.date_price"; +$sql .= " ORDER BY pp2.date_price DESC LIMIT 1) as old_price"; +$sql .= " FROM ".MAIN_DB_PREFIX."product_price pp"; +$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = pp.fk_product"; +$sql .= " WHERE pp.price_label = 'Preisbot'"; +$sql .= $sqlFilter; +$sql .= " ORDER BY pp.date_price DESC"; +$sql .= " LIMIT 200"; + +$resql = $db->query($sql); +if ($resql && $db->num_rows($resql) > 0) { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + while ($obj = $db->fetch_object($resql)) { + $newPrice = (float) $obj->price; + $oldPrice = !is_null($obj->old_price) ? (float) $obj->old_price : null; + $diff = !is_null($oldPrice) ? $newPrice - $oldPrice : null; + + if (!is_null($diff)) { + $diffStr = ($diff >= 0 ? '+' : '').number_format($diff, 2, ',', '.').' €'; + $diffClass = $diff > 0 ? ' style="color:green"' : ($diff < 0 ? ' style="color:red"' : ''); + } else { + $diffStr = '-'; + $diffClass = ''; + } + + $productLink = ''.$obj->ref.''; + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + } + + print '
'.$langs->trans("Date").''.$langs->trans("Ref").''.$langs->trans("Label").''.$langs->trans("PreviousPrice").''.$langs->trans("NewPrice").''.$langs->trans("Diff").''.$langs->trans("VAT").'
'.dol_print_date($db->jdate($obj->date_price), 'dayhour').''.$productLink.''.dol_trunc($obj->product_label, 40).''.(!is_null($oldPrice) ? number_format($oldPrice, 2, ',', '.').' €' : '-').''.number_format($newPrice, 2, ',', '.').' €'.$diffStr.''.number_format((float)$obj->tva_tx, 0).' %
'; +} else { + print '
'.$langs->trans("PreisBotNoPriceChanges").'
'; +} +print '
'; + +print dol_get_fiche_end(); +llxFooter(); +$db->close(); diff --git a/core/modules/modPreisBot.class.php b/core/modules/modPreisBot.class.php index 83c7de1..16f2346 100755 --- a/core/modules/modPreisBot.class.php +++ b/core/modules/modPreisBot.class.php @@ -76,7 +76,7 @@ class modPreisBot extends DolibarrModules $this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@preisbot' // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' - $this->version = '1.2'; + $this->version = '1.3'; // Url to the file with your last numberversion of this module //$this->url_last_version = 'http://www.example.com/versionmodule.txt'; diff --git a/langs/de_DE/preisbot.lang b/langs/de_DE/preisbot.lang index e49c867..539cd32 100755 --- a/langs/de_DE/preisbot.lang +++ b/langs/de_DE/preisbot.lang @@ -52,6 +52,24 @@ PreisBotExtrafieldDesc = Tragen Sie bei einem Produkt im Feld "Gewinnaufschlag % # PreisBotUpdatePrices = PreisBot - Verkaufspreise aktualisieren +# +# History / Verlauf +# +PreisBotHistory = Verlauf +PreisBotCronRuns = Cronjob-Läufe +PreisBotPriceChanges = Preisänderungen durch PreisBot +PreisBotNoCronRuns = Noch keine Läufe protokolliert. +PreisBotNoPriceChanges = Noch keine Preisänderungen vorhanden. +PreisBotLastOutput = Letzte Ausgabe +PreisBotUpdated = Aktualisiert +PreisBotSkipped = Übersprungen +Last30Days = Letzte 30 Tage +Last90Days = Letzte 90 Tage +LastYear = Letztes Jahr +PreviousPrice = Alter Preis +NewPrice = Neuer Preis +Period = Zeitraum + # # About Seite # diff --git a/langs/en_US/preisbot.lang b/langs/en_US/preisbot.lang index 6ca85ac..2e0b857 100755 --- a/langs/en_US/preisbot.lang +++ b/langs/en_US/preisbot.lang @@ -52,6 +52,24 @@ PreisBotExtrafieldDesc = Enter a value of 20%% or higher in the "Profit Margin % # PreisBotUpdatePrices = PreisBot - Update Sales Prices +# +# History +# +PreisBotHistory = History +PreisBotCronRuns = Cronjob Runs +PreisBotPriceChanges = Price Changes by PreisBot +PreisBotNoCronRuns = No runs recorded yet. +PreisBotNoPriceChanges = No price changes found. +PreisBotLastOutput = Last Output +PreisBotUpdated = Updated +PreisBotSkipped = Skipped +Last30Days = Last 30 Days +Last90Days = Last 90 Days +LastYear = Last Year +PreviousPrice = Previous Price +NewPrice = New Price +Period = Period + # # About page # diff --git a/lib/preisbot.lib.php b/lib/preisbot.lib.php index a2bae15..942dd7d 100755 --- a/lib/preisbot.lib.php +++ b/lib/preisbot.lib.php @@ -64,6 +64,11 @@ function preisbotAdminPrepareHead() $h++; */ + $head[$h][0] = dol_buildpath("/preisbot/admin/history.php", 1); + $head[$h][1] = $langs->trans("PreisBotHistory"); + $head[$h][2] = 'history'; + $h++; + $head[$h][0] = dol_buildpath("/preisbot/admin/about.php", 1); $head[$h][1] = $langs->trans("About"); $head[$h][2] = 'about';