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,90 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional, Tuple
|
|
2
|
+
import requests
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from brynq_sdk_functions import Functions
|
|
5
|
+
from .schemas.agreement import AgreementRemunerationBankAccountUpdate
|
|
6
|
+
from .schemas.bank_account import AgreementBankAccountsGet
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .acerta import Acerta
|
|
10
|
+
|
|
11
|
+
class BankAccounts:
|
|
12
|
+
"""Resource class for Employee Bank Accounts (HAL links)."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, acerta):
|
|
15
|
+
self.acerta: Acerta = acerta
|
|
16
|
+
self.base_uri = "v1"
|
|
17
|
+
|
|
18
|
+
def get(self, agreement_id: Optional[str] = None, from_date: Optional[str] = None, until_date: Optional[str] = None, full_segments_only: Optional[bool] = None, accept_language: str = "en") -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
19
|
+
"""
|
|
20
|
+
GET /v3/agreements/{agreementId}/remuneration - derive bank accounts
|
|
21
|
+
|
|
22
|
+
Fetches remuneration segments for agreements and returns only the bank account related fields
|
|
23
|
+
(via methodOfPayment.employeeBankAccounts/employerBankAccounts).
|
|
24
|
+
"""
|
|
25
|
+
# Ensure we have agreement ids
|
|
26
|
+
if not agreement_id and not self.acerta._agreement_ids:
|
|
27
|
+
self.acerta.agreements.get()
|
|
28
|
+
agreement_ids = [agreement_id] if agreement_id else self.acerta._agreement_ids
|
|
29
|
+
|
|
30
|
+
params = {k: v for k, v in {
|
|
31
|
+
"fromDate": from_date,
|
|
32
|
+
"untilDate": until_date,
|
|
33
|
+
"fullSegmentsOnly": str(full_segments_only).lower() if isinstance(full_segments_only, bool) else full_segments_only,
|
|
34
|
+
}.items() if v is not None}
|
|
35
|
+
|
|
36
|
+
frames = []
|
|
37
|
+
headers = {"Accept-Language": accept_language} if accept_language else None
|
|
38
|
+
for agr_id in agreement_ids:
|
|
39
|
+
request_headers = self.acerta.session.headers.copy()
|
|
40
|
+
if headers:
|
|
41
|
+
request_headers.update(headers)
|
|
42
|
+
response = self.acerta.session.get(
|
|
43
|
+
url=f"{self.acerta.base_url}/agreement-data-management/v3/agreements/{agr_id}/remuneration",
|
|
44
|
+
params=params,
|
|
45
|
+
headers=request_headers,
|
|
46
|
+
timeout=self.acerta.TIMEOUT,
|
|
47
|
+
)
|
|
48
|
+
response.raise_for_status()
|
|
49
|
+
raw = response.json()
|
|
50
|
+
df = pd.json_normalize(
|
|
51
|
+
raw,
|
|
52
|
+
record_path=["remunerationSegments"],
|
|
53
|
+
meta=["agreementId"],
|
|
54
|
+
errors="ignore",
|
|
55
|
+
sep=".",
|
|
56
|
+
)
|
|
57
|
+
frames.append(df)
|
|
58
|
+
|
|
59
|
+
combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
|
|
60
|
+
return Functions.validate_data(combined, AgreementBankAccountsGet, debug=self.acerta.debug)
|
|
61
|
+
|
|
62
|
+
def update(self, agreement_id: str, data: Dict[str, Any]) -> requests.Response:
|
|
63
|
+
"""
|
|
64
|
+
PUT /employee-data-management/v3/employees/agreement/{agreementId}/remuneration-bank-account - Agreement Remuneration Bank Account
|
|
65
|
+
|
|
66
|
+
Set the bank account for the employee and agreement. This endpoint updates the remuneration
|
|
67
|
+
bank account associated with a specific agreement.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
agreement_id: Unique identifier of an agreement
|
|
71
|
+
data: Dictionary with bank account data (iban, bic)
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
requests.Response: Raw response object
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
Exception: If the update fails
|
|
78
|
+
"""
|
|
79
|
+
# Validate the data (no nesting required for this simple schema)
|
|
80
|
+
validated_data = AgreementRemunerationBankAccountUpdate(**data)
|
|
81
|
+
|
|
82
|
+
# Make API request
|
|
83
|
+
response = self.acerta.session.put(
|
|
84
|
+
url=f"{self.acerta.base_url}/employee-data-management/v3/employees/agreement/{agreement_id}/remuneration-bank-account",
|
|
85
|
+
json=validated_data.model_dump(by_alias=True, exclude_none=True),
|
|
86
|
+
timeout=self.acerta.TIMEOUT,
|
|
87
|
+
)
|
|
88
|
+
response.raise_for_status()
|
|
89
|
+
|
|
90
|
+
return response
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import requests
|
|
4
|
+
from brynq_sdk_functions import Functions
|
|
5
|
+
from .schemas.planning import CountryGet, BelgianCityGet
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import List
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .acerta import Acerta
|
|
11
|
+
|
|
12
|
+
class CodeListAttribute(str, Enum):
|
|
13
|
+
ACE_CountryNisCode = 'ACE_CountryNisCode'
|
|
14
|
+
ACE_Gender = 'ACE_Gender'
|
|
15
|
+
ACE_Language = 'ACE_Language'
|
|
16
|
+
ACE_MaritalStatus = 'ACE_MaritalStatus'
|
|
17
|
+
ACE_Nationality = 'ACE_Nationality'
|
|
18
|
+
ACE_OfficialLanguageCode = 'ACE_OfficialLanguageCode'
|
|
19
|
+
ACE_RelationshipContact = 'ACE_RelationshipContact'
|
|
20
|
+
B10_AddressType = 'B10_AddressType'
|
|
21
|
+
B10_EducationalDegree = 'B10_EducationalDegree'
|
|
22
|
+
B10_LeadershipLevel = 'B10_LeadershipLevel'
|
|
23
|
+
B11_JointCommittee = 'B11_JointCommittee'
|
|
24
|
+
B11_PrefixNOSS = 'B11_PrefixNOSS'
|
|
25
|
+
B11_Qualification = 'B11_Qualification'
|
|
26
|
+
B11_PayFrequency = 'B11_PayFrequency'
|
|
27
|
+
B12_ApprenticeType = 'B12_ApprenticeType'
|
|
28
|
+
B12_CommunityCode = 'B12_CommunityCode'
|
|
29
|
+
B12_Customized_vacation_pay = 'B12_Customized_vacation_pay'
|
|
30
|
+
B12_Declaration_withholding_tax = 'B12_Declaration_withholding_tax '
|
|
31
|
+
B12_DMFA_Risc_labour_accident = 'B12_DMFA_Risc_labour_accident'
|
|
32
|
+
B12_Duration = 'B12_Duration'
|
|
33
|
+
B12_EarlyRetirementCode = 'B12_EarlyRetirementCode'
|
|
34
|
+
B12_EmployeeType = 'B12_EmployeeType'
|
|
35
|
+
B12_EmployeeTypeDet = 'B12_EmployeeTypeDet'
|
|
36
|
+
B12_ExemptionWorkPerformance = 'B12_ExemptionWorkPerformance'
|
|
37
|
+
B12_ExemptPartTimeCreditCode = 'B12_ExemptPartTimeCreditCode'
|
|
38
|
+
B12_Frequency_transport = 'B12_Frequency_transport'
|
|
39
|
+
B12_Functioncode = 'B12_Functioncode'
|
|
40
|
+
B12_HomeworkTransport = 'B12_HomeworkTransport'
|
|
41
|
+
B12_Involuntary_parttime = 'B12_Involuntary_parttime'
|
|
42
|
+
B12_Measure = 'B12_Measure'
|
|
43
|
+
B12_MeasureResumptionWorkCode = 'B12_MeasureResumptionWorkCode'
|
|
44
|
+
B12_Notion_NOSS = 'B12_Notion_NOSS'
|
|
45
|
+
B12_NotionDebtorCode = 'B12_NotionDebtorCode'
|
|
46
|
+
B12_Payment = 'B12_Payment'
|
|
47
|
+
B12_Reason_end_employment = 'B12_Reason_end_employment'
|
|
48
|
+
B12_Regularity = 'B12_Regularity'
|
|
49
|
+
B12_Salary_calculation = 'B12_Salary_calculation'
|
|
50
|
+
B12_Salary_split = 'B12_Salary_split'
|
|
51
|
+
B12_SalaryScaleTypeCode = 'B12_SalaryScaleTypeCode'
|
|
52
|
+
B12_Tariff_period = 'B12_Tariff_period'
|
|
53
|
+
B12_TimeCredit = 'B12_TimeCredit'
|
|
54
|
+
B12_TipsCode = 'B12_TipsCode'
|
|
55
|
+
B12_TipsTypeCode = 'B12_TipsTypeCode'
|
|
56
|
+
B12_Transport_type = 'B12_Transport_type'
|
|
57
|
+
B12_Type_of_subscription = 'B12_Type_of_subscription'
|
|
58
|
+
B12_Type_withholding_tax = 'B12_Type_withholding_tax'
|
|
59
|
+
B12_TypeAgreement = 'B12_TypeAgreement'
|
|
60
|
+
B12_WorkscheduleType = 'B12_WorkscheduleType'
|
|
61
|
+
BA4_BelgianPostalCode = 'BA4_BelgianPostalCode'
|
|
62
|
+
B12_MeasureNonProfit = 'B12_MeasureNonProfit'
|
|
63
|
+
B12_FunctionNOSS = 'B12_FunctionNOSS'
|
|
64
|
+
B12_PensionSystem = 'B12_PensionSystem'
|
|
65
|
+
B12_NACE = 'B12_NACE'
|
|
66
|
+
B12_Capelo_PersonnelCategory = 'B12_Capelo_PersonnelCategory'
|
|
67
|
+
B12_Capelo_Function = 'B12_Capelo_Function'
|
|
68
|
+
B12_Capelo_InstitutionType = 'B12_Capelo_InstitutionType'
|
|
69
|
+
B12_Capelo_ServiceType = 'B12_Capelo_ServiceType'
|
|
70
|
+
B12_SickLeaveReserve = 'B12_SickLeaveReserve'
|
|
71
|
+
B12_Wage_system = 'B12_Wage_system'
|
|
72
|
+
B12_Diploma = 'B12_Diploma'
|
|
73
|
+
B12_ParticularCompetence = 'B12_ParticularCompetence'
|
|
74
|
+
B12_DistinguishingLetter = 'B12_DistinguishingLetter'
|
|
75
|
+
B12_ServiceActivity = 'B12_ServiceActivity'
|
|
76
|
+
B12_StatusHospital = 'B12_StatusHospital'
|
|
77
|
+
B12_AdditionalKwalification = 'B12_AdditionalKwalification'
|
|
78
|
+
B12_Specialism = 'B12_Specialism'
|
|
79
|
+
B12_StaffTypeHospital = 'B12_StaffTypeHospital'
|
|
80
|
+
B12_FinancingCode = 'B12_FinancingCode'
|
|
81
|
+
B12_KwalificationEmployee = 'B12_KwalificationEmployee'
|
|
82
|
+
B12_OccupationalClassMWB = 'B12_OccupationalClassMWB'
|
|
83
|
+
B12_KwalificationSupervisor = 'B12_KwalificationSupervisor'
|
|
84
|
+
B12_IntensitySupervision = 'B12_IntensitySupervision'
|
|
85
|
+
B12_StatusTargetGroup = 'B12_StatusTargetGroup'
|
|
86
|
+
B12_Regional_recruitment_framework = 'B12_Regional_recruitment_framework'
|
|
87
|
+
B12_Level = 'B12_Level'
|
|
88
|
+
B12_ProgWorkResumpType = 'B12_ProgWorkResumpType'
|
|
89
|
+
B11_VIA = 'B11_VIA'
|
|
90
|
+
B12_SocialMaribel = 'B12_SocialMaribel'
|
|
91
|
+
B12_Tax_Statute = 'B12_Tax_Statute'
|
|
92
|
+
B12_Frontier_worker = 'B12_Frontier_worker'
|
|
93
|
+
B12_AidZone = 'B12_AidZone'
|
|
94
|
+
B12_Other_Social_Services = 'B12_Other_Social_Services'
|
|
95
|
+
B12_Restructuring_difficulties = 'B12_Restructuring_difficulties'
|
|
96
|
+
B12_Start_Job = 'B12_Start_Job'
|
|
97
|
+
B12_CareerMeasure = 'B12_CareerMeasure'
|
|
98
|
+
B12_Employer_recruitment_framework = 'B12_Employer_recruitment_framework'
|
|
99
|
+
B12_Employee_recruitment_framework = 'B12_Employee_recruitment_framework'
|
|
100
|
+
B12_IFICFunctionCode = 'B12_IFICFunctionCode'
|
|
101
|
+
B12_WageCategory = 'B12_WageCategory'
|
|
102
|
+
B12_RizivFrequencyCode = 'B12_RizivFrequencyCode'
|
|
103
|
+
B12_RizivCode = 'B12_RizivCode'
|
|
104
|
+
B12_PercentageCode = 'B12_PercentageCode'
|
|
105
|
+
B12_Employee_type_2 = 'B12_Employee_type_2'
|
|
106
|
+
B12_Statute = 'B12_Statute'
|
|
107
|
+
B12_Employee_type = 'B12_Employee_type'
|
|
108
|
+
B12_Sort = 'B12_Sort'
|
|
109
|
+
ACE_YesNoUnknown = 'ACE_YesNoUnknown'
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class CodeLists:
|
|
113
|
+
"""Resource class for Code Lists endpoints"""
|
|
114
|
+
|
|
115
|
+
def __init__(self, acerta):
|
|
116
|
+
self.acerta: Acerta = acerta
|
|
117
|
+
self.base_uri = "v1"
|
|
118
|
+
self.attributes = CodeListAttribute
|
|
119
|
+
|
|
120
|
+
def get_countries(self) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
121
|
+
"""
|
|
122
|
+
GET /v1/countries - Countries
|
|
123
|
+
|
|
124
|
+
Retrieves the list of all countries with their NIS codes and descriptions.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Tuple[pd.DataFrame, pd.DataFrame]: (valid_df, invalid_df) after validation
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
Exception: If the retrieval fails
|
|
131
|
+
"""
|
|
132
|
+
# Make API request
|
|
133
|
+
response = self.acerta.session.get(
|
|
134
|
+
url=f"{self.acerta.base_url}/employee-data-management/v1/countries",
|
|
135
|
+
timeout=self.acerta.TIMEOUT,
|
|
136
|
+
)
|
|
137
|
+
response.raise_for_status()
|
|
138
|
+
data = response.json()
|
|
139
|
+
|
|
140
|
+
# Normalize data
|
|
141
|
+
if isinstance(data, list):
|
|
142
|
+
df = pd.json_normalize(data)
|
|
143
|
+
else:
|
|
144
|
+
df = pd.DataFrame([data])
|
|
145
|
+
|
|
146
|
+
# Validate with schema
|
|
147
|
+
valid_data, invalid_data = Functions.validate_data(df, CountryGet)
|
|
148
|
+
|
|
149
|
+
return valid_data, invalid_data
|
|
150
|
+
|
|
151
|
+
def get_belgian_cities(self) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
152
|
+
"""
|
|
153
|
+
GET /v1/belgian-cities - Belgian Cities
|
|
154
|
+
|
|
155
|
+
Retrieves the list of all Belgian cities with their postal codes, names, and NIS codes.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Tuple[pd.DataFrame, pd.DataFrame]: (valid_df, invalid_df) after validation
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
Exception: If the retrieval fails
|
|
162
|
+
"""
|
|
163
|
+
# Make API request
|
|
164
|
+
response = self.acerta.session.get(
|
|
165
|
+
url=f"{self.acerta.base_url}/employee-data-management/v1/belgian-cities",
|
|
166
|
+
timeout=self.acerta.TIMEOUT,
|
|
167
|
+
)
|
|
168
|
+
response.raise_for_status()
|
|
169
|
+
data = response.json()
|
|
170
|
+
|
|
171
|
+
# Normalize data
|
|
172
|
+
if isinstance(data, list):
|
|
173
|
+
df = pd.json_normalize(data)
|
|
174
|
+
else:
|
|
175
|
+
df = pd.DataFrame([data])
|
|
176
|
+
|
|
177
|
+
# Validate with schema
|
|
178
|
+
valid_data, invalid_data = Functions.validate_data(df, BelgianCityGet)
|
|
179
|
+
|
|
180
|
+
return valid_data, invalid_data
|
|
181
|
+
|
|
182
|
+
def get_application_translations(self, app_id: str, lang: str, _format: str = "json"):
|
|
183
|
+
"""
|
|
184
|
+
GET /labelcms/v1/applications/{app_id}/translations - Application labels
|
|
185
|
+
|
|
186
|
+
Retrieves application labels/translations for specified languages.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
app_id: Application ID
|
|
190
|
+
lang: Comma separated list of languages (e.g., "EN,NL,FR")
|
|
191
|
+
_format: Request format (default: "json")
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Raw JSON response from API
|
|
195
|
+
|
|
196
|
+
Raises:
|
|
197
|
+
Exception: If the retrieval fails
|
|
198
|
+
"""
|
|
199
|
+
params = {
|
|
200
|
+
"lang": lang,
|
|
201
|
+
"_format": _format
|
|
202
|
+
}
|
|
203
|
+
response = self.acerta.session.get(
|
|
204
|
+
url=f"{self.acerta.base_url}/labelcms/v1/applications/{app_id}/translations",
|
|
205
|
+
params=params,
|
|
206
|
+
timeout=self.acerta.TIMEOUT,
|
|
207
|
+
)
|
|
208
|
+
response.raise_for_status()
|
|
209
|
+
return response.json()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def get_cod_lists(
|
|
213
|
+
self,
|
|
214
|
+
attributes: List[CodeListAttribute],
|
|
215
|
+
lang: str = 'en,nl'
|
|
216
|
+
):
|
|
217
|
+
"""
|
|
218
|
+
GET /labelcms/v1/translations - Attributes collection (COD lists)
|
|
219
|
+
|
|
220
|
+
Retrieves generic COD lists applicable within Acerta (country codes, gender, fuel types, etc.).
|
|
221
|
+
These lists are mainly used to facilitate updates and creations of resources (Employee, Agreement, etc.).
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
attributes: List of COD list enum values or names (e.g., [CodListAttribute.B11_Fuel, CodListAttribute.B11_Gender]).
|
|
225
|
+
lang: Comma separated list of languages (e.g., "en,nl,fr,de")
|
|
226
|
+
_format: Request format (default: "json")
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Raw JSON response from API containing possible values for specified attributes and languages
|
|
230
|
+
|
|
231
|
+
Raises:
|
|
232
|
+
Exception: If the retrieval fails
|
|
233
|
+
|
|
234
|
+
Example:
|
|
235
|
+
response = acerta.planning.get_cod_lists(
|
|
236
|
+
attributes=[CodListAttribute.B11_Fuel, CodListAttribute.B11_Gender],
|
|
237
|
+
lang="en,nl"
|
|
238
|
+
)
|
|
239
|
+
"""
|
|
240
|
+
# Accept string literals as fallback but enforce they match enum
|
|
241
|
+
valid_values = set(item.value for item in attributes)
|
|
242
|
+
attribute_strings = []
|
|
243
|
+
for attribute in attributes:
|
|
244
|
+
if isinstance(attribute, CodeListAttribute):
|
|
245
|
+
attribute_strings.append(attribute.value)
|
|
246
|
+
elif isinstance(attribute, str) and attribute in valid_values:
|
|
247
|
+
attribute_strings.append(attribute)
|
|
248
|
+
else:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
f"Invalid attribute: {attribute}. Possible values: {[a.value for a in attributes]}"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
attribute_string = ",".join(attribute_strings)
|
|
254
|
+
params = {
|
|
255
|
+
"lang": lang,
|
|
256
|
+
"attributes": attribute_string,
|
|
257
|
+
"_format": "json"
|
|
258
|
+
}
|
|
259
|
+
response = requests.get(
|
|
260
|
+
url=f"https://labelcms.acerta.be/en/v1/translations",
|
|
261
|
+
params=params
|
|
262
|
+
)
|
|
263
|
+
response.raise_for_status()
|
|
264
|
+
return response.json()
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from typing import Tuple, Dict, Any, Optional
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import requests
|
|
4
|
+
from brynq_sdk_functions import Functions
|
|
5
|
+
from .schemas.company_car import CompanyCarGet, CompanyCarCreate, CompanyCarUpdate
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .acerta import Acerta
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CompanyCars:
|
|
12
|
+
"""Resource class for Company Cars endpoints"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, acerta):
|
|
15
|
+
self.acerta: Acerta = acerta
|
|
16
|
+
self.base_uri = "employer-data-management/v1/employers"
|
|
17
|
+
|
|
18
|
+
def get(
|
|
19
|
+
self,
|
|
20
|
+
from_date: str = "1900-01-01",
|
|
21
|
+
until_date: str = "9999-12-31",
|
|
22
|
+
license_plate: Optional[str] = None,
|
|
23
|
+
size: int = 20,
|
|
24
|
+
page: int = 0,
|
|
25
|
+
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
26
|
+
"""
|
|
27
|
+
GET /v1/employers/{employerId}/company-cars - Company cars
|
|
28
|
+
|
|
29
|
+
Retrieve company cars for all configured employer IDs within an optional time window
|
|
30
|
+
and optional license plate filter. Supports basic pagination parameters.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
from_date: The lower bound of the time window (default: "1900-01-01")
|
|
34
|
+
until_date: The upper bound of the time window (default: "9999-12-31")
|
|
35
|
+
license_plate: Optional license plate filter (or part of a plate)
|
|
36
|
+
size: Number of items per page (default: 20)
|
|
37
|
+
page: Zero-based page index (default: 0)
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Tuple[pd.DataFrame, pd.DataFrame]: (valid_df, invalid_df) after normalizing and validating
|
|
41
|
+
"""
|
|
42
|
+
all_valid_data = []
|
|
43
|
+
all_invalid_data = []
|
|
44
|
+
for employer_id in self.acerta._employer_ids:
|
|
45
|
+
params = {
|
|
46
|
+
"fromDate": from_date,
|
|
47
|
+
"untilDate": until_date,
|
|
48
|
+
"size": size,
|
|
49
|
+
"page": page,
|
|
50
|
+
}
|
|
51
|
+
if license_plate:
|
|
52
|
+
params["licensePlate"] = license_plate
|
|
53
|
+
response = self.acerta.session.get(
|
|
54
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{employer_id}/company-cars",
|
|
55
|
+
params=params,
|
|
56
|
+
timeout=self.acerta.TIMEOUT,
|
|
57
|
+
)
|
|
58
|
+
response.raise_for_status()
|
|
59
|
+
items = response.json().get("companyCars", [])
|
|
60
|
+
df = pd.json_normalize(items, sep='.')
|
|
61
|
+
valid_data, invalid_data = Functions.validate_data(df, CompanyCarGet)
|
|
62
|
+
all_valid_data.append(valid_data)
|
|
63
|
+
all_invalid_data.append(invalid_data)
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
pd.concat(all_valid_data, ignore_index=True) if all_valid_data else pd.DataFrame(),
|
|
67
|
+
pd.concat(all_invalid_data, ignore_index=True) if all_invalid_data else pd.DataFrame(),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def get_by_id(self, employer_id: str, company_car_id: str) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
71
|
+
"""
|
|
72
|
+
GET /v1/employers/{employerId}/company-cars/{companyCarId} - Company car by ID
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
employer_id: Unique identifier of the employer (Acerta key)
|
|
76
|
+
company_car_id: Unique identifier of the company car (6 digits)
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Tuple[pd.DataFrame, pd.DataFrame]: (valid_df, invalid_df)
|
|
80
|
+
"""
|
|
81
|
+
response = self.acerta.session.get(
|
|
82
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{employer_id}/company-cars/{company_car_id}",
|
|
83
|
+
timeout=self.acerta.TIMEOUT,
|
|
84
|
+
)
|
|
85
|
+
response.raise_for_status()
|
|
86
|
+
raw = response.json()
|
|
87
|
+
if isinstance(raw, dict):
|
|
88
|
+
df = pd.json_normalize(raw, sep='.')
|
|
89
|
+
else:
|
|
90
|
+
df = pd.json_normalize([raw], sep='.')
|
|
91
|
+
return Functions.validate_data(df, CompanyCarGet)
|
|
92
|
+
|
|
93
|
+
def create(self, employer_id: str, data: Dict[str, Any]) -> requests.Response:
|
|
94
|
+
"""
|
|
95
|
+
POST /v1/employers/{employerId}/company-cars - Company car
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
employer_id: Unique identifier of the employer (Acerta key)
|
|
99
|
+
data: Flat dictionary with company car data
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
requests.Response: Raw response object
|
|
103
|
+
"""
|
|
104
|
+
# Convert flat data to nested using Functions.flat_to_nested_with_prefix
|
|
105
|
+
nested = Functions.flat_to_nested_with_prefix(data, CompanyCarCreate)
|
|
106
|
+
validated = CompanyCarCreate(**nested)
|
|
107
|
+
response = self.acerta.session.post(
|
|
108
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{employer_id}/company-cars",
|
|
109
|
+
json=validated.model_dump(by_alias=True, exclude_none=True),
|
|
110
|
+
timeout=self.acerta.TIMEOUT,
|
|
111
|
+
)
|
|
112
|
+
response.raise_for_status()
|
|
113
|
+
return response
|
|
114
|
+
|
|
115
|
+
def update(self, employer_id: str, company_car_id: str, data: Dict[str, Any]) -> requests.Response:
|
|
116
|
+
"""
|
|
117
|
+
PATCH /v1/employers/{employerId}/company-cars/{companyCarId} - Update company car
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
employer_id: Unique identifier of the employer (Acerta key)
|
|
121
|
+
company_car_id: Unique identifier of the company car
|
|
122
|
+
data: Flat dictionary with update fields
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
requests.Response: Raw response object
|
|
126
|
+
"""
|
|
127
|
+
nested = Functions.flat_to_nested_with_prefix(data, CompanyCarUpdate)
|
|
128
|
+
validated = CompanyCarUpdate(**nested)
|
|
129
|
+
response = self.acerta.session.patch(
|
|
130
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{employer_id}/company-cars/{company_car_id}",
|
|
131
|
+
json=validated.model_dump(by_alias=True, exclude_none=True),
|
|
132
|
+
timeout=self.acerta.TIMEOUT,
|
|
133
|
+
)
|
|
134
|
+
response.raise_for_status()
|
|
135
|
+
return response
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from typing import Dict, Any, Tuple
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import requests
|
|
4
|
+
from brynq_sdk_functions import Functions
|
|
5
|
+
from .schemas.contact_information import ContactInformationGet, ContactInformationUpdate
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .acerta import Acerta
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ContactInformation:
|
|
12
|
+
"""Resource class for Employee Contact Information endpoints"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, acerta):
|
|
15
|
+
self.acerta: Acerta = acerta
|
|
16
|
+
self.base_uri = "employee-data-management/v3/employees"
|
|
17
|
+
|
|
18
|
+
def get(self) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
19
|
+
"""
|
|
20
|
+
GET /employee-data-management/v3/employees/{employeeId}/contact-information - Employee Contact Information
|
|
21
|
+
|
|
22
|
+
Retrieves employee contact information for all cached employees including personal contact (phone, mobile, email),
|
|
23
|
+
work contact, and emergency contacts (primary and secondary).
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Tuple[pd.DataFrame, pd.DataFrame]: (valid_df, invalid_df) after normalizing and validating
|
|
29
|
+
"""
|
|
30
|
+
if not self.acerta._employee_ids:
|
|
31
|
+
self.acerta.agreements.get()
|
|
32
|
+
|
|
33
|
+
frames = []
|
|
34
|
+
for employee_id in self.acerta._employee_ids:
|
|
35
|
+
response = self.acerta.session.get(
|
|
36
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{employee_id}/contact-information",
|
|
37
|
+
timeout=self.acerta.TIMEOUT,
|
|
38
|
+
)
|
|
39
|
+
response.raise_for_status()
|
|
40
|
+
df = pd.json_normalize(response.json())
|
|
41
|
+
if 'externalReferences' in df.columns:
|
|
42
|
+
df = df.drop(columns=['externalReferences'])
|
|
43
|
+
frames.append(df)
|
|
44
|
+
|
|
45
|
+
combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
|
|
46
|
+
|
|
47
|
+
valid_df, invalid_df = Functions.validate_data(combined, ContactInformationGet)
|
|
48
|
+
|
|
49
|
+
return valid_df, invalid_df
|
|
50
|
+
|
|
51
|
+
def update(self, employee_id: str, data: Dict[str, Any]) -> requests.Response:
|
|
52
|
+
"""
|
|
53
|
+
PATCH /employee-data-management/v3/employees/{employeeId}/contact-information - Employee Contact Information
|
|
54
|
+
|
|
55
|
+
Update employee contact information including personal contact (phone, mobile, email),
|
|
56
|
+
work contact, and emergency contacts (primary and secondary).
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
employee_id: Unique identifier of an employee
|
|
60
|
+
data: Flat dictionary with contact information data
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
requests.Response: Raw response object
|
|
64
|
+
"""
|
|
65
|
+
# Convert flat data to nested using Functions.flat_to_nested_with_prefix
|
|
66
|
+
nested_data = Functions.flat_to_nested_with_prefix(data, ContactInformationUpdate)
|
|
67
|
+
|
|
68
|
+
# Validate the nested data
|
|
69
|
+
validated_data = ContactInformationUpdate(**nested_data)
|
|
70
|
+
|
|
71
|
+
# Make API request
|
|
72
|
+
response = self.acerta.session.patch(
|
|
73
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{employee_id}/contact-information",
|
|
74
|
+
json=validated_data.model_dump(by_alias=True, exclude_none=True),
|
|
75
|
+
timeout=self.acerta.TIMEOUT,
|
|
76
|
+
)
|
|
77
|
+
response.raise_for_status()
|
|
78
|
+
|
|
79
|
+
return response
|
|
@@ -0,0 +1,94 @@
|
|
|
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.cost_center import CostCenterGet, CostCenterCreate
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .acerta import Acerta
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CostCenters:
|
|
12
|
+
"""Resource class for Cost Centers endpoints"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, acerta):
|
|
15
|
+
self.acerta: Acerta = acerta
|
|
16
|
+
self.base_uri = "employer-data-management/v1/employers"
|
|
17
|
+
|
|
18
|
+
def get(self, from_date: str = "1900-01-01", until_date: str = "9999-12-31",
|
|
19
|
+
size: int = 20, page: int = 0) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
20
|
+
"""
|
|
21
|
+
GET /v1/employers/{employerId}/cost-centers - Cost centers
|
|
22
|
+
|
|
23
|
+
This endpoint returns the cost centers of an employer based on its employerId for a given period.
|
|
24
|
+
An employer can have 0 to N cost centers. Cost centers are primarily used for reporting purposes.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
employer_id: Unique identifier of the employer (Acerta key)
|
|
28
|
+
from_date: The lower bound of the time window (default: "1900-01-01")
|
|
29
|
+
until_date: The upper bound of the time window (default: "9999-12-31")
|
|
30
|
+
size: Number of items to be returned on each page (default: 20)
|
|
31
|
+
page: Zero-based page index (default: 0)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Tuple[pd.DataFrame, pd.DataFrame]: (valid_df, invalid_df) after normalizing and validating
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
Exception: If the retrieval fails
|
|
38
|
+
"""
|
|
39
|
+
all_valid_data = []
|
|
40
|
+
all_invalid_data = []
|
|
41
|
+
for employer_id in self.acerta._employer_ids:
|
|
42
|
+
# Prepare query parameters
|
|
43
|
+
params = {
|
|
44
|
+
"fromDate": from_date,
|
|
45
|
+
"untilDate": until_date,
|
|
46
|
+
"size": size,
|
|
47
|
+
"page": page
|
|
48
|
+
}
|
|
49
|
+
# Make API request
|
|
50
|
+
response = self.acerta.session.get(
|
|
51
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{employer_id}/cost-centers",
|
|
52
|
+
params=params,
|
|
53
|
+
timeout=self.acerta.TIMEOUT,
|
|
54
|
+
)
|
|
55
|
+
response.raise_for_status()
|
|
56
|
+
cost_centers_data = response.json().get("costCenters", [])
|
|
57
|
+
df = pd.json_normalize(cost_centers_data, sep='.')
|
|
58
|
+
valid_data, invalid_data = Functions.validate_data(df, CostCenterGet)
|
|
59
|
+
all_valid_data.append(valid_data)
|
|
60
|
+
all_invalid_data.append(invalid_data)
|
|
61
|
+
|
|
62
|
+
return pd.concat(all_valid_data, ignore_index=True), pd.concat(all_invalid_data, ignore_index=True)
|
|
63
|
+
|
|
64
|
+
def create(self, employer_id: str, data: Dict[str, Any]) -> requests.Response:
|
|
65
|
+
"""
|
|
66
|
+
POST /v1/employers/{employerId}/cost-centers - Cost center
|
|
67
|
+
|
|
68
|
+
This endpoint creates a cost center for an employer.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
employer_id: Unique identifier of the employer (Acerta key)
|
|
72
|
+
data: Flat dictionary with cost center data
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
requests.Response: Raw response object
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
Exception: If the creation fails
|
|
79
|
+
"""
|
|
80
|
+
# Convert flat data to nested using Functions.flat_to_nested_with_prefix
|
|
81
|
+
nested_data = Functions.flat_to_nested_with_prefix(data, CostCenterCreate)
|
|
82
|
+
|
|
83
|
+
# Validate the nested data
|
|
84
|
+
validated_data = CostCenterCreate(**nested_data)
|
|
85
|
+
|
|
86
|
+
# Make API request
|
|
87
|
+
response = self.acerta.session.post(
|
|
88
|
+
url=f"{self.acerta.base_url}/{self.base_uri}/{employer_id}/cost-centers",
|
|
89
|
+
json=validated_data.model_dump(by_alias=True, exclude_none=True),
|
|
90
|
+
timeout=self.acerta.TIMEOUT,
|
|
91
|
+
)
|
|
92
|
+
response.raise_for_status()
|
|
93
|
+
|
|
94
|
+
return response
|