565 lines
25 KiB
PHP
565 lines
25 KiB
PHP
<?php
|
||
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||
*
|
||
* 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'));
|
||
}
|