Removed unused components, services, and database schema related to nomenclature and custom tariff handling, seperated mass edit and single edit in frontend:
- **Frontend**: Deleted `SelectNode.vue` component and related styles. - **Backend**: Removed `CustomController`, `ChangeMaterialService`, `ChangeSupplierService`, `NomenclatureService`, `NomenclatureRepository`, and unused DTOs. - **Database**: Dropped `nomenclature` table and eliminated associated migration script. - **Other**: Cleaned up imports and references to the removed implementations throughout the codebase.
This commit is contained in:
parent
b99e7b3b4f
commit
305033e8f0
38 changed files with 981 additions and 18010 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container" :class="massEditClasses">
|
<div class="container">
|
||||||
<div class="search-bar-container">
|
<div class="search-bar-container">
|
||||||
<autosuggest-searchbar class="search-bar"
|
<autosuggest-searchbar class="search-bar"
|
||||||
placeholder="Add new Destination ..."
|
placeholder="Add new Destination ..."
|
||||||
|
|
@ -23,8 +23,6 @@
|
||||||
<div>Action</div>
|
<div>Action</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- TODO: straight this up -->
|
|
||||||
<div v-if="showDestinationsList">
|
<div v-if="showDestinationsList">
|
||||||
<destination-item v-for="destination in destinations" :key="destination.id"
|
<destination-item v-for="destination in destinations" :key="destination.id"
|
||||||
:id="destination.id" :destination="destination" @delete="deleteDestination"
|
:id="destination.id" :destination="destination" @delete="deleteDestination"
|
||||||
|
|
@ -37,7 +35,8 @@
|
||||||
</transition-group>
|
</transition-group>
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<modal :state="editDestinationModalState">
|
<modal :state="editDestinationModalState">
|
||||||
<destination-edit @accept="deselectDestination(true)" @discard="deselectDestination(false)"></destination-edit>
|
<destination-edit @accept="editDestinationFinish(true)"
|
||||||
|
@discard="editDestinationFinish(false)"></destination-edit>
|
||||||
</modal>
|
</modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -53,11 +52,12 @@ import Checkbox from "@/components/UI/Checkbox.vue";
|
||||||
import CalculationListItem from "@/components/layout/calculation/CalculationListItem.vue";
|
import CalculationListItem from "@/components/layout/calculation/CalculationListItem.vue";
|
||||||
import DestinationItem from "@/components/layout/edit/destination/DestinationItem.vue";
|
import DestinationItem from "@/components/layout/edit/destination/DestinationItem.vue";
|
||||||
import {mapStores} from "pinia";
|
import {mapStores} from "pinia";
|
||||||
import {usePremiseEditStore} from "@/store/premiseEdit.js";
|
|
||||||
import {useNodeStore} from "@/store/node.js";
|
import {useNodeStore} from "@/store/node.js";
|
||||||
import Modal from "@/components/UI/Modal.vue";
|
import Modal from "@/components/UI/Modal.vue";
|
||||||
import DestinationEdit from "@/components/layout/edit/destination/DestinationEdit.vue";
|
import DestinationEdit from "@/components/layout/edit/destination/DestinationEdit.vue";
|
||||||
import {UrlSafeBase64} from "@/common.js";
|
import {usePremiseSingleEditStore} from "@/store/premiseSingleEdit.js";
|
||||||
|
import logger from "@/logger.js";
|
||||||
|
import {useDestinationSingleEditStore} from "@/store/destinationSingleEdit.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "DestinationListView",
|
name: "DestinationListView",
|
||||||
|
|
@ -72,12 +72,9 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
massEditClasses() {
|
...mapStores(usePremiseSingleEditStore, useDestinationSingleEditStore, useNodeStore),
|
||||||
return this.premiseEditStore.isSingleSelect ? '' : 'container--mass-edit';
|
|
||||||
},
|
|
||||||
...mapStores(usePremiseEditStore, useNodeStore),
|
|
||||||
destinations() {
|
destinations() {
|
||||||
return this.premiseEditStore.getDestinationsView;
|
return this.premiseSingleEditStore.getDestinations;
|
||||||
},
|
},
|
||||||
showDestinationsList() {
|
showDestinationsList() {
|
||||||
return this.destinations !== null && this.destinations.length > 0;
|
return this.destinations !== null && this.destinations.length > 0;
|
||||||
|
|
@ -92,22 +89,35 @@ export default {
|
||||||
return node.country.iso_code;
|
return node.country.iso_code;
|
||||||
},
|
},
|
||||||
async addDestination(node) {
|
async addDestination(node) {
|
||||||
console.log(node)
|
logger.log(node)
|
||||||
const [id] = await this.premiseEditStore.addDestination(node);
|
const [id] = await this.premiseSingleEditStore.addDestination(node);
|
||||||
this.editDestination(id);
|
this.editDestination(id);
|
||||||
},
|
},
|
||||||
deleteDestination(id) {
|
async deleteDestination(id) {
|
||||||
this.premiseEditStore.deleteDestination(id);
|
this.premiseSingleEditStore.deleteDestination(id);
|
||||||
},
|
},
|
||||||
editDestination(id) {
|
editDestination(id) {
|
||||||
// TODO refactor.
|
|
||||||
if (id && this.premiseEditStore.getDestinationById(id) !== null) {
|
logger.log(id);
|
||||||
this.premiseEditStore.selectDestination(id);
|
|
||||||
|
if (id) {
|
||||||
|
const destination = this.premiseSingleEditStore.getDestinationById(id);
|
||||||
|
logger.log(destination);
|
||||||
|
this.destinationSingleEditStore.setDestination(destination);
|
||||||
this.editDestinationModalState = true;
|
this.editDestinationModalState = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deselectDestination(save) {
|
async editDestinationFinish(save) {
|
||||||
this.premiseEditStore.deselectDestinations(save);
|
|
||||||
|
if (save) {
|
||||||
|
|
||||||
|
const dest = this.premiseSingleEditStore.getDestinationById(this.destinationSingleEditStore.destination.id);
|
||||||
|
|
||||||
|
logger.log("id", this.destinationSingleEditStore.destination.id, "dest", dest);
|
||||||
|
this.destinationSingleEditStore.copyBack(dest);
|
||||||
|
await this.premiseSingleEditStore.updateDestination(this.destinationSingleEditStore.destination.id)
|
||||||
|
}
|
||||||
|
|
||||||
this.editDestinationModalState = false;
|
this.editDestinationModalState = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -132,10 +142,6 @@ export default {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container--mass-edit {
|
|
||||||
width: min(80vw, 120rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar-container {
|
.search-bar-container {
|
||||||
|
|
||||||
margin: 3rem 3rem 0 3rem;
|
margin: 3rem 3rem 0 3rem;
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,6 @@
|
||||||
<div class="supplier-map">
|
<div class="supplier-map">
|
||||||
<open-street-map-embed :coordinates="supplierCoordinates" :zoom="15" width="100%" height="300px" custom-filter="grayscale(0.8) sepia(0.5) hue-rotate(180deg) saturate(0.5) brightness(1.0)"></open-street-map-embed>
|
<open-street-map-embed :coordinates="supplierCoordinates" :zoom="15" width="100%" height="300px" 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="footer">
|
|
||||||
<modal :state="selectSupplierModalState" @close="closeEditModal">
|
|
||||||
<select-node @update-supplier="modalDialogClose"></select-node>
|
|
||||||
</modal>
|
|
||||||
<!-- <icon-button icon="plus" @click="openModal"></icon-button>-->
|
|
||||||
<!-- <icon-button icon="pencil-simple" @click="openModal"></icon-button>-->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -35,14 +28,12 @@ import InputField from "@/components/UI/InputField.vue";
|
||||||
import Flag from "@/components/UI/Flag.vue";
|
import Flag from "@/components/UI/Flag.vue";
|
||||||
import {PhUser} from "@phosphor-icons/vue";
|
import {PhUser} from "@phosphor-icons/vue";
|
||||||
import ModalDialog from "@/components/UI/ModalDialog.vue";
|
import ModalDialog from "@/components/UI/ModalDialog.vue";
|
||||||
import SelectNode from "@/components/layout/node/SelectNode.vue";
|
|
||||||
import Modal from "@/components/UI/Modal.vue";
|
import Modal from "@/components/UI/Modal.vue";
|
||||||
import OpenStreetMapEmbed from "@/components/UI/OpenStreetMapEmbed.vue";
|
import OpenStreetMapEmbed from "@/components/UI/OpenStreetMapEmbed.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SupplierView",
|
name: "SupplierView",
|
||||||
components: { OpenStreetMapEmbed, Modal, SelectNode, ModalDialog, PhUser, Flag, InputField, IconButton},
|
components: { OpenStreetMapEmbed, Modal, ModalDialog, PhUser, Flag, InputField, IconButton},
|
||||||
emits: ['updateSupplier'],
|
|
||||||
props: {
|
props: {
|
||||||
supplierName: {
|
supplierName: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -87,18 +78,6 @@ export default {
|
||||||
this.selectSupplierModalState = this.openSelectDirect;
|
this.selectSupplierModalState = this.openSelectDirect;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
closeEditModal() {
|
|
||||||
this.selectSupplierModalState = false;
|
|
||||||
},
|
|
||||||
modalDialogClose(data) {
|
|
||||||
this.selectSupplierModalState = false;
|
|
||||||
if (data.action === 'accept') {
|
|
||||||
this.$emit('updateSupplier', data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openModal() {
|
|
||||||
this.selectSupplierModalState = true;
|
|
||||||
},
|
|
||||||
convertToDMS(coordinate, type) {
|
convertToDMS(coordinate, type) {
|
||||||
|
|
||||||
if (!coordinate)
|
if (!coordinate)
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ import {mapStores} from "pinia";
|
||||||
import {usePremiseEditStore} from "@/store/premiseEdit.js";
|
import {usePremiseEditStore} from "@/store/premiseEdit.js";
|
||||||
import {set} from "@vueuse/core";
|
import {set} from "@vueuse/core";
|
||||||
import {parseNumberFromString} from "@/common.js";
|
import {parseNumberFromString} from "@/common.js";
|
||||||
|
import {useDestinationSingleEditStore} from "@/store/destinationSingleEdit.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "DestinationEditHandlingCost",
|
name: "DestinationEditHandlingCost",
|
||||||
|
|
@ -52,9 +53,9 @@ export default {
|
||||||
console.log("Destination:", this.destination)
|
console.log("Destination:", this.destination)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapStores(usePremiseEditStore),
|
...mapStores(useDestinationSingleEditStore),
|
||||||
destination() {
|
destination() {
|
||||||
return this.premiseEditStore.getSelectedDestinationsData;
|
return this.destinationSingleEditStore.destination;
|
||||||
},
|
},
|
||||||
repackaging: {
|
repackaging: {
|
||||||
get() {
|
get() {
|
||||||
|
|
|
||||||
|
|
@ -78,9 +78,9 @@
|
||||||
import RadioOption from "@/components/UI/RadioOption.vue";
|
import RadioOption from "@/components/UI/RadioOption.vue";
|
||||||
import DestinationRoute from "@/components/layout/edit/destination/DestinationRoute.vue";
|
import DestinationRoute from "@/components/layout/edit/destination/DestinationRoute.vue";
|
||||||
import {mapStores} from "pinia";
|
import {mapStores} from "pinia";
|
||||||
import {usePremiseEditStore} from "@/store/premiseEdit.js";
|
|
||||||
import {parseNumberFromString} from "@/common.js";
|
import {parseNumberFromString} from "@/common.js";
|
||||||
import Tooltip from "@/components/UI/Tooltip.vue";
|
import Tooltip from "@/components/UI/Tooltip.vue";
|
||||||
|
import {useDestinationSingleEditStore} from "@/store/destinationSingleEdit.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "DestinationEditRoutes",
|
name: "DestinationEditRoutes",
|
||||||
|
|
@ -149,9 +149,9 @@ export default {
|
||||||
this.destination.is_d2d = value === 'd2d';
|
this.destination.is_d2d = value === 'd2d';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...mapStores(usePremiseEditStore),
|
...mapStores(useDestinationSingleEditStore),
|
||||||
destination() {
|
destination() {
|
||||||
return this.premiseEditStore.getSelectedDestinationsData;
|
return this.destinationSingleEditStore.destination;
|
||||||
},
|
},
|
||||||
annualAmount: {
|
annualAmount: {
|
||||||
get() {
|
get() {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
<div class="error-section">
|
<div class="error-section">
|
||||||
<div class="error-label">Type</div>
|
<div class="error-label">Type</div>
|
||||||
<div class="error-value">
|
<div class="error-value">
|
||||||
<basic-badge :variant="badgeVariant">{{ error.type || 'UNKNOWN' }}</basic-badge>
|
<basic-badge :icon="badgeIcon" :variant="badgeVariant">{{ error.type || 'UNKNOWN' }}</basic-badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -122,6 +122,18 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
badgeIcon() {
|
||||||
|
if (this.error.type === "FRONTEND") {
|
||||||
|
return "desktop";
|
||||||
|
} else if (this.error.type === "BACKEND") {
|
||||||
|
return "hardDrives"
|
||||||
|
} else if (this.error.type === "BULK") {
|
||||||
|
return "stack"
|
||||||
|
} else if (this.error.type === "CALCULATION") {
|
||||||
|
return "calculator"
|
||||||
|
} else
|
||||||
|
return ""
|
||||||
|
},
|
||||||
badgeVariant() {
|
badgeVariant() {
|
||||||
switch (this.error.type) {
|
switch (this.error.type) {
|
||||||
case "FRONTEND":
|
case "FRONTEND":
|
||||||
|
|
@ -131,7 +143,6 @@ export default {
|
||||||
default:
|
default:
|
||||||
return "grey";
|
return "grey";
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
formattedTimestamp() {
|
formattedTimestamp() {
|
||||||
if (!this.error.timestamp) return 'N/A';
|
if (!this.error.timestamp) return 'N/A';
|
||||||
|
|
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="select-node-modal-container">
|
|
||||||
<h3 class="sub-header">Select supplier</h3>
|
|
||||||
<div class="select-node-container">
|
|
||||||
<div class="select-node-caption-column">Supplier</div>
|
|
||||||
<div class="select-node-input-column select-node-input-field-suppliername">
|
|
||||||
<div class="select-node-input-field-suppliername-searchbar">
|
|
||||||
<autosuggest-searchbar :fetch-suggestions="fetchSupplier"
|
|
||||||
placeholder="Find supplier..."
|
|
||||||
title-resolver="name"
|
|
||||||
subtitle-resolver="address"
|
|
||||||
:flag-resolver="resolveFlag"
|
|
||||||
variant="flags"
|
|
||||||
@selected="selected"
|
|
||||||
ref="searchbar"
|
|
||||||
:initial-value="initialValue"
|
|
||||||
:activate-watcher="true"
|
|
||||||
>
|
|
||||||
</autosuggest-searchbar>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="select-node-caption-column" v-if="nodeSelected">Address</div>
|
|
||||||
<div class="select-node-input-column select-node-input-field-address" v-if="nodeSelected">
|
|
||||||
<div class="supplier-flag">
|
|
||||||
<flag :iso="isoCode"/>
|
|
||||||
</div>
|
|
||||||
<div class="supplier-address">{{ supplierAddress }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="select-node-caption-column" v-if="nodeSelected">Coordinates</div>
|
|
||||||
<div class="select-node-input-column" v-if="nodeSelected">{{ coordinatesDMS }}</div>
|
|
||||||
<div class="select-node-checkbox" v-if="nodeSelected">
|
|
||||||
<checkbox :checked="updateMasterData" @checkbox-changed="checkboxChanged">update master data</checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="select-node-footer">
|
|
||||||
<basic-button :show-icon="false" :disabled="!nodeSelected" @click="action('accept')">OK</basic-button>
|
|
||||||
<basic-button variant="secondary" :show-icon="false" @click="action('cancel')">Cancel</basic-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import BasicButton from "@/components/UI/BasicButton.vue";
|
|
||||||
import InputField from "@/components/UI/InputField.vue";
|
|
||||||
import Flag from "@/components/UI/Flag.vue";
|
|
||||||
import AutosuggestSearchbar from "@/components/UI/AutoSuggestSearchBar.vue";
|
|
||||||
import {mapStores} from "pinia";
|
|
||||||
import {useNodeStore} from "@/store/node.js";
|
|
||||||
import Checkbox from "@/components/UI/Checkbox.vue";
|
|
||||||
import logger from "@/logger.js";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "SelectNode",
|
|
||||||
components: {Checkbox, AutosuggestSearchbar, Flag, InputField, BasicButton},
|
|
||||||
emits: ['updateSupplier'],
|
|
||||||
props: {
|
|
||||||
openSelectDirect: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
preSelectedNode: {
|
|
||||||
type: Object,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
logger.info("SelectNode created with openSelectDirect: " + this.openSelectDirect, this.preSelectedNode);
|
|
||||||
if(this.openSelectDirect) {
|
|
||||||
this.node = this.preSelectedNode;
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
action(action) {
|
|
||||||
this.$emit('updateSupplier', {action: action, nodeId: this.node?.id, updateMasterData: this.updateMasterData});
|
|
||||||
},
|
|
||||||
checkboxChanged(value) {
|
|
||||||
this.updateMasterData = value;
|
|
||||||
},
|
|
||||||
async fetchSupplier(query) {
|
|
||||||
logger.info("Fetching supplier for query: " + query);
|
|
||||||
await this.nodeStore.setSearch({searchTerm: query, nodeType: 'SOURCE', includeUserNode: true});
|
|
||||||
return this.nodeStore.nodes;
|
|
||||||
},
|
|
||||||
resolveFlag(node) {
|
|
||||||
return node.country.iso_code;
|
|
||||||
},
|
|
||||||
selected(node) {
|
|
||||||
logger.info("Selected node: ", node);
|
|
||||||
this.$refs.searchbar.clearSuggestions();
|
|
||||||
this.node = node;
|
|
||||||
},
|
|
||||||
convertToDMS(coordinate, type) {
|
|
||||||
|
|
||||||
if (!coordinate)
|
|
||||||
return '';
|
|
||||||
|
|
||||||
let direction;
|
|
||||||
if (type === 'lat') {
|
|
||||||
direction = coordinate >= 0 ? 'N' : 'S';
|
|
||||||
} else {
|
|
||||||
direction = coordinate >= 0 ? 'E' : 'W';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arbeite mit Absolutwert
|
|
||||||
const abs = Math.abs(coordinate);
|
|
||||||
|
|
||||||
// Grad (ganzzahliger Teil)
|
|
||||||
const degrees = Math.floor(abs);
|
|
||||||
|
|
||||||
// Minuten
|
|
||||||
const minutesFloat = (abs - degrees) * 60;
|
|
||||||
const minutes = minutesFloat.toFixed(4);
|
|
||||||
|
|
||||||
|
|
||||||
return `${degrees}° ${minutes}' ${direction}`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
node: null,
|
|
||||||
updateMasterData: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useNodeStore),
|
|
||||||
initialValue() {
|
|
||||||
if(this.node) {
|
|
||||||
return this.node.name;
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
supplierAddress() {
|
|
||||||
return this.node?.address ?? '';
|
|
||||||
},
|
|
||||||
nodeSelected() {
|
|
||||||
return this.node != null;
|
|
||||||
},
|
|
||||||
isoCode() {
|
|
||||||
return this.node?.country.iso_code ?? 'DE';
|
|
||||||
},
|
|
||||||
coordinatesDMS() {
|
|
||||||
return `${this.convertToDMS(this.node?.location?.latitude, 'lat')}, ${this.convertToDMS(this.node?.location?.longitude, 'lng')}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
.select-node-input-column {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
color: #6b7280;
|
|
||||||
max-width: 30rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-node-input-field-suppliername {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.8rem;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-node-input-field-suppliername-searchbar {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
min-width: 50rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-node-modal-container {
|
|
||||||
width: 60rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-node-container {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto 1fr;
|
|
||||||
grid-template-rows: repeat(3, fit-content(0));
|
|
||||||
align-content: center;
|
|
||||||
gap: 1.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-node-caption-column {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
font-weight: 500;
|
|
||||||
align-self: center;
|
|
||||||
justify-self: end;
|
|
||||||
color: #001D33
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-node-footer {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 1.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-node-checkbox {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 1.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-node-input-field-address {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.8rem;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.supplier-address {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
color: #6b7280;
|
|
||||||
max-width: 30rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
@ -156,7 +156,7 @@
|
||||||
<report-route :sections="premise.sections" :destination="premise.destination"
|
<report-route :sections="premise.sections" :destination="premise.destination"
|
||||||
:route-section-scale="routeSectionScale[idx]"></report-route>
|
:route-section-scale="routeSectionScale[idx]"></report-route>
|
||||||
|
|
||||||
<div class="report-sub-header">Premises</div>
|
<div class="report-sub-header">General</div>
|
||||||
|
|
||||||
<div class="report-content-container--2-col">
|
<div class="report-content-container--2-col">
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,6 @@ import PriceEdit from "@/components/layout/edit/PriceEdit.vue";
|
||||||
import MaterialEdit from "@/components/layout/edit/MaterialEdit.vue";
|
import MaterialEdit from "@/components/layout/edit/MaterialEdit.vue";
|
||||||
import PackagingEdit from "@/components/layout/edit/PackagingEdit.vue";
|
import PackagingEdit from "@/components/layout/edit/PackagingEdit.vue";
|
||||||
import DestinationListView from "@/components/layout/edit/DestinationListView.vue";
|
import DestinationListView from "@/components/layout/edit/DestinationListView.vue";
|
||||||
import SelectNode from "@/components/layout/node/SelectNode.vue";
|
|
||||||
import Toast from "@/components/UI/Toast.vue";
|
import Toast from "@/components/UI/Toast.vue";
|
||||||
import logger from "@/logger.js";
|
import logger from "@/logger.js";
|
||||||
import {useCustomsStore} from "@/store/customs.js";
|
import {useCustomsStore} from "@/store/customs.js";
|
||||||
|
|
@ -131,7 +130,6 @@ const COMPONENT_TYPES = {
|
||||||
price: PriceEdit,
|
price: PriceEdit,
|
||||||
material: MaterialEdit,
|
material: MaterialEdit,
|
||||||
packaging: PackagingEdit,
|
packaging: PackagingEdit,
|
||||||
supplier: SelectNode,
|
|
||||||
destinations: DestinationListView,
|
destinations: DestinationListView,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -414,11 +412,6 @@ export default {
|
||||||
mixable: premise.is_mixable ?? true,
|
mixable: premise.is_mixable ?? true,
|
||||||
stackable: premise.is_stackable ?? true
|
stackable: premise.is_stackable ?? true
|
||||||
}
|
}
|
||||||
} else if (type === "supplier") {
|
|
||||||
this.componentsData.supplier.props = {
|
|
||||||
preSelectedNode: premise.supplier,
|
|
||||||
openSelectDirect: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@
|
||||||
<div class="header-container">
|
<div class="header-container">
|
||||||
<h2 class="page-header">Edit calculation</h2>
|
<h2 class="page-header">Edit calculation</h2>
|
||||||
<div class="header-controls">
|
<div class="header-controls">
|
||||||
<basic-button @click="close" :show-icon="false" :disabled="premiseEditStore.selectedLoading"
|
<basic-button @click="close" :show-icon="false" :disabled="premiseSingleEditStore.showLoadingSpinner"
|
||||||
variant="secondary"> {{ fromMassEdit ? 'Back' : 'Close' }}
|
variant="secondary"> {{ fromMassEdit ? 'Back' : 'Close' }}
|
||||||
</basic-button>
|
</basic-button>
|
||||||
<basic-button v-if="!fromMassEdit"
|
<basic-button v-if="!fromMassEdit"
|
||||||
:show-icon="true"
|
:show-icon="true"
|
||||||
:disabled="premiseEditStore.selectedLoading || !premiseEditStore.isSingleSelect"
|
:disabled="premiseSingleEditStore.showLoadingSpinner || premiseSingleEditStore.isEmpty"
|
||||||
icon="Calculator"
|
icon="Calculator"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@click="startCalculation"
|
@click="startCalculation"
|
||||||
|
|
@ -19,12 +19,12 @@
|
||||||
</div>
|
</div>
|
||||||
<Toast ref="toast"/>
|
<Toast ref="toast"/>
|
||||||
|
|
||||||
<div v-if="premiseEditStore.selectedLoading" 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">
|
||||||
<spinner></spinner>
|
<spinner></spinner>
|
||||||
</box>
|
</box>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="!premiseEditStore.isSingleSelect" class="edit-calculation-spinner-container">
|
<div v-else-if="premiseSingleEditStore.isEmpty" class="edit-calculation-spinner-container">
|
||||||
<box class="edit-calculation-spinner">No calculation found.</box>
|
<box class="edit-calculation-spinner">No calculation found.</box>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
|
@ -36,7 +36,6 @@
|
||||||
:supplier-name="premise.supplier.name"
|
:supplier-name="premise.supplier.name"
|
||||||
:supplier-coordinates="premise.supplier.location"
|
:supplier-coordinates="premise.supplier.location"
|
||||||
:iso-code="premise.supplier.country.iso_code"
|
:iso-code="premise.supplier.country.iso_code"
|
||||||
@update-supplier="updateSupplier"
|
|
||||||
></supplier-view>
|
></supplier-view>
|
||||||
</box>
|
</box>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -61,7 +60,6 @@
|
||||||
v-model:hs-code="premise.hs_code"
|
v-model:hs-code="premise.hs_code"
|
||||||
v-model:tariff-rate="premise.tariff_rate"
|
v-model:tariff-rate="premise.tariff_rate"
|
||||||
v-model:country-id="premise.supplier.country.id"
|
v-model:country-id="premise.supplier.country.id"
|
||||||
@update-material="updateMaterial"
|
|
||||||
@save="save"></material-edit>
|
@save="save"></material-edit>
|
||||||
</box>
|
</box>
|
||||||
<box class="master-data-item">
|
<box class="master-data-item">
|
||||||
|
|
@ -108,7 +106,6 @@ import PackagingEdit from "@/components/layout/edit/PackagingEdit.vue";
|
||||||
import PriceEdit from "@/components/layout/edit/PriceEdit.vue";
|
import PriceEdit from "@/components/layout/edit/PriceEdit.vue";
|
||||||
import DestinationListView from "@/components/layout/edit/DestinationListView.vue";
|
import DestinationListView from "@/components/layout/edit/DestinationListView.vue";
|
||||||
import {mapStores} from "pinia";
|
import {mapStores} from "pinia";
|
||||||
import {usePremiseEditStore} from "@/store/premiseEdit.js";
|
|
||||||
import Spinner from "@/components/UI/Spinner.vue";
|
import Spinner from "@/components/UI/Spinner.vue";
|
||||||
import NotificationBar from "@/components/UI/NotificationBar.vue";
|
import NotificationBar from "@/components/UI/NotificationBar.vue";
|
||||||
import Modal from "@/components/UI/Modal.vue";
|
import Modal from "@/components/UI/Modal.vue";
|
||||||
|
|
@ -116,7 +113,7 @@ 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 Toast from "@/components/UI/Toast.vue";
|
||||||
import {UrlSafeBase64} from "@/common.js";
|
import {UrlSafeBase64} from "@/common.js";
|
||||||
import {useCustomsStore} from "@/store/customs.js";
|
import {usePremiseSingleEditStore} from "@/store/premiseSingleEdit.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SingleEdit",
|
name: "SingleEdit",
|
||||||
|
|
@ -140,26 +137,25 @@ export default {
|
||||||
traceModal: false,
|
traceModal: false,
|
||||||
bulkEditQuery: null,
|
bulkEditQuery: null,
|
||||||
id: null,
|
id: null,
|
||||||
processingMessage: "Please wait. Calculating ...",
|
|
||||||
showCalculationModal: false,
|
showCalculationModal: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapStores(usePremiseEditStore, useCustomsStore),
|
...mapStores(usePremiseSingleEditStore),
|
||||||
premise() {
|
premise() {
|
||||||
return this.premiseEditStore.singleSelectedPremise;
|
return this.premiseSingleEditStore.premise;
|
||||||
},
|
},
|
||||||
fromMassEdit() {
|
fromMassEdit() {
|
||||||
return this.bulkEditQuery !== null;
|
return this.bulkEditQuery !== null;
|
||||||
},
|
},
|
||||||
showProcessingModal() {
|
showProcessingModal() {
|
||||||
return this.premiseEditStore.showProcessingModal || this.showCalculationModal || this.customsStore.loadingTariff;
|
return this.premiseSingleEditStore.showProcessingModal || this.showCalculationModal;
|
||||||
},
|
},
|
||||||
shownProcessingMessage() {
|
shownProcessingMessage() {
|
||||||
if(this.customsStore.loadingTariff)
|
if (this.premiseSingleEditStore.routing)
|
||||||
return "Looking up tariff rate ..."
|
return "Please wait. Routing ..."
|
||||||
|
|
||||||
return this.processingMessage;
|
return "Please wait. Calculating ...";
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -167,7 +163,7 @@ export default {
|
||||||
|
|
||||||
async startCalculation() {
|
async startCalculation() {
|
||||||
this.showCalculationModal = true;
|
this.showCalculationModal = true;
|
||||||
const error = await this.premiseEditStore.startCalculation();
|
const error = await this.premiseSingleEditStore.startCalculation();
|
||||||
|
|
||||||
if (error !== null) {
|
if (error !== null) {
|
||||||
|
|
||||||
|
|
@ -199,7 +195,7 @@ export default {
|
||||||
close() {
|
close() {
|
||||||
if (this.bulkEditQuery) {
|
if (this.bulkEditQuery) {
|
||||||
//TODO: deselect and save
|
//TODO: deselect and save
|
||||||
this.premiseEditStore.deselectPremise();
|
// this.premiseEditStore.deselectPremise();
|
||||||
this.$router.push({name: 'bulk', params: {ids: this.bulkEditQuery}});
|
this.$router.push({name: 'bulk', params: {ids: this.bulkEditQuery}});
|
||||||
} else {
|
} else {
|
||||||
//TODO: deselect and save
|
//TODO: deselect and save
|
||||||
|
|
@ -210,20 +206,22 @@ export default {
|
||||||
let success = false;
|
let success = false;
|
||||||
|
|
||||||
if (type === 'price') {
|
if (type === 'price') {
|
||||||
success = await this.premiseEditStore.savePrice();
|
success = await this.premiseSingleEditStore.savePrice();
|
||||||
} else if (type === 'material') {
|
} else if (type === 'material') {
|
||||||
success = await this.premiseEditStore.saveMaterial();
|
success = await this.premiseSingleEditStore.saveMaterial();
|
||||||
} else if (type === 'packaging') {
|
} else if (type === 'packaging') {
|
||||||
success = await this.premiseEditStore.savePackaging();
|
success = await this.premiseSingleEditStore.savePackaging();
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
if (!success)
|
||||||
updateMaterial(id, action) {
|
this.$refs.toast.addToast({
|
||||||
console.log(id, action);
|
icon: 'warning',
|
||||||
this.premiseEditStore.setMaterial(id, action === 'updateMasterData');
|
message: "Failed to save data.",
|
||||||
},
|
title: "Error saving",
|
||||||
updateSupplier(data) {
|
variant: 'exception',
|
||||||
this.premiseEditStore.setSupplier(data.nodeId, data.updateMasterData);
|
duration: 8000
|
||||||
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
trace() {
|
trace() {
|
||||||
this.traceModal = true;
|
this.traceModal = true;
|
||||||
|
|
@ -232,12 +230,11 @@ export default {
|
||||||
created() {
|
created() {
|
||||||
[this.id] = new UrlSafeBase64().decodeIds(this.$route.params.id);
|
[this.id] = new UrlSafeBase64().decodeIds(this.$route.params.id);
|
||||||
|
|
||||||
if (this.$route.params.ids) {
|
if (this.$route.params.ids)
|
||||||
this.bulkEditQuery = this.$route.params.ids;
|
this.bulkEditQuery = this.$route.params.ids;
|
||||||
this.premiseEditStore.selectSinglePremise(this.id, new UrlSafeBase64().decodeIds(this.$route.params.ids));
|
|
||||||
} else {
|
this.premiseSingleEditStore.load(this.id)
|
||||||
this.premiseEditStore.loadAndSelectSinglePremise(this.id)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
56
src/frontend/src/store/destinationSingleEdit.js
Normal file
56
src/frontend/src/store/destinationSingleEdit.js
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import {defineStore} from 'pinia'
|
||||||
|
import {toRaw} from "vue";
|
||||||
|
|
||||||
|
export const useDestinationSingleEditStore = defineStore('destinationSingleEdit', {
|
||||||
|
state: () => ({
|
||||||
|
destination: null,
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
setDestination(from) {
|
||||||
|
|
||||||
|
const temp = {};
|
||||||
|
|
||||||
|
temp.id = `${from.id}`;
|
||||||
|
temp.destination_node = structuredClone(toRaw(from.destination_node));
|
||||||
|
temp.routes = structuredClone(toRaw(from.routes));
|
||||||
|
|
||||||
|
temp.annual_amount = from.annual_amount;
|
||||||
|
temp.is_d2d = from.is_d2d;
|
||||||
|
temp.rate_d2d = from.is_d2d ? from.rate_d2d : null;
|
||||||
|
temp.lead_time_d2d = from.is_d2d ? from.lead_time_d2d : null;
|
||||||
|
temp.handling_costs = from.handling_costs;
|
||||||
|
temp.disposal_costs = from.disposal_costs;
|
||||||
|
temp.repackaging_costs = from.repackaging_costs;
|
||||||
|
temp.userDefinedHandlingCosts = from.handling_costs !== null || from.disposal_costs !== null || from.repackaging_costs !== null;
|
||||||
|
|
||||||
|
|
||||||
|
this.destination = temp;
|
||||||
|
},
|
||||||
|
copyBack(to) {
|
||||||
|
|
||||||
|
|
||||||
|
to.id = parseInt(this.destination.id);
|
||||||
|
|
||||||
|
to.annual_amount = this.destination.annual_amount;
|
||||||
|
to.is_d2d = this.destination.is_d2d;
|
||||||
|
to.rate_d2d = this.destination.is_d2d ? this.destination.rate_d2d : null;
|
||||||
|
to.lead_time_d2d = this.destination.is_d2d ? this.destination.lead_time_d2d : null;
|
||||||
|
|
||||||
|
if (this.destination.userDefinedHandlingCosts) {
|
||||||
|
to.disposal_costs = this.destination.disposal_costs;
|
||||||
|
to.repackaging_costs = this.destination.repackaging_costs;
|
||||||
|
to.handling_costs = this.destination.handling_costs;
|
||||||
|
} else {
|
||||||
|
to.disposal_costs = null;
|
||||||
|
to.repackaging_costs = null;
|
||||||
|
to.handling_costs = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this.destination.routes ?? null) !== null) {
|
||||||
|
to.routes.forEach(route => route.is_selected = this.destination.routes.find(r => r.id === route.id)?.is_selected ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -242,32 +242,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
||||||
return state.premisses?.find(p => p.selected);
|
return state.premisses?.find(p => p.selected);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Getters for destination editing
|
|
||||||
* ===============================
|
|
||||||
*/
|
|
||||||
|
|
||||||
getDestinationsView(state) {
|
|
||||||
return state.destinations?.destinations ?? [];
|
|
||||||
},
|
|
||||||
|
|
||||||
getDestinationById(state) {
|
|
||||||
return function (id) {
|
|
||||||
if (state.loading || state.selectedLoading) {
|
|
||||||
if (state.throwsException)
|
|
||||||
throw new Error("Premises are accessed while still loading.");
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const p of state.premisses) {
|
|
||||||
const d = p.destinations.find(d => d.id === id);
|
|
||||||
if ((d ?? null) !== null) return d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getSelectedDestinationsData: (state) => state.selectedDestination,
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
@ -658,59 +633,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set methods
|
|
||||||
* (these are more extensive changes. The edited premises are replaced by the one returned by the backend)
|
|
||||||
*/
|
|
||||||
|
|
||||||
async setSupplier(id, updateMasterData, ids = null) {
|
|
||||||
logger.info("setSupplier");
|
|
||||||
|
|
||||||
const selectedId = this.singleSelectId;
|
|
||||||
|
|
||||||
this.processDestinationMassEdit = true;
|
|
||||||
|
|
||||||
const body = {supplier_node_id: id, update_master_data: updateMasterData};
|
|
||||||
const url = `${config.backendUrl}/calculation/supplier/`;
|
|
||||||
await this.setData(url, body, ids);
|
|
||||||
|
|
||||||
if (selectedId != null && this.destinations && !this.destinations.fromMassEditView) {
|
|
||||||
this.prepareDestinations(selectedId, [selectedId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.processDestinationMassEdit = false;
|
|
||||||
|
|
||||||
},
|
|
||||||
async setMaterial(id, updateMasterData, ids = null) {
|
|
||||||
logger.info("setMaterial");
|
|
||||||
const body = {material_id: id, update_master_data: updateMasterData};
|
|
||||||
const url = `${config.backendUrl}/calculation/material/`;
|
|
||||||
await this.setData(url, body, ids);
|
|
||||||
},
|
|
||||||
async setData(url, body, ids = null) {
|
|
||||||
|
|
||||||
const toBeUpdated = this.premisses ? (ids ? (ids) : (this.premisses.filter(p => p.selected).map(p => p.id))) : null;
|
|
||||||
|
|
||||||
if (null !== toBeUpdated) {
|
|
||||||
this.selectedLoading = true;
|
|
||||||
this.loading = true;
|
|
||||||
|
|
||||||
body.premise_id = toBeUpdated;
|
|
||||||
logger.info(url, body)
|
|
||||||
|
|
||||||
const {data: data} = await performRequest(this, 'PUT', url, body).catch(e => {
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
data.forEach(p => p.selected = true);
|
|
||||||
this.premisses = this.replacePremissesById(this.premisses, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
this.selectedLoading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace the premisses with the loaded ones by id.
|
* Replace the premisses with the loaded ones by id.
|
||||||
|
|
|
||||||
230
src/frontend/src/store/premiseSingleEdit.js
Normal file
230
src/frontend/src/store/premiseSingleEdit.js
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
import {defineStore} from 'pinia'
|
||||||
|
import {config} from '@/config'
|
||||||
|
import logger from "@/logger.js"
|
||||||
|
import performRequest from '@/backend.js'
|
||||||
|
|
||||||
|
export const usePremiseSingleEditStore = defineStore('premiseSingleEdit', {
|
||||||
|
state() {
|
||||||
|
return {
|
||||||
|
premise: null,
|
||||||
|
loading: false,
|
||||||
|
calculating: false,
|
||||||
|
routing: false,
|
||||||
|
throwsException: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
showProcessingModal(state) {
|
||||||
|
return state.calculating || state.routing;
|
||||||
|
},
|
||||||
|
showLoadingSpinner(state) {
|
||||||
|
return state.loading;
|
||||||
|
},
|
||||||
|
isEmpty(state) {
|
||||||
|
return state.premise === null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getters for destination editing
|
||||||
|
* ===============================
|
||||||
|
*/
|
||||||
|
|
||||||
|
getDestinations(state) {
|
||||||
|
return state.premise?.destinations ?? [];
|
||||||
|
},
|
||||||
|
|
||||||
|
getDestinationById(state) {
|
||||||
|
return function (id) {
|
||||||
|
if (state.loading || state.selectedLoading) {
|
||||||
|
if (state.throwsException)
|
||||||
|
throw new Error("Premises are accessed while still loading.");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = state.premise.destinations.find(d => String(d.id) === String(id));
|
||||||
|
if ((d ?? null) !== null) return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async load(id) {
|
||||||
|
this.loading = true;
|
||||||
|
this.premise = null;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('premissIds', id.toString());
|
||||||
|
const url = `${config.backendUrl}/calculation/edit/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
||||||
|
|
||||||
|
const {data: data, headers: headers} = await performRequest(this, 'GET', url, null).catch(e => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
[this.premise] = data;
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
async startCalculation() {
|
||||||
|
|
||||||
|
this.calculating = true;
|
||||||
|
const body = this.premise?.id;
|
||||||
|
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);
|
||||||
|
error = e.errorObj;
|
||||||
|
})
|
||||||
|
|
||||||
|
this.calculating = false;
|
||||||
|
return error;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destination editing
|
||||||
|
*/
|
||||||
|
|
||||||
|
async addDestination(node) {
|
||||||
|
|
||||||
|
if (this.premise === null) return;
|
||||||
|
this.routing = true;
|
||||||
|
|
||||||
|
const body = {destination_node_id: node.id, premise_id: [this.premise.id]};
|
||||||
|
const url = `${config.backendUrl}/calculation/destination/`;
|
||||||
|
|
||||||
|
|
||||||
|
const {data: destinations} = await performRequest(this, 'POST', url, body).catch(e => {
|
||||||
|
this.routing = false;
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
|
||||||
|
const ids = []
|
||||||
|
|
||||||
|
for (const destId of Object.keys(destinations)) {
|
||||||
|
this.premise.destinations.push(destinations[destId]);
|
||||||
|
ids.push(destinations[destId].id);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.routing = false;
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
|
||||||
|
},
|
||||||
|
async deleteDestination(id) {
|
||||||
|
|
||||||
|
if (this.premise === null || !this.premise.destinations.some(d => String(d.id) === String(id))) return;
|
||||||
|
|
||||||
|
|
||||||
|
const url = `${config.backendUrl}/calculation/destination/${id}`;
|
||||||
|
await performRequest(this, 'DELETE', url, null, false).catch(async e => {
|
||||||
|
logger.error("Unable to delete destination: " + id + "");
|
||||||
|
logger.error(e);
|
||||||
|
await this.load(this.premise.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
const toBeDeleted = this.premise.destinations.findIndex(d => String(d.id) === String(id));
|
||||||
|
|
||||||
|
if (toBeDeleted !== -1) {
|
||||||
|
this.premise.destinations.splice(toBeDeleted, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async updateDestination(id) {
|
||||||
|
if (this.premise === null) return;
|
||||||
|
|
||||||
|
const toUpdate = this.premise.destinations.find(to => String(id) === String(to.id));
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
annual_amount: toUpdate.annual_amount,
|
||||||
|
repackaging_costs: toUpdate.repackaging_costs,
|
||||||
|
handling_costs: toUpdate.handling_costs,
|
||||||
|
disposal_costs: toUpdate.disposal_costs,
|
||||||
|
is_d2d: toUpdate.is_d2d,
|
||||||
|
rate_d2d: toUpdate.rate_d2d,
|
||||||
|
lead_time_d2d: toUpdate.lead_time_d2d,
|
||||||
|
route_selected_id: toUpdate.routes.find(r => r.is_selected)?.id ?? null,
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.info(body)
|
||||||
|
|
||||||
|
const url = `${config.backendUrl}/calculation/destination/${toUpdate.id}`;
|
||||||
|
await performRequest(this, 'PUT', url, body, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save
|
||||||
|
*/
|
||||||
|
|
||||||
|
async savePrice() {
|
||||||
|
let success = true;
|
||||||
|
|
||||||
|
if (!this.premise) return;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
premise_ids: [this.premise.id],
|
||||||
|
material_cost: this.premise.material_cost,
|
||||||
|
oversea_share: this.premise.oversea_share,
|
||||||
|
is_fca_enabled: this.premise.is_fca_enabled
|
||||||
|
};
|
||||||
|
|
||||||
|
await performRequest(this, 'POST', `${config.backendUrl}/calculation/price/`, body, false).catch(_ => {
|
||||||
|
success = false;
|
||||||
|
})
|
||||||
|
|
||||||
|
return success;
|
||||||
|
},
|
||||||
|
async savePackaging() {
|
||||||
|
let success = true;
|
||||||
|
|
||||||
|
if (!this.premise) return;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
premise_ids: [this.premise.id],
|
||||||
|
|
||||||
|
handling_unit: {
|
||||||
|
weight: this.premise.handling_unit.weight,
|
||||||
|
weight_unit: this.premise.handling_unit.weight_unit,
|
||||||
|
|
||||||
|
length: this.premise.handling_unit.length,
|
||||||
|
width: this.premise.handling_unit.width,
|
||||||
|
height: this.premise.handling_unit.height,
|
||||||
|
dimension_unit: this.premise.handling_unit.dimension_unit,
|
||||||
|
|
||||||
|
content_unit_count: this.premise.handling_unit.content_unit_count,
|
||||||
|
},
|
||||||
|
|
||||||
|
is_mixable: this.premise.is_mixable,
|
||||||
|
is_stackable: this.premise.is_stackable
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
await performRequest(this, 'POST', `${config.backendUrl}/calculation/packaging/`, body, false).catch(() => {
|
||||||
|
success = false;
|
||||||
|
})
|
||||||
|
|
||||||
|
return success;
|
||||||
|
|
||||||
|
},
|
||||||
|
async saveMaterial() {
|
||||||
|
let success = true;
|
||||||
|
|
||||||
|
|
||||||
|
if (!this.premise) return;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
premise_ids: [this.premise.id],
|
||||||
|
hs_code: this.premise.hs_code,
|
||||||
|
tariff_rate: this.premise.tariff_rate,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
await performRequest(this, 'POST', `${config.backendUrl}/calculation/material/`, body, false).catch(() => {
|
||||||
|
success = false;
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return success;
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -8,7 +8,6 @@ import de.avatic.lcc.dto.calculation.ResolvePremiseDTO;
|
||||||
import de.avatic.lcc.dto.calculation.create.CreatePremiseDTO;
|
import de.avatic.lcc.dto.calculation.create.CreatePremiseDTO;
|
||||||
import de.avatic.lcc.dto.calculation.create.PremiseSearchResultDTO;
|
import de.avatic.lcc.dto.calculation.create.PremiseSearchResultDTO;
|
||||||
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
|
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
|
||||||
import de.avatic.lcc.dto.calculation.edit.SetDataDTO;
|
|
||||||
import de.avatic.lcc.dto.calculation.edit.destination.DestinationCreateDTO;
|
import de.avatic.lcc.dto.calculation.edit.destination.DestinationCreateDTO;
|
||||||
import de.avatic.lcc.dto.calculation.edit.destination.DestinationSetDTO;
|
import de.avatic.lcc.dto.calculation.edit.destination.DestinationSetDTO;
|
||||||
import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO;
|
import de.avatic.lcc.dto.calculation.edit.destination.DestinationUpdateDTO;
|
||||||
|
|
@ -17,8 +16,6 @@ import de.avatic.lcc.dto.calculation.edit.masterData.PackagingUpdateDTO;
|
||||||
import de.avatic.lcc.dto.calculation.edit.masterData.PriceUpdateDTO;
|
import de.avatic.lcc.dto.calculation.edit.masterData.PriceUpdateDTO;
|
||||||
import de.avatic.lcc.service.access.DestinationService;
|
import de.avatic.lcc.service.access.DestinationService;
|
||||||
import de.avatic.lcc.service.access.PremisesService;
|
import de.avatic.lcc.service.access.PremisesService;
|
||||||
import de.avatic.lcc.service.calculation.ChangeMaterialService;
|
|
||||||
import de.avatic.lcc.service.calculation.ChangeSupplierService;
|
|
||||||
import de.avatic.lcc.service.calculation.PremiseCreationService;
|
import de.avatic.lcc.service.calculation.PremiseCreationService;
|
||||||
import de.avatic.lcc.service.calculation.PremiseSearchStringAnalyzerService;
|
import de.avatic.lcc.service.calculation.PremiseSearchStringAnalyzerService;
|
||||||
import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException;
|
import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException;
|
||||||
|
|
@ -44,20 +41,17 @@ import java.util.Map;
|
||||||
public class PremiseController {
|
public class PremiseController {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(PremiseController.class);
|
private static final Logger log = LoggerFactory.getLogger(PremiseController.class);
|
||||||
|
|
||||||
private final PremiseSearchStringAnalyzerService premiseSearchStringAnalyzerService;
|
private final PremiseSearchStringAnalyzerService premiseSearchStringAnalyzerService;
|
||||||
private final PremisesService premisesServices;
|
private final PremisesService premisesServices;
|
||||||
private final PremiseCreationService premiseCreationService;
|
private final PremiseCreationService premiseCreationService;
|
||||||
private final DestinationService destinationService;
|
private final DestinationService destinationService;
|
||||||
private final ChangeSupplierService changeSupplierService;
|
|
||||||
private final ChangeMaterialService changeMaterialService;
|
|
||||||
|
|
||||||
public PremiseController(PremiseSearchStringAnalyzerService premiseSearchStringAnalyzerService, PremisesService premisesServices, PremiseCreationService premiseCreationService, DestinationService destinationService, ChangeSupplierService changeSupplierService, ChangeMaterialService changeMaterialService) {
|
public PremiseController(PremiseSearchStringAnalyzerService premiseSearchStringAnalyzerService, PremisesService premisesServices, PremiseCreationService premiseCreationService, DestinationService destinationService) {
|
||||||
this.premiseSearchStringAnalyzerService = premiseSearchStringAnalyzerService;
|
this.premiseSearchStringAnalyzerService = premiseSearchStringAnalyzerService;
|
||||||
this.premisesServices = premisesServices;
|
this.premisesServices = premisesServices;
|
||||||
this.premiseCreationService = premiseCreationService;
|
this.premiseCreationService = premiseCreationService;
|
||||||
this.destinationService = destinationService;
|
this.destinationService = destinationService;
|
||||||
this.changeSupplierService = changeSupplierService;
|
|
||||||
this.changeMaterialService = changeMaterialService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping({"/view", "/view/"})
|
@GetMapping({"/view", "/view/"})
|
||||||
|
|
@ -213,16 +207,6 @@ public class PremiseController {
|
||||||
return ResponseEntity.ok().build();
|
return ResponseEntity.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping({"/supplier", "/supplier/"})
|
|
||||||
@PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')")
|
|
||||||
public ResponseEntity<List<PremiseDetailDTO>> setSupplier(@RequestBody SetDataDTO setSupplierDTO) {
|
|
||||||
return ResponseEntity.ok(changeSupplierService.setSupplier(setSupplierDTO));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PutMapping({"/material", "/material/"})
|
|
||||||
@PreAuthorize("hasAnyRole('SUPER', 'CALCULATION')")
|
|
||||||
public ResponseEntity<List<PremiseDetailDTO>> setMaterial(@RequestBody SetDataDTO setMaterialDTO) {
|
|
||||||
return ResponseEntity.ok(changeMaterialService.setMaterial(setMaterialDTO));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
package de.avatic.lcc.controller.custom;
|
|
||||||
|
|
||||||
import de.avatic.lcc.dto.custom.CustomDTO;
|
|
||||||
import de.avatic.lcc.model.taric.Nomenclature;
|
|
||||||
import de.avatic.lcc.service.api.CustomApiService;
|
|
||||||
import de.avatic.lcc.service.api.EUTaxationApiWrapperService;
|
|
||||||
import de.avatic.lcc.service.calculation.NomenclatureService;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for handling custom tariff related requests.
|
|
||||||
* Provides endpoints for retrieving tariff rates based on HS code and country ID.
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/customs")
|
|
||||||
public class CustomController {
|
|
||||||
|
|
||||||
|
|
||||||
private final CustomApiService customApiService;
|
|
||||||
private final NomenclatureService nomenclatureService;
|
|
||||||
private final EUTaxationApiWrapperService eUTaxationApiWrapperService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new instance of CustomController with the given service.
|
|
||||||
*
|
|
||||||
* @param customApiService the service responsible for custom tariff calculations
|
|
||||||
*/
|
|
||||||
public CustomController(CustomApiService customApiService, NomenclatureService nomenclatureService, EUTaxationApiWrapperService eUTaxationApiWrapperService) {
|
|
||||||
this.customApiService = customApiService;
|
|
||||||
this.nomenclatureService = nomenclatureService;
|
|
||||||
this.eUTaxationApiWrapperService = eUTaxationApiWrapperService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the tariff rate for the specified HS code and country ID.
|
|
||||||
*
|
|
||||||
* @param hsCode the HS code representing the product classification
|
|
||||||
* @param countryIds the ID of the country for which the tariff rate is required
|
|
||||||
* @return a {@code ResponseEntity} containing the tariff rate as a {@code Number}
|
|
||||||
*/
|
|
||||||
@GetMapping({"/",""})
|
|
||||||
public ResponseEntity<List<CustomDTO>> getTariffRate(@RequestParam(value = "hs_code") String hsCode, @RequestParam(value = "country_ids") List<Integer> countryIds) {
|
|
||||||
var res = eUTaxationApiWrapperService.getTariffRates(hsCode, countryIds);
|
|
||||||
return ResponseEntity.ok(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping({"/search", "/search/"})
|
|
||||||
public ResponseEntity<List<String>> getNomenclature(@RequestParam(value = "hs_code") String hsCode) {
|
|
||||||
return ResponseEntity.ok(nomenclatureService.getNomenclature(hsCode));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -43,6 +43,9 @@ public class PremiseDetailDTO {
|
||||||
@JsonProperty("is_fca_enabled")
|
@JsonProperty("is_fca_enabled")
|
||||||
private Boolean isFcaEnabled;
|
private Boolean isFcaEnabled;
|
||||||
|
|
||||||
|
@JsonProperty("tariff_unlocked")
|
||||||
|
private Boolean tariffUnlocked;
|
||||||
|
|
||||||
|
|
||||||
public Double getMaterialCost() {
|
public Double getMaterialCost() {
|
||||||
return materialCost;
|
return materialCost;
|
||||||
|
|
@ -142,4 +145,13 @@ public class PremiseDetailDTO {
|
||||||
public void setDestinations(List<DestinationDTO> destinations) {
|
public void setDestinations(List<DestinationDTO> destinations) {
|
||||||
this.destinations = destinations;
|
this.destinations = destinations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTariffUnlocked(Boolean tariffUnlocked) {
|
||||||
|
this.tariffUnlocked = tariffUnlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public Boolean getTariffUnlocked() {
|
||||||
|
return tariffUnlocked;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,6 @@ public class MaterialUpdateDTO {
|
||||||
@Digits(integer = 4, fraction = 4, message = "Tariff rate must have at most 4 decimal places")
|
@Digits(integer = 4, fraction = 4, message = "Tariff rate must have at most 4 decimal places")
|
||||||
private Number tariffRate;
|
private Number tariffRate;
|
||||||
|
|
||||||
@JsonProperty("tariff_rates")
|
|
||||||
private Map<Integer, Number> tariffRates;
|
|
||||||
|
|
||||||
public String getHsCode() {
|
public String getHsCode() {
|
||||||
return hsCode;
|
return hsCode;
|
||||||
}
|
}
|
||||||
|
|
@ -49,11 +46,4 @@ public class MaterialUpdateDTO {
|
||||||
this.premiseIds = premiseIds;
|
this.premiseIds = premiseIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Integer, Number> getTariffRates() {
|
|
||||||
return tariffRates;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTariffRates(Map<Integer, Number> tariffRates) {
|
|
||||||
this.tariffRates = tariffRates;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ public class Premise {
|
||||||
@Digits(integer = 7, fraction = 2)
|
@Digits(integer = 7, fraction = 2)
|
||||||
private BigDecimal tariffRate;
|
private BigDecimal tariffRate;
|
||||||
|
|
||||||
|
private Boolean tariffUnlocked;
|
||||||
|
|
||||||
@Size(max = 16)
|
@Size(max = 16)
|
||||||
private PremiseState state;
|
private PremiseState state;
|
||||||
|
|
||||||
|
|
@ -276,4 +278,12 @@ public class Premise {
|
||||||
public void setUserId(Integer userId) {
|
public void setUserId(Integer userId) {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getTariffUnlocked() {
|
||||||
|
return tariffUnlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTariffUnlocked(Boolean tariffUnlocked) {
|
||||||
|
this.tariffUnlocked = tariffUnlocked;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package de.avatic.lcc.model.custom;
|
package de.avatic.lcc.model.eutaxation;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package de.avatic.lcc.model.zolltarifnummern;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ZolltarifnummernResponse {
|
||||||
|
|
||||||
|
String query;
|
||||||
|
|
||||||
|
String year;
|
||||||
|
|
||||||
|
String lang;
|
||||||
|
|
||||||
|
String version;
|
||||||
|
|
||||||
|
String total;
|
||||||
|
|
||||||
|
List<ZolltarifnummernResponseEntry> suggestions;
|
||||||
|
|
||||||
|
public String getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuery(String query) {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getYear() {
|
||||||
|
return year;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setYear(String year) {
|
||||||
|
this.year = year;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLang() {
|
||||||
|
return lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLang(String lang) {
|
||||||
|
this.lang = lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(String version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTotal() {
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotal(String total) {
|
||||||
|
this.total = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ZolltarifnummernResponseEntry> getSuggestions() {
|
||||||
|
return suggestions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuggestions(List<ZolltarifnummernResponseEntry> suggestions) {
|
||||||
|
this.suggestions = suggestions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package de.avatic.lcc.model.zolltarifnummern;
|
||||||
|
|
||||||
|
public class ZolltarifnummernResponseEntry {
|
||||||
|
|
||||||
|
String code;
|
||||||
|
|
||||||
|
String score;
|
||||||
|
|
||||||
|
String value;
|
||||||
|
|
||||||
|
String data;
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCode(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScore() {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScore(String score) {
|
||||||
|
this.score = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(String data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
@ -23,10 +24,15 @@ public class DistanceMatrixRepository {
|
||||||
this.jdbcTemplate = jdbcTemplate;
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
public Optional<Distance> getDistance(Node src, boolean isUsrFrom, Node dest, boolean isUsrTo) {
|
public Optional<Distance> getDistance(Node src, boolean isUsrFrom, Node dest, boolean isUsrTo) {
|
||||||
String query = "SELECT * FROM distance_matrix WHERE ? = ? AND ? = ? AND state = ?";
|
String fromCol = isUsrFrom ? "from_user_node_id" : "from_node_id";
|
||||||
|
String toCol = isUsrTo ? "to_user_node_id" : "to_node_id";
|
||||||
|
|
||||||
var distance = jdbcTemplate.query(query, new DistanceMapper(), isUsrFrom ? "from_user_node_id" : "from_node_id", src.getId(), isUsrTo ? "to_user_node_id" : "to_node_id", dest.getId(), DistanceMatrixState.VALID.name());
|
String query = "SELECT * FROM distance_matrix WHERE " + fromCol + " = ? AND " + toCol + " = ? AND state = ?";
|
||||||
|
|
||||||
|
var distance = jdbcTemplate.query(query, new DistanceMapper(),
|
||||||
|
src.getId(), dest.getId(), DistanceMatrixState.VALID.name());
|
||||||
|
|
||||||
if (distance.isEmpty())
|
if (distance.isEmpty())
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
|
|
@ -34,17 +40,22 @@ public class DistanceMatrixRepository {
|
||||||
return Optional.of(distance.getFirst());
|
return Optional.of(distance.getFirst());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
public void saveDistance(Distance distance) {
|
public void saveDistance(Distance distance) {
|
||||||
try {
|
try {
|
||||||
|
// Determine which columns to use
|
||||||
|
String fromCol = distance.getFromUserNodeId() != null ? "from_user_node_id" : "from_node_id";
|
||||||
|
String toCol = distance.getToUserNodeId() != null ? "to_user_node_id" : "to_node_id";
|
||||||
|
|
||||||
|
Integer fromId = distance.getFromUserNodeId() != null ? distance.getFromUserNodeId() : distance.getFromNodeId();
|
||||||
|
Integer toId = distance.getToUserNodeId() != null ? distance.getToUserNodeId() : distance.getToNodeId();
|
||||||
|
|
||||||
// First, check if an entry already exists
|
// First, check if an entry already exists
|
||||||
String checkQuery = "SELECT id FROM distance_matrix WHERE ? = ? AND ? = ?";
|
String checkQuery = "SELECT id FROM distance_matrix WHERE " + fromCol + " = ? AND " + toCol + " = ?";
|
||||||
var existingIds = jdbcTemplate.query(checkQuery,
|
var existingIds = jdbcTemplate.query(checkQuery,
|
||||||
(rs, rowNum) -> rs.getInt("id"),
|
(rs, rowNum) -> rs.getInt("id"),
|
||||||
distance.getFromUserNodeId() != null ? "from_user_node_id" : "from_node_id",
|
fromId,
|
||||||
distance.getFromUserNodeId() != null ? distance.getFromUserNodeId() : distance.getFromNodeId(),
|
toId);
|
||||||
distance.getToUserNodeId() != null ? "to_user_node_id" : "to_node_id",
|
|
||||||
distance.getToUserNodeId() != null ? distance.getToUserNodeId() : distance.getToNodeId());
|
|
||||||
|
|
||||||
if (!existingIds.isEmpty()) {
|
if (!existingIds.isEmpty()) {
|
||||||
// Update existing entry
|
// Update existing entry
|
||||||
|
|
@ -57,8 +68,7 @@ public class DistanceMatrixRepository {
|
||||||
distance = ?,
|
distance = ?,
|
||||||
state = ?,
|
state = ?,
|
||||||
updated_at = ?
|
updated_at = ?
|
||||||
WHERE ? = ? AND ? = ?
|
WHERE\s""" + fromCol + " = ? AND " + toCol + " = ?";
|
||||||
""";
|
|
||||||
|
|
||||||
jdbcTemplate.update(updateQuery,
|
jdbcTemplate.update(updateQuery,
|
||||||
distance.getFromGeoLat(),
|
distance.getFromGeoLat(),
|
||||||
|
|
@ -68,10 +78,8 @@ public class DistanceMatrixRepository {
|
||||||
distance.getDistance(),
|
distance.getDistance(),
|
||||||
distance.getState().name(),
|
distance.getState().name(),
|
||||||
distance.getUpdatedAt(),
|
distance.getUpdatedAt(),
|
||||||
distance.getFromUserNodeId() != null ? "from_user_node_id" : "from_node_id",
|
fromId,
|
||||||
distance.getFromUserNodeId() != null ? distance.getFromUserNodeId() : distance.getFromNodeId(),
|
toId);
|
||||||
distance.getToUserNodeId() != null ? "to_user_node_id" : "to_node_id",
|
|
||||||
distance.getToUserNodeId() != null ? distance.getToUserNodeId() : distance.getToNodeId());
|
|
||||||
|
|
||||||
logger.info("Updated existing distance entry for nodes {} -> {}",
|
logger.info("Updated existing distance entry for nodes {} -> {}",
|
||||||
distance.getFromNodeId(), distance.getToNodeId());
|
distance.getFromNodeId(), distance.getToNodeId());
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
package de.avatic.lcc.repositories;
|
|
||||||
|
|
||||||
import de.avatic.lcc.service.api.EUTaxationApiService;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
public class NomenclatureRepository {
|
|
||||||
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
|
||||||
private final EUTaxationApiService eUTaxationApiService;
|
|
||||||
|
|
||||||
public NomenclatureRepository(JdbcTemplate jdbcTemplate, EUTaxationApiService eUTaxationApiService) {
|
|
||||||
this.jdbcTemplate = jdbcTemplate;
|
|
||||||
this.eUTaxationApiService = eUTaxationApiService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> searchHsCode(String search) {
|
|
||||||
String sql = """
|
|
||||||
SELECT hs_code FROM nomenclature WHERE hs_code LIKE CONCAT(?, '%') LIMIT 10
|
|
||||||
""";
|
|
||||||
|
|
||||||
return jdbcTemplate.queryForList (sql, String.class, search);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean validate(String hsCode) {
|
|
||||||
try {
|
|
||||||
var foundNomenclature = eUTaxationApiService.getGoodsDescription(hsCode, "en");
|
|
||||||
return foundNomenclature.getReturn().getResult().getData().isDeclarable();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -285,6 +285,11 @@ public class PremiseRepository {
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void updateMaterial(List<Integer> premiseIds, String hsCode, BigDecimal tariffRate) {
|
public void updateMaterial(List<Integer> premiseIds, String hsCode, BigDecimal tariffRate) {
|
||||||
|
updateMaterial(premiseIds, hsCode, tariffRate, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void updateMaterial(List<Integer> premiseIds, String hsCode, BigDecimal tariffRate, Boolean tariffUnlocked) {
|
||||||
|
|
||||||
// Build the SET clause dynamically based on non-null parameters
|
// Build the SET clause dynamically based on non-null parameters
|
||||||
List<String> setClauses = new ArrayList<>();
|
List<String> setClauses = new ArrayList<>();
|
||||||
|
|
@ -300,6 +305,11 @@ public class PremiseRepository {
|
||||||
parameters.add(tariffRate);
|
parameters.add(tariffRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tariffUnlocked != null) {
|
||||||
|
setClauses.add("tariff_unlocked = ?");
|
||||||
|
parameters.add(tariffUnlocked);
|
||||||
|
}
|
||||||
|
|
||||||
// If no fields to update, return early
|
// If no fields to update, return early
|
||||||
if (setClauses.isEmpty()) {
|
if (setClauses.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -325,6 +335,8 @@ public class PremiseRepository {
|
||||||
ps.setBigDecimal(i + 1, (BigDecimal) param);
|
ps.setBigDecimal(i + 1, (BigDecimal) param);
|
||||||
} else if (param instanceof Integer) {
|
} else if (param instanceof Integer) {
|
||||||
ps.setInt(i + 1, (Integer) param);
|
ps.setInt(i + 1, (Integer) param);
|
||||||
|
} else if(param instanceof Boolean) {
|
||||||
|
ps.setBoolean(i + 1, (Boolean) param);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -669,6 +681,24 @@ public class PremiseRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public List<Integer> getIdsWithUnlockedTariffs(List<Integer> premiseIds) {
|
||||||
|
if (premiseIds == null || premiseIds.isEmpty()) {
|
||||||
|
return premiseIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
String sql = "SELECT id FROM premise WHERE id IN (:ids) AND tariff_unlocked = TRUE";
|
||||||
|
|
||||||
|
List<Integer> unlockedIds = namedParameterJdbcTemplate.query(
|
||||||
|
sql,
|
||||||
|
new MapSqlParameterSource("ids", premiseIds),
|
||||||
|
(rs, rowNum) -> rs.getInt("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
return unlockedIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates SQL query building logic
|
* Encapsulates SQL query building logic
|
||||||
*/
|
*/
|
||||||
|
|
@ -888,6 +918,8 @@ public class PremiseRepository {
|
||||||
|
|
||||||
entity.setTariffRate(rs.getBigDecimal("tariff_rate"));
|
entity.setTariffRate(rs.getBigDecimal("tariff_rate"));
|
||||||
|
|
||||||
|
entity.setTariffUnlocked(rs.getBoolean("tariff_unlocked"));
|
||||||
|
|
||||||
entity.setFcaEnabled(rs.getBoolean("is_fca_enabled"));
|
entity.setFcaEnabled(rs.getBoolean("is_fca_enabled"));
|
||||||
if (rs.wasNull())
|
if (rs.wasNull())
|
||||||
entity.setFcaEnabled(null);
|
entity.setFcaEnabled(null);
|
||||||
|
|
|
||||||
|
|
@ -211,15 +211,11 @@ public class PremisesService {
|
||||||
premiseRepository.checkOwner(materialUpdateDTO.getPremiseIds(), userId);
|
premiseRepository.checkOwner(materialUpdateDTO.getPremiseIds(), userId);
|
||||||
|
|
||||||
|
|
||||||
if(materialUpdateDTO.getTariffRates() != null) {
|
|
||||||
var rates = materialUpdateDTO.getTariffRates();
|
|
||||||
rates.keySet().forEach(id -> premiseRepository.updateMaterial(List.of(id), materialUpdateDTO.getHsCode(), rates.get(id) == null ? null : BigDecimal.valueOf(rates.get(id).doubleValue())));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var unlockedIds = premiseRepository.getIdsWithUnlockedTariffs(materialUpdateDTO.getPremiseIds());
|
||||||
|
|
||||||
var tariffRate = materialUpdateDTO.getTariffRate() == null ? null : BigDecimal.valueOf(materialUpdateDTO.getTariffRate().doubleValue());
|
var tariffRate = materialUpdateDTO.getTariffRate() == null ? null : BigDecimal.valueOf(materialUpdateDTO.getTariffRate().doubleValue());
|
||||||
premiseRepository.updateMaterial(materialUpdateDTO.getPremiseIds(), materialUpdateDTO.getHsCode(), tariffRate);
|
premiseRepository.updateMaterial(unlockedIds, materialUpdateDTO.getHsCode(), tariffRate);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -305,7 +301,7 @@ public class PremisesService {
|
||||||
var old = premiseRepository.getPremiseById(id).orElseThrow();
|
var old = premiseRepository.getPremiseById(id).orElseThrow();
|
||||||
var newId = premiseRepository.insert(old.getMaterialId(), old.getSupplierNodeId(), old.getUserSupplierNodeId(), BigDecimal.valueOf(old.getLocation().getLatitude()), BigDecimal.valueOf(old.getLocation().getLongitude()), old.getCountryId(), userId);
|
var newId = premiseRepository.insert(old.getMaterialId(), old.getSupplierNodeId(), old.getUserSupplierNodeId(), BigDecimal.valueOf(old.getLocation().getLatitude()), BigDecimal.valueOf(old.getLocation().getLongitude()), old.getCountryId(), userId);
|
||||||
|
|
||||||
premiseRepository.updateMaterial(Collections.singletonList(newId), old.getHsCode(), old.getTariffRate());
|
premiseRepository.updateMaterial(Collections.singletonList(newId), old.getHsCode(), old.getTariffRate(), old.getTariffUnlocked());
|
||||||
premiseRepository.updatePrice(Collections.singletonList(newId), old.getMaterialCost(), old.getFcaEnabled(), old.getOverseaShare());
|
premiseRepository.updatePrice(Collections.singletonList(newId), old.getMaterialCost(), old.getFcaEnabled(), old.getOverseaShare());
|
||||||
premiseRepository.updatePackaging(Collections.singletonList(newId), dimensionTransformer.toDimensionEntity(old), old.getHuStackable(), old.getHuMixable());
|
premiseRepository.updatePackaging(Collections.singletonList(newId), dimensionTransformer.toDimensionEntity(old), old.getHuStackable(), old.getHuMixable());
|
||||||
premiseRepository.setPackagingId(newId, old.getPackagingId());
|
premiseRepository.setPackagingId(newId, old.getPackagingId());
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ public class DistanceApiService {
|
||||||
distance.setToNodeId(null);
|
distance.setToNodeId(null);
|
||||||
} else {
|
} else {
|
||||||
distance.setToUserNodeId(null);
|
distance.setToUserNodeId(null);
|
||||||
distance.setToNodeId(from.getId());
|
distance.setToNodeId(to.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
distance.setFromGeoLat(from.getGeoLat());
|
distance.setFromGeoLat(from.getGeoLat());
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package de.avatic.lcc.service.api;
|
||||||
|
|
||||||
import eu.europa.ec.taxation.taric.client.*;
|
import eu.europa.ec.taxation.taric.client.*;
|
||||||
import jakarta.xml.bind.JAXBElement;
|
import jakarta.xml.bind.JAXBElement;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.ws.client.core.WebServiceTemplate;
|
import org.springframework.ws.client.core.WebServiceTemplate;
|
||||||
|
|
||||||
|
|
@ -9,6 +10,7 @@ import javax.xml.datatype.DatatypeFactory;
|
||||||
import javax.xml.datatype.XMLGregorianCalendar;
|
import javax.xml.datatype.XMLGregorianCalendar;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class EUTaxationApiService {
|
public class EUTaxationApiService {
|
||||||
|
|
@ -21,7 +23,8 @@ public class EUTaxationApiService {
|
||||||
this.objectFactory = new ObjectFactory();
|
this.objectFactory = new ObjectFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public GoodsDescrForWsResponse getGoodsDescription(String goodsCode, String languageCode) {
|
@Async("customLookupExecutor")
|
||||||
|
public CompletableFuture<GoodsDescrForWsResponse> getGoodsDescription(String goodsCode, String languageCode) {
|
||||||
GoodsDescrForWs request = new GoodsDescrForWs();
|
GoodsDescrForWs request = new GoodsDescrForWs();
|
||||||
request.setGoodsCode(goodsCode);
|
request.setGoodsCode(goodsCode);
|
||||||
request.setLanguageCode(languageCode);
|
request.setLanguageCode(languageCode);
|
||||||
|
|
@ -33,10 +36,11 @@ public class EUTaxationApiService {
|
||||||
JAXBElement<GoodsDescrForWsResponse> responseElement =
|
JAXBElement<GoodsDescrForWsResponse> responseElement =
|
||||||
(JAXBElement<GoodsDescrForWsResponse>) webServiceTemplate.marshalSendAndReceive(requestElement);
|
(JAXBElement<GoodsDescrForWsResponse>) webServiceTemplate.marshalSendAndReceive(requestElement);
|
||||||
|
|
||||||
return responseElement.getValue();
|
return CompletableFuture.completedFuture(responseElement.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public GoodsMeasForWsResponse getGoodsMeasures(String goodsCode, String countryCode, String tradeMovement) {
|
@Async("customLookupExecutor")
|
||||||
|
public CompletableFuture<GoodsMeasForWsResponse> getGoodsMeasures(String goodsCode, String countryCode, String tradeMovement) {
|
||||||
GoodsMeasForWs request = new GoodsMeasForWs();
|
GoodsMeasForWs request = new GoodsMeasForWs();
|
||||||
request.setGoodsCode(goodsCode);
|
request.setGoodsCode(goodsCode);
|
||||||
request.setCountryCode(countryCode);
|
request.setCountryCode(countryCode);
|
||||||
|
|
@ -49,7 +53,7 @@ public class EUTaxationApiService {
|
||||||
JAXBElement<GoodsMeasForWsResponse> responseElement =
|
JAXBElement<GoodsMeasForWsResponse> responseElement =
|
||||||
(JAXBElement<GoodsMeasForWsResponse>) webServiceTemplate.marshalSendAndReceive(requestElement);
|
(JAXBElement<GoodsMeasForWsResponse>) webServiceTemplate.marshalSendAndReceive(requestElement);
|
||||||
|
|
||||||
return responseElement.getValue();
|
return CompletableFuture.completedFuture(responseElement.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
private XMLGregorianCalendar getCurrentDate() {
|
private XMLGregorianCalendar getCurrentDate() {
|
||||||
|
|
|
||||||
|
|
@ -1,182 +0,0 @@
|
||||||
package de.avatic.lcc.service.api;
|
|
||||||
|
|
||||||
import de.avatic.lcc.dto.custom.CustomDTO;
|
|
||||||
import de.avatic.lcc.dto.custom.CustomMeasureDTO;
|
|
||||||
import de.avatic.lcc.dto.generic.PropertyDTO;
|
|
||||||
import de.avatic.lcc.model.custom.MeasureType;
|
|
||||||
import de.avatic.lcc.model.db.properties.SystemPropertyMappingId;
|
|
||||||
import de.avatic.lcc.repositories.country.CountryRepository;
|
|
||||||
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
|
||||||
import de.avatic.lcc.util.exception.base.InternalErrorException;
|
|
||||||
import eu.europa.ec.taxation.taric.client.GoodsMeasuresForWsResponse;
|
|
||||||
import org.springframework.scheduling.annotation.Async;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class EUTaxationApiWrapperService {
|
|
||||||
|
|
||||||
private final CountryRepository countryRepository;
|
|
||||||
private final EUTaxationApiService eUTaxationApiService;
|
|
||||||
private final PropertyRepository propertyRepository;
|
|
||||||
|
|
||||||
public EUTaxationApiWrapperService(CountryRepository countryRepository, EUTaxationApiService eUTaxationApiService, PropertyRepository propertyRepository) {
|
|
||||||
this.countryRepository = countryRepository;
|
|
||||||
this.eUTaxationApiService = eUTaxationApiService;
|
|
||||||
this.propertyRepository = propertyRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean validate(String hsCode) {
|
|
||||||
try {
|
|
||||||
var goodsDescription = eUTaxationApiService.getGoodsDescription(hsCode, "en");
|
|
||||||
return goodsDescription.getReturn().getResult().getData().isDeclarable() == true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
// just continue
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<CustomDTO> getTariffRates(String hsCode, List<Integer> countryId) {
|
|
||||||
var futures = countryId.stream().map(country -> getTariffRate(hsCode, country)).toList();
|
|
||||||
|
|
||||||
return futures.stream().map(CompletableFuture::join).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public CustomDTO getTariffRateImmediate(String hsCode, Integer countryId) {
|
|
||||||
var country = countryRepository.getById(countryId);
|
|
||||||
String iso = country.orElseThrow().getIsoCode().name();
|
|
||||||
|
|
||||||
List<CustomMeasureDTO> customMeasures = new ArrayList<>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
var measForWsResponse = eUTaxationApiService.getGoodsMeasures(hsCode, iso.toUpperCase(), "I");
|
|
||||||
|
|
||||||
GoodsMeasuresForWsResponse.Measures.Measure selectedMeasure = null;
|
|
||||||
Double selectedDuty = null;
|
|
||||||
int rank = Integer.MAX_VALUE;
|
|
||||||
|
|
||||||
var measures = filterToNewestMeasuresPerType(measForWsResponse.getReturn().getResult().getMeasures().getMeasure());
|
|
||||||
|
|
||||||
|
|
||||||
for (var measure : measures) {
|
|
||||||
var measureType = MeasureType.fromMeasureCode(measure.getMeasureType().getMeasureType());
|
|
||||||
boolean maybeRelevant = measureType.map(MeasureType::containsRelevantDuty).orElse(false);
|
|
||||||
|
|
||||||
if (maybeRelevant) {
|
|
||||||
var duty = extractDuty(measure);
|
|
||||||
|
|
||||||
if (rank > measureType.get().ordinal() && duty.isPresent()) {
|
|
||||||
rank = measureType.get().ordinal();
|
|
||||||
selectedDuty = duty.get();
|
|
||||||
selectedMeasure = measure;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customMeasures.add(new CustomMeasureDTO(
|
|
||||||
measure.getMeasureType().getMeasureType(),
|
|
||||||
measure.getMeasureType().getDescription(),
|
|
||||||
measure.getRegulationId(),
|
|
||||||
measure.getDutyRate()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedDuty != null) {
|
|
||||||
return new CustomDTO(false, selectedDuty, customMeasures, countryId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
// just continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.TARIFF_RATE).map(PropertyDTO::getCurrentValue).map(Double::valueOf).map(d -> new CustomDTO(d, customMeasures, countryId)).orElseThrow(() -> new InternalErrorException("Unable to load default custom rate. Please contact support."));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Async("customLookupExecutor")
|
|
||||||
public CompletableFuture<CustomDTO> getTariffRate(String hsCode, Integer countryId) {
|
|
||||||
var country = countryRepository.getById(countryId);
|
|
||||||
String iso = country.orElseThrow().getIsoCode().name();
|
|
||||||
|
|
||||||
List<CustomMeasureDTO> customMeasures = new ArrayList<>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
var measForWsResponse = eUTaxationApiService.getGoodsMeasures(hsCode, iso.toUpperCase(), "I");
|
|
||||||
|
|
||||||
GoodsMeasuresForWsResponse.Measures.Measure selectedMeasure = null;
|
|
||||||
Double selectedDuty = null;
|
|
||||||
int rank = Integer.MAX_VALUE;
|
|
||||||
|
|
||||||
var measures = filterToNewestMeasuresPerType(measForWsResponse.getReturn().getResult().getMeasures().getMeasure());
|
|
||||||
|
|
||||||
|
|
||||||
for (var measure : measures) {
|
|
||||||
var measureType = MeasureType.fromMeasureCode(measure.getMeasureType().getMeasureType());
|
|
||||||
boolean maybeRelevant = measureType.map(MeasureType::containsRelevantDuty).orElse(false);
|
|
||||||
|
|
||||||
if (maybeRelevant) {
|
|
||||||
var duty = extractDuty(measure);
|
|
||||||
|
|
||||||
if (rank > measureType.get().ordinal() && duty.isPresent()) {
|
|
||||||
rank = measureType.get().ordinal();
|
|
||||||
selectedDuty = duty.get();
|
|
||||||
selectedMeasure = measure;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customMeasures.add(new CustomMeasureDTO(
|
|
||||||
measure.getMeasureType().getMeasureType(),
|
|
||||||
measure.getMeasureType().getDescription(),
|
|
||||||
measure.getRegulationId(),
|
|
||||||
measure.getDutyRate()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedDuty != null) {
|
|
||||||
return CompletableFuture.completedFuture(new CustomDTO(false, selectedDuty, customMeasures, countryId));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
// just continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return CompletableFuture.completedFuture(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.TARIFF_RATE).map(PropertyDTO::getCurrentValue).map(Double::valueOf).map(d -> new CustomDTO(d, customMeasures, countryId)).orElseThrow(() -> new InternalErrorException("Unable to load default custom rate. Please contact support.")));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<GoodsMeasuresForWsResponse.Measures.Measure> filterToNewestMeasuresPerType(List<GoodsMeasuresForWsResponse.Measures.Measure> measures) {
|
|
||||||
Map<String, GoodsMeasuresForWsResponse.Measures.Measure> newestByType = new HashMap<>();
|
|
||||||
|
|
||||||
for (GoodsMeasuresForWsResponse.Measures.Measure measure : measures) {
|
|
||||||
String measureTypeKey = measure.getMeasureType().getMeasureType();
|
|
||||||
|
|
||||||
GoodsMeasuresForWsResponse.Measures.Measure existing = newestByType.get(measureTypeKey);
|
|
||||||
|
|
||||||
if (existing == null ||
|
|
||||||
measure.getValidityStartDate().compare(existing.getValidityStartDate()) > 0) {
|
|
||||||
newestByType.put(measureTypeKey, measure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayList<>(newestByType.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<Double> extractDuty(GoodsMeasuresForWsResponse.Measures.Measure measure) {
|
|
||||||
|
|
||||||
var dutyRate = measure.getDutyRate();
|
|
||||||
|
|
||||||
if (dutyRate == null) return Optional.empty();
|
|
||||||
|
|
||||||
if (dutyRate.trim().matches("\\d+\\.\\d+\\s*%")) {
|
|
||||||
return Optional.of(Double.parseDouble(dutyRate.trim().replace("%", "").trim()) / 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.empty();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,264 @@
|
||||||
|
package de.avatic.lcc.service.api;
|
||||||
|
|
||||||
|
import de.avatic.lcc.dto.custom.CustomDTO;
|
||||||
|
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.SystemPropertyMappingId;
|
||||||
|
import de.avatic.lcc.model.eutaxation.MeasureType;
|
||||||
|
import de.avatic.lcc.repositories.country.CountryRepository;
|
||||||
|
import de.avatic.lcc.repositories.properties.PropertyRepository;
|
||||||
|
import de.avatic.lcc.util.exception.base.InternalErrorException;
|
||||||
|
import eu.europa.ec.taxation.taric.client.GoodsMeasForWsResponse;
|
||||||
|
import eu.europa.ec.taxation.taric.client.GoodsMeasuresForWsResponse;
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TaxationResolverService {
|
||||||
|
|
||||||
|
private final CountryRepository countryRepository;
|
||||||
|
private final EUTaxationApiService eUTaxationApiService;
|
||||||
|
private final PropertyRepository propertyRepository;
|
||||||
|
private final ZolltarifnummernApiService zolltarifnummernApiService;
|
||||||
|
|
||||||
|
public TaxationResolverService(CountryRepository countryRepository, EUTaxationApiService eUTaxationApiService, PropertyRepository propertyRepository, ZolltarifnummernApiService zolltarifnummernApiService) {
|
||||||
|
this.countryRepository = countryRepository;
|
||||||
|
this.eUTaxationApiService = eUTaxationApiService;
|
||||||
|
this.propertyRepository = propertyRepository;
|
||||||
|
this.zolltarifnummernApiService = zolltarifnummernApiService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Map<TaxationResolverRequest, List<GoodsMeasForWsResponse>> doRequests(List<TaxationResolverRequest> requests) {
|
||||||
|
|
||||||
|
var filteredRequests = requests.stream().collect(Collectors.partitioningBy(r -> r.material().getHsCode() != null && r.material().getHsCode().length() < 10));
|
||||||
|
|
||||||
|
var joined = Stream.concat(
|
||||||
|
filteredRequests.get(false).stream()
|
||||||
|
.filter(r -> r.material().getHsCode() != null)
|
||||||
|
.map(r -> new TaxationResolverSingleRequest(r.material().getHsCode(), r.countryId(), r)),
|
||||||
|
resolveIncompleteHsCodes(filteredRequests.get(true)).stream());
|
||||||
|
|
||||||
|
|
||||||
|
var singleResponses = doSingleRequests(joined.toList());
|
||||||
|
|
||||||
|
return requests.stream().collect(Collectors.toMap(
|
||||||
|
r -> r,
|
||||||
|
r -> singleResponses.keySet().stream().filter(k -> k.origin.equals(r)).map(singleResponses::get).toList()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TaxationResolverSingleRequest> resolveIncompleteHsCodes(List<TaxationResolverRequest> request) {
|
||||||
|
|
||||||
|
var futures = request.stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
r -> r,
|
||||||
|
r -> zolltarifnummernApiService.getDeclarableHsCodes(r.material().getHsCode()))
|
||||||
|
);
|
||||||
|
|
||||||
|
CompletableFuture.allOf(futures.values().toArray(new CompletableFuture[0])).join();
|
||||||
|
|
||||||
|
return futures.keySet().stream().flatMap(k -> futures.get(k).join().stream().map(resp -> new TaxationResolverSingleRequest(resp, k.countryId(), k))).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Map<TaxationResolverSingleRequest, GoodsMeasForWsResponse> doSingleRequests(List<TaxationResolverSingleRequest> requests) {
|
||||||
|
|
||||||
|
|
||||||
|
Map<Integer, Country> countries = requests.stream().collect(Collectors.toMap(TaxationResolverSingleRequest::countryId, r -> countryRepository.getById(r.countryId()).orElseThrow()));
|
||||||
|
Map<TaxationResolverSingleRequest, CompletableFuture<GoodsMeasForWsResponse>> futureMap =
|
||||||
|
requests.stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
r -> r,
|
||||||
|
r -> eUTaxationApiService.getGoodsMeasures(
|
||||||
|
r.hsCode,
|
||||||
|
countries.get(r.countryId()).getIsoCode().getCode(),
|
||||||
|
"I"
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
CompletableFuture.allOf(futureMap.values().toArray(new CompletableFuture[0])).join();
|
||||||
|
|
||||||
|
return
|
||||||
|
futureMap.entrySet().stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
Map.Entry::getKey,
|
||||||
|
entry -> entry.getValue().join()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Double> extractDuty(GoodsMeasuresForWsResponse.Measures.Measure measure) {
|
||||||
|
|
||||||
|
var dutyRate = measure.getDutyRate();
|
||||||
|
|
||||||
|
if (dutyRate == null) return Optional.empty();
|
||||||
|
|
||||||
|
if (dutyRate.trim().matches("\\d+\\.\\d+\\s*%")) {
|
||||||
|
return Optional.of(Double.parseDouble(dutyRate.trim().replace("%", "").trim()) / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.empty();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<GoodsMeasuresForWsResponse.Measures.Measure> filterToNewestMeasuresPerType(List<GoodsMeasuresForWsResponse.Measures.Measure> measures) {
|
||||||
|
Map<String, GoodsMeasuresForWsResponse.Measures.Measure> newestByType = new HashMap<>();
|
||||||
|
|
||||||
|
for (GoodsMeasuresForWsResponse.Measures.Measure measure : measures) {
|
||||||
|
String measureTypeKey = measure.getMeasureType().getMeasureType();
|
||||||
|
|
||||||
|
GoodsMeasuresForWsResponse.Measures.Measure existing = newestByType.get(measureTypeKey);
|
||||||
|
|
||||||
|
if (existing == null ||
|
||||||
|
measure.getValidityStartDate().compare(existing.getValidityStartDate()) > 0) {
|
||||||
|
newestByType.put(measureTypeKey, measure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList<>(newestByType.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validate(String hsCode) {
|
||||||
|
try {
|
||||||
|
var goodsDescription = eUTaxationApiService.getGoodsDescription(hsCode, "en").join();
|
||||||
|
return goodsDescription.getReturn().getResult().getData().isDeclarable() == true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// just continue
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomDTO getTariffRateImmediate(String hsCode, Integer countryId) {
|
||||||
|
var country = countryRepository.getById(countryId);
|
||||||
|
String iso = country.orElseThrow().getIsoCode().name();
|
||||||
|
|
||||||
|
List<CustomMeasureDTO> customMeasures = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var measForWsResponse = eUTaxationApiService.getGoodsMeasures(hsCode, iso.toUpperCase(), "I");
|
||||||
|
|
||||||
|
GoodsMeasuresForWsResponse.Measures.Measure selectedMeasure = null;
|
||||||
|
Double selectedDuty = null;
|
||||||
|
int rank = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
var measures = filterToNewestMeasuresPerType(measForWsResponse.join().getReturn().getResult().getMeasures().getMeasure());
|
||||||
|
|
||||||
|
|
||||||
|
for (var measure : measures) {
|
||||||
|
var measureType = MeasureType.fromMeasureCode(measure.getMeasureType().getMeasureType());
|
||||||
|
boolean maybeRelevant = measureType.map(MeasureType::containsRelevantDuty).orElse(false);
|
||||||
|
|
||||||
|
if (maybeRelevant) {
|
||||||
|
var duty = extractDuty(measure);
|
||||||
|
|
||||||
|
if (rank > measureType.get().ordinal() && duty.isPresent()) {
|
||||||
|
rank = measureType.get().ordinal();
|
||||||
|
selectedDuty = duty.get();
|
||||||
|
selectedMeasure = measure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customMeasures.add(new CustomMeasureDTO(
|
||||||
|
measure.getMeasureType().getMeasureType(),
|
||||||
|
measure.getMeasureType().getDescription(),
|
||||||
|
measure.getRegulationId(),
|
||||||
|
measure.getDutyRate()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedDuty != null) {
|
||||||
|
return new CustomDTO(false, selectedDuty, customMeasures, countryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
// just continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.TARIFF_RATE).map(PropertyDTO::getCurrentValue).map(Double::valueOf).map(d -> new CustomDTO(d, customMeasures, countryId)).orElseThrow(() -> new InternalErrorException("Unable to load default custom rate. Please contact support."));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<CustomDTO> getTariffRate(String hsCode, Integer countryId) {
|
||||||
|
return CompletableFuture.completedFuture(getTariffRateImmediate(hsCode, countryId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TaxationResolverResponse> getTariffRates(List<TaxationResolverRequest> requests) {
|
||||||
|
Map<TaxationResolverRequest, List<GoodsMeasForWsResponse>> goodMeasures = doRequests(requests);
|
||||||
|
return goodMeasures.keySet().stream().map(r -> mapToResponse(r, goodMeasures.get(r))).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaxationResolverResponse mapToResponse(TaxationResolverRequest request, List<GoodsMeasForWsResponse> measForWsResponse) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
String selectedHsCode = null;
|
||||||
|
Double selectedDuty = null;
|
||||||
|
String selectedMeasure = null;
|
||||||
|
double maxDuty = Double.MIN_VALUE;
|
||||||
|
double minDuty = Double.MAX_VALUE;
|
||||||
|
int rank = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
|
||||||
|
var resp = measForWsResponse.stream().collect(
|
||||||
|
Collectors.toMap(
|
||||||
|
r -> r,
|
||||||
|
r -> filterToNewestMeasuresPerType(r.getReturn().getResult().getMeasures().getMeasure())
|
||||||
|
));
|
||||||
|
|
||||||
|
for (var entry : resp.entrySet()) {
|
||||||
|
for (var measure : entry.getValue()) {
|
||||||
|
var measureType = MeasureType.fromMeasureCode(measure.getMeasureType().getMeasureType());
|
||||||
|
boolean maybeRelevant = measureType.map(MeasureType::containsRelevantDuty).orElse(false);
|
||||||
|
|
||||||
|
if (maybeRelevant) {
|
||||||
|
var duty = extractDuty(measure);
|
||||||
|
|
||||||
|
if (duty.isPresent()) {
|
||||||
|
|
||||||
|
maxDuty = Math.max(maxDuty, duty.get());
|
||||||
|
minDuty = Math.min(minDuty, duty.get());
|
||||||
|
|
||||||
|
if (rank > measureType.get().ordinal()) {
|
||||||
|
rank = measureType.get().ordinal();
|
||||||
|
selectedDuty = duty.get();
|
||||||
|
selectedMeasure = measureType.map(MeasureType::getMeasureCode).orElse(null);
|
||||||
|
selectedHsCode = entry.getKey().getReturn().getResult().getRequest().getGoodsCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedDuty != null && (maxDuty - minDuty < 2)) {
|
||||||
|
return new TaxationResolverResponse(selectedDuty, selectedMeasure, selectedHsCode, request.material(), request.countryId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
// just continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TaxationResolverResponse(null, null, null, request.material(), request.countryId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public record TaxationResolverSingleRequest(String hsCode, Integer countryId, TaxationResolverRequest origin) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record TaxationResolverRequest(Material material, Integer countryId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record TaxationResolverResponse(@Nullable Double tariffRate,
|
||||||
|
@Nullable String measureCode,
|
||||||
|
@Nullable String actualHsCode,
|
||||||
|
Material material,
|
||||||
|
Integer countryId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package de.avatic.lcc.service.api;
|
||||||
|
|
||||||
|
import de.avatic.lcc.model.zolltarifnummern.ZolltarifnummernResponse;
|
||||||
|
import de.avatic.lcc.model.zolltarifnummern.ZolltarifnummernResponseEntry;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ZolltarifnummernApiService {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ZolltarifnummernApiService.class);
|
||||||
|
|
||||||
|
private static final String API_V_1 = "https://www.zolltarifnummern.de/api/v1/cnSuggest";
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
public ZolltarifnummernApiService(RestTemplate restTemplate) {
|
||||||
|
this.restTemplate = restTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async("customLookupExecutor")
|
||||||
|
public CompletableFuture<Set<String>> getDeclarableHsCodes(String incompleteHsCode) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
String url = UriComponentsBuilder.fromUriString(API_V_1)
|
||||||
|
.queryParam("term", incompleteHsCode)
|
||||||
|
.queryParam("lang", "en")
|
||||||
|
.encode()
|
||||||
|
.toUriString();
|
||||||
|
|
||||||
|
var resp = restTemplate.getForObject(url, ZolltarifnummernResponse.class);
|
||||||
|
|
||||||
|
if (resp != null && resp.getSuggestions() != null) {
|
||||||
|
return CompletableFuture.completedFuture(resp.getSuggestions().stream()
|
||||||
|
.map(ZolltarifnummernResponseEntry::getCode)
|
||||||
|
.filter(s -> s.startsWith(incompleteHsCode))
|
||||||
|
.filter(s -> s.length() >= 10).collect(Collectors.toSet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.error("HS code lookup failed with exception \"{}\"", t.getMessage());
|
||||||
|
// just continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn("Unable to load tarif numbers for HS code {}", incompleteHsCode);
|
||||||
|
|
||||||
|
return CompletableFuture.completedFuture(Collections.emptySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
package de.avatic.lcc.service.calculation;
|
|
||||||
|
|
||||||
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
|
|
||||||
import de.avatic.lcc.dto.calculation.edit.SetDataDTO;
|
|
||||||
import de.avatic.lcc.model.db.packaging.PackagingDimension;
|
|
||||||
import de.avatic.lcc.model.db.premises.Premise;
|
|
||||||
import de.avatic.lcc.model.db.properties.PackagingProperty;
|
|
||||||
import de.avatic.lcc.model.db.properties.PackagingPropertyMappingId;
|
|
||||||
import de.avatic.lcc.repositories.MaterialRepository;
|
|
||||||
import de.avatic.lcc.repositories.NodeRepository;
|
|
||||||
import de.avatic.lcc.repositories.packaging.PackagingDimensionRepository;
|
|
||||||
import de.avatic.lcc.repositories.packaging.PackagingPropertiesRepository;
|
|
||||||
import de.avatic.lcc.repositories.packaging.PackagingRepository;
|
|
||||||
import de.avatic.lcc.repositories.premise.PremiseRepository;
|
|
||||||
import de.avatic.lcc.repositories.users.UserNodeRepository;
|
|
||||||
import de.avatic.lcc.service.api.CustomApiService;
|
|
||||||
import de.avatic.lcc.service.access.PremisesService;
|
|
||||||
import de.avatic.lcc.service.api.EUTaxationApiWrapperService;
|
|
||||||
import de.avatic.lcc.service.users.AuthorizationService;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class ChangeMaterialService {
|
|
||||||
|
|
||||||
private final PremiseRepository premiseRepository;
|
|
||||||
private final PremisesService premisesService;
|
|
||||||
private final CustomApiService customApiService;
|
|
||||||
private final NodeRepository nodeRepository;
|
|
||||||
private final UserNodeRepository userNodeRepository;
|
|
||||||
private final PackagingRepository packagingRepository;
|
|
||||||
private final PackagingDimensionRepository packagingDimensionRepository;
|
|
||||||
private final PackagingPropertiesRepository packagingPropertiesRepository;
|
|
||||||
private final MaterialRepository materialRepository;
|
|
||||||
private final AuthorizationService authorizationService;
|
|
||||||
private final EUTaxationApiWrapperService eUTaxationApiWrapperService;
|
|
||||||
|
|
||||||
public ChangeMaterialService(PremiseRepository premiseRepository, PremisesService premisesService, CustomApiService customApiService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, MaterialRepository materialRepository, AuthorizationService authorizationService, EUTaxationApiWrapperService eUTaxationApiWrapperService) {
|
|
||||||
this.premiseRepository = premiseRepository;
|
|
||||||
this.premisesService = premisesService;
|
|
||||||
this.customApiService = customApiService;
|
|
||||||
this.nodeRepository = nodeRepository;
|
|
||||||
this.userNodeRepository = userNodeRepository;
|
|
||||||
this.packagingRepository = packagingRepository;
|
|
||||||
this.packagingDimensionRepository = packagingDimensionRepository;
|
|
||||||
this.packagingPropertiesRepository = packagingPropertiesRepository;
|
|
||||||
this.materialRepository = materialRepository;
|
|
||||||
this.authorizationService = authorizationService;
|
|
||||||
this.eUTaxationApiWrapperService = eUTaxationApiWrapperService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public List<PremiseDetailDTO> setMaterial(SetDataDTO dto) {
|
|
||||||
var userId = authorizationService.getUserId();
|
|
||||||
|
|
||||||
Integer materialId = dto.getMaterialId();
|
|
||||||
List<Integer> premiseIds = dto.getPremiseId();
|
|
||||||
|
|
||||||
if (materialId == null || premiseIds == null || premiseIds.isEmpty())
|
|
||||||
throw new IllegalArgumentException("No supplier supplierNodeId or premises given");
|
|
||||||
|
|
||||||
|
|
||||||
// get all premises first.
|
|
||||||
List<Premise> allPremises = premiseRepository.getPremisesById(premiseIds);
|
|
||||||
|
|
||||||
// find resulting duplicates, split into "keep" and "to be deleted".
|
|
||||||
Map<Integer, Premise> uniqueMap = new HashMap<>();
|
|
||||||
List<Premise> premisesToBeDeleted = new ArrayList<>();
|
|
||||||
allPremises.forEach(p -> {
|
|
||||||
if (null != uniqueMap.putIfAbsent(p.getSupplierNodeId(), p)) premisesToBeDeleted.add(p);
|
|
||||||
});
|
|
||||||
Collection<Premise> premisesToProcess = uniqueMap.values();
|
|
||||||
|
|
||||||
// check if user owns all premises:
|
|
||||||
if (allPremises.stream().anyMatch(p -> !p.getUserId().equals(userId)))
|
|
||||||
throw new IllegalArgumentException("Not authorized to change material of premises owned by other users");
|
|
||||||
|
|
||||||
// check for any other collisions, and mark as "to be deleted":
|
|
||||||
premisesToBeDeleted.addAll(premisesToProcess.stream().map(p -> premiseRepository.getCollidingPremisesOnChange(userId, p.getId(), materialId, p.getSupplierNodeId())).flatMap(List::stream).toList());
|
|
||||||
|
|
||||||
if(dto.isUpdateMasterData()) {
|
|
||||||
|
|
||||||
var material = materialRepository.getById(materialId).orElseThrow(() -> new IllegalArgumentException("No material with id " + materialId));
|
|
||||||
|
|
||||||
for (var premise : premisesToProcess) {
|
|
||||||
var countryId = dto.isUserSupplierNode() ? userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow().getCountryId() : nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow().getCountryId();
|
|
||||||
var tariffRate = eUTaxationApiWrapperService.getTariffRateImmediate(material.getHsCode(), countryId);
|
|
||||||
premiseRepository.updateMaterial(Collections.singletonList(premise.getId()), material.getHsCode(), BigDecimal.valueOf(tariffRate.getValue()));
|
|
||||||
|
|
||||||
if (!dto.isUserSupplierNode()) {
|
|
||||||
var packaging = packagingRepository.getByMaterialIdAndSupplierId(dto.getMaterialId(), premise.getSupplierNodeId());
|
|
||||||
Optional<PackagingDimension> dimension = packaging.flatMap(p -> packagingDimensionRepository.getById(p.getHuId()));
|
|
||||||
|
|
||||||
if (dimension.isPresent()) {
|
|
||||||
boolean stackable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.get().getId(), PackagingPropertyMappingId.STACKABLE.name()).map(PackagingProperty::getValue).map(Boolean::valueOf).orElse(false);
|
|
||||||
boolean mixable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.get().getId(), PackagingPropertyMappingId.MIXABLE.name()).map(PackagingProperty::getValue).map(Boolean::valueOf).orElse(false);
|
|
||||||
premiseRepository.updatePackaging(Collections.singletonList(premise.getId()), dimension.get(), stackable, mixable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// actually update materialId.
|
|
||||||
premiseRepository.setMaterialId(premisesToProcess.stream().map(Premise::getId).toList(), materialId);
|
|
||||||
|
|
||||||
|
|
||||||
//delete all conflicting premises:
|
|
||||||
premisesService.delete(premisesToBeDeleted.stream().map(Premise::getId).toList());
|
|
||||||
|
|
||||||
|
|
||||||
return premisesService.getPremises(premisesToProcess.stream().map(Premise::getId).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
package de.avatic.lcc.service.calculation;
|
|
||||||
|
|
||||||
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
|
|
||||||
import de.avatic.lcc.dto.calculation.edit.SetDataDTO;
|
|
||||||
import de.avatic.lcc.model.db.nodes.Node;
|
|
||||||
import de.avatic.lcc.model.db.packaging.PackagingDimension;
|
|
||||||
import de.avatic.lcc.model.db.premises.Premise;
|
|
||||||
import de.avatic.lcc.model.db.premises.route.Destination;
|
|
||||||
import de.avatic.lcc.model.db.properties.PackagingProperty;
|
|
||||||
import de.avatic.lcc.model.db.properties.PackagingPropertyMappingId;
|
|
||||||
import de.avatic.lcc.repositories.NodeRepository;
|
|
||||||
import de.avatic.lcc.repositories.packaging.PackagingDimensionRepository;
|
|
||||||
import de.avatic.lcc.repositories.packaging.PackagingPropertiesRepository;
|
|
||||||
import de.avatic.lcc.repositories.packaging.PackagingRepository;
|
|
||||||
import de.avatic.lcc.repositories.premise.*;
|
|
||||||
import de.avatic.lcc.repositories.users.UserNodeRepository;
|
|
||||||
import de.avatic.lcc.service.api.CustomApiService;
|
|
||||||
import de.avatic.lcc.service.access.DestinationService;
|
|
||||||
import de.avatic.lcc.service.access.PremisesService;
|
|
||||||
import de.avatic.lcc.service.api.EUTaxationApiWrapperService;
|
|
||||||
import de.avatic.lcc.service.users.AuthorizationService;
|
|
||||||
import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class ChangeSupplierService {
|
|
||||||
|
|
||||||
private final PremiseRepository premiseRepository;
|
|
||||||
private final DestinationService destinationService;
|
|
||||||
private final RoutingService routingService;
|
|
||||||
private final PremisesService premisesService;
|
|
||||||
private final CustomApiService customApiService;
|
|
||||||
private final NodeRepository nodeRepository;
|
|
||||||
private final UserNodeRepository userNodeRepository;
|
|
||||||
private final PackagingRepository packagingRepository;
|
|
||||||
private final PackagingDimensionRepository packagingDimensionRepository;
|
|
||||||
private final PackagingPropertiesRepository packagingPropertiesRepository;
|
|
||||||
private final DestinationRepository destinationRepository;
|
|
||||||
private final RouteRepository routeRepository;
|
|
||||||
private final RouteSectionRepository routeSectionRepository;
|
|
||||||
private final RouteNodeRepository routeNodeRepository;
|
|
||||||
private final AuthorizationService authorizationService;
|
|
||||||
private final EUTaxationApiWrapperService eUTaxationApiWrapperService;
|
|
||||||
|
|
||||||
public ChangeSupplierService(PremiseRepository premiseRepository, DestinationService destinationService, RoutingService routingService, PremisesService premisesService, CustomApiService customApiService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, DestinationRepository destinationRepository, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, AuthorizationService authorizationService, EUTaxationApiWrapperService eUTaxationApiWrapperService) {
|
|
||||||
this.premiseRepository = premiseRepository;
|
|
||||||
this.destinationService = destinationService;
|
|
||||||
this.routingService = routingService;
|
|
||||||
this.premisesService = premisesService;
|
|
||||||
this.customApiService = customApiService;
|
|
||||||
this.nodeRepository = nodeRepository;
|
|
||||||
this.userNodeRepository = userNodeRepository;
|
|
||||||
this.packagingRepository = packagingRepository;
|
|
||||||
this.packagingDimensionRepository = packagingDimensionRepository;
|
|
||||||
this.packagingPropertiesRepository = packagingPropertiesRepository;
|
|
||||||
this.destinationRepository = destinationRepository;
|
|
||||||
this.routeRepository = routeRepository;
|
|
||||||
this.routeSectionRepository = routeSectionRepository;
|
|
||||||
this.routeNodeRepository = routeNodeRepository;
|
|
||||||
this.authorizationService = authorizationService;
|
|
||||||
this.eUTaxationApiWrapperService = eUTaxationApiWrapperService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public List<PremiseDetailDTO> setSupplier(SetDataDTO dto) {
|
|
||||||
|
|
||||||
var userId = authorizationService.getUserId();
|
|
||||||
Integer supplierNodeId = dto.getSupplierNodeId();
|
|
||||||
List<Integer> premiseIds = dto.getPremiseId();
|
|
||||||
|
|
||||||
if (supplierNodeId == null || premiseIds == null || premiseIds.isEmpty())
|
|
||||||
throw new InvalidArgumentException("No supplier supplierNodeId or premises given");
|
|
||||||
|
|
||||||
Node supplier = dto.isUserSupplierNode() ? userNodeRepository.getById(supplierNodeId).orElseThrow() : nodeRepository.getById(supplierNodeId).orElseThrow();
|
|
||||||
|
|
||||||
// get all premises first.
|
|
||||||
List<Premise> allPremises = premiseRepository.getPremisesById(premiseIds);
|
|
||||||
|
|
||||||
// find resulting duplicates, split into "keep" and "to be deleted".
|
|
||||||
Map<Integer, Premise> uniqueMap = new HashMap<>();
|
|
||||||
List<Premise> premisesToBeDeleted = new ArrayList<>();
|
|
||||||
allPremises.forEach(p -> {
|
|
||||||
if (null != uniqueMap.putIfAbsent(p.getMaterialId(), p)) premisesToBeDeleted.add(p);
|
|
||||||
});
|
|
||||||
Collection<Premise> premisesToProcess = uniqueMap.values();
|
|
||||||
|
|
||||||
// check if user owns all premises:
|
|
||||||
if (allPremises.stream().anyMatch(p -> !p.getUserId().equals(userId)))
|
|
||||||
throw new IllegalArgumentException("Not authorized to change suppliers of premises owned by other users");
|
|
||||||
|
|
||||||
// check for any other collisions, and mark as "to be deleted":
|
|
||||||
premisesToBeDeleted.addAll(premisesToProcess.stream().map(p -> premiseRepository.getCollidingPremisesOnChange(userId, p.getId(), p.getMaterialId(), supplierNodeId)).flatMap(List::stream).toList());
|
|
||||||
|
|
||||||
// delete all routes of all destinations
|
|
||||||
destinationService.deleteAllDestinationsByPremiseId(premisesToProcess.stream().map(Premise::getId).toList(), true);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//recalculate routes:
|
|
||||||
for (Premise premise : premisesToProcess) {
|
|
||||||
List<Destination> destination = destinationRepository.getByPremiseId(premise.getId());
|
|
||||||
for (Destination d : destination) {
|
|
||||||
Node destinationNode = nodeRepository.getById(d.getDestinationNodeId()).orElseThrow();
|
|
||||||
destinationService.findRouteAndSave(d.getId(), destinationNode, supplier, dto.isUserSupplierNode() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//update master data:
|
|
||||||
if (dto.isUpdateMasterData()) {
|
|
||||||
for (var premise : premisesToProcess) {
|
|
||||||
var tariffRate = eUTaxationApiWrapperService.getTariffRateImmediate(premise.getHsCode(), supplier.getCountryId());
|
|
||||||
premiseRepository.updateTariffRate(premise.getId(), tariffRate.getValue());
|
|
||||||
|
|
||||||
if (!dto.isUserSupplierNode()) {
|
|
||||||
var packaging = packagingRepository.getByMaterialIdAndSupplierId(premise.getMaterialId(), supplierNodeId);
|
|
||||||
Optional<PackagingDimension> dimension = packaging.flatMap(p -> packagingDimensionRepository.getById(p.getHuId()));
|
|
||||||
|
|
||||||
if (dimension.isPresent()) {
|
|
||||||
boolean stackable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.get().getId(), PackagingPropertyMappingId.STACKABLE.name()).map(PackagingProperty::getValue).map(Boolean::valueOf).orElse(false);
|
|
||||||
boolean mixable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.get().getId(), PackagingPropertyMappingId.MIXABLE.name()).map(PackagingProperty::getValue).map(Boolean::valueOf).orElse(false);
|
|
||||||
premiseRepository.updatePackaging(Collections.singletonList(premise.getId()), dimension.get(), stackable, mixable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// actually update supplier supplierNodeId.
|
|
||||||
premiseRepository.setSupplierId(premisesToProcess.stream().map(Premise::getId).toList(), supplier, dto.isUserSupplierNode());
|
|
||||||
|
|
||||||
|
|
||||||
//delete all conflicting premises:
|
|
||||||
if(!premisesToBeDeleted.isEmpty())
|
|
||||||
premisesService.delete(premisesToBeDeleted.stream().map(Premise::getId).toList());
|
|
||||||
|
|
||||||
|
|
||||||
return premisesService.getPremises(premisesToProcess.stream().map(Premise::getId).toList());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package de.avatic.lcc.service.calculation;
|
|
||||||
|
|
||||||
import de.avatic.lcc.repositories.NomenclatureRepository;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class NomenclatureService {
|
|
||||||
|
|
||||||
private final NomenclatureRepository nomenclatureRepository;
|
|
||||||
|
|
||||||
public NomenclatureService(NomenclatureRepository nomenclatureRepository) {
|
|
||||||
this.nomenclatureRepository = nomenclatureRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public List<String> getNomenclature(String hsCode) {
|
|
||||||
return nomenclatureRepository.searchHsCode(hsCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -15,7 +15,7 @@ import de.avatic.lcc.repositories.premise.PremiseRepository;
|
||||||
import de.avatic.lcc.repositories.users.UserNodeRepository;
|
import de.avatic.lcc.repositories.users.UserNodeRepository;
|
||||||
import de.avatic.lcc.service.api.CustomApiService;
|
import de.avatic.lcc.service.api.CustomApiService;
|
||||||
import de.avatic.lcc.service.access.DestinationService;
|
import de.avatic.lcc.service.access.DestinationService;
|
||||||
import de.avatic.lcc.service.api.EUTaxationApiWrapperService;
|
import de.avatic.lcc.service.api.TaxationResolverService;
|
||||||
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
|
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
|
||||||
import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
|
import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
|
||||||
import de.avatic.lcc.service.users.AuthorizationService;
|
import de.avatic.lcc.service.users.AuthorizationService;
|
||||||
|
|
@ -41,11 +41,10 @@ public class PremiseCreationService {
|
||||||
private final PackagingRepository packagingRepository;
|
private final PackagingRepository packagingRepository;
|
||||||
private final PackagingDimensionRepository packagingDimensionRepository;
|
private final PackagingDimensionRepository packagingDimensionRepository;
|
||||||
private final PackagingPropertiesRepository packagingPropertiesRepository;
|
private final PackagingPropertiesRepository packagingPropertiesRepository;
|
||||||
private final CustomApiService customApiService;
|
|
||||||
private final AuthorizationService authorizationService;
|
private final AuthorizationService authorizationService;
|
||||||
private final EUTaxationApiWrapperService eUTaxationApiWrapperService;
|
private final TaxationResolverService taxationResolverService;
|
||||||
|
|
||||||
public PremiseCreationService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DestinationService destinationService, UserNodeRepository userNodeRepository, NodeRepository nodeRepository, MaterialRepository materialRepository, DimensionTransformer dimensionTransformer, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, CustomApiService customApiService, AuthorizationService authorizationService, EUTaxationApiWrapperService eUTaxationApiWrapperService) {
|
public PremiseCreationService(PremiseRepository premiseRepository, PremiseTransformer premiseTransformer, DestinationService destinationService, UserNodeRepository userNodeRepository, NodeRepository nodeRepository, MaterialRepository materialRepository, DimensionTransformer dimensionTransformer, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, CustomApiService customApiService, AuthorizationService authorizationService, TaxationResolverService taxationResolverService) {
|
||||||
this.premiseRepository = premiseRepository;
|
this.premiseRepository = premiseRepository;
|
||||||
this.premiseTransformer = premiseTransformer;
|
this.premiseTransformer = premiseTransformer;
|
||||||
this.destinationService = destinationService;
|
this.destinationService = destinationService;
|
||||||
|
|
@ -56,9 +55,9 @@ public class PremiseCreationService {
|
||||||
this.packagingRepository = packagingRepository;
|
this.packagingRepository = packagingRepository;
|
||||||
this.packagingDimensionRepository = packagingDimensionRepository;
|
this.packagingDimensionRepository = packagingDimensionRepository;
|
||||||
this.packagingPropertiesRepository = packagingPropertiesRepository;
|
this.packagingPropertiesRepository = packagingPropertiesRepository;
|
||||||
this.customApiService = customApiService;
|
|
||||||
this.authorizationService = authorizationService;
|
this.authorizationService = authorizationService;
|
||||||
this.eUTaxationApiWrapperService = eUTaxationApiWrapperService;
|
this.taxationResolverService = taxationResolverService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
|
|
@ -76,17 +75,19 @@ public class PremiseCreationService {
|
||||||
premises.forEach(p -> verifyNode(p, userId));
|
premises.forEach(p -> verifyNode(p, userId));
|
||||||
verifyMaterial(materialIds);
|
verifyMaterial(materialIds);
|
||||||
|
|
||||||
|
List<TaxationResolverService.TaxationResolverResponse> tariffs = taxationResolverService.getTariffRates(premises.stream().map(p -> new TaxationResolverService.TaxationResolverRequest(materialRepository.getById(p.getMaterialId()).orElseThrow(), p.getCountryId())).distinct().toList());
|
||||||
|
|
||||||
premises.forEach(p -> {
|
premises.forEach(p -> {
|
||||||
if (p.getPremise() == null) { // create new
|
if (p.getPremise() == null) { // create new
|
||||||
|
|
||||||
p.setId(premiseRepository.insert(p.getMaterialId(), p.getSupplierId(), p.getUserSupplierId(), p.getGeoLat(), p.getGeoLng(), p.getCountryId(), userId));
|
p.setId(premiseRepository.insert(p.getMaterialId(), p.getSupplierId(), p.getUserSupplierId(), p.getGeoLat(), p.getGeoLng(), p.getCountryId(), userId));
|
||||||
fillPremise(p, userId);
|
fillPremise(p, tariffs, userId);
|
||||||
|
|
||||||
} else if (p.getPremise().getState().equals(PremiseState.DRAFT)) { // recycle
|
} else if (p.getPremise().getState().equals(PremiseState.DRAFT)) { // recycle
|
||||||
p.setId(p.getPremise().getId());
|
p.setId(p.getPremise().getId());
|
||||||
if (createEmpty) {
|
if (createEmpty) {
|
||||||
// reset to defaults.
|
// reset to defaults.
|
||||||
fillPremise(p, userId);
|
fillPremise(p, tariffs, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (p.getPremise().getState().equals(PremiseState.COMPLETED)) {
|
} else if (p.getPremise().getState().equals(PremiseState.COMPLETED)) {
|
||||||
|
|
@ -105,13 +106,13 @@ public class PremiseCreationService {
|
||||||
private void copyPremise(TemporaryPremise p, Integer userId) {
|
private void copyPremise(TemporaryPremise p, Integer userId) {
|
||||||
var old = p.getPremise();
|
var old = p.getPremise();
|
||||||
|
|
||||||
premiseRepository.updateMaterial(Collections.singletonList(p.getId()), old.getHsCode(), old.getTariffRate());
|
premiseRepository.updateMaterial(Collections.singletonList(p.getId()), old.getHsCode(), old.getTariffRate(), old.getTariffUnlocked());
|
||||||
premiseRepository.updatePrice(Collections.singletonList(p.getId()), old.getMaterialCost(), old.getFcaEnabled(), old.getOverseaShare());
|
premiseRepository.updatePrice(Collections.singletonList(p.getId()), old.getMaterialCost(), old.getFcaEnabled(), old.getOverseaShare());
|
||||||
premiseRepository.updatePackaging(Collections.singletonList(p.getId()), dimensionTransformer.toDimensionEntity(old), old.getHuStackable(), old.getHuMixable());
|
premiseRepository.updatePackaging(Collections.singletonList(p.getId()), dimensionTransformer.toDimensionEntity(old), old.getHuStackable(), old.getHuMixable());
|
||||||
premiseRepository.setPackagingId(p.getId(), old.getPackagingId());
|
premiseRepository.setPackagingId(p.getId(), old.getPackagingId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillPremise(TemporaryPremise p, Integer userId) {
|
private void fillPremise(TemporaryPremise p, List<TaxationResolverService.TaxationResolverResponse> tariffs, Integer userId) {
|
||||||
|
|
||||||
if (!p.isUserSupplier()) {
|
if (!p.isUserSupplier()) {
|
||||||
var packaging = packagingRepository.getByMaterialIdAndSupplierId(p.getMaterialId(), p.getSupplierId());
|
var packaging = packagingRepository.getByMaterialIdAndSupplierId(p.getMaterialId(), p.getSupplierId());
|
||||||
|
|
@ -126,18 +127,13 @@ public class PremiseCreationService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var material = materialRepository.getById(p.getMaterialId());
|
tariffs.stream()
|
||||||
material.ifPresent(value -> premiseRepository.updateMaterial(Collections.singletonList(p.getId()), value.getHsCode(), BigDecimal.valueOf(eUTaxationApiWrapperService.getTariffRateImmediate(value.getHsCode(), getCountryId(p)).getValue())));
|
.filter(r -> r.material().getId().equals(p.getMaterialId()))
|
||||||
|
.findFirst()
|
||||||
}
|
.ifPresent(value -> premiseRepository.updateMaterial(Collections.singletonList(
|
||||||
|
p.getId()),
|
||||||
private Integer getCountryId(TemporaryPremise p) {
|
value.actualHsCode(),
|
||||||
|
value.tariffRate() == null ? null : BigDecimal.valueOf(value.tariffRate())));
|
||||||
if (p.isUserSupplier()) {
|
|
||||||
return userNodeRepository.getById(p.getUserSupplierId()).orElseThrow().getCountryId();
|
|
||||||
} else {
|
|
||||||
return nodeRepository.getById(p.getSupplierId()).orElseThrow().getCountryId();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void findExistingPremise(TemporaryPremise premise, boolean createEmpty, Integer userId) {
|
private void findExistingPremise(TemporaryPremise premise, boolean createEmpty, Integer userId) {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import de.avatic.lcc.repositories.rates.MatrixRateRepository;
|
||||||
import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
|
import de.avatic.lcc.repositories.rates.ValidityPeriodRepository;
|
||||||
import de.avatic.lcc.service.access.PropertyService;
|
import de.avatic.lcc.service.access.PropertyService;
|
||||||
import de.avatic.lcc.service.api.CustomApiService;
|
import de.avatic.lcc.service.api.CustomApiService;
|
||||||
import de.avatic.lcc.service.api.EUTaxationApiWrapperService;
|
import de.avatic.lcc.service.api.TaxationResolverService;
|
||||||
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
|
import de.avatic.lcc.service.transformer.generic.DimensionTransformer;
|
||||||
import de.avatic.lcc.util.exception.internalerror.PremiseValidationError;
|
import de.avatic.lcc.util.exception.internalerror.PremiseValidationError;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
|
@ -30,11 +30,9 @@ import org.springframework.stereotype.Service;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.temporal.TemporalUnit;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class PreCalculationCheckService {
|
public class PreCalculationCheckService {
|
||||||
|
|
@ -56,9 +54,9 @@ public class PreCalculationCheckService {
|
||||||
private final ValidityPeriodRepository validityPeriodRepository;
|
private final ValidityPeriodRepository validityPeriodRepository;
|
||||||
private final PropertySetRepository propertySetRepository;
|
private final PropertySetRepository propertySetRepository;
|
||||||
private final PropertyRepository propertyRepository;
|
private final PropertyRepository propertyRepository;
|
||||||
private final EUTaxationApiWrapperService eUTaxationApiWrapperService;
|
private final TaxationResolverService eUTaxationResolverService;
|
||||||
|
|
||||||
public PreCalculationCheckService(PremiseRepository premiseRepository, CustomApiService customApiService, DestinationRepository destinationRepository, RouteRepository routeRepository, NodeRepository nodeRepository, DimensionTransformer dimensionTransformer, PropertyService propertyService, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, MatrixRateRepository matrixRateRepository, ContainerRateRepository containerRateRepository, ValidityPeriodRepository validityPeriodRepository, PropertySetRepository propertySetRepository, PropertyRepository propertyRepository, EUTaxationApiWrapperService eUTaxationApiWrapperService) {
|
public PreCalculationCheckService(PremiseRepository premiseRepository, CustomApiService customApiService, DestinationRepository destinationRepository, RouteRepository routeRepository, NodeRepository nodeRepository, DimensionTransformer dimensionTransformer, PropertyService propertyService, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, MatrixRateRepository matrixRateRepository, ContainerRateRepository containerRateRepository, ValidityPeriodRepository validityPeriodRepository, PropertySetRepository propertySetRepository, PropertyRepository propertyRepository, TaxationResolverService eUTaxationResolverService) {
|
||||||
this.premiseRepository = premiseRepository;
|
this.premiseRepository = premiseRepository;
|
||||||
this.customApiService = customApiService;
|
this.customApiService = customApiService;
|
||||||
this.destinationRepository = destinationRepository;
|
this.destinationRepository = destinationRepository;
|
||||||
|
|
@ -74,7 +72,7 @@ public class PreCalculationCheckService {
|
||||||
this.validityPeriodRepository = validityPeriodRepository;
|
this.validityPeriodRepository = validityPeriodRepository;
|
||||||
this.propertySetRepository = propertySetRepository;
|
this.propertySetRepository = propertySetRepository;
|
||||||
this.propertyRepository = propertyRepository;
|
this.propertyRepository = propertyRepository;
|
||||||
this.eUTaxationApiWrapperService = eUTaxationApiWrapperService;
|
this.eUTaxationResolverService = eUTaxationResolverService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Async("calculationExecutor")
|
@Async("calculationExecutor")
|
||||||
|
|
@ -327,7 +325,7 @@ public class PreCalculationCheckService {
|
||||||
|
|
||||||
private void materialCheck(Premise premise) {
|
private void materialCheck(Premise premise) {
|
||||||
|
|
||||||
var isDeclarable = eUTaxationApiWrapperService.validate(premise.getHsCode());
|
var isDeclarable = eUTaxationResolverService.validate(premise.getHsCode());
|
||||||
|
|
||||||
if (!isDeclarable)
|
if (!isDeclarable)
|
||||||
throw new PremiseValidationError("Invalid HS code.");
|
throw new PremiseValidationError("Invalid HS code.");
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,7 @@ public class PremiseTransformer {
|
||||||
|
|
||||||
dto.setHsCode(entity.getHsCode());
|
dto.setHsCode(entity.getHsCode());
|
||||||
dto.setTariffRate(entity.getTariffRate() == null ? null : entity.getTariffRate().doubleValue());
|
dto.setTariffRate(entity.getTariffRate() == null ? null : entity.getTariffRate().doubleValue());
|
||||||
|
dto.setTariffUnlocked(entity.getTariffUnlocked());
|
||||||
|
|
||||||
dto.setFcaEnabled(entity.getFcaEnabled());
|
dto.setFcaEnabled(entity.getFcaEnabled());
|
||||||
dto.setOverseaShare(entity.getOverseaShare() == null ? null : entity.getOverseaShare().doubleValue());
|
dto.setOverseaShare(entity.getOverseaShare() == null ? null : entity.getOverseaShare().doubleValue());
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -214,8 +214,10 @@ CREATE TABLE IF NOT EXISTS outbound_country_mapping
|
||||||
CREATE TABLE IF NOT EXISTS distance_matrix
|
CREATE TABLE IF NOT EXISTS distance_matrix
|
||||||
(
|
(
|
||||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
from_node_id INT NOT NULL,
|
from_node_id INT DEFAULT NULL,
|
||||||
to_node_id INT NOT NULL,
|
to_node_id INT DEFAULT NULL,
|
||||||
|
from_user_node_id INT DEFAULT NULL,
|
||||||
|
to_user_node_id INT DEFAULT NULL,
|
||||||
from_geo_lat DECIMAL(8, 4) CHECK (from_geo_lat BETWEEN -90 AND 90),
|
from_geo_lat DECIMAL(8, 4) CHECK (from_geo_lat BETWEEN -90 AND 90),
|
||||||
from_geo_lng DECIMAL(8, 4) CHECK (from_geo_lng BETWEEN -180 AND 180),
|
from_geo_lng DECIMAL(8, 4) CHECK (from_geo_lng BETWEEN -180 AND 180),
|
||||||
to_geo_lat DECIMAL(8, 4) CHECK (to_geo_lat BETWEEN -90 AND 90),
|
to_geo_lat DECIMAL(8, 4) CHECK (to_geo_lat BETWEEN -90 AND 90),
|
||||||
|
|
@ -225,9 +227,21 @@ CREATE TABLE IF NOT EXISTS distance_matrix
|
||||||
state CHAR(10) NOT NULL,
|
state CHAR(10) NOT NULL,
|
||||||
FOREIGN KEY (from_node_id) REFERENCES node (id),
|
FOREIGN KEY (from_node_id) REFERENCES node (id),
|
||||||
FOREIGN KEY (to_node_id) REFERENCES node (id),
|
FOREIGN KEY (to_node_id) REFERENCES node (id),
|
||||||
|
FOREIGN KEY (from_user_node_id) REFERENCES sys_user_node (id),
|
||||||
|
FOREIGN KEY (to_user_node_id) REFERENCES sys_user_node (id),
|
||||||
CONSTRAINT `chk_distance_matrix_state` CHECK (`state` IN
|
CONSTRAINT `chk_distance_matrix_state` CHECK (`state` IN
|
||||||
('VALID', 'STALE')),
|
('VALID', 'STALE')),
|
||||||
INDEX idx_from_to_nodes (from_node_id, to_node_id)
|
CONSTRAINT `chk_from_node_xor` CHECK (
|
||||||
|
(from_node_id IS NOT NULL AND from_user_node_id IS NULL) OR
|
||||||
|
(from_node_id IS NULL AND from_user_node_id IS NOT NULL)
|
||||||
|
),
|
||||||
|
CONSTRAINT `chk_to_node_xor` CHECK (
|
||||||
|
(to_node_id IS NOT NULL AND to_user_node_id IS NULL) OR
|
||||||
|
(to_node_id IS NULL AND to_user_node_id IS NOT NULL)
|
||||||
|
),
|
||||||
|
INDEX idx_from_to_nodes (from_node_id, to_node_id),
|
||||||
|
INDEX idx_user_from_to_nodes (from_user_node_id, to_user_node_id),
|
||||||
|
CONSTRAINT uk_nodes_unique UNIQUE (from_node_id, to_node_id, from_user_node_id, to_user_node_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- container rates
|
-- container rates
|
||||||
|
|
@ -375,7 +389,9 @@ CREATE TABLE IF NOT EXISTS premise
|
||||||
is_fca_enabled BOOLEAN DEFAULT FALSE,
|
is_fca_enabled BOOLEAN DEFAULT FALSE,
|
||||||
oversea_share DECIMAL(8, 4) DEFAULT NULL,
|
oversea_share DECIMAL(8, 4) DEFAULT NULL,
|
||||||
hs_code CHAR(11) DEFAULT NULL,
|
hs_code CHAR(11) DEFAULT NULL,
|
||||||
|
tariff_measure INT UNSIGNED DEFAULT NULL COMMENT 'measure code of the selected tariff',
|
||||||
tariff_rate DECIMAL(8, 4) DEFAULT NULL,
|
tariff_rate DECIMAL(8, 4) DEFAULT NULL,
|
||||||
|
tariff_unlocked BOOLEAN DEFAULT FALSE,
|
||||||
state CHAR(10) NOT NULL DEFAULT 'DRAFT',
|
state CHAR(10) NOT NULL DEFAULT 'DRAFT',
|
||||||
individual_hu_length INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
individual_hu_length INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
||||||
individual_hu_height INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
individual_hu_height INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
||||||
|
|
|
||||||
|
|
@ -59,13 +59,6 @@ CREATE TABLE IF NOT EXISTS `country`
|
||||||
CHECK (`region_code` IN ('EMEA', 'LATAM', 'APAC', 'NAM'))
|
CHECK (`region_code` IN ('EMEA', 'LATAM', 'APAC', 'NAM'))
|
||||||
) COMMENT 'Master data table for country information and regional classification';
|
) COMMENT 'Master data table for country information and regional classification';
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `nomenclature`
|
|
||||||
(
|
|
||||||
`hs_code` VARCHAR(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
UNIQUE KEY `uk_nomenclature_name` (`hs_code`)
|
|
||||||
) COMMENT 'Master data table for nomenclature information';
|
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `country_property_type`
|
CREATE TABLE IF NOT EXISTS `country_property_type`
|
||||||
(
|
(
|
||||||
`id` INT NOT NULL AUTO_INCREMENT,
|
`id` INT NOT NULL AUTO_INCREMENT,
|
||||||
|
|
@ -221,8 +214,10 @@ CREATE TABLE IF NOT EXISTS outbound_country_mapping
|
||||||
CREATE TABLE IF NOT EXISTS distance_matrix
|
CREATE TABLE IF NOT EXISTS distance_matrix
|
||||||
(
|
(
|
||||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
from_node_id INT NOT NULL,
|
from_node_id INT DEFAULT NULL,
|
||||||
to_node_id INT NOT NULL,
|
to_node_id INT DEFAULT NULL,
|
||||||
|
from_user_node_id INT DEFAULT NULL,
|
||||||
|
to_user_node_id INT DEFAULT NULL,
|
||||||
from_geo_lat DECIMAL(8, 4) CHECK (from_geo_lat BETWEEN -90 AND 90),
|
from_geo_lat DECIMAL(8, 4) CHECK (from_geo_lat BETWEEN -90 AND 90),
|
||||||
from_geo_lng DECIMAL(8, 4) CHECK (from_geo_lng BETWEEN -180 AND 180),
|
from_geo_lng DECIMAL(8, 4) CHECK (from_geo_lng BETWEEN -180 AND 180),
|
||||||
to_geo_lat DECIMAL(8, 4) CHECK (to_geo_lat BETWEEN -90 AND 90),
|
to_geo_lat DECIMAL(8, 4) CHECK (to_geo_lat BETWEEN -90 AND 90),
|
||||||
|
|
@ -232,9 +227,21 @@ CREATE TABLE IF NOT EXISTS distance_matrix
|
||||||
state CHAR(10) NOT NULL,
|
state CHAR(10) NOT NULL,
|
||||||
FOREIGN KEY (from_node_id) REFERENCES node (id),
|
FOREIGN KEY (from_node_id) REFERENCES node (id),
|
||||||
FOREIGN KEY (to_node_id) REFERENCES node (id),
|
FOREIGN KEY (to_node_id) REFERENCES node (id),
|
||||||
|
FOREIGN KEY (from_user_node_id) REFERENCES sys_user_node (id),
|
||||||
|
FOREIGN KEY (to_user_node_id) REFERENCES sys_user_node (id),
|
||||||
CONSTRAINT `chk_distance_matrix_state` CHECK (`state` IN
|
CONSTRAINT `chk_distance_matrix_state` CHECK (`state` IN
|
||||||
('VALID', 'STALE')),
|
('VALID', 'STALE')),
|
||||||
INDEX idx_from_to_nodes (from_node_id, to_node_id)
|
CONSTRAINT `chk_from_node_xor` CHECK (
|
||||||
|
(from_node_id IS NOT NULL AND from_user_node_id IS NULL) OR
|
||||||
|
(from_node_id IS NULL AND from_user_node_id IS NOT NULL)
|
||||||
|
),
|
||||||
|
CONSTRAINT `chk_to_node_xor` CHECK (
|
||||||
|
(to_node_id IS NOT NULL AND to_user_node_id IS NULL) OR
|
||||||
|
(to_node_id IS NULL AND to_user_node_id IS NOT NULL)
|
||||||
|
),
|
||||||
|
INDEX idx_from_to_nodes (from_node_id, to_node_id),
|
||||||
|
INDEX idx_user_from_to_nodes (from_user_node_id, to_user_node_id),
|
||||||
|
CONSTRAINT uk_nodes_unique UNIQUE (from_node_id, to_node_id, from_user_node_id, to_user_node_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- container rates
|
-- container rates
|
||||||
|
|
@ -382,7 +389,9 @@ CREATE TABLE IF NOT EXISTS premise
|
||||||
is_fca_enabled BOOLEAN DEFAULT FALSE,
|
is_fca_enabled BOOLEAN DEFAULT FALSE,
|
||||||
oversea_share DECIMAL(8, 4) DEFAULT NULL,
|
oversea_share DECIMAL(8, 4) DEFAULT NULL,
|
||||||
hs_code CHAR(11) DEFAULT NULL,
|
hs_code CHAR(11) DEFAULT NULL,
|
||||||
|
tariff_measure CHAR(16) DEFAULT NULL COMMENT 'measure code of the selected tariff',
|
||||||
tariff_rate DECIMAL(8, 4) DEFAULT NULL,
|
tariff_rate DECIMAL(8, 4) DEFAULT NULL,
|
||||||
|
tariff_unlocked BOOLEAN DEFAULT FALSE,
|
||||||
state CHAR(10) NOT NULL DEFAULT 'DRAFT',
|
state CHAR(10) NOT NULL DEFAULT 'DRAFT',
|
||||||
individual_hu_length INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
individual_hu_length INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
||||||
individual_hu_height INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
individual_hu_height INT UNSIGNED COMMENT 'user entered dimensions in mm (if system-wide packaging is used, packaging dimensions are copied here after creation)',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue