d60349df67
- Neues Model SpecialAssignment mit AssignmentMode (fza|payroll|both)
- CRUD-Endpunkte unter /users/{id}/special-assignments
- Payroll-Report: GET /reports/special-assignments/payroll?year=&month=
- Migration 0029: special_assignments Tabelle + btree_gist Overlap-Constraint
- _recalculate_overtime_balance berücksichtigt FZA-Faktoren
- Frontend: Sondervertretungs-Zeiträume im UsersPage Edit-Modal
- Frontend: ReportsPage neuer Tab 'Sondervertretungen' mit Payroll-Tabelle + CSV-Export
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
77 lines
2.2 KiB
Python
77 lines
2.2 KiB
Python
from datetime import date
|
|
from decimal import Decimal
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field, model_validator
|
|
|
|
from app.models.special_assignment import AssignmentMode
|
|
|
|
|
|
class SpecialAssignmentCreate(BaseModel):
|
|
date_from: date
|
|
date_to: date
|
|
factor: Decimal = Field(gt=0, le=10, decimal_places=3)
|
|
mode: AssignmentMode = AssignmentMode.both
|
|
description: str | None = None
|
|
label: str | None = Field(None, max_length=100)
|
|
|
|
@model_validator(mode="after")
|
|
def dates_valid(self) -> "SpecialAssignmentCreate":
|
|
if self.date_from > self.date_to:
|
|
raise ValueError("date_from darf nicht nach date_to liegen")
|
|
return self
|
|
|
|
|
|
class SpecialAssignmentUpdate(BaseModel):
|
|
date_from: date | None = None
|
|
date_to: date | None = None
|
|
factor: Decimal | None = Field(None, gt=0, le=10)
|
|
mode: AssignmentMode | None = None
|
|
description: str | None = None
|
|
label: str | None = Field(None, max_length=100)
|
|
|
|
|
|
class SpecialAssignmentOut(BaseModel):
|
|
id: UUID
|
|
user_id: UUID
|
|
company_id: UUID
|
|
date_from: date
|
|
date_to: date
|
|
factor: Decimal
|
|
mode: AssignmentMode
|
|
description: str | None = None
|
|
label: str | None = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
# ── Payroll-Report-Schemas ────────────────────────────────────────────────────
|
|
|
|
class PayrollAssignmentEntry(BaseModel):
|
|
"""Einzelner Zeitraum mit Faktor für den Payroll-Report."""
|
|
assignment_id: UUID
|
|
label: str | None
|
|
date_from: date
|
|
date_to: date
|
|
factor: Decimal
|
|
normal_hours: float # Stunden ohne Faktor
|
|
factor_hours: float # Stunden * Faktor (effektiv für Abrechnung)
|
|
extra_hours: float # factor_hours - normal_hours (Mehrwert)
|
|
|
|
|
|
class PayrollAssignmentRow(BaseModel):
|
|
"""Zusammenfassung pro Mitarbeiter."""
|
|
user_id: UUID
|
|
user_name: str
|
|
personnel_number: str | None
|
|
assignments: list[PayrollAssignmentEntry]
|
|
total_normal_hours: float
|
|
total_factor_hours: float
|
|
total_extra_hours: float
|
|
|
|
|
|
class PayrollAssignmentReport(BaseModel):
|
|
year: int
|
|
month: int
|
|
rows: list[PayrollAssignmentRow]
|