Fix repository import mapping and preview [SCI-10773]

This commit is contained in:
Anton 2024-06-12 15:01:36 +02:00
parent 060a694fc0
commit 2a2c2b1347
15 changed files with 191 additions and 238 deletions

View file

@ -334,22 +334,8 @@ class RepositoriesController < ApplicationController
).import!
if status[:status] == :ok
log_activity(:import_inventory_items, num_of_items: status[:nr_of_added])
unless import_params[:preview]
flash[:success] = t('repositories.import_records.success_flash',
number_of_rows: status[:nr_of_added],
total_nr: status[:total_nr])
end
render json: import_params[:preview] ? status : {}, status: :ok
else
unless import_params[:preview]
flash[:alert] =
t('repositories.import_records.partial_success_flash',
nr: status[:nr_of_added], total_nr: status[:total_nr])
end
render json: {}, status: :unprocessable_entity
end
else

View file

@ -1,22 +1,24 @@
<template>
<div v-if="modalOpened">
<component
v-if="activeStep !== 'ExportModal'"
:is="activeStep"
:params="params"
:uploading="uploading"
@uploadFile="uploadFile"
@generatePreview="generatePreview"
@changeStep="changeStep"
@importRows="importRecords"
/>
<ExportModal
v-else
:rows="[{id: params.id, team: params.attributes.team_name}]"
:exportAction="params.attributes.export_actions"
@close="activeStep ='UploadStep'"
@export="activeStep = 'UploadStep'"
/>
<component
v-if="activeStep !== 'ExportModal'"
:is="activeStep"
:params="params"
:key="modalId"
:uploading="uploading"
@uploadFile="uploadFile"
@generatePreview="generatePreview"
@changeStep="changeStep"
@importRows="importRecords"
/>
<ExportModal
v-else
:rows="[{id: params.id, team: params.attributes.team_name}]"
:exportAction="params.attributes.export_actions"
@close="activeStep ='UploadStep'"
@export="activeStep = 'UploadStep'"
/>
</div>
</template>
@ -49,7 +51,8 @@ export default {
modalOpened: false,
activeStep: 'UploadStep',
uploading: false,
params: {}
params: {},
modalId: null
};
},
created() {
@ -64,6 +67,7 @@ export default {
axios.get(this.repositoryUrl)
.then((response) => {
this.params = response.data.data;
this.modalId = Math.random().toString(36);
this.modalOpened = true;
});
},

View file

@ -15,14 +15,15 @@
{{ this.i18n.t('repositories.import_records.steps.step2.subtitle') }}
</p>
<div class="flex gap-6 items-center my-6">
<div class="flex items-center gap-1">
<div class="flex items-center gap-2">
<div class="sci-checkbox-container">
<input type="checkbox" class="sci-checkbox" v-model="autoMapping" />
<span class="sci-checkbox-label"></span>
</div>
{{ i18n.t('repositories.import_records.steps.step2.autoMappingText') }}
</div>
<div class="flex items-center gap-1">
<!--
<div class="flex items-center gap-1">
<div class="sci-checkbox-container my-auto">
<input type="checkbox" class="sci-checkbox" :checked="updateWithEmptyCells" @change="toggleUpdateWithEmptyCells"/>
<span class="sci-checkbox-label"></span>
@ -36,6 +37,7 @@
</div>
{{ i18n.t('repositories.import_records.steps.step2.onlyAddNewItemsText') }}
</div>
-->
</div>
{{ i18n.t('repositories.import_records.steps.step2.importedFileText') }} {{ params.file_name }}
@ -50,7 +52,7 @@
:item="item"
:dropdownOptions="computedDropdownOptions"
:params="params"
:selected="this.selectedItemsIndexes.includes(index)"
:value="this.selectedItems.find((item) => item.index === index)"
@selection:changed="handleChange"
:autoMapping="this.autoMapping"
/>
@ -76,7 +78,7 @@
<button class="btn btn-secondary ml-auto" @click="close" aria-label="Close">
{{ i18n.t('repositories.import_records.steps.step2.cancelBtnText') }}
</button>
<button class="btn btn-primary" :disabled="!rowsIsValid" @click="importRecords">
<button class="btn btn-primary" @click="importRecords">
{{ i18n.t('repositories.import_records.steps.step2.confirmBtnText') }}
</button>
</div>
@ -107,7 +109,7 @@ export default {
},
data() {
return {
autoMapping: true,
autoMapping: false,
updateWithEmptyCells: false,
onlyAddNewItems: false,
columnLabels: {
@ -119,7 +121,6 @@ export default {
5: this.i18n.t('repositories.import_records.steps.step2.table.columnLabels.exampleData')
},
selectedItems: [],
selectedItemsIndexes: [],
importRecordsUrl: null,
teamId: null,
repositoryId: null,
@ -130,131 +131,77 @@ export default {
};
},
methods: {
toggleUpdateWithEmptyCells() {
this.updateWithEmptyCells = !this.updateWithEmptyCells;
},
toggleOnlyAddNewItems() {
this.onlyAddNewItems = !this.onlyAddNewItems;
},
handleChange(payload) {
this.error = null;
const { index, key, value } = payload;
// checking if the mapping is already selected
const foundItem = this.selectedItems.find((item) => item.index === index);
const item = this.selectedItems.find((i) => i.index === index);
const usedBeforeItem = this.selectedItems.find((i) => i.key === key && i.index !== index);
// if it's not, add it
if (!foundItem && key) {
this.selectedItems = [...this.selectedItems, { index, key, value }];
this.selectedItemsIndexes.push(index);
}
// if it is but the key is null then clear it
if (foundItem && !key) {
const indexToRemoveObj = this.selectedItems.findIndex((item) => item.index === index);
const indexToRemoveStr = this.selectedItemsIndexes.indexOf(index);
if ((indexToRemoveObj !== -1) && (indexToRemoveStr !== -1)) {
this.selectedItems.splice(indexToRemoveObj, 1);
this.selectedItemsIndexes.splice(indexToRemoveStr, 1);
}
}
// if it is and the key is not null then update it
if (foundItem && key) {
const indexToRemoveObj = this.selectedItems.findIndex((item) => item.index === index);
this.selectedItems.splice(indexToRemoveObj, 1);
this.selectedItems = [...this.selectedItems, { index, key, value }];
if (usedBeforeItem) {
usedBeforeItem.key = null;
usedBeforeItem.value = null;
}
this.updateAvailableItemsStatus();
},
// necessary for tracking which options are already selected
updateAvailableItemsStatus() {
let updatedAvailableFields = [];
const selectedItemsKeys = new Set(this.selectedItems.map((item) => item.key));
this.alwaysAvailableFields.forEach((field) => {
if (selectedItemsKeys.has(field.key)) {
const tempObj = { key: field.key, value: field.value, alreadySelected: true };
updatedAvailableFields.push(tempObj);
} else {
updatedAvailableFields.push(field);
}
});
this.availableFields = updatedAvailableFields;
updatedAvailableFields = [];
item.key = key;
item.value = value;
},
generateMapping() {
const mapping = {};
for (let i = 0; i < this.params.import_data.header.length; i++) {
const foundItem = this.selectedItems.find((item) => item.index === i);
if (foundItem) {
mapping[foundItem.index] = (foundItem.key === 'new' ? foundItem.value : foundItem.key);
} else {
mapping[i] = '';
}
}
return mapping;
return this.selectedItems.reduce((obj, item) => {
obj[item.index] = item.key || '';
return obj;
}, {});
},
importRecords() {
const selectedItemsKeys = new Set(this.selectedItems.map((item) => item.key));
if (!selectedItemsKeys.has('-1')) {
if (!this.selectedItems.find((item) => item.key === '-1')) {
this.error = this.i18n.t('repositories.import_records.steps.step2.selectNamePropertyError');
return '';
}
this.$emit(
'generatePreview',
this.generateMapping(),
this.updateWithEmptyCells,
this.onlyAddNewItems
this.generateMapping()
);
return true;
}
},
computed: {
computedDropdownOptions() {
const columnKeyToLabelMapping = {};
columnKeyToLabelMapping[-1] = this.i18n.t('repositories.import_records.steps.step2.computedDropdownOptions.name');
if (this.repositoryColumns) {
this.repositoryColumns.forEach((el) => {
const [key, colName, colType] = el;
columnKeyToLabelMapping[key] = this.i18n.t(`repositories.import_records.steps.step2.computedDropdownOptions.${colType}`);
});
}
if (this.availableFields) {
let options = this.availableFields.map((el) => [String(el.key), `${String(el.value)} (${columnKeyToLabelMapping[el.key]})`]);
options = [['new', this.i18n.t('repositories.import_records.steps.step2.table.tableRow.importAsNewColumn')]].concat(options);
return options;
}
return [];
return this.availableFields
.map((el) => [String(el.key), `${String(el.value)} (${el.typeName})`]);
// options = [['new', this.i18n.t('repositories.import_records.steps.step2.table.tableRow.importAsNewColumn')]].concat(options);
},
computedImportedIgnoredInfo() {
const importedSum = this.selectedItems.length;
const ignoredSum = this.params.import_data.header.length - importedSum;
const importedSum = this.selectedItems.filter((i) => i.key).length;
const ignoredSum = this.selectedItems.length - importedSum;
return { importedSum, ignoredSum };
},
rowsIsValid() {
let valid = true;
this.selectedItems.forEach((v) => {
if (v.key === 'new' && (!v.value.type || v.value.name.length < 2)) {
valid = false;
}
});
return valid;
}
},
created() {
this.repositoryColumns = this.params.attributes.repository_columns;
// Adding alreadySelected attribute for tracking
const tempAvailableFields = [];
this.availableFields = [];
this.selectedItems = this.params.import_data.header.map((item, index) => { return { index, key: null, value: null }; });
Object.entries(this.params.import_data.available_fields).forEach(([key, value]) => {
const field = { key, value, alreadySelected: false };
tempAvailableFields.push(field);
let columnTypeName = '';
if (key === '-1') {
columnTypeName = this.i18n.t('repositories.import_records.steps.step2.computedDropdownOptions.name');
} else if (key === '0') {
columnTypeName = this.i18n.t('repositories.import_records.steps.step2.computedDropdownOptions.id');
} else {
const column = this.repositoryColumns.find((el) => el[0] === parseInt(key, 10));
columnTypeName = this.i18n.t(`repositories.import_records.steps.step2.computedDropdownOptions.${column[2]}`);
}
const field = {
key, value, alreadySelected: false, typeName: columnTypeName
};
this.availableFields.push(field);
});
this.availableFields = tempAvailableFields;
this.alwaysAvailableFields = tempAvailableFields;
},
mounted() {
this.autoMapping = true;
}
};
</script>

View file

@ -17,25 +17,21 @@
<div class="py-1 min-h-12 flex items-center flex-col gap-2 px-2" :class="{
'bg-sn-super-light-blue': selected
}">
<!-- system generated data -->
<SelectDropdown v-if="systemGeneratedData.includes(item)"
:disabled="true"
:placeholder="String(item)"
></SelectDropdown>
<SelectDropdown
v-else
:options="dropdownOptions"
@change="changeSelected"
:clearable="true"
:size="'sm'"
:class="{
'outline-sn-alert-brittlebush outline-1 outline rounded': matchNotFound
}"
:placeholder="computeMatchNotFound ?
i18n.t('repositories.import_records.steps.step2.table.tableRow.placeholders.matchNotFound') :
i18n.t('repositories.import_records.steps.step2.table.tableRow.placeholders.doNotImport')"
:title="this.selectedColumnType?.value"
:value="this.selectedColumnType?.key"
></SelectDropdown>
<template v-if="selectedColumnType?.key == 'new'">
<template v-if="false">
<SelectDropdown
:options="newColumnTypes"
@change="(v) => { newColumn.type = v }"
@ -52,31 +48,10 @@
<div class="py-1 min-h-12 px-2 flex items-center" :class="{
'bg-sn-super-light-blue': selected
}">
<!-- import -->
<i v-if="this.selectedColumnType?.key && this.selectedColumnType?.value === item && !systemGeneratedData.includes(item)"
class="sn-icon sn-icon-check" :title="i18n.t('repositories.import_records.steps.step2.table.tableRow.importedColumnTitle')">
</i>
<!-- default column -->
<i v-else-if="systemGeneratedData.includes(item)"
class="sn-icon sn-icon-check text-sn-sleepy-grey" :title="i18n.t('repositories.import_records.steps.step2.table.tableRow.defaultColumnTitle')">
</i>
<!-- user defined this column -->
<i v-else-if="this.selectedColumnType?.key && this.selectedColumnType?.value !== item"
class="sn-icon sn-icon-info text-sn-science-blue"
:title="`${i18n.t('repositories.import_records.steps.step2.table.tableRow.userDefinedColumnTitle')} ${this.selectedColumnType.value}`"></i>
<!-- error: can not import -->
<!-- <i v-else-if=""></i> -->
<!-- match not found -->
<i v-else-if="computeMatchNotFound"
class="sn-icon sn-icon-close text-sn-alert-brittlebush" :title="i18n.t('repositories.import_records.steps.step2.table.tableRow.matchNotFoundColumnTitle')">
</i>
<!-- do not import -->
<i v-else class="sn-icon sn-icon-close text-sn-sleepy-grey" :title="i18n.t('repositories.import_records.steps.step2.table.tableRow.doNotImportColumnTitle')"></i>
<i v-if="differentMapingName" class="sn-icon sn-icon-info text-sn-science-blue"></i>
<i v-else-if="columnMapped" class="sn-icon sn-icon-check"></i>
<i v-else-if="matchNotFound" class="sn-icon sn-icon-close text-sn-alert-brittlebush"></i>
<i v-else class="sn-icon sn-icon-close text-sn-sleepy-grey"></i>
</div>
<div class="py-1 min-h-12 px-2 flex items-center" :title="params.import_data.columns[index]" :class="{
@ -114,10 +89,7 @@ export default {
type: Boolean,
required: true
},
selected: {
type: Boolean,
required: false
}
value: Object
},
data() {
return {
@ -129,24 +101,16 @@ export default {
newColumnTypes: [
['Text', this.i18n.t('repositories.import_records.steps.step2.table.tableRow.newColumnType.text')],
['List', this.i18n.t('repositories.import_records.steps.step2.table.tableRow.newColumnType.list')]
],
systemGeneratedData: [
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.itemId'),
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.createdOn'),
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.addedBy'),
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.addedOn'),
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.archivedBy'),
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.archivedOn'),
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.updatedBy'),
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.updatedOn')]
]
};
},
watch: {
newColumn() {
this.selectedColumnType.value = this.newColumn;
this.$emit('selection:changed', this.selectedColumnType);
selected() {
if (this.value?.key === null) {
this.selectedColumnType = null;
}
},
autoMapping(newVal, oldVal) {
autoMapping(newVal) {
if (newVal === true) {
this.autoMap();
} else {
@ -157,6 +121,18 @@ export default {
computed: {
computeMatchNotFound() {
return this.autoMapping && ((this.selectedColumnType && !this.selectedColumnType.key) || !this.selectedColumnType);
},
selected() {
return !!this.value?.key;
},
differentMapingName() {
return this.columnMapped && this.selectedColumnType?.value !== this.item;
},
matchNotFound() {
return this.autoMapping && !this.selectedColumnType?.key;
},
columnMapped() {
return this.selectedColumnType?.key;
}
},
methods: {
@ -171,15 +147,9 @@ export default {
this.changeSelected(null);
},
changeSelected(e) {
let value;
if (e === 'new') {
value = this.newColumn;
} else {
value = this.params.import_data.available_fields[e];
}
const selectedColumnType = { index: this.index, key: e, value };
this.selectedColumnType = selectedColumnType;
this.$emit('selection:changed', selectedColumnType);
const value = this.params.import_data.available_fields[e];
this.selectedColumnType = { index: this.index, key: e, value };
this.$emit('selection:changed', this.selectedColumnType);
}
},
mounted() {

View file

@ -18,32 +18,32 @@
<div>
<div v-html="i18n.t('repositories.import_records.steps.step3.updated_items')"></div>
<hr class="my-1">
<h2 class="m-0 text-sn-alert-green">0</h2>
<h2 class="m-0 text-sn-alert-green">{{ counters.updated }}</h2>
</div>
<div>
<div v-html="i18n.t('repositories.import_records.steps.step3.new_items')"></div>
<hr class="my-1">
<h2 class="m-0 text-sn-alert-green">0</h2>
<h2 class="m-0 text-sn-alert-green">{{ counters.created }}</h2>
</div>
<div>
<div v-html="i18n.t('repositories.import_records.steps.step3.unchanged_items')"></div>
<hr class="my-1">
<h2 class="m-0 ">0</h2>
<h2 class="m-0 ">{{ counters.unchanged }}</h2>
</div>
<div>
<div v-html="i18n.t('repositories.import_records.steps.step3.duplicated_items')"></div>
<hr class="my-1">
<h2 class="m-0 ">0</h2>
<h2 class="m-0 text-sn-alert-passion">{{ counters.duplicated }}</h2>
</div>
<div>
<div v-html="i18n.t('repositories.import_records.steps.step3.invalid_items')"></div>
<hr class="my-1">
<h2 class="m-0 text-sn-alert-passion">0</h2>
<h2 class="m-0 text-sn-alert-passion">{{ counters.invalid }}</h2>
</div>
<div>
<div v-html="i18n.t('repositories.import_records.steps.step3.invalid_items')"></div>
<div v-html="i18n.t('repositories.import_records.steps.step3.archived_items')"></div>
<hr class="my-1">
<h2 class="m-0 text-sn-alert-passion">0</h2>
<h2 class="m-0">{{ counters.archived }}</h2>
</div>
</div>
<div class="my-6">
@ -67,7 +67,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="$emit('changeStep', 'MappingStep')">
{{ i18n.t('repositories.import_records.steps.step3.cancel') }}
{{ i18n.t('general.back') }}
</button>
<button type="button" class="btn btn-primary" @click="$emit('importRows')">
{{ i18n.t('repositories.import_records.steps.step3.confirm') }}
@ -100,6 +100,16 @@ export default {
};
},
computed: {
counters() {
return {
updated: this.filterRows('updated').length,
created: this.filterRows('created').length,
unchanged: this.filterRows('unchanged').length,
duplicated: this.filterRows('duplicated').length,
invalid: this.filterRows('invalid').length,
archived: this.filterRows('archived').length
};
},
columnDefs() {
const columns = [
{
@ -142,6 +152,9 @@ export default {
}
},
methods: {
filterRows(status) {
return this.params.preview.data.filter((r) => r.attributes.import_status === status);
}
}
};
</script>

View file

@ -24,9 +24,6 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" >
{{ i18n.t('repositories.import_records.steps.step4.download_report') }}
</button>
<button type="button" class="btn btn-primary" @click="close">
{{ i18n.t('repositories.import_records.steps.step4.close') }}
</button>

View file

@ -85,13 +85,12 @@
</template>
<script>
import axios from '../../../../packs/custom_axios';
import DragAndDropUpload from '../../../shared/drag_and_drop_upload.vue';
import modalMixin from '../../../shared/modal_mixin';
export default {
name: 'UploadStep',
emits: ['uploadFile'],
emits: ['uploadFile', 'close'],
components: {
DragAndDropUpload
},

View file

@ -162,6 +162,7 @@ class Repository < RepositoryBase
def importable_repository_fields
fields = {}
# First and foremost add record name
fields['0'] = I18n.t('repositories.id_column')
fields['-1'] = I18n.t('repositories.default_column')
# Add all other custom columns
repository_columns.order(:created_at).each do |rc|

View file

@ -105,7 +105,7 @@ class RepositoryRow < ApplicationRecord
length: { maximum: Constants::NAME_MAX_LENGTH }
validates :created_by, presence: true
attr_accessor :import_status
attr_accessor :import_status, :import_message
scope :active, -> { where(archived: false) }
scope :archived, -> { where(archived: true) }

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
class RepositoryCellImportSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attributes :id, :value, :changes, :repository_column_id, :formatted_value
def changes
object.value.changes
end
def value
object.value
end
def formatted_value
object.value.formatted
end
end

View file

@ -3,11 +3,7 @@
class RepositoryCellSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attributes :id, :value, :changes, :repository_column_id, :formatted_value
def changes
object.value.changes
end
attributes :id, :value, :repository_column_id, :formatted_value
def value
object.value

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
class RepositoryRowImportSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attributes :id, :name, :code, :import_status, :import_message
has_many :repository_cells, serializer: RepositoryCellImportSerializer
attribute :code do
object.new_record? ? nil : object.code
end
end

View file

@ -3,7 +3,8 @@
class RepositoryRowSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attributes :id, :name, :code, :import_status
attributes :id, :name, :code
has_many :repository_cells, serializer: RepositoryCellImportSerializer
has_many :repository_cells, serializer: RepositoryCellSerializer
end

View file

@ -14,6 +14,7 @@ module RepositoryImportParser
def initialize(sheet, mappings, user, repository, can_edit_existing_items, should_overwrite_with_empty_cells, preview)
@columns = []
@name_index = -1
@id_index = nil
@total_new_rows = 0
@new_rows_added = 0
@header_skipped = false
@ -23,8 +24,8 @@ module RepositoryImportParser
@mappings = mappings
@user = user
@repository_columns = @repository.repository_columns
@can_edit_existing_items = can_edit_existing_items
@should_overwrite_with_empty_cells = should_overwrite_with_empty_cells
@can_edit_existing_items = true # can_edit_existing_items
@should_overwrite_with_empty_cells = true # should_overwrite_with_empty_cells
@preview = preview
end
@ -42,18 +43,12 @@ module RepositoryImportParser
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 Hash
new_repository_column = if @preview
@repository.repository_columns.new(created_by: @user, name: value['name'],
data_type: "Repository#{value['type']}Value")
else
@repository.repository_columns.create!(created_by: @user, name: value['name'],
data_type: "Repository#{value['type']}Value")
end
@columns << new_repository_column
else
@columns << @repository_columns.where(data_type: Extends::REPOSITORY_IMPORTABLE_TYPES)
.preload(Extends::REPOSITORY_IMPORT_COLUMN_PRELOADS)
@ -91,8 +86,10 @@ module RepositoryImportParser
end
@total_new_rows += 1
incoming_row = SpreadsheetParser.parse_row(row, @sheet, date_format: @user.settings['date_format'])
existing_row = RepositoryRow.includes(repository_cells: :value)
.find_by(id: incoming_row[0].to_s.gsub(RepositoryRow::ID_PREFIX, ''))
if @id_index
existing_row = RepositoryRow.includes(repository_cells: :value)
.find_by(id: incoming_row[@id_index].to_s.gsub(RepositoryRow::ID_PREFIX, ''))
end
if existing_row.present?
if !@can_edit_existing_items
@ -100,7 +97,8 @@ module RepositoryImportParser
elsif existing_row.archived
existing_row.import_status = 'archived'
elsif existing_row.repository_id != @repository.id
existing_row.import_status = 'incorrect_inventory'
existing_row.import_status = 'invalid'
existing_row.import_message = 'Item belongs to another repository'
elsif duplicate_ids.include?(existing_row.id)
existing_row.import_status = 'duplicated'
end
@ -113,13 +111,15 @@ module RepositoryImportParser
checked_rows << import_row(existing_row, incoming_row)
end
p checked_rows
changes = ActiveModelSerializers::SerializableResource.new(
checked_rows.compact,
each_serializer: RepositoryRowSerializer,
each_serializer: RepositoryRowImportSerializer,
include: [:repository_cells]
).as_json
p changes
{ status: :ok, nr_of_added: @new_rows_added, total_nr: @total_new_rows, changes: changes,
import_date: I18n.l(Date.today, format: :full_date) }
end
@ -129,7 +129,6 @@ module RepositoryImportParser
@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
@ -140,7 +139,12 @@ module RepositoryImportParser
import_status: 'created')
end
@preview ? repository_row.validate : repository_row.save!
if @preview
repository_row.validate
repository_row.id ||= SecureRandom.uuid # 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?
@ -180,7 +184,7 @@ module RepositoryImportParser
@errors << existing_cell.value.errors.full_messages.join(',') if existing_cell&.value&.errors.present?
end
repository_row.import_status = if @errors.present?
@errors.join(',')
'invalid'
elsif repository_row.import_status == 'created'
@new_rows_added += 1
'created'
@ -190,6 +194,7 @@ module RepositoryImportParser
else
'unchanged'
end
repository_row.import_message = @errors.join(',') if @errors.present?
repository_row
rescue ActiveRecord::RecordInvalid
raise ActiveRecord::Rollback

View file

@ -2226,9 +2226,10 @@ en:
importedFileText: 'Imported file:'
cancelBtnText: 'Cancel'
confirmBtnText: 'Confirm'
importedIgnoredSection: '<b>%{imported}</b> columns to <b>import.</b> <b>%{ignored}</b> columns <b>ignored</b>.'
importedIgnoredSection: '<b>%{imported}</b> columns to <b>import.</b> <b>%{ignored}</b> columns <b>ignored.</b>'
computedDropdownOptions:
name: 'Name'
id: 'ID'
RepositoryTextValue: 'Text'
RepositoryNumberValue: 'Number'
RepositoryAssetValue: 'File'
@ -2272,12 +2273,12 @@ en:
step3:
title: 'Import preview'
subtitle: 'This is a preview of items you are importing/updating to the %{inventory}. The import can still be canceled.'
updated_items: 'Updated<br>items'
new_items: 'New<br>items'
unchanged_items: 'Unchanged<br>items'
duplicated_items: 'Duplicated<br>items'
invalid_items: 'Invalid<br>items'
invalid_cells: 'Invalid<br>cells'
updated_items: 'Updated'
new_items: 'New'
unchanged_items: 'Unchanged'
duplicated_items: 'Duplicated'
invalid_items: 'Invalid'
archived_items: 'Archived'
code: 'Code'
name: 'Name'
status: 'Status'
@ -2425,6 +2426,7 @@ en:
no_records_selected_flash: "There were no selected items."
no_deleted_records_flash: "No items were deleted. %{other_records_number} of the selected items were created by other users and were not deleted."
default_column: 'Name'
id_column: 'Item ID'
copy_records_report: "%{number} item(s) successfully copied."
archive_inventories:
success_flash: "Inventories were successfully archived!"