diff --git a/src/wce_crm/backend/backend.py b/src/wce_crm/backend/backend.py index b4fb30e..f7e5ee0 100644 --- a/src/wce_crm/backend/backend.py +++ b/src/wce_crm/backend/backend.py @@ -24,7 +24,8 @@ from wce_crm.types import ( CompanyProfileConsultations, ConsultingType, ContactPersonInfo, - InitRecType, + EntityIds, + EntityType, LinkingConsultationsEntryCompany, LinkingConsultationsEntryPerson, MainPageEntry, @@ -160,7 +161,7 @@ def initrec_company_to_db( dump_data["geloescht"] = auto_form_data.geloescht with db.ENGINE.begin() as conn: - if auto_form_data.rec_id is None: + if auto_form_data.ids.un_id is None: logger.debug("[AutoForm -- backend] Insert...") stmt = db.grunderfassung_unternehmen.insert().returning( @@ -169,25 +170,23 @@ def initrec_company_to_db( db.grunderfassung_unternehmen.c.geloescht, ) ret = conn.execute(stmt, dump_data) - if ret.rowcount == 0: - raise IOError("Entry was not inserted correctly") from_db = ret.mappings().fetchall() assert len(from_db) == 1, "expected excactly one returned row" data_from_db = from_db[0] - auto_form_data.rec_id = data_from_db["un_id"] + auto_form_data.ids.un_id = data_from_db["un_id"] auto_form_data.geloescht = data_from_db["geloescht"] auto_form_data.db_data["Metadaten_aktualisierung"] = data_from_db[ "Metadaten_aktualisierung" ] - logger.debug("[AutoForm -- backend] Inserted InitRec successfully") + logger.debug("[AutoForm -- backend] Inserted InitRec Company successfully") else: logger.debug("[AutoForm -- backend] Update...") stmt = ( db.grunderfassung_unternehmen.update() - .where(db.grunderfassung_unternehmen.c.un_id == auto_form_data.rec_id) + .where(db.grunderfassung_unternehmen.c.un_id == auto_form_data.ids.un_id) .returning( db.grunderfassung_unternehmen.c.Metadaten_aktualisierung, db.grunderfassung_unternehmen.c.geloescht, @@ -204,8 +203,8 @@ def initrec_company_to_db( ] logger.debug( - "[AutoForm -- backend] Updated InitRec with ID %d successfully", - auto_form_data.rec_id, + "[AutoForm -- backend] Updated InitRec Company with ID %d successfully", + auto_form_data.ids.un_id, ) return auto_form_data @@ -229,7 +228,8 @@ def initrec_company_from_db( del data_from_db["geloescht"] return InitRec( - rec_id=id_, + ids=EntityIds(un_id=id_), + # rec_id=id_, # TODO remove geloescht=geloescht, db_data=data_from_db, ) @@ -244,34 +244,39 @@ def initrec_person_to_db( dump_data["geloescht"] = auto_form_data.geloescht with db.ENGINE.begin() as conn: - if auto_form_data.rec_id is None: + if auto_form_data.ids.pers_id is None: logger.debug("[AutoForm -- backend] Insert...") - stmt = db.grunderfassung_personen.insert().returning( + stmt = sa.insert(db.grunderfassung_personen).returning( db.grunderfassung_personen.c.pers_id, db.grunderfassung_personen.c.Metadaten_aktualisierung, db.grunderfassung_personen.c.geloescht, ) + stmt_compiled = str(stmt.compile(db.ENGINE)) + logger.debug( + "[AutoForm -- backend] Data to insert:\nStatement: %s\n%s", + stmt_compiled, + pformat(dump_data), + ) + ret = conn.execute(stmt, dump_data) - if ret.rowcount == 0: - raise IOError("Entry was not inserted correctly") from_db = ret.mappings().fetchall() assert len(from_db) == 1, "expected excactly one returned row" data_from_db = from_db[0] - auto_form_data.rec_id = data_from_db["un_id"] + auto_form_data.ids.pers_id = data_from_db["pers_id"] auto_form_data.geloescht = data_from_db["geloescht"] auto_form_data.db_data["Metadaten_aktualisierung"] = data_from_db[ "Metadaten_aktualisierung" ] - logger.debug("[AutoForm -- backend] Inserted InitRec successfully") + logger.debug("[AutoForm -- backend] Inserted InitRec Person successfully") else: logger.debug("[AutoForm -- backend] Update...") stmt = ( db.grunderfassung_personen.update() - .where(db.grunderfassung_personen.c.pers_id == auto_form_data.rec_id) + .where(db.grunderfassung_personen.c.pers_id == auto_form_data.ids.pers_id) .returning( db.grunderfassung_personen.c.Metadaten_aktualisierung, db.grunderfassung_personen.c.geloescht, @@ -288,8 +293,8 @@ def initrec_person_to_db( ] logger.debug( - "[AutoForm -- backend] Updated InitRec with ID %d successfully", - auto_form_data.rec_id, + "[AutoForm -- backend] Updated InitRec Person with ID %d successfully", + auto_form_data.ids.pers_id, ) return auto_form_data @@ -313,33 +318,34 @@ def initrec_person_from_db( del data_from_db["geloescht"] return InitRec( - rec_id=id_, + ids=EntityIds(pers_id=id_), + # rec_id=id_, # TODO remove geloescht=geloescht, db_data=data_from_db, ) # TODO check removal -# def initrec_company_get_initial_recording( -# id_: RecId, -# ) -> dict[str, Any]: -# logger.debug("[Call backend] get_initial_recording") -# stmt = db.grunderfassung_unternehmen.select().where( -# db.grunderfassung_unternehmen.c.un_id == id_ -# ) -# with db.ENGINE.connect() as conn: -# ret = conn.execute(stmt) +def initrec_company_get_initial_recording( + id_: RecId, +) -> dict[str, Any]: + logger.debug("[Call backend] get_initial_recording") + stmt = db.grunderfassung_unternehmen.select().where( + db.grunderfassung_unternehmen.c.un_id == id_ + ) + with db.ENGINE.connect() as conn: + ret = conn.execute(stmt) -# results = ret.mappings().all() -# if not results: -# raise KeyError(f"Database ID {id_} not found") + results = ret.mappings().all() + if not results: + raise KeyError(f"Database ID {id_} not found") -# assert len(results) == 1, "more than one company initial recording obtained" + assert len(results) == 1, "more than one company initial recording obtained" -# row = results[0] -# assert row, "row was not obtained" + row = results[0] + assert row, "row was not obtained" -# return dict(row) + return dict(row) # def initrec_company_delete_initial_recording( @@ -828,7 +834,7 @@ def _main_page_get_company_list() -> list[MainPageEntry]: rec_id=rec_id, display_name=display_name, Metadaten_aktualisierung=datetime_akt, - type=InitRecType.COMPANY, + type=EntityType.COMPANY, ) ) @@ -865,7 +871,7 @@ def _main_page_get_person_list() -> list[MainPageEntry]: rec_id=rec_id, display_name=display_name, Metadaten_aktualisierung=datetime_akt, - type=InitRecType.PERSON, + type=EntityType.PERSON, ) ) diff --git a/src/wce_crm/data_models.py b/src/wce_crm/data_models.py index a008ccf..72a6c8a 100644 --- a/src/wce_crm/data_models.py +++ b/src/wce_crm/data_models.py @@ -19,10 +19,11 @@ from pydantic import ( from pydantic_core import ErrorDetails from wce_crm.form_defs import FormField -from wce_crm.types import ConsId, ConsultingType, RecId, UserId +from wce_crm.types import EntityIds if TYPE_CHECKING: from wce_crm.gui import Page_Consulting_ConsultingSession # noqa: F401 + from wce_crm.types import ConsId, ConsultingType, EntityType, RecId, UserId ValidAge = Annotated[int, Field(ge=0, le=99)] COLUMN_SEP: Final[str] = "__" @@ -74,33 +75,7 @@ def _parse_json(value: Any) -> str: raise TypeError -# // GUI states and types -def set_page_state( - target: S, - source: S, - deep: bool = False, -) -> None: - """ - transfer all field values generically from 'source' to 'target' - """ - if not (dc.is_dataclass(target) and dc.is_dataclass(source)): - raise TypeError("Both source and atrget must be dataclasses") - - if type(target) is not type(source): - raise TypeError("Both source and target must have the same type") - - for field in dc.fields(source): - if field.name == "child_modules": - continue - - new_value = getattr(source, field.name) - - if deep: - new_value = copy.deepcopy(new_value) - - setattr(target, field.name, new_value) - - +# // interfaces class WrapperModule(Protocol): def _sync_state_to_GUI(self, *args, **kwargs) -> None: """build states for all child modules and use their `load_state` method @@ -178,20 +153,47 @@ class Module(WrapperModule, Protocol): # ) -> None: ... -class AutoFormToDb(Protocol): +class InitRecFormToDb(Protocol): def __call__( self, auto_form_data: InitRec, ) -> InitRec: ... -class AutoFormFromDb(Protocol): +class InitRecFormFromDb(Protocol): def __call__( self, id_: int, ) -> InitRec: ... +# // GUI states and types +def set_page_state( + target: S, + source: S, + deep: bool = False, +) -> None: + """ + transfer all field values generically from 'source' to 'target' + """ + if not (dc.is_dataclass(target) and dc.is_dataclass(source)): + raise TypeError("Both source and atrget must be dataclasses") + + if type(target) is not type(source): + raise TypeError("Both source and target must have the same type") + + for field in dc.fields(source): + if field.name == "child_modules": + continue + + new_value = getattr(source, field.name) + + if deep: + new_value = copy.deepcopy(new_value) + + setattr(target, field.name, new_value) + + @dc.dataclass(slots=True) class Session: user_id: UserId @@ -215,40 +217,45 @@ class Page_NewInitRec_State(PageState[Module]): @dc.dataclass(slots=True, kw_only=True) -class Page_InitRecCompany_State(PageState[Module]): - session: Session - un_id: RecId | None = None - locked: bool - - -@dc.dataclass(slots=True, kw_only=True) -class AutoFormConfig: +class InitRecFormConfig: model: type[FlatBaseModel] - to_db: AutoFormToDb - from_db: AutoFormFromDb + to_db: InitRecFormToDb + from_db: InitRecFormFromDb form_fields: Sequence[FormField] ignored_keys: Iterable[str] = tuple() add_buttons: bool = True @dc.dataclass(slots=True, kw_only=True) -class AutoForm_State(PageState[Module]): +class InitRecForm_State(PageState[Module]): session: Session - cfg: AutoFormConfig - rec_id: RecId | None = None + cfg: InitRecFormConfig + ids: EntityIds + ent_type: EntityType + # rec_id: RecId | None = None form_data: dict[str, Any] | None = None locked: bool geloescht: bool = False @dc.dataclass(slots=True, kw_only=True) -class Page_InitRecPerson_State(PageState[Module]): +class Page_InitRecCompany_State(PageState[Module]): session: Session - pers_id: RecId | None = None - un_id: RecId | None = None + ids: EntityIds + # un_id: RecId | None = None locked: bool +@dc.dataclass(slots=True, kw_only=True) +class Page_InitRecPerson_State(PageState[Module]): + session: Session + ids: EntityIds + # pers_id: RecId | None = None + # un_id: RecId | None = None + locked: bool + + +# TODO change to entity IDs @dc.dataclass(slots=True, kw_only=True) class Page_Consulting_State(PageState[Module]): session: Session @@ -264,6 +271,7 @@ class Page_Consulting_State(PageState[Module]): locked: bool +# TODO change to entity IDs @dc.dataclass(slots=True, kw_only=True) class Page_Consulting_Linking_State(PageState[Module]): session: Session @@ -279,7 +287,6 @@ class Page_Consulting_ConsultingSession_State(PageState[Module]): nutzer_name: str # must be set with the session (always known) beratung_id: int | None # known and not known entries zeitstempel: datetime.datetime | None = None - # dc.field(default_factory=lambda: datetime.datetime.now(tz=datetime.UTC)) ansprechpartner: str | None = None kommunikationsweg: str | None = None thema_crm_matrix: str | None = ( @@ -301,7 +308,8 @@ class Page_Consulting_Table_State(PageState["Page_Consulting_ConsultingSession"] @dc.dataclass(slots=True, kw_only=True) class Page_CompanyProfile_State(PageState[Module]): session: Session - rec_id: RecId | None = None + ids: EntityIds + # rec_id: RecId | None = None # // Pydantic models @@ -419,7 +427,8 @@ class FlatBaseModel(BaseModel): # ** InitRec class InitRec(BaseModel): - rec_id: RecId | None + ids: EntityIds + # rec_id: RecId | None geloescht: AwareDatetime | None = None db_data: dict[str, Any] diff --git a/src/wce_crm/gui.py b/src/wce_crm/gui.py index 2031744..878f6ea 100644 --- a/src/wce_crm/gui.py +++ b/src/wce_crm/gui.py @@ -84,13 +84,13 @@ from wce_crm.backend import backend from wce_crm.constants import TIMEZONE_CEST from wce_crm.data_models import ( COLUMN_SEP, - AutoForm_State, - AutoFormConfig, Beratungsgespraech_Einzelgespraech, Beratungsgespraech_Vorgang, Grunderfassung_Personen, Grunderfassung_Unternehmen, InitRec, + InitRecForm_State, + InitRecFormConfig, Page_CompanyProfile_State, Page_Consulting_ConsultingSession_State, Page_Consulting_Linking_State, @@ -117,11 +117,17 @@ from wce_crm.logging import ( logger_gui, logger_page_consulting, ) -from wce_crm.types import CompanyProfileConsultationEntry, ConsultingType, RecId +from wce_crm.types import ( + CompanyProfileConsultationEntry, + ConsultingType, + EntityIds, + EntityType, + RecId, +) if TYPE_CHECKING: from wce_crm.data_models import ( - AutoForm_State, + InitRecForm_State, Module, WrapperModule, ) @@ -1370,14 +1376,14 @@ def search_widgets_by_key( return hits -class AutoForm(QWidget): +class InitRecForm(QWidget): """a widget, which is managed by a code-defined field definition collection""" update_triggered = Signal() # formular saved (data changed for front page) def __init__( self, - state: AutoForm_State, + state: InitRecForm_State, ) -> None: super().__init__() self.STATE = state @@ -1519,7 +1525,8 @@ class AutoForm(QWidget): index = int(self.id_field_input.text()) except ValueError: index = None - self.STATE.rec_id = index + self.STATE.ids.un_id = index + self.STATE.ids.pers_id = index logger_gui.debug("[Auto-Form] Set index to %s, new state: %s", index, self.STATE) def _disable_save(self) -> None: @@ -1539,13 +1546,13 @@ class AutoForm(QWidget): self.save_btn.setText(self.edit_buttons.save_btn_txt_enabled) def _activate_delete(self) -> None: - if self.STATE.rec_id is not None: + if self.STATE.ids.valid(): self.delete_btn.setEnabled(True) else: self.delete_btn.setEnabled(False) def _delete_data(self) -> None: - assert self.STATE.rec_id is not None, "deletion initialised despite no index set" + assert self.STATE.ids.valid(), "deletion initialised despite IDs are not properly set" confirm = get_user_confirmation( self, "Löschen bestätigen", @@ -1592,7 +1599,9 @@ class AutoForm(QWidget): logger_auto_form.debug("[Auto-Form] Set form data...") logger_auto_form.debug("[Auto-Form] Form data:\n%s", pformat(form_data)) self._set_form_data(form_data) - self.STATE.rec_id = id_ + self.STATE.ids.set_id(id_, self.STATE.ent_type) + # TODO remove + # self.STATE.rec_id = id_ self.STATE.form_data = self._get_form_data() logger_auto_form.debug( "####### model dump vs get form data:\n%s\n\n%s", @@ -1602,7 +1611,9 @@ class AutoForm(QWidget): def reset_form(self) -> None: reset_form(self.widget_registry) - self.STATE.rec_id = None + self.STATE.ids.set_id(None, self.STATE.ent_type) + # TODO remove + # self.STATE.rec_id = None self.STATE.form_data = None self.STATE.geloescht = False @@ -1640,7 +1651,7 @@ class AutoForm(QWidget): ) -> None: if section is None or section == "form": if not self.STATE.geloescht: - self._load_from_id(self.STATE.rec_id) + self._load_from_id(self.STATE.ids.get_id(self.STATE.ent_type)) else: self.reset_form() @@ -1666,12 +1677,12 @@ class AutoForm(QWidget): def load_state( self, - new_state: AutoForm_State, + new_state: InitRecForm_State, ) -> None: set_page_state(self.STATE, new_state) self._sync_state_to_GUI(None) - def get_state(self) -> AutoForm_State: + def get_state(self) -> InitRecForm_State: self._sync_GUI_to_state() return self.STATE @@ -1680,20 +1691,23 @@ class AutoForm(QWidget): self, data: InitRec, ) -> None: - self.STATE.rec_id = data.rec_id + ent_type = self.STATE.ent_type + self.STATE.ids.set_id(data.ids.get_id(ent_type), ent_type) + # self.STATE.rec_id = data.rec_id self.STATE.form_data = None self.STATE.geloescht = True if data.geloescht else False self._sync_state_to_GUI(None) # TODO change to correct loading def save_data(self) -> None: - if self.STATE.rec_id is None and self.STATE.geloescht: + relevant_id = self.STATE.ids.get_id(self.STATE.ent_type) + if relevant_id is None and self.STATE.geloescht: logger_auto_form.debug( "[Auto-Form] This was never saved in the database " "and is marked for deletion - ignore." ) return - elif self.STATE.rec_id is not None and self.STATE.geloescht: + elif relevant_id is not None and self.STATE.geloescht: logger_auto_form.debug( "[Auto-Form] This was saved in the database " "and is marked for deletion - fast path." @@ -1701,7 +1715,8 @@ class AutoForm(QWidget): deleted_datetime = datetime.datetime.now(datetime.UTC) assert self.STATE.form_data, "cannot delete entry with uninitialised form data" to_db = InitRec( - rec_id=self.STATE.rec_id, + ids=self.STATE.ids, + # rec_id=self.STATE.rec_id, # TODO remove geloescht=deleted_datetime, db_data={}, ) @@ -1759,7 +1774,8 @@ class AutoForm(QWidget): try: # TODO change to result pattern to_db = InitRec( - rec_id=self.STATE.rec_id, + ids=self.STATE.ids, + # rec_id=self.STATE.rec_id, geloescht=deleted_datetime, db_data=db_data, ) @@ -2904,18 +2920,22 @@ class Page_NewInitRec(QWidget): layout.addWidget(btn_consulting) def _request_initrec_company(self) -> None: + ids = EntityIds(type=EntityType.COMPANY) req_state = Page_InitRecCompany_State( session=self.STATE.session, - un_id=None, + ids=ids, + # un_id=None, # TODO remove locked=False, ) logger_gui.debug("[Page -- InitRec] State to call: %s", req_state) self.company_requested.emit(req_state) def _request_initrec_person(self) -> None: + ids = EntityIds(type=EntityType.PERSON) req_state = Page_InitRecPerson_State( session=self.STATE.session, - pers_id=None, + ids=ids, + # pers_id=None, # TODO remove locked=False, ) logger_gui.debug("[Page -- InitRec] State to call: %s", req_state) @@ -2955,7 +2975,7 @@ class Page_NewInitRec(QWidget): pass -CONFIG_GRUNDERFASSUNG_UNTERNEHMEN: Final[AutoFormConfig] = AutoFormConfig( +CONFIG_GRUNDERFASSUNG_UNTERNEHMEN: Final[InitRecFormConfig] = InitRecFormConfig( model=Grunderfassung_Unternehmen, to_db=backend.initrec_company_to_db, from_db=backend.initrec_company_from_db, @@ -2966,7 +2986,7 @@ CONFIG_GRUNDERFASSUNG_UNTERNEHMEN: Final[AutoFormConfig] = AutoFormConfig( form_fields=INITREC_COMP, ) -CONFIG_GRUNDERFASSUNG_PERSONEN: Final[AutoFormConfig] = AutoFormConfig( +CONFIG_GRUNDERFASSUNG_PERSONEN: Final[InitRecFormConfig] = InitRecFormConfig( model=Grunderfassung_Personen, to_db=backend.initrec_person_to_db, from_db=backend.initrec_person_from_db, @@ -3036,8 +3056,8 @@ class Page_InitRecCompany(QWidget): ) self.comp_profile_btn.setFixedHeight(50) - self.initrec_person_btn = QPushButton("Individualperson erfassen") - self.initrec_person_btn.clicked.connect(self._request_inirec_person) + self.initrec_person_btn = QPushButton("Individualperson erfassen/anzeigen") + self.initrec_person_btn.clicked.connect(self._request_initrec_person) self.initrec_person_btn.setSizePolicy( QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed ) @@ -3079,13 +3099,16 @@ class Page_InitRecCompany(QWidget): # --- AUTO FORM LAYOUT --- container_layout.addSpacing(20) - self.AUTO_FORM_CFG = CONFIG_GRUNDERFASSUNG_UNTERNEHMEN - auto_form_state = AutoForm_State( + self.INITREC_FORM_CFG = CONFIG_GRUNDERFASSUNG_UNTERNEHMEN + assert self.STATE.ids.ent_type is EntityType.COMPANY, "mismatch of entity types" + auto_form_state = InitRecForm_State( session=self.STATE.session, - cfg=self.AUTO_FORM_CFG, + cfg=self.INITREC_FORM_CFG, + ids=self.STATE.ids, + ent_type=EntityType.COMPANY, locked=False, ) - self.auto_form = AutoForm(auto_form_state) + self.auto_form = InitRecForm(auto_form_state) container_layout.addWidget(self.auto_form) self.auto_form.update_triggered.connect(lambda: self.update_main_page.emit()) self.auto_form.update_triggered.connect(self._auto_form_updated) @@ -3132,24 +3155,29 @@ class Page_InitRecCompany(QWidget): self._sync_state_to_GUI("banners") def _request_company_profile(self) -> None: - assert self.STATE.un_id is not None, ( + assert self.STATE.ids.un_id is not None, ( "requested company profile for a company which is not known (no ID)" ) req_state = Page_CompanyProfile_State( session=self.STATE.session, - rec_id=self.STATE.un_id, + # rec_id=self.STATE.un_id, + ids=self.STATE.ids, ) logger_gui.debug("[Page -- InitRec Company] State to call: %s", req_state) self.company_profile_requested.emit(req_state) - def _request_inirec_person(self) -> None: - assert self.STATE.un_id is not None, ( + def _request_initrec_person(self) -> None: + assert self.STATE.ids.un_id is not None, ( "requested initrec of person for a company which is not known (no ID)" ) + if not self.STATE.locked: + self.auto_form.save_data() + req_state = Page_InitRecPerson_State( session=self.STATE.session, - pers_id=None, - un_id=self.STATE.un_id, + ids=self.STATE.ids, + # pers_id=None, # TODO remove + # un_id=self.STATE.un_id, locked=False, ) logger_gui.debug("[Page -- InitRec Company] State to call: %s", req_state) @@ -3160,13 +3188,16 @@ class Page_InitRecCompany(QWidget): def _sync_state_to_GUI( self, - section: Literal["auto_form", "comp_profile", "banners"] | None, + section: Literal["auto_form", "comp_profile", "initrec_person", "banners"] | None, ) -> None: if section is None or section == "auto_form": - auto_form_state = AutoForm_State( + assert self.STATE.ids.ent_type is EntityType.COMPANY, "mismatch of entity types" + auto_form_state = InitRecForm_State( session=self.STATE.session, - cfg=self.AUTO_FORM_CFG, - rec_id=self.STATE.un_id, + cfg=self.INITREC_FORM_CFG, + ids=self.STATE.ids, + ent_type=EntityType.COMPANY, + # rec_id=self.STATE.un_id, # TODO remove form_data=None, locked=self.STATE.locked, ) @@ -3174,14 +3205,20 @@ class Page_InitRecCompany(QWidget): self.auto_form.load_state(auto_form_state) if section is None or section == "comp_profile": - if self.STATE.un_id is None: - self.comp_profile_btn.setEnabled(False) - else: + if self.STATE.ids.valid(): self.comp_profile_btn.setEnabled(True) + else: + self.comp_profile_btn.setEnabled(False) + + if section is None or section == "initrec_person": + if self.STATE.ids.un_id is not None: + self.initrec_person_btn.setEnabled(True) + else: + self.initrec_person_btn.setEnabled(False) if section is None or section == "banners": self._clear_info_banners() - if self.STATE.un_id is None: + if self.STATE.ids.un_id is None: self._add_info_banner( text="⚠️ Dieser Eintrag ist noch nicht gespeichert.", button_text="", @@ -3189,7 +3226,8 @@ class Page_InitRecCompany(QWidget): def _sync_GUI_to_state(self) -> None: auto_form_state = self.auto_form.get_state() - self.STATE.un_id = auto_form_state.rec_id + self.STATE.ids = auto_form_state.ids + # self.STATE.un_id = auto_form_state.rec_id # TODO remove def load_state( self, @@ -3291,12 +3329,14 @@ class Page_InitRecPerson(QWidget): # --- AUTO FORM LAYOUT --- container_layout.addSpacing(20) self.AUTO_FORM_CFG = CONFIG_GRUNDERFASSUNG_PERSONEN - auto_form_state = AutoForm_State( + auto_form_state = InitRecForm_State( session=self.STATE.session, cfg=self.AUTO_FORM_CFG, + ids=self.STATE.ids, + ent_type=EntityType.PERSON, locked=False, ) - self.auto_form = AutoForm(auto_form_state) + self.auto_form = InitRecForm(auto_form_state) container_layout.addWidget(self.auto_form) self.auto_form.update_triggered.connect(lambda: self.update_triggered.emit()) self.auto_form.update_triggered.connect(self._auto_form_updated) @@ -3376,10 +3416,12 @@ class Page_InitRecPerson(QWidget): section: Literal["auto_form", "banners"] | None, ) -> None: if section is None or section == "auto_form": - auto_form_state = AutoForm_State( + auto_form_state = InitRecForm_State( session=self.STATE.session, cfg=self.AUTO_FORM_CFG, - rec_id=self.STATE.pers_id, + ids=self.STATE.ids, + ent_type=EntityType.PERSON, + # rec_id=self.STATE.pers_id, # TODO remove form_data=None, locked=self.STATE.locked, ) @@ -3387,7 +3429,7 @@ class Page_InitRecPerson(QWidget): if section is None or section == "banners": self._clear_info_banners() - if self.STATE.pers_id is None: + if self.STATE.ids.pers_id is None: self._add_info_banner( text="⚠️ Dieser Eintrag ist noch nicht gespeichert.", button_text="", @@ -3395,7 +3437,8 @@ class Page_InitRecPerson(QWidget): def _sync_GUI_to_state(self) -> None: auto_form_state = self.auto_form.get_state() - self.STATE.pers_id = auto_form_state.rec_id + self.STATE.ids = auto_form_state.ids + # self.STATE.ids.pers_id = auto_form_state.rec_id # TODO remove def load_state( self, @@ -3420,9 +3463,7 @@ class Page_InitRecPerson(QWidget): class Page_CompanyProfile(QWidget): back_main_requested = Signal() # back to main page - back_requested = Signal( - Page_InitRecCompany_State - ) # back to init rec (bool: reset yes/no) + back_requested = Signal() # 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) @@ -3473,7 +3514,8 @@ class Page_CompanyProfile(QWidget): back_btn_main.setMinimumWidth(200) back_btn_main.setMaximumWidth(200) back_btn_step = QPushButton("← Zurück") - back_btn_step.clicked.connect(self._request_initrec_company) + # back_btn_step.clicked.connect(self._request_initrec_company) # TODO remove + back_btn_step.clicked.connect(lambda: self.back_requested.emit()) back_btn_step.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) back_btn_step.setMinimumWidth(200) back_btn_step.setMaximumWidth(200) @@ -3688,26 +3730,26 @@ class Page_CompanyProfile(QWidget): target_idx = target_layout.count() - 1 target_layout.insertWidget(target_idx, dummy_click_cell) - def _request_initrec_company(self) -> None: - req_state = Page_InitRecCompany_State( - session=self.STATE.session, - un_id=self.STATE.rec_id, - locked=True, - ) - logger_gui.debug("[Page Company Profile] State to call: %s", req_state) - self.back_requested.emit(req_state) + # def _request_initrec_company(self) -> None: + # req_state = Page_InitRecCompany_State( + # session=self.STATE.session, + # un_id=self.STATE.rec_id, + # locked=True, + # ) + # logger_gui.debug("[Page Company Profile] State to call: %s", req_state) + # self.back_requested.emit(req_state) def _request_new_consulting( self, cons_type: ConsultingType, ) -> None: - assert self.STATE.rec_id is not None, ( + assert self.STATE.ids.un_id is not None, ( "tried to call consulting from comp profile without set ID" ) new_state = Page_Consulting_State( session=self.STATE.session, vorgang_id=None, - un_id=self.STATE.rec_id, + un_id=self.STATE.ids.un_id, beratungs_typ=cons_type, locked=False, ) @@ -3717,13 +3759,13 @@ class Page_CompanyProfile(QWidget): self, data: CompanyProfileConsultationEntry, ) -> None: - assert self.STATE.rec_id is not None, ( + assert self.STATE.ids.un_id is not None, ( "tried to call consulting from comp profile without set ID" ) new_state = Page_Consulting_State( session=self.STATE.session, vorgang_id=data.cons_id, - un_id=self.STATE.rec_id, + un_id=self.STATE.ids.un_id, beratungs_typ=data.cons_type, locked=True, ) @@ -3745,9 +3787,11 @@ class Page_CompanyProfile(QWidget): widget.deleteLater() def _update_consultations(self) -> None: - if self.STATE.rec_id is None: + if self.STATE.ids.un_id is None: return - consultations = backend.companyprofile_page_get_consultations(un_id=self.STATE.rec_id) + consultations = backend.companyprofile_page_get_consultations( + un_id=self.STATE.ids.un_id + ) logger_gui.debug( "[Page -- Company Profile] Consultations: %s", pformat(consultations) @@ -3785,7 +3829,7 @@ class Page_CompanyProfile(QWidget): self.add_btn_individual.setEnabled(True) def _sync_state_to_GUI(self) -> None: - if self.STATE.rec_id is None: + if self.STATE.ids.un_id is None: # just clear and do nothing else logger_gui.debug("just reset, ID is None") self.table_master_data.reset() @@ -3794,7 +3838,7 @@ class Page_CompanyProfile(QWidget): return self._enable_add_btns() - init_rec = backend.initrec_company_get_initial_recording(self.STATE.rec_id) + init_rec = backend.initrec_company_get_initial_recording(self.STATE.ids.un_id) 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) @@ -3809,16 +3853,18 @@ class Page_CompanyProfile(QWidget): self.set_company_name(data["Name:"]) self.update_current_page() - def _sync_GUI_to_state(self) -> None: ... + def _sync_GUI_to_state(self) -> None: + pass - def validate(self) -> None: ... + def validate(self) -> None: + pass def load_state( self, new_state: Page_CompanyProfile_State, ) -> None: - logger_gui.debug("Called function to load with ID: %s", new_state.rec_id) - self.STATE.rec_id = new_state.rec_id + logger_gui.debug("Called function to load with IDs: %s", pformat(new_state.ids)) + self.STATE.ids = new_state.ids self._sync_state_to_GUI() @@ -4015,7 +4061,7 @@ class MasterDataFormWidget(QWidget): class Page_Consulting(QWidget): back_main_requested = Signal() # back to main page - back_requested = Signal(bool) # back to init rec (bool: reset yes/no) + back_requested = Signal() # 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) @@ -4057,7 +4103,7 @@ class Page_Consulting(QWidget): back_btn_main.setMinimumWidth(200) back_btn_main.setMaximumWidth(200) back_btn_step = QPushButton("← Zurück") - back_btn_step.clicked.connect(lambda: self.back_requested.emit(False)) + back_btn_step.clicked.connect(lambda: self.back_requested.emit()) back_btn_step.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) back_btn_step.setMinimumWidth(200) back_btn_step.setMaximumWidth(200) @@ -5792,30 +5838,36 @@ class MainWindow(QMainWindow): self.new_initrec_select.consulting_requested.connect(self.show_page_consulting) self.stack.addWidget(self.new_initrec_select) # SITE: 'Grunderfassung Unternehmen' + ids = EntityIds(type=EntityType.COMPANY) initrec_company_state = Page_InitRecCompany_State( - session=self.STATE.session, locked=False + session=self.STATE.session, ids=ids, 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.back_requested.connect(self.simple_go_back) self.initrec_company.update_main_page.connect(self.update_grid) self.initrec_company.initrec_person_requested.connect(self.show_page_initrec_person) self.initrec_company.company_profile_requested.connect(self.show_page_company_profile) self.stack.addWidget(self.initrec_company) # SITE: 'Grunderfassung Person' + ids = EntityIds(type=EntityType.PERSON) initrec_person_state = Page_InitRecPerson_State( - session=self.STATE.session, locked=False + session=self.STATE.session, ids=ids, 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.back_requested.connect(self.simple_go_back) 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) + comp_profile_state = Page_CompanyProfile_State( + session=self.STATE.session, ids=EntityIds() + ) 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) + # TODO remove + # self.company_profile.back_requested.connect(self.show_page_initrec_company) + self.company_profile.back_requested.connect(self.simple_go_back) self.company_profile.consulting_requested.connect(self.show_page_consulting) self.stack.addWidget(self.company_profile) # SITE: 'Beratungsgespräch' @@ -5947,18 +5999,22 @@ class MainWindow(QMainWindow): data: types.MainPageEntry, ): target: QWidget - if data.type is types.InitRecType.COMPANY: + if data.type is types.EntityType.COMPANY: + ids = EntityIds(un_id=data.rec_id, type=EntityType.COMPANY) new_state = Page_InitRecCompany_State( session=self.STATE.session, - un_id=data.rec_id, + ids=ids, + # un_id=data.rec_id, # TODO remove locked=True, ) self.initrec_company.load_state(new_state) target = self.initrec_company - elif data.type is types.InitRecType.PERSON: + elif data.type is types.EntityType.PERSON: + ids = EntityIds(pers_id=data.rec_id, type=EntityType.PERSON) new_state = Page_InitRecPerson_State( session=self.STATE.session, - pers_id=data.rec_id, + ids=ids, + # pers_id=data.rec_id, # TODO remove locked=True, ) self.initrec_person.load_state(new_state) diff --git a/src/wce_crm/types.py b/src/wce_crm/types.py index dcf3e05..9d4fc34 100644 --- a/src/wce_crm/types.py +++ b/src/wce_crm/types.py @@ -18,7 +18,7 @@ ExtAnId: TypeAlias = int PolarsSchema: TypeAlias = dict[str, type["pl.DataType"]] -class InitRecType(enum.IntEnum): +class EntityType(enum.IntEnum): COMPANY = enum.auto() PERSON = enum.auto() @@ -28,10 +28,56 @@ class ConsultingType(enum.StrEnum): INDIVIDUAL = enum.auto() -@dc.dataclass(slots=True) +@dc.dataclass(slots=True, kw_only=True) class EntityIds: un_id: RecId | None = None pers_id: RecId | None = None + type: dc.InitVar[EntityType | None] = None + _ent_type: EntityType | None = dc.field(init=False) + + def __post_init__(self, type: EntityType | None) -> None: + self._ent_type = type + + @property + def ent_type(self) -> EntityType | None: + return self._ent_type + + def any(self) -> bool: + return self.un_id is not None or self.pers_id is not None + + def all(self) -> bool: + return self.un_id is not None and self.pers_id is not None + + def valid(self) -> bool: + if self.ent_type is None: + raise TypeError("Initrec type not defined. Can not determine valid state.") + elif self.ent_type is EntityType.COMPANY: + return self.all() + elif self.ent_type is EntityType.PERSON: + return self.any() + else: + raise RuntimeError( + f"Unknown initrec type {self.ent_type}. This is not supported." + ) + + def set_id( + self, + id_: RecId | None, + ent_type: EntityType, + ) -> None: + if ent_type is EntityType.COMPANY: + self.un_id = id_ + elif ent_type is EntityType.PERSON: + self.pers_id = id_ + + def get_id( + self, + ent_type: EntityType, + ) -> RecId | None: + if ent_type is EntityType.COMPANY: + return self.un_id + elif ent_type is EntityType.PERSON: + return self.pers_id # // database interaction data structures @@ -94,7 +140,7 @@ class MainPageEntry: rec_id: RecId display_name: str Metadaten_aktualisierung: datetime.datetime - type: InitRecType + type: EntityType @dc.dataclass(slots=True)