hicknhack-software/redmine_hourglass

View on GitHub
app/models/hourglass/time_tracker.rb

Summary

Maintainability
A
2 hrs
Test Coverage
module Hourglass
  class TimeTracker < ApplicationRecord
    include Namespace
    include ProjectIssueSyncing

    belongs_to :user
    belongs_to :project
    belongs_to :issue
    belongs_to :activity, class_name: 'TimeEntryActivity', foreign_key: 'activity_id'
    has_one :fixed_version, through: :issue

    acts_as_customizable

    after_initialize :init
    before_update if: :project_id_changed? do
      update_round project
      true
    end

    validates_uniqueness_of :user_id
    validates_presence_of :user, :start
    validates_presence_of :project, if: Proc.new { |tt| tt.project_id.present? }
    validates_presence_of :issue, if: Proc.new { |tt| tt.issue_id.present? }
    validates_presence_of :activity, if: Proc.new { |tt| tt.activity_id.present? }
    validates_length_of :comments, maximum: 255, allow_blank: true
    validate :does_not_overlap_with_other, if: [:user, :start?], on: :update

    class << self
      alias_method :start, :create
    end

    def stop
      stop = Time.now.change(sec: 0) + 1.minute
      time_log = nil
      transaction(requires_new: true) do
        if start < stop
          time_log = TimeLog.create time_log_params.merge stop: stop
          raise ActiveRecord::Rollback unless time_log.persisted?
          time_booking = bookable? ? time_log.book(time_booking_params) : nil
          raise ActiveRecord::Rollback if time_booking && !time_booking.persisted?
        end
        destroy
      end
      time_log
    end

    def hours
      DateTimeCalculations.time_diff_in_hours start, Time.now.change(sec: 0) + 1.minute
    end

    def available_custom_fields
      CustomField.where("type = 'TimeEntryCustomField'").sorted.to_a
    end

    def validate_custom_field_values
      super unless new_record?
    end

    private
    def init
      now = Time.now.change sec: 0
      self.user ||= User.current
      previous_time_log = user.hourglass_time_logs.find_by(stop: now + 1.minute)
      self.project_id ||= issue && issue.project_id
      update_round project_id unless round.present?

      self.start ||= previous_time_log && previous_time_log.stop || now

      self.activity ||= user.default_activity(TimeEntryActivity.applicable(user.projects.find_by id: project_id)) if project_id
    end

    def time_log_params
      attributes.with_indifferent_access.slice :start, :user_id, :comments
    end

    def time_booking_params
      attributes.with_indifferent_access.slice(:project_id, :issue_id, :activity_id, :round)
          .merge custom_field_values: custom_field_values.inject({}) { |h, v| h[v.custom_field_id.to_s] = v.value; h }
    end

    def bookable?
      project.present?
    end

    def update_round(project = nil)
      self.round = !Hourglass::SettingsStorage[:round_sums_only, project: project] &&
          Hourglass::SettingsStorage[:round_default, project: project]
    end

    def does_not_overlap_with_other
      errors.add :base, :overlaps unless user.hourglass_time_logs.overlaps_with(start, Time.now).empty?
    end
  end
end