From e71c59117b604863706bd7e133496c7e8732c7ee Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Sun, 14 Apr 2019 12:44:16 +0200
Subject: [PATCH] #415 - offer to move transactions to category on category
 delete

---
 .../categories/CategoryController.java        | 17 +++--
 .../categories/CategoryService.java           |  4 +-
 .../categories/DestinationCategory.java       | 28 ++++++++
 .../transactions/TransactionController.java   |  2 +
 src/main/resources/languages/_de.properties   |  2 +
 src/main/resources/languages/_en.properties   |  2 +
 src/main/resources/static/js/categories.js    |  5 ++
 .../resources/static/js/categorySelect.js     | 70 +++++++++++++++++++
 src/main/resources/static/js/transactions.js  | 69 ------------------
 .../templates/categories/categories.ftl       | 19 ++++-
 .../templates/transactions/newTransaction.ftl |  3 +-
 .../transactions/newTransactionMacros.ftl     | 11 ++-
 12 files changed, 149 insertions(+), 83 deletions(-)
 create mode 100644 src/main/java/de/deadlocker8/budgetmaster/categories/DestinationCategory.java
 create mode 100644 src/main/resources/static/js/categorySelect.js

diff --git a/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java b/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java
index 9a729e4f0..72059d64a 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java
@@ -15,6 +15,9 @@ import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 
+import java.util.List;
+import java.util.stream.Collectors;
+
 
 @Controller
 public class CategoryController extends BaseController
@@ -47,18 +50,24 @@ public class CategoryController extends BaseController
 			return "redirect:/categories";
 		}
 
-		model.addAttribute("categories", categoryRepository.findAllByOrderByNameAsc());
+		List<Category> allCategories = categoryService.getRepository().findAllByOrderByNameAsc();
+		List<Category> availableCategories = allCategories.stream().filter(category -> !category.getID().equals(ID)).collect(Collectors.toList());
+
+		model.addAttribute("categories", allCategories);
+		model.addAttribute("availableCategories", availableCategories);
+		model.addAttribute("preselectedCategory", categoryService.getRepository().findByType(CategoryType.NONE));
+
 		model.addAttribute("currentCategory", categoryRepository.getOne(ID));
 		model.addAttribute("settings", settingsService.getSettings());
 		return "categories/categories";
 	}
 
-	@RequestMapping("/categories/{ID}/delete")
-	public String deleteCategory(Model model, @PathVariable("ID") Integer ID)
+	@RequestMapping(value = "/categories/{ID}/delete", method = RequestMethod.POST)
+	public String deleteCategory(Model model, @PathVariable("ID") Integer ID, @ModelAttribute("DestinationCategory") DestinationCategory destinationCategory)
 	{
 		if(isDeletable(ID))
 		{
-			categoryService.deleteCategory(ID);
+			categoryService.deleteCategory(ID, destinationCategory.getCategory());
 		}
 
 		return "redirect:/categories";
diff --git a/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryService.java b/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryService.java
index cabe084ff..ca9f4223e 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryService.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryService.java
@@ -33,7 +33,7 @@ public class CategoryService implements Resetable
 		return categoryRepository;
 	}
 
-	public void deleteCategory(int ID)
+	public void deleteCategory(int ID, Category newCategory)
 	{
 		Category categoryToDelete = categoryRepository.findOne(ID);
 		List<Transaction> referringTransactions = categoryToDelete.getReferringTransactions();
@@ -41,7 +41,7 @@ public class CategoryService implements Resetable
 		{
 			for(Transaction transaction : referringTransactions)
 			{
-				transaction.setCategory(categoryRepository.findByType(CategoryType.NONE));
+				transaction.setCategory(newCategory);
 			}
 		}
 
diff --git a/src/main/java/de/deadlocker8/budgetmaster/categories/DestinationCategory.java b/src/main/java/de/deadlocker8/budgetmaster/categories/DestinationCategory.java
new file mode 100644
index 000000000..582fd0696
--- /dev/null
+++ b/src/main/java/de/deadlocker8/budgetmaster/categories/DestinationCategory.java
@@ -0,0 +1,28 @@
+package de.deadlocker8.budgetmaster.categories;
+
+public class DestinationCategory
+{
+	private Category category;
+
+	public DestinationCategory()
+	{
+	}
+
+	public Category getCategory()
+	{
+		return category;
+	}
+
+	public void setCategory(Category category)
+	{
+		this.category = category;
+	}
+
+	@Override
+	public String toString()
+	{
+		return "DestinationCategory{" +
+				"category=" + category +
+				'}';
+	}
+}
diff --git a/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionController.java b/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionController.java
index c3e762a7b..b30248f46 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionController.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionController.java
@@ -1,6 +1,7 @@
 package de.deadlocker8.budgetmaster.transactions;
 
 import de.deadlocker8.budgetmaster.categories.CategoryRepository;
+import de.deadlocker8.budgetmaster.categories.CategoryType;
 import de.deadlocker8.budgetmaster.controller.BaseController;
 import de.deadlocker8.budgetmaster.repeating.RepeatingOptionRepository;
 import de.deadlocker8.budgetmaster.settings.Settings;
@@ -128,6 +129,7 @@ public class TransactionController extends BaseController
 	{
 		DateTime date = helpers.getDateTimeFromCookie(cookieDate);
 		Transaction emptyTransaction = new Transaction();
+		emptyTransaction.setCategory(categoryRepository.findByType(CategoryType.NONE));
 		model.addAttribute("currentDate", date);
 		model.addAttribute("categories", categoryRepository.findAllByOrderByNameAsc());
 		model.addAttribute("accounts", accountService.getAllAccountsAsc());
diff --git a/src/main/resources/languages/_de.properties b/src/main/resources/languages/_de.properties
index 8346807ac..6ceb31a1b 100644
--- a/src/main/resources/languages/_de.properties
+++ b/src/main/resources/languages/_de.properties
@@ -78,6 +78,8 @@ month.december=Dezember
 # INFO
 info.title.category.delete=Kategorie l�schen
 info.text.category.delete=M�chtest du die Kategorie "{0}" wirklich unwiderruflich l�schen?
+info.title.category.delete.move=Verschieben nach
+info.text.category.delete.move=In welche Kategorie sollen alle vekn�pften Buchungen verschoben werden?
 info.title.account.delete=Konto l�schen
 info.text.account.delete=M�chtest du das Konto "{0}" wirklich unwiderruflich l�schen?<br>Hinweis: Diesem Konto sind {1} Buchungen zugeordnet. Beim L�schen des Kontos werden diese ebenfalls gel�scht!
 info.button.account.delete=Konto und Buchungen l�schen
diff --git a/src/main/resources/languages/_en.properties b/src/main/resources/languages/_en.properties
index bcf1596bd..50aa2fcfc 100644
--- a/src/main/resources/languages/_en.properties
+++ b/src/main/resources/languages/_en.properties
@@ -78,6 +78,8 @@ month.december=December
 # INFO
 info.title.category.delete=Delete Category
 info.text.category.delete=Do you really want to delete the category "{0}"? This can''t be undone.
+info.title.category.delete.move=Move to
+info.text.category.delete.move=In which category should all related transactions be moved?
 info.title.account.delete=Delete Account
 info.text.account.delete=Do you really want to delete the account "{0}"? This can''t be undone.<br>Note: There are {1} transactions associated with this account. Deleting this account will delete all releated transactions too!
 info.button.account.delete=Delete Account and Transactions
diff --git a/src/main/resources/static/js/categories.js b/src/main/resources/static/js/categories.js
index 1d4e14a3e..574a1ac29 100644
--- a/src/main/resources/static/js/categories.js
+++ b/src/main/resources/static/js/categories.js
@@ -26,6 +26,11 @@ $( document ).ready(function() {
             }
         });
     }
+
+    $('#buttonDeleteCategory').click(function()
+    {
+        document.getElementById("formDestinationCategory").submit();
+    });
 });
 
 function removeActive()
diff --git a/src/main/resources/static/js/categorySelect.js b/src/main/resources/static/js/categorySelect.js
new file mode 100644
index 000000000..717bd0af5
--- /dev/null
+++ b/src/main/resources/static/js/categorySelect.js
@@ -0,0 +1,70 @@
+$( document ).ready(function() {
+    if($("#transaction-category").length)
+    {
+        beautifyCategorySelect();
+    }
+});
+
+function beautifyCategorySelect() {
+    var counter = 0;
+
+    var select = M.FormSelect.init(document.getElementById('transaction-category'), {
+        dropdownOptions: {
+            onCloseStart: function () {
+                var listItems = select.dropdownOptions.childNodes;
+                var selectedItem;
+                for(var i = 0; i < listItems.length; i++)
+                {
+                    var currentItem = listItems[i];
+                    if(currentItem.classList.contains("selected"))
+                    {
+                        selectedItem = currentItem.textContent;
+                        break;
+                    }
+                }
+                select.input.value = selectedItem;
+            }
+        }
+    });
+
+    select.dropdownOptions.childNodes.forEach(function (item) {
+        var currentSpan = jQuery(item.querySelector('span'));
+        var categoryInfo = currentSpan.text().split("@@@");
+        var categoryName = categoryInfo[0];
+        var firstLetter = capitalizeFirstLetter(categoryName);
+        var categoryColor = categoryInfo[1];
+        var appropriateTextColor = categoryInfo[2];
+
+        currentSpan.text(categoryName);
+        currentSpan.data("infos", categoryInfo);
+        currentSpan.addClass("category-select");
+        currentSpan.parent().prepend('<div class="category-circle-small category-select" id="category-' + counter + '" style="background-color: ' + categoryColor + '"><span></span></div>');
+        $('#categoryWrapper').parent().append('<style>#category-' + counter + ':after{content: "' + firstLetter + '"; color: ' + appropriateTextColor + ';}</style>');
+
+        currentSpan.click(function () {
+            select.input.value = categoryName;
+        });
+        counter++;
+    });
+
+    // select current category from code again in order to avoid showing the full infos text (e.g. Test@@@#FFFFFF@#000000@@@1) in the input field by materialize
+    if(typeof selectedCategory !== 'undefined')
+    {
+        var listItems = select.dropdownOptions.childNodes;
+        for(var i = 0; i < listItems.length; i++)
+        {
+            var currentSpan = jQuery(listItems[i].querySelector('span.category-select'));
+            var categoryID = currentSpan.data("infos")[3];
+            if(categoryID === selectedCategory)
+            {
+                currentSpan.trigger("click");
+                break;
+            }
+        }
+    }
+}
+
+function capitalizeFirstLetter(text)
+{
+    return text.charAt(0).toUpperCase();
+}
\ No newline at end of file
diff --git a/src/main/resources/static/js/transactions.js b/src/main/resources/static/js/transactions.js
index 4b7b89f09..f3325d354 100644
--- a/src/main/resources/static/js/transactions.js
+++ b/src/main/resources/static/js/transactions.js
@@ -182,11 +182,6 @@ $( document ).ready(function() {
 
         document.getElementById("input-isPayment").value = 1;
     });
-
-    if($("#transaction-category").length)
-    {
-        beautifyCategorySelect();
-    }
 });
 
 var transactionRepeatingModifierID = "#transaction-repeating-modifier";
@@ -322,67 +317,3 @@ function validateForm()
 
     return true;
 }
-
-function beautifyCategorySelect() {
-    var counter = 0;
-
-    var select = M.FormSelect.init(document.getElementById('transaction-category'), {
-        dropdownOptions: {
-            onCloseStart: function () {
-                var listItems = select.dropdownOptions.childNodes;
-                var selectedItem;
-                for(var i = 0; i < listItems.length; i++)
-                {
-                    var currentItem = listItems[i];
-                    if(currentItem.classList.contains("selected"))
-                    {
-                        selectedItem = currentItem.textContent;
-                        break;
-                    }
-                }
-                select.input.value = selectedItem;
-            }
-        }
-    });
-
-    select.dropdownOptions.childNodes.forEach(function (item) {
-        var currentSpan = jQuery(item.querySelector('span'));
-        var categoryInfo = currentSpan.text().split("@@@");
-        var categoryName = categoryInfo[0];
-        var firstLetter = capitalizeFirstLetter(categoryName);
-        var categoryColor = categoryInfo[1];
-        var appropriateTextColor = categoryInfo[2];
-
-        currentSpan.text(categoryName);
-        currentSpan.data("infos", categoryInfo);
-        currentSpan.addClass("category-select");
-        currentSpan.parent().prepend('<div class="category-circle-small category-select" id="category-' + counter + '" style="background-color: ' + categoryColor + '"><span></span></div>');
-        $('#categoryWrapper').parent().append('<style>#category-' + counter + ':after{content: "' + firstLetter + '"; color: ' + appropriateTextColor + ';}</style>');
-
-        currentSpan.click(function () {
-            select.input.value = categoryName;
-        });
-        counter++;
-    });
-
-    // select current category from code again in order to avoid showing the full infos text (e.g. Test@@@#FFFFFF@#000000@@@1) in the input field by materialize
-    if(typeof selectedCategory !== 'undefined')
-    {
-        var listItems = select.dropdownOptions.childNodes;
-        for(var i = 0; i < listItems.length; i++)
-        {
-            var currentSpan = jQuery(listItems[i].querySelector('span.category-select'));
-            var categoryID = currentSpan.data("infos")[3];
-            if(categoryID === selectedCategory)
-            {
-                currentSpan.trigger("click");
-                break;
-            }
-        }
-    }
-}
-
-function capitalizeFirstLetter(text)
-{
-    return text.charAt(0).toUpperCase();
-}
\ No newline at end of file
diff --git a/src/main/resources/templates/categories/categories.ftl b/src/main/resources/templates/categories/categories.ftl
index 378774272..9d9c04795 100644
--- a/src/main/resources/templates/categories/categories.ftl
+++ b/src/main/resources/templates/categories/categories.ftl
@@ -10,6 +10,7 @@
         <@navbar.navbar "categories" settings/>
 
         <#import "categoriesFunctions.ftl" as categoriesFunctions>
+        <#import "../transactions/newTransactionMacros.ftl" as newTransactionMacros>
 
         <main>
             <div class="card main-card background-color">
@@ -55,18 +56,34 @@
                     <div class="modal-content">
                         <h4>${locale.getString("info.title.category.delete")}</h4>
                         <p>${locale.getString("info.text.category.delete", currentCategory.name)}</p>
+                        <p>${locale.getString("info.text.category.delete.move")}</p>
+
+                        <form name="DestinationCategory" id="formDestinationCategory" action="<@s.url '/categories/${currentCategory.ID?c}/delete'/>" method="post">
+                            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
+                            <#import "../helpers/validation.ftl" as validation>
+                            <@newTransactionMacros.categorySelect availableCategories preselectedCategory "col s12 m12 l8 offset-l2" locale.getString("info.title.category.delete.move")/>
+                        </form>
                     </div>
+
                     <div class="modal-footer background-color">
                         <a href="<@s.url '/categories'/>" class="modal-action modal-close waves-effect waves-light red btn-flat white-text">${locale.getString("cancel")}</a>
-                        <a href="<@s.url '/categories/${currentCategory.ID?c}/delete'/>" class="modal-action modal-close waves-effect waves-light green btn-flat white-text">${locale.getString("delete")}</a>
+                        <a id="buttonDeleteCategory" class="modal-action modal-close waves-effect waves-light green btn-flat white-text">${locale.getString("delete")}</a>
                     </div>
                 </div>
             </#if>
         </main>
 
         <!--  Scripts-->
+        <#-- pass selected account to JS in order to select current value for materialize select -->
+        <script>
+            <#if preselectedCategory??>
+                selectedCategory = "${preselectedCategory.getID()?c}";
+            </#if>
+        </script>
+
         <#import "../helpers/scripts.ftl" as scripts>
         <@scripts.scripts/>
         <script src="<@s.url '/js/categories.js'/>"></script>
+        <script src="<@s.url '/js/categorySelect.js'/>"></script>
     </body>
 </html>
\ No newline at end of file
diff --git a/src/main/resources/templates/transactions/newTransaction.ftl b/src/main/resources/templates/transactions/newTransaction.ftl
index 17cc4e2e1..8530925a3 100644
--- a/src/main/resources/templates/transactions/newTransaction.ftl
+++ b/src/main/resources/templates/transactions/newTransaction.ftl
@@ -48,7 +48,7 @@
                         </div>
 
                         <#-- category -->
-                        <@newTransactionMacros.category categories transaction/>
+                        <@newTransactionMacros.categorySelect categories transaction.getCategory() "col s12 m12 l8 offset-l2" locale.getString("transaction.new.label.category")/>
 
                         <#-- date -->
                         <div class="row">
@@ -159,5 +159,6 @@
         <@scripts.scripts/>
         <script src="<@s.url '/js/spectrum.js'/>"></script>
         <script src="<@s.url '/js/transactions.js'/>"></script>
+        <script src="<@s.url '/js/categorySelect.js'/>"></script>
     </body>
 </html>
diff --git a/src/main/resources/templates/transactions/newTransactionMacros.ftl b/src/main/resources/templates/transactions/newTransactionMacros.ftl
index d4c668a01..8a11ebf6b 100644
--- a/src/main/resources/templates/transactions/newTransactionMacros.ftl
+++ b/src/main/resources/templates/transactions/newTransactionMacros.ftl
@@ -49,11 +49,10 @@
     <a class="waves-effect waves-light btn ${color} buttonExpenditure"><i class="material-icons left">file_upload</i>${locale.getString("title.expenditure")}</a>
 </#macro>
 
-
 <#import "../categories/categoriesFunctions.ftl" as categoriesFunctions>
-<#macro category categories transaction>
+<#macro categorySelect categories selectedCategory inputClasses labelText>
     <div class="row">
-        <div class="input-field col s12 m12 l8 offset-l2" id="categoryWrapper">
+        <div class="input-field ${inputClasses}" id="categoryWrapper">
             <select id="transaction-category" name="category" <@validation.validation "category"/>>
                 <#list categories as category>
                     <#assign categoryInfos=categoriesFunctions.getCategoryName(category) + "@@@" + category.getColor() + "@@@" + category.getAppropriateTextColor() + "@@@" + category.getID()?c>
@@ -62,8 +61,8 @@
                         <#continue>
                     </#if>
 
-                    <#if transaction.getCategory()??>
-                        <#if transaction.getCategory().getID()?c == category.getID()?c>
+                    <#if selectedCategory??>
+                        <#if selectedCategory.getID()?c == category.getID()?c>
                             <option selected value="${category.getID()?c}">${categoryInfos}</option>
                         <#else>
                             <option value="${category.getID()?c}">${categoryInfos}</option>
@@ -79,7 +78,7 @@
                     <option value="${category.getID()?c}">${categoryInfos}</option>
                 </#list>
             </select>
-            <label for="transaction-category">${locale.getString("transaction.new.label.category")}</label>
+            <label for="transaction-category">${labelText}</label>
         </div>
     </div>
 </#macro>
-- 
GitLab