brynq-sdk-bob 2.6.2.dev10__py3-none-any.whl → 2.9.4__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 +16 -9
- brynq_sdk_bob/bank.py +3 -15
- brynq_sdk_bob/employment.py +1 -3
- brynq_sdk_bob/payments.py +17 -51
- brynq_sdk_bob/people.py +59 -41
- brynq_sdk_bob/reports.py +38 -0
- brynq_sdk_bob/salaries.py +2 -9
- brynq_sdk_bob/schemas/bank.py +7 -8
- brynq_sdk_bob/schemas/employment.py +16 -15
- brynq_sdk_bob/schemas/payments.py +0 -2
- brynq_sdk_bob/schemas/people.py +21 -12
- brynq_sdk_bob/schemas/salary.py +0 -1
- brynq_sdk_bob/schemas/timeoff.py +192 -2
- brynq_sdk_bob/schemas/work.py +12 -8
- brynq_sdk_bob/timeoff.py +34 -3
- brynq_sdk_bob/work.py +2 -10
- {brynq_sdk_bob-2.6.2.dev10.dist-info → brynq_sdk_bob-2.9.4.dist-info}/METADATA +1 -2
- brynq_sdk_bob-2.9.4.dist-info/RECORD +29 -0
- brynq_sdk_bob-2.6.2.dev10.dist-info/RECORD +0 -28
- {brynq_sdk_bob-2.6.2.dev10.dist-info → brynq_sdk_bob-2.9.4.dist-info}/WHEEL +0 -0
- {brynq_sdk_bob-2.6.2.dev10.dist-info → brynq_sdk_bob-2.9.4.dist-info}/top_level.txt +0 -0
brynq_sdk_bob/__init__.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import base64
|
|
2
|
-
import
|
|
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,13 +15,15 @@ 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):
|
|
23
22
|
super().__init__()
|
|
24
23
|
self.timeout = 3600
|
|
24
|
+
self.test_environment = test_environment
|
|
25
25
|
self.headers = self._get_request_headers(system_type)
|
|
26
|
-
if test_environment:
|
|
26
|
+
if self.test_environment:
|
|
27
27
|
self.base_url = "https://api.sandbox.hibob.com/v1/"
|
|
28
28
|
else:
|
|
29
29
|
self.base_url = "https://api.hibob.com/v1/"
|
|
@@ -40,15 +40,22 @@ class Bob(BrynQ):
|
|
|
40
40
|
self.companies = Company(self)
|
|
41
41
|
self.named_lists = NamedLists(self)
|
|
42
42
|
self.custom_tables = CustomTables(self)
|
|
43
|
+
self.payroll_history = History(self)
|
|
44
|
+
self.reports = Reports(self)
|
|
43
45
|
self.data_interface_id = os.getenv("DATA_INTERFACE_ID")
|
|
44
46
|
self.debug = debug
|
|
45
47
|
|
|
46
48
|
def _get_request_headers(self, system_type):
|
|
47
49
|
credentials = self.interfaces.credentials.get(system='bob', system_type=system_type)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
# multiple creds possible, not fetched by environment test status, get first occurence
|
|
51
|
+
if isinstance(credentials, list):
|
|
52
|
+
credentials = next(
|
|
53
|
+
(
|
|
54
|
+
element for element in credentials
|
|
55
|
+
if element.get('data', {}).get('Test Environment') == self.test_environment
|
|
56
|
+
),
|
|
57
|
+
credentials[0]
|
|
58
|
+
)
|
|
52
59
|
auth_token = base64.b64encode(f"{credentials.get('data').get('User ID')}:{credentials.get('data').get('API Token')}".encode()).decode('utf-8')
|
|
53
60
|
headers = {
|
|
54
61
|
"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
|
|
24
|
-
resp = self.
|
|
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)
|
brynq_sdk_bob/employment.py
CHANGED
brynq_sdk_bob/payments.py
CHANGED
|
@@ -2,7 +2,6 @@ import time
|
|
|
2
2
|
from typing import List, Optional
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
|
-
from tqdm import tqdm
|
|
6
5
|
|
|
7
6
|
from brynq_sdk_functions import Functions
|
|
8
7
|
|
|
@@ -14,61 +13,31 @@ class Payments:
|
|
|
14
13
|
self.bob = bob
|
|
15
14
|
self.schema = VariablePaymentSchema
|
|
16
15
|
|
|
17
|
-
def
|
|
18
|
-
|
|
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
|
-
)
|
|
16
|
+
def get(self, person_ids: List[str]) -> (pd.DataFrame, pd.DataFrame):
|
|
17
|
+
records = []
|
|
48
18
|
|
|
49
|
-
|
|
19
|
+
for person_id in person_ids:
|
|
20
|
+
# Throttle requests to respect API rate limits
|
|
21
|
+
time.sleep(0.2)
|
|
50
22
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
23
|
+
resp = self.bob.session.get(
|
|
24
|
+
url=f"{self.bob.base_url}people/{person_id}/variable",
|
|
25
|
+
timeout=self.bob.timeout
|
|
26
|
+
)
|
|
55
27
|
resp.raise_for_status()
|
|
56
|
-
data = resp.json()
|
|
57
|
-
df = pd.concat([df, pd.json_normalize(
|
|
58
|
-
data,
|
|
59
|
-
record_path='values'
|
|
60
|
-
)])
|
|
61
|
-
df['employee_id'] = person_id
|
|
62
28
|
|
|
63
|
-
|
|
64
|
-
time.sleep(1.3)
|
|
29
|
+
data = resp.json()
|
|
65
30
|
|
|
66
|
-
|
|
31
|
+
# Normalize nested json and assign ID before appending to list
|
|
32
|
+
df_temp = pd.json_normalize(data, record_path='values')
|
|
33
|
+
df_temp['employee_id'] = person_id
|
|
34
|
+
records.append(df_temp)
|
|
67
35
|
|
|
68
|
-
#
|
|
69
|
-
df =
|
|
36
|
+
# Concatenate once to improve performance
|
|
37
|
+
df = pd.concat(records, ignore_index=True) if records else pd.DataFrame()
|
|
70
38
|
|
|
71
39
|
valid_payments, invalid_payments = Functions.validate_data(df=df, schema=self.schema, debug=True)
|
|
40
|
+
|
|
72
41
|
return valid_payments, invalid_payments
|
|
73
42
|
|
|
74
43
|
def get_actual_payments(
|
|
@@ -154,9 +123,6 @@ class Payments:
|
|
|
154
123
|
|
|
155
124
|
df = pd.json_normalize(all_results)
|
|
156
125
|
|
|
157
|
-
# Apply named list mappings
|
|
158
|
-
df = self._apply_named_list_mappings(df)
|
|
159
|
-
|
|
160
126
|
valid_payments, invalid_payments = Functions.validate_data(
|
|
161
127
|
df=df,
|
|
162
128
|
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
|
|
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
|
|
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":
|
|
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
|
-
|
|
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])
|
brynq_sdk_bob/reports.py
ADDED
|
@@ -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, report_format: str = "csv") -> 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": report_format})
|
|
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
|
|
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=
|
|
15
|
+
params={"limit": 100})
|
|
23
16
|
data = self.bob.get_paginated_result(request)
|
|
24
17
|
df = pd.json_normalize(
|
|
25
18
|
data,
|
brynq_sdk_bob/schemas/bank.py
CHANGED
|
@@ -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="
|
|
11
|
-
amount:
|
|
12
|
-
allocation:
|
|
13
|
-
branch_address:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
import pandera as pa
|
|
3
|
+
from typing import Optional
|
|
3
4
|
from pandera import Bool
|
|
4
5
|
from pandera.typing import Series, String, Float, DateTime
|
|
5
6
|
from brynq_sdk_functions import BrynQPanderaDataFrameModel
|
|
6
7
|
|
|
7
8
|
class EmploymentSchema(BrynQPanderaDataFrameModel):
|
|
8
9
|
id: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Employment ID", alias="id")
|
|
9
|
-
employee_id: Series[
|
|
10
|
+
employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="employeeId")
|
|
10
11
|
active_effective_date: Series[DateTime] = pa.Field(coerce=True, description="Active Effective Date", alias="activeEffectiveDate")
|
|
11
|
-
contract: Series[String] = pa.Field(coerce=True, nullable=True, description="Contract", alias="contract") # has a list of possible values
|
|
12
|
-
creation_date: Series[DateTime] = pa.Field(coerce=True, nullable=True, description="Creation Date", alias="creationDate")
|
|
12
|
+
contract: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Contract", alias="contract") # has a list of possible values
|
|
13
|
+
creation_date: Optional[Series[DateTime]] = pa.Field(coerce=True, nullable=True, description="Creation Date", alias="creationDate")
|
|
13
14
|
effective_date: Series[DateTime] = pa.Field(coerce=True, description="Effective Date", alias="effectiveDate")
|
|
14
|
-
end_effective_date: Series[DateTime] = pa.Field(coerce=True, nullable=True, description="End Effective Date", alias="endEffectiveDate")
|
|
15
|
+
end_effective_date: Optional[Series[DateTime]] = pa.Field(coerce=True, nullable=True, description="End Effective Date", alias="endEffectiveDate")
|
|
15
16
|
fte: Series[Float] = pa.Field(coerce=True, description="FTE", alias="fte")
|
|
16
17
|
is_current: Series[Bool] = pa.Field(coerce=True, description="Is Current", alias="isCurrent")
|
|
17
|
-
modification_date: Series[DateTime] = pa.Field(coerce=True, nullable=True, description="Modification Date", alias="modificationDate")
|
|
18
|
-
salary_pay_type: Series[String] = pa.Field(coerce=True, nullable=True, description="Salary Pay Type", alias="salaryPayType")
|
|
19
|
-
weekly_hours: Series[Float] = pa.Field(coerce=True, nullable=True, description="Weekly Hours", alias="weeklyHours")
|
|
18
|
+
modification_date: Optional[Series[DateTime]] = pa.Field(coerce=True, nullable=True, description="Modification Date", alias="modificationDate")
|
|
19
|
+
salary_pay_type: Optional[Series[String]] = pa.Field(coerce=True, nullable=True, description="Salary Pay Type", alias="salaryPayType")
|
|
20
|
+
weekly_hours: Optional[Series[Float]] = pa.Field(coerce=True, nullable=True, description="Weekly Hours", alias="weeklyHours")
|
|
20
21
|
# weekly_hours_sort_factor: Series[pd.Int64Dtype] = pa.Field(coerce=True, nullable=False)
|
|
21
|
-
actual_working_pattern_working_pattern_type: Series[pa.String] = pa.Field(nullable=True, description="Actual Working Pattern Working Pattern Type", alias="actualWorkingPattern.workingPatternType")
|
|
22
|
-
actual_working_pattern_days_sunday: Series[Float] = pa.Field(nullable=True, description="Actual Working Pattern Days Sunday", alias="actualWorkingPattern.days.sunday")
|
|
23
|
-
actual_working_pattern_days_tuesday: Series[Float] = pa.Field(nullable=True, description="Actual Working Pattern Days Tuesday", alias="actualWorkingPattern.days.tuesday")
|
|
24
|
-
actual_working_pattern_days_wednesday: Series[Float] = pa.Field(nullable=True, description="Actual Working Pattern Days Wednesday", alias="actualWorkingPattern.days.wednesday")
|
|
25
|
-
actual_working_pattern_days_monday: Series[Float] = pa.Field(nullable=True, description="Actual Working Pattern Days Monday", alias="actualWorkingPattern.days.monday")
|
|
26
|
-
actual_working_pattern_days_friday: Series[Float] = pa.Field(nullable=True, description="Actual Working Pattern Days Friday", alias="actualWorkingPattern.days.friday")
|
|
27
|
-
actual_working_pattern_days_thursday: Series[Float] = pa.Field(nullable=True, description="Actual Working Pattern Days Thursday", alias="actualWorkingPattern.days.thursday")
|
|
28
|
-
actual_working_pattern_days_saturday: Series[Float] = pa.Field(nullable=True, description="Actual Working Pattern Days Saturday", alias="actualWorkingPattern.days.saturday")
|
|
22
|
+
actual_working_pattern_working_pattern_type: Optional[Series[pa.String]] = pa.Field(nullable=True, description="Actual Working Pattern Working Pattern Type", alias="actualWorkingPattern.workingPatternType")
|
|
23
|
+
actual_working_pattern_days_sunday: Optional[Series[Float]] = pa.Field(nullable=True, description="Actual Working Pattern Days Sunday", alias="actualWorkingPattern.days.sunday")
|
|
24
|
+
actual_working_pattern_days_tuesday: Optional[Series[Float]] = pa.Field(nullable=True, description="Actual Working Pattern Days Tuesday", alias="actualWorkingPattern.days.tuesday")
|
|
25
|
+
actual_working_pattern_days_wednesday: Optional[Series[Float]] = pa.Field(nullable=True, description="Actual Working Pattern Days Wednesday", alias="actualWorkingPattern.days.wednesday")
|
|
26
|
+
actual_working_pattern_days_monday: Optional[Series[Float]] = pa.Field(nullable=True, description="Actual Working Pattern Days Monday", alias="actualWorkingPattern.days.monday")
|
|
27
|
+
actual_working_pattern_days_friday: Optional[Series[Float]] = pa.Field(nullable=True, description="Actual Working Pattern Days Friday", alias="actualWorkingPattern.days.friday")
|
|
28
|
+
actual_working_pattern_days_thursday: Optional[Series[Float]] = pa.Field(nullable=True, description="Actual Working Pattern Days Thursday", alias="actualWorkingPattern.days.thursday")
|
|
29
|
+
actual_working_pattern_days_saturday: Optional[Series[Float]] = pa.Field(nullable=True, description="Actual Working Pattern Days Saturday", alias="actualWorkingPattern.days.saturday")
|
|
29
30
|
|
|
30
31
|
class Config:
|
|
31
32
|
coerce = True
|
|
@@ -24,7 +24,6 @@ class VariablePaymentSchema(BrynQPanderaDataFrameModel):
|
|
|
24
24
|
amount_value: Optional[Series[Float]] = pa.Field(coerce=True, description="Amount Value", alias="amount.value")
|
|
25
25
|
amount_alternative_value: Optional[Series[Float]] = pa.Field(coerce=True, description="Amount Value", alias="amount")
|
|
26
26
|
amount_currency: Optional[Series[String]] = pa.Field(coerce=True, description="Amount Currency", alias="amount.currency")
|
|
27
|
-
change_reason: Series[String] = pa.Field(nullable=True, coerce=True, description="Change Reason", alias="change.reason")
|
|
28
27
|
change_changed_by: Series[String] = pa.Field(nullable=True, coerce=True, description="Change Changed By", alias="change.changedBy")
|
|
29
28
|
change_changed_by_id: Series[pd.Int64Dtype] = pa.Field(nullable=True, coerce=True, description="Change Changed By ID", alias="change.changedById")
|
|
30
29
|
employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="employee_id") #set manually
|
|
@@ -38,7 +37,6 @@ class ActualPaymentsSchema(BrynQPanderaDataFrameModel):
|
|
|
38
37
|
pay_type: Series[String] = pa.Field(coerce=True, description="Pay Type", alias="payType")
|
|
39
38
|
amount_value: Series[Float] = pa.Field(coerce=True, description="Amount Value", alias="amount.value")
|
|
40
39
|
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
40
|
change_changed_by: Series[String] = pa.Field(nullable=True, coerce=True, description="Change Changed By", alias="change.changedBy")
|
|
43
41
|
change_changed_by_id: Series[String] = pa.Field(nullable=True, coerce=True, description="Change Changed By ID", alias="change.changedById")
|
|
44
42
|
|
brynq_sdk_bob/schemas/people.py
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from typing import
|
|
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.
|
|
9
|
-
|
|
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")
|
|
29
|
+
personal_birth_date: Optional[Series[DateTime]] = pa.Field(coerce=True, nullable=True, 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=
|
|
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=
|
|
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[
|
|
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")
|
brynq_sdk_bob/schemas/salary.py
CHANGED
|
@@ -18,7 +18,6 @@ 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")
|
|
22
21
|
pay_period: Series[String] = pa.Field(coerce=True, nullable=True, description="Pay Period", alias="payPeriod")
|
|
23
22
|
base_value: Series[Float] = pa.Field(coerce=True, nullable=True, description="Base Value", alias="base.value") #needs to become base.value?
|
|
24
23
|
base_currency: Series[String] = pa.Field(coerce=True, nullable=True, description="Base Currency", alias="base.currency")
|
brynq_sdk_bob/schemas/timeoff.py
CHANGED
|
@@ -4,14 +4,20 @@ from typing import Optional
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
from brynq_sdk_functions import BrynQPanderaDataFrameModel
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
# =============================================================================
|
|
9
|
+
# TimeOffSchema - For /timeoff/requests/changes endpoint (change events)
|
|
10
|
+
# =============================================================================
|
|
11
|
+
|
|
7
12
|
class TimeOffSchema(BrynQPanderaDataFrameModel):
|
|
13
|
+
"""Schema for time off change events from /timeoff/requests/changes endpoint."""
|
|
8
14
|
change_type: Series[String] = pa.Field(coerce=True, description="Change Type", alias="changeType")
|
|
9
|
-
employee_id: Series[
|
|
15
|
+
employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="employeeId")
|
|
10
16
|
employee_display_name: Series[String] = pa.Field(coerce=True, description="Employee Display Name", alias="employeeDisplayName")
|
|
11
17
|
employee_email: Series[String] = pa.Field(coerce=True, description="Employee Email", alias="employeeEmail")
|
|
12
18
|
request_id: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Request ID", alias="requestId")
|
|
13
19
|
policy_type_display_name: Series[String] = pa.Field(coerce=True, description="Policy Type Display Name", alias="policyTypeDisplayName")
|
|
14
|
-
type: Series[String] = pa.Field(coerce=True, description="
|
|
20
|
+
type: Series[String] = pa.Field(coerce=True, description="Request type", alias="type")
|
|
15
21
|
start_date: Series[String] = pa.Field(coerce=True, nullable=True, description="Start Date", alias="startDate")
|
|
16
22
|
start_portion: Series[String] = pa.Field(coerce=True, nullable=True, description="Start Portion", alias="startPortion")
|
|
17
23
|
end_date: Series[String] = pa.Field(coerce=True, nullable=True, description="End Date", alias="endDate")
|
|
@@ -30,7 +36,191 @@ class TimeOffSchema(BrynQPanderaDataFrameModel):
|
|
|
30
36
|
coerce = True
|
|
31
37
|
|
|
32
38
|
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# TimeOffRequest - For /timeoff/employees/{id}/requests/{requestId} endpoint
|
|
41
|
+
# =============================================================================
|
|
42
|
+
|
|
43
|
+
class TimeOffRequest(BrynQPanderaDataFrameModel):
|
|
44
|
+
"""
|
|
45
|
+
Schema for time off request details from Bob API.
|
|
46
|
+
|
|
47
|
+
Based on: https://apidocs.hibob.com/reference/get_timeoff-employees-id-requests-requestid
|
|
48
|
+
|
|
49
|
+
Supports all request types (discriminated by 'type' field):
|
|
50
|
+
- days: Request for X days
|
|
51
|
+
- hours: Request for X hours during the day (policy types measured in hours)
|
|
52
|
+
- portionOnRange: Every morning or afternoon during days requested
|
|
53
|
+
- hoursOnRange: X hours every day during days requested
|
|
54
|
+
- differentDayDurations: Different hours on each day requested
|
|
55
|
+
- specificHoursDayDurations: Specific hours per day
|
|
56
|
+
- differentSpecificHoursDayDurations: Different specific hours on each day
|
|
57
|
+
- percentageOnRange: X percent of every day during days requested
|
|
58
|
+
- openEnded: Request without an end date yet
|
|
59
|
+
|
|
60
|
+
All type-specific fields are optional since they vary by request type.
|
|
61
|
+
|
|
62
|
+
Note: Complex nested fields (attachmentLinks, durations arrays) are not included
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
# -------------------------------------------------------------------------
|
|
66
|
+
# IDENTIFIERS
|
|
67
|
+
# -------------------------------------------------------------------------
|
|
68
|
+
employee_id: Series[String] = pa.Field(
|
|
69
|
+
coerce=True, description="Employee ID", alias="employeeId"
|
|
70
|
+
)
|
|
71
|
+
request_id: Series[pd.Int64Dtype] = pa.Field(
|
|
72
|
+
coerce=True, description="Time Off Request ID", alias="requestId"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# -------------------------------------------------------------------------
|
|
76
|
+
# REQUEST METADATA
|
|
77
|
+
# -------------------------------------------------------------------------
|
|
78
|
+
policy_type_display_name: Series[String] = pa.Field(
|
|
79
|
+
coerce=True, description="Display name of the policy type", alias="policyTypeDisplayName"
|
|
80
|
+
)
|
|
81
|
+
created_on: Series[String] = pa.Field(
|
|
82
|
+
coerce=True, description="Date and time the request was created", alias="createdOn"
|
|
83
|
+
)
|
|
84
|
+
description: Optional[Series[String]] = pa.Field(
|
|
85
|
+
nullable=True, coerce=True, description="Request description", alias="description"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# -------------------------------------------------------------------------
|
|
89
|
+
# TYPE DISCRIMINATOR
|
|
90
|
+
# Valid values: days, hours, portionOnRange, hoursOnRange, differentDayDurations,
|
|
91
|
+
# specificHoursDayDurations, differentSpecificHoursDayDurations,
|
|
92
|
+
# percentageOnRange, openEnded
|
|
93
|
+
# -------------------------------------------------------------------------
|
|
94
|
+
type: Series[String] = pa.Field(
|
|
95
|
+
coerce=True, description="Request type discriminator", alias="type"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# GENERAL INFO
|
|
99
|
+
duration_unit: Series[String] = pa.Field(
|
|
100
|
+
coerce=True, description="Unit for totalDuration/totalCost: 'days' or 'hours'", alias="durationUnit"
|
|
101
|
+
)
|
|
102
|
+
total_duration: Series[Float] = pa.Field(
|
|
103
|
+
coerce=True, description="Total time including regular days off", alias="totalDuration"
|
|
104
|
+
)
|
|
105
|
+
total_cost: Series[Float] = pa.Field(
|
|
106
|
+
coerce=True, description="Amount deducted from balance", alias="totalCost"
|
|
107
|
+
)
|
|
108
|
+
status: Series[String] = pa.Field(
|
|
109
|
+
coerce=True, description="Request status: approved, pending, canceled, etc.", alias="status"
|
|
110
|
+
)
|
|
111
|
+
approved: Series[pd.BooleanDtype] = pa.Field(
|
|
112
|
+
coerce=True, description="Whether request is approved", alias="approved"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
has_attachment: Series[pd.BooleanDtype] = pa.Field(
|
|
116
|
+
coerce=True, description="Whether request has attachments", alias="hasAttachment"
|
|
117
|
+
)
|
|
118
|
+
# Note: attachmentLinks array is not included (complex nested structure)
|
|
119
|
+
|
|
120
|
+
reason_code: Optional[Series[String]] = pa.Field(
|
|
121
|
+
nullable=True, coerce=True, description="Reason code from policy type's list", alias="reasonCode"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
previous_request_id: Optional[Series[pd.Int64Dtype]] = pa.Field(
|
|
125
|
+
nullable=True, coerce=True,
|
|
126
|
+
description="ID of replaced request when date/time updated", alias="previousRequestId"
|
|
127
|
+
)
|
|
128
|
+
original_request_id: Optional[Series[pd.Int64Dtype]] = pa.Field(
|
|
129
|
+
nullable=True, coerce=True,
|
|
130
|
+
description="ID of the very first request in history chain", alias="originalRequestId"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
approved_by: Optional[Series[String]] = pa.Field(
|
|
134
|
+
nullable=True, coerce=True, description="Who approved the request", alias="approvedBy"
|
|
135
|
+
)
|
|
136
|
+
approved_at: Optional[Series[String]] = pa.Field(
|
|
137
|
+
nullable=True, coerce=True, description="When request was approved", alias="approvedAt"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
declined_by: Optional[Series[String]] = pa.Field(
|
|
141
|
+
nullable=True, coerce=True, description="Who declined the request", alias="declinedBy"
|
|
142
|
+
)
|
|
143
|
+
declined_at: Optional[Series[String]] = pa.Field(
|
|
144
|
+
nullable=True, coerce=True, description="When request was declined", alias="declinedAt"
|
|
145
|
+
)
|
|
146
|
+
decline_reason: Optional[Series[String]] = pa.Field(
|
|
147
|
+
nullable=True, coerce=True, description="Why request was declined", alias="declineReason"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
visibility: Series[String] = pa.Field(
|
|
151
|
+
coerce=True, description="Visibility: 'Public', 'Private' or 'Custom name'", alias="visibility"
|
|
152
|
+
)
|
|
153
|
+
time_zone_offset: Optional[Series[String]] = pa.Field(
|
|
154
|
+
nullable=True, coerce=True,
|
|
155
|
+
description="GMT offset (e.g., 'GMT -5:00') for requests with specific times", alias="timeZoneOffset"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# -------------------------------------------------------------------------
|
|
159
|
+
# TYPE-SPECIFIC FIELDS (optional, presence depends on 'type' value)
|
|
160
|
+
# -------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
# For types: days, portionOnRange, hoursOnRange, differentDayDurations,
|
|
163
|
+
# specificHoursDayDurations, differentSpecificHoursDayDurations,
|
|
164
|
+
# percentageOnRange, openEnded
|
|
165
|
+
start_date: Optional[Series[String]] = pa.Field(
|
|
166
|
+
nullable=True, coerce=True, description="First day of time off", alias="startDate"
|
|
167
|
+
)
|
|
168
|
+
end_date: Optional[Series[String]] = pa.Field(
|
|
169
|
+
nullable=True, coerce=True, description="Last day of time off (null for openEnded)", alias="endDate"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# For types: days, openEnded
|
|
173
|
+
start_portion: Optional[Series[String]] = pa.Field(
|
|
174
|
+
nullable=True, coerce=True,
|
|
175
|
+
description="First day portion: all_day, morning, afternoon", alias="startPortion"
|
|
176
|
+
)
|
|
177
|
+
end_portion: Optional[Series[String]] = pa.Field(
|
|
178
|
+
nullable=True, coerce=True,
|
|
179
|
+
description="Last day portion: all_day, morning, afternoon (null for openEnded)", alias="endPortion"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# For type: hours
|
|
183
|
+
date: Optional[Series[String]] = pa.Field(
|
|
184
|
+
nullable=True, coerce=True, description="Date for single-day hours request", alias="date"
|
|
185
|
+
)
|
|
186
|
+
hours_on_date: Optional[Series[Float]] = pa.Field(
|
|
187
|
+
nullable=True, coerce=True, description="Hours for single-day request", alias="hoursOnDate"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# For type: portionOnRange
|
|
191
|
+
day_portion: Optional[Series[String]] = pa.Field(
|
|
192
|
+
nullable=True, coerce=True,
|
|
193
|
+
description="Portion for range: morning or afternoon", alias="dayPortion"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# For type: hoursOnRange
|
|
197
|
+
daily_hours: Optional[Series[Float]] = pa.Field(
|
|
198
|
+
nullable=True, coerce=True, description="Hours per day for range", alias="dailyHours"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# For type: percentageOnRange
|
|
202
|
+
percentage_of_day: Optional[Series[pd.Int64Dtype]] = pa.Field(
|
|
203
|
+
nullable=True, coerce=True, description="Percent of each day requested", alias="percentageOfDay"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# For types: specificHoursDayDurations, differentSpecificHoursDayDurations, openEnded
|
|
207
|
+
time_zone: Optional[Series[String]] = pa.Field(
|
|
208
|
+
nullable=True, coerce=True,
|
|
209
|
+
description="Time zone name (e.g., 'Europe/London')", alias="timeZone"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Note: 'durations' array is not included (complex nested structure with per-day details)
|
|
213
|
+
|
|
214
|
+
class Config:
|
|
215
|
+
coerce = True
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# =============================================================================
|
|
219
|
+
# TimeOffBalanceSchema - For /timeoff/employees/{id}/balance endpoint
|
|
220
|
+
# =============================================================================
|
|
221
|
+
|
|
33
222
|
class TimeOffBalanceSchema(BrynQPanderaDataFrameModel):
|
|
223
|
+
"""Schema for time off balance from /timeoff/employees/{id}/balance endpoint."""
|
|
34
224
|
employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="employeeId")
|
|
35
225
|
policy_type_name: Series[String] = pa.Field(coerce=True, description="Policy Type Name", alias="policyTypeName")
|
|
36
226
|
policy_type_display_name: Series[String] = pa.Field(coerce=True, description="Policy Type Display Name", alias="policyTypeDisplayName")
|
brynq_sdk_bob/schemas/work.py
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
1
5
|
import pandera as pa
|
|
2
6
|
from pandera.typing import Series
|
|
3
|
-
|
|
4
|
-
from datetime import datetime
|
|
7
|
+
|
|
5
8
|
from brynq_sdk_functions import BrynQPanderaDataFrameModel
|
|
6
9
|
|
|
10
|
+
|
|
7
11
|
class WorkSchema(BrynQPanderaDataFrameModel):
|
|
8
12
|
can_be_deleted: Series[pa.Bool] = pa.Field(coerce=True, description="Can Be Deleted", alias="canBeDeleted")
|
|
9
13
|
work_change_type: Series[str] = pa.Field(coerce=True, description="Work Change Type", alias="workChangeType")
|
|
@@ -18,14 +22,14 @@ class WorkSchema(BrynQPanderaDataFrameModel):
|
|
|
18
22
|
active_effective_date: Series[datetime] = pa.Field(coerce=True, nullable=True, description="Active Effective Date", alias="activeEffectiveDate")
|
|
19
23
|
department: Series[str] = pa.Field(coerce=True, nullable=True, description="Department", alias="department")
|
|
20
24
|
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")
|
|
22
25
|
change_changed_by: Series[str] = pa.Field(coerce=True, nullable=True, description="Change Changed By", alias="change.changedBy")
|
|
23
26
|
change_changed_by_id: Series[str] = pa.Field(coerce=True, nullable=True, description="Change Changed By ID", alias="change.changedById")
|
|
24
|
-
reports_to_id: Series[str] = pa.Field(coerce=True, nullable=True, description="Reports To ID", alias="reportsTo.id")
|
|
25
|
-
reports_to_first_name: Series[str] = pa.Field(coerce=True, nullable=True, description="Reports To First Name", alias="reportsTo.firstName")
|
|
26
|
-
reports_to_surname: Series[str] = pa.Field(coerce=True, nullable=True, description="Reports To Surname", alias="reportsTo.surname")
|
|
27
|
-
reports_to_email: Series[str] = pa.Field(coerce=True, nullable=True, description="Reports To Email", alias="reportsTo.email")
|
|
28
|
-
reports_to_display_name: Series[str] = pa.Field(coerce=True, nullable=True, description="Reports To Display Name", alias="reportsTo.displayName")
|
|
27
|
+
reports_to_id: Optional[Series[str]] = pa.Field(coerce=True, nullable=True, description="Reports To ID", alias="reportsTo.id")
|
|
28
|
+
reports_to_first_name: Optional[Series[str]] = pa.Field(coerce=True, nullable=True, description="Reports To First Name", alias="reportsTo.firstName")
|
|
29
|
+
reports_to_surname: Optional[Series[str]] = pa.Field(coerce=True, nullable=True, description="Reports To Surname", alias="reportsTo.surname")
|
|
30
|
+
reports_to_email: Optional[Series[str]] = pa.Field(coerce=True, nullable=True, description="Reports To Email", alias="reportsTo.email")
|
|
31
|
+
reports_to_display_name: Optional[Series[str]] = pa.Field(coerce=True, nullable=True, description="Reports To Display Name", alias="reportsTo.displayName")
|
|
32
|
+
reports_to: Optional[Series[pd.Int64Dtype]] = pa.Field(coerce=True, nullable=True, description="Reports To", alias="reportsTo")
|
|
29
33
|
employee_id: Series[str] = pa.Field(coerce=True, description="Employee ID", alias="employeeId")
|
|
30
34
|
|
|
31
35
|
class Config:
|
brynq_sdk_bob/timeoff.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from datetime import datetime, timezone, timedelta
|
|
2
|
+
from typing import Union
|
|
2
3
|
import pandas as pd
|
|
3
4
|
from brynq_sdk_functions import Functions
|
|
4
|
-
from .schemas.timeoff import TimeOffSchema, TimeOffBalanceSchema
|
|
5
|
+
from .schemas.timeoff import TimeOffSchema, TimeOffBalanceSchema, TimeOffRequest
|
|
5
6
|
import warnings
|
|
6
7
|
|
|
7
8
|
|
|
@@ -40,13 +41,43 @@ class TimeOff:
|
|
|
40
41
|
params={'since': since, 'includePending': 'true' if include_pending else 'false'},
|
|
41
42
|
timeout=self.bob.timeout)
|
|
42
43
|
resp.raise_for_status()
|
|
43
|
-
data = resp.json()
|
|
44
|
-
# data = self.bob.get_paginated_result(request)
|
|
44
|
+
data = resp.json().get('changes', [])
|
|
45
45
|
df = pd.DataFrame(data)
|
|
46
46
|
valid_timeoff, invalid_timeoff = Functions.validate_data(df=df, schema=self.schema, debug=True)
|
|
47
47
|
|
|
48
48
|
return valid_timeoff, invalid_timeoff
|
|
49
49
|
|
|
50
|
+
def get_by_request_id(
|
|
51
|
+
self,
|
|
52
|
+
employee_id: Union[str, int],
|
|
53
|
+
request_id: Union[str, int],
|
|
54
|
+
) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
55
|
+
"""
|
|
56
|
+
Get time off request details by request ID.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
employee_id: The Employee ID (from database or Bob URL).
|
|
60
|
+
Example: "3332883884017713238" from URL "https://app.hibob.com/employee-profile/3332883884017713238"
|
|
61
|
+
request_id: The time off request ID.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
tuple[pd.DataFrame, pd.DataFrame]: (valid_request, invalid_request) as single-row DataFrames.
|
|
65
|
+
"""
|
|
66
|
+
resp = self.bob.session.get(
|
|
67
|
+
url=f"{self.bob.base_url}timeoff/employees/{employee_id}/requests/{request_id}",
|
|
68
|
+
timeout=self.bob.timeout
|
|
69
|
+
)
|
|
70
|
+
resp.raise_for_status()
|
|
71
|
+
data = resp.json()
|
|
72
|
+
|
|
73
|
+
# Single request returns a dict, wrap in list for DataFrame
|
|
74
|
+
df = pd.DataFrame([data])
|
|
75
|
+
|
|
76
|
+
valid_request, invalid_request = Functions.validate_data(df=df, schema=TimeOffRequest, debug=True)
|
|
77
|
+
|
|
78
|
+
return valid_request, invalid_request
|
|
79
|
+
|
|
80
|
+
|
|
50
81
|
def get_balance(self, employee_id: str, policy_type: str = None, as_of_date: str = None) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
51
82
|
"""
|
|
52
83
|
Get time off balance for a specific employee
|
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,16 +9,9 @@ class Work:
|
|
|
10
9
|
self.bob = bob
|
|
11
10
|
self.schema = WorkSchema
|
|
12
11
|
|
|
13
|
-
def get(self
|
|
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) ->(pd.DataFrame, pd.DataFrame):
|
|
20
13
|
request = requests.Request(method='GET',
|
|
21
|
-
url=f"{self.bob.base_url}bulk/people/work"
|
|
22
|
-
params=params)
|
|
14
|
+
url=f"{self.bob.base_url}bulk/people/work")
|
|
23
15
|
data = self.bob.get_paginated_result(request)
|
|
24
16
|
df = pd.json_normalize(
|
|
25
17
|
data,
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brynq_sdk_bob
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.4
|
|
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=AFIEA5akwZqu-USQbMt26Rp8krqumvOsG1J83jhMQSg,3470
|
|
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=43Ctdt5T8gtpCud67dm75xa90W8m6FyMrU2hAWKrzMc,4221
|
|
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=Tawmqm_ZmQ487loyk-29-A_fTCrgImbWCEf6zfwuaq4,1245
|
|
12
|
+
brynq_sdk_bob/salaries.py,sha256=BGQm-PT9QuKKJ9DP5nX6wmC8SZRAlm9M9I2EJhoZaII,1523
|
|
13
|
+
brynq_sdk_bob/timeoff.py,sha256=JtTu14PWFqQIEn9r-Z8ipeNE-5p7hqPz5N6wjjBeLTs,4438
|
|
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=1LsPjp-TexEc4B9nXjciMKFw94bl6xhEp3TNsgnLcds,3387
|
|
19
|
+
brynq_sdk_bob/schemas/named_lists.py,sha256=HJBRKrAI2vhrkq-5MVXqQcmpGNzFtoOnaZI2Ii_6_vs,725
|
|
20
|
+
brynq_sdk_bob/schemas/payments.py,sha256=izFY-xZLk54JhdgrzLocjT1vfYUEVkehw_ZwjR1-Qbw,4040
|
|
21
|
+
brynq_sdk_bob/schemas/payroll_history.py,sha256=JdAq0XaArHHEw8EsXo3GD0EhSAyBhPtYQMmdvjCiY8g,806
|
|
22
|
+
brynq_sdk_bob/schemas/people.py,sha256=42BJVgJmT-h5kzuQl6iI7wZDSGNA0KTQQVIAqeeyHNk,40149
|
|
23
|
+
brynq_sdk_bob/schemas/salary.py,sha256=7pq66_JfxmPbSWowX-25c-aKQvz3IGmoG5toRIq3H7g,4418
|
|
24
|
+
brynq_sdk_bob/schemas/timeoff.py,sha256=gTYu_bNcfHrkTz4eIHCZ4WzgMTj2U4nI3X6JTzDovhk,12817
|
|
25
|
+
brynq_sdk_bob/schemas/work.py,sha256=1odd3ia97SZff8VjLzL1a0FEQaF2ojGeWsfovcWvkhM,3017
|
|
26
|
+
brynq_sdk_bob-2.9.4.dist-info/METADATA,sha256=QZFoC1aZR74xLxLMFdqyVBlKN1a9VThkausO5vr3XMs,371
|
|
27
|
+
brynq_sdk_bob-2.9.4.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
28
|
+
brynq_sdk_bob-2.9.4.dist-info/top_level.txt,sha256=oGiWqOuAAiVoLIzGe6F-Lo4IJBYz5ftOwBft7HtPuoY,14
|
|
29
|
+
brynq_sdk_bob-2.9.4.dist-info/RECORD,,
|
|
@@ -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=IADipEuI_ofhhLZZV9oI8PV10VN07yluPPkLZT19Ze8,1013
|
|
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=N4Ylommed-hsw0aX7rjuZuVZQc4XU0iDBdSIxz8eAxc,4292
|
|
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=TSaM1g92y3oiDcUrfJW7ushgKZenI9xB6XW3kKuU0dE,4540
|
|
23
|
-
brynq_sdk_bob/schemas/timeoff.py,sha256=BHImTTT4n8j7bF7T5Ue_B0WHmmj1_QTPV9TKAlHeBZM,4124
|
|
24
|
-
brynq_sdk_bob/schemas/work.py,sha256=klzJtQf-avwhOkRHuPTM-jMhTIsroDPE56da1H5xaYs,2926
|
|
25
|
-
brynq_sdk_bob-2.6.2.dev10.dist-info/METADATA,sha256=hlLk5XIWFqHhiKBwQb8E-EPiyr34wWndRQvBxcSP5_M,408
|
|
26
|
-
brynq_sdk_bob-2.6.2.dev10.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
27
|
-
brynq_sdk_bob-2.6.2.dev10.dist-info/top_level.txt,sha256=oGiWqOuAAiVoLIzGe6F-Lo4IJBYz5ftOwBft7HtPuoY,14
|
|
28
|
-
brynq_sdk_bob-2.6.2.dev10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|