refactor: AIDA -> modulare Zeiterfassung
- Alle AIDA-Referenzen durch generische Begriffe ersetzt
(aida -> attendance im Code, "Zeiterfassung" im UI)
- DB-Spalten: last_aida_status -> last_attendance_status
- Settings: aida_csv_path -> attendance_csv_path
- REST API fuer Zeiterfassung auf Port 8081:
POST /api/attendance/{personal_nr} mit {"status": "anwesend"}
GET /api/attendance - alle Status abfragen
- Automatische DB-Migration fuer bestehende Installationen
- CSV bleibt als Option, API als Echtzeit-Alternative
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+31
-35
@@ -11,8 +11,8 @@ logger = logging.getLogger(__name__)
|
||||
graph = None
|
||||
|
||||
|
||||
def read_aida_csv(csv_path):
|
||||
"""Read AIDA status CSV. Returns list of lines."""
|
||||
def read_attendance_csv(csv_path):
|
||||
"""Read attendance status CSV. Returns list of lines."""
|
||||
if not csv_path or not os.path.exists(csv_path):
|
||||
return []
|
||||
try:
|
||||
@@ -20,7 +20,7 @@ def read_aida_csv(csv_path):
|
||||
lines = f.read().splitlines()
|
||||
return lines
|
||||
except Exception as e:
|
||||
logger.error("Fehler beim Lesen der AIDA CSV: %s", e)
|
||||
logger.error("Fehler beim Lesen der Zeiterfassungs-CSV: %s", e)
|
||||
return []
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ def _extract_nr(value):
|
||||
return value.strip().strip("~")
|
||||
|
||||
|
||||
def determine_aida_status(lines, personal_nr, settings):
|
||||
"""Determine AIDA status for a user based on their personal number."""
|
||||
def determine_attendance_status(lines, personal_nr, settings):
|
||||
"""Determine attendance status for a user based on their personal number."""
|
||||
if not personal_nr or not lines:
|
||||
return None
|
||||
|
||||
@@ -45,9 +45,9 @@ def determine_aida_status(lines, personal_nr, settings):
|
||||
|
||||
line = matching[0]
|
||||
# Check status part (after comma or at end)
|
||||
status_abwesend = _extract_nr(settings.get("aida_status_abwesend", "~~0~~"))
|
||||
status_anwesend = _extract_nr(settings.get("aida_status_anwesend", "~~1~~"))
|
||||
status_homeoffice = _extract_nr(settings.get("aida_status_homeoffice", "~~2~~"))
|
||||
status_abwesend = _extract_nr(settings.get("attendance_status_abwesend", "~~0~~"))
|
||||
status_anwesend = _extract_nr(settings.get("attendance_status_anwesend", "~~1~~"))
|
||||
status_homeoffice = _extract_nr(settings.get("attendance_status_homeoffice", "~~2~~"))
|
||||
|
||||
# Get the status value (last part after comma, stripped of ~)
|
||||
parts = line.split(",")
|
||||
@@ -90,7 +90,7 @@ def poll_status():
|
||||
users_with_assignments = conn.execute("""
|
||||
SELECT u.azure_id, u.display_name, u.personal_nr,
|
||||
a.segment, d.hostname,
|
||||
s.last_aida_status, s.last_teams_status,
|
||||
s.last_attendance_status, s.last_teams_status,
|
||||
COALESCE(s.phone_active, 0) as phone_active
|
||||
FROM users u
|
||||
JOIN assignments a ON a.user_azure_id = u.azure_id
|
||||
@@ -103,12 +103,12 @@ def poll_status():
|
||||
logger.debug("Keine aktiven User mit Zuordnungen gefunden")
|
||||
return
|
||||
|
||||
# Read AIDA CSV (kann leer sein)
|
||||
aida_lines = read_aida_csv(settings.get("aida_csv_path", ""))
|
||||
# Read attendance CSV (kann leer sein)
|
||||
attendance_lines = read_attendance_csv(settings.get("attendance_csv_path", ""))
|
||||
|
||||
for user in users_with_assignments:
|
||||
try:
|
||||
_process_user(user, aida_lines, settings, conn)
|
||||
_process_user(user, attendance_lines, settings, conn)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Fehler bei User %s: %s", user["display_name"], e
|
||||
@@ -120,7 +120,7 @@ def poll_status():
|
||||
conn.close()
|
||||
|
||||
|
||||
def _process_user(user, aida_lines, settings, conn):
|
||||
def _process_user(user, attendance_lines, settings, conn):
|
||||
"""Process a single user: check status and update WLED if needed."""
|
||||
global graph
|
||||
|
||||
@@ -129,7 +129,7 @@ def _process_user(user, aida_lines, settings, conn):
|
||||
personal_nr = user["personal_nr"]
|
||||
hostname = user["hostname"]
|
||||
segment = user["segment"]
|
||||
last_aida = user["last_aida_status"] or ""
|
||||
last_attendance = user["last_attendance_status"] or ""
|
||||
last_teams = user["last_teams_status"] or ""
|
||||
phone_active = user["phone_active"]
|
||||
|
||||
@@ -139,29 +139,25 @@ def _process_user(user, aida_lines, settings, conn):
|
||||
if presence and "activity" in presence:
|
||||
teams_status = "teams_" + presence["activity"].lower()
|
||||
|
||||
# AIDA-Status ermitteln
|
||||
aida_status = determine_aida_status(aida_lines, personal_nr, settings)
|
||||
# Anwesenheitsstatus ermitteln
|
||||
attendance_status = determine_attendance_status(attendance_lines, personal_nr, settings)
|
||||
|
||||
# Check if anything changed
|
||||
if aida_status == last_aida and teams_status == last_teams:
|
||||
if attendance_status == last_attendance and teams_status == last_teams:
|
||||
return # No change
|
||||
|
||||
# --- WLED nur steuern wenn AIDA-Status bekannt und kein aktives Telefonat ---
|
||||
wled_changed = aida_status is not None and not phone_active and (
|
||||
aida_status != last_aida or
|
||||
(aida_status == "anwesend" and teams_status != last_teams)
|
||||
# --- WLED nur steuern wenn Anwesenheitsstatus bekannt und kein aktives Telefonat ---
|
||||
wled_changed = attendance_status is not None and not phone_active and (
|
||||
attendance_status != last_attendance or
|
||||
(attendance_status == "anwesend" and teams_status != last_teams)
|
||||
)
|
||||
|
||||
if wled_changed:
|
||||
# Farbe bestimmen nach Originallogik:
|
||||
# abwesend -> abwesend-Farbe
|
||||
# homeoffice -> homeoffice-Farbe
|
||||
# anwesend -> Teams-Farbe
|
||||
if aida_status == "abwesend":
|
||||
if attendance_status == "abwesend":
|
||||
color = get_color_for_status("abwesend", conn)
|
||||
elif aida_status == "homeoffice":
|
||||
elif attendance_status == "homeoffice":
|
||||
color = get_color_for_status("homeoffice", conn)
|
||||
elif aida_status == "anwesend" and teams_status:
|
||||
elif attendance_status == "anwesend" and teams_status:
|
||||
color = get_color_for_status(teams_status, conn)
|
||||
else:
|
||||
color = get_color_for_status("available", conn)
|
||||
@@ -177,26 +173,26 @@ def _process_user(user, aida_lines, settings, conn):
|
||||
|
||||
log_activity(
|
||||
azure_id, display_name,
|
||||
aida_status or "-", teams_status or "-",
|
||||
attendance_status or "-", teams_status or "-",
|
||||
url, result_text,
|
||||
)
|
||||
|
||||
# State immer aktualisieren (fuer Dashboard)
|
||||
conn.execute("""
|
||||
INSERT INTO state (user_azure_id, last_aida_status, last_teams_status, updated_at)
|
||||
INSERT INTO state (user_azure_id, last_attendance_status, last_teams_status, updated_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(user_azure_id)
|
||||
DO UPDATE SET last_aida_status=?, last_teams_status=?, updated_at=?
|
||||
DO UPDATE SET last_attendance_status=?, last_teams_status=?, updated_at=?
|
||||
""", (
|
||||
azure_id,
|
||||
aida_status or last_aida, teams_status, datetime.now(),
|
||||
aida_status or last_aida, teams_status, datetime.now(),
|
||||
attendance_status or last_attendance, teams_status, datetime.now(),
|
||||
attendance_status or last_attendance, teams_status, datetime.now(),
|
||||
))
|
||||
conn.commit()
|
||||
|
||||
logger.info(
|
||||
"%-40s aida=%-12s teams=%-20s wled=%s",
|
||||
display_name, aida_status or "-", teams_status,
|
||||
"%-40s zeiterfassung=%-12s teams=%-20s wled=%s",
|
||||
display_name, attendance_status or "-", teams_status,
|
||||
"aktualisiert" if wled_changed else "nur Dashboard",
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user