Kundenauftrag soweit erstmal ohne Fehler Link Button hinzugefügt, Fehler in der Rechnung funktioniert gar nichts Angebot ein paar Kleinigkeiten bis jetzt
This commit is contained in:
parent
954329d701
commit
d8c77df6e4
5 changed files with 570 additions and 32 deletions
140
ajax/add_to_section.php
Normal file
140
ajax/add_to_section.php
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
<?php
|
||||||
|
define('NOTOKENRENEWAL', 1);
|
||||||
|
require '../../../main.inc.php';
|
||||||
|
require_once __DIR__.'/../lib/subtotaltitle.lib.php';
|
||||||
|
require_once __DIR__.'/../class/DocumentTypeHelper.class.php';
|
||||||
|
|
||||||
|
$line_id = GETPOST('line_id', 'int');
|
||||||
|
$section_id = GETPOST('section_id', 'int');
|
||||||
|
$document_id = GETPOST('document_id', 'int');
|
||||||
|
$docType = GETPOST('document_type', 'alpha');
|
||||||
|
|
||||||
|
subtotaltitle_debug_log('🔗 add_to_section: line='.$line_id.', section='.$section_id.', doc='.$document_id.', docType='.$docType);
|
||||||
|
|
||||||
|
if (!$line_id || !$section_id || !$document_id || !$docType) {
|
||||||
|
echo json_encode(['success' => 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]);
|
||||||
182
ajax/fix_section_hierarchy.php
Normal file
182
ajax/fix_section_hierarchy.php
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
<?php
|
||||||
|
define('NOTOKENRENEWAL', 1);
|
||||||
|
require '../../../main.inc.php';
|
||||||
|
require_once __DIR__.'/../lib/subtotaltitle.lib.php';
|
||||||
|
require_once __DIR__.'/../class/DocumentTypeHelper.class.php';
|
||||||
|
|
||||||
|
$doc_id = GETPOST('doc_id', 'int');
|
||||||
|
$docType = GETPOST('document_type', 'alpha');
|
||||||
|
|
||||||
|
if (!$doc_id || !$docType) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Missing parameters']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tables = DocumentTypeHelper::getTableNames($docType);
|
||||||
|
if (!$tables) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid document type']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->begin();
|
||||||
|
|
||||||
|
echo "<h2>Repariere Section-Hierarchie und Sortierung</h2>";
|
||||||
|
|
||||||
|
// 1. Alle Sections haben parent_section=NULL (Sections können nicht in Sections sein!)
|
||||||
|
echo "<h3>1. Korrigiere Section parent_section Werte</h3>";
|
||||||
|
$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)<br>";
|
||||||
|
|
||||||
|
// 2. Baue komplette neue line_order auf
|
||||||
|
echo "<h3>2. Neu-Sortierung aller Zeilen</h3>";
|
||||||
|
|
||||||
|
$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."<br>";
|
||||||
|
$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 "<strong>Section #".$sec_id." → line_order=".$new_order."</strong><br>";
|
||||||
|
$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."<br>";
|
||||||
|
$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."<br>";
|
||||||
|
$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."<br>";
|
||||||
|
$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."<br>";
|
||||||
|
$new_order++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Updates ausführen
|
||||||
|
echo "<h3>3. Schreibe neue line_order Werte</h3>";
|
||||||
|
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<br>";
|
||||||
|
|
||||||
|
// 4. Sync rang in Detail-Tabelle
|
||||||
|
echo "<h3>4. Synchronisiere rang in Detail-Tabelle</h3>";
|
||||||
|
$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."<br>";
|
||||||
|
$rang++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo "✅ ".$rang." Zeilen synchronisiert<br>";
|
||||||
|
|
||||||
|
$db->commit();
|
||||||
|
|
||||||
|
echo "<h3>✅ Reparatur abgeschlossen!</h3>";
|
||||||
|
echo "<p><a href='/dolibarr/commande/card.php?id=".$doc_id."'>Zurück zum Kundenauftrag</a></p>";
|
||||||
|
|
@ -92,41 +92,43 @@ $sections[$current_index] = $sections[$swap_index];
|
||||||
$sections[$swap_index] = $temp;
|
$sections[$swap_index] = $temp;
|
||||||
|
|
||||||
// 3. Baue komplette neue Reihenfolge auf
|
// 3. Baue komplette neue Reihenfolge auf
|
||||||
|
// Strategie: Freie Zeilen behalten ihre Position relativ zu Sections
|
||||||
$new_order = 1;
|
$new_order = 1;
|
||||||
$updates = array();
|
$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 = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id;
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id;
|
||||||
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
$sql .= " AND line_type = 'product'";
|
$sql .= " AND (parent_section IS NULL OR parent_section = 0)";
|
||||||
$sql .= " AND parent_section IS NULL";
|
$sql .= " AND line_type != 'section'";
|
||||||
|
$sql .= " AND line_order < ".(int)$first_section_order;
|
||||||
$sql .= " ORDER BY line_order";
|
$sql .= " ORDER BY line_order";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
$updates[$obj->rowid] = $new_order;
|
$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++;
|
$new_order++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Freie Textzeilen
|
// 2. SECTIONS in neuer Reihenfolge (nach dem Swap)
|
||||||
$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
|
|
||||||
foreach ($sections as $sec_id) {
|
foreach ($sections as $sec_id) {
|
||||||
// Section-Header
|
// Section-Header
|
||||||
$updates[$sec_id] = $new_order;
|
$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++;
|
$new_order++;
|
||||||
|
|
||||||
// Produkte dieser Section
|
// Produkte dieser Section
|
||||||
|
|
@ -140,6 +142,7 @@ foreach ($sections as $sec_id) {
|
||||||
|
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
$updates[$obj->rowid] = $new_order;
|
$updates[$obj->rowid] = $new_order;
|
||||||
|
$processed[$obj->rowid] = true;
|
||||||
$new_order++;
|
$new_order++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,6 +157,7 @@ foreach ($sections as $sec_id) {
|
||||||
|
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
$updates[$obj->rowid] = $new_order;
|
$updates[$obj->rowid] = $new_order;
|
||||||
|
$processed[$obj->rowid] = true;
|
||||||
$new_order++;
|
$new_order++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,19 +171,28 @@ foreach ($sections as $sec_id) {
|
||||||
|
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
$updates[$obj->rowid] = $new_order;
|
$updates[$obj->rowid] = $new_order;
|
||||||
|
$processed[$obj->rowid] = true;
|
||||||
$new_order++;
|
$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 = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id;
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id;
|
||||||
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
$sql .= " AND line_type = 'product'";
|
$sql .= " AND (parent_section IS NULL OR parent_section = 0)";
|
||||||
$sql .= " AND parent_section IS NULL";
|
$sql .= " AND line_type != 'section'";
|
||||||
$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 .= " ORDER BY line_order";
|
$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
|
// 4. Führe alle Updates aus
|
||||||
foreach ($updates as $rowid => $order) {
|
foreach ($updates as $rowid => $order) {
|
||||||
|
|
@ -190,7 +203,9 @@ foreach ($updates as $rowid => $order) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Sync rang in Detail-Tabelle
|
// 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 .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id;
|
||||||
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
$sql .= " AND ".$tables['fk_line']." IS NOT NULL";
|
$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 .= " SET rang = ".$rang;
|
||||||
$sql_upd .= " WHERE rowid = ".(int)$detail_id;
|
$sql_upd .= " WHERE rowid = ".(int)$detail_id;
|
||||||
$db->query($sql_upd);
|
$db->query($sql_upd);
|
||||||
|
dol_syslog('[SubtotalTitle] Sync rang: '.$obj->line_type.' manager#'.$obj->rowid.' detail#'.$detail_id.' → rang='.$rang, LOG_INFO);
|
||||||
$rang++;
|
$rang++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dol_syslog('[SubtotalTitle] move_section: rang-Synchronisation abgeschlossen, '.$rang.' Zeilen synchronisiert', LOG_INFO);
|
||||||
|
|
||||||
$db->commit();
|
$db->commit();
|
||||||
|
|
||||||
echo json_encode(array('success' => true));
|
echo json_encode(array('success' => true));
|
||||||
|
|
|
||||||
|
|
@ -21,16 +21,93 @@ if (!$tables) {
|
||||||
exit;
|
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 = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " SET parent_section = NULL";
|
$sql .= " SET parent_section = NULL";
|
||||||
$sql .= " WHERE ".$tables['fk_line']." = ".(int)$product_id;
|
$sql .= " WHERE ".$tables['fk_line']." = ".(int)$product_id;
|
||||||
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
$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) {
|
// 3. Wenn Product aus einer Section entfernt wurde, Subtotal neu berechnen
|
||||||
subtotaltitle_debug_log('✅ Produkt #' . $product_id . ' aus Section entfernt');
|
if ($old_section_id > 0) {
|
||||||
echo json_encode(['success' => true]);
|
// Prüfe ob Section Subtotal anzeigen soll
|
||||||
} else {
|
$sql = "SELECT show_subtotal, title FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
echo json_encode(['success' => false, 'error' => $db->lasterror()]);
|
$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]);
|
||||||
|
|
|
||||||
|
|
@ -583,8 +583,8 @@ function addUnlinkColumn() {
|
||||||
// Hat Section → Unlink-Button
|
// Hat Section → Unlink-Button
|
||||||
$row.append('<td class="linecolunlink center"><a href="#" onclick="removeFromSection(' + lineId + '); return false;" title="Aus Positionsgruppe entfernen"><span class="fas fa-unlink" style="color:#888;"></span></a></td>');
|
$row.append('<td class="linecolunlink center"><a href="#" onclick="removeFromSection(' + lineId + '); return false;" title="Aus Positionsgruppe entfernen"><span class="fas fa-unlink" style="color:#888;"></span></a></td>');
|
||||||
} else {
|
} else {
|
||||||
// Keine Section → leere Zelle
|
// Keine Section → Link-Button (zu passender Section hinzufügen)
|
||||||
$row.append('<td class="linecolunlink"></td>');
|
$row.append('<td class="linecolunlink center"><a href="#" onclick="linkToNearestSection(' + lineId + '); return false;" title="Zu Positionsgruppe hinzufügen"><span class="fas fa-link" style="color:#888;"></span></a></td>');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1307,3 +1307,124 @@ function hexToRgba(hex, alpha) {
|
||||||
var b = parseInt(hex.slice(5, 7), 16);
|
var b = parseInt(hex.slice(5, 7), 16);
|
||||||
return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')';
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue