brynq-sdk-nmbrs 2.3.1.dev0__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 (33) hide show
  1. brynq_sdk_nmbrs/__init__.py +86 -80
  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/debtors.py +76 -2
  6. brynq_sdk_nmbrs/department.py +113 -1
  7. brynq_sdk_nmbrs/document.py +50 -0
  8. brynq_sdk_nmbrs/employee_wage_tax_settings.py +16 -9
  9. brynq_sdk_nmbrs/function.py +128 -2
  10. brynq_sdk_nmbrs/leave.py +105 -8
  11. brynq_sdk_nmbrs/salaries.py +75 -3
  12. brynq_sdk_nmbrs/schedules.py +77 -3
  13. brynq_sdk_nmbrs/schemas/address.py +26 -0
  14. brynq_sdk_nmbrs/schemas/children.py +67 -0
  15. brynq_sdk_nmbrs/schemas/company.py +16 -0
  16. brynq_sdk_nmbrs/schemas/debtor.py +23 -1
  17. brynq_sdk_nmbrs/schemas/department.py +33 -0
  18. brynq_sdk_nmbrs/schemas/document.py +13 -0
  19. brynq_sdk_nmbrs/schemas/employees.py +3 -1
  20. brynq_sdk_nmbrs/schemas/function.py +28 -0
  21. brynq_sdk_nmbrs/schemas/leave.py +12 -0
  22. brynq_sdk_nmbrs/schemas/salary.py +18 -0
  23. brynq_sdk_nmbrs/schemas/schedules.py +49 -0
  24. brynq_sdk_nmbrs/schemas/social_insurance.py +39 -6
  25. brynq_sdk_nmbrs/schemas/wage_tax.py +65 -5
  26. brynq_sdk_nmbrs/schemas/wage_tax_settings.py +42 -29
  27. brynq_sdk_nmbrs/social_insurance.py +81 -3
  28. brynq_sdk_nmbrs/wage_tax.py +104 -3
  29. {brynq_sdk_nmbrs-2.3.1.dev0.dist-info → brynq_sdk_nmbrs-2.3.2.dev0.dist-info}/METADATA +1 -1
  30. brynq_sdk_nmbrs-2.3.2.dev0.dist-info/RECORD +55 -0
  31. brynq_sdk_nmbrs-2.3.1.dev0.dist-info/RECORD +0 -52
  32. {brynq_sdk_nmbrs-2.3.1.dev0.dist-info → brynq_sdk_nmbrs-2.3.2.dev0.dist-info}/WHEEL +0 -0
  33. {brynq_sdk_nmbrs-2.3.1.dev0.dist-info → brynq_sdk_nmbrs-2.3.2.dev0.dist-info}/top_level.txt +0 -0
@@ -21,17 +21,18 @@ from .costunit import Costunit
21
21
  from .days import FixedDays, VariableDays
22
22
  from .debtors import Debtors
23
23
  from .department import Departments, EmployeeDepartment
24
- from .document import Payslip
24
+ from .document import EmployeeDocument, Payslip
25
25
  from .employee_wage_tax_settings import EmployeeWageTaxSettings
26
26
  from .employees import Employees
27
27
  from .employment import Employment
28
28
  from .function import EmployeeFunction, Functions
29
29
  from .hours import FixedHours, VariableHours
30
+ from .leave import Leave, LeaveBalance, LeaveGroup
30
31
  from .manager import EmployeeManager, Manager
31
32
  from .salaries import Salaries
32
33
  from .salary_tables import SalaryScales, SalarySteps, SalaryTables
33
34
  from .schedules import Schedule
34
- from .wage_tax import WageTax
35
+ from .wage_tax import WageTax, WageTaxSettings
35
36
  from .wagecomponents import EmployeeFixedWageComponents, EmployeeVariableWageComponents
36
37
 
37
38
 
@@ -43,21 +44,42 @@ class Nmbrs(BrynQ):
43
44
  Args:
44
45
  label: The label of the system in BrynQ. legacy
45
46
  debug: Whether to print debug information
46
- 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
47
48
  """
48
49
  self.mock_mode = mock_mode
49
50
  self.debug = debug
50
51
  self.timeout = 3600
51
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
+
52
68
  if mock_mode is False:
69
+ # Initialize BrynQ parent class for REST API credentials
53
70
  super().__init__()
54
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
+
55
77
  headers = self._get_request_headers()
56
78
  self.base_url = "https://api.nmbrsapp.com/api/"
57
79
  self.session = requests.Session()
58
80
  self.session.headers.update(headers)
59
81
 
60
- # Initialize SOAP client
82
+ # Initialize SOAP clients
61
83
  self.soap_settings = Settings(
62
84
  strict=False,
63
85
  xml_huge_tree=True,
@@ -71,11 +93,24 @@ class Nmbrs(BrynQ):
71
93
  'https://api.nmbrs.nl/soap/v3/EmployeeService.asmx?wsdl',
72
94
  settings=self.soap_settings
73
95
  )
74
- self.soap_auth_header = self._get_soap_auth_header_companies()
75
- self.soap_auth_header_employees = self._get_soap_auth_header_employees()
76
- # 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
77
113
  if self.soap_auth_header is not None:
78
- self.companies = Companies(self)
79
114
  self.employees = Employees(self)
80
115
  self.soap_company_ids = self.companies.get_soap_ids()
81
116
  self.soap_employee_ids = self.employees.get_soap_ids()
@@ -84,96 +119,67 @@ class Nmbrs(BrynQ):
84
119
  self.salary_steps = SalarySteps(self)
85
120
  self.wage_tax = WageTax(self)
86
121
  self.absence = Absence(self)
87
- self.children = Children(self)
88
-
89
- self.address = Address(self)
90
- self.bank = Bank(self)
91
- self.children = Children(self)
92
- self.debtor = Debtors(self)
93
- self.companies = Companies(self)
94
- self.contract = Contract(self)
95
- self.employee_department = EmployeeDepartment(self)
96
- self.department = Departments(self)
97
- debtors, _ = self.debtor.get()
98
- self.debtor_ids = debtors['debtor_id'].to_list()
99
- self.company_ids = self.companies.get()['companyId'].to_list()
100
- self.employees = Employees(self)
101
- self.employment = Employment(self)
102
- self.employee_function = EmployeeFunction(self)
103
- self.functions = Functions(self)
104
- self.fixed_hours = FixedHours(self)
105
- self.fixed_days = FixedDays(self)
106
- self.variable_hours = VariableHours(self)
107
- self.variable_days = VariableDays(self)
108
- self.manager = Manager(self)
109
- self.employee_manager = EmployeeManager(self)
110
- self.salaries = Salaries(self)
111
- self.schedule = Schedule(self)
112
- self.cost_center = Costcenter(self)
113
- self.employee_cost_center = EmployeeCostcenter(self)
114
- self.cost_unit = Costunit(self)
115
- self.employee_wage_tax_settings = EmployeeWageTaxSettings(self)
116
- self.fixed_wagecomponents = EmployeeFixedWageComponents(self)
117
- self.variable_wagecomponents = EmployeeVariableWageComponents(self)
118
- self.current_period = self.companies.get_current_period()
119
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)
120
141
  def _get_request_headers(self):
121
- 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")
122
144
  headers = {
123
145
  "accept": "application/json",
124
- "Authorization": f"Bearer {credentials.get('data').get('access_token')}",
146
+ "Authorization": f"Bearer {access_token.strip() if access_token else ''}",
125
147
  # partner identifier
126
- "X-Subscription-Key": credentials.get("custom_data").get("subscription_key")
148
+ "X-Subscription-Key": subscription_key.strip() if subscription_key else ''
127
149
  }
128
150
 
129
151
  return headers
130
152
 
131
- def _get_soap_auth_header_companies(self):
153
+ def _create_soap_auth_header(self, soap_client):
132
154
  """
133
- Creates the SOAP authentication header using credentials from initial_credentials.
134
-
135
- Returns:
136
- AuthHeaderWithDomainType: The authentication header for SOAP requests
137
- """
138
- initial_credentials = self.interfaces.credentials.get(system='nmbrs', system_type=self.system_type)
139
- config = initial_credentials.get("custom_data", {})
140
-
141
- if 'soap_api_token' not in config.keys():
142
- return None
143
- else:
144
- # Get the AuthHeaderWithDomain type from the WSDL
145
- AuthHeaderWithDomainType = self.soap_client_companies.get_element('ns0:AuthHeaderWithDomain')
146
-
147
- # Create the auth header using credentials from config
148
- auth_header = AuthHeaderWithDomainType(
149
- Username=config.get("soap_api_username"),
150
- Token=config.get("soap_api_token"),
151
- Domain=config.get("soap_api_domain")
152
- )
153
-
154
- return auth_header
155
+ Creates SOAP authentication header for a specific SOAP client.
156
+ Each service needs its own auth header with matching namespace.
155
157
 
156
- def _get_soap_auth_header_employees(self):
157
- """
158
- Creates the SOAP authentication header using credentials from initial_credentials.
158
+ Args:
159
+ soap_client: The zeep SOAP client to create auth header for
159
160
 
160
161
  Returns:
161
- AuthHeaderWithDomainType: The authentication header for SOAP requests
162
+ AuthHeaderWithDomainType: The authentication header for SOAP requests,
163
+ or None if SOAP credentials are not configured.
162
164
  """
163
- initial_credentials = self.interfaces.credentials.get(system='nmbrs', system_type=self.system_type)
164
- custom_data = initial_credentials.get("custom_data", {})
165
+ if 'soap_api_token' not in self._custom_config.keys():
166
+ return None
165
167
 
166
- # Get the AuthHeaderWithDomain type from the WSDL
167
- AuthHeaderWithDomainType = self.soap_client_employees.get_element('ns0:AuthHeaderWithDomain')
168
+ AuthHeaderWithDomainType = soap_client.get_element('ns0:AuthHeaderWithDomain')
168
169
 
169
- # Create the auth header using credentials from config
170
- auth_header = AuthHeaderWithDomainType(
171
- Username=custom_data.get("soap_api_username"),
172
- Token=custom_data.get("soap_api_token"),
173
- Domain=custom_data.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")
174
174
  )
175
175
 
176
- 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
177
183
 
178
184
  def get_paginated_result(self, request: requests.Request) -> List:
179
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)}")
@@ -1,9 +1,11 @@
1
1
  import pandas as pd
2
2
  import requests
3
+ from typing import Dict, Any
4
+ from zeep.exceptions import Fault
3
5
  from brynq_sdk_functions import Functions
4
6
  from .department import Departments
5
7
  from .function import Functions as NmbrsFunctions
6
- from .schemas.debtor import DebtorsGet
8
+ from .schemas.debtor import DebtorsGet, DebtorCreate, DebtorUpdate
7
9
 
8
10
 
9
11
  class Debtors:
@@ -12,7 +14,6 @@ class Debtors:
12
14
  self.departments = Departments(nmbrs)
13
15
  self.functions = NmbrsFunctions(nmbrs)
14
16
 
15
-
16
17
  def get(self) -> (pd.DataFrame, pd.DataFrame):
17
18
  request = requests.Request(method='GET',
18
19
  url=f"{self.nmbrs.base_url}debtors")
@@ -23,3 +24,76 @@ class Debtors:
23
24
  valid_debtors, invalid_debtors = Functions.validate_data(df=df, schema=DebtorsGet, debug=True)
24
25
 
25
26
  return valid_debtors, invalid_debtors
27
+
28
+ def create(self, data: Dict[str, Any]) -> int:
29
+ """
30
+ Create a new debtor using SOAP API.
31
+
32
+ Args:
33
+ data: Dictionary containing debtor data with fields matching DebtorCreate schema:
34
+ - number: Debtor number
35
+ - name: Debtor name
36
+
37
+ Returns:
38
+ The ID of the newly created debtor.
39
+ """
40
+ debtor_model = DebtorCreate(**data)
41
+
42
+ if self.nmbrs.mock_mode:
43
+ return 12345 # Mock ID
44
+
45
+ try:
46
+ DebtorType = self.nmbrs.soap_client_debtors.get_type('ns0:Debtor')
47
+ soap_debtor = DebtorType(
48
+ Id=0, # 0 for new debtor
49
+ Number=debtor_model.number,
50
+ Name=debtor_model.name
51
+ )
52
+
53
+ response = self.nmbrs.soap_client_debtors.service.Debtor_Insert(
54
+ Debtor=soap_debtor,
55
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_debtors}
56
+ )
57
+ return response
58
+
59
+ except Fault as e:
60
+ raise Exception(f"SOAP request failed: {str(e)}")
61
+ except Exception as e:
62
+ raise Exception(f"Failed to create Debtor: {str(e)}")
63
+
64
+ def update(self, data: Dict[str, Any]):
65
+ """
66
+ Update a debtor using SOAP API.
67
+
68
+ Args:
69
+ data: Dictionary containing debtor data with fields matching DebtorUpdate schema:
70
+ - debtor_id: Debtor ID to update
71
+ - number: Debtor number
72
+ - name: Debtor name
73
+
74
+ Returns:
75
+ Response from the API
76
+ """
77
+ debtor_model = DebtorUpdate(**data)
78
+
79
+ if self.nmbrs.mock_mode:
80
+ return debtor_model
81
+
82
+ try:
83
+ DebtorType = self.nmbrs.soap_client_debtors.get_type('ns0:Debtor')
84
+ soap_debtor = DebtorType(
85
+ Id=debtor_model.debtor_id,
86
+ Number=debtor_model.number,
87
+ Name=debtor_model.name
88
+ )
89
+
90
+ response = self.nmbrs.soap_client_debtors.service.Debtor_Update(
91
+ Debtor=soap_debtor,
92
+ _soapheaders={'AuthHeaderWithDomain': self.nmbrs.soap_auth_header_debtors}
93
+ )
94
+ return response
95
+
96
+ except Fault as e:
97
+ raise Exception(f"SOAP request failed: {str(e)}")
98
+ except Exception as e:
99
+ raise Exception(f"Failed to update Debtor: {str(e)}")