brynq-sdk-nmbrs 2.3.0__tar.gz → 2.3.1.dev0__tar.gz

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 (59) hide show
  1. brynq_sdk_nmbrs-2.3.1.dev0/PKG-INFO +21 -0
  2. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/__init__.py +36 -22
  3. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/costcenter.py +53 -8
  4. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/costunit.py +16 -4
  5. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/department.py +36 -27
  6. brynq_sdk_nmbrs-2.3.1.dev0/brynq_sdk_nmbrs/employee_wage_tax_settings.py +106 -0
  7. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/employees.py +119 -24
  8. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/employment.py +12 -4
  9. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/salaries.py +5 -2
  10. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/address.py +4 -4
  11. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/contracts.py +25 -9
  12. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/costcenter.py +57 -16
  13. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/department.py +8 -5
  14. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/employees.py +42 -36
  15. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/employment.py +10 -8
  16. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/function.py +6 -6
  17. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/salary.py +19 -11
  18. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/wage_tax.py +4 -4
  19. brynq_sdk_nmbrs-2.3.1.dev0/brynq_sdk_nmbrs/schemas/wage_tax_settings.py +63 -0
  20. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/wage_tax.py +1 -1
  21. brynq_sdk_nmbrs-2.3.1.dev0/brynq_sdk_nmbrs.egg-info/PKG-INFO +21 -0
  22. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs.egg-info/SOURCES.txt +2 -0
  23. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/setup.py +2 -2
  24. brynq_sdk_nmbrs-2.3.0/PKG-INFO +0 -10
  25. brynq_sdk_nmbrs-2.3.0/brynq_sdk_nmbrs.egg-info/PKG-INFO +0 -10
  26. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/absence.py +0 -0
  27. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/address.py +0 -0
  28. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/bank.py +0 -0
  29. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/children.py +0 -0
  30. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/companies.py +0 -0
  31. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/contract.py +0 -0
  32. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/days.py +0 -0
  33. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/debtors.py +0 -0
  34. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/document.py +0 -0
  35. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/function.py +0 -0
  36. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/hours.py +0 -0
  37. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/leave.py +0 -0
  38. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/manager.py +0 -0
  39. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/salary_tables.py +0 -0
  40. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schedules.py +0 -0
  41. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/__init__.py +0 -0
  42. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/absence.py +0 -0
  43. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/bank.py +0 -0
  44. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/costunit.py +0 -0
  45. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/days.py +0 -0
  46. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/debtor.py +0 -0
  47. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/hours.py +0 -0
  48. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/leave.py +0 -0
  49. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/manager.py +0 -0
  50. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/schedules.py +0 -0
  51. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/social_insurance.py +0 -0
  52. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/schemas/wagecomponents.py +0 -0
  53. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/social_insurance.py +0 -0
  54. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs/wagecomponents.py +0 -0
  55. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs.egg-info/dependency_links.txt +0 -0
  56. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs.egg-info/not-zip-safe +0 -0
  57. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs.egg-info/requires.txt +0 -0
  58. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/brynq_sdk_nmbrs.egg-info/top_level.txt +0 -0
  59. {brynq_sdk_nmbrs-2.3.0 → brynq_sdk_nmbrs-2.3.1.dev0}/setup.cfg +0 -0
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: brynq_sdk_nmbrs
3
+ Version: 2.3.1.dev0
4
+ Summary: Nmbrs wrapper from BrynQ
5
+ Author: BrynQ
6
+ Author-email: support@brynq.com
7
+ License: BrynQ License
8
+ Requires-Dist: brynq-sdk-brynq<5,>=4
9
+ Requires-Dist: pandas<3.0.0,>=2.2.0
10
+ Requires-Dist: pydantic<3.0.0,>=2.5.0
11
+ Requires-Dist: pandera<1.0.0,>=0.16.0
12
+ Requires-Dist: zeep<5.0.0,>=4.0.0
13
+ Requires-Dist: brynq-sdk-functions>=2.0.5
14
+ Dynamic: author
15
+ Dynamic: author-email
16
+ Dynamic: description
17
+ Dynamic: license
18
+ Dynamic: requires-dist
19
+ Dynamic: summary
20
+
21
+ Nmbrs wrapper from BrynQ
@@ -1,34 +1,38 @@
1
- import warnings
2
- from typing import Union, List, Literal, Optional, get_args, get_origin
1
+ import os
3
2
  import re
3
+ import warnings
4
+ from typing import List, Literal, Optional, Union, get_args, get_origin
5
+
4
6
  import pandas as pd
5
- from pydantic import BaseModel
6
7
  import requests
8
+ from pydantic import BaseModel
9
+ from zeep import Client, Settings
10
+
7
11
  from brynq_sdk_brynq import BrynQ
12
+
8
13
  from .absence import Absence
9
- from .wage_tax import WageTax
10
14
  from .address import Address
11
15
  from .bank import Bank
12
- from zeep import Client, Settings
13
16
  from .children import Children
14
17
  from .companies import Companies
15
- from .debtors import Debtors
16
18
  from .contract import Contract
17
- from .costcenter import EmployeeCostcenter, Costcenter
19
+ from .costcenter import Costcenter, EmployeeCostcenter
18
20
  from .costunit import Costunit
19
- from .department import EmployeeDepartment
21
+ from .days import FixedDays, VariableDays
22
+ from .debtors import Debtors
23
+ from .department import Departments, EmployeeDepartment
20
24
  from .document import Payslip
25
+ from .employee_wage_tax_settings import EmployeeWageTaxSettings
21
26
  from .employees import Employees
22
- from .salary_tables import SalaryTables, SalaryScales, SalarySteps
23
27
  from .employment import Employment
24
- from .function import EmployeeFunction
25
- from .hours import VariableHours, FixedHours
26
- from .days import VariableDays, FixedDays
28
+ from .function import EmployeeFunction, Functions
29
+ from .hours import FixedHours, VariableHours
27
30
  from .manager import EmployeeManager, Manager
28
31
  from .salaries import Salaries
32
+ from .salary_tables import SalaryScales, SalarySteps, SalaryTables
29
33
  from .schedules import Schedule
34
+ from .wage_tax import WageTax
30
35
  from .wagecomponents import EmployeeFixedWageComponents, EmployeeVariableWageComponents
31
- import os
32
36
 
33
37
 
34
38
  class Nmbrs(BrynQ):
@@ -67,10 +71,14 @@ class Nmbrs(BrynQ):
67
71
  'https://api.nmbrs.nl/soap/v3/EmployeeService.asmx?wsdl',
68
72
  settings=self.soap_settings
69
73
  )
70
- self.soap_auth_header = self._get_soap_auth_header()
74
+ self.soap_auth_header = self._get_soap_auth_header_companies()
75
+ self.soap_auth_header_employees = self._get_soap_auth_header_employees()
71
76
  # Following methods can only be used if the SOAP authentication header is set (optional, this is not always in scope for all integrations)
72
77
  if self.soap_auth_header is not None:
78
+ self.companies = Companies(self)
79
+ self.employees = Employees(self)
73
80
  self.soap_company_ids = self.companies.get_soap_ids()
81
+ self.soap_employee_ids = self.employees.get_soap_ids()
74
82
  self.salary_tables = SalaryTables(self)
75
83
  self.salary_scales = SalaryScales(self)
76
84
  self.salary_steps = SalarySteps(self)
@@ -84,13 +92,15 @@ class Nmbrs(BrynQ):
84
92
  self.debtor = Debtors(self)
85
93
  self.companies = Companies(self)
86
94
  self.contract = Contract(self)
87
- self.department = EmployeeDepartment(self)
95
+ self.employee_department = EmployeeDepartment(self)
96
+ self.department = Departments(self)
88
97
  debtors, _ = self.debtor.get()
89
98
  self.debtor_ids = debtors['debtor_id'].to_list()
90
99
  self.company_ids = self.companies.get()['companyId'].to_list()
91
100
  self.employees = Employees(self)
92
101
  self.employment = Employment(self)
93
- self.function = EmployeeFunction(self)
102
+ self.employee_function = EmployeeFunction(self)
103
+ self.functions = Functions(self)
94
104
  self.fixed_hours = FixedHours(self)
95
105
  self.fixed_days = FixedDays(self)
96
106
  self.variable_hours = VariableHours(self)
@@ -99,6 +109,10 @@ class Nmbrs(BrynQ):
99
109
  self.employee_manager = EmployeeManager(self)
100
110
  self.salaries = Salaries(self)
101
111
  self.schedule = Schedule(self)
112
+ self.cost_center = Costcenter(self)
113
+ self.employee_cost_center = EmployeeCostcenter(self)
114
+ self.cost_unit = Costunit(self)
115
+ self.employee_wage_tax_settings = EmployeeWageTaxSettings(self)
102
116
  self.fixed_wagecomponents = EmployeeFixedWageComponents(self)
103
117
  self.variable_wagecomponents = EmployeeVariableWageComponents(self)
104
118
  self.current_period = self.companies.get_current_period()
@@ -114,7 +128,7 @@ class Nmbrs(BrynQ):
114
128
 
115
129
  return headers
116
130
 
117
- def _get_soap_auth_header(self):
131
+ def _get_soap_auth_header_companies(self):
118
132
  """
119
133
  Creates the SOAP authentication header using credentials from initial_credentials.
120
134
 
@@ -146,17 +160,17 @@ class Nmbrs(BrynQ):
146
160
  Returns:
147
161
  AuthHeaderWithDomainType: The authentication header for SOAP requests
148
162
  """
149
- initial_credentials = self.get_system_credential(system='nmbrs', label='bob')
150
- config = initial_credentials.get("config", {})
163
+ initial_credentials = self.interfaces.credentials.get(system='nmbrs', system_type=self.system_type)
164
+ custom_data = initial_credentials.get("custom_data", {})
151
165
 
152
166
  # Get the AuthHeaderWithDomain type from the WSDL
153
167
  AuthHeaderWithDomainType = self.soap_client_employees.get_element('ns0:AuthHeaderWithDomain')
154
168
 
155
169
  # Create the auth header using credentials from config
156
170
  auth_header = AuthHeaderWithDomainType(
157
- Username=config.get("soap_api_username"),
158
- Token=config.get("soap_api_token"),
159
- Domain=config.get("soap_api_domain")
171
+ Username=custom_data.get("soap_api_username"),
172
+ Token=custom_data.get("soap_api_token"),
173
+ Domain=custom_data.get("soap_api_domain")
160
174
  )
161
175
 
162
176
  return auth_header
@@ -1,13 +1,20 @@
1
+ from typing import TYPE_CHECKING, Any, Dict
2
+
1
3
  import pandas as pd
2
4
  import requests
3
- from dateutil.utils import today
4
- from requests import HTTPError
5
- from typing import Dict, Any, TYPE_CHECKING
6
5
 
7
6
  from brynq_sdk_functions import Functions
8
- from .schemas.costcenter import CostcenterGet, EmployeeCostcenterGet
9
- from .schemas.costcenter import EmployeeCostcenterUpdate, EmployeeCostcenterDelete
10
- from .schemas.costcenter import CostcenterCreate, CostcenterUpdate, CostcenterDelete
7
+
8
+ from .schemas.costcenter import (
9
+ CostcenterCreate,
10
+ CostcenterGet,
11
+ CostCentersResponse,
12
+ CostcenterUpdate,
13
+ EmployeeCostcenterGet,
14
+ EmployeeCostCentersResponse,
15
+ EmployeeCostcenterUpdate,
16
+ )
17
+
11
18
  if TYPE_CHECKING:
12
19
  from brynq_sdk_nmbrs import Nmbrs
13
20
 
@@ -40,12 +47,40 @@ class EmployeeCostcenter:
40
47
  url=f"{self.nmbrs.base_url}companies/{company_id}/employees/costcenters",
41
48
  params=params)
42
49
  data = self.nmbrs.get_paginated_result(request)
50
+
51
+ # Parse and validate API response using Pydantic models
52
+ if not data:
53
+ return pd.DataFrame()
54
+
55
+ # Validate response structure with Pydantic
56
+ response = EmployeeCostCentersResponse(data=data)
57
+
58
+ # Serialize models to dicts using model_dump with by_alias=True
59
+ serialized_data = [item.model_dump(by_alias=True, mode='json') for item in response.data]
60
+
61
+ # Use json_normalize to flatten nested structure efficiently
43
62
  df = pd.json_normalize(
44
- data,
63
+ serialized_data,
45
64
  record_path='employeeCostCenters',
46
65
  meta=['employeeId']
47
66
  )
48
67
 
68
+ # Flatten nested costUnits object if it exists
69
+ if 'costUnits' in df.columns:
70
+ # Check if costUnits contains dict-like objects
71
+ cost_units_dicts = df['costUnits'].dropna()
72
+ if not cost_units_dicts.empty and isinstance(cost_units_dicts.iloc[0], dict):
73
+ cost_units_expanded = pd.json_normalize(cost_units_dicts)
74
+ cost_units_expanded.columns = [f'costUnits.{col}' for col in cost_units_expanded.columns]
75
+ # Reindex to match original DataFrame index and fill missing values with None
76
+ cost_units_expanded = cost_units_expanded.reindex(df.index)
77
+ df = pd.concat([df.drop(columns=['costUnits']), cost_units_expanded], axis=1)
78
+ else:
79
+ # If costUnits is not dict-like or all None, create columns with None
80
+ for col in ['costUnits.costUnitId', 'costUnits.code', 'costUnits.description']:
81
+ df[col] = None
82
+ df = df.drop(columns=['costUnits'])
83
+
49
84
  return df
50
85
 
51
86
  def update(self, employee_id: str, data: Dict[str, Any]):
@@ -101,7 +136,17 @@ class Costcenter:
101
136
  request = requests.Request(method='GET',
102
137
  url=f"{self.nmbrs.base_url}companies/{company_id}/costcenters")
103
138
  data = self.nmbrs.get_paginated_result(request)
104
- df = pd.DataFrame(data)
139
+
140
+ # Parse and validate API response using Pydantic models
141
+ if not data:
142
+ return pd.DataFrame()
143
+
144
+ # Validate response structure with Pydantic
145
+ response = CostCentersResponse(data=data)
146
+
147
+ # Serialize models to dicts and convert to DataFrame efficiently
148
+ serialized_data = [item.model_dump(by_alias=True, mode='json') for item in response.data]
149
+ df = pd.DataFrame(serialized_data)
105
150
 
106
151
  return df
107
152
 
@@ -1,9 +1,16 @@
1
+ from typing import Any, Dict
2
+
1
3
  import pandas as pd
2
4
  import requests
3
- from typing import Dict, Any
4
5
 
5
6
  from brynq_sdk_functions import Functions
6
- from .schemas.costunit import CostunitGet, CostunitCreate, CostunitUpdate, CostunitDelete
7
+
8
+ from .schemas.costunit import (
9
+ CostunitCreate,
10
+ CostunitDelete,
11
+ CostunitGet,
12
+ CostunitUpdate,
13
+ )
7
14
 
8
15
 
9
16
  class Costunit:
@@ -13,7 +20,9 @@ class Costunit:
13
20
  def get(self) -> tuple[pd.DataFrame, pd.DataFrame]:
14
21
  costunits = pd.DataFrame()
15
22
  for company in self.nmbrs.company_ids:
16
- costunits = pd.concat([costunits, self._get(company)])
23
+ company_costunits = self._get(company)
24
+ if not company_costunits.empty:
25
+ costunits = pd.concat([costunits, company_costunits])
17
26
 
18
27
  valid_costunits, invalid_costunits = Functions.validate_data(df=costunits, schema=CostunitGet, debug=True)
19
28
 
@@ -22,9 +31,12 @@ class Costunit:
22
31
  def _get(self,
23
32
  company_id: str):
24
33
  request = requests.Request(method='GET',
25
- url=f"{self.nmbrs.base_url}companies/{company_id}/costunits")
34
+ url=f"{self.nmbrs.base_url}companies/{company_id}/costUnits")
26
35
 
27
36
  data = self.nmbrs.get_paginated_result(request)
37
+ if not data:
38
+ return pd.DataFrame()
39
+
28
40
  df = pd.DataFrame(data)
29
41
 
30
42
  return df
@@ -1,10 +1,19 @@
1
1
  import math
2
+ from typing import Any, Dict
3
+
2
4
  import pandas as pd
3
5
  import requests
4
- from typing import Dict, Any
5
- from .schemas.department import DepartmentCreate, EmployeeDepartmentUpdate, Period, EmployeeDepartmentGet, DepartmentGet
6
+
6
7
  from brynq_sdk_functions import Functions
7
8
 
9
+ from .schemas.department import (
10
+ DepartmentCreate,
11
+ DepartmentGet,
12
+ EmployeeDepartmentGet,
13
+ EmployeeDepartmentUpdate,
14
+ Period,
15
+ )
16
+
8
17
 
9
18
  class EmployeeDepartment:
10
19
  def __init__(self, nmbrs):
@@ -43,35 +52,35 @@ class EmployeeDepartment:
43
52
 
44
53
  return df
45
54
 
46
- def create(self, employee_id: str, data: Dict[str, Any]):
47
- """
48
- Create a new department for an employee using Pydantic validation.
55
+ # def create(self, employee_id: str, data: Dict[str, Any]):
56
+ # """
57
+ # Create a new department for an employee using Pydantic validation.
49
58
 
50
- Args:
51
- employee_id: The ID of the employee
52
- data: Dictionary containing department data with fields matching
53
- the DepartmentCreate schema (using camelCase field names)
59
+ # Args:
60
+ # employee_id: The ID of the employee
61
+ # data: Dictionary containing department data with fields matching
62
+ # the DepartmentCreate schema (using camelCase field names)
54
63
 
55
- Returns:
56
- Response from the API
57
- """
58
- # Validate with Pydantic model
59
- nested_data = self.nmbrs.flat_dict_to_nested_dict(data, DepartmentCreate)
60
- department_model = DepartmentCreate(**nested_data)
64
+ # Returns:
65
+ # Response from the API
66
+ # """
67
+ # # Validate with Pydantic model
68
+ # nested_data = self.nmbrs.flat_dict_to_nested_dict(data, DepartmentCreate)
69
+ # department_model = DepartmentCreate(**nested_data)
61
70
 
62
- if self.nmbrs.mock_mode:
63
- return department_model
71
+ # if self.nmbrs.mock_mode:
72
+ # return department_model
64
73
 
65
- # Convert validated model to dict for API payload
66
- payload = department_model.model_dump(exclude_none=True, by_alias=True)
74
+ # # Convert validated model to dict for API payload
75
+ # payload = department_model.model_dump(exclude_none=True, by_alias=True)
67
76
 
68
- # Send request
69
- resp = self.nmbrs.session.post(
70
- url=f"{self.nmbrs.base_url}employees/{employee_id}/department",
71
- json=payload,
72
- timeout=self.nmbrs.timeout
73
- )
74
- return resp
77
+ # # Send request
78
+ # resp = self.nmbrs.session.post(
79
+ # url=f"{self.nmbrs.base_url}employees/{employee_id}/department",
80
+ # json=payload,
81
+ # timeout=self.nmbrs.timeout
82
+ # )
83
+ # return resp
75
84
 
76
85
  def update(self, employee_id: str, data: Dict[str, Any]):
77
86
  """
@@ -93,7 +102,7 @@ class EmployeeDepartment:
93
102
  return department_model
94
103
 
95
104
  # Convert validated model to dict for API payload
96
- payload = department_model.model_dump(exclude_none=True, by_alias=True)
105
+ payload = department_model.model_dump(exclude_none=True, by_alias=True, mode='json')
97
106
 
98
107
  # Send request
99
108
  resp = self.nmbrs.session.put(
@@ -0,0 +1,106 @@
1
+ from typing import Any, Dict
2
+
3
+ import pandas as pd
4
+ import requests
5
+
6
+ from brynq_sdk_functions import Functions
7
+
8
+ from .schemas.wage_tax_settings import (
9
+ EmployeeWageTaxSettingsCreate,
10
+ EmployeeWageTaxSettingsGet,
11
+ )
12
+
13
+
14
+ class EmployeeWageTaxSettings:
15
+ def __init__(self, nmbrs):
16
+ self.nmbrs = nmbrs
17
+
18
+ def get(self,
19
+ employee_id: str = None) -> tuple[pd.DataFrame, pd.DataFrame]:
20
+ """
21
+ Get wage tax settings history for employees in companies.
22
+
23
+ Args:
24
+ employee_id: Optional filter for a specific employee ID
25
+
26
+ Returns:
27
+ Tuple of (valid_data, invalid_data) DataFrames
28
+ """
29
+ wage_tax_settings = pd.DataFrame()
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])
34
+
35
+ valid_settings, invalid_settings = Functions.validate_data(
36
+ df=wage_tax_settings, schema=EmployeeWageTaxSettingsGet, debug=True
37
+ )
38
+
39
+ return valid_settings, invalid_settings
40
+
41
+ def _get(self,
42
+ company_id: str,
43
+ employee_id: str = None) -> pd.DataFrame:
44
+ """
45
+ Get wage tax settings history for a specific company.
46
+
47
+ Args:
48
+ company_id: The ID of the company
49
+ employee_id: Optional filter for a specific employee ID
50
+
51
+ Returns:
52
+ DataFrame with wage tax settings
53
+ """
54
+ params = {}
55
+ if employee_id:
56
+ params['employeeId'] = employee_id
57
+
58
+ request = requests.Request(
59
+ method='GET',
60
+ url=f"{self.nmbrs.base_url}companies/{company_id}/employees/wagetaxsettings",
61
+ params=params
62
+ )
63
+
64
+ 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
+ df = pd.json_normalize(
71
+ data,
72
+ record_path='wagetaxsettings',
73
+ meta=['employeeId']
74
+ )
75
+
76
+ return df
77
+
78
+ def create(self, employee_id: str, data: Dict[str, Any]):
79
+ """
80
+ Create wage tax settings for an employee using Pydantic validation.
81
+
82
+ Args:
83
+ employee_id: The ID of the employee
84
+ data: Dictionary containing wage tax settings data with fields matching
85
+ the EmployeeWageTaxSettingsCreate schema (using camelCase field names)
86
+
87
+ Returns:
88
+ Response from the API
89
+ """
90
+ # Validate with Pydantic model
91
+ nested_data = self.nmbrs.flat_dict_to_nested_dict(data, EmployeeWageTaxSettingsCreate)
92
+ settings_model = EmployeeWageTaxSettingsCreate(**nested_data)
93
+
94
+ if self.nmbrs.mock_mode:
95
+ return settings_model
96
+
97
+ # Convert validated model to dict for API payload
98
+ payload = settings_model.model_dump(exclude_none=True, by_alias=True, mode='json')
99
+
100
+ # Send request
101
+ resp = self.nmbrs.session.post(
102
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/wagetaxsettings",
103
+ json=payload,
104
+ timeout=self.nmbrs.timeout
105
+ )
106
+ return resp