brynq-sdk-nmbrs 2.3.4.dev0__tar.gz → 2.4.5__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 (72) hide show
  1. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/PKG-INFO +1 -1
  2. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/__init__.py +95 -91
  3. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/absence.py +1 -4
  4. brynq_sdk_nmbrs-2.4.5/brynq_sdk_nmbrs/address.py +70 -0
  5. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/bank.py +1 -2
  6. brynq_sdk_nmbrs-2.4.5/brynq_sdk_nmbrs/companies.py +228 -0
  7. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/contract.py +0 -1
  8. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/days.py +1 -1
  9. brynq_sdk_nmbrs-2.4.5/brynq_sdk_nmbrs/debtors.py +87 -0
  10. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/department.py +23 -111
  11. brynq_sdk_nmbrs-2.4.5/brynq_sdk_nmbrs/document.py +271 -0
  12. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/employee_wage_tax_settings.py +13 -5
  13. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/employees.py +55 -21
  14. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/employment.py +0 -2
  15. brynq_sdk_nmbrs-2.4.5/brynq_sdk_nmbrs/extra_fields.py +126 -0
  16. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/function.py +35 -97
  17. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/leave.py +53 -58
  18. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/manager.py +51 -35
  19. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/salaries.py +48 -65
  20. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/salary_tables.py +1 -4
  21. brynq_sdk_nmbrs-2.4.5/brynq_sdk_nmbrs/schedules.py +85 -0
  22. brynq_sdk_nmbrs-2.4.5/brynq_sdk_nmbrs/schemas/absence.py +71 -0
  23. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/address.py +2 -29
  24. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/children.py +0 -2
  25. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/contracts.py +7 -6
  26. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/costcenter.py +2 -2
  27. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/costunit.py +2 -0
  28. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/days.py +13 -11
  29. brynq_sdk_nmbrs-2.4.5/brynq_sdk_nmbrs/schemas/debtor.py +16 -0
  30. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/department.py +5 -39
  31. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/document.py +0 -2
  32. brynq_sdk_nmbrs-2.4.5/brynq_sdk_nmbrs/schemas/employee_wage_tax_settings.py +75 -0
  33. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/employees.py +2 -0
  34. brynq_sdk_nmbrs-2.4.5/brynq_sdk_nmbrs/schemas/extra_fields.py +56 -0
  35. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/function.py +5 -32
  36. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/hours.py +5 -1
  37. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/leave.py +17 -6
  38. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/manager.py +11 -20
  39. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/salary.py +11 -1
  40. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/schedules.py +2 -54
  41. brynq_sdk_nmbrs-2.4.5/brynq_sdk_nmbrs/schemas/svw_settings.py +116 -0
  42. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/wage_tax.py +53 -21
  43. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/wagecomponents.py +6 -9
  44. brynq_sdk_nmbrs-2.4.5/brynq_sdk_nmbrs/svw_settings.py +213 -0
  45. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/wage_tax.py +120 -11
  46. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs.egg-info/PKG-INFO +1 -1
  47. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs.egg-info/SOURCES.txt +5 -2
  48. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/setup.py +1 -1
  49. brynq_sdk_nmbrs-2.3.4.dev0/brynq_sdk_nmbrs/address.py +0 -146
  50. brynq_sdk_nmbrs-2.3.4.dev0/brynq_sdk_nmbrs/companies.py +0 -137
  51. brynq_sdk_nmbrs-2.3.4.dev0/brynq_sdk_nmbrs/debtors.py +0 -99
  52. brynq_sdk_nmbrs-2.3.4.dev0/brynq_sdk_nmbrs/document.py +0 -80
  53. brynq_sdk_nmbrs-2.3.4.dev0/brynq_sdk_nmbrs/schedules.py +0 -158
  54. brynq_sdk_nmbrs-2.3.4.dev0/brynq_sdk_nmbrs/schemas/absence.py +0 -61
  55. brynq_sdk_nmbrs-2.3.4.dev0/brynq_sdk_nmbrs/schemas/debtor.py +0 -38
  56. brynq_sdk_nmbrs-2.3.4.dev0/brynq_sdk_nmbrs/schemas/social_insurance.py +0 -73
  57. brynq_sdk_nmbrs-2.3.4.dev0/brynq_sdk_nmbrs/social_insurance.py +0 -130
  58. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/children.py +0 -0
  59. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/costcenter.py +0 -0
  60. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/costunit.py +0 -0
  61. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/hours.py +0 -0
  62. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/__init__.py +0 -0
  63. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/bank.py +0 -0
  64. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/company.py +0 -0
  65. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/employment.py +0 -0
  66. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/schemas/wage_tax_settings.py +0 -0
  67. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs/wagecomponents.py +0 -0
  68. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs.egg-info/dependency_links.txt +0 -0
  69. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs.egg-info/not-zip-safe +0 -0
  70. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs.egg-info/requires.txt +0 -0
  71. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/brynq_sdk_nmbrs.egg-info/top_level.txt +0 -0
  72. {brynq_sdk_nmbrs-2.3.4.dev0 → brynq_sdk_nmbrs-2.4.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brynq_sdk_nmbrs
3
- Version: 2.3.4.dev0
3
+ Version: 2.4.5
4
4
  Summary: Nmbrs wrapper from BrynQ
5
5
  Author: BrynQ
6
6
  Author-email: support@brynq.com
@@ -17,14 +17,14 @@ from .children import Children
17
17
  from .companies import Companies
18
18
  from .contract import Contract
19
19
  from .costcenter import Costcenter, EmployeeCostcenter
20
- from .costunit import Costunit
21
20
  from .days import FixedDays, VariableDays
22
21
  from .debtors import Debtors
23
22
  from .department import Departments, EmployeeDepartment
24
- from .document import EmployeeDocument, Payslip
23
+ from .document import Document
25
24
  from .employee_wage_tax_settings import EmployeeWageTaxSettings
26
25
  from .employees import Employees
27
26
  from .employment import Employment
27
+ from .extra_fields import EmployeeExtraFields, ExtraFieldSettings
28
28
  from .function import EmployeeFunction, Functions
29
29
  from .hours import FixedHours, VariableHours
30
30
  from .leave import Leave, LeaveBalance, LeaveGroup
@@ -32,115 +32,119 @@ from .manager import EmployeeManager, Manager
32
32
  from .salaries import Salaries
33
33
  from .salary_tables import SalaryScales, SalarySteps, SalaryTables
34
34
  from .schedules import Schedule
35
- from .wage_tax import WageTax, WageTaxSettings
35
+ from .svw_settings import CompanySVWSettings, EmployeeSVWSettings
36
+ from .wage_tax import CompanyWageTax, WageTax
36
37
  from .wagecomponents import EmployeeFixedWageComponents, EmployeeVariableWageComponents
37
38
 
38
39
 
39
40
  class Nmbrs(BrynQ):
40
- def __init__(self, system_type: Optional[Literal['source', 'target']] = None, debug: bool = False, mock_mode: bool = True):
41
+ def __init__(self, system_type: Optional[Literal['source', 'target']] = None, debug: bool = False):
41
42
  """
42
43
  Initialize the Nmbrs class.
43
44
 
44
45
  Args:
45
- label: The label of the system in BrynQ. legacy
46
+ system_type: The type of system ('source' or 'target')
46
47
  debug: Whether to print debug information
47
- mock_mode: If true, data will NOT be sent to Nmbrs but only be tested for validity against Pydantic schemas
48
48
  """
49
- self.mock_mode = mock_mode
49
+ super().__init__()
50
+ self.mock_mode = False # Always disabled, kept for backwards compatibility with existing code
50
51
  self.debug = debug
51
52
  self.timeout = 3600
52
53
  self.system_type = system_type
54
+ self.data_interface_id = os.getenv("DATA_INTERFACE_ID")
55
+
56
+ # Fetch and store credentials
57
+ credentials = self.interfaces.credentials.get(system="nmbrs", system_type=system_type)
58
+ if isinstance(credentials, list):
59
+ credentials = credentials[0] if credentials else {}
60
+ if not isinstance(credentials, dict):
61
+ credentials = {}
62
+ self._credentials = credentials.get("data", credentials) or {}
63
+ self._custom_config = (
64
+ credentials.get("custom_config")
65
+ or credentials.get("custom_data")
66
+ or {}
67
+ )
68
+
69
+ headers = self._get_request_headers()
70
+ self.base_url = "https://api.nmbrsapp.com/api/"
71
+ self.session = requests.Session()
72
+ self.session.headers.update(headers)
53
73
 
54
- # Initialize classes that work in both mock and real mode
74
+ # Initialize SOAP client
75
+ self.soap_settings = Settings(
76
+ strict=False,
77
+ xml_huge_tree=True,
78
+ force_https=True
79
+ )
80
+ self.soap_client_companies = Client(
81
+ 'https://api.nmbrs.nl/soap/v3/CompanyService.asmx?wsdl',
82
+ settings=self.soap_settings
83
+ )
84
+ self.soap_client_employees = Client(
85
+ 'https://api.nmbrs.nl/soap/v3/EmployeeService.asmx?wsdl',
86
+ settings=self.soap_settings
87
+ )
88
+ self.soap_auth_header = self._get_soap_auth_header()
89
+ self.companies = Companies(self)
90
+ # Following methods can only be used if the SOAP authentication header is set (optional, this is not always in scope for all integrations)
91
+ if self.soap_auth_header is not None:
92
+ self.soap_company_ids = self.companies.get_soap_ids()
93
+ self.salary_tables = SalaryTables(self)
94
+ self.salary_scales = SalaryScales(self)
95
+ self.salary_steps = SalarySteps(self)
96
+ self.wage_tax = WageTax(self)
97
+ self.absence = Absence(self)
98
+ self.children = Children(self)
55
99
  self.address = Address(self)
56
100
  self.bank = Bank(self)
57
- self.children = Children(self)
101
+ self.cost_center = Costcenter(self)
102
+ self.employee_cost_center = EmployeeCostcenter(self)
103
+ if self.soap_auth_header is None:
104
+ self.children = Children(self)
105
+ self.debtor = Debtors(self)
58
106
  self.contract = Contract(self)
107
+ self.employee_department = EmployeeDepartment(self)
108
+ self.department = Departments(self)
109
+ debtors, _ = self.debtor.get()
110
+ self.debtor_ids = debtors['debtor_id'].to_list()
111
+ self.company_ids = self.companies.get()['companyId'].to_list()
112
+ self.employees = Employees(self)
113
+ self.employment = Employment(self)
114
+ self.employee_function = EmployeeFunction(self)
115
+ self.function = Functions(self)
116
+ self.fixed_hours = FixedHours(self)
117
+ self.fixed_days = FixedDays(self)
118
+ self.variable_hours = VariableHours(self)
119
+ self.variable_days = VariableDays(self)
120
+ self.manager = Manager(self)
121
+ self.employee_manager = EmployeeManager(self)
122
+ self.salaries = Salaries(self)
123
+ self.schedule = Schedule(self)
124
+ self.fixed_wagecomponents = EmployeeFixedWageComponents(self)
125
+ self.variable_wagecomponents = EmployeeVariableWageComponents(self)
126
+ self.current_period = self.companies.get_current_period()
127
+
128
+ self.company_wage_tax = CompanyWageTax(self)
129
+ self.documents = Document(self)
130
+ self.company_svw_settings = CompanySVWSettings(self)
131
+ self.employee_svw_settings = EmployeeSVWSettings(self)
132
+ self.employee_wage_tax_settings = EmployeeWageTaxSettings(self)
133
+ self.extra_field_settings = ExtraFieldSettings(self)
134
+ self.employee_extra_fields = EmployeeExtraFields(self)
59
135
  self.leave = Leave(self)
60
- self.leave_group = LeaveGroup(self)
61
- self.function = EmployeeFunction(self)
62
- self.functions = Functions(self)
63
- self.employee_document = EmployeeDocument(self)
64
- self.departments = Departments(self)
65
- self.debtor = Debtors(self)
66
- self.companies = Companies(self)
136
+ self.leave_balance = LeaveBalance(self)
137
+ self.leave_groups = LeaveGroup(self)
138
+
67
139
 
68
- if mock_mode is False:
69
- # Initialize BrynQ parent class for REST API credentials
70
- super().__init__()
71
- self.data_interface_id = os.getenv("DATA_INTERFACE_ID")
72
-
73
- # Get credentials once and store
74
- self._credentials = self.interfaces.credentials.get(system='nmbrs', system_type=self.system_type)
75
- self._custom_config = self._credentials.get("custom_data", {})
76
-
77
- headers = self._get_request_headers()
78
- self.base_url = "https://api.nmbrsapp.com/api/"
79
- self.session = requests.Session()
80
- self.session.headers.update(headers)
81
-
82
- # Initialize SOAP clients
83
- self.soap_settings = Settings(
84
- strict=False,
85
- xml_huge_tree=True,
86
- force_https=True
87
- )
88
- self.soap_client_companies = Client(
89
- 'https://api.nmbrs.nl/soap/v3/CompanyService.asmx?wsdl',
90
- settings=self.soap_settings
91
- )
92
- self.soap_client_employees = Client(
93
- 'https://api.nmbrs.nl/soap/v3/EmployeeService.asmx?wsdl',
94
- settings=self.soap_settings
95
- )
96
- self.soap_client_debtors = Client(
97
- 'https://api.nmbrs.nl/soap/v3/DebtorService.asmx?wsdl',
98
- settings=self.soap_settings
99
- )
100
- # Create auth headers for each service (namespace must match)
101
- self.soap_auth_header = self._create_soap_auth_header(self.soap_client_companies)
102
- self.soap_auth_header_employees = self._create_soap_auth_header(self.soap_client_employees)
103
- self.soap_auth_header_debtors = self._create_soap_auth_header(self.soap_client_debtors)
104
-
105
- # REST API dependent classes
106
- self.employee_department = EmployeeDepartment(self)
107
-
108
- debtors, _ = self.debtor.get()
109
- self.debtor_ids = debtors['debtor_id'].to_list()
110
- self.company_ids = self.companies.get()['companyId'].to_list()
111
-
112
- # SOAP dependent on REST - companies must be defined first
113
- if self.soap_auth_header is not None:
114
- self.employees = Employees(self)
115
- self.soap_company_ids = self.companies.get_soap_ids()
116
- self.soap_employee_ids = self.employees.get_soap_ids()
117
- self.salary_tables = SalaryTables(self)
118
- self.salary_scales = SalaryScales(self)
119
- self.salary_steps = SalarySteps(self)
120
- self.wage_tax = WageTax(self)
121
- self.absence = Absence(self)
122
-
123
- self.employees = Employees(self)
124
- self.employment = Employment(self)
125
- self.wage_tax_settings = WageTaxSettings(self)
126
- self.fixed_hours = FixedHours(self)
127
- self.fixed_days = FixedDays(self)
128
- self.variable_hours = VariableHours(self)
129
- self.variable_days = VariableDays(self)
130
- self.manager = Manager(self)
131
- self.employee_manager = EmployeeManager(self)
132
- self.salaries = Salaries(self)
133
- self.schedule = Schedule(self)
134
- self.fixed_wagecomponents = EmployeeFixedWageComponents(self)
135
- self.variable_wagecomponents = EmployeeVariableWageComponents(self)
136
- self.current_period = self.companies.get_current_period()
137
- self.cost_center = Costcenter(self)
138
- self.employee_cost_center = EmployeeCostcenter(self)
139
- self.cost_unit = Costunit(self)
140
- self.employee_wage_tax_settings = EmployeeWageTaxSettings(self)
141
140
  def _get_request_headers(self):
142
- access_token = self._credentials.get('data').get('access_token')
143
- subscription_key = self._custom_config.get("subscription_key")
141
+ access_token = self._credentials.get('access_token')
142
+ subscription_key = (
143
+ self._custom_config.get("subscription_key")
144
+ or self._custom_config.get("subscriptionKey")
145
+ or self._credentials.get("subscription_key")
146
+ or self._credentials.get("subscriptionKey")
147
+ )
144
148
  headers = {
145
149
  "accept": "application/json",
146
150
  "Authorization": f"Bearer {access_token.strip() if access_token else ''}",
@@ -1,11 +1,8 @@
1
- from typing import Any, Dict, List, Union, Tuple
1
+ from typing import Any, Dict, Tuple
2
2
  import pandas as pd
3
3
  from .schemas.absence import AbsenceCreate, AbsenceGet
4
4
  from zeep.exceptions import Fault
5
- from zeep.ns import WSDL, SOAP_ENV_11
6
- from zeep.xsd import ComplexType, Element, String
7
5
  from zeep.helpers import serialize_object
8
- # import logging
9
6
  from brynq_sdk_functions import Functions
10
7
 
11
8
 
@@ -0,0 +1,70 @@
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.address import AddressCreate, AddressGet
9
+
10
+
11
+ class Address:
12
+ def __init__(self, nmbrs):
13
+ self.nmbrs = nmbrs
14
+
15
+ def get(self,
16
+ created_from: str = None) -> pd.DataFrame:
17
+ addresses = pd.DataFrame()
18
+ for company in self.nmbrs.company_ids:
19
+ addresses = pd.concat([addresses, self._get(company, created_from)])
20
+
21
+ valid_addresses, invalid_addresses = Functions.validate_data(df=addresses, schema=AddressGet, debug=True)
22
+
23
+ return valid_addresses, invalid_addresses
24
+
25
+ def _get(self,
26
+ company_id: str,
27
+ created_from: str = None) -> pd.DataFrame:
28
+ params = {} if created_from is None else {'createdFrom': created_from}
29
+ request = requests.Request(method='GET',
30
+ url=f"{self.nmbrs.base_url}companies/{company_id}/employees/addresses",
31
+ params=params)
32
+
33
+ data = self.nmbrs.get_paginated_result(request)
34
+ df = pd.json_normalize(
35
+ data,
36
+ record_path='addresses',
37
+ meta=['employeeId']
38
+ )
39
+
40
+ return df
41
+
42
+ def create(self, employee_id: str, data: Dict[str, Any]):
43
+ """
44
+ Create a new address for an employee using Pydantic validation.
45
+
46
+ Args:
47
+ employee_id: The ID of the employee
48
+ data: Dictionary containing address data with fields matching
49
+ the AddressCreate schema (using camelCase field names)
50
+
51
+ Returns:
52
+ Response from the API
53
+ """
54
+ # Validate with Pydantic model - this will raise an error if required fields are missing
55
+ nested_data = self.nmbrs.flat_dict_to_nested_dict(data, AddressCreate)
56
+ address_model = AddressCreate(**nested_data)
57
+
58
+ if self.nmbrs.mock_mode:
59
+ return address_model
60
+
61
+ # Convert validated model to dict for API payload
62
+ payload = address_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}/address",
67
+ json=payload,
68
+ timeout=self.nmbrs.timeout
69
+ )
70
+ return resp
@@ -1,4 +1,3 @@
1
- import math
2
1
  from typing import Any, Dict
3
2
 
4
3
  import pandas as pd
@@ -40,7 +39,7 @@ class Bank:
40
39
  record_path='bankAccounts',
41
40
  meta=['employeeId']
42
41
  )
43
- except requests.HTTPError as e:
42
+ except requests.HTTPError:
44
43
  df = pd.DataFrame()
45
44
 
46
45
  return df
@@ -0,0 +1,228 @@
1
+ import logging
2
+ from typing import TYPE_CHECKING, Optional
3
+
4
+ import pandas as pd
5
+ import requests
6
+ from zeep.exceptions import Fault
7
+ from zeep.helpers import serialize_object
8
+
9
+ from .bank import Bank
10
+ from .costcenter import Costcenter
11
+ from .costunit import Costunit
12
+ from .hours import Hours
13
+ from .leave import LeaveGroup
14
+
15
+ if TYPE_CHECKING:
16
+ from brynq_sdk_nmbrs import Nmbrs
17
+
18
+
19
+ class Companies:
20
+ def __init__(self, nmbrs):
21
+ self.nmbrs: Nmbrs = nmbrs
22
+ self.costcenters = Costcenter(nmbrs)
23
+ self.costunits = Costunit(nmbrs)
24
+ self.hours = Hours(nmbrs)
25
+ self.banks = Bank(nmbrs)
26
+ self.logger = logging.getLogger(__name__)
27
+ self.leave_groups = LeaveGroup(nmbrs)
28
+
29
+ @property
30
+ def soap_client_companies(self):
31
+ return self.nmbrs.soap_client_companies
32
+
33
+ def get(self) -> pd.DataFrame:
34
+ try:
35
+ request = requests.Request(method='GET',
36
+ url=f"{self.nmbrs.base_url}companies")
37
+ data = self.nmbrs.get_paginated_result(request)
38
+ df = pd.DataFrame(data)
39
+ except requests.HTTPError as e:
40
+ if e.response.status_code == 403 and e.response.json().get("title") == "ForbiddenMultiDebtor.":
41
+ df = pd.DataFrame()
42
+ for debtor_id in self.nmbrs.debtor_ids:
43
+ request = requests.Request(method='GET',
44
+ url=f"{self.nmbrs.base_url}debtors/{debtor_id}/companies")
45
+ data = self.nmbrs.get_paginated_result(request)
46
+ df = pd.concat([df, pd.DataFrame(data)])
47
+ else:
48
+ raise e
49
+ # TODO: add validation
50
+
51
+ return df
52
+
53
+ def get_soap_ids(self) -> pd.DataFrame:
54
+ """
55
+ Get all companies using the SOAP API.
56
+
57
+ Returns:
58
+ pd.DataFrame: DataFrame containing all companies
59
+ """
60
+ try:
61
+ # Make SOAP request with the proper header structure
62
+ response = self.nmbrs.soap_client_companies.service.List_GetAll(
63
+ _soapheaders=[self.nmbrs.soap_auth_header]
64
+ )
65
+
66
+ # Convert response to DataFrame
67
+ if response:
68
+ # Convert Zeep objects to Python dictionaries
69
+ serialized_response = serialize_object(response)
70
+
71
+ # TODO: add validation here
72
+ # Convert to DataFrame
73
+ df = pd.DataFrame(serialized_response)
74
+ df = self.nmbrs._rename_camel_columns_to_snake_case(df)
75
+
76
+ return df
77
+ else:
78
+ return pd.DataFrame()
79
+
80
+ except Fault as e:
81
+ raise Exception(f"SOAP request failed: {str(e)}")
82
+ except Exception as e:
83
+ self.logger.exception("Exception occurred:")
84
+ raise Exception(f"Failed to get companies: {str(e)}")
85
+
86
+ def get_current_period(self) -> int:
87
+ try:
88
+ df = pd.DataFrame()
89
+ for company_id in self.nmbrs.company_ids:
90
+ request = requests.Request(method='GET',
91
+ url=f"{self.nmbrs.base_url}companies/{company_id}/period")
92
+ data = self.nmbrs.get_paginated_result(request)
93
+ df_temp = pd.json_normalize(data)
94
+ df_temp['company_id'] = company_id
95
+ df = pd.concat([df, df_temp])
96
+
97
+ return df
98
+ except Exception as e:
99
+ self.logger.exception("Exception occurred:")
100
+ raise Exception(f"Failed to get current period: {str(e)}")
101
+
102
+ def get_wage_models(self,
103
+ company_id: Optional[str] = None,
104
+ period: Optional[int] = None,
105
+ year: Optional[int] = None) -> pd.DataFrame:
106
+ """
107
+ Get company wage models per period.
108
+
109
+ Returns the available wage codes for employees of this company in the
110
+ period consulted. If no period is used it returns the current period information.
111
+
112
+ Args:
113
+ company_id: Optional company ID. If not provided, fetches for all companies.
114
+ period: Optional period filter
115
+ year: Optional year filter
116
+
117
+ Returns:
118
+ DataFrame containing wage models
119
+ """
120
+ if company_id:
121
+ return self._get_wage_models(company_id, period, year)
122
+
123
+ df = pd.DataFrame()
124
+ for company in self.nmbrs.company_ids:
125
+ df = pd.concat([df, self._get_wage_models(company, period, year)])
126
+
127
+ return df
128
+
129
+ def _get_wage_models(self,
130
+ company_id: str,
131
+ period: Optional[int] = None,
132
+ year: Optional[int] = None) -> pd.DataFrame:
133
+ """
134
+ Internal method to get wage models for a single company.
135
+ """
136
+ params = {}
137
+ if period is not None:
138
+ params['period'] = period
139
+ if year is not None:
140
+ params['year'] = year
141
+
142
+ request = requests.Request(
143
+ method='GET',
144
+ url=f"{self.nmbrs.base_url}companies/{company_id}/wagemodels",
145
+ params=params
146
+ )
147
+
148
+ data = self.nmbrs.get_paginated_result(request)
149
+ df = pd.DataFrame(data)
150
+
151
+ if not df.empty:
152
+ df['companyId'] = company_id
153
+
154
+ return df
155
+
156
+ def get_addresses(self, company_id: Optional[str] = None) -> pd.DataFrame:
157
+ """
158
+ Get addresses history for a company.
159
+
160
+ Args:
161
+ company_id: Optional company ID. If not provided, fetches for all companies.
162
+
163
+ Returns:
164
+ DataFrame containing company addresses
165
+ """
166
+ if company_id:
167
+ return self._get_addresses(company_id)
168
+
169
+ df = pd.DataFrame()
170
+ for company in self.nmbrs.company_ids:
171
+ df = pd.concat([df, self._get_addresses(company)])
172
+
173
+ return df
174
+
175
+ def _get_addresses(self, company_id: str) -> pd.DataFrame:
176
+ """
177
+ Internal method to get addresses for a single company.
178
+ """
179
+ request = requests.Request(
180
+ method='GET',
181
+ url=f"{self.nmbrs.base_url}companies/{company_id}/addresses"
182
+ )
183
+
184
+ data = self.nmbrs.get_paginated_result(request)
185
+ df = pd.DataFrame(data)
186
+
187
+ if not df.empty:
188
+ df['companyId'] = company_id
189
+
190
+ return df
191
+
192
+ def get_contact_persons(self, company_id: Optional[str] = None) -> pd.DataFrame:
193
+ """
194
+ Get contact persons for a company.
195
+
196
+ There's no history on these values.
197
+
198
+ Args:
199
+ company_id: Optional company ID. If not provided, fetches for all companies.
200
+
201
+ Returns:
202
+ DataFrame containing contact persons
203
+ """
204
+ if company_id:
205
+ return self._get_contact_persons(company_id)
206
+
207
+ df = pd.DataFrame()
208
+ for company in self.nmbrs.company_ids:
209
+ df = pd.concat([df, self._get_contact_persons(company)])
210
+
211
+ return df
212
+
213
+ def _get_contact_persons(self, company_id: str) -> pd.DataFrame:
214
+ """
215
+ Internal method to get contact persons for a single company.
216
+ """
217
+ request = requests.Request(
218
+ method='GET',
219
+ url=f"{self.nmbrs.base_url}companies/{company_id}/contactpersons"
220
+ )
221
+
222
+ data = self.nmbrs.get_paginated_result(request)
223
+ df = pd.DataFrame(data)
224
+
225
+ if not df.empty:
226
+ df['companyId'] = company_id
227
+
228
+ return df
@@ -1,4 +1,3 @@
1
- import math
2
1
  import pandas as pd
3
2
  import requests
4
3
  from typing import Dict, Any
@@ -2,7 +2,7 @@ import pandas as pd
2
2
  import requests
3
3
  from typing import Dict, Any
4
4
  from .schemas.days import (
5
- VariableDaysGet, VariableDaysCreate, FixedDaysCreate, FixedDaysGet
5
+ VariableDaysGet, VariableDaysCreate, FixedDaysCreate
6
6
  )
7
7
  from brynq_sdk_functions import Functions
8
8
 
@@ -0,0 +1,87 @@
1
+
2
+ import pandas as pd
3
+ import requests
4
+
5
+ from brynq_sdk_functions import Functions
6
+
7
+ from .department import Departments
8
+ from .function import Functions as NmbrsFunctions
9
+ from .schemas.debtor import DebtorsGet
10
+
11
+
12
+ class Debtors:
13
+ """
14
+ Class for handling debtor information.
15
+
16
+ Endpoints:
17
+ - GET /api/debtors
18
+ - GET /api/debtors/info
19
+ - GET /api/debtors/endcontractreasons/{Year}
20
+ """
21
+
22
+ def __init__(self, nmbrs):
23
+ self.nmbrs = nmbrs
24
+ self.departments = Departments(nmbrs)
25
+ self.functions = NmbrsFunctions(nmbrs)
26
+
27
+ def get(self) -> tuple:
28
+ """
29
+ Get a list of all debtors for the authenticated user.
30
+
31
+ Returns:
32
+ Tuple of (valid_data, invalid_data) DataFrames
33
+ """
34
+ request = requests.Request(method='GET',
35
+ url=f"{self.nmbrs.base_url}debtors")
36
+ data = self.nmbrs.get_paginated_result(request)
37
+
38
+ df = pd.DataFrame(data)
39
+
40
+ valid_debtors, invalid_debtors = Functions.validate_data(df=df, schema=DebtorsGet, debug=True)
41
+
42
+ return valid_debtors, invalid_debtors
43
+
44
+ def get_info(self) -> pd.DataFrame:
45
+ """
46
+ Get information about the authenticated debtor.
47
+
48
+ Returns info about the debtor that the API keys belong to,
49
+ including current settings and subscription details.
50
+
51
+ Returns:
52
+ DataFrame containing debtor info
53
+ """
54
+ resp = self.nmbrs.session.get(
55
+ url=f"{self.nmbrs.base_url}debtors/info",
56
+ timeout=self.nmbrs.timeout
57
+ )
58
+ resp.raise_for_status()
59
+ data = resp.json()
60
+
61
+ # Normalize the response if it's a dict
62
+ if isinstance(data, dict):
63
+ df = pd.json_normalize(data)
64
+ else:
65
+ df = pd.DataFrame(data)
66
+
67
+ return df
68
+
69
+ def get_end_contract_reasons(self, year: int) -> pd.DataFrame:
70
+ """
71
+ Get the available end contract reasons for a specific year.
72
+
73
+ Args:
74
+ year: The year to get end contract reasons for
75
+
76
+ Returns:
77
+ DataFrame containing end contract reasons
78
+ """
79
+ request = requests.Request(
80
+ method='GET',
81
+ url=f"{self.nmbrs.base_url}debtors/endcontractreasons/{year}"
82
+ )
83
+
84
+ data = self.nmbrs.get_paginated_result(request)
85
+ df = pd.DataFrame(data)
86
+
87
+ return df