From 5cc923e567616670285b9c2af2a739226c97518e Mon Sep 17 00:00:00 2001 From: Robert Goldmann <deadlocker@gmx.de> Date: Thu, 31 Mar 2022 21:02:23 +0200 Subject: [PATCH] Fixed #689 - transactions from hidden accounts are now longer shown if "all accounts" are selected --- .../TransactionSpecifications.java | 8 +- .../unit/TransactionSpecificationsTest.java | 18 +- .../unit/database/ImportServiceTest.java | 299 +-- src/test/resources/ImportServiceTest.json | 2240 +++++++++++++++++ 4 files changed, 2360 insertions(+), 205 deletions(-) create mode 100644 src/test/resources/ImportServiceTest.json diff --git a/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionSpecifications.java b/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionSpecifications.java index 3b2931cc8..a1bf41eb9 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionSpecifications.java +++ b/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionSpecifications.java @@ -1,6 +1,7 @@ package de.deadlocker8.budgetmaster.transactions; import de.deadlocker8.budgetmaster.accounts.Account; +import de.deadlocker8.budgetmaster.accounts.AccountState; import de.deadlocker8.budgetmaster.tags.Tag; import de.deadlocker8.budgetmaster.tags.Tag_; import org.springframework.data.jpa.domain.Specification; @@ -125,7 +126,12 @@ public class TransactionSpecifications final Predicate predicatesCombined = combinePredicates(predicates, builder); Predicate generalPredicates = builder.and(dateConstraint, predicatesCombined); - if(account != null) + if(account == null) + { + Predicate accountPredicate = transaction.get(Transaction_.account).get("accountState").in(List.of(AccountState.FULL_ACCESS, AccountState.READ_ONLY)); + generalPredicates = builder.and(generalPredicates, accountPredicate); + } + else { Predicate accountPredicate = builder.equal(transaction.get(Transaction_.account), account); generalPredicates = builder.and(generalPredicates, accountPredicate); diff --git a/src/test/java/de/deadlocker8/budgetmaster/unit/TransactionSpecificationsTest.java b/src/test/java/de/deadlocker8/budgetmaster/unit/TransactionSpecificationsTest.java index 65e5c8871..4e60acd95 100644 --- a/src/test/java/de/deadlocker8/budgetmaster/unit/TransactionSpecificationsTest.java +++ b/src/test/java/de/deadlocker8/budgetmaster/unit/TransactionSpecificationsTest.java @@ -432,9 +432,23 @@ class TransactionSpecificationsTest Specification spec = TransactionSpecifications.withDynamicQuery(startDate, endDate, null, true, true, false, null, List.of(), List.of(), null); List<Transaction> results = transactionRepository.findAll(spec); - assertThat(results).hasSize(3) + assertThat(results).hasSize(2) + .contains(transaction1) + .contains(repeatingTransaction); + } + + @Test + void getFromAllAccountsExceptHidden() + { + Specification spec = TransactionSpecifications.withDynamicQuery(startDate, LocalDate.now(), null, true, true, true, null, List.of(), List.of(), null); + + List<Transaction> results = transactionRepository.findAll(spec); + assertThat(results).hasSize(6) .contains(transaction1) + .contains(transaction2) + .contains(transaction3) .contains(repeatingTransaction) - .contains(transactionInHiddenAccount); + .contains(transferTransaction) + .contains(transferTransactionWrongAccount); } } \ No newline at end of file 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 e24535d2f..321d6d8d0 100644 --- a/src/test/java/de/deadlocker8/budgetmaster/unit/database/ImportServiceTest.java +++ b/src/test/java/de/deadlocker8/budgetmaster/unit/database/ImportServiceTest.java @@ -1,292 +1,187 @@ package de.deadlocker8.budgetmaster.unit.database; +import de.deadlocker8.budgetmaster.Main; import de.deadlocker8.budgetmaster.accounts.Account; import de.deadlocker8.budgetmaster.accounts.AccountRepository; +import de.deadlocker8.budgetmaster.accounts.AccountState; 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.charts.Chart; import de.deadlocker8.budgetmaster.charts.ChartRepository; import de.deadlocker8.budgetmaster.charts.ChartService; import de.deadlocker8.budgetmaster.charts.ChartType; +import de.deadlocker8.budgetmaster.database.DatabaseParser; import de.deadlocker8.budgetmaster.database.InternalDatabase; import de.deadlocker8.budgetmaster.database.accountmatches.AccountMatch; 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.ImageService; import de.deadlocker8.budgetmaster.repeating.RepeatingTransactionUpdater; import de.deadlocker8.budgetmaster.services.ImportService; -import de.deadlocker8.budgetmaster.tags.Tag; import de.deadlocker8.budgetmaster.tags.TagRepository; import de.deadlocker8.budgetmaster.templategroup.TemplateGroup; import de.deadlocker8.budgetmaster.templategroup.TemplateGroupRepository; import de.deadlocker8.budgetmaster.templategroup.TemplateGroupType; import de.deadlocker8.budgetmaster.templates.Template; import de.deadlocker8.budgetmaster.templates.TemplateRepository; -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.mockito.InjectMocks; -import org.mockito.Mock; import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.time.LocalDate; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; -@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = Main.class) class ImportServiceTest { - @Mock + @Autowired private CategoryRepository categoryRepository; - @Mock + @Autowired private TransactionRepository transactionRepository; - @Mock + @Autowired private TagRepository tagRepository; - @Mock + @Autowired private TemplateGroupRepository templateGroupRepository; - @Mock + @Autowired private TemplateRepository templateRepository; - @Mock + @Autowired private ChartService chartService; - @Mock + @Autowired private ImageService imageService; - @Mock + @Autowired private RepeatingTransactionUpdater repeatingTransactionUpdater; - @Mock + @Autowired private AccountRepository accountRepository; - @Mock + @Autowired private IconService iconService; - @InjectMocks + @Autowired private ImportService importService; @Test - void test_importFullDatabase() + void test_importFullDatabase() throws URISyntaxException, IOException { + final Path jsonPath = Paths.get(getClass().getClassLoader().getResource("ImportServiceTest.json").toURI()); + final String fileContent = Files.readString(jsonPath, StandardCharsets.UTF_8); + final DatabaseParser parser = new DatabaseParser(fileContent); + final InternalDatabase importedDatabase = parser.parseDatabaseFromJSON(); + // source accounts - Account sourceAccount1 = new Account("Source_Account_1", AccountType.CUSTOM); + final Account sourceAccount1 = new Account("Source_Account_1", AccountType.CUSTOM); sourceAccount1.setID(2); - Account sourceAccount2 = new Account("Source_Account_2", AccountType.CUSTOM); + final Account sourceAccount2 = new Account("Source_Account_2", AccountType.CUSTOM); sourceAccount2.setID(3); - List<Account> accounts = new ArrayList<>(); - accounts.add(sourceAccount1); - accounts.add(sourceAccount2); - // destination accounts Account destAccount1 = new Account("Destination_Account_1", AccountType.CUSTOM); - destAccount1.setID(5); + destAccount1.setAccountState(AccountState.FULL_ACCESS); + destAccount1 = accountRepository.save(destAccount1); Account destAccount2 = new Account("Destination_Account_2", AccountType.CUSTOM); - destAccount2.setID(2); - - // tags - Tag tag1 = new Tag("Car"); - List<Tag> tags = new ArrayList<>(); - tags.add(tag1); - - // categories - Category category1 = new Category("Category1", "#ff0000", CategoryType.CUSTOM); - Category category2 = new Category("Category2", "#00ffff", CategoryType.CUSTOM); - List<Category> categories = List.of(category1, category2); - - // transactions - List<Transaction> transactions = new ArrayList<>(); - Transaction transaction1 = new Transaction(); - transaction1.setAccount(sourceAccount1); - transaction1.setName("ShouldGoInAccount_1"); - transaction1.setAmount(200); - transaction1.setDate(LocalDate.of(2018, 10, 3)); - transaction1.setTags(tags); - transaction1.setCategory(category1); - transactions.add(transaction1); - - Transaction transaction2 = new Transaction(); - transaction2.setAccount(sourceAccount1); - transaction2.setName("ShouldGoInAccount_1_Too"); - transaction2.setAmount(100); - transaction2.setDate(LocalDate.of(2018, 10, 3)); - transaction2.setTags(tags); - transaction2.setCategory(category1); - transactions.add(transaction2); - - Transaction transaction3 = new Transaction(); - transaction3.setAccount(sourceAccount2); - transaction3.setName("ImPartOfAccount_2"); - transaction3.setAmount(-525); - transaction3.setDate(LocalDate.of(2018, 10, 3)); - transaction3.setTags(new ArrayList<>()); - transaction3.setCategory(category2); - transactions.add(transaction3); - - // template group - TemplateGroup templateGroup = new TemplateGroup(1, "My Template Group", TemplateGroupType.CUSTOM); - - // templates - Template template1 = new Template(); - template1.setTemplateName("MyTemplate"); - template1.setAmount(1500); - template1.setAccount(sourceAccount1); - template1.setName("Transaction from Template"); - template1.setCategory(category1); - List<Tag> tags2 = new ArrayList<>(); - tags2.add(tag1); - template1.setTags(tags2); - - Icon icon1 = new Icon("fas fa-icons"); - icon1.setID(12); - template1.setIconReference(icon1); - - Template template2 = new Template(); - template2.setTemplateName("MyTemplate2"); - template2.setTransferAccount(sourceAccount2); - template2.setCategory(category1); - template2.setTags(new ArrayList<>()); - - Template template3 = new Template(); - template3.setTemplateName("MyTemplate3"); - template3.setTransferAccount(sourceAccount1); - template3.setTags(new ArrayList<>()); - - List<Template> templates = new ArrayList<>(); - templates.add(template1); - templates.add(template2); - templates.add(template3); - - // charts - Chart chart = new Chart(); - chart.setID(9); - chart.setName("The best chart"); - chart.setType(ChartType.CUSTOM); - chart.setVersion(7); - chart.setScript("/* This list will be dynamically filled with all the transactions between\r\n* the start and and date you select on the \"Show Chart\" page\r\n* and filtered according to your specified filter.\r\n* An example entry for this list and tutorial about how to create custom charts ca be found in the BudgetMaster wiki:\r\n* https://github.com/deadlocker8/BudgetMaster/wiki/How-to-create-custom-charts\r\n*/\r\nvar transactionData \u003d [];\r\n\r\n// Prepare your chart settings here (mandatory)\r\nvar plotlyData \u003d [{\r\n x: [],\r\n y: [],\r\n type: \u0027bar\u0027\r\n}];\r\n\r\n// Add your Plotly layout settings here (optional)\r\nvar plotlyLayout \u003d {};\r\n\r\n// Add your Plotly configuration settings here (optional)\r\nvar plotlyConfig \u003d {\r\n showSendToCloud: false,\r\n displaylogo: false,\r\n showLink: false,\r\n responsive: true\r\n};\r\n\r\n// Don\u0027t touch this line\r\nPlotly.newPlot(\"containerID\", plotlyData, plotlyLayout, plotlyConfig);\r\n"); - - // database - InternalDatabase database = new InternalDatabase(categories, accounts, transactions, List.of(templateGroup), templates, List.of(chart), List.of(), List.of(icon1)); + destAccount2.setAccountState(AccountState.FULL_ACCESS); + destAccount2 = accountRepository.save(destAccount2); // account matches - AccountMatch match1 = new AccountMatch(sourceAccount1); + final AccountMatch match1 = new AccountMatch(sourceAccount1); match1.setAccountDestination(destAccount1); - AccountMatch match2 = new AccountMatch(sourceAccount2); + final AccountMatch match2 = new AccountMatch(sourceAccount2); match2.setAccountDestination(destAccount2); - List<AccountMatch> matches = new ArrayList<>(); - matches.add(match1); - matches.add(match2); - - AccountMatchList accountMatchList = new AccountMatchList(matches); - - // expected - Category expectedCategory1 = new Category("Category1", "#ff0000", CategoryType.CUSTOM); - expectedCategory1.setID(1); - Category expectedCategory2 = new Category("Category2", "#00ffff", CategoryType.CUSTOM); - expectedCategory2.setID(2); - - Transaction expectedTransaction1 = new Transaction(); - expectedTransaction1.setAccount(destAccount1); - expectedTransaction1.setName("ShouldGoInAccount_1"); - expectedTransaction1.setAmount(200); - expectedTransaction1.setDate(LocalDate.of(2018, 10, 3)); - expectedTransaction1.setTags(tags); - - Transaction expectedTransaction2 = new Transaction(); - expectedTransaction2.setAccount(destAccount1); - expectedTransaction2.setName("ShouldGoInAccount_1_Too"); - expectedTransaction2.setAmount(100); - expectedTransaction2.setDate(LocalDate.of(2018, 10, 3)); - expectedTransaction2.setTags(tags); - - Transaction expectedTransaction3 = new Transaction(); - expectedTransaction3.setAccount(destAccount2); - expectedTransaction3.setName("ImPartOfAccount_2"); - expectedTransaction3.setAmount(-525); - expectedTransaction3.setDate(LocalDate.of(2018, 10, 3)); - expectedTransaction3.setTags(new ArrayList<>()); - - Template expectedTemplate1 = new Template(); - expectedTemplate1.setTemplateName("MyTemplate"); - expectedTemplate1.setAmount(1500); - expectedTemplate1.setAccount(destAccount1); - expectedTemplate1.setName("Transaction from Template"); - List<Tag> expectedTemplateTags = new ArrayList<>(); - expectedTemplateTags.add(tag1); - expectedTemplate1.setTags(expectedTemplateTags); - - Icon expectedIcon = new Icon("fas fa-icons"); - expectedIcon.setID(28); - expectedTemplate1.setIconReference(expectedIcon); - - Template expectedTemplate2 = new Template(); - expectedTemplate2.setTemplateName("MyTemplate2"); - expectedTemplate2.setTransferAccount(destAccount2); - expectedTemplate2.setTags(new ArrayList<>()); - - Template expectedTemplate3 = new Template(); - expectedTemplate3.setTemplateName("MyTemplate3"); - expectedTemplate3.setTransferAccount(destAccount1); - expectedTemplate3.setTags(new ArrayList<>()); - - TemplateGroup expectedTemplateGroup = new TemplateGroup(5, "My Template Group", TemplateGroupType.CUSTOM); + final List<AccountMatch> matches = List.of(match1, match2); + final AccountMatchList accountMatchList = new AccountMatchList(matches); // act - Mockito.when(categoryRepository.save(category1)).thenReturn(expectedCategory1); - Mockito.when(categoryRepository.save(category2)).thenReturn(expectedCategory2); + importService.importDatabase(importedDatabase, accountMatchList, true, true, true); + final InternalDatabase databaseResult = importService.getDatabase(); - Mockito.when(tagRepository.save(Mockito.any(Tag.class))).thenReturn(tag1); + final Image image = new Image(new Byte[0], "awesomeIcon.png", ImageFileExtension.PNG); + image.setID(1); + + assertThat(imageService.getRepository().findAll()) + .hasSize(1) + .containsExactly(image); - Mockito.when(chartService.getHighestUsedID()).thenReturn(8); - final ChartRepository chartRepositoryMock = Mockito.mock(ChartRepository.class); - Mockito.when(chartService.getRepository()).thenReturn(chartRepositoryMock); - Mockito.when(accountRepository.findById(5)).thenReturn(Optional.of(destAccount1)); - Mockito.when(accountRepository.findById(2)).thenReturn(Optional.of(destAccount2)); + assertThat(iconService.getRepository().findAll()) + .hasSize(12) + .containsExactlyInAnyOrder(createIcon(1, "fas fa-landmark", null, null), + createIcon(3, null, null, null), + createIcon(4, null, null, null), + createIcon(6, "fas fa-ban", "#2eb952ff", null), + createIcon(7, null, null, image), // + createIcon(8, null, "#2e79b9ff", null), + createIcon(11, "fas fa-ambulance", null, null), + createIcon(12, null, null, image), // + createIcon(13, "fas fa-battery-three-quarters", "#e34f4fff", null), + createIcon(15, null, "#212121ff", null), + createIcon(17, "fas fa-award", "#212121ff", null), + createIcon(18, null, null, image)); - IconRepository iconRepositoryMock = Mockito.mock(IconRepository.class); - Mockito.when(iconService.getRepository()).thenReturn(iconRepositoryMock); - Mockito.when(iconRepositoryMock.save(Mockito.any())).thenReturn(expectedIcon); - Mockito.when(templateGroupRepository.save(Mockito.any())).thenReturn(expectedTemplateGroup); +// new Category("No Category", "#FFFFFF", CategoryType.NONE, ) - importService.importDatabase(database, accountMatchList, true, true, true); - InternalDatabase databaseResult = importService.getDatabase(); // assert - assertThat(databaseResult.getCategories()) - .hasSize(2) - .contains(expectedCategory1, expectedCategory2); - assertThat(databaseResult.getTransactions()) - .hasSize(3) - .contains(expectedTransaction1, expectedTransaction2, expectedTransaction3); - assertThat(databaseResult.getTemplateGroups()) - .hasSize(1) - .contains(templateGroup); - assertThat(databaseResult.getTemplates()) - .hasSize(3) - .contains(expectedTemplate1, expectedTemplate2, expectedTemplate3); - assertThat(databaseResult.getCharts()) - .hasSize(1) - .contains(chart); +// assertThat(categoryRepository.findAll()) +// .hasSize(4) +// .contains(expectedCategory1, expectedCategory2); +// assertThat(databaseResult.getTransactions()) +// .hasSize(3) +// .contains(expectedTransaction1, expectedTransaction2, expectedTransaction3); +// assertThat(databaseResult.getTemplateGroups()) +// .hasSize(1) +// .contains(expectedTemplateGroup); +// assertThat(databaseResult.getTemplates()) +// .hasSize(3) +// .contains(expectedTemplate1, expectedTemplate2, expectedTemplate3); +// assertThat(databaseResult.getCharts()) +// .hasSize(1) +// .contains(expectedC); assertThat(importService.getCollectedErrorMessages()).isEmpty(); } + private Icon createIcon(int ID, String builtinIdentifier, String fontColor, Image image) + { + Icon icon; + if(image == null) + { + icon = new Icon(builtinIdentifier, fontColor); + } + else + { + icon = new Icon(image); + } + + icon.setID(ID); + return icon; + } + @Test void test_skipTemplates() { diff --git a/src/test/resources/ImportServiceTest.json b/src/test/resources/ImportServiceTest.json new file mode 100644 index 000000000..09246e138 --- /dev/null +++ b/src/test/resources/ImportServiceTest.json @@ -0,0 +1,2240 @@ +{ + "TYPE": "BUDGETMASTER_DATABASE", + "VERSION": 8, + "categories": [ + { + "ID": 3, + "name": "Car", + "color": "#007afa", + "type": "CUSTOM", + "iconReferenceID": 11 + }, + { + "ID": 1, + "name": "No Category", + "color": "#FFFFFF", + "type": "NONE", + "iconReferenceID": 3 + }, + { + "ID": 4, + "name": "Rent", + "color": "#eeeeee", + "type": "CUSTOM", + "iconReferenceID": 12 + }, + { + "ID": 2, + "name": "Rest", + "color": "#FFFF00", + "type": "REST", + "iconReferenceID": 4 + } + ], + "accounts": [ + { + "ID": 1, + "name": "Placeholder", + "accountState": "FULL_ACCESS", + "type": "ALL", + "iconReferenceID": 1 + }, + { + "ID": 2, + "name": "Default Account", + "accountState": "FULL_ACCESS", + "type": "CUSTOM", + "iconReferenceID": 7 + }, + { + "ID": 3, + "name": "Read-only account", + "accountState": "READ_ONLY", + "type": "CUSTOM", + "iconReferenceID": 6 + }, + { + "ID": 4, + "name": "Second Account", + "accountState": "FULL_ACCESS", + "type": "CUSTOM", + "iconReferenceID": 8 + } + ], + "transactions": [ + { + "amount": -1100, + "isExpenditure": true, + "date": "2022-03-30", + "accountID": 2, + "categoryID": 3, + "name": "normal transaction", + "description": "Lorem Ipsum dolor", + "tags": [ + { + "name": "0815" + } + ] + }, + { + "amount": -100, + "isExpenditure": true, + "date": "2022-03-09", + "accountID": 2, + "categoryID": 1, + "name": "Repeating transaction", + "description": "", + "tags": [], + "repeatingOption": { + "startDate": "2022-03-09", + "modifier": { + "quantity": 1, + "localizationKey": "repeating.modifier.days" + }, + "endOption": { + "localizationKey": "repeating.end.key.afterXTimes", + "times": 3 + } + } + }, + { + "amount": -1600, + "isExpenditure": true, + "date": "2022-03-30", + "accountID": 2, + "categoryID": 4, + "name": "Transfer", + "description": "", + "tags": [ + { + "name": "12" + } + ], + "transferAccountID": 4 + }, + { + "amount": -200, + "isExpenditure": true, + "date": "2022-03-11", + "accountID": 2, + "categoryID": 3, + "name": "Repeating transfer", + "description": "", + "tags": [], + "repeatingOption": { + "startDate": "2022-03-11", + "modifier": { + "quantity": 1, + "localizationKey": "repeating.modifier.days" + }, + "endOption": { + "localizationKey": "repeating.end.key.afterXTimes", + "times": 2 + } + }, + "transferAccountID": 4 + }, + { + "amount": -2036, + "isExpenditure": true, + "date": "2022-03-30", + "accountID": 2, + "categoryID": 1, + "name": "Transactions with tags", + "description": "", + "tags": [ + { + "name": "12" + }, + { + "name": "13" + } + ] + } + ], + "templateGroups": [ + { + "ID": 1, + "name": "Not grouped", + "type": "DEFAULT" + }, + { + "ID": 2, + "name": "Enter the group!", + "type": "CUSTOM" + }, + { + "ID": 3, + "name": "Group 2.0", + "type": "CUSTOM" + } + ], + "templates": [ + { + "templateName": "Full template", + "amount": -1200, + "isExpenditure": true, + "accountID": 2, + "categoryID": 3, + "name": "My awesome transaction", + "description": "Lorem Ipsum", + "iconReferenceID": 13, + "tags": [ + { + "name": "0815" + }, + { + "name": "12" + } + ], + "transferAccountID": 4, + "templateGroupID": 3 + }, + { + "templateName": "Template with tags", + "isExpenditure": true, + "categoryID": 1, + "name": "", + "description": "", + "iconReferenceID": 18, + "tags": [ + { + "name": "12" + }, + { + "name": "13" + } + ], + "templateGroupID": 3 + }, + { + "templateName": "Ungrouped template", + "isExpenditure": true, + "categoryID": 1, + "name": "", + "description": "", + "iconReferenceID": 15, + "tags": [], + "templateGroupID": 1 + }, + { + "templateName": "Random template", + "isExpenditure": true, + "categoryID": 1, + "name": "", + "description": "", + "iconReferenceID": 17, + "tags": [], + "templateGroupID": 2 + } + ], + "charts": [ + { + "ID": 13, + "name": "Custom chart", + "script": "/* This list will be dynamically filled with all the transactions between\r\n* the start and and date you select on the \"Show Chart\" page\r\n* and filtered according to your specified filter.\r\n* An example entry for this list and tutorial about how to create custom charts ca be found in the BudgetMaster wiki:\r\n* https://github.com/deadlocker8/BudgetMaster/wiki/How-to-create-custom-charts\r\n*/\r\nvar transactionData \u003d [];\r\n\r\n// Prepare your chart settings here (mandatory)\r\nvar plotlyData \u003d [{\r\n x: [],\r\n y: [],\r\n type: \u0027bar\u0027\r\n}];\r\n\r\n// Add your Plotly layout settings here (optional)\r\nvar plotlyLayout \u003d {};\r\n\r\n// Add your Plotly configuration settings here (optional)\r\nvar plotlyConfig \u003d {\r\n showSendToCloud: false,\r\n displaylogo: false,\r\n showLink: false,\r\n responsive: true\r\n};\r\n\r\nconsole.log(\"dummy\");\r\n\r\n// Don\u0027t touch this line\r\nPlotly.newPlot(\"containerID\", plotlyData, plotlyLayout, plotlyConfig);", + "type": "CUSTOM", + "version": 0 + } + ], + "images": [ + { + "ID": 1, + "image": [ + 60, + 63, + 120, + 109, + 108, + 32, + 118, + 101, + 114, + 115, + 105, + 111, + 110, + 61, + 34, + 49, + 46, + 48, + 34, + 32, + 101, + 110, + 99, + 111, + 100, + 105, + 110, + 103, + 61, + 34, + 85, + 84, + 70, + 45, + 56, + 34, + 32, + 115, + 116, + 97, + 110, + 100, + 97, + 108, + 111, + 110, + 101, + 61, + 34, + 110, + 111, + 34, + 63, + 62, + 60, + 33, + 45, + 45, + 32, + 71, + 101, + 110, + 101, + 114, + 97, + 116, + 111, + 114, + 58, + 32, + 71, + 114, + 97, + 118, + 105, + 116, + 46, + 105, + 111, + 32, + 45, + 45, + 62, + 60, + 115, + 118, + 103, + 32, + 120, + 109, + 108, + 110, + 115, + 61, + 34, + 104, + 116, + 116, + 112, + 58, + 47, + 47, + 119, + 119, + 119, + 46, + 119, + 51, + 46, + 111, + 114, + 103, + 47, + 50, + 48, + 48, + 48, + 47, + 115, + 118, + 103, + 34, + 32, + 120, + 109, + 108, + 110, + 115, + 58, + 120, + 108, + 105, + 110, + 107, + 61, + 34, + 104, + 116, + 116, + 112, + 58, + 47, + 47, + 119, + 119, + 119, + 46, + 119, + 51, + 46, + 111, + 114, + 103, + 47, + 49, + 57, + 57, + 57, + 47, + 120, + 108, + 105, + 110, + 107, + 34, + 32, + 115, + 116, + 121, + 108, + 101, + 61, + 34, + 105, + 115, + 111, + 108, + 97, + 116, + 105, + 111, + 110, + 58, + 105, + 115, + 111, + 108, + 97, + 116, + 101, + 34, + 32, + 118, + 105, + 101, + 119, + 66, + 111, + 120, + 61, + 34, + 48, + 32, + 48, + 32, + 49, + 50, + 49, + 56, + 32, + 49, + 50, + 49, + 56, + 34, + 32, + 119, + 105, + 100, + 116, + 104, + 61, + 34, + 49, + 50, + 49, + 56, + 112, + 116, + 34, + 32, + 104, + 101, + 105, + 103, + 104, + 116, + 61, + 34, + 49, + 50, + 49, + 56, + 112, + 116, + 34, + 62, + 60, + 100, + 101, + 102, + 115, + 62, + 60, + 99, + 108, + 105, + 112, + 80, + 97, + 116, + 104, + 32, + 105, + 100, + 61, + 34, + 95, + 99, + 108, + 105, + 112, + 80, + 97, + 116, + 104, + 95, + 114, + 87, + 116, + 106, + 48, + 114, + 116, + 117, + 89, + 98, + 68, + 89, + 79, + 52, + 71, + 107, + 119, + 115, + 84, + 118, + 80, + 50, + 111, + 78, + 119, + 100, + 52, + 111, + 67, + 121, + 108, + 82, + 34, + 62, + 60, + 114, + 101, + 99, + 116, + 32, + 119, + 105, + 100, + 116, + 104, + 61, + 34, + 49, + 50, + 49, + 56, + 34, + 32, + 104, + 101, + 105, + 103, + 104, + 116, + 61, + 34, + 49, + 50, + 49, + 56, + 34, + 47, + 62, + 60, + 47, + 99, + 108, + 105, + 112, + 80, + 97, + 116, + 104, + 62, + 60, + 47, + 100, + 101, + 102, + 115, + 62, + 60, + 103, + 32, + 99, + 108, + 105, + 112, + 45, + 112, + 97, + 116, + 104, + 61, + 34, + 117, + 114, + 108, + 40, + 35, + 95, + 99, + 108, + 105, + 112, + 80, + 97, + 116, + 104, + 95, + 114, + 87, + 116, + 106, + 48, + 114, + 116, + 117, + 89, + 98, + 68, + 89, + 79, + 52, + 71, + 107, + 119, + 115, + 84, + 118, + 80, + 50, + 111, + 78, + 119, + 100, + 52, + 111, + 67, + 121, + 108, + 82, + 41, + 34, + 62, + 60, + 112, + 97, + 116, + 104, + 32, + 100, + 61, + 34, + 77, + 32, + 50, + 52, + 53, + 46, + 55, + 53, + 32, + 56, + 48, + 32, + 76, + 32, + 57, + 55, + 50, + 46, + 50, + 53, + 32, + 56, + 48, + 32, + 67, + 32, + 57, + 57, + 57, + 46, + 50, + 57, + 52, + 32, + 56, + 48, + 32, + 49, + 48, + 50, + 49, + 46, + 50, + 53, + 32, + 49, + 48, + 49, + 46, + 57, + 53, + 54, + 32, + 49, + 48, + 50, + 49, + 46, + 50, + 53, + 32, + 49, + 50, + 57, + 32, + 76, + 32, + 49, + 48, + 50, + 49, + 46, + 50, + 53, + 32, + 49, + 49, + 54, + 53, + 32, + 67, + 32, + 49, + 48, + 50, + 49, + 46, + 50, + 53, + 32, + 49, + 49, + 57, + 50, + 46, + 48, + 52, + 52, + 32, + 57, + 57, + 57, + 46, + 50, + 57, + 52, + 32, + 49, + 50, + 49, + 52, + 32, + 57, + 55, + 50, + 46, + 50, + 53, + 32, + 49, + 50, + 49, + 52, + 32, + 76, + 32, + 50, + 52, + 53, + 46, + 55, + 53, + 32, + 49, + 50, + 49, + 52, + 32, + 67, + 32, + 50, + 49, + 56, + 46, + 55, + 48, + 54, + 32, + 49, + 50, + 49, + 52, + 32, + 49, + 57, + 54, + 46, + 55, + 53, + 32, + 49, + 49, + 57, + 50, + 46, + 48, + 52, + 52, + 32, + 49, + 57, + 54, + 46, + 55, + 53, + 32, + 49, + 49, + 54, + 53, + 32, + 76, + 32, + 49, + 57, + 54, + 46, + 55, + 53, + 32, + 49, + 50, + 57, + 32, + 67, + 32, + 49, + 57, + 54, + 46, + 55, + 53, + 32, + 49, + 48, + 49, + 46, + 57, + 53, + 54, + 32, + 50, + 49, + 56, + 46, + 55, + 48, + 54, + 32, + 56, + 48, + 32, + 50, + 52, + 53, + 46, + 55, + 53, + 32, + 56, + 48, + 32, + 90, + 34, + 32, + 115, + 116, + 121, + 108, + 101, + 61, + 34, + 115, + 116, + 114, + 111, + 107, + 101, + 58, + 110, + 111, + 110, + 101, + 59, + 102, + 105, + 108, + 108, + 58, + 35, + 50, + 69, + 55, + 57, + 66, + 57, + 59, + 115, + 116, + 114, + 111, + 107, + 101, + 45, + 109, + 105, + 116, + 101, + 114, + 108, + 105, + 109, + 105, + 116, + 58, + 49, + 48, + 59, + 34, + 47, + 62, + 60, + 114, + 101, + 99, + 116, + 32, + 120, + 61, + 34, + 50, + 55, + 48, + 34, + 32, + 121, + 61, + 34, + 49, + 54, + 56, + 34, + 32, + 119, + 105, + 100, + 116, + 104, + 61, + 34, + 54, + 55, + 56, + 34, + 32, + 104, + 101, + 105, + 103, + 104, + 116, + 61, + 34, + 57, + 53, + 56, + 34, + 32, + 116, + 114, + 97, + 110, + 115, + 102, + 111, + 114, + 109, + 61, + 34, + 109, + 97, + 116, + 114, + 105, + 120, + 40, + 49, + 44, + 48, + 44, + 48, + 44, + 49, + 44, + 48, + 44, + 48, + 41, + 34, + 32, + 102, + 105, + 108, + 108, + 61, + 34, + 114, + 103, + 98, + 40, + 50, + 53, + 53, + 44, + 50, + 53, + 53, + 44, + 50, + 53, + 53, + 41, + 34, + 47, + 62, + 60, + 112, + 97, + 116, + 104, + 32, + 100, + 61, + 34, + 32, + 77, + 32, + 51, + 55, + 56, + 46, + 53, + 32, + 49, + 57, + 55, + 32, + 76, + 32, + 56, + 51, + 57, + 46, + 53, + 32, + 49, + 57, + 55, + 32, + 81, + 32, + 56, + 50, + 48, + 46, + 55, + 49, + 54, + 32, + 56, + 49, + 46, + 56, + 55, + 56, + 32, + 54, + 54, + 49, + 46, + 53, + 32, + 53, + 55, + 32, + 67, + 32, + 54, + 53, + 54, + 46, + 57, + 50, + 57, + 32, + 53, + 54, + 46, + 50, + 56, + 54, + 32, + 54, + 54, + 48, + 46, + 53, + 57, + 53, + 32, + 55, + 32, + 54, + 48, + 56, + 46, + 53, + 32, + 55, + 32, + 67, + 32, + 53, + 53, + 54, + 46, + 49, + 52, + 51, + 32, + 55, + 32, + 53, + 53, + 55, + 46, + 52, + 48, + 54, + 32, + 53, + 54, + 46, + 54, + 55, + 54, + 32, + 53, + 53, + 53, + 46, + 53, + 32, + 53, + 55, + 32, + 81, + 32, + 51, + 55, + 56, + 46, + 54, + 52, + 51, + 32, + 56, + 55, + 46, + 48, + 55, + 49, + 32, + 51, + 55, + 56, + 46, + 53, + 32, + 49, + 57, + 55, + 32, + 90, + 32, + 34, + 32, + 102, + 105, + 108, + 108, + 61, + 34, + 114, + 103, + 98, + 40, + 49, + 48, + 53, + 44, + 49, + 48, + 53, + 44, + 49, + 48, + 53, + 41, + 34, + 47, + 62, + 60, + 103, + 62, + 60, + 114, + 101, + 99, + 116, + 32, + 120, + 61, + 34, + 53, + 54, + 57, + 46, + 48, + 56, + 54, + 34, + 32, + 121, + 61, + 34, + 49, + 50, + 50, + 46, + 52, + 53, + 51, + 34, + 32, + 119, + 105, + 100, + 116, + 104, + 61, + 34, + 52, + 48, + 50, + 46, + 55, + 51, + 53, + 34, + 32, + 104, + 101, + 105, + 103, + 104, + 116, + 61, + 34, + 57, + 51, + 53, + 46, + 48, + 53, + 49, + 34, + 32, + 116, + 114, + 97, + 110, + 115, + 102, + 111, + 114, + 109, + 61, + 34, + 109, + 97, + 116, + 114, + 105, + 120, + 40, + 48, + 46, + 56, + 51, + 57, + 44, + 45, + 48, + 46, + 53, + 52, + 52, + 44, + 48, + 46, + 53, + 52, + 52, + 44, + 48, + 46, + 56, + 51, + 57, + 44, + 45, + 49, + 57, + 55, + 46, + 48, + 49, + 51, + 44, + 53, + 49, + 52, + 46, + 53, + 57, + 50, + 41, + 34, + 32, + 102, + 105, + 108, + 108, + 61, + 34, + 114, + 103, + 98, + 40, + 49, + 51, + 57, + 44, + 49, + 55, + 53, + 44, + 57, + 48, + 41, + 34, + 47, + 62, + 60, + 114, + 101, + 99, + 116, + 32, + 120, + 61, + 34, + 54, + 50, + 56, + 46, + 57, + 51, + 49, + 34, + 32, + 121, + 61, + 34, + 49, + 57, + 55, + 46, + 50, + 54, + 49, + 34, + 32, + 119, + 105, + 100, + 116, + 104, + 61, + 34, + 50, + 56, + 51, + 46, + 56, + 51, + 34, + 32, + 104, + 101, + 105, + 103, + 104, + 116, + 61, + 34, + 55, + 56, + 53, + 46, + 50, + 54, + 49, + 34, + 32, + 116, + 114, + 97, + 110, + 115, + 102, + 111, + 114, + 109, + 61, + 34, + 109, + 97, + 116, + 114, + 105, + 120, + 40, + 48, + 46, + 56, + 51, + 57, + 44, + 45, + 48, + 46, + 53, + 52, + 52, + 44, + 48, + 46, + 53, + 52, + 52, + 44, + 48, + 46, + 56, + 51, + 57, + 44, + 45, + 49, + 57, + 54, + 46, + 57, + 48, + 50, + 44, + 53, + 49, + 52, + 46, + 55, + 57, + 49, + 41, + 34, + 32, + 102, + 105, + 108, + 108, + 61, + 34, + 114, + 103, + 98, + 40, + 49, + 55, + 57, + 44, + 50, + 50, + 54, + 44, + 49, + 49, + 54, + 41, + 34, + 47, + 62, + 60, + 112, + 97, + 116, + 104, + 32, + 100, + 61, + 34, + 32, + 77, + 32, + 54, + 57, + 52, + 46, + 48, + 48, + 50, + 32, + 54, + 51, + 53, + 46, + 57, + 48, + 55, + 32, + 67, + 32, + 54, + 51, + 55, + 46, + 49, + 54, + 50, + 32, + 53, + 52, + 56, + 46, + 51, + 56, + 49, + 32, + 54, + 50, + 52, + 46, + 55, + 54, + 51, + 32, + 52, + 53, + 53, + 46, + 52, + 48, + 53, + 32, + 54, + 54, + 54, + 46, + 51, + 51, + 50, + 32, + 52, + 50, + 56, + 46, + 52, + 49, + 32, + 67, + 32, + 55, + 48, + 55, + 46, + 57, + 32, + 52, + 48, + 49, + 46, + 52, + 49, + 54, + 32, + 55, + 56, + 55, + 46, + 55, + 57, + 53, + 32, + 52, + 53, + 48, + 46, + 53, + 54, + 32, + 56, + 52, + 52, + 46, + 54, + 51, + 53, + 32, + 53, + 51, + 56, + 46, + 48, + 56, + 54, + 32, + 67, + 32, + 57, + 48, + 49, + 46, + 52, + 55, + 53, + 32, + 54, + 50, + 53, + 46, + 54, + 49, + 50, + 32, + 57, + 49, + 51, + 46, + 56, + 55, + 52, + 32, + 55, + 49, + 56, + 46, + 53, + 56, + 56, + 32, + 56, + 55, + 50, + 46, + 51, + 48, + 54, + 32, + 55, + 52, + 53, + 46, + 53, + 56, + 51, + 32, + 67, + 32, + 56, + 51, + 48, + 46, + 55, + 51, + 55, + 32, + 55, + 55, + 50, + 46, + 53, + 55, + 55, + 32, + 55, + 53, + 48, + 46, + 56, + 52, + 50, + 32, + 55, + 50, + 51, + 46, + 52, + 51, + 51, + 32, + 54, + 57, + 52, + 46, + 48, + 48, + 50, + 32, + 54, + 51, + 53, + 46, + 57, + 48, + 55, + 32, + 90, + 32, + 34, + 32, + 102, + 105, + 108, + 108, + 61, + 34, + 114, + 103, + 98, + 40, + 49, + 51, + 57, + 44, + 49, + 55, + 53, + 44, + 57, + 48, + 41, + 34, + 47, + 62, + 60, + 114, + 101, + 99, + 116, + 32, + 120, + 61, + 34, + 53, + 54, + 56, + 46, + 54, + 50, + 56, + 34, + 32, + 121, + 61, + 34, + 52, + 55, + 51, + 46, + 49, + 55, + 50, + 34, + 32, + 119, + 105, + 100, + 116, + 104, + 61, + 34, + 52, + 48, + 53, + 46, + 55, + 52, + 50, + 34, + 32, + 104, + 101, + 105, + 103, + 104, + 116, + 61, + 34, + 50, + 51, + 52, + 46, + 53, + 53, + 51, + 34, + 32, + 116, + 114, + 97, + 110, + 115, + 102, + 111, + 114, + 109, + 61, + 34, + 109, + 97, + 116, + 114, + 105, + 120, + 40, + 48, + 46, + 56, + 51, + 57, + 44, + 45, + 48, + 46, + 53, + 52, + 52, + 44, + 48, + 46, + 53, + 52, + 52, + 44, + 48, + 46, + 56, + 51, + 57, + 44, + 45, + 49, + 57, + 55, + 46, + 49, + 44, + 53, + 49, + 53, + 46, + 50, + 51, + 55, + 41, + 34, + 32, + 102, + 105, + 108, + 108, + 61, + 34, + 114, + 103, + 98, + 40, + 50, + 48, + 55, + 44, + 50, + 48, + 55, + 44, + 50, + 48, + 55, + 41, + 34, + 47, + 62, + 60, + 47, + 103, + 62, + 60, + 103, + 62, + 60, + 99, + 105, + 114, + 99, + 108, + 101, + 32, + 118, + 101, + 99, + 116, + 111, + 114, + 45, + 101, + 102, + 102, + 101, + 99, + 116, + 61, + 34, + 110, + 111, + 110, + 45, + 115, + 99, + 97, + 108, + 105, + 110, + 103, + 45, + 115, + 116, + 114, + 111, + 107, + 101, + 34, + 32, + 99, + 120, + 61, + 34, + 53, + 53, + 55, + 34, + 32, + 99, + 121, + 61, + 34, + 57, + 50, + 57, + 34, + 32, + 114, + 61, + 34, + 49, + 48, + 51, + 34, + 32, + 102, + 105, + 108, + 108, + 61, + 34, + 114, + 103, + 98, + 40, + 50, + 53, + 53, + 44, + 50, + 49, + 53, + 44, + 49, + 49, + 41, + 34, + 47, + 62, + 60, + 99, + 105, + 114, + 99, + 108, + 101, + 32, + 118, + 101, + 99, + 116, + 111, + 114, + 45, + 101, + 102, + 102, + 101, + 99, + 116, + 61, + 34, + 110, + 111, + 110, + 45, + 115, + 99, + 97, + 108, + 105, + 110, + 103, + 45, + 115, + 116, + 114, + 111, + 107, + 101, + 34, + 32, + 99, + 120, + 61, + 34, + 51, + 51, + 51, + 34, + 32, + 99, + 121, + 61, + 34, + 55, + 54, + 48, + 46, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 49, + 34, + 32, + 114, + 61, + 34, + 49, + 48, + 51, + 34, + 32, + 102, + 105, + 108, + 108, + 61, + 34, + 114, + 103, + 98, + 40, + 50, + 53, + 53, + 44, + 50, + 49, + 53, + 44, + 49, + 49, + 41, + 34, + 47, + 62, + 60, + 47, + 103, + 62, + 60, + 47, + 103, + 62, + 60, + 47, + 115, + 118, + 103, + 62 + ], + "fileName": "BudgetMaster.svg", + "fileExtension": "SVG" + } + ], + "icons": [ + { + "ID": 1, + "builtinIdentifier": "fas fa-landmark" + }, + { + "ID": 3 + }, + { + "ID": 4 + }, + { + "ID": 6, + "builtinIdentifier": "fas fa-ban", + "fontColor": "#2eb952ff" + }, + { + "ID": 7, + "imageID": 1 + }, + { + "ID": 8, + "fontColor": "#2e79b9ff" + }, + { + "ID": 11, + "builtinIdentifier": "fas fa-ambulance" + }, + { + "ID": 12, + "imageID": 1 + }, + { + "ID": 13, + "builtinIdentifier": "fas fa-battery-three-quarters", + "fontColor": "#e34f4fff" + }, + { + "ID": 15, + "fontColor": "#212121ff" + }, + { + "ID": 17, + "builtinIdentifier": "fas fa-award", + "fontColor": "#212121ff" + }, + { + "ID": 18, + "imageID": 1 + } + ] +} \ No newline at end of file -- GitLab