2018-11-03 15:57:14 -05:00
|
|
|
class Project < ApplicationRecord
|
2020-10-10 02:27:42 +03:00
|
|
|
has_many :todos, -> { order(Arel.sql("todos.due IS NULL, todos.due ASC, todos.created_at ASC")) }, dependent: :delete_all
|
|
|
|
has_many :notes, -> { order "created_at DESC" }, dependent: :delete_all
|
2010-04-04 18:20:07 +02:00
|
|
|
has_many :recurring_todos
|
2009-04-18 23:50:12 +02:00
|
|
|
|
2008-06-17 01:13:25 -04:00
|
|
|
belongs_to :default_context, :class_name => "Context", :foreign_key => "default_context_id"
|
2007-03-30 04:36:52 +00:00
|
|
|
belongs_to :user
|
2008-11-29 15:35:17 +01:00
|
|
|
|
2013-05-11 23:13:32 +02:00
|
|
|
scope :active, -> { where state: 'active' }
|
|
|
|
scope :hidden, -> { where state: 'hidden' }
|
|
|
|
scope :completed, -> { where state: 'completed' }
|
|
|
|
scope :uncompleted, -> { where("NOT(state = ?)", 'completed') }
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2020-09-05 01:24:23 +03:00
|
|
|
scope :with_name_or_description, lambda { |body| where("name " + Common.like_operator + " ? OR description " + Common.like_operator + " ?", body, body) }
|
|
|
|
scope :with_namepart, lambda { |body| where("name " + Common.like_operator + " ?", body + '%') }
|
2013-03-12 19:49:19 -05:00
|
|
|
|
2015-05-22 23:12:45 +02:00
|
|
|
before_create :set_last_reviewed_now
|
|
|
|
|
2020-10-27 21:39:19 +02:00
|
|
|
validates :name, presence: true, length: { maximum: 255 }, uniqueness: { scope: :user_id }
|
2007-03-30 04:36:52 +00:00
|
|
|
|
2011-09-13 11:15:14 +02:00
|
|
|
acts_as_list :scope => 'user_id = #{user_id} AND state = \'#{state}\'', :top_of_list => 0
|
|
|
|
|
2011-05-16 15:42:47 +08:00
|
|
|
include AASM
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2013-04-29 11:53:32 +02:00
|
|
|
aasm :column => :state do
|
|
|
|
state :active, :initial => true
|
2016-01-24 00:52:23 +01:00
|
|
|
state :hidden
|
2013-04-29 11:53:32 +02:00
|
|
|
state :completed, :enter => :set_completed_at_date, :exit => :clear_completed_at_date
|
2007-03-30 04:36:52 +00:00
|
|
|
|
2013-04-29 11:53:32 +02:00
|
|
|
event :activate do
|
|
|
|
transitions :to => :active, :from => [:active, :hidden, :completed]
|
|
|
|
end
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2013-04-29 11:53:32 +02:00
|
|
|
event :hide do
|
|
|
|
transitions :to => :hidden, :from => [:active, :completed]
|
|
|
|
end
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2013-04-29 11:53:32 +02:00
|
|
|
event :complete do
|
|
|
|
transitions :to => :completed, :from => [:active, :hidden]
|
|
|
|
end
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
attr_accessor :cached_note_count
|
|
|
|
|
|
|
|
def self.null_object
|
|
|
|
NullProject.new
|
|
|
|
end
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2015-05-22 23:12:45 +02:00
|
|
|
def set_last_reviewed_now
|
2020-10-10 13:58:13 +03:00
|
|
|
self.last_reviewed = Time.zone.now
|
2015-05-22 23:12:45 +02:00
|
|
|
end
|
|
|
|
|
2011-06-10 14:28:42 +02:00
|
|
|
def set_completed_at_date
|
|
|
|
self.completed_at = Time.zone.now
|
|
|
|
end
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2011-06-10 14:28:42 +02:00
|
|
|
def clear_completed_at_date
|
|
|
|
self.completed_at = nil
|
|
|
|
end
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def note_count
|
2011-06-12 05:38:45 +02:00
|
|
|
# TODO: test this for eager and not eager loading!!!
|
|
|
|
return 0 if notes.size == 0
|
2007-03-30 04:36:52 +00:00
|
|
|
cached_note_count || notes.count
|
|
|
|
end
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
alias_method :original_default_context, :default_context
|
|
|
|
|
|
|
|
def default_context
|
|
|
|
original_default_context.nil? ? Context.null_object : original_default_context
|
|
|
|
end
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
# would prefer to call this method state=(), but that causes an endless loop
|
|
|
|
# as a result of acts_as_state_machine calling state=() to update the attribute
|
|
|
|
def transition_to(candidate_state)
|
|
|
|
case candidate_state.to_sym
|
2013-12-30 20:52:35 +01:00
|
|
|
when aasm.current_state
|
2007-03-30 04:36:52 +00:00
|
|
|
return
|
|
|
|
when :hidden
|
|
|
|
hide!
|
|
|
|
when :active
|
|
|
|
activate!
|
|
|
|
when :completed
|
|
|
|
complete!
|
|
|
|
end
|
|
|
|
end
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2013-07-30 16:18:06 -05:00
|
|
|
def needs_review?(user)
|
2020-10-27 21:39:19 +02:00
|
|
|
return active? && (last_reviewed.nil? ||
|
2014-06-12 21:24:31 -04:00
|
|
|
(last_reviewed < Time.current - user.prefs.review_period.days))
|
2011-09-16 23:34:09 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def blocked?
|
|
|
|
## mutually exclusive for stalled and blocked
|
2011-09-28 13:54:50 +02:00
|
|
|
# blocked is uncompleted project with deferred or pending todos, but no next actions
|
2020-10-27 21:39:19 +02:00
|
|
|
return false if completed?
|
|
|
|
return !todos.deferred_or_blocked.empty? && todos.active.empty?
|
2011-09-16 23:34:09 -04:00
|
|
|
end
|
2011-09-28 13:54:50 +02:00
|
|
|
|
2011-09-16 23:34:09 -04:00
|
|
|
def stalled?
|
2011-12-19 12:30:51 -06:00
|
|
|
# Stalled projects are active projects with no active next actions
|
2020-10-27 21:39:19 +02:00
|
|
|
return false if completed? || hidden?
|
|
|
|
return !todos.deferred_or_blocked.exists? && !todos.active.exists?
|
2011-09-16 15:07:58 -04:00
|
|
|
end
|
|
|
|
|
2020-10-10 13:58:13 +03:00
|
|
|
def shortened_name(length = 40)
|
2012-07-18 11:42:26 +02:00
|
|
|
name.truncate(length, :omission => "...").html_safe
|
|
|
|
end
|
2011-09-16 15:07:58 -04:00
|
|
|
|
2007-10-03 03:05:34 +00:00
|
|
|
def name=(value)
|
2013-05-27 12:44:31 +02:00
|
|
|
if value
|
|
|
|
self[:name] = value.gsub(/\s{2,}/, " ").strip
|
|
|
|
else
|
|
|
|
self[:name] = nil
|
|
|
|
end
|
2007-10-03 03:05:34 +00:00
|
|
|
end
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2013-03-02 00:19:47 -05:00
|
|
|
def age_in_days
|
2014-03-30 21:36:31 -04:00
|
|
|
@age_in_days ||= (Time.current.to_date - created_at.to_date).to_i + 1
|
2013-03-02 00:19:47 -05:00
|
|
|
end
|
|
|
|
|
2014-11-05 17:07:21 +01:00
|
|
|
def running_time
|
|
|
|
if completed_at.nil?
|
|
|
|
return age_in_days
|
|
|
|
else
|
|
|
|
return (completed_at.to_date - created_at.to_date).to_i + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-09-16 11:37:16 +02:00
|
|
|
def self.import(filename, params, user)
|
2013-07-21 13:37:35 -04:00
|
|
|
count = 0
|
2013-09-16 11:37:16 +02:00
|
|
|
CSV.foreach(filename, headers: true) do |row|
|
2013-07-21 13:37:35 -04:00
|
|
|
unless find_by_name_and_user_id row[params[:name].to_i], user.id
|
2014-08-14 21:05:05 -05:00
|
|
|
project = new
|
2013-07-21 13:37:35 -04:00
|
|
|
project.name = row[params[:name].to_i]
|
|
|
|
project.user = user
|
|
|
|
project.description = row[params[:description].to_i] if row[params[:description].to_i].present?
|
|
|
|
project.state = 'active'
|
|
|
|
project.save!
|
|
|
|
count += 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
count
|
2013-07-23 01:28:43 -04:00
|
|
|
end
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
class NullProject
|
|
|
|
def hidden?
|
|
|
|
false
|
|
|
|
end
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def nil?
|
|
|
|
true
|
|
|
|
end
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def id
|
|
|
|
nil
|
|
|
|
end
|
2011-09-13 11:15:14 +02:00
|
|
|
|
2013-03-10 20:38:10 +01:00
|
|
|
def name
|
|
|
|
""
|
|
|
|
end
|
|
|
|
|
2014-05-06 21:51:39 -04:00
|
|
|
def persisted?
|
|
|
|
false
|
|
|
|
end
|
2009-04-19 00:18:12 +02:00
|
|
|
end
|