brynq-sdk-nmbrs 2.3.1__py3-none-any.whl → 2.3.2.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. brynq_sdk_nmbrs/__init__.py +104 -84
  2. brynq_sdk_nmbrs/address.py +82 -2
  3. brynq_sdk_nmbrs/children.py +96 -61
  4. brynq_sdk_nmbrs/companies.py +45 -1
  5. brynq_sdk_nmbrs/costcenter.py +53 -8
  6. brynq_sdk_nmbrs/costunit.py +16 -4
  7. brynq_sdk_nmbrs/debtors.py +76 -2
  8. brynq_sdk_nmbrs/department.py +149 -28
  9. brynq_sdk_nmbrs/document.py +50 -0
  10. brynq_sdk_nmbrs/employee_wage_tax_settings.py +113 -0
  11. brynq_sdk_nmbrs/employees.py +119 -24
  12. brynq_sdk_nmbrs/employment.py +12 -4
  13. brynq_sdk_nmbrs/function.py +128 -2
  14. brynq_sdk_nmbrs/leave.py +105 -8
  15. brynq_sdk_nmbrs/salaries.py +78 -3
  16. brynq_sdk_nmbrs/schedules.py +77 -3
  17. brynq_sdk_nmbrs/schemas/address.py +30 -5
  18. brynq_sdk_nmbrs/schemas/bank.py +0 -2
  19. brynq_sdk_nmbrs/schemas/children.py +67 -0
  20. brynq_sdk_nmbrs/schemas/company.py +16 -0
  21. brynq_sdk_nmbrs/schemas/contracts.py +25 -11
  22. brynq_sdk_nmbrs/schemas/costcenter.py +57 -18
  23. brynq_sdk_nmbrs/schemas/costunit.py +0 -2
  24. brynq_sdk_nmbrs/schemas/days.py +0 -2
  25. brynq_sdk_nmbrs/schemas/debtor.py +23 -1
  26. brynq_sdk_nmbrs/schemas/department.py +41 -7
  27. brynq_sdk_nmbrs/schemas/document.py +13 -0
  28. brynq_sdk_nmbrs/schemas/employees.py +44 -38
  29. brynq_sdk_nmbrs/schemas/employment.py +10 -10
  30. brynq_sdk_nmbrs/schemas/function.py +34 -7
  31. brynq_sdk_nmbrs/schemas/hours.py +0 -4
  32. brynq_sdk_nmbrs/schemas/leave.py +12 -1
  33. brynq_sdk_nmbrs/schemas/manager.py +0 -3
  34. brynq_sdk_nmbrs/schemas/salary.py +37 -12
  35. brynq_sdk_nmbrs/schemas/schedules.py +49 -1
  36. brynq_sdk_nmbrs/schemas/social_insurance.py +39 -6
  37. brynq_sdk_nmbrs/schemas/wage_tax.py +68 -8
  38. brynq_sdk_nmbrs/schemas/wage_tax_settings.py +76 -0
  39. brynq_sdk_nmbrs/schemas/wagecomponents.py +0 -4
  40. brynq_sdk_nmbrs/social_insurance.py +81 -3
  41. brynq_sdk_nmbrs/wage_tax.py +105 -4
  42. {brynq_sdk_nmbrs-2.3.1.dist-info → brynq_sdk_nmbrs-2.3.2.dev0.dist-info}/METADATA +1 -1
  43. brynq_sdk_nmbrs-2.3.2.dev0.dist-info/RECORD +55 -0
  44. {brynq_sdk_nmbrs-2.3.1.dist-info → brynq_sdk_nmbrs-2.3.2.dev0.dist-info}/WHEEL +1 -1
  45. brynq_sdk_nmbrs-2.3.1.dist-info/RECORD +0 -50
  46. {brynq_sdk_nmbrs-2.3.1.dist-info → brynq_sdk_nmbrs-2.3.2.dev0.dist-info}/top_level.txt +0 -0
@@ -1,34 +1,39 @@
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
20
- from .document import Payslip
21
+ from .days import FixedDays, VariableDays
22
+ from .debtors import Debtors
23
+ from .department import Departments, EmployeeDepartment
24
+ from .document import EmployeeDocument, 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
30
+ from .leave import Leave, LeaveBalance, LeaveGroup
27
31
  from .manager import EmployeeManager, Manager
28
32
  from .salaries import Salaries
33
+ from .salary_tables import SalaryScales, SalarySteps, SalaryTables
29
34
  from .schedules import Schedule
35
+ from .wage_tax import WageTax, WageTaxSettings
30
36
  from .wagecomponents import EmployeeFixedWageComponents, EmployeeVariableWageComponents
31
- import os
32
37
 
33
38
 
34
39
  class Nmbrs(BrynQ):
@@ -39,21 +44,42 @@ class Nmbrs(BrynQ):
39
44
  Args:
40
45
  label: The label of the system in BrynQ. legacy
41
46
  debug: Whether to print debug information
42
- 11 mock_mode: If true, data will NOT be sent to Nmbrs but only be tested for validity against Pydantic schemas
47
+ mock_mode: If true, data will NOT be sent to Nmbrs but only be tested for validity against Pydantic schemas
43
48
  """
44
49
  self.mock_mode = mock_mode
45
50
  self.debug = debug
46
51
  self.timeout = 3600
47
52
  self.system_type = system_type
53
+
54
+ # Initialize classes that work in both mock and real mode
55
+ self.address = Address(self)
56
+ self.bank = Bank(self)
57
+ self.children = Children(self)
58
+ self.contract = Contract(self)
59
+ 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)
67
+
48
68
  if mock_mode is False:
69
+ # Initialize BrynQ parent class for REST API credentials
49
70
  super().__init__()
50
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
+
51
77
  headers = self._get_request_headers()
52
78
  self.base_url = "https://api.nmbrsapp.com/api/"
53
79
  self.session = requests.Session()
54
80
  self.session.headers.update(headers)
55
81
 
56
- # Initialize SOAP client
82
+ # Initialize SOAP clients
57
83
  self.soap_settings = Settings(
58
84
  strict=False,
59
85
  xml_huge_tree=True,
@@ -67,99 +93,93 @@ class Nmbrs(BrynQ):
67
93
  'https://api.nmbrs.nl/soap/v3/EmployeeService.asmx?wsdl',
68
94
  settings=self.soap_settings
69
95
  )
70
- self.soap_auth_header = self._get_soap_auth_header()
71
- # Following methods can only be used if the SOAP authentication header is set (optional, this is not always in scope for all integrations)
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
72
113
  if self.soap_auth_header is not None:
114
+ self.employees = Employees(self)
73
115
  self.soap_company_ids = self.companies.get_soap_ids()
116
+ self.soap_employee_ids = self.employees.get_soap_ids()
74
117
  self.salary_tables = SalaryTables(self)
75
118
  self.salary_scales = SalaryScales(self)
76
119
  self.salary_steps = SalarySteps(self)
77
120
  self.wage_tax = WageTax(self)
78
121
  self.absence = Absence(self)
79
- self.children = Children(self)
80
-
81
- self.address = Address(self)
82
- self.bank = Bank(self)
83
- self.children = Children(self)
84
- self.debtor = Debtors(self)
85
- self.companies = Companies(self)
86
- self.contract = Contract(self)
87
- self.department = EmployeeDepartment(self)
88
- debtors, _ = self.debtor.get()
89
- self.debtor_ids = debtors['debtor_id'].to_list()
90
- self.company_ids = self.companies.get()['companyId'].to_list()
91
- self.employees = Employees(self)
92
- self.employment = Employment(self)
93
- self.function = EmployeeFunction(self)
94
- self.fixed_hours = FixedHours(self)
95
- self.fixed_days = FixedDays(self)
96
- self.variable_hours = VariableHours(self)
97
- self.variable_days = VariableDays(self)
98
- self.manager = Manager(self)
99
- self.employee_manager = EmployeeManager(self)
100
- self.salaries = Salaries(self)
101
- self.schedule = Schedule(self)
102
- self.fixed_wagecomponents = EmployeeFixedWageComponents(self)
103
- self.variable_wagecomponents = EmployeeVariableWageComponents(self)
104
- self.current_period = self.companies.get_current_period()
105
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)
106
141
  def _get_request_headers(self):
107
- credentials = self.interfaces.credentials.get(system='nmbrs', system_type=self.system_type)
142
+ access_token = self._credentials.get('data').get('access_token')
143
+ subscription_key = self._custom_config.get("subscription_key")
108
144
  headers = {
109
145
  "accept": "application/json",
110
- "Authorization": f"Bearer {credentials.get('data').get('access_token')}",
146
+ "Authorization": f"Bearer {access_token.strip() if access_token else ''}",
111
147
  # partner identifier
112
- "X-Subscription-Key": credentials.get("custom_data").get("subscription_key")
148
+ "X-Subscription-Key": subscription_key.strip() if subscription_key else ''
113
149
  }
114
150
 
115
151
  return headers
116
152
 
117
- def _get_soap_auth_header(self):
118
- """
119
- Creates the SOAP authentication header using credentials from initial_credentials.
120
-
121
- Returns:
122
- AuthHeaderWithDomainType: The authentication header for SOAP requests
153
+ def _create_soap_auth_header(self, soap_client):
123
154
  """
124
- initial_credentials = self.interfaces.credentials.get(system='nmbrs', system_type=self.system_type)
125
- config = initial_credentials.get("custom_data", {})
155
+ Creates SOAP authentication header for a specific SOAP client.
156
+ Each service needs its own auth header with matching namespace.
126
157
 
127
- if 'soap_api_token' not in config.keys():
128
- return None
129
- else:
130
- # Get the AuthHeaderWithDomain type from the WSDL
131
- AuthHeaderWithDomainType = self.soap_client_companies.get_element('ns0:AuthHeaderWithDomain')
132
-
133
- # Create the auth header using credentials from config
134
- auth_header = AuthHeaderWithDomainType(
135
- Username=config.get("soap_api_username"),
136
- Token=config.get("soap_api_token"),
137
- Domain=config.get("soap_api_domain")
138
- )
139
-
140
- return auth_header
141
-
142
- def _get_soap_auth_header_employees(self):
143
- """
144
- Creates the SOAP authentication header using credentials from initial_credentials.
158
+ Args:
159
+ soap_client: The zeep SOAP client to create auth header for
145
160
 
146
161
  Returns:
147
- AuthHeaderWithDomainType: The authentication header for SOAP requests
162
+ AuthHeaderWithDomainType: The authentication header for SOAP requests,
163
+ or None if SOAP credentials are not configured.
148
164
  """
149
- initial_credentials = self.get_system_credential(system='nmbrs', label='bob')
150
- config = initial_credentials.get("config", {})
165
+ if 'soap_api_token' not in self._custom_config.keys():
166
+ return None
151
167
 
152
- # Get the AuthHeaderWithDomain type from the WSDL
153
- AuthHeaderWithDomainType = self.soap_client_employees.get_element('ns0:AuthHeaderWithDomain')
168
+ AuthHeaderWithDomainType = soap_client.get_element('ns0:AuthHeaderWithDomain')
154
169
 
155
- # Create the auth header using credentials from config
156
- auth_header = AuthHeaderWithDomainType(
157
- Username=config.get("soap_api_username"),
158
- Token=config.get("soap_api_token"),
159
- Domain=config.get("soap_api_domain")
170
+ return AuthHeaderWithDomainType(
171
+ Username=self._custom_config.get("soap_api_username"),
172
+ Token=self._custom_config.get("soap_api_token"),
173
+ Domain=self._custom_config.get("soap_api_domain")
160
174
  )
161
175
 
162
- return auth_header
176
+ def _get_soap_auth_header(self):
177
+ """Legacy method - use _create_soap_auth_header instead."""
178
+ return self._create_soap_auth_header(self.soap_client_companies)
179
+
180
+ def _get_soap_auth_header_employees(self):
181
+ """Legacy method - use soap_auth_header_employees instead."""
182
+ return self.soap_auth_header_employees
163
183
 
164
184
  def get_paginated_result(self, request: requests.Request) -> List:
165
185
  has_next_page = True
@@ -1,9 +1,10 @@
1
1
  import pandas as pd
2
2
  import requests
3
- from typing import Dict, Any
4
- from .schemas.address import AddressCreate, AddressGet, Period
3
+ from typing import Dict, Any, Union
4
+ from .schemas.address import AddressCreate, AddressGet, AddressDelete, AddressUpdate, Period
5
5
  from brynq_sdk_functions import Functions
6
6
 
7
+
7
8
  class Address:
8
9
  def __init__(self, nmbrs):
9
10
  self.nmbrs = nmbrs
@@ -64,3 +65,82 @@ class Address:
64
65
  timeout=self.nmbrs.timeout
65
66
  )
66
67
  return resp
68
+
69
+ def delete(self, employee_id: Union[int, str], address_id: Union[int, str]) -> bool:
70
+ """
71
+ Delete an address for an employee using SOAP API.
72
+
73
+ REST API does not support address deletion, so we use SOAP as interim solution.
74
+
75
+ Args:
76
+ employee_id: The ID of the employee
77
+ address_id: The ID of the address to delete
78
+
79
+ Returns:
80
+ bool: True if deletion was successful
81
+
82
+ Raises:
83
+ Exception: If SOAP client is not available or deletion fails
84
+ """
85
+ # Validate input using Pydantic schema
86
+ delete_model = AddressDelete(employeeId=int(employee_id), addressId=int(address_id))
87
+
88
+ if self.nmbrs.mock_mode:
89
+ return delete_model
90
+
91
+ # Call SOAP Address_Delete using EmployeeService auth header
92
+ resp = self.nmbrs.soap_client_employees.service.Address_Delete(
93
+ EmployeeId=delete_model.employee_id,
94
+ AddressID=delete_model.address_id,
95
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_employees}
96
+ )
97
+
98
+ return resp
99
+
100
+ def update(self, employee_id: Union[int, str], data: Dict[str, Any]):
101
+ """
102
+ Update an address for an employee using SOAP API.
103
+
104
+ Args:
105
+ employee_id: The ID of the employee
106
+ data: Dictionary containing address data with fields matching AddressUpdate schema:
107
+ - id: Address ID to update
108
+ - street: Street name
109
+ - house_number: House number (optional)
110
+ - house_number_addition: House number addition (optional)
111
+ - postal_code: Postal code (optional)
112
+ - city: City name
113
+ - state_province: State or province (optional)
114
+ - country_iso_code: Country ISO code (e.g., "NL")
115
+
116
+ Returns:
117
+ Response from the API
118
+ """
119
+ # Validate with Pydantic model
120
+ address_model = AddressUpdate(**data)
121
+
122
+ if self.nmbrs.mock_mode:
123
+ return address_model
124
+
125
+ # Create EmployeeAddress type
126
+ AddressType = self.nmbrs.soap_client_employees.get_type('ns0:EmployeeAddress')
127
+ soap_address = AddressType(
128
+ Id=address_model.id,
129
+ Default=address_model.default,
130
+ Street=address_model.street,
131
+ HouseNumber=address_model.house_number or '',
132
+ HouseNumberAddition=address_model.house_number_addition or '',
133
+ PostalCode=address_model.postal_code or '',
134
+ City=address_model.city,
135
+ StateProvince=address_model.state_province or '',
136
+ CountryISOCode=address_model.country_iso_code,
137
+ Type=address_model.address_type
138
+ )
139
+
140
+ # Call SOAP Address_Update
141
+ result = self.nmbrs.soap_client_employees.service.Address_Update(
142
+ EmployeeId=int(employee_id),
143
+ Address=soap_address,
144
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_employees}
145
+ )
146
+ return result
@@ -1,24 +1,14 @@
1
- from datetime import datetime
1
+ from typing import Union, Dict, Any
2
2
 
3
- from zeep import Client
4
- from zeep.transports import Transport
5
3
  import requests
6
4
  import pandas as pd
7
5
 
6
+ from .schemas.children import ChildCreate, ChildDelete, ChildUpdate
7
+
8
8
 
9
9
  class Children:
10
10
  def __init__(self, nmbrs):
11
11
  self.nmbrs = nmbrs
12
- self.client = Client(wsdl='https://api.nmbrs.nl/soap/v3/EmployeeService.asmx?wsdl') #, transport=Transport(session=self.nmbrs.session))
13
- # self.client.set_default_soapheaders([auth_header])
14
- AuthHeaderWithDomainType = self.client.get_element('ns0:AuthHeaderWithDomain')
15
-
16
- auth_header = AuthHeaderWithDomainType(
17
- Username="erwin.vink@brynq.com",
18
- Token="cc358715f5c14cda8add964deef99ba3",
19
- Domain="extdev-brynq"
20
- )
21
- self.client.set_default_soapheaders([auth_header])
22
12
 
23
13
  def get(self,
24
14
  company_id: str,
@@ -42,59 +32,104 @@ class Children:
42
32
 
43
33
  return df
44
34
 
35
+ def create(self, employee_id: Union[int, str], data: Dict[str, Any]):
36
+ """
37
+ Create a new child for an employee using SOAP API.
38
+
39
+ Args:
40
+ employee_id: The ID of the employee
41
+ data: Dictionary containing child data with fields matching ChildCreate schema
42
+
43
+ Returns:
44
+ Response from the API (child ID)
45
+ """
46
+ # Validate with Pydantic model
47
+ child_model = ChildCreate(**data)
48
+
49
+ if self.nmbrs.mock_mode:
50
+ return child_model
51
+
52
+ ChildType = self.nmbrs.soap_client_employees.get_type('ns0:Child')
53
+ child = ChildType(
54
+ Id=0, # Use 0 for new child
55
+ Name=child_model.name,
56
+ FirstName=child_model.first_name,
57
+ Initials=child_model.initials or '',
58
+ Gender=child_model.gender,
59
+ Birthday=child_model.birthday
60
+ )
61
+
62
+ # Make the API call
63
+ result = self.nmbrs.soap_client_employees.service.Children_Insert(
64
+ EmployeeId=int(employee_id),
65
+ child=child,
66
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_employees}
67
+ )
68
+ return result
69
+
70
+ def delete(self, employee_id: Union[int, str], child_id: Union[int, str]):
71
+ """
72
+ Delete a child for an employee using SOAP API.
73
+
74
+ Args:
75
+ employee_id: The ID of the employee
76
+ child_id: The ID of the child to delete
77
+
78
+ Returns:
79
+ Response from the API
80
+ """
81
+ # Validate with Pydantic model
82
+ delete_model = ChildDelete(employeeId=int(employee_id), childId=int(child_id))
83
+
84
+ if self.nmbrs.mock_mode:
85
+ return delete_model
45
86
 
46
- def create(self,
47
- employee_id: str,
48
- data: dict):
87
+ # Call SOAP Child_Delete
88
+ resp = self.nmbrs.soap_client_employees.service.Child_Delete(
89
+ EmployeeId=delete_model.employee_id,
90
+ ChildId=delete_model.child_id,
91
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_employees}
92
+ )
93
+ return resp
94
+
95
+ def update(self, employee_id: Union[int, str], data: Dict[str, Any]):
96
+ """
97
+ Update a child for an employee using SOAP API.
98
+
99
+ Args:
100
+ employee_id: The ID of the employee
101
+ data: Dictionary containing child data with fields matching ChildUpdate schema:
102
+ - id: Child ID to update
103
+ - name: Last name
104
+ - first_name: First name
105
+ - initials: Initials (optional)
106
+ - gender: Gender (male/female/unknown/undefined)
107
+ - birthday: Birthday
49
108
 
50
- required_fields = ["first_name", "period", "function_id"]
51
- allowed_fields = {}
52
- # self.nmbrs.check_fields(data=data, required_fields=required_fields, allowed_fields=list(allowed_fields.keys()))
109
+ Returns:
110
+ Response from the API
111
+ """
112
+ # Validate with Pydantic model
113
+ child_model = ChildUpdate(**data)
53
114
 
54
- ChildType = self.client.get_type('ns0:Child')
115
+ if self.nmbrs.mock_mode:
116
+ return child_model
117
+
118
+ ChildType = self.nmbrs.soap_client_employees.get_type('ns0:Child')
55
119
  child = ChildType(
56
- Id=1, # Use 0 or omit if adding a new child
57
- Name='Doe',
58
- FirstName='John',
59
- Initials='J.D.',
60
- Gender='male', # Options: 'male', 'female', 'unknown', 'undefined'
61
- Birthday=datetime(2020, 1, 1) # Using a datetime object
120
+ Id=child_model.id,
121
+ Name=child_model.name,
122
+ FirstName=child_model.first_name,
123
+ Initials=child_model.initials or '',
124
+ Gender=child_model.gender,
125
+ Birthday=child_model.birthday
62
126
  )
63
127
 
64
128
  # Make the API call
65
- result = self.client.service.Children_Insert(
66
- EmployeeId=employee_id,
67
- child=child
129
+ result = self.nmbrs.soap_client_employees.service.Children_Update(
130
+ EmployeeId=int(employee_id),
131
+ child=child,
132
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_employees}
68
133
  )
69
- print("Child inserted successfully. Result:", result)
70
-
71
-
72
- # <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:emp="https://api.nmbrs.nl/soap/v3/EmployeeService">
73
- # <soap:Header>
74
- # <emp:AuthHeaderWithDomain>
75
- # <!--Optional:-->
76
- # <emp:Username>erwin.vink@brynq.com</emp:Username>
77
- # <!--Optional:-->
78
- # <emp:Token>cc358715f5c14cda8add964deef99ba3</emp:Token>
79
- # <!--Optional:-->
80
- # <emp:Domain>extdev-brynq</emp:Domain>
81
- # </emp:AuthHeaderWithDomain>
82
- # </soap:Header>
83
- # <soap:Body>
84
- # <emp:Children_Insert>
85
- # <emp:EmployeeId>11</emp:EmployeeId>
86
- # <!--Optional:-->
87
- # <emp:child>
88
- # <emp:Id>1</emp:Id>
89
- # <!--Optional:-->
90
- # <emp:Name>Doe</emp:Name>
91
- # <!--Optional:-->
92
- # <emp:FirstName>John</emp:FirstName>
93
- # <!--Optional:-->
94
- # <emp:Initials>J.</emp:Initials>
95
- # <emp:Gender>male</emp:Gender>
96
- # <emp:Birthday>2020-01-01T00:00:00</emp:Birthday>
97
- # </emp:child>
98
- # </emp:Children_Insert>
99
- # </soap:Body>
100
- # </soap:Envelope>
134
+ return result
135
+
@@ -1,6 +1,7 @@
1
1
  import pandas as pd
2
2
  import requests
3
3
  import logging
4
+ from typing import Dict, Any
4
5
 
5
6
  from .leave import LeaveGroup
6
7
  from .costcenter import Costcenter
@@ -8,6 +9,7 @@ from .costunit import Costunit
8
9
  from .hours import Hours
9
10
  from .bank import Bank
10
11
  from .function import Functions
12
+ from .schemas.company import CompanyCreate
11
13
  from zeep.exceptions import Fault
12
14
  from zeep.helpers import serialize_object
13
15
 
@@ -19,10 +21,13 @@ class Companies:
19
21
  self.costunits = Costunit(nmbrs)
20
22
  self.hours = Hours(nmbrs)
21
23
  self.banks = Bank(nmbrs)
22
- self.soap_client_companies = nmbrs.soap_client_companies
23
24
  self.logger = logging.getLogger(__name__)
24
25
  self.leave_groups = LeaveGroup(nmbrs)
25
26
 
27
+ @property
28
+ def soap_client_companies(self):
29
+ return self.nmbrs.soap_client_companies
30
+
26
31
  def get(self) -> pd.DataFrame:
27
32
  try:
28
33
  request = requests.Request(method='GET',
@@ -91,3 +96,42 @@ class Companies:
91
96
  except Exception as e:
92
97
  self.logger.exception("Exception occurred:")
93
98
  raise Exception(f"Failed to get current period: {str(e)}")
99
+
100
+ def create(self, data: Dict[str, Any]) -> int:
101
+ """
102
+ Create a new company using SOAP API.
103
+
104
+ Args:
105
+ data: Dictionary containing company data with fields matching CompanyCreate schema:
106
+ - debtor_id: Debtor ID
107
+ - company_name: Company name
108
+ - period_type: Period type (1=Monthly, 2=4-Weekly, 3=Weekly, 4=Quarterly)
109
+ - default_company_id: Default company ID to copy settings from (0 for none)
110
+ - labour_agreement_settings_group_guid: Labour agreement settings group GUID
111
+ - pay_in_advance: Pay in advance
112
+
113
+ Returns:
114
+ The ID of the newly created company.
115
+ """
116
+ company_model = CompanyCreate(**data)
117
+
118
+ if self.nmbrs.mock_mode:
119
+ return 12345 # Mock ID
120
+
121
+ try:
122
+ response = self.soap_client_companies.service.Company_Insert(
123
+ DebtorId=company_model.debtor_id,
124
+ CompanyName=company_model.company_name,
125
+ PeriodType=company_model.period_type,
126
+ DefaultCompanyId=company_model.default_company_id,
127
+ LabourAgreementSettingsGroupGuid=company_model.labour_agreement_settings_group_guid or "00000000-0000-0000-0000-000000000000",
128
+ PayInAdvance=company_model.pay_in_advance,
129
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header}
130
+ )
131
+ return response
132
+
133
+ except Fault as e:
134
+ raise Exception(f"SOAP request failed: {str(e)}")
135
+ except Exception as e:
136
+ self.logger.exception("Exception occurred:")
137
+ raise Exception(f"Failed to create company: {str(e)}")