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