diff --git a/ChangeLog.md b/ChangeLog.md index 40450f7..4e00490 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,19 @@ # CHANGELOG MODULE IMPORTZUGFERD FOR [DOLIBARR ERP CRM](https://www.dolibarr.org) +## 3.4 + +### Bugfixes +- Fehlende Lieferantenpreise: EAN-basierte Suche nutzt jetzt Barcode aus Lieferantenpreis statt Artikelnummer +- Fehlende Lieferantenpreise: Unique-Key auf Barcode entfernt (mehrere Lieferanten koennen gleichen EAN haben) +- Fehlende Lieferantenpreise: Variable $extrafields Namenskollision mit Dolibarr-Core behoben +- Fehlende Lieferantenpreise: Duplikate bei gleichen Produkten auf mehreren Rechnungszeilen vermieden +- Produktauswahl: select2-Suche funktioniert jetzt auf allen Zeilen (eindeutige HTML-IDs) + +### Verbesserungen +- Fehlende Lieferantenpreise in konsolidiertem Bereich unterhalb der Produkttabelle +- Refresh-Button fuer Produktlisten nach Anlage neuer Produkte +- Alle auswaehlen / Keine auswaehlen fuer fehlende Lieferantenpreise + ## 3.3 ### Sicherheit und Code-Qualitaet diff --git a/core/modules/modImportZugferd.class.php b/core/modules/modImportZugferd.class.php index 1a7eb6b..f4e8d9a 100755 --- a/core/modules/modImportZugferd.class.php +++ b/core/modules/modImportZugferd.class.php @@ -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' // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' - $this->version = '3.3'; + $this->version = '3.4'; // Url to the file with your last numberversion of this module //$this->url_last_version = 'http://www.example.com/versionmodule.txt'; diff --git a/import.php b/import.php index f97b6c1..83eb27c 100755 --- a/import.php +++ b/import.php @@ -77,6 +77,13 @@ $supplier_id = GETPOST('supplier_id', 'int'); $line_id = GETPOST('line_id', 'int'); $product_id = GETPOST('product_id', 'int'); $template_product_id = GETPOST('template_product_id', 'int'); +// Zeilenspezifische Produkt-IDs (wegen eindeutiger select2-IDs pro Zeile) +if (empty($product_id) && $line_id > 0) { + $product_id = GETPOST('product_id_'.$line_id, 'int'); +} +if (empty($template_product_id) && $line_id > 0) { + $template_product_id = GETPOST('template_product_id_'.$line_id, 'int'); +} // Initialize objects $form = new Form($db); @@ -499,21 +506,34 @@ if ($action == 'removeproduct' && $line_id > 0) { $import->fetch($id); } -// Add missing supplier prices from other catalogs +// Fehlende Lieferantenpreise aus anderen Katalogen hinzufuegen 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'; + $selectedPrices = GETPOST('add_prices', 'array'); + if (!empty($selectedPrices)) { $addedCount = 0; + $processedKeys = array(); + + foreach ($selectedPrices as $entry) { + // Duplikate ueberspringen + if (isset($processedKeys[$entry])) { + continue; + } + $processedKeys[$entry] = true; + + $parts = explode(',', $entry); + if (count($parts) !== 3) { + continue; + } + $productId = (int) $parts[0]; + $socId = (int) $parts[1]; + $datanormId = (int) $parts[2]; + + if ($productId <= 0 || $socId <= 0 || $datanormId <= 0) { + continue; + } - foreach ($addSupplierPrices as $socId => $datanormId) { - // Fetch the Datanorm article $datanorm = new Datanorm($db); if ($datanorm->fetch($datanormId) > 0) { $altSupplier = new Societe($db); @@ -524,38 +544,16 @@ if ($action == 'addmissingprices' && $id > 0) { $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 - ); + $priceExtrafields = datanormBuildSupplierPriceExtrafields($datanorm); + $result = datanormAddSupplierPrice($db, $productId, $datanorm, $altSupplier, $user, $purchasePrice, 19, $priceExtrafields); if ($result > 0) { - // Create product mapping + datanormInsertPriceExtrafields($db, $result, $priceExtrafields); + $mapping = new ProductMapping($db); $mapping->fk_soc = $socId; $mapping->supplier_ref = $datanorm->article_number; - $mapping->fk_product = $fk_product; + $mapping->fk_product = $productId; $mapping->ean = $datanorm->ean; $mapping->manufacturer_ref = $datanorm->manufacturer_ref; $mapping->description = $datanorm->short_text1; @@ -1968,6 +1966,7 @@ if ($action == 'edit' && $import->id > 0) { $allProductsMatched = true; $matchedLinesCount = 0; $totalLinesCount = count($lines); + $allMissingPrices = array(); // Fehlende Lieferantenpreise sammeln foreach ($lines as $line) { $hasProduct = ($line->fk_product > 0); @@ -2069,44 +2068,48 @@ if ($action == 'edit' && $import->id > 0) { print ''; print ''; - // Check for missing supplier prices from other catalogs + // Fehlende Lieferantenpreise aus anderen Katalogen sammeln (Anzeige weiter unten) 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"; + // Alle vorhandenen Lieferantenpreise fuer dieses Produkt laden + $sqlExistingPrices = "SELECT fk_soc, price, barcode FROM ".MAIN_DB_PREFIX."product_fournisseur_price"; $sqlExistingPrices .= " WHERE fk_product = ".(int)$line->fk_product; $resExistingPrices = $db->query($sqlExistingPrices); $existingSupplierIds = array(); + $currentSupplierPrice = 0; + $supplierEan = ''; if ($resExistingPrices) { while ($objPrice = $db->fetch_object($resExistingPrices)) { $existingSupplierIds[$objPrice->fk_soc] = true; + // Preis und EAN vom Rechnungslieferanten merken + if ($objPrice->fk_soc == $import->fk_soc) { + $currentSupplierPrice = $objPrice->price; + if (!empty($objPrice->barcode)) { + $supplierEan = $objPrice->barcode; + } + } } } - // 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; - } - } + // EAN-Quellen: 1. Lieferantenpreis-Barcode, 2. Import-Zeile EAN + $searchEan = !empty($supplierEan) ? $supplierEan : (!empty($line->ean) ? $line->ean : ''); - // 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); + $allCatalogResults = array(); + + // Primaer: EAN-Suche in Datanorm-Katalogen (zuverlaessigste Methode) + if (!empty($searchEan)) { + $allCatalogResults = $datanormSearch->searchByArticleNumber($searchEan, $import->fk_soc, true, 10); + } + // Fallback: Lieferanten-Artikelnummer wenn EAN nichts fand + if (empty($allCatalogResults) && !empty($line->supplier_ref)) { + $allCatalogResults = $datanormSearch->searchByArticleNumber($line->supplier_ref, $import->fk_soc, true, 10); + } + + if (!empty($allCatalogResults)) { - // 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']); @@ -2128,54 +2131,20 @@ if ($action == 'edit' && $import->id > 0) { } } - // Show missing suppliers with checkboxes if (!empty($missingSuppliers)) { - print '
'; - print '
'; - print ' '.$langs->trans('MissingSupplierPrices'); - print '
'; - print '
'; - print ''; - print ''; - print ''; - print ''; + // Hinweis in der Zeile anzeigen + print ' '.count($missingSuppliers).''; - 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 = ' '.number_format(abs($priceDiffPercent), 1).'%'; - } elseif ($priceDiff > 0) { - $diffHtml = ' +'.number_format($priceDiffPercent, 1).'%'; - } else { - $diffHtml = '='; - } - } - - print '
'; - print ''; - print '
'; + // Duplikate vermeiden (gleiches Produkt auf mehreren Rechnungszeilen) + if (!isset($allMissingPrices[$line->fk_product])) { + $allMissingPrices[$line->fk_product] = array( + 'product_id' => $line->fk_product, + 'product_ref' => $product->ref, + 'product_label' => $product->label, + 'current_price' => $currentSupplierPrice, + 'missing' => $missingSuppliers, + ); } - - print ''; - print '
'; - print '
'; } } } @@ -2186,7 +2155,7 @@ if ($action == 'edit' && $import->id > 0) { print ''; print ''; print ''; - print $form->select_produits('', 'product_id', '', 0, 0, -1, 2, '', 0, array(), 0, '1', 0, 'minwidth150 maxwidth200', 1, '', 0); + print $form->select_produits('', 'product_id_'.$line->id, '', 0, 0, -1, 2, '', 0, array(), 0, '1', 0, 'minwidth150 maxwidth200', 1, '', 0); print ' '; @@ -2212,6 +2181,11 @@ if ($action == 'edit' && $import->id > 0) { print ' '.$langs->trans('CreateProduct'); print ''; + // Refresh-Button nach Produktanlage + print ' '; + print ''; + print ''; + // Product template print '
'; print '
'; @@ -2219,7 +2193,7 @@ if ($action == 'edit' && $import->id > 0) { print ''; print ''; print ''; - print $form->select_produits('', 'template_product_id', '', 0, 0, -1, 2, '', 0, array(), 0, '1', 0, 'minwidth100 maxwidth150', 1, '', 0); + print $form->select_produits('', 'template_product_id_'.$line->id, '', 0, 0, -1, 2, '', 0, array(), 0, '1', 0, 'minwidth100 maxwidth150', 1, '', 0); print ' '; @@ -2324,6 +2298,77 @@ if ($action == 'edit' && $import->id > 0) { print ''; print ''; + // Fehlende Lieferantenpreise - konsolidierter Bereich + if (!empty($allMissingPrices)) { + print '
'; + print '
'; + print ''.$langs->trans('MissingSupplierPrices'); + $totalMissing = 0; + foreach ($allMissingPrices as $mp) { + $totalMissing += count($mp['missing']); + } + print ' '.$totalMissing.''; + print '
'; + + print ''; + print ''; + print ''; + print ''; + + foreach ($allMissingPrices as $mpData) { + print '
'; + print '
'; + print ''; + print dol_escape_htmltag($mpData['product_ref'].' - '.$mpData['product_label']); + if ($mpData['current_price'] > 0) { + print ' ('.price($mpData['current_price']).')'; + } + print '
'; + + foreach ($mpData['missing'] as $missing) { + $priceDiffHtml = ''; + if ($mpData['current_price'] > 0) { + $pDiff = $missing['purchase_price'] - $mpData['current_price']; + $pDiffPercent = ($pDiff / $mpData['current_price']) * 100; + if ($pDiff < 0) { + $priceDiffHtml = ' '.number_format(abs($pDiffPercent), 1).'%'; + } elseif ($pDiff > 0) { + $priceDiffHtml = ' +'.number_format($pDiffPercent, 1).'%'; + } else { + $priceDiffHtml = ' ='; + } + } + + // Wert: productId,socId,datanormId + $cbValue = $mpData['product_id'].','.$missing['fk_soc'].','.$missing['datanorm_id']; + print '
'; + print ''; + print '
'; + } + print '
'; + } + + // Alle auswaehlen / Keine auswaehlen + Submit + print '
'; + print ''.$langs->trans('SelectAll').''; + print ' / '; + print ''.$langs->trans('DeselectAll').''; + print '   '; + print ''; + print '
'; + + print ''; + print '
'; + } + // Datanorm Preview Section (shown when preview action was triggered) if (!empty($datanormPreviewMatches)) { print '
'; diff --git a/langs/de_DE/importzugferd.lang b/langs/de_DE/importzugferd.lang index 910a8d5..17ababb 100755 --- a/langs/de_DE/importzugferd.lang +++ b/langs/de_DE/importzugferd.lang @@ -486,3 +486,6 @@ AddSelectedPrices = Ausgewählte hinzufügen SupplierPricesAdded = %s Lieferantenpreise hinzugefügt CheaperBy = %s%% günstiger MoreExpensiveBy = %s%% teurer +RefreshProductListHelp = Produktlisten neu laden (nach Anlage neuer Produkte) +SelectAll = Alle auswählen +DeselectAll = Keine auswählen diff --git a/langs/en_US/importzugferd.lang b/langs/en_US/importzugferd.lang index 51694ec..a3252c0 100755 --- a/langs/en_US/importzugferd.lang +++ b/langs/en_US/importzugferd.lang @@ -417,3 +417,6 @@ AddSelectedPrices = Add Selected SupplierPricesAdded = %s supplier prices added CheaperBy = %s%% cheaper MoreExpensiveBy = %s%% more expensive +RefreshProductListHelp = Refresh product lists (after creating new products) +SelectAll = Select all +DeselectAll = Deselect all