diff --git a/ajax/add_to_section.php b/ajax/add_to_section.php new file mode 100644 index 0000000..7e49913 --- /dev/null +++ b/ajax/add_to_section.php @@ -0,0 +1,140 @@ + false, 'error' => 'Missing parameters']); + exit; +} + +$tables = DocumentTypeHelper::getTableNames($docType); +if (!$tables) { + echo json_encode(['success' => false, 'error' => 'Invalid document type']); + exit; +} + +$db->begin(); + +// 1. Hole die Manager-Zeile des Produkts +$sql = "SELECT rowid, line_order FROM ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql .= " WHERE ".$tables['fk_line']." = ".(int)$line_id; +$sql .= " AND ".$tables['fk_parent']." = ".(int)$document_id; +$sql .= " AND document_type = '".$db->escape($docType)."'"; +$sql .= " AND line_type = 'product'"; +$resql = $db->query($sql); + +if (!$resql || $db->num_rows($resql) == 0) { + $db->rollback(); + echo json_encode(['success' => false, 'error' => 'Product not found in manager table']); + exit; +} + +$product = $db->fetch_object($resql); +$manager_id = $product->rowid; +$current_line_order = $product->line_order; + +// 2. Hole die line_order der Section +$sql_section = "SELECT line_order FROM ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql_section .= " WHERE rowid = ".(int)$section_id; +$sql_section .= " AND line_type = 'section'"; +$sql_section .= " AND document_type = '".$db->escape($docType)."'"; +$resql_section = $db->query($sql_section); + +if (!$resql_section || $db->num_rows($resql_section) == 0) { + $db->rollback(); + echo json_encode(['success' => false, 'error' => 'Section not found']); + exit; +} + +$section = $db->fetch_object($resql_section); +$section_line_order = $section->line_order; + +// 3. Finde die neue line_order für das Produkt (nach der Section, vor anderen Produkten der Section) +$sql_next = "SELECT MIN(line_order) as next_order FROM ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql_next .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id; +$sql_next .= " AND document_type = '".$db->escape($docType)."'"; +$sql_next .= " AND parent_section = ".(int)$section_id; +$sql_next .= " AND line_type IN ('product', 'text')"; +$resql_next = $db->query($sql_next); +$obj_next = $db->fetch_object($resql_next); + +if ($obj_next && $obj_next->next_order) { + // Es gibt bereits Produkte in dieser Section → füge VOR dem ersten ein + $new_line_order = $obj_next->next_order; +} else { + // Keine Produkte in der Section → füge direkt nach der Section ein + $new_line_order = $section_line_order + 1; +} + +subtotaltitle_debug_log(' current_line_order='.$current_line_order.', section_line_order='.$section_line_order.', new_line_order='.$new_line_order); + +// 4. Verschiebe Zeilen je nach Richtung +if ($current_line_order > $new_line_order) { + // Nach vorne: Verschiebe Zeilen von new_line_order bis BEFORE current um +1 + $sql_shift = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql_shift .= " SET line_order = line_order + 1"; + $sql_shift .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id; + $sql_shift .= " AND document_type = '".$db->escape($docType)."'"; + $sql_shift .= " AND line_order >= ".(int)$new_line_order; + $sql_shift .= " AND line_order < ".(int)$current_line_order; + $db->query($sql_shift); + subtotaltitle_debug_log(' Nach vorne verschoben: Zeilen '.$new_line_order.'-'.($current_line_order-1).' um +1'); +} elseif ($current_line_order < $new_line_order) { + // Nach hinten: Verschiebe Zeilen von current+1 bis new um -1 + $sql_shift = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql_shift .= " SET line_order = line_order - 1"; + $sql_shift .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id; + $sql_shift .= " AND document_type = '".$db->escape($docType)."'"; + $sql_shift .= " AND line_order > ".(int)$current_line_order; + $sql_shift .= " AND line_order <= ".(int)$new_line_order; + $db->query($sql_shift); + subtotaltitle_debug_log(' Nach hinten verschoben: Zeilen '.($current_line_order+1).'-'.$new_line_order.' um -1'); +} +// Wenn current == new, keine Verschiebung nötig + +// 5. Update das Produkt: setze parent_section und neue line_order +$sql_update = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql_update .= " SET parent_section = ".(int)$section_id; +$sql_update .= ", line_order = ".(int)$new_line_order; +$sql_update .= " WHERE rowid = ".(int)$manager_id; +$db->query($sql_update); + +subtotaltitle_debug_log('✅ Produkt #'.$line_id.' zu Section #'.$section_id.' hinzugefügt'); + +// 6. Sync rang in Detail-Tabelle +subtotaltitle_debug_log(' Starte rang-Synchronisation für docType='.$docType.' doc_id='.$document_id); + +$sql = "SELECT rowid, line_type, ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id; +$sql .= " AND document_type = '".$db->escape($docType)."'"; +$sql .= " AND ".$tables['fk_line']." IS NOT NULL"; +$sql .= " ORDER BY line_order"; +$resql = $db->query($sql); + +$rang = 1; +while ($obj = $db->fetch_object($resql)) { + $detail_id = $obj->detail_id; + if ($detail_id) { + $sql_upd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table']; + $sql_upd .= " SET rang = ".$rang; + $sql_upd .= " WHERE rowid = ".(int)$detail_id; + $db->query($sql_upd); + subtotaltitle_debug_log(' Sync rang: '.$obj->line_type.' manager#'.$obj->rowid.' detail#'.$detail_id.' → rang='.$rang); + $rang++; + } +} + +subtotaltitle_debug_log(' rang-Synchronisation abgeschlossen, '.$rang.' Zeilen synchronisiert'); + +$db->commit(); + +echo json_encode(['success' => true]); diff --git a/ajax/fix_section_hierarchy.php b/ajax/fix_section_hierarchy.php new file mode 100644 index 0000000..101f6de --- /dev/null +++ b/ajax/fix_section_hierarchy.php @@ -0,0 +1,182 @@ + false, 'error' => 'Missing parameters']); + exit; +} + +$tables = DocumentTypeHelper::getTableNames($docType); +if (!$tables) { + echo json_encode(['success' => false, 'error' => 'Invalid document type']); + exit; +} + +$db->begin(); + +echo "

Repariere Section-Hierarchie und Sortierung

"; + +// 1. Alle Sections haben parent_section=NULL (Sections können nicht in Sections sein!) +echo "

1. Korrigiere Section parent_section Werte

"; +$sql = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql .= " SET parent_section = NULL"; +$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; +$sql .= " AND document_type = '".$db->escape($docType)."'"; +$sql .= " AND line_type = 'section'"; +$sql .= " AND parent_section IS NOT NULL"; +$result = $db->query($sql); +echo "✅ Sections korrigiert (parent_section=NULL gesetzt)
"; + +// 2. Baue komplette neue line_order auf +echo "

2. Neu-Sortierung aller Zeilen

"; + +$new_order = 1; +$updates = array(); + +// Hole die line_order der ersten Section +$sql = "SELECT MIN(line_order) as first_section_order FROM ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; +$sql .= " AND document_type = '".$db->escape($docType)."'"; +$sql .= " AND line_type = 'section'"; +$resql = $db->query($sql); +$obj = $db->fetch_object($resql); +$first_section_order = $obj ? $obj->first_section_order : 9999; + +// Freie Produkte VOR den Sections +$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; +$sql .= " AND document_type = '".$db->escape($docType)."'"; +$sql .= " AND line_type = 'product'"; +$sql .= " AND (parent_section IS NULL OR parent_section = 0)"; +$sql .= " AND line_order < ".(int)$first_section_order; +$sql .= " ORDER BY line_order"; +$resql = $db->query($sql); +while ($obj = $db->fetch_object($resql)) { + $updates[$obj->rowid] = $new_order; + echo "Freies Produkt (VOR Sections) #".$obj->rowid." → line_order=".$new_order."
"; + $new_order++; +} + +// Alle Sections +$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; +$sql .= " AND document_type = '".$db->escape($docType)."'"; +$sql .= " AND line_type = 'section'"; +$sql .= " ORDER BY line_order"; +$resql = $db->query($sql); + +$sections = array(); +while ($obj = $db->fetch_object($resql)) { + $sections[] = $obj->rowid; +} + +foreach ($sections as $sec_id) { + // Section selbst + $updates[$sec_id] = $new_order; + echo "Section #".$sec_id." → line_order=".$new_order."
"; + $new_order++; + + // Produkte dieser Section + $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; + $sql .= " AND document_type = '".$db->escape($docType)."'"; + $sql .= " AND line_type = 'product'"; + $sql .= " AND parent_section = ".(int)$sec_id; + $sql .= " ORDER BY line_order"; + $resql = $db->query($sql); + + while ($obj = $db->fetch_object($resql)) { + $updates[$obj->rowid] = $new_order; + echo "  â†’ Produkt #".$obj->rowid." → line_order=".$new_order."
"; + $new_order++; + } + + // Textzeilen dieser Section + $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; + $sql .= " AND document_type = '".$db->escape($docType)."'"; + $sql .= " AND line_type = 'text'"; + $sql .= " AND parent_section = ".(int)$sec_id; + $sql .= " ORDER BY line_order"; + $resql = $db->query($sql); + + while ($obj = $db->fetch_object($resql)) { + $updates[$obj->rowid] = $new_order; + echo "  â†’ Text #".$obj->rowid." → line_order=".$new_order."
"; + $new_order++; + } + + // Subtotal dieser Section + $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; + $sql .= " AND document_type = '".$db->escape($docType)."'"; + $sql .= " AND line_type = 'subtotal'"; + $sql .= " AND parent_section = ".(int)$sec_id; + $resql = $db->query($sql); + + while ($obj = $db->fetch_object($resql)) { + $updates[$obj->rowid] = $new_order; + echo "  â†’ Subtotal #".$obj->rowid." → line_order=".$new_order."
"; + $new_order++; + } +} + +// Freie Produkte NACH den Sections (alle, die noch nicht processed wurden) +$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; +$sql .= " AND document_type = '".$db->escape($docType)."'"; +$sql .= " AND line_type = 'product'"; +$sql .= " AND (parent_section IS NULL OR parent_section = 0)"; +$sql .= " ORDER BY line_order"; +$resql = $db->query($sql); +while ($obj = $db->fetch_object($resql)) { + if (!isset($updates[$obj->rowid])) { + $updates[$obj->rowid] = $new_order; + echo "Freies Produkt (NACH Sections) #".$obj->rowid." → line_order=".$new_order."
"; + $new_order++; + } +} + +// 3. Updates ausführen +echo "

3. Schreibe neue line_order Werte

"; +foreach ($updates as $rowid => $order) { + $sql = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql .= " SET line_order = ".(int)$order; + $sql .= " WHERE rowid = ".(int)$rowid; + $db->query($sql); +} +echo "✅ ".count($updates)." Zeilen neu sortiert
"; + +// 4. Sync rang in Detail-Tabelle +echo "

4. Synchronisiere rang in Detail-Tabelle

"; +$sql = "SELECT rowid, line_type, ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; +$sql .= " AND document_type = '".$db->escape($docType)."'"; +$sql .= " AND ".$tables['fk_line']." IS NOT NULL"; +$sql .= " ORDER BY line_order"; +$resql = $db->query($sql); + +$rang = 1; +while ($obj = $db->fetch_object($resql)) { + $detail_id = $obj->detail_id; + if ($detail_id) { + $sql_upd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table']; + $sql_upd .= " SET rang = ".$rang; + $sql_upd .= " WHERE rowid = ".(int)$detail_id; + $db->query($sql_upd); + echo $obj->line_type." manager#".$obj->rowid." detail#".$detail_id." → rang=".$rang."
"; + $rang++; + } +} +echo "✅ ".$rang." Zeilen synchronisiert
"; + +$db->commit(); + +echo "

✅ Reparatur abgeschlossen!

"; +echo "

Zurück zum Kundenauftrag

"; diff --git a/ajax/move_section.php b/ajax/move_section.php index a0ef0d0..124ad5b 100755 --- a/ajax/move_section.php +++ b/ajax/move_section.php @@ -92,41 +92,43 @@ $sections[$current_index] = $sections[$swap_index]; $sections[$swap_index] = $temp; // 3. Baue komplette neue Reihenfolge auf +// Strategie: Freie Zeilen behalten ihre Position relativ zu Sections $new_order = 1; $updates = array(); +$processed = array(); -// Freie Produkte zuerst +// Hole die aktuelle line_order der ersten Section (VOR dem Swap) +$sql = "SELECT MIN(line_order) as min_section_order FROM ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; +$sql .= " AND document_type = '".$db->escape($docType)."'"; +$sql .= " AND line_type = 'section'"; +$resql = $db->query($sql); +$obj = $db->fetch_object($resql); +$first_section_order = $obj ? $obj->min_section_order : 9999; + +// 1. FREIE ZEILEN VOR allen Sections (line_order < erste Section) $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager"; $sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; $sql .= " AND document_type = '".$db->escape($docType)."'"; -$sql .= " AND line_type = 'product'"; -$sql .= " AND parent_section IS NULL"; +$sql .= " AND (parent_section IS NULL OR parent_section = 0)"; +$sql .= " AND line_type != 'section'"; +$sql .= " AND line_order < ".(int)$first_section_order; $sql .= " ORDER BY line_order"; $resql = $db->query($sql); while ($obj = $db->fetch_object($resql)) { $updates[$obj->rowid] = $new_order; + $processed[$obj->rowid] = true; + dol_syslog('[SubtotalTitle] move_section: Freie Zeile VOR Sections #'.$obj->rowid.' → line_order='.$new_order, LOG_INFO); $new_order++; } -// Freie Textzeilen -$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager"; -$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; -$sql .= " AND document_type = '".$db->escape($docType)."'"; -$sql .= " AND line_type = 'text'"; -$sql .= " AND parent_section IS NULL"; -$sql .= " ORDER BY line_order"; -$resql = $db->query($sql); - -while ($obj = $db->fetch_object($resql)) { - $updates[$obj->rowid] = $new_order; - $new_order++; -} - -// Sections in neuer Reihenfolge +// 2. SECTIONS in neuer Reihenfolge (nach dem Swap) foreach ($sections as $sec_id) { // Section-Header $updates[$sec_id] = $new_order; + $processed[$sec_id] = true; + dol_syslog('[SubtotalTitle] move_section: Section #'.$sec_id.' → line_order='.$new_order, LOG_INFO); $new_order++; // Produkte dieser Section @@ -140,6 +142,7 @@ foreach ($sections as $sec_id) { while ($obj = $db->fetch_object($resql)) { $updates[$obj->rowid] = $new_order; + $processed[$obj->rowid] = true; $new_order++; } @@ -154,6 +157,7 @@ foreach ($sections as $sec_id) { while ($obj = $db->fetch_object($resql)) { $updates[$obj->rowid] = $new_order; + $processed[$obj->rowid] = true; $new_order++; } @@ -167,19 +171,28 @@ foreach ($sections as $sec_id) { while ($obj = $db->fetch_object($resql)) { $updates[$obj->rowid] = $new_order; + $processed[$obj->rowid] = true; $new_order++; } } -// Freie Produkte am Ende (nach allen Sections) +// 3. FREIE ZEILEN NACH allen Sections (die noch nicht processed wurden) $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager"; $sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; $sql .= " AND document_type = '".$db->escape($docType)."'"; -$sql .= " AND line_type = 'product'"; -$sql .= " AND parent_section IS NULL"; -$sql .= " AND rowid NOT IN (SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE ".$tables['fk_parent']." = ".(int)$doc_id." AND document_type = '".$db->escape($docType)."' AND line_type = 'product' AND parent_section IS NULL AND line_order < (SELECT MIN(line_order) FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE ".$tables['fk_parent']." = ".(int)$doc_id." AND document_type = '".$db->escape($docType)."' AND line_type = 'section'))"; +$sql .= " AND (parent_section IS NULL OR parent_section = 0)"; +$sql .= " AND line_type != 'section'"; $sql .= " ORDER BY line_order"; -// Diese Abfrage ist zu komplex - die freien Produkte wurden bereits oben behandelt +$resql = $db->query($sql); + +while ($obj = $db->fetch_object($resql)) { + if (!isset($processed[$obj->rowid])) { + $updates[$obj->rowid] = $new_order; + $processed[$obj->rowid] = true; + dol_syslog('[SubtotalTitle] move_section: Freie Zeile NACH Sections #'.$obj->rowid.' → line_order='.$new_order, LOG_INFO); + $new_order++; + } +} // 4. Führe alle Updates aus foreach ($updates as $rowid => $order) { @@ -190,7 +203,9 @@ foreach ($updates as $rowid => $order) { } // 5. Sync rang in Detail-Tabelle -$sql = "SELECT ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager"; +dol_syslog('[SubtotalTitle] move_section: Starte rang-Synchronisation für docType='.$docType.' doc_id='.$doc_id, LOG_INFO); + +$sql = "SELECT rowid, line_type, ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager"; $sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id; $sql .= " AND document_type = '".$db->escape($docType)."'"; $sql .= " AND ".$tables['fk_line']." IS NOT NULL"; @@ -205,10 +220,13 @@ while ($obj = $db->fetch_object($resql)) { $sql_upd .= " SET rang = ".$rang; $sql_upd .= " WHERE rowid = ".(int)$detail_id; $db->query($sql_upd); + dol_syslog('[SubtotalTitle] Sync rang: '.$obj->line_type.' manager#'.$obj->rowid.' detail#'.$detail_id.' → rang='.$rang, LOG_INFO); $rang++; } } +dol_syslog('[SubtotalTitle] move_section: rang-Synchronisation abgeschlossen, '.$rang.' Zeilen synchronisiert', LOG_INFO); + $db->commit(); echo json_encode(array('success' => true)); diff --git a/ajax/remove_from_section.php b/ajax/remove_from_section.php index f3e25bb..a30856c 100755 --- a/ajax/remove_from_section.php +++ b/ajax/remove_from_section.php @@ -21,16 +21,93 @@ if (!$tables) { exit; } +$db->begin(); + +// 1. Hole parent_section und document_id BEVOR wir entfernen +$sql = "SELECT parent_section, ".$tables['fk_parent']." as doc_id FROM ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql .= " WHERE ".$tables['fk_line']." = ".(int)$product_id; +$sql .= " AND document_type = '".$db->escape($docType)."'"; +$resql = $db->query($sql); + +if (!$resql || $db->num_rows($resql) == 0) { + $db->rollback(); + echo json_encode(['success' => false, 'error' => 'Product not found in manager table']); + exit; +} + +$obj = $db->fetch_object($resql); +$old_section_id = $obj->parent_section; +$document_id = $obj->doc_id; + +// 2. Entferne aus Section $sql = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager"; $sql .= " SET parent_section = NULL"; $sql .= " WHERE ".$tables['fk_line']." = ".(int)$product_id; $sql .= " AND document_type = '".$db->escape($docType)."'"; +$db->query($sql); -$result = $db->query($sql); +subtotaltitle_debug_log('✅ Produkt #' . $product_id . ' aus Section #'.$old_section_id.' entfernt'); -if ($result) { - subtotaltitle_debug_log('✅ Produkt #' . $product_id . ' aus Section entfernt'); - echo json_encode(['success' => true]); -} else { - echo json_encode(['success' => false, 'error' => $db->lasterror()]); +// 3. Wenn Product aus einer Section entfernt wurde, Subtotal neu berechnen +if ($old_section_id > 0) { + // Prüfe ob Section Subtotal anzeigen soll + $sql = "SELECT show_subtotal, title FROM ".MAIN_DB_PREFIX."facture_lines_manager"; + $sql .= " WHERE rowid = ".(int)$old_section_id; + $sql .= " AND line_type = 'section'"; + $resql = $db->query($sql); + + if ($resql && $obj = $db->fetch_object($resql)) { + if ($obj->show_subtotal) { + // Berechne neue Summe + $sql_sum = "SELECT SUM(d.total_ht) as total"; + $sql_sum .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager m"; + $sql_sum .= " INNER JOIN ".MAIN_DB_PREFIX.$tables['lines_table']." d ON d.rowid = m.".$tables['fk_line']; + $sql_sum .= " WHERE m.parent_section = ".(int)$old_section_id; + $sql_sum .= " AND m.document_type = '".$db->escape($docType)."'"; + $sql_sum .= " AND m.line_type = 'product'"; + $res_sum = $db->query($sql_sum); + $obj_sum = $db->fetch_object($res_sum); + $new_total = $obj_sum->total ? (float)$obj_sum->total : 0; + + // Update Subtotal in Detail-Tabelle + $sql_upd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table']." d"; + $sql_upd .= " INNER JOIN ".MAIN_DB_PREFIX."facture_lines_manager m ON m.".$tables['fk_line']." = d.rowid"; + $sql_upd .= " SET d.subprice = ".(float)$new_total.", d.total_ht = ".(float)$new_total.", d.total_ttc = ".(float)$new_total; + $sql_upd .= " WHERE m.parent_section = ".(int)$old_section_id; + $sql_upd .= " AND m.document_type = '".$db->escape($docType)."'"; + $sql_upd .= " AND m.line_type = 'subtotal'"; + $db->query($sql_upd); + + subtotaltitle_debug_log(' Subtotal für Section #'.$old_section_id.' aktualisiert: '.$new_total); + } + } } + +// 4. Sync rang in Detail-Tabelle +subtotaltitle_debug_log(' Starte rang-Synchronisation für docType='.$docType.' doc_id='.$document_id); + +$sql = "SELECT rowid, line_type, ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager"; +$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id; +$sql .= " AND document_type = '".$db->escape($docType)."'"; +$sql .= " AND ".$tables['fk_line']." IS NOT NULL"; +$sql .= " ORDER BY line_order"; +$resql = $db->query($sql); + +$rang = 1; +while ($obj = $db->fetch_object($resql)) { + $detail_id = $obj->detail_id; + if ($detail_id) { + $sql_upd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table']; + $sql_upd .= " SET rang = ".$rang; + $sql_upd .= " WHERE rowid = ".(int)$detail_id; + $db->query($sql_upd); + subtotaltitle_debug_log(' Sync rang: '.$obj->line_type.' manager#'.$obj->rowid.' detail#'.$detail_id.' → rang='.$rang); + $rang++; + } +} + +subtotaltitle_debug_log(' rang-Synchronisation abgeschlossen, '.$rang.' Zeilen synchronisiert'); + +$db->commit(); + +echo json_encode(['success' => true]); diff --git a/js/subtotaltitle.js b/js/subtotaltitle.js index 06ed433..cc6b146 100755 --- a/js/subtotaltitle.js +++ b/js/subtotaltitle.js @@ -583,8 +583,8 @@ function addUnlinkColumn() { // Hat Section → Unlink-Button $row.append(''); } else { - // Keine Section → leere Zelle - $row.append(''); + // Keine Section → Link-Button (zu passender Section hinzufügen) + $row.append(''); } return; } @@ -1307,3 +1307,124 @@ function hexToRgba(hex, alpha) { var b = parseInt(hex.slice(5, 7), 16); return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')'; } + +/** + * Verknüpft ein freies Produkt mit der passenden Section + * - Produkte VOR allen Sections → erste Section + * - Produkte NACH allen Sections → letzte Section + * - Produkte ZWISCHEN Sections → Section in deren Bereich + */ +function linkToNearestSection(lineId) { + debugLog('🔗 linkToNearestSection: line=' + lineId); + + var docInfo = getDocumentInfo(); + if (!docInfo || !docInfo.id) { + alert('Fehler: Dokument-Kontext nicht gefunden'); + return; + } + + // Hole line_order des Produkts + var $productRow = $('tr[id*="' + lineId + '"]'); + var productLineOrder = parseInt($productRow.attr('data-line-order')); + + if (!productLineOrder) { + alert('Fehler: line_order nicht gefunden'); + return; + } + + debugLog(' Produkt line_order: ' + productLineOrder); + + // Sammle alle Sections mit ihrer line_order + var sections = []; + $('tr.section-header').each(function() { + var sectionId = $(this).attr('data-section-id'); + var sectionLineOrder = parseInt($(this).attr('data-line-order')); + var sectionTitle = $(this).attr('data-section-title'); + + if (sectionId && sectionLineOrder) { + sections.push({ + id: parseInt(sectionId), + lineOrder: sectionLineOrder, + title: sectionTitle + }); + } + }); + + if (sections.length === 0) { + alert('Keine Positionsgruppen gefunden'); + return; + } + + // Sortiere Sections nach line_order + sections.sort(function(a, b) { return a.lineOrder - b.lineOrder; }); + + debugLog(' Sections: ' + sections.map(function(s) { return s.id + ':' + s.lineOrder; }).join(', ')); + + // Finde passende Section + var targetSection = null; + var firstSection = sections[0]; + var lastSection = sections[sections.length - 1]; + + if (productLineOrder < firstSection.lineOrder) { + // VOR allen Sections → erste Section + targetSection = firstSection; + debugLog(' → VOR allen Sections → erste Section #' + targetSection.id); + } else if (productLineOrder > lastSection.lineOrder) { + // NACH allen Sections → letzte Section + targetSection = lastSection; + debugLog(' → NACH allen Sections → letzte Section #' + targetSection.id); + } else { + // ZWISCHEN Sections → finde Section in deren Bereich das Produkt liegt + for (var i = 0; i < sections.length - 1; i++) { + var currentSection = sections[i]; + var nextSection = sections[i + 1]; + + if (productLineOrder > currentSection.lineOrder && productLineOrder < nextSection.lineOrder) { + // Liegt zwischen currentSection und nextSection + // Regel: Nimm die Section, die vor dem Produkt liegt + targetSection = currentSection; + debugLog(' → ZWISCHEN Section #' + currentSection.id + ' und #' + nextSection.id + ' → nehme #' + targetSection.id); + break; + } + } + + // Falls nicht gefunden (sollte nicht passieren), nimm letzte Section + if (!targetSection) { + targetSection = lastSection; + debugLog(' → Fallback → letzte Section #' + targetSection.id); + } + } + + if (!targetSection) { + alert('Fehler: Keine passende Section gefunden'); + return; + } + + // Bestätigung + if (!confirm('Produkt zur Positionsgruppe "' + targetSection.title + '" hinzufügen?')) { + return; + } + + debugLog(' → AJAX Call: add_to_section.php'); + + // AJAX Call zum Backend + $.post('/dolibarr/custom/subtotaltitle/ajax/add_to_section.php', { + line_id: lineId, + section_id: targetSection.id, + document_id: docInfo.id, + document_type: docInfo.type + }, function(response) { + debugLog(' → Response: ' + JSON.stringify(response)); + + if (response.success) { + // Reload Page + window.location.reload(); + } else { + alert('Fehler: ' + (response.error || 'Unbekannter Fehler')); + } + }, 'json').fail(function(xhr, status, error) { + console.error('AJAX Fehler:', status, error); + console.error('Response:', xhr.responseText); + alert('Fehler beim Verknüpfen: ' + xhr.responseText); + }); +}