* * Sync SubtotalTitle lines to/from facturedet */ define('NOTOKENRENEWAL', 1); $res = 0; if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php"; if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php"; if (!$res) die("Include of main fails"); require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; dol_include_once('/subtotaltitle/lib/subtotaltitle.lib.php'); require_once __DIR__.'/../class/DocumentTypeHelper.class.php'; header('Content-Type: application/json'); $action = GETPOST('action', 'alpha'); $line_id = GETPOST('line_id', 'int'); $line_type = GETPOST('line_type', 'alpha'); $facture_id = GETPOST('facture_id', 'int'); $docType = GETPOST('document_type', 'alpha'); subtotaltitle_debug_log('🔄 sync_to_facturedet: action='.$action.', line_id='.$line_id.', type='.$line_type.', docType='.$docType); if (!$line_id || !$action || !$docType) { echo json_encode(array('success' => false, 'error' => 'Missing parameters')); exit; } // Hole die richtigen Tabellennamen für diesen Dokumenttyp $tables = DocumentTypeHelper::getTableNames($docType); if (!$tables) { echo json_encode(array('success' => false, 'error' => 'Invalid document type')); exit; } // Special codes für unsere Zeilentypen $special_codes = array( 'section' => 100, 'text' => 101, 'subtotal' => 102 ); if ($action == 'add') { // ========== ZUR RECHNUNG HINZUFÜGEN ========== // Hole Daten aus unserer Manager-Tabelle $sql = "SELECT m.*, s.title as section_title, s.rowid as section_rowid"; $sql .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager m"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture_lines_manager s ON s.rowid = m.parent_section"; $sql .= " WHERE m.rowid = ".(int)$line_id; $resql = $db->query($sql); if (!$resql || $db->num_rows($resql) == 0) { echo json_encode(array('success' => false, 'error' => 'Line not found')); exit; } $line = $db->fetch_object($resql); $document_id = $line->{$tables['fk_parent']}; $line_type = $line->line_type; $fk_line_field = $tables['fk_line']; // Prüfe ob schon in detail-Tabelle (für nicht-Produkte) if ($line->$fk_line_field > 0 && $line_type != 'product') { echo json_encode(array('success' => false, 'error' => 'Already in detail table')); exit; } // AUTOMATISCHE REPARATUR: Wenn Section keine Subtotal-Zeile hat, erstelle sie if ($line_type == 'section') { $sql_check = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager"; $sql_check .= " WHERE parent_section = ".(int)$line_id; $sql_check .= " AND line_type = 'subtotal'"; $sql_check .= " AND document_type = '".$db->escape($docType)."'"; $res_check = $db->query($sql_check); if ($db->num_rows($res_check) == 0) { // Keine Subtotal-Zeile vorhanden - automatisch erstellen subtotaltitle_debug_log('⚠️ Section #'.$line_id.' hat keine Subtotal-Zeile - erstelle automatisch'); $sql_max = "SELECT MAX(line_order) as max_order FROM ".MAIN_DB_PREFIX."facture_lines_manager"; $sql_max .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id; $sql_max .= " AND document_type = '".$db->escape($docType)."'"; $res_max = $db->query($sql_max); $obj_max = $db->fetch_object($res_max); $next_order = ($obj_max && $obj_max->max_order) ? $obj_max->max_order + 1 : 9999; $fk_facture = ($docType === 'invoice') ? (int)$document_id : 'NULL'; $fk_propal = ($docType === 'propal') ? (int)$document_id : 'NULL'; $fk_commande = ($docType === 'order') ? (int)$document_id : 'NULL'; $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)$line_id.", ".$next_order.", NOW())"; $db->query($sql_subtotal); subtotaltitle_debug_log('✅ Subtotal-Zeile automatisch erstellt für Section #'.$line_id); } } // Bestimme special_code $special_code = isset($special_codes[$line_type]) ? $special_codes[$line_type] : 0; // Bestimme Beschreibung und Betrag $description = ''; $total_ht = 0; $qty = 0; switch ($line_type) { case 'section': $description = $line->title; $qty = 0; break; case 'text': $description = $line->title; $qty = 0; break; case 'subtotal': // Berechne Summe der Section $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)$line->parent_section; $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); $total_ht = $obj_sum->total ? $obj_sum->total : 0; $description = 'Zwischensumme: '.$line->section_title; $qty = 1; break; } // Bestimme rang (Position) - UNTERSCHIEDLICH für Sections vs andere Zeilen $new_rang = 1; if ($line_type == 'section') { // Für Sections: Finde das erste Produkt dieser Section und füge Section DAVOR ein $sql_first_product = "SELECT MIN(d.rang) as min_rang"; $sql_first_product .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager m"; $sql_first_product .= " INNER JOIN ".MAIN_DB_PREFIX.$tables['lines_table']." d ON d.rowid = m.".$tables['fk_line']; $sql_first_product .= " WHERE m.parent_section = ".(int)$line_id; $sql_first_product .= " AND m.".$tables['fk_parent']." = ".(int)$document_id; $sql_first_product .= " AND m.document_type = '".$db->escape($docType)."'"; $sql_first_product .= " AND m.line_type = 'product'"; $res_first = $db->query($sql_first_product); $obj_first = $db->fetch_object($res_first); if ($obj_first && $obj_first->min_rang) { // Section VOR dem ersten Produkt einfügen $new_rang = (int)$obj_first->min_rang; } else { // Keine Produkte in dieser Section - ans Ende $sql_max = "SELECT MAX(rang) as max_rang FROM ".MAIN_DB_PREFIX.$tables['lines_table']; $sql_max .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id; $res_max = $db->query($sql_max); $obj_max = $db->fetch_object($res_max); $new_rang = ($obj_max && $obj_max->max_rang) ? $obj_max->max_rang + 1 : 1; } } else { // Für Text/Subtotal: Basierend auf line_order Position $sql_rang = "SELECT MAX(d.rang) as max_rang"; $sql_rang .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager m"; $sql_rang .= " INNER JOIN ".MAIN_DB_PREFIX.$tables['lines_table']." d ON d.rowid = m.".$tables['fk_line']; $sql_rang .= " WHERE m.".$tables['fk_parent']." = ".(int)$document_id; $sql_rang .= " AND m.document_type = '".$db->escape($docType)."'"; $sql_rang .= " AND m.line_order < ".(int)$line->line_order; $res_rang = $db->query($sql_rang); $obj_rang = $db->fetch_object($res_rang); $new_rang = ($obj_rang && $obj_rang->max_rang) ? $obj_rang->max_rang + 1 : 1; } subtotaltitle_debug_log('📝 Berechne rang: line_type='.$line_type.', new_rang='.$new_rang); // Verschiebe alle nachfolgenden Zeilen $sql_shift = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table']; $sql_shift .= " SET rang = rang + 1"; $sql_shift .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id; $sql_shift .= " AND rang >= ".(int)$new_rang; $db->query($sql_shift); // Füge neue Zeile in Detail-Tabelle ein subtotaltitle_debug_log('📝 INSERT: line_type='.$line_type.', special_code='.$special_code); $sql_ins = "INSERT INTO ".MAIN_DB_PREFIX.$tables['lines_table']; $sql_ins .= " (".$tables['fk_parent'].", description, qty, subprice, total_ht, total_tva, total_ttc,"; $sql_ins .= " tva_tx, product_type, special_code, rang, info_bits)"; $sql_ins .= " VALUES ("; $sql_ins .= (int)$document_id.", "; $sql_ins .= "'".$db->escape($description)."', "; $sql_ins .= (float)$qty.", "; $sql_ins .= ($line_type == 'subtotal') ? (float)$total_ht.", " : "0, "; $sql_ins .= ($line_type == 'subtotal') ? (float)$total_ht.", " : "0, "; $sql_ins .= "0, "; // total_tva $sql_ins .= ($line_type == 'subtotal') ? (float)$total_ht.", " : "0, "; $sql_ins .= "0, "; // tva_tx $sql_ins .= "9, "; // product_type = 9 (Titel/Kommentar) $sql_ins .= (int)$special_code.", "; $sql_ins .= (int)$new_rang.", "; $sql_ins .= "0)"; subtotaltitle_debug_log('📝 SQL: '.$sql_ins); if (!$db->query($sql_ins)) { echo json_encode(array('success' => false, 'error' => $db->lasterror())); exit; } $new_detail_id = $db->last_insert_id(MAIN_DB_PREFIX.$tables['lines_table']); // Update unsere Manager-Tabelle $sql_upd = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager"; $sql_upd .= " SET ".$tables['fk_line']." = ".(int)$new_detail_id; $sql_upd .= ", in_facturedet = 1"; $sql_upd .= " WHERE rowid = ".(int)$line_id; $db->query($sql_upd); subtotaltitle_debug_log('✅ Zeile #'.$line_id.' zu '.$tables['lines_table'].' hinzugefügt als #'.$new_detail_id); echo json_encode(array( 'success' => true, 'detail_id' => $new_detail_id, 'rang' => $new_rang )); } elseif ($action == 'remove') { // ========== AUS DETAIL-TABELLE ENTFERNEN ========== // Hole Daten $sql = "SELECT ".$tables['fk_line']." as detail_id, ".$tables['fk_parent']." as parent_id, line_type FROM ".MAIN_DB_PREFIX."facture_lines_manager"; $sql .= " WHERE rowid = ".(int)$line_id; $resql = $db->query($sql); if (!$resql || $db->num_rows($resql) == 0) { echo json_encode(array('success' => false, 'error' => 'Line not found')); exit; } $line = $db->fetch_object($resql); // Produkte dürfen nicht entfernt werden if ($line->line_type == 'product') { echo json_encode(array('success' => false, 'error' => 'Cannot remove products')); exit; } if (!$line->detail_id) { echo json_encode(array('success' => false, 'error' => 'Not in detail table')); exit; } // Hole rang bevor wir löschen $sql_rang = "SELECT rang FROM ".MAIN_DB_PREFIX.$tables['lines_table']." WHERE rowid = ".(int)$line->detail_id; $res_rang = $db->query($sql_rang); $obj_rang = $db->fetch_object($res_rang); $old_rang = $obj_rang ? $obj_rang->rang : 0; // Lösche aus Detail-Tabelle $sql_del = "DELETE FROM ".MAIN_DB_PREFIX.$tables['lines_table']; $sql_del .= " WHERE rowid = ".(int)$line->detail_id; if (!$db->query($sql_del)) { echo json_encode(array('success' => false, 'error' => $db->lasterror())); exit; } // Schließe Lücke in rang if ($old_rang > 0) { $sql_shift = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table']; $sql_shift .= " SET rang = rang - 1"; $sql_shift .= " WHERE ".$tables['fk_parent']." = ".(int)$line->parent_id; $sql_shift .= " AND rang > ".(int)$old_rang; $db->query($sql_shift); } // Update unsere Manager-Tabelle $sql_upd = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager"; $sql_upd .= " SET ".$tables['fk_line']." = NULL"; $sql_upd .= ", in_facturedet = 0"; $sql_upd .= " WHERE rowid = ".(int)$line_id; $db->query($sql_upd); subtotaltitle_debug_log('✅ Zeile #'.$line_id.' aus '.$tables['lines_table'].' entfernt'); echo json_encode(array('success' => true)); } elseif ($action == 'update_subtotal') { // ========== SUBTOTAL-BETRAG AKTUALISIEREN ========== $sql = "SELECT m.".$tables['fk_line']." as detail_id, m.parent_section, s.title as section_title"; $sql .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager m"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture_lines_manager s ON s.rowid = m.parent_section"; $sql .= " WHERE m.rowid = ".(int)$line_id; $sql .= " AND m.line_type = 'subtotal'"; $resql = $db->query($sql); if (!$resql || $db->num_rows($resql) == 0) { echo json_encode(array('success' => false, 'error' => 'Subtotal not found')); exit; } $line = $db->fetch_object($resql); if (!$line->detail_id) { echo json_encode(array('success' => false, 'error' => 'Not in detail table')); exit; } // 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)$line->parent_section; $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); $total_ht = $obj_sum->total ? $obj_sum->total : 0; // Update Detail-Tabelle $sql_upd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table']; $sql_upd .= " SET subprice = ".(float)$total_ht; $sql_upd .= ", total_ht = ".(float)$total_ht; $sql_upd .= ", total_ttc = ".(float)$total_ht; $sql_upd .= " WHERE rowid = ".(int)$line->detail_id; $db->query($sql_upd); subtotaltitle_debug_log('✅ Subtotal #'.$line_id.' aktualisiert: '.$total_ht); echo json_encode(array('success' => true, 'total_ht' => $total_ht)); } else { echo json_encode(array('success' => false, 'error' => 'Unknown action')); }