feat: Überstunden-Kappung + Jahresverfall pro Firma konfigurierbar

Backend:
- Company: overtime_cap_hours, overtime_expiry_enabled/month/day,
  overtime_max_carryover_hours
- OvertimeBalance: last_expiry_applied_at
- Migration 0031: neue Spalten in companies + overtime_balances
- _recalculate_overtime_balance: Kappung direkt nach Berechnung
- apply_overtime_expiry_if_needed(): lazy Verfall beim Balance-Abruf
- GET /absences/overtime-balance: prüft + wendet Verfall automatisch an
- POST /absences/overtime-balance/apply-expiry: manueller Trigger (Admin)

Frontend:
- CompanySettingsPage: neuer Block 'Überstunden-Konto'
  - Toggle Kappungsgrenze + Stunden-Input
  - Toggle Jahresverfall + Stichtag (Tag/Monat) + max. Übertrag
  - 'Verfall anwenden'-Button für Admins

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 22:48:30 +02:00
parent 23b45881a1
commit 23ba7f1762
8 changed files with 364 additions and 1 deletions
+67
View File
@@ -1491,3 +1491,70 @@ Keine Commits in dieser Session.
- frontend/src/types/hoursPayout.ts | 26 ++ - frontend/src/types/hoursPayout.ts | 26 ++
--- ---
## 2026-05-25 22:21 22:22 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** timemaster
### Commits
- 549783a feat: Stunden-Auszahlungen in /mobile Profil-Screen
### Geänderte Dateien
- DEVLOG.md | 21 ++++++
- frontend/src/pages/mobile/MobileProfileScreen.tsx | 87 ++++++++++++++++++++++-
---
## 2026-05-25 22:25 22:29 (3m)
**Beschreibung:** Claude Code Session
**Projekt:** timemaster
### Commits
- 23b4588 fix: Überstunden tages-weise berechnen statt Gesamtzeitraum
### Geänderte Dateien
- backend/app/services/report_service.py | 19 ++++++++++++++++---
---
## 2026-05-25 22:32 22:33 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** timemaster
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- backend/app/services/report_service.py | 19 ++++++++++++++++---
---
## 2026-05-25 22:37 22:39 (2m)
**Beschreibung:** Claude Code Session
**Projekt:** timemaster
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- backend/app/services/report_service.py | 19 ++++++++++++++++---
---
## 2026-05-25 22:39 22:40 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** timemaster
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- backend/app/services/report_service.py | 19 ++++++++++++++++---
---
## 2026-05-25 22:44 22:47 (2m)
**Beschreibung:** Claude Code Session
**Projekt:** timemaster
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- backend/app/services/report_service.py | 19 ++++++++++++++++---
---
+7
View File
@@ -55,6 +55,13 @@ class Company(Base):
# Freizeitausgleich-Konfiguration # Freizeitausgleich-Konfiguration
overtime_overdraft_allowed: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) overtime_overdraft_allowed: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
overtime_warning_threshold_hours: Mapped[int] = mapped_column(Integer, nullable=False, default=0) overtime_warning_threshold_hours: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
# Überstunden-Kappung
overtime_cap_hours: Mapped[int | None] = mapped_column(Integer, nullable=True)
# Überstunden-Verfall
overtime_expiry_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
overtime_expiry_month: Mapped[int] = mapped_column(Integer, nullable=False, default=3) # März
overtime_expiry_day: Mapped[int] = mapped_column(Integer, nullable=False, default=31) # 31.
overtime_max_carryover_hours: Mapped[int | None] = mapped_column(Integer, nullable=True) # None = alles
# Relationships # Relationships
users: Mapped[list["User"]] = relationship("User", back_populates="company", lazy="noload") users: Mapped[list["User"]] = relationship("User", back_populates="company", lazy="noload")
+1
View File
@@ -34,6 +34,7 @@ class OvertimeBalance(Base):
total_hours: Mapped[Decimal] = mapped_column(Numeric(8, 2), default=Decimal("0")) total_hours: Mapped[Decimal] = mapped_column(Numeric(8, 2), default=Decimal("0"))
taken_hours: Mapped[Decimal] = mapped_column(Numeric(8, 2), default=Decimal("0")) taken_hours: Mapped[Decimal] = mapped_column(Numeric(8, 2), default=Decimal("0"))
last_calculated: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) last_calculated: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
last_expiry_applied_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column( updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now() DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
+32
View File
@@ -210,6 +210,15 @@ async def get_overtime_balance(
) )
if bal is None: if bal is None:
return OvertimeBalanceOut(total_hours=0, taken_hours=0, available_hours=0) return OvertimeBalanceOut(total_hours=0, taken_hours=0, available_hours=0)
# Verfall anwenden wenn nötig
from app.services.report_service import apply_overtime_expiry_if_needed
from app.models.company import Company as CompanyModel
company = await db.get(CompanyModel, current_user.company_id)
changed = await apply_overtime_expiry_if_needed(bal, company, db)
if changed:
await db.commit()
return OvertimeBalanceOut( return OvertimeBalanceOut(
total_hours=float(bal.total_hours), total_hours=float(bal.total_hours),
taken_hours=float(bal.taken_hours), taken_hours=float(bal.taken_hours),
@@ -217,6 +226,29 @@ async def get_overtime_balance(
) )
@router.post("/absences/overtime-balance/apply-expiry")
async def apply_overtime_expiry_all(
current_user: User = require_role(UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN),
db: AsyncSession = Depends(get_db),
):
"""Überstunden-Verfall manuell für alle Mitarbeiter der Firma anwenden."""
from sqlalchemy import select as sa_select
from app.models.company import Company as CompanyModel
from app.services.report_service import apply_overtime_expiry_if_needed
company = await db.get(CompanyModel, current_user.company_id)
balances = list(await db.scalars(
sa_select(OvertimeBalance).where(OvertimeBalance.company_id == current_user.company_id)
))
applied_count = 0
for bal in balances:
changed = await apply_overtime_expiry_if_needed(bal, company, db)
if changed:
applied_count += 1
await db.commit()
return {"applied_to": applied_count, "total": len(balances)}
@router.get("/absences/", response_model=AbsenceListResponse) @router.get("/absences/", response_model=AbsenceListResponse)
async def list_absences( async def list_absences(
current_user: CurrentUser, current_user: CurrentUser,
+10
View File
@@ -24,6 +24,11 @@ class CompanyOut(BaseModel):
mobile_stamping_enabled: bool = True mobile_stamping_enabled: bool = True
overtime_overdraft_allowed: bool = True overtime_overdraft_allowed: bool = True
overtime_warning_threshold_hours: int = 0 overtime_warning_threshold_hours: int = 0
overtime_cap_hours: int | None = None
overtime_expiry_enabled: bool = False
overtime_expiry_month: int = 3
overtime_expiry_day: int = 31
overtime_max_carryover_hours: int | None = None
kiosk_require_approval: bool = True kiosk_require_approval: bool = True
kiosk_track_current_user: bool = True kiosk_track_current_user: bool = True
kiosk_heartbeat_interval_sec: int = 30 kiosk_heartbeat_interval_sec: int = 30
@@ -39,6 +44,11 @@ class CompanyUpdate(BaseModel):
mobile_stamping_enabled: bool | None = None mobile_stamping_enabled: bool | None = None
overtime_overdraft_allowed: bool | None = None overtime_overdraft_allowed: bool | None = None
overtime_warning_threshold_hours: int | None = Field(None, ge=0) overtime_warning_threshold_hours: int | None = Field(None, ge=0)
overtime_cap_hours: int | None = Field(None, ge=1, le=9999)
overtime_expiry_enabled: bool | None = None
overtime_expiry_month: int | None = Field(None, ge=1, le=12)
overtime_expiry_day: int | None = Field(None, ge=1, le=31)
overtime_max_carryover_hours: int | None = Field(None, ge=0, le=9999)
kiosk_require_approval: bool | None = None kiosk_require_approval: bool | None = None
kiosk_track_current_user: bool | None = None kiosk_track_current_user: bool | None = None
kiosk_heartbeat_interval_sec: int | None = Field(None, ge=10, le=120) kiosk_heartbeat_interval_sec: int | None = Field(None, ge=10, le=120)
+54
View File
@@ -242,9 +242,63 @@ async def _recalculate_overtime_balance(
bal.total_hours = Decimal(str(round(overtime, 2))) bal.total_hours = Decimal(str(round(overtime, 2)))
bal.last_calculated = datetime.utcnow() bal.last_calculated = datetime.utcnow()
# Kappung anwenden
company = await db.get(Company, user.company_id)
if company and company.overtime_cap_hours is not None:
cap = Decimal(str(company.overtime_cap_hours))
if bal.total_hours > cap:
bal.total_hours = cap
return bal return bal
async def apply_overtime_expiry_if_needed(
bal: OvertimeBalance,
company, # Company model
db: AsyncSession,
) -> bool:
"""
Prüft ob der Überstunden-Verfall angewendet werden muss und tut es ggf.
Gibt True zurück wenn Verfall angewendet wurde.
"""
if not company or not company.overtime_expiry_enabled:
return False
today = date.today()
try:
expiry_this_year = date(today.year, company.overtime_expiry_month, company.overtime_expiry_day)
except ValueError:
# Ungültiges Datum (z.B. 31. Februar) überspringen
return False
expiry_last_year_year = today.year - 1
try:
expiry_last_year = date(expiry_last_year_year, company.overtime_expiry_month, company.overtime_expiry_day)
except ValueError:
expiry_last_year = None
last_applicable_expiry = expiry_this_year if today >= expiry_this_year else expiry_last_year
if last_applicable_expiry is None:
return False
# Schon angewendet?
if bal.last_expiry_applied_at:
applied_date = bal.last_expiry_applied_at.date() if hasattr(bal.last_expiry_applied_at, 'date') else bal.last_expiry_applied_at
if applied_date >= last_applicable_expiry:
return False
# Verfall anwenden: available_hours auf max_carryover kappen
available = bal.total_hours - bal.taken_hours
if company.overtime_max_carryover_hours is not None:
max_carry = Decimal(str(company.overtime_max_carryover_hours))
if available > max_carry:
bal.total_hours = bal.taken_hours + max_carry
bal.last_expiry_applied_at = datetime.utcnow()
return True
def _check_arbzg_day(entry: TimeEntry) -> list[str]: def _check_arbzg_day(entry: TimeEntry) -> list[str]:
"""ArbZG-Prüfung für einen einzelnen Zeiteintrag.""" """ArbZG-Prüfung für einen einzelnen Zeiteintrag."""
if entry.end_time is None: if entry.end_time is None:
@@ -0,0 +1,33 @@
"""overtime cap and expiry config
Revision ID: 0031
Revises: 0030
Create Date: 2026-05-25
"""
from alembic import op
import sqlalchemy as sa
revision = '0031'
down_revision = '0030'
branch_labels = None
depends_on = None
def upgrade():
# Companies: Kappung + Verfall
op.add_column('companies', sa.Column('overtime_cap_hours', sa.Integer(), nullable=True))
op.add_column('companies', sa.Column('overtime_expiry_enabled', sa.Boolean(), nullable=False, server_default='false'))
op.add_column('companies', sa.Column('overtime_expiry_month', sa.Integer(), nullable=False, server_default='3'))
op.add_column('companies', sa.Column('overtime_expiry_day', sa.Integer(), nullable=False, server_default='31'))
op.add_column('companies', sa.Column('overtime_max_carryover_hours', sa.Integer(), nullable=True))
# OvertimeBalance: Verfall-Zeitstempel
op.add_column('overtime_balances', sa.Column('last_expiry_applied_at', sa.DateTime(timezone=True), nullable=True))
def downgrade():
op.drop_column('companies', 'overtime_cap_hours')
op.drop_column('companies', 'overtime_expiry_enabled')
op.drop_column('companies', 'overtime_expiry_month')
op.drop_column('companies', 'overtime_expiry_day')
op.drop_column('companies', 'overtime_max_carryover_hours')
op.drop_column('overtime_balances', 'last_expiry_applied_at')
+160 -1
View File
@@ -62,6 +62,14 @@ export function CompanySettingsPage() {
// Freizeitausgleich // Freizeitausgleich
const [fzaOverdraftAllowed, setFzaOverdraftAllowed] = useState(true) const [fzaOverdraftAllowed, setFzaOverdraftAllowed] = useState(true)
const [fzaWarningThreshold, setFzaWarningThreshold] = useState(0) const [fzaWarningThreshold, setFzaWarningThreshold] = useState(0)
// Überstunden-Kappung
const [overtimeCapEnabled, setOvertimeCapEnabled] = useState(false)
const [overtimeCapHours, setOvertimeCapHours] = useState(150)
// Überstunden-Verfall
const [overtimeExpiryEnabled, setOvertimeExpiryEnabled] = useState(false)
const [overtimeExpiryMonth, setOvertimeExpiryMonth] = useState(3)
const [overtimeExpiryDay, setOvertimeExpiryDay] = useState(31)
const [overtimeMaxCarryoverHours, setOvertimeMaxCarryoverHours] = useState<number | null>(null)
// Busylight // Busylight
const [blStatus, setBlStatus] = useState<{ configured: boolean; created_at: string | null } | null>(null) const [blStatus, setBlStatus] = useState<{ configured: boolean; created_at: string | null } | null>(null)
const [blPlaintext, setBlPlaintext] = useState<string | null>(null) const [blPlaintext, setBlPlaintext] = useState<string | null>(null)
@@ -85,13 +93,19 @@ export function CompanySettingsPage() {
setPnRequired(c.personnel_number_required ?? false) setPnRequired(c.personnel_number_required ?? false)
setPnMode(c.personnel_number_mode ?? 'manual') setPnMode(c.personnel_number_mode ?? 'manual')
setPnNext(c.personnel_number_next ?? 1) setPnNext(c.personnel_number_next ?? 1)
const cc = c as CompanyOut & { mobile_stamping_enabled?: boolean; overtime_overdraft_allowed?: boolean; overtime_warning_threshold_hours?: number; kiosk_require_approval?: boolean; kiosk_track_current_user?: boolean; kiosk_heartbeat_interval_sec?: number } const cc = c as CompanyOut & { mobile_stamping_enabled?: boolean; overtime_overdraft_allowed?: boolean; overtime_warning_threshold_hours?: number; kiosk_require_approval?: boolean; kiosk_track_current_user?: boolean; kiosk_heartbeat_interval_sec?: number; overtime_cap_hours?: number | null; overtime_expiry_enabled?: boolean; overtime_expiry_month?: number; overtime_expiry_day?: number; overtime_max_carryover_hours?: number | null }
setMobileStamping(cc.mobile_stamping_enabled ?? true) setMobileStamping(cc.mobile_stamping_enabled ?? true)
setFzaOverdraftAllowed(cc.overtime_overdraft_allowed ?? true) setFzaOverdraftAllowed(cc.overtime_overdraft_allowed ?? true)
setFzaWarningThreshold(cc.overtime_warning_threshold_hours ?? 0) setFzaWarningThreshold(cc.overtime_warning_threshold_hours ?? 0)
setKioskRequireApproval(cc.kiosk_require_approval ?? true) setKioskRequireApproval(cc.kiosk_require_approval ?? true)
setKioskTrackCurrentUser(cc.kiosk_track_current_user ?? true) setKioskTrackCurrentUser(cc.kiosk_track_current_user ?? true)
setKioskHeartbeatIntervalSec(cc.kiosk_heartbeat_interval_sec ?? 30) setKioskHeartbeatIntervalSec(cc.kiosk_heartbeat_interval_sec ?? 30)
setOvertimeCapEnabled(cc.overtime_cap_hours != null)
setOvertimeCapHours(cc.overtime_cap_hours ?? 150)
setOvertimeExpiryEnabled(cc.overtime_expiry_enabled ?? false)
setOvertimeExpiryMonth(cc.overtime_expiry_month ?? 3)
setOvertimeExpiryDay(cc.overtime_expiry_day ?? 31)
setOvertimeMaxCarryoverHours(cc.overtime_max_carryover_hours ?? null)
}).catch(() => {}) }).catch(() => {})
api.get<{ configured: boolean; created_at: string | null }>('/companies/me/busylight-token') api.get<{ configured: boolean; created_at: string | null }>('/companies/me/busylight-token')
.then(setBlStatus) .then(setBlStatus)
@@ -165,6 +179,11 @@ export function CompanySettingsPage() {
kiosk_require_approval: kioskRequireApproval, kiosk_require_approval: kioskRequireApproval,
kiosk_track_current_user: kioskTrackCurrentUser, kiosk_track_current_user: kioskTrackCurrentUser,
kiosk_heartbeat_interval_sec: kioskHeartbeatIntervalSec, kiosk_heartbeat_interval_sec: kioskHeartbeatIntervalSec,
overtime_cap_hours: overtimeCapEnabled ? overtimeCapHours : null,
overtime_expiry_enabled: overtimeExpiryEnabled,
overtime_expiry_month: overtimeExpiryMonth,
overtime_expiry_day: overtimeExpiryDay,
overtime_max_carryover_hours: overtimeExpiryEnabled ? overtimeMaxCarryoverHours : null,
}) })
setCompany(updated) setCompany(updated)
setSaved(true) setSaved(true)
@@ -176,6 +195,18 @@ export function CompanySettingsPage() {
} }
} }
async function applyOvertimeExpiry() {
if (!confirm('Verfall jetzt auf alle Mitarbeiter anwenden? Nicht rückgängig zu machen.')) return
try {
await api.post('/absences/overtime-balance/apply-expiry', {})
setError(null)
setSaved(true)
setTimeout(() => setSaved(false), 3000)
} catch (e: unknown) {
setError(e instanceof Error ? e.message : 'Fehler beim Anwenden des Verfalls')
}
}
const isAdmin = me?.role === 'COMPANY_ADMIN' || me?.role === 'SUPER_ADMIN' const isAdmin = me?.role === 'COMPANY_ADMIN' || me?.role === 'SUPER_ADMIN'
const hourOptions = Array.from({ length: 24 }, (_, i) => i.toString()) const hourOptions = Array.from({ length: 24 }, (_, i) => i.toString())
@@ -699,6 +730,134 @@ export function CompanySettingsPage() {
</div> </div>
</div> </div>
{/* Überstunden-Konto-Regeln */}
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 space-y-5">
<div className="flex items-center gap-2">
<span className="text-lg">📊</span>
<h2 className="font-semibold text-gray-700">Überstunden-Konto</h2>
</div>
{/* Kappungsgrenze */}
<div className="space-y-3">
<div className="flex items-center justify-between gap-4">
<div>
<p className="text-sm font-medium text-gray-800">Kappungsgrenze aktivieren</p>
<p className="text-xs text-gray-500 mt-0.5">
Das Überstunden-Konto kann nicht über diesen Wert anwachsen.
</p>
</div>
<button
type="button"
disabled={!isAdmin}
onClick={() => setOvertimeCapEnabled(v => !v)}
className={`relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed ${
overtimeCapEnabled ? 'bg-blue-600' : 'bg-gray-300'
}`}
>
<span
className={`pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform transition-transform duration-200 ${
overtimeCapEnabled ? 'translate-x-5' : 'translate-x-0'
}`}
/>
</button>
</div>
{overtimeCapEnabled && (
<div className="flex items-center justify-between gap-4 pl-4 border-l-2 border-blue-100">
<p className="text-sm text-gray-700">Maximale Überstunden (Stunden)</p>
<input
type="number" min={1} max={9999} step={1}
value={overtimeCapHours}
onChange={e => setOvertimeCapHours(Math.max(1, parseInt(e.target.value) || 1))}
disabled={!isAdmin}
className="w-24 border border-gray-300 rounded-lg px-3 py-1.5 text-sm text-right disabled:bg-gray-50"
/>
</div>
)}
</div>
{/* Verfall */}
<div className="space-y-3">
<div className="flex items-center justify-between gap-4">
<div>
<p className="text-sm font-medium text-gray-800">Jahresverfall aktivieren</p>
<p className="text-xs text-gray-500 mt-0.5">
Nicht genommene Überstunden verfallen einmal jährlich zum konfigurierten Stichtag.
</p>
</div>
<button
type="button"
disabled={!isAdmin}
onClick={() => setOvertimeExpiryEnabled(v => !v)}
className={`relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed ${
overtimeExpiryEnabled ? 'bg-blue-600' : 'bg-gray-300'
}`}
>
<span
className={`pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform transition-transform duration-200 ${
overtimeExpiryEnabled ? 'translate-x-5' : 'translate-x-0'
}`}
/>
</button>
</div>
{overtimeExpiryEnabled && (
<div className="pl-4 border-l-2 border-blue-100 space-y-3">
{/* Stichtag */}
<div className="flex items-center justify-between gap-4">
<p className="text-sm text-gray-700">Verfallsstichtag</p>
<div className="flex items-center gap-2">
<input
type="number" min={1} max={31} step={1}
value={overtimeExpiryDay}
onChange={e => setOvertimeExpiryDay(Math.min(31, Math.max(1, parseInt(e.target.value) || 1)))}
disabled={!isAdmin}
className="w-16 border border-gray-300 rounded-lg px-2 py-1.5 text-sm text-right disabled:bg-gray-50"
/>
<span className="text-sm text-gray-500">.</span>
<select
value={overtimeExpiryMonth}
onChange={e => setOvertimeExpiryMonth(parseInt(e.target.value))}
disabled={!isAdmin}
className="border border-gray-300 rounded-lg px-2 py-1.5 text-sm disabled:bg-gray-50"
>
{['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'].map((m,i) => (
<option key={i+1} value={i+1}>{m}</option>
))}
</select>
</div>
</div>
{/* Max. Übertrag */}
<div className="flex items-center justify-between gap-4">
<div>
<p className="text-sm text-gray-700">Maximaler Übertrag (Stunden)</p>
<p className="text-xs text-gray-400">0 = alles verfällt · leer = alles übertragen</p>
</div>
<input
type="number" min={0} max={9999} step={1}
value={overtimeMaxCarryoverHours ?? ''}
placeholder="unbegrenzt"
onChange={e => setOvertimeMaxCarryoverHours(e.target.value === '' ? null : Math.max(0, parseInt(e.target.value) || 0))}
disabled={!isAdmin}
className="w-28 border border-gray-300 rounded-lg px-3 py-1.5 text-sm text-right disabled:bg-gray-50 placeholder-gray-400"
/>
</div>
{/* Manueller Trigger */}
{isAdmin && (
<div className="flex items-center justify-between gap-4 pt-1">
<p className="text-xs text-gray-500">Verfall jetzt auf alle Mitarbeiter anwenden</p>
<button
type="button"
onClick={applyOvertimeExpiry}
className="px-3 py-1.5 text-xs font-medium bg-orange-50 border border-orange-200 text-orange-700 rounded-lg hover:bg-orange-100 transition-colors"
>
Verfall anwenden
</button>
</div>
)}
</div>
)}
</div>
</div>
{/* Firmen-Info (readonly) */} {/* Firmen-Info (readonly) */}
{company && ( {company && (
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6"> <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">