From caf8bdfe61715373f943a7237014fa11ed65dc96 Mon Sep 17 00:00:00 2001 From: Robert Goldmann <deadlocker@gmx.de> Date: Sun, 30 Jan 2022 12:46:45 +0100 Subject: [PATCH] #546 - import/export database: importing template groups is optional --- .../database/InternalDatabase.java | 1 + .../budgetmaster/services/EntityType.java | 2 +- .../budgetmaster/services/ImportService.java | 21 ++++++-- .../settings/SettingsController.java | 8 ++- .../resources/languages/base_de.properties | 6 ++- .../resources/languages/base_en.properties | 4 +- ...tabaseParser_v8_convertToInternalTest.java | 2 +- .../unit/database/ImportServiceTest.java | 50 ++++++++++++++----- 8 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/main/java/de/deadlocker8/budgetmaster/database/InternalDatabase.java b/src/main/java/de/deadlocker8/budgetmaster/database/InternalDatabase.java index 6437bc5da..9fcb7495f 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/database/InternalDatabase.java +++ b/src/main/java/de/deadlocker8/budgetmaster/database/InternalDatabase.java @@ -96,6 +96,7 @@ public class InternalDatabase numberOfEntitiesByType.put(EntityType.TRANSACTION, transactions.size()); numberOfEntitiesByType.put(EntityType.TEMPLATE, templates.size()); + numberOfEntitiesByType.put(EntityType.TEMPLATE_GROUP, templateGroups.size()); numberOfEntitiesByType.put(EntityType.IMAGE, images.size()); numberOfEntitiesByType.put(EntityType.CHART, charts.size()); return numberOfEntitiesByType; diff --git a/src/main/java/de/deadlocker8/budgetmaster/services/EntityType.java b/src/main/java/de/deadlocker8/budgetmaster/services/EntityType.java index 28f35c47e..ee0a10e30 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/services/EntityType.java +++ b/src/main/java/de/deadlocker8/budgetmaster/services/EntityType.java @@ -17,7 +17,7 @@ public enum EntityType implements LocalizedEnum IMAGE("image", "background-grey", ImportRequired.REQUIRED), HOTKEYS("keyboard", "background-grey", ImportRequired.NONE), ABOUT("info", "background-grey", ImportRequired.NONE), - TEMPLATE_GROUP("folder", "background-orange-dark", ImportRequired.REQUIRED); + TEMPLATE_GROUP("folder", "background-orange-dark", ImportRequired.OPTIONAL); public enum ImportRequired diff --git a/src/main/java/de/deadlocker8/budgetmaster/services/ImportService.java b/src/main/java/de/deadlocker8/budgetmaster/services/ImportService.java index 95c8612a6..31f395868 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/services/ImportService.java +++ b/src/main/java/de/deadlocker8/budgetmaster/services/ImportService.java @@ -72,7 +72,7 @@ public class ImportService this.iconService = iconService; } - public List<ImportResultItem> importDatabase(InternalDatabase database, AccountMatchList accountMatchList, Boolean importTemplates, Boolean importCharts) + public List<ImportResultItem> importDatabase(InternalDatabase database, AccountMatchList accountMatchList, Boolean importTemplateGroups, Boolean importTemplates, Boolean importCharts) { this.database = database; this.collectedErrorMessages = new ArrayList<>(); @@ -86,14 +86,21 @@ public class ImportService importResultItems.add(importAccounts(accountMatchList)); importResultItems.add(importTransactions()); - if(importTemplates) + if(importTemplateGroups) { importResultItems.add(importTemplateGroups()); - importResultItems.add(importTemplates()); } else { importResultItems.add(new ImportResultItem(EntityType.TEMPLATE_GROUP, 0, 0)); + } + + if(importTemplates) + { + importResultItems.add(importTemplates(importTemplateGroups)); + } + else + { importResultItems.add(new ImportResultItem(EntityType.TEMPLATE, 0, 0)); } @@ -471,7 +478,7 @@ public class ImportService return updatedItems; } - private ImportResultItem importTemplates() + private ImportResultItem importTemplates(Boolean importTemplateGroups) { List<Template> templates = database.getTemplates(); LOGGER.debug(MessageFormat.format("Importing {0} templates...", templates.size())); @@ -486,6 +493,12 @@ public class ImportService LOGGER.debug(MessageFormat.format("Importing template {0}/{1} (templateName: {2})", i + 1, templates.size(), template.getTemplateName())); updateTagsForItem(template); template.setID(null); + + if(!importTemplateGroups) + { + template.setTemplateGroup(null); + } + templateRepository.save(template); numberOfImportedTemplates++; diff --git a/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java b/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java index 988ef3c95..f580603b1 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java @@ -80,6 +80,7 @@ public class SettingsController extends BaseController { prepareBasicModel(model, settingsService.getSettings()); request.removeAttribute("database", RequestAttributes.SCOPE_SESSION); + request.removeAttribute("importTemplatesGroups", RequestAttributes.SCOPE_SESSION); request.removeAttribute("importTemplates", RequestAttributes.SCOPE_SESSION); request.removeAttribute("importCharts", RequestAttributes.SCOPE_SESSION); @@ -316,9 +317,11 @@ public class SettingsController extends BaseController @PostMapping("/database/import/step2") public String importStepTwoPost(WebRequest request, Model model, @RequestParam(value = "TEMPLATE", required = false) boolean importTemplates, + @RequestParam(value = "TEMPLATE_GROUP", required = false) boolean importTemplatesGroups, @RequestParam(value = "CHART", required = false) boolean importCharts) { request.setAttribute("importTemplates", importTemplates, RequestAttributes.SCOPE_SESSION); + request.setAttribute("importTemplateGroups", importTemplatesGroups, RequestAttributes.SCOPE_SESSION); request.setAttribute("importCharts", importCharts, RequestAttributes.SCOPE_SESSION); model.addAttribute("database", request.getAttribute("database", RequestAttributes.SCOPE_SESSION)); @@ -358,12 +361,15 @@ public class SettingsController extends BaseController final Boolean importTemplates = (Boolean) request.getAttribute("importTemplates", RequestAttributes.SCOPE_SESSION); request.removeAttribute("importTemplates", RequestAttributes.SCOPE_SESSION); + final Boolean importTemplateGroups = (Boolean) request.getAttribute("importTemplateGroups", RequestAttributes.SCOPE_SESSION); + request.removeAttribute("importTemplateGroups", RequestAttributes.SCOPE_SESSION); + final Boolean importCharts = (Boolean) request.getAttribute("importCharts", RequestAttributes.SCOPE_SESSION); request.removeAttribute("importCharts", RequestAttributes.SCOPE_SESSION); prepareBasicModel(model, settingsService.getSettings()); - final List<ImportResultItem> importResultItems = importService.importDatabase(database, accountMatchList, importTemplates, importCharts); + final List<ImportResultItem> importResultItems = importService.importDatabase(database, accountMatchList, importTemplateGroups, importTemplates, importCharts); model.addAttribute("importResultItems", importResultItems); model.addAttribute("errorMessages", importService.getCollectedErrorMessages()); diff --git a/src/main/resources/languages/base_de.properties b/src/main/resources/languages/base_de.properties index 0114d98da..73091b2ba 100644 --- a/src/main/resources/languages/base_de.properties +++ b/src/main/resources/languages/base_de.properties @@ -595,14 +595,16 @@ statistics.first.transaction=Erste Buchung {0} entity.account=Konten entity.category=Kategorien entity.transaction=Buchungen +entity.template_group=Vorlagengruppen entity.template=Vorlagen entity.chart=Diagramme entity.image=Bilder import.entity.category=Alle Kategorien werden nacheinander importiert.<br>Wird dabei eine Kategorie mit gleichem Namen und gleicher Farbe in der bestehenden BudgetMaster Datenbank gefunden, so werden alle Buchungen und Vorlagen, die zur Kategorie gehören in diese bestehende Kategorie verschoben.<br>Das Icon der Kategorie wird in diesem Fall nicht angepasst.<br>Wenn keine bestehende Kategorie den gleichen Namen und die gleiche Farbe aufweist, so wird eine neue Kategorie angelegt. -import.entity.account=Jedes zu importierende Konto wird in den weiteren Schritten des Importvorgangs einem Konto in der bestehenden Datenbank zugeordnet.<br>Die zugehörigen Buchungen und Vorlagen werden dann mit dem zugordneten Konto verknüpft.<br>Falls nicht ausreichend Konten oder kein passendes Konto existiert, können neue Konten während des Importvorgangs angelegt werden.<br>Wenn ein zu importierendes Konto ein Icon gesetzt hat, so wird das Icon im bestehenden Konto ersetzt. +import.entity.account=Jedes zu importierende Konto wird in den weiteren Schritten des Importvorgangs einem Konto in der bestehenden Datenbank zugeordnet.<br>Die zugehörigen Buchungen und Vorlagen werden dann mit dem zugeordneten Konto verknüpft.<br>Falls nicht ausreichend Konten oder kein passendes Konto existiert, können neue Konten während des Importvorgangs angelegt werden.<br>Wenn ein zu importierendes Konto ein Icon gesetzt hat, so wird das Icon im bestehenden Konto ersetzt. import.entity.transaction=Alle Buchungen werden nacheinander importiert und dabei dem jeweils verknüpften Konto zugeordnet.<br>Wiederholende Buchungen werden am Ende des Importvorgangs automatisch bis zum aktuellen Datum aktualisiert. -import.entity.template=Alle Vorlagen werden nacheinander importiert und dabei die verknüpften Konten neu zugeordnet.<br>Der Import von Vorlagen ist optional. +import.entity.template_group=Wenn bereits eine Gruppe mit gleichem Namen in der bestehenden BudgetMaster Datenbank existiert, so werden alle Vorlagen, die zur Gruppe gehören in diese bestehende Gruppe verschoben.<br>Wenn keine bestehende Gruppe den gleichen Namen aufweist, so wird eine neue Gruppe angelegt.<br>Der Import von Vorlagengruppen ist optional.<br>Wird der Import deaktiviert, so werden alle Vorlagen, die einer Gruppe zugeordnet sind mit der Standardgruppe verknüpft. +import.entity.template=Alle Vorlagen werden nacheinander importiert und dabei die verknüpften Konten neu zugeordnet.<br>Der Import von Vorlagen ist optional.<br>Vorlagen verlieren ihre Gruppenzuordnung, wenn der Import von Vorlagengruppen deaktiviert wird. import.entity.image=Icons werden automatisch mit den zugehörigen Konten, Vorlagen und Kategorien importiert. import.entity.chart=Es werden nur die benutzerdefinierten Diagramme importiert.<br>Der Import von Diagrammen ist optional. diff --git a/src/main/resources/languages/base_en.properties b/src/main/resources/languages/base_en.properties index 8cef55684..c3c55f683 100644 --- a/src/main/resources/languages/base_en.properties +++ b/src/main/resources/languages/base_en.properties @@ -594,6 +594,7 @@ statistics.first.transaction=First Transaction {0} entity.account=Accounts entity.category=Categories entity.transaction=Transactions +entity.template_group=Template groups entity.template=Templates entity.chart=Charts entity.image=Images @@ -601,7 +602,8 @@ entity.image=Images import.entity.category=All categories will be imported one by one.<br>If a category with the same name and color already exists in the BudgetMaster database, all transactions and templates belonging to the category will be moved to this existing category.<br>The category icon will not be updated in this case.<br>If no existing category has the same name and color, a new category will be created. import.entity.account=Each account to be imported will be assigned to an existing account in the database during the next steps of the import process.<br>The associated transactions and templates will then be linked to the assigned account.<br>If there are not enough accounts or no matching account, new accounts can be created during the import process.<br>If an account has an icon set, the icon in the existing account will be replaced. import.entity.transaction=All transactions will be imported one by one, assigning them to the respective linked account.<br>Recurring transactions are automatically updated to the current date at the end of the import process. -import.entity.template=All templates will be imported one by one, reassigning the linked accounts.<br>The import of templates is optional. +import.entity.template_group=If a group with the same name already exists in the BudgetMaster database, all templates belonging to the group will be moved to this existing group.<br>f no existing group has the same name, a new group will be created.<br>The import of template groups is optional.<br>If the import is deactivated, all templates assigned to a group will be linked to the default group. +import.entity.template=All templates will be imported one by one, reassigning the linked accounts.<br>The import of templates is optional.<br>Templates lose their group assignment when template group import is disabled. import.entity.image=Icons are automatically imported with their associated accounts, templates and categories. import.entity.chart=Only user-defined charts will be imported.<br>The import of charts is optional. diff --git a/src/test/java/de/deadlocker8/budgetmaster/unit/database/DatabaseParser_v8_convertToInternalTest.java b/src/test/java/de/deadlocker8/budgetmaster/unit/database/DatabaseParser_v8_convertToInternalTest.java index 56dd3b010..8cba7d792 100644 --- a/src/test/java/de/deadlocker8/budgetmaster/unit/database/DatabaseParser_v8_convertToInternalTest.java +++ b/src/test/java/de/deadlocker8/budgetmaster/unit/database/DatabaseParser_v8_convertToInternalTest.java @@ -182,7 +182,7 @@ class DatabaseParser_v8_convertToInternalTest template.setIconReference(icon); template.setTags(List.of()); - assertThat(database.getTemplates()).hasSize(4) + assertThat(database.getTemplates()).hasSize(5) .contains(template); assertThat(database.getTemplates().get(3).getIconReference()) .isEqualTo(icon); 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 5bad4b468..35b0ebb6b 100644 --- a/src/test/java/de/deadlocker8/budgetmaster/unit/database/ImportServiceTest.java +++ b/src/test/java/de/deadlocker8/budgetmaster/unit/database/ImportServiceTest.java @@ -515,7 +515,7 @@ class ImportServiceTest Mockito.when(templateGroupRepository.save(Mockito.any())).thenReturn(expectedTemplateGroup); - importService.importDatabase(database, accountMatchList, true, true); + importService.importDatabase(database, accountMatchList, true, true, true); InternalDatabase databaseResult = importService.getDatabase(); // assert @@ -553,7 +553,7 @@ class ImportServiceTest final ChartRepository chartRepositoryMock = Mockito.mock(ChartRepository.class); Mockito.when(chartService.getRepository()).thenReturn(chartRepositoryMock); - importService.importDatabase(database, new AccountMatchList(List.of()), true, true); + importService.importDatabase(database, new AccountMatchList(List.of()), true, true, true); InternalDatabase databaseResult = importService.getDatabase(); // assert @@ -596,7 +596,7 @@ class ImportServiceTest Mockito.when(imageRepositoryMock.save(Mockito.any())).thenReturn(newImage); InternalDatabase database = new InternalDatabase(List.of(), List.of(), List.of(), List.of(), List.of(), List.of(), List.of(image), List.of()); - importService.importDatabase(database, new AccountMatchList(List.of()), true, true); + importService.importDatabase(database, new AccountMatchList(List.of()), true, true, true); Image expectedImage = new Image(image.getImage(), image.getFileName(), image.getFileExtension()); Mockito.verify(imageRepositoryMock, Mockito.atLeast(1)).save(expectedImage); @@ -616,7 +616,7 @@ class ImportServiceTest Mockito.when(imageRepositoryMock.save(Mockito.any())).thenReturn(newImage); InternalDatabase database = new InternalDatabase(List.of(), List.of(), List.of(), List.of(), List.of(), List.of(), List.of(image), List.of()); - importService.importDatabase(database, new AccountMatchList(List.of()), true, true); + importService.importDatabase(database, new AccountMatchList(List.of()), true, true, true); Image expectedImage = new Image(image.getImage(), image.getFileName(), image.getFileExtension()); Mockito.verify(imageRepositoryMock, Mockito.atLeast(1)).save(expectedImage); @@ -655,7 +655,7 @@ class ImportServiceTest Mockito.when(iconService.getRepository()).thenReturn(iconRepositoryMock); Mockito.when(iconRepositoryMock.save(Mockito.any())).thenReturn(expectedIcon); - importService.importDatabase(database, new AccountMatchList(List.of(accountMatch)), true, true); + importService.importDatabase(database, new AccountMatchList(List.of(accountMatch)), true,true, true); Mockito.verify(accountRepository, Mockito.atLeast(1)).save(expectedAccount); } @@ -663,8 +663,6 @@ class ImportServiceTest @Test void test_skipTemplates() { - TemplateGroup templateGroup = new TemplateGroup(1, "My Template Group", TemplateGroupType.CUSTOM); - Template template = new Template(); template.setTemplateName("myTemplate"); template.setAmount(200); @@ -672,14 +670,40 @@ class ImportServiceTest template.setTags(new ArrayList<>()); // database - InternalDatabase database = new InternalDatabase(List.of(), List.of(), List.of(), List.of(templateGroup), List.of(template), List.of(), List.of(), List.of()); + InternalDatabase database = new InternalDatabase(List.of(), List.of(), List.of(), List.of(), List.of(template), List.of(), List.of(), List.of()); // act - importService.importDatabase(database, new AccountMatchList(List.of()), false, true); + importService.importDatabase(database, new AccountMatchList(List.of()), true, false, true); // assert Mockito.verify(templateRepository, Mockito.never()).save(Mockito.any()); + } + + @Test + void test_skipTemplateGroups() + { + TemplateGroup templateGroup = new TemplateGroup(1, "My Template Group", TemplateGroupType.CUSTOM); + + Template templateWithGroup = new Template(); + templateWithGroup.setTemplateName("myTemplate"); + templateWithGroup.setTags(new ArrayList<>()); + templateWithGroup.setTemplateGroup(templateGroup); + + // database + InternalDatabase database = new InternalDatabase(List.of(), List.of(), List.of(), List.of(templateGroup), List.of(templateWithGroup), List.of(), List.of(), List.of()); + + // act + importService.importDatabase(database, new AccountMatchList(List.of()), false, true, true); + + // assert Mockito.verify(templateGroupRepository, Mockito.never()).save(Mockito.any()); + + Template expectedTemplate = new Template(); + expectedTemplate.setTemplateName("myTemplate"); + expectedTemplate.setTags(new ArrayList<>()); + expectedTemplate.setTemplateGroup(null); + + Mockito.verify(templateRepository, Mockito.atLeast(1)).save(expectedTemplate); } @Test @@ -699,7 +723,7 @@ class ImportServiceTest Mockito.when(chartService.getRepository()).thenReturn(chartRepositoryMock); // act - importService.importDatabase(database, new AccountMatchList(List.of()), true, false); + importService.importDatabase(database, new AccountMatchList(List.of()), true, true, false); // assert Mockito.verify(chartRepositoryMock, Mockito.never()).save(Mockito.any()); @@ -719,7 +743,7 @@ class ImportServiceTest Mockito.when(categoryRepository.findByNameAndColorAndType(Mockito.eq("Category2"), Mockito.any(), Mockito.any())).thenReturn(category2); InternalDatabase database = new InternalDatabase(List.of(category1, category2), List.of(), List.of(), List.of(), List.of(), List.of(), List.of(), List.of()); - final List<ImportResultItem> importResultItems = importService.importDatabase(database, new AccountMatchList(List.of()), false, false); + final List<ImportResultItem> importResultItems = importService.importDatabase(database, new AccountMatchList(List.of()), false, false, false); assertThat(importResultItems).hasSize(7) .contains(new ImportResultItem(EntityType.CATEGORY, 1, 2)); @@ -788,7 +812,7 @@ class ImportServiceTest Mockito.when(templateGroupRepository.save(Mockito.any())).thenReturn(newTemplateGroup); InternalDatabase database = new InternalDatabase(List.of(), List.of(), List.of(), List.of(templateGroup), List.of(), List.of(), List.of(), List.of()); - importService.importDatabase(database, new AccountMatchList(List.of()), true, true); + importService.importDatabase(database, new AccountMatchList(List.of()), true, true, true); TemplateGroup expectedTemplateGroup = new TemplateGroup(templateGroup.getName(), templateGroup.getType()); Mockito.verify(templateGroupRepository, Mockito.atLeast(1)).save(expectedTemplateGroup); @@ -802,7 +826,7 @@ class ImportServiceTest Mockito.when(templateGroupRepository.findFirstByType(TemplateGroupType.DEFAULT)).thenReturn(templateGroup); InternalDatabase database = new InternalDatabase(List.of(), List.of(), List.of(), List.of(templateGroup), List.of(), List.of(), List.of(), List.of()); - importService.importDatabase(database, new AccountMatchList(List.of()), true, true); + importService.importDatabase(database, new AccountMatchList(List.of()), true, true, true); Mockito.verify(templateGroupRepository, Mockito.never()).save(Mockito.any()); } -- GitLab