brynq-sdk-bob 2.8.4__py3-none-any.whl → 2.8.5__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/schemas/timeoff.py +191 -1
- brynq_sdk_bob/timeoff.py +34 -3
- {brynq_sdk_bob-2.8.4.dist-info → brynq_sdk_bob-2.8.5.dist-info}/METADATA +1 -1
- {brynq_sdk_bob-2.8.4.dist-info → brynq_sdk_bob-2.8.5.dist-info}/RECORD +6 -6
- {brynq_sdk_bob-2.8.4.dist-info → brynq_sdk_bob-2.8.5.dist-info}/WHEEL +0 -0
- {brynq_sdk_bob-2.8.4.dist-info → brynq_sdk_bob-2.8.5.dist-info}/top_level.txt +0 -0
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
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/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
|
|
@@ -10,7 +10,7 @@ brynq_sdk_bob/payroll_history.py,sha256=wHo6da7kLDe1ViL4egyMdyJBMZnWVhwjNjmh4cTC
|
|
|
10
10
|
brynq_sdk_bob/people.py,sha256=t1A1dABX6UZ0pyLTGOL-Sp5pHY630KWIyIO3JQ_Pjdk,5970
|
|
11
11
|
brynq_sdk_bob/reports.py,sha256=Tawmqm_ZmQ487loyk-29-A_fTCrgImbWCEf6zfwuaq4,1245
|
|
12
12
|
brynq_sdk_bob/salaries.py,sha256=BGQm-PT9QuKKJ9DP5nX6wmC8SZRAlm9M9I2EJhoZaII,1523
|
|
13
|
-
brynq_sdk_bob/timeoff.py,sha256=
|
|
13
|
+
brynq_sdk_bob/timeoff.py,sha256=JtTu14PWFqQIEn9r-Z8ipeNE-5p7hqPz5N6wjjBeLTs,4438
|
|
14
14
|
brynq_sdk_bob/work.py,sha256=0bVZkQ0I6z-z2_ql-EsOpFExx8VgsJvpcCQdOfiJYQM,712
|
|
15
15
|
brynq_sdk_bob/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
brynq_sdk_bob/schemas/bank.py,sha256=lDmXP4P8091N20fL2CmhPU2wFuaK60Z-p-dvYSNCMaQ,1846
|
|
@@ -21,9 +21,9 @@ brynq_sdk_bob/schemas/payments.py,sha256=LrSr8WApYxqbMDmhYmh0EISEbWJinWovsULZV6s
|
|
|
21
21
|
brynq_sdk_bob/schemas/payroll_history.py,sha256=JdAq0XaArHHEw8EsXo3GD0EhSAyBhPtYQMmdvjCiY8g,806
|
|
22
22
|
brynq_sdk_bob/schemas/people.py,sha256=42BJVgJmT-h5kzuQl6iI7wZDSGNA0KTQQVIAqeeyHNk,40149
|
|
23
23
|
brynq_sdk_bob/schemas/salary.py,sha256=TSaM1g92y3oiDcUrfJW7ushgKZenI9xB6XW3kKuU0dE,4540
|
|
24
|
-
brynq_sdk_bob/schemas/timeoff.py,sha256=
|
|
24
|
+
brynq_sdk_bob/schemas/timeoff.py,sha256=gTYu_bNcfHrkTz4eIHCZ4WzgMTj2U4nI3X6JTzDovhk,12817
|
|
25
25
|
brynq_sdk_bob/schemas/work.py,sha256=YgtBJ0WXJOq55bFlT_kY_IbHh0SlQEtaa0W8vms-xA4,3048
|
|
26
|
-
brynq_sdk_bob-2.8.
|
|
27
|
-
brynq_sdk_bob-2.8.
|
|
28
|
-
brynq_sdk_bob-2.8.
|
|
29
|
-
brynq_sdk_bob-2.8.
|
|
26
|
+
brynq_sdk_bob-2.8.5.dist-info/METADATA,sha256=lf34rFTbsXhfWfTdBkdmjLHTvTzXXwUm6IYhuyW0sOY,371
|
|
27
|
+
brynq_sdk_bob-2.8.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
28
|
+
brynq_sdk_bob-2.8.5.dist-info/top_level.txt,sha256=oGiWqOuAAiVoLIzGe6F-Lo4IJBYz5ftOwBft7HtPuoY,14
|
|
29
|
+
brynq_sdk_bob-2.8.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|