class Task < ActiveRecord::Base include UserSystem COMPLETED = 'COMPLETED' POSTPONED = 'POSTPONED' MOVED = 'MOVED' ABORTED = 'ABORTED' belongs_to :backlog belongs_to :period acts_as_list :scope => :period_id has_many :estimates, :order => 'created_at', :dependent => :destroy has_many :works, :order => 'completed_at', :dependent => :destroy acts_as_tree :order => 'position' 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_absence_of :position, :if => :finished_at #validates_absence_of :finished_at, :if => :position validates_presence_of :resolution, :if => :finished_at validates_presence_of :description, :if => :backlog_id validates_size_of :description, :maximum => 80, :if => :description validates_size_of :customer, :maximum => 64, :if => :customer #validates_uniqueness_of :description, :scope => :backlog_id, :if => Proc.new {|task| task.backlog_id && task.previous_task_id.nil?} validates_uniqueness_of :description, :scope => :period_id, :if => :period_id validates_uniqueness_of :position, :scope => :period_id, :if => :period_id, :allow_nil => true validates_uniqueness_of :position, :scope => :parent_id, :if => :parent_id, :allow_nil => true validates_uniqueness_of :position, :scope => :backlog_id, :if => Proc.new {|task| task.period_id.nil? && task.parent_id.nil?}, :allow_nil => true def validate if self.parent_id && (self.period_id || self.backlog_id) errors.add :parent_id, "A subtask may not have neither period nor backlog set." end if new_record? && self.period && 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) 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 = current_user previous_estimate.save! else new_estimate.task_id = self.id new_estimate.todo = new_todo new_estimate.created_at = now new_estimate.user = current_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 if finished_at insert_at 1 self.position = 1 self.finished_at = nil self.resolution = nil estimate(initial_estimate) parent.open if parent end end def reopen if period.passed? flash[:notice] = "You cannot reopen a task in a period that is passed." else open save! children.each {|child_task| child_task.reopen} end end def move_to_period(period) 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 new_task.previous_task_id = self.previous_task_id || self.id new_task.backlog = root_task.backlog new_task.period = period new_task.description = self.description new_task.save! new_task.estimate(self.todo) new_task.move_to_top self.finish(new_task.period.party == self.period.party ? Task::POSTPONED : Task::MOVED, true) end def finish(resolution, save_work) unless finished_at || work_started? self.finished_at = Time.now self.resolution = resolution remove_from_list self.position = nil save! estimate(0) if save_work parent.check_finished(self.finished_at, resolution, save_work) if parent end end def check_finished(subtask_finsihed_at, resolution, save_work) return if self.finished_at children.each do |child_task| return if child_task.active? end finish(resolution, save_work) end def active? finished_at.nil? || work_started? || 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_users? root_task.backlog.enable_users? end def enable_customer? root_task.backlog.enable_customer? end def enable_invoicing? root_task.backlog.enable_invoicing? 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 return if work_started? open new_work = works.new new_work.started_at = Time.previous_quarter if works.size > 0 if current_user last_work = works.select {|work| work.user == current_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 = current_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) end def work_started? !started_work.nil? end def started_work started_works = works.select {|work| work.completed_at.nil?} if current_user started_by_user = started_works.select {|work| work.user == current_user}.last return started_by_user if started_by_user end started_works.select {|work| work.user.nil?}.last end end