Files
aza/AzA march 2026/workforce_planner/absences/service.py
2026-03-25 22:03:39 +01:00

128 lines
4.5 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.
# -*- 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)