feat(workflow): Tages-Workflow-UI für Telefon-Akquise (Liste + Maske)
Server: neuer Endpunkt /xhr/workflow/list aggregiert pro Kontaktadresse die letzte Gesprächsnotiz, das effektiv gültige Wiedervorlage-Datum (jüngste Notiz mit Wert), den Erledigt-Status und eine priorisierte Sortierung (überfällig → ohne Notizen → Rest). Filter: kunden_id, status (offen/erledigt), wiedervorlage (heute/3tage/ueberfaellig/woche). Sichtbarkeit wie kontaktadressen über projekt_users. CallLog erfasst den Ersteller serverseitig (user_id, Migration _db_V01_14, kein FK wegen separater Auth-DB); to_dict ergänzt mitarbeiter_name. calllog_list unterstützt kontaktadresse_id als Filter. Client: TagesWorkflowListe (Route /tagesworkflow, Filterleiste) und TagesWorkflowMaske (Route /tagesworkflowkontakt, lesende Historie, Notiz-Formular mit "Speichern und weiter"), Routes + Menüpunkt erster Eintrag. berechneWiedervorlageDatum aus der Call_Log-Maske nach models/tag.ts ausgelagert (gemeinsame Nutzung). Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
981b28df03
commit
c96ed6e97d
13 files changed
+1247
-44
No files matched your search
@@ -263,12 +263,14 @@ class Kontaktadresse(dbapp.Model):
|
||||
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. is_erledigt wird dynamisch aus den
|
||||
Gesprächsnotizen des Kontakts berechnet.
|
||||
über columns2dict serialisiert; projekt_name und kunden_name werden
|
||||
zusätzlich aus der Projekt- bzw. Kunde-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['kunden_name'] = self.projekt.kunde.name if self.projekt and self.projekt.kunde else None
|
||||
result['is_erledigt'] = self.is_erledigt()
|
||||
return result
|
||||
|
||||
@@ -296,6 +298,14 @@ class CallLog(dbapp.Model):
|
||||
# NOT NULL) — die Pflicht wird über die Server-Validierung in
|
||||
# crm.model.call_log durchgesetzt.
|
||||
kontaktadresse_id = Column(Integer, ForeignKey('kontaktadressen.id'), nullable=False)
|
||||
# Ersteller der Gesprächsnotiz (Mitarbeiter, der den Anruf getätigt
|
||||
# hat). Referenziert logisch die 'id' in der Auth-Datenbanktabelle
|
||||
# 'users'; ein physischer Fremdschlüssel ist aufgrund der zwei
|
||||
# getrennten SQLite-Datenbanken nicht möglich (Muster wie
|
||||
# projekt_users.user_id). nullable=True, da Alt-Notizen (angelegt
|
||||
# vor der Spalte) keinen Ersteller erfassen konnten; die Auflösung
|
||||
# auf den username erfolgt zur Laufzeit über eine separate Query.
|
||||
user_id = Column(Integer, nullable=True)
|
||||
datum = Column(Date, nullable=True)
|
||||
uhrzeit_beginn = Column(String(10), nullable=True)
|
||||
dauer = Column(String(10), nullable=True)
|
||||
@@ -319,8 +329,10 @@ class CallLog(dbapp.Model):
|
||||
"""
|
||||
Liefert die Gesprächsnotiz als Dictionary. Die Tabellenspalten werden
|
||||
über columns2dict serialisiert; projekt_name und kunden_name werden
|
||||
zusätzlich aus der Projekt-Relationship eingefügt. Die zugeordneten
|
||||
Tags werden als Liste von Tag-Dictionaries unter 'tags' ergänzt.
|
||||
zusätzlich aus der Projekt-Relationship eingefügt. mitarbeiter_name
|
||||
löst die user_id gegen die Auth-Datenbank auf (None bei Alt-Notizen
|
||||
ohne erfassten Ersteller). Die zugeordneten Tags werden als Liste
|
||||
von Tag-Dictionaries unter 'tags' ergänzt.
|
||||
"""
|
||||
result = columns2dict(self)
|
||||
if self.projekt:
|
||||
@@ -329,6 +341,14 @@ class CallLog(dbapp.Model):
|
||||
else:
|
||||
result['projekt_name'] = None
|
||||
result['kunden_name'] = None
|
||||
# Ersteller gegen die Auth-Datenbank auflösen (Muster wie
|
||||
# crm.model.projekt_user). Alt-Notizen ohne user_id erhalten None.
|
||||
result['mitarbeiter_name'] = None
|
||||
if self.user_id is not None:
|
||||
from auth.model import User
|
||||
user = User.query.get(self.user_id)
|
||||
if user is not None:
|
||||
result['mitarbeiter_name'] = user.username
|
||||
# Zugeordnete Tags serialisieren (nur is_parent=False sind zuordenbar).
|
||||
result['tags'] = [clt.tag.to_dict() for clt in self.tags_rel]
|
||||
return result
|
||||
|
||||
@@ -94,13 +94,17 @@ def _get_validierte_kontaktadresse_id(data: dict, projekt_id) -> int:
|
||||
return kontaktadresse.id
|
||||
|
||||
|
||||
def get_call_logs(projekt_id: int | None = None, user_id: int | None = None):
|
||||
def get_call_logs(projekt_id: int | None = None,
|
||||
user_id: int | None = None,
|
||||
kontaktadresse_id: int | None = None):
|
||||
"""
|
||||
Liefert Gesprächsnotizen, sortiert nach Datum absteigend.
|
||||
|
||||
Ist projekt_id gesetzt, werden nur die Notizen dieses Projekts
|
||||
zurückgegeben (serverseitige Filterung). Ohne projekt_id werden
|
||||
alle Notizen geliefert.
|
||||
zurückgegeben (serverseitige Filterung). Ist kontaktadresse_id
|
||||
gesetzt, werden nur die Notizen dieses Kontakts zurückgegeben —
|
||||
beide Filter können kombiniert werden. Ohne Filter werden alle
|
||||
Notizen geliefert.
|
||||
|
||||
Ist user_id gesetzt, werden nur Notizen von Projekten geliefert, denen
|
||||
der Nutzer über projekt_users zugeordnet ist (Sichtbarkeits-Filterung).
|
||||
@@ -109,6 +113,8 @@ def get_call_logs(projekt_id: int | None = None, user_id: int | None = None):
|
||||
query = CallLog.query
|
||||
if projekt_id is not None:
|
||||
query = query.filter(CallLog.projekt_id == projekt_id)
|
||||
if kontaktadresse_id is not None:
|
||||
query = query.filter(CallLog.kontaktadresse_id == kontaktadresse_id)
|
||||
if user_id is not None:
|
||||
# Nur Notizen von Projekten, denen der Nutzer zugeordnet ist.
|
||||
query = query.filter(
|
||||
@@ -148,7 +154,7 @@ def get_call_log(call_log_id: int, user_id: int | None = None) -> CallLog:
|
||||
return call_log
|
||||
|
||||
|
||||
def create_call_log(data: dict) -> CallLog:
|
||||
def create_call_log(data: dict, user_id: int | None = None) -> CallLog:
|
||||
"""
|
||||
Legt eine neue Gesprächsnotiz an. Die vom Client übergebene ID wird
|
||||
ignoriert; die ID wird serverseitig per Auto-Increment vergeben.
|
||||
@@ -156,6 +162,11 @@ def create_call_log(data: dict) -> CallLog:
|
||||
- data: dict mit den Feldern projekt_id, kontaktadresse_id, datum,
|
||||
uhrzeit_beginn, dauer, betreff, name_ansprechpartner, telefon,
|
||||
notiz, wiedervorlage_datum, erledigt, tag_ids
|
||||
- user_id: ID des angemeldeten Mitarbeiters, der die Notiz erfasst
|
||||
(Ersteller). Wird von der Route übergeben (current_user.id),
|
||||
damit diese Funktion frei vom Flask-Request-Kontext bleibt.
|
||||
Der Ersteller wird beim Anlegen gesetzt und beim späteren
|
||||
Update nicht mehr verändert.
|
||||
"""
|
||||
projekt_id = data.get("projekt_id")
|
||||
if projekt_id is None:
|
||||
@@ -173,6 +184,12 @@ def create_call_log(data: dict) -> CallLog:
|
||||
call_log = CallLog()
|
||||
call_log.projekt_id = int(projekt_id)
|
||||
call_log.kontaktadresse_id = kontaktadresse_id
|
||||
# Ersteller erfassen: welcher Mitarbeiter den Anruf getätigt hat.
|
||||
# Alt-Notizen (vor der user_id-Spalte angelegt) haben NULL; neue
|
||||
# Notizen ohne Angabe erhalten ebenfalls NULL, bleiben aber die
|
||||
# Ausnahme, da die Route immer current_user.id übergibt.
|
||||
if user_id is not None:
|
||||
call_log.user_id = int(user_id)
|
||||
call_log.datum = _parse_date(data.get("datum"))
|
||||
call_log.uhrzeit_beginn = (data.get("uhrzeit_beginn") or "").strip() or None
|
||||
call_log.dauer = (data.get("dauer") or "").strip() or None
|
||||
|
||||
@@ -325,6 +325,47 @@ def _db_V01_13():
|
||||
session.commit()
|
||||
|
||||
|
||||
def _db_V01_14():
|
||||
"""
|
||||
Fügt der Tabelle 'call_logs' die Spalte 'user_id' hinzu: Jede
|
||||
Gesprächsnotiz erfasst, welcher Mitarbeiter den Anruf getätigt hat.
|
||||
Die Spalte referenziert logisch die 'id' in der Auth-Datenbanktabelle
|
||||
'users'; ein physischer Fremdschlüssel ist aufgrund der zwei
|
||||
getrennten SQLite-Datenbanken nicht möglich (Muster wie
|
||||
projekt_users.user_id).
|
||||
|
||||
Kein Backfill: Für bestehende CallLogs ist der Ersteller nicht mehr
|
||||
ermittelbar — die Spalte bleibt dort NULL (Anzeige 'unbekannt').
|
||||
Neue Notizen erhalten die user_id serverseitig über current_user.id
|
||||
(siehe crm.routes.calllog_create).
|
||||
|
||||
Idempotent: Bei Neuinstallation hat create_all die Spalte bereits
|
||||
angelegt — in diesem Fall wird das ALTER TABLE übersprungen. Der
|
||||
Index ist über IF NOT EXISTS ebenfalls idempotent.
|
||||
"""
|
||||
from sqlalchemy import text
|
||||
|
||||
result = session.execute(text("PRAGMA table_info(call_logs)"))
|
||||
spalten = {row[1] for row in result}
|
||||
if 'user_id' not in spalten:
|
||||
session.execute(text(
|
||||
"ALTER TABLE call_logs ADD COLUMN user_id INTEGER"
|
||||
))
|
||||
session.execute(text(
|
||||
"CREATE INDEX IF NOT EXISTS ix_call_logs_user_id"
|
||||
" ON call_logs (user_id)"
|
||||
))
|
||||
# Roh-SQL-Änderungen committen, BEVOR die Systeminfo aktualisiert wird
|
||||
# (dasselbe Muster wie _db_V01_08 und _db_V01_13): Die DDL-Anweisung
|
||||
# läuft über die Default-Bind-Verbindung — SQLALCHEMY_DATABASE_URI
|
||||
# zeigt auf dieselbe Datei wie der appdb-Bind. Das ORM-Update der
|
||||
# Systeminfo läuft über die appdb-Bind-Verbindung; ohne dieses Commit
|
||||
# blockiert die offene Transaktion das UPDATE ('database is locked').
|
||||
session.commit()
|
||||
_updatesysteminfo('01.14')
|
||||
session.commit()
|
||||
|
||||
|
||||
def checkupdates():
|
||||
"""
|
||||
Ermittelt die aktuelle DB-Version aus der Systeminfo-Tabelle und führt alle
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
"""
|
||||
Geschäftslogik für den Tages-Workflow (Telefon-Akquise).
|
||||
|
||||
Aggregiert pro Kontaktadresse die für den Tages-Workflow relevanten
|
||||
Informationen: letzte Gesprächsnotiz, aktuell gültiges Wiedervorlage-Datum
|
||||
und dynamisch berechneter Erledigt-Status. Filter (Kunde, Status,
|
||||
Wiedervorlage-Zeitraum) und die priorisierte Sortierung sind Teil der
|
||||
Aggregation — der Client erhält eine fertige, gefilterte Liste.
|
||||
|
||||
Aufgerufen werden die Funktionen aus crm/routes.py; die Autorisierungs-
|
||||
prüfung erfolgt dort über den Decorator authorisation_required.
|
||||
|
||||
:author: Mistral Vibe <vibe@mistral.ai>
|
||||
:date: 2026-09-23
|
||||
:version: 1.0
|
||||
"""
|
||||
from datetime import date, timedelta
|
||||
|
||||
from db import dbapp
|
||||
from crm.model import Kontaktadresse, Projekt, ProjektUser
|
||||
|
||||
session = dbapp.session
|
||||
|
||||
|
||||
def _naechste_woche_start_und_ende(heute: date) -> tuple[date, date]:
|
||||
"""
|
||||
Liefert Montag und Sonntag der ISO-Kalenderwoche, in die der
|
||||
übergebene Tag fällt.
|
||||
"""
|
||||
jahr, woche, _ = heute.isocalendar()
|
||||
montag = date.fromisocalendar(jahr, woche, 1)
|
||||
sonntag = montag + timedelta(days=6)
|
||||
return montag, sonntag
|
||||
|
||||
|
||||
def _ist_im_wiedervorlage_zeitraum(wiedervorlage_datum: date | None,
|
||||
zeitraum: str | None,
|
||||
heute: date) -> bool:
|
||||
"""
|
||||
Prüft, ob das Wiedervorlage-Datum in den angeforderten Zeitraum
|
||||
fällt. Ohne Zeitraum (None) ist die Prüfung immer wahr.
|
||||
|
||||
Kontakte ohne wiedervorlage_datum fallen bei gesetztem Zeitraum
|
||||
heraus — auch Kontakte ohne Gesprächsnotizen, da für sie kein
|
||||
Wiedervorlage-Datum existiert.
|
||||
"""
|
||||
if not zeitraum:
|
||||
return True
|
||||
if wiedervorlage_datum is None:
|
||||
return False
|
||||
if zeitraum == "heute":
|
||||
return wiedervorlage_datum == heute
|
||||
if zeitraum == "3tage":
|
||||
return heute <= wiedervorlage_datum <= heute + timedelta(days=3)
|
||||
if zeitraum == "ueberfaellig":
|
||||
return wiedervorlage_datum < heute
|
||||
if zeitraum == "woche":
|
||||
montag, sonntag = _naechste_woche_start_und_ende(heute)
|
||||
return montag <= wiedervorlage_datum <= sonntag
|
||||
# Unbekannte Zeitraum-Angaben wirken wie kein Filter.
|
||||
return True
|
||||
|
||||
|
||||
def _sortierschlüssel(eintrag: dict, heute: date) -> tuple:
|
||||
"""
|
||||
Priorisierte Sortierung der Tages-Workflow-Liste (Konzept 2.2):
|
||||
|
||||
- Prio 0: überfällige Wiedervorlagen (wiedervorlage < heute),
|
||||
aufsteigend nach Datum
|
||||
- Prio 1: Kontakte ohne Gesprächsnotizen
|
||||
- Prio 2: Rest (heute/zukünftig), aufsteigend nach Wiedervorlage;
|
||||
Notizen ohne Wiedervorlage ans Ende von Prio 2
|
||||
|
||||
Innerhalb gleicher Priorität/Datum entscheidet der Kontaktname.
|
||||
"""
|
||||
wiedervorlage = eintrag["wiedervorlage_datum"]
|
||||
hat_notizen = eintrag["letzte_notiz"] is not None
|
||||
# wiedervorlage ist ein ISO-String (yyyy-MM-dd) und sortiert lexikalisch
|
||||
# genauso wie das zugehörige date — daher String-Vergleich mit dem
|
||||
# isoformat() des heutigen Tages.
|
||||
heute_str = heute.isoformat()
|
||||
if wiedervorlage is not None and wiedervorlage < heute_str:
|
||||
prio = 0
|
||||
elif not hat_notizen:
|
||||
prio = 1
|
||||
else:
|
||||
prio = 2
|
||||
# Ohne Wiedervorlage ans Ende der Prioritätsgruppe sortieren.
|
||||
datum_key = wiedervorlage if wiedervorlage is not None else "9999-12-31"
|
||||
return (prio, datum_key, eintrag["name"])
|
||||
|
||||
|
||||
def get_tages_workflow_list(user_id: int | None = None,
|
||||
kunden_id: int | None = None,
|
||||
status: str | None = None,
|
||||
wiedervorlage: str | None = None) -> list[dict]:
|
||||
"""
|
||||
Liefert die Tages-Workflow-Liste: pro Kontaktadresse ein Dictionary
|
||||
mit den Kontakt- und Projektdaten, der letzten Gesprächsnotiz, dem
|
||||
aktuell gültigen Wiedervorlage-Datum und dem Erledigt-Status.
|
||||
|
||||
Das aktuell gültige Wiedervorlage-Datum ist das wiedervorlage_datum
|
||||
der jüngsten Gesprächsnotiz, die eines gesetzt hat (Reihenfolge nach
|
||||
datum/uhrzeit_beginn absteigend). Spätere Notizen ohne Wiedervorlage
|
||||
heben eine früher gesetzte nicht auf.
|
||||
|
||||
Ist user_id gesetzt, werden nur Kontaktadressen von Projekten geliefert,
|
||||
denen der Nutzer über projekt_users zugeordnet ist (Sichtbarkeits-
|
||||
Filterung). SysAdmin und AppAdmin übergeben user_id=None und sehen alle.
|
||||
|
||||
Filter (alle optional):
|
||||
- kunden_id: nur Kontaktadressen von Projekten dieses Kunden
|
||||
- status: 'offen' (nicht erledigte) oder 'erledigt'; alles andere
|
||||
wirkt wie kein Filter
|
||||
- wiedervorlage: 'heute', '3tage' (bis heute+3), 'ueberfaellig'
|
||||
(< heute) oder 'woche' (ISO-Kalenderwoche des heutigen Tages)
|
||||
"""
|
||||
heute = date.today()
|
||||
query = Kontaktadresse.query
|
||||
if kunden_id is not None:
|
||||
# Nur Kontaktadressen von Projekten des ausgewählten Kunden.
|
||||
query = query.filter(
|
||||
Kontaktadresse.projekt_id.in_(
|
||||
dbapp.session.query(Projekt.id).filter(
|
||||
Projekt.kunden_id == kunden_id
|
||||
)
|
||||
)
|
||||
)
|
||||
if user_id is not None:
|
||||
# Nur Kontaktadressen von Projekten, denen der Nutzer zugeordnet ist.
|
||||
query = query.filter(
|
||||
Kontaktadresse.projekt_id.in_(
|
||||
dbapp.session.query(ProjektUser.projekt_id).filter(
|
||||
ProjektUser.user_id == user_id
|
||||
)
|
||||
)
|
||||
)
|
||||
kontaktadressen = query.all()
|
||||
|
||||
eintraege = []
|
||||
for kontaktadresse in kontaktadressen:
|
||||
# Gesprächsnotizen des Kontakts, jüngste zuerst. uhrzeit_beginn
|
||||
# ist ein 'HH:MM'-String und sortiert lexikalisch korrekt.
|
||||
logs = sorted(
|
||||
kontaktadresse.call_logs,
|
||||
key=lambda log: (log.datum or date.min, log.uhrzeit_beginn or ""),
|
||||
reverse=True,
|
||||
)
|
||||
letzte_notiz = logs[0] if logs else None
|
||||
# Aktuell gültiges Wiedervorlage-Datum: jüngste Notiz, die
|
||||
# eines gesetzt hat.
|
||||
wiedervorlage_datum = next(
|
||||
(log.wiedervorlage_datum for log in logs
|
||||
if log.wiedervorlage_datum is not None),
|
||||
None,
|
||||
)
|
||||
ist_erledigt = kontaktadresse.is_erledigt()
|
||||
|
||||
# Status-Filter: 'offen' bzw. 'erledigt'; alles andere wirkt
|
||||
# wie kein Filter.
|
||||
if status == "offen" and ist_erledigt:
|
||||
continue
|
||||
if status == "erledigt" and not ist_erledigt:
|
||||
continue
|
||||
# Wiedervorlage-Zeitraum-Filter.
|
||||
if not _ist_im_wiedervorlage_zeitraum(wiedervorlage_datum,
|
||||
wiedervorlage, heute):
|
||||
continue
|
||||
|
||||
projekt = kontaktadresse.projekt
|
||||
eintrag = {
|
||||
'kontaktadresse_id': kontaktadresse.id,
|
||||
'projekt_id': kontaktadresse.projekt_id,
|
||||
'projekt_name': projekt.projektname if projekt else None,
|
||||
'kunden_id': projekt.kunden_id if projekt else None,
|
||||
'kunden_name': projekt.kunde.name if projekt and projekt.kunde else None,
|
||||
'anrede': kontaktadresse.anrede,
|
||||
'name': kontaktadresse.name,
|
||||
'firma': kontaktadresse.firma,
|
||||
'position': kontaktadresse.position,
|
||||
'telefon': kontaktadresse.telefon,
|
||||
'email': kontaktadresse.email,
|
||||
'is_erledigt': ist_erledigt,
|
||||
# Text der letzten Gesprächsnotiz (leerer String, falls der
|
||||
# jüngste Log keinen Notiztext enthält) bzw. None, wenn der
|
||||
# Kontakt noch keine Gesprächsnotiz hat.
|
||||
'letzte_notiz': (letzte_notiz.notiz or "") if letzte_notiz else None,
|
||||
'letzte_notiz_datum': letzte_notiz.datum.isoformat() if letzte_notiz and letzte_notiz.datum else None,
|
||||
'letzte_notiz_uhrzeit': letzte_notiz.uhrzeit_beginn if letzte_notiz else None,
|
||||
# Tags der letzten Gesprächsnotiz (für die Listen-Anzeige);
|
||||
# leere Liste, wenn der Kontakt noch keine Notiz hat.
|
||||
'letzte_notiz_tags': (
|
||||
[clt.tag.to_dict() for clt in letzte_notiz.tags_rel]
|
||||
if letzte_notiz else []
|
||||
),
|
||||
'wiedervorlage_datum': wiedervorlage_datum.isoformat() if wiedervorlage_datum else None,
|
||||
}
|
||||
eintraege.append(eintrag)
|
||||
|
||||
# Priorisierte Sortierung: überfällige Wiedervorlagen zuerst, dann
|
||||
# Kontakte ohne Gesprächsnotizen, dann der Rest nach Wiedervorlage.
|
||||
eintraege.sort(key=lambda eintrag: _sortierschlüssel(eintrag, heute))
|
||||
return eintraege
|
||||
@@ -27,6 +27,7 @@ from crm.model import projekt as projekte
|
||||
from crm.model import projekt_user as projekt_users
|
||||
from crm.model import kontaktadresse as kontaktadressen
|
||||
from crm.model import call_log as call_logs
|
||||
from crm.model import tages_workflow
|
||||
from crm.model import tag as tags
|
||||
|
||||
print("crm-routes")
|
||||
@@ -332,11 +333,15 @@ def kontaktadresse_delete():
|
||||
@app.get("/xhr/calllog/list")
|
||||
@authorisation_required([MITARBEITER, KUNDEN_MANAGER])
|
||||
def calllog_list():
|
||||
"""Liefert Gesprächsnotizen als Liste. Optional nach projekt_id gefiltert.
|
||||
Für Nicht-SysAdmin/Nicht-AppAdmin nur Notizen sichtbarer Projekte."""
|
||||
"""Liefert Gesprächsnotizen als Liste. Optional nach projekt_id und/oder
|
||||
kontaktadresse_id gefiltert. Für Nicht-SysAdmin/Nicht-AppAdmin nur
|
||||
Notizen sichtbarer Projekte."""
|
||||
projekt_id = request.args.get("projekt_id", type=int)
|
||||
kontaktadresse_id = request.args.get("kontaktadresse_id", type=int)
|
||||
user_id = _get_visible_user_id()
|
||||
result = [l.to_dict() for l in call_logs.get_call_logs(projekt_id, user_id)]
|
||||
result = [l.to_dict() for l in call_logs.get_call_logs(
|
||||
projekt_id, user_id, kontaktadresse_id
|
||||
)]
|
||||
return jsonify(result), 200
|
||||
|
||||
|
||||
@@ -354,9 +359,10 @@ def calllog_get():
|
||||
@app.post("/xhr/calllog/create")
|
||||
@authorisation_required([KUNDEN_MANAGER])
|
||||
def calllog_create():
|
||||
"""Legt eine neue Gesprächsnotiz an. ID wird serverseitig vergeben."""
|
||||
"""Legt eine neue Gesprächsnotiz an. ID wird serverseitig vergeben.
|
||||
current_user.id wird als Ersteller (user_id) erfasst."""
|
||||
_require_projekt_access(int(request.json["projekt_id"]))
|
||||
call_log = call_logs.create_call_log(request.json)
|
||||
call_log = call_logs.create_call_log(request.json, current_user.id)
|
||||
return jsonify(call_log.to_dict()), 200
|
||||
|
||||
|
||||
@@ -381,6 +387,30 @@ def calllog_delete():
|
||||
return jsonify({}), 200
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Tages-Workflow #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
@app.get("/xhr/workflow/list")
|
||||
@authorisation_required([MITARBEITER, KUNDEN_MANAGER])
|
||||
def workflow_list():
|
||||
"""Tages-Workflow-Liste: Kontaktadressen mit letzter Notiz, Wiedervorlage
|
||||
und dynamisch berechnetem Status. Optional gefiltert nach kunden_id,
|
||||
status ('offen'/'erledigt') und wiedervorlage ('heute'/'3tage'/
|
||||
'ueberfaellig'/'woche'). Für Nicht-SysAdmin/Nicht-AppAdmin nur
|
||||
Kontaktadressen sichtbarer Projekte. Die Sortierung ist priorisiert:
|
||||
überfällige Wiedervorlagen, dann Kontakte ohne Notizen, dann der Rest."""
|
||||
user_id = _get_visible_user_id()
|
||||
result = tages_workflow.get_tages_workflow_list(
|
||||
user_id,
|
||||
request.args.get("kunden_id", type=int),
|
||||
request.args.get("status"),
|
||||
request.args.get("wiedervorlage"),
|
||||
)
|
||||
return jsonify(result), 200
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Tag (hierarchische Taxonomie) #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
Reference in new issue
Block a user