demographic 0.8.6.RKM.89602.beta.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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +479 -0
  4. data/Rakefile +22 -0
  5. data/app/controllers/demographic/application_controller.rb +87 -0
  6. data/app/controllers/demographic/custom_parameter_values_controller.rb +25 -0
  7. data/app/controllers/demographic/optionals_bulk_controller.rb +33 -0
  8. data/app/controllers/demographic/optionals_controller.rb +68 -0
  9. data/app/controllers/demographic/optionals_types_controller.rb +17 -0
  10. data/app/controllers/demographic/user_optionals_controller.rb +12 -0
  11. data/app/jobs/demographic/application_job.rb +4 -0
  12. data/app/mailers/demographic/application_mailer.rb +6 -0
  13. data/app/serializers/demographic/optionals/filter_list_serializer.rb +34 -0
  14. data/app/serializers/demographic/optionals/list_serializer.rb +17 -0
  15. data/app/serializers/demographic/optionals/optional_serializer.rb +20 -0
  16. data/app/serializers/demographic/optionals/value_serializer.rb +5 -0
  17. data/app/services/demographic/custom_parameter_values/index.rb +53 -0
  18. data/app/services/demographic/helpers/association_localizer.rb +13 -0
  19. data/app/services/demographic/helpers/import/bulk/build_optionals.rb +54 -0
  20. data/app/services/demographic/helpers/import/bulk/create_optional_values.rb +32 -0
  21. data/app/services/demographic/helpers/import/bulk/create_or_update_user_optionals.rb +110 -0
  22. data/app/services/demographic/helpers/import/create_optionals_list.rb +60 -0
  23. data/app/services/demographic/helpers/import/create_or_update_custom_parameter_values.rb +97 -0
  24. data/app/services/demographic/helpers/import/create_or_update_user_values.rb +161 -0
  25. data/app/services/demographic/helpers/import/create_or_update_values.rb +124 -0
  26. data/app/services/demographic/optional_values/create_list.rb +61 -0
  27. data/app/services/demographic/optional_values/update_list.rb +85 -0
  28. data/app/services/demographic/optionals/bulk_create.rb +115 -0
  29. data/app/services/demographic/optionals/bulk_create_validations.rb +122 -0
  30. data/app/services/demographic/optionals/create.rb +90 -0
  31. data/app/services/demographic/optionals/list.rb +51 -0
  32. data/app/services/demographic/optionals/optional_values.rb +52 -0
  33. data/app/services/demographic/optionals/update.rb +100 -0
  34. data/app/services/demographic/parse/data_serializer.rb +87 -0
  35. data/app/services/demographic/parse/types/values.rb +99 -0
  36. data/app/services/demographic/user_optionals/list.rb +60 -0
  37. data/app/services/import/templates/create_optionals.rb +26 -0
  38. data/app/services/import/templates/xlsx.rb +44 -0
  39. data/config/initializers/custom_classes/string.rb +11 -0
  40. data/config/routes.rb +38 -0
  41. data/db/migrate/20201207181535_create_demographic_optionals.rb +18 -0
  42. data/db/migrate/20201207184210_create_users.rb +12 -0
  43. data/db/migrate/20201211204926_create_demographic_demographic_optional_values.rb +15 -0
  44. data/db/migrate/20201215193056_create_demographic_user_optional_values.rb +11 -0
  45. data/db/migrate/20220427163444_add_token_to_optionals.rb +6 -0
  46. data/db/migrate/20220427163507_add_token_to_optional_values.rb +6 -0
  47. data/lib/demographic/engine.rb +17 -0
  48. data/lib/demographic/version.rb +5 -0
  49. data/lib/demographic.rb +78 -0
  50. data/lib/generators/init/USAGE +8 -0
  51. data/lib/generators/init/init_generator.rb +11 -0
  52. data/lib/generators/init/templates/init_demographic.rb +14 -0
  53. data/lib/generators/init/templates/policy_demographic.rb +70 -0
  54. data/lib/generators/init_manual_update/USAGE +8 -0
  55. data/lib/generators/init_manual_update/init_manual_update_generator.rb +12 -0
  56. data/lib/generators/init_manual_update/templates/20220427163444_add_token_to_optionals.rb +6 -0
  57. data/lib/generators/init_manual_update/templates/20220427163507_add_token_to_optional_values.rb +6 -0
  58. data/lib/generators/init_manual_update/templates/demographic_update_tokens.rake +13 -0
  59. data/lib/generators/init_manual_update/templates/demographic_update_tokens_by_tenants.rake +24 -0
  60. data/lib/generators/init_manual_update_v2/init_manual_update_generator.rb +10 -0
  61. data/lib/generators/init_manual_update_v2/templates/20240206120304_create_custom_parameter_values.rb +10 -0
  62. data/lib/generators/init_manual_update_v2/templates/custom_parameter_value.rb +5 -0
  63. data/lib/generators/init_model/USAGE +8 -0
  64. data/lib/generators/init_model/init_model_generator.rb +12 -0
  65. data/lib/generators/init_model/templates/optional.rb +5 -0
  66. data/lib/generators/init_model/templates/optional_value.rb +4 -0
  67. data/lib/generators/init_model/templates/user.rb +5 -0
  68. data/lib/generators/init_model/templates/user_optional.rb +5 -0
  69. data/lib/tasks/demographic_tasks.rake +4 -0
  70. metadata +346 -0
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+ module Demographic
3
+ module Helpers
4
+ module Import
5
+ class CreateOrUpdateUserValues
6
+ # optional = "{optional_id, optional_value_id, value:}"
7
+
8
+ def self.call(user:, optionals:, parent_module:, reference_id:)
9
+ return { success: false, message: 'error has not optionls!!!' } unless optionals.present?
10
+
11
+ adjustable = parent_model(parent_module).to_s.capitalize.classify.constantize.find_by!(id: reference_id)
12
+
13
+ optionals_array = []
14
+ optional_ids = optionals.map { |element| element[:optional_id] }.compact
15
+
16
+ optionals_hash = adjustable.optionals.where(id: optional_ids).select(:id, :input_type).index_by(&:id)
17
+
18
+ user_optionals_hash = user.user_optionals.where(optional_id: optional_ids).index_by(&:optional_id)
19
+
20
+ optional_value_ids = optionals.map { |element| element[:optional_value_id] }.compact
21
+ optional_values_hash = ::OptionalValue.where(id: optional_value_ids).select(:id, :value).index_by(&:id)
22
+ simple_inputs_hash = Demographic.simple_inputs.index_with { |_s| true }
23
+
24
+ optionals.each do |element|
25
+ next unless element[:optional_id].present?
26
+ optional_id = element[:optional_id].to_i
27
+ optional = optionals_hash[optional_id]
28
+ # exception for replace find_by!
29
+ raise StandardError, "Optional not found #{element}" unless optional.present?
30
+
31
+ user_optional = user_optionals_hash[optional_id]
32
+
33
+ if simple_inputs_hash[optional.input_type]
34
+ current_value = format_value(element[:value], optional)
35
+ optional_attrs = simple_input_v2(user.id, optional_id, user_optional, current_value)
36
+ else
37
+ optional_value = optional_values_hash[element[:optional_value_id]&.to_i]
38
+ optional_attrs = multiple_input_v2(user.id, optional_id, user_optional, optional_value)
39
+ end
40
+
41
+ optionals_array << optional_attrs
42
+ end
43
+
44
+ { success: true, message: 'Success!!!', optionals: optionals_array }
45
+ rescue StandardError => e
46
+ { success: false, message: e.to_s, optionals: [] }
47
+ end
48
+
49
+ private
50
+
51
+ def self.multiple_input(user, optional, element)
52
+ user_optional = user.user_optionals.find_by(optional_id: optional.id)
53
+
54
+ if user_optional.present? && !element[:optional_value_id].present?
55
+ user_optional.destroy!
56
+ return {}
57
+ end
58
+
59
+ return {} unless element[:optional_value_id].present?
60
+
61
+ optional_value = optional.optional_values.find_by(id: element[:optional_value_id])
62
+ return {} unless optional_value.present?
63
+
64
+ user_optional ||= user.user_optionals.new
65
+ user_optional.optional_id = optional.id
66
+ user_optional.optional_value_id = optional_value.id
67
+ user_optional.value = optional_value.value
68
+ user_optional.save!
69
+
70
+ { optional_user_id: user_optional.id, optional_value_id: optional_value.id }
71
+ end
72
+
73
+ def self.multiple_input_v2(user_id, optional_id, user_optional, optional_value)
74
+ if optional_value.nil?
75
+ user_optional&.destroy!
76
+ return {}
77
+ end
78
+
79
+ user_optional ||= UserOptional.new
80
+ user_optional.user_id = user_id
81
+ user_optional.optional_id = optional_id
82
+ user_optional.optional_value_id = optional_value.id
83
+ user_optional.value = optional_value.value
84
+ user_optional.save!
85
+
86
+ { optional_id: optional_id, optional_user_id: user_optional.id, optional_value_id: optional_value.id }
87
+ end
88
+
89
+ def self.simple_input(user, optional, element)
90
+ current_value = format_value(element[:value], optional)
91
+ user_optional = user.user_optionals.find_by(optional_id: optional.id)
92
+
93
+ if user_optional.present? && !current_value.present?
94
+ user_optional.destroy!
95
+ return {}
96
+ end
97
+
98
+ return {} unless current_value.present?
99
+
100
+ code = Demographic.generate_code(optional.id, current_value)
101
+ optional_value = optional.optional_values.find_or_create_by(slug: current_value.str_slug,
102
+ code: code) do |optional_value|
103
+ optional_value.token = Demographic.generate_token
104
+ optional_value.value = current_value
105
+ optional_value.value_translate.merge!({ I18n.locale => current_value })
106
+ end
107
+
108
+ user_optional ||= user.user_optionals.new
109
+ user_optional.optional_id = optional.id
110
+ user_optional.optional_value_id = optional_value.id
111
+ user_optional.value = optional_value.value
112
+ user_optional.save!
113
+
114
+ { optional_user_id: user_optional.id, optional_value_id: optional_value.id }
115
+ end
116
+
117
+ def self.simple_input_v2(user_id, optional_id, user_optional, current_value)
118
+ unless current_value.present?
119
+ user_optional&.destroy!
120
+ return {}
121
+ end
122
+
123
+ code = Demographic.generate_code(optional_id, current_value)
124
+ optional_value = ::OptionalValue.find_or_create_by(optional_id: optional_id,
125
+ slug: current_value.str_slug,
126
+ code: code) do |optional_value|
127
+ optional_value.token = Demographic.generate_token
128
+ optional_value.value = current_value
129
+ optional_value.value_translate.merge!({ I18n.locale => current_value })
130
+ end
131
+
132
+ # TODO: verificar impacto de quitar creacion de user_optinal para simple_input
133
+ user_optional ||= UserOptional.new
134
+ user_optional.user_id = user_id
135
+ user_optional.optional_id = optional_id
136
+ user_optional.optional_value_id = optional_value.id
137
+ user_optional.value = optional_value.value
138
+ user_optional.save!
139
+
140
+ { optional_id: optional_id, optional_user_id: user_optional.id, optional_value_id: optional_value.id }
141
+ end
142
+
143
+ def self.parent_model(parent_module)
144
+ parent = Demographic.parent_model[parent_module.to_sym]
145
+ raise 'init Demographic parent_model error param :module!!!' unless parent.present?
146
+
147
+ parent
148
+ end
149
+
150
+ def self.format_value(value, optional)
151
+ return unless value.present?
152
+
153
+ return value.to_s.to_date.strftime if optional.input_type.eql?('date')
154
+
155
+ value.to_s
156
+ rescue StandardError
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Demographic
4
+ module Helpers
5
+ module Import
6
+ class CreateOrUpdateValues
7
+ def self.call(user_data:, parent_module:, reference_id:)
8
+ @permit_user_data = user_data.is_a?(Hash) ? user_data : user_data.to_unsafe_hash
9
+ return { success: false, message: 'User data not present', optionals: [] } unless @permit_user_data.present?
10
+
11
+ optionals = optional_hash
12
+ return { success: false, message: 'Optionals Empty!!!', optionals: [] } unless optionals.present?
13
+
14
+ optional_codes = optionals.map { |element| element[:code]&.downcase }.compact
15
+ optionals_query = Optional.unscoped.where('lower(code) IN (?)', optional_codes).order(id: :desc)
16
+ optionals_hash = optionals_query.index_by{|optional| optional.code.downcase }
17
+
18
+ records = optionals.map do |element|
19
+ current_value = element[:value]
20
+
21
+ optional = optionals_hash[element[:code]&.downcase]
22
+ return { success: false, message: "Optional not found #{element}", optionals: [] } unless optional.present?
23
+
24
+ next { optional_id: optional.id, optional_value_id: nil, value: nil } unless current_value.present?
25
+
26
+ value = value_valid(optional, current_value)
27
+ unless value.is_valid?
28
+ return { success: false, message: "The value is not valid #{current_value}", optionals: [] }
29
+ end
30
+
31
+ optional_value = find_or_create_by_value(optional, value.current)
32
+
33
+ { optional_id: optional.id, optional_value_id: optional_value.id, value: value.current }
34
+ end
35
+
36
+ { success: true, optionals: records.compact }
37
+ rescue StandardError => e
38
+ { success: false, message: e.to_s, optionals: [] }
39
+ end
40
+
41
+ private
42
+
43
+ def self.optional_hash
44
+ @permit_user_data.with_indifferent_access.map do |key, value|
45
+ code = key.to_s
46
+ next unless code.include?('optional_') && !key.nil?
47
+
48
+ name_type = code.gsub('optional_', '').split('#')
49
+ name = name_type.first
50
+
51
+ {
52
+ name: name,
53
+ code: name.parameterize.str_slug,
54
+ value: value
55
+ }
56
+ end.compact
57
+ end
58
+
59
+ def self.parent_model(parent_module)
60
+ parent = Demographic.parent_model[parent_module.to_sym]
61
+ raise 'init Demographic parent_model error param :module!!!' unless parent.present?
62
+
63
+ parent
64
+ end
65
+
66
+ def self.datetime?(d)
67
+ d.methods.include? :strftime
68
+ end
69
+
70
+ def self.value_valid(optional, value)
71
+ struct = Struct.new(:is_valid?, :current)
72
+
73
+ return struct.new(true, value) if optional.input_type.eql?('list') || optional.input_type.eql?('text')
74
+
75
+ return struct.new(value.to_s.is_numeric?, value) if optional.input_type.eql?('number')
76
+
77
+ return conversion_date(value, struct) if optional.input_type.eql?('date')
78
+
79
+ return conversion_bool(value, struct) if optional.input_type.eql?('bool')
80
+
81
+ struct.new(false, value.to_s)
82
+ end
83
+
84
+ def self.conversion_bool(value, struct)
85
+ bool_hash = { '0' => 'false', '1' => 'true' }
86
+
87
+ bool = bool_hash[value.to_i.to_s]
88
+
89
+ struct.new(bool.present?, bool)
90
+ end
91
+
92
+ def self.conversion_date(value, struct)
93
+ formats = %w[%Y-%m-%d %Y/%m/%d %y-%m-%d %y/%m/%d %d-%m-%Y %d/%m/%Y %d-%m-%y %d/%m/%y]
94
+
95
+ formats.each do |format|
96
+ date = format_date(value, format)
97
+ return struct.new(true, date) if date != false
98
+ end
99
+
100
+ struct.new(false, value.to_s)
101
+ end
102
+
103
+ def self.format_date(value, format)
104
+ Date.strptime(value, format).strftime
105
+ rescue StandardError
106
+ false
107
+ end
108
+
109
+ def self.find_or_create_by_value(optional, current_value)
110
+ code = Demographic.generate_code(optional.id, current_value.to_s)
111
+ optional.optional_values.find_or_create_by(slug: current_value.to_s.str_slug,
112
+ code: code) do |optional_value|
113
+ optional_value.token = Demographic.generate_token
114
+ optional_value.value = current_value
115
+ optional_value.value_translate = { es: current_value }
116
+ end
117
+ rescue ActiveRecord::RecordNotUnique
118
+ code = Demographic.generate_code(optional.id, current_value.to_s)
119
+ optional.optional_values.where('code ILIKE ?', code).first
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,61 @@
1
+ module Demographic
2
+ module OptionalValues
3
+ class CreateList
4
+
5
+ attr_accessor :adjustable, :values, :optional
6
+
7
+ def initialize(adjustable:, optional:, values:)
8
+ @adjustable = adjustable
9
+ @optional = optional
10
+ @values = values
11
+ end
12
+
13
+ def call
14
+ return true unless values.present?
15
+
16
+ ActiveRecord::Base.transaction do
17
+ validation_values
18
+ create_values
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def validation_values
25
+ values.each do |value|
26
+ if !value[:value_translate].present? || !value[:value_translate].values.any?{ |v| v.present? }
27
+ optional.errors.add(:value_translate, 'value_translate is empty')
28
+ raise ActiveRecord::RecordInvalid.new(optional)
29
+ end
30
+ end
31
+ end
32
+
33
+ def create_values
34
+
35
+ values.each do |value|
36
+ if value[:is_valid].present? && value[:is_valid].to_d == 0
37
+ next
38
+ end
39
+
40
+ current_value = current_value(value)
41
+ code = Demographic.generate_code(optional.id, current_value.str_slug)
42
+
43
+ optional.optional_values.find_or_create_by!(slug: current_value.str_slug,
44
+ code: code) do |optional_value|
45
+ optional_value.token = Demographic.generate_token
46
+ optional_value.value = current_value
47
+ optional_value.value_translate = value[:value_translate]
48
+ optional_value.order_num = value[:order_num]
49
+ end
50
+ end
51
+ end
52
+
53
+ def current_value(value)
54
+ return value[:value_translate]['es'] if value[:value_translate]['es'].present?
55
+
56
+ value[:value_translate].values.detect{|v| v.present? }
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,85 @@
1
+ module Demographic
2
+ module OptionalValues
3
+ class UpdateList
4
+
5
+ attr_accessor :adjustable, :values, :optional
6
+
7
+ def initialize(adjustable:, optional:, values:)
8
+ @adjustable = adjustable
9
+ @optional = optional
10
+ @values = values
11
+ end
12
+
13
+ def call
14
+ return true unless values.present?
15
+
16
+ ActiveRecord::Base.transaction do
17
+ validation_values
18
+ delete_values
19
+ update_values
20
+ create_values
21
+ end
22
+
23
+ end
24
+
25
+ private
26
+
27
+ def validation_values
28
+ values.each do |value|
29
+ if !value[:value_translate].present? || !value[:value_translate].values.any?{ |v| v.present? }
30
+ optional.errors.add(:value_translate, 'value_translate is empty')
31
+ raise ActiveRecord::RecordInvalid.new(optional)
32
+ end
33
+ end
34
+ end
35
+
36
+ def delete_values
37
+ values_ids = values.map{|v| v[:id] }.compact
38
+ return unless values_ids.present?
39
+
40
+ optional.optional_values.where.not(id: values_ids).destroy_all
41
+ end
42
+
43
+ def update_values
44
+ values_ids = values.map{|v| v[:id] }.compact
45
+ return unless values_ids.present?
46
+
47
+ current_values = optional.optional_values.where(id: values_ids)
48
+ return unless current_values.present?
49
+
50
+ current_values.each do |optional_value|
51
+ value = values.detect{|v| v[:id] == optional_value.id}
52
+ current_value = current_value(value)
53
+ optional_value.code = Demographic.generate_code(optional.id, current_value.str_slug)
54
+ optional_value.slug = current_value.str_slug
55
+ optional_value.value = current_value
56
+ optional_value.value_translate = value[:value_translate]
57
+ optional_value.order_num = value[:order_num]
58
+ optional_value.save!
59
+ update_user_values(optional_value, current_value)
60
+ end
61
+ end
62
+
63
+ def create_values
64
+ new_values = values.select{|v| !v[:id].present? }
65
+ return unless new_values.present?
66
+
67
+ ::Demographic::OptionalValues::CreateList.new(adjustable: adjustable,
68
+ values: new_values,
69
+ optional: optional).call
70
+ end
71
+
72
+ def current_value(value)
73
+ return value[:value_translate]['es'] if value[:value_translate]['es'].present?
74
+
75
+ value[:value_translate].values.detect{|v| v.present? }
76
+ end
77
+
78
+ def update_user_values(optional_value, current_value)
79
+ optional.user_optionals
80
+ .where(optional_value_id: optional_value.id)
81
+ .update_all(value: current_value)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,115 @@
1
+ module Demographic
2
+ module Optionals
3
+ class BulkCreate
4
+ attr_reader :file, :adjustable, :optionals_by_code
5
+
6
+ def initialize(file:, adjustable:)
7
+ @file = file
8
+ @adjustable = adjustable
9
+ @optionals_by_code = adjustable.optionals.index_by(&:code)
10
+ end
11
+
12
+ def call
13
+ return response(validations.error.data, validations.error.status) if validations.error.present?
14
+
15
+ response(create_bulk_import, 200)
16
+ end
17
+
18
+ private
19
+
20
+ def validations
21
+ @validations ||= ::Demographic::Optionals::BulkCreateValidations.new(file: file).call
22
+ end
23
+
24
+ def create_bulk_import
25
+ return json_response.merge!(message: "No devuelve ids del bulk import") unless bulk_import.ids.present?
26
+
27
+ optionals = Optional.where(id: bulk_import.ids)
28
+ return json_response.merge!(message: "No devuelve optionals aun que tenemos los ids del bulk import") unless optionals.ids.present?
29
+
30
+ return json_response.merge!(message: "El module es incorrecto") unless change_module
31
+ run_create_callbacks(optionals)
32
+
33
+ json_response.merge!(message: "Success")
34
+ end
35
+
36
+ def bulk_import
37
+ @bulk_import ||= bulk_import_transaction
38
+ end
39
+
40
+ def bulk_import_transaction
41
+ current_bulk_import = Optional.bulk_import(data_hash.values,
42
+ batch_size: 1000,
43
+ on_duplicate_key_ignore: true,
44
+ validate: true,
45
+ returning: [:id, :name, :input_type])
46
+
47
+ delay_transaction
48
+ current_bulk_import
49
+ end
50
+
51
+ def json_response
52
+ @json_response ||= {
53
+ optionals_created: bulk_import.results
54
+ }
55
+ end
56
+
57
+ def run_create_callbacks(optionals)
58
+ optionals.each { |optional| optional.run_callbacks(:create) }
59
+ end
60
+
61
+ def data_hash
62
+ return @data_hash if @data_hash.present?
63
+
64
+ @data_hash = {}
65
+
66
+ validations.data.each do |row|
67
+ serialized_row = serialize_row(row)
68
+ @data_hash[serialized_row.code] = serialized_row
69
+ end
70
+
71
+ @data_hash
72
+ end
73
+
74
+ def serialize_row(row)
75
+ base_optional_hash = validations.headers.zip(row).to_h
76
+ code = base_optional_hash['name'].parameterize.str_slug
77
+
78
+ optional_hash = {}.tap do |hash|
79
+ hash[:name] = base_optional_hash['name']
80
+ hash[:input_type] = base_optional_hash['type']
81
+ hash[:code] = base_optional_hash['code'] || code
82
+ hash[:token] = Demographic.generate_token
83
+ hash[:name_translate] = { es: base_optional_hash['name'] }
84
+ end
85
+
86
+ optionals_by_code[code] || adjustable.optionals.new(optional_hash)
87
+ end
88
+
89
+ def delay_transaction
90
+ # We need to do this because the .bulk_import is causing issues on production, because in prod exists a delay
91
+ # for the bd replicas
92
+ sleep 1
93
+ end
94
+
95
+ def response(data, status)
96
+ Struct.new(:data,
97
+ :status).new(data,
98
+ status)
99
+ end
100
+
101
+ def change_module
102
+ if base_optional_hash['module'].present?
103
+ parent_model = Demographic.parent_model[base_optional_hash['module'].to_sym]
104
+ model_class = parent_model.to_s.capitalize.classify.constantize
105
+ if adjustable.id == "0"
106
+ #non-persisted object to avoid errors downstream
107
+ return adjustable = model_class.new(id: 0)
108
+ end
109
+
110
+ adjustable = model_class.find_by!(id: adjustable.id)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,122 @@
1
+ module Demographic
2
+ module Optionals
3
+ class BulkCreateValidations
4
+ attr_reader :file
5
+ attr_accessor :data_hash, :headers, :xls
6
+
7
+ def initialize(file:)
8
+ @file = file
9
+ end
10
+
11
+ def call
12
+ response(validate_file)
13
+ end
14
+
15
+ private
16
+
17
+ def validate_file
18
+ excel_validation = valid_excel(file)
19
+ return excel_validation if excel_validation.respond_to?(:error)
20
+
21
+ headers_validation = validate_headers
22
+ return headers_validation if headers_validation.respond_to?(:error)
23
+
24
+ data_validation = validate_data
25
+ return data_validation if data_validation.respond_to?(:error)
26
+
27
+ data_hash
28
+ end
29
+
30
+ def valid_excel(excel_file)
31
+ ext = file.original_filename.split('.').last
32
+
33
+ unless %w[xls xlsx].include? ext
34
+ return wrong_file_format(excel_file.original_filename, 'Formato de archivo no válido. Solo XLSX y XLS')
35
+ end
36
+
37
+ xls = (Roo::Spreadsheet.open excel_file.tempfile.path, extension: ext) rescue nil
38
+
39
+ if xls.nil?
40
+ inverse_ext = ext.eql?('xls') ? 'xlsx' : 'xls'
41
+ xls = Roo::Spreadsheet.open excel_file.tempfile.path, extension: inverse_ext
42
+ end
43
+ # Importante: construir las filas manualmente para NO perder la primera fila (cabeceras).
44
+ # Algunos métodos de parseo de Roo omiten la fila de cabecera por defecto.
45
+ sheet = xls.sheet(0)
46
+ rows = []
47
+ (sheet.first_row..sheet.last_row).each do |row_index|
48
+ rows << sheet.row(row_index)
49
+ end
50
+ @xls = rows
51
+ end
52
+
53
+ def validate_headers
54
+ @headers = xls.shift
55
+ required_headers = ::Import::Templates::CreateOptionals::HEADERS
56
+ missing_headers = required_headers - headers
57
+ return missing_parameters("Faltan las siguientes cabeceras en el archivo: #{missing_headers.join(', ')}") unless missing_headers.empty?
58
+
59
+ @headers
60
+ end
61
+
62
+ def validate_data
63
+ return missing_parameters("El archivo está vacío") if xls.blank?
64
+
65
+ @data_hash = []
66
+ data_errors = []
67
+ type_index = headers.index('type')
68
+
69
+ xls.each do |row|
70
+ unless row.all?
71
+ data_errors << row
72
+ next
73
+ end
74
+
75
+ unless available_types.include?(row[type_index])
76
+ data_errors << row
77
+ next
78
+ end
79
+
80
+ @data_hash << row
81
+ end
82
+
83
+ return missing_parameters("El archivo contiene errores en: #{parse_row_error(data_errors)}") if data_errors.present?
84
+
85
+ @data_hash
86
+ end
87
+
88
+ def parse_row_error(rows)
89
+ rows.map { |pair| pair.join(': ') }.join(', ')
90
+ end
91
+
92
+ def available_types
93
+ Demographic.simple_inputs + Demographic.multiples_inputs
94
+ end
95
+
96
+ # Start Structs methods
97
+ def wrong_file_format(file_name, message)
98
+ data = { wrong_file_format?: true, file_name: file_name, message: message }
99
+ Struct.new(:data, :status, :error).new(data, 400, true)
100
+ end
101
+
102
+ def missing_parameters(message)
103
+ data = { missing_parameters?: true, message: message }
104
+ Struct.new(:data, :status, :error).new(data, 400, true)
105
+ end
106
+
107
+ def response(validations)
108
+ error = validations.respond_to?(:error)
109
+ params = {}.tap do |hash|
110
+ hash[:data] = error ? [] : data_hash
111
+ hash[:headers] = error ? [] : headers
112
+ hash[:error] = error ? validations : false
113
+ end
114
+
115
+ Struct.new(:data, :headers, :error).new(params[:data],
116
+ params[:headers],
117
+ params[:error])
118
+ end
119
+ # Ends Structs methods
120
+ end
121
+ end
122
+ end