Fehler beseitig Verwaiste Zeilen über Button entfernen

This commit is contained in:
Eduard Wisch 2026-02-03 13:17:28 +01:00
parent 458f393612
commit ea2609c66e
18 changed files with 164 additions and 59 deletions

0
.claude/settings.json Normal file → Executable file
View file

0
MIGRATION_MULTITYPE.md Normal file → Executable file
View file

0
ajax/add_to_section.php Normal file → Executable file
View file

0
ajax/check_subtotal.php Normal file → Executable file
View file

0
ajax/cleanup_subtotals.php Normal file → Executable file
View file

0
ajax/fix_section_hierarchy.php Normal file → Executable file
View file

0
ajax/fix_sections.php Normal file → Executable file
View file

0
ajax/repair_missing_subtotals.php Normal file → Executable file
View file

View file

@ -312,6 +312,80 @@ if ($action == 'add') {
echo json_encode(array('success' => true, 'total_ht' => $total_ht));
} elseif ($action == 'remove_all') {
// ========== ALLE SPEZIALZEILEN UND VERWAISTE EINTRÄGE ENTFERNEN ==========
// document_id wird benötigt
$document_id = GETPOST('document_id', 'int');
if (!$document_id) {
echo json_encode(array('success' => false, 'error' => 'Missing document_id'));
exit;
}
$removed_count = 0;
$orphan_count = 0;
// 1. Entferne ALLE Einträge mit special_code 100, 101, 102 aus der Detail-Tabelle
// (unabhängig davon ob sie noch in der Manager-Tabelle existieren)
$sql_delete_all = "DELETE FROM ".MAIN_DB_PREFIX.$tables['lines_table'];
$sql_delete_all .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
$sql_delete_all .= " AND special_code IN (100, 101, 102)";
subtotaltitle_debug_log('🗑️ Remove ALL special lines: '.$sql_delete_all);
if ($db->query($sql_delete_all)) {
$removed_count = $db->affected_rows($db->query("SELECT ROW_COUNT()"));
// Fallback: Zähle manuell wenn affected_rows nicht funktioniert
if ($removed_count === 0) {
// Zähle vorher
$sql_count = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX.$tables['lines_table'];
$sql_count .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
$sql_count .= " AND special_code IN (100, 101, 102)";
// Da wir schon gelöscht haben, ist es jetzt 0
$removed_count = -1; // Unbekannt, aber erfolgreich
}
}
// 2. Setze in_facturedet und fk_*det auf NULL für alle Manager-Einträge dieses Dokuments
$sql_reset = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
$sql_reset .= " SET ".$tables['fk_line']." = NULL, in_facturedet = 0";
$sql_reset .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
$sql_reset .= " AND document_type = '".$db->escape($docType)."'";
$sql_reset .= " AND line_type IN ('section', 'text', 'subtotal')";
subtotaltitle_debug_log('🔄 Reset manager entries: '.$sql_reset);
$db->query($sql_reset);
// 3. Normalisiere die rang-Werte (schließe Lücken)
$sql_reorder = "SET @r = 0; UPDATE ".MAIN_DB_PREFIX.$tables['lines_table'];
$sql_reorder .= " SET rang = (@r := @r + 1)";
$sql_reorder .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
$sql_reorder .= " ORDER BY rang";
// MySQL erlaubt kein SET in einer Anweisung mit UPDATE, also manuell:
$sql_get_lines = "SELECT rowid FROM ".MAIN_DB_PREFIX.$tables['lines_table'];
$sql_get_lines .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
$sql_get_lines .= " ORDER BY rang";
$res_lines = $db->query($sql_get_lines);
$new_rang = 1;
while ($obj = $db->fetch_object($res_lines)) {
$sql_upd_rang = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table'];
$sql_upd_rang .= " SET rang = ".(int)$new_rang;
$sql_upd_rang .= " WHERE rowid = ".(int)$obj->rowid;
$db->query($sql_upd_rang);
$new_rang++;
}
subtotaltitle_debug_log('✅ Remove ALL completed: removed='.$removed_count);
echo json_encode(array(
'success' => true,
'removed' => $removed_count,
'orphans_cleaned' => $orphan_count,
'message' => 'Alle Spezialzeilen wurden entfernt'
));
} else {
echo json_encode(array('success' => false, 'error' => 'Unknown action'));
}

0
class/DocumentTypeHelper.class.php Normal file → Executable file
View file

View file

@ -1440,10 +1440,25 @@ class ActionsSubtotalTitle extends CommonHookActions
$html .= ' $(\'form[name="addproduct"]\').append(\'<input type="hidden" name="section_id" id="section_id" value="">\');';
// Lade gespeicherte Auswahl aus sessionStorage (dokumentspezifisch)
$html .= ' var storageKey = \'subtotaltitle_section_\' + '.$document_id.';';
$html .= ' var savedSection = sessionStorage.getItem(storageKey);';
$html .= ' if (savedSection) {';
$html .= ' $(\'#section_id_dropdown\').val(savedSection);';
$html .= ' $(\'#section_id\').val(savedSection);';
$html .= ' console.log(\'[SubtotalTitle] Gespeicherte Section geladen:\', savedSection);';
$html .= ' }';
$html .= ' $(document).on(\'change\', \'#section_id_dropdown\', function() {';
$html .= ' var val = $(this).val();';
$html .= ' console.log(\'Section selected:\', val);';
$html .= ' $(\'#section_id\').val(val);';
// Speichere Auswahl in sessionStorage (dokumentspezifisch)
$html .= ' if (val) {';
$html .= ' sessionStorage.setItem(storageKey, val);';
$html .= ' } else {';
$html .= ' sessionStorage.removeItem(storageKey);';
$html .= ' }';
$html .= ' });';
$html .= '});';

0
debug_sections.php Normal file → Executable file
View file

0
img/grip.png Normal file → Executable file
View file

Before

Width:  |  Height:  |  Size: 90 B

After

Width:  |  Height:  |  Size: 90 B

View file

@ -345,8 +345,8 @@ function createNewSection() {
}
showInputDialog(
lang.sectionCreate || 'Positionsgruppe erstellen',
lang.sectionName || 'Name der Positionsgruppe:',
lang.sectionCreate || 'Produktgruppe erstellen',
lang.sectionName || 'Name der Produktgruppe:',
'',
function(title) {
debugLog('Erstelle Section: ' + title + ' für ' + docInfo.type + ' ID ' + docInfo.id);
@ -414,8 +414,8 @@ function renameSection(sectionId, currentTitle) {
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
showInputDialog(
lang.buttonEdit || 'Positionsgruppe umbenennen',
lang.sectionName || 'Name der Positionsgruppe:',
lang.sectionEdit || 'Produktgruppe umbenennen',
lang.sectionName || 'Name der Produktgruppe:',
currentTitle || '',
function(newTitle) {
debugLog('✏️ Benenne Section ' + sectionId + ' um zu: ' + newTitle);
@ -821,6 +821,33 @@ function addUnlinkColumn() {
var $table = $('#tablelines');
if (!$table.length) return;
// NUR auf Dokumentdetailseiten ausführen (card), NICHT auf Listen
// Prüfe ob wir auf einer gültigen Seite sind (invoicecard, propalcard, ordercard)
var url = window.location.href;
var isDocumentCard = (url.indexOf('/facture/card.php') !== -1 ||
url.indexOf('/propal/card.php') !== -1 ||
url.indexOf('/commande/card.php') !== -1);
// Zusätzliche Prüfung: Modul muss aktiv sein (subtotalTitleIsDraft wird von PHP gesetzt)
var isModuleActive = (typeof subtotalTitleIsDraft !== 'undefined');
if (!isDocumentCard) {
debugLog('🔗 Keine Dokumentdetailseite, Unlink-Spalte wird übersprungen');
return;
}
if (!isModuleActive) {
debugLog('🔗 SubtotalTitle Modul nicht aktiv auf dieser Seite, Unlink-Spalte wird übersprungen');
return;
}
// Prüfe ob mindestens eine Produktgruppe (Section) vorhanden ist
var hasSections = ($('tr.section-header').length > 0);
if (!hasSections) {
debugLog('🔗 Keine Produktgruppen vorhanden, Unlink-Spalte wird übersprungen');
return;
}
// Prüfe ob schon ausgeführt
if ($table.data('unlink-added')) {
debugLog('🔗 Unlink-Spalte bereits vorhanden, überspringe');
@ -828,7 +855,7 @@ function addUnlinkColumn() {
}
$table.data('unlink-added', true);
debugLog('🔗 Füge Unlink-Spalte hinzu...');
debugLog('🔗 Füge Unlink-Spalte hinzu (Sections vorhanden)...');
// THEAD: Leere Spalte hinzufügen
$table.find('thead tr').each(function() {

View file

@ -144,6 +144,9 @@ function syncAllToFacturedet() {
var errors = 0;
var docType = getDocumentTypeForSync();
// Zeige Loading-Hinweis
$('body').append('<div id="sync-loading" style="position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.3);z-index:9999;display:flex;align-items:center;justify-content:center;"><div style="background:#fff;padding:20px 40px;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,0.2);">Synchronisiere... Bitte warten.</div></div>');
$unchecked.each(function() {
var lineId = $(this).data('line-id');
var lineType = $(this).data('line-type');
@ -155,16 +158,16 @@ function syncAllToFacturedet() {
document_type: docType
}, function(response) {
done++;
if (response.success) {
updateSyncCheckbox(lineId, true);
} else {
if (!response.success) {
errors++;
}
if (done >= total) {
debugLog('✅ Sync abgeschlossen: ' + (total - errors) + ' erfolgreich, ' + errors + ' Fehler');
if (errors > 0) {
$('#sync-loading').remove();
showErrorAlert((total - errors) + ' von ' + total + ' Elementen hinzugefügt. ' + errors + ' Fehler aufgetreten.');
} else {
// Direkt reloaden ohne UI-Update
safeReload();
}
}
@ -181,56 +184,42 @@ function syncAllToFacturedet() {
/**
* Entfernt ALLE Sections/Textzeilen/Subtotals aus facturedet
* Inkl. verwaister Einträge die nicht mehr in der Manager-Tabelle existieren
*/
function removeAllFromFacturedet() {
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
var $checked = $('.sync-checkbox:checked');
var total = $checked.length;
if (total === 0) {
showErrorAlert(lang.noElementsInInvoice || 'Keine Elemente in der Rechnung vorhanden.');
return;
}
showConfirmDialog(
'Alle aus Rechnung entfernen',
(lang.confirmRemoveAll || 'ALLE Positionsgruppen-Elemente aus der Rechnung entfernen?') + '<br><br><em>Die Elemente bleiben in der Verwaltung erhalten.</em>',
(lang.confirmRemoveAll || 'ALLE Positionsgruppen-Elemente (Sections, Textzeilen, Zwischensummen) aus der Rechnung entfernen?') +
'<br><br><em>Inkl. verwaister Einträge. Die Elemente in der Verwaltung bleiben erhalten.</em>',
function() {
debugLog('📥 Remove ALL from facturedet...');
debugLog('📥 Remove ALL from facturedet (server-side)...');
var done = 0;
var errors = 0;
var docType = getDocumentTypeForSync();
var documentId = getFactureId();
$checked.each(function() {
var lineId = $(this).data('line-id');
var lineType = $(this).data('line-type');
if (!documentId) {
showErrorAlert('Fehler: Keine Dokument-ID gefunden');
return;
}
$.post(subtotaltitleAjaxUrl + 'sync_to_facturedet.php', {
action: 'remove',
line_id: lineId,
line_type: lineType,
document_type: docType
}, function(response) {
done++;
if (response.success) {
updateSyncCheckbox(lineId, false);
} else {
errors++;
}
if (done >= total) {
debugLog('✅ Remove abgeschlossen: ' + (total - errors) + ' erfolgreich, ' + errors + ' Fehler');
if (errors > 0) {
showErrorAlert((total - errors) + ' von ' + total + ' Elementen entfernt. ' + errors + ' Fehler aufgetreten.');
} else {
safeReload();
}
}
}, 'json').fail(function() {
done++;
errors++;
});
$.post(subtotaltitleAjaxUrl + 'sync_to_facturedet.php', {
action: 'remove_all',
line_id: 1, // Dummy, wird benötigt wegen Parameter-Check
document_id: documentId,
document_type: docType
}, function(response) {
debugLog('Remove ALL response: ' + JSON.stringify(response));
if (response.success) {
debugLog('✅ Alle Spezialzeilen entfernt');
safeReload();
} else {
showErrorAlert((lang.errorSyncing || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
}
}, 'json').fail(function(xhr, status, error) {
debugLog('AJAX Fehler: ' + status + ' ' + error);
showErrorAlert((lang.errorSyncing || 'Fehler') + ': ' + error);
});
},
'Ja, alle entfernen',

View file

@ -86,9 +86,9 @@ ButtonCancel = Abbrechen
# UI Elements - Section Actions
#
SectionCreate = Produktgruppe erstellen
SectionEdit = Section bearbeiten
SectionDelete = Section löschen
SectionName = Section-Name
SectionEdit = Produktgruppe bearbeiten
SectionDelete = Produktgruppe löschen
SectionName = Name der Produktgruppe
SectionSubtotal = Zwischensumme anzeigen
ProductCount = Produkte

View file

@ -85,10 +85,10 @@ ButtonCancel = Cancel
#
# UI Elements - Section Actions
#
SectionCreate = Create section
SectionEdit = Edit section
SectionDelete = Delete section
SectionName = Section name
SectionCreate = Create product group
SectionEdit = Edit product group
SectionDelete = Delete product group
SectionName = Product group name
SectionSubtotal = Show subtotal
ProductCount = Products

0
sql/llx_facture_lines_manager.sql Normal file → Executable file
View file