fat_free_crm 0.21.0 → 0.22.1

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.

Potentially problematic release.


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

Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/fat_free_crm.js +3 -0
  3. data/app/assets/config/manifest.js +1 -3
  4. data/app/assets/javascripts/application.js.erb +1 -0
  5. data/app/assets/javascripts/crm.js.coffee +6 -3
  6. data/app/assets/javascripts/crm_select2.js.coffee +4 -1
  7. data/app/assets/javascripts/crm_tags.js.coffee +4 -4
  8. data/app/assets/javascripts/crm_validations.js.coffee +12 -0
  9. data/app/assets/stylesheets/bootstrap-custom.scss +3 -3
  10. data/app/assets/stylesheets/common.scss +9 -0
  11. data/app/assets/stylesheets/rails.scss +1 -1
  12. data/app/controllers/admin/field_groups_controller.rb +0 -2
  13. data/app/controllers/admin/fields_controller.rb +16 -13
  14. data/app/controllers/admin/tags_controller.rb +1 -1
  15. data/app/controllers/admin/users_controller.rb +1 -1
  16. data/app/controllers/application_controller.rb +11 -0
  17. data/app/controllers/comments_controller.rb +2 -0
  18. data/app/controllers/emails_controller.rb +2 -0
  19. data/app/controllers/entities/accounts_controller.rb +3 -1
  20. data/app/controllers/entities/campaigns_controller.rb +3 -1
  21. data/app/controllers/entities/contacts_controller.rb +3 -1
  22. data/app/controllers/entities/leads_controller.rb +4 -2
  23. data/app/controllers/entities/opportunities_controller.rb +3 -1
  24. data/app/controllers/entities_controller.rb +3 -1
  25. data/app/controllers/home_controller.rb +2 -0
  26. data/app/controllers/lists_controller.rb +7 -4
  27. data/app/controllers/tasks_controller.rb +3 -1
  28. data/app/controllers/users_controller.rb +2 -0
  29. data/app/helpers/application_helper.rb +4 -4
  30. data/app/helpers/opportunities_helper.rb +4 -2
  31. data/app/helpers/users_helper.rb +1 -1
  32. data/app/models/entities/campaign.rb +2 -2
  33. data/app/models/fields/custom_field.rb +2 -2
  34. data/app/models/fields/custom_field_pair.rb +6 -7
  35. data/app/models/fields/field.rb +1 -3
  36. data/app/models/list.rb +1 -1
  37. data/app/models/observers/entity_observer.rb +1 -1
  38. data/app/models/polymorphic/comment.rb +1 -1
  39. data/app/models/setting.rb +4 -5
  40. data/app/models/users/user.rb +1 -1
  41. data/app/views/accounts/_contact_info.html.haml +1 -1
  42. data/app/views/accounts/_top_section.html.haml +3 -3
  43. data/app/views/accounts/show.js.haml +1 -1
  44. data/app/views/accounts/update.js.haml +1 -0
  45. data/app/views/admin/custom_fields/_check_boxes_field.html.haml +4 -1
  46. data/app/views/admin/custom_fields/_date_pair_field.html.haml +1 -1
  47. data/app/views/campaigns/_top_section.html.haml +2 -2
  48. data/app/views/campaigns/show.js.haml +1 -1
  49. data/app/views/campaigns/update.js.haml +1 -0
  50. data/app/views/contacts/_top_section.html.haml +5 -5
  51. data/app/views/contacts/show.js.haml +2 -3
  52. data/app/views/contacts/update.js.haml +1 -0
  53. data/app/views/devise/sessions/new.html.haml +4 -5
  54. data/app/views/fields/_group.html.haml +5 -2
  55. data/app/views/fields/_group_table.html.haml +4 -5
  56. data/app/views/fields/_group_view.html.haml +4 -1
  57. data/app/views/fields/_sidebar_show.html.haml +5 -8
  58. data/app/views/fields/group.js.erb +3 -1
  59. data/app/views/layouts/application.html.haml +1 -1
  60. data/app/views/leads/_top_section.html.haml +4 -4
  61. data/app/views/leads/show.js.haml +1 -1
  62. data/app/views/leads/update.js.haml +1 -0
  63. data/app/views/opportunities/_top_section.html.haml +2 -2
  64. data/app/views/opportunities/show.js.haml +1 -1
  65. data/app/views/opportunities/update.js.haml +1 -0
  66. data/app/views/shared/_add_comment.html.haml +1 -1
  67. data/app/views/shared/_address.html.haml +1 -1
  68. data/app/views/tasks/_top_section.html.haml +4 -4
  69. data/bin/bundle +108 -2
  70. data/bin/rails +3 -3
  71. data/bin/rake +2 -2
  72. data/bin/setup +12 -15
  73. data/config/application.rb +9 -4
  74. data/config/boot.rb +3 -5
  75. data/config/cable.yml +10 -0
  76. data/config/database.yml +25 -0
  77. data/config/environment.rb +4 -3
  78. data/config/environments/development.rb +47 -14
  79. data/config/environments/production.rb +17 -15
  80. data/config/environments/test.rb +19 -9
  81. data/config/initializers/action_mailer.rb +1 -0
  82. data/config/initializers/content_security_policy.rb +21 -26
  83. data/config/initializers/custom_field_ransack_translations.rb +2 -1
  84. data/config/initializers/filter_parameter_logging.rb +6 -2
  85. data/config/initializers/inflections.rb +4 -4
  86. data/config/initializers/permissions_policy.rb +12 -0
  87. data/config/settings.default.yml +2 -1
  88. data/config/storage.yml +5 -5
  89. data/db/demo/field_groups.yml +2 -1
  90. data/db/migrate/20230526212613_convert_to_active_storage.rb +27 -11
  91. data/db/schema.rb +107 -105
  92. data/lib/fat_free_crm/callback.rb +2 -3
  93. data/lib/fat_free_crm/custom_fields.rb +1 -0
  94. data/lib/fat_free_crm/mail_processor/dropbox.rb +1 -1
  95. data/lib/fat_free_crm/version.rb +2 -2
  96. metadata +43 -19
  97. data/config/initializers/new_framework_defaults_6_0.rb +0 -46
@@ -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_now if item.assignee.present? && current_user.present? && can_send_email?
22
+ UserMailer.assigned_entity_notification(item, current_user).deliver_later if item.assignee.present? && 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
  commentable.subscribed_users.reject { |user_id| user_id == user.id }.each do |subscriber_id|
56
56
  if subscriber = User.find_by_id(subscriber_id)
57
- SubscriptionMailer.comment_notification(subscriber, self).deliver_now
57
+ SubscriptionMailer.comment_notification(subscriber, self).deliver_later
58
58
  end
59
59
  end
60
60
  end
@@ -58,13 +58,11 @@ class Setting < ActiveRecord::Base
58
58
  return cache[name] if cache.key?(name)
59
59
 
60
60
  # Check database
61
- if database_and_table_exists?
62
- if setting = find_by_name(name.to_s)
63
- return cache[name] = setting.value unless setting.value.nil?
64
- end
61
+ if database_and_table_exists? && (setting = find_by_name(name.to_s))&.value.present?
62
+ return cache[name] = setting.value
65
63
  end
66
64
  # Check YAML settings
67
- return cache[name] = yaml_settings[name] if yaml_settings.key?(name)
65
+ cache[name] = yaml_settings[name] if yaml_settings.key?(name)
68
66
  end
69
67
 
70
68
  # Set setting value
@@ -73,6 +71,7 @@ class Setting < ActiveRecord::Base
73
71
  raise ArgumentError, "name cannot be blank" if name.blank?
74
72
 
75
73
  return nil unless database_and_table_exists?
74
+
76
75
  setting = find_by_name(name.to_s) || new(name: name.to_s)
77
76
  setting.value = value
78
77
  setting.save
@@ -61,7 +61,7 @@ class User < ActiveRecord::Base
61
61
  has_many :opportunities
62
62
  has_many :assigned_opportunities, class_name: 'Opportunity', foreign_key: 'assigned_to'
63
63
  has_many :permissions, dependent: :destroy
64
- has_many :preferences, dependent: :destroy
64
+ has_many :preferences, class_name: 'Preference', dependent: :destroy
65
65
  has_many :lists
66
66
  has_and_belongs_to_many :groups
67
67
 
@@ -1,5 +1,5 @@
1
1
  - edit ||= false
2
- - collapsed = (@account.errors.empty? && session[:account_contact].nil?)
2
+ - collapsed = session[:account_contact].nil?
3
3
  = subtitle :account_contact, collapsed, t(:contact_info)
4
4
  .section
5
5
  %small#account_contact_intro{ hidden_if(!collapsed) }
@@ -2,9 +2,9 @@
2
2
  .section
3
3
  %table
4
4
  %tr
5
- %td(colspan="5")
5
+ %td{class: (@account.errors['name'].present? ? 'fieldWithErrors' : nil)}(colspan="5")
6
6
  .label.top.req #{t :name}:
7
- = f.text_field :name, autofocus: true, style: "width:500px"
7
+ = f.text_field :name, autofocus: true, style: "width:500px", required: "required"
8
8
  %tr
9
9
  %td
10
10
  .label #{t :assigned_to}:
@@ -12,7 +12,7 @@
12
12
  %td= spacer
13
13
  %td
14
14
  .label #{t :category}:
15
- = f.select :category, Setting.unroll(:account_category), { selected: (@account.category || "other").to_sym, include_blank: t(:other) }, { style: "width:160px", class: 'select2' }
15
+ = f.select :category, Setting.unroll(:account_category), { selected: (@account.category || "other").to_sym, include_blank: t(:other) }, { style: "width:160px", class: 'select2', placeholder: t(:other) }
16
16
  %td= spacer
17
17
  %td
18
18
  .label #{t :rating}:
@@ -1,5 +1,5 @@
1
1
  - entity_name = controller.controller_name.singularize.underscore #account
2
2
  - @entity = instance_variable_get("@#{entity_name}")
3
3
 
4
- $('#main').html('#{ j (render template: "#{entity_name.pluralize}/show.html", entity_name => @entity) }');
4
+ $('#main').html('#{ j (render template: "#{entity_name.pluralize}/show", formats: [:html], entity_name => @entity) }');
5
5
  = raw generate_js_for_popups(@entity, :tasks, :contacts, :opportunities)
@@ -7,6 +7,7 @@
7
7
  crm.flip_form('edit_#{entity_name}');
8
8
  crm.set_title('edit_#{entity_name}', '#{j @entity.name}');
9
9
  = refresh_sidebar(:show)
10
+ $('#summary').html('#{ j (render partial: "#{entity_name.pluralize}/sidebar_show", entity_name => @entity) }');
10
11
  - else
11
12
  $('##{id}').replaceWith('#{ j render(partial: entity_name, collection: [ @entity ]) }');
12
13
  $('##{id}').effect("highlight", { duration:1500 });
@@ -1,6 +1,9 @@
1
1
  %div
2
2
  .label.top.req
3
3
  = "Select Options (pipe separated):"
4
- = f.text_field :collection_string, class: 'field_collection_string', size: 78
4
+ = f.text_field :collection_string, class: 'field_collection_string', size: 78, placeholder: "Option 1|Option 2|Option 3"
5
5
 
6
6
  = render partial: 'admin/custom_fields/base_field', locals: {f: f}
7
+
8
+ - if f.object.new_record?
9
+ .info2 After saving, you must restart all instances of the Rails server to apply column serialization.
@@ -1,5 +1,5 @@
1
1
  - field1 = f.object || CustomFieldPair.new
2
- - field2 = f.object.respond_to?(:paired_with) ? field1.paired_with : CustomFieldPair.new
2
+ - field2 = (field1.pair || CustomFieldPair.new)
3
3
 
4
4
  %table{class: :pairs}
5
5
  = fields_for("pair[0]", field1) do |first|
@@ -2,9 +2,9 @@
2
2
  .section
3
3
  %table
4
4
  %tr
5
- %td(colspan="5")
5
+ %td{class: (@campaign.errors['name'].present? ? 'fieldWithErrors' : nil)}(colspan="5")
6
6
  .label.top.req #{t :name}:
7
- = f.text_field :name, autofocus: true, style: "width:500px"
7
+ = f.text_field :name, autofocus: true, style: "width:500px", required: "required"
8
8
  %tr
9
9
  %td
10
10
  .label #{t :start_date}:
@@ -1,5 +1,5 @@
1
1
  - entity_name = controller.controller_name.singularize.underscore #account
2
2
  - @entity = instance_variable_get("@#{entity_name}")
3
3
 
4
- $('#main').html('#{ j (render template: "#{entity_name.pluralize}/show.html", entity_name => @entity) }');
4
+ $('#main').html('#{ j (render template: "#{entity_name.pluralize}/show", formats: [:html], entity_name => @entity) }');
5
5
  = raw generate_js_for_popups(@entity, :tasks, :leads, :opportunities)
@@ -7,6 +7,7 @@
7
7
  crm.flip_form('edit_#{entity_name}');
8
8
  crm.set_title('edit_#{entity_name}', '#{h @entity.name}');
9
9
  = refresh_sidebar(:show)
10
+ $('#summary').html('#{ j (render partial: "#{entity_name.pluralize}/sidebar_show", entity_name => @entity) }');
10
11
  - else
11
12
  $('##{id}').replaceWith('#{ j render(partial: entity_name, collection: [ @entity ]) }');
12
13
  $('##{id}').effect("highlight", { duration:1500 });
@@ -3,13 +3,13 @@
3
3
  .section
4
4
  %table
5
5
  %tr
6
- %td
7
- .label.top.req{ class: "#{Setting.require_first_names ? 'req' : nil}" } #{t :first_name}:
8
- = f.text_field :first_name, autofocus: true
6
+ %td{class: (@contact.errors['first_name'].present? ? 'fieldWithErrors' : nil)}
7
+ .label.top{ class: "#{Setting.require_first_names ? 'req' : nil}" } #{t :first_name}:
8
+ = f.text_field :first_name, autofocus: true, required: (Setting.require_first_names ? "required" : nil)
9
9
  %td= spacer
10
- %td
10
+ %td{class: (@contact.errors['last_name'].present? ? 'fieldWithErrors' : nil)}
11
11
  .label.top{ class: "#{Setting.require_last_names ? 'req' : nil}" } #{t :last_name}:
12
- = f.text_field :last_name
12
+ = f.text_field :last_name, required: (Setting.require_last_names ? "required" : nil)
13
13
  %tr
14
14
  %td
15
15
  .label #{t :email}:
@@ -1,5 +1,4 @@
1
- - entity_name = controller.controller_name.singularize.underscore #account
1
+ - entity_name = controller.controller_name.singularize.underscore
2
2
  - @entity = instance_variable_get("@#{entity_name}")
3
-
4
- $('#main').html('#{ j (render template: "#{entity_name.pluralize}/show.html", entity_name => @entity) }');
3
+ $('#main').html('#{ j (render template: "#{entity_name.pluralize}/show", formats: [:html], entity_name => @entity) }');
5
4
  = raw generate_js_for_popups(@entity, :tasks, :opportunities)
@@ -8,6 +8,7 @@
8
8
  crm.flip_form('edit_#{entity_name}');
9
9
  crm.set_title('edit_#{entity_name}', '#{h @entity.full_name}');
10
10
  = refresh_sidebar(:show)
11
+ $('#summary').html('#{ j (render partial: "#{entity_name.pluralize}/sidebar_show", entity_name => @entity) }');
11
12
  - else
12
13
  $('##{id}').replaceWith('#{ j render(partial: entity_name, collection: [ @entity ]) }');
13
14
  $('##{id}').effect("highlight", { duration:1500 });
@@ -23,10 +23,9 @@
23
23
  .label= t(:password)
24
24
  = f.input_field :password
25
25
 
26
- %div(style="margin-left:12px")
26
+ .section
27
27
  = f.input :remember_me, as: :boolean, inline_label: t('remember_me')
28
- %br
29
- .buttonbar
30
- = f.submit t(:login)
31
- = t(:or)
28
+ = f.submit t(:login), class: 'btn btn-primary'
29
+
30
+ .section
32
31
  = link_to t(:forgot_password) + '?', new_password_path(resource_name)
@@ -1,6 +1,9 @@
1
1
  - if field_group.name != 'custom_fields'
2
- -# start a new section
3
- - collapsed = session[field_group.key].nil?
2
+ - # Ensure field groups containing validation errors are expanded
3
+ - required_field_names = field_group.fields.select(&:required?).map(&:name)
4
+ - fields_with_errors = f.object.errors.map{|e| e.attribute.to_s}
5
+ - force_open = (required_field_names & fields_with_errors).any?
6
+ - collapsed = session[field_group.key].nil? && !force_open
4
7
  %div{ id: "#{field_group.key}_container", :"data-tag" => field_group.tag.try(:name) }
5
8
  = subtitle field_group.key, collapsed, t(field_group.name, default: field_group.label)
6
9
  .section
@@ -2,12 +2,11 @@
2
2
  - field_group.fields.without_pairs.in_groups_of(2, false) do |group|
3
3
  %tr
4
4
  - group.each_with_index do |field, i|
5
- %td
5
+ %td{class: (f.object.errors[field.name].present? ? 'fieldWithErrors' : nil)}
6
6
  - if field.hint.present?
7
7
  = image_tag "info_tiny.png", title: field.hint, class: "tooltip-icon"
8
- - if field.as == 'check_boxes'
9
- - value = f.object.send(field.name)
10
- - checked = YAML.load(value.to_s)
11
- = f.input field.name, field.input_options.merge(checked: checked)
8
+ .label.top{class: (field.required? ? 'req': nil)}
9
+ = "#{field.label}:"
10
+ = f.input_field field.name, field.input_options
12
11
  - if i == 0
13
12
  %td= spacer
@@ -4,7 +4,10 @@
4
4
  %tr
5
5
  - group.each do |field|
6
6
  = col(field.label, (i == groups.size - 1) ? :last : nil) do
7
- = field.render_value(entity)
7
+ - if field.as == "text"
8
+ = simple_format(field.render_value(entity))
9
+ - else
10
+ = field.render_value(entity)
8
11
  - if group.size == 1
9
12
  %th.last
10
13
  %td.last
@@ -1,9 +1,6 @@
1
1
  - asset.field_groups.each do |field_group|
2
- - fg = field_group.fields.without_pairs
3
- - if fg.select{|f| asset.send(f.name).present? }.any?
4
- %div
5
- - unless field_group.name == 'custom_fields'
6
- .caption #{field_group.label_i18n}
7
- - fg.each do |field|
8
- - if (value = field.render_value(asset)).present?
9
- == #{field.label}:<br /> <b>#{truncate(value, length: 35)}</b><br />
2
+ - fields = field_group.fields.without_pairs
3
+ - if fields.select{|f| asset.send(f.name).present? }.any?
4
+ - unless field_group.name == 'custom_fields'
5
+ .caption #{field_group.label_i18n}
6
+ = render("fields/group_view", fields: fields, entity: asset) unless fields.nil?
@@ -1,3 +1,5 @@
1
1
  <%= simple_fields_for(@asset) do |f| %>
2
- $('#field_groups').append('<%= j render(partial: 'fields/group', locals: {f: f, field_group: @field_group, fields: @field_group.fields}) %>')
2
+ <% @field_groups.each do |field_group| %>
3
+ $('#field_groups').append('<%= j render(partial: 'fields/group', locals: {f: f, field_group: field_group, fields: field_group.fields}) %>')
4
+ <% end %>
3
5
  <% end %>
@@ -42,7 +42,7 @@
42
42
  = render "layouts/footer"
43
43
 
44
44
  %script{type: "text/javascript"}
45
- = "crm.base_url = '#{Setting.base_url}';" unless Setting.base_url.blank?
45
+ = "crm.base_url = '#{h Setting.base_url}';".html_safe unless Setting.base_url.blank?
46
46
  = get_browser_timezone_offset
47
47
  = content_for :javascript_epilogue
48
48
  = hook(:javascript_epilogue, self)
@@ -3,13 +3,13 @@
3
3
  .section
4
4
  %table
5
5
  %tr
6
- %td
6
+ %td{ class: (@lead.errors['first_name'].present? ? 'fieldWithErrors' : nil)}
7
7
  .label.top{ class: "#{Setting.require_first_names ? 'req' : nil}" } #{t :first_name}:
8
- = f.text_field :first_name, autofocus: true
8
+ = f.text_field :first_name, autofocus: true, required: (Setting.require_first_names ? "required" : nil)
9
9
  %td= spacer
10
- %td
10
+ %td{ class: (@lead.errors['last_name'].present? ? 'fieldWithErrors' : nil)}
11
11
  .label.top{ class: "#{Setting.require_last_names ? 'req' : nil}" } #{t :last_name}:
12
- = f.text_field :last_name
12
+ = f.text_field :last_name, required: (Setting.require_last_names ? "required" : nil)
13
13
  %tr
14
14
  %td
15
15
  .label #{t :email}:
@@ -1,5 +1,5 @@
1
1
  - entity_name = controller.controller_name.singularize.underscore #account
2
2
  - @entity = instance_variable_get("@#{entity_name}")
3
3
 
4
- $('#main').html('#{ j (render template: "#{entity_name.pluralize}/show.html", entity_name => @entity) }');
4
+ $('#main').html('#{ j (render template: "#{entity_name.pluralize}/show", formats: [:html], entity_name => @entity) }');
5
5
  = raw generate_js_for_popups(@entity, :tasks)
@@ -7,6 +7,7 @@
7
7
  crm.flip_form('edit_#{entity_name}');
8
8
  crm.set_title('edit_#{entity_name}', '#{h @entity.full_name}');
9
9
  = refresh_sidebar(:show)
10
+ $('#summary').html('#{ j (render partial: "#{entity_name.pluralize}/sidebar_show", entity_name => @entity) }');
10
11
  - else
11
12
  $('##{id}').replaceWith('#{ j render(partial: entity_name, collection: [ @entity ]) }');
12
13
  $('##{id}').effect("highlight", { duration:1500 });
@@ -2,9 +2,9 @@
2
2
  .section
3
3
  %table
4
4
  %tr
5
- %td
5
+ %td{class: (@opportunity.errors['name'].present? ? 'fieldWithErrors' : nil)}
6
6
  .label.req.top #{t :name}:
7
- = f.text_field :name, autofocus: true, style: "width:325px"
7
+ = f.text_field :name, autofocus: true, style: "width:325px", required: "required"
8
8
  %td= spacer
9
9
  %td
10
10
  .label.req.top #{t :stage}:
@@ -1,5 +1,5 @@
1
1
  - entity_name = controller.controller_name.singularize.underscore #account
2
2
  - @entity = instance_variable_get("@#{entity_name}")
3
3
 
4
- $('#main').html('#{ j (render template: "#{entity_name.pluralize}/show.html", entity_name => @entity) }');
4
+ $('#main').html('#{ j (render template: "#{entity_name.pluralize}/show", formats: [:html], entity_name => @entity) }');
5
5
  = raw generate_js_for_popups(@entity, :tasks, :contacts)
@@ -7,6 +7,7 @@
7
7
  crm.flip_form('edit_#{entity_name}');
8
8
  crm.set_title('edit_#{entity_name}', '#{h @entity.name}');
9
9
  = refresh_sidebar(:show)
10
+ $('#summary').html('#{ j (render partial: "#{entity_name.pluralize}/sidebar_show", entity_name => @entity) }');
10
11
  - else
11
12
  $('##{id}').replaceWith('#{ j render(partial: entity_name, collection: [ @entity ]) }');
12
13
  $('##{id}').effect("highlight", { duration:1500 });
@@ -1,5 +1,5 @@
1
1
  - edit ||= false
2
- - collapsed = @comment_body.nil? && f.object.errors.empty?
2
+ - collapsed = session[:comment].nil?
3
3
  = subtitle :comment, collapsed, t(:comment)
4
4
  .section
5
5
  %small#comment_intro{ hidden_if(!collapsed) }
@@ -41,4 +41,4 @@
41
41
  = address_field(a, :zipcode, "width:80px;")
42
42
  %td= spacer
43
43
  %td
44
- = a.country_select(:country, priority_countries: priority_countries, include_blank: "", :"data-placeholder" => t(:select_a_country), style: "width:150px; margin-top:6px", class: 'select2')
44
+ = a.country_select(:country, {priority_countries: priority_countries, include_blank: true}, {data: { placeholder: t(:select_a_country)}, class: 'select2'})
@@ -3,10 +3,10 @@
3
3
  %tr
4
4
  %td(colspan="5")
5
5
  .label.top.req #{t :name}:
6
- = f.text_field :name, autofocus: true, style: "width:500px"
6
+ = f.text_field :name, autofocus: true, style: "width:500px", required: "required"
7
7
  %tr
8
8
  %td
9
- .label.req #{t :due}:
9
+ .label #{t :due}:
10
10
  - bucket = (params[:bucket].blank? ? @task.bucket : params[:bucket]) || "due_asap"
11
11
  - with_time = Setting.task_calendar_with_time
12
12
  - if @task.bucket != "specific_time"
@@ -18,11 +18,11 @@
18
18
  = f.text_field :calendar, value: f.object.due_at.strftime(fmt), style: "width:160px;", autocomplete: :off, class: (with_time ? 'datetime' : 'date')
19
19
  %td= spacer
20
20
  %td
21
- .label.req #{t :assign_to}:
21
+ .label #{t :assign_to}:
22
22
  = user_select(:task, all_users, current_user)
23
23
  %td= spacer
24
24
  %td
25
- .label.req #{t :category}:
25
+ .label #{t :category}:
26
26
  = f.select :category, @category, { selected: @task.category.blank? ? nil : @task.category.to_sym, include_blank: t(:select_blank) }, { style: "width:160px", class: 'select2' }
27
27
 
28
28
  - if Setting.background_info && Setting.background_info.include?(:task)
data/bin/bundle CHANGED
@@ -1,5 +1,111 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
5
- load Gem.bin_path('bundler', 'bundle')
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'bundle' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "rubygems"
12
+
13
+ m = Module.new do
14
+ module_function
15
+
16
+ def invoked_as_script?
17
+ File.expand_path($PROGRAM_NAME) == File.expand_path(__FILE__)
18
+ end
19
+
20
+ def env_var_version
21
+ ENV["BUNDLER_VERSION"]
22
+ end
23
+
24
+ def cli_arg_version
25
+ return unless invoked_as_script? # don't want to hijack other binstubs
26
+ return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27
+
28
+ bundler_version = nil
29
+ update_index = nil
30
+ ARGV.each_with_index do |a, i|
31
+ bundler_version = a if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
32
+ next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
33
+
34
+ bundler_version = Regexp.last_match(1)
35
+ update_index = i
36
+ end
37
+ bundler_version
38
+ end
39
+
40
+ def gemfile
41
+ gemfile = ENV["BUNDLE_GEMFILE"]
42
+ return gemfile if gemfile && !gemfile.empty?
43
+
44
+ File.expand_path("../Gemfile", __dir__)
45
+ end
46
+
47
+ def lockfile
48
+ lockfile =
49
+ case File.basename(gemfile)
50
+ when "gems.rb" then gemfile.sub(/\.rb$/, ".locked")
51
+ else "#{gemfile}.lock"
52
+ end
53
+ File.expand_path(lockfile)
54
+ end
55
+
56
+ def lockfile_version
57
+ return unless File.file?(lockfile)
58
+
59
+ lockfile_contents = File.read(lockfile)
60
+ return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
61
+
62
+ Regexp.last_match(1)
63
+ end
64
+
65
+ def bundler_requirement
66
+ @bundler_requirement ||=
67
+ env_var_version ||
68
+ cli_arg_version ||
69
+ bundler_requirement_for(lockfile_version)
70
+ end
71
+
72
+ def bundler_requirement_for(version)
73
+ return "#{Gem::Requirement.default}.a" unless version
74
+
75
+ bundler_gem_version = Gem::Version.new(version)
76
+
77
+ bundler_gem_version.approximate_recommendation
78
+ end
79
+
80
+ def load_bundler!
81
+ ENV["BUNDLE_GEMFILE"] ||= gemfile
82
+
83
+ activate_bundler
84
+ end
85
+
86
+ def activate_bundler
87
+ gem_error = activation_error_handling do
88
+ gem "bundler", bundler_requirement
89
+ end
90
+ return if gem_error.nil?
91
+
92
+ require_error = activation_error_handling do
93
+ require "bundler/version"
94
+ end
95
+ return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
96
+
97
+ warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
98
+ exit 42
99
+ end
100
+
101
+ def activation_error_handling
102
+ yield
103
+ nil
104
+ rescue StandardError, LoadError => e
105
+ e
106
+ end
107
+ end
108
+
109
+ m.load_bundler!
110
+
111
+ load Gem.bin_path("bundler", "bundle") if m.invoked_as_script?
data/bin/rails CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- APP_PATH = File.expand_path('../config/application', __dir__)
5
- require_relative '../config/boot'
6
- require 'rails/commands'
4
+ APP_PATH = File.expand_path("../config/application", __dir__)
5
+ require_relative "../config/boot"
6
+ require "rails/commands"
data/bin/rake CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative '../config/boot'
5
- require 'rake'
4
+ require_relative "../config/boot"
5
+ require "rake"
6
6
  Rake.application.run
data/bin/setup CHANGED
@@ -1,38 +1,35 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'fileutils'
4
+ require "fileutils"
5
5
 
6
6
  # path to your application root.
7
- APP_ROOT = File.expand_path('..', __dir__)
7
+ APP_ROOT = File.expand_path("..", __dir__)
8
8
 
9
9
  def system!(*args)
10
10
  system(*args) || abort("\n== Command #{args} failed ==")
11
11
  end
12
12
 
13
13
  FileUtils.chdir APP_ROOT do
14
- # This script is a way to setup or update your development environment automatically.
15
- # This script is idempotent, so that you can run it at anytime and get an expectable outcome.
14
+ # This script is a way to set up or update your development environment automatically.
15
+ # This script is idempotent, so that you can run it at any time and get an expectable outcome.
16
16
  # Add necessary setup steps to this file.
17
17
 
18
- puts '== Installing dependencies =='
19
- system! 'gem install bundler --conservative'
20
- system('bundle check') || system!('bundle install')
21
-
22
- # Install JavaScript dependencies
23
- # system('bin/yarn')
18
+ puts "== Installing dependencies =="
19
+ system! "gem install bundler --conservative"
20
+ system("bundle check") || system!("bundle install")
24
21
 
25
22
  # puts "\n== Copying sample files =="
26
- # unless File.exist?('config/database.yml')
27
- # FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
23
+ # unless File.exist?("config/database.yml")
24
+ # FileUtils.cp "config/database.yml.sample", "config/database.yml"
28
25
  # end
29
26
 
30
27
  puts "\n== Preparing database =="
31
- system! 'bin/rails db:prepare'
28
+ system! "bin/rails db:prepare"
32
29
 
33
30
  puts "\n== Removing old logs and tempfiles =="
34
- system! 'bin/rails log:clear tmp:clear'
31
+ system! "bin/rails log:clear tmp:clear"
35
32
 
36
33
  puts "\n== Restarting application server =="
37
- system! 'bin/rails restart'
34
+ system! "bin/rails restart"
38
35
  end
@@ -30,11 +30,15 @@ require 'fat_free_crm/gem_ext/rails/engine'
30
30
  module FatFreeCRM
31
31
  class Application < Rails::Application
32
32
  # Initialize configuration defaults for originally generated Rails version.
33
- config.load_defaults 6.0
33
+ config.load_defaults 7.0
34
34
 
35
- # Settings in config/environments/* take precedence over those specified here.
36
- # Application configuration should go into files in config/initializers
37
- # -- all .rb files in that directory are automatically loaded.
35
+ # Configuration for the application, engines, and railties goes here.
36
+ #
37
+ # These settings can be overridden in specific environments using the files
38
+ # in config/environments, which are processed later.
39
+ #
40
+ # config.time_zone = "Central Time (US & Canada)"
41
+ # config.eager_load_paths << Rails.root.join("extras")
38
42
 
39
43
  # Models are organized in sub-directories
40
44
  config.autoload_paths += Dir[Rails.root.join("app/models/**")] +
@@ -78,6 +82,7 @@ module FatFreeCRM
78
82
  config.active_record.use_yaml_unsafe_load = false
79
83
  config.active_record.yaml_column_permitted_classes = [
80
84
  ::ActiveRecord::Type::Time::Value,
85
+ ::ActiveSupport::HashWithIndifferentAccess, # for Field#settings serialization see app/models/fields/field.rb
81
86
  ::ActiveSupport::TimeWithZone,
82
87
  ::ActiveSupport::TimeZone,
83
88
  ::BigDecimal,
data/config/boot.rb CHANGED
@@ -6,9 +6,7 @@
6
6
  # See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
7
7
  #------------------------------------------------------------------------------
8
8
 
9
- # Set up gems listed in the Gemfile.
10
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
9
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
11
10
 
12
- require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
13
-
14
- require 'bootsnap/setup'
11
+ require "bundler/setup" # Set up gems listed in the Gemfile.
12
+ require "bootsnap/setup" # Speed up boot time by caching expensive operations.