Abwesenheits-Modul abgerundet (Feature-Parität mit Urlaubsverwaltung):
- Vertretung: Overlap-Warnung beim Anlegen, E-Mail an Vertretung bei
Genehmigung, GET /absences/?as_substitute=true, neuer schlanker
GET /users/colleagues (alle Rollen, RLS-gefenced) für die Auswahl;
Vertreter-Dropdown + Anzeige in der Liste.
- Stornierung mit Re-Genehmigung: neuer Status CANCELLATION_REQUESTED,
POST /absences/{id}/request-cancellation; Manager genehmigt/lehnt über
bestehende approve/reject ab (Urlaub + FZA-Rückbuchung via _apply_cancellation).
- Kommentare: Model AbsenceComment (company_id-RLS), GET/POST comments,
System-Kommentare bei Statuswechsel, AbsenceCommentsModal.
- Fix: CalDAV fire-and-forget nutzte die Request-Session weiter (in Tests
geteilt -> "another operation in progress"); jetzt sync_*_bg mit eigener
Session + RLS-Bypass.
Migration 0035. 178/178 Tests grün. Deployed auf 137 + 164.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
62 lines
2.6 KiB
Python
62 lines
2.6 KiB
Python
"""Absence comments + cancellation-request status (agent-11 PR1)
|
||
|
||
Revision ID: 0035
|
||
Revises: 0034
|
||
Create Date: 2026-06-23
|
||
|
||
- Neuer Enum-Wert AbsenceStatus.CANCELLATION_REQUESTED (Mitarbeiter-Stornoantrag
|
||
für genehmigte Anträge → Manager genehmigt/lehnt ab)
|
||
- Neue Tabelle absence_comments (Kommentar-Thread + System-Kommentare bei Statuswechsel)
|
||
mit company_id-Spalte → reguläre company_id-RLS-Policy (analog 0024).
|
||
"""
|
||
from alembic import op
|
||
from sqlalchemy import text
|
||
|
||
revision = "0035"
|
||
down_revision = "0034"
|
||
branch_labels = None
|
||
depends_on = None
|
||
|
||
_BYPASS = "COALESCE(current_setting('app.bypass_rls', true), 'off') = 'on'"
|
||
_CID = "company_id = NULLIF(current_setting('app.company_id', true), '')::uuid"
|
||
_USING = f"({_BYPASS} OR {_CID})"
|
||
|
||
|
||
def _exec(sql: str) -> None:
|
||
op.execute(text(sql))
|
||
|
||
|
||
def upgrade() -> None:
|
||
# 1) Enum-Wert ergänzen (idempotent)
|
||
_exec("ALTER TYPE absencestatus ADD VALUE IF NOT EXISTS 'cancellation_requested'")
|
||
|
||
# 2) Tabelle anlegen
|
||
_exec("""
|
||
CREATE TABLE IF NOT EXISTS absence_comments (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
absence_id UUID NOT NULL REFERENCES absences(id) ON DELETE CASCADE,
|
||
company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
||
author_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||
body TEXT NOT NULL,
|
||
is_system BOOLEAN NOT NULL DEFAULT FALSE,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
)
|
||
""")
|
||
_exec("CREATE INDEX IF NOT EXISTS ix_absence_comments_absence_id ON absence_comments(absence_id)")
|
||
_exec("CREATE INDEX IF NOT EXISTS ix_absence_comments_company_id ON absence_comments(company_id)")
|
||
|
||
# 3) RLS (company_id-gefenced, analog 0024)
|
||
_exec("ALTER TABLE absence_comments ENABLE ROW LEVEL SECURITY")
|
||
_exec("ALTER TABLE absence_comments FORCE ROW LEVEL SECURITY")
|
||
for cmd in ("select", "insert", "update", "delete"):
|
||
_exec(f"DROP POLICY IF EXISTS rls_absence_comments_{cmd} ON absence_comments")
|
||
_exec(f"CREATE POLICY rls_absence_comments_select ON absence_comments FOR SELECT USING {_USING}")
|
||
_exec(f"CREATE POLICY rls_absence_comments_insert ON absence_comments FOR INSERT WITH CHECK {_USING}")
|
||
_exec(f"CREATE POLICY rls_absence_comments_update ON absence_comments FOR UPDATE USING {_USING} WITH CHECK {_USING}")
|
||
_exec(f"CREATE POLICY rls_absence_comments_delete ON absence_comments FOR DELETE USING {_USING}")
|
||
|
||
|
||
def downgrade() -> None:
|
||
# Enum-Wert kann in PostgreSQL nicht entfernt werden – Tabelle wird gedroppt.
|
||
_exec("DROP TABLE IF EXISTS absence_comments")
|