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,
Any,
Final,
Generic,
Literal,
Protocol,
TypeAlias,
@@ -103,8 +104,9 @@ if TYPE_CHECKING:
K = TypeVar("K")
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_SEARCH_WIDGET: bool = False
@@ -224,9 +226,9 @@ class CustomForm(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: ...
@@ -314,24 +316,29 @@ class Session:
@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
row_count: int = 0
@dc.dataclass(slots=True)
class Page_NewInitRec_State:
@dc.dataclass(slots=True, kw_only=True)
class Page_NewInitRec_State(PageState[Module]):
session: Session
@dc.dataclass(slots=True)
class Page_InitRecCompany_State:
@dc.dataclass(slots=True, kw_only=True)
class Page_InitRecCompany_State(PageState[Module]):
session: Session
un_id: RecId | None = None
@dc.dataclass(slots=True)
class Page_InitRecPerson_State:
@dc.dataclass(slots=True, kw_only=True)
class Page_InitRecPerson_State(PageState[Module]):
session: Session
pers_id: RecId | None = None
@@ -341,8 +348,8 @@ class ConsultingType(enum.StrEnum):
INDIVIUAL = enum.auto()
@dc.dataclass(slots=True)
class Page_Consulting_State:
@dc.dataclass(slots=True, kw_only=True)
class Page_Consulting_State(PageState[Module]):
session: Session
un_id: RecId | None = None
pers_id: RecId | None = None
@@ -352,8 +359,8 @@ class Page_Consulting_State:
)
@dc.dataclass(slots=True)
class Page_Consulting_ConsultingSession_State:
@dc.dataclass(slots=True, kw_only=True)
class Page_Consulting_ConsultingSession_State(PageState[Module]):
nutzer_id: UserId # must be set with the session (always known)
nutzer_name: str # must be set with the session (always known)
beratung_id: int | None = None # known and not known entries
@@ -369,20 +376,19 @@ class Page_Consulting_ConsultingSession_State:
rueckmeldung: str | None = None
@dc.dataclass(slots=True)
class Page_Consulting_Table_State:
rows: list[Page_Consulting_ConsultingSession] = dc.field(default_factory=list)
@dc.dataclass(slots=True, kw_only=True)
class Page_Consulting_Table_State(PageState["Page_Consulting_ConsultingSession"]):
row_states: list[Page_Consulting_ConsultingSession_State] = dc.field(default_factory=list)
@dc.dataclass(slots=True)
class Page_CompanyProfile_State:
@dc.dataclass(slots=True, kw_only=True)
class Page_CompanyProfile_State(PageState[Module]):
session: Session
rec_id: RecId | None = None
def _add_widget_to_layout(
form_field: FormField[T],
form_field: FormField[W],
widget: QWidget,
target_layout: QLayout,
widget_only: bool = False,
@@ -407,10 +413,10 @@ def _add_widget_to_layout(
def build_and_add_widget(
form_field: FormField[T],
form_field: FormField[W],
parent_layout: QLayout,
prefix: str = "",
) -> tuple[str, T]:
) -> tuple[str, W]:
no_scroll_filter = NoScrollFilter(parent_layout)
if prefix and 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)
return full_key, cast(T, widget)
return full_key, cast(W, widget)
def _build_ui_recursively(
@@ -1599,7 +1605,7 @@ class AutoForm(QWidget):
def sync_state_to_GUI(self) -> None:
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()
self.STATE.form_data = form_data
@@ -1609,12 +1615,13 @@ class AutoForm(QWidget):
self,
new_state: AutoForm_State,
) -> None:
self.STATE.rec_id = new_state.rec_id
self.STATE.form_data = new_state.form_data
load_state(self.STATE, new_state)
# self.STATE.rec_id = new_state.rec_id
# self.STATE.form_data = new_state.form_data
self.sync_state_to_GUI()
def get_state(self) -> AutoForm_State:
self.sync_GUI_to_state()
self.__sync_GUI_to_state()
return self.STATE
@@ -2685,10 +2692,10 @@ class Page_NewInitRec(QWidget):
logger_gui.debug("[Page -- InitRec Company] State to call: %s", req_state)
self.person_requested.emit(req_state)
def sync_state_to_GUI(self) -> None:
def __sync_state_to_GUI(self) -> None:
pass
def sync_GUI_to_state(self) -> None:
def __sync_GUI_to_state(self) -> None:
pass
def validate(self) -> None:
@@ -2698,10 +2705,10 @@ class Page_NewInitRec(QWidget):
self,
new_state: Page_NewInitRec_State,
) -> None:
self.sync_state_to_GUI()
self.__sync_state_to_GUI()
def get_state(self) -> Page_NewInitRec_State:
self.sync_GUI_to_state()
self.__sync_GUI_to_state()
return self.STATE
def save_data(self) -> None:
@@ -2896,7 +2903,7 @@ class Page_InitRecCompany(QWidget):
def reset_form(self) -> None:
self.auto_form._reset_form()
def sync_state_to_GUI(self) -> None:
def __sync_state_to_GUI(self) -> None:
auto_form_state = AutoForm_State(
session=self.STATE.session,
cfg=self.AUTO_FORM_CFG,
@@ -2905,8 +2912,9 @@ class Page_InitRecCompany(QWidget):
)
self.auto_form.load_state(auto_form_state)
def sync_GUI_to_state(self) -> None:
self.auto_form.sync_GUI_to_state()
def __sync_GUI_to_state(self) -> None:
# TODO behaviour not consistent
self.auto_form.__sync_GUI_to_state()
def validate(self) -> None: ...
@@ -2916,10 +2924,10 @@ class Page_InitRecCompany(QWidget):
) -> None:
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:
self.sync_GUI_to_state()
self.__sync_GUI_to_state()
return self.STATE
def save_data(self) -> None:
@@ -3061,7 +3069,7 @@ class Page_InitRecPerson(QWidget):
def reset_form(self) -> None:
self.auto_form._reset_form()
def sync_state_to_GUI(self) -> None:
def __sync_state_to_GUI(self) -> None:
auto_form_state = AutoForm_State(
session=self.STATE.session,
cfg=self.AUTO_FORM_CFG,
@@ -3070,8 +3078,8 @@ class Page_InitRecPerson(QWidget):
)
self.auto_form.load_state(auto_form_state)
def sync_GUI_to_state(self) -> None:
self.auto_form.sync_GUI_to_state()
def __sync_GUI_to_state(self) -> None:
self.auto_form.__sync_GUI_to_state()
def validate(self) -> None: ...
@@ -3081,10 +3089,10 @@ class Page_InitRecPerson(QWidget):
) -> None:
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:
self.sync_GUI_to_state()
self.__sync_GUI_to_state()
return self.STATE
def save_data(self) -> None:
@@ -3344,7 +3352,7 @@ class Page_CompanyProfile(QWidget):
self.add_btn_pauschal.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:
# just clear and do nothing else
logger_gui.debug("just reset, ID is None")
@@ -3368,7 +3376,7 @@ class Page_CompanyProfile(QWidget):
self.table_master_data.load_data(data)
self.set_company_name(data["Name:"])
def sync_GUI_to_state(self) -> None: ...
def __sync_GUI_to_state(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)
self.STATE.rec_id = new_state.rec_id
self.sync_state_to_GUI()
self.__sync_state_to_GUI()
def get_state(self) -> Any: ...
@@ -3737,6 +3745,7 @@ class Page_Consulting(QWidget):
# container_layout.addSpacing(50)
table_state = Page_Consulting_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.addSpacing(50)
@@ -3827,23 +3836,31 @@ class Page_Consulting(QWidget):
# self.cons_entries.remove(row_widget)
def _debug_get_data_table(self) -> None:
self.table_sessions.get_data()
self.table_sessions.get_state()
# TODO placeholder, check if needed
def sync_state_to_GUI(self) -> None:
def __sync_state_to_GUI(self) -> None:
match self.STATE.cons_type:
case ConsultingType.PAUSCHAL:
self.type_pauschal_btn.setChecked(True)
case ConsultingType.INDIVIUAL:
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(
self,
button_id: int,
) -> None:
logger_gui.debug("[Consulting Page] Trigger type buttons...")
logger_page_consulting.debug("[Consulting Page] Trigger type buttons...")
cons_type: ConsultingType
if button_id == 0:
@@ -3853,7 +3870,7 @@ class Page_Consulting(QWidget):
else:
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
def load_from_state(
@@ -3862,12 +3879,15 @@ class Page_Consulting(QWidget):
) -> None:
# TODO add correct loading behaviour
# TODO add validation (correct type with necessary information)
logger_gui.debug("[Consulting] Loading with request:\n%s", new_state)
self.STATE.un_id = new_state.un_id
self.STATE.pers_id = new_state.pers_id
self.STATE.cons_type = new_state.cons_type
logger_page_consulting.debug("[Consulting] Loading with request:\n%s", new_state)
load_state(self.STATE, new_state)
# self.STATE.un_id = new_state.un_id
# 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):
@@ -3885,12 +3905,15 @@ class Page_Consulting_ConsultingSession(QWidget):
assert isinstance(state, Page_Consulting_ConsultingSession_State)
self.SITE_STATE = site_state
self.STATE = state
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 2, 0, 2) # Sehr kompakt
layout.setSpacing(10)
layout.setAlignment(Qt.AlignmentFlag.AlignVCenter)
ROW_HEIGHT = 70
# ?? widget registry?
user_field_def = FormField[QLineEdit](
"",
FormFieldType.TEXT,
@@ -3962,10 +3985,9 @@ class Page_Consulting_ConsultingSession(QWidget):
layout.setStretch(i, stretch)
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:
# TODO set correct timestamp
def __sync_state_to_GUI(self) -> None:
logger_page_consulting.debug(
"[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.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.ansprechpartner = get_widget_value(self.contact)
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.rueckmeldung = get_widget_value(self.response)
def get_data(self) -> Page_Consulting_ConsultingSession_State:
self.sync_gui_to_state()
def get_state(self) -> Page_Consulting_ConsultingSession_State:
self.__sync_GUI_to_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):
"""child of 'Page_Consulting'"""
@@ -4004,6 +4062,7 @@ class Page_Consulting_Table(QWidget):
self.resize(600, 400)
self.STATE = state
# TODO eliminate site state
self.SITE_STATE = site_state
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(
self,
row_state: Page_Consulting_ConsultingSession_State | None = None,
@@ -4174,47 +4228,76 @@ class Page_Consulting_Table(QWidget):
index = self.container_layout.count() - 1
self.container_layout.insertWidget(index, row)
self.STATE.rows.append(row)
self.STATE.child_modules.append(row)
def remove_row(
self,
table_row_widget: Page_Consulting_ConsultingSession,
) -> None:
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()
def reset(self) -> None:
while self.STATE.rows:
self.remove_row(self.STATE.rows[0])
while self.STATE.child_modules:
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:
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] = []
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"
logger_page_consulting.debug(
"[Page -- Consulting] Table - Child modules: %s",
pformat(self.STATE.child_modules),
)
for row in self.STATE.child_modules:
row_data.append(row.get_state())
if isinstance(widget, Page_Consulting_ConsultingSession):
row_data.append(widget.get_data())
# 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"
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
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...")
self.sync_gui_to_state()
self.__sync_GUI_to_state()
logger_page_consulting.debug("[Page Consulting -- Table]: State = %s", 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
####################################################################