def ignored_assignment(laure_data: object, session: object) -> (bool, dict): """Perform necessary transition after photo set validation. :param laure_data: Python object representing Laure data after assignment validation :return: Flag indicating whether operation was successful, empty dict """ with transaction.manager: assignment_id = laure_data.assignment.id assignment = Assignment.get(assignment_id) if not assignment: logger.error('Got message for unknown assignment id {0}'.format( assignment_id)) return False, {} if assignment.state != 'asset_validation': logger.error( "Got message to transition assignment '{0}' which is in state '{1}'" .format(assignment_id, assignment.state)) return False, {} logger.info( "Assignment '{0}' assets validation ignored. Transitioning to 'in_qa'" .format(assignment_id)) assignment.workflow.context = SystemUser assignment.workflow.validate_assets(message='Ignored validation.') logger.info('Assignment {0} state set to {1}'.format( assignment.slug, assignment.state)) cache_region.invalidate(assignment) cache_region.invalidate(assignment.order) return True, {}
def _notify_late_submissions(assignment: Assignment) -> bool: """Create a new comment to let professionals know that 48hs passed after the shoot. Task name: leica.task.notify_late_submission Task events: * leica.task.notify_late_submission.success * leica.task.notify_late_submission.failure :param assignment: Assignment to be processed :return: True if a new notify comment was registered in the Assignment. """ status = False has_notify_comment = assignment.comments.filter( Comment.content == LATE_SUBMISSION_MSG, Comment.internal.is_(True), ).all() now = timezone_now('UTC') delta = now - assignment.scheduled_datetime config_delta = timedelta(seconds=int(LATE_SUBMISSION_SECONDS)) should_notify = delta > config_delta and delta.days <= int( LATE_SUBMISSION_MAX_DAYS) if assignment.state != 'awaiting_assets' or has_notify_comment or not should_notify: return status payload = dict( entity_id=assignment.id, entity_type=assignment.__class__.__name__, author_id=SystemUser.id, content=LATE_SUBMISSION_MSG, author_role='project_manager', to_role='professional_user', internal=True, ) try: comment = Comment(**payload) session = assignment.__session__ session.add(comment) assignment.comments.append(comment) session.flush() except Exception as exc: msg = 'Failure to add comment to assignment: {id}. Error: {exc}' logger.error(msg.format(id=assignment.id, exc=str(exc))) else: cache_region.invalidate(assignment) cache_region.invalidate(assignment.order) status = True task_name = 'leica.task.notify_late_submission' event = LeicaTaskEvent(task_name=task_name, success=status, obj=assignment) event() return status
def _notify_before_shooting(assignment: Assignment) -> bool: """Create a new comment to let professionals about scheduled datetime before shooting. Task name: leica.task.notify_before_shooting Task events: * leica.task.notify_before_shooting.success * leica.task.notify_before_shooting.failure :param assignment: Assignment to be processed :return: True if a new notify comment was registered in the Assignment. """ status = False now = timezone_now('UTC') has_notify_comment = assignment.comments.filter( Comment.content == BEFORE_SHOOTING_MSG, Comment.internal.is_(True), ).all() delta = assignment.scheduled_datetime - now config_delta = timedelta(seconds=int(BEFORE_SHOOTING_SECONDS)) should_notify = delta.days >= 0 and delta <= config_delta if assignment.state != 'scheduled' or not should_notify or has_notify_comment: return status payload = dict( entity_id=assignment.id, entity_type=assignment.__class__.__name__, author_id=SystemUser.id, content=BEFORE_SHOOTING_MSG, author_role='project_manager', to_role='professional_user', internal=True, ) try: comment = Comment(**payload) session = assignment.__session__ session.add(comment) assignment.comments.append(comment) session.flush() except Exception as exc: msg = 'Failure to add comment to assignment: {id}. Error: {exc}' logger.error(msg.format(id=assignment.id, exc=str(exc))) else: cache_region.invalidate(assignment) cache_region.invalidate(assignment.order) status = True task_name = 'leica.task.notify_before_shooting' event = LeicaTaskEvent(task_name=task_name, success=status, obj=assignment) event() return status
def _move_order_accepted(order: Order) -> bool: """Move Order from delivered to accepted after a certain amount of working days. Task name: leica.task.order_accepted Task events: * leica.task.order_accepted.success * leica.task.order_accepted.failure :param order: Order to be processed. :return: Status of the transition """ task_name = 'leica.task.order_accepted' status = False state = order.state last_deliver_date = order.last_deliver_date if state == 'delivered' and last_deliver_date: now = datetime_utcnow() project = order.project approval_window = project.approval_window allowed_accept_date = workday(last_deliver_date, approval_window) wf = order.workflow wf.context = SystemUser if now >= allowed_accept_date: if not wf.can_accept: assignment_states = [a.state for a in order.assignments] msg = 'Approval window for Order {id} expired but it can not be completed. ' \ 'Assignment states: {states}' msg = msg.format(id=order.id, states=str(assignment_states)) else: wf.accept( message= 'Order automatic accepted after the end of the approval window.' ) msg = 'Order {id} moved to completed. Delivery date: {deliver_date}' msg = msg.format(id=order.id, deliver_date=last_deliver_date) status = True # Trigger task event cache_region.invalidate(order) for assignment in order.assignments: cache_region.invalidate(assignment) event = LeicaTaskEvent(task_name=task_name, success=status, obj=order) event() logger.info(msg) return status
def approve_assignment(laure_data: object, session: object, copy_ignored: bool = False) -> (bool, dict): """Perform necessary updates after set was copied to destination folders. :param laure_data: Python object representing Laure data after assignment approval :param copy_ignored: Set when copying assets was ignored on ms.laure :return: Flag indicating if the operation was successful, empty dict """ status = False with transaction.manager: assignment_id = laure_data.assignment.id assignment = Assignment.get(assignment_id) if not assignment: logger.warn( 'Got assignment approval message for non existing assignment {0}' .format(assignment_id)) return False, {} order = assignment.order if not copy_ignored: delivery_info = update_delivery(order, laure_data) msg = 'Assets copied over on laure - committing delivery URL to order "{order_id}"' status = True else: # We need to get the existing delivery to execute the proper transition msg = 'Assets were a result of previous manual review and were not ' \ 'touched on ms.laure. Order "{order_id}" unchanged!' status = True delivery_info = order.delivery if order.state == 'in_qa' and assignment.state == 'approved': # force new instance object to make sure sqlalchemy will detect the change delivery = delivery_info.copy() fields = dict(delivery=delivery) wf = order.workflow wf.context = SystemUser wf.deliver(fields=fields, message='Assets automatic delivered.') cache_region.invalidate(assignment) cache_region.invalidate(order) logger.info(msg.format(order_id=assignment.order.id)) return status, {}
def asset_copy_malfunction(laure_data: object, session: object) -> (bool, dict): """Perform necessary transition and field update after photo set was deemed invalid. :param laure_data: Python object representing Laure data after assignment validation :return: Flag indicating if the operation was successful, empty dict """ with transaction.manager: assignment_id = laure_data.assignment.id assignment = Assignment.get(assignment_id) if not assignment: logger.warn( 'Got assignment message for non existing assignment {0}'. format(assignment_id)) return False, {} text = ( 'This assignment\'s assets could not be copied automatically!\n' 'Please take manual actions that may be needed.') comment = Comment( entity_id=assignment_id, entity_type=assignment.__class__.__name__, author_id=SystemUser.id, author_role='system', to_role='qa_manager', content=text, internal=True, ) session.add(comment) logger.warn( 'There was a problem copying assets to delivery or archive folders.' 'Adding comment to assignment {0}'.format(assignment_id)) comment.workflow_context = SystemUser assignment.workflow_context = SystemUser assignment.comments.append(comment) cache_region.invalidate(assignment) cache_region.invalidate(assignment.order) return True, {}
def invalidate_assignment(laure_data: object, session: object) -> (bool, dict): """Perform necessary transition and field update after photo set was deemed invalid. :param laure_data: Python object representing Laure data after assignment validation :return: Flag indicating whether operation was successful, empty dict """ with transaction.manager: assignment_id = laure_data.assignment.id assignment = Assignment.get(assignment_id) if not assignment: logger.error( 'Got a message for non-existing assignment id {0}'.format( assignment_id)) return False, {} if assignment.state != 'asset_validation': logger.error( "Got message to invalidate '{0}' which is in state '{1}'". format(assignment_id, assignment.state)) return False, {} feedback_text = '\n'.join( [item.complete_feedback for item in laure_data.validation]) logger.info( 'Assignment "{0}" assets reported as not sufficient. Transitioning back ' 'to "waiting_assets" and adding comments to assignment.'.format( assignment_id)) assignment.workflow_context = SystemUser assignment.workflow.invalidate_assets(message=feedback_text) logger.info('Assignment {0} state set to {1}'.format( assignment.slug, assignment.state)) cache_region.invalidate(assignment) cache_region.invalidate(assignment.order) return True, {}
def move_assignments_awaiting_assets(): """Move Assignments from scheduled to awaiting_assets.""" query = Assignment.query().filter(Assignment.state == 'scheduled').all() assignments = [] for item in query: now = timezone_now('UTC') if item.scheduled_datetime and item.scheduled_datetime < now: assignments.append(item) logger.info( 'Total assignments to be moved: {size}'.format(size=len(assignments))) total_moved = 0 for assignment in assignments: status = _move_assignment_awaiting_assets(assignment) total_moved += 1 if status else 0 cache_region.invalidate(assignment) cache_region.invalidate(assignment.order) logger.info('Total assignments moved to awaiting assets: {total}'.format( total=total_moved))
def _move_assignment_awaiting_assets(assignment: Assignment) -> bool: """Move Assignments from scheduled to awaiting_assets. Task name: leica.task.assignment_awaiting_assets Task events: * leica.task.assignment_awaiting_assets.success * leica.task.assignment_awaiting_assets.failure :param assignment: Assignment to be processed :return: Status of the transition """ task_name = 'leica.task.assignment_awaiting_assets' now = timezone_now('UTC') status = False if assignment.state == 'scheduled' and assignment.scheduled_datetime < now: wf = assignment.workflow wf.context = SystemUser try: wf.ready_for_upload() except WorkflowTransitionException as e: logger.exception( 'Assignment {id} not moved to awaiting_assets.'.format( id=assignment.id)) else: status = True logger.info( 'Assignment {id} moved to awaiting_assets. Shoot time: {shoot_time}' .format(id=assignment.id, shoot_time=assignment.scheduled_datetime)) cache_region.invalidate(assignment) event = LeicaTaskEvent(task_name=task_name, success=status, obj=assignment) event() return status
def project_after_update(mapper, connection, target): """Invalidate Project cache after instance update.""" project = target cache_region.invalidate(project)