Dev #7

Merged
foefl merged 74 commits from dev into main 2026-07-31 14:19:01 +00:00
Showing only changes of commit af62025558 - Show all commits
+305 -301
View File
@@ -3669,6 +3669,9 @@ class Page_CompanyProfile(QWidget):
self.layout_cons_indiv.insertWidget(target_idx, click_cell) self.layout_cons_indiv.insertWidget(target_idx, click_cell)
self.cons_indiv.append(click_cell) self.cons_indiv.append(click_cell)
def update_current_page(self) -> None:
self._update_consultations()
def _disable_add_btns(self) -> None: def _disable_add_btns(self) -> None:
self.add_btn_pauschal.setEnabled(False) self.add_btn_pauschal.setEnabled(False)
self.add_btn_individual.setEnabled(False) self.add_btn_individual.setEnabled(False)
@@ -3700,6 +3703,7 @@ class Page_CompanyProfile(QWidget):
self.table_master_data.load_data(data) self.table_master_data.load_data(data)
self.set_company_name(data["Name:"]) self.set_company_name(data["Name:"])
self.update_current_page()
def _sync_GUI_to_state(self) -> None: ... def _sync_GUI_to_state(self) -> None: ...
@@ -4496,8 +4500,7 @@ class Page_Consulting(QWidget):
"Die Daten wurden erfolgreich gespeichert", "Die Daten wurden erfolgreich gespeichert",
) )
msg_box.exec() msg_box.exec()
# TODO check triggers for rest of application self.update_triggered.emit()
# self.update_triggered.emit()
# self._activate_delete() # self._activate_delete()
finally: finally:
# always re-enable save, even if error occurred # always re-enable save, even if error occurred
@@ -5073,305 +5076,6 @@ def clear_layout(
clear_layout(child.layout()) clear_layout(child.layout())
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# STATE MANAGEMENT
session = Session(user_id=42, user_name="TEST-USER")
self.STATE = Page_MainPage_State(session=session)
self._history_stack: list[int] = []
self.setWindowTitle("Master")
self.resize(1800, 1000)
# MENU
self.create_menu()
# STACK: stack to change between 'sites'
self.stack = QStackedWidget()
self.setCentralWidget(self.stack)
# MAIN PAGE: table view
self.main_page = self.setup_main_page()
self.stack.addWidget(self.main_page)
# SITE: add new entries for 'Grunderfassung'
new_initrec_select_state = Page_NewInitRec_State(session=self.STATE.session)
self.new_initrec_select = Page_NewInitRec(new_initrec_select_state)
self.new_initrec_select.back_requested.connect(self.show_main_page)
self.new_initrec_select.company_requested.connect(self.show_page_initrec_company)
self.new_initrec_select.person_requested.connect(self.show_page_initrec_person)
self.new_initrec_select.consulting_requested.connect(self.show_page_consulting)
self.stack.addWidget(self.new_initrec_select)
# SITE: 'Grunderfassung Unternehmen'
initrec_company_state = Page_InitRecCompany_State(
session=self.STATE.session, locked=False
)
self.initrec_company = Page_InitRecCompany(initrec_company_state)
self.initrec_company.back_main_requested.connect(self.show_main_page)
self.initrec_company.back_requested.connect(self.show_new_entry_select)
self.initrec_company.update_main_page.connect(self.update_grid)
self.initrec_company.company_profile_requested.connect(self.show_page_company_profile)
self.stack.addWidget(self.initrec_company)
# SITE: 'Grunderfassung Person'
initrec_person_state = Page_InitRecPerson_State(
session=self.STATE.session, locked=False
)
self.initrec_person = Page_InitRecPerson(initrec_person_state)
self.initrec_person.back_main_requested.connect(self.show_main_page)
self.initrec_person.back_requested.connect(self.show_new_entry_select)
self.initrec_person.update_triggered.connect(self.update_grid)
self.stack.addWidget(self.initrec_person)
# SITE: 'Unternehmensprofil'
comp_profile_state = Page_CompanyProfile_State(session=self.STATE.session)
self.company_profile = Page_CompanyProfile(comp_profile_state)
self.company_profile.back_main_requested.connect(self.show_main_page)
self.company_profile.back_requested.connect(self.show_page_initrec_company)
self.company_profile.consulting_requested.connect(self.show_page_consulting)
self.stack.addWidget(self.company_profile)
# SITE: 'Beratungsgespräch'
self.consulting_page = Page_Consulting(self.STATE.session)
self.consulting_page.back_main_requested.connect(self.show_main_page)
# self.consulting_page.back_requested.connect(
# self.show_page_company_profile_without_load
# )
self.consulting_page.back_requested.connect(self.simple_go_back)
self.stack.addWidget(self.consulting_page)
def setup_main_page(self):
# QMainWindow defines frame --> container widget needed for the middle
main_widget = QWidget()
outer_layout = QHBoxLayout(main_widget)
vert_layout = QVBoxLayout()
# add buttons
new_btn = QPushButton("Neu →")
new_btn.setFixedWidth(100)
new_btn.setFixedHeight(40)
new_btn.clicked.connect(self.show_new_entry_select)
vert_layout.addWidget(new_btn)
if DEBUG:
update_btn = QPushButton("UPDATE")
update_btn.setFixedWidth(100)
update_btn.setFixedHeight(40)
update_btn.clicked.connect(self.update_grid)
clear_btn = QPushButton("CLEAR")
clear_btn.setFixedWidth(100)
clear_btn.setFixedHeight(40)
clear_btn.clicked.connect(self._clear_layout)
vert_layout.addWidget(update_btn)
vert_layout.addWidget(clear_btn)
separator = QFrame()
separator.setFrameShape(QFrame.Shape.HLine)
separator.setFrameShadow(QFrame.Shadow.Sunken)
vert_layout.addWidget(separator)
# container for table or grid
container = QWidget()
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setMinimumWidth(700)
scroll_area.setMaximumWidth(1500)
scroll_area.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
# delete frame of ScrollArea to get a flat look
scroll_area.setFrameShape(QFrame.Shape.NoFrame)
scroll_area.setWidget(container)
vert_layout.addSpacing(20)
vert_layout.addWidget(scroll_area)
# springs left and right of container to center the it
outer_layout.addStretch(1)
outer_layout.addLayout(vert_layout, stretch=100)
outer_layout.addStretch(1)
outer_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
# Wir geben dem Container ein vertikales Layout
container_layout = QVBoxLayout(container)
container_layout.setContentsMargins(0, 0, 0, 0)
# grid for table without direct container
self.header_grid = QGridLayout()
self.header_grid.setColumnStretch(0, 1)
self.header_grid.setColumnStretch(1, 1)
self.header_grid.setColumnStretch(2, 1)
self.header_grid.setColumnStretch(3, 1)
self.header_grid.setColumnMinimumWidth(4, 100)
self.grid = QGridLayout()
self.grid.setSpacing(10)
self.grid.setColumnStretch(0, 1)
self.grid.setColumnStretch(1, 1)
self.grid.setColumnStretch(2, 1)
self.grid.setColumnStretch(3, 1)
self.grid.setColumnMinimumWidth(4, 100)
# add grid to vertical layout
container_layout.addLayout(self.header_grid)
container_layout.addLayout(self.grid)
# spring below the layout to push it to top
container_layout.addStretch()
self.current_row = 0
headers = [
"UN/ NWP/ Kontaktperson",
"Individualberatung",
"Pauschalberatung",
"Verlaufsprotokoll",
"Datum",
]
for col_idx, title in enumerate(headers):
self.header_grid.addWidget(HeaderCell(title), self.current_row, col_idx)
self.update_grid()
return main_widget
def _clear_layout(self) -> None:
clear_layout(self.grid)
def update_grid(self) -> None:
clear_layout(self.grid)
data = backend.main_page_get_entries()
for entry in data:
self.add_row_to_grid(entry)
def add_row_to_grid(
self,
entry: types.MainPageEntry,
):
row = self.current_row
# NAME
cell_name = MainPage_ClickableCell(entry.display_name, entry)
cell_name.clicked.connect(self.goto_initial_recording)
self.grid.addWidget(cell_name, row, 0)
# DATE
cell_date_name = entry.Metadaten_aktualisierung.date().strftime(DATE_FMT)
cell_date = MainPage_ClickableCell(cell_date_name, entry)
cell_date.clicked.connect(self.goto_initial_recording)
self.grid.addWidget(cell_date, row, 4)
self.current_row += 1 # Zähler für den nächsten Eintrag erhöhen
def goto_initial_recording(
self,
data: types.MainPageEntry,
):
target: QWidget
if data.type is types.InitRecType.COMPANY:
new_state = Page_InitRecCompany_State(
session=self.STATE.session,
un_id=data.rec_id,
locked=True,
)
self.initrec_company.load_state(new_state)
target = self.initrec_company
elif data.type is types.InitRecType.PERSON:
new_state = Page_InitRecPerson_State(
session=self.STATE.session,
pers_id=data.rec_id,
locked=True,
)
self.initrec_person.load_state(new_state)
target = self.initrec_person
else:
raise TypeError(f"Unknown init rec type: {data.type}")
self._navigate_to_target(target)
def _navigate_to_target(
self,
widget: QWidget,
) -> None:
curr_idx = self.stack.currentIndex()
if curr_idx != -1:
self._history_stack.append(curr_idx)
self.stack.setCurrentWidget(widget)
def simple_go_back(self) -> None:
if not self._history_stack:
logger_gui.info("[main program] Called >back< on empty history")
prev_idx = self._history_stack.pop()
self.stack.setCurrentIndex(prev_idx)
def show_main_page(self):
self._history_stack.clear()
self._navigate_to_target(self.main_page)
def show_new_entry_select(self):
self._navigate_to_target(self.new_initrec_select)
def show_page_initrec_company(
self,
new_state: Page_InitRecCompany_State,
):
self.initrec_company.load_state(new_state)
self._navigate_to_target(self.initrec_company)
def show_page_initrec_person(
self,
new_state: Page_InitRecPerson_State,
):
self.initrec_person.load_state(new_state)
self._navigate_to_target(self.initrec_person)
def show_page_company_profile(
self,
new_state: Page_CompanyProfile_State,
) -> None:
self.company_profile.load_state(new_state)
self._navigate_to_target(self.company_profile)
def show_page_consulting(
self,
new_state: Page_Consulting_State,
) -> None:
self.consulting_page.load_state(new_state)
self._navigate_to_target(self.consulting_page)
# --- MENÜ LOGIK ---
def create_menu(self):
menu_bar = self.menuBar()
# file menu
file_menu = menu_bar.addMenu("Datei")
exit_action = QAction("Beenden", self)
exit_action.setShortcut("Ctrl+Q")
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# help menu
help_menu = menu_bar.addMenu("Hilfe")
about_action = QAction("Über", self)
help_menu.addAction(about_action)
# ** global exception handling
def global_exception_handler(exc_type, exc_value, exc_traceback):
"""catches all unhandled errors"""
# format error
error_msg = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
logger_gui.critical(f"UNEXPECTED ERROR:\n{error_msg}")
# message to user
# check if QApplication exists otherwise crashing of crash handler possible)
if QApplication.instance():
msg_box = get_message_box(
QMessageBox.Icon.Critical,
"Kritischer Fehler",
"Ein unerwarteter Fehler ist aufgetreten. Die Details wurden protokolliert.",
error_msg,
)
msg_box.exec()
sys.exit(1)
def get_message_box( def get_message_box(
msg_type: QMessageBox.Icon, msg_type: QMessageBox.Icon,
title: str, title: str,
@@ -5568,6 +5272,306 @@ class EditButtons(QWidget):
self.delete_btn.setEnabled(enable_modification) self.delete_btn.setEnabled(enable_modification)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# STATE MANAGEMENT
session = Session(user_id=42, user_name="TEST-USER")
self.STATE = Page_MainPage_State(session=session)
self._history_stack: list[int] = []
self.setWindowTitle("Master")
self.resize(1800, 1000)
# MENU
self.create_menu()
# STACK: stack to change between 'sites'
self.stack = QStackedWidget()
self.setCentralWidget(self.stack)
# MAIN PAGE: table view
self.main_page = self.setup_main_page()
self.stack.addWidget(self.main_page)
# SITE: add new entries for 'Grunderfassung'
new_initrec_select_state = Page_NewInitRec_State(session=self.STATE.session)
self.new_initrec_select = Page_NewInitRec(new_initrec_select_state)
self.new_initrec_select.back_requested.connect(self.show_main_page)
self.new_initrec_select.company_requested.connect(self.show_page_initrec_company)
self.new_initrec_select.person_requested.connect(self.show_page_initrec_person)
self.new_initrec_select.consulting_requested.connect(self.show_page_consulting)
self.stack.addWidget(self.new_initrec_select)
# SITE: 'Grunderfassung Unternehmen'
initrec_company_state = Page_InitRecCompany_State(
session=self.STATE.session, locked=False
)
self.initrec_company = Page_InitRecCompany(initrec_company_state)
self.initrec_company.back_main_requested.connect(self.show_main_page)
self.initrec_company.back_requested.connect(self.show_new_entry_select)
self.initrec_company.update_main_page.connect(self.update_grid)
self.initrec_company.company_profile_requested.connect(self.show_page_company_profile)
self.stack.addWidget(self.initrec_company)
# SITE: 'Grunderfassung Person'
initrec_person_state = Page_InitRecPerson_State(
session=self.STATE.session, locked=False
)
self.initrec_person = Page_InitRecPerson(initrec_person_state)
self.initrec_person.back_main_requested.connect(self.show_main_page)
self.initrec_person.back_requested.connect(self.show_new_entry_select)
self.initrec_person.update_triggered.connect(self.update_grid)
self.stack.addWidget(self.initrec_person)
# SITE: 'Unternehmensprofil'
comp_profile_state = Page_CompanyProfile_State(session=self.STATE.session)
self.company_profile = Page_CompanyProfile(comp_profile_state)
self.company_profile.back_main_requested.connect(self.show_main_page)
self.company_profile.back_requested.connect(self.show_page_initrec_company)
self.company_profile.consulting_requested.connect(self.show_page_consulting)
self.stack.addWidget(self.company_profile)
# SITE: 'Beratungsgespräch'
self.consulting_page = Page_Consulting(self.STATE.session)
self.consulting_page.back_main_requested.connect(self.show_main_page)
self.consulting_page.back_requested.connect(self.simple_go_back)
self.consulting_page.update_triggered.connect(self.update_page_company_profile)
self.stack.addWidget(self.consulting_page)
def setup_main_page(self):
# QMainWindow defines frame --> container widget needed for the middle
main_widget = QWidget()
outer_layout = QHBoxLayout(main_widget)
vert_layout = QVBoxLayout()
# add buttons
new_btn = QPushButton("Neu →")
new_btn.setFixedWidth(100)
new_btn.setFixedHeight(40)
new_btn.clicked.connect(self.show_new_entry_select)
vert_layout.addWidget(new_btn)
if DEBUG:
update_btn = QPushButton("UPDATE")
update_btn.setFixedWidth(100)
update_btn.setFixedHeight(40)
update_btn.clicked.connect(self.update_grid)
clear_btn = QPushButton("CLEAR")
clear_btn.setFixedWidth(100)
clear_btn.setFixedHeight(40)
clear_btn.clicked.connect(self._clear_layout)
vert_layout.addWidget(update_btn)
vert_layout.addWidget(clear_btn)
separator = QFrame()
separator.setFrameShape(QFrame.Shape.HLine)
separator.setFrameShadow(QFrame.Shadow.Sunken)
vert_layout.addWidget(separator)
# container for table or grid
container = QWidget()
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setMinimumWidth(700)
scroll_area.setMaximumWidth(1500)
scroll_area.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
# delete frame of ScrollArea to get a flat look
scroll_area.setFrameShape(QFrame.Shape.NoFrame)
scroll_area.setWidget(container)
vert_layout.addSpacing(20)
vert_layout.addWidget(scroll_area)
# springs left and right of container to center the it
outer_layout.addStretch(1)
outer_layout.addLayout(vert_layout, stretch=100)
outer_layout.addStretch(1)
outer_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
# Wir geben dem Container ein vertikales Layout
container_layout = QVBoxLayout(container)
container_layout.setContentsMargins(0, 0, 0, 0)
# grid for table without direct container
self.header_grid = QGridLayout()
self.header_grid.setColumnStretch(0, 1)
self.header_grid.setColumnStretch(1, 1)
self.header_grid.setColumnStretch(2, 1)
self.header_grid.setColumnStretch(3, 1)
self.header_grid.setColumnMinimumWidth(4, 100)
self.grid = QGridLayout()
self.grid.setSpacing(10)
self.grid.setColumnStretch(0, 1)
self.grid.setColumnStretch(1, 1)
self.grid.setColumnStretch(2, 1)
self.grid.setColumnStretch(3, 1)
self.grid.setColumnMinimumWidth(4, 100)
# add grid to vertical layout
container_layout.addLayout(self.header_grid)
container_layout.addLayout(self.grid)
# spring below the layout to push it to top
container_layout.addStretch()
self.current_row = 0
headers = [
"UN/ NWP/ Kontaktperson",
"Individualberatung",
"Pauschalberatung",
"Verlaufsprotokoll",
"Datum",
]
for col_idx, title in enumerate(headers):
self.header_grid.addWidget(HeaderCell(title), self.current_row, col_idx)
self.update_grid()
return main_widget
def _clear_layout(self) -> None:
clear_layout(self.grid)
def update_grid(self) -> None:
clear_layout(self.grid)
data = backend.main_page_get_entries()
for entry in data:
self.add_row_to_grid(entry)
def add_row_to_grid(
self,
entry: types.MainPageEntry,
):
row = self.current_row
# NAME
cell_name = MainPage_ClickableCell(entry.display_name, entry)
cell_name.clicked.connect(self.goto_initial_recording)
self.grid.addWidget(cell_name, row, 0)
# DATE
cell_date_name = entry.Metadaten_aktualisierung.date().strftime(DATE_FMT)
cell_date = MainPage_ClickableCell(cell_date_name, entry)
cell_date.clicked.connect(self.goto_initial_recording)
self.grid.addWidget(cell_date, row, 4)
self.current_row += 1 # Zähler für den nächsten Eintrag erhöhen
def goto_initial_recording(
self,
data: types.MainPageEntry,
):
target: QWidget
if data.type is types.InitRecType.COMPANY:
new_state = Page_InitRecCompany_State(
session=self.STATE.session,
un_id=data.rec_id,
locked=True,
)
self.initrec_company.load_state(new_state)
target = self.initrec_company
elif data.type is types.InitRecType.PERSON:
new_state = Page_InitRecPerson_State(
session=self.STATE.session,
pers_id=data.rec_id,
locked=True,
)
self.initrec_person.load_state(new_state)
target = self.initrec_person
else:
raise TypeError(f"Unknown init rec type: {data.type}")
self._navigate_to_target(target)
def _navigate_to_target(
self,
widget: QWidget,
) -> None:
curr_idx = self.stack.currentIndex()
if curr_idx != -1:
self._history_stack.append(curr_idx)
self.stack.setCurrentWidget(widget)
def simple_go_back(self) -> None:
if not self._history_stack:
logger_gui.info("[main program] Called >back< on empty history")
prev_idx = self._history_stack.pop()
self.stack.setCurrentIndex(prev_idx)
def show_main_page(self):
self._history_stack.clear()
self._navigate_to_target(self.main_page)
def show_new_entry_select(self):
self._navigate_to_target(self.new_initrec_select)
def show_page_initrec_company(
self,
new_state: Page_InitRecCompany_State,
):
self.initrec_company.load_state(new_state)
self._navigate_to_target(self.initrec_company)
def show_page_initrec_person(
self,
new_state: Page_InitRecPerson_State,
):
self.initrec_person.load_state(new_state)
self._navigate_to_target(self.initrec_person)
def show_page_company_profile(
self,
new_state: Page_CompanyProfile_State,
) -> None:
self.company_profile.load_state(new_state)
self._navigate_to_target(self.company_profile)
def update_page_company_profile(self) -> None:
self.company_profile.update_current_page()
def show_page_consulting(
self,
new_state: Page_Consulting_State,
) -> None:
self.consulting_page.load_state(new_state)
self._navigate_to_target(self.consulting_page)
# --- MENÜ LOGIK ---
def create_menu(self):
menu_bar = self.menuBar()
# file menu
file_menu = menu_bar.addMenu("Datei")
exit_action = QAction("Beenden", self)
exit_action.setShortcut("Ctrl+Q")
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# help menu
help_menu = menu_bar.addMenu("Hilfe")
about_action = QAction("Über", self)
help_menu.addAction(about_action)
# ** global exception handling
def global_exception_handler(exc_type, exc_value, exc_traceback):
"""catches all unhandled errors"""
# format error
error_msg = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
logger_gui.critical(f"UNEXPECTED ERROR:\n{error_msg}")
# message to user
# check if QApplication exists otherwise crashing of crash handler possible)
if QApplication.instance():
msg_box = get_message_box(
QMessageBox.Icon.Critical,
"Kritischer Fehler",
"Ein unerwarteter Fehler ist aufgetreten. Die Details wurden protokolliert.",
error_msg,
)
msg_box.exec()
sys.exit(1)
def qt_message_handler(mode, context, message): def qt_message_handler(mode, context, message):
"""forwards internal Qt C++ warnings to Python loggers""" """forwards internal Qt C++ warnings to Python loggers"""
if mode == QtMsgType.QtInfoMsg: if mode == QtMsgType.QtInfoMsg: