1784 lines
65 KiB
JavaScript
Executable file
1784 lines
65 KiB
JavaScript
Executable file
// DEBUG FLAG - true für Debug-Ausgaben, false für Produktiv
|
|
var SUBTOTAL_DEBUG = false;
|
|
|
|
// Fallback für AJAX-URL wenn nicht von PHP gesetzt (z.B. bei direktem JS-Include)
|
|
if (typeof subtotaltitleAjaxUrl === 'undefined') {
|
|
// Versuche URL aus aktuellem Script-Pfad abzuleiten
|
|
var scripts = document.getElementsByTagName('script');
|
|
for (var i = 0; i < scripts.length; i++) {
|
|
var src = scripts[i].src || '';
|
|
if (src.indexOf('subtotaltitle') !== -1 && src.indexOf('/js/') !== -1) {
|
|
// Pfad: .../subtotaltitle/js/subtotaltitle.js -> .../subtotaltitle/ajax/
|
|
var subtotaltitleAjaxUrl = src.replace(/\/js\/.*$/, '/ajax/');
|
|
break;
|
|
}
|
|
}
|
|
// Letzter Fallback
|
|
if (typeof subtotaltitleAjaxUrl === 'undefined') {
|
|
var subtotaltitleAjaxUrl = '/custom/subtotaltitle/ajax/';
|
|
}
|
|
}
|
|
|
|
function debugLog(message) {
|
|
if (SUBTOTAL_DEBUG) {
|
|
console.log(message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Zeigt einen Dolibarr-styled Bestätigungsdialog
|
|
* @param {string} title - Dialogtitel
|
|
* @param {string} content - Dialoginhalt (HTML erlaubt)
|
|
* @param {function} onConfirm - Callback bei Bestätigung
|
|
* @param {string} confirmLabel - Text für Bestätigen-Button (optional)
|
|
* @param {string} cancelLabel - Text für Abbrechen-Button (optional)
|
|
*/
|
|
function showConfirmDialog(title, content, onConfirm, confirmLabel, cancelLabel) {
|
|
confirmLabel = confirmLabel || 'Ja';
|
|
cancelLabel = cancelLabel || 'Abbrechen';
|
|
|
|
var dialogId = 'subtotal-confirm-dialog-' + Date.now();
|
|
|
|
// Entferne vorherige Dialoge
|
|
$('.subtotal-confirm-dialog').remove();
|
|
|
|
var $dialog = $('<div/>', {
|
|
id: dialogId,
|
|
'class': 'subtotal-confirm-dialog',
|
|
title: title
|
|
}).appendTo('body');
|
|
|
|
$dialog.html(content);
|
|
|
|
$dialog.dialog({
|
|
autoOpen: true,
|
|
modal: true,
|
|
width: 'auto',
|
|
minWidth: 350,
|
|
dialogClass: 'confirm-dialog-box',
|
|
buttons: [
|
|
{
|
|
text: confirmLabel,
|
|
'class': 'button-delete',
|
|
style: 'background:#c00;color:#fff;',
|
|
click: function() {
|
|
$(this).dialog('close');
|
|
if (typeof onConfirm === 'function') {
|
|
onConfirm();
|
|
}
|
|
}
|
|
},
|
|
{
|
|
text: cancelLabel,
|
|
'class': 'button-cancel',
|
|
click: function() {
|
|
$(this).dialog('close');
|
|
}
|
|
}
|
|
],
|
|
close: function() {
|
|
$(this).dialog('destroy').remove();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Zeigt eine Dolibarr-styled Fehlermeldung
|
|
* @param {string} message - Fehlermeldung
|
|
*/
|
|
function showErrorAlert(message) {
|
|
var dialogId = 'subtotal-error-dialog-' + Date.now();
|
|
|
|
$('.subtotal-error-dialog').remove();
|
|
|
|
var $dialog = $('<div/>', {
|
|
id: dialogId,
|
|
'class': 'subtotal-error-dialog',
|
|
title: 'Fehler'
|
|
}).appendTo('body');
|
|
|
|
$dialog.html('<div class="error" style="padding:10px;">' + message + '</div>');
|
|
|
|
$dialog.dialog({
|
|
autoOpen: true,
|
|
modal: true,
|
|
width: 'auto',
|
|
minWidth: 300,
|
|
dialogClass: 'confirm-dialog-box',
|
|
buttons: [
|
|
{
|
|
text: 'OK',
|
|
click: function() {
|
|
$(this).dialog('close');
|
|
}
|
|
}
|
|
],
|
|
close: function() {
|
|
$(this).dialog('destroy').remove();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Zeigt einen Dolibarr-styled Eingabedialog
|
|
* @param {string} title - Dialogtitel
|
|
* @param {string} label - Label für das Eingabefeld
|
|
* @param {string} defaultValue - Vorausgefüllter Wert (optional)
|
|
* @param {function} onConfirm - Callback bei Bestätigung, erhält den eingegebenen Wert
|
|
* @param {string} confirmLabel - Text für Bestätigen-Button (optional)
|
|
* @param {string} cancelLabel - Text für Abbrechen-Button (optional)
|
|
*/
|
|
function showInputDialog(title, label, defaultValue, onConfirm, confirmLabel, cancelLabel) {
|
|
confirmLabel = confirmLabel || 'OK';
|
|
cancelLabel = cancelLabel || 'Abbrechen';
|
|
defaultValue = defaultValue || '';
|
|
|
|
var dialogId = 'subtotal-input-dialog-' + Date.now();
|
|
var inputId = 'subtotal-input-' + Date.now();
|
|
|
|
// Entferne vorherige Dialoge
|
|
$('.subtotal-input-dialog').remove();
|
|
|
|
var $dialog = $('<div/>', {
|
|
id: dialogId,
|
|
'class': 'subtotal-input-dialog',
|
|
title: title
|
|
}).appendTo('body');
|
|
|
|
var content = '<div style="padding:10px 0;">';
|
|
content += '<label for="' + inputId + '" style="display:block;margin-bottom:8px;">' + label + '</label>';
|
|
content += '<input type="text" id="' + inputId + '" class="flat minwidth300" style="width:100%;padding:8px;" value="' + defaultValue.replace(/"/g, '"') + '">';
|
|
content += '</div>';
|
|
|
|
$dialog.html(content);
|
|
|
|
$dialog.dialog({
|
|
autoOpen: true,
|
|
modal: true,
|
|
width: 400,
|
|
dialogClass: 'confirm-dialog-box',
|
|
open: function() {
|
|
// Fokus auf Eingabefeld setzen
|
|
var $input = $('#' + inputId);
|
|
$input.focus().select();
|
|
|
|
// Enter-Taste zum Bestätigen
|
|
$input.on('keypress', function(e) {
|
|
if (e.which === 13) {
|
|
var value = $(this).val().trim();
|
|
if (value) {
|
|
$dialog.dialog('close');
|
|
if (typeof onConfirm === 'function') {
|
|
onConfirm(value);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
buttons: [
|
|
{
|
|
text: confirmLabel,
|
|
'class': 'button-save',
|
|
style: 'background:#0077b3;color:#fff;',
|
|
click: function() {
|
|
var value = $('#' + inputId).val().trim();
|
|
if (value) {
|
|
$(this).dialog('close');
|
|
if (typeof onConfirm === 'function') {
|
|
onConfirm(value);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
text: cancelLabel,
|
|
'class': 'button-cancel',
|
|
click: function() {
|
|
$(this).dialog('close');
|
|
}
|
|
}
|
|
],
|
|
close: function() {
|
|
$(this).dialog('destroy').remove();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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(subtotaltitleAjaxUrl + '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?
|
|
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) {
|
|
// Cleanup verwaister Subtotals (wo show_subtotal=0)
|
|
cleanupOrphanedSubtotals();
|
|
|
|
// Prüfe ob Dokument im Entwurfsstatus ist
|
|
if (typeof subtotalTitleIsDraft !== 'undefined' && subtotalTitleIsDraft === true) {
|
|
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');
|
|
}
|
|
} else {
|
|
debugLog('⚠️ Dokument nicht im Entwurfsstatus - Button wird nicht angezeigt');
|
|
}
|
|
|
|
initDragAndDrop();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Holt die Dokument-ID und Typ aus der URL
|
|
*/
|
|
function getDocumentInfo() {
|
|
var url = window.location.href;
|
|
|
|
// Versuche ID aus URL-Parametern zu extrahieren (mehrere Formate)
|
|
var id = null;
|
|
|
|
// Format 1: ?id=123 oder &id=123
|
|
var match = url.match(/[?&]id=(\d+)/);
|
|
if (match) {
|
|
id = match[1];
|
|
}
|
|
|
|
// Format 2: Aus URLSearchParams (robuster)
|
|
if (!id) {
|
|
try {
|
|
var params = new URLSearchParams(window.location.search);
|
|
id = params.get('id');
|
|
} catch(e) {
|
|
// URLSearchParams nicht verfügbar in alten Browsern
|
|
}
|
|
}
|
|
|
|
// Format 3: Aus dem DOM (falls ID dort gespeichert ist)
|
|
if (!id) {
|
|
// Versuche aus versteckten Feldern oder data-Attributen
|
|
var $idInput = $('input[name="id"], input[name="facid"], input[name="socid"]').first();
|
|
if ($idInput.length > 0) {
|
|
id = $idInput.val();
|
|
}
|
|
}
|
|
|
|
// Format 4: Aus der Seite selbst (Dolibarr zeigt oft die ID an)
|
|
if (!id) {
|
|
var $refDiv = $('.refid, .ref').first();
|
|
if ($refDiv.length > 0) {
|
|
var refMatch = $refDiv.text().match(/\((\d+)\)/);
|
|
if (refMatch) {
|
|
id = refMatch[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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';
|
|
}
|
|
|
|
debugLog('getDocumentInfo: id=' + id + ', type=' + docType + ', url=' + url);
|
|
|
|
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 docInfo = getDocumentInfo();
|
|
if (!docInfo.id) {
|
|
showErrorAlert(lang.errorLoadingSections || 'Fehler: Keine Dokument-ID gefunden');
|
|
return;
|
|
}
|
|
|
|
showInputDialog(
|
|
lang.sectionCreate || 'Positionsgruppe erstellen',
|
|
lang.sectionName || 'Name der Positionsgruppe:',
|
|
'',
|
|
function(title) {
|
|
debugLog('Erstelle Section: ' + title + ' für ' + docInfo.type + ' ID ' + docInfo.id);
|
|
|
|
$.post(subtotaltitleAjaxUrl + '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 {
|
|
showErrorAlert((lang.errorSavingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
|
}
|
|
}, 'json').fail(function(xhr, status, error) {
|
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
|
showErrorAlert((lang.errorSavingSection || 'Fehler beim Erstellen') + ': ' + error);
|
|
});
|
|
},
|
|
lang.buttonSave || 'Erstellen',
|
|
lang.buttonCancel || 'Abbrechen'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Verschiebt eine Section nach oben/unten
|
|
*/
|
|
function moveSection(sectionId, direction) {
|
|
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
|
var docInfo = getDocumentInfo();
|
|
debugLog('🔄 Verschiebe Section ' + sectionId + ' ' + direction + ' (docType: ' + docInfo.type + ')');
|
|
|
|
$.post(subtotaltitleAjaxUrl + 'move_section.php', {
|
|
section_id: sectionId,
|
|
direction: direction,
|
|
document_type: docInfo.type
|
|
}, function(response) {
|
|
debugLog('Move response: ' + JSON.stringify(response));
|
|
if (response.success) {
|
|
window.location.href = window.location.pathname + window.location.search;
|
|
} else {
|
|
// "Already at top/bottom" ist kein echter Fehler, sondern eine Info
|
|
if (response.error === 'Already at top' || response.error === 'Already at bottom') {
|
|
debugLog('Section ist bereits am ' + (response.error === 'Already at top' ? 'Anfang' : 'Ende'));
|
|
// Kein Alert nötig - einfach nichts tun
|
|
} 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 : {};
|
|
|
|
showInputDialog(
|
|
lang.buttonEdit || 'Positionsgruppe umbenennen',
|
|
lang.sectionName || 'Name der Positionsgruppe:',
|
|
currentTitle || '',
|
|
function(newTitle) {
|
|
debugLog('✏️ Benenne Section ' + sectionId + ' um zu: ' + newTitle);
|
|
|
|
$.post(subtotaltitleAjaxUrl + '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 {
|
|
showErrorAlert((lang.errorSavingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
|
}
|
|
}, 'json').fail(function(xhr, status, error) {
|
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
|
showErrorAlert((lang.errorSavingSection || 'Fehler beim Umbenennen') + ': ' + error);
|
|
});
|
|
},
|
|
lang.buttonSave || 'Speichern',
|
|
lang.buttonCancel || 'Abbrechen'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Löscht eine leere Section
|
|
*/
|
|
function deleteSection(sectionId) {
|
|
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
|
|
|
showConfirmDialog(
|
|
'Positionsgruppe löschen',
|
|
lang.confirmDeleteSection || 'Leere Positionsgruppe löschen?',
|
|
function() {
|
|
debugLog('🗑️ Lösche leere Section ' + sectionId);
|
|
|
|
$.post(subtotaltitleAjaxUrl + 'delete_section.php', {
|
|
section_id: sectionId,
|
|
force: 0,
|
|
document_type: getDocumentType()
|
|
}, function(response) {
|
|
debugLog('Delete response: ' + JSON.stringify(response));
|
|
if (response.success) {
|
|
window.location.href = window.location.pathname + window.location.search;
|
|
} else {
|
|
showErrorAlert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
|
}
|
|
}, 'json').fail(function(xhr, status, error) {
|
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
|
showErrorAlert((lang.errorDeletingSection || 'Fehler beim Löschen') + ': ' + error);
|
|
});
|
|
},
|
|
'Ja, löschen',
|
|
'Abbrechen'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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 || 'Wollen Sie wirklich die Positionsgruppe UND alle %s enthaltenen Produkte löschen?').replace('%s', productCount);
|
|
|
|
showConfirmDialog(
|
|
'Achtung - Positionsgruppe löschen',
|
|
'<div style="color:#c00;"><strong>WARNUNG!</strong></div><br>' + msg1 + '<br><br><em>Diese Aktion kann nicht rückgängig gemacht werden!</em>',
|
|
function() {
|
|
var msg2 = (lang.confirmDeleteSectionForce2 || 'Sind Sie WIRKLICH sicher? %s Produkte werden unwiderruflich gelöscht!').replace('%s', productCount);
|
|
|
|
showConfirmDialog(
|
|
'Letzte Warnung',
|
|
'<div style="color:#c00;font-weight:bold;">' + msg2 + '</div>',
|
|
function() {
|
|
debugLog('Force-Delete Section ' + sectionId + ' mit Produkten: ' + JSON.stringify(productIds));
|
|
|
|
$.post(subtotaltitleAjaxUrl + 'delete_section.php', {
|
|
section_id: sectionId,
|
|
force: 1,
|
|
product_ids: JSON.stringify(productIds),
|
|
document_type: getDocumentType()
|
|
}, function(response) {
|
|
debugLog('Force-Delete response: ' + JSON.stringify(response));
|
|
if (response.success) {
|
|
window.location.href = window.location.pathname + window.location.search;
|
|
} else {
|
|
showErrorAlert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
|
}
|
|
}, 'json').fail(function(xhr, status, error) {
|
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
|
showErrorAlert((lang.errorDeletingSection || 'Fehler beim Löschen') + ': ' + error);
|
|
});
|
|
},
|
|
'Endgültig löschen',
|
|
'Abbrechen'
|
|
);
|
|
},
|
|
'Ja, löschen',
|
|
'Abbrechen'
|
|
);
|
|
}
|
|
/**
|
|
* 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(subtotaltitleAjaxUrl + '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 = '<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-', '');
|
|
|
|
// 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({
|
|
type: 'product',
|
|
id: productId,
|
|
order: order,
|
|
parent_section: assignToSection
|
|
});
|
|
debugLog(' ' + order + '. 📦 Produkt #' + productId + ' → ' + (assignToSection ? 'Section ' + assignToSection : 'FREI'));
|
|
order++;
|
|
}
|
|
else if ($row.hasClass('textline-row')) {
|
|
var textlineId = $row.attr('data-textline-id');
|
|
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({
|
|
type: 'text',
|
|
id: textlineId,
|
|
order: order,
|
|
parent_section: textAssignToSection
|
|
});
|
|
debugLog(' ' + order + '. 📝 Text #' + textlineId + ' → ' + (textAssignToSection ? 'Section ' + textAssignToSection : 'FREI'));
|
|
order++;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (updates.length === 0) {
|
|
debugLog('⚠️ Keine Updates gefunden!');
|
|
return;
|
|
}
|
|
|
|
debugLog('🚀 Sende ' + updates.length + ' Updates...');
|
|
|
|
var docInfo = getDocumentInfo();
|
|
$.post(subtotaltitleAjaxUrl + '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(subtotaltitleAjaxUrl + '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');
|
|
|
|
// Robustere Prüfung: Hat gültige Section-ID?
|
|
var hasValidSection = parentSection && parentSection !== 'null' && parentSection !== 'undefined' && parentSection !== '' && parseInt(parentSection) > 0;
|
|
|
|
debugLog('🔗 Zeile ' + lineId + ': parent_section="' + parentSection + '" → hasValidSection=' + hasValidSection);
|
|
|
|
if (hasValidSection) {
|
|
// 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 → Link-Button (zu passender Section hinzufügen)
|
|
$row.append('<td class="linecolunlink center"><a href="#" onclick="linkToNearestSection(' + lineId + '); return false;" title="Zu Positionsgruppe hinzufügen"><span class="fas fa-link" style="color:#888;"></span></a></td>');
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 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 den PHP-Scripts die data-parent-section setzen
|
|
// Timeout erhöht auf 1500ms um sicherzustellen dass alle Attribute gesetzt sind
|
|
setTimeout(addUnlinkColumn, 1500);
|
|
|
|
// 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
|
|
* Nach der Zuweisung wird die Darstellung per JavaScript aktualisiert (KEIN zweiter Reload)
|
|
*/
|
|
function assignLastProductToSection(sectionId, factureId) {
|
|
var docInfo = getDocumentInfo();
|
|
debugLog('🎯 Weise neustes Produkt zu Section ' + sectionId + ' zu (docType: ' + docInfo.type + ')...');
|
|
|
|
$.post(subtotaltitleAjaxUrl + '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');
|
|
|
|
// KEIN zweiter Reload - stattdessen Zeile per JavaScript aktualisieren
|
|
var $productRow = $('#row-' + response.product_id);
|
|
if ($productRow.length > 0) {
|
|
// Setze data-parent-section Attribut
|
|
$productRow.attr('data-parent-section', sectionId);
|
|
|
|
// Verschiebe die Zeile an die richtige Position (nach der Section)
|
|
var $sectionRow = $('tr.section-header[data-section-id="' + sectionId + '"]');
|
|
if ($sectionRow.length > 0) {
|
|
// Finde das letzte Element dieser Section
|
|
var $lastInSection = $('tr[data-parent-section="' + sectionId + '"]').last();
|
|
if ($lastInSection.length > 0 && $lastInSection[0] !== $productRow[0]) {
|
|
$lastInSection.after($productRow);
|
|
} else if ($lastInSection.length === 0) {
|
|
// Erstes Produkt in der Section
|
|
$sectionRow.after($productRow);
|
|
}
|
|
}
|
|
|
|
// Färbung aktualisieren
|
|
colorSections();
|
|
|
|
debugLog('✅ Zeile per JavaScript aktualisiert - kein Reload nötig');
|
|
} else {
|
|
// Fallback: Wenn Zeile nicht gefunden, doch reloaden
|
|
debugLog('⚠️ Zeile nicht gefunden, Fallback: Reload');
|
|
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();
|
|
|
|
// DEAKTIVIERT: JavaScript-Subtotal verursacht Duplikate
|
|
// insertLastSectionSubtotal();
|
|
|
|
debugLog('✅ Collapse initialisiert');
|
|
}
|
|
|
|
/**
|
|
* 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 insertLastSectionSubtotal() {
|
|
debugLog('🔢 Prüfe Subtotal für letzte Section...');
|
|
|
|
var $allSections = $('tr.section-header');
|
|
if ($allSections.length === 0) {
|
|
debugLog(' Keine Sections vorhanden');
|
|
return;
|
|
}
|
|
|
|
// Nur die LETZTE Section prüfen
|
|
var $lastHeader = $allSections.last();
|
|
var sectionId = $lastHeader.attr('data-section-id');
|
|
var $checkbox = $lastHeader.find('.subtotal-toggle');
|
|
|
|
// Nur wenn Checkbox existiert UND aktiviert ist
|
|
if (!$checkbox.length || !$checkbox.is(':checked')) {
|
|
debugLog(' Letzte Section ' + sectionId + ': Subtotal nicht aktiviert');
|
|
return;
|
|
}
|
|
|
|
// Finde Produkte dieser Section
|
|
var $products = $('tr[data-parent-section="' + sectionId + '"]');
|
|
if ($products.length === 0) {
|
|
debugLog(' Letzte Section ' + sectionId + ': Keine Produkte');
|
|
return;
|
|
}
|
|
|
|
var $lastProduct = $products.last();
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Prüfe auch nächste Zeile nach letztem Produkt
|
|
var $nextRow = $lastProduct.next('tr');
|
|
if ($nextRow.hasClass('subtotal-row') || $nextRow.find('td:contains("Zwischensumme")').length > 0) {
|
|
debugLog(' Letzte Section ' + sectionId + ': Subtotal direkt nach Produkt ✓');
|
|
return;
|
|
}
|
|
|
|
debugLog(' Letzte Section ' + sectionId + ': Subtotal fehlt in DOM, hole Daten...');
|
|
|
|
// Berechne Summe lokal
|
|
var sum = 0;
|
|
$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;
|
|
}
|
|
}
|
|
});
|
|
|
|
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: subtotaltitleAjaxUrl + '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() {
|
|
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(subtotaltitleAjaxUrl + '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) {
|
|
if (event) event.stopPropagation();
|
|
|
|
var show = checkbox.checked;
|
|
var docType = getDocumentType();
|
|
|
|
debugLog('🔢 Toggle Subtotal für Section ' + sectionId + ': ' + show + ', docType: ' + docType);
|
|
|
|
$.ajax({
|
|
url: subtotaltitleAjaxUrl + 'toggle_subtotal.php',
|
|
method: 'POST',
|
|
data: {
|
|
section_id: sectionId,
|
|
show: show ? 1 : 0,
|
|
document_type: docType
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
debugLog('Subtotal Response: ' + JSON.stringify(response));
|
|
if (response.success && response.reload) {
|
|
safeReload();
|
|
}
|
|
},
|
|
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') {
|
|
|
|
// Grip-Hintergrundbild für alle tdlineupdown Elemente setzen (wie Dolibarr es macht)
|
|
// Das ist nötig für dynamisch hinzugefügte Zeilen
|
|
// Verwende die von PHP bereitgestellte URL (identisch zu Dolibarr's ajaxrow.tpl.php)
|
|
if (typeof subtotalTitleGripUrl !== 'undefined') {
|
|
$(".tdlineupdown").css("background-image", 'url(' + subtotalTitleGripUrl + ')');
|
|
$(".tdlineupdown").css("background-repeat", "no-repeat");
|
|
$(".tdlineupdown").css("background-position", "center center");
|
|
debugLog('🖼️ Grip-Bild gesetzt: ' + subtotalTitleGripUrl);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
});
|
|
|
|
// Hover-Effekt für Drag-Handle (wie Dolibarr es macht)
|
|
$(".tdlineupdown").off("mouseenter mouseleave").hover(
|
|
function() { $(this).addClass('showDragHandle'); },
|
|
function() { $(this).removeClass('showDragHandle'); }
|
|
);
|
|
|
|
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 docInfo = getDocumentInfo();
|
|
if (!docInfo.id) {
|
|
showErrorAlert(lang.errorLoadingSections || 'Fehler: Keine Dokument-ID gefunden');
|
|
return;
|
|
}
|
|
|
|
showInputDialog(
|
|
lang.buttonCreateTextline || 'Textzeile erstellen',
|
|
lang.textlineContent || 'Text eingeben:',
|
|
'',
|
|
function(text) {
|
|
debugLog('Erstelle Textzeile für ' + docInfo.type + ' ID ' + docInfo.id);
|
|
|
|
$.post(subtotaltitleAjaxUrl + '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 {
|
|
showErrorAlert((lang.errorSavingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
|
}
|
|
}, 'json').fail(function(xhr, status, error) {
|
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
|
showErrorAlert((lang.errorSavingTextline || 'Fehler beim Erstellen') + ': ' + error);
|
|
});
|
|
},
|
|
lang.buttonSave || 'Erstellen',
|
|
lang.buttonCancel || 'Abbrechen'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Textzeile bearbeiten
|
|
*/
|
|
function editTextLine(textlineId, currentText) {
|
|
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
|
|
|
showInputDialog(
|
|
lang.buttonEdit || 'Textzeile bearbeiten',
|
|
lang.textlineContent || 'Text:',
|
|
currentText || '',
|
|
function(newText) {
|
|
debugLog('✏️ Bearbeite Textzeile ' + textlineId);
|
|
|
|
$.post(subtotaltitleAjaxUrl + '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 {
|
|
showErrorAlert((lang.errorSavingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
|
}
|
|
}, 'json').fail(function(xhr, status, error) {
|
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
|
showErrorAlert((lang.errorSavingTextline || 'Fehler beim Bearbeiten') + ': ' + error);
|
|
});
|
|
},
|
|
lang.buttonSave || 'Speichern',
|
|
lang.buttonCancel || 'Abbrechen'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Textzeile löschen
|
|
*/
|
|
function deleteTextLine(textlineId) {
|
|
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
|
|
|
showConfirmDialog(
|
|
'Textzeile löschen',
|
|
lang.confirmDeleteTextline || 'Textzeile wirklich löschen?',
|
|
function() {
|
|
debugLog('Lösche Textzeile ' + textlineId);
|
|
|
|
$.post(subtotaltitleAjaxUrl + 'delete_textline.php', {
|
|
textline_id: textlineId,
|
|
document_type: getDocumentType()
|
|
}, function(response) {
|
|
debugLog('Delete response: ' + JSON.stringify(response));
|
|
if (response.success) {
|
|
window.location.href = window.location.pathname + window.location.search;
|
|
} else {
|
|
showErrorAlert((lang.errorDeletingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
|
}
|
|
}, 'json').fail(function(xhr, status, error) {
|
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
|
showErrorAlert((lang.errorDeletingTextline || 'Fehler beim Löschen') + ': ' + error);
|
|
});
|
|
},
|
|
'Ja, löschen',
|
|
'Abbrechen'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Entfernt ein Produkt aus seiner Section
|
|
*/
|
|
function removeFromSection(productId) {
|
|
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
|
|
|
showConfirmDialog(
|
|
'Aus Positionsgruppe entfernen',
|
|
lang.confirmRemoveFromSection || 'Produkt aus Positionsgruppe entfernen?',
|
|
function() {
|
|
var docType = getDocumentType();
|
|
debugLog('Entferne Produkt ' + productId + ' aus Section (docType: ' + docType + ')');
|
|
|
|
$.post(subtotaltitleAjaxUrl + 'remove_from_section.php', {
|
|
product_id: productId,
|
|
document_type: docType
|
|
}, function(response) {
|
|
debugLog('Remove response: ' + JSON.stringify(response));
|
|
if (response.success) {
|
|
safeReload();
|
|
} else {
|
|
showErrorAlert((lang.errorReordering || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
|
}
|
|
}, 'json').fail(function(xhr, status, error) {
|
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
|
showErrorAlert((lang.errorReordering || 'Fehler') + ': ' + error);
|
|
});
|
|
},
|
|
'Ja, entfernen',
|
|
'Abbrechen'
|
|
);
|
|
}
|
|
|
|
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 (Ausgewählte löschen ganz rechts)
|
|
$('#btnMassDelete').after(
|
|
'<a id="btnMassCancel" class="butAction" href="#" onclick="toggleMassDelete(); return false;">Abbrechen</a>' +
|
|
'<a id="btnMassSelectAll" class="butAction" href="#" onclick="selectAllLines(); return false;">Alle auswählen</a>' +
|
|
'<a id="btnMassDoDelete" class="butActionDelete" href="#" onclick="deleteMassSelected(); return false;" style="background:#c00;color:#fff;">Ausgewählte löschen</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) {
|
|
showErrorAlert(lang.noLinesSelected || 'Keine Zeilen ausgewählt!');
|
|
return;
|
|
}
|
|
|
|
var msg1 = (lang.confirmDeleteLines || 'Wirklich %s Zeilen löschen?').replace('%s', selectedIds.length);
|
|
|
|
showConfirmDialog('Zeilen löschen', msg1, function() {
|
|
// Erste Bestätigung OK - zweite Warnung zeigen
|
|
var msg2 = (lang.confirmDeleteLinesWarning || 'LETZTE WARNUNG: %s Zeilen werden UNWIDERRUFLICH gelöscht!').replace('%s', selectedIds.length);
|
|
|
|
showConfirmDialog('Letzte Warnung', '<div style="color:#c00;font-weight:bold;">' + msg2 + '</div>', function() {
|
|
// Zweite Bestätigung OK - jetzt löschen
|
|
$.post(subtotaltitleAjaxUrl + 'mass_delete.php', {
|
|
line_ids: JSON.stringify(selectedIds),
|
|
facture_id: getFactureId(),
|
|
document_type: getDocumentType()
|
|
}, function(response) {
|
|
if (response.success) {
|
|
safeReload();
|
|
} else {
|
|
showErrorAlert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannt'));
|
|
}
|
|
}, 'json');
|
|
}, 'Endgültig löschen', 'Abbrechen');
|
|
}, 'Ja, löschen', 'Abbrechen');
|
|
}
|
|
|
|
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 + ')';
|
|
}
|
|
|
|
/**
|
|
* Verknüpft ein freies Produkt mit der passenden Section
|
|
* - Produkte VOR allen Sections → erste Section
|
|
* - Produkte NACH allen Sections → letzte Section
|
|
* - Produkte ZWISCHEN Sections → Section in deren Bereich
|
|
*/
|
|
function linkToNearestSection(lineId) {
|
|
debugLog('🔗 linkToNearestSection: line=' + lineId);
|
|
|
|
var docInfo = getDocumentInfo();
|
|
if (!docInfo || !docInfo.id) {
|
|
alert('Fehler: Dokument-Kontext nicht gefunden');
|
|
return;
|
|
}
|
|
|
|
// Hole line_order des Produkts
|
|
var $productRow = $('tr[id*="' + lineId + '"]');
|
|
var productLineOrder = parseInt($productRow.attr('data-line-order'));
|
|
|
|
if (!productLineOrder) {
|
|
alert('Fehler: line_order nicht gefunden');
|
|
return;
|
|
}
|
|
|
|
debugLog(' Produkt line_order: ' + productLineOrder);
|
|
|
|
// Sammle alle Sections mit ihrer line_order
|
|
var sections = [];
|
|
$('tr.section-header').each(function() {
|
|
var sectionId = $(this).attr('data-section-id');
|
|
var sectionLineOrder = parseInt($(this).attr('data-line-order'));
|
|
var sectionTitle = $(this).attr('data-section-title');
|
|
|
|
if (sectionId && sectionLineOrder) {
|
|
sections.push({
|
|
id: parseInt(sectionId),
|
|
lineOrder: sectionLineOrder,
|
|
title: sectionTitle
|
|
});
|
|
}
|
|
});
|
|
|
|
if (sections.length === 0) {
|
|
alert('Keine Positionsgruppen gefunden');
|
|
return;
|
|
}
|
|
|
|
// Sortiere Sections nach line_order
|
|
sections.sort(function(a, b) { return a.lineOrder - b.lineOrder; });
|
|
|
|
debugLog(' Sections: ' + sections.map(function(s) { return s.id + ':' + s.lineOrder; }).join(', '));
|
|
|
|
// Finde passende Section
|
|
var targetSection = null;
|
|
var firstSection = sections[0];
|
|
var lastSection = sections[sections.length - 1];
|
|
|
|
if (productLineOrder < firstSection.lineOrder) {
|
|
// VOR allen Sections → erste Section
|
|
targetSection = firstSection;
|
|
debugLog(' → VOR allen Sections → erste Section #' + targetSection.id);
|
|
} else if (productLineOrder > lastSection.lineOrder) {
|
|
// NACH allen Sections → letzte Section
|
|
targetSection = lastSection;
|
|
debugLog(' → NACH allen Sections → letzte Section #' + targetSection.id);
|
|
} else {
|
|
// ZWISCHEN Sections → finde Section in deren Bereich das Produkt liegt
|
|
for (var i = 0; i < sections.length - 1; i++) {
|
|
var currentSection = sections[i];
|
|
var nextSection = sections[i + 1];
|
|
|
|
if (productLineOrder > currentSection.lineOrder && productLineOrder < nextSection.lineOrder) {
|
|
// Liegt zwischen currentSection und nextSection
|
|
// Regel: Nimm die Section, die vor dem Produkt liegt
|
|
targetSection = currentSection;
|
|
debugLog(' → ZWISCHEN Section #' + currentSection.id + ' und #' + nextSection.id + ' → nehme #' + targetSection.id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Falls nicht gefunden (sollte nicht passieren), nimm letzte Section
|
|
if (!targetSection) {
|
|
targetSection = lastSection;
|
|
debugLog(' → Fallback → letzte Section #' + targetSection.id);
|
|
}
|
|
}
|
|
|
|
if (!targetSection) {
|
|
showErrorAlert('Keine passende Positionsgruppe gefunden');
|
|
return;
|
|
}
|
|
|
|
// Bestätigung
|
|
showConfirmDialog(
|
|
'Zur Positionsgruppe hinzufügen',
|
|
'Produkt zur Positionsgruppe "' + targetSection.title + '" hinzufügen?',
|
|
function() {
|
|
debugLog(' AJAX Call: add_to_section.php');
|
|
|
|
// AJAX Call zum Backend
|
|
$.post(subtotaltitleAjaxUrl + 'add_to_section.php', {
|
|
line_id: lineId,
|
|
section_id: targetSection.id,
|
|
document_id: docInfo.id,
|
|
document_type: docInfo.type
|
|
}, function(response) {
|
|
debugLog(' Response: ' + JSON.stringify(response));
|
|
|
|
if (response.success) {
|
|
// safeReload statt reload() um POST-Warnung zu vermeiden
|
|
safeReload();
|
|
} else {
|
|
showErrorAlert('Fehler: ' + (response.error || 'Unbekannter Fehler'));
|
|
}
|
|
}, 'json').fail(function(xhr, status, error) {
|
|
console.error('AJAX Fehler:', status, error);
|
|
console.error('Response:', xhr.responseText);
|
|
showErrorAlert('Fehler beim Verknüpfen: ' + xhr.responseText);
|
|
});
|
|
},
|
|
'Ja, hinzufügen',
|
|
'Abbrechen'
|
|
);
|
|
}
|