fat_free_crm 0.18.2 → 0.19.0

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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +61 -160
  3. data/.travis.yml +27 -11
  4. data/CHANGELOG.md +40 -24
  5. data/CONTRIBUTORS.md +1 -0
  6. data/Dockerfile +45 -14
  7. data/Gemfile +16 -10
  8. data/Gemfile.lock +230 -222
  9. data/Procfile +1 -1
  10. data/README.md +2 -2
  11. data/Rakefile +1 -1
  12. data/app/assets/stylesheets/common.scss +1 -1
  13. data/app/controllers/admin/application_controller.rb +1 -1
  14. data/app/controllers/admin/field_groups_controller.rb +1 -3
  15. data/app/controllers/admin/tags_controller.rb +1 -3
  16. data/app/controllers/admin/users_controller.rb +5 -8
  17. data/app/controllers/application_controller.rb +11 -45
  18. data/app/controllers/comments_controller.rb +2 -5
  19. data/{config/initializers/authlogic.rb → app/controllers/confirmations_controller.rb} +4 -2
  20. data/app/controllers/emails_controller.rb +0 -2
  21. data/app/controllers/entities/accounts_controller.rb +1 -3
  22. data/app/controllers/entities/campaigns_controller.rb +1 -3
  23. data/app/controllers/entities/contacts_controller.rb +4 -24
  24. data/app/controllers/entities/leads_controller.rb +7 -10
  25. data/app/controllers/entities/opportunities_controller.rb +4 -14
  26. data/app/controllers/entities_controller.rb +21 -7
  27. data/app/controllers/home_controller.rb +2 -4
  28. data/app/controllers/passwords_controller.rb +3 -59
  29. data/{spec/features/support/maintain_sessions.rb → app/controllers/registrations_controller.rb} +12 -5
  30. data/{lib/development_tasks/gem.rake → app/controllers/sessions_controller.rb} +6 -6
  31. data/app/controllers/tasks_controller.rb +8 -17
  32. data/app/controllers/users_controller.rb +8 -29
  33. data/app/helpers/admin/users_helper.rb +1 -1
  34. data/app/helpers/application_helper.rb +27 -32
  35. data/app/helpers/campaigns_helper.rb +1 -1
  36. data/app/helpers/contacts_helper.rb +1 -3
  37. data/app/helpers/opportunities_helper.rb +4 -12
  38. data/app/helpers/tasks_helper.rb +1 -1
  39. data/app/helpers/users_helper.rb +1 -3
  40. data/{config/initializers/paper_trail.rb → app/mailers/devise_mailer.rb} +5 -1
  41. data/app/mailers/user_mailer.rb +0 -9
  42. data/app/models/entities/account.rb +10 -10
  43. data/app/models/entities/campaign.rb +4 -6
  44. data/app/models/entities/contact.rb +24 -12
  45. data/app/models/entities/lead.rb +7 -7
  46. data/app/models/entities/opportunity.rb +7 -9
  47. data/app/models/fields/custom_field.rb +1 -0
  48. data/app/models/fields/custom_field_date_pair.rb +2 -0
  49. data/app/models/fields/field.rb +1 -3
  50. data/app/models/list.rb +1 -1
  51. data/app/models/observers/entity_observer.rb +3 -7
  52. data/app/models/observers/lead_observer.rb +2 -4
  53. data/app/models/observers/opportunity_observer.rb +2 -4
  54. data/app/models/observers/task_observer.rb +1 -1
  55. data/app/models/polymorphic/email.rb +2 -2
  56. data/app/models/polymorphic/task.rb +13 -9
  57. data/app/models/polymorphic/version.rb +3 -2
  58. data/app/models/setting.rb +2 -0
  59. data/app/models/users/permission.rb +3 -3
  60. data/app/models/users/preference.rb +2 -1
  61. data/app/models/users/user.rb +67 -42
  62. data/app/views/accounts/_top_section.html.haml +1 -1
  63. data/app/views/accounts/edit.js.haml +1 -1
  64. data/app/views/accounts/update.js.haml +2 -2
  65. data/app/views/admin/users/_user.html.haml +4 -4
  66. data/app/views/contacts/_index_brief.html.haml +1 -1
  67. data/app/views/contacts/_index_full.html.haml +1 -1
  68. data/app/views/contacts/_index_long.html.haml +1 -1
  69. data/app/views/devise/confirmations/new.html.haml +9 -0
  70. data/app/views/devise/mailer/confirmation_instructions.html.haml +4 -0
  71. data/app/views/devise/mailer/password_change.html.haml +3 -0
  72. data/app/views/devise/mailer/reset_password_instructions.html.haml +6 -0
  73. data/app/views/devise/passwords/edit.html.haml +18 -0
  74. data/app/views/devise/passwords/new.html.haml +10 -0
  75. data/app/views/devise/registrations/new.html.haml +21 -0
  76. data/app/views/devise/sessions/new.html.haml +32 -0
  77. data/app/views/layouts/_about.html.haml +5 -5
  78. data/app/views/layouts/_header.html.haml +3 -3
  79. data/app/views/layouts/admin/_header.html.haml +1 -1
  80. data/app/views/shared/_address.html.haml +5 -5
  81. data/app/views/shared/_paginate_with_per_page.html.haml +1 -0
  82. data/app/views/users/_avatar.html.haml +1 -1
  83. data/bin/bundle +1 -1
  84. data/bin/rails +1 -1
  85. data/bin/setup +38 -0
  86. data/bin/update +33 -0
  87. data/bin/yarn +13 -0
  88. data/config/application.rb +8 -6
  89. data/config/boot.rb +1 -1
  90. data/config/brakeman.ignore +2 -2
  91. data/config/database.postgres.docker.yml +5 -5
  92. data/config/environment.rb +1 -1
  93. data/config/environments/development.rb +1 -0
  94. data/config/environments/test.rb +7 -0
  95. data/config/initializers/action_mailer.rb +1 -3
  96. data/config/initializers/application_controller_renderer.rb +9 -0
  97. data/config/initializers/assets.rb +6 -11
  98. data/config/initializers/backtrace_silencers.rb +0 -6
  99. data/config/initializers/content_security_policy.rb +26 -0
  100. data/config/initializers/cookies_serializer.rb +3 -6
  101. data/config/initializers/devise.rb +289 -0
  102. data/config/initializers/filter_parameter_logging.rb +0 -5
  103. data/config/initializers/gravatar.rb +0 -1
  104. data/config/initializers/inflections.rb +0 -6
  105. data/config/initializers/mime_types.rb +1 -9
  106. data/config/initializers/new_framework_defaults_5_2.rb +40 -0
  107. data/config/initializers/relative_url_root.rb +1 -3
  108. data/config/initializers/session_store.rb +1 -3
  109. data/config/initializers/wrap_parameters.rb +4 -9
  110. data/config/locales/fat_free_crm.en-GB.yml +5 -5
  111. data/config/locales/fat_free_crm.en-US.yml +5 -5
  112. data/config/locales/fat_free_crm.fr.yml +1 -1
  113. data/config/locales/fat_free_crm.ru.yml +1 -0
  114. data/config/routes.rb +20 -9
  115. data/db/demo/users.yml +62 -81
  116. data/db/migrate/20100928030620_remove_uuid.rb +1 -2
  117. data/db/migrate/20120316045804_activities_to_versions.rb +1 -0
  118. data/db/migrate/20120510025219_add_not_null_constraints_for_timestamp_columns.rb +1 -0
  119. data/db/migrate/20180107082701_authlogic_to_devise.rb +58 -0
  120. data/db/schema.rb +48 -43
  121. data/docker-compose.yml +10 -0
  122. data/fat_free_crm.gemspec +11 -13
  123. data/lib/development_tasks/license.rake +2 -2
  124. data/lib/fat_free_crm/callback.rb +2 -2
  125. data/lib/fat_free_crm/comment_extensions.rb +2 -4
  126. data/lib/fat_free_crm/core_ext/string.rb +1 -1
  127. data/lib/fat_free_crm/engine.rb +1 -1
  128. data/lib/fat_free_crm/errors.rb +1 -1
  129. data/lib/fat_free_crm/export_csv.rb +1 -0
  130. data/lib/fat_free_crm/exportable.rb +1 -1
  131. data/lib/fat_free_crm/fields.rb +1 -1
  132. data/lib/fat_free_crm/gem_dependencies.rb +1 -1
  133. data/lib/fat_free_crm/gem_ext/simple_form/action_view_extensions/form_helper.rb +1 -3
  134. data/lib/fat_free_crm/i18n.rb +2 -2
  135. data/lib/fat_free_crm/mail_processor/base.rb +4 -10
  136. data/lib/fat_free_crm/mail_processor/dropbox.rb +5 -15
  137. data/lib/fat_free_crm/permissions.rb +7 -4
  138. data/lib/fat_free_crm/sortable.rb +1 -1
  139. data/lib/fat_free_crm/tabs.rb +2 -2
  140. data/lib/fat_free_crm/version.rb +2 -2
  141. data/lib/gravatar_image_tag.rb +7 -8
  142. data/lib/missing_translation_detector.rb +1 -0
  143. data/lib/tasks/ffcrm/missing_translations.rake +1 -0
  144. data/lib/tasks/ffcrm/setup.rake +10 -1
  145. data/lib/tasks/ffcrm/update_data.rake +2 -2
  146. data/script/rails +2 -2
  147. data/spec/controllers/admin/users_controller_spec.rb +0 -56
  148. data/spec/controllers/comments_controller_spec.rb +6 -6
  149. data/spec/controllers/entities/campaigns_controller_spec.rb +1 -1
  150. data/spec/controllers/entities/contacts_controller_spec.rb +2 -1
  151. data/spec/controllers/entities/leads_controller_spec.rb +2 -2
  152. data/spec/controllers/entities/opportunities_controller_spec.rb +1 -1
  153. data/spec/controllers/entities_controller_spec.rb +5 -0
  154. data/spec/controllers/home_controller_spec.rb +5 -5
  155. data/spec/controllers/tasks_controller_spec.rb +6 -4
  156. data/spec/controllers/users_controller_spec.rb +28 -98
  157. data/spec/factories/account_factories.rb +5 -5
  158. data/spec/factories/campaign_factories.rb +3 -3
  159. data/spec/factories/contact_factories.rb +8 -8
  160. data/spec/factories/field_factories.rb +4 -3
  161. data/spec/factories/lead_factories.rb +5 -5
  162. data/spec/factories/list_factories.rb +2 -2
  163. data/spec/factories/opportunity_factories.rb +3 -3
  164. data/spec/factories/setting_factories.rb +2 -2
  165. data/spec/factories/shared_factories.rb +11 -9
  166. data/spec/factories/task_factories.rb +7 -7
  167. data/spec/factories/user_factories.rb +16 -19
  168. data/spec/features/admin/groups_spec.rb +1 -1
  169. data/spec/features/admin/users_spec.rb +3 -1
  170. data/spec/features/campaigns_spec.rb +1 -1
  171. data/spec/features/contacts_spec.rb +1 -1
  172. data/spec/features/dashboard_spec.rb +1 -1
  173. data/spec/features/devise/sign_in_spec.rb +58 -0
  174. data/spec/features/devise/sign_up_spec.rb +36 -0
  175. data/spec/features/leads_spec.rb +1 -1
  176. data/spec/features/opportunities_overview_spec.rb +1 -1
  177. data/spec/features/opportunities_spec.rb +3 -3
  178. data/spec/features/support/browser.rb +2 -1
  179. data/spec/features/tasks_spec.rb +1 -1
  180. data/spec/helpers/admin/field_groups_helper_spec.rb +1 -1
  181. data/spec/helpers/users_helper_spec.rb +4 -4
  182. data/spec/lib/comment_extensions_spec.rb +10 -4
  183. data/spec/lib/errors_spec.rb +2 -2
  184. data/spec/lib/mail_processor/dropbox_spec.rb +1 -1
  185. data/spec/lib/mail_processor/sample_emails/dropbox.rb +8 -8
  186. data/spec/lib/permissions_spec.rb +8 -3
  187. data/spec/mailers/devise_mailer_spec.rb +35 -0
  188. data/spec/mailers/user_mailer_spec.rb +0 -26
  189. data/spec/models/entities/account_spec.rb +27 -0
  190. data/spec/models/entities/contact_spec.rb +96 -1
  191. data/spec/models/fields/custom_field_date_pair_spec.rb +4 -2
  192. data/spec/models/fields/custom_field_spec.rb +4 -2
  193. data/spec/models/observers/entity_observer_spec.rb +1 -1
  194. data/spec/models/polymorphic/version_spec.rb +7 -7
  195. data/spec/models/users/user_spec.rb +22 -26
  196. data/spec/routing/users_routing_spec.rb +30 -8
  197. data/spec/shared/controllers.rb +3 -9
  198. data/spec/spec_helper.rb +10 -2
  199. data/spec/support/assert_select.rb +1 -0
  200. data/spec/support/devise_helpers.rb +28 -0
  201. data/spec/{features/support/helpers.rb → support/feature_helpers.rb} +10 -10
  202. data/spec/support/macros.rb +4 -1
  203. data/spec/views/contacts/update.js.haml_spec.rb +1 -1
  204. data/spec/views/opportunities/update.js.haml_spec.rb +1 -1
  205. data/vendor/gems/ransack_ui-1.3.4/.gitignore +17 -0
  206. data/vendor/gems/ransack_ui-1.3.4/Gemfile +7 -0
  207. data/vendor/gems/ransack_ui-1.3.4/LICENSE.txt +22 -0
  208. data/vendor/gems/ransack_ui-1.3.4/README.md +57 -0
  209. data/vendor/gems/ransack_ui-1.3.4/Rakefile +1 -0
  210. data/vendor/gems/ransack_ui-1.3.4/app/assets/images/ransack_ui/calendar.png +0 -0
  211. data/vendor/gems/ransack_ui-1.3.4/app/assets/images/ransack_ui/delete.png +0 -0
  212. data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack/predicates.js.coffee +41 -0
  213. data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack_ui_bootstrap/button_group_select.js.coffee +26 -0
  214. data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack_ui_bootstrap/index.js.coffee +2 -0
  215. data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack_ui_jquery/index.js +2 -0
  216. data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack_ui_jquery/search_form.js.coffee.erb +499 -0
  217. data/vendor/gems/ransack_ui-1.3.4/app/assets/stylesheets/ransack_ui_bootstrap/index.css +3 -0
  218. data/vendor/gems/ransack_ui-1.3.4/app/assets/stylesheets/ransack_ui_bootstrap/search.css.scss +41 -0
  219. data/vendor/gems/ransack_ui-1.3.4/app/views/ransack_ui/_condition_fields.html.erb +15 -0
  220. data/vendor/gems/ransack_ui-1.3.4/app/views/ransack_ui/_grouping_fields.html.erb +16 -0
  221. data/vendor/gems/ransack_ui-1.3.4/app/views/ransack_ui/_search.html.erb +29 -0
  222. data/vendor/gems/ransack_ui-1.3.4/app/views/ransack_ui/_sort_fields.html.erb +4 -0
  223. data/vendor/gems/ransack_ui-1.3.4/config/locales/en.yml +24 -0
  224. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui.rb +9 -0
  225. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/adapters/active_record.rb +6 -0
  226. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/adapters/active_record/base.rb +46 -0
  227. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/controller_helpers.rb +18 -0
  228. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/rails/engine.rb +21 -0
  229. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/adapters/active_record/base.rb +47 -0
  230. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/configuration.rb +15 -0
  231. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/context.rb +9 -0
  232. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/helpers/form_builder.rb +262 -0
  233. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/nodes/attribute.rb +13 -0
  234. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/nodes/condition.rb +13 -0
  235. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/nodes/grouping.rb +20 -0
  236. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/version.rb +3 -0
  237. data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/view_helpers.rb +30 -0
  238. data/vendor/gems/ransack_ui-1.3.4/ransack_ui.gemspec +23 -0
  239. metadata +79 -67
  240. data/app/controllers/authentications_controller.rb +0 -53
  241. data/app/models/users/authentication.rb +0 -56
  242. data/app/views/authentications/new.html.haml +0 -19
  243. data/app/views/passwords/edit.html.haml +0 -15
  244. data/app/views/passwords/new.html.haml +0 -10
  245. data/app/views/user_mailer/password_reset_instructions.html.haml +0 -6
  246. data/app/views/users/new.html.haml +0 -19
  247. data/spec/controllers/authentications_controller_spec.rb +0 -150
  248. data/spec/controllers/passwords_controller_spec.rb +0 -32
  249. data/spec/models/users/authentication_spec.rb +0 -19
  250. data/spec/support/auth_macros.rb +0 -49
  251. data/spec/views/authentications/new.haml_spec.rb +0 -31
@@ -14,7 +14,7 @@ module CampaignsHelper
14
14
 
15
15
  #----------------------------------------------------------------------------
16
16
  def performance(actual, target)
17
- if target.to_i > 0 && actual.to_i > 0
17
+ if target.to_i.positive? && actual.to_i.positive?
18
18
  if target > actual
19
19
  n = 100 - actual * 100 / target
20
20
  html = content_tag(:span, "(-#{number_to_percentage(n, precision: 1)})", class: "warn")
@@ -12,9 +12,7 @@ module ContactsHelper
12
12
  summary = ['']
13
13
  summary << contact.title.titleize if contact.title?
14
14
  summary << contact.department if contact.department?
15
- if contact.account&.name?
16
- summary.last += " #{t(:at)} #{contact.account.name}"
17
- end
15
+ summary.last += " #{t(:at)} #{contact.account.name}" if contact.account&.name?
18
16
  summary << contact.email if contact.email.present?
19
17
  summary << "#{t(:phone_small)}: #{contact.phone}" if contact.phone.present?
20
18
  summary << "#{t(:mobile_small)}: #{contact.mobile}" if contact.mobile.present?
@@ -51,24 +51,16 @@ module OpportunitiesHelper
51
51
  msg = []
52
52
  won_or_lost = %w[won lost].include?(opportunity.stage)
53
53
 
54
- if opportunity.weighted_amount != 0
55
- msg << content_tag(:b, number_to_currency(opportunity.weighted_amount, precision: 0))
56
- end
54
+ msg << content_tag(:b, number_to_currency(opportunity.weighted_amount, precision: 0)) if opportunity.weighted_amount != 0
57
55
 
58
56
  unless won_or_lost
59
57
  if detailed
60
- if opportunity.amount.to_f != 0
61
- msg << number_to_currency(opportunity.amount.to_f, precision: 0)
62
- end
58
+ msg << number_to_currency(opportunity.amount.to_f, precision: 0) if opportunity.amount.to_f != 0
63
59
 
64
- if opportunity.discount.to_f != 0
65
- msg << t(:discount) + ' ' + number_to_currency(opportunity.discount, precision: 0)
66
- end
60
+ msg << t(:discount) + ' ' + number_to_currency(opportunity.discount, precision: 0) if opportunity.discount.to_f != 0
67
61
  end
68
62
 
69
- if opportunity.probability.to_i != 0
70
- msg << t(:probability) + ' ' + opportunity.probability.to_s + '%'
71
- end
63
+ msg << t(:probability) + ' ' + opportunity.probability.to_s + '%' if opportunity.probability.to_i != 0
72
64
  end
73
65
 
74
66
  msg << opportunity_closes_on_message(opportunity, won_or_lost)
@@ -11,7 +11,7 @@ module TasksHelper
11
11
  #----------------------------------------------------------------------------
12
12
  def task_filter_checkbox(view, filter, count)
13
13
  name = "filter_by_task_#{view}"
14
- checked = (session[name] ? session[name].split(",").include?(filter.to_s) : count > 0)
14
+ checked = (session[name] ? session[name].split(",").include?(filter.to_s) : count.positive?)
15
15
  url = url_for(action: :filter, view: view)
16
16
  onclick = %{
17
17
  $('#loading').show();
@@ -7,9 +7,7 @@
7
7
  #------------------------------------------------------------------------------
8
8
  module UsersHelper
9
9
  def language_for(user)
10
- if user.preference[:locale]
11
- _locale, language = languages.detect { |locale, _language| locale == user.preference[:locale] }
12
- end
10
+ _locale, language = languages.detect { |locale, _language| locale == user.preference[:locale] } if user.preference[:locale]
13
11
  language || "English"
14
12
  end
15
13
 
@@ -5,4 +5,8 @@
5
5
  # Fat Free CRM is freely distributable under the terms of MIT license.
6
6
  # See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
7
7
  #------------------------------------------------------------------------------
8
- PaperTrail.config.track_associations = false
8
+ class DeviseMailer < Devise::Mailer
9
+ def template_paths
10
+ ["devise/mailer"]
11
+ end
12
+ end
@@ -6,15 +6,6 @@
6
6
  # See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
7
7
  #------------------------------------------------------------------------------
8
8
  class UserMailer < ActionMailer::Base
9
- def password_reset_instructions(user)
10
- @edit_password_url = edit_password_url(user.perishable_token)
11
-
12
- mail subject: "Fat Free CRM: " + I18n.t(:password_reset_instruction),
13
- to: user.email,
14
- from: from_address,
15
- date: Time.now
16
- end
17
-
18
9
  def assigned_entity_notification(entity, assigner)
19
10
  @entity_url = url_for(entity)
20
11
  @entity_name = entity.name
@@ -28,8 +28,8 @@
28
28
  #
29
29
 
30
30
  class Account < ActiveRecord::Base
31
- belongs_to :user
32
- belongs_to :assignee, class_name: "User", foreign_key: :assigned_to
31
+ belongs_to :user, optional: true # TODO: Is this really optional?
32
+ belongs_to :assignee, class_name: "User", foreign_key: :assigned_to, optional: true
33
33
  has_many :account_contacts, dependent: :destroy
34
34
  has_many :contacts, -> { distinct }, through: :account_contacts
35
35
  has_many :account_opportunities, dependent: :destroy
@@ -46,7 +46,7 @@ class Account < ActiveRecord::Base
46
46
  accepts_nested_attributes_for :billing_address, allow_destroy: true, reject_if: proc { |attributes| Address.reject_address(attributes) }
47
47
  accepts_nested_attributes_for :shipping_address, allow_destroy: true, reject_if: proc { |attributes| Address.reject_address(attributes) }
48
48
 
49
- scope :state, ->(filters) {
49
+ scope :state, lambda { |filters|
50
50
  where('category IN (?)' + (filters.delete('other') ? ' OR category IS NULL' : ''), filters)
51
51
  }
52
52
  scope :created_by, ->(user) { where(user_id: user.id) }
@@ -54,7 +54,7 @@ class Account < ActiveRecord::Base
54
54
 
55
55
  scope :text_search, ->(query) { ransack('name_or_email_cont' => query).result }
56
56
 
57
- scope :visible_on_dashboard, ->(user) {
57
+ scope :visible_on_dashboard, lambda { |user|
58
58
  # Show accounts which either belong to the user and are unassigned, or are assigned to the user
59
59
  where('(user_id = :user_id AND assigned_to IS NULL) OR assigned_to = :user_id', user_id: user.id)
60
60
  }
@@ -91,6 +91,7 @@ class Account < ActiveRecord::Base
91
91
  #----------------------------------------------------------------------------
92
92
  def location
93
93
  return "" unless self[:billing_address]
94
+
94
95
  location = self[:billing_address].strip.split("\n").last
95
96
  location&.gsub(/(^|\s+)\d+(:?\s+|$)/, " ")&.strip
96
97
  end
@@ -98,9 +99,7 @@ class Account < ActiveRecord::Base
98
99
  # Attach given attachment to the account if it hasn't been attached already.
99
100
  #----------------------------------------------------------------------------
100
101
  def attach!(attachment)
101
- unless send("#{attachment.class.name.downcase}_ids").include?(attachment.id)
102
- send(attachment.class.name.tableize) << attachment
103
- end
102
+ send(attachment.class.name.tableize) << attachment unless send("#{attachment.class.name.downcase}_ids").include?(attachment.id)
104
103
  end
105
104
 
106
105
  # Discard given attachment from the account.
@@ -117,14 +116,15 @@ class Account < ActiveRecord::Base
117
116
  #----------------------------------------------------------------------------
118
117
  def self.create_or_select_for(model, params)
119
118
  # Attempt to find existing account
120
- if params[:id].present?
121
- return Account.find(params[:id])
122
- elsif params[:name].present?
119
+ return Account.find(params[:id]) if params[:id].present?
120
+
121
+ if params[:name].present?
123
122
  account = Account.find_by(name: params[:name])
124
123
  return account if account
125
124
  end
126
125
 
127
126
  # Fallback to create new account
127
+ params[:user] = model.user if model
128
128
  account = Account.new(params)
129
129
  if account.access != "Lead" || model.nil?
130
130
  account.save
@@ -32,8 +32,8 @@
32
32
  #
33
33
 
34
34
  class Campaign < ActiveRecord::Base
35
- belongs_to :user
36
- belongs_to :assignee, class_name: "User", foreign_key: :assigned_to
35
+ belongs_to :user, optional: true # TODO: Is this really optional?
36
+ belongs_to :assignee, class_name: "User", foreign_key: :assigned_to, optional: true # TODO: Is this really optional?
37
37
  has_many :tasks, as: :asset, dependent: :destroy # , :order => 'created_at DESC'
38
38
  has_many :leads, -> { order "id DESC" }, dependent: :destroy
39
39
  has_many :opportunities, -> { order "id DESC" }, dependent: :destroy
@@ -41,7 +41,7 @@ class Campaign < ActiveRecord::Base
41
41
 
42
42
  serialize :subscribed_users, Set
43
43
 
44
- scope :state, ->(filters) {
44
+ scope :state, lambda { |filters|
45
45
  where('status IN (?)' + (filters.delete('other') ? ' OR status IS NULL' : ''), filters)
46
46
  }
47
47
  scope :created_by, ->(user) { where(user_id: user.id) }
@@ -103,9 +103,7 @@ class Campaign < ActiveRecord::Base
103
103
  # Make sure end date > start date.
104
104
  #----------------------------------------------------------------------------
105
105
  def start_and_end_dates
106
- if (starts_on && ends_on) && (starts_on > ends_on)
107
- errors.add(:ends_on, :dates_not_in_sequence)
108
- end
106
+ errors.add(:ends_on, :dates_not_in_sequence) if (starts_on && ends_on) && (starts_on > ends_on)
109
107
  end
110
108
 
111
109
  # Make sure at least one user has been selected if the campaign is being shared.
@@ -40,9 +40,9 @@
40
40
 
41
41
  class Contact < ActiveRecord::Base
42
42
  belongs_to :user
43
- belongs_to :lead
44
- belongs_to :assignee, class_name: "User", foreign_key: :assigned_to
45
- belongs_to :reporting_user, class_name: "User", foreign_key: :reports_to
43
+ belongs_to :lead, optional: true # TODO: Is this really optional?
44
+ belongs_to :assignee, class_name: "User", foreign_key: :assigned_to, optional: true # TODO: Is this really optional?
45
+ belongs_to :reporting_user, class_name: "User", foreign_key: :reports_to, optional: true # TODO: Is this really optional?
46
46
  has_one :account_contact, dependent: :destroy
47
47
  has_one :account, through: :account_contact
48
48
  has_many :contact_opportunities, dependent: :destroy
@@ -64,7 +64,7 @@ class Contact < ActiveRecord::Base
64
64
  scope :created_by, ->(user) { where(user_id: user.id) }
65
65
  scope :assigned_to, ->(user) { where(assigned_to: user.id) }
66
66
 
67
- scope :text_search, ->(query) {
67
+ scope :text_search, lambda { |query|
68
68
  t = Contact.arel_table
69
69
  # We can't always be sure that names are entered in the right order, so we must
70
70
  # split the query into all possible first/last name permutations.
@@ -98,6 +98,21 @@ class Contact < ActiveRecord::Base
98
98
  validates_presence_of :last_name, message: :missing_last_name, if: -> { Setting.require_last_names }
99
99
  validate :users_for_shared_access
100
100
 
101
+ validates_length_of :first_name, maximum: 64
102
+ validates_length_of :last_name, maximum: 64
103
+ validates_length_of :title, maximum: 64
104
+ validates_length_of :department, maximum: 64
105
+ validates_length_of :email, maximum: 254
106
+ validates_length_of :alt_email, maximum: 254
107
+ validates_length_of :phone, maximum: 32
108
+ validates_length_of :mobile, maximum: 32
109
+ validates_length_of :fax, maximum: 32
110
+ validates_length_of :blog, maximum: 128
111
+ validates_length_of :linkedin, maximum: 128
112
+ validates_length_of :facebook, maximum: 128
113
+ validates_length_of :twitter, maximum: 128
114
+ validates_length_of :skype, maximum: 128
115
+
101
116
  # Default values provided through class methods.
102
117
  #----------------------------------------------------------------------------
103
118
  def self.per_page
@@ -140,9 +155,7 @@ class Contact < ActiveRecord::Base
140
155
  # Attach given attachment to the contact if it hasn't been attached already.
141
156
  #----------------------------------------------------------------------------
142
157
  def attach!(attachment)
143
- unless send("#{attachment.class.name.downcase}_ids").include?(attachment.id)
144
- send(attachment.class.name.tableize) << attachment
145
- end
158
+ send(attachment.class.name.tableize) << attachment unless send("#{attachment.class.name.downcase}_ids").include?(attachment.id)
146
159
  end
147
160
 
148
161
  # Discard given attachment from the contact.
@@ -160,7 +173,7 @@ class Contact < ActiveRecord::Base
160
173
  def self.create_for(model, account, opportunity, params)
161
174
  attributes = {
162
175
  lead_id: model.id,
163
- user_id: params[:account][:user_id],
176
+ user_id: params[:account][:user_id] || account.user_id,
164
177
  assigned_to: params[:account][:assigned_to],
165
178
  access: params[:access]
166
179
  }
@@ -173,9 +186,7 @@ class Contact < ActiveRecord::Base
173
186
  # Set custom fields.
174
187
  if model.class.respond_to?(:fields)
175
188
  model.class.fields.each do |field|
176
- if contact.respond_to?(field.name)
177
- contact.send "#{field.name}=", model.send(field.name)
178
- end
189
+ contact.send "#{field.name}=", model.send(field.name) if contact.respond_to?(field.name)
179
190
  end
180
191
  end
181
192
 
@@ -207,7 +218,8 @@ class Contact < ActiveRecord::Base
207
218
  #----------------------------------------------------------------------------
208
219
  def save_account(params)
209
220
  account_params = params[:account]
210
- self.account = if account_params && account_params[:id] != "" && account_params[:name] != ""
221
+ valid_account = account_params && (account_params[:id].present? || account_params[:name].present?)
222
+ self.account = if valid_account
211
223
  Account.create_or_select_for(self, account_params)
212
224
  else
213
225
  nil
@@ -39,9 +39,9 @@
39
39
  #
40
40
 
41
41
  class Lead < ActiveRecord::Base
42
- belongs_to :user
43
- belongs_to :campaign
44
- belongs_to :assignee, class_name: "User", foreign_key: :assigned_to
42
+ belongs_to :user, optional: true # TODO: Is this really optional?
43
+ belongs_to :campaign, optional: true # TODO: Is this really optional?
44
+ belongs_to :assignee, class_name: "User", foreign_key: :assigned_to, optional: true # TODO: Is this really optional?
45
45
  has_one :contact, dependent: :nullify # On destroy keep the contact, but nullify its lead_id
46
46
  has_many :tasks, as: :asset, dependent: :destroy # , :order => 'created_at DESC'
47
47
  has_one :business_address, -> { where "address_type='Business'" }, dependent: :destroy, as: :addressable, class_name: "Address"
@@ -50,9 +50,9 @@ class Lead < ActiveRecord::Base
50
50
 
51
51
  serialize :subscribed_users, Set
52
52
 
53
- accepts_nested_attributes_for :business_address, allow_destroy: true
53
+ accepts_nested_attributes_for :business_address, allow_destroy: true, reject_if: proc { |attributes| Address.reject_address(attributes) }
54
54
 
55
- scope :state, ->(filters) {
55
+ scope :state, lambda { |filters|
56
56
  where(['status IN (?)' + (filters.delete('other') ? ' OR status IS NULL' : ''), filters])
57
57
  }
58
58
  scope :converted, -> { where(status: 'converted') }
@@ -123,8 +123,8 @@ class Lead < ActiveRecord::Base
123
123
  # successful promotion Lead status gets set to :converted.
124
124
  #----------------------------------------------------------------------------
125
125
  def promote(params)
126
- account_params = params[:account] ? params[:account] : {}
127
- opportunity_params = params[:opportunity] ? params[:opportunity] : {}
126
+ account_params = params[:account] || {}
127
+ opportunity_params = params[:opportunity] || {}
128
128
 
129
129
  account = Account.create_or_select_for(self, account_params)
130
130
  opportunity = Opportunity.create_for(self, account, opportunity_params)
@@ -28,9 +28,9 @@
28
28
  #
29
29
 
30
30
  class Opportunity < ActiveRecord::Base
31
- belongs_to :user
32
- belongs_to :campaign
33
- belongs_to :assignee, class_name: "User", foreign_key: :assigned_to
31
+ belongs_to :user, optional: true
32
+ belongs_to :campaign, optional: true
33
+ belongs_to :assignee, class_name: "User", foreign_key: :assigned_to, optional: true
34
34
  has_one :account_opportunity, dependent: :destroy
35
35
  has_one :account, through: :account_opportunity
36
36
  has_many :contact_opportunities, dependent: :destroy
@@ -40,7 +40,7 @@ class Opportunity < ActiveRecord::Base
40
40
 
41
41
  serialize :subscribed_users, Set
42
42
 
43
- scope :state, ->(filters) {
43
+ scope :state, lambda { |filters|
44
44
  where('stage IN (?)' + (filters.delete('other') ? ' OR stage IS NULL' : ''), filters)
45
45
  }
46
46
  scope :created_by, ->(user) { where('user_id = ?', user.id) }
@@ -53,7 +53,7 @@ class Opportunity < ActiveRecord::Base
53
53
  scope :weighted_sort, -> { select('*, amount*probability') }
54
54
 
55
55
  # Search by name OR id
56
- scope :text_search, ->(query) {
56
+ scope :text_search, lambda { |query|
57
57
  if query.match?(/\A\d+\z/)
58
58
  where('upper(name) LIKE upper(:name) OR opportunities.id = :id', name: "%#{query}%", id: query)
59
59
  else
@@ -61,7 +61,7 @@ class Opportunity < ActiveRecord::Base
61
61
  end
62
62
  }
63
63
 
64
- scope :visible_on_dashboard, ->(user) {
64
+ scope :visible_on_dashboard, lambda { |user|
65
65
  # Show opportunities which either belong to the user and are unassigned, or are assigned to the user and haven't been closed (won/lost)
66
66
  where('(user_id = :user_id AND assigned_to IS NULL) OR assigned_to = :user_id', user_id: user.id).where("opportunities.stage != 'won'").where("opportunities.stage != 'lost'")
67
67
  }
@@ -135,9 +135,7 @@ class Opportunity < ActiveRecord::Base
135
135
  # Attach given attachment to the opportunity if it hasn't been attached already.
136
136
  #----------------------------------------------------------------------------
137
137
  def attach!(attachment)
138
- unless send("#{attachment.class.name.downcase}_ids").include?(attachment.id)
139
- send(attachment.class.name.tableize) << attachment
140
- end
138
+ send(attachment.class.name.tableize) << attachment unless send("#{attachment.class.name.downcase}_ids").include?(attachment.id)
141
139
  end
142
140
 
143
141
  # Discard given attachment from the opportunity.
@@ -96,6 +96,7 @@ class CustomField < Field
96
96
  return :safe if SAFE_DB_TRANSITIONS[:any].any? do |col_set|
97
97
  [old_col, new_col].all? { |c| col_set.include?(c.to_s) } # any-to-any
98
98
  end
99
+
99
100
  :unsafe # Else, unsafe.
100
101
  end
101
102
 
@@ -11,6 +11,7 @@ class CustomFieldDatePair < CustomFieldPair
11
11
  #------------------------------------------------------------------------------
12
12
  def render_value(object)
13
13
  return "" unless paired_with.present?
14
+
14
15
  from = render(object.send(name))
15
16
  to = render(object.send(paired_with.name))
16
17
  if from.present? && to.present?
@@ -34,6 +35,7 @@ class CustomFieldDatePair < CustomFieldPair
34
35
  if pair_id.present?
35
36
  start = CustomFieldPair.find(pair_id)
36
37
  return if start.nil?
38
+
37
39
  from = obj.send(start.name)
38
40
  to = obj.send(name)
39
41
  obj.errors.add(name.to_sym, ::I18n.t('activerecord.errors.models.custom_field.endbeforestart', field: start.label)) if from.present? && to.present? && (from > to)
@@ -34,7 +34,7 @@ class Field < ActiveRecord::Base
34
34
  serialize :collection, Array
35
35
  serialize :settings, HashWithIndifferentAccess
36
36
 
37
- belongs_to :field_group
37
+ belongs_to :field_group, optional: true # TODO: Is this really optional?
38
38
 
39
39
  scope :core_fields, -> { where(type: 'CoreField') }
40
40
  scope :custom_fields, -> { where("type != 'CoreField'") }
@@ -105,8 +105,6 @@ class Field < ActiveRecord::Base
105
105
  end
106
106
  end
107
107
 
108
- protected
109
-
110
108
  class << self
111
109
  # Provides access to registered field_types
112
110
  #------------------------------------------------------------------------------
data/app/models/list.rb CHANGED
@@ -12,7 +12,7 @@ class List < ActiveRecord::Base
12
12
 
13
13
  # Parses the controller from the url
14
14
  def controller
15
- (url || "").sub(/\A\//, '').split(/\/|\?/).first
15
+ (url || "").sub(%r{\A/}, '').split(%r{/|\?}).first
16
16
  end
17
17
 
18
18
  ActiveSupport.run_load_hooks(:fat_free_crm_list, self)
@@ -13,17 +13,13 @@ class EntityObserver < ActiveRecord::Observer
13
13
  end
14
14
 
15
15
  def after_update(item)
16
- if item.saved_change_to_assigned_to? && item.assignee != current_user
17
- send_notification_to_assignee(item)
18
- end
16
+ send_notification_to_assignee(item) if item.saved_change_to_assigned_to? && item.assignee != current_user
19
17
  end
20
18
 
21
19
  private
22
20
 
23
21
  def send_notification_to_assignee(item)
24
- if item.assignee.present? && current_user.present? && can_send_email?
25
- UserMailer.assigned_entity_notification(item, current_user).deliver_now
26
- end
22
+ UserMailer.assigned_entity_notification(item, current_user).deliver_now if item.assignee.present? && current_user.present? && can_send_email?
27
23
  end
28
24
 
29
25
  # Need to have a host set before email can be sent
@@ -33,7 +29,7 @@ class EntityObserver < ActiveRecord::Observer
33
29
 
34
30
  def current_user
35
31
  # this deals with whodunnit inconsistencies, where in some cases it's set to a user's id and others the user object itself
36
- user_id_or_user = PaperTrail.whodunnit
32
+ user_id_or_user = PaperTrail.request.whodunnit
37
33
  if user_id_or_user.is_a?(User)
38
34
  user_id_or_user
39
35
  elsif user_id_or_user.is_a?(String)