mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-05 19:34:34 +08:00
Create 'import mapping' step [SCI-10579]
This commit is contained in:
parent
bc6e43052f
commit
db3ba2aadb
16 changed files with 683 additions and 122 deletions
|
@ -327,13 +327,14 @@ class RepositoriesController < ApplicationController
|
|||
.find_by_id(import_params[:id]))
|
||||
|
||||
# Access the checkbox values from params
|
||||
can_edit_existing_items = params[:edit_existing_items_checkbox]
|
||||
should_overwrite_with_empty_cells = params[:overwrite_with_empty_cells]
|
||||
can_edit_existing_items = params[:can_edit_existing_items]
|
||||
should_overwrite_with_empty_cells = params[:should_overwrite_with_empty_cells]
|
||||
preview = params[:preview]
|
||||
|
||||
# Check if there exist mapping for repository record (it's mandatory)
|
||||
if import_params[:mappings].present? && import_params[:mappings].value?('-1')
|
||||
import_records = repostiory_import_actions
|
||||
status = import_records.import!(can_edit_existing_items, should_overwrite_with_empty_cells)
|
||||
status = import_records.import!(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
|
||||
if status[:status] == :ok
|
||||
log_activity(:import_inventory_items,
|
||||
|
@ -343,10 +344,12 @@ class RepositoriesController < ApplicationController
|
|||
number_of_rows: status[:nr_of_added],
|
||||
total_nr: status[:total_nr])
|
||||
|
||||
log_activity(:item_added_with_import,
|
||||
num_of_items: status[:nr_of_added])
|
||||
if preview
|
||||
render json: status, status: :ok
|
||||
else
|
||||
render json: {}, status: :ok
|
||||
end
|
||||
|
||||
render json: {}, status: :ok
|
||||
else
|
||||
flash[:alert] =
|
||||
t('repositories.import_records.partial_success_flash',
|
||||
|
@ -558,7 +561,7 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def import_params
|
||||
params.permit(:id, :file, :file_id, mappings: {}).to_h
|
||||
params.permit(:id, :file, :file_id, :preview, mappings: {}).to_h
|
||||
end
|
||||
|
||||
def repository_response(message)
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
:startHidden="true"
|
||||
:infoParams="infoParams"
|
||||
:title="steps[activeStep].title"
|
||||
:helpText="steps[activeStep].helpText">
|
||||
:subtitle="steps[activeStep].subtitle"
|
||||
:helpText="steps[activeStep].helpText"
|
||||
>
|
||||
<component
|
||||
:key="steps[activeStep].id"
|
||||
:is="steps[activeStep].component"
|
||||
@step:next="proceedToNext"
|
||||
@step:back="activeStep -= 1"
|
||||
:stepData="stepData"
|
||||
@step:next="proceedToNextStep"
|
||||
@step:back="goBackToPrevStep"
|
||||
:stepProps="steps[activeStep].stepData"
|
||||
/>
|
||||
</InfoModal>
|
||||
</template>
|
||||
|
@ -19,86 +22,91 @@
|
|||
import { shallowRef } from 'vue';
|
||||
import InfoModal from '../../shared/info_modal.vue';
|
||||
import FirstStep from './import/first_step.vue';
|
||||
import SecondStep from './import/second_step.vue';
|
||||
|
||||
export default {
|
||||
name: 'ImportRepositoryModal',
|
||||
components: { InfoModal, FirstStep },
|
||||
components: { InfoModal, FirstStep, SecondStep },
|
||||
props: {
|
||||
repositoryUrl: String, required: true
|
||||
repositoryUrl: String,
|
||||
required: true
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeStep: 0,
|
||||
repositoryData: null,
|
||||
steps: [
|
||||
{
|
||||
id: I18n.t('repositories.import_records.steps.step0.id'),
|
||||
icon: I18n.t('repositories.import_records.steps.step0.icon'),
|
||||
label: I18n.t('repositories.import_records.steps.step0.label'),
|
||||
title: I18n.t('repositories.import_records.steps.step0.title'),
|
||||
helpText: I18n.t('repositories.import_records.steps.step0.helpText'),
|
||||
component: shallowRef(FirstStep)
|
||||
id: this.i18n.t('repositories.import_records.steps.step1.id'),
|
||||
icon: this.i18n.t('repositories.import_records.steps.step1.icon'),
|
||||
label: this.i18n.t('repositories.import_records.steps.step1.label'),
|
||||
title: this.i18n.t('repositories.import_records.steps.step1.title'),
|
||||
subtitle: this.i18n.t('repositories.import_records.steps.step1.subtitle'),
|
||||
helpText: this.i18n.t('repositories.import_records.steps.step1.helpText'),
|
||||
component: shallowRef(FirstStep),
|
||||
stepData: null
|
||||
},
|
||||
{
|
||||
id: this.i18n.t('repositories.import_records.steps.step2.id'),
|
||||
icon: this.i18n.t('repositories.import_records.steps.step2.icon'),
|
||||
label: this.i18n.t('repositories.import_records.steps.step2.label'),
|
||||
title: this.i18n.t('repositories.import_records.steps.step2.title'),
|
||||
subtitle: this.i18n.t('repositories.import_records.steps.step2.subtitle'),
|
||||
component: shallowRef(SecondStep),
|
||||
stepData: null
|
||||
}
|
||||
],
|
||||
infoParams: {
|
||||
title: I18n.t('repositories.import_records.info_sidebar.title'),
|
||||
title: this.i18n.t('repositories.import_records.info_sidebar.title'),
|
||||
elements: [
|
||||
{
|
||||
id: I18n.t('repositories.import_records.info_sidebar.elements.element0.id'),
|
||||
icon: I18n.t('repositories.import_records.info_sidebar.elements.element0.icon'),
|
||||
label: I18n.t('repositories.import_records.info_sidebar.elements.element0.label'),
|
||||
subtext: I18n.t('repositories.import_records.info_sidebar.elements.element0.subtext')
|
||||
id: this.i18n.t('repositories.import_records.info_sidebar.elements.element0.id'),
|
||||
icon: this.i18n.t('repositories.import_records.info_sidebar.elements.element0.icon'),
|
||||
label: this.i18n.t('repositories.import_records.info_sidebar.elements.element0.label'),
|
||||
subtext: this.i18n.t('repositories.import_records.info_sidebar.elements.element0.subtext')
|
||||
},
|
||||
{
|
||||
id: I18n.t('repositories.import_records.info_sidebar.elements.element1.id'),
|
||||
icon: I18n.t('repositories.import_records.info_sidebar.elements.element1.icon'),
|
||||
label: I18n.t('repositories.import_records.info_sidebar.elements.element1.label'),
|
||||
subtext: I18n.t('repositories.import_records.info_sidebar.elements.element1.subtext')
|
||||
id: this.i18n.t('repositories.import_records.info_sidebar.elements.element1.id'),
|
||||
icon: this.i18n.t('repositories.import_records.info_sidebar.elements.element1.icon'),
|
||||
label: this.i18n.t('repositories.import_records.info_sidebar.elements.element1.label'),
|
||||
subtext: this.i18n.t('repositories.import_records.info_sidebar.elements.element1.subtext')
|
||||
},
|
||||
{
|
||||
id: I18n.t('repositories.import_records.info_sidebar.elements.element2.id'),
|
||||
icon: I18n.t('repositories.import_records.info_sidebar.elements.element2.icon'),
|
||||
label: I18n.t('repositories.import_records.info_sidebar.elements.element2.label'),
|
||||
subtext: I18n.t('repositories.import_records.info_sidebar.elements.element2.subtext')
|
||||
id: this.i18n.t('repositories.import_records.info_sidebar.elements.element2.id'),
|
||||
icon: this.i18n.t('repositories.import_records.info_sidebar.elements.element2.icon'),
|
||||
label: this.i18n.t('repositories.import_records.info_sidebar.elements.element2.label'),
|
||||
subtext: this.i18n.t('repositories.import_records.info_sidebar.elements.element2.subtext')
|
||||
},
|
||||
{
|
||||
id: I18n.t('repositories.import_records.info_sidebar.elements.element3.id'),
|
||||
icon: I18n.t('repositories.import_records.info_sidebar.elements.element3.icon'),
|
||||
label: I18n.t('repositories.import_records.info_sidebar.elements.element3.label'),
|
||||
subtext: I18n.t('repositories.import_records.info_sidebar.elements.element3.subtext')
|
||||
id: this.i18n.t('repositories.import_records.info_sidebar.elements.element3.id'),
|
||||
icon: this.i18n.t('repositories.import_records.info_sidebar.elements.element3.icon'),
|
||||
label: this.i18n.t('repositories.import_records.info_sidebar.elements.element3.label'),
|
||||
subtext: this.i18n.t('repositories.import_records.info_sidebar.elements.element3.subtext')
|
||||
},
|
||||
{
|
||||
id: I18n.t('repositories.import_records.info_sidebar.elements.element4.id'),
|
||||
icon: I18n.t('repositories.import_records.info_sidebar.elements.element4.icon'),
|
||||
label: I18n.t('repositories.import_records.info_sidebar.elements.element4.label'),
|
||||
subtext: I18n.t('repositories.import_records.info_sidebar.elements.element4.subtext'),
|
||||
linkTo: I18n.t('repositories.import_records.info_sidebar.elements.element4.linkTo')
|
||||
id: this.i18n.t('repositories.import_records.info_sidebar.elements.element4.id'),
|
||||
icon: this.i18n.t('repositories.import_records.info_sidebar.elements.element4.icon'),
|
||||
label: this.i18n.t('repositories.import_records.info_sidebar.elements.element4.label'),
|
||||
subtext: this.i18n.t('repositories.import_records.info_sidebar.elements.element4.subtext'),
|
||||
linkTo: this.i18n.t('repositories.import_records.info_sidebar.elements.element4.linkTo')
|
||||
}
|
||||
]
|
||||
},
|
||||
stepData: null
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
activeStep(newVal, oldVal) {
|
||||
console.log(`${oldVal} -> ${newVal}`);
|
||||
},
|
||||
stepData(newVal, oldVal) {
|
||||
console.log(`${oldVal} -> ${newVal}`);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
window.importRepositoryModalComponent = this;
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.$refs.modal.open();
|
||||
},
|
||||
proceedToNext(data) {
|
||||
console.log('incoming data', data);
|
||||
this.stepData = data;
|
||||
proceedToNextStep(data) {
|
||||
this.steps[this.activeStep + 1].stepData = data;
|
||||
this.activeStep += 1;
|
||||
},
|
||||
goBackToPrevStep() {
|
||||
this.activeStep -= 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -7,16 +7,16 @@
|
|||
<!-- export -->
|
||||
<div id="export-section" class="flex flex-col gap-3">
|
||||
<h3 class="my-0 text-sn-dark-grey">
|
||||
{{ i18n.t('repositories.import_records.steps.step0.importTitle') }}
|
||||
{{ i18n.t('repositories.import_records.steps.step1.importTitle') }}
|
||||
</h3>
|
||||
<div id="export-buttons" class="flex flex-row gap-4">
|
||||
<button class="btn btn-secondary btn-sm" @click="exportFullInventory">
|
||||
<i class="sn-icon sn-icon-export"></i>
|
||||
{{ i18n.t('repositories.import_records.steps.step0.exportFullInvBtnText') }}
|
||||
{{ i18n.t('repositories.import_records.steps.step1.exportFullInvBtnText') }}
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-sm">
|
||||
<i class="sn-icon sn-icon-export"></i>
|
||||
{{ i18n.t('repositories.import_records.steps.step0.exportEmptyInvBtnText') }}
|
||||
{{ i18n.t('repositories.import_records.steps.step1.exportEmptyInvBtnText') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -24,13 +24,13 @@
|
|||
<!-- import -->
|
||||
<div id="import-section" class="flex flex-col gap-3 h-full w-full">
|
||||
<h3 class="my-0 text-sn-dark-grey">
|
||||
{{ i18n.t('repositories.import_records.steps.step0.importBtnText') }}
|
||||
{{ i18n.t('repositories.import_records.steps.step1.importBtnText') }}
|
||||
</h3>
|
||||
<DragAndDropUpload
|
||||
@file:dropped="uploadFile"
|
||||
@file:error="handleError"
|
||||
@file:error:clear="this.error = null"
|
||||
:supportingText="`${i18n.t('repositories.import_records.steps.step0.dragAndDropSupportingText')}`"
|
||||
:supportingText="`${i18n.t('repositories.import_records.steps.step1.dragAndDropSupportingText')}`"
|
||||
:supportedFormats="['xlsx', 'csv', 'xls', 'txt', 'tsv']"
|
||||
/>
|
||||
</div>
|
||||
|
@ -50,7 +50,7 @@
|
|||
<div class="my-auto">{{ exportInventoryMessage }}</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary" data-dismiss="modal" aria-label="Close">
|
||||
{{ i18n.t('repositories.import_records.steps.step0.cancelBtnText') }}
|
||||
{{ i18n.t('repositories.import_records.steps.step1.cancelBtnText') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -62,10 +62,16 @@ import DragAndDropUpload from '../../../shared/drag_and_drop_upload.vue';
|
|||
|
||||
export default {
|
||||
name: 'FirstStep',
|
||||
emits: ['step:next'],
|
||||
emits: ['step:next', 'info:hide'],
|
||||
components: {
|
||||
DragAndDropUpload
|
||||
},
|
||||
props: {
|
||||
stepProps: {
|
||||
type: Object,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showingInfo: false,
|
||||
|
@ -136,14 +142,23 @@ export default {
|
|||
// First, parse the sheet
|
||||
const parsedSheetResponse = await this.parseSheet(file);
|
||||
|
||||
// If parsed successfully, go to next step and pass the necessary data
|
||||
// If parsed successfully, go to next step and pass through the necessary data
|
||||
if (parsedSheetResponse) {
|
||||
const {
|
||||
header: columnNames,
|
||||
available_fields: availableFields,
|
||||
columns: exampleData
|
||||
} = parsedSheetResponse.data.import_data;
|
||||
this.$emit('step:next', { columnNames, availableFields, exampleData });
|
||||
const fileName = file.name;
|
||||
const tempFile = parsedSheetResponse.data.temp_file;
|
||||
|
||||
this.$emit('step:next', {
|
||||
columnNames,
|
||||
availableFields,
|
||||
exampleData,
|
||||
fileName,
|
||||
tempFile
|
||||
});
|
||||
}
|
||||
},
|
||||
async parseSheet(file) {
|
||||
|
|
329
app/javascript/vue/repositories/modals/import/second_step.vue
Normal file
329
app/javascript/vue/repositories/modals/import/second_step.vue
Normal file
|
@ -0,0 +1,329 @@
|
|||
<template>
|
||||
<div ref="secondStep" class="flex flex-col gap-6 h-full">
|
||||
|
||||
<!-- body -->
|
||||
<div class="flex flex-col gap-6 h-fit w-full">
|
||||
|
||||
<!-- toggle section -->
|
||||
<div id="toggle-section" class="flex flex-row gap-6">
|
||||
<!-- auto-mapping -->
|
||||
<div id="auto-mapping-toggle" class="flex flex-row gap-1">
|
||||
<span class="sci-toggle-checkbox-container">
|
||||
<input type="checkbox"
|
||||
class="sci-toggle-checkbox"
|
||||
v-model="autoMapping"
|
||||
/>
|
||||
<span class="sci-toggle-checkbox-label"></span>
|
||||
</span>
|
||||
<div class="flex my-auto w-32">
|
||||
{{ i18n.t('repositories.import_records.steps.step2.autoMappingText') }} {{ autoMapping ? 'ON' : 'OFF' }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- update empty cells -->
|
||||
<div id="update-empty-cells" class="flex flex-row gap-1">
|
||||
<div class="sci-checkbox-container my-auto">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="sci-checkbox"
|
||||
:checked="updateWithEmptyCells"
|
||||
@change="toggleUpdateWithEmptyCells"
|
||||
/>
|
||||
<label class="sci-checkbox-label"></label>
|
||||
</div>
|
||||
<div class="flex my-auto">
|
||||
{{ i18n.t('repositories.import_records.steps.step2.updateEmptyCellsText') }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- only add new items -->
|
||||
<div id="only-add-new-items" class="flex flex-row gap-1">
|
||||
<div class="sci-checkbox-container my-auto">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="sci-checkbox"
|
||||
:checked="onlyAddNewItems"
|
||||
@change="toggleOnlyAddNewItems"
|
||||
/>
|
||||
<label class="sci-checkbox-label"></label>
|
||||
</div>
|
||||
<div class="flex my-auto">
|
||||
{{ i18n.t('repositories.import_records.steps.step2.onlyAddNewItemsText') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- imported file section -->
|
||||
<div class="flex flex-row text-sn-black">
|
||||
{{ i18n.t('repositories.import_records.steps.step2.importedFileText') }} {{ stepProps.fileName }}
|
||||
</div>
|
||||
|
||||
<div id="table-section" class="flex flex-col w-full h-full gap-1">
|
||||
<!-- divider -->
|
||||
<div class="sci-divider"></div>
|
||||
|
||||
<!-- table -->
|
||||
<div id="table" class="flex flex-col h-[28rem] w-full">
|
||||
<!-- labels -->
|
||||
<div id="column-labels" class="flex flex-row justify-between font-bold p-3">
|
||||
<div class="w-6">{{ i18n.t('repositories.import_records.steps.step2.table.columnLabels.number') }}</div>
|
||||
<div class="w-40">{{ i18n.t('repositories.import_records.steps.step2.table.columnLabels.importedColumns') }}</div>
|
||||
<div class="w-6"></div>
|
||||
<div class="w-60">{{ i18n.t('repositories.import_records.steps.step2.table.columnLabels.scinoteColumns') }}</div>
|
||||
<div class="w-14">{{ i18n.t('repositories.import_records.steps.step2.table.columnLabels.status') }}</div>
|
||||
<div class="w-56">{{ i18n.t('repositories.import_records.steps.step2.table.columnLabels.exampleData') }}</div>
|
||||
</div>
|
||||
|
||||
<div id="table-rows" ref="tableRowsRef" class="w-full h-[28rem] flex flex-col py-4 overflow-auto gap-1">
|
||||
<!-- rows -->
|
||||
<div v-for="(item, index) in stepProps.columnNames" :key="item"
|
||||
class="flex flex-col gap-4 min-h-[56px] justify-center px-4 rounded"
|
||||
:class="{'bg-sn-super-light-blue': this.selectedItemsIndexes.includes(index)}"
|
||||
>
|
||||
<SecondStepTableRow
|
||||
:key="item"
|
||||
:index="index"
|
||||
:item="item"
|
||||
:dropdownOptions="computedDropdownOptions"
|
||||
:stepProps="stepProps"
|
||||
@selection:changed="handleChange"
|
||||
:availableFields="this.availableFields"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- divider -->
|
||||
<div class="sci-divider"></div>
|
||||
|
||||
<!-- imported/ignored section -->
|
||||
<div class="flex flex-row">
|
||||
<b class="pr-1">{{ computedImportedIgnoredInfo.importedSum }}</b>
|
||||
<div class="pr-1">{{ i18n.t('repositories.import_records.steps.step2.importedIgnoredSection.columnsTo') }}</div>
|
||||
<b class="pr-1">{{ i18n.t('repositories.import_records.steps.step2.importedIgnoredSection.import') }}</b>
|
||||
<b class="pr-1">{{ computedImportedIgnoredInfo.ignoredSum }}</b>
|
||||
<div class="pr-1">{{ i18n.t('repositories.import_records.steps.step2.importedIgnoredSection.columns') }}</div>
|
||||
<b>{{ i18n.t('repositories.import_records.steps.step2.importedIgnoredSection.ignored') }}</b>
|
||||
</div>
|
||||
|
||||
<!-- divider -->
|
||||
<div class="sci-divider"></div>
|
||||
|
||||
<!-- footer -->
|
||||
<div class="flex justify-between">
|
||||
<div id="error" class="flex flex-row gap-3 text-sn-delete-red">
|
||||
<i v-if="error" class="sn-icon sn-icon-alert-warning my-auto"></i>
|
||||
<div class="my-auto">{{ error ? error : '' }}</div>
|
||||
</div>
|
||||
|
||||
<div id="buttons" class="flex gap-4">
|
||||
<button class="btn btn-secondary" data-dismiss="modal" aria-label="Close">
|
||||
{{ i18n.t('repositories.import_records.steps.step2.cancelBtnText') }}
|
||||
</button>
|
||||
|
||||
<button class="btn btn-primary" @click="importRecords">
|
||||
{{ i18n.t('repositories.import_records.steps.step2.confirmBtnText') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from '../../../../packs/custom_axios';
|
||||
import SelectDropdown from '../../../shared/select_dropdown.vue';
|
||||
import SecondStepTableRow from './second_step_table_row.vue';
|
||||
|
||||
export default {
|
||||
name: 'SecondStep',
|
||||
emits: ['step:next'],
|
||||
components: {
|
||||
SelectDropdown,
|
||||
SecondStepTableRow
|
||||
},
|
||||
props: {
|
||||
stepProps: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
file: {
|
||||
type: File,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
autoMapping: false,
|
||||
updateWithEmptyCells: false,
|
||||
onlyAddNewItems: false,
|
||||
columnLabels: {
|
||||
0: this.i18n.t('repositories.import_records.steps.step2.table.columnLabels.number'),
|
||||
1: this.i18n.t('repositories.import_records.steps.step2.table.columnLabels.importedColumns'),
|
||||
2: this.i18n.t('repositories.import_records.steps.step2.table.columnLabels.scinoteColumns'),
|
||||
3: this.i18n.t('repositories.import_records.steps.step2.table.columnLabels.status'),
|
||||
4: this.i18n.t('repositories.import_records.steps.step2.table.columnLabels.exampleData')
|
||||
},
|
||||
selectedItems: [],
|
||||
selectedItemsIndexes: [],
|
||||
importRecordsUrl: null,
|
||||
teamId: null,
|
||||
repositoryId: null,
|
||||
availableFields: [],
|
||||
alwaysAvailableFields: [],
|
||||
repositoryColumns: null,
|
||||
error: null
|
||||
};
|
||||
},
|
||||
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);
|
||||
|
||||
// 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 }];
|
||||
}
|
||||
|
||||
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 = [];
|
||||
},
|
||||
generateMapping() {
|
||||
const mapping = {};
|
||||
for (let i = 0; i < this.stepProps.columnNames.length; i++) {
|
||||
const foundItem = this.selectedItems.find((item) => item.index === i);
|
||||
if (foundItem) {
|
||||
mapping[foundItem.index] = foundItem.key;
|
||||
} else {
|
||||
mapping[i] = '';
|
||||
}
|
||||
}
|
||||
return mapping;
|
||||
},
|
||||
async importRecords() {
|
||||
const selectedItemsKeys = new Set(this.selectedItems.map((item) => item.key));
|
||||
if (!selectedItemsKeys.has('-1')) {
|
||||
this.error = this.i18n.t('repositories.import_records.steps.step2.selectNamePropertyError');
|
||||
return '';
|
||||
}
|
||||
|
||||
const mapping = this.generateMapping();
|
||||
|
||||
const jsonData = {
|
||||
file_id: this.stepProps.tempFile.id,
|
||||
mappings: mapping,
|
||||
id: this.teamId,
|
||||
preview: true,
|
||||
should_overwrite_with_empty_cells: this.updateWithEmptyCells,
|
||||
can_edit_existing_items: !this.onlyAddNewItems
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await axios.post(this.importRecordsUrl, jsonData);
|
||||
if (!response.status === 200) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
async fetchSerializedRepositoryData() {
|
||||
const url = window.location.pathname;
|
||||
try {
|
||||
const response = await axios.get(url);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
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) {
|
||||
const options = this.availableFields.map((el) => [String(el.key), `${String(el.value)} (${columnKeyToLabelMapping[el.key]})`]);
|
||||
return options;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
computedImportedIgnoredInfo() {
|
||||
const importedSum = this.selectedItems.length;
|
||||
const ignoredSum = this.stepProps.columnNames.length - importedSum;
|
||||
return { importedSum, ignoredSum };
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
// Fetch repository data and set it to state
|
||||
const repositoryData = await this.fetchSerializedRepositoryData();
|
||||
this.teamId = String(repositoryData.data.attributes.team_id);
|
||||
this.repositoryId = String(repositoryData.data.id);
|
||||
this.importRecordsUrl = repositoryData.data.attributes.urls.import_records;
|
||||
this.repositoryColumns = repositoryData.data.attributes.repository_columns;
|
||||
|
||||
// Adding alreadySelected attribute for tracking
|
||||
const tempAvailableFields = [];
|
||||
Object.entries(this.stepProps.availableFields).forEach(([key, value]) => {
|
||||
const field = { key, value, alreadySelected: false };
|
||||
tempAvailableFields.push(field);
|
||||
});
|
||||
this.availableFields = tempAvailableFields;
|
||||
this.alwaysAvailableFields = tempAvailableFields;
|
||||
|
||||
// Remove infoComponent if it's still present
|
||||
const infoComponent = this.$parent.$refs.infoPartRef;
|
||||
infoComponent.remove();
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,117 @@
|
|||
<template>
|
||||
<!-- columns -->
|
||||
<div class="flex flex-row justify-between gap-6">
|
||||
<!-- number col -->
|
||||
<div class="w-6 my-auto">{{ index + 1 }}</div>
|
||||
|
||||
<div class="w-40 my-auto truncate" :title="item">{{ item }}</div>
|
||||
|
||||
<i class="sn-icon sn-icon-arrow-right w-6 my-auto relative left-5"></i>
|
||||
|
||||
<div class="w-60 my-auto">
|
||||
|
||||
<!-- system generated data -->
|
||||
<SelectDropdown v-if="systemGeneratedData.includes(item)"
|
||||
:disabled="true"
|
||||
:placeholder="String(item)"
|
||||
></SelectDropdown>
|
||||
|
||||
<SelectDropdown
|
||||
v-else
|
||||
:options="dropdownOptions"
|
||||
@change="changeSelected"
|
||||
@isOpen="handleIsOpen"
|
||||
:clearable="true"
|
||||
:size="'sm'"
|
||||
placeholder="Do not import"
|
||||
:title="this.selectedColumnType?.value"
|
||||
></SelectDropdown>
|
||||
</div>
|
||||
|
||||
<div class="w-14 my-auto flex justify-center">
|
||||
<!-- 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=""></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>
|
||||
</div>
|
||||
|
||||
<div class="w-56 truncate my-auto" :title="stepProps.exampleData[index]">{{ stepProps.exampleData[index] }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectDropdown from '../../../shared/select_dropdown.vue';
|
||||
|
||||
export default {
|
||||
name: 'SecondStepTableRow',
|
||||
emits: ['selection:changed'],
|
||||
components: {
|
||||
SelectDropdown
|
||||
},
|
||||
props: {
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
dropdownOptions: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
item: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
stepProps: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedColumnType: null,
|
||||
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')]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeSelected(e) {
|
||||
const value = this.stepProps.availableFields[e];
|
||||
const selectedColumnType = { index: this.index, key: e, value };
|
||||
this.selectedColumnType = selectedColumnType;
|
||||
this.$emit('selection:changed', selectedColumnType);
|
||||
},
|
||||
handleIsOpen(isOpen) {
|
||||
const tableRows = this.$parent.$refs.tableRowsRef;
|
||||
if (isOpen) {
|
||||
tableRows.style.overflow = 'hidden';
|
||||
} else tableRows.style.overflow = 'auto';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false">
|
||||
<div class="modal-dialog" role="document" :class="[{'!w-[900px]' : showingInfo}, {'!w-[600px]' : !showingInfo}]">
|
||||
<div class="modal-dialog" role="document" :class="[{'!w-[900px]' : showingInfo}, {'!w-fit' : !showingInfo}]">
|
||||
<div class="modal-content !p-0 bg-sn-white w-full h-full flex" :class="[{'flex-row': showingInfo}, {'flex-col': !showingInfo}]">
|
||||
<div id="body-container" class="flex flex-row w-full h-full">
|
||||
<!-- info -->
|
||||
<div id="info-part">
|
||||
<div id="info-part" ref="infoPartRef">
|
||||
<InfoComponent
|
||||
v-if="showingInfo"
|
||||
:infoParams="infoParams"
|
||||
|
@ -13,17 +13,22 @@
|
|||
<!-- content -->
|
||||
<div id="content-part" class="flex flex-col w-full p-6 gap-6">
|
||||
<!-- header -->
|
||||
<div id="info-modal-header" class="flex flex-row h-fit w-full justify-between">
|
||||
<div id="title-with-help" class="flex flex-row gap-3">
|
||||
<h3 class="modal-title text-sn-dark-grey">{{ title }}</h3>
|
||||
<button class="btn btn-light btn-sm" @click="showingInfo = !showingInfo">
|
||||
<i class="sn-icon sn-icon-help-s"></i>
|
||||
{{ helpText }}
|
||||
<div id="info-modal-header" class="flex flex-col h-fit w-full gap-2">
|
||||
<div id="title-part" class="flex flex-row h-fit w-full justify-between">
|
||||
<div id="title-with-help" class="flex flex-row gap-3">
|
||||
<h3 class="modal-title text-sn-dark-grey">{{ title }}</h3>
|
||||
<button v-if="helpText" class="btn btn-light btn-sm" @click="showingInfo = !showingInfo">
|
||||
<i class="sn-icon sn-icon-help-s"></i>
|
||||
{{ helpText }}
|
||||
</button>
|
||||
</div>
|
||||
<button id="close-btn" type="button" class="close my-auto" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button id="close-btn" type="button" class="close my-auto" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<div id="subtitle-part" class="text-sn-dark-grey">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- main content -->
|
||||
<div id="info-modal-main-content" class="h-full">
|
||||
|
@ -47,9 +52,13 @@ export default {
|
|||
type: String,
|
||||
required: true
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
helpText: {
|
||||
type: String,
|
||||
required: true
|
||||
required: false
|
||||
},
|
||||
infoParams: {
|
||||
type: Object,
|
||||
|
@ -64,7 +73,7 @@ export default {
|
|||
components: { InfoComponent },
|
||||
data() {
|
||||
return {
|
||||
showingInfo: true
|
||||
showingInfo: false
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -253,7 +253,8 @@ export default {
|
|||
value(newValue) {
|
||||
this.newValue = newValue;
|
||||
},
|
||||
isOpen() {
|
||||
isOpen(newVal) {
|
||||
this.$emit('isOpen', newVal);
|
||||
if (this.isOpen) {
|
||||
this.$nextTick(() => {
|
||||
this.setPosition();
|
||||
|
@ -284,7 +285,7 @@ export default {
|
|||
clear() {
|
||||
this.newValue = this.multiple ? [] : null;
|
||||
this.query = '';
|
||||
this.$emit('change', this.newValue, this.getLabels(this.newValue));
|
||||
this.$emit('change', this.newValue, '');
|
||||
},
|
||||
close(e) {
|
||||
if (e && e.target.closest('.sn-select-dropdown')) return;
|
||||
|
|
|
@ -201,9 +201,9 @@ class Repository < RepositoryBase
|
|||
new_repo
|
||||
end
|
||||
|
||||
def import_records(sheet, mappings, user, can_edit_existing_items, should_overwrite_with_empty_cells)
|
||||
def import_records(sheet, mappings, user, can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
importer = RepositoryImportParser::Importer.new(sheet, mappings, user, self)
|
||||
importer.run(can_edit_existing_items, should_overwrite_with_empty_cells)
|
||||
importer.run(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
end
|
||||
|
||||
def assigned_rows(my_module)
|
||||
|
|
|
@ -72,7 +72,6 @@ class RepositoryChecklistValue < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: after ticket for tracking changes on checklist items
|
||||
def update_data!(new_data, user, preview: false)
|
||||
item_ids = new_data.is_a?(String) ? JSON.parse(new_data) : new_data
|
||||
|
||||
|
|
15
app/serializers/repository_cell_serializer.rb
Normal file
15
app/serializers/repository_cell_serializer.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryCellSerializer < ActiveModel::Serializer
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :id, :value, :changes
|
||||
|
||||
def changes
|
||||
object.value.changes
|
||||
end
|
||||
|
||||
def value
|
||||
object.value
|
||||
end
|
||||
end
|
|
@ -4,9 +4,5 @@ class RepositoryRowSerializer < ActiveModel::Serializer
|
|||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :id, :name, :code
|
||||
|
||||
def urls
|
||||
{
|
||||
}
|
||||
end
|
||||
has_many :repository_cells
|
||||
end
|
||||
|
|
|
@ -3,11 +3,16 @@
|
|||
class RepositorySerializer < ActiveModel::Serializer
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :urls, :id, :team_id
|
||||
attributes :urls, :id, :team_id, :repository_columns
|
||||
|
||||
def repository_columns
|
||||
object.repository_columns.pluck(:id, :name, :data_type)
|
||||
end
|
||||
|
||||
def urls
|
||||
{
|
||||
parse_sheet: parse_sheet_repository_path(object)
|
||||
parse_sheet: parse_sheet_repository_path(object),
|
||||
import_records: import_records_repository_path(object)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,22 +8,23 @@ module ImportRepository
|
|||
@user = options.fetch(:user)
|
||||
end
|
||||
|
||||
def import!(can_edit_existing_items, should_overwrite_with_empty_cells)
|
||||
status = run_import_actions(can_edit_existing_items, should_overwrite_with_empty_cells)
|
||||
def import!(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
status = run_import_actions(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
@temp_file.destroy
|
||||
status
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def run_import_actions(can_edit_existing_items, should_overwrite_with_empty_cells)
|
||||
def run_import_actions(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
@temp_file.file.open do |temp_file|
|
||||
@repository.import_records(
|
||||
SpreadsheetParser.open_spreadsheet(temp_file),
|
||||
@mappings,
|
||||
@user,
|
||||
can_edit_existing_items,
|
||||
should_overwrite_with_empty_cells
|
||||
should_overwrite_with_empty_cells,
|
||||
preview
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -60,11 +60,12 @@ module RepositoryCsvExport
|
|||
cell = row.repository_cells.find_by(repository_column_id: c_id)
|
||||
|
||||
csv_row << if cell
|
||||
if cell.value_type == 'RepositoryAssetValue' && handle_file_name_func
|
||||
handle_file_name_func.call(cell.value.asset)
|
||||
else
|
||||
cell.value.export_formatted
|
||||
end
|
||||
if cell.value_type == 'RepositoryAssetValue' && handle_file_name_func
|
||||
handle_file_name_func.call(cell.value.asset)
|
||||
else
|
||||
cell.value.export_formatted
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
csv_row << row.row_consumption(row.stock_consumption) if add_consumption
|
||||
|
|
|
@ -25,14 +25,11 @@ module RepositoryImportParser
|
|||
@repository_columns = @repository.repository_columns
|
||||
end
|
||||
|
||||
def run(can_edit_existing_items, should_overwrite_with_empty_cells)
|
||||
def run(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
fetch_columns
|
||||
return check_for_duplicate_columns if check_for_duplicate_columns
|
||||
|
||||
# Used for developing preview changes (will be removed)
|
||||
preview = false
|
||||
|
||||
import_rows!(can_edit_existing_items, should_overwrite_with_empty_cells, preview: preview)
|
||||
import_rows!(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -66,10 +63,12 @@ module RepositoryImportParser
|
|||
end
|
||||
end
|
||||
|
||||
def import_rows!(can_edit_existing_items, should_overwrite_with_empty_cells, preview: false)
|
||||
def import_rows!(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
errors = false
|
||||
duplicate_ids = SpreadsheetParser.duplicate_ids(@sheet)
|
||||
|
||||
imported_rows = []
|
||||
|
||||
@repository.transaction do
|
||||
batch_counter = 0
|
||||
full_row_import_batch = []
|
||||
|
@ -79,7 +78,7 @@ module RepositoryImportParser
|
|||
next if row.blank?
|
||||
|
||||
# Skip duplicates
|
||||
next if duplicate_ids.include?(row.first) && !preview
|
||||
next if duplicate_ids.include?(row.first)
|
||||
|
||||
unless @header_skipped
|
||||
@header_skipped = true
|
||||
|
@ -98,7 +97,7 @@ module RepositoryImportParser
|
|||
if index == @name_index
|
||||
|
||||
# check if row (inventory) already exists
|
||||
existing_row = RepositoryRow.find_by(id: incoming_row[0].gsub(RepositoryRow::ID_PREFIX, ''))
|
||||
existing_row = RepositoryRow.includes(repository_cells: :value).find_by(id: incoming_row[0].gsub(RepositoryRow::ID_PREFIX, ''))
|
||||
|
||||
# if it doesn't exist create it
|
||||
unless existing_row
|
||||
|
@ -122,13 +121,13 @@ module RepositoryImportParser
|
|||
# otherwise add according to criteria
|
||||
else
|
||||
# if it does exist but shouldn't be edited, error out and break
|
||||
if existing_row && can_edit_existing_items == '0'
|
||||
if existing_row && (can_edit_existing_items == false)
|
||||
errors = true
|
||||
break
|
||||
end
|
||||
|
||||
# if it does exist and should be edited, update the existing row
|
||||
if existing_row && can_edit_existing_items == '1'
|
||||
if existing_row && (can_edit_existing_items == true)
|
||||
# update the existing row with incoming row data
|
||||
new_full_row[:repository_row] = existing_row
|
||||
end
|
||||
|
@ -146,13 +145,14 @@ module RepositoryImportParser
|
|||
|
||||
next if batch_counter < IMPORT_BATCH_SIZE
|
||||
|
||||
import_batch_to_database(full_row_import_batch, can_edit_existing_items, should_overwrite_with_empty_cells, preview: preview)
|
||||
# import_batch_to_database(full_row_import_batch, can_edit_existing_items, should_overwrite_with_empty_cells, preview: preview)
|
||||
imported_rows += import_batch_to_database(full_row_import_batch, can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
full_row_import_batch = []
|
||||
batch_counter = 0
|
||||
end
|
||||
|
||||
# Import of the remaining rows
|
||||
import_batch_to_database(full_row_import_batch, can_edit_existing_items, should_overwrite_with_empty_cells, preview: preview) if full_row_import_batch.any?
|
||||
imported_rows += import_batch_to_database(full_row_import_batch, can_edit_existing_items, should_overwrite_with_empty_cells, preview) if full_row_import_batch.any?
|
||||
|
||||
full_row_import_batch
|
||||
end
|
||||
|
@ -162,13 +162,19 @@ module RepositoryImportParser
|
|||
nr_of_added: @new_rows_added,
|
||||
total_nr: @total_new_rows }
|
||||
end
|
||||
{ status: :ok, nr_of_added: @new_rows_added, total_nr: @total_new_rows }
|
||||
changes = ActiveModelSerializers::SerializableResource.new(
|
||||
imported_rows,
|
||||
each_serializer: RepositoryRowSerializer,
|
||||
include: [:repository_cells]
|
||||
).as_json[:included]
|
||||
|
||||
{ status: :ok, nr_of_added: @new_rows_added, total_nr: @total_new_rows, changes: changes }
|
||||
end
|
||||
|
||||
def import_batch_to_database(full_row_import_batch, can_edit_existing_items, should_overwrite_with_empty_cells, preview: false)
|
||||
def import_batch_to_database(full_row_import_batch, can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
skipped_rows = []
|
||||
|
||||
full_row_import_batch.each do |full_row|
|
||||
full_row_import_batch.map do |full_row|
|
||||
# skip archived rows and rows that belong to other repositories
|
||||
if full_row[:repository_row].archived || full_row[:repository_row].repository_id != @repository.id
|
||||
skipped_rows << full_row[:repository_row]
|
||||
|
@ -199,16 +205,16 @@ module RepositoryImportParser
|
|||
@user.as_json(root: true, only: :settings).deep_symbolize_keys
|
||||
)
|
||||
|
||||
existing_cell = full_row[:repository_row].repository_cells.find_by(repository_column: column)
|
||||
existing_cell = full_row[:repository_row].repository_cells.find { |c| c.repository_column_id == column.id }
|
||||
|
||||
next if cell_value.nil? && existing_cell.nil?
|
||||
|
||||
if existing_cell
|
||||
# existing_cell present && !can_edit_existing_items
|
||||
next if can_edit_existing_items == '0'
|
||||
next if can_edit_existing_items == false
|
||||
|
||||
# existing_cell present && can_edit_existing_items
|
||||
if can_edit_existing_items == '1'
|
||||
if can_edit_existing_items == true
|
||||
# if incoming cell is not empty
|
||||
case cell_value
|
||||
|
||||
|
@ -229,10 +235,10 @@ module RepositoryImportParser
|
|||
end
|
||||
|
||||
# if incoming cell is empty && should_overwrite_with_empty_cells
|
||||
existing_cell.value.destroy! if cell_value.nil? && should_overwrite_with_empty_cells == '1'
|
||||
existing_cell.value.destroy! if cell_value.nil? && should_overwrite_with_empty_cells == true
|
||||
|
||||
# if incoming cell is empty && !should_overwrite_with_empty_cells
|
||||
next if cell_value.nil? && should_overwrite_with_empty_cells == '0'
|
||||
next if cell_value.nil? && should_overwrite_with_empty_cells == false
|
||||
end
|
||||
else
|
||||
# no existing_cell. Create a new one.
|
||||
|
@ -240,6 +246,8 @@ module RepositoryImportParser
|
|||
cell_value.save!(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
full_row[:repository_row]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2033,7 +2033,7 @@ en:
|
|||
error_searching: "Error searching, please try again"
|
||||
button_tooltip:
|
||||
new: "Create new item"
|
||||
import: "Import"
|
||||
import: "Update inventory"
|
||||
filters: "Filters"
|
||||
search: "Quick search"
|
||||
filters:
|
||||
|
@ -2192,11 +2192,12 @@ en:
|
|||
import_records:
|
||||
update_inventory: 'Update inventory'
|
||||
steps:
|
||||
step0:
|
||||
id: 'step0'
|
||||
step1:
|
||||
id: 'step1'
|
||||
icon: 'sn-icon-open'
|
||||
label: 'Step 1'
|
||||
title: 'Update inventory'
|
||||
subtitle: 'To add or edit items, export the inventory and reimport edited inventory.'
|
||||
helpText: 'Help'
|
||||
exportTitle: 'Export'
|
||||
exportFullInvBtnText: 'Export full inventory'
|
||||
|
@ -2205,6 +2206,59 @@ en:
|
|||
importBtnText: 'Import'
|
||||
cancelBtnText: 'Cancel'
|
||||
dragAndDropSupportingText: '.XLSX, .XLS or .CSV file'
|
||||
step2:
|
||||
id: 'step2'
|
||||
icon: 'sn-icon-open'
|
||||
label: 'Step 2'
|
||||
title: 'Mapping data'
|
||||
subtitle: 'Match your imported columns with the columns in the SciNote inventory.'
|
||||
selectNamePropertyError: 'Select Name attribute field to import your items.'
|
||||
autoMappingText: 'Auto-mapping'
|
||||
updateEmptyCellsText: 'Update empty cells'
|
||||
onlyAddNewItemsText: 'Only add new items'
|
||||
importedFileText: 'Imported file:'
|
||||
cancelBtnText: 'Cancel'
|
||||
confirmBtnText: 'Confirm'
|
||||
importedIgnoredSection:
|
||||
columnsTo: 'columns to'
|
||||
import: 'import.'
|
||||
columns: 'columns'
|
||||
ignored: 'ignored.'
|
||||
|
||||
computedDropdownOptions:
|
||||
name: 'Name'
|
||||
RepositoryTextValue: 'Text'
|
||||
RepositoryNumberValue: 'Number'
|
||||
RepositoryAssetValue: 'File'
|
||||
RepositoryChecklistValue: 'Checklist'
|
||||
RepositoryDateRangeValue: 'Date range'
|
||||
RepositoryDateTimeRangeValue: 'Date-time range'
|
||||
RepositoryDateTimeValue: 'Date-time'
|
||||
RepositoryDateValue: 'Date'
|
||||
RepositoryListValue: 'List'
|
||||
RepositoryStatusValue: 'Status'
|
||||
RepositoryStockValue: 'Stock'
|
||||
table:
|
||||
tableRow:
|
||||
defaultColumnTitle: 'Default column. Mapped as identifier.'
|
||||
userDefinedColumnTitle: 'Column name does not match. Column will be imported as '
|
||||
importedColumnTitle: 'Column will be imported.'
|
||||
doNotImportColumnTitle: 'Column will not import.'
|
||||
systemGeneratedData:
|
||||
itemId: 'Item ID'
|
||||
createdOn: 'Created on'
|
||||
addedBy: 'Added by'
|
||||
addedOn: 'Added on'
|
||||
archivedBy: 'Archived by'
|
||||
archivedOn: 'Archived on'
|
||||
updatedBy: 'Updated by'
|
||||
updatedOn: 'Updated on'
|
||||
columnLabels:
|
||||
number: 'No.'
|
||||
importedColumns: 'Imported columns'
|
||||
scinoteColumns: 'SciNote columns'
|
||||
status: 'Status'
|
||||
exampleData: 'Example data'
|
||||
info_sidebar:
|
||||
title: 'Guide for updating the inventory'
|
||||
elements:
|
||||
|
|
Loading…
Add table
Reference in a new issue