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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +61 -160
- data/.travis.yml +27 -11
- data/CHANGELOG.md +40 -24
- data/CONTRIBUTORS.md +1 -0
- data/Dockerfile +45 -14
- data/Gemfile +16 -10
- data/Gemfile.lock +230 -222
- data/Procfile +1 -1
- data/README.md +2 -2
- data/Rakefile +1 -1
- data/app/assets/stylesheets/common.scss +1 -1
- data/app/controllers/admin/application_controller.rb +1 -1
- data/app/controllers/admin/field_groups_controller.rb +1 -3
- data/app/controllers/admin/tags_controller.rb +1 -3
- data/app/controllers/admin/users_controller.rb +5 -8
- data/app/controllers/application_controller.rb +11 -45
- data/app/controllers/comments_controller.rb +2 -5
- data/{config/initializers/authlogic.rb → app/controllers/confirmations_controller.rb} +4 -2
- data/app/controllers/emails_controller.rb +0 -2
- data/app/controllers/entities/accounts_controller.rb +1 -3
- data/app/controllers/entities/campaigns_controller.rb +1 -3
- data/app/controllers/entities/contacts_controller.rb +4 -24
- data/app/controllers/entities/leads_controller.rb +7 -10
- data/app/controllers/entities/opportunities_controller.rb +4 -14
- data/app/controllers/entities_controller.rb +21 -7
- data/app/controllers/home_controller.rb +2 -4
- data/app/controllers/passwords_controller.rb +3 -59
- data/{spec/features/support/maintain_sessions.rb → app/controllers/registrations_controller.rb} +12 -5
- data/{lib/development_tasks/gem.rake → app/controllers/sessions_controller.rb} +6 -6
- data/app/controllers/tasks_controller.rb +8 -17
- data/app/controllers/users_controller.rb +8 -29
- data/app/helpers/admin/users_helper.rb +1 -1
- data/app/helpers/application_helper.rb +27 -32
- data/app/helpers/campaigns_helper.rb +1 -1
- data/app/helpers/contacts_helper.rb +1 -3
- data/app/helpers/opportunities_helper.rb +4 -12
- data/app/helpers/tasks_helper.rb +1 -1
- data/app/helpers/users_helper.rb +1 -3
- data/{config/initializers/paper_trail.rb → app/mailers/devise_mailer.rb} +5 -1
- data/app/mailers/user_mailer.rb +0 -9
- data/app/models/entities/account.rb +10 -10
- data/app/models/entities/campaign.rb +4 -6
- data/app/models/entities/contact.rb +24 -12
- data/app/models/entities/lead.rb +7 -7
- data/app/models/entities/opportunity.rb +7 -9
- data/app/models/fields/custom_field.rb +1 -0
- data/app/models/fields/custom_field_date_pair.rb +2 -0
- data/app/models/fields/field.rb +1 -3
- data/app/models/list.rb +1 -1
- data/app/models/observers/entity_observer.rb +3 -7
- data/app/models/observers/lead_observer.rb +2 -4
- data/app/models/observers/opportunity_observer.rb +2 -4
- data/app/models/observers/task_observer.rb +1 -1
- data/app/models/polymorphic/email.rb +2 -2
- data/app/models/polymorphic/task.rb +13 -9
- data/app/models/polymorphic/version.rb +3 -2
- data/app/models/setting.rb +2 -0
- data/app/models/users/permission.rb +3 -3
- data/app/models/users/preference.rb +2 -1
- data/app/models/users/user.rb +67 -42
- data/app/views/accounts/_top_section.html.haml +1 -1
- data/app/views/accounts/edit.js.haml +1 -1
- data/app/views/accounts/update.js.haml +2 -2
- data/app/views/admin/users/_user.html.haml +4 -4
- data/app/views/contacts/_index_brief.html.haml +1 -1
- data/app/views/contacts/_index_full.html.haml +1 -1
- data/app/views/contacts/_index_long.html.haml +1 -1
- data/app/views/devise/confirmations/new.html.haml +9 -0
- data/app/views/devise/mailer/confirmation_instructions.html.haml +4 -0
- data/app/views/devise/mailer/password_change.html.haml +3 -0
- data/app/views/devise/mailer/reset_password_instructions.html.haml +6 -0
- data/app/views/devise/passwords/edit.html.haml +18 -0
- data/app/views/devise/passwords/new.html.haml +10 -0
- data/app/views/devise/registrations/new.html.haml +21 -0
- data/app/views/devise/sessions/new.html.haml +32 -0
- data/app/views/layouts/_about.html.haml +5 -5
- data/app/views/layouts/_header.html.haml +3 -3
- data/app/views/layouts/admin/_header.html.haml +1 -1
- data/app/views/shared/_address.html.haml +5 -5
- data/app/views/shared/_paginate_with_per_page.html.haml +1 -0
- data/app/views/users/_avatar.html.haml +1 -1
- data/bin/bundle +1 -1
- data/bin/rails +1 -1
- data/bin/setup +38 -0
- data/bin/update +33 -0
- data/bin/yarn +13 -0
- data/config/application.rb +8 -6
- data/config/boot.rb +1 -1
- data/config/brakeman.ignore +2 -2
- data/config/database.postgres.docker.yml +5 -5
- data/config/environment.rb +1 -1
- data/config/environments/development.rb +1 -0
- data/config/environments/test.rb +7 -0
- data/config/initializers/action_mailer.rb +1 -3
- data/config/initializers/application_controller_renderer.rb +9 -0
- data/config/initializers/assets.rb +6 -11
- data/config/initializers/backtrace_silencers.rb +0 -6
- data/config/initializers/content_security_policy.rb +26 -0
- data/config/initializers/cookies_serializer.rb +3 -6
- data/config/initializers/devise.rb +289 -0
- data/config/initializers/filter_parameter_logging.rb +0 -5
- data/config/initializers/gravatar.rb +0 -1
- data/config/initializers/inflections.rb +0 -6
- data/config/initializers/mime_types.rb +1 -9
- data/config/initializers/new_framework_defaults_5_2.rb +40 -0
- data/config/initializers/relative_url_root.rb +1 -3
- data/config/initializers/session_store.rb +1 -3
- data/config/initializers/wrap_parameters.rb +4 -9
- data/config/locales/fat_free_crm.en-GB.yml +5 -5
- data/config/locales/fat_free_crm.en-US.yml +5 -5
- data/config/locales/fat_free_crm.fr.yml +1 -1
- data/config/locales/fat_free_crm.ru.yml +1 -0
- data/config/routes.rb +20 -9
- data/db/demo/users.yml +62 -81
- data/db/migrate/20100928030620_remove_uuid.rb +1 -2
- data/db/migrate/20120316045804_activities_to_versions.rb +1 -0
- data/db/migrate/20120510025219_add_not_null_constraints_for_timestamp_columns.rb +1 -0
- data/db/migrate/20180107082701_authlogic_to_devise.rb +58 -0
- data/db/schema.rb +48 -43
- data/docker-compose.yml +10 -0
- data/fat_free_crm.gemspec +11 -13
- data/lib/development_tasks/license.rake +2 -2
- data/lib/fat_free_crm/callback.rb +2 -2
- data/lib/fat_free_crm/comment_extensions.rb +2 -4
- data/lib/fat_free_crm/core_ext/string.rb +1 -1
- data/lib/fat_free_crm/engine.rb +1 -1
- data/lib/fat_free_crm/errors.rb +1 -1
- data/lib/fat_free_crm/export_csv.rb +1 -0
- data/lib/fat_free_crm/exportable.rb +1 -1
- data/lib/fat_free_crm/fields.rb +1 -1
- data/lib/fat_free_crm/gem_dependencies.rb +1 -1
- data/lib/fat_free_crm/gem_ext/simple_form/action_view_extensions/form_helper.rb +1 -3
- data/lib/fat_free_crm/i18n.rb +2 -2
- data/lib/fat_free_crm/mail_processor/base.rb +4 -10
- data/lib/fat_free_crm/mail_processor/dropbox.rb +5 -15
- data/lib/fat_free_crm/permissions.rb +7 -4
- data/lib/fat_free_crm/sortable.rb +1 -1
- data/lib/fat_free_crm/tabs.rb +2 -2
- data/lib/fat_free_crm/version.rb +2 -2
- data/lib/gravatar_image_tag.rb +7 -8
- data/lib/missing_translation_detector.rb +1 -0
- data/lib/tasks/ffcrm/missing_translations.rake +1 -0
- data/lib/tasks/ffcrm/setup.rake +10 -1
- data/lib/tasks/ffcrm/update_data.rake +2 -2
- data/script/rails +2 -2
- data/spec/controllers/admin/users_controller_spec.rb +0 -56
- data/spec/controllers/comments_controller_spec.rb +6 -6
- data/spec/controllers/entities/campaigns_controller_spec.rb +1 -1
- data/spec/controllers/entities/contacts_controller_spec.rb +2 -1
- data/spec/controllers/entities/leads_controller_spec.rb +2 -2
- data/spec/controllers/entities/opportunities_controller_spec.rb +1 -1
- data/spec/controllers/entities_controller_spec.rb +5 -0
- data/spec/controllers/home_controller_spec.rb +5 -5
- data/spec/controllers/tasks_controller_spec.rb +6 -4
- data/spec/controllers/users_controller_spec.rb +28 -98
- data/spec/factories/account_factories.rb +5 -5
- data/spec/factories/campaign_factories.rb +3 -3
- data/spec/factories/contact_factories.rb +8 -8
- data/spec/factories/field_factories.rb +4 -3
- data/spec/factories/lead_factories.rb +5 -5
- data/spec/factories/list_factories.rb +2 -2
- data/spec/factories/opportunity_factories.rb +3 -3
- data/spec/factories/setting_factories.rb +2 -2
- data/spec/factories/shared_factories.rb +11 -9
- data/spec/factories/task_factories.rb +7 -7
- data/spec/factories/user_factories.rb +16 -19
- data/spec/features/admin/groups_spec.rb +1 -1
- data/spec/features/admin/users_spec.rb +3 -1
- data/spec/features/campaigns_spec.rb +1 -1
- data/spec/features/contacts_spec.rb +1 -1
- data/spec/features/dashboard_spec.rb +1 -1
- data/spec/features/devise/sign_in_spec.rb +58 -0
- data/spec/features/devise/sign_up_spec.rb +36 -0
- data/spec/features/leads_spec.rb +1 -1
- data/spec/features/opportunities_overview_spec.rb +1 -1
- data/spec/features/opportunities_spec.rb +3 -3
- data/spec/features/support/browser.rb +2 -1
- data/spec/features/tasks_spec.rb +1 -1
- data/spec/helpers/admin/field_groups_helper_spec.rb +1 -1
- data/spec/helpers/users_helper_spec.rb +4 -4
- data/spec/lib/comment_extensions_spec.rb +10 -4
- data/spec/lib/errors_spec.rb +2 -2
- data/spec/lib/mail_processor/dropbox_spec.rb +1 -1
- data/spec/lib/mail_processor/sample_emails/dropbox.rb +8 -8
- data/spec/lib/permissions_spec.rb +8 -3
- data/spec/mailers/devise_mailer_spec.rb +35 -0
- data/spec/mailers/user_mailer_spec.rb +0 -26
- data/spec/models/entities/account_spec.rb +27 -0
- data/spec/models/entities/contact_spec.rb +96 -1
- data/spec/models/fields/custom_field_date_pair_spec.rb +4 -2
- data/spec/models/fields/custom_field_spec.rb +4 -2
- data/spec/models/observers/entity_observer_spec.rb +1 -1
- data/spec/models/polymorphic/version_spec.rb +7 -7
- data/spec/models/users/user_spec.rb +22 -26
- data/spec/routing/users_routing_spec.rb +30 -8
- data/spec/shared/controllers.rb +3 -9
- data/spec/spec_helper.rb +10 -2
- data/spec/support/assert_select.rb +1 -0
- data/spec/support/devise_helpers.rb +28 -0
- data/spec/{features/support/helpers.rb → support/feature_helpers.rb} +10 -10
- data/spec/support/macros.rb +4 -1
- data/spec/views/contacts/update.js.haml_spec.rb +1 -1
- data/spec/views/opportunities/update.js.haml_spec.rb +1 -1
- data/vendor/gems/ransack_ui-1.3.4/.gitignore +17 -0
- data/vendor/gems/ransack_ui-1.3.4/Gemfile +7 -0
- data/vendor/gems/ransack_ui-1.3.4/LICENSE.txt +22 -0
- data/vendor/gems/ransack_ui-1.3.4/README.md +57 -0
- data/vendor/gems/ransack_ui-1.3.4/Rakefile +1 -0
- data/vendor/gems/ransack_ui-1.3.4/app/assets/images/ransack_ui/calendar.png +0 -0
- data/vendor/gems/ransack_ui-1.3.4/app/assets/images/ransack_ui/delete.png +0 -0
- data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack/predicates.js.coffee +41 -0
- data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack_ui_bootstrap/button_group_select.js.coffee +26 -0
- data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack_ui_bootstrap/index.js.coffee +2 -0
- data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack_ui_jquery/index.js +2 -0
- data/vendor/gems/ransack_ui-1.3.4/app/assets/javascripts/ransack_ui_jquery/search_form.js.coffee.erb +499 -0
- data/vendor/gems/ransack_ui-1.3.4/app/assets/stylesheets/ransack_ui_bootstrap/index.css +3 -0
- data/vendor/gems/ransack_ui-1.3.4/app/assets/stylesheets/ransack_ui_bootstrap/search.css.scss +41 -0
- data/vendor/gems/ransack_ui-1.3.4/app/views/ransack_ui/_condition_fields.html.erb +15 -0
- data/vendor/gems/ransack_ui-1.3.4/app/views/ransack_ui/_grouping_fields.html.erb +16 -0
- data/vendor/gems/ransack_ui-1.3.4/app/views/ransack_ui/_search.html.erb +29 -0
- data/vendor/gems/ransack_ui-1.3.4/app/views/ransack_ui/_sort_fields.html.erb +4 -0
- data/vendor/gems/ransack_ui-1.3.4/config/locales/en.yml +24 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui.rb +9 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/adapters/active_record.rb +6 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/adapters/active_record/base.rb +46 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/controller_helpers.rb +18 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/rails/engine.rb +21 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/adapters/active_record/base.rb +47 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/configuration.rb +15 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/context.rb +9 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/helpers/form_builder.rb +262 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/nodes/attribute.rb +13 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/nodes/condition.rb +13 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/ransack_overrides/nodes/grouping.rb +20 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/version.rb +3 -0
- data/vendor/gems/ransack_ui-1.3.4/lib/ransack_ui/view_helpers.rb +30 -0
- data/vendor/gems/ransack_ui-1.3.4/ransack_ui.gemspec +23 -0
- metadata +79 -67
- data/app/controllers/authentications_controller.rb +0 -53
- data/app/models/users/authentication.rb +0 -56
- data/app/views/authentications/new.html.haml +0 -19
- data/app/views/passwords/edit.html.haml +0 -15
- data/app/views/passwords/new.html.haml +0 -10
- data/app/views/user_mailer/password_reset_instructions.html.haml +0 -6
- data/app/views/users/new.html.haml +0 -19
- data/spec/controllers/authentications_controller_spec.rb +0 -150
- data/spec/controllers/passwords_controller_spec.rb +0 -32
- data/spec/models/users/authentication_spec.rb +0 -19
- data/spec/support/auth_macros.rb +0 -49
- 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
|
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)
|
data/app/helpers/tasks_helper.rb
CHANGED
@@ -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
|
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();
|
data/app/helpers/users_helper.rb
CHANGED
@@ -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
|
-
|
8
|
+
class DeviseMailer < Devise::Mailer
|
9
|
+
def template_paths
|
10
|
+
["devise/mailer"]
|
11
|
+
end
|
12
|
+
end
|
data/app/mailers/user_mailer.rb
CHANGED
@@ -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,
|
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,
|
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
|
-
|
122
|
-
|
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,
|
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,
|
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
|
-
|
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
|
data/app/models/entities/lead.rb
CHANGED
@@ -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,
|
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]
|
127
|
-
opportunity_params = 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,
|
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,
|
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,
|
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.
|
@@ -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)
|
data/app/models/fields/field.rb
CHANGED
@@ -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(
|
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)
|