Add file uploading to transaction creation
This commit is contained in:
@@ -126,7 +126,7 @@ public class AccountController {
|
||||
|
||||
model.addAttribute("account", account);
|
||||
model.addAttribute("transactions", transactions);
|
||||
model.addAttribute("showActions", true);
|
||||
model.addAttribute("showActionDelete", true);
|
||||
model.addAttribute("isClosed", AccountStatus.CLOSED.equals(account.getStatus()));
|
||||
}
|
||||
catch(FinancerRestException e) {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package de.financer.controller;
|
||||
|
||||
import de.financer.config.FinancerConfig;
|
||||
import de.financer.model.File;
|
||||
import de.financer.template.FinancerRestTemplate;
|
||||
import de.financer.template.exception.FinancerRestException;
|
||||
import de.financer.util.ControllerUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
@Controller
|
||||
public class FileController {
|
||||
@Autowired
|
||||
private FinancerConfig financerConfig;
|
||||
|
||||
@GetMapping("/downloadFile")
|
||||
public ResponseEntity<Resource> downloadFile(Long fileId) {
|
||||
final UriComponentsBuilder builder = UriComponentsBuilder
|
||||
.fromHttpUrl(ControllerUtils.buildUrl(this.financerConfig, Function.FILE_GET))
|
||||
.queryParam("fileId", fileId);
|
||||
|
||||
final File file;
|
||||
|
||||
try {
|
||||
file = FinancerRestTemplate.exchangeGet(builder, File.class);
|
||||
} catch (FinancerRestException e) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getName() + "\"")
|
||||
.body(new ByteArrayResource(Base64.getDecoder().decode(file.getContent())));
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,9 @@ public enum Function {
|
||||
RT_CREATE_TRANSACTION("recurringTransactions/createTransaction"),
|
||||
|
||||
P_GET_CURRENT_EXPENSE_PERIOD("periods/getCurrentExpensePeriod"),
|
||||
P_CLOSE_CURRENT_EXPENSE_PERIOD("periods/closeCurrentExpensePeriod");
|
||||
P_CLOSE_CURRENT_EXPENSE_PERIOD("periods/closeCurrentExpensePeriod"),
|
||||
|
||||
FILE_GET("file");
|
||||
|
||||
private final String path;
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -32,7 +34,7 @@ public class TransactionController {
|
||||
@GetMapping("/searchTransactions")
|
||||
public String searchTransaction(Model model) {
|
||||
model.addAttribute("form", new SearchTransactionsForm());
|
||||
model.addAttribute("showActions", false);
|
||||
model.addAttribute("showActionDelete", false);
|
||||
ControllerUtils.addVersionAttribute(model, this.financerConfig);
|
||||
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
|
||||
|
||||
@@ -60,7 +62,7 @@ public class TransactionController {
|
||||
}
|
||||
|
||||
model.addAttribute("form", form);
|
||||
model.addAttribute("showActions", false);
|
||||
model.addAttribute("showActionDelete", false);
|
||||
ControllerUtils.addVersionAttribute(model, this.financerConfig);
|
||||
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
|
||||
|
||||
@@ -117,6 +119,15 @@ public class TransactionController {
|
||||
requestDto.setDescription(form.getDescription());
|
||||
requestDto.setTaxRelevant(form.getTaxRelevant());
|
||||
|
||||
if (form.getFile() != null) {
|
||||
try {
|
||||
requestDto.setFileContent(Base64.getEncoder().encodeToString(form.getFile().getBytes()));
|
||||
requestDto.setFileName(form.getFile().getOriginalFilename());
|
||||
} catch (IOException ioe) {
|
||||
// TODO No file for us :(
|
||||
}
|
||||
}
|
||||
|
||||
final ResponseReason responseReason = FinancerRestTemplate
|
||||
.exchangePost(this.financerConfig, Function.TR_CREATE_TRANSACTION, requestDto);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package de.financer.form;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
public class NewTransactionForm {
|
||||
private String fromAccountKey;
|
||||
private String toAccountKey;
|
||||
@@ -7,6 +9,7 @@ public class NewTransactionForm {
|
||||
private String date;
|
||||
private String description;
|
||||
private Boolean taxRelevant;
|
||||
private MultipartFile file;
|
||||
|
||||
public String getFromAccountKey() {
|
||||
return fromAccountKey;
|
||||
@@ -55,4 +58,12 @@ public class NewTransactionForm {
|
||||
public void setTaxRelevant(Boolean taxRelevant) {
|
||||
this.taxRelevant = taxRelevant;
|
||||
}
|
||||
|
||||
public MultipartFile getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public void setFile(MultipartFile file) {
|
||||
this.file = file;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,19 @@ public class FinancerRestTemplate<T> {
|
||||
return ResponseReason.fromResponseEntity(response);
|
||||
}
|
||||
|
||||
public static <R> R exchangeGet(UriComponentsBuilder builder, Class<R> type) throws FinancerRestException {
|
||||
final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
try {
|
||||
return restTemplate.exchange(builder.toUriString(), HttpMethod.GET, null, type).getBody();
|
||||
} catch (HttpStatusCodeException e) {
|
||||
final ResponseEntity<String> exceptionResponse = new ResponseEntity<>(e.getResponseBodyAsString(), e
|
||||
.getStatusCode());
|
||||
|
||||
throw new FinancerRestException(ResponseReason.fromResponseEntity(exceptionResponse));
|
||||
}
|
||||
}
|
||||
|
||||
public static <R> R exchangeGet(FinancerConfig financerConfig,
|
||||
Function endpoint,
|
||||
Class<R> type) throws FinancerRestException {
|
||||
|
||||
@@ -44,6 +44,7 @@ financer.transaction-new.label.amount=Amount\:
|
||||
financer.transaction-new.label.date=Date\:
|
||||
financer.transaction-new.label.description=Description\:
|
||||
financer.transaction-new.label.taxRelevant=Tax relevant\:
|
||||
financer.transaction-new.label.file=File\:
|
||||
financer.transaction-new.submit=Create transaction
|
||||
financer.transaction-new.account-type.BANK={0}|Bank|{1}{2}
|
||||
financer.transaction-new.account-type.CASH={0}|Cash|{1}{2}
|
||||
@@ -112,6 +113,7 @@ financer.account-details.details.balance=Current balance\:
|
||||
financer.account-details.details.group=Group\:
|
||||
financer.transaction-list.table-header.actions=Actions
|
||||
financer.transaction-list.table.actions.deleteTransaction=Delete
|
||||
financer.transaction-list.table.actions.downloadFile=Download file
|
||||
financer.transaction-list.table.recurring.yes=Yes
|
||||
financer.transaction-list.table.recurring.no=No
|
||||
financer.transaction-list.table.taxRelevant.true=Yes
|
||||
@@ -135,6 +137,7 @@ financer.search-transactions.show-query-options.toAccountGroup=toAccountGroup\:
|
||||
financer.search-transactions.show-query-options.date=date\: the date of the transaction in the format yyyy-mm-dd
|
||||
financer.search-transactions.show-query-options.recurring=recurring\: whether the transaction has been created from a recurring transaction
|
||||
financer.search-transactions.show-query-options.taxRelevant=taxRelevant\: whether the transaction is relevant for tax declaration
|
||||
financer.search-transactions.show-query-options.hasFile=hasFile\: whether the transaction has a file linked
|
||||
financer.search-transactions.show-query-options.period=period\: the period the transaction is assigned to
|
||||
financer.search-transactions.show-query-options.period.CURRENT=CURRENT\: denotes the current expense period
|
||||
financer.search-transactions.show-query-options.period.LAST=LAST\: denotes the last expense period
|
||||
@@ -242,4 +245,7 @@ financer.error-message.ACCOUNT_NOT_FOUND=The account could not be found!
|
||||
financer.error-message.DUPLICATE_ACCOUNT_KEY=An account with the given key already exists!
|
||||
financer.error-message.DUPLICATE_ACCOUNT_GROUP_NAME=An account group with the given key already exists!
|
||||
financer.error-message.ACCOUNT_GROUP_NOT_FOUND=The account group could not be found!
|
||||
financer.error-message.UNKNOWN_CHART_TYPE=The selected chart type is not known!
|
||||
financer.error-message.UNKNOWN_CHART_TYPE=The selected chart type is not known!
|
||||
financer.error-message.INVALID_HAS_FILE_VALUE=The value for parameter hasFile cannot be parsed!
|
||||
financer.error-message.INVALID_FILE_CONTENT=File content is missing!
|
||||
financer.error-message.INVALID_FILE_NAME=File name is missing!
|
||||
@@ -44,6 +44,7 @@ financer.transaction-new.label.amount=Betrag\:
|
||||
financer.transaction-new.label.date=Datum\:
|
||||
financer.transaction-new.label.description=Beschreibung\:
|
||||
financer.transaction-new.label.taxRelevant=Relevant f\u00FCr Steuererkl\u00E4rung\:
|
||||
financer.transaction-new.label.file=Datei\:
|
||||
financer.transaction-new.submit=Buchung erstellen
|
||||
financer.transaction-new.account-type.BANK={0}|Bank|{1}{2}
|
||||
financer.transaction-new.account-type.CASH={0}|Bar|{1}{2}
|
||||
@@ -112,6 +113,7 @@ financer.account-details.details.balance=Kontostand\:
|
||||
financer.account-details.details.group=Gruppe\:
|
||||
financer.transaction-list.table-header.actions=Aktionen
|
||||
financer.transaction-list.table.actions.deleteTransaction=L\u00F6schen
|
||||
financer.transaction-list.table.actions.downloadFile=Datei herunterladen
|
||||
financer.transaction-list.table.recurring.yes=Ja
|
||||
financer.transaction-list.table.recurring.no=Nein
|
||||
financer.transaction-list.table.taxRelevant.true=Ja
|
||||
@@ -135,6 +137,7 @@ financer.search-transactions.show-query-options.toAccountGroup=toAccountGroup\:
|
||||
financer.search-transactions.show-query-options.date=date\: das Datum der Buchung im Format jjjj-mm-tt
|
||||
financer.search-transactions.show-query-options.recurring=recurring\: ob die Buchung durch eine wiederkehrende Buchung erzeugt wurde
|
||||
financer.search-transactions.show-query-options.taxRelevant=taxRelevant\: ob die Buchung als steuerrelevant markiert wurde
|
||||
financer.search-transactions.show-query-options.hasFile=hasFile\: ob eine Datei der Buchung zugeordnet ist
|
||||
financer.search-transactions.show-query-options.period=period\: die Periode der Buchung
|
||||
financer.search-transactions.show-query-options.period.CURRENT=CURRENT\: bezeichnet die aktuelle Ausgabenperiode
|
||||
financer.search-transactions.show-query-options.period.LAST=LAST\: bezeichnet die letzte Ausgabenperiode
|
||||
@@ -241,4 +244,7 @@ financer.error-message.ACCOUNT_NOT_FOUND=Das ausgew\u00E4hlte Konto wurde nicht
|
||||
financer.error-message.DUPLICATE_ACCOUNT_KEY=Ein Konto mit diesem Schl\u00FCssel existiert bereits!
|
||||
financer.error-message.DUPLICATE_ACCOUNT_GROUP_NAME=Eine Konto-Gruppe mit diesem Namen existiert bereits!
|
||||
financer.error-message.ACCOUNT_GROUP_NOT_FOUND=Die ausgew\u00E4hlte Konto-Gruppe wurde nicht gefunden!
|
||||
financer.error-message.UNKNOWN_CHART_TYPE=Das ausgew\u00E4hlte Diagramm wurde nicht gefunden!
|
||||
financer.error-message.UNKNOWN_CHART_TYPE=Das ausgew\u00E4hlte Diagramm wurde nicht gefunden!
|
||||
financer.error-message.INVALID_HAS_FILE_VALUE=Der Wert des Parameters hasFile kann nicht verarbeitet werden!
|
||||
financer.error-message.INVALID_FILE_CONTENT=Der Inhalt der Datei fehlt!
|
||||
financer.error-message.INVALID_FILE_NAME=Der Dateiname fehlt!
|
||||
@@ -1,5 +1,7 @@
|
||||
v27 -> v28:
|
||||
- Add account end balance to account statistics for closed expense periods. This is currently not visible in the UI
|
||||
- Add file upload to transaction creation for e.g. invoices. They can be downloaded in the transaction overview of an
|
||||
account
|
||||
|
||||
v26 -> v27:
|
||||
- Changed sort order of accounts in overview page. The accounts are now sorted by the account type first (BCILES), then
|
||||
@@ -7,7 +9,7 @@ v26 -> v27:
|
||||
- Add tax relevance flag to transaction and recurring transaction creation. This flag denotes whether a transaction or
|
||||
the instances of a recurring transaction are relevant for a tax declaration. This is preparation for extended reports
|
||||
- Add searching of transactions via FQL (Financer Query Language)
|
||||
- Rework /transaction end point to better adhere to REST API requirements (proper HTTP return codes and HTTP method
|
||||
- Rework /transactions end point to better adhere to REST API requirements (proper HTTP return codes and HTTP method
|
||||
usage)
|
||||
|
||||
v25 -> v26:
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<span class="errorMessage" th:if="${errorMessage != null}" th:text="#{'financer.error-message.' + ${errorMessage}}"/>
|
||||
<a th:href="@{/accountOverview}" th:text="#{financer.cancel-back-to-overview}"/>
|
||||
<form id="new-transaction-form" action="#" th:action="@{/saveTransaction}" th:object="${form}"
|
||||
method="post">
|
||||
method="post" enctype="multipart/form-data">
|
||||
<label for="selectFromAccount" th:text="#{financer.transaction-new.label.from-account}"/>
|
||||
<select id="selectFromAccount" th:field="*{fromAccountKey}">
|
||||
<option th:each="acc : ${fromAccounts}" th:value="${acc.key}"
|
||||
@@ -31,8 +31,10 @@
|
||||
<input type="text" id="inputDescription" th:field="*{description}"/>
|
||||
<label for="inputTaxRelevant" th:text="#{financer.transaction-new.label.taxRelevant}" />
|
||||
<input type="checkbox" id="inputTaxRelevant" th:field="*{taxRelevant}" />
|
||||
<input type="submit" th:value="#{financer.transaction-new.submit}"/>
|
||||
<label for="inputFile" th:text="#{financer.transaction-new.label.file}" />
|
||||
<input type="file" id="inputFile" th:field="*{file}" />
|
||||
<input type="submit" th:value="#{financer.transaction-new.submit}" />
|
||||
</form>
|
||||
<div th:replace="includes/footer :: footer"/>
|
||||
<div th:replace="includes/footer :: footer" />
|
||||
</body>
|
||||
</html>
|
||||
@@ -31,6 +31,7 @@
|
||||
<li th:text="#{financer.search-transactions.show-query-options.date}" />
|
||||
<li th:text="#{financer.search-transactions.show-query-options.recurring}" />
|
||||
<li th:text="#{financer.search-transactions.show-query-options.taxRelevant}" />
|
||||
<li th:text="#{financer.search-transactions.show-query-options.hasFile}" />
|
||||
<li><span th:text="#{financer.search-transactions.show-query-options.period}" />
|
||||
<ul>
|
||||
<li th:text="#{financer.search-transactions.show-query-options.period.CURRENT}" />
|
||||
|
||||
@@ -21,10 +21,14 @@
|
||||
<td th:if="${transaction.recurring}" th:text="#{financer.transaction-list.table.recurring.yes}" />
|
||||
<td th:if="${!transaction.recurring}" th:text="#{financer.transaction-list.table.recurring.no}" />
|
||||
<td th:text="#{'financer.transaction-list.table.taxRelevant.' + ${transaction.taxRelevant}}" />
|
||||
<td th:if="${showActions}">
|
||||
<td nowrap>
|
||||
<div id="account-transaction-table-actions-container">
|
||||
<a th:href="@{/deleteTransaction(transactionId=${transaction.id}, accountKey=${account.key})}"
|
||||
th:text="#{financer.transaction-list.table.actions.deleteTransaction}"/>
|
||||
th:text="#{financer.transaction-list.table.actions.deleteTransaction}"
|
||||
th:if="${showActionDelete}" />
|
||||
<a th:href="@{/downloadFile(fileId=${transaction.fileId})}"
|
||||
th:text="#{financer.transaction-list.table.actions.downloadFile}"
|
||||
th:if="${transaction.fileId != null}" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user