mirror of
https://github.com/mvvasilev/personal-finances.git
synced 2025-04-19 14:19:52 +03:00
Start working on widgets
This commit is contained in:
parent
1a9dc36cf5
commit
c6c44e1604
16 changed files with 443 additions and 36 deletions
|
@ -2,21 +2,24 @@ package dev.mvvasilev.finances.controllers;
|
|||
|
||||
import dev.mvvasilev.common.controller.AbstractRestController;
|
||||
import dev.mvvasilev.common.web.APIResponseDTO;
|
||||
import dev.mvvasilev.finances.dtos.SpendingByCategoryDTO;
|
||||
import dev.mvvasilev.finances.dtos.SpendingByCategoriesDTO;
|
||||
import dev.mvvasilev.finances.dtos.SpendingOverTimeByCategoryDTO;
|
||||
import dev.mvvasilev.finances.enums.TimePeriod;
|
||||
import dev.mvvasilev.finances.services.StatisticsService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
|
||||
@RestController("/statistics")
|
||||
@RestController
|
||||
@RequestMapping("/statistics")
|
||||
public class StatisticsController extends AbstractRestController {
|
||||
|
||||
private final StatisticsService statisticsService;
|
||||
|
@ -27,23 +30,24 @@ public class StatisticsController extends AbstractRestController {
|
|||
}
|
||||
|
||||
@GetMapping("/totalSpendingByCategory")
|
||||
public ResponseEntity<APIResponseDTO<SpendingByCategoryDTO>> fetchSpendingByCategory(
|
||||
@PreAuthorize("@authService.isOwner(#categoryId, T(dev.mvvasilev.finances.entity.RawStatement))")
|
||||
public ResponseEntity<APIResponseDTO<SpendingByCategoriesDTO>> fetchSpendingByCategory(
|
||||
Long[] categoryId,
|
||||
@RequestParam(defaultValue = "1970-01-01T00:00:00") LocalDateTime from,
|
||||
@RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to,
|
||||
Authentication authentication
|
||||
@RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to
|
||||
) {
|
||||
return ok(statisticsService.spendingByCategory(from, to, Integer.parseInt(authentication.getName())));
|
||||
return ok(statisticsService.spendingByCategory(categoryId, from, to));
|
||||
}
|
||||
|
||||
@GetMapping("/spendingOverTimeByCategory")
|
||||
@PreAuthorize("@authService.isOwner(#categoryId, T(dev.mvvasilev.finances.entity.RawStatement))")
|
||||
public ResponseEntity<APIResponseDTO<SpendingOverTimeByCategoryDTO>> fetchSpendingOverTimeByCategory(
|
||||
Collection<Long> categoryId,
|
||||
TimePeriod period,
|
||||
Long[] categoryId,
|
||||
@RequestParam(defaultValue = "DAILY") TimePeriod period,
|
||||
@RequestParam(defaultValue = "1970-01-01T00:00:00") LocalDateTime from,
|
||||
@RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to,
|
||||
Authentication authentication
|
||||
@RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to
|
||||
) {
|
||||
throw new UnsupportedOperationException();
|
||||
return ok(statisticsService.spendingByCategoryOverTime(categoryId, period, from, to));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package dev.mvvasilev.finances.controllers;
|
||||
|
||||
import dev.mvvasilev.common.controller.AbstractRestController;
|
||||
import dev.mvvasilev.common.web.APIResponseDTO;
|
||||
import dev.mvvasilev.common.web.CrudResponseDTO;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/widgets")
|
||||
public class WidgetsController extends AbstractRestController {
|
||||
|
||||
// <CreateDTO> ResponseEntity<APIResponseDTO<CrudResponseDTO>> create(CreateDTO dto);
|
||||
//
|
||||
// <UpdateDTO> ResponseEntity<APIResponseDTO<CrudResponseDTO>> update(Long id, UpdateDTO dto);
|
||||
//
|
||||
// ResponseEntity<APIResponseDTO<CrudResponseDTO>> delete(Long id);
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package dev.mvvasilev.finances.dtos;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public record SpendingByCategoriesDTO(
|
||||
Collection<CategoryDTO> categories,
|
||||
|
||||
Map<Long, Double> spendingByCategory
|
||||
) {
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package dev.mvvasilev.finances.dtos;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record SpendingByCategoryDTO(
|
||||
Collection<CategoryDTO> categories,
|
||||
public record SpendingByCategoryDTO (
|
||||
Long categoryId,
|
||||
|
||||
Map<Long, Double> spendingByCategory
|
||||
) {
|
||||
}
|
||||
LocalDateTime timestamp,
|
||||
|
||||
Double amount
|
||||
) {}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
package dev.mvvasilev.finances.dtos;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Period;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public record SpendingOverTimeByCategoryDTO(
|
||||
Collection<CategoryDTO> categories,
|
||||
|
||||
Map<Long, Collection<Double>> spendingOverTime
|
||||
Period timePeriodDuration,
|
||||
|
||||
Collection<SpendingByCategoryDTO> spending
|
||||
) {
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package dev.mvvasilev.finances.entity;
|
||||
|
||||
import dev.mvvasilev.common.data.AbstractEntity;
|
||||
import dev.mvvasilev.finances.enums.WidgetType;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.Converter;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "widgets")
|
||||
public class Widget extends AbstractEntity {
|
||||
|
||||
@Convert(converter = WidgetType.JpaConverter.class)
|
||||
private WidgetType type;
|
||||
|
||||
private int positionX;
|
||||
|
||||
private int positionY;
|
||||
|
||||
private int sizeX;
|
||||
|
||||
private int sizeY;
|
||||
|
||||
private String name;
|
||||
|
||||
public Widget() {
|
||||
}
|
||||
|
||||
public WidgetType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(WidgetType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getPositionX() {
|
||||
return positionX;
|
||||
}
|
||||
|
||||
public void setPositionX(int positionX) {
|
||||
this.positionX = positionX;
|
||||
}
|
||||
|
||||
public int getPositionY() {
|
||||
return positionY;
|
||||
}
|
||||
|
||||
public void setPositionY(int positionY) {
|
||||
this.positionY = positionY;
|
||||
}
|
||||
|
||||
public int getSizeX() {
|
||||
return sizeX;
|
||||
}
|
||||
|
||||
public void setSizeX(int sizeX) {
|
||||
this.sizeX = sizeX;
|
||||
}
|
||||
|
||||
public int getSizeY() {
|
||||
return sizeY;
|
||||
}
|
||||
|
||||
public void setSizeY(int sizeY) {
|
||||
this.sizeY = sizeY;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package dev.mvvasilev.finances.entity;
|
||||
|
||||
import dev.mvvasilev.common.data.AbstractEntity;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "widgets")
|
||||
public class WidgetParameter extends AbstractEntity {
|
||||
|
||||
private Long widgetId;
|
||||
|
||||
private String name;
|
||||
|
||||
private String stringValue;
|
||||
|
||||
private Double numericValue;
|
||||
|
||||
private LocalDateTime timestampValue;
|
||||
|
||||
private Boolean booleanValue;
|
||||
|
||||
public WidgetParameter() {
|
||||
}
|
||||
|
||||
public Long getWidgetId() {
|
||||
return widgetId;
|
||||
}
|
||||
|
||||
public void setWidgetId(Long widgetId) {
|
||||
this.widgetId = widgetId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getStringValue() {
|
||||
return stringValue;
|
||||
}
|
||||
|
||||
public void setStringValue(String stringValue) {
|
||||
this.stringValue = stringValue;
|
||||
}
|
||||
|
||||
public Double getNumericValue() {
|
||||
return numericValue;
|
||||
}
|
||||
|
||||
public void setNumericValue(Double numericValue) {
|
||||
this.numericValue = numericValue;
|
||||
}
|
||||
|
||||
public LocalDateTime getTimestampValue() {
|
||||
return timestampValue;
|
||||
}
|
||||
|
||||
public void setTimestampValue(LocalDateTime timestampValue) {
|
||||
this.timestampValue = timestampValue;
|
||||
}
|
||||
|
||||
public Boolean getBooleanValue() {
|
||||
return booleanValue;
|
||||
}
|
||||
|
||||
public void setBooleanValue(Boolean booleanValue) {
|
||||
this.booleanValue = booleanValue;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,27 @@
|
|||
package dev.mvvasilev.finances.enums;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Period;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
public enum TimePeriod {
|
||||
SECONDLY,
|
||||
MINUTELY,
|
||||
HOURLY,
|
||||
DAILY,
|
||||
WEEKLY,
|
||||
BIWEEKLY,
|
||||
MONTHLY,
|
||||
QUARTERLY,
|
||||
YEARLY
|
||||
// SECONDLY(Duration.of(1, ChronoUnit.SECONDS)),
|
||||
// MINUTELY(Duration.of(1, ChronoUnit.MINUTES)),
|
||||
// HOURLY(Duration.of(1, ChronoUnit.HOURS)),
|
||||
DAILY(Period.ofDays(1)),
|
||||
WEEKLY(Period.ofDays(7)),
|
||||
BIWEEKLY(Period.ofDays(14)),
|
||||
MONTHLY(Period.ofMonths(1)),
|
||||
QUARTERLY(Period.ofMonths(3)),
|
||||
YEARLY(Period.ofYears(1));
|
||||
|
||||
private final Period duration;
|
||||
|
||||
TimePeriod(Period duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public Period getDuration() {
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package dev.mvvasilev.finances.enums;
|
||||
|
||||
import dev.mvvasilev.common.data.AbstractEnumConverter;
|
||||
import dev.mvvasilev.common.data.PersistableEnum;
|
||||
|
||||
public enum WidgetType implements PersistableEnum<String> {
|
||||
TOTAL_SPENDING_PER_CATEGORY,
|
||||
SPENDING_OVER_TIME_PER_CATEGORY;
|
||||
|
||||
@Override
|
||||
public String value() {
|
||||
return name();
|
||||
}
|
||||
|
||||
public static class JpaConverter extends AbstractEnumConverter<WidgetType, String> {
|
||||
public JpaConverter() {
|
||||
super(WidgetType.class);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,18 @@
|
|||
package dev.mvvasilev.finances.persistence;
|
||||
|
||||
import dev.mvvasilev.finances.dtos.SpendingOverTimeByCategoryDTO;
|
||||
import dev.mvvasilev.finances.enums.TimePeriod;
|
||||
import dev.mvvasilev.finances.persistence.dtos.SpendingOverTimeDTO;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.Query;
|
||||
import jakarta.persistence.Tuple;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -21,7 +27,7 @@ public class StatisticsRepository {
|
|||
this.entityManager = entityManager;
|
||||
}
|
||||
|
||||
public Map<Long, Double> fetchSpendingByCategory(LocalDateTime from, LocalDateTime to, List<Long> categoryIds) {
|
||||
public Map<Long, Double> fetchSpendingByCategory(Long[] categoryId, LocalDateTime from, LocalDateTime to) {
|
||||
Query nativeQuery = entityManager.createNativeQuery(
|
||||
"""
|
||||
SELECT ptc.category_id AS category_id, ROUND(CAST(SUM(pt.amount) AS NUMERIC), 2) AS total_spending
|
||||
|
@ -37,7 +43,7 @@ public class StatisticsRepository {
|
|||
Tuple.class
|
||||
);
|
||||
|
||||
nativeQuery.setParameter(1, categoryIds);
|
||||
nativeQuery.setParameter(1, categoryId);
|
||||
nativeQuery.setParameter(2, from);
|
||||
nativeQuery.setParameter(3, to);
|
||||
|
||||
|
@ -47,4 +53,21 @@ public class StatisticsRepository {
|
|||
(Tuple tuple) -> ((Number) tuple.get("total_spending")).doubleValue()
|
||||
));
|
||||
}
|
||||
|
||||
public Collection<SpendingOverTimeDTO> fetchSpendingByCategoryOverTime(LocalDateTime from, LocalDateTime to, TimePeriod period, Long[] categoryId) {
|
||||
Query nativeQuery = entityManager.createNativeQuery("SELECT * FROM statistics.spending_over_time(?1, ?2, ?3, ?4) ORDER BY period_beginning_timestamp;", Tuple.class);
|
||||
|
||||
nativeQuery.setParameter(1, categoryId);
|
||||
nativeQuery.setParameter(2, period.toString());
|
||||
nativeQuery.setParameter(3, from);
|
||||
nativeQuery.setParameter(4, to);
|
||||
|
||||
|
||||
//noinspection unchecked
|
||||
return nativeQuery.getResultStream().map(r -> new SpendingOverTimeDTO(
|
||||
((Tuple) r).get("category_id", Long.class),
|
||||
((Tuple) r).get("amount_for_period", Double.class),
|
||||
((Tuple) r).get("period_beginning_timestamp", Timestamp.class).toLocalDateTime()
|
||||
)).toList();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package dev.mvvasilev.finances.persistence.dtos;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record SpendingOverTimeDTO(
|
||||
Long categoryId,
|
||||
|
||||
Double amountForPeriod,
|
||||
|
||||
LocalDateTime periodBeginningTimestamp
|
||||
) {}
|
|
@ -5,6 +5,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@Service("authService")
|
||||
public class AuthorizationService {
|
||||
|
||||
|
@ -49,4 +51,8 @@ public class AuthorizationService {
|
|||
return !entityManager.createQuery(finalQuery).setMaxResults(1).getResultList().isEmpty();
|
||||
}
|
||||
|
||||
public boolean isOwner(Collection<Long> ids, Class<?> userOwnedEntity) {
|
||||
return ids.stream().allMatch(id -> isOwner(id, userOwnedEntity));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,12 +2,17 @@ package dev.mvvasilev.finances.services;
|
|||
|
||||
import dev.mvvasilev.common.data.AbstractEntity;
|
||||
import dev.mvvasilev.finances.dtos.CategoryDTO;
|
||||
import dev.mvvasilev.finances.dtos.SpendingByCategoriesDTO;
|
||||
import dev.mvvasilev.finances.dtos.SpendingByCategoryDTO;
|
||||
import dev.mvvasilev.finances.dtos.SpendingOverTimeByCategoryDTO;
|
||||
import dev.mvvasilev.finances.enums.TimePeriod;
|
||||
import dev.mvvasilev.finances.persistence.StatisticsRepository;
|
||||
import dev.mvvasilev.finances.persistence.TransactionCategoryRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
public class StatisticsService {
|
||||
|
@ -21,17 +26,53 @@ public class StatisticsService {
|
|||
this.statisticsRepository = statisticsRepository;
|
||||
}
|
||||
|
||||
public SpendingByCategoryDTO spendingByCategory(LocalDateTime from, LocalDateTime to, int userId) {
|
||||
final var categories = transactionCategoryRepository.fetchTransactionCategoriesWithUserId(userId);
|
||||
public SpendingByCategoriesDTO spendingByCategory(Long[] categoryId, LocalDateTime from, LocalDateTime to) {
|
||||
final var categories = transactionCategoryRepository.findAllById(Arrays.stream(categoryId).toList()).stream().map(c -> new CategoryDTO(c.getId(), c.getName(), null)).toList();
|
||||
|
||||
final var spendingByCategory = statisticsRepository.fetchSpendingByCategory(
|
||||
categoryId,
|
||||
from,
|
||||
to,
|
||||
categories.stream().map(AbstractEntity::getId).toList()
|
||||
to
|
||||
);
|
||||
|
||||
return new SpendingByCategoryDTO(
|
||||
categories.stream().map(c -> new CategoryDTO(c.getId(), c.getName(), null)).toList(),
|
||||
return new SpendingByCategoriesDTO(
|
||||
categories,
|
||||
spendingByCategory
|
||||
);
|
||||
}
|
||||
|
||||
public SpendingOverTimeByCategoryDTO spendingByCategoryOverTime(
|
||||
Long[] categoryId,
|
||||
TimePeriod period,
|
||||
LocalDateTime from,
|
||||
LocalDateTime to
|
||||
) {
|
||||
return new SpendingOverTimeByCategoryDTO(
|
||||
transactionCategoryRepository.findAllById(Arrays.stream(categoryId).toList()).stream().map(c -> new CategoryDTO(c.getId(), c.getName(), null)).toList(),
|
||||
period.getDuration(),
|
||||
statisticsRepository.fetchSpendingByCategoryOverTime(from, to, period, categoryId).stream().map(dto -> new SpendingByCategoryDTO(
|
||||
dto.categoryId(),
|
||||
dto.periodBeginningTimestamp(),
|
||||
dto.amountForPeriod()
|
||||
)).toList()
|
||||
);
|
||||
}
|
||||
|
||||
// Impose limits if necessary
|
||||
private boolean validatePeriodWithinLimits(LocalDateTime from, LocalDateTime to, TimePeriod period) {
|
||||
return switch (period) {
|
||||
// Can't request daily spending breakdown for a period longer than a month
|
||||
case DAILY -> ChronoUnit.MONTHS.between(from, to) <= 1;
|
||||
// Can't request weekly spending breakdown for a period longer than a year
|
||||
case WEEKLY -> ChronoUnit.YEARS.between(from, to) <= 1;
|
||||
// Can't request bi-weekly spending breakdown for a period longer than a year
|
||||
case BIWEEKLY -> ChronoUnit.YEARS.between(from, to) <= 1;
|
||||
// Can't request monthly spending breakdown for a period longer than 3 years
|
||||
case MONTHLY -> ChronoUnit.YEARS.between(from, to) <= 3;
|
||||
// Can't request quarterly spending breakdown for a period longer than 5 years
|
||||
case QUARTERLY -> ChronoUnit.YEARS.between(from, to) <= 5;
|
||||
// Can't request yearly spending breakdown for a period longer than 30 years
|
||||
case YEARLY -> ChronoUnit.YEARS.between(from, to) <= 30;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
CREATE OR REPLACE FUNCTION statistics.spending_over_time(
|
||||
category_ids BIGINT[],
|
||||
time_period TEXT,
|
||||
from_date TIMESTAMP,
|
||||
to_date TIMESTAMP
|
||||
)
|
||||
RETURNS TABLE (
|
||||
category_id BIGINT,
|
||||
amount_for_period FLOAT,
|
||||
period_beginning_timestamp TIMESTAMP
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
time_interval interval;
|
||||
BEGIN
|
||||
time_interval := CASE
|
||||
WHEN time_period = 'DAILY' THEN interval '1 day'
|
||||
WHEN time_period = 'WEEKLY' THEN interval '1 week'
|
||||
WHEN time_period = 'BIWEEKLY' THEN interval '2 weeks'
|
||||
WHEN time_period = 'MONTHLY' THEN interval '1 month'
|
||||
WHEN time_period = 'QUARTERLY' THEN interval '3 months'
|
||||
WHEN time_period = 'YEARLY' THEN interval '1 year'
|
||||
ELSE interval '1 day'
|
||||
END;
|
||||
|
||||
RETURN QUERY WITH start_end AS (
|
||||
SELECT
|
||||
c.id as category_id,
|
||||
generate_series(
|
||||
from_date,
|
||||
to_date,
|
||||
time_interval
|
||||
) as period_beginning_timestamp,
|
||||
generate_series(
|
||||
from_date,
|
||||
to_date,
|
||||
time_interval
|
||||
) + time_interval AS period_ending_timestamp
|
||||
FROM categories.transaction_category AS c
|
||||
WHERE c.id = any(category_ids)
|
||||
),
|
||||
amounts AS (
|
||||
SELECT
|
||||
ptc.category_id,
|
||||
SUM(pt.amount) AS amount_for_period,
|
||||
start_end.period_beginning_timestamp
|
||||
FROM start_end
|
||||
JOIN categories.processed_transaction_category AS ptc ON ptc.category_id = start_end.category_id
|
||||
JOIN transactions.processed_transaction AS pt ON ptc.processed_transaction_id = pt.id
|
||||
WHERE
|
||||
(
|
||||
start_end.period_ending_timestamp > to_date
|
||||
AND pt.timestamp BETWEEN start_end.period_beginning_timestamp AND to_date
|
||||
)
|
||||
OR (
|
||||
start_end.period_ending_timestamp <= to_date
|
||||
AND pt.timestamp BETWEEN start_end.period_beginning_timestamp AND start_end.period_ending_timestamp
|
||||
)
|
||||
GROUP BY (
|
||||
start_end.period_beginning_timestamp,
|
||||
start_end.period_ending_timestamp,
|
||||
ptc.category_id
|
||||
)
|
||||
)
|
||||
SELECT
|
||||
start_end.category_id,
|
||||
COALESCE(amounts.amount_for_period, 0) AS amount_for_period,
|
||||
start_end.period_beginning_timestamp
|
||||
FROM start_end
|
||||
LEFT OUTER JOIN amounts ON amounts.category_id = start_end.category_id AND amounts.period_beginning_timestamp = start_end.period_beginning_timestamp;
|
||||
END
|
||||
$$;
|
|
@ -0,0 +1,14 @@
|
|||
CREATE SCHEMA IF NOT EXISTS widgets;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS widgets.widget (
|
||||
id BIGSERIAL,
|
||||
type VARCHAR(255),
|
||||
positionX INTEGER,
|
||||
positionY INTEGER,
|
||||
sizeX INTEGER,
|
||||
sizeY INTEGER,
|
||||
name VARCHAR(255),
|
||||
time_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
time_last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT PK_widget PRIMARY KEY (id)
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
CREATE TABLE IF NOT EXISTS widgets.widget_parameter (
|
||||
id BIGSERIAL,
|
||||
widget_id BIGINT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
string_value VARCHAR(1024),
|
||||
numeric_value FLOAT,
|
||||
timestamp_value TIMESTAMP,
|
||||
boolean_value BOOLEAN,
|
||||
time_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
time_last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT PK_widget_parameter PRIMARY KEY (id),
|
||||
CONSTRAINT FK_widget_parameter_widget FOREIGN KEY (widget_id) REFERENCES widgets.widget(id)
|
||||
);
|
Loading…
Add table
Reference in a new issue