diff --git a/CODEOWNERS b/CODEOWNERS
index 714a3b974b8cc..f2eb7a550651a 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -403,6 +403,7 @@
/bundles/org.openhab.transform.regex/ @openhab/add-ons-maintainers
/bundles/org.openhab.transform.rollershutterposition/ @jsjames
/bundles/org.openhab.transform.scale/ @clinique
+/bundles/org.openhab.transform.vat/ @jlaur
/bundles/org.openhab.transform.xpath/ @openhab/add-ons-maintainers
/bundles/org.openhab.transform.xslt/ @openhab/add-ons-maintainers
/bundles/org.openhab.voice.googlestt/ @GiviMAD
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 644888df361ed..155513f6237b8 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -2011,6 +2011,11 @@
org.openhab.transform.scale
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.transform.vat
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.transform.xpath
diff --git a/bundles/org.openhab.transform.vat/NOTICE b/bundles/org.openhab.transform.vat/NOTICE
new file mode 100644
index 0000000000000..38d625e349232
--- /dev/null
+++ b/bundles/org.openhab.transform.vat/NOTICE
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.transform.vat/README.md b/bundles/org.openhab.transform.vat/README.md
new file mode 100644
index 0000000000000..a39046905d340
--- /dev/null
+++ b/bundles/org.openhab.transform.vat/README.md
@@ -0,0 +1,41 @@
+# VAT Transformation Service
+
+The VAT Transformation Service adds VAT (Value-Added Tax) to a given input amount.
+The input string must be either an ISO 3166 alpha-2 country code or a percentage, i.e. numerical format.
+
+## Examples
+
+### Display
+
+```java
+Number CurrentSpotPrice "Current Spot Price incl. VAT [VAT(12.5):%s]"
+```
+
+### In a Rule
+
+Add Danish VAT to price:
+
+```java
+var Number price = 499
+logInfo("Price", "Price incl. VAT: " + transform("VAT", "DK", price.toString))
+```
+
+## Usage as a Profile
+
+The functionality of this `TransformationService` can also be used in a `Profile` on an `ItemChannelLink`.
+This is the most powerful usage since VAT will be added without providing any explicit country code, percentage or configuration.
+To use this, an `.items` file can be configured as follows:
+
+```java
+Number CurrentSpotPrice "Current Spot Price" { channel="" [profile="transform:VAT"] }
+```
+
+To override VAT percentage for configured system country:
+
+```java
+Number CurrentSpotPrice "Current Spot Price" { channel="" [profile="transform:VAT", percentage="12.5"] }
+```
+
+If VAT is not known for the configured country or the provided percentage is invalid, the default is 0%, so the input value will be put into the transformation without any changes.
+
+Please note: This profile is a one-way transformation, i.e. only values from a device towards the item are changed, the other direction is left untouched.
diff --git a/bundles/org.openhab.transform.vat/pom.xml b/bundles/org.openhab.transform.vat/pom.xml
new file mode 100644
index 0000000000000..f8660d1cc686e
--- /dev/null
+++ b/bundles/org.openhab.transform.vat/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 4.0.0-SNAPSHOT
+
+
+ org.openhab.transform.vat
+
+ openHAB Add-ons :: Bundles :: Transformation Service :: Value-Added Tax
+
+
diff --git a/bundles/org.openhab.transform.vat/src/main/feature/feature.xml b/bundles/org.openhab.transform.vat/src/main/feature/feature.xml
new file mode 100644
index 0000000000000..2a75b2a2bbb93
--- /dev/null
+++ b/bundles/org.openhab.transform.vat/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.transform.vat/${project.version}
+
+
diff --git a/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/VATTransformationConstants.java b/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/VATTransformationConstants.java
new file mode 100644
index 0000000000000..99689ee9a4207
--- /dev/null
+++ b/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/VATTransformationConstants.java
@@ -0,0 +1,205 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.transform.vat.internal;
+
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.profiles.ProfileTypeUID;
+import org.openhab.core.transform.TransformationService;
+
+/**
+ * The {@link VATTransformationConstants} class defines constants
+ * used across the whole profile.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class VATTransformationConstants {
+
+ public static final ProfileTypeUID PROFILE_TYPE_UID = new ProfileTypeUID(
+ TransformationService.TRANSFORM_PROFILE_SCOPE, "VAT");
+
+ public static final Map RATES = Map.ofEntries(
+ // European Union countries
+ Map.entry("AT", "20"), // Austria
+ Map.entry("BE", "21"), // Belgium
+ Map.entry("BG", "20"), // Bulgaria
+ Map.entry("HR", "25"), // Croatia
+ Map.entry("CY", "19"), // Cyprus
+ Map.entry("CZ", "21"), // Czech Republic
+ Map.entry("DK", "25"), // Denmark
+ Map.entry("EE", "20"), // Estonia
+ Map.entry("FI", "24"), // Finland
+ Map.entry("FR", "20"), // France
+ Map.entry("DE", "19"), // Germany
+ Map.entry("GR", "24"), // Greece
+ Map.entry("HU", "27"), // Hungary
+ Map.entry("IE", "23"), // Ireland
+ Map.entry("IT", "22"), // Italy
+ Map.entry("LV", "21"), // Latvia
+ Map.entry("LT", "21"), // Lithuania
+ Map.entry("LU", "17"), // Luxembourg
+ Map.entry("MT", "18"), // Malta
+ Map.entry("NL", "21"), // Netherlands
+ Map.entry("PL", "23"), // Poland
+ Map.entry("PT", "23"), // Portugal
+ Map.entry("RO", "19"), // Romania
+ Map.entry("SK", "20"), // Slovakia
+ Map.entry("SI", "22"), // Slovenia
+ Map.entry("ES", "21"), // Spain
+ Map.entry("SE", "25"), // Sweden
+
+ // Non-European Union countries
+ Map.entry("AL", "20"), // Albania
+ Map.entry("DZ", "19"), // Algeria
+ Map.entry("AD", "4.5"), // Andorra
+ Map.entry("AO", "14"), // Angola
+ Map.entry("AG", "15"), // Antigua and Barbuda
+ Map.entry("AR", "21"), // Argentina
+ Map.entry("AM", "20"), // Armenia
+ Map.entry("AU", "10"), // Australia
+ Map.entry("AZ", "18"), // Azerbaijan
+ Map.entry("BS", "12"), // Bahamas
+ Map.entry("BH", "10"), // Bahrain
+ Map.entry("BD", "15"), // Bangladesh
+ Map.entry("BB", "17.5"), // Barbados
+ Map.entry("BY", "20"), // Belarus
+ Map.entry("BZ", "12.5"), // Belize
+ Map.entry("BJ", "18"), // Benin
+ Map.entry("BO", "13"), // Bolivia
+ Map.entry("BA", "17"), // Bosnia and Herzegovina
+ Map.entry("BW", "12"), // Botswana
+ Map.entry("BR", "20"), // Brazil
+ Map.entry("BF", "18"), // Burkina Faso
+ Map.entry("BI", "18"), // Burundi
+ Map.entry("KH", "10"), // Cambodia
+ Map.entry("CM", "19.25"), // Cameroon
+ Map.entry("CA", "5"), // Canada
+ Map.entry("CV", "15"), // Cape Verde
+ Map.entry("CF", "19"), // Central African Republic
+ Map.entry("TD", "18"), // Chad
+ Map.entry("CL", "19"), // Chile
+ Map.entry("CN", "13"), // China
+ Map.entry("CO", "19"), // Colombia
+ Map.entry("CR", "13"), // Costa Rica
+ Map.entry("CD", "16"), // Democratic Republic of the Congo
+ Map.entry("DM", "15"), // Dominica
+ Map.entry("DO", "18"), // Dominican Republic
+ Map.entry("EC", "12"), // Ecuador
+ Map.entry("EG", "14"), // Egypt
+ Map.entry("SV", "13"), // El Salvador
+ Map.entry("GQ", "15"), // Equatorial Guinea
+ Map.entry("ET", "15"), // Ethiopia
+ Map.entry("FO", "25"), // Faroe Islands
+ Map.entry("FJ", "15"), // Fiji
+ Map.entry("GA", "18"), // Gabon
+ Map.entry("GM", "15"), // Gambia
+ Map.entry("GE", "18"), // Georgia
+ Map.entry("GH", "15"), // Ghana
+ Map.entry("GD", "15"), // Grenada
+ Map.entry("GT", "12"), // Guatemala
+ Map.entry("GN", "18"), // Guinea
+ Map.entry("GW", "15"), // Guinea-Bissau
+ Map.entry("GY", "16"), // Guyana
+ Map.entry("HT", "10"), // Haiti
+ Map.entry("HN", "15"), // Honduras
+ Map.entry("IS", "24"), // Iceland
+ Map.entry("IN", "5.5"), // India
+ Map.entry("ID", "11"), // Indonesia
+ Map.entry("IR", "9"), // Iran
+ Map.entry("IM", "20"), // Isle of Man
+ Map.entry("IL", "17"), // Israel
+ Map.entry("CI", "18"), // Ivory Coast
+ Map.entry("JM", "12.5"), // Jamaica
+ Map.entry("JP", "10"), // Japan
+ Map.entry("JE", "5"), // Jersey
+ Map.entry("JO", "16"), // Jordan
+ Map.entry("KZ", "12"), // Kazakhstan
+ Map.entry("KE", "16"), // Kenya
+ Map.entry("KG", "20"), // Kyrgyzstan
+ Map.entry("LA", "10"), // Laos
+ Map.entry("LB", "11"), // Lebanon
+ Map.entry("LS", "14"), // Lesotho
+ Map.entry("LI", "7.7"), // Liechtenstein
+ Map.entry("MG", "20"), // Madagascar
+ Map.entry("MW", "16.5"), // Malawi
+ Map.entry("MY", "6"), // Malaysia
+ Map.entry("MV", "6"), // Maldives
+ Map.entry("ML", "18"), // Mali
+ Map.entry("MR", "14"), // Mauritania
+ Map.entry("MU", "15"), // Mauritius
+ Map.entry("MX", "16"), // Mexico
+ Map.entry("MD", "20"), // Moldova
+ Map.entry("MC", "19.6"), // Monaco
+ Map.entry("MN", "10"), // Mongolia
+ Map.entry("ME", "21"), // Montenegro
+ Map.entry("MA", "20"), // Morocco
+ Map.entry("MZ", "17"), // Mozambique
+ Map.entry("NA", "15"), // Namibia
+ Map.entry("NP", "13"), // Nepal
+ Map.entry("NZ", "15"), // New Zealand
+ Map.entry("NI", "15"), // Nicaragua
+ Map.entry("NE", "19"), // Niger
+ Map.entry("NG", "7.5"), // Nigeria
+ Map.entry("NU", "5"), // Niue
+ Map.entry("MK", "18"), // North Macedonia
+ Map.entry("NO", "25"), // Norway
+ Map.entry("PK", "17"), // Pakistan
+ Map.entry("PW", "10"), // Palau
+ Map.entry("PS", "16"), // Palestine
+ Map.entry("PA", "7"), // Panama
+ Map.entry("PG", "10"), // Papua New Guinea
+ Map.entry("PY", "10"), // Paraguay
+ Map.entry("PE", "18"), // Peru
+ Map.entry("PH", "12"), // Philippines
+ Map.entry("CG", "16"), // Republic of Congo
+ Map.entry("RU", "20"), // Russia
+ Map.entry("RW", "18"), // Rwanda
+ Map.entry("KN", "17"), // Saint Kitts and Nevis
+ Map.entry("VC", "15"), // Saint Vincent and the Grenadines
+ Map.entry("WS", "15"), // Samoa
+ Map.entry("SA", "15"), // Saudi Arabia
+ Map.entry("SN", "18"), // Senegal
+ Map.entry("RS", "20"), // Serbia
+ Map.entry("SC", "15"), // Seychelles
+ Map.entry("SL", "15"), // Sierra Leone
+ Map.entry("SG", "8"), // Singapore
+ Map.entry("ZA", "15"), // South Africa
+ Map.entry("KR", "10"), // South Korea
+ Map.entry("LK", "12"), // Sri Lanka
+ Map.entry("SD", "17"), // Sudan
+ Map.entry("CH", "7.7"), // Switzerland
+ Map.entry("TW", "5"), // Taiwan
+ Map.entry("TJ", "20"), // Tajikistan
+ Map.entry("TZ", "18"), // Tanzania
+ Map.entry("TH", "10"), // Thailand
+ Map.entry("TG", "18"), // Togo
+ Map.entry("TO", "15"), // Tonga
+ Map.entry("TT", "12.5"), // Trinidad and Tobago
+ Map.entry("TN", "18"), // Tunisia
+ Map.entry("TR", "18"), // Turkey
+ Map.entry("TM", "15"), // Turkmenistan
+ Map.entry("UG", "18"), // Uganda
+ Map.entry("UA", "20"), // Ukraine
+ Map.entry("AE", "5"), // United Arab Emirates
+ Map.entry("GB", "20"), // United Kingdom
+ Map.entry("UY", ""), // Uruguay
+ Map.entry("UZ", "12"), // Uzbekistan
+ Map.entry("VU", "13"), // Vanuatu
+ Map.entry("VN", "10"), // Vietnam
+ Map.entry("VE", "12"), // Venezuela
+ Map.entry("ZM", "16"), // Zambia
+ Map.entry("ZW", "15") // Zimbabwe
+ );
+}
diff --git a/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/VATTransformationService.java b/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/VATTransformationService.java
new file mode 100644
index 0000000000000..a0518c8811d2d
--- /dev/null
+++ b/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/VATTransformationService.java
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.transform.vat.internal;
+
+import static org.openhab.transform.vat.internal.VATTransformationConstants.*;
+
+import java.math.BigDecimal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.transform.TransformationException;
+import org.openhab.core.transform.TransformationService;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This {@link TransformationService} adds VAT to the input according to configured country.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = { TransformationService.class }, property = { "openhab.transform=VAT" })
+public class VATTransformationService implements TransformationService {
+
+ private final Logger logger = LoggerFactory.getLogger(VATTransformationService.class);
+
+ @Override
+ public @Nullable String transform(String valueString, String sourceString) throws TransformationException {
+ QuantityType> source;
+ try {
+ source = new QuantityType<>(sourceString);
+ } catch (IllegalArgumentException e) {
+ logger.warn("Input value '{}' could not be converted to a valid number", sourceString);
+ throw new TransformationException("VAT Transformation can only be used with numeric inputs", e);
+ }
+ BigDecimal value;
+ try {
+ value = new BigDecimal(valueString);
+ } catch (NumberFormatException e) {
+ String rate = RATES.get(valueString);
+ if (rate == null) {
+ logger.warn("Input value '{}' could not be converted to a valid number or country code", valueString);
+ throw new TransformationException("VAT Transformation can only be used with numeric inputs", e);
+ }
+ value = new BigDecimal(rate);
+ }
+
+ return addVAT(source, value).toString();
+ }
+
+ private QuantityType> addVAT(QuantityType> source, BigDecimal percentage) {
+ return source.multiply(percentage.divide(new BigDecimal("100")).add(BigDecimal.ONE));
+ }
+}
diff --git a/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/config/VATConfig.java b/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/config/VATConfig.java
new file mode 100644
index 0000000000000..c2d83a45885b3
--- /dev/null
+++ b/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/config/VATConfig.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.transform.vat.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link VATConfig} class contains the parameters for the VAT transformation.
+ *
+ * @author Jacob Laursen - initial contribution
+ *
+ */
+@NonNullByDefault
+public class VATConfig {
+ public String percentage = "";
+}
diff --git a/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/profile/VATTransformationProfile.java b/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/profile/VATTransformationProfile.java
new file mode 100644
index 0000000000000..1985d9beb5f66
--- /dev/null
+++ b/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/profile/VATTransformationProfile.java
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.transform.vat.internal.profile;
+
+import static org.openhab.transform.vat.internal.VATTransformationConstants.*;
+
+import java.math.BigDecimal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.i18n.LocaleProvider;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.thing.profiles.ProfileCallback;
+import org.openhab.core.thing.profiles.ProfileContext;
+import org.openhab.core.thing.profiles.ProfileTypeUID;
+import org.openhab.core.thing.profiles.StateProfile;
+import org.openhab.core.transform.TransformationException;
+import org.openhab.core.transform.TransformationHelper;
+import org.openhab.core.transform.TransformationService;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.openhab.core.types.Type;
+import org.openhab.transform.vat.internal.config.VATConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Profile to offer the {@link VATTransformationService} on an ItemChannelLink.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class VATTransformationProfile implements StateProfile {
+
+ private final Logger logger = LoggerFactory.getLogger(VATTransformationProfile.class);
+
+ private final ProfileCallback callback;
+ private final TransformationService service;
+ private final LocaleProvider localeProvider;
+
+ private VATConfig configuration;
+
+ public VATTransformationProfile(final ProfileCallback callback, final TransformationService service,
+ final ProfileContext context, LocaleProvider localeProvider) {
+ this.callback = callback;
+ this.service = service;
+ this.localeProvider = localeProvider;
+ this.configuration = context.getConfiguration().as(VATConfig.class);
+ }
+
+ @Override
+ public ProfileTypeUID getProfileTypeUID() {
+ return PROFILE_TYPE_UID;
+ }
+
+ @Override
+ public void onCommandFromItem(Command command) {
+ callback.handleCommand(command);
+ }
+
+ @Override
+ public void onStateUpdateFromItem(State state) {
+ }
+
+ @Override
+ public void onCommandFromHandler(Command command) {
+ callback.sendCommand((Command) transformState(command));
+ }
+
+ @Override
+ public void onStateUpdateFromHandler(State state) {
+ callback.sendUpdate((State) transformState(state));
+ }
+
+ private Type transformState(Type state) {
+ String result = state.toFullString();
+ try {
+ result = TransformationHelper.transform(service, getVATPercentage(), "%s", result);
+ } catch (TransformationException e) {
+ logger.warn("Could not apply '{}' transformation on state '{}' with value '{}'.", PROFILE_TYPE_UID.getId(),
+ state, getVATPercentage());
+ }
+ Type resultType = state;
+ if (result != null) {
+ if (state instanceof DecimalType) {
+ resultType = DecimalType.valueOf(result);
+ } else if (state instanceof QuantityType) {
+ resultType = QuantityType.valueOf(result);
+ }
+ logger.debug("Transformed '{}' into '{}'", state, resultType);
+ }
+ return resultType;
+ }
+
+ private String getVATPercentage() {
+ if (!configuration.percentage.isBlank()) {
+ return getOverriddenVAT();
+ }
+
+ String country = localeProvider.getLocale().getCountry();
+ String rate = RATES.get(country);
+ if (rate == null) {
+ logger.warn("No VAT rate for country {}", country);
+ return "0";
+ }
+ return rate;
+ }
+
+ private String getOverriddenVAT() {
+ String percentage = configuration.percentage.trim();
+ if (percentage.endsWith("%")) {
+ percentage = percentage.substring(0, percentage.length() - 1).trim();
+ }
+ try {
+ return new BigDecimal(percentage).toString();
+ } catch (NumberFormatException e) {
+ logger.warn("{} is not a valid percentage", percentage);
+ return "0";
+ }
+ }
+}
diff --git a/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/profile/VATTransformationProfileFactory.java b/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/profile/VATTransformationProfileFactory.java
new file mode 100644
index 0000000000000..67f254d6807ae
--- /dev/null
+++ b/bundles/org.openhab.transform.vat/src/main/java/org/openhab/transform/vat/internal/profile/VATTransformationProfileFactory.java
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.transform.vat.internal.profile;
+
+import static org.openhab.transform.vat.internal.VATTransformationConstants.PROFILE_TYPE_UID;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.i18n.LocaleProvider;
+import org.openhab.core.i18n.LocalizedKey;
+import org.openhab.core.library.CoreItemFactory;
+import org.openhab.core.thing.profiles.Profile;
+import org.openhab.core.thing.profiles.ProfileCallback;
+import org.openhab.core.thing.profiles.ProfileContext;
+import org.openhab.core.thing.profiles.ProfileFactory;
+import org.openhab.core.thing.profiles.ProfileType;
+import org.openhab.core.thing.profiles.ProfileTypeBuilder;
+import org.openhab.core.thing.profiles.ProfileTypeProvider;
+import org.openhab.core.thing.profiles.ProfileTypeUID;
+import org.openhab.core.thing.profiles.i18n.ProfileTypeI18nLocalizationService;
+import org.openhab.core.transform.TransformationService;
+import org.openhab.core.util.BundleResolver;
+import org.osgi.framework.Bundle;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link VATTransformationProfileFactory} is responsible for creating profiles.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = { ProfileFactory.class, ProfileTypeProvider.class })
+public class VATTransformationProfileFactory implements ProfileFactory, ProfileTypeProvider {
+
+ private final LocaleProvider localeProvider;
+ private final ProfileTypeI18nLocalizationService profileTypeI18nLocalizationService;
+ private final Map localizedProfileTypeCache = new ConcurrentHashMap<>();
+ private final Bundle bundle;
+
+ private @NonNullByDefault({}) TransformationService transformationService;
+
+ @Activate
+ public VATTransformationProfileFactory(final @Reference LocaleProvider localeProvider,
+ final @Reference ProfileTypeI18nLocalizationService profileTypeI18nLocalizationService,
+ final @Reference BundleResolver bundleResolver) {
+ this.localeProvider = localeProvider;
+ this.profileTypeI18nLocalizationService = profileTypeI18nLocalizationService;
+ this.bundle = bundleResolver.resolveBundle(VATTransformationProfileFactory.class);
+ }
+
+ @Override
+ public Collection getProfileTypes(@Nullable Locale locale) {
+ return Set.of(createLocalizedProfileType(ProfileTypeBuilder.newState(PROFILE_TYPE_UID, PROFILE_TYPE_UID.getId())
+ .withSupportedItemTypes(CoreItemFactory.NUMBER).build(), locale));
+ }
+
+ @Override
+ public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
+ ProfileContext profileContext) {
+ return new VATTransformationProfile(callback, transformationService, profileContext, localeProvider);
+ }
+
+ private ProfileType createLocalizedProfileType(ProfileType profileType, @Nullable Locale locale) {
+ final LocalizedKey localizedKey = new LocalizedKey(profileType.getUID(),
+ locale != null ? locale.toLanguageTag() : null);
+
+ final ProfileType cachedlocalizedProfileType = localizedProfileTypeCache.get(localizedKey);
+ if (cachedlocalizedProfileType != null) {
+ return cachedlocalizedProfileType;
+ }
+
+ final ProfileType localizedProfileType = profileTypeI18nLocalizationService.createLocalizedProfileType(bundle,
+ profileType, locale);
+ localizedProfileTypeCache.put(localizedKey, localizedProfileType);
+
+ return localizedProfileType;
+ }
+
+ @Override
+ public Collection getSupportedProfileTypeUIDs() {
+ return List.of(PROFILE_TYPE_UID);
+ }
+
+ @Reference(target = "(openhab.transform=VAT)")
+ public void addTransformationService(TransformationService service) {
+ this.transformationService = service;
+ }
+
+ public void removeTransformationService(TransformationService service) {
+ this.transformationService = null;
+ }
+}
diff --git a/bundles/org.openhab.transform.vat/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.transform.vat/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644
index 0000000000000..21368d9969a9f
--- /dev/null
+++ b/bundles/org.openhab.transform.vat/src/main/resources/OH-INF/addon/addon.xml
@@ -0,0 +1,10 @@
+
+
+
+ transformation
+ VAT transformation
+ Transforms the input by adding Value-Added Tax according to configured country or percentage.
+
+
diff --git a/bundles/org.openhab.transform.vat/src/main/resources/OH-INF/config/vatProfile.xml b/bundles/org.openhab.transform.vat/src/main/resources/OH-INF/config/vatProfile.xml
new file mode 100644
index 0000000000000..3d95b561a7fbe
--- /dev/null
+++ b/bundles/org.openhab.transform.vat/src/main/resources/OH-INF/config/vatProfile.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Manually configured VAT percentage (overriding VAT rate for configured country).
+ true
+
+
+
diff --git a/bundles/org.openhab.transform.vat/src/main/resources/OH-INF/i18n/vat.properties b/bundles/org.openhab.transform.vat/src/main/resources/OH-INF/i18n/vat.properties
new file mode 100644
index 0000000000000..a4bf2f3ae7f16
--- /dev/null
+++ b/bundles/org.openhab.transform.vat/src/main/resources/OH-INF/i18n/vat.properties
@@ -0,0 +1,10 @@
+# add-on
+
+addon.vat.name = VAT transformation
+addon.vat.description = Transforms the input by adding Value-Added Tax according to configured country or percentage.
+
+# profile
+
+profile-type.transform.VAT.label = Value-Added Tax
+profile.config.transform.VAT.percentage.label = VAT Percentage
+profile.config.transform.VAT.percentage.description = Manually configured VAT percentage (overriding VAT rate for configured country).
diff --git a/bundles/pom.xml b/bundles/pom.xml
index ad9da9684c1f8..47e5118ee4d94 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -41,6 +41,7 @@
org.openhab.transform.regex
org.openhab.transform.rollershutterposition
org.openhab.transform.scale
+ org.openhab.transform.vat
org.openhab.transform.xpath
org.openhab.transform.xslt