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:
parent
b970a66339
commit
941aae90f7
11 changed files with 1973 additions and 761 deletions
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
.idea/SupplierLink3.iml
Normal file → Executable file
0
.idea/SupplierLink3.iml
Normal file → Executable file
239
README.md
239
README.md
|
|
@ -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
|
||||
|
||||
<!--
|
||||
{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`
|
||||
|
|
|
|||
134
admin/about.php
134
admin/about.php
|
|
@ -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 <data@data-it-solution.de></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();
|
||||
|
|
|
|||
942
admin/setup.php
942
admin/setup.php
File diff suppressed because it is too large
Load diff
|
|
@ -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 .= ' · 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)
|
||||
{
|
||||
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 .= ' <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 .= ' <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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
359
create_supplier_order.php
Executable 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 ' ';
|
||||
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
167
js/replenish.js
Executable 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
97
langs/de_DE/supplierlink3.lang
Executable 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue