fat_free_crm 0.17.1 → 0.18.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fat_free_crm might be problematic. Click here for more details.

Files changed (199) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +1 -1
  3. data/.rubocop_todo.yml +32 -11
  4. data/.travis.yml +14 -9
  5. data/CHANGELOG.md +75 -0
  6. data/CONTRIBUTORS.md +95 -53
  7. data/Gemfile +11 -7
  8. data/Gemfile.lock +97 -97
  9. data/README.md +7 -4
  10. data/app/assets/javascripts/crm.js.coffee +3 -3
  11. data/app/assets/javascripts/crm_select2.js.coffee +16 -4
  12. data/app/controllers/admin/application_controller.rb +1 -1
  13. data/app/controllers/admin/field_groups_controller.rb +8 -1
  14. data/app/controllers/admin/fields_controller.rb +9 -6
  15. data/app/controllers/admin/groups_controller.rb +1 -1
  16. data/app/controllers/admin/plugins_controller.rb +7 -1
  17. data/app/controllers/admin/settings_controller.rb +5 -1
  18. data/app/controllers/admin/tags_controller.rb +6 -2
  19. data/app/controllers/application_controller.rb +21 -4
  20. data/app/controllers/authentications_controller.rb +1 -1
  21. data/app/controllers/comments_controller.rb +15 -7
  22. data/app/controllers/entities/campaigns_controller.rb +7 -2
  23. data/app/controllers/entities/leads_controller.rb +9 -2
  24. data/app/controllers/entities/opportunities_controller.rb +13 -2
  25. data/app/controllers/entities_controller.rb +10 -5
  26. data/app/controllers/lists_controller.rb +5 -1
  27. data/app/controllers/tasks_controller.rb +15 -1
  28. data/app/helpers/accounts_helper.rb +7 -5
  29. data/app/helpers/application_helper.rb +2 -2
  30. data/app/helpers/leads_helper.rb +1 -1
  31. data/app/helpers/opportunities_helper.rb +56 -3
  32. data/app/helpers/tags_helper.rb +1 -1
  33. data/app/models/entities/lead.rb +0 -7
  34. data/app/models/entities/opportunity.rb +3 -2
  35. data/app/models/observers/opportunity_observer.rb +4 -4
  36. data/app/models/users/ability.rb +3 -4
  37. data/app/views/campaigns/_metrics.html.haml +3 -3
  38. data/app/views/home/_opportunity.html.haml +4 -19
  39. data/app/views/opportunities/_index_long.html.haml +1 -24
  40. data/app/views/opportunities/_sidebar_show.html.haml +3 -3
  41. data/app/views/opportunities/_top_section.html.haml +1 -1
  42. data/config/initializers/ransack.rb +1 -1
  43. data/db/schema.rb +0 -3
  44. data/fat_free_crm.gemspec +1 -1
  45. data/lib/fat_free_crm/core_ext/string.rb +1 -1
  46. data/lib/fat_free_crm/engine.rb +2 -2
  47. data/lib/fat_free_crm/fields.rb +1 -1
  48. data/lib/fat_free_crm/permissions.rb +0 -14
  49. data/lib/fat_free_crm/version.rb +2 -2
  50. data/lib/tasks/ffcrm/setup.rake +4 -4
  51. data/spec/controllers/admin/users_controller_spec.rb +27 -27
  52. data/spec/controllers/authentications_controller_spec.rb +7 -7
  53. data/spec/controllers/comments_controller_spec.rb +13 -13
  54. data/spec/controllers/emails_controller_spec.rb +2 -2
  55. data/spec/controllers/entities/accounts_controller_spec.rb +56 -56
  56. data/spec/controllers/entities/campaigns_controller_spec.rb +66 -66
  57. data/spec/controllers/entities/contacts_controller_spec.rb +67 -67
  58. data/spec/controllers/entities/leads_controller_spec.rb +125 -125
  59. data/spec/controllers/entities/opportunities_controller_spec.rb +100 -100
  60. data/spec/controllers/home_controller_spec.rb +26 -26
  61. data/spec/controllers/passwords_controller_spec.rb +1 -1
  62. data/spec/controllers/tasks_controller_spec.rb +37 -37
  63. data/spec/controllers/users_controller_spec.rb +18 -18
  64. data/spec/factories/account_factories.rb +8 -8
  65. data/spec/factories/campaign_factories.rb +5 -5
  66. data/spec/factories/contact_factories.rb +10 -10
  67. data/spec/factories/field_factories.rb +7 -7
  68. data/spec/factories/lead_factories.rb +8 -8
  69. data/spec/factories/list_factories.rb +1 -1
  70. data/spec/factories/opportunity_factories.rb +6 -6
  71. data/spec/factories/sequences.rb +1 -1
  72. data/spec/factories/setting_factories.rb +3 -3
  73. data/spec/factories/shared_factories.rb +14 -14
  74. data/spec/factories/subscription_factories.rb +1 -1
  75. data/spec/factories/tag_factories.rb +1 -1
  76. data/spec/factories/task_factories.rb +4 -4
  77. data/spec/factories/user_factories.rb +13 -13
  78. data/spec/features/accounts_spec.rb +17 -4
  79. data/spec/features/admin/groups_spec.rb +1 -1
  80. data/spec/features/admin/users_spec.rb +1 -1
  81. data/spec/features/campaigns_spec.rb +4 -4
  82. data/spec/features/contacts_spec.rb +10 -4
  83. data/spec/features/dashboard_spec.rb +7 -7
  84. data/spec/features/leads_spec.rb +4 -4
  85. data/spec/features/opportunities_overview_spec.rb +15 -15
  86. data/spec/features/opportunities_spec.rb +34 -8
  87. data/spec/features/support/autocomlete_helper.rb +17 -0
  88. data/spec/features/support/browser.rb +3 -8
  89. data/spec/features/support/helpers.rb +1 -1
  90. data/spec/features/tasks_spec.rb +4 -4
  91. data/spec/helpers/admin/field_groups_helper_spec.rb +1 -1
  92. data/spec/helpers/application_helper_spec.rb +1 -1
  93. data/spec/helpers/tasks_helper_spec.rb +1 -1
  94. data/spec/helpers/users_helper_spec.rb +3 -3
  95. data/spec/lib/comment_extensions_spec.rb +1 -1
  96. data/spec/lib/mail_processor/base_spec.rb +3 -3
  97. data/spec/lib/mail_processor/comment_replies_spec.rb +3 -3
  98. data/spec/lib/mail_processor/dropbox_spec.rb +16 -16
  99. data/spec/lib/permissions_spec.rb +7 -25
  100. data/spec/mailers/user_mailer_spec.rb +7 -7
  101. data/spec/models/entities/account_spec.rb +31 -32
  102. data/spec/models/entities/campaign_spec.rb +18 -25
  103. data/spec/models/entities/contact_spec.rb +18 -21
  104. data/spec/models/entities/lead_spec.rb +9 -11
  105. data/spec/models/entities/opportunity_spec.rb +45 -45
  106. data/spec/models/fields/custom_field_spec.rb +17 -17
  107. data/spec/models/list_spec.rb +2 -2
  108. data/spec/models/observers/entity_observer_spec.rb +6 -6
  109. data/spec/models/polymorphic/address_spec.rb +1 -1
  110. data/spec/models/polymorphic/avatar_spec.rb +5 -5
  111. data/spec/models/polymorphic/comment_spec.rb +5 -5
  112. data/spec/models/polymorphic/task_spec.rb +65 -58
  113. data/spec/models/polymorphic/version_spec.rb +26 -26
  114. data/spec/models/setting_spec.rb +2 -2
  115. data/spec/models/users/preference_spec.rb +6 -6
  116. data/spec/models/users/user_spec.rb +26 -26
  117. data/spec/shared/controllers.rb +3 -3
  118. data/spec/shared/models.rb +22 -22
  119. data/spec/spec_helper.rb +2 -2
  120. data/spec/support/auth_macros.rb +1 -1
  121. data/spec/support/macros.rb +3 -3
  122. data/spec/views/accounts/_edit.haml_spec.rb +1 -1
  123. data/spec/views/accounts/create.js.haml_spec.rb +2 -2
  124. data/spec/views/accounts/destroy.js.haml_spec.rb +1 -1
  125. data/spec/views/accounts/edit.js.haml_spec.rb +2 -2
  126. data/spec/views/accounts/index.haml_spec.rb +2 -2
  127. data/spec/views/accounts/index.js.haml_spec.rb +1 -1
  128. data/spec/views/accounts/show.haml_spec.rb +4 -4
  129. data/spec/views/accounts/update.js.haml_spec.rb +1 -1
  130. data/spec/views/admin/field_groups/create.js.haml_spec.rb +1 -1
  131. data/spec/views/admin/field_groups/destroy.js.haml_spec.rb +1 -1
  132. data/spec/views/admin/field_groups/edit.js.haml_spec.rb +1 -1
  133. data/spec/views/admin/field_groups/new.js.haml_spec.rb +1 -1
  134. data/spec/views/admin/field_groups/update.js.haml_spec.rb +1 -1
  135. data/spec/views/admin/users/create.js.haml_spec.rb +2 -2
  136. data/spec/views/admin/users/destroy.js.haml_spec.rb +2 -2
  137. data/spec/views/admin/users/edit.js.haml_spec.rb +2 -2
  138. data/spec/views/admin/users/index.haml_spec.rb +1 -1
  139. data/spec/views/admin/users/index.js.haml_spec.rb +2 -2
  140. data/spec/views/admin/users/reactivate.js.haml_spec.rb +1 -1
  141. data/spec/views/admin/users/suspend.js.haml_spec.rb +1 -1
  142. data/spec/views/admin/users/update.js.haml_spec.rb +1 -1
  143. data/spec/views/application/auto_complete.haml_spec.rb +3 -3
  144. data/spec/views/campaigns/_edit.haml_spec.rb +1 -1
  145. data/spec/views/campaigns/create.js.haml_spec.rb +3 -3
  146. data/spec/views/campaigns/destroy.js.haml_spec.rb +1 -1
  147. data/spec/views/campaigns/edit.js.haml_spec.rb +2 -2
  148. data/spec/views/campaigns/index.haml_spec.rb +1 -1
  149. data/spec/views/campaigns/index.js.haml_spec.rb +1 -1
  150. data/spec/views/campaigns/show.haml_spec.rb +4 -4
  151. data/spec/views/campaigns/update.js.haml_spec.rb +1 -1
  152. data/spec/views/contacts/_edit.haml_spec.rb +7 -7
  153. data/spec/views/contacts/_new.haml_spec.rb +1 -1
  154. data/spec/views/contacts/create.js.haml_spec.rb +4 -4
  155. data/spec/views/contacts/destroy.js.haml_spec.rb +1 -1
  156. data/spec/views/contacts/edit.js.haml_spec.rb +3 -3
  157. data/spec/views/contacts/index.haml_spec.rb +1 -1
  158. data/spec/views/contacts/index.js.html_spec.rb +1 -1
  159. data/spec/views/contacts/new.js.haml_spec.rb +1 -1
  160. data/spec/views/contacts/show.haml_spec.rb +3 -3
  161. data/spec/views/contacts/update.js.haml_spec.rb +2 -2
  162. data/spec/views/home/index.haml_spec.rb +1 -1
  163. data/spec/views/home/index.js.haml_spec.rb +1 -1
  164. data/spec/views/home/options.js.haml_spec.rb +2 -2
  165. data/spec/views/leads/_convert.haml_spec.rb +3 -3
  166. data/spec/views/leads/_edit.haml_spec.rb +2 -2
  167. data/spec/views/leads/_new.haml_spec.rb +2 -2
  168. data/spec/views/leads/_sidebar_show.haml_spec.rb +5 -5
  169. data/spec/views/leads/convert.js.haml_spec.rb +4 -4
  170. data/spec/views/leads/create.js.haml_spec.rb +5 -5
  171. data/spec/views/leads/destroy.js.haml_spec.rb +2 -2
  172. data/spec/views/leads/edit.js.haml_spec.rb +4 -4
  173. data/spec/views/leads/index.haml_spec.rb +1 -1
  174. data/spec/views/leads/index.js.haml_spec.rb +1 -1
  175. data/spec/views/leads/new.js.haml_spec.rb +1 -1
  176. data/spec/views/leads/promote.js.haml_spec.rb +7 -7
  177. data/spec/views/leads/reject.js.haml_spec.rb +2 -2
  178. data/spec/views/leads/show.haml_spec.rb +2 -2
  179. data/spec/views/leads/update.js.haml_spec.rb +4 -4
  180. data/spec/views/opportunities/_edit.haml_spec.rb +7 -7
  181. data/spec/views/opportunities/_new.haml_spec.rb +2 -2
  182. data/spec/views/opportunities/create.js.haml_spec.rb +6 -6
  183. data/spec/views/opportunities/destroy.js.haml_spec.rb +3 -3
  184. data/spec/views/opportunities/edit.js.haml_spec.rb +3 -3
  185. data/spec/views/opportunities/index.haml_spec.rb +1 -1
  186. data/spec/views/opportunities/index.js.haml_spec.rb +1 -1
  187. data/spec/views/opportunities/new.js.haml_spec.rb +1 -1
  188. data/spec/views/opportunities/show.haml_spec.rb +3 -3
  189. data/spec/views/opportunities/update.js.haml_spec.rb +4 -4
  190. data/spec/views/tasks/_edit.haml_spec.rb +1 -1
  191. data/spec/views/tasks/complete.js.haml_spec.rb +4 -4
  192. data/spec/views/tasks/create.js.haml_spec.rb +6 -6
  193. data/spec/views/tasks/destroy.js.haml_spec.rb +2 -2
  194. data/spec/views/tasks/index.haml_spec.rb +4 -4
  195. data/spec/views/tasks/new.js.haml_spec.rb +1 -1
  196. data/spec/views/tasks/uncomplete.js.haml_spec.rb +2 -2
  197. data/spec/views/tasks/update.js.haml_spec.rb +18 -18
  198. data/spec/views/users/upload_avatar.js.haml_spec.rb +2 -2
  199. metadata +5 -4
@@ -26,11 +26,13 @@ module AccountsHelper
26
26
  # and prepends the currently selected account, if any.
27
27
  #----------------------------------------------------------------------------
28
28
  def account_select(options = {})
29
- options[:selected] = @account&.id || 0
29
+ options[:selected] = @account&.id.to_i
30
30
  accounts = ([@account.new_record? ? nil : @account] + Account.my(current_user).order(:name).limit(25)).compact.uniq
31
31
  collection_select :account, :id, accounts, :id, :name,
32
- { prompt: t(:select_an_account), include_blank: false },
33
- style: 'width:330px;', class: 'select2'
32
+ { include_blank: true },
33
+ style: 'width:330px;', class: 'select2',
34
+ placeholder: t(:select_an_account),
35
+ "data-url": auto_complete_accounts_path(format: 'json')
34
36
  end
35
37
 
36
38
  # Select an existing account or create a new one.
@@ -42,10 +44,10 @@ module AccountsHelper
42
44
  content_tag(:div, class: 'label') do
43
45
  t(:account).html_safe +
44
46
  content_tag(:span, id: 'account_create_title') do
45
- "(#{t :create_new} #{t :or} <a href='#' onclick='crm.show_select_account(); return false;'>#{t :select_existing}</a>):".html_safe
47
+ " (#{t :create_new} #{t :or} <a href='#' onclick='crm.show_select_account(); return false;'>#{t :select_existing}</a>):".html_safe
46
48
  end +
47
49
  content_tag(:span, id: 'account_select_title') do
48
- "(<a href='#' onclick='crm.show_create_account(); return false;'>#{t :create_new}</a> #{t :or} #{t :select_existing}):".html_safe
50
+ " (<a href='#' onclick='crm.show_create_account(); return false;'>#{t :create_new}</a> #{t :or} #{t :select_existing}):".html_safe
49
51
  end +
50
52
  content_tag(:span, ':', id: 'account_disabled_title')
51
53
  end +
@@ -7,7 +7,7 @@
7
7
  #------------------------------------------------------------------------------
8
8
  module ApplicationHelper
9
9
  def tabs(tabs = nil)
10
- tabs ||= controller_path =~ /admin/ ? FatFreeCRM::Tabs.admin : FatFreeCRM::Tabs.main
10
+ tabs ||= controller_path.match?(/admin/) ? FatFreeCRM::Tabs.admin : FatFreeCRM::Tabs.main
11
11
  if tabs
12
12
  @current_tab ||= tabs.first[:text] # Select first tab by default.
13
13
  tabs.each { |tab| tab[:active] = (@current_tab == tab[:text] || @current_tab == tab[:url][:controller]) }
@@ -254,7 +254,7 @@ module ApplicationHelper
254
254
  if site == :skype
255
255
  url = "callto:" + url
256
256
  else
257
- url = "http://" + url unless url =~ /^https?:\/\//
257
+ url = "http://" + url unless url.match?(/^https?:\/\//)
258
258
  end
259
259
  link_to(image_tag("#{site}.gif", size: "15x15"), h(url), "data-popup": true, title: t(:open_in_window, h(url)))
260
260
  end.compact.join("\n").html_safe
@@ -11,7 +11,7 @@ module LeadsHelper
11
11
  #----------------------------------------------------------------------------
12
12
  def stars_for(lead)
13
13
  star = '&#9733;'
14
- rating = lead.rating || 0
14
+ rating = lead.rating.to_i
15
15
  (star * rating).html_safe + content_tag(:font, (star * (RATING_STARS - rating)).html_safe, color: 'gainsboro')
16
16
  end
17
17
 
@@ -20,9 +20,9 @@ module OpportunitiesHelper
20
20
  summary << (opportunity.stage ? t(opportunity.stage) : t(:other))
21
21
  summary << number_to_currency(opportunity.weighted_amount, precision: 0)
22
22
  unless %w[won lost].include?(opportunity.stage)
23
- amount << number_to_currency(opportunity.amount || 0, precision: 0)
23
+ amount << number_to_currency(opportunity.amount.to_f, precision: 0)
24
24
  amount << (opportunity.discount ? t(:discount_number, number_to_currency(opportunity.discount, precision: 0)) : t(:no_discount))
25
- amount << t(:probability_number, (opportunity.probability || 0).to_s + '%')
25
+ amount << t(:probability_number, opportunity.probability.to_i.to_s + '%')
26
26
  summary << amount.join(' ')
27
27
  end
28
28
  summary << if opportunity.closes_on
@@ -37,11 +37,64 @@ module OpportunitiesHelper
37
37
  # and prepends the currently selected campaign, if any.
38
38
  #----------------------------------------------------------------------------
39
39
  def opportunity_campaign_select(options = {})
40
- options[:selected] ||= @opportunity.campaign_id || 0
40
+ options[:selected] ||= @opportunity.campaign_id.to_i
41
41
  selected_campaign = Campaign.find_by_id(options[:selected])
42
42
  campaigns = ([selected_campaign] + Campaign.my(current_user).order(:name).limit(25)).compact.uniq
43
43
  collection_select :opportunity, :campaign_id, campaigns, :id, :name,
44
44
  { selected: options[:selected], prompt: t(:select_a_campaign) },
45
45
  style: 'width:330px;', class: 'select2'
46
46
  end
47
+
48
+ # Generates the inline revenue message for the opportunity list table.
49
+ #----------------------------------------------------------------------------
50
+ def opportunity_revenue_message(opportunity, detailed = false)
51
+ msg = []
52
+ won_or_lost = %w[won lost].include?(opportunity.stage)
53
+
54
+ if opportunity.weighted_amount != 0
55
+ msg << content_tag(:b, number_to_currency(opportunity.weighted_amount, precision: 0))
56
+ end
57
+
58
+ unless won_or_lost
59
+ if detailed
60
+ if opportunity.amount.to_f != 0
61
+ msg << number_to_currency(opportunity.amount.to_f, precision: 0)
62
+ end
63
+
64
+ if opportunity.discount.to_f != 0
65
+ msg << t(:discount) + ' ' + number_to_currency(opportunity.discount, precision: 0)
66
+ end
67
+ end
68
+
69
+ if opportunity.probability.to_i != 0
70
+ msg << t(:probability) + ' ' + opportunity.probability.to_s + '%'
71
+ end
72
+ end
73
+
74
+ msg << opportunity_closes_on_message(opportunity, won_or_lost)
75
+
76
+ msg.join(' | ').html_safe
77
+ end
78
+
79
+ private
80
+
81
+ def opportunity_closes_on_message(opportunity, won_or_lost)
82
+ if opportunity.closes_on
83
+ if won_or_lost
84
+ if opportunity.closes_on >= Date.today
85
+ t(:closing_date, l(opportunity.closes_on, format: :mmddyy))
86
+ else
87
+ t(:closed_ago_on, time_ago: distance_of_time_in_words(opportunity.closes_on, Date.today), date: l(opportunity.closes_on, format: :mmddyy))
88
+ end
89
+ elsif opportunity.closes_on > Date.today
90
+ t(:expected_to_close, time: distance_of_time_in_words(Date.today, opportunity.closes_on), date: l(opportunity.closes_on, format: :mmddyy))
91
+ elsif opportunity.closes_on == Date.today
92
+ content_tag(:span, t(:closes_today), class: 'warn')
93
+ else
94
+ content_tag(:span, t(:past_due, distance_of_time_in_words(opportunity.closes_on, Date.today)), class: 'warn')
95
+ end
96
+ else
97
+ t(:no_closing_date)
98
+ end
99
+ end
47
100
  end
@@ -17,7 +17,7 @@ module TagsHelper
17
17
  elsif !query.include?(hashtag)
18
18
  query += " #{hashtag}"
19
19
  end
20
- out << link_to_function(tag, "crm.search_tagged('#{query}', '#{model.class.to_s.tableize}')", title: tag)
20
+ out << link_to_function(tag, "crm.search_tagged('#{escape_javascript(query)}', '#{model.class.to_s.tableize}')", title: tag)
21
21
  end
22
22
  end
23
23
 
@@ -104,13 +104,6 @@ class Lead < ActiveRecord::Base
104
104
  end
105
105
  end
106
106
 
107
- # Deprecated: see update_with_lead_counters
108
- #----------------------------------------------------------------------------
109
- def update_with_permissions(attributes, _users = nil)
110
- ActiveSupport::Deprecation.warn "lead.update_with_permissions is deprecated and may be removed from future releases, use user_ids and group_ids inside attributes instead and call lead.update_with_lead_counters"
111
- update_with_lead_counters(attributes)
112
- end
113
-
114
107
  # Update lead attributes taking care of campaign lead counters when necessary.
115
108
  #----------------------------------------------------------------------------
116
109
  def update_with_lead_counters(attributes)
@@ -50,10 +50,11 @@ class Opportunity < ActiveRecord::Base
50
50
  scope :not_lost, -> { where("opportunities.stage <> 'lost'") }
51
51
  scope :pipeline, -> { where("opportunities.stage IS NULL OR (opportunities.stage != 'won' AND opportunities.stage != 'lost')") }
52
52
  scope :unassigned, -> { where("opportunities.assigned_to IS NULL") }
53
+ scope :weighted_sort, -> { select('*, amount*probability') }
53
54
 
54
55
  # Search by name OR id
55
56
  scope :text_search, ->(query) {
56
- if query =~ /\A\d+\z/
57
+ if query.match?(/\A\d+\z/)
57
58
  where('upper(name) LIKE upper(:name) OR opportunities.id = :id', name: "%#{query}%", id: query)
58
59
  else
59
60
  ransack('name_cont' => query).result
@@ -100,7 +101,7 @@ class Opportunity < ActiveRecord::Base
100
101
 
101
102
  #----------------------------------------------------------------------------
102
103
  def weighted_amount
103
- ((amount || 0) - (discount || 0)) * (probability || 0) / 100.0
104
+ (amount.to_f - discount.to_f) * probability.to_i / 100.0
104
105
  end
105
106
 
106
107
  # Backend handler for [Create New Opportunity] form (see opportunity/create).
@@ -12,7 +12,7 @@ class OpportunityObserver < ActiveRecord::Observer
12
12
 
13
13
  def after_create(item)
14
14
  if item.campaign && item.stage == "won"
15
- update_campaign_revenue(item.campaign, (item.amount || 0) - (item.discount || 0))
15
+ update_campaign_revenue(item.campaign, item.amount.to_f - item.discount.to_f)
16
16
  end
17
17
  end
18
18
 
@@ -24,11 +24,11 @@ class OpportunityObserver < ActiveRecord::Observer
24
24
  original = @@opportunities.delete(item.id)
25
25
  if original
26
26
  if original.stage != "won" && item.stage == "won" # :other to :won -- add to total campaign revenue.
27
- update_campaign_revenue(item.campaign, (item.amount || 0) - (item.discount || 0))
27
+ update_campaign_revenue(item.campaign, item.amount.to_f - item.discount.to_f)
28
28
  item.update_attribute(:probability, 100) # Set probability to 100% if won
29
29
  return log_activity(item, :won)
30
30
  elsif original.stage == "won" && item.stage != "won" # :won to :other -- substract from total campaign revenue.
31
- update_campaign_revenue(original.campaign, -((original.amount || 0) - (original.discount || 0)))
31
+ update_campaign_revenue(original.campaign, -(original.amount.to_f - original.discount.to_f))
32
32
  elsif original.stage != "lost" && item.stage == "lost"
33
33
  item.update_attribute(:probability, 0) # Set probability to 0% if lost
34
34
  end
@@ -42,7 +42,7 @@ class OpportunityObserver < ActiveRecord::Observer
42
42
  end
43
43
 
44
44
  def update_campaign_revenue(campaign, revenue)
45
- campaign&.update_attribute(:revenue, (campaign.revenue || 0) + revenue)
45
+ campaign&.update_attribute(:revenue, campaign.revenue.to_f + revenue)
46
46
  end
47
47
 
48
48
  ActiveSupport.run_load_hooks(:fat_free_crm_opportunity_observer, self)
@@ -47,10 +47,9 @@ class Ability
47
47
  scope = scope.or(t[:group_id].eq_any(group_ids))
48
48
  end
49
49
 
50
- entities.each do |klass|
51
- if (asset_ids = Permission.where(scope.and(t[:asset_type].eq(klass.name))).pluck(:asset_id)).any?
52
- can :manage, klass, id: asset_ids
53
- end
50
+ permissions = Permission.select(:asset_type, :asset_id).where(scope).where(asset_type: entities.map { |k| k.name.to_s })
51
+ permissions.each do |p|
52
+ can :manage, p.asset_type.constantize, id: p.asset_id
54
53
  end
55
54
  end
56
55
 
@@ -9,8 +9,8 @@
9
9
  %dt
10
10
  - unless objectives.empty?
11
11
  %b #{t :actual}:
12
- = t(:revenue_number, number_to_currency(campaign.revenue || 0, precision: 0)) + " | "
13
- = t('pluralize.lead', campaign.leads_count || 0) + " | "
14
- = t('pluralize.opportunity', campaign.opportunities_count || 0)
12
+ = t(:revenue_number, number_to_currency(campaign.revenue.to_f, precision: 0)) + " | "
13
+ = t('pluralize.lead', campaign.leads_count.to_i) + " | "
14
+ = t('pluralize.opportunity', campaign.opportunities_count.to_i)
15
15
  - if campaign.opportunities_count? && campaign.leads_count? && campaign.leads_count != 0
16
16
  = " | " + t(:conversion_number, number_to_percentage(campaign.opportunities_count * 100 / campaign.leads_count, precision: 1))
@@ -17,25 +17,10 @@
17
17
  = t(:added_ago, value: timeago(opportunity.created_at)).html_safe
18
18
  - unless current_user.preference[:opportunities_index_view] == "opportunities_index_brief"
19
19
  %dt
20
- %b= number_to_currency(opportunity.weighted_amount, precision: 0) + " | "
21
- - won_or_lost = %w(won lost).include?(opportunity.stage)
22
- - unless won_or_lost
23
- = t(:probability_number, (opportunity.probability || 0).to_s + '%') + " | "
24
- - if opportunity.closes_on
25
- - if won_or_lost
26
- - if opportunity.closes_on >= Date.today
27
- = t(:closing_date, l(opportunity.closes_on, format: :mmddyy))
28
- - else
29
- = t(:closed_ago_on, time_ago: distance_of_time_in_words(opportunity.closes_on, Date.today), date: l(opportunity.closes_on, format: :mmddyy))
30
- - elsif opportunity.closes_on > Date.today
31
- = t(:expected_to_close, time: distance_of_time_in_words(Date.today, opportunity.closes_on), date: l(opportunity.closes_on, format: :mmddyy))
32
- - elsif opportunity.closes_on == Date.today
33
- %span.warn= t(:closes_today)
34
- - else
35
- %span.warn= t(:past_due, distance_of_time_in_words(opportunity.closes_on, Date.today))
36
- - else
37
- = t(:no_closing_date)
20
+ = opportunity_revenue_message(opportunity)
21
+
38
22
  - if opportunity.tags.present?
39
23
  %dt
40
24
  .tags= tags_for_dashboard(opportunity)
41
- = hook(:opportunity_bottom, self, opportunity: opportunity)
25
+
26
+ = hook(:opportunity_bottom, self, opportunity: opportunity)
@@ -27,30 +27,7 @@
27
27
  = t(:added_ago, value: timeago(opportunity.created_at)).html_safe
28
28
 
29
29
  %dt
30
- %b= number_to_currency(opportunity.weighted_amount, precision: 0) + " | "
31
- - won_or_lost = %w(won lost).include?(opportunity.stage)
32
- - unless won_or_lost
33
- = number_to_currency(opportunity.amount || 0, precision: 0)
34
- - if opportunity.discount
35
- = t(:discount_number, number_to_currency(opportunity.discount, precision: 0))
36
- - else
37
- = t(:no_discount)
38
- = t(:probability_number, (opportunity.probability || 0).to_s + '%') + " | "
39
-
40
- - if opportunity.closes_on
41
- - if won_or_lost
42
- - if opportunity.closes_on >= Date.today
43
- = t(:closing_date, l(opportunity.closes_on, format: :mmddyy))
44
- - else
45
- = t(:closed_ago_on, time_ago: distance_of_time_in_words(opportunity.closes_on, Date.today), date: l(opportunity.closes_on, format: :mmddyy))
46
- - elsif opportunity.closes_on > Date.today
47
- = t(:expected_to_close, time: distance_of_time_in_words(Date.today, opportunity.closes_on), date: l(opportunity.closes_on, format: :mmddyy))
48
- - elsif opportunity.closes_on == Date.today
49
- %span.warn= t(:closes_today)
50
- - else
51
- %span.warn= t(:past_due, distance_of_time_in_words(opportunity.closes_on, Date.today))
52
- - else
53
- = t(:no_closing_date)
30
+ = opportunity_revenue_message(opportunity, true)
54
31
 
55
32
  - if opportunity.tags.present?
56
33
  %dt
@@ -19,16 +19,16 @@
19
19
  %dt= @opportunity.probability ? @opportunity.probability.to_s + "%" : t(:n_a)
20
20
  %tt #{t :probability}:
21
21
  %li
22
- %dt= @opportunity.amount ? number_to_currency(@opportunity.amount || 0, precision: 0) : t(:n_a)
22
+ %dt= @opportunity.amount ? number_to_currency(@opportunity.amount.to_f, precision: 0) : t(:n_a)
23
23
  %tt #{t :amount}:
24
24
  %li
25
- %dt= @opportunity.discount ? number_to_currency(@opportunity.discount || 0, precision: 0) : t(:n_a)
25
+ %dt= @opportunity.discount ? number_to_currency(@opportunity.discount.to_f, precision: 0) : t(:n_a)
26
26
  - if @opportunity.amount && @opportunity.discount
27
27
  %tt== Discount (#{number_to_percentage(@opportunity.discount / @opportunity.amount * 100, precision: 1)}):
28
28
  - else
29
29
  %tt #{t :discount}:
30
30
  %li
31
- %dt= @opportunity.amount ? number_to_currency(@opportunity.weighted_amount || 0, precision: 0) : t(:n_a)
31
+ %dt= @opportunity.amount ? number_to_currency(@opportunity.weighted_amount.to_f, precision: 0) : t(:n_a)
32
32
  %tt #{t :weighted_amount}:
33
33
  %li
34
34
  %dt= @opportunity.assigned_to ? truncate(@opportunity.assignee.full_name, length: 16) : nil
@@ -37,7 +37,7 @@
37
37
  %td
38
38
  != account_select_or_create(a) do |options|
39
39
  -# Add [-- None --] account choice when editing existing opportunity that has an account.
40
- - options[:selected] = @account.id || 0
40
+ - options[:selected] = @account.id.to_i
41
41
 
42
42
  %td= spacer
43
43
  %td
@@ -17,6 +17,6 @@ Ransack.configure do |config|
17
17
  config.ajax_options = {
18
18
  url: '/:controller/auto_complete.json',
19
19
  type: 'POST',
20
- key: 'auto_complete_query'
20
+ key: 'term'
21
21
  }
22
22
  end
data/db/schema.rb CHANGED
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  # This file is auto-generated from the current state of the database. Instead
4
2
  # of editing this file, please use the migrations feature of Active Record to
5
3
  # incrementally modify your database, and then regenerate this schema definition.
@@ -54,7 +52,6 @@ ActiveRecord::Schema.define(version: 20180103223438) do
54
52
  t.text "subscribed_users"
55
53
  t.integer "contacts_count", default: 0
56
54
  t.integer "opportunities_count", default: 0
57
- t.integer "pipeline_opportunities_count", default: 0
58
55
  t.index ["assigned_to"], name: "index_accounts_on_assigned_to"
59
56
  t.index ["user_id", "name", "deleted_at"], name: "index_accounts_on_user_id_and_name_and_deleted_at", unique: true
60
57
  end
data/fat_free_crm.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
18
18
  gem.email = ['mike@fatfreecrm.com', 'nathan@fatfreecrm.com', 'warp@fatfreecrm.com', 'steveyken@gmail.com', 'daniel.oconnor@gmail.com']
19
19
  gem.files = files
20
20
  gem.version = FatFreeCRM::VERSION::STRING
21
- gem.required_ruby_version = '>= 2.3.0'
21
+ gem.required_ruby_version = '>= 2.4.0'
22
22
  gem.license = 'MIT'
23
23
 
24
24
  gem.add_dependency 'rails', '~> 5.1.0'
@@ -21,7 +21,7 @@ class String
21
21
  end
22
22
 
23
23
  def to_url
24
- match(/^https?:\/\//) ? self : "http://#{self}"
24
+ match?(/^https?:\/\//) ? self : "http://#{self}"
25
25
  end
26
26
 
27
27
  def true?
@@ -13,8 +13,8 @@ module FatFreeCRM
13
13
  config.active_record.observers = %i[lead_observer opportunity_observer
14
14
  task_observer entity_observer]
15
15
 
16
- initializer "model_core.factories", after: "factory_girl.set_factory_paths" do
17
- FactoryGirl.definition_file_paths << File.expand_path('../../../spec/factories', __FILE__) if defined?(FactoryGirl)
16
+ initializer "model_core.factories", after: "factory_bot.set_factory_paths" do
17
+ FactoryBot.definition_file_paths << File.expand_path('../../../spec/factories', __FILE__) if defined?(FactoryBot)
18
18
  end
19
19
 
20
20
  initializer :append_migrations do |app|
@@ -75,7 +75,7 @@ module FatFreeCRM
75
75
  end
76
76
 
77
77
  def method_missing(method_id, *args, &block)
78
- if method_id.to_s =~ /\Acf_.*[^=]\Z/
78
+ if method_id.to_s.match?(/\Acf_.*[^=]\Z/)
79
79
  # Refresh columns and try again.
80
80
  self.class.reset_column_information
81
81
  # If new record, create new object from class, else reload class
@@ -76,20 +76,6 @@ module FatFreeCRM
76
76
  permissions_to_remove.each { |p| permissions.delete(p); p.destroy }
77
77
  end
78
78
 
79
- # Save the model along with its permissions if any.
80
- #--------------------------------------------------------------------------
81
- def save_with_permissions(_users = nil)
82
- ActiveSupport::Deprecation.warn "save_with_permissions is deprecated and may be removed from future releases, use user_ids and group_ids inside attributes instead."
83
- save
84
- end
85
-
86
- # Update the model along with its permissions if any.
87
- #--------------------------------------------------------------------------
88
- def update_with_permissions(attributes, _users = nil)
89
- ActiveSupport::Deprecation.warn "update_with_permissions is deprecated and may be removed from future releases, use user_ids and group_ids inside attributes instead."
90
- update_attributes(attributes)
91
- end
92
-
93
79
  # Save the model copying other model's permissions.
94
80
  #--------------------------------------------------------------------------
95
81
  def save_with_model_permissions(model)
@@ -8,8 +8,8 @@
8
8
  module FatFreeCRM
9
9
  module VERSION #:nodoc:
10
10
  MAJOR = 0
11
- MINOR = 17
12
- TINY = 1
11
+ MINOR = 18
12
+ TINY = 2
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
@@ -18,7 +18,7 @@ namespace :ffcrm do
18
18
  end
19
19
 
20
20
  # Don't continue unless user typed y(es)
21
- if proceed =~ /y(?:es)*/i
21
+ if proceed.match?(/y(?:es)*/i)
22
22
  Rake::Task["db:migrate"].invoke
23
23
  Rake::Task["ffcrm:setup:admin"].invoke
24
24
  else
@@ -44,7 +44,7 @@ namespace :ffcrm do
44
44
 
45
45
  password ||= "manager"
46
46
  print "Password [#{password}]: "
47
- echo = ->(toggle) { return if RUBY_PLATFORM =~ /mswin/; system(toggle ? "stty echo && echo" : "stty -echo") }
47
+ echo = ->(toggle) { return if RUBY_PLATFORM.match?(/mswin/); system(toggle ? "stty echo && echo" : "stty -echo") }
48
48
  begin
49
49
  echo.call(false)
50
50
  reply = STDIN.gets.strip
@@ -68,8 +68,8 @@ namespace :ffcrm do
68
68
  reply = STDIN.gets.strip
69
69
  break unless reply.blank?
70
70
  end
71
- break if reply =~ /y(?:es)*/i
72
- redo if reply =~ /no*/i
71
+ break if reply.match?(/y(?:es)*/i)
72
+ redo if reply.match?(/no*/i)
73
73
  puts "No admin user was created."
74
74
  exit
75
75
  end