fat_free_crm 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/bootstrap-custom.scss +1 -0
- data/app/assets/stylesheets/common.scss +9 -3
- data/app/controllers/admin/research_tools_controller.rb +45 -0
- data/app/controllers/admin/settings_controller.rb +71 -0
- data/app/controllers/admin/users_controller.rb +0 -1
- data/app/controllers/comments_controller.rb +2 -2
- data/app/controllers/emails_controller.rb +2 -1
- data/app/controllers/users_controller.rb +2 -1
- data/app/helpers/application_helper.rb +28 -12
- data/app/models/entities/contact.rb +1 -3
- data/app/models/entities/lead.rb +0 -1
- data/app/models/observers/entity_observer.rb +1 -1
- data/app/models/polymorphic/comment.rb +1 -1
- data/app/models/research_tool.rb +4 -0
- data/app/models/users/ability.rb +6 -0
- data/app/models/users/user.rb +0 -1
- data/app/views/accounts/_sidebar_show.html.haml +2 -0
- data/app/views/admin/research_tools/_form.html.haml +12 -0
- data/app/views/admin/research_tools/_new.html.haml +11 -0
- data/app/views/admin/research_tools/_research_tool.html.haml +8 -0
- data/app/views/admin/research_tools/_top_section.html.haml +6 -0
- data/app/views/admin/research_tools/create.js.haml +7 -0
- data/app/views/admin/research_tools/destroy.js.haml +1 -0
- data/app/views/admin/research_tools/edit.html.haml +1 -0
- data/app/views/admin/research_tools/index.html.haml +19 -0
- data/app/views/admin/research_tools/new.js.haml +7 -0
- data/app/views/admin/research_tools/update.js.haml +4 -0
- data/app/views/admin/settings/_ai_prompts.html.haml +15 -0
- data/app/views/admin/settings/_application.html.haml +44 -0
- data/app/views/admin/settings/_customization.html.haml +43 -0
- data/app/views/admin/settings/_email.html.haml +112 -0
- data/app/views/admin/settings/_user_signup.html.haml +13 -0
- data/app/views/admin/settings/_validations.html.haml +19 -0
- data/app/views/admin/settings/index.html.haml +10 -2
- data/app/views/admin/tags/index.html.haml +4 -0
- data/app/views/contacts/_index_brief.html.haml +2 -2
- data/app/views/contacts/_index_full.html.haml +1 -1
- data/app/views/contacts/_index_long.html.haml +1 -1
- data/app/views/contacts/_sidebar_show.html.haml +3 -1
- data/app/views/contacts/_web.html.haml +1 -3
- data/app/views/contacts/index.xls.builder +0 -2
- data/app/views/devise/sessions/new.html.haml +1 -1
- data/app/views/entities/_title_bar.html.haml +1 -1
- data/app/views/layouts/admin/application.html.haml +1 -1
- data/app/views/layouts/application.html.haml +1 -1
- data/app/views/leads/_sidebar_show.html.haml +3 -1
- data/app/views/leads/_web.html.haml +1 -3
- data/app/views/leads/index.xls.builder +0 -2
- data/app/views/shared/_research_tools.html.haml +8 -0
- data/app/views/users/_avatar.html.haml +1 -1
- data/app/views/users/_profile.html.haml +11 -4
- data/app/views/users/_user.html.haml +3 -4
- data/config/application.rb +2 -0
- data/config/initializers/devise.rb +5 -1
- data/config/initializers/rack_attack.rb +29 -0
- data/config/locales/fat_free_crm.cs.yml +1 -2
- data/config/locales/{fat_free_crm.de.yml → fat_free_crm.de-DE.yml} +1 -2
- data/config/locales/fat_free_crm.en-GB.yml +74 -133
- data/config/locales/fat_free_crm.en-US.yml +18 -4
- data/config/locales/fat_free_crm.es-CL.yml +0 -1
- data/config/locales/fat_free_crm.es.yml +1 -2
- data/config/locales/fat_free_crm.et.yml +1 -2
- data/config/locales/fat_free_crm.fr-CA.yml +1 -2
- data/config/locales/fat_free_crm.fr.yml +1 -2
- data/config/locales/fat_free_crm.it.yml +1 -2
- data/config/locales/fat_free_crm.ja.yml +1 -2
- data/config/locales/fat_free_crm.nl.yml +1 -2
- data/config/locales/fat_free_crm.pl.yml +0 -1
- data/config/locales/fat_free_crm.pt-BR.yml +0 -1
- data/config/locales/fat_free_crm.ru.yml +1 -2
- data/config/locales/fat_free_crm.sv-SE.yml +1 -2
- data/config/locales/fat_free_crm.th.yml +0 -1
- data/config/locales/fat_free_crm.zh-CN.yml +1 -2
- data/config/routes.rb +8 -2
- data/config/settings.default.yml +19 -5
- data/db/demo/contacts.yml +0 -2
- data/db/demo/leads.yml +0 -1
- data/db/demo/research_tools.yml +12 -0
- data/db/demo/users.yml +0 -1
- data/db/fat_free_crm_development.sqlite3 +0 -0
- data/db/fat_free_crm_test.sqlite3 +0 -0
- data/db/{fat_free_crm_development.sqlite3-shm → fat_free_crm_test.sqlite3-shm} +0 -0
- data/db/fat_free_crm_test.sqlite3-wal +0 -0
- data/db/migrate/20230526212613_convert_to_active_storage.rb +2 -0
- data/db/migrate/20230526212614_create_research_tools.rb +13 -0
- data/db/migrate/20250502095012_remove_skype_from_users_contacts_and_leads.rb +15 -0
- data/db/migrate/20250806025815_add_email_preferences_to_users.rb +11 -0
- data/db/schema.rb +3 -5
- data/db/seeds.rb +2 -1
- data/lib/fat_free_crm/tabs.rb +2 -1
- data/lib/fat_free_crm/version.rb +1 -1
- data/lib/tasks/ffcrm/demo.rake +15 -0
- metadata +48 -16
- data/db/fat_free_crm_development.sqlite3-wal +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba3bf6976179155013188261941cad491438f42dda298d39f01f37502d1928d9
|
|
4
|
+
data.tar.gz: 616970ceed5e34145399c915e6f3ccf740091101d1491771f53904c2b98b1a9a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f5995672093e110187c94aabe0c6d5f4ea3e950fadd6192d1dc2989d361259eb5ee6baa3917f7ee6be80d6433d2eacf6d6bdaed848fbd55572286372e1edf87e
|
|
7
|
+
data.tar.gz: f1dcc4238b2ece5850b811f611d928dd4db015cc0b29807a143cacd8d2521367a50a16f486ad034377c020c67b7705d3e5323906ceb3c1da5c42c04bd73c19a0
|
|
@@ -78,6 +78,11 @@ $sidebar_width: 210px;
|
|
|
78
78
|
margin: auto;
|
|
79
79
|
width: 480px; }
|
|
80
80
|
|
|
81
|
+
.gravatar {
|
|
82
|
+
border-radius: 50% !important;
|
|
83
|
+
box-shadow: 0 0 0 1px #aaa
|
|
84
|
+
}
|
|
85
|
+
|
|
81
86
|
.standalone {
|
|
82
87
|
background: whitesmoke;
|
|
83
88
|
border: 20px lightsteelblue solid;
|
|
@@ -338,7 +343,7 @@ $bg_color9: #FFDD4A;
|
|
|
338
343
|
margin-left: 88px; }
|
|
339
344
|
|
|
340
345
|
.indentslim {
|
|
341
|
-
margin-left:
|
|
346
|
+
margin-left: 4em; }
|
|
342
347
|
|
|
343
348
|
.indentwide {
|
|
344
349
|
margin-left: 106px; }
|
|
@@ -376,7 +381,8 @@ $bg_color9: #FFDD4A;
|
|
|
376
381
|
width: 500px; }
|
|
377
382
|
.gravatar {
|
|
378
383
|
float: left;
|
|
379
|
-
margin: 3px 10px 3px 3px;
|
|
384
|
+
margin: 3px 10px 3px 3px;
|
|
385
|
+
}
|
|
380
386
|
.body {
|
|
381
387
|
display: inline;
|
|
382
388
|
font-size: 0.9em; }
|
|
@@ -483,7 +489,7 @@ $bg_color9: #FFDD4A;
|
|
|
483
489
|
padding: 6px 0px 6px 0px;
|
|
484
490
|
.gravatar {
|
|
485
491
|
float: left;
|
|
486
|
-
|
|
492
|
+
margin: 0 0.5em ; }
|
|
487
493
|
tt {
|
|
488
494
|
color: dimgray;
|
|
489
495
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Admin::ResearchToolsController < Admin::ApplicationController
|
|
4
|
+
before_action :setup_current_tab, only: %i[index]
|
|
5
|
+
|
|
6
|
+
load_resource
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@research_tools = ResearchTool.all
|
|
10
|
+
respond_with(@research_tools)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def new
|
|
14
|
+
respond_with(@research_tool)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def edit
|
|
18
|
+
respond_with(@research_tool)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def create
|
|
22
|
+
@research_tool.update(research_tool_params)
|
|
23
|
+
respond_with(@research_tool, location: -> { admin_research_tools_path })
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def update
|
|
27
|
+
@research_tool.update(research_tool_params)
|
|
28
|
+
respond_with(@research_tool, location: -> { admin_research_tools_path })
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def destroy
|
|
32
|
+
@research_tool.destroy
|
|
33
|
+
respond_with(@research_tool)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
protected
|
|
37
|
+
|
|
38
|
+
def research_tool_params
|
|
39
|
+
params.require(:research_tool).permit(:name, :url_template, :enabled)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def setup_current_tab
|
|
43
|
+
set_current_tab('admin/research_tools')
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -14,6 +14,77 @@ class Admin::SettingsController < Admin::ApplicationController
|
|
|
14
14
|
def index
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
# PUT /admin/settings
|
|
18
|
+
#----------------------------------------------------------------------------
|
|
19
|
+
def update
|
|
20
|
+
settings = settings_params.to_h.with_indifferent_access
|
|
21
|
+
|
|
22
|
+
# All settings are strings from the form.
|
|
23
|
+
# We need to convert them to their correct types before saving.
|
|
24
|
+
|
|
25
|
+
# Booleans
|
|
26
|
+
%w[per_user_locale compound_address task_calendar_with_time require_first_names require_last_names require_unique_account_names comments_visible_on_dashboard enforce_international_phone_format].each do |key|
|
|
27
|
+
settings[key] = (settings[key] == '1') if settings.key?(key)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Nested booleans
|
|
31
|
+
settings[:email_dropbox][:ssl] = (settings[:email_dropbox][:ssl] == '1') if settings.key?(:email_dropbox) && settings[:email_dropbox].key?(:ssl)
|
|
32
|
+
settings[:email_comment_replies][:ssl] = (settings[:email_comment_replies][:ssl] == '1') if settings.key?(:email_comment_replies) && settings[:email_comment_replies].key?(:ssl)
|
|
33
|
+
|
|
34
|
+
# Arrays from textareas
|
|
35
|
+
%w[account_category campaign_status lead_status lead_source opportunity_stage task_category task_bucket task_completed priority_countries].each do |key|
|
|
36
|
+
settings[key] = settings[key].split(/\r?\n/).map(&:strip).compact_blank if settings[key].is_a?(String)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Symbols
|
|
40
|
+
settings[:user_signup] = settings[:user_signup].to_sym if settings[:user_signup].is_a?(String)
|
|
41
|
+
|
|
42
|
+
# Save all settings
|
|
43
|
+
settings.each do |key, value|
|
|
44
|
+
Setting[key] = value
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
redirect_to admin_settings_path, notice: t('fat_free_crm.settings_updated')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def settings_params
|
|
53
|
+
params.require(:settings).permit(
|
|
54
|
+
:host, :base_url, :locale, :per_user_locale, :default_access, :user_signup,
|
|
55
|
+
:compound_address, :task_calendar_with_time, :require_first_names,
|
|
56
|
+
:require_last_names, :require_unique_account_names,
|
|
57
|
+
:comments_visible_on_dashboard, :enforce_international_phone_format,
|
|
58
|
+
:opportunity_default_stage,
|
|
59
|
+
background_info: [],
|
|
60
|
+
priority_countries: [],
|
|
61
|
+
account_category: [],
|
|
62
|
+
campaign_status: [],
|
|
63
|
+
lead_status: [],
|
|
64
|
+
lead_source: [],
|
|
65
|
+
opportunity_stage: [],
|
|
66
|
+
task_category: [],
|
|
67
|
+
task_bucket: [],
|
|
68
|
+
task_completed: [],
|
|
69
|
+
smtp: %i[
|
|
70
|
+
address from enable_starttls_auto port authentication
|
|
71
|
+
user_name password
|
|
72
|
+
],
|
|
73
|
+
email_dropbox: [
|
|
74
|
+
:server, :port, :ssl, :address, :user, :password, :scan_folder,
|
|
75
|
+
:attach_to_account, :move_to_folder, :move_invalid_to_folder,
|
|
76
|
+
{ address_aliases: [] }
|
|
77
|
+
],
|
|
78
|
+
email_comment_replies: %i[
|
|
79
|
+
server port ssl address user password scan_folder
|
|
80
|
+
move_to_folder move_invalid_to_folder
|
|
81
|
+
],
|
|
82
|
+
ai_prompts: %i[
|
|
83
|
+
about_my_business how_i_plan_to_use_ffcrm
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
17
88
|
def setup_current_tab
|
|
18
89
|
set_current_tab('admin/settings')
|
|
19
90
|
end
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
# See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
|
|
7
7
|
#------------------------------------------------------------------------------
|
|
8
8
|
class CommentsController < ApplicationController
|
|
9
|
+
load_and_authorize_resource
|
|
10
|
+
|
|
9
11
|
# GET /comments
|
|
10
12
|
# GET /comments.json
|
|
11
13
|
# GET /comments.xml
|
|
@@ -62,7 +64,6 @@ class CommentsController < ApplicationController
|
|
|
62
64
|
# PUT /comments/1.xml not implemented
|
|
63
65
|
#----------------------------------------------------------------------------
|
|
64
66
|
def update
|
|
65
|
-
@comment = Comment.find(params[:id])
|
|
66
67
|
@comment.update(comment_params)
|
|
67
68
|
respond_with(@comment)
|
|
68
69
|
end
|
|
@@ -72,7 +73,6 @@ class CommentsController < ApplicationController
|
|
|
72
73
|
# DELETE /comments/1.xml not implemented
|
|
73
74
|
#----------------------------------------------------------------------------
|
|
74
75
|
def destroy
|
|
75
|
-
@comment = Comment.find(params[:id])
|
|
76
76
|
@comment.destroy
|
|
77
77
|
respond_with(@comment)
|
|
78
78
|
end
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
# See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
|
|
7
7
|
#------------------------------------------------------------------------------
|
|
8
8
|
class EmailsController < ApplicationController
|
|
9
|
+
load_and_authorize_resource
|
|
10
|
+
|
|
9
11
|
# DELETE /emails/1
|
|
10
12
|
# DELETE /emails/1.json
|
|
11
13
|
# DELETE /emails/1.xml AJAX
|
|
12
14
|
#----------------------------------------------------------------------------
|
|
13
15
|
def destroy
|
|
14
|
-
@email = Email.find(params[:id])
|
|
15
16
|
@email.destroy
|
|
16
17
|
respond_with(@email)
|
|
17
18
|
end
|
|
@@ -269,21 +269,16 @@ module ApplicationHelper
|
|
|
269
269
|
def web_presence_icons(person)
|
|
270
270
|
sites = []
|
|
271
271
|
icon_for_site = {
|
|
272
|
-
skype: "skype",
|
|
273
272
|
facebook: "facebook",
|
|
274
273
|
linkedin: "linkedin",
|
|
275
274
|
twitter: "twitter",
|
|
276
275
|
blog: "external-link"
|
|
277
276
|
}
|
|
278
|
-
%i[blog linkedin facebook twitter
|
|
277
|
+
%i[blog linkedin facebook twitter].each do |site|
|
|
279
278
|
url = person.send(site)
|
|
280
279
|
next if url.blank?
|
|
281
280
|
|
|
282
|
-
|
|
283
|
-
url = "callto:" + url
|
|
284
|
-
else
|
|
285
|
-
url = "http://" + url unless url.match?(%r{^https?://})
|
|
286
|
-
end
|
|
281
|
+
url = "http://" + url unless url.match?(%r{^https?://})
|
|
287
282
|
sites << if icon_for_site[site]
|
|
288
283
|
link_to(content_tag(:i, "", { class: "fa fa-#{icon_for_site[site]}" }), h(url), "data-popup": true, title: t(:open_in_window, h(url)))
|
|
289
284
|
else
|
|
@@ -338,7 +333,7 @@ module ApplicationHelper
|
|
|
338
333
|
raw "$.get('#{timezone_path}', {offset: (new Date()).getTimezoneOffset()});" unless session[:timezone_offset]
|
|
339
334
|
end
|
|
340
335
|
|
|
341
|
-
STYLES = { large: "
|
|
336
|
+
STYLES = { large: "180x180#", medium: "50x50#", small: "25x25#", thumb: "16x16#" }.freeze
|
|
342
337
|
|
|
343
338
|
# Convert STYLE symbols to 'w x h' format for Gravatar and Rails
|
|
344
339
|
# e.g. size_from_style(:size => :large) -> '75x75'
|
|
@@ -365,10 +360,22 @@ module ApplicationHelper
|
|
|
365
360
|
if model.respond_to?(:avatar) && model.avatar.present?
|
|
366
361
|
size = args[:size].split('x').map(&:to_i) # convert '75x75' into [75, 75]
|
|
367
362
|
|
|
368
|
-
image_tag model.avatar.image.variant(resize_to_limit: size)
|
|
363
|
+
image_tag model.avatar.image.variant(resize_to_limit: size), args
|
|
369
364
|
else
|
|
370
365
|
gravatar_image_tag(model.email, args)
|
|
371
|
-
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def ai_prompt_link(prompt)
|
|
370
|
+
return unless Setting[:about_my_business].present? || Setting[:how_i_plan_to_use_ffcrm].present?
|
|
371
|
+
|
|
372
|
+
full_prompt = [Setting[:about_my_business], Setting[:how_i_plan_to_use_ffcrm], prompt].compact.compact_blank.join(". ")
|
|
373
|
+
|
|
374
|
+
link_to(t(:ai_prompt_link), "https://chat.openai.com/?model=gpt-4o&prompt=#{URI.encode_uri_component(full_prompt)}",
|
|
375
|
+
target: "_blank",
|
|
376
|
+
title: "#{t(:ai_prompt_link_tooltip)}: #{prompt}",
|
|
377
|
+
rel: "noopener noreferrer",
|
|
378
|
+
class: "ai-prompt-link")
|
|
372
379
|
end
|
|
373
380
|
|
|
374
381
|
# Returns default permissions intro.
|
|
@@ -473,7 +480,7 @@ module ApplicationHelper
|
|
|
473
480
|
link_to_email(fmt_value)
|
|
474
481
|
else
|
|
475
482
|
fmt_value.gsub(%r{((http|ftp|https)://[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/\+#]*[\w\-\@?^=%&/\+#])?)}, "<a href=\"\\1\">\\1</a>")
|
|
476
|
-
|
|
483
|
+
end
|
|
477
484
|
out << content_tag(:td, fmt_value, class: last_class)
|
|
478
485
|
end
|
|
479
486
|
out
|
|
@@ -511,7 +518,7 @@ module ApplicationHelper
|
|
|
511
518
|
"#{h view.name}-button active"
|
|
512
519
|
else
|
|
513
520
|
"#{h view.name}-button"
|
|
514
|
-
|
|
521
|
+
end
|
|
515
522
|
lis << content_tag(:li) do
|
|
516
523
|
url = show_or_index_action == "index" ? send("redraw_#{controller.controller_name}_path") : send("#{controller.controller_name.singularize}_path")
|
|
517
524
|
link_to('#', title: t(view.name, default: h(view.title)), "data-view": h(view.name), "data-url": h(url), "data-context": show_or_index_action, class: classes) do
|
|
@@ -569,4 +576,13 @@ module ApplicationHelper
|
|
|
569
576
|
def current_view_name
|
|
570
577
|
current_user.pref[:"#{controller.controller_name}_#{show_or_index_action}_view"]
|
|
571
578
|
end
|
|
579
|
+
|
|
580
|
+
def expand_research_tool_url(tool, entity)
|
|
581
|
+
template = Addressable::Template.new(tool.url_template)
|
|
582
|
+
mappings = {}
|
|
583
|
+
template.keys.each do |key| # rubocop:disable Style/HashEachMethods
|
|
584
|
+
mappings[key] = entity.send(key) if entity.respond_to?(key)
|
|
585
|
+
end
|
|
586
|
+
template.expand(mappings).to_s
|
|
587
|
+
end
|
|
572
588
|
end
|
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
# created_at :datetime
|
|
36
36
|
# updated_at :datetime
|
|
37
37
|
# background_info :string(255)
|
|
38
|
-
# skype :string(128)
|
|
39
38
|
#
|
|
40
39
|
|
|
41
40
|
class Contact < ActiveRecord::Base
|
|
@@ -111,7 +110,6 @@ class Contact < ActiveRecord::Base
|
|
|
111
110
|
validates_length_of :linkedin, maximum: 128
|
|
112
111
|
validates_length_of :facebook, maximum: 128
|
|
113
112
|
validates_length_of :twitter, maximum: 128
|
|
114
|
-
validates_length_of :skype, maximum: 128
|
|
115
113
|
|
|
116
114
|
# Default values provided through class methods.
|
|
117
115
|
#----------------------------------------------------------------------------
|
|
@@ -177,7 +175,7 @@ class Contact < ActiveRecord::Base
|
|
|
177
175
|
assigned_to: params[:account][:assigned_to],
|
|
178
176
|
access: params[:access]
|
|
179
177
|
}
|
|
180
|
-
%w[first_name last_name title source email alt_email phone mobile blog linkedin facebook twitter
|
|
178
|
+
%w[first_name last_name title source email alt_email phone mobile blog linkedin facebook twitter do_not_call background_info].each do |name|
|
|
181
179
|
attributes[name] = model.send(name.intern)
|
|
182
180
|
end
|
|
183
181
|
|
data/app/models/entities/lead.rb
CHANGED
|
@@ -19,7 +19,7 @@ class EntityObserver < ActiveRecord::Observer
|
|
|
19
19
|
private
|
|
20
20
|
|
|
21
21
|
def send_notification_to_assignee(item)
|
|
22
|
-
UserMailer.assigned_entity_notification(item, current_user).deliver_later if item.assignee.present? && current_user.present? && can_send_email?
|
|
22
|
+
UserMailer.assigned_entity_notification(item, current_user).deliver_later if item.assignee.present? && item.assignee.receive_assigned_notifications? && current_user.present? && can_send_email?
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
# Need to have a host set before email can be sent
|
|
@@ -54,7 +54,7 @@ class Comment < ActiveRecord::Base
|
|
|
54
54
|
def notify_subscribers
|
|
55
55
|
users_to_notify = User.where(id: commentable.subscribed_users.reject { |user_id| user_id == user.id })
|
|
56
56
|
users_to_notify.select(&:emailable?).each do |subscriber|
|
|
57
|
-
SubscriptionMailer.comment_notification(subscriber, self).deliver_later
|
|
57
|
+
SubscriptionMailer.comment_notification(subscriber, self).deliver_later if subscriber.subscribe_to_comment_replies?
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
data/app/models/users/ability.rb
CHANGED
|
@@ -31,6 +31,12 @@ class Ability
|
|
|
31
31
|
can :manage, entities + [Task], user_id: user.id
|
|
32
32
|
can :manage, entities + [Task], assigned_to: user.id
|
|
33
33
|
|
|
34
|
+
can :create, Comment
|
|
35
|
+
can :manage, Comment, user_id: user.id
|
|
36
|
+
|
|
37
|
+
can :create, Email
|
|
38
|
+
can :manage, Email, user_id: user.id
|
|
39
|
+
|
|
34
40
|
#
|
|
35
41
|
# Due to an obscure bug (see https://github.com/ryanb/cancan/issues/213)
|
|
36
42
|
# we must switch on user.admin? here to avoid the nil constraints which
|
data/app/models/users/user.rb
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
= form_for([:admin, @research_tool]) do |f|
|
|
2
|
+
.section
|
|
3
|
+
%table
|
|
4
|
+
%tr
|
|
5
|
+
%td
|
|
6
|
+
.label.top.req Name:
|
|
7
|
+
= f.text_field :name, placeholder: 'e.g. Google Scholar', required: true
|
|
8
|
+
%tr
|
|
9
|
+
%td
|
|
10
|
+
.label.top.req URL Template:
|
|
11
|
+
= f.text_field :url_template, placeholder: 'e.g. https://example.com/search?q={query}', required: true
|
|
12
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
- path = new_admin_research_tool_path
|
|
2
|
+
|
|
3
|
+
= form_for([:admin, @research_tool], html: one_submit_only, remote: true) do |f|
|
|
4
|
+
= link_to_close path
|
|
5
|
+
= f.error_messages
|
|
6
|
+
= render partial: "form", locals: { f: f }
|
|
7
|
+
.buttonbar
|
|
8
|
+
= f.submit t(:create_research_tool), id: :research_tool_submit
|
|
9
|
+
or
|
|
10
|
+
= link_to_cancel path
|
|
11
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
%li.highlight[research_tool]
|
|
2
|
+
= research_tool.name
|
|
3
|
+
.tools
|
|
4
|
+
= link_to_edit(research_tool, url: edit_admin_research_tool_path(research_tool))
|
|
5
|
+
|
|
|
6
|
+
= link_to_delete(research_tool, url: admin_research_tool_path(research_tool),
|
|
7
|
+
data: { confirm: t(:are_you_sure) },
|
|
8
|
+
method: :delete)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$("#<%= dom_id(@research_tool) %>").fadeOut(250);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
= render 'form'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.title_tools
|
|
2
|
+
= link_to_inline(:create_research_tool, new_admin_research_tool_path, text: t(:create_research_tool))
|
|
3
|
+
|
|
4
|
+
.title
|
|
5
|
+
%span#create_research_tool_title #{t :research_tools}
|
|
6
|
+
= image_tag("loading.gif", size: :thumb, id: "loading", style: "display: none;")
|
|
7
|
+
.remote#create_research_tool{ hidden }
|
|
8
|
+
|
|
9
|
+
.list#research_tools
|
|
10
|
+
- if @research_tools.any?
|
|
11
|
+
= render partial: "admin/research_tools/research_tool", collection: @research_tools
|
|
12
|
+
- else
|
|
13
|
+
= render "shared/empty"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
= styles_for :research_tools
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
#export= render "shared/export"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
crm.flip_form('create_research_tool');
|
|
2
|
+
|
|
3
|
+
- unless params[:cancel].true?
|
|
4
|
+
$('#create_research_tool').html('#{ j render(partial: "new") }');
|
|
5
|
+
crm.set_title('create_research_tool', '#{t(:create_research_tool)}');
|
|
6
|
+
- else
|
|
7
|
+
crm.set_title('create_research_tool', '#{t(:research_tools)}');
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
%fieldset.panel.panel-default
|
|
2
|
+
.panel-heading
|
|
3
|
+
%h4= t(:ai_prompts)
|
|
4
|
+
%p= t(:ai_prompts_description)
|
|
5
|
+
.panel-body
|
|
6
|
+
= f.fields_for :ai_prompts, Setting.ai_prompts do |ai_fields|
|
|
7
|
+
.row
|
|
8
|
+
.col-md-6
|
|
9
|
+
.form-group
|
|
10
|
+
= ai_fields.label :about_my_business, t(:about_my_business)
|
|
11
|
+
= ai_fields.text_area :about_my_business, class: 'form-control', placeholder: t(:about_my_business_placeholder), rows: 5
|
|
12
|
+
.col-md-6
|
|
13
|
+
.form-group
|
|
14
|
+
= ai_fields.label :how_i_plan_to_use_ffcrm, t(:how_i_plan_to_use_ffcrm)
|
|
15
|
+
= ai_fields.text_area :how_i_plan_to_use_ffcrm, class: 'form-control', placeholder: t(:how_i_plan_to_use_ffcrm_placeholder), rows: 5
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
%fieldset.panel.panel-default
|
|
2
|
+
.panel-heading
|
|
3
|
+
%h4= t('admin.settings.application.title', default: 'Application')
|
|
4
|
+
.panel-body
|
|
5
|
+
.row
|
|
6
|
+
.col-md-6
|
|
7
|
+
.form-group
|
|
8
|
+
= f.label :host
|
|
9
|
+
= f.text_field :host, value: Setting.host, class: 'form-control'
|
|
10
|
+
.form-group
|
|
11
|
+
= f.label :base_url, "Base URL"
|
|
12
|
+
= f.text_field :base_url, value: Setting.base_url, class: 'form-control', placeholder: "/crm"
|
|
13
|
+
.form-group
|
|
14
|
+
= f.label :locale, "Default Language"
|
|
15
|
+
= f.select :locale, options_for_select(I18n.available_locales, Setting.locale), {}, class: 'form-control'
|
|
16
|
+
.form-group
|
|
17
|
+
= f.label :default_access, "Default record permissions"
|
|
18
|
+
= f.select :default_access, options_for_select([['Public', 'Public'], ['Private', 'Private'], ['Shared', 'Shared']], Setting.default_access), {}, class: 'form-control'
|
|
19
|
+
.col-md-6
|
|
20
|
+
.form-group
|
|
21
|
+
.checkbox
|
|
22
|
+
= f.label :per_user_locale do
|
|
23
|
+
= f.check_box :per_user_locale, checked: Setting.per_user_locale
|
|
24
|
+
Allow users to select their own language
|
|
25
|
+
.form-group
|
|
26
|
+
.checkbox
|
|
27
|
+
= f.label :compound_address do
|
|
28
|
+
= f.check_box :compound_address, checked: Setting.compound_address
|
|
29
|
+
Use separate fields for addresses
|
|
30
|
+
.form-group
|
|
31
|
+
.checkbox
|
|
32
|
+
= f.label :task_calendar_with_time do
|
|
33
|
+
= f.check_box :task_calendar_with_time, checked: Setting.task_calendar_with_time
|
|
34
|
+
Show time in task calendar
|
|
35
|
+
.form-group
|
|
36
|
+
.checkbox
|
|
37
|
+
= f.label :comments_visible_on_dashboard do
|
|
38
|
+
= f.check_box :comments_visible_on_dashboard, checked: Setting.comments_visible_on_dashboard
|
|
39
|
+
Show comments on dashboard
|
|
40
|
+
.form-group
|
|
41
|
+
.checkbox
|
|
42
|
+
= f.label :enforce_international_phone_format do
|
|
43
|
+
= f.check_box :enforce_international_phone_format, checked: Setting.enforce_international_phone_format
|
|
44
|
+
Enforce international phone number format
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
%fieldset.panel.panel-default
|
|
2
|
+
.panel-heading
|
|
3
|
+
%h4= t('admin.settings.customization.title', default: 'Customization')
|
|
4
|
+
.panel-body
|
|
5
|
+
.row
|
|
6
|
+
.col-md-4
|
|
7
|
+
.form-group
|
|
8
|
+
= f.label :background_info
|
|
9
|
+
= f.select :background_info, options_for_select([:account, :campaign, :contact, :lead, :opportunity, :task], Setting.background_info), {}, { multiple: true, class: 'form-control' }
|
|
10
|
+
%span.help-block Select models that should have background information displayed.
|
|
11
|
+
.form-group
|
|
12
|
+
= f.label :priority_countries
|
|
13
|
+
= f.text_area :priority_countries, value: (Setting.priority_countries || []).join("\n"), class: 'form-control', rows: 4
|
|
14
|
+
%span.help-block One country code per line (e.g. US, GB).
|
|
15
|
+
.form-group
|
|
16
|
+
= f.label :opportunity_default_stage
|
|
17
|
+
= f.select :opportunity_default_stage, options_for_select(Setting.opportunity_stage.map{|s| [s.to_s.humanize, s]}, Setting.opportunity_default_stage), {}, class: 'form-control'
|
|
18
|
+
.col-md-4
|
|
19
|
+
.form-group
|
|
20
|
+
= f.label :account_category
|
|
21
|
+
= f.text_area :account_category, value: Setting.account_category.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
|
|
22
|
+
.form-group
|
|
23
|
+
= f.label :campaign_status
|
|
24
|
+
= f.text_area :campaign_status, value: Setting.campaign_status.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
|
|
25
|
+
.form-group
|
|
26
|
+
= f.label :lead_status
|
|
27
|
+
= f.text_area :lead_status, value: Setting.lead_status.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
|
|
28
|
+
.form-group
|
|
29
|
+
= f.label :lead_source
|
|
30
|
+
= f.text_area :lead_source, value: Setting.lead_source.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
|
|
31
|
+
.col-md-4
|
|
32
|
+
.form-group
|
|
33
|
+
= f.label :opportunity_stage
|
|
34
|
+
= f.text_area :opportunity_stage, value: Setting.opportunity_stage.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
|
|
35
|
+
.form-group
|
|
36
|
+
= f.label :task_category
|
|
37
|
+
= f.text_area :task_category, value: Setting.task_category.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
|
|
38
|
+
.form-group
|
|
39
|
+
= f.label :task_bucket
|
|
40
|
+
= f.text_area :task_bucket, value: Setting.task_bucket.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
|
|
41
|
+
.form-group
|
|
42
|
+
= f.label :task_completed
|
|
43
|
+
= f.text_area :task_completed, value: Setting.task_completed.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
|