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.
Files changed (46) hide show
  1. brynq_sdk_nmbrs/__init__.py +104 -84
  2. brynq_sdk_nmbrs/address.py +82 -2
  3. brynq_sdk_nmbrs/children.py +96 -61
  4. brynq_sdk_nmbrs/companies.py +45 -1
  5. brynq_sdk_nmbrs/costcenter.py +53 -8
  6. brynq_sdk_nmbrs/costunit.py +16 -4
  7. brynq_sdk_nmbrs/debtors.py +76 -2
  8. brynq_sdk_nmbrs/department.py +149 -28
  9. brynq_sdk_nmbrs/document.py +50 -0
  10. brynq_sdk_nmbrs/employee_wage_tax_settings.py +113 -0
  11. brynq_sdk_nmbrs/employees.py +119 -24
  12. brynq_sdk_nmbrs/employment.py +12 -4
  13. brynq_sdk_nmbrs/function.py +128 -2
  14. brynq_sdk_nmbrs/leave.py +105 -8
  15. brynq_sdk_nmbrs/salaries.py +78 -3
  16. brynq_sdk_nmbrs/schedules.py +77 -3
  17. brynq_sdk_nmbrs/schemas/address.py +30 -5
  18. brynq_sdk_nmbrs/schemas/bank.py +0 -2
  19. brynq_sdk_nmbrs/schemas/children.py +67 -0
  20. brynq_sdk_nmbrs/schemas/company.py +16 -0
  21. brynq_sdk_nmbrs/schemas/contracts.py +25 -11
  22. brynq_sdk_nmbrs/schemas/costcenter.py +57 -18
  23. brynq_sdk_nmbrs/schemas/costunit.py +0 -2
  24. brynq_sdk_nmbrs/schemas/days.py +0 -2
  25. brynq_sdk_nmbrs/schemas/debtor.py +23 -1
  26. brynq_sdk_nmbrs/schemas/department.py +41 -7
  27. brynq_sdk_nmbrs/schemas/document.py +13 -0
  28. brynq_sdk_nmbrs/schemas/employees.py +44 -38
  29. brynq_sdk_nmbrs/schemas/employment.py +10 -10
  30. brynq_sdk_nmbrs/schemas/function.py +34 -7
  31. brynq_sdk_nmbrs/schemas/hours.py +0 -4
  32. brynq_sdk_nmbrs/schemas/leave.py +12 -1
  33. brynq_sdk_nmbrs/schemas/manager.py +0 -3
  34. brynq_sdk_nmbrs/schemas/salary.py +37 -12
  35. brynq_sdk_nmbrs/schemas/schedules.py +49 -1
  36. brynq_sdk_nmbrs/schemas/social_insurance.py +39 -6
  37. brynq_sdk_nmbrs/schemas/wage_tax.py +68 -8
  38. brynq_sdk_nmbrs/schemas/wage_tax_settings.py +76 -0
  39. brynq_sdk_nmbrs/schemas/wagecomponents.py +0 -4
  40. brynq_sdk_nmbrs/social_insurance.py +81 -3
  41. brynq_sdk_nmbrs/wage_tax.py +105 -4
  42. {brynq_sdk_nmbrs-2.3.1.dist-info → brynq_sdk_nmbrs-2.3.2.dev0.dist-info}/METADATA +1 -1
  43. brynq_sdk_nmbrs-2.3.2.dev0.dist-info/RECORD +55 -0
  44. {brynq_sdk_nmbrs-2.3.1.dist-info → brynq_sdk_nmbrs-2.3.2.dev0.dist-info}/WHEEL +1 -1
  45. brynq_sdk_nmbrs-2.3.1.dist-info/RECORD +0 -50
  46. {brynq_sdk_nmbrs-2.3.1.dist-info → brynq_sdk_nmbrs-2.3.2.dev0.dist-info}/top_level.txt +0 -0
@@ -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 VariableHours, FixedHours
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
- BsnGet, EmployeeGet, EmployeeCreate, EmployeeUpdate, EmployeeDelete,
26
- BasicInfo, BirthInfo, ContactInfo, PartnerInfo, Period, AdditionalEmployeeInfo,
27
- CreateEmployeePersonalInfo
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
- employees = pd.concat([employees, self._get(company, employee_type)])
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['companyId'] = company_id
97
+ if df.empty:
98
+ return df
79
99
 
80
- df['createdAt'] = pd.to_datetime(df['createdAt'])
81
- df = df.loc[df.groupby('employeeId')['createdAt'].idxmax()]
82
- df = df.reset_index(drop=True)
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
- # valid_default_templates, invalid_default_templates = Functions.validate_data(df=default_templates, schema=EmployeeGet, debug=True)
132
+ valid_default_templates, invalid_default_templates = Functions.validate_data(df=default_templates, schema=DefaultEmployeeTemplates, debug=True)
112
133
 
113
- return default_templates
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
- if 'socialSecurityNumber' in data:
209
+ #TODO niuet overtschirjven
210
+ if 'social_security_number' in data:
187
211
  social_security_payload = {
188
- "socialSecurityNumber": data['socialSecurityNumber']
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}/social_security_number",
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)}")
@@ -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
- from typing import Dict, Any
6
- from .schemas.employment import EmploymentGet, EmploymentCreate, EmploymentUpdate, EmploymentDelete
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(
@@ -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 EmployeeFunctionGet, FunctionUpdate, FunctionGet
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) -> pd.DataFrame:
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
- changed_from: str = None) -> tuple[pd.DataFrame, pd.DataFrame]:
137
- leave = pd.DataFrame()
138
- for company in self.nmbrs.company_ids:
139
- leave = pd.concat([leave, self._get(company, changed_from)])
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)}")
@@ -1,9 +1,13 @@
1
- import math
1
+ from typing import Any, Dict, Union
2
+
2
3
  import pandas as pd
3
4
  import requests
4
- from typing import Dict, Any
5
+ from zeep.exceptions import Fault
6
+ from zeep.helpers import serialize_object
7
+
5
8
  from brynq_sdk_functions import Functions
6
- from .schemas.salary import SalaryGet, SalaryCreate
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)}")