brynq-sdk-nmbrs 1.2.0__tar.gz → 1.3.1__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 (67) hide show
  1. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/PKG-INFO +1 -1
  2. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/brynq_sdk_nmbrs/__init__.py +58 -19
  3. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/address.py +66 -0
  4. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/bank.py +123 -0
  5. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/brynq_sdk_nmbrs/children.py +0 -1
  6. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/brynq_sdk_nmbrs/companies.py +3 -6
  7. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/contract.py +128 -0
  8. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/costcenter.py +234 -0
  9. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/costunit.py +112 -0
  10. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/brynq_sdk_nmbrs/debtors.py +2 -1
  11. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/department.py +141 -0
  12. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/brynq_sdk_nmbrs/document.py +3 -2
  13. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/employees.py +169 -0
  14. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/employment.py +129 -0
  15. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/function.py +138 -0
  16. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/hours.py +246 -0
  17. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/salaries.py +136 -0
  18. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/brynq_sdk_nmbrs/salary_tables.py +0 -11
  19. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/brynq_sdk_nmbrs/schedules.py +64 -9
  20. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/__init__.py +31 -0
  21. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/address.py +105 -0
  22. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/bank.py +82 -0
  23. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/contracts.py +58 -0
  24. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/costcenter.py +87 -0
  25. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/costunit.py +38 -0
  26. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/department.py +51 -0
  27. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/employees.py +138 -0
  28. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/employment.py +46 -0
  29. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/function.py +50 -0
  30. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/hours.py +114 -0
  31. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/salary.py +77 -0
  32. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/schedules.py +105 -0
  33. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/schemas/wagecomponents.py +110 -0
  34. brynq_sdk_nmbrs-1.3.1/brynq_sdk_nmbrs/wagecomponents.py +268 -0
  35. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/brynq_sdk_nmbrs.egg-info/PKG-INFO +1 -1
  36. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/brynq_sdk_nmbrs.egg-info/SOURCES.txt +1 -0
  37. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/brynq_sdk_nmbrs.egg-info/requires.txt +2 -0
  38. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/setup.py +3 -1
  39. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/address.py +0 -70
  40. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/bank.py +0 -95
  41. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/contract.py +0 -102
  42. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/costcenter.py +0 -120
  43. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/department.py +0 -86
  44. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/employees.py +0 -219
  45. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/employment.py +0 -92
  46. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/function.py +0 -84
  47. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/hours.py +0 -152
  48. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/salaries.py +0 -96
  49. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/__init__.py +0 -31
  50. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/address.py +0 -34
  51. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/bank.py +0 -41
  52. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/contracts.py +0 -29
  53. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/costcenter.py +0 -26
  54. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/costunit.py +0 -11
  55. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/department.py +0 -29
  56. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/employees.py +0 -38
  57. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/employment.py +0 -28
  58. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/function.py +0 -28
  59. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/hours.py +0 -88
  60. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/salary.py +0 -29
  61. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/schedules.py +0 -79
  62. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/schemas/wagecomponents.py +0 -53
  63. brynq_sdk_nmbrs-1.2.0/brynq_sdk_nmbrs/wagecomponents.py +0 -215
  64. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/brynq_sdk_nmbrs.egg-info/dependency_links.txt +0 -0
  65. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/brynq_sdk_nmbrs.egg-info/not-zip-safe +0 -0
  66. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/brynq_sdk_nmbrs.egg-info/top_level.txt +0 -0
  67. {brynq_sdk_nmbrs-1.2.0 → brynq_sdk_nmbrs-1.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.0
2
2
  Name: brynq_sdk_nmbrs
3
- Version: 1.2.0
3
+ Version: 1.3.1
4
4
  Summary: Nmbrs wrapper from BrynQ
5
5
  Home-page: UNKNOWN
6
6
  Author: BrynQ
@@ -4,43 +4,81 @@ import re
4
4
  import pandas as pd
5
5
  import requests
6
6
  from brynq_sdk_brynq import BrynQ
7
+ from .address import Address
8
+ from .bank import Bank
7
9
  from zeep import Client, Settings
8
10
  from .children import Children
9
11
  from .companies import Companies
10
12
  from .debtors import Debtors
13
+ from .contract import Contract
14
+ from .costcenter import EmployeeCostcenter, Costcenter
15
+ from .costunit import Costunit
16
+ from .department import EmployeeDepartment
17
+ from .document import Payslip
11
18
  from .employees import Employees
12
19
  from .salary_tables import SalaryTables, SalaryScales, SalarySteps
20
+ from .employment import Employment
21
+ from .function import EmployeeFunction
22
+ from .hours import VariableHours, FixedHours
23
+ from .salaries import Salaries
24
+ from .schedules import Schedule
25
+ from .wagecomponents import EmployeeFixedWageComponents, EmployeeVariableWageComponents
13
26
 
14
27
 
15
28
  class Nmbrs(BrynQ):
16
- def __init__(self, label: Union[str, List], debug: bool = False):
17
- super().__init__()
18
- self.label = label
19
- headers = self._get_request_headers(label=label)
20
- self.base_url = "https://api.nmbrsapp.com/api/"
21
- self.session = requests.Session()
22
- self.session.headers.update(headers)
23
- # self.session.headers.update({'Content-Type': 'application/json'})
24
- # Initialize SOAP client
25
- self.soap_settings = Settings(
26
- strict=False,
27
- xml_huge_tree=True,
28
- force_https=True
29
- )
30
- self.soap_client = Client(
31
- 'https://api.nmbrs.nl/soap/v3/CompanyService.asmx?wsdl',
32
- settings=self.soap_settings
33
- )
29
+ def __init__(self, label: Union[str, List], debug: bool = False, mock_mode: bool = True):
30
+ """
31
+ Initialize the Nmbrs class.
32
+
33
+ Args:
34
+ label: The label of the system in BrynQ. legacy
35
+ debug: Whether to print debug information
36
+ mock_mode: If true, data will NOT be send to Nmbrs but only be tested for validity against Pydantic schemas
37
+ """
38
+ self.mock_mode = mock_mode
39
+ if mock_mode is False:
40
+ super().__init__()
41
+ self.label = label
42
+ headers = self._get_request_headers(label=label)
43
+ self.base_url = "https://api.nmbrsapp.com/api/"
44
+ self.session = requests.Session()
45
+ self.session.headers.update(headers)
46
+
47
+ # Initialize SOAP client
48
+ self.soap_settings = Settings(
49
+ strict=False,
50
+ xml_huge_tree=True,
51
+ force_https=True
52
+ )
53
+ self.soap_client = Client(
54
+ 'https://api.nmbrs.nl/soap/v3/CompanyService.asmx?wsdl',
55
+ settings=self.soap_settings
56
+ )
57
+
58
+ self.address = Address(self)
59
+ self.bank = Bank(self)
60
+ self.children = Children(self)
34
61
  self.debtor = Debtors(self)
35
62
  self.companies = Companies(self)
63
+ self.contract = Contract(self)
64
+ self.department = EmployeeDepartment(self)
36
65
  self.company_ids = self.companies.get()['company_id']
37
66
  self.soap_company_ids = self.companies.get_soap_ids()
38
67
  self.employees = Employees(self)
68
+ self.employment = Employment(self)
69
+ self.function = EmployeeFunction(self)
70
+ self.fixed_hours = FixedHours(self)
71
+ self.variable_hours = VariableHours(self)
72
+ self.salaries = Salaries(self)
73
+ self.schedule = Schedule(self)
74
+ self.fixed_wagecomponents = EmployeeFixedWageComponents(self)
75
+ self.variable_wagecomponents = EmployeeVariableWageComponents(self)
39
76
  self.children = Children(self)
40
77
  self.salary_tables = SalaryTables(self)
41
78
  self.salary_scales = SalaryScales(self)
42
79
  self.salary_steps = SalarySteps(self)
43
80
  self.debug = debug
81
+ self.timeout = 3600
44
82
 
45
83
  def _get_request_headers(self, label):
46
84
  initial_credentials = self.get_system_credential(system='nmbrs', label=label)
@@ -82,7 +120,7 @@ class Nmbrs(BrynQ):
82
120
  while has_next_page:
83
121
  prepped = request.prepare()
84
122
  prepped.headers.update(self.session.headers)
85
- resp = self.session.send(prepped)
123
+ resp = self.session.send(prepped, timeout=self.nmbrs.timeout)
86
124
  resp.raise_for_status()
87
125
  response_data = resp.json()
88
126
  result_data += response_data['data']
@@ -119,3 +157,4 @@ class Nmbrs(BrynQ):
119
157
  df.columns = map(camel_to_snake_case, df.columns)
120
158
 
121
159
  return df
160
+
@@ -0,0 +1,66 @@
1
+ import pandas as pd
2
+ import requests
3
+ from typing import Dict, Any
4
+ from .schemas.address import AddressCreate, AddressGet, Period
5
+ from brynq_sdk_functions import Functions
6
+
7
+ class Address:
8
+ def __init__(self, nmbrs):
9
+ self.nmbrs = nmbrs
10
+
11
+ def get(self,
12
+ created_from: str = None) -> pd.DataFrame:
13
+ addresses = pd.DataFrame()
14
+ for company in self.nmbrs.company_ids:
15
+ addresses = pd.concat([addresses, self._get(company, created_from)])
16
+
17
+ valid_addresses, invalid_addresses = Functions.validate_data(df=addresses, schema=AddressGet, debug=True)
18
+
19
+ return valid_addresses, invalid_addresses
20
+
21
+ def _get(self,
22
+ company_id: str,
23
+ created_from: str = None) -> pd.DataFrame:
24
+ params = {} if created_from is None else {'createdFrom': created_from}
25
+ request = requests.Request(method='GET',
26
+ url=f"{self.nmbrs.base_url}companies/{company_id}/employees/addresses",
27
+ params=params)
28
+
29
+ data = self.nmbrs.get_paginated_result(request)
30
+ df = pd.json_normalize(
31
+ data,
32
+ record_path='addresses',
33
+ meta=['employeeId']
34
+ )
35
+
36
+ return df
37
+
38
+ def create(self, employee_id: str, data: Dict[str, Any]):
39
+ """
40
+ Create a new address for an employee using Pydantic validation.
41
+
42
+ Args:
43
+ employee_id: The ID of the employee
44
+ data: Dictionary containing address data with fields matching
45
+ the AddressCreate schema (using camelCase field names)
46
+
47
+ Returns:
48
+ Response from the API
49
+ """
50
+ # Validate with Pydantic model - this will raise an error if required fields are missing
51
+ address_model = AddressCreate(**data)
52
+
53
+ if self.nmbrs.mock_mode:
54
+ return address_model
55
+
56
+ # Convert validated model to dict for API payload
57
+ payload = address_model.dict(exclude_none=True)
58
+
59
+ # Send request
60
+ resp = self.nmbrs.session.post(
61
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/address",
62
+ json=payload,
63
+ timeout=self.nmbrs.timeout
64
+ )
65
+ return resp
66
+
@@ -0,0 +1,123 @@
1
+ import math
2
+ import pandas as pd
3
+ import requests
4
+ from brynq_sdk_functions import Functions
5
+ from typing import Dict, Any
6
+ from .schemas.bank import BankGet, BankCreate, BankUpdate, BankDelete
7
+
8
+
9
+ class Bank:
10
+ def __init__(self, nmbrs):
11
+ self.nmbrs = nmbrs
12
+
13
+ def get(self,
14
+ created_from: str = None) -> tuple[pd.DataFrame, pd.DataFrame]:
15
+ banks = pd.DataFrame()
16
+ for company in self.nmbrs.company_ids:
17
+ banks = pd.concat([banks, self._get(company, created_from)])
18
+
19
+ valid_banks, invalid_banks = Functions.validate_data(df=banks, schema=BankGet, debug=True)
20
+
21
+ return valid_banks, invalid_banks
22
+
23
+ def _get(self,
24
+ company_id: str,
25
+ created_from: str = None) -> pd.DataFrame:
26
+ params = {}
27
+ if created_from:
28
+ params['createdFrom'] = created_from
29
+ try:
30
+ request = requests.Request(method='GET',
31
+ url=f"{self.nmbrs.base_url}companies/{company_id}/employees/bankaccounts",
32
+ params=params)
33
+
34
+ data = self.nmbrs.get_paginated_result(request)
35
+ df = pd.json_normalize(
36
+ data,
37
+ record_path='bankAccounts',
38
+ meta=['employeeId']
39
+ )
40
+ except requests.HTTPError as e:
41
+ df = pd.DataFrame()
42
+
43
+ return df
44
+
45
+ def create(self, employee_id: str, data: Dict[str, Any]):
46
+ """
47
+ Create a new bank account for an employee using Pydantic validation.
48
+
49
+ Args:
50
+ employee_id: The ID of the employee
51
+ data: Dictionary containing bank account data in the format matching BankCreate schema
52
+
53
+ Returns:
54
+ Response from the API
55
+ """
56
+ # Validate with Pydantic model - this will raise an error if required fields are missing
57
+ bank_model = BankCreate(**data)
58
+
59
+ if self.nmbrs.mock_mode:
60
+ return bank_model
61
+
62
+ # Convert validated model to dict for API payload
63
+ payload = bank_model.dict(exclude_none=True)
64
+
65
+ # Send request
66
+ resp = self.nmbrs.session.post(
67
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/bankaccount",
68
+ json=payload,
69
+ timeout=self.nmbrs.timeout
70
+ )
71
+ return resp
72
+
73
+ def update(self, employee_id: str, data: Dict[str, Any]):
74
+ """
75
+ Update a bank account for an employee using Pydantic validation.
76
+
77
+ Args:
78
+ employee_id: The ID of the employee
79
+ data: Dictionary containing bank account data in the format matching BankUpdate schema
80
+
81
+ Returns:
82
+ Response from the API
83
+ """
84
+ # Validate with Pydantic model - this will raise an error if required fields are missing
85
+ bank_model = BankUpdate(**data)
86
+
87
+ if self.nmbrs.mock_mode:
88
+ return bank_model
89
+
90
+ # Convert validated model to dict for API payload
91
+ payload = bank_model.dict(exclude_none=True)
92
+
93
+ # Send request
94
+ resp = self.nmbrs.session.put(
95
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/bankaccount",
96
+ json=payload,
97
+ timeout=self.nmbrs.timeout
98
+ )
99
+ return resp
100
+
101
+ def delete(self, employee_id: str, bank_account_id: str):
102
+ """
103
+ Delete a bank account for an employee.
104
+
105
+ Args:
106
+ employee_id: The ID of the employee
107
+ bank_account_id: The ID of the bank account to delete
108
+
109
+ Returns:
110
+ Response from the API
111
+ """
112
+ # Create and validate a BankDelete model
113
+ bank_model = BankDelete(bankAccountId=bank_account_id)
114
+
115
+ if self.nmbrs.mock_mode:
116
+ return bank_model
117
+
118
+ # Send request
119
+ resp = self.nmbrs.session.delete(
120
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/bankaccounts/{bank_account_id}",
121
+ timeout=self.nmbrs.timeout
122
+ )
123
+ return resp
@@ -37,7 +37,6 @@ class Children:
37
37
  record_path='functions',
38
38
  meta=['employeeId']
39
39
  )
40
- df = self.nmbrs._rename_camel_columns_to_snake_case(df)
41
40
  except requests.HTTPError as e:
42
41
  df = pd.DataFrame()
43
42
 
@@ -1,7 +1,8 @@
1
1
  import pandas as pd
2
2
  import requests
3
3
  import logging
4
- from .costcenter import Costcenter, Costunit
4
+ from .costcenter import Costcenter
5
+ from .costunit import Costunit
5
6
  from .hours import Hours
6
7
  from .bank import Bank
7
8
  from .function import Functions
@@ -23,7 +24,7 @@ class Companies:
23
24
  request = requests.Request(method='GET',
24
25
  url=f"{self.nmbrs.base_url}companies")
25
26
  data = self.nmbrs.get_paginated_result(request)
26
- df = self.nmbrs._rename_camel_columns_to_snake_case(pd.DataFrame(data))
27
+ df = pd.DataFrame(data)
27
28
 
28
29
  return df
29
30
 
@@ -51,10 +52,6 @@ class Companies:
51
52
  # Convert to DataFrame
52
53
  df = pd.DataFrame(serialized_response)
53
54
 
54
- # Rename columns to snake_case
55
- if not df.empty:
56
- df = self.nmbrs._rename_camel_columns_to_snake_case(df)
57
-
58
55
  return df
59
56
  else:
60
57
  return pd.DataFrame()
@@ -0,0 +1,128 @@
1
+ import math
2
+ import pandas as pd
3
+ import requests
4
+ from typing import Dict, Any
5
+ from .schemas.contracts import ContractGet, ContractCreate, ContractUpdate, ContractDelete
6
+ from brynq_sdk_functions import Functions
7
+
8
+
9
+ class Contract:
10
+ def __init__(self, nmbrs):
11
+ self.nmbrs = nmbrs
12
+
13
+ def get(self,
14
+ created_from: str = None,
15
+ employee_id: str = None) -> (pd.DataFrame, pd.DataFrame):
16
+ contracts = pd.DataFrame()
17
+ for company in self.nmbrs.company_ids:
18
+ contracts = pd.concat([contracts, self._get(company, created_from, employee_id)])
19
+
20
+ valid_contracts, invalid_contracts = Functions.validate_data(df=contracts, schema=ContractGet, debug=True)
21
+
22
+ return valid_contracts, invalid_contracts
23
+
24
+ def _get(self,
25
+ company_id: str,
26
+ created_from: str = None,
27
+ employee_id: str = None) -> pd.DataFrame:
28
+ params = {}
29
+ if created_from:
30
+ params['createdFrom'] = created_from
31
+ if employee_id:
32
+ params['employeeId'] = employee_id
33
+ request = requests.Request(method='GET',
34
+ url=f"{self.nmbrs.base_url}companies/{company_id}/employees/contracts",
35
+ params=params)
36
+
37
+ data = self.nmbrs.get_paginated_result(request)
38
+ df = pd.json_normalize(
39
+ data,
40
+ record_path='contracts',
41
+ meta=['employeeId']
42
+ )
43
+
44
+ df['company_id'] = company_id
45
+
46
+ return df
47
+
48
+ def create(self, employee_id: str, data: Dict[str, Any]):
49
+ """
50
+ Create a new contract for an employee using Pydantic validation.
51
+
52
+ Args:
53
+ employee_id: The ID of the employee
54
+ data: Dictionary containing contract data with fields matching
55
+ the ContractCreate schema (using camelCase field names)
56
+
57
+ Returns:
58
+ Response from the API
59
+ """
60
+ # Validate with Pydantic model - this will raise an error if required fields are missing
61
+ contract_model = ContractCreate(**data)
62
+
63
+ if self.nmbrs.mock_mode:
64
+ return contract_model
65
+
66
+ # Convert validated model to dict for API payload
67
+ payload = contract_model.dict(exclude_none=True)
68
+
69
+ # Send request
70
+ resp = self.nmbrs.session.post(
71
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/contract",
72
+ json=payload,
73
+ timeout=self.nmbrs.timeout
74
+ )
75
+ return resp
76
+
77
+ def update(self, employee_id: str, data: Dict[str, Any]):
78
+ """
79
+ Update a contract for an employee using Pydantic validation.
80
+
81
+ Args:
82
+ employee_id: The ID of the employee
83
+ data: Dictionary containing contract data with fields matching
84
+ the ContractUpdate schema (using camelCase field names)
85
+
86
+ Returns:
87
+ Response from the API
88
+ """
89
+ # Validate with Pydantic model - this will raise an error if required fields are missing
90
+ contract_model = ContractUpdate(**data)
91
+
92
+ if self.nmbrs.mock_mode:
93
+ return contract_model
94
+
95
+ # Convert validated model to dict for API payload
96
+ payload = contract_model.dict(exclude_none=True)
97
+
98
+ # Send request
99
+ resp = self.nmbrs.session.put(
100
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/contract",
101
+ json=payload,
102
+ timeout=self.nmbrs.timeout
103
+ )
104
+ return resp
105
+
106
+ def delete(self, employee_id: str, contract_id: str):
107
+ """
108
+ Delete a contract for an employee.
109
+
110
+ Args:
111
+ employee_id: The ID of the employee
112
+ contract_id: The ID of the contract to delete
113
+
114
+ Returns:
115
+ Response from the API
116
+ """
117
+ # Create and validate a ContractDelete model
118
+ contract_model = ContractDelete(contractId=contract_id)
119
+
120
+ if self.nmbrs.mock_mode:
121
+ return contract_model
122
+
123
+ # Send request
124
+ resp = self.nmbrs.session.delete(
125
+ url=f"{self.nmbrs.base_url}employees/{employee_id}/contracts/{contract_id}",
126
+ timeout=self.nmbrs.timeout
127
+ )
128
+ return resp