From 48fc5a2eecfcb24ddf729b8f6791579bc7ac73b9 Mon Sep 17 00:00:00 2001 From: Robert Goldmann <deadlocker@gmx.de> Date: Sat, 21 Apr 2018 15:42:22 +0200 Subject: [PATCH] #297 - accounts can now be created, updated and deleted --- .../controller/AccountController.java | 74 +++++++++++++++++++ .../controller/CategoryController.java | 2 +- .../controller/EmptyPageController.java | 7 -- .../budgetmaster/services/AccountService.java | 38 ++++++++++ .../budgetmaster/utils/Strings.java | 4 + .../validators/AccountValidator.java | 21 ++++++ src/main/resources/languages/_de.properties | 9 +++ src/main/resources/languages/_en.properties | 29 +++++--- .../resources/templates/accounts/accounts.ftl | 57 ++++++++++++++ .../templates/accounts/newAccount.ftl | 68 +++++++++++++++++ 10 files changed, 291 insertions(+), 18 deletions(-) create mode 100644 src/main/java/de/deadlocker8/budgetmaster/services/AccountService.java create mode 100644 src/main/java/de/deadlocker8/budgetmaster/validators/AccountValidator.java create mode 100644 src/main/resources/templates/accounts/accounts.ftl create mode 100644 src/main/resources/templates/accounts/newAccount.ftl diff --git a/src/main/java/de/deadlocker8/budgetmaster/controller/AccountController.java b/src/main/java/de/deadlocker8/budgetmaster/controller/AccountController.java index adf916014..f6825fc36 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/controller/AccountController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/controller/AccountController.java @@ -2,10 +2,17 @@ package de.deadlocker8.budgetmaster.controller; import de.deadlocker8.budgetmaster.entities.Account; import de.deadlocker8.budgetmaster.repositories.AccountRepository; +import de.deadlocker8.budgetmaster.services.AccountService; +import de.deadlocker8.budgetmaster.utils.ResourceNotFoundException; +import de.deadlocker8.budgetmaster.validators.AccountValidator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; import javax.servlet.http.HttpServletRequest; import java.util.List; @@ -17,6 +24,9 @@ public class AccountController extends BaseController @Autowired private AccountRepository accountRepository; + @Autowired + private AccountService accountService; + @RequestMapping(value = "/account/{ID}/select") public String selectAccount(HttpServletRequest request, @PathVariable("ID") Integer ID) { @@ -33,4 +43,68 @@ public class AccountController extends BaseController return "redirect:" + request.getHeader("Referer"); } + + @RequestMapping("/accounts") + public String accounts(Model model) + { + model.addAttribute("accounts", accountRepository.findAllByOrderByNameAsc()); + return "accounts/accounts"; + } + + @RequestMapping("/accounts/{ID}/requestDelete") + public String requestDeleteAccount(Model model, @PathVariable("ID") Integer ID) + { + model.addAttribute("accounts", accountRepository.findAllByOrderByNameAsc()); + model.addAttribute("currentAccount", accountRepository.getOne(ID)); + return "accounts/accounts"; + } + + @RequestMapping("/accounts/{ID}/delete") + public String deleteAccountAndReferringPayments(Model model, @PathVariable("ID") Integer ID) + { + accountService.deleteAccount(ID); + + return "redirect:/accounts"; + } + + @RequestMapping("/accounts/newAccount") + public String newAccount(Model model) + { + Account emptyAccount = new Account(); + model.addAttribute("account", emptyAccount); + return "accounts/newAccount"; + } + + @RequestMapping("/accounts/{ID}/edit") + public String editAccount(Model model, @PathVariable("ID") Integer ID) + { + Account account = accountRepository.findOne(ID); + if(account == null) + { + throw new ResourceNotFoundException(); + } + + model.addAttribute("account", account); + return "accounts/newAccount"; + } + + @RequestMapping(value = "/accounts/newAccount", method = RequestMethod.POST) + public String post(Model model, @ModelAttribute("NewAccount") Account account, BindingResult bindingResult) + { + AccountValidator accountValidator = new AccountValidator(); + accountValidator.validate(account, bindingResult); + + if(bindingResult.hasErrors()) + { + model.addAttribute("error", bindingResult); + model.addAttribute("account", account); + return "accounts/newAccount"; + } + else + { + accountRepository.save(account); + } + + return "redirect:/accounts"; + } } \ No newline at end of file diff --git a/src/main/java/de/deadlocker8/budgetmaster/controller/CategoryController.java b/src/main/java/de/deadlocker8/budgetmaster/controller/CategoryController.java index 5079ecabb..32a11a087 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/controller/CategoryController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/controller/CategoryController.java @@ -32,7 +32,7 @@ public class CategoryController extends BaseController private HelpersService helpers; @RequestMapping("/categories") - public String index(Model model) + public String categories(Model model) { model.addAttribute("categories", categoryRepository.findAllByOrderByNameAsc()); return "categories/categories"; diff --git a/src/main/java/de/deadlocker8/budgetmaster/controller/EmptyPageController.java b/src/main/java/de/deadlocker8/budgetmaster/controller/EmptyPageController.java index 178217dee..da1b03012 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/controller/EmptyPageController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/controller/EmptyPageController.java @@ -8,13 +8,6 @@ import org.springframework.web.bind.annotation.RequestMapping; @Controller public class EmptyPageController extends BaseController { - @RequestMapping("/accounts") - public String accounts(Model model) - { - model.addAttribute("active", "accounts"); - return "emptyPage"; - } - @RequestMapping("/reports") public String reports(Model model) { diff --git a/src/main/java/de/deadlocker8/budgetmaster/services/AccountService.java b/src/main/java/de/deadlocker8/budgetmaster/services/AccountService.java new file mode 100644 index 000000000..80cac1942 --- /dev/null +++ b/src/main/java/de/deadlocker8/budgetmaster/services/AccountService.java @@ -0,0 +1,38 @@ +package de.deadlocker8.budgetmaster.services; + +import de.deadlocker8.budgetmaster.entities.Account; +import de.deadlocker8.budgetmaster.repositories.AccountRepository; +import de.deadlocker8.budgetmaster.repositories.PaymentRepository; +import de.deadlocker8.budgetmaster.utils.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class AccountService +{ + private final Logger LOGGER = LoggerFactory.getLogger(this.getClass()); + private AccountRepository accountRepository; + private PaymentRepository paymentRepository; + + @Autowired + public AccountService(AccountRepository accountRepository, PaymentRepository paymentRepository) + { + this.accountRepository = accountRepository; + this.paymentRepository = paymentRepository; + + if(accountRepository.findAll().size() == 0) + { + accountRepository.save(new Account(Strings.ACCOUNT_DEFAULT_NAME)); + LOGGER.debug("Created default category NONE"); + } + } + + public void deleteAccount(int ID) + { + Account accountToDelete = accountRepository.findOne(ID); + paymentRepository.delete(accountToDelete.getReferringPayments()); + accountRepository.delete(ID); + } +} diff --git a/src/main/java/de/deadlocker8/budgetmaster/utils/Strings.java b/src/main/java/de/deadlocker8/budgetmaster/utils/Strings.java index 9e3dea2fc..470cdfb19 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/utils/Strings.java +++ b/src/main/java/de/deadlocker8/budgetmaster/utils/Strings.java @@ -107,6 +107,9 @@ public class Strings public static final String REPORT_PAYMENTS = "report.payments"; public static final String REPORT_BUDGET_REST = "report.budget.rest"; + //ACCOUNT + public static final String ACCOUNT_DEFAULT_NAME = "account.default.name"; + //WEEK_DAYS public static final String MONDAY ="monday"; public static final String TUESDAY ="tuesday"; @@ -192,6 +195,7 @@ public class Strings public static final String WARNING_INTEGER_HEIGHT_IN_PIXELS = "warning.integer.height.in.pixels"; public static final String WARNING_EMPTY_SAVEPATH_CHART = "warning.empty.savepath.chart"; public static final String WARNING_EMPTY_CATEGORY_NAME = "warning.empty.category.name"; + public static final String WARNING_EMPTY_ACCOUNT_NAME = "warning.empty.account.name"; public static final String WARNING_EMPTY_CATEGORY_COLOR = "warning.empty.category.color"; public static final String WARNING_EMPTY_PAYMENT_NAME = "warning.empty.payment.name"; public static final String WARNING_NAME_CHARACTER_LIMIT_REACHED_45 = "warning.name.character.limit.reached.45"; diff --git a/src/main/java/de/deadlocker8/budgetmaster/validators/AccountValidator.java b/src/main/java/de/deadlocker8/budgetmaster/validators/AccountValidator.java new file mode 100644 index 000000000..0cff68a91 --- /dev/null +++ b/src/main/java/de/deadlocker8/budgetmaster/validators/AccountValidator.java @@ -0,0 +1,21 @@ +package de.deadlocker8.budgetmaster.validators; + +import de.deadlocker8.budgetmaster.entities.Category; +import de.deadlocker8.budgetmaster.utils.Strings; +import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; +import org.springframework.validation.Validator; + + +public class AccountValidator implements Validator +{ + public boolean supports(Class clazz) + { + return Category.class.equals(clazz); + } + + public void validate(Object obj, Errors errors) + { + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", Strings.WARNING_EMPTY_ACCOUNT_NAME); + } +} \ No newline at end of file diff --git a/src/main/resources/languages/_de.properties b/src/main/resources/languages/_de.properties index 26afbed66..995eb3513 100644 --- a/src/main/resources/languages/_de.properties +++ b/src/main/resources/languages/_de.properties @@ -43,6 +43,8 @@ title.database.import=Datenbank importieren title.datepicker=Datum w�hlen title.tags=Das Tag-Eingabefeld title.account=Konto +title.account.new=Neues Konto +title.account.edit=Konto bearbeiten # LOAD load.charts=Lade Diagramme... @@ -145,6 +147,9 @@ notification.no.update.available=Kein Update verf # INFO info.title.category.delete=Kategorie l�schen info.text.category.delete=M�chtest du die Kategorie "{0}" wirklich unwiderruflich l�schen? +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 un Buchungen l�schen info.title.payment.delete=Buchung l�schen info.text.payment.delete=M�chtest du die Buchung "{0}" wirklich unwiderruflich l�schen? info.text.payment.repeating.delete=Es handelt sich um eine wiederkehrende Zahlung. Welche Zahlungen sollen gel�scht werden? @@ -198,6 +203,7 @@ warning.empty.height.in.pixels=Bitte gib eine H warning.integer.height.in.pixels=Nur ganzahlige Werte sind f�r das Feld H�he erlaubt. warning.empty.savepath.chart=W�hle einen Speicherort f�r das Diagramm aus. warning.empty.category.name=Bitte gib einen Namen ein. +warning.empty.account.name=Bitte gib einen Namen ein. warning.empty.category.color=Die Kategoriefarbe darf nicht leer sein. warning.empty.payment.name=Das Feld f�r den Namen darf nicht leer sein. warning.name.character.limit.reached.45=Der Name darf maximal 45 Zeichen lang sein. @@ -304,6 +310,9 @@ menu.accounts=Konten category.new.label.name=Name +account.new.label.name=Name +account.default.name=Standardkonto + payment.new.label.name=Name payment.new.label.amount=Betrag payment.new.label.category=Kategorie diff --git a/src/main/resources/languages/_en.properties b/src/main/resources/languages/_en.properties index e49c5c03d..5963c3101 100644 --- a/src/main/resources/languages/_en.properties +++ b/src/main/resources/languages/_en.properties @@ -13,8 +13,8 @@ github.url=https://github.com/deadlocker8/BudgetMaster # ERRORPAGES errorpages.home=To Homepage errorpages.403=Access denied. -errorpages.404=The requested page doesn't exist. -errorpages.418=I'm a teapot. +errorpages.404=The requested page doesn''t exist. +errorpages.418=I''m a teapot. errorpages.418.credits=Teapot icon made by <a href="http://www.freepik.com" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a> errorpages.500=An internal server error occurred. @@ -43,6 +43,8 @@ title.database.import=Import Database title.datepicker=Choose date title.tags=The Tag-Inputfield title.account=Account +title.account.new=New Account +title.account.edit=Edit Account # LOAD load.charts=Loading Charts... @@ -82,7 +84,7 @@ undefined=undefined tagfield.placeholder=Enter Tag here shortcut.dev.console=F12 local.server.status.ok=Server is running. -local.server.status.not.started=Server couldn't be started. +local.server.status.not.started=Server couldn''t be started. local.server.action.not.started=Start local.server.status.not.present=Server not found. local.server.action.not.present=Download @@ -144,9 +146,12 @@ notification.no.update.available=No update available. # 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.text.category.delete=Do you really want to delete the category "{0}"? This can''t be undone. +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} payments associated with this account. Deleting this account will delete all releated payments too! +info.button.account.delete=Delete Account and Payments info.title.payment.delete=Delete Entry -info.text.payment.delete=Do you really want to delete the entry "{0}"? This can't be undone. +info.text.payment.delete=Do you really want to delete the entry "{0}"? This can''t be undone. info.text.payment.repeating.delete=The entry you want to delete is a repeating entry. What entries should be deleted? info.text.payment.repeating.delete.all=All Entries info.text.payment.repeating.delete.futures=Future Entries @@ -168,7 +173,7 @@ info.text.database.import.dialog=Do you want to delete the database before impor info.text.database.import.dialog.delete=Yes, delete database info.text.database.import.dialog.append=No, append data info.title.database.delete=Delete Database -info.header.text.database.delete=Do you really want to delete this entry? This can't be undone. +info.header.text.database.delete=Do you really want to delete this entry? This can''t be undone. info.text.database.delete=Please enter the following code for verification:\t{0} info.title.welcome=Welcome info.header.text.welcome=Welcome to BudgetMaster @@ -185,7 +190,7 @@ info.text.update.available.show.changes.detailed=(detailed information on GitHub info.text.update.available.now=Update Now info.title.start.after.update=Update successful info.header.text.start.after.update=Successfully updated BudgetMaster to version {0} -info.text.start.after.update=Note: You have to update the BudgetMasterServer manually, if it's no local server! +info.text.start.after.update=Note: You have to update the BudgetMasterServer manually, if it''s no local server! info.tags=Suggestions based on already used tags will show up once you start typing.\n\nEnter - Appends the current input field content as a new tag.\nArrow Down - Opens the suggestions if the input field is empty. info.title.shutdown=Shutdown BudgetMaster info.text.shutdown=Do you really want to shutdown BudgetMaster? This could lead to unforeseen consequences during running tasks. @@ -198,6 +203,7 @@ warning.empty.height.in.pixels=Please enter a height in pixels. warning.integer.height.in.pixels=Only integer values are allowed for the height field. warning.empty.savepath.chart=Please select a location where you want to save the chart. warning.empty.category.name=Please insert a name. +warning.empty.account.name=Please insert a name. warning.empty.category.color=The category color should not be empty. warning.empty.payment.name=The field for the name can not be empty. warning.name.character.limit.reached.45=The name must not exceed 45 characters in length. @@ -229,9 +235,9 @@ error.500=An internal server error occurred while processing the request. error.create.ui=An error occurred while creating the user interface. error.server.connection=An error occurred while connecting to the server. Please check your settings. error.server.connection.with.details=An error occurred while connecting to the server. Please check your settings.\n\nError details:\n{0} -error.open.folder=The folder couldn't be opened.\n\n{0} -error.open.chart=The chart couldn't be opened.\n\n{0} -error.open.report=The report couldn't be opened.\n\n{0} +error.open.folder=The folder couldn''t be opened.\n\n{0} +error.open.chart=The chart couldn''t be opened.\n\n{0} +error.open.report=The report couldn''t be opened.\n\n{0} error.chart.export=An error occurred while exporting the chart:\n\n{0} error.report.save=An error occurred while creating the month report:\n\n{0} error.settings.save=An error occurred while saving the settings. @@ -304,6 +310,9 @@ menu.accounts=Accounts category.new.label.name=Name +account.new.label.name=Name +account.default.name=Default Account + payment.new.label.name=Name payment.new.label.amount=Amount payment.new.label.category=Category diff --git a/src/main/resources/templates/accounts/accounts.ftl b/src/main/resources/templates/accounts/accounts.ftl new file mode 100644 index 000000000..dc63922b2 --- /dev/null +++ b/src/main/resources/templates/accounts/accounts.ftl @@ -0,0 +1,57 @@ +<html> + <head> + <#import "../header.ftl" as header> + <@header.header/> + <#assign locale = static["tools.Localization"]> + </head> + <body class="budgetmaster-blue-light"> + <#import "../navbar.ftl" as navbar> + <@navbar.navbar "accounts"/> + + <main> + <div class="card main-card"> + <div class="container"> + <div class="section center-align"> + <div class="grey-text text-darken-4 headline">${locale.getString("menu.accounts")}</div> + </div> + </div> + <br> + <div class="center-align"><a href="/accounts/newAccount" class="waves-effect waves-light btn budgetmaster-blue"><i class="material-icons left">add</i>${locale.getString("title.account.new")}</a></div> + <br> + <div class="container"> + <table class="bordered"> + <#list accounts as account> + <tr> + <td>${account.getName()}</td> + <td> + <a href="/accounts/${account.getID()}/edit" class="btn-flat no-padding"><i class="material-icons left">edit</i></a> + <a href="/accounts/${account.getID()}/requestDelete" class="btn-flat no-padding"><i class="material-icons left">delete</i></a> + </td> + </tr> + </#list> + </table> + </div> + </div> + + <#if currentAccount??> + <!-- confirm delete modal --> + <div id="modalConfirmDelete" class="modal"> + <div class="modal-content"> + <h4>${locale.getString("info.title.account.delete")}</h4> + <p>${locale.getString("info.text.account.delete", currentAccount.getName(), currentAccount.getReferringPayments()?size)}</p> + </div> + <div class="modal-footer"> + <a href="/accounts" class="modal-action modal-close waves-effect waves-red btn-flat ">${locale.getString("cancel")}</a> + <a href="/accounts/${currentAccount.getID()}/delete" class="modal-action modal-close waves-effect waves-green btn-flat ">${locale.getString("info.button.account.delete")}</a> + </div> + </div> + </#if> + </main> + + <!-- Scripts--> + <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script> + <script src="/js/main.js"></script> + <script src="/js/categories.js"></script> + </body> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/accounts/newAccount.ftl b/src/main/resources/templates/accounts/newAccount.ftl new file mode 100644 index 000000000..a34bdbe00 --- /dev/null +++ b/src/main/resources/templates/accounts/newAccount.ftl @@ -0,0 +1,68 @@ +<html> + <head> + <#import "../header.ftl" as header> + <@header.header/> + <#assign locale = static["tools.Localization"]> + </head> + <body class="budgetmaster-blue-light"> + <#import "../navbar.ftl" as navbar> + <@navbar.navbar "accounts"/> + + <main> + <div class="card main-card"> + <div class="container"> + <div class="section center-align"> + <div class="grey-text text-darken-4 headline"><#if account.getID()??>${locale.getString("title.account.edit")}<#else>${locale.getString("title.account.new")}</#if></div> + </div> + </div> + <div class="container"> + <#import "../validation.ftl" as validation> + <form name="NewAccount" action="/accounts/newAccount" method="post"> + <input type="hidden" name="ID" value="<#if account.getID()??>${account.getID()}</#if>"> + + <#-- name --> + <div class="row"> + <div class="input-field col s12 m12 l8 offset-l2"> + <input id="account-name" type="text" name="name" <@validation.validation "name"/> value="<#if account.getName()??>${account.getName()}</#if>"> + <label for="account-name">${locale.getString("account.new.label.name")}</label> + </div> + </div> + <br> + + <#-- buttons --> + <div class="row hide-on-small-only"> + <div class="col m6 l4 offset-l2 right-align"> + <a href="/accounts" class="waves-effect waves-light btn budgetmaster-blue"><i class="material-icons left">clear</i>${locale.getString("cancel")}</a> + </div> + + <div class="col m6 l4 left-align"> + <button class="btn waves-effect waves-light budgetmaster-blue" type="submit" name="action"> + <i class="material-icons left">save</i>${locale.getString("save")} + </button> + </div> + </div> + <div class="hide-on-med-and-up"> + <div class="row center-align"> + <div class="col s12"> + <a href="/accounts" class="waves-effect waves-light btn budgetmaster-blue"><i class="material-icons left">clear</i>${locale.getString("cancel")}</a> + </div> + </div> + <div class="row center-align"> + <div class="col s12"> + <button class="btn waves-effect waves-light budgetmaster-blue" type="submit" name="buttonSave"> + <i class="material-icons left">save</i>${locale.getString("save")} + </button> + </div> + </div> + </div> + </form> + </div> + </div> + </main> + + <!-- Scripts--> + <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script> + <script src="/js/main.js"></script> + </body> +</html> \ No newline at end of file -- GitLab