brynq-sdk-zenegy 1.3.7__py3-none-any.whl → 1.3.10__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.
@@ -1,21 +1,25 @@
1
1
  # Generated endpoint class for tag: Employees
2
+ from typing import Any, Dict, List, Tuple
3
+ from uuid import UUID
4
+
2
5
  import pandas as pd
3
6
  import requests
4
- from uuid import UUID
7
+
5
8
  from brynq_sdk_functions import Functions
6
9
 
7
- from .schemas.employees import (EmployeeCreate,
8
- EmployeeUpdate,
9
- EmployeesGet,
10
- EmployeesGetById,
11
- EmployeeEmploymentDataUpdate,
12
- EmployeeEmploymentUpdate,
13
- EmployeeAdditionalUpdate,
14
- StartSaldo,
15
- EmployeePatch)
16
10
  from .paychecks import PayChecks
17
11
  from .pensions import Pensions
18
- from typing import Dict, Any, Tuple, List
12
+ from .schemas.employees import (
13
+ EmployeeAdditionalUpdate,
14
+ EmployeeCreate,
15
+ EmployeeEmploymentDataUpdate,
16
+ EmployeeEmploymentUpdate,
17
+ EmployeePatch,
18
+ EmployeesGet,
19
+ EmployeesGetById,
20
+ EmployeeUpdate,
21
+ StartSaldo,
22
+ )
19
23
 
20
24
 
21
25
  class Employees:
@@ -282,7 +286,7 @@ class Employees:
282
286
  Single entry point for patching employees using a flat data dictionary.
283
287
  Flat keys may include prefixes for nested fields using a single underscore '_',
284
288
  e.g., 'start_saldo_start_g_days', 'language_name'. All generated operations
285
- are sent in ONE PATCH request as a JSON array per endpoint capability.
289
+ are grouped into one or more PATCH requests to avoid all-or-none failures.
286
290
 
287
291
  Args:
288
292
  employee_uid (UUID): The employee uid
@@ -296,41 +300,122 @@ class Employees:
296
300
  if not op:
297
301
  raise ValueError("Patch operation 'op' must be provided")
298
302
 
299
- operations = self.build_patch_operations(data=data, op=op)
303
+ operation_batches = self.build_patch_operation_batches(data=data, op=op)
304
+ if not operation_batches:
305
+ raise ValueError("No valid fields to patch")
306
+
300
307
  endpoint = f"{self.endpoint}/{employee_uid}".lstrip('/')
301
- response = self.zenegy.patch(endpoint=endpoint, json=operations)
302
- self.zenegy.raise_for_status_with_details(response)
303
- return response
308
+ last_response = None
309
+ for operations in operation_batches:
310
+ response = self.zenegy.patch(endpoint=endpoint, json=operations)
311
+ self.zenegy.raise_for_status_with_details(response)
312
+ last_response = response
313
+ return last_response
304
314
  except Exception as e:
305
315
  raise Exception(f"Failed to patch employee: {str(e)}")
306
316
 
307
317
 
308
- def build_patch_operations(self, data: Dict[str, Any], op: str = "replace") -> List[Dict[str, Any]]:
318
+ def build_patch_operation_batches(self, data: Dict[str, Any], op: str = "replace") -> List[List[Dict[str, Any]]]:
309
319
  """
310
- Build JSON Patch operations from a flat employee data dictionary using
311
- the EmployeePatch flat schema (aliases map directly to JSON Patch paths).
320
+ Build grouped JSON Patch operations from a flat employee data dictionary.
321
+ Grouping reduces validation failures when certain fields must be updated together.
312
322
 
313
323
  Args:
314
324
  data (Dict[str, Any]): Flat data with pythonic keys (e.g., name, email, start_g_days)
315
325
  op (str): JSON Patch op. Defaults to "replace".
316
326
 
317
327
  Returns:
318
- List[Dict[str, Any]]: JSON Patch operations
328
+ List[List[Dict[str, Any]]]: Batches of JSON Patch operations
319
329
  """
320
330
  if not isinstance(data, dict):
321
331
  raise ValueError("data must be a dictionary")
322
332
  if not op:
323
333
  raise ValueError("Patch operation 'op' must be provided")
324
334
 
325
- # Validate against flat schema and dump using alias names
326
- validated = EmployeePatch(**data)
327
- alias_dump: Dict[str, Any] = validated.model_dump(by_alias=True, mode='json', exclude_none=True, exclude_unset=True)
328
-
329
- operations: List[Dict[str, Any]] = []
330
- for alias_key, value in alias_dump.items():
331
- operations.append({
332
- "op": op,
333
- "path": f"/{alias_key}",
334
- "value": value,
335
- })
336
- return operations
335
+ def is_blank_value(value: Any) -> bool:
336
+ if value is None:
337
+ return True
338
+ if isinstance(value, str) and value.strip() == "":
339
+ return True
340
+ if isinstance(value, str) and value in ("nan", "NaN"):
341
+ return True
342
+ try:
343
+ if pd.isna(value):
344
+ return True
345
+ except Exception:
346
+ pass
347
+ return False
348
+
349
+ payloads: List[Dict[str, Any]] = []
350
+ processed_fields: set[str] = set()
351
+
352
+ # Address fields must be patched together and cannot be blank
353
+ address_fields = ["address", "postal_number", "city"]
354
+ if any(field in data for field in address_fields):
355
+ address_payload: Dict[str, Any] = {}
356
+ for field in address_fields:
357
+ if field in data:
358
+ value = data.get(field)
359
+ if not is_blank_value(value):
360
+ address_payload[field] = value
361
+ processed_fields.add(field)
362
+ if address_payload:
363
+ payloads.append(address_payload)
364
+
365
+ # reg_number and konto_number should be patched together
366
+ bank_fields = ["reg_number", "konto_number"]
367
+ if any(field in data for field in bank_fields):
368
+ bank_payload: Dict[str, Any] = {}
369
+ for field in bank_fields:
370
+ if field in data:
371
+ bank_payload[field] = data[field]
372
+ processed_fields.add(field)
373
+ if bank_payload:
374
+ payloads.append(bank_payload)
375
+
376
+ # swift should be patched together with iban if provided
377
+ if "swift" in data:
378
+ swift_payload: Dict[str, Any] = {"swift": data.get("swift")}
379
+ processed_fields.add("swift")
380
+ if "iban" in data:
381
+ swift_payload["iban"] = data.get("iban")
382
+ processed_fields.add("iban")
383
+ payloads.append(swift_payload)
384
+
385
+ # department_id and department_uid should be patched together when available
386
+ if "department_id" in data:
387
+ dept_payload: Dict[str, Any] = {"department_id": data.get("department_id")}
388
+ processed_fields.add("department_id")
389
+ if "department_uid" in data:
390
+ dept_payload["department_uid"] = data.get("department_uid")
391
+ processed_fields.add("department_uid")
392
+ payloads.append(dept_payload)
393
+
394
+ # Every other field is sent separately to avoid all-or-none failures
395
+ for field, value in data.items():
396
+ if field not in processed_fields:
397
+ payloads.append({field: value})
398
+ processed_fields.add(field)
399
+
400
+ operation_batches: List[List[Dict[str, Any]]] = []
401
+ for payload in payloads:
402
+ validated = EmployeePatch(**payload)
403
+ alias_dump: Dict[str, Any] = validated.model_dump(
404
+ by_alias=True,
405
+ mode='json',
406
+ exclude_none=True,
407
+ exclude_unset=True,
408
+ )
409
+ if not alias_dump:
410
+ continue
411
+ operations: List[Dict[str, Any]] = []
412
+ for alias_key, value in alias_dump.items():
413
+ operations.append({
414
+ "op": op,
415
+ "path": f"/{alias_key}",
416
+ "value": value,
417
+ })
418
+ if operations:
419
+ operation_batches.append(operations)
420
+
421
+ return operation_batches
@@ -27,7 +27,7 @@ class Payslips:
27
27
  Tuple of (valid_data, invalid_data) DataFrames
28
28
  """
29
29
  try:
30
- endpoint = f"api/companies/{self.zenegy.company_uid}/employees/{employee_uid}/payslips"
30
+ endpoint = f"api/companies/{self.zenegy.company_uid}/employees/{employee_uid}/payslips/report"
31
31
  content = self.zenegy.get(endpoint=endpoint)
32
32
 
33
33
  # Normalize the response (data field contains the list)
@@ -9,7 +9,10 @@ from brynq_sdk_zenegy.schemas.company_departments import CompanyDepartmentData
9
9
 
10
10
  class PaycheckCreate(BaseModel):
11
11
  """Schema for creating pay checks."""
12
+ # Function Parameters
13
+ employee_uid: UUID = Field(..., description="Employee identifier", alias="employeeUid")
12
14
  paycheck_for: Optional[int] = Field(alias="payCheckFor", default=None, description="Target paycheck type/code", example=1)
15
+
13
16
  name: str = Field(..., min_length=0, max_length=128, description="Paycheck item name", example="Bonus")
14
17
  unit: Optional[float] = Field(default=None, description="Units for the item", example=10.0)
15
18
  payment_per_unit: Optional[float] = Field(alias="paymentPerUnit", default=None, description="Payment per unit", example=150.0)
@@ -30,7 +33,11 @@ class PaycheckCreate(BaseModel):
30
33
 
31
34
  class PaycheckUpdate(BaseModel):
32
35
  """Schema for updating pay checks."""
36
+ # Function Parameters
37
+ employee_uid: UUID = Field(..., description="Employee identifier", alias="employeeUid")
38
+ paycheck_uid: UUID = Field(..., description="Paycheck identifier", alias="paycheckUid")
33
39
  paycheck_for: Optional[int] = Field(alias="payCheckFor", default=None, description="Target paycheck type/code", example=1)
40
+
34
41
  name: str = Field(..., min_length=0, max_length=128, description="Paycheck item name", example="Bonus")
35
42
  unit: Optional[float] = Field(default=None, description="Units for the item", example=10.0)
36
43
  payment_per_unit: Optional[float] = Field(alias="paymentPerUnit", default=None, description="Payment per unit", example=150.0)
@@ -22,10 +22,13 @@ class Pension(BaseModel):
22
22
  populate_by_name = True
23
23
 
24
24
  class EmployeePensionCreate(BaseModel):
25
+ # Function Parameters
26
+ employee_uid: UUID = Field(..., description="Employee identifier", alias="employeeUid")
25
27
  pension_uid: Optional[UUID] = Field(
26
28
  example="00000000-0000-0000-0000-000000000000", alias="pensionUid", default=None,
27
29
  description="Target pension UID (if updating an existing pension)"
28
30
  )
31
+
29
32
  pension: Pension = Field(
30
33
  default=None,
31
34
  description="Pension master data (name, type, PBS codes)",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brynq_sdk_zenegy
3
- Version: 1.3.7
3
+ Version: 1.3.10
4
4
  Summary: Zenegy wrapper from BrynQ
5
5
  Author: BrynQ
6
6
  Author-email: support@brynq.com
@@ -4,12 +4,12 @@ brynq_sdk_zenegy/companies.py,sha256=DbNx4E-tjqdohbqpPeVrKTWCNCyOa9m7ZIXwuPx4ctU
4
4
  brynq_sdk_zenegy/cost_center.py,sha256=zdBSe8KZ683LIeHIXCt5Y9sCY4HcXI_eEcooq3P9jSU,2763
5
5
  brynq_sdk_zenegy/departments.py,sha256=o2G59BkLioyo9_FhwppA_1nurzNf30k6XkkTYDLdsa4,2451
6
6
  brynq_sdk_zenegy/employee_documents.py,sha256=xYXukrSd6gqI5UbiupGb5_zrKzOsNeh4FbVippdIpw8,1396
7
- brynq_sdk_zenegy/employees.py,sha256=buIEIxGjeSq-C8yy1PJcc11CP7YNIHAXjvNj3epKhnY,13650
7
+ brynq_sdk_zenegy/employees.py,sha256=7dKAIe431EwGTP2H9aJJBSMm5EU19yLJjlLE9d9j6n0,16740
8
8
  brynq_sdk_zenegy/global_value_sets.py,sha256=2T63dLUvD4mY7L1coXYQD8UttE4m0gKGolH55r_2mmI,10289
9
9
  brynq_sdk_zenegy/global_values.py,sha256=RBv33piYctIu95Z022noDFsMhPjSzsaBbyeaU84V2bw,10598
10
10
  brynq_sdk_zenegy/paychecks.py,sha256=9kUuFY3jc8vfyYVxqBXDWSVXD_X-FlW693an-CirlTw,4844
11
11
  brynq_sdk_zenegy/payroll.py,sha256=gVLBEaMe2Rq3Vt06Gf2KllnGmwt5glNJFFiSylQ4AMs,4921
12
- brynq_sdk_zenegy/payslips.py,sha256=2Tkb99rMy2uj9rDFWnlzOA4u1NqjfFxyBMt6Tdmw1tU,2210
12
+ brynq_sdk_zenegy/payslips.py,sha256=yqYhREtpO41fO-7C4eGC9a8UO4BqoFTq4cHASUnWPGI,2217
13
13
  brynq_sdk_zenegy/pensions.py,sha256=1-IBvs8CzABTCDh_FYjx9nSk4qWQ0e6gXijncWXxgzg,4581
14
14
  brynq_sdk_zenegy/supplements_and_deductions_rates.py,sha256=cGVs73SYDFxeIifzvHfKGqzd_SrtxqeG-KFGJdDu2pA,2723
15
15
  brynq_sdk_zenegy/zenegy.py,sha256=XbRm-Wj8jC4fVNmZfbOj-Rk0d6cFoPDeQYGz_MSXFsU,7745
@@ -19,15 +19,15 @@ brynq_sdk_zenegy/schemas/companies.py,sha256=8HAs1Y6E1lj54P8bN_HgEt_WDrABm7F1_pe
19
19
  brynq_sdk_zenegy/schemas/company_cost_centers.py,sha256=PG1POeqMDVSUg_NE_ICNOiBy7hTlO1f4SGPhvUiNTlc,1896
20
20
  brynq_sdk_zenegy/schemas/company_departments.py,sha256=supGcTOp5cKq10OFzt82zdo5ZJjzd2h_A6W33qMNiOg,11449
21
21
  brynq_sdk_zenegy/schemas/employee_documents.py,sha256=eGVDkx48GFTQ31C1uKZ9rC2fXD-_-KqFu8azNU2rskY,1871
22
- brynq_sdk_zenegy/schemas/employee_pay_checks.py,sha256=sRsx8BGo3Roi3WcdOKEujTfeoVVX6Ihq3QBtdPKADsA,20985
23
- brynq_sdk_zenegy/schemas/employee_pensions.py,sha256=MrpNRaqlaX4B3IBBufwFI7fD1yNqK5W0SQ_acRofIcY,9231
22
+ brynq_sdk_zenegy/schemas/employee_pay_checks.py,sha256=gXZ7A9t35Pp3rkcG5dykkmv3VLPx5Bb70vTovW0h_Rg,21315
23
+ brynq_sdk_zenegy/schemas/employee_pensions.py,sha256=rbVezcnD4vSJPDna7EZgiBVfb0ha272dS9iOXnRUHe8,9350
24
24
  brynq_sdk_zenegy/schemas/employees.py,sha256=Fb5S7LOZiZJ6FzMHPdR_yh-Qn1yWSn_rIc173EShXLQ,225480
25
25
  brynq_sdk_zenegy/schemas/global_value_sets.py,sha256=YEZm-im-22VbTcDpiBwNLcJgT7UAqkBUN3I961ffd8Y,12912
26
26
  brynq_sdk_zenegy/schemas/global_values.py,sha256=cdfOewCPiMIJynWaxml-GAmY44qR5SQwGYgijD-f7fg,34270
27
27
  brynq_sdk_zenegy/schemas/payrolls.py,sha256=DLCDpm5jSBeC4KhdRHVVbne8x3zdbAKwRM3MOChWHBM,16514
28
28
  brynq_sdk_zenegy/schemas/payslips.py,sha256=kgRuEky2B2huVtag7OZzaXOX_hc2_cZMOS84DyZCZvo,2466
29
29
  brynq_sdk_zenegy/schemas/supplements_and_deductions_rates.py,sha256=Ig4V27KhJrt_HqF1mOrr0yb0KgYxiYqs1A3k3AcQ-Ng,16928
30
- brynq_sdk_zenegy-1.3.7.dist-info/METADATA,sha256=wBoaz8xh2mtlDRsJPAxWnqrf6m1tkC7iQd4DHMSZY-c,341
31
- brynq_sdk_zenegy-1.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
- brynq_sdk_zenegy-1.3.7.dist-info/top_level.txt,sha256=quyHxFQMtqHujnE15mGvieYNp-0MR1aLx0ns5VYYWOo,17
33
- brynq_sdk_zenegy-1.3.7.dist-info/RECORD,,
30
+ brynq_sdk_zenegy-1.3.10.dist-info/METADATA,sha256=LNd-kTiWkxod-_znMdHe1aVFvBUw6wmwVuHocClFKZo,342
31
+ brynq_sdk_zenegy-1.3.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
32
+ brynq_sdk_zenegy-1.3.10.dist-info/top_level.txt,sha256=quyHxFQMtqHujnE15mGvieYNp-0MR1aLx0ns5VYYWOo,17
33
+ brynq_sdk_zenegy-1.3.10.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5