Files
timemaster/backend/app/schemas/company.py
T
patrickandClaude Opus 4.8 65596e80a4
Security Audit / Node.js Dependency Audit (push) Has been cancelled
Security Audit / Python Dependency Audit (push) Has been cancelled
feat(overtime): Auszahlungs-Anträge durch Mitarbeiter (opt-in pro Firma)
Mitarbeiter beantragt Überstunden-Auszahlung, HR genehmigt/lehnt ab.
Firmen-Opt-in payout_request_enabled (Default aus - nur HR-Direktbuchung).
Saldo-Abzug erst bei Genehmigung; Reject/Cancel bucht nichts.

- Migration 0042: hours_payouts.status/decided_by/decided_at/rejection_reason
  + companies.payout_request_enabled (nur Spalten, RLS unveraendert)
- Router: POST /hr/payouts/request|{id}/approve|reject|cancel; list status-Filter;
  HR-Direktbuchung bleibt (status approved)
- Frontend: PayoutRequestCard (Selbstbedienung in AbsencesPage), HR-Page
  Status-Spalte + Genehmigen/Ablehnen, CompanySettings-Toggle
- 4 pytest-Cases

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 22:43:26 +02:00

116 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import uuid
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
PersonnelNumberModeT = Literal["manual", "auto"]
class CompanySettingsUpdate(BaseModel):
"""Validiertes Sub-Schema für das company.settings JSONB-Feld.
Nur bekannte Top-Level-Keys sind erlaubt (extra="forbid").
Derzeit genutzte Keys: carryover_expires_month, carryover_expires_day
(gelesen in absences.py für Resturlaub-Verfall-Berechnung).
"""
model_config = ConfigDict(extra="forbid")
carryover_expires_month: int | None = Field(None, ge=1, le=12)
carryover_expires_day: int | None = Field(None, ge=1, le=31)
class CompanyOut(BaseModel):
model_config = {"from_attributes": True}
id: uuid.UUID
name: str
slug: str
plan: str
logo_url: str | None
country: str
state: str | None
settings: dict
personnel_number_required: bool = False
personnel_number_mode: PersonnelNumberModeT = "manual"
personnel_number_next: int = 1
mobile_stamping_enabled: bool = True
overtime_overdraft_allowed: bool = True
overtime_warning_threshold_hours: int = 0
overtime_cap_hours: int | None = None
overtime_expiry_enabled: bool = False
overtime_expiry_month: int = 3
overtime_expiry_day: int = 31
overtime_max_carryover_hours: int | None = None
payout_request_enabled: bool = False
kiosk_require_approval: bool = True
kiosk_track_current_user: bool = True
kiosk_heartbeat_interval_sec: int = 30
public_stamp_enabled: bool = False
vacation_default_days: int = 30
vacation_prorate_first_year: bool = True
vacation_from_schedule: bool = False
two_stage_approval_enabled: bool = False
two_stage_min_days: int = 0
class PublicStampTokenStatus(BaseModel):
"""Status des öffentlichen QR-Stempel-Tokens (kein Klartext)."""
enabled: bool
configured: bool
created_at: datetime | None = None
class PublicStampTokenRotated(BaseModel):
"""Antwort beim Rotieren enthält Klartext-Token + fertige URL (nur einmalig)."""
token: str = Field(..., description="Klartext-Token, wird nur einmal angezeigt.")
public_url: str
created_at: datetime
class CompanyUpdate(BaseModel):
name: str | None = Field(None, min_length=2, max_length=255)
state: str | None = Field(None, max_length=10)
settings: CompanySettingsUpdate | None = None
personnel_number_required: bool | None = None
personnel_number_mode: PersonnelNumberModeT | None = None
personnel_number_next: int | None = Field(None, ge=1)
mobile_stamping_enabled: bool | None = None
overtime_overdraft_allowed: bool | None = None
overtime_warning_threshold_hours: int | None = Field(None, ge=0)
overtime_cap_hours: int | None = Field(None, ge=1, le=9999)
overtime_expiry_enabled: bool | None = None
overtime_expiry_month: int | None = Field(None, ge=1, le=12)
overtime_expiry_day: int | None = Field(None, ge=1, le=31)
overtime_max_carryover_hours: int | None = Field(None, ge=0, le=9999)
payout_request_enabled: bool | None = None
kiosk_require_approval: bool | None = None
kiosk_track_current_user: bool | None = None
kiosk_heartbeat_interval_sec: int | None = Field(None, ge=10, le=120)
public_stamp_enabled: bool | None = None
vacation_default_days: int | None = Field(None, ge=0, le=365)
vacation_prorate_first_year: bool | None = None
vacation_from_schedule: bool | None = None
two_stage_approval_enabled: bool | None = None
two_stage_min_days: int | None = Field(None, ge=0, le=365)
class DepartmentOut(BaseModel):
model_config = {"from_attributes": True}
id: uuid.UUID
company_id: uuid.UUID
name: str
manager_id: uuid.UUID | None
class DepartmentCreate(BaseModel):
name: str = Field(min_length=1, max_length=255)
manager_id: uuid.UUID | None = None
class DepartmentUpdate(BaseModel):
name: str | None = Field(None, min_length=1, max_length=255)
manager_id: uuid.UUID | None = None