brynq-sdk-nmbrs 2.1.0__tar.gz → 2.2.0__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.
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/PKG-INFO +1 -1
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/__init__.py +41 -5
- brynq_sdk_nmbrs-2.2.0/brynq_sdk_nmbrs/absence.py +131 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/address.py +3 -3
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/bank.py +6 -4
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/companies.py +39 -9
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/contract.py +10 -6
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/costcenter.py +14 -37
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/costunit.py +6 -4
- brynq_sdk_nmbrs-2.2.0/brynq_sdk_nmbrs/days.py +137 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/department.py +9 -6
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/employees.py +34 -17
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/employment.py +6 -4
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/function.py +4 -32
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/hours.py +44 -38
- brynq_sdk_nmbrs-2.2.0/brynq_sdk_nmbrs/leave.py +139 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/salaries.py +6 -33
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/salary_tables.py +28 -28
- brynq_sdk_nmbrs-2.2.0/brynq_sdk_nmbrs/schedules.py +84 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/__init__.py +11 -8
- brynq_sdk_nmbrs-2.2.0/brynq_sdk_nmbrs/schemas/absence.py +61 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/address.py +1 -31
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/bank.py +16 -17
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/contracts.py +3 -3
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/costcenter.py +18 -16
- brynq_sdk_nmbrs-2.2.0/brynq_sdk_nmbrs/schemas/days.py +96 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/department.py +5 -11
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/employees.py +16 -12
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/function.py +1 -8
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/hours.py +10 -7
- brynq_sdk_nmbrs-2.2.0/brynq_sdk_nmbrs/schemas/leave.py +52 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/salary.py +8 -16
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/schedules.py +9 -28
- brynq_sdk_nmbrs-2.2.0/brynq_sdk_nmbrs/schemas/social_insurance.py +40 -0
- brynq_sdk_nmbrs-2.2.0/brynq_sdk_nmbrs/schemas/wage_tax.py +57 -0
- brynq_sdk_nmbrs-2.2.0/brynq_sdk_nmbrs/social_insurance.py +55 -0
- brynq_sdk_nmbrs-2.2.0/brynq_sdk_nmbrs/wage_tax.py +124 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs.egg-info/PKG-INFO +1 -1
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs.egg-info/SOURCES.txt +10 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/setup.py +1 -1
- brynq_sdk_nmbrs-2.1.0/brynq_sdk_nmbrs/schedules.py +0 -149
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/children.py +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/debtors.py +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/document.py +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/manager.py +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/costunit.py +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/debtor.py +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/employment.py +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/manager.py +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/schemas/wagecomponents.py +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs/wagecomponents.py +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs.egg-info/dependency_links.txt +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs.egg-info/not-zip-safe +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs.egg-info/requires.txt +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/brynq_sdk_nmbrs.egg-info/top_level.txt +0 -0
- {brynq_sdk_nmbrs-2.1.0 → brynq_sdk_nmbrs-2.2.0}/setup.cfg +0 -0
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import warnings
|
|
2
|
-
from typing import Union, List, Literal, Optional
|
|
2
|
+
from typing import Union, List, Literal, Optional, get_args, get_origin
|
|
3
3
|
import re
|
|
4
4
|
import pandas as pd
|
|
5
|
+
from pydantic import BaseModel
|
|
5
6
|
import requests
|
|
6
7
|
from brynq_sdk_brynq import BrynQ
|
|
8
|
+
from .absence import Absence
|
|
9
|
+
from .wage_tax import WageTax
|
|
7
10
|
from .address import Address
|
|
8
11
|
from .bank import Bank
|
|
9
12
|
from zeep import Client, Settings
|
|
@@ -20,6 +23,7 @@ from .salary_tables import SalaryTables, SalaryScales, SalarySteps
|
|
|
20
23
|
from .employment import Employment
|
|
21
24
|
from .function import EmployeeFunction
|
|
22
25
|
from .hours import VariableHours, FixedHours
|
|
26
|
+
from .days import VariableDays, FixedDays
|
|
23
27
|
from .manager import EmployeeManager, Manager
|
|
24
28
|
from .salaries import Salaries
|
|
25
29
|
from .schedules import Schedule
|
|
@@ -35,7 +39,7 @@ class Nmbrs(BrynQ):
|
|
|
35
39
|
Args:
|
|
36
40
|
label: The label of the system in BrynQ. legacy
|
|
37
41
|
debug: Whether to print debug information
|
|
38
|
-
mock_mode: If true, data will NOT be
|
|
42
|
+
11 mock_mode: If true, data will NOT be sent to Nmbrs but only be tested for validity against Pydantic schemas
|
|
39
43
|
"""
|
|
40
44
|
self.mock_mode = mock_mode
|
|
41
45
|
self.debug = debug
|
|
@@ -55,10 +59,14 @@ class Nmbrs(BrynQ):
|
|
|
55
59
|
xml_huge_tree=True,
|
|
56
60
|
force_https=True
|
|
57
61
|
)
|
|
58
|
-
self.
|
|
62
|
+
self.soap_client_companies = Client(
|
|
59
63
|
'https://api.nmbrs.nl/soap/v3/CompanyService.asmx?wsdl',
|
|
60
64
|
settings=self.soap_settings
|
|
61
65
|
)
|
|
66
|
+
self.soap_client_employees = Client(
|
|
67
|
+
'https://api.nmbrs.nl/soap/v3/EmployeeService.asmx?wsdl',
|
|
68
|
+
settings=self.soap_settings
|
|
69
|
+
)
|
|
62
70
|
|
|
63
71
|
self.address = Address(self)
|
|
64
72
|
self.bank = Bank(self)
|
|
@@ -67,13 +75,17 @@ class Nmbrs(BrynQ):
|
|
|
67
75
|
self.companies = Companies(self)
|
|
68
76
|
self.contract = Contract(self)
|
|
69
77
|
self.department = EmployeeDepartment(self)
|
|
70
|
-
|
|
78
|
+
debtors, _ = self.debtor.get()
|
|
79
|
+
self.debtor_ids = debtors['debtor_id'].to_list()
|
|
80
|
+
self.company_ids = self.companies.get()['companyId'].to_list()
|
|
71
81
|
self.soap_company_ids = self.companies.get_soap_ids()
|
|
72
82
|
self.employees = Employees(self)
|
|
73
83
|
self.employment = Employment(self)
|
|
74
84
|
self.function = EmployeeFunction(self)
|
|
75
85
|
self.fixed_hours = FixedHours(self)
|
|
86
|
+
self.fixed_days = FixedDays(self)
|
|
76
87
|
self.variable_hours = VariableHours(self)
|
|
88
|
+
self.variable_days = VariableDays(self)
|
|
77
89
|
self.manager = Manager(self)
|
|
78
90
|
self.employee_manager = EmployeeManager(self)
|
|
79
91
|
self.salaries = Salaries(self)
|
|
@@ -84,6 +96,9 @@ class Nmbrs(BrynQ):
|
|
|
84
96
|
self.salary_tables = SalaryTables(self)
|
|
85
97
|
self.salary_scales = SalaryScales(self)
|
|
86
98
|
self.salary_steps = SalarySteps(self)
|
|
99
|
+
self.wage_tax = WageTax(self)
|
|
100
|
+
self.absence = Absence(self)
|
|
101
|
+
self.current_period = self.companies.get_current_period()
|
|
87
102
|
|
|
88
103
|
def _get_request_headers(self):
|
|
89
104
|
credentials = self.interfaces.credentials.get(system='nmbrs', system_type=self.system_type)
|
|
@@ -99,6 +114,7 @@ class Nmbrs(BrynQ):
|
|
|
99
114
|
def _get_soap_auth_header(self):
|
|
100
115
|
"""
|
|
101
116
|
Creates the SOAP authentication header using credentials from initial_credentials.
|
|
117
|
+
|
|
102
118
|
Returns:
|
|
103
119
|
AuthHeaderWithDomainType: The authentication header for SOAP requests
|
|
104
120
|
"""
|
|
@@ -106,7 +122,7 @@ class Nmbrs(BrynQ):
|
|
|
106
122
|
config = initial_credentials.get("config", {})
|
|
107
123
|
|
|
108
124
|
# Get the AuthHeaderWithDomain type from the WSDL
|
|
109
|
-
AuthHeaderWithDomainType = self.
|
|
125
|
+
AuthHeaderWithDomainType = self.soap_client_companies.get_element('ns0:AuthHeaderWithDomain')
|
|
110
126
|
|
|
111
127
|
# Create the auth header using credentials from config
|
|
112
128
|
auth_header = AuthHeaderWithDomainType(
|
|
@@ -160,3 +176,23 @@ class Nmbrs(BrynQ):
|
|
|
160
176
|
df.columns = map(camel_to_snake_case, df.columns)
|
|
161
177
|
|
|
162
178
|
return df
|
|
179
|
+
|
|
180
|
+
def flat_dict_to_nested_dict(self, flat_dict: dict, model: BaseModel) -> dict:
|
|
181
|
+
nested = {}
|
|
182
|
+
for name, field in model.model_fields.items():
|
|
183
|
+
key_in_input = name # Original model field name as key in flat_dict
|
|
184
|
+
alias = field.alias or name
|
|
185
|
+
if isinstance(field.annotation, type) and issubclass(field.annotation, BaseModel):
|
|
186
|
+
nested[alias] = self.flat_dict_to_nested_dict(flat_dict, field.annotation)
|
|
187
|
+
elif any(isinstance(item, type) and issubclass(item, BaseModel) for item in get_args(field.annotation)):
|
|
188
|
+
# get the basemodel class from the list
|
|
189
|
+
nested_model_name = [item for item in get_args(field.annotation) if isinstance(item, type) and issubclass(item, BaseModel)][0]
|
|
190
|
+
origin = get_origin(field.annotation)
|
|
191
|
+
if origin in (list, List):
|
|
192
|
+
nested[alias] = [self.flat_dict_to_nested_dict(flat_dict, nested_model_name)]
|
|
193
|
+
else:
|
|
194
|
+
nested[alias] = self.flat_dict_to_nested_dict(flat_dict, nested_model_name)
|
|
195
|
+
else:
|
|
196
|
+
if key_in_input in flat_dict:
|
|
197
|
+
nested[alias] = flat_dict[key_in_input]
|
|
198
|
+
return nested
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Union, Tuple
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from .schemas.absence import AbsenceCreate, AbsenceGet
|
|
4
|
+
from zeep.exceptions import Fault
|
|
5
|
+
from zeep.ns import WSDL, SOAP_ENV_11
|
|
6
|
+
from zeep.xsd import ComplexType, Element, String
|
|
7
|
+
from zeep.helpers import serialize_object
|
|
8
|
+
# import logging
|
|
9
|
+
from brynq_sdk_functions import Functions
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Absence:
|
|
13
|
+
def __init__(self, nmbrs):
|
|
14
|
+
self.nmbrs = nmbrs
|
|
15
|
+
self.soap_client_companies = nmbrs.soap_client_companies
|
|
16
|
+
self.soap_client_employees = nmbrs.soap_client_employees
|
|
17
|
+
|
|
18
|
+
def get(self, employee_id: int = None) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
19
|
+
"""
|
|
20
|
+
Get salary tables for all companies for a specific period and year.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
period (int): The period number
|
|
24
|
+
year (int): The year
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
pd.DataFrame: DataFrame containing the salary tables
|
|
28
|
+
"""
|
|
29
|
+
absences = pd.DataFrame()
|
|
30
|
+
for company in self.nmbrs.soap_company_ids.to_dict(orient='records'):
|
|
31
|
+
absences_temp = self._get(company['i_d'], employee_id=employee_id)
|
|
32
|
+
if not absences_temp.empty:
|
|
33
|
+
absences_temp['companyId'] = company['number']
|
|
34
|
+
absences = pd.concat([absences, absences_temp])
|
|
35
|
+
|
|
36
|
+
valid_absences, invalid_absences = Functions.validate_data(df=absences, schema=AbsenceGet, debug=True)
|
|
37
|
+
|
|
38
|
+
# No validation schema for now, but could be added later
|
|
39
|
+
return valid_absences, invalid_absences
|
|
40
|
+
|
|
41
|
+
def _get(self, company_id: int, employee_id: int = None) -> pd.DataFrame:
|
|
42
|
+
"""
|
|
43
|
+
Get salary tables for a specific company, period and year.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
company_id (int): The ID of the company
|
|
47
|
+
period (int): The period number
|
|
48
|
+
year (int): The year
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
pd.DataFrame: DataFrame containing the salary tables
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
|
|
55
|
+
# Get the auth header using the centralized method
|
|
56
|
+
auth_header = self.nmbrs._get_soap_auth_header()
|
|
57
|
+
|
|
58
|
+
if employee_id is None:
|
|
59
|
+
response = self.soap_client_employees.service.Absence_GetAll_AllEmployeesByCompany(
|
|
60
|
+
CompanyId=company_id,
|
|
61
|
+
_soapheaders=[auth_header]
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
# Make SOAP request with the proper header structure
|
|
65
|
+
response = self.soap_client_employees.service.Absence_GetList(
|
|
66
|
+
EmployeeId=employee_id,
|
|
67
|
+
_soapheaders=[auth_header]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Convert response to DataFrame
|
|
71
|
+
if response:
|
|
72
|
+
# Convert Zeep objects to Python dictionaries
|
|
73
|
+
serialized_response = serialize_object(response)
|
|
74
|
+
|
|
75
|
+
# Convert to list if it's not already
|
|
76
|
+
if not isinstance(serialized_response, list):
|
|
77
|
+
serialized_response = [serialized_response]
|
|
78
|
+
|
|
79
|
+
# Convert to DataFrame
|
|
80
|
+
df = pd.DataFrame(serialized_response)
|
|
81
|
+
|
|
82
|
+
return df
|
|
83
|
+
else:
|
|
84
|
+
return pd.DataFrame()
|
|
85
|
+
|
|
86
|
+
except Fault as e:
|
|
87
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
|
88
|
+
except Exception as e:
|
|
89
|
+
raise Exception(f"Failed to get salary tables: {str(e)}")
|
|
90
|
+
|
|
91
|
+
def create(self, data: Dict[str, Any]) -> pd.DataFrame:
|
|
92
|
+
try:
|
|
93
|
+
absence_model = AbsenceCreate(**data)
|
|
94
|
+
|
|
95
|
+
if self.nmbrs.mock_mode:
|
|
96
|
+
return absence_model
|
|
97
|
+
|
|
98
|
+
# Get the auth header using the centralized method
|
|
99
|
+
auth_header = self.nmbrs._get_soap_auth_header()
|
|
100
|
+
|
|
101
|
+
# Use the model's built-in SOAP conversion method
|
|
102
|
+
absence_settings = absence_model.to_soap_settings(self.nmbrs.soap_client_employees)
|
|
103
|
+
|
|
104
|
+
# Make SOAP request with clean, simple call
|
|
105
|
+
response = self.nmbrs.soap_client_employees.service.Absence_Create(
|
|
106
|
+
EmployeeId=absence_model.employee_id,
|
|
107
|
+
Dossier=absence_model.new_dossier,
|
|
108
|
+
Absence=absence_settings,
|
|
109
|
+
_soapheaders=[auth_header]
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Convert response to DataFrame
|
|
113
|
+
if response:
|
|
114
|
+
# Convert Zeep objects to Python dictionaries
|
|
115
|
+
serialized_response = serialize_object(response)
|
|
116
|
+
|
|
117
|
+
# Convert to list if it's not already
|
|
118
|
+
if not isinstance(serialized_response, list):
|
|
119
|
+
serialized_response = [serialized_response]
|
|
120
|
+
|
|
121
|
+
# Convert to DataFrame
|
|
122
|
+
df = pd.DataFrame(serialized_response)
|
|
123
|
+
|
|
124
|
+
return df
|
|
125
|
+
else:
|
|
126
|
+
return pd.DataFrame()
|
|
127
|
+
|
|
128
|
+
except Fault as e:
|
|
129
|
+
raise Exception(f"SOAP request failed: {str(e)}")
|
|
130
|
+
except Exception as e:
|
|
131
|
+
raise Exception(f"Failed to update WageTax: {str(e)}")
|
|
@@ -48,13 +48,14 @@ class Address:
|
|
|
48
48
|
Response from the API
|
|
49
49
|
"""
|
|
50
50
|
# Validate with Pydantic model - this will raise an error if required fields are missing
|
|
51
|
-
|
|
51
|
+
nested_data = self.nmbrs.flat_dict_to_nested_dict(data, AddressCreate)
|
|
52
|
+
address_model = AddressCreate(**nested_data)
|
|
52
53
|
|
|
53
54
|
if self.nmbrs.mock_mode:
|
|
54
55
|
return address_model
|
|
55
56
|
|
|
56
57
|
# Convert validated model to dict for API payload
|
|
57
|
-
payload = address_model.
|
|
58
|
+
payload = address_model.model_dump(exclude_none=True, by_alias=True)
|
|
58
59
|
|
|
59
60
|
# Send request
|
|
60
61
|
resp = self.nmbrs.session.post(
|
|
@@ -63,4 +64,3 @@ class Address:
|
|
|
63
64
|
timeout=self.nmbrs.timeout
|
|
64
65
|
)
|
|
65
66
|
return resp
|
|
66
|
-
|
|
@@ -54,13 +54,14 @@ class Bank:
|
|
|
54
54
|
Response from the API
|
|
55
55
|
"""
|
|
56
56
|
# Validate with Pydantic model - this will raise an error if required fields are missing
|
|
57
|
-
|
|
57
|
+
nested_data = self.nmbrs.flat_dict_to_nested_dict(data, BankCreate)
|
|
58
|
+
bank_model = BankCreate(**nested_data)
|
|
58
59
|
|
|
59
60
|
if self.nmbrs.mock_mode:
|
|
60
61
|
return bank_model
|
|
61
62
|
|
|
62
63
|
# Convert validated model to dict for API payload
|
|
63
|
-
payload = bank_model.
|
|
64
|
+
payload = bank_model.model_dump(exclude_none=True, by_alias=True)
|
|
64
65
|
|
|
65
66
|
# Send request
|
|
66
67
|
resp = self.nmbrs.session.post(
|
|
@@ -82,13 +83,14 @@ class Bank:
|
|
|
82
83
|
Response from the API
|
|
83
84
|
"""
|
|
84
85
|
# Validate with Pydantic model - this will raise an error if required fields are missing
|
|
85
|
-
|
|
86
|
+
nested_data = self.nmbrs.flat_dict_to_nested_dict(data, BankUpdate)
|
|
87
|
+
bank_model = BankUpdate(**nested_data)
|
|
86
88
|
|
|
87
89
|
if self.nmbrs.mock_mode:
|
|
88
90
|
return bank_model
|
|
89
91
|
|
|
90
92
|
# Convert validated model to dict for API payload
|
|
91
|
-
payload = bank_model.
|
|
93
|
+
payload = bank_model.model_dump(exclude_none=True, by_alias=True)
|
|
92
94
|
|
|
93
95
|
# Send request
|
|
94
96
|
resp = self.nmbrs.session.put(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
import requests
|
|
3
3
|
import logging
|
|
4
|
+
|
|
5
|
+
from .leave import LeaveGroup
|
|
4
6
|
from .costcenter import Costcenter
|
|
5
7
|
from .costunit import Costunit
|
|
6
8
|
from .hours import Hours
|
|
@@ -17,22 +19,34 @@ class Companies:
|
|
|
17
19
|
self.costunits = Costunit(nmbrs)
|
|
18
20
|
self.hours = Hours(nmbrs)
|
|
19
21
|
self.banks = Bank(nmbrs)
|
|
20
|
-
self.
|
|
22
|
+
self.soap_client_companies = nmbrs.soap_client_companies
|
|
21
23
|
self.logger = logging.getLogger(__name__)
|
|
24
|
+
self.leave_groups = LeaveGroup(nmbrs)
|
|
22
25
|
|
|
23
26
|
def get(self) -> pd.DataFrame:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
29
43
|
|
|
30
44
|
return df
|
|
31
45
|
|
|
32
46
|
def get_soap_ids(self) -> pd.DataFrame:
|
|
33
47
|
"""
|
|
34
48
|
Get all companies using the SOAP API.
|
|
35
|
-
|
|
49
|
+
|
|
36
50
|
Returns:
|
|
37
51
|
pd.DataFrame: DataFrame containing all companies
|
|
38
52
|
"""
|
|
@@ -41,10 +55,10 @@ class Companies:
|
|
|
41
55
|
auth_header = self.nmbrs._get_soap_auth_header()
|
|
42
56
|
|
|
43
57
|
# Make SOAP request with the proper header structure
|
|
44
|
-
response = self.
|
|
58
|
+
response = self.soap_client_companies.service.List_GetAll(
|
|
45
59
|
_soapheaders=[auth_header]
|
|
46
60
|
)
|
|
47
|
-
|
|
61
|
+
|
|
48
62
|
# Convert response to DataFrame
|
|
49
63
|
if response:
|
|
50
64
|
# Convert Zeep objects to Python dictionaries
|
|
@@ -64,3 +78,19 @@ class Companies:
|
|
|
64
78
|
except Exception as e:
|
|
65
79
|
self.logger.exception("Exception occurred:")
|
|
66
80
|
raise Exception(f"Failed to get companies: {str(e)}")
|
|
81
|
+
|
|
82
|
+
def get_current_period(self) -> int:
|
|
83
|
+
try:
|
|
84
|
+
df = pd.DataFrame()
|
|
85
|
+
for company_id in self.nmbrs.company_ids:
|
|
86
|
+
request = requests.Request(method='GET',
|
|
87
|
+
url=f"{self.nmbrs.base_url}companies/{company_id}/period")
|
|
88
|
+
data = self.nmbrs.get_paginated_result(request)
|
|
89
|
+
df_temp = pd.json_normalize(data)
|
|
90
|
+
df_temp['company_id'] = company_id
|
|
91
|
+
df = pd.concat([df, df_temp])
|
|
92
|
+
|
|
93
|
+
return df
|
|
94
|
+
except Exception as e:
|
|
95
|
+
self.logger.exception("Exception occurred:")
|
|
96
|
+
raise Exception(f"Failed to get current period: {str(e)}")
|
|
@@ -58,18 +58,20 @@ class Contract:
|
|
|
58
58
|
Response from the API
|
|
59
59
|
"""
|
|
60
60
|
# Validate with Pydantic model - this will raise an error if required fields are missing
|
|
61
|
-
|
|
61
|
+
nested_data = self.nmbrs.flat_dict_to_nested_dict(data, ContractCreate)
|
|
62
|
+
contract_model = ContractCreate(**nested_data)
|
|
62
63
|
|
|
63
64
|
if self.nmbrs.mock_mode:
|
|
64
65
|
return contract_model
|
|
65
66
|
|
|
66
67
|
# Convert validated model to dict for API payload
|
|
67
|
-
payload = contract_model.
|
|
68
|
+
payload = contract_model.model_dump_json(exclude_none=True, by_alias=True)
|
|
68
69
|
|
|
69
70
|
# Send request
|
|
70
71
|
resp = self.nmbrs.session.post(
|
|
71
72
|
url=f"{self.nmbrs.base_url}employees/{employee_id}/contract",
|
|
72
|
-
|
|
73
|
+
data=payload,
|
|
74
|
+
headers={"Content-Type": "application/json"},
|
|
73
75
|
timeout=self.nmbrs.timeout
|
|
74
76
|
)
|
|
75
77
|
return resp
|
|
@@ -87,18 +89,20 @@ class Contract:
|
|
|
87
89
|
Response from the API
|
|
88
90
|
"""
|
|
89
91
|
# Validate with Pydantic model - this will raise an error if required fields are missing
|
|
90
|
-
|
|
92
|
+
nested_data = self.nmbrs.flat_dict_to_nested_dict(data, ContractUpdate)
|
|
93
|
+
contract_model = ContractUpdate(**nested_data)
|
|
91
94
|
|
|
92
95
|
if self.nmbrs.mock_mode:
|
|
93
96
|
return contract_model
|
|
94
97
|
|
|
95
98
|
# Convert validated model to dict for API payload
|
|
96
|
-
payload = contract_model.
|
|
99
|
+
payload = contract_model.model_dump_json(exclude_none=True, by_alias=True)
|
|
97
100
|
|
|
98
101
|
# Send request
|
|
99
102
|
resp = self.nmbrs.session.put(
|
|
100
103
|
url=f"{self.nmbrs.base_url}employees/{employee_id}/contract",
|
|
101
|
-
|
|
104
|
+
data=payload,
|
|
105
|
+
headers={"Content-Type": "application/json"},
|
|
102
106
|
timeout=self.nmbrs.timeout
|
|
103
107
|
)
|
|
104
108
|
return resp
|
|
@@ -6,7 +6,7 @@ from typing import Dict, Any
|
|
|
6
6
|
|
|
7
7
|
from brynq_sdk_functions import Functions
|
|
8
8
|
from .schemas.costcenter import CostcenterGet, EmployeeCostcenterGet
|
|
9
|
-
from .schemas.costcenter import
|
|
9
|
+
from .schemas.costcenter import EmployeeCostcenterUpdate, EmployeeCostcenterDelete
|
|
10
10
|
from .schemas.costcenter import CostcenterCreate, CostcenterUpdate, CostcenterDelete
|
|
11
11
|
|
|
12
12
|
|
|
@@ -46,35 +46,6 @@ class EmployeeCostcenter:
|
|
|
46
46
|
|
|
47
47
|
return df
|
|
48
48
|
|
|
49
|
-
def create(self, employee_id: str, data: Dict[str, Any]):
|
|
50
|
-
"""
|
|
51
|
-
Create a new costcenter for an employee using Pydantic validation.
|
|
52
|
-
|
|
53
|
-
Args:
|
|
54
|
-
employee_id: The ID of the employee
|
|
55
|
-
data: Dictionary containing costcenter data with fields matching
|
|
56
|
-
the EmployeeCostcenterCreate schema (using camelCase field names)
|
|
57
|
-
|
|
58
|
-
Returns:
|
|
59
|
-
Response from the API
|
|
60
|
-
"""
|
|
61
|
-
# Validate with Pydantic model - this will raise an error if required fields are missing
|
|
62
|
-
costcenter_model = EmployeeCostcenterCreate(**data)
|
|
63
|
-
|
|
64
|
-
if self.nmbrs.mock_mode:
|
|
65
|
-
return costcenter_model
|
|
66
|
-
|
|
67
|
-
# Convert validated model to dict for API payload
|
|
68
|
-
payload = costcenter_model.dict(exclude_none=True)
|
|
69
|
-
|
|
70
|
-
# Send request
|
|
71
|
-
resp = self.nmbrs.session.post(
|
|
72
|
-
url=f"{self.nmbrs.base_url}employees/{employee_id}/costcenter",
|
|
73
|
-
json=payload,
|
|
74
|
-
timeout=self.nmbrs.timeout
|
|
75
|
-
)
|
|
76
|
-
return resp
|
|
77
|
-
|
|
78
49
|
def update(self, employee_id: str, data: Dict[str, Any]):
|
|
79
50
|
"""
|
|
80
51
|
Update a costcenter for an employee using Pydantic validation.
|
|
@@ -87,18 +58,22 @@ class EmployeeCostcenter:
|
|
|
87
58
|
Returns:
|
|
88
59
|
Response from the API
|
|
89
60
|
"""
|
|
61
|
+
# this is the Nmbrs GUID that is returned after creating an employee, for some reason also included in body here.
|
|
62
|
+
data['employee_id'] = employee_id
|
|
63
|
+
data['default'] = True
|
|
90
64
|
# Validate with Pydantic model - this will raise an error if required fields are missing
|
|
91
|
-
|
|
65
|
+
nested_data = self.nmbrs.flat_dict_to_nested_dict(data, EmployeeCostcenterUpdate)
|
|
66
|
+
costcenter_model = EmployeeCostcenterUpdate(**nested_data)
|
|
92
67
|
|
|
93
68
|
if self.nmbrs.mock_mode:
|
|
94
69
|
return costcenter_model
|
|
95
70
|
|
|
96
71
|
# Convert validated model to dict for API payload
|
|
97
|
-
payload = costcenter_model.
|
|
72
|
+
payload = costcenter_model.model_dump(exclude_none=True, by_alias=True)
|
|
98
73
|
|
|
99
74
|
# Send request
|
|
100
75
|
resp = self.nmbrs.session.put(
|
|
101
|
-
url=f"{self.nmbrs.base_url}employees/{employee_id}/
|
|
76
|
+
url=f"{self.nmbrs.base_url}employees/{employee_id}/employeecostcenter",
|
|
102
77
|
json=payload,
|
|
103
78
|
timeout=self.nmbrs.timeout
|
|
104
79
|
)
|
|
@@ -141,13 +116,14 @@ class Costcenter:
|
|
|
141
116
|
Response from the API
|
|
142
117
|
"""
|
|
143
118
|
# Validate with Pydantic model - this will raise an error if required fields are missing
|
|
144
|
-
|
|
119
|
+
nested_data = self.nmbrs.flat_dict_to_nested_dict(data, CostcenterCreate)
|
|
120
|
+
costcenter_model = CostcenterCreate(**nested_data)
|
|
145
121
|
|
|
146
122
|
if self.nmbrs.mock_mode:
|
|
147
123
|
return costcenter_model
|
|
148
124
|
|
|
149
125
|
# Convert validated model to dict for API payload
|
|
150
|
-
payload = costcenter_model.
|
|
126
|
+
payload = costcenter_model.model_dump(exclude_none=True, by_alias=True)
|
|
151
127
|
|
|
152
128
|
# Send request
|
|
153
129
|
resp = self.nmbrs.session.post(
|
|
@@ -170,13 +146,14 @@ class Costcenter:
|
|
|
170
146
|
Response from the API
|
|
171
147
|
"""
|
|
172
148
|
# Validate with Pydantic model - this will raise an error if required fields are missing
|
|
173
|
-
|
|
149
|
+
nested_data = self.nmbrs.flat_dict_to_nested_dict(data, CostcenterUpdate)
|
|
150
|
+
costcenter_model = CostcenterUpdate(**nested_data)
|
|
174
151
|
|
|
175
152
|
if self.nmbrs.mock_mode:
|
|
176
153
|
return costcenter_model
|
|
177
154
|
|
|
178
155
|
# Convert validated model to dict for API payload
|
|
179
|
-
payload = costcenter_model.
|
|
156
|
+
payload = costcenter_model.model_dump(exclude_none=True, by_alias=True)
|
|
180
157
|
|
|
181
158
|
# Send request
|
|
182
159
|
resp = self.nmbrs.session.put(
|
|
@@ -42,13 +42,14 @@ class Costunit:
|
|
|
42
42
|
Response from the API
|
|
43
43
|
"""
|
|
44
44
|
# Validate with Pydantic model - this will raise an error if required fields are missing
|
|
45
|
-
|
|
45
|
+
nested_data = self.nmbrs.flat_dict_to_nested_dict(data, CostunitCreate)
|
|
46
|
+
costunit_model = CostunitCreate(**nested_data)
|
|
46
47
|
|
|
47
48
|
if self.nmbrs.mock_mode:
|
|
48
49
|
return costunit_model
|
|
49
50
|
|
|
50
51
|
# Convert validated model to dict for API payload
|
|
51
|
-
payload = costunit_model.
|
|
52
|
+
payload = costunit_model.model_dump(exclude_none=True, by_alias=True)
|
|
52
53
|
|
|
53
54
|
# Send request
|
|
54
55
|
resp = self.nmbrs.session.post(
|
|
@@ -71,13 +72,14 @@ class Costunit:
|
|
|
71
72
|
Response from the API
|
|
72
73
|
"""
|
|
73
74
|
# Validate with Pydantic model - this will raise an error if required fields are missing
|
|
74
|
-
|
|
75
|
+
nested_data = self.nmbrs.flat_dict_to_nested_dict(data, CostunitUpdate)
|
|
76
|
+
costunit_model = CostunitUpdate(**nested_data)
|
|
75
77
|
|
|
76
78
|
if self.nmbrs.mock_mode:
|
|
77
79
|
return costunit_model
|
|
78
80
|
|
|
79
81
|
# Convert validated model to dict for API payload
|
|
80
|
-
payload = costunit_model.
|
|
82
|
+
payload = costunit_model.model_dump(exclude_none=True, by_alias=True)
|
|
81
83
|
|
|
82
84
|
# Send request
|
|
83
85
|
resp = self.nmbrs.session.put(
|