brynq-sdk-acerta 1.0.0__tar.gz → 1.1.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 (44) hide show
  1. brynq_sdk_acerta-1.1.1/PKG-INFO +21 -0
  2. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/__init__.py +2 -1
  3. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/acerta.py +2 -0
  4. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/agreements.py +16 -88
  5. brynq_sdk_acerta-1.1.1/brynq_sdk_acerta/bank_accounts.py +90 -0
  6. brynq_sdk_acerta-1.1.1/brynq_sdk_acerta/company_cars.py +135 -0
  7. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/family_members.py +2 -1
  8. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/schemas/__init__.py +6 -2
  9. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/schemas/address.py +3 -0
  10. brynq_sdk_acerta-1.1.1/brynq_sdk_acerta/schemas/agreement.py +982 -0
  11. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/schemas/bank_account.py +22 -19
  12. brynq_sdk_acerta-1.1.1/brynq_sdk_acerta/schemas/company_car.py +124 -0
  13. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/schemas/contact_information.py +3 -0
  14. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/schemas/cost_center.py +3 -0
  15. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/schemas/employee.py +6 -0
  16. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/schemas/family.py +6 -0
  17. brynq_sdk_acerta-1.1.1/brynq_sdk_acerta.egg-info/PKG-INFO +21 -0
  18. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta.egg-info/SOURCES.txt +2 -0
  19. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/setup.py +1 -1
  20. brynq_sdk_acerta-1.0.0/PKG-INFO +0 -10
  21. brynq_sdk_acerta-1.0.0/brynq_sdk_acerta/bank_accounts.py +0 -84
  22. brynq_sdk_acerta-1.0.0/brynq_sdk_acerta/schemas/agreement.py +0 -627
  23. brynq_sdk_acerta-1.0.0/brynq_sdk_acerta.egg-info/PKG-INFO +0 -10
  24. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/README.md +0 -0
  25. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/addresses.py +0 -0
  26. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/code_lists.py +0 -0
  27. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/contact_information.py +0 -0
  28. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/cost_centers.py +0 -0
  29. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/employees.py +0 -0
  30. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/employees_additional_information.py +0 -0
  31. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/employer.py +0 -0
  32. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/family_situation.py +0 -0
  33. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/inservice.py +0 -0
  34. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/salaries.py +0 -0
  35. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/schemas/employer.py +0 -0
  36. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/schemas/in_service.py +0 -0
  37. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/schemas/in_service_config.py +0 -0
  38. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/schemas/planning.py +0 -0
  39. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta/schemas/salaries.py +0 -0
  40. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta.egg-info/dependency_links.txt +0 -0
  41. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta.egg-info/not-zip-safe +0 -0
  42. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta.egg-info/requires.txt +0 -0
  43. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/brynq_sdk_acerta.egg-info/top_level.txt +0 -0
  44. {brynq_sdk_acerta-1.0.0 → brynq_sdk_acerta-1.1.1}/setup.cfg +0 -0
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: brynq_sdk_acerta
3
+ Version: 1.1.1
4
+ Summary: Acerta wrapper from BrynQ
5
+ Author: BrynQ
6
+ Author-email: support@brynq.com
7
+ License: BrynQ License
8
+ Requires-Dist: pandas<3.0.0,>=2.2.0
9
+ Requires-Dist: pydantic<3.0.0,>=2.5.0
10
+ Requires-Dist: pandera<1.0.0,>=0.16.0
11
+ Requires-Dist: requests<3.0.0,>=2.25.1
12
+ Requires-Dist: brynq-sdk-functions>=2.0.5
13
+ Requires-Dist: brynq-sdk-brynq>=3
14
+ Dynamic: author
15
+ Dynamic: author-email
16
+ Dynamic: description
17
+ Dynamic: license
18
+ Dynamic: requires-dist
19
+ Dynamic: summary
20
+
21
+ Acerta wrapper from BrynQ
@@ -9,5 +9,6 @@ from .code_lists import CodeLists
9
9
  from .employer import Employer
10
10
  from .employees_additional_information import EmployeesAdditionalInformation
11
11
  from .inservice import InService
12
+ from .company_cars import CompanyCars
12
13
 
13
- __all__ = ['Acerta', 'CodeLists', 'Agreements', 'Employees', 'Employer', 'InService', 'EmployeesAdditionalInformation']
14
+ __all__ = ['Acerta', 'CodeLists', 'Agreements', 'Employees', 'Employer', 'InService', 'EmployeesAdditionalInformation', 'CompanyCars']
@@ -8,6 +8,7 @@ from brynq_sdk_brynq import BrynQ
8
8
  from .code_lists import CodeLists
9
9
  from .agreements import Agreements
10
10
  from .inservice import InService
11
+ from .company_cars import CompanyCars
11
12
  from .employees import Employees
12
13
  from .employer import Employer
13
14
  from requests_oauthlib import OAuth2Session
@@ -96,6 +97,7 @@ class Acerta(BrynQ):
96
97
  self.employees = Employees(self)
97
98
  self.employers = Employer(self)
98
99
  self.salaries = Salaries(self)
100
+ self.company_cars = CompanyCars(self)
99
101
 
100
102
  def _ensure_valid_token(self):
101
103
  """Ensure the OAuth token exists and is not about to expire."""
@@ -1,7 +1,16 @@
1
1
  from typing import Dict, Any, Optional, Tuple
2
2
  import requests
3
3
  import pandas as pd
4
- from .schemas.agreement import AgreementGet, AgreementRemunerationBankAccountCreate, PatchAgreementRequest
4
+ from .schemas.agreement import (
5
+ AgreementGet,
6
+ PatchAgreementRequest,
7
+ AgreementBasicInformationGet,
8
+ AgreementEmploymentsGet,
9
+ AgreementWorkingTimeGet,
10
+ AgreementCommutingGet,
11
+ AgreementCustomFieldsGet,
12
+ AgreementCostCenterAllocationGet,
13
+ )
5
14
  from .schemas.in_service_config import JointCommitteeGet, FunctionGet
6
15
  from brynq_sdk_functions import Functions
7
16
  from typing import TYPE_CHECKING
@@ -67,46 +76,6 @@ class Agreements:
67
76
  combined = pd.concat(all_rows, ignore_index=True) if all_rows else pd.DataFrame()
68
77
  return Functions.validate_data(combined, FunctionGet)
69
78
 
70
- def create_bank_account(self, agreement_id: str, data: Dict[str, Any], if_match: str, accept_language: str = "en") -> requests.Response:
71
- """
72
- POST /v1/agreements/{agreementId}/remuneration-bank-account - Agreement Remuneration Bank Account
73
-
74
- Create or update the remuneration bank account for an agreement. Requires the ETag
75
- of the resource for optimistic concurrency control.
76
-
77
- Args:
78
- agreement_id: Unique identifier of an agreement
79
- data: Dictionary with bank account data (iban, bic)
80
- if_match: The ETag of this resource (required for concurrency control)
81
- accept_language: The language for the request (defaults to "en")
82
-
83
- Returns:
84
- requests.Response: Raw response object
85
-
86
- Raises:
87
- Exception: If the bank account creation fails
88
- """
89
- # Validate request data
90
- validated_data = AgreementRemunerationBankAccountCreate(**data)
91
-
92
- # Prepare headers
93
- headers = {
94
- "If-Match": if_match,
95
- "Accept-Language": accept_language
96
- }
97
-
98
- # Make API request
99
- request_headers = self.acerta.session.headers.copy()
100
- request_headers.update(headers)
101
- response = self.acerta.session.post(
102
- url=f"{self.acerta.base_url}/v1/agreements/{agreement_id}/remuneration-bank-account",
103
- json=validated_data.model_dump(by_alias=True, exclude_none=True),
104
- headers=request_headers,
105
- timeout=self.acerta.TIMEOUT,
106
- )
107
- response.raise_for_status()
108
-
109
- return response
110
79
 
111
80
  def get(self) -> Tuple[pd.DataFrame, pd.DataFrame]:
112
81
  """
@@ -245,7 +214,7 @@ class Agreements:
245
214
  )
246
215
  frames.append(df)
247
216
  combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
248
- return combined, pd.DataFrame()
217
+ return Functions.validate_data(combined, AgreementBasicInformationGet, debug=self.acerta.debug)
249
218
 
250
219
  def get_employments(
251
220
  self,
@@ -285,7 +254,7 @@ class Agreements:
285
254
  )
286
255
  frames.append(df)
287
256
  combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
288
- return combined, pd.DataFrame()
257
+ return Functions.validate_data(combined, AgreementEmploymentsGet, debug=self.acerta.debug)
289
258
 
290
259
  def get_working_time(
291
260
  self,
@@ -327,49 +296,8 @@ class Agreements:
327
296
  )
328
297
  frames.append(df)
329
298
  combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
330
- return combined, pd.DataFrame()
299
+ return Functions.validate_data(combined, AgreementWorkingTimeGet, debug=self.acerta.debug)
331
300
 
332
- def get_remuneration(
333
- self,
334
- agreement_id: Optional[str] = None,
335
- from_date: Optional[str] = None,
336
- until_date: Optional[str] = None,
337
- full_segments_only: Optional[bool] = None,
338
- accept_language: str = "en",
339
- ) -> Tuple[pd.DataFrame, pd.DataFrame]:
340
- """GET /v3/agreements/{agreementId}/remuneration"""
341
- params = {k: v for k, v in {
342
- "fromDate": from_date,
343
- "untilDate": until_date,
344
- "fullSegmentsOnly": str(full_segments_only).lower() if isinstance(full_segments_only, bool) else full_segments_only,
345
- }.items() if v is not None}
346
- if not agreement_id and not self.acerta._agreement_ids:
347
- self.get()
348
- ids = [agreement_id] if agreement_id else self.acerta._agreement_ids
349
- frames = []
350
- headers = {"Accept-Language": accept_language} if accept_language else None
351
- for agr_id in ids:
352
- request_headers = self.acerta.session.headers.copy()
353
- if headers:
354
- request_headers.update(headers)
355
- response = self.acerta.session.get(
356
- url=f"{self.acerta.base_url}/{self.base_uri}/{agr_id}/remuneration",
357
- params=params,
358
- headers=request_headers,
359
- timeout=self.acerta.TIMEOUT,
360
- )
361
- response.raise_for_status()
362
- raw = response.json()
363
- df = pd.json_normalize(
364
- raw,
365
- record_path=["remunerationSegments"],
366
- meta=["agreementId"],
367
- errors="ignore",
368
- sep=".",
369
- )
370
- frames.append(df)
371
- combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
372
- return combined, pd.DataFrame()
373
301
 
374
302
  def get_commuting(
375
303
  self,
@@ -411,7 +339,7 @@ class Agreements:
411
339
  )
412
340
  frames.append(df)
413
341
  combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
414
- return combined, pd.DataFrame()
342
+ return Functions.validate_data(combined, AgreementCommutingGet, debug=self.acerta.debug)
415
343
 
416
344
  def get_custom_fields(
417
345
  self,
@@ -455,7 +383,7 @@ class Agreements:
455
383
  )
456
384
  frames.append(df)
457
385
  combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
458
- return combined, pd.DataFrame()
386
+ return Functions.validate_data(combined, AgreementCustomFieldsGet, debug=self.acerta.debug)
459
387
 
460
388
  def get_cost_center_allocation(
461
389
  self,
@@ -495,4 +423,4 @@ class Agreements:
495
423
  )
496
424
  frames.append(df)
497
425
  combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
498
- return combined, pd.DataFrame()
426
+ return Functions.validate_data(combined, AgreementCostCenterAllocationGet, debug=self.acerta.debug)
@@ -0,0 +1,90 @@
1
+ from typing import Dict, Any, Optional, Tuple
2
+ import requests
3
+ import pandas as pd
4
+ from brynq_sdk_functions import Functions
5
+ from .schemas.agreement import AgreementRemunerationBankAccountUpdate
6
+ from .schemas.bank_account import AgreementBankAccountsGet
7
+ from typing import TYPE_CHECKING
8
+ if TYPE_CHECKING:
9
+ from .acerta import Acerta
10
+
11
+ class BankAccounts:
12
+ """Resource class for Employee Bank Accounts (HAL links)."""
13
+
14
+ def __init__(self, acerta):
15
+ self.acerta: Acerta = acerta
16
+ self.base_uri = "v1"
17
+
18
+ def get(self, agreement_id: Optional[str] = None, from_date: Optional[str] = None, until_date: Optional[str] = None, full_segments_only: Optional[bool] = None, accept_language: str = "en") -> Tuple[pd.DataFrame, pd.DataFrame]:
19
+ """
20
+ GET /v3/agreements/{agreementId}/remuneration - derive bank accounts
21
+
22
+ Fetches remuneration segments for agreements and returns only the bank account related fields
23
+ (via methodOfPayment.employeeBankAccounts/employerBankAccounts).
24
+ """
25
+ # Ensure we have agreement ids
26
+ if not agreement_id and not self.acerta._agreement_ids:
27
+ self.acerta.agreements.get()
28
+ agreement_ids = [agreement_id] if agreement_id else self.acerta._agreement_ids
29
+
30
+ params = {k: v for k, v in {
31
+ "fromDate": from_date,
32
+ "untilDate": until_date,
33
+ "fullSegmentsOnly": str(full_segments_only).lower() if isinstance(full_segments_only, bool) else full_segments_only,
34
+ }.items() if v is not None}
35
+
36
+ frames = []
37
+ headers = {"Accept-Language": accept_language} if accept_language else None
38
+ for agr_id in agreement_ids:
39
+ request_headers = self.acerta.session.headers.copy()
40
+ if headers:
41
+ request_headers.update(headers)
42
+ response = self.acerta.session.get(
43
+ url=f"{self.acerta.base_url}/agreement-data-management/v3/agreements/{agr_id}/remuneration",
44
+ params=params,
45
+ headers=request_headers,
46
+ timeout=self.acerta.TIMEOUT,
47
+ )
48
+ response.raise_for_status()
49
+ raw = response.json()
50
+ df = pd.json_normalize(
51
+ raw,
52
+ record_path=["remunerationSegments"],
53
+ meta=["agreementId"],
54
+ errors="ignore",
55
+ sep=".",
56
+ )
57
+ frames.append(df)
58
+
59
+ combined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
60
+ return Functions.validate_data(combined, AgreementBankAccountsGet, debug=self.acerta.debug)
61
+
62
+ def update(self, agreement_id: str, data: Dict[str, Any]) -> requests.Response:
63
+ """
64
+ PUT /employee-data-management/v3/employees/agreement/{agreementId}/remuneration-bank-account - Agreement Remuneration Bank Account
65
+
66
+ Set the bank account for the employee and agreement. This endpoint updates the remuneration
67
+ bank account associated with a specific agreement.
68
+
69
+ Args:
70
+ agreement_id: Unique identifier of an agreement
71
+ data: Dictionary with bank account data (iban, bic)
72
+
73
+ Returns:
74
+ requests.Response: Raw response object
75
+
76
+ Raises:
77
+ Exception: If the update fails
78
+ """
79
+ # Validate the data (no nesting required for this simple schema)
80
+ validated_data = AgreementRemunerationBankAccountUpdate(**data)
81
+
82
+ # Make API request
83
+ response = self.acerta.session.put(
84
+ url=f"{self.acerta.base_url}/employee-data-management/v3/employees/agreement/{agreement_id}/remuneration-bank-account",
85
+ json=validated_data.model_dump(by_alias=True, exclude_none=True),
86
+ timeout=self.acerta.TIMEOUT,
87
+ )
88
+ response.raise_for_status()
89
+
90
+ return response
@@ -0,0 +1,135 @@
1
+ from typing import Tuple, Dict, Any, Optional
2
+ import pandas as pd
3
+ import requests
4
+ from brynq_sdk_functions import Functions
5
+ from .schemas.company_car import CompanyCarGet, CompanyCarCreate, CompanyCarUpdate
6
+ from typing import TYPE_CHECKING
7
+ if TYPE_CHECKING:
8
+ from .acerta import Acerta
9
+
10
+
11
+ class CompanyCars:
12
+ """Resource class for Company Cars endpoints"""
13
+
14
+ def __init__(self, acerta):
15
+ self.acerta: Acerta = acerta
16
+ self.base_uri = "employer-data-management/v1/employers"
17
+
18
+ def get(
19
+ self,
20
+ from_date: str = "1900-01-01",
21
+ until_date: str = "9999-12-31",
22
+ license_plate: Optional[str] = None,
23
+ size: int = 20,
24
+ page: int = 0,
25
+ ) -> Tuple[pd.DataFrame, pd.DataFrame]:
26
+ """
27
+ GET /v1/employers/{employerId}/company-cars - Company cars
28
+
29
+ Retrieve company cars for all configured employer IDs within an optional time window
30
+ and optional license plate filter. Supports basic pagination parameters.
31
+
32
+ Args:
33
+ from_date: The lower bound of the time window (default: "1900-01-01")
34
+ until_date: The upper bound of the time window (default: "9999-12-31")
35
+ license_plate: Optional license plate filter (or part of a plate)
36
+ size: Number of items per page (default: 20)
37
+ page: Zero-based page index (default: 0)
38
+
39
+ Returns:
40
+ Tuple[pd.DataFrame, pd.DataFrame]: (valid_df, invalid_df) after normalizing and validating
41
+ """
42
+ all_valid_data = []
43
+ all_invalid_data = []
44
+ for employer_id in self.acerta._employer_ids:
45
+ params = {
46
+ "fromDate": from_date,
47
+ "untilDate": until_date,
48
+ "size": size,
49
+ "page": page,
50
+ }
51
+ if license_plate:
52
+ params["licensePlate"] = license_plate
53
+ response = self.acerta.session.get(
54
+ url=f"{self.acerta.base_url}/{self.base_uri}/{employer_id}/company-cars",
55
+ params=params,
56
+ timeout=self.acerta.TIMEOUT,
57
+ )
58
+ response.raise_for_status()
59
+ items = response.json().get("companyCars", [])
60
+ df = pd.json_normalize(items, sep='.')
61
+ valid_data, invalid_data = Functions.validate_data(df, CompanyCarGet)
62
+ all_valid_data.append(valid_data)
63
+ all_invalid_data.append(invalid_data)
64
+
65
+ return (
66
+ pd.concat(all_valid_data, ignore_index=True) if all_valid_data else pd.DataFrame(),
67
+ pd.concat(all_invalid_data, ignore_index=True) if all_invalid_data else pd.DataFrame(),
68
+ )
69
+
70
+ def get_by_id(self, employer_id: str, company_car_id: str) -> Tuple[pd.DataFrame, pd.DataFrame]:
71
+ """
72
+ GET /v1/employers/{employerId}/company-cars/{companyCarId} - Company car by ID
73
+
74
+ Args:
75
+ employer_id: Unique identifier of the employer (Acerta key)
76
+ company_car_id: Unique identifier of the company car (6 digits)
77
+
78
+ Returns:
79
+ Tuple[pd.DataFrame, pd.DataFrame]: (valid_df, invalid_df)
80
+ """
81
+ response = self.acerta.session.get(
82
+ url=f"{self.acerta.base_url}/{self.base_uri}/{employer_id}/company-cars/{company_car_id}",
83
+ timeout=self.acerta.TIMEOUT,
84
+ )
85
+ response.raise_for_status()
86
+ raw = response.json()
87
+ if isinstance(raw, dict):
88
+ df = pd.json_normalize(raw, sep='.')
89
+ else:
90
+ df = pd.json_normalize([raw], sep='.')
91
+ return Functions.validate_data(df, CompanyCarGet)
92
+
93
+ def create(self, employer_id: str, data: Dict[str, Any]) -> requests.Response:
94
+ """
95
+ POST /v1/employers/{employerId}/company-cars - Company car
96
+
97
+ Args:
98
+ employer_id: Unique identifier of the employer (Acerta key)
99
+ data: Flat dictionary with company car data
100
+
101
+ Returns:
102
+ requests.Response: Raw response object
103
+ """
104
+ # Convert flat data to nested using Functions.flat_to_nested_with_prefix
105
+ nested = Functions.flat_to_nested_with_prefix(data, CompanyCarCreate)
106
+ validated = CompanyCarCreate(**nested)
107
+ response = self.acerta.session.post(
108
+ url=f"{self.acerta.base_url}/{self.base_uri}/{employer_id}/company-cars",
109
+ json=validated.model_dump(by_alias=True, exclude_none=True),
110
+ timeout=self.acerta.TIMEOUT,
111
+ )
112
+ response.raise_for_status()
113
+ return response
114
+
115
+ def update(self, employer_id: str, company_car_id: str, data: Dict[str, Any]) -> requests.Response:
116
+ """
117
+ PATCH /v1/employers/{employerId}/company-cars/{companyCarId} - Update company car
118
+
119
+ Args:
120
+ employer_id: Unique identifier of the employer (Acerta key)
121
+ company_car_id: Unique identifier of the company car
122
+ data: Flat dictionary with update fields
123
+
124
+ Returns:
125
+ requests.Response: Raw response object
126
+ """
127
+ nested = Functions.flat_to_nested_with_prefix(data, CompanyCarUpdate)
128
+ validated = CompanyCarUpdate(**nested)
129
+ response = self.acerta.session.patch(
130
+ url=f"{self.acerta.base_url}/{self.base_uri}/{employer_id}/company-cars/{company_car_id}",
131
+ json=validated.model_dump(by_alias=True, exclude_none=True),
132
+ timeout=self.acerta.TIMEOUT,
133
+ )
134
+ response.raise_for_status()
135
+ return response
@@ -9,10 +9,11 @@ if TYPE_CHECKING:
9
9
 
10
10
  class FamilyMembers:
11
11
  """Resource class for Family endpoints"""
12
+ # According to Acerta this is hardly ever used, is not relevant for payroll.
12
13
 
13
14
  def __init__(self, acerta):
14
15
  self.acerta: Acerta = acerta
15
- self.base_uri = "employee-data-management/v1/employees"
16
+ self.base_uri = "employee-data-management/v2/employees"
16
17
 
17
18
  def get(self, employee_id: str, from_date: str = "1900-01-01", until_date: str = "9999-12-31",
18
19
  full_segments_only: bool = False) -> Tuple[pd.DataFrame, pd.DataFrame]:
@@ -19,7 +19,7 @@ from .family import (
19
19
  Dependants,
20
20
  FiscalDetails
21
21
  )
22
- from .bank_account import BankAccountLinkGet, BankAccountUpdate, BankOwner, OwnerName, BankDetails
22
+ from .bank_account import BankAccountUpdate, BankOwner, OwnerName, BankDetails
23
23
  from .employee import (
24
24
  AdditionalInformationGet,
25
25
  AdditionalInformationUpdate,
@@ -55,6 +55,7 @@ from .employer import JointCommitteeGet, FunctionGet, SalaryCodeGet
55
55
  from .in_service import EmploymentCreate, EmploymentRehire
56
56
  from .cost_center import CostCenterGet, CostCenterCreate, CostCenterDescription, CostCenterPeriod
57
57
  from .salaries import BasicSalaryGet, PatchBasicSalariesRequest
58
+ from .company_car import CompanyCarGet, CompanyCarCreate, CompanyCarUpdate
58
59
 
59
60
  __all__ = [
60
61
  'AgreementGet',
@@ -127,5 +128,8 @@ __all__ = [
127
128
  'CostCenterDescription',
128
129
  'CostCenterPeriod',
129
130
  'BasicSalaryGet',
130
- 'PatchBasicSalariesRequest'
131
+ 'PatchBasicSalariesRequest',
132
+ 'CompanyCarGet',
133
+ 'CompanyCarCreate',
134
+ 'CompanyCarUpdate'
131
135
  ]
@@ -69,6 +69,9 @@ CorrespondenceAddressUpdate = Address
69
69
 
70
70
  class AddressUpdate(BaseModel):
71
71
  """Schema for PATCH /employee-data-management/v3/employees/{employeeId}/addresses endpoint"""
72
+ # Function parameters
73
+ employee_id: str = Field(..., description="Employee identifier", alias="employeeId")
74
+
72
75
  from_date: Optional[str] = Field(None, example="2022-01-01", description="Date from which this information is valid", alias="fromDate")
73
76
  official_address: Optional[Address] = Field(None, description="Official address of the employee", alias="officialAddress", json_schema_extra={"prefix": "official_address_"})
74
77
  correspondence_address: Optional[Address] = Field(None, description="Correspondence address of the employee", alias="correspondenceAddress", json_schema_extra={"prefix": "correspondence_address_"})