#23 Transaction import: new period

This commit is contained in:
2021-09-01 22:41:18 +02:00
parent 6a3359ea5c
commit 70218ad7dc
14 changed files with 235 additions and 90 deletions

View File

@@ -2,7 +2,29 @@ package de.financer.dto;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import java.util.List;
public class CreateUploadedTransactionsRequestDto {
private List<CreateUploadedTransactionsRequestEntry> entries;
private Long newPeriodOnRecurringTransactionId;
public List<CreateUploadedTransactionsRequestEntry> getEntries() {
return entries;
}
public void setEntries(List<CreateUploadedTransactionsRequestEntry> entries) {
this.entries = entries;
}
public Long getNewPeriodOnRecurringTransactionId() {
return newPeriodOnRecurringTransactionId;
}
public void setNewPeriodOnRecurringTransactionId(Long newPeriodOnRecurringTransactionId) {
this.newPeriodOnRecurringTransactionId = newPeriodOnRecurringTransactionId;
}
public static class CreateUploadedTransactionsRequestEntry {
private String fromAccountKey;
private String toAccountKey;
private String amount;
@@ -89,4 +111,5 @@ public class CreateUploadedTransactionsRequestDto {
public void setRecurringTransactionId(String recurringTransactionId) {
this.recurringTransactionId = recurringTransactionId;
}
}
}

View File

@@ -10,7 +10,9 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("periods")
@@ -26,7 +28,8 @@ public class PeriodController {
LOGGER.debug("/periods/closeCurrentExpensePeriod called");
}
final ResponseEntity responseEntity = this.periodService.closeCurrentExpensePeriod().toResponseEntity();
final ResponseEntity responseEntity = this.periodService.closeCurrentExpensePeriod(Optional.empty())
.toResponseEntity();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("/periods/closeCurrentExpensePeriod returns with %s", responseEntity));

View File

@@ -168,12 +168,12 @@ public class TransactionController {
}
@PostMapping(value = "/transactions/upload")
public ResponseEntity createTransaction(@RequestBody Collection<CreateUploadedTransactionsRequestDto> requestDtos) {
public ResponseEntity createTransaction(@RequestBody CreateUploadedTransactionsRequestDto requestDto) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("POST /transactions/upload got parameters: %s", requestDtos));
LOGGER.debug(String.format("POST /transactions/upload got parameters: %s", requestDto));
}
final ResponseReason responseReason = this.transactionService.createTransactions(requestDtos);
final ResponseReason responseReason = this.transactionService.createTransactions(requestDto);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("POST /transactions/upload returns with %s", responseReason.name()));

View File

@@ -45,14 +45,17 @@ public class PeriodService {
/**
* This method closes the current expense period and opens a new one.
*
* @param endDate an optional end date timestamp for the currently open expense period. If none is given the current
* timestamp is used
*
* @return {@link ResponseReason#OK} if the operation succeeded, {@link ResponseReason#UNKNOWN_ERROR} if an
* unexpected exception occurred.
*/
@Transactional(propagation = Propagation.REQUIRED)
public ResponseReason closeCurrentExpensePeriod() {
public ResponseReason closeCurrentExpensePeriod(Optional<LocalDateTime> endDate) {
final Period currentPeriod = this.getCurrentExpensePeriod();
final Period nextPeriod = new Period();
final LocalDateTime now = LocalDateTime.now();
final LocalDateTime now = endDate.orElse(LocalDateTime.now());
ResponseReason response;
currentPeriod.setEnd(now);

View File

@@ -22,6 +22,8 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
@@ -418,15 +420,16 @@ public class TransactionService {
/**
* This method creates multiple transactions in one batch based on the given parameters. In case of invalid
* parameters the creation aborts and the first issue found is returned.
* parameters the creation aborts and the first issue found is returned. If requested via the given parameter
* the method will also close and open expense periods.
*
* @param requestDtos parameters to create the transactions for
* @param requestDto parameter to create the transactions for
* @return {@link ResponseReason#CREATED CREATED} in case of success, another instance of {@link ResponseReason}
* otherwise. Never <code>null</code>
*/
@Transactional(propagation = Propagation.REQUIRED)
public ResponseReason createTransactions(Collection<CreateUploadedTransactionsRequestDto> requestDtos) {
requestDtos.stream().forEach(dto -> {
public ResponseReason createTransactions(CreateUploadedTransactionsRequestDto requestDto) {
requestDto.getEntries().stream().forEach(dto -> {
final ResponseReason responseReason;
if(StringUtils.isNotEmpty(dto.getRecurringTransactionId())) {
@@ -435,6 +438,15 @@ public class TransactionService {
.orElseThrow(() -> new FinancerServiceException(
ResponseReason.RECURRING_TRANSACTION_NOT_FOUND));
if(recurringTransaction.getId().equals(requestDto.getNewPeriodOnRecurringTransactionId())) {
this.periodService
.closeCurrentExpensePeriod(Optional.of(
LocalDateTime.of(
LocalDate.parse(dto.getDate(),
DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat())),
LocalTime.now())));
}
responseReason = this.createTransaction(dto.getFromAccountKey(),
dto.getToAccountKey(),
Long.valueOf(dto.getAmount()),

View File

@@ -19,8 +19,12 @@ import de.financer.transactionUpload.TransactionUploadWorker;
import de.financer.transactionUpload.TransactionUploadWorkerFactory;
import de.financer.transactionUpload.UploadedTransaction;
import de.financer.util.ControllerUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;
@@ -180,6 +184,17 @@ public class TransactionController {
@GetMapping("/uploadTransactions")
public String uploadTransaction(Model model) {
final ResponseEntity<Iterable<RecurringTransaction>> response = new GetAllRecurringTransactionsTemplate()
.exchange(this.financerConfig);
final List<RecurringTransaction> recurringTransactionList = new ArrayList<>();
final RecurringTransaction emptyRecurring = new RecurringTransaction();
emptyRecurring.setDescription("-");
recurringTransactionList.add(emptyRecurring);
recurringTransactionList.addAll(IterableUtils.toList(response.getBody()));
model.addAttribute("recurringTransactions", recurringTransactionList);
model.addAttribute("form", new UploadTransactionsForm());
model.addAttribute("formats", TransactionUploadFormat.values());
@@ -201,10 +216,30 @@ public class TransactionController {
final Collection<UploadedTransaction> uploadedTransactions = worker.process(form.getFile());
return _uploadTransactions(model, Optional.empty(), CreateUploadedTransactionForm.of(uploadedTransactions));
Long newPeriodOnRecurringTransaction = null;
if(BooleanUtils.isTrue(form.getEnableNewPeriodOnRecurringTransaction())) {
if(NumberUtils.isCreatable(form.getNewPeriodOnRecurringTransaction())) {
newPeriodOnRecurringTransaction = Long.valueOf(form.getNewPeriodOnRecurringTransaction());
}
}
return _uploadTransactions(model, Optional.empty(),
CreateUploadedTransactionForm.of(uploadedTransactions, newPeriodOnRecurringTransaction));
}
catch(Exception e) {
// TODO
final ResponseEntity<Iterable<RecurringTransaction>> response = new GetAllRecurringTransactionsTemplate()
.exchange(this.financerConfig);
final List<RecurringTransaction> recurringTransactionList = new ArrayList<>();
final RecurringTransaction emptyRecurring = new RecurringTransaction();
emptyRecurring.setDescription("-");
recurringTransactionList.add(emptyRecurring);
recurringTransactionList.addAll(IterableUtils.toList(response.getBody()));
model.addAttribute("recurringTransactions", recurringTransactionList);
model.addAttribute("errorMessage", "KAPUTT");
model.addAttribute("formats", TransactionUploadFormat.values());
model.addAttribute("form", form);
@@ -220,38 +255,47 @@ public class TransactionController {
@PostMapping("/createUploadedTransactions")
public String createUploadedTransactions(CreateUploadedTransactionForm form, Model model) {
try {
final Collection<CreateUploadedTransactionsRequestDto> dtos =
final List<CreateUploadedTransactionsRequestDto.CreateUploadedTransactionsRequestEntry> entries =
form.getEntries()
.stream()
.filter(CreateUploadedTransactionForm.CreateUploadedTransactionFormEntry::getCreate)
.map(e -> {
final CreateUploadedTransactionsRequestDto dto = new CreateUploadedTransactionsRequestDto();
final CreateUploadedTransactionsRequestDto.CreateUploadedTransactionsRequestEntry entry =
new CreateUploadedTransactionsRequestDto.CreateUploadedTransactionsRequestEntry();
dto.setAmount(e.getAmount().toString());
dto.setDate(ControllerUtils.formatDate(this.financerConfig, e.getDate()));
dto.setDescription(e.getDescription());
dto.setFromAccountKey(e.getFromAccountKey());
dto.setToAccountKey(e.getToAccountKey());
dto.setRecurringTransactionId(Optional.ofNullable(e.getRecurringTransactionId())
entry.setAmount(e.getAmount().toString());
entry.setDate(ControllerUtils.formatDate(this.financerConfig, e.getDate()));
entry.setDescription(e.getDescription());
entry.setFromAccountKey(e.getFromAccountKey());
entry.setToAccountKey(e.getToAccountKey());
entry.setRecurringTransactionId(Optional.ofNullable(e.getRecurringTransactionId())
.map(id -> id.toString())
.orElse(null));
dto.setTaxRelevant(e.getTaxRelevant());
entry.setTaxRelevant(e.getTaxRelevant());
if (e.getFile() != null && StringUtils.isNotEmpty(e.getFile().getOriginalFilename())) {
try {
dto.setFileContent(Base64.getEncoder().encodeToString(e.getFile().getBytes()));
dto.setFileName(e.getFile().getOriginalFilename());
entry.setFileContent(Base64.getEncoder().encodeToString(e.getFile().getBytes()));
entry.setFileName(e.getFile().getOriginalFilename());
} catch (IOException ioe) {
// TODO No file for us :(
}
}
return dto;
return entry;
})
.collect(Collectors.toList());
final CreateUploadedTransactionsRequestDto dto = new CreateUploadedTransactionsRequestDto();
// We need to reverse the entries because of the actual booking order
// In the UI it is sorted descending, but we need to book ascending
Collections.reverse(entries);
dto.setEntries(entries);
dto.setNewPeriodOnRecurringTransactionId(form.getNewPeriodOnRecurringTransaction());
final ResponseReason responseReason = FinancerRestTemplate
.exchangePost(this.financerConfig, Function.TR_CREATE_UPLOADED_TRANSACTIONS, dtos);
.exchangePost(this.financerConfig, Function.TR_CREATE_UPLOADED_TRANSACTIONS, dto);
if (!ResponseReason.CREATED.equals(responseReason)) {
return _uploadTransactions(model, Optional.of(responseReason), form);

View File

@@ -9,11 +9,13 @@ import java.util.List;
public class CreateUploadedTransactionForm {
private List<CreateUploadedTransactionFormEntry> entries = new ArrayList<>();
private Long newPeriodOnRecurringTransaction;
public static CreateUploadedTransactionForm of(Collection<UploadedTransaction> uploadedTransactions) {
public static CreateUploadedTransactionForm of(Collection<UploadedTransaction> uploadedTransactions, Long newPeriodOnRecurringTransaction) {
final CreateUploadedTransactionForm form = new CreateUploadedTransactionForm();
uploadedTransactions.stream().forEach(e -> form.getEntries().add(CreateUploadedTransactionFormEntry.of(e)));
form.setNewPeriodOnRecurringTransaction(newPeriodOnRecurringTransaction);
return form;
}
@@ -26,6 +28,14 @@ public class CreateUploadedTransactionForm {
this.entries = entries;
}
public Long getNewPeriodOnRecurringTransaction() {
return newPeriodOnRecurringTransaction;
}
public void setNewPeriodOnRecurringTransaction(Long newPeriodOnRecurringTransaction) {
this.newPeriodOnRecurringTransaction = newPeriodOnRecurringTransaction;
}
public static final class CreateUploadedTransactionFormEntry {
private Boolean create;
private String fromAccountKey;

View File

@@ -5,6 +5,8 @@ import org.springframework.web.multipart.MultipartFile;
public class UploadTransactionsForm {
private String format;
private MultipartFile file;
private String newPeriodOnRecurringTransaction;
private Boolean enableNewPeriodOnRecurringTransaction;
public String getFormat() {
return format;
@@ -21,4 +23,20 @@ public class UploadTransactionsForm {
public void setFile(MultipartFile file) {
this.file = file;
}
public String getNewPeriodOnRecurringTransaction() {
return newPeriodOnRecurringTransaction;
}
public void setNewPeriodOnRecurringTransaction(String newPeriodOnRecurringTransaction) {
this.newPeriodOnRecurringTransaction = newPeriodOnRecurringTransaction;
}
public Boolean getEnableNewPeriodOnRecurringTransaction() {
return enableNewPeriodOnRecurringTransaction;
}
public void setEnableNewPeriodOnRecurringTransaction(Boolean enableNewPeriodOnRecurringTransaction) {
this.enableNewPeriodOnRecurringTransaction = enableNewPeriodOnRecurringTransaction;
}
}

View File

@@ -176,6 +176,9 @@ financer.upload-transactions.label.format=Format\:
financer.upload-transactions.format.MT940_CSV=MT940 CSV
financer.upload-transactions.label.file=File\:
financer.upload-transactions.submit=Upload transactions
financer.upload-transactions.label.new-period-on-recurring-transaction-summary=New period on encountering a certain recurring transaction
financer.upload-transactions.label.new-period-on-recurring-transaction=Recurring transaction\:
financer.upload-transactions.label.new-period-on-recurring-transaction-enable=Enable\:
financer.create-upload-transactions.title=financer\: create uploaded transactions
financer.create-upload-transactions.table-header.create=Create
@@ -272,8 +275,8 @@ financer.heading.account-edit=financer\: edit account
financer.cancel-back-to-overview=Cancel and back to overview
financer.back-to-overview=Back to overview
financer.show-actions=Show...
financer.show-options=Show options...
financer.chart.account-group-expenses-current-period.title=Expenses of the current period grouped by account group
financer.chart.account-group-expenses-for-period.title=Expenses for period from {0} to {1} grouped by account group

View File

@@ -176,6 +176,9 @@ financer.upload-transactions.label.format=Format\:
financer.upload-transactions.format.MT940_CSV=MT940 CSV
financer.upload-transactions.label.file=Datei\:
financer.upload-transactions.submit=Buchungen hochladen
financer.upload-transactions.label.new-period-on-recurring-transaction-summary=Neue Periode bei wiederkehrender Buchung
financer.upload-transactions.label.new-period-on-recurring-transaction=Wiederkehrende Buchung\:
financer.upload-transactions.label.new-period-on-recurring-transaction-enable=Aktiv\:
financer.create-upload-transactions.title=financer\: Erstelle hochgeladene Buchungen
financer.create-upload-transactions.table-header.create=Erstellen
@@ -272,6 +275,7 @@ financer.heading.account-edit=financer\: Bearbeite Konto
financer.cancel-back-to-overview=Abbrechen und zur\u00FCck zur \u00DCbersicht
financer.back-to-overview=Zur\u00FCck zur \u00DCbersicht
financer.show-actions=Anzeigen...
financer.show-options=Zeige Optionen...
financer.chart.account-group-expenses-current-period.title=Ausgaben in der aktuellen Periode gruppiert nach Konto-Gruppe
financer.chart.account-group-expenses-for-period.title=Ausgaben in der Periode vom {0} bis {1} gruppiert nach Konto-Gruppe

View File

@@ -3,6 +3,8 @@ v48 -> v49:
only the active ones
- #11 It is now possible to specify a date during creation of a transaction from a recurring transaction
- #25 Added the possibility to edit accounts
- #23 Added an option to specify a recurring transaction during transaction upload that will close and open expense
periods
v47 -> v48:
- #20 Added new property 'transaction type' to a transaction, denoting the type of the transaction, e.g. asset swap,

View File

@@ -389,3 +389,12 @@ input[type=submit] {
.cal-day-con-today {
background-color: #f5e5b8;
}
.transaction-upload-fieldset {
margin-left: 1em;
margin-top: 0px !important;
}
.transaction-upload-fieldset * {
width: 17.75em !important;
}

View File

@@ -66,6 +66,7 @@
</td>
</tr>
</table>
<input type="hidden" id="inputNewPeriodOnRecurringTransaction" th:field="*{newPeriodOnRecurringTransaction}"/>
<input type="submit" th:value="#{financer.create-upload-transactions.submit}"/>
</form>
<div th:replace="includes/footer :: footer"/>

View File

@@ -22,6 +22,19 @@
</select>
<label for="inputFile" th:text="#{financer.upload-transactions.label.file}" />
<input type="file" id="inputFile" th:field="*{file}" />
<details>
<summary th:text="#{financer.show-options}"/>
<fieldset class="transaction-upload-fieldset">
<legend th:text="#{financer.upload-transactions.label.new-period-on-recurring-transaction-summary}"/>
<label for="inputEnableNewPeriodOnRecurringTransaction" th:text="#{financer.upload-transactions.label.new-period-on-recurring-transaction-enable}"/>
<input type="checkbox" id="inputEnableNewPeriodOnRecurringTransaction" th:field="*{enableNewPeriodOnRecurringTransaction}" />
<label for="inputRecurringTransaction" th:text="#{financer.upload-transactions.label.new-period-on-recurring-transaction}" />
<select th:field="*{newPeriodOnRecurringTransaction}" id="inputRecurringTransaction">
<option th:each="rt : ${recurringTransactions}" th:value="${rt.id}"
th:text="${rt.description}"/>
</select>
</fieldset>
</details>
<input type="submit" th:value="#{financer.upload-transactions.submit}" />
</form>
<div th:replace="includes/footer :: footer" />