diff --git a/src/wce_crm/backend/backend.py b/src/wce_crm/backend/backend.py index 6c5dead..5887155 100644 --- a/src/wce_crm/backend/backend.py +++ b/src/wce_crm/backend/backend.py @@ -1,6 +1,7 @@ from __future__ import annotations import datetime +from pprint import pformat from typing import TYPE_CHECKING, Any, cast import polars as pl @@ -8,11 +9,12 @@ import sqlalchemy as sql from wce_crm import db from wce_crm.constants import TIMEZONE_CEST +from wce_crm.data_models import Beratungsgespraech_Vorgang from wce_crm.logging import logger_back as logger from wce_crm.types import CompanyInfo, ContactPersonInfo, InitRecType, MainPageEntry if TYPE_CHECKING: - from wce_crm.types import ExtAnId, ExtMaId, RecId + from wce_crm.types import ConsId, ExtAnId, ExtMaId, RecId def _transform_for_gui_output( @@ -226,6 +228,52 @@ def initrec_person_delete_initial_recording( raise KeyError(f"Database ID {id_} not found for deletion") +def page_consulting_to_db( + data: Beratungsgespraech_Vorgang, +) -> None: + logger.debug("[Consulting Page] Call Database Routine...") + fk_vorgang_id: int + if data.vorgang_id is None: + # insert new "Vorgang" + insert_data = data.model_dump(exclude={"vorgang_id", "beratungen"}) + logger.debug("[Consulting Page] Call insert with data:\n%s", pformat(insert_data)) + fk_vorgang_id = 42 # TODO replace for real inserted PK + else: + fk_vorgang_id = data.vorgang_id + logger.debug("[Consulting Page] VorgangID already set. ID: %d", fk_vorgang_id) + + rows_for_db_insert: list[dict[str, Any]] = [] + rows_for_db_update: list[dict[str, Any]] = [] + + for cons_session in data.beratungen: + cons_session.vorgang_id = fk_vorgang_id + if cons_session.beratung_id is None: + row_data = cons_session.model_dump(exclude={"beratung_id"}) + rows_for_db_insert.append(row_data) + else: + row_data = cons_session.model_dump() + rows_for_db_update.append(row_data) + + if rows_for_db_update: + # ... update + logger.debug( + "[Consulting Page] Call update for sessions:\n%s", pformat(rows_for_db_update) + ) + if rows_for_db_insert: + # ... insert + logger.debug( + "[Consulting Page] Call insert for sessions:\n%s", pformat(rows_for_db_insert) + ) + + +def page_consulting_from_db( + cons_id: ConsId, +) -> Beratungsgespraech_Vorgang: + # TODO add Routine + # get "Vorgang" and all associated sessions to instantiate Pydantic data model + ... + + # // main page interaction def _main_page_get_company_list() -> list[MainPageEntry]: logger.debug("[Call backend] get_company_list") diff --git a/src/wce_crm/data_models.py b/src/wce_crm/data_models.py index 667a58c..76690b6 100644 --- a/src/wce_crm/data_models.py +++ b/src/wce_crm/data_models.py @@ -2,7 +2,7 @@ from __future__ import annotations import datetime import json -from typing import Annotated, Any, Final +from typing import Annotated, Any, Final, Literal from pydantic import ( AwareDatetime, @@ -346,23 +346,27 @@ class Grunderfassung_Sprachen(BaseModel): # ** Consulting -class Beratungsgespraech(BaseModel): +class Beratungsgespraech_Vorgang(BaseModel): model_config = ConfigDict(str_strip_whitespace=True) + vorgang_id: int | None un_id: int | None pers_id: int | None titel: str - beratungs_typ: str + beratungs_typ: Literal["pauschal", "individuell"] # default in SQLAlchemy with lambda and timezone-aware datetime erstellt: AwareDatetime | None = None aktualisiert: AwareDatetime | None = None # see above + beratungen: list[Beratungsgespraech_Einzelgespraech] -class Beratungsgespraech_Vorgang(BaseModel): + +class Beratungsgespraech_Einzelgespraech(BaseModel): model_config = ConfigDict(str_strip_whitespace=True) - vorgang_id: int + vorgang_id: int | None + beratung_id: int | None nutzer_id: int nutzer_name: str zeitstempel: AwareDatetime diff --git a/src/wce_crm/gui.py b/src/wce_crm/gui.py index 0316cef..ac2f537 100644 --- a/src/wce_crm/gui.py +++ b/src/wce_crm/gui.py @@ -6,6 +6,7 @@ import datetime import enum import re import sys +import time import traceback from collections import defaultdict from collections.abc import Container, Iterable, Sequence @@ -77,6 +78,8 @@ from wce_crm.backend import backend from wce_crm.constants import TIMEZONE_CEST from wce_crm.data_models import ( COLUMN_SEP, + Beratungsgespraech_Einzelgespraech, + Beratungsgespraech_Vorgang, FlatBaseModel, Grunderfassung_Personen, Grunderfassung_Unternehmen, @@ -95,10 +98,9 @@ from wce_crm.logging import ( logger_gui, logger_page_consulting, ) -from wce_crm.types import ExtMaId, RecId, UserId if TYPE_CHECKING: - ... + from wce_crm.types import ConsId, ExtMaId, RecId, UserId K = TypeVar("K") V = TypeVar("V") @@ -343,15 +345,17 @@ class Page_InitRecPerson_State(PageState[Module]): class ConsultingType(enum.StrEnum): PAUSCHAL = enum.auto() - INDIVIUAL = enum.auto() + INDIVIDUAL = enum.auto() @dc.dataclass(slots=True, kw_only=True) class Page_Consulting_State(PageState[Module]): session: Session + vorgang_id: ConsId | None un_id: RecId | None = None pers_id: RecId | None = None - cons_type: ConsultingType | None = None + titel: str = "TEST-TITEL" # add title later to GUI or build it in the background + beratungs_typ: ConsultingType cons_sessions: list[Page_Consulting_ConsultingSession_State] = dc.field( default_factory=list ) @@ -359,15 +363,16 @@ class Page_Consulting_State(PageState[Module]): @dc.dataclass(slots=True, kw_only=True) class Page_Consulting_ConsultingSession_State(PageState[Module]): + session: Session nutzer_id: UserId # must be set with the session (always known) nutzer_name: str # must be set with the session (always known) - beratung_id: int | None = None # known and not known entries + beratung_id: int | None # known and not known entries zeitstempel: datetime.datetime = dc.field( default_factory=lambda: datetime.datetime.now(tz=datetime.UTC) ) ansprechpartner: str | None = None - interaction_scheme: str | None = None - topic: str | None = ( + kommunikationsweg: str | None = None + thema_crm_matrix: str | None = ( None # TODO should be linked to something, target unclear (need specification by customer) ) anmerkungen: str | None = None @@ -376,6 +381,7 @@ class Page_Consulting_ConsultingSession_State(PageState[Module]): @dc.dataclass(slots=True, kw_only=True) class Page_Consulting_Table_State(PageState["Page_Consulting_ConsultingSession"]): + session: Session row_states: list[Page_Consulting_ConsultingSession_State] = dc.field(default_factory=list) @@ -2829,53 +2835,6 @@ class Page_InitRecCompany(QWidget): self.search_widget_trigger = cast( QLineEdit, search_widget.company_widgets["ma_unternehmensname"] ) - # TODO check how this should be done with new architecture - # text_widget_set = search_widgets_by_key( - # self.auto_form.widget_registry, f"Kontaktperson{COLUMN_SEP}KP_name_partner" - # ) - # assert len(text_widget_set) == 1 - # self.text_widget_set = cast(QLineEdit, text_widget_set[0]["widget"]) - # self.search_widget_trigger.textChanged.connect( - # self._custom_set_company_name_contact_person - # ) - - # ** 'Bundesland' only if 'Inland' selected in 'Stammdaten' - # TODO check removal - # person_location = search_widgets_by_key( - # self.auto_form.widget_registry, f"Stammdaten{COLUMN_SEP}aufenthaltsort" - # ) - # assert len(person_location) == 1 - # self.person_location = cast(QComboBox, person_location[0]["widget"]) - # assert isinstance(self.person_location, QComboBox) - # self.person_location.currentIndexChanged.connect(self._custom_county_selection) - - # selection_county = search_widgets_by_key( - # self.auto_form.widget_registry, f"Stammdaten{COLUMN_SEP}bundesland" - # ) - # assert len(selection_county) == 1 - # self.selection_county = cast(QComboBox, selection_county[0]["widget"]) - # assert isinstance(self.selection_county, QComboBox) - # self.selection_county.setProperty("styleClass", "stempel") - # self.selection_county.setEnabled(False) - - # def _custom_set_company_name_contact_person(self, value: str) -> None: - # self.text_widget_set.setText(str(value)) - - # def _custom_county_selection(self, idx: int) -> None: - # value = self.person_location.itemData(idx) - # if value == "Inland": - # self.selection_county.setEnabled(True) - # self.selection_county.setProperty("styleClass", "") - # self.selection_county.style().unpolish(self.selection_county) - # self.selection_county.style().polish(self.selection_county) - # self.selection_county.update() - # else: - # self.selection_county.setCurrentIndex(0) - # self.selection_county.setEnabled(False) - # self.selection_county.setProperty("styleClass", "stempel") - # self.selection_county.style().unpolish(self.selection_county) - # self.selection_county.style().polish(self.selection_county) - # self.selection_county.update() def _request_company_profile(self) -> None: req_state = Page_CompanyProfile_State( @@ -2993,23 +2952,6 @@ class Page_InitRecPerson(QWidget): container_layout.addSpacing(15) # --- CUSTOM LOGIC --- - # ** fill 'Kontaktperson -> Namen Unternehmen' - # TODO check how partners are selected with new architecture - # search_res = search_widgets_by_key(self.auto_form.widget_registry, "Partnersuche") - # assert len(search_res) == 1 - # search_widget = cast(Grunderfassung_SuchWidget, search_res[0]["widget"]) - # self.search_widget_trigger = cast( - # QLineEdit, search_widget.company_widgets["ma_unternehmensname"] - # ) - # text_widget_set = search_widgets_by_key( - # self.auto_form.widget_registry, f"Kontaktperson{COLUMN_SEP}KP_name_partner" - # ) - # assert len(text_widget_set) == 1 - # self.text_widget_set = cast(QLineEdit, text_widget_set[0]["widget"]) - # self.search_widget_trigger.textChanged.connect( - # self._custom_set_company_name_contact_person - # ) - # ** 'Bundesland' only if 'Inland' selected in 'Stammdaten' person_location = search_widgets_by_key( self.auto_form.widget_registry, f"Stammdaten{COLUMN_SEP}aufenthaltsort" @@ -3028,9 +2970,6 @@ class Page_InitRecPerson(QWidget): self.selection_county.setProperty("styleClass", "stempel") self.selection_county.setEnabled(False) - # def _custom_set_company_name_contact_person(self, value: str) -> None: - # self.text_widget_set.setText(str(value)) - def _custom_county_selection(self, idx: int) -> None: value = self.person_location.itemData(idx) if value == "Inland": @@ -3207,7 +3146,7 @@ class Page_CompanyProfile(QWidget): QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed ) left_layout.addWidget(master_data_label) - # self.table_master_data = MasterDataTable() + default_labels = tuple(self.COMPANY_FEATURES_MASTER_DATA.values()) self.table_master_data = MasterDataFormWidget(default_labels) left_layout.addWidget(self.table_master_data) @@ -3254,7 +3193,7 @@ class Page_CompanyProfile(QWidget): bottom_right_layout.addWidget(individual_consultancy_label) self.add_btn_individual = QPushButton("+ Hinzufügen") self.add_btn_individual.clicked.connect( - lambda: self._request_consulting(ConsultingType.INDIVIUAL) + lambda: self._request_consulting(ConsultingType.INDIVIDUAL) ) self.add_btn_individual.setSizePolicy( QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed @@ -3317,7 +3256,10 @@ class Page_CompanyProfile(QWidget): "tried to call consulting from comp profile without set ID" ) new_state = Page_Consulting_State( - session=self.STATE.session, un_id=self.STATE.rec_id, cons_type=cons_type + session=self.STATE.session, + vorgang_id=None, + un_id=self.STATE.rec_id, + beratungs_typ=cons_type, ) self.consulting_requested.emit(new_state) @@ -3340,7 +3282,7 @@ class Page_CompanyProfile(QWidget): self._enable_add_btns() init_rec = backend.initrec_company_get_initial_recording(self.STATE.rec_id) - ma_id = cast(ExtMaId, init_rec["Partnersuche__un_suche"]) + ma_id = cast("ExtMaId", init_rec["Partnersuche__un_suche"]) assert ma_id is not None, "no ma_id obtained" comp_info = backend.initrec_comp_search_get_info(ma_id) @@ -3598,7 +3540,7 @@ class Page_Consulting(QWidget): back_requested = Signal(bool) # back to init rec (bool: reset yes/no) # TODO update should trigger something else # more like listening to 'child' ("Beratungen") - # update_triggered = Signal() # form saved (data changed for front page) + update_triggered = Signal() # form saved (data changed for front page) def __init__( self, @@ -3606,7 +3548,11 @@ class Page_Consulting(QWidget): ): super().__init__() # state management - self.STATE = Page_Consulting_State(session=session) + self.STATE = Page_Consulting_State( + session=session, + vorgang_id=None, + beratungs_typ=ConsultingType.PAUSCHAL, + ) # Hauptlayout der Seite outer_layout = QHBoxLayout(self) @@ -3695,6 +3641,19 @@ class Page_Consulting(QWidget): container_layout.addSpacing(30) + # --- BUTTONS --- + self.edit_buttons = EditButtons(add_reset_btn=False) + # reset_btn = self.edit_buttons.reset_btn + # assert reset_btn + self.save_btn = self.edit_buttons.save_btn + # self.reset_btn = reset_btn + self.delete_btn = self.edit_buttons.delete_btn + + self.save_btn.clicked.connect(self.save_data) + # self.reset_btn.clicked.connect(self.reset_form) + self.delete_btn.clicked.connect(self._delete_data) + container_layout.addWidget(self.edit_buttons) + type_group_box = QGroupBox("Typ") type_layout = QVBoxLayout(type_group_box) container_layout.addWidget(type_group_box) @@ -3712,8 +3671,8 @@ class Page_Consulting(QWidget): type_layout.addWidget(self.type_individual_btn) # container_layout.addSpacing(50) - table_state = Page_Consulting_Table_State() - self.table_sessions = Page_Consulting_Table(site_state=self.STATE, state=table_state) + table_state = Page_Consulting_Table_State(session=self.STATE.session) + self.table_sessions = Page_Consulting_Table(state=table_state) self.STATE.child_modules.append(self.table_sessions) container_layout.addWidget(self.table_sessions) @@ -3748,6 +3707,28 @@ class Page_Consulting(QWidget): # font-weight: bold; # } # """) + self._sync_state_to_GUI() + + def _delete_data(self) -> None: + logger_page_consulting.debug("[Consulting Page] Call data delete...") + + def _disable_save(self) -> None: + self.save_btn.setEnabled(False) + self.save_btn.setText(self.edit_buttons.save_btn_txt_disabled) + + def _activate_delete(self) -> None: + if self.STATE.un_id is not None or self.STATE.pers_id is not None: + self.delete_btn.setEnabled(True) + else: + self.delete_btn.setEnabled(False) + + def _enable_save( + self, + timeout: int = 3000, + ) -> None: + QTimer.singleShot(timeout, lambda: self.save_btn.setEnabled(True)) + QTimer.singleShot(timeout + 1, lambda: self.save_btn.setShortcut("Ctrl+S")) + self.save_btn.setText(self.edit_buttons.save_btn_txt_enabled) def type_consulting_changed( self, @@ -3759,12 +3740,12 @@ class Page_Consulting(QWidget): if button_id == 0: cons_type = ConsultingType.PAUSCHAL elif button_id == 1: - cons_type = ConsultingType.INDIVIUAL + cons_type = ConsultingType.INDIVIDUAL else: raise RuntimeError(f"No consulting type defined for button id: {button_id}") logger_page_consulting.debug("[Consulting Page] Selected cons_type: %s", cons_type) - self.STATE.cons_type = cons_type + self.STATE.beratungs_typ = cons_type def _debug_get_state(self) -> None: logger_page_consulting.debug("[Consulting Page] Call get state on table...") @@ -3782,10 +3763,10 @@ class Page_Consulting(QWidget): logger_page_consulting.debug("[Consulting Page] Page errors:\n%s", pformat(errors)) def _sync_state_to_GUI(self) -> None: - match self.STATE.cons_type: + match self.STATE.beratungs_typ: case ConsultingType.PAUSCHAL: self.type_pauschal_btn.setChecked(True) - case ConsultingType.INDIVIUAL: + case ConsultingType.INDIVIDUAL: self.type_individual_btn.setChecked(True) # deactivate if at least one ID is set (type is pre-defined by how @@ -3803,6 +3784,7 @@ class Page_Consulting(QWidget): # this method table_state = self.table_sessions.get_state() self.STATE.cons_sessions = table_state.row_states + self._activate_delete() def validate(self) -> list[str]: errors: list[str] = [] @@ -3817,7 +3799,10 @@ class Page_Consulting(QWidget): logger_page_consulting.debug("[Consulting] Loading with request:\n%s", new_state) set_page_state(self.STATE, new_state) # child modules - table_state = Page_Consulting_Table_State(row_states=self.STATE.cons_sessions) + table_state = Page_Consulting_Table_State( + session=self.STATE.session, + row_states=self.STATE.cons_sessions, + ) self.table_sessions.load_state(table_state) self._sync_state_to_GUI() @@ -3827,6 +3812,98 @@ class Page_Consulting(QWidget): return self.STATE + def save_data(self) -> None: + logger_page_consulting.debug("[Consulting Page] Call saving data...") + self._disable_save() + errors = self.validate() + + if errors: + logger_page_consulting.error( + "[Consulting Page] Error during GUI validation phase:\n%s", pformat(errors) + ) + GUI_validation_error_handling(errors) + self._enable_save() + return + + logger_page_consulting.info("[Consulting Page] Success! All data is valid") + logger_page_consulting.info("[Consulting Page] Get form data call...") + state = self.get_state() + + logger_page_consulting.debug("[Consulting Page] Get state:\n%s", state) + + try: + logger_page_consulting.debug("[Consulting Page] Call Pydantic...") + + pydantic_sessions: list[Beratungsgespraech_Einzelgespraech] = [] + + for row in self.STATE.cons_sessions: + cons_session_dict = dc.asdict(row) + cons_session = Beratungsgespraech_Einzelgespraech( + vorgang_id=self.STATE.vorgang_id, + **cons_session_dict, + ) + pydantic_sessions.append(cons_session) + + consulting_process = Beratungsgespraech_Vorgang( + vorgang_id=self.STATE.vorgang_id, + un_id=self.STATE.un_id, + pers_id=self.STATE.pers_id, + titel=self.STATE.titel, + beratungs_typ=self.STATE.beratungs_typ, # type: ignore + beratungen=pydantic_sessions, + ) + + except ValidationError as e: + logger_auto_form.error( + "[Consulting Page] Error during Pydantic validation phase:\n%s", + pformat(e.errors()), + ) + pydantic_validation_error_handling(e) + else: + if DEBUG_NO_DATABASE: + return + # logger_page_consulting.debug( + # "[Consulting Page] Validated Pydantic successfully. Model:\n%s", + # pformat(consulting_process.model_dump()), + # ) + # !! should be placed in a try block to handle errors + backend.page_consulting_to_db(consulting_process) + + # # !! this code is only called if the 'try' block was successful + # # save data to database + # # db_data = validated_data.to_db(exclude=self.cfg.ignored_keys) + # # logger_auto_form.debug( + # # "[Consulting Page] Form data with 'exlude' (must be saved in the database):\n%s", + # # pformat(db_data), + # # ) + + # if self.STATE.rec_id is None: + # logger_auto_form.debug("[Consulting Page] Insert triggered...") + # # should return "vorgang_id" + # # init_rec_id = self.cfg.data_insert(db_data) + # # assert isinstance(init_rec_id, int) + # # self.STATE.rec_id = init_rec_id + # else: + # logger_auto_form.debug("[Consulting Page] Update triggered...") + # # self.cfg.data_update(self.STATE.rec_id, db_data) + + # logger_auto_form.info("[Consulting Page] Data saved successfully") + # self.update_triggered.emit() + # self._activate_delete() + finally: + # always re-enable save, even if error occurred + self._enable_save() + + def load_database( + self, + cons_id: ConsId, + ) -> None: + # TODO add routine + # load data model from database + consulting_process = backend.page_consulting_from_db(cons_id) + # instantiate state from data model + ... + class Page_Consulting_ConsultingSession(QWidget): """child of 'Page_Consulting_DynamicTable'""" @@ -3835,13 +3912,10 @@ class Page_Consulting_ConsultingSession(QWidget): def __init__( self, - site_state: Page_Consulting_State, state: Page_Consulting_ConsultingSession_State, ) -> None: super().__init__() - assert isinstance(site_state, Page_Consulting_State) assert isinstance(state, Page_Consulting_ConsultingSession_State) - self.SITE_STATE = site_state self.STATE = state layout = QHBoxLayout(self) @@ -3866,7 +3940,7 @@ class Page_Consulting_ConsultingSession(QWidget): "Zeitstempel", FormFieldType.DATETIME_FLEXIBLE, required=True, - enable_uuid_key=True, + key="zeitstempel", ) _, self.timestamp = build_and_add_widget( timestamp_def, @@ -3880,7 +3954,7 @@ class Page_Consulting_ConsultingSession(QWidget): FormFieldType.TEXT, placeholder="Freitext", required=True, - enable_uuid_key=True, + key="ansprechpartner", ) _, self.contact = build_and_add_widget( contact_def, @@ -3893,7 +3967,7 @@ class Page_Consulting_ConsultingSession(QWidget): FormFieldType.DROPDOWN, options=[("Persönlich", None), ("Telefon", None), ("Mail", None)], required=True, - enable_uuid_key=True, + key="kommunikationsweg", ) _, self.contact_type = build_and_add_widget( contact_type_def, @@ -3906,7 +3980,7 @@ class Page_Consulting_ConsultingSession(QWidget): FormFieldType.TEXT, placeholder="CRM Links", required=True, - enable_uuid_key=True, + key="thema_crm_matrix", ) _, self.crm_link = build_and_add_widget( crm_link_def, @@ -3917,7 +3991,7 @@ class Page_Consulting_ConsultingSession(QWidget): plain_text_def = FormField[QPlainTextEdit]( "Anmerkungen", FormFieldType.LONGTEXT, - enable_uuid_key=True, + key="anmerkungen", ) _, self.plain_text = build_and_add_widget( plain_text_def, @@ -3928,7 +4002,7 @@ class Page_Consulting_ConsultingSession(QWidget): response_def = FormField[QPlainTextEdit]( "Rückmeldung", FormFieldType.LONGTEXT, - enable_uuid_key=True, + key="rueckmeldung", ) _, self.response = build_and_add_widget( response_def, @@ -3983,16 +4057,18 @@ class Page_Consulting_ConsultingSession(QWidget): set_widget_value(self.user_field, self.STATE.nutzer_name) set_widget_value(self.timestamp, self.STATE.zeitstempel.astimezone(TIMEZONE_CEST)) set_widget_value(self.contact, self.STATE.ansprechpartner) - set_widget_value(self.contact_type, self.STATE.interaction_scheme) - set_widget_value(self.crm_link, self.STATE.topic) + set_widget_value(self.contact_type, self.STATE.kommunikationsweg) + set_widget_value(self.crm_link, self.STATE.thema_crm_matrix) set_widget_value(self.plain_text, self.STATE.anmerkungen) set_widget_value(self.response, self.STATE.rueckmeldung) def _sync_GUI_to_state(self) -> None: self.STATE.zeitstempel = self.timestamp.get_pydatetime() self.STATE.ansprechpartner = get_widget_value(self.contact) - self.STATE.interaction_scheme = get_widget_value(self.contact_type) - self.STATE.topic = get_widget_value(self.crm_link) # TODO: change to correct link + self.STATE.kommunikationsweg = get_widget_value(self.contact_type) + self.STATE.thema_crm_matrix = get_widget_value( + self.crm_link + ) # TODO: change to correct link self.STATE.anmerkungen = get_widget_value(self.plain_text) self.STATE.rueckmeldung = get_widget_value(self.response) @@ -4050,17 +4126,14 @@ class Page_Consulting_Table(QWidget): def __init__( self, - site_state: Page_Consulting_State, state: Page_Consulting_Table_State, ) -> None: super().__init__() + self.STATE = state + self.setWindowTitle("Native Dynamische Liste") self.resize(600, 400) - self.STATE = state - # TODO eliminate site state - self.SITE_STATE = site_state - main_layout = QVBoxLayout(self) main_layout.setContentsMargins(0, 15, 0, 15) main_layout.setSpacing(10) @@ -4215,11 +4288,13 @@ class Page_Consulting_Table(QWidget): ) -> None: if row_state is None: row_state = Page_Consulting_ConsultingSession_State( - nutzer_id=self.SITE_STATE.session.user_id, - nutzer_name=self.SITE_STATE.session.user_name, + session=self.STATE.session, + beratung_id=None, + nutzer_id=self.STATE.session.user_id, + nutzer_name=self.STATE.session.user_name, ) - row = Page_Consulting_ConsultingSession(site_state=self.SITE_STATE, state=row_state) + row = Page_Consulting_ConsultingSession(state=row_state) row.delete_request.connect(self.remove_row) index = self.container_layout.count() - 1 @@ -4254,16 +4329,6 @@ class Page_Consulting_Table(QWidget): for row in self.STATE.child_modules: row_data.append(row.get_state()) - # TODO remove - # for i in range(self.container_layout.count() - 1): - # item = self.container_layout.itemAt(i) - # assert item, "item not found" - # widget = item.widget() - # assert widget, "no widget obtained" - - # if isinstance(widget, Page_Consulting_ConsultingSession): - # row_data.append(widget.get_state()) - assert len(row_data) == len(self.STATE.child_modules), ( "mismatch in row count and data length" ) @@ -4674,21 +4739,6 @@ def GUI_validation_error_handling( msg_box.exec() -# @dc.dataclass(slots=True) -# class EditButton: -# btn: QWidget -# txt_enabled: str -# txt_disabled: str - - -# @dc.dataclass(slots=True, kw_only=True) -# class EditButtonLayout: -# layout: QLayout -# save: EditButton -# delete: EditButton -# reset: EditButton | None - - class EditButtons(QWidget): def __init__( self, @@ -4698,8 +4748,6 @@ class EditButtons(QWidget): layout_btn = QHBoxLayout(self) layout_btn.setContentsMargins(0, 0, 0, 0) - - # main_layout.addLayout(layout_btn) # TODO add in component # save self.save_btn_txt_enabled = "Speichern (Strg + S)" self.save_btn_txt_disabled = "Wird gespeichert..." @@ -4707,9 +4755,7 @@ class EditButtons(QWidget): self.save_btn.setShortcut("Ctrl+S") self.save_btn.setFixedHeight(50) self.save_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) - # save_btn.clicked.connect(save_data) # TODO add in component layout_btn.addWidget(self.save_btn) - # reset self.reset_button: QPushButton | None = None if add_reset_btn: @@ -4719,66 +4765,15 @@ class EditButtons(QWidget): self.reset_btn.setSizePolicy( QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed ) - # reset_btn.clicked.connect(reset_form) # TODO add in component layout_btn.addWidget(self.reset_btn) - # delete self.delete_btn = QPushButton("Eintrag löschen (Strg + L)") self.delete_btn.setShortcut("Ctrl+L") self.delete_btn.setFixedHeight(50) self.delete_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) - # delete_btn.clicked.connect(self._delete_data) # TODO add in component layout_btn.addWidget(self.delete_btn) -# def get_edit_buttons( -# add_reset_btn: bool, -# ) -> EditButtonLayout: -# layout_btn = QHBoxLayout() -# # main_layout.addLayout(layout_btn) # TODO add in component -# # save -# save_btn_txt_enabled = "Speichern (Strg + S)" -# save_btn_txt_disabled = "Wird gespeichert..." -# save_btn = QPushButton(save_btn_txt_enabled) -# save_btn.setShortcut("Ctrl+S") -# save_btn.setFixedHeight(50) -# save_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) -# # save_btn.clicked.connect(save_data) # TODO add in component -# edit_button_save = EditButton(save_btn, "Speichern (Strg + S)", "Wird gespeichert...") -# layout_btn.addWidget(save_btn) - -# # reset -# edit_button_reset: EditButton | None = None -# if add_reset_btn: -# reset_btn = QPushButton("Zurücksetzen (Strg + Z)") -# reset_btn.setShortcut("Ctrl+Z") -# reset_btn.setFixedHeight(50) -# reset_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) -# # reset_btn.clicked.connect(reset_form) # TODO add in component -# edit_button_reset = EditButton( -# save_btn, "Zurücksetzen (Strg + Z)", "Zurücksetzen (Strg + Z)" -# ) -# layout_btn.addWidget(reset_btn) - -# # delete -# delete_btn = QPushButton("Eintrag löschen (Strg + L)") -# delete_btn.setShortcut("Ctrl+L") -# delete_btn.setFixedHeight(50) -# delete_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) -# edit_button_delete = EditButton( -# save_btn, "Eintrag löschen (Strg + L)", "Eintrag löschen (Strg + L)" -# ) -# # delete_btn.clicked.connect(self._delete_data) # TODO add in component -# layout_btn.addWidget(delete_btn) - -# return EditButtonLayout( -# layout=layout_btn, -# save=edit_button_save, -# delete=edit_button_delete, -# reset=edit_button_reset, -# ) - - def qt_message_handler(mode, context, message): """forwards internal Qt C++ warnings to Python loggers""" if mode == QtMsgType.QtInfoMsg: diff --git a/src/wce_crm/types.py b/src/wce_crm/types.py index b250ea4..eb8e648 100644 --- a/src/wce_crm/types.py +++ b/src/wce_crm/types.py @@ -10,6 +10,7 @@ if TYPE_CHECKING: UserId: TypeAlias = int RecId: TypeAlias = int +ConsId: TypeAlias = int ExtMaId: TypeAlias = int ExtAnId: TypeAlias = int