class Task < ActiveRecord::Base COMPLETED = 'COMPLETED' POSTPONED = 'POSTPONED' MOVED = 'MOVED' ABORTED = 'ABORTED' belongs_to :backlog belongs_to :period acts_as_list :scope => '#{period_id ? "period_id = #{period_id}" : "parent_id = #{parent_id}"} AND finished_at IS NULL' has_many :estimates, :order => 'created_at', :dependent => :destroy has_many :works, :order => 'completed_at', :dependent => :destroy acts_as_tree :order => 'position' validates_size_of :description, :maximum => 80 validates_size_of :customer, :maximum => 64, :if => :customer validates_presence_of :backlog_id, :if => Proc.new { |task| task.parent_id.nil? } validates_presence_of :parent_id, :if => Proc.new { |task| task.backlog_id.nil? } validates_uniqueness_of :description, :scope => :period_id def validate unless (self.period_id || self.parent_id) && !(self.period_id && self.parent_id) errors.add :parent_id, "A task may have either period or parent task set, not both." end if new_record? && self.period.passed? errors.add :period_id, "You may not add a task to a past period." end end def self.find_open find(:all, :conditions => 'finished_at IS NULL', :order => 'description') end def self.find_started(user) if user user_clause = " OR user_id = #{user.id}" end conditions = "completed_at IS NULL AND (user_id IS NULL#{user_clause})" Work.find(:all, :conditions => conditions).map {|work| work.task} end def self.recent_conditions return "finished_at >= '#{1.week.ago.iso8601}'" end def todo estimates.last ? estimates.last.todo : initial_estimate end def estimate(new_todo, user) return unless new_todo && new_todo != '' previous_estimate = estimates.last new_estimate = Estimate.new if previous_estimate return if new_todo == previous_estimate.todo else return if new_todo == initial_estimate end now = Time.now if previous_estimate && (now < previous_estimate.created_at || now.strftime('%Y-%m-%DT%H:%M:%S') == previous_estimate.created_at.strftime('%Y-%m-%DT%H:%M:%S')) previous_estimate.todo = new_todo previous_estimate.user = user previous_estimate.save! else new_estimate.task_id = self.id new_estimate.todo = new_todo new_estimate.created_at = now new_estimate.user = user new_estimate.save! estimates << new_estimate end end def done nil end def done=(hours_done) return unless hours_done && hours_done != '' && BigDecimal(hours_done) != 0 return if end_work(hours_done) new_work = Work.new new_work.hours = hours_done new_work.task_id = self.id new_work.completed_at = DateTime.now new_work.save! works << new_work end def total_done total = BigDecimal('0') works.each {|work| total += work.hours} total end def open(user) if finished_at self.finished_at = nil self.resolution = nil estimate(initial_estimate, user) insert_at 1 parent.open(user) if parent end end def reopen(user) if period.passed? flash[:notice] = "You cannot reopen a task in a period that is passed." else open(user) save! children.each {|child_task| child_task.reopen(user)} end end def move_to_period(period, user) raise "Period missing" unless period raise "Cannot move finished tasks" unless active? ancestor_id = self.previous_task_id || self.id new_task = Task.find_by_period_id_and_id(period.id, ancestor_id) new_task ||= Task.find_by_period_id_and_previous_task_id(period.id, ancestor_id) new_task ||= Task.new new_task.open(user) new_task.previous_task_id = self.previous_task_id || self.id new_task.backlog = backlog new_task.period = period new_task.description = self.description new_task.save! new_task.estimate(self.todo, user) new_task.move_to_top new_task.estimate(self.todo, user) self.finish(new_task.period.party == self.period.party ? Task::POSTPONED : Task::MOVED, true, user) end def finish(resolution, save_work, user) unless finished_at || work_started?(user) remove_from_list self.finished_at = Time.now self.resolution = resolution self.position = nil save! estimate(0, user) if save_work parent.check_finished(self.finished_at, resolution, save_work, user) if parent end end def check_finished(subtask_finsihed_at, resolution, save_work, user) return if self.finished_at children.each do |child_task| return if child_task.active? end finish(resolution, save_work, user) end def active? finished_at.nil? || active_children? end def active_children? children.detect {|child| child.active?} end def completed? finished_at || completed_children? end def completed_children? children.detect {|child| child.completed?} end def loggable? active? && leaf? end def leaf? children.size == 0 end def root? parent.nil? end def root_task root_task = self root_task = root_task.parent while root_task.parent root_task end def track_done? root_task.backlog.track_done end def track_times? root_task.backlog.track_times? end def track_todo? root_task.backlog.track_todo? end def enable_subtasks? root_task.backlog.enable_subtasks? end def enable_customer? root_task.backlog.enable_customer? end alias_method :old_period, :period def period old_period || root_task.old_period end def depth root_task = self depth = 0 while !root_task.root? root_task = root_task.parent depth += 1 end depth end def estimate_data(date, actual=false) if children.empty? return 0 if actual && (resolution == ABORTED || resolution == POSTPONED) return 0 if finished_at && (date >= finished_at.to_date) estimate = Estimate.find(:first, :conditions => "task_id = #{id} AND created_at < '#{(date+1).to_s}'", :order => 'created_at DESC,id DESC') if estimate estimate.todo elsif actual || created_at < (date+1).to_time initial_estimate else 0 end else total = BigDecimal('0') children.each {|child_task| total += child_task.estimate_data(date, actual)} total end end def work_data(date) return 0 if resolution == ABORTED relevant_works = Work.find(:all, :conditions => "task_id = #{id} AND completed_at < '#{(date+1).to_s}'") total = BigDecimal('0') relevant_works.each do |work| total += work.hours end total end def start_work(user) return if work_started?(user) open(user) new_work = works.new new_work.started_at = Time.previous_quarter if works.size > 0 if user last_work = works.select {|work| work.user == user}.last end unless last_work last_work = works.select {|work| work.user.nil?}.last end if last_work && last_work.completed_at > new_work.started_at new_work.started_at = last_work.completed_at end end new_work.user = user new_work.save! end def works_with_children works << children.map {|t| t.works_with_children}.flatten end def abort(user) finish(Task::ABORTED, false, user) end def work_started?(user) !started_work(user).nil? end def started_work(user) started_works = works.select {|work| work.completed_at.nil?} if user started_by_user = started_works.select {|work| work.user == user}.last return started_by_user if started_by_user end started_works.select {|work| work.user.nil?}.last end end