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,93 @@
1
+ import pandas as pd
2
+ import requests
3
+ import logging
4
+
5
+ from .leave import LeaveGroup
6
+ from .costcenter import Costcenter
7
+ from .costunit import Costunit
8
+ from .hours import Hours
9
+ from .bank import Bank
10
+ from .function import Functions
11
+ from zeep.exceptions import Fault
12
+ from zeep.helpers import serialize_object
13
+
14
+
15
+ class Companies:
16
+ def __init__(self, nmbrs):
17
+ self.nmbrs = nmbrs
18
+ self.costcenters = Costcenter(nmbrs)
19
+ self.costunits = Costunit(nmbrs)
20
+ self.hours = Hours(nmbrs)
21
+ self.banks = Bank(nmbrs)
22
+ self.soap_client_companies = nmbrs.soap_client_companies
23
+ self.logger = logging.getLogger(__name__)
24
+ self.leave_groups = LeaveGroup(nmbrs)
25
+
26
+ def get(self) -> pd.DataFrame:
27
+ try:
28
+ request = requests.Request(method='GET',
29
+ url=f"{self.nmbrs.base_url}companies")
30
+ data = self.nmbrs.get_paginated_result(request)
31
+ df = pd.DataFrame(data)
32
+ except requests.HTTPError as e:
33
+ if e.response.status_code == 403 and e.response.json().get("title") == "ForbiddenMultiDebtor.":
34
+ df = pd.DataFrame()
35
+ for debtor_id in self.nmbrs.debtor_ids:
36
+ request = requests.Request(method='GET',
37
+ url=f"{self.nmbrs.base_url}debtors/{debtor_id}/companies")
38
+ data = self.nmbrs.get_paginated_result(request)
39
+ df = pd.concat([df, pd.DataFrame(data)])
40
+ else:
41
+ raise e
42
+ # TODO: add validation
43
+
44
+ return df
45
+
46
+ def get_soap_ids(self) -> pd.DataFrame:
47
+ """
48
+ Get all companies using the SOAP API.
49
+
50
+ Returns:
51
+ pd.DataFrame: DataFrame containing all companies
52
+ """
53
+ try:
54
+ # Make SOAP request with the proper header structure
55
+ response = self.soap_client_companies.service.List_GetAll(
56
+ _soapheaders=[self.nmbrs.soap_auth_header]
57
+ )
58
+
59
+ # Convert response to DataFrame
60
+ if response:
61
+ # Convert Zeep objects to Python dictionaries
62
+ serialized_response = serialize_object(response)
63
+
64
+ # TODO: add validation here
65
+ # Convert to DataFrame
66
+ df = pd.DataFrame(serialized_response)
67
+ df = self.nmbrs._rename_camel_columns_to_snake_case(df)
68
+
69
+ return df
70
+ else:
71
+ return pd.DataFrame()
72
+
73
+ except Fault as e:
74
+ raise Exception(f"SOAP request failed: {str(e)}")
75
+ except Exception as e:
76
+ self.logger.exception("Exception occurred:")
77
+ raise Exception(f"Failed to get companies: {str(e)}")
78
+
79
+ def get_current_period(self) -> int:
80
+ try:
81
+ df = pd.DataFrame()
82
+ for company_id in self.nmbrs.company_ids:
83
+ request = requests.Request(method='GET',
84
+ url=f"{self.nmbrs.base_url}companies/{company_id}/period")
85
+ data = self.nmbrs.get_paginated_result(request)
86
+ df_temp = pd.json_normalize(data)
87
+ df_temp['company_id'] = company_id
88
+ df = pd.concat([df, df_temp])
89
+
90
+ return df
91
+ except Exception as e:
92
+ self.logger.exception("Exception occurred:")
93
+ raise Exception(f"Failed to get current period: {str(e)}")
@@ -0,0 +1,132 @@
1
+ import math
2
+ import pandas as pd
3
+ import requests
4
+ from typing import Dict, Any
5
+ from .schemas.contracts import ContractGet, ContractCreate, ContractUpdate, ContractDelete
6
+ from brynq_sdk_functions import Functions
7
+
8
+
9
+ class Contract:
10
+ def __init__(self, nmbrs):
11
+ self.nmbrs = nmbrs
12
+
13
+ def get(self,
14
+ created_from: str = None,
15
+ employee_id: str = None) -> (pd.DataFrame, pd.DataFrame):
16
+ contracts = pd.DataFrame()
17
+ for company in self.nmbrs.company_ids:
18
+ contracts = pd.concat([contracts, self._get(company, created_from, employee_id)])
19
+
20
+ valid_contracts, invalid_contracts = Functions.validate_data(df=contracts, schema=ContractGet, debug=True)
21
+
22
+ return valid_contracts, invalid_contracts
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/contracts",
35
+ params=params)
36
+
37
+ data = self.nmbrs.get_paginated_result(request)
38
+ df = pd.json_normalize(
39
+ data,
40
+ record_path='contracts',
41
+ meta=['employeeId']
42
+ )
43
+
44
+ df['company_id'] = company_id
45
+
46
+ return df
47
+
48
+ def create(self, employee_id: str, data: Dict[str, Any]):
49
+ """
50
+ Create a new contract for an employee using Pydantic validation.
51
+
52
+ Args:
53
+ employee_id: The ID of the employee
54
+ data: Dictionary containing contract data with fields matching
55
+ the ContractCreate schema (using camelCase field names)
56
+
57
+ Returns:
58
+ Response from the API
59
+ """
60
+ # Validate with Pydantic model - this will raise an error if required fields are missing
61
+ nested_data = self.nmbrs.flat_dict_to_nested_dict(data, ContractCreate)
62
+ contract_model = ContractCreate(**nested_data)
63
+
64
+ if self.nmbrs.mock_mode:
65
+ return contract_model
66
+
67
+ # Convert validated model to dict for API payload
68
+ payload = contract_model.model_dump_json(exclude_none=True, by_alias=True)
69
+
70
+ # Send request
71
+ resp = self.nmbrs.session.post(
72
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/contract",
73
+ data=payload,
74
+ headers={"Content-Type": "application/json"},
75
+ timeout=self.nmbrs.timeout
76
+ )
77
+ return resp
78
+
79
+ def update(self, employee_id: str, data: Dict[str, Any]):
80
+ """
81
+ Update a contract for an employee using Pydantic validation.
82
+
83
+ Args:
84
+ employee_id: The ID of the employee
85
+ data: Dictionary containing contract data with fields matching
86
+ the ContractUpdate schema (using camelCase field names)
87
+
88
+ Returns:
89
+ Response from the API
90
+ """
91
+ # Validate with Pydantic model - this will raise an error if required fields are missing
92
+ nested_data = self.nmbrs.flat_dict_to_nested_dict(data, ContractUpdate)
93
+ contract_model = ContractUpdate(**nested_data)
94
+
95
+ if self.nmbrs.mock_mode:
96
+ return contract_model
97
+
98
+ # Convert validated model to dict for API payload
99
+ payload = contract_model.model_dump_json(exclude_none=True, by_alias=True)
100
+
101
+ # Send request
102
+ resp = self.nmbrs.session.put(
103
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/contract",
104
+ data=payload,
105
+ headers={"Content-Type": "application/json"},
106
+ timeout=self.nmbrs.timeout
107
+ )
108
+ return resp
109
+
110
+ def delete(self, employee_id: str, contract_id: str):
111
+ """
112
+ Delete a contract for an employee.
113
+
114
+ Args:
115
+ employee_id: The ID of the employee
116
+ contract_id: The ID of the contract to delete
117
+
118
+ Returns:
119
+ Response from the API
120
+ """
121
+ # Create and validate a ContractDelete model
122
+ contract_model = ContractDelete(contractId=contract_id)
123
+
124
+ if self.nmbrs.mock_mode:
125
+ return contract_model
126
+
127
+ # Send request
128
+ resp = self.nmbrs.session.delete(
129
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/contracts/{contract_id}",
130
+ timeout=self.nmbrs.timeout
131
+ )
132
+ return resp
@@ -0,0 +1,166 @@
1
+ import pandas as pd
2
+ import requests
3
+ from dateutil.utils import today
4
+ from requests import HTTPError
5
+ from typing import Dict, Any, TYPE_CHECKING
6
+
7
+ 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
11
+ if TYPE_CHECKING:
12
+ from brynq_sdk_nmbrs import Nmbrs
13
+
14
+
15
+ class EmployeeCostcenter:
16
+ def __init__(self, nmbrs):
17
+ self.nmbrs: Nmbrs = nmbrs
18
+
19
+ def get(self,
20
+ created_from: str = None,
21
+ employee_id: str = None) -> tuple[pd.DataFrame, pd.DataFrame]:
22
+ costcenters = pd.DataFrame()
23
+ for company in self.nmbrs.company_ids:
24
+ costcenters = pd.concat([costcenters, self._get(company, created_from, employee_id)])
25
+
26
+ valid_costcenters, invalid_costcenters = Functions.validate_data(df=costcenters, schema=EmployeeCostcenterGet, debug=True)
27
+
28
+ return valid_costcenters, invalid_costcenters
29
+
30
+ def _get(self,
31
+ company_id: str,
32
+ created_from: str = None,
33
+ employee_id: str = None) -> pd.DataFrame:
34
+ params = {}
35
+ if created_from:
36
+ params['createdFrom'] = created_from
37
+ if employee_id:
38
+ params['employeeId'] = employee_id
39
+ request = requests.Request(method='GET',
40
+ url=f"{self.nmbrs.base_url}companies/{company_id}/employees/costcenters",
41
+ params=params)
42
+ data = self.nmbrs.get_paginated_result(request)
43
+ df = pd.json_normalize(
44
+ data,
45
+ record_path='employeeCostCenters',
46
+ meta=['employeeId']
47
+ )
48
+
49
+ return df
50
+
51
+ def update(self, employee_id: str, data: Dict[str, Any]):
52
+ """
53
+ Update a costcenter for an employee using Pydantic validation.
54
+
55
+ Args:
56
+ employee_id: The ID of the employee
57
+ data: Dictionary containing costcenter data with fields matching
58
+ the EmployeeCostcenterUpdate schema (using camelCase field names)
59
+
60
+ Returns:
61
+ Response from the API
62
+ """
63
+ # this is the Nmbrs GUID that is returned after creating an employee, for some reason also included in body here.
64
+ data['employee_id'] = employee_id
65
+ data['default'] = True
66
+ # Validate with Pydantic model - this will raise an error if required fields are missing
67
+ nested_data = self.nmbrs.flat_dict_to_nested_dict(data, EmployeeCostcenterUpdate)
68
+ costcenter_model = EmployeeCostcenterUpdate(**nested_data)
69
+
70
+ if self.nmbrs.mock_mode:
71
+ return costcenter_model
72
+
73
+ # Convert validated model to dict for API payload
74
+ payload = costcenter_model.model_dump(exclude_none=True, by_alias=True)
75
+
76
+ # Send request
77
+ resp = self.nmbrs.session.put(
78
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/employeecostcenter",
79
+ json=payload,
80
+ timeout=self.nmbrs.timeout
81
+ )
82
+ return resp
83
+
84
+
85
+
86
+ class Costcenter:
87
+ def __init__(self, nmbrs):
88
+ self.nmbrs = nmbrs
89
+
90
+ def get(self) -> tuple[pd.DataFrame, pd.DataFrame]:
91
+ costcenters = pd.DataFrame()
92
+ for company in self.nmbrs.company_ids:
93
+ costcenters = pd.concat([costcenters, self._get(company)])
94
+
95
+ valid_costcenters, invalid_costcenters = Functions.validate_data(df=costcenters, schema=CostcenterGet, debug=True)
96
+
97
+ return valid_costcenters, invalid_costcenters
98
+
99
+ def _get(self,
100
+ company_id: str):
101
+ request = requests.Request(method='GET',
102
+ url=f"{self.nmbrs.base_url}companies/{company_id}/costcenters")
103
+ data = self.nmbrs.get_paginated_result(request)
104
+ df = pd.DataFrame(data)
105
+
106
+ return df
107
+
108
+ def create(self, company_id: str, data: Dict[str, Any]):
109
+ """
110
+ Create a new costcenter using Pydantic validation.
111
+
112
+ Args:
113
+ company_id: The ID of the company
114
+ data: Dictionary containing costcenter data with fields matching
115
+ the CostcenterCreate schema (using camelCase field names)
116
+
117
+ Returns:
118
+ Response from the API
119
+ """
120
+ # Validate with Pydantic model - this will raise an error if required fields are missing
121
+ nested_data = self.nmbrs.flat_dict_to_nested_dict(data, CostcenterCreate)
122
+ costcenter_model = CostcenterCreate(**nested_data)
123
+
124
+ if self.nmbrs.mock_mode:
125
+ return costcenter_model
126
+
127
+ # Convert validated model to dict for API payload
128
+ payload = costcenter_model.model_dump(exclude_none=True, by_alias=True)
129
+
130
+ # Send request
131
+ resp = self.nmbrs.session.post(
132
+ url=f"{self.nmbrs.base_url}companies/{company_id}/costcenter",
133
+ json=payload,
134
+ timeout=self.nmbrs.timeout
135
+ )
136
+ return resp
137
+
138
+ def update(self, company_id: str, data: Dict[str, Any]):
139
+ """
140
+ Update a costcenter using Pydantic validation.
141
+
142
+ Args:
143
+ company_id: The ID of the company
144
+ data: Dictionary containing costcenter data with fields matching
145
+ the CostcenterUpdate schema (using camelCase field names)
146
+
147
+ Returns:
148
+ Response from the API
149
+ """
150
+ # Validate with Pydantic model - this will raise an error if required fields are missing
151
+ nested_data = self.nmbrs.flat_dict_to_nested_dict(data, CostcenterUpdate)
152
+ costcenter_model = CostcenterUpdate(**nested_data)
153
+
154
+ if self.nmbrs.mock_mode:
155
+ return costcenter_model
156
+
157
+ # Convert validated model to dict for API payload
158
+ payload = costcenter_model.model_dump(exclude_none=True, by_alias=True)
159
+
160
+ # Send request
161
+ resp = self.nmbrs.session.put(
162
+ url=f"{self.nmbrs.base_url}companies/{company_id}/costcenter",
163
+ json=payload,
164
+ timeout=self.nmbrs.timeout
165
+ )
166
+ return resp
@@ -0,0 +1,90 @@
1
+ import pandas as pd
2
+ import requests
3
+ from typing import Dict, Any
4
+
5
+ from brynq_sdk_functions import Functions
6
+ from .schemas.costunit import CostunitGet, CostunitCreate, CostunitUpdate, CostunitDelete
7
+
8
+
9
+ class Costunit:
10
+ def __init__(self, nmbrs):
11
+ self.nmbrs = nmbrs
12
+
13
+ def get(self) -> tuple[pd.DataFrame, pd.DataFrame]:
14
+ costunits = pd.DataFrame()
15
+ for company in self.nmbrs.company_ids:
16
+ costunits = pd.concat([costunits, self._get(company)])
17
+
18
+ valid_costunits, invalid_costunits = Functions.validate_data(df=costunits, schema=CostunitGet, debug=True)
19
+
20
+ return valid_costunits, invalid_costunits
21
+
22
+ def _get(self,
23
+ company_id: str):
24
+ request = requests.Request(method='GET',
25
+ url=f"{self.nmbrs.base_url}companies/{company_id}/costunits")
26
+
27
+ data = self.nmbrs.get_paginated_result(request)
28
+ df = pd.DataFrame(data)
29
+
30
+ return df
31
+
32
+ def create(self, company_id: str, data: Dict[str, Any]):
33
+ """
34
+ Create a new costunit using Pydantic validation.
35
+
36
+ Args:
37
+ company_id: The ID of the company
38
+ data: Dictionary containing costunit data with fields matching
39
+ the CostunitCreate schema (using camelCase field names)
40
+
41
+ Returns:
42
+ Response from the API
43
+ """
44
+ # Validate with Pydantic model - this will raise an error if required fields are missing
45
+ nested_data = self.nmbrs.flat_dict_to_nested_dict(data, CostunitCreate)
46
+ costunit_model = CostunitCreate(**nested_data)
47
+
48
+ if self.nmbrs.mock_mode:
49
+ return costunit_model
50
+
51
+ # Convert validated model to dict for API payload
52
+ payload = costunit_model.model_dump(exclude_none=True, by_alias=True)
53
+
54
+ # Send request
55
+ resp = self.nmbrs.session.post(
56
+ url=f"{self.nmbrs.base_url}companies/{company_id}/costunit",
57
+ json=payload,
58
+ timeout=self.nmbrs.timeout
59
+ )
60
+ return resp
61
+
62
+ def update(self, company_id: str, data: Dict[str, Any]):
63
+ """
64
+ Update a costunit using Pydantic validation.
65
+
66
+ Args:
67
+ company_id: The ID of the company
68
+ data: Dictionary containing costunit data with fields matching
69
+ the CostunitUpdate schema (using camelCase field names)
70
+
71
+ Returns:
72
+ Response from the API
73
+ """
74
+ # Validate with Pydantic model - this will raise an error if required fields are missing
75
+ nested_data = self.nmbrs.flat_dict_to_nested_dict(data, CostunitUpdate)
76
+ costunit_model = CostunitUpdate(**nested_data)
77
+
78
+ if self.nmbrs.mock_mode:
79
+ return costunit_model
80
+
81
+ # Convert validated model to dict for API payload
82
+ payload = costunit_model.model_dump(exclude_none=True, by_alias=True)
83
+
84
+ # Send request
85
+ resp = self.nmbrs.session.put(
86
+ url=f"{self.nmbrs.base_url}companies/{company_id}/costunit",
87
+ json=payload,
88
+ timeout=self.nmbrs.timeout
89
+ )
90
+ return resp
@@ -0,0 +1,137 @@
1
+ import pandas as pd
2
+ import requests
3
+ from typing import Dict, Any
4
+ from .schemas.days import (
5
+ VariableDaysGet, VariableDaysCreate, FixedDaysCreate, FixedDaysGet
6
+ )
7
+ from brynq_sdk_functions import Functions
8
+
9
+
10
+ class VariableDays:
11
+ def __init__(self, nmbrs):
12
+ self.nmbrs = nmbrs
13
+
14
+ def get(self,
15
+ employee_id: str,
16
+ created_from: str = None,
17
+ period: int = None,
18
+ year: int = None) -> tuple[pd.DataFrame, pd.DataFrame]:
19
+ params = {}
20
+ if created_from:
21
+ params['createdFrom'] = created_from
22
+ if period:
23
+ params['period'] = period
24
+ if year:
25
+ params['year'] = year
26
+ request = requests.Request(method='GET',
27
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/variabledays",
28
+ params=params)
29
+
30
+ data = self.nmbrs.get_paginated_result(request)
31
+
32
+ df = pd.DataFrame(data)
33
+
34
+ df['employeeId'] = employee_id # Add employee_id for tracking
35
+ df['period'] = period
36
+ df['year'] = year
37
+ # Validate data using the schema
38
+ valid_days, invalid_days = Functions.validate_data(df=df, schema=VariableDaysGet, debug=True)
39
+
40
+ return valid_days, invalid_days
41
+
42
+ def create(self, employee_id: str, data: Dict[str, Any]):
43
+ """
44
+ Create variable hours for an employee using Pydantic validation.
45
+
46
+ Args:
47
+ employee_id: The ID of the employee
48
+ data: Dictionary containing hours data with fields matching
49
+ the VariableHoursCreate schema (using camelCase field names)
50
+
51
+ Returns:
52
+ Response from the API
53
+ """
54
+ # Validate with Pydantic model
55
+ nested_data = self.nmbrs.flat_dict_to_nested_dict(data, VariableDaysCreate)
56
+ hours_model = VariableDaysCreate(**nested_data)
57
+
58
+ if self.nmbrs.mock_mode:
59
+ return hours_model
60
+
61
+ # Convert validated model to dict for API payload
62
+ payload = hours_model.model_dump(exclude_none=True, by_alias=True)
63
+
64
+ # Send request
65
+ resp = self.nmbrs.session.post(
66
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/variabledays",
67
+ json=payload,
68
+ timeout=self.nmbrs.timeout
69
+ )
70
+ return resp
71
+
72
+
73
+ class FixedDays:
74
+ def __init__(self, nmbrs):
75
+ self.nmbrs = nmbrs
76
+
77
+ def get(self,
78
+ company_id: str,
79
+ created_from: str = None,
80
+ employee_id: str = None,
81
+ period: int = None,
82
+ year: int = None) -> tuple[pd.DataFrame, pd.DataFrame]:
83
+ params = {}
84
+ if created_from:
85
+ params['createdFrom'] = created_from
86
+ if employee_id:
87
+ params['employeeId'] = employee_id
88
+ if period:
89
+ params['period'] = period
90
+ if year:
91
+ params['year'] = year
92
+ request = requests.Request(method='GET',
93
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/fixeddays",
94
+ params=params)
95
+
96
+ data = self.nmbrs.get_paginated_result(request)
97
+ df = pd.DataFrame(data)
98
+ df['employeeId'] = employee_id # Add employee_id for tracking
99
+ df['period'] = period
100
+ df['year'] = year
101
+
102
+ # Validate data using the schema
103
+ valid_days, invalid_days = Functions.validate_data(df=df, schema=VariableDaysGet, debug=True)
104
+
105
+ return valid_days, invalid_days
106
+
107
+ return df
108
+
109
+ def create(self, employee_id: str, data: Dict[str, Any]):
110
+ """
111
+ Create fixed hours for an employee using Pydantic validation.
112
+
113
+ Args:
114
+ employee_id: The ID of the employee
115
+ data: Dictionary containing hours data with fields matching
116
+ the FixedHoursCreate schema (using camelCase field names)
117
+
118
+ Returns:
119
+ Response from the API
120
+ """
121
+ # Validate with Pydantic model
122
+ nested_data = self.nmbrs.flat_dict_to_nested_dict(data, FixedDaysCreate)
123
+ hours_model = FixedDaysCreate(**nested_data)
124
+
125
+ if self.nmbrs.mock_mode:
126
+ return hours_model
127
+
128
+ # Convert validated model to dict for API payload
129
+ payload = hours_model.model_dump(exclude_none=True, by_alias=True)
130
+
131
+ # Send request
132
+ resp = self.nmbrs.session.post(
133
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/fixeddays",
134
+ json=payload,
135
+ timeout=self.nmbrs.timeout
136
+ )
137
+ return resp
@@ -0,0 +1,25 @@
1
+ import pandas as pd
2
+ import requests
3
+ from brynq_sdk_functions import Functions
4
+ from .department import Departments
5
+ from .function import Functions as NmbrsFunctions
6
+ from .schemas.debtor import DebtorsGet
7
+
8
+
9
+ class Debtors:
10
+ def __init__(self, nmbrs):
11
+ self.nmbrs = nmbrs
12
+ self.departments = Departments(nmbrs)
13
+ self.functions = NmbrsFunctions(nmbrs)
14
+
15
+
16
+ def get(self) -> (pd.DataFrame, pd.DataFrame):
17
+ request = requests.Request(method='GET',
18
+ url=f"{self.nmbrs.base_url}debtors")
19
+ data = self.nmbrs.get_paginated_result(request)
20
+
21
+ df = pd.DataFrame(data)
22
+
23
+ valid_debtors, invalid_debtors = Functions.validate_data(df=df, schema=DebtorsGet, debug=True)
24
+
25
+ return valid_debtors, invalid_debtors