@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.financer.dba;
|
||||
|
||||
import de.financer.dto.PeriodOverviewDto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PeriodRepositoryCustom {
|
||||
List<PeriodOverviewDto> getPeriodOverview();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user