mirror of
				https://github.com/scinote-eln/scinote-web.git
				synced 2025-10-31 08:26:31 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			321 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			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
 |