projectal.entities.staff
1from copy import deepcopy 2from datetime import datetime 3 4import projectal 5from projectal.entity import Entity 6from projectal.linkers import * 7from projectal.errors import UsageException 8from projectal.enums import DateLimit 9 10 11class Staff( 12 Entity, 13 LocationLinker, 14 ResourceLinker, 15 SkillLinker, 16 FileLinker, 17 CompanyLinker, 18 DepartmentLinker, 19 TaskLinker, 20 TaskTemplateLinker, 21 NoteLinker, 22 CalendarLinker, 23 TagLinker, 24 ContractLinker, 25): 26 """ 27 Implementation of the [Staff](https://projectal.com/docs/latest/#tag/Staff) API. 28 """ 29 30 _path = "staff" 31 _name = "staff" 32 _links = [ 33 LocationLinker, 34 ResourceLinker, 35 SkillLinker, 36 FileLinker, 37 NoteLinker, 38 CalendarLinker, 39 TagLinker, 40 ContractLinker, 41 ] 42 _links_reverse = [CompanyLinker, DepartmentLinker, TaskLinker, TaskTemplateLinker] 43 44 @classmethod 45 def calendar(cls, uuId, begin=None, until=None): 46 """ 47 Returns the calendar of the staff with `uuId`. 48 49 `begin`: Start date in `yyyy-MM-dd`. 50 51 `until`: End date in `yyyy-MM-dd`. 52 53 54 Optionally specify a date range. If no range specified, the 55 minimum and maximum dates are used (see projectal.enums.DateLimit). 56 """ 57 if begin: 58 begin = datetime.strptime(begin, "%Y-%m-%d").date() 59 if until: 60 until = datetime.strptime(until, "%Y-%m-%d").date() 61 62 url = "/api/staff/{}/calendar?".format(uuId) 63 params = [] 64 params.append("begin={}".format(begin)) if begin else None 65 params.append("until={}".format(until)) if until else None 66 url += "&".join(params) 67 68 cals = api.get(url) 69 cals = [projectal.Calendar(c) for c in cals] 70 return cals 71 72 @classmethod 73 def calendar_availability(cls, uuId, begin=None, until=None): 74 """ 75 Returns the availability (in hours) of the staff in `uuId` 76 for each day within the specified date range. 77 78 `begin`: Start date in `yyyy-MM-dd`. 79 80 `until`: End date in `yyyy-MM-dd`. 81 82 If no range specified, the minimum and maximum dates are 83 used (see projectal.enums.DateLimit). 84 """ 85 if begin: 86 begin = datetime.strptime(begin, "%Y-%m-%d").date() 87 if until: 88 until = datetime.strptime(until, "%Y-%m-%d").date() 89 90 url = "/api/staff/{}/calendar/availability?".format(uuId) 91 params = [] 92 params.append("begin={}".format(begin)) if begin else None 93 params.append("until={}".format(until)) if until else None 94 url += "&".join(params) 95 96 return api.get(url) 97 98 @classmethod 99 def usage( 100 cls, 101 begin, 102 until, 103 holder=None, 104 start=None, 105 limit=None, 106 span=None, 107 ksort=None, 108 order=None, 109 staff=None, 110 ): 111 """ 112 Returns the staff-to-task allocations for all staff within the `holder`. 113 114 See [Usage API](https://projectal.com/docs/latest/#tag/Staff/paths/~1api~1staff~1usage/post) 115 for full details. 116 """ 117 url = "/api/staff/usage?begin={}&until={}".format(begin, until) 118 params = [] 119 params.append("holder={}".format(holder)) if holder else None 120 params.append("start={}".format(start)) if start else None 121 params.append("limit={}".format(limit)) if limit else None 122 params.append("span={}".format(span)) if span else None 123 params.append("ksort={}".format(ksort)) if ksort else None 124 params.append("order={}".format(order)) if order else None 125 if len(params) > 0: 126 url += "&" + "&".join(params) 127 payload = staff if staff and not holder else None 128 response = api.post(url, payload) 129 # Do some extra checks for empty list case 130 if "status" in response: 131 # We didn't have a 'jobCase' key and returned the outer dict. 132 return [] 133 return response 134 135 @classmethod 136 def auto_assign( 137 cls, 138 type="Recommend", 139 over_allocate_staff=False, 140 include_assigned_task=False, 141 include_started_task=False, 142 skills=None, 143 tasks=None, 144 staffs=None, 145 ): 146 """ 147 Automatically assign a set of staff (real or generic) to a set of tasks 148 using various skill and allocation criteria. 149 150 See [Staff Assign API](https://projectal.com/docs/latest/#tag/Staff-Assign/paths/~1api~1allocation~1staff/post) 151 for full details. 152 """ 153 url = "/api/allocation/staff" 154 payload = { 155 "type": type, 156 "overAllocateStaff": over_allocate_staff, 157 "includeAssignedTask": include_assigned_task, 158 "includeStartedTask": include_started_task, 159 "skillMatchList": skills if skills else [], 160 "staffList": staffs if staffs else [], 161 "taskList": tasks if tasks else [], 162 } 163 return api.post(url, payload) 164 165 @classmethod 166 def create_contract( 167 cls, 168 UUID, 169 payload=None, 170 end_current_contract=False, 171 start_new_contract=False, 172 include_calendars=False, 173 ): 174 """ 175 Creates a new Contract for a staff, with updated fields from the payload 176 177 end_current: Sets the end date of the current contract to today's date 178 start_new_date: Sets the start date of the new contract to today's date 179 include_calendars: If False (default), calendarList is set to [] so calendars are not cloned 180 181 See [Staff Clone API](https://projectal.com/docs/latest#tag/Staff/paths/~1api~1staff~1clone/post) 182 for full details. 183 """ 184 185 payload = deepcopy(payload) if payload else {} 186 if not include_calendars: 187 payload["calendarList"] = [] 188 189 url = "/api/staff/clone?reference={}&as_contract=true".format(UUID) 190 date_today = datetime.today().strftime("%Y-%m-%d") 191 192 current_staff = None 193 if end_current_contract: 194 # Check if setting end date to today is 195 # invalid for current Staff Contract 196 current_staff = cls.get(UUID) 197 if projectal.timestamp_from_date( 198 current_staff.get("startDate") 199 ) > projectal.timestamp_from_date(date_today): 200 raise UsageException( 201 f"Cannot set endDate before startDate for current contract: {date_today}" 202 ) 203 204 if start_new_contract: 205 payload["startDate"] = date_today 206 payload["endDate"] = DateLimit.Max 207 208 response = api.post(url, payload) 209 210 if end_current_contract and current_staff: 211 current_staff["endDate"] = date_today 212 current_staff.save() 213 214 return response["jobClue"]["uuId"]
12class Staff( 13 Entity, 14 LocationLinker, 15 ResourceLinker, 16 SkillLinker, 17 FileLinker, 18 CompanyLinker, 19 DepartmentLinker, 20 TaskLinker, 21 TaskTemplateLinker, 22 NoteLinker, 23 CalendarLinker, 24 TagLinker, 25 ContractLinker, 26): 27 """ 28 Implementation of the [Staff](https://projectal.com/docs/latest/#tag/Staff) API. 29 """ 30 31 _path = "staff" 32 _name = "staff" 33 _links = [ 34 LocationLinker, 35 ResourceLinker, 36 SkillLinker, 37 FileLinker, 38 NoteLinker, 39 CalendarLinker, 40 TagLinker, 41 ContractLinker, 42 ] 43 _links_reverse = [CompanyLinker, DepartmentLinker, TaskLinker, TaskTemplateLinker] 44 45 @classmethod 46 def calendar(cls, uuId, begin=None, until=None): 47 """ 48 Returns the calendar of the staff with `uuId`. 49 50 `begin`: Start date in `yyyy-MM-dd`. 51 52 `until`: End date in `yyyy-MM-dd`. 53 54 55 Optionally specify a date range. If no range specified, the 56 minimum and maximum dates are used (see projectal.enums.DateLimit). 57 """ 58 if begin: 59 begin = datetime.strptime(begin, "%Y-%m-%d").date() 60 if until: 61 until = datetime.strptime(until, "%Y-%m-%d").date() 62 63 url = "/api/staff/{}/calendar?".format(uuId) 64 params = [] 65 params.append("begin={}".format(begin)) if begin else None 66 params.append("until={}".format(until)) if until else None 67 url += "&".join(params) 68 69 cals = api.get(url) 70 cals = [projectal.Calendar(c) for c in cals] 71 return cals 72 73 @classmethod 74 def calendar_availability(cls, uuId, begin=None, until=None): 75 """ 76 Returns the availability (in hours) of the staff in `uuId` 77 for each day within the specified date range. 78 79 `begin`: Start date in `yyyy-MM-dd`. 80 81 `until`: End date in `yyyy-MM-dd`. 82 83 If no range specified, the minimum and maximum dates are 84 used (see projectal.enums.DateLimit). 85 """ 86 if begin: 87 begin = datetime.strptime(begin, "%Y-%m-%d").date() 88 if until: 89 until = datetime.strptime(until, "%Y-%m-%d").date() 90 91 url = "/api/staff/{}/calendar/availability?".format(uuId) 92 params = [] 93 params.append("begin={}".format(begin)) if begin else None 94 params.append("until={}".format(until)) if until else None 95 url += "&".join(params) 96 97 return api.get(url) 98 99 @classmethod 100 def usage( 101 cls, 102 begin, 103 until, 104 holder=None, 105 start=None, 106 limit=None, 107 span=None, 108 ksort=None, 109 order=None, 110 staff=None, 111 ): 112 """ 113 Returns the staff-to-task allocations for all staff within the `holder`. 114 115 See [Usage API](https://projectal.com/docs/latest/#tag/Staff/paths/~1api~1staff~1usage/post) 116 for full details. 117 """ 118 url = "/api/staff/usage?begin={}&until={}".format(begin, until) 119 params = [] 120 params.append("holder={}".format(holder)) if holder else None 121 params.append("start={}".format(start)) if start else None 122 params.append("limit={}".format(limit)) if limit else None 123 params.append("span={}".format(span)) if span else None 124 params.append("ksort={}".format(ksort)) if ksort else None 125 params.append("order={}".format(order)) if order else None 126 if len(params) > 0: 127 url += "&" + "&".join(params) 128 payload = staff if staff and not holder else None 129 response = api.post(url, payload) 130 # Do some extra checks for empty list case 131 if "status" in response: 132 # We didn't have a 'jobCase' key and returned the outer dict. 133 return [] 134 return response 135 136 @classmethod 137 def auto_assign( 138 cls, 139 type="Recommend", 140 over_allocate_staff=False, 141 include_assigned_task=False, 142 include_started_task=False, 143 skills=None, 144 tasks=None, 145 staffs=None, 146 ): 147 """ 148 Automatically assign a set of staff (real or generic) to a set of tasks 149 using various skill and allocation criteria. 150 151 See [Staff Assign API](https://projectal.com/docs/latest/#tag/Staff-Assign/paths/~1api~1allocation~1staff/post) 152 for full details. 153 """ 154 url = "/api/allocation/staff" 155 payload = { 156 "type": type, 157 "overAllocateStaff": over_allocate_staff, 158 "includeAssignedTask": include_assigned_task, 159 "includeStartedTask": include_started_task, 160 "skillMatchList": skills if skills else [], 161 "staffList": staffs if staffs else [], 162 "taskList": tasks if tasks else [], 163 } 164 return api.post(url, payload) 165 166 @classmethod 167 def create_contract( 168 cls, 169 UUID, 170 payload=None, 171 end_current_contract=False, 172 start_new_contract=False, 173 include_calendars=False, 174 ): 175 """ 176 Creates a new Contract for a staff, with updated fields from the payload 177 178 end_current: Sets the end date of the current contract to today's date 179 start_new_date: Sets the start date of the new contract to today's date 180 include_calendars: If False (default), calendarList is set to [] so calendars are not cloned 181 182 See [Staff Clone API](https://projectal.com/docs/latest#tag/Staff/paths/~1api~1staff~1clone/post) 183 for full details. 184 """ 185 186 payload = deepcopy(payload) if payload else {} 187 if not include_calendars: 188 payload["calendarList"] = [] 189 190 url = "/api/staff/clone?reference={}&as_contract=true".format(UUID) 191 date_today = datetime.today().strftime("%Y-%m-%d") 192 193 current_staff = None 194 if end_current_contract: 195 # Check if setting end date to today is 196 # invalid for current Staff Contract 197 current_staff = cls.get(UUID) 198 if projectal.timestamp_from_date( 199 current_staff.get("startDate") 200 ) > projectal.timestamp_from_date(date_today): 201 raise UsageException( 202 f"Cannot set endDate before startDate for current contract: {date_today}" 203 ) 204 205 if start_new_contract: 206 payload["startDate"] = date_today 207 payload["endDate"] = DateLimit.Max 208 209 response = api.post(url, payload) 210 211 if end_current_contract and current_staff: 212 current_staff["endDate"] = date_today 213 current_staff.save() 214 215 return response["jobClue"]["uuId"]
Implementation of the Staff API.
45 @classmethod 46 def calendar(cls, uuId, begin=None, until=None): 47 """ 48 Returns the calendar of the staff with `uuId`. 49 50 `begin`: Start date in `yyyy-MM-dd`. 51 52 `until`: End date in `yyyy-MM-dd`. 53 54 55 Optionally specify a date range. If no range specified, the 56 minimum and maximum dates are used (see projectal.enums.DateLimit). 57 """ 58 if begin: 59 begin = datetime.strptime(begin, "%Y-%m-%d").date() 60 if until: 61 until = datetime.strptime(until, "%Y-%m-%d").date() 62 63 url = "/api/staff/{}/calendar?".format(uuId) 64 params = [] 65 params.append("begin={}".format(begin)) if begin else None 66 params.append("until={}".format(until)) if until else None 67 url += "&".join(params) 68 69 cals = api.get(url) 70 cals = [projectal.Calendar(c) for c in cals] 71 return cals
Returns the calendar of the staff with uuId.
begin: Start date in yyyy-MM-dd.
until: End date in yyyy-MM-dd.
Optionally specify a date range. If no range specified, the minimum and maximum dates are used (see projectal.enums.DateLimit).
73 @classmethod 74 def calendar_availability(cls, uuId, begin=None, until=None): 75 """ 76 Returns the availability (in hours) of the staff in `uuId` 77 for each day within the specified date range. 78 79 `begin`: Start date in `yyyy-MM-dd`. 80 81 `until`: End date in `yyyy-MM-dd`. 82 83 If no range specified, the minimum and maximum dates are 84 used (see projectal.enums.DateLimit). 85 """ 86 if begin: 87 begin = datetime.strptime(begin, "%Y-%m-%d").date() 88 if until: 89 until = datetime.strptime(until, "%Y-%m-%d").date() 90 91 url = "/api/staff/{}/calendar/availability?".format(uuId) 92 params = [] 93 params.append("begin={}".format(begin)) if begin else None 94 params.append("until={}".format(until)) if until else None 95 url += "&".join(params) 96 97 return api.get(url)
Returns the availability (in hours) of the staff in uuId
for each day within the specified date range.
begin: Start date in yyyy-MM-dd.
until: End date in yyyy-MM-dd.
If no range specified, the minimum and maximum dates are used (see projectal.enums.DateLimit).
99 @classmethod 100 def usage( 101 cls, 102 begin, 103 until, 104 holder=None, 105 start=None, 106 limit=None, 107 span=None, 108 ksort=None, 109 order=None, 110 staff=None, 111 ): 112 """ 113 Returns the staff-to-task allocations for all staff within the `holder`. 114 115 See [Usage API](https://projectal.com/docs/latest/#tag/Staff/paths/~1api~1staff~1usage/post) 116 for full details. 117 """ 118 url = "/api/staff/usage?begin={}&until={}".format(begin, until) 119 params = [] 120 params.append("holder={}".format(holder)) if holder else None 121 params.append("start={}".format(start)) if start else None 122 params.append("limit={}".format(limit)) if limit else None 123 params.append("span={}".format(span)) if span else None 124 params.append("ksort={}".format(ksort)) if ksort else None 125 params.append("order={}".format(order)) if order else None 126 if len(params) > 0: 127 url += "&" + "&".join(params) 128 payload = staff if staff and not holder else None 129 response = api.post(url, payload) 130 # Do some extra checks for empty list case 131 if "status" in response: 132 # We didn't have a 'jobCase' key and returned the outer dict. 133 return [] 134 return response
Returns the staff-to-task allocations for all staff within the holder.
See Usage API for full details.
136 @classmethod 137 def auto_assign( 138 cls, 139 type="Recommend", 140 over_allocate_staff=False, 141 include_assigned_task=False, 142 include_started_task=False, 143 skills=None, 144 tasks=None, 145 staffs=None, 146 ): 147 """ 148 Automatically assign a set of staff (real or generic) to a set of tasks 149 using various skill and allocation criteria. 150 151 See [Staff Assign API](https://projectal.com/docs/latest/#tag/Staff-Assign/paths/~1api~1allocation~1staff/post) 152 for full details. 153 """ 154 url = "/api/allocation/staff" 155 payload = { 156 "type": type, 157 "overAllocateStaff": over_allocate_staff, 158 "includeAssignedTask": include_assigned_task, 159 "includeStartedTask": include_started_task, 160 "skillMatchList": skills if skills else [], 161 "staffList": staffs if staffs else [], 162 "taskList": tasks if tasks else [], 163 } 164 return api.post(url, payload)
Automatically assign a set of staff (real or generic) to a set of tasks using various skill and allocation criteria.
See Staff Assign API for full details.
166 @classmethod 167 def create_contract( 168 cls, 169 UUID, 170 payload=None, 171 end_current_contract=False, 172 start_new_contract=False, 173 include_calendars=False, 174 ): 175 """ 176 Creates a new Contract for a staff, with updated fields from the payload 177 178 end_current: Sets the end date of the current contract to today's date 179 start_new_date: Sets the start date of the new contract to today's date 180 include_calendars: If False (default), calendarList is set to [] so calendars are not cloned 181 182 See [Staff Clone API](https://projectal.com/docs/latest#tag/Staff/paths/~1api~1staff~1clone/post) 183 for full details. 184 """ 185 186 payload = deepcopy(payload) if payload else {} 187 if not include_calendars: 188 payload["calendarList"] = [] 189 190 url = "/api/staff/clone?reference={}&as_contract=true".format(UUID) 191 date_today = datetime.today().strftime("%Y-%m-%d") 192 193 current_staff = None 194 if end_current_contract: 195 # Check if setting end date to today is 196 # invalid for current Staff Contract 197 current_staff = cls.get(UUID) 198 if projectal.timestamp_from_date( 199 current_staff.get("startDate") 200 ) > projectal.timestamp_from_date(date_today): 201 raise UsageException( 202 f"Cannot set endDate before startDate for current contract: {date_today}" 203 ) 204 205 if start_new_contract: 206 payload["startDate"] = date_today 207 payload["endDate"] = DateLimit.Max 208 209 response = api.post(url, payload) 210 211 if end_current_contract and current_staff: 212 current_staff["endDate"] = date_today 213 current_staff.save() 214 215 return response["jobClue"]["uuId"]
Creates a new Contract for a staff, with updated fields from the payload
end_current: Sets the end date of the current contract to today's date start_new_date: Sets the start date of the new contract to today's date include_calendars: If False (default), calendarList is set to [] so calendars are not cloned
See Staff Clone API for full details.