annotate app/models/version.rb @ 8:0c83d98252d9 yuya

* Add custom repo prefix and proper auth realm, remove auth cache (seems like an unwise feature), pass DB handle around, various other bits of tidying
author Chris Cannam
date Thu, 12 Aug 2010 15:31:37 +0100
parents 513646585e45
children 40f7cfd4df19
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@0 76
Chris@0 77 # Returns the completion percentage of this version based on the amount of open/closed issues
Chris@0 78 # and the time spent on the open issues.
Chris@0 79 def completed_pourcent
Chris@0 80 if issues_count == 0
Chris@0 81 0
Chris@0 82 elsif open_issues_count == 0
Chris@0 83 100
Chris@0 84 else
Chris@0 85 issues_progress(false) + issues_progress(true)
Chris@0 86 end
Chris@0 87 end
Chris@0 88
Chris@0 89 # Returns the percentage of issues that have been marked as 'closed'.
Chris@0 90 def closed_pourcent
Chris@0 91 if issues_count == 0
Chris@0 92 0
Chris@0 93 else
Chris@0 94 issues_progress(false)
Chris@0 95 end
Chris@0 96 end
Chris@0 97
Chris@0 98 # Returns true if the version is overdue: due date reached and some open issues
Chris@0 99 def overdue?
Chris@0 100 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
Chris@0 101 end
Chris@0 102
Chris@0 103 # Returns assigned issues count
Chris@0 104 def issues_count
Chris@0 105 @issue_count ||= fixed_issues.count
Chris@0 106 end
Chris@0 107
Chris@0 108 # Returns the total amount of open issues for this version.
Chris@0 109 def open_issues_count
Chris@0 110 @open_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, false], :include => :status)
Chris@0 111 end
Chris@0 112
Chris@0 113 # Returns the total amount of closed issues for this version.
Chris@0 114 def closed_issues_count
Chris@0 115 @closed_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, true], :include => :status)
Chris@0 116 end
Chris@0 117
Chris@0 118 def wiki_page
Chris@0 119 if project.wiki && !wiki_page_title.blank?
Chris@0 120 @wiki_page ||= project.wiki.find_page(wiki_page_title)
Chris@0 121 end
Chris@0 122 @wiki_page
Chris@0 123 end
Chris@0 124
Chris@0 125 def to_s; name end
Chris@0 126
Chris@0 127 # Versions are sorted by effective_date and "Project Name - Version name"
Chris@0 128 # Those with no effective_date are at the end, sorted by "Project Name - Version name"
Chris@0 129 def <=>(version)
Chris@0 130 if self.effective_date
Chris@0 131 if version.effective_date
Chris@0 132 if self.effective_date == version.effective_date
Chris@0 133 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
Chris@0 134 else
Chris@0 135 self.effective_date <=> version.effective_date
Chris@0 136 end
Chris@0 137 else
Chris@0 138 -1
Chris@0 139 end
Chris@0 140 else
Chris@0 141 if version.effective_date
Chris@0 142 1
Chris@0 143 else
Chris@0 144 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
Chris@0 145 end
Chris@0 146 end
Chris@0 147 end
Chris@0 148
Chris@0 149 # Returns the sharings that +user+ can set the version to
Chris@0 150 def allowed_sharings(user = User.current)
Chris@0 151 VERSION_SHARINGS.select do |s|
Chris@0 152 if sharing == s
Chris@0 153 true
Chris@0 154 else
Chris@0 155 case s
Chris@0 156 when 'system'
Chris@0 157 # Only admin users can set a systemwide sharing
Chris@0 158 user.admin?
Chris@0 159 when 'hierarchy', 'tree'
Chris@0 160 # Only users allowed to manage versions of the root project can
Chris@0 161 # set sharing to hierarchy or tree
Chris@0 162 project.nil? || user.allowed_to?(:manage_versions, project.root)
Chris@0 163 else
Chris@0 164 true
Chris@0 165 end
Chris@0 166 end
Chris@0 167 end
Chris@0 168 end
Chris@0 169
Chris@0 170 private
Chris@0 171
Chris@0 172 # Update the issue's fixed versions. Used if a version's sharing changes.
Chris@0 173 def update_issues_from_sharing_change
Chris@0 174 if sharing_changed?
Chris@0 175 if VERSION_SHARINGS.index(sharing_was).nil? ||
Chris@0 176 VERSION_SHARINGS.index(sharing).nil? ||
Chris@0 177 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
Chris@0 178 Issue.update_versions_from_sharing_change self
Chris@0 179 end
Chris@0 180 end
Chris@0 181 end
Chris@0 182
Chris@0 183 # Returns the average estimated time of assigned issues
Chris@0 184 # or 1 if no issue has an estimated time
Chris@0 185 # Used to weigth unestimated issues in progress calculation
Chris@0 186 def estimated_average
Chris@0 187 if @estimated_average.nil?
Chris@0 188 average = fixed_issues.average(:estimated_hours).to_f
Chris@0 189 if average == 0
Chris@0 190 average = 1
Chris@0 191 end
Chris@0 192 @estimated_average = average
Chris@0 193 end
Chris@0 194 @estimated_average
Chris@0 195 end
Chris@0 196
Chris@0 197 # Returns the total progress of open or closed issues. The returned percentage takes into account
Chris@0 198 # the amount of estimated time set for this version.
Chris@0 199 #
Chris@0 200 # Examples:
Chris@0 201 # issues_progress(true) => returns the progress percentage for open issues.
Chris@0 202 # issues_progress(false) => returns the progress percentage for closed issues.
Chris@0 203 def issues_progress(open)
Chris@0 204 @issues_progress ||= {}
Chris@0 205 @issues_progress[open] ||= begin
Chris@0 206 progress = 0
Chris@0 207 if issues_count > 0
Chris@0 208 ratio = open ? 'done_ratio' : 100
Chris@0 209
Chris@0 210 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
Chris@0 211 :include => :status,
Chris@0 212 :conditions => ["is_closed = ?", !open]).to_f
Chris@0 213 progress = done / (estimated_average * issues_count)
Chris@0 214 end
Chris@0 215 progress
Chris@0 216 end
Chris@0 217 end
Chris@0 218 end