diff --git a/ 2026-01-27-07-40-37-Zugferd-ZUGFERD2493150.pdf b/ 2026-01-27-07-40-37-Zugferd-ZUGFERD2493150.pdf new file mode 100755 index 0000000..51f59c1 Binary files /dev/null and b/ 2026-01-27-07-40-37-Zugferd-ZUGFERD2493150.pdf differ diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b399236..789940c 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,8 @@ "Bash(grep:*)", "Bash(mysql:*)", "Bash(ls:*)", - "Bash(php:*)" + "Bash(php:*)", + "Bash(mariadb:*)" ] } } diff --git a/admin/setup.php b/admin/setup.php index 9e77e77..05b9a55 100755 --- a/admin/setup.php +++ b/admin/setup.php @@ -166,6 +166,39 @@ $item->fieldAttr['placeholder'] = '30'; $formSetup->newItem('IMPORTZUGFERD_DATANORM_SEARCH_ALL')->setAsYesNo(); +// Accounting Codes Section (Standard-Konten für neue Produkte) +$formSetup->newItem('AccountingSettings')->setAsTitle(); + +$item = $formSetup->newItem('IMPORTZUGFERD_ACCOUNTING_CODE_SELL'); +$item->defaultFieldValue = ''; +$item->cssClass = 'minwidth200'; +$item->fieldAttr['placeholder'] = '700000'; + +$item = $formSetup->newItem('IMPORTZUGFERD_ACCOUNTING_CODE_SELL_INTRA'); +$item->defaultFieldValue = ''; +$item->cssClass = 'minwidth200'; +$item->fieldAttr['placeholder'] = '700100'; + +$item = $formSetup->newItem('IMPORTZUGFERD_ACCOUNTING_CODE_SELL_EXPORT'); +$item->defaultFieldValue = ''; +$item->cssClass = 'minwidth200'; +$item->fieldAttr['placeholder'] = '700200'; + +$item = $formSetup->newItem('IMPORTZUGFERD_ACCOUNTING_CODE_BUY'); +$item->defaultFieldValue = ''; +$item->cssClass = 'minwidth200'; +$item->fieldAttr['placeholder'] = '400000'; + +$item = $formSetup->newItem('IMPORTZUGFERD_ACCOUNTING_CODE_BUY_INTRA'); +$item->defaultFieldValue = ''; +$item->cssClass = 'minwidth200'; +$item->fieldAttr['placeholder'] = '400100'; + +$item = $formSetup->newItem('IMPORTZUGFERD_ACCOUNTING_CODE_BUY_EXPORT'); +$item->defaultFieldValue = ''; +$item->cssClass = 'minwidth200'; +$item->fieldAttr['placeholder'] = '400200'; + /* * Actions */ diff --git a/class/datanorm.class.php b/class/datanorm.class.php index f4d566c..a4a45cb 100644 --- a/class/datanorm.class.php +++ b/class/datanorm.class.php @@ -528,18 +528,18 @@ class Datanorm extends CommonObject $sql .= " WHERE (article_number LIKE '" . $this->db->escape($article_number) . "%'"; $sql .= " OR ean = '" . $this->db->escape($article_number) . "'"; $sql .= " OR manufacturer_ref LIKE '" . $this->db->escape($article_number) . "%')"; - - if ($fk_soc > 0 && !$searchAll) { - $sql .= " AND fk_soc = " . (int) $fk_soc; - } elseif ($fk_soc > 0 && $searchAll) { - // Order by matching supplier first - $sql .= " ORDER BY CASE WHEN fk_soc = " . (int) $fk_soc . " THEN 0 ELSE 1 END, article_number"; - } - $sql .= " AND active = 1"; $sql .= " AND entity = " . (int) $conf->entity; - if ($fk_soc == 0 || !$searchAll) { + if ($fk_soc > 0 && !$searchAll) { + $sql .= " AND fk_soc = " . (int) $fk_soc; + } + + // ORDER BY clause + if ($fk_soc > 0 && $searchAll) { + // Order by matching supplier first + $sql .= " ORDER BY CASE WHEN fk_soc = " . (int) $fk_soc . " THEN 0 ELSE 1 END, article_number"; + } else { $sql .= " ORDER BY article_number"; } diff --git a/class/importline.class.php b/class/importline.class.php index c507ac2..1f54d90 100644 --- a/class/importline.class.php +++ b/class/importline.class.php @@ -114,11 +114,26 @@ class ImportLine */ public $ean; + /** + * @var float Copper surcharge per unit (Kupferzuschlag) + */ + public $copper_surcharge; + + /** + * @var float Basis quantity for copper surcharge + */ + public $copper_surcharge_basis_qty; + /** * @var int Assigned Dolibarr product ID */ public $fk_product; + /** + * @var int Assigned Datanorm article ID + */ + public $fk_datanorm; + /** * @var string Match method description */ @@ -157,7 +172,8 @@ class ImportLine $sql = "INSERT INTO " . MAIN_DB_PREFIX . $this->table_element . " ("; $sql .= "fk_import, line_id, supplier_ref, product_name, description,"; $sql .= "quantity, unit_code, unit_price, unit_price_raw, basis_quantity, basis_quantity_unit,"; - $sql .= "line_total, tax_percent, ean, fk_product, match_method, date_creation"; + $sql .= "line_total, tax_percent, ean, copper_surcharge, copper_surcharge_basis_qty,"; + $sql .= "fk_product, match_method, date_creation"; $sql .= ") VALUES ("; $sql .= ((int) $this->fk_import) . ","; $sql .= "'" . $this->db->escape($this->line_id) . "',"; @@ -173,6 +189,8 @@ class ImportLine $sql .= ((float) $this->line_total) . ","; $sql .= ((float) $this->tax_percent) . ","; $sql .= "'" . $this->db->escape($this->ean) . "',"; + $sql .= ($this->copper_surcharge !== null ? ((float) $this->copper_surcharge) : "NULL") . ","; + $sql .= ($this->copper_surcharge_basis_qty !== null ? ((float) $this->copper_surcharge_basis_qty) : "NULL") . ","; $sql .= ($this->fk_product > 0 ? ((int) $this->fk_product) : "NULL") . ","; $sql .= "'" . $this->db->escape($this->match_method) . "',"; $sql .= "'" . $this->db->idate($this->date_creation) . "'"; @@ -199,7 +217,8 @@ class ImportLine { $sql = "SELECT rowid, fk_import, line_id, supplier_ref, product_name, description,"; $sql .= " quantity, unit_code, unit_price, unit_price_raw, basis_quantity, basis_quantity_unit,"; - $sql .= " line_total, tax_percent, ean, fk_product, match_method, date_creation"; + $sql .= " line_total, tax_percent, ean, copper_surcharge, copper_surcharge_basis_qty,"; + $sql .= " fk_product, fk_datanorm, match_method, date_creation"; $sql .= " FROM " . MAIN_DB_PREFIX . $this->table_element; $sql .= " WHERE rowid = " . ((int) $id); @@ -223,7 +242,10 @@ class ImportLine $this->line_total = $obj->line_total; $this->tax_percent = $obj->tax_percent; $this->ean = $obj->ean; + $this->copper_surcharge = $obj->copper_surcharge; + $this->copper_surcharge_basis_qty = $obj->copper_surcharge_basis_qty; $this->fk_product = $obj->fk_product; + $this->fk_datanorm = $obj->fk_datanorm; $this->match_method = $obj->match_method; $this->date_creation = $this->db->jdate($obj->date_creation); return 1; @@ -244,6 +266,7 @@ class ImportLine { $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element . " SET"; $sql .= " fk_product = " . ($this->fk_product > 0 ? ((int) $this->fk_product) : "NULL") . ","; + $sql .= " fk_datanorm = " . ($this->fk_datanorm > 0 ? ((int) $this->fk_datanorm) : "NULL") . ","; $sql .= " match_method = '" . $this->db->escape($this->match_method) . "'"; $sql .= " WHERE rowid = " . ((int) $this->id); @@ -374,4 +397,38 @@ class ImportLine $this->match_method = $match_method; return $this->update($user); } + + /** + * Set Datanorm reference for this line + * + * @param int $fk_datanorm Datanorm article ID + * @param User $user User making the change + * @return int >0 if OK, <0 if KO + */ + public function setDatanorm($fk_datanorm, $user) + { + $this->fk_datanorm = $fk_datanorm; + $this->match_method = 'datanorm_assigned'; + return $this->update($user); + } + + /** + * Count lines with Datanorm assignment + * + * @param int $fk_import Import ID + * @return int Number of lines with Datanorm + */ + public function countLinesWithDatanorm($fk_import) + { + $sql = "SELECT COUNT(*) as cnt FROM " . MAIN_DB_PREFIX . $this->table_element; + $sql .= " WHERE fk_import = " . ((int) $fk_import); + $sql .= " AND fk_datanorm IS NOT NULL AND fk_datanorm > 0"; + + $resql = $this->db->query($sql); + if ($resql) { + $obj = $this->db->fetch_object($resql); + return (int) $obj->cnt; + } + return 0; + } } diff --git a/class/zugferdimport.class.php b/class/zugferdimport.class.php index 344bcc5..100e849 100644 --- a/class/zugferdimport.class.php +++ b/class/zugferdimport.class.php @@ -606,6 +606,15 @@ class ZugferdImport extends CommonObject $line->tax_percent = $line_data['tax_percent']; $line->ean = $line_data['product']['global_id']; + // Copper surcharge (Kupferzuschlag) from ZUGFeRD - always set (0 if not present) + if (isset($line_data['copper_surcharge']) && $line_data['copper_surcharge'] > 0) { + $line->copper_surcharge = $line_data['copper_surcharge']; + $line->copper_surcharge_basis_qty = isset($line_data['copper_surcharge_basis_qty']) ? $line_data['copper_surcharge_basis_qty'] : $line->basis_quantity; + } else { + $line->copper_surcharge = 0; + $line->copper_surcharge_basis_qty = $line->basis_quantity; + } + // Try to match product $fk_product = 0; $match_method = ''; diff --git a/core/modules/modImportZugferd.class.php b/core/modules/modImportZugferd.class.php index c3337b0..7127695 100755 --- a/core/modules/modImportZugferd.class.php +++ b/core/modules/modImportZugferd.class.php @@ -638,6 +638,28 @@ class modImportZugferd extends DolibarrModules 'isModEnabled("importzugferd")' // enabled condition ); + // Add extrafield for product group (Warengruppe) on supplier prices + $extrafields->addExtraField( + 'warengruppe', // attribute code + 'Warengruppe', // label (translation key) + 'varchar', // type + 125, // position + 32, // size + 'product_fournisseur_price', // element type + 0, // unique + 0, // required + '', // default value + '', // param + 1, // always editable + '', // permission + 1, // list (show in list) + 0, // printable + '', // totalizable + '', // langfile + 'importzugferd@importzugferd', // module + 'isModEnabled("importzugferd")' // enabled condition + ); + // Add extrafield for copper content (Kupfergehalt) on product - kg per km for cables $extrafields->addExtraField( 'kupfergehalt', // attribute code diff --git a/import.php b/import.php index 180a034..c797175 100644 --- a/import.php +++ b/import.php @@ -93,6 +93,74 @@ $message = ''; * Actions */ +// AJAX: Get raw Datanorm lines for debugging +if ($action == 'get_raw_lines' && GETPOST('article_number', 'alphanohtml')) { + header('Content-Type: application/json'); + $article_number = GETPOST('article_number', 'alphanohtml'); + $ajax_fk_soc = GETPOSTINT('fk_soc'); + + $result = array( + 'datanorm_line' => '', + 'datpreis_line' => '', + 'article_number' => $article_number + ); + + // Get the upload directory for this supplier + $upload_dir = $conf->importzugferd->dir_output.'/datanorm/'.$ajax_fk_soc; + + if (is_dir($upload_dir)) { + $allFiles = glob($upload_dir . '/*'); + + // Search in DATANORM files + foreach ($allFiles as $file) { + $basename = strtoupper(basename($file)); + if (preg_match('/^DATANORM\.\d{3}$/', $basename)) { + $handle = fopen($file, 'r'); + if ($handle) { + while (($line = fgets($handle)) !== false) { + // A-Satz starts with A; and contains the article number + if (preg_match('/^A;/', $line)) { + $parts = explode(';', $line); + if (isset($parts[2]) && trim($parts[2]) == $article_number) { + $result['datanorm_line'] = trim($line); + break; + } + } + } + fclose($handle); + } + if (!empty($result['datanorm_line'])) break; + } + } + + // Search in DATPREIS files + foreach ($allFiles as $file) { + $basename = strtoupper(basename($file)); + if (preg_match('/^DATPREIS\.\d{3}$/', $basename)) { + $handle = fopen($file, 'r'); + if ($handle) { + while (($line = fgets($handle)) !== false) { + // P-Satz contains article numbers at various positions + if (preg_match('/^P;/', $line) && strpos($line, $article_number) !== false) { + $result['datpreis_line'] = trim($line); + break; + } + } + fclose($handle); + } + if (!empty($result['datpreis_line'])) break; + } + } + + $result['upload_dir'] = $upload_dir; + } else { + $result['error'] = 'Upload directory not found: ' . $upload_dir; + } + + echo json_encode($result); + exit; +} + // Upload and parse PDF - creates import record immediately if ($action == 'upload') { if (!empty($_FILES['zugferd_file']['tmp_name'])) { @@ -436,6 +504,14 @@ if ($action == 'createfromdatanorm' && $line_id > 0) { // Generate reference $newproduct->ref = 'NEW-'.$supplierPrefix.'-'.$datanorm->article_number; + // Set default accounting codes from module settings + $newproduct->accountancy_code_sell = getDolGlobalString('IMPORTZUGFERD_ACCOUNTING_CODE_SELL', ''); + $newproduct->accountancy_code_sell_intra = getDolGlobalString('IMPORTZUGFERD_ACCOUNTING_CODE_SELL_INTRA', ''); + $newproduct->accountancy_code_sell_export = getDolGlobalString('IMPORTZUGFERD_ACCOUNTING_CODE_SELL_EXPORT', ''); + $newproduct->accountancy_code_buy = getDolGlobalString('IMPORTZUGFERD_ACCOUNTING_CODE_BUY', ''); + $newproduct->accountancy_code_buy_intra = getDolGlobalString('IMPORTZUGFERD_ACCOUNTING_CODE_BUY_INTRA', ''); + $newproduct->accountancy_code_buy_export = getDolGlobalString('IMPORTZUGFERD_ACCOUNTING_CODE_BUY_EXPORT', ''); + // Label from Datanorm $newproduct->label = $datanorm->short_text1; if (!empty($datanorm->short_text2) && strlen($newproduct->label) < 100) { @@ -451,8 +527,27 @@ if ($action == 'createfromdatanorm' && $line_id > 0) { $purchasePrice = $datanorm->price / $datanorm->price_unit; } - // Selling price with markup - $sellingPrice = $purchasePrice * (1 + $markup / 100); + // Get copper surcharge for selling price calculation + // Priority: 1. Datanorm, 2. ZUGFeRD + $copperSurchargeForPrice = 0; + if (!empty($datanorm->metal_surcharge) && $datanorm->metal_surcharge > 0) { + $copperSurchargeForPrice = $datanorm->metal_surcharge; + // Normalize to per-unit if basis quantity differs + if ($datanorm->price_unit > 1) { + $copperSurchargeForPrice = $datanorm->metal_surcharge / $datanorm->price_unit; + } + } elseif (!empty($lineObj->copper_surcharge) && $lineObj->copper_surcharge > 0) { + $copperSurchargeForPrice = $lineObj->copper_surcharge; + // Normalize to per-unit if basis quantity differs + if (!empty($lineObj->copper_surcharge_basis_qty) && $lineObj->copper_surcharge_basis_qty > 1) { + $copperSurchargeForPrice = $lineObj->copper_surcharge / $lineObj->copper_surcharge_basis_qty; + } elseif (!empty($lineObj->basis_quantity) && $lineObj->basis_quantity > 1) { + $copperSurchargeForPrice = $lineObj->copper_surcharge / $lineObj->basis_quantity; + } + } + + // Selling price with markup: (purchase price + copper surcharge) × (1 + markup%) + $sellingPrice = ($purchasePrice + $copperSurchargeForPrice) * (1 + $markup / 100); $newproduct->price = $sellingPrice; $newproduct->price_base_type = 'HT'; $newproduct->tva_tx = $lineObj->tax_percent ?: 19; @@ -490,7 +585,30 @@ if ($action == 'createfromdatanorm' && $line_id > 0) { $supplierEanType = 2; // EAN13 } - // Add supplier price entry with EAN + // Prepare extrafields for supplier price + $supplierPriceExtrafields = array(); + // Kupferzuschlag (metal surcharge) - händlerspezifisch + // Priorität: 1. Datanorm, 2. ZUGFeRD Import Line + if (!empty($datanorm->metal_surcharge) && $datanorm->metal_surcharge > 0) { + $supplierPriceExtrafields['options_kupferzuschlag'] = $datanorm->metal_surcharge; + } elseif (!empty($lineObj->copper_surcharge) && $lineObj->copper_surcharge > 0) { + // Kupferzuschlag aus ZUGFeRD-Rechnung + $supplierPriceExtrafields['options_kupferzuschlag'] = $lineObj->copper_surcharge; + } + // Preiseinheit - händlerspezifisch + // Priorität: 1. Datanorm, 2. ZUGFeRD basis_quantity + if (!empty($datanorm->price_unit) && $datanorm->price_unit > 1) { + $supplierPriceExtrafields['options_preiseinheit'] = $datanorm->price_unit; + } elseif (!empty($lineObj->basis_quantity) && $lineObj->basis_quantity > 1) { + // Preiseinheit aus ZUGFeRD-Rechnung (z.B. 100 für "pro 100 Meter") + $supplierPriceExtrafields['options_preiseinheit'] = $lineObj->basis_quantity; + } + // Warengruppe aus Datanorm + if (!empty($datanorm->product_group)) { + $supplierPriceExtrafields['options_warengruppe'] = $datanorm->product_group; + } + + // Add supplier price entry with EAN and extrafields $res = $prodfourn->update_buyprice( 1, // Quantity $purchasePrice, // Price @@ -506,12 +624,49 @@ if ($action == 'createfromdatanorm' && $line_id > 0) { 0, // No price minimum 0, // Delivery delay 0, // Reputation - array(), // Extra fields - 0, // Charges array + array(), // Localtaxes array + '', // Default VAT code + 0, // Multicurrency price + 'HT', // Multicurrency price base type + 1, // Multicurrency tx + '', // Multicurrency code + trim($datanorm->short_text1 . ($datanorm->short_text2 ? ' ' . $datanorm->short_text2 : '')), // Description from Datanorm $supplierEan, // Barcode/EAN in supplier price - $supplierEanType // Barcode type (EAN13) + $supplierEanType, // Barcode type (EAN13) + $supplierPriceExtrafields // Extra fields (kupferzuschlag, preiseinheit) ); + // Manually ensure extrafields record exists for supplier price + // (Dolibarr update_buyprice doesn't always create it properly) + $sqlGetPrice = "SELECT rowid FROM ".MAIN_DB_PREFIX."product_fournisseur_price"; + $sqlGetPrice .= " WHERE fk_product = ".(int)$newproduct->id; + $sqlGetPrice .= " AND fk_soc = ".(int)$supplier->id; + $sqlGetPrice .= " ORDER BY rowid DESC LIMIT 1"; + $resGetPrice = $db->query($sqlGetPrice); + if ($resGetPrice && $db->num_rows($resGetPrice) > 0) { + $objPrice = $db->fetch_object($resGetPrice); + $priceId = $objPrice->rowid; + + // Check if extrafields record exists + $sqlCheckExtra = "SELECT rowid FROM ".MAIN_DB_PREFIX."product_fournisseur_price_extrafields"; + $sqlCheckExtra .= " WHERE fk_object = ".(int)$priceId; + $resCheckExtra = $db->query($sqlCheckExtra); + if ($resCheckExtra && $db->num_rows($resCheckExtra) == 0) { + // Insert extrafields record + $kupferzuschlag = !empty($supplierPriceExtrafields['options_kupferzuschlag']) ? (float)$supplierPriceExtrafields['options_kupferzuschlag'] : 'NULL'; + $preiseinheit = !empty($supplierPriceExtrafields['options_preiseinheit']) ? (int)$supplierPriceExtrafields['options_preiseinheit'] : 1; + $warengruppe = !empty($supplierPriceExtrafields['options_warengruppe']) ? "'".$db->escape($supplierPriceExtrafields['options_warengruppe'])."'" : 'NULL'; + + $sqlInsertExtra = "INSERT INTO ".MAIN_DB_PREFIX."product_fournisseur_price_extrafields"; + $sqlInsertExtra .= " (fk_object, kupferzuschlag, preiseinheit, warengruppe) VALUES ("; + $sqlInsertExtra .= (int)$priceId.", "; + $sqlInsertExtra .= ($kupferzuschlag === 'NULL' ? "NULL" : $kupferzuschlag).", "; + $sqlInsertExtra .= $preiseinheit.", "; + $sqlInsertExtra .= $warengruppe.")"; + $db->query($sqlInsertExtra); + } + } + // Create product mapping for future imports $mapping = new ProductMapping($db); $mapping->fk_soc = $import->fk_soc; @@ -544,6 +699,80 @@ if ($action == 'createfromdatanorm' && $line_id > 0) { $import->fetch($id); } +// "Alle zuordnen" - Assign Datanorm matches to import lines +if ($action == 'assignallfromdatanorm' && $id > 0) { + $import->fetch($id); + + if ($import->fk_soc > 0) { + $searchAll = getDolGlobalString('IMPORTZUGFERD_DATANORM_SEARCH_ALL', 0); + + // Get all lines without product + $lines = $importLine->fetchAllByImport($import->id); + $datanorm = new Datanorm($db); + $mapping = new ProductMapping($db); + $assignedCount = 0; + $datanormFoundCount = 0; + + foreach ($lines as $lineObj) { + // Skip lines that already have a product + if ($lineObj->fk_product > 0) { + continue; + } + + // Skip lines without supplier_ref + if (empty($lineObj->supplier_ref)) { + continue; + } + + // Search in Datanorm database + $results = $datanorm->searchByArticleNumber($lineObj->supplier_ref, $import->fk_soc, $searchAll, 1); + + if (empty($results) && !empty($lineObj->ean)) { + $results = $datanorm->searchByArticleNumber($lineObj->ean, $import->fk_soc, $searchAll, 1); + } + + if (!empty($results)) { + $datanormFoundCount++; + $datanormMatch = $results[0]; + // Get Datanorm ID and article number (array access) + $datanormId = isset($datanormMatch['id']) ? $datanormMatch['id'] : (isset($datanormMatch['rowid']) ? $datanormMatch['rowid'] : 0); + $articleNumber = isset($datanormMatch['article_number']) ? $datanormMatch['article_number'] : ''; + + // Check if product already exists for this supplier ref + $existingProductId = $mapping->findProductBySupplierRef($import->fk_soc, $articleNumber); + + if ($existingProductId > 0) { + // Product exists - assign both product and Datanorm to the line + $lineObj->fk_product = $existingProductId; + $lineObj->fk_datanorm = $datanormId; + $lineObj->match_method = 'datanorm_assign'; + $lineObj->update($user); + $assignedCount++; + } else { + // No product yet - save Datanorm reference for later product creation + $lineObj->fk_datanorm = $datanormId; + $lineObj->match_method = 'datanorm_pending'; + $lineObj->update($user); + } + } + } + + if ($assignedCount > 0) { + setEventMessages($langs->trans('ProductsAssignedFromDatanorm', $assignedCount), null, 'mesgs'); + } + if ($datanormFoundCount > $assignedCount) { + $pendingCount = $datanormFoundCount - $assignedCount; + setEventMessages($langs->trans('DatanormMatchesFoundNotAssigned', $pendingCount), null, 'mesgs'); + } + if ($datanormFoundCount == 0) { + setEventMessages($langs->trans('DatanormBatchNoMatches'), null, 'warnings'); + } + } + + header('Location: '.$_SERVER['PHP_SELF'].'?action=edit&id='.$id.'&token='.newToken()); + exit; +} + // Create ALL products from Datanorm (batch) if ($action == 'createallfromdatanorm' && $id > 0) { $import->fetch($id); @@ -594,6 +823,23 @@ if ($action == 'createallfromdatanorm' && $id > 0) { $purchasePrice = $datanorm->price / $datanorm->price_unit; } + // Get copper surcharge for selling price calculation + // Priority: 1. Datanorm, 2. ZUGFeRD + $copperSurchargeForPrice = 0; + if (!empty($datanorm->metal_surcharge) && $datanorm->metal_surcharge > 0) { + $copperSurchargeForPrice = $datanorm->metal_surcharge; + // Normalize to per-unit if price_unit > 1 + if ($datanorm->price_unit > 1) { + $copperSurchargeForPrice = $datanorm->metal_surcharge / $datanorm->price_unit; + } + } elseif (!empty($lineObj->copper_surcharge) && $lineObj->copper_surcharge > 0) { + $copperSurchargeForPrice = $lineObj->copper_surcharge; + // Normalize based on copper_surcharge_basis_qty + if (!empty($lineObj->copper_surcharge_basis_qty) && $lineObj->copper_surcharge_basis_qty > 1) { + $copperSurchargeForPrice = $lineObj->copper_surcharge / $lineObj->copper_surcharge_basis_qty; + } + } + // Check if product already exists in Dolibarr $existingProduct = new Product($db); $productExists = false; @@ -651,7 +897,16 @@ if ($action == 'createallfromdatanorm' && $id > 0) { } $newproduct->description = $datanorm->getFullDescription(); - $sellingPrice = $purchasePrice * (1 + $markup / 100); + // Set default accounting codes from module settings + $newproduct->accountancy_code_sell = getDolGlobalString('IMPORTZUGFERD_ACCOUNTING_CODE_SELL', ''); + $newproduct->accountancy_code_sell_intra = getDolGlobalString('IMPORTZUGFERD_ACCOUNTING_CODE_SELL_INTRA', ''); + $newproduct->accountancy_code_sell_export = getDolGlobalString('IMPORTZUGFERD_ACCOUNTING_CODE_SELL_EXPORT', ''); + $newproduct->accountancy_code_buy = getDolGlobalString('IMPORTZUGFERD_ACCOUNTING_CODE_BUY', ''); + $newproduct->accountancy_code_buy_intra = getDolGlobalString('IMPORTZUGFERD_ACCOUNTING_CODE_BUY_INTRA', ''); + $newproduct->accountancy_code_buy_export = getDolGlobalString('IMPORTZUGFERD_ACCOUNTING_CODE_BUY_EXPORT', ''); + + // Selling price: (purchase price + copper surcharge) × (1 + markup%) + $sellingPrice = ($purchasePrice + $copperSurchargeForPrice) * (1 + $markup / 100); $newproduct->price = $sellingPrice; $newproduct->price_base_type = 'HT'; $newproduct->tva_tx = $lineObj->tax_percent ?: 19; @@ -683,12 +938,83 @@ if ($action == 'createallfromdatanorm' && $id > 0) { $supplierEanType = 2; } + // Prepare extrafields for supplier price + $supplierPriceExtrafields = array(); + // Kupferzuschlag - Priorität: 1. Datanorm, 2. ZUGFeRD + if (!empty($datanorm->metal_surcharge) && $datanorm->metal_surcharge > 0) { + $supplierPriceExtrafields['options_kupferzuschlag'] = $datanorm->metal_surcharge; + } elseif (!empty($lineObj->copper_surcharge) && $lineObj->copper_surcharge > 0) { + $supplierPriceExtrafields['options_kupferzuschlag'] = $lineObj->copper_surcharge; + } + // Preiseinheit - Priorität: 1. Datanorm, 2. ZUGFeRD basis_quantity + if (!empty($datanorm->price_unit) && $datanorm->price_unit > 1) { + $supplierPriceExtrafields['options_preiseinheit'] = $datanorm->price_unit; + } elseif (!empty($lineObj->basis_quantity) && $lineObj->basis_quantity > 1) { + $supplierPriceExtrafields['options_preiseinheit'] = $lineObj->basis_quantity; + } + // Warengruppe aus Datanorm + if (!empty($datanorm->product_group)) { + $supplierPriceExtrafields['options_warengruppe'] = $datanorm->product_group; + } + $prodfourn->update_buyprice( - 1, $purchasePrice, $user, 'HT', $supplier, 0, - $datanorm->article_number, $lineObj->tax_percent ?: 19, - 0, 0, 0, 0, 0, 0, array(), 0, $supplierEan, $supplierEanType + 1, // Quantity + $purchasePrice, // Price + $user, + 'HT', // Price base + $supplier, // Supplier + 0, // Availability + $datanorm->article_number, // Supplier ref + $lineObj->tax_percent ?: 19, // VAT + 0, // Charges + 0, // Remise + 0, // Remise percentage + 0, // No price minimum + 0, // Delivery delay + 0, // Reputation + array(), // Localtaxes array + '', // Default VAT code + 0, // Multicurrency price + 'HT', // Multicurrency price base type + 1, // Multicurrency tx + '', // Multicurrency code + trim($datanorm->short_text1 . ($datanorm->short_text2 ? ' ' . $datanorm->short_text2 : '')), // Description from Datanorm + $supplierEan, // Barcode/EAN + $supplierEanType, // Barcode type + $supplierPriceExtrafields // Extra fields ); + // Manually ensure extrafields record exists for supplier price + // (Dolibarr update_buyprice doesn't always create it properly) + $sqlGetPrice = "SELECT rowid FROM ".MAIN_DB_PREFIX."product_fournisseur_price"; + $sqlGetPrice .= " WHERE fk_product = ".(int)$newproduct->id; + $sqlGetPrice .= " AND fk_soc = ".(int)$supplier->id; + $sqlGetPrice .= " ORDER BY rowid DESC LIMIT 1"; + $resGetPrice = $db->query($sqlGetPrice); + if ($resGetPrice && $db->num_rows($resGetPrice) > 0) { + $objPrice = $db->fetch_object($resGetPrice); + $priceId = $objPrice->rowid; + + // Check if extrafields record exists + $sqlCheckExtra = "SELECT rowid FROM ".MAIN_DB_PREFIX."product_fournisseur_price_extrafields"; + $sqlCheckExtra .= " WHERE fk_object = ".(int)$priceId; + $resCheckExtra = $db->query($sqlCheckExtra); + if ($resCheckExtra && $db->num_rows($resCheckExtra) == 0) { + // Insert extrafields record + $kupferzuschlag = !empty($supplierPriceExtrafields['options_kupferzuschlag']) ? (float)$supplierPriceExtrafields['options_kupferzuschlag'] : 'NULL'; + $preiseinheit = !empty($supplierPriceExtrafields['options_preiseinheit']) ? (int)$supplierPriceExtrafields['options_preiseinheit'] : 1; + $warengruppe = !empty($supplierPriceExtrafields['options_warengruppe']) ? "'".$db->escape($supplierPriceExtrafields['options_warengruppe'])."'" : 'NULL'; + + $sqlInsertExtra = "INSERT INTO ".MAIN_DB_PREFIX."product_fournisseur_price_extrafields"; + $sqlInsertExtra .= " (fk_object, kupferzuschlag, preiseinheit, warengruppe) VALUES ("; + $sqlInsertExtra .= (int)$priceId.", "; + $sqlInsertExtra .= ($kupferzuschlag === 'NULL' ? "NULL" : $kupferzuschlag).", "; + $sqlInsertExtra .= $preiseinheit.", "; + $sqlInsertExtra .= $warengruppe.")"; + $db->query($sqlInsertExtra); + } + } + // Create product mapping $mapping = new ProductMapping($db); $mapping->fk_soc = $import->fk_soc; @@ -733,6 +1059,138 @@ if ($action == 'createallfromdatanorm' && $id > 0) { $import->fetch($id); } +// Preview Datanorm matches (step 1 - show what will be created) +$datanormPreviewMatches = array(); +if ($action == 'previewdatanorm' && $id > 0) { + $import->fetch($id); + + if ($import->fk_soc > 0) { + // Get Datanorm settings + $markup = getDolGlobalString('IMPORTZUGFERD_DATANORM_MARKUP', 30); + $searchAll = getDolGlobalString('IMPORTZUGFERD_DATANORM_SEARCH_ALL', 0); + + // Load supplier + $supplier = new Societe($db); + $supplier->fetch($import->fk_soc); + $supplierPrefix = strtoupper(substr(preg_replace('/[^a-zA-Z]/', '', $supplier->name), 0, 3)); + + // Get all lines without product + $lines = $importLine->fetchAllByImport($import->id); + $datanorm = new Datanorm($db); + + require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php'; + + foreach ($lines as $lineObj) { + // Skip lines that already have a product + if ($lineObj->fk_product > 0) { + continue; + } + + // Skip lines without supplier_ref + if (empty($lineObj->supplier_ref)) { + continue; + } + + // Search in Datanorm database + $results = $datanorm->searchByArticleNumber($lineObj->supplier_ref, $import->fk_soc, $searchAll, 1); + + if (empty($results) && !empty($lineObj->ean)) { + $results = $datanorm->searchByArticleNumber($lineObj->ean, $import->fk_soc, $searchAll, 1); + } + + if (!empty($results)) { + $datanormArticle = $results[0]; + $datanorm->fetch($datanormArticle['id']); + + $purchasePrice = $datanorm->price; + if ($datanorm->price_unit > 1) { + $purchasePrice = $datanorm->price / $datanorm->price_unit; + } + + // Get copper surcharge for selling price calculation + $copperSurchargeForPrice = 0; + if (!empty($datanorm->metal_surcharge) && $datanorm->metal_surcharge > 0) { + $copperSurchargeForPrice = $datanorm->metal_surcharge; + if ($datanorm->price_unit > 1) { + $copperSurchargeForPrice = $datanorm->metal_surcharge / $datanorm->price_unit; + } + } elseif (!empty($lineObj->copper_surcharge) && $lineObj->copper_surcharge > 0) { + $copperSurchargeForPrice = $lineObj->copper_surcharge; + if (!empty($lineObj->copper_surcharge_basis_qty) && $lineObj->copper_surcharge_basis_qty > 1) { + $copperSurchargeForPrice = $lineObj->copper_surcharge / $lineObj->copper_surcharge_basis_qty; + } + } + + // Calculate selling price + $sellingPrice = ($purchasePrice + $copperSurchargeForPrice) * (1 + $markup / 100); + + // Check if product already exists in Dolibarr + $existingProductId = 0; + $productAction = 'create'; // 'create' or 'assign' + + // 1. Check by supplier reference (ProductFournisseur) + $sqlCheck = "SELECT DISTINCT pf.fk_product FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pf"; + $sqlCheck .= " WHERE pf.fk_soc = ".(int)$import->fk_soc; + $sqlCheck .= " AND pf.ref_fourn = '".$db->escape($datanorm->article_number)."'"; + $sqlCheck .= " AND pf.entity IN (".getEntity('product').")"; + $resqlCheck = $db->query($sqlCheck); + if ($resqlCheck && $db->num_rows($resqlCheck) > 0) { + $objCheck = $db->fetch_object($resqlCheck); + $existingProductId = $objCheck->fk_product; + $productAction = 'assign'; + } + + // 2. Check by product reference pattern + if ($existingProductId <= 0) { + $expectedRef = 'NEW-'.$supplierPrefix.'-'.$datanorm->article_number; + $existingProduct = new Product($db); + $fetchResult = $existingProduct->fetch(0, $expectedRef); + if ($fetchResult > 0) { + $existingProductId = $existingProduct->id; + $productAction = 'assign'; + } + } + + // 3. Check by EAN if available + if ($existingProductId <= 0 && !empty($datanorm->ean)) { + $sqlEan = "SELECT rowid FROM ".MAIN_DB_PREFIX."product"; + $sqlEan .= " WHERE barcode = '".$db->escape($datanorm->ean)."'"; + $sqlEan .= " AND entity IN (".getEntity('product').")"; + $resqlEan = $db->query($sqlEan); + if ($resqlEan && $db->num_rows($resqlEan) > 0) { + $objEan = $db->fetch_object($resqlEan); + $existingProductId = $objEan->rowid; + $productAction = 'assign'; + } + } + + // Store match info for preview + $datanormPreviewMatches[] = array( + 'line_id' => $lineObj->id, + 'line_supplier_ref' => $lineObj->supplier_ref, + 'line_product_name' => $lineObj->product_name, + 'line_quantity' => $lineObj->quantity, + 'line_unit_price' => $lineObj->unit_price, + 'datanorm_id' => $datanorm->id, + 'datanorm_article_number' => $datanorm->article_number, + 'datanorm_short_text1' => $datanorm->short_text1, + 'datanorm_short_text2' => $datanorm->short_text2, + 'datanorm_price' => $datanorm->price, + 'datanorm_price_unit' => $datanorm->price_unit, + 'datanorm_ean' => $datanorm->ean, + 'purchase_price' => $purchasePrice, + 'selling_price' => $sellingPrice, + 'copper_surcharge' => $copperSurchargeForPrice, + 'existing_product_id' => $existingProductId, + 'action' => $productAction, + 'new_ref' => 'NEW-'.$supplierPrefix.'-'.$datanorm->article_number + ); + } + } + } + $action = 'edit'; +} + // Create supplier invoice if ($action == 'createinvoice' && $id > 0) { $import->fetch($id); @@ -756,13 +1214,21 @@ if ($action == 'createinvoice' && $id > 0) { $error++; setEventMessages($langs->trans('ErrorNotAllProductsAssigned'), null, 'errors'); } else { + // Load supplier to get default values + $supplier = new Societe($db); + $supplier->fetch($import->fk_soc); + // Create invoice $invoice = new FactureFournisseur($db); $invoice->socid = $import->fk_soc; $invoice->ref_supplier = $import->invoice_number; $invoice->date = $import->invoice_date; $invoice->note_private = $langs->trans('ImportedFromZugferd').' ('.$import->ref.')'; - $invoice->cond_reglement_id = 1; + + // Use supplier default values for payment + $invoice->cond_reglement_id = $supplier->cond_reglement_supplier_id ?: 1; + $invoice->mode_reglement_id = $supplier->mode_reglement_supplier_id ?: 0; + $invoice->fk_account = $supplier->fk_account ?: 0; $db->begin(); $result = $invoice->create($user); @@ -1160,22 +1626,22 @@ if ($action == 'edit' && $import->id > 0) { foreach ($lines as $line) { $hasProduct = ($line->fk_product > 0); - $rowClass = $hasProduct ? 'oddeven opacitymedium' : 'oddeven'; + $rowStyle = $hasProduct ? 'background-color: #dff0d8;' : ''; // Green for matched products - print ''; + print ''; print ''.$line->line_id.''; print ''.dol_escape_htmltag($line->supplier_ref).''; print ''; print dol_escape_htmltag($line->product_name); if (!empty($line->ean) && !$hasProduct) { - print '
EAN: '.$line->ean.''; + print '
EAN: '.$line->ean.''; } print ''; print ''.price2num($line->quantity, 'MS').' '.zugferdGetUnitLabel($line->unit_code).''; print ''; print price($line->unit_price); if (!empty($line->basis_quantity) && $line->basis_quantity != 1) { - print '
('.price($line->unit_price_raw).'/'.price2num($line->basis_quantity, 'MS').zugferdGetUnitLabel($line->basis_quantity_unit).')'; + print '
('.price($line->unit_price_raw).'/'.price2num($line->basis_quantity, 'MS').zugferdGetUnitLabel($line->basis_quantity_unit).')'; } print ''; @@ -1316,10 +1782,20 @@ if ($action == 'edit' && $import->id > 0) { print ''; print ''.$langs->trans('CreateFromDatanorm'); print ''; - print '
'; - print dol_trunc($datanormArticle['short_text1'], 40); - print ' - '.price($datanormArticle['price']); - print ''; + // Button to show raw Datanorm data + print ' '; + print ''; + print ''; + // Show comparison: Invoice name vs Datanorm name + print '
'; + print ''; + print ''; + print ''; + print ''; + print ''; + print '
Rechnung:'.dol_trunc($line->product_name, 50).'
Datanorm:'.dol_trunc($datanormArticle['short_text1'], 50).''; + print ' ('.price($datanormArticle['price']).')
'; + print '
'; } } } @@ -1392,6 +1868,127 @@ if ($action == 'edit' && $import->id > 0) { print ''; print ''; + // Datanorm Preview Section (shown when preview action was triggered) + if (!empty($datanormPreviewMatches)) { + print '
'; + print '
'; + print ''.$langs->trans('DatanormPreview'); + print ' '.count($datanormPreviewMatches).' '.$langs->trans('Matches').''; + print '
'; + + print '
'; + print ''; + print ''; + print ''; + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + + $countCreate = 0; + $countAssign = 0; + foreach ($datanormPreviewMatches as $match) { + $rowClass = ($match['action'] == 'assign') ? 'background-color: #d9edf7;' : 'background-color: #dff0d8;'; + + print ''; + print ''; + print ''; + + // Invoice product name + print ''; + + // Datanorm product name + print ''; + + // Invoice price (from ZUGFeRD) + print ''; + + // Datanorm price - show original price and calculated unit price + print ''; + + // Selling price + print ''; + + // Action + print ''; + print ''; + } + + print '
'.$langs->trans('SupplierRef').''.$langs->trans('InvoiceProductName').''.$langs->trans('DatanormProductName').''.$langs->trans('InvoicePrice').''.$langs->trans('DatanormPrice').''.$langs->trans('SellingPrice').''.$langs->trans('Action').'
'.$match['datanorm_article_number'].''; + if (!empty($match['datanorm_ean'])) { + print '
EAN: '.$match['datanorm_ean'].''; + } + print '
'; + print ''.dol_trunc($match['line_product_name'], 40).''; + print ''; + print ''.dol_trunc($match['datanorm_short_text1'], 40).''; + if (!empty($match['datanorm_short_text2'])) { + print '
'.dol_trunc($match['datanorm_short_text2'], 40).''; + } + print '
'; + print ''.price($match['line_unit_price']).''; + print ''; + if ($match['datanorm_price_unit'] > 1) { + // Show original price and price unit + print ''.price($match['datanorm_price']).'/'.$match['datanorm_price_unit'].''; + print '
= '.price($match['purchase_price']).''; + } else { + print ''.price($match['purchase_price']).''; + } + if ($match['copper_surcharge'] > 0) { + print '
+ '.price($match['copper_surcharge']).' Cu'; + } + print '
'.price($match['selling_price']).''; + if ($match['action'] == 'assign') { + print ' '.$langs->trans('Assign').''; + $countAssign++; + } else { + print ' '.$langs->trans('Create').''; + print '
'.$match['new_ref'].''; + $countCreate++; + } + print '
'; + + // Summary and confirm button + print '
'; + print '
'; + if ($countCreate > 0) { + print ' '.$countCreate.' '.$langs->trans('ToCreate').''; + } + if ($countAssign > 0) { + print ' '.$countAssign.' '.$langs->trans('ToAssign').''; + } + print '
'; + print ''; + print '   '; + print ''; + print ''.$langs->trans('Cancel'); + print ''; + print '
'; + + print '
'; + print '
'; + + // JavaScript for select all checkbox + print ''; + } + // Action buttons print '
'; @@ -1410,10 +2007,17 @@ if ($action == 'edit' && $import->id > 0) { print '   '; } - // Button to create all products from Datanorm - if ($missingProducts > 0 && $import->fk_soc > 0) { + // Datanorm buttons - show when products are missing and supplier is set + if ($missingProducts > 0 && $import->fk_soc > 0 && empty($datanormPreviewMatches)) { + // "Alle zuordnen" - creates all products from Datanorm print ''; - print ''.$langs->trans('CreateAllFromDatanorm'); + print ''.$langs->trans('AssignAllFromDatanorm'); + print ''; + print '   '; + + // "Datanorm Vorschau" - preview what will be created + print ''; + print ''.$langs->trans('PreviewDatanormMatches'); print ''; print '   '; } @@ -1433,6 +2037,169 @@ if ($action == 'edit' && $import->id > 0) { print '
'; print ''; + + // Modal CSS and HTML for raw Datanorm data + print ''; + + // Modal for raw data + print '
'; + print '
'; + print '×'; + print '

Rohdaten:

'; + print '
'; + print '

Laden...

'; + print '
'; + print '
'; + print '
'; + + print ''; } llxFooter(); diff --git a/langs/de_DE/importzugferd.lang b/langs/de_DE/importzugferd.lang index 58959a2..182500d 100644 --- a/langs/de_DE/importzugferd.lang +++ b/langs/de_DE/importzugferd.lang @@ -229,6 +229,22 @@ IMPORTZUGFERD_DATANORM_MARKUP = Preisaufschlag (%) IMPORTZUGFERD_DATANORM_MARKUPTooltip = Prozentualer Aufschlag auf den Datanorm-Einkaufspreis für den Verkaufspreis IMPORTZUGFERD_DATANORM_SEARCH_ALL = In allen Lieferanten-Katalogen suchen IMPORTZUGFERD_DATANORM_SEARCH_ALLTooltip = Bei Aktivierung wird nicht nur im Katalog des aktuellen Lieferanten gesucht, sondern in allen Datanorm-Katalogen + +# Accounting Settings (Standard-Konten für neue Produkte) +AccountingSettings = Buchungskonten für neue Produkte +IMPORTZUGFERD_ACCOUNTING_CODE_SELL = Erlöskonto (Verkauf) +IMPORTZUGFERD_ACCOUNTING_CODE_SELLTooltip = Standard-Erlöskonto für neue Produkte aus Datanorm (z.B. 700000) +IMPORTZUGFERD_ACCOUNTING_CODE_SELL_INTRA = Erlöskonto (innergemeinschaftlich) +IMPORTZUGFERD_ACCOUNTING_CODE_SELL_INTRATooltip = Erlöskonto für innergemeinschaftliche Lieferungen (z.B. 700100) +IMPORTZUGFERD_ACCOUNTING_CODE_SELL_EXPORT = Erlöskonto (Export) +IMPORTZUGFERD_ACCOUNTING_CODE_SELL_EXPORTTooltip = Erlöskonto für Exporte außerhalb EU (z.B. 700200) +IMPORTZUGFERD_ACCOUNTING_CODE_BUY = Aufwandskonto (Einkauf) +IMPORTZUGFERD_ACCOUNTING_CODE_BUYTooltip = Standard-Aufwandskonto für neue Produkte aus Datanorm (z.B. 400000) +IMPORTZUGFERD_ACCOUNTING_CODE_BUY_INTRA = Aufwandskonto (innergemeinschaftlich) +IMPORTZUGFERD_ACCOUNTING_CODE_BUY_INTRATooltip = Aufwandskonto für innergemeinschaftliche Erwerbe (z.B. 400100) +IMPORTZUGFERD_ACCOUNTING_CODE_BUY_EXPORT = Aufwandskonto (Import) +IMPORTZUGFERD_ACCOUNTING_CODE_BUY_EXPORTTooltip = Aufwandskonto für Importe von außerhalb EU (z.B. 400200) + UploadDatanorm = Datanorm hochladen DatanormFiles = Datanorm Dateien DatanormFileHelp = DATANORM.001, DATANORM.WRG oder XML-Dateien (Datanorm 4.0/5.0) @@ -259,6 +275,29 @@ DatanormBatchCreated = %s Produkte aus Datanorm erstellt DatanormBatchAssigned = %s vorhandene Produkte zugeordnet DatanormBatchErrors = %s Produkte konnten nicht erstellt werden DatanormBatchNoMatches = Keine passenden Datanorm-Artikel gefunden +PreviewDatanormMatches = Datanorm Vorschau +DatanormPreview = Datanorm Vorschau - Gefundene Übereinstimmungen +Matches = Treffer +InvoiceProductName = Rechnung Bezeichnung +DatanormProductName = Datanorm Bezeichnung +InvoicePrice = Rechnungspreis +DatanormPrice = Datanorm EK +PurchasePrice = Einkaufspreis +SellingPrice = Verkaufspreis +ProductAlreadyExists = Produkt existiert bereits +Assign = Zuordnen +Create = Anlegen +ToCreate = anzulegen +ToAssign = zuzuordnen +ConfirmAndCreateProducts = Bestätigen und Produkte anlegen +CreateAllWithoutPreview = Direkt anlegen +ConfirmCreateAllWithoutPreview = Alle passenden Produkte aus Datanorm anlegen (ohne Vorschau)? +AssignAllFromDatanorm = Alle zuordnen +ConfirmAssignAllFromDatanorm = Alle vorhandenen Produkte aus Datanorm zuordnen? +NoProductsToAssign = Keine vorhandenen Produkte zum Zuordnen gefunden +ProductsAssignedFromDatanorm = %s Produkte wurden aus Datanorm zugeordnet +DatanormMatchesFoundNotAssigned = %s Datanorm-Treffer gefunden (Produkte können mit "Direkt anlegen" erstellt werden) +ShowRawDatanorm = Rohdaten anzeigen # # Scheduling @@ -414,6 +453,8 @@ Kupferzuschlag = Kupferzuschlag KupferzuschlagHelp = Metallzuschlag pro Einheit (wird aus Rechnungen extrahiert) Preiseinheit = Preiseinheit PreiseinheitHelp = Anzahl Einheiten pro Preis (z.B. 100 = Preis pro 100 Stück) +Warengruppe = Warengruppe +WarengruppeHelp = Produktgruppe aus Datanorm (für Rabattsteuerung und Kategorisierung) MetalSurchargeDetected = Metallzuschlag erkannt MetalSurchargeUpdated = Kupferzuschlag aktualisiert auf %s €/Einheit AddAllWithDifferences = Alle mit Unterschieden hinzufügen diff --git a/langs/en_US/importzugferd.lang b/langs/en_US/importzugferd.lang index 7f39d0b..a85d640 100755 --- a/langs/en_US/importzugferd.lang +++ b/langs/en_US/importzugferd.lang @@ -229,6 +229,22 @@ IMPORTZUGFERD_DATANORM_MARKUP = Price Markup (%) IMPORTZUGFERD_DATANORM_MARKUPTooltip = Percentage markup on Datanorm purchase price for selling price IMPORTZUGFERD_DATANORM_SEARCH_ALL = Search in all supplier catalogs IMPORTZUGFERD_DATANORM_SEARCH_ALLTooltip = When enabled, search all Datanorm catalogs, not just the current supplier + +# Accounting Settings (Default accounts for new products) +AccountingSettings = Accounting Codes for New Products +IMPORTZUGFERD_ACCOUNTING_CODE_SELL = Sales Account (Domestic) +IMPORTZUGFERD_ACCOUNTING_CODE_SELLTooltip = Default sales account for new products from Datanorm (e.g. 700000) +IMPORTZUGFERD_ACCOUNTING_CODE_SELL_INTRA = Sales Account (Intra-EU) +IMPORTZUGFERD_ACCOUNTING_CODE_SELL_INTRATooltip = Sales account for intra-community deliveries (e.g. 700100) +IMPORTZUGFERD_ACCOUNTING_CODE_SELL_EXPORT = Sales Account (Export) +IMPORTZUGFERD_ACCOUNTING_CODE_SELL_EXPORTTooltip = Sales account for exports outside EU (e.g. 700200) +IMPORTZUGFERD_ACCOUNTING_CODE_BUY = Purchase Account (Domestic) +IMPORTZUGFERD_ACCOUNTING_CODE_BUYTooltip = Default purchase account for new products from Datanorm (e.g. 400000) +IMPORTZUGFERD_ACCOUNTING_CODE_BUY_INTRA = Purchase Account (Intra-EU) +IMPORTZUGFERD_ACCOUNTING_CODE_BUY_INTRATooltip = Purchase account for intra-community acquisitions (e.g. 400100) +IMPORTZUGFERD_ACCOUNTING_CODE_BUY_EXPORT = Purchase Account (Import) +IMPORTZUGFERD_ACCOUNTING_CODE_BUY_EXPORTTooltip = Purchase account for imports from outside EU (e.g. 400200) + UploadDatanorm = Upload Datanorm DatanormFiles = Datanorm Files DatanormFileHelp = DATANORM.001, DATANORM.WRG or XML files (Datanorm 4.0/5.0) @@ -259,6 +275,29 @@ DatanormBatchCreated = %s products created from Datanorm DatanormBatchAssigned = %s existing products assigned DatanormBatchErrors = %s products could not be created DatanormBatchNoMatches = No matching Datanorm articles found +PreviewDatanormMatches = Datanorm Preview +DatanormPreview = Datanorm Preview - Found Matches +Matches = matches +InvoiceProductName = Invoice Product Name +DatanormProductName = Datanorm Product Name +InvoicePrice = Invoice Price +DatanormPrice = Datanorm Price +PurchasePrice = Purchase Price +SellingPrice = Selling Price +ProductAlreadyExists = Product already exists +Assign = Assign +Create = Create +ToCreate = to create +ToAssign = to assign +ConfirmAndCreateProducts = Confirm and Create Products +CreateAllWithoutPreview = Create directly +ConfirmCreateAllWithoutPreview = Create all matching products from Datanorm (without preview)? +AssignAllFromDatanorm = Assign all +ConfirmAssignAllFromDatanorm = Assign all existing products from Datanorm? +NoProductsToAssign = No existing products found to assign +ProductsAssignedFromDatanorm = %s products have been assigned from Datanorm +DatanormMatchesFoundNotAssigned = %s Datanorm matches found (products can be created with "Create directly") +ShowRawDatanorm = Show raw data # # Scheduling @@ -352,6 +391,8 @@ Kupferzuschlag = Copper Surcharge KupferzuschlagHelp = Metal surcharge per unit (extracted from invoices) Preiseinheit = Price Unit PreiseinheitHelp = Number of units per price (e.g. 100 = price per 100 pieces) +Warengruppe = Product Group +WarengruppeHelp = Product group from Datanorm (for discount control and categorization) MetalSurchargeDetected = Metal surcharge detected MetalSurchargeUpdated = Metal surcharge updated to %s €/unit diff --git a/sql/dolibarr_allversions.sql b/sql/dolibarr_allversions.sql index 5026bb4..9533edd 100755 --- a/sql/dolibarr_allversions.sql +++ b/sql/dolibarr_allversions.sql @@ -1,3 +1,10 @@ -- -- Script run when an upgrade of Dolibarr is done. Whatever is the Dolibarr version. -- + +-- Add copper surcharge fields to import_line table (v2.8) +ALTER TABLE llx_importzugferd_import_line ADD COLUMN copper_surcharge double(24,8) DEFAULT NULL AFTER ean; +ALTER TABLE llx_importzugferd_import_line ADD COLUMN copper_surcharge_basis_qty double(24,8) DEFAULT NULL AFTER copper_surcharge; + +-- Add fk_datanorm field to import_line table (v2.9) +ALTER TABLE llx_importzugferd_import_line ADD COLUMN fk_datanorm integer DEFAULT NULL AFTER fk_product; diff --git a/sql/llx_importzugferd_import_line.sql b/sql/llx_importzugferd_import_line.sql index 5157b7d..f46c097 100644 --- a/sql/llx_importzugferd_import_line.sql +++ b/sql/llx_importzugferd_import_line.sql @@ -28,7 +28,10 @@ CREATE TABLE llx_importzugferd_import_line ( line_total double(24,8) DEFAULT 0, -- Zeilensumme netto tax_percent double(24,8) DEFAULT 0, -- MwSt-Satz ean varchar(20), -- EAN/GTIN falls vorhanden + copper_surcharge double(24,8) DEFAULT NULL, -- Kupferzuschlag pro Einheit + copper_surcharge_basis_qty double(24,8) DEFAULT NULL, -- Basismenge für Kupferzuschlag fk_product integer, -- Zugeordnetes Dolibarr-Produkt + fk_datanorm integer, -- Zugeordneter Datanorm-Artikel match_method varchar(50), -- Wie wurde Produkt gefunden date_creation datetime NOT NULL, tms timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP