Centralized notification/error management in frontend, more infos in error log, bugfixing in tariff rate services.
This commit is contained in:
parent
35b0b8584d
commit
5adadb671e
28 changed files with 872 additions and 560 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<error-notification></error-notification>
|
<the-notification-system />
|
||||||
<the-header></the-header>
|
<the-header></the-header>
|
||||||
<router-view v-slot="slotProps">
|
<router-view v-slot="slotProps">
|
||||||
<transition name="route" mode="out-in">
|
<transition name="route" mode="out-in">
|
||||||
|
|
@ -12,10 +12,10 @@
|
||||||
|
|
||||||
|
|
||||||
import TheHeader from "@/components/layout/TheHeader.vue";
|
import TheHeader from "@/components/layout/TheHeader.vue";
|
||||||
import ErrorNotification from "@/components/UI/ErrorNotifcation.vue";
|
import TheNotificationSystem from "@/components/UI/TheNotificationSystem.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {ErrorNotification, TheHeader},
|
components: {TheNotificationSystem, TheHeader},
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import logger from "@/logger.js";
|
import logger from "@/logger.js";
|
||||||
import {useErrorStore} from "@/store/error.js";
|
import {useErrorStore} from "@/store/error.js";
|
||||||
|
import {config} from "@/config";
|
||||||
|
|
||||||
const getCsrfToken = () => {
|
const getCsrfToken = () => {
|
||||||
const value = `; ${document.cookie}`;
|
const value = `; ${document.cookie}`;
|
||||||
|
|
@ -13,44 +14,32 @@ const getCsrfToken = () => {
|
||||||
let sessionRefreshInterval = null;
|
let sessionRefreshInterval = null;
|
||||||
let lastActivity = Date.now();
|
let lastActivity = Date.now();
|
||||||
|
|
||||||
|
|
||||||
const refreshSession = async () => {
|
const refreshSession = async () => {
|
||||||
try {
|
try {
|
||||||
// Ein simpler authenticated GET request hält die Session am Leben
|
await performRequest(null, 'GET', `${config.backendUrl}/error/`, null);
|
||||||
await fetch('/api/session/keepalive', {
|
logger.log('Session refreshed');
|
||||||
method: 'GET',
|
|
||||||
credentials: 'include'
|
|
||||||
});
|
|
||||||
console.log('Session refreshed');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Session refresh failed', e);
|
logger.error('Session refresh failed', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const trackActivity = () => {
|
const trackActivity = () => {
|
||||||
lastActivity = Date.now();
|
lastActivity = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const startSessionRefresh = () => {
|
const startSessionRefresh = () => {
|
||||||
|
|
||||||
['mousedown', 'keypress', 'scroll', 'touchstart'].forEach(event => {
|
|
||||||
document.addEventListener(event, trackActivity);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
if (sessionRefreshInterval) {
|
if (sessionRefreshInterval) {
|
||||||
clearInterval(sessionRefreshInterval);
|
clearInterval(sessionRefreshInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionRefreshInterval = setInterval(() => {
|
sessionRefreshInterval = setInterval(async () => {
|
||||||
const timeSinceActivity = Date.now() - lastActivity;
|
const timeSinceActivity = Date.now() - lastActivity;
|
||||||
|
|
||||||
if (timeSinceActivity < (12 * 60 * 60 * 1000)) {
|
if (timeSinceActivity < (10 * 60 * 1000)) {
|
||||||
refreshSession();
|
await refreshSession();
|
||||||
}
|
}
|
||||||
}, 10 * 60 * 1000);
|
}, 2 * 60 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopSessionRefresh = () => {
|
const stopSessionRefresh = () => {
|
||||||
|
|
@ -93,12 +82,17 @@ const performDownload = async (url, toFile, expectResponse = true, expectedExcep
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const request = {url: url, params: params, expectResponse: expectResponse, expectedException: expectedException, type: 'blob'};
|
const request = {
|
||||||
|
url: url,
|
||||||
|
params: params,
|
||||||
|
expectResponse: expectResponse,
|
||||||
|
expectedException: expectedException,
|
||||||
|
type: 'blob'
|
||||||
|
};
|
||||||
logger.info("Request:", request);
|
logger.info("Request:", request);
|
||||||
|
|
||||||
const resp = await executeRequest(null, request);
|
const resp = await executeRequest(null, request);
|
||||||
|
|
||||||
|
|
||||||
const downloadUrl = window.URL.createObjectURL(resp.data);
|
const downloadUrl = window.URL.createObjectURL(resp.data);
|
||||||
|
|
||||||
// Create temporary link element and trigger download
|
// Create temporary link element and trigger download
|
||||||
|
|
@ -147,17 +141,26 @@ function handleErrorResponse(data, requestingStore, request) {
|
||||||
const error = new Error('Internal backend error');
|
const error = new Error('Internal backend error');
|
||||||
error.errorObj = errorObj;
|
error.errorObj = errorObj;
|
||||||
|
|
||||||
|
const errorStore = useErrorStore();
|
||||||
|
|
||||||
if (request.expectedException === null || (Array.isArray(request.expectResponse) && !request.expectedException.includes(data.error.title)) || (typeof request.expectedException === 'string' && data.error.title !== request.expectedException)) {
|
if (request.expectedException === null || (Array.isArray(request.expectResponse) && !request.expectedException.includes(data.error.title)) || (typeof request.expectedException === 'string' && data.error.title !== request.expectedException)) {
|
||||||
logger.error(errorObj, request.expectedException);
|
logger.error(errorObj, request.expectedException);
|
||||||
const errorStore = useErrorStore();
|
|
||||||
void errorStore.addError(errorObj, {store: requestingStore, request: request});
|
void errorStore.addError(errorObj, {store: requestingStore, request: request});
|
||||||
|
} else {
|
||||||
|
errorStore.addNotification({
|
||||||
|
icon: 'warning',
|
||||||
|
message: data.error.message,
|
||||||
|
title: data.error.title,
|
||||||
|
variant: 'exception',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const executeRequest = async (requestingStore, request) => {
|
const executeRequest = async (requestingStore, request) => {
|
||||||
|
trackActivity();
|
||||||
|
|
||||||
const response = await fetch(request.url, request.params
|
const response = await fetch(request.url, request.params
|
||||||
).catch(e => {
|
).catch(e => {
|
||||||
const error = {
|
const error = {
|
||||||
|
|
@ -180,7 +183,7 @@ const executeRequest = async (requestingStore, request) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = null;
|
let data = null;
|
||||||
if (request.expectResponse) {
|
if (request.expectResponse) { // backend should return something
|
||||||
try {
|
try {
|
||||||
if (request.type === 'blob' && response.ok) {
|
if (request.type === 'blob' && response.ok) {
|
||||||
data = await response.blob();
|
data = await response.blob();
|
||||||
|
|
@ -207,7 +210,7 @@ const executeRequest = async (requestingStore, request) => {
|
||||||
} else {
|
} else {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
||||||
const data = await response.json().catch(e => {
|
const data = await response.json().catch(_ => {
|
||||||
const error = {
|
const error = {
|
||||||
code: "Return code error " + response.status,
|
code: "Return code error " + response.status,
|
||||||
message: "Server returned wrong response code",
|
message: "Server returned wrong response code",
|
||||||
|
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
<template>
|
|
||||||
<teleport to="body">
|
|
||||||
<transition
|
|
||||||
name="error-container"
|
|
||||||
tag="div"
|
|
||||||
class="error-notification-container">
|
|
||||||
<div class="error-notification" v-if="error">
|
|
||||||
<div>
|
|
||||||
<ph-warning size="24"></ph-warning>
|
|
||||||
</div>
|
|
||||||
<div class="error-message">
|
|
||||||
<div class="error-message-title">
|
|
||||||
{{ title }}
|
|
||||||
</div>
|
|
||||||
<div class="error-message-content">
|
|
||||||
{{ message }}
|
|
||||||
</div>
|
|
||||||
<div class="error-view-trace" v-if="trace" @click="activateTrace">
|
|
||||||
View trace
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="icon-error-notification">
|
|
||||||
<ph-x size="24" @click="close"></ph-x>
|
|
||||||
</div>
|
|
||||||
<modal :z-index="9001" :state="showTrace"><trace-view :error="error" @close="deactivateTrace"></trace-view></modal>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</teleport>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import {useErrorStore} from "@/store/error.js";
|
|
||||||
import {mapStores} from "pinia";
|
|
||||||
import TraceView from "@/components/layout/TraceView.vue";
|
|
||||||
import Modal from "@/components/UI/Modal.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "ErrorNotification",
|
|
||||||
components: {Modal, TraceView},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showTrace: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useErrorStore),
|
|
||||||
title() {
|
|
||||||
return this.error?.title || 'Error';
|
|
||||||
},
|
|
||||||
message() {
|
|
||||||
return this.error?.message || 'An unexpected error occurred';
|
|
||||||
},
|
|
||||||
trace() {
|
|
||||||
return !((this.error?.trace ?? null) === null) || !((this.error?.traceCombined ?? null) === null);
|
|
||||||
},
|
|
||||||
error() {
|
|
||||||
return this.errorStore.lastError;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
close() {
|
|
||||||
this.errorStore.clearErrors();
|
|
||||||
},
|
|
||||||
activateTrace() {
|
|
||||||
this.showTrace = true;
|
|
||||||
},
|
|
||||||
deactivateTrace() {
|
|
||||||
this.showTrace = false;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message-title {
|
|
||||||
font-size: 1.6rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message-content {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-view-trace {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-notification-container {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 2rem;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, 0);
|
|
||||||
z-index: 9000;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-notification {
|
|
||||||
min-width: 30rem;
|
|
||||||
max-width: 100rem;
|
|
||||||
padding: 1.6rem;
|
|
||||||
border-radius: 0.8rem;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1.6rem;
|
|
||||||
|
|
||||||
background-color: #BC2B72;
|
|
||||||
color: #ffffff;
|
|
||||||
|
|
||||||
font-size: 1.4rem;
|
|
||||||
cursor: pointer;
|
|
||||||
pointer-events: auto;
|
|
||||||
|
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-error-notification {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.1s ease-in-out;
|
|
||||||
background-color: #BC2B72;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-error-notification:hover, .icon-error-notification:active {
|
|
||||||
cursor: pointer;
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Transition animations */
|
|
||||||
.error-container-enter-active {
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-container-leave-active {
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-container-enter-from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translate(-50%, 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-container-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translate(-50%, 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-container-move {
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
@ -40,6 +40,10 @@ export default {
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
|
.flag-container img {
|
||||||
|
filter: saturate(0.9) brightness(0.95) hue-rotate(-10deg);
|
||||||
|
}
|
||||||
|
|
||||||
.flag-container {
|
.flag-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
173
src/frontend/src/components/UI/TheNotificationSystem.vue
Normal file
173
src/frontend/src/components/UI/TheNotificationSystem.vue
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
<template>
|
||||||
|
<teleport to="body">
|
||||||
|
<toast ref="toast"></toast>
|
||||||
|
<!-- <transition-->
|
||||||
|
<!-- name="error-container"-->
|
||||||
|
<!-- tag="div"-->
|
||||||
|
<!-- class="error-notification-container">-->
|
||||||
|
<!-- <div class="error-notification" v-if="error">-->
|
||||||
|
<!-- <div>-->
|
||||||
|
<!-- <ph-warning size="24"></ph-warning>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="error-message">-->
|
||||||
|
<!-- <div class="error-message-title">-->
|
||||||
|
<!-- {{ title }}-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="error-message-content">-->
|
||||||
|
<!-- {{ message }}-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="error-view-trace" v-if="trace" @click="activateTrace">-->
|
||||||
|
<!-- View trace-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="icon-error-notification">-->
|
||||||
|
<!-- <ph-x size="24" @click="close"></ph-x>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <modal :z-index="9001" :state="showTrace"><trace-view :error="error" @close="deactivateTrace"></trace-view></modal>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </transition>-->
|
||||||
|
</teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import {useErrorStore} from "@/store/error.js";
|
||||||
|
import {mapStores} from "pinia";
|
||||||
|
import Toast from "@/components/UI/Toast.vue";
|
||||||
|
import logger from "@/logger.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "TheNotificationSystem",
|
||||||
|
components: {Toast},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useErrorStore),
|
||||||
|
notifications() {
|
||||||
|
return this.errorStore.notifications;
|
||||||
|
},
|
||||||
|
hasNotifications() {
|
||||||
|
return this.errorStore.notifications?.length !== 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
hasNotifications(newValue) {
|
||||||
|
if (newValue)
|
||||||
|
this.fireToasts();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fireToasts() {
|
||||||
|
while (this.hasNotifications) {
|
||||||
|
const msg = this.errorStore.popNotification();
|
||||||
|
|
||||||
|
logger.log("fire msg", msg);
|
||||||
|
|
||||||
|
if (msg)
|
||||||
|
this.$refs.toast.addToast({
|
||||||
|
icon: msg.icon ?? null,
|
||||||
|
message: msg.message ?? null,
|
||||||
|
title: msg.title ?? null,
|
||||||
|
variant: msg.variant ?? 'exception',
|
||||||
|
duration: msg.duration ?? 8000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message-title {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message-content {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-view-trace {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-notification-container {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
z-index: 9000;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-notification {
|
||||||
|
min-width: 30rem;
|
||||||
|
max-width: 100rem;
|
||||||
|
padding: 1.6rem;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.6rem;
|
||||||
|
|
||||||
|
background-color: #BC2B72;
|
||||||
|
color: #ffffff;
|
||||||
|
|
||||||
|
font-size: 1.4rem;
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-error-notification {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
background-color: #BC2B72;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-error-notification:hover, .icon-error-notification:active {
|
||||||
|
cursor: pointer;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Transition animations */
|
||||||
|
.error-container-enter-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-50%, 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-50%, 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container-move {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -55,7 +55,7 @@ export default {
|
||||||
id: this.nextId++,
|
id: this.nextId++,
|
||||||
message: options.message || 'Notification',
|
message: options.message || 'Notification',
|
||||||
title: options.title || null,
|
title: options.title || null,
|
||||||
variant: options.variant || 'primary',
|
variant: this.mapVariant(options.variant || 'primary' ),
|
||||||
duration: options.duration !== undefined ? options.duration : 5000,
|
duration: options.duration !== undefined ? options.duration : 5000,
|
||||||
icon: options.icon ? `Ph${options.icon.charAt(0).toUpperCase() + options.icon.slice(1)}` : null,
|
icon: options.icon ? `Ph${options.icon.charAt(0).toUpperCase() + options.icon.slice(1)}` : null,
|
||||||
};
|
};
|
||||||
|
|
@ -72,6 +72,21 @@ export default {
|
||||||
return toast.id
|
return toast.id
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mapVariant(variant) {
|
||||||
|
switch(variant) {
|
||||||
|
case 'success':
|
||||||
|
return 'primary'
|
||||||
|
case 'warning':
|
||||||
|
return 'secondary'
|
||||||
|
case 'info':
|
||||||
|
return 'primary'
|
||||||
|
case 'error':
|
||||||
|
return 'exception'
|
||||||
|
default:
|
||||||
|
return variant;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a toast by ID
|
* Remove a toast by ID
|
||||||
* @param {number} id - Toast ID
|
* @param {number} id - Toast ID
|
||||||
|
|
@ -189,8 +204,8 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast--secondary {
|
.toast--secondary {
|
||||||
background-color: #002F54;
|
color: #002F54;
|
||||||
color: #ffffff;
|
background-color: #c3cfdf;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast--exception{
|
.toast--exception{
|
||||||
|
|
|
||||||
|
|
@ -148,8 +148,8 @@ export default {
|
||||||
rateTypeValue: "container",
|
rateTypeValue: "container",
|
||||||
selectedTypeColumns: [],
|
selectedTypeColumns: [],
|
||||||
matrixColumns: [
|
matrixColumns: [
|
||||||
{key: 'source.iso_code', label: 'From Country (ISO code)'},
|
{key: 'source', label: 'From Country (ISO code)', formatter: (country) => {return `${country.iso_code} (${country.name})`}},
|
||||||
{key: 'destination.iso_code', label: 'To Country (ISO code)'},
|
{key: 'destination', label: 'To Country (ISO code)', formatter: (country) => {return `${country.iso_code} (${country.name})`}},
|
||||||
{key: 'rate', align: 'right', label: 'Rate [EUR/km]'},
|
{key: 'rate', align: 'right', label: 'Rate [EUR/km]'},
|
||||||
],
|
],
|
||||||
containerColumns: [
|
containerColumns: [
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,12 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="tariffUnlocked" class="tariff-rate-info-header">
|
<div v-if="tariffUnlocked" class="tariff-rate-info-container">
|
||||||
<ph-warning size="24" />
|
<ph-warning size="24" />
|
||||||
<div>
|
<div>
|
||||||
<div>Tariff rate lookup failed</div>
|
<div>Automatic tariff rate determination was ambiguous</div>
|
||||||
<div class="tariff-rate-info-headerbox">
|
<div class="tariff-rate-info-text">
|
||||||
Please contact a customs expert to obtain HS code and tariff rate.
|
Please contact a customs expert to obtain correct HS code and tariff rate.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -179,12 +179,14 @@ export default {
|
||||||
const multiple = 10 ** digits;
|
const multiple = 10 ** digits;
|
||||||
return Math.round(number * multiple) / multiple;
|
return Math.round(number * multiple) / multiple;
|
||||||
},
|
},
|
||||||
|
|
||||||
hsCodeChanged(event) {
|
hsCodeChanged(event) {
|
||||||
const sanitized = event.target.value
|
let sanitized = event.target.value
|
||||||
.replace(/\D/g, '')
|
.replace(/\D/g, '')
|
||||||
.substring(0, 10);
|
.substring(0, 10);
|
||||||
|
|
||||||
|
if(sanitized.length !== 10)
|
||||||
|
sanitized = null;
|
||||||
|
|
||||||
this.$emit("update:hsCode", sanitized);
|
this.$emit("update:hsCode", sanitized);
|
||||||
event.target.value = sanitized;
|
event.target.value = sanitized;
|
||||||
}
|
}
|
||||||
|
|
@ -305,13 +307,13 @@ export default {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tariff-rate-info-headerbox {
|
.tariff-rate-info-text {
|
||||||
color: #002F54;
|
color: #002F54;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tariff-rate-info-header {
|
.tariff-rate-info-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.8rem;
|
gap: 0.8rem;
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import ErrorModalTraceView from "@/components/layout/error/ErrorModalTraceView.v
|
||||||
import IconButton from "@/components/UI/IconButton.vue";
|
import IconButton from "@/components/UI/IconButton.vue";
|
||||||
import ErrorModalOverview from "@/components/layout/error/ErrorModalOverview.vue";
|
import ErrorModalOverview from "@/components/layout/error/ErrorModalOverview.vue";
|
||||||
import ErrorModalPiniaStore from "@/components/layout/error/ErrorModalPiniaStore.vue";
|
import ErrorModalPiniaStore from "@/components/layout/error/ErrorModalPiniaStore.vue";
|
||||||
|
import ErrorModalRequest from "@/components/layout/error/ErrorModalRequest.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ErrorModal",
|
name: "ErrorModal",
|
||||||
|
|
@ -37,24 +38,38 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
tabsError() {
|
tabsError() {
|
||||||
return [
|
const tabs = [];
|
||||||
{
|
|
||||||
title: 'Overview',
|
tabs.push({
|
||||||
component: markRaw(ErrorModalOverview),
|
title: 'Overview',
|
||||||
|
component: markRaw(ErrorModalOverview),
|
||||||
|
props: {isSelected: false, error: this.error},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.error.request)
|
||||||
|
tabs.push({
|
||||||
|
title: 'API request',
|
||||||
|
component: markRaw(ErrorModalRequest),
|
||||||
props: {isSelected: false, error: this.error},
|
props: {isSelected: false, error: this.error},
|
||||||
},
|
});
|
||||||
{
|
|
||||||
|
if (this.error.trace)
|
||||||
|
tabs.push({
|
||||||
title: 'Stack trace',
|
title: 'Stack trace',
|
||||||
component: markRaw(ErrorModalTraceView),
|
component: markRaw(ErrorModalTraceView),
|
||||||
props: {isSelected: false, error: this.error},
|
props: {isSelected: false, error: this.error},
|
||||||
},
|
});
|
||||||
{
|
|
||||||
|
|
||||||
|
if (this.error.pinia)
|
||||||
|
tabs.push({
|
||||||
title: 'Frontend storage',
|
title: 'Frontend storage',
|
||||||
component: markRaw(ErrorModalPiniaStore),
|
component: markRaw(ErrorModalPiniaStore),
|
||||||
props: {isSelected: false, error: this.error},
|
props: {isSelected: false, error: this.error},
|
||||||
},
|
});
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
return tabs;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<basic-button icon="clipboard" @click="copyToClipboard">Copy error to clipboard</basic-button>
|
<basic-button icon="clipboard" @click="copyToClipboard">Copy error to clipboard</basic-button>
|
||||||
<Toast ref="toast"/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="error-section">
|
<div class="error-section">
|
||||||
|
|
@ -87,12 +86,13 @@
|
||||||
import BasicBadge from "@/components/UI/BasicBadge.vue";
|
import BasicBadge from "@/components/UI/BasicBadge.vue";
|
||||||
import {buildDate} from "@/common.js";
|
import {buildDate} from "@/common.js";
|
||||||
import BasicButton from "@/components/UI/BasicButton.vue";
|
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||||
import Toast from "@/components/UI/Toast.vue";
|
import logger from "@/logger.js";
|
||||||
import {error} from "loglevel";
|
import {mapStores} from "pinia";
|
||||||
|
import {useErrorStore} from "@/store/error.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ErrorModalOverview",
|
name: "ErrorModalOverview",
|
||||||
components: {Toast, BasicButton, BasicBadge},
|
components: {BasicButton, BasicBadge},
|
||||||
props: {
|
props: {
|
||||||
isSelected: {
|
isSelected: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
@ -108,20 +108,31 @@ export default {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(JSON.stringify(this.error));
|
await navigator.clipboard.writeText(JSON.stringify(this.error));
|
||||||
|
|
||||||
this.$refs.toast.addToast({
|
this.errorStore.addNotification({
|
||||||
icon: 'Clipboard',
|
icon: 'Clipboard',
|
||||||
message: "The error has been copied to clipboard",
|
message: "The error has been copied to clipboard",
|
||||||
title: "Successful copied to clipboard",
|
title: "Successful copied to clipboard",
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
duration: 4000
|
duration: 4000
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Fehler beim Kopieren:', err);
|
logger.error('Fehler beim Kopieren:', err);
|
||||||
|
|
||||||
|
this.errorStore.addNotification({
|
||||||
|
icon: 'Clipboard',
|
||||||
|
message: "Cannot copy to clipboard",
|
||||||
|
title: "Not copied to clipboard",
|
||||||
|
variant: 'error',
|
||||||
|
duration: 4000
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapStores(useErrorStore),
|
||||||
badgeIcon() {
|
badgeIcon() {
|
||||||
if (this.error.type === "FRONTEND") {
|
if (this.error.type === "FRONTEND") {
|
||||||
return "desktop";
|
return "desktop";
|
||||||
|
|
@ -214,6 +225,7 @@ code {
|
||||||
.error-label {
|
.error-label {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
|
align-self: start;
|
||||||
color: #6B869C;
|
color: #6B869C;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.05rem;
|
letter-spacing: 0.05rem;
|
||||||
|
|
@ -225,21 +237,8 @@ code {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-title {
|
.error-section:last-child {
|
||||||
font-weight: 500;
|
border-bottom: none;
|
||||||
color: #d32f2f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-code {
|
|
||||||
font-family: 'Roboto Mono', monospace;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
padding: 0.8rem 1.2rem;
|
|
||||||
border-radius: 0.4rem;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-code code {
|
.error-code code {
|
||||||
|
|
@ -247,46 +246,4 @@ code {
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-location {
|
|
||||||
font-family: 'Roboto Mono', monospace;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
padding: 0.8rem 1.2rem;
|
|
||||||
border-radius: 0.4rem;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-location code {
|
|
||||||
color: #1976d2;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-badge {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.6rem 1.2rem;
|
|
||||||
border-radius: 0.4rem;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1.3rem;
|
|
||||||
width: fit-content;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-backend {
|
|
||||||
background-color: #ffebee;
|
|
||||||
color: #c62828;
|
|
||||||
border: 1px solid #ef5350;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-frontend {
|
|
||||||
background-color: #fff3e0;
|
|
||||||
color: #e65100;
|
|
||||||
border: 1px solid #ff9800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-unknown {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
color: #757575;
|
|
||||||
border: 1px solid #bdbdbd;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
236
src/frontend/src/components/layout/error/ErrorModalRequest.vue
Normal file
236
src/frontend/src/components/layout/error/ErrorModalRequest.vue
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="showCard" class="error-overview-container">
|
||||||
|
<div class="error-overview">
|
||||||
|
|
||||||
|
<div class="error-section">
|
||||||
|
<div class="error-label">Method</div>
|
||||||
|
<div class="error-value">
|
||||||
|
<basic-badge :icon="requestIcon" :variant="badgeVariant">{{ requestMethod || 'UNKNOWN' }}</basic-badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="error-section">
|
||||||
|
<div class="error-label">Url</div>
|
||||||
|
<div class="error-value">
|
||||||
|
{{ url }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="error-section">
|
||||||
|
<div class="error-label">Response expected</div>
|
||||||
|
<div class="error-value">
|
||||||
|
{{ expectResponse }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="error-section" v-if="expectedException">
|
||||||
|
<div class="error-label">Expected exceptions</div>
|
||||||
|
<div class="error-value">
|
||||||
|
{{ expectedException }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="error-section">
|
||||||
|
<div class="error-label">Header</div>
|
||||||
|
<div class="error-value">
|
||||||
|
<!-- <div class="json-display">-->
|
||||||
|
<!-- {{ formattedHeader }}-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<div class="error-sub-section-container">
|
||||||
|
<div class="error-sub-section" v-for="(value, key) in params.headers" :key="key">
|
||||||
|
<div class="error-sub-label"> {{ key }}</div>
|
||||||
|
<div class="error-sub-value"> {{ value }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="error-section">
|
||||||
|
<div class="error-label">Body</div>
|
||||||
|
<div class="error-value">
|
||||||
|
<div class="json-display">
|
||||||
|
{{ formattedBody }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="no-data">
|
||||||
|
<span class="space-around">No request data available</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||||
|
import BasicBadge from "@/components/UI/BasicBadge.vue";
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ErrorModalRequest",
|
||||||
|
components: {BasicBadge, BasicButton},
|
||||||
|
props: {
|
||||||
|
error: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
expectedException() {
|
||||||
|
return this.request.expectedException
|
||||||
|
},
|
||||||
|
expectResponse() {
|
||||||
|
return this.request.expectResponse
|
||||||
|
},
|
||||||
|
showCard() {
|
||||||
|
return (this.error != null && this.error.request != null);
|
||||||
|
},
|
||||||
|
request() {
|
||||||
|
return JSON.parse(this.error.request);
|
||||||
|
},
|
||||||
|
url() {
|
||||||
|
return this.request.url;
|
||||||
|
},
|
||||||
|
params() {
|
||||||
|
return this.request.params;
|
||||||
|
},
|
||||||
|
requestMethod() {
|
||||||
|
return this.params.method;
|
||||||
|
},
|
||||||
|
requestIcon() {
|
||||||
|
switch (this.requestMethod) {
|
||||||
|
case "GET":
|
||||||
|
return "download";
|
||||||
|
case "POST":
|
||||||
|
case "PUT":
|
||||||
|
return "upload";
|
||||||
|
case "DELETE":
|
||||||
|
return "trash";
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
header() {
|
||||||
|
return this.params.headers;
|
||||||
|
},
|
||||||
|
body() {
|
||||||
|
return this.params.body;
|
||||||
|
},
|
||||||
|
badgeVariant() {
|
||||||
|
switch (this.requestMethod) {
|
||||||
|
case "GET":
|
||||||
|
return "primary";
|
||||||
|
case "POST":
|
||||||
|
case "PUT":
|
||||||
|
return "secondary";
|
||||||
|
case "DELETE":
|
||||||
|
default:
|
||||||
|
return "grey";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formattedBody() {
|
||||||
|
if (!this.body) return '[]';
|
||||||
|
return JSON.stringify(this.body, null, 2);
|
||||||
|
},
|
||||||
|
formattedHeader() {
|
||||||
|
if (!this.header) return '';
|
||||||
|
return JSON.stringify(this.header, null, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.error-overview-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-overview {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 1.6rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.6rem;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 20rem 1fr;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.8rem;
|
||||||
|
gap: 0.6rem;
|
||||||
|
border-bottom: .1rem solid #E3EDFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-sub-section-container {
|
||||||
|
|
||||||
|
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-section:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-label {
|
||||||
|
align-self: start;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #6B869C;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-sub-label {
|
||||||
|
color: #6B869C;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-sub-value {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
padding-bottom: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-value {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-around {
|
||||||
|
margin: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-display {
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Roboto mono', monospace;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
flex: 1;
|
||||||
|
color: #6b7280;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
height: 100%
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -13,8 +13,9 @@
|
||||||
<div class="text-container"><input class="input-field" v-model="nodeAddress" @input="checkChange"/></div>
|
<div class="text-container"><input class="input-field" v-model="nodeAddress" @input="checkChange"/></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<basic-button icon="SealCheck" @click.prevent="verifyAddress" :disabled="addressVerified" @submit.prevent>Check address</basic-button>
|
<basic-button icon="SealCheck" @click.prevent="verifyAddress" :disabled="addressVerified" @submit.prevent>
|
||||||
<Toast ref="toast"/>
|
Check address
|
||||||
|
</basic-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-field-caption" v-if="addressVerified">Country:</div>
|
<div class="input-field-caption" v-if="addressVerified">Country:</div>
|
||||||
<div class="country-field" v-if="addressVerified">
|
<div class="country-field" v-if="addressVerified">
|
||||||
|
|
@ -30,7 +31,8 @@
|
||||||
custom-filter="grayscale(0.8) sepia(0.5) hue-rotate(180deg) saturate(0.5) brightness(1.0)"></open-street-map-embed>
|
custom-filter="grayscale(0.8) sepia(0.5) hue-rotate(180deg) saturate(0.5) brightness(1.0)"></open-street-map-embed>
|
||||||
</div>
|
</div>
|
||||||
<div class="create-new-node-footer">
|
<div class="create-new-node-footer">
|
||||||
<basic-button @click.prevent="send" variant="primary" :show-icon="false" :disabled="unverified">Create</basic-button>
|
<basic-button @click.prevent="send" variant="primary" :show-icon="false" :disabled="unverified">Create
|
||||||
|
</basic-button>
|
||||||
<basic-button @click.prevent="cancel" variant="secondary" :show-icon="false">Cancel</basic-button>
|
<basic-button @click.prevent="cancel" variant="secondary" :show-icon="false">Cancel</basic-button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -43,12 +45,11 @@ import BasicButton from "@/components/UI/BasicButton.vue";
|
||||||
import {mapStores} from "pinia";
|
import {mapStores} from "pinia";
|
||||||
import {useNodeStore} from "@/store/node.js";
|
import {useNodeStore} from "@/store/node.js";
|
||||||
import OpenStreetMapEmbed from "@/components/UI/OpenStreetMapEmbed.vue";
|
import OpenStreetMapEmbed from "@/components/UI/OpenStreetMapEmbed.vue";
|
||||||
import Toast from "@/components/UI/Toast.vue";
|
|
||||||
import Flag from "@/components/UI/Flag.vue";
|
import Flag from "@/components/UI/Flag.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "CreateNewNode",
|
name: "CreateNewNode",
|
||||||
components: {Flag, Toast, OpenStreetMapEmbed, BasicButton},
|
components: {Flag, OpenStreetMapEmbed, BasicButton},
|
||||||
emits: ['created', 'close'],
|
emits: ['created', 'close'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -114,15 +115,7 @@ export default {
|
||||||
this.addressVerified = false;
|
this.addressVerified = false;
|
||||||
this.verifiedAddress = null;
|
this.verifiedAddress = null;
|
||||||
|
|
||||||
if (error !== null) {
|
if (error === null && node !== null) {
|
||||||
this.$refs.toast.addToast({
|
|
||||||
icon: 'warning',
|
|
||||||
message: error.message,
|
|
||||||
title: "Cannot locate address.",
|
|
||||||
variant: 'exception',
|
|
||||||
duration: 8000
|
|
||||||
})
|
|
||||||
} else if (node) {
|
|
||||||
this.nodeCoordinates = node.location;
|
this.nodeCoordinates = node.location;
|
||||||
this.nodeAddress = node.address;
|
this.nodeAddress = node.address;
|
||||||
|
|
||||||
|
|
@ -132,7 +125,6 @@ export default {
|
||||||
this.addressVerified = true;
|
this.addressVerified = true;
|
||||||
this.verifiedAddress = node.address;
|
this.verifiedAddress = node.address;
|
||||||
this.node = node;
|
this.node = node;
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="start-calculation-container">
|
<div class="start-calculation-container">
|
||||||
<h2 class="page-header">Create Calculation</h2>
|
<h2 class="page-header">Create Calculation</h2>
|
||||||
|
|
||||||
|
|
||||||
<div class="part-numbers-headers">
|
<div class="part-numbers-headers">
|
||||||
<h3 class="sub-header">Part Numbers</h3>
|
<h3 class="sub-header">Part Numbers</h3>
|
||||||
<basic-button icon="CloudArrowUp" :showIcon="true" @click="activateModal('partNumber')">Drop part numbers
|
<basic-button icon="CloudArrowUp" :showIcon="true" @click="activateModal('partNumber')">Drop part numbers
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Toast ref="toast"/>
|
|
||||||
|
|
||||||
<div v-if="premiseSingleEditStore.showLoadingSpinner" class="edit-calculation-spinner-container">
|
<div v-if="premiseSingleEditStore.showLoadingSpinner" class="edit-calculation-spinner-container">
|
||||||
<box class="edit-calculation-spinner">
|
<box class="edit-calculation-spinner">
|
||||||
|
|
@ -112,14 +111,14 @@ import NotificationBar from "@/components/UI/NotificationBar.vue";
|
||||||
import Modal from "@/components/UI/Modal.vue";
|
import Modal from "@/components/UI/Modal.vue";
|
||||||
import TraceView from "@/components/layout/TraceView.vue";
|
import TraceView from "@/components/layout/TraceView.vue";
|
||||||
import IconButton from "@/components/UI/IconButton.vue";
|
import IconButton from "@/components/UI/IconButton.vue";
|
||||||
import Toast from "@/components/UI/Toast.vue";
|
|
||||||
import {UrlSafeBase64} from "@/common.js";
|
import {UrlSafeBase64} from "@/common.js";
|
||||||
import {usePremiseSingleEditStore} from "@/store/premiseSingleEdit.js";
|
import {usePremiseSingleEditStore} from "@/store/premiseSingleEdit.js";
|
||||||
|
import logger from "@/logger.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SingleEdit",
|
name: "SingleEdit",
|
||||||
components: {
|
components: {
|
||||||
Toast,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
TraceView,
|
TraceView,
|
||||||
Modal,
|
Modal,
|
||||||
|
|
@ -157,7 +156,6 @@ export default {
|
||||||
return "Please wait. Routing ..."
|
return "Please wait. Routing ..."
|
||||||
|
|
||||||
return "Please wait. Calculating ...";
|
return "Please wait. Calculating ...";
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -166,28 +164,7 @@ export default {
|
||||||
this.showCalculationModal = true;
|
this.showCalculationModal = true;
|
||||||
const error = await this.premiseSingleEditStore.startCalculation();
|
const error = await this.premiseSingleEditStore.startCalculation();
|
||||||
|
|
||||||
if (error !== null) {
|
if (error === null) {
|
||||||
|
|
||||||
if (error.title === 'Internal Server Error') {
|
|
||||||
this.$refs.toast.addToast({
|
|
||||||
icon: 'warning',
|
|
||||||
message: error.message,
|
|
||||||
title: "Calculation finished with errors",
|
|
||||||
variant: 'exception',
|
|
||||||
duration: 8000
|
|
||||||
});
|
|
||||||
this.close();
|
|
||||||
} else {
|
|
||||||
this.$refs.toast.addToast({
|
|
||||||
icon: 'warning',
|
|
||||||
message: error.message,
|
|
||||||
title: "Cannot start calculation",
|
|
||||||
variant: 'exception',
|
|
||||||
duration: 8000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,25 +181,13 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async save(type) {
|
async save(type) {
|
||||||
let success = false;
|
|
||||||
|
|
||||||
if (type === 'price') {
|
if (type === 'price') {
|
||||||
success = await this.premiseSingleEditStore.savePrice();
|
await this.premiseSingleEditStore.savePrice();
|
||||||
} else if (type === 'material') {
|
} else if (type === 'material') {
|
||||||
success = await this.premiseSingleEditStore.saveMaterial();
|
await this.premiseSingleEditStore.saveMaterial();
|
||||||
} else if (type === 'packaging') {
|
} else if (type === 'packaging') {
|
||||||
success = await this.premiseSingleEditStore.savePackaging();
|
await this.premiseSingleEditStore.savePackaging();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success)
|
|
||||||
this.$refs.toast.addToast({
|
|
||||||
icon: 'warning',
|
|
||||||
message: "Failed to save data.",
|
|
||||||
title: "Error saving",
|
|
||||||
variant: 'exception',
|
|
||||||
duration: 8000
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
},
|
||||||
trace() {
|
trace() {
|
||||||
this.traceModal = true;
|
this.traceModal = true;
|
||||||
|
|
@ -234,7 +199,8 @@ export default {
|
||||||
if (this.$route.params.ids)
|
if (this.$route.params.ids)
|
||||||
this.bulkEditQuery = this.$route.params.ids;
|
this.bulkEditQuery = this.$route.params.ids;
|
||||||
|
|
||||||
this.premiseSingleEditStore.load(this.id)
|
if (!isNaN(this.id))
|
||||||
|
this.premiseSingleEditStore.load(this.id)
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -305,11 +271,6 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.trace-link {
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-around {
|
.space-around {
|
||||||
margin: 3rem;
|
margin: 3rem;
|
||||||
}
|
}
|
||||||
|
|
@ -333,7 +294,4 @@ export default {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.destination-list-container {
|
|
||||||
margin: 2.4rem 1.6rem;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -2,28 +2,48 @@ import {defineStore, getActivePinia} from 'pinia'
|
||||||
import {config} from '@/config'
|
import {config} from '@/config'
|
||||||
import {toRaw} from "vue";
|
import {toRaw} from "vue";
|
||||||
import {getCsrfToken} from "@/backend.js";
|
import {getCsrfToken} from "@/backend.js";
|
||||||
|
import logger from "@/logger.js";
|
||||||
|
|
||||||
export const useErrorStore = defineStore('error', {
|
export const useErrorStore = defineStore('error', {
|
||||||
state() {
|
state() {
|
||||||
return {
|
return {
|
||||||
errors: [],
|
notifications: [],
|
||||||
sendCache: [],
|
sendCache: [],
|
||||||
autoSubmitInterval: 30000,
|
autoSubmitInterval: 30000,
|
||||||
autoSubmitTimer: null
|
autoSubmitTimer: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
lastError: (state) => state.errors.length > 0 ? state.errors[state.errors.length - 1].error : null,
|
lastError: (state) => state.notifications.length > 0 ? state.notifications[state.notifications.length - 1].error : null,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
clearErrors() {
|
clearErrors() {
|
||||||
this.errors = [];
|
this.notifications = [];
|
||||||
console.log("Cleared errors");
|
console.log("Cleared errors");
|
||||||
},
|
},
|
||||||
|
popNotification() {
|
||||||
|
return this.notifications.pop();
|
||||||
|
},
|
||||||
|
addNotification(notification) {
|
||||||
|
this.notifications.push({
|
||||||
|
icon: notification.icon ?? null,
|
||||||
|
message: notification.message ?? 'Unknown notification',
|
||||||
|
title: notification.title ?? 'Notification',
|
||||||
|
variant: notification.variant ?? 'success',
|
||||||
|
duration: notification.duration ?? 8000
|
||||||
|
})
|
||||||
|
},
|
||||||
async addError(errorDto, options = {}) {
|
async addError(errorDto, options = {}) {
|
||||||
const {request = null, store = null, global = false} = options;
|
const {request = null, store = null, global = false} = options;
|
||||||
const state = this.captureStoreState(store, global);
|
const state = this.captureStoreState(store, global);
|
||||||
|
|
||||||
|
this.addNotification({
|
||||||
|
icon: 'bug',
|
||||||
|
message: errorDto.message ?? 'Unknown error code',
|
||||||
|
title: errorDto.title ?? 'Unknown error',
|
||||||
|
variant: 'exception',
|
||||||
|
});
|
||||||
|
|
||||||
const error = {
|
const error = {
|
||||||
error: {
|
error: {
|
||||||
code: errorDto.code ?? 'Unknown error code',
|
code: errorDto.code ?? 'Unknown error code',
|
||||||
|
|
@ -37,9 +57,8 @@ export const useErrorStore = defineStore('error', {
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(error);
|
logger.log(error);
|
||||||
|
|
||||||
this.errors.push(error);
|
|
||||||
this.sendCache.push(error);
|
this.sendCache.push(error);
|
||||||
await this.transmitErrors();
|
await this.transmitErrors();
|
||||||
},
|
},
|
||||||
|
|
@ -57,7 +76,6 @@ export const useErrorStore = defineStore('error', {
|
||||||
|
|
||||||
const url = `${config.backendUrl}/error/`;
|
const url = `${config.backendUrl}/error/`;
|
||||||
|
|
||||||
|
|
||||||
const response = await fetch(url, params
|
const response = await fetch(url, params
|
||||||
).catch(e => {
|
).catch(e => {
|
||||||
this.startAutoSubmitTimer();
|
this.startAutoSubmitTimer();
|
||||||
|
|
@ -117,7 +135,7 @@ export const useErrorStore = defineStore('error', {
|
||||||
},
|
},
|
||||||
async submitOnBeforeUnload() {
|
async submitOnBeforeUnload() {
|
||||||
if (this.sendCache.length > 0) {
|
if (this.sendCache.length > 0) {
|
||||||
navigator.sendBeacon('/api/errors', JSON.stringify(
|
navigator.sendBeacon(`${config.backendUrl}/error/`, JSON.stringify(
|
||||||
toRaw(this.sendCache)
|
toRaw(this.sendCache)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
@ -132,30 +150,30 @@ export function setupErrorBuffer() {
|
||||||
const errorStore = useErrorStore()
|
const errorStore = useErrorStore()
|
||||||
|
|
||||||
//Unhandled Promise Rejections
|
//Unhandled Promise Rejections
|
||||||
// window.addEventListener('unhandledrejection', (event) => {
|
window.addEventListener('unhandledrejection', (event) => {
|
||||||
|
|
||||||
// const error = {
|
const error = {
|
||||||
// code: "Unhandled rejection",
|
code: "Unhandled rejection",
|
||||||
// title: "Frontend error",
|
title: "Frontend error",
|
||||||
// message: event.reason?.message || 'Unhandled Promise Rejection',
|
message: event.reason?.message || 'Unhandled Promise Rejection',
|
||||||
// traceCombined: event.reason?.stack,
|
traceCombined: event.reason?.stack,
|
||||||
// };
|
};
|
||||||
|
|
||||||
// errorStore.addError(error, {global: true}).then(r => {} );
|
errorStore.addError(error, {global: true}).then(r => {} );
|
||||||
// })
|
})
|
||||||
|
|
||||||
// // JavaScript Errors
|
// // JavaScript Errors
|
||||||
// window.addEventListener('error', (event) => {
|
window.addEventListener('error', (event) => {
|
||||||
// const error = {
|
const error = {
|
||||||
// code: "General error",
|
code: "General error",
|
||||||
// title: "Frontend error",
|
title: "Frontend error",
|
||||||
// message: event.reason?.message || 'Unhandled Promise Rejection',
|
message: event.reason?.message || 'Unhandled Promise Rejection',
|
||||||
// traceCombined: event.reason?.stack,
|
traceCombined: event.reason?.stack,
|
||||||
// };
|
};
|
||||||
// errorStore.addError(error, {global: true}).then(r => {} );
|
errorStore.addError(error, {global: true}).then(r => {} );
|
||||||
// })
|
})
|
||||||
|
|
||||||
window.addEventListener('beforeunload', () => {
|
window.addEventListener('beforeunload', async () => {
|
||||||
errorStore.submitOnBeforeUnload();
|
await errorStore.submitOnBeforeUnload();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -35,7 +35,6 @@ export const useNodeStore = defineStore('node', {
|
||||||
let error = null;
|
let error = null;
|
||||||
params.append('address', address);
|
params.append('address', address);
|
||||||
const data = await performRequest(this, "GET", `${config.backendUrl}/nodes/locate/${params.size === 0 ? '' : '?'}${params.toString()}`, null, true, 'Geocoding error').catch(e => {
|
const data = await performRequest(this, "GET", `${config.backendUrl}/nodes/locate/${params.size === 0 ? '' : '?'}${params.toString()}`, null, true, 'Geocoding error').catch(e => {
|
||||||
logger.log("geo locate exception", e.errorObj);
|
|
||||||
error = e.errorObj;
|
error = e.errorObj;
|
||||||
});
|
});
|
||||||
return {node: data?.data, error: error};
|
return {node: data?.data, error: error};
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ export const usePremiseSingleEditStore = defineStore('premiseSingleEdit', {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.premise = null;
|
this.premise = null;
|
||||||
|
|
||||||
|
logger.info("load premise", id);
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('premissIds', id.toString());
|
params.append('premissIds', id.toString());
|
||||||
const url = `${config.backendUrl}/calculation/edit/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
const url = `${config.backendUrl}/calculation/edit/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
||||||
|
|
@ -71,8 +73,7 @@ export const usePremiseSingleEditStore = defineStore('premiseSingleEdit', {
|
||||||
const url = `${config.backendUrl}/calculation/start/`;
|
const url = `${config.backendUrl}/calculation/start/`;
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
await performRequest(this, 'PUT', url, body, false, ['Premiss validation error', 'Internal Server Error']).catch(e => {
|
await performRequest(this, 'PUT', url, body, false, ['Premiss validation error']).catch(e => {
|
||||||
logger.log("startCalculation exception", e.errorObj);
|
|
||||||
error = e.errorObj;
|
error = e.errorObj;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import org.springframework.web.method.annotation.MethodArgumentTypeMismatchExcep
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ControllerAdvice
|
@ControllerAdvice
|
||||||
|
|
@ -27,7 +28,7 @@ public class GlobalExceptionHandler {
|
||||||
|
|
||||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||||
public ResponseEntity<ErrorResponseDTO> handleValidationExceptions(
|
public ResponseEntity<ErrorResponseDTO> handleMalformedReqExceptions(
|
||||||
HttpMessageNotReadableException exception) {
|
HttpMessageNotReadableException exception) {
|
||||||
|
|
||||||
ErrorDTO error = new ErrorDTO(
|
ErrorDTO error = new ErrorDTO(
|
||||||
|
|
@ -59,7 +60,7 @@ public class GlobalExceptionHandler {
|
||||||
// You might want to create a custom ErrorDTO that can handle field errors
|
// You might want to create a custom ErrorDTO that can handle field errors
|
||||||
ErrorDTO error = new ErrorDTO(
|
ErrorDTO error = new ErrorDTO(
|
||||||
exception.getClass().getName(),
|
exception.getClass().getName(),
|
||||||
"Validation Failed",
|
"Validation failed",
|
||||||
errorMessage,
|
errorMessage,
|
||||||
Arrays.asList(exception.getStackTrace())
|
Arrays.asList(exception.getStackTrace())
|
||||||
);
|
);
|
||||||
|
|
@ -113,7 +114,6 @@ public class GlobalExceptionHandler {
|
||||||
@ExceptionHandler(ConstraintViolationException.class)
|
@ExceptionHandler(ConstraintViolationException.class)
|
||||||
public ResponseEntity<ErrorResponseDTO> handleConstraintViolation(ConstraintViolationException exception) {
|
public ResponseEntity<ErrorResponseDTO> handleConstraintViolation(ConstraintViolationException exception) {
|
||||||
|
|
||||||
// Extract constraint violation details
|
|
||||||
String errorMessage = exception.getConstraintViolations()
|
String errorMessage = exception.getConstraintViolations()
|
||||||
.stream()
|
.stream()
|
||||||
.map(ConstraintViolation::getMessage)
|
.map(ConstraintViolation::getMessage)
|
||||||
|
|
@ -153,12 +153,30 @@ public class GlobalExceptionHandler {
|
||||||
return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.INTERNAL_SERVER_ERROR);
|
return new ResponseEntity<>(new ErrorResponseDTO(error), HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(CompletionException.class)
|
||||||
|
public ResponseEntity<ErrorResponseDTO> handleCompletionException(CompletionException exception) {
|
||||||
|
Throwable cause = exception.getCause();
|
||||||
|
|
||||||
|
if (cause instanceof PremiseValidationError) {
|
||||||
|
return handlePremiseValidationException((PremiseValidationError) cause);
|
||||||
|
} else if (cause instanceof GeocodingException) {
|
||||||
|
return handleGeocodingException((GeocodingException) cause);
|
||||||
|
} else if (cause instanceof BadRequestException) {
|
||||||
|
return handleMethodArgumentNotValid((BadRequestException) cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleGenericException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public ResponseEntity<ErrorResponseDTO> handlePremiseValidationException(Exception exception) {
|
public ResponseEntity<ErrorResponseDTO> handleGenericException(Exception exception) {
|
||||||
|
|
||||||
|
Throwable cause = exception.getCause();
|
||||||
|
|
||||||
ErrorDTO error = new ErrorDTO(
|
ErrorDTO error = new ErrorDTO(
|
||||||
exception.getClass().getName(),
|
cause != null ? cause.getClass().getName() : exception.getClass().getName(),
|
||||||
"Internal Server Error",
|
"Internal Server Error",
|
||||||
exception.getMessage(),
|
cause != null ? cause.getMessage() : exception.getMessage(),
|
||||||
Arrays.asList(exception.getStackTrace())
|
Arrays.asList(exception.getStackTrace())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import java.util.List;
|
||||||
@RequestMapping("/api/reports")
|
@RequestMapping("/api/reports")
|
||||||
public class ReportingController {
|
public class ReportingController {
|
||||||
|
|
||||||
|
//TODO: rollenbeschränkung
|
||||||
|
|
||||||
private final ReportingService reportingService;
|
private final ReportingService reportingService;
|
||||||
private final ExcelReportingService excelReportingService;
|
private final ExcelReportingService excelReportingService;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ public class ErrorLogDTO {
|
||||||
|
|
||||||
private String pinia;
|
private String pinia;
|
||||||
|
|
||||||
|
private String request;
|
||||||
|
|
||||||
@JsonProperty("calculation_job_id")
|
@JsonProperty("calculation_job_id")
|
||||||
private Integer calculationJobId;
|
private Integer calculationJobId;
|
||||||
|
|
||||||
|
|
@ -29,6 +31,14 @@ public class ErrorLogDTO {
|
||||||
@JsonProperty("timestamp")
|
@JsonProperty("timestamp")
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
public String getRequest() {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequest(String request) {
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
public Integer getUserId() {
|
public Integer getUserId() {
|
||||||
return userId;
|
return userId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,8 @@ public class SysError {
|
||||||
|
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
private String request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the unique identifier of the system error.
|
* Gets the unique identifier of the system error.
|
||||||
*
|
*
|
||||||
|
|
@ -249,4 +251,12 @@ public class SysError {
|
||||||
public void setCreatedAt(LocalDateTime createdAt) {
|
public void setCreatedAt(LocalDateTime createdAt) {
|
||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRequest() {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequest(String request) {
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,182 +4,184 @@ import java.util.Optional;
|
||||||
|
|
||||||
public enum MeasureType {
|
public enum MeasureType {
|
||||||
|
|
||||||
|
// Serie D - Anti-dumping or countervailing duties (Anti-Dumping/Ausgleichszölle)
|
||||||
|
DUMPP("551", true, "D"), // Provisional anti-dumping duty - Vorläufiger Anti-Dumping-Zoll
|
||||||
|
DUMPD("552", true, "D"), // Definitive anti-dumping duty - Endgültiger Anti-Dumping-Zoll
|
||||||
|
COMPP("553", true, "D"), // Provisional countervailing duty - Vorläufiger Ausgleichszoll
|
||||||
|
COMPD("554", true, "D"), // Definitive countervailing duty - Endgültiger Ausgleichszoll
|
||||||
|
PCDUM("555", true, "D"), // Anti-dumping/countervailing duty - Pending collection - Schwebende Erhebung
|
||||||
|
NTDUM("561", false, "D"), // Notice of initiation of anti-dumping proceeding - Verfahrenseröffnung
|
||||||
|
SPDUM("562", true, "D"), // Suspended anti-dumping/countervailing duty - Ausgesetzter Zoll
|
||||||
|
RGDUM("564", false, "D"), // Anti-dumping/countervailing registration - Registrierung
|
||||||
|
REDUM("565", false, "D"), // Anti-dumping/countervailing review - Überprüfung
|
||||||
|
STDUM("566", false, "D"), // Anti-dumping/countervailing statistic - Statistik
|
||||||
|
CTDUM("570", false, "D"), // Anti-dumping/countervailing duty - Control - Kontrolle
|
||||||
|
|
||||||
// Serie C - Applicable duty (Anwendbare Zollsätze)
|
// Serie C - Applicable duty (Anwendbare Zollsätze)
|
||||||
APPL("103", true), // Third country duty - Standard-Drittlandszollsatz
|
APPL("103", true, "C"), // Third country duty - Standard-Drittlandszollsatz
|
||||||
APPEU("105", true), // Non preferential duty under end-use - Zollsatz unter End-Use
|
APPEU("105", true, "C"), // Non preferential duty under end-use - Zollsatz unter End-Use
|
||||||
CUD("106", true), // Customs Union Duty - Zollunionszollsatz
|
CUD("106", true, "C"), // Customs Union Duty - Zollunionszollsatz
|
||||||
S("112", true), // Autonomous tariff suspension - Autonome Zollaussetzung
|
S("112", true, "C"), // Autonomous tariff suspension - Autonome Zollaussetzung
|
||||||
SUSEU("115", true), // Autonomous suspension under end-use - Aussetzung unter End-Use
|
SUSEU("115", true, "C"), // Autonomous suspension under end-use - Aussetzung unter End-Use
|
||||||
SUSSH("117", false), // Suspension - ships/boats/vessels - Aussetzung für Schiffe (nicht relevant)
|
SUSSH("117", false, "C"), // Suspension - ships/boats/vessels - Aussetzung für Schiffe (nicht relevant)
|
||||||
AIRWO("119", false), // Airworthiness tariff suspension - Lufttüchtigkeits-Zollaussetzung (nicht relevant)
|
AIRWO("119", false, "C"), // Airworthiness tariff suspension - Lufttüchtigkeits-Zollaussetzung (nicht relevant)
|
||||||
K_122("122", true), // Non preferential tariff quota - Nichtpräferenzielles Zollkontingent
|
K_122("122", true, "C"), // Non preferential tariff quota - Nichtpräferenzielles Zollkontingent
|
||||||
KEU("123", true), // Non preferential tariff quota under end-use - Kontingent unter End-Use
|
KEU("123", true, "C"), // Non preferential tariff quota under end-use - Kontingent unter End-Use
|
||||||
OPQ("140", true), // Outward processing tariff preference - Passive Veredelung
|
OPQ("140", true, "C"), // Outward processing tariff preference - Passive Veredelung
|
||||||
S_141("141", true), // Preferential suspension - Präferenzielle Aussetzung
|
S_141("141", true, "C"), // Preferential suspension - Präferenzielle Aussetzung
|
||||||
PREF("142", true), // Tariff preference - Zollpräferenz
|
PREF("142", true, "C"), // Tariff preference - Zollpräferenz
|
||||||
K_143("143", true), // Preferential tariff quota - Präferenzielles Zollkontingent
|
K_143("143", true, "C"), // Preferential tariff quota - Präferenzielles Zollkontingent
|
||||||
C("144", true), // Preferential ceiling - Präferenzplafond
|
C("144", true, "C"), // Preferential ceiling - Präferenzplafond
|
||||||
PRFEU("145", true), // Preference under end-use - Präferenz unter End-Use
|
PRFEU("145", true, "C"), // Preference under end-use - Präferenz unter End-Use
|
||||||
PRKEU("146", true), // Preferential tariff quota under end-use - Präferenzkontingent unter End-Use
|
PRKEU("146", true, "C"), // Preferential tariff quota under end-use - Präferenzkontingent unter End-Use
|
||||||
CUQ("147", true), // Customs Union Quota - Zollunionskontingent
|
CUQ("147", true, "C"), // Customs Union Quota - Zollunionskontingent
|
||||||
|
|
||||||
// Serie A - Import/Export prohibited (Verbote)
|
// Serie A - Import/Export prohibited (Verbote)
|
||||||
PRO("277", false), // Import prohibition - Einfuhrverbot
|
PRO("277", false, "A"), // Import prohibition - Einfuhrverbot
|
||||||
PRX("278", false), // Export prohibition - Ausfuhrverbot
|
PRX("278", false, "A"), // Export prohibition - Ausfuhrverbot
|
||||||
RSUBH("481", false), // Declaration of subheading submitted to restrictions (import) - Einfuhrbeschränkung
|
RSUBH("481", false, "A"), // Declaration of subheading submitted to restrictions (import) - Einfuhrbeschränkung
|
||||||
RSHEX("485", false), // Declaration of subheading submitted to restrictions (export) - Ausfuhrbeschränkung
|
RSHEX("485", false, "A"), // Declaration of subheading submitted to restrictions (export) - Ausfuhrbeschränkung
|
||||||
|
|
||||||
// Serie P - VAT (Mehrwertsteuer)
|
// Serie P - VAT (Mehrwertsteuer)
|
||||||
TVA("305", false), // Value added tax - Mehrwertsteuer
|
TVA("305", false, "P"), // Value added tax - Mehrwertsteuer
|
||||||
|
|
||||||
// Serie Q - Excises (Verbrauchssteuern)
|
// Serie Q - Excises (Verbrauchssteuern)
|
||||||
ACC("306", false), // Excises - Verbrauchssteuern
|
ACC("306", false, "Q"), // Excises - Verbrauchssteuern
|
||||||
|
|
||||||
// Serie O - Supplementary unit (Besondere Maßeinheiten)
|
// Serie O - Supplementary unit (Besondere Maßeinheiten)
|
||||||
SUPUN("109", false), // Supplementary unit - Besondere Maßeinheit
|
SUPUN("109", false, "O"), // Supplementary unit - Besondere Maßeinheit
|
||||||
USIMP("110", false), // Supplementary unit import - Besondere Maßeinheit Einfuhr
|
USIMP("110", false, "O"), // Supplementary unit import - Besondere Maßeinheit Einfuhr
|
||||||
USEXP("111", false), // Supplementary unit export - Besondere Maßeinheit Ausfuhr
|
USEXP("111", false, "O"), // Supplementary unit export - Besondere Maßeinheit Ausfuhr
|
||||||
|
|
||||||
// Serie B - Entry subject to conditions (Einfuhr unter Bedingungen)
|
// Serie B - Entry subject to conditions (Einfuhr unter Bedingungen)
|
||||||
VETCTR("410", false), // Veterinary control - Veterinärkontrolle (nicht relevant für Gabelstapler)
|
VETCTR("410", false, "B"), // Veterinary control - Veterinärkontrolle (nicht relevant für Gabelstapler)
|
||||||
PRIOR_SURV("420", true), // Entry into free circulation (prior surveillance) - Vorherige Überwachung
|
PRIOR_SURV("420", true, "B"), // Entry into free circulation (prior surveillance) - Vorherige Überwachung
|
||||||
REU("464", true), // Declaration of subheading submitted to end-use provisions - End-Use-Erklärung
|
REU("464", true, "B"), // Declaration of subheading submitted to end-use provisions - End-Use-Erklärung
|
||||||
LPS("465", true), // Restriction on entry into free circulation - Einfuhrbeschränkung
|
LPS("465", true, "B"), // Restriction on entry into free circulation - Einfuhrbeschränkung
|
||||||
SPX("467", false), // Restriction on export - Ausfuhrbeschränkung
|
SPX("467", false, "B"), // Restriction on export - Ausfuhrbeschränkung
|
||||||
QX("473", false), // Export authorization - Ausfuhrgenehmigung
|
QX("473", false, "B"), // Export authorization - Ausfuhrgenehmigung
|
||||||
LPQ("474", true), // Entry into free circulation (quantitative limitation) - Mengenmäßige Beschränkung
|
LPQ("474", true, "B"), // Entry into free circulation (quantitative limitation) - Mengenmäßige Beschränkung
|
||||||
LPR("475", true), // Restriction on entry into free circulation - Einfuhrbeschränkung
|
LPR("475", true, "B"), // Restriction on entry into free circulation - Einfuhrbeschränkung
|
||||||
RX("476", false), // Restriction on export - Ausfuhrbeschränkung
|
RX("476", false, "B"), // Restriction on export - Ausfuhrbeschränkung
|
||||||
OPT("477", true), // Entry into free circulation (outward processing traffic) - Passive Veredelung
|
OPT("477", true, "B"), // Entry into free circulation (outward processing traffic) - Passive Veredelung
|
||||||
DURX("478", false), // Export authorization (Dual use) - Ausfuhrgenehmigung Dual-Use
|
DURX("478", false, "B"), // Export authorization (Dual use) - Ausfuhrgenehmigung Dual-Use
|
||||||
CHMEX("479", false), // Export control on dangerous chemicals - Exportkontrolle gefährliche Chemikalien
|
CHMEX("479", false, "B"), // Export control on dangerous chemicals - Exportkontrolle gefährliche Chemikalien
|
||||||
LCRED_M1("482", false), // Declaration submitted to legal restrictions (net weight/supplementary unit) - Rechtliche Beschränkungen
|
LCRED_M1("482", false, "B"), // Declaration submitted to legal restrictions (net weight/supplementary unit) - Rechtliche Beschränkungen
|
||||||
LCRED_V1("483", false), // Declaration submitted to legal restrictions (unit price) - Rechtliche Beschränkungen
|
LCRED_V1("483", false, "B"), // Declaration submitted to legal restrictions (unit price) - Rechtliche Beschränkungen
|
||||||
PCRED_M1("484", false), // Declaration submitted to physical restrictions (net weight/supplementary unit) - Physische Beschränkungen
|
PCRED_M1("484", false, "B"), // Declaration submitted to physical restrictions (net weight/supplementary unit) - Physische Beschränkungen
|
||||||
PCRED_V("491", false), // Declaration submitted to physical restrictions (declared statistical value) - Physische Beschränkungen
|
PCRED_V("491", false, "B"), // Declaration submitted to physical restrictions (declared statistical value) - Physische Beschränkungen
|
||||||
PCRED_M("492", false), // Declaration submitted to physical restrictions (declared net mass) - Physische Beschränkungen
|
PCRED_M("492", false, "B"), // Declaration submitted to physical restrictions (declared net mass) - Physische Beschränkungen
|
||||||
PCRED_S("493", false), // Declaration submitted to physical restrictions (declared supplementary unit) - Physische Beschränkungen
|
PCRED_S("493", false, "B"), // Declaration submitted to physical restrictions (declared supplementary unit) - Physische Beschränkungen
|
||||||
LCRED_V("494", false), // Declaration submitted to legal restrictions (declared statistical value) - Rechtliche Beschränkungen
|
LCRED_V("494", false, "B"), // Declaration submitted to legal restrictions (declared statistical value) - Rechtliche Beschränkungen
|
||||||
LCRED_M("495", false), // Declaration submitted to legal restrictions (declared net mass) - Rechtliche Beschränkungen
|
LCRED_M("495", false, "B"), // Declaration submitted to legal restrictions (declared net mass) - Rechtliche Beschränkungen
|
||||||
LCRED_S("496", false), // Declaration submitted to legal restrictions (declared supplementary unit) - Rechtliche Beschränkungen
|
LCRED_S("496", false, "B"), // Declaration submitted to legal restrictions (declared supplementary unit) - Rechtliche Beschränkungen
|
||||||
TRIMP("705", false), // Import prohibition on goods for torture and repression - Folter-/Repressionsgüter
|
TRIMP("705", false, "B"), // Import prohibition on goods for torture and repression - Folter-/Repressionsgüter
|
||||||
TREXP("706", false), // Export prohibition - torture/repression goods - Ausfuhrverbot Folter-/Repressionsgüter
|
TREXP("706", false, "B"), // Export prohibition - torture/repression goods - Ausfuhrverbot Folter-/Repressionsgüter
|
||||||
IMPORT_CTRL("707", true), // Import control - Einfuhrkontrolle (potentiell relevant für Bauteile)
|
IMPORT_CTRL("707", true, "B"), // Import control - Einfuhrkontrolle (potentiell relevant für Bauteile)
|
||||||
TRRX("708", false), // Export restriction - torture/repression goods - Ausfuhrbeschränkung
|
TRRX("708", false, "B"), // Export restriction - torture/repression goods - Ausfuhrbeschränkung
|
||||||
EXPCTRL("709", false), // Export control - Ausfuhrkontrolle
|
EXPCTRL("709", false, "B"), // Export control - Ausfuhrkontrolle
|
||||||
CITESIMP("710", false), // Import control - CITES - CITES-Kontrolle (nicht relevant)
|
CITESIMP("710", false, "B"), // Import control - CITES - CITES-Kontrolle (nicht relevant)
|
||||||
MILIM("711", false), // Import control on restricted goods and technologies - Militärgüter (nicht relevant)
|
MILIM("711", false, "B"), // Import control on restricted goods and technologies - Militärgüter (nicht relevant)
|
||||||
IAS("712", false), // Import control - IAS - Invasive Arten (nicht relevant)
|
IAS("712", false, "B"), // Import control - IAS - Invasive Arten (nicht relevant)
|
||||||
IMPORT_CTRL_GMO("713", false), // Import control on GMOs - GMO-Kontrolle (nicht relevant)
|
IMPORT_CTRL_GMO("713", false, "B"), // Import control on GMOs - GMO-Kontrolle (nicht relevant)
|
||||||
IMPCTRL("714", true), // Import control - Einfuhrkontrolle (potentiell relevant)
|
IMPCTRL("714", true, "B"), // Import control - Einfuhrkontrolle (potentiell relevant)
|
||||||
CITESEX("715", false), // Export control - CITES - CITES-Ausfuhrkontrolle
|
CITESEX("715", false, "B"), // Export control - CITES - CITES-Ausfuhrkontrolle
|
||||||
CFISH("716", false), // Export control - Fish - Fischereikontrolle (nicht relevant)
|
CFISH("716", false, "B"), // Export control - Fish - Fischereikontrolle (nicht relevant)
|
||||||
MILEX("717", false), // Export control on restricted goods and technologies - Militärgüter
|
MILEX("717", false, "B"), // Export control on restricted goods and technologies - Militärgüter
|
||||||
LUXEX("718", false), // Export control on luxury goods - Luxusgüter (nicht relevant)
|
LUXEX("718", false, "B"), // Export control on luxury goods - Luxusgüter (nicht relevant)
|
||||||
CIUUF("719", false), // Control on illegal fishing - Illegale Fischerei (nicht relevant)
|
CIUUF("719", false, "B"), // Control on illegal fishing - Illegale Fischerei (nicht relevant)
|
||||||
FFIM("722", false), // Entry restriction - feed and food - Futter-/Lebensmittel (nicht relevant)
|
FFIM("722", false, "B"), // Entry restriction - feed and food - Futter-/Lebensmittel (nicht relevant)
|
||||||
FGGIMP("724", false), // Import control of fluorinated greenhouse gases - Treibhausgase
|
FGGIMP("724", false, "B"), // Import control of fluorinated greenhouse gases - Treibhausgase
|
||||||
OZEXP("725", false), // Export control on ozone-depleting substances - Ozonschicht
|
OZEXP("725", false, "B"), // Export control on ozone-depleting substances - Ozonschicht
|
||||||
OZIMP("726", false), // Import control on ozone-depleting substances - Ozonschicht
|
OZIMP("726", false, "B"), // Import control on ozone-depleting substances - Ozonschicht
|
||||||
LUXIM("728", false), // Import control on luxury goods - Luxusgüter (nicht relevant)
|
LUXIM("728", false, "B"), // Import control on luxury goods - Luxusgüter (nicht relevant)
|
||||||
EXPCHE("730", false), // Compliance with pre-export checks requirements - Vorausfuhrkontrollen
|
EXPCHE("730", false, "B"), // Compliance with pre-export checks requirements - Vorausfuhrkontrollen
|
||||||
IMPORT_CTRL_731("731", true), // Import control - Einfuhrkontrolle (potentiell relevant)
|
IMPORT_CTRL_731("731", true, "B"), // Import control - Einfuhrkontrolle (potentiell relevant)
|
||||||
IMPORT_CTRL_732("732", true), // Import control - Einfuhrkontrolle (potentiell relevant)
|
IMPORT_CTRL_732("732", true, "B"), // Import control - Einfuhrkontrolle (potentiell relevant)
|
||||||
CGIMP("734", false), // Import control on cultural goods - Kulturgüter (nicht relevant)
|
CGIMP("734", false, "B"), // Import control on cultural goods - Kulturgüter (nicht relevant)
|
||||||
CGEXP("735", false), // Export control on cultural goods - Kulturgüter (nicht relevant)
|
CGEXP("735", false, "B"), // Export control on cultural goods - Kulturgüter (nicht relevant)
|
||||||
EXPORT_CTRL_736("736", false), // Export control - Ausfuhrkontrolle
|
EXPORT_CTRL_736("736", false, "B"), // Export control - Ausfuhrkontrolle
|
||||||
EXPORT_CTRL_737("737", false), // Export control - Ausfuhrkontrolle
|
EXPORT_CTRL_737("737", false, "B"), // Export control - Ausfuhrkontrolle
|
||||||
IMPORT_EXPORT_CML("738", false), // Import/export restriction - Common Military List goods - Militärgüter
|
IMPORT_EXPORT_CML("738", false, "B"), // Import/export restriction - Common Military List goods - Militärgüter
|
||||||
FUREX("740", false), // Export control on cat and dog fur - Katzen-/Hundefell (nicht relevant)
|
FUREX("740", false, "B"), // Export control on cat and dog fur - Katzen-/Hundefell (nicht relevant)
|
||||||
FURIM("745", false), // Import control on cat and dog fur - Katzen-/Hundefell (nicht relevant)
|
FURIM("745", false, "B"), // Import control on cat and dog fur - Katzen-/Hundefell (nicht relevant)
|
||||||
SEAL("746", false), // Import control on seal products - Robbenprodukte (nicht relevant)
|
SEAL("746", false, "B"), // Import control on seal products - Robbenprodukte (nicht relevant)
|
||||||
FLEGT("747", false), // Import control of timber - FLEGT licensing - Holzprodukte (nicht relevant)
|
FLEGT("747", false, "B"), // Import control of timber - FLEGT licensing - Holzprodukte (nicht relevant)
|
||||||
MERIM("748", false), // Import control of mercury - Quecksilber
|
MERIM("748", false, "B"), // Import control of mercury - Quecksilber
|
||||||
MEREX("749", false), // Export control of mercury - Quecksilber
|
MEREX("749", false, "B"), // Export control of mercury - Quecksilber
|
||||||
ORGANIC("750", false), // Import control of organic products - Bio-Produkte (nicht relevant)
|
ORGANIC("750", false, "B"), // Import control of organic products - Bio-Produkte (nicht relevant)
|
||||||
WSTEX("751", false), // Export control - Waste - Abfall
|
WSTEX("751", false, "B"), // Export control - Waste - Abfall
|
||||||
WSTIM("755", false), // Import control - Waste - Abfall
|
WSTIM("755", false, "B"), // Import control - Waste - Abfall
|
||||||
IMPCTRL_760("760", true), // Import control - Einfuhrkontrolle (potentiell relevant)
|
IMPCTRL_760("760", true, "B"), // Import control - Einfuhrkontrolle (potentiell relevant)
|
||||||
CHMIM("761", false), // Import control on REACH - Chemikalien
|
CHMIM("761", false, "B"), // Import control on REACH - Chemikalien
|
||||||
IMPORT_CTRL_762("762", true), // Import control - Einfuhrkontrolle (potentiell relevant)
|
IMPORT_CTRL_762("762", true, "B"), // Import control - Einfuhrkontrolle (potentiell relevant)
|
||||||
IMPORT_CTRL_763("763", true), // Import control - Einfuhrkontrolle (potentiell relevant)
|
IMPORT_CTRL_763("763", true, "B"), // Import control - Einfuhrkontrolle (potentiell relevant)
|
||||||
FGEXP("765", false), // Export control of fluorinated greenhouse gases - Treibhausgase
|
FGEXP("765", false, "B"), // Export control of fluorinated greenhouse gases - Treibhausgase
|
||||||
EXPORT_CTRL_766("766", false), // Export control - Ausfuhrkontrolle
|
EXPORT_CTRL_766("766", false, "B"), // Export control - Ausfuhrkontrolle
|
||||||
EXPORT_CTRL_767("767", false), // Export control - Ausfuhrkontrolle
|
EXPORT_CTRL_767("767", false, "B"), // Export control - Ausfuhrkontrolle
|
||||||
EXPORT_CTRL_768("768", false), // Export control - Ausfuhrkontrolle
|
EXPORT_CTRL_768("768", false, "B"), // Export control - Ausfuhrkontrolle
|
||||||
IMPOP("769", false), // Import control on persistent organic pollutants - Organische Schadstoffe
|
IMPOP("769", false, "B"), // Import control on persistent organic pollutants - Organische Schadstoffe
|
||||||
FLEGT_GHANA("770", false), // Import control timber - FLEGT Ghana - Holzprodukte (nicht relevant)
|
FLEGT_GHANA("770", false, "B"), // Import control timber - FLEGT Ghana - Holzprodukte (nicht relevant)
|
||||||
FLEGT_CAMEROON("771", false), // Import control timber - FLEGT Cameroon - Holzprodukte (nicht relevant)
|
FLEGT_CAMEROON("771", false, "B"), // Import control timber - FLEGT Cameroon - Holzprodukte (nicht relevant)
|
||||||
FLEGT_CONGO("772", false), // Import control timber - FLEGT Congo - Holzprodukte (nicht relevant)
|
FLEGT_CONGO("772", false, "B"), // Import control timber - FLEGT Congo - Holzprodukte (nicht relevant)
|
||||||
FLEGT_CAR("773", false), // Import control timber - FLEGT Central Africa - Holzprodukte (nicht relevant)
|
FLEGT_CAR("773", false, "B"), // Import control timber - FLEGT Central Africa - Holzprodukte (nicht relevant)
|
||||||
FLEGT_VIETNAM("774", false), // Import control timber - FLEGT Vietnam - Holzprodukte (nicht relevant)
|
FLEGT_VIETNAM("774", false, "B"), // Import control timber - FLEGT Vietnam - Holzprodukte (nicht relevant)
|
||||||
CBAM("775", false), // Carbon Border Adjustment Mechanism - CO2-Grenzausgleich
|
CBAM("775", false, "B"), // Carbon Border Adjustment Mechanism - CO2-Grenzausgleich
|
||||||
DFRSTIM("776", false), // Import control on deforestation - Entwaldung
|
DFRSTIM("776", false, "B"), // Import control on deforestation - Entwaldung
|
||||||
DFRSEXP("777", false), // Export control on deforestation - Entwaldung
|
DFRSEXP("777", false, "B"), // Export control on deforestation - Entwaldung
|
||||||
EXPORT_CTRL_780("780", false), // Export control - Ausfuhrkontrolle
|
EXPORT_CTRL_780("780", false, "B"), // Export control - Ausfuhrkontrolle
|
||||||
EXPORT_CTRL_781("781", false), // Export control - Ausfuhrkontrolle
|
EXPORT_CTRL_781("781", false, "B"), // Export control - Ausfuhrkontrolle
|
||||||
|
|
||||||
// Serie N - Posterior surveillance (Nachträgliche Überwachung)
|
// Serie N - Posterior surveillance (Nachträgliche Überwachung)
|
||||||
PIM("440", false), // Public Import Monitoring - Öffentliche Einfuhrüberwachung
|
PIM("440", false, "N"), // Public Import Monitoring - Öffentliche Einfuhrüberwachung
|
||||||
PEM("445", false), // Public Export Monitoring - Öffentliche Ausfuhrüberwachung
|
PEM("445", false, "N"), // Public Export Monitoring - Öffentliche Ausfuhrüberwachung
|
||||||
SUB("450", false), // Statistical surveillance - Statistische Überwachung
|
SUB("450", false, "N"), // Statistical surveillance - Statistische Überwachung
|
||||||
SUA("460", false), // Computer surveillance - Computerüberwachung
|
SUA("460", false, "N"), // Computer surveillance - Computerüberwachung
|
||||||
SQR("461", false), // Community surveillance - reference quantities - Referenzmengen
|
SQR("461", false, "N"), // Community surveillance - reference quantities - Referenzmengen
|
||||||
SUR("462", false), // Posterior import surveillance - Nachträgliche Einfuhrüberwachung
|
SUR("462", false, "N"), // Posterior import surveillance - Nachträgliche Einfuhrüberwachung
|
||||||
SUX("463", false), // Posterior export surveillance - Nachträgliche Ausfuhrüberwachung
|
SUX("463", false, "N"), // Posterior export surveillance - Nachträgliche Ausfuhrüberwachung
|
||||||
SUREX("470", false), // Export surveillance - Ausfuhrüberwachung
|
SUREX("470", false, "N"), // Export surveillance - Ausfuhrüberwachung
|
||||||
SUEX("471", false), // Export surveillance (TQS) - Ausfuhrüberwachung
|
SUEX("471", false, "N"), // Export surveillance (TQS) - Ausfuhrüberwachung
|
||||||
IMPMNTR_702("702", false), // Import Monitoring - Einfuhrüberwachung
|
IMPMNTR_702("702", false, "N"), // Import Monitoring - Einfuhrüberwachung
|
||||||
IMPMNTR_703("703", false), // Import Monitoring - Einfuhrüberwachung
|
IMPMNTR_703("703", false, "N"), // Import Monitoring - Einfuhrüberwachung
|
||||||
IMPMNTR_704("704", false), // Import Monitoring - Einfuhrüberwachung
|
IMPMNTR_704("704", false, "N"), // Import Monitoring - Einfuhrüberwachung
|
||||||
|
|
||||||
// Serie M - Unit price, standard import value (Preise und Werte)
|
// Serie M - Unit price, standard import value (Preise und Werte)
|
||||||
VU("488", false), // Unit price - Stückpreis
|
VU("488", false, "M"), // Unit price - Stückpreis
|
||||||
REPSU("489", false), // Representative price - Repräsentativer Preis
|
REPSU("489", false, "M"), // Representative price - Repräsentativer Preis
|
||||||
SIV("490", false), // Standard import value - Standard-Einfuhrwert
|
SIV("490", false, "M"), // Standard import value - Standard-Einfuhrwert
|
||||||
|
|
||||||
// Serie D - Anti-dumping or countervailing duties (Anti-Dumping/Ausgleichszölle)
|
|
||||||
DUMPP("551", true), // Provisional anti-dumping duty - Vorläufiger Anti-Dumping-Zoll
|
|
||||||
DUMPD("552", true), // Definitive anti-dumping duty - Endgültiger Anti-Dumping-Zoll
|
|
||||||
COMPP("553", true), // Provisional countervailing duty - Vorläufiger Ausgleichszoll
|
|
||||||
COMPD("554", true), // Definitive countervailing duty - Endgültiger Ausgleichszoll
|
|
||||||
PCDUM("555", true), // Anti-dumping/countervailing duty - Pending collection - Schwebende Erhebung
|
|
||||||
NTDUM("561", false), // Notice of initiation of anti-dumping proceeding - Verfahrenseröffnung
|
|
||||||
SPDUM("562", true), // Suspended anti-dumping/countervailing duty - Ausgesetzter Zoll
|
|
||||||
RGDUM("564", false), // Anti-dumping/countervailing registration - Registrierung
|
|
||||||
REDUM("565", false), // Anti-dumping/countervailing review - Überprüfung
|
|
||||||
STDUM("566", false), // Anti-dumping/countervailing statistic - Statistik
|
|
||||||
CTDUM("570", false), // Anti-dumping/countervailing duty - Control - Kontrolle
|
|
||||||
|
|
||||||
// Serie S - Supplementary amount (Zusätzliche Beträge)
|
// Serie S - Supplementary amount (Zusätzliche Beträge)
|
||||||
ASURE("651", false), // Security based on representative price - Sicherheit basierend auf Preis
|
ASURE("651", false, "S"), // Security based on representative price - Sicherheit basierend auf Preis
|
||||||
ASUCI("652", false), // Additional duty based on CIF price - Zusatzzoll basierend auf CIF-Preis
|
ASUCI("652", false, "S"), // Additional duty based on CIF price - Zusatzzoll basierend auf CIF-Preis
|
||||||
SECQ("653", false), // Security reduced under tariff quota - Reduzierte Sicherheit
|
SECQ("653", false, "S"), // Security reduced under tariff quota - Reduzierte Sicherheit
|
||||||
ADQ("654", false), // Additional duty reduced under tariff quota - Reduzierter Zusatzzoll
|
ADQ("654", false, "S"), // Additional duty reduced under tariff quota - Reduzierter Zusatzzoll
|
||||||
SECPO("655", false), // Security (poultry) based on representative price - Sicherheit Geflügel
|
SECPO("655", false, "S"), // Security (poultry) based on representative price - Sicherheit Geflügel
|
||||||
ADPOC("656", false), // Additional duty (poultry) based on CIF price - Zusatzzoll Geflügel
|
ADPOC("656", false, "S"), // Additional duty (poultry) based on CIF price - Zusatzzoll Geflügel
|
||||||
RESEC("657", false), // Reduced security based on representative price - Reduzierte Sicherheit
|
RESEC("657", false, "S"), // Reduced security based on representative price - Reduzierte Sicherheit
|
||||||
READ("658", false), // Reduced additional duty based on CIF price - Reduzierter Zusatzzoll
|
READ("658", false, "S"), // Reduced additional duty based on CIF price - Reduzierter Zusatzzoll
|
||||||
|
|
||||||
// Serie F - Additional duty on sugar, flour (Zusatzzölle Zucker/Mehl)
|
// Serie F - Additional duty on sugar, flour (Zusatzzölle Zucker/Mehl)
|
||||||
AD_SZ("672", false), // Amount of additional duty on sugar - Zusatzzoll Zucker (nicht relevant)
|
AD_SZ("672", false, "F"), // Amount of additional duty on sugar - Zusatzzoll Zucker (nicht relevant)
|
||||||
AD_FM("673", false), // Amount of additional duty on flour - Zusatzzoll Mehl (nicht relevant)
|
AD_FM("673", false, "F"), // Amount of additional duty on flour - Zusatzzoll Mehl (nicht relevant)
|
||||||
|
|
||||||
// Serie E - Levies, export refunds (Abgaben, Ausfuhrerstattungen)
|
// Serie E - Levies, export refunds (Abgaben, Ausfuhrerstattungen)
|
||||||
EA("674", false), // Agricultural component - Landwirtschaftliche Komponente (nicht relevant)
|
EA("674", false, "E"), // Agricultural component - Landwirtschaftliche Komponente (nicht relevant)
|
||||||
|
|
||||||
// Serie J - Countervailing charge (Ausgleichsabgaben)
|
// Serie J - Countervailing charge (Ausgleichsabgaben)
|
||||||
TC("690", false), // Countervailing charge - Ausgleichsabgabe
|
TC("690", false, "J"), // Countervailing charge - Ausgleichsabgabe
|
||||||
DR("695", true), // Additional duties - Zusatzzölle
|
DR("695", true, "J"), // Additional duties - Zusatzzölle
|
||||||
SAFDU("696", true); // Additional duties (safeguard) - Schutzmaßnahmen-Zusatzzölle
|
SAFDU("696", true, "J"); // Additional duties (safeguard) - Schutzmaßnahmen-Zusatzzölle
|
||||||
|
|
||||||
|
|
||||||
private final String measureCode;
|
private final String measureCode;
|
||||||
private final boolean containsRelevantDuty;
|
private final boolean containsRelevantDuty;
|
||||||
|
private final String series;
|
||||||
|
|
||||||
MeasureType(String measureCode, boolean containsRelevantDuty) {
|
MeasureType(String measureCode, boolean containsRelevantDuty, String series) {
|
||||||
this.measureCode = measureCode;
|
this.measureCode = measureCode;
|
||||||
this.containsRelevantDuty = containsRelevantDuty;
|
this.containsRelevantDuty = containsRelevantDuty;
|
||||||
|
this.series = series;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMeasureCode() {
|
public String getMeasureCode() {
|
||||||
|
|
@ -190,6 +192,10 @@ public enum MeasureType {
|
||||||
return containsRelevantDuty;
|
return containsRelevantDuty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSeries() {
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
|
||||||
public static Optional<MeasureType> fromMeasureCode(String measureCode) {
|
public static Optional<MeasureType> fromMeasureCode(String measureCode) {
|
||||||
for (MeasureType type : values()) {
|
for (MeasureType type : values()) {
|
||||||
if (type.measureCode.equals(measureCode)) {
|
if (type.measureCode.equals(measureCode)) {
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public class SysErrorRepository {
|
||||||
@Transactional
|
@Transactional
|
||||||
public Integer insert(SysError error) {
|
public Integer insert(SysError error) {
|
||||||
|
|
||||||
String errorSql = "INSERT INTO sys_error (user_id, title, code, message, pinia, calculation_job_id, bulk_operation_id, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
String errorSql = "INSERT INTO sys_error (user_id, title, code, message, pinia, calculation_job_id, bulk_operation_id, type, request) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
|
||||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||||
|
|
||||||
|
|
@ -50,6 +50,7 @@ public class SysErrorRepository {
|
||||||
ps.setObject(6, error.getCalculationJobId()); // Use setObject for nullable Integer
|
ps.setObject(6, error.getCalculationJobId()); // Use setObject for nullable Integer
|
||||||
ps.setObject(7, error.getBulkOperationId()); // Use setObject for nullable Integer
|
ps.setObject(7, error.getBulkOperationId()); // Use setObject for nullable Integer
|
||||||
ps.setString(8, error.getType().name());
|
ps.setString(8, error.getType().name());
|
||||||
|
ps.setString(9, error.getRequest());
|
||||||
return ps;
|
return ps;
|
||||||
}, keyHolder);
|
}, keyHolder);
|
||||||
|
|
||||||
|
|
@ -68,7 +69,7 @@ public class SysErrorRepository {
|
||||||
@Transactional
|
@Transactional
|
||||||
public void insert(List<SysError> errors) {
|
public void insert(List<SysError> errors) {
|
||||||
// First insert the sys_error records
|
// 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 (?, ?, ?, ?, ?, ?, ?, ?)";
|
String errorSql = "INSERT INTO sys_error (user_id, title, code, message, pinia, calculation_job_id, bulk_operation_id, type, request) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
|
||||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||||
|
|
||||||
|
|
@ -83,6 +84,7 @@ public class SysErrorRepository {
|
||||||
ps.setObject(6, error.getCalculationJobId()); // Use setObject for nullable Integer
|
ps.setObject(6, error.getCalculationJobId()); // Use setObject for nullable Integer
|
||||||
ps.setObject(7, error.getBulkOperationId()); // Use setObject for nullable Integer
|
ps.setObject(7, error.getBulkOperationId()); // Use setObject for nullable Integer
|
||||||
ps.setString(8, error.getType().name());
|
ps.setString(8, error.getType().name());
|
||||||
|
ps.setString(9, error.getRequest());
|
||||||
return ps;
|
return ps;
|
||||||
}, keyHolder);
|
}, keyHolder);
|
||||||
|
|
||||||
|
|
@ -128,7 +130,7 @@ public class SysErrorRepository {
|
||||||
// Build main query with pagination
|
// Build main query with pagination
|
||||||
String sql = """
|
String sql = """
|
||||||
SELECT e.id, e.user_id, e.title, e.code, e.message, e.pinia,
|
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
|
e.calculation_job_id, e.bulk_operation_id, e.type, e.created_at, e.request
|
||||||
FROM sys_error e
|
FROM sys_error e
|
||||||
""" + whereClause + """
|
""" + whereClause + """
|
||||||
ORDER BY e.created_at DESC
|
ORDER BY e.created_at DESC
|
||||||
|
|
@ -215,6 +217,7 @@ public class SysErrorRepository {
|
||||||
error.setCode(rs.getString("code"));
|
error.setCode(rs.getString("code"));
|
||||||
error.setMessage(rs.getString("message"));
|
error.setMessage(rs.getString("message"));
|
||||||
error.setPinia(rs.getString("pinia"));
|
error.setPinia(rs.getString("pinia"));
|
||||||
|
error.setRequest(rs.getString("request"));
|
||||||
error.setCalculationJobId(rs.getObject("calculation_job_id", Integer.class));
|
error.setCalculationJobId(rs.getObject("calculation_job_id", Integer.class));
|
||||||
error.setBulkOperationId(rs.getObject("bulk_operation_id", Integer.class));
|
error.setBulkOperationId(rs.getObject("bulk_operation_id", Integer.class));
|
||||||
error.setType(SysErrorType.valueOf(rs.getString("type")));
|
error.setType(SysErrorType.valueOf(rs.getString("type")));
|
||||||
|
|
@ -234,6 +237,7 @@ public class SysErrorRepository {
|
||||||
traceItem.setFile(rs.getString("file"));
|
traceItem.setFile(rs.getString("file"));
|
||||||
traceItem.setMethod(rs.getString("method"));
|
traceItem.setMethod(rs.getString("method"));
|
||||||
traceItem.setFullPath(rs.getString("fullPath"));
|
traceItem.setFullPath(rs.getString("fullPath"));
|
||||||
|
|
||||||
return traceItem;
|
return traceItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,11 @@ import de.avatic.lcc.dto.custom.CustomMeasureDTO;
|
||||||
import de.avatic.lcc.dto.generic.PropertyDTO;
|
import de.avatic.lcc.dto.generic.PropertyDTO;
|
||||||
import de.avatic.lcc.model.db.country.Country;
|
import de.avatic.lcc.model.db.country.Country;
|
||||||
import de.avatic.lcc.model.db.materials.Material;
|
import de.avatic.lcc.model.db.materials.Material;
|
||||||
|
import de.avatic.lcc.model.db.properties.CountryPropertyMappingId;
|
||||||
|
import de.avatic.lcc.model.db.properties.CustomUnionType;
|
||||||
import de.avatic.lcc.model.db.properties.SystemPropertyMappingId;
|
import de.avatic.lcc.model.db.properties.SystemPropertyMappingId;
|
||||||
import de.avatic.lcc.model.eutaxation.MeasureType;
|
import de.avatic.lcc.model.eutaxation.MeasureType;
|
||||||
|
import de.avatic.lcc.repositories.country.CountryPropertyRepository;
|
||||||
import de.avatic.lcc.repositories.country.CountryRepository;
|
import de.avatic.lcc.repositories.country.CountryRepository;
|
||||||
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
||||||
import de.avatic.lcc.service.calculation.NomenclatureService;
|
import de.avatic.lcc.service.calculation.NomenclatureService;
|
||||||
|
|
@ -29,19 +32,34 @@ public class TaxationResolverService {
|
||||||
private final PropertyRepository propertyRepository;
|
private final PropertyRepository propertyRepository;
|
||||||
private final ZolltarifnummernApiService zolltarifnummernApiService;
|
private final ZolltarifnummernApiService zolltarifnummernApiService;
|
||||||
private final NomenclatureService nomenclatureService;
|
private final NomenclatureService nomenclatureService;
|
||||||
|
private final CountryPropertyRepository countryPropertyRepository;
|
||||||
|
|
||||||
public TaxationResolverService(CountryRepository countryRepository, EUTaxationApiService eUTaxationApiService, PropertyRepository propertyRepository, ZolltarifnummernApiService zolltarifnummernApiService, NomenclatureService nomenclatureService) {
|
public TaxationResolverService(CountryRepository countryRepository, EUTaxationApiService eUTaxationApiService, PropertyRepository propertyRepository, ZolltarifnummernApiService zolltarifnummernApiService, NomenclatureService nomenclatureService, CountryPropertyRepository countryPropertyRepository) {
|
||||||
this.countryRepository = countryRepository;
|
this.countryRepository = countryRepository;
|
||||||
this.eUTaxationApiService = eUTaxationApiService;
|
this.eUTaxationApiService = eUTaxationApiService;
|
||||||
this.propertyRepository = propertyRepository;
|
this.propertyRepository = propertyRepository;
|
||||||
this.zolltarifnummernApiService = zolltarifnummernApiService;
|
this.zolltarifnummernApiService = zolltarifnummernApiService;
|
||||||
this.nomenclatureService = nomenclatureService;
|
this.nomenclatureService = nomenclatureService;
|
||||||
|
this.countryPropertyRepository = countryPropertyRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Map<TaxationResolverRequest, List<GoodsMeasForWsResponse>> doRequests(List<TaxationResolverRequest> requests) {
|
private Map<TaxationResolverRequest, TaxationResolverApiResponse> doRequests(List<TaxationResolverRequest> requests) {
|
||||||
|
|
||||||
var filteredRequests = requests.stream().collect(Collectors.partitioningBy(r -> r.material().getHsCode() != null && r.material().getHsCode().length() < 10));
|
Map<Integer, CustomUnionType> union = requests.stream()
|
||||||
|
.map(TaxationResolverRequest::countryId)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
countryId -> countryId,
|
||||||
|
countryId -> countryPropertyRepository
|
||||||
|
.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, countryId)
|
||||||
|
.map(p -> CustomUnionType.valueOf(p.getCurrentValue()))
|
||||||
|
.orElseThrow()
|
||||||
|
));
|
||||||
|
|
||||||
|
var byCustomUnion = requests.stream().collect(Collectors.groupingBy(r -> (union.get(r.countryId()))));
|
||||||
|
|
||||||
|
var filteredRequests = byCustomUnion.getOrDefault(CustomUnionType.NONE, Collections.emptyList()).stream().collect(Collectors.partitioningBy(r -> r.material().getHsCode() != null && r.material().getHsCode().length() < 10));
|
||||||
|
|
||||||
var joined = Stream.concat(
|
var joined = Stream.concat(
|
||||||
filteredRequests.get(false).stream()
|
filteredRequests.get(false).stream()
|
||||||
|
|
@ -49,13 +67,25 @@ public class TaxationResolverService {
|
||||||
.map(r -> new TaxationResolverSingleRequest(r.material().getHsCode(), r.countryId(), r)),
|
.map(r -> new TaxationResolverSingleRequest(r.material().getHsCode(), r.countryId(), r)),
|
||||||
resolveIncompleteHsCodesIntern(filteredRequests.get(true)).stream());
|
resolveIncompleteHsCodesIntern(filteredRequests.get(true)).stream());
|
||||||
|
|
||||||
|
|
||||||
var singleResponses = doSingleRequests(joined.toList());
|
var singleResponses = doSingleRequests(joined.toList());
|
||||||
|
|
||||||
return requests.stream().collect(Collectors.toMap(
|
return Stream.of(
|
||||||
r -> r,
|
byCustomUnion.getOrDefault(CustomUnionType.NONE,Collections.emptyList()).stream().collect(Collectors.toMap(
|
||||||
r -> singleResponses.keySet().stream().filter(k -> k.origin.equals(r)).map(singleResponses::get).toList()
|
r -> r,
|
||||||
));
|
r -> new TaxationResolverApiResponse(
|
||||||
|
r.material(),
|
||||||
|
true,
|
||||||
|
singleResponses.keySet().stream().filter(k -> k.origin.equals(r)).map(singleResponses::get).toList()))),
|
||||||
|
|
||||||
|
byCustomUnion.getOrDefault(CustomUnionType.EU,Collections.emptyList()).stream().collect(Collectors.toMap(
|
||||||
|
r -> r,
|
||||||
|
r -> new TaxationResolverApiResponse(
|
||||||
|
r.material(),
|
||||||
|
false,
|
||||||
|
null))))
|
||||||
|
|
||||||
|
.flatMap(map -> map.entrySet().stream()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,(r1,_) -> r1));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<TaxationResolverSingleRequest> resolveIncompleteHsCodesIntern(List<TaxationResolverRequest> request) {
|
private List<TaxationResolverSingleRequest> resolveIncompleteHsCodesIntern(List<TaxationResolverRequest> request) {
|
||||||
|
|
@ -78,7 +108,6 @@ public class TaxationResolverService {
|
||||||
|
|
||||||
private Map<TaxationResolverSingleRequest, GoodsMeasForWsResponse> doSingleRequests(List<TaxationResolverSingleRequest> requests) {
|
private Map<TaxationResolverSingleRequest, GoodsMeasForWsResponse> doSingleRequests(List<TaxationResolverSingleRequest> requests) {
|
||||||
|
|
||||||
|
|
||||||
Map<Integer, Country> countries = requests.stream()
|
Map<Integer, Country> countries = requests.stream()
|
||||||
.map(TaxationResolverSingleRequest::countryId)
|
.map(TaxationResolverSingleRequest::countryId)
|
||||||
.distinct()
|
.distinct()
|
||||||
|
|
@ -87,6 +116,7 @@ public class TaxationResolverService {
|
||||||
countryId -> countryRepository.getById(countryId).orElseThrow()
|
countryId -> countryRepository.getById(countryId).orElseThrow()
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
Map<TaxationResolverSingleRequest, CompletableFuture<GoodsMeasForWsResponse>> futureMap =
|
Map<TaxationResolverSingleRequest, CompletableFuture<GoodsMeasForWsResponse>> futureMap =
|
||||||
requests.stream()
|
requests.stream()
|
||||||
.collect(Collectors.toMap(
|
.collect(Collectors.toMap(
|
||||||
|
|
@ -100,12 +130,11 @@ public class TaxationResolverService {
|
||||||
|
|
||||||
CompletableFuture.allOf(futureMap.values().toArray(new CompletableFuture[0])).join();
|
CompletableFuture.allOf(futureMap.values().toArray(new CompletableFuture[0])).join();
|
||||||
|
|
||||||
return
|
return futureMap.entrySet().stream()
|
||||||
futureMap.entrySet().stream()
|
.collect(Collectors.toMap(
|
||||||
.collect(Collectors.toMap(
|
Map.Entry::getKey,
|
||||||
Map.Entry::getKey,
|
entry -> entry.getValue().join()
|
||||||
entry -> entry.getValue().join()
|
));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Double> extractDuty(GoodsMeasuresForWsResponse.Measures.Measure measure) {
|
private Optional<Double> extractDuty(GoodsMeasuresForWsResponse.Measures.Measure measure) {
|
||||||
|
|
@ -124,19 +153,27 @@ public class TaxationResolverService {
|
||||||
|
|
||||||
private List<GoodsMeasuresForWsResponse.Measures.Measure> filterToNewestMeasuresPerType(List<GoodsMeasuresForWsResponse.Measures.Measure> measures) {
|
private List<GoodsMeasuresForWsResponse.Measures.Measure> filterToNewestMeasuresPerType(List<GoodsMeasuresForWsResponse.Measures.Measure> measures) {
|
||||||
Map<String, GoodsMeasuresForWsResponse.Measures.Measure> newestByType = new HashMap<>();
|
Map<String, GoodsMeasuresForWsResponse.Measures.Measure> newestByType = new HashMap<>();
|
||||||
|
ArrayList<GoodsMeasuresForWsResponse.Measures.Measure> unfiltered = new ArrayList<>();
|
||||||
|
|
||||||
for (GoodsMeasuresForWsResponse.Measures.Measure measure : measures) {
|
for (GoodsMeasuresForWsResponse.Measures.Measure measure : measures) {
|
||||||
String measureTypeKey = measure.getMeasureType().getMeasureType();
|
String measureCode = measure.getMeasureType().getMeasureType();
|
||||||
|
|
||||||
GoodsMeasuresForWsResponse.Measures.Measure existing = newestByType.get(measureTypeKey);
|
GoodsMeasuresForWsResponse.Measures.Measure existing = newestByType.get(measureCode);
|
||||||
|
|
||||||
|
var type = MeasureType.fromMeasureCode(measureCode);
|
||||||
|
|
||||||
|
// filter multiples only in C series.
|
||||||
|
if (type.isPresent() && type.get().getSeries().equals("C")) {
|
||||||
|
if (existing == null ||
|
||||||
|
measure.getValidityStartDate().compare(existing.getValidityStartDate()) > 0) {
|
||||||
|
newestByType.put(measureCode, measure);
|
||||||
|
}
|
||||||
|
} else unfiltered.add(measure);
|
||||||
|
|
||||||
if (existing == null ||
|
|
||||||
measure.getValidityStartDate().compare(existing.getValidityStartDate()) > 0) {
|
|
||||||
newestByType.put(measureTypeKey, measure);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ArrayList<>(newestByType.values());
|
unfiltered.addAll(newestByType.values());
|
||||||
|
return unfiltered;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean validate(String hsCode) {
|
public boolean validate(String hsCode) {
|
||||||
|
|
@ -204,11 +241,18 @@ public class TaxationResolverService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TaxationResolverResponse> getTariffRates(List<TaxationResolverRequest> requests) {
|
public List<TaxationResolverResponse> getTariffRates(List<TaxationResolverRequest> requests) {
|
||||||
Map<TaxationResolverRequest, List<GoodsMeasForWsResponse>> goodMeasures = doRequests(requests);
|
var goodMeasures = doRequests(requests);
|
||||||
return goodMeasures.keySet().stream().map(r -> mapToResponse(r, goodMeasures.get(r))).toList();
|
return goodMeasures.keySet().stream().map(r -> mapToResponse(r, goodMeasures.get(r))).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private TaxationResolverResponse mapToResponse(TaxationResolverRequest request, List<GoodsMeasForWsResponse> measForWsResponse) {
|
private TaxationResolverResponse mapToResponse(TaxationResolverRequest request, TaxationResolverApiResponse apiResponse) {
|
||||||
|
|
||||||
|
// source is EU country.
|
||||||
|
if(!apiResponse.requestExecuted)
|
||||||
|
return new TaxationResolverResponse(0.0, null, request.material().getHsCode(), request.material(), request.countryId());
|
||||||
|
|
||||||
|
|
||||||
|
List<GoodsMeasForWsResponse> measForWsResponse = apiResponse.apiResponse();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String selectedHsCode = null;
|
String selectedHsCode = null;
|
||||||
|
|
@ -249,7 +293,7 @@ public class TaxationResolverService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedDuty != null && (maxDuty - minDuty < 2)) {
|
if (selectedDuty != null && (maxDuty - minDuty < 0.02)) {
|
||||||
return new TaxationResolverResponse(selectedDuty, selectedMeasure, selectedHsCode, request.material(), request.countryId());
|
return new TaxationResolverResponse(selectedDuty, selectedMeasure, selectedHsCode, request.material(), request.countryId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,6 +312,10 @@ public class TaxationResolverService {
|
||||||
public record TaxationResolverRequest(Material material, Integer countryId) {
|
public record TaxationResolverRequest(Material material, Integer countryId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record TaxationResolverApiResponse(Material material, boolean requestExecuted,
|
||||||
|
List<GoodsMeasForWsResponse> apiResponse) {
|
||||||
|
}
|
||||||
|
|
||||||
public record TaxationResolverResponse(@Nullable Double tariffRate,
|
public record TaxationResolverResponse(@Nullable Double tariffRate,
|
||||||
@Nullable String measureCode,
|
@Nullable String measureCode,
|
||||||
@Nullable String actualHsCode,
|
@Nullable String actualHsCode,
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ public class BulkOperationExecutionService {
|
||||||
try {
|
try {
|
||||||
execution(id);
|
execution(id);
|
||||||
return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
} catch (Exception e) {
|
} catch (Throwable e) {
|
||||||
bulkOperationRepository.updateState(id, BulkOperationState.EXCEPTION);
|
bulkOperationRepository.updateState(id, BulkOperationState.EXCEPTION);
|
||||||
|
|
||||||
var error = new SysError();
|
var error = new SysError();
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ public class SysErrorTransformer {
|
||||||
entity.setMessage(frontendErrorDTO.getError().getMessage());
|
entity.setMessage(frontendErrorDTO.getError().getMessage());
|
||||||
entity.setTitle(frontendErrorDTO.getError().getTitle());
|
entity.setTitle(frontendErrorDTO.getError().getTitle());
|
||||||
entity.setPinia(frontendErrorDTO.getState());
|
entity.setPinia(frontendErrorDTO.getState());
|
||||||
|
entity.setRequest(frontendErrorDTO.getRequest());
|
||||||
entity.setUserId(userId);
|
entity.setUserId(userId);
|
||||||
|
|
||||||
var traceItems = frontendErrorDTO.getError().getTrace();
|
var traceItems = frontendErrorDTO.getError().getTrace();
|
||||||
|
|
@ -107,6 +108,7 @@ public class SysErrorTransformer {
|
||||||
dto.setType(sysError.getType().name());
|
dto.setType(sysError.getType().name());
|
||||||
dto.setTrace(sysError.getTrace().stream().map(this::toSysErrorTraceItemDto).toList());
|
dto.setTrace(sysError.getTrace().stream().map(this::toSysErrorTraceItemDto).toList());
|
||||||
dto.setCreatedAt(sysError.getCreatedAt());
|
dto.setCreatedAt(sysError.getCreatedAt());
|
||||||
|
dto.setRequest(sysError.getRequest());
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -637,6 +637,7 @@ CREATE TABLE IF NOT EXISTS sys_error
|
||||||
title VARCHAR(255) NOT NULL,
|
title VARCHAR(255) NOT NULL,
|
||||||
code VARCHAR(255) NOT NULL,
|
code VARCHAR(255) NOT NULL,
|
||||||
message VARCHAR(1024) NOT NULL,
|
message VARCHAR(1024) NOT NULL,
|
||||||
|
request TEXT,
|
||||||
pinia TEXT,
|
pinia TEXT,
|
||||||
calculation_job_id INT DEFAULT NULL,
|
calculation_job_id INT DEFAULT NULL,
|
||||||
bulk_operation_id INT DEFAULT NULL,
|
bulk_operation_id INT DEFAULT NULL,
|
||||||
|
|
|
||||||
|
|
@ -637,6 +637,7 @@ CREATE TABLE IF NOT EXISTS sys_error
|
||||||
title VARCHAR(255) NOT NULL,
|
title VARCHAR(255) NOT NULL,
|
||||||
code VARCHAR(255) NOT NULL,
|
code VARCHAR(255) NOT NULL,
|
||||||
message VARCHAR(1024) NOT NULL,
|
message VARCHAR(1024) NOT NULL,
|
||||||
|
request TEXT,
|
||||||
pinia TEXT,
|
pinia TEXT,
|
||||||
calculation_job_id INT DEFAULT NULL,
|
calculation_job_id INT DEFAULT NULL,
|
||||||
bulk_operation_id INT DEFAULT NULL,
|
bulk_operation_id INT DEFAULT NULL,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue