Dev #7

Merged
foefl merged 74 commits from dev into main 2026-07-31 14:19:01 +00:00
3 changed files with 143 additions and 44 deletions
Showing only changes of commit 76ff68cb43 - Show all commits
+8 -6
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
import dataclasses as dc import dataclasses as dc
import datetime import datetime
from typing import Any, TypeAlias, TypedDict, cast from typing import TYPE_CHECKING, Any, TypedDict, cast
import polars as pl import polars as pl
import sqlalchemy as sql import sqlalchemy as sql
@@ -10,9 +10,11 @@ import sqlalchemy as sql
from wce_crm import db from wce_crm import db
from wce_crm.logging import logger_back as logger 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): class CompanyInfo(TypedDict):
ma_id: str ma_id: str
wce_id: str wce_id: str
@@ -100,7 +102,7 @@ def initrec_comp_search_choices() -> tuple[tuple[str, int], ...]:
def initrec_comp_search_get_info( def initrec_comp_search_get_info(
ma_id: int, ma_id: ExtMaId,
) -> CompanyInfo: ) -> CompanyInfo:
logger.debug("[Call backend] comp_search_get_info") logger.debug("[Call backend] comp_search_get_info")
df = db.DF_CRM_MASTER.filter(pl.col.ma_id == ma_id) 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( def initrec_comp_contact_person_search_choices(
ma_id: int | None, ma_id: ExtMaId | None,
use_both_names: bool, use_both_names: bool,
) -> tuple[tuple[str, int], ...]: ) -> tuple[tuple[str, int], ...]:
# TODO no reload functionality # TODO no reload functionality
@@ -140,7 +142,7 @@ def initrec_comp_contact_person_search_choices(
def initrec_comp_contact_person_search_get_info( def initrec_comp_contact_person_search_get_info(
an_id: int, an_id: ExtAnId,
) -> ContactPersonInfo: ) -> ContactPersonInfo:
logger.debug("[Call backend] contact_person_search_get_info") logger.debug("[Call backend] contact_person_search_get_info")
df = db.DF_CONTACT_PERSON.filter(pl.col.an_id == an_id) 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)) return cast(ContactPersonInfo, df.row(0, named=True))
# // internals
def initrec_insert_initial_recording( def initrec_insert_initial_recording(
data: dict[str, Any], data: dict[str, Any],
) -> InitRecId: ) -> InitRecId:
@@ -210,7 +213,6 @@ def initrec_delete_initial_recording(
@dc.dataclass(slots=True) @dc.dataclass(slots=True)
class FrontpageCompany: class FrontpageCompany:
erfassung_id: InitRecId erfassung_id: InitRecId
# ma_id: int
name: str name: str
Metadaten_aktualisierung: datetime.datetime Metadaten_aktualisierung: datetime.datetime
is_company: bool is_company: bool
+132 -38
View File
@@ -11,7 +11,7 @@ from collections import defaultdict
from collections.abc import Container, Iterable, Sequence from collections.abc import Container, Iterable, Sequence
from pathlib import Path from pathlib import Path
from pprint import pformat 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 typing_extensions import override
from pydantic import ( from pydantic import (
@@ -70,6 +70,10 @@ from wce_crm.logging import (
logger_get_data, logger_get_data,
logger_gui, logger_gui,
) )
from wce_crm.types import ExtMaId, InitRecId
if TYPE_CHECKING:
...
K = TypeVar("K") K = TypeVar("K")
V = TypeVar("V") V = TypeVar("V")
@@ -1315,7 +1319,7 @@ class AutoForm(QWidget):
self.delete_btn.clicked.connect(self.delete_data) self.delete_btn.clicked.connect(self.delete_data)
self.layout_btn.addWidget(self.delete_btn) self.layout_btn.addWidget(self.delete_btn)
self.current_id: int = -1 self.current_id: InitRecId = -1
logger_auto_form.debug( logger_auto_form.debug(
"Initialised Auto Form Widget. Registry:%s", "Initialised Auto Form Widget. Registry:%s",
@@ -2072,7 +2076,7 @@ class NewEntrySelect_view(QWidget):
"""view for new entry for initial recording ("Grunderfassung")""" """view for new entry for initial recording ("Grunderfassung")"""
back_requested = Signal() # Signal back button 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" person_requested = Signal() # Signal "Individualperson"
def __init__(self): def __init__(self):
@@ -2094,7 +2098,7 @@ class NewEntrySelect_view(QWidget):
btn_company = QPushButton("Unternehmen →") btn_company = QPushButton("Unternehmen →")
btn_company.setFixedWidth(300) btn_company.setFixedWidth(300)
btn_company.setFixedHeight(40) 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 = QPushButton("Individualperson →")
btn_person.setFixedWidth(300) btn_person.setFixedWidth(300)
@@ -2148,7 +2152,7 @@ class Page_InitRecCompany(QWidget):
back_main_requested = Signal() # back to main page back_main_requested = Signal() # back to main page
back_requested = Signal() # back button back_requested = Signal() # back button
update_triggered = Signal() # form saved (data changed for front page) update_triggered = Signal() # form saved (data changed for front page)
company_profile_requested = Signal() company_profile_requested = Signal(InitRecId)
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -2172,7 +2176,9 @@ class Page_InitRecCompany(QWidget):
header_layout.setContentsMargins(0, 0, 0, 10) header_layout.setContentsMargins(0, 0, 0, 10)
comp_profile_btn = QPushButton("Unternehmensprofil") 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.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
comp_profile_btn.setMinimumWidth(250) comp_profile_btn.setMinimumWidth(250)
# comp_profile_btn.setMaximumWidth(200) # comp_profile_btn.setMaximumWidth(200)
@@ -2281,6 +2287,10 @@ class Page_InitRecCompany(QWidget):
def reset_form(self) -> None: def reset_form(self) -> None:
self.auto_form.reset_form() self.auto_form.reset_form()
@property
def current_id(self) -> InitRecId:
return self.auto_form.current_id
class Page_InitRecPerson(QWidget): class Page_InitRecPerson(QWidget):
back_main_requested = Signal() # back to main page back_main_requested = Signal() # back to main page
@@ -2413,12 +2423,27 @@ class Page_InitRecPerson(QWidget):
class Page_CompanyProfile(QWidget): class Page_CompanyProfile(QWidget):
back_main_requested = Signal() # back to main page 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 # 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): def __init__(self):
super().__init__() 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 # Hauptlayout der Seite
outer_layout = QHBoxLayout(self) outer_layout = QHBoxLayout(self)
vert_layout = QVBoxLayout() vert_layout = QVBoxLayout()
@@ -2444,7 +2469,7 @@ class Page_CompanyProfile(QWidget):
back_btn_main.setMinimumWidth(200) back_btn_main.setMinimumWidth(200)
back_btn_main.setMaximumWidth(200) back_btn_main.setMaximumWidth(200)
back_btn_step = QPushButton("← Zurück") 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.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
back_btn_step.setMinimumWidth(200) back_btn_step.setMinimumWidth(200)
back_btn_step.setMaximumWidth(200) back_btn_step.setMaximumWidth(200)
@@ -2521,7 +2546,8 @@ class Page_CompanyProfile(QWidget):
) )
left_layout.addWidget(master_data_label) left_layout.addWidget(master_data_label)
# self.table_master_data = MasterDataTable() # 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(self.table_master_data)
left_layout.addWidget(QLabel("PLATZHALTER Vermerke: Welche Notizen?")) left_layout.addWidget(QLabel("PLATZHALTER Vermerke: Welche Notizen?"))
@@ -2575,6 +2601,7 @@ class Page_CompanyProfile(QWidget):
container_layout.addLayout(main_hor_layout) container_layout.addLayout(main_hor_layout)
container_layout.addStretch() container_layout.addStretch()
# --- STYLE ---
self.setStyleSheet(""" self.setStyleSheet("""
QFrame#Segment { QFrame#Segment {
background-color: #ffffff; 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: if not name:
name = self.change_title_text.text() name = self.change_title_text.text()
if not name: if not name:
@@ -2615,6 +2642,42 @@ class Page_CompanyProfile(QWidget):
txt = f"Unternehmen: {name}" txt = f"Unternehmen: {name}"
self.title_company_name.setText(txt) 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): class MasterDataTable(QWidget):
def __init__(self): def __init__(self):
@@ -2705,8 +2768,13 @@ class MasterDataTable(QWidget):
class MasterDataFormWidget(QWidget): class MasterDataFormWidget(QWidget):
def __init__(self): def __init__(
self,
default_labels: Sequence[str],
):
super().__init__() super().__init__()
self.default_labels = default_labels
self.default_data = {label: None for label in self.default_labels}
# main layout # main layout
main_layout = QVBoxLayout(self) main_layout = QVBoxLayout(self)
@@ -2716,37 +2784,55 @@ class MasterDataFormWidget(QWidget):
segment_frame = QFrame() segment_frame = QFrame()
segment_frame.setObjectName("Segment") segment_frame.setObjectName("Segment")
# form layout # form layout
form_layout = QFormLayout(segment_frame) self.form_layout = QFormLayout(segment_frame)
form_layout.setSpacing(15) # Vertikaler Abstand zwischen den Zeilen self.form_layout.setSpacing(15)
form_layout.setLabelAlignment(Qt.AlignmentFlag.AlignLeft) # Merkmale linksbündig self.form_layout.setLabelAlignment(Qt.AlignmentFlag.AlignLeft)
# load data # 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.style_ui()
self.reset()
def load_data(self, form_layout): def load_data(
daten = [ self,
("Anwendungsname", "MyQtApp Pro"), data: dict[str, Any],
("Version", "2.4.1-beta"), ) -> None:
("Datenbank-Pfad", "/var/lib/sqlite/data.db"), self.clear_current_layout()
("Letzter Login", "2026-06-25 09:30"), # daten = [
("Benutzer-ID", "USR-88291"), # ("Anwendungsname", "MyQtApp Pro"),
("Verschlüsselung", "AES-256-GCM"), # ("Version", "2.4.1-beta"),
] # ("Datenbank-Pfad", "/var/lib/sqlite/data.db"),
simulated_retrieved_data = {x[0]: x[1] for x in daten} # ("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(): for feature, value in data.items():
lbl_merkmal = QLabel(f"{merkmal}") label_feature = QLabel(f"{feature}")
lbl_merkmal.setObjectName("Merkmal") label_feature.setObjectName("Merkmal")
lbl_wert = QLabel(str(wert)) if value is None:
lbl_wert.setObjectName("Wert") 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): def style_ui(self):
self.setStyleSheet(""" self.setStyleSheet("""
@@ -2771,7 +2857,8 @@ class MasterDataFormWidget(QWidget):
/* Linke Spalte (Merkmale): Grau und Fett */ /* Linke Spalte (Merkmale): Grau und Fett */
QLabel#Merkmal { QLabel#Merkmal {
font-weight: bold; font-weight: bold;
color: #64748b; /* color: #64748b; */
color: #475569;
} }
/* Rechte Spalte (Werte): Dunkel und gut lesbar */ /* Rechte Spalte (Werte): Dunkel und gut lesbar */
@@ -2999,15 +3086,22 @@ class MainWindow(QMainWindow):
def show_new_entry_select(self): def show_new_entry_select(self):
self.stack.setCurrentWidget(self.new_entry_select) self.stack.setCurrentWidget(self.new_entry_select)
def show_page_initrec_company(self): def show_page_initrec_company(self, reset: bool):
self.initrec_company.reset_form() # 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) self.stack.setCurrentWidget(self.initrec_company)
def show_page_initrec_person(self): def show_page_initrec_person(self):
self.initrec_person.reset_form() self.initrec_person.reset_form()
self.stack.setCurrentWidget(self.initrec_person) 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) self.stack.setCurrentWidget(self.company_profile)
# --- MENÜ LOGIK --- # --- MENÜ LOGIK ---
+3
View File
@@ -5,5 +5,8 @@ from typing import TYPE_CHECKING, TypeAlias
if TYPE_CHECKING: if TYPE_CHECKING:
import polars as pl import polars as pl
InitRecId: TypeAlias = int
ExtMaId: TypeAlias = int
ExtAnId: TypeAlias = int
PolarsSchema: TypeAlias = dict[str, type["pl.DataType"]] PolarsSchema: TypeAlias = dict[str, type["pl.DataType"]]