#20 Add transaction type

This commit is contained in:
2021-08-08 23:38:41 +02:00
parent 7ffc597727
commit 0bb534c8b4
19 changed files with 373 additions and 104 deletions

View File

@@ -39,6 +39,8 @@ public class Transaction {
// the inline expense history chart and the expense/income/liability chart
// No UI to set the flag as its use is only for very special cases
private boolean expenseNeutral;
@Enumerated(EnumType.STRING)
private TransactionType transactionType;
public Long getId() {
return id;
@@ -123,4 +125,12 @@ public class Transaction {
public void setExpenseNeutral(boolean expenseNeutral) {
this.expenseNeutral = expenseNeutral;
}
public TransactionType getTransactionType() {
return transactionType;
}
public void setTransactionType(TransactionType transactionType) {
this.transactionType = transactionType;
}
}

View File

@@ -0,0 +1,51 @@
package de.financer.model;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* This enum specifies constants that specify the type of a transaction. Actual specification which type is used when
* is implemented in <code>financer-server#RuleService</code>
*/
public enum TransactionType {
/**
* Specifies that a transaction swaps money from one asset account (e.g. {@link AccountType#BANK BANK})
* to another one (e.g. {@link AccountType#CASH CASH})
*/
ASSET_SWAP,
/**
* Specifies that a transaction captured the process of buying something
*/
EXPENSE,
/**
* Specifies that a transaction was made to pay back a liability
*/
LIABILITY,
/**
* Specifies a transaction that increases the assets
*/
INCOME,
/**
* Specifies that a transaction was used to book the starting amount to a liability account.
*/
START_LIABILITY;
/**
* This method validates whether the given string represents a valid transaction type.
*
* @param type to check
* @return whether the given type represents a valid transaction type
*/
public static boolean isValidType(String type) {
return Arrays.stream(TransactionType.values()).anyMatch((accountType) -> accountType.name().equals(type));
}
public static List<String> valueList() {
return Arrays.stream(TransactionType.values()).map(TransactionType::name).collect(Collectors.toList());
}
}

View File

@@ -23,7 +23,7 @@ orderByExpression
regularExpression
: (stringExpression | intExpression | booleanExpression | dateExpression) ;
stringExpression
: field=IDENTIFIER operator=STRING_OPERATOR value=(STRING_VALUE | ACCOUNT_TYPE_VALUE) ;
: field=IDENTIFIER operator=STRING_OPERATOR value=STRING_VALUE ;
intExpression
: field=IDENTIFIER operator=(INT_OPERATOR | STRING_OPERATOR) value=INT_VALUE ;
@@ -122,7 +122,7 @@ L_PAREN : '(' ;
R_PAREN : ')' ;
IDENTIFIER : [a-zA-Z]+ ;
INT_VALUE : [0-9]+ ;
STRING_VALUE : '\'' [a-zA-Z0-9\-/.&%@$ ]+ '\'' ;
STRING_VALUE : '\'' [a-zA-Z0-9\-/.&%@$_ ]+ '\'' ;
DATE_VALUE : [0-9][0-9][0-9][0-9][-][0-9][0-9][-][0-9][0-9] ; // ANTLR does not support regex quantifiers
NEWLINE : ('\r'? '\n' | '\r')+ ;

View File

@@ -0,0 +1,133 @@
package database.common;
import de.financer.model.AccountType;
import de.financer.model.TransactionType;
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import static de.financer.model.AccountType.*;
import static de.financer.model.AccountType.BANK;
public class V48_0_1__calculateTransactionType extends BaseJavaMigration {
private static class TransactionContainer {
public final Long transactionId;
public final AccountType fromAccountType;
public final AccountType toAccountType;
public TransactionContainer(Long transactionId, AccountType fromAccountType, AccountType toAccountType) {
this.transactionId = transactionId;
this.fromAccountType = fromAccountType;
this.toAccountType = toAccountType;
}
}
private static final String GET_CONTAINERS_SQL = "SELECT t.id, fa.type, ta.type FROM \"transaction\" t INNER JOIN account fa ON fa.id = t.from_account_id INNER JOIN account ta ON ta.id = t.to_account_id";
private static final String UPDATE_TRANSACTION_SQL = "UPDATE \"transaction\" SET transaction_type = ? WHERE id = ?";
private Map<TransactionTypeDefinition, TransactionType> transactionTypeDefinitions;
@Override
public void migrate(Context context) throws Exception {
initTransactionTypes();
final List<TransactionContainer> cons = getContainers(context);
TransactionType transactionType;
for (TransactionContainer con : cons) {
transactionType = getTransactionType(con.fromAccountType, con.toAccountType);
updateTransaction(context, con.transactionId, transactionType.name());
}
}
private void updateTransaction(Context context, Long transactionId, String transactionType) throws SQLException {
try (PreparedStatement statementUpdate =
context
.getConnection()
.prepareStatement(UPDATE_TRANSACTION_SQL)) {
statementUpdate.setString(1, transactionType);
statementUpdate.setLong(2, transactionId);
statementUpdate.executeUpdate();
}
}
private List<TransactionContainer> getContainers(Context context) throws SQLException {
try (PreparedStatement statement =
context
.getConnection()
.prepareStatement(GET_CONTAINERS_SQL)) {
final ResultSet rs = statement.executeQuery();
final List<TransactionContainer> cons = new ArrayList<>();
while(rs.next()) {
cons.add(new TransactionContainer(
rs.getLong(1), // transaction id
AccountType.valueOf(rs.getString(2)), // from account type
AccountType.valueOf(rs.getString(3)) // to account type
));
}
return cons;
}
}
private void initTransactionTypes() {
this.transactionTypeDefinitions = new HashMap<>();
this.transactionTypeDefinitions
.put(TransactionTypeDefinition.withFrom(CASH, BANK)
.withTo(CASH, BANK), TransactionType.ASSET_SWAP);
this.transactionTypeDefinitions
.put(TransactionTypeDefinition.withFrom(CASH, BANK, LIABILITY)
.withTo(EXPENSE), TransactionType.EXPENSE);
this.transactionTypeDefinitions
.put(TransactionTypeDefinition.withFrom(CASH, BANK)
.withTo(LIABILITY), TransactionType.LIABILITY);
this.transactionTypeDefinitions
.put(TransactionTypeDefinition.withFrom(START, INCOME, LIABILITY)
.withTo(CASH, BANK), TransactionType.INCOME);
this.transactionTypeDefinitions
.put(TransactionTypeDefinition.withFrom(START)
.withTo(LIABILITY), TransactionType.START_LIABILITY);
}
private TransactionType getTransactionType(AccountType fromAccountType, AccountType toAccountType) {
return this.transactionTypeDefinitions.entrySet().stream()
.filter(ttdE -> ttdE.getKey().matches(fromAccountType, toAccountType))
.findFirst().map(ttd -> ttd.getValue()).orElse(null);
}
private static class TransactionTypeDefinition {
private Collection<AccountType> fromAccountTypes = new ArrayList<>();
private Collection<AccountType> toAccountTypes = new ArrayList<>();
public static TransactionTypeDefinition withFrom(AccountType... fromAccountTypes) {
TransactionTypeDefinition def = new TransactionTypeDefinition();
def.fromAccountTypes.addAll(Arrays.asList(fromAccountTypes));
return def;
}
public TransactionTypeDefinition withTo(AccountType... toAccountTypes) {
this.toAccountTypes.addAll(Arrays.asList(toAccountTypes));
return this;
}
public boolean matches(AccountType fromAccountType, AccountType toAccountType) {
return this.fromAccountTypes.contains(fromAccountType) && this.toAccountTypes.contains(toAccountType);
}
}
}

View File

@@ -50,7 +50,10 @@ public enum FieldMapping {
JoinKey.of(File.class), null, NotNullSyntheticHandler.class),
DESCRIPTION("description", Transaction_.DESCRIPTION, Transaction.class, NoopJoinHandler.class,
JoinKey.of(Transaction.class), null, StringHandler.class);
JoinKey.of(Transaction.class), null, StringHandler.class),
TRANSACTION_TYPE("transactionType", Transaction_.TRANSACTION_TYPE, Transaction.class, NoopJoinHandler.class,
JoinKey.of(Transaction.class), null, TransactionTypeHandler.class);
private final String fieldName;

View File

@@ -0,0 +1,38 @@
package de.financer.fql.field_handler;
import de.financer.fql.FQLException;
import de.financer.fql.FieldMapping;
import de.financer.fql.join_handler.JoinKey;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Predicate;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
public abstract class AbstractEnumTypeHandler<T extends Enum<?>> implements FieldHandler<String> {
private Class<T> clazz;
protected AbstractEnumTypeHandler(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public Predicate apply(FieldMapping fieldMapping, Map<JoinKey, From<?, ?>> froms, CriteriaBuilder criteriaBuilder, String value) {
return criteriaBuilder
.equal(froms.get(fieldMapping.getJoinKey()).get(fieldMapping.getAttributeName()), toEnumType(FieldHandlerUtils.removeQuotes(value)));
}
private T toEnumType(String value) {
if (value == null) {
throw new FQLException(String.format("NULL cannot be resolved to %s!", this.clazz.getName()));
}
try {
return (T) this.clazz.getMethod("valueOf", String.class).invoke(null, value.toUpperCase());
}
catch (IllegalArgumentException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new FQLException(e);
}
}
}

View File

@@ -1,32 +1,9 @@
package de.financer.fql.field_handler;
import de.financer.fql.FQLException;
import de.financer.fql.FieldMapping;
import de.financer.fql.join_handler.JoinKey;
import de.financer.model.AccountType;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Predicate;
import java.util.Map;
public class AccountTypeHandler implements FieldHandler<String> {
@Override
public Predicate apply(FieldMapping fieldMapping, Map<JoinKey, From<?, ?>> froms, CriteriaBuilder criteriaBuilder, String value) {
return criteriaBuilder
.equal(froms.get(fieldMapping.getJoinKey()).get(fieldMapping.getAttributeName()), toAccountType(FieldHandlerUtils.removeQuotes(value)));
}
private static AccountType toAccountType(String value) {
if (value == null) {
throw new FQLException("NULL cannot be solved to AccountType!");
}
try {
return AccountType.valueOf(value.toUpperCase());
}
catch (IllegalArgumentException e) {
throw new FQLException(e);
}
public class AccountTypeHandler extends AbstractEnumTypeHandler<AccountType> {
public AccountTypeHandler() {
super(AccountType.class);
}
}

View File

@@ -0,0 +1,9 @@
package de.financer.fql.field_handler;
import de.financer.model.TransactionType;
public class TransactionTypeHandler extends AbstractEnumTypeHandler<TransactionType> {
public TransactionTypeHandler() {
super(TransactionType.class);
}
}

View File

@@ -1,9 +1,7 @@
package de.financer.service;
import de.financer.config.FinancerConfig;
import de.financer.model.Account;
import de.financer.model.AccountType;
import de.financer.model.IntervalType;
import de.financer.model.*;
import de.jollyday.HolidayManager;
import de.jollyday.ManagerParameters;
import org.slf4j.Logger;
@@ -32,11 +30,13 @@ public class RuleService implements InitializingBean {
private Map<AccountType, Collection<AccountType>> bookingRules;
private Map<IntervalType, Period> intervalPeriods;
private Map<TransactionTypeDefinition, TransactionType> transactionTypeDefinitions;
@Override
public void afterPropertiesSet() {
initBookingRules();
initIntervalValues();
initTransactionTypes();
}
private void initIntervalValues() {
@@ -63,6 +63,30 @@ public class RuleService implements InitializingBean {
this.bookingRules.put(START, Arrays.asList(BANK, CASH, LIABILITY));
}
private void initTransactionTypes() {
this.transactionTypeDefinitions = new HashMap<>();
this.transactionTypeDefinitions
.put(TransactionTypeDefinition.withFrom(CASH, BANK)
.withTo(CASH, BANK), TransactionType.ASSET_SWAP);
this.transactionTypeDefinitions
.put(TransactionTypeDefinition.withFrom(CASH, BANK, LIABILITY)
.withTo(EXPENSE), TransactionType.EXPENSE);
this.transactionTypeDefinitions
.put(TransactionTypeDefinition.withFrom(CASH, BANK)
.withTo(LIABILITY), TransactionType.LIABILITY);
this.transactionTypeDefinitions
.put(TransactionTypeDefinition.withFrom(START, INCOME, LIABILITY)
.withTo(CASH, BANK), TransactionType.INCOME);
this.transactionTypeDefinitions
.put(TransactionTypeDefinition.withFrom(START)
.withTo(LIABILITY), TransactionType.START_LIABILITY);
}
/**
* This method returns the multiplier for the given from account.
* <p>
@@ -185,4 +209,42 @@ public class RuleService implements InitializingBean {
public boolean isWeekend(LocalDate now) {
return EnumSet.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY).contains(now.getDayOfWeek());
}
/**
* @param fromAccountType the from account
* @param toAccountType the to account
* @return the {@link TransactionType} matching the given from and to accounts or <code>null</code>
* if no matching transaction type was found
*/
public TransactionType getTransactionType(AccountType fromAccountType, AccountType toAccountType) {
return this.transactionTypeDefinitions.entrySet().stream()
.filter(ttdE -> ttdE.getKey().matches(fromAccountType, toAccountType))
.findFirst().map(ttd -> ttd.getValue()).orElse(null);
}
/**
* Helper class
*/
private static class TransactionTypeDefinition {
private Collection<AccountType> fromAccountTypes = new ArrayList<>();
private Collection<AccountType> toAccountTypes = new ArrayList<>();
public static TransactionTypeDefinition withFrom(AccountType... fromAccountTypes) {
TransactionTypeDefinition def = new TransactionTypeDefinition();
def.fromAccountTypes.addAll(Arrays.asList(fromAccountTypes));
return def;
}
public TransactionTypeDefinition withTo(AccountType... toAccountTypes) {
this.toAccountTypes.addAll(Arrays.asList(toAccountTypes));
return this;
}
public boolean matches(AccountType fromAccountType, AccountType toAccountType) {
return this.fromAccountTypes.contains(fromAccountType) && this.toAccountTypes.contains(toAccountType);
}
}
}

View File

@@ -105,6 +105,8 @@ public class TransactionService {
transaction.setExpenseNeutral(true);
}
transaction.setTransactionType(this.ruleService.getTransactionType(fromAccount.getType(), toAccount.getType()));
this.transactionRepository.save(transaction);
this.accountStatisticService.calculateStatistics(transaction);

View File

@@ -0,0 +1,2 @@
ALTER TABLE "transaction" --escape keyword "transaction"
ADD COLUMN transaction_type VARCHAR(255);

View File

@@ -0,0 +1,2 @@
ALTER TABLE "transaction" --escape keyword "transaction"
ALTER COLUMN transaction_type SET NOT NULL;

View File

@@ -1,41 +1,12 @@
WITH income AS (
-- Regular income based on INCOME accounts
SELECT p.ID, SUM(asIncome.spending_total_from) AS incomeSum
FROM period p
INNER JOIN account_statistic asIncome ON asIncome.period_id = p.id
INNER JOIN account aIncome ON aIncome.id = asIncome.account_id AND aIncome.type = 'INCOME'
SELECT p.id, SUM(amount) AS incomeSum
FROM "transaction" t
INNER JOIN link_transaction_period ltp on ltp.transaction_id = t.id
INNER JOIN period p on p.id = ltp.period_id
WHERE 1 = 1
AND t.transaction_type = 'INCOME'
GROUP BY p.id, p.type, p.start, p."end"
),
incomeCredit as (
-- Special case for credits that can be booked from a LIABILITY account to a BANK/CASH account
-- Need to be counted as income as the money is used for expenses and those expenses will be counted
-- as expense, so to make it even we need to count it here as well
SELECT p2.id, SUM(amount) AS incomeCreditSum
FROM "transaction" t
INNER JOIN account a on a.id = t.from_account_id
INNER JOIN account a2 on a2.id = t.to_account_id
INNER JOIN link_transaction_period ltp on ltp.transaction_id = t.id
INNER JOIN period p2 on p2.id = ltp.period_id
WHERE 1 = 1
AND a.type in ('LIABILITY')
AND a2.type in ('BANK', 'CASH')
GROUP BY p2.id, p2.type, p2.start, p2."end"
),
incomeStart as (
-- Special case for money that was there at the starting time of a financer instance
-- Will be counted as income as this money is used for expanses, so to make it even
-- we need to count it here as well
SELECT p2.id, SUM(amount) AS incomeStartSum
FROM "transaction" t
INNER JOIN account a on a.id = t.from_account_id
INNER JOIN account a2 on a2.id = t.to_account_id
INNER JOIN link_transaction_period ltp on ltp.transaction_id = t.id
INNER JOIN period p2 on p2.id = ltp.period_id
WHERE 1 = 1
AND a.type in ('START')
AND a2.type in ('BANK', 'CASH')
GROUP BY p2.id, p2.type, p2.start, p2."end"
),
expense AS (
-- Expense booking - NOT counted is the case LIABILITY -> EXPENSE even though that is a
-- valid booking in the app. This is because we would count the expense once here and a second time
@@ -43,28 +14,21 @@ expense AS (
SELECT p2.id, SUM(amount) AS expenseSum
FROM "transaction" t
INNER JOIN account a on a.id = t.from_account_id
INNER JOIN account a2 on a2.id = t.to_account_id
INNER JOIN link_transaction_period ltp on ltp.transaction_id = t.id
INNER JOIN period p2 on p2.id = ltp.period_id
WHERE 1 = 1
AND t.transaction_type = 'EXPENSE'
AND a.type in ('BANK', 'CASH')
AND a2.type in ('EXPENSE')
GROUP BY p2.id, p2.type, p2.start, p2."end"
),
liability AS (
-- Excluded is the special case for start bookings, START -> LIABILITY
-- as the actual expense for that was some time in the past before the starting
-- of the financer instance
SELECT p2.id, SUM(amount) AS liabilitySum
SELECT p3.id, SUM(amount) AS liabilitySum
FROM "transaction" t
INNER JOIN account a on a.id = t.from_account_id
INNER JOIN account a2 on a2.id = t.to_account_id
INNER JOIN link_transaction_period ltp on ltp.transaction_id = t.id
INNER JOIN period p2 on p2.id = ltp.period_id
INNER JOIN period p3 on p3.id = ltp.period_id
WHERE 1 = 1
AND a.type in ('BANK', 'CASH')
AND a2.type in ('LIABILITY')
GROUP BY p2.id, p2.type, p2.start, p2."end"
AND t.transaction_type = 'LIABILITY'
GROUP BY p3.id, p3.type, p3.start, p3."end"
),
assets AS (
-- Returns only the assets for closed periods
@@ -86,15 +50,8 @@ SELECT
p.start PERIOD_START,
p."end" PERIOD_END,
CASE
-- 2^3 possible cases
WHEN i.incomeSum IS NULL AND ic.incomeCreditSum IS NULL AND "is".incomeStartSum IS NULL THEN 0
WHEN i.incomeSum IS NOT NULL AND ic.incomeCreditSum IS NULL AND "is".incomeStartSum IS NULL THEN i.incomeSum
WHEN i.incomeSum IS NULL AND ic.incomeCreditSum IS NOT NULL AND "is".incomeStartSum IS NULL THEN ic.incomeCreditSum
WHEN i.incomeSum IS NOT NULL AND ic.incomeCreditSum IS NOT NULL AND "is".incomeStartSum IS NULL THEN (i.incomeSum + ic.incomeCreditSum)
WHEN i.incomeSum IS NULL AND ic.incomeCreditSum IS NULL AND "is".incomeStartSum IS NOT NULL THEN "is".incomeStartSum
WHEN i.incomeSum IS NOT NULL AND ic.incomeCreditSum IS NULL AND "is".incomeStartSum IS NOT NULL THEN (i.incomeSum + "is".incomeStartSum)
WHEN i.incomeSum IS NULL AND ic.incomeCreditSum IS NOT NULL AND "is".incomeStartSum IS NOT NULL THEN (ic.incomeCreditSum + "is".incomeStartSum)
WHEN i.incomeSum IS NOT NULL AND ic.incomeCreditSum IS NOT NULL AND "is".incomeStartSum IS NOT NULL THEN (i.incomeSum + ic.incomeCreditSum + "is".incomeStartSum)
WHEN i.incomeSum IS NULL THEN 0
WHEN i.incomeSum IS NOT NULL THEN i.incomeSum
END INCOME_SUM,
CASE
WHEN e.expenseSum IS NULL THEN 0
@@ -125,8 +82,6 @@ SELECT
FROM
period p
LEFT JOIN income i ON i.ID = p.ID
LEFT JOIN incomeCredit ic ON ic.ID = p.ID
LEFT JOIN incomeStart "is" ON "is".ID = p.ID
LEFT JOIN expense e ON e.ID = p.ID
LEFT JOIN assets a ON a.ID = p.ID
LEFT JOIN liability l ON l.ID = p.ID

View File

@@ -103,14 +103,15 @@ public class PeriodController {
@GetMapping("/showIncomeTransactions")
public String showIncomeTransactions(Model model, Long periodId) {
final String fql = String
.format("periodId = '%s' AND (fromAccountType = 'INCOME' OR (fromAccountType = 'LIABILITY' AND (toAccountType = 'BANK' OR toAccountType = 'CASH')) OR (fromAccountType = 'START' AND (toAccountType = 'BANK' OR toAccountType = 'CASH'))) ORDER BY date DESC", periodId);
final String fql = String.format("periodId = '%s' AND transactionType = 'INCOME' ORDER BY date DESC", periodId);
return showInternal(model, fql, true);
}
@GetMapping("/showExpenseTransactions")
public String showExpenseTransactions(Model model, Long periodId) {
// We cannot use transactionType = 'EXPENSE' because we do not want to show the Liability -> Expense bookings
// as these are counted via asset type account -> Liability
final String fql = String
.format("periodId = '%s' AND (fromAccountType = 'BANK' OR fromAccountType = 'CASH') AND toAccountType = 'EXPENSE' ORDER BY date DESC", periodId);
@@ -119,8 +120,7 @@ public class PeriodController {
@GetMapping("/showLiabilityTransactions")
public String showLiabilityTransactions(Model model, Long periodId) {
final String fql = String
.format("periodId = '%s' AND (fromAccountType = 'BANK' OR fromAccountType = 'CASH') AND toAccountType = 'LIABILITY' ORDER BY date DESC", periodId);
final String fql = String.format("periodId = '%s' AND transactionType = 'LIABILITY' ORDER BY date DESC", periodId);
return showInternal(model, fql, true);
}

View File

@@ -145,6 +145,7 @@ financer.search-transactions.show-query-options.recurring=recurring\: whether th
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.description=description\: the description of the transaction
financer.search-transactions.show-query-options.transactionType=transactionType\: the type of the transaction
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

View File

@@ -145,6 +145,7 @@ financer.search-transactions.show-query-options.recurring=recurring\: ob die Buc
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.description=description\: die Beschreibung der Buchung
financer.search-transactions.show-query-options.transactionType=transactionType\: der Typ der Buchung
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

View File

@@ -1,3 +1,10 @@
v47 -> v48:
- Added new property 'transaction type' to a transaction, denoting the type of the transaction, e.g. asset swap,
expense, liability or income. This can also be queried via FQL
v46 -> v47:
- Fix a bug that occurred while creating a transaction from a recurring transaction with amount overwrite
v45 -> v46:
- #17 Add actions to the period overview
- #18 Improve actions in the period overview

View File

@@ -16,7 +16,7 @@
9. Reporting
10. FQL
11. Setup
12. Planned features
12. Links
1. About
========
@@ -165,6 +165,21 @@
7. Transactions
===============
Transactions have a type that denotes the business case:
- Asset swap: Transferring money from one asset type account (Bank, Cash) to another account of an asset type
E.g. from Bank to Cash, e.g. making a withdrawal at an ATM, B- -> C+
- Expense: Booking money for an expense from a Bank, Cash or Liability account to an Expense account
E.g. from Cash to Expense, e.g. buying goods, C- -> E+
- Liability: Paying a credit installment from an asset type account (Bank, Cash) to a Liability account
E.g. from Bank to Liability, e.g. making an installment, B- -> L-
- Income: Increasing the assets from a Start, Income or Liability account to an asset type account (Bank, Cash).
Start -> asset type account bookings need to counted as the money that was there at the starting time of
a financer instance is used for expanses.
Liability -> asset type account bookings need to counted as the money is used for expenses.
E.g. from Liability to Bank, e.g. payout of a credit, L+ -> B+
- Start liability: Booking the current balance of a liability at the inception of a financer instance
E.g. from Start to Liability, e.g. initial setup of liability amount, S+ -> L+
8. Recurring transactions
=========================
@@ -195,8 +210,8 @@
\q
exit
12. Planned features
====================
This chapter lists planned features. The list is in no particular order:
- Transaction import from online banking (file based)
- Extended reports, e.g. forecasting based on recurring transactions and average spending
12. Links
=========
This chapter contains useful links:
- financer web page: https://financer.dev/
- financer git repository: https://77zzcx7.de/gitea/MK13/financer

View File

@@ -38,6 +38,7 @@
<li th:text="#{financer.search-transactions.show-query-options.taxRelevant}" />
<li th:text="#{financer.search-transactions.show-query-options.hasFile}" />
<li th:text="#{financer.search-transactions.show-query-options.description}" />
<li th:text="#{financer.search-transactions.show-query-options.transactionType}" />
<li><span th:text="#{financer.search-transactions.show-query-options.period}" />
<ul>
<li th:text="#{financer.search-transactions.show-query-options.period.CURRENT}" />