// DEBUG FLAG - true für Debug-Ausgaben, false für Produktiv var SUBTOTAL_DEBUG = true; function debugLog(message) { if (SUBTOTAL_DEBUG) { console.log(message); } } // Flag: Wird gerade gezogen? var isDragging = false; var isTogglingSubtotal = false; // Nur einmal laden! if (typeof SubtotalTitleLoaded === 'undefined') { SubtotalTitleLoaded = true; $(document).ready(function() { debugLog('✅ SubtotalTitle JS loaded!'); // Füge Button zu den Standard-Buttons hinzu - NUR im Entwurfsstatus if ($('#tablelines').length > 0) { var factureId = getFactureId(); // Prüfe ob Dokument im Entwurfsstatus ist if (typeof subtotalTitleIsDraft !== 'undefined' && subtotalTitleIsDraft === true) { var createBtn = '' + (typeof subtotalTitleLang !== 'undefined' ? subtotalTitleLang.sectionCreate || 'Section erstellen' : 'Section erstellen') + ''; if ($('.tabsAction').length > 0) { $('.tabsAction').prepend(createBtn); debugLog('✅ Section-Button hinzugefügt'); } } else { debugLog('⚠️ Dokument nicht im Entwurfsstatus - Button wird nicht angezeigt'); } // ⬇️ HIER FEHLTE DER AUFRUF! ⬇️ initDragAndDrop(); } }); } /** * Holt die Dokument-ID und Typ aus der URL */ function getDocumentInfo() { var url = window.location.href; var match = url.match(/[?&]id=(\d+)/); var id = match ? match[1] : null; // Erkenne Dokumenttyp anhand der URL var docType = 'invoice'; // Default if (url.indexOf('/comm/propal/') !== -1) { docType = 'propal'; } else if (url.indexOf('/commande/') !== -1) { docType = 'order'; } return { id: id, type: docType }; } /** * DEPRECATED: Verwende getDocumentInfo() stattdessen */ function getFactureId() { return getDocumentInfo().id; } /** * Erstellt eine neue Section */ function createNewSection() { var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {}; var title = prompt(lang.sectionName || 'Neue Positionsgruppe - Name eingeben:'); if (!title) return; var docInfo = getDocumentInfo(); if (!docInfo.id) { alert(lang.errorLoadingSections || 'Fehler: Keine Dokument-ID gefunden'); return; } debugLog('Erstelle Section: ' + title + ' für ' + docInfo.type + ' ID ' + docInfo.id); $.post('/dolibarr/custom/subtotaltitle/ajax/create_section.php', { facture_id: docInfo.id, document_type: docInfo.type, title: title }, function(response) { debugLog('Section erstellt: ' + JSON.stringify(response)); if (response.success) { window.location.href = window.location.pathname + window.location.search; } else { alert((lang.errorSavingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler')); } }, 'json').fail(function(xhr, status, error) { debugLog('AJAX Fehler: ' + status + ' ' + error); alert((lang.errorSavingSection || 'Fehler beim Erstellen') + ': ' + error); }); } /** * Verschiebt eine Section nach oben/unten */ function moveSection(sectionId, direction) { var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {}; debugLog('🔄 Verschiebe Section ' + sectionId + ' ' + direction); $.post('/dolibarr/custom/subtotaltitle/ajax/move_section.php', { section_id: sectionId, direction: direction }, function(response) { debugLog('Move response: ' + JSON.stringify(response)); if (response.success) { window.location.href = window.location.pathname + window.location.search; } else { alert((lang.errorReordering || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler')); } }, 'json').fail(function(xhr, status, error) { if (SUBTOTAL_DEBUG) { console.error('AJAX Fehler: ' + status); console.error('Response Text: ' + xhr.responseText); console.error('Error:', error); } alert((lang.errorReordering || 'Fehler beim Verschieben') + (SUBTOTAL_DEBUG ? '. Siehe Console (F12) für Details.' : '.')); }); } /** * Section umbenennen */ function renameSection(sectionId, currentTitle) { var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {}; var newTitle = prompt(lang.sectionName || 'Positionsgruppe umbenennen:', currentTitle); if (!newTitle) return; debugLog('✏️ Benenne Section ' + sectionId + ' um zu: ' + newTitle); $.post('/dolibarr/custom/subtotaltitle/ajax/rename_section.php', { section_id: sectionId, title: newTitle }, function(response) { debugLog('Rename response: ' + JSON.stringify(response)); if (response.success) { window.location.href = window.location.pathname + window.location.search; } else { alert((lang.errorSavingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler')); } }, 'json').fail(function(xhr, status, error) { debugLog('AJAX Fehler: ' + status + ' ' + error); alert((lang.errorSavingSection || 'Fehler beim Umbenennen') + ': ' + error); }); } /** * Löscht eine leere Section */ function deleteSection(sectionId) { var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {}; if (!confirm(lang.confirmDeleteSection || 'Leere Positionsgruppe löschen?')) { return; } debugLog('🗑️ Lösche leere Section ' + sectionId); $.post('/dolibarr/custom/subtotaltitle/ajax/delete_section.php', { section_id: sectionId, force: 0 // Nur leere löschen }, function(response) { debugLog('Delete response: ' + JSON.stringify(response)); if (response.success) { window.location.href = window.location.pathname + window.location.search; } else { alert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler')); } }, 'json').fail(function(xhr, status, error) { debugLog('AJAX Fehler: ' + status + ' ' + error); alert((lang.errorDeletingSection || 'Fehler beim Löschen') + ': ' + error); }); } /** * Löscht eine Section MIT allen Produkten (Force-Delete) */ function deleteSectionForce(sectionId) { var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {}; var $row = $('tr.section-header[data-section-id="' + sectionId + '"]'); var productCount = $row.data('product-count'); var productIds = $row.data('product-ids') || []; var msg1 = (lang.confirmDeleteSectionForce || '⚠️ ACHTUNG!\n\nWollen Sie wirklich die Positionsgruppe\nUND alle %s enthaltenen Produkte löschen?\n\nDiese Aktion kann nicht rückgängig gemacht werden!').replace('%s', productCount); if (!confirm(msg1)) { return; } var msg2 = (lang.confirmDeleteSectionForce2 || 'Sind Sie WIRKLICH sicher?\n\n%s Produkte werden unwiderruflich gelöscht!').replace('%s', productCount); if (!confirm(msg2)) { return; } debugLog('🔴 Force-Delete Section ' + sectionId + ' mit Produkten: ' + JSON.stringify(productIds)); $.post('/dolibarr/custom/subtotaltitle/ajax/delete_section.php', { section_id: sectionId, force: 1, product_ids: JSON.stringify(productIds) }, function(response) { debugLog('Force-Delete response: ' + JSON.stringify(response)); if (response.success) { window.location.href = window.location.pathname + window.location.search; } else { alert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler')); } }, 'json').fail(function(xhr, status, error) { debugLog('AJAX Fehler: ' + status + ' ' + error); alert((lang.errorDeletingSection || 'Fehler beim Löschen') + ': ' + error); }); } /** * Fügt leere Sections an die richtige Stelle in der Tabelle ein */ function insertEmptySections() { var docInfo = getDocumentInfo(); if (!docInfo.id) return; debugLog('📦 Lade alle Sections für ' + docInfo.type + ' ID ' + docInfo.id); $.get('/dolibarr/custom/subtotaltitle/ajax/get_sections.php', { facture_id: docInfo.id, document_type: docInfo.type }, function(response) { if (!response.success) return; debugLog('Gefundene Sections: ' + response.sections.length); // Filtere nur leere Sections var emptySections = response.sections.filter(function(s) { return s.is_empty; }); debugLog('Leere Sections: ' + emptySections.length); // Füge jede leere Section an die richtige Stelle ein emptySections.forEach(function(section) { insertEmptySection(section); }); }, 'json'); } /** * Fügt eine leere Section an die richtige Stelle ein */ function insertEmptySection(section) { if ($('tr.section-header[data-section-id="' + section.id + '"]').length > 0) { debugLog('Section ' + section.title + ' existiert bereits, überspringe'); return; } debugLog('Füge leere Section ein: ' + section.title + ' (order: ' + section.line_order + ')'); var sectionHtml = ''; // Titel (colspan=10) sectionHtml += ''; sectionHtml += ''; sectionHtml += section.title; sectionHtml += '0 Produkte'; // Buttons sectionHtml += ''; sectionHtml += ''; sectionHtml += ''; sectionHtml += ''; // Edit (Spalte 11) sectionHtml += ''; sectionHtml += ''; sectionHtml += ''; sectionHtml += ''; // Delete (Spalte 12) - leere Section = normaler Mülleimer sectionHtml += ''; sectionHtml += ''; sectionHtml += ''; sectionHtml += ''; // Move (Spalte 13) sectionHtml += ''; // Unlink (Spalte 14) sectionHtml += ''; sectionHtml += ''; // Finde die richtige Position var inserted = false; $('#tablelines tbody tr').each(function() { var $row = $(this); if ($row.hasClass('section-empty')) return; var rowOrder = parseInt($row.attr('data-line-order')); if (rowOrder && section.line_order < rowOrder) { $row.before(sectionHtml); inserted = true; debugLog(' → Eingefügt vor Zeile mit order ' + rowOrder); return false; } }); if (!inserted) { // Finde die "Hinzufügen"-Zeile und füge davor ein var $addRow = $('#tablelines tbody tr.liste_titre_create'); if ($addRow.length > 0) { $addRow.before(sectionHtml); debugLog(' → Vor Hinzufügen-Zeile eingefügt'); } else { // Fallback: ans Ende $('#tablelines tbody').append(sectionHtml); debugLog(' → Ans Ende angehängt'); } } } function initDragAndDrop() { debugLog('🖱️ Installiere Drop-Listener...'); setTimeout(function() { debugLog('✅ Überwache Tabellen-Änderungen...'); // Überwache die Tabelle auf Änderungen var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // Prüfe ob Zeilen verschoben wurden if (mutation.type === 'childList') { // Ignoriere wenn gerade gezogen wird if (isDragging) { debugLog('📦 Tabelle geändert (Drag läuft noch...)'); return; } debugLog('📦 Tabelle geändert - prüfe Reihenfolge...'); // Warte kurz, dann speichere unsere Section-Logik setTimeout(function() { if (!isDragging) { saveCurrentOrder(); } }, 500); } }); }); // Beobachte tbody var tbody = document.querySelector('#tablelines tbody'); if (tbody) { observer.observe(tbody, { childList: true, subtree: false }); debugLog('✅ MutationObserver installiert!'); } }, 2000); } function saveCurrentOrder() { // Nicht speichern wenn gerade Subtotal getoggelt wird if (isTogglingSubtotal) { debugLog('⏸️ Skip saveCurrentOrder - Subtotal Toggle aktiv'); return; } debugLog('💾 Speichere Section-Zuordnungen...'); var updates = []; var order = 1; var currentSectionId = null; $('#tablelines tbody tr').each(function() { var $row = $(this); if ($row.hasClass('liste_titre') || $row.hasClass('liste_titre_add') || $row.hasClass('liste_titre_create') || $row.hasClass('trlinefordates') || $row.attr('id') === 'trlinefordates') { return; } if ($row.hasClass('section-header')) { var sectionId = $row.attr('data-section-id'); if (sectionId) { currentSectionId = sectionId; updates.push({ type: 'section', id: sectionId, order: order }); debugLog(' ' + order + '. 📁 Section #' + sectionId); order++; } } else if ($row.attr('id') && $row.attr('id').indexOf('row-') === 0) { var productId = $row.attr('id').replace('row-', ''); updates.push({ type: 'product', id: productId, order: order, parent_section: currentSectionId }); debugLog(' ' + order + '. 📦 Produkt #' + productId + ' → ' + (currentSectionId ? 'Section ' + currentSectionId : 'FREI')); order++; } else if ($row.hasClass('textline-row')) { var textlineId = $row.attr('data-textline-id'); if (textlineId) { updates.push({ type: 'text', id: textlineId, order: order, parent_section: currentSectionId }); debugLog(' ' + order + '. 📝 Text #' + textlineId + ' → ' + (currentSectionId ? 'Section ' + currentSectionId : 'FREI')); order++; } } }); if (updates.length === 0) { debugLog('⚠️ Keine Updates gefunden!'); return; } debugLog('🚀 Sende ' + updates.length + ' Updates...'); var docInfo = getDocumentInfo(); $.post('/dolibarr/custom/subtotaltitle/ajax/reorder_all.php', { facture_id: getFactureId(), document_type: docInfo.type, new_order: JSON.stringify(updates) }, function(response) { debugLog('✅ Server: ' + JSON.stringify(response)); // Kein Reload mehr - Reihenfolge ist gespeichert // if (response.success) { // debugLog('🔄 Reload...'); // window.location.href = window.location.pathname + window.location.search; // } }, 'json').fail(function(xhr) { debugLog('❌ Fehler: ' + xhr.responseText); }); } /** * Verschiebt ein Produkt zu einer Section * MUSS AUSSERHALB sein! */ function moveProductToSection(productId, sectionId, newLineOrder) { var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {}; debugLog('🚀 Verschiebe Produkt ' + productId + ' zu Section ' + (sectionId || 'FREI') + ' auf Position ' + newLineOrder); $.post('/dolibarr/custom/subtotaltitle/ajax/move_product.php', { product_id: productId, new_section_id: sectionId || 0, new_line_order: newLineOrder }, function(response) { debugLog('Move response: ' + JSON.stringify(response)); if (response.success) { window.location.href = window.location.pathname + window.location.search; } else { alert((lang.errorReordering || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler')); } }, 'json').fail(function(xhr, status, error) { if (SUBTOTAL_DEBUG) { console.error('AJAX Fehler: ' + status); console.error('Response Text: ' + xhr.responseText); } alert((lang.errorReordering || 'Fehler beim Verschieben') + (SUBTOTAL_DEBUG ? '. Siehe Console (F12) für Details.' : '.')); }); } /** * Fügt allen Zeilen eine "Unlink"-Spalte hinzu */ function addUnlinkColumn() { var $table = $('#tablelines'); if (!$table.length) return; // Prüfe ob schon ausgeführt if ($table.data('unlink-added')) { debugLog('🔗 Unlink-Spalte bereits vorhanden, überspringe'); return; } $table.data('unlink-added', true); debugLog('🔗 Füge Unlink-Spalte hinzu...'); // THEAD: Leere Spalte hinzufügen $table.find('thead tr').each(function() { if ($(this).find('th.linecolunlink').length === 0) { $(this).append(''); } }); // Alle Body-Zeilen durchgehen $table.find('tbody tr').each(function() { var $row = $(this); var rowId = $row.attr('id') || ''; // Prüfe ob Zeile bereits eine Unlink-Spalte hat if ($row.find('td.linecolunlink').length > 0) { return; } // Produkt-Zeilen (row-XXX) if (rowId.match(/^row-\d+/)) { var lineId = rowId.replace('row-', '').split('-')[0]; var parentSection = $row.attr('data-parent-section'); if (parentSection && parentSection !== 'null') { // Hat Section → Unlink-Button $row.append(''); } else { // Keine Section → leere Zelle $row.append(''); } return; } // Alle anderen Zeilen (trlinefordates, liste_titre, etc.): leere Zelle if ($row.find('td').length > 0) { $row.append(''); } }); debugLog('✅ Unlink-Spalte hinzugefügt'); } /** * PLAN B: Section-Assignment beim Produkt hinzufügen */ $(document).ready(function() { // Unlink-Spalte hinzufügen (nach data-Attributen) setTimeout(addUnlinkColumn, 600); // 1. Beim Submit des Formulars: Section merken $(document).on('submit', 'form[name="addproduct"]', function(e) { var selectedSection = $('#section_id_dropdown').val(); if (selectedSection) { debugLog('📝 Merke Section ' + selectedSection + ' für nächstes Produkt'); sessionStorage.setItem('pending_section_assignment', selectedSection); sessionStorage.setItem('pending_section_facture', getFactureId()); } }); // 2. Nach Page Reload: Prüfe ob Assignment pending ist var pendingSection = sessionStorage.getItem('pending_section_assignment'); var pendingFacture = sessionStorage.getItem('pending_section_facture'); var currentFacture = getFactureId(); if (pendingSection && pendingFacture == currentFacture) { debugLog('✅ Section-Assignment pending: Section ' + pendingSection); // Entferne aus sessionStorage sessionStorage.removeItem('pending_section_assignment'); sessionStorage.removeItem('pending_section_facture'); // Warte kurz, dann weise zu setTimeout(function() { assignLastProductToSection(pendingSection, currentFacture); }, 1000); } }); /** * Weist das neueste Produkt einer Section zu */ function assignLastProductToSection(sectionId, factureId) { var docInfo = getDocumentInfo(); debugLog('🎯 Weise neustes Produkt zu Section ' + sectionId + ' zu (docType: ' + docInfo.type + ')...'); $.post('/dolibarr/custom/subtotaltitle/ajax/assign_last_product.php', { facture_id: factureId, section_id: sectionId, document_type: docInfo.type }, function(response) { debugLog('✅ Assignment Response: ' + JSON.stringify(response)); if (response.success) { debugLog('✅ Produkt #' + response.product_id + ' zu Section zugewiesen'); window.location.href = window.location.pathname + window.location.search; } else { debugLog('❌ Fehler: ' + response.error); } }, 'json').fail(function(xhr, status, error) { debugLog('❌ AJAX Fehler: ' + status); debugLog('Response: ' + xhr.responseText); }); } /** * Ein-/Ausklappen einer Section */ function toggleSection(sectionId) { var $sectionRow = $('tr.section-header[data-section-id="' + sectionId + '"]'); var $toggle = $sectionRow.find('.section-toggle'); var isCollapsed = $sectionRow.hasClass('collapsed'); if (isCollapsed) { // Ausklappen $sectionRow.removeClass('collapsed'); $('tr[data-parent-section="' + sectionId + '"]').removeClass('section-collapsed'); $toggle.text('▼'); saveCollapseState(sectionId, false); debugLog('📂 Section ' + sectionId + ' ausgeklappt'); } else { // Einklappen $sectionRow.addClass('collapsed'); $('tr[data-parent-section="' + sectionId + '"]').addClass('section-collapsed'); $toggle.text('▶'); saveCollapseState(sectionId, true); debugLog('📁 Section ' + sectionId + ' eingeklappt'); } } /** * Alle Sections einklappen */ function collapseAllSections() { $('tr.section-header').each(function() { var sectionId = $(this).attr('data-section-id'); $(this).addClass('collapsed'); $('tr[data-parent-section="' + sectionId + '"]').addClass('section-collapsed'); saveCollapseState(sectionId, true); }); debugLog('📁 Alle Sections eingeklappt'); } /** * Alle Sections ausklappen */ function expandAllSections() { $('tr.section-header').each(function() { var sectionId = $(this).attr('data-section-id'); $(this).removeClass('collapsed'); $('tr[data-parent-section="' + sectionId + '"]').removeClass('section-collapsed'); saveCollapseState(sectionId, false); }); debugLog('📂 Alle Sections ausgeklappt'); } /** * Speichert Collapse-Zustand in localStorage */ function saveCollapseState(sectionId, isCollapsed) { var key = 'section_collapsed_' + sectionId; if (isCollapsed) { localStorage.setItem(key, '1'); debugLog('💾 Gespeichert: ' + key + ' = 1'); } else { localStorage.removeItem(key); debugLog('💾 Gelöscht: ' + key); } } /** * Lädt Collapse-Zustand aus localStorage */ function loadCollapseState(sectionId) { var key = 'section_collapsed_' + sectionId; var value = localStorage.getItem(key); debugLog('📂 Lade: ' + key + ' = ' + value); return value === '1'; } function initCollapse() { debugLog('🔽 Initialisiere Collapse...'); // Aktualisiere Count und lade Zustand für jede Section $('tr.section-header').each(function() { var sectionId = $(this).attr('data-section-id'); var productCount = $('tr[data-parent-section="' + sectionId + '"]').length; // Update count $(this).find('.section-count').text(productCount + ' Produkte'); // Lade gespeicherten Zustand var isCollapsed = loadCollapseState(sectionId); if (isCollapsed) { $(this).addClass('collapsed'); $(this).find('.section-toggle').text('▶'); $('tr[data-parent-section="' + sectionId + '"]').addClass('section-collapsed'); debugLog('Section ' + sectionId + ': ' + productCount + ' Produkte (eingeklappt)'); } else { debugLog('Section ' + sectionId + ': ' + productCount + ' Produkte'); } }); colorSections(); // NEU: Fehlende Subtotals einfügen insertMissingSubtotals(); debugLog('✅ Collapse initialisiert'); } /** * Fügt fehlende Subtotals am Ende ein (für letzte Section) */ function insertMissingSubtotals() { debugLog('🔢 Prüfe fehlende Subtotals...'); $('tr.section-header').each(function() { var $header = $(this); var sectionId = $header.attr('data-section-id'); var $checkbox = $header.find('.subtotal-toggle'); // Nur wenn Checkbox aktiviert ist if (!$checkbox.length || !$checkbox.is(':checked')) { return; } // Finde letztes Produkt dieser Section var $products = $('tr[data-parent-section="' + sectionId + '"]'); if ($products.length === 0) { return; } var $lastProduct = $products.last(); // Prüfe ob direkt danach schon ein Subtotal kommt var $nextRow = $lastProduct.next('tr'); if ($nextRow.hasClass('subtotal-row')) { debugLog(' Section ' + sectionId + ': Subtotal vorhanden ✓'); return; } debugLog(' Section ' + sectionId + ': Subtotal fehlt - füge ein...'); // Berechne Summe aus Produktzeilen var sum = 0; $products.each(function() { var $row = $(this); // Versuche Netto-Betrag aus der Zeile zu holen var priceText = $row.find('td.linecolht').text().trim(); if (!priceText) { // Fallback: letzte Spalte mit Zahl $row.find('td').each(function() { var text = $(this).text().trim(); if (text.match(/^-?[\d\s.,]+$/)) { priceText = text; } }); } 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 = ''; html += 'Zwischensumme:'; html += '' + formattedSum + ''; html += ''; html += ''; html += ''; html += ''; html += ''; $lastProduct.after(html); debugLog(' → Subtotal eingefügt: ' + formattedSum + ' €'); }); } $(document).ready(function() { setTimeout(function() { insertEmptySections(); // ← HIER HINZUFÜGEN! insertTextLines(); initCollapse(); }, 1500); }); /** * Fügt Textzeilen an die richtige Stelle ein */ function insertTextLines() { var docInfo = getDocumentInfo(); if (!docInfo.id) return; debugLog('📝 Lade Textzeilen für ' + docInfo.type + ' ID ' + docInfo.id); $.get('/dolibarr/custom/subtotaltitle/ajax/get_textlines.php', { facture_id: docInfo.id, document_type: docInfo.type }, function(response) { if (!response.success) return; debugLog('Gefundene Textzeilen: ' + response.textlines.length); response.textlines.forEach(function(textline) { insertTextLine(textline); }); // tableDnD neu initialisieren damit Textzeilen ziehbar sind if (response.textlines.length > 0) { reinitTableDnD(); } }, 'json'); } function toggleSubtotal(sectionId, checkbox) { event.stopPropagation(); var show = checkbox.checked; debugLog('🔢 Toggle Subtotal für Section ' + sectionId + ': ' + show); $.ajax({ url: '/dolibarr/custom/subtotaltitle/ajax/toggle_subtotal.php', method: 'POST', data: { section_id: sectionId, show: show ? 1 : 0 }, dataType: 'json', success: function(response) { debugLog('Subtotal Response: ' + JSON.stringify(response)); if (response.success && response.reload) { window.location.reload(); } }, error: function(xhr, status, error) { debugLog('Subtotal AJAX Error: ' + error); } }); } /** * Initialisiert tableDnD neu für dynamisch hinzugefügte Zeilen */ function reinitTableDnD() { debugLog('🔄 Reinitialisiere tableDnD...'); var $table = $('#tablelines'); if ($table.length && typeof $.fn.tableDnD !== 'undefined') { // Neu initialisieren $table.tableDnD({ onDragClass: 'myDragClass', dragHandle: '.linecolmove', onDragStart: function(table, row) { isDragging = true; debugLog('🎯 Drag gestartet: ' + row.className); }, onDrop: function(table, row) { isDragging = false; debugLog('📦 Drop: ' + row.className); // Kurz warten, dann speichern setTimeout(function() { saveCurrentOrder(); }, 300); } }); debugLog('✅ tableDnD neu initialisiert'); } } /** * Fügt eine Textzeile an die richtige Stelle ein */ function insertTextLine(textline) { if ($('tr.textline-row[data-textline-id="' + textline.id + '"]').length > 0) { debugLog('Textzeile ' + textline.id + ' existiert bereits, überspringe'); return; } debugLog('Füge Textzeile ein: ' + textline.title + ' (order: ' + textline.line_order + ')'); var html = ''; // Inhalt (colspan=10) html += ''; html += textline.title; html += ''; // Edit (Spalte 11) html += ''; html += ''; html += ''; html += ''; // Delete (Spalte 12) html += ''; html += ''; html += ''; html += ''; // Move (Spalte 13) html += ''; // Unlink (Spalte 14) html += ''; html += ''; // Finde die richtige Position var inserted = false; $('#tablelines tbody tr').each(function() { var $row = $(this); var rowOrder = parseInt($row.attr('data-line-order')); if (rowOrder && textline.line_order < rowOrder) { $row.before(html); inserted = true; debugLog(' → Eingefügt vor Zeile mit order ' + rowOrder); return false; } }); if (!inserted) { var $addRow = $('#tablelines tbody tr.liste_titre_create'); if ($addRow.length > 0) { $addRow.before(html); debugLog(' → Vor Hinzufügen-Zeile eingefügt'); } else { $('#tablelines tbody').append(html); debugLog(' → Ans Ende angehängt'); } } } /** * Erstellt eine neue Textzeile */ function createTextLine() { var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {}; var text = prompt(lang.textlineContent || 'Text eingeben:'); if (!text) return; var docInfo = getDocumentInfo(); if (!docInfo.id) { alert(lang.errorLoadingSections || 'Fehler: Keine Dokument-ID gefunden'); return; } debugLog('Erstelle Textzeile für ' + docInfo.type + ' ID ' + docInfo.id); $.post('/dolibarr/custom/subtotaltitle/ajax/create_textline.php', { facture_id: docInfo.id, document_type: docInfo.type, text: text }, function(response) { debugLog('Textzeile erstellt: ' + JSON.stringify(response)); if (response.success) { window.location.href = window.location.pathname + window.location.search; } else { alert((lang.errorSavingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler')); } }, 'json').fail(function(xhr, status, error) { debugLog('AJAX Fehler: ' + status + ' ' + error); alert((lang.errorSavingTextline || 'Fehler beim Erstellen') + ': ' + error); }); } /** * Textzeile bearbeiten */ function editTextLine(textlineId, currentText) { var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {}; var newText = prompt(lang.textlineContent || 'Text bearbeiten:', currentText || ''); if (!newText) return; debugLog('✏️ Bearbeite Textzeile ' + textlineId); $.post('/dolibarr/custom/subtotaltitle/ajax/edit_textline.php', { textline_id: textlineId, text: newText }, function(response) { debugLog('Edit response: ' + JSON.stringify(response)); if (response.success) { window.location.href = window.location.pathname + window.location.search; } else { alert((lang.errorSavingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler')); } }, 'json').fail(function(xhr, status, error) { debugLog('AJAX Fehler: ' + status + ' ' + error); alert((lang.errorSavingTextline || 'Fehler beim Bearbeiten') + ': ' + error); }); } /** * Textzeile löschen */ function deleteTextLine(textlineId) { var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {}; if (!confirm(lang.confirmDeleteTextline || 'Textzeile wirklich löschen?')) { return; } debugLog('🗑️ Lösche Textzeile ' + textlineId); $.post('/dolibarr/custom/subtotaltitle/ajax/delete_textline.php', { textline_id: textlineId }, function(response) { debugLog('Delete response: ' + JSON.stringify(response)); if (response.success) { window.location.href = window.location.pathname + window.location.search; } else { alert((lang.errorDeletingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler')); } }, 'json').fail(function(xhr, status, error) { debugLog('AJAX Fehler: ' + status + ' ' + error); alert((lang.errorDeletingTextline || 'Fehler beim Löschen') + ': ' + error); }); } /** * Entfernt ein Produkt aus seiner Section */ function removeFromSection(productId) { var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {}; if (!confirm(lang.confirmRemoveFromSection || 'Produkt aus Positionsgruppe entfernen?')) { return; } debugLog('🔓 Entferne Produkt ' + productId + ' aus Section'); $.post('/dolibarr/custom/subtotaltitle/ajax/remove_from_section.php', { product_id: productId }, function(response) { debugLog('Remove response: ' + JSON.stringify(response)); if (response.success) { window.location.href = window.location.pathname + window.location.search; } else { alert((lang.errorReordering || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler')); } }, 'json').fail(function(xhr, status, error) { debugLog('AJAX Fehler: ' + status + ' ' + error); alert((lang.errorReordering || 'Fehler') + ': ' + error); }); } function toggleMassDelete() { var $checkboxes = $('.mass-delete-checkbox'); if ($checkboxes.length === 0) { // Checkboxen einblenden $('tr.drag[data-line-order]').each(function() { var $row = $(this); var lineId = $row.attr('id')?.match(/row-(\d+)/)?.[1]; if (lineId) { $row.find('td:first').prepend( '' ); } }); // Buttons NACH dem Massenlösch-Button einfügen $('#btnMassDelete').after( 'Ausgewählte löschen' + 'Alle auswählen' + 'Abbrechen' ); // Original-Button verstecken $('#btnMassDelete').hide(); } else { // Checkboxen + Buttons entfernen $('.mass-delete-checkbox').remove(); $('#btnMassDoDelete, #btnMassSelectAll, #btnMassCancel').remove(); $('#btnMassDelete').show(); } } function selectAllLines() { $('.mass-delete-checkbox').prop('checked', true); } function deleteMassSelected() { var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {}; var selectedIds = []; $('.mass-delete-checkbox:checked').each(function() { selectedIds.push($(this).data('line-id')); }); if (selectedIds.length === 0) { alert(lang.noLinesSelected || 'Keine Zeilen ausgewählt!'); return; } var msg1 = (lang.confirmDeleteLines || 'Wirklich %s Zeilen löschen?').replace('%s', selectedIds.length); if (!confirm(msg1)) { return; } var msg2 = (lang.confirmDeleteLinesWarning || 'LETZTE WARNUNG: %s Zeilen werden UNWIDERRUFLICH gelöscht!').replace('%s', selectedIds.length); if (!confirm(msg2)) { return; } $.post('/dolibarr/custom/subtotaltitle/ajax/mass_delete.php', { line_ids: JSON.stringify(selectedIds), facture_id: getFactureId() }, function(response) { if (response.success) { window.location.reload(); } else { alert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannt')); } }, 'json'); } function getFactureId() { // Aus URL holen (funktioniert für alle Dokumenttypen) var match = window.location.search.match(/id=(\d+)/); return match ? match[1] : 0; } function getDocumentType() { // Erkenne Dokumenttyp aus URL var path = window.location.pathname; if (path.indexOf('/compta/facture/') > -1 || path.indexOf('/facture/') > -1) { return 'invoice'; } if (path.indexOf('/comm/propal/') > -1 || path.indexOf('/propal/') > -1) { return 'propal'; } if (path.indexOf('/commande/') > -1) { return 'order'; } return 'invoice'; // Fallback } /** * Färbt Sections unterschiedlich ein */ function colorSections() { debugLog('🎨 Färbe Sections ein...'); var colors = ['#4a90d9', '#50b87d', '#e67e22', '#9b59b6', '#e74c3c', '#1abc9c', '#f39c12', '#3498db']; var colorIndex = 0; $('tr.section-header').each(function() { var sectionId = $(this).attr('data-section-id'); var color = colors[colorIndex % colors.length]; // Section-Header färben $(this).find('td:first').css('border-left', '4px solid ' + color); // Alle Produkte dieser Section färben $('tr[data-parent-section="' + sectionId + '"]').each(function() { $(this).find('td:first').css('border-left', '4px solid ' + color); $(this).find('td').css('background-color', hexToRgba(color, 0.05)); }); // Textzeilen dieser Section färben $('tr.textline-row[data-parent-section="' + sectionId + '"]').each(function() { $(this).find('td:first').css('border-left', '4px solid ' + color); }); debugLog(' Section ' + sectionId + ' → ' + color); colorIndex++; }); debugLog('✅ Sections eingefärbt'); } /** * Hex zu RGBA konvertieren */ function hexToRgba(hex, alpha) { var r = parseInt(hex.slice(1, 3), 16); var g = parseInt(hex.slice(3, 5), 16); var b = parseInt(hex.slice(5, 7), 16); return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')'; }