- Refactored transcations table and queries to use catetories table
- Added historical rates chart for investments page
This commit is contained in:
parent
60ddc816a0
commit
f5b3e49ca6
Binary file not shown.
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 162 KiB |
|
|
@ -135,7 +135,8 @@ public class PdfManager {
|
|||
|
||||
if (matchedCategory != null) {
|
||||
String label = matchedCategory.replace(" ", "");
|
||||
transaction.setCategory(matchedCategory);
|
||||
int categoryId = categoriesRepo.findCategoryIdByCategoryName(matchedCategory);
|
||||
transaction.setCategory(Integer.toString(categoryId));
|
||||
|
||||
int endIndex = rightmostIndex + label.length();
|
||||
line = line.substring(0, rightmostIndex) + line.substring(endIndex);
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@ import org.springframework.ui.Model;
|
|||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import com.example.FinanceTracker.Components.BootstrapColours;
|
||||
// import com.example.FinanceTracker.Components.ColourComponent;
|
||||
//import com.example.FinanceTracker.Components.PdfManager;
|
||||
import com.example.FinanceTracker.Components.PdfManager;
|
||||
import com.example.FinanceTracker.DTOs.GroupedTransaction;
|
||||
import com.example.FinanceTracker.DTOs.Home.OverviewCard;
|
||||
import com.example.FinanceTracker.Models.Transaction;
|
||||
|
|
@ -27,8 +26,8 @@ import com.example.FinanceTracker.Services.TransactionService;
|
|||
@Controller
|
||||
public class HomeController {
|
||||
|
||||
// @Autowired
|
||||
// private PdfManager pdfManager;
|
||||
@Autowired
|
||||
private PdfManager pdfManager;
|
||||
|
||||
@Autowired
|
||||
private TransactionService transactionsService;
|
||||
|
|
@ -42,7 +41,7 @@ public class HomeController {
|
|||
@GetMapping("/")
|
||||
public String Home(Model model) {
|
||||
|
||||
// transactionsService.saveAll(pdfManager.extractTransactions());
|
||||
//transactionsService.saveAll(pdfManager.extractTransactions());
|
||||
|
||||
// Balance Text
|
||||
model.addAttribute("latestBalance",
|
||||
|
|
|
|||
|
|
@ -1,33 +1,52 @@
|
|||
package com.example.FinanceTracker.Controllers;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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.client.RestClient;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import com.example.FinanceTracker.DTOs.Investments.CurrRate;
|
||||
import com.example.FinanceTracker.DTOs.Investments.Currency;
|
||||
import com.example.FinanceTracker.DTOs.Investments.ExchangeRate;
|
||||
import com.example.FinanceTracker.Services.InvestmentService;
|
||||
|
||||
@Controller
|
||||
public class InvestmentsController {
|
||||
|
||||
@Autowired
|
||||
private RestClient restClient;
|
||||
private InvestmentService investmentService;
|
||||
|
||||
@GetMapping("/investments")
|
||||
public String investments(Model model) {
|
||||
|
||||
// Create Currency Pills
|
||||
List<CurrRate> allRates = investmentService.getCurrRates();
|
||||
model.addAttribute("currRates", allRates);
|
||||
|
||||
// Populate Historical Rates Dropdown
|
||||
List<Currency> allCurrencies = getAllCurrencies();
|
||||
List<Currency> allCurrencies = investmentService.getAllCurrencies();
|
||||
model.addAttribute("allCurrencies", allCurrencies);
|
||||
|
||||
// Create Currency Pills
|
||||
List<CurrRate> allRates = getCurrRates();
|
||||
model.addAttribute("currRates", allRates);
|
||||
// historical rates chart
|
||||
List<ExchangeRate> historicalExchangeRates = investmentService.getHistoricalRates("USD", 1).reversed();
|
||||
|
||||
// Get all the rates from the historical Exchange Rates
|
||||
ArrayList<BigDecimal> historicalRates = new ArrayList<>();
|
||||
ArrayList<LocalDate> historicalRatesLabels = new ArrayList<>();
|
||||
for (ExchangeRate exchangeRate : historicalExchangeRates) {
|
||||
historicalRates.add(exchangeRate.getRate());
|
||||
historicalRatesLabels.add(exchangeRate.getCreatedAt().toLocalDate());
|
||||
}
|
||||
|
||||
model.addAttribute("historicalRates", historicalRates);
|
||||
model.addAttribute("labels", historicalRatesLabels);
|
||||
|
||||
// footer
|
||||
model.addAttribute("footerDetails", "Copyright © FinanceTracker " + LocalDate.now().getYear());
|
||||
|
|
@ -35,53 +54,50 @@ public class InvestmentsController {
|
|||
return "investments";
|
||||
}
|
||||
|
||||
private List<CurrRate> getCurrRates() {
|
||||
return List.of("EUR", "USD", "JPY", "GBP", "CAD", "AUD").stream()
|
||||
.map(code -> {
|
||||
Double rate = restClient.get()
|
||||
.uri(uriBuilder -> uriBuilder
|
||||
.path("/exchangeRate/pair")
|
||||
.queryParam("from", code)
|
||||
.queryParam("to", "ZAR")
|
||||
.build())
|
||||
.retrieve()
|
||||
.body(Double.class);
|
||||
@GetMapping("/historicalRatesWeekly")
|
||||
public String showHistoricalRatesWeek(Model model,
|
||||
@RequestParam("historicalRatesWeekly") boolean historicalRatesWeekly){
|
||||
|
||||
return new CurrRate(code, rate.toString(), getChange(code));
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
// historical rates chart
|
||||
List<ExchangeRate> historicalExchangeRates = investmentService.getHistoricalRates("USD", 3).reversed();
|
||||
|
||||
private String getChange(String targetCurr) {
|
||||
|
||||
final String effectiveTargetCurr = targetCurr.toUpperCase().contains("USD") ? "ZAR" : targetCurr;
|
||||
|
||||
List<ExchangeRate> exchangeRate = restClient.get()
|
||||
.uri(uriBuilder -> uriBuilder
|
||||
.path("exchangeRate/Historical")
|
||||
.queryParam("targetCurrency", effectiveTargetCurr)
|
||||
.queryParam("fromDate", LocalDate.now().minusDays(1))
|
||||
.queryParam("toDate", LocalDate.now().plusDays(1))
|
||||
.build())
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<List<ExchangeRate>>() {
|
||||
});
|
||||
|
||||
if (exchangeRate.get(0).getRate().compareTo(exchangeRate.get(1).getRate()) > 0) {
|
||||
return "UP";
|
||||
} else {
|
||||
return "DOWN";
|
||||
// Get all the rates from the historical Exchange Rates
|
||||
ArrayList<BigDecimal> historicalRates = new ArrayList<>();
|
||||
ArrayList<LocalDate> historicalRatesLabels = new ArrayList<>();
|
||||
for (ExchangeRate exchangeRate : historicalExchangeRates) {
|
||||
historicalRates.add(exchangeRate.getRate());
|
||||
historicalRatesLabels.add(exchangeRate.getCreatedAt().toLocalDate());
|
||||
}
|
||||
|
||||
model.addAttribute("historicalRates", historicalRates);
|
||||
model.addAttribute("labels", historicalRatesLabels);
|
||||
|
||||
return "investments :: HistoricalRatesChart";
|
||||
}
|
||||
|
||||
private List<Currency> getAllCurrencies() {
|
||||
|
||||
return restClient.get()
|
||||
.uri("/currency/currencies")
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<List<Currency>>() {
|
||||
});
|
||||
@GetMapping("/changeHistoricalCurr")
|
||||
public String changeHistoricalCurr(Model model,
|
||||
@RequestParam("currencyChange") String currencyChange){
|
||||
|
||||
// historical rates chart
|
||||
List<ExchangeRate> historicalExchangeRates = investmentService.getHistoricalRates(currencyChange, 3).reversed();
|
||||
|
||||
// Get all the rates from the historical Exchange Rates
|
||||
ArrayList<BigDecimal> historicalRates = new ArrayList<>();
|
||||
ArrayList<LocalDate> historicalRatesLabels = new ArrayList<>();
|
||||
for (ExchangeRate exchangeRate : historicalExchangeRates) {
|
||||
historicalRates.add(exchangeRate.getRate());
|
||||
historicalRatesLabels.add(exchangeRate.getCreatedAt().toLocalDate());
|
||||
}
|
||||
|
||||
model.addAttribute("historicalRates", historicalRates);
|
||||
model.addAttribute("labels", historicalRatesLabels);
|
||||
|
||||
return "investments :: HistoricalRatesChart";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public class Transaction {
|
|||
private String description;
|
||||
|
||||
@Column
|
||||
private String category;
|
||||
private int category;
|
||||
|
||||
@Column
|
||||
private BigDecimal moneyIn;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import java.util.List;
|
|||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.example.FinanceTracker.Models.Categories;
|
||||
|
|
@ -15,4 +16,7 @@ public interface CategoriesRepo extends JpaRepository<Categories, Integer>{
|
|||
@Query(value = "SELECT category_name FROM categories", nativeQuery=true)
|
||||
List<String> findAllCategoryNames();
|
||||
|
||||
@Query(value = "SELECT id FROM categories WHERE category_name = :category_name", nativeQuery = true)
|
||||
int findCategoryIdByCategoryName(@Param("category_name") String category_name);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,162 +18,172 @@ import com.example.FinanceTracker.Models.Transaction;
|
|||
@Repository
|
||||
public interface TransactionRepo extends JpaRepository<Transaction, UUID> {
|
||||
|
||||
@Query(value = """
|
||||
SELECT *
|
||||
FROM transactions
|
||||
WHERE date BETWEEN :fromDate AND :toDate
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
Page<Transaction> getTransactionsBetween(Pageable pageable, @Param("fromDate") LocalDate fromDate,
|
||||
@Param("toDate") LocalDate toDate);
|
||||
@Query(value = """
|
||||
SELECT *
|
||||
FROM transactions
|
||||
WHERE date BETWEEN :fromDate AND :toDate
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
Page<Transaction> getTransactionsBetween(Pageable pageable, @Param("fromDate") LocalDate fromDate,
|
||||
@Param("toDate") LocalDate toDate);
|
||||
|
||||
@Query(value = "SELECT * FROM transactions ORDER BY date DESC", nativeQuery = true)
|
||||
Page<Transaction> findAllPaged(Pageable pageable);
|
||||
@Query(value = """
|
||||
SELECT t.balance, t.date, t.money_in, t.money_out,
|
||||
t.description, c.category_name as category, t.fee,
|
||||
t.id
|
||||
FROM transactions t
|
||||
JOIN categories c ON t.category = c.id
|
||||
ORDER BY date DESC
|
||||
""", nativeQuery = true)
|
||||
Page<Transaction> findAllPaged(Pageable pageable);
|
||||
|
||||
@Query(value = """
|
||||
SELECT * FROM transactions
|
||||
WHERE CONCAT_WS('', date, description, category, money_in, money_out, fee, balance)
|
||||
LIKE Concat('%', :searchValue, '%')
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
Page<Transaction> searchTransactions(Pageable pageable, @Param("searchValue") String SearchValue);
|
||||
@Query(value = """
|
||||
SELECT
|
||||
t.balance, t.date, t.money_in, t.money_out, t.description, c.category_name as category, t.fee, t.id
|
||||
FROM transactions t
|
||||
JOIN categories c ON t.category = c.id
|
||||
WHERE CONCAT_WS('', t.balance, t.date, t.money_in, t.money_out, t.description, c.category_name, t.fee)
|
||||
LIKE Concat('%', :searchValue, '%')
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
Page<Transaction> searchTransactions(Pageable pageable, @Param("searchValue") String SearchValue);
|
||||
|
||||
@Query(value = """
|
||||
SELECT (balance - 30) as balance
|
||||
FROM transactions ORDER BY date DESC LIMIT 1
|
||||
""", nativeQuery = true)
|
||||
double getLatestBalance();
|
||||
@Query(value = """
|
||||
SELECT (balance - 30) as balance
|
||||
FROM transactions ORDER BY date DESC LIMIT 1
|
||||
""", nativeQuery = true)
|
||||
double getLatestBalance();
|
||||
|
||||
// =============================================================
|
||||
// MONEY IN QUERIES
|
||||
// =============================================================
|
||||
// =============================================================
|
||||
// MONEY IN QUERIES
|
||||
// =============================================================
|
||||
|
||||
@Query(value = """
|
||||
SELECT SUM(money_in)
|
||||
FROM transactions
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
double getTotalMoneyIn();
|
||||
@Query(value = """
|
||||
SELECT SUM(money_in)
|
||||
FROM transactions
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
double getTotalMoneyIn();
|
||||
|
||||
@Query(value = """
|
||||
SELECT SUM(money_in)
|
||||
FROM transactions
|
||||
WHERE YEAR(date) = :year
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
double getTotalMoneyInByYear(@Param("year") int year);
|
||||
@Query(value = """
|
||||
SELECT SUM(money_in)
|
||||
FROM transactions
|
||||
WHERE YEAR(date) = :year
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
double getTotalMoneyInByYear(@Param("year") int year);
|
||||
|
||||
@Query(value = """
|
||||
SELECT SUM(money_in) / (SELECT COUNT(DISTINCT MONTH(date)) FROM transactions WHERE YEAR(date) = :year)
|
||||
FROM transactions
|
||||
WHERE YEAR(date) = :year
|
||||
""", nativeQuery = true)
|
||||
double getAverageMonthlyMoneyIn(int year);
|
||||
@Query(value = """
|
||||
SELECT SUM(money_in) / (SELECT COUNT(DISTINCT MONTH(date)) FROM transactions WHERE YEAR(date) = :year)
|
||||
FROM transactions
|
||||
WHERE YEAR(date) = :year
|
||||
""", nativeQuery = true)
|
||||
double getAverageMonthlyMoneyIn(int year);
|
||||
|
||||
@Query(value = """
|
||||
SELECT category, SUM(money_in)
|
||||
FROM transactions
|
||||
WHERE money_in != 0
|
||||
GROUP BY category
|
||||
""", nativeQuery = true)
|
||||
List<GroupedTransaction> getIncomeGroupedTransactions();
|
||||
@Query(value = """
|
||||
SELECT c.category_name, SUM(money_in)
|
||||
FROM transactions t JOIN categories c ON t.category = c.id
|
||||
WHERE money_in != 0
|
||||
GROUP BY category
|
||||
""", nativeQuery = true)
|
||||
List<GroupedTransaction> getIncomeGroupedTransactions();
|
||||
|
||||
// =============================================================
|
||||
// MONEY OUT QUERIES
|
||||
// =============================================================
|
||||
@Query(value = """
|
||||
SELECT SUM(money_out)
|
||||
FROM transactions
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
double getTotalMoneyOut();
|
||||
// =============================================================
|
||||
// MONEY OUT QUERIES
|
||||
// =============================================================
|
||||
@Query(value = """
|
||||
SELECT SUM(money_out)
|
||||
FROM transactions
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
double getTotalMoneyOut();
|
||||
|
||||
@Query(value = """
|
||||
SELECT SUM(money_out) / (SELECT COUNT(DISTINCT MONTH(date)) FROM transactions WHERE YEAR(date) = :year)
|
||||
FROM transactions
|
||||
WHERE YEAR(date) = :year
|
||||
""", nativeQuery = true)
|
||||
double getAverageMonthlyMoneyOut(int year);
|
||||
@Query(value = """
|
||||
SELECT SUM(money_out) / (SELECT COUNT(DISTINCT MONTH(date)) FROM transactions WHERE YEAR(date) = :year)
|
||||
FROM transactions
|
||||
WHERE YEAR(date) = :year
|
||||
""", nativeQuery = true)
|
||||
double getAverageMonthlyMoneyOut(int year);
|
||||
|
||||
@Query(value = """
|
||||
SELECT category, SUM(money_out)
|
||||
FROM transactions
|
||||
WHERE money_out != 0
|
||||
GROUP BY category
|
||||
""", nativeQuery = true)
|
||||
List<GroupedTransaction> getExpenditureGroupedTransactions();
|
||||
@Query(value = """
|
||||
SELECT c.category_name, SUM(money_out)
|
||||
FROM transactions t JOIN categories c ON t.category = c.id
|
||||
WHERE money_out != 0
|
||||
GROUP BY category
|
||||
""", nativeQuery = true)
|
||||
List<GroupedTransaction> getExpenditureGroupedTransactions();
|
||||
|
||||
// =============================================================
|
||||
// FEE QUERIES
|
||||
// =============================================================
|
||||
@Query(value = """
|
||||
SELECT SUM(fee)
|
||||
FROM transactions
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
double getTotalFees();
|
||||
// =============================================================
|
||||
// FEE QUERIES
|
||||
// =============================================================
|
||||
@Query(value = """
|
||||
SELECT SUM(fee)
|
||||
FROM transactions
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
double getTotalFees();
|
||||
|
||||
@Query(value = """
|
||||
SELECT SUM(fee)
|
||||
FROM transactions
|
||||
WHERE YEAR(date) = :year
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
double getTotalFeesByYear(@Param("year") int year);
|
||||
@Query(value = """
|
||||
SELECT SUM(fee)
|
||||
FROM transactions
|
||||
WHERE YEAR(date) = :year
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
double getTotalFeesByYear(@Param("year") int year);
|
||||
|
||||
// =============================================================
|
||||
// INTEREST QUERIES
|
||||
// =============================================================
|
||||
@Query(value = """
|
||||
SELECT SUM(money_in)
|
||||
FROM transactions
|
||||
WHERE category = 'Interest'
|
||||
""", nativeQuery = true)
|
||||
double getTotalInterest();
|
||||
// =============================================================
|
||||
// INTEREST QUERIES
|
||||
// =============================================================
|
||||
@Query(value = """
|
||||
SELECT SUM(money_in)
|
||||
FROM transactions
|
||||
WHERE category = 'Interest'
|
||||
""", nativeQuery = true)
|
||||
double getTotalInterest();
|
||||
|
||||
@Query(value = """
|
||||
SELECT SUM(money_in) / (SELECT COUNT(DISTINCT MONTH(date)) FROM transactions WHERE YEAR(date) = :year)
|
||||
FROM transactions
|
||||
WHERE YEAR(date) = :year AND category = 'Interest'
|
||||
""", nativeQuery = true)
|
||||
double averageMonthlyInterest(@Param("year") int year);
|
||||
@Query(value = """
|
||||
SELECT SUM(money_in) / (SELECT COUNT(DISTINCT MONTH(date)) FROM transactions WHERE YEAR(date) = :year)
|
||||
FROM transactions
|
||||
WHERE YEAR(date) = :year AND category = 'Interest'
|
||||
""", nativeQuery = true)
|
||||
double averageMonthlyInterest(@Param("year") int year);
|
||||
|
||||
// =============================================================
|
||||
// OTHER QUERIES
|
||||
// =============================================================
|
||||
@Query(value = """
|
||||
SELECT
|
||||
CONCAT('Q', QUARTER(date)) AS Quarters,
|
||||
SUM(money_in) AS \"Total MoneyIn\",
|
||||
SUM(money_out) AS \"Total MoneyOut\",
|
||||
SUM(fee) AS \"Total Fees\"
|
||||
FROM transactions
|
||||
WHERE YEAR(date) = :year
|
||||
GROUP BY QUARTER(date)
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
List<QuartlyTotals> getQuarterlyTotals(@Param("year") int year);
|
||||
// =============================================================
|
||||
// OTHER QUERIES
|
||||
// =============================================================
|
||||
@Query(value = """
|
||||
SELECT
|
||||
CONCAT('Q', QUARTER(date)) AS Quarters,
|
||||
SUM(money_in) AS \"Total MoneyIn\",
|
||||
SUM(money_out) AS \"Total MoneyOut\",
|
||||
SUM(fee) AS \"Total Fees\"
|
||||
FROM transactions
|
||||
WHERE YEAR(date) = :year
|
||||
GROUP BY QUARTER(date)
|
||||
ORDER BY date
|
||||
""", nativeQuery = true)
|
||||
List<QuartlyTotals> getQuarterlyTotals(@Param("year") int year);
|
||||
|
||||
@Query(value = """
|
||||
SELECT
|
||||
(SELECT SUM(money_in) FROM transactions WHERE category != 'Interest' AND money_in != 0) AS money_in,
|
||||
(SELECT SUM(money_out) * -1 FROM transactions WHERE category != 'Fees' AND money_out != 0) AS money_out,
|
||||
(SELECT SUM(money_in) FROM transactions WHERE category = 'Interest') AS Interest,
|
||||
(SELECT SUM(fee) * -1 FROM transactions) AS fees
|
||||
FROM transactions LIMIT 1
|
||||
""", nativeQuery = true)
|
||||
List<Object[]> getTotals();
|
||||
@Query(value = """
|
||||
SELECT
|
||||
(SELECT SUM(money_in) FROM transactions WHERE category != 'Interest' AND money_in != 0) AS money_in,
|
||||
(SELECT SUM(money_out) * -1 FROM transactions WHERE category != 'Fees' AND money_out != 0) AS money_out,
|
||||
(SELECT SUM(money_in) FROM transactions WHERE category = 'Interest') AS Interest,
|
||||
(SELECT SUM(fee) * -1 FROM transactions) AS fees
|
||||
FROM transactions LIMIT 1
|
||||
""", nativeQuery = true)
|
||||
List<Object[]> getTotals();
|
||||
|
||||
@Query(value = """
|
||||
SELECT (SUM(money_in) + SUM(money_out)) AS "Net Cashflow"
|
||||
FROM transactions
|
||||
""", nativeQuery = true)
|
||||
double getNetCashflow();
|
||||
@Query(value = """
|
||||
SELECT (SUM(money_in) + SUM(money_out)) AS "Net Cashflow"
|
||||
FROM transactions
|
||||
""", nativeQuery = true)
|
||||
double getNetCashflow();
|
||||
|
||||
@Query(value = """
|
||||
SELECT (SUM(money_in) + SUM(money_out)) / NULLIF(SUM(money_in), 0) AS "Savings Rate"
|
||||
FROM transactions;
|
||||
""", nativeQuery = true)
|
||||
double getSavingRate();
|
||||
@Query(value = """
|
||||
SELECT (SUM(money_in) + SUM(money_out)) / NULLIF(SUM(money_in), 0) AS "Savings Rate"
|
||||
FROM transactions;
|
||||
""", nativeQuery = true)
|
||||
double getSavingRate();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
package com.example.FinanceTracker.Services;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import com.example.FinanceTracker.DTOs.Investments.CurrRate;
|
||||
import com.example.FinanceTracker.DTOs.Investments.Currency;
|
||||
import com.example.FinanceTracker.DTOs.Investments.ExchangeRate;
|
||||
|
||||
@Service
|
||||
public class InvestmentService {
|
||||
|
||||
@Autowired
|
||||
private RestClient restClient;
|
||||
|
||||
private final int WEEK = 1;
|
||||
private final int MONTH = 2;
|
||||
private final int YEAR = 3;
|
||||
|
||||
public List<CurrRate> getCurrRates() {
|
||||
return List.of("EUR", "USD", "JPY", "GBP", "CAD", "AUD").stream()
|
||||
.map(code -> {
|
||||
Double rate = restClient.get()
|
||||
.uri(uriBuilder -> uriBuilder
|
||||
.path("/exchangeRate/pair")
|
||||
.queryParam("from", code)
|
||||
.queryParam("to", "ZAR")
|
||||
.build())
|
||||
.retrieve()
|
||||
.body(Double.class);
|
||||
|
||||
return new CurrRate(code, rate.toString(), getChange(code, "ZAR"));
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
private String getChange(String targetCurr, String desiredCurrency) {
|
||||
|
||||
final String effectiveTargetCurr = targetCurr.toUpperCase().contains("USD") ? "ZAR" : targetCurr;
|
||||
|
||||
List<ExchangeRate> exchangeRate = restClient.get()
|
||||
.uri(uriBuilder -> uriBuilder
|
||||
.path("exchangeRate/historical")
|
||||
.queryParam("targetCurrency", effectiveTargetCurr)
|
||||
.queryParam("desiredCurrency", desiredCurrency)
|
||||
.queryParam("fromDate", LocalDate.now().minusDays(1))
|
||||
.queryParam("toDate", LocalDate.now().plusDays(1))
|
||||
.build())
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<List<ExchangeRate>>() {
|
||||
});
|
||||
|
||||
if (exchangeRate.get(0).getRate().compareTo(exchangeRate.get(1).getRate()) > 0) {
|
||||
return "UP";
|
||||
} else {
|
||||
return "DOWN";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<ExchangeRate> getHistoricalRates(String targetCurr, int duration) {
|
||||
|
||||
LocalDate toDate = LocalDate.now().plusDays(1);
|
||||
LocalDate fromDate;
|
||||
|
||||
switch (duration) {
|
||||
case WEEK:
|
||||
fromDate = LocalDate.now().minusWeeks(1);
|
||||
break;
|
||||
case MONTH:
|
||||
fromDate = LocalDate.now().minusMonths(1);
|
||||
break;
|
||||
case YEAR:
|
||||
fromDate = LocalDate.now().minusYears(1);
|
||||
break;
|
||||
default:
|
||||
fromDate = LocalDate.now().minusWeeks(1);
|
||||
}
|
||||
|
||||
List<ExchangeRate> historicalRates = restClient.get()
|
||||
.uri(uriBuilder -> uriBuilder
|
||||
.path("exchangeRate/historical")
|
||||
.queryParam("targetCurrency", targetCurr)
|
||||
.queryParam("desiredCurrency", "ZAR")
|
||||
.queryParam("fromDate", fromDate.toString())
|
||||
.queryParam("toDate", toDate.toString())
|
||||
.build())
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<List<ExchangeRate>>() {
|
||||
});
|
||||
|
||||
|
||||
|
||||
return historicalRates;
|
||||
}
|
||||
|
||||
public List<Currency> getAllCurrencies() {
|
||||
|
||||
return restClient.get()
|
||||
.uri("/currency/currencies")
|
||||
.retrieve()
|
||||
.body(new ParameterizedTypeReference<List<Currency>>() {
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,39 +1,32 @@
|
|||
<div th:fragment="overviewCard(card)" class="col-md-6 col-xl-3 mb-4">
|
||||
<div class="card shadow-sm h-100 border-start border-4"
|
||||
th:classappend="${card.borderColour}"
|
||||
<div class="card shadow-sm h-100 text-center"
|
||||
role="group"
|
||||
th:aria-label="${card.heading}">
|
||||
|
||||
<div class="card-body d-flex align-items-center justify-content-between">
|
||||
<!-- Text Content -->
|
||||
<div class="me-3">
|
||||
<div class="text-uppercase fw-semibold small"
|
||||
th:classappend="${card.textColour}">
|
||||
<span th:text="${card.heading}">
|
||||
Average Earnings (Monthly)
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="fw-bold fs-4 text-dark">
|
||||
<span th:text="${card.value}">$40,000</span>
|
||||
</div>
|
||||
<i class="material-icons fs-3 mb-2 text-muted"
|
||||
th:text="${card.icon}"
|
||||
aria-hidden="true">
|
||||
attach_money
|
||||
</i>
|
||||
|
||||
<!-- Optional subtext -->
|
||||
<div class="text-muted small"
|
||||
th:if="${card.subText}"
|
||||
th:text="${card.subText}">
|
||||
Compared to last month
|
||||
</div>
|
||||
<div class="fw-bold fs-5"
|
||||
th:text="${card.value}">
|
||||
$40,000
|
||||
</div>
|
||||
|
||||
<!-- Icon -->
|
||||
<div class="flex-shrink-0">
|
||||
<i class="material-icons fs-2 text-muted"
|
||||
th:text="${card.icon}"
|
||||
aria-hidden="true">
|
||||
attach_money
|
||||
</i>
|
||||
<div class="text-uppercase fs-7 fw-semibold mt-1"
|
||||
th:classappend="${card.textColour}">
|
||||
<span th:text="${card.heading}">Average Earnings</span>
|
||||
</div>
|
||||
|
||||
<div class="small text-muted mt-1"
|
||||
th:if="${card.subText}"
|
||||
th:text="${card.subText}">
|
||||
Compared to last month
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,15 +6,16 @@
|
|||
${currRate.change} == 'UP'
|
||||
? ' border border-success text-success'
|
||||
: ' border border-danger text-danger'
|
||||
"
|
||||
>
|
||||
">
|
||||
|
||||
<span
|
||||
class="material-icons bg-gray"
|
||||
th:text="${currRate.change} == 'UP' ? 'trending_up' : 'trending_down'">
|
||||
</span>
|
||||
|
||||
<span class="fw-semibold text-dark" th:text="${currRate.ISO}"></span>
|
||||
|
||||
<span th:text="${currRate.value}"></span>
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -135,9 +135,12 @@
|
|||
<span class="fw-bold text-primary mb-1">Historical Rates</span>
|
||||
</div>
|
||||
<div class="col-lg-4 mw-50">
|
||||
<div class="btn-group d-flex justify-content-center mb-1" role="group" aria-label="Time range">
|
||||
<input type="radio" class="btn-check" name="options" id="option1"
|
||||
autocomplete="off" checked>
|
||||
<div class="btn-group d-flex justify-content-center mb-1" role="group"
|
||||
aria-label="Time range">
|
||||
<input th:hx-get="@{/historicalRatesWeekly}" hx-trigger="click once"
|
||||
hx-swap="outerHTML" hx-target="#HistoricalRatesChartContainer"
|
||||
type="radio" class="btn-check" name="historicalRatesWeekly"
|
||||
id="option1" autocomplete="off" checked>
|
||||
<label class="btn btn-secondary" for="option1">Week</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="options" id="option2"
|
||||
|
|
@ -151,7 +154,11 @@
|
|||
</div>
|
||||
|
||||
<div class="col-lg-4 d-flex justify-content-end">
|
||||
<select class="form-select" name="" id="" style="max-width: 200px;">
|
||||
<select class="form-select" name="currencyChange" id="" style="max-width: 200px;"
|
||||
th:hx-get="@{/changeHistoricalCurr}"
|
||||
hx-trigger="change"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#HistoricalRatesChartContainer">
|
||||
<option th:each="currency : ${allCurrencies}"
|
||||
th:value="${currency.code}"
|
||||
th:text="${currency.code + ' - ' + currency.currencyName}">Test
|
||||
|
|
@ -161,7 +168,44 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="HistoricalRatesChart"></canvas>
|
||||
<div id="HistoricalRatesChartContainer" th:fragment="HistoricalRatesChart"
|
||||
style="height: 350px;">
|
||||
|
||||
|
||||
<canvas id="HistoricalRatesChart"></canvas>
|
||||
|
||||
<script th:inline="javascript">
|
||||
/*<![CDATA[*/
|
||||
new Chart(document.getElementById('HistoricalRatesChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: /*[[${labels}]]*/[],
|
||||
datasets: [{
|
||||
label: 'Rates',
|
||||
data: /*[[${historicalRates}]]*/[],
|
||||
borderColor: '#2E7D32',
|
||||
backgroundColor: 'rgba(46,125,50,0.15)',
|
||||
borderWidth: 2,
|
||||
fill: true,
|
||||
tension: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: v => `$${v.toLocaleString()}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
/*]]>*/
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -185,6 +229,40 @@
|
|||
<script src="assets/js/chart.min.js"></script>
|
||||
<script src="assets/js/bs-init.js"></script>
|
||||
<script src="assets/js/theme.js"></script>
|
||||
<script th:inline="javascript">
|
||||
/*<![CDATA[*/
|
||||
new Chart(document.getElementById('HistoricalRatesChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: /*[[${labels}]]*/[],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Rates',
|
||||
data: /*[[${historicalRates}]]*/[],
|
||||
borderColor: '#2E7D32',
|
||||
backgroundColor: 'rgba(46,125,50,0.15)',
|
||||
borderWidth: 2,
|
||||
fill: true,
|
||||
tension: 0
|
||||
},
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: v => `$${v.toLocaleString()}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
/*]]>*/
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -128,22 +128,22 @@
|
|||
<table th:fragment="transactionTable" class="table my-0" id="dataTable">
|
||||
<!-- Fixed column widths -->
|
||||
<colgroup>
|
||||
<col style="width: 120px;"> <!-- Date -->
|
||||
<col style="width: 130px;"> <!-- Money In -->
|
||||
<col style="width: 130px;"> <!-- Money Out -->
|
||||
<col style="width: 140px;"> <!-- Balance -->
|
||||
<col style="width: 110px;"> <!-- Fees -->
|
||||
<col style="width: 140px;"> <!-- Balance -->
|
||||
<col style="width: 120px;"> <!-- Date -->
|
||||
<col style="width: 160px;"> <!-- Category -->
|
||||
<col style="width: 260px;"> <!-- Description -->
|
||||
</colgroup>
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th class="numeric">Money In</th>
|
||||
<th class="numeric">Money Out</th>
|
||||
<th class="numeric">Balance</th>
|
||||
<th class="numeric">Fees</th>
|
||||
<th class="numeric">Balance</th>
|
||||
<th>Date</th>
|
||||
<th>Category</th>
|
||||
<th>Description</th>
|
||||
|
||||
|
|
@ -154,15 +154,15 @@
|
|||
<tr th:each="transaction : ${transactions}"
|
||||
th:with="rowClass=${(transaction.moneyIn + transaction.moneyOut + transaction.fee) < 0 ? 'txn-negative' : 'txn-positive'}"
|
||||
th:class="${rowClass}">
|
||||
<td th:text="${transaction.date}"></td>
|
||||
<td class="numeric"
|
||||
th:text="${#numbers.formatCurrency(transaction.moneyIn)}"></td>
|
||||
<td class="numeric"
|
||||
th:text="${#numbers.formatCurrency(transaction.moneyOut)}"></td>
|
||||
<td class="numeric"
|
||||
th:text="${#numbers.formatCurrency(transaction.balance)}"></td>
|
||||
<td class="numeric" th:text="${#numbers.formatCurrency(transaction.fee)}">
|
||||
</td>
|
||||
<td class="numeric"
|
||||
th:text="${#numbers.formatCurrency(transaction.balance)}"></td>
|
||||
<td th:text="${transaction.date}"></td>
|
||||
<td th:text="${transaction.category}"></td>
|
||||
<td class="description" th:text="${transaction.description}"></td>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue