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)
|