"Fossies" - the Fresh Open Source Software Archive

Member "redmine-4.1.1/app/models/time_entry.rb" (6 Apr 2020, 8383 Bytes) of package /linux/www/redmine-4.1.1.tar.gz:


As a special service "Fossies" has tried to format the requested text file into HTML format (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "time_entry.rb": 4.1.0_vs_4.1.1.

    1 # frozen_string_literal: true
    2 
    3 # Redmine - project management software
    4 # Copyright (C) 2006-2019  Jean-Philippe Lang
    5 #
    6 # This program is free software; you can redistribute it and/or
    7 # modify it under the terms of the GNU General Public License
    8 # as published by the Free Software Foundation; either version 2
    9 # of the License, or (at your option) any later version.
   10 #
   11 # This program is distributed in the hope that it will be useful,
   12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14 # GNU General Public License for more details.
   15 #
   16 # You should have received a copy of the GNU General Public License
   17 # along with this program; if not, write to the Free Software
   18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
   19 
   20 class TimeEntry < ActiveRecord::Base
   21   include Redmine::SafeAttributes
   22   # could have used polymorphic association
   23   # project association here allows easy loading of time entries at project level with one database trip
   24   belongs_to :project
   25   belongs_to :issue
   26   belongs_to :user
   27   belongs_to :author, :class_name => 'User'
   28   belongs_to :activity, :class_name => 'TimeEntryActivity'
   29 
   30   acts_as_customizable
   31   acts_as_event :title =>
   32                   Proc.new {|o|
   33                     related   = o.issue if o.issue && o.issue.visible?
   34                     related ||= o.project
   35                     "#{l_hours(o.hours)} (#{related.event_title})"
   36                   },
   37                 :url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}},
   38                 :author => :user,
   39                 :group => :issue,
   40                 :description => :comments
   41 
   42   acts_as_activity_provider :timestamp => "#{table_name}.created_on",
   43                             :author_key => :user_id,
   44                             :scope => joins(:project).preload(:project)
   45 
   46   validates_presence_of :author_id, :user_id, :activity_id, :project_id, :hours, :spent_on
   47   validates_presence_of :issue_id, :if => lambda { Setting.timelog_required_fields.include?('issue_id') }
   48   validates_presence_of :comments, :if => lambda { Setting.timelog_required_fields.include?('comments') }
   49   validates_numericality_of :hours, :allow_nil => true, :message => :invalid
   50   validates_length_of :comments, :maximum => 1024, :allow_nil => true
   51   validates :spent_on, :date => true
   52   before_validation :set_project_if_nil
   53   #TODO: remove this, author should be always explicitly set
   54   before_validation :set_author_if_nil
   55   validate :validate_time_entry
   56 
   57   scope :visible, lambda {|*args|
   58     joins(:project).
   59     where(TimeEntry.visible_condition(args.shift || User.current, *args))
   60   }
   61   scope :left_join_issue, lambda {
   62     joins("LEFT OUTER JOIN #{Issue.table_name} ON #{Issue.table_name}.id = #{TimeEntry.table_name}.issue_id")
   63   }
   64   scope :on_issue, lambda {|issue|
   65     joins(:issue).
   66     where("#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}")
   67   }
   68 
   69   safe_attributes 'user_id', 'hours', 'comments', 'project_id', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields'
   70 
   71   # Returns a SQL conditions string used to find all time entries visible by the specified user
   72   def self.visible_condition(user, options={})
   73     Project.allowed_to_condition(user, :view_time_entries, options) do |role, user|
   74       if role.time_entries_visibility == 'all'
   75         nil
   76       elsif role.time_entries_visibility == 'own' && user.id && user.logged?
   77         "#{table_name}.user_id = #{user.id}"
   78       else
   79         '1=0'
   80       end
   81     end
   82   end
   83 
   84   # Returns true if user or current user is allowed to view the time entry
   85   def visible?(user=nil)
   86     (user || User.current).allowed_to?(:view_time_entries, self.project) do |role, user|
   87       if role.time_entries_visibility == 'all'
   88         true
   89       elsif role.time_entries_visibility == 'own'
   90         self.user == user
   91       else
   92         false
   93       end
   94     end
   95   end
   96 
   97   def initialize(attributes=nil, *args)
   98     super
   99     if new_record? && self.activity.nil?
  100       if default_activity = TimeEntryActivity.default
  101         self.activity_id = default_activity.id
  102       end
  103       self.hours = nil if hours == 0
  104     end
  105   end
  106 
  107   def safe_attributes=(attrs, user=User.current)
  108     if attrs
  109       attrs = super(attrs)
  110       if issue_id_changed? && issue
  111         if issue.visible?(user) && user.allowed_to?(:log_time, issue.project)
  112           if attrs[:project_id].blank? && issue.project_id != project_id
  113             self.project_id = issue.project_id
  114           end
  115           @invalid_issue_id = nil
  116         else
  117           @invalid_issue_id = issue_id
  118         end
  119       end
  120       if user_id_changed? && user_id != author_id && !user.allowed_to?(:log_time_for_other_users, project)
  121         @invalid_user_id = user_id
  122       else
  123         @invalid_user_id = nil
  124       end
  125     end
  126     attrs
  127   end
  128 
  129   def set_project_if_nil
  130     self.project = issue.project if issue && project.nil?
  131   end
  132 
  133   def set_author_if_nil
  134     self.author = User.current if author.nil?
  135   end
  136 
  137   def validate_time_entry
  138     if hours
  139       errors.add :hours, :invalid if hours < 0
  140       errors.add :hours, :invalid if hours == 0.0 && hours_changed? && !Setting.timelog_accept_0_hours?
  141 
  142       max_hours = Setting.timelog_max_hours_per_day.to_f
  143       if hours_changed? && max_hours > 0.0
  144         logged_hours = other_hours_with_same_user_and_day
  145         if logged_hours + hours > max_hours
  146           errors.add(
  147             :base,
  148             I18n.t(:error_exceeds_maximum_hours_per_day,
  149                    :logged_hours => format_hours(logged_hours),
  150                    :max_hours => format_hours(max_hours)))
  151         end
  152       end
  153     end
  154     errors.add :project_id, :invalid if project.nil?
  155     if @invalid_user_id || (user_id_changed? && user_id != author_id && !self.assignable_users.map(&:id).include?(user_id))
  156       errors.add :user_id, :invalid
  157     end
  158     errors.add :issue_id, :invalid if (issue_id && !issue) || (issue && project!=issue.project) || @invalid_issue_id
  159     errors.add :activity_id, :inclusion if activity_id_changed? && project && !project.activities.include?(activity)
  160     if spent_on_changed? && user
  161       errors.add :base, I18n.t(:error_spent_on_future_date) if !Setting.timelog_accept_future_dates? && (spent_on > user.today)
  162     end
  163   end
  164 
  165   def hours=(h)
  166     write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h)
  167   end
  168 
  169   def hours
  170     h = read_attribute(:hours)
  171     if h.is_a?(Float)
  172       h.round(2)
  173     else
  174       h
  175     end
  176   end
  177 
  178   # tyear, tmonth, tweek assigned where setting spent_on attributes
  179   # these attributes make time aggregations easier
  180   def spent_on=(date)
  181     super
  182     self.tyear = spent_on ? spent_on.year : nil
  183     self.tmonth = spent_on ? spent_on.month : nil
  184     self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
  185   end
  186 
  187   # Returns true if the time entry can be edited by usr, otherwise false
  188   def editable_by?(usr)
  189     visible?(usr) && (
  190       (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
  191     )
  192   end
  193 
  194   # Returns the custom_field_values that can be edited by the given user
  195   def editable_custom_field_values(user=nil)
  196     visible_custom_field_values
  197   end
  198 
  199   # Returns the custom fields that can be edited by the given user
  200   def editable_custom_fields(user=nil)
  201     editable_custom_field_values(user).map(&:custom_field).uniq
  202   end
  203 
  204   def visible_custom_field_values(user = nil)
  205     user ||= User.current
  206     custom_field_values.select do |value|
  207       value.custom_field.visible_by?(project, user)
  208     end
  209   end
  210 
  211   def assignable_users
  212     users = []
  213     if project
  214       users = project.members.active.preload(:user)
  215       users = users.map(&:user).select{ |u| u.allowed_to?(:log_time, project) }
  216     end
  217     users << User.current if User.current.logged? && !users.include?(User.current)
  218     users
  219   end
  220 
  221   private
  222 
  223   # Returns the hours that were logged in other time entries for the same user and the same day
  224   def other_hours_with_same_user_and_day
  225     if user_id && spent_on
  226       TimeEntry.
  227         where(:user_id => user_id, :spent_on => spent_on).
  228         where.not(:id => id).
  229         sum(:hours).to_f
  230     else
  231       0.0
  232     end
  233   end
  234 end