Kontaktadresse: Neues Feld Position, Filter-Funktionen null-sicher, Projektnamen-Spalte entfernt

- Server: Neue Spalte position in Kontaktadresse-Modell (String(200), nullable)
- Server: Migration V01_04 (ALTER TABLE kontaktadressen ADD COLUMN position)
- Server: position in Geschäftslogik create/update aufgenommen
- Server: CLI-Musterdaten um Position ergänzt (Geschäftsführer, IT-Leiter etc.)
- Client: position in iKontaktadresse-Interface, View nach Firma, Columnmapper, Filter
- Client: Projektnamen-Spalte aus Kontaktadressenliste entfernt (steht in Überschrift)
- Client: Filter-Funktionen in allen Listen null-sicher (kunde, kontakt, projekt, kontaktadresse, call_log)

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
gg committed 2026-09-04 14:58:52 +02:00
1 parent b510c50497
commit e9a2f48ede
10 files changed
+59 -36

No files matched your search

+7 -6
View File
@@ -164,11 +164,11 @@ def _seed_muster_kontaktadressen():
projekte = session.query(Projekt).order_by(Projekt.id).all()
vorlagen = [
("Herr", "Thomas Becker", "Becker Webdesign GmbH"),
("Frau", "Sandra Klein", "Klein Marketing"),
("Herr", "Michael Wagner", "Wagner Online Shop"),
("Frau", "Julia Hoffmann", "Hoffmann Consulting"),
("Herr", "Stefan Roth", "Roth IT Solutions"),
("Herr", "Thomas Becker", "Becker Webdesign GmbH", "Geschäftsführer"),
("Frau", "Sandra Klein", "Klein Marketing", "Marketingleiterin"),
("Herr", "Michael Wagner", "Wagner Online Shop", "IT-Leiter"),
("Frau", "Julia Hoffmann", "Hoffmann Consulting", "Beraterin"),
("Herr", "Stefan Roth", "Roth IT Solutions", "Technischer Leiter"),
]
orte = [
("Industriestr. 12", "45131", "Essen", "Deutschland"),
@@ -181,7 +181,7 @@ def _seed_muster_kontaktadressen():
for projekt in projekte:
anzahl = rng.randint(1, 3)
for _ in range(anzahl):
anrede, name, firma = vorlagen[vorlagen_idx % len(vorlagen)]
anrede, name, firma, position = vorlagen[vorlagen_idx % len(vorlagen)]
vorlagen_idx += 1
strasse, plz, ort, land = rng.choice(orte)
ka = Kontaktadresse()
@@ -189,6 +189,7 @@ def _seed_muster_kontaktadressen():
ka.anrede = anrede
ka.name = name
ka.firma = firma
ka.position = position
ka.telefon = "+49 {vorwahl} {nummer}".format(
vorwahl=rng.randint(100, 999),
nummer=rng.randint(100000, 999999)
+1
View File
@@ -168,6 +168,7 @@ class Kontaktadresse(dbapp.Model):
anrede = Column(String(30), nullable=True)
name = Column(String(200), nullable=False)
firma = Column(String(200), nullable=True)
position = Column(String(200), nullable=True)
telefon = Column(String(100), nullable=True)
email = Column(String(200), nullable=True)
strasse = Column(String(200), nullable=True)
+13
View File
@@ -78,6 +78,19 @@ def _db_V01_03():
session.commit()
def _db_V01_04():
"""
Fügt die Spalte 'position' zur Tabelle 'kontaktadressen' hinzu.
Bei bestehenden Datenbanken, die mit Version 01.03 initialisiert wurden,
fehlt diese Spalte, da das Feld erst nachträglich ergänzt wurde.
"""
from sqlalchemy import text
session.execute(text("ALTER TABLE kontaktadressen ADD COLUMN position VARCHAR(200)"))
_updatesysteminfo('01.04')
session.commit()
def checkupdates():
"""
Ermittelt die aktuelle DB-Version aus der Systeminfo-Tabelle und führt alle
@@ -68,6 +68,7 @@ def create_kontaktadresse(data: dict) -> Kontaktadresse:
kontaktadresse.anrede = (data.get("anrede") or "").strip() or None
kontaktadresse.name = data["name"].strip()
kontaktadresse.firma = (data.get("firma") or "").strip() or None
kontaktadresse.position = (data.get("position") or "").strip() or None
kontaktadresse.telefon = (data.get("telefon") or "").strip() or None
kontaktadresse.email = (data.get("email") or "").strip() or None
kontaktadresse.strasse = (data.get("strasse") or "").strip() or None
@@ -107,6 +108,7 @@ def update_kontaktadresse(data: dict) -> Kontaktadresse:
kontaktadresse.anrede = (data.get("anrede") or "").strip() or None
kontaktadresse.name = data["name"].strip()
kontaktadresse.firma = (data.get("firma") or "").strip() or None
kontaktadresse.position = (data.get("position") or "").strip() or None
kontaktadresse.telefon = (data.get("telefon") or "").strip() or None
kontaktadresse.email = (data.get("email") or "").strip() or None
kontaktadresse.strasse = (data.get("strasse") or "").strip() or None
+8 -8
View File
@@ -74,14 +74,14 @@ export const call_log_liste: m.ClosureComponent = (vnode) => {
const filterFunction = (node: iTreenode, searchvalue: string): boolean => {
let log: iCallLog = node.nodevalue
let searchLower = searchvalue.toLowerCase()
return log.kunden_name.toLowerCase().includes(searchLower) ||
log.projekt_name.toLowerCase().includes(searchLower) ||
log.datum.includes(searchLower) ||
log.uhrzeit_beginn.toLowerCase().includes(searchLower) ||
log.betreff.toLowerCase().includes(searchLower) ||
log.name_ansprechpartner.toLowerCase().includes(searchLower) ||
log.notiz.toLowerCase().includes(searchLower) ||
log.wiedervorlage_datum.includes(searchLower)
return (log.kunden_name || "").toLowerCase().includes(searchLower) ||
(log.projekt_name || "").toLowerCase().includes(searchLower) ||
(log.datum || "").includes(searchLower) ||
(log.uhrzeit_beginn || "").toLowerCase().includes(searchLower) ||
(log.betreff || "").toLowerCase().includes(searchLower) ||
(log.name_ansprechpartner || "").toLowerCase().includes(searchLower) ||
(log.notiz || "").toLowerCase().includes(searchLower) ||
(log.wiedervorlage_datum || "").includes(searchLower)
}
return {
+5 -5
View File
@@ -54,11 +54,11 @@ export const kontaktliste: m.ClosureComponent = (vnode) => {
const filterFunction = (node: iTreenode, searchvalue: string): boolean => {
let kontakt: iKontakt = node.nodevalue
let searchLower = searchvalue.toLowerCase()
return kontakt.name.toLowerCase().includes(searchLower) ||
kontakt.position.toLowerCase().includes(searchLower) ||
kontakt.telefon.toLowerCase().includes(searchLower) ||
kontakt.email.toLowerCase().includes(searchLower) ||
kontakt.kunden_name.toLowerCase().includes(searchLower)
return (kontakt.name || "").toLowerCase().includes(searchLower) ||
(kontakt.position || "").toLowerCase().includes(searchLower) ||
(kontakt.telefon || "").toLowerCase().includes(searchLower) ||
(kontakt.email || "").toLowerCase().includes(searchLower) ||
(kontakt.kunden_name || "").toLowerCase().includes(searchLower)
}
return {
+12 -7
View File
@@ -64,10 +64,10 @@ export const kontaktadresseliste: m.ClosureComponent = (vnode) => {
return [
m("div", m("strong", kontaktadresse.name)),
m("span", kontaktadresse.firma),
m("span", kontaktadresse.position),
m("span", kontaktadresse.telefon),
m("span", kontaktadresse.email),
m("span", kontaktadresse.ort),
m("span", kontaktadresse.projekt_name),
m("div", {style: {display: 'flex', gap: '0', 'white-space': 'nowrap'}},
m("button.w3-button.w3-small", {style: {padding: '0 2px'}, onclick: (e: Event) => editKontaktadresse(kontaktadresse, e)}, m("i.material-icons", "edit")),
m("button.w3-button.w3-small", {title: "Gesprächsnotiz hinzufügen", style: {padding: '0 2px'}, onclick: (e: Event) => addCallLog(kontaktadresse, e)}, m("i.material-icons", "add_call"))
@@ -78,12 +78,13 @@ export const kontaktadresseliste: m.ClosureComponent = (vnode) => {
const filterFunction = (node: iTreenode, searchvalue: string): boolean => {
let kontaktadresse: iKontaktadresse = node.nodevalue
let searchLower = searchvalue.toLowerCase()
return kontaktadresse.name.toLowerCase().includes(searchLower) ||
kontaktadresse.firma.toLowerCase().includes(searchLower) ||
kontaktadresse.telefon.toLowerCase().includes(searchLower) ||
kontaktadresse.email.toLowerCase().includes(searchLower) ||
kontaktadresse.ort.toLowerCase().includes(searchLower) ||
kontaktadresse.projekt_name.toLowerCase().includes(searchLower)
return (kontaktadresse.name || "").toLowerCase().includes(searchLower) ||
(kontaktadresse.firma || "").toLowerCase().includes(searchLower) ||
(kontaktadresse.position || "").toLowerCase().includes(searchLower) ||
(kontaktadresse.telefon || "").toLowerCase().includes(searchLower) ||
(kontaktadresse.email || "").toLowerCase().includes(searchLower) ||
(kontaktadresse.ort || "").toLowerCase().includes(searchLower) ||
(kontaktadresse.projekt_name || "").toLowerCase().includes(searchLower)
}
return {
@@ -152,6 +153,7 @@ export const Kontaktadresse: m.ClosureComponent = (vnode) => {
anrede: string
name: string
firma: string
position: string
telefon: string
email: string
strasse: string
@@ -166,6 +168,7 @@ export const Kontaktadresse: m.ClosureComponent = (vnode) => {
anrede: "",
name: "",
firma: "",
position: "",
telefon: "",
email: "",
strasse: "",
@@ -200,6 +203,7 @@ export const Kontaktadresse: m.ClosureComponent = (vnode) => {
anrede: (formData.get("anrede") as string) || "",
name: (formData.get("name") as string) || "",
firma: (formData.get("firma") as string) || "",
position: (formData.get("position") as string) || "",
telefon: (formData.get("telefon") as string) || "",
email: (formData.get("email") as string) || "",
strasse: (formData.get("strasse") as string) || "",
@@ -228,6 +232,7 @@ export const Kontaktadresse: m.ClosureComponent = (vnode) => {
m("label", "Anrede", m(Input, {name: "anrede", value: currentKontaktadresse.anrede, type: "text"} as iInput)),
m("label", "Name", m(Input, {name: "name", value: currentKontaktadresse.name, type: "text", required: true} as iInput)),
m("label", "Firma", m(Input, {name: "firma", value: currentKontaktadresse.firma, type: "text"} as iInput)),
m("label", "Position", m(Input, {name: "position", value: currentKontaktadresse.position, type: "text"} as iInput)),
m("label", "Telefon", m(Input, {name: "telefon", value: currentKontaktadresse.telefon, type: "tel"} as iInput)),
m("label", "E-Mail", m(Input, {name: "email", value: currentKontaktadresse.email, type: "email"} as iInput)),
m("label", "Straße", m(Input, {name: "strasse", value: currentKontaktadresse.strasse, type: "text"} as iInput)),
+5 -5
View File
@@ -117,11 +117,11 @@ export const kundenliste: m.ClosureComponent = (vnode) => {
const filterFunction = (node: iTreenode, searchvalue: string): boolean => {
let kunde: iKunde = node.nodevalue
let searchLower = searchvalue.toLowerCase()
return kunde.name.toLowerCase().includes(searchLower) ||
kunde.strasse.toLowerCase().includes(searchLower) ||
kunde.plz.includes(searchLower) ||
kunde.ort.toLowerCase().includes(searchLower) ||
kunde.land.toLowerCase().includes(searchLower)
return (kunde.name || "").toLowerCase().includes(searchLower) ||
(kunde.strasse || "").toLowerCase().includes(searchLower) ||
(kunde.plz || "").includes(searchLower) ||
(kunde.ort || "").toLowerCase().includes(searchLower) ||
(kunde.land || "").toLowerCase().includes(searchLower)
}
return {
+5 -5
View File
@@ -91,11 +91,11 @@ export const projektliste: m.ClosureComponent = (vnode) => {
const filterFunction = (node: iTreenode, searchvalue: string): boolean => {
let projekt: iProjekt = node.nodevalue
let searchLower = searchvalue.toLowerCase()
return projekt.kunden_name.toLowerCase().includes(searchLower) ||
projekt.projektname.toLowerCase().includes(searchLower) ||
projekt.zweck.toLowerCase().includes(searchLower) ||
projekt.verantwortlicher.toLowerCase().includes(searchLower) ||
projekt.startdatum.includes(searchLower)
return (projekt.kunden_name || "").toLowerCase().includes(searchLower) ||
(projekt.projektname || "").toLowerCase().includes(searchLower) ||
(projekt.zweck || "").toLowerCase().includes(searchLower) ||
(projekt.verantwortlicher || "").toLowerCase().includes(searchLower) ||
(projekt.startdatum || "").includes(searchLower)
}
return {
+1
View File
@@ -14,6 +14,7 @@ export interface iKontaktadresse {
anrede: string
name: string
firma: string
position: string
telefon: string
email: string
strasse: string