From 1fe4d700aa70dec64d03484feca4d75de5b46a23 Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 29 Sep 2025 18:46:55 +0200 Subject: [PATCH] - Enhanced bulk import error messaging and input file handling. - Improved calculation view and tree view display. - Added draft clearing for container rates and corrected indentation issues. - Various frontend usability and styling refinements. --- src/frontend/src/components/UI/TreeNode.vue | 7 +- .../layout/config/BulkOperations.vue | 69 +++++++++++++++---- src/frontend/src/pages/CalcualtionDump.vue | 17 +++-- src/frontend/src/pages/Calculations.vue | 2 +- .../de/avatic/lcc/dto/bulk/BulkFileType.java | 13 +++- .../rates/ContainerRateRepository.java | 6 ++ .../lcc/service/bulk/BulkImportService.java | 2 +- .../ContainerRateImportService.java | 1 + 8 files changed, 89 insertions(+), 28 deletions(-) diff --git a/src/frontend/src/components/UI/TreeNode.vue b/src/frontend/src/components/UI/TreeNode.vue index 91c9804..10ae520 100644 --- a/src/frontend/src/components/UI/TreeNode.vue +++ b/src/frontend/src/components/UI/TreeNode.vue @@ -63,7 +63,7 @@ export default { expanded: { type: Boolean, default: false - } + }, }, data() { return { @@ -79,12 +79,11 @@ export default { }, displayKey() { if (this.keyName === 'root') return 'JSON' - return Array.isArray(this.parentData) ? `[${this.keyName}]` : this.keyName + return this.keyName }, keyClass() { return { - 'array-index': Array.isArray(this.parentData), - 'object-key': !Array.isArray(this.parentData), + 'object-key': this.keyName !== 'root', 'root-key': this.keyName === 'root' } }, diff --git a/src/frontend/src/components/layout/config/BulkOperations.vue b/src/frontend/src/components/layout/config/BulkOperations.vue index a2bb5f0..9e1a614 100644 --- a/src/frontend/src/components/layout/config/BulkOperations.vue +++ b/src/frontend/src/components/layout/config/BulkOperations.vue @@ -26,13 +26,13 @@
validity period
- +
@@ -77,11 +77,11 @@
-
+
History
No recent bulk operations
-
+
@@ -112,6 +112,7 @@ export default { importDataset: "NODE", selectedFileName: null, selectedFile: null, + fileBlob: null, uploading: false, processId: null, } @@ -126,7 +127,7 @@ export default { async isSelected(newVal) { if(newVal === true) await this.validityPeriodStore.loadPeriods(); - await this.bulkOperationStore.manageStatus(); + await this.bulkOperationStore.manageStatus(); } }, computed: { @@ -166,7 +167,7 @@ export default { } }, methods: { - buildDate(date) { + buildDate(date) { return `${date[0]}-${date[1].toString().padStart(2, '0')}-${date[2].toString().padStart(2, '0')} ${date[3].toString().padStart(2, '0')}:${date[4].toString().padStart(2, '0')}:${date[5].toString().padStart(2, '0')}` }, async fetchFile(id) { @@ -183,22 +184,60 @@ export default { await this.bulkOperationStore.scheduleDownload(this.exportDataset, isCurrent ? null : this.selectedPeriod); } }, - inputFile(event) { + async inputFile(event) { const file = event.target.files[0]; if (file) { - this.selectedFile = file; - this.selectedFileName = file.name; + try { + // Datei sofort in den RAM laden als Blob + this.fileBlob = await this.readFileAsBlob(file); + + // File-Objekt mit dem Blob erstellen, das den originalen Namen und Typ behält + this.selectedFile = new File([this.fileBlob], file.name, { type: file.type }); + this.selectedFileName = file.name; + + logger.info(`File loaded into memory: ${file.name} (${(file.size / 1024).toFixed(2)} KB)`); + } catch (error) { + logger.error('Error reading file:', error); + this.selectedFile = null; + this.selectedFileName = null; + this.fileBlob = null; + } } else { this.selectedFile = null; this.selectedFileName = null; + this.fileBlob = null; } + + // Input zurücksetzen, damit dieselbe Datei erneut ausgewählt werden kann + event.target.value = ''; + }, + readFileAsBlob(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + + reader.onload = (e) => { + // ArrayBuffer in Blob konvertieren + const blob = new Blob([e.target.result], { type: file.type }); + resolve(blob); + }; + + reader.onerror = (error) => { + reject(error); + }; + + // Datei als ArrayBuffer lesen + reader.readAsArrayBuffer(file); + }); }, async uploadFile() { if (!this.selectedFile) return; - await this.bulkOperationStore.scheduleUpload(this.importDataset, this.selectedFile); + const file = this.selectedFile; + this.selectedFile = null; + this.selectedFileName = null; + await this.bulkOperationStore.scheduleUpload(this.importDataset, file); } } } diff --git a/src/frontend/src/pages/CalcualtionDump.vue b/src/frontend/src/pages/CalcualtionDump.vue index 194aa1a..78a4f57 100644 --- a/src/frontend/src/pages/CalcualtionDump.vue +++ b/src/frontend/src/pages/CalcualtionDump.vue @@ -1,15 +1,20 @@ diff --git a/src/frontend/src/pages/Calculations.vue b/src/frontend/src/pages/Calculations.vue index ef20c9d..2a13e83 100644 --- a/src/frontend/src/pages/Calculations.vue +++ b/src/frontend/src/pages/Calculations.vue @@ -97,7 +97,7 @@ export default { if (ids.length === 1) { this.$router.push({name: "edit", params: {id: new UrlSafeBase64().encodeIds([ids[0]])}}); - } else { + } else if(ids.length !== 0) { this.$router.push({name: "bulk", params: {ids: new UrlSafeBase64().encodeIds(ids)}}); } diff --git a/src/main/java/de/avatic/lcc/dto/bulk/BulkFileType.java b/src/main/java/de/avatic/lcc/dto/bulk/BulkFileType.java index 5ac102b..c55edf7 100644 --- a/src/main/java/de/avatic/lcc/dto/bulk/BulkFileType.java +++ b/src/main/java/de/avatic/lcc/dto/bulk/BulkFileType.java @@ -1,5 +1,16 @@ package de.avatic.lcc.dto.bulk; public enum BulkFileType { - CONTAINER_RATE, COUNTRY_MATRIX, MATERIAL, PACKAGING, NODE + CONTAINER_RATE("container rate"), COUNTRY_MATRIX("kilometer rate"), MATERIAL("material"), PACKAGING("packaging"), NODE("node"); + + private final String fileType; + + BulkFileType(String fileType) { + this.fileType = fileType; + } + + + public String getFileType() { + return fileType; + } } diff --git a/src/main/java/de/avatic/lcc/repositories/rates/ContainerRateRepository.java b/src/main/java/de/avatic/lcc/repositories/rates/ContainerRateRepository.java index 149227c..9c38f2c 100644 --- a/src/main/java/de/avatic/lcc/repositories/rates/ContainerRateRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/rates/ContainerRateRepository.java @@ -224,6 +224,12 @@ public class ContainerRateRepository { nodeId, nodeId, TransportType.SEA.name(), TransportType.RAIL.name())); } + @Transactional + public void clearDraft() { + var sql = "DELETE FROM container_rate WHERE container_rate.validity_period_id = (SELECT id FROM validity_period WHERE state = ?)"; + jdbcTemplate.update(sql, ValidityPeriodState.DRAFT.name()); + } + private static class ContainerRateMapper implements RowMapper { diff --git a/src/main/java/de/avatic/lcc/service/bulk/BulkImportService.java b/src/main/java/de/avatic/lcc/service/bulk/BulkImportService.java index 4ec0a45..8dba972 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/BulkImportService.java +++ b/src/main/java/de/avatic/lcc/service/bulk/BulkImportService.java @@ -59,7 +59,7 @@ public class BulkImportService { Sheet sheet = workbook.getSheet(BulkFileTypes.valueOf(type.name()).getSheetName()); if(sheet == null) - throw new ExcelValidationError("Provided file does not contain a sheet named " + BulkFileTypes.valueOf(type.name()).getSheetName()); + throw new ExcelValidationError("Unable to import " + op.getFileType().getFileType() + ", because \"" + BulkFileTypes.valueOf(type.name()).getSheetName() + "\" sheet is missing. Please use correct template."); switch (type) { diff --git a/src/main/java/de/avatic/lcc/service/bulk/bulkImport/ContainerRateImportService.java b/src/main/java/de/avatic/lcc/service/bulk/bulkImport/ContainerRateImportService.java index 8a0de68..97cb070 100644 --- a/src/main/java/de/avatic/lcc/service/bulk/bulkImport/ContainerRateImportService.java +++ b/src/main/java/de/avatic/lcc/service/bulk/bulkImport/ContainerRateImportService.java @@ -20,6 +20,7 @@ public class ContainerRateImportService { public void processContainerRates(List containerRates) { Integer periodId = validityPeriodRepository.getDraftPeriodId(); + containerRateRepository.clearDraft(); containerRates.forEach(rate -> processContainerRate(rate,periodId)); }