Example #1
0
    def post(self, org_id, location_id, role_id, schedule_id):
        parser = restful.reqparse.RequestParser()
        parser.add_argument("user_id", type=int, required=True)
        parser.add_argument("preference", type=str, required=True)
        parameters = parser.parse_args(strict=True)

        # Check and see if there is existing preference
        p = Preference.query.filter(
            Preference.user_id == parameters.get("user_id"),
            Preference.schedule_id == schedule_id).first()

        if p is not None:
            return {
                "message":
                "User already has preference - use a patch method to modify it."
            }, 400

        try:
            preference = json.loads(parameters.get("preference"))
        except:
            return {"message": "Unable to parse preference json body"}, 400
        if preference is None:
            return {"message": "Unable to parse preference json body"}, 400
        if not verify_days_of_week_struct(preference, True):
            return {"message": "preference is improperly formatted"}, 400

        user_id = parameters.get("user_id")

        # verify user_id is valid to add
        assocs = Role.query.get(role_id).members
        ok = False
        for assoc in assocs:
            if user_id == assoc.user_id:
                ok = True
        if not ok:
            return {"message": "User not in role"}, 400

        p = Preference(
            preference=json.dumps(preference),
            user_id=user_id,
            schedule_id=schedule_id,
        )

        db.session.add(p)
        try:
            db.session.commit()
            g.current_user.track_event("created_preference")
            return marshal(p, preference_fields), 201
        except:
            abort(500)
Example #2
0
    def patch(self, org_id, location_id, role_id, schedule_id, user_id):
        parser = reqparse.RequestParser()
        parser.add_argument("preference", type=str, required=True)
        changes = parser.parse_args(strict=True)

        # Filter out null values
        changes = dict((k, v) for k, v in changes.iteritems() if v is not None)
        p = Preference.query.filter(
            Preference.user_id == user_id,
            Preference.schedule_id == schedule_id).first()

        if p is None:
            abort(404)

        if changes.get("preference") is not None:
            try:
                preference = json.loads(changes.get("preference"))
            except:
                return {"message": "Unable to parse preference json body"}, 400
            if preference is None:
                return {"message": "Unable to parse preference json body"}, 400
            if not verify_days_of_week_struct(preference, True):
                return {"message": "preference is improperly formatted"}, 400

            changes["preference"] = preference

        for change, value in changes.iteritems():
            if change == "preference":
                value = json.dumps(value)

            if value is not None:
                try:
                    setattr(p, change, value)
                    db.session.commit()
                except Exception as exception:
                    db.session.rollback()
                    current_app.logger.exception(str(exception))
                    abort(400)

        g.current_user.track_event("updated_preference")
        return changes
Example #3
0
    def post(self, org_id, location_id, role_id):
        """ Add a user as a worker either by user id or email """

        role = Role.query.get_or_404(role_id)
        org = Organization.query.get_or_404(org_id)

        parser = reqparse.RequestParser()
        parser.add_argument("email", type=str)
        parser.add_argument("id", type=int)
        parser.add_argument("min_hours_per_workweek", type=int, required=True)
        parser.add_argument("max_hours_per_workweek", type=int, required=True)
        parser.add_argument("name", type=str)
        parser.add_argument("internal_id", type=str)
        parser.add_argument("working_hours", type=str)
        parameters = parser.parse_args(strict=True)

        if parameters.get("id") is not None:
            user = User.query.get_or_404(parameters.get("id"))
        elif parameters.get("email") is not None:
            email = parameters.get("email")

            # Check if valid email
            if "@" not in email:
                abort(400)

            # Check if user has email
            user = User.query.filter_by(email=email.lower().strip()).first()

            # otherwise invite by email
            if user is None:
                user = User.create_and_invite(email, parameters.get("name"),
                                              org.name)
        else:
            return {"message": "unable to identify user"}, 400

        # get min/max workweek values
        min_half_hours_per_workweek = parameters.get(
            "min_hours_per_workweek") * 2
        max_half_hours_per_workweek = parameters.get(
            "max_hours_per_workweek") * 2

        if min_half_hours_per_workweek > max_half_hours_per_workweek:
            return {
                "message":
                "min_hours_per_workweek cannot be greater than max_hours_per_workweek"
            }, 400

        if not (0 <= min_half_hours_per_workweek <= 336):
            return {
                "message": "min_hours_per_workweek cannot be less than 0"
            }, 400

        if not (0 <= max_half_hours_per_workweek <= 336):
            return {
                "message": "max_hours_per_workweek cannot be greater than 168"
            }, 400

        # check if the user already is in the role
        membership = RoleToUser.query.filter_by(role_id=role_id).filter_by(
            user_id=user.id).first()

        if membership:
            if membership.archived == False:
                return {"message": "user already in role"}, 400

            membership.archived = False

        else:
            membership = RoleToUser()
            membership.user = user
            membership.role = role

        membership.min_half_hours_per_workweek = min_half_hours_per_workweek
        membership.max_half_hours_per_workweek = max_half_hours_per_workweek

        # internal_id in post? I'll allow it
        if parameters.get("internal_id") is not None:
            membership.internal_id = parameters["internal_id"]

        if parameters.get("working_hours") is not None:
            try:
                working_hours = json.loads(parameters.get("working_hours"))
            except:
                return {
                    "message": "Unable to parse working hours json body"
                }, 400
            if working_hours is None:
                return {
                    "message": "Unable to parse working hours json body"
                }, 400
            if not verify_days_of_week_struct(working_hours, True):
                return {
                    "message": "working hours is improperly formatted"
                }, 400

            membership.working_hours = json.dumps(working_hours)

        try:
            db.session.commit()
        except Exception as exception:
            db.session.rollback()
            current_app.logger.exception(str(exception))
            abort(400)

        location = Location.query.get(location_id)
        organization = Organization.query.get(org_id)

        alert_email(
            user, "You have been added to %s on Staffjoy" % organization.name,
            "%s is using Staffjoy to manage its workforce, and you have been added to the team <b>%s</b> at the <b>%s</b> location."
            % (
                organization.name,
                role.name,
                location.name,
            ))

        data = {}
        data.update(marshal(user, user_fields))
        data.update(marshal(membership, role_to_user_fields))
        g.current_user.track_event("added_role_member")

        return data, 201
Example #4
0
    def patch(self, org_id, location_id, role_id, schedule_id):
        schedule = Schedule2.query.get_or_404(schedule_id)
        org = Organization.query.get_or_404(org_id)

        parser = reqparse.RequestParser()
        parser.add_argument("demand", type=str)
        parser.add_argument("state", type=str)
        parser.add_argument("min_shift_length_hour", type=int)
        parser.add_argument("max_shift_length_hour", type=int)
        changes = parser.parse_args(strict=True)

        # Filter out null values
        changes = dict((k, v) for k, v in changes.iteritems() if v is not None)

        original_state = schedule.state

        if len(changes) == 0:
            return {"message": "No valid changes detected"}, 400

        # schedule can only be modified from initial or unpublished state if not sudo
        if not g.current_user.is_sudo():
            if original_state not in ["initial", "unpublished"]:
                return {
                    "message":
                    "You are not able to modify a schedule from its current state."
                }, 400

        if "min_shift_length_hour" in changes:
            min_shift_length_half_hour = changes["min_shift_length_hour"] * 2
        else:
            min_shift_length_half_hour = schedule.min_shift_length_half_hour

        if "max_shift_length_hour" in changes:
            max_shift_length_half_hour = changes["max_shift_length_hour"] * 2
        else:
            max_shift_length_half_hour = schedule.max_shift_length_half_hour

        # now verification
        # NOTE that if we choose to support lengths of 0, these 1st two checks will break
        # because None and 0 get evalulated as the same
        if bool(min_shift_length_half_hour) != bool(
                max_shift_length_half_hour):
            return {
                "message":
                "min_shift_length_hour and max_shift_length_hour most both be defined"
            }, 400

        if min_shift_length_half_hour and max_shift_length_half_hour:
            if min_shift_length_half_hour > max_shift_length_half_hour:
                return {
                    "message":
                    "min_shift_length_hour cannot be greater than max_shift_length_hour"
                }, 400

        if min_shift_length_half_hour:
            if not (1 <= min_shift_length_half_hour <= 46):
                return {
                    "message": "min_shift_length_hour must be between 1 and 24"
                }, 400

        if max_shift_length_half_hour:
            if not (1 <= max_shift_length_half_hour <= 46):
                return {
                    "message": "max_shift_length_hour must be between 1 and 24"
                }, 400

        if "min_shift_length_hour" in changes:
            del changes["min_shift_length_hour"]
            changes["min_shift_length_half_hour"] = min_shift_length_half_hour

        if "max_shift_length_hour" in changes:
            del changes["max_shift_length_hour"]
            changes["max_shift_length_half_hour"] = max_shift_length_half_hour

        if "demand" in changes:

            # admins can only modify demand in the unpublished state
            if not g.current_user.is_sudo():
                if changes.get("state", schedule.state) not in [
                        "unpublished", "chomp-queue"
                ]:
                    return {
                        "message":
                        "Admins can only modify demand when the schedule is in the unpublished state."
                    }, 400

            # demand can be set to None when it is sent down without a value in the request
            # (not "") will resolve to True, which we consider None - assume json for all other cases
            if not changes["demand"]:
                changes["demand"] = None
            else:
                try:
                    demand = json.loads(changes.get("demand"))
                except:
                    return {"message": "Unable to parse demand json body"}, 400

                if demand is None or not isinstance(demand, dict):
                    return {"message": "Unable to parse demand json body"}, 400

                # Check that days of week are right
                if not verify_days_of_week_struct(demand):
                    return {"message": "demand is improperly formatted"}, 400

                try:
                    changes["demand"] = json.dumps(demand)
                except Exception as exception:
                    return {"message": "Unable to parse demand json body"}, 400

            g.current_user.track_event("updated_demand")

        if "state" in changes:
            state = changes.get("state")

            if state == original_state:
                return {
                    "message": "Schedule is already in state %s." % state
                }, 400

            if state not in [
                    "unpublished", "chomp-queue", "mobius-queue", "published"
            ]:
                return {
                    "message":
                    "State can only be updated to 'unpublished', 'chomp-queue', 'mobius-queue' or 'done'."
                }, 400

            if not org.active:
                return {
                    "message":
                    "This organization must be active for a state change"
                }, 400

            if state == "chomp-queue":
                if not changes.get("min_shift_length_half_hour",
                                   schedule.min_shift_length_half_hour):
                    return {
                        "message":
                        "min_shift_length_hour must be set for chomp queue"
                    }, 400

                if not changes.get("max_shift_length_half_hour",
                                   schedule.max_shift_length_half_hour):
                    return {
                        "message":
                        "max_shift_length_hour must be set for chomp queue"
                    }, 400

                if original_state not in ["unpublished", "chomp-processing"]:
                    return {"message": "This state change is not allowed"}, 400

                # reset timing measurements - although they will soon be reset, the monitoring timing
                # may be inaccurate for the duration of calculation (e.g. a requeue)
                changes["chomp_start"] = None
                changes["chomp_end"] = None

                if not g.current_user.is_sudo():
                    g.current_user.track_event("chomp_schedule_calculation")

                schedule.transition_to_chomp_queue()

            elif state == "published":

                if original_state not in ["unpublished", "mobius-processing"]:
                    return {"message": "This state change is not allowed"}, 400

                schedule.transition_to_published()

                if not g.current_user.is_sudo():
                    g.current_user.track_event("published_schedule")

            elif state == "mobius-queue":

                if original_state not in ["unpublished", "mobius-processing"]:
                    return {"message": "This state change is not allowed"}, 400

                # reset timing measurements - although they will soon be reset, the monitoring timing
                # may be inaccurate for the duration of calculation (e.g. a requeue)
                changes["mobius_start"] = None
                changes["mobius_end"] = None

                schedule.transition_to_mobius_queue()

            elif state == "unpublished":
                if original_state not in ["initial", "chomp-processing"]:
                    return {
                        "message":
                        "Schedule cannot be set to unpublished from its current state"
                    }

                schedule.transition_to_unpublished()

        for change, value in changes.iteritems():
            try:
                setattr(schedule, change, value)
                db.session.commit()
            except Exception as exception:
                db.session.rollback()
                current_app.logger.exception(str(exception))
                abort(400)

        Schedules2Cache.delete(role_id)

        return changes
Example #5
0
    def patch(self, org_id, location_id, role_id, user_id):
        parser = reqparse.RequestParser()
        parser.add_argument("min_hours_per_workweek", type=int)
        parser.add_argument("max_hours_per_workweek", type=int)
        parser.add_argument("internal_id", type=str)
        parser.add_argument("archived", type=inputs.boolean)
        parser.add_argument("activateReminder", type=inputs.boolean)
        parser.add_argument("working_hours", type=str)

        # Filter out null values
        changes = parser.parse_args(strict=True)
        changes = dict((k, v) for k, v in changes.iteritems() if v is not None)

        rtu = RoleToUser.query.filter_by(user_id=user_id,
                                         role_id=role_id).first()
        if rtu is None:
            abort(404)

        if "archived" in changes:
            if not g.current_user.is_sudo():
                return {
                    "message":
                    "You do not have permission to modify 'archived'."
                }, 401

            role = Role.query.get_or_404(role_id)
            if role.archived:
                return {"message": "The parent role is archived."}, 400

        elif rtu.archived:
            abort(400)

        # activation email reminder - it can't be committed to this RTU model though
        if "activateReminder" in changes:
            user = User.query.get_or_404(user_id)
            org = Organization.query.get_or_404(org_id)

            if user.active:
                return {"message": "This user is already active"}, 400

            user.send_activation_reminder(user, org.name)
            del changes["activateReminder"]

        # extract workweek limits and convert into half hour
        if "min_hours_per_workweek" in changes:
            min_half_hours_per_workweek = changes["min_hours_per_workweek"] * 2
        else:
            min_half_hours_per_workweek = rtu.min_half_hours_per_workweek

        if "max_hours_per_workweek" in changes:
            max_half_hours_per_workweek = changes["max_hours_per_workweek"] * 2
        else:
            max_half_hours_per_workweek = rtu.max_half_hours_per_workweek

        # some verification
        if min_half_hours_per_workweek > max_half_hours_per_workweek:
            return {
                "message":
                "min_hours_per_workweek cannot be greater than max_hours_per_workweek"
            }, 400

        if not (0 <= min_half_hours_per_workweek <= 336):
            return {
                "message": "min_hours_per_workweek cannot be less than 0"
            }, 400

        if not (0 <= max_half_hours_per_workweek <= 336):
            return {
                "message": "max_hours_per_workweek cannot be greater than 168"
            }, 400

        # the proper db term must be submitted if it is intended to be changed
        if "min_hours_per_workweek" in changes:
            del changes["min_hours_per_workweek"]
            changes[
                "min_half_hours_per_workweek"] = min_half_hours_per_workweek

        if "max_hours_per_workweek" in changes:
            del changes["max_hours_per_workweek"]
            changes[
                "max_half_hours_per_workweek"] = max_half_hours_per_workweek

        if changes.get("working_hours") is not None:
            try:
                working_hours = json.loads(changes.get("working_hours"))
            except:
                return {
                    "message": "Unable to parse working hours json body"
                }, 400
            if working_hours is None:
                return {
                    "message": "Unable to parse working hours json body"
                }, 400
            if not verify_days_of_week_struct(working_hours, True):
                return {
                    "message": "working hours is improperly formatted"
                }, 400

            g.current_user.track_event("modified_working_hours")

        for change, value in changes.iteritems():
            if value is not None:
                try:
                    setattr(rtu, change, value)
                    db.session.add(rtu)
                    db.session.commit()
                except Exception as exception:
                    db.session.rollback()
                    current_app.logger.exception(str(exception))
                    abort(400)

        g.current_user.track_event("modified_role_member")
        return changes