/app/models/polymorphic/task.rb

https://github.com/steventen/fat_free_crm · Ruby · 284 lines · 205 code · 28 blank · 51 comment · 19 complexity · 31b6675a6f021e57211d3dee558f0abd MD5 · raw file

  1. # Copyright (c) 2008-2013 Michael Dvorkin and contributors.
  2. #
  3. # Fat Free CRM is freely distributable under the terms of MIT license.
  4. # See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
  5. #------------------------------------------------------------------------------
  6. # == Schema Information
  7. #
  8. # Table name: tasks
  9. #
  10. # id :integer not null, primary key
  11. # user_id :integer
  12. # assigned_to :integer
  13. # completed_by :integer
  14. # name :string(255) default(""), not null
  15. # asset_id :integer
  16. # asset_type :string(255)
  17. # priority :string(32)
  18. # category :string(32)
  19. # bucket :string(32)
  20. # due_at :datetime
  21. # completed_at :datetime
  22. # deleted_at :datetime
  23. # created_at :datetime
  24. # updated_at :datetime
  25. # background_info :string(255)
  26. #
  27. class Task < ActiveRecord::Base
  28. attr_accessor :calendar
  29. ALLOWED_VIEWS = %w(pending assigned completed)
  30. belongs_to :user
  31. belongs_to :assignee, :class_name => "User", :foreign_key => :assigned_to
  32. belongs_to :completor, :class_name => "User", :foreign_key => :completed_by
  33. belongs_to :asset, :polymorphic => true
  34. serialize :subscribed_users, Set
  35. # Tasks created by the user for herself, or assigned to her by others. That's
  36. # what gets shown on Tasks/Pending and Tasks/Completed pages.
  37. scope :my, ->(*args) {
  38. options = args[0] || {}
  39. user_option = (options.is_a?(Hash) ? options[:user] : options) || User.current_user
  40. includes(:assignee).
  41. where('(user_id = ? AND assigned_to IS NULL) OR assigned_to = ?', user_option, user_option).
  42. order(options[:order] || 'name ASC').
  43. limit(options[:limit]) # nil selects all records
  44. }
  45. scope :created_by, ->(user) { where( user_id: user.id ) }
  46. scope :assigned_to, ->(user) { where( assigned_to: user.id ) }
  47. # Tasks assigned by the user to others. That's what we see on Tasks/Assigned.
  48. scope :assigned_by, ->(user) {
  49. includes(:assignee).
  50. where('user_id = ? AND assigned_to IS NOT NULL AND assigned_to != ?', user.id, user.id)
  51. }
  52. # Tasks created by the user or assigned to the user, i.e. the union of the two
  53. # scopes above. That's the tasks the user is allowed to see and track.
  54. scope :tracked_by, ->(user) {
  55. includes(:assignee).
  56. where('user_id = ? OR assigned_to = ?', user.id, user.id)
  57. }
  58. # Show opportunities which either belong to the user and are unassigned, or are assigned to the user
  59. scope :visible_on_dashboard, ->(user) {
  60. where('(user_id = :user_id AND assigned_to IS NULL) OR assigned_to = :user_id', :user_id => user.id).where('completed_at IS NULL')
  61. }
  62. scope :by_due_at, -> {
  63. order({
  64. "MySQL" => "due_at NOT NULL, due_at ASC",
  65. "PostgreSQL" => "due_at ASC NULLS FIRST"
  66. }[ActiveRecord::Base.connection.adapter_name] || :due_at)
  67. }
  68. # Status based scopes to be combined with the due date and completion time.
  69. scope :pending, -> { where('completed_at IS NULL').order('tasks.due_at, tasks.id') }
  70. scope :assigned, -> { where('completed_at IS NULL AND assigned_to IS NOT NULL').order('tasks.due_at, tasks.id') }
  71. scope :completed, -> { where('completed_at IS NOT NULL').order('tasks.completed_at DESC') }
  72. # Due date scopes.
  73. scope :due_asap, -> { where("due_at IS NULL AND bucket = 'due_asap'").order('tasks.id DESC') }
  74. scope :overdue, -> { where('due_at IS NOT NULL AND due_at < ?', Time.zone.now.midnight.utc).order('tasks.id DESC') }
  75. scope :due_today, -> { where('due_at >= ? AND due_at < ?', Time.zone.now.midnight.utc, Time.zone.now.midnight.tomorrow.utc).order('tasks.id DESC') }
  76. scope :due_tomorrow, -> { where('due_at >= ? AND due_at < ?', Time.zone.now.midnight.tomorrow.utc, Time.zone.now.midnight.tomorrow.utc + 1.day).order('tasks.id DESC') }
  77. scope :due_this_week, -> { where('due_at >= ? AND due_at < ?', Time.zone.now.midnight.tomorrow.utc + 1.day, Time.zone.now.next_week.utc).order('tasks.id DESC') }
  78. scope :due_next_week, -> { where('due_at >= ? AND due_at < ?', Time.zone.now.next_week.utc, Time.zone.now.next_week.end_of_week.utc + 1.day).order('tasks.id DESC') }
  79. scope :due_later, -> { where("(due_at IS NULL AND bucket = 'due_later') OR due_at >= ?", Time.zone.now.next_week.end_of_week.utc + 1.day).order('tasks.id DESC') }
  80. # Completion time scopes.
  81. scope :completed_today, -> { where('completed_at >= ? AND completed_at < ?', Time.zone.now.midnight.utc, Time.zone.now.midnight.tomorrow.utc) }
  82. scope :completed_yesterday, -> { where('completed_at >= ? AND completed_at < ?', Time.zone.now.midnight.yesterday.utc, Time.zone.now.midnight.utc) }
  83. scope :completed_this_week, -> { where('completed_at >= ? AND completed_at < ?', Time.zone.now.beginning_of_week.utc , Time.zone.now.midnight.yesterday.utc) }
  84. scope :completed_last_week, -> { where('completed_at >= ? AND completed_at < ?', Time.zone.now.beginning_of_week.utc - 7.days, Time.zone.now.beginning_of_week.utc) }
  85. scope :completed_this_month, -> { where('completed_at >= ? AND completed_at < ?', Time.zone.now.beginning_of_month.utc, Time.zone.now.beginning_of_week.utc - 7.days) }
  86. scope :completed_last_month, -> { where('completed_at >= ? AND completed_at < ?', (Time.zone.now.beginning_of_month.utc - 1.day).beginning_of_month.utc, Time.zone.now.beginning_of_month.utc) }
  87. scope :text_search, ->(query) {
  88. query = query.gsub(/[^\w\s\-\.'\p{L}]/u, '').strip
  89. where('upper(name) LIKE upper(?)', "%#{query}%")
  90. }
  91. acts_as_commentable
  92. has_paper_trail :meta => { :related => :asset }, :ignore => [ :subscribed_users ]
  93. has_fields
  94. exportable
  95. validates_presence_of :user
  96. validates_presence_of :name, :message => :missing_task_name
  97. validates_presence_of :calendar, :if => "self.bucket == 'specific_time' && !self.completed_at"
  98. validate :specific_time, :unless => :completed?
  99. before_create :set_due_date
  100. before_update :set_due_date, :unless => :completed?
  101. before_save :notify_assignee
  102. # Matcher for the :my named scope.
  103. #----------------------------------------------------------------------------
  104. def my?(user)
  105. (self.user == user && assignee.nil?) || assignee == user
  106. end
  107. # Matcher for the :assigned_by named scope.
  108. #----------------------------------------------------------------------------
  109. def assigned_by?(user)
  110. self.user == user && assignee && assignee != user
  111. end
  112. #----------------------------------------------------------------------------
  113. def completed?
  114. !!self.completed_at
  115. end
  116. # Matcher for the :tracked_by? named scope.
  117. #----------------------------------------------------------------------------
  118. def tracked_by?(user)
  119. self.user == user || self.assignee == user
  120. end
  121. # Check whether the due date has specific time ignoring 23:59:59 timestamp
  122. # set by Time.now.end_of_week.
  123. #----------------------------------------------------------------------------
  124. def at_specific_time?
  125. self.due_at.present? && !due_end_of_day? && !due_beginning_of_day?
  126. end
  127. # Convert specific due_date to "due_today", "due_tomorrow", etc. bucket name.
  128. #----------------------------------------------------------------------------
  129. def computed_bucket
  130. return self.bucket if self.bucket != "specific_time"
  131. case
  132. when overdue?
  133. "overdue"
  134. when due_today?
  135. "due_today"
  136. when due_tomorrow?
  137. "due_tomorrow"
  138. when due_this_week? && !due_today? && !due_tomorrow?
  139. "due_this_week"
  140. when due_next_week?
  141. "due_next_week"
  142. else
  143. "due_later"
  144. end
  145. end
  146. # Returns list of tasks grouping them by due date as required by tasks/index.
  147. #----------------------------------------------------------------------------
  148. def self.find_all_grouped(user, view)
  149. return {} unless ALLOWED_VIEWS.include?(view)
  150. settings = (view == "completed" ? Setting.task_completed : Setting.task_bucket)
  151. Hash[
  152. settings.map do |key, value|
  153. [ key, view == "assigned" ? assigned_by(user).send(key).pending : my(user).send(key).send(view) ]
  154. end
  155. ]
  156. end
  157. # Returns bucket if it's empty (i.e. we have to hide it), nil otherwise.
  158. #----------------------------------------------------------------------------
  159. def self.bucket_empty?(bucket, user, view = "pending")
  160. return false if bucket.blank? or !ALLOWED_VIEWS.include?(view)
  161. if view == "assigned"
  162. assigned_by(user).send(bucket).pending.count
  163. else
  164. my(user).send(bucket).send(view).count
  165. end == 0
  166. end
  167. # Returns task totals for each of the views as needed by tasks sidebar.
  168. #----------------------------------------------------------------------------
  169. def self.totals(user, view = "pending")
  170. return {} unless ALLOWED_VIEWS.include?(view)
  171. settings = (view == "completed" ? Setting.task_completed : Setting.task_bucket)
  172. settings.inject({ :all => 0 }) do |hash, key|
  173. hash[key] = (view == "assigned" ? assigned_by(user).send(key).pending.count : my(user).send(key).send(view).count)
  174. hash[:all] += hash[key]
  175. hash
  176. end
  177. end
  178. private
  179. #----------------------------------------------------------------------------
  180. def set_due_date
  181. self.due_at = case self.bucket
  182. when "overdue"
  183. self.due_at || Time.zone.now.midnight.yesterday
  184. when "due_today"
  185. Time.zone.now.midnight
  186. when "due_tomorrow"
  187. Time.zone.now.midnight.tomorrow
  188. when "due_this_week"
  189. Time.zone.now.end_of_week
  190. when "due_next_week"
  191. Time.zone.now.next_week.end_of_week
  192. when "due_later"
  193. Time.zone.now.midnight + 100.years
  194. when "specific_time"
  195. self.calendar ? parse_calendar_date : nil
  196. else # due_later or due_asap
  197. nil
  198. end
  199. end
  200. #----------------------------------------------------------------------------
  201. def due_end_of_day?
  202. self.due_at.present? && (self.due_at == self.due_at.end_of_day)
  203. end
  204. #----------------------------------------------------------------------------
  205. def due_beginning_of_day?
  206. self.due_at.present? && (self.due_at == self.due_at.beginning_of_day)
  207. end
  208. #----------------------------------------------------------------------------
  209. def overdue?
  210. self.due_at < Time.zone.now.midnight
  211. end
  212. #----------------------------------------------------------------------------
  213. def due_today?
  214. self.due_at.between?(Time.zone.now.midnight, Time.zone.now.end_of_day)
  215. end
  216. #----------------------------------------------------------------------------
  217. def due_tomorrow?
  218. self.due_at.between?(Time.zone.now.midnight.tomorrow, Time.zone.now.tomorrow.end_of_day)
  219. end
  220. #----------------------------------------------------------------------------
  221. def due_this_week?
  222. self.due_at.between?(Time.zone.now.beginning_of_week, Time.zone.now.end_of_week)
  223. end
  224. #----------------------------------------------------------------------------
  225. def due_next_week?
  226. self.due_at.between?(Time.zone.now.next_week, Time.zone.now.next_week.end_of_week)
  227. end
  228. #----------------------------------------------------------------------------
  229. def notify_assignee
  230. if self.assigned_to
  231. # Notify assignee.
  232. end
  233. end
  234. #----------------------------------------------------------------------------
  235. def specific_time
  236. parse_calendar_date if self.bucket == "specific_time"
  237. rescue ArgumentError
  238. errors.add(:calendar, :invalid_date)
  239. end
  240. #----------------------------------------------------------------------------
  241. def parse_calendar_date
  242. # always in 2012-10-28 06:28 format regardless of language
  243. Time.parse(self.calendar)
  244. end
  245. ActiveSupport.run_load_hooks(:fat_free_crm_task, self)
  246. end