Neuer PDF-Export GET /reports/datev-monthly/export: ein Blatt pro Mitarbeiter/Monat im Layout der DATEV-Stundenaufzeichnungs-Vorlage (Beginn/Pause/Ende/Dauer + K/U/UU/F/SA/SU-Kürzel + Bemerkungen + Summe + Unterschriftsfelder). Führt time_entries, Absences und Feiertage pro Kalendertag zusammen. Außerdem: Mustervorlagen für neue Backend-Module (Model/Schema/Router/ Migration/Test) und eine Frontend-Page-Vorlage, abgeleitet vom hours_payouts-Modul als aktuellstem sauberen Muster. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CviFgc739S89xS97pvjszj
217 lines
6.8 KiB
Python
217 lines
6.8 KiB
Python
import uuid
|
||
from datetime import date, time
|
||
from pydantic import BaseModel
|
||
|
||
|
||
# ── Employee Dashboard ─────────────────────────────────────────────────────────
|
||
|
||
class EmployeeDashboard(BaseModel):
|
||
today_open: bool
|
||
today_start: time | None
|
||
today_hours_so_far: float | None
|
||
week_hours_worked: float
|
||
week_hours_expected: float
|
||
week_overtime: float
|
||
vacation_remaining_days: int | None
|
||
vacation_used_days: int
|
||
vacation_entitled_days: int
|
||
pending_absences: int
|
||
overtime_balance_hours: float | None # verfügbares Überstundenguthaben
|
||
schedule_name: str | None # zugewiesener Arbeitsplan
|
||
|
||
|
||
# ── Team Dashboard ─────────────────────────────────────────────────────────────
|
||
|
||
class TeamMemberStatus(BaseModel):
|
||
user_id: uuid.UUID
|
||
user_name: str
|
||
department: str | None
|
||
status: str # "present" | "on_leave" | "absent"
|
||
absence_type: str | None
|
||
time_in: time | None
|
||
hours_today: float | None
|
||
|
||
|
||
class TeamDashboard(BaseModel):
|
||
present_count: int
|
||
on_leave_count: int
|
||
absent_count: int
|
||
pending_time_approvals: int
|
||
pending_absence_approvals: int
|
||
members: list[TeamMemberStatus]
|
||
|
||
|
||
# ── Company Dashboard ──────────────────────────────────────────────────────────
|
||
|
||
class UpcomingAbsence(BaseModel):
|
||
user_id: uuid.UUID
|
||
user_name: str
|
||
absence_type: str
|
||
start_date: date
|
||
end_date: date
|
||
working_days: float
|
||
|
||
|
||
class CompanyDashboard(BaseModel):
|
||
total_employees: int
|
||
active_today: int
|
||
attendance_rate: float
|
||
month_hours_worked: float
|
||
month_hours_expected: float
|
||
month_overtime: float
|
||
pending_time_approvals: int
|
||
pending_absence_approvals: int
|
||
upcoming_absences: list[UpcomingAbsence]
|
||
|
||
|
||
# ── Time Report ────────────────────────────────────────────────────────────────
|
||
|
||
class HoursBreakdown(BaseModel):
|
||
"""Stunden-Aufteilung nach §3b EStG für den Steuerberater."""
|
||
normal_hours: float # Mo–Fr, 06–20 Uhr, kein Feiertag
|
||
night_25_hours: float # 20–24 + 04–06 Uhr (25% Zuschlag)
|
||
night_40_hours: float # 00–04 Uhr (40% Zuschlag)
|
||
sunday_hours: float # Sonntag 00–24 Uhr (50% Zuschlag)
|
||
holiday_125_hours: float # gesetzl. Feiertag (125% Zuschlag)
|
||
holiday_150_hours: float # besondere Feiertage 25.12, 26.12, 01.05 etc. (150%)
|
||
holiday_name: str | None # Name des Feiertags falls zutreffend
|
||
|
||
|
||
class TimeReportRow(BaseModel):
|
||
date: date
|
||
user_id: uuid.UUID
|
||
user_name: str
|
||
personnel_number: str | None = None
|
||
department: str | None
|
||
start_time: time
|
||
end_time: time | None
|
||
break_minutes: int
|
||
worked_hours: float | None
|
||
status: str
|
||
source: str
|
||
note: str | None
|
||
breakdown: HoursBreakdown | None = None # None wenn kein Bundesland konfiguriert
|
||
|
||
|
||
class TimeReport(BaseModel):
|
||
date_from: date
|
||
date_to: date
|
||
total_rows: int
|
||
total_hours: float
|
||
rows: list[TimeReportRow]
|
||
|
||
|
||
# ── Absence Report ─────────────────────────────────────────────────────────────
|
||
|
||
class AbsenceReportRow(BaseModel):
|
||
user_id: uuid.UUID
|
||
user_name: str
|
||
personnel_number: str | None = None
|
||
department: str | None
|
||
absence_type: str
|
||
start_date: date
|
||
end_date: date
|
||
working_days: float
|
||
status: str
|
||
note: str | None
|
||
|
||
|
||
class AbsenceReport(BaseModel):
|
||
date_from: date
|
||
date_to: date
|
||
total_rows: int
|
||
total_days: float
|
||
rows: list[AbsenceReportRow]
|
||
|
||
|
||
# ── Overtime Report ────────────────────────────────────────────────────────────
|
||
|
||
class OvertimeReportRow(BaseModel):
|
||
user_id: uuid.UUID
|
||
user_name: str
|
||
personnel_number: str | None = None
|
||
department: str | None
|
||
hours_worked: float
|
||
hours_expected: float
|
||
overtime_hours: float
|
||
|
||
|
||
class OvertimeReport(BaseModel):
|
||
date_from: date
|
||
date_to: date
|
||
total_employees: int
|
||
total_overtime: float
|
||
rows: list[OvertimeReportRow]
|
||
|
||
|
||
# ── Overtime Detail Report (Option A: Inline-Expand) ──────────────────────────
|
||
|
||
class DayEntry(BaseModel):
|
||
"""Einzelner Zeiteintrag innerhalb eines Tages (mehrere möglich)."""
|
||
start_time: time
|
||
end_time: time
|
||
break_minutes: int
|
||
hours_worked: float
|
||
status: str
|
||
arbzg_warnings: list[str] = []
|
||
breakdown: HoursBreakdown | None = None
|
||
|
||
|
||
class OvertimeDay(BaseModel):
|
||
date: date
|
||
weekday: str # "Mo", "Di", …
|
||
hours_worked: float # Summe aller Einträge des Tages
|
||
hours_expected: float
|
||
overtime: float
|
||
entries: list[DayEntry] = [] # leer = kein Eintrag an dem Tag
|
||
|
||
|
||
class OvertimeWeek(BaseModel):
|
||
week_nr: int
|
||
week_start: date
|
||
week_end: date
|
||
hours_worked: float
|
||
hours_expected: float
|
||
overtime: float
|
||
days: list[OvertimeDay]
|
||
|
||
|
||
class OvertimeReportRowDetailed(OvertimeReportRow):
|
||
weeks: list[OvertimeWeek] = []
|
||
arbzg_violation_days: int = 0 # Tage > 10h
|
||
special_hours_total: HoursBreakdown | None = None
|
||
|
||
|
||
class OvertimeReportDetailed(BaseModel):
|
||
date_from: date
|
||
date_to: date
|
||
total_employees: int
|
||
total_overtime: float
|
||
rows: list[OvertimeReportRowDetailed]
|
||
|
||
|
||
# ── DATEV-Monatsblatt ──────────────────────────────────────────────────────────
|
||
# Layout entspricht "Stundenaufzeichnungen Muster blanko DATEV ab 2015":
|
||
# ein Blatt pro Mitarbeiter/Monat, Zeile pro Kalendertag.
|
||
|
||
class DatevDayRow(BaseModel):
|
||
day: int # Kalendertag 1-31
|
||
weekday_label: str # "Mo".."So" für Anzeige
|
||
start_time: time | None = None
|
||
break_minutes: int | None = None
|
||
end_time: time | None = None
|
||
duration_hours: float | None = None
|
||
code: str | None = None # K/U/UU/F/SA/SU – siehe DATEV_CODES
|
||
recorded_on: date | None = None # "aufgezeichnet am" (created_at des Eintrags)
|
||
note: str | None = None # Bemerkungen (Absence-Typ-Name o.ä.)
|
||
|
||
|
||
class DatevMonthlySheet(BaseModel):
|
||
company_name: str
|
||
user_name: str
|
||
personnel_number: str | None
|
||
year: int
|
||
month: int
|
||
rows: list[DatevDayRow]
|
||
total_hours: float
|