brynq-sdk-bob 2.9.5__tar.gz → 2.10.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/PKG-INFO +4 -1
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/__init__.py +3 -4
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/bank.py +7 -7
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/company.py +4 -4
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/custom_tables.py +8 -8
- brynq_sdk_bob-2.10.1/brynq_sdk_bob/documents.py +37 -0
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/employment.py +8 -7
- brynq_sdk_bob-2.10.1/brynq_sdk_bob/entitlements.py +37 -0
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/named_lists.py +8 -9
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/payments.py +14 -13
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/payroll_history.py +16 -17
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/people.py +15 -16
- brynq_sdk_bob-2.10.1/brynq_sdk_bob/reports.py +41 -0
- brynq_sdk_bob-2.10.1/brynq_sdk_bob/salaries.py +38 -0
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/bank.py +2 -2
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/custom_tables.py +2 -2
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/employment.py +9 -9
- brynq_sdk_bob-2.10.1/brynq_sdk_bob/schemas/entitlements.py +110 -0
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/named_lists.py +1 -1
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/payments.py +3 -2
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/payroll_history.py +1 -1
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/people.py +1 -1
- brynq_sdk_bob-2.10.1/brynq_sdk_bob/schemas/reports.py +129 -0
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/salary.py +3 -4
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/timeoff.py +2 -2
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/work.py +1 -1
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/timeoff.py +13 -13
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/work.py +8 -7
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob.egg-info/PKG-INFO +4 -1
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob.egg-info/SOURCES.txt +3 -0
- brynq_sdk_bob-2.10.1/brynq_sdk_bob.egg-info/requires.txt +5 -0
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/setup.py +4 -1
- brynq_sdk_bob-2.9.5/brynq_sdk_bob/documents.py +0 -47
- brynq_sdk_bob-2.9.5/brynq_sdk_bob/reports.py +0 -38
- brynq_sdk_bob-2.9.5/brynq_sdk_bob/salaries.py +0 -38
- brynq_sdk_bob-2.9.5/brynq_sdk_bob.egg-info/requires.txt +0 -2
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob/schemas/__init__.py +0 -0
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob.egg-info/dependency_links.txt +0 -0
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob.egg-info/not-zip-safe +0 -0
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/brynq_sdk_bob.egg-info/top_level.txt +0 -0
- {brynq_sdk_bob-2.9.5 → brynq_sdk_bob-2.10.1}/setup.cfg +0 -0
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brynq_sdk_bob
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.10.1
|
|
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
|
+
Requires-Dist: brynq-sdk-functions>=2.0.5
|
|
9
10
|
Requires-Dist: pandas<3.0.0,>=2.2.0
|
|
11
|
+
Requires-Dist: pydantic<3.0.0,>=2.5.0
|
|
12
|
+
Requires-Dist: pandera<1.0.0,>=0.16.0
|
|
10
13
|
Dynamic: author
|
|
11
14
|
Dynamic: author-email
|
|
12
15
|
Dynamic: description
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import os
|
|
3
3
|
from typing import List, Literal, Optional
|
|
4
|
-
|
|
5
4
|
import requests
|
|
6
5
|
from requests.adapters import HTTPAdapter
|
|
7
6
|
from urllib3.util.retry import Retry
|
|
8
|
-
|
|
9
7
|
from brynq_sdk_brynq import BrynQ
|
|
10
|
-
|
|
11
8
|
from .bank import Bank
|
|
12
9
|
from .company import Company
|
|
13
|
-
from .custom_tables import CustomTables
|
|
14
10
|
from .documents import CustomDocuments
|
|
15
11
|
from .employment import Employment
|
|
16
12
|
from .named_lists import NamedLists
|
|
@@ -21,6 +17,8 @@ from .reports import Reports
|
|
|
21
17
|
from .salaries import Salaries
|
|
22
18
|
from .timeoff import TimeOff
|
|
23
19
|
from .work import Work
|
|
20
|
+
from .custom_tables import CustomTables
|
|
21
|
+
from .entitlements import Entitlements
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
class Bob(BrynQ):
|
|
@@ -57,6 +55,7 @@ class Bob(BrynQ):
|
|
|
57
55
|
self.custom_tables = CustomTables(self)
|
|
58
56
|
self.payroll_history = History(self)
|
|
59
57
|
self.reports = Reports(self)
|
|
58
|
+
self.entitlements = Entitlements(self)
|
|
60
59
|
self.data_interface_id = os.getenv("DATA_INTERFACE_ID")
|
|
61
60
|
self.debug = debug
|
|
62
61
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
from brynq_sdk_functions import Functions
|
|
3
|
-
from .schemas.bank import
|
|
3
|
+
from .schemas.bank import BankGet
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class Bank:
|
|
7
|
-
def __init__(self,
|
|
8
|
-
self.
|
|
9
|
-
self.schema =
|
|
7
|
+
def __init__(self, client):
|
|
8
|
+
self.client = client
|
|
9
|
+
self.schema = BankGet
|
|
10
10
|
|
|
11
|
-
def get(self, person_ids: pd.Series, field_selection: list[str] = []) ->
|
|
11
|
+
def get(self, person_ids: pd.Series, field_selection: list[str] = []) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
12
12
|
data = []
|
|
13
13
|
for person_id in person_ids:
|
|
14
|
-
resp = self.
|
|
14
|
+
resp = self.client.session.get(url=f"{self.client.base_url}people/{person_id}/bank-accounts", timeout=self.client.timeout)
|
|
15
15
|
resp.raise_for_status()
|
|
16
16
|
temp_data = resp.json()['values']
|
|
17
17
|
# when an employee has one or more bank accounts, the response is a list of dictionaries.
|
|
@@ -21,6 +21,6 @@ class Bank:
|
|
|
21
21
|
|
|
22
22
|
df = pd.DataFrame(data)
|
|
23
23
|
|
|
24
|
-
valid_banks, invalid_banks = Functions.validate_data(df=df, schema=
|
|
24
|
+
valid_banks, invalid_banks = Functions.validate_data(df=df, schema=BankGet, debug=True)
|
|
25
25
|
|
|
26
26
|
return valid_banks, invalid_banks
|
|
@@ -2,19 +2,19 @@ import pandas as pd
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class Company:
|
|
5
|
-
def __init__(self,
|
|
6
|
-
self.
|
|
5
|
+
def __init__(self, client):
|
|
6
|
+
self.client = client
|
|
7
7
|
|
|
8
8
|
def get_variable_values(self, list_name: str = None) -> dict:
|
|
9
9
|
values = {}
|
|
10
10
|
|
|
11
11
|
if list_name is not None:
|
|
12
|
-
resp = self.
|
|
12
|
+
resp = self.client.session.get(url=f"{self.client.base_url}company/named-lists/{list_name}", timeout=self.client.timeout)
|
|
13
13
|
resp.raise_for_status()
|
|
14
14
|
data = resp.json()
|
|
15
15
|
values.update({data["name"]: [value['id'] for value in data['values']]})
|
|
16
16
|
else:
|
|
17
|
-
resp = self.
|
|
17
|
+
resp = self.client.session.get(url=f"{self.client.base_url}company/named-lists", timeout=self.client.timeout)
|
|
18
18
|
resp.raise_for_status()
|
|
19
19
|
data = resp.json()
|
|
20
20
|
for list_key, list_data in data.items():
|
|
@@ -4,13 +4,13 @@ import pandas as pd
|
|
|
4
4
|
|
|
5
5
|
from brynq_sdk_functions import BrynQPanderaDataFrameModel, Functions
|
|
6
6
|
|
|
7
|
-
from .schemas.custom_tables import
|
|
7
|
+
from .schemas.custom_tables import CustomTableMetadataGet, CustomTableGet
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class CustomTables:
|
|
11
|
-
def __init__(self,
|
|
12
|
-
self.
|
|
13
|
-
self.schema =
|
|
11
|
+
def __init__(self, client):
|
|
12
|
+
self.client = client
|
|
13
|
+
self.schema = CustomTableGet
|
|
14
14
|
|
|
15
15
|
def get(self, employee_ids: List[str], custom_table_id: str, schema_custom_fields: Optional[BrynQPanderaDataFrameModel] = None) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
16
16
|
"""
|
|
@@ -26,7 +26,7 @@ class CustomTables:
|
|
|
26
26
|
"""
|
|
27
27
|
df = pd.DataFrame()
|
|
28
28
|
for employee_id in employee_ids:
|
|
29
|
-
resp = self.
|
|
29
|
+
resp = self.client.session.get(url=f"{self.client.base_url}people/custom-tables/{employee_id}/{custom_table_id}", timeout=self.client.timeout)
|
|
30
30
|
resp.raise_for_status()
|
|
31
31
|
data = resp.json()
|
|
32
32
|
|
|
@@ -53,8 +53,8 @@ class CustomTables:
|
|
|
53
53
|
Returns:
|
|
54
54
|
A tuple of (valid_data, invalid_data) as pandas DataFrames containing table and column metadata
|
|
55
55
|
"""
|
|
56
|
-
url = f"{self.
|
|
57
|
-
resp = self.
|
|
56
|
+
url = f"{self.client.base_url}people/custom-tables/metadata"
|
|
57
|
+
resp = self.client.session.get(url=url)
|
|
58
58
|
resp.raise_for_status()
|
|
59
59
|
data = resp.json()
|
|
60
60
|
|
|
@@ -82,6 +82,6 @@ class CustomTables:
|
|
|
82
82
|
df = pd.DataFrame(rows)
|
|
83
83
|
|
|
84
84
|
# Validate against the metadata schema
|
|
85
|
-
valid_data, invalid_data = Functions.validate_data(df=df, schema=
|
|
85
|
+
valid_data, invalid_data = Functions.validate_data(df=df, schema=CustomTableMetadataGet, debug=True)
|
|
86
86
|
|
|
87
87
|
return valid_data, invalid_data
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from io import BytesIO
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from brynq_sdk_functions import Functions
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CustomDocuments:
|
|
9
|
+
def __init__(self, client):
|
|
10
|
+
self.client = client
|
|
11
|
+
|
|
12
|
+
def get(self, person_id: datetime) -> pd.DataFrame:
|
|
13
|
+
resp = self.client.session.get(url=f"{self.client.base_url}docs/people/{person_id}", timeout=self.client.timeout)
|
|
14
|
+
resp.raise_for_status()
|
|
15
|
+
data = resp.json()['documents']
|
|
16
|
+
df = pd.DataFrame(data)
|
|
17
|
+
df = self.client.rename_camel_columns_to_snake_case(df)
|
|
18
|
+
|
|
19
|
+
return df
|
|
20
|
+
|
|
21
|
+
def get_folders(self) -> dict:
|
|
22
|
+
resp = self.client.session.get(url=f"{self.client.base_url}docs/folders/metadata", timeout=self.client.timeout)
|
|
23
|
+
resp.raise_for_status()
|
|
24
|
+
data = resp.json()
|
|
25
|
+
|
|
26
|
+
return data
|
|
27
|
+
|
|
28
|
+
def create(self,
|
|
29
|
+
person_id: datetime,
|
|
30
|
+
folder_id: str,
|
|
31
|
+
file_name: str,
|
|
32
|
+
file_object: BytesIO):
|
|
33
|
+
files = {"file": (file_name, file_object, "application/pdf")}
|
|
34
|
+
resp = self.client.session.post(url=f"{self.client.base_url}docs/people/{person_id}/folders/{folder_id}/upload",
|
|
35
|
+
files=files,
|
|
36
|
+
timeout=self.client.timeout)
|
|
37
|
+
resp.raise_for_status()
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
import requests
|
|
3
|
-
from .schemas.employment import
|
|
3
|
+
from .schemas.employment import EmploymentGet
|
|
4
4
|
from brynq_sdk_functions import Functions
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class Employment:
|
|
8
|
-
def __init__(self,
|
|
9
|
-
self.
|
|
10
|
-
self.schema =
|
|
8
|
+
def __init__(self, client):
|
|
9
|
+
self.client = client
|
|
10
|
+
self.schema = EmploymentGet
|
|
11
11
|
|
|
12
|
-
def get(self) ->
|
|
12
|
+
def get(self) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
13
13
|
request = requests.Request(method='GET',
|
|
14
|
-
url=f"{self.
|
|
15
|
-
|
|
14
|
+
url=f"{self.client.base_url}bulk/people/employment",
|
|
15
|
+
params={"includeArchived": "true"})
|
|
16
|
+
data = self.client.get_paginated_result(request)
|
|
16
17
|
df = pd.json_normalize(
|
|
17
18
|
data,
|
|
18
19
|
record_path='values',
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from brynq_sdk_functions import Functions
|
|
4
|
+
from brynq_sdk_bob.schemas.entitlements import EntitlementReportGet
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Entitlements:
|
|
8
|
+
def __init__(self, client):
|
|
9
|
+
self.client = client
|
|
10
|
+
|
|
11
|
+
def get(self, report_id: str | None = None, debug: bool = False) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
12
|
+
"""
|
|
13
|
+
Get entitlement history data from the Bob reports.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
report_id: Optional report ID. If not provided, will search for 'Entitlement history' report.
|
|
17
|
+
debug: Whether to print debug information.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Tuple of (valid_entitlements, invalid_entitlements) DataFrames
|
|
21
|
+
"""
|
|
22
|
+
reports_df, _ = self.client.reports.get()
|
|
23
|
+
if report_id is None:
|
|
24
|
+
entitlement_report = reports_df[reports_df['name'] == 'Entitlement history']
|
|
25
|
+
if entitlement_report.empty:
|
|
26
|
+
raise ValueError("Report 'Entitlement history' not found.")
|
|
27
|
+
report_id = entitlement_report['id'].iloc[0]
|
|
28
|
+
|
|
29
|
+
report_data = self.client.reports.download(report_id=report_id)
|
|
30
|
+
df_raw = pd.json_normalize(report_data, record_path='employees')
|
|
31
|
+
valid_entitlements, invalid_entitlements = Functions.validate_data(
|
|
32
|
+
df=df_raw,
|
|
33
|
+
schema=EntitlementReportGet,
|
|
34
|
+
debug=debug,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
return valid_entitlements, invalid_entitlements
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
import pandas as pd
|
|
3
3
|
from brynq_sdk_functions import Functions
|
|
4
|
-
from .schemas.named_lists import
|
|
4
|
+
from .schemas.named_lists import NamedListGet
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class NamedLists:
|
|
8
|
-
def __init__(self,
|
|
9
|
-
self.
|
|
10
|
-
self.schema =
|
|
8
|
+
def __init__(self, client):
|
|
9
|
+
self.client = client
|
|
10
|
+
self.schema = NamedListGet
|
|
11
11
|
|
|
12
|
-
def get(self) ->
|
|
12
|
+
def get(self) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
13
13
|
"""
|
|
14
14
|
Get custom table data for an employee
|
|
15
15
|
|
|
@@ -19,8 +19,8 @@ class NamedLists:
|
|
|
19
19
|
Returns:
|
|
20
20
|
A tuple of (valid_data, invalid_data) as pandas DataFrames
|
|
21
21
|
"""
|
|
22
|
-
url = f"{self.
|
|
23
|
-
resp = self.
|
|
22
|
+
url = f"{self.client.base_url}company/named-lists/"
|
|
23
|
+
resp = self.client.session.get(url=url)
|
|
24
24
|
resp.raise_for_status()
|
|
25
25
|
data = resp.json()
|
|
26
26
|
|
|
@@ -31,7 +31,6 @@ class NamedLists:
|
|
|
31
31
|
])
|
|
32
32
|
|
|
33
33
|
# Normalize the nested JSON response
|
|
34
|
-
|
|
35
|
-
valid_data, invalid_data = Functions.validate_data(df=df, schema=NamedListSchema, debug=True)
|
|
34
|
+
valid_data, invalid_data = Functions.validate_data(df=df, schema=NamedListGet, debug=True)
|
|
36
35
|
|
|
37
36
|
return valid_data, invalid_data
|
|
@@ -5,24 +5,25 @@ import pandas as pd
|
|
|
5
5
|
|
|
6
6
|
from brynq_sdk_functions import Functions
|
|
7
7
|
|
|
8
|
-
from .schemas.payments import
|
|
8
|
+
from .schemas.payments import ActualPaymentsGet, VariablePaymentGet
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Payments:
|
|
12
|
-
def __init__(self,
|
|
13
|
-
self.
|
|
14
|
-
self.schema =
|
|
12
|
+
def __init__(self, client):
|
|
13
|
+
self.client = client
|
|
14
|
+
self.schema = VariablePaymentGet
|
|
15
15
|
|
|
16
|
-
def get(self, person_ids: List[str]) ->
|
|
16
|
+
def get(self, person_ids: List[str]) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
17
17
|
records = []
|
|
18
18
|
|
|
19
19
|
for person_id in person_ids:
|
|
20
20
|
# Throttle requests to respect API rate limits
|
|
21
21
|
time.sleep(0.2)
|
|
22
22
|
|
|
23
|
-
resp = self.
|
|
24
|
-
url=f"{self.
|
|
25
|
-
timeout=self.
|
|
23
|
+
resp = self.client.session.get(
|
|
24
|
+
url=f"{self.client.base_url}people/{person_id}/variable",
|
|
25
|
+
timeout=self.client.timeout,
|
|
26
|
+
params={"includeArchived": "true"}
|
|
26
27
|
)
|
|
27
28
|
resp.raise_for_status()
|
|
28
29
|
|
|
@@ -46,7 +47,7 @@ class Payments:
|
|
|
46
47
|
employee_ids: Optional[List[str]] = None,
|
|
47
48
|
pay_date_from: Optional[str] = None,
|
|
48
49
|
pay_date_to: Optional[str] = None
|
|
49
|
-
) ->
|
|
50
|
+
) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
50
51
|
"""
|
|
51
52
|
Search for actual payments with optional employee and pay date filters.
|
|
52
53
|
This method auto-paginates until all results are fetched.
|
|
@@ -101,10 +102,10 @@ class Payments:
|
|
|
101
102
|
if next_cursor:
|
|
102
103
|
payload["pagination"]["cursor"] = next_cursor
|
|
103
104
|
|
|
104
|
-
resp = self.
|
|
105
|
-
url=f"{self.
|
|
105
|
+
resp = self.client.session.post(
|
|
106
|
+
url=f"{self.client.base_url}people/actual-payments/search",
|
|
106
107
|
json=payload,
|
|
107
|
-
timeout=self.
|
|
108
|
+
timeout=self.client.timeout
|
|
108
109
|
)
|
|
109
110
|
resp.raise_for_status()
|
|
110
111
|
data = resp.json()
|
|
@@ -125,7 +126,7 @@ class Payments:
|
|
|
125
126
|
|
|
126
127
|
valid_payments, invalid_payments = Functions.validate_data(
|
|
127
128
|
df=df,
|
|
128
|
-
schema=
|
|
129
|
+
schema=ActualPaymentsGet,
|
|
129
130
|
debug=True
|
|
130
131
|
)
|
|
131
132
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
from brynq_sdk_functions import Functions
|
|
3
|
-
from .schemas.people import
|
|
3
|
+
from .schemas.people import PeopleGet
|
|
4
4
|
|
|
5
5
|
class History:
|
|
6
|
-
def __init__(self,
|
|
7
|
-
self.
|
|
8
|
-
self.schema =
|
|
6
|
+
def __init__(self, client):
|
|
7
|
+
self.client = client
|
|
8
|
+
self.schema = PeopleGet
|
|
9
9
|
self.field_name_in_body, self.field_name_in_response, self.endpoint_to_response = self._init_fields()
|
|
10
10
|
|
|
11
11
|
def get(self, additional_fields: list[str] = [], field_selection: list[str] = []) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
@@ -16,7 +16,6 @@ class History:
|
|
|
16
16
|
additional_fields (list[str]): Additional fields to get (not defined in the schema)
|
|
17
17
|
field_selection (list[str]): Fields to get (defined in the schema), if not provided, all fields are returned
|
|
18
18
|
"""
|
|
19
|
-
#resp = self.bob.session.get(url=f"{self.bob.base_url}profiles", timeout=self.bob.timeout)
|
|
20
19
|
body_fields = list(set(self.field_name_in_body + additional_fields))
|
|
21
20
|
response_fields = list(set(self.field_name_in_response + additional_fields))
|
|
22
21
|
|
|
@@ -25,25 +24,25 @@ class History:
|
|
|
25
24
|
response_fields = [self.endpoint_to_response.get(field) for field in field_selection if field in self.endpoint_to_response]
|
|
26
25
|
|
|
27
26
|
# Bob sucks with default fields so you need to do a search call to retrieve additional fields.
|
|
28
|
-
resp_additional_fields = self.
|
|
27
|
+
resp_additional_fields = self.client.session.post(url=f"{self.client.base_url}people/search",
|
|
29
28
|
json={
|
|
30
29
|
"fields": body_fields,
|
|
31
30
|
"filters": []
|
|
32
31
|
},
|
|
33
|
-
timeout=self.
|
|
32
|
+
timeout=self.client.timeout)
|
|
34
33
|
json_response = resp_additional_fields.json()
|
|
35
34
|
df = pd.json_normalize(resp_additional_fields.json()['employees'])
|
|
36
35
|
df = df[[col for col in response_fields if col in df.columns]]
|
|
37
|
-
# Get the valid column names from
|
|
38
|
-
valid_people, invalid_people = Functions.validate_data(df=df, schema=
|
|
36
|
+
# Get the valid column names from PeopleGet
|
|
37
|
+
valid_people, invalid_people = Functions.validate_data(df=df, schema=PeopleGet, debug=True)
|
|
39
38
|
return valid_people, invalid_people
|
|
40
39
|
|
|
41
40
|
|
|
42
41
|
def _init_fields(self) -> tuple[list[str], list[str], dict[str, str]]:
|
|
43
|
-
resp_fields = self.
|
|
44
|
-
url=f"{self.
|
|
45
|
-
timeout=self.
|
|
46
|
-
headers=self.
|
|
42
|
+
resp_fields = self.client.session.get(
|
|
43
|
+
url=f"{self.client.base_url}company/people/fields",
|
|
44
|
+
timeout=self.client.timeout,
|
|
45
|
+
headers=self.client.headers
|
|
47
46
|
)
|
|
48
47
|
fields = resp_fields.json()
|
|
49
48
|
field_name_in_body = [field.get('id') for field in fields]
|
|
@@ -58,14 +57,14 @@ class History:
|
|
|
58
57
|
body_fields = [employee_id_in_company, person_id]
|
|
59
58
|
response_fields = [self.endpoint_to_response.get(field) for field in body_fields if field in self.endpoint_to_response]
|
|
60
59
|
|
|
61
|
-
resp_additional_fields = self.
|
|
60
|
+
resp_additional_fields = self.client.session.post(url=f"{self.client.base_url}people/search",
|
|
62
61
|
json={
|
|
63
62
|
"fields": body_fields,
|
|
64
63
|
"filters": []
|
|
65
64
|
},
|
|
66
|
-
timeout=self.
|
|
65
|
+
timeout=self.client.timeout)
|
|
67
66
|
df = pd.json_normalize(resp_additional_fields.json()['employees'])
|
|
68
67
|
df = df[[col for col in response_fields if col in df.columns]]
|
|
69
|
-
# Get the valid column names from
|
|
70
|
-
valid_people, invalid_people = Functions.validate_data(df=df, schema=
|
|
68
|
+
# Get the valid column names from PeopleGet
|
|
69
|
+
valid_people, invalid_people = Functions.validate_data(df=df, schema=PeopleGet, debug=True)
|
|
71
70
|
return valid_people, invalid_people
|
|
@@ -9,19 +9,19 @@ from .bank import Bank
|
|
|
9
9
|
from .custom_tables import CustomTables
|
|
10
10
|
from .employment import Employment
|
|
11
11
|
from .salaries import Salaries
|
|
12
|
-
from .schemas.people import
|
|
12
|
+
from .schemas.people import PeopleGet
|
|
13
13
|
from .work import Work
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class People:
|
|
17
|
-
def __init__(self,
|
|
18
|
-
self.
|
|
19
|
-
self.salaries = Salaries(
|
|
20
|
-
self.employment = Employment(
|
|
21
|
-
self.bank = Bank(
|
|
22
|
-
self.work = Work(
|
|
23
|
-
self.custom_tables = CustomTables(
|
|
24
|
-
self.schema =
|
|
17
|
+
def __init__(self, client):
|
|
18
|
+
self.client = client
|
|
19
|
+
self.salaries = Salaries(client)
|
|
20
|
+
self.employment = Employment(client)
|
|
21
|
+
self.bank = Bank(client)
|
|
22
|
+
self.work = Work(client)
|
|
23
|
+
self.custom_tables = CustomTables(client)
|
|
24
|
+
self.schema = PeopleGet
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
|
|
@@ -33,19 +33,18 @@ class People:
|
|
|
33
33
|
for col_name, col in schema.columns.items()
|
|
34
34
|
]
|
|
35
35
|
|
|
36
|
-
def get(self, schema_custom_fields: Optional[BrynQPanderaDataFrameModel] = None) -> pd.DataFrame:
|
|
36
|
+
def get(self, schema_custom_fields: Optional[BrynQPanderaDataFrameModel] = None) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
37
37
|
|
|
38
|
-
core_fields = self.__build_api_fields(
|
|
38
|
+
core_fields = self.__build_api_fields(PeopleGet)
|
|
39
39
|
custom_fields = self.__build_api_fields(schema_custom_fields) if schema_custom_fields is not None else []
|
|
40
40
|
fields = core_fields + custom_fields
|
|
41
41
|
|
|
42
|
-
resp = self.
|
|
42
|
+
resp = self.client.session.post(url=f"{self.client.base_url}people/search",
|
|
43
43
|
json={
|
|
44
44
|
"fields": fields,
|
|
45
45
|
"filters": []
|
|
46
|
-
#"humanReadable": "REPLACE"
|
|
47
46
|
},
|
|
48
|
-
timeout=self.
|
|
47
|
+
timeout=self.client.timeout)
|
|
49
48
|
resp.raise_for_status()
|
|
50
49
|
df = pd.json_normalize(resp.json()['employees'])
|
|
51
50
|
|
|
@@ -72,7 +71,7 @@ class People:
|
|
|
72
71
|
|
|
73
72
|
|
|
74
73
|
# 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.
|
|
75
|
-
resp_named_lists = self.
|
|
74
|
+
resp_named_lists = self.client.session.get(url=f"{self.client.base_url}company/named-lists", timeout=self.client.timeout, headers=self.client.headers)
|
|
76
75
|
named_lists = resp_named_lists.json()
|
|
77
76
|
|
|
78
77
|
# Transform named_lists to create id-to-value mappings for each field
|
|
@@ -102,7 +101,7 @@ class People:
|
|
|
102
101
|
invalid_people_custom = pd.DataFrame()
|
|
103
102
|
|
|
104
103
|
|
|
105
|
-
valid_people, invalid_people = Functions.validate_data(df=valid_people, schema=
|
|
104
|
+
valid_people, invalid_people = Functions.validate_data(df=valid_people, schema=PeopleGet, debug=True)
|
|
106
105
|
|
|
107
106
|
# For columns ending with .value.value, use fillna to fill the corresponding base column since that's not done automatically
|
|
108
107
|
def _normalize_key(key: str) -> str:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Literal, TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from brynq_sdk_functions import Functions
|
|
5
|
+
from brynq_sdk_bob.schemas.reports import ReportGet
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from brynq_sdk_bob import Bob
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Reports:
|
|
11
|
+
def __init__(self, client):
|
|
12
|
+
self.client: Bob = client
|
|
13
|
+
|
|
14
|
+
def get(self) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
15
|
+
resp = self.client.session.get(url=f"{self.client.base_url}company/reports", timeout=self.client.timeout)
|
|
16
|
+
resp.raise_for_status()
|
|
17
|
+
data = resp.json()
|
|
18
|
+
df = pd.json_normalize(
|
|
19
|
+
data,
|
|
20
|
+
record_path='views'
|
|
21
|
+
)
|
|
22
|
+
valid_reports, invalid_reports = Functions.validate_data(df=df, schema=ReportGet, debug=True)
|
|
23
|
+
|
|
24
|
+
return valid_reports, invalid_reports
|
|
25
|
+
|
|
26
|
+
def download(self, report_id: int | str = None, report_format: Literal["json", "csv"] = "json") -> dict | bytes:
|
|
27
|
+
if report_id:
|
|
28
|
+
url = f"{self.client.base_url}company/reports/{report_id}/download"
|
|
29
|
+
else:
|
|
30
|
+
raise ValueError("Either report_id or report_name must be provided")
|
|
31
|
+
|
|
32
|
+
resp = self.client.session.get(url=url, timeout=self.client.timeout, params={"format": report_format})
|
|
33
|
+
resp.raise_for_status()
|
|
34
|
+
if report_format == "json":
|
|
35
|
+
data = resp.json()
|
|
36
|
+
elif report_format == "csv":
|
|
37
|
+
data = resp.content
|
|
38
|
+
else:
|
|
39
|
+
raise ValueError(f"Invalid report format: {report_format}")
|
|
40
|
+
|
|
41
|
+
return data
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import requests
|
|
3
|
+
from brynq_sdk_functions import Functions
|
|
4
|
+
from .schemas.salary import SalaryGet, SalaryCreate
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Salaries:
|
|
8
|
+
def __init__(self, client):
|
|
9
|
+
self.client = client
|
|
10
|
+
self.schema = SalaryGet
|
|
11
|
+
|
|
12
|
+
def get(self) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
13
|
+
request = requests.Request(method='GET',
|
|
14
|
+
url=f"{self.client.base_url}bulk/people/salaries",
|
|
15
|
+
params={"limit": 100, "includeArchived": "true"})
|
|
16
|
+
data = self.client.get_paginated_result(request)
|
|
17
|
+
df = pd.json_normalize(
|
|
18
|
+
data,
|
|
19
|
+
record_path='values',
|
|
20
|
+
meta=['employeeId']
|
|
21
|
+
)
|
|
22
|
+
valid_salaries, invalid_salaries = Functions.validate_data(df=df, schema=SalaryGet, debug=True)
|
|
23
|
+
|
|
24
|
+
return valid_salaries, invalid_salaries
|
|
25
|
+
|
|
26
|
+
def create(self, salary_data: dict) -> requests.Response:
|
|
27
|
+
nested_data = self.client.flat_dict_to_nested_dict(salary_data, SalaryCreate)
|
|
28
|
+
salary_data = SalaryCreate(**nested_data)
|
|
29
|
+
payload = salary_data.model_dump(exclude_none=True, by_alias=True)
|
|
30
|
+
|
|
31
|
+
resp = self.client.session.post(url=f"{self.client.base_url}people/{salary_data.employee_id}/salaries", json=payload)
|
|
32
|
+
resp.raise_for_status()
|
|
33
|
+
return resp
|
|
34
|
+
|
|
35
|
+
def delete(self, employee_id: str, salary_id: str) -> requests.Response:
|
|
36
|
+
resp = self.client.session.delete(url=f"{self.client.base_url}people/{employee_id}/salaries/{salary_id}")
|
|
37
|
+
resp.raise_for_status()
|
|
38
|
+
return resp
|
|
@@ -4,9 +4,9 @@ import pandas as pd
|
|
|
4
4
|
from brynq_sdk_functions import BrynQPanderaDataFrameModel
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
class
|
|
7
|
+
class BankGet(BrynQPanderaDataFrameModel):
|
|
8
8
|
id: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Bank ID", alias="id")
|
|
9
|
-
employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="
|
|
9
|
+
employee_id: Series[String] = pa.Field(coerce=True, description="Employee ID", alias="employee_id")
|
|
10
10
|
amount: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Amount", alias="amount")
|
|
11
11
|
allocation: Series[String] = pa.Field(coerce=True, nullable=True, description="Allocation", alias="allocation")
|
|
12
12
|
branch_address: Series[String] = pa.Field(coerce=True, nullable=True, description="Branch Address", alias="branchAddress")
|
|
@@ -3,13 +3,13 @@ from pandera.typing import Series
|
|
|
3
3
|
import pandas as pd
|
|
4
4
|
from brynq_sdk_functions import BrynQPanderaDataFrameModel
|
|
5
5
|
|
|
6
|
-
class
|
|
6
|
+
class CustomTableGet(BrynQPanderaDataFrameModel):
|
|
7
7
|
id: Series[pd.Int64Dtype] = pa.Field(coerce=True, description="Custom Table ID", alias="id")
|
|
8
8
|
employee_id: Series[str] = pa.Field(coerce=True, description="Employee ID", alias="employee_id")
|
|
9
9
|
|
|
10
10
|
class Config:
|
|
11
11
|
coerce = True
|
|
12
|
-
class
|
|
12
|
+
class CustomTableMetadataGet(BrynQPanderaDataFrameModel):
|
|
13
13
|
# Table information
|
|
14
14
|
table_id: Series[str] = pa.Field(coerce=True, description="Table ID", alias="table_id")
|
|
15
15
|
table_name: Series[str] = pa.Field(coerce=True, description="Table Name", alias="table_name")
|