projectal.entities.staff

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

Implementation of the Staff API.

@classmethod
def calendar(cls, uuId, begin=None, until=None):
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

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).

@classmethod
def calendar_availability(cls, uuId, begin=None, until=None):
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)

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).

@classmethod
def usage( cls, begin, until, holder=None, start=None, limit=None, span=None, ksort=None, order=None, staff=None):
 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

Returns the staff-to-task allocations for all staff within the holder.

See Usage API for full details.

@classmethod
def auto_assign( cls, type='Recommend', over_allocate_staff=False, include_assigned_task=False, include_started_task=False, skills=None, tasks=None, staffs=None):
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)

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.

@classmethod
def create_contract( cls, UUID, payload={}, end_current_contract=False, start_new_contract=False):
165    @classmethod
166    def create_contract(
167        cls,
168        UUID,
169        payload={},
170        end_current_contract=False,
171        start_new_contract=False,
172    ):
173        """
174        Creates a new Contract for a staff, with updated fields from the payload
175
176        end_current: Sets the end date of the current contract to today's date
177        start_new_date: Sets the start date of the new contract to today's date
178
179        See [Staff Clone API](https://projectal.com/docs/latest#tag/Staff/paths/~1api~1staff~1clone/post)
180        for full details.
181        """
182
183        url = "/api/staff/clone?reference={}&as_contract=true".format(UUID)
184        date_today = datetime.today().strftime("%Y-%m-%d")
185
186        current_staff = None
187        if end_current_contract:
188            # Check if setting end date to today is
189            # invalid for current Staff Contract
190            current_staff = cls.get(UUID)
191            if projectal.timestamp_from_date(
192                current_staff.get("startDate")
193            ) > projectal.timestamp_from_date(date_today):
194                raise UsageException(
195                    f"Cannot set endDate before startDate for current contract: {date_today}"
196                )
197
198        if start_new_contract:
199            payload["startDate"] = date_today
200            payload["endDate"] = DateLimit.Max
201
202        response = api.post(url, payload)
203
204        if end_current_contract and current_staff:
205            current_staff["endDate"] = date_today
206            current_staff.save()
207
208        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

See Staff Clone API for full details.