From a5adbe45e9b67dcc710f5455907e30712974aec8 Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Mon, 11 May 2020 20:45:54 +0200
Subject: [PATCH] #338 - first implementation for adding templates from scratch
 (normal transactions)

---
 .../templates/TemplateController.java         | 43 ++++++++--
 .../templates/TemplateService.java            | 29 ++++++-
 .../templates/TemplateValidator.java          | 21 +++++
 src/main/resources/languages/_de.properties   |  4 +-
 src/main/resources/languages/_en.properties   |  4 +-
 .../templates/templates/newTemplate.ftl       | 80 +++++++++++++++++++
 .../templates/templates/templateFunctions.ftl | 10 +++
 .../transactions/newTransactionMacros.ftl     |  1 +
 .../transactions/newTransactionNormal.ftl     |  2 +-
 .../transactions/newTransactionTransfer.ftl   |  2 +-
 10 files changed, 186 insertions(+), 10 deletions(-)
 create mode 100644 src/main/java/de/deadlocker8/budgetmaster/templates/TemplateValidator.java
 create mode 100644 src/main/resources/templates/templates/newTemplate.ftl

diff --git a/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateController.java b/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateController.java
index 16b449615..2d47b2151 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateController.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateController.java
@@ -14,6 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.server.ResponseStatusException;
 
@@ -72,11 +73,11 @@ public class TemplateController extends BaseController
 	}
 
 	@PostMapping(value = "/templates/fromTransaction")
-	public String postNormal(@RequestParam(value = "templateName") String templateName,
-							 @ModelAttribute("NewTransaction") Transaction transaction,
-							 @RequestParam(value = "isPayment", required = false) boolean isPayment,
-							 @RequestParam(value = "includeCategory") Boolean includeCategory,
-							 @RequestParam(value = "includeAccount") Boolean includeAccount)
+	public String postFromTransaction(@RequestParam(value = "templateName") String templateName,
+									  @ModelAttribute("NewTransaction") Transaction transaction,
+									  @RequestParam(value = "isPayment", required = false) boolean isPayment,
+									  @RequestParam(value = "includeCategory") Boolean includeCategory,
+									  @RequestParam(value = "includeAccount") Boolean includeAccount)
 	{
 		transactionService.handleAmount(transaction, isPayment);
 		transactionService.handleTags(transaction);
@@ -141,4 +142,36 @@ public class TemplateController extends BaseController
 		}
 		return "transactions/newTransactionNormal";
 	}
+
+	@GetMapping("/templates/newTemplate")
+	public String newTemplate(Model model)
+	{
+		final Template emptyTemplate = new Template();
+		templateService.prepareTemplateForNewTransaction(emptyTemplate);
+		templateService.prepareModelNewOrEdit(model, false, emptyTemplate, true, accountService.getAllAccountsAsc());
+		return "templates/newTemplate";
+	}
+
+	@PostMapping(value = "/templates/newTemplate")
+	public String post(Model model,
+					   @ModelAttribute("NewTemplate") Template template, BindingResult bindingResult,
+					   @RequestParam(value = "isPayment", required = false) boolean isPayment)
+	{
+
+		TemplateValidator templateValidator = new TemplateValidator();
+		templateValidator.validate(template, bindingResult);
+
+		transactionService.handleAmount(template, isPayment);
+		transactionService.handleTags(template);
+
+		if(bindingResult.hasErrors())
+		{
+			model.addAttribute("error", bindingResult);
+			templateService.prepareModelNewOrEdit(model, template.getID() != null, template, isPayment, accountService.getAllAccountsAsc());
+			return "templates/newTemplate";
+		}
+
+		templateService.getRepository().save(template);
+		return "redirect:/transactions";
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateService.java b/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateService.java
index 07fefa6c9..45c614903 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateService.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateService.java
@@ -1,30 +1,46 @@
 package de.deadlocker8.budgetmaster.templates;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
 import de.deadlocker8.budgetmaster.accounts.Account;
 import de.deadlocker8.budgetmaster.accounts.AccountService;
 import de.deadlocker8.budgetmaster.categories.CategoryService;
 import de.deadlocker8.budgetmaster.categories.CategoryType;
 import de.deadlocker8.budgetmaster.services.Resetable;
+import de.deadlocker8.budgetmaster.settings.SettingsService;
 import de.deadlocker8.budgetmaster.transactions.Transaction;
+import de.deadlocker8.budgetmaster.transactions.TransactionBase;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.ui.Model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
 
 @Service
 public class TemplateService implements Resetable
 {
+	private static final Gson GSON = new GsonBuilder()
+			.setPrettyPrinting()
+			.create();
+
 	private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
 	private final TemplateRepository templateRepository;
 	private final AccountService accountService;
 	private final CategoryService categoryService;
+	private final SettingsService settingsService;
+
 
 	@Autowired
-	public TemplateService(TemplateRepository templateRepository, AccountService accountService, CategoryService categoryService)
+	public TemplateService(TemplateRepository templateRepository, AccountService accountService, CategoryService categoryService, SettingsService settingsService)
 	{
 		this.templateRepository = templateRepository;
 		this.accountService = accountService;
 		this.categoryService = categoryService;
+		this.settingsService = settingsService;
 	}
 
 	public TemplateRepository getRepository()
@@ -72,4 +88,15 @@ public class TemplateService implements Resetable
 			template.setAccount(selectedAccount);
 		}
 	}
+
+	public void prepareModelNewOrEdit(Model model, boolean isEdit, TransactionBase item, boolean isPayment, List<Account> accounts)
+	{
+		model.addAttribute("isEdit", isEdit);
+		model.addAttribute("categories", categoryService.getRepository().findAllByOrderByNameAsc());
+		model.addAttribute("accounts", accounts);
+		model.addAttribute("template", item);
+		model.addAttribute("settings", settingsService.getSettings());
+		model.addAttribute("isPayment", isPayment);
+		model.addAttribute("suggestionsJSON", GSON.toJson(new ArrayList<String>()));
+	}
 }
diff --git a/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateValidator.java b/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateValidator.java
new file mode 100644
index 000000000..39e12581c
--- /dev/null
+++ b/src/main/java/de/deadlocker8/budgetmaster/templates/TemplateValidator.java
@@ -0,0 +1,21 @@
+package de.deadlocker8.budgetmaster.templates;
+
+import de.deadlocker8.budgetmaster.transactions.Transaction;
+import de.deadlocker8.budgetmaster.utils.Strings;
+import org.springframework.validation.Errors;
+import org.springframework.validation.ValidationUtils;
+import org.springframework.validation.Validator;
+
+
+public class TemplateValidator implements Validator
+{
+	public boolean supports(Class clazz)
+	{
+		return Transaction.class.equals(clazz);
+	}
+
+	public void validate(Object obj, Errors errors)
+	{
+		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "templateName", Strings.WARNING_EMPTY_TRANSACTION_NAME);
+	}
+}
\ No newline at end of file
diff --git a/src/main/resources/languages/_de.properties b/src/main/resources/languages/_de.properties
index b40ac6425..e7b0784d1 100644
--- a/src/main/resources/languages/_de.properties
+++ b/src/main/resources/languages/_de.properties
@@ -139,7 +139,7 @@ warning.settings.password.confirmation.empty=Bitte gib dein Passwort zur Bestät
 warning.settings.password.confirmation.wrong=Passwort und Passwort Wiederholung stimmen nicht überein.
 warning.empty.chart.name=Bitte gib einen Namen ein.
 warning.empty.chart.script=Bitte gib ein Script ein.
-warning.empty.template.name=Es existiert bereits eine Vorlage mit diesem Namen.
+warning.duplicate.template.name=Es existiert bereits eine Vorlage mit diesem Namen.
 
 # UI
 menu.home=Startseite
@@ -230,6 +230,8 @@ repeating.end.afterXTimes.A=nach
 repeating.end.afterXTimes.B=Wiederholungen
 repeating.end.date=am
 
+template.new.label.name=Vorlagenname
+
 login.password=Passwort
 login.button=Login
 logout.success=Erfolgreich abgemeldet.
diff --git a/src/main/resources/languages/_en.properties b/src/main/resources/languages/_en.properties
index 261d7f186..762a81a7c 100644
--- a/src/main/resources/languages/_en.properties
+++ b/src/main/resources/languages/_en.properties
@@ -139,7 +139,7 @@ warning.settings.password.confirmation.empty=Please enter your password again fo
 warning.settings.password.confirmation.wrong=Password and password confirmation do not match.
 warning.empty.chart.name=Please insert a name.
 warning.empty.chart.script=Please insert a script.
-warning.empty.template.name=A template with this name is already existing.
+warning.duplicate.template.name=A template with this name is already existing.
 
 # UI
 menu.home=Home
@@ -230,6 +230,8 @@ repeating.end.afterXTimes.A=after
 repeating.end.afterXTimes.B=times
 repeating.end.date=on
 
+template.new.label.name=Template name
+
 login.password=Password
 login.button=Login
 logout.success=Successfully logged out.
diff --git a/src/main/resources/templates/templates/newTemplate.ftl b/src/main/resources/templates/templates/newTemplate.ftl
new file mode 100644
index 000000000..5a5d5c525
--- /dev/null
+++ b/src/main/resources/templates/templates/newTemplate.ftl
@@ -0,0 +1,80 @@
+<html>
+    <head>
+        <#import "../helpers/header.ftl" as header>
+        <@header.header "BudgetMaster"/>
+        <@header.style "transactions"/>
+        <@header.style "datepicker"/>
+        <@header.style "categories"/>
+        <@header.style "collapsible"/>
+        <#import "/spring.ftl" as s>
+    </head>
+    <body class="budgetmaster-blue-light">
+        <#import "../helpers/navbar.ftl" as navbar>
+        <@navbar.navbar "transactions" settings/>
+
+        <#import "../transactions/newTransactionMacros.ftl" as newTransactionMacros>
+        <#import "templateFunctions.ftl" as templateFunctions>
+
+        <main>
+            <div class="card main-card background-color">
+                <div class="container">
+                    <div class="section center-align">
+                        <div class="headline"><#if isEdit>${locale.getString("title.template.edit")}<#else>${locale.getString("title.template.new")}</#if></div>
+                    </div>
+                </div>
+                <div class="container">
+                    <#import "../helpers/validation.ftl" as validation>
+                    <form name="NewTemplate" action="<@s.url '/templates/newTemplate'/>" method="post" onsubmit="return validateForm()">
+                        <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
+                        <input type="hidden" name="ID" value="<#if template.getID()??>${template.getID()?c}</#if>">
+
+                        <#-- isPayment switch -->
+                        <@newTransactionMacros.isExpenditureSwitch template isPayment/>
+
+                        <#-- template name -->
+                        <@templateFunctions.templateName template/>
+
+                        <#-- name -->
+                        <@newTransactionMacros.transactionName template suggestionsJSON/>
+
+                        <#-- amount -->
+                        <@newTransactionMacros.transactionAmount template/>
+
+                        <#-- category -->
+                        <@newTransactionMacros.categorySelect categories template.getCategory() "col s12 m12 l8 offset-l2" locale.getString("transaction.new.label.category")/>
+
+                        <#-- description -->
+                        <@newTransactionMacros.transactionDescription template/>
+
+                        <#-- tags -->
+                        <@newTransactionMacros.transactionTags template/>
+
+                        <#-- account -->
+                        <#if template.getAccount()??>
+                            <@newTransactionMacros.account accounts template.getAccount() "transaction-account" "account" locale.getString("transaction.new.label.account")/>
+                        <#else>
+                            <@newTransactionMacros.account accounts helpers.getCurrentAccountOrDefault() "transaction-account" "account" locale.getString("transaction.new.label.account")/>
+                        </#if>
+
+                        <br>
+                        <#-- buttons -->
+                        <@newTransactionMacros.buttons/>
+                    </form>
+                </div>
+            </div>
+        </main>
+
+        <!-- Pass localization to JS -->
+        <#import "../helpers/globalDatePicker.ftl" as datePicker>
+        <@datePicker.datePickerLocalization/>
+
+        <!-- Scripts-->
+        <#import "../helpers/scripts.ftl" as scripts>
+        <@scripts.scripts/>
+        <script src="<@s.url '/js/libs/spectrum.js'/>"></script>
+        <script src="<@s.url '/js/helpers.js'/>"></script>
+        <script src="<@s.url '/js/transactions.js'/>"></script>
+        <script src="<@s.url '/js/categorySelect.js'/>"></script>
+        <script src="<@s.url '/js/templates.js'/>"></script>
+    </body>
+</html>
diff --git a/src/main/resources/templates/templates/templateFunctions.ftl b/src/main/resources/templates/templates/templateFunctions.ftl
index e2cf1267f..2ad4251a5 100644
--- a/src/main/resources/templates/templates/templateFunctions.ftl
+++ b/src/main/resources/templates/templates/templateFunctions.ftl
@@ -1,4 +1,5 @@
 <#import "/spring.ftl" as s>
+<#import "../helpers/validation.ftl" as validation>
 
 <#macro buttonNew>
     <a href="<@s.url '/templates/newTemplate'/>" class="waves-effect waves-light btn budgetmaster-blue"><i
@@ -126,4 +127,13 @@
             <td>${template.getTransferAccount().getName()}</td>
         </tr>
     </#if>
+</#macro>
+
+<#macro templateName template>
+    <div class="row">
+        <div class="input-field col s12 m12 l8 offset-l2">
+            <input id="template-name" type="text" name="templateName" <@validation.validation "templateName"/> value="<#if template.getTemplateName()??>${template.getTemplateName()}</#if>">
+            <label for="template-name">${locale.getString("template.new.label.name")}</label>
+        </div>
+    </div>
 </#macro>
\ No newline at end of file
diff --git a/src/main/resources/templates/transactions/newTransactionMacros.ftl b/src/main/resources/templates/transactions/newTransactionMacros.ftl
index 143f854fb..5702f7fd4 100644
--- a/src/main/resources/templates/transactions/newTransactionMacros.ftl
+++ b/src/main/resources/templates/transactions/newTransactionMacros.ftl
@@ -1,4 +1,5 @@
 <#import "/spring.ftl" as s>
+<#import "../helpers/validation.ftl" as validation>
 
 <#macro isExpenditureSwitch transaction isPayment>
     <#if isPayment>
diff --git a/src/main/resources/templates/transactions/newTransactionNormal.ftl b/src/main/resources/templates/transactions/newTransactionNormal.ftl
index 39e39fe6b..2890327da 100644
--- a/src/main/resources/templates/transactions/newTransactionNormal.ftl
+++ b/src/main/resources/templates/transactions/newTransactionNormal.ftl
@@ -74,7 +74,7 @@
         <script>
             createTemplateWithErrorInForm = '${locale.getString("save.as.template.errorsInForm")}';
             templateNameEmptyValidationMessage = "${locale.getString("warning.empty.transaction.name")}";
-            templateNameDuplicateValidationMessage = "${locale.getString("warning.empty.template.name")}";
+            templateNameDuplicateValidationMessage = "${locale.getString("warning.duplicate.template.name")}";
         </script>
 
         <!-- Scripts-->
diff --git a/src/main/resources/templates/transactions/newTransactionTransfer.ftl b/src/main/resources/templates/transactions/newTransactionTransfer.ftl
index e8f3cd7c0..498f3cea2 100644
--- a/src/main/resources/templates/transactions/newTransactionTransfer.ftl
+++ b/src/main/resources/templates/transactions/newTransactionTransfer.ftl
@@ -79,7 +79,7 @@
         <script>
             createTemplateWithErrorInForm = '${locale.getString("save.as.template.errorsInForm")}';
             templateNameEmptyValidationMessage = "${locale.getString("warning.empty.transaction.name")}";
-            templateNameDuplicateValidationMessage = "${locale.getString("warning.empty.template.name")}";
+            templateNameDuplicateValidationMessage = "${locale.getString("warning.duplicate.template.name")}";
         </script>
 
         <!-- Scripts-->
-- 
GitLab