Dev #7

Merged
foefl merged 74 commits from dev into main 2026-07-31 14:19:01 +00:00
2 changed files with 334 additions and 233 deletions
Showing only changes of commit 16ae573687 - Show all commits
+325 -227
View File
@@ -93,6 +93,7 @@ from wce_crm.form_defs import (
)
from wce_crm.logging import (
logger_auto_form,
logger_custom_fields,
logger_get_data,
logger_gui,
logger_page_consulting,
@@ -107,6 +108,7 @@ V = TypeVar("V")
T = TypeVar("T")
W = TypeVar("W", bound="QWidget")
M = TypeVar("M", bound="Module")
S = TypeVar("S", bound="PageState")
DEBUG: bool = True
DEBUG_SEARCH_WIDGET: bool = False
@@ -225,16 +227,45 @@ class CustomForm(Protocol):
def validate_form_data(self) -> list[str]: ...
class Module(Protocol):
def __sync_state_to_GUI(self) -> None: ... # type: ignore
class WrapperModule(Protocol):
def _sync_state_to_GUI(self) -> None:
"""build states for all child modules and use their `load_state` method
to initialise them with the new data
"""
...
def __sync_GUI_to_state(self) -> None: ... # type: ignore
def validate(self) -> None: ...
def _sync_GUI_to_state(self) -> None:
"""call `get_state` method on all child modules and assign respective
properties to state of parent module
"""
...
def load_state(self, new_state: Any) -> None: ...
def get_state(self) -> Any: ...
def get_state(self) -> Any:
"""should always be a simple call to `_sync_GUI_to_state` and then returning
the module's state
Returns
-------
Any
state of the respective module
"""
...
class Module(WrapperModule, Protocol):
def validate(self) -> list[str]:
"""should be a list of field names which could not be validated successfully,
propagates through: parent components must call this on all child modules and extend
their own error list with the return value
Returns
-------
list[str]
list of error fields (form field label)
"""
...
def save_data(self) -> None: ...
@@ -268,26 +299,6 @@ class AutoFormDelete(Protocol):
) -> None: ...
@dc.dataclass(slots=True)
class AutoFormConfig:
model: type[FlatBaseModel]
data_insert: AutoFormInsert
data_update: AutoFormUpdate
data_get: AutoFormGet
data_delete: AutoFormDelete
form_fields: Sequence[FormField]
ignored_keys: Iterable[str] = tuple()
add_buttons: bool = True
@dc.dataclass(slots=True)
class AutoForm_State:
session: Session
cfg: AutoFormConfig
rec_id: RecId | None = None
form_data: dict[str, Any] | None = None
class CustomWidget(QWidget):
def __init__(
self,
@@ -337,6 +348,26 @@ class Page_InitRecCompany_State(PageState[Module]):
un_id: RecId | None = None
@dc.dataclass(slots=True, kw_only=True)
class AutoFormConfig:
model: type[FlatBaseModel]
data_insert: AutoFormInsert
data_update: AutoFormUpdate
data_get: AutoFormGet
data_delete: AutoFormDelete
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]):
session: Session
cfg: AutoFormConfig
rec_id: RecId | None = None
form_data: dict[str, Any] | None = None
@dc.dataclass(slots=True, kw_only=True)
class Page_InitRecPerson_State(PageState[Module]):
session: Session
@@ -415,6 +446,7 @@ def _add_widget_to_layout(
def build_and_add_widget(
form_field: FormField[W],
parent_layout: QLayout,
widget_registry: WidgetRegistry | None = None,
prefix: str = "",
) -> tuple[str, W]:
no_scroll_filter = NoScrollFilter(parent_layout)
@@ -609,6 +641,12 @@ def build_and_add_widget(
_add_widget_to_layout(form_field, widget, parent_layout, widget_only=layout_widget_only)
if widget_registry is not None:
widget_registry[full_key] = {
"widget": widget,
"form_field": form_field,
}
return full_key, cast(W, widget)
@@ -642,11 +680,17 @@ def _build_ui_recursively(
parent_layout.addRow(group_box)
case _:
full_key, widget = build_and_add_widget(field, parent_layout, prefix=prefix)
widget_registry[full_key] = {
"widget": widget,
"form_field": field,
}
full_key, _ = build_and_add_widget(
field,
parent_layout,
widget_registry=widget_registry,
prefix=prefix,
)
# TODO remove
# widget_registry[full_key] = {
# "widget": widget,
# "form_field": field,
# }
keys.append(full_key)
return keys
@@ -1010,6 +1054,7 @@ def validate_form_data(
for key, registry_entry in widget_registry.items():
error_post: bool = False
custom_formatting: bool = False
widget = registry_entry["widget"]
form_field = registry_entry["form_field"]
@@ -1041,16 +1086,10 @@ def validate_form_data(
if widget.currentData() is not None:
continue
error_post = True
elif isinstance(widget, FlexibleDateInput):
date = widget.get_date()
if date and date.isValid():
continue
error_post = True
elif isinstance(widget, FlexibleDateTimeInput):
datetime = widget.get_datetime()
if datetime and datetime.isValid():
continue
error_post = True
elif isinstance(widget, (FlexibleDateInput, FlexibleDateTimeInput)):
valid = widget.validate()
error_post = not valid
custom_formatting = True
elif isinstance(
widget,
(
@@ -1078,6 +1117,7 @@ def validate_form_data(
error = error.replace("&&", "&")
errors.append(error)
# optical feedback to highlight erroneous cells
if not custom_formatting:
widget.setStyleSheet("""
border: 1px solid #ef4444;
background-color: #ffe9e9;
@@ -1088,6 +1128,14 @@ def validate_form_data(
return errors
def validate_child_modules(modules: Iterable[Module]) -> list[str]:
errors: list[str] = []
for mod in modules:
errors.extend(mod.validate())
return errors
class Grunderfassung_SuchWidget(CustomWidget):
def __init__(
self,
@@ -1487,7 +1535,7 @@ class AutoForm(QWidget):
self.reset_btn.setSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
)
self.reset_btn.clicked.connect(self._reset_form)
self.reset_btn.clicked.connect(self.reset_form)
self.layout_btn.addWidget(self.reset_btn)
# delete
self.delete_btn = QPushButton("Eintrag löschen (Strg + L)")
@@ -1527,7 +1575,7 @@ class AutoForm(QWidget):
QTimer.singleShot(timeout + 1, lambda: self.save_btn.setShortcut("Ctrl+S"))
self.save_btn.setText(self.save_btn_txt_enabled)
def _activation_delete(self) -> None:
def _activate_delete(self) -> None:
if self.STATE.rec_id is not None:
self.delete_btn.setEnabled(True)
else:
@@ -1537,7 +1585,7 @@ class AutoForm(QWidget):
assert self.STATE.rec_id is not None, "deletion initialised despite no index set"
self.cfg.data_delete(self.STATE.rec_id)
self.update_triggered.emit()
self._reset_form()
self.reset_form()
def _load_data(
self,
@@ -1550,7 +1598,7 @@ class AutoForm(QWidget):
if lookup_id is None:
# only debugging
lookup_id = self.STATE.rec_id
self._reset_form()
self.reset_form()
logger_auto_form.debug("Lookup ID: %d", lookup_id)
@@ -1572,15 +1620,12 @@ class AutoForm(QWidget):
logger_auto_form.debug("Form data:\n%s", pformat(form_data))
self._set_form_data(form_data)
self.STATE.rec_id = lookup_id
self._activation_delete()
self._activate_delete()
def _validate_form_data(self) -> list[str]:
return validate_form_data(self.widget_registry)
def _reset_form(self) -> None:
def reset_form(self) -> None:
reset_form(self.widget_registry)
self.STATE.rec_id = None
self._activation_delete()
self._activate_delete()
def _get_form_data(self) -> dict[str, Any]:
form_data = get_form_data(self.widget_registry)
@@ -1602,32 +1647,32 @@ class AutoForm(QWidget):
set_form_data(self.widget_registry, data)
def sync_state_to_GUI(self) -> None:
def _sync_state_to_GUI(self) -> None:
self._load_data(self.STATE.rec_id)
def __sync_GUI_to_state(self) -> None:
form_data = self._get_form_data()
self.STATE.form_data = form_data
def _sync_GUI_to_state(self) -> None:
self.STATE.form_data = self._get_form_data()
def validate(self) -> None: ...
def validate(self) -> list[str]:
return validate_form_data(self.widget_registry)
def load_state(
self,
new_state: AutoForm_State,
) -> None:
load_state(self.STATE, new_state)
set_page_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()
self._sync_state_to_GUI()
def get_state(self) -> AutoForm_State:
self.__sync_GUI_to_state()
self._sync_GUI_to_state()
return self.STATE
def save_data(self) -> None:
self._disable_save()
errors = self._validate_form_data()
errors = self.validate()
if errors:
# errors: abort saving and show pop up window
@@ -1696,7 +1741,7 @@ class AutoForm(QWidget):
logger_auto_form.info("Data saved successfully")
self.update_triggered.emit()
self._activation_delete()
self._activate_delete()
finally:
# always re-enable save, even if error occurred
self._enable_save()
@@ -1740,7 +1785,7 @@ class FlexibleDateInput(QWidget):
self.button.clicked.connect(self.show_calendar)
self.calendar.activated.connect(self.set_date)
# signal to validate
self.line_edit.editingFinished.connect(self.validate_input)
self.line_edit.editingFinished.connect(self._validate_input)
self.line_edit.installEventFilter(self)
self.setReadOnly(False)
@@ -1754,10 +1799,10 @@ class FlexibleDateInput(QWidget):
if watched == self.line_edit:
if event.type() == QEvent.Type.KeyPress:
if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter): # type: ignore
self.validate_input()
self._validate_input()
return True # mark event as processed
elif event.type() == QEvent.Type.FocusOut:
self.validate_input()
self._validate_input()
# no return to not block focus change
return super().eventFilter(watched, event)
@@ -1798,7 +1843,7 @@ class FlexibleDateInput(QWidget):
self.line_edit.setInputMask("99.99.9999;_")
self.line_edit.setPlaceholderText("TT.MM.JJJJ")
def validate_input(self) -> None:
def _validate_input(self) -> None:
logger_gui.debug("[Widget -- FlexibleDateInput] Trigger input validation")
if self._read_only:
return
@@ -1824,6 +1869,16 @@ class FlexibleDateInput(QWidget):
else:
self.line_edit.setStyleSheet("")
def validate(self) -> bool:
logger_custom_fields.debug("[CustomField - FlexDate]: Call validate...")
self._validate_input()
date = self.get_date()
if date and date.isValid():
logger_custom_fields.debug("[CustomField - FlexDate]: returning valid")
return True
logger_custom_fields.debug("[CustomField - FlexDate]: returning invalid")
return False
def set_date(
self,
date: QDate,
@@ -1905,7 +1960,7 @@ class FlexibleDateTimeInput(QWidget):
self.calendar.activated.connect(self.set_date_from_calendar)
# signal to validate
self.line_edit.editingFinished.connect(self.validate_input)
self.line_edit.editingFinished.connect(self._validate_input)
self.line_edit.installEventFilter(self)
self.setReadOnly(False)
@@ -1919,10 +1974,10 @@ class FlexibleDateTimeInput(QWidget):
if watched == self.line_edit:
if event.type() == QEvent.Type.KeyPress:
if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter): # type: ignore
self.validate_input()
self._validate_input()
return True # mark event as processed
elif event.type() == QEvent.Type.FocusOut:
self.validate_input()
self._validate_input()
# no return to not block focus change
return super().eventFilter(watched, event)
@@ -1963,7 +2018,7 @@ class FlexibleDateTimeInput(QWidget):
self.line_edit.setInputMask("99.99.9999 99:99;_")
self.line_edit.setPlaceholderText("TT.MM.JJJJ hh:mm")
def validate_input(self) -> None:
def _validate_input(self) -> None:
logger_gui.debug("[Widget -- FlexibleDateTimeInput] Trigger input validation")
if self._read_only:
return
@@ -1990,6 +2045,16 @@ class FlexibleDateTimeInput(QWidget):
else:
self.line_edit.setStyleSheet("")
def validate(self) -> bool:
logger_custom_fields.debug("[CustomField - FlexDatetime]: Call validate...")
self._validate_input()
datetime = self.get_datetime()
if datetime and datetime.isValid():
logger_custom_fields.debug("[CustomField - FlexDatetime]: returning valid")
return True
logger_custom_fields.debug("[CustomField - FlexDatetime]: returning invalid")
return False
def set_date_from_calendar(
self,
date: QDate,
@@ -2692,10 +2757,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:
@@ -2705,10 +2770,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:
@@ -2900,10 +2965,10 @@ class Page_InitRecCompany(QWidget):
logger_gui.debug("[Page -- InitRec Company] State to call: %s", req_state)
self.company_profile_requested.emit(req_state)
def reset_form(self) -> None:
self.auto_form._reset_form()
def reset(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,
@@ -2912,26 +2977,22 @@ class Page_InitRecCompany(QWidget):
)
self.auto_form.load_state(auto_form_state)
def __sync_GUI_to_state(self) -> None:
# TODO behaviour not consistent
self.auto_form.__sync_GUI_to_state()
def validate(self) -> None: ...
def _sync_GUI_to_state(self) -> None:
auto_form_state = self.auto_form.get_state()
self.STATE.un_id = auto_form_state.rec_id
def load_state(
self,
new_state: Page_InitRecCompany_State,
) -> None:
self.STATE.un_id = new_state.un_id
set_page_state(self.STATE, new_state)
self.__sync_state_to_GUI()
self._sync_state_to_GUI()
def get_state(self) -> Page_InitRecCompany_State:
self.__sync_GUI_to_state()
return self.STATE
self._sync_GUI_to_state()
def save_data(self) -> None:
self.auto_form.save_data()
return self.STATE
class Page_InitRecPerson(QWidget):
@@ -3067,9 +3128,9 @@ class Page_InitRecPerson(QWidget):
self.selection_county.update()
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(
session=self.STATE.session,
cfg=self.AUTO_FORM_CFG,
@@ -3078,25 +3139,21 @@ 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 validate(self) -> None: ...
def _sync_GUI_to_state(self) -> None:
auto_form_state = self.auto_form.get_state()
self.STATE.pers_id = auto_form_state.rec_id
def load_state(
self,
new_state: Page_InitRecPerson_State,
) -> None:
self.STATE.pers_id = new_state.pers_id
self.__sync_state_to_GUI()
set_page_state(self.STATE, new_state)
self._sync_state_to_GUI()
def get_state(self) -> Page_InitRecPerson_State:
self.__sync_GUI_to_state()
return self.STATE
self._sync_GUI_to_state()
def save_data(self) -> None:
self.auto_form.save_data()
return self.STATE
class Page_CompanyProfile(QWidget):
@@ -3352,7 +3409,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")
@@ -3376,7 +3433,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: ...
@@ -3387,7 +3444,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: ...
@@ -3715,9 +3772,14 @@ class Page_Consulting(QWidget):
# layout_change_title.addWidget(change_title_btn)
# change_title_btn.clicked.connect(self.set_company_name)
# container_layout.addLayout(layout_change_title)
btn_get_data_table = QPushButton("Call 'get_data' from table")
btn_get_data_table.clicked.connect(self._debug_get_data_table)
btn_get_data_table = QPushButton("Debug call 'get_state' from table and page")
btn_get_data_table.clicked.connect(self._debug_get_state)
container_layout.addWidget(btn_get_data_table)
btn_validate_data_table = QPushButton(
"Debuig call 'validate' from table and page"
)
btn_validate_data_table.clicked.connect(self._debug_validate)
container_layout.addWidget(btn_validate_data_table)
separator2 = QFrame()
separator2.setFrameShape(QFrame.Shape.HLine)
@@ -3747,24 +3809,6 @@ class Page_Consulting(QWidget):
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)
# self.layout_sessions = QVBoxLayout()
# self.layout_sessions.setContentsMargins(0, 0, 0, 0)
# container_layout.addLayout(self.layout_sessions)
# self.btn_add_cons_session = QPushButton("+ Hinzufügen")
# self.btn_add_cons_session.setStyleSheet(
# "color: #0369a1; font-weight: bold; border: 1px dashed #0369a1; padding: 5px;"
# )
# self.btn_add_cons_session.clicked.connect(self.add_cons_entry)
# self.layout_sessions.addWidget(self.btn_add_cons_session)
# self.layout_sessions.addStretch(1)
# self.cons_entries: list[QWidget] = []
# self.layout_sessions.addStretch()
# container_layout.addStretch()
# --- STYLE ---
self.setStyleSheet("""
@@ -3798,70 +3842,6 @@ class Page_Consulting(QWidget):
# }
# """)
# def add_cons_entry(self) -> None:
# logger_gui.debug("[Consulting Page] Adding entry...")
# row_widget = QWidget()
# entry_row = QHBoxLayout(row_widget)
# entry_fields = QHBoxLayout()
# entry_row.addLayout(entry_fields)
# txt1 = QLineEdit()
# txt2 = QPlainTextEdit()
# txt2.setMaximumHeight(100)
# entry_fields.addWidget(txt1)
# entry_fields.addWidget(txt2)
# del_btn = QPushButton("🗑️")
# del_btn.setFixedSize(30, 30)
# # Lambda with default parameter to delete exactly this(!) box
# del_btn.clicked.connect(
# lambda checked=False, widget=row_widget: self.remove_cons_entry(widget)
# )
# entry_row.addWidget(del_btn)
# row_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
# # row_widget.setMaximumHeight(200)
# self.cons_entries.append(row_widget)
# self.layout_sessions.insertWidget(self.layout_sessions.count() - 2, row_widget)
# def remove_cons_entry(
# self,
# row_widget: QWidget,
# ) -> None:
# logger_gui.debug("[Consulting Page]: Delete clicked...")
# self.layout_sessions.removeWidget(row_widget)
# row_widget.deleteLater()
# self.cons_entries.remove(row_widget)
def _debug_get_data_table(self) -> None:
self.table_sessions.get_state()
# TODO placeholder, check if needed
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)
# deactivate if at least one ID is set (type is pre-defined by how
# the site is called)
if self.STATE.un_id or self.STATE.pers_id:
self.type_pauschal_btn.setEnabled(False)
self.type_individual_btn.setEnabled(False)
# 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,
@@ -3879,21 +3859,68 @@ class Page_Consulting(QWidget):
logger_page_consulting.debug("[Consulting Page] Selected cons_type: %s", cons_type)
self.STATE.cons_type = cons_type
def load_from_state(
def _debug_get_state(self) -> None:
logger_page_consulting.debug("[Consulting Page] Call get state on table...")
_ = self.table_sessions.get_state()
logger_page_consulting.debug("[Consulting Page] Call get state on page...")
state = self.get_state()
logger_page_consulting.debug("[Consulting Page] Page state:\n%s", state)
def _debug_validate(self) -> None:
logger_page_consulting.debug("[Consulting Page] Call validate data on table...")
errors = self.table_sessions.validate()
logger_page_consulting.debug("[Consulting Page] Table errors:\n%s", pformat(errors))
logger_page_consulting.debug("[Consulting Page] Call get state on page...")
errors = self.validate()
logger_page_consulting.debug("[Consulting Page] Page errors:\n%s", pformat(errors))
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)
# deactivate if at least one ID is set (type is pre-defined by how
# the site is called)
if self.STATE.un_id or self.STATE.pers_id:
self.type_pauschal_btn.setEnabled(False)
self.type_individual_btn.setEnabled(False)
# call to child modules' syncing method 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' syncing method 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 validate(self) -> list[str]:
errors: list[str] = []
errors.extend(self.table_sessions.validate())
return errors
def load_state(
self,
new_state: Page_Consulting_State,
) -> None:
# TODO add correct loading behaviour
# TODO add validation (correct type with necessary information)
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
set_page_state(self.STATE, new_state)
# child modules
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()
def get_state(self) -> Page_Consulting_State:
self._sync_GUI_to_state()
return self.STATE
class Page_Consulting_ConsultingSession(QWidget):
@@ -3918,40 +3945,91 @@ class Page_Consulting_ConsultingSession(QWidget):
layout.setAlignment(Qt.AlignmentFlag.AlignVCenter)
ROW_HEIGHT = 70
# ?? widget registry?
self.widget_registry: WidgetRegistry = {}
user_field_def = FormField[QLineEdit](
"",
"Nutzername",
FormFieldType.TEXT,
readonly=True,
placeholder="wird über aktiven Nutzer bei Anlage gesetzt",
enable_uuid_key=True,
)
_, self.user_field = build_and_add_widget(user_field_def, layout)
# ?? date or datetime?
timestamp_def = FormField[FlexibleDateTimeInput]("", FormFieldType.DATETIME_FLEXIBLE)
_, self.timestamp = build_and_add_widget(timestamp_def, layout)
timestamp_def = FormField[FlexibleDateTimeInput](
"Zeitstempel",
FormFieldType.DATETIME_FLEXIBLE,
required=True,
enable_uuid_key=True,
)
_, self.timestamp = build_and_add_widget(
timestamp_def,
layout,
widget_registry=self.widget_registry,
)
self.timestamp.set_pydatetime(datetime.datetime.now())
contact_def = FormField[QLineEdit]("", FormFieldType.TEXT, placeholder="Freitext")
_, self.contact = build_and_add_widget(contact_def, layout)
contact_def = FormField[QLineEdit](
"Kontaktperson",
FormFieldType.TEXT,
placeholder="Freitext",
required=True,
enable_uuid_key=True,
)
_, self.contact = build_and_add_widget(
contact_def,
layout,
widget_registry=self.widget_registry,
)
contact_type_def = FormField[QComboBox](
"",
"Kontakttyp",
FormFieldType.DROPDOWN,
placeholder="sollte Dropdown sein",
options=[("Persönlich", None), ("Telefon", None), ("Mail", None)],
required=True,
enable_uuid_key=True,
)
_, self.contact_type = build_and_add_widget(
contact_type_def,
layout,
widget_registry=self.widget_registry,
)
_, self.contact_type = build_and_add_widget(contact_type_def, layout)
crm_link_def = FormField[QLineEdit]("", FormFieldType.TEXT, placeholder="CRM Links")
_, self.crm_link = build_and_add_widget(crm_link_def, layout)
crm_link_def = FormField[QLineEdit](
"CRM-Verknüpfung",
FormFieldType.TEXT,
placeholder="CRM Links",
required=True,
enable_uuid_key=True,
)
_, self.crm_link = build_and_add_widget(
crm_link_def,
layout,
widget_registry=self.widget_registry,
)
plain_text_def = FormField[QPlainTextEdit]("", FormFieldType.LONGTEXT)
_, self.plain_text = build_and_add_widget(plain_text_def, layout)
plain_text_def = FormField[QPlainTextEdit](
"Anmerkungen",
FormFieldType.LONGTEXT,
enable_uuid_key=True,
)
_, self.plain_text = build_and_add_widget(
plain_text_def,
layout,
widget_registry=self.widget_registry,
)
response_def = FormField[QPlainTextEdit]("", FormFieldType.LONGTEXT)
_, self.response = build_and_add_widget(response_def, layout)
response_def = FormField[QPlainTextEdit](
"Rückmeldung",
FormFieldType.LONGTEXT,
enable_uuid_key=True,
)
_, self.response = build_and_add_widget(
response_def,
layout,
widget_registry=self.widget_registry,
)
self.btn_delete = QPushButton("")
self.btn_delete.setFlat(True) # Entfernt den blockigen Rahmen
@@ -3991,9 +4069,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:
def _sync_state_to_GUI(self) -> None:
logger_page_consulting.debug(
"[Page Consulting -- Consulting Session]: State = %s", self.STATE
)
@@ -4005,7 +4083,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)
@@ -4014,12 +4092,16 @@ class Page_Consulting_ConsultingSession(QWidget):
self.STATE.rueckmeldung = get_widget_value(self.response)
def get_state(self) -> Page_Consulting_ConsultingSession_State:
self.__sync_GUI_to_state()
self._sync_GUI_to_state()
return self.STATE
def validate(self) -> None:
pass
def validate(self) -> list[str]:
logger_page_consulting.debug(
">>>>> Call validate on Session. Widget registry:\n%s",
pprint_registry(self.widget_registry),
)
return validate_form_data(self.widget_registry)
def save_data(self) -> None:
pass
@@ -4028,13 +4110,13 @@ class Page_Consulting_ConsultingSession(QWidget):
self,
new_state: Page_Consulting_ConsultingSession_State,
) -> None:
load_state(self.STATE, new_state)
self.__sync_state_to_GUI()
set_page_state(self.STATE, new_state)
self._sync_state_to_GUI()
def load_state(
target: T,
source: T,
def set_page_state(
target: S,
source: S,
deep: bool = False,
) -> None:
"""
@@ -4047,6 +4129,9 @@ def load_state(
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:
@@ -4248,12 +4333,12 @@ class Page_Consulting_Table(QWidget):
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] = []
@@ -4281,7 +4366,7 @@ class Page_Consulting_Table(QWidget):
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)
@@ -4294,12 +4379,16 @@ class Page_Consulting_Table(QWidget):
logger_page_consulting.debug(
"[Page -- Consulting] Table: Call loading from state, %s", new_state
)
self.STATE.row_states = new_state.row_states
set_page_state(self.STATE, new_state)
self.__sync_state_to_GUI()
self._sync_state_to_GUI()
def validate(self) -> None:
pass
def validate(self) -> list[str]:
errors: list[str] = []
for m in self.STATE.child_modules:
errors.extend(m.validate())
return errors
def save_data(self) -> None:
pass
@@ -4578,7 +4667,7 @@ class MainWindow(QMainWindow):
self,
new_state: Page_Consulting_State,
) -> None:
self.consulting_page.load_from_state(new_state)
self.consulting_page.load_state(new_state)
self._navigate_to_target(self.consulting_page)
# --- MENÜ LOGIK ---
@@ -4632,6 +4721,15 @@ def qt_message_handler(mode, context, message):
logger_gui.critical(message)
if TYPE_CHECKING:
# TODO add all relevant modules
# enable static type checking for the protocol
_m1: type[Module] = Page_Consulting
_m2: type[Module] = Page_Consulting_Table
_m3: type[Module] = Page_Consulting_ConsultingSession
_w1: type[WrapperModule] = Page_InitRecCompany
_w2: type[WrapperModule] = Page_InitRecPerson
if __name__ == "__main__":
sys.excepthook = global_exception_handler
qInstallMessageHandler(qt_message_handler)
+3
View File
@@ -37,6 +37,9 @@ logger_get_data.setLevel(logging.DEBUG)
logger_auto_form = logger_gui.getChild("get_data_auto_form")
logger_auto_form.setLevel(logging.DEBUG)
logger_custom_fields = logger_gui.getChild("custom_fields")
logger_custom_fields.setLevel(logging.DEBUG)
logger_page_consulting = logger_gui.getChild("page_consulting")
logger_page_consulting.setLevel(logging.DEBUG)