fix: Datanorm-Preisvergleich mit Kupferzuschlag (v4.3)

Datanorm-Preisvergleich korrigiert:
- Datanorm-Preise enthalten nur Materialpreis ohne Kupferzuschlag
- Kupferzuschlag wird aus Produkt-Extrafields geladen und addiert
- Vergleich: Datanorm+Kupfer vs. aktueller Dolibarr-Lieferantenpreis
- Anzeige zeigt Aufschlüsselung: "Base-Preis + Kupfer = Gesamt"

Kabel-Filter hinzugefügt:
- Checkbox "Kabel ausblenden" in Datanorm-Vorschau
- Filtert Kabel-Produkte basierend auf Warengruppe
- Erleichtert Fokus auf Standard-Artikel

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-03-03 08:15:22 +01:00
parent efa36ba86d
commit 953774c9f7
4 changed files with 102 additions and 7 deletions

View file

@ -2,6 +2,20 @@
Alle wesentlichen Änderungen an diesem Projekt werden in dieser Datei dokumentiert. Alle wesentlichen Änderungen an diesem Projekt werden in dieser Datei dokumentiert.
## [4.3] - 2026-03-03
### Behoben
- **Datanorm-Preisvergleich**: Korrekte Preisberechnung mit Kupferzuschlag
- Problem: Datanorm-Preise enthalten nur Materialpreis, kein Kupferzuschlag
- Lösung: Kupferzuschlag aus Produkt-Extrafields wird zu Datanorm-Preis addiert
- Vergleich jetzt: Datanorm+Kupfer vs. aktueller Dolibarr-Preis
- Zeigt Aufschlüsselung: "Datanorm-Preis + Kupferzuschlag = Gesamtpreis"
### Hinzugefügt
- **Kabel-Filter**: Checkbox "Kabel ausblenden" in Datanorm-Vorschau
- Filtert Kabel-Produkte aus der Anzeige (basierend auf Warengruppe)
- Erleichtert Fokus auf Standard-Artikel
## [4.2] - 2026-03-02 ## [4.2] - 2026-03-02
### Behoben ### Behoben

View file

@ -104,7 +104,11 @@ Available in:
See [CHANGELOG.md](CHANGELOG.md) for detailed version history. See [CHANGELOG.md](CHANGELOG.md) for detailed version history.
### 4.2 (Current) ### 4.3 (Current)
- Fixed Datanorm price comparison with copper surcharge calculation
- Added cable filter for Datanorm preview
### 4.2
- PDF attachments now properly linked to supplier invoices via ECM database - PDF attachments now properly linked to supplier invoices via ECM database
- Most expensive item shown as invoice label in supplier invoice list - Most expensive item shown as invoice label in supplier invoice list

View file

@ -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 = '4.2'; $this->version = '4.3';
// 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';

View file

@ -1877,15 +1877,32 @@ if ($action == 'previewdatanorm' && $id > 0) {
// Only show suppliers that don't already have a price for this product // Only show suppliers that don't already have a price for this product
$supplierAlternatives = array(); $supplierAlternatives = array();
$existingPriceSuppliers = array(); $existingPriceSuppliers = array();
$currentDolibarrPrice = 0; // Current supplier price from Dolibarr
// If product exists, load existing supplier prices // Load existing product's copper surcharge for price comparison
$productCopperSurcharge = 0;
if ($existingProductId > 0) { if ($existingProductId > 0) {
$sqlExisting = "SELECT fk_soc FROM ".MAIN_DB_PREFIX."product_fournisseur_price"; $sqlExisting = "SELECT fk_soc, unitprice FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
$sqlExisting .= " WHERE fk_product = ".(int)$existingProductId; $sqlExisting .= " WHERE fk_product = ".(int)$existingProductId;
$resExisting = $db->query($sqlExisting); $resExisting = $db->query($sqlExisting);
if ($resExisting) { if ($resExisting) {
while ($objEx = $db->fetch_object($resExisting)) { while ($objEx = $db->fetch_object($resExisting)) {
$existingPriceSuppliers[$objEx->fk_soc] = true; $existingPriceSuppliers[$objEx->fk_soc] = true;
// Load current invoice supplier's Dolibarr price for comparison
if ($objEx->fk_soc == $import->fk_soc) {
$currentDolibarrPrice = (float)$objEx->unitprice;
}
}
}
// Load copper surcharge from product extrafields
$sqlCopper = "SELECT kupferzuschlag FROM ".MAIN_DB_PREFIX."product_extrafields";
$sqlCopper .= " WHERE fk_object = ".(int)$existingProductId;
$resCopper = $db->query($sqlCopper);
if ($resCopper && $db->num_rows($resCopper) > 0) {
$objCopper = $db->fetch_object($resCopper);
if (!empty($objCopper->kupferzuschlag) && $objCopper->kupferzuschlag > 0) {
$productCopperSurcharge = (float)$objCopper->kupferzuschlag;
} }
} }
} }
@ -1899,11 +1916,16 @@ if ($action == 'previewdatanorm' && $id > 0) {
$altSupplier = new Societe($db); $altSupplier = new Societe($db);
$altSupplier->fetch($altResult['fk_soc']); $altSupplier->fetch($altResult['fk_soc']);
// Datanorm base price (per unit)
$altPurchasePrice = $altResult['price']; $altPurchasePrice = $altResult['price'];
if ($altResult['price_unit'] > 1) { if ($altResult['price_unit'] > 1) {
$altPurchasePrice = $altResult['price'] / $altResult['price_unit']; $altPurchasePrice = $altResult['price'] / $altResult['price_unit'];
} }
// Add copper surcharge from existing product for comparison
// Datanorm has only material price, not copper surcharge
$altPurchasePriceWithCopper = $altPurchasePrice + $productCopperSurcharge;
$supplierAlternatives[] = array( $supplierAlternatives[] = array(
'datanorm_id' => $altResult['id'], 'datanorm_id' => $altResult['id'],
'fk_soc' => $altResult['fk_soc'], 'fk_soc' => $altResult['fk_soc'],
@ -1912,7 +1934,9 @@ if ($action == 'previewdatanorm' && $id > 0) {
'short_text1' => $altResult['short_text1'], 'short_text1' => $altResult['short_text1'],
'price' => $altResult['price'], 'price' => $altResult['price'],
'price_unit' => $altResult['price_unit'], 'price_unit' => $altResult['price_unit'],
'purchase_price' => $altPurchasePrice, 'purchase_price' => $altPurchasePriceWithCopper,
'datanorm_base_price' => $altPurchasePrice, // Without copper
'copper_surcharge' => $productCopperSurcharge,
'ean' => $altResult['ean'], 'ean' => $altResult['ean'],
'manufacturer_ref' => $altResult['manufacturer_ref'], 'manufacturer_ref' => $altResult['manufacturer_ref'],
'is_invoice_supplier' => ($altResult['fk_soc'] == $import->fk_soc), 'is_invoice_supplier' => ($altResult['fk_soc'] == $import->fk_soc),
@ -1933,7 +1957,8 @@ if ($action == 'previewdatanorm' && $id > 0) {
'datanorm_price' => $datanorm->price, 'datanorm_price' => $datanorm->price,
'datanorm_price_unit' => $datanorm->price_unit, 'datanorm_price_unit' => $datanorm->price_unit,
'datanorm_ean' => $datanorm->ean, 'datanorm_ean' => $datanorm->ean,
'purchase_price' => $purchasePrice, 'purchase_price' => $currentDolibarrPrice, // Current Dolibarr price for comparison
'datanorm_purchase_price' => $purchasePrice, // New Datanorm price
'selling_price' => $sellingPrice, 'selling_price' => $sellingPrice,
'copper_surcharge' => $copperSurchargeForPrice, 'copper_surcharge' => $copperSurchargeForPrice,
'existing_product_id' => $existingProductId, 'existing_product_id' => $existingProductId,
@ -2588,6 +2613,18 @@ if ($action == 'edit' && $import->id > 0) {
$datanormSearch = new Datanorm($db); $datanormSearch = new Datanorm($db);
$allCatalogResults = array(); $allCatalogResults = array();
// Load copper surcharge from product extrafields for price comparison
$productCopperSurcharge = 0;
$sqlCopper = "SELECT kupferzuschlag FROM ".MAIN_DB_PREFIX."product_extrafields";
$sqlCopper .= " WHERE fk_object = ".(int)$line->fk_product;
$resCopper = $db->query($sqlCopper);
if ($resCopper && $db->num_rows($resCopper) > 0) {
$objCopper = $db->fetch_object($resCopper);
if (!empty($objCopper->kupferzuschlag) && $objCopper->kupferzuschlag > 0) {
$productCopperSurcharge = (float)$objCopper->kupferzuschlag;
}
}
// Suche mit Artikelnummer - die Funktion nutzt dann die EAN für Cross-Catalog // Suche mit Artikelnummer - die Funktion nutzt dann die EAN für Cross-Catalog
if (!empty($line->supplier_ref)) { if (!empty($line->supplier_ref)) {
$allCatalogResults = $datanormSearch->searchByArticleNumber($line->supplier_ref, $import->fk_soc, true, 10); $allCatalogResults = $datanormSearch->searchByArticleNumber($line->supplier_ref, $import->fk_soc, true, 10);
@ -2601,11 +2638,16 @@ if ($action == 'edit' && $import->id > 0) {
$altSupplier = new Societe($db); $altSupplier = new Societe($db);
$altSupplier->fetch($catalogResult['fk_soc']); $altSupplier->fetch($catalogResult['fk_soc']);
// Datanorm base price (per unit)
$altPurchasePrice = $catalogResult['price']; $altPurchasePrice = $catalogResult['price'];
if ($catalogResult['price_unit'] > 1) { if ($catalogResult['price_unit'] > 1) {
$altPurchasePrice = $catalogResult['price'] / $catalogResult['price_unit']; $altPurchasePrice = $catalogResult['price'] / $catalogResult['price_unit'];
} }
// Add copper surcharge from existing product for comparison
// Datanorm has only material price, not copper surcharge
$altPurchasePriceWithCopper = $altPurchasePrice + $productCopperSurcharge;
$missingSuppliers[] = array( $missingSuppliers[] = array(
'datanorm_id' => $catalogResult['id'], 'datanorm_id' => $catalogResult['id'],
'fk_soc' => $catalogResult['fk_soc'], 'fk_soc' => $catalogResult['fk_soc'],
@ -2613,7 +2655,9 @@ if ($action == 'edit' && $import->id > 0) {
'article_number' => $catalogResult['article_number'], 'article_number' => $catalogResult['article_number'],
'price' => $catalogResult['price'], 'price' => $catalogResult['price'],
'price_unit' => $catalogResult['price_unit'], 'price_unit' => $catalogResult['price_unit'],
'purchase_price' => $altPurchasePrice, 'purchase_price' => $altPurchasePriceWithCopper,
'datanorm_base_price' => $altPurchasePrice, // Without copper
'copper_surcharge' => $productCopperSurcharge,
'ean' => $catalogResult['ean'], 'ean' => $catalogResult['ean'],
); );
} }
@ -2862,12 +2906,30 @@ if ($action == 'edit' && $import->id > 0) {
print '<div class="titre" style="margin-bottom: 10px;">'; print '<div class="titre" style="margin-bottom: 10px;">';
print '<i class="fas fa-database paddingright"></i>'.$langs->trans('DatanormPreview'); print '<i class="fas fa-database paddingright"></i>'.$langs->trans('DatanormPreview');
print ' <span class="badge badge-info">'.count($datanormPreviewMatches).' '.$langs->trans('Matches').'</span>'; print ' <span class="badge badge-info">'.count($datanormPreviewMatches).' '.$langs->trans('Matches').'</span>';
// Filter: Kabel ausblenden (in eigenem Form für sofortiges Submit)
print '<span style="margin-left: 15px;">';
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'" style="display: inline;">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="previewdatanorm">';
print '<input type="hidden" name="id" value="'.$import->id.'">';
$hideCables = GETPOST('hide_cables', 'int');
$checked = $hideCables ? ' checked' : '';
print '<label style="font-weight: normal; cursor: pointer;">';
print '<input type="checkbox" id="hide_cables" name="hide_cables" value="1"'.$checked.' onchange="this.form.submit();">';
print ' <span style="color: #666;">Kabel ausblenden</span>';
print '</label>';
print '</form>';
print '</span>';
print '</div>'; print '</div>';
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'" id="datanorm_confirm_form">'; print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'" id="datanorm_confirm_form">';
print '<input type="hidden" name="token" value="'.newToken().'">'; print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="createallfromdatanorm">'; print '<input type="hidden" name="action" value="createallfromdatanorm">';
print '<input type="hidden" name="id" value="'.$import->id.'">'; print '<input type="hidden" name="id" value="'.$import->id.'">';
if ($hideCables) {
print '<input type="hidden" name="hide_cables" value="1">';
}
print '<table class="noborder centpercent">'; print '<table class="noborder centpercent">';
print '<tr class="liste_titre">'; print '<tr class="liste_titre">';
@ -2883,7 +2945,18 @@ if ($action == 'edit' && $import->id > 0) {
$countCreate = 0; $countCreate = 0;
$countAssign = 0; $countAssign = 0;
$hideCables = GETPOST('hide_cables', 'int');
foreach ($datanormPreviewMatches as $match) { foreach ($datanormPreviewMatches as $match) {
// Filter: Kabel ausblenden wenn gewünscht
if ($hideCables) {
// Load Datanorm product group to check if it's a cable
$datanormObj = new Datanorm($db);
$datanormObj->fetch($match['datanorm_id']);
if (isCableProduct($datanormObj)) {
continue; // Skip cables
}
}
$rowClass = ($match['action'] == 'assign') ? 'background-color: #d9edf7;' : 'background-color: #dff0d8;'; $rowClass = ($match['action'] == 'assign') ? 'background-color: #d9edf7;' : 'background-color: #dff0d8;';
print '<tr class="oddeven" style="'.$rowClass.'">'; print '<tr class="oddeven" style="'.$rowClass.'">';
@ -3033,6 +3106,10 @@ if ($action == 'edit' && $import->id > 0) {
if ($alt['price_unit'] > 1) { if ($alt['price_unit'] > 1) {
print '<span class="small" style="color: #666;">'.price($alt['price']).'/'.$alt['price_unit'].'</span><br>'; print '<span class="small" style="color: #666;">'.price($alt['price']).'/'.$alt['price_unit'].'</span><br>';
} }
// Show breakdown: Datanorm base price + copper surcharge
if (!empty($alt['copper_surcharge']) && $alt['copper_surcharge'] > 0) {
print '<span class="small" style="color: #666;">'.price($alt['datanorm_base_price']).' + '.price($alt['copper_surcharge']).' Kupfer</span><br>';
}
print '<strong>'.price($alt['purchase_price']).'</strong>'; print '<strong>'.price($alt['purchase_price']).'</strong>';
print '</td>'; print '</td>';