feat: agent-11 PR1 – Vertretung, Storno-Re-Genehmigung, Kommentare
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>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
"""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")
|
||||
Reference in New Issue
Block a user