scinote-web/app/utilities/repository_import_parser/importer.rb
2024-07-23 15:55:39 +02:00

321 lines
11 KiB
Ruby

# frozen_string_literal: true
# handles the import of repository records
# requires 4 parameters:
# @sheet: the csv file with imported rows
# @mappings: mappings for columns
# @user: current_user
# @repository: the repository in which we import the items
module RepositoryImportParser
class Importer
IMPORT_BATCH_SIZE = 500
def initialize(sheet, mappings, user, repository, can_edit_existing_items, should_overwrite_with_empty_cells, preview)
@columns = []
@name_index = -1
@id_index = nil
@created_rows_count = 0
@updated_rows_count = 0
@header_skipped = false
@repository = repository
@sheet = sheet
@rows = SpreadsheetParser.spreadsheet_enumerator(@sheet).reject { |r| r.all?(&:blank?) }
@mappings = mappings
@user = user
@repository_columns = @repository.repository_columns
@can_edit_existing_items = true # can_edit_existing_items
@should_overwrite_with_empty_cells = true # should_overwrite_with_empty_cells
@preview = preview
end
def run
fetch_columns
return check_for_duplicate_columns if check_for_duplicate_columns
import_rows!
end
private
def fetch_columns
@mappings.each_with_index do |(_, value), index|
value = value.to_s unless value.is_a?(Hash)
case value
when '0'
@columns << nil
@id_index = index
when '-1'
@columns << nil
@name_index = index
when 'do_not_import'
@columns << nil
else
@columns << @repository_columns.where(data_type: Extends::REPOSITORY_IMPORTABLE_TYPES)
.preload(Extends::REPOSITORY_IMPORT_COLUMN_PRELOADS)
.find_by(id: value)
end
end
end
def check_for_duplicate_columns
col_compact = @columns.compact
if col_compact.map(&:id).uniq.length != col_compact.length
{ status: :error, total_rows_count: total_rows_count, updated_rows_count: @updated_rows_count, created_rows_count: @created_rows_count }
end
end
def import_rows!
checked_rows = []
duplicate_ids = SpreadsheetParser.duplicate_ids(@sheet)
@rows.each do |row|
unless @header_skipped
@header_skipped = true
next
end
incoming_row = SpreadsheetParser.parse_row(row, @sheet, date_format: @user.settings['date_format'])
next if incoming_row.compact.blank?
if @id_index
id = incoming_row[@id_index].to_s.gsub(RepositoryRow::ID_PREFIX, '')
if id.present?
existing_row = @repository.repository_rows.includes(repository_cells: :value).find_by(id: id)
existing_row ||= @repository.repository_rows.new(
id: SecureRandom.uuid,
created_by: @user,
last_modified_by: @user,
import_status: 'invalid',
import_message: I18n.t('repositories.import_records.steps.step3.status_message.not_exist', id: id.to_i)
)
end
end
if existing_row.present?
if !@can_edit_existing_items
existing_row.import_status = 'unchanged'
elsif existing_row.archived
existing_row.import_status = 'archived'
elsif duplicate_ids.include?(existing_row.code)
existing_row.import_status = 'duplicated'
end
if existing_row.import_status.present?
checked_rows << existing_row if @preview
next
end
end
checked_rows << import_row(existing_row, incoming_row)
end
changes = ActiveModelSerializers::SerializableResource.new(
checked_rows.compact,
each_serializer: RepositoryRowImportSerializer,
include: [:repository_cells]
).as_json
{ status: :ok,
total_rows_count: total_rows_count,
created_rows_count: @created_rows_count,
updated_rows_count: @updated_rows_count,
changes: changes,
import_date: I18n.l(Time.zone.today, format: :full_date) }
end
def import_row(repository_row, import_row)
@repository.transaction do
@errors = []
@updated = false
repository_row_name = try_decimal_to_string(import_row[@name_index])
if repository_row.present?
repository_row.name = repository_row_name
else
repository_row = RepositoryRow.new(name: repository_row_name,
repository: @repository,
created_by: @user,
last_modified_by: @user,
import_status: 'created')
end
if @preview
repository_row.validate
repository_row.id = SecureRandom.uuid.gsub(/[a-zA-Z-]/, '') unless repository_row.id.present? # ID required for preview with serializer
else
repository_row.save!
end
@errors << repository_row.errors.full_messages.join(',') if repository_row.errors.present?
@updated = repository_row.changed?
@columns.each_with_index do |column, index|
next if column.blank?
value = import_row[index]
value = try_decimal_to_string(value) unless column.repository_number_value?
cell_value = if value.present?
column.data_type.constantize.import_from_text(
value,
{
created_by: @user,
last_modified_by: @user,
repository_cell_attributes: {
repository_row: repository_row,
repository_column: column,
importing: true
}
},
@user.as_json(root: true, only: :settings).deep_symbolize_keys
)
end
if value.present? && cell_value.nil?
raise ActiveRecord::Rollback unless @preview
@errors << I18n.t('activerecord.errors.models.repository_cell.incorrect_format')
next
end
existing_cell = repository_row.repository_cells.find { |c| c.repository_column_id == column.id }
existing_cell = if cell_value.nil?
handle_nil_cell_value(existing_cell)
else
handle_existing_cell_value(existing_cell, cell_value, repository_row)
end
@updated ||= @preview ? existing_cell&.value&.changed? : existing_cell&.value&.saved_changes?
@errors << existing_cell.value.errors.full_messages.join(',') if existing_cell&.value&.errors.present?
end
repository_row.import_status = if @errors.present?
'invalid'
elsif repository_row.import_status == 'created'
@created_rows_count += 1
'created'
elsif @updated
@updated_rows_count += 1
'updated'
else
'unchanged'
end
repository_row.import_message = @errors.join(',').downcase if @errors.present?
repository_row
rescue ActiveRecord::RecordInvalid
raise ActiveRecord::Rollback
end
end
def handle_nil_cell_value(repository_cell)
return unless repository_cell.present? && @should_overwrite_with_empty_cells
@updated = erase_cell!(repository_cell, preview: @preview)
repository_cell
end
def handle_existing_cell_value(repository_cell, cell_value, repository_row)
if repository_cell.present?
case cell_value
when RepositoryStockValue
repository_cell.value.update_data!(
{
amount: cell_value.amount,
unit_item_id: cell_value.repository_stock_unit_item_id
},
@user,
preview: @preview
)
when RepositoryListValue
repository_list_item_id = cell_value[:repository_list_item_id]
repository_cell.value.update_data!(repository_list_item_id, @user, preview: @preview)
when RepositoryStatusValue
repository_status_item_id = cell_value[:repository_status_item_id]
repository_cell.value.update_data!(repository_status_item_id, @user, preview: @preview)
when RepositoryTimeValue
repository_cell.value.update_data!(
repository_cell.value.data.change(hour: cell_value.data.hour, min: cell_value.data.min),
@user,
preview: @preview
)
else
sanitized_cell_value_data = sanitize_cell_value_data(cell_value.data)
repository_cell.value.update_data!(sanitized_cell_value_data, @user, preview: @preview)
end
repository_cell
else
# Create new cell
if @preview
repository_cell = repository_row.repository_cells.build(value: cell_value, repository_column: cell_value.repository_cell.repository_column)
repository_cell.validate
repository_cell.value.validate
repository_cell.id = SecureRandom.uuid.gsub(/[a-zA-Z-]/, '') unless cell_value.repository_cell.id.present? # ID required for preview with serializer
return repository_cell
else
cell_value.repository_cell.value = cell_value
repository_row.repository_cells << cell_value.repository_cell
cell_value.save!
end
@updated ||= true
cell_value.repository_cell
end
end
def sanitize_cell_value_data(cell_value_data)
case cell_value_data
# when importing from .csv for:
# repository_text_value, repository_number_value, repository_date_value, repository_date_time_value_base
when String, Numeric, Date, Time
cell_value_data
when Array
cell_value_data.filter_map do |element|
if element.is_a?(Hash) && element.key?(:value)
element[:value].to_s
elsif element.is_a?(String)
element
end
end
else
[]
end
end
def try_decimal_to_string(value)
if value.is_a?(BigDecimal)
value.frac.zero? ? value.to_i.to_s : value.to_s
else
value
end
end
def total_rows_count
# all rows minus header
@rows.count - 1
end
def erase_cell!(repository_cell, preview: false)
case repository_cell.value
when RepositoryStockValue
return false if repository_cell.value.amount.zero?
repository_cell.value.update_data!(
{
amount: 0,
unit_item_id: repository_cell.value.repository_stock_unit_item_id
},
@user,
preview: preview
)
else
preview ? repository_cell.to_destroy = true : repository_cell.value.destroy!
end
true
end
end
end