feat(calllog): kontaktadresse_id als Pflicht-FK + is_erledigt()-Methoden
- Migration V01_13: Spalte kontaktadresse_id auf call_logs mit Backfill über die erste Kontaktadresse des Projekts; Commit vor dem Systeminfo-Update (Muster wie V01_08) gegen 'database is locked'. - CallLog gehört zu genau einer Kontaktadresse (FK, Pflichtfeld); create/update validieren Existenz und Projekt-Zugehörigkeit. - is_erledigt() auf Kontaktadresse (mind. ein erledigend-Tag reicht) und Projekt (False ohne Kontakte, sonst alle erledigt); beide in to_dict() serialisiert. - Client: kontaktadresse_id in Notizmaske/Submit (required), Interfaces um is_erledigt erweitert. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
24b492c5c1
commit
977b64292c
9 files changed
+174
-18
No files matched your search
@@ -148,15 +148,33 @@ class Projekt(dbapp.Model):
|
||||
startdatum = Column(Date, nullable=True)
|
||||
|
||||
kunde = relationship('Kunde')
|
||||
# Kontaktadressen dieses Projekts (Akquise-Ziele). Die Rückrichtung
|
||||
# ist Kontaktadresse.projekt (back_populates).
|
||||
kontaktadressen = relationship('Kontaktadresse', back_populates='projekt')
|
||||
|
||||
def is_erledigt(self):
|
||||
"""
|
||||
Prüft dynamisch, ob das Projekt als erledigt gilt.
|
||||
|
||||
True, wenn das Projekt Kontaktadressen hat und alle davon erledigt
|
||||
sind. Ein Projekt ohne Kontaktadressen ist offen (False), da
|
||||
Kontaktadressen erst nach dem Anlegen des Projekts erfasst werden
|
||||
können — ein neues Projekt ist somit zunächst offen.
|
||||
"""
|
||||
if not self.kontaktadressen:
|
||||
return False
|
||||
return all(k.is_erledigt() for k in self.kontaktadressen)
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Liefert die Projektdaten als Dictionary. Die Tabellenspalten werden
|
||||
über columns2dict serialisiert; kunden_name wird zusätzlich aus der
|
||||
Kunde-Relationship eingefügt.
|
||||
Kunde-Relationship eingefügt. is_erledigt wird dynamisch aus den
|
||||
zugeordneten Kontaktadressen berechnet.
|
||||
"""
|
||||
result = columns2dict(self)
|
||||
result['kunden_name'] = self.kunde.name if self.kunde else None
|
||||
result['is_erledigt'] = self.is_erledigt()
|
||||
return result
|
||||
|
||||
|
||||
@@ -220,34 +238,64 @@ class Kontaktadresse(dbapp.Model):
|
||||
land = Column(String(80), nullable=True)
|
||||
notiz = Column(String(2000), nullable=True)
|
||||
|
||||
projekt = relationship('Projekt')
|
||||
projekt = relationship('Projekt', back_populates='kontaktadressen')
|
||||
# Gesprächsnotizen, die zu diesem Kontakt erfasst wurden. Die
|
||||
# Rückrichtung ist CallLog.kontaktadresse (back_populates); die
|
||||
# Join-Bedingung ergibt sich aus dem Fremdschlüssel
|
||||
# call_logs.kontaktadresse_id -> kontaktadressen.id.
|
||||
call_logs = relationship('CallLog', back_populates='kontaktadresse')
|
||||
|
||||
def is_erledigt(self):
|
||||
"""
|
||||
Prüft dynamisch, ob der Kontakt als erledigt gilt.
|
||||
|
||||
True, sobald mindestens eine Gesprächsnotiz des Kontakts einen Tag
|
||||
mit is_erledigend=True hat. Spätere Notizen ohne erledigend-Tag
|
||||
setzen den Status nicht zurück. False bei keinen Notizen oder wenn
|
||||
keine Notiz einen erledigend-Tag hat.
|
||||
"""
|
||||
return any(
|
||||
clt.tag.is_erledigend
|
||||
for notiz in self.call_logs
|
||||
for clt in notiz.tags_rel
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Liefert die Kontaktdaten als Dictionary. Die Tabellenspalten werden
|
||||
über columns2dict serialisiert; projekt_name wird zusätzlich aus der
|
||||
Projekt-Relationship eingefügt.
|
||||
Projekt-Relationship eingefügt. is_erledigt wird dynamisch aus den
|
||||
Gesprächsnotizen des Kontakts berechnet.
|
||||
"""
|
||||
result = columns2dict(self)
|
||||
result['projekt_name'] = self.projekt.projektname if self.projekt else None
|
||||
result['is_erledigt'] = self.is_erledigt()
|
||||
return result
|
||||
|
||||
|
||||
class CallLog(dbapp.Model):
|
||||
"""
|
||||
Gesprächsnotiz für ein Projekt. Jede Gesprächsnotiz ist genau einem
|
||||
Projekt zugeordnet (projekt_id). Der Projektname (projekt_name) und
|
||||
der Kundenname (kunden_name) sind keine eigenen Spalten, sondern
|
||||
werden über die Projekt-Relationship abgeleitet und nur in to_dict()
|
||||
für die JSON-Serialisierung ergänzt.
|
||||
Gesprächsnotiz für einen Kontakt. Jede Gesprächsnotiz ist genau einem
|
||||
Projekt (projekt_id) und genau einer Kontaktadresse (kontaktadresse_id)
|
||||
zugeordnet. Der Projektname (projekt_name) und der Kundenname
|
||||
(kunden_name) sind keine eigenen Spalten, sondern werden über die
|
||||
Projekt-Relationship abgeleitet und nur in to_dict() für die
|
||||
JSON-Serialisierung ergänzt.
|
||||
"""
|
||||
__tablename__ = 'call_logs'
|
||||
__bind_key__ = BINDKEY
|
||||
__table_args__ = (
|
||||
Index('ix_call_logs_projekt_id', 'projekt_id'),
|
||||
Index('ix_call_logs_kontaktadresse_id', 'kontaktadresse_id'),
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
projekt_id = Column(Integer, ForeignKey('projekte.id'), nullable=False)
|
||||
# Jede Gesprächsnotiz gehört zu genau einer Kontaktadresse
|
||||
# (Pflichtfeld). nullable=False im Modell; in bestehenden Datenbanken
|
||||
# ist die physische Spalte nullable (SQLite kann kein nachträgliches
|
||||
# NOT NULL) — die Pflicht wird über die Server-Validierung in
|
||||
# crm.model.call_log durchgesetzt.
|
||||
kontaktadresse_id = Column(Integer, ForeignKey('kontaktadressen.id'), nullable=False)
|
||||
datum = Column(Date, nullable=True)
|
||||
uhrzeit_beginn = Column(String(10), nullable=True)
|
||||
dauer = Column(String(10), nullable=True)
|
||||
@@ -259,6 +307,7 @@ class CallLog(dbapp.Model):
|
||||
erledigt = Column(Boolean, nullable=False, default=False)
|
||||
|
||||
projekt = relationship('Projekt')
|
||||
kontaktadresse = relationship('Kontaktadresse', back_populates='call_logs')
|
||||
# n:m-Beziehung zu Tags über die Verknüpfungstabelle call_logs_tags.
|
||||
# cascade='all, delete-orphan' stellt sicher, dass die Zuordnungen
|
||||
# automatisch gelöscht werden, wenn die Gesprächsnotiz gelöscht wird.
|
||||
|
||||
Reference in new issue
Block a user