diff --git a/src/frontend/src/components/UI/TableView.vue b/src/frontend/src/components/UI/TableView.vue
index 7f2cc8a..cd8a24a 100644
--- a/src/frontend/src/components/UI/TableView.vue
+++ b/src/frontend/src/components/UI/TableView.vue
@@ -18,31 +18,31 @@
-
- |
- {{ filter ? 'No results found' : 'No data available' }}
- |
-
+
+ |
+ {{ filter ? 'No results found' : 'No data available' }}
+ |
+
-
- |
-
- |
-
+
+ |
+
+ |
+
-
- |
- {{ getCellValue(item, column) }}
-
- |
-
+
+ |
+ {{ getCellValue(item, column) }}
+
+ |
+
@@ -66,11 +66,17 @@ import Pagination from "@/components/UI/Pagination.vue";
export default {
name: "TableView",
components: {Pagination, Box, SearchBar, BasicButton, Checkbox, Spinner},
+ emits: ['row-click'],
props: {
dataSource: {
type: Function,
required: true
},
+ mouseOver: {
+ type: Boolean,
+ default: false,
+
+ },
columns: {
type: Array,
required: true,
@@ -195,7 +201,7 @@ export default {
overflow: visible;
border-radius: 0.8rem;
box-shadow: 0 0.4rem 0.6rem -0.1rem rgba(0, 0, 0, 0.1);
-
+ background-color: #FFFFFF;
}
.loading {
@@ -261,5 +267,9 @@ export default {
background-color: rgba(107, 134, 156, 0.05);
}
+.table-row--hover {
+ cursor: pointer;
+}
+
\ No newline at end of file
diff --git a/src/frontend/src/components/layout/ErrorLogModal.vue b/src/frontend/src/components/layout/ErrorLogModal.vue
new file mode 100644
index 0000000..6a44358
--- /dev/null
+++ b/src/frontend/src/components/layout/ErrorLogModal.vue
@@ -0,0 +1,127 @@
+
+
+
+
+
+
{{ exception }}
+
{{ fullClassName(traceItem) }} ({{ fileName(traceItem) }})
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/frontend/src/components/layout/config/BulkOperations.vue b/src/frontend/src/components/layout/config/BulkOperations.vue
index 9414d05..d7fd21a 100644
--- a/src/frontend/src/components/layout/config/BulkOperations.vue
+++ b/src/frontend/src/components/layout/config/BulkOperations.vue
@@ -59,11 +59,6 @@
materials
packaging
-
@@ -111,7 +106,6 @@ export default {
exportType: "templates",
exportDataset: "NODE",
importDataset: "NODE",
- importType: "APPEND",
selectedFileName: null,
selectedFile: null,
uploading: false,
@@ -177,8 +171,8 @@ export default {
if (!this.selectedFile)
return;
- const url = `${config.backendUrl}/bulk/upload/${this.importDataset}/${this.importType}/`
- this.processId = await performUpload(url, this.selectedFile);
+ const url = `${config.backendUrl}/bulk/upload/${this.importDataset}/`
+ this.processId = await performUpload(url, this.selectedFile).catch(error => {});
}
}
}
diff --git a/src/frontend/src/pages/ErrorLog.vue b/src/frontend/src/pages/ErrorLog.vue
new file mode 100644
index 0000000..58f643c
--- /dev/null
+++ b/src/frontend/src/pages/ErrorLog.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/frontend/src/router.js b/src/frontend/src/router.js
index c7db374..abd85da 100644
--- a/src/frontend/src/router.js
+++ b/src/frontend/src/router.js
@@ -5,6 +5,7 @@ import Config from "@/pages/Config.vue";
import CalculationSingleEdit from "@/pages/CalculationSingleEdit.vue";
import CalculationMassEdit from "@/pages/CalculationMassEdit.vue";
import CalculationAssistant from "@/pages/CalculationAssistant.vue";
+import ErrorLog from "@/pages/ErrorLog.vue";
const router = createRouter({
history: createWebHistory(),
@@ -48,6 +49,11 @@ const router = createRouter({
path: '/config',
component: Config
},
+ {
+ path: '/error',
+ component: ErrorLog
+
+ },
{
path: '/:pathMatch(.*)*',
redirect: '/calculations'
diff --git a/src/frontend/src/store/error.js b/src/frontend/src/store/error.js
index 8b70169..d577c5f 100644
--- a/src/frontend/src/store/error.js
+++ b/src/frontend/src/store/error.js
@@ -36,6 +36,8 @@ export const useErrorStore = defineStore('error', {
timestamp: Date.now()
}
+ console.log(error);
+
this.errors.push(error);
this.sendCache.push(error);
await this.transmitErrors();
@@ -52,6 +54,7 @@ export const useErrorStore = defineStore('error', {
const url = `${config.backendUrl}/error/`;
+
const response = await fetch(url, params
).catch(e => {
this.startAutoSubmitTimer();
@@ -110,7 +113,7 @@ export const useErrorStore = defineStore('error', {
return storeState;
},
async submitOnBeforeUnload() {
- if (this.errors.length > 0) {
+ if (this.sendCache.length > 0) {
navigator.sendBeacon('/api/errors', JSON.stringify(
toRaw(this.sendCache)
))
@@ -125,29 +128,29 @@ export const useErrorStore = defineStore('error', {
export function setupErrorBuffer() {
const errorStore = useErrorStore()
- // // Unhandled Promise Rejections
- // window.addEventListener('unhandledrejection', (event) => {
- //
- // const error = {
- // code: "Unhandled rejection",
- // title: "Frontend error",
- // message: event.reason?.message || 'Unhandled Promise Rejection',
- // traceCombined: event.reason?.stack,
- // };
- //
- // errorStore.addError(error, {global: true}).then(r => {} );
- // })
- //
- // // JavaScript Errors
- // window.addEventListener('error', (event) => {
- // const error = {
- // code: "General error",
- // title: "Frontend error",
- // message: event.reason?.message || 'Unhandled Promise Rejection',
- // traceCombined: event.reason?.stack,
- // };
- // errorStore.addError(error, {global: true}).then(r => {} );
- // })
+ //Unhandled Promise Rejections
+ window.addEventListener('unhandledrejection', (event) => {
+
+ const error = {
+ code: "Unhandled rejection",
+ title: "Frontend error",
+ message: event.reason?.message || 'Unhandled Promise Rejection',
+ traceCombined: event.reason?.stack,
+ };
+
+ errorStore.addError(error, {global: true}).then(r => {} );
+ })
+
+ // JavaScript Errors
+ window.addEventListener('error', (event) => {
+ const error = {
+ code: "General error",
+ title: "Frontend error",
+ message: event.reason?.message || 'Unhandled Promise Rejection',
+ traceCombined: event.reason?.stack,
+ };
+ errorStore.addError(error, {global: true}).then(r => {} );
+ })
window.addEventListener('beforeunload', () => {
errorStore.submitOnBeforeUnload();
diff --git a/src/frontend/src/store/errorLog.js b/src/frontend/src/store/errorLog.js
new file mode 100644
index 0000000..86358bf
--- /dev/null
+++ b/src/frontend/src/store/errorLog.js
@@ -0,0 +1,60 @@
+import {defineStore} from 'pinia'
+import {config} from '@/config'
+import performRequest from "@/backend.js";
+
+export const useErrorLogStore = defineStore('errorLog', {
+ state() {
+ return {
+ errors: [],
+ loading: false,
+ query: {},
+ pagination: {}
+ }
+ },
+ getters: {
+ isLoading(state) {
+ return state.loading;
+ },
+ getErrors(state) {
+ return state.errors;
+ },
+ getPagination(state) {
+ return state.pagination;
+ }
+
+ },
+ actions: {
+ async setQuery(query = {}) {
+ this.query = query;
+ await this.updateErrors();
+ },
+ async updateErrors() {
+ this.loading = true;
+
+ this.errors = [];
+
+ const params = new URLSearchParams();
+ if (this.query?.searchTerm && this.query.searchTerm !== '')
+ params.append('filter', this.query.searchTerm);
+
+ if(this.query?.page)
+ params.append('page', this.query.page);
+
+ if(this.query?.pageSize)
+ params.append('limit', this.query.pageSize);
+
+ const url = `${config.backendUrl}/error/${params.size === 0 ? '' : '?'}${params.toString()}`;
+
+ const {data: data, headers: headers} = await performRequest(this, "GET", url, null);
+
+ this.errors = data;
+ this.pagination = { page: parseInt(headers.get('X-Current-Page')), pageCount: parseInt(headers.get('X-Page-Count')), totalCount: parseInt(headers.get('X-Total-Count'))};
+
+ console.log(this.pagination)
+
+ this.loading = false;
+
+ }
+ }
+
+});
\ No newline at end of file
diff --git a/src/main/java/de/avatic/lcc/controller/ErrorController.java b/src/main/java/de/avatic/lcc/controller/ErrorController.java
index f7cf420..b72c2c1 100644
--- a/src/main/java/de/avatic/lcc/controller/ErrorController.java
+++ b/src/main/java/de/avatic/lcc/controller/ErrorController.java
@@ -1,21 +1,42 @@
package de.avatic.lcc.controller;
+import de.avatic.lcc.dto.error.ErrorLogDTO;
import de.avatic.lcc.dto.error.FrontendErrorDTO;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import de.avatic.lcc.repositories.pagination.SearchQueryResult;
+import de.avatic.lcc.service.error.SysErrorService;
+import jakarta.validation.constraints.Min;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
import java.util.List;
+import java.util.Optional;
@RestController
@RequestMapping({"/api/error", "/api/error/"})
public class ErrorController {
+ private final SysErrorService sysErrorService;
+
+ public ErrorController(SysErrorService sysErrorService) {
+ this.sysErrorService = sysErrorService;
+ }
+
@PostMapping
- public void error(@RequestBody List
error) {
- System.out.println(error.toString());
- //TODO store in database.
+ public void error(@RequestBody List errors) {
+ sysErrorService.addErrors(errors);
+ }
+
+ @GetMapping
+ public ResponseEntity> listErrors(@RequestParam(defaultValue = "20") @Min(1) int limit,
+ @RequestParam(defaultValue = "1") @Min(1) int page,
+ @RequestParam(required = false) Optional filter) {
+ SearchQueryResult errors = sysErrorService.listErrors(filter, page, limit);
+
+ return ResponseEntity.ok()
+ .header("X-Total-Count", String.valueOf(errors.getTotalElements()))
+ .header("X-Page-Count", String.valueOf(errors.getTotalPages()))
+ .header("X-Current-Page", String.valueOf(errors.getPage()))
+ .body(errors.toList());
}
}
diff --git a/src/main/java/de/avatic/lcc/controller/bulk/BulkOperationController.java b/src/main/java/de/avatic/lcc/controller/bulk/BulkOperationController.java
index 60b7bc5..e5d47bc 100644
--- a/src/main/java/de/avatic/lcc/controller/bulk/BulkOperationController.java
+++ b/src/main/java/de/avatic/lcc/controller/bulk/BulkOperationController.java
@@ -1,9 +1,9 @@
package de.avatic.lcc.controller.bulk;
+import com.azure.core.annotation.BodyParam;
import de.avatic.lcc.dto.bulk.BulkFileType;
-import de.avatic.lcc.dto.bulk.BulkProcessingType;
-import de.avatic.lcc.dto.bulk.BulkStatus;
+import de.avatic.lcc.dto.bulk.BulkStatusDTO;
import de.avatic.lcc.service.bulk.BulkExportService;
import de.avatic.lcc.service.bulk.BulkFileProcessingService;
import de.avatic.lcc.service.bulk.TemplateExportService;
@@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
+import java.util.List;
/**
* REST controller for handling bulk operations, including file uploads, template generation,
@@ -36,27 +37,25 @@ public class BulkOperationController {
}
/**
- * Retrieves the current status of a specific bulk processing operation.
+ * Retrieves the current status of all bulk processing operations.
*
- * @param id The unique identifier of the operation (processing_id) to check its status.
* @return A ResponseEntity with the bulk processing status payload.
*/
- @GetMapping({"/status/{processing_id}","/status/{processing_id}/"})
- public ResponseEntity getUploadStatus(@PathVariable("processing_id") Integer id) {
- return ResponseEntity.ok(bulkProcessingService.getStatus(id));
+ @GetMapping({"/status/","/status"})
+ public ResponseEntity> getUploadStatus() {
+ return ResponseEntity.ok(bulkProcessingService.getStatus());
}
/**
* Handles the upload of a file for a specific processing type and file type.
*
- * @param processingType The processing type ("full" or "append") associated with the upload.
* @param type The file type being uploaded, as defined in {@link BulkFileType}.
* @param file The file to be uploaded, provided as a multipart file.
* @return A ResponseEntity indicating whether the upload was processed successfully.
*/
- @PostMapping({"/upload/{type}/{processing_type}","/upload/{type}/{processing_type}/"})
- public ResponseEntity uploadFile(@PathVariable("processing_type") BulkProcessingType processingType, @PathVariable BulkFileType type, @RequestParam("file") MultipartFile file) {
- return ResponseEntity.ok(bulkProcessingService.processFile(type, processingType, file));
+ @PostMapping({"/upload/{type}","/upload/{type}/"})
+ public ResponseEntity uploadFile(@PathVariable BulkFileType type, @BodyParam("file") MultipartFile file) {
+ return ResponseEntity.ok(bulkProcessingService.processFile(type, file));
}
/**
@@ -89,7 +88,7 @@ public class BulkOperationController {
* @throws IllegalArgumentException if the provided file type is not supported.
*/
@GetMapping({"/download/{type}", "/download/{type}/"})
- public ResponseEntity downloadFile(@PathVariable BulkFileType type) throws IOException {
+ public ResponseEntity scheduleDownload(@PathVariable BulkFileType type) throws IOException {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment; filename=lcc_export_" + type.name().toLowerCase() + ".xlsx");
@@ -110,7 +109,7 @@ public class BulkOperationController {
* @throws IllegalArgumentException if the file type or validity period ID is invalid.
*/
@GetMapping({"/download/{type}/{validity_period_id}","/download/{type}/{validity_period_id}/"})
- public ResponseEntity downloadFile(@PathVariable BulkFileType type, @PathVariable("validity_period_id") Integer validityPeriodId) throws IOException {
+ public ResponseEntity scheduleDownload(@PathVariable BulkFileType type, @PathVariable("validity_period_id") Integer validityPeriodId) throws IOException {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment; filename=lcc_export_" + type.name().toLowerCase() + ".xlsx");
@@ -120,4 +119,20 @@ public class BulkOperationController {
.contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
.body(new InputStreamResource(bulkExportService.generateExport(BulkFileType.valueOf(type.name().toUpperCase()), validityPeriodId)));
}
+
+// @GetMapping({"/file/{processId}","/file/{processId}/"})
+// public ResponseEntity download(@PathVariable Integer processId) throws IOException {
+// bulkExportService.export(processId);
+//
+// HttpHeaders headers = new HttpHeaders();
+// headers.add("Content-Disposition", "attachment; filename=lcc_export_" + type.name().toLowerCase() + ".xlsx");
+//
+//
+// return ResponseEntity
+// .ok()
+// .headers(headers)
+// .contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
+// .body(new InputStreamResource(bulkExportService.generateExport(BulkFileType.valueOf(type.name().toUpperCase()), validityPeriodId)));
+// }
+
}
diff --git a/src/main/java/de/avatic/lcc/dto/bulk/BulkProcessingType.java b/src/main/java/de/avatic/lcc/dto/bulk/BulkProcessingType.java
index 08bb087..977b9aa 100644
--- a/src/main/java/de/avatic/lcc/dto/bulk/BulkProcessingType.java
+++ b/src/main/java/de/avatic/lcc/dto/bulk/BulkProcessingType.java
@@ -1,5 +1,5 @@
package de.avatic.lcc.dto.bulk;
public enum BulkProcessingType {
- FULL, APPEND
+ UPLOAD, DOWNLOAD
}
diff --git a/src/main/java/de/avatic/lcc/dto/bulk/BulkState.java b/src/main/java/de/avatic/lcc/dto/bulk/BulkState.java
new file mode 100644
index 0000000..5fc11a7
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/dto/bulk/BulkState.java
@@ -0,0 +1,5 @@
+package de.avatic.lcc.dto.bulk;
+
+public enum BulkState {
+ QUEUED, PROCESSING, COMPLETED, FAILED
+}
diff --git a/src/main/java/de/avatic/lcc/dto/bulk/BulkStatus.java b/src/main/java/de/avatic/lcc/dto/bulk/BulkStatus.java
deleted file mode 100644
index 5e28fd0..0000000
--- a/src/main/java/de/avatic/lcc/dto/bulk/BulkStatus.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package de.avatic.lcc.dto.bulk;
-
-public class BulkStatus {
-}
diff --git a/src/main/java/de/avatic/lcc/dto/bulk/BulkStatusDTO.java b/src/main/java/de/avatic/lcc/dto/bulk/BulkStatusDTO.java
new file mode 100644
index 0000000..9cd0255
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/dto/bulk/BulkStatusDTO.java
@@ -0,0 +1,12 @@
+package de.avatic.lcc.dto.bulk;
+
+public class BulkStatusDTO {
+
+ private BulkFileType operation;
+
+ private int processingId;
+
+ private BulkState state;
+
+
+}
diff --git a/src/main/java/de/avatic/lcc/dto/error/ErrorDTO.java b/src/main/java/de/avatic/lcc/dto/error/ErrorDTO.java
index 5c6b2f8..9c42bdc 100644
--- a/src/main/java/de/avatic/lcc/dto/error/ErrorDTO.java
+++ b/src/main/java/de/avatic/lcc/dto/error/ErrorDTO.java
@@ -11,6 +11,7 @@ public class ErrorDTO {
private String title;
private String message;
private List trace;
+ private String traceCombined;
public ErrorDTO() {
}
@@ -79,4 +80,11 @@ public class ErrorDTO {
return Objects.hash(code, message, trace);
}
+ public String getTraceCombined() {
+ return traceCombined;
+ }
+
+ public void setTraceCombined(String traceCombined) {
+ this.traceCombined = traceCombined;
+ }
}
diff --git a/src/main/java/de/avatic/lcc/dto/error/ErrorLogDTO.java b/src/main/java/de/avatic/lcc/dto/error/ErrorLogDTO.java
new file mode 100644
index 0000000..40be3cd
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/dto/error/ErrorLogDTO.java
@@ -0,0 +1,112 @@
+package de.avatic.lcc.dto.error;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public class ErrorLogDTO {
+
+ @JsonProperty("user_id")
+ private Integer userId;
+
+ private String code;
+ private String title;
+ private String message;
+
+ private String pinia;
+
+ @JsonProperty("calculation_job_id")
+ private Integer calculationJobId;
+
+ @JsonProperty("bulk_operation_id")
+ private Integer bulkOperationId;
+
+ private String type;
+
+ private List trace;
+
+ @JsonProperty("timestamp")
+ private LocalDateTime createdAt;
+
+ public Integer getUserId() {
+ return userId;
+ }
+
+ public void setUserId(Integer userId) {
+ this.userId = userId;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getPinia() {
+ return pinia;
+ }
+
+ public void setPinia(String pinia) {
+ this.pinia = pinia;
+ }
+
+ public Integer getCalculationJobId() {
+ return calculationJobId;
+ }
+
+ public void setCalculationJobId(Integer calculationJobId) {
+ this.calculationJobId = calculationJobId;
+ }
+
+ public Integer getBulkOperationId() {
+ return bulkOperationId;
+ }
+
+ public void setBulkOperationId(Integer bulkOperationId) {
+ this.bulkOperationId = bulkOperationId;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public List getTrace() {
+ return trace;
+ }
+
+ public void setTrace(List trace) {
+ this.trace = trace;
+ }
+
+ @JsonProperty("timestamp")
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
+}
diff --git a/src/main/java/de/avatic/lcc/dto/error/ErrorLogTraceItemDto.java b/src/main/java/de/avatic/lcc/dto/error/ErrorLogTraceItemDto.java
new file mode 100644
index 0000000..88e3ad4
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/dto/error/ErrorLogTraceItemDto.java
@@ -0,0 +1,41 @@
+package de.avatic.lcc.dto.error;
+
+public class ErrorLogTraceItemDto {
+
+ Integer line;
+ String file;
+ String method;
+ String fullPath;
+
+ public Integer getLine() {
+ return line;
+ }
+
+ public void setLine(Integer line) {
+ this.line = line;
+ }
+
+ public String getFile() {
+ return file;
+ }
+
+ public void setFile(String file) {
+ this.file = file;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+
+ public void setMethod(String method) {
+ this.method = method;
+ }
+
+ public String getFullPath() {
+ return fullPath;
+ }
+
+ public void setFullPath(String fullPath) {
+ this.fullPath = fullPath;
+ }
+}
diff --git a/src/main/java/de/avatic/lcc/model/bulk/BulkOperation.java b/src/main/java/de/avatic/lcc/model/bulk/BulkOperation.java
new file mode 100644
index 0000000..85eb65c
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/model/bulk/BulkOperation.java
@@ -0,0 +1,29 @@
+package de.avatic.lcc.model.bulk;
+
+public class BulkOperation {
+
+ private BulkOperationType type;
+
+ private T entity;
+
+ public BulkOperation(T entity, BulkOperationType type) {
+ this.entity = entity;
+ this.type = type;
+ }
+
+ public BulkOperationType getType() {
+ return type;
+ }
+
+ public void setType(BulkOperationType type) {
+ this.type = type;
+ }
+
+ public T getEntity() {
+ return entity;
+ }
+
+ public void setEntity(T entity) {
+ this.entity = entity;
+ }
+}
diff --git a/src/main/java/de/avatic/lcc/model/bulk/BulkOperationType.java b/src/main/java/de/avatic/lcc/model/bulk/BulkOperationType.java
new file mode 100644
index 0000000..7568206
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/model/bulk/BulkOperationType.java
@@ -0,0 +1,5 @@
+package de.avatic.lcc.model.bulk;
+
+public enum BulkOperationType {
+ UPDATE, DELETE
+}
diff --git a/src/main/java/de/avatic/lcc/model/bulk/BulkProcess.java b/src/main/java/de/avatic/lcc/model/bulk/BulkProcess.java
new file mode 100644
index 0000000..603e8b6
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/model/bulk/BulkProcess.java
@@ -0,0 +1,71 @@
+package de.avatic.lcc.model.bulk;
+
+import de.avatic.lcc.dto.bulk.BulkFileType;
+import de.avatic.lcc.dto.bulk.BulkProcessingType;
+import de.avatic.lcc.dto.bulk.BulkState;
+import org.apache.commons.compress.parallel.InputStreamSupplier;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.multipart.MultipartFile;
+
+public class BulkProcess {
+
+ private int id;
+
+ private BulkState state;
+
+ private BulkFileType type;
+
+ private BulkProcessingType processingType;
+
+ private MultipartFile bulkRequest;
+
+ private ResponseEntity bulkResponse;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public BulkState getState() {
+ return state;
+ }
+
+ public void setState(BulkState state) {
+ this.state = state;
+ }
+
+ public BulkFileType getType() {
+ return type;
+ }
+
+ public void setType(BulkFileType type) {
+ this.type = type;
+ }
+
+ public BulkProcessingType getProcessingType() {
+ return processingType;
+ }
+
+ public void setProcessingType(BulkProcessingType processingType) {
+ this.processingType = processingType;
+ }
+
+ public MultipartFile getBulkRequest() {
+ return bulkRequest;
+ }
+
+ public void setBulkRequest(MultipartFile bulkRequest) {
+ this.bulkRequest = bulkRequest;
+ }
+
+ public ResponseEntity getBulkResponse() {
+ return bulkResponse;
+ }
+
+ public void setBulkResponse(ResponseEntity bulkResponse) {
+ this.bulkResponse = bulkResponse;
+ }
+}
diff --git a/src/main/java/de/avatic/lcc/model/bulk/BulkProcessState.java b/src/main/java/de/avatic/lcc/model/bulk/BulkProcessState.java
new file mode 100644
index 0000000..e23c1f8
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/model/bulk/BulkProcessState.java
@@ -0,0 +1,5 @@
+package de.avatic.lcc.model.bulk;
+
+public enum BulkProcessState {
+ QUEUED, PROCESSING, COMPLETED, FAILED
+}
diff --git a/src/main/java/de/avatic/lcc/model/bulk/ContainerRateHeader.java b/src/main/java/de/avatic/lcc/model/bulk/header/ContainerRateHeader.java
similarity index 91%
rename from src/main/java/de/avatic/lcc/model/bulk/ContainerRateHeader.java
rename to src/main/java/de/avatic/lcc/model/bulk/header/ContainerRateHeader.java
index 83dc789..f5eb689 100644
--- a/src/main/java/de/avatic/lcc/model/bulk/ContainerRateHeader.java
+++ b/src/main/java/de/avatic/lcc/model/bulk/header/ContainerRateHeader.java
@@ -1,4 +1,4 @@
-package de.avatic.lcc.model.bulk;
+package de.avatic.lcc.model.bulk.header;
public enum ContainerRateHeader implements HeaderProvider {
FROM_NODE("Origin"), TO_NODE("Destination"), CONTAINER_RATE_TYPE("Transport mode"), RATE_FEU("Rate 40 ft GP [EUR]"),
diff --git a/src/main/java/de/avatic/lcc/model/bulk/HeaderProvider.java b/src/main/java/de/avatic/lcc/model/bulk/header/HeaderProvider.java
similarity index 59%
rename from src/main/java/de/avatic/lcc/model/bulk/HeaderProvider.java
rename to src/main/java/de/avatic/lcc/model/bulk/header/HeaderProvider.java
index 83355c0..eeade12 100644
--- a/src/main/java/de/avatic/lcc/model/bulk/HeaderProvider.java
+++ b/src/main/java/de/avatic/lcc/model/bulk/header/HeaderProvider.java
@@ -1,5 +1,6 @@
-package de.avatic.lcc.model.bulk;
+package de.avatic.lcc.model.bulk.header;
public interface HeaderProvider {
String getHeader();
+
}
diff --git a/src/main/java/de/avatic/lcc/model/bulk/HiddenCountryHeader.java b/src/main/java/de/avatic/lcc/model/bulk/header/HiddenCountryHeader.java
similarity index 69%
rename from src/main/java/de/avatic/lcc/model/bulk/HiddenCountryHeader.java
rename to src/main/java/de/avatic/lcc/model/bulk/header/HiddenCountryHeader.java
index e6cc168..aa6cba8 100644
--- a/src/main/java/de/avatic/lcc/model/bulk/HiddenCountryHeader.java
+++ b/src/main/java/de/avatic/lcc/model/bulk/header/HiddenCountryHeader.java
@@ -1,6 +1,6 @@
-package de.avatic.lcc.model.bulk;
+package de.avatic.lcc.model.bulk.header;
-public enum HiddenCountryHeader implements HeaderProvider{
+public enum HiddenCountryHeader implements HeaderProvider {
ISO_CODE("Iso Code"), NAME("Name");
private final String header;
diff --git a/src/main/java/de/avatic/lcc/model/bulk/HiddenNodeHeader.java b/src/main/java/de/avatic/lcc/model/bulk/header/HiddenNodeHeader.java
similarity index 87%
rename from src/main/java/de/avatic/lcc/model/bulk/HiddenNodeHeader.java
rename to src/main/java/de/avatic/lcc/model/bulk/header/HiddenNodeHeader.java
index bfddd0d..75592f8 100644
--- a/src/main/java/de/avatic/lcc/model/bulk/HiddenNodeHeader.java
+++ b/src/main/java/de/avatic/lcc/model/bulk/header/HiddenNodeHeader.java
@@ -1,4 +1,4 @@
-package de.avatic.lcc.model.bulk;
+package de.avatic.lcc.model.bulk.header;
public enum HiddenNodeHeader implements HeaderProvider {
MAPPING_ID("Mapping Id"), NAME("Name");
diff --git a/src/main/java/de/avatic/lcc/model/bulk/MaterialHeader.java b/src/main/java/de/avatic/lcc/model/bulk/header/MaterialHeader.java
similarity index 82%
rename from src/main/java/de/avatic/lcc/model/bulk/MaterialHeader.java
rename to src/main/java/de/avatic/lcc/model/bulk/header/MaterialHeader.java
index ac42b80..6fda921 100644
--- a/src/main/java/de/avatic/lcc/model/bulk/MaterialHeader.java
+++ b/src/main/java/de/avatic/lcc/model/bulk/header/MaterialHeader.java
@@ -1,6 +1,7 @@
-package de.avatic.lcc.model.bulk;
+package de.avatic.lcc.model.bulk.header;
public enum MaterialHeader implements HeaderProvider {
+ OPERATION("Operation"),
PART_NUMBER("Part number"),
DESCRIPTION("Material description"),
HS_CODE("HS code");
diff --git a/src/main/java/de/avatic/lcc/model/bulk/MatrixRateHeader.java b/src/main/java/de/avatic/lcc/model/bulk/header/MatrixRateHeader.java
similarity index 90%
rename from src/main/java/de/avatic/lcc/model/bulk/MatrixRateHeader.java
rename to src/main/java/de/avatic/lcc/model/bulk/header/MatrixRateHeader.java
index 41e054e..413e209 100644
--- a/src/main/java/de/avatic/lcc/model/bulk/MatrixRateHeader.java
+++ b/src/main/java/de/avatic/lcc/model/bulk/header/MatrixRateHeader.java
@@ -1,4 +1,4 @@
-package de.avatic.lcc.model.bulk;
+package de.avatic.lcc.model.bulk.header;
public enum MatrixRateHeader implements HeaderProvider {
FROM_COUNTRY("Country of origin (ISO 3166-1)"),
diff --git a/src/main/java/de/avatic/lcc/model/bulk/NodeHeader.java b/src/main/java/de/avatic/lcc/model/bulk/header/NodeHeader.java
similarity index 79%
rename from src/main/java/de/avatic/lcc/model/bulk/NodeHeader.java
rename to src/main/java/de/avatic/lcc/model/bulk/header/NodeHeader.java
index 841299e..9e91718 100644
--- a/src/main/java/de/avatic/lcc/model/bulk/NodeHeader.java
+++ b/src/main/java/de/avatic/lcc/model/bulk/header/NodeHeader.java
@@ -1,7 +1,7 @@
-package de.avatic.lcc.model.bulk;
+package de.avatic.lcc.model.bulk.header;
public enum NodeHeader implements HeaderProvider {
- MAPPING_ID("Mapping ID"), NAME("Name"), ADDRESS("Address"),
+ OPERATION("Operation"), MAPPING_ID("Mapping ID"), NAME("Name"), ADDRESS("Address"),
COUNTRY("Country (ISO 3166-1)"), GEO_LATITUDE("Latitude"), GEO_LONGITUDE("Longitude"),
IS_SOURCE("Source"), IS_INTERMEDIATE("Intermediate"), IS_DESTINATION("Destination"),
OUTBOUND_COUNTRIES("Outbound countries (ISO 3166-1)"), PREDECESSOR_NODES("Predecessor Nodes (Mapping ID)"),
@@ -9,11 +9,15 @@ public enum NodeHeader implements HeaderProvider {
private final String header;
+
NodeHeader(String header) {
this.header = header;
}
+ @Override
public String getHeader() {
return header;
}
+
+
}
diff --git a/src/main/java/de/avatic/lcc/model/bulk/PackagingHeader.java b/src/main/java/de/avatic/lcc/model/bulk/header/PackagingHeader.java
similarity index 87%
rename from src/main/java/de/avatic/lcc/model/bulk/PackagingHeader.java
rename to src/main/java/de/avatic/lcc/model/bulk/header/PackagingHeader.java
index c9ca86c..325264c 100644
--- a/src/main/java/de/avatic/lcc/model/bulk/PackagingHeader.java
+++ b/src/main/java/de/avatic/lcc/model/bulk/header/PackagingHeader.java
@@ -1,6 +1,7 @@
-package de.avatic.lcc.model.bulk;
+package de.avatic.lcc.model.bulk.header;
public enum PackagingHeader implements HeaderProvider {
+ OPERATION("Operation"),
PART_NUMBER("Part number"),
SUPPLIER("Supplier (Mapping ID)"),
SHU_LENGTH("SHU length"),
@@ -16,8 +17,7 @@ public enum PackagingHeader implements HeaderProvider {
HU_DIMENSION_UNIT("HU Dimension unit"),
HU_WEIGHT("HU gross weight"),
HU_WEIGHT_UNIT("HU gross weight unit"),
- HU_UNIT_COUNT("Units/HU [pieces]"),
- STACKABLE("Stackable");
+ HU_UNIT_COUNT("Units/HU [pieces]");
private final String header;
diff --git a/src/main/java/de/avatic/lcc/model/error/SysError.java b/src/main/java/de/avatic/lcc/model/error/SysError.java
new file mode 100644
index 0000000..f251565
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/model/error/SysError.java
@@ -0,0 +1,252 @@
+package de.avatic.lcc.model.error;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * Represents a system error with detailed information about the error occurrence,
+ * including user context, error type, and stack trace.
+ */
+public class SysError {
+
+ /**
+ * The unique identifier of the system error.
+ */
+ private Integer id;
+
+ /**
+ * The identifier of the user associated with this error.
+ */
+ private Integer userId;
+
+ /**
+ * The title or brief description of the error.
+ */
+ private String title;
+
+ /**
+ * The detailed error message.
+ */
+ private String message;
+
+ /**
+ * The error code identifier.
+ */
+ private String code;
+
+ /**
+ * The Pinia store state related to this error.
+ */
+ private String pinia;
+
+ /**
+ * The type classification of the system error.
+ */
+ private SysErrorType type;
+
+ /**
+ * The identifier of the calculation job associated with this error.
+ */
+ private Integer calculationJobId;
+
+ /**
+ * The identifier of the bulk operation associated with this error.
+ */
+ private Integer bulkOperationId;
+
+ /**
+ * The list of stack trace items providing error location details.
+ */
+
+ private List trace;
+
+ private LocalDateTime createdAt;
+
+ /**
+ * Gets the unique identifier of the system error.
+ *
+ * @return the error ID
+ */
+ public Integer getId() {
+ return id;
+ }
+
+ /**
+ * Sets the unique identifier of the system error.
+ *
+ * @param id the error ID to set
+ */
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ /**
+ * Gets the identifier of the user associated with this error.
+ *
+ * @return the user ID
+ */
+ public Integer getUserId() {
+ return userId;
+ }
+
+ /**
+ * Sets the identifier of the user associated with this error.
+ *
+ * @param userId the user ID to set
+ */
+ public void setUserId(Integer userId) {
+ this.userId = userId;
+ }
+
+ /**
+ * Gets the title or brief description of the error.
+ *
+ * @return the error title
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Sets the title or brief description of the error.
+ *
+ * @param title the error title to set
+ */
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ /**
+ * Gets the detailed error message.
+ *
+ * @return the error message
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Sets the detailed error message.
+ *
+ * @param message the error message to set
+ */
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * Gets the error code identifier.
+ *
+ * @return the error code
+ */
+ public String getCode() {
+ return code;
+ }
+
+ /**
+ * Sets the error code identifier.
+ *
+ * @param code the error code to set
+ */
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ /**
+ * Gets the Pinia store state related to this error.
+ *
+ * @return the Pinia store state
+ */
+ public String getPinia() {
+ return pinia;
+ }
+
+ /**
+ * Sets the Pinia store identifier related to this error.
+ *
+ * @param pinia the Pinia store identifier to set
+ */
+ public void setPinia(String pinia) {
+ this.pinia = pinia;
+ }
+
+ /**
+ * Gets the type classification of the system error.
+ *
+ * @return the error type
+ */
+ public SysErrorType getType() {
+ return type;
+ }
+
+ /**
+ * Sets the type classification of the system error.
+ *
+ * @param type the error type to set
+ */
+ public void setType(SysErrorType type) {
+ this.type = type;
+ }
+
+ /**
+ * Gets the identifier of the calculation job associated with this error.
+ *
+ * @return the calculation job ID
+ */
+ public Integer getCalculationJobId() {
+ return calculationJobId;
+ }
+
+ /**
+ * Sets the identifier of the calculation job associated with this error.
+ *
+ * @param calculationJobId the calculation job ID to set
+ */
+ public void setCalculationJobId(Integer calculationJobId) {
+ this.calculationJobId = calculationJobId;
+ }
+
+ /**
+ * Gets the identifier of the bulk operation associated with this error.
+ *
+ * @return the bulk operation ID
+ */
+ public Integer getBulkOperationId() {
+ return bulkOperationId;
+ }
+
+ /**
+ * Sets the identifier of the bulk operation associated with this error.
+ *
+ * @param bulkOperationId the bulk operation ID to set
+ */
+ public void setBulkOperationId(Integer bulkOperationId) {
+ this.bulkOperationId = bulkOperationId;
+ }
+
+ /**
+ * Gets the list of stack trace items providing error location details.
+ *
+ * @return the list of trace items
+ */
+ public List getTrace() {
+ return trace;
+ }
+
+ /**
+ * Sets the list of stack trace items providing error location details.
+ *
+ * @param trace the list of trace items to set
+ */
+ public void setTrace(List trace) {
+ this.trace = trace;
+ }
+
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
+}
diff --git a/src/main/java/de/avatic/lcc/model/error/SysErrorTraceItem.java b/src/main/java/de/avatic/lcc/model/error/SysErrorTraceItem.java
new file mode 100644
index 0000000..5d264eb
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/model/error/SysErrorTraceItem.java
@@ -0,0 +1,145 @@
+package de.avatic.lcc.model.error;
+
+/**
+ * Represents a trace item in system error stack trace.
+ * Contains information about the location and context of an error occurrence.
+ */
+public class SysErrorTraceItem {
+
+ /**
+ * The unique identifier of the trace item.
+ */
+ private Integer id;
+
+ /**
+ * The identifier of the associated system error.
+ */
+ private Integer errorId;
+
+ /**
+ * The line number where the error occurred.
+ */
+ private Integer line;
+ /**
+ * The file name where the error occurred.
+ */
+ private String file;
+ /**
+ * The method name where the error occurred.
+ */
+ private String method;
+
+ /**
+ * The full path to the file where the error occurred.
+ */
+ private String fullPath;
+
+
+ /**
+ * Gets the unique identifier of the trace item.
+ *
+ * @return the trace item ID
+ */
+ public Integer getId() {
+ return id;
+ }
+
+ /**
+ * Sets the unique identifier of the trace item.
+ *
+ * @param id the trace item ID to set
+ */
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ /**
+ * Gets the identifier of the associated system error.
+ *
+ * @return the error ID
+ */
+ public Integer getErrorId() {
+ return errorId;
+ }
+
+ /**
+ * Sets the identifier of the associated system error.
+ *
+ * @param errorId the error ID to set
+ */
+ public void setErrorId(Integer errorId) {
+ this.errorId = errorId;
+ }
+
+ /**
+ * Gets the line number where the error occurred.
+ *
+ * @return the line number
+ */
+ public Integer getLine() {
+ return line;
+ }
+
+ /**
+ * Sets the line number where the error occurred.
+ *
+ * @param line the line number to set
+ */
+ public void setLine(Integer line) {
+ this.line = line;
+ }
+
+ /**
+ * Gets the file name where the error occurred.
+ *
+ * @return the file name
+ */
+ public String getFile() {
+ return file;
+ }
+
+ /**
+ * Sets the file name where the error occurred.
+ *
+ * @param file the file name to set
+ */
+ public void setFile(String file) {
+ this.file = file;
+ }
+
+ /**
+ * Gets the method name where the error occurred.
+ *
+ * @return the method name
+ */
+ public String getMethod() {
+ return method;
+ }
+
+ /**
+ * Sets the method name where the error occurred.
+ *
+ * @param method the method name to set
+ */
+ public void setMethod(String method) {
+ this.method = method;
+ }
+
+ /**
+ * Gets the full path to the file where the error occurred.
+ *
+ * @return the full path
+ */
+ public String getFullPath() {
+ return fullPath;
+ }
+
+ /**
+ * Sets the full path to the file where the error occurred.
+ *
+ * @param fullPath the full path to set
+ */
+ public void setFullPath(String fullPath) {
+ this.fullPath = fullPath;
+ }
+}
diff --git a/src/main/java/de/avatic/lcc/model/error/SysErrorType.java b/src/main/java/de/avatic/lcc/model/error/SysErrorType.java
new file mode 100644
index 0000000..c425e2a
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/model/error/SysErrorType.java
@@ -0,0 +1,5 @@
+package de.avatic.lcc.model.error;
+
+public enum SysErrorType {
+BACKEND, FRONTEND, BULK, CALCULATION
+}
diff --git a/src/main/java/de/avatic/lcc/repositories/bulk/BulkOperationRepository.java b/src/main/java/de/avatic/lcc/repositories/bulk/BulkOperationRepository.java
new file mode 100644
index 0000000..39975c3
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/repositories/bulk/BulkOperationRepository.java
@@ -0,0 +1,7 @@
+package de.avatic.lcc.repositories.bulk;
+
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class BulkOperationRepository {
+}
diff --git a/src/main/java/de/avatic/lcc/repositories/error/SysErrorRepository.java b/src/main/java/de/avatic/lcc/repositories/error/SysErrorRepository.java
new file mode 100644
index 0000000..3c84016
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/repositories/error/SysErrorRepository.java
@@ -0,0 +1,178 @@
+package de.avatic.lcc.repositories.error;
+
+
+import de.avatic.lcc.model.error.SysError;
+import de.avatic.lcc.model.error.SysErrorTraceItem;
+import de.avatic.lcc.model.error.SysErrorType;
+import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
+import de.avatic.lcc.repositories.pagination.SearchQueryResult;
+import jakarta.validation.constraints.Min;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.jdbc.support.GeneratedKeyHolder;
+import org.springframework.jdbc.support.KeyHolder;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.sql.PreparedStatement;
+import java.sql.Statement;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Repository
+public class SysErrorRepository {
+
+ private final JdbcTemplate jdbcTemplate;
+ private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+
+ public SysErrorRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
+ }
+
+ @Transactional
+ public void insert(List errors) {
+ // First insert the sys_error records
+ String errorSql = "INSERT INTO sys_error (user_id, title, code, message, pinia, calculation_job_id, bulk_operation_id, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
+
+ KeyHolder keyHolder = new GeneratedKeyHolder();
+
+ for (SysError error : errors) {
+ jdbcTemplate.update(connection -> {
+ PreparedStatement ps = connection.prepareStatement(errorSql, Statement.RETURN_GENERATED_KEYS);
+ ps.setObject(1, error.getUserId()); // Use setObject for nullable Integer
+ ps.setString(2, error.getTitle());
+ ps.setString(3, error.getCode());
+ ps.setString(4, error.getMessage());
+ ps.setString(5, error.getPinia());
+ ps.setObject(6, error.getCalculationJobId()); // Use setObject for nullable Integer
+ ps.setObject(7, error.getBulkOperationId()); // Use setObject for nullable Integer
+ ps.setString(8, error.getType().name());
+ return ps;
+ }, keyHolder);
+
+ // Get the generated error ID
+ Integer errorId = Objects.requireNonNull(keyHolder.getKey()).intValue();
+
+ // Insert trace items if they exist
+ if (error.getTrace() != null && !error.getTrace().isEmpty()) {
+ insertTraceItems(errorId, error.getTrace());
+ }
+ }
+ }
+
+ private void insertTraceItems(Integer errorId, List traceItems) {
+ String traceSql = "INSERT INTO sys_error_trace_item (error_id, line, file, method, fullPath) VALUES (?, ?, ?, ?, ?)";
+
+ jdbcTemplate.batchUpdate(traceSql, traceItems, traceItems.size(),
+ (ps, traceItem) -> {
+ ps.setInt(1, errorId);
+ ps.setObject(2, traceItem.getLine()); // Use setObject for nullable Integer
+ ps.setString(3, traceItem.getFile());
+ ps.setString(4, traceItem.getMethod());
+ ps.setString(5, traceItem.getFullPath());
+ });
+ }
+
+ @Transactional
+ public SearchQueryResult listErrors(Optional filter, SearchQueryPagination pagination) {
+ StringBuilder whereClause = new StringBuilder();
+ MapSqlParameterSource parameters = new MapSqlParameterSource();
+
+ // Build WHERE clause if filter is provided
+ if (filter.isPresent() && !filter.get().trim().isEmpty()) {
+ String filterValue = "%" + filter.get().trim() + "%";
+ whereClause.append(" WHERE (e.title LIKE :filter OR e.message LIKE :filter OR e.code LIKE :filter)");
+ parameters.addValue("filter", filterValue);
+ }
+
+ // Count total elements
+ String countSql = "SELECT COUNT(*) FROM sys_error e" + whereClause.toString();
+ Integer totalElements = namedParameterJdbcTemplate.queryForObject(countSql, parameters, Integer.class);
+
+ // Build main query with pagination
+ String sql = """
+ SELECT e.id, e.user_id, e.title, e.code, e.message, e.pinia,
+ e.calculation_job_id, e.bulk_operation_id, e.type, e.created_at
+ FROM sys_error e
+ """ + whereClause.toString() + """
+ ORDER BY e.created_at DESC
+ LIMIT :limit OFFSET :offset
+ """;
+
+ // Add pagination parameters
+ parameters.addValue("limit", pagination.getLimit());
+ parameters.addValue("offset", pagination.getOffset());
+
+ // Execute query
+ List errors = namedParameterJdbcTemplate.query(sql, parameters, (rs, rowNum) -> {
+ SysError error = new SysError();
+ error.setId(rs.getInt("id"));
+ error.setUserId(rs.getObject("user_id", Integer.class));
+ error.setTitle(rs.getString("title"));
+ error.setCode(rs.getString("code"));
+ error.setMessage(rs.getString("message"));
+ error.setPinia(rs.getString("pinia"));
+ error.setCalculationJobId(rs.getObject("calculation_job_id", Integer.class));
+ error.setBulkOperationId(rs.getObject("bulk_operation_id", Integer.class));
+ error.setType(SysErrorType.valueOf(rs.getString("type")));
+ error.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
+ return error;
+ });
+
+ // Load trace items for each error
+ if (!errors.isEmpty()) {
+ loadTraceItemsForErrors(errors);
+ }
+
+ return new SearchQueryResult<>(errors, pagination.getPage(), totalElements, pagination.getLimit());
+ }
+
+ private void loadTraceItemsForErrors(List errors) {
+ // Get all error IDs
+ List errorIds = errors.stream()
+ .map(SysError::getId)
+ .toList();
+
+ if (errorIds.isEmpty()) {
+ return;
+ }
+
+ String traceSql = """
+ SELECT error_id, id, line, file, method, fullPath
+ FROM sys_error_trace_item
+ WHERE error_id IN (:errorIds)
+ ORDER BY error_id, id
+ """;
+
+ MapSqlParameterSource traceParameters = new MapSqlParameterSource("errorIds", errorIds);
+
+ // Query trace items
+ List allTraceItems = namedParameterJdbcTemplate.query(
+ traceSql,
+ traceParameters,
+ (rs, rowNum) -> {
+ SysErrorTraceItem traceItem = new SysErrorTraceItem();
+ traceItem.setErrorId(rs.getInt("error_id"));
+ traceItem.setId(rs.getInt("id"));
+ traceItem.setLine(rs.getObject("line", Integer.class));
+ traceItem.setFile(rs.getString("file"));
+ traceItem.setMethod(rs.getString("method"));
+ traceItem.setFullPath(rs.getString("fullPath"));
+ return traceItem;
+ }
+ );
+
+ // Group trace items by error ID
+ Map> traceItemsByErrorId = allTraceItems.stream()
+ .collect(Collectors.groupingBy(SysErrorTraceItem::getErrorId));
+
+ // Assign trace items to their respective errors
+ errors.forEach(error -> {
+ List traceItems = traceItemsByErrorId.getOrDefault(error.getId(), new ArrayList<>());
+ error.setTrace(traceItems);
+ });
+ }
+
+}
diff --git a/src/main/java/de/avatic/lcc/service/bulk/BulkFileProcessingService.java b/src/main/java/de/avatic/lcc/service/bulk/BulkFileProcessingService.java
index 29c0110..cc9f740 100644
--- a/src/main/java/de/avatic/lcc/service/bulk/BulkFileProcessingService.java
+++ b/src/main/java/de/avatic/lcc/service/bulk/BulkFileProcessingService.java
@@ -1,11 +1,11 @@
package de.avatic.lcc.service.bulk;
import de.avatic.lcc.dto.bulk.BulkFileType;
-import de.avatic.lcc.dto.bulk.BulkProcessingType;
-import de.avatic.lcc.dto.bulk.BulkStatus;
+import de.avatic.lcc.dto.bulk.BulkStatusDTO;
import de.avatic.lcc.model.bulk.BulkFileTypes;
import de.avatic.lcc.service.excelMapper.*;
import de.avatic.lcc.util.exception.badrequest.FileFormatNotSupportedException;
+import de.avatic.lcc.util.exception.base.BadRequestException;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
@@ -13,6 +13,7 @@ import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
+import java.util.List;
@Service
public class BulkFileProcessingService {
@@ -22,9 +23,9 @@ public class BulkFileProcessingService {
private final MaterialExcelMapper materialExcelMapper;
private final PackagingExcelMapper packagingExcelMapper;
private final NodeExcelMapper nodeExcelMapper;
- private final BulkStatusService bulkStatusService;
+ private final BulkProcessingService bulkStatusService;
- public BulkFileProcessingService(MatrixRateExcelMapper matrixRateExcelMapper, ContainerRateExcelMapper containerRateExcelMapper, MaterialExcelMapper materialExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper, BulkStatusService bulkStatusService) {
+ public BulkFileProcessingService(MatrixRateExcelMapper matrixRateExcelMapper, ContainerRateExcelMapper containerRateExcelMapper, MaterialExcelMapper materialExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper, BulkProcessingService bulkStatusService) {
this.matrixRateExcelMapper = matrixRateExcelMapper;
this.containerRateExcelMapper = containerRateExcelMapper;
this.materialExcelMapper = materialExcelMapper;
@@ -33,10 +34,11 @@ public class BulkFileProcessingService {
this.bulkStatusService = bulkStatusService;
}
- public Integer processFile(BulkFileType type, BulkProcessingType processingType, MultipartFile file) {
+ public Integer processFile(BulkFileType type, MultipartFile file) {
//TODO: launch parallel task
+
String contentType = file.getContentType();
if (!"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet".equals(contentType) &&
!"application/vnd.ms-excel".equals(contentType)) {
@@ -47,7 +49,6 @@ public class BulkFileProcessingService {
Workbook workbook = new XSSFWorkbook(in);
Sheet sheet = workbook.getSheet(BulkFileTypes.valueOf(type.name()).getSheetName());
- //todo: if processing type = append than merge with current data, if full, delete old data
switch (type) {
case CONTAINER_RATE:
var containerRates = containerRateExcelMapper.extractSheet(sheet);
@@ -70,13 +71,13 @@ public class BulkFileProcessingService {
}
} catch (Exception e) {
-
+ throw new BadRequestException("Unable to read excel sheet", e.getMessage(), e);
}
- return null;
+ return 0;
}
- public BulkStatus getStatus(Integer id) {
- return bulkStatusService.getStatus(id);
+ public List getStatus() {
+ return bulkStatusService.getStatus();
}
}
diff --git a/src/main/java/de/avatic/lcc/service/bulk/BulkProcessingService.java b/src/main/java/de/avatic/lcc/service/bulk/BulkProcessingService.java
new file mode 100644
index 0000000..6ad9975
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/service/bulk/BulkProcessingService.java
@@ -0,0 +1,30 @@
+package de.avatic.lcc.service.bulk;
+
+import de.avatic.lcc.dto.bulk.BulkProcessingType;
+import de.avatic.lcc.dto.bulk.BulkStatusDTO;
+import de.avatic.lcc.model.bulk.BulkProcess;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+import java.util.Queue;
+
+@Service
+public class BulkProcessingService {
+
+ int processCount = 0;
+
+ private Queue processes;
+
+
+ public Integer queueUpload(MultipartFile bulkRequest, BulkProcessingType processingType) {
+
+
+ return processCount++;
+ }
+
+
+ public List getStatus() {
+ return null; //TODO implement me
+ }
+}
diff --git a/src/main/java/de/avatic/lcc/service/bulk/BulkStatusService.java b/src/main/java/de/avatic/lcc/service/bulk/BulkStatusService.java
deleted file mode 100644
index a0f7811..0000000
--- a/src/main/java/de/avatic/lcc/service/bulk/BulkStatusService.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package de.avatic.lcc.service.bulk;
-
-import de.avatic.lcc.dto.bulk.BulkStatus;
-import org.springframework.stereotype.Service;
-
-@Service
-public class BulkStatusService {
-
- public BulkStatus getStatus(Integer id) {
- return null; //TODO implement me
- }
-}
diff --git a/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java b/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java
index 1041b6d..89883ed 100644
--- a/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java
+++ b/src/main/java/de/avatic/lcc/service/bulk/TemplateExportService.java
@@ -2,6 +2,7 @@ package de.avatic.lcc.service.bulk;
import de.avatic.lcc.dto.bulk.BulkFileType;
import de.avatic.lcc.model.bulk.*;
+import de.avatic.lcc.model.bulk.header.*;
import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider;
import de.avatic.lcc.service.bulk.helper.HeaderGenerator;
import de.avatic.lcc.service.excelMapper.*;
diff --git a/src/main/java/de/avatic/lcc/service/bulk/helper/ConstraintGenerator.java b/src/main/java/de/avatic/lcc/service/bulk/helper/ConstraintGenerator.java
index 0393c28..e854948 100644
--- a/src/main/java/de/avatic/lcc/service/bulk/helper/ConstraintGenerator.java
+++ b/src/main/java/de/avatic/lcc/service/bulk/helper/ConstraintGenerator.java
@@ -1,21 +1,17 @@
package de.avatic.lcc.service.bulk.helper;
import de.avatic.lcc.model.bulk.HiddenTableType;
-import org.apache.poi.ss.SpreadsheetVersion;
-import org.apache.poi.ss.usermodel.DataValidationConstraint;
-import org.apache.poi.ss.usermodel.Name;
-import org.apache.poi.ss.usermodel.Sheet;
-import org.apache.poi.ss.usermodel.Workbook;
+import de.avatic.lcc.util.exception.internalerror.ExcelValidationError;
+import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.springframework.stereotype.Service;
import java.util.EnumSet;
-import java.util.function.Function;
@Service
public class ConstraintGenerator {
- private static final int MAX_ROWS = 10000;
+ private static final int MAX_ROWS = 100000;
public void createBooleanConstraint(Sheet sheet, Integer columnIdx) {
@@ -26,16 +22,11 @@ public class ConstraintGenerator {
createConstraint(sheet, columnIdx, EnumSet.allOf(values).stream().map(Enum::name).toArray(String[]::new));
}
- public > void createEnumConstraint(Sheet sheet, Integer columnIdx, Class values, Function resolver) {
- createConstraint(sheet, columnIdx, EnumSet.allOf(values).stream().map(Enum::name).toArray(String[]::new));
- }
-
private void createConstraint(Sheet sheet, Integer columnIdx, String[] values) {
var helper = sheet.getDataValidationHelper();
var constraint = helper.createExplicitListConstraint(values);
-
var validation = helper.createValidation(constraint, new CellRangeAddressList(1, MAX_ROWS, columnIdx, columnIdx));
String errorMessage = values.length > 8 ? "Please check dropdown for allowed values." :
@@ -118,4 +109,115 @@ public class ConstraintGenerator {
}
return result.toString();
}
+
+ public void validateDecimalConstraint(Row row, int columnIdx, double min, double max) {
+
+ CellType cellType = row.getCell(columnIdx).getCellType();
+
+ if(cellType == CellType.NUMERIC) {
+ double value = row.getCell(columnIdx).getNumericCellValue();
+
+ if(value >= min && value <= max) {
+ return;
+ }
+ }
+
+ throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected numeric value within range: " + min + " - " + max);
+
+
+ }
+
+ public void validateBooleanConstraint(Row row, int columnIdx) {
+ CellType cellType = row.getCell(columnIdx).getCellType();
+
+ if (cellType == CellType.STRING) {
+ try {
+ //noinspection ResultOfMethodCallIgnored
+ Boolean.valueOf(row.getCell(columnIdx).getStringCellValue());
+ return;
+ } catch (Exception e) {
+ // cont
+ }
+ }
+
+ throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected string value within: [true, false]");
+
+ }
+
+ public void validateLengthConstraint(Row row, int columnIdx, int min, int max) {
+ CellType cellType = row.getCell(columnIdx).getCellType();
+
+ if (cellType == CellType.STRING) {
+
+
+ String value = row.getCell(columnIdx).getStringCellValue();
+
+ if (value.length() >= min && value.length() <= max) {
+ return;
+ }
+ }
+
+ throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected string with length within range: " + min + " - " + max);
+
+ }
+
+ public > void validateEnumConstraint(Row row, int columnIdx, Class clazz) {
+
+ CellType cellType = row.getCell(columnIdx).getCellType();
+
+ if (cellType == CellType.STRING) {
+
+ String value = row.getCell(columnIdx).getStringCellValue();
+
+ if (EnumSet.allOf(clazz).stream().anyMatch(enumValue -> enumValue.name().equals(value))) {
+ return;
+ }
+
+ }
+
+ throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected string value within: [" + String.join(", ", EnumSet.allOf(clazz).stream().map(Enum::name).toArray(String[]::new)) + "]");
+
+
+ }
+
+ public void validateNumericCell(Row row, int columnIdx) {
+ CellType cellType = row.getCell(columnIdx).getCellType();
+
+ if (cellType == CellType.NUMERIC) {
+
+ Double value = row.getCell(columnIdx).getNumericCellValue();
+ return;
+ }
+
+ throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected numeric value");
+
+ }
+
+ public void validateStringCell(Row row, int columnIdx) {
+ CellType cellType = row.getCell(columnIdx).getCellType();
+
+ if (cellType == CellType.STRING) {
+
+ String value = row.getCell(columnIdx).getStringCellValue();
+ return;
+ }
+
+ throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected numeric value");
+
+ }
+
+ public void validateIntegerConstraint(Row row, int columnIdx, int min, int max) {
+ CellType cellType = row.getCell(columnIdx).getCellType();
+
+ if(cellType == CellType.NUMERIC) {
+ double value = row.getCell(columnIdx).getNumericCellValue();
+
+ if(value % 1 == 0 && value >= min && value <= max) {
+ return;
+ }
+ }
+
+ throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected numeric integer value within range: " + min + " - " + max);
+
+ }
}
diff --git a/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderGenerator.java b/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderGenerator.java
index 633fa91..5e888cf 100644
--- a/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderGenerator.java
+++ b/src/main/java/de/avatic/lcc/service/bulk/helper/HeaderGenerator.java
@@ -1,7 +1,7 @@
package de.avatic.lcc.service.bulk.helper;
-import de.avatic.lcc.model.bulk.HeaderProvider;
-import de.avatic.lcc.model.bulk.NodeHeader;
+import de.avatic.lcc.model.bulk.header.HeaderProvider;
+import de.avatic.lcc.util.exception.internalerror.ExcelValidationError;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
@@ -15,27 +15,26 @@ public class HeaderGenerator {
private static final int ADD_COLUMN_SIZE = (10*256);
- public & HeaderProvider> boolean validateHeader(Sheet sheet, Class headers) {
+ public & HeaderProvider> void validateHeader(Sheet sheet, Class headers) {
Row row = sheet.getRow(0);
for(H header : EnumSet.allOf(headers)){
Cell cell = row.getCell(header.ordinal());
if(cell == null || !cell.getStringCellValue().equals(header.getHeader())){
- return false;
+ throw new ExcelValidationError("Unable to validate header " + header.getHeader() + ": Header of column " + toExcelLetter(header.ordinal()) + " has to be " + header.getHeader());
}
}
- return true;
}
- public boolean validateHeader(Sheet sheet, String[] headers) {
+ public void validateHeader(Sheet sheet, String[] headers) {
Row row = sheet.getRow(0);
int idx = 0;
for(String header : headers){
Cell cell = row.getCell(idx++);
if(cell == null || !cell.getStringCellValue().equals(header)){
- return false;
+ throw new ExcelValidationError("Unable to validate header " + header + ": Header of column " + toExcelLetter(idx) + " has to be " + header);
+
}
}
- return true;
}
public & HeaderProvider> void generateHeader(Sheet worksheet, Class headers, CellStyle style) {
@@ -75,4 +74,15 @@ public class HeaderGenerator {
sheet.setColumnWidth(header.ordinal(),sheet.getColumnWidth(header.ordinal())+ADD_COLUMN_SIZE);
}
}
+
+ private String toExcelLetter(int columnIdx) {
+ StringBuilder result = new StringBuilder();
+ columnIdx++; // Convert from 0-based to 1-based for the algorithm
+ while (columnIdx > 0) {
+ columnIdx--; // Adjust for 1-based indexing
+ result.insert(0, (char) ('A' + columnIdx % 26));
+ columnIdx /= 26;
+ }
+ return result.toString();
+ }
}
diff --git a/src/main/java/de/avatic/lcc/service/error/SysErrorService.java b/src/main/java/de/avatic/lcc/service/error/SysErrorService.java
new file mode 100644
index 0000000..49f7563
--- /dev/null
+++ b/src/main/java/de/avatic/lcc/service/error/SysErrorService.java
@@ -0,0 +1,34 @@
+package de.avatic.lcc.service.error;
+
+import de.avatic.lcc.dto.error.ErrorLogDTO;
+import de.avatic.lcc.dto.error.FrontendErrorDTO;
+import de.avatic.lcc.repositories.error.SysErrorRepository;
+import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
+import de.avatic.lcc.repositories.pagination.SearchQueryResult;
+import de.avatic.lcc.service.transformer.error.SysErrorMapper;
+import jakarta.validation.constraints.Min;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class SysErrorService {
+
+
+ private final SysErrorRepository sysErrorRepository;
+ private final SysErrorMapper sysErrorMapper;
+
+ public SysErrorService(SysErrorRepository sysErrorRepository, SysErrorMapper sysErrorMapper) {
+ this.sysErrorRepository = sysErrorRepository;
+ this.sysErrorMapper = sysErrorMapper;
+ }
+
+ public void addErrors(List dto) {
+ sysErrorRepository.insert(dto.stream().map(sysErrorMapper::toSysErrorEntity).toList());
+ }
+
+ public SearchQueryResult listErrors(Optional filter, @Min(1) int page, @Min(1) int limit) {
+ return SearchQueryResult.map(sysErrorRepository.listErrors(filter, new SearchQueryPagination(page, limit)), sysErrorMapper::toSysErrorDto);
+ }
+}
diff --git a/src/main/java/de/avatic/lcc/service/excelMapper/ContainerRateExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/ContainerRateExcelMapper.java
index 377c830..2662be1 100644
--- a/src/main/java/de/avatic/lcc/service/excelMapper/ContainerRateExcelMapper.java
+++ b/src/main/java/de/avatic/lcc/service/excelMapper/ContainerRateExcelMapper.java
@@ -1,8 +1,8 @@
package de.avatic.lcc.service.excelMapper;
import de.avatic.lcc.dto.generic.TransportType;
-import de.avatic.lcc.model.bulk.ContainerRateHeader;
-import de.avatic.lcc.model.bulk.HiddenNodeHeader;
+import de.avatic.lcc.model.bulk.header.ContainerRateHeader;
+import de.avatic.lcc.model.bulk.header.HiddenNodeHeader;
import de.avatic.lcc.model.bulk.HiddenTableType;
import de.avatic.lcc.model.rates.ContainerRate;
import de.avatic.lcc.repositories.NodeRepository;
@@ -67,18 +67,25 @@ public class ContainerRateExcelMapper {
}
public List extractSheet(Sheet sheet) {
- if(!headerGenerator.validateHeader(sheet, ContainerRateHeader.class)) return null;
+ headerGenerator.validateHeader(sheet, ContainerRateHeader.class);
var rates = new ArrayList();
- sheet.forEach(row -> rates.add(mapToEntity(row)));
+
+ sheet.forEach(row -> {
+ if(row.getRowNum() == 0) return;
+ rates.add(mapToEntity(row));
+ });
+
return rates;
}
private ContainerRate mapToEntity(Row row) {
ContainerRate entity = new ContainerRate();
+ validateConstraints(row);
+
entity.setFromNodeId(nodeRepository.getByExternalMappingId(row.getCell(ContainerRateHeader.FROM_NODE.ordinal()).getStringCellValue()).orElseThrow().getId());
- entity.setFromNodeId(nodeRepository.getByExternalMappingId(row.getCell(ContainerRateHeader.TO_NODE.ordinal()).getStringCellValue()).orElseThrow().getId());
+ entity.setToNodeId(nodeRepository.getByExternalMappingId(row.getCell(ContainerRateHeader.TO_NODE.ordinal()).getStringCellValue()).orElseThrow().getId());
entity.setType(TransportType.valueOf(row.getCell(ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal()).getStringCellValue()));
entity.setLeadTime(Double.valueOf(row.getCell(ContainerRateHeader.LEAD_TIME.ordinal()).getNumericCellValue()).intValue());
entity.setType(TransportType.valueOf(row.getCell(ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal()).getStringCellValue()));
@@ -88,4 +95,16 @@ public class ContainerRateExcelMapper {
return entity;
}
+
+ private void validateConstraints(Row row) {
+
+ constraintGenerator.validateStringCell(row, ContainerRateHeader.FROM_NODE.ordinal());
+ constraintGenerator.validateStringCell(row, ContainerRateHeader.TO_NODE.ordinal());
+ constraintGenerator.validateEnumConstraint(row, ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal(), TransportType.class);
+ constraintGenerator.validateDecimalConstraint(row, ContainerRateHeader.RATE_FEU.ordinal(), 0.0, 1000000.0);
+ constraintGenerator.validateDecimalConstraint(row, ContainerRateHeader.RATE_TEU.ordinal(), 0.0, 1000000.0);
+ constraintGenerator.validateDecimalConstraint(row, ContainerRateHeader.RATE_HC.ordinal(), 0.0, 1000000.0);
+ constraintGenerator.validateIntegerConstraint(row, ContainerRateHeader.LEAD_TIME.ordinal(), 0, 365);
+
+ }
}
diff --git a/src/main/java/de/avatic/lcc/service/excelMapper/HiddenCountryExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/HiddenCountryExcelMapper.java
index 523fdfa..85e4d20 100644
--- a/src/main/java/de/avatic/lcc/service/excelMapper/HiddenCountryExcelMapper.java
+++ b/src/main/java/de/avatic/lcc/service/excelMapper/HiddenCountryExcelMapper.java
@@ -1,6 +1,6 @@
package de.avatic.lcc.service.excelMapper;
-import de.avatic.lcc.model.bulk.HiddenCountryHeader;
+import de.avatic.lcc.model.bulk.header.HiddenCountryHeader;
import de.avatic.lcc.model.country.Country;
import de.avatic.lcc.repositories.country.CountryRepository;
import de.avatic.lcc.service.bulk.helper.HeaderGenerator;
diff --git a/src/main/java/de/avatic/lcc/service/excelMapper/HiddenNodeExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/HiddenNodeExcelMapper.java
index a1914cd..f4c42ad 100644
--- a/src/main/java/de/avatic/lcc/service/excelMapper/HiddenNodeExcelMapper.java
+++ b/src/main/java/de/avatic/lcc/service/excelMapper/HiddenNodeExcelMapper.java
@@ -1,6 +1,6 @@
package de.avatic.lcc.service.excelMapper;
-import de.avatic.lcc.model.bulk.HiddenNodeHeader;
+import de.avatic.lcc.model.bulk.header.HiddenNodeHeader;
import de.avatic.lcc.model.nodes.Node;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.service.bulk.helper.HeaderGenerator;
diff --git a/src/main/java/de/avatic/lcc/service/excelMapper/MaterialExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/MaterialExcelMapper.java
index 16b0c1b..0b0913e 100644
--- a/src/main/java/de/avatic/lcc/service/excelMapper/MaterialExcelMapper.java
+++ b/src/main/java/de/avatic/lcc/service/excelMapper/MaterialExcelMapper.java
@@ -1,6 +1,8 @@
package de.avatic.lcc.service.excelMapper;
-import de.avatic.lcc.model.bulk.MaterialHeader;
+import de.avatic.lcc.model.bulk.BulkOperation;
+import de.avatic.lcc.model.bulk.BulkOperationType;
+import de.avatic.lcc.model.bulk.header.MaterialHeader;
import de.avatic.lcc.model.materials.Material;
import de.avatic.lcc.repositories.MaterialRepository;
import de.avatic.lcc.service.bulk.helper.ConstraintGenerator;
@@ -33,6 +35,10 @@ public class MaterialExcelMapper {
}
private void mapToRow(Material material, Row row) {
+
+
+ row.createCell(MaterialHeader.OPERATION.ordinal()).setCellValue(BulkOperationType.UPDATE.name());
+
row.createCell(MaterialHeader.PART_NUMBER.ordinal()).setCellValue(material.getPartNumber());
row.createCell(MaterialHeader.DESCRIPTION.ordinal()).setCellValue(material.getName());
row.createCell(MaterialHeader.HS_CODE.ordinal()).setCellValue(material.getHsCode());
@@ -42,20 +48,30 @@ public class MaterialExcelMapper {
constraintGenerator.createLengthConstraint(sheet, MaterialHeader.PART_NUMBER.ordinal(), 0, 12);
constraintGenerator.createLengthConstraint(sheet, MaterialHeader.HS_CODE.ordinal(), 0, 11);
constraintGenerator.createLengthConstraint(sheet, MaterialHeader.DESCRIPTION.ordinal(), 1, 500);
+ constraintGenerator.createEnumConstraint(sheet, MaterialHeader.OPERATION.ordinal(), BulkOperationType.class);
}
- public List extractSheet(Sheet sheet) {
- if(!headerGenerator.validateHeader(sheet, MaterialHeader.class)) return null;
+ public List> extractSheet(Sheet sheet) {
+ headerGenerator.validateHeader(sheet, MaterialHeader.class);
- var materials = new ArrayList();
- sheet.forEach(row -> materials.add(mapToEntity(row)));
+ var materials = new ArrayList>();
+ sheet.forEach(row -> {
+ if(row.getRowNum() == 0) return;
+ materials.add(mapToEntity(row));
+ });
return materials;
}
- private Material mapToEntity(Row row) {
+ private BulkOperation mapToEntity(Row row) {
Material entity = new Material();
+ constraintGenerator.validateLengthConstraint(row, MaterialHeader.PART_NUMBER.ordinal(), 0, 12);
+ constraintGenerator.validateLengthConstraint(row, MaterialHeader.HS_CODE.ordinal(), 0, 11);
+ constraintGenerator.validateLengthConstraint(row, MaterialHeader.DESCRIPTION.ordinal(), 1, 500);
+ constraintGenerator.validateEnumConstraint(row, MaterialHeader.OPERATION.ordinal(), BulkOperationType.class);
+
+
entity.setPartNumber(row.getCell(MaterialHeader.PART_NUMBER.ordinal()).getStringCellValue());
entity.setName(row.getCell(MaterialHeader.DESCRIPTION.ordinal()).getStringCellValue());
entity.setHsCode(row.getCell(MaterialHeader.HS_CODE.ordinal()).getStringCellValue());
@@ -64,7 +80,7 @@ public class MaterialExcelMapper {
if(!validateHsCode(entity.getHsCode())) throw new IllegalArgumentException("Invalid HS Code");
- return entity;
+ return new BulkOperation<>(entity,BulkOperationType.valueOf(row.getCell(MaterialHeader.OPERATION.ordinal()).getStringCellValue()));
}
private String normalizePartNumber(String partNumber) {
diff --git a/src/main/java/de/avatic/lcc/service/excelMapper/MatrixRateExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/MatrixRateExcelMapper.java
index 69af47b..349e7a6 100644
--- a/src/main/java/de/avatic/lcc/service/excelMapper/MatrixRateExcelMapper.java
+++ b/src/main/java/de/avatic/lcc/service/excelMapper/MatrixRateExcelMapper.java
@@ -1,6 +1,8 @@
package de.avatic.lcc.service.excelMapper;
import de.avatic.lcc.model.bulk.*;
+import de.avatic.lcc.model.bulk.header.HiddenCountryHeader;
+import de.avatic.lcc.model.bulk.header.MatrixRateHeader;
import de.avatic.lcc.model.country.IsoCode;
import de.avatic.lcc.model.rates.MatrixRate;
import de.avatic.lcc.repositories.country.CountryRepository;
@@ -39,16 +41,25 @@ public class MatrixRateExcelMapper {
}
public List extractSheet(Sheet sheet) {
- if(!headerGenerator.validateHeader(sheet, MatrixRateHeader.class)) return null;
+ headerGenerator.validateHeader(sheet, MatrixRateHeader.class);
List rates = new ArrayList<>();
- sheet.forEach(row -> rates.add(mapToEntity(row)));
+
+ sheet.forEach(row -> {
+ if(row.getRowNum() == 0) return;
+ rates.add(mapToEntity(row));
+ });
+
return rates;
}
private MatrixRate mapToEntity(Row row) {
MatrixRate entity = new MatrixRate();
+ constraintGenerator.validateStringCell(row, MatrixRateHeader.FROM_COUNTRY.ordinal());
+ constraintGenerator.validateStringCell(row, MatrixRateHeader.TO_COUNTRY.ordinal());
+ constraintGenerator.validateDecimalConstraint(row, MatrixRateHeader.RATE.ordinal(), 0.0, 1000000.0);
+
entity.setToCountry(countryRepository.getByIsoCode(IsoCode.valueOf(row.getCell(MatrixRateHeader.TO_COUNTRY.ordinal()).getStringCellValue())).orElseThrow().getId());
entity.setFromCountry(countryRepository.getByIsoCode(IsoCode.valueOf(row.getCell(MatrixRateHeader.FROM_COUNTRY.ordinal()).getStringCellValue())).orElseThrow().getId());
entity.setRate(BigDecimal.valueOf(row.getCell(MatrixRateHeader.RATE.ordinal()).getNumericCellValue()));
diff --git a/src/main/java/de/avatic/lcc/service/excelMapper/NodeExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/NodeExcelMapper.java
index 44a106c..61c6930 100644
--- a/src/main/java/de/avatic/lcc/service/excelMapper/NodeExcelMapper.java
+++ b/src/main/java/de/avatic/lcc/service/excelMapper/NodeExcelMapper.java
@@ -1,9 +1,9 @@
package de.avatic.lcc.service.excelMapper;
import de.avatic.lcc.excelmodel.ExcelNode;
-import de.avatic.lcc.model.bulk.HiddenCountryHeader;
-import de.avatic.lcc.model.bulk.HiddenTableType;
-import de.avatic.lcc.model.bulk.NodeHeader;
+import de.avatic.lcc.model.bulk.*;
+import de.avatic.lcc.model.bulk.header.HiddenCountryHeader;
+import de.avatic.lcc.model.bulk.header.NodeHeader;
import de.avatic.lcc.model.country.Country;
import de.avatic.lcc.model.country.IsoCode;
import de.avatic.lcc.model.nodes.Node;
@@ -37,26 +37,26 @@ public class NodeExcelMapper {
this.constraintGenerator = constraintGenerator;
}
- public void fillSheet(Sheet sheet, CellStyle headerStyle)
- {
+ public void fillSheet(Sheet sheet, CellStyle headerStyle) {
headerGenerator.generateHeader(sheet, NodeHeader.class, headerStyle);
- nodeRepository.listAllNodes(false).forEach(node -> mapToRow(node, sheet.createRow(sheet.getLastRowNum()+1)));
+ nodeRepository.listAllNodes(false).forEach(node -> mapToRow(node, sheet.createRow(sheet.getLastRowNum() + 1)));
headerGenerator.fixWidth(sheet, NodeHeader.class);
}
private void mapToRow(Node node, Row row) {
+ row.createCell(NodeHeader.OPERATION.ordinal()).setCellValue(BulkOperationType.UPDATE.name());
row.createCell(NodeHeader.MAPPING_ID.ordinal()).setCellValue(node.getExternalMappingId());
row.createCell(NodeHeader.NAME.ordinal()).setCellValue(node.getName());
row.createCell(NodeHeader.ADDRESS.ordinal()).setCellValue(node.getAddress());
row.createCell(NodeHeader.COUNTRY.ordinal()).setCellValue(countryRepository.getById(node.getCountryId()).orElseThrow().getIsoCode().getCode());
row.createCell(NodeHeader.GEO_LATITUDE.ordinal()).setCellValue(node.getGeoLat().doubleValue());
row.createCell(NodeHeader.GEO_LONGITUDE.ordinal()).setCellValue(node.getGeoLng().doubleValue());
- row.createCell(NodeHeader.IS_SOURCE.ordinal()).setCellValue(node.getSource() ? "true":"false");
- row.createCell(NodeHeader.IS_INTERMEDIATE.ordinal()).setCellValue(node.getIntermediate()? "true":"false");
- row.createCell(NodeHeader.IS_DESTINATION.ordinal()).setCellValue(node.getDestination()? "true":"false");
+ row.createCell(NodeHeader.IS_SOURCE.ordinal()).setCellValue(node.getSource() ? "true" : "false");
+ row.createCell(NodeHeader.IS_INTERMEDIATE.ordinal()).setCellValue(node.getIntermediate() ? "true" : "false");
+ row.createCell(NodeHeader.IS_DESTINATION.ordinal()).setCellValue(node.getDestination() ? "true" : "false");
row.createCell(NodeHeader.OUTBOUND_COUNTRIES.ordinal()).setCellValue(mapOutboundCountriesToCell(node.getOutboundCountries()));
row.createCell(NodeHeader.PREDECESSOR_NODES.ordinal()).setCellValue(mapChains(node.getNodePredecessors()));
- row.createCell(NodeHeader.IS_PREDECESSOR_MANDATORY.ordinal()).setCellValue(node.getPredecessorRequired()? "true":"false");
+ row.createCell(NodeHeader.IS_PREDECESSOR_MANDATORY.ordinal()).setCellValue(node.getPredecessorRequired() ? "true" : "false");
}
private String mapChains(List