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>
51 lines
1.6 KiB
Python
51 lines
1.6 KiB
Python
import uuid
|
||
from datetime import datetime
|
||
from decimal import Decimal
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
class HoursPayoutCreate(BaseModel):
|
||
user_id: uuid.UUID
|
||
hours: Decimal = Field(gt=0, le=999.99, decimal_places=2)
|
||
period_year: int | None = Field(None, ge=2000, le=2100)
|
||
period_month: int | None = Field(None, ge=1, le=12)
|
||
note: str | None = Field(None, max_length=500)
|
||
|
||
|
||
class HoursPayoutRequest(BaseModel):
|
||
"""Mitarbeiter beantragt Auszahlung eigener Überstunden (kein user_id – immer self)."""
|
||
hours: Decimal = Field(gt=0, le=999.99, decimal_places=2)
|
||
period_year: int | None = Field(None, ge=2000, le=2100)
|
||
period_month: int | None = Field(None, ge=1, le=12)
|
||
note: str | None = Field(None, max_length=500)
|
||
|
||
|
||
class HoursPayoutReject(BaseModel):
|
||
rejection_reason: str | None = Field(None, max_length=500)
|
||
|
||
|
||
class HoursPayoutOut(BaseModel):
|
||
model_config = {"from_attributes": True}
|
||
|
||
id: uuid.UUID
|
||
company_id: uuid.UUID
|
||
user_id: uuid.UUID
|
||
user_name: str = "" # Computed: first_name + last_name (wird im Router gesetzt)
|
||
hours: Decimal
|
||
period_year: int | None
|
||
period_month: int | None
|
||
note: str | None
|
||
status: str = "approved"
|
||
rejection_reason: str | None = None
|
||
created_by: uuid.UUID
|
||
created_by_name: str = "" # Computed im Router
|
||
decided_by: uuid.UUID | None = None
|
||
decided_by_name: str = "" # Computed im Router
|
||
decided_at: datetime | None = None
|
||
created_at: datetime
|
||
|
||
|
||
class HoursPayoutListResponse(BaseModel):
|
||
payouts: list[HoursPayoutOut]
|
||
total_count: int
|