brynq-sdk-nmbrs 2.3.1__py3-none-any.whl → 2.3.2.dev0__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 +104 -84
- brynq_sdk_nmbrs/address.py +82 -2
- brynq_sdk_nmbrs/children.py +96 -61
- brynq_sdk_nmbrs/companies.py +45 -1
- brynq_sdk_nmbrs/costcenter.py +53 -8
- brynq_sdk_nmbrs/costunit.py +16 -4
- brynq_sdk_nmbrs/debtors.py +76 -2
- brynq_sdk_nmbrs/department.py +149 -28
- brynq_sdk_nmbrs/document.py +50 -0
- brynq_sdk_nmbrs/employee_wage_tax_settings.py +113 -0
- brynq_sdk_nmbrs/employees.py +119 -24
- brynq_sdk_nmbrs/employment.py +12 -4
- brynq_sdk_nmbrs/function.py +128 -2
- brynq_sdk_nmbrs/leave.py +105 -8
- brynq_sdk_nmbrs/salaries.py +78 -3
- brynq_sdk_nmbrs/schedules.py +77 -3
- brynq_sdk_nmbrs/schemas/address.py +30 -5
- brynq_sdk_nmbrs/schemas/bank.py +0 -2
- brynq_sdk_nmbrs/schemas/children.py +67 -0
- brynq_sdk_nmbrs/schemas/company.py +16 -0
- brynq_sdk_nmbrs/schemas/contracts.py +25 -11
- brynq_sdk_nmbrs/schemas/costcenter.py +57 -18
- brynq_sdk_nmbrs/schemas/costunit.py +0 -2
- brynq_sdk_nmbrs/schemas/days.py +0 -2
- brynq_sdk_nmbrs/schemas/debtor.py +23 -1
- brynq_sdk_nmbrs/schemas/department.py +41 -7
- brynq_sdk_nmbrs/schemas/document.py +13 -0
- brynq_sdk_nmbrs/schemas/employees.py +44 -38
- brynq_sdk_nmbrs/schemas/employment.py +10 -10
- brynq_sdk_nmbrs/schemas/function.py +34 -7
- brynq_sdk_nmbrs/schemas/hours.py +0 -4
- brynq_sdk_nmbrs/schemas/leave.py +12 -1
- brynq_sdk_nmbrs/schemas/manager.py +0 -3
- brynq_sdk_nmbrs/schemas/salary.py +37 -12
- brynq_sdk_nmbrs/schemas/schedules.py +49 -1
- brynq_sdk_nmbrs/schemas/social_insurance.py +39 -6
- brynq_sdk_nmbrs/schemas/wage_tax.py +68 -8
- brynq_sdk_nmbrs/schemas/wage_tax_settings.py +76 -0
- brynq_sdk_nmbrs/schemas/wagecomponents.py +0 -4
- brynq_sdk_nmbrs/social_insurance.py +81 -3
- brynq_sdk_nmbrs/wage_tax.py +105 -4
- {brynq_sdk_nmbrs-2.3.1.dist-info → brynq_sdk_nmbrs-2.3.2.dev0.dist-info}/METADATA +1 -1
- brynq_sdk_nmbrs-2.3.2.dev0.dist-info/RECORD +55 -0
- {brynq_sdk_nmbrs-2.3.1.dist-info → brynq_sdk_nmbrs-2.3.2.dev0.dist-info}/WHEEL +1 -1
- brynq_sdk_nmbrs-2.3.1.dist-info/RECORD +0 -50
- {brynq_sdk_nmbrs-2.3.1.dist-info → brynq_sdk_nmbrs-2.3.2.dev0.dist-info}/top_level.txt +0 -0
brynq_sdk_nmbrs/employees.py
CHANGED
|
@@ -1,36 +1,50 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
|
|
1
4
|
import pandas as pd
|
|
2
5
|
import requests
|
|
3
6
|
from pydantic import BaseModel
|
|
7
|
+
from zeep.exceptions import Fault
|
|
8
|
+
from zeep.helpers import serialize_object
|
|
4
9
|
|
|
5
10
|
from brynq_sdk_functions import Functions
|
|
6
|
-
from typing import Dict, Any, Optional
|
|
7
|
-
|
|
8
|
-
from .wage_tax import WageTax
|
|
9
11
|
|
|
10
|
-
from .document import Payslip
|
|
11
12
|
from .address import Address
|
|
13
|
+
from .bank import Bank
|
|
12
14
|
from .contract import Contract
|
|
13
15
|
from .costcenter import EmployeeCostcenter
|
|
16
|
+
from .days import VariableDays
|
|
14
17
|
from .department import EmployeeDepartment
|
|
18
|
+
from .document import Payslip
|
|
15
19
|
from .employment import Employment
|
|
16
20
|
from .function import EmployeeFunction
|
|
17
|
-
from .hours import
|
|
18
|
-
from .schedules import Schedule
|
|
19
|
-
from .salaries import Salaries
|
|
20
|
-
from .bank import Bank
|
|
21
|
-
from .days import VariableDays
|
|
22
|
-
from .wagecomponents import EmployeeVariableWageComponents, EmployeeFixedWageComponents
|
|
21
|
+
from .hours import FixedHours, VariableHours
|
|
23
22
|
from .leave import Leave, LeaveBalance
|
|
23
|
+
from .salaries import Salaries
|
|
24
|
+
from .schedules import Schedule
|
|
24
25
|
from .schemas.employees import (
|
|
25
|
-
|
|
26
|
-
BasicInfo,
|
|
27
|
-
|
|
26
|
+
AdditionalEmployeeInfo,
|
|
27
|
+
BasicInfo,
|
|
28
|
+
BirthInfo,
|
|
29
|
+
BsnGet,
|
|
30
|
+
ContactInfo,
|
|
31
|
+
CreateEmployeePersonalInfo,
|
|
32
|
+
DefaultEmployeeTemplates,
|
|
33
|
+
EmployeeCreate,
|
|
34
|
+
EmployeeDelete,
|
|
35
|
+
EmployeeGet,
|
|
36
|
+
EmployeeUpdate,
|
|
37
|
+
PartnerInfo,
|
|
38
|
+
Period,
|
|
28
39
|
)
|
|
40
|
+
from .wage_tax import WageTax
|
|
41
|
+
from .wagecomponents import EmployeeFixedWageComponents, EmployeeVariableWageComponents
|
|
29
42
|
|
|
30
43
|
|
|
31
44
|
class Employees:
|
|
32
45
|
def __init__(self, nmbrs):
|
|
33
46
|
self.nmbrs = nmbrs
|
|
47
|
+
self.logger = logging.getLogger(__name__)
|
|
34
48
|
self.address = Address(nmbrs)
|
|
35
49
|
self.functions = EmployeeFunction(nmbrs)
|
|
36
50
|
self.contract = Contract(nmbrs)
|
|
@@ -55,7 +69,9 @@ class Employees:
|
|
|
55
69
|
) -> (pd.DataFrame, pd.DataFrame):
|
|
56
70
|
employees = pd.DataFrame()
|
|
57
71
|
for company in self.nmbrs.company_ids:
|
|
58
|
-
|
|
72
|
+
company_employees = self._get(company, employee_type)
|
|
73
|
+
if not company_employees.empty:
|
|
74
|
+
employees = pd.concat([employees, company_employees])
|
|
59
75
|
|
|
60
76
|
valid_employees, invalid_employees = Functions.validate_data(df=employees, schema=EmployeeGet, debug=True)
|
|
61
77
|
|
|
@@ -70,18 +86,23 @@ class Employees:
|
|
|
70
86
|
params=params)
|
|
71
87
|
|
|
72
88
|
data = self.nmbrs.get_paginated_result(request)
|
|
89
|
+
if not data:
|
|
90
|
+
return pd.DataFrame()
|
|
91
|
+
|
|
73
92
|
df = pd.json_normalize(
|
|
74
93
|
data,
|
|
75
94
|
record_path='info',
|
|
76
95
|
meta=['employeeId']
|
|
77
96
|
)
|
|
78
|
-
df
|
|
97
|
+
if df.empty:
|
|
98
|
+
return df
|
|
79
99
|
|
|
80
|
-
df['
|
|
81
|
-
|
|
82
|
-
|
|
100
|
+
df['companyId'] = company_id
|
|
101
|
+
if 'createdAt' in df.columns and 'employeeId' in df.columns:
|
|
102
|
+
df['createdAt'] = pd.to_datetime(df['createdAt'])
|
|
103
|
+
df = df.loc[df.groupby('employeeId')['createdAt'].idxmax()]
|
|
83
104
|
|
|
84
|
-
return df
|
|
105
|
+
return df.reset_index(drop=True)
|
|
85
106
|
|
|
86
107
|
def get_private_info(self) -> str:
|
|
87
108
|
combined = []
|
|
@@ -108,9 +129,9 @@ class Employees:
|
|
|
108
129
|
default_templates_temp['companyId'] = company
|
|
109
130
|
default_templates = pd.concat([default_templates, default_templates_temp])
|
|
110
131
|
|
|
111
|
-
|
|
132
|
+
valid_default_templates, invalid_default_templates = Functions.validate_data(df=default_templates, schema=DefaultEmployeeTemplates, debug=True)
|
|
112
133
|
|
|
113
|
-
return
|
|
134
|
+
return valid_default_templates, invalid_default_templates
|
|
114
135
|
|
|
115
136
|
|
|
116
137
|
def _get_default_templates(self, company_id: str, employee_type: str = None) -> pd.DataFrame:
|
|
@@ -150,6 +171,8 @@ class Employees:
|
|
|
150
171
|
json=payload,
|
|
151
172
|
timeout=self.nmbrs.timeout
|
|
152
173
|
)
|
|
174
|
+
|
|
175
|
+
|
|
153
176
|
return resp
|
|
154
177
|
|
|
155
178
|
def update(self, employee_id: str, data: Dict[str, Any]):
|
|
@@ -183,14 +206,86 @@ class Employees:
|
|
|
183
206
|
)
|
|
184
207
|
|
|
185
208
|
# Handle social security number update if present
|
|
186
|
-
|
|
209
|
+
#TODO niuet overtschirjven
|
|
210
|
+
if 'social_security_number' in data:
|
|
187
211
|
social_security_payload = {
|
|
188
|
-
"socialSecurityNumber": data['
|
|
212
|
+
"socialSecurityNumber": data['social_security_number']
|
|
189
213
|
}
|
|
190
214
|
resp = self.nmbrs.session.put(
|
|
191
|
-
url=f"{self.nmbrs.base_url}employees/{employee_id}/
|
|
215
|
+
url=f"{self.nmbrs.base_url}employees/{employee_id}/socialsecuritynumber",
|
|
192
216
|
json=social_security_payload,
|
|
193
217
|
timeout=self.nmbrs.timeout
|
|
194
218
|
)
|
|
195
219
|
|
|
196
220
|
return resp
|
|
221
|
+
|
|
222
|
+
def get_soap_ids(self) -> pd.DataFrame:
|
|
223
|
+
"""
|
|
224
|
+
Get all employees using the SOAP API.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
pd.DataFrame: DataFrame containing all companies
|
|
228
|
+
"""
|
|
229
|
+
try:
|
|
230
|
+
# Make SOAP request with the proper header structure
|
|
231
|
+
emp_list = []
|
|
232
|
+
comp_ids = self.nmbrs.soap_company_ids.i_d.unique()
|
|
233
|
+
for company_id in comp_ids:
|
|
234
|
+
response = self.nmbrs.soap_client_employees.service.Function_GetAll_AllEmployeesByCompany_V2(
|
|
235
|
+
_soapheaders=[self.nmbrs.soap_auth_header_employees],
|
|
236
|
+
CompanyID=company_id
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Convert response to DataFrame
|
|
240
|
+
if response:
|
|
241
|
+
# Convert Zeep objects to Python dictionaries
|
|
242
|
+
serialized_response = serialize_object(response)
|
|
243
|
+
|
|
244
|
+
# TODO: add validation here
|
|
245
|
+
# Convert to DataFrame
|
|
246
|
+
df = pd.DataFrame(serialized_response)
|
|
247
|
+
df = self.nmbrs._rename_camel_columns_to_snake_case(df)
|
|
248
|
+
if not df.empty:
|
|
249
|
+
emp_list.append(df)
|
|
250
|
+
|
|
251
|
+
if len(emp_list) > 0:
|
|
252
|
+
return pd.concat(emp_list, ignore_index=True)
|
|
253
|
+
else:
|
|
254
|
+
return pd.DataFrame()
|
|
255
|
+
|
|
256
|
+
except Fault as e:
|
|
257
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
|
258
|
+
except Exception as e:
|
|
259
|
+
self.logger.exception("Exception occurred:")
|
|
260
|
+
raise Exception(f"Failed to get companies: {str(e)}")
|
|
261
|
+
|
|
262
|
+
def get_soap_personal_info(self):
|
|
263
|
+
try:
|
|
264
|
+
emp_list = []
|
|
265
|
+
comp_ids = self.nmbrs.soap_company_ids.i_d.unique()
|
|
266
|
+
for company_id in comp_ids:
|
|
267
|
+
response = self.nmbrs.soap_client_employees.service.PersonalInfo_GetAll_AllEmployeesByCompany(
|
|
268
|
+
_soapheaders=[self.nmbrs.soap_auth_header_employees],
|
|
269
|
+
CompanyID=company_id
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
if response:
|
|
273
|
+
serialized_response = serialize_object(response)
|
|
274
|
+
df = pd.json_normalize(
|
|
275
|
+
serialized_response,
|
|
276
|
+
record_path=['EmployeePersonalInfos', 'PersonalInfo_V2'],
|
|
277
|
+
meta=['EmployeeId']
|
|
278
|
+
)
|
|
279
|
+
df = self.nmbrs._rename_camel_columns_to_snake_case(df)
|
|
280
|
+
if not df.empty:
|
|
281
|
+
emp_list.append(df)
|
|
282
|
+
if len(emp_list) > 0:
|
|
283
|
+
return pd.concat(emp_list, ignore_index=True)
|
|
284
|
+
else:
|
|
285
|
+
return pd.DataFrame()
|
|
286
|
+
|
|
287
|
+
except Fault as e:
|
|
288
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
|
289
|
+
except Exception as e:
|
|
290
|
+
self.logger.exception("Exception occurred:")
|
|
291
|
+
raise Exception(f"Failed to get companies: {str(e)}")
|
brynq_sdk_nmbrs/employment.py
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import math
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
|
|
2
4
|
import pandas as pd
|
|
3
5
|
import requests
|
|
6
|
+
|
|
4
7
|
from brynq_sdk_functions import Functions
|
|
5
|
-
|
|
6
|
-
from .schemas.employment import
|
|
8
|
+
|
|
9
|
+
from .schemas.employment import (
|
|
10
|
+
EmploymentCreate,
|
|
11
|
+
EmploymentDelete,
|
|
12
|
+
EmploymentGet,
|
|
13
|
+
EmploymentUpdate,
|
|
14
|
+
)
|
|
7
15
|
|
|
8
16
|
|
|
9
17
|
class Employment:
|
|
@@ -66,7 +74,7 @@ class Employment:
|
|
|
66
74
|
return employment_model
|
|
67
75
|
|
|
68
76
|
# Convert validated model to dict for API payload
|
|
69
|
-
payload = employment_model.model_dump(exclude_none=True, by_alias=True)
|
|
77
|
+
payload = employment_model.model_dump(exclude_none=True, by_alias=True, mode='json')
|
|
70
78
|
|
|
71
79
|
# Send request
|
|
72
80
|
resp = self.nmbrs.session.post(
|
|
@@ -96,7 +104,7 @@ class Employment:
|
|
|
96
104
|
return employment_model
|
|
97
105
|
|
|
98
106
|
# Convert validated model to dict for API payload
|
|
99
|
-
payload = employment_model.model_dump(exclude_none=True, by_alias=True)
|
|
107
|
+
payload = employment_model.model_dump(exclude_none=True, by_alias=True, mode='json')
|
|
100
108
|
|
|
101
109
|
# Send request
|
|
102
110
|
resp = self.nmbrs.session.put(
|
brynq_sdk_nmbrs/function.py
CHANGED
|
@@ -2,8 +2,13 @@ import pandas as pd
|
|
|
2
2
|
import requests
|
|
3
3
|
from brynq_sdk_functions import Functions as BrynQFunctions
|
|
4
4
|
import math
|
|
5
|
-
from typing import Dict, Any
|
|
6
|
-
from .schemas.function import
|
|
5
|
+
from typing import Dict, Any, Union
|
|
6
|
+
from .schemas.function import (
|
|
7
|
+
EmployeeFunctionGet, FunctionUpdate, FunctionGet,
|
|
8
|
+
FunctionCreate, FunctionDelete, FunctionMasterUpdate
|
|
9
|
+
)
|
|
10
|
+
from zeep.exceptions import Fault
|
|
11
|
+
from zeep.helpers import serialize_object
|
|
7
12
|
|
|
8
13
|
|
|
9
14
|
class EmployeeFunction:
|
|
@@ -68,9 +73,44 @@ class EmployeeFunction:
|
|
|
68
73
|
)
|
|
69
74
|
return resp
|
|
70
75
|
|
|
76
|
+
def get_current(self, employee_id: Union[int, str]) -> pd.DataFrame:
|
|
77
|
+
"""
|
|
78
|
+
Get current function for a specific employee using SOAP API.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
employee_id: The ID of the employee
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
DataFrame with current function
|
|
85
|
+
"""
|
|
86
|
+
if self.nmbrs.mock_mode:
|
|
87
|
+
return pd.DataFrame()
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
response = self.nmbrs.soap_client_employees.service.Function_GetCurrent(
|
|
91
|
+
EmployeeId=int(employee_id),
|
|
92
|
+
_soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_employees}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if response:
|
|
96
|
+
serialized = serialize_object(response)
|
|
97
|
+
if not isinstance(serialized, list):
|
|
98
|
+
serialized = [serialized]
|
|
99
|
+
df = pd.DataFrame(serialized)
|
|
100
|
+
df['employee_id'] = str(employee_id)
|
|
101
|
+
return df
|
|
102
|
+
else:
|
|
103
|
+
return pd.DataFrame()
|
|
104
|
+
|
|
105
|
+
except Fault as e:
|
|
106
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
|
71
107
|
|
|
72
108
|
|
|
73
109
|
class Functions:
|
|
110
|
+
"""
|
|
111
|
+
Master Functions (Debtor level) - manages function definitions.
|
|
112
|
+
Uses DebtorService SOAP API for create, update, delete.
|
|
113
|
+
"""
|
|
74
114
|
def __init__(self, nmbrs):
|
|
75
115
|
self.nmbrs = nmbrs
|
|
76
116
|
|
|
@@ -87,3 +127,89 @@ class Functions:
|
|
|
87
127
|
df = pd.DataFrame()
|
|
88
128
|
|
|
89
129
|
return valid_functions, invalid_functions
|
|
130
|
+
|
|
131
|
+
def create(self, debtor_id: Union[int, str], data: Dict[str, Any]):
|
|
132
|
+
"""
|
|
133
|
+
Create a new master function using SOAP API.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
debtor_id: The ID of the debtor
|
|
137
|
+
data: Dictionary containing function data with fields matching FunctionCreate schema
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Response from the API (function ID)
|
|
141
|
+
"""
|
|
142
|
+
function_model = FunctionCreate(**data)
|
|
143
|
+
|
|
144
|
+
if self.nmbrs.mock_mode:
|
|
145
|
+
return function_model
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
response = self.nmbrs.soap_client_debtors.service.Function_Insert(
|
|
149
|
+
DebtorId=int(debtor_id),
|
|
150
|
+
function={
|
|
151
|
+
'Id': 0, # 0 for new function
|
|
152
|
+
'Code': function_model.code,
|
|
153
|
+
'Description': function_model.description
|
|
154
|
+
},
|
|
155
|
+
_soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_debtors}
|
|
156
|
+
)
|
|
157
|
+
return response
|
|
158
|
+
except Fault as e:
|
|
159
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
|
160
|
+
|
|
161
|
+
def delete(self, debtor_id: Union[int, str], function_id: Union[int, str]):
|
|
162
|
+
"""
|
|
163
|
+
Delete a master function using SOAP API.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
debtor_id: The ID of the debtor
|
|
167
|
+
function_id: The ID of the function to delete
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Response from the API
|
|
171
|
+
"""
|
|
172
|
+
delete_model = FunctionDelete(function_id=int(function_id))
|
|
173
|
+
|
|
174
|
+
if self.nmbrs.mock_mode:
|
|
175
|
+
return delete_model
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
response = self.nmbrs.soap_client_debtors.service.Function_Delete(
|
|
179
|
+
DebtorId=int(debtor_id),
|
|
180
|
+
id=delete_model.function_id,
|
|
181
|
+
_soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_debtors}
|
|
182
|
+
)
|
|
183
|
+
return response
|
|
184
|
+
except Fault as e:
|
|
185
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
|
186
|
+
|
|
187
|
+
def update(self, debtor_id: Union[int, str], data: Dict[str, Any]):
|
|
188
|
+
"""
|
|
189
|
+
Update a master function using SOAP API.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
debtor_id: The ID of the debtor
|
|
193
|
+
data: Dictionary containing function data with fields matching FunctionMasterUpdate schema
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Response from the API
|
|
197
|
+
"""
|
|
198
|
+
function_model = FunctionMasterUpdate(**data)
|
|
199
|
+
|
|
200
|
+
if self.nmbrs.mock_mode:
|
|
201
|
+
return function_model
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
response = self.nmbrs.soap_client_debtors.service.Function_Update(
|
|
205
|
+
DebtorId=int(debtor_id),
|
|
206
|
+
function={
|
|
207
|
+
'Id': function_model.function_id,
|
|
208
|
+
'Code': function_model.code,
|
|
209
|
+
'Description': function_model.description
|
|
210
|
+
},
|
|
211
|
+
_soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_debtors}
|
|
212
|
+
)
|
|
213
|
+
return response
|
|
214
|
+
except Fault as e:
|
|
215
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
brynq_sdk_nmbrs/leave.py
CHANGED
|
@@ -2,8 +2,9 @@ import math
|
|
|
2
2
|
import pandas as pd
|
|
3
3
|
import requests
|
|
4
4
|
from brynq_sdk_functions import Functions
|
|
5
|
-
from typing import Dict, Any
|
|
6
|
-
from .schemas.leave import LeaveBalanceGet, LeaveGet, LeaveCreate
|
|
5
|
+
from typing import Dict, Any, Union
|
|
6
|
+
from .schemas.leave import LeaveBalanceGet, LeaveGet, LeaveCreate, LeaveDelete, LeaveUpdate
|
|
7
|
+
from zeep.exceptions import Fault
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class Leave:
|
|
@@ -95,6 +96,42 @@ class Leave:
|
|
|
95
96
|
)
|
|
96
97
|
return resp
|
|
97
98
|
|
|
99
|
+
def update(self, data: Dict[str, Any]):
|
|
100
|
+
"""
|
|
101
|
+
Update a leave request for an employee using SOAP API.
|
|
102
|
+
(REST API does not support leave update)
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
data: Dictionary containing leave data with fields matching LeaveUpdate schema:
|
|
106
|
+
- employee_id: Employee ID
|
|
107
|
+
- leave_id: Leave ID
|
|
108
|
+
- start_date: Start date
|
|
109
|
+
- end_date: End date
|
|
110
|
+
- description: Optional description
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Response from the API
|
|
114
|
+
"""
|
|
115
|
+
leave_model = LeaveUpdate(**data)
|
|
116
|
+
|
|
117
|
+
if self.nmbrs.mock_mode:
|
|
118
|
+
return leave_model
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
resp = self.nmbrs.soap_client_employees.service.Leave_Update(
|
|
122
|
+
EmployeeId=leave_model.employee_id,
|
|
123
|
+
Leave={
|
|
124
|
+
'Id': leave_model.leave_id,
|
|
125
|
+
'Start': leave_model.start_date,
|
|
126
|
+
'End': leave_model.end_date,
|
|
127
|
+
'Description': leave_model.description or ''
|
|
128
|
+
},
|
|
129
|
+
_soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_employees}
|
|
130
|
+
)
|
|
131
|
+
return resp
|
|
132
|
+
except Fault as e:
|
|
133
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
|
134
|
+
|
|
98
135
|
|
|
99
136
|
class LeaveBalance:
|
|
100
137
|
def __init__(self, nmbrs):
|
|
@@ -111,7 +148,12 @@ class LeaveBalance:
|
|
|
111
148
|
return valid_leave, invalid_leave
|
|
112
149
|
|
|
113
150
|
def _get(self,
|
|
114
|
-
company_id: str
|
|
151
|
+
company_id: str,
|
|
152
|
+
changed_from: str = None) -> pd.DataFrame:
|
|
153
|
+
"""
|
|
154
|
+
Note: changed_from parameter is accepted for consistency but not used
|
|
155
|
+
by the leaveBalances endpoint.
|
|
156
|
+
"""
|
|
115
157
|
try:
|
|
116
158
|
request = requests.Request(method='GET',
|
|
117
159
|
url=f"{self.nmbrs.base_url}companies/{company_id}/employees/leaveBalances")
|
|
@@ -129,11 +171,66 @@ class LeaveBalance:
|
|
|
129
171
|
|
|
130
172
|
|
|
131
173
|
class LeaveGroup:
|
|
174
|
+
"""
|
|
175
|
+
LeaveGroup (Leave Type Groups) - uses SOAP CompanyLeaveTypeGroups_Get
|
|
176
|
+
"""
|
|
132
177
|
def __init__(self, nmbrs):
|
|
133
178
|
self.nmbrs = nmbrs
|
|
134
179
|
|
|
135
|
-
def get(self,
|
|
136
|
-
|
|
137
|
-
leave
|
|
138
|
-
|
|
139
|
-
|
|
180
|
+
def get(self, company_id: Union[int, str] = None) -> pd.DataFrame:
|
|
181
|
+
"""
|
|
182
|
+
Get leave type groups using SOAP API.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
company_id: Optional. If provided, get groups for this company only.
|
|
186
|
+
If not provided, get groups for all companies.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
DataFrame with leave type groups
|
|
190
|
+
"""
|
|
191
|
+
if self.nmbrs.mock_mode:
|
|
192
|
+
return pd.DataFrame()
|
|
193
|
+
|
|
194
|
+
from zeep.helpers import serialize_object
|
|
195
|
+
|
|
196
|
+
all_groups = []
|
|
197
|
+
try:
|
|
198
|
+
# If company_id provided, only get for that company
|
|
199
|
+
if company_id is not None:
|
|
200
|
+
company_ids = [int(company_id)]
|
|
201
|
+
else:
|
|
202
|
+
# Get all company IDs from SOAP
|
|
203
|
+
companies_response = self.nmbrs.soap_client_companies.service.List_GetAll(
|
|
204
|
+
_soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header}
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Handle response - could be list or object with Company attribute
|
|
208
|
+
companies = []
|
|
209
|
+
if companies_response:
|
|
210
|
+
if hasattr(companies_response, 'Company'):
|
|
211
|
+
companies = companies_response.Company or []
|
|
212
|
+
elif isinstance(companies_response, list):
|
|
213
|
+
companies = companies_response
|
|
214
|
+
|
|
215
|
+
company_ids = [c.ID for c in companies]
|
|
216
|
+
|
|
217
|
+
for cid in company_ids:
|
|
218
|
+
try:
|
|
219
|
+
response = self.nmbrs.soap_client_companies.service.CompanyLeaveTypeGroups_Get(
|
|
220
|
+
CompanyId=cid,
|
|
221
|
+
_soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header}
|
|
222
|
+
)
|
|
223
|
+
if response and response.LeaveTypeGroup:
|
|
224
|
+
groups = serialize_object(response.LeaveTypeGroup)
|
|
225
|
+
if not isinstance(groups, list):
|
|
226
|
+
groups = [groups]
|
|
227
|
+
for group in groups:
|
|
228
|
+
group['company_id'] = str(cid)
|
|
229
|
+
all_groups.extend(groups)
|
|
230
|
+
except Exception:
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
return pd.DataFrame(all_groups)
|
|
234
|
+
|
|
235
|
+
except Fault as e:
|
|
236
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
brynq_sdk_nmbrs/salaries.py
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
from typing import Any, Dict, Union
|
|
2
|
+
|
|
2
3
|
import pandas as pd
|
|
3
4
|
import requests
|
|
4
|
-
from
|
|
5
|
+
from zeep.exceptions import Fault
|
|
6
|
+
from zeep.helpers import serialize_object
|
|
7
|
+
|
|
5
8
|
from brynq_sdk_functions import Functions
|
|
6
|
-
|
|
9
|
+
|
|
10
|
+
from .schemas.salary import SalaryCreate, SalaryGet, SalaryUpdate
|
|
7
11
|
|
|
8
12
|
|
|
9
13
|
class Salaries:
|
|
@@ -83,3 +87,74 @@ class Salaries:
|
|
|
83
87
|
timeout=self.nmbrs.timeout
|
|
84
88
|
)
|
|
85
89
|
return resp
|
|
90
|
+
|
|
91
|
+
def get_current(self, employee_id: Union[int, str]) -> pd.DataFrame:
|
|
92
|
+
"""
|
|
93
|
+
Get current salary for an employee via SOAP.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
employee_id: The ID of the employee
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
DataFrame with current salary
|
|
100
|
+
"""
|
|
101
|
+
if self.nmbrs.mock_mode:
|
|
102
|
+
return pd.DataFrame()
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
response = self.nmbrs.soap_client_employees.service.Salary_GetCurrent(
|
|
106
|
+
EmployeeId=int(employee_id),
|
|
107
|
+
_soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_employees}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if response:
|
|
111
|
+
serialized = serialize_object(response)
|
|
112
|
+
if not isinstance(serialized, list):
|
|
113
|
+
serialized = [serialized]
|
|
114
|
+
df = pd.DataFrame(serialized)
|
|
115
|
+
df['employee_id'] = str(employee_id)
|
|
116
|
+
return df
|
|
117
|
+
else:
|
|
118
|
+
return pd.DataFrame()
|
|
119
|
+
|
|
120
|
+
except Fault as e:
|
|
121
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
|
122
|
+
except Exception as e:
|
|
123
|
+
raise Exception(f"Failed to get Salary: {str(e)}")
|
|
124
|
+
|
|
125
|
+
def update(self, data: Dict[str, Any]):
|
|
126
|
+
"""
|
|
127
|
+
Update salary for an employee using SOAP API.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
data: Dictionary containing salary data with fields matching SalaryUpdate schema:
|
|
131
|
+
- employee_id: Employee ID
|
|
132
|
+
- salary_value: Salary value (gross amount)
|
|
133
|
+
- salary_type: Salary type (1=FulltimeSalary, 2=ParttimeSalary, etc.)
|
|
134
|
+
- start_date: Start date (optional)
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Response from the API
|
|
138
|
+
"""
|
|
139
|
+
try:
|
|
140
|
+
salary_model = SalaryUpdate(**data)
|
|
141
|
+
|
|
142
|
+
if self.nmbrs.mock_mode:
|
|
143
|
+
return salary_model
|
|
144
|
+
|
|
145
|
+
# Make SOAP request with dict - SalaryInput only has Value, Type, SalaryTable
|
|
146
|
+
response = self.nmbrs.soap_client_employees.service.Salary_UpdateCurrent(
|
|
147
|
+
EmployeeId=salary_model.employee_id,
|
|
148
|
+
Salary={
|
|
149
|
+
'Value': salary_model.salary_value,
|
|
150
|
+
'Type': salary_model.salary_type
|
|
151
|
+
},
|
|
152
|
+
_soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_employees}
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
return response
|
|
156
|
+
|
|
157
|
+
except Fault as e:
|
|
158
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
|
159
|
+
except Exception as e:
|
|
160
|
+
raise Exception(f"Failed to update Salary: {str(e)}")
|