prepare backend interaction scheme

This commit is contained in:
2026-07-09 16:20:01 +02:00
parent 39d4327e19
commit 14357df8cd
4 changed files with 242 additions and 194 deletions
+49 -1
View File
@@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import datetime import datetime
from pprint import pformat
from typing import TYPE_CHECKING, Any, cast from typing import TYPE_CHECKING, Any, cast
import polars as pl import polars as pl
@@ -8,11 +9,12 @@ import sqlalchemy as sql
from wce_crm import db from wce_crm import db
from wce_crm.constants import TIMEZONE_CEST 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.logging import logger_back as logger
from wce_crm.types import CompanyInfo, ContactPersonInfo, InitRecType, MainPageEntry from wce_crm.types import CompanyInfo, ContactPersonInfo, InitRecType, MainPageEntry
if TYPE_CHECKING: 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( 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") 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 # // main page interaction
def _main_page_get_company_list() -> list[MainPageEntry]: def _main_page_get_company_list() -> list[MainPageEntry]:
logger.debug("[Call backend] get_company_list") logger.debug("[Call backend] get_company_list")
+9 -5
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
import datetime import datetime
import json import json
from typing import Annotated, Any, Final from typing import Annotated, Any, Final, Literal
from pydantic import ( from pydantic import (
AwareDatetime, AwareDatetime,
@@ -346,23 +346,27 @@ class Grunderfassung_Sprachen(BaseModel):
# ** Consulting # ** Consulting
class Beratungsgespraech(BaseModel): class Beratungsgespraech_Vorgang(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True) model_config = ConfigDict(str_strip_whitespace=True)
vorgang_id: int | None
un_id: int | None un_id: int | None
pers_id: int | None pers_id: int | None
titel: str titel: str
beratungs_typ: str beratungs_typ: Literal["pauschal", "individuell"]
# default in SQLAlchemy with lambda and timezone-aware datetime # default in SQLAlchemy with lambda and timezone-aware datetime
erstellt: AwareDatetime | None = None erstellt: AwareDatetime | None = None
aktualisiert: AwareDatetime | None = None # see above 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) model_config = ConfigDict(str_strip_whitespace=True)
vorgang_id: int vorgang_id: int | None
beratung_id: int | None
nutzer_id: int nutzer_id: int
nutzer_name: str nutzer_name: str
zeitstempel: AwareDatetime zeitstempel: AwareDatetime
+183 -188
View File
@@ -6,6 +6,7 @@ import datetime
import enum import enum
import re import re
import sys import sys
import time
import traceback import traceback
from collections import defaultdict from collections import defaultdict
from collections.abc import Container, Iterable, Sequence 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.constants import TIMEZONE_CEST
from wce_crm.data_models import ( from wce_crm.data_models import (
COLUMN_SEP, COLUMN_SEP,
Beratungsgespraech_Einzelgespraech,
Beratungsgespraech_Vorgang,
FlatBaseModel, FlatBaseModel,
Grunderfassung_Personen, Grunderfassung_Personen,
Grunderfassung_Unternehmen, Grunderfassung_Unternehmen,
@@ -95,10 +98,9 @@ from wce_crm.logging import (
logger_gui, logger_gui,
logger_page_consulting, logger_page_consulting,
) )
from wce_crm.types import ExtMaId, RecId, UserId
if TYPE_CHECKING: if TYPE_CHECKING:
... from wce_crm.types import ConsId, ExtMaId, RecId, UserId
K = TypeVar("K") K = TypeVar("K")
V = TypeVar("V") V = TypeVar("V")
@@ -343,15 +345,17 @@ class Page_InitRecPerson_State(PageState[Module]):
class ConsultingType(enum.StrEnum): class ConsultingType(enum.StrEnum):
PAUSCHAL = enum.auto() PAUSCHAL = enum.auto()
INDIVIUAL = enum.auto() INDIVIDUAL = enum.auto()
@dc.dataclass(slots=True, kw_only=True) @dc.dataclass(slots=True, kw_only=True)
class Page_Consulting_State(PageState[Module]): class Page_Consulting_State(PageState[Module]):
session: Session session: Session
vorgang_id: ConsId | None
un_id: RecId | None = None un_id: RecId | None = None
pers_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( cons_sessions: list[Page_Consulting_ConsultingSession_State] = dc.field(
default_factory=list default_factory=list
) )
@@ -359,15 +363,16 @@ class Page_Consulting_State(PageState[Module]):
@dc.dataclass(slots=True, kw_only=True) @dc.dataclass(slots=True, kw_only=True)
class Page_Consulting_ConsultingSession_State(PageState[Module]): class Page_Consulting_ConsultingSession_State(PageState[Module]):
session: Session
nutzer_id: UserId # must be set with the session (always known) nutzer_id: UserId # must be set with the session (always known)
nutzer_name: str # 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( zeitstempel: datetime.datetime = dc.field(
default_factory=lambda: datetime.datetime.now(tz=datetime.UTC) default_factory=lambda: datetime.datetime.now(tz=datetime.UTC)
) )
ansprechpartner: str | None = None ansprechpartner: str | None = None
interaction_scheme: str | None = None kommunikationsweg: str | None = None
topic: str | None = ( thema_crm_matrix: str | None = (
None # TODO should be linked to something, target unclear (need specification by customer) None # TODO should be linked to something, target unclear (need specification by customer)
) )
anmerkungen: str | None = None anmerkungen: str | None = None
@@ -376,6 +381,7 @@ class Page_Consulting_ConsultingSession_State(PageState[Module]):
@dc.dataclass(slots=True, kw_only=True) @dc.dataclass(slots=True, kw_only=True)
class Page_Consulting_Table_State(PageState["Page_Consulting_ConsultingSession"]): class Page_Consulting_Table_State(PageState["Page_Consulting_ConsultingSession"]):
session: Session
row_states: list[Page_Consulting_ConsultingSession_State] = dc.field(default_factory=list) 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( self.search_widget_trigger = cast(
QLineEdit, search_widget.company_widgets["ma_unternehmensname"] 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: def _request_company_profile(self) -> None:
req_state = Page_CompanyProfile_State( req_state = Page_CompanyProfile_State(
@@ -2993,23 +2952,6 @@ class Page_InitRecPerson(QWidget):
container_layout.addSpacing(15) container_layout.addSpacing(15)
# --- CUSTOM LOGIC --- # --- 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' # ** 'Bundesland' only if 'Inland' selected in 'Stammdaten'
person_location = search_widgets_by_key( person_location = search_widgets_by_key(
self.auto_form.widget_registry, f"Stammdaten{COLUMN_SEP}aufenthaltsort" 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.setProperty("styleClass", "stempel")
self.selection_county.setEnabled(False) 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: def _custom_county_selection(self, idx: int) -> None:
value = self.person_location.itemData(idx) value = self.person_location.itemData(idx)
if value == "Inland": if value == "Inland":
@@ -3207,7 +3146,7 @@ class Page_CompanyProfile(QWidget):
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
) )
left_layout.addWidget(master_data_label) left_layout.addWidget(master_data_label)
# self.table_master_data = MasterDataTable()
default_labels = tuple(self.COMPANY_FEATURES_MASTER_DATA.values()) default_labels = tuple(self.COMPANY_FEATURES_MASTER_DATA.values())
self.table_master_data = MasterDataFormWidget(default_labels) self.table_master_data = MasterDataFormWidget(default_labels)
left_layout.addWidget(self.table_master_data) left_layout.addWidget(self.table_master_data)
@@ -3254,7 +3193,7 @@ class Page_CompanyProfile(QWidget):
bottom_right_layout.addWidget(individual_consultancy_label) bottom_right_layout.addWidget(individual_consultancy_label)
self.add_btn_individual = QPushButton("+ Hinzufügen") self.add_btn_individual = QPushButton("+ Hinzufügen")
self.add_btn_individual.clicked.connect( self.add_btn_individual.clicked.connect(
lambda: self._request_consulting(ConsultingType.INDIVIUAL) lambda: self._request_consulting(ConsultingType.INDIVIDUAL)
) )
self.add_btn_individual.setSizePolicy( self.add_btn_individual.setSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
@@ -3317,7 +3256,10 @@ class Page_CompanyProfile(QWidget):
"tried to call consulting from comp profile without set ID" "tried to call consulting from comp profile without set ID"
) )
new_state = Page_Consulting_State( 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) self.consulting_requested.emit(new_state)
@@ -3340,7 +3282,7 @@ class Page_CompanyProfile(QWidget):
self._enable_add_btns() 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.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" assert ma_id is not None, "no ma_id obtained"
comp_info = backend.initrec_comp_search_get_info(ma_id) 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) back_requested = Signal(bool) # back to init rec (bool: reset yes/no)
# TODO update should trigger something else # TODO update should trigger something else
# more like listening to 'child' ("Beratungen") # 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__( def __init__(
self, self,
@@ -3606,7 +3548,11 @@ class Page_Consulting(QWidget):
): ):
super().__init__() super().__init__()
# state management # 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 # Hauptlayout der Seite
outer_layout = QHBoxLayout(self) outer_layout = QHBoxLayout(self)
@@ -3695,6 +3641,19 @@ class Page_Consulting(QWidget):
container_layout.addSpacing(30) 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_group_box = QGroupBox("Typ")
type_layout = QVBoxLayout(type_group_box) type_layout = QVBoxLayout(type_group_box)
container_layout.addWidget(type_group_box) container_layout.addWidget(type_group_box)
@@ -3712,8 +3671,8 @@ class Page_Consulting(QWidget):
type_layout.addWidget(self.type_individual_btn) type_layout.addWidget(self.type_individual_btn)
# container_layout.addSpacing(50) # container_layout.addSpacing(50)
table_state = Page_Consulting_Table_State() table_state = Page_Consulting_Table_State(session=self.STATE.session)
self.table_sessions = Page_Consulting_Table(site_state=self.STATE, state=table_state) self.table_sessions = Page_Consulting_Table(state=table_state)
self.STATE.child_modules.append(self.table_sessions) self.STATE.child_modules.append(self.table_sessions)
container_layout.addWidget(self.table_sessions) container_layout.addWidget(self.table_sessions)
@@ -3748,6 +3707,28 @@ class Page_Consulting(QWidget):
# font-weight: bold; # 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( def type_consulting_changed(
self, self,
@@ -3759,12 +3740,12 @@ class Page_Consulting(QWidget):
if button_id == 0: if button_id == 0:
cons_type = ConsultingType.PAUSCHAL cons_type = ConsultingType.PAUSCHAL
elif button_id == 1: elif button_id == 1:
cons_type = ConsultingType.INDIVIUAL cons_type = ConsultingType.INDIVIDUAL
else: else:
raise RuntimeError(f"No consulting type defined for button id: {button_id}") raise RuntimeError(f"No consulting type defined for button id: {button_id}")
logger_page_consulting.debug("[Consulting Page] Selected cons_type: %s", cons_type) 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: def _debug_get_state(self) -> None:
logger_page_consulting.debug("[Consulting Page] Call get state on table...") 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)) logger_page_consulting.debug("[Consulting Page] Page errors:\n%s", pformat(errors))
def _sync_state_to_GUI(self) -> None: def _sync_state_to_GUI(self) -> None:
match self.STATE.cons_type: match self.STATE.beratungs_typ:
case ConsultingType.PAUSCHAL: case ConsultingType.PAUSCHAL:
self.type_pauschal_btn.setChecked(True) self.type_pauschal_btn.setChecked(True)
case ConsultingType.INDIVIUAL: case ConsultingType.INDIVIDUAL:
self.type_individual_btn.setChecked(True) self.type_individual_btn.setChecked(True)
# deactivate if at least one ID is set (type is pre-defined by how # deactivate if at least one ID is set (type is pre-defined by how
@@ -3803,6 +3784,7 @@ class Page_Consulting(QWidget):
# this method # this method
table_state = self.table_sessions.get_state() table_state = self.table_sessions.get_state()
self.STATE.cons_sessions = table_state.row_states self.STATE.cons_sessions = table_state.row_states
self._activate_delete()
def validate(self) -> list[str]: def validate(self) -> list[str]:
errors: 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) logger_page_consulting.debug("[Consulting] Loading with request:\n%s", new_state)
set_page_state(self.STATE, new_state) set_page_state(self.STATE, new_state)
# child modules # 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.table_sessions.load_state(table_state)
self._sync_state_to_GUI() self._sync_state_to_GUI()
@@ -3827,6 +3812,98 @@ class Page_Consulting(QWidget):
return self.STATE 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): class Page_Consulting_ConsultingSession(QWidget):
"""child of 'Page_Consulting_DynamicTable'""" """child of 'Page_Consulting_DynamicTable'"""
@@ -3835,13 +3912,10 @@ class Page_Consulting_ConsultingSession(QWidget):
def __init__( def __init__(
self, self,
site_state: Page_Consulting_State,
state: Page_Consulting_ConsultingSession_State, state: Page_Consulting_ConsultingSession_State,
) -> None: ) -> None:
super().__init__() super().__init__()
assert isinstance(site_state, Page_Consulting_State)
assert isinstance(state, Page_Consulting_ConsultingSession_State) assert isinstance(state, Page_Consulting_ConsultingSession_State)
self.SITE_STATE = site_state
self.STATE = state self.STATE = state
layout = QHBoxLayout(self) layout = QHBoxLayout(self)
@@ -3866,7 +3940,7 @@ class Page_Consulting_ConsultingSession(QWidget):
"Zeitstempel", "Zeitstempel",
FormFieldType.DATETIME_FLEXIBLE, FormFieldType.DATETIME_FLEXIBLE,
required=True, required=True,
enable_uuid_key=True, key="zeitstempel",
) )
_, self.timestamp = build_and_add_widget( _, self.timestamp = build_and_add_widget(
timestamp_def, timestamp_def,
@@ -3880,7 +3954,7 @@ class Page_Consulting_ConsultingSession(QWidget):
FormFieldType.TEXT, FormFieldType.TEXT,
placeholder="Freitext", placeholder="Freitext",
required=True, required=True,
enable_uuid_key=True, key="ansprechpartner",
) )
_, self.contact = build_and_add_widget( _, self.contact = build_and_add_widget(
contact_def, contact_def,
@@ -3893,7 +3967,7 @@ class Page_Consulting_ConsultingSession(QWidget):
FormFieldType.DROPDOWN, FormFieldType.DROPDOWN,
options=[("Persönlich", None), ("Telefon", None), ("Mail", None)], options=[("Persönlich", None), ("Telefon", None), ("Mail", None)],
required=True, required=True,
enable_uuid_key=True, key="kommunikationsweg",
) )
_, self.contact_type = build_and_add_widget( _, self.contact_type = build_and_add_widget(
contact_type_def, contact_type_def,
@@ -3906,7 +3980,7 @@ class Page_Consulting_ConsultingSession(QWidget):
FormFieldType.TEXT, FormFieldType.TEXT,
placeholder="CRM Links", placeholder="CRM Links",
required=True, required=True,
enable_uuid_key=True, key="thema_crm_matrix",
) )
_, self.crm_link = build_and_add_widget( _, self.crm_link = build_and_add_widget(
crm_link_def, crm_link_def,
@@ -3917,7 +3991,7 @@ class Page_Consulting_ConsultingSession(QWidget):
plain_text_def = FormField[QPlainTextEdit]( plain_text_def = FormField[QPlainTextEdit](
"Anmerkungen", "Anmerkungen",
FormFieldType.LONGTEXT, FormFieldType.LONGTEXT,
enable_uuid_key=True, key="anmerkungen",
) )
_, self.plain_text = build_and_add_widget( _, self.plain_text = build_and_add_widget(
plain_text_def, plain_text_def,
@@ -3928,7 +4002,7 @@ class Page_Consulting_ConsultingSession(QWidget):
response_def = FormField[QPlainTextEdit]( response_def = FormField[QPlainTextEdit](
"Rückmeldung", "Rückmeldung",
FormFieldType.LONGTEXT, FormFieldType.LONGTEXT,
enable_uuid_key=True, key="rueckmeldung",
) )
_, self.response = build_and_add_widget( _, self.response = build_and_add_widget(
response_def, 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.user_field, self.STATE.nutzer_name)
set_widget_value(self.timestamp, self.STATE.zeitstempel.astimezone(TIMEZONE_CEST)) set_widget_value(self.timestamp, self.STATE.zeitstempel.astimezone(TIMEZONE_CEST))
set_widget_value(self.contact, self.STATE.ansprechpartner) set_widget_value(self.contact, self.STATE.ansprechpartner)
set_widget_value(self.contact_type, self.STATE.interaction_scheme) set_widget_value(self.contact_type, self.STATE.kommunikationsweg)
set_widget_value(self.crm_link, self.STATE.topic) set_widget_value(self.crm_link, self.STATE.thema_crm_matrix)
set_widget_value(self.plain_text, self.STATE.anmerkungen) set_widget_value(self.plain_text, self.STATE.anmerkungen)
set_widget_value(self.response, self.STATE.rueckmeldung) set_widget_value(self.response, self.STATE.rueckmeldung)
def _sync_GUI_to_state(self) -> None: def _sync_GUI_to_state(self) -> None:
self.STATE.zeitstempel = self.timestamp.get_pydatetime() self.STATE.zeitstempel = self.timestamp.get_pydatetime()
self.STATE.ansprechpartner = get_widget_value(self.contact) self.STATE.ansprechpartner = get_widget_value(self.contact)
self.STATE.interaction_scheme = get_widget_value(self.contact_type) self.STATE.kommunikationsweg = get_widget_value(self.contact_type)
self.STATE.topic = get_widget_value(self.crm_link) # TODO: change to correct link 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.anmerkungen = get_widget_value(self.plain_text)
self.STATE.rueckmeldung = get_widget_value(self.response) self.STATE.rueckmeldung = get_widget_value(self.response)
@@ -4050,17 +4126,14 @@ class Page_Consulting_Table(QWidget):
def __init__( def __init__(
self, self,
site_state: Page_Consulting_State,
state: Page_Consulting_Table_State, state: Page_Consulting_Table_State,
) -> None: ) -> None:
super().__init__() super().__init__()
self.STATE = state
self.setWindowTitle("Native Dynamische Liste") self.setWindowTitle("Native Dynamische Liste")
self.resize(600, 400) self.resize(600, 400)
self.STATE = state
# TODO eliminate site state
self.SITE_STATE = site_state
main_layout = QVBoxLayout(self) main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(0, 15, 0, 15) main_layout.setContentsMargins(0, 15, 0, 15)
main_layout.setSpacing(10) main_layout.setSpacing(10)
@@ -4215,11 +4288,13 @@ class Page_Consulting_Table(QWidget):
) -> None: ) -> None:
if row_state is None: if row_state is None:
row_state = Page_Consulting_ConsultingSession_State( row_state = Page_Consulting_ConsultingSession_State(
nutzer_id=self.SITE_STATE.session.user_id, session=self.STATE.session,
nutzer_name=self.SITE_STATE.session.user_name, 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) row.delete_request.connect(self.remove_row)
index = self.container_layout.count() - 1 index = self.container_layout.count() - 1
@@ -4254,16 +4329,6 @@ class Page_Consulting_Table(QWidget):
for row in self.STATE.child_modules: for row in self.STATE.child_modules:
row_data.append(row.get_state()) 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), ( assert len(row_data) == len(self.STATE.child_modules), (
"mismatch in row count and data length" "mismatch in row count and data length"
) )
@@ -4674,21 +4739,6 @@ def GUI_validation_error_handling(
msg_box.exec() 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): class EditButtons(QWidget):
def __init__( def __init__(
self, self,
@@ -4698,8 +4748,6 @@ class EditButtons(QWidget):
layout_btn = QHBoxLayout(self) layout_btn = QHBoxLayout(self)
layout_btn.setContentsMargins(0, 0, 0, 0) layout_btn.setContentsMargins(0, 0, 0, 0)
# main_layout.addLayout(layout_btn) # TODO add in component
# save # save
self.save_btn_txt_enabled = "Speichern (Strg + S)" self.save_btn_txt_enabled = "Speichern (Strg + S)"
self.save_btn_txt_disabled = "Wird gespeichert..." self.save_btn_txt_disabled = "Wird gespeichert..."
@@ -4707,9 +4755,7 @@ class EditButtons(QWidget):
self.save_btn.setShortcut("Ctrl+S") self.save_btn.setShortcut("Ctrl+S")
self.save_btn.setFixedHeight(50) self.save_btn.setFixedHeight(50)
self.save_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) 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) layout_btn.addWidget(self.save_btn)
# reset # reset
self.reset_button: QPushButton | None = None self.reset_button: QPushButton | None = None
if add_reset_btn: if add_reset_btn:
@@ -4719,66 +4765,15 @@ class EditButtons(QWidget):
self.reset_btn.setSizePolicy( self.reset_btn.setSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
) )
# reset_btn.clicked.connect(reset_form) # TODO add in component
layout_btn.addWidget(self.reset_btn) layout_btn.addWidget(self.reset_btn)
# delete # delete
self.delete_btn = QPushButton("Eintrag löschen (Strg + L)") self.delete_btn = QPushButton("Eintrag löschen (Strg + L)")
self.delete_btn.setShortcut("Ctrl+L") self.delete_btn.setShortcut("Ctrl+L")
self.delete_btn.setFixedHeight(50) self.delete_btn.setFixedHeight(50)
self.delete_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) 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) 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): 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:
+1
View File
@@ -10,6 +10,7 @@ if TYPE_CHECKING:
UserId: TypeAlias = int UserId: TypeAlias = int
RecId: TypeAlias = int RecId: TypeAlias = int
ConsId: TypeAlias = int
ExtMaId: TypeAlias = int ExtMaId: TypeAlias = int
ExtAnId: TypeAlias = int ExtAnId: TypeAlias = int