';
+
+ $isFirst = true;
+ foreach ($suppliers as $supplier) {
+ $isCurrentSupplier = ($supplier['supplier_id'] == $currentSupplierId);
+
+ $rowStyle = '';
+ if ($isCurrentSupplier) {
+ $rowStyle = 'background-color: #e8f4fd;';
+ }
+
+ $html .= '
';
+
+ // Lieferanten-Name mit Stern für günstigsten
+ $html .= '
';
+ if ($isFirst) {
+ $html .= '';
+ }
+ $html .= dol_escape_htmltag($supplier['supplier_name']);
+ if ($isCurrentSupplier) {
+ $html .= ' (aktueller Lieferant)';
+ }
+ $html .= '
';
+
+ // Preis und Mindestmenge
+ $html .= '
';
+ $html .= ''.number_format($supplier['price'], 2, ',', '.').' EUR';
+ $html .= ' · Mindestmenge: '.(int)$supplier['min_qty'].' Stk.';
+ $html .= ' · Art.-Nr: '.dol_escape_htmltag($supplier['ref_fourn']);
+ $html .= '
';
+
+ // Shop-Link Button
+ $html .= '
+
+
+ fk_product;
+
+ $resStock = $this->db->query($sqlStock);
+ $qtyStock = 0;
+ $desiredQty = 0;
+ $alertQty = 0;
+ if ($resStock && $this->db->num_rows($resStock) > 0) {
+ $objStock = $this->db->fetch_object($resStock);
+ $qtyStock = (float) $objStock->stock;
+ $desiredQty = (float) $objStock->desiredstock;
+ $alertQty = (float) $objStock->seuil_stock_alerte;
+ }
+
+ // Lieferanten-Shop-Link Logik (nur für Lieferantenbestellungen)
+ if (!empty($object->socid) && $isSupplierOrder) {
+ $fk_supplier = $object->socid;
+
+ // Alle Lieferanten für dieses Produkt abrufen
+ $allSuppliers = $this->getAllSuppliersForProduct($line->fk_product);
+
+ // Shop-Icon/Modal generieren
+ $lineId = !empty($line->id) ? $line->id : uniqid();
+ $shopLinkHtml = $this->getShopLinkHtml($allSuppliers, $fk_supplier, $line->ref_fourn, $lineId);
+
+ // Lagerbestand-Badge generieren (4 Zustände: rot/orange/grau/grün)
+ $stockBadgeHtml = $this->getStockBadgeHtml($qtyStock, $desiredQty, $alertQty);
+
+ // Neue ref_fourn zusammenbauen:
+ // [Shop-Icon feste Breite] [Lagerbestand feste Breite] [Artikel-Nummer]
+ $newref = '';
+
+ // Shop-Icon mit fester Breite (damit alle untereinander stehen)
+ $newref .= '';
+ if (!empty($shopLinkHtml)) {
+ $newref .= $shopLinkHtml;
+ }
+ $newref .= '';
+
+ // Lagerbestand-Badge mit fester Breite (rechtsbündig für gleichmäßige Ausrichtung)
+ $newref .= '';
+ $newref .= $stockBadgeHtml;
+ $newref .= '';
+
+ // Artikel-Nummer als normaler Text
+ $newref .= '' . dol_escape_htmltag($line->ref_fourn) . '';
+
+ $newref .= '
';
+
+ $line->ref_fourn = $newref;
+
+ } elseif ($isCustomerOrder || $isProposal) {
+ // Kundenauftrag oder Angebot - Shop-Link und Lagerbestand anzeigen
+ // NUR für Produkte, NICHT für Dienstleistungen
+ if (!empty($line->fk_product) && $line->product_type == 0) {
+
+ // Alle Lieferanten für dieses Produkt abrufen
+ $allSuppliers = $this->getAllSuppliersForProduct($line->fk_product);
+
+ // Shop-Icon/Modal generieren (ohne aktuellen Lieferanten, da Kundenauftrag/Angebot)
+ $lineId = !empty($line->id) ? $line->id : uniqid();
+ $shopLinkHtml = $this->getShopLinkHtml($allSuppliers, 0, '', $lineId);
+
+ // Lagerbestand-Badge mit neuem System (4 Zustände: rot/orange/grau/grün)
+ $stockBadgeHtml = $this->getStockBadgeHtml($qtyStock, $desiredQty, $alertQty);
+
+ // Rechtsbündig: [Shop-Icon feste Breite] [Lagerbestand feste Breite]
+ $stockInfo = '';
+
+ // Shop-Icon mit fester Breite
+ $stockInfo .= '';
+ if (!empty($shopLinkHtml)) {
+ $stockInfo .= $shopLinkHtml;
+ }
+ $stockInfo .= '';
+
+ // Lagerbestand-Badge mit fester Breite (rechtsbündig für gleichmäßige Ausrichtung)
+ $stockInfo .= '';
+ $stockInfo .= $stockBadgeHtml;
+ $stockInfo .= '';
+
+ $stockInfo .= '';
+
+ // An Produktlabel anhängen
+ if (isset($line->product_label)) {
+ $line->product_label .= $stockInfo;
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Hook für zusätzliche Buttons auf der Kundenauftrags-Seite
+ * WICHTIG: Dolibarr gibt $hookmanager->resPrint bei addMoreActionsButtons NICHT aus!
+ * Daher verwenden wir JavaScript um den Button dynamisch einzufügen (wie SubtotalTitle)
+ */
+ public function addMoreActionsButtons($parameters, &$object, &$action, $hookmanager)
{
- static $css_added = false;
+ global $db;
- $isSupplierOrder = ($parameters['currentcontext'] == 'ordersuppliercard');
- $isCustomerOrder = ($parameters['currentcontext'] == 'ordercard');
+ $this->debugLog('Hook aufgerufen - context: '.(isset($parameters['currentcontext']) ? $parameters['currentcontext'] : 'nicht gesetzt'), 'addMoreActionsButtons');
+ $this->debugLog('Object element: '.(is_object($object) ? $object->element : 'kein Objekt'), 'addMoreActionsButtons');
+ $this->debugLog('Object id: '.(is_object($object) && isset($object->id) ? $object->id : 'keine ID'), 'addMoreActionsButtons');
- if (!$isSupplierOrder && !$isCustomerOrder) {
+ // Nur für Kundenaufträge (Commande)
+ if (!is_object($object) || $object->element != 'commande') {
+ $this->debugLog('Abbruch: Kein Commande-Objekt', 'addMoreActionsButtons');
return 0;
}
- // CSS nur einmal hinzufügen
- if (!$css_added) {
- ?>
-
- id."
+ AND (product_type = 0 OR fk_product = 0 OR fk_product IS NULL)";
+ $resql = $db->query($sql);
+ $hasLines = false;
+ if ($resql) {
+ $obj = $db->fetch_object($resql);
+ $hasLines = ($obj->nb > 0);
+ $this->debugLog('Produkt-/Freitextzeilen gefunden: '.$obj->nb, 'addMoreActionsButtons');
+ } else {
+ $this->debugLog('SQL Fehler: '.$db->lasterror(), 'addMoreActionsButtons');
}
- $line = $parameters['line'];
+ if ($hasLines) {
+ $buttonUrl = dol_buildpath('/custom/supplierlink3/create_supplier_order.php', 1).'?origin=commande&originid='.$object->id;
- $line = $parameters['line'];
+ // Button per JavaScript einfügen (da Dolibarr resPrint bei diesem Hook nicht ausgibt)
+ print ''."\n";
- // Lagerbestand und Wunschbestand abfragen
- $sqlStock = "SELECT stock, desiredstock
- FROM ".MAIN_DB_PREFIX."product
- WHERE rowid = ".(int)$line->fk_product;
-
- $resStock = $this->db->query($sqlStock);
- $qtyStock = 0;
- $desiredQty = 0;
- if ($resStock && $this->db->num_rows($resStock) > 0) {
- $objStock = $this->db->fetch_object($resStock);
- $qtyStock = (float) $objStock->stock;
- $desiredQty = (float) $objStock->desiredstock;
- }
-
- // Farbe setzen
- $stockColor = 'ffffff';
- if ($qtyStock < 1) {
- $stockColor = 'red'; // ausverkauft
- } elseif ($qtyStock < $desiredQty) {
- $stockColor = 'orange'; // kleiner als Wunschbestand
- }
-
- // Lieferanten-ID aus dem Bestell-Objekt holen
- if (!empty($object->socid) && $isSupplierOrder) {
- $fk_supplier = $object->socid;
-
-
-
- // Extrafeld-Wert vom Lieferanten laden
- $sql = "SELECT shop_url FROM ".MAIN_DB_PREFIX."societe_extrafields";
- $sql .= " WHERE fk_object = ".(int)$fk_supplier;
-
- $resql = $this->db->query($sql);
-
- if ($resql && $this->db->num_rows($resql) > 0) {
- $obj = $this->db->fetch_object($resql);
-
- if (isset($obj->shop_url) && $obj->shop_url !== '' && $obj->shop_url !== null) {
-
- $shop_url = trim($obj->shop_url);
-
- if (!empty($shop_url)) {
-
- // Artikel-Link
- $full_url = rtrim($shop_url, '/') . '/' . $line->ref_fourn;
- $newref = '';
- $newref .= $line->ref_fourn;
- $newref .= '';
-
- // Lagerbestand daneben mit Farbe
- $newref .= '
'.$qtyStock.'';
-
- $line->ref_fourn = $newref;
-
- }
- }
- }
- } elseif ($isCustomerOrder) {
- // NUR für Produkte, NICHT für Dienstleistungen
- if (!empty($line->fk_product) && $line->product_type == 0) {
-
- // Lagerbestand rechtsbündig mit float
- $stockInfo = '
';
- $stockInfo .= '';
- $stockInfo .= ' ' . $qtyStock . '';
- $stockInfo .= '';
-
- // An verschiedene mögliche Felder anhängen
- if (isset($line->product_label)) {
- $line->product_label .= $stockInfo;
- }
-
- }
+ $this->debugLog('Button per JavaScript eingefügt für URL: '.$buttonUrl, 'addMoreActionsButtons');
+ } else {
+ $this->debugLog('Kein Button - keine Produktzeilen', 'addMoreActionsButtons');
}
return 0;
@@ -520,6 +904,11 @@ class ActionsSupplierLink3 extends CommonHookActions
return 0;
}
+ // Prüfen ob Produktkarte aktiviert ist
+ if (!getDolGlobalInt('SUPPLIERLINK3_ENABLE_PRODUCTCARD', 1)) {
+ return 0;
+ }
+
if ($action != 'create' && $action != 'edit') {
$sql = "SELECT DISTINCT pfp.ref_fourn, pfp.quantity, pfp.price, s.nom, se.shop_url
@@ -575,4 +964,113 @@ class ActionsSupplierLink3 extends CommonHookActions
return 0;
}
+
+ /**
+ * Hook für Footer der Liste - JavaScript einfügen das die Stock-Spalte ersetzt
+ * KEINE zusätzliche Spalte - nur die bestehende Stock-Spalte wird modifiziert
+ */
+ public function printFieldListFooter($parameters, &$object, &$action, $hookmanager)
+ {
+ global $db;
+
+ if (strpos($parameters['currentcontext'], 'stockreplenishlist') === false) {
+ return 0;
+ }
+
+ // Prüfen ob Nachbestellung aktiviert ist
+ if (!getDolGlobalInt('SUPPLIERLINK3_ENABLE_REPLENISH', 1)) {
+ return 0;
+ }
+
+ // Alle Produkte mit Shop-URLs abrufen
+ $sql = "SELECT DISTINCT pfp.fk_product,
+ pfp.fk_soc as supplier_id,
+ pfp.ref_fourn,
+ pfp.price,
+ pfp.quantity as min_qty,
+ s.nom as supplier_name,
+ se.shop_url
+ FROM ".MAIN_DB_PREFIX."product_fournisseur_price pfp
+ INNER JOIN ".MAIN_DB_PREFIX."societe s ON s.rowid = pfp.fk_soc
+ LEFT JOIN ".MAIN_DB_PREFIX."societe_extrafields se ON se.fk_object = pfp.fk_soc
+ WHERE se.shop_url IS NOT NULL AND se.shop_url != ''
+ ORDER BY pfp.fk_product, pfp.price ASC";
+
+ $resql = $db->query($sql);
+ $productSuppliers = array();
+ if ($resql) {
+ while ($obj = $db->fetch_object($resql)) {
+ $productId = $obj->fk_product;
+ if (!isset($productSuppliers[$productId])) {
+ $productSuppliers[$productId] = array();
+ }
+ $productSuppliers[$productId][] = array(
+ 'supplier_id' => $obj->supplier_id,
+ 'supplier_name' => $obj->supplier_name,
+ 'ref_fourn' => $obj->ref_fourn,
+ 'price' => $obj->price,
+ 'min_qty' => $obj->min_qty,
+ 'full_url' => rtrim($obj->shop_url, '/').'/'.$obj->ref_fourn
+ );
+ }
+ }
+
+ $suppliersJson = json_encode($productSuppliers);
+ $jsUrl = dol_buildpath('/supplierlink3/js/replenish.js', 1);
+ $shopIcon = getDolGlobalString('SUPPLIERLINK3_SHOP_ICON', 'fas fa-store');
+
+ // Daten und Script direkt in den Footer schreiben (nach der Seite)
+ // Verwende print statt resprints um außerhalb der Tabelle zu landen
+ ?>
+
+ editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@supplierlink3'
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
- $this->version = '1.0';
+ $this->version = '2.1';
// Url to the file with your last numberversion of this module
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
@@ -87,7 +87,7 @@ class modSupplierLink3 extends DolibarrModules
// If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue'
// If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module'
// To use a supported fa-xxx css style of font awesome, use this->picto='xxx'
- $this->picto = 'fa-file';
+ $this->picto = 'fa-shopping-cart';
// Define some features supported by module (triggers, login, substitutions, menus, css, etc...)
$this->module_parts = array(
@@ -120,13 +120,12 @@ class modSupplierLink3 extends DolibarrModules
// Set here all hooks context managed by module. To find available hook context, make a "grep -r '>initHooks(' *" on source code. You can also set hook context to 'all'
/* BEGIN MODULEBUILDER HOOKSCONTEXTS */
'hooks' => array(
- 'data' => array(
- 'ordersuppliercard',
- 'ordercard',
- 'productcard',
- 'productpricecard ',
- ),
- 'entity' => '0',
+ 'ordersuppliercard',
+ 'ordercard',
+ 'propalcard',
+ 'productcard',
+ 'productpricecard',
+ 'stockreplenishlist',
),
/* END MODULEBUILDER HOOKSCONTEXTS */
// Set this to 1 if features of module are opened to external users
@@ -509,8 +508,6 @@ class modSupplierLink3 extends DolibarrModules
'isModEnabled("supplierlink3")' // Enabled condition
);
- print_r($result0);
-
// Permissions
$this->remove($options);
diff --git a/create_supplier_order.php b/create_supplier_order.php
new file mode 100644
index 0000000..24b7152
--- /dev/null
+++ b/create_supplier_order.php
@@ -0,0 +1,466 @@
+
+ *
+ * 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.
+ */
+
+// Load Dolibarr environment
+$res = 0;
+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 && 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.'/commande/class/commande.class.php';
+require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/functions.lib.php';
+
+$langs->loadLangs(array('orders', 'sendings', 'companies', 'suppliers'));
+
+// Parameter
+$origin = GETPOST('origin', 'alpha');
+$originid = GETPOST('originid', 'int');
+$action = GETPOST('action', 'alpha');
+
+// Berechtigungen prüfen
+if (empty($user->rights->fournisseur->commande->creer)) {
+ accessforbidden();
+}
+
+// Kundenauftrag laden
+$objectsrc = new Commande($db);
+$objectsrc->fetch($originid);
+$objectsrc->fetch_lines();
+
+// Kunde laden für Lieferantenreferenz
+$customer = new Societe($db);
+$customer->fetch($objectsrc->socid);
+
+// Lieferantenreferenz Format: "Kundenname - Auftragsnummer / Kundenzeichen"
+$supplier_ref_default = $customer->name.' - '.$objectsrc->ref;
+if (!empty($objectsrc->ref_client)) {
+ $supplier_ref_default .= ' / '.$objectsrc->ref_client;
+}
+
+/*
+ * Actions
+ */
+
+if ($action == 'create_supplier_orders') {
+ $toselect = GETPOST('toselect', 'array');
+ $supplier_id = GETPOST('supplier_id', 'int');
+ $ref_supplier = GETPOST('ref_supplier', 'alpha');
+
+ if (empty($toselect)) {
+ setEventMessages('Bitte wählen Sie mindestens eine Zeile aus.', null, 'errors');
+ } elseif (empty($supplier_id)) {
+ setEventMessages('Bitte wählen Sie einen Lieferanten aus.', null, 'errors');
+ } else {
+ // Lieferantenbestellung erstellen
+ $supplierorder = new CommandeFournisseur($db);
+
+ $supplierorder->socid = $supplier_id;
+ $supplierorder->ref_supplier = !empty($ref_supplier) ? $ref_supplier : $supplier_ref_default;
+ $supplierorder->cond_reglement_id = 0;
+ $supplierorder->mode_reglement_id = 0;
+ $supplierorder->origin = 'commande';
+ $supplierorder->origin_id = $originid;
+
+ $db->begin();
+
+ $result = $supplierorder->create($user);
+
+ if ($result > 0) {
+ // Zeilen hinzufügen
+ $errors = 0;
+ foreach ($toselect as $lineid) {
+ // Zeile aus Kundenauftrag finden
+ $srcline = null;
+ foreach ($objectsrc->lines as $line) {
+ if ($line->id == $lineid) {
+ $srcline = $line;
+ break;
+ }
+ }
+
+ if ($srcline) {
+ $fournprice = 0;
+ $fournref = '';
+ $fournpricerowid = 0;
+
+ // Für Produkte: Günstigsten Lieferantenpreis für diesen Lieferanten finden
+ if (!empty($srcline->fk_product)) {
+ $sql = "SELECT pfp.rowid, pfp.ref_fourn, pfp.price, pfp.quantity as min_qty
+ FROM ".MAIN_DB_PREFIX."product_fournisseur_price pfp
+ WHERE pfp.fk_product = ".(int)$srcline->fk_product."
+ AND pfp.fk_soc = ".(int)$supplier_id."
+ ORDER BY pfp.price ASC
+ LIMIT 1";
+
+ $resql = $db->query($sql);
+
+ if ($resql && $db->num_rows($resql) > 0) {
+ $objprice = $db->fetch_object($resql);
+ $fournprice = $objprice->price;
+ $fournref = $objprice->ref_fourn;
+ $fournpricerowid = $objprice->rowid;
+ }
+ }
+
+ // Beschreibung für Freitext-Zeilen mit Hinweis versehen
+ $desc = $srcline->desc;
+ if (empty($srcline->fk_product)) {
+ $desc = "[FREITEXT - Produkt noch zuordnen]\n".$srcline->desc;
+ }
+
+ // Zeile zur Lieferantenbestellung hinzufügen
+ $result_line = $supplierorder->addline(
+ $desc, // Description
+ $fournprice, // Unit price
+ $srcline->qty, // Quantity
+ $srcline->tva_tx, // VAT rate
+ $srcline->localtax1_tx, // Local tax 1
+ $srcline->localtax2_tx, // Local tax 2
+ $srcline->fk_product, // Product ID (0 für Freitext)
+ $fournpricerowid, // Supplier price ID
+ $fournref, // Supplier ref
+ $srcline->remise_percent, // Discount
+ 'HT', // Price base type
+ 0, // pu_ht_devise
+ $srcline->product_type, // type (0=Produkt, 1=Dienstleistung)
+ 0, // info_bits
+ false, // notrigger
+ null, // date_start
+ null, // date_end
+ 0, // array_options
+ '', // fk_unit
+ 0, // origin
+ 0, // origin_id
+ 1 // rang
+ );
+
+ if ($result_line < 0) {
+ $errors++;
+ }
+ }
+ }
+
+ if ($errors == 0) {
+ $db->commit();
+ setEventMessages('Lieferantenbestellung '.$supplierorder->ref.' wurde erstellt.', null, 'mesgs');
+ header('Location: '.DOL_URL_ROOT.'/fourn/commande/card.php?id='.$supplierorder->id);
+ exit;
+ } else {
+ $db->rollback();
+ setEventMessages('Fehler beim Hinzufügen der Zeilen.', null, 'errors');
+ }
+ } else {
+ $db->rollback();
+ setEventMessages('Fehler beim Erstellen der Lieferantenbestellung: '.$supplierorder->error, null, 'errors');
+ }
+ }
+}
+
+/*
+ * View
+ */
+
+$title = 'Lieferantenbestellung aus Kundenauftrag erstellen';
+llxHeader('', $title);
+
+print load_fiche_titre($title, '', 'order');
+
+// Formular für Auswahl
+print '
';
+
+// JavaScript für "Alle auswählen"
+print '';
+
+// Badge-Styles
+print '';
+
+llxFooter();
+$db->close();
diff --git a/js/replenish.js b/js/replenish.js
new file mode 100644
index 0000000..5feb48b
--- /dev/null
+++ b/js/replenish.js
@@ -0,0 +1,167 @@
+/**
+ * SupplierLink3 - Replenish page enhancements
+ * Ersetzt die "Aktueller Lagerbestand"-Spalte mit Badge + Shop-Link
+ */
+console.log('SL3: replenish.js loaded');
+
+$(document).ready(function() {
+ console.log('SL3: DOM ready, sl3Data exists:', typeof window.sl3Data !== 'undefined');
+
+ if (typeof window.sl3Data === 'undefined') {
+ console.log('SL3: No data found, exiting');
+ return;
+ }
+
+ var productSuppliers = window.sl3Data;
+ var shopIcon = window.sl3ShopIcon || 'fas fa-store';
+ console.log('SL3: Processing', Object.keys(productSuppliers).length, 'products');
+
+ // CSS einfügen
+ $('head').append('');
+
+ // Modal-Handler
+ $(document).on('click', '.sl3-modal-trigger', function(e) {
+ e.preventDefault();
+ var suppliers = $(this).data('suppliers');
+ if (!suppliers || suppliers.length === 0) return;
+
+ var content = '';
+ for (var i = 0; i < suppliers.length; i++) {
+ var sup = suppliers[i];
+ var isFirst = (i === 0);
+ var bgStyle = isFirst ? 'background-color: #e8f4fd;' : '';
+ var star = isFirst ? '
' : '';
+
+ content += '
';
+ content += '
' + star + sup.supplier_name + '
';
+ content += '
';
+ content += '' + parseFloat(sup.price).toFixed(2).replace('.', ',') + ' EUR';
+ content += ' - Art.-Nr: ' + sup.ref_fourn;
+ if (sup.min_qty > 1) content += ' (ab ' + sup.min_qty + ' St.)';
+ content += '
';
+ content += '
Im Shop';
+ content += '
';
+ }
+
+ $('
').html(content).dialog({
+ modal: true,
+ title: 'Lieferanten-Shops',
+ width: 400,
+ buttons: { 'Schließen': function() { $(this).dialog('close'); } }
+ });
+ });
+
+ // Spalten-Indizes aus der Header-Zeile ermitteln
+ var colIndex = { stock: -1, desired: -1, alert: -1 };
+ $('table.liste tr.liste_titre th, table.liste tr.liste_titre td').each(function(idx) {
+ var text = $(this).text().trim().toLowerCase();
+ // Stock-Spalte (Aktueller Lagerbestand / Physical stock / Physischer Bestand)
+ if (text.indexOf('lagerbestand') >= 0 || text.indexOf('physical stock') >= 0 ||
+ text.indexOf('stock physique') >= 0 || text.indexOf('physischer bestand') >= 0) {
+ colIndex.stock = idx;
+ }
+ // Desired Stock (Gewünschter Lagerbestand / Wunschbestand)
+ if (text.indexOf('gewünscht') >= 0 || text.indexOf('wunsch') >= 0 ||
+ text.indexOf('desired') >= 0 || text.indexOf('souhaité') >= 0) {
+ colIndex.desired = idx;
+ }
+ // Alert Stock (Grenzwert für Warnung / Mindestbestand / Stock limit)
+ if (text.indexOf('grenzwert') >= 0 || text.indexOf('warnung') >= 0 ||
+ text.indexOf('mindest') >= 0 || text.indexOf('limit') >= 0 ||
+ text.indexOf('alerte') >= 0 || text.indexOf('alert') >= 0) {
+ colIndex.alert = idx;
+ }
+ });
+
+ console.log('SL3: Column indices found:', colIndex);
+
+ // Fallback auf feste Indizes wenn nicht gefunden (für Produkte ohne Service-Spalte)
+ if (colIndex.stock < 0) colIndex.stock = 5;
+ if (colIndex.desired < 0) colIndex.desired = 3;
+ if (colIndex.alert < 0) colIndex.alert = 4;
+
+ // Jede Datenzeile in der Tabelle durchgehen
+ var rowsFound = 0;
+ var rowsProcessed = 0;
+ $('table.liste tr').not('.liste_titre').each(function() {
+ rowsFound++;
+ var $row = $(this);
+
+ // Produkt-Link finden um die Produkt-ID zu extrahieren
+ var $productLink = $row.find('td a[href*="product/card.php?id="]').first();
+ if ($productLink.length === 0) {
+ // Versuche alternative Selektoren
+ $productLink = $row.find('td a[href*="product.php?id="]').first();
+ }
+ if ($productLink.length === 0) {
+ console.log('SL3: Row', rowsFound, '- no product link found');
+ return;
+ }
+
+ var href = $productLink.attr('href');
+ var match = href.match(/id=(\d+)/);
+ if (!match) {
+ console.log('SL3: Row', rowsFound, '- no ID in href:', href);
+ return;
+ }
+ var productId = match[1];
+ rowsProcessed++;
+
+ // Stock-Spalte dynamisch finden
+ var $stockCell = $row.find('td').eq(colIndex.stock);
+ if ($stockCell.length === 0) return;
+
+ // Stock-Wert aus der Zelle lesen
+ var stock = parseFloat($stockCell.text().trim()) || 0;
+
+ // Desired-Stock und Alert-Stock
+ var desired = parseFloat($row.find('td').eq(colIndex.desired).text().trim()) || 0;
+ var alert = parseFloat($row.find('td').eq(colIndex.alert).text().trim()) || 0;
+
+ // Badge-Klasse bestimmen
+ var badgeClass = 'badge-success';
+ if (stock < 1) {
+ badgeClass = 'badge-danger';
+ } else if (alert > 0 && stock <= alert) {
+ badgeClass = 'badge-warning';
+ } else if (desired > 0 && stock < desired) {
+ badgeClass = 'badge-secondary';
+ }
+
+ // Shop-Link erstellen (immer mit Container für feste Breite)
+ var shopIconHtml = '';
+ var suppliers = productSuppliers[productId];
+ if (suppliers && suppliers.length > 0) {
+ if (suppliers.length === 1) {
+ var sup = suppliers[0];
+ shopIconHtml = '
' +
+ '';
+ } else {
+ shopIconHtml = '
' +
+ '';
+ }
+ }
+
+ // Stock-Zelle ersetzen: inline-flex wie im Kundenauftrag
+ var html = '
' +
+ '' + shopIconHtml + '' +
+ '' + Math.floor(stock) + '' +
+ '
';
+ $stockCell.html(html).addClass('right').css('text-align', 'right');
+ });
+
+ console.log('SL3: Processing complete - rows found:', rowsFound, 'rows processed:', rowsProcessed);
+});
diff --git a/langs/de_DE/supplierlink3.lang b/langs/de_DE/supplierlink3.lang
new file mode 100644
index 0000000..b126cbe
--- /dev/null
+++ b/langs/de_DE/supplierlink3.lang
@@ -0,0 +1,97 @@
+# SupplierLink3 - German translation
+# Copyright (C) 2025 Eduard Wisch
+
+#
+# Generic
+#
+ModuleSupplierLink3Name = SupplierLink3
+ModuleSupplierLink3Desc = Lieferantenverknüpfung mit Shop-Links und Lagerbestand-Anzeige
+
+#
+# Admin page
+#
+SupplierLink3Setup = SupplierLink3 Einstellungen
+Settings = Einstellungen
+SupplierLink3SetupPage = SupplierLink3 Einstellungsseite
+SupplierLink3Description = Zeigt Shop-Links und Lagerbestände in Aufträgen, Angeboten und Bestellungen an
+
+# Settings sections
+SL3_DisplaySettings = Anzeige-Einstellungen
+SL3_DisplaySettingsDesc = Wählen Sie, wo Shop-Links und Lagerbestände angezeigt werden sollen
+SL3_IconSettings = Symbol-Einstellungen
+SL3_IconSettingsDesc = Passen Sie die verwendeten Symbole an
+SL3_DebugSettings = Debug-Einstellungen
+SL3_RegisteredHooks = Registrierte Hooks
+
+# Display areas
+SL3_EnableOrderCard = Kundenaufträge
+SL3_EnableOrderCardDesc = Shop-Links und Lagerbestand in Kundenaufträgen anzeigen
+SL3_EnablePropalCard = Angebote
+SL3_EnablePropalCardDesc = Shop-Links und Lagerbestand in Angeboten anzeigen
+SL3_EnableSupplierOrder = Lieferantenbestellungen
+SL3_EnableSupplierOrderDesc = Shop-Links und Lagerbestand in Lieferantenbestellungen anzeigen
+SL3_EnableReplenish = Nachbestellung (Lager)
+SL3_EnableReplenishDesc = Shop-Links und Lagerbestand in der Nachbestellungsliste anzeigen
+SL3_EnableProductCard = Produktkarte
+SL3_EnableProductCardDesc = Shop-Links auf der Produktkarte anzeigen
+
+# Icons
+SL3_ShopIcon = Shop-Symbol
+SL3_ShopIconDesc = FontAwesome-Klasse für das Shop-Symbol (z.B. fa-store, fa-shopping-cart, fa-external-link-alt)
+SL3_StockIcon = Lagerbestand-Symbol
+SL3_StockIconDesc = FontAwesome-Klasse für das Lagerbestand-Symbol (z.B. fa-box, fa-warehouse, fa-cubes)
+SL3_IconPreview = Vorschau
+
+# Debug
+SL3_DebugMode = Debug-Modus
+SL3_DebugModeDesc = Schreibt Debug-Meldungen in die Log-Datei
+SL3_DebugLog = Debug-Log
+SL3_DebugLogFile = Log-Datei
+SL3_DebugLogSize = Größe
+SL3_DebugLogClear = Log löschen
+SL3_DebugLogCleared = Debug-Log wurde gelöscht
+SL3_DebugLogEmpty = Keine Log-Datei vorhanden. Aktivieren Sie den Debug-Modus und führen Sie eine Aktion aus.
+SL3_DebugLogLastEntries = Letzte Einträge
+
+# Hooks
+SL3_HookOrderSupplier = Lieferantenbestellungen - Shop-Links und Lagerbestand
+SL3_HookOrderCard = Kundenaufträge - Shop-Links, Lagerbestand und Button "Lieferantenbestellung erstellen"
+SL3_HookPropalCard = Angebote - Shop-Links und Lagerbestand
+SL3_HookProductCard = Produktkarte - Shop-Links
+SL3_HookProductPrice = Produktpreise - Shop-Links
+SL3_HookReplenish = Nachbestellung - Shop-Links und Lagerbestand
+
+# Messages
+SetupSaved = Einstellungen gespeichert
+
+#
+# About page
+#
+About = Über
+SupplierLink3About = Über SupplierLink3
+SupplierLink3AboutPage = SupplierLink3 Informationen
+SL3_Version = Version
+SL3_Author = Autor
+SL3_License = Lizenz
+SL3_Features = Funktionen
+SL3_Feature1 = Shop-Links zu Lieferanten-Webshops
+SL3_Feature2 = Lagerbestand-Anzeige mit farbigen Badges
+SL3_Feature3 = Schnelle Lieferantenbestellung aus Kundenaufträgen
+SL3_Feature4 = Multi-Lieferanten-Unterstützung mit Popup-Auswahl
+SL3_Documentation = Dokumentation
+
+#
+# Stock badges
+#
+SL3_StockCritical = Kritisch (< 1)
+SL3_StockWarning = Warnung (unter Mindestbestand)
+SL3_StockLow = Niedrig (unter Wunschbestand)
+SL3_StockOk = Ausreichend
+
+#
+# Supplier order creation
+#
+SL3_CreateSupplierOrder = Lieferantenbestellung erstellen
+SL3_SelectSupplier = Lieferant wählen
+SL3_SelectProducts = Produkte auswählen
+SL3_NoSuppliers = Keine Lieferanten verfügbar