annotate app/models/.svn/text-base/version.rb.svn-base @ 45:65d9e2cabaa3 luisf

Added tipoftheday to the config/settings in order to correct previous issues. Tip of the day is now working correctly. Added the heading strings to the locales files.
author luisf
date Tue, 23 Nov 2010 11:50:01 +0000
parents 40f7cfd4df19
children af80e5618e9b
rev   line source
Chris@0 1 # Redmine - project management software
Chris@0 2 # Copyright (C) 2006-2010 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 class Version < ActiveRecord::Base
Chris@0 19 after_update :update_issues_from_sharing_change
Chris@0 20 belongs_to :project
Chris@0 21 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
Chris@0 22 acts_as_customizable
Chris@0 23 acts_as_attachable :view_permission => :view_files,
Chris@0 24 :delete_permission => :manage_files
Chris@0 25
Chris@0 26 VERSION_STATUSES = %w(open locked closed)
Chris@0 27 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
Chris@0 28
Chris@0 29 validates_presence_of :name
Chris@0 30 validates_uniqueness_of :name, :scope => [:project_id]
Chris@0 31 validates_length_of :name, :maximum => 60
Chris@0 32 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
Chris@0 33 validates_inclusion_of :status, :in => VERSION_STATUSES
Chris@0 34 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
Chris@0 35
Chris@0 36 named_scope :open, :conditions => {:status => 'open'}
Chris@0 37 named_scope :visible, lambda {|*args| { :include => :project,
Chris@0 38 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
Chris@0 39
Chris@0 40 # Returns true if +user+ or current user is allowed to view the version
Chris@0 41 def visible?(user=User.current)
Chris@0 42 user.allowed_to?(:view_issues, self.project)
Chris@0 43 end
Chris@0 44
Chris@0 45 def start_date
Chris@0 46 effective_date
Chris@0 47 end
Chris@0 48
Chris@0 49 def due_date
Chris@0 50 effective_date
Chris@0 51 end
Chris@0 52
Chris@0 53 # Returns the total estimated time for this version
Chris@0 54 # (sum of leaves estimated_hours)
Chris@0 55 def estimated_hours
Chris@0 56 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
Chris@0 57 end
Chris@0 58
Chris@0 59 # Returns the total reported time for this version
Chris@0 60 def spent_hours
Chris@0 61 @spent_hours ||= TimeEntry.sum(:hours, :include => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
Chris@0 62 end
Chris@0 63
Chris@0 64 def closed?
Chris@0 65 status == 'closed'
Chris@0 66 end
Chris@0 67
Chris@0 68 def open?
Chris@0 69 status == 'open'
Chris@0 70 end
Chris@0 71
Chris@0 72 # Returns true if the version is completed: due date reached and no open issues
Chris@0 73 def completed?
Chris@0 74 effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
Chris@0 75 end
chris@22 76
chris@22 77 def behind_schedule?
chris@22 78 if completed_pourcent == 100
chris@22 79 return false
chris@22 80 elsif due_date && fixed_issues.present? && fixed_issues.minimum('start_date') # TODO: should use #start_date but that method is wrong...
chris@22 81 start_date = fixed_issues.minimum('start_date')
chris@22 82 done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor
chris@22 83 return done_date <= Date.today
chris@22 84 else
chris@22 85 false # No issues so it's not late
chris@22 86 end
chris@22 87 end
Chris@0 88
Chris@0 89 # Returns the completion percentage of this version based on the amount of open/closed issues
Chris@0 90 # and the time spent on the open issues.
Chris@0 91 def completed_pourcent
Chris@0 92 if issues_count == 0
Chris@0 93 0
Chris@0 94 elsif open_issues_count == 0
Chris@0 95 100
Chris@0 96 else
Chris@0 97 issues_progress(false) + issues_progress(true)
Chris@0 98 end
Chris@0 99 end
Chris@0 100
Chris@0 101 # Returns the percentage of issues that have been marked as 'closed'.
Chris@0 102 def closed_pourcent
Chris@0 103 if issues_count == 0
Chris@0 104 0
Chris@0 105 else
Chris@0 106 issues_progress(false)
Chris@0 107 end
Chris@0 108 end
Chris@0 109
Chris@0 110 # Returns true if the version is overdue: due date reached and some open issues
Chris@0 111 def overdue?
Chris@0 112 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
Chris@0 113 end
Chris@0 114
Chris@0 115 # Returns assigned issues count
Chris@0 116 def issues_count
Chris@0 117 @issue_count ||= fixed_issues.count
Chris@0 118 end
Chris@0 119
Chris@0 120 # Returns the total amount of open issues for this version.
Chris@0 121 def open_issues_count
Chris@0 122 @open_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, false], :include => :status)
Chris@0 123 end
Chris@0 124
Chris@0 125 # Returns the total amount of closed issues for this version.
Chris@0 126 def closed_issues_count
Chris@0 127 @closed_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, true], :include => :status)
Chris@0 128 end
Chris@0 129
Chris@0 130 def wiki_page
Chris@0 131 if project.wiki && !wiki_page_title.blank?
Chris@0 132 @wiki_page ||= project.wiki.find_page(wiki_page_title)
Chris@0 133 end
Chris@0 134 @wiki_page
Chris@0 135 end
Chris@0 136
Chris@0 137 def to_s; name end
chris@22 138
chris@22 139 def to_s_with_project
chris@22 140 "#{project} - #{name}"
chris@22 141 end
Chris@0 142
Chris@0 143 # Versions are sorted by effective_date and "Project Name - Version name"
Chris@0 144 # Those with no effective_date are at the end, sorted by "Project Name - Version name"
Chris@0 145 def <=>(version)
Chris@0 146 if self.effective_date
Chris@0 147 if version.effective_date
Chris@0 148 if self.effective_date == version.effective_date
Chris@0 149 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
Chris@0 150 else
Chris@0 151 self.effective_date <=> version.effective_date
Chris@0 152 end
Chris@0 153 else
Chris@0 154 -1
Chris@0 155 end
Chris@0 156 else
Chris@0 157 if version.effective_date
Chris@0 158 1
Chris@0 159 else
Chris@0 160 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
Chris@0 161 end
Chris@0 162 end
Chris@0 163 end
Chris@0 164
Chris@0 165 # Returns the sharings that +user+ can set the version to
Chris@0 166 def allowed_sharings(user = User.current)
Chris@0 167 VERSION_SHARINGS.select do |s|
Chris@0 168 if sharing == s
Chris@0 169 true
Chris@0 170 else
Chris@0 171 case s
Chris@0 172 when 'system'
Chris@0 173 # Only admin users can set a systemwide sharing
Chris@0 174 user.admin?
Chris@0 175 when 'hierarchy', 'tree'
Chris@0 176 # Only users allowed to manage versions of the root project can
Chris@0 177 # set sharing to hierarchy or tree
Chris@0 178 project.nil? || user.allowed_to?(:manage_versions, project.root)
Chris@0 179 else
Chris@0 180 true
Chris@0 181 end
Chris@0 182 end
Chris@0 183 end
Chris@0 184 end
Chris@0 185
Chris@0 186 private
Chris@0 187
Chris@0 188 # Update the issue's fixed versions. Used if a version's sharing changes.
Chris@0 189 def update_issues_from_sharing_change
Chris@0 190 if sharing_changed?
Chris@0 191 if VERSION_SHARINGS.index(sharing_was).nil? ||
Chris@0 192 VERSION_SHARINGS.index(sharing).nil? ||
Chris@0 193 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
Chris@0 194 Issue.update_versions_from_sharing_change self
Chris@0 195 end
Chris@0 196 end
Chris@0 197 end
Chris@0 198
Chris@0 199 # Returns the average estimated time of assigned issues
Chris@0 200 # or 1 if no issue has an estimated time
Chris@0 201 # Used to weigth unestimated issues in progress calculation
Chris@0 202 def estimated_average
Chris@0 203 if @estimated_average.nil?
Chris@0 204 average = fixed_issues.average(:estimated_hours).to_f
Chris@0 205 if average == 0
Chris@0 206 average = 1
Chris@0 207 end
Chris@0 208 @estimated_average = average
Chris@0 209 end
Chris@0 210 @estimated_average
Chris@0 211 end
Chris@0 212
Chris@0 213 # Returns the total progress of open or closed issues. The returned percentage takes into account
Chris@0 214 # the amount of estimated time set for this version.
Chris@0 215 #
Chris@0 216 # Examples:
Chris@0 217 # issues_progress(true) => returns the progress percentage for open issues.
Chris@0 218 # issues_progress(false) => returns the progress percentage for closed issues.
Chris@0 219 def issues_progress(open)
Chris@0 220 @issues_progress ||= {}
Chris@0 221 @issues_progress[open] ||= begin
Chris@0 222 progress = 0
Chris@0 223 if issues_count > 0
Chris@0 224 ratio = open ? 'done_ratio' : 100
Chris@0 225
Chris@0 226 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
Chris@0 227 :include => :status,
Chris@0 228 :conditions => ["is_closed = ?", !open]).to_f
Chris@0 229 progress = done / (estimated_average * issues_count)
Chris@0 230 end
Chris@0 231 progress
Chris@0 232 end
Chris@0 233 end
Chris@0 234 end