Skip to content

Commit

Permalink
Merge pull request #12 from philipflohr/feature/absences
Browse files Browse the repository at this point in the history
Feature/absences
  • Loading branch information
klamann authored Mar 9, 2021
2 parents f98d36e + cbf67ce commit 39de44c
Show file tree
Hide file tree
Showing 9 changed files with 719 additions and 142 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,12 @@ Available
* [`GET /company/employees`](https://developer.personio.de/reference#get_company-employees): list all employees
* [`GET /company/employees/{id}`](https://developer.personio.de/reference#get_company-employees-employee-id): get the employee with the specified ID
* [`GET /company/employees/{id}/profile-picture/{width}`](https://developer.personio.de/reference#get_company-employees-employee-id-profile-picture-width): get the profile picture of the specified employee
* [`GET /company/time-offs`](https://developer.personio.de/reference#get_company-time-offs): fetch absence data for the company employees
* [`GET /company/attendances`](https://developer.personio.de/reference#get_company-attendances): fetch attendance data for the company employees
* [`GET /company/time-off-types`](https://developer.personio.de/reference#get_company-time-off-types): get a list of available absences types
* [`GET /company/time-offs`](https://developer.personio.de/reference#get_company-time-offs): fetch absence data for the company employees
* [`POST /company/time-offs`](https://developer.personio.de/reference#post_company-time-offs): add absence data for the company employees
* [`GET /company/time-offs/{id}`](https://developer.personio.de/reference#get_company-time-offs-id): get the absence entry with the specified ID
* [`DELETE /company/time-offs/{id}`](https://developer.personio.de/reference#delete_company-time-offs-id): delete the absence entry with the specified ID

Work in Progress

Expand All @@ -103,10 +107,6 @@ Work in Progress
* [`POST /company/attendances`](https://developer.personio.de/reference#post_company-attendances): add attendance data for the company employees
* [`DELETE /company/attendances/{id}`](https://developer.personio.de/reference#delete_company-attendances-id): delete the attendance entry with the specified ID
* [`PATCH /company/attendances/{id}`](https://developer.personio.de/reference#patch_company-attendances-id): update the attendance entry with the specified ID
* [`GET /company/time-off-types`](https://developer.personio.de/reference#get_company-time-off-types): get a list of available absences types
* [`POST /company/time-offs`](https://developer.personio.de/reference#post_company-time-offs): add absence data for the company employees
* [`DELETE /company/time-offs/{id}`](https://developer.personio.de/reference#delete_company-time-offs-id): delete the absence entry with the specified ID
* [`GET /company/time-offs/{id}`](https://developer.personio.de/reference#get_company-time-offs-id): get the absence entry with the specified ID

## Contact

Expand Down
82 changes: 72 additions & 10 deletions src/personio_py/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,10 @@ def request_paginated(
resp_data = response['data']
if resp_data:
data_acc.extend(resp_data)
params['offset'] += len(resp_data)
if response['metadata']['current_page'] == response['metadata']['total_pages']:
break
else:
params['offset'] += len(resp_data)
else:
break
# return the accumulated data
Expand Down Expand Up @@ -341,23 +344,58 @@ def get_absences(self, employees: Union[int, List[int], Employee, List[Employee]
return self._get_employee_metadata(
'company/time-offs', Absence, employees, start_date, end_date)

def get_absence(self, absence_id: int) -> Absence:
def get_absence(self, absence: Union[Absence, int]) -> Absence:
"""
placeholder; not ready to be used
Get an absence record from a given id.
:param absence: The absence id to fetch.
"""
raise NotImplementedError()
if isinstance(absence, int):
response = self.request_json(f'company/time-offs/{absence}')
return Absence.from_dict(response['data'], self)
else:
if absence.id_:
return self.get_absence(absence.id_)
else:
self.__add_remote_absence_id(absence)
return self.get_absence(absence.id_)

def create_absence(self, absence: Absence):
def create_absence(self, absence: Absence) -> Absence:
"""
placeholder; not ready to be used
Creates an absence record on the Personio servers
:param absence: The absence object to be created
:raises PersonioError: If the absence could not be created on the Personio servers
"""
raise NotImplementedError()
data = absence.to_body_params()
response = self.request_json('company/time-offs', method='POST', data=data)
if response['success']:
absence.id_ = response['data']['attributes']['id']
return absence
raise PersonioError("Could not create absence")

def delete_absence(self, absence_id: int):
def delete_absence(self, absence: Union[Absence, int]):
"""
placeholder; not ready to be used
Delete an existing record
An absence id is required.
:param absence: The Absence object holding
the new data or an absence record id to delete.
:raises:
ValueError: If a query is required but not allowed
or the query does not provide exactly one result.
"""
raise NotImplementedError()
if isinstance(absence, int):
response = self.request_json(path=f'company/time-offs/{absence}', method='DELETE')
return response['success']
elif isinstance(absence, Absence):
if absence.id_ is not None:
return self.delete_absence(absence.id_)
else:
raise ValueError("Only an absence with an absence id can be deleted.")
else:
raise ValueError("absence must be an Absence object or an integer")

def _get_employee_metadata(
self, path: str, resource_cls: Type[PersonioResourceType],
Expand Down Expand Up @@ -408,3 +446,27 @@ def _normalize_timeframe_params(
employees = [employees]
employee_ids = [(e.id_ if isinstance(e, Employee) else e) for e in employees]
return employee_ids, start_date, end_date

def __add_remote_absence_id(self, absence: Absence) -> Absence:
"""
Queries the API for an absence record matching
the given Absence object and adds the remote id.
:param absence: The absence object to be updated
:return: The absence object with the absence_id set
"""
if absence.employee is None:
raise ValueError("For a remote query an employee_id is required")
if absence.start_date is None:
raise ValueError("For a remote query a start date is required")
if absence.end_date is None:
raise ValueError("For a remote query an end date is required")
matching_remote_absences = self.get_absences(employees=[absence.employee.id_],
start_date=absence.start_date,
end_date=absence.end_date)
if len(matching_remote_absences) == 0:
raise PersonioError("The absence to patch was not found")
elif len(matching_remote_absences) > 1:
raise PersonioError("More than one absence found.")
absence.id_ = matching_remote_absences[0].id_
return absence
31 changes: 22 additions & 9 deletions src/personio_py/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,8 +573,8 @@ def __init__(self,
start_date: datetime = None,
end_date: datetime = None,
days_count: float = None,
half_day_start: int = None,
half_day_end: int = None,
half_day_start: bool = False,
half_day_end: bool = False,
time_off_type: AbsenceType = None,
employee: ShortEmployee = None,
created_by: str = None,
Expand All @@ -588,19 +588,32 @@ def __init__(self,
self.start_date = start_date
self.end_date = end_date
self.days_count = days_count
self.half_day_start = half_day_start
self.half_day_end = half_day_end
self.half_day_start = bool(half_day_start)
self.half_day_end = bool(half_day_end)
self.time_off_type = time_off_type
self.employee = employee
self.created_by = created_by
self.certificate = certificate
self.created_at = created_at

def _create(self, client: 'Personio'):
pass

def _delete(self, client: 'Personio'):
pass
def _create(self, client: 'Personio' = None):
return get_client(self, client).create_absence(self)

def _delete(self, client: 'Personio' = None):
return get_client(self, client).delete_absence(self)

def to_body_params(self):
data = {
'employee_id': self.employee.id_,
'time_off_type_id': self.time_off_type.id_,
'start_date': self.start_date.strftime("%Y-%m-%d"),
'end_date': self.end_date.strftime("%Y-%m-%d"),
'half_day_start': self.half_day_start,
'half_day_end': self.half_day_end
}
if self.comment is not None:
data['comment'] = self.comment
return data


class Attendance(WritablePersonioResource):
Expand Down
5 changes: 5 additions & 0 deletions tests/apitest_shared.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import os
from functools import lru_cache
from datetime import date

import pytest

from personio_py import Personio, PersonioError

# Test time. if used on a personio instance, only touch entries during this time range
NOT_BEFORE = date(year=2022, month=1, day=1)
NOT_AFTER = date(year=2022, month=12, day=31)

# Personio client authentication
CLIENT_ID = os.getenv('CLIENT_ID')
CLIENT_SECRET = os.getenv('CLIENT_SECRET')
Expand Down
Loading

0 comments on commit 39de44c

Please sign in to comment.