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>
39 lines
1.8 KiB
Python
39 lines
1.8 KiB
Python
"""Sondervertretungs-Faktoren: Per-User Zeitraum-Zuweisung mit Multiplikator."""
|
|
import enum
|
|
from datetime import date
|
|
from decimal import Decimal
|
|
from uuid import UUID, uuid4
|
|
|
|
from sqlalchemy import CheckConstraint, Date, Enum, ForeignKey, Numeric, String, Text
|
|
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.core.database import Base
|
|
|
|
|
|
class AssignmentMode(str, enum.Enum):
|
|
fza = "fza" # Nur FZA-Stunden-Anrechnung
|
|
payroll = "payroll" # Nur Gehaltsabrechnung (Bericht)
|
|
both = "both" # Beides
|
|
|
|
|
|
class SpecialAssignment(Base):
|
|
"""Sondervertretungs-Zeitraum mit Faktor für einen Mitarbeiter."""
|
|
__tablename__ = "special_assignments"
|
|
__table_args__ = (
|
|
CheckConstraint("factor > 0 AND factor <= 10", name="ck_special_assignment_factor"),
|
|
CheckConstraint("date_from <= date_to", name="ck_special_assignment_dates"),
|
|
)
|
|
|
|
id: Mapped[UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
user_id: Mapped[UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
company_id: Mapped[UUID] = mapped_column(PG_UUID(as_uuid=True), ForeignKey("companies.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
date_from: Mapped[date] = mapped_column(Date, nullable=False)
|
|
date_to: Mapped[date] = mapped_column(Date, nullable=False)
|
|
|
|
factor: Mapped[Decimal] = mapped_column(Numeric(5, 3), nullable=False)
|
|
mode: Mapped[AssignmentMode] = mapped_column(Enum(AssignmentMode, name="assignment_mode"), nullable=False, default=AssignmentMode.both)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
label: Mapped[str | None] = mapped_column(String(100), nullable=True) # z.B. "Schichtleiter Vertretung"
|