ari/jobsworth

View on GitHub
app/models/work_log.rb

Summary

Maintainability
B
4 hrs
Test Coverage
# encoding: UTF-8

# A work entry, belonging to a user & task
# Has a duration in seconds for work entries

class WorkLog < ActiveRecord::Base
  APPROVED = 1
  REJECTED = 2
  ['APPROVED', 'REJECTED'].each do |status_name|
    define_method(status_name.downcase + '?') { status == self.class.const_get(status_name) }
  end

  has_many(:custom_attribute_values, :as => :attributable, :dependent => :destroy,
           # set validate = false because validate method is over-ridden and does that for us
           :validate => false)
  include CustomAttributeMethods

  belongs_to :_user_, :class_name => 'User', :foreign_key => 'user_id'
  belongs_to :email_address
  belongs_to :company
  belongs_to :project
  belongs_to :customer
  belongs_to :task, :class_name => 'AbstractTask', :foreign_key => 'task_id'
  belongs_to :access_level

  has_one :ical_entry, :dependent => :destroy
  has_one :event_log, :as => :target, :dependent => :destroy
  has_many :email_deliveries
  has_many :project_files

  validates_presence_of :started_at
  validate :validate_logs

  delegate :recalculate_worked_minutes!, :to => :task, :allow_nil => true

  after_create :manage_associated_task
  after_update :update_associated_task_and_ical
  after_destroy :recalculate_worked_minutes!

  scope :worktimes, -> { where('work_logs.duration > 0') }
  scope :comments, -> { where("work_logs.body IS NOT NULL AND work_logs.body <> ''") }
  scope :duration_per_user, -> {
    select('work_logs.user_id, SUM(work_logs.duration) as duration, MIN(work_logs.started_at) as started_at')
        .group('work_logs.user_id')
  }

  #check all access rights for user
  scope :on_tasks_owned_by, lambda { |user|
    select('work_logs.*')
        .joins('INNER JOIN tasks ON work_logs.task_id = tasks.id
            INNER JOIN task_users ON work_logs.task_id = task_users.task_id')
        .where('task_users.user_id' => user)
  }

  scope :accessed_by, lambda { |user|
    readonly(false).joins(%q{
      JOIN projects ON work_logs.project_id = projects.id
      JOIN project_permissions ON project_permissions.project_id = projects.id
      JOIN users ON project_permissions.user_id= users.id}
    ).joins(:task).where(%q{
      projects.completed_at IS NULL AND
      users.id = ? AND
      ( project_permissions.can_see_unwatched = ? OR
        users.id IN ( SELECT task_users.user_id
                      FROM task_users
                      WHERE task_users.task_id=tasks.id )) AND
      work_logs.company_id = ? AND
      work_logs.access_level_id <= ? }, user.id, true, user.company_id, user.access_level_id
    )
  }

  scope :level_accessed_by, lambda { |user|
    where('work_logs.access_level_id <= ?', user.access_level_id)
  }

  scope :all_accessed_by, lambda { |user|
    readonly(false).joins(:task).joins(%q{
      JOIN project_permissions ON work_logs.project_id = project_permissions.project_id
      JOIN users               ON project_permissions.user_id = users.id}
    ).where(%q{
      users.id = ? AND
      ( project_permissions.can_see_unwatched = ? OR
        users.id IN (
          SELECT task_users.user_id
          FROM task_users
          WHERE task_users.task_id = tasks.id)) AND
      work_logs.access_level_id <= ?}, user.id, true, user.access_level_id
    )
  }

  ###
  # Creates and saves a worklog for the given task.
  # The newly created worklog is returned.
  # If anything goes wrong, raise an exception
  ###
  def self.create_task_created!(task, user)
    worklog = WorkLog.new(:user => user, :body => task.description)
    worklog.for_task(task)
    worklog.save!

    worklog.create_event_log(
        :user => user,
        :event_type => EventLog::TASK_CREATED,
        :company => worklog.company,
        :project => worklog.project
    )

    return worklog
  end

  # Builds a new (unsaved) work log for task using the given params
  # params must look like {:work_log=>{...},:comment=>""}
  # build only if we have :duration or :comment else retur false
  def self.build_work_added_or_comment(task, user, params=nil)
    work_log_params=params[:work_log].nil? ? {} : params[:work_log].clone
    if (work_log_params and !work_log_params[:duration].blank?) or (params and !params[:comment].blank?)
      unless params[:comment].blank?
        work_log_params[:body] = params[:comment]
      end
      if (user.option_tracktime.to_i == 1) and !work_log_params[:duration].blank?
        work_log_params[:duration] = TimeParser.parse_time(work_log_params[:duration])
        if work_log_params[:started_at].blank?
          work_log_params[:started_at] = Time.now.utc
        else
          work_log_params[:started_at] = TimeParser.date_from_string(user, work_log_params[:started_at])
        end
      else
        work_log_params[:duration] = 0
        work_log_params[:started_at]=Time.now.utc
      end
      work_log_params[:user] = user
      work_log_params[:company] = task.company
      work_log_params[:project] = task.project
      work_log_params[:customer] = (task.customers.first || task.project.customer)

      work_log = task.work_logs.build(work_log_params)
      event_log = work_log.create_event_log(
          :user => user,
          :event_type => work_log.worktime? ? EventLog::TASK_WORK_ADDED : EventLog::TASK_COMMENT,
          :project => task.project,
          :company => task.company
      )
      return work_log
    else
      return false
    end
  end

  def comment?
    self.body.present?
  end

  def comment_event_log?
    !event_log || event_log.event_type == EventLog::TASK_COMMENT
  end

  def worktime?
    self.duration > 0
  end

  def ended_at
    self.started_at + self.duration * 60
  end

  # Sets the associated customer using the given name
  def customer_name=(name)
    self.customer = company.customers.find_by(:name => name)
  end

  # Returns the name of the associated customer
  def customer_name
    customer.name if customer
  end

  def validate_logs
    validate_custom_attributes if self.worktime?
  end

  def notify(files=[])
    self.project_files = files unless files.empty?
    emails = (access_level_id > 1) ? [] : task.email_addresses.to_a
    users = task.users_to_notify(user).select { |user| user.access_level_id >= self.access_level_id }

    # only send to user once
    user_emails = users.collect { |u| u.email_addresses.collect { |ea| ea.email } }.flatten
    emails.reject! { |ea| user_emails.include?(ea.email) }

    emails += users.map { |u| u.email_addresses.detect { |pv| pv.default } }
    emails = emails.uniq.compact

    emails.each do |email|
      EmailDelivery.create!(status: 'queued', email: email.email, user: email.user, work_log: self)
    end
  end

  def for_task(task)
    if _user_.nil? and self.email_address.nil?
      self.email_address_id = task.updated_by_id
      self.user= User.where('email_addresses.id' => email_address_id).joins(:email_addresses).first
    end
    self.task=task
    self.project=task.project
    self.company= task.project.company
    self.customer= task.project.customer
    self.started_at= Time.now.utc
    self.duration = 0
    self
  end

  # create user accessor to rewrite user association
  def user
    if _user_.nil?
      User.new(:name => "Unknown User (#{email_address.try(:email)})", :email => email_address.try(:email), :company => company) #
    else
      _user_
    end
  end

  def user=(u)
    self._user_ = u
  end

  private
  def mark_unread_for_users
    q = task.task_users
            .joins(:user)
            .where('users.access_level_id >= ?', self.access_level_id)
    # Do not <> on NULL, see https://dev.mysql.com/doc/refman/5.0/en/working-with-null.html
    if self.user_id.present?
      q = q.where('task_users.user_id <> ?', self.user_id)
    end
    q.update_all(:unread => true)
  end

  def manage_associated_task
    recalculate_worked_minutes! if duration.to_i > 0
    mark_unread_for_users if comment?
    task.reopen! if comment? && task.done? && comment_event_log?
  end

  def update_associated_task_and_ical
    ical_entry.destroy if ical_entry
    recalculate_worked_minutes! if duration.to_i > 0
  end

end


# == Schema Information
#
# Table name: work_logs
#
#  id               :integer(4)      not null, primary key
#  user_id          :integer(4)      default(0)
#  task_id          :integer(4)
#  project_id       :integer(4)      default(0), not null
#  company_id       :integer(4)      default(0), not null
#  customer_id      :integer(4)      default(0), not null
#  started_at       :datetime        not null
#  duration         :integer(4)      default(0), not null
#  body             :text
#  exported         :datetime
#  status           :integer(4)      default(0)
#  access_level_id  :integer(4)      default(1)
#  email_address_id :integer(4)
#
# Indexes
#
#  work_logs_company_id_index                 (company_id)
#  work_logs_customer_id_index                (customer_id)
#  work_logs_project_id_index                 (project_id)
#  work_logs_task_id_index                    (task_id,log_type)
#  index_work_logs_on_task_id_and_started_at  (task_id,started_at)
#  work_logs_user_id_index                    (user_id,task_id)
#