From 5e114ce859bbe7b2299fc51a30532bc4ef4f94f5 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 19 Sep 2025 17:00:18 +0200 Subject: [PATCH] Added errors to database and simple error view in frontend --- src/frontend/src/components/UI/TableView.vue | 54 ++-- .../src/components/layout/ErrorLogModal.vue | 127 +++++++++ .../layout/config/BulkOperations.vue | 10 +- src/frontend/src/pages/ErrorLog.vue | 67 +++++ src/frontend/src/router.js | 6 + src/frontend/src/store/error.js | 51 ++-- src/frontend/src/store/errorLog.js | 60 +++++ .../lcc/controller/ErrorController.java | 35 ++- .../bulk/BulkOperationController.java | 41 ++- .../lcc/dto/bulk/BulkProcessingType.java | 2 +- .../de/avatic/lcc/dto/bulk/BulkState.java | 5 + .../de/avatic/lcc/dto/bulk/BulkStatus.java | 4 - .../de/avatic/lcc/dto/bulk/BulkStatusDTO.java | 12 + .../de/avatic/lcc/dto/error/ErrorDTO.java | 8 + .../de/avatic/lcc/dto/error/ErrorLogDTO.java | 112 ++++++++ .../lcc/dto/error/ErrorLogTraceItemDto.java | 41 +++ .../avatic/lcc/model/bulk/BulkOperation.java | 29 ++ .../lcc/model/bulk/BulkOperationType.java | 5 + .../de/avatic/lcc/model/bulk/BulkProcess.java | 71 +++++ .../lcc/model/bulk/BulkProcessState.java | 5 + .../{ => header}/ContainerRateHeader.java | 2 +- .../bulk/{ => header}/HeaderProvider.java | 3 +- .../{ => header}/HiddenCountryHeader.java | 4 +- .../bulk/{ => header}/HiddenNodeHeader.java | 2 +- .../bulk/{ => header}/MaterialHeader.java | 3 +- .../bulk/{ => header}/MatrixRateHeader.java | 2 +- .../model/bulk/{ => header}/NodeHeader.java | 8 +- .../bulk/{ => header}/PackagingHeader.java | 6 +- .../de/avatic/lcc/model/error/SysError.java | 252 ++++++++++++++++++ .../lcc/model/error/SysErrorTraceItem.java | 145 ++++++++++ .../avatic/lcc/model/error/SysErrorType.java | 5 + .../bulk/BulkOperationRepository.java | 7 + .../error/SysErrorRepository.java | 178 +++++++++++++ .../bulk/BulkFileProcessingService.java | 21 +- .../service/bulk/BulkProcessingService.java | 30 +++ .../lcc/service/bulk/BulkStatusService.java | 12 - .../service/bulk/TemplateExportService.java | 1 + .../bulk/helper/ConstraintGenerator.java | 126 ++++++++- .../service/bulk/helper/HeaderGenerator.java | 26 +- .../lcc/service/error/SysErrorService.java | 34 +++ .../excelMapper/ContainerRateExcelMapper.java | 29 +- .../excelMapper/HiddenCountryExcelMapper.java | 2 +- .../excelMapper/HiddenNodeExcelMapper.java | 2 +- .../excelMapper/MaterialExcelMapper.java | 30 ++- .../excelMapper/MatrixRateExcelMapper.java | 15 +- .../service/excelMapper/NodeExcelMapper.java | 73 +++-- .../excelMapper/PackagingExcelMapper.java | 46 +++- .../transformer/error/SysErrorMapper.java | 121 +++++++++ .../base/InternalErrorException.java | 6 + .../internalerror/ExcelValidationError.java | 11 + .../internalerror/PremiseValidationError.java | 2 +- src/main/resources/application.properties | 2 + src/main/resources/schema.sql | 61 ++++- 53 files changed, 1827 insertions(+), 185 deletions(-) create mode 100644 src/frontend/src/components/layout/ErrorLogModal.vue create mode 100644 src/frontend/src/pages/ErrorLog.vue create mode 100644 src/frontend/src/store/errorLog.js create mode 100644 src/main/java/de/avatic/lcc/dto/bulk/BulkState.java delete mode 100644 src/main/java/de/avatic/lcc/dto/bulk/BulkStatus.java create mode 100644 src/main/java/de/avatic/lcc/dto/bulk/BulkStatusDTO.java create mode 100644 src/main/java/de/avatic/lcc/dto/error/ErrorLogDTO.java create mode 100644 src/main/java/de/avatic/lcc/dto/error/ErrorLogTraceItemDto.java create mode 100644 src/main/java/de/avatic/lcc/model/bulk/BulkOperation.java create mode 100644 src/main/java/de/avatic/lcc/model/bulk/BulkOperationType.java create mode 100644 src/main/java/de/avatic/lcc/model/bulk/BulkProcess.java create mode 100644 src/main/java/de/avatic/lcc/model/bulk/BulkProcessState.java rename src/main/java/de/avatic/lcc/model/bulk/{ => header}/ContainerRateHeader.java (91%) rename src/main/java/de/avatic/lcc/model/bulk/{ => header}/HeaderProvider.java (59%) rename src/main/java/de/avatic/lcc/model/bulk/{ => header}/HiddenCountryHeader.java (69%) rename src/main/java/de/avatic/lcc/model/bulk/{ => header}/HiddenNodeHeader.java (87%) rename src/main/java/de/avatic/lcc/model/bulk/{ => header}/MaterialHeader.java (82%) rename src/main/java/de/avatic/lcc/model/bulk/{ => header}/MatrixRateHeader.java (90%) rename src/main/java/de/avatic/lcc/model/bulk/{ => header}/NodeHeader.java (79%) rename src/main/java/de/avatic/lcc/model/bulk/{ => header}/PackagingHeader.java (87%) create mode 100644 src/main/java/de/avatic/lcc/model/error/SysError.java create mode 100644 src/main/java/de/avatic/lcc/model/error/SysErrorTraceItem.java create mode 100644 src/main/java/de/avatic/lcc/model/error/SysErrorType.java create mode 100644 src/main/java/de/avatic/lcc/repositories/bulk/BulkOperationRepository.java create mode 100644 src/main/java/de/avatic/lcc/repositories/error/SysErrorRepository.java create mode 100644 src/main/java/de/avatic/lcc/service/bulk/BulkProcessingService.java delete mode 100644 src/main/java/de/avatic/lcc/service/bulk/BulkStatusService.java create mode 100644 src/main/java/de/avatic/lcc/service/error/SysErrorService.java create mode 100644 src/main/java/de/avatic/lcc/service/transformer/error/SysErrorMapper.java create mode 100644 src/main/java/de/avatic/lcc/util/exception/internalerror/ExcelValidationError.java 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 @@ + + + + + \ 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 -
import mode
-
- append existing data - fully replace data -
file
@@ -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> chains) { @@ -86,42 +86,71 @@ public class NodeExcelMapper { constraintGenerator.createLengthConstraint(sheet, NodeHeader.ADDRESS.ordinal(), 1, 500); constraintGenerator.createLengthConstraint(sheet, NodeHeader.NAME.ordinal(), 1, 255); + constraintGenerator.createEnumConstraint(sheet, NodeHeader.OPERATION.ordinal(), BulkOperationType.class); + } - public List extractSheet(Sheet sheet) { - if(!headerGenerator.validateHeader(sheet, NodeHeader.class)) return null; + public List> extractSheet(Sheet sheet) { + headerGenerator.validateHeader(sheet, NodeHeader.class); - var nodes = new ArrayList(); - sheet.forEach(row -> nodes.add(mapToEntity(row))); + var nodes = new ArrayList>(); + sheet.forEach(row -> { + if (row.getRowNum() == 0) return; + nodes.add(mapToEntity(row)); + }); return nodes; } - private ExcelNode mapToEntity(Row row) { + private BulkOperation mapToEntity(Row row) { ExcelNode entity = new ExcelNode(); + validateConstraints(row); + entity.setExternalMappingId(row.getCell(NodeHeader.MAPPING_ID.ordinal()).getStringCellValue()); entity.setName(row.getCell(NodeHeader.NAME.ordinal()).getStringCellValue()); entity.setAddress(row.getCell(NodeHeader.ADDRESS.ordinal()).getStringCellValue()); entity.setCountryId(IsoCode.valueOf(row.getCell(NodeHeader.COUNTRY.ordinal()).getStringCellValue())); entity.setGeoLat(BigDecimal.valueOf(row.getCell(NodeHeader.GEO_LATITUDE.ordinal()).getNumericCellValue())); entity.setGeoLng(BigDecimal.valueOf(row.getCell(NodeHeader.GEO_LONGITUDE.ordinal()).getNumericCellValue())); - entity.setSource(row.getCell(NodeHeader.IS_SOURCE.ordinal()).getBooleanCellValue()); - entity.setIntermediate(row.getCell(NodeHeader.IS_INTERMEDIATE.ordinal()).getBooleanCellValue()); - entity.setDestination(row.getCell(NodeHeader.IS_DESTINATION.ordinal()).getBooleanCellValue()); - entity.setPredecessorRequired(row.getCell(NodeHeader.IS_PREDECESSOR_MANDATORY.ordinal()).getBooleanCellValue()); + entity.setSource(Boolean.valueOf(row.getCell(NodeHeader.IS_SOURCE.ordinal()).getStringCellValue())); + entity.setIntermediate(Boolean.valueOf(row.getCell(NodeHeader.IS_INTERMEDIATE.ordinal()).getStringCellValue())); + entity.setDestination(Boolean.valueOf(row.getCell(NodeHeader.IS_DESTINATION.ordinal()).getStringCellValue())); + entity.setPredecessorRequired(Boolean.valueOf(row.getCell(NodeHeader.IS_PREDECESSOR_MANDATORY.ordinal()).getStringCellValue())); entity.setNodePredecessors(mapChainsFromCell(row.getCell(NodeHeader.PREDECESSOR_NODES.ordinal()).getStringCellValue())); entity.setOutboundCountries(mapOutboundCountriesFromCell(row.getCell(NodeHeader.OUTBOUND_COUNTRIES.ordinal()).getStringCellValue())); - return entity; + return new BulkOperation<>(entity, BulkOperationType.valueOf(row.getCell(NodeHeader.OPERATION.ordinal()).getStringCellValue())); + } + + + private void validateConstraints(Row row) { + + constraintGenerator.validateStringCell(row, NodeHeader.MAPPING_ID.ordinal()); + constraintGenerator.validateStringCell(row, NodeHeader.COUNTRY.ordinal()); + constraintGenerator.validateStringCell(row, NodeHeader.OUTBOUND_COUNTRIES.ordinal()); + constraintGenerator.validateStringCell(row, NodeHeader.PREDECESSOR_NODES.ordinal()); + constraintGenerator.validateStringCell(row, NodeHeader.NAME.ordinal()); + constraintGenerator.validateStringCell(row, NodeHeader.ADDRESS.ordinal()); + + constraintGenerator.validateDecimalConstraint(row, NodeHeader.GEO_LATITUDE.ordinal(), -90.0, 90.0); + constraintGenerator.validateDecimalConstraint(row, NodeHeader.GEO_LONGITUDE.ordinal(), -180.0, 180.0); + constraintGenerator.validateBooleanConstraint(row, NodeHeader.IS_SOURCE.ordinal()); + constraintGenerator.validateBooleanConstraint(row, NodeHeader.IS_INTERMEDIATE.ordinal()); + constraintGenerator.validateBooleanConstraint(row, NodeHeader.IS_DESTINATION.ordinal()); + constraintGenerator.validateBooleanConstraint(row, NodeHeader.IS_PREDECESSOR_MANDATORY.ordinal()); + constraintGenerator.validateLengthConstraint(row, NodeHeader.ADDRESS.ordinal(), 1, 500); + constraintGenerator.validateLengthConstraint(row, NodeHeader.NAME.ordinal(), 1, 255); + constraintGenerator.validateEnumConstraint(row, NodeHeader.OPERATION.ordinal(), BulkOperationType.class); + } private List mapOutboundCountriesFromCell(String outboundCountryIds) { - if(outboundCountryIds == null || outboundCountryIds.isBlank()) return Collections.emptyList(); + if (outboundCountryIds == null || outboundCountryIds.isBlank()) return Collections.emptyList(); return Arrays.stream(outboundCountryIds.split(",")).map(String::trim).toList(); } private List> mapChainsFromCell(String cell) { - if(cell == null || cell.isBlank()) return Collections.emptyList(); + if (cell == null || cell.isBlank()) return Collections.emptyList(); return Arrays.stream(cell.split(";")).map(String::trim).map(this::mapChainFromCell).toList(); } diff --git a/src/main/java/de/avatic/lcc/service/excelMapper/PackagingExcelMapper.java b/src/main/java/de/avatic/lcc/service/excelMapper/PackagingExcelMapper.java index 3a50710..cb06895 100644 --- a/src/main/java/de/avatic/lcc/service/excelMapper/PackagingExcelMapper.java +++ b/src/main/java/de/avatic/lcc/service/excelMapper/PackagingExcelMapper.java @@ -2,9 +2,9 @@ package de.avatic.lcc.service.excelMapper; import de.avatic.lcc.excelmodel.ExcelPackaging; -import de.avatic.lcc.model.bulk.HiddenNodeHeader; -import de.avatic.lcc.model.bulk.HiddenTableType; -import de.avatic.lcc.model.bulk.PackagingHeader; +import de.avatic.lcc.model.bulk.*; +import de.avatic.lcc.model.bulk.header.HiddenNodeHeader; +import de.avatic.lcc.model.bulk.header.PackagingHeader; import de.avatic.lcc.model.packaging.Packaging; import de.avatic.lcc.model.packaging.PackagingDimension; import de.avatic.lcc.model.properties.PropertyType; @@ -61,6 +61,8 @@ public class PackagingExcelMapper { Optional shu = packagingDimensionRepository.getById(packaging.getShuId()); Optional hu = packagingDimensionRepository.getById(packaging.getShuId()); + row.createCell(PackagingHeader.OPERATION.ordinal()).setCellValue(BulkOperationType.UPDATE.name()); + row.createCell(PackagingHeader.PART_NUMBER.ordinal()).setCellValue(materialRepository.getByIdIncludeDeprecated(packaging.getMaterialId()).orElseThrow().getPartNumber()); row.createCell(PackagingHeader.SUPPLIER.ordinal()).setCellValue(nodeRepository.getById(packaging.getSupplierId()).orElseThrow().getExternalMappingId()); @@ -104,9 +106,9 @@ public class PackagingExcelMapper { if (resolver.apply(dimension.get()) instanceof String) cell.setCellValue((String) resolver.apply(dimension.get())); if (resolver.apply(dimension.get()) instanceof DimensionUnit) - cell.setCellValue(((DimensionUnit) resolver.apply(dimension.get())).getDisplayedName()); + cell.setCellValue(((DimensionUnit) resolver.apply(dimension.get())).name()); if (resolver.apply(dimension.get()) instanceof WeightUnit) - cell.setCellValue(((WeightUnit) resolver.apply(dimension.get())).getDisplayedName()); + cell.setCellValue(((WeightUnit) resolver.apply(dimension.get())).name()); } else cell.setBlank(); } @@ -121,6 +123,8 @@ public class PackagingExcelMapper { constraintGenerator.createEnumConstraint(sheet, PackagingHeader.HU_DIMENSION_UNIT.ordinal(), DimensionUnit.class); constraintGenerator.createEnumConstraint(sheet, PackagingHeader.HU_WEIGHT_UNIT.ordinal(), WeightUnit.class); + constraintGenerator.createEnumConstraint(sheet, PackagingHeader.OPERATION.ordinal(), BulkOperationType.class); + //TODO: check hu dimensions... @@ -129,10 +133,13 @@ public class PackagingExcelMapper { } public List extractSheet(Sheet sheet) { - if (!headerGenerator.validateHeader(sheet, PackagingHeader.class)) return null; + headerGenerator.validateHeader(sheet, PackagingHeader.class); var packaging = new ArrayList(); - sheet.forEach(row -> packaging.add(mapToEntity(row))); + sheet.forEach(row -> { + if(row.getRowNum() == 0) return; + packaging.add(mapToEntity(row)); + }); return packaging; } @@ -140,6 +147,8 @@ public class PackagingExcelMapper { private ExcelPackaging mapToEntity(Row row) { ExcelPackaging entity = new ExcelPackaging(); + validateConstraints(row); + entity.setSupplierMappingId(row.getCell(PackagingHeader.SUPPLIER.ordinal()).getStringCellValue()); entity.setPartNumber(row.getCell(PackagingHeader.PART_NUMBER.ordinal()).getStringCellValue()); @@ -162,4 +171,27 @@ public class PackagingExcelMapper { return entity; } + + private void validateConstraints(Row row) { + + constraintGenerator.validateStringCell(row, PackagingHeader.SUPPLIER.ordinal()); + constraintGenerator.validateStringCell(row, PackagingHeader.PART_NUMBER.ordinal()); + + constraintGenerator.validateNumericCell(row, PackagingHeader.HU_HEIGHT.ordinal()); + constraintGenerator.validateNumericCell(row, PackagingHeader.HU_WIDTH.ordinal()); + constraintGenerator.validateNumericCell(row, PackagingHeader.HU_LENGTH.ordinal()); + constraintGenerator.validateNumericCell(row, PackagingHeader.HU_WEIGHT.ordinal()); + constraintGenerator.validateNumericCell(row, PackagingHeader.SHU_HEIGHT.ordinal()); + constraintGenerator.validateNumericCell(row, PackagingHeader.SHU_WIDTH.ordinal()); + constraintGenerator.validateNumericCell(row, PackagingHeader.SHU_LENGTH.ordinal()); + constraintGenerator.validateNumericCell(row, PackagingHeader.SHU_WEIGHT.ordinal()); + + constraintGenerator.validateLengthConstraint(row, PackagingHeader.PART_NUMBER.ordinal(), 0, 12); + constraintGenerator.validateEnumConstraint(row, PackagingHeader.SHU_DIMENSION_UNIT.ordinal(), DimensionUnit.class); + constraintGenerator.validateEnumConstraint(row, PackagingHeader.SHU_WEIGHT_UNIT.ordinal(), WeightUnit.class); + constraintGenerator.validateEnumConstraint(row, PackagingHeader.HU_DIMENSION_UNIT.ordinal(), DimensionUnit.class); + constraintGenerator.validateEnumConstraint(row, PackagingHeader.HU_WEIGHT_UNIT.ordinal(), WeightUnit.class); + constraintGenerator.validateEnumConstraint(row, PackagingHeader.OPERATION.ordinal(), BulkOperationType.class); + + } } diff --git a/src/main/java/de/avatic/lcc/service/transformer/error/SysErrorMapper.java b/src/main/java/de/avatic/lcc/service/transformer/error/SysErrorMapper.java new file mode 100644 index 0000000..3736ace --- /dev/null +++ b/src/main/java/de/avatic/lcc/service/transformer/error/SysErrorMapper.java @@ -0,0 +1,121 @@ +package de.avatic.lcc.service.transformer.error; + +import de.avatic.lcc.dto.error.ErrorLogDTO; +import de.avatic.lcc.dto.error.ErrorLogTraceItemDto; +import de.avatic.lcc.dto.error.FrontendErrorDTO; +import de.avatic.lcc.model.error.SysError; +import de.avatic.lcc.model.error.SysErrorTraceItem; +import de.avatic.lcc.model.error.SysErrorType; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Service +public class SysErrorMapper { + + + private static final String TRACE_REGEX = "at\\s+(?:async\\s+)?(?:(.+?)\\s+)?\\(([^?]+(?:\\?[^:]*)?):(\\d+):\\d+\\)"; + private static final Pattern TRACE_REGEX_PATTERN = Pattern.compile(TRACE_REGEX); + + public SysError toSysErrorEntity(FrontendErrorDTO frontendErrorDTO) { + int userId = 1; //TODO use actual user + + SysError entity = new SysError(); + + entity.setCode(frontendErrorDTO.getError().getCode()); + entity.setMessage(frontendErrorDTO.getError().getMessage()); + entity.setTitle(frontendErrorDTO.getError().getTitle()); + entity.setPinia(frontendErrorDTO.getState()); + entity.setUserId(userId); + + var traceItems = frontendErrorDTO.getError().getTrace(); + + if (traceItems == null) { + entity.setType(SysErrorType.FRONTEND); + + var traceCombined = frontendErrorDTO.getError().getTraceCombined(); + if (null != traceCombined) { + entity.setTrace(toSysErrorTraceItem(frontendErrorDTO.getError().getTraceCombined())); + } + + } else { + entity.setType(SysErrorType.BACKEND); + entity.setTrace(toSysErrorTraceItem(frontendErrorDTO.getError().getTrace())); + } + + return entity; + + } + + private List toSysErrorTraceItem(String traceCombined) { + List items = new ArrayList<>(); + var lines = traceCombined.split("\n"); + + for (var line : lines) { + Matcher matcher = TRACE_REGEX_PATTERN.matcher(line); + + while (matcher.find()) { + SysErrorTraceItem item = new SysErrorTraceItem(); + + item.setFile(matcher.group(2)); + item.setLine(Integer.parseInt(matcher.group(3))); + item.setMethod(matcher.group(1)); + item.setFullPath("at " + matcher.group(1)); + + items.add(item); + + } + + } + + return items; + } + + private List toSysErrorTraceItem(List trace) { + List items = new ArrayList<>(); + + for(var traceElement : trace) { + SysErrorTraceItem item = new SysErrorTraceItem(); + item.setFile(traceElement.getFileName()); + item.setLine(traceElement.getLineNumber()); + item.setMethod(traceElement.getMethodName()); + item.setFullPath("at " + traceElement.getClassName() + "." + traceElement.getMethodName()); + + items.add(item); + } + + return items; + + } + + public ErrorLogDTO toSysErrorDto(SysError sysError) { + var dto = new ErrorLogDTO(); + + dto.setUserId(sysError.getUserId()); + dto.setCode(sysError.getCode()); + dto.setMessage(sysError.getMessage()); + dto.setTitle(sysError.getTitle()); + dto.setPinia(sysError.getPinia()); + dto.setBulkOperationId(sysError.getBulkOperationId()); + dto.setCalculationJobId(sysError.getCalculationJobId()); + dto.setType(sysError.getType().name()); + dto.setTrace(sysError.getTrace().stream().map(this::toSysErrorTraceItemDto).toList()); + dto.setCreatedAt(sysError.getCreatedAt()); + + return dto; + } + + private ErrorLogTraceItemDto toSysErrorTraceItemDto(SysErrorTraceItem sysErrorTraceItem) { + var dto = new ErrorLogTraceItemDto(); + + dto.setFullPath(sysErrorTraceItem.getFullPath()); + dto.setMethod(sysErrorTraceItem.getMethod()); + dto.setLine(sysErrorTraceItem.getLine()); + dto.setFile(sysErrorTraceItem.getFile()); + + return dto; + } +} diff --git a/src/main/java/de/avatic/lcc/util/exception/base/InternalErrorException.java b/src/main/java/de/avatic/lcc/util/exception/base/InternalErrorException.java index 2d0585f..f6af10c 100644 --- a/src/main/java/de/avatic/lcc/util/exception/base/InternalErrorException.java +++ b/src/main/java/de/avatic/lcc/util/exception/base/InternalErrorException.java @@ -12,6 +12,12 @@ public class InternalErrorException extends RuntimeException{ this.message = message; } + public InternalErrorException(String title, String message) { + super(message); + this.title = title; + this.message = message; + } + public InternalErrorException(String message, Exception trace) { super(message, trace); this.title = "Internal Error"; diff --git a/src/main/java/de/avatic/lcc/util/exception/internalerror/ExcelValidationError.java b/src/main/java/de/avatic/lcc/util/exception/internalerror/ExcelValidationError.java new file mode 100644 index 0000000..bc74c0f --- /dev/null +++ b/src/main/java/de/avatic/lcc/util/exception/internalerror/ExcelValidationError.java @@ -0,0 +1,11 @@ +package de.avatic.lcc.util.exception.internalerror; + +import de.avatic.lcc.util.exception.base.InternalErrorException; + +public class ExcelValidationError extends InternalErrorException { + + public ExcelValidationError(String message) { + super("Excel validation failed.", message); + } + +} diff --git a/src/main/java/de/avatic/lcc/util/exception/internalerror/PremiseValidationError.java b/src/main/java/de/avatic/lcc/util/exception/internalerror/PremiseValidationError.java index 83756f0..b8ff029 100644 --- a/src/main/java/de/avatic/lcc/util/exception/internalerror/PremiseValidationError.java +++ b/src/main/java/de/avatic/lcc/util/exception/internalerror/PremiseValidationError.java @@ -5,7 +5,7 @@ import de.avatic.lcc.util.exception.base.InternalErrorException; public class PremiseValidationError extends InternalErrorException { public PremiseValidationError(String message) { - super(message); + super("Calculation data validation failed.", message); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f60fde8..b1c9ada 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -11,4 +11,6 @@ lcc.allowed_cors=${ALLOWED_CORS_DOMAIN} azure.maps.subscription.key=${AZURE_MAPS_SUBSCRIPTION_KEY} azure.maps.client.id=your-app-registration-client-id azure.maps.resource.id=/subscriptions/sub-id/resourceGroups/rg-name/providers/Microsoft.Maps/accounts/account-name +spring.servlet.multipart.max-file-size=30MB +spring.servlet.multipart.max-request-size=50MB diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 16afb82..a776daa 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -45,11 +45,11 @@ CREATE TABLE IF NOT EXISTS `system_property` -- country CREATE TABLE IF NOT EXISTS `country` ( - `id` INT NOT NULL AUTO_INCREMENT, - `iso_code` CHAR(2) NOT NULL COMMENT 'ISO 3166-1 alpha-2 country code', - `region_code` CHAR(5) NOT NULL COMMENT 'Geographic region code (EMEA/LATAM/APAC/NAM)', + `id` INT NOT NULL AUTO_INCREMENT, + `iso_code` CHAR(2) NOT NULL COMMENT 'ISO 3166-1 alpha-2 country code', + `region_code` CHAR(5) NOT NULL COMMENT 'Geographic region code (EMEA/LATAM/APAC/NAM)', `name` VARCHAR(255) NOT NULL, - `is_deprecated` BOOLEAN NOT NULL DEFAULT FALSE, + `is_deprecated` BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY (`id`), UNIQUE KEY `uk_country_iso_code` (`iso_code`), CONSTRAINT `chk_country_region_code` @@ -403,7 +403,7 @@ CREATE TABLE IF NOT EXISTS premise_route_node user_node_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, address VARCHAR(500), - external_mapping_id VARCHAR(32) NOT NULL, + external_mapping_id VARCHAR(32) NOT NULL, country_id INT NOT NULL, is_destination BOOLEAN DEFAULT FALSE, is_intermediate BOOLEAN DEFAULT FALSE, @@ -466,7 +466,7 @@ CREATE TABLE IF NOT EXISTS calculation_job property_set_id INT NOT NULL, job_state CHAR(10) NOT NULL CHECK (job_state IN ('CREATED', 'SCHEDULED', 'VALID', 'INVALID', 'EXCEPTION')), - error_id INT DEFAULT NULL, + error_id INT DEFAULT NULL, user_id INT NOT NULL, FOREIGN KEY (premise_id) REFERENCES premise (id), FOREIGN KEY (validity_period_id) REFERENCES validity_period (id), @@ -565,3 +565,52 @@ CREATE TABLE IF NOT EXISTS calculation_job_route_section ); +CREATE TABLE IF NOT EXISTS bulk_operation +( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + bulk_file_type CHAR(32) NOT NULL, + state CHAR(10) NOT NULL, + file LONGBLOB NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES sys_user (id), + CONSTRAINT chk_bulk_file_type CHECK (bulk_file_type IN + ('CONTAINER_RATE', 'COUNTRY_MATRIX', 'MATERIAL', 'PACKAGING', 'NODE')), + CONSTRAINT chk_bulk_operation_state CHECK (state IN ('SCHEDULED', 'PROCESSING', 'COMPLETED', 'EXCEPTION')) +); + +CREATE TABLE IF NOT EXISTS sys_error +( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + user_id INT DEFAULT NULL, + title VARCHAR(255) NOT NULL, + code VARCHAR(255) NOT NULL, + message VARCHAR(512) NOT NULL, + pinia TEXT, + calculation_job_id INT DEFAULT NULL, + bulk_operation_id INT DEFAULT NULL, + type CHAR(16) NOT NULL DEFAULT 'BACKEND', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES sys_user (id), + FOREIGN KEY (calculation_job_id) REFERENCES calculation_job (id), + FOREIGN KEY (bulk_operation_id) REFERENCES bulk_operation (id), + CONSTRAINT chk_error_type CHECK (type IN ('BACKEND', 'FRONTEND', 'BULK', 'CALCULATION')), + INDEX idx_user_id (user_id), + INDEX idx_calculation_job_id (calculation_job_id) +); + +CREATE TABLE IF NOT EXISTS sys_error_trace_item +( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + error_id INT NOT NULL, + line INT UNSIGNED NOT NULL, + file VARCHAR(255) NOT NULL, + method VARCHAR(255) NOT NULL, + fullPath VARCHAR(512) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (error_id) REFERENCES sys_error (id) +); + + + +