brynq-sdk-nmbrs 2.3.1.dev0__py3-none-any.whl → 2.3.3.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 (36) hide show
  1. brynq_sdk_nmbrs/__init__.py +86 -80
  2. brynq_sdk_nmbrs/address.py +82 -2
  3. brynq_sdk_nmbrs/bank.py +5 -2
  4. brynq_sdk_nmbrs/children.py +96 -61
  5. brynq_sdk_nmbrs/companies.py +45 -1
  6. brynq_sdk_nmbrs/debtors.py +76 -2
  7. brynq_sdk_nmbrs/department.py +113 -1
  8. brynq_sdk_nmbrs/document.py +50 -0
  9. brynq_sdk_nmbrs/employee_wage_tax_settings.py +16 -9
  10. brynq_sdk_nmbrs/function.py +128 -2
  11. brynq_sdk_nmbrs/leave.py +105 -8
  12. brynq_sdk_nmbrs/salaries.py +75 -3
  13. brynq_sdk_nmbrs/schedules.py +77 -3
  14. brynq_sdk_nmbrs/schemas/address.py +33 -4
  15. brynq_sdk_nmbrs/schemas/children.py +67 -0
  16. brynq_sdk_nmbrs/schemas/company.py +16 -0
  17. brynq_sdk_nmbrs/schemas/debtor.py +23 -1
  18. brynq_sdk_nmbrs/schemas/department.py +33 -0
  19. brynq_sdk_nmbrs/schemas/document.py +13 -0
  20. brynq_sdk_nmbrs/schemas/employees.py +3 -1
  21. brynq_sdk_nmbrs/schemas/function.py +28 -0
  22. brynq_sdk_nmbrs/schemas/leave.py +12 -0
  23. brynq_sdk_nmbrs/schemas/salary.py +18 -0
  24. brynq_sdk_nmbrs/schemas/schedules.py +81 -16
  25. brynq_sdk_nmbrs/schemas/social_insurance.py +39 -6
  26. brynq_sdk_nmbrs/schemas/wage_tax.py +65 -5
  27. brynq_sdk_nmbrs/schemas/wage_tax_settings.py +42 -29
  28. brynq_sdk_nmbrs/schemas/wagecomponents.py +66 -9
  29. brynq_sdk_nmbrs/social_insurance.py +81 -3
  30. brynq_sdk_nmbrs/wage_tax.py +104 -3
  31. brynq_sdk_nmbrs/wagecomponents.py +81 -45
  32. {brynq_sdk_nmbrs-2.3.1.dev0.dist-info → brynq_sdk_nmbrs-2.3.3.dev0.dist-info}/METADATA +1 -1
  33. brynq_sdk_nmbrs-2.3.3.dev0.dist-info/RECORD +55 -0
  34. {brynq_sdk_nmbrs-2.3.1.dev0.dist-info → brynq_sdk_nmbrs-2.3.3.dev0.dist-info}/WHEEL +1 -1
  35. brynq_sdk_nmbrs-2.3.1.dev0.dist-info/RECORD +0 -52
  36. {brynq_sdk_nmbrs-2.3.1.dev0.dist-info → brynq_sdk_nmbrs-2.3.3.dev0.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,11 @@
1
1
  import pandas as pd
2
2
  import requests
3
+ from typing import Dict, Any
4
+ from zeep.exceptions import Fault
3
5
  from brynq_sdk_functions import Functions
4
6
  from .department import Departments
5
7
  from .function import Functions as NmbrsFunctions
6
- from .schemas.debtor import DebtorsGet
8
+ from .schemas.debtor import DebtorsGet, DebtorCreate, DebtorUpdate
7
9
 
8
10
 
9
11
  class Debtors:
@@ -12,7 +14,6 @@ class Debtors:
12
14
  self.departments = Departments(nmbrs)
13
15
  self.functions = NmbrsFunctions(nmbrs)
14
16
 
15
-
16
17
  def get(self) -> (pd.DataFrame, pd.DataFrame):
17
18
  request = requests.Request(method='GET',
18
19
  url=f"{self.nmbrs.base_url}debtors")
@@ -23,3 +24,76 @@ class Debtors:
23
24
  valid_debtors, invalid_debtors = Functions.validate_data(df=df, schema=DebtorsGet, debug=True)
24
25
 
25
26
  return valid_debtors, invalid_debtors
27
+
28
+ def create(self, data: Dict[str, Any]) -> int:
29
+ """
30
+ Create a new debtor using SOAP API.
31
+
32
+ Args:
33
+ data: Dictionary containing debtor data with fields matching DebtorCreate schema:
34
+ - number: Debtor number
35
+ - name: Debtor name
36
+
37
+ Returns:
38
+ The ID of the newly created debtor.
39
+ """
40
+ debtor_model = DebtorCreate(**data)
41
+
42
+ if self.nmbrs.mock_mode:
43
+ return 12345 # Mock ID
44
+
45
+ try:
46
+ DebtorType = self.nmbrs.soap_client_debtors.get_type('ns0:Debtor')
47
+ soap_debtor = DebtorType(
48
+ Id=0, # 0 for new debtor
49
+ Number=debtor_model.number,
50
+ Name=debtor_model.name
51
+ )
52
+
53
+ response = self.nmbrs.soap_client_debtors.service.Debtor_Insert(
54
+ Debtor=soap_debtor,
55
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_debtors}
56
+ )
57
+ return response
58
+
59
+ except Fault as e:
60
+ raise Exception(f"SOAP request failed: {str(e)}")
61
+ except Exception as e:
62
+ raise Exception(f"Failed to create Debtor: {str(e)}")
63
+
64
+ def update(self, data: Dict[str, Any]):
65
+ """
66
+ Update a debtor using SOAP API.
67
+
68
+ Args:
69
+ data: Dictionary containing debtor data with fields matching DebtorUpdate schema:
70
+ - debtor_id: Debtor ID to update
71
+ - number: Debtor number
72
+ - name: Debtor name
73
+
74
+ Returns:
75
+ Response from the API
76
+ """
77
+ debtor_model = DebtorUpdate(**data)
78
+
79
+ if self.nmbrs.mock_mode:
80
+ return debtor_model
81
+
82
+ try:
83
+ DebtorType = self.nmbrs.soap_client_debtors.get_type('ns0:Debtor')
84
+ soap_debtor = DebtorType(
85
+ Id=debtor_model.debtor_id,
86
+ Number=debtor_model.number,
87
+ Name=debtor_model.name
88
+ )
89
+
90
+ response = self.nmbrs.soap_client_debtors.service.Debtor_Update(
91
+ Debtor=soap_debtor,
92
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_debtors}
93
+ )
94
+ return response
95
+
96
+ except Fault as e:
97
+ raise Exception(f"SOAP request failed: {str(e)}")
98
+ except Exception as e:
99
+ raise Exception(f"Failed to update Debtor: {str(e)}")
@@ -1,14 +1,17 @@
1
- import math
2
1
  from typing import Any, Dict
3
2
 
4
3
  import pandas as pd
5
4
  import requests
5
+ from zeep.exceptions import Fault
6
6
 
7
7
  from brynq_sdk_functions import Functions
8
8
 
9
9
  from .schemas.department import (
10
10
  DepartmentCreate,
11
11
  DepartmentGet,
12
+ DepartmentMasterCreate,
13
+ DepartmentMasterDelete,
14
+ DepartmentMasterUpdate,
12
15
  EmployeeDepartmentGet,
13
16
  EmployeeDepartmentUpdate,
14
17
  Period,
@@ -115,6 +118,8 @@ class EmployeeDepartment:
115
118
 
116
119
 
117
120
  class Departments:
121
+ """Master department operations (Debtor level) - uses SOAP for create/update/delete."""
122
+
118
123
  def __init__(self, nmbrs):
119
124
  self.nmbrs = nmbrs
120
125
 
@@ -129,3 +134,110 @@ class Departments:
129
134
  valid_departments, invalid_departments = Functions.validate_data(df=df, schema=DepartmentGet, debug=True)
130
135
 
131
136
  return valid_departments, invalid_departments
137
+
138
+ def create(self, data: Dict[str, Any]) -> int:
139
+ """
140
+ Create a new master department for a debtor using SOAP API.
141
+
142
+ Args:
143
+ data: Dictionary containing department data with fields matching DepartmentMasterCreate schema:
144
+ - debtor_id: Debtor ID
145
+ - code: Department code
146
+ - description: Department description
147
+
148
+ Returns:
149
+ The ID of the newly created department.
150
+ """
151
+ dept_model = DepartmentMasterCreate(**data)
152
+
153
+ if self.nmbrs.mock_mode:
154
+ return 12345 # Mock ID
155
+
156
+ try:
157
+ DepartmentType = self.nmbrs.soap_client_debtors.get_type('ns0:Department')
158
+ soap_department = DepartmentType(
159
+ Id=0, # 0 for new department
160
+ Code=dept_model.code,
161
+ Description=dept_model.description
162
+ )
163
+
164
+ response = self.nmbrs.soap_client_debtors.service.Department_Insert(
165
+ DebtorId=dept_model.debtor_id,
166
+ department=soap_department,
167
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_debtors}
168
+ )
169
+ return response
170
+
171
+ except Fault as e:
172
+ raise Exception(f"SOAP request failed: {str(e)}")
173
+ except Exception as e:
174
+ raise Exception(f"Failed to create Department: {str(e)}")
175
+
176
+ def update(self, data: Dict[str, Any]):
177
+ """
178
+ Update a master department for a debtor using SOAP API.
179
+
180
+ Args:
181
+ data: Dictionary containing department data with fields matching DepartmentMasterUpdate schema:
182
+ - debtor_id: Debtor ID
183
+ - department_id: Department ID to update
184
+ - code: Department code
185
+ - description: Department description
186
+
187
+ Returns:
188
+ Response from the API
189
+ """
190
+ dept_model = DepartmentMasterUpdate(**data)
191
+
192
+ if self.nmbrs.mock_mode:
193
+ return dept_model
194
+
195
+ try:
196
+ DepartmentType = self.nmbrs.soap_client_debtors.get_type('ns0:Department')
197
+ soap_department = DepartmentType(
198
+ Id=dept_model.department_id,
199
+ Code=dept_model.code,
200
+ Description=dept_model.description
201
+ )
202
+
203
+ response = self.nmbrs.soap_client_debtors.service.Department_Update(
204
+ DebtorId=dept_model.debtor_id,
205
+ department=soap_department,
206
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_debtors}
207
+ )
208
+ return response
209
+
210
+ except Fault as e:
211
+ raise Exception(f"SOAP request failed: {str(e)}")
212
+ except Exception as e:
213
+ raise Exception(f"Failed to update Department: {str(e)}")
214
+
215
+ def delete(self, data: Dict[str, Any]):
216
+ """
217
+ Delete a master department for a debtor using SOAP API.
218
+
219
+ Args:
220
+ data: Dictionary containing department data with fields matching DepartmentMasterDelete schema:
221
+ - debtor_id: Debtor ID
222
+ - department_id: Department ID to delete
223
+
224
+ Returns:
225
+ Response from the API
226
+ """
227
+ dept_model = DepartmentMasterDelete(**data)
228
+
229
+ if self.nmbrs.mock_mode:
230
+ return True
231
+
232
+ try:
233
+ response = self.nmbrs.soap_client_debtors.service.Department_Delete(
234
+ DebtorId=dept_model.debtor_id,
235
+ id=dept_model.department_id,
236
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_debtors}
237
+ )
238
+ return response
239
+
240
+ except Fault as e:
241
+ raise Exception(f"SOAP request failed: {str(e)}")
242
+ except Exception as e:
243
+ raise Exception(f"Failed to delete Department: {str(e)}")
@@ -1,7 +1,57 @@
1
1
  from io import BytesIO
2
+ import base64
3
+ from typing import Dict, Any, Union
2
4
 
3
5
  import pandas as pd
4
6
  import requests
7
+ from zeep.exceptions import Fault
8
+
9
+ from .schemas.document import DocumentUpload
10
+
11
+
12
+ class EmployeeDocument:
13
+ """Handle employee document operations via SOAP API."""
14
+
15
+ def __init__(self, nmbrs):
16
+ self.nmbrs = nmbrs
17
+
18
+ def upload(self, data: Dict[str, Any], file_content: bytes) -> bool:
19
+ """
20
+ Upload a document to an employee using SOAP API.
21
+
22
+ Args:
23
+ data: Dictionary containing document data with fields matching DocumentUpload schema:
24
+ - employee_id: Employee ID
25
+ - document_name: Document name (with extension, e.g., "contract.pdf")
26
+ - document_type_guid: Document type GUID
27
+ file_content: Binary content of the file to upload
28
+
29
+ Returns:
30
+ True if upload was successful
31
+ """
32
+ doc_model = DocumentUpload(**data)
33
+
34
+ if self.nmbrs.mock_mode:
35
+ return True
36
+
37
+ try:
38
+ # Convert file content to base64
39
+ body_base64 = base64.b64encode(file_content)
40
+
41
+ response = self.nmbrs.soap_client_employees.service.EmployeeDocument_UploadDocument(
42
+ EmployeeId=doc_model.employee_id,
43
+ StrDocumentName=doc_model.document_name,
44
+ Body=body_base64,
45
+ GuidDocumentType=doc_model.document_type_guid,
46
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_employees}
47
+ )
48
+
49
+ return response
50
+
51
+ except Fault as e:
52
+ raise Exception(f"SOAP request failed: {str(e)}")
53
+ except Exception as e:
54
+ raise Exception(f"Failed to upload document: {str(e)}")
5
55
 
6
56
 
7
57
  class Payslip:
@@ -28,9 +28,7 @@ class EmployeeWageTaxSettings:
28
28
  """
29
29
  wage_tax_settings = pd.DataFrame()
30
30
  for company in self.nmbrs.company_ids:
31
- company_settings = self._get(company, employee_id)
32
- if not company_settings.empty:
33
- wage_tax_settings = pd.concat([wage_tax_settings, company_settings])
31
+ wage_tax_settings = pd.concat([wage_tax_settings, self._get(company, employee_id)])
34
32
 
35
33
  valid_settings, invalid_settings = Functions.validate_data(
36
34
  df=wage_tax_settings, schema=EmployeeWageTaxSettingsGet, debug=True
@@ -62,17 +60,26 @@ class EmployeeWageTaxSettings:
62
60
  )
63
61
 
64
62
  data = self.nmbrs.get_paginated_result(request)
65
-
66
- if not data:
67
- return pd.DataFrame()
68
-
69
- # Normalize nested structure similar to departments/employments
70
63
  df = pd.json_normalize(
71
64
  data,
72
- record_path='wagetaxsettings',
65
+ record_path='wageTaxSettings',
73
66
  meta=['employeeId']
74
67
  )
75
68
 
69
+ # Flatten nested Calc30PercentRuling object if it exists
70
+ if 'Calc30PercentRuling' in df.columns:
71
+ calc_ruling_dicts = df['Calc30PercentRuling'].dropna()
72
+ if not calc_ruling_dicts.empty and isinstance(calc_ruling_dicts.iloc[0], dict):
73
+ calc_ruling_expanded = pd.json_normalize(calc_ruling_dicts)
74
+ calc_ruling_expanded.columns = [f'Calc30PercentRuling.{col}' for col in calc_ruling_expanded.columns]
75
+ calc_ruling_expanded = calc_ruling_expanded.reindex(df.index)
76
+ df = pd.concat([df.drop(columns=['Calc30PercentRuling']), calc_ruling_expanded], axis=1)
77
+ else:
78
+ # If Calc30PercentRuling is not dict-like or all None, create columns with None
79
+ for col in ['Calc30PercentRuling.Cal30PercentRuling', 'Calc30PercentRuling.endPeriod', 'Calc30PercentRuling.endYear']:
80
+ df[col] = None
81
+ df = df.drop(columns=['Calc30PercentRuling'])
82
+
76
83
  return df
77
84
 
78
85
  def create(self, employee_id: str, data: Dict[str, Any]):
@@ -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)}")