Version 3.2: Cross-Katalog-Suche und Multi-Lieferanten-Preise
- Cross-Katalog-Suche: Artikel werden via EAN/Hersteller-Art.Nr. in allen Katalogen gefunden - Multi-Lieferanten-Anzeige mit Preisvergleich (Prozent guenstiger/teurer) - Fehlende Lieferantenpreise werden bei zugeordneten Produkten angeboten - Fix: Kluxen-Datanorm Preise im A-Record (Cent -> Euro Umrechnung) - Neue Uebersetzungen fuer Lieferanten-Alternativen Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9e8165f1ab
commit
63dd72f5be
7 changed files with 610 additions and 26 deletions
16
ChangeLog.md
16
ChangeLog.md
|
|
@ -1,5 +1,21 @@
|
||||||
# CHANGELOG MODULE IMPORTZUGFERD FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
|
# CHANGELOG MODULE IMPORTZUGFERD FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
|
||||||
|
|
||||||
|
## 3.2
|
||||||
|
|
||||||
|
### Neue Funktionen
|
||||||
|
- Cross-Katalog-Suche: Artikel werden ueber EAN/Hersteller-Artikelnummer in allen Lieferanten-Katalogen gefunden
|
||||||
|
- Multi-Lieferanten-Anzeige: Bei Produktzuordnung werden alle verfuegbaren Lieferanten mit Preisen angezeigt
|
||||||
|
- Fehlende Lieferantenpreise: Bei zugeordneten Produkten werden fehlende EK-Preise anderer Lieferanten angeboten
|
||||||
|
- Preisvergleich mit Prozentangabe (guenstiger/teurer) fuer Lieferanten-Alternativen
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
- Datanorm Import: Kluxen-Format (Preise im A-Record in Cent) wird jetzt korrekt verarbeitet
|
||||||
|
- Datanorm Import: Preise aus A-Record werden von Cent in Euro umgerechnet (geteilt durch 100)
|
||||||
|
|
||||||
|
### Hinweise
|
||||||
|
- Kluxen-Katalog enthaelt nur Listenpreise (UVP), keine Netto-Einkaufspreise
|
||||||
|
- Cross-Katalog-Suche erfordert aktivierte Einstellung "In allen Lieferanten-Katalogen suchen"
|
||||||
|
|
||||||
## 2.1
|
## 2.1
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
|
|
|
||||||
|
|
@ -510,17 +510,85 @@ class Datanorm extends CommonObject
|
||||||
global $conf;
|
global $conf;
|
||||||
|
|
||||||
$results = array();
|
$results = array();
|
||||||
|
$foundEan = '';
|
||||||
|
$foundManufacturerRef = '';
|
||||||
|
$foundIds = array(); // Track found IDs to avoid duplicates
|
||||||
|
|
||||||
// First try exact match with specified supplier
|
// First try exact match with specified supplier
|
||||||
if ($fk_soc > 0) {
|
if ($fk_soc > 0) {
|
||||||
$result = $this->fetchByArticleNumber($fk_soc, $article_number);
|
$result = $this->fetchByArticleNumber($fk_soc, $article_number);
|
||||||
if ($result > 0) {
|
if ($result > 0) {
|
||||||
$results[] = $this->toArray();
|
$results[] = $this->toArray();
|
||||||
|
$foundIds[$this->id] = true;
|
||||||
|
// Store EAN and manufacturer_ref for cross-catalog search
|
||||||
|
$foundEan = $this->ean;
|
||||||
|
$foundManufacturerRef = $this->manufacturer_ref;
|
||||||
|
|
||||||
|
// If not searching all catalogs, return immediately
|
||||||
|
if (!$searchAll) {
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If searchAll is enabled and we found article with EAN/manufacturer_ref,
|
||||||
|
// search other catalogs using these identifiers (cross-catalog search)
|
||||||
|
if ($searchAll && $fk_soc > 0 && (!empty($foundEan) || !empty($foundManufacturerRef))) {
|
||||||
|
$sql = "SELECT rowid, fk_soc, article_number, short_text1, short_text2,";
|
||||||
|
$sql .= " ean, manufacturer_ref, manufacturer_name, unit_code,";
|
||||||
|
$sql .= " price, price_unit, discount_group, product_group, matchcode";
|
||||||
|
$sql .= " FROM " . MAIN_DB_PREFIX . $this->table_element;
|
||||||
|
$sql .= " WHERE (";
|
||||||
|
|
||||||
|
$conditions = array();
|
||||||
|
if (!empty($foundEan)) {
|
||||||
|
$conditions[] = "ean = '" . $this->db->escape($foundEan) . "'";
|
||||||
|
}
|
||||||
|
if (!empty($foundManufacturerRef)) {
|
||||||
|
$conditions[] = "manufacturer_ref = '" . $this->db->escape($foundManufacturerRef) . "'";
|
||||||
|
}
|
||||||
|
$sql .= implode(' OR ', $conditions) . ")";
|
||||||
|
|
||||||
|
$sql .= " AND active = 1";
|
||||||
|
$sql .= " AND entity = " . (int) $conf->entity;
|
||||||
|
$sql .= " AND fk_soc != " . (int) $fk_soc; // Exclude already found supplier
|
||||||
|
|
||||||
|
$sql .= " ORDER BY price ASC"; // Show cheapest alternatives first
|
||||||
|
$sql .= " LIMIT " . (int) ($limit - count($results));
|
||||||
|
|
||||||
|
$resql = $this->db->query($sql);
|
||||||
|
if ($resql) {
|
||||||
|
while ($obj = $this->db->fetch_object($resql)) {
|
||||||
|
if (!isset($foundIds[$obj->rowid])) {
|
||||||
|
$results[] = array(
|
||||||
|
'id' => $obj->rowid,
|
||||||
|
'fk_soc' => $obj->fk_soc,
|
||||||
|
'article_number' => $obj->article_number,
|
||||||
|
'short_text1' => $obj->short_text1,
|
||||||
|
'short_text2' => $obj->short_text2,
|
||||||
|
'ean' => $obj->ean,
|
||||||
|
'manufacturer_ref' => $obj->manufacturer_ref,
|
||||||
|
'manufacturer_name' => $obj->manufacturer_name,
|
||||||
|
'unit_code' => $obj->unit_code,
|
||||||
|
'price' => $obj->price,
|
||||||
|
'price_unit' => $obj->price_unit,
|
||||||
|
'discount_group' => $obj->discount_group,
|
||||||
|
'product_group' => $obj->product_group,
|
||||||
|
'matchcode' => $obj->matchcode,
|
||||||
|
);
|
||||||
|
$foundIds[$obj->rowid] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->db->free($resql);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found results via cross-catalog search, return them
|
||||||
|
if (!empty($results)) {
|
||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search partial match
|
// Fallback: Search by partial match on article_number, ean, or manufacturer_ref
|
||||||
$sql = "SELECT rowid, fk_soc, article_number, short_text1, short_text2,";
|
$sql = "SELECT rowid, fk_soc, article_number, short_text1, short_text2,";
|
||||||
$sql .= " ean, manufacturer_ref, manufacturer_name, unit_code,";
|
$sql .= " ean, manufacturer_ref, manufacturer_name, unit_code,";
|
||||||
$sql .= " price, price_unit, discount_group, product_group, matchcode";
|
$sql .= " price, price_unit, discount_group, product_group, matchcode";
|
||||||
|
|
@ -537,8 +605,8 @@ class Datanorm extends CommonObject
|
||||||
|
|
||||||
// ORDER BY clause
|
// ORDER BY clause
|
||||||
if ($fk_soc > 0 && $searchAll) {
|
if ($fk_soc > 0 && $searchAll) {
|
||||||
// Order by matching supplier first
|
// Order by matching supplier first, then by price
|
||||||
$sql .= " ORDER BY CASE WHEN fk_soc = " . (int) $fk_soc . " THEN 0 ELSE 1 END, article_number";
|
$sql .= " ORDER BY CASE WHEN fk_soc = " . (int) $fk_soc . " THEN 0 ELSE 1 END, price ASC";
|
||||||
} else {
|
} else {
|
||||||
$sql .= " ORDER BY article_number";
|
$sql .= " ORDER BY article_number";
|
||||||
}
|
}
|
||||||
|
|
@ -548,22 +616,25 @@ class Datanorm extends CommonObject
|
||||||
$resql = $this->db->query($sql);
|
$resql = $this->db->query($sql);
|
||||||
if ($resql) {
|
if ($resql) {
|
||||||
while ($obj = $this->db->fetch_object($resql)) {
|
while ($obj = $this->db->fetch_object($resql)) {
|
||||||
$results[] = array(
|
if (!isset($foundIds[$obj->rowid])) {
|
||||||
'id' => $obj->rowid,
|
$results[] = array(
|
||||||
'fk_soc' => $obj->fk_soc,
|
'id' => $obj->rowid,
|
||||||
'article_number' => $obj->article_number,
|
'fk_soc' => $obj->fk_soc,
|
||||||
'short_text1' => $obj->short_text1,
|
'article_number' => $obj->article_number,
|
||||||
'short_text2' => $obj->short_text2,
|
'short_text1' => $obj->short_text1,
|
||||||
'ean' => $obj->ean,
|
'short_text2' => $obj->short_text2,
|
||||||
'manufacturer_ref' => $obj->manufacturer_ref,
|
'ean' => $obj->ean,
|
||||||
'manufacturer_name' => $obj->manufacturer_name,
|
'manufacturer_ref' => $obj->manufacturer_ref,
|
||||||
'unit_code' => $obj->unit_code,
|
'manufacturer_name' => $obj->manufacturer_name,
|
||||||
'price' => $obj->price,
|
'unit_code' => $obj->unit_code,
|
||||||
'price_unit' => $obj->price_unit,
|
'price' => $obj->price,
|
||||||
'discount_group' => $obj->discount_group,
|
'price_unit' => $obj->price_unit,
|
||||||
'product_group' => $obj->product_group,
|
'discount_group' => $obj->discount_group,
|
||||||
'matchcode' => $obj->matchcode,
|
'product_group' => $obj->product_group,
|
||||||
);
|
'matchcode' => $obj->matchcode,
|
||||||
|
);
|
||||||
|
$foundIds[$obj->rowid] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->db->free($resql);
|
$this->db->free($resql);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -435,13 +435,23 @@ class DatanormParser
|
||||||
$firstField = trim($parts[0] ?? '');
|
$firstField = trim($parts[0] ?? '');
|
||||||
|
|
||||||
if ($firstField === 'A' && isset($parts[1]) && strlen(trim($parts[1])) <= 2) {
|
if ($firstField === 'A' && isset($parts[1]) && strlen(trim($parts[1])) <= 2) {
|
||||||
// Sonepar format with action code (N=New, L=Delete, A=Update)
|
// Sonepar/Kluxen format with action code (N=New, L=Delete, A=Update)
|
||||||
// A;N;ArtNr;TextKz;Kurztext1;Kurztext2;PreisKz;PE;ME;Preis;RabGrp;WG;LangTextKey
|
// A;N;ArtNr;TextKz;Kurztext1;Kurztext2;PreisKz;PE;ME;Preis;RabGrp;WG;LangTextKey
|
||||||
// PE is at index 7 and is a CODE (0=1, 1=10, 2=100, 3=1000)
|
// PE is at index 7 and is a CODE (0=1, 1=10, 2=100, 3=1000)
|
||||||
|
// Preis is at index 9 - in CENTS (e.g., 27800 = 278,00 €)
|
||||||
$actionCode = strtoupper(trim($parts[1] ?? 'N'));
|
$actionCode = strtoupper(trim($parts[1] ?? 'N'));
|
||||||
$peCode = (int)trim($parts[7] ?? '0');
|
$peCode = (int)trim($parts[7] ?? '0');
|
||||||
$priceUnit = self::convertPriceUnitCode($peCode);
|
$priceUnit = self::convertPriceUnitCode($peCode);
|
||||||
|
|
||||||
|
// Price from A-record (for formats without separate DATPREIS file like Kluxen)
|
||||||
|
// Price is in cents, convert to euros
|
||||||
|
$priceRaw = trim($parts[9] ?? '0');
|
||||||
|
$price = 0.0;
|
||||||
|
if (!empty($priceRaw) && is_numeric($priceRaw)) {
|
||||||
|
// Price is in cents (integer without decimal), convert to euros
|
||||||
|
$price = (float)$priceRaw / 100;
|
||||||
|
}
|
||||||
|
|
||||||
$article = array(
|
$article = array(
|
||||||
'article_number' => trim($parts[2] ?? ''),
|
'article_number' => trim($parts[2] ?? ''),
|
||||||
'action_code' => $actionCode, // N=New, A=Update, L=Delete
|
'action_code' => $actionCode, // N=New, A=Update, L=Delete
|
||||||
|
|
@ -458,7 +468,7 @@ class DatanormParser
|
||||||
'manufacturer_name' => '',
|
'manufacturer_name' => '',
|
||||||
'ean' => '',
|
'ean' => '',
|
||||||
'long_text' => '',
|
'long_text' => '',
|
||||||
'price' => 0,
|
'price' => $price, // Price from A-record (in euros)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Standard format: A;ArtNr;Matchcode;Kurztext1;Kurztext2;ME;PE;RabGrp;WG;...
|
// Standard format: A;ArtNr;Matchcode;Kurztext1;Kurztext2;ME;PE;RabGrp;WG;...
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ class modImportZugferd extends DolibarrModules
|
||||||
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@importzugferd'
|
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@importzugferd'
|
||||||
|
|
||||||
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||||
$this->version = '3.0';
|
$this->version = '3.2';
|
||||||
// Url to the file with your last numberversion of this module
|
// Url to the file with your last numberversion of this module
|
||||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||||
|
|
||||||
|
|
|
||||||
467
import.php
467
import.php
|
|
@ -391,6 +391,80 @@ if ($action == 'removeproduct' && $line_id > 0) {
|
||||||
$import->fetch($id);
|
$import->fetch($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add missing supplier prices from other catalogs
|
||||||
|
if ($action == 'addmissingprices' && $id > 0) {
|
||||||
|
$import->fetch($id);
|
||||||
|
$fk_product = GETPOSTINT('fk_product');
|
||||||
|
$addSupplierPrices = GETPOST('add_supplier_prices', 'array');
|
||||||
|
|
||||||
|
if ($fk_product > 0 && !empty($addSupplierPrices)) {
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
|
||||||
|
require_once './class/datanorm.class.php';
|
||||||
|
require_once './class/productmapping.class.php';
|
||||||
|
|
||||||
|
$addedCount = 0;
|
||||||
|
|
||||||
|
foreach ($addSupplierPrices as $socId => $datanormId) {
|
||||||
|
// Fetch the Datanorm article
|
||||||
|
$datanorm = new Datanorm($db);
|
||||||
|
if ($datanorm->fetch($datanormId) > 0) {
|
||||||
|
$altSupplier = new Societe($db);
|
||||||
|
$altSupplier->fetch($socId);
|
||||||
|
|
||||||
|
$purchasePrice = $datanorm->price;
|
||||||
|
if ($datanorm->price_unit > 1) {
|
||||||
|
$purchasePrice = $datanorm->price / $datanorm->price_unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare extrafields
|
||||||
|
$extrafields = array();
|
||||||
|
if (!empty($datanorm->metal_surcharge) && $datanorm->metal_surcharge > 0) {
|
||||||
|
$extrafields['options_kupferzuschlag'] = $datanorm->metal_surcharge;
|
||||||
|
}
|
||||||
|
if (!empty($datanorm->price_unit) && $datanorm->price_unit > 1) {
|
||||||
|
$extrafields['options_preiseinheit'] = $datanorm->price_unit;
|
||||||
|
}
|
||||||
|
if (!empty($datanorm->product_group)) {
|
||||||
|
$extrafields['options_warengruppe'] = $datanorm->product_group;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add supplier price
|
||||||
|
$prodfourn = new ProductFournisseur($db);
|
||||||
|
$prodfourn->id = $fk_product;
|
||||||
|
$result = $prodfourn->update_buyprice(
|
||||||
|
1, $purchasePrice, $user, 'HT', $altSupplier, 0,
|
||||||
|
$datanorm->article_number, 19,
|
||||||
|
0, 0, 0, 0, 0, 0, array(), '',
|
||||||
|
0, 'HT', 1, '',
|
||||||
|
trim($datanorm->short_text1 . ($datanorm->short_text2 ? ' ' . $datanorm->short_text2 : '')),
|
||||||
|
!empty($datanorm->ean) ? $datanorm->ean : '',
|
||||||
|
!empty($datanorm->ean) ? 2 : 0,
|
||||||
|
$extrafields
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($result > 0) {
|
||||||
|
// Create product mapping
|
||||||
|
$mapping = new ProductMapping($db);
|
||||||
|
$mapping->fk_soc = $socId;
|
||||||
|
$mapping->supplier_ref = $datanorm->article_number;
|
||||||
|
$mapping->fk_product = $fk_product;
|
||||||
|
$mapping->ean = $datanorm->ean;
|
||||||
|
$mapping->manufacturer_ref = $datanorm->manufacturer_ref;
|
||||||
|
$mapping->description = $datanorm->short_text1;
|
||||||
|
$mapping->create($user);
|
||||||
|
|
||||||
|
$addedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($addedCount > 0) {
|
||||||
|
setEventMessages($langs->trans('SupplierPricesAdded', $addedCount), null, 'mesgs');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$action = 'edit';
|
||||||
|
}
|
||||||
|
|
||||||
// Update supplier
|
// Update supplier
|
||||||
if ($action == 'setsupplier' && $id > 0) {
|
if ($action == 'setsupplier' && $id > 0) {
|
||||||
$import->fetch($id);
|
$import->fetch($id);
|
||||||
|
|
@ -883,6 +957,70 @@ if ($action == 'createallfromdatanorm' && $id > 0) {
|
||||||
if ($productExists && $existingProductId > 0) {
|
if ($productExists && $existingProductId > 0) {
|
||||||
// Product exists - just assign it to the line
|
// Product exists - just assign it to the line
|
||||||
$lineObj->setProduct($existingProductId, 'datanorm', $user);
|
$lineObj->setProduct($existingProductId, 'datanorm', $user);
|
||||||
|
|
||||||
|
// Add additional supplier prices from selected alternatives (for existing products too)
|
||||||
|
$supplierPricesPost = GETPOST('supplier_prices', 'array');
|
||||||
|
if (!empty($supplierPricesPost[$lineObj->id])) {
|
||||||
|
foreach ($supplierPricesPost[$lineObj->id] as $altSocId => $altDatanormId) {
|
||||||
|
// Check if supplier price already exists for this product/supplier
|
||||||
|
$sqlCheckSupplier = "SELECT rowid FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
|
||||||
|
$sqlCheckSupplier .= " WHERE fk_product = ".(int)$existingProductId;
|
||||||
|
$sqlCheckSupplier .= " AND fk_soc = ".(int)$altSocId;
|
||||||
|
$resCheckSupplier = $db->query($sqlCheckSupplier);
|
||||||
|
if ($resCheckSupplier && $db->num_rows($resCheckSupplier) > 0) {
|
||||||
|
continue; // Skip if supplier price already exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the alternative Datanorm article
|
||||||
|
$altDatanorm = new Datanorm($db);
|
||||||
|
if ($altDatanorm->fetch($altDatanormId) > 0) {
|
||||||
|
$altSupplier = new Societe($db);
|
||||||
|
$altSupplier->fetch($altSocId);
|
||||||
|
|
||||||
|
$altPurchasePrice = $altDatanorm->price;
|
||||||
|
if ($altDatanorm->price_unit > 1) {
|
||||||
|
$altPurchasePrice = $altDatanorm->price / $altDatanorm->price_unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare extrafields
|
||||||
|
$altExtrafields = array();
|
||||||
|
if (!empty($altDatanorm->metal_surcharge) && $altDatanorm->metal_surcharge > 0) {
|
||||||
|
$altExtrafields['options_kupferzuschlag'] = $altDatanorm->metal_surcharge;
|
||||||
|
}
|
||||||
|
if (!empty($altDatanorm->price_unit) && $altDatanorm->price_unit > 1) {
|
||||||
|
$altExtrafields['options_preiseinheit'] = $altDatanorm->price_unit;
|
||||||
|
}
|
||||||
|
if (!empty($altDatanorm->product_group)) {
|
||||||
|
$altExtrafields['options_warengruppe'] = $altDatanorm->product_group;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add supplier price
|
||||||
|
$altProdfourn = new ProductFournisseur($db);
|
||||||
|
$altProdfourn->id = $existingProductId;
|
||||||
|
$altProdfourn->update_buyprice(
|
||||||
|
1, $altPurchasePrice, $user, 'HT', $altSupplier, 0,
|
||||||
|
$altDatanorm->article_number, $lineObj->tax_percent ?: 19,
|
||||||
|
0, 0, 0, 0, 0, 0, array(), '',
|
||||||
|
0, 'HT', 1, '',
|
||||||
|
trim($altDatanorm->short_text1 . ($altDatanorm->short_text2 ? ' ' . $altDatanorm->short_text2 : '')),
|
||||||
|
!empty($altDatanorm->ean) ? $altDatanorm->ean : '',
|
||||||
|
!empty($altDatanorm->ean) ? 2 : 0,
|
||||||
|
$altExtrafields
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create product mapping
|
||||||
|
$altMapping = new ProductMapping($db);
|
||||||
|
$altMapping->fk_soc = $altSocId;
|
||||||
|
$altMapping->supplier_ref = $altDatanorm->article_number;
|
||||||
|
$altMapping->fk_product = $existingProductId;
|
||||||
|
$altMapping->ean = $altDatanorm->ean;
|
||||||
|
$altMapping->manufacturer_ref = $altDatanorm->manufacturer_ref;
|
||||||
|
$altMapping->description = $altDatanorm->short_text1;
|
||||||
|
$altMapping->create($user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$assignedCount++;
|
$assignedCount++;
|
||||||
} else {
|
} else {
|
||||||
// Create new product
|
// Create new product
|
||||||
|
|
@ -1025,6 +1163,71 @@ if ($action == 'createallfromdatanorm' && $id > 0) {
|
||||||
$mapping->description = $datanorm->short_text1;
|
$mapping->description = $datanorm->short_text1;
|
||||||
$mapping->create($user);
|
$mapping->create($user);
|
||||||
|
|
||||||
|
// Add additional supplier prices from selected alternatives
|
||||||
|
$supplierPricesPost = GETPOST('supplier_prices', 'array');
|
||||||
|
if (!empty($supplierPricesPost[$lineObj->id])) {
|
||||||
|
foreach ($supplierPricesPost[$lineObj->id] as $altSocId => $altDatanormId) {
|
||||||
|
// Skip the main invoice supplier (already added above)
|
||||||
|
if ($altSocId == $import->fk_soc) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the alternative Datanorm article
|
||||||
|
$altDatanorm = new Datanorm($db);
|
||||||
|
if ($altDatanorm->fetch($altDatanormId) > 0) {
|
||||||
|
$altSupplier = new Societe($db);
|
||||||
|
$altSupplier->fetch($altSocId);
|
||||||
|
|
||||||
|
$altPurchasePrice = $altDatanorm->price;
|
||||||
|
if ($altDatanorm->price_unit > 1) {
|
||||||
|
$altPurchasePrice = $altDatanorm->price / $altDatanorm->price_unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare extrafields for alternative supplier price
|
||||||
|
$altExtrafields = array();
|
||||||
|
if (!empty($altDatanorm->metal_surcharge) && $altDatanorm->metal_surcharge > 0) {
|
||||||
|
$altExtrafields['options_kupferzuschlag'] = $altDatanorm->metal_surcharge;
|
||||||
|
}
|
||||||
|
if (!empty($altDatanorm->price_unit) && $altDatanorm->price_unit > 1) {
|
||||||
|
$altExtrafields['options_preiseinheit'] = $altDatanorm->price_unit;
|
||||||
|
}
|
||||||
|
if (!empty($altDatanorm->product_group)) {
|
||||||
|
$altExtrafields['options_warengruppe'] = $altDatanorm->product_group;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add supplier price for alternative supplier
|
||||||
|
$altProdfourn = new ProductFournisseur($db);
|
||||||
|
$altProdfourn->id = $newproduct->id;
|
||||||
|
$altProdfourn->update_buyprice(
|
||||||
|
1, // Quantity
|
||||||
|
$altPurchasePrice, // Price
|
||||||
|
$user,
|
||||||
|
'HT', // Price base
|
||||||
|
$altSupplier, // Alternative supplier
|
||||||
|
0, // Availability
|
||||||
|
$altDatanorm->article_number, // Supplier ref
|
||||||
|
$lineObj->tax_percent ?: 19, // VAT
|
||||||
|
0, 0, 0, 0, 0, 0, array(), '',
|
||||||
|
0, 'HT', 1, '',
|
||||||
|
trim($altDatanorm->short_text1 . ($altDatanorm->short_text2 ? ' ' . $altDatanorm->short_text2 : '')),
|
||||||
|
!empty($altDatanorm->ean) ? $altDatanorm->ean : '',
|
||||||
|
!empty($altDatanorm->ean) ? 2 : 0,
|
||||||
|
$altExtrafields
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create product mapping for alternative supplier
|
||||||
|
$altMapping = new ProductMapping($db);
|
||||||
|
$altMapping->fk_soc = $altSocId;
|
||||||
|
$altMapping->supplier_ref = $altDatanorm->article_number;
|
||||||
|
$altMapping->fk_product = $newproduct->id;
|
||||||
|
$altMapping->ean = $altDatanorm->ean;
|
||||||
|
$altMapping->manufacturer_ref = $altDatanorm->manufacturer_ref;
|
||||||
|
$altMapping->description = $altDatanorm->short_text1;
|
||||||
|
$altMapping->create($user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Assign to import line
|
// Assign to import line
|
||||||
$lineObj->setProduct($newproduct->id, 'datanorm', $user);
|
$lineObj->setProduct($newproduct->id, 'datanorm', $user);
|
||||||
$createdCount++;
|
$createdCount++;
|
||||||
|
|
@ -1091,14 +1294,15 @@ if ($action == 'previewdatanorm' && $id > 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search in Datanorm database
|
// Search in Datanorm database - get ALL supplier alternatives
|
||||||
$results = $datanorm->searchByArticleNumber($lineObj->supplier_ref, $import->fk_soc, $searchAll, 1);
|
$results = $datanorm->searchByArticleNumber($lineObj->supplier_ref, $import->fk_soc, $searchAll, 10);
|
||||||
|
|
||||||
if (empty($results) && !empty($lineObj->ean)) {
|
if (empty($results) && !empty($lineObj->ean)) {
|
||||||
$results = $datanorm->searchByArticleNumber($lineObj->ean, $import->fk_soc, $searchAll, 1);
|
$results = $datanorm->searchByArticleNumber($lineObj->ean, $import->fk_soc, $searchAll, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($results)) {
|
if (!empty($results)) {
|
||||||
|
// Process the primary result (first = current supplier or cheapest)
|
||||||
$datanormArticle = $results[0];
|
$datanormArticle = $results[0];
|
||||||
$datanorm->fetch($datanormArticle['id']);
|
$datanorm->fetch($datanormArticle['id']);
|
||||||
|
|
||||||
|
|
@ -1164,6 +1368,32 @@ if ($action == 'previewdatanorm' && $id > 0) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build supplier alternatives array
|
||||||
|
$supplierAlternatives = array();
|
||||||
|
foreach ($results as $altResult) {
|
||||||
|
$altSupplier = new Societe($db);
|
||||||
|
$altSupplier->fetch($altResult['fk_soc']);
|
||||||
|
|
||||||
|
$altPurchasePrice = $altResult['price'];
|
||||||
|
if ($altResult['price_unit'] > 1) {
|
||||||
|
$altPurchasePrice = $altResult['price'] / $altResult['price_unit'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$supplierAlternatives[] = array(
|
||||||
|
'datanorm_id' => $altResult['id'],
|
||||||
|
'fk_soc' => $altResult['fk_soc'],
|
||||||
|
'supplier_name' => $altSupplier->name,
|
||||||
|
'article_number' => $altResult['article_number'],
|
||||||
|
'short_text1' => $altResult['short_text1'],
|
||||||
|
'price' => $altResult['price'],
|
||||||
|
'price_unit' => $altResult['price_unit'],
|
||||||
|
'purchase_price' => $altPurchasePrice,
|
||||||
|
'ean' => $altResult['ean'],
|
||||||
|
'manufacturer_ref' => $altResult['manufacturer_ref'],
|
||||||
|
'is_invoice_supplier' => ($altResult['fk_soc'] == $import->fk_soc),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Store match info for preview
|
// Store match info for preview
|
||||||
$datanormPreviewMatches[] = array(
|
$datanormPreviewMatches[] = array(
|
||||||
'line_id' => $lineObj->id,
|
'line_id' => $lineObj->id,
|
||||||
|
|
@ -1183,7 +1413,8 @@ if ($action == 'previewdatanorm' && $id > 0) {
|
||||||
'copper_surcharge' => $copperSurchargeForPrice,
|
'copper_surcharge' => $copperSurchargeForPrice,
|
||||||
'existing_product_id' => $existingProductId,
|
'existing_product_id' => $existingProductId,
|
||||||
'action' => $productAction,
|
'action' => $productAction,
|
||||||
'new_ref' => 'NEW-'.$supplierPrefix.'-'.$datanorm->article_number
|
'new_ref' => 'NEW-'.$supplierPrefix.'-'.$datanorm->article_number,
|
||||||
|
'supplier_alternatives' => $supplierAlternatives
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1723,6 +1954,117 @@ if ($action == 'edit' && $import->id > 0) {
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?action=removeproduct&line_id='.$line->id.'&id='.$import->id.'&token='.newToken().'" class="button buttongen">';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?action=removeproduct&line_id='.$line->id.'&id='.$import->id.'&token='.newToken().'" class="button buttongen">';
|
||||||
print '<i class="fas fa-times"></i>';
|
print '<i class="fas fa-times"></i>';
|
||||||
print '</a>';
|
print '</a>';
|
||||||
|
|
||||||
|
// Check for missing supplier prices from other catalogs
|
||||||
|
if ($import->fk_soc > 0 && getDolGlobalString('IMPORTZUGFERD_DATANORM_SEARCH_ALL')) {
|
||||||
|
// Get existing supplier prices for this product
|
||||||
|
$sqlExistingPrices = "SELECT fk_soc FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
|
||||||
|
$sqlExistingPrices .= " WHERE fk_product = ".(int)$line->fk_product;
|
||||||
|
$resExistingPrices = $db->query($sqlExistingPrices);
|
||||||
|
$existingSupplierIds = array();
|
||||||
|
if ($resExistingPrices) {
|
||||||
|
while ($objPrice = $db->fetch_object($resExistingPrices)) {
|
||||||
|
$existingSupplierIds[$objPrice->fk_soc] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current supplier price for comparison
|
||||||
|
$currentSupplierPrice = 0;
|
||||||
|
if (isset($existingSupplierIds[$import->fk_soc])) {
|
||||||
|
$sqlCurrentPrice = "SELECT price FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
|
||||||
|
$sqlCurrentPrice .= " WHERE fk_product = ".(int)$line->fk_product;
|
||||||
|
$sqlCurrentPrice .= " AND fk_soc = ".(int)$import->fk_soc;
|
||||||
|
$sqlCurrentPrice .= " ORDER BY unitprice ASC LIMIT 1";
|
||||||
|
$resCurrentPrice = $db->query($sqlCurrentPrice);
|
||||||
|
if ($resCurrentPrice && $db->num_rows($resCurrentPrice) > 0) {
|
||||||
|
$objCurrentPrice = $db->fetch_object($resCurrentPrice);
|
||||||
|
$currentSupplierPrice = $objCurrentPrice->price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search in all Datanorm catalogs for this article
|
||||||
|
$datanormSearch = new Datanorm($db);
|
||||||
|
$searchRef = !empty($line->supplier_ref) ? $line->supplier_ref : (!empty($line->ean) ? $line->ean : '');
|
||||||
|
if (!empty($searchRef)) {
|
||||||
|
$allCatalogResults = $datanormSearch->searchByArticleNumber($searchRef, $import->fk_soc, true, 10);
|
||||||
|
|
||||||
|
// Filter to only show suppliers without existing price
|
||||||
|
$missingSuppliers = array();
|
||||||
|
foreach ($allCatalogResults as $catalogResult) {
|
||||||
|
if (!isset($existingSupplierIds[$catalogResult['fk_soc']])) {
|
||||||
|
// Load supplier name
|
||||||
|
$altSupplier = new Societe($db);
|
||||||
|
$altSupplier->fetch($catalogResult['fk_soc']);
|
||||||
|
|
||||||
|
$altPurchasePrice = $catalogResult['price'];
|
||||||
|
if ($catalogResult['price_unit'] > 1) {
|
||||||
|
$altPurchasePrice = $catalogResult['price'] / $catalogResult['price_unit'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$missingSuppliers[] = array(
|
||||||
|
'datanorm_id' => $catalogResult['id'],
|
||||||
|
'fk_soc' => $catalogResult['fk_soc'],
|
||||||
|
'supplier_name' => $altSupplier->name,
|
||||||
|
'article_number' => $catalogResult['article_number'],
|
||||||
|
'price' => $catalogResult['price'],
|
||||||
|
'price_unit' => $catalogResult['price_unit'],
|
||||||
|
'purchase_price' => $altPurchasePrice,
|
||||||
|
'ean' => $catalogResult['ean'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show missing suppliers with checkboxes
|
||||||
|
if (!empty($missingSuppliers)) {
|
||||||
|
print '<div style="margin-top: 8px; padding: 8px; background-color: #fcf8e3; border: 1px solid #faebcc; border-radius: 4px; font-size: 0.85em;">';
|
||||||
|
print '<div style="font-weight: bold; color: #8a6d3b; margin-bottom: 5px;">';
|
||||||
|
print '<i class="fas fa-plus-circle"></i> '.$langs->trans('MissingSupplierPrices');
|
||||||
|
print '</div>';
|
||||||
|
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'" style="margin: 0;">';
|
||||||
|
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||||
|
print '<input type="hidden" name="action" value="addmissingprices">';
|
||||||
|
print '<input type="hidden" name="id" value="'.$import->id.'">';
|
||||||
|
print '<input type="hidden" name="fk_product" value="'.$line->fk_product.'">';
|
||||||
|
|
||||||
|
foreach ($missingSuppliers as $missing) {
|
||||||
|
$priceDiff = 0;
|
||||||
|
$priceDiffPercent = 0;
|
||||||
|
$diffHtml = '';
|
||||||
|
|
||||||
|
if ($currentSupplierPrice > 0) {
|
||||||
|
$priceDiff = $missing['purchase_price'] - $currentSupplierPrice;
|
||||||
|
$priceDiffPercent = ($priceDiff / $currentSupplierPrice) * 100;
|
||||||
|
|
||||||
|
if ($priceDiff < 0) {
|
||||||
|
$diffHtml = '<span style="color: #5cb85c;"><i class="fas fa-arrow-down"></i> '.number_format(abs($priceDiffPercent), 1).'%</span>';
|
||||||
|
} elseif ($priceDiff > 0) {
|
||||||
|
$diffHtml = '<span style="color: #d9534f;"><i class="fas fa-arrow-up"></i> +'.number_format($priceDiffPercent, 1).'%</span>';
|
||||||
|
} else {
|
||||||
|
$diffHtml = '<span class="opacitymedium">=</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print '<div style="padding: 3px 0;">';
|
||||||
|
print '<label style="cursor: pointer;">';
|
||||||
|
print '<input type="checkbox" name="add_supplier_prices['.$missing['fk_soc'].']" value="'.$missing['datanorm_id'].'" style="margin-right: 5px;">';
|
||||||
|
print '<strong>'.dol_escape_htmltag($missing['supplier_name']).'</strong>';
|
||||||
|
print ' <code style="font-size: 0.9em;">'.dol_escape_htmltag($missing['article_number']).'</code>';
|
||||||
|
print ' @ <strong>'.price($missing['purchase_price']).'</strong>';
|
||||||
|
if (!empty($diffHtml)) {
|
||||||
|
print ' '.$diffHtml;
|
||||||
|
}
|
||||||
|
print '</label>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
print '<button type="submit" class="button buttongen small" style="margin-top: 5px;">';
|
||||||
|
print '<i class="fas fa-plus"></i> '.$langs->trans('AddSelectedPrices');
|
||||||
|
print '</button>';
|
||||||
|
print '</form>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Product selection form
|
// Product selection form
|
||||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'" class="inline-block">';
|
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'" class="inline-block">';
|
||||||
|
|
@ -1953,6 +2295,123 @@ if ($action == 'edit' && $import->id > 0) {
|
||||||
}
|
}
|
||||||
print '</td>';
|
print '</td>';
|
||||||
print '</tr>';
|
print '</tr>';
|
||||||
|
|
||||||
|
// Show supplier alternatives if available (more than 1 supplier found)
|
||||||
|
if (!empty($match['supplier_alternatives']) && count($match['supplier_alternatives']) > 1) {
|
||||||
|
print '<tr class="oddeven" style="background-color: #f9f9f9;">';
|
||||||
|
print '<td></td>'; // Empty checkbox column
|
||||||
|
print '<td colspan="7" style="padding: 10px;">';
|
||||||
|
print '<div style="background-color: #fff; border: 1px solid #ddd; border-radius: 5px; padding: 10px;">';
|
||||||
|
print '<div style="font-weight: bold; margin-bottom: 8px; color: #555;">';
|
||||||
|
print '<i class="fas fa-store-alt paddingright"></i>'.$langs->trans('SupplierAlternatives');
|
||||||
|
print ' <span class="badge" style="background-color: #5bc0de;">'.count($match['supplier_alternatives']).' '.$langs->trans('Suppliers').'</span>';
|
||||||
|
print '</div>';
|
||||||
|
|
||||||
|
print '<table class="noborder" style="width: 100%; font-size: 0.9em;">';
|
||||||
|
print '<tr style="background-color: #f5f5f5;">';
|
||||||
|
print '<th class="center" style="width: 40px;">'.$langs->trans('Select').'</th>';
|
||||||
|
print '<th>'.$langs->trans('Supplier').'</th>';
|
||||||
|
print '<th>'.$langs->trans('SupplierRef').'</th>';
|
||||||
|
print '<th>'.$langs->trans('EAN').'</th>';
|
||||||
|
print '<th>'.$langs->trans('ManufacturerRef').'</th>';
|
||||||
|
print '<th class="right">'.$langs->trans('UnitPrice').'</th>';
|
||||||
|
print '<th class="right">'.$langs->trans('Difference').'</th>';
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
|
$lowestPrice = PHP_FLOAT_MAX;
|
||||||
|
foreach ($match['supplier_alternatives'] as $alt) {
|
||||||
|
if ($alt['purchase_price'] < $lowestPrice) {
|
||||||
|
$lowestPrice = $alt['purchase_price'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($match['supplier_alternatives'] as $altIdx => $alt) {
|
||||||
|
$isInvoiceSupplier = $alt['is_invoice_supplier'];
|
||||||
|
$isCheapest = ($alt['purchase_price'] == $lowestPrice);
|
||||||
|
$rowStyle = '';
|
||||||
|
if ($isInvoiceSupplier) {
|
||||||
|
$rowStyle = 'background-color: #d9edf7;'; // Blue for invoice supplier
|
||||||
|
} elseif ($isCheapest) {
|
||||||
|
$rowStyle = 'background-color: #dff0d8;'; // Green for cheapest
|
||||||
|
}
|
||||||
|
|
||||||
|
print '<tr style="'.$rowStyle.'">';
|
||||||
|
|
||||||
|
// Checkbox for selecting this supplier as purchase source
|
||||||
|
print '<td class="center">';
|
||||||
|
$checkboxName = 'supplier_prices['.$match['line_id'].']['.$alt['fk_soc'].']';
|
||||||
|
$checked = $isInvoiceSupplier ? ' checked' : '';
|
||||||
|
print '<input type="checkbox" name="'.$checkboxName.'" value="'.$alt['datanorm_id'].'"'.$checked.' title="'.$langs->trans('AddAsPurchasePrice').'">';
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
// Supplier name
|
||||||
|
print '<td>';
|
||||||
|
print '<strong>'.dol_escape_htmltag($alt['supplier_name']).'</strong>';
|
||||||
|
if ($isInvoiceSupplier) {
|
||||||
|
print ' <span class="badge" style="background-color: #337ab7; font-size: 0.75em;"><i class="fas fa-file-invoice"></i></span>';
|
||||||
|
}
|
||||||
|
if ($isCheapest) {
|
||||||
|
print ' <span class="badge" style="background-color: #5cb85c; font-size: 0.75em;"><i class="fas fa-tag"></i></span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
// Supplier article number
|
||||||
|
print '<td><code>'.dol_escape_htmltag($alt['article_number']).'</code></td>';
|
||||||
|
|
||||||
|
// EAN
|
||||||
|
print '<td>';
|
||||||
|
if (!empty($alt['ean'])) {
|
||||||
|
print '<span class="small">'.dol_escape_htmltag($alt['ean']).'</span>';
|
||||||
|
} else {
|
||||||
|
print '<span class="opacitymedium">-</span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
// Manufacturer ref
|
||||||
|
print '<td>';
|
||||||
|
if (!empty($alt['manufacturer_ref'])) {
|
||||||
|
print '<span class="small">'.dol_escape_htmltag($alt['manufacturer_ref']).'</span>';
|
||||||
|
} else {
|
||||||
|
print '<span class="opacitymedium">-</span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
// Price
|
||||||
|
print '<td class="right nowraponall">';
|
||||||
|
if ($alt['price_unit'] > 1) {
|
||||||
|
print '<span class="small" style="color: #666;">'.price($alt['price']).'/'.$alt['price_unit'].'</span><br>';
|
||||||
|
}
|
||||||
|
print '<strong>'.price($alt['purchase_price']).'</strong>';
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
// Difference from invoice supplier price
|
||||||
|
print '<td class="right nowraponall">';
|
||||||
|
if (!$isInvoiceSupplier && isset($match['purchase_price'])) {
|
||||||
|
$diff = $alt['purchase_price'] - $match['purchase_price'];
|
||||||
|
$diffPercent = ($match['purchase_price'] > 0) ? ($diff / $match['purchase_price'] * 100) : 0;
|
||||||
|
if ($diff < 0) {
|
||||||
|
print '<span style="color: #5cb85c;"><i class="fas fa-arrow-down"></i> '.price(abs($diff)).' ('.number_format(abs($diffPercent), 1).'%)</span>';
|
||||||
|
} elseif ($diff > 0) {
|
||||||
|
print '<span style="color: #d9534f;"><i class="fas fa-arrow-up"></i> +'.price($diff).' (+'.number_format($diffPercent, 1).'%)</span>';
|
||||||
|
} else {
|
||||||
|
print '<span class="opacitymedium">=</span>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print '<span class="opacitymedium">-</span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
print '</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
print '</table>';
|
||||||
|
print '<div class="small opacitymedium" style="margin-top: 5px;">';
|
||||||
|
print '<i class="fas fa-info-circle"></i> '.$langs->trans('SelectSuppliersForPurchasePrices');
|
||||||
|
print '</div>';
|
||||||
|
print '</div>';
|
||||||
|
print '</td>';
|
||||||
|
print '</tr>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print '</table>';
|
print '</table>';
|
||||||
|
|
|
||||||
|
|
@ -472,3 +472,17 @@ BoxNewProductsToReview = Neue Produkte prüfen
|
||||||
NewProductsToReview = Neue Produkte prüfen
|
NewProductsToReview = Neue Produkte prüfen
|
||||||
NoNewProductsToReview = Keine neuen Produkte zur Überprüfung
|
NoNewProductsToReview = Keine neuen Produkte zur Überprüfung
|
||||||
ShowAll = Alle anzeigen
|
ShowAll = Alle anzeigen
|
||||||
|
|
||||||
|
#
|
||||||
|
# Multi-Supplier Alternatives
|
||||||
|
#
|
||||||
|
SupplierAlternatives = Lieferanten-Alternativen
|
||||||
|
Suppliers = Lieferanten
|
||||||
|
AddAsPurchasePrice = Als Einkaufspreis hinzufügen
|
||||||
|
SelectSuppliersForPurchasePrices = Wählen Sie die Lieferanten aus, bei denen ein Einkaufspreis hinterlegt werden soll
|
||||||
|
ManufacturerRef = Hersteller-Art.Nr.
|
||||||
|
MissingSupplierPrices = Fehlende Lieferantenpreise
|
||||||
|
AddSelectedPrices = Ausgewählte hinzufügen
|
||||||
|
SupplierPricesAdded = %s Lieferantenpreise hinzugefügt
|
||||||
|
CheaperBy = %s%% günstiger
|
||||||
|
MoreExpensiveBy = %s%% teurer
|
||||||
|
|
|
||||||
|
|
@ -403,3 +403,17 @@ BoxNewProductsToReview = New Products to Review
|
||||||
NewProductsToReview = New Products to Review
|
NewProductsToReview = New Products to Review
|
||||||
NoNewProductsToReview = No new products to review
|
NoNewProductsToReview = No new products to review
|
||||||
ShowAll = Show all
|
ShowAll = Show all
|
||||||
|
|
||||||
|
#
|
||||||
|
# Multi-Supplier Alternatives
|
||||||
|
#
|
||||||
|
SupplierAlternatives = Supplier Alternatives
|
||||||
|
Suppliers = Suppliers
|
||||||
|
AddAsPurchasePrice = Add as Purchase Price
|
||||||
|
SelectSuppliersForPurchasePrices = Select suppliers where a purchase price should be stored
|
||||||
|
ManufacturerRef = Manufacturer Ref
|
||||||
|
MissingSupplierPrices = Missing Supplier Prices
|
||||||
|
AddSelectedPrices = Add Selected
|
||||||
|
SupplierPricesAdded = %s supplier prices added
|
||||||
|
CheaperBy = %s%% cheaper
|
||||||
|
MoreExpensiveBy = %s%% more expensive
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue