Files
timemaster/backend/app/models/hours_payout.py
T
patrickandClaude Opus 4.8 65596e80a4
Security Audit / Node.js Dependency Audit (push) Has been cancelled
Security Audit / Python Dependency Audit (push) Has been cancelled
feat(overtime): Auszahlungs-Anträge durch Mitarbeiter (opt-in pro Firma)
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>
2026-07-08 22:43:26 +02:00

61 lines
2.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Stunden-Auszahlung: HR/Admin weist Überstunden-Stunden zur Lohn-Auszahlung an."""
import enum
import uuid
from datetime import datetime
from decimal import Decimal
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKey, Integer, Numeric, String, Text, func
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
if TYPE_CHECKING:
from app.models.user import User
from app.models.company import Company
class PayoutStatus(str, enum.Enum):
REQUESTED = "requested" # Mitarbeiter hat beantragt, wartet auf HR
APPROVED = "approved" # genehmigt / HR-Direktbuchung Saldo abgezogen
REJECTED = "rejected" # HR hat abgelehnt
CANCELLED = "cancelled" # Antragsteller hat zurückgezogen (nur requested)
class HoursPayout(Base):
"""Ein Auszahlungsvorgang für Überstunden-Stunden."""
__tablename__ = "hours_payouts"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
company_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("companies.id", ondelete="CASCADE"),
nullable=False, index=True
)
user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"),
nullable=False, index=True
)
hours: Mapped[Decimal] = mapped_column(Numeric(6, 2), nullable=False) # ausgezahlte Stunden
period_year: Mapped[int | None] = mapped_column(Integer) # Abrechnungsmonat Jahr
period_month: Mapped[int | None] = mapped_column(Integer) # Abrechnungsmonat Monat
note: Mapped[str | None] = mapped_column(Text) # Notiz für Buchhaltung
status: Mapped[str] = mapped_column(String(12), nullable=False, default=PayoutStatus.APPROVED.value)
rejection_reason: Mapped[str | None] = mapped_column(Text)
created_by: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"),
nullable=False
)
decided_by: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL")
)
decided_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), index=True
)
user: Mapped["User"] = relationship("User", foreign_keys=[user_id], lazy="noload")
creator: Mapped["User"] = relationship("User", foreign_keys=[created_by], lazy="noload")
decider: Mapped["User"] = relationship("User", foreign_keys=[decided_by], lazy="noload")
company: Mapped["Company"] = relationship("Company", lazy="noload")