Feat: Veraltete XSL-Einträge nach DB-Import erkennen und entfernen (v1.2.7)

Beim Import aus der PostgreSQL-Datenbank werden nun XSL-Einträge erkannt,
die nicht mehr in der DB vorhanden sind. Ein Dialog zeigt diese gruppiert
in einer Baumansicht an und bietet die Option, sie samt nicht mehr
verwendeter XML-/PDF-Dateien aus dem Projekt zu entfernen.
Leere TreeNodes werden automatisch bereinigt.

Zusätzlich: SQL-Filter `r3.export = 0` in data.sql ergänzt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-30 20:30:32 +02:00
parent d7282082f4
commit 9370c03e90
9 changed files with 398 additions and 6 deletions
+89
View File
@@ -14,6 +14,13 @@ from PySide6.QtCore import QThread, Signal, Qt
from PySide6.QtWidgets import QMessageBox, QProgressDialog
from conf import app_settings, TreeNode, XslFile
from obsolete_detector import (
collect_unused_xml_files,
extract_db_xsl_ids,
find_obsolete_xsl_entries,
remove_empty_tree_nodes,
)
from ui.ObsoleteEntriesDialog import ObsoleteEntriesDialog
logger = logging.getLogger(__name__)
@@ -31,6 +38,7 @@ class DatabaseQueryThread(QThread):
def run(self):
import polars as pl
try:
df = pl.read_database_uri(self.sql_query, self.connection_string, engine="connectorx").sort(
["reporttyp_bez", "report_bez", "repfile_bez"]
@@ -107,6 +115,7 @@ class DatabaseMixin:
try:
new_nodes = self._process_sql_data(df)
self._merge_nodes_with_existing(new_nodes)
self._check_and_remove_obsolete_entries(new_nodes)
self._save_project_settings()
self._load_nodes_to_tree()
except Exception as e:
@@ -209,6 +218,7 @@ class DatabaseMixin:
list[TreeNode]: Liste der erstellten Root-Nodes
"""
import polars as pl
try:
start_time = time.time()
@@ -350,3 +360,82 @@ class DatabaseMixin:
if new_child.id not in existing_child_ids:
existing_node.children.append(new_child)
logger.info(f"Neues Kind hinzugefügt zu Node {existing_node.id}: {new_child.bez}")
def _check_and_remove_obsolete_entries(self, new_nodes: list[TreeNode]) -> None:
"""
Prüft nach dem Merge ob XslFile-Einträge nicht mehr in der DB vorhanden sind.
Zeigt einen Bestätigungsdialog und entfernt veraltete Einträge inklusive
PDF- und optionaler XML-Bereinigung.
Args:
new_nodes: Frisch aus der DB geladene Nodes (Ergebnis von _process_sql_data)
"""
if not self.pdf_project or not self.pdf_project.nodes:
return
try:
db_xsl_ids = extract_db_xsl_ids(new_nodes)
obsolete_groups = find_obsolete_xsl_entries(self.pdf_project.nodes, db_xsl_ids)
if not obsolete_groups:
logger.debug("Keine veralteten Einträge gefunden")
return
total_count = sum(len(g.xsl_entries) for g in obsolete_groups)
logger.info(f"{total_count} veraltete XSL-Einträge gefunden")
dialog = ObsoleteEntriesDialog(self, obsolete_groups)
if dialog.exec() != ObsoleteEntriesDialog.DialogCode.Accepted:
logger.info("Entfernung veralteter Einträge vom Benutzer abgebrochen")
return
delete_xml = dialog.delete_xml_files()
# Phase 1: XslFiles aus dem Datenmodell entfernen
# WICHTIG: Zuerst entfernen, damit _is_xml_xsl_combination_used_elsewhere
# und _is_xml_file_used_elsewhere die gelöschten Einträge nicht mehr sehen
# (gleiche Reihenfolge wie in _delete_tree_node)
for group in obsolete_groups:
parent = group.parent_node
obsolete_xsl_ids = {id(entry.xsl_file) for entry in group.xsl_entries}
parent.children = [c for c in parent.children if id(c) not in obsolete_xsl_ids]
logger.info(f"{len(group.xsl_entries)} XSL-Einträge aus '{' > '.join(group.node_path)}' entfernt")
# Phase 2: PDF-Bereinigung für alle entfernten XslFiles
total_deleted_pdfs = 0
for group in obsolete_groups:
for entry in group.xsl_entries:
xsl_id = entry.xsl_file.id
for xml_file_obj in entry.xsl_file.xmls:
is_used = self._is_xml_xsl_combination_used_elsewhere(xml_file_obj.xml, xsl_id, entry.xsl_file)
if not is_used:
total_deleted_pdfs += self._delete_pdf_files_for_xml_xsl_combination(
xml_file_obj.xml, xsl_id
)
if total_deleted_pdfs > 0:
logger.info(f"{total_deleted_pdfs} PDF-Datei(en) bereinigt")
# Phase 3: Leere TreeNodes bereinigen (bottom-up durch rekursiven Aufruf)
self.pdf_project.nodes = remove_empty_tree_nodes(self.pdf_project.nodes)
# Phase 4: Optionale physische XML-Löschung
if delete_xml:
unused_xml = collect_unused_xml_files(
obsolete_groups,
Path(self.project.project_dir),
self._is_xml_file_used_elsewhere,
)
for _rel, xml_abs in unused_xml:
try:
xml_abs.unlink()
logger.info(f"Physische XML-Datei gelöscht: {xml_abs}")
except Exception as e:
logger.warning(f"Konnte XML-Datei nicht löschen: {xml_abs} - {e}")
logger.info(f"Bereinigung abgeschlossen: {total_count} veraltete Einträge entfernt")
except Exception as e:
error_msg = f"Fehler beim Entfernen veralteter Einträge: {str(e)}"
logger.exception(error_msg)
QMessageBox.critical(self, "Fehler", error_msg)