Stabile 2.0 Version erweiter auf Kundenauftrag, Angebot, Lieferantenbestellung

Lager. Shoplink angepasst neuer Tab danach immer im selben Tab. Kundenauftrag
Button für Bestellung aus dem Entwurf heraus
This commit is contained in:
Eduard Wisch 2026-01-30 10:25:03 +01:00
parent b970a66339
commit 941aae90f7
11 changed files with 1973 additions and 761 deletions

0
.gitignore vendored Normal file → Executable file
View file

0
.idea/SupplierLink3.iml Normal file → Executable file
View file

239
README.md
View file

@ -1,96 +1,217 @@
# SUPPLIERLINK3 FOR [DOLIBARR ERP & CRM](https://www.dolibarr.org)
# SupplierLink3 for [Dolibarr ERP & CRM](https://www.dolibarr.org)
**Version 2.0** | **License: GPL v3** | **Author: Eduard Wisch**
Dolibarr module for linking supplier webshops with products and displaying stock levels.
## Features
Description of the module...
### Shop Links
- Direct access to supplier webshops from within Dolibarr
- Automatic URL generation with supplier article number
- Multi-supplier support with popup selection (sorted by price)
- Configurable shop icon (FontAwesome)
- Same supplier opens in same browser tab
<!--
![Screenshot supplierlink3](img/screenshot_supplierlink3.png?raw=true "SupplierLink3"){imgmd}
-->
### Stock Display
Colored badges for quick overview:
- **Red**: Out of stock (< 1)
- **Orange**: Below minimum stock (alert threshold)
- **Gray**: Below desired stock
- **Green**: Sufficient stock
Other external modules are available on [Dolistore.com](https://www.dolistore.com).
## Translations
Translations can be completed manually by editing files in the module directories under `langs`.
<!--
This module contains also a sample configuration for Transifex, under the hidden directory [.tx](.tx), so it is possible to manage translation using this service.
For more information, see the [translator's documentation](https://wiki.dolibarr.org/index.php/Translator_documentation).
There is a [Transifex project](https://transifex.com/projects/p/dolibarr-module-template) for this module.
-->
### Supported Areas
- Customer Orders
- Proposals/Quotes
- Supplier Orders
- Stock Replenishment
- Product Card
### Quick Supplier Order Creation
- "Create Supplier Order" button in customer orders
- Automatic product and quantity transfer
- Supplier reference with customer name and order number
## Installation
Prerequisites: You must have Dolibarr ERP & CRM software installed. You can download it from [Dolistore.org](https://www.dolibarr.org).
You can also get a ready-to-use instance in the cloud from https://saas.dolibarr.org
### From ZIP file
1. Go to menu **Home > Setup > Modules > Deploy external module**
2. Upload the zip file
### Manual Installation
1. Copy module folder to `/custom/supplierlink3/`
2. In Dolibarr: **Setup > Modules > SupplierLink3** - activate
3. On activation, extrafield `shop_url` is created for suppliers
### From the ZIP file and GUI interface
## Configuration
If the module is a ready-to-deploy zip file, so with a name `module_xxx-version.zip` (e.g., when downloading it from a marketplace like [Dolistore](https://www.dolistore.com)),
go to menu `Home> Setup> Modules> Deploy external module` and upload the zip file.
### Admin Center
Settings under **Setup > Modules > SupplierLink3 > Settings**
<!--
#### Display Settings
Enable/disable the function for each area:
- Customer Orders
- Proposals
- Supplier Orders
- Stock Replenishment
- Product Card
Note: If this screen tells you that there is no "custom" directory, check that your setup is correct:
#### Icon Settings
Customizable FontAwesome icons:
- **Shop Icon**: Default `fas fa-store`
- **Stock Icon**: Optional
- In your Dolibarr installation directory, edit the `htdocs/conf/conf.php` file and check that following lines are not commented:
#### Debug Mode
- Disabled by default
- When enabled, writes to `/tmp/supplierlink3_debug.log`
```php
//$dolibarr_main_url_root_alt ...
//$dolibarr_main_document_root_alt ...
```
### Setting up Shop URL for Suppliers
1. Open supplier > Edit
2. Fill extrafield "Shop URL" (e.g. `https://shop.supplier.com/article/`)
3. The supplier article number is automatically appended
- Uncomment them if necessary (delete the leading `//`) and assign the proper value according to your Dolibarr installation
## Usage
For example :
### In Orders/Proposals
- Click shop icon next to stock level
- Multiple suppliers: Dropdown with price comparison
- Same supplier opens in same browser tab
- UNIX:
```php
$dolibarr_main_url_root_alt = '/custom';
$dolibarr_main_document_root_alt = '/var/www/Dolibarr/htdocs/custom';
```
### Creating Supplier Order
1. Open customer order
2. Click "Create Supplier Order" button
3. Select supplier
4. Select products
5. Create order
- Windows:
```php
$dolibarr_main_url_root_alt = '/custom';
$dolibarr_main_document_root_alt = 'C:/My Web Sites/Dolibarr/htdocs/custom';
```
-->
## Technical Details
<!--
### Hooks
- `ordersuppliercard` - Supplier Orders
- `ordercard` - Customer Orders
- `propalcard` - Proposals
- `productcard` - Product Card
- `productpricecard` - Product Prices
- `stockreplenishlist` - Replenishment List
### From a GIT repository
### Database
Uses extrafield `shop_url` in `llx_societe_extrafields`.
Clone the repository in `$dolibarr_main_document_root_alt/supplierlink3`
```shell
cd ....../custom
git clone git@github.com:gitlogin/supplierlink3.git supplierlink3
### File Structure
```
supplierlink3/
├── admin/
│ ├── setup.php # Settings
│ └── about.php # About page
├── class/
│ └── actions_supplierlink3.class.php # Hook implementation
├── core/modules/
│ └── modSupplierLink3.class.php # Module definition
├── js/
│ └── replenish.js # JavaScript for replenishment
├── langs/
│ ├── de_DE/supplierlink3.lang
│ └── en_US/supplierlink3.lang
├── lib/
│ └── supplierlink3.lib.php
└── create_supplier_order.php # Create supplier order
```
-->
## Changelog
### Final steps
### Version 2.0
- Admin center completely redesigned
- Settings per area (enable/disable)
- Configurable icons (FontAwesome)
- Debug mode disabled by default
- Bilingual (German/English)
- Stock replenishment list integrated
- Improved icon alignment
Using your browser:
### Version 1.0
- Initial release
- Shop links in orders
- Stock badges
- Supplier order from customer order
- Log into Dolibarr as a super-administrator
- Go to "Setup"> "Modules"
- You should now be able to find and enable the module
## Translations
Translations are available in:
- German (de_DE)
- English (en_US)
Additional translations can be added by creating files in `langs/xx_XX/supplierlink3.lang`
## Support
For questions or issues: data@data-it-solution.de
## Licenses
### Main code
### Main Code
GPLv3 or (at your option) any later version. See file COPYING for more information.
### Documentation
All texts and readmes are licensed under [GFDL](https://www.gnu.org/licenses/fdl-1.3.en.html).
All texts and readme's are licensed under [GFDL](https://www.gnu.org/licenses/fdl-1.3.en.html).
---
# SupplierLink3 für [Dolibarr ERP & CRM](https://www.dolibarr.org) (Deutsch)
**Version 2.0** | **Lizenz: GPL v3** | **Autor: Eduard Wisch**
Dolibarr-Modul zur Verknüpfung von Lieferanten-Webshops mit Produkten und Anzeige von Lagerbeständen.
## Funktionen
### Shop-Links
- Direkter Zugang zu Lieferanten-Webshops aus Dolibarr heraus
- Automatische URL-Generierung mit Lieferanten-Artikelnummer
- Multi-Lieferanten-Unterstützung mit Popup-Auswahl (sortiert nach Preis)
- Konfigurierbares Shop-Symbol (FontAwesome)
- Gleicher Lieferant öffnet sich im gleichen Browser-Tab
### Lagerbestand-Anzeige
Farbige Badges für schnelle Übersicht:
- **Rot**: Nicht auf Lager (< 1)
- **Orange**: Unter Mindestbestand (Alarm-Schwelle)
- **Grau**: Unter Wunschbestand
- **Grün**: Ausreichend auf Lager
### Unterstützte Bereiche
- Kundenaufträge
- Angebote
- Lieferantenbestellungen
- Nachbestellung (Lager)
- Produktkarte
### Schnelle Lieferantenbestellung
- Button "Lieferantenbestellung erstellen" im Kundenauftrag
- Automatische Übernahme von Produkten und Mengen
- Lieferanten-Referenz mit Kundenname und Auftragsnummer
## Konfiguration
### Shop-URL für Lieferanten einrichten
1. Lieferant öffnen > Bearbeiten
2. Extrafeld "Shop URL" ausfüllen (z.B. `https://shop.lieferant.de/artikel/`)
3. Die Lieferanten-Artikelnummer wird automatisch angehängt
### Admin-Center
Einstellungen unter **Einstellungen > Module > SupplierLink3 > Einstellungen**
#### Anzeige-Einstellungen
Aktivieren/Deaktivieren pro Bereich:
- Kundenaufträge
- Angebote
- Lieferantenbestellungen
- Nachbestellung (Lager)
- Produktkarte
#### Symbol-Einstellungen
- **Shop-Symbol**: Standard `fas fa-store`
- **Lagerbestand-Symbol**: Optional
#### Debug-Modus
- Standardmäßig deaktiviert
- Schreibt bei Aktivierung nach `/tmp/supplierlink3_debug.log`

View file

@ -1,7 +1,7 @@
<?php
/* Copyright (C) 2004-2017 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2025 Eduard Wisch <data@data-it-solution.de>
* Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
* 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
@ -25,34 +25,31 @@
// Load Dolibarr environment
$res = 0;
// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
}
// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
$tmp2 = realpath(__FILE__);
$i = strlen($tmp) - 1;
$j = strlen($tmp2) - 1;
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
$i--;
$j--;
$i--;
$j--;
}
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
$res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
$res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
}
if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) {
$res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
$res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
}
// Try main.inc.php using relative path
if (!$res && file_exists("../../main.inc.php")) {
$res = @include "../../main.inc.php";
$res = @include "../../main.inc.php";
}
if (!$res && file_exists("../../../main.inc.php")) {
$res = @include "../../../main.inc.php";
$res = @include "../../../main.inc.php";
}
if (!$res) {
die("Include of main fails");
die("Include of main fails");
}
// Libraries
@ -60,59 +57,116 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
require_once '../lib/supplierlink3.lib.php';
/**
* @var Conf $conf
* @var DoliDB $db
* @var HookManager $hookmanager
* @var Translate $langs
* @var User $user
*/
// Translations
$langs->loadLangs(array("errors", "admin", "supplierlink3@supplierlink3"));
// Access control
if (!$user->admin) {
accessforbidden();
accessforbidden();
}
// Parameters
$action = GETPOST('action', 'aZ09');
$backtopage = GETPOST('backtopage', 'alpha');
/*
* Actions
*/
// None
/*
* View
*/
$form = new Form($db);
$help_url = '';
$title = "SupplierLink3Setup";
$title = $langs->trans("SupplierLink3Setup");
llxHeader('', $langs->trans($title), $help_url, '', 0, 0, '', '', '', 'mod-supplierlink3 page-admin_about');
llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-supplierlink3 page-admin_about');
// Subheader
$linkback = '<a href="'.($backtopage ? $backtopage : DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1').'">'.$langs->trans("BackToModuleList").'</a>';
$linkback = '<a href="'.DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1">'.$langs->trans("BackToModuleList").'</a>';
print load_fiche_titre($langs->trans($title), $linkback, 'title_setup');
print load_fiche_titre($title, $linkback, 'title_setup');
// Configuration header
$head = supplierlink3AdminPrepareHead();
print dol_get_fiche_head($head, 'about', $langs->trans($title), 0, 'supplierlink3@supplierlink3');
print dol_get_fiche_head($head, 'about', $title, -1, 'supplierlink3@supplierlink3');
// Module info
dol_include_once('/supplierlink3/core/modules/modSupplierLink3.class.php');
$tmpmodule = new modSupplierLink3($db);
print $tmpmodule->getDescLong();
print '<table class="noborder centpercent">';
// Version
print '<tr class="liste_titre">';
print '<th colspan="2">'.$langs->trans('SL3_Version').' & '.$langs->trans('SL3_Author').'</th>';
print '</tr>';
print '<tr class="oddeven">';
print '<td width="30%">'.$langs->trans('SL3_Version').'</td>';
print '<td><strong>'.$tmpmodule->version.'</strong></td>';
print '</tr>';
print '<tr class="oddeven">';
print '<td>'.$langs->trans('SL3_Author').'</td>';
print '<td>Eduard Wisch &lt;data@data-it-solution.de&gt;</td>';
print '</tr>';
print '<tr class="oddeven">';
print '<td>'.$langs->trans('SL3_License').'</td>';
print '<td>GPL v3</td>';
print '</tr>';
print '</table>';
print '<br>';
// Features
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th>'.$langs->trans('SL3_Features').'</th>';
print '</tr>';
$features = array(
'SL3_Feature1' => 'fa-store',
'SL3_Feature2' => 'fa-boxes',
'SL3_Feature3' => 'fa-truck-loading',
'SL3_Feature4' => 'fa-users',
);
foreach ($features as $key => $icon) {
print '<tr class="oddeven">';
print '<td><i class="fas '.$icon.'" style="color: #0077b6; margin-right: 10px; width: 20px;"></i>'.$langs->trans($key).'</td>';
print '</tr>';
}
print '</table>';
print '<br>';
// Stock badge legend
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th colspan="2">'.$langs->trans('SL3_StockBadges', 'Lagerbestand-Badges').'</th>';
print '</tr>';
print '<tr class="oddeven">';
print '<td width="150"><span class="badge" style="background-color: #dc3545; color: #fff;">0</span></td>';
print '<td>'.$langs->trans('SL3_StockCritical').'</td>';
print '</tr>';
print '<tr class="oddeven">';
print '<td><span class="badge" style="background-color: #fd7e14; color: #fff;">5</span></td>';
print '<td>'.$langs->trans('SL3_StockWarning').'</td>';
print '</tr>';
print '<tr class="oddeven">';
print '<td><span class="badge" style="background-color: #6c757d; color: #fff;">15</span></td>';
print '<td>'.$langs->trans('SL3_StockLow').'</td>';
print '</tr>';
print '<tr class="oddeven">';
print '<td><span class="badge" style="background-color: #28a745; color: #fff;">42</span></td>';
print '<td>'.$langs->trans('SL3_StockOk').'</td>';
print '</tr>';
print '</table>';
// Page end
print dol_get_fiche_end();
llxFooter();
$db->close();

File diff suppressed because it is too large Load diff

View file

@ -36,6 +36,16 @@ class ActionsSupplierLink3 extends CommonHookActions
*/
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)
*/
@ -63,6 +73,16 @@ class ActionsSupplierLink3 extends CommonHookActions
public $priority;
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
/**
* @var string Shop icon class (FontAwesome)
*/
private $shopIcon = 'fas fa-store';
/**
* Constructor
*
@ -71,6 +91,38 @@ class ActionsSupplierLink3 extends CommonHookActions
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);
}
@ -401,111 +453,443 @@ class ActionsSupplierLink3 extends CommonHookActions
return 1;
}*/
/* Add other hook methods here... */
public function printObjectLine($parameters, &$object, &$action, $hookmanager)
/**
* 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 .= ' &middot; Mindestmenge: '.(int)$supplier['min_qty'].' Stk.';
$html .= ' &middot; 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)
{
static $css_added = false;
global $db;
$isSupplierOrder = ($parameters['currentcontext'] == 'ordersuppliercard');
$isCustomerOrder = ($parameters['currentcontext'] == 'ordercard');
$this->debugLog('Hook aufgerufen - context: '.(isset($parameters['currentcontext']) ? $parameters['currentcontext'] : 'nicht gesetzt'), 'addMoreActionsButtons');
$this->debugLog('Object element: '.(is_object($object) ? $object->element : 'kein Objekt'), 'addMoreActionsButtons');
$this->debugLog('Object id: '.(is_object($object) && isset($object->id) ? $object->id : 'keine ID'), 'addMoreActionsButtons');
if (!$isSupplierOrder && !$isCustomerOrder) {
// Nur für Kundenaufträge (Commande)
if (!is_object($object) || $object->element != 'commande') {
$this->debugLog('Abbruch: Kein Commande-Objekt', 'addMoreActionsButtons');
return 0;
}
// CSS nur einmal hinzufügen
if (!$css_added) {
?>
<style>
/* Verstecke Info-Icons innerhalb der Lagerbestand-Anzeige */
.stock-display-wrapper .fa-info-circle {
display: none !important;
}
#tablelines tbody .fa-info-circle {
display: none !important;
}
</style>
<?php
$css_added = true;
// 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');
}
$line = $parameters['line'];
if ($hasProducts) {
$buttonUrl = dol_buildpath('/custom/supplierlink3/create_supplier_order.php', 1).'?origin=commande&originid='.$object->id;
$line = $parameters['line'];
// Button per JavaScript einfügen (da Dolibarr resPrint bei diesem Hook nicht ausgibt)
print '<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";
// Lagerbestand und Wunschbestand abfragen
$sqlStock = "SELECT stock, desiredstock
FROM ".MAIN_DB_PREFIX."product
WHERE rowid = ".(int)$line->fk_product;
$resStock = $this->db->query($sqlStock);
$qtyStock = 0;
$desiredQty = 0;
if ($resStock && $this->db->num_rows($resStock) > 0) {
$objStock = $this->db->fetch_object($resStock);
$qtyStock = (float) $objStock->stock;
$desiredQty = (float) $objStock->desiredstock;
}
// Farbe setzen
$stockColor = 'ffffff';
if ($qtyStock < 1) {
$stockColor = 'red'; // ausverkauft
} elseif ($qtyStock < $desiredQty) {
$stockColor = 'orange'; // kleiner als Wunschbestand
}
// Lieferanten-ID aus dem Bestell-Objekt holen
if (!empty($object->socid) && $isSupplierOrder) {
$fk_supplier = $object->socid;
// Extrafeld-Wert vom Lieferanten laden
$sql = "SELECT shop_url FROM ".MAIN_DB_PREFIX."societe_extrafields";
$sql .= " WHERE fk_object = ".(int)$fk_supplier;
$resql = $this->db->query($sql);
if ($resql && $this->db->num_rows($resql) > 0) {
$obj = $this->db->fetch_object($resql);
if (isset($obj->shop_url) && $obj->shop_url !== '' && $obj->shop_url !== null) {
$shop_url = trim($obj->shop_url);
if (!empty($shop_url)) {
// Artikel-Link
$full_url = rtrim($shop_url, '/') . '/' . $line->ref_fourn;
$newref = '<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 .= '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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;
}
}
}
} elseif ($isCustomerOrder) {
// NUR für Produkte, NICHT für Dienstleistungen
if (!empty($line->fk_product) && $line->product_type == 0) {
// Lagerbestand rechtsbündig mit float
$stockInfo = '<span style="float: right; margin-left: 20px;">';
$stockInfo .= '<span class="fas fa-box-open" style="color: ' . $stockColor . ';"></span>';
$stockInfo .= '&nbsp;<span style="color: ' . $stockColor . '; font-weight: bold;">' . $qtyStock . '</span>';
$stockInfo .= '</span>';
// An verschiedene mögliche Felder anhängen
if (isset($line->product_label)) {
$line->product_label .= $stockInfo;
}
}
$this->debugLog('Button per JavaScript eingefügt für URL: '.$buttonUrl, 'addMoreActionsButtons');
} else {
$this->debugLog('Kein Button - keine Produktzeilen', 'addMoreActionsButtons');
}
return 0;
@ -520,6 +904,11 @@ class ActionsSupplierLink3 extends CommonHookActions
return 0;
}
// Prüfen ob Produktkarte aktiviert ist
if (!getDolGlobalInt('SUPPLIERLINK3_ENABLE_PRODUCTCARD', 1)) {
return 0;
}
if ($action != 'create' && $action != 'edit') {
$sql = "SELECT DISTINCT pfp.ref_fourn, pfp.quantity, pfp.price, s.nom, se.shop_url
@ -575,4 +964,113 @@ class ActionsSupplierLink3 extends CommonHookActions
return 0;
}
/**
* Hook für Footer der Liste - JavaScript einfügen das die Stock-Spalte ersetzt
* KEINE zusätzliche Spalte - nur die bestehende Stock-Spalte wird modifiziert
*/
public function printFieldListFooter($parameters, &$object, &$action, $hookmanager)
{
global $db;
if (strpos($parameters['currentcontext'], 'stockreplenishlist') === false) {
return 0;
}
// Prüfen ob Nachbestellung aktiviert ist
if (!getDolGlobalInt('SUPPLIERLINK3_ENABLE_REPLENISH', 1)) {
return 0;
}
// Alle Produkte mit Shop-URLs abrufen
$sql = "SELECT DISTINCT pfp.fk_product,
pfp.fk_soc as supplier_id,
pfp.ref_fourn,
pfp.price,
pfp.quantity as min_qty,
s.nom as supplier_name,
se.shop_url
FROM ".MAIN_DB_PREFIX."product_fournisseur_price pfp
INNER JOIN ".MAIN_DB_PREFIX."societe s ON s.rowid = pfp.fk_soc
LEFT JOIN ".MAIN_DB_PREFIX."societe_extrafields se ON se.fk_object = pfp.fk_soc
WHERE se.shop_url IS NOT NULL AND se.shop_url != ''
ORDER BY pfp.fk_product, pfp.price ASC";
$resql = $db->query($sql);
$productSuppliers = array();
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
$productId = $obj->fk_product;
if (!isset($productSuppliers[$productId])) {
$productSuppliers[$productId] = array();
}
$productSuppliers[$productId][] = array(
'supplier_id' => $obj->supplier_id,
'supplier_name' => $obj->supplier_name,
'ref_fourn' => $obj->ref_fourn,
'price' => $obj->price,
'min_qty' => $obj->min_qty,
'full_url' => rtrim($obj->shop_url, '/').'/'.$obj->ref_fourn
);
}
}
$suppliersJson = json_encode($productSuppliers);
$jsUrl = dol_buildpath('/supplierlink3/js/replenish.js', 1);
$shopIcon = getDolGlobalString('SUPPLIERLINK3_SHOP_ICON', 'fas fa-store');
// Daten und Script direkt in den Footer schreiben (nach der Seite)
// Verwende print statt resprints um außerhalb der Tabelle zu landen
?>
<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;
}
}

View 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'
// 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.0';
// Url to the file with your last numberversion of this module
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
@ -120,13 +120,12 @@ class modSupplierLink3 extends DolibarrModules
// Set here all hooks context managed by module. To find available hook context, make a "grep -r '>initHooks(' *" on source code. You can also set hook context to 'all'
/* BEGIN MODULEBUILDER HOOKSCONTEXTS */
'hooks' => array(
'data' => array(
'ordersuppliercard',
'ordercard',
'productcard',
'productpricecard ',
),
'entity' => '0',
'ordersuppliercard',
'ordercard',
'propalcard',
'productcard',
'productpricecard',
'stockreplenishlist',
),
/* END MODULEBUILDER HOOKSCONTEXTS */
// Set this to 1 if features of module are opened to external users
@ -509,8 +508,6 @@ class modSupplierLink3 extends DolibarrModules
'isModEnabled("supplierlink3")' // Enabled condition
);
print_r($result0);
// Permissions
$this->remove($options);

359
create_supplier_order.php Executable file
View file

@ -0,0 +1,359 @@
<?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 && !empty($srcline->fk_product)) {
// Günstigsten Lieferantenpreis für diesen Lieferanten finden
$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);
$fournprice = 0;
$fournref = '';
$fournpricerowid = 0;
if ($resql && $db->num_rows($resql) > 0) {
$objprice = $db->fetch_object($resql);
$fournprice = $objprice->price;
$fournref = $objprice->ref_fourn;
$fournpricerowid = $objprice->rowid;
}
// Zeile zur Lieferantenbestellung hinzufügen
$result_line = $supplierorder->addline(
$srcline->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
$fournpricerowid, // Supplier price ID
$fournref, // Supplier ref
$srcline->remise_percent, // Discount
'HT', // Price base type
0, // pu_ht_devise
0, // type
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>';
// Alle Lieferanten mit Shop-URL abrufen, die Produkte aus diesem Auftrag liefern können
$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);
$suppliers = array();
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
$suppliers[$obj->rowid] = $obj->nom;
}
}
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>';
foreach ($suppliers as $id => $name) {
$selected = (GETPOST('supplier_id', 'int') == $id) ? ' selected' : '';
print '<option value="'.$id.'"'.$selected.'>'.dol_escape_htmltag($name).'</option>';
}
print '</select></td></tr>';
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>';
// Produkte aus Kundenauftrag auflisten
foreach ($objectsrc->lines as $line) {
// Nur Produkte, keine Dienstleistungen
if (empty($line->fk_product) || $line->product_type != 0) {
continue;
}
// 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);
$supplierList = array();
if ($ressuppliers) {
while ($objsup = $db->fetch_object($ressuppliers)) {
$supplierList[] = dol_escape_htmltag($objsup->nom).' ('.number_format($objsup->price, 2, ',', '.').' EUR)';
}
}
print '<tr class="oddeven">';
// Checkbox
print '<td class="center">';
print '<input type="checkbox" name="toselect[]" value="'.$line->id.'" class="linecheckbox">';
print '</td>';
// Produkt
print '<td>';
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">';
print '<span class="badge '.$stockClass.'">'.(int)$qtyStock.'</span>';
print '</td>';
// Lieferanten
print '<td>';
if (!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 ' &nbsp; ';
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; }
</style>';
llxFooter();
$db->close();

167
js/replenish.js Executable file
View 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 Executable file
View 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

View file

@ -1,42 +1,97 @@
# Translation file
# SupplierLink3 - English translation
# Copyright (C) 2025 Eduard Wisch <data@data-it-solution.de>
#
# Generic
#
# Module label 'ModuleSupplierLink3Name'
ModuleSupplierLink3Name = SupplierLink3
# Module description 'ModuleSupplierLink3Desc'
ModuleSupplierLink3Desc = SupplierLink3 description
ModuleSupplierLink3Desc = Supplier linking with shop links and stock display
#
# Admin page
#
SupplierLink3Setup = SupplierLink3 setup
SupplierLink3Setup = SupplierLink3 Setup
Settings = Settings
SupplierLink3SetupPage = SupplierLink3 setup page
NewSection=New section
SUPPLIERLINK3_MYPARAM1 = My param 1
SUPPLIERLINK3_MYPARAM1Tooltip = My param 1 tooltip
SUPPLIERLINK3_MYPARAM2=My param 2
SUPPLIERLINK3_MYPARAM2Tooltip=My param 2 tooltip
SupplierLink3Description = Displays shop links and stock levels in orders, proposals and supplier orders
# Settings sections
SL3_DisplaySettings = Display Settings
SL3_DisplaySettingsDesc = Choose where shop links and stock levels should be displayed
SL3_IconSettings = Icon Settings
SL3_IconSettingsDesc = Customize the icons used
SL3_DebugSettings = Debug Settings
SL3_RegisteredHooks = Registered Hooks
# Display areas
SL3_EnableOrderCard = Customer Orders
SL3_EnableOrderCardDesc = Show shop links and stock in customer orders
SL3_EnablePropalCard = Proposals
SL3_EnablePropalCardDesc = Show shop links and stock in proposals
SL3_EnableSupplierOrder = Supplier Orders
SL3_EnableSupplierOrderDesc = Show shop links and stock in supplier orders
SL3_EnableReplenish = Stock Replenishment
SL3_EnableReplenishDesc = Show shop links and stock in the replenishment list
SL3_EnableProductCard = Product Card
SL3_EnableProductCardDesc = Show shop links on the product card
# Icons
SL3_ShopIcon = Shop Icon
SL3_ShopIconDesc = FontAwesome class for the shop icon (e.g. fa-store, fa-shopping-cart, fa-external-link-alt)
SL3_StockIcon = Stock Icon
SL3_StockIconDesc = FontAwesome class for the stock icon (e.g. fa-box, fa-warehouse, fa-cubes)
SL3_IconPreview = Preview
# Debug
SL3_DebugMode = Debug Mode
SL3_DebugModeDesc = Writes debug messages to the log file
SL3_DebugLog = Debug Log
SL3_DebugLogFile = Log file
SL3_DebugLogSize = Size
SL3_DebugLogClear = Clear log
SL3_DebugLogCleared = Debug log cleared
SL3_DebugLogEmpty = No log file exists. Enable debug mode and perform an action.
SL3_DebugLogLastEntries = Last entries
# Hooks
SL3_HookOrderSupplier = Supplier Orders - Shop links and stock
SL3_HookOrderCard = Customer Orders - Shop links, stock and "Create supplier order" button
SL3_HookPropalCard = Proposals - Shop links and stock
SL3_HookProductCard = Product Card - Shop links
SL3_HookProductPrice = Product Prices - Shop links
SL3_HookReplenish = Replenishment - Shop links and stock
# Messages
SetupSaved = Settings saved
#
# About page
#
About = About
SupplierLink3About = About SupplierLink3
SupplierLink3AboutPage = SupplierLink3 about page
SupplierLink3AboutPage = SupplierLink3 information
SL3_Version = Version
SL3_Author = Author
SL3_License = License
SL3_Features = Features
SL3_Feature1 = Shop links to supplier webshops
SL3_Feature2 = Stock display with colored badges
SL3_Feature3 = Quick supplier order creation from customer orders
SL3_Feature4 = Multi-supplier support with popup selection
SL3_Documentation = Documentation
#
# Sample page
# Stock badges
#
SupplierLink3Area = Home SupplierLink3
MyPageName = My page name
SL3_StockCritical = Critical (< 1)
SL3_StockWarning = Warning (below minimum stock)
SL3_StockLow = Low (below desired stock)
SL3_StockOk = Sufficient
#
# Sample widget
# Supplier order creation
#
MyWidget = My widget
MyWidgetDescription = My widget description
SL3_CreateSupplierOrder = Create Supplier Order
SL3_SelectSupplier = Select supplier
SL3_SelectProducts = Select products
SL3_NoSuppliers = No suppliers available