feat(dsgvo): Löschkonzept/Aufbewahrungsfristen (Auto-Purge)
Neuer retention_service.py: Lohn-/zeitrelevante Daten (time_entries, hours_payouts) werden nach konfigurierbarer Frist gelöscht (company.settings.retention_lohn_years, Default 10 Jahre). Technische Tabellen mit fester Frist: audit_logs (3 Jahre), abgelaufene sessions/password_resets (sofort). Täglicher Scheduler-Job (03:00 Uhr, Redis-Tageslock analog Reminder-Jobs) plus manuelle Trigger: POST /companies/me/run-retention-purge (COMPANY_ADMIN/HR, nur eigene Firma) und POST /admin/run-retention-purge (SUPER_ADMIN, global inkl. technischer Tabellen). Letzter offener Punkt aus dem DSGVO-Löschkonzept (Art. 15/17 bereits erledigt). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gis16MnuwkYcivLrSxK1pD
This commit is contained in:
@@ -23,6 +23,7 @@ from app.models.user import User, UserRole
|
||||
from app.models.vacation_balance import VacationBalance
|
||||
from app.services.absence_service import absence_service
|
||||
from app.services.email_service import email_service
|
||||
from app.services.retention_service import run_retention_purge
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -188,6 +189,22 @@ async def _run_job(name: str, fn) -> None:
|
||||
log.exception("Reminder-Job %s fehlgeschlagen: %s", name, exc)
|
||||
|
||||
|
||||
async def _run_purge_job(name: str, fn) -> None:
|
||||
if not _acquire_daily_lock(name):
|
||||
log.info("Purge-Job %s bereits von anderem Prozess übernommen.", name)
|
||||
return
|
||||
from sqlalchemy import text
|
||||
from app.core.database import AsyncSessionLocal
|
||||
try:
|
||||
async with AsyncSessionLocal() as db:
|
||||
await db.execute(text("SET LOCAL app.bypass_rls = 'on'"))
|
||||
result = await fn(db)
|
||||
await db.commit()
|
||||
log.info("Purge-Job %s: %s", name, result)
|
||||
except Exception as exc:
|
||||
log.exception("Purge-Job %s fehlgeschlagen: %s", name, exc)
|
||||
|
||||
|
||||
def start() -> None:
|
||||
global _scheduler
|
||||
if _scheduler is not None:
|
||||
@@ -207,6 +224,8 @@ def start() -> None:
|
||||
args=["carryover_expiry", run_carryover_expiry], id="carryover_expiry")
|
||||
_scheduler.add_job(_run_job, CronTrigger(hour=hour, minute=20),
|
||||
args=["certificate_overdue", run_certificate_overdue], id="certificate_overdue")
|
||||
_scheduler.add_job(_run_purge_job, CronTrigger(hour=3, minute=0),
|
||||
args=["retention_purge", run_retention_purge], id="retention_purge")
|
||||
_scheduler.start()
|
||||
log.info("Reminder-Scheduler gestartet (täglich ab %02d:00 Uhr).", hour)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user