def delete(self, org_id, location_id, user_id): """removes the user from the management position""" organization = Organization.query.get_or_404(org_id) location = Location.query.get_or_404(location_id) user = User.query.get_or_404(user_id) if not user.is_location_manager(location_id): return {"message": "User does not exist or is not a manager"}, 404 location.managers.remove(user) try: db.session.commit() except Exception as exception: db.session.rollback() current_app.logger.exception(str(exception)) abort(400) alert_email( user, "You have been removed as a %s manager at %s" % (location.name, organization.name), "You have been removed as a manager at the %s location of %s" % (location.name, organization.name)) return {}, 204
def _deactivate_expired_organizations(self): orgs_to_deactivate = Organization.query\ .filter_by(active=True)\ .filter( and_( or_( Organization.paid_until == None, Organization.paid_until < func.now() ), func.timestampdiff( sqltext("SECOND"), Organization.created_at, func.now(), ) > (Organization.trial_days * constants.SECONDS_PER_DAY), ) ) for org in orgs_to_deactivate: manager_url = url_for('manager.manager_app', org_id=org.id, _external=True) + "#settings" # alert admins of deactivation for admin in org.admins: alert_email( admin, "[Action Required] %s scheduling is on hold" % org.name, "In order to continue scheduling, please set up billing at:<br><a href='%s'>%s</a>" % (manager_url, manager_url)) org.active = False current_app.logger.info( "Deactivated org %s because it is unpaid and the trial is over" % org.id) db.session.commit()
def post(self, org_id, location_id): """ Add a user as a manager either by user id or email """ parser = reqparse.RequestParser() parser.add_argument("email", type=str) parser.add_argument("id", type=int) parser.add_argument("name", type=str) parameters = parser.parse_args(strict=True) organization = Organization.query.get_or_404(org_id) location = Location.query.get_or_404(location_id) 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") if "@" not in email: abort(400) # Check if user has email user = User.query.filter_by(email=email.lower()).first() # otherwise invite by email if user is None: user = User.create_and_invite(email, parameters.get("name"), organization.name) else: return {"message": "unable to identify user"}, 400 if user.is_location_manager(location_id): return {"message": "user already an admin"}, 400 location.managers.append(user) try: db.session.commit() except Exception as exception: db.session.rollback() current_app.logger.exception(str(exception)) abort(400) alert_email( user, "You have been added as a %s manager at %s" % (location.name, organization.name), "You have been added as a manager at the %s location of %s." % (location.name, organization.name)) return marshal(user, user_fields), 201
def _close_abandoned_timeclocks(self): """Close timeclocks if they have been open for more than 23 hours """ cutoff_start = datetime.utcnow() - timedelta( hours=constants.MAX_TIMECLOCK_HOURS) timeclocks_to_close = Timeclock.query\ .filter( Timeclock.stop==None, cutoff_start > Timeclock.start, )\ .all() for timeclock in timeclocks_to_close: timeclock.stop = timeclock.start + timedelta( hours=constants.MAX_TIMECLOCK_HOURS) db.session.commit() current_app.logger.info( "Closed abandoned timeclock id %s (user %s)" % (timeclock.id, timeclock.user_id)) user = User.query.get(timeclock.user_id) alert_email( user, "You have been automatically clocked out", "You have been clocked in for over %s hours, so you have been automatically clocked out." % constants.MAX_TIMECLOCK_HOURS) if user.phone_number: user.send_sms( "You have been automatically clocked out by Staffjoy after being clocked in for %s hours" % constants.MAX_TIMECLOCK_HOURS) role = Role.query.get(timeclock.role_id) location = Location.query.get(role.location_id) org = Organization.query.get(location.organization_id) location.send_manager_email( "[Action Required] %s forgot to clock out" % user.name, "%s (%s) forgot to clock out of their last shift, so Staffjoy just automatically clocked them out after %s hours. Please review and adjust their timeclock on the %s attendance page." % (user.name, user.email, constants.MAX_TIMECLOCK_HOURS, location.name), url_for("manager.manager_app", org_id=org.id, _external=True) + "#locations/%s/attendance" % location.id)
def delete(self, org_id, user_id): organization = Organization.query.get_or_404(org_id) user = User.query.get_or_404(user_id) if not user.is_org_admin(org_id): return {"message": "user does not exist or not an admin"}, 404 organization.admins.remove(user) try: db.session.commit() except Exception as exception: db.session.rollback() current_app.logger.exception(str(exception)) abort(400) # Force send because this could have security implications alert_email( user, "You have been removed as an administator of %s on Staffjoy" % organization.name, "You have been removed as an organization administrator of the Staffjoy account for %s." % organization.name, force_send=True) return {}, 204
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
def delete(self, org_id, location_id, role_id, user_id): user = User.query.get_or_404(user_id) role = Role.query.get_or_404(role_id) assoc = RoleToUser.query.filter_by(user_id=user.id, role_id=role.id).first() if assoc is None: abort(404) if assoc.archived: abort(400) assoc.archived = True try: db.session.commit() except: abort(500) location = Location.query.get(location_id) organization = Organization.query.get(org_id) # Set future shifts to unassigned # Be careful to not unassign them from other orgs! future_shifts = Shift2.query.filter( Shift2.user_id == user.id, Shift2.role_id == role_id, Shift2.start > datetime.datetime.utcnow(), ).all() for shift in future_shifts: shift.user_id = None # clear cache too schedule = Schedule2.query \ .filter( Schedule2.role_id == role_id, Schedule2.start <= shift.start, Schedule2.stop > shift.start, ).first() if schedule is not None: Shifts2Cache.delete(schedule.id) # deny future time off requests that are open future_time_off_requests = TimeOffRequest.query \ .filter_by(role_to_user_id=assoc.id) \ .filter_by(state=None) \ .filter( TimeOffRequest.start > datetime.datetime.utcnow(), ) \ .all() for time_off_request in future_time_off_requests: time_off_request.state = "denied" # unassign all recurring shifts recurring_shifts = RecurringShift.query \ .filter_by( role_id=role_id, user_id=user_id ) \ .all() for recurring_shift in recurring_shifts: current_app.logger.info( "Setting recurring shift %s to unassigned because user %s is being removed from role %s" % (recurring_shift.id, user_id, role_id)) recurring_shift.user_id = None # close open timeclocks timeclocks = Timeclock.query \ .filter_by( role_id=role_id, user_id=user_id, stop=None ) \ .all() for timeclock in timeclocks: original_start = timeclock.start original_stop = timeclock.stop timeclock.stop = datetime.datetime.utcnow() current_app.logger.info( "Closing timeclock %s because user %s is being removed from role %s" % (timeclock.id, user_id, role_id)) alert_timeclock_change(timeclock, org_id, location_id, role_id, original_start, original_stop, user, g.current_user) alert_email( user, "You have been removed from a team at %s" % organization.name, "You have been removed from the team <b>%s</b> at the <b>%s</b> location of <b>%s</b>. This may happen as the scheduling manager changes your role or location." % (role.name, location.name, organization.name), force_send=True) g.current_user.track_event("deleted_role_member") return {}, 204
def patch(self, org_id, location_id, role_id, user_id, time_off_request_id): """ modifies an existing time_off_request record NOTE that start and stop cannot be modified """ parser = reqparse.RequestParser() parser.add_argument("state", type=str) parser.add_argument("minutes_paid", type=int) parameters = parser.parse_args() # Filter out null values parameters = dict( (k, v) for k, v in parameters.iteritems() if v is not None) changes = {} time_off_request = TimeOffRequest.query.get_or_404(time_off_request_id) role_to_user = RoleToUser.query.get(time_off_request.role_to_user_id) user = User.query.get(user_id) location = Location.query.get(location_id) org = Organization.query.get(org_id) # verify for state state = parameters.get("state", time_off_request.state) if state not in [ None, "", "approved_paid", "approved_unpaid", "sick", "denied" ]: return {"message": "Invalid time off request state"}, 400 if "state" in parameters: # state can be set to None - which get parsed through as an empty string if not parameters["state"]: changes["state"] = None changes["approver_user_id"] = None else: changes["state"] = parameters["state"] # log the approver if its an administrator if g.current_user.is_org_admin_or_location_manager( org_id, location_id): changes["approver_user_id"] = g.current_user.id else: changes["approver_user_id"] = None # verification for minutes_paid minutes_paid = parameters.get("minutes_paid", time_off_request.minutes_paid) duration = int( (time_off_request.stop - time_off_request.start).total_seconds()) if not (0 <= minutes_paid * 60 <= duration): return { "message": "minutes_paid must be within the duration of the day" }, 400 if minutes_paid > 0 and state == "approved_unpaid": return { "message": "Unpaid time off requests cannot have a minutes_paid greater than 0" }, 400 if minutes_paid == 0 and state == "approved_paid": return { "message": "Paid time off requests must have a postitive minutes_paid" }, 400 if state is None and minutes_paid != 0: return { "message": "Cannot have minutes_paid greater than 0 for time off requests with an undefined state" }, 400 if "minutes_paid" in parameters: changes["minutes_paid"] = parameters["minutes_paid"] for change, value in changes.iteritems(): try: setattr(time_off_request, 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("time_off_request_modified") # unassign shifts that overlap if changes.get("state") is not None and time_off_request.state in [ "sick", "approved_paid", "approved_unpaid" ]: time_off_request.unassign_overlapping_shifts() # send an email to the user whenever the approved state changes # only send an email for unarchived workers and # the time off request is in the future if changes.get("state") is not None \ and not role_to_user.archived \ and time_off_request.start > datetime.datetime.utcnow(): default_tz = get_default_tz() local_tz = location.timezone_pytz start_local = default_tz.localize( time_off_request.start).astimezone(local_tz) display_date = start_local.strftime("%A, %B %-d") # calculate myschedules url week_start_date = org.get_week_start_from_datetime( start_local).strftime("%Y-%m-%d") myschedules_url = "%s#week/%s" % (url_for( 'myschedules.myschedules_app', org_id=org_id, location_id=location_id, role_id=role_id, user_id=user_id, _external=True), week_start_date) # prepare subject and starting body - each state can be fine tuned if time_off_request.state == "denied": subject = "Your time off request on %s has been denied" % display_date body = "Your time off request on %s has been denied" % display_date elif time_off_request.state == "approved_paid": subject = "Your time off request on %s has been approved" % display_date body = "You have been approved for paid time off on %s" % display_date elif time_off_request.state == "approved_unpaid": subject = "Your time off request on %s has been approved" % display_date body = "You have been approved for unpaid time off on %s" % display_date elif time_off_request.state == "sick": subject = "Your time off request on %s has been approved" % display_date body = "You have been approved to take a sick day on %s" % display_date # add in approval info if it is avilable if time_off_request.approver_user_id: approval_user = User.query.get( time_off_request.approver_user_id) approval_name = approval_user.email if approval_user.name is None else approval_user.email body += " by %s." % approval_name else: body += "." body += " Visit My Schedules to learn more:<br><a href=\"%s\">%s</a>" % ( myschedules_url, myschedules_url) alert_email(user, subject, body) return changes