Added errors to database and simple error view in frontend
This commit is contained in:
parent
e5bd56d3a9
commit
5e114ce859
53 changed files with 1827 additions and 185 deletions
|
|
@ -32,7 +32,7 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
<tbody v-else>
|
||||
<tr v-for="(item, index) in data" :key="index" class="table-row">
|
||||
<tr v-for="(item, index) in data" :key="index" class="table-row" @click="$emit('row-click', item)" :class="{'table-row--hover': mouseOver}">
|
||||
<td v-for="column in columns" :key="column.key" class="table-cell" :class="getAlignment(column.align)">
|
||||
<span v-if="column.iconResolver == null">{{ getCellValue(item, column) }}</span>
|
||||
<component v-else
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
127
src/frontend/src/components/layout/ErrorLogModal.vue
Normal file
127
src/frontend/src/components/layout/ErrorLogModal.vue
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<template>
|
||||
|
||||
<div class="trace-view-container" :class="trace ? '' : 'trace-view-container--no-trace'">
|
||||
<div class="trace-view-header">
|
||||
<h3 class="sub-header">{{ title }} <br><span class="trace-view-message">{{ message }}</span>
|
||||
</h3>
|
||||
<icon-button icon="x" @click="$emit('close')"></icon-button>
|
||||
</div>
|
||||
<div v-if="trace" class="trace-view">
|
||||
<div class="trace-view-exception">{{ exception }}</div>
|
||||
<div :class="highlightClasses(traceItem)" v-for="traceItem of trace">{{ fullClassName(traceItem) }} (<span
|
||||
class="trace-view-file">{{ fileName(traceItem) }}</span>)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||
import IconButton from "@/components/UI/IconButton.vue";
|
||||
import {PhWarning} from "@phosphor-icons/vue";
|
||||
|
||||
export default {
|
||||
name: "ErrorLogModal",
|
||||
components: {PhWarning, IconButton, BasicButton},
|
||||
emits: ['close'],
|
||||
props: {
|
||||
error: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
trace() {
|
||||
return this.error.trace;
|
||||
},
|
||||
exception() {
|
||||
return this.error.code;
|
||||
},
|
||||
title() {
|
||||
return this.error.title;
|
||||
},
|
||||
code() {
|
||||
return this.error.code;
|
||||
},
|
||||
message() {
|
||||
return this.error.message;
|
||||
},
|
||||
|
||||
},
|
||||
created() {
|
||||
console.log(this.error);
|
||||
console.log(this.error.trace);
|
||||
},
|
||||
methods: {
|
||||
highlightClasses(traceItem) {
|
||||
return traceItem.fullPath?.includes('de.avatic.lcc') ? 'highlight-trace-item' : 'trace-item';
|
||||
},
|
||||
fullClassName(traceItem) {
|
||||
return traceItem.fullPath;
|
||||
},
|
||||
fileName(traceItem) {
|
||||
return `${traceItem.file}:${traceItem.line}`;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Cal+Sans&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap');
|
||||
|
||||
.highlight-trace-item {
|
||||
|
||||
color: #002F54;
|
||||
background-color: #F2F2F2;
|
||||
padding-left: 1.6rem;
|
||||
}
|
||||
|
||||
.trace-item {
|
||||
padding-left: 1.6rem;
|
||||
}
|
||||
|
||||
.trace-view {
|
||||
overflow: auto;
|
||||
font-family: Roboto Mono, monospace;
|
||||
font-size: 1.2rem;
|
||||
flex: 1;
|
||||
padding: 0 1.6rem;
|
||||
}
|
||||
|
||||
.trace-view-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.trace-view-message {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.trace-view-file {
|
||||
font-weight: 500;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.trace-view-exception {
|
||||
|
||||
|
||||
}
|
||||
|
||||
.trace-view-container {
|
||||
height: 90vh;
|
||||
width: 80vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.6rem;
|
||||
}
|
||||
|
||||
.trace-view-container--no-trace {
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -59,11 +59,6 @@
|
|||
<radio-option name="import-dataset" value="MATERIAL" v-model="importDataset">materials</radio-option>
|
||||
<radio-option name="import-dataset" value="PACKAGING" v-model="importDataset">packaging</radio-option>
|
||||
</div>
|
||||
<div class="bulk-operation-caption">import mode</div>
|
||||
<div class="bulk-operation-data">
|
||||
<radio-option name="import-type" value="APPEND" v-model="importType">append existing data</radio-option>
|
||||
<radio-option name="import-type" value="FULL" v-model="importType">fully replace data</radio-option>
|
||||
</div>
|
||||
|
||||
<div class="bulk-operation-caption">file</div>
|
||||
<div class="bulk-operation-data">
|
||||
|
|
@ -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 => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
67
src/frontend/src/pages/ErrorLog.vue
Normal file
67
src/frontend/src/pages/ErrorLog.vue
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<template>
|
||||
<div>
|
||||
<h2 class="page-header">Errors</h2>
|
||||
|
||||
<modal :state="showModal">
|
||||
<error-log-modal :error="error" @close="showModal = false"></error-log-modal>
|
||||
</modal>
|
||||
<table-view :columns="columns" :data-source="fetchData" :page="pagination.page" :page-size="pageSize" :page-count="pagination.pageCount" :total-count="pagination.totalCount" @row-click="showDetails" :mouse-over="true"></table-view>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import TableView from "@/components/UI/TableView.vue";
|
||||
import {useErrorLogStore} from "@/store/errorLog.js";
|
||||
import {mapStores} from "pinia";
|
||||
import Modal from "@/components/UI/Modal.vue";
|
||||
import TraceView from "@/components/layout/TraceView.vue";
|
||||
import ErrorLogModal from "@/components/layout/ErrorLogModal.vue";
|
||||
|
||||
export default {
|
||||
name: "ErrorLog",
|
||||
components: {ErrorLogModal, TraceView, Modal, TableView},
|
||||
data() {
|
||||
return {
|
||||
showModal: false,
|
||||
error: null,
|
||||
pageSize: 10,
|
||||
pagination: { page: 1, pageCount: 10, totalCount: 1 },
|
||||
columns: [
|
||||
{key: 'type', label: 'Type'},
|
||||
{key: 'title', label: 'Title'},
|
||||
{key: 'message', label: 'Message'},
|
||||
{key: 'code', label: 'Exception'},
|
||||
{key: 'timestamp', label: 'Timestamp', formatter: (value) => this.buildDate(value) },
|
||||
{key: 'user_id', label: 'User'},
|
||||
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useErrorLogStore)
|
||||
},
|
||||
methods: {
|
||||
async fetchData(query) {
|
||||
await this.errorLogStore.setQuery(query);
|
||||
this.pagination = this.errorLogStore.getPagination;
|
||||
return this.errorLogStore.getErrors;
|
||||
},
|
||||
buildDate(date) {
|
||||
return `${date[0]}-${date[1].toString().padStart(2, '0')}-${date[2].toString().padStart(2, '0')} ${date[3].toString().padStart(2, '0')}:${date[4].toString().padStart(2, '0')}:${date[5].toString().padStart(2, '0')}`
|
||||
},
|
||||
showDetails(error) {
|
||||
console.log("click")
|
||||
this.error = error;
|
||||
this.showModal = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
60
src/frontend/src/store/errorLog.js
Normal file
60
src/frontend/src/store/errorLog.js
Normal file
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -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<FrontendErrorDTO> error) {
|
||||
System.out.println(error.toString());
|
||||
//TODO store in database.
|
||||
public void error(@RequestBody List<FrontendErrorDTO> errors) {
|
||||
sysErrorService.addErrors(errors);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<ErrorLogDTO>> listErrors(@RequestParam(defaultValue = "20") @Min(1) int limit,
|
||||
@RequestParam(defaultValue = "1") @Min(1) int page,
|
||||
@RequestParam(required = false) Optional<String> filter) {
|
||||
SearchQueryResult<ErrorLogDTO> 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<BulkStatus> getUploadStatus(@PathVariable("processing_id") Integer id) {
|
||||
return ResponseEntity.ok(bulkProcessingService.getStatus(id));
|
||||
@GetMapping({"/status/","/status"})
|
||||
public ResponseEntity<List<BulkStatusDTO>> 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<Integer> 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<Integer> 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<InputStreamResource> downloadFile(@PathVariable BulkFileType type) throws IOException {
|
||||
public ResponseEntity<InputStreamResource> 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<InputStreamResource> downloadFile(@PathVariable BulkFileType type, @PathVariable("validity_period_id") Integer validityPeriodId) throws IOException {
|
||||
public ResponseEntity<InputStreamResource> 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<InputStreamResource> 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)));
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
package de.avatic.lcc.dto.bulk;
|
||||
|
||||
public enum BulkProcessingType {
|
||||
FULL, APPEND
|
||||
UPLOAD, DOWNLOAD
|
||||
}
|
||||
|
|
|
|||
5
src/main/java/de/avatic/lcc/dto/bulk/BulkState.java
Normal file
5
src/main/java/de/avatic/lcc/dto/bulk/BulkState.java
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package de.avatic.lcc.dto.bulk;
|
||||
|
||||
public enum BulkState {
|
||||
QUEUED, PROCESSING, COMPLETED, FAILED
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
package de.avatic.lcc.dto.bulk;
|
||||
|
||||
public class BulkStatus {
|
||||
}
|
||||
12
src/main/java/de/avatic/lcc/dto/bulk/BulkStatusDTO.java
Normal file
12
src/main/java/de/avatic/lcc/dto/bulk/BulkStatusDTO.java
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package de.avatic.lcc.dto.bulk;
|
||||
|
||||
public class BulkStatusDTO {
|
||||
|
||||
private BulkFileType operation;
|
||||
|
||||
private int processingId;
|
||||
|
||||
private BulkState state;
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ public class ErrorDTO {
|
|||
private String title;
|
||||
private String message;
|
||||
private List<StackTraceElement> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
112
src/main/java/de/avatic/lcc/dto/error/ErrorLogDTO.java
Normal file
112
src/main/java/de/avatic/lcc/dto/error/ErrorLogDTO.java
Normal file
|
|
@ -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<ErrorLogTraceItemDto> 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<ErrorLogTraceItemDto> getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
public void setTrace(List<ErrorLogTraceItemDto> trace) {
|
||||
this.trace = trace;
|
||||
}
|
||||
|
||||
@JsonProperty("timestamp")
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
29
src/main/java/de/avatic/lcc/model/bulk/BulkOperation.java
Normal file
29
src/main/java/de/avatic/lcc/model/bulk/BulkOperation.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package de.avatic.lcc.model.bulk;
|
||||
|
||||
public class BulkOperation<T> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package de.avatic.lcc.model.bulk;
|
||||
|
||||
public enum BulkOperationType {
|
||||
UPDATE, DELETE
|
||||
}
|
||||
71
src/main/java/de/avatic/lcc/model/bulk/BulkProcess.java
Normal file
71
src/main/java/de/avatic/lcc/model/bulk/BulkProcess.java
Normal file
|
|
@ -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<InputStreamSupplier> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package de.avatic.lcc.model.bulk;
|
||||
|
||||
public enum BulkProcessState {
|
||||
QUEUED, PROCESSING, COMPLETED, FAILED
|
||||
}
|
||||
|
|
@ -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]"),
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package de.avatic.lcc.model.bulk;
|
||||
package de.avatic.lcc.model.bulk.header;
|
||||
|
||||
public interface HeaderProvider {
|
||||
String getHeader();
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package de.avatic.lcc.model.bulk;
|
||||
package de.avatic.lcc.model.bulk.header;
|
||||
|
||||
public enum HiddenCountryHeader implements HeaderProvider {
|
||||
ISO_CODE("Iso Code"), NAME("Name");
|
||||
|
|
@ -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");
|
||||
|
|
@ -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");
|
||||
|
|
@ -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)"),
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
252
src/main/java/de/avatic/lcc/model/error/SysError.java
Normal file
252
src/main/java/de/avatic/lcc/model/error/SysError.java
Normal file
|
|
@ -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<SysErrorTraceItem> 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<SysErrorTraceItem> 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<SysErrorTraceItem> trace) {
|
||||
this.trace = trace;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
}
|
||||
145
src/main/java/de/avatic/lcc/model/error/SysErrorTraceItem.java
Normal file
145
src/main/java/de/avatic/lcc/model/error/SysErrorTraceItem.java
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package de.avatic.lcc.model.error;
|
||||
|
||||
public enum SysErrorType {
|
||||
BACKEND, FRONTEND, BULK, CALCULATION
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package de.avatic.lcc.repositories.bulk;
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class BulkOperationRepository {
|
||||
}
|
||||
|
|
@ -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<SysError> 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<SysErrorTraceItem> 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<SysError> listErrors(Optional<String> 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<SysError> 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<SysError> errors) {
|
||||
// Get all error IDs
|
||||
List<Integer> 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<SysErrorTraceItem> 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<Integer, List<SysErrorTraceItem>> traceItemsByErrorId = allTraceItems.stream()
|
||||
.collect(Collectors.groupingBy(SysErrorTraceItem::getErrorId));
|
||||
|
||||
// Assign trace items to their respective errors
|
||||
errors.forEach(error -> {
|
||||
List<SysErrorTraceItem> traceItems = traceItemsByErrorId.getOrDefault(error.getId(), new ArrayList<>());
|
||||
error.setTrace(traceItems);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<BulkStatusDTO> getStatus() {
|
||||
return bulkStatusService.getStatus();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<BulkProcess> processes;
|
||||
|
||||
|
||||
public Integer queueUpload(MultipartFile bulkRequest, BulkProcessingType processingType) {
|
||||
|
||||
|
||||
return processCount++;
|
||||
}
|
||||
|
||||
|
||||
public List<BulkStatusDTO> getStatus() {
|
||||
return null; //TODO implement me
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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.*;
|
||||
|
|
|
|||
|
|
@ -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 <T extends Enum<T>> void createEnumConstraint(Sheet sheet, Integer columnIdx, Class<T> values, Function<T, String> 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 <T extends Enum<T>> void validateEnumConstraint(Row row, int columnIdx, Class<T> 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);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <H extends Enum<H> & HeaderProvider> boolean validateHeader(Sheet sheet, Class<H> headers) {
|
||||
public <H extends Enum<H> & HeaderProvider> void validateHeader(Sheet sheet, Class<H> 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 <H extends Enum<H> & HeaderProvider> void generateHeader(Sheet worksheet, Class<H> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<FrontendErrorDTO> dto) {
|
||||
sysErrorRepository.insert(dto.stream().map(sysErrorMapper::toSysErrorEntity).toList());
|
||||
}
|
||||
|
||||
public SearchQueryResult<ErrorLogDTO> listErrors(Optional<String> filter, @Min(1) int page, @Min(1) int limit) {
|
||||
return SearchQueryResult.map(sysErrorRepository.listErrors(filter, new SearchQueryPagination(page, limit)), sysErrorMapper::toSysErrorDto);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ContainerRate> extractSheet(Sheet sheet) {
|
||||
if(!headerGenerator.validateHeader(sheet, ContainerRateHeader.class)) return null;
|
||||
headerGenerator.validateHeader(sheet, ContainerRateHeader.class);
|
||||
|
||||
var rates = new ArrayList<ContainerRate>();
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<Material> extractSheet(Sheet sheet) {
|
||||
if(!headerGenerator.validateHeader(sheet, MaterialHeader.class)) return null;
|
||||
public List<BulkOperation<Material>> extractSheet(Sheet sheet) {
|
||||
headerGenerator.validateHeader(sheet, MaterialHeader.class);
|
||||
|
||||
var materials = new ArrayList<Material>();
|
||||
sheet.forEach(row -> materials.add(mapToEntity(row)));
|
||||
var materials = new ArrayList<BulkOperation<Material>>();
|
||||
sheet.forEach(row -> {
|
||||
if(row.getRowNum() == 0) return;
|
||||
materials.add(mapToEntity(row));
|
||||
});
|
||||
return materials;
|
||||
|
||||
}
|
||||
|
||||
private Material mapToEntity(Row row) {
|
||||
private BulkOperation<Material> 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) {
|
||||
|
|
|
|||
|
|
@ -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<MatrixRate> extractSheet(Sheet sheet) {
|
||||
if(!headerGenerator.validateHeader(sheet, MatrixRateHeader.class)) return null;
|
||||
headerGenerator.validateHeader(sheet, MatrixRateHeader.class);
|
||||
|
||||
List<MatrixRate> 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()));
|
||||
|
|
|
|||
|
|
@ -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,14 +37,14 @@ 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)));
|
||||
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());
|
||||
|
|
@ -86,33 +86,62 @@ 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<ExcelNode> extractSheet(Sheet sheet) {
|
||||
if(!headerGenerator.validateHeader(sheet, NodeHeader.class)) return null;
|
||||
public List<BulkOperation<ExcelNode>> extractSheet(Sheet sheet) {
|
||||
headerGenerator.validateHeader(sheet, NodeHeader.class);
|
||||
|
||||
var nodes = new ArrayList<ExcelNode>();
|
||||
sheet.forEach(row -> nodes.add(mapToEntity(row)));
|
||||
var nodes = new ArrayList<BulkOperation<ExcelNode>>();
|
||||
sheet.forEach(row -> {
|
||||
if (row.getRowNum() == 0) return;
|
||||
nodes.add(mapToEntity(row));
|
||||
});
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private ExcelNode mapToEntity(Row row) {
|
||||
private BulkOperation<ExcelNode> 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<String> mapOutboundCountriesFromCell(String outboundCountryIds) {
|
||||
|
|
|
|||
|
|
@ -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<PackagingDimension> shu = packagingDimensionRepository.getById(packaging.getShuId());
|
||||
Optional<PackagingDimension> 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<ExcelPackaging> extractSheet(Sheet sheet) {
|
||||
if (!headerGenerator.validateHeader(sheet, PackagingHeader.class)) return null;
|
||||
headerGenerator.validateHeader(sheet, PackagingHeader.class);
|
||||
|
||||
var packaging = new ArrayList<ExcelPackaging>();
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<SysErrorTraceItem> toSysErrorTraceItem(String traceCombined) {
|
||||
List<SysErrorTraceItem> 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<SysErrorTraceItem> toSysErrorTraceItem(List<StackTraceElement> trace) {
|
||||
List<SysErrorTraceItem> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue