mirror of
https://github.com/TracksApp/tracks.git
synced 2025-09-22 05:50:47 +02:00
169 lines
4.4 KiB
Ruby
169 lines
4.4 KiB
Ruby
class Project < ApplicationRecord
|
|
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
|
|
has_many :recurring_todos
|
|
|
|
belongs_to :default_context, :class_name => "Context", :foreign_key => "default_context_id"
|
|
belongs_to :user
|
|
|
|
scope :active, -> { where state: 'active' }
|
|
scope :hidden, -> { where state: 'hidden' }
|
|
scope :completed, -> { where state: 'completed' }
|
|
scope :uncompleted, -> { where("NOT(state = ?)", 'completed') }
|
|
|
|
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 + '%') }
|
|
|
|
before_create :set_last_reviewed_now
|
|
|
|
validates_presence_of :name
|
|
validates_length_of :name, :maximum => 255
|
|
validates_uniqueness_of :name, :scope => "user_id", :case_sensitive => true
|
|
|
|
acts_as_list :scope => 'user_id = #{user_id} AND state = \'#{state}\'', :top_of_list => 0
|
|
|
|
include AASM
|
|
|
|
aasm :column => :state do
|
|
state :active, :initial => true
|
|
state :hidden
|
|
state :completed, :enter => :set_completed_at_date, :exit => :clear_completed_at_date
|
|
|
|
event :activate do
|
|
transitions :to => :active, :from => [:active, :hidden, :completed]
|
|
end
|
|
|
|
event :hide do
|
|
transitions :to => :hidden, :from => [:active, :completed]
|
|
end
|
|
|
|
event :complete do
|
|
transitions :to => :completed, :from => [:active, :hidden]
|
|
end
|
|
end
|
|
|
|
attr_accessor :cached_note_count
|
|
|
|
def self.null_object
|
|
NullProject.new
|
|
end
|
|
|
|
def set_last_reviewed_now
|
|
self.last_reviewed = Time.zone.now
|
|
end
|
|
|
|
def set_completed_at_date
|
|
self.completed_at = Time.zone.now
|
|
end
|
|
|
|
def clear_completed_at_date
|
|
self.completed_at = nil
|
|
end
|
|
|
|
def note_count
|
|
# TODO: test this for eager and not eager loading!!!
|
|
return 0 if notes.size == 0
|
|
cached_note_count || notes.count
|
|
end
|
|
|
|
alias_method :original_default_context, :default_context
|
|
|
|
def default_context
|
|
original_default_context.nil? ? Context.null_object : original_default_context
|
|
end
|
|
|
|
# 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
|
|
when aasm.current_state
|
|
return
|
|
when :hidden
|
|
hide!
|
|
when :active
|
|
activate!
|
|
when :completed
|
|
complete!
|
|
end
|
|
end
|
|
|
|
def needs_review?(user)
|
|
return active? && ( last_reviewed.nil? ||
|
|
(last_reviewed < Time.current - user.prefs.review_period.days))
|
|
end
|
|
|
|
def blocked?
|
|
## mutually exclusive for stalled and blocked
|
|
# blocked is uncompleted project with deferred or pending todos, but no next actions
|
|
return false if self.completed?
|
|
return !self.todos.deferred_or_blocked.empty? && self.todos.active.empty?
|
|
end
|
|
|
|
def stalled?
|
|
# Stalled projects are active projects with no active next actions
|
|
return false if self.completed? || self.hidden?
|
|
return !self.todos.deferred_or_blocked.exists? && !self.todos.active.exists?
|
|
end
|
|
|
|
def shortened_name(length = 40)
|
|
name.truncate(length, :omission => "...").html_safe
|
|
end
|
|
|
|
def name=(value)
|
|
if value
|
|
self[:name] = value.gsub(/\s{2,}/, " ").strip
|
|
else
|
|
self[:name] = nil
|
|
end
|
|
end
|
|
|
|
def age_in_days
|
|
@age_in_days ||= (Time.current.to_date - created_at.to_date).to_i + 1
|
|
end
|
|
|
|
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
|
|
|
|
def self.import(filename, params, user)
|
|
count = 0
|
|
CSV.foreach(filename, headers: true) do |row|
|
|
unless find_by_name_and_user_id row[params[:name].to_i], user.id
|
|
project = new
|
|
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
|
|
end
|
|
end
|
|
|
|
class NullProject
|
|
def hidden?
|
|
false
|
|
end
|
|
|
|
def nil?
|
|
true
|
|
end
|
|
|
|
def id
|
|
nil
|
|
end
|
|
|
|
def name
|
|
""
|
|
end
|
|
|
|
def persisted?
|
|
false
|
|
end
|
|
end
|