Initial commit – TimeMaster Zeiterfassung & HR-Tool
Stand: agent-06 (Audit-Log), agent-05 (Krankmeldung), agent-07 Phase 1 (Personalnummer), Busylight-Pull-Integration, TOTP/2FA, Abwesenheiten, Zeiterfassung, Kiosk-Grundgerüst. Migrations 0001–0023 deployed auf 192.168.1.137 + .164. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.models.absence import AbsenceStatus
|
||||
from app.models.absence_type import AbsenceCategory
|
||||
|
||||
|
||||
# ── AbsenceType ───────────────────────────────────────────────────────────────
|
||||
|
||||
class AbsenceTypeOut(BaseModel):
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
id: uuid.UUID
|
||||
company_id: uuid.UUID
|
||||
name: str
|
||||
color: str
|
||||
category: AbsenceCategory
|
||||
requires_approval: bool
|
||||
deducts_vacation: bool
|
||||
affects_overtime_balance: bool
|
||||
requires_certificate: bool
|
||||
certificate_after_days: int
|
||||
is_paid: bool
|
||||
max_days_per_year: int | None
|
||||
is_active: bool
|
||||
|
||||
|
||||
class AbsenceTypeCreate(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
color: str = Field("#3B82F6", pattern=r"^#[0-9A-Fa-f]{6}$")
|
||||
category: AbsenceCategory = AbsenceCategory.OTHER
|
||||
requires_approval: bool = True
|
||||
deducts_vacation: bool = False
|
||||
affects_overtime_balance: bool = False
|
||||
requires_certificate: bool = False
|
||||
certificate_after_days: int = Field(3, ge=0, le=365)
|
||||
is_paid: bool = True
|
||||
max_days_per_year: int | None = Field(None, ge=1)
|
||||
|
||||
|
||||
class AbsenceTypeUpdate(BaseModel):
|
||||
name: str | None = Field(None, min_length=1, max_length=255)
|
||||
color: str | None = Field(None, pattern=r"^#[0-9A-Fa-f]{6}$")
|
||||
category: AbsenceCategory | None = None
|
||||
requires_approval: bool | None = None
|
||||
deducts_vacation: bool | None = None
|
||||
affects_overtime_balance: bool | None = None
|
||||
requires_certificate: bool | None = None
|
||||
certificate_after_days: int | None = Field(None, ge=0, le=365)
|
||||
is_paid: bool | None = None
|
||||
max_days_per_year: int | None = Field(None, ge=1)
|
||||
is_active: bool | None = None
|
||||
|
||||
|
||||
# ── Absence ───────────────────────────────────────────────────────────────────
|
||||
|
||||
class AbsenceOut(BaseModel):
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
id: uuid.UUID
|
||||
user_id: uuid.UUID
|
||||
type_id: uuid.UUID
|
||||
start_date: date
|
||||
end_date: date
|
||||
half_day_start: bool
|
||||
half_day_end: bool
|
||||
working_days: float
|
||||
status: AbsenceStatus
|
||||
approved_by: uuid.UUID | None
|
||||
substitute_id: uuid.UUID | None
|
||||
note: str | None
|
||||
correction_note: str | None
|
||||
rejection_reason: str | None
|
||||
certificate_required_by: date | None = None
|
||||
certificate_received_at: date | None = None
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class AbsenceCreate(BaseModel):
|
||||
type_id: uuid.UUID
|
||||
start_date: date
|
||||
end_date: date
|
||||
half_day_start: bool = False
|
||||
half_day_end: bool = False
|
||||
substitute_id: uuid.UUID | None = None
|
||||
note: str | None = None
|
||||
for_user_id: uuid.UUID | None = None # HR/Admin: Abwesenheit für anderen Mitarbeiter anlegen
|
||||
|
||||
def model_post_init(self, __context) -> None:
|
||||
if self.end_date < self.start_date:
|
||||
raise ValueError("end_date must be >= start_date")
|
||||
|
||||
|
||||
class AbsenceUpdate(BaseModel):
|
||||
type_id: uuid.UUID | None = None
|
||||
start_date: date | None = None
|
||||
end_date: date | None = None
|
||||
half_day_start: bool | None = None
|
||||
half_day_end: bool | None = None
|
||||
substitute_id: uuid.UUID | None = None
|
||||
note: str | None = None
|
||||
correction_note: str | None = None # Pflicht bei Änderung genehmigter Anträge (Mitarbeiter)
|
||||
|
||||
def model_post_init(self, __context) -> None:
|
||||
if self.start_date and self.end_date and self.end_date < self.start_date:
|
||||
raise ValueError("end_date must be >= start_date")
|
||||
|
||||
|
||||
class AbsenceReject(BaseModel):
|
||||
rejection_reason: str = Field(min_length=1)
|
||||
|
||||
|
||||
class AbsenceListResponse(BaseModel):
|
||||
total: int
|
||||
items: list[AbsenceOut]
|
||||
|
||||
|
||||
# ── Krankmeldung ──────────────────────────────────────────────────────────────
|
||||
|
||||
class QuickSickIn(BaseModel):
|
||||
start_date: date
|
||||
end_date: date
|
||||
|
||||
def model_post_init(self, __context) -> None:
|
||||
if self.end_date < self.start_date:
|
||||
raise ValueError("end_date must be >= start_date")
|
||||
|
||||
|
||||
class CertificateMarkIn(BaseModel):
|
||||
received_at: date | None = None # default = heute
|
||||
|
||||
|
||||
class SickStatsOut(BaseModel):
|
||||
user_id: uuid.UUID
|
||||
user_name: str
|
||||
personnel_number: str | None = None
|
||||
episodes: int
|
||||
total_days: float
|
||||
bradford_factor: float
|
||||
certificates_overdue: int
|
||||
|
||||
|
||||
# ── VacationBalance ───────────────────────────────────────────────────────────
|
||||
|
||||
class VacationBalanceOut(BaseModel):
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
id: uuid.UUID
|
||||
user_id: uuid.UUID
|
||||
year: int
|
||||
entitled_days: int
|
||||
special_days: int = 0
|
||||
carried_over: int
|
||||
used_days: int
|
||||
total_days: int
|
||||
remaining_days: int
|
||||
pending_days: float = 0
|
||||
# Resturlaub-Verfall (wird zur Laufzeit befüllt, nicht in DB)
|
||||
carried_over_expires_at: date | None = None
|
||||
carried_over_expired: bool = False
|
||||
|
||||
|
||||
class VacationBalanceUpdate(BaseModel):
|
||||
entitled_days: int | None = Field(None, ge=0, le=365)
|
||||
special_days: int | None = Field(None, ge=0, le=365)
|
||||
carried_over: int | None = Field(None, ge=0, le=365)
|
||||
|
||||
|
||||
# ── PublicHoliday ─────────────────────────────────────────────────────────────
|
||||
|
||||
class PublicHolidayOut(BaseModel):
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
id: uuid.UUID
|
||||
country: str
|
||||
state: str | None
|
||||
date: date
|
||||
name: str
|
||||
year: int
|
||||
|
||||
|
||||
class PublicHolidayCreate(BaseModel):
|
||||
country: str = Field("DE", min_length=2, max_length=10)
|
||||
state: str | None = Field(None, max_length=10)
|
||||
date: date
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
|
||||
|
||||
# ── OvertimeBalance ───────────────────────────────────────────────────────────
|
||||
|
||||
class OvertimeBalanceOut(BaseModel):
|
||||
total_hours: float
|
||||
taken_hours: float
|
||||
available_hours: float
|
||||
|
||||
|
||||
# ── Calendar ──────────────────────────────────────────────────────────────────
|
||||
|
||||
class CalendarEntry(BaseModel):
|
||||
user_id: uuid.UUID
|
||||
user_name: str
|
||||
absence_id: uuid.UUID
|
||||
type_name: str
|
||||
type_color: str
|
||||
start_date: date
|
||||
end_date: date
|
||||
status: AbsenceStatus
|
||||
working_days: float
|
||||
Reference in New Issue
Block a user