BACKEND: Introduce AzureMapsController for managing Azure Maps configuration with Entra ID integration; add endpoints for configuration retrieval, health checks, and role-based permissions; enhance country dataset in alldata.sql with names for better clarity.

This commit is contained in:
Jan 2025-09-04 16:43:02 +02:00
parent 45742d731d
commit 32feeb06a0
20 changed files with 1518 additions and 587 deletions

View file

@ -64,6 +64,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>

View file

@ -0,0 +1,18 @@
<svg width="32" height="24" viewBox="0 0 32 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_270_67459)">
<rect width="32" height="24" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0V24H32V0H0Z" fill="#F7FCFF"/>
<mask id="mask0_270_67459" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="32" height="24">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0V24H32V0H0Z" fill="white"/>
</mask>
<g mask="url(#mask0_270_67459)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0V8H32V0H0Z" fill="#E31D1C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 16V24H32V16H0Z" fill="#3D58DB"/>
</g>
</g>
<defs>
<clipPath id="clip0_270_67459">
<rect width="32" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 788 B

View file

@ -0,0 +1,346 @@
<template>
<div ref="mapContainer" class="map-container"></div>
</template>
<script>
import { config } from '@/config';
// Annahme: Sie haben bereits MSAL oder ähnliche Entra ID Integration
// import { msalInstance } from '@/auth/msal'; // Ihr MSAL Setup
export default {
name: 'AzureMapsComponent',
props: {
coordinates: {
type: Object,
required: true,
validator(value) {
return value &&
typeof value.latitude === 'number' &&
typeof value.longitude === 'number';
}
},
draggable: {
type: Boolean,
default: false
}
},
data() {
return {
map: null,
marker: null,
datasource: null,
azureMapsClientId: null // Wird vom Backend geholt
};
},
mounted() {
this.initializeMap();
},
beforeUnmount() {
if (this.map) {
this.map.dispose();
}
},
watch: {
coordinates: {
handler(newCoords) {
if (this.marker && this.map) {
this.updateMarkerPosition(newCoords);
}
},
deep: true
},
draggable(newValue) {
if (this.marker) {
this.updateMarkerDraggable(newValue);
}
}
},
methods: {
async initializeMap() {
try {
// Azure Maps SDK laden
if (!window.atlas) {
await this.loadAzureMapsSDK();
}
// Azure Maps Client ID vom Backend abrufen
await this.fetchAzureMapsConfig();
// Karte mit Entra ID-Authentifizierung initialisieren
this.map = new window.atlas.Map(this.$refs.mapContainer, {
center: [this.coordinates.longitude, this.coordinates.latitude],
zoom: 15,
authOptions: {
authType: 'aad',
clientId: this.azureMapsClientId,
aadAppId: this.azureMapsClientId,
aadTenant: 'common', // oder Ihre spezifische Tenant ID
getToken: this.getEntraIdTokenForMaps
}
});
// Warten bis die Karte geladen ist
this.map.events.add('ready', () => {
this.setupMap();
});
} catch (error) {
console.error('Fehler beim Initialisieren der Karte:', error);
this.$emit('error', error);
}
},
/**
* Azure Maps Konfiguration vom Backend abrufen
*/
async fetchAzureMapsConfig() {
try {
const response = await fetch(`${config.backendUrl}/maps/config`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await this.getCurrentUserToken()}`
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const configData = await response.json();
this.azureMapsClientId = configData.clientId;
console.debug('Azure Maps Konfiguration erfolgreich abgerufen');
} catch (error) {
console.error('Fehler beim Abrufen der Azure Maps Konfiguration:', error);
throw new Error('Konnte Azure Maps Konfiguration nicht abrufen');
}
},
/**
* Entra ID Token für Azure Maps abrufen
*/
async getEntraIdTokenForMaps() {
try {
// Azure Maps spezifischen Scope verwenden
const tokenRequest = {
scopes: ['https://atlas.microsoft.com/user_impersonation'],
account: msalInstance.getActiveAccount()
};
const response = await msalInstance.acquireTokenSilent(tokenRequest);
return response.accessToken;
} catch (error) {
console.error('Fehler beim Abrufen des Azure Maps Tokens:', error);
// Fallback: Interactive Token Acquisition
try {
const response = await msalInstance.acquireTokenPopup(tokenRequest);
return response.accessToken;
} catch (popupError) {
console.error('Interactive Token-Abruf fehlgeschlagen:', popupError);
throw new Error('Konnte kein Token für Azure Maps abrufen');
}
}
},
/**
* Aktuelles Benutzer-Token für Backend-Aufrufe
*/
async getCurrentUserToken() {
try {
const account = msalInstance.getActiveAccount();
if (!account) {
throw new Error('Kein aktiver Benutzer gefunden');
}
const tokenRequest = {
scopes: [`${config.backendUrl}/.default`], // Backend API Scope
account: account
};
const response = await msalInstance.acquireTokenSilent(tokenRequest);
return response.accessToken;
} catch (error) {
console.error('Fehler beim Abrufen des Backend-Tokens:', error);
throw new Error('Konnte Backend-Token nicht abrufen');
}
},
loadAzureMapsSDK() {
return new Promise((resolve, reject) => {
if (window.atlas) {
resolve();
return;
}
// CSS laden
const linkElement = document.createElement('link');
linkElement.rel = 'stylesheet';
linkElement.href = 'https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css';
document.head.appendChild(linkElement);
// JavaScript SDK laden
const scriptElement = document.createElement('script');
scriptElement.src = 'https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js';
scriptElement.onload = resolve;
scriptElement.onerror = reject;
document.head.appendChild(scriptElement);
});
},
setupMap() {
// Datenquelle für den Marker erstellen
this.datasource = new window.atlas.source.DataSource();
this.map.sources.add(this.datasource);
// Symbol-Layer für den Marker hinzufügen
this.map.layers.add(new window.atlas.layer.SymbolLayer(this.datasource, null, {
iconOptions: {
allowOverlap: true,
ignorePlacement: true
}
}));
// Marker erstellen
this.createMarker();
// Event-Listener für draggable Funktionalität
if (this.draggable) {
this.enableMarkerDrag();
}
},
createMarker() {
const point = new window.atlas.data.Point([
this.coordinates.longitude,
this.coordinates.latitude
]);
this.marker = new window.atlas.data.Feature(point, {
id: 'marker',
draggable: this.draggable
});
this.datasource.add(this.marker);
},
updateMarkerPosition(coords) {
if (this.marker && this.datasource) {
this.marker.geometry.coordinates = [coords.longitude, coords.latitude];
this.datasource.setShapes([this.marker]);
this.map.setCamera({
center: [coords.longitude, coords.latitude]
});
}
},
updateMarkerDraggable(isDraggable) {
if (isDraggable) {
this.enableMarkerDrag();
} else {
this.disableMarkerDrag();
}
},
enableMarkerDrag() {
this.map.events.add('mousedown', this.datasource, this.onMarkerMouseDown);
this.map.events.add('mousemove', this.onMarkerMouseMove);
this.map.events.add('mouseup', this.onMarkerMouseUp);
this.map.events.add('touchstart', this.datasource, this.onMarkerTouchStart);
this.map.events.add('touchmove', this.onMarkerTouchMove);
this.map.events.add('touchend', this.onMarkerTouchEnd);
this.map.getCanvasContainer().style.cursor = 'pointer';
},
disableMarkerDrag() {
this.map.events.remove('mousedown', this.datasource, this.onMarkerMouseDown);
this.map.events.remove('mousemove', this.onMarkerMouseMove);
this.map.events.remove('mouseup', this.onMarkerMouseUp);
this.map.events.remove('touchstart', this.datasource, this.onMarkerTouchStart);
this.map.events.remove('touchmove', this.onMarkerTouchMove);
this.map.events.remove('touchend', this.onMarkerTouchEnd);
this.map.getCanvasContainer().style.cursor = 'default';
},
onMarkerMouseDown(e) {
if (e.shapes && e.shapes.length > 0) {
this.isDragging = true;
this.map.getCanvasContainer().style.cursor = 'grabbing';
}
},
onMarkerMouseMove(e) {
if (this.isDragging) {
this.marker.geometry.coordinates = [e.position[0], e.position[1]];
this.datasource.setShapes([this.marker]);
this.$emit('coordinates-changed', {
latitude: e.position[1],
longitude: e.position[0]
});
}
},
onMarkerMouseUp() {
if (this.isDragging) {
this.isDragging = false;
this.map.getCanvasContainer().style.cursor = 'pointer';
}
},
onMarkerTouchStart(e) {
if (e.shapes && e.shapes.length > 0) {
this.isDragging = true;
}
},
onMarkerTouchMove(e) {
if (this.isDragging && e.position) {
this.marker.geometry.coordinates = [e.position[0], e.position[1]];
this.datasource.setShapes([this.marker]);
this.$emit('coordinates-changed', {
latitude: e.position[1],
longitude: e.position[0]
});
}
},
onMarkerTouchEnd() {
this.isDragging = false;
},
// Öffentliche Methoden
setCenter(coords) {
if (this.map) {
this.map.setCamera({
center: [coords.longitude, coords.latitude]
});
}
},
setZoom(zoom) {
if (this.map) {
this.map.setCamera({ zoom });
}
}
}
};
</script>
<style scoped>
.map-container {
width: 100%;
height: 250px;
min-height: 150px;
}
</style>

View file

@ -1,15 +1,139 @@
<template>
<h3>Country properties</h3>
<div class="country-container">
<div class="country-search-container">
<div>Find country to edit:</div>
<div class="country-search-box-container"><autosuggest-searchbar
:fetch-suggestions="query"
flag-resolver="iso_code"
placeholder="Search country..."
no-results-text="no country found"
variant="flags"
title-resolver="name"
subtitle-resolver="iso_code"
:reset-on-select="true"
@selected="selectCountry"
></autosuggest-searchbar></div>
</div>
<div class="country-edit-container">
<div class="country-info-container">
<box v-if="selectedCountry" class="country-info-box">
<flag size="xl" :iso="selectedCountry.iso_code"></flag>
<div class="country-info-text" >
<div class="country-info-text-caption">{{ selectedCountry.name }} ({{ selectedCountry.iso_code}})</div>
<div class="country-info-text-subline">{{ selectedCountry.region_code}}</div>
</div>
</box>
</div>
<div v-if="selectedCountry" >
<div v-if="!loading" class="period-select-container">
<span class="period-select-caption">Property set:</span>
<dropdown :options="periods"
emptyText="No property set available"
class="period-select"
placeholder="Select a property set"
v-model="selectedPeriod"
></dropdown>
</div>
<transition name="properties-fade" mode="out-in">
<div v-if="!loading" class="properties-list" :key="selectedPeriod">
<transition-group name="property-item" tag="div">
<property v-for="property in properties"
:key="`${selectedPeriod}-${selectedCountry.id}-${property.external_mapping_id}`"
:property="property"
:disabled="!isValidPeriodActive"
@save="saveProperty"></property>
</transition-group>
</div>
</transition>
</div>
</div>
</div>
</template>
<script>
import {mapStores} from "pinia";
import {usePropertiesStore} from "@/store/properties.js";
import {usePremiseEditStore} from "@/store/premiseEdit.js";
import {useCountryStore} from "@/store/country.js";
import {usePropertySetsStore} from "@/store/propertySets.js";
import Flag from "@/components/UI/Flag.vue";
import Box from "@/components/UI/Box.vue";
import AutosuggestSearchbar from "@/components/UI/AutoSuggestSearchBar.vue";
import Property from "@/components/layout/config/Property.vue";
import IconButton from "@/components/UI/IconButton.vue";
import Tooltip from "@/components/UI/Tooltip.vue";
import ModalDialog from "@/components/UI/ModalDialog.vue";
import Dropdown from "@/components/UI/Dropdown.vue";
export default {
name: "CountryProperties",
components: {Dropdown, ModalDialog, Tooltip, IconButton, Property, AutosuggestSearchbar, Box, Flag},
computed: {
periods() {
const periods = [];
const ps = this.propertySetsStore.getPeriods;
const current = this.propertySetsStore.getCurrentPeriodId;
if ((ps ?? null) === null) {
return null;
}
for (const p of ps) {
const value = (p.state === "DRAFT" || p.state === "VALID") ? "CURRENT" : `${this.buildDate(p.start_date)} - ${this.buildDate(p.end_date)} ${p.state === "INVALID" ? "(INVALID)" : ""}`;
const period = {id: p.id, value: value};
if (p.state !== "VALID" || p.id === current)
periods.push(period);
}
return periods;
},
loading() {
return this.countryStore.isLoading;
},
...mapStores(useCountryStore, usePropertySetsStore),
countries() {
return this.countryStore.getCountries;
},
properties() {
return this.selectedCountry.properties;
},
isValidPeriodActive() {
const state = this.propertySetsStore.getPeriodState(this.selectedPeriod);
return state === "VALID" || state === "DRAFT";
},
selectedCountry() {
return this.countryStore.getSelectedCountry;
},
selectedPeriod: {
get() {
return this.propertySetsStore.getSelectedPeriod
},
set(value) {
console.log(value)
this.propertySetsStore.setSelectedPeriod(value);
this.countryStore.selectPeriod(value);
}
},
},
methods: {
async saveProperty(property) {
},
async query(query) {
await this.countryStore.setQuery(query);
return this.countryStore.getCountries;
},
async selectCountry(country) {
this.countryStore.selectCountry(country.id);
},
async selectPeriod(periodId) {
this.countryStore.selectPeriod(periodId);
}
}
}
</script>
@ -17,4 +141,92 @@ export default {
<style scoped>
.country-info-text {
display: flex;
flex-direction: column;
gap: 0.4rem;
font-size: 1.4rem;
}
.country-info-text-caption {
display: flex;
flex-direction: column;
gap: 0.4rem;
font-size: 1.6rem;
font-weight: 500;
}
.country-info-text-subline {
display: flex;
flex-direction: column;
gap: 0.4rem;
font-size: 1.4rem;
font-weight: 400;
color: #6b7280;
}
.country-info-container {
display: flex;
justify-content: flex-start;
gap: 1.6rem;
margin: 3.2rem 0 ;
}
.country-info-box {
min-width: 20rem;
display: flex;
flex-direction: row;
gap: 3.6rem;
padding: 2.4rem;
align-items: center;
}
.period-select-container {
display: flex;
justify-content: flex-end;
align-items: center;
margin-bottom: 1rem;
gap: 1.6rem;
font-size: 1.4rem;
}
.period-select {
flex: 0 1 40rem
}
.period-select-caption {
font-weight: 500;
}
.country-container {
display: flex;
flex-direction: column;
min-height: 50vh;
}
.country-search-container {
display: flex;
flex-direction: row;
gap: 1.6rem;
align-items: center;
font-size: 1.4rem;
font-weight: 500;
}
.country-search-box-container {
flex: 1 1 auto;
}
.country-edit-container {
display: flex;
flex-direction: column;
gap: 1.6rem;
font-size: 1.4rem;
flex: 1 1 auto;
}
</style>

View file

@ -1,18 +1,20 @@
<template>
<div class="properties-container">
<div v-if="!loading" class="period-select-container"><span class="period-select-caption">Property set:</span>
<div v-if="!loading" class="period-select-container">
<span class="period-select-caption">Property set:</span>
<dropdown :options="periods"
emptyText="No property set available"
class="period-select"
placeholder="Select a property set"
v-model="selectedPeriod"
></dropdown>
<tooltip position="left" text="Invalidate the selected property set">
<icon-button icon="trash" @click="deletePeriod" :disabled="disableDeleteButton"></icon-button>
</tooltip>
<modal-dialog title="Do you really want to invalidate this property set?" dismiss-text="No" accept-text="Yes"
<modal-dialog title="Do you really want to invalidate this property set?"
dismiss-text="No"
accept-text="Yes"
:state="modalDialogDeleteState"
message="If you invalidate this property set, this will also invalidate all calculations done with this property set. This cannot be undone!"
@click="deleteModalClick"
@ -20,17 +22,21 @@
</modal-dialog>
</div>
<div v-if="!loading" class="properties-list">
<property v-for="property in properties" :key="property.external_mapping_id" :property="property"
:disabled="!isValidPeriodActive" @save="saveProperty"></property>
<transition name="properties-fade" mode="out-in">
<div v-if="!loading" class="properties-list" :key="selectedPeriod">
<transition-group name="property-item" tag="div">
<property v-for="property in properties"
:key="`${selectedPeriod}-${property.external_mapping_id}`"
:property="property"
:disabled="!isValidPeriodActive"
@save="saveProperty"></property>
</transition-group>
</div>
</transition>
</div>
</template>
<script>
import {mapStores} from "pinia";
import {usePropertiesStore} from "@/store/properties.js";
import Property from "@/components/layout/config/Property.vue";
@ -38,7 +44,6 @@ import Dropdown from "@/components/UI/Dropdown.vue";
import BasicButton from "@/components/UI/BasicButton.vue";
import IconButton from "@/components/UI/IconButton.vue";
import Tooltip from "@/components/UI/Tooltip.vue";
import modalDialog from "@/components/UI/ModalDialog.vue";
import ModalDialog from "@/components/UI/ModalDialog.vue";
import NotificationBar from "@/components/UI/NotificationBar.vue";
import {usePropertySetsStore} from "@/store/propertySets.js";
@ -67,11 +72,11 @@ export default {
},
selectedPeriod: {
get() {
return this.selectedPeriodId === null ? this.propertySetsStore.getCurrentPeriodId : this.selectedPeriodId;
return this.propertySetsStore.getSelectedPeriod
},
set(value) {
console.log(value)
this.selectedPeriodId = value;
this.propertySetsStore.setSelectedPeriod(value);
this.propertiesStore.loadProperties(value);
}
},
@ -92,8 +97,6 @@ export default {
const value = (p.state === "DRAFT" || p.state === "VALID") ? "CURRENT" : `${this.buildDate(p.start_date)} - ${this.buildDate(p.end_date)} ${p.state === "INVALID" ? "(INVALID)" : ""}`;
const period = {id: p.id, value: value};
console.log(p, p.state !== "VALID" , p.id === current, period)
if (p.state !== "VALID" || p.id === current)
periods.push(period);
}
@ -105,7 +108,6 @@ export default {
this.propertiesStore.reload();
},
methods: {
saveProperty(property) {
this.propertiesStore.setProperty(property);
},
@ -121,15 +123,13 @@ export default {
this.modalDialogDeleteState = false;
if (action === 'accept')
this.propertySetsStore.invalidate(this.selectedPeriodId);
this.propertySetsStore.invalidate();
}
}
}
</script>
<style scoped>
.period-select-container {
display: flex;
justify-content: flex-end;
@ -141,11 +141,68 @@ export default {
.period-select {
flex: 0 1 40rem
}
.period-select-caption {
font-weight: 500;
}
/* SOLUTION 1: Add relative positioning and min-height to prevent collapse */
.properties-list {
position: relative;
min-height: 100px; /* Adjust based on your typical content height */
}
/* SOLUTION 2: Keep elements in normal flow during transition
.properties-fade-enter-active,
.properties-fade-leave-active {
transition: opacity 0.3s ease;
}
.properties-fade-enter-from {
opacity: 0;
}
.properties-fade-leave-to {
opacity: 0;
}*/
/* SOLUTION 3: For transition-group, ensure proper positioning
.property-item-enter-active,
.property-item-leave-active {
transition: all 0.3s ease;
}*/
.property-item-enter-from {
opacity: 0;
transform: translateY(-10px);
}
.property-item-leave-to {
opacity: 0;
transform: translateY(10px);
}
/* Prevent layout jumping during leave transition */
.property-item-leave-active {
position: absolute;
width: 100%;
}
/* Smooth repositioning of remaining items */
.property-item-move {
transition: transform 0.3s ease;
}
/* ALTERNATIVE SOLUTION: If you still have issues, try this instead */
/*
.properties-container {
min-height: 500px;
}
.properties-list {
position: relative;
overflow: hidden;
}
*/
</style>

View file

@ -16,8 +16,7 @@
<div class="input-field">{{ coordinatesDMS }}</div>
</div>
<div class="supplier-map">
<img width="300px"
src="https://www.galerie-braunbehrens.de/wp-content/uploads/2020/06/placeholder-google-maps.jpg" alt="map">
<azure-maps-component :draggable="true" :coordinates="supplierCoordinates"></azure-maps-component>
</div>
<div class="footer">
<modal :state="selectSupplierModalState" @close="closeEditModal">
@ -38,10 +37,11 @@ import {PhUser} from "@phosphor-icons/vue";
import ModalDialog from "@/components/UI/ModalDialog.vue";
import SelectNode from "@/components/layout/node/SelectNode.vue";
import Modal from "@/components/UI/Modal.vue";
import AzureMapsComponent from "@/components/UI/AzureMapsComponent.vue";
export default {
name: "SupplierView",
components: {Modal, SelectNode, ModalDialog, PhUser, Flag, InputField, IconButton},
components: {AzureMapsComponent, Modal, SelectNode, ModalDialog, PhUser, Flag, InputField, IconButton},
emits: ['updateSupplier'],
props: {
supplierName: {

View file

@ -2,25 +2,33 @@ import {defineStore} from 'pinia'
import {config} from '@/config'
import {useErrorStore} from "@/store/error.js";
import {useStageStore} from "@/store/stage.js";
import {usePropertySetsStore} from "@/store/propertySets.js";
export const useCountryStore = defineStore('countryStore', {
export const useCountryStore = defineStore('country', {
state() {
return {
countries: null,
properties: null,
periods: null,
loadedPeriod: null,
loading: false,
query: null,
selectedCountryId: null,
selectedPeriodId: null,
countryDetail: null,
}
},
getters: {
getCountries(state) {
return state.countries;
},
getSelectedCountry(state) {
return state.countryDetail;
}
},
actions: {
async setProperty(property) {
if(this.properties === null) return;
console.log(property)
const prop = this.properties.find(p => p.external_mapping_id === property.id);
if((prop ?? null) === null) return;
@ -31,33 +39,43 @@ export const useCountryStore = defineStore('countryStore', {
await this.performRequest('PUT', url, body, false);
prop.draft_value = property.reset ? null : property.value;
},
async reload() {
await this.loadPeriods();
await this.loadCountries();
const stage = useStageStore();
await stage.checkStagedChanges();
async selectPeriod(periodId) {
this.selectedPeriodId = periodId;
await this.loadCountryDetail();
},
async loadPeriods() {
this.loading = true;
const url = `${config.backendUrl}/properties/periods`;
this.periods = await this.performRequest('GET', url, null);
this.loading = false;
async selectCountry(countryId) {
this.selectedCountryId = countryId;
await this.loadCountryDetail();
},
async loadCountries(period = null) {
async loadCountryDetail() {
if(this.selectedCountryId == null) return;
this.loading = true;
const params = new URLSearchParams();
if (period !== null)
params.append('property_set', period);
const url = `${config.backendUrl}/countries/all/${params.size === 0 ? '' : '?'}${params.toString()}`;
this.properties = await this.performRequest('GET', url, null);
this.loadedPeriod = period;
if(this.selectedPeriodId !== null)
params.append('property_set', this.selectedPeriodId);
const url = `${config.backendUrl}/countries/${this.selectedCountryId}/${params.size === 0 ? '' : '?'}${params.toString()}`;
this.countryDetail = await this.performRequest('GET', url, null);
this.loading = false;
},
async setQuery(query) {
this.query = query;
await this.fetchCountryList();
},
async reload() {
const stage = useStageStore();
await stage.checkStagedChanges();
},
async fetchCountryList() {
const params = new URLSearchParams();
if (this.query !== null)
params.append('filter', this.query);
const url = `${config.backendUrl}/countries/all/${params.size === 0 ? '' : '?'}${params.toString()}`;
this.countries = await this.performRequest('GET', url, null);
},
async performRequest(method, url, body, expectResponse = true) {
const params = {

View file

@ -7,6 +7,7 @@ export const usePropertySetsStore = defineStore('propertySets', {
state() {
return {
periods: null,
selectedPeriod: null,
}
},
getters: {
@ -27,6 +28,9 @@ export const usePropertySetsStore = defineStore('propertySets', {
return period.id;
}
},
getSelectedPeriod(state) {
return state.selectedPeriod;
},
getPeriodState(state) {
return function(periodId) {
if (state.periods === null)
@ -37,10 +41,12 @@ export const usePropertySetsStore = defineStore('propertySets', {
}
},
actions: {
async invalidate(periodId) {
setSelectedPeriod(periodId) {
this.selectedPeriod = periodId;
},
async invalidate() {
const url = `${config.backendUrl}/properties/periods/${periodId}/`;
const url = `${config.backendUrl}/properties/periods/${this.getSelectedPeriod}/`;
this.periods = await this.performRequest('DELETE', url, null, false);
await this.reload();
@ -50,6 +56,7 @@ export const usePropertySetsStore = defineStore('propertySets', {
this.loading = true;
const url = `${config.backendUrl}/properties/periods`;
this.periods = await this.performRequest('GET', url, null, );
this.selectedPeriod = this.getCurrentPeriodId;
this.loading = false;
},
async performRequest(method, url, body, expectResponse = true) {

View file

@ -0,0 +1,39 @@
package de.avatic.lcc.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.boot.web.client.RestTemplateBuilder;
import java.time.Duration;
/**
* Konfiguration für Azure Maps Integration
*/
@Configuration
@EnableCaching
public class AzureMapsConfig {
/**
* RestTemplate Bean für HTTP-Requests
*/
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.build();
}
/**
* Cache Manager für Token-Caching
* Tokens werden für 50 Minuten gecacht (Azure Maps Tokens sind 60 Min gültig)
*/
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("azureMapsTokens");
}
}

View file

@ -0,0 +1,219 @@
package de.avatic.lcc.controller.maps;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.security.access.prepost.PreAuthorize;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Instant;
/**
* REST Controller für Azure Maps mit Entra ID Integration
*/
@RestController
@RequestMapping("/api/maps")
@CrossOrigin(origins = {"http://localhost:3000", "http://localhost:8080", "https://yourdomain.com"})
public class AzureMapsController {
private static final Logger logger = LoggerFactory.getLogger(AzureMapsController.class);
@Value("${azure.maps.client.id}")
private String azureMapsClientId;
@Value("${azure.maps.resource.id:}")
private String azureMapsResourceId;
/**
* Endpoint zum Abrufen der Azure Maps Konfiguration
* Nur authentifizierte Benutzer können diese Konfiguration abrufen
*
* @return ConfigResponse mit Client ID und anderen Konfigurationsdaten
*/
@GetMapping("/config")
@PreAuthorize("hasAuthority('SCOPE_https://yourdomain.com/api.read')") // Anpassen an Ihre API-Scopes
public ResponseEntity<?> getConfig() {
try {
logger.debug("Providing Azure Maps configuration for authenticated user");
if (azureMapsClientId == null || azureMapsClientId.trim().isEmpty()) {
logger.error("Azure Maps Client ID is null or empty - check application.properties");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("Azure Maps Client ID not configured"));
}
ConfigResponse response = new ConfigResponse(
azureMapsClientId.trim(),
azureMapsResourceId != null ? azureMapsResourceId.trim() : null
);
logger.debug("Successfully providing Azure Maps configuration");
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("Error providing Azure Maps configuration: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("Internal server error: " + e.getMessage()));
}
}
/**
* Health Check Endpoint für Azure Maps Integration
* Öffentlich zugänglich für Monitoring
*
* @return Status der Azure Maps Konfiguration
*/
@GetMapping("/health")
public ResponseEntity<?> healthCheck() {
try {
boolean isConfigured = azureMapsClientId != null && !azureMapsClientId.trim().isEmpty();
if (isConfigured) {
return ResponseEntity.ok(new HealthResponse("Azure Maps Entra ID configuration is valid", true));
} else {
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(new HealthResponse("Azure Maps Client ID is not configured", false));
}
} catch (Exception e) {
logger.error("Health check failed", e);
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(new HealthResponse("Azure Maps service is unavailable", false));
}
}
/**
* Endpoint für rollenbasierte Zugriffskontrolle
* Prüft ob der Benutzer Azure Maps verwenden darf
*/
@GetMapping("/permissions")
@PreAuthorize("hasAuthority('SCOPE_https://yourdomain.com/api.read')")
public ResponseEntity<?> checkPermissions() {
try {
// Hier können Sie zusätzliche Berechtigungsprüfungen implementieren
// z.B. basierend auf Benutzerrolle, Gruppe, etc.
PermissionResponse response = new PermissionResponse(
true, // canUseAzureMaps
true, // canCreateMarkers
true // canModifyMarkers
);
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("Error checking permissions: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("Error checking permissions: " + e.getMessage()));
}
}
/**
* Response-Klasse für Konfiguration
*/
public static class ConfigResponse {
private final String clientId;
private final String resourceId;
private final long timestamp;
public ConfigResponse(String clientId, String resourceId) {
this.clientId = clientId;
this.resourceId = resourceId;
this.timestamp = Instant.now().toEpochMilli();
}
public String getClientId() {
return clientId;
}
public String getResourceId() {
return resourceId;
}
public long getTimestamp() {
return timestamp;
}
}
/**
* Response-Klasse für Berechtigungen
*/
public static class PermissionResponse {
private final boolean canUseAzureMaps;
private final boolean canCreateMarkers;
private final boolean canModifyMarkers;
private final long timestamp;
public PermissionResponse(boolean canUseAzureMaps, boolean canCreateMarkers, boolean canModifyMarkers) {
this.canUseAzureMaps = canUseAzureMaps;
this.canCreateMarkers = canCreateMarkers;
this.canModifyMarkers = canModifyMarkers;
this.timestamp = Instant.now().toEpochMilli();
}
public boolean isCanUseAzureMaps() {
return canUseAzureMaps;
}
public boolean isCanCreateMarkers() {
return canCreateMarkers;
}
public boolean isCanModifyMarkers() {
return canModifyMarkers;
}
public long getTimestamp() {
return timestamp;
}
}
/**
* Response-Klasse für Fehler-Antworten
*/
public static class ErrorResponse {
private final String error;
private final long timestamp;
public ErrorResponse(String error) {
this.error = error;
this.timestamp = Instant.now().toEpochMilli();
}
public String getError() {
return error;
}
public long getTimestamp() {
return timestamp;
}
}
/**
* Response-Klasse für Health Check
*/
public static class HealthResponse {
private final String message;
private final boolean healthy;
private final long timestamp;
public HealthResponse(String message, boolean healthy) {
this.message = message;
this.healthy = healthy;
this.timestamp = Instant.now().toEpochMilli();
}
public String getMessage() {
return message;
}
public boolean isHealthy() {
return healthy;
}
public long getTimestamp() {
return timestamp;
}
}
}

View file

@ -19,6 +19,9 @@ public class Country {
@Size(max=5)
private RegionCode regionCode;
@NotNull
@Size(max=255)
private String name;
@NotNull
private Boolean isDeprecated;
@ -54,4 +57,12 @@ public class Country {
public void setDeprecated(Boolean deprecated) {
isDeprecated = deprecated;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View file

@ -29,7 +29,7 @@ public class CountryRepository {
public Optional<Country> getById(Integer id) {
String query = """
SELECT country.id AS id, country.iso_code AS iso_code, country.region_code AS region_code, country.is_deprecated AS is_deprecated
SELECT country.id AS id, country.iso_code AS iso_code, country.region_code AS region_code, country.name AS name, country.is_deprecated AS is_deprecated
FROM country
WHERE country.id = ?
""";
@ -47,7 +47,7 @@ public class CountryRepository {
public Optional<Country> getByIsoCode(IsoCode isoCode) {
String query = """
SELECT country.id AS id, country.iso_code AS iso_code, country.region_code AS region_code, country.is_deprecated AS is_deprecated
SELECT country.id AS id, country.iso_code AS iso_code, country.region_code AS region_code, country.is_deprecated AS is_deprecated, country.name AS name
FROM country
WHERE country.iso_code = ?
""";
@ -67,14 +67,14 @@ public class CountryRepository {
var countries = filter.isPresent() ?
jdbcTemplate.query(query, new CountryMapper(),
"%" + filter.get() + "%", "%" + filter.get() + "%", pagination.getLimit(), pagination.getOffset()) :
"%" + filter.get() + "%", "%" + filter.get() + "%", "%" + filter.get() + "%", pagination.getLimit(), pagination.getOffset()) :
jdbcTemplate.query(query, new CountryMapper()
, pagination.getLimit(), pagination.getOffset());
Integer totalCount = filter.isPresent() ?
jdbcTemplate.queryForObject(
buildCountQuery(filter.get(), excludeDeprecated),
Integer.class, "%" + filter.get() + "%", "%" + filter.get() + "%"
Integer.class, "%" + filter.get() + "%", "%" + filter.get() + "%", "%" + filter.get() + "%"
) :
jdbcTemplate.queryForObject(
buildCountQuery(null, excludeDeprecated),
@ -89,12 +89,12 @@ public class CountryRepository {
String query = buildQuery(filter.orElse(null), excludeDeprecated, false);
var countries = filter.map(f -> jdbcTemplate.query(query, new CountryMapper(),
"%" + f + "%", "%" + f + "%"))
"%" + f + "%", "%" + f + "%", "%" + f + "%"))
.orElseGet(() -> jdbcTemplate.query(query, new CountryMapper()));
Integer totalCount = filter.map(f -> jdbcTemplate.queryForObject(
buildCountQuery(f, excludeDeprecated),
Integer.class, "%" + f + "%", "%" + f + "%"))
Integer.class, "%" + f + "%", "%" + f + "%", "%" + f + "%"))
.orElseGet(() -> jdbcTemplate.queryForObject(
buildCountQuery(null, excludeDeprecated),
Integer.class));
@ -111,7 +111,7 @@ public class CountryRepository {
queryBuilder.append(" AND is_deprecated = FALSE");
}
if (filter != null) {
queryBuilder.append(" AND (iso_code LIKE ? OR region_code LIKE ?) ");
queryBuilder.append(" AND (iso_code LIKE ? OR region_code LIKE ? or name LIKE ?) ");
}
return queryBuilder.toString();
@ -119,14 +119,14 @@ public class CountryRepository {
private String buildQuery(String filter, boolean excludeDeprecated, boolean hasLimit) {
StringBuilder queryBuilder = new StringBuilder("""
SELECT id, iso_code, region_code, is_deprecated
SELECT id, iso_code, region_code, is_deprecated, name
FROM country WHERE 1=1""");
if (excludeDeprecated) {
queryBuilder.append(" AND is_deprecated = FALSE ");
}
if (filter != null) {
queryBuilder.append(" AND (iso_code LIKE ? OR region_code LIKE ?) ");
queryBuilder.append(" AND (iso_code LIKE ? OR region_code LIKE ? OR name LIKE ?) ");
}
if (hasLimit) {
queryBuilder.append(" ORDER BY iso_code LIMIT ? OFFSET ? ");
@ -151,6 +151,7 @@ public class CountryRepository {
entity.setIsoCode(IsoCode.valueOf(rs.getString("iso_code")));
entity.setRegionCode(RegionCode.valueOf(rs.getString("region_code")));
entity.setDeprecated(rs.getBoolean("is_deprecated"));
entity.setName(rs.getString("name"));
return entity;
}

View file

@ -29,7 +29,7 @@ public class SearchQueryResult<T> {
public SearchQueryResult(List<T> result, Integer page, Integer totalElements, Integer elementsPerPage) {
this.result = result;
this.page = page;
this.totalPages = totalElements/elementsPerPage + (totalElements%elementsPerPage == 0 ? 0 : 1);
this.totalPages = elementsPerPage == 0 ? 0 : (totalElements/elementsPerPage + (totalElements%elementsPerPage == 0 ? 0 : 1));
this.totalElements = totalElements;
this.elementsPerPage = elementsPerPage;
}

View file

@ -5,6 +5,7 @@ import de.avatic.lcc.dto.generic.CountryDTO;
import de.avatic.lcc.model.country.Country;
import de.avatic.lcc.model.country.IsoCode;
import de.avatic.lcc.model.properties.CountryPropertyMappingId;
import de.avatic.lcc.model.rates.ValidityPeriodState;
import de.avatic.lcc.repositories.country.CountryPropertyRepository;
import de.avatic.lcc.repositories.country.CountryRepository;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
@ -143,7 +144,9 @@ public class CountryService {
dto.setName(entity.getIsoCode().getFullName());
dto.setId(entity.getId());
if (0 == propertySetId) {
var state = propertySetId != 0 ? propertySetRepository.getState(propertySetId) : null;
if (0 == propertySetId || (state != ValidityPeriodState.EXPIRED && state != ValidityPeriodState.INVALID)) {
var props = countryPropertyRepository.listPropertiesByCountryId(entity.getId());
if (props.isEmpty())

View file

@ -13,7 +13,7 @@ public class CountryTransformer {
dto.setIsoCode(entity.getIsoCode().getCode());
dto.setRegionCode(entity.getRegionCode().getCode());
dto.setName(entity.getIsoCode().getFullName());
dto.setName(entity.getName());
dto.setId(entity.getId());
return dto;

View file

@ -8,3 +8,7 @@ spring.sql.init.mode=never
#spring.profiles.active=setup
lcc.bulk.sheet_password=secretSheet?!
lcc.allowed_cors=${ALLOWED_CORS_DOMAIN}
azure.maps.subscription.key=${AZURE_MAPS_SUBSCRIPTION_KEY}
azure.maps.client.id=your-app-registration-client-id
azure.maps.resource.id=/subscriptions/sub-id/resourceGroups/rg-name/providers/Microsoft.Maps/accounts/account-name

View file

@ -254,18 +254,6 @@ VALUES (
'0.7'
);
INSERT INTO system_property (property_set_id, system_property_type_id, property_value)
VALUES (
(SELECT ps.id FROM `property_set` ps
WHERE ps.state = 'VALID'
AND ps.start_date <= NOW()
AND (ps.end_date IS NULL OR ps.end_date > NOW())
ORDER BY ps.start_date DESC
LIMIT 1),
(SELECT spt.id FROM system_property_type spt WHERE spt.external_mapping_id = 'TRUCK_UTIL'),
'0.7'
);
INSERT INTO system_property (property_set_id, system_property_type_id, property_value)
VALUES (
(SELECT ps.id FROM `property_set` ps

View file

@ -24,266 +24,266 @@ WHERE NOT EXISTS (
INSERT INTO `country_property_type`
(`name`, `external_mapping_id`, `data_type`, `validation_rule`, `is_required`)
VALUES
('Customs Union', 'UNION', 'TEXT', NULL, FALSE),
('Safety Stock [working days]', 'SAFETY_STOCK', 'INT', 'CHECK (CAST(property_value AS UNSIGNED) >= 0)', FALSE),
('Air Freight Share [%]', 'AIR_SHARE', 'PERCENTAGE', 'CHECK (property_value REGEXP "^[0-9]+%$")', FALSE),
('Wage Factor [%]', 'WAGE', 'PERCENTAGE', 'CHECK (property_value REGEXP "^[0-9]+%$")', FALSE);
('Customs Union', 'UNION', 'ENUMERATION', '{ "ENUM" : ["EU", "NONE"]}', FALSE),
('Safety Stock [working days]', 'SAFETY_STOCK', 'INT', '{"GTE": 0}', FALSE),
('Air Freight Share [%]', 'AIR_SHARE', 'PERCENTAGE', '{"GTE": 0}', FALSE),
('Wage Factor [%]', 'WAGE', 'PERCENTAGE', '{"GT": 0}', FALSE);
-- =============================================================================
-- 2. INSERT COUNTRIES
-- =============================================================================
INSERT INTO `country` (`iso_code`, `region_code`, `is_deprecated`) VALUES
('AD', 'EMEA', FALSE),
('AE', 'EMEA', FALSE),
('AF', 'EMEA', FALSE),
('AG', 'LATAM', FALSE),
('AI', 'LATAM', FALSE),
('AL', 'EMEA', FALSE),
('AM', 'EMEA', FALSE),
('AO', 'EMEA', FALSE),
('AQ', 'EMEA', FALSE),
('AR', 'LATAM', FALSE),
('AS', 'APAC', FALSE),
('AT', 'EMEA', FALSE),
('AU', 'APAC', FALSE),
('AW', 'LATAM', FALSE),
('AX', 'EMEA', FALSE),
('AZ', 'EMEA', FALSE),
('BA', 'EMEA', FALSE),
('BB', 'LATAM', FALSE),
('BD', 'EMEA', FALSE),
('BE', 'EMEA', FALSE),
('BF', 'EMEA', FALSE),
('BG', 'EMEA', FALSE),
('BH', 'EMEA', FALSE),
('BI', 'EMEA', FALSE),
('BJ', 'EMEA', FALSE),
('BL', 'LATAM', FALSE),
('BM', 'NAM', FALSE),
('BN', 'APAC', FALSE),
('BO', 'LATAM', FALSE),
('BQ', 'LATAM', FALSE),
('BR', 'LATAM', FALSE),
('BS', 'LATAM', FALSE),
('BT', 'APAC', FALSE),
('BV', 'EMEA', FALSE),
('BW', 'EMEA', FALSE),
('BY', 'EMEA', FALSE),
('BZ', 'LATAM', FALSE),
('CA', 'NAM', FALSE),
('CC', 'APAC', FALSE),
('CD', 'EMEA', FALSE),
('CF', 'EMEA', FALSE),
('CG', 'EMEA', FALSE),
('CH', 'EMEA', FALSE),
('CI', 'EMEA', FALSE),
('CK', 'APAC', FALSE),
('CL', 'LATAM', FALSE),
('CM', 'EMEA', FALSE),
('CN', 'APAC', FALSE),
('CO', 'LATAM', FALSE),
('CR', 'LATAM', FALSE),
('CU', 'LATAM', FALSE),
('CV', 'EMEA', FALSE),
('CW', 'LATAM', FALSE),
('CX', 'APAC', FALSE),
('CY', 'EMEA', FALSE),
('CZ', 'EMEA', FALSE),
('DE', 'EMEA', FALSE),
('DJ', 'EMEA', FALSE),
('DK', 'EMEA', FALSE),
('DM', 'LATAM', FALSE),
('DO', 'LATAM', FALSE),
('DZ', 'EMEA', FALSE),
('EC', 'LATAM', FALSE),
('EE', 'EMEA', FALSE),
('EG', 'EMEA', FALSE),
('EH', 'EMEA', FALSE),
('ER', 'EMEA', FALSE),
('ES', 'EMEA', FALSE),
('ET', 'EMEA', FALSE),
('FI', 'EMEA', FALSE),
('FJ', 'APAC', FALSE),
('FK', 'LATAM', FALSE),
('FM', 'APAC', FALSE),
('FO', 'EMEA', FALSE),
('FR', 'EMEA', FALSE),
('GA', 'EMEA', FALSE),
('GB', 'EMEA', FALSE),
('GD', 'LATAM', FALSE),
('GE', 'EMEA', FALSE),
('GF', 'LATAM', FALSE),
('GG', 'EMEA', FALSE),
('GH', 'EMEA', FALSE),
('GI', 'EMEA', FALSE),
('GL', 'NAM', FALSE),
('GM', 'EMEA', FALSE),
('GN', 'EMEA', FALSE),
('GP', 'LATAM', FALSE),
('GQ', 'EMEA', FALSE),
('GR', 'EMEA', FALSE),
('GS', 'LATAM', FALSE),
('GT', 'LATAM', FALSE),
('GU', 'APAC', FALSE),
('GW', 'EMEA', FALSE),
('GY', 'LATAM', FALSE),
('HK', 'APAC', FALSE),
('HM', 'APAC', FALSE),
('HN', 'LATAM', FALSE),
('HR', 'EMEA', FALSE),
('HT', 'LATAM', FALSE),
('HU', 'EMEA', FALSE),
('ID', 'APAC', FALSE),
('IE', 'EMEA', FALSE),
('IL', 'EMEA', FALSE),
('IM', 'EMEA', FALSE),
('IN', 'APAC', FALSE),
('IO', 'APAC', FALSE),
('IQ', 'EMEA', FALSE),
('IR', 'EMEA', FALSE),
('IS', 'EMEA', FALSE),
('IT', 'EMEA', FALSE),
('JE', 'EMEA', FALSE),
('JM', 'LATAM', FALSE),
('JO', 'EMEA', FALSE),
('JP', 'APAC', FALSE),
('KE', 'EMEA', FALSE),
('KG', 'EMEA', FALSE),
('KH', 'APAC', FALSE),
('KI', 'APAC', FALSE),
('KM', 'EMEA', FALSE),
('KN', 'LATAM', FALSE),
('KP', 'APAC', FALSE),
('KR', 'APAC', FALSE),
('KW', 'EMEA', FALSE),
('KY', 'LATAM', FALSE),
('KZ', 'EMEA', FALSE),
('LA', 'APAC', FALSE),
('LB', 'EMEA', FALSE),
('LC', 'LATAM', FALSE),
('LI', 'EMEA', FALSE),
('LK', 'APAC', FALSE),
('LR', 'EMEA', FALSE),
('LS', 'EMEA', FALSE),
('LT', 'EMEA', FALSE),
('LU', 'EMEA', FALSE),
('LV', 'EMEA', FALSE),
('LY', 'EMEA', FALSE),
('MA', 'EMEA', FALSE),
('MC', 'EMEA', FALSE),
('MD', 'EMEA', FALSE),
('ME', 'EMEA', FALSE),
('MF', 'LATAM', FALSE),
('MG', 'EMEA', FALSE),
('MH', 'APAC', FALSE),
('MK', 'EMEA', FALSE),
('ML', 'EMEA', FALSE),
('MM', 'APAC', FALSE),
('MN', 'APAC', FALSE),
('MO', 'APAC', FALSE),
('MP', 'APAC', FALSE),
('MQ', 'LATAM', FALSE),
('MR', 'EMEA', FALSE),
('MS', 'LATAM', FALSE),
('MT', 'EMEA', FALSE),
('MU', 'EMEA', FALSE),
('MV', 'APAC', FALSE),
('MW', 'EMEA', FALSE),
('MX', 'LATAM', FALSE),
('MY', 'APAC', FALSE),
('MZ', 'EMEA', FALSE),
('NA', 'EMEA', FALSE),
('NC', 'APAC', FALSE),
('NE', 'EMEA', FALSE),
('NF', 'APAC', FALSE),
('NG', 'EMEA', FALSE),
('NI', 'LATAM', FALSE),
('NL', 'EMEA', FALSE),
('NO', 'EMEA', FALSE),
('NP', 'APAC', FALSE),
('NR', 'APAC', FALSE),
('NU', 'APAC', FALSE),
('NZ', 'APAC', FALSE),
('OM', 'EMEA', FALSE),
('PA', 'LATAM', FALSE),
('PE', 'LATAM', FALSE),
('PF', 'APAC', FALSE),
('PG', 'APAC', FALSE),
('PH', 'APAC', FALSE),
('PK', 'APAC', FALSE),
('PL', 'EMEA', FALSE),
('PM', 'NAM', FALSE),
('PN', 'APAC', FALSE),
('PR', 'LATAM', FALSE),
('PS', 'EMEA', FALSE),
('PT', 'EMEA', FALSE),
('PW', 'APAC', FALSE),
('PY', 'LATAM', FALSE),
('QA', 'EMEA', FALSE),
('RE', 'EMEA', FALSE),
('RO', 'EMEA', FALSE),
('RS', 'EMEA', FALSE),
('RU', 'EMEA', FALSE),
('RW', 'EMEA', FALSE),
('SA', 'EMEA', FALSE),
('SB', 'APAC', FALSE),
('SC', 'EMEA', FALSE),
('SD', 'EMEA', FALSE),
('SE', 'EMEA', FALSE),
('SG', 'APAC', FALSE),
('SH', 'EMEA', FALSE),
('SI', 'EMEA', FALSE),
('SJ', 'EMEA', FALSE),
('SK', 'EMEA', FALSE),
('SL', 'EMEA', FALSE),
('SM', 'EMEA', FALSE),
('SN', 'EMEA', FALSE),
('SO', 'EMEA', FALSE),
('SR', 'LATAM', FALSE),
('SS', 'EMEA', FALSE),
('ST', 'EMEA', FALSE),
('SV', 'LATAM', FALSE),
('SX', 'LATAM', FALSE),
('SY', 'EMEA', FALSE),
('SZ', 'EMEA', FALSE),
('TC', 'LATAM', FALSE),
('TD', 'EMEA', FALSE),
('TF', 'EMEA', FALSE),
('TG', 'EMEA', FALSE),
('TH', 'APAC', FALSE),
('TJ', 'EMEA', FALSE),
('TK', 'APAC', FALSE),
('TL', 'APAC', FALSE),
('TM', 'EMEA', FALSE),
('TN', 'EMEA', FALSE),
('TO', 'APAC', FALSE),
('TR', 'EMEA', FALSE),
('TT', 'LATAM', FALSE),
('TV', 'APAC', FALSE),
('TW', 'APAC', FALSE),
('TZ', 'EMEA', FALSE),
('UA', 'EMEA', FALSE),
('UG', 'EMEA', FALSE),
('UM', 'APAC', FALSE),
('US', 'NAM', FALSE),
('UY', 'LATAM', FALSE),
('UZ', 'EMEA', FALSE),
('VA', 'EMEA', FALSE),
('VC', 'LATAM', FALSE),
('VE', 'LATAM', FALSE),
('VG', 'LATAM', FALSE),
('VI', 'LATAM', FALSE),
('VN', 'APAC', FALSE),
('VU', 'APAC', FALSE),
('WF', 'APAC', FALSE),
('WS', 'APAC', FALSE),
('YE', 'EMEA', FALSE),
('YT', 'EMEA', FALSE),
('ZA', 'EMEA', FALSE),
('ZM', 'EMEA', FALSE),
('ZW', 'EMEA', FALSE),
('XK', 'EMEA', FALSE);
INSERT INTO `country` (`iso_code`, `name`, `region_code`, `is_deprecated`) VALUES
('AD', 'Andorra', 'EMEA', FALSE),
('AE', 'United Arab Emirates', 'EMEA', FALSE),
('AF', 'Afghanistan', 'EMEA', FALSE),
('AG', 'Antigua and Barbuda', 'LATAM', FALSE),
('AI', 'Anguilla', 'LATAM', FALSE),
('AL', 'Albania', 'EMEA', FALSE),
('AM', 'Armenia', 'EMEA', FALSE),
('AO', 'Angola', 'EMEA', FALSE),
('AQ', 'Antarctica', 'EMEA', FALSE),
('AR', 'Argentina', 'LATAM', FALSE),
('AS', 'American Samoa', 'APAC', FALSE),
('AT', 'Austria', 'EMEA', FALSE),
('AU', 'Australia', 'APAC', FALSE),
('AW', 'Aruba', 'LATAM', FALSE),
('AX', 'Åland Islands', 'EMEA', FALSE),
('AZ', 'Azerbaijan', 'EMEA', FALSE),
('BA', 'Bosnia and Herzegovina', 'EMEA', FALSE),
('BB', 'Barbados', 'LATAM', FALSE),
('BD', 'Bangladesh', 'EMEA', FALSE),
('BE', 'Belgium', 'EMEA', FALSE),
('BF', 'Burkina Faso', 'EMEA', FALSE),
('BG', 'Bulgaria', 'EMEA', FALSE),
('BH', 'Bahrain', 'EMEA', FALSE),
('BI', 'Burundi', 'EMEA', FALSE),
('BJ', 'Benin', 'EMEA', FALSE),
('BL', 'Saint Barthélemy', 'LATAM', FALSE),
('BM', 'Bermuda', 'NAM', FALSE),
('BN', 'Brunei Darussalam', 'APAC', FALSE),
('BO', 'Bolivia', 'LATAM', FALSE),
('BQ', 'Bonaire, Sint Eustatius and Saba', 'LATAM', FALSE),
('BR', 'Brazil', 'LATAM', FALSE),
('BS', 'Bahamas', 'LATAM', FALSE),
('BT', 'Bhutan', 'APAC', FALSE),
('BV', 'Bouvet Island', 'EMEA', FALSE),
('BW', 'Botswana', 'EMEA', FALSE),
('BY', 'Belarus', 'EMEA', FALSE),
('BZ', 'Belize', 'LATAM', FALSE),
('CA', 'Canada', 'NAM', FALSE),
('CC', 'Cocos (Keeling) Islands', 'APAC', FALSE),
('CD', 'Congo, Democratic Republic', 'EMEA', FALSE),
('CF', 'Central African Republic', 'EMEA', FALSE),
('CG', 'Congo', 'EMEA', FALSE),
('CH', 'Switzerland', 'EMEA', FALSE),
('CI', 'Côte d''Ivoire', 'EMEA', FALSE),
('CK', 'Cook Islands', 'APAC', FALSE),
('CL', 'Chile', 'LATAM', FALSE),
('CM', 'Cameroon', 'EMEA', FALSE),
('CN', 'China', 'APAC', FALSE),
('CO', 'Colombia', 'LATAM', FALSE),
('CR', 'Costa Rica', 'LATAM', FALSE),
('CU', 'Cuba', 'LATAM', FALSE),
('CV', 'Cabo Verde', 'EMEA', FALSE),
('CW', 'Curaçao', 'LATAM', FALSE),
('CX', 'Christmas Island', 'APAC', FALSE),
('CY', 'Cyprus', 'EMEA', FALSE),
('CZ', 'Czech Republic', 'EMEA', FALSE),
('DE', 'Germany', 'EMEA', FALSE),
('DJ', 'Djibouti', 'EMEA', FALSE),
('DK', 'Denmark', 'EMEA', FALSE),
('DM', 'Dominica', 'LATAM', FALSE),
('DO', 'Dominican Republic', 'LATAM', FALSE),
('DZ', 'Algeria', 'EMEA', FALSE),
('EC', 'Ecuador', 'LATAM', FALSE),
('EE', 'Estonia', 'EMEA', FALSE),
('EG', 'Egypt', 'EMEA', FALSE),
('EH', 'Western Sahara', 'EMEA', FALSE),
('ER', 'Eritrea', 'EMEA', FALSE),
('ES', 'Spain', 'EMEA', FALSE),
('ET', 'Ethiopia', 'EMEA', FALSE),
('FI', 'Finland', 'EMEA', FALSE),
('FJ', 'Fiji', 'APAC', FALSE),
('FK', 'Falkland Islands', 'LATAM', FALSE),
('FM', 'Micronesia', 'APAC', FALSE),
('FO', 'Faroe Islands', 'EMEA', FALSE),
('FR', 'France', 'EMEA', FALSE),
('GA', 'Gabon', 'EMEA', FALSE),
('GB', 'United Kingdom', 'EMEA', FALSE),
('GD', 'Grenada', 'LATAM', FALSE),
('GE', 'Georgia', 'EMEA', FALSE),
('GF', 'French Guiana', 'LATAM', FALSE),
('GG', 'Guernsey', 'EMEA', FALSE),
('GH', 'Ghana', 'EMEA', FALSE),
('GI', 'Gibraltar', 'EMEA', FALSE),
('GL', 'Greenland', 'NAM', FALSE),
('GM', 'Gambia', 'EMEA', FALSE),
('GN', 'Guinea', 'EMEA', FALSE),
('GP', 'Guadeloupe', 'LATAM', FALSE),
('GQ', 'Equatorial Guinea', 'EMEA', FALSE),
('GR', 'Greece', 'EMEA', FALSE),
('GS', 'South Georgia and South Sandwich Islands', 'LATAM', FALSE),
('GT', 'Guatemala', 'LATAM', FALSE),
('GU', 'Guam', 'APAC', FALSE),
('GW', 'Guinea-Bissau', 'EMEA', FALSE),
('GY', 'Guyana', 'LATAM', FALSE),
('HK', 'Hong Kong', 'APAC', FALSE),
('HM', 'Heard Island and McDonald Islands', 'APAC', FALSE),
('HN', 'Honduras', 'LATAM', FALSE),
('HR', 'Croatia', 'EMEA', FALSE),
('HT', 'Haiti', 'LATAM', FALSE),
('HU', 'Hungary', 'EMEA', FALSE),
('ID', 'Indonesia', 'APAC', FALSE),
('IE', 'Ireland', 'EMEA', FALSE),
('IL', 'Israel', 'EMEA', FALSE),
('IM', 'Isle of Man', 'EMEA', FALSE),
('IN', 'India', 'APAC', FALSE),
('IO', 'British Indian Ocean Territory', 'APAC', FALSE),
('IQ', 'Iraq', 'EMEA', FALSE),
('IR', 'Iran', 'EMEA', FALSE),
('IS', 'Iceland', 'EMEA', FALSE),
('IT', 'Italy', 'EMEA', FALSE),
('JE', 'Jersey', 'EMEA', FALSE),
('JM', 'Jamaica', 'LATAM', FALSE),
('JO', 'Jordan', 'EMEA', FALSE),
('JP', 'Japan', 'APAC', FALSE),
('KE', 'Kenya', 'EMEA', FALSE),
('KG', 'Kyrgyzstan', 'EMEA', FALSE),
('KH', 'Cambodia', 'APAC', FALSE),
('KI', 'Kiribati', 'APAC', FALSE),
('KM', 'Comoros', 'EMEA', FALSE),
('KN', 'Saint Kitts and Nevis', 'LATAM', FALSE),
('KP', 'Korea, North', 'APAC', FALSE),
('KR', 'Korea, South', 'APAC', FALSE),
('KW', 'Kuwait', 'EMEA', FALSE),
('KY', 'Cayman Islands', 'LATAM', FALSE),
('KZ', 'Kazakhstan', 'EMEA', FALSE),
('LA', 'Laos', 'APAC', FALSE),
('LB', 'Lebanon', 'EMEA', FALSE),
('LC', 'Saint Lucia', 'LATAM', FALSE),
('LI', 'Liechtenstein', 'EMEA', FALSE),
('LK', 'Sri Lanka', 'APAC', FALSE),
('LR', 'Liberia', 'EMEA', FALSE),
('LS', 'Lesotho', 'EMEA', FALSE),
('LT', 'Lithuania', 'EMEA', FALSE),
('LU', 'Luxembourg', 'EMEA', FALSE),
('LV', 'Latvia', 'EMEA', FALSE),
('LY', 'Libya', 'EMEA', FALSE),
('MA', 'Morocco', 'EMEA', FALSE),
('MC', 'Monaco', 'EMEA', FALSE),
('MD', 'Moldova', 'EMEA', FALSE),
('ME', 'Montenegro', 'EMEA', FALSE),
('MF', 'Saint Martin', 'LATAM', FALSE),
('MG', 'Madagascar', 'EMEA', FALSE),
('MH', 'Marshall Islands', 'APAC', FALSE),
('MK', 'North Macedonia', 'EMEA', FALSE),
('ML', 'Mali', 'EMEA', FALSE),
('MM', 'Myanmar', 'APAC', FALSE),
('MN', 'Mongolia', 'APAC', FALSE),
('MO', 'Macao', 'APAC', FALSE),
('MP', 'Northern Mariana Islands', 'APAC', FALSE),
('MQ', 'Martinique', 'LATAM', FALSE),
('MR', 'Mauritania', 'EMEA', FALSE),
('MS', 'Montserrat', 'LATAM', FALSE),
('MT', 'Malta', 'EMEA', FALSE),
('MU', 'Mauritius', 'EMEA', FALSE),
('MV', 'Maldives', 'APAC', FALSE),
('MW', 'Malawi', 'EMEA', FALSE),
('MX', 'Mexico', 'LATAM', FALSE),
('MY', 'Malaysia', 'APAC', FALSE),
('MZ', 'Mozambique', 'EMEA', FALSE),
('NA', 'Namibia', 'EMEA', FALSE),
('NC', 'New Caledonia', 'APAC', FALSE),
('NE', 'Niger', 'EMEA', FALSE),
('NF', 'Norfolk Island', 'APAC', FALSE),
('NG', 'Nigeria', 'EMEA', FALSE),
('NI', 'Nicaragua', 'LATAM', FALSE),
('NL', 'Netherlands', 'EMEA', FALSE),
('NO', 'Norway', 'EMEA', FALSE),
('NP', 'Nepal', 'APAC', FALSE),
('NR', 'Nauru', 'APAC', FALSE),
('NU', 'Niue', 'APAC', FALSE),
('NZ', 'New Zealand', 'APAC', FALSE),
('OM', 'Oman', 'EMEA', FALSE),
('PA', 'Panama', 'LATAM', FALSE),
('PE', 'Peru', 'LATAM', FALSE),
('PF', 'French Polynesia', 'APAC', FALSE),
('PG', 'Papua New Guinea', 'APAC', FALSE),
('PH', 'Philippines', 'APAC', FALSE),
('PK', 'Pakistan', 'APAC', FALSE),
('PL', 'Poland', 'EMEA', FALSE),
('PM', 'Saint Pierre and Miquelon', 'NAM', FALSE),
('PN', 'Pitcairn', 'APAC', FALSE),
('PR', 'Puerto Rico', 'LATAM', FALSE),
('PS', 'Palestine', 'EMEA', FALSE),
('PT', 'Portugal', 'EMEA', FALSE),
('PW', 'Palau', 'APAC', FALSE),
('PY', 'Paraguay', 'LATAM', FALSE),
('QA', 'Qatar', 'EMEA', FALSE),
('RE', 'Réunion', 'EMEA', FALSE),
('RO', 'Romania', 'EMEA', FALSE),
('RS', 'Serbia', 'EMEA', FALSE),
('RU', 'Russian Federation', 'EMEA', FALSE),
('RW', 'Rwanda', 'EMEA', FALSE),
('SA', 'Saudi Arabia', 'EMEA', FALSE),
('SB', 'Solomon Islands', 'APAC', FALSE),
('SC', 'Seychelles', 'EMEA', FALSE),
('SD', 'Sudan', 'EMEA', FALSE),
('SE', 'Sweden', 'EMEA', FALSE),
('SG', 'Singapore', 'APAC', FALSE),
('SH', 'Saint Helena', 'EMEA', FALSE),
('SI', 'Slovenia', 'EMEA', FALSE),
('SJ', 'Svalbard and Jan Mayen', 'EMEA', FALSE),
('SK', 'Slovakia', 'EMEA', FALSE),
('SL', 'Sierra Leone', 'EMEA', FALSE),
('SM', 'San Marino', 'EMEA', FALSE),
('SN', 'Senegal', 'EMEA', FALSE),
('SO', 'Somalia', 'EMEA', FALSE),
('SR', 'Suriname', 'LATAM', FALSE),
('SS', 'South Sudan', 'EMEA', FALSE),
('ST', 'Sao Tome and Principe', 'EMEA', FALSE),
('SV', 'El Salvador', 'LATAM', FALSE),
('SX', 'Sint Maarten', 'LATAM', FALSE),
('SY', 'Syrian Arab Republic', 'EMEA', FALSE),
('SZ', 'Eswatini', 'EMEA', FALSE),
('TC', 'Turks and Caicos Islands', 'LATAM', FALSE),
('TD', 'Chad', 'EMEA', FALSE),
('TF', 'French Southern Territories', 'EMEA', FALSE),
('TG', 'Togo', 'EMEA', FALSE),
('TH', 'Thailand', 'APAC', FALSE),
('TJ', 'Tajikistan', 'EMEA', FALSE),
('TK', 'Tokelau', 'APAC', FALSE),
('TL', 'Timor-Leste', 'APAC', FALSE),
('TM', 'Turkmenistan', 'EMEA', FALSE),
('TN', 'Tunisia', 'EMEA', FALSE),
('TO', 'Tonga', 'APAC', FALSE),
('TR', 'Turkey', 'EMEA', FALSE),
('TT', 'Trinidad and Tobago', 'LATAM', FALSE),
('TV', 'Tuvalu', 'APAC', FALSE),
('TW', 'Taiwan', 'APAC', FALSE),
('TZ', 'Tanzania', 'EMEA', FALSE),
('UA', 'Ukraine', 'EMEA', FALSE),
('UG', 'Uganda', 'EMEA', FALSE),
('UM', 'United States Minor Outlying Islands', 'APAC', FALSE),
('US', 'United States', 'NAM', FALSE),
('UY', 'Uruguay', 'LATAM', FALSE),
('UZ', 'Uzbekistan', 'EMEA', FALSE),
('VA', 'Vatican City', 'EMEA', FALSE),
('VC', 'Saint Vincent and the Grenadines', 'LATAM', FALSE),
('VE', 'Venezuela', 'LATAM', FALSE),
('VG', 'Virgin Islands, British', 'LATAM', FALSE),
('VI', 'Virgin Islands, U.S.', 'LATAM', FALSE),
('VN', 'Viet Nam', 'APAC', FALSE),
('VU', 'Vanuatu', 'APAC', FALSE),
('WF', 'Wallis and Futuna', 'APAC', FALSE),
('WS', 'Samoa', 'APAC', FALSE),
('YE', 'Yemen', 'EMEA', FALSE),
('YT', 'Mayotte', 'EMEA', FALSE),
('ZA', 'South Africa', 'EMEA', FALSE),
('ZM', 'Zambia', 'EMEA', FALSE),
('ZW', 'Zimbabwe', 'EMEA', FALSE),
('XK', 'Kosovo', 'EMEA', FALSE);
-- =============================================================================
-- 3. INSERT COUNTRY PROPERTIES
@ -307,10 +307,13 @@ SELECT
AND (ps.end_date IS NULL OR ps.end_date > NOW())
ORDER BY ps.start_date DESC
LIMIT 1),
'EU'
CASE
WHEN c.iso_code IN ('AT', 'BE', 'BG', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK')
THEN 'EU'
ELSE 'NONE'
END
FROM `country` c, `country_property_type` cpt
WHERE c.iso_code IN ('AT', 'BE', 'BG', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK')
AND cpt.external_mapping_id = 'UNION';
WHERE cpt.external_mapping_id = 'UNION';
-- Safety Stock Properties
INSERT INTO `country_property`
@ -635,10 +638,10 @@ SELECT
WHEN 'SE' THEN '0.94'
WHEN 'SI' THEN '0.62'
WHEN 'SK' THEN '0.42'
ELSE '1'
END
FROM `country` c, `country_property_type` cpt
WHERE c.iso_code IN ('AT', 'BE', 'BG', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK')
AND cpt.external_mapping_id = 'WAGE';
WHERE cpt.external_mapping_id = 'WAGE';
-- =============================================================================
-- VERIFICATION QUERIES (Optional - for testing)

View file

@ -48,6 +48,7 @@ CREATE TABLE IF NOT EXISTS `country`
`id` INT NOT NULL AUTO_INCREMENT,
`iso_code` CHAR(2) NOT NULL COMMENT 'ISO 3166-1 alpha-2 country code',
`region_code` CHAR(5) NOT NULL COMMENT 'Geographic region code (EMEA/LATAM/APAC/NAM)',
`name` VARCHAR(255) NOT NULL,
`is_deprecated` BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_country_iso_code` (`iso_code`),

View file

@ -644,257 +644,257 @@ VALUES
-- 2. INSERT COUNTRIES
-- =============================================================================
INSERT INTO `country` (`iso_code`, `region_code`, `is_deprecated`) VALUES
('AD', 'EMEA', FALSE),
('AE', 'EMEA', FALSE),
('AF', 'EMEA', FALSE),
('AG', 'LATAM', FALSE),
('AI', 'LATAM', FALSE),
('AL', 'EMEA', FALSE),
('AM', 'EMEA', FALSE),
('AO', 'EMEA', FALSE),
('AQ', 'EMEA', FALSE),
('AR', 'LATAM', FALSE),
('AS', 'APAC', FALSE),
('AT', 'EMEA', FALSE),
('AU', 'APAC', FALSE),
('AW', 'LATAM', FALSE),
('AX', 'EMEA', FALSE),
('AZ', 'EMEA', FALSE),
('BA', 'EMEA', FALSE),
('BB', 'LATAM', FALSE),
('BD', 'EMEA', FALSE),
('BE', 'EMEA', FALSE),
('BF', 'EMEA', FALSE),
('BG', 'EMEA', FALSE),
('BH', 'EMEA', FALSE),
('BI', 'EMEA', FALSE),
('BJ', 'EMEA', FALSE),
('BL', 'LATAM', FALSE),
('BM', 'NAM', FALSE),
('BN', 'APAC', FALSE),
('BO', 'LATAM', FALSE),
('BQ', 'LATAM', FALSE),
('BR', 'LATAM', FALSE),
('BS', 'LATAM', FALSE),
('BT', 'APAC', FALSE),
('BV', 'EMEA', FALSE),
('BW', 'EMEA', FALSE),
('BY', 'EMEA', FALSE),
('BZ', 'LATAM', FALSE),
('CA', 'NAM', FALSE),
('CC', 'APAC', FALSE),
('CD', 'EMEA', FALSE),
('CF', 'EMEA', FALSE),
('CG', 'EMEA', FALSE),
('CH', 'EMEA', FALSE),
('CI', 'EMEA', FALSE),
('CK', 'APAC', FALSE),
('CL', 'LATAM', FALSE),
('CM', 'EMEA', FALSE),
('CN', 'APAC', FALSE),
('CO', 'LATAM', FALSE),
('CR', 'LATAM', FALSE),
('CU', 'LATAM', FALSE),
('CV', 'EMEA', FALSE),
('CW', 'LATAM', FALSE),
('CX', 'APAC', FALSE),
('CY', 'EMEA', FALSE),
('CZ', 'EMEA', FALSE),
('DE', 'EMEA', FALSE),
('DJ', 'EMEA', FALSE),
('DK', 'EMEA', FALSE),
('DM', 'LATAM', FALSE),
('DO', 'LATAM', FALSE),
('DZ', 'EMEA', FALSE),
('EC', 'LATAM', FALSE),
('EE', 'EMEA', FALSE),
('EG', 'EMEA', FALSE),
('EH', 'EMEA', FALSE),
('ER', 'EMEA', FALSE),
('ES', 'EMEA', FALSE),
('ET', 'EMEA', FALSE),
('FI', 'EMEA', FALSE),
('FJ', 'APAC', FALSE),
('FK', 'LATAM', FALSE),
('FM', 'APAC', FALSE),
('FO', 'EMEA', FALSE),
('FR', 'EMEA', FALSE),
('GA', 'EMEA', FALSE),
('GB', 'EMEA', FALSE),
('GD', 'LATAM', FALSE),
('GE', 'EMEA', FALSE),
('GF', 'LATAM', FALSE),
('GG', 'EMEA', FALSE),
('GH', 'EMEA', FALSE),
('GI', 'EMEA', FALSE),
('GL', 'NAM', FALSE),
('GM', 'EMEA', FALSE),
('GN', 'EMEA', FALSE),
('GP', 'LATAM', FALSE),
('GQ', 'EMEA', FALSE),
('GR', 'EMEA', FALSE),
('GS', 'LATAM', FALSE),
('GT', 'LATAM', FALSE),
('GU', 'APAC', FALSE),
('GW', 'EMEA', FALSE),
('GY', 'LATAM', FALSE),
('HK', 'APAC', FALSE),
('HM', 'APAC', FALSE),
('HN', 'LATAM', FALSE),
('HR', 'EMEA', FALSE),
('HT', 'LATAM', FALSE),
('HU', 'EMEA', FALSE),
('ID', 'APAC', FALSE),
('IE', 'EMEA', FALSE),
('IL', 'EMEA', FALSE),
('IM', 'EMEA', FALSE),
('IN', 'APAC', FALSE),
('IO', 'APAC', FALSE),
('IQ', 'EMEA', FALSE),
('IR', 'EMEA', FALSE),
('IS', 'EMEA', FALSE),
('IT', 'EMEA', FALSE),
('JE', 'EMEA', FALSE),
('JM', 'LATAM', FALSE),
('JO', 'EMEA', FALSE),
('JP', 'APAC', FALSE),
('KE', 'EMEA', FALSE),
('KG', 'EMEA', FALSE),
('KH', 'APAC', FALSE),
('KI', 'APAC', FALSE),
('KM', 'EMEA', FALSE),
('KN', 'LATAM', FALSE),
('KP', 'APAC', FALSE),
('KR', 'APAC', FALSE),
('KW', 'EMEA', FALSE),
('KY', 'LATAM', FALSE),
('KZ', 'EMEA', FALSE),
('LA', 'APAC', FALSE),
('LB', 'EMEA', FALSE),
('LC', 'LATAM', FALSE),
('LI', 'EMEA', FALSE),
('LK', 'APAC', FALSE),
('LR', 'EMEA', FALSE),
('LS', 'EMEA', FALSE),
('LT', 'EMEA', FALSE),
('LU', 'EMEA', FALSE),
('LV', 'EMEA', FALSE),
('LY', 'EMEA', FALSE),
('MA', 'EMEA', FALSE),
('MC', 'EMEA', FALSE),
('MD', 'EMEA', FALSE),
('ME', 'EMEA', FALSE),
('MF', 'LATAM', FALSE),
('MG', 'EMEA', FALSE),
('MH', 'APAC', FALSE),
('MK', 'EMEA', FALSE),
('ML', 'EMEA', FALSE),
('MM', 'APAC', FALSE),
('MN', 'APAC', FALSE),
('MO', 'APAC', FALSE),
('MP', 'APAC', FALSE),
('MQ', 'LATAM', FALSE),
('MR', 'EMEA', FALSE),
('MS', 'LATAM', FALSE),
('MT', 'EMEA', FALSE),
('MU', 'EMEA', FALSE),
('MV', 'APAC', FALSE),
('MW', 'EMEA', FALSE),
('MX', 'LATAM', FALSE),
('MY', 'APAC', FALSE),
('MZ', 'EMEA', FALSE),
('NA', 'EMEA', FALSE),
('NC', 'APAC', FALSE),
('NE', 'EMEA', FALSE),
('NF', 'APAC', FALSE),
('NG', 'EMEA', FALSE),
('NI', 'LATAM', FALSE),
('NL', 'EMEA', FALSE),
('NO', 'EMEA', FALSE),
('NP', 'APAC', FALSE),
('NR', 'APAC', FALSE),
('NU', 'APAC', FALSE),
('NZ', 'APAC', FALSE),
('OM', 'EMEA', FALSE),
('PA', 'LATAM', FALSE),
('PE', 'LATAM', FALSE),
('PF', 'APAC', FALSE),
('PG', 'APAC', FALSE),
('PH', 'APAC', FALSE),
('PK', 'APAC', FALSE),
('PL', 'EMEA', FALSE),
('PM', 'NAM', FALSE),
('PN', 'APAC', FALSE),
('PR', 'LATAM', FALSE),
('PS', 'EMEA', FALSE),
('PT', 'EMEA', FALSE),
('PW', 'APAC', FALSE),
('PY', 'LATAM', FALSE),
('QA', 'EMEA', FALSE),
('RE', 'EMEA', FALSE),
('RO', 'EMEA', FALSE),
('RS', 'EMEA', FALSE),
('RU', 'EMEA', FALSE),
('RW', 'EMEA', FALSE),
('SA', 'EMEA', FALSE),
('SB', 'APAC', FALSE),
('SC', 'EMEA', FALSE),
('SD', 'EMEA', FALSE),
('SE', 'EMEA', FALSE),
('SG', 'APAC', FALSE),
('SH', 'EMEA', FALSE),
('SI', 'EMEA', FALSE),
('SJ', 'EMEA', FALSE),
('SK', 'EMEA', FALSE),
('SL', 'EMEA', FALSE),
('SM', 'EMEA', FALSE),
('SN', 'EMEA', FALSE),
('SO', 'EMEA', FALSE),
('SR', 'LATAM', FALSE),
('SS', 'EMEA', FALSE),
('ST', 'EMEA', FALSE),
('SV', 'LATAM', FALSE),
('SX', 'LATAM', FALSE),
('SY', 'EMEA', FALSE),
('SZ', 'EMEA', FALSE),
('TC', 'LATAM', FALSE),
('TD', 'EMEA', FALSE),
('TF', 'EMEA', FALSE),
('TG', 'EMEA', FALSE),
('TH', 'APAC', FALSE),
('TJ', 'EMEA', FALSE),
('TK', 'APAC', FALSE),
('TL', 'APAC', FALSE),
('TM', 'EMEA', FALSE),
('TN', 'EMEA', FALSE),
('TO', 'APAC', FALSE),
('TR', 'EMEA', FALSE),
('TT', 'LATAM', FALSE),
('TV', 'APAC', FALSE),
('TW', 'APAC', FALSE),
('TZ', 'EMEA', FALSE),
('UA', 'EMEA', FALSE),
('UG', 'EMEA', FALSE),
('UM', 'APAC', FALSE),
('US', 'NAM', FALSE),
('UY', 'LATAM', FALSE),
('UZ', 'EMEA', FALSE),
('VA', 'EMEA', FALSE),
('VC', 'LATAM', FALSE),
('VE', 'LATAM', FALSE),
('VG', 'LATAM', FALSE),
('VI', 'LATAM', FALSE),
('VN', 'APAC', FALSE),
('VU', 'APAC', FALSE),
('WF', 'APAC', FALSE),
('WS', 'APAC', FALSE),
('YE', 'EMEA', FALSE),
('YT', 'EMEA', FALSE),
('ZA', 'EMEA', FALSE),
('ZM', 'EMEA', FALSE),
('ZW', 'EMEA', FALSE),
('XK', 'EMEA', FALSE);
INSERT INTO `country` (`iso_code`, `name`, `region_code`, `is_deprecated`) VALUES
('AD', 'Andorra', 'EMEA', FALSE),
('AE', 'United Arab Emirates', 'EMEA', FALSE),
('AF', 'Afghanistan', 'EMEA', FALSE),
('AG', 'Antigua and Barbuda', 'LATAM', FALSE),
('AI', 'Anguilla', 'LATAM', FALSE),
('AL', 'Albania', 'EMEA', FALSE),
('AM', 'Armenia', 'EMEA', FALSE),
('AO', 'Angola', 'EMEA', FALSE),
('AQ', 'Antarctica', 'EMEA', FALSE),
('AR', 'Argentina', 'LATAM', FALSE),
('AS', 'American Samoa', 'APAC', FALSE),
('AT', 'Austria', 'EMEA', FALSE),
('AU', 'Australia', 'APAC', FALSE),
('AW', 'Aruba', 'LATAM', FALSE),
('AX', 'Åland Islands', 'EMEA', FALSE),
('AZ', 'Azerbaijan', 'EMEA', FALSE),
('BA', 'Bosnia and Herzegovina', 'EMEA', FALSE),
('BB', 'Barbados', 'LATAM', FALSE),
('BD', 'Bangladesh', 'EMEA', FALSE),
('BE', 'Belgium', 'EMEA', FALSE),
('BF', 'Burkina Faso', 'EMEA', FALSE),
('BG', 'Bulgaria', 'EMEA', FALSE),
('BH', 'Bahrain', 'EMEA', FALSE),
('BI', 'Burundi', 'EMEA', FALSE),
('BJ', 'Benin', 'EMEA', FALSE),
('BL', 'Saint Barthélemy', 'LATAM', FALSE),
('BM', 'Bermuda', 'NAM', FALSE),
('BN', 'Brunei Darussalam', 'APAC', FALSE),
('BO', 'Bolivia', 'LATAM', FALSE),
('BQ', 'Bonaire, Sint Eustatius and Saba', 'LATAM', FALSE),
('BR', 'Brazil', 'LATAM', FALSE),
('BS', 'Bahamas', 'LATAM', FALSE),
('BT', 'Bhutan', 'APAC', FALSE),
('BV', 'Bouvet Island', 'EMEA', FALSE),
('BW', 'Botswana', 'EMEA', FALSE),
('BY', 'Belarus', 'EMEA', FALSE),
('BZ', 'Belize', 'LATAM', FALSE),
('CA', 'Canada', 'NAM', FALSE),
('CC', 'Cocos (Keeling) Islands', 'APAC', FALSE),
('CD', 'Congo, Democratic Republic', 'EMEA', FALSE),
('CF', 'Central African Republic', 'EMEA', FALSE),
('CG', 'Congo', 'EMEA', FALSE),
('CH', 'Switzerland', 'EMEA', FALSE),
('CI', 'Côte d''Ivoire', 'EMEA', FALSE),
('CK', 'Cook Islands', 'APAC', FALSE),
('CL', 'Chile', 'LATAM', FALSE),
('CM', 'Cameroon', 'EMEA', FALSE),
('CN', 'China', 'APAC', FALSE),
('CO', 'Colombia', 'LATAM', FALSE),
('CR', 'Costa Rica', 'LATAM', FALSE),
('CU', 'Cuba', 'LATAM', FALSE),
('CV', 'Cabo Verde', 'EMEA', FALSE),
('CW', 'Curaçao', 'LATAM', FALSE),
('CX', 'Christmas Island', 'APAC', FALSE),
('CY', 'Cyprus', 'EMEA', FALSE),
('CZ', 'Czech Republic', 'EMEA', FALSE),
('DE', 'Germany', 'EMEA', FALSE),
('DJ', 'Djibouti', 'EMEA', FALSE),
('DK', 'Denmark', 'EMEA', FALSE),
('DM', 'Dominica', 'LATAM', FALSE),
('DO', 'Dominican Republic', 'LATAM', FALSE),
('DZ', 'Algeria', 'EMEA', FALSE),
('EC', 'Ecuador', 'LATAM', FALSE),
('EE', 'Estonia', 'EMEA', FALSE),
('EG', 'Egypt', 'EMEA', FALSE),
('EH', 'Western Sahara', 'EMEA', FALSE),
('ER', 'Eritrea', 'EMEA', FALSE),
('ES', 'Spain', 'EMEA', FALSE),
('ET', 'Ethiopia', 'EMEA', FALSE),
('FI', 'Finland', 'EMEA', FALSE),
('FJ', 'Fiji', 'APAC', FALSE),
('FK', 'Falkland Islands', 'LATAM', FALSE),
('FM', 'Micronesia', 'APAC', FALSE),
('FO', 'Faroe Islands', 'EMEA', FALSE),
('FR', 'France', 'EMEA', FALSE),
('GA', 'Gabon', 'EMEA', FALSE),
('GB', 'United Kingdom', 'EMEA', FALSE),
('GD', 'Grenada', 'LATAM', FALSE),
('GE', 'Georgia', 'EMEA', FALSE),
('GF', 'French Guiana', 'LATAM', FALSE),
('GG', 'Guernsey', 'EMEA', FALSE),
('GH', 'Ghana', 'EMEA', FALSE),
('GI', 'Gibraltar', 'EMEA', FALSE),
('GL', 'Greenland', 'NAM', FALSE),
('GM', 'Gambia', 'EMEA', FALSE),
('GN', 'Guinea', 'EMEA', FALSE),
('GP', 'Guadeloupe', 'LATAM', FALSE),
('GQ', 'Equatorial Guinea', 'EMEA', FALSE),
('GR', 'Greece', 'EMEA', FALSE),
('GS', 'South Georgia and South Sandwich Islands', 'LATAM', FALSE),
('GT', 'Guatemala', 'LATAM', FALSE),
('GU', 'Guam', 'APAC', FALSE),
('GW', 'Guinea-Bissau', 'EMEA', FALSE),
('GY', 'Guyana', 'LATAM', FALSE),
('HK', 'Hong Kong', 'APAC', FALSE),
('HM', 'Heard Island and McDonald Islands', 'APAC', FALSE),
('HN', 'Honduras', 'LATAM', FALSE),
('HR', 'Croatia', 'EMEA', FALSE),
('HT', 'Haiti', 'LATAM', FALSE),
('HU', 'Hungary', 'EMEA', FALSE),
('ID', 'Indonesia', 'APAC', FALSE),
('IE', 'Ireland', 'EMEA', FALSE),
('IL', 'Israel', 'EMEA', FALSE),
('IM', 'Isle of Man', 'EMEA', FALSE),
('IN', 'India', 'APAC', FALSE),
('IO', 'British Indian Ocean Territory', 'APAC', FALSE),
('IQ', 'Iraq', 'EMEA', FALSE),
('IR', 'Iran', 'EMEA', FALSE),
('IS', 'Iceland', 'EMEA', FALSE),
('IT', 'Italy', 'EMEA', FALSE),
('JE', 'Jersey', 'EMEA', FALSE),
('JM', 'Jamaica', 'LATAM', FALSE),
('JO', 'Jordan', 'EMEA', FALSE),
('JP', 'Japan', 'APAC', FALSE),
('KE', 'Kenya', 'EMEA', FALSE),
('KG', 'Kyrgyzstan', 'EMEA', FALSE),
('KH', 'Cambodia', 'APAC', FALSE),
('KI', 'Kiribati', 'APAC', FALSE),
('KM', 'Comoros', 'EMEA', FALSE),
('KN', 'Saint Kitts and Nevis', 'LATAM', FALSE),
('KP', 'Korea, North', 'APAC', FALSE),
('KR', 'Korea, South', 'APAC', FALSE),
('KW', 'Kuwait', 'EMEA', FALSE),
('KY', 'Cayman Islands', 'LATAM', FALSE),
('KZ', 'Kazakhstan', 'EMEA', FALSE),
('LA', 'Laos', 'APAC', FALSE),
('LB', 'Lebanon', 'EMEA', FALSE),
('LC', 'Saint Lucia', 'LATAM', FALSE),
('LI', 'Liechtenstein', 'EMEA', FALSE),
('LK', 'Sri Lanka', 'APAC', FALSE),
('LR', 'Liberia', 'EMEA', FALSE),
('LS', 'Lesotho', 'EMEA', FALSE),
('LT', 'Lithuania', 'EMEA', FALSE),
('LU', 'Luxembourg', 'EMEA', FALSE),
('LV', 'Latvia', 'EMEA', FALSE),
('LY', 'Libya', 'EMEA', FALSE),
('MA', 'Morocco', 'EMEA', FALSE),
('MC', 'Monaco', 'EMEA', FALSE),
('MD', 'Moldova', 'EMEA', FALSE),
('ME', 'Montenegro', 'EMEA', FALSE),
('MF', 'Saint Martin', 'LATAM', FALSE),
('MG', 'Madagascar', 'EMEA', FALSE),
('MH', 'Marshall Islands', 'APAC', FALSE),
('MK', 'North Macedonia', 'EMEA', FALSE),
('ML', 'Mali', 'EMEA', FALSE),
('MM', 'Myanmar', 'APAC', FALSE),
('MN', 'Mongolia', 'APAC', FALSE),
('MO', 'Macao', 'APAC', FALSE),
('MP', 'Northern Mariana Islands', 'APAC', FALSE),
('MQ', 'Martinique', 'LATAM', FALSE),
('MR', 'Mauritania', 'EMEA', FALSE),
('MS', 'Montserrat', 'LATAM', FALSE),
('MT', 'Malta', 'EMEA', FALSE),
('MU', 'Mauritius', 'EMEA', FALSE),
('MV', 'Maldives', 'APAC', FALSE),
('MW', 'Malawi', 'EMEA', FALSE),
('MX', 'Mexico', 'LATAM', FALSE),
('MY', 'Malaysia', 'APAC', FALSE),
('MZ', 'Mozambique', 'EMEA', FALSE),
('NA', 'Namibia', 'EMEA', FALSE),
('NC', 'New Caledonia', 'APAC', FALSE),
('NE', 'Niger', 'EMEA', FALSE),
('NF', 'Norfolk Island', 'APAC', FALSE),
('NG', 'Nigeria', 'EMEA', FALSE),
('NI', 'Nicaragua', 'LATAM', FALSE),
('NL', 'Netherlands', 'EMEA', FALSE),
('NO', 'Norway', 'EMEA', FALSE),
('NP', 'Nepal', 'APAC', FALSE),
('NR', 'Nauru', 'APAC', FALSE),
('NU', 'Niue', 'APAC', FALSE),
('NZ', 'New Zealand', 'APAC', FALSE),
('OM', 'Oman', 'EMEA', FALSE),
('PA', 'Panama', 'LATAM', FALSE),
('PE', 'Peru', 'LATAM', FALSE),
('PF', 'French Polynesia', 'APAC', FALSE),
('PG', 'Papua New Guinea', 'APAC', FALSE),
('PH', 'Philippines', 'APAC', FALSE),
('PK', 'Pakistan', 'APAC', FALSE),
('PL', 'Poland', 'EMEA', FALSE),
('PM', 'Saint Pierre and Miquelon', 'NAM', FALSE),
('PN', 'Pitcairn', 'APAC', FALSE),
('PR', 'Puerto Rico', 'LATAM', FALSE),
('PS', 'Palestine', 'EMEA', FALSE),
('PT', 'Portugal', 'EMEA', FALSE),
('PW', 'Palau', 'APAC', FALSE),
('PY', 'Paraguay', 'LATAM', FALSE),
('QA', 'Qatar', 'EMEA', FALSE),
('RE', 'Réunion', 'EMEA', FALSE),
('RO', 'Romania', 'EMEA', FALSE),
('RS', 'Serbia', 'EMEA', FALSE),
('RU', 'Russian Federation', 'EMEA', FALSE),
('RW', 'Rwanda', 'EMEA', FALSE),
('SA', 'Saudi Arabia', 'EMEA', FALSE),
('SB', 'Solomon Islands', 'APAC', FALSE),
('SC', 'Seychelles', 'EMEA', FALSE),
('SD', 'Sudan', 'EMEA', FALSE),
('SE', 'Sweden', 'EMEA', FALSE),
('SG', 'Singapore', 'APAC', FALSE),
('SH', 'Saint Helena', 'EMEA', FALSE),
('SI', 'Slovenia', 'EMEA', FALSE),
('SJ', 'Svalbard and Jan Mayen', 'EMEA', FALSE),
('SK', 'Slovakia', 'EMEA', FALSE),
('SL', 'Sierra Leone', 'EMEA', FALSE),
('SM', 'San Marino', 'EMEA', FALSE),
('SN', 'Senegal', 'EMEA', FALSE),
('SO', 'Somalia', 'EMEA', FALSE),
('SR', 'Suriname', 'LATAM', FALSE),
('SS', 'South Sudan', 'EMEA', FALSE),
('ST', 'Sao Tome and Principe', 'EMEA', FALSE),
('SV', 'El Salvador', 'LATAM', FALSE),
('SX', 'Sint Maarten', 'LATAM', FALSE),
('SY', 'Syrian Arab Republic', 'EMEA', FALSE),
('SZ', 'Eswatini', 'EMEA', FALSE),
('TC', 'Turks and Caicos Islands', 'LATAM', FALSE),
('TD', 'Chad', 'EMEA', FALSE),
('TF', 'French Southern Territories', 'EMEA', FALSE),
('TG', 'Togo', 'EMEA', FALSE),
('TH', 'Thailand', 'APAC', FALSE),
('TJ', 'Tajikistan', 'EMEA', FALSE),
('TK', 'Tokelau', 'APAC', FALSE),
('TL', 'Timor-Leste', 'APAC', FALSE),
('TM', 'Turkmenistan', 'EMEA', FALSE),
('TN', 'Tunisia', 'EMEA', FALSE),
('TO', 'Tonga', 'APAC', FALSE),
('TR', 'Turkey', 'EMEA', FALSE),
('TT', 'Trinidad and Tobago', 'LATAM', FALSE),
('TV', 'Tuvalu', 'APAC', FALSE),
('TW', 'Taiwan', 'APAC', FALSE),
('TZ', 'Tanzania', 'EMEA', FALSE),
('UA', 'Ukraine', 'EMEA', FALSE),
('UG', 'Uganda', 'EMEA', FALSE),
('UM', 'United States Minor Outlying Islands', 'APAC', FALSE),
('US', 'United States', 'NAM', FALSE),
('UY', 'Uruguay', 'LATAM', FALSE),
('UZ', 'Uzbekistan', 'EMEA', FALSE),
('VA', 'Vatican City', 'EMEA', FALSE),
('VC', 'Saint Vincent and the Grenadines', 'LATAM', FALSE),
('VE', 'Venezuela', 'LATAM', FALSE),
('VG', 'Virgin Islands, British', 'LATAM', FALSE),
('VI', 'Virgin Islands, U.S.', 'LATAM', FALSE),
('VN', 'Viet Nam', 'APAC', FALSE),
('VU', 'Vanuatu', 'APAC', FALSE),
('WF', 'Wallis and Futuna', 'APAC', FALSE),
('WS', 'Samoa', 'APAC', FALSE),
('YE', 'Yemen', 'EMEA', FALSE),
('YT', 'Mayotte', 'EMEA', FALSE),
('ZA', 'South Africa', 'EMEA', FALSE),
('ZM', 'Zambia', 'EMEA', FALSE),
('ZW', 'Zimbabwe', 'EMEA', FALSE),
('XK', 'Kosovo', 'EMEA', FALSE);
-- =============================================================================
-- 3. INSERT COUNTRY PROPERTIES