From 76ff68cb43c3d60e59c937c868ff13be5ccafb8a Mon Sep 17 00:00:00 2001 From: foefl Date: Thu, 25 Jun 2026 16:26:38 +0200 Subject: [PATCH] data loading of company profiles --- src/wce_crm/backend/backend.py | 14 +-- src/wce_crm/gui.py | 170 +++++++++++++++++++++++++-------- src/wce_crm/types.py | 3 + 3 files changed, 143 insertions(+), 44 deletions(-) diff --git a/src/wce_crm/backend/backend.py b/src/wce_crm/backend/backend.py index 3ccadf3..bbf3524 100644 --- a/src/wce_crm/backend/backend.py +++ b/src/wce_crm/backend/backend.py @@ -2,7 +2,7 @@ from __future__ import annotations import dataclasses as dc import datetime -from typing import Any, TypeAlias, TypedDict, cast +from typing import TYPE_CHECKING, Any, TypedDict, cast import polars as pl import sqlalchemy as sql @@ -10,9 +10,11 @@ import sqlalchemy as sql from wce_crm import db from wce_crm.logging import logger_back as logger -InitRecId: TypeAlias = int +if TYPE_CHECKING: + from wce_crm.types import ExtAnId, ExtMaId, InitRecId +# // externals class CompanyInfo(TypedDict): ma_id: str wce_id: str @@ -100,7 +102,7 @@ def initrec_comp_search_choices() -> tuple[tuple[str, int], ...]: def initrec_comp_search_get_info( - ma_id: int, + ma_id: ExtMaId, ) -> CompanyInfo: logger.debug("[Call backend] comp_search_get_info") df = db.DF_CRM_MASTER.filter(pl.col.ma_id == ma_id) @@ -112,7 +114,7 @@ def initrec_comp_search_get_info( def initrec_comp_contact_person_search_choices( - ma_id: int | None, + ma_id: ExtMaId | None, use_both_names: bool, ) -> tuple[tuple[str, int], ...]: # TODO no reload functionality @@ -140,7 +142,7 @@ def initrec_comp_contact_person_search_choices( def initrec_comp_contact_person_search_get_info( - an_id: int, + an_id: ExtAnId, ) -> ContactPersonInfo: logger.debug("[Call backend] contact_person_search_get_info") df = db.DF_CONTACT_PERSON.filter(pl.col.an_id == an_id) @@ -151,6 +153,7 @@ def initrec_comp_contact_person_search_get_info( return cast(ContactPersonInfo, df.row(0, named=True)) +# // internals def initrec_insert_initial_recording( data: dict[str, Any], ) -> InitRecId: @@ -210,7 +213,6 @@ def initrec_delete_initial_recording( @dc.dataclass(slots=True) class FrontpageCompany: erfassung_id: InitRecId - # ma_id: int name: str Metadaten_aktualisierung: datetime.datetime is_company: bool diff --git a/src/wce_crm/gui.py b/src/wce_crm/gui.py index 2453b64..f3a1be6 100644 --- a/src/wce_crm/gui.py +++ b/src/wce_crm/gui.py @@ -11,7 +11,7 @@ from collections import defaultdict from collections.abc import Container, Iterable, Sequence from pathlib import Path from pprint import pformat -from typing import Any, Final, Protocol, TypeAlias, TypedDict, TypeVar, cast +from typing import TYPE_CHECKING, Any, Final, Protocol, TypeAlias, TypedDict, TypeVar, cast from typing_extensions import override from pydantic import ( @@ -70,6 +70,10 @@ from wce_crm.logging import ( logger_get_data, logger_gui, ) +from wce_crm.types import ExtMaId, InitRecId + +if TYPE_CHECKING: + ... K = TypeVar("K") V = TypeVar("V") @@ -1315,7 +1319,7 @@ class AutoForm(QWidget): self.delete_btn.clicked.connect(self.delete_data) self.layout_btn.addWidget(self.delete_btn) - self.current_id: int = -1 + self.current_id: InitRecId = -1 logger_auto_form.debug( "Initialised Auto Form Widget. Registry:%s", @@ -2072,7 +2076,7 @@ class NewEntrySelect_view(QWidget): """view for new entry for initial recording ("Grunderfassung")""" back_requested = Signal() # Signal back button - company_requested = Signal() # Signal "Unternehmen" + company_requested = Signal(bool) # Signal "Unternehmen" (bool: reset yes/no) person_requested = Signal() # Signal "Individualperson" def __init__(self): @@ -2094,7 +2098,7 @@ class NewEntrySelect_view(QWidget): btn_company = QPushButton("Unternehmen →") btn_company.setFixedWidth(300) btn_company.setFixedHeight(40) - btn_company.clicked.connect(lambda: self.company_requested.emit()) + btn_company.clicked.connect(lambda: self.company_requested.emit(True)) btn_person = QPushButton("Individualperson →") btn_person.setFixedWidth(300) @@ -2148,7 +2152,7 @@ class Page_InitRecCompany(QWidget): back_main_requested = Signal() # back to main page back_requested = Signal() # back button update_triggered = Signal() # form saved (data changed for front page) - company_profile_requested = Signal() + company_profile_requested = Signal(InitRecId) def __init__(self): super().__init__() @@ -2172,7 +2176,9 @@ class Page_InitRecCompany(QWidget): header_layout.setContentsMargins(0, 0, 0, 10) comp_profile_btn = QPushButton("Unternehmensprofil") - comp_profile_btn.clicked.connect(lambda: self.company_profile_requested.emit()) + comp_profile_btn.clicked.connect( + lambda: self.company_profile_requested.emit(self.current_id) + ) comp_profile_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) comp_profile_btn.setMinimumWidth(250) # comp_profile_btn.setMaximumWidth(200) @@ -2281,6 +2287,10 @@ class Page_InitRecCompany(QWidget): def reset_form(self) -> None: self.auto_form.reset_form() + @property + def current_id(self) -> InitRecId: + return self.auto_form.current_id + class Page_InitRecPerson(QWidget): back_main_requested = Signal() # back to main page @@ -2413,12 +2423,27 @@ class Page_InitRecPerson(QWidget): class Page_CompanyProfile(QWidget): back_main_requested = Signal() # back to main page - back_requested = Signal() # back button + back_requested = Signal(bool) # back to init rec (bool: reset yes/no) # TODO update should trigger something else - update_triggered = Signal() # form saved (data changed for front page) + # update_triggered = Signal() # form saved (data changed for front page) def __init__(self): super().__init__() + + # state management + self.current_id: InitRecId = -1 + self.COMPANY_FETAURES_MASTER_DATA: dict[str, str] = { + "ma_unternehmensname": "Name:", + "ma_branche": "Branche:", + "ma_strasse": "Straße:", + "ma_hausnummer": "Hausnummer:", + "ma_plz": "PLZ:", + "ma_ort": "Ort:", + "ma_website": "Webseite:", + "ma_mail": "E-Mail:", + "ma_telefonnummer": "Telefon:", + } + # Hauptlayout der Seite outer_layout = QHBoxLayout(self) vert_layout = QVBoxLayout() @@ -2444,7 +2469,7 @@ class Page_CompanyProfile(QWidget): back_btn_main.setMinimumWidth(200) back_btn_main.setMaximumWidth(200) back_btn_step = QPushButton("← Zurück") - back_btn_step.clicked.connect(lambda: self.back_requested.emit()) + back_btn_step.clicked.connect(lambda: self.back_requested.emit(False)) back_btn_step.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) back_btn_step.setMinimumWidth(200) back_btn_step.setMaximumWidth(200) @@ -2521,7 +2546,8 @@ class Page_CompanyProfile(QWidget): ) left_layout.addWidget(master_data_label) # self.table_master_data = MasterDataTable() - self.table_master_data = MasterDataFormWidget() + default_labels = tuple(self.COMPANY_FETAURES_MASTER_DATA.values()) + self.table_master_data = MasterDataFormWidget(default_labels) left_layout.addWidget(self.table_master_data) left_layout.addWidget(QLabel("PLATZHALTER Vermerke: Welche Notizen?")) @@ -2575,6 +2601,7 @@ class Page_CompanyProfile(QWidget): container_layout.addLayout(main_hor_layout) container_layout.addStretch() + # --- STYLE --- self.setStyleSheet(""" QFrame#Segment { background-color: #ffffff; @@ -2606,7 +2633,7 @@ class Page_CompanyProfile(QWidget): # } # """) - def set_company_name(self, name: str = "") -> None: + def set_company_name(self, name: str) -> None: if not name: name = self.change_title_text.text() if not name: @@ -2615,6 +2642,42 @@ class Page_CompanyProfile(QWidget): txt = f"Unternehmen: {name}" self.title_company_name.setText(txt) + def load_from_id( + self, + id_: InitRecId, + ) -> None: + assert id_ is not None, "tried to load with InitRecId None" + logger_gui.debug(f"Called function to load with ID: {id_}") + + if id_ < 0: + # just clear and do nothing else + logger_gui.debug("just reset with ID < 0") + self.table_master_data.reset() + self.set_company_name("") + self.current_id = id_ + return + + if id_ == self.current_id: + logger_gui.debug("ID did not change. Do nothing") + self.current_id = id_ + return + + # TODO retrieve and process data to load + init_rec = backend.initrec_get_initial_recording(id_) + ma_id = cast(ExtMaId, init_rec["Partnersuche__un_suche"]) + assert ma_id is not None, "no ma_id obtained" + comp_info = backend.initrec_comp_search_get_info(ma_id) + + data: dict[str, Any] = {} + + for feature, value in comp_info.items(): + if feature in self.COMPANY_FETAURES_MASTER_DATA: + data[self.COMPANY_FETAURES_MASTER_DATA[feature]] = value + + self.table_master_data.load_data(data) + self.set_company_name(data["Name:"]) + self.current_id = id_ + class MasterDataTable(QWidget): def __init__(self): @@ -2705,8 +2768,13 @@ class MasterDataTable(QWidget): class MasterDataFormWidget(QWidget): - def __init__(self): + def __init__( + self, + default_labels: Sequence[str], + ): super().__init__() + self.default_labels = default_labels + self.default_data = {label: None for label in self.default_labels} # main layout main_layout = QVBoxLayout(self) @@ -2716,37 +2784,55 @@ class MasterDataFormWidget(QWidget): segment_frame = QFrame() segment_frame.setObjectName("Segment") # form layout - form_layout = QFormLayout(segment_frame) - form_layout.setSpacing(15) # Vertikaler Abstand zwischen den Zeilen - form_layout.setLabelAlignment(Qt.AlignmentFlag.AlignLeft) # Merkmale linksbündig + self.form_layout = QFormLayout(segment_frame) + self.form_layout.setSpacing(15) + self.form_layout.setLabelAlignment(Qt.AlignmentFlag.AlignLeft) # load data - self.load_data(form_layout) + # self.load_data() - main_layout.addWidget(segment_frame)n + main_layout.addWidget(segment_frame) self.style_ui() + self.reset() - def load_data(self, form_layout): - daten = [ - ("Anwendungsname", "MyQtApp Pro"), - ("Version", "2.4.1-beta"), - ("Datenbank-Pfad", "/var/lib/sqlite/data.db"), - ("Letzter Login", "2026-06-25 09:30"), - ("Benutzer-ID", "USR-88291"), - ("Verschlüsselung", "AES-256-GCM"), - ] - simulated_retrieved_data = {x[0]: x[1] for x in daten} + def load_data( + self, + data: dict[str, Any], + ) -> None: + self.clear_current_layout() + # daten = [ + # ("Anwendungsname", "MyQtApp Pro"), + # ("Version", "2.4.1-beta"), + # ("Datenbank-Pfad", "/var/lib/sqlite/data.db"), + # ("Letzter Login", "2026-06-25 09:30"), + # ("Benutzer-ID", "USR-88291"), + # ("Verschlüsselung", "AES-256-GCM"), + # ] + # simulated_retrieved_data = {x[0]: x[1] for x in daten} - for merkmal, wert in simulated_retrieved_data.items(): - lbl_merkmal = QLabel(f"{merkmal}") - lbl_merkmal.setObjectName("Merkmal") + for feature, value in data.items(): + label_feature = QLabel(f"{feature}") + label_feature.setObjectName("Merkmal") - lbl_wert = QLabel(str(wert)) - lbl_wert.setObjectName("Wert") + if value is None: + value = "" - lbl_wert.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + label_value = QLabel(str(value)) + label_value.setObjectName("Wert") + label_value.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) - form_layout.addRow(lbl_merkmal, lbl_wert) + self.form_layout.addRow(label_feature, label_value) + + def clear_current_layout(self): + while self.form_layout.count() > 0: + item = self.form_layout.takeAt(0) + assert item is not None + widget = item.widget() + if widget is not None: + widget.deleteLater() + + def reset(self) -> None: + self.load_data(self.default_data) def style_ui(self): self.setStyleSheet(""" @@ -2771,7 +2857,8 @@ class MasterDataFormWidget(QWidget): /* Linke Spalte (Merkmale): Grau und Fett */ QLabel#Merkmal { font-weight: bold; - color: #64748b; + /* color: #64748b; */ + color: #475569; } /* Rechte Spalte (Werte): Dunkel und gut lesbar */ @@ -2999,15 +3086,22 @@ class MainWindow(QMainWindow): def show_new_entry_select(self): self.stack.setCurrentWidget(self.new_entry_select) - def show_page_initrec_company(self): - self.initrec_company.reset_form() + def show_page_initrec_company(self, reset: bool): + # TODO: allow for "back" on profile to sustain current view + # TODO: reset if approached from "other side" by "new entry" + if reset: + self.initrec_company.reset_form() self.stack.setCurrentWidget(self.initrec_company) def show_page_initrec_person(self): self.initrec_person.reset_form() self.stack.setCurrentWidget(self.initrec_person) - def show_page_company_profile(self) -> None: + def show_page_company_profile( + self, + id_: InitRecId, + ) -> None: + self.company_profile.load_from_id(id_) self.stack.setCurrentWidget(self.company_profile) # --- MENÜ LOGIK --- diff --git a/src/wce_crm/types.py b/src/wce_crm/types.py index ff8d5fa..edec434 100644 --- a/src/wce_crm/types.py +++ b/src/wce_crm/types.py @@ -5,5 +5,8 @@ from typing import TYPE_CHECKING, TypeAlias if TYPE_CHECKING: import polars as pl +InitRecId: TypeAlias = int +ExtMaId: TypeAlias = int +ExtAnId: TypeAlias = int PolarsSchema: TypeAlias = dict[str, type["pl.DataType"]]