brynq-sdk-acerta 1.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- brynq_sdk_acerta/__init__.py +14 -0
- brynq_sdk_acerta/acerta.py +118 -0
- brynq_sdk_acerta/addresses.py +99 -0
- brynq_sdk_acerta/agreements.py +426 -0
- brynq_sdk_acerta/bank_accounts.py +90 -0
- brynq_sdk_acerta/code_lists.py +264 -0
- brynq_sdk_acerta/company_cars.py +135 -0
- brynq_sdk_acerta/contact_information.py +79 -0
- brynq_sdk_acerta/cost_centers.py +94 -0
- brynq_sdk_acerta/employees.py +121 -0
- brynq_sdk_acerta/employees_additional_information.py +87 -0
- brynq_sdk_acerta/employer.py +179 -0
- brynq_sdk_acerta/family_members.py +99 -0
- brynq_sdk_acerta/family_situation.py +99 -0
- brynq_sdk_acerta/inservice.py +99 -0
- brynq_sdk_acerta/salaries.py +74 -0
- brynq_sdk_acerta/schemas/__init__.py +135 -0
- brynq_sdk_acerta/schemas/address.py +80 -0
- brynq_sdk_acerta/schemas/agreement.py +982 -0
- brynq_sdk_acerta/schemas/bank_account.py +87 -0
- brynq_sdk_acerta/schemas/company_car.py +124 -0
- brynq_sdk_acerta/schemas/contact_information.py +83 -0
- brynq_sdk_acerta/schemas/cost_center.py +82 -0
- brynq_sdk_acerta/schemas/employee.py +406 -0
- brynq_sdk_acerta/schemas/employer.py +71 -0
- brynq_sdk_acerta/schemas/family.py +220 -0
- brynq_sdk_acerta/schemas/in_service.py +243 -0
- brynq_sdk_acerta/schemas/in_service_config.py +28 -0
- brynq_sdk_acerta/schemas/planning.py +37 -0
- brynq_sdk_acerta/schemas/salaries.py +84 -0
- brynq_sdk_acerta-1.1.1.dist-info/METADATA +21 -0
- brynq_sdk_acerta-1.1.1.dist-info/RECORD +34 -0
- brynq_sdk_acerta-1.1.1.dist-info/WHEEL +5 -0
- brynq_sdk_acerta-1.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Brynq SDK for Acerta API integration
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .acerta import Acerta
|
|
6
|
+
from .agreements import Agreements
|
|
7
|
+
from .employees import Employees
|
|
8
|
+
from .code_lists import CodeLists
|
|
9
|
+
from .employer import Employer
|
|
10
|
+
from .employees_additional_information import EmployeesAdditionalInformation
|
|
11
|
+
from .inservice import InService
|
|
12
|
+
from .company_cars import CompanyCars
|
|
13
|
+
|
|
14
|
+
__all__ = ['Acerta', 'CodeLists', 'Agreements', 'Employees', 'Employer', 'InService', 'EmployeesAdditionalInformation', 'CompanyCars']
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from typing_extensions import List
|
|
2
|
+
from typing import Literal, Optional, Union
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from .salaries import Salaries
|
|
6
|
+
from .cost_centers import CostCenters
|
|
7
|
+
from brynq_sdk_brynq import BrynQ
|
|
8
|
+
from .code_lists import CodeLists
|
|
9
|
+
from .agreements import Agreements
|
|
10
|
+
from .inservice import InService
|
|
11
|
+
from .company_cars import CompanyCars
|
|
12
|
+
from .employees import Employees
|
|
13
|
+
from .employer import Employer
|
|
14
|
+
from requests_oauthlib import OAuth2Session
|
|
15
|
+
from oauthlib.oauth2 import BackendApplicationClient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Acerta(BrynQ):
|
|
19
|
+
"""
|
|
20
|
+
Base class for interacting with the Acerta API.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Default timeout in seconds for all requests
|
|
24
|
+
TIMEOUT = 30
|
|
25
|
+
|
|
26
|
+
def __init__(self, employers: Union[str, List], system_type: Optional[Literal['source', 'target']] = None, test_environment: bool = True, debug: bool = False):
|
|
27
|
+
"""
|
|
28
|
+
Initialize the Acerta API client.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
system_type (str): System type ('source' or 'target')
|
|
32
|
+
debug (bool): Debug flag - if True uses test environment, if False uses production
|
|
33
|
+
"""
|
|
34
|
+
super().__init__()
|
|
35
|
+
|
|
36
|
+
# Compute environment-specific prefix once and reuse
|
|
37
|
+
env_prefix = "a-" if test_environment else ""
|
|
38
|
+
self.base_url = f"https://{env_prefix}api.acerta.be"
|
|
39
|
+
|
|
40
|
+
# Extract credentials and configure OAuth2 session with automatic token renewal
|
|
41
|
+
credentials = self.interfaces.credentials.get(
|
|
42
|
+
system="acerta-acceptance",
|
|
43
|
+
system_type=system_type,
|
|
44
|
+
)
|
|
45
|
+
data = credentials.get("data", {})
|
|
46
|
+
client_id = data.get("client_id")
|
|
47
|
+
client_secret = data.get("client_secret")
|
|
48
|
+
|
|
49
|
+
# Token endpoint (match test/prod like base_url)
|
|
50
|
+
token_url = f"https://{env_prefix}signin.acerta.be/am/oauth2/access_token"
|
|
51
|
+
|
|
52
|
+
# Store client credentials for reuse
|
|
53
|
+
self._client_id = client_id
|
|
54
|
+
self._client_secret = client_secret
|
|
55
|
+
self._token_url = token_url
|
|
56
|
+
|
|
57
|
+
# Create OAuth2 session using client credentials (Backend Application flow)
|
|
58
|
+
client = BackendApplicationClient(client_id=self._client_id)
|
|
59
|
+
oauth_session = OAuth2Session(client=client)
|
|
60
|
+
|
|
61
|
+
# Fetch initial token
|
|
62
|
+
token = oauth_session.fetch_token(
|
|
63
|
+
token_url=self._token_url,
|
|
64
|
+
client_id=self._client_id,
|
|
65
|
+
client_secret=self._client_secret,
|
|
66
|
+
include_client_id=True,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Keep access_token attribute for backward compatibility
|
|
70
|
+
self.access_token = token.get("access_token")
|
|
71
|
+
|
|
72
|
+
# Attach default headers; Authorization is managed by OAuth2Session
|
|
73
|
+
oauth_session.headers.update({
|
|
74
|
+
"Accept": "application/json",
|
|
75
|
+
"Content-Type": "application/json",
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
# Ensure token is valid before each request (client_credentials has no refresh_token)
|
|
79
|
+
self._orig_request = oauth_session.request
|
|
80
|
+
oauth_session.request = self._request_with_pre_expiry # type: ignore[assignment]
|
|
81
|
+
|
|
82
|
+
# Use the OAuth2 session for all requests
|
|
83
|
+
self.session = oauth_session
|
|
84
|
+
self.session.timeout = self.TIMEOUT # type: ignore[attr-defined]
|
|
85
|
+
self._employer_ids = employers if isinstance(employers, List) else [employers]
|
|
86
|
+
self._employee_ids = set()
|
|
87
|
+
self._agreement_ids = set()
|
|
88
|
+
|
|
89
|
+
# Set debug mode
|
|
90
|
+
self.debug = debug
|
|
91
|
+
|
|
92
|
+
# Initialize resource classes
|
|
93
|
+
self.agreements = Agreements(self)
|
|
94
|
+
self.inservice = InService(self)
|
|
95
|
+
self.cost_centers = CostCenters(self)
|
|
96
|
+
self.code_lists = CodeLists(self)
|
|
97
|
+
self.employees = Employees(self)
|
|
98
|
+
self.employers = Employer(self)
|
|
99
|
+
self.salaries = Salaries(self)
|
|
100
|
+
self.company_cars = CompanyCars(self)
|
|
101
|
+
|
|
102
|
+
def _ensure_valid_token(self):
|
|
103
|
+
"""Ensure the OAuth token exists and is not about to expire."""
|
|
104
|
+
tok = getattr(self.session, "token", {}) or {}
|
|
105
|
+
expires_at = tok.get("expires_at")
|
|
106
|
+
# Refresh if missing or expiring within 30 seconds
|
|
107
|
+
if not expires_at or (expires_at - time.time()) < 30:
|
|
108
|
+
new_token = self.session.fetch_token(
|
|
109
|
+
token_url=self._token_url,
|
|
110
|
+
client_id=self._client_id,
|
|
111
|
+
client_secret=self._client_secret,
|
|
112
|
+
include_client_id=True,
|
|
113
|
+
)
|
|
114
|
+
self.access_token = new_token.get("access_token")
|
|
115
|
+
|
|
116
|
+
def _request_with_pre_expiry(self, method, url, **kwargs):
|
|
117
|
+
self._ensure_valid_token()
|
|
118
|
+
return self._orig_request(method, url, **kwargs)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from typing import Tuple, Dict, Any
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import requests
|
|
4
|
+
from brynq_sdk_functions import Functions
|
|
5
|
+
from .schemas.address import AddressGet, AddressUpdate
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .acerta import Acerta
|
|
9
|
+
|
|
10
|
+
class Addresses:
|
|
11
|
+
"""Resource class for Employee Address endpoints"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, acerta):
|
|
14
|
+
self.acerta: Acerta = acerta
|
|
15
|
+
self.base_uri = "v3/employee-data-management/v3/employees"
|
|
16
|
+
|
|
17
|
+
def get(self, from_date: str = "1900-01-01", until_date: str = "9999-12-31") -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
18
|
+
"""
|
|
19
|
+
GET /employee-data-management/v3/employees/{employeeId}/addresses - Employee Addresses
|
|
20
|
+
|
|
21
|
+
Retrieve employee address history within a specified time window for all cached employees.
|
|
22
|
+
Returns both official and correspondence addresses with their validity periods.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
from_date: Start date of the time window (default: "1900-01-01")
|
|
26
|
+
until_date: End date of the time window (default: "9999-12-31")
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Tuple[pd.DataFrame, pd.DataFrame]: (valid_df, invalid_df) after validation
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
RuntimeError: If the retrieval fails
|
|
33
|
+
"""
|
|
34
|
+
params = {
|
|
35
|
+
"fromDate": from_date,
|
|
36
|
+
"untilDate": until_date
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if not self.acerta._employee_ids:
|
|
40
|
+
# Auto-warm cache by fetching agreements (populates employee IDs)
|
|
41
|
+
self.acerta.agreements.get()
|
|
42
|
+
|
|
43
|
+
all_frames = []
|
|
44
|
+
for employee_id in self.acerta._employee_ids:
|
|
45
|
+
response = self.acerta.session.get(
|
|
46
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{employee_id}/addresses",
|
|
47
|
+
params=params,
|
|
48
|
+
timeout=self.acerta.TIMEOUT,
|
|
49
|
+
)
|
|
50
|
+
response.raise_for_status()
|
|
51
|
+
content = response.json()
|
|
52
|
+
df = pd.json_normalize(
|
|
53
|
+
content,
|
|
54
|
+
record_path=['addressSegments'],
|
|
55
|
+
meta=['employeeId'],
|
|
56
|
+
sep='.'
|
|
57
|
+
)
|
|
58
|
+
if 'externalReferences' in df.columns:
|
|
59
|
+
df = df.drop(columns=['externalReferences'])
|
|
60
|
+
all_frames.append(df)
|
|
61
|
+
|
|
62
|
+
combined = pd.concat(all_frames, ignore_index=True) if all_frames else pd.DataFrame()
|
|
63
|
+
|
|
64
|
+
valid_data, invalid_data = Functions.validate_data(combined, AddressGet)
|
|
65
|
+
|
|
66
|
+
return valid_data, invalid_data
|
|
67
|
+
|
|
68
|
+
def update(self, employee_id: str, data: Dict[str, Any]) -> requests.Response:
|
|
69
|
+
"""
|
|
70
|
+
PATCH /employee-data-management/v3/employees/{employeeId}/addresses - Employee Address
|
|
71
|
+
|
|
72
|
+
Update employee address information including official address and optional
|
|
73
|
+
correspondence address. Addresses are historical data with validity dates.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
employee_id: Unique identifier of an employee
|
|
77
|
+
data: Flat dictionary with address data
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
requests.Response: Raw response object
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
RuntimeError: If the address update fails
|
|
84
|
+
"""
|
|
85
|
+
# Convert flat data to nested using Functions.flat_to_nested_with_prefix
|
|
86
|
+
nested_data = Functions.flat_to_nested_with_prefix(data, AddressUpdate)
|
|
87
|
+
|
|
88
|
+
# Validate the nested data
|
|
89
|
+
validated_data = AddressUpdate(**nested_data)
|
|
90
|
+
|
|
91
|
+
# Make API request
|
|
92
|
+
response = self.acerta.session.patch(
|
|
93
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{employee_id}/addresses",
|
|
94
|
+
json=validated_data.model_dump(by_alias=True, exclude_none=True),
|
|
95
|
+
timeout=self.acerta.TIMEOUT,
|
|
96
|
+
)
|
|
97
|
+
response.raise_for_status()
|
|
98
|
+
|
|
99
|
+
return response
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional, Tuple
|
|
2
|
+
import requests
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from .schemas.agreement import (
|
|
5
|
+
AgreementGet,
|
|
6
|
+
PatchAgreementRequest,
|
|
7
|
+
AgreementBasicInformationGet,
|
|
8
|
+
AgreementEmploymentsGet,
|
|
9
|
+
AgreementWorkingTimeGet,
|
|
10
|
+
AgreementCommutingGet,
|
|
11
|
+
AgreementCustomFieldsGet,
|
|
12
|
+
AgreementCostCenterAllocationGet,
|
|
13
|
+
)
|
|
14
|
+
from .schemas.in_service_config import JointCommitteeGet, FunctionGet
|
|
15
|
+
from brynq_sdk_functions import Functions
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from .acerta import Acerta
|
|
19
|
+
|
|
20
|
+
class Agreements:
|
|
21
|
+
"""Resource class for Agreement endpoints"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, acerta):
|
|
24
|
+
self.acerta: Acerta = acerta
|
|
25
|
+
self.base_uri = "agreement-data-management/v3/agreements"
|
|
26
|
+
# We cache agreements to avoid duplicate requests. Agreements always need to be fetched in order to get employee_ids.
|
|
27
|
+
# For ease of use, we do that in the background, so you can retrieve employees without having to fetch agreements first.
|
|
28
|
+
# To avoid retrieving agreements twice, we cache them so we can return them if they are already cached.
|
|
29
|
+
self._cached_agreements = None
|
|
30
|
+
|
|
31
|
+
def get_joint_committees(self, employer_id: Optional[str] = None, in_service_type: Optional[str] = None, accept_language: str = "en") -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
32
|
+
"""
|
|
33
|
+
GET /employee-in-service-request/v1/employers/{employerId}/joint-committees
|
|
34
|
+
"""
|
|
35
|
+
employers = [employer_id] if employer_id else self.acerta._employer_ids
|
|
36
|
+
all_rows = []
|
|
37
|
+
for emp_id in employers:
|
|
38
|
+
params = {"Accept-Language": accept_language}
|
|
39
|
+
if in_service_type:
|
|
40
|
+
params["inServiceType"] = in_service_type
|
|
41
|
+
response = self.acerta.session.get(
|
|
42
|
+
url=f"{self.acerta.base_url}/employee-in-service-request/v1/employers/{emp_id}/joint-committees",
|
|
43
|
+
params=params,
|
|
44
|
+
timeout=self.acerta.TIMEOUT,
|
|
45
|
+
)
|
|
46
|
+
response.raise_for_status()
|
|
47
|
+
raw = response.json()
|
|
48
|
+
items = raw.get("_embedded", {}).get("jointCommittees", [])
|
|
49
|
+
if items:
|
|
50
|
+
df = pd.json_normalize(items)
|
|
51
|
+
df["employer_id"] = emp_id
|
|
52
|
+
all_rows.append(df)
|
|
53
|
+
combined = pd.concat(all_rows, ignore_index=True) if all_rows else pd.DataFrame()
|
|
54
|
+
return Functions.validate_data(combined, JointCommitteeGet)
|
|
55
|
+
|
|
56
|
+
def get_functions(self, employer_id: Optional[str] = None, accept_language: str = "en") -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
57
|
+
"""
|
|
58
|
+
GET /employee-in-service-request/v1/employers/{employerId}/functions
|
|
59
|
+
"""
|
|
60
|
+
employers = [employer_id] if employer_id else self.acerta._employer_ids
|
|
61
|
+
all_rows = []
|
|
62
|
+
for emp_id in employers:
|
|
63
|
+
params = {"Accept-Language": accept_language}
|
|
64
|
+
response = self.acerta.session.get(
|
|
65
|
+
url=f"{self.acerta.base_url}/employee-in-service-request/v1/employers/{emp_id}/functions",
|
|
66
|
+
params=params,
|
|
67
|
+
timeout=self.acerta.TIMEOUT,
|
|
68
|
+
)
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
raw = response.json()
|
|
71
|
+
items = raw.get("_embedded", {}).get("functions", [])
|
|
72
|
+
if items:
|
|
73
|
+
df = pd.json_normalize(items)
|
|
74
|
+
df["employer_id"] = emp_id
|
|
75
|
+
all_rows.append(df)
|
|
76
|
+
combined = pd.concat(all_rows, ignore_index=True) if all_rows else pd.DataFrame()
|
|
77
|
+
return Functions.validate_data(combined, FunctionGet)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get(self) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
81
|
+
"""
|
|
82
|
+
GET /v3/agreements - Agreements
|
|
83
|
+
|
|
84
|
+
Retrieve agreements for all configured employer IDs. Returns agreement details including
|
|
85
|
+
agreement ID, employer information, legal entity, and agreement type.
|
|
86
|
+
|
|
87
|
+
Behavior:
|
|
88
|
+
- Aggregates results across `self.acerta._employer_ids`
|
|
89
|
+
- Validates and splits into valid/invalid DataFrames
|
|
90
|
+
- Updates `self.acerta._employee_ids` and `_agreement_ids`
|
|
91
|
+
- Caches results on first call within this instance
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Tuple[pd.DataFrame, pd.DataFrame]: (valid_df, invalid_df)
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
Exception: If retrieval or validation fails
|
|
98
|
+
"""
|
|
99
|
+
if self._cached_agreements is None:
|
|
100
|
+
self._cached_agreements = self._get_agreements()
|
|
101
|
+
return self._cached_agreements
|
|
102
|
+
|
|
103
|
+
def _get_agreements(self):
|
|
104
|
+
all_dfs = []
|
|
105
|
+
for employer in self.acerta._employer_ids:
|
|
106
|
+
page = 0
|
|
107
|
+
total_pages = 1
|
|
108
|
+
|
|
109
|
+
while page < total_pages:
|
|
110
|
+
params = {k: v for k, v in {
|
|
111
|
+
"employerId": employer,
|
|
112
|
+
"page": page,
|
|
113
|
+
"size": 200,
|
|
114
|
+
}.items() if v is not None}
|
|
115
|
+
|
|
116
|
+
response = self.acerta.session.get(
|
|
117
|
+
url=f"{self.acerta.base_url}/{self.base_uri}",
|
|
118
|
+
params=params,
|
|
119
|
+
timeout=self.acerta.TIMEOUT,
|
|
120
|
+
)
|
|
121
|
+
response.raise_for_status()
|
|
122
|
+
|
|
123
|
+
agreements_data = response.json().get("_embedded", {}).get("agreements", [])
|
|
124
|
+
if agreements_data:
|
|
125
|
+
df = pd.json_normalize(agreements_data, sep=".")
|
|
126
|
+
all_dfs.append(df)
|
|
127
|
+
|
|
128
|
+
page_info = response.json().get("page") or {}
|
|
129
|
+
total_pages = max(total_pages, page_info.get("totalPages", total_pages))
|
|
130
|
+
page += 1
|
|
131
|
+
|
|
132
|
+
agreements = pd.concat(all_dfs, ignore_index=True) if all_dfs else pd.DataFrame()
|
|
133
|
+
valid_agreements, invalid_agreements = Functions.validate_data(agreements, AgreementGet, debug=self.acerta.debug)
|
|
134
|
+
|
|
135
|
+
if not valid_agreements.empty:
|
|
136
|
+
self.acerta._employee_ids.update(valid_agreements["employee_id"].dropna().unique())
|
|
137
|
+
self.acerta._agreement_ids.update(valid_agreements["agreement_id"].dropna().unique())
|
|
138
|
+
|
|
139
|
+
return valid_agreements, invalid_agreements
|
|
140
|
+
|
|
141
|
+
def get_by_id(self, agreement_id: str, accept_language: str = "en") -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
142
|
+
"""GET /v3/agreements/{agreementId}"""
|
|
143
|
+
headers = {"Accept-Language": accept_language} if accept_language else None
|
|
144
|
+
request_headers = self.acerta.session.headers.copy()
|
|
145
|
+
if headers:
|
|
146
|
+
request_headers.update(headers)
|
|
147
|
+
response = self.acerta.session.get(
|
|
148
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{agreement_id}",
|
|
149
|
+
headers=request_headers,
|
|
150
|
+
timeout=self.acerta.TIMEOUT,
|
|
151
|
+
)
|
|
152
|
+
response.raise_for_status()
|
|
153
|
+
raw = response.json()
|
|
154
|
+
if isinstance(raw, dict):
|
|
155
|
+
df = pd.json_normalize(raw, sep=".")
|
|
156
|
+
else:
|
|
157
|
+
df = pd.json_normalize([raw], sep=".")
|
|
158
|
+
return df, pd.DataFrame()
|
|
159
|
+
|
|
160
|
+
def update(self, agreement_id: str, data: Dict[str, Any], accept_language: str = "en") -> requests.Response:
|
|
161
|
+
"""PATCH /v3/agreements/{agreementId}"""
|
|
162
|
+
nested_data = Functions.flat_to_nested_with_prefix(data, PatchAgreementRequest)
|
|
163
|
+
validated = PatchAgreementRequest(**nested_data)
|
|
164
|
+
headers = {"Accept-Language": accept_language} if accept_language else None
|
|
165
|
+
request_headers = self.acerta.session.headers.copy()
|
|
166
|
+
if headers:
|
|
167
|
+
request_headers.update(headers)
|
|
168
|
+
response = self.acerta.session.patch(
|
|
169
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{agreement_id}",
|
|
170
|
+
json=validated.model_dump(by_alias=True, exclude_none=True),
|
|
171
|
+
headers=request_headers,
|
|
172
|
+
timeout=self.acerta.TIMEOUT,
|
|
173
|
+
)
|
|
174
|
+
response.raise_for_status()
|
|
175
|
+
return response
|
|
176
|
+
|
|
177
|
+
def get_basic_information(
|
|
178
|
+
self,
|
|
179
|
+
agreement_id: Optional[str] = None,
|
|
180
|
+
from_date: Optional[str] = None,
|
|
181
|
+
until_date: Optional[str] = None,
|
|
182
|
+
full_segments_only: Optional[bool] = None,
|
|
183
|
+
accept_language: str = "en",
|
|
184
|
+
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
185
|
+
"""GET /v3/agreements/{agreementId}/basic-information"""
|
|
186
|
+
params = {k: v for k, v in {
|
|
187
|
+
"fromDate": from_date,
|
|
188
|
+
"untilDate": until_date,
|
|
189
|
+
"fullSegmentsOnly": str(full_segments_only).lower() if isinstance(full_segments_only, bool) else full_segments_only,
|
|
190
|
+
}.items() if v is not None}
|
|
191
|
+
if not agreement_id and not self.acerta._agreement_ids:
|
|
192
|
+
self.get()
|
|
193
|
+
ids = [agreement_id] if agreement_id else self.acerta._agreement_ids
|
|
194
|
+
frames = []
|
|
195
|
+
headers = {"Accept-Language": accept_language} if accept_language else None
|
|
196
|
+
for agr_id in ids:
|
|
197
|
+
request_headers = self.acerta.session.headers.copy()
|
|
198
|
+
if headers:
|
|
199
|
+
request_headers.update(headers)
|
|
200
|
+
response = self.acerta.session.get(
|
|
201
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{agr_id}/basic-information",
|
|
202
|
+
params=params,
|
|
203
|
+
headers=request_headers,
|
|
204
|
+
timeout=self.acerta.TIMEOUT,
|
|
205
|
+
)
|
|
206
|
+
response.raise_for_status()
|
|
207
|
+
raw = response.json()
|
|
208
|
+
df = pd.json_normalize(
|
|
209
|
+
raw,
|
|
210
|
+
record_path=["basicInformationSegments"],
|
|
211
|
+
meta=["agreementId", ["agreementType", "code"], ["agreementType", "description"]],
|
|
212
|
+
errors="ignore",
|
|
213
|
+
sep=".",
|
|
214
|
+
)
|
|
215
|
+
frames.append(df)
|
|
216
|
+
combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
|
|
217
|
+
return Functions.validate_data(combined, AgreementBasicInformationGet, debug=self.acerta.debug)
|
|
218
|
+
|
|
219
|
+
def get_employments(
|
|
220
|
+
self,
|
|
221
|
+
agreement_id: Optional[str] = None,
|
|
222
|
+
from_date: Optional[str] = None,
|
|
223
|
+
until_date: Optional[str] = None,
|
|
224
|
+
accept_language: str = "en",
|
|
225
|
+
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
226
|
+
"""GET /v3/agreements/{agreementId}/employments"""
|
|
227
|
+
params = {k: v for k, v in {
|
|
228
|
+
"fromDate": from_date,
|
|
229
|
+
"untilDate": until_date,
|
|
230
|
+
}.items() if v is not None}
|
|
231
|
+
if not agreement_id and not self.acerta._agreement_ids:
|
|
232
|
+
self.get()
|
|
233
|
+
ids = [agreement_id] if agreement_id else self.acerta._agreement_ids
|
|
234
|
+
frames = []
|
|
235
|
+
headers = {"Accept-Language": accept_language} if accept_language else None
|
|
236
|
+
for agr_id in ids:
|
|
237
|
+
request_headers = self.acerta.session.headers.copy()
|
|
238
|
+
if headers:
|
|
239
|
+
request_headers.update(headers)
|
|
240
|
+
response = self.acerta.session.get(
|
|
241
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{agr_id}/employments",
|
|
242
|
+
params=params,
|
|
243
|
+
headers=request_headers,
|
|
244
|
+
timeout=self.acerta.TIMEOUT,
|
|
245
|
+
)
|
|
246
|
+
response.raise_for_status()
|
|
247
|
+
raw = response.json()
|
|
248
|
+
df = pd.json_normalize(
|
|
249
|
+
raw,
|
|
250
|
+
record_path=["employments"],
|
|
251
|
+
meta=["agreementId"],
|
|
252
|
+
errors="ignore",
|
|
253
|
+
sep=".",
|
|
254
|
+
)
|
|
255
|
+
frames.append(df)
|
|
256
|
+
combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
|
|
257
|
+
return Functions.validate_data(combined, AgreementEmploymentsGet, debug=self.acerta.debug)
|
|
258
|
+
|
|
259
|
+
def get_working_time(
|
|
260
|
+
self,
|
|
261
|
+
agreement_id: Optional[str] = None,
|
|
262
|
+
from_date: Optional[str] = None,
|
|
263
|
+
until_date: Optional[str] = None,
|
|
264
|
+
full_segments_only: Optional[bool] = None,
|
|
265
|
+
accept_language: str = "en",
|
|
266
|
+
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
267
|
+
"""GET /v3/agreements/{agreementId}/working-time"""
|
|
268
|
+
params = {k: v for k, v in {
|
|
269
|
+
"fromDate": from_date,
|
|
270
|
+
"untilDate": until_date,
|
|
271
|
+
"fullSegmentsOnly": str(full_segments_only).lower() if isinstance(full_segments_only, bool) else full_segments_only,
|
|
272
|
+
}.items() if v is not None}
|
|
273
|
+
if not agreement_id and not self.acerta._agreement_ids:
|
|
274
|
+
self.get()
|
|
275
|
+
ids = [agreement_id] if agreement_id else self.acerta._agreement_ids
|
|
276
|
+
frames = []
|
|
277
|
+
headers = {"Accept-Language": accept_language} if accept_language else None
|
|
278
|
+
for agr_id in ids:
|
|
279
|
+
request_headers = self.acerta.session.headers.copy()
|
|
280
|
+
if headers:
|
|
281
|
+
request_headers.update(headers)
|
|
282
|
+
response = self.acerta.session.get(
|
|
283
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{agr_id}/working-time",
|
|
284
|
+
params=params,
|
|
285
|
+
headers=request_headers,
|
|
286
|
+
timeout=self.acerta.TIMEOUT,
|
|
287
|
+
)
|
|
288
|
+
response.raise_for_status()
|
|
289
|
+
raw = response.json()
|
|
290
|
+
df = pd.json_normalize(
|
|
291
|
+
raw,
|
|
292
|
+
record_path=["workingTimeSegments"],
|
|
293
|
+
meta=["agreementId"],
|
|
294
|
+
errors="ignore",
|
|
295
|
+
sep=".",
|
|
296
|
+
)
|
|
297
|
+
frames.append(df)
|
|
298
|
+
combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
|
|
299
|
+
return Functions.validate_data(combined, AgreementWorkingTimeGet, debug=self.acerta.debug)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def get_commuting(
|
|
303
|
+
self,
|
|
304
|
+
agreement_id: Optional[str] = None,
|
|
305
|
+
from_date: Optional[str] = None,
|
|
306
|
+
until_date: Optional[str] = None,
|
|
307
|
+
full_segments_only: Optional[bool] = None,
|
|
308
|
+
accept_language: str = "en",
|
|
309
|
+
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
310
|
+
"""GET /v3/agreements/{agreementId}/commuting"""
|
|
311
|
+
params = {k: v for k, v in {
|
|
312
|
+
"fromDate": from_date,
|
|
313
|
+
"untilDate": until_date,
|
|
314
|
+
"fullSegmentsOnly": str(full_segments_only).lower() if isinstance(full_segments_only, bool) else full_segments_only,
|
|
315
|
+
}.items() if v is not None}
|
|
316
|
+
if not agreement_id and not self.acerta._agreement_ids:
|
|
317
|
+
self.get()
|
|
318
|
+
ids = [agreement_id] if agreement_id else self.acerta._agreement_ids
|
|
319
|
+
frames = []
|
|
320
|
+
headers = {"Accept-Language": accept_language} if accept_language else None
|
|
321
|
+
for agr_id in ids:
|
|
322
|
+
request_headers = self.acerta.session.headers.copy()
|
|
323
|
+
if headers:
|
|
324
|
+
request_headers.update(headers)
|
|
325
|
+
response = self.acerta.session.get(
|
|
326
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{agr_id}/commuting",
|
|
327
|
+
params=params,
|
|
328
|
+
headers=request_headers,
|
|
329
|
+
timeout=self.acerta.TIMEOUT,
|
|
330
|
+
)
|
|
331
|
+
response.raise_for_status()
|
|
332
|
+
raw = response.json()
|
|
333
|
+
df = pd.json_normalize(
|
|
334
|
+
raw,
|
|
335
|
+
record_path=["commutingSegments"],
|
|
336
|
+
meta=["agreementId"],
|
|
337
|
+
errors="ignore",
|
|
338
|
+
sep=".",
|
|
339
|
+
)
|
|
340
|
+
frames.append(df)
|
|
341
|
+
combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
|
|
342
|
+
return Functions.validate_data(combined, AgreementCommutingGet, debug=self.acerta.debug)
|
|
343
|
+
|
|
344
|
+
def get_custom_fields(
|
|
345
|
+
self,
|
|
346
|
+
agreement_id: Optional[str] = None,
|
|
347
|
+
from_date: Optional[str] = None,
|
|
348
|
+
until_date: Optional[str] = None,
|
|
349
|
+
custom_field_names: Optional[list] = None,
|
|
350
|
+
accept_language: str = "en",
|
|
351
|
+
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
352
|
+
"""GET /v3/agreements/{agreementId}/custom-fields"""
|
|
353
|
+
params_dict: Dict[str, Any] = {
|
|
354
|
+
"fromDate": from_date,
|
|
355
|
+
"untilDate": until_date,
|
|
356
|
+
}
|
|
357
|
+
if custom_field_names:
|
|
358
|
+
params_dict["customFieldNames"] = ",".join(str(x) for x in custom_field_names)
|
|
359
|
+
params = {k: v for k, v in params_dict.items() if v is not None}
|
|
360
|
+
if not agreement_id and not self.acerta._agreement_ids:
|
|
361
|
+
self.get()
|
|
362
|
+
ids = [agreement_id] if agreement_id else self.acerta._agreement_ids
|
|
363
|
+
frames = []
|
|
364
|
+
headers = {"Accept-Language": accept_language} if accept_language else None
|
|
365
|
+
for agr_id in ids:
|
|
366
|
+
request_headers = self.acerta.session.headers.copy()
|
|
367
|
+
if headers:
|
|
368
|
+
request_headers.update(headers)
|
|
369
|
+
response = self.acerta.session.get(
|
|
370
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{agr_id}/custom-fields",
|
|
371
|
+
params=params,
|
|
372
|
+
headers=request_headers,
|
|
373
|
+
timeout=self.acerta.TIMEOUT,
|
|
374
|
+
)
|
|
375
|
+
response.raise_for_status()
|
|
376
|
+
raw = response.json()
|
|
377
|
+
df = pd.json_normalize(
|
|
378
|
+
raw,
|
|
379
|
+
record_path=["customFieldsSegments"],
|
|
380
|
+
meta=["agreementId"],
|
|
381
|
+
errors="ignore",
|
|
382
|
+
sep=".",
|
|
383
|
+
)
|
|
384
|
+
frames.append(df)
|
|
385
|
+
combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
|
|
386
|
+
return Functions.validate_data(combined, AgreementCustomFieldsGet, debug=self.acerta.debug)
|
|
387
|
+
|
|
388
|
+
def get_cost_center_allocation(
|
|
389
|
+
self,
|
|
390
|
+
agreement_id: Optional[str] = None,
|
|
391
|
+
from_date: Optional[str] = None,
|
|
392
|
+
until_date: Optional[str] = None,
|
|
393
|
+
accept_language: str = "en",
|
|
394
|
+
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
395
|
+
"""GET /v3/agreements/{agreementId}/cost-center-allocation"""
|
|
396
|
+
params = {k: v for k, v in {
|
|
397
|
+
"fromDate": from_date,
|
|
398
|
+
"untilDate": until_date,
|
|
399
|
+
}.items() if v is not None}
|
|
400
|
+
if not agreement_id and not self.acerta._agreement_ids:
|
|
401
|
+
self.get()
|
|
402
|
+
ids = [agreement_id] if agreement_id else self.acerta._agreement_ids
|
|
403
|
+
frames = []
|
|
404
|
+
headers = {"Accept-Language": accept_language} if accept_language else None
|
|
405
|
+
for agr_id in ids:
|
|
406
|
+
request_headers = self.acerta.session.headers.copy()
|
|
407
|
+
if headers:
|
|
408
|
+
request_headers.update(headers)
|
|
409
|
+
response = self.acerta.session.get(
|
|
410
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{agr_id}/cost-center-allocation",
|
|
411
|
+
params=params,
|
|
412
|
+
headers=request_headers,
|
|
413
|
+
timeout=self.acerta.TIMEOUT,
|
|
414
|
+
)
|
|
415
|
+
response.raise_for_status()
|
|
416
|
+
raw = response.json()
|
|
417
|
+
df = pd.json_normalize(
|
|
418
|
+
raw,
|
|
419
|
+
record_path=["costCenterAllocation"],
|
|
420
|
+
meta=["agreementId"],
|
|
421
|
+
errors="ignore",
|
|
422
|
+
sep=".",
|
|
423
|
+
)
|
|
424
|
+
frames.append(df)
|
|
425
|
+
combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
|
|
426
|
+
return Functions.validate_data(combined, AgreementCostCenterAllocationGet, debug=self.acerta.debug)
|