Dev #7

Merged
foefl merged 74 commits from dev into main 2026-07-31 14:19:01 +00:00
Showing only changes of commit 770ae11ad6 - Show all commits
+165 -82
View File
@@ -16,6 +16,7 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
Final, Final,
Generic,
Literal, Literal,
Protocol, Protocol,
TypeAlias, TypeAlias,
@@ -103,8 +104,9 @@ if TYPE_CHECKING:
K = TypeVar("K") K = TypeVar("K")
V = TypeVar("V") V = TypeVar("V")
T = TypeVar("T", bound="QWidget") T = TypeVar("T")
W = TypeVar("W", bound="QWidget")
M = TypeVar("M", bound="Module")
DEBUG: bool = True DEBUG: bool = True
DEBUG_SEARCH_WIDGET: bool = False DEBUG_SEARCH_WIDGET: bool = False
@@ -224,9 +226,9 @@ class CustomForm(Protocol):
class Module(Protocol): class Module(Protocol):
def sync_state_to_GUI(self) -> None: ... def __sync_state_to_GUI(self) -> None: ... # type: ignore
def sync_GUI_to_state(self) -> None: ... def __sync_GUI_to_state(self) -> None: ... # type: ignore
def validate(self) -> None: ... def validate(self) -> None: ...
@@ -314,24 +316,29 @@ class Session:
@dc.dataclass(slots=True) @dc.dataclass(slots=True)
class Page_MainPage_State: class PageState(Generic[M]):
child_modules: list[M] = dc.field(default_factory=list)
@dc.dataclass(slots=True, kw_only=True)
class Page_MainPage_State(PageState[Module]):
session: Session session: Session
row_count: int = 0 row_count: int = 0
@dc.dataclass(slots=True) @dc.dataclass(slots=True, kw_only=True)
class Page_NewInitRec_State: class Page_NewInitRec_State(PageState[Module]):
session: Session session: Session
@dc.dataclass(slots=True) @dc.dataclass(slots=True, kw_only=True)
class Page_InitRecCompany_State: class Page_InitRecCompany_State(PageState[Module]):
session: Session session: Session
un_id: RecId | None = None un_id: RecId | None = None
@dc.dataclass(slots=True) @dc.dataclass(slots=True, kw_only=True)
class Page_InitRecPerson_State: class Page_InitRecPerson_State(PageState[Module]):
session: Session session: Session
pers_id: RecId | None = None pers_id: RecId | None = None
@@ -341,8 +348,8 @@ class ConsultingType(enum.StrEnum):
INDIVIUAL = enum.auto() INDIVIUAL = enum.auto()
@dc.dataclass(slots=True) @dc.dataclass(slots=True, kw_only=True)
class Page_Consulting_State: class Page_Consulting_State(PageState[Module]):
session: Session session: Session
un_id: RecId | None = None un_id: RecId | None = None
pers_id: RecId | None = None pers_id: RecId | None = None
@@ -352,8 +359,8 @@ class Page_Consulting_State:
) )
@dc.dataclass(slots=True) @dc.dataclass(slots=True, kw_only=True)
class Page_Consulting_ConsultingSession_State: class Page_Consulting_ConsultingSession_State(PageState[Module]):
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 = None # known and not known entries
@@ -369,20 +376,19 @@ class Page_Consulting_ConsultingSession_State:
rueckmeldung: str | None = None rueckmeldung: str | None = None
@dc.dataclass(slots=True) @dc.dataclass(slots=True, kw_only=True)
class Page_Consulting_Table_State: class Page_Consulting_Table_State(PageState["Page_Consulting_ConsultingSession"]):
rows: list[Page_Consulting_ConsultingSession] = dc.field(default_factory=list)
row_states: list[Page_Consulting_ConsultingSession_State] = dc.field(default_factory=list) row_states: list[Page_Consulting_ConsultingSession_State] = dc.field(default_factory=list)
@dc.dataclass(slots=True) @dc.dataclass(slots=True, kw_only=True)
class Page_CompanyProfile_State: class Page_CompanyProfile_State(PageState[Module]):
session: Session session: Session
rec_id: RecId | None = None rec_id: RecId | None = None
def _add_widget_to_layout( def _add_widget_to_layout(
form_field: FormField[T], form_field: FormField[W],
widget: QWidget, widget: QWidget,
target_layout: QLayout, target_layout: QLayout,
widget_only: bool = False, widget_only: bool = False,
@@ -407,10 +413,10 @@ def _add_widget_to_layout(
def build_and_add_widget( def build_and_add_widget(
form_field: FormField[T], form_field: FormField[W],
parent_layout: QLayout, parent_layout: QLayout,
prefix: str = "", prefix: str = "",
) -> tuple[str, T]: ) -> tuple[str, W]:
no_scroll_filter = NoScrollFilter(parent_layout) no_scroll_filter = NoScrollFilter(parent_layout)
if prefix and form_field.key: if prefix and form_field.key:
full_key = f"{prefix}{COLUMN_SEP}{form_field.key}" if prefix else form_field.key full_key = f"{prefix}{COLUMN_SEP}{form_field.key}" if prefix else form_field.key
@@ -603,7 +609,7 @@ def build_and_add_widget(
_add_widget_to_layout(form_field, widget, parent_layout, widget_only=layout_widget_only) _add_widget_to_layout(form_field, widget, parent_layout, widget_only=layout_widget_only)
return full_key, cast(T, widget) return full_key, cast(W, widget)
def _build_ui_recursively( def _build_ui_recursively(
@@ -1599,7 +1605,7 @@ class AutoForm(QWidget):
def sync_state_to_GUI(self) -> None: def sync_state_to_GUI(self) -> None:
self._load_data(self.STATE.rec_id) self._load_data(self.STATE.rec_id)
def sync_GUI_to_state(self) -> None: def __sync_GUI_to_state(self) -> None:
form_data = self._get_form_data() form_data = self._get_form_data()
self.STATE.form_data = form_data self.STATE.form_data = form_data
@@ -1609,12 +1615,13 @@ class AutoForm(QWidget):
self, self,
new_state: AutoForm_State, new_state: AutoForm_State,
) -> None: ) -> None:
self.STATE.rec_id = new_state.rec_id load_state(self.STATE, new_state)
self.STATE.form_data = new_state.form_data # self.STATE.rec_id = new_state.rec_id
# self.STATE.form_data = new_state.form_data
self.sync_state_to_GUI() self.sync_state_to_GUI()
def get_state(self) -> AutoForm_State: def get_state(self) -> AutoForm_State:
self.sync_GUI_to_state() self.__sync_GUI_to_state()
return self.STATE return self.STATE
@@ -2685,10 +2692,10 @@ class Page_NewInitRec(QWidget):
logger_gui.debug("[Page -- InitRec Company] State to call: %s", req_state) logger_gui.debug("[Page -- InitRec Company] State to call: %s", req_state)
self.person_requested.emit(req_state) self.person_requested.emit(req_state)
def sync_state_to_GUI(self) -> None: def __sync_state_to_GUI(self) -> None:
pass pass
def sync_GUI_to_state(self) -> None: def __sync_GUI_to_state(self) -> None:
pass pass
def validate(self) -> None: def validate(self) -> None:
@@ -2698,10 +2705,10 @@ class Page_NewInitRec(QWidget):
self, self,
new_state: Page_NewInitRec_State, new_state: Page_NewInitRec_State,
) -> None: ) -> None:
self.sync_state_to_GUI() self.__sync_state_to_GUI()
def get_state(self) -> Page_NewInitRec_State: def get_state(self) -> Page_NewInitRec_State:
self.sync_GUI_to_state() self.__sync_GUI_to_state()
return self.STATE return self.STATE
def save_data(self) -> None: def save_data(self) -> None:
@@ -2896,7 +2903,7 @@ class Page_InitRecCompany(QWidget):
def reset_form(self) -> None: def reset_form(self) -> None:
self.auto_form._reset_form() self.auto_form._reset_form()
def sync_state_to_GUI(self) -> None: def __sync_state_to_GUI(self) -> None:
auto_form_state = AutoForm_State( auto_form_state = AutoForm_State(
session=self.STATE.session, session=self.STATE.session,
cfg=self.AUTO_FORM_CFG, cfg=self.AUTO_FORM_CFG,
@@ -2905,8 +2912,9 @@ class Page_InitRecCompany(QWidget):
) )
self.auto_form.load_state(auto_form_state) self.auto_form.load_state(auto_form_state)
def sync_GUI_to_state(self) -> None: def __sync_GUI_to_state(self) -> None:
self.auto_form.sync_GUI_to_state() # TODO behaviour not consistent
self.auto_form.__sync_GUI_to_state()
def validate(self) -> None: ... def validate(self) -> None: ...
@@ -2916,10 +2924,10 @@ class Page_InitRecCompany(QWidget):
) -> None: ) -> None:
self.STATE.un_id = new_state.un_id self.STATE.un_id = new_state.un_id
self.sync_state_to_GUI() self.__sync_state_to_GUI()
def get_state(self) -> Page_InitRecCompany_State: def get_state(self) -> Page_InitRecCompany_State:
self.sync_GUI_to_state() self.__sync_GUI_to_state()
return self.STATE return self.STATE
def save_data(self) -> None: def save_data(self) -> None:
@@ -3061,7 +3069,7 @@ class Page_InitRecPerson(QWidget):
def reset_form(self) -> None: def reset_form(self) -> None:
self.auto_form._reset_form() self.auto_form._reset_form()
def sync_state_to_GUI(self) -> None: def __sync_state_to_GUI(self) -> None:
auto_form_state = AutoForm_State( auto_form_state = AutoForm_State(
session=self.STATE.session, session=self.STATE.session,
cfg=self.AUTO_FORM_CFG, cfg=self.AUTO_FORM_CFG,
@@ -3070,8 +3078,8 @@ class Page_InitRecPerson(QWidget):
) )
self.auto_form.load_state(auto_form_state) self.auto_form.load_state(auto_form_state)
def sync_GUI_to_state(self) -> None: def __sync_GUI_to_state(self) -> None:
self.auto_form.sync_GUI_to_state() self.auto_form.__sync_GUI_to_state()
def validate(self) -> None: ... def validate(self) -> None: ...
@@ -3081,10 +3089,10 @@ class Page_InitRecPerson(QWidget):
) -> None: ) -> None:
self.STATE.pers_id = new_state.pers_id self.STATE.pers_id = new_state.pers_id
self.sync_state_to_GUI() self.__sync_state_to_GUI()
def get_state(self) -> Page_InitRecPerson_State: def get_state(self) -> Page_InitRecPerson_State:
self.sync_GUI_to_state() self.__sync_GUI_to_state()
return self.STATE return self.STATE
def save_data(self) -> None: def save_data(self) -> None:
@@ -3344,7 +3352,7 @@ class Page_CompanyProfile(QWidget):
self.add_btn_pauschal.setEnabled(True) self.add_btn_pauschal.setEnabled(True)
self.add_btn_individual.setEnabled(True) self.add_btn_individual.setEnabled(True)
def sync_state_to_GUI(self) -> None: def __sync_state_to_GUI(self) -> None:
if self.STATE.rec_id is None: if self.STATE.rec_id is None:
# just clear and do nothing else # just clear and do nothing else
logger_gui.debug("just reset, ID is None") logger_gui.debug("just reset, ID is None")
@@ -3368,7 +3376,7 @@ class Page_CompanyProfile(QWidget):
self.table_master_data.load_data(data) self.table_master_data.load_data(data)
self.set_company_name(data["Name:"]) self.set_company_name(data["Name:"])
def sync_GUI_to_state(self) -> None: ... def __sync_GUI_to_state(self) -> None: ...
def validate(self) -> None: ... def validate(self) -> None: ...
@@ -3379,7 +3387,7 @@ class Page_CompanyProfile(QWidget):
logger_gui.debug("Called function to load with ID: %s", new_state.rec_id) logger_gui.debug("Called function to load with ID: %s", new_state.rec_id)
self.STATE.rec_id = new_state.rec_id self.STATE.rec_id = new_state.rec_id
self.sync_state_to_GUI() self.__sync_state_to_GUI()
def get_state(self) -> Any: ... def get_state(self) -> Any: ...
@@ -3737,6 +3745,7 @@ class Page_Consulting(QWidget):
# container_layout.addSpacing(50) # container_layout.addSpacing(50)
table_state = Page_Consulting_Table_State() table_state = Page_Consulting_Table_State()
self.table_sessions = Page_Consulting_Table(site_state=self.STATE, state=table_state) self.table_sessions = Page_Consulting_Table(site_state=self.STATE, state=table_state)
self.STATE.child_modules.append(self.table_sessions)
container_layout.addWidget(self.table_sessions) container_layout.addWidget(self.table_sessions)
# container_layout.addSpacing(50) # container_layout.addSpacing(50)
@@ -3827,23 +3836,31 @@ class Page_Consulting(QWidget):
# self.cons_entries.remove(row_widget) # self.cons_entries.remove(row_widget)
def _debug_get_data_table(self) -> None: def _debug_get_data_table(self) -> None:
self.table_sessions.get_data() self.table_sessions.get_state()
# TODO placeholder, check if needed # TODO placeholder, check if needed
def sync_state_to_GUI(self) -> None: def __sync_state_to_GUI(self) -> None:
match self.STATE.cons_type: match self.STATE.cons_type:
case ConsultingType.PAUSCHAL: case ConsultingType.PAUSCHAL:
self.type_pauschal_btn.setChecked(True) self.type_pauschal_btn.setChecked(True)
case ConsultingType.INDIVIUAL: case ConsultingType.INDIVIUAL:
self.type_individual_btn.setChecked(True) self.type_individual_btn.setChecked(True)
def sync_GUI_to_state(self) -> None: ... # call to child modules not needed because these are initialised by their own
# "load_state" method which again calls the modules sync method
def __sync_GUI_to_state(self) -> None:
# call to child modules not needed because their syncing method is called when
# the "get_state" method is called --> this has to be managed within this method
table_state = self.table_sessions.get_state()
self.STATE.cons_sessions = table_state.row_states
def type_consulting_changed( def type_consulting_changed(
self, self,
button_id: int, button_id: int,
) -> None: ) -> None:
logger_gui.debug("[Consulting Page] Trigger type buttons...") logger_page_consulting.debug("[Consulting Page] Trigger type buttons...")
cons_type: ConsultingType cons_type: ConsultingType
if button_id == 0: if button_id == 0:
@@ -3853,7 +3870,7 @@ class Page_Consulting(QWidget):
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_gui.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.cons_type = cons_type
def load_from_state( def load_from_state(
@@ -3862,12 +3879,15 @@ class Page_Consulting(QWidget):
) -> None: ) -> None:
# TODO add correct loading behaviour # TODO add correct loading behaviour
# TODO add validation (correct type with necessary information) # TODO add validation (correct type with necessary information)
logger_gui.debug("[Consulting] Loading with request:\n%s", new_state) logger_page_consulting.debug("[Consulting] Loading with request:\n%s", new_state)
self.STATE.un_id = new_state.un_id load_state(self.STATE, new_state)
self.STATE.pers_id = new_state.pers_id # self.STATE.un_id = new_state.un_id
self.STATE.cons_type = new_state.cons_type # self.STATE.pers_id = new_state.pers_id
# self.STATE.cons_type = new_state.cons_type
table_state = Page_Consulting_Table_State(row_states=self.STATE.cons_sessions)
self.table_sessions.load_state(table_state)
self.sync_state_to_GUI() self.__sync_state_to_GUI()
class Page_Consulting_ConsultingSession(QWidget): class Page_Consulting_ConsultingSession(QWidget):
@@ -3885,12 +3905,15 @@ class Page_Consulting_ConsultingSession(QWidget):
assert isinstance(state, Page_Consulting_ConsultingSession_State) assert isinstance(state, Page_Consulting_ConsultingSession_State)
self.SITE_STATE = site_state self.SITE_STATE = site_state
self.STATE = state self.STATE = state
layout = QHBoxLayout(self) layout = QHBoxLayout(self)
layout.setContentsMargins(0, 2, 0, 2) # Sehr kompakt layout.setContentsMargins(0, 2, 0, 2) # Sehr kompakt
layout.setSpacing(10) layout.setSpacing(10)
layout.setAlignment(Qt.AlignmentFlag.AlignVCenter) layout.setAlignment(Qt.AlignmentFlag.AlignVCenter)
ROW_HEIGHT = 70 ROW_HEIGHT = 70
# ?? widget registry?
user_field_def = FormField[QLineEdit]( user_field_def = FormField[QLineEdit](
"", "",
FormFieldType.TEXT, FormFieldType.TEXT,
@@ -3962,10 +3985,9 @@ class Page_Consulting_ConsultingSession(QWidget):
layout.setStretch(i, stretch) layout.setStretch(i, stretch)
self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
self.sync_state_to_gui() # ?? only needed if entry is from database? self.__sync_state_to_GUI() # ?? only needed if entry is from database?
def sync_state_to_gui(self) -> None: def __sync_state_to_GUI(self) -> None:
# TODO set correct timestamp
logger_page_consulting.debug( logger_page_consulting.debug(
"[Page Consulting -- Consulting Session]: State = %s", self.STATE "[Page Consulting -- Consulting Session]: State = %s", self.STATE
) )
@@ -3977,7 +3999,7 @@ class Page_Consulting_ConsultingSession(QWidget):
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.interaction_scheme = get_widget_value(self.contact_type)
@@ -3985,11 +4007,47 @@ class Page_Consulting_ConsultingSession(QWidget):
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)
def get_data(self) -> Page_Consulting_ConsultingSession_State: def get_state(self) -> Page_Consulting_ConsultingSession_State:
self.sync_gui_to_state() self.__sync_GUI_to_state()
return self.STATE return self.STATE
def validate(self) -> None:
pass
def save_data(self) -> None:
pass
def load_state(
self,
new_state: Page_Consulting_ConsultingSession_State,
) -> None:
load_state(self.STATE, new_state)
self.__sync_state_to_GUI()
def load_state(
target: T,
source: T,
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):
new_value = getattr(source, field.name)
if deep:
new_value = copy.deepcopy(new_value)
setattr(target, field.name, new_value)
class Page_Consulting_Table(QWidget): class Page_Consulting_Table(QWidget):
"""child of 'Page_Consulting'""" """child of 'Page_Consulting'"""
@@ -4004,6 +4062,7 @@ class Page_Consulting_Table(QWidget):
self.resize(600, 400) self.resize(600, 400)
self.STATE = state self.STATE = state
# TODO eliminate site state
self.SITE_STATE = site_state self.SITE_STATE = site_state
main_layout = QVBoxLayout(self) main_layout = QVBoxLayout(self)
@@ -4154,11 +4213,6 @@ class Page_Consulting_Table(QWidget):
# } # }
# """) # """)
if DEBUG:
# add rows for debugging
self.add_row()
self.add_row()
def add_row( def add_row(
self, self,
row_state: Page_Consulting_ConsultingSession_State | None = None, row_state: Page_Consulting_ConsultingSession_State | None = None,
@@ -4174,47 +4228,76 @@ class Page_Consulting_Table(QWidget):
index = self.container_layout.count() - 1 index = self.container_layout.count() - 1
self.container_layout.insertWidget(index, row) self.container_layout.insertWidget(index, row)
self.STATE.rows.append(row) self.STATE.child_modules.append(row)
def remove_row( def remove_row(
self, self,
table_row_widget: Page_Consulting_ConsultingSession, table_row_widget: Page_Consulting_ConsultingSession,
) -> None: ) -> None:
self.container_layout.removeWidget(table_row_widget) self.container_layout.removeWidget(table_row_widget)
self.STATE.rows.remove(table_row_widget) self.STATE.child_modules.remove(table_row_widget)
table_row_widget.deleteLater() table_row_widget.deleteLater()
def reset(self) -> None: def reset(self) -> None:
while self.STATE.rows: while self.STATE.child_modules:
self.remove_row(self.STATE.rows[0]) self.remove_row(self.STATE.child_modules[0])
def sync_state_to_gui(self) -> None: def __sync_state_to_GUI(self) -> None:
self.reset()
for row_state in self.STATE.row_states: for row_state in self.STATE.row_states:
self.add_row(row_state) self.add_row(row_state)
def sync_gui_to_state(self) -> None: def __sync_GUI_to_state(self) -> None:
logger_page_consulting.debug("[Page -- Consulting] Table: Call syncing GUI to state")
row_data: list[Page_Consulting_ConsultingSession_State] = [] row_data: list[Page_Consulting_ConsultingSession_State] = []
for i in range(self.container_layout.count() - 1): logger_page_consulting.debug(
item = self.container_layout.itemAt(i) "[Page -- Consulting] Table - Child modules: %s",
assert item, "item not found" pformat(self.STATE.child_modules),
widget = item.widget() )
assert widget, "no widget obtained" for row in self.STATE.child_modules:
row_data.append(row.get_state())
if isinstance(widget, Page_Consulting_ConsultingSession): # TODO remove
row_data.append(widget.get_data()) # 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"
assert len(row_data) == len(self.STATE.rows), "mismatch in row count and data length" # 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"
)
self.STATE.row_states = row_data self.STATE.row_states = row_data
def get_data(self) -> Page_Consulting_Table_State: def get_state(self) -> Page_Consulting_Table_State:
logger_page_consulting.debug("[Page Consulting -- Table] Syncing GUI to state...") logger_page_consulting.debug("[Page Consulting -- Table] Syncing GUI to state...")
self.sync_gui_to_state() self.__sync_GUI_to_state()
logger_page_consulting.debug("[Page Consulting -- Table]: State = %s", self.STATE) logger_page_consulting.debug("[Page Consulting -- Table]: State = %s", self.STATE)
return self.STATE return self.STATE
def load_state(
self,
new_state: Page_Consulting_Table_State,
) -> None:
logger_page_consulting.debug(
"[Page -- Consulting] Table: Call loading from state, %s", new_state
)
self.STATE.row_states = new_state.row_states
self.__sync_state_to_GUI()
def validate(self) -> None:
pass
def save_data(self) -> None:
pass
#################################################################### ####################################################################