128 lines
4.5 KiB
Python
128 lines
4.5 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""Service Layer – Orchestriert Business-Logik für Abwesenheiten."""
|
||
|
||
import datetime
|
||
from typing import Optional
|
||
|
||
from sqlalchemy.orm import Session
|
||
|
||
from ..core.models import Absence
|
||
from ..core.enums import AbsenceStatus, ABSENCE_META
|
||
from ..core.schemas import (
|
||
AbsenceCreate, AbsenceUpdate, AbsenceRead,
|
||
BalanceRead, BalanceAdjust, ApprovalDecision,
|
||
)
|
||
from .repository import AbsenceRepository, BalanceRepository
|
||
from .rules import validate_absence, _business_days, RuleViolation
|
||
from ..employees.repository import EmployeeRepository
|
||
|
||
|
||
class AbsenceService:
|
||
"""Zentrale Geschäftslogik – wird von API UND Desktop-Client genutzt."""
|
||
|
||
def __init__(self, db: Session):
|
||
self.db = db
|
||
self.abs_repo = AbsenceRepository(db)
|
||
self.bal_repo = BalanceRepository(db)
|
||
self.emp_repo = EmployeeRepository(db)
|
||
|
||
def create_absence(self, data: AbsenceCreate) -> Absence:
|
||
emp = self.emp_repo.get_by_id(data.employee_id)
|
||
if not emp:
|
||
raise ValueError(f"Mitarbeiter {data.employee_id} nicht gefunden")
|
||
|
||
validate_absence(self.db, data)
|
||
|
||
bd = _business_days(data.start_date, data.end_date)
|
||
|
||
meta = ABSENCE_META.get(data.category, {})
|
||
absence = self.abs_repo.create(data, business_days=bd)
|
||
|
||
if not meta.get("requires_approval"):
|
||
absence.status = AbsenceStatus.APPROVED
|
||
|
||
self.db.commit()
|
||
return absence
|
||
|
||
def update_absence(self, absence_id: str, data: AbsenceUpdate) -> Optional[Absence]:
|
||
absence = self.abs_repo.get_by_id(absence_id)
|
||
if not absence:
|
||
return None
|
||
|
||
if data.start_date or data.end_date:
|
||
start = data.start_date or absence.start_date
|
||
end = data.end_date or absence.end_date
|
||
check_data = AbsenceCreate(
|
||
employee_id=absence.employee_id,
|
||
category=data.category or absence.category,
|
||
start_date=start,
|
||
end_date=end,
|
||
reason=data.reason or absence.reason,
|
||
)
|
||
validate_absence(self.db, check_data, skip_rules=[])
|
||
absence.business_days = _business_days(start, end)
|
||
|
||
result = self.abs_repo.update(absence_id, data)
|
||
self.db.commit()
|
||
return result
|
||
|
||
def delete_absence(self, absence_id: str) -> bool:
|
||
ok = self.abs_repo.delete(absence_id)
|
||
if ok:
|
||
self.db.commit()
|
||
return ok
|
||
|
||
def approve(self, decision: ApprovalDecision) -> Absence:
|
||
absence = self.abs_repo.get_by_id(decision.absence_id)
|
||
if not absence:
|
||
raise ValueError("Abwesenheit nicht gefunden")
|
||
if absence.status != AbsenceStatus.PENDING:
|
||
raise ValueError(f"Status ist bereits {absence.status.value}")
|
||
|
||
absence.approver_id = decision.approver_id
|
||
absence.approved_at = datetime.datetime.utcnow()
|
||
absence.status = AbsenceStatus.APPROVED if decision.approved else AbsenceStatus.REJECTED
|
||
self.db.commit()
|
||
return absence
|
||
|
||
def get_absences_for_employee(self, employee_id: str, year: Optional[int] = None) -> list[Absence]:
|
||
return self.abs_repo.list_for_employee(employee_id, year)
|
||
|
||
def get_absences_for_period(
|
||
self, start: datetime.date, end: datetime.date
|
||
) -> list[Absence]:
|
||
return self.abs_repo.list_for_period(start, end)
|
||
|
||
def get_all_absences(self, year: Optional[int] = None) -> list[Absence]:
|
||
return self.abs_repo.list_all(year)
|
||
|
||
def get_balance(self, employee_id: str, year: int) -> BalanceRead:
|
||
emp = self.emp_repo.get_by_id(employee_id)
|
||
if not emp:
|
||
raise ValueError(f"Mitarbeiter {employee_id} nicht gefunden")
|
||
|
||
ba = self.bal_repo.get_or_create(employee_id, year)
|
||
used = self.abs_repo.used_days(employee_id, year)
|
||
|
||
return BalanceRead(
|
||
employee_id=employee_id,
|
||
employee_name=emp.name,
|
||
year=year,
|
||
yearly_quota=ba.yearly_quota,
|
||
carry_over=ba.carry_over,
|
||
manual_adjustment=ba.manual_adjustment,
|
||
total_available=ba.total_available,
|
||
used_days=used,
|
||
remaining=ba.total_available - used,
|
||
)
|
||
|
||
def adjust_balance(self, employee_id: str, year: int, data: BalanceAdjust) -> BalanceRead:
|
||
self.bal_repo.update(
|
||
employee_id, year,
|
||
carry_over=data.carry_over,
|
||
manual_adjustment=data.manual_adjustment,
|
||
yearly_quota=data.yearly_quota,
|
||
)
|
||
self.db.commit()
|
||
return self.get_balance(employee_id, year)
|