From 076b6b73e516dcccc2cf2d2d1af771331d7e73fc Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Wed, 30 Mar 2022 21:29:51 +0200
Subject: [PATCH] #691 - importer for transactions

---
 .../database/DatabaseService.java             |   2 +-
 .../database/importer/TagImporter.java        |  37 ++
 .../importer/TransactionImporter.java         |  44 +++
 .../budgetmaster/services/ImportService.java  |  56 +---
 .../de/deadlocker8/budgetmaster/tags/Tag.java |   3 +-
 .../transactions/Transaction.java             |   3 +-
 .../unit/database/ImportServiceTest.java      |  61 ----
 .../importer/TransactionImporterTest.java     | 317 ++++++++++++++++++
 8 files changed, 407 insertions(+), 116 deletions(-)
 create mode 100644 src/main/java/de/deadlocker8/budgetmaster/database/importer/TagImporter.java
 create mode 100644 src/main/java/de/deadlocker8/budgetmaster/database/importer/TransactionImporter.java
 create mode 100644 src/test/java/de/deadlocker8/budgetmaster/unit/database/importer/TransactionImporterTest.java

diff --git a/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseService.java b/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseService.java
index a694dc11c..a77b411db 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseService.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseService.java
@@ -81,7 +81,7 @@ public class DatabaseService
 
 	public void reset()
 	{
-		final List<Resettable> services = List.of(this.transactionService, templateService, templateGroupService, categoryService, accountService, tagService, chartService, iconService, imageService);
+		final List<Resettable> services = List.of(transactionService, templateService, templateGroupService, categoryService, accountService, tagService, chartService, iconService, imageService);
 
 		for(Resettable service : services)
 		{
diff --git a/src/main/java/de/deadlocker8/budgetmaster/database/importer/TagImporter.java b/src/main/java/de/deadlocker8/budgetmaster/database/importer/TagImporter.java
new file mode 100644
index 000000000..2a01e8a52
--- /dev/null
+++ b/src/main/java/de/deadlocker8/budgetmaster/database/importer/TagImporter.java
@@ -0,0 +1,37 @@
+package de.deadlocker8.budgetmaster.database.importer;
+
+import de.deadlocker8.budgetmaster.services.EntityType;
+import de.deadlocker8.budgetmaster.tags.Tag;
+import de.deadlocker8.budgetmaster.tags.TagRepository;
+
+public class TagImporter extends ItemImporter<Tag>
+{
+	public TagImporter(TagRepository tagRepository)
+	{
+		super(tagRepository, EntityType.TAGS, false);
+	}
+
+	@Override
+	protected int importSingleItem(Tag tag)
+	{
+		if(!(repository instanceof TagRepository repository))
+		{
+			throw new IllegalArgumentException("Invalid repository type");
+		}
+
+		final Tag existingTag = repository.findByName(tag.getName());
+		if(existingTag == null)
+		{
+			final Tag newTag = repository.save(new Tag(tag.getName()));
+			return newTag.getID();
+		}
+
+		return tag.getID();
+	}
+
+	@Override
+	protected String getNameForItem(Tag item)
+	{
+		return String.valueOf(item.getName());
+	}
+}
diff --git a/src/main/java/de/deadlocker8/budgetmaster/database/importer/TransactionImporter.java b/src/main/java/de/deadlocker8/budgetmaster/database/importer/TransactionImporter.java
new file mode 100644
index 000000000..354b865a3
--- /dev/null
+++ b/src/main/java/de/deadlocker8/budgetmaster/database/importer/TransactionImporter.java
@@ -0,0 +1,44 @@
+package de.deadlocker8.budgetmaster.database.importer;
+
+import de.deadlocker8.budgetmaster.services.EntityType;
+import de.deadlocker8.budgetmaster.transactions.Transaction;
+import de.deadlocker8.budgetmaster.transactions.TransactionRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.MessageFormat;
+
+public class TransactionImporter extends ItemImporter<Transaction>
+{
+	private static final Logger LOGGER = LoggerFactory.getLogger(TransactionImporter.class);
+
+	private final TagImporter tagImporter;
+
+	public TransactionImporter(TransactionRepository transactionRepository, TagImporter tagImporter)
+	{
+		super(transactionRepository, EntityType.TRANSACTION, true);
+		this.tagImporter = tagImporter;
+	}
+
+	@Override
+	protected int importSingleItem(Transaction transaction)
+	{
+		if(!(repository instanceof TransactionRepository repository))
+		{
+			throw new IllegalArgumentException("Invalid repository type");
+		}
+
+		LOGGER.debug(MessageFormat.format("Importing transaction with name: {0}, date: {1}", transaction.getName(), transaction.getDate()));
+		tagImporter.importItems(transaction.getTags());
+		transaction.setID(null);
+		final Transaction newTransaction = repository.save(transaction);
+
+		return newTransaction.getID();
+	}
+
+	@Override
+	protected String getNameForItem(Transaction item)
+	{
+		return item.getName();
+	}
+}
diff --git a/src/main/java/de/deadlocker8/budgetmaster/services/ImportService.java b/src/main/java/de/deadlocker8/budgetmaster/services/ImportService.java
index 1dad5b5ae..190c101f4 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/services/ImportService.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/services/ImportService.java
@@ -86,7 +86,9 @@ public class ImportService
 		new IconImporter(iconRepository).importItems(database.getIcons());
 		importResultItems.add(new CategoryImporter(categoryRepository).importItems(database.getCategories()));
 		importResultItems.add(new AccountImporter(accountRepository).importItems(database.getAccounts(), accountMatchList));
-		importResultItems.add(importTransactions());
+
+		final TagImporter tagImporter = new TagImporter(tagRepository);
+		importResultItems.add(new TransactionImporter(transactionRepository, tagImporter).importItems(database.getTransactions()));
 
 		if(importTemplateGroups)
 		{
@@ -137,56 +139,6 @@ public class ImportService
 		return MessageFormat.format("{0}: {1} ({2})", errorMessage, e.getClass().getName(), e.getMessage());
 	}
 
-	private ImportResultItem importTransactions()
-	{
-		List<Transaction> transactions = database.getTransactions();
-		LOGGER.debug(MessageFormat.format("Importing {0} transactions...", transactions.size()));
-
-		int numberOfImportedTransactions = 0;
-
-		for(int i = 0; i < transactions.size(); i++)
-		{
-			Transaction transaction = transactions.get(i);
-			try
-			{
-				LOGGER.debug(MessageFormat.format("Importing transaction {0}/{1} (name: {2}, date: {3})", i + 1, transactions.size(), transaction.getName(), transaction.getDate()));
-				updateTagsForItem(transaction);
-				transaction.setID(null);
-				transactionRepository.save(transaction);
-
-				numberOfImportedTransactions++;
-			}
-			catch(Exception e)
-			{
-				final String errorMessage = MessageFormat.format("Error while importing transaction with name \"{0}\" from {1}", transaction.getName(), transaction.getDate().format(DateTimeFormatter.ofPattern(DateFormatStyle.NORMAL.getKey())));
-				LOGGER.error(errorMessage, e);
-				collectedErrorMessages.add(formatErrorMessage(errorMessage, e));
-			}
-		}
-
-		LOGGER.debug(MessageFormat.format("Importing transactions DONE ({0}/{1})", numberOfImportedTransactions, transactions.size()));
-		return new ImportResultItem(EntityType.TRANSACTION, numberOfImportedTransactions, transactions.size(), collectedErrorMessages);
-	}
-
-	public void updateTagsForItem(TransactionBase item)
-	{
-		List<Tag> tags = item.getTags();
-		for(int i = 0; i < tags.size(); i++)
-		{
-			Tag currentTag = tags.get(i);
-			Tag existingTag = tagRepository.findByName(currentTag.getName());
-			if(existingTag == null)
-			{
-				final Tag newTag = tagRepository.save(new Tag(currentTag.getName()));
-				tags.set(i, newTag);
-			}
-			else
-			{
-				tags.set(i, existingTag);
-			}
-		}
-	}
-
 	private ImportResultItem importTemplateGroups()
 	{
 		List<TemplateGroup> templateGroups = database.getTemplateGroups();
@@ -290,7 +242,7 @@ public class ImportService
 			try
 			{
 				LOGGER.debug(MessageFormat.format("Importing template {0}/{1} (templateName: {2})", i + 1, templates.size(), template.getTemplateName()));
-				updateTagsForItem(template);
+//				updateTagsForItem(template);
 				template.setID(null);
 
 				if(!importTemplateGroups || template.getTemplateGroup() == null)
diff --git a/src/main/java/de/deadlocker8/budgetmaster/tags/Tag.java b/src/main/java/de/deadlocker8/budgetmaster/tags/Tag.java
index d5281062a..e9e8aa26a 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/tags/Tag.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/tags/Tag.java
@@ -3,6 +3,7 @@ package de.deadlocker8.budgetmaster.tags;
 import com.google.gson.annotations.Expose;
 import de.deadlocker8.budgetmaster.templates.Template;
 import de.deadlocker8.budgetmaster.transactions.Transaction;
+import de.deadlocker8.budgetmaster.utils.ProvidesID;
 
 import javax.persistence.*;
 import javax.validation.constraints.NotNull;
@@ -11,7 +12,7 @@ import java.util.List;
 import java.util.Objects;
 
 @Entity
-public class Tag
+public class Tag implements ProvidesID
 {
 	@Id
 	@GeneratedValue(strategy = GenerationType.IDENTITY)
diff --git a/src/main/java/de/deadlocker8/budgetmaster/transactions/Transaction.java b/src/main/java/de/deadlocker8/budgetmaster/transactions/Transaction.java
index 8f2a3a650..c6bfc4133 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/transactions/Transaction.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/transactions/Transaction.java
@@ -8,6 +8,7 @@ import de.deadlocker8.budgetmaster.categories.CategoryType;
 import de.deadlocker8.budgetmaster.repeating.RepeatingOption;
 import de.deadlocker8.budgetmaster.tags.Tag;
 import de.deadlocker8.budgetmaster.utils.DateHelper;
+import de.deadlocker8.budgetmaster.utils.ProvidesID;
 import org.springframework.format.annotation.DateTimeFormat;
 
 import javax.persistence.*;
@@ -17,7 +18,7 @@ import java.util.List;
 import java.util.Objects;
 
 @Entity
-public class Transaction implements TransactionBase
+public class Transaction implements TransactionBase, ProvidesID
 {
 	@Id
 	@GeneratedValue(strategy = GenerationType.IDENTITY)
diff --git a/src/test/java/de/deadlocker8/budgetmaster/unit/database/ImportServiceTest.java b/src/test/java/de/deadlocker8/budgetmaster/unit/database/ImportServiceTest.java
index f74b0da6d..6996d9e22 100644
--- a/src/test/java/de/deadlocker8/budgetmaster/unit/database/ImportServiceTest.java
+++ b/src/test/java/de/deadlocker8/budgetmaster/unit/database/ImportServiceTest.java
@@ -16,9 +16,6 @@ import de.deadlocker8.budgetmaster.database.accountmatches.AccountMatchList;
 import de.deadlocker8.budgetmaster.icon.Icon;
 import de.deadlocker8.budgetmaster.icon.IconRepository;
 import de.deadlocker8.budgetmaster.icon.IconService;
-import de.deadlocker8.budgetmaster.images.Image;
-import de.deadlocker8.budgetmaster.images.ImageFileExtension;
-import de.deadlocker8.budgetmaster.images.ImageRepository;
 import de.deadlocker8.budgetmaster.images.ImageService;
 import de.deadlocker8.budgetmaster.repeating.RepeatingTransactionUpdater;
 import de.deadlocker8.budgetmaster.services.ImportService;
@@ -81,64 +78,6 @@ class ImportServiceTest
 	@InjectMocks
 	private ImportService importService;
 
-
-	@Test
-	void test_updateTagsForItem_ExistingTag()
-	{
-		Account account1 = new Account("Account_1", AccountType.CUSTOM);
-		account1.setID(2);
-
-		Tag existingTag = new Tag("ExistingTag");
-		existingTag.setID(2);
-
-		Transaction transaction1 = new Transaction();
-		transaction1.setAccount(account1);
-		transaction1.setName("ShouldGoInAccount_1");
-		transaction1.setAmount(200);
-		transaction1.setDate(LocalDate.of(2018, 10, 3));
-		List<Tag> tags = new ArrayList<>();
-		tags.add(existingTag);
-		transaction1.setTags(tags);
-
-		Mockito.when(tagRepository.findByName(existingTag.getName())).thenReturn(existingTag);
-
-		importService.updateTagsForItem(transaction1);
-		assertThat(transaction1.getTags()).hasSize(1);
-		assertThat(transaction1.getTags().get(0))
-				.hasFieldOrPropertyWithValue("ID", 2)
-				.hasFieldOrPropertyWithValue("name", existingTag.getName());
-	}
-
-	@Test
-	void test_updateTagsForItem_NewTag()
-	{
-		Account account1 = new Account("Account_1", AccountType.CUSTOM);
-		account1.setID(2);
-
-		Tag newTag = new Tag("NewTag");
-		newTag.setID(5);
-
-		Transaction transaction1 = new Transaction();
-		transaction1.setAccount(account1);
-		transaction1.setName("ShouldGoInAccount_1");
-		transaction1.setAmount(200);
-		transaction1.setDate(LocalDate.of(2018, 10, 3));
-		List<Tag> tags = new ArrayList<>();
-		tags.add(newTag);
-		transaction1.setTags(tags);
-
-		Tag savedTag = new Tag("NewTag");
-		savedTag.setID(1);
-		Mockito.when(tagRepository.save(Mockito.any(Tag.class))).thenReturn(savedTag);
-		Mockito.when(tagRepository.findByName(newTag.getName())).thenReturn(null);
-
-		importService.updateTagsForItem(transaction1);
-		assertThat(transaction1.getTags()).hasSize(1);
-		assertThat(transaction1.getTags().get(0))
-				.hasFieldOrPropertyWithValue("ID", 1)
-				.hasFieldOrPropertyWithValue("name", newTag.getName());
-	}
-
 	@Test
 	void test_importFullDatabase()
 	{
diff --git a/src/test/java/de/deadlocker8/budgetmaster/unit/database/importer/TransactionImporterTest.java b/src/test/java/de/deadlocker8/budgetmaster/unit/database/importer/TransactionImporterTest.java
new file mode 100644
index 000000000..575a5540e
--- /dev/null
+++ b/src/test/java/de/deadlocker8/budgetmaster/unit/database/importer/TransactionImporterTest.java
@@ -0,0 +1,317 @@
+package de.deadlocker8.budgetmaster.unit.database.importer;
+
+import de.deadlocker8.budgetmaster.accounts.Account;
+import de.deadlocker8.budgetmaster.accounts.AccountRepository;
+import de.deadlocker8.budgetmaster.accounts.AccountType;
+import de.deadlocker8.budgetmaster.categories.Category;
+import de.deadlocker8.budgetmaster.categories.CategoryRepository;
+import de.deadlocker8.budgetmaster.categories.CategoryType;
+import de.deadlocker8.budgetmaster.database.importer.IconImporter;
+import de.deadlocker8.budgetmaster.database.importer.TagImporter;
+import de.deadlocker8.budgetmaster.database.importer.TransactionImporter;
+import de.deadlocker8.budgetmaster.icon.Icon;
+import de.deadlocker8.budgetmaster.icon.IconRepository;
+import de.deadlocker8.budgetmaster.images.Image;
+import de.deadlocker8.budgetmaster.images.ImageFileExtension;
+import de.deadlocker8.budgetmaster.images.ImageRepository;
+import de.deadlocker8.budgetmaster.repeating.RepeatingOption;
+import de.deadlocker8.budgetmaster.repeating.endoption.RepeatingEndAfterXTimes;
+import de.deadlocker8.budgetmaster.repeating.modifier.RepeatingModifierDays;
+import de.deadlocker8.budgetmaster.services.EntityType;
+import de.deadlocker8.budgetmaster.services.ImportResultItem;
+import de.deadlocker8.budgetmaster.tags.Tag;
+import de.deadlocker8.budgetmaster.tags.TagRepository;
+import de.deadlocker8.budgetmaster.transactions.Transaction;
+import de.deadlocker8.budgetmaster.transactions.TransactionRepository;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.time.LocalDate;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.as;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ExtendWith(SpringExtension.class)
+@DataJpaTest
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+class TransactionImporterTest
+{
+	@Autowired
+	private AccountRepository accountRepository;
+
+	@Autowired
+	private CategoryRepository categoryRepository;
+
+	@Autowired
+	private TransactionRepository transactionRepository;
+
+	@Autowired
+	private TagRepository tagRepository;
+
+	@Test
+	void test_importNormalTransaction()
+	{
+		Category category = new Category("Awesome Category", "#ff0000", CategoryType.CUSTOM);
+		category = categoryRepository.save(category);
+
+		Account account = new Account("Awesome Account", AccountType.CUSTOM);
+		account = accountRepository.save(account);
+
+		final Transaction transaction = new Transaction();
+		transaction.setID(15);
+		transaction.setName("My transaction");
+		transaction.setAmount(-100);
+		transaction.setIsExpenditure(true);
+		transaction.setCategory(category);
+		transaction.setAccount(account);
+		transaction.setTags(List.of());
+		transaction.setDate(LocalDate.of(2022, 3, 30));
+		transaction.setDescription("Lorem Ipsum");
+
+		final TagImporter tagImporter = new TagImporter(tagRepository);
+		final TransactionImporter importer = new TransactionImporter(transactionRepository, tagImporter);
+		final ImportResultItem resultItem = importer.importItems(List.of(transaction));
+
+		final ImportResultItem expected = new ImportResultItem(EntityType.TRANSACTION, 1, 1, List.of());
+		assertThat(resultItem).isEqualTo(expected);
+
+		final List<Transaction> transactions = transactionRepository.findAll();
+		assertThat(transactions).hasSize(1);
+		final Transaction actualTransaction = transactions.get(0);
+		assertThat(actualTransaction)
+				.hasFieldOrPropertyWithValue("ID", 1)
+				.hasFieldOrPropertyWithValue("name", "My transaction")
+				.hasFieldOrPropertyWithValue("amount", -100)
+				.hasFieldOrPropertyWithValue("isExpenditure", true)
+				.hasFieldOrPropertyWithValue("category", category)
+				.hasFieldOrPropertyWithValue("account", account)
+				.hasFieldOrPropertyWithValue("date", LocalDate.of(2022, 3, 30))
+				.hasFieldOrPropertyWithValue("description","Lorem Ipsum")
+				.hasFieldOrPropertyWithValue("repeatingOption", null)
+				.hasFieldOrPropertyWithValue("transferAccount", null);
+		assertThat(actualTransaction.getTags()).isEmpty();
+	}
+
+	@Test
+	void test_importTransferTransaction()
+	{
+		Category category = new Category("Awesome Category", "#ff0000", CategoryType.CUSTOM);
+		category = categoryRepository.save(category);
+
+		Account account = new Account("Awesome Account", AccountType.CUSTOM);
+		account = accountRepository.save(account);
+
+		Account transferAccount = new Account("Transfer Account", AccountType.CUSTOM);
+		transferAccount = accountRepository.save(transferAccount);
+
+		final Transaction transaction = new Transaction();
+		transaction.setID(15);
+		transaction.setName("My transaction");
+		transaction.setAmount(-100);
+		transaction.setIsExpenditure(true);
+		transaction.setCategory(category);
+		transaction.setAccount(account);
+		transaction.setTransferAccount(transferAccount);
+		transaction.setTags(List.of());
+		transaction.setDate(LocalDate.of(2022, 3, 30));
+		transaction.setDescription("Lorem Ipsum");
+
+		final TagImporter tagImporter = new TagImporter(tagRepository);
+		final TransactionImporter importer = new TransactionImporter(transactionRepository, tagImporter);
+		final ImportResultItem resultItem = importer.importItems(List.of(transaction));
+
+		final ImportResultItem expected = new ImportResultItem(EntityType.TRANSACTION, 1, 1, List.of());
+		assertThat(resultItem).isEqualTo(expected);
+
+		final List<Transaction> transactions = transactionRepository.findAll();
+		assertThat(transactions).hasSize(1);
+		final Transaction actualTransaction = transactions.get(0);
+		assertThat(actualTransaction)
+				.hasFieldOrPropertyWithValue("ID", 1)
+				.hasFieldOrPropertyWithValue("name", "My transaction")
+				.hasFieldOrPropertyWithValue("amount", -100)
+				.hasFieldOrPropertyWithValue("isExpenditure", true)
+				.hasFieldOrPropertyWithValue("category", category)
+				.hasFieldOrPropertyWithValue("account", account)
+				.hasFieldOrPropertyWithValue("date", LocalDate.of(2022, 3, 30))
+				.hasFieldOrPropertyWithValue("description","Lorem Ipsum")
+				.hasFieldOrPropertyWithValue("repeatingOption", null)
+				.hasFieldOrPropertyWithValue("transferAccount", transferAccount);
+		assertThat(actualTransaction.getTags()).isEmpty();
+	}
+
+	@Test
+	void test_importRepeatingTransaction()
+	{
+		Category category = new Category("Awesome Category", "#ff0000", CategoryType.CUSTOM);
+		category = categoryRepository.save(category);
+
+		Account account = new Account("Awesome Account", AccountType.CUSTOM);
+		account = accountRepository.save(account);
+
+		final Transaction transaction = new Transaction();
+		transaction.setID(15);
+		transaction.setName("My transaction");
+		transaction.setAmount(-100);
+		transaction.setIsExpenditure(true);
+		transaction.setCategory(category);
+		transaction.setAccount(account);
+		transaction.setTags(List.of());
+		final LocalDate date = LocalDate.of(2022, 3, 30);
+		transaction.setDate(date);
+		transaction.setDescription("Lorem Ipsum");
+
+		final RepeatingOption repeatingOption = new RepeatingOption(date, new RepeatingModifierDays(2), new RepeatingEndAfterXTimes(3));
+		transaction.setRepeatingOption(repeatingOption);
+
+		final TagImporter tagImporter = new TagImporter(tagRepository);
+		final TransactionImporter importer = new TransactionImporter(transactionRepository, tagImporter);
+		final ImportResultItem resultItem = importer.importItems(List.of(transaction));
+
+		final ImportResultItem expected = new ImportResultItem(EntityType.TRANSACTION, 1, 1, List.of());
+		assertThat(resultItem).isEqualTo(expected);
+
+		final List<Transaction> transactions = transactionRepository.findAll();
+		assertThat(transactions).hasSize(1);
+		final Transaction actualTransaction = transactions.get(0);
+		assertThat(actualTransaction)
+				.hasFieldOrPropertyWithValue("ID", 1)
+				.hasFieldOrPropertyWithValue("name", "My transaction")
+				.hasFieldOrPropertyWithValue("amount", -100)
+				.hasFieldOrPropertyWithValue("isExpenditure", true)
+				.hasFieldOrPropertyWithValue("category", category)
+				.hasFieldOrPropertyWithValue("account", account)
+				.hasFieldOrPropertyWithValue("date", date)
+				.hasFieldOrPropertyWithValue("description","Lorem Ipsum")
+				.hasFieldOrPropertyWithValue("repeatingOption", repeatingOption)
+				.hasFieldOrPropertyWithValue("transferAccount", null);
+		assertThat(actualTransaction.getTags()).isEmpty();
+	}
+
+	@Test
+	void test_importTransactionWithTags()
+	{
+		Category category = new Category("Awesome Category", "#ff0000", CategoryType.CUSTOM);
+		category = categoryRepository.save(category);
+
+		Account account = new Account("Awesome Account", AccountType.CUSTOM);
+		account = accountRepository.save(account);
+
+		final Transaction transaction = new Transaction();
+		transaction.setID(15);
+		transaction.setName("My transaction");
+		transaction.setAmount(-100);
+		transaction.setIsExpenditure(true);
+		transaction.setCategory(category);
+		transaction.setAccount(account);
+		transaction.setDate(LocalDate.of(2022, 3, 30));
+		transaction.setDescription("Lorem Ipsum");
+
+		final Tag tag1 = new Tag("0815");
+		tag1.setID(1);
+		final Tag tag2 = new Tag("Apple Pie");
+		tag2.setID(2);
+
+		transaction.setTags(List.of(tag1, tag2));
+
+		final TagImporter tagImporter = new TagImporter(tagRepository);
+		final TransactionImporter importer = new TransactionImporter(transactionRepository, tagImporter);
+		final ImportResultItem resultItem = importer.importItems(List.of(transaction));
+
+		final ImportResultItem expected = new ImportResultItem(EntityType.TRANSACTION, 1, 1, List.of());
+		assertThat(resultItem).isEqualTo(expected);
+
+		final List<Transaction> transactions = transactionRepository.findAll();
+		assertThat(transactions).hasSize(1);
+		final Transaction actualTransaction = transactions.get(0);
+		assertThat(actualTransaction)
+				.hasFieldOrPropertyWithValue("ID", 1)
+				.hasFieldOrPropertyWithValue("name", "My transaction")
+				.hasFieldOrPropertyWithValue("amount", -100)
+				.hasFieldOrPropertyWithValue("isExpenditure", true)
+				.hasFieldOrPropertyWithValue("category", category)
+				.hasFieldOrPropertyWithValue("account", account)
+				.hasFieldOrPropertyWithValue("date", LocalDate.of(2022, 3, 30))
+				.hasFieldOrPropertyWithValue("description","Lorem Ipsum")
+				.hasFieldOrPropertyWithValue("repeatingOption", null)
+				.hasFieldOrPropertyWithValue("transferAccount", null);
+
+		final Tag expectedTag1 = new Tag("0815");
+		expectedTag1.setID(1);
+		final Tag expectedTag2 = new Tag("Apple Pie");
+		expectedTag2.setID(2);
+		assertThat(actualTransaction.getTags())
+				.containsExactly(expectedTag1, expectedTag2);
+	}
+
+	@Test
+	void test_importMultipleTransactionWithSomeSimilarTags()
+	{
+		Category category = new Category("Awesome Category", "#ff0000", CategoryType.CUSTOM);
+		category = categoryRepository.save(category);
+
+		Account account = new Account("Awesome Account", AccountType.CUSTOM);
+		account = accountRepository.save(account);
+
+		final Tag tag1 = new Tag("0815");
+		tag1.setID(1);
+		final Tag tag2 = new Tag("Apple Pie");
+		tag2.setID(2);
+
+		final Transaction transaction = new Transaction();
+		transaction.setID(15);
+		transaction.setName("My transaction");
+		transaction.setAmount(-100);
+		transaction.setIsExpenditure(true);
+		transaction.setCategory(category);
+		transaction.setAccount(account);
+		transaction.setDate(LocalDate.of(2022, 3, 30));
+		transaction.setTags(List.of(tag1, tag2));
+
+		final Transaction transaction2 = new Transaction();
+		transaction2.setID(16);
+		transaction2.setName("My transaction 2");
+		transaction2.setAmount(-250);
+		transaction2.setIsExpenditure(true);
+		transaction2.setCategory(category);
+		transaction2.setAccount(account);
+		transaction2.setDate(LocalDate.of(2022, 3, 30));
+		transaction2.setTags(List.of(tag1));
+
+		final TagImporter tagImporter = new TagImporter(tagRepository);
+		final TransactionImporter importer = new TransactionImporter(transactionRepository, tagImporter);
+		final ImportResultItem resultItem = importer.importItems(List.of(transaction, transaction2));
+
+		final ImportResultItem expected = new ImportResultItem(EntityType.TRANSACTION, 2, 2, List.of());
+		assertThat(resultItem).isEqualTo(expected);
+
+		final List<Transaction> transactions = transactionRepository.findAll();
+		assertThat(transactions).hasSize(2);
+		final Transaction actualTransaction = transactions.get(0);
+		assertThat(actualTransaction)
+				.hasFieldOrPropertyWithValue("ID", 1)
+				.hasFieldOrPropertyWithValue("name", "My transaction");
+
+		final Tag expectedTag1 = new Tag("0815");
+		expectedTag1.setID(1);
+		final Tag expectedTag2 = new Tag("Apple Pie");
+		expectedTag2.setID(2);
+		assertThat(actualTransaction.getTags())
+				.containsExactly(expectedTag1, expectedTag2);
+
+		final Transaction actualTransaction2 = transactions.get(1);
+		assertThat(actualTransaction2)
+				.hasFieldOrPropertyWithValue("ID", 2)
+				.hasFieldOrPropertyWithValue("name", "My transaction 2");
+
+		assertThat(actualTransaction2.getTags())
+				.containsExactly(expectedTag1);
+
+		assertThat(tagRepository.findAll()).hasSize(2);
+	}
+}
\ No newline at end of file
-- 
GitLab