diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java index 6877d55a6650248237204886a46ee3d64f1fb1da..a5b20376badad96f52173c005632f924b7fd9e06 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/TransactionImportController.java @@ -2,19 +2,21 @@ package de.deadlocker8.budgetmaster.transactions; import de.deadlocker8.budgetmaster.controller.BaseController; import de.deadlocker8.budgetmaster.services.HelpersService; +import de.deadlocker8.budgetmaster.transactions.csvImport.CsvImport; import de.deadlocker8.budgetmaster.transactions.csvImport.CsvParser; import de.deadlocker8.budgetmaster.transactions.csvImport.CsvRow; import de.deadlocker8.budgetmaster.utils.Mappings; 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.validation.FieldError; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.WebRequest; -import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; @@ -26,19 +28,20 @@ public class TransactionImportController extends BaseController { private static class ModelAttributes { + public static final String ERROR = "error"; public static final String ERROR_UPLOAD = "errorUpload"; } private static class ReturnValues { public static final String TRANSACTION_IMPORT = "transactions/transactionImport"; + public static final String REDIRECT_IMPORT = "redirect:/transactionImport"; public static final String REDIRECT_CANCEL = "redirect:/transactionImport/cancel"; - } private static class RequestAttributeNames { - public static final String IMPORTED_FILE = "importedFile"; + public static final String CSV_IMPORT = "csvImport"; public static final String CSV_ROWS = "csvRows"; } @@ -55,35 +58,46 @@ public class TransactionImportController extends BaseController @GetMapping public String transactionImport(HttpServletRequest request, Model model) { + model.addAttribute(RequestAttributeNames.CSV_IMPORT, new CsvImport(null, ";")); return ReturnValues.TRANSACTION_IMPORT; } @PostMapping("/upload") - public String upload(WebRequest request, Model model, @RequestParam("file") MultipartFile file) + public String upload(WebRequest request, + Model model, + @ModelAttribute("CsvImport") CsvImport csvImport, + BindingResult bindingResult) { - if(file.isEmpty()) + if(csvImport.file().isEmpty()) { return ReturnValues.REDIRECT_CANCEL; } - try + if(!csvImport.isValidSeparator()) { - final String csvString = new String(file.getBytes(), StandardCharsets.UTF_8); - final List<CsvRow> csvRows = CsvParser.parseCsv(csvString, ';'); + bindingResult.addError(new FieldError("CsvImport", "separator", "", false, new String[]{"warning.transaction.import.separator"}, null, null)); + } - String fileName = file.getOriginalFilename(); - if(fileName == null) - { - fileName = file.getName(); - } + if(bindingResult.hasErrors()) + { + model.addAttribute(ModelAttributes.ERROR, bindingResult); + request.setAttribute(RequestAttributeNames.CSV_IMPORT, csvImport, RequestAttributes.SCOPE_SESSION); + return ReturnValues.TRANSACTION_IMPORT; + } - request.setAttribute(RequestAttributeNames.IMPORTED_FILE, fileName, RequestAttributes.SCOPE_SESSION); + try + { + final String csvString = new String(csvImport.file().getBytes(), StandardCharsets.UTF_8); + final List<CsvRow> csvRows = CsvParser.parseCsv(csvString, csvImport.separator().charAt(0)); + + request.setAttribute(RequestAttributeNames.CSV_IMPORT, csvImport, RequestAttributes.SCOPE_SESSION); request.setAttribute(RequestAttributeNames.CSV_ROWS, csvRows, RequestAttributes.SCOPE_SESSION); } catch(Exception e) { LOGGER.error("CSV upload failed", e); + // TODO: show in html model.addAttribute(ModelAttributes.ERROR_UPLOAD, e.getMessage()); } @@ -93,9 +107,9 @@ public class TransactionImportController extends BaseController @GetMapping("/cancel") public String cancel(WebRequest request) { - request.removeAttribute(RequestAttributeNames.IMPORTED_FILE, RequestAttributes.SCOPE_SESSION); + request.removeAttribute(RequestAttributeNames.CSV_IMPORT, RequestAttributes.SCOPE_SESSION); request.removeAttribute(RequestAttributeNames.CSV_ROWS, RequestAttributes.SCOPE_SESSION); - return ReturnValues.TRANSACTION_IMPORT; + return ReturnValues.REDIRECT_IMPORT; } } \ No newline at end of file diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvImport/CsvImport.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvImport/CsvImport.java new file mode 100644 index 0000000000000000000000000000000000000000..c5e94490088f33f598a80bb173eafd1f32f6544c --- /dev/null +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/transactions/csvImport/CsvImport.java @@ -0,0 +1,41 @@ +package de.deadlocker8.budgetmaster.transactions.csvImport; + +import org.springframework.web.multipart.MultipartFile; + +public record CsvImport(MultipartFile file, String separator) +{ + @Override + public String toString() + { + return "CsvImport{" + + "file=" + file + + ", separator='" + separator + '\'' + + '}'; + } + + public boolean isValidSeparator() + { + if(separator == null) + { + return false; + } + + return separator.strip().length() == 1; + } + + public String getFileName() + { + if(file == null) + { + return null; + } + + final String fileName = file.getOriginalFilename(); + if(fileName == null) + { + return file.getName(); + } + + return fileName; + } +} diff --git a/BudgetMasterServer/src/main/resources/languages/base_de.properties b/BudgetMasterServer/src/main/resources/languages/base_de.properties index f6aa6df888f62f222ff6d5bee23b7092fe7e08aa..1f0e210fd3a236329c4da8d1b561c58175cb01c5 100644 --- a/BudgetMasterServer/src/main/resources/languages/base_de.properties +++ b/BudgetMasterServer/src/main/resources/languages/base_de.properties @@ -232,6 +232,7 @@ warning.empty.git.url=Bitte gib die URL zum git-Server ein. warning.empty.git.branch.name=Bitte gib den Namen des git-Branches ein. warning.empty.git.user.name=Bitte gib deinen git-Nutzernamen ein. warning.empty.git.token=Bitte gib dein git-Zugriffstoken ein. +warning.transaction.import.separator=Ungültiges Trennzeichen. Bitte genau ein Zeichen eingeben. # UI @@ -370,6 +371,7 @@ transactions.recurring.headline=Aktive wiederholende Buchungen transactions.recurring.placeholder=Keine aktiven wiederholenden Buchungen transactions.import.overview=Übersicht transactions.import.column=Spalte +transactions.import.separator=Trennzeichen repeating.button.add=Wiederholung hinzufügen repeating.button.remove=Wiederholung entfernen diff --git a/BudgetMasterServer/src/main/resources/languages/base_en.properties b/BudgetMasterServer/src/main/resources/languages/base_en.properties index 79df3e632435209785eb2d1f733461f8da42cb5f..9d7e57ce55685cfc7bea45ed7631c08c8314e45e 100644 --- a/BudgetMasterServer/src/main/resources/languages/base_en.properties +++ b/BudgetMasterServer/src/main/resources/languages/base_en.properties @@ -21,7 +21,6 @@ error.database.import.invalid.json=The uploaded JSON File is invalid. error.database.import.unknown.version=The uploaded JSON File does not contain a valid BudgetMaster database definition. error.database.import.version.too.old=The uploaded JSON File is too old. Version: {0} Minimum version: {1} - # TITLE title.incomes=Incomes title.income=Income @@ -233,6 +232,7 @@ warning.empty.git.url=Please insert the git server's URL. warning.empty.git.branch.name=Please insert the git branch name. warning.empty.git.user.name=Please insert your git username. warning.empty.git.token=Please insert your git access token. +warning.transaction.import.separator=Invalid separator. Please enter exactly one character. # UI @@ -370,6 +370,7 @@ transactions.recurring.headline=Active Recurring Transactions transactions.recurring.placeholder=No active recurring transactions transactions.import.overview=Overview transactions.import.column=Column +transactions.import.separator=Separator repeating.button.add=Add repetition repeating.button.remove=Remove repetition diff --git a/BudgetMasterServer/src/main/resources/templates/transactions/transactionImport.ftl b/BudgetMasterServer/src/main/resources/templates/transactions/transactionImport.ftl index 41cac76727a76a641aee055c5c2ec40b3978ae22..cd7c01f51ab5809f9731900bae1396231a423cef 100644 --- a/BudgetMasterServer/src/main/resources/templates/transactions/transactionImport.ftl +++ b/BudgetMasterServer/src/main/resources/templates/transactions/transactionImport.ftl @@ -1,6 +1,7 @@ <html> <head> <#import "../helpers/header.ftl" as header> + <#import "../helpers/validation.ftl" as validation> <@header.globals/> <@header.header "BudgetMaster - ${locale.getString('menu.transactions.import')}"/> <#import "/spring.ftl" as s> @@ -21,10 +22,10 @@ <@header.content> <div class="container"> - <#if importedFile??> + <#if !error?? && csvImport.getFileName()??> <div class="row center-align"> <div class="col s12 m12 l8 offset-l2 headline-small text-green"> - <i class="fas fa-file-csv"></i> ${importedFile} + <i class="fas fa-file-csv"></i> ${csvImport.getFileName()} </div> </div> @@ -52,7 +53,7 @@ </html> <#macro csvUpload> - <form id="form-csv-import" method="POST" action="<@s.url '/transactionImport/upload'/>" enctype="multipart/form-data" accept-charset="UTF-8"> + <form id="form-csv-import" name="CsvImport" method="POST" action="<@s.url '/transactionImport/upload'/>" enctype="multipart/form-data" accept-charset="UTF-8"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> <div class="row"> @@ -67,6 +68,13 @@ </div> </div> + <div class="row"> + <div class="input-field col s2 offset-s5"> + <input id="separator" type="text" name="separator" <@validation.validation "separator" "center-align"/> value="<#if csvImport??>${csvImport.separator()}</#if>"> + <label class="input-label" for="separator">${locale.getString("transactions.import.separator")}</label> + </div> + </div> + <div class="row"> <div class="col s12 center-align"> <@header.buttonSubmit name='action' icon='cloud_upload' localizationKey='settings.database.import' id='button-confirm-csv-import' classes='text-white'/>