brynq-sdk-nmbrs 2.3.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_nmbrs/__init__.py +226 -0
- brynq_sdk_nmbrs/absence.py +124 -0
- brynq_sdk_nmbrs/address.py +66 -0
- brynq_sdk_nmbrs/bank.py +125 -0
- brynq_sdk_nmbrs/children.py +100 -0
- brynq_sdk_nmbrs/companies.py +93 -0
- brynq_sdk_nmbrs/contract.py +132 -0
- brynq_sdk_nmbrs/costcenter.py +166 -0
- brynq_sdk_nmbrs/costunit.py +90 -0
- brynq_sdk_nmbrs/days.py +137 -0
- brynq_sdk_nmbrs/debtors.py +25 -0
- brynq_sdk_nmbrs/department.py +122 -0
- brynq_sdk_nmbrs/document.py +30 -0
- brynq_sdk_nmbrs/employees.py +196 -0
- brynq_sdk_nmbrs/employment.py +107 -0
- brynq_sdk_nmbrs/function.py +89 -0
- brynq_sdk_nmbrs/hours.py +252 -0
- brynq_sdk_nmbrs/leave.py +139 -0
- brynq_sdk_nmbrs/manager.py +294 -0
- brynq_sdk_nmbrs/salaries.py +85 -0
- brynq_sdk_nmbrs/salary_tables.py +242 -0
- brynq_sdk_nmbrs/schedules.py +84 -0
- brynq_sdk_nmbrs/schemas/__init__.py +37 -0
- brynq_sdk_nmbrs/schemas/absence.py +61 -0
- brynq_sdk_nmbrs/schemas/address.py +76 -0
- brynq_sdk_nmbrs/schemas/bank.py +83 -0
- brynq_sdk_nmbrs/schemas/contracts.py +60 -0
- brynq_sdk_nmbrs/schemas/costcenter.py +91 -0
- brynq_sdk_nmbrs/schemas/costunit.py +40 -0
- brynq_sdk_nmbrs/schemas/days.py +98 -0
- brynq_sdk_nmbrs/schemas/debtor.py +16 -0
- brynq_sdk_nmbrs/schemas/department.py +57 -0
- brynq_sdk_nmbrs/schemas/employees.py +153 -0
- brynq_sdk_nmbrs/schemas/employment.py +48 -0
- brynq_sdk_nmbrs/schemas/function.py +50 -0
- brynq_sdk_nmbrs/schemas/hours.py +121 -0
- brynq_sdk_nmbrs/schemas/leave.py +53 -0
- brynq_sdk_nmbrs/schemas/manager.py +126 -0
- brynq_sdk_nmbrs/schemas/salary.py +92 -0
- brynq_sdk_nmbrs/schemas/schedules.py +96 -0
- brynq_sdk_nmbrs/schemas/social_insurance.py +40 -0
- brynq_sdk_nmbrs/schemas/wage_tax.py +98 -0
- brynq_sdk_nmbrs/schemas/wagecomponents.py +114 -0
- brynq_sdk_nmbrs/social_insurance.py +52 -0
- brynq_sdk_nmbrs/wage_tax.py +164 -0
- brynq_sdk_nmbrs/wagecomponents.py +268 -0
- brynq_sdk_nmbrs-2.3.1.dist-info/METADATA +21 -0
- brynq_sdk_nmbrs-2.3.1.dist-info/RECORD +50 -0
- brynq_sdk_nmbrs-2.3.1.dist-info/WHEEL +5 -0
- brynq_sdk_nmbrs-2.3.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import requests
|
|
4
|
+
from typing import Dict, Any
|
|
5
|
+
from brynq_sdk_functions import Functions
|
|
6
|
+
from .schemas.salary import SalaryGet, SalaryCreate
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Salaries:
|
|
10
|
+
def __init__(self, nmbrs):
|
|
11
|
+
self.nmbrs = nmbrs
|
|
12
|
+
|
|
13
|
+
def get(self,
|
|
14
|
+
created_from: str = None,
|
|
15
|
+
employee_id: str = None) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
16
|
+
salaries = pd.DataFrame()
|
|
17
|
+
for company in self.nmbrs.company_ids:
|
|
18
|
+
salaries = pd.concat([salaries, self._get(company, created_from, employee_id)])
|
|
19
|
+
|
|
20
|
+
valid_salaries, invalid_salaries = Functions.validate_data(df=salaries, schema=SalaryGet, debug=True)
|
|
21
|
+
|
|
22
|
+
return valid_salaries, invalid_salaries
|
|
23
|
+
|
|
24
|
+
def _get(self,
|
|
25
|
+
company_id: str,
|
|
26
|
+
created_from: str = None,
|
|
27
|
+
employee_id: str = None) -> pd.DataFrame:
|
|
28
|
+
params = {}
|
|
29
|
+
if created_from:
|
|
30
|
+
params['createdFrom'] = created_from
|
|
31
|
+
if employee_id:
|
|
32
|
+
params['employeeId'] = employee_id
|
|
33
|
+
request = requests.Request(method='GET',
|
|
34
|
+
url=f"{self.nmbrs.base_url}companies/{company_id}/employees/salaries",
|
|
35
|
+
params=params)
|
|
36
|
+
data = self.nmbrs.get_paginated_result(request)
|
|
37
|
+
df = pd.json_normalize(
|
|
38
|
+
data,
|
|
39
|
+
record_path='salaries',
|
|
40
|
+
meta=['employeeId']
|
|
41
|
+
)
|
|
42
|
+
return df
|
|
43
|
+
|
|
44
|
+
def get_salary_tables(self,
|
|
45
|
+
salary_table_id: str) -> pd.DataFrame:
|
|
46
|
+
params = {}
|
|
47
|
+
request = requests.Request(method='GET',
|
|
48
|
+
url=f"{self.nmbrs.base_url}salarytable/{salary_table_id}",
|
|
49
|
+
params=params)
|
|
50
|
+
data = self.nmbrs.get_paginated_result(request)
|
|
51
|
+
|
|
52
|
+
df = pd.DataFrame(data)
|
|
53
|
+
|
|
54
|
+
return df
|
|
55
|
+
|
|
56
|
+
def create(self, employee_id: str, data: Dict[str, Any]):
|
|
57
|
+
"""
|
|
58
|
+
Create a new salary for an employee using Pydantic validation.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
employee_id: The ID of the employee
|
|
62
|
+
data: Dictionary containing salary data with fields matching
|
|
63
|
+
the SalaryCreate schema (using camelCase field names)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Response from the API
|
|
67
|
+
"""
|
|
68
|
+
# Validate with Pydantic model
|
|
69
|
+
nested_data = self.nmbrs.flat_dict_to_nested_dict(data, SalaryCreate)
|
|
70
|
+
salary_model = SalaryCreate(**nested_data)
|
|
71
|
+
|
|
72
|
+
if self.nmbrs.mock_mode:
|
|
73
|
+
return salary_model
|
|
74
|
+
|
|
75
|
+
# Convert validated model to dict for API payload
|
|
76
|
+
payload = salary_model.model_dump_json(exclude_none=True, by_alias=True)
|
|
77
|
+
|
|
78
|
+
# Send request
|
|
79
|
+
resp = self.nmbrs.session.post(
|
|
80
|
+
url=f"{self.nmbrs.base_url}employees/{employee_id}/salary",
|
|
81
|
+
data=payload,
|
|
82
|
+
headers={"Content-Type": "application/json"},
|
|
83
|
+
timeout=self.nmbrs.timeout
|
|
84
|
+
)
|
|
85
|
+
return resp
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
from typing import Dict, List, Union, Tuple
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from zeep.exceptions import Fault
|
|
4
|
+
from zeep.ns import WSDL, SOAP_ENV_11
|
|
5
|
+
from zeep.xsd import ComplexType, Element, String
|
|
6
|
+
from zeep.helpers import serialize_object
|
|
7
|
+
# import logging
|
|
8
|
+
from brynq_sdk_functions import Functions
|
|
9
|
+
from .schemas.salary import SalaryTableGet, SalaryScalesGet, SalaryStepsGet
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SalaryTables:
|
|
13
|
+
def __init__(self, nmbrs):
|
|
14
|
+
self.nmbrs = nmbrs
|
|
15
|
+
self.soap_client_companies = nmbrs.soap_client_companies
|
|
16
|
+
|
|
17
|
+
def get(self, period: int, year: int) -> (pd.DataFrame, pd.DataFrame):
|
|
18
|
+
"""
|
|
19
|
+
Get salary tables for all companies for a specific period and year.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
period (int): The period number
|
|
23
|
+
year (int): The year
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
pd.DataFrame: DataFrame containing the salary tables
|
|
27
|
+
"""
|
|
28
|
+
salary_tables = pd.DataFrame()
|
|
29
|
+
for company in self.nmbrs.soap_company_ids.to_dict(orient='records'):
|
|
30
|
+
salary_table_temp = self._get(company['i_d'], period, year)
|
|
31
|
+
if not salary_table_temp.empty:
|
|
32
|
+
salary_table_temp['companyId'] = company['number']
|
|
33
|
+
salary_tables = pd.concat([salary_tables, salary_table_temp])
|
|
34
|
+
|
|
35
|
+
valid_salary_tables, invalid_salary_tables = Functions.validate_data(df=salary_tables, schema=SalaryTableGet, debug=True)
|
|
36
|
+
|
|
37
|
+
# No validation schema for now, but could be added later
|
|
38
|
+
return valid_salary_tables, invalid_salary_tables
|
|
39
|
+
|
|
40
|
+
def _get(self, company_id: int, period: int, year: int) -> pd.DataFrame:
|
|
41
|
+
"""
|
|
42
|
+
Get salary tables for a specific company, period and year.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
company_id (int): The ID of the company
|
|
46
|
+
period (int): The period number
|
|
47
|
+
year (int): The year
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
pd.DataFrame: DataFrame containing the salary tables
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
# Make SOAP request with the proper header structure
|
|
54
|
+
response = self.soap_client_companies.service.SalaryTable2_Get(
|
|
55
|
+
CompanyId=company_id,
|
|
56
|
+
Period=period,
|
|
57
|
+
Year=year,
|
|
58
|
+
_soapheaders=[self.nmbrs.soap_auth_header]
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Convert response to DataFrame
|
|
62
|
+
if response:
|
|
63
|
+
# Convert Zeep objects to Python dictionaries
|
|
64
|
+
serialized_response = serialize_object(response)
|
|
65
|
+
|
|
66
|
+
# Convert to list if it's not already
|
|
67
|
+
if not isinstance(serialized_response, list):
|
|
68
|
+
serialized_response = [serialized_response]
|
|
69
|
+
|
|
70
|
+
# Convert to DataFrame
|
|
71
|
+
df = pd.DataFrame(serialized_response)
|
|
72
|
+
|
|
73
|
+
return df
|
|
74
|
+
else:
|
|
75
|
+
return pd.DataFrame()
|
|
76
|
+
|
|
77
|
+
except Fault as e:
|
|
78
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
|
79
|
+
except Exception as e:
|
|
80
|
+
raise Exception(f"Failed to get salary tables: {str(e)}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class SalaryScales:
|
|
84
|
+
def __init__(self, nmbrs):
|
|
85
|
+
self.nmbrs = nmbrs
|
|
86
|
+
self.soap_client_companies = nmbrs.soap_client_companies
|
|
87
|
+
|
|
88
|
+
def get(self, period: int, year: int) -> (pd.DataFrame, pd.DataFrame):
|
|
89
|
+
"""
|
|
90
|
+
Get salary scales for all companies for a specific period and year.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
period (int): The period number
|
|
94
|
+
year (int): The year
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
pd.DataFrame: DataFrame containing the salary scales
|
|
98
|
+
"""
|
|
99
|
+
salary_scales = pd.DataFrame()
|
|
100
|
+
for company in self.nmbrs.soap_company_ids.to_dict(orient='records'):
|
|
101
|
+
salary_scale_temp = self._get(company['i_d'], period, year)
|
|
102
|
+
if not salary_scale_temp.empty:
|
|
103
|
+
salary_scale_temp['companyId'] = company['number']
|
|
104
|
+
salary_scales = pd.concat([salary_scales, salary_scale_temp])
|
|
105
|
+
|
|
106
|
+
valid_salary_scales, invalid_salary_scales = Functions.validate_data(df=salary_scales, schema=SalaryScalesGet, debug=True)
|
|
107
|
+
|
|
108
|
+
return valid_salary_scales, invalid_salary_scales
|
|
109
|
+
|
|
110
|
+
def _get(self, company_id: int, period: int, year: int) -> pd.DataFrame:
|
|
111
|
+
"""
|
|
112
|
+
Get salary scales for a specific company, period and year.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
company_id (int): The ID of the company
|
|
116
|
+
period (int): The period number
|
|
117
|
+
year (int): The year
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
pd.DataFrame: DataFrame containing the salary scales
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
# Make SOAP request with the proper header structure
|
|
124
|
+
response = self.soap_client_companies.service.SalaryTable2_GetScales(
|
|
125
|
+
CompanyId=company_id,
|
|
126
|
+
Period=period,
|
|
127
|
+
Year=year,
|
|
128
|
+
_soapheaders=[self.nmbrs.soap_auth_header]
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Convert response to DataFrame
|
|
132
|
+
if response:
|
|
133
|
+
# Convert Zeep objects to Python dictionaries
|
|
134
|
+
serialized_response = serialize_object(response)
|
|
135
|
+
|
|
136
|
+
# Convert to list if it's not already
|
|
137
|
+
if not isinstance(serialized_response, list):
|
|
138
|
+
serialized_response = [serialized_response]
|
|
139
|
+
|
|
140
|
+
# Convert to DataFrame
|
|
141
|
+
df = pd.DataFrame(serialized_response)
|
|
142
|
+
|
|
143
|
+
return df
|
|
144
|
+
else:
|
|
145
|
+
return pd.DataFrame()
|
|
146
|
+
|
|
147
|
+
except Fault as e:
|
|
148
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
|
149
|
+
except Exception as e:
|
|
150
|
+
raise Exception(f"Failed to get salary scales: {str(e)}")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class SalarySteps:
|
|
154
|
+
def __init__(self, nmbrs):
|
|
155
|
+
self.nmbrs = nmbrs
|
|
156
|
+
self.soap_client_companies = nmbrs.soap_client_companies
|
|
157
|
+
|
|
158
|
+
def get(self, period: int, year: int, scale: dict) -> (pd.DataFrame, pd.DataFrame):
|
|
159
|
+
"""
|
|
160
|
+
Get salary steps for all companies for a specific period, year and scale.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
period (int): The period number
|
|
164
|
+
year (int): The year
|
|
165
|
+
scale (dict): Dictionary containing scale information with keys:
|
|
166
|
+
- scale (str): The scale identifier (e.g., "Swapper")
|
|
167
|
+
- schaal_description (str): Description of the scale
|
|
168
|
+
- scale_value (float): The scale value
|
|
169
|
+
- scale_percentage_max (float): Maximum percentage
|
|
170
|
+
- scale_percentage_min (float): Minimum percentage
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
pd.DataFrame: DataFrame containing the salary steps
|
|
174
|
+
"""
|
|
175
|
+
salary_steps = pd.DataFrame()
|
|
176
|
+
for company in self.nmbrs.soap_company_ids.to_dict(orient='records'):
|
|
177
|
+
salary_steps_temp = self._get(company['i_d'], period, year, scale)
|
|
178
|
+
if not salary_steps_temp.empty:
|
|
179
|
+
salary_steps_temp['companyId'] = company['number']
|
|
180
|
+
salary_steps = pd.concat([salary_steps, salary_steps_temp])
|
|
181
|
+
|
|
182
|
+
valid_salary_steps, invalid_salary_steps = Functions.validate_data(df=salary_steps, schema=SalaryStepsGet, debug=True)
|
|
183
|
+
|
|
184
|
+
return valid_salary_steps, invalid_salary_steps
|
|
185
|
+
|
|
186
|
+
def _get(self, company_id: int, period: int, year: int, scale: dict) -> pd.DataFrame:
|
|
187
|
+
"""
|
|
188
|
+
Get salary steps for a specific company, period, year and scale.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
company_id (int): The ID of the company
|
|
192
|
+
period (int): The period number
|
|
193
|
+
year (int): The year
|
|
194
|
+
scale (dict): Dictionary containing scale information with keys:
|
|
195
|
+
- scale (str): The scale identifier (e.g., "Swapper")
|
|
196
|
+
- schaal_description (str): Description of the scale
|
|
197
|
+
- scale_value (float): The scale value
|
|
198
|
+
- scale_percentage_max (float): Maximum percentage
|
|
199
|
+
- scale_percentage_min (float): Minimum percentage
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
pd.DataFrame: DataFrame containing the salary steps
|
|
203
|
+
"""
|
|
204
|
+
try:
|
|
205
|
+
# Make SOAP request with the proper header structure
|
|
206
|
+
response = self.soap_client_companies.service.SalaryTable2_GetSteps(
|
|
207
|
+
CompanyId=company_id,
|
|
208
|
+
Period=period,
|
|
209
|
+
Year=year,
|
|
210
|
+
Scale={
|
|
211
|
+
'Scale': scale.get('scale', ''),
|
|
212
|
+
'SchaalDescription': scale.get('schaal_description', ''),
|
|
213
|
+
'ScaleValue': scale.get('scale_value', 0),
|
|
214
|
+
'ScalePercentageMax': scale.get('scale_percentage_max', 0),
|
|
215
|
+
'ScalePercentageMin': scale.get('scale_percentage_min', 0)
|
|
216
|
+
},
|
|
217
|
+
_soapheaders=[self.nmbrs.soap_auth_header]
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Convert response to DataFrame
|
|
221
|
+
if response:
|
|
222
|
+
# Convert Zeep objects to Python dictionaries
|
|
223
|
+
serialized_response = serialize_object(response)
|
|
224
|
+
|
|
225
|
+
# Convert to list if it's not already
|
|
226
|
+
if not isinstance(serialized_response, list):
|
|
227
|
+
serialized_response = [serialized_response]
|
|
228
|
+
|
|
229
|
+
# Convert to DataFrame
|
|
230
|
+
df = pd.DataFrame(serialized_response)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
return df
|
|
234
|
+
else:
|
|
235
|
+
return pd.DataFrame()
|
|
236
|
+
|
|
237
|
+
except Fault as e:
|
|
238
|
+
# logger.exception(f"SOAP Fault: {str(e)}")
|
|
239
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
|
240
|
+
except Exception as e:
|
|
241
|
+
# logger.exception("Exception in SalarySteps.get:")
|
|
242
|
+
raise Exception(f"Failed to get salary steps: {str(e)}")
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import requests
|
|
3
|
+
import math
|
|
4
|
+
from brynq_sdk_functions import Functions
|
|
5
|
+
from .schemas.schedules import ScheduleGet, ScheduleCreate
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Dict, Any, Optional, Tuple
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Schedule:
|
|
11
|
+
def __init__(self, nmbrs):
|
|
12
|
+
self.nmbrs = nmbrs
|
|
13
|
+
|
|
14
|
+
def get(self,
|
|
15
|
+
created_from: str = None,
|
|
16
|
+
employee_id: str = None) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
17
|
+
schedules = pd.DataFrame()
|
|
18
|
+
for company in self.nmbrs.company_ids:
|
|
19
|
+
schedules = pd.concat([schedules, self._get(company, created_from, employee_id)])
|
|
20
|
+
|
|
21
|
+
valid_schedules, invalid_schedules = Functions.validate_data(df=schedules, schema=ScheduleGet, debug=True)
|
|
22
|
+
|
|
23
|
+
return valid_schedules, invalid_schedules
|
|
24
|
+
|
|
25
|
+
def _get(self,
|
|
26
|
+
company_id: str,
|
|
27
|
+
created_from: str = None,
|
|
28
|
+
employee_id: str = None) -> pd.DataFrame:
|
|
29
|
+
params = {}
|
|
30
|
+
if created_from:
|
|
31
|
+
params['createdFrom'] = created_from
|
|
32
|
+
if employee_id:
|
|
33
|
+
params['employeeId'] = employee_id
|
|
34
|
+
|
|
35
|
+
request = requests.Request(method='GET',
|
|
36
|
+
url=f"{self.nmbrs.base_url}companies/{company_id}/employees/schedules",
|
|
37
|
+
params=params)
|
|
38
|
+
|
|
39
|
+
data = self.nmbrs.get_paginated_result(request)
|
|
40
|
+
df = pd.json_normalize(
|
|
41
|
+
data,
|
|
42
|
+
record_path='schedules',
|
|
43
|
+
meta=['employeeId']
|
|
44
|
+
)
|
|
45
|
+
return df
|
|
46
|
+
|
|
47
|
+
def create(self,
|
|
48
|
+
employee_id: str,
|
|
49
|
+
data: Dict[str, Any]):
|
|
50
|
+
"""
|
|
51
|
+
Create a new schedule for an employee using Pydantic validation
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
employee_id: The employee ID
|
|
55
|
+
data: Schedule data dictionary with the following keys:
|
|
56
|
+
- start_date_schedule: Start date of the schedule
|
|
57
|
+
- weekly_hours: Hours per week (optional)
|
|
58
|
+
- hours_monday, hours_tuesday, etc.: Hours for each day
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Response from the API
|
|
62
|
+
"""
|
|
63
|
+
# Validate with Pydantic schema
|
|
64
|
+
try:
|
|
65
|
+
nested_data = self.nmbrs.flat_dict_to_nested_dict(data, ScheduleCreate)
|
|
66
|
+
validated_data = ScheduleCreate(**nested_data)
|
|
67
|
+
|
|
68
|
+
if self.nmbrs.mock_mode:
|
|
69
|
+
return validated_data
|
|
70
|
+
|
|
71
|
+
# Convert validated model to dict for API payload
|
|
72
|
+
payload = validated_data.model_dump_json(exclude_none=True, by_alias=True)
|
|
73
|
+
|
|
74
|
+
# Use the validated data for the API call
|
|
75
|
+
resp = self.nmbrs.session.post(
|
|
76
|
+
url=f"{self.nmbrs.base_url}employees/{employee_id}/schedule",
|
|
77
|
+
data=payload,
|
|
78
|
+
timeout=self.nmbrs.timeout,
|
|
79
|
+
headers={'Content-Type': 'application/json'}
|
|
80
|
+
)
|
|
81
|
+
return resp
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
raise ValueError(f"Schedule validation failed: {str(e)}")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Schema definitions for Nmbrs package"""
|
|
2
|
+
|
|
3
|
+
DATEFORMAT = '%Y%m%d'
|
|
4
|
+
|
|
5
|
+
from .address import AddressGet, AddressCreate
|
|
6
|
+
from .bank import BankGet, BankCreate, BankUpdate, BankDelete
|
|
7
|
+
from .contracts import ContractGet, ContractCreate, ContractUpdate, ContractDelete
|
|
8
|
+
from .department import EmployeeDepartmentGet, DepartmentCreate, EmployeeDepartmentUpdate, DepartmentGet
|
|
9
|
+
from .employees import EmployeeGet, EmployeeCreate, EmployeeUpdate, EmployeeDelete
|
|
10
|
+
from .employment import EmploymentGet, EmploymentCreate, EmploymentUpdate, EmploymentDelete
|
|
11
|
+
from .function import FunctionGet, FunctionUpdate
|
|
12
|
+
from .hours import FixedHoursGet, FixedHoursCreate, FixedHoursUpdate, HoursDelete, VariableHoursGet, VariableHoursCreate, VariableHoursUpdate
|
|
13
|
+
from .salary import SalaryGet, SalaryCreate
|
|
14
|
+
from .manager import ManagerGet, ManagerBasicGet, EmployeeManagerGet, ManagerHistoricBasicGet, ManagerCreate, ManagerUpdate, ManagerDelete, UpdateEmployeeManager
|
|
15
|
+
from .salary import SalaryGet, SalaryCreate
|
|
16
|
+
from .wagecomponents import FixedWageComponentGet, FixedWageComponentCreate, FixedWageComponentUpdate, WageComponentDelete, VariableWageComponentGet, VariableWageComponentCreate, VariableWageComponentUpdate
|
|
17
|
+
from .wage_tax import WageTaxGet, WageTaxUpdate
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
'DATEFORMAT',
|
|
22
|
+
'AddressGet', 'AddressCreate',
|
|
23
|
+
'BankGet', 'BankCreate', 'BankUpdate', 'BankDelete',
|
|
24
|
+
'ContractGet', 'ContractCreate', 'ContractUpdate', 'ContractDelete',
|
|
25
|
+
'EmployeeDepartmentGet', 'DepartmentCreate', 'EmployeeDepartmentUpdate', 'DepartmentGet',
|
|
26
|
+
'EmployeeGet', 'EmployeeCreate', 'EmployeeUpdate', 'EmployeeDelete',
|
|
27
|
+
'EmploymentGet', 'EmploymentCreate', 'EmploymentUpdate', 'EmploymentDelete',
|
|
28
|
+
'FunctionGet', 'FunctionUpdate',
|
|
29
|
+
'FixedHoursGet', 'FixedHoursCreate', 'FixedHoursUpdate', 'HoursDelete',
|
|
30
|
+
'VariableHoursGet', 'VariableHoursCreate', 'VariableHoursUpdate',
|
|
31
|
+
'ManagerGet', 'ManagerBasicGet', 'EmployeeManagerGet', 'ManagerHistoricBasicGet',
|
|
32
|
+
'ManagerCreate', 'ManagerUpdate', 'ManagerDelete', 'UpdateEmployeeManager',
|
|
33
|
+
'SalaryGet', 'SalaryCreate',
|
|
34
|
+
'FixedWageComponentGet', 'FixedWageComponentCreate', 'FixedWageComponentUpdate', 'WageComponentDelete',
|
|
35
|
+
'VariableWageComponentGet', 'VariableWageComponentCreate', 'VariableWageComponentUpdate',
|
|
36
|
+
'WageTaxGet', 'WageTaxUpdate'
|
|
37
|
+
]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import pandera as pa
|
|
4
|
+
from pandera import Bool
|
|
5
|
+
from pandera.typing import Series, String, Float, DateTime
|
|
6
|
+
import pandera.extensions as extensions
|
|
7
|
+
from brynq_sdk_functions import BrynQPanderaDataFrameModel
|
|
8
|
+
from typing import Optional, Annotated
|
|
9
|
+
from pydantic import BaseModel, Field, StringConstraints
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
#<AbsenceId>int</AbsenceId>
|
|
13
|
+
# <Comment>string</Comment>
|
|
14
|
+
# <Percentage>int</Percentage>
|
|
15
|
+
# <Start>dateTime</Start>
|
|
16
|
+
# <RegistrationStartDate>dateTime</RegistrationStartDate>
|
|
17
|
+
# <End>dateTime</End>
|
|
18
|
+
# <RegistrationEndDate>dateTime</RegistrationEndDate>
|
|
19
|
+
# <Dossier>string</Dossier>
|
|
20
|
+
# <Dossiernr>int</Dossiernr>
|
|
21
|
+
|
|
22
|
+
class AbsenceGet(BrynQPanderaDataFrameModel):
|
|
23
|
+
employee_id: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Employee ID", alias="EmployeeId")
|
|
24
|
+
absence_id: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Absence ID", alias="AbsenceId")
|
|
25
|
+
comment: Series[String] = pa.Field(coerce=True, description="Comment", alias="Comment")
|
|
26
|
+
percentage: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Percentage", alias="Percentage")
|
|
27
|
+
start: Series[DateTime] = pa.Field(coerce=True, description="Start", alias="Start")
|
|
28
|
+
registration_start_date: Series[DateTime] = pa.Field(coerce=True, description="Registration Start Date", alias="RegistrationStartDate")
|
|
29
|
+
end: Series[DateTime] = pa.Field(coerce=True, description="End", alias="End")
|
|
30
|
+
registration_end_date: Series[DateTime] = pa.Field(coerce=True, description="Registration End Date", alias="RegistrationEndDate")
|
|
31
|
+
dossier: Series[String] = pa.Field(coerce=True, description="Dossier", alias="Dossier")
|
|
32
|
+
dossiernr: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Dossier Number", alias="Dossiernr")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AbsenceCreate(BaseModel):
|
|
36
|
+
employee_id: Optional[int] = Field(None, example="1234567890", description="Employee ID", alias="EmployeeId")
|
|
37
|
+
absence_id: int = Field(coerce=True, description="Absence ID", alias="AbsenceId")
|
|
38
|
+
comment: str = Field(coerce=True, description="Comment", alias="Comment")
|
|
39
|
+
percentage: int = Field(coerce=True, description="Percentage", alias="Percentage")
|
|
40
|
+
start: datetime = Field(coerce=True, description="Start", alias="Start")
|
|
41
|
+
registration_start_date: datetime = Field(coerce=True, description="Registration Start Date", alias="RegistrationStartDate")
|
|
42
|
+
end: datetime = Field(coerce=True, description="End", alias="End")
|
|
43
|
+
registration_end_date: datetime = Field(coerce=True, description="Registration End Date", alias="RegistrationEndDate")
|
|
44
|
+
dossier: str = Field(coerce=True, description="Dossier", alias="Dossier")
|
|
45
|
+
dossiernr: int = Field(coerce=True, description="Dossier Number", alias="Dossiernr")
|
|
46
|
+
new_dossier: bool = pa.Field(coerce=True, description="New Dossier", alias="NewDossier")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def to_soap_settings(self, soap_client):
|
|
50
|
+
"""Convert to SOAP Absence object"""
|
|
51
|
+
AbsenceType = soap_client.get_type(
|
|
52
|
+
'{https://api.nmbrs.nl/soap/v3/EmployeeService}Absence'
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Get payload with alias renaming, excluding employee_id field
|
|
56
|
+
payload = self.model_dump(exclude_none=True, by_alias=True, exclude={'employee_id', 'new_dossier'})
|
|
57
|
+
|
|
58
|
+
return AbsenceType(**payload)
|
|
59
|
+
|
|
60
|
+
class Config:
|
|
61
|
+
populate_by_name = True
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import pandera as pa
|
|
3
|
+
from pandera import Bool
|
|
4
|
+
from pandera.typing import Series, String, Float, DateTime
|
|
5
|
+
import pandera.extensions as extensions
|
|
6
|
+
from brynq_sdk_functions import BrynQPanderaDataFrameModel
|
|
7
|
+
from typing import Optional, Annotated
|
|
8
|
+
from pydantic import BaseModel, Field, StringConstraints
|
|
9
|
+
|
|
10
|
+
# ---------------------------
|
|
11
|
+
# Get Schemas
|
|
12
|
+
# ---------------------------
|
|
13
|
+
class AddressGet(BrynQPanderaDataFrameModel):
|
|
14
|
+
employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="employeeId")
|
|
15
|
+
address_id: Series[String] = pa.Field(coerce=True, description="Address ID", alias="addressId")
|
|
16
|
+
is_default: Series[Bool] = pa.Field(coerce=True, description="Default Address", alias="isDefault")
|
|
17
|
+
type: Series[String] = pa.Field(
|
|
18
|
+
coerce=True,
|
|
19
|
+
isin=["homeAddress", "postAddress", "absenceAddress", "holidaysAddress", "weekendAddress", "workAddress"],
|
|
20
|
+
description="Address Type",
|
|
21
|
+
alias="type"
|
|
22
|
+
)
|
|
23
|
+
street: Series[String] = pa.Field(coerce=True, description="Street", alias="street")
|
|
24
|
+
house_number: Series[String] = pa.Field(coerce=True, nullable=True, description="House Number", alias="houseNumber")
|
|
25
|
+
house_number_addition: Series[String] = pa.Field(coerce=True, nullable=True, description="House Number Addition", alias="houseNumberAddition")
|
|
26
|
+
postal_code: Series[String] = pa.Field(coerce=True, description="Postal Code", alias="postalCode")
|
|
27
|
+
city: Series[String] = pa.Field(coerce=True, description="City", alias="city")
|
|
28
|
+
state_province: Series[String] = pa.Field(coerce=True, nullable=True, description="State or Province", alias="stateProvince")
|
|
29
|
+
country_i_s_o_code: Series[String] = pa.Field(coerce=True, nullable=True, description="Country ISO code", alias="countryISOCode")
|
|
30
|
+
period_year: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=True, description="Year", alias="period.year")
|
|
31
|
+
period_period: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=True, description="Period", alias="period.period")
|
|
32
|
+
|
|
33
|
+
class _Annotation:
|
|
34
|
+
primary_key = "address_id"
|
|
35
|
+
foreign_keys = {
|
|
36
|
+
"employee_id": {
|
|
37
|
+
"parent_schema": "EmployeeSchema",
|
|
38
|
+
"parent_column": "employee_id",
|
|
39
|
+
"cardinality": "1:1"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# ---------------------------
|
|
44
|
+
# Upload Schemas
|
|
45
|
+
# ---------------------------
|
|
46
|
+
|
|
47
|
+
class Period(BaseModel):
|
|
48
|
+
year: int = Field(..., ge=1900, le=2100, example=2021, description="Year", alias="year")
|
|
49
|
+
period: int = Field(..., ge=1, le=53, example=4, description="Period", alias="period")
|
|
50
|
+
|
|
51
|
+
class AddressCreate(BaseModel):
|
|
52
|
+
employee_id: str = Field(..., description="Employee identifier", alias="employeeId")
|
|
53
|
+
is_default: Optional[bool] = Field(None, description="Default Address", alias="isDefault")
|
|
54
|
+
type: Annotated[
|
|
55
|
+
str,
|
|
56
|
+
StringConstraints(
|
|
57
|
+
pattern=r'^(homeAddress|postAddress|absenceAddress|holidaysAddress|weekendAddress|workAddress)$',
|
|
58
|
+
strip_whitespace=True
|
|
59
|
+
)
|
|
60
|
+
] = Field(..., example="homeAddress")
|
|
61
|
+
street: str = Field(..., min_length=1, max_length=200, example="Naritaweg", description="Street", alias="street")
|
|
62
|
+
house_number: Optional[str] = Field(None, max_length=20, example="70", description="House Number", alias="houseNumber")
|
|
63
|
+
house_number_addition: Optional[str] = Field(None, max_length=20, example="A", description="House Number Addition", alias="houseNumberAddition")
|
|
64
|
+
postal_code: Optional[str] = Field(None, max_length=15, example="1043BZ", description="Postal Code", alias="postalCode")
|
|
65
|
+
city: str = Field(..., min_length=1, max_length=100, example="Amsterdam", description="City", alias="city")
|
|
66
|
+
state_province: Optional[str] = Field(None, max_length=100, example="Noord-Holland", description="State or Province", alias="stateProvince")
|
|
67
|
+
country_code: Annotated[
|
|
68
|
+
str,
|
|
69
|
+
StringConstraints(
|
|
70
|
+
pattern=r'^[A-Za-z]+$',
|
|
71
|
+
strip_whitespace=True,
|
|
72
|
+
min_length=2,
|
|
73
|
+
max_length=3
|
|
74
|
+
)
|
|
75
|
+
] = Field(..., example="NL", description="Country ISO Code", alias="countryISOCode")
|
|
76
|
+
period: Period
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import pandera as pa
|
|
3
|
+
from pandera.typing import Series, String, Float, DateTime
|
|
4
|
+
import pandera.extensions as extensions
|
|
5
|
+
from brynq_sdk_functions import BrynQPanderaDataFrameModel
|
|
6
|
+
from typing import Optional, Annotated
|
|
7
|
+
from pydantic import BaseModel, Field, StringConstraints
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
# ---------------------------
|
|
11
|
+
# Enums
|
|
12
|
+
# ---------------------------
|
|
13
|
+
class BankAccountType(str, Enum):
|
|
14
|
+
BANK_ACCOUNT_1 = "bankAccount1"
|
|
15
|
+
BANK_ACCOUNT_2 = "bankAccount2"
|
|
16
|
+
BANK_ACCOUNT_3 = "bankAccount3"
|
|
17
|
+
BANK_ACCOUNT_4 = "bankAccount4"
|
|
18
|
+
BANK_ACCOUNT_5 = "bankAccount5"
|
|
19
|
+
SALARY_SAVINGS = "salarySavings"
|
|
20
|
+
LIFECYCLE_SAVING_SCHEMES = "lifecycleSavingSchemes"
|
|
21
|
+
STANDARD = "standard"
|
|
22
|
+
|
|
23
|
+
# ---------------------------
|
|
24
|
+
# Get Schemas
|
|
25
|
+
# ---------------------------
|
|
26
|
+
class BankGet(BrynQPanderaDataFrameModel):
|
|
27
|
+
employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="employeeId")
|
|
28
|
+
bank_account_id: Series[String] = pa.Field(coerce=True, nullable=True, description="Bank Account ID", alias="bankAccountId")
|
|
29
|
+
number: Series[String] = pa.Field(coerce=True, nullable=True, description="Bank Account Number", alias="number")
|
|
30
|
+
description: Series[String] = pa.Field(coerce=True, nullable=True, description="Bank Account Description", alias="description")
|
|
31
|
+
iban: Series[String] = pa.Field(coerce=True, nullable=True, description="IBAN", alias="IBAN")
|
|
32
|
+
city: Series[String] = pa.Field(coerce=True, nullable=True, description="City Bank", alias="city")
|
|
33
|
+
name: Series[String] = pa.Field(coerce=True, nullable=True, description="Name Bank", alias="name")
|
|
34
|
+
bank_account_type: Series[String] = pa.Field(
|
|
35
|
+
coerce=True,
|
|
36
|
+
isin=BankAccountType,
|
|
37
|
+
description="Bank Account Type",
|
|
38
|
+
alias="bankAccountType"
|
|
39
|
+
)
|
|
40
|
+
created_at: Series[DateTime] = pa.Field(coerce=True, nullable=True, description="Bank Account Created At", alias="createdAt")
|
|
41
|
+
|
|
42
|
+
class _Annotation:
|
|
43
|
+
primary_key = "bank_account_id"
|
|
44
|
+
foreign_keys = {
|
|
45
|
+
"employee_id": {
|
|
46
|
+
"parent_schema": "EmployeeSchema",
|
|
47
|
+
"parent_column": "employee_id",
|
|
48
|
+
"cardinality": "1:1"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ---------------------------
|
|
53
|
+
# Upload Schemas
|
|
54
|
+
# ---------------------------
|
|
55
|
+
class BankCreate(BaseModel):
|
|
56
|
+
employee_id: str = Field(..., description="Employee identifier", alias="employeeId")
|
|
57
|
+
number: Optional[str] = Field(None, max_length=34, example="123456789", description="Bank Account Number", alias="number")
|
|
58
|
+
description: Optional[str] = Field(None, max_length=100, example="Salary Bank", description="Bank Account Description", alias="description")
|
|
59
|
+
iban: str = Field(..., min_length=5, max_length=34, example="NL20INGB0001234567", description="IBAN", alias="IBAN")
|
|
60
|
+
city: Optional[str] = Field(None, max_length=100, example="Amsterdam", description="City Bank", alias="city")
|
|
61
|
+
name: Optional[str] = Field(None, max_length=100, example="ING Bank", description="Name Bank", alias="name")
|
|
62
|
+
bank_account_type: Optional[BankAccountType] = Field(None, example=BankAccountType.BANK_ACCOUNT_1.value, description="Bank Account Type", alias="bankAccountType")
|
|
63
|
+
|
|
64
|
+
class BankUpdate(BaseModel):
|
|
65
|
+
employee_id: str = Field(..., description="Employee identifier", alias="employeeId")
|
|
66
|
+
bank_account_id: str = Field(..., example="49a69eda-252e-4ccb-a220-38ea90511d4f", description="Bank Account ID", alias="bankAccountId")
|
|
67
|
+
number: Optional[str] = Field(None, max_length=34, example="123456789", description="Bank Account Number", alias="number")
|
|
68
|
+
description: Optional[str] = Field(None, max_length=100, example="Main Checking Account", description="Bank Account Description", alias="description")
|
|
69
|
+
iban: Optional[str] = Field(None, min_length=5, max_length=34, example="NL20INGB0001234567", description="IBAN", alias="IBAN")
|
|
70
|
+
city: Optional[str] = Field(None, max_length=100, example="Rotterdam", description="City Bank", alias="city")
|
|
71
|
+
name: Optional[str] = Field(None, max_length=100, example="ABN AMRO", description="Name Bank", alias="name")
|
|
72
|
+
country_code: Optional[Annotated[
|
|
73
|
+
str,
|
|
74
|
+
StringConstraints(
|
|
75
|
+
pattern=r'^[A-Za-z]+$',
|
|
76
|
+
strip_whitespace=True,
|
|
77
|
+
min_length=2,
|
|
78
|
+
max_length=3
|
|
79
|
+
)
|
|
80
|
+
]] = Field(None, example="NL", description="Country Code Bank", alias="countryCode")
|
|
81
|
+
|
|
82
|
+
class BankDelete(BaseModel):
|
|
83
|
+
bank_account_id: str = Field(..., example="49a69eda-252e-4ccb-a220-38ea90511d4f", description="Bank Account ID")
|