Lager. Shoplink angepasst neuer Tab danach immer im selben Tab. Kundenauftrag Button für Bestellung aus dem Entwurf heraus
1076 lines
38 KiB
PHP
Executable file
1076 lines
38 KiB
PHP
Executable file
<?php
|
|
/* Copyright (C) 2023 Laurent Destailleur <eldy@users.sourceforge.net>
|
|
* 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* \file supplierlink3/class/actions_supplierlink3.class.php
|
|
* \ingroup supplierlink3
|
|
* \brief Example hook overload.
|
|
*
|
|
* TODO: Write detailed description here.
|
|
*/
|
|
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/commonhookactions.class.php';
|
|
|
|
/**
|
|
* Class ActionsSupplierLink3
|
|
*/
|
|
class ActionsSupplierLink3 extends CommonHookActions
|
|
{
|
|
/**
|
|
* @var DoliDB Database handler.
|
|
*/
|
|
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)
|
|
*/
|
|
public $error = '';
|
|
|
|
/**
|
|
* @var string[] Errors
|
|
*/
|
|
public $errors = array();
|
|
|
|
|
|
/**
|
|
* @var mixed[] Hook results. Propagated to $hookmanager->resArray for later reuse
|
|
*/
|
|
public $results = array();
|
|
|
|
/**
|
|
* @var ?string String displayed by executeHook() immediately after return
|
|
*/
|
|
public $resprints;
|
|
|
|
/**
|
|
* @var int Priority of hook (50 is used if value is not defined)
|
|
*/
|
|
public $priority;
|
|
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param DoliDB $db Database handler
|
|
*/
|
|
/**
|
|
* @var string Shop icon class (FontAwesome)
|
|
*/
|
|
private $shopIcon = 'fas fa-store';
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param DoliDB $db Database handler
|
|
*/
|
|
public function __construct($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);
|
|
}
|
|
|
|
|
|
/**
|
|
* Execute action
|
|
*
|
|
* @param array<string,mixed> $parameters Array of parameters
|
|
* @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
|
|
* @param string $action 'add', 'update', 'view'
|
|
* @return int Return integer <0 if KO,
|
|
* =0 if OK but we want to process standard actions too,
|
|
* >0 if OK and we want to replace standard actions.
|
|
*/
|
|
public function getNomUrl($parameters, &$object, &$action)
|
|
{
|
|
global $db, $langs, $conf, $user;
|
|
$this->resprints = '';
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Overload the doActions function : replacing the parent's function with the one below
|
|
*
|
|
* @param array<string,mixed> $parameters Hook metadata (context, etc...)
|
|
* @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
|
|
* @param ?string $action Current action (if set). Generally create or edit or null
|
|
* @param HookManager $hookmanager Hook manager propagated to allow calling another hook
|
|
* @return int Return integer < 0 on error, 0 on success, 1 to replace standard code
|
|
*/
|
|
public function doActions($parameters, &$object, &$action, $hookmanager)
|
|
{
|
|
$error = 0; // Error counter
|
|
|
|
/* print_r($parameters); print_r($object); echo "action: " . $action; */
|
|
if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) { // do something only for the context 'somecontext1' or 'somecontext2'
|
|
// Do what you want here...
|
|
// You can for example load and use call global vars like $fieldstosearchall to overwrite them, or update the database depending on $action and GETPOST values.
|
|
|
|
if (!$error) {
|
|
$this->results = array('myreturn' => 999);
|
|
$this->resprints = 'A text to show';
|
|
return 0; // or return 1 to replace standard code
|
|
} else {
|
|
$this->errors[] = 'Error message';
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Overload the doMassActions function : replacing the parent's function with the one below
|
|
*
|
|
* @param array<string,mixed> $parameters Hook metadata (context, etc...)
|
|
* @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
|
|
* @param ?string $action Current action (if set). Generally create or edit or null
|
|
* @param HookManager $hookmanager Hook manager propagated to allow calling another hook
|
|
* @return int Return integer < 0 on error, 0 on success, 1 to replace standard code
|
|
*/
|
|
public function doMassActions($parameters, &$object, &$action, $hookmanager)
|
|
{
|
|
global $conf, $user, $langs;
|
|
|
|
$error = 0; // Error counter
|
|
|
|
/* print_r($parameters); print_r($object); echo "action: " . $action; */
|
|
if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) { // do something only for the context 'somecontext1' or 'somecontext2'
|
|
// @phan-suppress-next-line PhanPluginEmptyStatementForeachLoop
|
|
foreach ($parameters['toselect'] as $objectid) {
|
|
// Do action on each object id
|
|
}
|
|
|
|
if (!$error) {
|
|
$this->results = array('myreturn' => 999);
|
|
$this->resprints = 'A text to show';
|
|
return 0; // or return 1 to replace standard code
|
|
} else {
|
|
$this->errors[] = 'Error message';
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Overload the addMoreMassActions function : replacing the parent's function with the one below
|
|
*
|
|
* @param array<string,mixed> $parameters Hook metadata (context, etc...)
|
|
* @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
|
|
* @param ?string $action Current action (if set). Generally create or edit or null
|
|
* @param HookManager $hookmanager Hook manager propagated to allow calling another hook
|
|
* @return int Return integer < 0 on error, 0 on success, 1 to replace standard code
|
|
*/
|
|
public function addMoreMassActions($parameters, &$object, &$action, $hookmanager)
|
|
{
|
|
global $conf, $user, $langs;
|
|
|
|
$error = 0; // Error counter
|
|
$disabled = 1;
|
|
|
|
/* print_r($parameters); print_r($object); echo "action: " . $action; */
|
|
if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) { // do something only for the context 'somecontext1' or 'somecontext2'
|
|
$this->resprints = '<option value="0"'.($disabled ? ' disabled="disabled"' : '').'>'.$langs->trans("SupplierLink3MassAction").'</option>';
|
|
}
|
|
|
|
if (!$error) {
|
|
return 0; // or return 1 to replace standard code
|
|
} else {
|
|
$this->errors[] = 'Error message';
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Execute action before PDF (document) creation
|
|
*
|
|
* @param array<string,mixed> $parameters Array of parameters
|
|
* @param CommonObject $object Object output on PDF
|
|
* @param string $action 'add', 'update', 'view'
|
|
* @return int Return integer <0 if KO,
|
|
* =0 if OK but we want to process standard actions too,
|
|
* >0 if OK and we want to replace standard actions.
|
|
*/
|
|
public function beforePDFCreation($parameters, &$object, &$action)
|
|
{
|
|
global $conf, $user, $langs;
|
|
global $hookmanager;
|
|
|
|
$outputlangs = $langs;
|
|
|
|
$ret = 0;
|
|
$deltemp = array();
|
|
dol_syslog(get_class($this).'::executeHooks action='.$action);
|
|
|
|
/* print_r($parameters); print_r($object); echo "action: " . $action; */
|
|
// @phan-suppress-next-line PhanPluginEmptyStatementIf
|
|
if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) {
|
|
// do something only for the context 'somecontext1' or 'somecontext2'
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Execute action after PDF (document) creation
|
|
*
|
|
* @param array<string,mixed> $parameters Array of parameters
|
|
* @param CommonDocGenerator $pdfhandler PDF builder handler
|
|
* @param string $action 'add', 'update', 'view'
|
|
* @return int Return integer <0 if KO,
|
|
* =0 if OK but we want to process standard actions too,
|
|
* >0 if OK and we want to replace standard actions.
|
|
*/
|
|
public function afterPDFCreation($parameters, &$pdfhandler, &$action)
|
|
{
|
|
global $conf, $user, $langs;
|
|
global $hookmanager;
|
|
|
|
$outputlangs = $langs;
|
|
|
|
$ret = 0;
|
|
$deltemp = array();
|
|
dol_syslog(get_class($this).'::executeHooks action='.$action);
|
|
|
|
/* print_r($parameters); print_r($object); echo "action: " . $action; */
|
|
// @phan-suppress-next-line PhanPluginEmptyStatementIf
|
|
if (in_array($parameters['currentcontext'], array('somecontext1', 'somecontext2'))) {
|
|
// do something only for the context 'somecontext1' or 'somecontext2'
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Overload the loadDataForCustomReports function : returns data to complete the customreport tool
|
|
*
|
|
* @param array<string,mixed> $parameters Hook metadata (context, etc...)
|
|
* @param ?string $action Current action (if set). Generally create or edit or null
|
|
* @param HookManager $hookmanager Hook manager propagated to allow calling another hook
|
|
* @return int Return integer < 0 on error, 0 on success, 1 to replace standard code
|
|
*/
|
|
public function loadDataForCustomReports($parameters, &$action, $hookmanager)
|
|
{
|
|
global $langs;
|
|
|
|
$langs->load("supplierlink3@supplierlink3");
|
|
|
|
$this->results = array();
|
|
|
|
$head = array();
|
|
$h = 0;
|
|
|
|
if ($parameters['tabfamily'] == 'supplierlink3') {
|
|
$head[$h][0] = dol_buildpath('/module/index.php', 1);
|
|
$head[$h][1] = $langs->trans("Home");
|
|
$head[$h][2] = 'home';
|
|
$h++;
|
|
|
|
$this->results['title'] = $langs->trans("SupplierLink3");
|
|
$this->results['picto'] = 'supplierlink3@supplierlink3';
|
|
}
|
|
|
|
$head[$h][0] = 'customreports.php?objecttype='.$parameters['objecttype'].(empty($parameters['tabfamily']) ? '' : '&tabfamily='.$parameters['tabfamily']);
|
|
$head[$h][1] = $langs->trans("CustomReports");
|
|
$head[$h][2] = 'customreports';
|
|
|
|
$this->results['head'] = $head;
|
|
|
|
$arrayoftypes = array();
|
|
//$arrayoftypes['supplierlink3_myobject'] = array('label' => 'MyObject', 'picto'=>'myobject@supplierlink3', 'ObjectClassName' => 'MyObject', 'enabled' => isModEnabled('supplierlink3'), 'ClassPath' => "/supplierlink3/class/myobject.class.php", 'langs'=>'supplierlink3@supplierlink3')
|
|
|
|
$this->results['arrayoftype'] = $arrayoftypes;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Overload the restrictedArea function : check permission on an object
|
|
*
|
|
* @param array<string,mixed> $parameters Hook metadata (context, etc...)
|
|
* @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
|
|
* @param string $action Current action (if set). Generally create or edit or null
|
|
* @param HookManager $hookmanager Hook manager propagated to allow calling another hook
|
|
* @return int Return integer <0 if KO,
|
|
* =0 if OK but we want to process standard actions too,
|
|
* >0 if OK and we want to replace standard actions.
|
|
*/
|
|
public function restrictedArea($parameters, $object, &$action, $hookmanager)
|
|
{
|
|
global $user;
|
|
|
|
if ($parameters['features'] == 'myobject') {
|
|
if ($user->hasRight('supplierlink3', 'myobject', 'read')) {
|
|
$this->results['result'] = 1;
|
|
return 1;
|
|
} else {
|
|
$this->results['result'] = 0;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Execute action completeTabsHead
|
|
*
|
|
* @param array<string,mixed> $parameters Array of parameters
|
|
* @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
|
|
* @param string $action 'add', 'update', 'view'
|
|
* @param Hookmanager $hookmanager Hookmanager
|
|
* @return int Return integer <0 if KO,
|
|
* =0 if OK but we want to process standard actions too,
|
|
* >0 if OK and we want to replace standard actions.
|
|
*/
|
|
public function completeTabsHead(&$parameters, &$object, &$action, $hookmanager)
|
|
{
|
|
global $langs, $conf, $user;
|
|
|
|
if (!isset($parameters['object']->element)) {
|
|
return 0;
|
|
}
|
|
if ($parameters['mode'] == 'remove') {
|
|
// used to make some tabs removed
|
|
return 0;
|
|
} elseif ($parameters['mode'] == 'add') {
|
|
$langs->load('supplierlink3@supplierlink3');
|
|
// used when we want to add some tabs
|
|
$counter = count($parameters['head']);
|
|
$element = $parameters['object']->element;
|
|
$id = $parameters['object']->id;
|
|
// verifier le type d'onglet comme member_stats où ça ne doit pas apparaitre
|
|
// if (in_array($element, ['societe', 'member', 'contrat', 'fichinter', 'project', 'propal', 'commande', 'facture', 'order_supplier', 'invoice_supplier'])) {
|
|
if (in_array($element, ['context1', 'context2'])) {
|
|
$datacount = 0;
|
|
|
|
$parameters['head'][$counter][0] = dol_buildpath('/supplierlink3/supplierlink3_tab.php', 1) . '?id=' . $id . '&module='.$element;
|
|
$parameters['head'][$counter][1] = $langs->trans('SupplierLink3Tab');
|
|
if ($datacount > 0) {
|
|
$parameters['head'][$counter][1] .= '<span class="badge marginleftonlyshort">' . $datacount . '</span>';
|
|
}
|
|
$parameters['head'][$counter][2] = 'supplierlink3emails';
|
|
$counter++;
|
|
}
|
|
if ($counter > 0 && (int) DOL_VERSION < 14) { // @phpstan-ignore-line
|
|
$this->results = $parameters['head'];
|
|
// return 1 to replace standard code
|
|
return 1;
|
|
} else {
|
|
// From V14 onwards, $parameters['head'] is modifiable by reference
|
|
return 0;
|
|
}
|
|
} else {
|
|
// Bad value for $parameters['mode']
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Overload the showLinkToObjectBlock function : add or replace array of object linkable
|
|
*
|
|
* @param array<string,mixed> $parameters Hook metadata (context, etc...)
|
|
* @param CommonObject $object The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
|
|
* @param ?string $action Current action (if set). Generally create or edit or null
|
|
* @param HookManager $hookmanager Hook manager propagated to allow calling another hook
|
|
* @return int Return integer < 0 on error, 0 on success, 1 to replace standard code
|
|
*/
|
|
/*public function showLinkToObjectBlock($parameters, &$object, &$action, $hookmanager)
|
|
{
|
|
$myobject = new MyObject($object->db);
|
|
$this->results = array('myobject@supplierlink3' => array(
|
|
'enabled' => isModEnabled('supplierlink3'),
|
|
'perms' => 1,
|
|
'label' => 'LinkToMyObject',
|
|
'sql' => "SELECT t.rowid, t.ref, t.ref as 'name' FROM " . $this->db->prefix() . $myobject->table_element. " as t "),);
|
|
|
|
return 1;
|
|
}*/
|
|
/* 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)
|
|
{
|
|
static $css_js_added = false;
|
|
|
|
$isSupplierOrder = ($parameters['currentcontext'] == 'ordersuppliercard');
|
|
$isCustomerOrder = ($parameters['currentcontext'] == 'ordercard');
|
|
$isProposal = ($parameters['currentcontext'] == 'propalcard');
|
|
|
|
if (!$isSupplierOrder && !$isCustomerOrder && !$isProposal) {
|
|
return 0;
|
|
}
|
|
|
|
// Prüfen ob diese Funktion für den jeweiligen Kontext aktiviert ist
|
|
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>
|
|
/* Verstecke Info-Icons innerhalb der Lagerbestand-Anzeige */
|
|
.stock-display-wrapper .fa-info-circle,
|
|
#tablelines tbody .fa-info-circle {
|
|
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>
|
|
<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
|
|
$css_js_added = true;
|
|
}
|
|
|
|
$line = $parameters['line'];
|
|
|
|
// Lagerbestand, Wunschbestand und Mindestbestand abfragen
|
|
$sqlStock = "SELECT stock, desiredstock, seuil_stock_alerte
|
|
FROM ".MAIN_DB_PREFIX."product
|
|
WHERE rowid = ".(int)$line->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 = '<div style="display: inline-flex; align-items: center;">';
|
|
|
|
// Shop-Icon mit fester Breite (damit alle untereinander stehen)
|
|
$newref .= '<span class="supplier-shop-col" style="display: inline-block; width: 28px; text-align: center;">';
|
|
if (!empty($shopLinkHtml)) {
|
|
$newref .= $shopLinkHtml;
|
|
}
|
|
$newref .= '</span>';
|
|
|
|
// 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>';
|
|
|
|
// Artikel-Nummer als normaler Text
|
|
$newref .= '<span>' . dol_escape_htmltag($line->ref_fourn) . '</span>';
|
|
|
|
$newref .= '</div>';
|
|
|
|
$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 = '<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>';
|
|
|
|
// 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)) {
|
|
$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)
|
|
{
|
|
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 vorhanden sind
|
|
$sql = "SELECT COUNT(*) as nb FROM ".MAIN_DB_PREFIX."commandedet
|
|
WHERE fk_commande = ".(int)$object->id."
|
|
AND fk_product > 0
|
|
AND product_type = 0";
|
|
$resql = $db->query($sql);
|
|
$hasProducts = false;
|
|
if ($resql) {
|
|
$obj = $db->fetch_object($resql);
|
|
$hasProducts = ($obj->nb > 0);
|
|
$this->debugLog('Produktzeilen gefunden: '.$obj->nb, 'addMoreActionsButtons');
|
|
} else {
|
|
$this->debugLog('SQL Fehler: '.$db->lasterror(), 'addMoreActionsButtons');
|
|
}
|
|
|
|
if ($hasProducts) {
|
|
$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)
|
|
{
|
|
global $db;
|
|
|
|
// Auf Produktkarte UND Preis-Seite
|
|
if (strpos($parameters['context'], 'productcard') === false && strpos($parameters['context'], 'productpricecard') === false) {
|
|
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
|
|
FROM ".MAIN_DB_PREFIX."product_fournisseur_price pfp
|
|
INNER JOIN ".MAIN_DB_PREFIX."societe s ON s.rowid = pfp.fk_soc
|
|
INNER JOIN ".MAIN_DB_PREFIX."societe_extrafields se ON se.fk_object = pfp.fk_soc
|
|
WHERE pfp.fk_product = ".(int)$object->id."
|
|
AND se.shop_url IS NOT NULL
|
|
AND se.shop_url != ''
|
|
ORDER BY pfp.price ASC";
|
|
|
|
$resql = $db->query($sql);
|
|
$links = array();
|
|
$minPrice = null;
|
|
|
|
// Ersten Durchlauf: günstigsten Preis finden
|
|
if ($resql) {
|
|
$results = array();
|
|
while ($obj = $db->fetch_object($resql)) {
|
|
$results[] = $obj;
|
|
if ($minPrice === null || $obj->price < $minPrice) {
|
|
$minPrice = $obj->price;
|
|
}
|
|
}
|
|
|
|
// Zweiter Durchlauf: Links bauen
|
|
foreach ($results as $obj) {
|
|
$shortName = explode(' ', $obj->nom)[0];
|
|
$tooltip = '<b><u>'.$obj->nom.'</u></b><br><b>Artikel-Nr:</b> '.$obj->ref_fourn.'<br><b>Preis:</b> '.round($obj->price,2).' EUR<br><b>Mindestmenge: </b>'.$obj->quantity;
|
|
|
|
// Günstigster Lieferant wird fett
|
|
$displayText = $shortName.' ('.$obj->ref_fourn.')';
|
|
if ($obj->price == $minPrice) {
|
|
$displayText = '<b>'.$displayText.'</b>';
|
|
}
|
|
|
|
$links[] = '<a href="'.dol_escape_htmltag($obj->shop_url).$obj->ref_fourn.'" target="_blank" class="classfortooltip" title="'.$tooltip.'">'.$displayText.'</a>';
|
|
}
|
|
}
|
|
|
|
if (!empty($links)) {
|
|
$linksHtml = implode(' | ', $links);
|
|
?>
|
|
<script type="text/javascript">
|
|
$(document).ready(function() {
|
|
var linksDiv = '<div class="refidno opacitymedium" style="font-size: 12px;"><?php echo addslashes($linksHtml); ?></div>';
|
|
$('.refidno').last().after(linksDiv);
|
|
});
|
|
</script>
|
|
<?php
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|