#10 Create period overview

WIP commit
This commit is contained in:
2021-03-04 18:12:18 +01:00
parent 51d491c5d1
commit 5a13429e9b
18 changed files with 543 additions and 4 deletions

View File

@@ -0,0 +1,130 @@
package de.financer.dto;
import de.financer.model.PeriodType;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import java.time.LocalDate;
public class PeriodOverviewDto {
private Long periodId;
private PeriodType periodType;
private LocalDate periodStart;
private LocalDate periodEnd;
private Long incomeSum;
private Long expenseSum;
private Long liabilitySum;
private Long total;
private Long assetsSum;
private Long transactionCount;
public PeriodOverviewDto() {
}
public PeriodOverviewDto(Long periodId,
String periodType,
LocalDate periodStart,
LocalDate periodEnd,
Long incomeSum,
Long expenseSum,
Long liabilitySum,
Long total,
Long assetsSum,
Long transactionCount) {
this.periodId = periodId;
this.periodType = PeriodType.valueOf(periodType);
this.periodStart = periodStart;
this.periodEnd = periodEnd;
this.incomeSum = incomeSum;
this.expenseSum = expenseSum;
this.liabilitySum = liabilitySum;
this.total = total;
this.assetsSum = assetsSum;
this.transactionCount = transactionCount;
}
public Long getPeriodId() {
return periodId;
}
public void setPeriodId(Long periodId) {
this.periodId = periodId;
}
public PeriodType getPeriodType() {
return periodType;
}
public void setPeriodType(PeriodType periodType) {
this.periodType = periodType;
}
public LocalDate getPeriodStart() {
return periodStart;
}
public void setPeriodStart(LocalDate periodStart) {
this.periodStart = periodStart;
}
public LocalDate getPeriodEnd() {
return periodEnd;
}
public void setPeriodEnd(LocalDate periodEnd) {
this.periodEnd = periodEnd;
}
public Long getIncomeSum() {
return incomeSum;
}
public void setIncomeSum(Long incomeSum) {
this.incomeSum = incomeSum;
}
public Long getExpenseSum() {
return expenseSum;
}
public void setExpenseSum(Long expenseSum) {
this.expenseSum = expenseSum;
}
public Long getLiabilitySum() {
return liabilitySum;
}
public void setLiabilitySum(Long liabilitySum) {
this.liabilitySum = liabilitySum;
}
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
@Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
public Long getAssetsSum() {
return assetsSum;
}
public void setAssetsSum(Long assetsSum) {
this.assetsSum = assetsSum;
}
public Long getTransactionCount() {
return transactionCount;
}
public void setTransactionCount(Long transactionCount) {
this.transactionCount = transactionCount;
}
}

View File

@@ -1,9 +1,29 @@
package de.financer.model;
import de.financer.dto.PeriodOverviewDto;
import javax.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Entity
// I don't like having that here, but it needs to be annotated on some @Entity
// and this entity is the best match
@SqlResultSetMapping(name = "PeriodOverviewResult",
classes = {
@ConstructorResult(targetClass = PeriodOverviewDto.class,
columns = {
@ColumnResult(name = "PERIOD_ID", type = Long.class),
@ColumnResult(name = "PERIOD_TYPE", type = String.class),
@ColumnResult(name = "PERIOD_START", type = LocalDate.class),
@ColumnResult(name = "PERIOD_END", type = LocalDate.class),
@ColumnResult(name = "INCOME_SUM", type = Long.class),
@ColumnResult(name = "EXPENSE_SUM", type = Long.class),
@ColumnResult(name = "LIABILITY_SUM", type = Long.class),
@ColumnResult(name = "TOTAL", type = Long.class),
@ColumnResult(name = "ASSETS_SUM", type = Long.class),
@ColumnResult(name = "TRANSACTION_COUNT", type = Long.class)})
})
public class Period {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@@ -1,5 +1,6 @@
package de.financer.controller;
import de.financer.dto.PeriodOverviewDto;
import de.financer.model.Period;
import de.financer.service.PeriodService;
import org.slf4j.Logger;
@@ -9,6 +10,8 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("periods")
public class PeriodController {
@@ -46,4 +49,19 @@ public class PeriodController {
return currentExpensePeriod;
}
@RequestMapping("getPeriodOverview")
public List<PeriodOverviewDto> getPeriodOverview() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("/periods/getPeriodOverview called");
}
final List<PeriodOverviewDto> overview = this.periodService.getPeriodOverview();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("/periods/getPeriodOverview returns with %s", overview));
}
return overview;
}
}

View File

@@ -0,0 +1,22 @@
package de.financer.dba;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
public enum NativeQueries {
PERIOD_OVERVIEW_QUERY("period_overview.sql");
private String query;
private NativeQueries(String fileName) {
this.query = new BufferedReader(
new InputStreamReader(NativeQueries.class.getClassLoader()
.getResourceAsStream("native_queries/" + fileName)))
.lines().collect(Collectors.joining("\n"));
}
public String getQuery() {
return this.query;
}
}

View File

@@ -10,7 +10,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@Transactional(propagation = Propagation.REQUIRED)
public interface PeriodRepository extends CrudRepository<Period, Long> {
public interface PeriodRepository extends CrudRepository<Period, Long>, PeriodRepositoryCustom {
@Query("SELECT p FROM Period p WHERE p.type = :type AND p.end IS NULL")
Period findCurrentExpensePeriod(PeriodType type);

View File

@@ -0,0 +1,9 @@
package de.financer.dba;
import de.financer.dto.PeriodOverviewDto;
import java.util.List;
public interface PeriodRepositoryCustom {
List<PeriodOverviewDto> getPeriodOverview();
}

View File

@@ -0,0 +1,23 @@
package de.financer.dba.impl;
import de.financer.dba.NativeQueries;
import de.financer.dba.PeriodRepositoryCustom;
import de.financer.dto.PeriodOverviewDto;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
@Repository
public class PeriodRepositoryCustomImpl implements PeriodRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<PeriodOverviewDto> getPeriodOverview() {
return this.entityManager
.createNativeQuery(NativeQueries.PERIOD_OVERVIEW_QUERY.getQuery(), "PeriodOverviewResult")
.getResultList();
}
}

View File

@@ -2,6 +2,7 @@ package de.financer.service;
import de.financer.ResponseReason;
import de.financer.dba.PeriodRepository;
import de.financer.dto.PeriodOverviewDto;
import de.financer.model.Period;
import de.financer.model.PeriodType;
import de.financer.model.Transaction;
@@ -14,6 +15,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Optional;
@Service
@@ -139,4 +141,8 @@ public class PeriodService {
public Optional<Period> getPeriodById(Long id) {
return this.periodRepository.findById(id);
}
public List<PeriodOverviewDto> getPeriodOverview() {
return this.periodRepository.getPeriodOverview();
}
}

View File

@@ -0,0 +1,10 @@
This folder contains DBMS-agnostic native queries.
Reasons for doing it like this:
- Queries in here are quite big and expressing those either with Criteria API or HQL/JPQL is a PITA
- Easy formatting for readability
- Actually SQL files
Downsides:
- No compile-time check via e.g. the static meta model created during build, but will be mitigated with Unit tests
- Quite unique infrastructure to integrate these queries into the Spring repositories (SqlResultSetMapping, NativeQueries)

View File

@@ -0,0 +1,130 @@
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'
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
-- with the liability query
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 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
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 ('BANK', 'CASH')
AND a2.type in ('LIABILITY')
GROUP BY p2.id, p2.type, p2.start, p2."end"
),
assets AS (
-- Returns only the assets for closed periods
SELECT p.ID, SUM(asBankCash.end_balance) AS assetSum
FROM period p
INNER JOIN account_statistic asBankCash ON asBankCash.period_id = p.id
INNER JOIN account aBankCash ON aBankCash.id = asBankCash.account_id AND aBankCash.type IN ('BANK', 'CASH')
GROUP BY p.id, p.type, p.start, p."end"
),
transactions AS (
-- The count of transactions in a period
SELECT ltp.period_id, COUNT(*) AS transaction_count
FROM link_transaction_period ltp
GROUP BY ltp.period_id
)
SELECT
p.ID PERIOD_ID,
p.type PERIOD_TYPE,
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)
END INCOME_SUM,
CASE
WHEN e.expenseSum IS NULL THEN 0
WHEN e.expenseSum IS NOT NULL THEN e.expenseSum
END EXPENSE_SUM,
CASE
WHEN l.liabilitySum IS NULL THEN 0
WHEN l.liabilitySum IS NOT NULL THEN l.liabilitySum
END LIABILITY_SUM,
CASE
-- 2^2 possible cases
WHEN e.expenseSum IS NULL AND l.liabilitySum IS NULL THEN 0
WHEN e.expenseSum IS NOT NULL AND l.liabilitySum IS NULL THEN e.expenseSum
WHEN e.expenseSum IS NULL AND l.liabilitySum IS NOT NULL THEN l.liabilitySum
WHEN e.expenseSum IS NOT NULL AND l.liabilitySum IS NOT NULL THEN (e.expenseSum + l.liabilitySum)
END TOTAL,
a.assetSum ASSETS_SUM,
CASE
WHEN t.transaction_count IS NULL THEN 0
WHEN t.transaction_count IS NOT NULL THEN t.transaction_count
END TRANSACTION_COUNT
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
LEFT JOIN transactions t ON t.period_id = p.ID
ORDER BY
"end" DESC,
start ASC;

View File

@@ -0,0 +1,50 @@
package de.financer.controller.integration;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.financer.FinancerApplication;
import de.financer.dto.PeriodOverviewDto;
import de.financer.model.Account;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import java.util.List;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = FinancerApplication.class)
@AutoConfigureMockMvc
@TestPropertySource(
locations = "classpath:application-integrationtest.properties")
public class PeriodController_getPeriodOverviewIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
public void test_getPeriodOverview() throws Exception {
final MvcResult mvcResult = this.mockMvc
.perform(get("/periods/getPeriodOverview").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
final List<PeriodOverviewDto> periodOverview = this.objectMapper
.readValue(mvcResult.getResponse().getContentAsByteArray(), new TypeReference<List<PeriodOverviewDto>>() {});
// No results in DB, we just want to test the execution of the query because it is native
Assert.assertEquals(0, periodOverview.size());
}
}

View File

@@ -34,6 +34,7 @@ public enum Function {
P_GET_CURRENT_EXPENSE_PERIOD("periods/getCurrentExpensePeriod"),
P_CLOSE_CURRENT_EXPENSE_PERIOD("periods/closeCurrentExpensePeriod"),
P_GET_PERIOD_OVERVIEW("periods/getPeriodOverview"),
FILE_GET("file");

View File

@@ -1,13 +1,21 @@
package de.financer.controller;
import de.financer.config.FinancerConfig;
import de.financer.dto.PeriodOverviewDto;
import de.financer.dto.SearchTransactionsResponseDto;
import de.financer.template.FinancerRestTemplate;
import de.financer.template.StringTemplate;
import de.financer.template.exception.FinancerRestException;
import de.financer.util.ControllerUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.List;
@Controller
public class PeriodController {
@Autowired
@@ -22,4 +30,27 @@ public class PeriodController {
return "redirect:/accountOverview";
}
@GetMapping("/periodOverview")
public String periodOverview(Model model) {
final UriComponentsBuilder periodBuilder = UriComponentsBuilder
.fromHttpUrl(ControllerUtils.buildUrl(this.financerConfig, Function.P_GET_PERIOD_OVERVIEW));
try {
final List<PeriodOverviewDto> periodOverviews = FinancerRestTemplate.exchangeGet(periodBuilder,
new ParameterizedTypeReference<List<PeriodOverviewDto>>() {
});
model.addAttribute("periodOverviews", periodOverviews);
} catch (FinancerRestException e) {
// TODO
model.addAttribute("errorMessage", e.getResponseReason().name());
}
ControllerUtils.addVersionAttribute(model, this.financerConfig);
ControllerUtils.addCurrencySymbol(model, this.financerConfig);
ControllerUtils.addDarkMode(model, this.financerConfig);
return "period/periodOverview";
}
}

View File

@@ -11,6 +11,7 @@ financer.account-overview.available-actions.create-account-group=Create new acco
financer.account-overview.available-actions.select-chart=Generate a chart
financer.account-overview.available-actions.close-current-period=Close the current expense period
financer.account-overview.available-actions.recurring-transaction-calendar=Recurring transaction calendar
financer.account-overview.available-actions.period-overview=Period overview
financer.account-overview.status=Status\:
financer.account-overview.status.recurring-transaction-due-today=Recurring transactions due today\:
financer.account-overview.status.recurring-transaction-active=Active recurring transactions\:
@@ -172,6 +173,18 @@ financer.chart-config-account-expenses-for-period.label.from-date=From date\:
financer.chart-config-account-expenses-for-period.label.to-date=To date\:
financer.chart-config-account-expenses-for-period.submit=Generate
financer.period-overview.title=Period overview
financer.period-overview.table-header.id=ID
financer.period-overview.table-header.type=Period type
financer.period-overview.table-header.start=Start
financer.period-overview.table-header.end=End
financer.period-overview.table-header.income=Income
financer.period-overview.table-header.expense=Expenses
financer.period-overview.table-header.liability=Liabilities
financer.period-overview.table-header.total=Total
financer.period-overview.table-header.assets=Assets
financer.period-overview.table-header.transactions=Transaction count
financer.interval-type.DAILY=Daily
financer.interval-type.WEEKLY=Weekly
financer.interval-type.MONTHLY=Monthly
@@ -193,6 +206,10 @@ financer.account-type.START=Start
financer.account-status.OPEN=Open
financer.account-status.CLOSED=Closed
financer.period-type.EXPENSE=Expense
financer.period-type.EXPENSE_YEAR=Expense year
financer.period-type.GRAND_TOTAL=Grand total
financer.heading.transaction-new=financer\: create new transaction
financer.heading.recurring-transaction-new=financer\: create new recurring transaction
financer.heading.account-new=financer\: create new account
@@ -208,6 +225,7 @@ financer.heading.chart-config-account-group-expenses-for-period=financer\: confi
financer.heading.chart-config-account-expenses-for-period=financer\: configure account expenses for period chart
financer.heading.search-transactions=financer\: search transactions
financer.heading.recurring-transaction-calendar=financer\: recurring transaction calendar
financer.heading.period-overview=financer\: period overview
financer.cancel-back-to-overview=Cancel and back to overview
financer.back-to-overview=Back to overview

View File

@@ -11,6 +11,7 @@ financer.account-overview.available-actions.create-account-group=Neue Konto-Grup
financer.account-overview.available-actions.select-chart=Ein Diagramm erzeugen
financer.account-overview.available-actions.close-current-period=Aktuelle Periode schlie\u00DFen
financer.account-overview.available-actions.recurring-transaction-calendar=Kalender wiederkehrende Buchung
financer.account-overview.available-actions.period-overview=Perioden\u00FCbersicht
financer.account-overview.status=Status\:
financer.account-overview.status.recurring-transaction-due-today=Wiederkehrende Buchungen heute f\u00E4llig\:
financer.account-overview.status.recurring-transaction-active=Aktive wiederkehrende Buchungen\:
@@ -172,6 +173,18 @@ financer.chart-config-account-expenses-for-period.label.from-date=Von Datum\:
financer.chart-config-account-expenses-for-period.label.to-date=Bis Datum\:
financer.chart-config-account-expenses-for-period.submit=Erzeugen
financer.period-overview.title=Perioden\u00FCbersicht
financer.period-overview.table-header.id=ID
financer.period-overview.table-header.type=Periodentyp
financer.period-overview.table-header.start=Start
financer.period-overview.table-header.end=Ende
financer.period-overview.table-header.income=Einkommen
financer.period-overview.table-header.expense=Ausgaben
financer.period-overview.table-header.liability=Verbindlichkeiten
financer.period-overview.table-header.total=Insgesamt
financer.period-overview.table-header.assets=Umlaufverm\u00F6gen
financer.period-overview.table-header.transactions=Anzahl Buchungen
financer.interval-type.DAILY=T\u00E4glich
financer.interval-type.WEEKLY=W\u00F6chentlich
financer.interval-type.MONTHLY=Monatlich
@@ -193,6 +206,10 @@ financer.account-type.START=Anfangsbestand
financer.account-status.OPEN=Offen
financer.account-status.CLOSED=Geschlossen
financer.period-type.EXPENSE=Ausgaben
financer.period-type.EXPENSE_YEAR=Jahresausgaben
financer.period-type.GRAND_TOTAL=Gesamtausgaben
financer.heading.transaction-new=financer\: Neue Buchung erstellen
financer.heading.recurring-transaction-new=financer\: Neue wiederkehrende Buchung erstellen
financer.heading.account-new=financer\: Neues Konto erstellen
@@ -207,6 +224,7 @@ financer.heading.chart-select=financer\: Ein Diagramm zum Erzeugen ausw\u00E4hle
financer.heading.chart-config-account-group-expenses-for-period=financer\: Konfigurieren von Ausgaben f\u00FCr Periode gruppiert nach Konto-Gruppe Diagramm
financer.heading.search-transactions=financer\: Buchungen suchen
financer.heading.recurring-transaction-calendar=financer\: Kalender wiederkehrende Buchung
financer.heading.period-overview=financer\: Perioden\u00FCbersicht
financer.cancel-back-to-overview=Abbrechen und zur\u00FCck zur \u00DCbersicht
financer.back-to-overview=Zur\u00FCck zur \u00DCbersicht

View File

@@ -34,7 +34,8 @@ a {
#account-overview-table,
#transaction-table,
#recurring-transaction-list-table {
#recurring-transaction-list-table,
#period-overview-table {
width: 100%;
border-collapse: collapse;
text-align: left;
@@ -47,7 +48,9 @@ a {
#transaction-table th,
#transaction-table td,
#recurring-transaction-list-table th,
#recurring-transaction-list-table td {
#recurring-transaction-list-table td,
#period-overview-table th,
#period-overview-table td {
border-bottom: 1px solid var(--border-color);
padding: 0.3em;
vertical-align: top;
@@ -55,7 +58,8 @@ a {
#account-overview-table th,
#transaction-table th,
#recurring-transaction-list-table th {
#recurring-transaction-list-table th,
#period-overview-table th {
position: sticky;
top: 0px;
background-color: var(--background-color);

View File

@@ -66,6 +66,8 @@
<div id="action-container-sub-period">
<a th:href="@{/closePeriod}"
th:text="#{financer.account-overview.available-actions.close-current-period}"/>
<a th:href="@{/periodOverview}"
th:text="#{financer.account-overview.available-actions.period-overview}"/>
</div>
<div id="action-container-sub-reports">
<a th:href="@{/selectChart}"

View File

@@ -0,0 +1,47 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{financer.period-overview.title}"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link th:if="${darkMode}" rel="stylesheet" th:href="@{/css/darkModeColors.css}"/>
<link th:if="${!darkMode}" rel="stylesheet" th:href="@{/css/lightModeColors.css}"/>
<link rel="stylesheet" th:href="@{/css/main.css}">
<link rel="shortcut icon" th:href="@{/favicon.ico}"/>
</head>
<body>
<h1 th:text="#{financer.heading.period-overview}"/>
<span class="errorMessage" th:if="${errorMessage != null}" th:text="#{'financer.error-message.' + ${errorMessage}}"/>
<a th:href="@{/accountOverview}" th:text="#{financer.cancel-back-to-overview}"/>
<table id="period-overview-table">
<tr>
<th th:text="#{financer.period-overview.table-header.id}"/>
<th th:text="#{financer.period-overview.table-header.type}"/>
<th th:text="#{financer.period-overview.table-header.start}"/>
<th th:text="#{financer.period-overview.table-header.end}"/>
<th th:text="#{financer.period-overview.table-header.income}" />
<th th:text="#{financer.period-overview.table-header.expense}"/>
<th th:text="#{financer.period-overview.table-header.liability}"/>
<th th:text="#{financer.period-overview.table-header.total}"/>
<th th:text="#{financer.period-overview.table-header.assets}"/>
<th th:text="#{financer.period-overview.table-header.transactions}"/>
</tr>
<tr th:each="periodOverview : ${periodOverviews}">
<td th:text="${periodOverview.periodId}"/>
<td th:text="#{'financer.period-type.' + ${periodOverview.periodType}}"/>
<td th:text="${#temporals.format(periodOverview.periodStart)}"/>
<td th:text="${#temporals.format(periodOverview.periodEnd)}"/>
<td th:utext="${#numbers.formatDecimal(periodOverview.incomeSum/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
<td th:utext="${#numbers.formatDecimal(periodOverview.expenseSum/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
<td th:utext="${#numbers.formatDecimal(periodOverview.liabilitySum/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
<td th:utext="${#numbers.formatDecimal(periodOverview.total/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"
th:classappend="${periodOverview.total > periodOverview.incomeSum} ? overspend"/>
<td th:if="${periodOverview.assetsSum != null}"
th:utext="${#numbers.formatDecimal(periodOverview.assetsSum/100D, 1, 'DEFAULT', 2, 'DEFAULT') + currencySymbol}"/>
<td th:if="${periodOverview.assetsSum == null}" />
<td th:text="${periodOverview.transactionCount}"/>
</tr>
</table>
<div th:replace="includes/footer :: footer"/>
</body>
</html>