brynq-sdk-nmbrs 2.3.1__py3-none-any.whl

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