fat_free_crm 0.17.3 → 0.18.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/.dockerignore +11 -0
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +30 -8
- data/.travis.yml +14 -9
- data/CHANGELOG.md +43 -7
- data/CONTRIBUTORS.md +95 -53
- data/Gemfile +11 -7
- data/Gemfile.lock +83 -83
- data/README.md +7 -4
- data/app/assets/javascripts/crm.js.coffee +3 -3
- data/app/assets/javascripts/crm_select2.js.coffee +15 -14
- data/app/controllers/admin/field_groups_controller.rb +8 -1
- data/app/controllers/admin/fields_controller.rb +4 -4
- data/app/controllers/admin/groups_controller.rb +1 -1
- data/app/controllers/admin/tags_controller.rb +1 -1
- data/app/controllers/application_controller.rb +11 -0
- data/app/controllers/authentications_controller.rb +1 -1
- data/app/controllers/comments_controller.rb +15 -7
- data/app/controllers/entities/campaigns_controller.rb +7 -2
- data/app/controllers/entities/leads_controller.rb +9 -2
- data/app/controllers/entities/opportunities_controller.rb +13 -2
- data/app/controllers/entities_controller.rb +10 -5
- data/app/controllers/lists_controller.rb +5 -1
- data/app/controllers/tasks_controller.rb +15 -1
- data/app/helpers/accounts_helper.rb +1 -1
- data/app/helpers/application_helper.rb +2 -2
- data/app/helpers/leads_helper.rb +1 -1
- data/app/helpers/opportunities_helper.rb +56 -3
- data/app/helpers/tags_helper.rb +1 -1
- data/app/models/entities/lead.rb +0 -7
- data/app/models/entities/opportunity.rb +3 -2
- data/app/models/observers/opportunity_observer.rb +4 -4
- data/app/models/users/ability.rb +3 -4
- data/app/views/campaigns/_metrics.html.haml +3 -3
- data/app/views/home/_opportunity.html.haml +4 -19
- data/app/views/opportunities/_index_long.html.haml +1 -24
- data/app/views/opportunities/_sidebar_show.html.haml +3 -3
- data/app/views/opportunities/_top_section.html.haml +1 -1
- data/db/schema.rb +0 -3
- data/fat_free_crm.gemspec +1 -1
- data/lib/fat_free_crm/core_ext/string.rb +1 -1
- data/lib/fat_free_crm/engine.rb +2 -2
- data/lib/fat_free_crm/fields.rb +1 -1
- data/lib/fat_free_crm/permissions.rb +0 -14
- data/lib/fat_free_crm/version.rb +2 -2
- data/lib/tasks/ffcrm/setup.rake +4 -4
- data/spec/controllers/admin/users_controller_spec.rb +27 -27
- data/spec/controllers/authentications_controller_spec.rb +7 -7
- data/spec/controllers/comments_controller_spec.rb +13 -13
- data/spec/controllers/emails_controller_spec.rb +2 -2
- data/spec/controllers/entities/accounts_controller_spec.rb +56 -56
- data/spec/controllers/entities/campaigns_controller_spec.rb +66 -66
- data/spec/controllers/entities/contacts_controller_spec.rb +67 -67
- data/spec/controllers/entities/leads_controller_spec.rb +125 -125
- data/spec/controllers/entities/opportunities_controller_spec.rb +100 -100
- data/spec/controllers/home_controller_spec.rb +26 -26
- data/spec/controllers/passwords_controller_spec.rb +1 -1
- data/spec/controllers/tasks_controller_spec.rb +37 -37
- data/spec/controllers/users_controller_spec.rb +18 -18
- data/spec/factories/account_factories.rb +8 -8
- data/spec/factories/campaign_factories.rb +5 -5
- data/spec/factories/contact_factories.rb +10 -10
- data/spec/factories/field_factories.rb +7 -7
- data/spec/factories/lead_factories.rb +8 -8
- data/spec/factories/list_factories.rb +1 -1
- data/spec/factories/opportunity_factories.rb +6 -6
- data/spec/factories/sequences.rb +1 -1
- data/spec/factories/setting_factories.rb +3 -3
- data/spec/factories/shared_factories.rb +14 -14
- data/spec/factories/subscription_factories.rb +1 -1
- data/spec/factories/tag_factories.rb +1 -1
- data/spec/factories/task_factories.rb +4 -4
- data/spec/factories/user_factories.rb +13 -13
- data/spec/features/accounts_spec.rb +17 -4
- data/spec/features/admin/groups_spec.rb +1 -1
- data/spec/features/admin/users_spec.rb +1 -1
- data/spec/features/campaigns_spec.rb +4 -4
- data/spec/features/contacts_spec.rb +10 -4
- data/spec/features/dashboard_spec.rb +7 -7
- data/spec/features/leads_spec.rb +4 -4
- data/spec/features/opportunities_overview_spec.rb +15 -15
- data/spec/features/opportunities_spec.rb +34 -8
- data/spec/features/support/autocomlete_helper.rb +17 -0
- data/spec/features/support/browser.rb +3 -8
- data/spec/features/support/helpers.rb +1 -1
- data/spec/features/tasks_spec.rb +4 -4
- data/spec/helpers/admin/field_groups_helper_spec.rb +1 -1
- data/spec/helpers/application_helper_spec.rb +1 -1
- data/spec/helpers/tasks_helper_spec.rb +1 -1
- data/spec/helpers/users_helper_spec.rb +3 -3
- data/spec/lib/comment_extensions_spec.rb +1 -1
- data/spec/lib/mail_processor/base_spec.rb +3 -3
- data/spec/lib/mail_processor/comment_replies_spec.rb +3 -3
- data/spec/lib/mail_processor/dropbox_spec.rb +16 -16
- data/spec/lib/permissions_spec.rb +7 -25
- data/spec/mailers/user_mailer_spec.rb +7 -7
- data/spec/models/entities/account_spec.rb +31 -32
- data/spec/models/entities/campaign_spec.rb +18 -25
- data/spec/models/entities/contact_spec.rb +18 -21
- data/spec/models/entities/lead_spec.rb +9 -11
- data/spec/models/entities/opportunity_spec.rb +45 -45
- data/spec/models/fields/custom_field_spec.rb +17 -17
- data/spec/models/list_spec.rb +2 -2
- data/spec/models/observers/entity_observer_spec.rb +6 -6
- data/spec/models/polymorphic/address_spec.rb +1 -1
- data/spec/models/polymorphic/avatar_spec.rb +5 -5
- data/spec/models/polymorphic/comment_spec.rb +5 -5
- data/spec/models/polymorphic/task_spec.rb +65 -58
- data/spec/models/polymorphic/version_spec.rb +26 -26
- data/spec/models/setting_spec.rb +2 -2
- data/spec/models/users/preference_spec.rb +6 -6
- data/spec/models/users/user_spec.rb +26 -26
- data/spec/shared/models.rb +22 -22
- data/spec/spec_helper.rb +2 -2
- data/spec/support/auth_macros.rb +1 -1
- data/spec/support/macros.rb +3 -3
- data/spec/views/accounts/_edit.haml_spec.rb +1 -1
- data/spec/views/accounts/create.js.haml_spec.rb +2 -2
- data/spec/views/accounts/destroy.js.haml_spec.rb +1 -1
- data/spec/views/accounts/edit.js.haml_spec.rb +2 -2
- data/spec/views/accounts/index.haml_spec.rb +2 -2
- data/spec/views/accounts/index.js.haml_spec.rb +1 -1
- data/spec/views/accounts/show.haml_spec.rb +4 -4
- data/spec/views/accounts/update.js.haml_spec.rb +1 -1
- data/spec/views/admin/field_groups/create.js.haml_spec.rb +1 -1
- data/spec/views/admin/field_groups/destroy.js.haml_spec.rb +1 -1
- data/spec/views/admin/field_groups/edit.js.haml_spec.rb +1 -1
- data/spec/views/admin/field_groups/new.js.haml_spec.rb +1 -1
- data/spec/views/admin/field_groups/update.js.haml_spec.rb +1 -1
- data/spec/views/admin/users/create.js.haml_spec.rb +2 -2
- data/spec/views/admin/users/destroy.js.haml_spec.rb +2 -2
- data/spec/views/admin/users/edit.js.haml_spec.rb +2 -2
- data/spec/views/admin/users/index.haml_spec.rb +1 -1
- data/spec/views/admin/users/index.js.haml_spec.rb +2 -2
- data/spec/views/admin/users/reactivate.js.haml_spec.rb +1 -1
- data/spec/views/admin/users/suspend.js.haml_spec.rb +1 -1
- data/spec/views/admin/users/update.js.haml_spec.rb +1 -1
- data/spec/views/application/auto_complete.haml_spec.rb +3 -3
- data/spec/views/campaigns/_edit.haml_spec.rb +1 -1
- data/spec/views/campaigns/create.js.haml_spec.rb +3 -3
- data/spec/views/campaigns/destroy.js.haml_spec.rb +1 -1
- data/spec/views/campaigns/edit.js.haml_spec.rb +2 -2
- data/spec/views/campaigns/index.haml_spec.rb +1 -1
- data/spec/views/campaigns/index.js.haml_spec.rb +1 -1
- data/spec/views/campaigns/show.haml_spec.rb +4 -4
- data/spec/views/campaigns/update.js.haml_spec.rb +1 -1
- data/spec/views/contacts/_edit.haml_spec.rb +7 -7
- data/spec/views/contacts/_new.haml_spec.rb +1 -1
- data/spec/views/contacts/create.js.haml_spec.rb +4 -4
- data/spec/views/contacts/destroy.js.haml_spec.rb +1 -1
- data/spec/views/contacts/edit.js.haml_spec.rb +3 -3
- data/spec/views/contacts/index.haml_spec.rb +1 -1
- data/spec/views/contacts/index.js.html_spec.rb +1 -1
- data/spec/views/contacts/new.js.haml_spec.rb +1 -1
- data/spec/views/contacts/show.haml_spec.rb +3 -3
- data/spec/views/contacts/update.js.haml_spec.rb +2 -2
- data/spec/views/home/index.haml_spec.rb +1 -1
- data/spec/views/home/index.js.haml_spec.rb +1 -1
- data/spec/views/home/options.js.haml_spec.rb +2 -2
- data/spec/views/leads/_convert.haml_spec.rb +3 -3
- data/spec/views/leads/_edit.haml_spec.rb +2 -2
- data/spec/views/leads/_new.haml_spec.rb +2 -2
- data/spec/views/leads/_sidebar_show.haml_spec.rb +5 -5
- data/spec/views/leads/convert.js.haml_spec.rb +4 -4
- data/spec/views/leads/create.js.haml_spec.rb +5 -5
- data/spec/views/leads/destroy.js.haml_spec.rb +2 -2
- data/spec/views/leads/edit.js.haml_spec.rb +4 -4
- data/spec/views/leads/index.haml_spec.rb +1 -1
- data/spec/views/leads/index.js.haml_spec.rb +1 -1
- data/spec/views/leads/new.js.haml_spec.rb +1 -1
- data/spec/views/leads/promote.js.haml_spec.rb +7 -7
- data/spec/views/leads/reject.js.haml_spec.rb +2 -2
- data/spec/views/leads/show.haml_spec.rb +2 -2
- data/spec/views/leads/update.js.haml_spec.rb +4 -4
- data/spec/views/opportunities/_edit.haml_spec.rb +7 -7
- data/spec/views/opportunities/_new.haml_spec.rb +2 -2
- data/spec/views/opportunities/create.js.haml_spec.rb +6 -6
- data/spec/views/opportunities/destroy.js.haml_spec.rb +3 -3
- data/spec/views/opportunities/edit.js.haml_spec.rb +3 -3
- data/spec/views/opportunities/index.haml_spec.rb +1 -1
- data/spec/views/opportunities/index.js.haml_spec.rb +1 -1
- data/spec/views/opportunities/new.js.haml_spec.rb +1 -1
- data/spec/views/opportunities/show.haml_spec.rb +3 -3
- data/spec/views/opportunities/update.js.haml_spec.rb +4 -4
- data/spec/views/tasks/_edit.haml_spec.rb +1 -1
- data/spec/views/tasks/complete.js.haml_spec.rb +4 -4
- data/spec/views/tasks/create.js.haml_spec.rb +6 -6
- data/spec/views/tasks/destroy.js.haml_spec.rb +2 -2
- data/spec/views/tasks/index.haml_spec.rb +4 -4
- data/spec/views/tasks/new.js.haml_spec.rb +1 -1
- data/spec/views/tasks/uncomplete.js.haml_spec.rb +2 -2
- data/spec/views/tasks/update.js.haml_spec.rb +18 -18
- data/spec/views/users/upload_avatar.js.haml_spec.rb +2 -2
- metadata +5 -3
@@ -80,6 +80,13 @@ class Admin::FieldGroupsController < Admin::ApplicationController
|
|
80
80
|
protected
|
81
81
|
|
82
82
|
def field_group_params
|
83
|
-
params
|
83
|
+
params.require(:field_group).permit(
|
84
|
+
:name,
|
85
|
+
:label,
|
86
|
+
:position,
|
87
|
+
:hint,
|
88
|
+
:tag_id,
|
89
|
+
:klass_name
|
90
|
+
)
|
84
91
|
end
|
85
92
|
end
|
@@ -43,10 +43,10 @@ class Admin::FieldsController < Admin::ApplicationController
|
|
43
43
|
def create
|
44
44
|
as = field_params[:as]
|
45
45
|
@field =
|
46
|
-
if as
|
46
|
+
if as.match?(/pair/)
|
47
47
|
CustomFieldPair.create_pair(params).first
|
48
48
|
elsif as.present?
|
49
|
-
klass = Field.lookup_class(as)
|
49
|
+
klass = find_class(Field.lookup_class(as))
|
50
50
|
klass.create(field_params)
|
51
51
|
else
|
52
52
|
Field.new(field_params).tap(&:valid?)
|
@@ -59,7 +59,7 @@ class Admin::FieldsController < Admin::ApplicationController
|
|
59
59
|
# PUT /fields/1.xml AJAX
|
60
60
|
#----------------------------------------------------------------------------
|
61
61
|
def update
|
62
|
-
if field_params[:as]
|
62
|
+
if field_params[:as].match?(/pair/)
|
63
63
|
@field = CustomFieldPair.update_pair(params).first
|
64
64
|
else
|
65
65
|
@field = Field.find(params[:id])
|
@@ -102,7 +102,7 @@ class Admin::FieldsController < Admin::ApplicationController
|
|
102
102
|
Field.find(id).tap { |f| f.as = as }
|
103
103
|
else
|
104
104
|
field_group_id = field[:field_group_id]
|
105
|
-
klass = Field.lookup_class(as)
|
105
|
+
klass = find_class(Field.lookup_class(as))
|
106
106
|
klass.new(field_group_id: field_group_id, as: as)
|
107
107
|
end
|
108
108
|
|
@@ -281,4 +281,15 @@ class ApplicationController < ActionController::Base
|
|
281
281
|
render plain: ''
|
282
282
|
end
|
283
283
|
end
|
284
|
+
|
285
|
+
def find_class(asset)
|
286
|
+
Rails.application.eager_load! unless Rails.application.config.cache_classes
|
287
|
+
classes = ActiveRecord::Base.descendants.map(&:name)
|
288
|
+
find = classes.find { |m| m == asset.classify }
|
289
|
+
if find
|
290
|
+
find.safe_constantize
|
291
|
+
else
|
292
|
+
raise "Unknown resource"
|
293
|
+
end
|
294
|
+
end
|
284
295
|
end
|
@@ -21,7 +21,7 @@ class AuthenticationsController < ApplicationController
|
|
21
21
|
|
22
22
|
#----------------------------------------------------------------------------
|
23
23
|
def create
|
24
|
-
@authentication = Authentication.new(params[:authentication].permit(:username, :password, :remember_me))
|
24
|
+
@authentication = Authentication.new(params[:authentication].permit(:username, :password, :remember_me).to_h)
|
25
25
|
|
26
26
|
if @authentication.save && !@authentication.user.suspended?
|
27
27
|
flash[:notice] = t(:msg_welcome)
|
@@ -15,7 +15,7 @@ class CommentsController < ApplicationController
|
|
15
15
|
def index
|
16
16
|
@commentable = extract_commentable_name(params)
|
17
17
|
if @commentable
|
18
|
-
@asset = @commentable.
|
18
|
+
@asset = find_class(@commentable).my(current_user).find(params[:"#{@commentable}_id"])
|
19
19
|
@comments = @asset.comments.order("created_at DESC")
|
20
20
|
end
|
21
21
|
respond_with(@comments) do |format|
|
@@ -35,9 +35,9 @@ class CommentsController < ApplicationController
|
|
35
35
|
def edit
|
36
36
|
@comment = Comment.find(params[:id])
|
37
37
|
|
38
|
-
model = @comment.commentable_type
|
38
|
+
model = find_class(@comment.commentable_type)
|
39
39
|
id = @comment.commentable_id
|
40
|
-
unless model.
|
40
|
+
unless model.my(current_user).find_by_id(id)
|
41
41
|
respond_to_related_not_found(model.downcase)
|
42
42
|
end
|
43
43
|
end
|
@@ -51,13 +51,13 @@ class CommentsController < ApplicationController
|
|
51
51
|
comment_params.merge(user_id: current_user.id)
|
52
52
|
)
|
53
53
|
# Make sure commentable object exists and is accessible to the current user.
|
54
|
-
model = @comment.commentable_type
|
54
|
+
model = find_class(@comment.commentable_type)
|
55
55
|
id = @comment.commentable_id
|
56
|
-
if model.
|
56
|
+
if model.my(current_user).find_by_id(id)
|
57
57
|
@comment.save
|
58
58
|
respond_with(@comment)
|
59
59
|
else
|
60
|
-
respond_to_related_not_found(model.downcase)
|
60
|
+
respond_to_related_not_found(model.name.downcase)
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
@@ -85,7 +85,15 @@ class CommentsController < ApplicationController
|
|
85
85
|
|
86
86
|
def comment_params
|
87
87
|
return {} unless params[:comment]
|
88
|
-
params
|
88
|
+
params.require(:comment).permit(
|
89
|
+
:user_id,
|
90
|
+
:commentable_type,
|
91
|
+
:commentable_id,
|
92
|
+
:private,
|
93
|
+
:title,
|
94
|
+
:comment,
|
95
|
+
:state
|
96
|
+
)
|
89
97
|
end
|
90
98
|
|
91
99
|
private
|
@@ -196,8 +196,13 @@ class CampaignsController < EntitiesController
|
|
196
196
|
other: 0
|
197
197
|
]
|
198
198
|
Setting.campaign_status.each do |key|
|
199
|
-
@campaign_status_total[key] =
|
200
|
-
|
199
|
+
@campaign_status_total[key] = 0
|
200
|
+
end
|
201
|
+
|
202
|
+
status_counts = Campaign.my(current_user).where(status: Setting.campaign_status).group(:status).count
|
203
|
+
status_counts.each do |key, total|
|
204
|
+
@campaign_status_total[key.to_sym] = total
|
205
|
+
@campaign_status_total[:other] -= total
|
201
206
|
end
|
202
207
|
@campaign_status_total[:other] += @campaign_status_total[:all]
|
203
208
|
end
|
@@ -248,10 +248,17 @@ class LeadsController < EntitiesController
|
|
248
248
|
all: Lead.my(current_user).count,
|
249
249
|
other: 0
|
250
250
|
]
|
251
|
+
|
251
252
|
Setting.lead_status.each do |key|
|
252
|
-
@lead_status_total[key] =
|
253
|
-
|
253
|
+
@lead_status_total[key] = 0
|
254
|
+
end
|
255
|
+
|
256
|
+
status_counts = Lead.my(current_user).where(status: Setting.lead_status).group(:status).count
|
257
|
+
status_counts.each do |key, total|
|
258
|
+
@lead_status_total[key.to_sym] = total
|
259
|
+
@lead_status_total[:other] -= total
|
254
260
|
end
|
261
|
+
|
255
262
|
@lead_status_total[:other] += @lead_status_total[:all]
|
256
263
|
end
|
257
264
|
end
|
@@ -169,6 +169,10 @@ class OpportunitiesController < EntitiesController
|
|
169
169
|
|
170
170
|
private
|
171
171
|
|
172
|
+
def order_by_attributes(scope, order)
|
173
|
+
scope.weighted_sort.order(order)
|
174
|
+
end
|
175
|
+
|
172
176
|
#----------------------------------------------------------------------------
|
173
177
|
alias get_opportunities get_list_of_records
|
174
178
|
|
@@ -207,9 +211,16 @@ class OpportunitiesController < EntitiesController
|
|
207
211
|
all: Opportunity.my(current_user).count,
|
208
212
|
other: 0
|
209
213
|
]
|
214
|
+
stages = []
|
210
215
|
@stage.each do |_value, key|
|
211
|
-
|
212
|
-
@opportunity_stage_total[
|
216
|
+
stages << key
|
217
|
+
@opportunity_stage_total[key] = 0
|
218
|
+
end
|
219
|
+
|
220
|
+
stage_counts = Opportunity.my(current_user).where(stage: stages).group(:stage).count
|
221
|
+
stage_counts.each do |key, total|
|
222
|
+
@opportunity_stage_total[key.to_sym] = total
|
223
|
+
@opportunity_stage_total[:other] -= total
|
213
224
|
end
|
214
225
|
@opportunity_stage_total[:other] += @opportunity_stage_total[:all]
|
215
226
|
end
|
@@ -22,7 +22,7 @@ class EntitiesController < ApplicationController
|
|
22
22
|
# Common attach handler for all core controllers.
|
23
23
|
#----------------------------------------------------------------------------
|
24
24
|
def attach
|
25
|
-
@attachment = params[:assets].
|
25
|
+
@attachment = find_class(params[:assets]).find(params[:asset_id])
|
26
26
|
@attached = entity.attach!(@attachment)
|
27
27
|
entity.reload
|
28
28
|
|
@@ -32,7 +32,7 @@ class EntitiesController < ApplicationController
|
|
32
32
|
# Common discard handler for all core controllers.
|
33
33
|
#----------------------------------------------------------------------------
|
34
34
|
def discard
|
35
|
-
@attachment = params[:attachment].
|
35
|
+
@attachment = find_class(params[:attachment]).find(params[:attachment_id])
|
36
36
|
entity.discard!(@attachment)
|
37
37
|
entity.reload
|
38
38
|
|
@@ -152,16 +152,16 @@ class EntitiesController < ApplicationController
|
|
152
152
|
scope = scope.state(filter) if filter.present?
|
153
153
|
end
|
154
154
|
|
155
|
-
scope = scope.text_search(query)
|
155
|
+
scope = scope.text_search(query) if query.present?
|
156
156
|
scope = scope.tagged_with(tags, on: :tags) if tags.present?
|
157
157
|
|
158
158
|
# Ignore this order when doing advanced search
|
159
159
|
unless advanced_search
|
160
160
|
order = current_user.pref[:"#{controller_name}_sort_by"] || klass.sort_by
|
161
|
-
scope = scope
|
161
|
+
scope = order_by_attributes(scope, order)
|
162
162
|
end
|
163
163
|
|
164
|
-
@search_results_count = scope.
|
164
|
+
@search_results_count = scope.size
|
165
165
|
|
166
166
|
# Pagination is disabled for xls and csv requests
|
167
167
|
unless wants.xls? || wants.csv?
|
@@ -178,6 +178,11 @@ class EntitiesController < ApplicationController
|
|
178
178
|
scope
|
179
179
|
end
|
180
180
|
|
181
|
+
#----------------------------------------------------------------------------
|
182
|
+
def order_by_attributes(scope, order)
|
183
|
+
scope.order(order)
|
184
|
+
end
|
185
|
+
|
181
186
|
#----------------------------------------------------------------------------
|
182
187
|
def update_recently_viewed
|
183
188
|
entity.versions.create(event: :view, whodunnit: PaperTrail.whodunnit)
|
@@ -173,7 +173,21 @@ class TasksController < ApplicationController
|
|
173
173
|
|
174
174
|
def task_params
|
175
175
|
return {} unless params[:task]
|
176
|
-
params
|
176
|
+
params.require(:task).permit(
|
177
|
+
:user_id,
|
178
|
+
:assigned_to,
|
179
|
+
:completed_by,
|
180
|
+
:name,
|
181
|
+
:asset_id,
|
182
|
+
:asset_type,
|
183
|
+
:priority,
|
184
|
+
:category,
|
185
|
+
:bucket,
|
186
|
+
:due_at,
|
187
|
+
:completed_at,
|
188
|
+
:deleted_at,
|
189
|
+
:background_info
|
190
|
+
)
|
177
191
|
end
|
178
192
|
|
179
193
|
private
|
@@ -26,7 +26,7 @@ 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
|
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
32
|
{ include_blank: true },
|
@@ -7,7 +7,7 @@
|
|
7
7
|
#------------------------------------------------------------------------------
|
8
8
|
module ApplicationHelper
|
9
9
|
def tabs(tabs = nil)
|
10
|
-
tabs ||= controller_path
|
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
|
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
|
data/app/helpers/leads_helper.rb
CHANGED
@@ -11,7 +11,7 @@ module LeadsHelper
|
|
11
11
|
#----------------------------------------------------------------------------
|
12
12
|
def stars_for(lead)
|
13
13
|
star = '★'
|
14
|
-
rating = lead.rating
|
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
|
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,
|
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
|
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
|
data/app/helpers/tags_helper.rb
CHANGED
@@ -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('#{
|
20
|
+
out << link_to_function(tag, "crm.search_tagged('#{query}', '#{model.class.to_s.tableize}')", title: tag)
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
data/app/models/entities/lead.rb
CHANGED
@@ -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
|
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
|
-
(
|
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).
|