Version 2.1: Freitext-Zeilen Unterstützung für Lieferantenbestellungen
- Freitext-Zeilen aus Kundenaufträgen werden nun angezeigt und übernommen - Neue Lieferanten-Auswahl: Automatisch (passende) / Manuell (alle) - JavaScript-basierte Umschaltung ohne Seiten-Reload - Fallback für Systeme ohne markierte Lieferanten - Button erscheint nun auch bei reinen Freitext-Aufträgen Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b970a66339
commit
209ce1e3f8
5 changed files with 1331 additions and 106 deletions
598
class/actions_supplierlink3.class.php
Executable file → Normal file
598
class/actions_supplierlink3.class.php
Executable file → Normal file
|
|
@ -36,6 +36,16 @@ class ActionsSupplierLink3 extends CommonHookActions
|
||||||
*/
|
*/
|
||||||
public $db;
|
public $db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int Debug mode (0=off, 1=on)
|
||||||
|
*/
|
||||||
|
private $debug = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Debug log file path
|
||||||
|
*/
|
||||||
|
private $debugLogFile = '/tmp/supplierlink3_debug.log';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string Error code (or message)
|
* @var string Error code (or message)
|
||||||
*/
|
*/
|
||||||
|
|
@ -63,6 +73,16 @@ class ActionsSupplierLink3 extends CommonHookActions
|
||||||
public $priority;
|
public $priority;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param DoliDB $db Database handler
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @var string Shop icon class (FontAwesome)
|
||||||
|
*/
|
||||||
|
private $shopIcon = 'fas fa-store';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
|
|
@ -71,6 +91,38 @@ class ActionsSupplierLink3 extends CommonHookActions
|
||||||
public function __construct($db)
|
public function __construct($db)
|
||||||
{
|
{
|
||||||
$this->db = $db;
|
$this->db = $db;
|
||||||
|
|
||||||
|
// Debug-Modus aus Dolibarr-Konfiguration laden (Standard: AUS)
|
||||||
|
$this->debug = getDolGlobalInt('SUPPLIERLINK3_DEBUG_MODE', 0);
|
||||||
|
|
||||||
|
// Shop-Icon aus Konfiguration laden
|
||||||
|
$this->shopIcon = getDolGlobalString('SUPPLIERLINK3_SHOP_ICON', 'fas fa-store');
|
||||||
|
|
||||||
|
// Nur loggen wenn Debug aktiviert
|
||||||
|
if ($this->debug) {
|
||||||
|
error_log('['.date('Y-m-d H:i:s').'] [CONSTRUCTOR] ActionsSupplierLink3 instanziiert'."\n", 3, $this->debugLogFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug-Log schreiben
|
||||||
|
*
|
||||||
|
* @param string $message Nachricht
|
||||||
|
* @param string $method Methodenname
|
||||||
|
*/
|
||||||
|
private function debugLog($message, $method = '')
|
||||||
|
{
|
||||||
|
if (!$this->debug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$logLine = '['.date('Y-m-d H:i:s').']';
|
||||||
|
if ($method) {
|
||||||
|
$logLine .= ' ['.$method.']';
|
||||||
|
}
|
||||||
|
$logLine .= ' '.$message."\n";
|
||||||
|
|
||||||
|
error_log($logLine, 3, $this->debugLogFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -401,116 +453,448 @@ class ActionsSupplierLink3 extends CommonHookActions
|
||||||
return 1;
|
return 1;
|
||||||
}*/
|
}*/
|
||||||
/* Add other hook methods here... */
|
/* Add other hook methods here... */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holt alle Lieferanten mit Shop-URL für ein Produkt
|
||||||
|
*
|
||||||
|
* @param int $fk_product Produkt-ID
|
||||||
|
* @return array Array mit Lieferanten-Daten, sortiert nach Preis aufsteigend
|
||||||
|
*/
|
||||||
|
private function getAllSuppliersForProduct($fk_product)
|
||||||
|
{
|
||||||
|
$suppliers = array();
|
||||||
|
|
||||||
|
$sql = "SELECT DISTINCT
|
||||||
|
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 pfp.fk_product = ".(int)$fk_product."
|
||||||
|
AND se.shop_url IS NOT NULL
|
||||||
|
AND se.shop_url != ''
|
||||||
|
ORDER BY pfp.price ASC";
|
||||||
|
|
||||||
|
$resql = $this->db->query($sql);
|
||||||
|
if ($resql) {
|
||||||
|
while ($obj = $this->db->fetch_object($resql)) {
|
||||||
|
$suppliers[] = array(
|
||||||
|
'supplier_id' => $obj->supplier_id,
|
||||||
|
'ref_fourn' => $obj->ref_fourn,
|
||||||
|
'price' => $obj->price,
|
||||||
|
'min_qty' => $obj->min_qty,
|
||||||
|
'supplier_name' => $obj->supplier_name,
|
||||||
|
'shop_url' => rtrim($obj->shop_url, '/'),
|
||||||
|
'full_url' => rtrim($obj->shop_url, '/') . '/' . $obj->ref_fourn
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $suppliers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generiert HTML-Badge für Lagerbestand
|
||||||
|
*
|
||||||
|
* 4 Zustände:
|
||||||
|
* - ROT: stock <= 0 (Nicht verfügbar)
|
||||||
|
* - ORANGE: stock < seuil_stock_alerte (Unter Mindestmenge)
|
||||||
|
* - GRAU: stock < desiredstock (Unter Wunschmenge)
|
||||||
|
* - GRÜN: stock >= desiredstock (Ausreichend)
|
||||||
|
*
|
||||||
|
* @param float $qtyStock Aktueller Lagerbestand
|
||||||
|
* @param float $desiredQty Wunschbestand
|
||||||
|
* @param float $alertQty Mindestbestand (seuil_stock_alerte)
|
||||||
|
* @return string HTML-Badge
|
||||||
|
*/
|
||||||
|
private function getStockBadgeHtml($qtyStock, $desiredQty, $alertQty = 0)
|
||||||
|
{
|
||||||
|
if ($qtyStock <= 0) {
|
||||||
|
// ROT: Nicht verfügbar (0 oder negativ)
|
||||||
|
$badgeClass = 'badge-danger';
|
||||||
|
$icon = 'fa-times-circle';
|
||||||
|
$tooltip = 'Nicht auf Lager';
|
||||||
|
} elseif ($alertQty > 0 && $qtyStock < $alertQty) {
|
||||||
|
// ORANGE: Unter Mindestmenge (Alarm-Schwelle)
|
||||||
|
$badgeClass = 'badge-warning';
|
||||||
|
$icon = 'fa-exclamation-triangle';
|
||||||
|
$tooltip = 'Unter Mindestmenge ('.(int)$alertQty.')';
|
||||||
|
} elseif ($desiredQty > 0 && $qtyStock < $desiredQty) {
|
||||||
|
// GRAU: Unter Wunschmenge
|
||||||
|
$badgeClass = 'badge-secondary';
|
||||||
|
$icon = 'fa-box-open';
|
||||||
|
$tooltip = 'Unter Wunschmenge ('.(int)$desiredQty.')';
|
||||||
|
} else {
|
||||||
|
// GRÜN: Ausreichend auf Lager
|
||||||
|
$badgeClass = 'badge-success';
|
||||||
|
$icon = 'fa-check-circle';
|
||||||
|
$tooltip = 'Ausreichend auf Lager';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = '<span class="badge '.$badgeClass.' classfortooltip" title="'.dol_escape_htmltag($tooltip).'" style="font-size: 11px;">';
|
||||||
|
$html .= '<i class="fas '.$icon.'" style="margin-right: 4px;"></i>';
|
||||||
|
$html .= number_format($qtyStock, 0, ',', '.');
|
||||||
|
$html .= '</span>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generiert Shop-Link HTML (Icon oder Modal-Trigger für mehrere Lieferanten)
|
||||||
|
*
|
||||||
|
* @param array $suppliers Array der Lieferanten
|
||||||
|
* @param int $currentSupplierId Aktueller Lieferant der Bestellung
|
||||||
|
* @param string $refFourn Lieferanten-Artikelnummer
|
||||||
|
* @param int $lineId Zeilen-ID für eindeutige Modal-ID
|
||||||
|
* @return string HTML für Shop-Link
|
||||||
|
*/
|
||||||
|
private function getShopLinkHtml($suppliers, $currentSupplierId, $refFourn, $lineId)
|
||||||
|
{
|
||||||
|
if (empty($suppliers)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall 1: Nur ein Lieferant mit Shop-URL - direkter Link
|
||||||
|
if (count($suppliers) == 1) {
|
||||||
|
$supplier = $suppliers[0];
|
||||||
|
$html = '<a href="'.dol_escape_htmltag($supplier['full_url']).'" ';
|
||||||
|
$html .= 'target="supplier_'.$supplier['supplier_id'].'" ';
|
||||||
|
$html .= 'class="classfortooltip" ';
|
||||||
|
$html .= 'title="'.dol_escape_htmltag('Im Shop ansehen: '.$supplier['supplier_name']).'" ';
|
||||||
|
$html .= 'style="color: #0077b6; font-size: 14px;">';
|
||||||
|
$html .= '<i class="'.$this->shopIcon.'"></i>';
|
||||||
|
$html .= '</a>';
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall 2: Mehrere Lieferanten - Modal-Trigger
|
||||||
|
$modalId = 'supplier_modal_'.$lineId;
|
||||||
|
|
||||||
|
// Trigger-Button mit Dropdown-Pfeil
|
||||||
|
$html = '<a href="#" class="supplier-modal-trigger classfortooltip" ';
|
||||||
|
$html .= 'data-modal="'.$modalId.'" ';
|
||||||
|
$html .= 'title="'.dol_escape_htmltag('Lieferanten-Shops ('.count($suppliers).')').'" ';
|
||||||
|
$html .= 'style="color: #0077b6; font-size: 14px;">';
|
||||||
|
$html .= '<i class="'.$this->shopIcon.'"></i>';
|
||||||
|
$html .= '<i class="fas fa-caret-down" style="font-size: 8px; margin-left: 2px;"></i>';
|
||||||
|
$html .= '</a>';
|
||||||
|
|
||||||
|
// Modal-Container (versteckt)
|
||||||
|
$html .= '<div id="'.$modalId.'" class="supplier-modal-content" style="display: none;" title="Lieferanten-Shops">';
|
||||||
|
|
||||||
|
$isFirst = true;
|
||||||
|
foreach ($suppliers as $supplier) {
|
||||||
|
$isCurrentSupplier = ($supplier['supplier_id'] == $currentSupplierId);
|
||||||
|
|
||||||
|
$rowStyle = '';
|
||||||
|
if ($isCurrentSupplier) {
|
||||||
|
$rowStyle = 'background-color: #e8f4fd;';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '<div class="supplier-modal-item" style="padding: 10px; border-bottom: 1px solid #eee; '.$rowStyle.'">';
|
||||||
|
|
||||||
|
// Lieferanten-Name mit Stern für günstigsten
|
||||||
|
$html .= '<div style="font-weight: '.($isFirst ? 'bold' : 'normal').'; margin-bottom: 5px;">';
|
||||||
|
if ($isFirst) {
|
||||||
|
$html .= '<i class="fas fa-star" style="color: gold; margin-right: 5px;" title="Günstigster Lieferant"></i>';
|
||||||
|
}
|
||||||
|
$html .= dol_escape_htmltag($supplier['supplier_name']);
|
||||||
|
if ($isCurrentSupplier) {
|
||||||
|
$html .= ' <span style="font-size: 10px; color: #666;">(aktueller Lieferant)</span>';
|
||||||
|
}
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Preis und Mindestmenge
|
||||||
|
$html .= '<div style="font-size: 12px; color: #666; margin-bottom: 8px;">';
|
||||||
|
$html .= '<strong>'.number_format($supplier['price'], 2, ',', '.').' EUR</strong>';
|
||||||
|
$html .= ' · Mindestmenge: '.(int)$supplier['min_qty'].' Stk.';
|
||||||
|
$html .= ' · Art.-Nr: '.dol_escape_htmltag($supplier['ref_fourn']);
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Shop-Link Button
|
||||||
|
$html .= '<a href="'.dol_escape_htmltag($supplier['full_url']).'" ';
|
||||||
|
$html .= 'target="supplier_'.$supplier['supplier_id'].'" ';
|
||||||
|
$html .= 'class="button small" style="font-size: 11px; padding: 4px 10px;">';
|
||||||
|
$html .= '<i class="fas fa-external-link-alt" style="margin-right: 5px;"></i>Im Shop öffnen';
|
||||||
|
$html .= '</a>';
|
||||||
|
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$isFirst = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
public function printObjectLine($parameters, &$object, &$action, $hookmanager)
|
public function printObjectLine($parameters, &$object, &$action, $hookmanager)
|
||||||
{
|
{
|
||||||
static $css_added = false;
|
static $css_js_added = false;
|
||||||
|
|
||||||
$isSupplierOrder = ($parameters['currentcontext'] == 'ordersuppliercard');
|
$isSupplierOrder = ($parameters['currentcontext'] == 'ordersuppliercard');
|
||||||
$isCustomerOrder = ($parameters['currentcontext'] == 'ordercard');
|
$isCustomerOrder = ($parameters['currentcontext'] == 'ordercard');
|
||||||
|
$isProposal = ($parameters['currentcontext'] == 'propalcard');
|
||||||
|
|
||||||
if (!$isSupplierOrder && !$isCustomerOrder) {
|
if (!$isSupplierOrder && !$isCustomerOrder && !$isProposal) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSS nur einmal hinzufügen
|
// Prüfen ob diese Funktion für den jeweiligen Kontext aktiviert ist
|
||||||
if (!$css_added) {
|
if ($isSupplierOrder && !getDolGlobalInt('SUPPLIERLINK3_ENABLE_SUPPLIERORDER', 1)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if ($isCustomerOrder && !getDolGlobalInt('SUPPLIERLINK3_ENABLE_ORDERCARD', 1)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if ($isProposal && !getDolGlobalInt('SUPPLIERLINK3_ENABLE_PROPALCARD', 1)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSS und JavaScript nur einmal hinzufügen
|
||||||
|
if (!$css_js_added) {
|
||||||
?>
|
?>
|
||||||
<style>
|
<style>
|
||||||
/* Verstecke Info-Icons innerhalb der Lagerbestand-Anzeige */
|
/* Verstecke Info-Icons innerhalb der Lagerbestand-Anzeige */
|
||||||
.stock-display-wrapper .fa-info-circle {
|
.stock-display-wrapper .fa-info-circle,
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tablelines tbody .fa-info-circle {
|
#tablelines tbody .fa-info-circle {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Badge-Styling - 4 Zustände */
|
||||||
|
.badge.badge-danger {
|
||||||
|
background-color: #dc3545 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.badge.badge-warning {
|
||||||
|
background-color: #fd7e14 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.badge.badge-secondary {
|
||||||
|
background-color: #6c757d !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.badge.badge-success {
|
||||||
|
background-color: #28a745 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal-Item Styling */
|
||||||
|
.supplier-modal-item:last-child {
|
||||||
|
border-bottom: none !important;
|
||||||
|
}
|
||||||
|
.supplier-modal-item:hover {
|
||||||
|
background-color: #f5f5f5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button Styling */
|
||||||
|
.supplier-modal-item .button.small {
|
||||||
|
background: linear-gradient(to bottom, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #495057;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.supplier-modal-item .button.small:hover {
|
||||||
|
background: linear-gradient(to bottom, #e9ecef 0%, #dee2e6 100%);
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* jQuery UI Dialog Anpassungen */
|
||||||
|
.ui-dialog .supplier-modal-content {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Modal-Dialog bei Klick auf Trigger öffnen
|
||||||
|
$(document).on('click', '.supplier-modal-trigger', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
var modalId = $(this).data('modal');
|
||||||
|
var $modal = $('#' + modalId);
|
||||||
|
|
||||||
|
if ($modal.length) {
|
||||||
|
$modal.dialog({
|
||||||
|
modal: true,
|
||||||
|
width: 450,
|
||||||
|
maxHeight: 400,
|
||||||
|
buttons: {
|
||||||
|
'Schließen': function() {
|
||||||
|
$(this).dialog('close');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
open: function() {
|
||||||
|
// Styling für Dialog-Buttons
|
||||||
|
$(this).parent().find('.ui-dialog-buttonpane button').css({
|
||||||
|
'font-size': '12px',
|
||||||
|
'padding': '6px 15px'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<?php
|
<?php
|
||||||
$css_added = true;
|
$css_js_added = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$line = $parameters['line'];
|
$line = $parameters['line'];
|
||||||
|
|
||||||
$line = $parameters['line'];
|
// Lagerbestand, Wunschbestand und Mindestbestand abfragen
|
||||||
|
$sqlStock = "SELECT stock, desiredstock, seuil_stock_alerte
|
||||||
// Lagerbestand und Wunschbestand abfragen
|
|
||||||
$sqlStock = "SELECT stock, desiredstock
|
|
||||||
FROM ".MAIN_DB_PREFIX."product
|
FROM ".MAIN_DB_PREFIX."product
|
||||||
WHERE rowid = ".(int)$line->fk_product;
|
WHERE rowid = ".(int)$line->fk_product;
|
||||||
|
|
||||||
$resStock = $this->db->query($sqlStock);
|
$resStock = $this->db->query($sqlStock);
|
||||||
$qtyStock = 0;
|
$qtyStock = 0;
|
||||||
$desiredQty = 0;
|
$desiredQty = 0;
|
||||||
|
$alertQty = 0;
|
||||||
if ($resStock && $this->db->num_rows($resStock) > 0) {
|
if ($resStock && $this->db->num_rows($resStock) > 0) {
|
||||||
$objStock = $this->db->fetch_object($resStock);
|
$objStock = $this->db->fetch_object($resStock);
|
||||||
$qtyStock = (float) $objStock->stock;
|
$qtyStock = (float) $objStock->stock;
|
||||||
$desiredQty = (float) $objStock->desiredstock;
|
$desiredQty = (float) $objStock->desiredstock;
|
||||||
|
$alertQty = (float) $objStock->seuil_stock_alerte;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Farbe setzen
|
// Lieferanten-Shop-Link Logik (nur für Lieferantenbestellungen)
|
||||||
$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) {
|
if (!empty($object->socid) && $isSupplierOrder) {
|
||||||
$fk_supplier = $object->socid;
|
$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);
|
||||||
|
|
||||||
// Extrafeld-Wert vom Lieferanten laden
|
// Lagerbestand-Badge generieren (4 Zustände: rot/orange/grau/grün)
|
||||||
$sql = "SELECT shop_url FROM ".MAIN_DB_PREFIX."societe_extrafields";
|
$stockBadgeHtml = $this->getStockBadgeHtml($qtyStock, $desiredQty, $alertQty);
|
||||||
$sql .= " WHERE fk_object = ".(int)$fk_supplier;
|
|
||||||
|
|
||||||
$resql = $this->db->query($sql);
|
// Neue ref_fourn zusammenbauen:
|
||||||
|
// [Shop-Icon feste Breite] [Lagerbestand feste Breite] [Artikel-Nummer]
|
||||||
|
$newref = '<div style="display: inline-flex; align-items: center;">';
|
||||||
|
|
||||||
if ($resql && $this->db->num_rows($resql) > 0) {
|
// Shop-Icon mit fester Breite (damit alle untereinander stehen)
|
||||||
$obj = $this->db->fetch_object($resql);
|
$newref .= '<span class="supplier-shop-col" style="display: inline-block; width: 28px; text-align: center;">';
|
||||||
|
if (!empty($shopLinkHtml)) {
|
||||||
|
$newref .= $shopLinkHtml;
|
||||||
|
}
|
||||||
|
$newref .= '</span>';
|
||||||
|
|
||||||
if (isset($obj->shop_url) && $obj->shop_url !== '' && $obj->shop_url !== null) {
|
// Lagerbestand-Badge mit fester Breite (rechtsbündig für gleichmäßige Ausrichtung)
|
||||||
|
$newref .= '<span class="supplier-stock-col" style="display: inline-block; min-width: 55px; text-align: right; margin-right: 8px;">';
|
||||||
|
$newref .= $stockBadgeHtml;
|
||||||
|
$newref .= '</span>';
|
||||||
|
|
||||||
$shop_url = trim($obj->shop_url);
|
// Artikel-Nummer als normaler Text
|
||||||
|
$newref .= '<span>' . dol_escape_htmltag($line->ref_fourn) . '</span>';
|
||||||
|
|
||||||
if (!empty($shop_url)) {
|
$newref .= '</div>';
|
||||||
|
|
||||||
// Artikel-Link
|
|
||||||
$full_url = rtrim($shop_url, '/') . '/' . $line->ref_fourn;
|
|
||||||
$newref = '<a href="'.$full_url.'" target="_blank" class="classfortooltip" title="'.dol_escape_htmltag('Artikel im Shop ansehen').'">';
|
|
||||||
$newref .= $line->ref_fourn;
|
|
||||||
$newref .= '</a>';
|
|
||||||
|
|
||||||
// Lagerbestand daneben mit Farbe
|
|
||||||
$newref .= ' <span class="fas fa-box-open em080 pictofixedwidth" title="Lagerbestand" style="color: '.$stockColor.'"></span><b> <span style="color: '.$stockColor.'">'.$qtyStock.'</span></b>';
|
|
||||||
|
|
||||||
$line->ref_fourn = $newref;
|
$line->ref_fourn = $newref;
|
||||||
|
|
||||||
}
|
} elseif ($isCustomerOrder || $isProposal) {
|
||||||
}
|
// Kundenauftrag oder Angebot - Shop-Link und Lagerbestand anzeigen
|
||||||
}
|
|
||||||
} elseif ($isCustomerOrder) {
|
|
||||||
// NUR für Produkte, NICHT für Dienstleistungen
|
// NUR für Produkte, NICHT für Dienstleistungen
|
||||||
if (!empty($line->fk_product) && $line->product_type == 0) {
|
if (!empty($line->fk_product) && $line->product_type == 0) {
|
||||||
|
|
||||||
// Lagerbestand rechtsbündig mit float
|
// Alle Lieferanten für dieses Produkt abrufen
|
||||||
$stockInfo = '<span style="float: right; margin-left: 20px;">';
|
$allSuppliers = $this->getAllSuppliersForProduct($line->fk_product);
|
||||||
$stockInfo .= '<span class="fas fa-box-open" style="color: ' . $stockColor . ';"></span>';
|
|
||||||
$stockInfo .= ' <span style="color: ' . $stockColor . '; font-weight: bold;">' . $qtyStock . '</span>';
|
// 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 = '<span style="float: right; margin-left: 15px; display: inline-flex; align-items: center;">';
|
||||||
|
|
||||||
|
// Shop-Icon mit fester Breite
|
||||||
|
$stockInfo .= '<span class="supplier-shop-col" style="display: inline-block; width: 28px; text-align: center;">';
|
||||||
|
if (!empty($shopLinkHtml)) {
|
||||||
|
$stockInfo .= $shopLinkHtml;
|
||||||
|
}
|
||||||
$stockInfo .= '</span>';
|
$stockInfo .= '</span>';
|
||||||
|
|
||||||
// An verschiedene mögliche Felder anhängen
|
// Lagerbestand-Badge mit fester Breite (rechtsbündig für gleichmäßige Ausrichtung)
|
||||||
|
$stockInfo .= '<span class="supplier-stock-col" style="display: inline-block; min-width: 55px; text-align: right;">';
|
||||||
|
$stockInfo .= $stockBadgeHtml;
|
||||||
|
$stockInfo .= '</span>';
|
||||||
|
|
||||||
|
$stockInfo .= '</span>';
|
||||||
|
|
||||||
|
// An Produktlabel anhängen
|
||||||
if (isset($line->product_label)) {
|
if (isset($line->product_label)) {
|
||||||
$line->product_label .= $stockInfo;
|
$line->product_label .= $stockInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
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)
|
||||||
|
{
|
||||||
|
global $db;
|
||||||
|
|
||||||
|
$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');
|
||||||
|
|
||||||
|
// Nur für Kundenaufträge (Commande)
|
||||||
|
if (!is_object($object) || $object->element != 'commande') {
|
||||||
|
$this->debugLog('Abbruch: Kein Commande-Objekt', 'addMoreActionsButtons');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfen ob Produktzeilen oder Freitext-Zeilen vorhanden sind
|
||||||
|
// Freitext-Zeilen haben fk_product = 0 oder NULL
|
||||||
|
$sql = "SELECT COUNT(*) as nb FROM ".MAIN_DB_PREFIX."commandedet
|
||||||
|
WHERE fk_commande = ".(int)$object->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');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hasLines) {
|
||||||
|
$buttonUrl = dol_buildpath('/custom/supplierlink3/create_supplier_order.php', 1).'?origin=commande&originid='.$object->id;
|
||||||
|
|
||||||
|
// Button per JavaScript einfügen (da Dolibarr resPrint bei diesem Hook nicht ausgibt)
|
||||||
|
print '<script>$(document).ready(function() {';
|
||||||
|
print ' if ($(".tabsAction").length > 0 && $("#btnSupplierLink3").length === 0) {';
|
||||||
|
print ' $(".tabsAction").append(\'<a id="btnSupplierLink3" class="butAction" href="'.dol_escape_js($buttonUrl).'"><i class="fas fa-truck-loading" style="margin-right: 5px;"></i>Lieferantenbestellung erstellen</a>\');';
|
||||||
|
print ' }';
|
||||||
|
print '});</script>'."\n";
|
||||||
|
|
||||||
|
$this->debugLog('Button per JavaScript eingefügt für URL: '.$buttonUrl, 'addMoreActionsButtons');
|
||||||
|
} else {
|
||||||
|
$this->debugLog('Kein Button - keine Produktzeilen', 'addMoreActionsButtons');
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public function formObjectOptions($parameters, &$object, &$action)
|
public function formObjectOptions($parameters, &$object, &$action)
|
||||||
{
|
{
|
||||||
global $db;
|
global $db;
|
||||||
|
|
@ -520,6 +904,11 @@ class ActionsSupplierLink3 extends CommonHookActions
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prüfen ob Produktkarte aktiviert ist
|
||||||
|
if (!getDolGlobalInt('SUPPLIERLINK3_ENABLE_PRODUCTCARD', 1)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if ($action != 'create' && $action != 'edit') {
|
if ($action != 'create' && $action != 'edit') {
|
||||||
|
|
||||||
$sql = "SELECT DISTINCT pfp.ref_fourn, pfp.quantity, pfp.price, s.nom, se.shop_url
|
$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;
|
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
|
||||||
|
?>
|
||||||
|
<script>
|
||||||
|
window.sl3Data = <?php echo $suppliersJson; ?>;
|
||||||
|
window.sl3JsUrl = "<?php echo $jsUrl; ?>";
|
||||||
|
window.sl3ShopIcon = "<?php echo dol_escape_js($shopIcon); ?>";
|
||||||
|
console.log("SL3: Init data loaded for", Object.keys(window.sl3Data).length, "products");
|
||||||
|
|
||||||
|
// Externes Script laden wenn DOM fertig
|
||||||
|
(function() {
|
||||||
|
var loadScript = function() {
|
||||||
|
var s = document.createElement("script");
|
||||||
|
s.src = window.sl3JsUrl + "?v=" + Date.now();
|
||||||
|
document.body.appendChild(s);
|
||||||
|
console.log("SL3: Loading external script", s.src);
|
||||||
|
};
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", loadScript);
|
||||||
|
} else {
|
||||||
|
loadScript();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook für Zeilenwerte - KEIN Output, keine zusätzliche Spalte
|
||||||
|
*/
|
||||||
|
public function printFieldListValue($parameters, &$object, &$action, $hookmanager)
|
||||||
|
{
|
||||||
|
// Kein Output - wir ersetzen die bestehende Stock-Spalte per JavaScript
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook für Spaltenüberschrift - KEIN Output, keine zusätzliche Spalte
|
||||||
|
*/
|
||||||
|
public function printFieldListTitle($parameters, &$object, &$action, $hookmanager)
|
||||||
|
{
|
||||||
|
// Kein Output
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook für Filter-Zeile - KEIN Output, keine zusätzliche Spalte
|
||||||
|
*/
|
||||||
|
public function printFieldListOption($parameters, &$object, &$action, $hookmanager)
|
||||||
|
{
|
||||||
|
// Kein Output
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
core/modules/modSupplierLink3.class.php
Executable file → Normal file
13
core/modules/modSupplierLink3.class.php
Executable file → Normal file
|
|
@ -76,7 +76,7 @@ class modSupplierLink3 extends DolibarrModules
|
||||||
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@supplierlink3'
|
$this->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'
|
// 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
|
// Url to the file with your last numberversion of this module
|
||||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
//$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 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'
|
// 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'
|
// 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...)
|
// Define some features supported by module (triggers, login, substitutions, menus, css, etc...)
|
||||||
$this->module_parts = array(
|
$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'
|
// 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 */
|
/* BEGIN MODULEBUILDER HOOKSCONTEXTS */
|
||||||
'hooks' => array(
|
'hooks' => array(
|
||||||
'data' => array(
|
|
||||||
'ordersuppliercard',
|
'ordersuppliercard',
|
||||||
'ordercard',
|
'ordercard',
|
||||||
|
'propalcard',
|
||||||
'productcard',
|
'productcard',
|
||||||
'productpricecard ',
|
'productpricecard',
|
||||||
),
|
'stockreplenishlist',
|
||||||
'entity' => '0',
|
|
||||||
),
|
),
|
||||||
/* END MODULEBUILDER HOOKSCONTEXTS */
|
/* END MODULEBUILDER HOOKSCONTEXTS */
|
||||||
// Set this to 1 if features of module are opened to external users
|
// 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
|
'isModEnabled("supplierlink3")' // Enabled condition
|
||||||
);
|
);
|
||||||
|
|
||||||
print_r($result0);
|
|
||||||
|
|
||||||
// Permissions
|
// Permissions
|
||||||
$this->remove($options);
|
$this->remove($options);
|
||||||
|
|
||||||
|
|
|
||||||
466
create_supplier_order.php
Normal file
466
create_supplier_order.php
Normal file
|
|
@ -0,0 +1,466 @@
|
||||||
|
<?php
|
||||||
|
/* Copyright (C) 2025 Eduard Wisch <data@data-it-solution.de>
|
||||||
|
*
|
||||||
|
* 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 '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';
|
||||||
|
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||||
|
print '<input type="hidden" name="action" value="create_supplier_orders">';
|
||||||
|
print '<input type="hidden" name="origin" value="'.$origin.'">';
|
||||||
|
print '<input type="hidden" name="originid" value="'.$originid.'">';
|
||||||
|
|
||||||
|
// Zwei Boxen nebeneinander
|
||||||
|
print '<div class="fichecenter">';
|
||||||
|
|
||||||
|
// Linke Box: Kundenauftrag-Info
|
||||||
|
print '<div class="fichehalfleft">';
|
||||||
|
print '<div class="underbanner clearboth"></div>';
|
||||||
|
print '<table class="border centpercent tableforfield">';
|
||||||
|
print '<tr class="liste_titre"><th colspan="2">Kundenauftrag</th></tr>';
|
||||||
|
|
||||||
|
print '<tr class="oddeven"><td class="titlefield">'.$langs->trans("Ref").'</td>';
|
||||||
|
print '<td>'.$objectsrc->getNomUrl(1).'</td></tr>';
|
||||||
|
|
||||||
|
print '<tr class="oddeven"><td>'.$langs->trans("Customer").'</td>';
|
||||||
|
print '<td>'.$customer->getNomUrl(1).'</td></tr>';
|
||||||
|
|
||||||
|
print '<tr class="oddeven"><td>'.$langs->trans("RefCustomer").'</td>';
|
||||||
|
print '<td>'.dol_escape_htmltag($objectsrc->ref_client).'</td></tr>';
|
||||||
|
|
||||||
|
print '<tr class="oddeven"><td>'.$langs->trans("Status").'</td>';
|
||||||
|
print '<td>'.$objectsrc->getLibStatut(4).'</td></tr>';
|
||||||
|
|
||||||
|
print '</table>';
|
||||||
|
print '</div>';
|
||||||
|
|
||||||
|
// Rechte Box: Lieferant und Referenz
|
||||||
|
print '<div class="fichehalfright">';
|
||||||
|
print '<div class="underbanner clearboth"></div>';
|
||||||
|
print '<table class="border centpercent tableforfield">';
|
||||||
|
print '<tr class="liste_titre"><th colspan="2">Neue Lieferantenbestellung</th></tr>';
|
||||||
|
|
||||||
|
// Prüfen ob Produkte im Auftrag sind
|
||||||
|
$sqlCheckProducts = "SELECT COUNT(*) as nb FROM ".MAIN_DB_PREFIX."commandedet
|
||||||
|
WHERE fk_commande = ".(int)$originid." AND fk_product > 0";
|
||||||
|
$resCheckProducts = $db->query($sqlCheckProducts);
|
||||||
|
$hasProducts = false;
|
||||||
|
if ($resCheckProducts) {
|
||||||
|
$objCheck = $db->fetch_object($resCheckProducts);
|
||||||
|
$hasProducts = ($objCheck->nb > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatische Lieferanten: Nur die, die Produkte aus diesem Auftrag liefern können
|
||||||
|
$autoSuppliers = array();
|
||||||
|
if ($hasProducts) {
|
||||||
|
$sql = "SELECT DISTINCT s.rowid, s.nom
|
||||||
|
FROM ".MAIN_DB_PREFIX."societe s
|
||||||
|
INNER JOIN ".MAIN_DB_PREFIX."product_fournisseur_price pfp ON pfp.fk_soc = s.rowid
|
||||||
|
WHERE pfp.fk_product IN (
|
||||||
|
SELECT cd.fk_product FROM ".MAIN_DB_PREFIX."commandedet cd
|
||||||
|
WHERE cd.fk_commande = ".(int)$originid." AND cd.fk_product > 0
|
||||||
|
)
|
||||||
|
AND s.fournisseur = 1
|
||||||
|
ORDER BY s.nom ASC";
|
||||||
|
$resql = $db->query($sql);
|
||||||
|
if ($resql) {
|
||||||
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
|
$autoSuppliers[$obj->rowid] = $obj->nom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manuelle Lieferanten: Alle Lieferanten
|
||||||
|
$manualSuppliers = array();
|
||||||
|
$sql = "SELECT s.rowid, s.nom
|
||||||
|
FROM ".MAIN_DB_PREFIX."societe s
|
||||||
|
WHERE s.fournisseur > 0
|
||||||
|
ORDER BY s.nom ASC";
|
||||||
|
$resql = $db->query($sql);
|
||||||
|
if ($resql) {
|
||||||
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
|
$manualSuppliers[$obj->rowid] = $obj->nom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn keine automatischen Lieferanten, Standard auf manuell
|
||||||
|
$defaultMode = !empty($autoSuppliers) ? 'auto' : 'manual';
|
||||||
|
|
||||||
|
// Auswahl-Modus
|
||||||
|
print '<tr class="oddeven"><td class="titlefield">Lieferanten-Auswahl</td>';
|
||||||
|
print '<td>';
|
||||||
|
print '<label style="margin-right: 15px;"><input type="radio" name="supplier_mode" value="auto" id="mode_auto"'.($defaultMode == 'auto' ? ' checked' : '').(!empty($autoSuppliers) ? '' : ' disabled').'> Automatisch <span class="opacitymedium">('.count($autoSuppliers).' passende)</span></label>';
|
||||||
|
print '<label><input type="radio" name="supplier_mode" value="manual" id="mode_manual"'.($defaultMode == 'manual' ? ' checked' : '').'> Manuell <span class="opacitymedium">(alle '.count($manualSuppliers).' Lieferanten)</span></label>';
|
||||||
|
print '</td></tr>';
|
||||||
|
|
||||||
|
print '<tr class="oddeven"><td class="titlefield fieldrequired">Lieferant</td>';
|
||||||
|
print '<td><select name="supplier_id" id="supplier_id" class="flat maxwidth300" required>';
|
||||||
|
print '<option value="">-- Lieferant wählen --</option>';
|
||||||
|
// Beide Listen als data-Attribute für JavaScript speichern
|
||||||
|
print '</select>';
|
||||||
|
|
||||||
|
// Hidden divs mit den Optionen für JavaScript
|
||||||
|
print '<div id="autoSupplierOptions" style="display:none;">';
|
||||||
|
foreach ($autoSuppliers as $id => $name) {
|
||||||
|
print '<span data-id="'.$id.'">'.dol_escape_htmltag($name).'</span>';
|
||||||
|
}
|
||||||
|
print '</div>';
|
||||||
|
print '<div id="manualSupplierOptions" style="display:none;">';
|
||||||
|
foreach ($manualSuppliers as $id => $name) {
|
||||||
|
print '<span data-id="'.$id.'">'.dol_escape_htmltag($name).'</span>';
|
||||||
|
}
|
||||||
|
print '</div>';
|
||||||
|
|
||||||
|
print '</td></tr>';
|
||||||
|
|
||||||
|
// JavaScript für Modus-Umschaltung
|
||||||
|
print '<script type="text/javascript">
|
||||||
|
function updateSupplierDropdown() {
|
||||||
|
var mode = document.querySelector(\'input[name="supplier_mode"]:checked\').value;
|
||||||
|
var select = document.getElementById("supplier_id");
|
||||||
|
var sourceDiv = (mode == "auto") ? document.getElementById("autoSupplierOptions") : document.getElementById("manualSupplierOptions");
|
||||||
|
|
||||||
|
// Dropdown leeren
|
||||||
|
select.innerHTML = \'<option value="">-- Lieferant wählen --</option>\';
|
||||||
|
|
||||||
|
// Optionen aus dem entsprechenden div laden
|
||||||
|
var spans = sourceDiv.getElementsByTagName("span");
|
||||||
|
for (var i = 0; i < spans.length; i++) {
|
||||||
|
var option = document.createElement("option");
|
||||||
|
option.value = spans[i].getAttribute("data-id");
|
||||||
|
option.text = spans[i].textContent;
|
||||||
|
select.appendChild(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event-Listener für Radio-Buttons
|
||||||
|
document.getElementById("mode_auto").addEventListener("change", updateSupplierDropdown);
|
||||||
|
document.getElementById("mode_manual").addEventListener("change", updateSupplierDropdown);
|
||||||
|
|
||||||
|
// Initial laden
|
||||||
|
document.addEventListener("DOMContentLoaded", updateSupplierDropdown);
|
||||||
|
</script>';
|
||||||
|
|
||||||
|
print '<tr class="oddeven"><td>Lieferanten-Referenz</td>';
|
||||||
|
print '<td><input type="text" name="ref_supplier" value="'.dol_escape_htmltag($supplier_ref_default).'" class="flat quatrevingtpercent" placeholder="Kunde - Auftrag / Zeichen">';
|
||||||
|
print '</td></tr>';
|
||||||
|
|
||||||
|
print '</table>';
|
||||||
|
print '</div>'; // Ende fichehalfright
|
||||||
|
|
||||||
|
print '</div>'; // Ende fichecenter
|
||||||
|
print '<div class="clearboth"></div>';
|
||||||
|
|
||||||
|
print '<br>';
|
||||||
|
|
||||||
|
// Zeilen-Auswahl mit Checkboxen
|
||||||
|
print '<div class="div-table-responsive-no-min">';
|
||||||
|
print '<table class="noborder centpercent" id="tablelines">';
|
||||||
|
print '<tr class="liste_titre">';
|
||||||
|
print '<th class="center" style="width: 30px;"><input type="checkbox" id="checkall" onclick="toggleAll(this)"></th>';
|
||||||
|
print '<th>Produkt</th>';
|
||||||
|
print '<th class="right">Menge</th>';
|
||||||
|
print '<th class="right">Lagerbestand</th>';
|
||||||
|
print '<th colspan="2">Lieferanten</th>';
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
|
// Zeilen aus Kundenauftrag auflisten (Produkte und Freitext)
|
||||||
|
foreach ($objectsrc->lines as $line) {
|
||||||
|
// Dienstleistungen überspringen (nur Produkte und Freitext)
|
||||||
|
if (!empty($line->fk_product) && $line->product_type != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isFreitext = empty($line->fk_product);
|
||||||
|
$product = null;
|
||||||
|
$qtyStock = 0;
|
||||||
|
$stockClass = '';
|
||||||
|
$supplierList = array();
|
||||||
|
|
||||||
|
if (!$isFreitext) {
|
||||||
|
// Produkt laden
|
||||||
|
$product = new Product($db);
|
||||||
|
$product->fetch($line->fk_product);
|
||||||
|
|
||||||
|
// Lagerbestand
|
||||||
|
$qtyStock = $product->stock_reel;
|
||||||
|
|
||||||
|
// Stock-Badge Farbe
|
||||||
|
$stockClass = 'badge-success';
|
||||||
|
if ($qtyStock <= 0) {
|
||||||
|
$stockClass = 'badge-danger';
|
||||||
|
} elseif ($product->seuil_stock_alerte > 0 && $qtyStock < $product->seuil_stock_alerte) {
|
||||||
|
$stockClass = 'badge-warning';
|
||||||
|
} elseif ($product->desiredstock > 0 && $qtyStock < $product->desiredstock) {
|
||||||
|
$stockClass = 'badge-secondary';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lieferanten für dieses Produkt
|
||||||
|
$sql_suppliers = "SELECT pfp.fk_soc, pfp.price, pfp.ref_fourn, s.nom
|
||||||
|
FROM ".MAIN_DB_PREFIX."product_fournisseur_price pfp
|
||||||
|
INNER JOIN ".MAIN_DB_PREFIX."societe s ON s.rowid = pfp.fk_soc
|
||||||
|
WHERE pfp.fk_product = ".(int)$line->fk_product."
|
||||||
|
ORDER BY pfp.price ASC";
|
||||||
|
$ressuppliers = $db->query($sql_suppliers);
|
||||||
|
if ($ressuppliers) {
|
||||||
|
while ($objsup = $db->fetch_object($ressuppliers)) {
|
||||||
|
$supplierList[] = dol_escape_htmltag($objsup->nom).' ('.number_format($objsup->price, 2, ',', '.').' EUR)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print '<tr class="oddeven'.($isFreitext ? ' freitext-row' : '').'">';
|
||||||
|
|
||||||
|
// Checkbox
|
||||||
|
print '<td class="center">';
|
||||||
|
print '<input type="checkbox" name="toselect[]" value="'.$line->id.'" class="linecheckbox">';
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
// Produkt oder Freitext
|
||||||
|
print '<td>';
|
||||||
|
if ($isFreitext) {
|
||||||
|
print '<span class="badge badge-info" style="margin-right: 5px;">FREITEXT</span>';
|
||||||
|
// Beschreibung anzeigen (gekürzt wenn zu lang)
|
||||||
|
$desc = dol_string_nohtmltag($line->desc);
|
||||||
|
if (strlen($desc) > 100) {
|
||||||
|
$desc = substr($desc, 0, 100).'...';
|
||||||
|
}
|
||||||
|
print '<em>'.dol_escape_htmltag($desc).'</em>';
|
||||||
|
} else {
|
||||||
|
print $product->getNomUrl(1);
|
||||||
|
print ' - '.dol_escape_htmltag($product->label);
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
// Menge
|
||||||
|
print '<td class="right">'.(int)$line->qty.'</td>';
|
||||||
|
|
||||||
|
// Lagerbestand
|
||||||
|
print '<td class="right">';
|
||||||
|
if ($isFreitext) {
|
||||||
|
print '<span class="opacitymedium">-</span>';
|
||||||
|
} else {
|
||||||
|
print '<span class="badge '.$stockClass.'">'.(int)$qtyStock.'</span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
// Lieferanten
|
||||||
|
print '<td>';
|
||||||
|
if ($isFreitext) {
|
||||||
|
print '<span class="opacitymedium">Produkt manuell zuordnen</span>';
|
||||||
|
} elseif (!empty($supplierList)) {
|
||||||
|
print implode('<br>', $supplierList);
|
||||||
|
} else {
|
||||||
|
print '<span class="opacitymedium">Keine Lieferanten</span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
print '</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
print '</table>';
|
||||||
|
print '</div>';
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
print '<div class="center" style="margin-top: 20px;">';
|
||||||
|
print '<input type="submit" class="button button-save" value="Lieferantenbestellung erstellen">';
|
||||||
|
print ' ';
|
||||||
|
print '<a class="button button-cancel" href="'.DOL_URL_ROOT.'/commande/card.php?id='.$originid.'">Abbrechen</a>';
|
||||||
|
print '</div>';
|
||||||
|
|
||||||
|
print '</form>';
|
||||||
|
|
||||||
|
// JavaScript für "Alle auswählen"
|
||||||
|
print '<script type="text/javascript">
|
||||||
|
function toggleAll(checkbox) {
|
||||||
|
var checkboxes = document.getElementsByClassName("linecheckbox");
|
||||||
|
for (var i = 0; i < checkboxes.length; i++) {
|
||||||
|
checkboxes[i].checked = checkbox.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>';
|
||||||
|
|
||||||
|
// Badge-Styles
|
||||||
|
print '<style>
|
||||||
|
.badge.badge-danger { background-color: #dc3545 !important; color: #fff !important; }
|
||||||
|
.badge.badge-warning { background-color: #fd7e14 !important; color: #fff !important; }
|
||||||
|
.badge.badge-secondary { background-color: #6c757d !important; color: #fff !important; }
|
||||||
|
.badge.badge-success { background-color: #28a745 !important; color: #fff !important; }
|
||||||
|
.badge.badge-info { background-color: #17a2b8 !important; color: #fff !important; }
|
||||||
|
.freitext-row { background-color: #fff3cd !important; }
|
||||||
|
</style>';
|
||||||
|
|
||||||
|
llxFooter();
|
||||||
|
$db->close();
|
||||||
167
js/replenish.js
Normal file
167
js/replenish.js
Normal file
|
|
@ -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('<style>' +
|
||||||
|
'.badge.badge-danger { background-color: #dc3545 !important; color: #fff !important; }' +
|
||||||
|
'.badge.badge-warning { background-color: #fd7e14 !important; color: #fff !important; }' +
|
||||||
|
'.badge.badge-secondary { background-color: #6c757d !important; color: #fff !important; }' +
|
||||||
|
'.badge.badge-success { background-color: #28a745 !important; color: #fff !important; }' +
|
||||||
|
'.sl3-stock-wrapper { display: inline-flex; align-items: center; justify-content: flex-end; }' +
|
||||||
|
'.sl3-shop-col { display: inline-block; width: 28px; text-align: center; }' +
|
||||||
|
'.sl3-badge-col { display: inline-block; min-width: 55px; text-align: right; }' +
|
||||||
|
'.sl3-modal-item { padding: 10px; border-bottom: 1px solid #eee; }' +
|
||||||
|
'.sl3-modal-item:hover { background-color: #f5f5f5; }' +
|
||||||
|
'.sl3-modal-item:last-child { border-bottom: none; }' +
|
||||||
|
'</style>');
|
||||||
|
|
||||||
|
// 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 ? '<i class="fas fa-star" style="color: gold; margin-right: 5px;"></i>' : '';
|
||||||
|
|
||||||
|
content += '<div class="sl3-modal-item" style="' + bgStyle + '">';
|
||||||
|
content += '<div style="font-weight: ' + (isFirst ? 'bold' : 'normal') + '; margin-bottom: 5px;">' + star + sup.supplier_name + '</div>';
|
||||||
|
content += '<div style="font-size: 12px; color: #666; margin-bottom: 8px;">';
|
||||||
|
content += '<strong>' + parseFloat(sup.price).toFixed(2).replace('.', ',') + ' EUR</strong>';
|
||||||
|
content += ' - Art.-Nr: ' + sup.ref_fourn;
|
||||||
|
if (sup.min_qty > 1) content += ' (ab ' + sup.min_qty + ' St.)';
|
||||||
|
content += '</div>';
|
||||||
|
content += '<a href="' + sup.full_url + '" target="supplier_' + sup.supplier_id + '" class="button small">Im Shop</a>';
|
||||||
|
content += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$('<div>').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 = '<a href="' + sup.full_url + '" target="supplier_' + sup.supplier_id + '" ' +
|
||||||
|
'class="classfortooltip" title="' + sup.supplier_name + '" style="color: #0077b6; font-size: 14px;">' +
|
||||||
|
'<i class="' + shopIcon + '"></i></a>';
|
||||||
|
} else {
|
||||||
|
shopIconHtml = '<a href="#" class="sl3-modal-trigger" data-suppliers=\'' + JSON.stringify(suppliers) + '\' ' +
|
||||||
|
'title="' + suppliers.length + ' Lieferanten" style="color: #0077b6; font-size: 14px;">' +
|
||||||
|
'<i class="' + shopIcon + '"></i><i class="fas fa-caret-down" style="font-size:8px;margin-left:2px;"></i></a>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stock-Zelle ersetzen: inline-flex wie im Kundenauftrag
|
||||||
|
var html = '<div class="sl3-stock-wrapper">' +
|
||||||
|
'<span class="sl3-shop-col">' + shopIconHtml + '</span>' +
|
||||||
|
'<span class="sl3-badge-col"><span class="badge ' + badgeClass + '">' + Math.floor(stock) + '</span></span>' +
|
||||||
|
'</div>';
|
||||||
|
$stockCell.html(html).addClass('right').css('text-align', 'right');
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('SL3: Processing complete - rows found:', rowsFound, 'rows processed:', rowsProcessed);
|
||||||
|
});
|
||||||
97
langs/de_DE/supplierlink3.lang
Normal file
97
langs/de_DE/supplierlink3.lang
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
# SupplierLink3 - German translation
|
||||||
|
# Copyright (C) 2025 Eduard Wisch <data@data-it-solution.de>
|
||||||
|
|
||||||
|
#
|
||||||
|
# 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
|
||||||
Loading…
Reference in a new issue