generated from dopt-python/py311
refactoring and basic loading of existing consultations on the company profile
This commit is contained in:
@@ -10,9 +10,20 @@ from dopt_basics.result_pattern import wrap_result
|
|||||||
|
|
||||||
from wce_crm import db
|
from wce_crm import db
|
||||||
from wce_crm.constants import TIMEZONE_CEST
|
from wce_crm.constants import TIMEZONE_CEST
|
||||||
from wce_crm.data_models import Beratungsgespraech_Einzelgespraech, Beratungsgespraech_Vorgang
|
from wce_crm.data_models import (
|
||||||
|
Beratungsgespraech_Einzelgespraech,
|
||||||
|
Beratungsgespraech_Vorgang,
|
||||||
|
)
|
||||||
from wce_crm.logging import logger_back as logger
|
from wce_crm.logging import logger_back as logger
|
||||||
from wce_crm.types import CompanyInfo, ContactPersonInfo, InitRecType, MainPageEntry
|
from wce_crm.types import (
|
||||||
|
CompanyInfo,
|
||||||
|
CompanyProfileConsultationEntry,
|
||||||
|
CompanyProfileConsultations,
|
||||||
|
ConsultingType,
|
||||||
|
ContactPersonInfo,
|
||||||
|
InitRecType,
|
||||||
|
MainPageEntry,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from wce_crm.types import ConsId, ExtAnId, ExtMaId, RecId
|
from wce_crm.types import ConsId, ExtAnId, ExtMaId, RecId
|
||||||
@@ -393,6 +404,59 @@ def page_consulting_from_db(
|
|||||||
return consultation_data
|
return consultation_data
|
||||||
|
|
||||||
|
|
||||||
|
# // consulting page interaction
|
||||||
|
def companyprofile_page_get_consultations(
|
||||||
|
un_id: RecId,
|
||||||
|
) -> CompanyProfileConsultations:
|
||||||
|
logger.debug("[Call backend] _companyprofile_page_get_consultations")
|
||||||
|
stmt = sql.select(
|
||||||
|
db.beratung_vorgang.c.vorgang_id,
|
||||||
|
db.beratung_vorgang.c.aktualisiert,
|
||||||
|
db.beratung_vorgang.c.titel,
|
||||||
|
db.beratung_vorgang.c.beratungs_typ,
|
||||||
|
).where(db.beratung_vorgang.c.un_id == un_id)
|
||||||
|
|
||||||
|
with db.ENGINE.connect() as conn:
|
||||||
|
res = conn.execute(stmt)
|
||||||
|
|
||||||
|
cons_entries_pauschal: list[CompanyProfileConsultationEntry] = []
|
||||||
|
cons_entries_individual: list[CompanyProfileConsultationEntry] = []
|
||||||
|
|
||||||
|
for entry in res.mappings():
|
||||||
|
cons_id = entry["vorgang_id"]
|
||||||
|
assert cons_id, "no VorgangID defined"
|
||||||
|
|
||||||
|
datetime_updated = cast(datetime.datetime, entry["aktualisiert"])
|
||||||
|
datetime_updated = datetime_updated.astimezone(TIMEZONE_CEST)
|
||||||
|
|
||||||
|
base_title = entry["titel"]
|
||||||
|
|
||||||
|
_cons_type = entry["beratungs_typ"]
|
||||||
|
cons_type = ConsultingType(_cons_type)
|
||||||
|
|
||||||
|
con_entry = CompanyProfileConsultationEntry(
|
||||||
|
cons_id=cons_id,
|
||||||
|
title=base_title,
|
||||||
|
date_updated=datetime_updated,
|
||||||
|
cons_type=cons_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
if cons_type is ConsultingType.PAUSCHAL:
|
||||||
|
cons_entries_pauschal.append(con_entry)
|
||||||
|
elif cons_type is ConsultingType.INDIVIDUAL:
|
||||||
|
cons_entries_individual.append(con_entry)
|
||||||
|
else:
|
||||||
|
raise TypeError(f"Unknown consulting type: {_cons_type}")
|
||||||
|
|
||||||
|
cons_entries_pauschal.sort(key=lambda x: x.date_updated, reverse=True)
|
||||||
|
cons_entries_individual.sort(key=lambda x: x.date_updated, reverse=True)
|
||||||
|
|
||||||
|
return CompanyProfileConsultations(
|
||||||
|
pauschal=cons_entries_pauschal,
|
||||||
|
individual=cons_entries_individual,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# // main page interaction
|
# // main page interaction
|
||||||
def _main_page_get_company_list() -> list[MainPageEntry]:
|
def _main_page_get_company_list() -> list[MainPageEntry]:
|
||||||
logger.debug("[Call backend] get_company_list")
|
logger.debug("[Call backend] get_company_list")
|
||||||
|
|||||||
+209
-9
@@ -1,9 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import dataclasses as dc
|
||||||
import datetime
|
import datetime
|
||||||
import enum
|
|
||||||
import json
|
import json
|
||||||
from typing import Annotated, Any, Final, Literal
|
from collections.abc import Iterable, Sequence
|
||||||
|
from typing import TYPE_CHECKING, Annotated, Any, Final, Generic, Protocol, TypeVar
|
||||||
|
|
||||||
from pydantic import (
|
from pydantic import (
|
||||||
AwareDatetime,
|
AwareDatetime,
|
||||||
@@ -16,8 +18,16 @@ from pydantic import (
|
|||||||
)
|
)
|
||||||
from pydantic_core import ErrorDetails
|
from pydantic_core import ErrorDetails
|
||||||
|
|
||||||
ValidAge = Annotated[int, Field(ge=0, le=99)]
|
from wce_crm.form_defs import FormField
|
||||||
|
from wce_crm.types import ConsId, ConsultingType, RecId, UserId
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from wce_crm.gui import Page_Consulting_ConsultingSession # noqa: F401
|
||||||
|
|
||||||
|
ValidAge = Annotated[int, Field(ge=0, le=99)]
|
||||||
|
COLUMN_SEP: Final[str] = "__"
|
||||||
|
M = TypeVar("M", bound="Module")
|
||||||
|
S = TypeVar("S", bound="PageState")
|
||||||
|
|
||||||
# // validation translation
|
# // validation translation
|
||||||
GERMAN_ERROR_MESSAGES: Final[dict[str, str]] = {
|
GERMAN_ERROR_MESSAGES: Final[dict[str, str]] = {
|
||||||
@@ -64,9 +74,204 @@ def _parse_json(value: Any) -> str:
|
|||||||
raise TypeError
|
raise TypeError
|
||||||
|
|
||||||
|
|
||||||
COLUMN_SEP: Final[str] = "__"
|
# // GUI states and types
|
||||||
|
def set_page_state(
|
||||||
|
target: S,
|
||||||
|
source: S,
|
||||||
|
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):
|
||||||
|
if field.name == "child_modules":
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_value = getattr(source, field.name)
|
||||||
|
|
||||||
|
if deep:
|
||||||
|
new_value = copy.deepcopy(new_value)
|
||||||
|
|
||||||
|
setattr(target, field.name, new_value)
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
"""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:
|
||||||
|
"""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: ...
|
||||||
|
|
||||||
|
|
||||||
|
class AutoFormInsert(Protocol):
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
data: dict[str, Any],
|
||||||
|
) -> RecId: ...
|
||||||
|
|
||||||
|
|
||||||
|
class AutoFormUpdate(Protocol):
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
id_: int,
|
||||||
|
data: dict[str, Any],
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
class AutoFormGet(Protocol):
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
id_: int,
|
||||||
|
) -> dict[str, Any]: ...
|
||||||
|
|
||||||
|
|
||||||
|
class AutoFormDelete(Protocol):
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
id_: int,
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
@dc.dataclass(slots=True)
|
||||||
|
class Session:
|
||||||
|
user_id: UserId
|
||||||
|
user_name: str
|
||||||
|
|
||||||
|
|
||||||
|
@dc.dataclass(slots=True)
|
||||||
|
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, kw_only=True)
|
||||||
|
class Page_NewInitRec_State(PageState[Module]):
|
||||||
|
session: Session
|
||||||
|
|
||||||
|
|
||||||
|
@dc.dataclass(slots=True, kw_only=True)
|
||||||
|
class Page_InitRecCompany_State(PageState[Module]):
|
||||||
|
session: Session
|
||||||
|
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
|
||||||
|
pers_id: RecId | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dc.dataclass(slots=True, kw_only=True)
|
||||||
|
class Page_Consulting_State(PageState[Module]):
|
||||||
|
session: Session
|
||||||
|
vorgang_id: ConsId | None
|
||||||
|
un_id: RecId | None = None
|
||||||
|
pers_id: RecId | None = None
|
||||||
|
titel: str = "TEST-TITEL" # add title later to GUI or build it in the background
|
||||||
|
beratungs_typ: ConsultingType
|
||||||
|
cons_sessions: list[Page_Consulting_ConsultingSession_State] = dc.field(
|
||||||
|
default_factory=list
|
||||||
|
)
|
||||||
|
ist_geloescht: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@dc.dataclass(slots=True, kw_only=True)
|
||||||
|
class Page_Consulting_ConsultingSession_State(PageState[Module]):
|
||||||
|
session: Session
|
||||||
|
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 # known and not known entries
|
||||||
|
zeitstempel: datetime.datetime | None = None
|
||||||
|
# dc.field(default_factory=lambda: datetime.datetime.now(tz=datetime.UTC))
|
||||||
|
ansprechpartner: str | None = None
|
||||||
|
kommunikationsweg: str | None = None
|
||||||
|
thema_crm_matrix: str | None = (
|
||||||
|
None # TODO should be linked to something, target unclear (need specification by customer)
|
||||||
|
)
|
||||||
|
anmerkungen: str | None = None
|
||||||
|
rueckmeldung: str | None = None
|
||||||
|
ist_geloescht: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@dc.dataclass(slots=True, kw_only=True)
|
||||||
|
class Page_Consulting_Table_State(PageState["Page_Consulting_ConsultingSession"]):
|
||||||
|
session: Session
|
||||||
|
row_states: list[Page_Consulting_ConsultingSession_State] = dc.field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dc.dataclass(slots=True, kw_only=True)
|
||||||
|
class Page_CompanyProfile_State(PageState[Module]):
|
||||||
|
session: Session
|
||||||
|
rec_id: RecId | None = None
|
||||||
|
|
||||||
|
|
||||||
|
# // Pydantic models
|
||||||
class FlatBaseModel(BaseModel):
|
class FlatBaseModel(BaseModel):
|
||||||
"""
|
"""
|
||||||
Optimised Pydantic base class, which parses JSON strings and column
|
Optimised Pydantic base class, which parses JSON strings and column
|
||||||
@@ -347,11 +552,6 @@ class Grunderfassung_Sprachen(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
# ** Consulting
|
# ** Consulting
|
||||||
class ConsultingType(enum.StrEnum):
|
|
||||||
PAUSCHAL = enum.auto()
|
|
||||||
INDIVIDUAL = enum.auto()
|
|
||||||
|
|
||||||
|
|
||||||
class Beratungsgespraech_Vorgang(BaseModel):
|
class Beratungsgespraech_Vorgang(BaseModel):
|
||||||
model_config = ConfigDict(str_strip_whitespace=True)
|
model_config = ConfigDict(str_strip_whitespace=True)
|
||||||
|
|
||||||
|
|||||||
+263
-261
@@ -7,13 +7,12 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections.abc import Container, Iterable, Sequence
|
from collections.abc import Callable, Container, Iterable, Sequence
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
Final,
|
Final,
|
||||||
Generic,
|
|
||||||
Literal,
|
Literal,
|
||||||
Protocol,
|
Protocol,
|
||||||
TypeAlias,
|
TypeAlias,
|
||||||
@@ -80,12 +79,22 @@ from wce_crm.backend import backend
|
|||||||
from wce_crm.constants import TIMEZONE_CEST
|
from wce_crm.constants import TIMEZONE_CEST
|
||||||
from wce_crm.data_models import (
|
from wce_crm.data_models import (
|
||||||
COLUMN_SEP,
|
COLUMN_SEP,
|
||||||
|
AutoForm_State,
|
||||||
|
AutoFormConfig,
|
||||||
Beratungsgespraech_Einzelgespraech,
|
Beratungsgespraech_Einzelgespraech,
|
||||||
Beratungsgespraech_Vorgang,
|
Beratungsgespraech_Vorgang,
|
||||||
ConsultingType,
|
|
||||||
FlatBaseModel,
|
|
||||||
Grunderfassung_Personen,
|
Grunderfassung_Personen,
|
||||||
Grunderfassung_Unternehmen,
|
Grunderfassung_Unternehmen,
|
||||||
|
Page_CompanyProfile_State,
|
||||||
|
Page_Consulting_ConsultingSession_State,
|
||||||
|
Page_Consulting_State,
|
||||||
|
Page_Consulting_Table_State,
|
||||||
|
Page_InitRecCompany_State,
|
||||||
|
Page_InitRecPerson_State,
|
||||||
|
Page_MainPage_State,
|
||||||
|
Page_NewInitRec_State,
|
||||||
|
Session,
|
||||||
|
set_page_state,
|
||||||
translate_pydantic_errors,
|
translate_pydantic_errors,
|
||||||
)
|
)
|
||||||
from wce_crm.form_defs import (
|
from wce_crm.form_defs import (
|
||||||
@@ -101,16 +110,17 @@ from wce_crm.logging import (
|
|||||||
logger_gui,
|
logger_gui,
|
||||||
logger_page_consulting,
|
logger_page_consulting,
|
||||||
)
|
)
|
||||||
|
from wce_crm.types import CompanyProfileConsultationEntry, ConsultingType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from wce_crm.types import ConsId, ExtMaId, RecId, UserId
|
from wce_crm.data_models import AutoForm_State, Module, WrapperModule
|
||||||
|
from wce_crm.types import ConsId, ExtMaId
|
||||||
|
|
||||||
K = TypeVar("K")
|
K = TypeVar("K")
|
||||||
V = TypeVar("V")
|
V = TypeVar("V")
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
W = TypeVar("W", bound="QWidget")
|
W = TypeVar("W", bound="QWidget")
|
||||||
M = TypeVar("M", bound="Module")
|
|
||||||
S = TypeVar("S", bound="PageState")
|
|
||||||
|
|
||||||
DEBUG: bool = True
|
DEBUG: bool = True
|
||||||
DEBUG_SEARCH_WIDGET: bool = False
|
DEBUG_SEARCH_WIDGET: bool = False
|
||||||
@@ -137,6 +147,60 @@ QSS = """
|
|||||||
*[styleClass="stempel"]:focus {
|
*[styleClass="stempel"]:focus {
|
||||||
border: 1px dashed #cbd5e1;
|
border: 1px dashed #cbd5e1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPushButton[styleClass="addButton"] {
|
||||||
|
background-color: #007acc;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 15px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton[styleClass="addButton"]:hover {
|
||||||
|
background-color: #0098ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton[styleClass="outlineAddButton"] {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #007acc;
|
||||||
|
border: 1px solid #007acc;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 5px 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton[styleClass="outlineAddButton"]:hover {
|
||||||
|
background-color: #007acc;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton[styleClass="softAddButton"] {
|
||||||
|
background-color: #e0f2fe;
|
||||||
|
color: #0369a1;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton[styleClass="softAddButton"]:hover {
|
||||||
|
background-color: #bae6fd;
|
||||||
|
color: #0369a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton[styleClass="flatAddButton"] {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #475569;
|
||||||
|
border: none;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton[styleClass="flatAddButton"]:hover {
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
color: #007acc;
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
DROPDOWN_DEFAULT: Final[str] = "--- Bitte wählen ---"
|
DROPDOWN_DEFAULT: Final[str] = "--- Bitte wählen ---"
|
||||||
DYNAMIC_LIST_KEY_PATTERN: Final[re.Pattern] = re.compile(r"-\[(\d+)\]")
|
DYNAMIC_LIST_KEY_PATTERN: Final[re.Pattern] = re.compile(r"-\[(\d+)\]")
|
||||||
@@ -199,78 +263,6 @@ class CustomForm(Protocol):
|
|||||||
def validate_form_data(self) -> list[str]: ...
|
def validate_form_data(self) -> list[str]: ...
|
||||||
|
|
||||||
|
|
||||||
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:
|
|
||||||
"""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:
|
|
||||||
"""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: ...
|
|
||||||
|
|
||||||
|
|
||||||
class AutoFormInsert(Protocol):
|
|
||||||
def __call__(
|
|
||||||
self,
|
|
||||||
data: dict[str, Any],
|
|
||||||
) -> backend.RecId: ...
|
|
||||||
|
|
||||||
|
|
||||||
class AutoFormUpdate(Protocol):
|
|
||||||
def __call__(
|
|
||||||
self,
|
|
||||||
id_: int,
|
|
||||||
data: dict[str, Any],
|
|
||||||
) -> None: ...
|
|
||||||
|
|
||||||
|
|
||||||
class AutoFormGet(Protocol):
|
|
||||||
def __call__(
|
|
||||||
self,
|
|
||||||
id_: int,
|
|
||||||
) -> dict[str, Any]: ...
|
|
||||||
|
|
||||||
|
|
||||||
class AutoFormDelete(Protocol):
|
|
||||||
def __call__(
|
|
||||||
self,
|
|
||||||
id_: int,
|
|
||||||
) -> None: ...
|
|
||||||
|
|
||||||
|
|
||||||
class CustomWidget(QWidget):
|
class CustomWidget(QWidget):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -292,104 +284,6 @@ class CustomWidget(QWidget):
|
|||||||
def validate_form_data(self) -> list[str]: ...
|
def validate_form_data(self) -> list[str]: ...
|
||||||
|
|
||||||
|
|
||||||
@dc.dataclass(slots=True)
|
|
||||||
class Session:
|
|
||||||
user_id: UserId
|
|
||||||
user_name: str
|
|
||||||
|
|
||||||
|
|
||||||
@dc.dataclass(slots=True)
|
|
||||||
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, kw_only=True)
|
|
||||||
class Page_NewInitRec_State(PageState[Module]):
|
|
||||||
session: Session
|
|
||||||
|
|
||||||
|
|
||||||
@dc.dataclass(slots=True, kw_only=True)
|
|
||||||
class Page_InitRecCompany_State(PageState[Module]):
|
|
||||||
session: Session
|
|
||||||
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
|
|
||||||
pers_id: RecId | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@dc.dataclass(slots=True, kw_only=True)
|
|
||||||
class Page_Consulting_State(PageState[Module]):
|
|
||||||
session: Session
|
|
||||||
vorgang_id: ConsId | None
|
|
||||||
un_id: RecId | None = None
|
|
||||||
pers_id: RecId | None = None
|
|
||||||
titel: str = "TEST-TITEL" # add title later to GUI or build it in the background
|
|
||||||
beratungs_typ: ConsultingType
|
|
||||||
cons_sessions: list[Page_Consulting_ConsultingSession_State] = dc.field(
|
|
||||||
default_factory=list
|
|
||||||
)
|
|
||||||
ist_geloescht: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
@dc.dataclass(slots=True, kw_only=True)
|
|
||||||
class Page_Consulting_ConsultingSession_State(PageState[Module]):
|
|
||||||
session: Session
|
|
||||||
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 # known and not known entries
|
|
||||||
zeitstempel: datetime.datetime | None = None
|
|
||||||
# dc.field(default_factory=lambda: datetime.datetime.now(tz=datetime.UTC))
|
|
||||||
ansprechpartner: str | None = None
|
|
||||||
kommunikationsweg: str | None = None
|
|
||||||
thema_crm_matrix: str | None = (
|
|
||||||
None # TODO should be linked to something, target unclear (need specification by customer)
|
|
||||||
)
|
|
||||||
anmerkungen: str | None = None
|
|
||||||
rueckmeldung: str | None = None
|
|
||||||
ist_geloescht: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
@dc.dataclass(slots=True, kw_only=True)
|
|
||||||
class Page_Consulting_Table_State(PageState["Page_Consulting_ConsultingSession"]):
|
|
||||||
session: Session
|
|
||||||
row_states: list[Page_Consulting_ConsultingSession_State] = dc.field(default_factory=list)
|
|
||||||
|
|
||||||
|
|
||||||
@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(
|
def _add_widget_to_layout(
|
||||||
form_field: FormField[W],
|
form_field: FormField[W],
|
||||||
widget: QWidget,
|
widget: QWidget,
|
||||||
@@ -2581,25 +2475,43 @@ class NoScrollFilter(QObject):
|
|||||||
return super().eventFilter(obj, event)
|
return super().eventFilter(obj, event)
|
||||||
|
|
||||||
|
|
||||||
class MainPage_ClickableCell(QFrame):
|
class HeaderCell(QLabel):
|
||||||
"""cell in the table on the startup screen"""
|
"""basic header for table like objects"""
|
||||||
|
|
||||||
clicked = Signal(types.MainPageEntry)
|
def __init__(
|
||||||
|
self,
|
||||||
|
text: str,
|
||||||
|
font_size: int = 12,
|
||||||
|
):
|
||||||
|
super().__init__(text)
|
||||||
|
self.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self.setStyleSheet(f"""
|
||||||
|
HeaderCell {{
|
||||||
|
background-color: #e2e8f0;
|
||||||
|
color: #475569;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: {font_size}px;
|
||||||
|
}}
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
class ClickableCell(QFrame):
|
||||||
|
"""basic cell which can be clicked"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
text: str,
|
text: str,
|
||||||
data_record: types.MainPageEntry,
|
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.data_record = data_record
|
|
||||||
self.setStyleSheet("""
|
self.setStyleSheet("""
|
||||||
MainPage_ClickableCell {
|
ClickableCell {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid #e2e8f0;
|
border: 1px solid #e2e8f0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
MainPage_ClickableCell:hover {
|
ClickableCell:hover {
|
||||||
background-color: #eff6ff;
|
background-color: #eff6ff;
|
||||||
border: 1px solid #60a5fa;
|
border: 1px solid #60a5fa;
|
||||||
}
|
}
|
||||||
@@ -2610,29 +2522,41 @@ class MainPage_ClickableCell(QFrame):
|
|||||||
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
layout.addWidget(label)
|
layout.addWidget(label)
|
||||||
|
|
||||||
|
|
||||||
|
class CompanyProfile_Consultation_ClickableCell(ClickableCell):
|
||||||
|
"""cell on the company profile for consultations"""
|
||||||
|
|
||||||
|
clicked = Signal(CompanyProfileConsultationEntry)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
text: str,
|
||||||
|
data_record: CompanyProfileConsultationEntry,
|
||||||
|
):
|
||||||
|
super().__init__(text=text)
|
||||||
|
self.data_record = data_record
|
||||||
|
|
||||||
def mousePressEvent(self, event):
|
def mousePressEvent(self, event):
|
||||||
if event.button() == Qt.MouseButton.LeftButton:
|
if event.button() == Qt.MouseButton.LeftButton:
|
||||||
self.clicked.emit(self.data_record)
|
self.clicked.emit(self.data_record)
|
||||||
|
|
||||||
|
|
||||||
class MainPage_HeaderCell(QLabel):
|
class MainPage_ClickableCell(ClickableCell):
|
||||||
|
"""cell in the table on the startup screen"""
|
||||||
|
|
||||||
|
clicked = Signal(types.MainPageEntry)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
text: str,
|
text: str,
|
||||||
font_size: int = 12,
|
data_record: types.MainPageEntry,
|
||||||
):
|
):
|
||||||
super().__init__(text)
|
super().__init__(text=text)
|
||||||
self.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
self.data_record = data_record
|
||||||
self.setStyleSheet(f"""
|
|
||||||
MainPage_HeaderCell {{
|
def mousePressEvent(self, event):
|
||||||
background-color: #e2e8f0;
|
if event.button() == Qt.MouseButton.LeftButton:
|
||||||
color: #475569;
|
self.clicked.emit(self.data_record)
|
||||||
font-weight: bold;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: {font_size}px;
|
|
||||||
}}
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
||||||
class Page_NewInitRec(QWidget):
|
class Page_NewInitRec(QWidget):
|
||||||
@@ -2867,6 +2791,9 @@ class Page_InitRecCompany(QWidget):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _request_company_profile(self) -> None:
|
def _request_company_profile(self) -> None:
|
||||||
|
assert self.STATE.un_id is not None, (
|
||||||
|
"requested company profile for a company which is not known (no ID)"
|
||||||
|
)
|
||||||
req_state = Page_CompanyProfile_State(
|
req_state = Page_CompanyProfile_State(
|
||||||
session=self.STATE.session,
|
session=self.STATE.session,
|
||||||
rec_id=self.STATE.un_id,
|
rec_id=self.STATE.un_id,
|
||||||
@@ -3148,6 +3075,25 @@ class Page_CompanyProfile(QWidget):
|
|||||||
change_title_btn.clicked.connect(self.set_company_name)
|
change_title_btn.clicked.connect(self.set_company_name)
|
||||||
container_layout.addLayout(layout_change_title)
|
container_layout.addLayout(layout_change_title)
|
||||||
|
|
||||||
|
btn_dummy_pauschal = QPushButton("Dummy Pauschalberatung hinzufügen")
|
||||||
|
btn_dummy_pauschal.clicked.connect(
|
||||||
|
lambda: self._debug_consulting_dummy(ConsultingType.PAUSCHAL)
|
||||||
|
)
|
||||||
|
container_layout.addWidget(btn_dummy_pauschal)
|
||||||
|
btn_dummy_inidividual = QPushButton("Dummy Individualberatung hinzufügen")
|
||||||
|
btn_dummy_inidividual.clicked.connect(
|
||||||
|
lambda: self._debug_consulting_dummy(ConsultingType.INDIVIDUAL)
|
||||||
|
)
|
||||||
|
container_layout.addWidget(btn_dummy_inidividual)
|
||||||
|
|
||||||
|
btn_clear_cons = QPushButton("Beratungen leeren")
|
||||||
|
btn_clear_cons.clicked.connect(self._clear_consultations)
|
||||||
|
container_layout.addWidget(btn_clear_cons)
|
||||||
|
|
||||||
|
btn_update_cons = QPushButton("Beratungen aktualisieren")
|
||||||
|
btn_update_cons.clicked.connect(self._update_consultations)
|
||||||
|
container_layout.addWidget(btn_update_cons)
|
||||||
|
|
||||||
separator2 = QFrame()
|
separator2 = QFrame()
|
||||||
separator2.setFrameShape(QFrame.Shape.HLine)
|
separator2.setFrameShape(QFrame.Shape.HLine)
|
||||||
separator2.setFrameShadow(QFrame.Shadow.Sunken)
|
separator2.setFrameShadow(QFrame.Shadow.Sunken)
|
||||||
@@ -3155,7 +3101,7 @@ class Page_CompanyProfile(QWidget):
|
|||||||
|
|
||||||
container_layout.addSpacing(30)
|
container_layout.addSpacing(30)
|
||||||
|
|
||||||
self.title_company_name = MainPage_HeaderCell("Unternehmen: n.v.", font_size=16)
|
self.title_company_name = HeaderCell("Unternehmen: n.v.", font_size=16)
|
||||||
container_layout.addWidget(self.title_company_name)
|
container_layout.addWidget(self.title_company_name)
|
||||||
|
|
||||||
container_layout.addSpacing(20)
|
container_layout.addSpacing(20)
|
||||||
@@ -3171,7 +3117,7 @@ class Page_CompanyProfile(QWidget):
|
|||||||
left_frame.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum)
|
left_frame.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum)
|
||||||
left_layout = QVBoxLayout(left_frame)
|
left_layout = QVBoxLayout(left_frame)
|
||||||
main_hor_layout.addWidget(left_frame, stretch=1, alignment=Qt.AlignmentFlag.AlignTop)
|
main_hor_layout.addWidget(left_frame, stretch=1, alignment=Qt.AlignmentFlag.AlignTop)
|
||||||
master_data_label = MainPage_HeaderCell("Stammdaten & Vermerke")
|
master_data_label = HeaderCell("Stammdaten & Vermerke")
|
||||||
master_data_label.setSizePolicy(
|
master_data_label.setSizePolicy(
|
||||||
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
||||||
)
|
)
|
||||||
@@ -3193,21 +3139,23 @@ class Page_CompanyProfile(QWidget):
|
|||||||
top_right_frame.setSizePolicy(
|
top_right_frame.setSizePolicy(
|
||||||
QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum
|
QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum
|
||||||
)
|
)
|
||||||
top_right_layout = QVBoxLayout(top_right_frame)
|
self.layout_cons_pauschal = QVBoxLayout(top_right_frame)
|
||||||
common_consultancy_label = MainPage_HeaderCell("Pauschalberatung")
|
self.cons_pauschal: list[CompanyProfile_Consultation_ClickableCell] = []
|
||||||
|
common_consultancy_label = HeaderCell("Pauschalberatung")
|
||||||
common_consultancy_label.setSizePolicy(
|
common_consultancy_label.setSizePolicy(
|
||||||
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
||||||
)
|
)
|
||||||
top_right_layout.addWidget(common_consultancy_label)
|
self.layout_cons_pauschal.addWidget(common_consultancy_label)
|
||||||
self.add_btn_pauschal = QPushButton("+ Hinzufügen")
|
self.add_btn_pauschal = QPushButton("+ Hinzufügen")
|
||||||
self.add_btn_pauschal.clicked.connect(
|
self.add_btn_pauschal.clicked.connect(
|
||||||
lambda: self._request_consulting(ConsultingType.PAUSCHAL)
|
lambda: self._request_new_consulting(ConsultingType.PAUSCHAL)
|
||||||
)
|
)
|
||||||
self.add_btn_pauschal.setSizePolicy(
|
self.add_btn_pauschal.setSizePolicy(
|
||||||
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
||||||
)
|
)
|
||||||
self.add_btn_pauschal.setMinimumHeight(30)
|
self.add_btn_pauschal.setMinimumHeight(30)
|
||||||
top_right_layout.addWidget(self.add_btn_pauschal)
|
self.add_btn_pauschal.setProperty("styleClass", "flatAddButton")
|
||||||
|
self.layout_cons_pauschal.addWidget(self.add_btn_pauschal)
|
||||||
|
|
||||||
# bottom right area
|
# bottom right area
|
||||||
bottom_right_frame = QFrame()
|
bottom_right_frame = QFrame()
|
||||||
@@ -3215,21 +3163,23 @@ class Page_CompanyProfile(QWidget):
|
|||||||
bottom_right_frame.setSizePolicy(
|
bottom_right_frame.setSizePolicy(
|
||||||
QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum
|
QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum
|
||||||
)
|
)
|
||||||
bottom_right_layout = QVBoxLayout(bottom_right_frame)
|
self.layout_cons_indiv = QVBoxLayout(bottom_right_frame)
|
||||||
individual_consultancy_label = MainPage_HeaderCell("Individualberatung")
|
self.cons_indiv: list[CompanyProfile_Consultation_ClickableCell] = []
|
||||||
|
individual_consultancy_label = HeaderCell("Individualberatung")
|
||||||
individual_consultancy_label.setSizePolicy(
|
individual_consultancy_label.setSizePolicy(
|
||||||
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
||||||
)
|
)
|
||||||
bottom_right_layout.addWidget(individual_consultancy_label)
|
self.layout_cons_indiv.addWidget(individual_consultancy_label)
|
||||||
self.add_btn_individual = QPushButton("+ Hinzufügen")
|
self.add_btn_individual = QPushButton("+ Hinzufügen")
|
||||||
self.add_btn_individual.clicked.connect(
|
self.add_btn_individual.clicked.connect(
|
||||||
lambda: self._request_consulting(ConsultingType.INDIVIDUAL)
|
lambda: self._request_new_consulting(ConsultingType.INDIVIDUAL)
|
||||||
)
|
)
|
||||||
self.add_btn_individual.setSizePolicy(
|
self.add_btn_individual.setSizePolicy(
|
||||||
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
||||||
)
|
)
|
||||||
self.add_btn_individual.setMinimumHeight(30)
|
self.add_btn_individual.setMinimumHeight(30)
|
||||||
bottom_right_layout.addWidget(self.add_btn_individual)
|
self.add_btn_individual.setProperty("styleClass", "flatAddButton")
|
||||||
|
self.layout_cons_indiv.addWidget(self.add_btn_individual)
|
||||||
|
|
||||||
right_column_layout.addWidget(top_right_frame)
|
right_column_layout.addWidget(top_right_frame)
|
||||||
right_column_layout.addWidget(bottom_right_frame)
|
right_column_layout.addWidget(bottom_right_frame)
|
||||||
@@ -3270,15 +3220,37 @@ class Page_CompanyProfile(QWidget):
|
|||||||
# }
|
# }
|
||||||
# """)
|
# """)
|
||||||
|
|
||||||
|
def _debug_consulting_dummy(
|
||||||
|
self,
|
||||||
|
type: ConsultingType,
|
||||||
|
):
|
||||||
|
if type is ConsultingType.PAUSCHAL:
|
||||||
|
dummy_click_cell = CompanyProfile_Consultation_ClickableCell(
|
||||||
|
"Dummy Pauschal",
|
||||||
|
None, # type: ignore
|
||||||
|
)
|
||||||
|
target_layout = self.layout_cons_pauschal
|
||||||
|
self.cons_pauschal.append(dummy_click_cell)
|
||||||
|
else:
|
||||||
|
dummy_click_cell = CompanyProfile_Consultation_ClickableCell(
|
||||||
|
"Dummy Individual",
|
||||||
|
None, # type: ignore
|
||||||
|
)
|
||||||
|
target_layout = self.layout_cons_indiv
|
||||||
|
self.cons_indiv.append(dummy_click_cell)
|
||||||
|
|
||||||
|
target_idx = target_layout.count() - 1
|
||||||
|
target_layout.insertWidget(target_idx, dummy_click_cell)
|
||||||
|
|
||||||
def _request_initrec_company(self) -> None:
|
def _request_initrec_company(self) -> None:
|
||||||
req_state = Page_InitRecCompany_State(
|
req_state = Page_InitRecCompany_State(
|
||||||
session=self.STATE.session,
|
session=self.STATE.session,
|
||||||
un_id=self.STATE.rec_id,
|
un_id=self.STATE.rec_id,
|
||||||
)
|
)
|
||||||
logger_gui.debug("[Page -- InitRec Company] State to call: %s", req_state)
|
logger_gui.debug("[Page Company Profile] State to call: %s", req_state)
|
||||||
self.back_requested.emit(req_state)
|
self.back_requested.emit(req_state)
|
||||||
|
|
||||||
def _request_consulting(
|
def _request_new_consulting(
|
||||||
self,
|
self,
|
||||||
cons_type: ConsultingType,
|
cons_type: ConsultingType,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -3293,6 +3265,62 @@ class Page_CompanyProfile(QWidget):
|
|||||||
)
|
)
|
||||||
self.consulting_requested.emit(new_state)
|
self.consulting_requested.emit(new_state)
|
||||||
|
|
||||||
|
def _request_existing_consulting(
|
||||||
|
self,
|
||||||
|
data: CompanyProfileConsultationEntry,
|
||||||
|
) -> None:
|
||||||
|
assert self.STATE.rec_id is not None, (
|
||||||
|
"tried to call consulting from comp profile without set ID"
|
||||||
|
)
|
||||||
|
new_state = Page_Consulting_State(
|
||||||
|
session=self.STATE.session,
|
||||||
|
vorgang_id=data.cons_id,
|
||||||
|
un_id=self.STATE.rec_id,
|
||||||
|
beratungs_typ=data.cons_type,
|
||||||
|
)
|
||||||
|
self.consulting_requested.emit(new_state)
|
||||||
|
|
||||||
|
def _clear_consultations(self) -> None:
|
||||||
|
logger_gui.debug("[Page Company Profile] Clear consultations...")
|
||||||
|
|
||||||
|
while self.cons_pauschal:
|
||||||
|
widget = self.cons_pauschal[0]
|
||||||
|
self.layout_cons_pauschal.removeWidget(widget)
|
||||||
|
self.cons_pauschal.remove(widget)
|
||||||
|
widget.deleteLater()
|
||||||
|
|
||||||
|
while self.cons_indiv:
|
||||||
|
widget = self.cons_indiv[0]
|
||||||
|
self.layout_cons_indiv.removeWidget(widget)
|
||||||
|
self.cons_indiv.remove(widget)
|
||||||
|
widget.deleteLater()
|
||||||
|
|
||||||
|
def _update_consultations(self) -> None:
|
||||||
|
assert self.STATE.rec_id is not None, (
|
||||||
|
"cannot update consultations if RecID is not set"
|
||||||
|
)
|
||||||
|
consultations = backend.companyprofile_page_get_consultations(un_id=self.STATE.rec_id)
|
||||||
|
|
||||||
|
self._clear_consultations()
|
||||||
|
|
||||||
|
for entry in consultations.pauschal:
|
||||||
|
date_formatted = entry.date_updated.strftime("%d.%m.%Y %H:%M")
|
||||||
|
display_title = f"Pauschalberatung ({date_formatted}): {entry.title}"
|
||||||
|
click_cell = CompanyProfile_Consultation_ClickableCell(display_title, entry)
|
||||||
|
click_cell.clicked.connect(self._request_existing_consulting)
|
||||||
|
target_idx = self.layout_cons_pauschal.count() - 1
|
||||||
|
self.layout_cons_pauschal.insertWidget(target_idx, click_cell)
|
||||||
|
self.cons_pauschal.append(click_cell)
|
||||||
|
|
||||||
|
for entry in consultations.individual:
|
||||||
|
date_formatted = entry.date_updated.strftime("%d.%m.%Y %H:%M")
|
||||||
|
display_title = f"Pauschalberatung ({date_formatted}): {entry.title}"
|
||||||
|
click_cell = CompanyProfile_Consultation_ClickableCell(display_title, entry)
|
||||||
|
click_cell.clicked.connect(self._request_existing_consulting)
|
||||||
|
target_idx = self.layout_cons_indiv.count() - 1
|
||||||
|
self.layout_cons_indiv.insertWidget(target_idx, click_cell)
|
||||||
|
self.cons_indiv.append(click_cell)
|
||||||
|
|
||||||
def _disable_add_btns(self) -> None:
|
def _disable_add_btns(self) -> None:
|
||||||
self.add_btn_pauschal.setEnabled(False)
|
self.add_btn_pauschal.setEnabled(False)
|
||||||
self.add_btn_individual.setEnabled(False)
|
self.add_btn_individual.setEnabled(False)
|
||||||
@@ -3797,13 +3825,18 @@ class Page_Consulting(QWidget):
|
|||||||
self,
|
self,
|
||||||
text: str,
|
text: str,
|
||||||
button_text: str,
|
button_text: str,
|
||||||
|
button_func: Callable[[], None] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if button_text and button_func is None:
|
||||||
|
raise ValueError("Can not add button without calling function")
|
||||||
|
|
||||||
banner = InfoBanner(
|
banner = InfoBanner(
|
||||||
text=text,
|
text=text,
|
||||||
button_text=button_text,
|
button_text=button_text,
|
||||||
)
|
)
|
||||||
|
if button_func is not None:
|
||||||
|
banner.btn_clicked.connect(button_func)
|
||||||
self.info_banners.append(banner)
|
self.info_banners.append(banner)
|
||||||
banner.link_clicked.connect(self._open_linking_dialogue)
|
|
||||||
self.info_banners_layout.addWidget(banner)
|
self.info_banners_layout.addWidget(banner)
|
||||||
|
|
||||||
def _sync_state_to_GUI(self) -> None:
|
def _sync_state_to_GUI(self) -> None:
|
||||||
@@ -3824,11 +3857,17 @@ class Page_Consulting(QWidget):
|
|||||||
|
|
||||||
# info banners needed
|
# info banners needed
|
||||||
self._clear_info_banners()
|
self._clear_info_banners()
|
||||||
|
if self.STATE.vorgang_id is None:
|
||||||
|
self._add_linking_banner(
|
||||||
|
text="⚠️ Dieser Eintrag ist noch nicht gespeichert.",
|
||||||
|
button_text="",
|
||||||
|
)
|
||||||
|
else:
|
||||||
if self.STATE.un_id is None and self.STATE.pers_id is None:
|
if self.STATE.un_id is None and self.STATE.pers_id is None:
|
||||||
# link missing
|
|
||||||
self._add_linking_banner(
|
self._add_linking_banner(
|
||||||
text="⚠️ Dieser Eintrag ist noch nicht mit einer Entität verknüpft.",
|
text="⚠️ Dieser Eintrag ist noch nicht mit einer Entität verknüpft.",
|
||||||
button_text="Jetzt verknüpfen",
|
button_text="Jetzt verknüpfen",
|
||||||
|
button_func=self._open_linking_dialogue,
|
||||||
)
|
)
|
||||||
elif (
|
elif (
|
||||||
self.STATE.beratungs_typ is ConsultingType.INDIVIDUAL
|
self.STATE.beratungs_typ is ConsultingType.INDIVIDUAL
|
||||||
@@ -3840,6 +3879,7 @@ class Page_Consulting(QWidget):
|
|||||||
"einer Person fehlt."
|
"einer Person fehlt."
|
||||||
),
|
),
|
||||||
button_text="Jetzt verknüpfen",
|
button_text="Jetzt verknüpfen",
|
||||||
|
button_func=self._open_linking_dialogue,
|
||||||
)
|
)
|
||||||
|
|
||||||
# child modules
|
# child modules
|
||||||
@@ -4124,6 +4164,7 @@ class Page_Consulting_ConsultingSession(QWidget):
|
|||||||
layout,
|
layout,
|
||||||
widget_registry=self.widget_registry,
|
widget_registry=self.widget_registry,
|
||||||
)
|
)
|
||||||
|
self.timestamp.set_pydatetime(datetime.datetime.now(TIMEZONE_CEST))
|
||||||
|
|
||||||
contact_def = FormField[QLineEdit](
|
contact_def = FormField[QLineEdit](
|
||||||
"Kontaktperson",
|
"Kontaktperson",
|
||||||
@@ -4302,24 +4343,10 @@ class Page_Consulting_Table(QWidget):
|
|||||||
# --- BUTTON (ADD) ---
|
# --- BUTTON (ADD) ---
|
||||||
self.btn_add = QPushButton("+ Neuen Eintrag hinzufügen")
|
self.btn_add = QPushButton("+ Neuen Eintrag hinzufügen")
|
||||||
self.btn_add.setCursor(Qt.CursorShape.PointingHandCursor)
|
self.btn_add.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
self.btn_add.setObjectName("BlueAddButton") # ID vergeben
|
self.btn_add.setProperty("styleClass", "addButton")
|
||||||
main_layout.addWidget(self.btn_add)
|
main_layout.addWidget(self.btn_add)
|
||||||
self.btn_add.clicked.connect(lambda: self.add_row(None, False))
|
self.btn_add.clicked.connect(lambda: self.add_row(None, False))
|
||||||
|
|
||||||
self.btn_add.setStyleSheet("""
|
|
||||||
#BlueAddButton {
|
|
||||||
background-color: #007acc;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 8px 15px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
#BlueAddButton:hover {
|
|
||||||
background-color: #0098ff;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
|
|
||||||
# --- HEADER ---
|
# --- HEADER ---
|
||||||
header_widget = QWidget()
|
header_widget = QWidget()
|
||||||
header_layout = QHBoxLayout(header_widget)
|
header_layout = QHBoxLayout(header_widget)
|
||||||
@@ -4547,7 +4574,7 @@ class Page_Consulting_Table(QWidget):
|
|||||||
|
|
||||||
|
|
||||||
class InfoBanner(QFrame):
|
class InfoBanner(QFrame):
|
||||||
link_clicked = Signal()
|
btn_clicked = Signal()
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -4582,43 +4609,18 @@ class InfoBanner(QFrame):
|
|||||||
layout.setContentsMargins(15, 10, 15, 10)
|
layout.setContentsMargins(15, 10, 15, 10)
|
||||||
|
|
||||||
self.label = QLabel(text)
|
self.label = QLabel(text)
|
||||||
self.button = QPushButton(button_text)
|
|
||||||
layout.addWidget(self.label)
|
layout.addWidget(self.label)
|
||||||
layout.addStretch()
|
layout.addStretch()
|
||||||
layout.addWidget(self.button)
|
|
||||||
|
|
||||||
self.button.clicked.connect(self.link_clicked.emit)
|
if button_text:
|
||||||
|
self.button = QPushButton(button_text)
|
||||||
|
layout.addWidget(self.button)
|
||||||
|
self.button.clicked.connect(self.btn_clicked.emit)
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
|
|
||||||
|
|
||||||
def set_page_state(
|
|
||||||
target: S,
|
|
||||||
source: S,
|
|
||||||
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):
|
|
||||||
if field.name == "child_modules":
|
|
||||||
continue
|
|
||||||
|
|
||||||
new_value = getattr(source, field.name)
|
|
||||||
|
|
||||||
if deep:
|
|
||||||
new_value = copy.deepcopy(new_value)
|
|
||||||
|
|
||||||
setattr(target, field.name, new_value)
|
|
||||||
|
|
||||||
|
|
||||||
def clear_layout(
|
def clear_layout(
|
||||||
layout: QLayout | None,
|
layout: QLayout | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -4782,7 +4784,7 @@ class MainWindow(QMainWindow):
|
|||||||
"Datum",
|
"Datum",
|
||||||
]
|
]
|
||||||
for col_idx, title in enumerate(headers):
|
for col_idx, title in enumerate(headers):
|
||||||
self.header_grid.addWidget(MainPage_HeaderCell(title), self.current_row, col_idx)
|
self.header_grid.addWidget(HeaderCell(title), self.current_row, col_idx)
|
||||||
|
|
||||||
self.update_grid()
|
self.update_grid()
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from typing import TYPE_CHECKING, TypeAlias, TypedDict
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import polars as pl
|
import polars as pl
|
||||||
|
|
||||||
|
|
||||||
UserId: TypeAlias = int
|
UserId: TypeAlias = int
|
||||||
RecId: TypeAlias = int
|
RecId: TypeAlias = int
|
||||||
ConsId: TypeAlias = int
|
ConsId: TypeAlias = int
|
||||||
@@ -22,6 +23,11 @@ class InitRecType(enum.IntEnum):
|
|||||||
PERSON = enum.auto()
|
PERSON = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
|
class ConsultingType(enum.StrEnum):
|
||||||
|
PAUSCHAL = enum.auto()
|
||||||
|
INDIVIDUAL = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
# // database interaction data structures
|
# // database interaction data structures
|
||||||
# ** externals
|
# ** externals
|
||||||
class CompanyInfo(TypedDict):
|
class CompanyInfo(TypedDict):
|
||||||
@@ -83,3 +89,17 @@ class MainPageEntry:
|
|||||||
display_name: str
|
display_name: str
|
||||||
Metadaten_aktualisierung: datetime.datetime
|
Metadaten_aktualisierung: datetime.datetime
|
||||||
type: InitRecType
|
type: InitRecType
|
||||||
|
|
||||||
|
|
||||||
|
@dc.dataclass(slots=True)
|
||||||
|
class CompanyProfileConsultationEntry:
|
||||||
|
title: str
|
||||||
|
cons_id: ConsId
|
||||||
|
date_updated: datetime.datetime
|
||||||
|
cons_type: ConsultingType
|
||||||
|
|
||||||
|
|
||||||
|
@dc.dataclass(slots=True)
|
||||||
|
class CompanyProfileConsultations:
|
||||||
|
pauschal: list[CompanyProfileConsultationEntry]
|
||||||
|
individual: list[CompanyProfileConsultationEntry]
|
||||||
|
|||||||
Reference in New Issue
Block a user