def handle_cycle_created(obj, manually): today = datetime.date.today() if manually: create_notification = "manual_cycle_created" else: create_notification = "cycle_created" pusher.update_or_create_notifications(obj, today, create_notification) if not obj.is_current: return notification_names = [ get_notif_name_by_wf(obj.workflow), create_notification, "cycle_task_overdue", "cycle_task_due_today" ] tasks = list(obj.cycle_task_group_object_tasks) query = pusher.get_notification_query( *tasks, **{"notification_names": notification_names}) exists_notifications = collections.defaultdict(set) for notification in query: exists_notifications[notification.notification_type_id].add( notification.object_id) for notification_type in pusher.get_notification_types( *notification_names): object_ids = exists_notifications[notification_type.id] notify_tasks = [ t for t in tasks if not (t.id in object_ids or t.is_done) ] if create_notification == notification_type.name: pusher.create_notifications_for_objects(notification_type, today, *notify_tasks) else: for task in notify_tasks: pusher.create_notifications_for_objects( notification_type, task.end_date, task)
def handle_cycle_created(obj, manually): today = datetime.date.today() if manually: create_notification = "manual_cycle_created" else: create_notification = "cycle_created" pusher.update_or_create_notifications(obj, today, create_notification) if not obj.is_current: return notification_names = [get_notif_name_by_wf(obj.workflow), create_notification, "cycle_task_overdue", "cycle_task_due_today"] tasks = list(obj.cycle_task_group_object_tasks) query = pusher.get_notification_query( *tasks, **{"notification_names": notification_names}) exists_notifications = collections.defaultdict(set) for notification in query: exists_notifications[notification.notification_type_id].add( notification.object_id) for notification_type in pusher.get_notification_types(*notification_names): object_ids = exists_notifications[notification_type.id] notify_tasks = [t for t in tasks if not (t.id in object_ids or t.is_done)] if create_notification == notification_type.name: pusher.create_notifications_for_objects(notification_type, today, *notify_tasks) else: for task in notify_tasks: pusher.create_notifications_for_objects(notification_type, task.end_date, task)
def not_done_tasks_notify(tasks, day): """Notification handling for tasks that moved to active state. It will - create notification for tasks if it's needed - delete all notifications for related cycles """ if not tasks: return cycles = {task.cycle for task in tasks} # delete all notifications for cycles about all tasks completed if cycles: pusher.get_notification_query( *(list(cycles)), **{ "notification_names": ["all_cycle_tasks_completed"] }).delete(synchronize_session=False) # recreate notifications if it's necessary for task in tasks: pusher.update_or_create_notifications(task, task.end_date, get_notif_name_by_wf( task.cycle.workflow), "cycle_task_due_today", "cycle_task_overdue", day=day)
def not_done_tasks_notify(tasks, day): """Notification handling for tasks that moved to active state. It will - create notification for tasks if it's needed - delete all notifications for related cycles """ if not tasks: return cycles = {task.cycle for task in tasks} # delete all notifications for cycles about all tasks completed if cycles: pusher.get_notification_query( *(list(cycles)), **{"notification_names": ["all_cycle_tasks_completed"]} ).delete( synchronize_session=False ) # recreate notifications if it's necessary for task in tasks: pusher.update_or_create_notifications( task, task.end_date, get_notif_name_by_wf(task.cycle.workflow), "cycle_task_due_today", "cycle_task_overdue", day=day )
def build_cycle(workflow, cycle=None, current_user=None): """Build a cycle with it's child objects""" if not workflow.tasks: logger.error("Starting a cycle has failed on Workflow with " "slug == '%s' and id == '%s'", workflow.slug, workflow.id) pusher.update_or_create_notifications(workflow, date.today(), "cycle_start_failed") return # Determine the relevant Workflow cycle = cycle or models.Cycle() # Use Admin role when this is called via the cron job. if not current_user: current_user = workflow.get_persons_for_rolename("Admin")[0] # Populate the top-level Cycle object cycle.workflow = workflow cycle.is_current = True cycle.context = workflow.context cycle.title = workflow.title cycle.description = workflow.description cycle.is_verification_needed = workflow.is_verification_needed cycle.status = models.Cycle.ASSIGNED # Populate CycleTaskGroups based on Workflow's TaskGroups for task_group in workflow.task_groups: cycle_task_group = models.CycleTaskGroup( context=cycle.context, cycle=cycle, task_group=task_group, title=task_group.title, description=task_group.description, end_date=cycle.end_date, modified_by=current_user, contact=task_group.contact, status=models.CycleTaskGroup.ASSIGNED, sort_index=task_group.sort_index, ) # preserve the old cycle creation for old workflows, so each object # gets its own cycle task if workflow.is_old_workflow: create_old_style_cycle(cycle, task_group, cycle_task_group, current_user) else: for task_group_task in task_group.task_group_tasks: cycle_task_group_object_task = _create_cycle_task( task_group_task, cycle, cycle_task_group, current_user) for task_group_object in task_group.task_group_objects: object_ = task_group_object.object Relationship(source=cycle_task_group_object_task, destination=object_) update_cycle_dates(cycle) workflow.repeat_multiplier += 1 workflow.next_cycle_start_date = workflow.calc_next_adjusted_date( workflow.min_task_start_date) return cycle
def handle_cycle_task_group_object_task_put(obj): if inspect(obj).attrs._access_control_list.history.has_changes(): types = [ "cycle_task_reassigned", "cycle_created", "manual_cycle_created" ] if not pusher.notification_exists_for(obj, notification_names=types): pusher.push(obj, pusher.get_notification_type("cycle_task_reassigned")) if inspect(obj).attrs.end_date.history.has_changes() and not obj.is_done: # if you change end date to past than overdue # notification should be send today pusher.update_or_create_notifications( obj, obj.end_date, get_notif_name_by_wf(obj.cycle.workflow), "cycle_task_due_today", "cycle_task_overdue")
def handle_cycle_task_group_object_task_put(obj): if inspect(obj).attrs._access_control_list.history.has_changes(): types = ["cycle_task_reassigned", "cycle_created", "manual_cycle_created"] if not pusher.notification_exists_for(obj, notification_names=types): pusher.push(obj, pusher.get_notification_type("cycle_task_reassigned")) if inspect(obj).attrs.end_date.history.has_changes() and not obj.is_done: # if you change end date to past than overdue # notification should be sent today pusher.update_or_create_notifications( obj, obj.end_date, get_notif_name_by_wf(obj.cycle.workflow), "cycle_task_due_today", "cycle_task_overdue")
def handle_workflow_modify(sender, obj=None, src=None, service=None): """Update or add notifications on a workflow update.""" if obj.status != obj.ACTIVE or obj.unit is None: return if not obj.next_cycle_start_date: obj.next_cycle_start_date = obj.min_task_start_date pusher.update_or_create_notifications( obj, obj.next_cycle_start_date, "{}_workflow_starts_in".format(obj.unit), "cycle_start_failed") query = pusher.get_notification_query( *list(obj.tasks), **{"notification_names": ["cycle_start_failed"]}) notif_type = pusher.get_notification_type("cycle_start_failed") exist_task_ids = set(query.distinct(Notification.object_id)) for task in obj.tasks: if task.id not in exist_task_ids: pusher.push(task, notif_type, task.start_date)
def handle_cycle_created(obj, manually): today = datetime.date.today() if manually: create_notification = "manual_cycle_created" else: create_notification = "cycle_created" pusher.update_or_create_notifications(obj, today, create_notification) if not obj.is_current: return task_notif_name = get_notif_name_by_wf(obj.workflow) notification_names = [ task_notif_name, create_notification, "cycle_task_overdue", "cycle_task_due_today" ] notif_dict = [pusher.get_notification_type(n) for n in notification_names] tasks = list(obj.cycle_task_group_object_tasks) query = pusher.get_notification_query( *tasks, **{"notification_names": notification_names}) exists_notifications = collections.defaultdict(set) for notification in query: exists_notifications[notification.notification_type_id].add( notification.object_id) for notification_type in notif_dict: object_ids = exists_notifications[notification_type.id] repeatable_notification = (notification_type.name in pusher.REPEATABLE_NOTIFICATIONS) for task in tasks: if task.id in object_ids or task.is_done: continue if create_notification == notification_type.name: send_on = today else: send_on = task.end_date send_on -= datetime.timedelta(notification_type.advance_notice) if repeatable_notification: send_on = max(send_on, today) if send_on >= today: pusher.push(task, notification_type, send_on, repeatable_notification)
def handle_cycle_created(obj, manually): today = datetime.date.today() if manually: create_notification = "manual_cycle_created" else: create_notification = "cycle_created" pusher.update_or_create_notifications(obj, today, create_notification) if not obj.is_current: return task_notif_name = get_notif_name_by_wf(obj.workflow) notification_names = [task_notif_name, create_notification, "cycle_task_overdue", "cycle_task_due_today"] notif_dict = [pusher.get_notification_type(n) for n in notification_names] tasks = list(obj.cycle_task_group_object_tasks) query = pusher.get_notification_query( *tasks, **{"notification_names": notification_names}) exists_notifications = collections.defaultdict(set) for notification in query: exists_notifications[notification.notification_type_id].add( notification.object_id) for notification_type in notif_dict: object_ids = exists_notifications[notification_type.id] repeatable_notification = ( notification_type.name in pusher.REPEATABLE_NOTIFICATIONS ) for task in tasks: if task.id in object_ids or task.is_done: continue if create_notification == notification_type.name: send_on = today else: send_on = task.end_date send_on -= datetime.timedelta(notification_type.advance_notice) if repeatable_notification: send_on = max(send_on, today) if send_on >= today: pusher.push(task, notification_type, send_on, repeatable_notification)
def handle_cycle_task_status_change(obj): if obj.is_done: query = pusher.get_notification_query(obj) query.delete(synchronize_session="fetch") # if all tasks are in inactive states then add notification to cycle if all(task.is_done for task in obj.cycle.cycle_task_group_object_tasks): pusher.update_or_create_notifications(obj.cycle, datetime.date.today(), "all_cycle_tasks_completed") else: pusher.get_notification_query( obj.cycle, **{ "notification_names": ["all_cycle_tasks_completed"] }).delete(synchronize_session="fetch") if obj.status == models.CycleTaskGroupObjectTask.DECLINED: pusher.update_or_create_notifications(obj, datetime.date.today(), "cycle_task_declined") pusher.update_or_create_notifications( obj, obj.end_date, get_notif_name_by_wf(obj.cycle.workflow), "cycle_task_due_today", "cycle_task_overdue")
def handle_cycle_task_status_change(obj): if obj.is_done: query = pusher.get_notification_query(obj) query.delete(synchronize_session="fetch") # if all tasks are in inactive states then add notification to cycle if all(task.is_done for task in obj.cycle.cycle_task_group_object_tasks): pusher.update_or_create_notifications( obj.cycle, datetime.date.today(), "all_cycle_tasks_completed") else: pusher.get_notification_query( obj.cycle, **{"notification_names": ["all_cycle_tasks_completed"]} ).delete(synchronize_session="fetch") if obj.status == models.CycleTaskGroupObjectTask.DECLINED: pusher.update_or_create_notifications( obj, datetime.date.today(), "cycle_task_declined") pusher.update_or_create_notifications( obj, obj.end_date, get_notif_name_by_wf(obj.cycle.workflow), "cycle_task_due_today", "cycle_task_overdue")
def build_cycle(workflow, cycle=None, current_user=None): """Build a cycle with it's child objects""" build_failed = False if not workflow.tasks: logger.error("Starting a cycle has failed on Workflow with " "slug == '%s' and id == '%s', due to empty setup", workflow.slug, workflow.id) build_failed = True # Use Admin role when this is called via the cron job. if not current_user: admins = workflow.get_persons_for_rolename("Admin") if admins: current_user = admins[0] else: logger.error("Cannot start cycle on Workflow with slug == '%s' and " "id == '%s', cause it doesn't have Admins", workflow.slug, workflow.id) build_failed = True if build_failed: pusher.update_or_create_notifications(workflow, date.today(), "cycle_start_failed") return # Determine the relevant Workflow cycle = cycle or models.Cycle() # Populate the top-level Cycle object cycle.workflow = workflow cycle.is_current = True cycle.context = workflow.context cycle.title = workflow.title cycle.description = workflow.description cycle.is_verification_needed = workflow.is_verification_needed cycle.status = models.Cycle.ASSIGNED # Populate CycleTaskGroups based on Workflow's TaskGroups for task_group in workflow.task_groups: cycle_task_group = models.CycleTaskGroup( context=cycle.context, cycle=cycle, task_group=task_group, title=task_group.title, description=task_group.description, end_date=cycle.end_date, modified_by=current_user, contact=task_group.contact, status=models.CycleTaskGroup.ASSIGNED, ) # preserve the old cycle creation for old workflows, so each object # gets its own cycle task if workflow.is_old_workflow: create_old_style_cycle(cycle, task_group, cycle_task_group, current_user) else: for task_group_task in task_group.task_group_tasks: cycle_task_group_object_task = _create_cycle_task( task_group_task, cycle, cycle_task_group, current_user) related_objs = [obj for obj in task_group.related_objects() if not isinstance(obj, ( all_models.TaskGroupTask, all_models.Workflow ))] for obj in related_objs: Relationship(source=cycle_task_group_object_task, destination=obj) update_cycle_dates(cycle) workflow.repeat_multiplier += 1 workflow.next_cycle_start_date = workflow.calc_next_adjusted_date( workflow.min_task_start_date) return cycle
def build_cycle(workflow, cycle=None, current_user=None): """Build a cycle with it's child objects""" build_failed = False if not workflow.tasks: logger.error("Starting a cycle has failed on Workflow with " "slug == '%s' and id == '%s', due to empty setup", workflow.slug, workflow.id) build_failed = True # Use Admin role when this is called via the cron job. if not current_user: admins = workflow.get_persons_for_rolename("Admin") if admins: current_user = admins[0] else: logger.error("Cannot start cycle on Workflow with slug == '%s' and " "id == '%s', cause it doesn't have Admins", workflow.slug, workflow.id) build_failed = True if build_failed: pusher.update_or_create_notifications(workflow, date.today(), "cycle_start_failed") return # Determine the relevant Workflow cycle = cycle or models.Cycle() # Populate the top-level Cycle object cycle.workflow = workflow cycle.is_current = True cycle.context = workflow.context cycle.title = workflow.title cycle.description = workflow.description cycle.is_verification_needed = workflow.is_verification_needed cycle.status = models.Cycle.ASSIGNED # Populate CycleTaskGroups based on Workflow's TaskGroups for task_group in workflow.task_groups: cycle_task_group = models.CycleTaskGroup( context=cycle.context, cycle=cycle, task_group=task_group, title=task_group.title, description=task_group.description, end_date=cycle.end_date, modified_by=current_user, contact=task_group.contact, status=models.CycleTaskGroup.ASSIGNED, sort_index=task_group.sort_index, ) # preserve the old cycle creation for old workflows, so each object # gets its own cycle task if workflow.is_old_workflow: create_old_style_cycle(cycle, task_group, cycle_task_group, current_user) else: for task_group_task in task_group.task_group_tasks: cycle_task_group_object_task = _create_cycle_task( task_group_task, cycle, cycle_task_group, current_user) for task_group_object in task_group.task_group_objects: object_ = task_group_object.object Relationship(source=cycle_task_group_object_task, destination=object_) update_cycle_dates(cycle) workflow.repeat_multiplier += 1 workflow.next_cycle_start_date = workflow.calc_next_adjusted_date( workflow.min_task_start_date) return cycle
def build_cycle(workflow, cycle=None, current_user=None): """Build a cycle with it's child objects""" if not workflow.tasks: logger.error("Starting a cycle has failed on Workflow with " "slug == '%s' and id == '%s'", workflow.slug, workflow.id) pusher.update_or_create_notifications(workflow, date.today(), "cycle_start_failed") return # Determine the relevant Workflow cycle = cycle or models.Cycle() # Use WorkflowOwner role when this is called via the cron job. if not current_user: for user_role in workflow.context.user_roles: if user_role.role.name == "WorkflowOwner": current_user = user_role.person break # Populate the top-level Cycle object cycle.workflow = workflow cycle.is_current = True cycle.context = workflow.context cycle.title = workflow.title cycle.description = workflow.description cycle.is_verification_needed = workflow.is_verification_needed cycle.status = models.Cycle.ASSIGNED # Populate CycleTaskGroups based on Workflow's TaskGroups for task_group in workflow.task_groups: cycle_task_group = models.CycleTaskGroup( context=cycle.context, cycle=cycle, task_group=task_group, title=task_group.title, description=task_group.description, end_date=cycle.end_date, modified_by=current_user, contact=task_group.contact, status=models.CycleTaskGroup.ASSIGNED, sort_index=task_group.sort_index, ) # preserve the old cycle creation for old workflows, so each object # gets its own cycle task if workflow.is_old_workflow: create_old_style_cycle(cycle, task_group, cycle_task_group, current_user) else: for task_group_task in task_group.task_group_tasks: cycle_task_group_object_task = _create_cycle_task( task_group_task, cycle, cycle_task_group, current_user) for task_group_object in task_group.task_group_objects: object_ = task_group_object.object Relationship(source=cycle_task_group_object_task, destination=object_) update_cycle_dates(cycle) Signals.workflow_cycle_start.send( cycle.__class__, obj=cycle, new_status=cycle.status, old_status=None ) workflow.repeat_multiplier += 1 workflow.next_cycle_start_date = workflow.calc_next_adjusted_date( workflow.min_task_start_date) return cycle