From af6202555838c7d3cc7f3f2b3dbea85364b48e9d Mon Sep 17 00:00:00 2001 From: foefl Date: Wed, 15 Jul 2026 15:52:32 +0200 Subject: [PATCH] enable loading of consultations on company profile --- src/wce_crm/gui.py | 606 +++++++++++++++++++++++---------------------- 1 file changed, 305 insertions(+), 301 deletions(-) diff --git a/src/wce_crm/gui.py b/src/wce_crm/gui.py index a2a3ac5..37bb5d8 100644 --- a/src/wce_crm/gui.py +++ b/src/wce_crm/gui.py @@ -3669,6 +3669,9 @@ class Page_CompanyProfile(QWidget): self.layout_cons_indiv.insertWidget(target_idx, click_cell) self.cons_indiv.append(click_cell) + def update_current_page(self) -> None: + self._update_consultations() + def _disable_add_btns(self) -> None: self.add_btn_pauschal.setEnabled(False) self.add_btn_individual.setEnabled(False) @@ -3700,6 +3703,7 @@ class Page_CompanyProfile(QWidget): self.table_master_data.load_data(data) self.set_company_name(data["Name:"]) + self.update_current_page() def _sync_GUI_to_state(self) -> None: ... @@ -4496,8 +4500,7 @@ class Page_Consulting(QWidget): "Die Daten wurden erfolgreich gespeichert", ) msg_box.exec() - # TODO check triggers for rest of application - # self.update_triggered.emit() + self.update_triggered.emit() # self._activate_delete() finally: # always re-enable save, even if error occurred @@ -5073,305 +5076,6 @@ def clear_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( msg_type: QMessageBox.Icon, title: str, @@ -5568,6 +5272,306 @@ class EditButtons(QWidget): 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): """forwards internal Qt C++ warnings to Python loggers""" if mode == QtMsgType.QtInfoMsg: