fix: Add try-catch to prevent cronjob from hanging

- Wrap updatePrices() in try-catch for fatal errors
- Separate try-catch for GlobalNotify and mail sending
- Prevents SMTP timeouts from blocking the entire cronjob

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-03-10 14:07:48 +01:00
parent c478e5003b
commit af137e7d83

202
class/preisbot.class.php Normal file → Executable file
View file

@ -54,117 +54,129 @@ class PreisBot
{ {
global $conf, $user, $langs; global $conf, $user, $langs;
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; try {
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
$priceSource = getDolGlobalString('PREISBOT_PRICE_SOURCE', 'cheapest'); $priceSource = getDolGlobalString('PREISBOT_PRICE_SOURCE', 'cheapest');
$priceDirection = getDolGlobalString('PREISBOT_PRICE_DIRECTION', 'up_only'); $priceDirection = getDolGlobalString('PREISBOT_PRICE_DIRECTION', 'up_only');
$minMargin = (float) getDolGlobalString('PREISBOT_MIN_MARGIN', 20); $minMargin = (float) getDolGlobalString('PREISBOT_MIN_MARGIN', 20);
$updated = 0; $updated = 0;
$skipped = 0; $skipped = 0;
$errors = 0; $errors = 0;
$priceChanges = array(); // Für Mail-Bericht $priceChanges = array();
// Hole alle Produkte mit gesetztem Gewinnaufschlag (nur zum Verkauf stehende) // Hole alle Produkte mit gesetztem Gewinnaufschlag (nur zum Verkauf stehende)
$sql = "SELECT p.rowid, p.ref, p.label, p.price, pe.preisbot_margin"; $sql = "SELECT p.rowid, p.ref, p.label, p.price, pe.preisbot_margin";
$sql .= " FROM ".$this->db->prefix()."product as p"; $sql .= " FROM ".$this->db->prefix()."product as p";
$sql .= " LEFT JOIN ".$this->db->prefix()."product_extrafields as pe ON pe.fk_object = p.rowid"; $sql .= " LEFT JOIN ".$this->db->prefix()."product_extrafields as pe ON pe.fk_object = p.rowid";
$sql .= " WHERE pe.preisbot_margin IS NOT NULL"; $sql .= " WHERE pe.preisbot_margin IS NOT NULL";
$sql .= " AND pe.preisbot_margin >= ".((float) $minMargin); $sql .= " AND pe.preisbot_margin >= ".((float) $minMargin);
$sql .= " AND p.tosell = 1"; // Nur Produkte die zum Verkauf stehen $sql .= " AND p.tosell = 1";
$sql .= " AND p.entity IN (".getEntity('product').")"; $sql .= " AND p.entity IN (".getEntity('product').")";
$resql = $this->db->query($sql); $resql = $this->db->query($sql);
if (!$resql) { if (!$resql) {
$this->error = $this->db->lasterror(); $this->error = $this->db->lasterror();
return -1; return -1;
}
while ($obj = $this->db->fetch_object($resql)) {
$productId = $obj->rowid;
$currentPrice = (float) $obj->price;
$margin = (float) $obj->preisbot_margin;
// Hole Einkaufspreis basierend auf Einstellung
$buyPrice = $this->getSupplierPrice($productId, $priceSource);
if ($buyPrice <= 0) {
$skipped++;
continue;
} }
// Berechne neuen Verkaufspreis while ($obj = $this->db->fetch_object($resql)) {
$newPrice = $buyPrice * (1 + ($margin / 100)); $productId = $obj->rowid;
$newPrice = round($newPrice, 2); $currentPrice = (float) $obj->price;
$margin = (float) $obj->preisbot_margin;
// Prüfe Preisrichtung // Hole Einkaufspreis basierend auf Einstellung
if ($priceDirection === 'up_only' && $newPrice < $currentPrice) { $buyPrice = $this->getSupplierPrice($productId, $priceSource);
$skipped++;
continue;
}
// Nur aktualisieren wenn Preis sich ändert if ($buyPrice <= 0) {
if (abs($newPrice - $currentPrice) < 0.01) { $skipped++;
$skipped++; continue;
continue; }
}
// Preis aktualisieren // Berechne neuen Verkaufspreis
$product = new Product($this->db); $newPrice = $buyPrice * (1 + ($margin / 100));
$product->fetch($productId); $newPrice = round($newPrice, 2);
// updatePrice($newprice, $newpricebase, $user, $newvat, $newminprice, $level, $newnpr, $newpbq, $ignore_autogen, $localtaxes_array, $newdefaultvatcode, $price_label, $notrigger) // Prüfe Preisrichtung
// Wir aktualisieren NUR den Preis, behalten aber alle anderen Werte bei if ($priceDirection === 'up_only' && $newPrice < $currentPrice) {
$result = $product->updatePrice( $skipped++;
$newPrice, // neuer Preis continue;
$product->price_base_type, // HT oder TTC - BEIBEHALTEN }
$user, // User
$product->tva_tx, // MwSt-Satz - BEIBEHALTEN
$product->price_min, // Mindestpreis - BEIBEHALTEN
0, // Level (Preisstufe)
$product->tva_npr, // NPR - BEIBEHALTEN
0, // price by quantity
0, // ignore autogen
array(), // localtaxes
$product->default_vat_code, // default vat code - BEIBEHALTEN
'Preisbot' // price_label - Bezeichnung der Preisänderung
);
if ($result > 0) { // Nur aktualisieren wenn Preis sich ändert
$updated++; if (abs($newPrice - $currentPrice) < 0.01) {
$diff = $newPrice - $currentPrice; $skipped++;
$diffPercent = $currentPrice > 0 ? round(($diff / $currentPrice) * 100, 2) : 0; continue;
}
$priceChanges[] = array( // Preis aktualisieren
'ref' => $obj->ref, $product = new Product($this->db);
'label' => $obj->label, $product->fetch($productId);
'old_price' => $currentPrice,
'new_price' => $newPrice, $result = $product->updatePrice(
'diff' => $diff, $newPrice,
'diff_percent' => $diffPercent, $product->price_base_type,
'margin' => $margin, $user,
'buy_price' => $buyPrice $product->tva_tx,
$product->price_min,
0,
$product->tva_npr,
0,
0,
array(),
$product->default_vat_code,
'Preisbot'
); );
$this->output .= "Produkt ".$obj->ref.": ".$currentPrice." -> ".$newPrice." EUR (Aufschlag: ".$margin."%)\n"; if ($result > 0) {
} else { $updated++;
$errors++; $diff = $newPrice - $currentPrice;
$this->errors[] = "Fehler bei Produkt ".$obj->ref.": ".$product->error; $diffPercent = $currentPrice > 0 ? round(($diff / $currentPrice) * 100, 2) : 0;
$priceChanges[] = array(
'ref' => $obj->ref,
'label' => $obj->label,
'old_price' => $currentPrice,
'new_price' => $newPrice,
'diff' => $diff,
'diff_percent' => $diffPercent,
'margin' => $margin,
'buy_price' => $buyPrice
);
$this->output .= "Produkt ".$obj->ref.": ".$currentPrice." -> ".$newPrice." EUR (Aufschlag: ".$margin."%)\n";
} else {
$errors++;
$this->errors[] = "Fehler bei Produkt ".$obj->ref.": ".$product->error;
}
} }
$this->output .= "\n--- Zusammenfassung ---\n";
$this->output .= "Aktualisiert: ".$updated."\n";
$this->output .= "Übersprungen: ".$skipped."\n";
$this->output .= "Fehler: ".$errors."\n";
// Benachrichtigung und Mail nur wenn es Änderungen gab
if ($updated > 0) {
try {
$this->sendNotification($priceChanges, $updated, $errors);
} catch (Exception $e) {
$this->output .= "Warnung: GlobalNotify fehlgeschlagen: ".$e->getMessage()."\n";
}
try {
$this->sendMailReport($priceChanges, $updated, $skipped, $errors);
} catch (Exception $e) {
$this->output .= "Warnung: Mail-Versand fehlgeschlagen: ".$e->getMessage()."\n";
}
}
return ($errors > 0) ? -1 : 0;
} catch (Exception $e) {
$this->error = 'PreisBot Exception: '.$e->getMessage();
$this->output .= 'FEHLER: '.$e->getMessage()."\n";
return -1;
} }
$this->output .= "\n--- Zusammenfassung ---\n";
$this->output .= "Aktualisiert: ".$updated."\n";
$this->output .= "Übersprungen: ".$skipped."\n";
$this->output .= "Fehler: ".$errors."\n";
// Benachrichtigung und Mail nur wenn es Änderungen gab
if ($updated > 0) {
$this->sendNotification($priceChanges, $updated, $errors);
$this->sendMailReport($priceChanges, $updated, $skipped, $errors);
}
return ($errors > 0) ? -1 : 0;
} }
/** /**