Class: Issue
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- Issue
- Includes:
- Redmine::SafeAttributes
- Defined in:
- app/models/issue.rb
Overview
Redmine - project management software Copyright © 2006-2011 Jean-Philippe Lang
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Constant Summary collapse
- DONE_RATIO_OPTIONS =
%w(issue_field issue_status)
Instance Attribute Summary collapse
-
#current_journal ⇒ Object
readonly
Returns the value of attribute current_journal.
Class Method Summary collapse
-
.allowed_target_projects_on_move ⇒ Object
Returns an array of projects that current user can move issues to.
- .by_assigned_to(project) ⇒ Object
- .by_author(project) ⇒ Object
- .by_category(project) ⇒ Object
- .by_priority(project) ⇒ Object
- .by_subproject(project) ⇒ Object
-
.by_tracker(project) ⇒ Object
Extracted from the ReportsController.
- .by_version(project) ⇒ Object
-
.update_versions_from_hierarchy_change(project) ⇒ Object
Unassigns issues from versions that are no longer shared after
project
was moved. -
.update_versions_from_sharing_change(version) ⇒ Object
Unassigns issues from
version
if it’s no longer shared with issue’s project. - .use_field_for_done_ratio? ⇒ Boolean
- .use_status_for_done_ratio? ⇒ Boolean
-
.visible_condition(user, options = {}) ⇒ Object
Returns a SQL conditions string used to find all issues visible by the specified user.
Instance Method Summary collapse
- #<=>(issue) ⇒ Object
- #after_initialize ⇒ Object
- #all_dependent_issues(except = []) ⇒ Object
-
#assignable_users ⇒ Object
Users the issue can be assigned to.
-
#assignable_versions ⇒ Object
Versions that the issue can be assigned to.
-
#attributes_with_tracker_first=(new_attributes, *args) ⇒ Object
Overrides attributes= so that tracker_id gets assigned first.
-
#available_custom_fields ⇒ Object
Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields.
-
#behind_schedule? ⇒ Boolean
Is the amount of work done less than it should for the due date.
-
#blocked? ⇒ Boolean
Returns true if this issue is blocked by another issue that is still open.
-
#children? ⇒ Boolean
Does this issue have children?.
-
#closed? ⇒ Boolean
Return true if the issue is closed, otherwise false.
-
#closing? ⇒ Boolean
Return true if the issue is being closed.
- #copy_from(arg) ⇒ Object
-
#css_classes ⇒ Object
Returns a string of css classes that apply to the issue.
- #done_ratio ⇒ Object
-
#due_before ⇒ Object
Returns the due date or the target due date if any Used on gantt chart.
-
#duplicates ⇒ Object
Returns an array of issues that duplicate this one.
-
#duration ⇒ Object
Returns the time scheduled for this issue.
- #estimated_hours=(h) ⇒ Object
- #init_journal(user, notes = "") ⇒ Object
-
#move_to_project(*args) ⇒ Object
Moves/copies an issue to a new project and tracker Returns the moved/copied issue on success, false on failure.
- #move_to_project_without_transaction(new_project, new_tracker = nil, options = {}) ⇒ Object
-
#new_statuses_allowed_to(user, include_default = false) ⇒ Object
Returns an array of status that user is able to apply.
-
#overdue? ⇒ Boolean
Returns true if the issue is overdue.
- #parent_issue_id ⇒ Object
- #parent_issue_id=(arg) ⇒ Object
- #priority_id=(pid) ⇒ Object
-
#recipients ⇒ Object
Returns the mail adresses of users that should be notified.
- #relations ⇒ Object
-
#reopened? ⇒ Boolean
Return true if the issue is being reopened.
- #reschedule_after(date) ⇒ Object
-
#safe_attributes=(attrs, user = User.current) ⇒ Object
Safely sets attributes Should be called from controllers instead of #attributes= attr_accessible is too rough because we still want things like Issue.new(:project => foo) to work TODO: move workflow/permission checks from controllers to here.
-
#save_issue_with_child_records(params, existing_time_entry = nil) ⇒ Object
Saves an issue, time_entry, attachments, and a journal from the parameters Returns false if save fails.
- #soonest_start ⇒ Object
-
#spent_hours ⇒ Object
Returns the total number of hours spent on this issue and its descendants.
- #status_id=(sid) ⇒ Object
- #to_s ⇒ Object
- #tracker_id=(tid) ⇒ Object
-
#update_done_ratio_from_issue_status ⇒ Object
Set the done_ratio using the status if that setting is set.
- #validate ⇒ Object
-
#visible?(usr = nil) ⇒ Boolean
Returns true if usr or current user is allowed to view the issue.
Methods included from Redmine::SafeAttributes
#delete_unsafe_attributes, included, #safe_attribute_names
Instance Attribute Details
#current_journal ⇒ Object (readonly)
Returns the value of attribute current_journal.
54 55 56 |
# File 'app/models/issue.rb', line 54 def current_journal @current_journal end |
Class Method Details
.allowed_target_projects_on_move ⇒ Object
Returns an array of projects that current user can move issues to
662 663 664 665 666 667 668 669 670 671 672 673 674 675 |
# File 'app/models/issue.rb', line 662 def self.allowed_target_projects_on_move projects = [] if User.current.admin? # admin is allowed to move issues to any active (visible) project projects = Project.visible.all elsif User.current.logged? if Role.non_member.allowed_to?(:move_issues) projects = Project.visible.all else User.current.memberships.each {|m| projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}} end end projects end |
.by_assigned_to(project) ⇒ Object
635 636 637 638 639 |
# File 'app/models/issue.rb', line 635 def self.by_assigned_to(project) count_and_group_by(:project => project, :field => 'assigned_to_id', :joins => User.table_name) end |
.by_author(project) ⇒ Object
641 642 643 644 645 |
# File 'app/models/issue.rb', line 641 def self.(project) count_and_group_by(:project => project, :field => 'author_id', :joins => User.table_name) end |
.by_category(project) ⇒ Object
629 630 631 632 633 |
# File 'app/models/issue.rb', line 629 def self.by_category(project) count_and_group_by(:project => project, :field => 'category_id', :joins => IssueCategory.table_name) end |
.by_priority(project) ⇒ Object
623 624 625 626 627 |
# File 'app/models/issue.rb', line 623 def self.by_priority(project) count_and_group_by(:project => project, :field => 'priority_id', :joins => IssuePriority.table_name) end |
.by_subproject(project) ⇒ Object
647 648 649 650 651 652 653 654 655 656 657 658 |
# File 'app/models/issue.rb', line 647 def self.by_subproject(project) ActiveRecord::Base.connection.select_all("select s.id as status_id, s.is_closed as closed, i.project_id as project_id, count(i.id) as total from #{Issue.table_name} i, #{IssueStatus.table_name} s where i.status_id=s.id and i.project_id IN (#{project.descendants.active.collect{|p| p.id}.join(',')}) group by s.id, s.is_closed, i.project_id") if project.descendants.active.any? end |
.by_tracker(project) ⇒ Object
Extracted from the ReportsController.
611 612 613 614 615 |
# File 'app/models/issue.rb', line 611 def self.by_tracker(project) count_and_group_by(:project => project, :field => 'tracker_id', :joins => Tracker.table_name) end |
.by_version(project) ⇒ Object
617 618 619 620 621 |
# File 'app/models/issue.rb', line 617 def self.by_version(project) count_and_group_by(:project => project, :field => 'fixed_version_id', :joins => Version.table_name) end |
.update_versions_from_hierarchy_change(project) ⇒ Object
Unassigns issues from versions that are no longer shared after project
was moved
586 587 588 589 590 |
# File 'app/models/issue.rb', line 586 def self.update_versions_from_hierarchy_change(project) moved_project_ids = project.self_and_descendants.reload.collect(&:id) # Update issues of the moved projects and issues assigned to a version of a moved project Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids]) end |
.update_versions_from_sharing_change(version) ⇒ Object
Unassigns issues from version
if it’s no longer shared with issue’s project
579 580 581 582 |
# File 'app/models/issue.rb', line 579 def self.update_versions_from_sharing_change(version) # Update issues assigned to the version update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id]) end |
.use_field_for_done_ratio? ⇒ Boolean
292 293 294 |
# File 'app/models/issue.rb', line 292 def self.use_field_for_done_ratio? Setting.issue_done_ratio == 'issue_field' end |
.use_status_for_done_ratio? ⇒ Boolean
288 289 290 |
# File 'app/models/issue.rb', line 288 def self.use_status_for_done_ratio? Setting.issue_done_ratio == 'issue_status' end |
.visible_condition(user, options = {}) ⇒ Object
Returns a SQL conditions string used to find all issues visible by the specified user
90 91 92 |
# File 'app/models/issue.rb', line 90 def self.visible_condition(user, ={}) Project.allowed_to_condition(user, :view_issues, ) end |
Instance Method Details
#<=>(issue) ⇒ Object
516 517 518 519 520 521 522 523 524 |
# File 'app/models/issue.rb', line 516 def <=>(issue) if issue.nil? -1 elsif root_id != issue.root_id (root_id || 0) <=> (issue.root_id || 0) else (lft || 0) <=> (issue.lft || 0) end end |
#after_initialize ⇒ Object
99 100 101 102 103 104 105 |
# File 'app/models/issue.rb', line 99 def after_initialize if new_record? # set default values for new records only self.status ||= IssueStatus.default self.priority ||= IssuePriority.default end end |
#all_dependent_issues(except = []) ⇒ Object
463 464 465 466 467 468 469 470 471 472 473 |
# File 'app/models/issue.rb', line 463 def all_dependent_issues(except=[]) except << self dependencies = [] relations_from.each do |relation| if relation.issue_to && !except.include?(relation.issue_to) dependencies << relation.issue_to dependencies += relation.issue_to.all_dependent_issues(except) end end dependencies end |
#assignable_users ⇒ Object
Users the issue can be assigned to
407 408 409 410 411 |
# File 'app/models/issue.rb', line 407 def assignable_users users = project.assignable_users users << if users.uniq.sort end |
#assignable_versions ⇒ Object
Versions that the issue can be assigned to
414 415 416 |
# File 'app/models/issue.rb', line 414 def assignable_versions @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort end |
#attributes_with_tracker_first=(new_attributes, *args) ⇒ Object
Overrides attributes= so that tracker_id gets assigned first
203 204 205 206 207 208 209 210 |
# File 'app/models/issue.rb', line 203 def attributes_with_tracker_first=(new_attributes, *args) return if new_attributes.nil? new_tracker_id = new_attributes['tracker_id'] || new_attributes[:tracker_id] if new_tracker_id self.tracker_id = new_tracker_id end send :attributes_without_tracker_first=, new_attributes, *args end |
#available_custom_fields ⇒ Object
Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
108 109 110 |
# File 'app/models/issue.rb', line 108 def available_custom_fields (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : [] end |
#behind_schedule? ⇒ Boolean
Is the amount of work done less than it should for the due date
395 396 397 398 399 |
# File 'app/models/issue.rb', line 395 def behind_schedule? return false if start_date.nil? || due_date.nil? done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor return done_date <= Date.today end |
#blocked? ⇒ Boolean
Returns true if this issue is blocked by another issue that is still open
419 420 421 |
# File 'app/models/issue.rb', line 419 def blocked? !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil? end |
#children? ⇒ Boolean
Does this issue have children?
402 403 404 |
# File 'app/models/issue.rb', line 402 def children? !leaf? end |
#closed? ⇒ Boolean
Return true if the issue is closed, otherwise false
361 362 363 |
# File 'app/models/issue.rb', line 361 def closed? self.status.is_closed? end |
#closing? ⇒ Boolean
Return true if the issue is being closed
378 379 380 381 382 383 384 385 386 387 |
# File 'app/models/issue.rb', line 378 def closing? if !new_record? && status_id_changed? status_was = IssueStatus.find_by_id(status_id_was) status_new = IssueStatus.find_by_id(status_id) if status_was && status_new && !status_was.is_closed? && status_new.is_closed? return true end end false end |
#copy_from(arg) ⇒ Object
112 113 114 115 116 117 118 |
# File 'app/models/issue.rb', line 112 def copy_from(arg) issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg) self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on") self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h} self.status = issue.status self end |
#css_classes ⇒ Object
Returns a string of css classes that apply to the issue
531 532 533 534 535 536 537 538 539 540 |
# File 'app/models/issue.rb', line 531 def css_classes s = "issue status-#{status.position} priority-#{priority.position}" s << ' closed' if closed? s << ' overdue' if overdue? s << ' child' if child? s << ' parent' unless leaf? s << ' created-by-me' if User.current.logged? && == User.current.id s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id s end |
#done_ratio ⇒ Object
280 281 282 283 284 285 286 |
# File 'app/models/issue.rb', line 280 def done_ratio if Issue.use_status_for_done_ratio? && status && status.default_done_ratio status.default_done_ratio else read_attribute(:done_ratio) end end |
#due_before ⇒ Object
Returns the due date or the target due date if any Used on gantt chart
482 483 484 |
# File 'app/models/issue.rb', line 482 def due_before due_date || (fixed_version ? fixed_version.effective_date : nil) end |
#duplicates ⇒ Object
Returns an array of issues that duplicate this one
476 477 478 |
# File 'app/models/issue.rb', line 476 def duplicates relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from} end |
#duration ⇒ Object
Returns the time scheduled for this issue.
Example:
Start Date: 2/26/09, End Date: 3/04/09
duration => 6
491 492 493 |
# File 'app/models/issue.rb', line 491 def duration (start_date && due_date) ? due_date - start_date : 0 end |
#estimated_hours=(h) ⇒ Object
214 215 216 |
# File 'app/models/issue.rb', line 214 def estimated_hours=(h) write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h) end |
#init_journal(user, notes = "") ⇒ Object
349 350 351 352 353 354 355 356 357 358 |
# File 'app/models/issue.rb', line 349 def init_journal(user, notes = "") @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes) @issue_before_change = self.clone @issue_before_change.status = self.status @custom_values_before_change = {} self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value } # Make sure updated_on is updated when adding a note. updated_on_will_change! @current_journal end |
#move_to_project(*args) ⇒ Object
Moves/copies an issue to a new project and tracker Returns the moved/copied issue on success, false on failure
122 123 124 125 126 |
# File 'app/models/issue.rb', line 122 def move_to_project(*args) ret = Issue.transaction do move_to_project_without_transaction(*args) || raise(ActiveRecord::Rollback) end || false end |
#move_to_project_without_transaction(new_project, new_tracker = nil, options = {}) ⇒ Object
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'app/models/issue.rb', line 128 def move_to_project_without_transaction(new_project, new_tracker = nil, = {}) ||= {} issue = [:copy] ? self.class.new.copy_from(self) : self if new_project && issue.project_id != new_project.id # delete issue relations unless Setting.cross_project_issue_relations? issue.relations_from.clear issue.relations_to.clear end # issue is moved to another project # reassign to the category with same name if any new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name) issue.category = new_category # Keep the fixed_version if it's still valid in the new_project unless new_project.shared_versions.include?(issue.fixed_version) issue.fixed_version = nil end issue.project = new_project if issue.parent && issue.parent.project_id != issue.project_id issue.parent_issue_id = nil end end if new_tracker issue.tracker = new_tracker issue.reset_custom_values! end if [:copy] issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h} issue.status = if [:attributes] && [:attributes][:status_id] IssueStatus.find_by_id([:attributes][:status_id]) else self.status end end # Allow bulk setting of attributes on the issue if [:attributes] issue.attributes = [:attributes] end if issue.save unless [:copy] # Manually update project_id on related time entries TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id}) issue.children.each do |child| unless child.move_to_project_without_transaction(new_project) # Move failed and transaction was rollback'd return false end end end else return false end issue end |
#new_statuses_allowed_to(user, include_default = false) ⇒ Object
Returns an array of status that user is able to apply
424 425 426 427 428 429 430 431 432 433 434 435 |
# File 'app/models/issue.rb', line 424 def new_statuses_allowed_to(user, include_default=false) statuses = status.find_new_statuses_allowed_to( user.roles_for_project(project), tracker, == user, assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id ) statuses << status unless statuses.empty? statuses << IssueStatus.default if include_default statuses = statuses.uniq.sort blocked? ? statuses.reject {|s| s.is_closed?} : statuses end |
#overdue? ⇒ Boolean
Returns true if the issue is overdue
390 391 392 |
# File 'app/models/issue.rb', line 390 def overdue? !due_date.nil? && (due_date < Date.today) && !status.is_closed? end |
#parent_issue_id ⇒ Object
602 603 604 605 606 607 608 |
# File 'app/models/issue.rb', line 602 def parent_issue_id if instance_variable_defined? :@parent_issue @parent_issue.nil? ? nil : @parent_issue.id else parent_id end end |
#parent_issue_id=(arg) ⇒ Object
592 593 594 595 596 597 598 599 600 |
# File 'app/models/issue.rb', line 592 def parent_issue_id=(arg) parent_issue_id = arg.blank? ? nil : arg.to_i if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id) @parent_issue.id else @parent_issue = nil nil end end |
#priority_id=(pid) ⇒ Object
190 191 192 193 |
# File 'app/models/issue.rb', line 190 def priority_id=(pid) self.priority = nil write_attribute(:priority_id, pid) end |
#recipients ⇒ Object
Returns the mail adresses of users that should be notified
438 439 440 441 442 443 444 445 446 447 448 |
# File 'app/models/issue.rb', line 438 def recipients notified = project.notified_users # Author and assignee are always notified unless they have been # locked or don't want to be notified notified << if && .active? && .notify_about?(self) notified << assigned_to if assigned_to && assigned_to.active? && assigned_to.notify_about?(self) notified.uniq! # Remove users that can not view the issue notified.reject! {|user| !visible?(user)} notified.collect(&:mail) end |
#relations ⇒ Object
459 460 461 |
# File 'app/models/issue.rb', line 459 def relations (relations_from + relations_to).sort end |
#reopened? ⇒ Boolean
Return true if the issue is being reopened
366 367 368 369 370 371 372 373 374 375 |
# File 'app/models/issue.rb', line 366 def reopened? if !new_record? && status_id_changed? status_was = IssueStatus.find_by_id(status_id_was) status_new = IssueStatus.find_by_id(status_id) if status_was && status_new && status_was.is_closed? && !status_new.is_closed? return true end end false end |
#reschedule_after(date) ⇒ Object
502 503 504 505 506 507 508 509 510 511 512 513 514 |
# File 'app/models/issue.rb', line 502 def reschedule_after(date) return if date.nil? if leaf? if start_date.nil? || start_date < date self.start_date, self.due_date = date, date + duration save end else leaves.each do |leaf| leaf.reschedule_after(date) end end end |
#safe_attributes=(attrs, user = User.current) ⇒ Object
Safely sets attributes Should be called from controllers instead of #attributes= attr_accessible is too rough because we still want things like Issue.new(:project => foo) to work TODO: move workflow/permission checks from controllers to here
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'app/models/issue.rb', line 247 def safe_attributes=(attrs, user=User.current) return unless attrs.is_a?(Hash) # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed attrs = delete_unsafe_attributes(attrs, user) return if attrs.empty? # Tracker must be set before since new_statuses_allowed_to depends on it. if t = attrs.delete('tracker_id') self.tracker_id = t end if attrs['status_id'] unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i) attrs.delete('status_id') end end unless leaf? attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)} end if attrs.has_key?('parent_issue_id') if !user.allowed_to?(:manage_subtasks, project) attrs.delete('parent_issue_id') elsif !attrs['parent_issue_id'].blank? attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i) end end self.attributes = attrs end |
#save_issue_with_child_records(params, existing_time_entry = nil) ⇒ Object
Saves an issue, time_entry, attachments, and a journal from the parameters Returns false if save fails
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 |
# File 'app/models/issue.rb', line 544 def save_issue_with_child_records(params, existing_time_entry=nil) Issue.transaction do if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project) @time_entry = existing_time_entry || TimeEntry.new @time_entry.project = project @time_entry.issue = self @time_entry.user = User.current @time_entry.spent_on = Date.today @time_entry.attributes = params[:time_entry] self.time_entries << @time_entry end if valid? = Attachment.attach_files(self, params[:attachments]) [:files].each {|a| @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)} # TODO: Rename hook Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal}) begin if save # TODO: Rename hook Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal}) else raise ActiveRecord::Rollback end rescue ActiveRecord::StaleObjectError [:files].each(&:destroy) errors.add_to_base l(:notice_locking_conflict) raise ActiveRecord::Rollback end end end end |
#soonest_start ⇒ Object
495 496 497 498 499 500 |
# File 'app/models/issue.rb', line 495 def soonest_start @soonest_start ||= ( relations_to.collect{|relation| relation.successor_soonest_start} + ancestors.collect(&:soonest_start) ).compact.max end |
#spent_hours ⇒ Object
Returns the total number of hours spent on this issue and its descendants
Example:
spent_hours => 0.0
spent_hours => 50.2
455 456 457 |
# File 'app/models/issue.rb', line 455 def spent_hours @spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", :include => :time_entries).to_f || 0.0 end |
#status_id=(sid) ⇒ Object
185 186 187 188 |
# File 'app/models/issue.rb', line 185 def status_id=(sid) self.status = nil write_attribute(:status_id, sid) end |
#to_s ⇒ Object
526 527 528 |
# File 'app/models/issue.rb', line 526 def to_s "#{tracker} ##{id}: #{subject}" end |
#tracker_id=(tid) ⇒ Object
195 196 197 198 199 200 |
# File 'app/models/issue.rb', line 195 def tracker_id=(tid) self.tracker = nil result = write_attribute(:tracker_id, tid) @custom_field_values = nil result end |
#update_done_ratio_from_issue_status ⇒ Object
Set the done_ratio using the status if that setting is set. This will keep the done_ratios even if the user turns off the setting later
343 344 345 346 347 |
# File 'app/models/issue.rb', line 343 def update_done_ratio_from_issue_status if Issue.use_status_for_done_ratio? && status && status.default_done_ratio self.done_ratio = status.default_done_ratio end end |
#validate ⇒ Object
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'app/models/issue.rb', line 296 def validate if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty? errors.add :due_date, :not_a_date end if self.due_date and self.start_date and self.due_date < self.start_date errors.add :due_date, :greater_than_start_date end if start_date && soonest_start && start_date < soonest_start errors.add :start_date, :invalid end if fixed_version if !assignable_versions.include?(fixed_version) errors.add :fixed_version_id, :inclusion elsif reopened? && fixed_version.closed? errors.add_to_base I18n.t(:error_can_not_reopen_issue_on_closed_version) end end # Checks that the issue can not be added/moved to a disabled tracker if project && (tracker_id_changed? || project_id_changed?) unless project.trackers.include?(tracker) errors.add :tracker_id, :inclusion end end # Checks parent issue assignment if @parent_issue if @parent_issue.project_id != project_id errors.add :parent_issue_id, :not_same_project elsif !new_record? # moving an existing issue if @parent_issue.root_id != root_id # we can always move to another tree elsif move_possible?(@parent_issue) # move accepted inside tree else errors.add :parent_issue_id, :not_a_valid_parent end end end end |