bmc 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/README.md +28 -0
- data/Rakefile +41 -0
- data/app/assets/config/bmc_manifest.js +2 -0
- data/app/controllers/bmc/application_controller.rb +3 -0
- data/app/controllers/bmc/filters_controller.rb +38 -0
- data/app/controllers/concerns/bmc/api_controller_concern.rb +69 -0
- data/app/controllers/concerns/bmc/back_url_concern.rb +26 -0
- data/app/emails/bmc/email.rb +113 -0
- data/app/filters/bmc/filter/by_date.rb +6 -0
- data/app/filters/bmc/filter/by_date_begin.rb +7 -0
- data/app/filters/bmc/filter/by_date_end.rb +7 -0
- data/app/filters/bmc/filter/by_date_or_datetime_period.rb +56 -0
- data/app/filters/bmc/filter/by_date_period.rb +5 -0
- data/app/filters/bmc/filter/by_datetime_period.rb +5 -0
- data/app/filters/bmc/filter/by_key_value.rb +27 -0
- data/app/filters/bmc/filter/by_key_values.rb +13 -0
- data/app/filters/bmc/filter.rb +86 -0
- data/app/forms/bmc/mini_form_object.rb +29 -0
- data/app/helpers/bmc/all_helpers.rb +15 -0
- data/app/helpers/bmc/bootstrap_helper.rb +63 -0
- data/app/helpers/bmc/button_helper.rb +150 -0
- data/app/helpers/bmc/filters_helper.rb +16 -0
- data/app/helpers/bmc/font_awesome_helper.rb +67 -0
- data/app/helpers/bmc/form_helper.rb +24 -0
- data/app/helpers/bmc/i18n_helper.rb +5 -0
- data/app/helpers/bmc/link_helper.rb +42 -0
- data/app/helpers/bmc/pagination_helper.rb +22 -0
- data/app/helpers/bmc/routes_helper.rb +24 -0
- data/app/helpers/bmc/sorting_helper.rb +55 -0
- data/app/helpers/bmc/text_helper.rb +127 -0
- data/app/helpers/h.rb +3 -0
- data/app/jobs/concerns/bmc/setup_job_concern.rb +35 -0
- data/app/libs/bmc/collection_update.rb +38 -0
- data/app/libs/bmc/fcm/notifier.rb +32 -0
- data/app/libs/bmc/fcm/request.rb +54 -0
- data/app/libs/bmc/mini_model_serializer/serialize.rb +30 -0
- data/app/libs/bmc/mini_model_serializer/serializer.rb +23 -0
- data/app/libs/bmc/monkey.rb +39 -0
- data/app/libs/bmc/sortable_uuid_generator.rb +11 -0
- data/app/libs/bmc/token_generator.rb +37 -0
- data/app/mailers/bmc/application_mailer.rb +2 -0
- data/app/mailers/bmc/generic_mailer.rb +9 -0
- data/app/models/concerns/bmc/active_record_uuid_concern.rb +17 -0
- data/app/models/concerns/bmc/default_values_concern.rb +14 -0
- data/app/models/concerns/bmc/model_i18n.rb +56 -0
- data/app/models/concerns/bmc/model_to_s.rb +9 -0
- data/app/models/concerns/bmc/pluck_distinct.rb +13 -0
- data/app/models/concerns/bmc/pluck_to_hash.rb +9 -0
- data/app/models/concerns/bmc/polymorphic_id.rb +37 -0
- data/app/models/concerns/bmc/search.rb +28 -0
- data/app/models/concerns/bmc/timestamp_helpers.rb +17 -0
- data/app/serializers/bmc/serializers/base.rb +63 -0
- data/app/serializers/bmc/serializers/xlsx.rb +38 -0
- data/app/serializers/bmc/serializers.rb +2 -0
- data/app/sms/bmc/sms/application_sms.rb +42 -0
- data/app/sms/bmc/sms/message.rb +25 -0
- data/app/sms/bmc/sms/strategies/amazon_sns.rb +48 -0
- data/app/sms/bmc/sms/strategies/base.rb +15 -0
- data/app/sms/bmc/sms/strategies/test.rb +10 -0
- data/app/sms/bmc/sms.rb +35 -0
- data/app/sorters/bmc/sorter.rb +26 -0
- data/app/views/bmc/search/_form.html.slim +1 -0
- data/app/views/bmc/search/_form_bs3.html.slim +13 -0
- data/app/views/bmc/search/_form_bs4.html.slim +9 -0
- data/app/views/bmc/search/_form_bs5.html.slim +8 -0
- data/app/views/kaminari/bootstrap4/_first_page.html.slim +2 -0
- data/app/views/kaminari/bootstrap4/_gap.html.slim +2 -0
- data/app/views/kaminari/bootstrap4/_last_page.html.slim +2 -0
- data/app/views/kaminari/bootstrap4/_next_page.html.slim +2 -0
- data/app/views/kaminari/bootstrap4/_page.html.slim +6 -0
- data/app/views/kaminari/bootstrap4/_paginator.html.slim +12 -0
- data/app/views/kaminari/bootstrap4/_prev_page.html.slim +2 -0
- data/config/locales/actions.en.yml +76 -0
- data/config/locales/actions.fr.yml +76 -0
- data/config/locales/attributes.en.yml +108 -0
- data/config/locales/attributes.fr.yml +108 -0
- data/config/locales/common.en.yml +39 -0
- data/config/locales/common.fr.yml +39 -0
- data/config/locales/dates.fr.yml +8 -0
- data/config/locales/errors.en.yml +5 -0
- data/config/locales/errors.fr.yml +5 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20000101000000_enable_bmc_extensions.rb +7 -0
- data/lib/bmc/active_model_custom_error_messages.rb +23 -0
- data/lib/bmc/active_model_type_cast.rb +52 -0
- data/lib/bmc/config.rb +19 -0
- data/lib/bmc/core_and_rails_ext.rb +3 -0
- data/lib/bmc/engine.rb +36 -0
- data/lib/bmc/engine_file.rb +10 -0
- data/lib/bmc/errors_middleware.rb +50 -0
- data/lib/bmc/form_back_url.rb +18 -0
- data/lib/bmc/version.rb +3 -0
- data/lib/bmc.rb +5 -0
- data/lib/tasks/bmc_tasks.rake +4 -0
- metadata +236 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
module BMC::ButtonHelper
|
2
|
+
class << self
|
3
|
+
attr_writer :default_size, :default_style
|
4
|
+
|
5
|
+
def default_size
|
6
|
+
@default_size ||= :sm
|
7
|
+
end
|
8
|
+
|
9
|
+
def default_style
|
10
|
+
@default_style ||= :outline_primary
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def bs_button( # rubocop:disable Metrics/ParameterLists
|
15
|
+
url,
|
16
|
+
method: :get,
|
17
|
+
confirm: nil,
|
18
|
+
text: nil,
|
19
|
+
icon:,
|
20
|
+
action: nil,
|
21
|
+
title: text,
|
22
|
+
btn_size: BMC::ButtonHelper.default_size,
|
23
|
+
btn_style: BMC::ButtonHelper.default_style,
|
24
|
+
add_class: [],
|
25
|
+
**options
|
26
|
+
)
|
27
|
+
text = ta(action) if text.nil? && action
|
28
|
+
|
29
|
+
content = icon(icon).concat(" ").concat(tag.span(text, class: "text"))
|
30
|
+
|
31
|
+
if method != :get
|
32
|
+
options[:method] = method
|
33
|
+
confirm = true if confirm.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
unless options[:class]
|
37
|
+
options[:class] = [
|
38
|
+
"btn",
|
39
|
+
"btn-#{btn_size}",
|
40
|
+
"btn-#{btn_style.to_s.tr('_', '-')}",
|
41
|
+
("link-#{action}" if action),
|
42
|
+
*add_class,
|
43
|
+
]
|
44
|
+
end
|
45
|
+
|
46
|
+
options[:title] = title
|
47
|
+
|
48
|
+
unless confirm.nil?
|
49
|
+
confirm = ta(:confirm) if confirm == true
|
50
|
+
options.deep_merge!(data: {confirm: confirm})
|
51
|
+
end
|
52
|
+
|
53
|
+
link_to(content, url, options.sort.to_h)
|
54
|
+
end
|
55
|
+
|
56
|
+
def new_button(url = url_for(action: :new), **options)
|
57
|
+
options = {
|
58
|
+
:icon => :plus,
|
59
|
+
:action => :new,
|
60
|
+
:btn_style => :success,
|
61
|
+
}.merge(options)
|
62
|
+
|
63
|
+
bs_button(url, **options)
|
64
|
+
end
|
65
|
+
|
66
|
+
def read_button(url, **options)
|
67
|
+
options = {
|
68
|
+
:icon => :info_circle,
|
69
|
+
:action => :read,
|
70
|
+
}.merge(options)
|
71
|
+
|
72
|
+
bs_button(url, **options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def edit_button(url, **options)
|
76
|
+
options = {
|
77
|
+
:icon => :pencil_alt,
|
78
|
+
:action => :edit,
|
79
|
+
}.merge(options)
|
80
|
+
|
81
|
+
bs_button(url, **options)
|
82
|
+
end
|
83
|
+
|
84
|
+
def delete_button(url, **options)
|
85
|
+
options = {
|
86
|
+
:icon => :trash,
|
87
|
+
:action => :delete,
|
88
|
+
:btn_style => :danger,
|
89
|
+
:method => :delete,
|
90
|
+
}.merge(options)
|
91
|
+
|
92
|
+
bs_button(url, **options)
|
93
|
+
end
|
94
|
+
|
95
|
+
def print_button(**options)
|
96
|
+
options = {
|
97
|
+
:icon => :print,
|
98
|
+
:action => :print,
|
99
|
+
:onclick => "print(); return false;",
|
100
|
+
}.merge(options)
|
101
|
+
|
102
|
+
bs_button("#", **options)
|
103
|
+
end
|
104
|
+
|
105
|
+
def download_button(url, **options)
|
106
|
+
options = {
|
107
|
+
:icon => :cloud_download_alt,
|
108
|
+
:action => :download,
|
109
|
+
:target => :_blank,
|
110
|
+
}.merge(options)
|
111
|
+
|
112
|
+
bs_button(url, **options)
|
113
|
+
end
|
114
|
+
|
115
|
+
def export_button(url, **options)
|
116
|
+
ext = url.split(".").last
|
117
|
+
|
118
|
+
if ext.length <= 4
|
119
|
+
text = "#{ta(:export)} (#{ext.upcase})"
|
120
|
+
else
|
121
|
+
text = ta(:export)
|
122
|
+
end
|
123
|
+
|
124
|
+
options = {
|
125
|
+
:icon => :download,
|
126
|
+
:action => :export,
|
127
|
+
:text => text,
|
128
|
+
}.merge(options)
|
129
|
+
|
130
|
+
download_button(url, **options)
|
131
|
+
end
|
132
|
+
|
133
|
+
def import_button(url, **options)
|
134
|
+
options = {
|
135
|
+
:icon => :upload,
|
136
|
+
:action => :import,
|
137
|
+
}.merge(options)
|
138
|
+
|
139
|
+
bs_button(url, **options)
|
140
|
+
end
|
141
|
+
|
142
|
+
def copy_button(url, **options)
|
143
|
+
options = {
|
144
|
+
:icon => :copy,
|
145
|
+
:action => :copy,
|
146
|
+
}.merge(options)
|
147
|
+
|
148
|
+
bs_button(url, **options)
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module BMC::FiltersHelper
|
2
|
+
def bmc_time_periods_for_select
|
3
|
+
{
|
4
|
+
t("time_periods.all_time") => "",
|
5
|
+
t("time_periods.today") => "today",
|
6
|
+
t("time_periods.yesterday") => "yesterday",
|
7
|
+
t("time_periods.this_week") => "this_week",
|
8
|
+
t("time_periods.last_week") => "last_week",
|
9
|
+
t("time_periods.this_month") => "this_month",
|
10
|
+
t("time_periods.last_month") => "last_month",
|
11
|
+
t("time_periods.this_year") => "this_year",
|
12
|
+
t("time_periods.last_year") => "last_year",
|
13
|
+
t("time_periods.custom_date") => "custom_date",
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module BMC::FontAwesomeHelper
|
2
|
+
def icon(id, fa_style: nil, size: nil, spin: false, **options)
|
3
|
+
id = id.to_s.tr("_", "-").to_sym
|
4
|
+
|
5
|
+
if fa_style.nil?
|
6
|
+
fa_style = BMC::FontAwesomeHelper.default_fa_style_for_id(id)
|
7
|
+
end
|
8
|
+
|
9
|
+
css_classes = options.delete(:class).to_s.split
|
10
|
+
css_classes << "icon"
|
11
|
+
css_classes << "fa-#{id}"
|
12
|
+
css_classes << "fa#{fa_style.to_s[0]}"
|
13
|
+
css_classes << "fa-#{size}" if size
|
14
|
+
css_classes << "fa-spin" if spin
|
15
|
+
|
16
|
+
attributes = options.merge(class: css_classes.sort.join(" ")).sort.to_h
|
17
|
+
|
18
|
+
tag.span(**attributes)
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def database
|
23
|
+
@database ||= YAML.safe_load(database_yml).deep_symbolize_keys
|
24
|
+
end
|
25
|
+
|
26
|
+
def database_path
|
27
|
+
Rails.root.join("tmp", "fa_database_#{version}.yml")
|
28
|
+
end
|
29
|
+
|
30
|
+
def database_yml
|
31
|
+
download_database! unless File.size?(database_path)
|
32
|
+
File.read(database_path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def database_url
|
36
|
+
short_version = version.split(".")[0, 3].join(".") # 1.20.14.2 => 1.20.14
|
37
|
+
url = "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/#{short_version}/metadata/icons.yml"
|
38
|
+
|
39
|
+
if Gem::Version.new(short_version) < Gem::Version.new("5.6.0")
|
40
|
+
url = url.gsub("/metadata", "/advanced-options/metadata")
|
41
|
+
end
|
42
|
+
|
43
|
+
url
|
44
|
+
end
|
45
|
+
|
46
|
+
def download_database!
|
47
|
+
require "open-uri"
|
48
|
+
data = URI.parse(database_url).open.read
|
49
|
+
File.write(database_path, data)
|
50
|
+
end
|
51
|
+
|
52
|
+
def version
|
53
|
+
require "font_awesome/sass/version"
|
54
|
+
FontAwesome::Sass::VERSION
|
55
|
+
end
|
56
|
+
|
57
|
+
def default_fa_style_for_id(id)
|
58
|
+
return if version.start_with?("4")
|
59
|
+
|
60
|
+
if version.start_with?("5")
|
61
|
+
return BMC::FontAwesomeHelper.database.dig(id, :styles).to_a.first
|
62
|
+
end
|
63
|
+
|
64
|
+
raise "invalid font-awesome-sass version"
|
65
|
+
end
|
66
|
+
end # class << self
|
67
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module BMC::FormHelper
|
2
|
+
include BMC::I18nHelper
|
3
|
+
|
4
|
+
# Hidden submit to be the default triggered on <enter> keypress on a form
|
5
|
+
# Does not work if display:none
|
6
|
+
def form_hidden_submit
|
7
|
+
css = "position:absolute;top:-9999px;left:-9999px;opacity:0;height:0;width:0;visibility: hidden"
|
8
|
+
tag(:input, type: "submit", class: "hidden-submit", style: css)
|
9
|
+
end
|
10
|
+
|
11
|
+
def search_form(action: request.fullpath)
|
12
|
+
render "bmc/search/form", action: action
|
13
|
+
end
|
14
|
+
|
15
|
+
def hidden_inputs_for_get_form(url, except: [])
|
16
|
+
query_values = Addressable::URI.parse(url).query_values.to_h
|
17
|
+
.with_indifferent_access
|
18
|
+
.except(*except)
|
19
|
+
|
20
|
+
return if query_values.empty?
|
21
|
+
|
22
|
+
query_values.sum { |k, v| tag.input(type: "hidden", name: k, value: v) }
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module BMC::LinkHelper
|
2
|
+
def link_to_object(obj, options = {})
|
3
|
+
return if obj.nil?
|
4
|
+
|
5
|
+
if policy(obj).read?
|
6
|
+
link_to(obj.to_s, engine_polymorphic_path(obj), options)
|
7
|
+
else
|
8
|
+
obj.to_s
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def icon_link_to(icon, name, options = nil, html_options = nil, &block)
|
13
|
+
name = icon(icon).concat(" ").concat(name)
|
14
|
+
link_to(name, options, html_options, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def web_link(text, opts = {})
|
18
|
+
return if text.to_s.blank?
|
19
|
+
|
20
|
+
href = text
|
21
|
+
href = "http://#{text}" unless text.include?("://")
|
22
|
+
|
23
|
+
link_to(text, href, opts)
|
24
|
+
end
|
25
|
+
|
26
|
+
def email_link(text, opts = {})
|
27
|
+
return if text.to_s.blank?
|
28
|
+
|
29
|
+
href = "mailto:#{text}"
|
30
|
+
|
31
|
+
link_to(text, href, opts)
|
32
|
+
end
|
33
|
+
|
34
|
+
def tel_link(text, opts = {})
|
35
|
+
return if text.to_s.blank?
|
36
|
+
|
37
|
+
value = text.delete(" ")
|
38
|
+
href = "tel:#{value}"
|
39
|
+
|
40
|
+
link_to(text, href, opts)
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module BMC::PaginationHelper
|
2
|
+
class << self
|
3
|
+
attr_writer :theme
|
4
|
+
|
5
|
+
def theme
|
6
|
+
@theme ||= "bootstrap4"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def paginate(objects, options = {})
|
11
|
+
options = {theme: BMC::PaginationHelper.theme}.merge(options)
|
12
|
+
super(objects, **options).gsub(/>(\s+)</, "><").html_safe
|
13
|
+
end
|
14
|
+
|
15
|
+
def pagination_infos(collection)
|
16
|
+
tag.p(class: "pagination-infos") { page_entries_info(collection) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def pagination_and_infos(collection)
|
20
|
+
paginate(collection) + pagination_infos(collection)
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module BMC::RoutesHelper
|
2
|
+
def engine_polymorphic_path(obj, opts = {})
|
3
|
+
if Rails::VERSION::STRING >= "6.0.0"
|
4
|
+
engine = obj.class.module_parents[-2]
|
5
|
+
else
|
6
|
+
engine = obj.class.parents[-2]
|
7
|
+
end
|
8
|
+
|
9
|
+
if engine.nil?
|
10
|
+
routes = main_app
|
11
|
+
else
|
12
|
+
routes = engine::Engine.routes
|
13
|
+
end
|
14
|
+
|
15
|
+
opts = {
|
16
|
+
:controller => "/#{obj.class.to_s.tableize}",
|
17
|
+
:action => :show,
|
18
|
+
:id => obj.to_param,
|
19
|
+
:only_path => true,
|
20
|
+
}.merge(opts)
|
21
|
+
|
22
|
+
routes.url_for(opts)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module BMC::SortingHelper
|
2
|
+
def sortable_column(name, column, options = {})
|
3
|
+
unless column.is_a?(Symbol)
|
4
|
+
raise ArgumentError, "invalid column, please use symbol"
|
5
|
+
end
|
6
|
+
|
7
|
+
current_column, current_direction = sortable_column_order
|
8
|
+
|
9
|
+
if current_column == column
|
10
|
+
if current_direction == :asc
|
11
|
+
name = "#{name} ↓"
|
12
|
+
new_sort_param = "-#{column}"
|
13
|
+
end
|
14
|
+
|
15
|
+
if current_direction == :desc
|
16
|
+
name = "#{name} ↑"
|
17
|
+
new_sort_param = column
|
18
|
+
end
|
19
|
+
|
20
|
+
klass = "sort #{current_direction}"
|
21
|
+
else
|
22
|
+
new_sort_param = column
|
23
|
+
klass = "sort"
|
24
|
+
end
|
25
|
+
|
26
|
+
unless (url_params = options.delete(:url_params))
|
27
|
+
url_params = (params.try(:permit!) || params).to_h.symbolize_keys
|
28
|
+
end
|
29
|
+
url_params[:sort] = new_sort_param
|
30
|
+
|
31
|
+
html_options = {class: klass}.merge(options)
|
32
|
+
|
33
|
+
link_to(name, url_params, html_options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def sortable_column_order(sort_param = params[:sort])
|
37
|
+
sort_param = sort_param.to_s
|
38
|
+
|
39
|
+
if sort_param.present?
|
40
|
+
if sort_param.start_with?("-")
|
41
|
+
column = sort_param[1..].to_sym
|
42
|
+
direction = :desc
|
43
|
+
else
|
44
|
+
column = sort_param.to_sym
|
45
|
+
direction = :asc
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if block_given?
|
50
|
+
yield(column, direction)
|
51
|
+
else
|
52
|
+
[column, direction]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module BMC::TextHelper
|
2
|
+
include ::ActionView::Helpers::NumberHelper
|
3
|
+
include ::ActionView::Helpers::SanitizeHelper
|
4
|
+
include ::ActionView::Helpers::TextHelper
|
5
|
+
|
6
|
+
def nbsp(text = :no_argument)
|
7
|
+
if text == :no_argument
|
8
|
+
"\u00A0"
|
9
|
+
else
|
10
|
+
text.to_s.gsub(" ", nbsp)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def euros(n)
|
15
|
+
currency(n, "€")
|
16
|
+
end
|
17
|
+
|
18
|
+
def currency(n, u)
|
19
|
+
return if n.nil?
|
20
|
+
|
21
|
+
I18n.t("number.currency.format.format")
|
22
|
+
.gsub("%n", number(n))
|
23
|
+
.gsub("%u", u)
|
24
|
+
.tr(" ", nbsp)
|
25
|
+
end
|
26
|
+
|
27
|
+
def percentage(n)
|
28
|
+
return if n.nil?
|
29
|
+
|
30
|
+
number(n) + nbsp + "%"
|
31
|
+
end
|
32
|
+
|
33
|
+
def number(n)
|
34
|
+
return if n.nil?
|
35
|
+
|
36
|
+
opts = {}
|
37
|
+
|
38
|
+
if n.class.to_s.match?(/Float|Decimal/i)
|
39
|
+
opts[:precision] = 2
|
40
|
+
else
|
41
|
+
opts[:precision] = 0
|
42
|
+
end
|
43
|
+
|
44
|
+
opts[:delimiter] = I18n.t("number.format.delimiter")
|
45
|
+
opts[:separator] = I18n.t("number.format.separator")
|
46
|
+
|
47
|
+
number_with_precision(n, opts).tr(" ", nbsp)
|
48
|
+
end
|
49
|
+
|
50
|
+
def date(d, **args)
|
51
|
+
I18n.l(d, **args) unless d.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
def boolean_icon(bool)
|
55
|
+
return if bool.nil?
|
56
|
+
|
57
|
+
return icon(:check, style: "color: green") if bool == true
|
58
|
+
return icon(:times, style: "color: red") if bool == false
|
59
|
+
|
60
|
+
raise "#{bool} is not a boolean"
|
61
|
+
end
|
62
|
+
|
63
|
+
def text2html(str)
|
64
|
+
return if str.to_s.blank?
|
65
|
+
|
66
|
+
str = str.delete("\r").strip
|
67
|
+
strip_tags(str).gsub("\n", "<br />").html_safe
|
68
|
+
end
|
69
|
+
|
70
|
+
def lf2br(str)
|
71
|
+
return if str.to_s.blank?
|
72
|
+
|
73
|
+
str.delete("\r").gsub("\n", "<br />").html_safe
|
74
|
+
end
|
75
|
+
|
76
|
+
def info( # rubocop:disable Metrics/ParameterLists
|
77
|
+
object,
|
78
|
+
attribute,
|
79
|
+
value: nil,
|
80
|
+
default: nil,
|
81
|
+
helper: nil,
|
82
|
+
tag: :div,
|
83
|
+
separator: " : ",
|
84
|
+
label: object.t(attribute)
|
85
|
+
)
|
86
|
+
if default == :hide
|
87
|
+
value = fma(object, attribute, value: value, helper: helper)
|
88
|
+
return if value.nil?
|
89
|
+
end
|
90
|
+
|
91
|
+
value = fma(object, attribute, value: value, default: default, helper: helper)
|
92
|
+
|
93
|
+
separator = " :<br/>".html_safe if separator == :br
|
94
|
+
klass = object.is_a?(Module) ? object : object.class
|
95
|
+
object_type = klass.to_s.split("::").last.underscore
|
96
|
+
|
97
|
+
html_label = self.tag.strong(class: "info-label") { label }
|
98
|
+
span_css_class = "info-value #{object_type}-#{attribute}"
|
99
|
+
html_value = self.tag.span(class: span_css_class) { value }
|
100
|
+
separator_html = self.tag.span(class: "info-separator") { separator }
|
101
|
+
|
102
|
+
container_css_class = ["info", ("blank" if value.blank?)]
|
103
|
+
|
104
|
+
content_tag(tag, class: container_css_class) do
|
105
|
+
[html_label, separator_html, html_value].sum
|
106
|
+
end
|
107
|
+
end # def info
|
108
|
+
|
109
|
+
# format model attribute
|
110
|
+
def fma(object, attribute, value: nil, default: nil, helper: nil)
|
111
|
+
value = object.public_send(attribute) if value.nil?
|
112
|
+
value = default if value != false && value.blank?
|
113
|
+
|
114
|
+
i18n_scope = object.model_name.instance_variable_get(:@klass).i18n_scope
|
115
|
+
i18n_key = "#{object.model_name.i18n_key}/#{attribute}"
|
116
|
+
nested = I18n.t("#{i18n_scope}.attributes.#{i18n_key}", default: "").is_a?(Hash)
|
117
|
+
|
118
|
+
value = object.tv(attribute) if nested
|
119
|
+
value = send(helper, value) if helper
|
120
|
+
value = t("yes") if value == true
|
121
|
+
value = t("no") if value == false
|
122
|
+
value = number(value) if value.is_a?(Numeric)
|
123
|
+
value = date(value) if value.is_a?(Date) || value.is_a?(Time)
|
124
|
+
|
125
|
+
value.to_s.presence
|
126
|
+
end
|
127
|
+
end
|
data/app/helpers/h.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module BMC::SetupJobConcern
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def perform(*args)
|
5
|
+
setup(*args)
|
6
|
+
call
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
class_methods do
|
14
|
+
def setup_with(*attrs)
|
15
|
+
attr_reader(*attrs)
|
16
|
+
|
17
|
+
define_method(:setup) do |*args|
|
18
|
+
if attrs.length != args.length
|
19
|
+
message = "wrong number of arguments (given #{args.length}, expected #{attrs.length})"
|
20
|
+
raise ArgumentError, message
|
21
|
+
end
|
22
|
+
|
23
|
+
attrs.length.times do |i|
|
24
|
+
instance_variable_set("@#{attrs[i]}", args[i])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private :setup_with
|
30
|
+
|
31
|
+
def setup(*args)
|
32
|
+
new.tap { |instance| instance.setup(*args) }
|
33
|
+
end
|
34
|
+
end # class_methods
|
35
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class BMC::CollectionUpdate
|
2
|
+
extend AttrExtras.mixin
|
3
|
+
|
4
|
+
attr_reader_initialize :scope, :params_array
|
5
|
+
|
6
|
+
def self.call(...)
|
7
|
+
new(...).call
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
return update
|
12
|
+
end
|
13
|
+
|
14
|
+
def update!
|
15
|
+
ApplicationRecord.transaction do
|
16
|
+
params_array.each do |object_param|
|
17
|
+
if object_param[:id].present?
|
18
|
+
object = scope.find(object_param[:id])
|
19
|
+
else
|
20
|
+
object = scope.new
|
21
|
+
end
|
22
|
+
|
23
|
+
if object_param.delete(:_destroy).to_i == 1
|
24
|
+
object.destroy!
|
25
|
+
else
|
26
|
+
object.update!(object_param)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def update
|
34
|
+
update!
|
35
|
+
rescue ActiveRecord::RecordInvalid
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class BMC::FCM::Notifier
|
2
|
+
attr_reader :to, :title, :body, :badge, :sound, :data
|
3
|
+
|
4
|
+
# rubocop:disable Metrics/ParameterLists
|
5
|
+
def initialize(to:, title: nil, body:, badge: 0, sound: "default", data: {})
|
6
|
+
super()
|
7
|
+
@to = to
|
8
|
+
@title = title
|
9
|
+
@body = body
|
10
|
+
@badge = badge
|
11
|
+
@sound = sound
|
12
|
+
@data = data
|
13
|
+
end
|
14
|
+
# rubocop:enable Metrics/ParameterLists
|
15
|
+
|
16
|
+
def call
|
17
|
+
BMC::FCM::Request.call(
|
18
|
+
:to => to,
|
19
|
+
:notification => {
|
20
|
+
:title => title,
|
21
|
+
:body => body,
|
22
|
+
:badge => badge,
|
23
|
+
:sound => sound,
|
24
|
+
},
|
25
|
+
:data => data,
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.call(...)
|
30
|
+
new(...).call
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class BMC::FCM::Request
|
2
|
+
extend AttrExtras.mixin
|
3
|
+
|
4
|
+
URL = "https://fcm.googleapis.com/fcm/send"
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_writer :api_key
|
8
|
+
|
9
|
+
def api_key
|
10
|
+
@api_key ||= ENV["FCM_API_KEY"]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader_initialize :request_body
|
15
|
+
|
16
|
+
attr_reader :response_json
|
17
|
+
|
18
|
+
def self.call(...)
|
19
|
+
new(...).call
|
20
|
+
end
|
21
|
+
|
22
|
+
def call
|
23
|
+
response_body = HTTParty.post(URL,
|
24
|
+
:body => request_body.to_json,
|
25
|
+
:headers => {
|
26
|
+
"Content-Type" => "application/json",
|
27
|
+
"Authorization" => "key=#{self.class.api_key}",
|
28
|
+
},
|
29
|
+
).body
|
30
|
+
|
31
|
+
@response_json = JSON.parse(response_body).deep_symbolize_keys
|
32
|
+
|
33
|
+
self
|
34
|
+
rescue JSON::ParserError
|
35
|
+
@response_json = {success: 0, failure: 1, results: [{:error => "InvalidJsonResponse"}]}
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def ok?
|
40
|
+
response_json[:success].positive? && response_json[:failure].zero?
|
41
|
+
end
|
42
|
+
|
43
|
+
def error?
|
44
|
+
!ok?
|
45
|
+
end
|
46
|
+
|
47
|
+
def errors
|
48
|
+
response_json[:results].pluck(:error).compact
|
49
|
+
end
|
50
|
+
|
51
|
+
def invalid_token?
|
52
|
+
errors.include?("NotRegistered") || errors.include?("InvalidRegistration")
|
53
|
+
end
|
54
|
+
end
|