1142 lines
40 KiB
JavaScript
Executable file
1142 lines
40 KiB
JavaScript
Executable file
// 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
|
|
if ($('#tablelines').length > 0) {
|
|
var factureId = getFactureId();
|
|
|
|
var createBtn = '<a class="butAction" href="#" onclick="createNewSection(); return false;">' + (typeof subtotalTitleLang !== 'undefined' ? subtotalTitleLang.sectionCreate || 'Section erstellen' : 'Section erstellen') + '</a>';
|
|
|
|
if ($('.tabsAction').length > 0) {
|
|
$('.tabsAction').prepend(createBtn);
|
|
debugLog('✅ Section-Button hinzugefügt');
|
|
}
|
|
|
|
// ⬇️ HIER FEHLTE DER AUFRUF! ⬇️
|
|
initDragAndDrop();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Holt die Facture ID aus der URL
|
|
|
|
function getFactureId() {
|
|
var url = window.location.href;
|
|
var match = url.match(/[?&]id=(\d+)/);
|
|
return match ? match[1] : null;
|
|
}*/
|
|
|
|
/**
|
|
* 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 factureId = getFactureId();
|
|
if (!factureId) {
|
|
alert(lang.errorLoadingSections || 'Fehler: Keine Facture ID gefunden');
|
|
return;
|
|
}
|
|
|
|
debugLog('Erstelle Section: ' + title + ' für Facture ' + factureId);
|
|
|
|
$.post('/dolibarr/custom/subtotaltitle/ajax/create_section.php', {
|
|
facture_id: factureId,
|
|
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 factureId = getFactureId();
|
|
if (!factureId) return;
|
|
|
|
debugLog('📦 Lade alle Sections...');
|
|
|
|
$.get('/dolibarr/custom/subtotaltitle/ajax/get_sections.php', {
|
|
facture_id: factureId
|
|
}, 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 = '<tr class="section-header section-empty" data-section-id="' + section.id + '" data-section-title="' + section.title + '" data-line-order="' + section.line_order + '" data-product-count="0" data-product-ids="[]">';
|
|
|
|
// Titel (colspan=10)
|
|
sectionHtml += '<td colspan="9" style="font-weight:bold; padding:8px;">';
|
|
sectionHtml += '<span class="section-toggle" onclick="toggleSection(' + section.id + '); event.stopPropagation();">▼</span>';
|
|
sectionHtml += section.title;
|
|
sectionHtml += '<span class="section-count">0 Produkte</span>';
|
|
|
|
// Buttons
|
|
sectionHtml += '</td><td align="right" style="white-space:nowrap;">';
|
|
sectionHtml += '<a href="#" onclick="moveSection(' + section.id + ', \'up\'); return false;" title="Nach oben"><span class="fa fa-long-arrow-alt-up"></span></a>';
|
|
sectionHtml += '<a href="#" onclick="moveSection(' + section.id + ', \'down\'); return false;" title="Nach unten"><span class="fa fa-long-arrow-alt-down"></span></a>';
|
|
sectionHtml += '</td>';
|
|
|
|
// Edit (Spalte 11)
|
|
sectionHtml += '<td class="linecoledit center">';
|
|
sectionHtml += '<a href="#" onclick="renameSection(' + section.id + '); return false;" title="Umbenennen">';
|
|
sectionHtml += '<span class="fas fa-pencil-alt" style="color:#444;" title="Ändern"></span></a>';
|
|
sectionHtml += '</td>';
|
|
|
|
// Delete (Spalte 12) - leere Section = normaler Mülleimer
|
|
sectionHtml += '<td class="linecoldelete center">';
|
|
sectionHtml += '<a href="#" onclick="deleteSection(' + section.id + '); return false;" title="Leere Gruppe löschen">';
|
|
sectionHtml += '<span class="fas fa-trash pictodelete" title="Löschen"></span></a>';
|
|
sectionHtml += '</td>';
|
|
|
|
// Move (Spalte 13)
|
|
sectionHtml += '<td class="linecolmove tdlineupdown center"></td>';
|
|
|
|
// Unlink (Spalte 14)
|
|
sectionHtml += '<td class="linecolunlink"></td>';
|
|
|
|
sectionHtml += '</tr>';
|
|
|
|
// 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...');
|
|
|
|
$.post('/dolibarr/custom/subtotaltitle/ajax/reorder_all.php', {
|
|
facture_id: getFactureId(),
|
|
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('<th class="linecolunlink" style="width:24px;"></th>');
|
|
}
|
|
});
|
|
|
|
// 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('<td class="linecolunlink center"><a href="#" onclick="removeFromSection(' + lineId + '); return false;" title="Aus Positionsgruppe entfernen"><span class="fas fa-unlink" style="color:#888;"></span></a></td>');
|
|
} else {
|
|
// Keine Section → leere Zelle
|
|
$row.append('<td class="linecolunlink"></td>');
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Alle anderen Zeilen (trlinefordates, liste_titre, etc.): leere Zelle
|
|
if ($row.find('td').length > 0) {
|
|
$row.append('<td class="linecolunlink"></td>');
|
|
}
|
|
});
|
|
|
|
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) {
|
|
debugLog('🎯 Weise neustes Produkt zu Section ' + sectionId + ' zu...');
|
|
|
|
$.post('/dolibarr/custom/subtotaltitle/ajax/assign_last_product.php', {
|
|
facture_id: factureId,
|
|
section_id: sectionId
|
|
}, 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 = '<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 + ' €');
|
|
});
|
|
}
|
|
|
|
$(document).ready(function() {
|
|
setTimeout(function() {
|
|
insertEmptySections(); // ← HIER HINZUFÜGEN!
|
|
insertTextLines();
|
|
initCollapse();
|
|
}, 1500);
|
|
});
|
|
|
|
/**
|
|
* Fügt Textzeilen an die richtige Stelle ein
|
|
*/
|
|
function insertTextLines() {
|
|
var factureId = getFactureId();
|
|
if (!factureId) return;
|
|
|
|
debugLog('📝 Lade Textzeilen...');
|
|
|
|
$.get('/dolibarr/custom/subtotaltitle/ajax/get_textlines.php', {
|
|
facture_id: factureId
|
|
}, 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 = '<tr class="textline-row drag" data-textline-id="' + textline.id + '" data-line-order="' + textline.line_order + '">';
|
|
|
|
// Inhalt (colspan=10)
|
|
html += '<td colspan="10" style="padding:8px; font-style:italic; font-weight:bold;">';
|
|
html += textline.title;
|
|
html += '</td>';
|
|
|
|
// Edit (Spalte 11)
|
|
html += '<td class="linecoledit center">';
|
|
html += '<a href="#" onclick="editTextLine(' + textline.id + '); return false;" title="Bearbeiten">';
|
|
html += '<span class="fas fa-pencil-alt" style="color:#444;"></span></a>';
|
|
html += '</td>';
|
|
|
|
// Delete (Spalte 12)
|
|
html += '<td class="linecoldelete center">';
|
|
html += '<a href="#" onclick="deleteTextLine(' + textline.id + '); return false;" title="Löschen">';
|
|
html += '<span class="fas fa-trash pictodelete"></span></a>';
|
|
html += '</td>';
|
|
|
|
// Move (Spalte 13)
|
|
html += '<td class="linecolmove tdlineupdown center"></td>';
|
|
|
|
// Unlink (Spalte 14)
|
|
html += '<td class="linecolunlink"></td>';
|
|
|
|
html += '</tr>';
|
|
|
|
// 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 factureId = getFactureId();
|
|
if (!factureId) {
|
|
alert(lang.errorLoadingSections || 'Fehler: Keine Facture ID gefunden');
|
|
return;
|
|
}
|
|
|
|
debugLog('Erstelle Textzeile für Facture ' + factureId);
|
|
|
|
$.post('/dolibarr/custom/subtotaltitle/ajax/create_textline.php', {
|
|
facture_id: factureId,
|
|
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) {
|
|
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
|
var newText = prompt(lang.textlineContent || 'Text bearbeiten:');
|
|
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(
|
|
'<input type="checkbox" class="mass-delete-checkbox" data-line-id="' + lineId + '" style="margin-right:8px;">'
|
|
);
|
|
}
|
|
});
|
|
|
|
// Buttons NACH dem Massenlösch-Button einfügen
|
|
$('#btnMassDelete').after(
|
|
'<a id="btnMassDoDelete" class="butActionDelete" href="#" onclick="deleteMassSelected(); return false;" style="background:#c00;color:#fff;margin-left:5px;">Ausgewählte löschen</a>' +
|
|
'<a id="btnMassSelectAll" class="butAction" href="#" onclick="selectAllLines(); return false;">Alle auswählen</a>' +
|
|
'<a id="btnMassCancel" class="butAction" href="#" onclick="toggleMassDelete(); return false;">Abbrechen</a>'
|
|
);
|
|
|
|
// 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
|
|
var match = window.location.search.match(/id=(\d+)/);
|
|
return match ? match[1] : 0;
|
|
}
|
|
|
|
/**
|
|
* 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 + ')';
|
|
}
|