From bb761ebd89fc5b74f003d68e9661527d4cde4460 Mon Sep 17 00:00:00 2001 From: data Date: Tue, 3 Feb 2026 13:47:05 +0100 Subject: [PATCH] Import Produkt Gruppen von einem Objekt zum anderen --- ajax/import_from_origin.php | 565 ++++++++++++++++++++++++++ class/actions_subtotaltitle.class.php | 44 +- js/subtotaltitle.js | 121 ++++++ langs/de_DE/subtotaltitle.lang | 11 + langs/en_US/subtotaltitle.lang | 11 + 5 files changed, 751 insertions(+), 1 deletion(-) create mode 100644 ajax/import_from_origin.php diff --git a/ajax/import_from_origin.php b/ajax/import_from_origin.php new file mode 100644 index 0000000..1e93dda --- /dev/null +++ b/ajax/import_from_origin.php @@ -0,0 +1,565 @@ + + * + * Import sections/textlines from origin document (Angebot→Auftrag→Rechnung) + */ + +define('NOTOKENRENEWAL', 1); + +$res = 0; +if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php"; +if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php"; +if (!$res) die("Include of main fails"); + +require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; +require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php'; +require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php'; +dol_include_once('/subtotaltitle/lib/subtotaltitle.lib.php'); +require_once __DIR__.'/../class/DocumentTypeHelper.class.php'; + +header('Content-Type: application/json'); + +$action = GETPOST('action', 'alpha'); +$target_id = GETPOST('target_id', 'int'); +$target_type = GETPOST('target_type', 'alpha'); + +subtotaltitle_debug_log('📥 import_from_origin: action='.$action.', target_id='.$target_id.', target_type='.$target_type); + +if (!$target_id || !$target_type) { + echo json_encode(array('success' => false, 'error' => 'Missing parameters')); + exit; +} + +// Hole die richtigen Tabellennamen für Ziel-Dokumenttyp +$target_tables = DocumentTypeHelper::getTableNames($target_type); +if (!$target_tables) { + echo json_encode(array('success' => false, 'error' => 'Invalid target document type')); + exit; +} + +/** + * Ermittelt das Ursprungsdokument basierend auf Zieldokument + * Dolibarr speichert die Herkunft in origin/origin_id ODER in llx_element_element + */ +function getOriginDocument($db, $target_id, $target_type) +{ + $target_tables = DocumentTypeHelper::getTableNames($target_type); + if (!$target_tables) { + return null; + } + + // Lade Zieldokument + $target_doc = DocumentTypeHelper::loadDocument($target_type, $target_id, $db); + if (!$target_doc) { + subtotaltitle_debug_log('❌ Zieldokument nicht gefunden: '.$target_type.' #'.$target_id); + return null; + } + + subtotaltitle_debug_log('🔍 Zieldokument geladen: element='.$target_doc->element.', origin='.($target_doc->origin ?? 'NULL').', origin_id='.($target_doc->origin_id ?? 'NULL')); + + // Methode 1: Direkte Objekteigenschaften prüfen + $origin = $target_doc->origin ?? null; + $origin_id = $target_doc->origin_id ?? null; + + // Methode 2: Falls nicht gesetzt, prüfe llx_element_element Tabelle + if (empty($origin) || empty($origin_id)) { + $elementType = $target_doc->element; // z.B. 'commande', 'facture', 'propal' + subtotaltitle_debug_log('🔍 Suche in element_element für '.$elementType.' #'.$target_id); + + $sql_origin = "SELECT fk_source, sourcetype FROM ".MAIN_DB_PREFIX."element_element"; + $sql_origin .= " WHERE fk_target = ".(int)$target_id; + $sql_origin .= " AND targettype = '".$db->escape($elementType)."'"; + $sql_origin .= " LIMIT 1"; + + subtotaltitle_debug_log('SQL: '.$sql_origin); + + $res_origin = $db->query($sql_origin); + if ($res_origin && $db->num_rows($res_origin) > 0) { + $obj_origin = $db->fetch_object($res_origin); + $origin = $obj_origin->sourcetype; + $origin_id = $obj_origin->fk_source; + subtotaltitle_debug_log('✅ Gefunden in element_element: '.$origin.' #'.$origin_id); + } else { + subtotaltitle_debug_log('❌ Kein Eintrag in element_element gefunden'); + } + } + + // Prüfe ob origin gesetzt ist + if (empty($origin) || empty($origin_id)) { + subtotaltitle_debug_log('❌ Kein Ursprungsdokument verknüpft (weder direkt noch in element_element)'); + return null; + } + + // Mappe Dolibarr origin zu unserem document_type + $origin_type_map = array( + 'propal' => 'propal', + 'commande' => 'order', + 'facture' => 'invoice', + 'order_supplier' => null, // Lieferantenauftrag - nicht unterstützt + 'invoice_supplier' => null // Lieferantenrechnung - nicht unterstützt + ); + + $origin_type = isset($origin_type_map[$origin]) ? $origin_type_map[$origin] : null; + if (!$origin_type) { + subtotaltitle_debug_log('❌ Nicht unterstützter Ursprungstyp: '.$origin); + return null; + } + + // Lade Ursprungsdokument + $origin_doc = DocumentTypeHelper::loadDocument($origin_type, $origin_id, $db); + if (!$origin_doc) { + subtotaltitle_debug_log('❌ Ursprungsdokument nicht gefunden: '.$origin_type.' #'.$origin_id); + return null; + } + + subtotaltitle_debug_log('✅ Ursprungsdokument gefunden: '.$origin_type.' #'.$origin_id); + + return array( + 'document' => $origin_doc, + 'type' => $origin_type, + 'id' => $origin_id + ); +} + +/** + * Sucht die passende Produktzeile im Zieldokument basierend auf fk_product + */ +function findMatchingProductLine($db, $target_id, $target_type, $source_product_id) +{ + if (!$source_product_id) { + return null; + } + + $target_tables = DocumentTypeHelper::getTableNames($target_type); + + // Suche nach Zeile mit gleichem Produkt im Zieldokument + $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$target_tables['lines_table']; + $sql .= " WHERE ".$target_tables['fk_parent']." = ".(int)$target_id; + $sql .= " AND fk_product = ".(int)$source_product_id; + $sql .= " LIMIT 1"; + + $resql = $db->query($sql); + if ($resql && $db->num_rows($resql) > 0) { + $obj = $db->fetch_object($resql); + return $obj->rowid; + } + + return null; +} + +if ($action == 'check') { + // ========== PRÜFE OB IMPORT MÖGLICH IST ========== + + $origin = getOriginDocument($db, $target_id, $target_type); + if (!$origin) { + echo json_encode(array( + 'success' => true, + 'has_origin' => false, + 'message' => 'Kein Ursprungsdokument verknüpft' + )); + exit; + } + + $origin_tables = DocumentTypeHelper::getTableNames($origin['type']); + + // Zähle Sections und Textlines im Ursprungsdokument + $sql = "SELECT COUNT(*) as cnt, line_type FROM ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql .= " WHERE ".$origin_tables['fk_parent']." = ".(int)$origin['id']; + $sql .= " AND document_type = '".$db->escape($origin['type'])."'"; + $sql .= " AND line_type IN ('section', 'text')"; + $sql .= " GROUP BY line_type"; + + $resql = $db->query($sql); + $counts = array('section' => 0, 'text' => 0); + while ($obj = $db->fetch_object($resql)) { + $counts[$obj->line_type] = $obj->cnt; + } + + // Prüfe ob im Zieldokument schon Sections existieren + $sql_target = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql_target .= " WHERE ".$target_tables['fk_parent']." = ".(int)$target_id; + $sql_target .= " AND document_type = '".$db->escape($target_type)."'"; + $sql_target .= " AND line_type = 'section'"; + $res_target = $db->query($sql_target); + $obj_target = $db->fetch_object($res_target); + $has_existing = ($obj_target && $obj_target->cnt > 0); + + // Ermittle Anzeigename für Ursprungsdokument + $origin_name = ''; + $origin_ref = $origin['document']->ref; + switch ($origin['type']) { + case 'propal': + $origin_name = 'Angebot '.$origin_ref; + break; + case 'order': + $origin_name = 'Auftrag '.$origin_ref; + break; + case 'invoice': + $origin_name = 'Rechnung '.$origin_ref; + break; + } + + echo json_encode(array( + 'success' => true, + 'has_origin' => true, + 'origin_type' => $origin['type'], + 'origin_id' => $origin['id'], + 'origin_ref' => $origin_ref, + 'origin_name' => $origin_name, + 'sections_count' => (int)$counts['section'], + 'textlines_count' => (int)$counts['text'], + 'has_existing' => $has_existing, + 'can_import' => ($counts['section'] > 0 || $counts['text'] > 0) + )); + +} elseif ($action == 'import') { + // ========== KOMPLETTER IMPORT MIT RANG-SYNCHRONISATION ========== + // Strategie: + // 1. Lösche bestehende Einträge in Manager-Tabelle für Zieldokument + // 2. Importiere alle Sections/Textlines/Subtotals aus Ursprung + // 3. Synchronisiere ALLE Produkte aus Ziel-Dolibarr-Tabelle in Manager-Tabelle + // 4. Ordne Produkte den Sections zu basierend auf fk_product Matching + // 5. Produkte die NUR im Zieldokument sind, kommen am Ende + // 6. Neu-Nummerierung line_order UND rang in beiden Tabellen + + $origin = getOriginDocument($db, $target_id, $target_type); + if (!$origin) { + echo json_encode(array('success' => false, 'error' => 'Kein Ursprungsdokument gefunden')); + exit; + } + + $origin_tables = DocumentTypeHelper::getTableNames($origin['type']); + + // Starte Transaktion + $db->begin(); + + $imported_sections = 0; + $imported_textlines = 0; + $imported_subtotals = 0; + $product_assignments = 0; + $new_products = 0; + $section_mapping = array(); // Alte Section-ID => Neue Section-ID + + try { + // ============================================================ + // SCHRITT 1: Lösche bestehende Einträge in Manager-Tabelle + // ============================================================ + subtotaltitle_debug_log('🗑️ Lösche bestehende Manager-Einträge...'); + + $sql_delete = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql_delete .= " WHERE ".$target_tables['fk_parent']." = ".(int)$target_id; + $sql_delete .= " AND document_type = '".$db->escape($target_type)."'"; + $db->query($sql_delete); + + subtotaltitle_debug_log('✅ Bestehende Einträge gelöscht'); + + // FK-Werte für Zieldokument + $fk_facture = ($target_type === 'invoice') ? (int)$target_id : 'NULL'; + $fk_propal = ($target_type === 'propal') ? (int)$target_id : 'NULL'; + $fk_commande = ($target_type === 'order') ? (int)$target_id : 'NULL'; + + // ============================================================ + // SCHRITT 2: Baue Mapping fk_product → Section aus Ursprung + // ============================================================ + subtotaltitle_debug_log('🗺️ Erstelle fk_product → Section Mapping...'); + + $product_section_map = array(); // fk_product => origin_section_id + + $sql_origin_products = "SELECT m.parent_section, d.fk_product FROM ".MAIN_DB_PREFIX."facture_lines_manager m"; + $sql_origin_products .= " LEFT JOIN ".MAIN_DB_PREFIX.$origin_tables['lines_table']." d ON d.rowid = m.".$origin_tables['fk_line']; + $sql_origin_products .= " WHERE m.".$origin_tables['fk_parent']." = ".(int)$origin['id']; + $sql_origin_products .= " AND m.document_type = '".$db->escape($origin['type'])."'"; + $sql_origin_products .= " AND m.line_type = 'product'"; + $sql_origin_products .= " AND m.parent_section IS NOT NULL"; + $sql_origin_products .= " AND d.fk_product IS NOT NULL"; + + $res_origin_products = $db->query($sql_origin_products); + while ($row = $db->fetch_object($res_origin_products)) { + $product_section_map[$row->fk_product] = $row->parent_section; + subtotaltitle_debug_log(' Mapping: fk_product='.$row->fk_product.' → Section #'.$row->parent_section); + } + + subtotaltitle_debug_log('✅ '.count($product_section_map).' Produkt-Section Mappings erstellt'); + + // ============================================================ + // SCHRITT 3: Hole ALLE Einträge aus Ursprung (sortiert nach line_order) + // ============================================================ + subtotaltitle_debug_log('📦 Hole alle Einträge aus Ursprungsdokument...'); + + $sql_origin_all = "SELECT m.*, d.fk_product FROM ".MAIN_DB_PREFIX."facture_lines_manager m"; + $sql_origin_all .= " LEFT JOIN ".MAIN_DB_PREFIX.$origin_tables['lines_table']." d ON d.rowid = m.".$origin_tables['fk_line']; + $sql_origin_all .= " WHERE m.".$origin_tables['fk_parent']." = ".(int)$origin['id']; + $sql_origin_all .= " AND m.document_type = '".$db->escape($origin['type'])."'"; + $sql_origin_all .= " ORDER BY m.line_order"; + + $res_origin_all = $db->query($sql_origin_all); + + // Sammle alle Einträge gruppiert + $origin_entries = array(); + while ($entry = $db->fetch_object($res_origin_all)) { + $origin_entries[] = $entry; + } + + subtotaltitle_debug_log('✅ '.count($origin_entries).' Einträge aus Ursprung geladen'); + + // ============================================================ + // SCHRITT 4: Hole ALLE Produktzeilen aus Zieldokument (Dolibarr-Tabelle) + // ============================================================ + subtotaltitle_debug_log('📦 Hole alle Produkte aus Zieldokument...'); + + $sql_target_products = "SELECT rowid, fk_product, rang FROM ".MAIN_DB_PREFIX.$target_tables['lines_table']; + $sql_target_products .= " WHERE ".$target_tables['fk_parent']." = ".(int)$target_id; + $sql_target_products .= " ORDER BY rang"; + + $res_target_products = $db->query($sql_target_products); + $target_products = array(); + while ($row = $db->fetch_object($res_target_products)) { + $target_products[$row->rowid] = $row; + } + + subtotaltitle_debug_log('✅ '.count($target_products).' Produkte aus Zieldokument geladen'); + + // Sammle fk_products die schon zugeordnet werden (aus Ursprung) + $assigned_fk_products = array(); + $assigned_line_ids = array(); + + // ============================================================ + // SCHRITT 5: Importiere Structure aus Ursprung mit richtiger Reihenfolge + // ============================================================ + subtotaltitle_debug_log('🏗️ Importiere Struktur aus Ursprungsdokument...'); + + $line_order = 10; + $rang = 1; + $new_entries = array(); // Sammle alle neuen Einträge für spätere Rang-Zuweisung + + foreach ($origin_entries as $entry) { + if ($entry->line_type === 'section') { + // Section importieren + $sql_insert = "INSERT INTO ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql_insert .= " (fk_facture, fk_propal, fk_commande, document_type, line_type, title,"; + $sql_insert .= " parent_section, show_subtotal, collapsed, line_order, in_facturedet, date_creation)"; + $sql_insert .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.","; + $sql_insert .= " '".$db->escape($target_type)."', 'section',"; + $sql_insert .= " '".$db->escape($entry->title)."',"; + $sql_insert .= " NULL,"; + $sql_insert .= " ".(int)$entry->show_subtotal.","; + $sql_insert .= " ".(int)$entry->collapsed.","; + $sql_insert .= " ".(int)$line_order.","; + $sql_insert .= " 0, NOW())"; + + if (!$db->query($sql_insert)) { + throw new Exception('Fehler beim Erstellen der Section: '.$db->lasterror()); + } + + $new_section_id = $db->last_insert_id(MAIN_DB_PREFIX."facture_lines_manager"); + $section_mapping[$entry->rowid] = $new_section_id; + $imported_sections++; + $line_order += 10; + + subtotaltitle_debug_log('✅ Section: "'.$entry->title.'" (#'.$entry->rowid.' → #'.$new_section_id.')'); + + } elseif ($entry->line_type === 'text') { + // Textline importieren + $new_parent = isset($section_mapping[$entry->parent_section]) + ? (int)$section_mapping[$entry->parent_section] + : 'NULL'; + + $sql_insert = "INSERT INTO ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql_insert .= " (fk_facture, fk_propal, fk_commande, document_type, line_type, title,"; + $sql_insert .= " parent_section, line_order, in_facturedet, date_creation)"; + $sql_insert .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.","; + $sql_insert .= " '".$db->escape($target_type)."', 'text',"; + $sql_insert .= " '".$db->escape($entry->title)."',"; + $sql_insert .= " ".$new_parent.","; + $sql_insert .= " ".(int)$line_order.","; + $sql_insert .= " 0, NOW())"; + + if (!$db->query($sql_insert)) { + throw new Exception('Fehler beim Erstellen der Textline: '.$db->lasterror()); + } + + $imported_textlines++; + $line_order += 10; + + subtotaltitle_debug_log('✅ Textline: "'.$entry->title.'"'); + + } elseif ($entry->line_type === 'subtotal') { + // Subtotal importieren (nur wenn parent Section existiert) + if (!isset($section_mapping[$entry->parent_section])) { + continue; + } + + $new_parent = (int)$section_mapping[$entry->parent_section]; + + $sql_insert = "INSERT INTO ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql_insert .= " (fk_facture, fk_propal, fk_commande, document_type, line_type, title,"; + $sql_insert .= " parent_section, line_order, in_facturedet, date_creation)"; + $sql_insert .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.","; + $sql_insert .= " '".$db->escape($target_type)."', 'subtotal',"; + $sql_insert .= " '".$db->escape($entry->title)."',"; + $sql_insert .= " ".$new_parent.","; + $sql_insert .= " ".(int)$line_order.","; + $sql_insert .= " 0, NOW())"; + + if (!$db->query($sql_insert)) { + throw new Exception('Fehler beim Erstellen des Subtotals: '.$db->lasterror()); + } + + $imported_subtotals++; + $line_order += 10; + + subtotaltitle_debug_log('✅ Subtotal für Section #'.$new_parent); + + } elseif ($entry->line_type === 'product' && !empty($entry->fk_product)) { + // Produkt - finde passende Zeile im Zieldokument + $target_line_id = null; + + foreach ($target_products as $tp_id => $tp) { + if ($tp->fk_product == $entry->fk_product && !isset($assigned_line_ids[$tp_id])) { + $target_line_id = $tp_id; + $assigned_line_ids[$tp_id] = true; + $assigned_fk_products[$entry->fk_product] = true; + break; + } + } + + if ($target_line_id) { + $new_parent = isset($section_mapping[$entry->parent_section]) + ? (int)$section_mapping[$entry->parent_section] + : 'NULL'; + + $sql_insert = "INSERT INTO ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql_insert .= " (fk_facture, fk_propal, fk_commande, ".$target_tables['fk_line'].", document_type,"; + $sql_insert .= " line_type, parent_section, line_order, in_facturedet, date_creation)"; + $sql_insert .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.","; + $sql_insert .= " ".(int)$target_line_id.", '".$db->escape($target_type)."',"; + $sql_insert .= " 'product', ".$new_parent.", ".(int)$line_order.", 1, NOW())"; + + if (!$db->query($sql_insert)) { + throw new Exception('Fehler beim Erstellen des Produkts: '.$db->lasterror()); + } + + // Speichere für Rang-Update + $new_entries[] = array( + 'type' => 'product', + 'line_id' => $target_line_id, + 'rang' => $rang + ); + + $product_assignments++; + $line_order += 10; + $rang++; + + subtotaltitle_debug_log('✅ Produkt: fk_product='.$entry->fk_product.' → Section #'.$new_parent.' (Line #'.$target_line_id.')'); + } + } + } + + // ============================================================ + // SCHRITT 6: Füge neue Produkte hinzu (nur im Zieldokument) + // ============================================================ + subtotaltitle_debug_log('➕ Füge neue Produkte hinzu (nur im Zieldokument)...'); + + foreach ($target_products as $tp_id => $tp) { + if (isset($assigned_line_ids[$tp_id])) { + continue; // Schon zugeordnet + } + + // Produkt ist NEU - füge am Ende hinzu (ohne Section) + $sql_insert = "INSERT INTO ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql_insert .= " (fk_facture, fk_propal, fk_commande, ".$target_tables['fk_line'].", document_type,"; + $sql_insert .= " line_type, parent_section, line_order, in_facturedet, date_creation)"; + $sql_insert .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.","; + $sql_insert .= " ".(int)$tp_id.", '".$db->escape($target_type)."',"; + $sql_insert .= " 'product', NULL, ".(int)$line_order.", 1, NOW())"; + + if (!$db->query($sql_insert)) { + throw new Exception('Fehler beim Erstellen des neuen Produkts: '.$db->lasterror()); + } + + // Speichere für Rang-Update + $new_entries[] = array( + 'type' => 'product', + 'line_id' => $tp_id, + 'rang' => $rang + ); + + $new_products++; + $line_order += 10; + $rang++; + + subtotaltitle_debug_log('➕ Neues Produkt: Line #'.$tp_id.' (fk_product='.$tp->fk_product.')'); + } + + // ============================================================ + // SCHRITT 7: Finale Neu-Nummerierung in beiden Tabellen + // ============================================================ + subtotaltitle_debug_log('🔄 Finale Neu-Nummerierung in beiden Tabellen...'); + + // A) line_order in Manager-Tabelle (basierend auf tatsächlicher Reihenfolge) + $sql_reorder = "SELECT rowid, line_type, ".$target_tables['fk_line']." as fk_line FROM ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql_reorder .= " WHERE ".$target_tables['fk_parent']." = ".(int)$target_id; + $sql_reorder .= " AND document_type = '".$db->escape($target_type)."'"; + $sql_reorder .= " ORDER BY line_order"; + + $res_reorder = $db->query($sql_reorder); + + $final_order = 10; + $final_rang = 1; + $product_rang_updates = array(); + + while ($row = $db->fetch_object($res_reorder)) { + // Update line_order + $sql_upd = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager SET line_order = ".(int)$final_order; + $sql_upd .= " WHERE rowid = ".(int)$row->rowid; + $db->query($sql_upd); + + // Sammle Rang für Produkte + if ($row->line_type === 'product' && $row->fk_line) { + $product_rang_updates[$row->fk_line] = $final_rang; + $final_rang++; + } + + $final_order += 10; + } + + subtotaltitle_debug_log('✅ line_order neu nummeriert'); + + // B) rang in Dolibarr-Tabelle aktualisieren + subtotaltitle_debug_log('🔄 Aktualisiere rang in Dolibarr-Tabelle ('.$target_tables['lines_table'].')...'); + + foreach ($product_rang_updates as $line_id => $new_rang) { + $sql_rang = "UPDATE ".MAIN_DB_PREFIX.$target_tables['lines_table']; + $sql_rang .= " SET rang = ".(int)$new_rang; + $sql_rang .= " WHERE rowid = ".(int)$line_id; + $db->query($sql_rang); + subtotaltitle_debug_log(' Line #'.$line_id.' → rang='.$new_rang); + } + + subtotaltitle_debug_log('✅ rang in Dolibarr-Tabelle aktualisiert ('.count($product_rang_updates).' Zeilen)'); + + // Commit Transaktion + $db->commit(); + + subtotaltitle_debug_log('✅ Import komplett: '.$imported_sections.' Sections, '.$imported_textlines.' Textlines, '.$imported_subtotals.' Subtotals, '.$product_assignments.' zugeordnete Produkte, '.$new_products.' neue Produkte'); + + echo json_encode(array( + 'success' => true, + 'imported_sections' => $imported_sections, + 'imported_textlines' => $imported_textlines, + 'imported_subtotals' => $imported_subtotals, + 'product_assignments' => $product_assignments, + 'new_products' => $new_products, + 'message' => sprintf('%d Sections, %d Textlines, %d Produkte zugeordnet, %d neue Produkte', + $imported_sections, $imported_textlines, $product_assignments, $new_products) + )); + + } catch (Exception $e) { + $db->rollback(); + subtotaltitle_debug_log('❌ Import fehlgeschlagen: '.$e->getMessage()); + echo json_encode(array('success' => false, 'error' => $e->getMessage())); + } + +} else { + echo json_encode(array('success' => false, 'error' => 'Unknown action')); +} diff --git a/class/actions_subtotaltitle.class.php b/class/actions_subtotaltitle.class.php index c4fa3ce..52f75ed 100755 --- a/class/actions_subtotaltitle.class.php +++ b/class/actions_subtotaltitle.class.php @@ -235,7 +235,15 @@ class ActionsSubtotalTitle extends CommonHookActions echo ' elementsRemovedWithErrors: '.json_encode($langs->trans('ElementsRemovedWithErrors')).','."\n"; echo ' successSyncedToInvoice: '.json_encode($langs->trans('SuccessSyncedToInvoice')).','."\n"; echo ' successRemovedFromInvoice: '.json_encode($langs->trans('SuccessRemovedFromInvoice')).','."\n"; - echo ' errorSyncing: '.json_encode($langs->trans('ErrorSyncing'))."\n"; + echo ' errorSyncing: '.json_encode($langs->trans('ErrorSyncing')).','."\n"; + // Import feature strings + echo ' importFromOrigin: '.json_encode($langs->trans('ImportFromOrigin')).','."\n"; + echo ' importFromOriginTitle: '.json_encode($langs->trans('ImportFromOriginTitle')).','."\n"; + echo ' importFromOriginConfirm: '.json_encode($langs->trans('ImportFromOriginConfirm')).','."\n"; + echo ' importFromOriginSuccess: '.json_encode($langs->trans('ImportFromOriginSuccess')).','."\n"; + echo ' importFromOriginNoOrigin: '.json_encode($langs->trans('ImportFromOriginNoOrigin')).','."\n"; + echo ' importFromOriginNoData: '.json_encode($langs->trans('ImportFromOriginNoData')).','."\n"; + echo ' importFromOriginError: '.json_encode($langs->trans('ImportFromOriginError'))."\n"; echo '};'."\n"; echo ''."\n"; @@ -247,6 +255,30 @@ class ActionsSubtotalTitle extends CommonHookActions $jsSyncPath = dol_buildpath('/custom/subtotaltitle/js/subtotaltitle_sync.js', 1); echo ''."\n"; + // Prüfe ob Dokument ein Ursprungsdokument hat (für Import-Feature) + // Methode 1: Direkte Objekteigenschaften + $hasOrigin = (!empty($object->origin) && !empty($object->origin_id)); + + // Methode 2: Falls nicht gesetzt, prüfe llx_element_element Tabelle + if (!$hasOrigin && $object->id > 0) { + $elementType = $object->element; // z.B. 'commande', 'facture', 'propal' + $sql_origin = "SELECT fk_source, sourcetype FROM ".MAIN_DB_PREFIX."element_element"; + $sql_origin .= " WHERE fk_target = ".(int)$object->id; + $sql_origin .= " AND targettype = '".$db->escape($elementType)."'"; + $sql_origin .= " LIMIT 1"; + $res_origin = $db->query($sql_origin); + if ($res_origin && $db->num_rows($res_origin) > 0) { + $obj_origin = $db->fetch_object($res_origin); + $object->origin = $obj_origin->sourcetype; + $object->origin_id = $obj_origin->fk_source; + $hasOrigin = true; + } + } + + // Debug-Log für Import-Feature + $logFile = '/srv/http/dolibarr/documents/subtotaltitle/debug.log'; + error_log('['.date('Y-m-d H:i:s').'] Import-Check - element: '.($object->element ?? 'NULL').', origin: '.($object->origin ?? 'NULL').', origin_id: '.($object->origin_id ?? 'NULL').', hasOrigin: '.($hasOrigin ? 'true' : 'false')."\n", 3, $logFile); + // Buttons nur im Entwurfsstatus anzeigen if ($is_draft) { // Textzeile-Button @@ -256,6 +288,16 @@ class ActionsSubtotalTitle extends CommonHookActions echo ' }'; echo '});'."\n"; + // Import-Button (wenn Ursprungsdokument existiert) + // Button erscheint immer - die check-Action prüft ob es etwas zu importieren gibt + if ($hasOrigin) { + echo ''."\n"; + } + // Massenlösch-Button (ans ENDE der Hauptzeile) - NUR EINMAL EINFÜGEN echo '