mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-08 14:15:35 +08:00
Fix repository import mapping and preview [SCI-10773]
This commit is contained in:
parent
060a694fc0
commit
2a2c2b1347
15 changed files with 191 additions and 238 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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) }
|
||||
|
|
19
app/serializers/repository_cell_import_serializer.rb
Normal file
19
app/serializers/repository_cell_import_serializer.rb
Normal 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
|
|
@ -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
|
||||
|
|
13
app/serializers/repository_row_import_serializer.rb
Normal file
13
app/serializers/repository_row_import_serializer.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!"
|
||||
|
|
Loading…
Add table
Reference in a new issue