brynq-sdk-bob 2.6.2.dev13__py3-none-any.whl → 2.8.2__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.
brynq_sdk_bob/__init__.py CHANGED
@@ -1,11 +1,9 @@
1
1
  import base64
2
- import re
3
- from typing import Union, List, Optional, Literal
4
- import pandas as pd
2
+ from typing import List, Optional, Literal
5
3
  import requests
6
4
  import os
5
+ from .reports import Reports
7
6
  from brynq_sdk_brynq import BrynQ
8
- from brynq_sdk_functions import Functions
9
7
  from .bank import Bank
10
8
  from .company import Company
11
9
  from .documents import CustomDocuments
@@ -17,6 +15,7 @@ from .salaries import Salaries
17
15
  from .timeoff import TimeOff
18
16
  from .work import Work
19
17
  from .custom_tables import CustomTables
18
+ from .payroll_history import History
20
19
 
21
20
  class Bob(BrynQ):
22
21
  def __init__(self, system_type: Optional[Literal['source', 'target']] = None, test_environment: bool = True, debug: bool = False, target_system: str = None):
@@ -40,15 +39,13 @@ class Bob(BrynQ):
40
39
  self.companies = Company(self)
41
40
  self.named_lists = NamedLists(self)
42
41
  self.custom_tables = CustomTables(self)
42
+ self.payroll_history = History(self)
43
+ self.reports = Reports(self)
43
44
  self.data_interface_id = os.getenv("DATA_INTERFACE_ID")
44
45
  self.debug = debug
45
46
 
46
47
  def _get_request_headers(self, system_type):
47
48
  credentials = self.interfaces.credentials.get(system='bob', system_type=system_type)
48
- if type(credentials) is list:
49
- credentials = credentials[0]
50
-
51
- print(credentials)
52
49
  auth_token = base64.b64encode(f"{credentials.get('data').get('User ID')}:{credentials.get('data').get('API Token')}".encode()).decode('utf-8')
53
50
  headers = {
54
51
  "accept": "application/json",
brynq_sdk_bob/bank.py CHANGED
@@ -2,35 +2,23 @@ import pandas as pd
2
2
  from brynq_sdk_functions import Functions
3
3
  from .schemas.bank import BankSchema
4
4
 
5
- import time
6
- from tqdm import tqdm
7
- from tenacity import retry, stop_after_delay, wait_exponential
8
5
 
9
6
  class Bank:
10
7
  def __init__(self, bob):
11
8
  self.bob = bob
12
9
  self.schema = BankSchema
13
10
 
14
- @retry(stop=stop_after_delay(120), wait=wait_exponential(multiplier=1, min=1, max=10))
15
- def _get_bank_accounts(self, person_id: str):
16
- """Fetch bank accounts for a person with retry logic (max 2 minutes)."""
17
- resp = self.bob.session.get(url=f"{self.bob.base_url}people/{person_id}/bank-accounts", timeout=self.bob.timeout)
18
- resp.raise_for_status()
19
- return resp
20
-
21
11
  def get(self, person_ids: pd.Series, field_selection: list[str] = []) -> (pd.DataFrame, pd.DataFrame):
22
12
  data = []
23
- for person_id in tqdm(person_ids, desc="Fetching bank accounts"):
24
- resp = self._get_bank_accounts(person_id)
13
+ for person_id in person_ids:
14
+ resp = self.bob.session.get(url=f"{self.bob.base_url}people/{person_id}/bank-accounts", timeout=self.bob.timeout)
15
+ resp.raise_for_status()
25
16
  temp_data = resp.json()['values']
26
17
  # when an employee has one or more bank accounts, the response is a list of dictionaries.
27
18
  for account in temp_data:
28
19
  account['employee_id'] = person_id
29
20
  data += temp_data
30
21
 
31
- # rate limit is 50 per minute
32
- time.sleep(1.3)
33
-
34
22
  df = pd.DataFrame(data)
35
23
 
36
24
  valid_banks, invalid_banks = Functions.validate_data(df=df, schema=BankSchema, debug=True)
@@ -1,9 +1,7 @@
1
1
  import pandas as pd
2
2
  import requests
3
-
4
- from brynq_sdk_functions import Functions
5
-
6
3
  from .schemas.employment import EmploymentSchema
4
+ from brynq_sdk_functions import Functions
7
5
 
8
6
 
9
7
  class Employment:
brynq_sdk_bob/payments.py CHANGED
@@ -1,12 +1,7 @@
1
- import time
2
- from typing import List, Optional
3
-
4
1
  import pandas as pd
5
- from tqdm import tqdm
6
-
2
+ from typing import Optional, List
7
3
  from brynq_sdk_functions import Functions
8
-
9
- from .schemas.payments import ActualPaymentsSchema, VariablePaymentSchema
4
+ from .schemas.payments import VariablePaymentSchema, ActualPaymentsSchema
10
5
 
11
6
 
12
7
  class Payments:
@@ -14,43 +9,9 @@ class Payments:
14
9
  self.bob = bob
15
10
  self.schema = VariablePaymentSchema
16
11
 
17
- def _apply_named_list_mappings(self, df: pd.DataFrame) -> pd.DataFrame:
18
- """Apply named list ID-to-value mappings to dataframe columns."""
19
- if df.empty:
20
- return df
21
-
22
- # Fetch named lists from Bob API
23
- resp_named_lists = self.bob.session.get(
24
- url=f"{self.bob.base_url}company/named-lists",
25
- timeout=self.bob.timeout,
26
- headers=self.bob.headers
27
- )
28
- named_lists = resp_named_lists.json()
29
-
30
- # Transform named_lists to create id-to-value mappings for each field
31
- named_lists = {
32
- key.split('.')[-1]: {item['id']: item['value'] for item in value['values']}
33
- for key, value in named_lists.items()
34
- }
35
-
36
- # rename payrollVariableType to variableType in named lists
37
- named_lists['variableType'] = named_lists['payrollVariableType']
38
-
39
- for field in df.columns:
40
- # Fields in the response and in the named-list have different building blocks
41
- # but they both end with the same last block
42
- field_df = field.split('.')[-1].split('work_')[-1]
43
- if field_df in named_lists.keys() and field_df not in ['site']:
44
- mapping = named_lists[field_df]
45
- df[field] = df[field].apply(
46
- lambda v: [mapping.get(x, x) for x in v] if isinstance(v, list) else mapping.get(v, v)
47
- )
48
-
49
- return df
50
-
51
12
  def get(self, person_ids: List[str]) -> (pd.DataFrame, pd.DataFrame):
52
13
  df = pd.DataFrame()
53
- for person_id in tqdm(person_ids, desc="Fetching variable payments"):
14
+ for person_id in person_ids:
54
15
  resp = self.bob.session.get(url=f"{self.bob.base_url}people/{person_id}/variable", timeout=self.bob.timeout)
55
16
  resp.raise_for_status()
56
17
  data = resp.json()
@@ -59,15 +20,7 @@ class Payments:
59
20
  record_path='values'
60
21
  )])
61
22
  df['employee_id'] = person_id
62
-
63
- # Rate limit is 50 per minute
64
- time.sleep(1.3)
65
-
66
23
  df = df.reset_index(drop=True)
67
-
68
- # Apply named list mappings
69
- df = self._apply_named_list_mappings(df)
70
-
71
24
  valid_payments, invalid_payments = Functions.validate_data(df=df, schema=self.schema, debug=True)
72
25
  return valid_payments, invalid_payments
73
26
 
@@ -154,9 +107,6 @@ class Payments:
154
107
 
155
108
  df = pd.json_normalize(all_results)
156
109
 
157
- # Apply named list mappings
158
- df = self._apply_named_list_mappings(df)
159
-
160
110
  valid_payments, invalid_payments = Functions.validate_data(
161
111
  df=df,
162
112
  schema=ActualPaymentsSchema,
brynq_sdk_bob/people.py CHANGED
@@ -1,15 +1,14 @@
1
- from typing import List, Optional
2
-
3
1
  import pandas as pd
4
-
5
- from brynq_sdk_functions import BrynQPanderaDataFrameModel, Functions
6
-
2
+ import re
3
+ from typing import Optional
4
+ from brynq_sdk_functions import Functions
5
+ from brynq_sdk_functions import BrynQPanderaDataFrameModel
7
6
  from .bank import Bank
8
- from .custom_tables import CustomTables
9
7
  from .employment import Employment
10
8
  from .salaries import Salaries
11
9
  from .schemas.people import PeopleSchema
12
10
  from .work import Work
11
+ from .custom_tables import CustomTables
13
12
 
14
13
 
15
14
  class People:
@@ -23,6 +22,7 @@ class People:
23
22
  self.schema = PeopleSchema
24
23
 
25
24
 
25
+
26
26
  # Build API fields using column metadata if present (api_field), otherwise use the column (alias) name
27
27
  def __build_api_fields(self, schema_model: BrynQPanderaDataFrameModel) -> list[str]:
28
28
  schema = schema_model.to_schema()
@@ -31,78 +31,96 @@ class People:
31
31
  for col_name, col in schema.columns.items()
32
32
  ]
33
33
 
34
- def get(self, schema_custom_fields: Optional[BrynQPanderaDataFrameModel] = None, employee_ids: Optional[List[str]] = None, show_inactive: bool = False) -> pd.DataFrame:
34
+ def get(self, schema_custom_fields: Optional[BrynQPanderaDataFrameModel] = None) -> pd.DataFrame:
35
+
35
36
  core_fields = self.__build_api_fields(PeopleSchema)
36
37
  custom_fields = self.__build_api_fields(schema_custom_fields) if schema_custom_fields is not None else []
37
38
  fields = core_fields + custom_fields
38
39
 
39
- # Build filters based on employee_ids if provided
40
- filters = []
41
- if employee_ids is not None:
42
- filters = [
43
- {
44
- "fieldPath": "root.id",
45
- "operator": "equals",
46
- "values": employee_ids
47
- }
48
- ]
49
-
50
40
  resp = self.bob.session.post(url=f"{self.bob.base_url}people/search",
51
41
  json={
52
42
  "fields": fields,
53
- "filters": filters,
54
- "showInactive": show_inactive,
43
+ "filters": []
55
44
  #"humanReadable": "REPLACE"
56
45
  },
57
46
  timeout=self.bob.timeout)
58
47
  resp.raise_for_status()
59
48
  df = pd.json_normalize(resp.json()['employees'])
60
- if df.empty and employee_ids is not None and resp.status_code == 200:
61
- raise Exception(f"No employees found in HiBob for employee_ids: {employee_ids}")
62
49
 
63
- df = df.loc[:, ~df.columns.str.contains('value')]
64
50
 
65
51
  # Normalize separators in incoming data: convert '/' to '.' to match schema aliases
66
52
  df.columns = df.columns.str.replace('/', '.', regex=False)
67
53
 
54
+
55
+ # Clean up custom .value columns; keeps only one field per custom key. the .value column actually contains the value., other column is nested key.
56
+ custom_cols = [col for col in df.columns if "custom" in str(col) and str(col).endswith('.value')]
57
+ for col in custom_cols:
58
+ new_col = col.removesuffix('.value')
59
+ # prefer non-empty clean column, otherwise upgrade .value data
60
+ if new_col not in df.columns:
61
+ df = df.rename(columns={col: new_col})
62
+ elif df[new_col].isna().all() and not df[col].isna().all():
63
+ df[new_col] = df[col]
64
+ df = df.drop(columns=[col])
65
+ else:
66
+ # Drop identical/redundant .value col
67
+ if df[new_col].equals(df[col]):
68
+ df = df.drop(columns=[col])
69
+ pass
70
+
71
+
68
72
  # A lot of fields from Bob are returned with only ID's. Those fields should be mapped to names. Therefore, we need to get the mapping from the named-lists endpoint.
69
73
  resp_named_lists = self.bob.session.get(url=f"{self.bob.base_url}company/named-lists", timeout=self.bob.timeout, headers=self.bob.headers)
70
74
  named_lists = resp_named_lists.json()
71
- # save json to file
72
- # import json
73
- # with open('named_lists.json', 'w') as f:
74
- # json.dump(named_lists, f, indent=4)
75
75
 
76
76
  # Transform named_lists to create id-to-value mappings for each field
77
77
  named_lists = {key.split('.')[-1]: {item['id']: item['value'] for item in value['values']} for key, value in named_lists.items()}
78
78
 
79
- deviating_named_list_cols_mapping = {
80
- 'payroll.employment.actualWorkingPattern.workingPatternId': 'workingPattern_entity_list',
81
- 'payroll.employment.type': 'payrollEmploymentType',
82
- 'home.familyStatus': 'familystatus',
83
- 'personal.nationality': 'nationalities',
84
- 'internal.terminationReason': 'terminationreason',
85
- }
86
-
87
79
  for field in df.columns:
88
80
  # Fields in the response and in the named-list does have different building blocks (e.g. people.payroll.entitlement. or people.entitlement.). But they both end with the same last block
89
81
  field_df = field.split('.')[-1].split('work_')[-1]
90
-
91
- # Check if this field has a deviating mapping
92
- named_list_key = deviating_named_list_cols_mapping.get(field, field_df)
93
-
94
- if named_list_key in named_lists.keys() and named_list_key not in ['site']:
95
- mapping = named_lists[named_list_key]
82
+ if field_df in named_lists.keys() and field_df not in ['site']:
83
+ mapping = named_lists[field_df]
96
84
  df[field] = df[field].apply(
97
85
  lambda v: [mapping.get(x, x) for x in v] if isinstance(v, list) else mapping.get(v, v)
98
86
  )
99
87
 
88
+
100
89
  if schema_custom_fields is not None:
90
+
101
91
  valid_people, invalid_people_custom = Functions.validate_data(df=df, schema=schema_custom_fields, debug=True)
92
+
93
+
102
94
  else:
103
95
  valid_people = df
104
96
  invalid_people_custom = pd.DataFrame()
105
97
 
98
+
106
99
  valid_people, invalid_people = Functions.validate_data(df=valid_people, schema=PeopleSchema, debug=True)
107
100
 
101
+ # For columns ending with .value.value, use fillna to fill the corresponding base column since that's not done automatically
102
+ def _normalize_key(key: str) -> str:
103
+ # Build mapping from custom schema alias (API path) to the real column name used after validation
104
+ key = key.lstrip('.')
105
+ key = key.replace('/', '.')
106
+ key = key.replace('.', '_')
107
+ key = re.sub(r'(?<!^)([A-Z])', r'_\1', key).lower()
108
+ return key
109
+
110
+ alias_to_real: dict = {}
111
+ if schema_custom_fields is not None:
112
+ alias_map = getattr(schema_custom_fields, "_alias_map", {}) # real_name -> alias
113
+ alias_to_real = {_normalize_key(alias): real for real, alias in alias_map.items()}
114
+
115
+ for col in valid_people.columns:
116
+ if col.endswith('value.value'):
117
+ # Compute the base alias (remove suffix), normalise, and resolve to real column name via custom schema
118
+ base_alias = col[:-12]
119
+ target_key = _normalize_key(base_alias)
120
+ target_col = alias_to_real.get(target_key, target_key)
121
+ if target_col in valid_people.columns:
122
+ valid_people[target_col] = valid_people[target_col].fillna(valid_people[col])
123
+
124
+ # Remove columns that contain '.value' or '_get'
125
+ valid_people = valid_people.loc[:, ~valid_people.columns.str.contains(r'\.value|_get')]
108
126
  return valid_people, pd.concat([invalid_people, invalid_people_custom])
@@ -0,0 +1,38 @@
1
+ from datetime import datetime
2
+ from io import BytesIO
3
+ from typing import Optional, TYPE_CHECKING
4
+
5
+ import pandas as pd
6
+ from brynq_sdk_functions import Functions
7
+ if TYPE_CHECKING:
8
+ from brynq_sdk_bob import Bob
9
+
10
+
11
+ class Reports:
12
+ def __init__(self, bob):
13
+ self.bob: Bob = bob
14
+
15
+ def get(self) -> pd.DataFrame:
16
+ resp = self.bob.session.get(url=f"{self.bob.base_url}company/reports", timeout=self.bob.timeout)
17
+ resp.raise_for_status()
18
+ data = resp.json()
19
+ df = pd.json_normalize(
20
+ data,
21
+ record_path='views'
22
+ )
23
+ # df = self.bob.rename_camel_columns_to_snake_case(df)
24
+ # valid_documents, invalid_documents = Functions.validate_data(df=df, schema=DocumentsSchema, debug=True)
25
+
26
+ return df
27
+
28
+ def download(self, report_id: int | str = None) -> bytes:
29
+ if report_id:
30
+ url = f"{self.bob.base_url}company/reports/{report_id}/download"
31
+ else:
32
+ raise ValueError("Either report_id or report_name must be provided")
33
+
34
+ resp = self.bob.session.get(url=url, timeout=self.bob.timeout, params={"format": "csv"})
35
+ resp.raise_for_status()
36
+ data = resp.content
37
+
38
+ return data
brynq_sdk_bob/salaries.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import pandas as pd
2
2
  import requests
3
- from typing import Optional, List
4
3
  from brynq_sdk_functions import Functions
5
4
  from .schemas.salary import SalarySchema, SalaryCreateSchema
6
5
 
@@ -10,16 +9,10 @@ class Salaries:
10
9
  self.bob = bob
11
10
  self.schema = SalarySchema
12
11
 
13
- def get(self, employee_ids: Optional[List[str]] = None) -> tuple[pd.DataFrame, pd.DataFrame]:
14
- params = {"limit": 100}
15
-
16
- # Add employeeIds filter if provided
17
- if employee_ids is not None:
18
- params["employeeIds"] = ",".join(employee_ids)
19
-
12
+ def get(self) -> tuple[pd.DataFrame, pd.DataFrame]:
20
13
  request = requests.Request(method='GET',
21
14
  url=f"{self.bob.base_url}bulk/people/salaries",
22
- params=params)
15
+ params={"limit": 100})
23
16
  data = self.bob.get_paginated_result(request)
24
17
  df = pd.json_normalize(
25
18
  data,
@@ -1,5 +1,4 @@
1
1
  import pandera as pa
2
- from typing import Optional
3
2
  from pandera.typing import Series, String
4
3
  import pandas as pd
5
4
  from brynq_sdk_functions import BrynQPanderaDataFrameModel
@@ -7,19 +6,19 @@ from brynq_sdk_functions import BrynQPanderaDataFrameModel
7
6
 
8
7
  class BankSchema(BrynQPanderaDataFrameModel):
9
8
  id: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Bank ID", alias="id")
10
- employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="employee_id")
11
- amount: Optional[Series[pd.Int64Dtype]] = pa.Field(coerce=True, description="Amount", alias="amount")
12
- allocation: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Allocation", alias="allocation")
13
- branch_address: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Branch Address", alias="branchAddress")
9
+ employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="employeeId")
10
+ amount: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Amount", alias="amount")
11
+ allocation: Series[String] = pa.Field(coerce=True, nullable=True, description="Allocation", alias="allocation")
12
+ branch_address: Series[String] = pa.Field(coerce=True, nullable=True, description="Branch Address", alias="branchAddress")
14
13
  bank_name: Series[String] = pa.Field(coerce=True, nullable=True, description="Bank Name", alias="bankName")
15
14
  account_number: Series[String] = pa.Field(coerce=True, nullable=True, description="Account Number", alias="accountNumber")
16
- routing_number: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Routing Number", alias="routingNumber")
15
+ routing_number: Series[String] = pa.Field(coerce=True, nullable=True, description="Routing Number", alias="routingNumber")
17
16
  bank_account_type: Series[String] = pa.Field(coerce=True, nullable=True, description="Bank Account Type", alias="bankAccountType")
18
- bic_or_swift: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="BIC or SWIFT", alias="bicOrSwift")
17
+ bic_or_swift: Series[String] = pa.Field(coerce=True, nullable=True, description="BIC or SWIFT", alias="bicOrSwift")
19
18
  changed_by: Series[String] = pa.Field(coerce=True, nullable=True, description="Changed By", alias="changedBy")
20
19
  iban: Series[String] = pa.Field(coerce=True, description="IBAN", alias="iban")
21
20
  account_nickname: Series[String] = pa.Field(coerce=True, nullable=True, description="Account Nickname", alias="accountNickname")
22
- use_for_bonus: Optional[Series[pd.BooleanDtype]] = pa.Field(coerce=True, nullable=True, description="Use for Bonus", alias="useForBonus")
21
+ use_for_bonus: Series[pd.BooleanDtype] = pa.Field(coerce=True, nullable=True, description="Use for Bonus", alias="useForBonus")
23
22
 
24
23
  class Config:
25
24
  coerce = True
@@ -6,7 +6,7 @@ from brynq_sdk_functions import BrynQPanderaDataFrameModel
6
6
 
7
7
  class EmploymentSchema(BrynQPanderaDataFrameModel):
8
8
  id: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Employment ID", alias="id")
9
- employee_id: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Employee ID", alias="employeeId")
9
+ employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="employeeId")
10
10
  active_effective_date: Series[DateTime] = pa.Field(coerce=True, description="Active Effective Date", alias="activeEffectiveDate")
11
11
  contract: Series[String] = pa.Field(coerce=True, nullable=True, description="Contract", alias="contract") # has a list of possible values
12
12
  creation_date: Series[DateTime] = pa.Field(coerce=True, nullable=True, description="Creation Date", alias="creationDate")
@@ -38,8 +38,8 @@ class ActualPaymentsSchema(BrynQPanderaDataFrameModel):
38
38
  pay_type: Series[String] = pa.Field(coerce=True, description="Pay Type", alias="payType")
39
39
  amount_value: Series[Float] = pa.Field(coerce=True, description="Amount Value", alias="amount.value")
40
40
  amount_currency: Series[String] = pa.Field(coerce=True, description="Amount Currency", alias="amount.currency")
41
- # change_reason: Series[String] = pa.Field(nullable=True, coerce=True, description="Change Reason", alias="change.reason")
42
- # change_changed_by: Series[String] = pa.Field(nullable=True, coerce=True, description="Change Changed By", alias="change.changedBy")
41
+ change_reason: Series[String] = pa.Field(nullable=True, coerce=True, description="Change Reason", alias="change.reason")
42
+ change_changed_by: Series[String] = pa.Field(nullable=True, coerce=True, description="Change Changed By", alias="change.changedBy")
43
43
  change_changed_by_id: Series[String] = pa.Field(nullable=True, coerce=True, description="Change Changed By ID", alias="change.changedById")
44
44
 
45
45
  class Config:
@@ -1,14 +1,13 @@
1
1
  from datetime import datetime
2
- from typing import Dict, List, Optional
2
+ from typing import Optional, List, Dict
3
3
 
4
4
  import pandas as pd
5
5
  import pandera as pa
6
- import pandera.extensions as extensions
7
6
  from pandera import Bool
8
- from pandera.engines.pandas_engine import DateTime
9
- from pandera.typing import Float, Series, String
10
-
7
+ from pandera.typing import Series, String, Float
8
+ import pandera.extensions as extensions
11
9
  from brynq_sdk_functions import BrynQPanderaDataFrameModel
10
+ from pandera.engines.pandas_engine import DateTime
12
11
 
13
12
 
14
13
  @extensions.register_check_method()
@@ -27,7 +26,7 @@ class PeopleSchema(BrynQPanderaDataFrameModel):
27
26
  first_name: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="First Name", alias="firstName", metadata={"api_field": "root.firstName"})
28
27
  full_name: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Full Name", alias="fullName", metadata={"api_field": "root.fullName"})
29
28
  # the date is in DD/MM/YYYY format,
30
- personal_birth_date: Optional[Series[DateTime]] = pa.Field(coerce=True, nullable=True, description="Personal Birth Date", alias="personal.birthDate") # , dtype_kwargs={"to_datetime_kwargs": {"format": "%d/%m/%Y"}}
29
+ personal_birth_date: Optional[Series[DateTime]] = pa.Field(coerce=True, nullable=False, description="Personal Birth Date", alias="personal.birthDate")
31
30
  personal_pronouns: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Personal Pronouns", alias="personal.pronouns")
32
31
  personal_honorific: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Personal Honorific", alias="personal.honorific")
33
32
  personal_nationality: Optional[Series[object]] = pa.Field(coerce=True, check_name=check_list, description="Personal Nationality", alias="personal.nationality")
@@ -41,10 +40,10 @@ class PeopleSchema(BrynQPanderaDataFrameModel):
41
40
  work_manager: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Work Manager", alias="work.manager")
42
41
  work_work_phone: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Work Work Phone", alias="work.workPhone")
43
42
  work_tenure_duration_period_i_s_o: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Work Tenure Duration Period ISO", alias="work.tenureDuration.periodISO")
44
- work_tenure_duration_sort_factor: Optional[Series[pd.Int64Dtype]] = pa.Field(coerce=True, nullable=True, description="Work Tenure Duration Sort Factor", alias="work.tenureDuration.sortFactor")
43
+ work_tenure_duration_sort_factor: Optional[Series[pd.Int64Dtype]] = pa.Field(coerce=True, nullable=False, description="Work Tenure Duration Sort Factor", alias="work.tenureDuration.sortFactor")
45
44
  work_tenure_duration_humanize: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Work Tenure Duration Humanize", alias="work.tenureDuration.humanize")
46
45
  work_duration_of_employment_period_i_s_o: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Work Duration of Employment Period ISO", alias="work.durationOfEmployment.periodISO")
47
- work_duration_of_employment_sort_factor: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Work Duration of Employment Sort Factor", alias="work.durationOfEmployment.sortFactor")
46
+ work_duration_of_employment_sort_factor: Optional[Series[String]] = pa.Field(coerce=True, nullable=False, description="Work Duration of Employment Sort Factor", alias="work.durationOfEmployment.sortFactor")
48
47
  work_duration_of_employment_humanize: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Work Duration of Employment Humanize", alias="work.durationOfEmployment.humanize")
49
48
  work_reports_to_id_in_company: Optional[Series[pd.Int64Dtype]] = pa.Field(coerce=True, nullable=True, description="Work Reports to ID in Company", alias="work.reportsToIdInCompany")
50
49
  work_employee_id_in_company: Optional[Series[pd.Int64Dtype]] = pa.Field(coerce=True, nullable=True, description="Work Employee ID in Company", alias="work.employeeIdInCompany")
@@ -203,7 +202,7 @@ class PeopleSchema(BrynQPanderaDataFrameModel):
203
202
  payroll_employment_actual_working_pattern_days_thursday: Optional[Series[float]] = pa.Field(coerce=True, nullable=True, description="Actual Working Pattern - Thursday", alias="payroll.employment.actualWorkingPattern.days.thursday")
204
203
  payroll_employment_actual_working_pattern_days_saturday: Optional[Series[float]] = pa.Field(coerce=True, nullable=True, description="Actual Working Pattern - Saturday", alias="payroll.employment.actualWorkingPattern.days.saturday")
205
204
  payroll_employment_actual_working_pattern_hours_per_day: Optional[Series[float]] = pa.Field(coerce=True, nullable=True, description="Actual Working Pattern - Hours Per Day", alias="payroll.employment.actualWorkingPattern.hoursPerDay")
206
- payroll_employment_actual_working_pattern_working_pattern_id: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Actual Working Pattern - Working Pattern ID", alias="payroll.employment.actualWorkingPattern.workingPatternId")
205
+ payroll_employment_actual_working_pattern_working_pattern_id: Optional[Series[pd.Int64Dtype]] = pa.Field(coerce=True, nullable=True, description="Actual Working Pattern - Working Pattern ID", alias="payroll.employment.actualWorkingPattern.workingPatternId")
207
206
  payroll_employment_site_working_pattern_working_pattern_type: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Site Working Pattern - Working Pattern Type", alias="payroll.employment.siteWorkingPattern.workingPatternType")
208
207
  payroll_employment_site_working_pattern_days_sunday: Optional[Series[float]] = pa.Field(coerce=True, nullable=True, description="Site Working Pattern - Sunday", alias="payroll.employment.siteWorkingPattern.days.sunday")
209
208
  payroll_employment_site_working_pattern_days_tuesday: Optional[Series[float]] = pa.Field(coerce=True, nullable=True, description="Site Working Pattern - Tuesday", alias="payroll.employment.siteWorkingPattern.days.tuesday")
@@ -215,13 +214,24 @@ class PeopleSchema(BrynQPanderaDataFrameModel):
215
214
  payroll_employment_site_working_pattern_hours_per_day: Optional[Series[float]] = pa.Field(coerce=True, nullable=True, description="Site Working Pattern - Hours Per Day", alias="payroll.employment.siteWorkingPattern.hoursPerDay")
216
215
  payroll_employment_site_working_pattern_working_pattern_id: Optional[Series[pd.Int64Dtype]] = pa.Field(coerce=True, nullable=True, description="Site Working Pattern - Working Pattern ID", alias="payroll.employment.siteWorkingPattern.workingPatternId")
217
216
  payroll_employment_calendar_id: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Calendar ID", alias="payroll.employment.calendarId")
218
-
219
-
220
217
  payroll_employment_salary_pay_type: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Salary Pay Type", alias="payroll.employment.salaryPayType")
221
218
  payroll_employment_flsa_code: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="FLSA Code", alias="payroll.employment.flsaCode")
222
219
  payroll_employment_fte: Optional[Series[Float]] = pa.Field(coerce=True, nullable=True, description="FTE %", alias="payroll.employment.fte")
223
220
  payroll_employment_weekly_hours: Optional[Series[Float]] = pa.Field(coerce=True, nullable=True, description="Weekly Hours", alias="payroll.employment.weeklyHours")
224
221
 
222
+ # Salary information
223
+ # SOLUTION NOW FOR MFT: TODO: FIX UNNESTING (including .value.value)
224
+ payroll_salary_monthly_get: Optional[Series[str]] = pa.Field(coerce=True, nullable=True, description="Base salary (monthly), used for getting nested json", alias="payroll.salary.monthlyPayment")
225
+ payroll_salary_monthly_payment: Optional[Series[Float]] = pa.Field(coerce=True, nullable=True, description="Base salary (monthly)", alias="payroll.salary.monthlyPayment.value")
226
+ payroll_salary_monthly_payment_currency: Optional[Series[str]] = pa.Field(coerce=True, nullable=True, description="Base salary (monthly) currency", alias="payroll.salary.monthlyPayment.currency")
227
+
228
+ payroll_salary_yearly_get: Optional[Series[str]] = pa.Field(coerce=True, nullable=True, description="Base salary (yearly), used for getting nested json", alias="payroll.salary.yearlyPayment")
229
+ payroll_salary_yearly_payment: Optional[Series[Float]] = pa.Field(coerce=True, nullable=True, description="Base salary (yearly)", alias="payroll.salary.yearlyPayment.value")
230
+ payroll_salary_yearly_payment_currency: Optional[Series[str]] = pa.Field(coerce=True, nullable=True, description="Base salary (yearly) currency", alias="payroll.salary.yearlyPayment.currency")
231
+
232
+ payroll_salary_active_effective_date: Optional[Series[str]] = pa.Field(coerce=True, nullable=True, description="effective date payment", alias="payroll.salary.activeEffectiveDate")
233
+ payroll_salary_payment: Optional[Series[Float]] = pa.Field(coerce=True, nullable=True, description="Base Salary", alias="payroll.salary.payment")
234
+
225
235
  # Emergency contact
226
236
  emergency_first_name: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Emergency First Name", alias="emergency.firstName")
227
237
  emergency_second_name: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Emergency Middle Name", alias="emergency.secondName")
@@ -277,7 +287,6 @@ class PeopleSchema(BrynQPanderaDataFrameModel):
277
287
  # Positions / Job profile
278
288
  employee_position_opening_id: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Position Opening ID", alias="employee.positionOpeningId")
279
289
  employee_job_profile_id: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Job Profile ID", alias="employee.jobProfileId")
280
- employee_job_profile_code: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Job Profile Code", alias="employee.jobProfileCode")
281
290
  employee_recent_leave_start_date: Optional[Series[DateTime]] = pa.Field(coerce=True, nullable=True, description="Recent Leave Start Date", alias="employee.recentLeaveStartDate")
282
291
  employee_recent_leave_end_date: Optional[Series[DateTime]] = pa.Field(coerce=True, nullable=True, description="Recent Leave End Date", alias="employee.recentLeaveEndDate")
283
292
  employee_first_day_of_work: Optional[Series[DateTime]] = pa.Field(coerce=True, nullable=True, description="First Day Of Work", alias="employee.firstDayOfWork")
@@ -18,6 +18,7 @@ class SalarySchema(BrynQPanderaDataFrameModel):
18
18
  modification_date: Series[DateTime] = pa.Field(coerce=True, nullable=True, description="Modification Date", alias="modificationDate")
19
19
  effective_date: Series[DateTime] = pa.Field(coerce=True, description="Effective Date", alias="effectiveDate")
20
20
  end_effective_date: Series[DateTime] = pa.Field(coerce=True, nullable=True, description="End Effective Date", alias="endEffectiveDate")
21
+ change_reason: Series[str] = pa.Field(coerce=True, nullable=True, description="Change Reason", alias="change.reason")
21
22
  pay_period: Series[String] = pa.Field(coerce=True, nullable=True, description="Pay Period", alias="payPeriod")
22
23
  base_value: Series[Float] = pa.Field(coerce=True, nullable=True, description="Base Value", alias="base.value") #needs to become base.value?
23
24
  base_currency: Series[String] = pa.Field(coerce=True, nullable=True, description="Base Currency", alias="base.currency")
@@ -6,7 +6,7 @@ from brynq_sdk_functions import BrynQPanderaDataFrameModel
6
6
 
7
7
  class TimeOffSchema(BrynQPanderaDataFrameModel):
8
8
  change_type: Series[String] = pa.Field(coerce=True, description="Change Type", alias="changeType")
9
- employee_id: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Employee ID", alias="employeeId")
9
+ employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="employeeId")
10
10
  employee_display_name: Series[String] = pa.Field(coerce=True, description="Employee Display Name", alias="employeeDisplayName")
11
11
  employee_email: Series[String] = pa.Field(coerce=True, description="Employee Email", alias="employeeEmail")
12
12
  request_id: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Request ID", alias="requestId")
@@ -18,6 +18,7 @@ class WorkSchema(BrynQPanderaDataFrameModel):
18
18
  active_effective_date: Series[datetime] = pa.Field(coerce=True, nullable=True, description="Active Effective Date", alias="activeEffectiveDate")
19
19
  department: Series[str] = pa.Field(coerce=True, nullable=True, description="Department", alias="department")
20
20
  effective_date: Series[datetime] = pa.Field(coerce=True, nullable=True, description="Effective Date", alias="effectiveDate")
21
+ change_reason: Series[str] = pa.Field(coerce=True, nullable=True, description="Change Reason", alias="change.reason")
21
22
  change_changed_by: Series[str] = pa.Field(coerce=True, nullable=True, description="Change Changed By", alias="change.changedBy")
22
23
  change_changed_by_id: Series[str] = pa.Field(coerce=True, nullable=True, description="Change Changed By ID", alias="change.changedById")
23
24
  reports_to_id: Series[str] = pa.Field(coerce=True, nullable=True, description="Reports To ID", alias="reportsTo.id")
@@ -25,6 +26,7 @@ class WorkSchema(BrynQPanderaDataFrameModel):
25
26
  reports_to_surname: Series[str] = pa.Field(coerce=True, nullable=True, description="Reports To Surname", alias="reportsTo.surname")
26
27
  reports_to_email: Series[str] = pa.Field(coerce=True, nullable=True, description="Reports To Email", alias="reportsTo.email")
27
28
  reports_to_display_name: Series[str] = pa.Field(coerce=True, nullable=True, description="Reports To Display Name", alias="reportsTo.displayName")
29
+ reports_to: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=True, description="Reports To", alias="reportsTo")
28
30
  employee_id: Series[str] = pa.Field(coerce=True, description="Employee ID", alias="employeeId")
29
31
 
30
32
  class Config:
brynq_sdk_bob/work.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import pandas as pd
2
2
  import requests
3
- from typing import Optional, List
4
3
  from brynq_sdk_functions import Functions
5
4
  from .schemas.work import WorkSchema
6
5
 
@@ -10,38 +9,15 @@ class Work:
10
9
  self.bob = bob
11
10
  self.schema = WorkSchema
12
11
 
13
- def _ensure_reports_to_columns(self, df: pd.DataFrame) -> pd.DataFrame:
14
- reports_to_columns = [
15
- 'reportsTo.id',
16
- 'reportsTo.firstName',
17
- 'reportsTo.surname',
18
- 'reportsTo.email',
19
- 'reportsTo.displayName'
20
- ]
21
-
22
- for col in reports_to_columns:
23
- if col not in df.columns:
24
- df[col] = pd.NA
25
-
26
- return df
27
-
28
- def get(self, employee_ids: Optional[List[str]] = None) ->(pd.DataFrame, pd.DataFrame):
29
- params = {"limit": 100}
30
-
31
- # Add employeeIds filter if provided
32
- if employee_ids is not None:
33
- params["employeeIds"] = ",".join(employee_ids)
34
-
12
+ def get(self) ->(pd.DataFrame, pd.DataFrame):
35
13
  request = requests.Request(method='GET',
36
- url=f"{self.bob.base_url}bulk/people/work",
37
- params=params)
14
+ url=f"{self.bob.base_url}bulk/people/work")
38
15
  data = self.bob.get_paginated_result(request)
39
16
  df = pd.json_normalize(
40
17
  data,
41
18
  record_path='values',
42
19
  meta=['employeeId']
43
20
  )
44
- df = self._ensure_reports_to_columns(df)
45
21
  valid_work, invalid_work = Functions.validate_data(df=df, schema=self.schema, debug=True)
46
22
 
47
23
  return valid_work, invalid_work
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brynq_sdk_bob
3
- Version: 2.6.2.dev13
3
+ Version: 2.8.2
4
4
  Summary: Bob wrapper from BrynQ
5
5
  Author: BrynQ
6
6
  Author-email: support@brynq.com
7
7
  License: BrynQ License
8
8
  Requires-Dist: brynq-sdk-brynq<5,>=4
9
9
  Requires-Dist: pandas<3.0.0,>=2.2.0
10
- Requires-Dist: tenacity==8.2.3
11
10
  Dynamic: author
12
11
  Dynamic: author-email
13
12
  Dynamic: description
@@ -0,0 +1,29 @@
1
+ brynq_sdk_bob/__init__.py,sha256=M2y-juQ-GgzglQa0HvbWmeA9fBzv5L3MShHbTfb9rsk,3014
2
+ brynq_sdk_bob/bank.py,sha256=zTdfe_qCZt2FB7SZbQ7njIDspwTinLFdbeH_xUby2FY,966
3
+ brynq_sdk_bob/company.py,sha256=rjOpkm0CZ1EeJ-jddBl36GrGKUQviC1ca1aUL2tl1_M,848
4
+ brynq_sdk_bob/custom_tables.py,sha256=MvnR2mIcyK0rpwd0P7xV3BPIvCYQVEClBvo901GttPs,2642
5
+ brynq_sdk_bob/documents.py,sha256=ww101owiBGARCxOANdDtmWrNedSBe9V-BEed6QspQPg,1756
6
+ brynq_sdk_bob/employment.py,sha256=uNllQrIBbo8yPG_2-ln1PWeWUFU672T289PpWWvL-V8,763
7
+ brynq_sdk_bob/named_lists.py,sha256=ksLXV2ysBFegq4gZiiaC56gjkgdnPzL7WajZTGvjYIM,1069
8
+ brynq_sdk_bob/payments.py,sha256=HVXoMyA0Jyhyk5EcgCU2OQfdbMtElhdLfdBKgI9xC4Q,3922
9
+ brynq_sdk_bob/payroll_history.py,sha256=wHo6da7kLDe1ViL4egyMdyJBMZnWVhwjNjmh4cTCTeY,3972
10
+ brynq_sdk_bob/people.py,sha256=t1A1dABX6UZ0pyLTGOL-Sp5pHY630KWIyIO3JQ_Pjdk,5970
11
+ brynq_sdk_bob/reports.py,sha256=fiYzd6037siGl6c4KQLE7F4CeFRRDVDgSIVcWVVWsvI,1209
12
+ brynq_sdk_bob/salaries.py,sha256=BGQm-PT9QuKKJ9DP5nX6wmC8SZRAlm9M9I2EJhoZaII,1523
13
+ brynq_sdk_bob/timeoff.py,sha256=NbBZ39qy9D7jbS_z9bpmB-BKNuUGmNrkYTbEw034tZ0,3339
14
+ brynq_sdk_bob/work.py,sha256=0bVZkQ0I6z-z2_ql-EsOpFExx8VgsJvpcCQdOfiJYQM,712
15
+ brynq_sdk_bob/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ brynq_sdk_bob/schemas/bank.py,sha256=lDmXP4P8091N20fL2CmhPU2wFuaK60Z-p-dvYSNCMaQ,1846
17
+ brynq_sdk_bob/schemas/custom_tables.py,sha256=638NH2n50vHzw1LFlbKVCBKBhfSsfGqtEGuor0Z-QS4,1567
18
+ brynq_sdk_bob/schemas/employment.py,sha256=KUGbT8hjYuQIN-3BGlIrBBswXT6fSPkLtn5blT19xkY,3219
19
+ brynq_sdk_bob/schemas/named_lists.py,sha256=HJBRKrAI2vhrkq-5MVXqQcmpGNzFtoOnaZI2Ii_6_vs,725
20
+ brynq_sdk_bob/schemas/payments.py,sha256=LrSr8WApYxqbMDmhYmh0EISEbWJinWovsULZV6sItG0,4290
21
+ brynq_sdk_bob/schemas/payroll_history.py,sha256=JdAq0XaArHHEw8EsXo3GD0EhSAyBhPtYQMmdvjCiY8g,806
22
+ brynq_sdk_bob/schemas/people.py,sha256=Zs58mO4i8M-u-DLWRS0IHbyDNiwrhxSxOX8t3mLr2fw,40150
23
+ brynq_sdk_bob/schemas/salary.py,sha256=TSaM1g92y3oiDcUrfJW7ushgKZenI9xB6XW3kKuU0dE,4540
24
+ brynq_sdk_bob/schemas/timeoff.py,sha256=uswH42djiiWE2H0wXKHoneDvOJQ5BlaIeNdNl-kYd-s,4117
25
+ brynq_sdk_bob/schemas/work.py,sha256=YgtBJ0WXJOq55bFlT_kY_IbHh0SlQEtaa0W8vms-xA4,3048
26
+ brynq_sdk_bob-2.8.2.dist-info/METADATA,sha256=ulFwB84MiNowHTx0EF7zGUwi1OxfoZt2qMlJWXexwOA,371
27
+ brynq_sdk_bob-2.8.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
+ brynq_sdk_bob-2.8.2.dist-info/top_level.txt,sha256=oGiWqOuAAiVoLIzGe6F-Lo4IJBYz5ftOwBft7HtPuoY,14
29
+ brynq_sdk_bob-2.8.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.2)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,28 +0,0 @@
1
- brynq_sdk_bob/__init__.py,sha256=bw2Www_wgVxqZczJpz9K_94k7OdgqwWKF0aQaKG5TQE,3052
2
- brynq_sdk_bob/bank.py,sha256=5ncWsmeAvNoiLnVvIvPgFDRQ5DB5_VY2ipMC439GVHU,1460
3
- brynq_sdk_bob/company.py,sha256=rjOpkm0CZ1EeJ-jddBl36GrGKUQviC1ca1aUL2tl1_M,848
4
- brynq_sdk_bob/custom_tables.py,sha256=MvnR2mIcyK0rpwd0P7xV3BPIvCYQVEClBvo901GttPs,2642
5
- brynq_sdk_bob/documents.py,sha256=ww101owiBGARCxOANdDtmWrNedSBe9V-BEed6QspQPg,1756
6
- brynq_sdk_bob/employment.py,sha256=kBEKfUmKEw-A_FjC9fOuJqcsT7NxUKwXJs_V_-x9LbI,765
7
- brynq_sdk_bob/named_lists.py,sha256=ksLXV2ysBFegq4gZiiaC56gjkgdnPzL7WajZTGvjYIM,1069
8
- brynq_sdk_bob/payments.py,sha256=e1TnJcXlbotOfULukVUiaYZ1N-bDGynAtmPgpux7It0,5670
9
- brynq_sdk_bob/payroll_history.py,sha256=wHo6da7kLDe1ViL4egyMdyJBMZnWVhwjNjmh4cTCTeY,3972
10
- brynq_sdk_bob/people.py,sha256=6b7uCucl_xqtAq_4YJZU457fUY0qssMwfCCStzbNG0M,5036
11
- brynq_sdk_bob/salaries.py,sha256=8xq9XDTK473Az2MpuAPofz9CvZstjufSoWtF0bi1wC4,1766
12
- brynq_sdk_bob/timeoff.py,sha256=NbBZ39qy9D7jbS_z9bpmB-BKNuUGmNrkYTbEw034tZ0,3339
13
- brynq_sdk_bob/work.py,sha256=DzShGngMETPYP4TrMLm7pg6GmIft1rgBUZNyNFwxPsM,1488
14
- brynq_sdk_bob/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- brynq_sdk_bob/schemas/bank.py,sha256=Yfq-ouzcAyaqcZj5_lWLcaQgtLikI9zlcsHhsuPUQzY,1935
16
- brynq_sdk_bob/schemas/custom_tables.py,sha256=638NH2n50vHzw1LFlbKVCBKBhfSsfGqtEGuor0Z-QS4,1567
17
- brynq_sdk_bob/schemas/employment.py,sha256=uErbSyl8xYaYo5Hu0FZ7_tl1WCOaytcU9pjkIWJbmOo,3226
18
- brynq_sdk_bob/schemas/named_lists.py,sha256=HJBRKrAI2vhrkq-5MVXqQcmpGNzFtoOnaZI2Ii_6_vs,725
19
- brynq_sdk_bob/schemas/payments.py,sha256=LiFff0YwmgFi-6vHau1iuX2RZb42ZGX5keGL_Ai5Jlk,4294
20
- brynq_sdk_bob/schemas/payroll_history.py,sha256=JdAq0XaArHHEw8EsXo3GD0EhSAyBhPtYQMmdvjCiY8g,806
21
- brynq_sdk_bob/schemas/people.py,sha256=cy0mLKRdUhjOv52WB53AWd1XHn6mwrACGZAvK2ufsz4,38780
22
- brynq_sdk_bob/schemas/salary.py,sha256=7pq66_JfxmPbSWowX-25c-aKQvz3IGmoG5toRIq3H7g,4418
23
- brynq_sdk_bob/schemas/timeoff.py,sha256=BHImTTT4n8j7bF7T5Ue_B0WHmmj1_QTPV9TKAlHeBZM,4124
24
- brynq_sdk_bob/schemas/work.py,sha256=USjLR6vNhfCFb0Qul_A9cWru8wIzb370p5j1YxcDBFM,2804
25
- brynq_sdk_bob-2.6.2.dev13.dist-info/METADATA,sha256=fRJIgqWg-8uyqcNIhnBNrMOlwqdD3OXvf5sX-wRUyWY,408
26
- brynq_sdk_bob-2.6.2.dev13.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
27
- brynq_sdk_bob-2.6.2.dev13.dist-info/top_level.txt,sha256=oGiWqOuAAiVoLIzGe6F-Lo4IJBYz5ftOwBft7HtPuoY,14
28
- brynq_sdk_bob-2.6.2.dev13.dist-info/RECORD,,