Compare commits
2 commits
59ce17b7b5
...
745fc68fc9
| Author | SHA1 | Date | |
|---|---|---|---|
| 745fc68fc9 | |||
| 8b0d1830a3 |
10 changed files with 938 additions and 256 deletions
32
CHANGELOG.md
Normal file → Executable file
32
CHANGELOG.md
Normal file → Executable file
|
|
@ -2,6 +2,38 @@
|
||||||
|
|
||||||
Alle wesentlichen Änderungen an diesem Projekt werden in dieser Datei dokumentiert.
|
Alle wesentlichen Änderungen an diesem Projekt werden in dieser Datei dokumentiert.
|
||||||
|
|
||||||
|
## [3.8] - 2026-02-25
|
||||||
|
|
||||||
|
### Hinzugefügt
|
||||||
|
- **Kabel-Preisberechnung**: Zentrale Funktion `calculateCablePricing()` für einheitliche Preislogik
|
||||||
|
- **Kupfergehalt-Berechnung**: Automatische Berechnung aus Aderanzahl × Querschnitt × 8.9
|
||||||
|
- **Ringgröße-Erkennung**: Unterstützt Ri100, Tr500, Fol.25m, "Ring 100m", "Trommel 500m"
|
||||||
|
- **Extrafield "produktpreis"**: Speichert reinen Materialpreis ohne Kupferzuschlag (nur Kabel)
|
||||||
|
- **EAN-Auto-Update**: Barcodes aus ZUGFeRD-Rechnungen werden automatisch in Lieferantenpreise übernommen
|
||||||
|
|
||||||
|
### Verbessert
|
||||||
|
- **Lieferanten-Formate**: Korrekte Unterscheidung zwischen Sonepar (price_unit=1, Ring im Namen) und Kluxen/Witte (price_unit=100)
|
||||||
|
- **Cross-Catalog-Suche**: Nur noch über EAN, nicht mehr über Artikelnummern (verhindert Fehlzuordnungen)
|
||||||
|
- **EAN-Barcode-Typ**: Automatische Erkennung (EAN8, EAN13, UPC-A) statt hardcoded EAN13
|
||||||
|
- **Error-Handling**: Besseres Logging bei Extrafield-Fehlern
|
||||||
|
|
||||||
|
### Behoben
|
||||||
|
- Division durch Null bei Preisberechnung abgesichert
|
||||||
|
- Mindestbestellmenge und Verpackungseinheit werden von existierenden Lieferantenpreisen übernommen
|
||||||
|
|
||||||
|
## [3.7] - 2026-02-23
|
||||||
|
|
||||||
|
### Hinzugefügt
|
||||||
|
- **GlobalNotify Integration**: Benachrichtigungen über das zentrale GlobalNotify-Modul
|
||||||
|
- Import-Fehler: Warnung bei fehlgeschlagenen Importen
|
||||||
|
- Rechnungen zur Prüfung: Aktion wenn neue Rechnungen warten
|
||||||
|
- IMAP-Fehler: Warnung wenn E-Mail Postfach nicht erreichbar
|
||||||
|
- Exception/Fatal: Sofortige Benachrichtigung bei Abstürzen
|
||||||
|
- **Helper-Funktion**: `notify()` für sichere GlobalNotify-Nutzung mit Fallback
|
||||||
|
|
||||||
|
### Hinweis
|
||||||
|
GlobalNotify ist optional. Ohne das Modul werden Benachrichtigungen ins Dolibarr-Log geschrieben.
|
||||||
|
|
||||||
## [3.6] - 2026-02-23
|
## [3.6] - 2026-02-23
|
||||||
|
|
||||||
### Behoben
|
### Behoben
|
||||||
|
|
|
||||||
34
README.md
34
README.md
|
|
@ -102,23 +102,29 @@ Available in:
|
||||||
|
|
||||||
## Version History
|
## Version History
|
||||||
|
|
||||||
### 1.1
|
See [CHANGELOG.md](CHANGELOG.md) for detailed version history.
|
||||||
- New persistent import workflow with database storage
|
|
||||||
- Manual product assignment via dropdown
|
### 3.8 (Current)
|
||||||
- Product removal/reassignment
|
- Improved cable pricing for different supplier formats (Sonepar vs Kluxen/Witte)
|
||||||
- Status "Pending" for imports requiring manual intervention
|
- Automatic ring size detection from product names (Ri100, Tr500, etc.)
|
||||||
- Pending imports overview on upload page
|
- EAN auto-update from ZUGFeRD invoices with automatic barcode type detection
|
||||||
- UN/ECE unit code translation (C62 → Stk., MTR → m, etc.)
|
- New extrafield "produktpreis" for material price without copper surcharge
|
||||||
- Batch import from folder or IMAP mailbox
|
- Cross-catalog search now EAN-only (prevents mismatches)
|
||||||
- IMAP connection test with folder selection
|
|
||||||
- Product template feature (duplicate existing product)
|
### 3.7
|
||||||
|
- GlobalNotify integration for import notifications
|
||||||
|
|
||||||
|
### 3.6
|
||||||
|
- Cron job stability fixes and dedicated logging
|
||||||
|
|
||||||
|
### 3.5
|
||||||
|
- Automatic cron import from watch folder and IMAP
|
||||||
|
|
||||||
|
### 3.0
|
||||||
|
- Datanorm integration for article prices
|
||||||
|
|
||||||
### 1.0
|
### 1.0
|
||||||
- Initial release
|
- Initial release
|
||||||
- Basic ZUGFeRD/Factur-X import
|
|
||||||
- Automatic product matching
|
|
||||||
- Supplier detection
|
|
||||||
- Duplicate detection
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -316,6 +316,32 @@ class ActionsImportZugferd
|
||||||
$processed_line['product_ref'] = $product->ref;
|
$processed_line['product_ref'] = $product->ref;
|
||||||
$processed_line['product_label'] = $product->label;
|
$processed_line['product_label'] = $product->label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update supplier price with EAN from invoice if empty
|
||||||
|
$invoiceEan = !empty($line['product']['global_id']) ? trim($line['product']['global_id']) : '';
|
||||||
|
$supplierRef = !empty($line['product']['seller_id']) ? $line['product']['seller_id'] : '';
|
||||||
|
if (!empty($invoiceEan) && !empty($supplierRef) && ctype_digit($invoiceEan)) {
|
||||||
|
// Barcode-Typ basierend auf Länge bestimmen
|
||||||
|
$eanLen = strlen($invoiceEan);
|
||||||
|
if ($eanLen == 13) {
|
||||||
|
$barcodeType = 2; // EAN13
|
||||||
|
} elseif ($eanLen == 8) {
|
||||||
|
$barcodeType = 1; // EAN8
|
||||||
|
} elseif ($eanLen == 12) {
|
||||||
|
$barcodeType = 3; // UPC-A
|
||||||
|
} else {
|
||||||
|
$barcodeType = 0; // Unbekannt
|
||||||
|
}
|
||||||
|
|
||||||
|
$sqlEan = "UPDATE " . MAIN_DB_PREFIX . "product_fournisseur_price";
|
||||||
|
$sqlEan .= " SET barcode = '" . $this->db->escape($invoiceEan) . "'";
|
||||||
|
$sqlEan .= ", fk_barcode_type = " . (int)$barcodeType;
|
||||||
|
$sqlEan .= " WHERE fk_product = " . (int)$match['fk_product'];
|
||||||
|
$sqlEan .= " AND fk_soc = " . (int)$supplier_id;
|
||||||
|
$sqlEan .= " AND ref_fourn = '" . $this->db->escape($supplierRef) . "'";
|
||||||
|
$sqlEan .= " AND (barcode IS NULL OR barcode = '')";
|
||||||
|
$this->db->query($sqlEan);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$processed_line['needs_creation'] = true;
|
$processed_line['needs_creation'] = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,57 @@ class CronImportZugferd
|
||||||
*/
|
*/
|
||||||
private $startTime = 0;
|
private $startTime = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send notification via GlobalNotify (if available)
|
||||||
|
*
|
||||||
|
* @param string $type 'error', 'warning', 'info', 'action'
|
||||||
|
* @param string $title Title
|
||||||
|
* @param string $message Message
|
||||||
|
* @param string $actionUrl URL for action button
|
||||||
|
* @param string $actionLabel Label for action button
|
||||||
|
* @return bool True if sent via GlobalNotify
|
||||||
|
*/
|
||||||
|
protected function notify($type, $title, $message, $actionUrl = '', $actionLabel = '')
|
||||||
|
{
|
||||||
|
if (!isModEnabled('globalnotify')) {
|
||||||
|
dol_syslog("ImportZugferd [{$type}]: {$title} - {$message}", LOG_WARNING);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classFile = dol_buildpath('/globalnotify/class/globalnotify.class.php', 0);
|
||||||
|
if (!file_exists($classFile)) {
|
||||||
|
dol_syslog("ImportZugferd [{$type}]: {$title} - {$message}", LOG_WARNING);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once $classFile;
|
||||||
|
|
||||||
|
if (!class_exists('GlobalNotify')) {
|
||||||
|
dol_syslog("ImportZugferd [{$type}]: {$title} - {$message}", LOG_WARNING);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch ($type) {
|
||||||
|
case 'error':
|
||||||
|
GlobalNotify::error('importzugferd', $title, $message, $actionUrl, $actionLabel);
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
GlobalNotify::warning('importzugferd', $title, $message, $actionUrl, $actionLabel);
|
||||||
|
break;
|
||||||
|
case 'action':
|
||||||
|
GlobalNotify::actionRequired('importzugferd', $title, $message, $actionUrl, $actionLabel ?: 'Aktion erforderlich');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
GlobalNotify::info('importzugferd', $title, $message, $actionUrl, $actionLabel);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
dol_syslog("GlobalNotify error: ".$e->getMessage(), LOG_ERR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
|
|
@ -226,17 +277,34 @@ class CronImportZugferd
|
||||||
$this->cronLog("Completed: imported={$this->imported_count}, skipped={$this->skipped_count}, errors={$this->error_count}, duration={$duration}s");
|
$this->cronLog("Completed: imported={$this->imported_count}, skipped={$this->skipped_count}, errors={$this->error_count}, duration={$duration}s");
|
||||||
$this->cronLog("========== CRON END (success) ==========");
|
$this->cronLog("========== CRON END (success) ==========");
|
||||||
|
|
||||||
|
// Send GlobalNotify notifications
|
||||||
|
$this->sendImportNotifications();
|
||||||
|
|
||||||
return ($this->error_count > 0) ? -1 : 0;
|
return ($this->error_count > 0) ? -1 : 0;
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$this->error = 'Exception: '.$e->getMessage();
|
$this->error = 'Exception: '.$e->getMessage();
|
||||||
$this->cronLog("EXCEPTION: ".$e->getMessage()."\n".$e->getTraceAsString(), 'ERROR');
|
$this->cronLog("EXCEPTION: ".$e->getMessage()."\n".$e->getTraceAsString(), 'ERROR');
|
||||||
$this->cronLog("========== CRON END (exception) ==========");
|
$this->cronLog("========== CRON END (exception) ==========");
|
||||||
|
$this->notify(
|
||||||
|
'error',
|
||||||
|
'ZUGFeRD Import fehlgeschlagen',
|
||||||
|
'Exception: '.$e->getMessage(),
|
||||||
|
dol_buildpath('/importzugferd/admin/setup.php', 1),
|
||||||
|
'Einstellungen prüfen'
|
||||||
|
);
|
||||||
return -1;
|
return -1;
|
||||||
} catch (Throwable $t) {
|
} catch (Throwable $t) {
|
||||||
$this->error = 'Fatal: '.$t->getMessage();
|
$this->error = 'Fatal: '.$t->getMessage();
|
||||||
$this->cronLog("FATAL: ".$t->getMessage()."\n".$t->getTraceAsString(), 'ERROR');
|
$this->cronLog("FATAL: ".$t->getMessage()."\n".$t->getTraceAsString(), 'ERROR');
|
||||||
$this->cronLog("========== CRON END (fatal) ==========");
|
$this->cronLog("========== CRON END (fatal) ==========");
|
||||||
|
$this->notify(
|
||||||
|
'error',
|
||||||
|
'ZUGFeRD Import Absturz',
|
||||||
|
'Fatal: '.$t->getMessage(),
|
||||||
|
dol_buildpath('/importzugferd/admin/setup.php', 1),
|
||||||
|
'Einstellungen prüfen'
|
||||||
|
);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -645,4 +713,79 @@ class CronImportZugferd
|
||||||
dol_syslog("CronImportZugferd: Keeping error file in watch folder: " . $file, LOG_INFO);
|
dol_syslog("CronImportZugferd: Keeping error file in watch folder: " . $file, LOG_INFO);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send notifications based on import results
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function sendImportNotifications()
|
||||||
|
{
|
||||||
|
// Check for errors
|
||||||
|
if ($this->error_count > 0) {
|
||||||
|
$errorSummary = count($this->errors) > 0 ? implode(', ', array_slice($this->errors, 0, 3)) : 'Siehe Log';
|
||||||
|
$this->notify(
|
||||||
|
'warning',
|
||||||
|
$this->error_count.' ZUGFeRD Import-Fehler',
|
||||||
|
$errorSummary,
|
||||||
|
dol_buildpath('/importzugferd/list.php?status=error', 1),
|
||||||
|
'Fehler anzeigen'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for imported invoices that need review
|
||||||
|
if ($this->imported_count > 0) {
|
||||||
|
// Count pending invoices (drafts needing approval)
|
||||||
|
$pendingCount = $this->countPendingInvoices();
|
||||||
|
|
||||||
|
if ($pendingCount > 0) {
|
||||||
|
$this->notify(
|
||||||
|
'action',
|
||||||
|
$this->imported_count.' ZUGFeRD Rechnungen importiert',
|
||||||
|
"{$pendingCount} Lieferantenrechnungen warten auf Prüfung und Freigabe",
|
||||||
|
dol_buildpath('/fourn/facture/list.php?search_status=0', 1),
|
||||||
|
'Rechnungen prüfen'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// All auto-created and validated
|
||||||
|
$this->notify(
|
||||||
|
'info',
|
||||||
|
$this->imported_count.' ZUGFeRD Rechnungen importiert',
|
||||||
|
'Alle Rechnungen wurden erfolgreich verarbeitet',
|
||||||
|
dol_buildpath('/fourn/facture/list.php', 1),
|
||||||
|
'Anzeigen'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMAP connection issues
|
||||||
|
if (strpos($this->error, 'IMAP connection failed') !== false) {
|
||||||
|
$this->notify(
|
||||||
|
'error',
|
||||||
|
'IMAP Verbindung fehlgeschlagen',
|
||||||
|
'E-Mail Postfach für ZUGFeRD-Import nicht erreichbar',
|
||||||
|
dol_buildpath('/importzugferd/admin/setup.php', 1),
|
||||||
|
'IMAP prüfen'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count pending (draft) supplier invoices
|
||||||
|
*
|
||||||
|
* @return int Number of draft supplier invoices
|
||||||
|
*/
|
||||||
|
protected function countPendingInvoices()
|
||||||
|
{
|
||||||
|
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."facture_fourn";
|
||||||
|
$sql .= " WHERE fk_statut = 0"; // Draft status
|
||||||
|
$sql .= " AND entity IN (".getEntity('facture_fourn').")";
|
||||||
|
|
||||||
|
$resql = $this->db->query($sql);
|
||||||
|
if ($resql) {
|
||||||
|
$obj = $this->db->fetch_object($resql);
|
||||||
|
return (int) $obj->cnt;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -520,9 +520,22 @@ class Datanorm extends CommonObject
|
||||||
if ($result > 0) {
|
if ($result > 0) {
|
||||||
$results[] = $this->toArray();
|
$results[] = $this->toArray();
|
||||||
$foundIds[$this->id] = true;
|
$foundIds[$this->id] = true;
|
||||||
// Store EAN and manufacturer_ref for cross-catalog search
|
// Store EAN from Datanorm
|
||||||
$foundEan = $this->ean;
|
$foundEan = $this->ean;
|
||||||
$foundManufacturerRef = $this->manufacturer_ref;
|
|
||||||
|
// If Datanorm has no EAN, try to get it from supplier price (barcode field)
|
||||||
|
if (empty($foundEan)) {
|
||||||
|
$sqlEan = "SELECT barcode FROM " . MAIN_DB_PREFIX . "product_fournisseur_price";
|
||||||
|
$sqlEan .= " WHERE fk_soc = " . (int)$fk_soc;
|
||||||
|
$sqlEan .= " AND ref_fourn = '" . $this->db->escape($article_number) . "'";
|
||||||
|
$sqlEan .= " AND barcode IS NOT NULL AND barcode != ''";
|
||||||
|
$sqlEan .= " LIMIT 1";
|
||||||
|
$resEan = $this->db->query($sqlEan);
|
||||||
|
if ($resEan && $this->db->num_rows($resEan) > 0) {
|
||||||
|
$objEan = $this->db->fetch_object($resEan);
|
||||||
|
$foundEan = $objEan->barcode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If not searching all catalogs, return immediately
|
// If not searching all catalogs, return immediately
|
||||||
if (!$searchAll) {
|
if (!$searchAll) {
|
||||||
|
|
@ -531,24 +544,15 @@ class Datanorm extends CommonObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If searchAll is enabled and we found article with EAN/manufacturer_ref,
|
// If searchAll is enabled and we found article with EAN,
|
||||||
// search other catalogs using these identifiers (cross-catalog search)
|
// search other catalogs using EAN ONLY (cross-catalog search)
|
||||||
if ($searchAll && $fk_soc > 0 && (!empty($foundEan) || !empty($foundManufacturerRef))) {
|
// Note: Artikelnummern-Vergleich macht keinen Sinn über Kataloge hinweg
|
||||||
|
if ($searchAll && $fk_soc > 0 && !empty($foundEan)) {
|
||||||
$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";
|
||||||
$sql .= " FROM " . MAIN_DB_PREFIX . $this->table_element;
|
$sql .= " FROM " . MAIN_DB_PREFIX . $this->table_element;
|
||||||
$sql .= " WHERE (";
|
$sql .= " WHERE ean = '" . $this->db->escape($foundEan) . "'";
|
||||||
|
|
||||||
$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 active = 1";
|
||||||
$sql .= " AND entity = " . (int) $conf->entity;
|
$sql .= " AND entity = " . (int) $conf->entity;
|
||||||
$sql .= " AND fk_soc != " . (int) $fk_soc; // Exclude already found supplier
|
$sql .= " AND fk_soc != " . (int) $fk_soc; // Exclude already found supplier
|
||||||
|
|
@ -588,55 +592,44 @@ class Datanorm extends CommonObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: Search by partial match on article_number, ean, or manufacturer_ref
|
// Fallback: Search by EXACT article number match for the specified supplier only
|
||||||
$sql = "SELECT rowid, fk_soc, article_number, short_text1, short_text2,";
|
// No LIKE search - cross-catalog comparisons only work via EAN
|
||||||
$sql .= " ean, manufacturer_ref, manufacturer_name, unit_code,";
|
if ($fk_soc > 0 && empty($results)) {
|
||||||
$sql .= " price, price_unit, discount_group, product_group, matchcode";
|
$sql = "SELECT rowid, fk_soc, article_number, short_text1, short_text2,";
|
||||||
$sql .= " FROM " . MAIN_DB_PREFIX . $this->table_element;
|
$sql .= " ean, manufacturer_ref, manufacturer_name, unit_code,";
|
||||||
$sql .= " WHERE (article_number LIKE '" . $this->db->escape($article_number) . "%'";
|
$sql .= " price, price_unit, discount_group, product_group, matchcode";
|
||||||
$sql .= " OR ean = '" . $this->db->escape($article_number) . "'";
|
$sql .= " FROM " . MAIN_DB_PREFIX . $this->table_element;
|
||||||
$sql .= " OR manufacturer_ref LIKE '" . $this->db->escape($article_number) . "%')";
|
$sql .= " WHERE article_number = '" . $this->db->escape($article_number) . "'";
|
||||||
$sql .= " AND active = 1";
|
|
||||||
$sql .= " AND entity = " . (int) $conf->entity;
|
|
||||||
|
|
||||||
if ($fk_soc > 0 && !$searchAll) {
|
|
||||||
$sql .= " AND fk_soc = " . (int) $fk_soc;
|
$sql .= " AND fk_soc = " . (int) $fk_soc;
|
||||||
}
|
$sql .= " AND active = 1";
|
||||||
|
$sql .= " AND entity = " . (int) $conf->entity;
|
||||||
|
$sql .= " LIMIT 1";
|
||||||
|
|
||||||
// ORDER BY clause
|
$resql = $this->db->query($sql);
|
||||||
if ($fk_soc > 0 && $searchAll) {
|
if ($resql) {
|
||||||
// Order by matching supplier first, then by price
|
while ($obj = $this->db->fetch_object($resql)) {
|
||||||
$sql .= " ORDER BY CASE WHEN fk_soc = " . (int) $fk_soc . " THEN 0 ELSE 1 END, price ASC";
|
if (!isset($foundIds[$obj->rowid])) {
|
||||||
} else {
|
$results[] = array(
|
||||||
$sql .= " ORDER BY article_number";
|
'id' => $obj->rowid,
|
||||||
}
|
'fk_soc' => $obj->fk_soc,
|
||||||
|
'article_number' => $obj->article_number,
|
||||||
$sql .= " LIMIT " . (int) $limit;
|
'short_text1' => $obj->short_text1,
|
||||||
|
'short_text2' => $obj->short_text2,
|
||||||
$resql = $this->db->query($sql);
|
'ean' => $obj->ean,
|
||||||
if ($resql) {
|
'manufacturer_ref' => $obj->manufacturer_ref,
|
||||||
while ($obj = $this->db->fetch_object($resql)) {
|
'manufacturer_name' => $obj->manufacturer_name,
|
||||||
if (!isset($foundIds[$obj->rowid])) {
|
'unit_code' => $obj->unit_code,
|
||||||
$results[] = array(
|
'price' => $obj->price,
|
||||||
'id' => $obj->rowid,
|
'price_unit' => $obj->price_unit,
|
||||||
'fk_soc' => $obj->fk_soc,
|
'discount_group' => $obj->discount_group,
|
||||||
'article_number' => $obj->article_number,
|
'product_group' => $obj->product_group,
|
||||||
'short_text1' => $obj->short_text1,
|
'matchcode' => $obj->matchcode,
|
||||||
'short_text2' => $obj->short_text2,
|
);
|
||||||
'ean' => $obj->ean,
|
$foundIds[$obj->rowid] = true;
|
||||||
'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);
|
||||||
}
|
}
|
||||||
$this->db->free($resql);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $results;
|
return $results;
|
||||||
|
|
|
||||||
|
|
@ -624,6 +624,32 @@ class ZugferdImport extends CommonObject
|
||||||
if (!empty($match) && $match['fk_product'] > 0) {
|
if (!empty($match) && $match['fk_product'] > 0) {
|
||||||
$fk_product = $match['fk_product'];
|
$fk_product = $match['fk_product'];
|
||||||
$match_method = $match['method'];
|
$match_method = $match['method'];
|
||||||
|
|
||||||
|
// Update supplier price with EAN from invoice if empty
|
||||||
|
$invoiceEan = !empty($line_data['product']['global_id']) ? trim($line_data['product']['global_id']) : '';
|
||||||
|
$supplierRef = !empty($line_data['product']['seller_id']) ? $line_data['product']['seller_id'] : '';
|
||||||
|
if (!empty($invoiceEan) && !empty($supplierRef) && ctype_digit($invoiceEan)) {
|
||||||
|
// Barcode-Typ basierend auf Länge bestimmen
|
||||||
|
$eanLen = strlen($invoiceEan);
|
||||||
|
if ($eanLen == 13) {
|
||||||
|
$barcodeType = 2; // EAN13
|
||||||
|
} elseif ($eanLen == 8) {
|
||||||
|
$barcodeType = 1; // EAN8
|
||||||
|
} elseif ($eanLen == 12) {
|
||||||
|
$barcodeType = 3; // UPC-A
|
||||||
|
} else {
|
||||||
|
$barcodeType = 0; // Unbekannt
|
||||||
|
}
|
||||||
|
|
||||||
|
$sqlEan = "UPDATE " . MAIN_DB_PREFIX . "product_fournisseur_price";
|
||||||
|
$sqlEan .= " SET barcode = '" . $this->db->escape($invoiceEan) . "'";
|
||||||
|
$sqlEan .= ", fk_barcode_type = " . (int)$barcodeType;
|
||||||
|
$sqlEan .= " WHERE fk_product = " . (int)$fk_product;
|
||||||
|
$sqlEan .= " AND fk_soc = " . (int)$supplier_id;
|
||||||
|
$sqlEan .= " AND ref_fourn = '" . $this->db->escape($supplierRef) . "'";
|
||||||
|
$sqlEan .= " AND (barcode IS NULL OR barcode = '')";
|
||||||
|
$this->db->query($sqlEan);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.6';
|
$this->version = '3.8';
|
||||||
// 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';
|
||||||
|
|
||||||
|
|
@ -616,6 +616,28 @@ class modImportZugferd extends DolibarrModules
|
||||||
'isModEnabled("importzugferd")' // enabled condition
|
'isModEnabled("importzugferd")' // enabled condition
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add extrafield for product price without copper surcharge (only for cables)
|
||||||
|
$extrafields->addExtraField(
|
||||||
|
'produktpreis', // attribute code
|
||||||
|
'Produktpreis', // label (translation key)
|
||||||
|
'price', // type (price field)
|
||||||
|
115, // position
|
||||||
|
'24,8', // 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 price unit (Preiseinheit) on supplier prices
|
// Add extrafield for price unit (Preiseinheit) on supplier prices
|
||||||
$extrafields->addExtraField(
|
$extrafields->addExtraField(
|
||||||
'preiseinheit', // attribute code
|
'preiseinheit', // attribute code
|
||||||
|
|
|
||||||
782
import.php
782
import.php
File diff suppressed because it is too large
Load diff
|
|
@ -451,6 +451,8 @@ LabelChange = Namensänderung
|
||||||
#
|
#
|
||||||
Kupferzuschlag = Kupferzuschlag
|
Kupferzuschlag = Kupferzuschlag
|
||||||
KupferzuschlagHelp = Metallzuschlag pro Einheit (wird aus Rechnungen extrahiert)
|
KupferzuschlagHelp = Metallzuschlag pro Einheit (wird aus Rechnungen extrahiert)
|
||||||
|
Produktpreis = Produktpreis
|
||||||
|
ProduktpreisHelp = Reiner Materialpreis ohne Kupferzuschlag (nur bei Kabeln)
|
||||||
Preiseinheit = Preiseinheit
|
Preiseinheit = Preiseinheit
|
||||||
PreiseinheitHelp = Anzahl Einheiten pro Preis (z.B. 100 = Preis pro 100 Stück)
|
PreiseinheitHelp = Anzahl Einheiten pro Preis (z.B. 100 = Preis pro 100 Stück)
|
||||||
Warengruppe = Warengruppe
|
Warengruppe = Warengruppe
|
||||||
|
|
@ -489,3 +491,7 @@ MoreExpensiveBy = %s%% teurer
|
||||||
RefreshProductListHelp = Produktlisten neu laden (nach Anlage neuer Produkte)
|
RefreshProductListHelp = Produktlisten neu laden (nach Anlage neuer Produkte)
|
||||||
SelectAll = Alle auswählen
|
SelectAll = Alle auswählen
|
||||||
DeselectAll = Keine auswählen
|
DeselectAll = Keine auswählen
|
||||||
|
|
||||||
|
# UI Buttons
|
||||||
|
ExpandAll = Alle aufklappen
|
||||||
|
CollapseAll = Alle zuklappen
|
||||||
|
|
|
||||||
|
|
@ -389,6 +389,8 @@ NotifyEmail = Recipient email
|
||||||
#
|
#
|
||||||
Kupferzuschlag = Copper Surcharge
|
Kupferzuschlag = Copper Surcharge
|
||||||
KupferzuschlagHelp = Metal surcharge per unit (extracted from invoices)
|
KupferzuschlagHelp = Metal surcharge per unit (extracted from invoices)
|
||||||
|
Produktpreis = Material Price
|
||||||
|
ProduktpreisHelp = Material price without copper surcharge (cables only)
|
||||||
Preiseinheit = Price Unit
|
Preiseinheit = Price Unit
|
||||||
PreiseinheitHelp = Number of units per price (e.g. 100 = price per 100 pieces)
|
PreiseinheitHelp = Number of units per price (e.g. 100 = price per 100 pieces)
|
||||||
Warengruppe = Product Group
|
Warengruppe = Product Group
|
||||||
|
|
@ -420,3 +422,7 @@ MoreExpensiveBy = %s%% more expensive
|
||||||
RefreshProductListHelp = Refresh product lists (after creating new products)
|
RefreshProductListHelp = Refresh product lists (after creating new products)
|
||||||
SelectAll = Select all
|
SelectAll = Select all
|
||||||
DeselectAll = Deselect all
|
DeselectAll = Deselect all
|
||||||
|
|
||||||
|
# UI Buttons
|
||||||
|
ExpandAll = Expand all
|
||||||
|
CollapseAll = Collapse all
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue