mirror of
https://github.com/mvvasilev/personal-finances.git
synced 2025-04-19 14:19:52 +03:00
Add statistics endpoint for fetching total spending by category for time period
This commit is contained in:
parent
acfbe7033a
commit
1a9dc36cf5
6 changed files with 162 additions and 1 deletions
|
@ -1,10 +1,49 @@
|
||||||
package dev.mvvasilev.finances.controllers;
|
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.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.core.Authentication;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
@RestController("/statistics")
|
@RestController("/statistics")
|
||||||
public class StatisticsController {
|
public class StatisticsController extends AbstractRestController {
|
||||||
|
|
||||||
|
private final StatisticsService statisticsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public StatisticsController(StatisticsService statisticsService) {
|
||||||
|
this.statisticsService = statisticsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/totalSpendingByCategory")
|
||||||
|
public ResponseEntity<APIResponseDTO<SpendingByCategoryDTO>> fetchSpendingByCategory(
|
||||||
|
@RequestParam(defaultValue = "1970-01-01T00:00:00") LocalDateTime from,
|
||||||
|
@RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to,
|
||||||
|
Authentication authentication
|
||||||
|
) {
|
||||||
|
return ok(statisticsService.spendingByCategory(from, to, Integer.parseInt(authentication.getName())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/spendingOverTimeByCategory")
|
||||||
|
public ResponseEntity<APIResponseDTO<SpendingOverTimeByCategoryDTO>> fetchSpendingOverTimeByCategory(
|
||||||
|
Collection<Long> categoryId,
|
||||||
|
TimePeriod period,
|
||||||
|
@RequestParam(defaultValue = "1970-01-01T00:00:00") LocalDateTime from,
|
||||||
|
@RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to,
|
||||||
|
Authentication authentication
|
||||||
|
) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package dev.mvvasilev.finances.dtos;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public record SpendingByCategoryDTO(
|
||||||
|
Collection<CategoryDTO> categories,
|
||||||
|
|
||||||
|
Map<Long, Double> spendingByCategory
|
||||||
|
) {
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package dev.mvvasilev.finances.dtos;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public record SpendingOverTimeByCategoryDTO(
|
||||||
|
Collection<CategoryDTO> categories,
|
||||||
|
|
||||||
|
Map<Long, Collection<Double>> spendingOverTime
|
||||||
|
) {
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package dev.mvvasilev.finances.enums;
|
||||||
|
|
||||||
|
public enum TimePeriod {
|
||||||
|
SECONDLY,
|
||||||
|
MINUTELY,
|
||||||
|
HOURLY,
|
||||||
|
DAILY,
|
||||||
|
WEEKLY,
|
||||||
|
BIWEEKLY,
|
||||||
|
MONTHLY,
|
||||||
|
QUARTERLY,
|
||||||
|
YEARLY
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package dev.mvvasilev.finances.persistence;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.Query;
|
||||||
|
import jakarta.persistence.Tuple;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public class StatisticsRepository {
|
||||||
|
|
||||||
|
private final EntityManager entityManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public StatisticsRepository(EntityManager entityManager) {
|
||||||
|
this.entityManager = entityManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Long, Double> fetchSpendingByCategory(LocalDateTime from, LocalDateTime to, List<Long> categoryIds) {
|
||||||
|
Query nativeQuery = entityManager.createNativeQuery(
|
||||||
|
"""
|
||||||
|
SELECT ptc.category_id AS category_id, ROUND(CAST(SUM(pt.amount) AS NUMERIC), 2) AS total_spending
|
||||||
|
FROM transactions.processed_transaction AS pt
|
||||||
|
JOIN categories.processed_transaction_category AS ptc ON ptc.processed_transaction_id = pt.id
|
||||||
|
WHERE
|
||||||
|
pt.is_inflow = FALSE
|
||||||
|
AND ptc.category_id IN (?1)
|
||||||
|
AND (pt.timestamp BETWEEN ?2 AND ?3)
|
||||||
|
GROUP BY ptc.category_id
|
||||||
|
ORDER BY total_spending DESC;
|
||||||
|
""",
|
||||||
|
Tuple.class
|
||||||
|
);
|
||||||
|
|
||||||
|
nativeQuery.setParameter(1, categoryIds);
|
||||||
|
nativeQuery.setParameter(2, from);
|
||||||
|
nativeQuery.setParameter(3, to);
|
||||||
|
|
||||||
|
//noinspection unchecked
|
||||||
|
return (Map<Long, Double>) nativeQuery.getResultStream().collect(Collectors.toMap(
|
||||||
|
(Tuple tuple) -> ((Number) tuple.get("category_id")).longValue(),
|
||||||
|
(Tuple tuple) -> ((Number) tuple.get("total_spending")).doubleValue()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package dev.mvvasilev.finances.services;
|
||||||
|
|
||||||
|
import dev.mvvasilev.common.data.AbstractEntity;
|
||||||
|
import dev.mvvasilev.finances.dtos.CategoryDTO;
|
||||||
|
import dev.mvvasilev.finances.dtos.SpendingByCategoryDTO;
|
||||||
|
import dev.mvvasilev.finances.persistence.StatisticsRepository;
|
||||||
|
import dev.mvvasilev.finances.persistence.TransactionCategoryRepository;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class StatisticsService {
|
||||||
|
|
||||||
|
private final TransactionCategoryRepository transactionCategoryRepository;
|
||||||
|
|
||||||
|
private final StatisticsRepository statisticsRepository;
|
||||||
|
|
||||||
|
public StatisticsService(TransactionCategoryRepository transactionCategoryRepository, StatisticsRepository statisticsRepository) {
|
||||||
|
this.transactionCategoryRepository = transactionCategoryRepository;
|
||||||
|
this.statisticsRepository = statisticsRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpendingByCategoryDTO spendingByCategory(LocalDateTime from, LocalDateTime to, int userId) {
|
||||||
|
final var categories = transactionCategoryRepository.fetchTransactionCategoriesWithUserId(userId);
|
||||||
|
final var spendingByCategory = statisticsRepository.fetchSpendingByCategory(
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
categories.stream().map(AbstractEntity::getId).toList()
|
||||||
|
);
|
||||||
|
|
||||||
|
return new SpendingByCategoryDTO(
|
||||||
|
categories.stream().map(c -> new CategoryDTO(c.getId(), c.getName(), null)).toList(),
|
||||||
|
spendingByCategory
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue