Fehlerbehebungen Drag and Drop, Checkboxen, Anzeige Zwischensumme. Fehler in New Order fix_sections
This commit is contained in:
parent
050f2316b2
commit
954329d701
12 changed files with 718 additions and 183 deletions
|
|
@ -46,17 +46,40 @@ $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE ".$tables['fk_line']." = ".(int)$product_id;
|
$sql .= " WHERE ".$tables['fk_line']." = ".(int)$product_id;
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
||||||
|
// Hole die line_order der Section (Produkt soll direkt danach kommen)
|
||||||
|
$sql_section = "SELECT line_order FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE rowid = ".(int)$section_id;
|
||||||
|
$resql_section = $db->query($sql_section);
|
||||||
|
$section_order = 1;
|
||||||
|
if ($obj_section = $db->fetch_object($resql_section)) {
|
||||||
|
$section_order = $obj_section->line_order;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Berechne neue line_order: Höchste line_order der Produkte in dieser Section + 1
|
||||||
|
// Oder Section line_order + 1 wenn keine Produkte vorhanden
|
||||||
|
$sql_max_in_section = "SELECT MAX(line_order) as max_order FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
$sql_max_in_section .= " WHERE parent_section = ".(int)$section_id;
|
||||||
|
$sql_max_in_section .= " AND line_type = 'product'";
|
||||||
|
$resql_max_section = $db->query($sql_max_in_section);
|
||||||
|
$obj_max_section = $db->fetch_object($resql_max_section);
|
||||||
|
|
||||||
|
if ($obj_max_section && $obj_max_section->max_order) {
|
||||||
|
$new_line_order = $obj_max_section->max_order + 1;
|
||||||
|
} else {
|
||||||
|
$new_line_order = $section_order + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
subtotaltitle_debug_log(' → Section line_order='.$section_order.', neue Produkt line_order='.$new_line_order);
|
||||||
|
|
||||||
|
// Verschiebe alle nachfolgenden Zeilen um 1 nach hinten
|
||||||
|
$sql_shift = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
$sql_shift .= " SET line_order = line_order + 1";
|
||||||
|
$sql_shift .= " WHERE ".$tables['fk_parent']." = ".(int)$facture_id;
|
||||||
|
$sql_shift .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
$sql_shift .= " AND line_order >= ".$new_line_order;
|
||||||
|
$db->query($sql_shift);
|
||||||
|
|
||||||
if ($db->num_rows($resql) == 0) {
|
if ($db->num_rows($resql) == 0) {
|
||||||
// Produkt fehlt - hinzufügen
|
// Produkt fehlt - hinzufügen
|
||||||
$next_order = 1;
|
|
||||||
$sql_max = "SELECT MAX(line_order) as max_order FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
|
||||||
$sql_max .= " WHERE ".$tables['fk_parent']." = ".(int)$facture_id;
|
|
||||||
$sql_max .= " AND document_type = '".$db->escape($docType)."'";
|
|
||||||
$resql_max = $db->query($sql_max);
|
|
||||||
if ($obj = $db->fetch_object($resql_max)) {
|
|
||||||
$next_order = ($obj->max_order ? $obj->max_order + 1 : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setze alle FK-Felder explizit (NULL für nicht genutzte)
|
// Setze alle FK-Felder explizit (NULL für nicht genutzte)
|
||||||
$fk_facture = ($docType === 'invoice') ? (int)$facture_id : 'NULL';
|
$fk_facture = ($docType === 'invoice') ? (int)$facture_id : 'NULL';
|
||||||
$fk_propal = ($docType === 'propal') ? (int)$facture_id : 'NULL';
|
$fk_propal = ($docType === 'propal') ? (int)$facture_id : 'NULL';
|
||||||
|
|
@ -64,20 +87,21 @@ if ($db->num_rows($resql) == 0) {
|
||||||
|
|
||||||
$sql_ins = "INSERT INTO ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql_ins = "INSERT INTO ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql_ins .= " (fk_facture, fk_propal, fk_commande, document_type, line_type, ".$tables['fk_line'].", parent_section, line_order, date_creation)";
|
$sql_ins .= " (fk_facture, fk_propal, fk_commande, document_type, line_type, ".$tables['fk_line'].", parent_section, line_order, date_creation)";
|
||||||
$sql_ins .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.", '".$db->escape($docType)."', 'product', ".(int)$product_id.", ".(int)$section_id.", ".$next_order.", NOW())";
|
$sql_ins .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.", '".$db->escape($docType)."', 'product', ".(int)$product_id.", ".(int)$section_id.", ".$new_line_order.", NOW())";
|
||||||
$db->query($sql_ins);
|
$db->query($sql_ins);
|
||||||
|
|
||||||
subtotaltitle_debug_log(' → Produkt zu Manager-Tabelle hinzugefügt (line_order=' . $next_order . ')');
|
subtotaltitle_debug_log(' → Produkt zu Manager-Tabelle hinzugefügt (line_order=' . $new_line_order . ')');
|
||||||
} else {
|
} else {
|
||||||
// Produkt existiert - UPDATE parent_section
|
// Produkt existiert - UPDATE parent_section UND line_order
|
||||||
subtotaltitle_debug_log('🔵🔵🔵 assign_last_product: Produkt #'.$product_id.' → parent_section='.$section_id);
|
subtotaltitle_debug_log('🔵🔵🔵 assign_last_product: Produkt #'.$product_id.' → parent_section='.$section_id.', line_order='.$new_line_order);
|
||||||
|
|
||||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql_upd .= " SET parent_section = ".(int)$section_id;
|
$sql_upd .= " SET parent_section = ".(int)$section_id;
|
||||||
|
$sql_upd .= ", line_order = ".$new_line_order;
|
||||||
$sql_upd .= " WHERE ".$tables['fk_line']." = ".(int)$product_id;
|
$sql_upd .= " WHERE ".$tables['fk_line']." = ".(int)$product_id;
|
||||||
$db->query($sql_upd);
|
$db->query($sql_upd);
|
||||||
|
|
||||||
subtotaltitle_debug_log(' → parent_section updated');
|
subtotaltitle_debug_log(' → parent_section und line_order updated');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neu sortieren
|
// Neu sortieren
|
||||||
|
|
|
||||||
48
ajax/check_subtotal.php
Normal file
48
ajax/check_subtotal.php
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Prüft ob ein Subtotal für eine Section existiert
|
||||||
|
*/
|
||||||
|
define('NOTOKENRENEWAL', 1);
|
||||||
|
require '../../../main.inc.php';
|
||||||
|
require_once __DIR__.'/../class/DocumentTypeHelper.class.php';
|
||||||
|
|
||||||
|
$section_id = GETPOST('section_id', 'int');
|
||||||
|
$docType = GETPOST('document_type', 'alpha');
|
||||||
|
|
||||||
|
if (!$section_id || !$docType) {
|
||||||
|
echo json_encode(['exists' => false, 'error' => 'Missing parameters']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tables = DocumentTypeHelper::getTableNames($docType);
|
||||||
|
if (!$tables) {
|
||||||
|
echo json_encode(['exists' => false, 'error' => 'Invalid document type']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfe ob Subtotal in Manager-Tabelle existiert
|
||||||
|
$sql = "SELECT rowid, ".$tables['fk_line']." as detail_id, in_facturedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
$sql .= " WHERE parent_section = ".(int)$section_id;
|
||||||
|
$sql .= " AND line_type = 'subtotal'";
|
||||||
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
|
||||||
|
$resql = $db->query($sql);
|
||||||
|
$exists = false;
|
||||||
|
$subtotal_id = null;
|
||||||
|
$detail_id = null;
|
||||||
|
$in_facturedet = false;
|
||||||
|
|
||||||
|
if ($resql && $db->num_rows($resql) > 0) {
|
||||||
|
$obj = $db->fetch_object($resql);
|
||||||
|
$exists = true;
|
||||||
|
$subtotal_id = $obj->rowid;
|
||||||
|
$detail_id = $obj->detail_id;
|
||||||
|
$in_facturedet = $obj->in_facturedet ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'exists' => $exists,
|
||||||
|
'subtotal_id' => $subtotal_id,
|
||||||
|
'detail_id' => $detail_id,
|
||||||
|
'in_facturedet' => $in_facturedet
|
||||||
|
]);
|
||||||
144
ajax/cleanup_subtotals.php
Normal file
144
ajax/cleanup_subtotals.php
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Bereinigt verwaiste Subtotals und fehlerhafte Einträge
|
||||||
|
*/
|
||||||
|
define('NOTOKENRENEWAL', 1);
|
||||||
|
require '../../../main.inc.php';
|
||||||
|
require_once __DIR__.'/../lib/subtotaltitle.lib.php';
|
||||||
|
require_once __DIR__.'/../class/DocumentTypeHelper.class.php';
|
||||||
|
|
||||||
|
$facture_id = GETPOST('facture_id', 'int');
|
||||||
|
$docType = GETPOST('document_type', 'alpha');
|
||||||
|
|
||||||
|
if (!$facture_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();
|
||||||
|
$deleted = 0;
|
||||||
|
$fixed = 0;
|
||||||
|
|
||||||
|
// 0. Sections dürfen KEINE parent_section haben - korrigiere das zuerst
|
||||||
|
$sql_fix_sections = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
$sql_fix_sections .= " SET parent_section = NULL";
|
||||||
|
$sql_fix_sections .= " WHERE line_type = 'section'";
|
||||||
|
$sql_fix_sections .= " AND parent_section IS NOT NULL";
|
||||||
|
$sql_fix_sections .= " AND ".$tables['fk_parent']." = ".(int)$facture_id;
|
||||||
|
$sql_fix_sections .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
$db->query($sql_fix_sections);
|
||||||
|
$sections_fixed = $db->affected_rows();
|
||||||
|
if ($sections_fixed > 0) {
|
||||||
|
subtotaltitle_debug_log('🧹 ' . $sections_fixed . ' Sections mit falscher parent_section korrigiert');
|
||||||
|
$fixed += $sections_fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0b. parent_section = 0 sollte NULL sein
|
||||||
|
$sql_fix_zero = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
$sql_fix_zero .= " SET parent_section = NULL";
|
||||||
|
$sql_fix_zero .= " WHERE parent_section = 0";
|
||||||
|
$sql_fix_zero .= " AND ".$tables['fk_parent']." = ".(int)$facture_id;
|
||||||
|
$sql_fix_zero .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
$db->query($sql_fix_zero);
|
||||||
|
$zero_fixed = $db->affected_rows();
|
||||||
|
if ($zero_fixed > 0) {
|
||||||
|
subtotaltitle_debug_log('🧹 ' . $zero_fixed . ' Einträge mit parent_section=0 korrigiert');
|
||||||
|
$fixed += $zero_fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Lösche fehlerhafte "Produkte" in der Detail-Tabelle die eigentlich Zwischensummen sind
|
||||||
|
// (erkennbar an description LIKE 'Zwischensumme%' aber OHNE special_code 102)
|
||||||
|
$sql_bad = "SELECT rowid FROM ".MAIN_DB_PREFIX.$tables['lines_table'];
|
||||||
|
$sql_bad .= " WHERE ".$tables['fk_parent']." = ".(int)$facture_id;
|
||||||
|
$sql_bad .= " AND description LIKE 'Zwischensumme%'";
|
||||||
|
$sql_bad .= " AND (special_code IS NULL OR special_code != 102)";
|
||||||
|
$resql_bad = $db->query($sql_bad);
|
||||||
|
|
||||||
|
while ($obj = $db->fetch_object($resql_bad)) {
|
||||||
|
// Lösche aus Detail-Tabelle
|
||||||
|
$sql_del = "DELETE FROM ".MAIN_DB_PREFIX.$tables['lines_table']." WHERE rowid = ".(int)$obj->rowid;
|
||||||
|
$db->query($sql_del);
|
||||||
|
|
||||||
|
// Lösche auch aus Manager-Tabelle falls vorhanden
|
||||||
|
$sql_del_mgr = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
$sql_del_mgr .= " WHERE ".$tables['fk_line']." = ".(int)$obj->rowid;
|
||||||
|
$db->query($sql_del_mgr);
|
||||||
|
|
||||||
|
subtotaltitle_debug_log('🧹 Fehlerhaftes Zwischensummen-Produkt gelöscht: #' . $obj->rowid);
|
||||||
|
$fixed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Lösche Subtotals deren Section show_subtotal = 0 hat
|
||||||
|
$sql = "SELECT sub.rowid, sub.".$tables['fk_line']." as detail_id";
|
||||||
|
$sql .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager sub";
|
||||||
|
$sql .= " INNER JOIN ".MAIN_DB_PREFIX."facture_lines_manager sec ON sec.rowid = sub.parent_section";
|
||||||
|
$sql .= " WHERE sub.line_type = 'subtotal'";
|
||||||
|
$sql .= " AND sub.".$tables['fk_parent']." = ".(int)$facture_id;
|
||||||
|
$sql .= " AND sub.document_type = '".$db->escape($docType)."'";
|
||||||
|
$sql .= " AND (sec.show_subtotal = 0 OR sec.show_subtotal IS NULL)";
|
||||||
|
$resql = $db->query($sql);
|
||||||
|
|
||||||
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
|
// Auch aus Detail-Tabelle löschen falls vorhanden
|
||||||
|
if ($obj->detail_id) {
|
||||||
|
$sql_del_det = "DELETE FROM ".MAIN_DB_PREFIX.$tables['lines_table']." WHERE rowid = ".(int)$obj->detail_id;
|
||||||
|
$db->query($sql_del_det);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aus Manager löschen
|
||||||
|
$sql_del = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE rowid = ".(int)$obj->rowid;
|
||||||
|
$db->query($sql_del);
|
||||||
|
$deleted++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($deleted > 0 || $fixed > 0) {
|
||||||
|
subtotaltitle_debug_log('🧹 Cleanup: ' . $deleted . ' verwaiste Subtotals, ' . $fixed . ' fehlerhafte Produkte gelöscht');
|
||||||
|
|
||||||
|
// line_order neu durchnummerieren
|
||||||
|
$sql_reorder = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
$sql_reorder .= " WHERE ".$tables['fk_parent']." = ".(int)$facture_id;
|
||||||
|
$sql_reorder .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
$sql_reorder .= " ORDER BY line_order";
|
||||||
|
$resql = $db->query($sql_reorder);
|
||||||
|
|
||||||
|
$new_order = 1;
|
||||||
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
|
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
$sql_upd .= " SET line_order = ".$new_order;
|
||||||
|
$sql_upd .= " WHERE rowid = ".(int)$obj->rowid;
|
||||||
|
$db->query($sql_upd);
|
||||||
|
$new_order++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auch rang in Detail-Tabelle neu durchnummerieren
|
||||||
|
$sql_sync = "SELECT ".$tables['fk_line']." FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
$sql_sync .= " WHERE ".$tables['fk_parent']." = ".(int)$facture_id;
|
||||||
|
$sql_sync .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
$sql_sync .= " AND ".$tables['fk_line']." IS NOT NULL";
|
||||||
|
$sql_sync .= " ORDER BY line_order";
|
||||||
|
$resql_sync = $db->query($sql_sync);
|
||||||
|
|
||||||
|
$rang = 1;
|
||||||
|
while ($obj = $db->fetch_object($resql_sync)) {
|
||||||
|
$fk_line_value = $obj->{$tables['fk_line']};
|
||||||
|
$sql_upd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table'];
|
||||||
|
$sql_upd .= " SET rang = ".$rang;
|
||||||
|
$sql_upd .= " WHERE rowid = ".(int)$fk_line_value;
|
||||||
|
$db->query($sql_upd);
|
||||||
|
$rang++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->commit();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'deleted' => $deleted,
|
||||||
|
'fixed' => $fixed
|
||||||
|
]);
|
||||||
|
|
@ -40,12 +40,8 @@ $sql .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.", '".$db->esc
|
||||||
if ($db->query($sql)) {
|
if ($db->query($sql)) {
|
||||||
$section_id = $db->last_insert_id(MAIN_DB_PREFIX."facture_lines_manager");
|
$section_id = $db->last_insert_id(MAIN_DB_PREFIX."facture_lines_manager");
|
||||||
|
|
||||||
// Erstelle automatisch auch eine Zwischensumme für diese Section
|
// KEIN automatisches Subtotal mehr - wird nur erstellt wenn Checkbox aktiviert wird
|
||||||
$subtotal_order = $next_order + 1000; // Hohe Nummer, wird später normalisiert
|
// Das Subtotal wird über toggle_subtotal.php erstellt/gelöscht
|
||||||
$sql_subtotal = "INSERT INTO ".MAIN_DB_PREFIX."facture_lines_manager";
|
|
||||||
$sql_subtotal .= " (fk_facture, fk_propal, fk_commande, document_type, line_type, title, parent_section, line_order, date_creation)";
|
|
||||||
$sql_subtotal .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.", '".$db->escape($docType)."', 'subtotal', 'Zwischensumme', ".(int)$section_id.", ".$subtotal_order.", NOW())";
|
|
||||||
$db->query($sql_subtotal);
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'section_id' => $section_id]);
|
echo json_encode(['success' => true, 'section_id' => $section_id]);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
58
ajax/fix_sections.php
Normal file
58
ajax/fix_sections.php
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Repariert fehlerhafte parent_section Werte
|
||||||
|
* - Sections sollten KEINE parent_section haben
|
||||||
|
* - parent_section = 0 sollte NULL sein
|
||||||
|
*/
|
||||||
|
define('NOTOKENRENEWAL', 1);
|
||||||
|
require '../../../main.inc.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->begin();
|
||||||
|
$fixed = 0;
|
||||||
|
|
||||||
|
// 1. Sections dürfen KEINE parent_section haben
|
||||||
|
$sql = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
$sql .= " SET parent_section = NULL";
|
||||||
|
$sql .= " WHERE line_type = 'section'";
|
||||||
|
$sql .= " AND parent_section IS NOT NULL";
|
||||||
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
if ($docType == 'invoice') {
|
||||||
|
$sql .= " AND fk_facture = ".(int)$doc_id;
|
||||||
|
} elseif ($docType == 'propal') {
|
||||||
|
$sql .= " AND fk_propal = ".(int)$doc_id;
|
||||||
|
} elseif ($docType == 'order') {
|
||||||
|
$sql .= " AND fk_commande = ".(int)$doc_id;
|
||||||
|
}
|
||||||
|
$db->query($sql);
|
||||||
|
$fixed += $db->affected_rows();
|
||||||
|
|
||||||
|
// 2. parent_section = 0 sollte NULL sein
|
||||||
|
$sql = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
$sql .= " SET parent_section = NULL";
|
||||||
|
$sql .= " WHERE parent_section = 0";
|
||||||
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
if ($docType == 'invoice') {
|
||||||
|
$sql .= " AND fk_facture = ".(int)$doc_id;
|
||||||
|
} elseif ($docType == 'propal') {
|
||||||
|
$sql .= " AND fk_propal = ".(int)$doc_id;
|
||||||
|
} elseif ($docType == 'order') {
|
||||||
|
$sql .= " AND fk_commande = ".(int)$doc_id;
|
||||||
|
}
|
||||||
|
$db->query($sql);
|
||||||
|
$fixed += $db->affected_rows();
|
||||||
|
|
||||||
|
$db->commit();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'fixed' => $fixed,
|
||||||
|
'message' => $fixed . ' Einträge korrigiert'
|
||||||
|
]);
|
||||||
|
|
@ -22,7 +22,7 @@ if (!$tables) {
|
||||||
|
|
||||||
// Hole ALLE Sections für diesen Dokumenttyp
|
// Hole ALLE Sections für diesen Dokumenttyp
|
||||||
$sql = "SELECT s.rowid, s.title, s.line_order, ";
|
$sql = "SELECT s.rowid, s.title, s.line_order, ";
|
||||||
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."facture_lines_manager p WHERE p.parent_section = s.rowid) as product_count";
|
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."facture_lines_manager p WHERE p.parent_section = s.rowid AND p.line_type = 'product' AND p.document_type = '".$db->escape($docType)."') as product_count";
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager s";
|
$sql .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager s";
|
||||||
$sql .= " WHERE s.".$tables['fk_parent']." = ".(int)$facture_id;
|
$sql .= " WHERE s.".$tables['fk_parent']." = ".(int)$facture_id;
|
||||||
$sql .= " AND s.document_type = '".$db->escape($docType)."'";
|
$sql .= " AND s.document_type = '".$db->escape($docType)."'";
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,41 @@
|
||||||
<?php
|
<?php
|
||||||
define('NOTOKENRENEWAL', 1);
|
define('NOTOKENRENEWAL', 1);
|
||||||
require '../../../main.inc.php';
|
require '../../../main.inc.php';
|
||||||
|
require_once __DIR__.'/../class/DocumentTypeHelper.class.php';
|
||||||
|
|
||||||
$section_id = GETPOST('section_id', 'int');
|
$section_id = GETPOST('section_id', 'int');
|
||||||
$direction = GETPOST('direction', 'alpha');
|
$direction = GETPOST('direction', 'alpha');
|
||||||
|
$docType = GETPOST('document_type', 'alpha');
|
||||||
|
|
||||||
if (!$section_id || !$direction) {
|
if (!$section_id || !$direction) {
|
||||||
echo json_encode(array('success' => false, 'error' => 'Missing parameters'));
|
echo json_encode(array('success' => false, 'error' => 'Missing parameters'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wenn kein docType übergeben, versuche ihn aus der Section zu ermitteln
|
||||||
|
if (!$docType) {
|
||||||
|
$sql = "SELECT document_type FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE rowid = ".(int)$section_id;
|
||||||
|
$res = $db->query($sql);
|
||||||
|
if ($res && $obj = $db->fetch_object($res)) {
|
||||||
|
$docType = $obj->document_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$docType) {
|
||||||
|
$docType = 'invoice'; // Fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
$tables = DocumentTypeHelper::getTableNames($docType);
|
||||||
|
if (!$tables) {
|
||||||
|
echo json_encode(array('success' => false, 'error' => 'Invalid document type'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
// Hole Section-Info
|
// Hole Section-Info
|
||||||
$sql = "SELECT fk_facture FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql = "SELECT ".$tables['fk_parent']." as doc_id FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE rowid = ".(int)$section_id;
|
$sql .= " WHERE rowid = ".(int)$section_id;
|
||||||
$sql .= " AND line_type = 'section'";
|
$sql .= " AND line_type = 'section'";
|
||||||
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
||||||
if (!$resql || $db->num_rows($resql) == 0) {
|
if (!$resql || $db->num_rows($resql) == 0) {
|
||||||
|
|
@ -22,13 +44,14 @@ if (!$resql || $db->num_rows($resql) == 0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$section = $db->fetch_object($resql);
|
$section = $db->fetch_object($resql);
|
||||||
$facture_id = $section->fk_facture;
|
$doc_id = $section->doc_id;
|
||||||
|
|
||||||
$db->begin();
|
$db->begin();
|
||||||
|
|
||||||
// 1. Hole alle Sections (sortiert)
|
// 1. Hole alle Sections (sortiert)
|
||||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE fk_facture = ".(int)$facture_id;
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id;
|
||||||
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
$sql .= " AND line_type = 'section'";
|
$sql .= " AND line_type = 'section'";
|
||||||
$sql .= " ORDER BY line_order";
|
$sql .= " ORDER BY line_order";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
@ -42,18 +65,21 @@ while ($obj = $db->fetch_object($resql)) {
|
||||||
$current_index = array_search($section_id, $sections);
|
$current_index = array_search($section_id, $sections);
|
||||||
|
|
||||||
if ($current_index === false) {
|
if ($current_index === false) {
|
||||||
|
$db->rollback();
|
||||||
echo json_encode(array('success' => false, 'error' => 'Section not in list'));
|
echo json_encode(array('success' => false, 'error' => 'Section not in list'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($direction == 'up') {
|
if ($direction == 'up') {
|
||||||
if ($current_index == 0) {
|
if ($current_index == 0) {
|
||||||
|
$db->rollback();
|
||||||
echo json_encode(array('success' => false, 'error' => 'Already at top'));
|
echo json_encode(array('success' => false, 'error' => 'Already at top'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
$swap_index = $current_index - 1;
|
$swap_index = $current_index - 1;
|
||||||
} else {
|
} else {
|
||||||
if ($current_index == count($sections) - 1) {
|
if ($current_index == count($sections) - 1) {
|
||||||
|
$db->rollback();
|
||||||
echo json_encode(array('success' => false, 'error' => 'Already at bottom'));
|
echo json_encode(array('success' => false, 'error' => 'Already at bottom'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +97,8 @@ $updates = array();
|
||||||
|
|
||||||
// Freie Produkte zuerst
|
// Freie Produkte zuerst
|
||||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE fk_facture = ".(int)$facture_id;
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id;
|
||||||
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
$sql .= " AND line_type = 'product'";
|
$sql .= " AND line_type = 'product'";
|
||||||
$sql .= " AND parent_section IS NULL";
|
$sql .= " AND parent_section IS NULL";
|
||||||
$sql .= " ORDER BY line_order";
|
$sql .= " ORDER BY line_order";
|
||||||
|
|
@ -84,7 +111,8 @@ while ($obj = $db->fetch_object($resql)) {
|
||||||
|
|
||||||
// Freie Textzeilen
|
// Freie Textzeilen
|
||||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE fk_facture = ".(int)$facture_id;
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id;
|
||||||
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
$sql .= " AND line_type = 'text'";
|
$sql .= " AND line_type = 'text'";
|
||||||
$sql .= " AND parent_section IS NULL";
|
$sql .= " AND parent_section IS NULL";
|
||||||
$sql .= " ORDER BY line_order";
|
$sql .= " ORDER BY line_order";
|
||||||
|
|
@ -103,7 +131,8 @@ foreach ($sections as $sec_id) {
|
||||||
|
|
||||||
// Produkte dieser Section
|
// Produkte dieser Section
|
||||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE fk_facture = ".(int)$facture_id;
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id;
|
||||||
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
$sql .= " AND line_type = 'product'";
|
$sql .= " AND line_type = 'product'";
|
||||||
$sql .= " AND parent_section = ".(int)$sec_id;
|
$sql .= " AND parent_section = ".(int)$sec_id;
|
||||||
$sql .= " ORDER BY line_order";
|
$sql .= " ORDER BY line_order";
|
||||||
|
|
@ -116,7 +145,8 @@ foreach ($sections as $sec_id) {
|
||||||
|
|
||||||
// Textzeilen dieser Section
|
// Textzeilen dieser Section
|
||||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE fk_facture = ".(int)$facture_id;
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id;
|
||||||
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
$sql .= " AND line_type = 'text'";
|
$sql .= " AND line_type = 'text'";
|
||||||
$sql .= " AND parent_section = ".(int)$sec_id;
|
$sql .= " AND parent_section = ".(int)$sec_id;
|
||||||
$sql .= " ORDER BY line_order";
|
$sql .= " ORDER BY line_order";
|
||||||
|
|
@ -127,9 +157,10 @@ foreach ($sections as $sec_id) {
|
||||||
$new_order++;
|
$new_order++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== SUBTOTAL DIESER SECTION ==========
|
// Subtotal dieser Section
|
||||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE fk_facture = ".(int)$facture_id;
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id;
|
||||||
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
$sql .= " AND line_type = 'subtotal'";
|
$sql .= " AND line_type = 'subtotal'";
|
||||||
$sql .= " AND parent_section = ".(int)$sec_id;
|
$sql .= " AND parent_section = ".(int)$sec_id;
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
@ -140,6 +171,16 @@ foreach ($sections as $sec_id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Freie Produkte am Ende (nach allen 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";
|
||||||
|
$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";
|
||||||
|
// Diese Abfrage ist zu komplex - die freien Produkte wurden bereits oben behandelt
|
||||||
|
|
||||||
// 4. Führe alle Updates aus
|
// 4. Führe alle Updates aus
|
||||||
foreach ($updates as $rowid => $order) {
|
foreach ($updates as $rowid => $order) {
|
||||||
$sql = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
|
@ -148,20 +189,24 @@ foreach ($updates as $rowid => $order) {
|
||||||
$db->query($sql);
|
$db->query($sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Sync rang
|
// 5. Sync rang in Detail-Tabelle
|
||||||
$sql = "SELECT fk_facturedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql = "SELECT ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE fk_facture = ".(int)$facture_id;
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$doc_id;
|
||||||
$sql .= " AND line_type = 'product'";
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
$sql .= " AND ".$tables['fk_line']." IS NOT NULL";
|
||||||
$sql .= " ORDER BY line_order";
|
$sql .= " ORDER BY line_order";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
||||||
$rang = 1;
|
$rang = 1;
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facturedet";
|
$detail_id = $obj->detail_id;
|
||||||
$sql_upd .= " SET rang = ".$rang;
|
if ($detail_id) {
|
||||||
$sql_upd .= " WHERE rowid = ".(int)$obj->fk_facturedet;
|
$sql_upd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table'];
|
||||||
$db->query($sql_upd);
|
$sql_upd .= " SET rang = ".$rang;
|
||||||
$rang++;
|
$sql_upd .= " WHERE rowid = ".(int)$detail_id;
|
||||||
|
$db->query($sql_upd);
|
||||||
|
$rang++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$db->commit();
|
$db->commit();
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,34 @@
|
||||||
define('NOTOKENRENEWAL', 1);
|
define('NOTOKENRENEWAL', 1);
|
||||||
require '../../../main.inc.php';
|
require '../../../main.inc.php';
|
||||||
require_once __DIR__.'/../lib/subtotaltitle.lib.php';
|
require_once __DIR__.'/../lib/subtotaltitle.lib.php';
|
||||||
|
require_once __DIR__.'/../class/DocumentTypeHelper.class.php';
|
||||||
|
|
||||||
$product_id = GETPOST('product_id', 'int');
|
$product_id = GETPOST('product_id', 'int');
|
||||||
|
$docType = GETPOST('document_type', 'alpha');
|
||||||
|
|
||||||
subtotaltitle_debug_log('🔓 remove_from_section: product=' . $product_id);
|
subtotaltitle_debug_log('🔓 remove_from_section: product=' . $product_id . ', docType=' . $docType);
|
||||||
|
|
||||||
if (!$product_id) {
|
if (!$product_id || !$docType) {
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing product_id']);
|
echo json_encode(['success' => false, 'error' => 'Missing parameters']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hole die richtigen Tabellennamen für diesen Dokumenttyp
|
||||||
|
$tables = DocumentTypeHelper::getTableNames($docType);
|
||||||
|
if (!$tables) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid document type']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$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 fk_facturedet = ".(int)$product_id;
|
$sql .= " WHERE ".$tables['fk_line']." = ".(int)$product_id;
|
||||||
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
|
||||||
$result = $db->query($sql);
|
$result = $db->query($sql);
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
|
subtotaltitle_debug_log('✅ Produkt #' . $product_id . ' aus Section entfernt');
|
||||||
echo json_encode(['success' => true]);
|
echo json_encode(['success' => true]);
|
||||||
} else {
|
} else {
|
||||||
echo json_encode(['success' => false, 'error' => $db->lasterror()]);
|
echo json_encode(['success' => false, 'error' => $db->lasterror()]);
|
||||||
|
|
|
||||||
|
|
@ -62,30 +62,39 @@ foreach ($new_order as $item) {
|
||||||
// ========== SUBTOTALS NEU POSITIONIEREN ==========
|
// ========== SUBTOTALS NEU POSITIONIEREN ==========
|
||||||
subtotaltitle_debug_log('🔢 Repositioniere Subtotals...');
|
subtotaltitle_debug_log('🔢 Repositioniere Subtotals...');
|
||||||
|
|
||||||
|
// Hole alle Subtotals für dieses Dokument
|
||||||
$sql = "SELECT rowid, parent_section FROM ".MAIN_DB_PREFIX."facture_lines_manager
|
$sql = "SELECT rowid, parent_section FROM ".MAIN_DB_PREFIX."facture_lines_manager
|
||||||
WHERE ".$tables['fk_parent']." = ".(int)$facture_id."
|
WHERE ".$tables['fk_parent']." = ".(int)$facture_id."
|
||||||
AND document_type = '".$db->escape($docType)."'
|
AND document_type = '".$db->escape($docType)."'
|
||||||
AND line_type = 'subtotal'";
|
AND line_type = 'subtotal'";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
||||||
|
$subtotals_to_update = array();
|
||||||
while ($subtotal = $db->fetch_object($resql)) {
|
while ($subtotal = $db->fetch_object($resql)) {
|
||||||
|
$subtotals_to_update[] = $subtotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($subtotals_to_update as $subtotal) {
|
||||||
// Finde höchste line_order der Produkte dieser Section
|
// Finde höchste line_order der Produkte dieser Section
|
||||||
$sql_max = "SELECT MAX(line_order) as max_order
|
$sql_max = "SELECT MAX(line_order) as max_order
|
||||||
FROM ".MAIN_DB_PREFIX."facture_lines_manager
|
FROM ".MAIN_DB_PREFIX."facture_lines_manager
|
||||||
WHERE parent_section = ".(int)$subtotal->parent_section."
|
WHERE parent_section = ".(int)$subtotal->parent_section."
|
||||||
AND line_type = 'product'";
|
AND line_type = 'product'
|
||||||
|
AND document_type = '".$db->escape($docType)."'";
|
||||||
$res_max = $db->query($sql_max);
|
$res_max = $db->query($sql_max);
|
||||||
$obj_max = $db->fetch_object($res_max);
|
$obj_max = $db->fetch_object($res_max);
|
||||||
|
|
||||||
if ($obj_max && $obj_max->max_order) {
|
if ($obj_max && $obj_max->max_order) {
|
||||||
// Subtotal bekommt hohe Nummer (wird gleich normalisiert)
|
// Subtotal kommt direkt nach dem letzten Produkt: max_order + 0.5
|
||||||
$temp_order = (int)$obj_max->max_order * 100 + 50;
|
$temp_order = (float)$obj_max->max_order + 0.5;
|
||||||
|
|
||||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager
|
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager
|
||||||
SET line_order = ".$temp_order."
|
SET line_order = ".$temp_order."
|
||||||
WHERE rowid = ".(int)$subtotal->rowid;
|
WHERE rowid = ".(int)$subtotal->rowid;
|
||||||
$db->query($sql_upd);
|
$db->query($sql_upd);
|
||||||
subtotaltitle_debug_log(' Subtotal #'.$subtotal->rowid.' → temp_order='.$temp_order.' (nach Section '.$subtotal->parent_section.')');
|
subtotaltitle_debug_log(' Subtotal #'.$subtotal->rowid.' (Section '.$subtotal->parent_section.') → temp_order='.$temp_order);
|
||||||
|
} else {
|
||||||
|
subtotaltitle_debug_log(' ⚠️ Subtotal #'.$subtotal->rowid.' hat keine Produkte in Section '.$subtotal->parent_section);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -518,10 +518,8 @@ class ActionsSubtotalTitle extends CommonHookActions
|
||||||
// Prüfe ob diese Zeile eine unserer speziellen Zeilen ist (Section, Text, Subtotal)
|
// Prüfe ob diese Zeile eine unserer speziellen Zeilen ist (Section, Text, Subtotal)
|
||||||
// special_code: 100=Section, 101=Text, 102=Subtotal
|
// special_code: 100=Section, 101=Text, 102=Subtotal
|
||||||
if (isset($line->special_code) && in_array($line->special_code, array(100, 101, 102))) {
|
if (isset($line->special_code) && in_array($line->special_code, array(100, 101, 102))) {
|
||||||
// Diese Zeile ist eine unserer speziellen Zeilen - per JS ausblenden
|
// Diese Zeile wird von uns selbst gerendert - Original sofort per CSS verstecken
|
||||||
echo '<script>$(document).ready(function() { ';
|
echo '<style>#row-'.$line->id.', tr[data-line-id="'.$line->id.'"] { display: none !important; }</style>';
|
||||||
echo ' $("tr[id*=\''.$line->id.'\']").hide();';
|
|
||||||
echo '});</script>';
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -598,23 +596,31 @@ class ActionsSubtotalTitle extends CommonHookActions
|
||||||
$current_parent_section = $obj_current->parent_section;
|
$current_parent_section = $obj_current->parent_section;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subtotal der VORHERIGEN Section rendern (wenn Section-Wechsel)
|
// Subtotal der VORHERIGEN Section rendern (wenn Section-Wechsel UND show_subtotal aktiviert)
|
||||||
if ($last_parent_section[$doc_key] && $last_parent_section[$doc_key] != $current_parent_section) {
|
if ($last_parent_section[$doc_key] && $last_parent_section[$doc_key] != $current_parent_section) {
|
||||||
$sql_subtotal = "SELECT rowid, title, parent_section, line_order FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
// Prüfe erst ob die Section show_subtotal aktiviert hat
|
||||||
$sql_subtotal .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
$sql_check_show = "SELECT show_subtotal FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql_subtotal .= " AND document_type = '".$db->escape($docType)."'";
|
$sql_check_show .= " WHERE rowid = ".(int)$last_parent_section[$doc_key];
|
||||||
$sql_subtotal .= " AND parent_section = ".(int)$last_parent_section[$doc_key];
|
$resql_check = $db->query($sql_check_show);
|
||||||
$sql_subtotal .= " AND line_type = 'subtotal'";
|
$section_obj = $db->fetch_object($resql_check);
|
||||||
$resql_subtotal = $db->query($sql_subtotal);
|
|
||||||
|
|
||||||
if ($obj_sub = $db->fetch_object($resql_subtotal)) {
|
if ($section_obj && $section_obj->show_subtotal) {
|
||||||
$subtotal_key = 'subtotal_'.$obj_sub->rowid;
|
$sql_subtotal = "SELECT rowid, title, parent_section, line_order FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
if (!in_array($subtotal_key, self::$rendered_sections[$doc_key])) {
|
$sql_subtotal .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||||
echo $this->renderSubtotalLine($obj_sub);
|
$sql_subtotal .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
self::$rendered_sections[$doc_key][] = $subtotal_key;
|
$sql_subtotal .= " AND parent_section = ".(int)$last_parent_section[$doc_key];
|
||||||
|
$sql_subtotal .= " AND line_type = 'subtotal'";
|
||||||
|
$resql_subtotal = $db->query($sql_subtotal);
|
||||||
|
|
||||||
if ($this->debug) {
|
if ($obj_sub = $db->fetch_object($resql_subtotal)) {
|
||||||
error_log('[SubtotalTitle] ✅ Subtotal "'.$obj_sub->title.'" gerendert (Section-Wechsel)');
|
$subtotal_key = 'subtotal_'.$obj_sub->rowid;
|
||||||
|
if (!in_array($subtotal_key, self::$rendered_sections[$doc_key])) {
|
||||||
|
echo $this->renderSubtotalLine($obj_sub);
|
||||||
|
self::$rendered_sections[$doc_key][] = $subtotal_key;
|
||||||
|
|
||||||
|
if ($this->debug) {
|
||||||
|
error_log('[SubtotalTitle] ✅ Subtotal "'.$obj_sub->title.'" gerendert (Section-Wechsel)');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -682,6 +688,55 @@ class ActionsSubtotalTitle extends CommonHookActions
|
||||||
// Merke für nächsten Durchlauf
|
// Merke für nächsten Durchlauf
|
||||||
$last_rang[$doc_key] = $current_rang;
|
$last_rang[$doc_key] = $current_rang;
|
||||||
$last_parent_section[$doc_key] = $current_parent_section;
|
$last_parent_section[$doc_key] = $current_parent_section;
|
||||||
|
|
||||||
|
// Prüfe ob dies die LETZTE Produktzeile ist - dann Subtotal per JavaScript NACH dieser Zeile einfügen
|
||||||
|
if ($current_parent_section) {
|
||||||
|
// Hole max rang für dieses Dokument (nur echte Produkte, keine special_code 100-102)
|
||||||
|
$sql_max = "SELECT MAX(rang) as max_rang FROM ".MAIN_DB_PREFIX.$tables['lines_table'];
|
||||||
|
$sql_max .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||||
|
$sql_max .= " AND (special_code IS NULL OR special_code = 0 OR special_code < 100 OR special_code > 102)";
|
||||||
|
$res_max = $db->query($sql_max);
|
||||||
|
$obj_max = $db->fetch_object($res_max);
|
||||||
|
|
||||||
|
if ($obj_max && $current_rang >= $obj_max->max_rang) {
|
||||||
|
// Dies ist die letzte Produktzeile - Subtotal per JS NACH dieser Zeile einfügen
|
||||||
|
$sql_check_show = "SELECT show_subtotal FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
$sql_check_show .= " WHERE rowid = ".(int)$current_parent_section;
|
||||||
|
$resql_check = $db->query($sql_check_show);
|
||||||
|
$section_obj = $db->fetch_object($resql_check);
|
||||||
|
|
||||||
|
if ($section_obj && $section_obj->show_subtotal) {
|
||||||
|
$sql_subtotal = "SELECT rowid, title, parent_section, line_order FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
$sql_subtotal .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||||
|
$sql_subtotal .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
$sql_subtotal .= " AND parent_section = ".(int)$current_parent_section;
|
||||||
|
$sql_subtotal .= " AND line_type = 'subtotal'";
|
||||||
|
$resql_subtotal = $db->query($sql_subtotal);
|
||||||
|
|
||||||
|
if ($obj_sub = $db->fetch_object($resql_subtotal)) {
|
||||||
|
$subtotal_key = 'subtotal_'.$obj_sub->rowid;
|
||||||
|
if (!in_array($subtotal_key, self::$rendered_sections[$doc_key])) {
|
||||||
|
// Subtotal-HTML generieren
|
||||||
|
$subtotalHtml = $this->renderSubtotalLine($obj_sub);
|
||||||
|
// Per JavaScript NACH der aktuellen Zeile einfügen
|
||||||
|
$escapedHtml = addslashes(str_replace(array("\r", "\n"), '', $subtotalHtml));
|
||||||
|
echo '<script>$(document).ready(function() { ';
|
||||||
|
echo ' var $lastRow = $("tr[data-line-id=\''.$line->id.'\']");';
|
||||||
|
echo ' if ($lastRow.length === 0) $lastRow = $("#row-'.$line->id.'");';
|
||||||
|
echo ' if ($lastRow.length > 0 && $lastRow.next(".subtotal-row").length === 0) {';
|
||||||
|
echo ' $lastRow.after("'.$escapedHtml.'");';
|
||||||
|
echo ' }';
|
||||||
|
echo '});</script>';
|
||||||
|
self::$rendered_sections[$doc_key][] = $subtotal_key;
|
||||||
|
|
||||||
|
if ($this->debug) {
|
||||||
|
error_log('[SubtotalTitle] ✅ Subtotal für letzte Section per JS eingefügt');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -839,29 +894,40 @@ class ActionsSubtotalTitle extends CommonHookActions
|
||||||
$sql_cleanup .= " AND d.rowid IS NULL";
|
$sql_cleanup .= " AND d.rowid IS NULL";
|
||||||
$result = $db->query($sql_cleanup);
|
$result = $db->query($sql_cleanup);
|
||||||
|
|
||||||
// 2. Hole alle Produktzeilen des Dokuments
|
// 2. Hole alle Produktzeilen des Dokuments mit rang
|
||||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$tables['lines_table'];
|
$sql = "SELECT rowid, rang FROM ".MAIN_DB_PREFIX.$tables['lines_table'];
|
||||||
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||||
$sql .= " ORDER BY rang";
|
$sql .= " ORDER BY rang";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
||||||
|
$new_products = array();
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
$sql_check = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql_check = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql_check .= " WHERE ".$tables['fk_line']." = ".(int)$obj->rowid;
|
$sql_check .= " WHERE ".$tables['fk_line']." = ".(int)$obj->rowid;
|
||||||
$resql_check = $db->query($sql_check);
|
$resql_check = $db->query($sql_check);
|
||||||
|
|
||||||
if ($db->num_rows($resql_check) == 0) {
|
if ($db->num_rows($resql_check) == 0) {
|
||||||
$next_order = $this->getNextLineOrder($document_id, $docType);
|
// Neues Produkt gefunden - merken mit rang
|
||||||
|
$new_products[] = array('rowid' => $obj->rowid, 'rang' => $obj->rang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Setze alle FK-Felder explizit (NULL für nicht genutzte)
|
// 3. Füge neue Produkte ein - am Ende der Liste
|
||||||
$fk_facture = ($docType === 'invoice') ? (int)$document_id : 'NULL';
|
if (count($new_products) > 0) {
|
||||||
$fk_propal = ($docType === 'propal') ? (int)$document_id : 'NULL';
|
// Hole einmal die höchste line_order
|
||||||
$fk_commande = ($docType === 'order') ? (int)$document_id : 'NULL';
|
$next_order = $this->getNextLineOrder($document_id, $docType);
|
||||||
|
|
||||||
|
// Setze alle FK-Felder explizit (NULL für nicht genutzte)
|
||||||
|
$fk_facture = ($docType === 'invoice') ? (int)$document_id : 'NULL';
|
||||||
|
$fk_propal = ($docType === 'propal') ? (int)$document_id : 'NULL';
|
||||||
|
$fk_commande = ($docType === 'order') ? (int)$document_id : 'NULL';
|
||||||
|
|
||||||
|
foreach ($new_products as $product) {
|
||||||
$sql_ins = "INSERT INTO ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql_ins = "INSERT INTO ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql_ins .= " (fk_facture, fk_propal, fk_commande, document_type, line_type, ".$tables['fk_line'].", line_order, date_creation)";
|
$sql_ins .= " (fk_facture, fk_propal, fk_commande, document_type, line_type, ".$tables['fk_line'].", line_order, date_creation)";
|
||||||
$sql_ins .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.", '".$db->escape($docType)."', 'product', ".(int)$obj->rowid.", ".$next_order.", NOW())";
|
$sql_ins .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.", '".$db->escape($docType)."', 'product', ".(int)$product['rowid'].", ".$next_order.", NOW())";
|
||||||
$db->query($sql_ins);
|
$db->query($sql_ins);
|
||||||
|
$next_order++; // Für jedes weitere Produkt erhöhen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1202,9 +1268,12 @@ class ActionsSubtotalTitle extends CommonHookActions
|
||||||
global $db;
|
global $db;
|
||||||
|
|
||||||
$tables = DocumentTypeHelper::getTableNames($docType);
|
$tables = DocumentTypeHelper::getTableNames($docType);
|
||||||
|
if (!$tables) return 1;
|
||||||
|
|
||||||
$sql = "SELECT MAX(line_order) as max_order";
|
$sql = "SELECT MAX(line_order) as max_order";
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= $this->getDocumentWhere($document_id, $docType);
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||||
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
if (!$resql || !($obj = $db->fetch_object($resql))) {
|
if (!$resql || !($obj = $db->fetch_object($resql))) {
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// DEBUG FLAG - true für Debug-Ausgaben, false für Produktiv
|
// DEBUG FLAG - true für Debug-Ausgaben, false für Produktiv
|
||||||
var SUBTOTAL_DEBUG = true;
|
var SUBTOTAL_DEBUG = false;
|
||||||
|
|
||||||
function debugLog(message) {
|
function debugLog(message) {
|
||||||
if (SUBTOTAL_DEBUG) {
|
if (SUBTOTAL_DEBUG) {
|
||||||
|
|
@ -7,6 +7,36 @@ function debugLog(message) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seite neu laden ohne POST-Warnung (GET-Request)
|
||||||
|
*/
|
||||||
|
function safeReload() {
|
||||||
|
var baseUrl = window.location.href.split('?')[0];
|
||||||
|
var id = getFactureId();
|
||||||
|
window.location.href = baseUrl + '?id=' + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bereinigt verwaiste Subtotals und fehlerhafte Einträge (NUR aktuelles Dokument!)
|
||||||
|
*/
|
||||||
|
function cleanupOrphanedSubtotals() {
|
||||||
|
var docInfo = getDocumentInfo();
|
||||||
|
if (!docInfo.id) return;
|
||||||
|
|
||||||
|
$.post('/dolibarr/custom/subtotaltitle/ajax/cleanup_subtotals.php', {
|
||||||
|
facture_id: docInfo.id,
|
||||||
|
document_type: docInfo.type
|
||||||
|
}, function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
var total = (response.deleted || 0) + (response.fixed || 0);
|
||||||
|
if (total > 0) {
|
||||||
|
debugLog('🧹 Cleanup (Dokument ' + docInfo.id + '): ' + (response.deleted || 0) + ' verwaiste Subtotals, ' + (response.fixed || 0) + ' fehlerhafte Einträge');
|
||||||
|
// Kein automatischer Reload - nur im Hintergrund bereinigen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 'json');
|
||||||
|
}
|
||||||
|
|
||||||
// Flag: Wird gerade gezogen?
|
// Flag: Wird gerade gezogen?
|
||||||
var isDragging = false;
|
var isDragging = false;
|
||||||
var isTogglingSubtotal = false;
|
var isTogglingSubtotal = false;
|
||||||
|
|
@ -20,7 +50,8 @@ if (typeof SubtotalTitleLoaded === 'undefined') {
|
||||||
|
|
||||||
// Füge Button zu den Standard-Buttons hinzu - NUR im Entwurfsstatus
|
// Füge Button zu den Standard-Buttons hinzu - NUR im Entwurfsstatus
|
||||||
if ($('#tablelines').length > 0) {
|
if ($('#tablelines').length > 0) {
|
||||||
var factureId = getFactureId();
|
// Cleanup verwaister Subtotals (wo show_subtotal=0)
|
||||||
|
cleanupOrphanedSubtotals();
|
||||||
|
|
||||||
// Prüfe ob Dokument im Entwurfsstatus ist
|
// Prüfe ob Dokument im Entwurfsstatus ist
|
||||||
if (typeof subtotalTitleIsDraft !== 'undefined' && subtotalTitleIsDraft === true) {
|
if (typeof subtotalTitleIsDraft !== 'undefined' && subtotalTitleIsDraft === true) {
|
||||||
|
|
@ -34,7 +65,6 @@ if (typeof SubtotalTitleLoaded === 'undefined') {
|
||||||
debugLog('⚠️ Dokument nicht im Entwurfsstatus - Button wird nicht angezeigt');
|
debugLog('⚠️ Dokument nicht im Entwurfsstatus - Button wird nicht angezeigt');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⬇️ HIER FEHLTE DER AUFRUF! ⬇️
|
|
||||||
initDragAndDrop();
|
initDragAndDrop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -104,11 +134,13 @@ function createNewSection() {
|
||||||
*/
|
*/
|
||||||
function moveSection(sectionId, direction) {
|
function moveSection(sectionId, direction) {
|
||||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||||
debugLog('🔄 Verschiebe Section ' + sectionId + ' ' + direction);
|
var docInfo = getDocumentInfo();
|
||||||
|
debugLog('🔄 Verschiebe Section ' + sectionId + ' ' + direction + ' (docType: ' + docInfo.type + ')');
|
||||||
|
|
||||||
$.post('/dolibarr/custom/subtotaltitle/ajax/move_section.php', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/move_section.php', {
|
||||||
section_id: sectionId,
|
section_id: sectionId,
|
||||||
direction: direction
|
direction: direction,
|
||||||
|
document_type: docInfo.type
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
debugLog('Move response: ' + JSON.stringify(response));
|
debugLog('Move response: ' + JSON.stringify(response));
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
|
@ -401,25 +433,56 @@ function saveCurrentOrder() {
|
||||||
else if ($row.attr('id') && $row.attr('id').indexOf('row-') === 0) {
|
else if ($row.attr('id') && $row.attr('id').indexOf('row-') === 0) {
|
||||||
var productId = $row.attr('id').replace('row-', '');
|
var productId = $row.attr('id').replace('row-', '');
|
||||||
|
|
||||||
|
// Prüfe ob Produkt explizit einer Section zugeordnet ist oder frei ist
|
||||||
|
var rowParentSection = $row.attr('data-parent-section');
|
||||||
|
var assignToSection = null;
|
||||||
|
|
||||||
|
if (rowParentSection) {
|
||||||
|
// Produkt hat explizite Section-Zuordnung - behalten
|
||||||
|
assignToSection = rowParentSection;
|
||||||
|
} else if (currentSectionId) {
|
||||||
|
// Produkt hat keine Zuordnung - prüfe ob es NACH einem Subtotal steht
|
||||||
|
// Wenn ja, bleibt es frei (am Ende der Liste)
|
||||||
|
var $prevRows = $row.prevAll('tr.subtotal-row[data-section-id="' + currentSectionId + '"]');
|
||||||
|
if ($prevRows.length === 0) {
|
||||||
|
// Kein Subtotal davor = Produkt gehört zur aktuellen Section
|
||||||
|
assignToSection = currentSectionId;
|
||||||
|
}
|
||||||
|
// Sonst: Subtotal davor = Produkt ist frei (nach der Section)
|
||||||
|
}
|
||||||
|
|
||||||
updates.push({
|
updates.push({
|
||||||
type: 'product',
|
type: 'product',
|
||||||
id: productId,
|
id: productId,
|
||||||
order: order,
|
order: order,
|
||||||
parent_section: currentSectionId
|
parent_section: assignToSection
|
||||||
});
|
});
|
||||||
debugLog(' ' + order + '. 📦 Produkt #' + productId + ' → ' + (currentSectionId ? 'Section ' + currentSectionId : 'FREI'));
|
debugLog(' ' + order + '. 📦 Produkt #' + productId + ' → ' + (assignToSection ? 'Section ' + assignToSection : 'FREI'));
|
||||||
order++;
|
order++;
|
||||||
}
|
}
|
||||||
else if ($row.hasClass('textline-row')) {
|
else if ($row.hasClass('textline-row')) {
|
||||||
var textlineId = $row.attr('data-textline-id');
|
var textlineId = $row.attr('data-textline-id');
|
||||||
if (textlineId) {
|
if (textlineId) {
|
||||||
|
// Gleiche Logik wie für Produkte
|
||||||
|
var textParentSection = $row.attr('data-parent-section');
|
||||||
|
var textAssignToSection = null;
|
||||||
|
|
||||||
|
if (textParentSection) {
|
||||||
|
textAssignToSection = textParentSection;
|
||||||
|
} else if (currentSectionId) {
|
||||||
|
var $prevSubtotals = $row.prevAll('tr.subtotal-row[data-section-id="' + currentSectionId + '"]');
|
||||||
|
if ($prevSubtotals.length === 0) {
|
||||||
|
textAssignToSection = currentSectionId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updates.push({
|
updates.push({
|
||||||
type: 'text',
|
type: 'text',
|
||||||
id: textlineId,
|
id: textlineId,
|
||||||
order: order,
|
order: order,
|
||||||
parent_section: currentSectionId
|
parent_section: textAssignToSection
|
||||||
});
|
});
|
||||||
debugLog(' ' + order + '. 📝 Text #' + textlineId + ' → ' + (currentSectionId ? 'Section ' + currentSectionId : 'FREI'));
|
debugLog(' ' + order + '. 📝 Text #' + textlineId + ' → ' + (textAssignToSection ? 'Section ' + textAssignToSection : 'FREI'));
|
||||||
order++;
|
order++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -697,85 +760,140 @@ function initCollapse() {
|
||||||
|
|
||||||
colorSections();
|
colorSections();
|
||||||
|
|
||||||
// NEU: Fehlende Subtotals einfügen
|
// DEAKTIVIERT: JavaScript-Subtotal verursacht Duplikate
|
||||||
insertMissingSubtotals();
|
// insertLastSectionSubtotal();
|
||||||
|
|
||||||
debugLog('✅ Collapse initialisiert');
|
debugLog('✅ Collapse initialisiert');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fügt fehlende Subtotals am Ende ein (für letzte Section)
|
* Prüft und zeigt Subtotal für die LETZTE Section an (wenn aktiviert)
|
||||||
|
* PHP rendert Subtotals nur zwischen Sections, nicht am Ende der Tabelle
|
||||||
|
* Diese Funktion holt die Daten via AJAX und fügt die Zeile mit Checkbox ein
|
||||||
*/
|
*/
|
||||||
function insertMissingSubtotals() {
|
function insertLastSectionSubtotal() {
|
||||||
debugLog('🔢 Prüfe fehlende Subtotals...');
|
debugLog('🔢 Prüfe Subtotal für letzte Section...');
|
||||||
|
|
||||||
$('tr.section-header').each(function() {
|
var $allSections = $('tr.section-header');
|
||||||
var $header = $(this);
|
if ($allSections.length === 0) {
|
||||||
var sectionId = $header.attr('data-section-id');
|
debugLog(' Keine Sections vorhanden');
|
||||||
var $checkbox = $header.find('.subtotal-toggle');
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Nur wenn Checkbox aktiviert ist
|
// Nur die LETZTE Section prüfen
|
||||||
if (!$checkbox.length || !$checkbox.is(':checked')) {
|
var $lastHeader = $allSections.last();
|
||||||
return;
|
var sectionId = $lastHeader.attr('data-section-id');
|
||||||
}
|
var $checkbox = $lastHeader.find('.subtotal-toggle');
|
||||||
|
|
||||||
// Finde letztes Produkt dieser Section
|
// Nur wenn Checkbox existiert UND aktiviert ist
|
||||||
var $products = $('tr[data-parent-section="' + sectionId + '"]');
|
if (!$checkbox.length || !$checkbox.is(':checked')) {
|
||||||
if ($products.length === 0) {
|
debugLog(' Letzte Section ' + sectionId + ': Subtotal nicht aktiviert');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var $lastProduct = $products.last();
|
// Finde Produkte dieser Section
|
||||||
|
var $products = $('tr[data-parent-section="' + sectionId + '"]');
|
||||||
|
if ($products.length === 0) {
|
||||||
|
debugLog(' Letzte Section ' + sectionId + ': Keine Produkte');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Prüfe ob direkt danach schon ein Subtotal kommt
|
var $lastProduct = $products.last();
|
||||||
var $nextRow = $lastProduct.next('tr');
|
|
||||||
if ($nextRow.hasClass('subtotal-row')) {
|
|
||||||
debugLog(' Section ' + sectionId + ': Subtotal vorhanden ✓');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
debugLog(' Section ' + sectionId + ': Subtotal fehlt - füge ein...');
|
// Prüfe ob Subtotal für diese Section irgendwo im DOM existiert
|
||||||
|
// (sowohl data-section-id als auch data-subtotal-id prüfen)
|
||||||
|
var $existingSubtotal = $('tr.subtotal-row[data-section-id="' + sectionId + '"]');
|
||||||
|
if ($existingSubtotal.length > 0) {
|
||||||
|
debugLog(' Letzte Section ' + sectionId + ': Subtotal existiert bereits im DOM ✓');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Berechne Summe aus Produktzeilen
|
// Prüfe auch nächste Zeile nach letztem Produkt
|
||||||
var sum = 0;
|
var $nextRow = $lastProduct.next('tr');
|
||||||
$products.each(function() {
|
if ($nextRow.hasClass('subtotal-row') || $nextRow.find('td:contains("Zwischensumme")').length > 0) {
|
||||||
var $row = $(this);
|
debugLog(' Letzte Section ' + sectionId + ': Subtotal direkt nach Produkt ✓');
|
||||||
// Versuche Netto-Betrag aus der Zeile zu holen
|
return;
|
||||||
var priceText = $row.find('td.linecolht').text().trim();
|
}
|
||||||
if (!priceText) {
|
|
||||||
// Fallback: letzte Spalte mit Zahl
|
debugLog(' Letzte Section ' + sectionId + ': Subtotal fehlt in DOM, hole Daten...');
|
||||||
$row.find('td').each(function() {
|
|
||||||
var text = $(this).text().trim();
|
// Berechne Summe lokal
|
||||||
if (text.match(/^-?[\d\s.,]+$/)) {
|
var sum = 0;
|
||||||
priceText = text;
|
$products.each(function() {
|
||||||
}
|
var priceText = $(this).find('td.linecolht').text().trim();
|
||||||
});
|
if (priceText) {
|
||||||
|
var price = parseFloat(priceText.replace(/\s/g, '').replace('.', '').replace(',', '.'));
|
||||||
|
if (!isNaN(price)) {
|
||||||
|
sum += price;
|
||||||
}
|
}
|
||||||
if (priceText) {
|
}
|
||||||
var price = parseFloat(priceText.replace(/\s/g, '').replace('.', '').replace(',', '.'));
|
|
||||||
if (!isNaN(price)) {
|
|
||||||
sum += price;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var formattedSum = sum.toLocaleString('de-DE', {
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
maximumFractionDigits: 2
|
|
||||||
});
|
|
||||||
|
|
||||||
// Subtotal-Zeile einfügen
|
|
||||||
var html = '<tr class="subtotal-row" data-section-id="' + sectionId + '" style="font-weight:bold;">';
|
|
||||||
html += '<td colspan="9" style="text-align:right; padding:8px;">Zwischensumme:</td>';
|
|
||||||
html += '<td class="linecolht right" style="padding:8px;">' + formattedSum + '</td>';
|
|
||||||
html += '<td class="linecoledit"></td>';
|
|
||||||
html += '<td class="linecoldelete"></td>';
|
|
||||||
html += '<td class="linecolmove"></td>';
|
|
||||||
html += '<td class="linecolunlink"></td>';
|
|
||||||
html += '</tr>';
|
|
||||||
|
|
||||||
$lastProduct.after(html);
|
|
||||||
debugLog(' → Subtotal eingefügt: ' + formattedSum + ' €');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var formattedSum = sum.toLocaleString('de-DE', {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hole Subtotal-ID aus Datenbank (oder erstelle ihn falls nötig)
|
||||||
|
var docType = getDocumentType();
|
||||||
|
$.ajax({
|
||||||
|
url: '/dolibarr/custom/subtotaltitle/ajax/check_subtotal.php',
|
||||||
|
method: 'GET',
|
||||||
|
data: {
|
||||||
|
section_id: sectionId,
|
||||||
|
document_type: docType
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
if (response.exists && response.subtotal_id) {
|
||||||
|
// Subtotal existiert - zeige ihn mit Checkbox an
|
||||||
|
renderSubtotalRow($lastProduct, sectionId, response.subtotal_id, response.in_facturedet, formattedSum);
|
||||||
|
} else {
|
||||||
|
// Subtotal existiert nicht in DB - zeige ohne Checkbox (nur Vorschau)
|
||||||
|
renderSubtotalRow($lastProduct, sectionId, null, false, formattedSum);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
// Bei Fehler: zeige Subtotal ohne Checkbox
|
||||||
|
renderSubtotalRow($lastProduct, sectionId, null, false, formattedSum);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendert eine Subtotal-Zeile im DOM
|
||||||
|
*/
|
||||||
|
function renderSubtotalRow($afterElement, sectionId, subtotalId, inFacturedet, formattedSum) {
|
||||||
|
var inClass = inFacturedet ? ' in-facturedet' : '';
|
||||||
|
var syncChecked = inFacturedet ? 'checked' : '';
|
||||||
|
|
||||||
|
var html = '<tr class="subtotal-row' + inClass + '" data-section-id="' + sectionId + '"';
|
||||||
|
if (subtotalId) {
|
||||||
|
html += ' data-subtotal-id="' + subtotalId + '"';
|
||||||
|
}
|
||||||
|
html += ' style="font-weight:bold; background:#f5f5f5;">';
|
||||||
|
|
||||||
|
html += '<td colspan="9" style="text-align:right; padding:8px;">';
|
||||||
|
html += 'Zwischensumme:';
|
||||||
|
|
||||||
|
// Checkbox nur wenn Subtotal in DB existiert
|
||||||
|
if (subtotalId) {
|
||||||
|
html += ' <label style="font-weight:normal;font-size:12px;margin-left:10px;" title="In Rechnung/PDF anzeigen">';
|
||||||
|
html += '<input type="checkbox" class="sync-checkbox" data-line-id="' + subtotalId + '" data-line-type="subtotal" ' + syncChecked;
|
||||||
|
html += ' onclick="toggleFacturedetSync(' + subtotalId + ', \'subtotal\', this);">';
|
||||||
|
html += ' 📄</label>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '</td>';
|
||||||
|
html += '<td class="linecolht right" style="padding:8px;">' + formattedSum + ' €</td>';
|
||||||
|
html += '<td class="linecoledit"></td>';
|
||||||
|
html += '<td class="linecoldelete"></td>';
|
||||||
|
html += '<td class="linecolmove"></td>';
|
||||||
|
html += '<td class="linecolunlink"></td>';
|
||||||
|
html += '</tr>';
|
||||||
|
|
||||||
|
$afterElement.after(html);
|
||||||
|
debugLog(' → Subtotal eingefügt: ' + formattedSum + ' € (ID: ' + (subtotalId || 'keine') + ')');
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
@ -816,24 +934,26 @@ function insertTextLines() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSubtotal(sectionId, checkbox) {
|
function toggleSubtotal(sectionId, checkbox) {
|
||||||
event.stopPropagation();
|
if (event) event.stopPropagation();
|
||||||
|
|
||||||
var show = checkbox.checked;
|
var show = checkbox.checked;
|
||||||
|
var docType = getDocumentType();
|
||||||
|
|
||||||
debugLog('🔢 Toggle Subtotal für Section ' + sectionId + ': ' + show);
|
debugLog('🔢 Toggle Subtotal für Section ' + sectionId + ': ' + show + ', docType: ' + docType);
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/dolibarr/custom/subtotaltitle/ajax/toggle_subtotal.php',
|
url: '/dolibarr/custom/subtotaltitle/ajax/toggle_subtotal.php',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
section_id: sectionId,
|
section_id: sectionId,
|
||||||
show: show ? 1 : 0
|
show: show ? 1 : 0,
|
||||||
|
document_type: docType
|
||||||
},
|
},
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
debugLog('Subtotal Response: ' + JSON.stringify(response));
|
debugLog('Subtotal Response: ' + JSON.stringify(response));
|
||||||
if (response.success && response.reload) {
|
if (response.success && response.reload) {
|
||||||
window.location.reload();
|
safeReload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
|
|
@ -1032,14 +1152,16 @@ function removeFromSection(productId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('🔓 Entferne Produkt ' + productId + ' aus Section');
|
var docType = getDocumentType();
|
||||||
|
debugLog('🔓 Entferne Produkt ' + productId + ' aus Section (docType: ' + docType + ')');
|
||||||
|
|
||||||
$.post('/dolibarr/custom/subtotaltitle/ajax/remove_from_section.php', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/remove_from_section.php', {
|
||||||
product_id: productId
|
product_id: productId,
|
||||||
|
document_type: docType
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
debugLog('Remove response: ' + JSON.stringify(response));
|
debugLog('Remove response: ' + JSON.stringify(response));
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
window.location.href = window.location.pathname + window.location.search;
|
safeReload();
|
||||||
} else {
|
} else {
|
||||||
alert((lang.errorReordering || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
alert((lang.errorReordering || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||||
}
|
}
|
||||||
|
|
@ -1114,7 +1236,7 @@ function deleteMassSelected() {
|
||||||
facture_id: getFactureId()
|
facture_id: getFactureId()
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
window.location.reload();
|
safeReload();
|
||||||
} else {
|
} else {
|
||||||
alert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannt'));
|
alert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannt'));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,8 @@ function syncAllToFacturedet() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var docType = getDocumentTypeForSync();
|
||||||
|
|
||||||
$unchecked.each(function() {
|
$unchecked.each(function() {
|
||||||
var lineId = $(this).data('line-id');
|
var lineId = $(this).data('line-id');
|
||||||
var lineType = $(this).data('line-type');
|
var lineType = $(this).data('line-type');
|
||||||
|
|
@ -142,7 +144,8 @@ function syncAllToFacturedet() {
|
||||||
$.post('/dolibarr/custom/subtotaltitle/ajax/sync_to_facturedet.php', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/sync_to_facturedet.php', {
|
||||||
action: 'add',
|
action: 'add',
|
||||||
line_id: lineId,
|
line_id: lineId,
|
||||||
line_type: lineType
|
line_type: lineType,
|
||||||
|
document_type: docType
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
done++;
|
done++;
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
|
@ -189,6 +192,8 @@ function removeAllFromFacturedet() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var docType = getDocumentTypeForSync();
|
||||||
|
|
||||||
$checked.each(function() {
|
$checked.each(function() {
|
||||||
var lineId = $(this).data('line-id');
|
var lineId = $(this).data('line-id');
|
||||||
var lineType = $(this).data('line-type');
|
var lineType = $(this).data('line-type');
|
||||||
|
|
@ -196,7 +201,8 @@ function removeAllFromFacturedet() {
|
||||||
$.post('/dolibarr/custom/subtotaltitle/ajax/sync_to_facturedet.php', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/sync_to_facturedet.php', {
|
||||||
action: 'remove',
|
action: 'remove',
|
||||||
line_id: lineId,
|
line_id: lineId,
|
||||||
line_type: lineType
|
line_type: lineType,
|
||||||
|
document_type: docType
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
done++;
|
done++;
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
|
@ -237,12 +243,15 @@ function updateAllSubtotals() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var docType = getDocumentTypeForSync();
|
||||||
|
|
||||||
$subtotals.each(function() {
|
$subtotals.each(function() {
|
||||||
var lineId = $(this).data('line-id');
|
var lineId = $(this).data('line-id');
|
||||||
|
|
||||||
$.post('/dolibarr/custom/subtotaltitle/ajax/sync_to_facturedet.php', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/sync_to_facturedet.php', {
|
||||||
action: 'update_subtotal',
|
action: 'update_subtotal',
|
||||||
line_id: lineId
|
line_id: lineId,
|
||||||
|
document_type: docType
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
done++;
|
done++;
|
||||||
debugLog('Subtotal #' + lineId + ' updated: ' + JSON.stringify(response));
|
debugLog('Subtotal #' + lineId + ' updated: ' + JSON.stringify(response));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue