Integrated tariff rate calculations and customs API:

- **Backend**: Introduced `EUTaxationApiService` for TARIC data retrieval and integrated tariff rate lookup functionality. Added supporting DTOs and updated services to handle custom measures and updated logic for material and supplier changes.
- **Frontend**: Enhanced mass and single edit calculation components to include tariff rate lookup functionality and warnings. Introduced `useCustomsStore` for state management of customs data.
- **Database**: Added `nomenclature` table for storing HS code-related information.
- **Other**: Configured SOAP client for TARIC API and added logging for debugging.
This commit is contained in:
Jan 2025-11-06 17:35:41 +01:00
parent be1ef5091b
commit ce6b8dd9b0
58 changed files with 19749 additions and 51 deletions

34
pom.xml
View file

@ -49,6 +49,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter</artifactId>
@ -162,6 +167,15 @@
<artifactId>flyway-mysql</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.2</version>
</dependency>
</dependencies>
<dependencyManagement>
@ -179,6 +193,26 @@
<build>
<plugins>
<plugin>
<groupId>org.jvnet.jaxb</groupId>
<artifactId>jaxb-maven-plugin</artifactId>
<version>4.0.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaDirectory>${project.basedir}/src/main/resources/wsdl</schemaDirectory>
<schemaIncludes>
<include>*.wsdl</include>
</schemaIncludes>
<generatePackage>eu.europa.ec.taxation.taric.client</generatePackage>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>

View file

@ -10,7 +10,7 @@
<div class="edit-calculation-cell-line">{{ premise.material.part_number }}</div>
<div class="edit-calculation-cell-line edit-calculation-cell-subline" v-if="premise.hs_code">
HS Code:
{{ premise.material.hs_code }}
{{ premise.hs_code }}
</div>
<div class="edit-calculation-cell-line edit-calculation-cell-subline"
v-if="premise.tariff_rate && premise.tariff_rate > 0">

View file

@ -1,6 +1,7 @@
<template>
<div class="container" :class="{ 'responsive': responsive }" @focusout="focusLost">
<div class="field-group" v-if="!hideDescription">
<div class="caption-column">Part number</div>
<div class="input-column">
@ -19,24 +20,61 @@
<div class="caption-column">HS code</div>
<div class="input-column">
<div class="hs-code-container">
<autosuggest-searchbar :activate-watcher="true" :fetch-suggestions="fetchHsCode" :initial-value="hsCode"
<autosuggest-searchbar ref="hsCodeSearchbar" :activate-watcher="true" :fetch-suggestions="fetchHsCode"
:initial-value="hsCode"
@selected="hsCodeSelected" @blur="hsCodeChanged"
placeholder="Find hs code" no-results-text="Not found."></autosuggest-searchbar>
</div>
</div>
</div>
<div class="field-group">
<div class="field-group" :class="{ 'disabled': !disableTariff }">
<div class="caption-column">Tariff rate [%]</div>
<div class="input-column">
<div class="text-container input-field-tariffrate">
<input ref="tariffRateInput" :value="tariffRatePercent" @blur="validateTariffRate"
<input ref="tariffRateInput" :value="tariffRatePercent" @blur="validateTariffRateEvent"
class="input-field"
:disabled="!disableTariff"
autocomplete="off"/>
<ph-warning v-if="showTariffWarning" size="18" class="warning-icon"></ph-warning>
<div v-if="showTariffWarning" class="tariff-rate-info">
<div>
<div class="tariff-rate-info-header">
<ph-warning size="24"></ph-warning>
<div v-if="tariffIsDefault">
<div>Using default value</div>
<div class="tariff-rate-info-headerbox">Automatic determination failed</div>
</div>
<div v-else>
<div>More than one custom measure found</div>
<div class="tariff-rate-info-headerbox">Please review carefully</div>
</div>
</div>
<div class="tariff-rate-info-container">
<template v-for="measure in measures" :key="measure.code">
<div class="tariff-rate-info-cell-measure">Measure {{ measure.code }}</div>
<div>Regulation {{ measure.regulation }}</div>
<div class="tariff-rate-info-cell-tariffrate">{{ measure.tariffRate }}</div>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="field-group" v-if="showCheckbox">
<div class="caption-column"></div>
<div class="input-column">
<checkbox :checked="false" @checkbox-changed="checkBoxChanged">tariff lookup on close</checkbox>
</div>
</div>
</div>
</template>
@ -48,21 +86,24 @@ import Flag from "@/components/UI/Flag.vue";
import InputField from "@/components/UI/InputField.vue";
import AutosuggestSearchbar from "@/components/UI/AutoSuggestSearchBar.vue";
import ModalDialog from "@/components/UI/ModalDialog.vue";
import {PhArrowCounterClockwise} from "@phosphor-icons/vue";
import {PhArrowCounterClockwise, PhWarning} from "@phosphor-icons/vue";
import {useMaterialStore} from "@/store/material.js";
import {mapStores} from "pinia";
import {parseNumberFromString} from "@/common.js";
import Modal from "@/components/UI/Modal.vue";
import SelectMaterial from "@/components/layout/material/SelectMaterial.vue";
import {useCustomsStore} from "@/store/customs.js";
import Checkbox from "@/components/UI/Checkbox.vue";
export default {
name: "MaterialEdit",
components: {
Checkbox,
PhWarning,
SelectMaterial,
Modal, PhArrowCounterClockwise, ModalDialog, AutosuggestSearchbar, InputField, Flag, IconButton
},
emits: ["update:tariffRate", "updateMaterial", "update:partNumber", "update:hsCode", "save", "close"],
emits: ["update:tariffRate", "updateMaterial", "update:partNumber", "update:hsCode", "save", "close", "startLookup"],
props: {
description: {
type: [String, null],
@ -72,6 +113,10 @@ export default {
required: true,
validator: (value) => value === null || typeof value === 'string'
},
countryId: {
required: true,
validator: (value) => value === null || typeof value === 'number'
},
tariffRate: {
required: true,
validator: (value) => value === null || typeof value === 'number'
@ -93,11 +138,29 @@ export default {
...mapStores(useMaterialStore, useCustomsStore),
tariffRatePercent() {
return ((this.tariffRate ?? null) !== null) ? (this.tariffRate * 100).toFixed(2) : '';
},
showTariffWarning() {
return this.customsStore.showWarning(this.countryId);
},
tariffIsDefault() {
return this.tariffInfo?.default ?? false;
},
tariffInfo() {
return this.customsStore.findByCountryId(this.countryId);
},
measures() {
return this.tariffInfo.measures;
},
showCheckbox() {
return this.countryId === null;
},
disableTariff() {
return !this.doLookup;
}
},
data() {
return {
doLookup: false
}
},
created() {
@ -115,22 +178,54 @@ export default {
}
});
},
validateTariffRate(event) {
const percentValue = parseNumberFromString(event.target.value, 2, true);
validateTariffRateEvent(event) {
event.target.value = this.validateTariffRate(event.target.value);
},
validateTariffRate(value) {
const percentValue = parseNumberFromString(value, 2, true);
const validatedPercent = (percentValue === null) ? null : Math.max(0, Math.min(999.99, percentValue));
const validatedDecimal = (validatedPercent === null) ? null : validatedPercent / 100;
if (validatedDecimal !== this.tariffRate) {
this.$emit('update:tariffRate', validatedDecimal);
this.$emit('update:tariffRate', this.roundNumber(validatedDecimal, 4));
}
event.target.value = validatedPercent === null ? null : validatedPercent.toFixed(2);
return validatedPercent === null ? null : validatedPercent.toFixed(2);
},
roundNumber(number, digits) {
const multiple = Math.pow(10, digits);
return Math.round(number * multiple) / multiple;
},
async fetchHsCode(query) {
const hsCodeQuery = {searchTerm: query};
await this.customsStore.setQuery(hsCodeQuery);
return this.customsStore.hsCodes;
return await this.customsStore.findHsCode(query);
},
hsCodeChanged() {
const currentValue = this.$refs.hsCodeSearchbar.searchQuery;
this.$emit("update:hsCode", currentValue);
},
async hsCodeSelected(hsCode) {
let save = false;
if (hsCode !== this.hsCode) {
this.$emit("update:hsCode", hsCode)
save = true;
}
if ((this.countryId ?? null) !== null) {
const query = {hsCode: hsCode, countryIds: [this.countryId]};
await this.customsStore.findTariffRate(query);
this.validateTariffRate((this.customsStore.findByCountryId(this.countryId)?.value ?? 0.03) * 100);
save = true;
}
if (save)
this.$emit('save', 'material');
},
checkBoxChanged(checked) {
this.doLookup = checked
this.$emit('startLookup', checked);
}
}
}
@ -139,11 +234,42 @@ export default {
<style scoped>
.field-group.disabled {
opacity: 0.5;
pointer-events: none;
}
.field-group.disabled .caption-column,
.field-group.disabled .input-field {
color: #9ca3af;
}
.field-group.disabled .text-container {
background-color: #f3f4f6;
cursor: not-allowed;
border-color: #f3f4f6;
}
.input-field:disabled {
cursor: not-allowed;
color: #9ca3af;
}
.warning-icon {
color: #002F54;
}
.text-container:hover .warning-icon {
transform: scale(1.02);
}
.hs-code-container {
display: flex;
align-items: center;
gap: 0.8rem;
width: 100%;
z-index: 1000;
}
.container {
@ -165,7 +291,7 @@ export default {
minmax(auto, max-content) /* Spalte 5: Label */
minmax(120px, 1fr) /* Spalte 6: Input */
minmax(auto, max-content) /* Spalte 7: Label */
minmax(120px, 1fr); /* Spalte 8: Input/Dropdown */
minmax(120px, 1fr); /* Spalte 8: Input/Dropdown */
grid-template-rows: auto;
gap: 1.2rem 1rem;
align-items: center;
@ -230,12 +356,15 @@ export default {
border: 0.2rem solid #E3EDFF;
transition: all 0.1s ease;
flex: 1 1 auto;
position: relative; /* Hinzufügen */
z-index: 10;
}
.text-container:hover {
background: #EEF4FF;
border: 0.2rem solid #8DB3FE;
transform: scale(1.01);
}
.caption-column {
@ -250,6 +379,74 @@ export default {
.input-field-tariffrate {
min-width: 20rem;
flex: 0 1 10rem;
position: relative;
}
.tariff-rate-info {
position: absolute;
top: 3rem;
left: -3rem;
display: flex;
flex-direction: column;
justify-content: center;
gap: 1.2rem;
z-index: 5000;
background-color: #fff;
border-radius: 0.8rem;
border: 0.1rem solid #E3EDFF;
visibility: hidden;
opacity: 0;
transform: translateY(-20px);
padding: 1.6rem 2.4rem;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
min-width: 40rem;
}
.input-field-tariffrate:hover .tariff-rate-info {
visibility: visible;
opacity: 1;
transform: translateY(0);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.tariff-rate-info-cell-measure {
font-size: 1.2rem;
font-weight: 500;
color: #002F54;
justify-self: flex-end;
}
.tariff-rate-info-cell-tariffrate {
justify-self: flex-end;
}
.tariff-rate-info-container {
margin-top: 2.4rem;
display: grid;
grid-template-columns: auto 1fr 1fr;
gap: 0.8rem;
font-size: 1.2rem;
font-weight: 400;
}
.tariff-rate-info-headerbox {
color: #002F54;
font-size: 1.2rem;
font-weight: 400;
}
.tariff-rate-info-header {
display: flex;
align-items: center;
gap: 0.8rem;
justify-content: flex-start;
font-size: 1.4rem;
font-weight: 500;
background-color: #c3cfdf;
color: #002F54;
border-radius: 0.8rem;
padding: 1.6rem;
margin-bottom: 1.6rem;
}
/* Optimierung für kleinere Bildschirme unter 1500px - nur wenn responsive aktiviert ist */

View file

@ -57,7 +57,7 @@
<modal :z-index="3000" :state="showProcessingModal">
<div class="edit-calculation-spinner-container space-around">
<spinner></spinner>
<span>{{ processingMessage }}</span>
<span>{{ shownProcessingMessage }}</span>
</div>
</modal>
@ -86,8 +86,10 @@
v-model:mixable="componentProps.mixable"
v-model:stackable="componentProps.stackable"
v-model:hideDescription="componentProps.hideDescription"
:countryId=null
:responsive="false"
@close="closeEditModalAction('cancel')"
@start-lookup="doLookupOnClose"
>
</component>
@ -122,6 +124,7 @@ import DestinationListView from "@/components/layout/edit/DestinationListView.vu
import SelectNode from "@/components/layout/node/SelectNode.vue";
import Toast from "@/components/UI/Toast.vue";
import logger from "@/logger.js";
import {useCustomsStore} from "@/store/customs.js";
const COMPONENT_TYPES = {
@ -146,7 +149,7 @@ export default {
BasicButton
},
computed: {
...mapStores(usePremiseEditStore),
...mapStores(usePremiseEditStore, useCustomsStore),
hasSelection() {
if (this.premiseEditStore.isLoading || this.premiseEditStore.selectedLoading) {
return false;
@ -190,7 +193,14 @@ export default {
return this.modalType ? this.componentsData[this.modalType] : null;
},
showProcessingModal() {
return this.premiseEditStore.showProcessingModal || this.showCalculationModal;
return this.premiseEditStore.showProcessingModal || this.showCalculationModal || this.customsStore.loadingTariff;
},
shownProcessingMessage() {
if(this.customsStore.loadingTariff)
return "Looking up tariff rate ..."
return this.processingMessage;
}
},
created() {
@ -201,6 +211,7 @@ export default {
data() {
return {
doLookup: false,
ids: [],
bulkQuery: null,
modalType: null,
@ -225,7 +236,7 @@ export default {
editIds: null,
dataSourceId: null,
processingMessage: "Please wait. Calculating ...",
showCalculationModal: false
showCalculationModal: false,
}
},
methods: {
@ -290,6 +301,9 @@ export default {
logger.info("open modal", massEdit, this.modalType, this.editIds, this.dataSourceId)
},
doLookupOnClose(doLookup) {
this.doLookup = doLookup;
},
async closeEditModalAction(action) {
if (this.modalType === "destinations") {
if (action === "accept") {
@ -305,7 +319,11 @@ export default {
await this.premiseEditStore.batchUpdatePrice(this.editIds, props);
break;
case "material":
await this.premiseEditStore.batchUpdateMaterial(this.editIds, props);
let tariffRates = null;
if (this.doLookup) {
tariffRates = await this.tariffLookUp(props.hsCode);
}
await this.premiseEditStore.batchUpdateMaterial(this.editIds, props, tariffRates);
break;
case "packaging":
await this.premiseEditStore.batchUpdatePackaging(this.editIds, props);
@ -317,6 +335,23 @@ export default {
this.fillData(this.modalType);
this.modalType = null;
},
async tariffLookUp(hsCode) {
const countryIds = this.premiseEditStore.getCountryIdByPremiseIds(this.editIds);
const query = {hsCode: hsCode, countryIds: [...new Set(Array.from(countryIds.values()))]};
const tariffRates = await this.customsStore.findTariffRate(query);
const found = new Map();
for(const editId of this.editIds) {
const countryId = countryIds.get(editId);
found.set(editId, tariffRates.find(rate => rate.countryId === countryId).value);
}
return found;
},
fillData(type, id = -1, hideDescription = false) {
if (id === -1) {
@ -360,7 +395,7 @@ export default {
this.componentsData.material.props = {
partNumber: premise.material.part_number,
hsCode: premise.material.hs_code,
hsCode: premise.hs_code,
tariffRate: premise.tariff_rate ?? 0.00,
description: premise.material.name ?? "",
hideDescription: hideDescription

View file

@ -60,6 +60,7 @@
:id="premise.material.id"
v-model:hs-code="premise.hs_code"
v-model:tariff-rate="premise.tariff_rate"
v-model:country-id="premise.supplier.country.id"
@update-material="updateMaterial"
@save="save"></material-edit>
</box>
@ -89,7 +90,7 @@
<modal :z-index="3000" :state="showProcessingModal">
<div class="edit-calculation-spinner-container space-around">
<spinner></spinner>
<span>{{ processingMessage }}</span>
<span>{{ shownProcessingMessage }}</span>
</div>
</modal>
@ -115,6 +116,7 @@ import TraceView from "@/components/layout/TraceView.vue";
import IconButton from "@/components/UI/IconButton.vue";
import Toast from "@/components/UI/Toast.vue";
import {UrlSafeBase64} from "@/common.js";
import {useCustomsStore} from "@/store/customs.js";
export default {
name: "SingleEdit",
@ -143,7 +145,7 @@ export default {
}
},
computed: {
...mapStores(usePremiseEditStore),
...mapStores(usePremiseEditStore, useCustomsStore),
premise() {
return this.premiseEditStore.singleSelectedPremise;
},
@ -151,8 +153,15 @@ export default {
return this.bulkEditQuery !== null;
},
showProcessingModal() {
return this.premiseEditStore.showProcessingModal || this.showCalculationModal;
return this.premiseEditStore.showProcessingModal || this.showCalculationModal || this.customsStore.loadingTariff;
},
shownProcessingMessage() {
if(this.customsStore.loadingTariff)
return "Looking up tariff rate ..."
return this.processingMessage;
}
},
methods: {

View file

@ -1,19 +1,53 @@
import {defineStore} from 'pinia'
import {config} from '@/config'
import performRequest from "@/backend.js";
export const useCustomsStore = defineStore('customs', {
state() {
return {
hsCodes: [],
query: null
tariffInfo: null,
loadingTariff: false,
}
},
getters: {},
actions: {
async setQuery(query) {
this.query = query;
getters: {
showWarning(state) {
return function (countryId) {
const info = state.tariffInfo?.find(t => t.countryId === countryId);
if ((info ?? null) !== null) {
return info.default || info.measures?.length > 1;
}
return false;
}
},
async queryCustomApi(query) {
findByCountryId(state) {
return function (countryId) {
return state.tariffInfo?.find(t => t.countryId === countryId);
}
}
},
actions: {
async findTariffRate(query) {
this.loadingTariff = true;
const url = `${config.backendUrl}/customs?hs_code=${query.hsCode}&country_ids=${query.countryIds}`;
const resp = await performRequest(this, 'GET', url, null).catch(() => {
this.tariffInfo = null;
this.loadingTariff = false;
})
this.tariffInfo = resp.data;
this.loadingTariff = false;
return this.tariffInfo;
},
async findHsCode(query) {
const url = `${config.backendUrl}/customs/search?hs_code=${query}`;
const resp = await performRequest(this, 'GET', url, null);
this.hsCodes = resp.data;
return this.hsCodes;
}
}

View file

@ -32,6 +32,28 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
},
getters: {
getCountryIdByPremiseIds(state) {
return function (ids) {
if (state.loading) {
if (state.throwsException)
throw new Error("Premises are accessed while still loading.");
return null;
}
const premiss = state.premisses?.filter(p => ids.some(id => id === p.id));
const premiseCountryMap = new Map();
premiss?.forEach(premise => {
premiseCountryMap.set(premise.id, premise.supplier?.country?.id);
});
return premiseCountryMap;
}
},
/**
* Returns the ids of all premises.
* @param state
@ -266,23 +288,24 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
return await this.savePrice(ids, priceData);
},
async batchUpdateMaterial(ids, materialData) {
async batchUpdateMaterial(ids, materialData, tariffRates = null) {
const updatedPremises = this.premisses.map(p => {
if (ids.includes(p.id)) {
return {
...p,
material: {
...p.material,
...(materialData.hsCode !== null && {hs_code: materialData.hsCode})
},
...(materialData.tariffRate !== null && {tariff_rate: materialData.tariffRate})
...(materialData.hsCode !== null && {hs_code: materialData.hsCode}),
...((tariffRates !== null) ? tariffRates.get(p.id) : (materialData.tariffRate !== null && {tariff_rate: materialData.tariffRate}))
};
}
return p;
});
this.premisses = updatedPremises;
return await this.saveMaterial(ids, materialData);
return await this.saveMaterial(ids, materialData, tariffRates);
},
async batchUpdatePackaging(ids, packagingData) {
@ -759,7 +782,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
return success;
},
async saveMaterial(ids = null, materialData = null) {
async saveMaterial(ids = null, materialData = null, tariffRates = null) {
let success = true;
const toBeUpdated = this.premisses ? (ids ? (ids.map(id => this.premisses.find(p => String(p.id) === String(id)))) : (this.premisses.filter(p => p.selected))) : null;
@ -771,6 +794,7 @@ export const usePremiseEditStore = defineStore('premiseEdit', {
hs_code: materialData === null ? toBeUpdated[0].hs_code : materialData.hsCode,
tariff_rate: materialData === null ? toBeUpdated[0].tariff_rate : materialData.tariffRate,
tariff_rates: (null === tariffRates) ? null : Object.fromEntries(tariffRates),
};
await performRequest(this, 'POST', `${config.backendUrl}/calculation/material/`, body, false).catch(() => {

View file

@ -0,0 +1,38 @@
package de.avatic.lcc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.soap.SoapVersion;
import org.springframework.ws.soap.saaj.SaajSoapMessageFactory;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class SoapConfig {
@Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("eu.europa.ec.taxation.taric.client");
return marshaller;
}
@Bean
public SaajSoapMessageFactory messageFactory() {
SaajSoapMessageFactory messageFactory = new SaajSoapMessageFactory();
messageFactory.setSoapVersion(SoapVersion.SOAP_11);
return messageFactory;
}
@Bean
public WebServiceTemplate webServiceTemplate(Jaxb2Marshaller marshaller, SaajSoapMessageFactory messageFactory) {
WebServiceTemplate template = new WebServiceTemplate(messageFactory);
template.setMarshaller(marshaller);
template.setUnmarshaller(marshaller);
template.setDefaultUri("https://ec.europa.eu/taxation_customs/dds2/taric/services/goods");
return template;
}
}

View file

@ -1,12 +1,18 @@
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.
@ -17,26 +23,36 @@ 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) {
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 countryId the ID of the country for which the tariff rate is required
* @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<Number> getTariffRate(@RequestParam(value = "hs_code") String hsCode, @RequestParam(value = "country_id") Integer countryId) {
return ResponseEntity.ok(customApiService.getTariffRate(hsCode, countryId));
@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));
}
}

View file

@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.*;
import java.util.List;
import java.util.Map;
public class MaterialUpdateDTO {
@ -21,6 +22,9 @@ public class MaterialUpdateDTO {
@Digits(integer = 4, fraction = 4, message = "Tariff rate must have at most 4 decimal places")
private Number tariffRate;
@JsonProperty("tariff_rates")
private Map<Integer, Number> tariffRates;
public String getHsCode() {
return hsCode;
}
@ -44,4 +48,12 @@ public class MaterialUpdateDTO {
public void setPremiseIds(List<Integer> premiseIds) {
this.premiseIds = premiseIds;
}
public Map<Integer, Number> getTariffRates() {
return tariffRates;
}
public void setTariffRates(Map<Integer, Number> tariffRates) {
this.tariffRates = tariffRates;
}
}

View file

@ -0,0 +1,64 @@
package de.avatic.lcc.dto.custom;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class CustomDTO {
private boolean isDefault;
private Double value;
private List<CustomMeasureDTO> measures;
private Integer countryId;
public CustomDTO(Double value, List<CustomMeasureDTO> measures, Integer countryId) {
this(true, value, measures, countryId);
}
public CustomDTO(boolean isDefault, Double value, List<CustomMeasureDTO> measures, Integer countryId) {
this.isDefault = isDefault;
this.value = value;
this.measures = measures;
this.countryId = countryId;
}
public CustomDTO(Double value, Integer countryId) {
this(value, null, countryId);
}
public boolean isDefault() {
return isDefault;
}
public void setDefault(boolean aDefault) {
isDefault = aDefault;
}
public Double getValue() {
return value;
}
public void setValue(Double value) {
this.value = value;
}
public List<CustomMeasureDTO> getMeasures() {
return measures;
}
public void setMeasures(List<CustomMeasureDTO> measures) {
this.measures = measures;
}
public Integer getCountryId() {
return countryId;
}
public void setCountryId(Integer countryId) {
this.countryId = countryId;
}
}

View file

@ -0,0 +1,54 @@
package de.avatic.lcc.dto.custom;
public class CustomMeasureDTO {
private String code;
private String description;
private String regulation;
private String tariffRate;
public CustomMeasureDTO(String code, String description, String regulation, String tariffRate) {
this.code = code;
this.description = description;
this.regulation = regulation;
this.tariffRate = tariffRate;
}
public CustomMeasureDTO() {
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getRegulation() {
return regulation;
}
public void setRegulation(String regulation) {
this.regulation = regulation;
}
public String getTariffRate() {
return tariffRate;
}
public void setTariffRate(String tariffRate) {
this.tariffRate = tariffRate;
}
}

View file

@ -0,0 +1,209 @@
package de.avatic.lcc.model.custom;
import java.util.Optional;
public enum MeasureType {
// Serie C - Applicable duty (Anwendbare Zollsätze)
APPL("103", true), // Third country duty - Standard-Drittlandszollsatz
APPEU("105", true), // Non preferential duty under end-use - Zollsatz unter End-Use
CUD("106", true), // Customs Union Duty - Zollunionszollsatz
S("112", true), // Autonomous tariff suspension - Autonome Zollaussetzung
SUSEU("115", true), // Autonomous suspension under end-use - Aussetzung unter End-Use
SUSSH("117", false), // Suspension - ships/boats/vessels - Aussetzung für Schiffe (nicht relevant)
AIRWO("119", false), // Airworthiness tariff suspension - Lufttüchtigkeits-Zollaussetzung (nicht relevant)
K_122("122", true), // Non preferential tariff quota - Nichtpräferenzielles Zollkontingent
KEU("123", true), // Non preferential tariff quota under end-use - Kontingent unter End-Use
OPQ("140", true), // Outward processing tariff preference - Passive Veredelung
S_141("141", true), // Preferential suspension - Präferenzielle Aussetzung
PREF("142", true), // Tariff preference - Zollpräferenz
K_143("143", true), // Preferential tariff quota - Präferenzielles Zollkontingent
C("144", true), // Preferential ceiling - Präferenzplafond
PRFEU("145", true), // Preference under end-use - Präferenz unter End-Use
PRKEU("146", true), // Preferential tariff quota under end-use - Präferenzkontingent unter End-Use
CUQ("147", true), // Customs Union Quota - Zollunionskontingent
// Serie A - Import/Export prohibited (Verbote)
PRO("277", false), // Import prohibition - Einfuhrverbot
PRX("278", false), // Export prohibition - Ausfuhrverbot
RSUBH("481", false), // Declaration of subheading submitted to restrictions (import) - Einfuhrbeschränkung
RSHEX("485", false), // Declaration of subheading submitted to restrictions (export) - Ausfuhrbeschränkung
// Serie P - VAT (Mehrwertsteuer)
TVA("305", false), // Value added tax - Mehrwertsteuer
// Serie Q - Excises (Verbrauchssteuern)
ACC("306", false), // Excises - Verbrauchssteuern
// Serie O - Supplementary unit (Besondere Maßeinheiten)
SUPUN("109", false), // Supplementary unit - Besondere Maßeinheit
USIMP("110", false), // Supplementary unit import - Besondere Maßeinheit Einfuhr
USEXP("111", false), // Supplementary unit export - Besondere Maßeinheit Ausfuhr
// Serie B - Entry subject to conditions (Einfuhr unter Bedingungen)
VETCTR("410", false), // Veterinary control - Veterinärkontrolle (nicht relevant für Gabelstapler)
PRIOR_SURV("420", true), // Entry into free circulation (prior surveillance) - Vorherige Überwachung
REU("464", true), // Declaration of subheading submitted to end-use provisions - End-Use-Erklärung
LPS("465", true), // Restriction on entry into free circulation - Einfuhrbeschränkung
SPX("467", false), // Restriction on export - Ausfuhrbeschränkung
QX("473", false), // Export authorization - Ausfuhrgenehmigung
LPQ("474", true), // Entry into free circulation (quantitative limitation) - Mengenmäßige Beschränkung
LPR("475", true), // Restriction on entry into free circulation - Einfuhrbeschränkung
RX("476", false), // Restriction on export - Ausfuhrbeschränkung
OPT("477", true), // Entry into free circulation (outward processing traffic) - Passive Veredelung
DURX("478", false), // Export authorization (Dual use) - Ausfuhrgenehmigung Dual-Use
CHMEX("479", false), // Export control on dangerous chemicals - Exportkontrolle gefährliche Chemikalien
LCRED_M1("482", false), // Declaration submitted to legal restrictions (net weight/supplementary unit) - Rechtliche Beschränkungen
LCRED_V1("483", false), // Declaration submitted to legal restrictions (unit price) - Rechtliche Beschränkungen
PCRED_M1("484", false), // Declaration submitted to physical restrictions (net weight/supplementary unit) - Physische Beschränkungen
PCRED_V("491", false), // Declaration submitted to physical restrictions (declared statistical value) - Physische Beschränkungen
PCRED_M("492", false), // Declaration submitted to physical restrictions (declared net mass) - Physische Beschränkungen
PCRED_S("493", false), // Declaration submitted to physical restrictions (declared supplementary unit) - Physische Beschränkungen
LCRED_V("494", false), // Declaration submitted to legal restrictions (declared statistical value) - Rechtliche Beschränkungen
LCRED_M("495", false), // Declaration submitted to legal restrictions (declared net mass) - Rechtliche Beschränkungen
LCRED_S("496", false), // Declaration submitted to legal restrictions (declared supplementary unit) - Rechtliche Beschränkungen
TRIMP("705", false), // Import prohibition on goods for torture and repression - Folter-/Repressionsgüter
TREXP("706", false), // Export prohibition - torture/repression goods - Ausfuhrverbot Folter-/Repressionsgüter
IMPORT_CTRL("707", true), // Import control - Einfuhrkontrolle (potentiell relevant für Bauteile)
TRRX("708", false), // Export restriction - torture/repression goods - Ausfuhrbeschränkung
EXPCTRL("709", false), // Export control - Ausfuhrkontrolle
CITESIMP("710", false), // Import control - CITES - CITES-Kontrolle (nicht relevant)
MILIM("711", false), // Import control on restricted goods and technologies - Militärgüter (nicht relevant)
IAS("712", false), // Import control - IAS - Invasive Arten (nicht relevant)
IMPORT_CTRL_GMO("713", false), // Import control on GMOs - GMO-Kontrolle (nicht relevant)
IMPCTRL("714", true), // Import control - Einfuhrkontrolle (potentiell relevant)
CITESEX("715", false), // Export control - CITES - CITES-Ausfuhrkontrolle
CFISH("716", false), // Export control - Fish - Fischereikontrolle (nicht relevant)
MILEX("717", false), // Export control on restricted goods and technologies - Militärgüter
LUXEX("718", false), // Export control on luxury goods - Luxusgüter (nicht relevant)
CIUUF("719", false), // Control on illegal fishing - Illegale Fischerei (nicht relevant)
FFIM("722", false), // Entry restriction - feed and food - Futter-/Lebensmittel (nicht relevant)
FGGIMP("724", false), // Import control of fluorinated greenhouse gases - Treibhausgase
OZEXP("725", false), // Export control on ozone-depleting substances - Ozonschicht
OZIMP("726", false), // Import control on ozone-depleting substances - Ozonschicht
LUXIM("728", false), // Import control on luxury goods - Luxusgüter (nicht relevant)
EXPCHE("730", false), // Compliance with pre-export checks requirements - Vorausfuhrkontrollen
IMPORT_CTRL_731("731", true), // Import control - Einfuhrkontrolle (potentiell relevant)
IMPORT_CTRL_732("732", true), // Import control - Einfuhrkontrolle (potentiell relevant)
CGIMP("734", false), // Import control on cultural goods - Kulturgüter (nicht relevant)
CGEXP("735", false), // Export control on cultural goods - Kulturgüter (nicht relevant)
EXPORT_CTRL_736("736", false), // Export control - Ausfuhrkontrolle
EXPORT_CTRL_737("737", false), // Export control - Ausfuhrkontrolle
IMPORT_EXPORT_CML("738", false), // Import/export restriction - Common Military List goods - Militärgüter
FUREX("740", false), // Export control on cat and dog fur - Katzen-/Hundefell (nicht relevant)
FURIM("745", false), // Import control on cat and dog fur - Katzen-/Hundefell (nicht relevant)
SEAL("746", false), // Import control on seal products - Robbenprodukte (nicht relevant)
FLEGT("747", false), // Import control of timber - FLEGT licensing - Holzprodukte (nicht relevant)
MERIM("748", false), // Import control of mercury - Quecksilber
MEREX("749", false), // Export control of mercury - Quecksilber
ORGANIC("750", false), // Import control of organic products - Bio-Produkte (nicht relevant)
WSTEX("751", false), // Export control - Waste - Abfall
WSTIM("755", false), // Import control - Waste - Abfall
IMPCTRL_760("760", true), // Import control - Einfuhrkontrolle (potentiell relevant)
CHMIM("761", false), // Import control on REACH - Chemikalien
IMPORT_CTRL_762("762", true), // Import control - Einfuhrkontrolle (potentiell relevant)
IMPORT_CTRL_763("763", true), // Import control - Einfuhrkontrolle (potentiell relevant)
FGEXP("765", false), // Export control of fluorinated greenhouse gases - Treibhausgase
EXPORT_CTRL_766("766", false), // Export control - Ausfuhrkontrolle
EXPORT_CTRL_767("767", false), // Export control - Ausfuhrkontrolle
EXPORT_CTRL_768("768", false), // Export control - Ausfuhrkontrolle
IMPOP("769", false), // Import control on persistent organic pollutants - Organische Schadstoffe
FLEGT_GHANA("770", false), // Import control timber - FLEGT Ghana - Holzprodukte (nicht relevant)
FLEGT_CAMEROON("771", false), // Import control timber - FLEGT Cameroon - Holzprodukte (nicht relevant)
FLEGT_CONGO("772", false), // Import control timber - FLEGT Congo - Holzprodukte (nicht relevant)
FLEGT_CAR("773", false), // Import control timber - FLEGT Central Africa - Holzprodukte (nicht relevant)
FLEGT_VIETNAM("774", false), // Import control timber - FLEGT Vietnam - Holzprodukte (nicht relevant)
CBAM("775", false), // Carbon Border Adjustment Mechanism - CO2-Grenzausgleich
DFRSTIM("776", false), // Import control on deforestation - Entwaldung
DFRSEXP("777", false), // Export control on deforestation - Entwaldung
EXPORT_CTRL_780("780", false), // Export control - Ausfuhrkontrolle
EXPORT_CTRL_781("781", false), // Export control - Ausfuhrkontrolle
// Serie N - Posterior surveillance (Nachträgliche Überwachung)
PIM("440", false), // Public Import Monitoring - Öffentliche Einfuhrüberwachung
PEM("445", false), // Public Export Monitoring - Öffentliche Ausfuhrüberwachung
SUB("450", false), // Statistical surveillance - Statistische Überwachung
SUA("460", false), // Computer surveillance - Computerüberwachung
SQR("461", false), // Community surveillance - reference quantities - Referenzmengen
SUR("462", false), // Posterior import surveillance - Nachträgliche Einfuhrüberwachung
SUX("463", false), // Posterior export surveillance - Nachträgliche Ausfuhrüberwachung
SUREX("470", false), // Export surveillance - Ausfuhrüberwachung
SUEX("471", false), // Export surveillance (TQS) - Ausfuhrüberwachung
IMPMNTR_702("702", false), // Import Monitoring - Einfuhrüberwachung
IMPMNTR_703("703", false), // Import Monitoring - Einfuhrüberwachung
IMPMNTR_704("704", false), // Import Monitoring - Einfuhrüberwachung
// Serie M - Unit price, standard import value (Preise und Werte)
VU("488", false), // Unit price - Stückpreis
REPSU("489", false), // Representative price - Repräsentativer Preis
SIV("490", false), // Standard import value - Standard-Einfuhrwert
// Serie D - Anti-dumping or countervailing duties (Anti-Dumping/Ausgleichszölle)
DUMPP("551", true), // Provisional anti-dumping duty - Vorläufiger Anti-Dumping-Zoll
DUMPD("552", true), // Definitive anti-dumping duty - Endgültiger Anti-Dumping-Zoll
COMPP("553", true), // Provisional countervailing duty - Vorläufiger Ausgleichszoll
COMPD("554", true), // Definitive countervailing duty - Endgültiger Ausgleichszoll
PCDUM("555", true), // Anti-dumping/countervailing duty - Pending collection - Schwebende Erhebung
NTDUM("561", false), // Notice of initiation of anti-dumping proceeding - Verfahrenseröffnung
SPDUM("562", true), // Suspended anti-dumping/countervailing duty - Ausgesetzter Zoll
RGDUM("564", false), // Anti-dumping/countervailing registration - Registrierung
REDUM("565", false), // Anti-dumping/countervailing review - Überprüfung
STDUM("566", false), // Anti-dumping/countervailing statistic - Statistik
CTDUM("570", false), // Anti-dumping/countervailing duty - Control - Kontrolle
// Serie S - Supplementary amount (Zusätzliche Beträge)
ASURE("651", false), // Security based on representative price - Sicherheit basierend auf Preis
ASUCI("652", false), // Additional duty based on CIF price - Zusatzzoll basierend auf CIF-Preis
SECQ("653", false), // Security reduced under tariff quota - Reduzierte Sicherheit
ADQ("654", false), // Additional duty reduced under tariff quota - Reduzierter Zusatzzoll
SECPO("655", false), // Security (poultry) based on representative price - Sicherheit Geflügel
ADPOC("656", false), // Additional duty (poultry) based on CIF price - Zusatzzoll Geflügel
RESEC("657", false), // Reduced security based on representative price - Reduzierte Sicherheit
READ("658", false), // Reduced additional duty based on CIF price - Reduzierter Zusatzzoll
// Serie F - Additional duty on sugar, flour (Zusatzzölle Zucker/Mehl)
AD_SZ("672", false), // Amount of additional duty on sugar - Zusatzzoll Zucker (nicht relevant)
AD_FM("673", false), // Amount of additional duty on flour - Zusatzzoll Mehl (nicht relevant)
// Serie E - Levies, export refunds (Abgaben, Ausfuhrerstattungen)
EA("674", false), // Agricultural component - Landwirtschaftliche Komponente (nicht relevant)
// Serie J - Countervailing charge (Ausgleichsabgaben)
TC("690", false), // Countervailing charge - Ausgleichsabgabe
DR("695", true), // Additional duties - Zusatzzölle
SAFDU("696", true); // Additional duties (safeguard) - Schutzmaßnahmen-Zusatzzölle
private final String measureCode;
private final boolean containsRelevantDuty;
MeasureType(String measureCode, boolean containsRelevantDuty) {
this.measureCode = measureCode;
this.containsRelevantDuty = containsRelevantDuty;
}
public String getMeasureCode() {
return measureCode;
}
public boolean containsRelevantDuty() {
return containsRelevantDuty;
}
public static Optional<MeasureType> fromMeasureCode(String measureCode) {
for (MeasureType type : values()) {
if (type.measureCode.equals(measureCode)) {
return Optional.of(type);
}
}
return Optional.empty();
}
public static MeasureType fromShortName(String shortName) {
try {
return valueOf(shortName.toUpperCase().replace(" ", "_"));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Unknown short name: " + shortName);
}
}
}

View file

@ -0,0 +1,100 @@
package de.avatic.lcc.model.taric;
import java.time.LocalDate;
import java.util.Set;
public class AppliedMeasure {
private LocalDate startDate;
private LocalDate endDate;
private String amount;
private Integer orderNumber;
private Set<MeasureFootnote> measureFootnotes;
private Measure measure;
private LegalBase legalBase;
private Set<MeasureExclusion> exclusions;
private Set<AppliedMeasureCondition> conditions;
public LocalDate getStartDate() {
return startDate;
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public LocalDate getEndDate() {
return endDate;
}
public void setEndDate(LocalDate endDate) {
this.endDate = endDate;
}
public String getAmount() {
return amount;
}
public void setAmount(String amount) {
this.amount = amount;
}
public Integer getOrderNumber() {
return orderNumber;
}
public void setOrderNumber(Integer orderNumber) {
this.orderNumber = orderNumber;
}
public Set<MeasureFootnote> getMeasureFootnotes() {
return measureFootnotes;
}
public void setMeasureFootnotes(Set<MeasureFootnote> measureFootnotes) {
this.measureFootnotes = measureFootnotes;
}
public Set<MeasureExclusion> getExclusions() {
return exclusions;
}
public void setExclusions(Set<MeasureExclusion> exclusions) {
this.exclusions = exclusions;
}
public Set<AppliedMeasureCondition> getConditions() {
return conditions;
}
public void setConditions(Set<AppliedMeasureCondition> conditions) {
this.conditions = conditions;
}
public Measure getMeasure() {
return measure;
}
public void setMeasure(Measure measure) {
this.measure = measure;
}
public LegalBase getLegalBase() {
return legalBase;
}
public void setLegalBase(LegalBase legalBase) {
this.legalBase = legalBase;
}
}

View file

@ -0,0 +1,75 @@
package de.avatic.lcc.model.taric;
public class AppliedMeasureCondition {
private Integer sequenceNo;
private String amount;
private MeasureAction measureAction;
private MonetaryUnit monetaryUnit;
private Unit unit;
private Certificate certificate;
private ConditionType conditionType;
public Integer getSequenceNo() {
return sequenceNo;
}
public void setSequenceNo(Integer sequenceNo) {
this.sequenceNo = sequenceNo;
}
public String getAmount() {
return amount;
}
public void setAmount(String amount) {
this.amount = amount;
}
public MeasureAction getMeasureAction() {
return measureAction;
}
public void setMeasureAction(MeasureAction measureAction) {
this.measureAction = measureAction;
}
public MonetaryUnit getMonetaryUnit() {
return monetaryUnit;
}
public void setMonetaryUnit(MonetaryUnit monetaryUnit) {
this.monetaryUnit = monetaryUnit;
}
public Unit getUnit() {
return unit;
}
public void setUnit(Unit unit) {
this.unit = unit;
}
public Certificate getCertificate() {
return certificate;
}
public void setCertificate(Certificate certificate) {
this.certificate = certificate;
}
public ConditionType getConditionType() {
return conditionType;
}
public void setConditionType(ConditionType conditionType) {
this.conditionType = conditionType;
}
}

View file

@ -0,0 +1,56 @@
package de.avatic.lcc.model.taric;
import java.time.LocalDate;
public class Certificate {
private String certificateCode;
private LocalDate startDate;
private LocalDate endDate;
private CertificateType certificateType;
private CertificateDesc certificateDesc;
public String getCertificateCode() {
return certificateCode;
}
public void setCertificateCode(String certificateCode) {
this.certificateCode = certificateCode;
}
public LocalDate getStartDate() {
return startDate;
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public LocalDate getEndDate() {
return endDate;
}
public void setEndDate(LocalDate endDate) {
this.endDate = endDate;
}
public CertificateType getCertificateType() {
return certificateType;
}
public void setCertificateType(CertificateType certificateType) {
this.certificateType = certificateType;
}
public CertificateDesc getCertificateDesc() {
return certificateDesc;
}
public void setCertificateDesc(CertificateDesc certificateDesc) {
this.certificateDesc = certificateDesc;
}
}

View file

@ -0,0 +1,36 @@
package de.avatic.lcc.model.taric;
import java.time.LocalDate;
public class CertificateDesc {
private String lang;
private String desc;
private LocalDate descStartDate;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public LocalDate getDescStartDate() {
return descStartDate;
}
public void setDescStartDate(LocalDate descStartDate) {
this.descStartDate = descStartDate;
}
}

View file

@ -0,0 +1,24 @@
package de.avatic.lcc.model.taric;
public class CertificateType {
private String certificateTypeCode;
private CertificateTypeDesc certificateTypeDesc;
public String getCertificateTypeCode() {
return certificateTypeCode;
}
public void setCertificateTypeCode(String certificateTypeCode) {
this.certificateTypeCode = certificateTypeCode;
}
public CertificateTypeDesc getCertificateTypeDesc() {
return certificateTypeDesc;
}
public void setCertificateTypeDesc(CertificateTypeDesc certificateTypeDesc) {
this.certificateTypeDesc = certificateTypeDesc;
}
}

View file

@ -0,0 +1,37 @@
package de.avatic.lcc.model.taric;
import java.time.LocalDate;
public class CertificateTypeDesc {
private String lang;
private String desc;
private LocalDate descStartDate;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public LocalDate getDescStartDate() {
return descStartDate;
}
public void setDescStartDate(LocalDate descStartDate) {
this.descStartDate = descStartDate;
}
}

View file

@ -0,0 +1,24 @@
package de.avatic.lcc.model.taric;
public class ConditionType {
private String conditionTypeCode;
private ConditionTypeDesc conditionTypeDesc;
public String getConditionTypeCode() {
return conditionTypeCode;
}
public void setConditionTypeCode(String conditionTypeCode) {
this.conditionTypeCode = conditionTypeCode;
}
public ConditionTypeDesc getConditionTypeDesc() {
return conditionTypeDesc;
}
public void setConditionTypeDesc(ConditionTypeDesc conditionTypeDesc) {
this.conditionTypeDesc = conditionTypeDesc;
}
}

View file

@ -0,0 +1,36 @@
package de.avatic.lcc.model.taric;
import java.time.LocalDate;
public class ConditionTypeDesc {
private String lang;
private String desc;
private LocalDate descStartDate;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public LocalDate getDescStartDate() {
return descStartDate;
}
public void setDescStartDate(LocalDate descStartDate) {
this.descStartDate = descStartDate;
}
}

View file

@ -0,0 +1,57 @@
package de.avatic.lcc.model.taric;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import jakarta.validation.constraints.Size;
import org.springframework.data.annotation.Id;
import java.time.LocalDate;
import java.util.Set;
/**
* Represents a footnote reference
*/
public class Footnote {
private String footnote;
private LocalDate startDate;
private LocalDate endDate;
private FootnotesDesc footnotesDesc;
public String getFootnote() {
return footnote;
}
public void setFootnote(String footnote) {
this.footnote = footnote;
}
public LocalDate getStartDate() {
return startDate;
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public LocalDate getEndDate() {
return endDate;
}
public void setEndDate(LocalDate endDate) {
this.endDate = endDate;
}
public FootnotesDesc getFootnotesDesc() {
return footnotesDesc;
}
public void setFootnotesDesc(FootnotesDesc footnotesDesc) {
this.footnotesDesc = footnotesDesc;
}
}

View file

@ -0,0 +1,40 @@
package de.avatic.lcc.model.taric;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
public class FootnotesDesc {
private String lang;
private String desc;
private LocalDate descStartDate;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public LocalDate getDescStartDate() {
return descStartDate;
}
public void setDescStartDate(LocalDate descStartDate) {
this.descStartDate = descStartDate;
}
}

View file

@ -0,0 +1,31 @@
package de.avatic.lcc.model.taric;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import jakarta.validation.constraints.Size;
import org.springframework.data.annotation.Id;
import java.util.Set;
public class Geo {
private String iso3166Code;
private GeoDesc geoDesc;
public String getIso3166Code() {
return iso3166Code;
}
public void setIso3166Code(String iso3166Code) {
this.iso3166Code = iso3166Code;
}
public GeoDesc getGeoDesc() {
return geoDesc;
}
public void setGeoDesc(GeoDesc geoDesc) {
this.geoDesc = geoDesc;
}
}

View file

@ -0,0 +1,41 @@
package de.avatic.lcc.model.taric;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import jakarta.validation.constraints.Size;
import org.springframework.data.annotation.Id;
import java.time.LocalDate;
public class GeoDesc {
private String lang;
private String desc;
private LocalDate descStartDate;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public LocalDate getDescStartDate() {
return descStartDate;
}
public void setDescStartDate(LocalDate descStartDate) {
this.descStartDate = descStartDate;
}
}

View file

@ -0,0 +1,50 @@
package de.avatic.lcc.model.taric;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.validation.constraints.Size;
import org.springframework.data.annotation.Id;
import java.time.LocalDate;
public class LegalBase {
private String legalBase;
private String journal;
private Integer page;
private LocalDate date;
public String getLegalBase() {
return legalBase;
}
public void setLegalBase(String legalBase) {
this.legalBase = legalBase;
}
public String getJournal() {
return journal;
}
public void setJournal(String journal) {
this.journal = journal;
}
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public LocalDate getDate() {
return date;
}
public void setDate(LocalDate date) {
this.date = date;
}
}

View file

@ -0,0 +1,66 @@
package de.avatic.lcc.model.taric;
import java.time.LocalDate;
public class Measure {
private String measureCode;
private String shortDesc;
private Integer tmCode;
private LocalDate startDate;
private MeasureDesc measureDesc;
private MeasureSeries measureSeries;
public String getMeasureCode() {
return measureCode;
}
public void setMeasureCode(String measureCode) {
this.measureCode = measureCode;
}
public String getShortDesc() {
return shortDesc;
}
public void setShortDesc(String shortDesc) {
this.shortDesc = shortDesc;
}
public Integer getTmCode() {
return tmCode;
}
public void setTmCode(Integer tmCode) {
this.tmCode = tmCode;
}
public LocalDate getStartDate() {
return startDate;
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public MeasureDesc getMeasureDesc() {
return measureDesc;
}
public void setMeasureDesc(MeasureDesc measureDesc) {
this.measureDesc = measureDesc;
}
public MeasureSeries getMeasureSeries() {
return measureSeries;
}
public void setMeasureSeries(MeasureSeries measureSeries) {
this.measureSeries = measureSeries;
}
}

View file

@ -0,0 +1,24 @@
package de.avatic.lcc.model.taric;
public class MeasureAction {
private String measureActionCode;
private MeasureActionDesc measureActionDesc;
public String getMeasureActionCode() {
return measureActionCode;
}
public void setMeasureActionCode(String measureActionCode) {
this.measureActionCode = measureActionCode;
}
public MeasureActionDesc getMeasureActionDesc() {
return measureActionDesc;
}
public void setMeasureActionDesc(MeasureActionDesc measureActionDesc) {
this.measureActionDesc = measureActionDesc;
}
}

View file

@ -0,0 +1,38 @@
package de.avatic.lcc.model.taric;
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
public class MeasureActionDesc {
private String lang;
private String desc;
private LocalDate descStartDate;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public LocalDate getDescStartDate() {
return descStartDate;
}
public void setDescStartDate(LocalDate descStartDate) {
this.descStartDate = descStartDate;
}
}

View file

@ -0,0 +1,90 @@
package de.avatic.lcc.model.taric;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigDecimal;
/**
* Represents a measure component (duty rate, etc.)
*/
public class MeasureComponent {
@JsonProperty("duty_expression_id")
private String dutyExpressionId;
@JsonProperty("duty_amount")
private BigDecimal dutyAmount;
@JsonProperty("monetary_unit_code")
private String monetaryUnitCode;
@JsonProperty("measurement_unit_code")
private String measurementUnitCode;
@JsonProperty("measurement_unit_qualifier_code")
private String measurementUnitQualifierCode;
@JsonProperty("duty_expression_description")
private String dutyExpressionDescription;
@JsonProperty("duty_expression_abbreviation")
private String dutyExpressionAbbreviation;
// Getters and Setters
public String getDutyExpressionId() {
return dutyExpressionId;
}
public void setDutyExpressionId(String dutyExpressionId) {
this.dutyExpressionId = dutyExpressionId;
}
public BigDecimal getDutyAmount() {
return dutyAmount;
}
public void setDutyAmount(BigDecimal dutyAmount) {
this.dutyAmount = dutyAmount;
}
public String getMonetaryUnitCode() {
return monetaryUnitCode;
}
public void setMonetaryUnitCode(String monetaryUnitCode) {
this.monetaryUnitCode = monetaryUnitCode;
}
public String getMeasurementUnitCode() {
return measurementUnitCode;
}
public void setMeasurementUnitCode(String measurementUnitCode) {
this.measurementUnitCode = measurementUnitCode;
}
public String getMeasurementUnitQualifierCode() {
return measurementUnitQualifierCode;
}
public void setMeasurementUnitQualifierCode(String measurementUnitQualifierCode) {
this.measurementUnitQualifierCode = measurementUnitQualifierCode;
}
public String getDutyExpressionDescription() {
return dutyExpressionDescription;
}
public void setDutyExpressionDescription(String dutyExpressionDescription) {
this.dutyExpressionDescription = dutyExpressionDescription;
}
public String getDutyExpressionAbbreviation() {
return dutyExpressionAbbreviation;
}
public void setDutyExpressionAbbreviation(String dutyExpressionAbbreviation) {
this.dutyExpressionAbbreviation = dutyExpressionAbbreviation;
}
}

View file

@ -0,0 +1,135 @@
package de.avatic.lcc.model.taric;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigDecimal;
import java.util.List;
/**
* Represents a measure condition
*/
public class MeasureCondition {
@JsonProperty("condition_code")
private String conditionCode;
@JsonProperty("condition_sequence_number")
private Integer conditionSequenceNumber;
@JsonProperty("condition_duty_amount")
private BigDecimal conditionDutyAmount;
@JsonProperty("condition_monetary_unit_code")
private String conditionMonetaryUnitCode;
@JsonProperty("condition_measurement_unit_code")
private String conditionMeasurementUnitCode;
@JsonProperty("condition_measurement_unit_qualifier_code")
private String conditionMeasurementUnitQualifierCode;
@JsonProperty("action_code")
private String actionCode;
@JsonProperty("certificate_type_code")
private String certificateTypeCode;
@JsonProperty("certificate_code")
private String certificateCode;
@JsonProperty("certificate_description")
private String certificateDescription;
@JsonProperty("condition_components")
private List<MeasureComponent> conditionComponents;
// Getters and Setters
public String getConditionCode() {
return conditionCode;
}
public void setConditionCode(String conditionCode) {
this.conditionCode = conditionCode;
}
public Integer getConditionSequenceNumber() {
return conditionSequenceNumber;
}
public void setConditionSequenceNumber(Integer conditionSequenceNumber) {
this.conditionSequenceNumber = conditionSequenceNumber;
}
public BigDecimal getConditionDutyAmount() {
return conditionDutyAmount;
}
public void setConditionDutyAmount(BigDecimal conditionDutyAmount) {
this.conditionDutyAmount = conditionDutyAmount;
}
public String getConditionMonetaryUnitCode() {
return conditionMonetaryUnitCode;
}
public void setConditionMonetaryUnitCode(String conditionMonetaryUnitCode) {
this.conditionMonetaryUnitCode = conditionMonetaryUnitCode;
}
public String getConditionMeasurementUnitCode() {
return conditionMeasurementUnitCode;
}
public void setConditionMeasurementUnitCode(String conditionMeasurementUnitCode) {
this.conditionMeasurementUnitCode = conditionMeasurementUnitCode;
}
public String getConditionMeasurementUnitQualifierCode() {
return conditionMeasurementUnitQualifierCode;
}
public void setConditionMeasurementUnitQualifierCode(String conditionMeasurementUnitQualifierCode) {
this.conditionMeasurementUnitQualifierCode = conditionMeasurementUnitQualifierCode;
}
public String getActionCode() {
return actionCode;
}
public void setActionCode(String actionCode) {
this.actionCode = actionCode;
}
public String getCertificateTypeCode() {
return certificateTypeCode;
}
public void setCertificateTypeCode(String certificateTypeCode) {
this.certificateTypeCode = certificateTypeCode;
}
public String getCertificateCode() {
return certificateCode;
}
public void setCertificateCode(String certificateCode) {
this.certificateCode = certificateCode;
}
public String getCertificateDescription() {
return certificateDescription;
}
public void setCertificateDescription(String certificateDescription) {
this.certificateDescription = certificateDescription;
}
public List<MeasureComponent> getConditionComponents() {
return conditionComponents;
}
public void setConditionComponents(List<MeasureComponent> conditionComponents) {
this.conditionComponents = conditionComponents;
}
}

View file

@ -0,0 +1,37 @@
package de.avatic.lcc.model.taric;
import java.time.LocalDate;
public class MeasureDesc {
private String lang;
private String desc;
private LocalDate descStartDate;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public LocalDate getDescStartDate() {
return descStartDate;
}
public void setDescStartDate(LocalDate descStartDate) {
this.descStartDate = descStartDate;
}
}

View file

@ -0,0 +1,43 @@
package de.avatic.lcc.model.taric;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import jakarta.validation.constraints.NotNull;
import org.springframework.data.annotation.Id;
import java.time.LocalDate;
public class MeasureExclusion {
private LocalDate startDate;
private LocalDate endDate;
private Geo geo;
public LocalDate getStartDate() {
return startDate;
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public LocalDate getEndDate() {
return endDate;
}
public void setEndDate(LocalDate endDate) {
this.endDate = endDate;
}
public Geo getGeo() {
return geo;
}
public void setGeo(Geo geo) {
this.geo = geo;
}
}

View file

@ -0,0 +1,38 @@
package de.avatic.lcc.model.taric;
import java.time.LocalDate;
public class MeasureFootnote {
private LocalDate startDate;
private LocalDate endDate;
private Footnote footnote;
public LocalDate getStartDate() {
return startDate;
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public LocalDate getEndDate() {
return endDate;
}
public void setEndDate(LocalDate endDate) {
this.endDate = endDate;
}
public Footnote getFootnote() {
return footnote;
}
public void setFootnote(Footnote footnote) {
this.footnote = footnote;
}
}

View file

@ -0,0 +1,24 @@
package de.avatic.lcc.model.taric;
public class MeasureSeries {
private String measureSeriesCode;
private MeasureSeriesDesc measureSeriesDesc;
public String getMeasureSeriesCode() {
return measureSeriesCode;
}
public void setMeasureSeriesCode(String measureSeriesCode) {
this.measureSeriesCode = measureSeriesCode;
}
public MeasureSeriesDesc getMeasureSeriesDesc() {
return measureSeriesDesc;
}
public void setMeasureSeriesDesc(MeasureSeriesDesc measureSeriesDesc) {
this.measureSeriesDesc = measureSeriesDesc;
}
}

View file

@ -0,0 +1,40 @@
package de.avatic.lcc.model.taric;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.validation.constraints.Size;
import org.springframework.data.annotation.Id;
import java.time.LocalDate;
public class MeasureSeriesDesc {
private String lang;
private String desc;
private LocalDate descStartDate;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public LocalDate getDescStartDate() {
return descStartDate;
}
public void setDescStartDate(LocalDate descStartDate) {
this.descStartDate = descStartDate;
}
}

View file

@ -0,0 +1,30 @@
package de.avatic.lcc.model.taric;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.validation.constraints.Size;
import org.springframework.data.annotation.Id;
import java.util.Set;
public class MonetaryUnit {
private String monetaryUnitCode;
private MonetaryUnitDesc monetaryUnitDesc;
public String getMonetaryUnitCode() {
return monetaryUnitCode;
}
public void setMonetaryUnitCode(String monetaryUnitCode) {
this.monetaryUnitCode = monetaryUnitCode;
}
public MonetaryUnitDesc getMonetaryUnitDesc() {
return monetaryUnitDesc;
}
public void setMonetaryUnitDesc(MonetaryUnitDesc monetaryUnitDesc) {
this.monetaryUnitDesc = monetaryUnitDesc;
}
}

View file

@ -0,0 +1,36 @@
package de.avatic.lcc.model.taric;
import java.time.LocalDate;
public class MonetaryUnitDesc {
private String lang;
private String desc;
private LocalDate descStartDate;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public LocalDate getDescStartDate() {
return descStartDate;
}
public void setDescStartDate(LocalDate descStartDate) {
this.descStartDate = descStartDate;
}
}

View file

@ -0,0 +1,127 @@
package de.avatic.lcc.model.taric;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import jakarta.validation.constraints.Size;
import org.springframework.data.annotation.Id;
import java.time.LocalDate;
import java.util.List;
import java.util.Set;
/**
* Represents a nomenclature entry in the TARIC system
*/
public class Nomenclature {
private String hscode;
private String suffix;
private Integer hierachy;
private Integer indent;
private LocalDate startDate;
private LocalDate endDate;
private Boolean isLeaf;
private LocalDate isLeafStartDate;
private Integer parentId;
private NomenclatureDesc nomenclatureDescs;
private Set<NomenclatureFootnote> footnotes;
public String getHscode() {
return hscode;
}
public void setHscode(String hscode) {
this.hscode = hscode;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public Integer getHierachy() {
return hierachy;
}
public void setHierachy(Integer hierachy) {
this.hierachy = hierachy;
}
public Integer getIndent() {
return indent;
}
public void setIndent(Integer indent) {
this.indent = indent;
}
public LocalDate getStartDate() {
return startDate;
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public LocalDate getEndDate() {
return endDate;
}
public void setEndDate(LocalDate endDate) {
this.endDate = endDate;
}
public Boolean getLeaf() {
return isLeaf;
}
public void setLeaf(Boolean leaf) {
isLeaf = leaf;
}
public LocalDate getIsLeafStartDate() {
return isLeafStartDate;
}
public void setIsLeafStartDate(LocalDate isLeafStartDate) {
this.isLeafStartDate = isLeafStartDate;
}
public Integer getParentId() {
return parentId;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
public NomenclatureDesc getNomenclatureDescs() {
return nomenclatureDescs;
}
public void setNomenclatureDescs(NomenclatureDesc nomenclatureDescs) {
this.nomenclatureDescs = nomenclatureDescs;
}
public Set<NomenclatureFootnote> getFootnotes() {
return footnotes;
}
public void setFootnotes(Set<NomenclatureFootnote> footnotes) {
this.footnotes = footnotes;
}
}

View file

@ -0,0 +1,37 @@
package de.avatic.lcc.model.taric;
import java.time.LocalDate;
public class NomenclatureDesc {
private String lang;
private String desc;
private LocalDate descStartDate;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public LocalDate getDescStartDate() {
return descStartDate;
}
public void setDescStartDate(LocalDate descStartDate) {
this.descStartDate = descStartDate;
}
}

View file

@ -0,0 +1,22 @@
package de.avatic.lcc.model.taric;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import jakarta.validation.constraints.NotNull;
/**
* Represents a footnote reference for a nomenclature
*/
public class NomenclatureFootnote {
private Footnote footnote;
public Footnote getFootnote() {
return footnote;
}
public void setFootnote(Footnote footnote) {
this.footnote = footnote;
}
}

View file

@ -0,0 +1,69 @@
package de.avatic.lcc.model.taric;
import java.util.List;
import java.util.Map;
/**
* Wrapper for the TARIC API tariff response
* Structure: { "measureType": { "additionalCode": [ AppliedMeasure... ] } }
*/
public class TariffResponse {
// Outer map: MeasureType -> Inner map
// Inner map: AdditionalCode -> List of AppliedMeasures
private Map<String, Map<String, List<AppliedMeasure>>> measures;
public TariffResponse() {
}
public TariffResponse(Map<String, Map<String, List<AppliedMeasure>>> measures) {
this.measures = measures;
}
public Map<String, Map<String, List<AppliedMeasure>>> getMeasures() {
return measures;
}
public void setMeasures(Map<String, Map<String, List<AppliedMeasure>>> measures) {
this.measures = measures;
}
/**
* Get all measures as a flat list
*/
public List<AppliedMeasure> getAllMeasures() {
if (measures == null) {
return List.of();
}
return measures.values().stream()
.flatMap(innerMap -> innerMap.values().stream())
.flatMap(List::stream)
.toList();
}
/**
* Get measures by measure type
*/
public List<AppliedMeasure> getMeasuresByType(String measureType) {
if (measures == null || !measures.containsKey(measureType)) {
return List.of();
}
return measures.get(measureType).values().stream()
.flatMap(List::stream)
.toList();
}
/**
* Get measures by measure type and additional code
*/
public List<AppliedMeasure> getMeasuresByTypeAndCode(String measureType, String additionalCode) {
if (measures == null || !measures.containsKey(measureType)) {
return List.of();
}
Map<String, List<AppliedMeasure>> innerMap = measures.get(measureType);
return innerMap.getOrDefault(additionalCode, List.of());
}
}

View file

@ -0,0 +1,51 @@
package de.avatic.lcc.model.taric;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import jakarta.validation.constraints.Size;
import org.springframework.data.annotation.Id;
import java.util.Set;
public class Unit {
private String unitCode;
private String unitQualifier;
private String label;
private UnitDesc unitDesc;
public String getUnitCode() {
return unitCode;
}
public void setUnitCode(String unitCode) {
this.unitCode = unitCode;
}
public String getUnitQualifier() {
return unitQualifier;
}
public void setUnitQualifier(String unitQualifier) {
this.unitQualifier = unitQualifier;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public UnitDesc getUnitDesc() {
return unitDesc;
}
public void setUnitDesc(UnitDesc unitDesc) {
this.unitDesc = unitDesc;
}
}

View file

@ -0,0 +1,37 @@
package de.avatic.lcc.model.taric;
import java.time.LocalDate;
public class UnitDesc {
private String lang;
private String desc;
private LocalDate descStartDate;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public LocalDate getDescStartDate() {
return descStartDate;
}
public void setDescStartDate(LocalDate descStartDate) {
this.descStartDate = descStartDate;
}
}

View file

@ -0,0 +1,37 @@
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;
}
}
}

View file

@ -209,6 +209,14 @@ public class PremisesService {
if (!admin)
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 tariffRate = materialUpdateDTO.getTariffRate() == null ? null : BigDecimal.valueOf(materialUpdateDTO.getTariffRate().doubleValue());
premiseRepository.updateMaterial(materialUpdateDTO.getPremiseIds(), materialUpdateDTO.getHsCode(), tariffRate);

View file

@ -1,18 +1,165 @@
package de.avatic.lcc.service.api;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.avatic.lcc.dto.custom.CustomDTO;
import de.avatic.lcc.dto.generic.PropertyDTO;
import de.avatic.lcc.model.db.properties.SystemPropertyMappingId;
import de.avatic.lcc.model.taric.AppliedMeasure;
import de.avatic.lcc.model.taric.Nomenclature;
import de.avatic.lcc.repositories.country.CountryRepository;
import de.avatic.lcc.repositories.properties.PropertyRepository;
import de.avatic.lcc.util.exception.base.InternalErrorException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.math.BigDecimal;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
public class CustomApiService {
public BigDecimal getTariffRate(String hsCode, Integer countryId) {
return BigDecimal.valueOf(0.03);
//TODO implement me
private final CountryRepository countryRepository;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
private final String taricApiUrl;
private final String taricApiKey;
private final PropertyRepository propertyRepository;
public CustomApiService(@Value("${taric.api.key}") String taricApiKey, @Value("${taric.api.url}") String taricApiUrl, CountryRepository countryRepository, RestTemplate restTemplate, ObjectMapper objectMapper, PropertyRepository propertyRepository) {
this.taricApiKey = taricApiKey;
this.taricApiUrl = taricApiUrl;
this.countryRepository = countryRepository;
this.restTemplate = restTemplate;
this.objectMapper = objectMapper;
this.propertyRepository = propertyRepository;
}
public CustomDTO getTariffRate(String hsCode, Integer countryId) {
var country = countryRepository.getById(countryId);
String iso = country.orElseThrow().getIsoCode().name();
try {
URI uri = UriComponentsBuilder.fromUriString(taricApiUrl).queryParam("hscode", hsCode).queryParam("countryCode", iso).path("/api/v1/tariff").build().toUri();
ResponseEntity<Map<String, Map<String, List<AppliedMeasure>>>> response = restTemplate.exchange(uri, HttpMethod.GET, createHttpEntity(), new ParameterizedTypeReference<>() {
});
var applMeasure = response.getBody();
if (applMeasure != null) {
List<String> sortedKeys = applMeasure.keySet().stream().sorted().toList().reversed();
for (String key : sortedKeys) {
Map<String, List<AppliedMeasure>> geo = applMeasure.get(key);
if (geo.containsKey(iso)) {
var rate = findMeasure(geo.get(iso));
if (rate.isPresent()) return new CustomDTO(false, rate.get(), null, countryId);
}
for (String geoKey : geo.keySet()) {
if (!"ERGA OMNES".equals(geoKey)) {
var rate = findMeasure(geo.get(geoKey));
if (rate.isPresent()) return new CustomDTO(false, rate.get(), null, countryId);
}
}
for (String geoKey : geo.keySet()) {
if ("ERGA OMNES".equals(geoKey)) {
var rate = findMeasure(geo.get(geoKey));
if (rate.isPresent()) return new CustomDTO(false, rate.get(), null, countryId);
}
}
}
}
} catch (Exception _) {
}
return propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.TARIFF_RATE).map(PropertyDTO::getCurrentValue).map(Double::valueOf).map(aDouble -> new CustomDTO(aDouble, countryId)).orElseThrow(() -> new InternalErrorException("Unable to load default custom rate. Please contact support."));
}
private Optional<Double> findMeasure(List<AppliedMeasure> appliedMeasures) {
var series = List.of("B", "C", "D");
for (var seriesCode : series) {
var measures = appliedMeasures.stream().filter(m -> m.getMeasure().getMeasureSeries().getMeasureSeriesCode().equals(seriesCode))
.map(AppliedMeasure::getAmount).filter(str -> str != null && str.trim().matches("\\d+\\.\\d+\\s*%"))
.map(str -> Double.parseDouble(str.trim().replace("%", "").trim()) / 100)
.toList();
if (!measures.isEmpty())
return Optional.of(measures.getFirst());
}
return Optional.empty();
}
private HttpHeaders createHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.set("X-API-Key", taricApiKey);
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
private HttpEntity<Void> createHttpEntity() {
return new HttpEntity<>(createHeaders());
}
public List<String> getNomenclature(String searchTerm) {
try {
URI uri = UriComponentsBuilder.fromUriString(taricApiUrl).path("/api/v1/nomenclature/declarable").queryParam("hscode", searchTerm).build().toUri();
ResponseEntity<List<Nomenclature>> response = restTemplate.exchange(uri, HttpMethod.GET, createHttpEntity(), new ParameterizedTypeReference<>() {
});
return response.getBody().stream().map(Nomenclature::getHscode).toList();
} catch (Exception e) {
return List.of();
}
}
public boolean validate(String hsCode) {
return true;
try {
URI url = UriComponentsBuilder.fromUriString(taricApiUrl).queryParam("hscode", hsCode).buildAndExpand("/api/v1/nomenclature").encode().toUri();
HttpHeaders headers = new HttpHeaders();
headers.set("X-API-Key", taricApiKey);
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
// Check if response is valid (200) and parse to Nomenclature
if (response.getStatusCode() == HttpStatus.OK) {
Nomenclature nomenclature = objectMapper.readValue(response.getBody(), Nomenclature.class);
// Check if isLeaf is true
return Boolean.TRUE.equals(nomenclature.getLeaf());
}
return false;
} catch (HttpClientErrorException.NotFound e) {
// 404 - HS code not found
return false;
} catch (Exception e) {
return false;
}
}
}
}

View file

@ -0,0 +1,63 @@
package de.avatic.lcc.service.api;
import eu.europa.ec.taxation.taric.client.*;
import jakarta.xml.bind.JAXBElement;
import org.springframework.stereotype.Service;
import org.springframework.ws.client.core.WebServiceTemplate;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@Service
public class EUTaxationApiService {
private final WebServiceTemplate webServiceTemplate;
private final ObjectFactory objectFactory;
public EUTaxationApiService(WebServiceTemplate webServiceTemplate) {
this.webServiceTemplate = webServiceTemplate;
this.objectFactory = new ObjectFactory();
}
public GoodsDescrForWsResponse getGoodsDescription(String goodsCode, String languageCode) {
GoodsDescrForWs request = new GoodsDescrForWs();
request.setGoodsCode(goodsCode);
request.setLanguageCode(languageCode);
request.setReferenceDate(getCurrentDate());
JAXBElement<GoodsDescrForWs> requestElement = objectFactory.createGoodsDescrForWs(request);
@SuppressWarnings("unchecked")
JAXBElement<GoodsDescrForWsResponse> responseElement =
(JAXBElement<GoodsDescrForWsResponse>) webServiceTemplate.marshalSendAndReceive(requestElement);
return responseElement.getValue();
}
public GoodsMeasForWsResponse getGoodsMeasures(String goodsCode, String countryCode, String tradeMovement) {
GoodsMeasForWs request = new GoodsMeasForWs();
request.setGoodsCode(goodsCode);
request.setCountryCode(countryCode);
request.setReferenceDate(getCurrentDate());
request.setTradeMovement(TradeMovementCode.fromValue(tradeMovement));
JAXBElement<GoodsMeasForWs> requestElement = objectFactory.createGoodsMeasForWs(request);
@SuppressWarnings("unchecked")
JAXBElement<GoodsMeasForWsResponse> responseElement =
(JAXBElement<GoodsMeasForWsResponse>) webServiceTemplate.marshalSendAndReceive(requestElement);
return responseElement.getValue();
}
private XMLGregorianCalendar getCurrentDate() {
try {
String currentDate = LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);
return DatatypeFactory.newInstance().newXMLGregorianCalendar(currentDate);
} catch (Exception e) {
throw new RuntimeException("Fehler beim Erstellen des Datums", e);
}
}
}

View file

@ -0,0 +1,120 @@
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 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();
}
@Async
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();
}
}

View file

@ -19,6 +19,7 @@ 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
@ -84,7 +85,7 @@ public class ChangeMaterialService {
for (var premise : premisesToProcess) {
var countryId = dto.isUserSupplierNode() ? userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow().getCountryId() : nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow().getCountryId();
var tariffRate = customApiService.getTariffRate(material.getHsCode(), countryId);
premiseRepository.updateMaterial(Collections.singletonList(premise.getId()), material.getHsCode(), tariffRate);
premiseRepository.updateMaterial(Collections.singletonList(premise.getId()), material.getHsCode(), BigDecimal.valueOf(tariffRate.getValue()));
if (!dto.isUserSupplierNode()) {
var packaging = packagingRepository.getByMaterialIdAndSupplierId(dto.getMaterialId(), premise.getSupplierNodeId());

View file

@ -111,7 +111,7 @@ public class ChangeSupplierService {
if (dto.isUpdateMasterData()) {
for (var premise : premisesToProcess) {
var tariffRate = customApiService.getTariffRate(premise.getHsCode(), supplier.getCountryId());
premiseRepository.updateTariffRate(premise.getId(), tariffRate);
premiseRepository.updateTariffRate(premise.getId(), tariffRate.getValue());
if (!dto.isUserSupplierNode()) {
var packaging = packagingRepository.getByMaterialIdAndSupplierId(premise.getMaterialId(), supplierNodeId);

View file

@ -0,0 +1,21 @@
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);
}
}

View file

@ -124,7 +124,7 @@ public class PremiseCreationService {
}
var material = materialRepository.getById(p.getMaterialId());
material.ifPresent(value -> premiseRepository.updateMaterial(Collections.singletonList(p.getId()), value.getHsCode(), customApiService.getTariffRate(value.getHsCode(), getCountryId(p))));
material.ifPresent(value -> premiseRepository.updateMaterial(Collections.singletonList(p.getId()), value.getHsCode(), BigDecimal.valueOf(customApiService.getTariffRate(value.getHsCode(), getCountryId(p)).getValue())));
}

View file

@ -74,7 +74,7 @@ public class CustomCostCalculationService {
var shippingFrequency = shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount);
var customFee = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.CUSTOM_FEE, setId).orElseThrow().getCurrentValue());
var tariffRate = premise.getTariffRate() == null ? customApiService.getTariffRate(premise.getHsCode(), premise.getCountryId()) : premise.getTariffRate();
var tariffRate = premise.getTariffRate();
var materialCost = premise.getMaterialCost().multiply(BigDecimal.valueOf(destination.getAnnualAmount()));
var fcaFee = BigDecimal.ZERO;

View file

@ -23,4 +23,7 @@ spring.flyway.baseline-on-migrate=true
spring.sql.init.mode=never
lcc.allowed_cors=
lcc.allowed_oauth_token_cors=*
lcc.allowed_oauth_token_cors=*
logging.level.org.springframework.ws=DEBUG
logging.level.org.springframework.oxm=DEBUG

File diff suppressed because it is too large Load diff

View file

@ -59,6 +59,13 @@ CREATE TABLE IF NOT EXISTS `country`
CHECK (`region_code` IN ('EMEA', 'LATAM', 'APAC', 'NAM'))
) 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`
(
`id` INT NOT NULL AUTO_INCREMENT,

View file

@ -0,0 +1,85 @@
<!-- Published by JAX-WS RI (http://jax-ws.java.net). RI's version is JAX-WS RI 2.3.0-wls122140-b230824.1031 svn-revision#e4bad6ac24018736698e2952f77e76e7f403a9f1. -->
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://goodsNomenclatureForWS.ws.taric.dds.s/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://goodsNomenclatureForWS.ws.taric.dds.s/" name="GoodsNomenclatureForWS">
<wsdl:types>
<xsd:schema xmlns:ns0="http://goodsNomenclatureForWS.ws.taric.dds.s/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://goodsNomenclatureForWS.ws.taric.dds.s/">
<xsd:include schemaLocation="https://ec.europa.eu/taxation_customs/dds2/taric/services/goods?xsd=1"/>
<xsd:complexType name="goodsDescrForWs">
<xsd:sequence>
<xsd:element ref="ns0:goodsCode"/>
<xsd:element ref="ns0:languageCode"/>
<xsd:element ref="ns0:referenceDate" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="goodsDescrForWsResponse">
<xsd:sequence>
<xsd:element name="return" type="ns0:goodsDescriptionForWsResult" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="goodsDescrForWsResponse" type="ns0:goodsDescrForWsResponse"/>
<xsd:element name="goodsDescrForWs" type="ns0:goodsDescrForWs"/>
<xsd:complexType name="goodsMeasForWs">
<xsd:sequence>
<xsd:element ref="ns0:goodsCode"/>
<xsd:element ref="ns0:countryCode"/>
<xsd:element ref="ns0:referenceDate" minOccurs="0"/>
<xsd:element ref="ns0:tradeMovement" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="goodsMeasForWsResponse">
<xsd:sequence>
<xsd:element name="return" type="ns0:goodsMeasuresForWsResult" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="goodsMeasForWsResponse" type="ns0:goodsMeasForWsResponse"/>
<xsd:element name="goodsMeasForWs" type="ns0:goodsMeasForWs"/>
</xsd:schema>
</wsdl:types>
<wsdl:message name="goodsDescrForWs">
<wsdl:part name="parameters" element="tns:goodsDescrForWs"/>
</wsdl:message>
<wsdl:message name="goodsDescrForWsResponse">
<wsdl:part name="parameters" element="tns:goodsDescrForWsResponse"/>
</wsdl:message>
<wsdl:message name="goodsMeasForWs">
<wsdl:part name="parameters" element="tns:goodsMeasForWs"/>
</wsdl:message>
<wsdl:message name="goodsMeasForWsResponse">
<wsdl:part name="parameters" element="tns:goodsMeasForWsResponse"/>
</wsdl:message>
<wsdl:portType name="GoodsNomenclatureForWs">
<wsdl:operation name="goodsDescrForWs">
<wsdl:input name="goodsDescrForWs" message="tns:goodsDescrForWs"/>
<wsdl:output name="goodsDescrForWsResponse" message="tns:goodsDescrForWsResponse"/>
</wsdl:operation>
<wsdl:operation name="goodsMeasForWs">
<wsdl:input name="goodsMeasForWs" message="tns:goodsMeasForWs"/>
<wsdl:output name="goodsMeasForWsResponse" message="tns:goodsMeasForWsResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="GoodsNomenclatureForWsImplPortBinding" type="tns:GoodsNomenclatureForWs">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<wsdl:operation name="goodsDescrForWs">
<soap:operation soapAction="" style="document"/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="goodsMeasForWs">
<soap:operation soapAction="" style="document"/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="GoodsNomenclatureForWs">
<wsdl:port name="GoodsNomenclatureForWsImplPort" binding="tns:GoodsNomenclatureForWsImplPortBinding">
<soap:address location="https://ec.europa.eu/taxation_customs/dds2/taric/services/goods"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>