Add new "Brokerage" account type, and add more convenience for getting total assets.
This commit is contained in:
		
							parent
							
								
									d43c61d6ee
								
							
						
					
					
						commit
						5a339cbee6
					
				| 
						 | 
				
			
			@ -4,7 +4,6 @@ import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
 | 
			
		|||
import com.andrewlalis.perfin.data.AccountRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.CurrencyUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.Account;
 | 
			
		||||
import com.andrewlalis.perfin.model.MoneyValue;
 | 
			
		||||
import com.andrewlalis.perfin.model.Profile;
 | 
			
		||||
import com.andrewlalis.perfin.view.component.AccountTile;
 | 
			
		||||
import javafx.application.Platform;
 | 
			
		||||
| 
						 | 
				
			
			@ -57,15 +56,9 @@ public class AccountsViewController implements RouteSelectionListener {
 | 
			
		|||
                                .toList()
 | 
			
		||||
                        ));
 | 
			
		||||
            });
 | 
			
		||||
            // Compute grand totals!
 | 
			
		||||
            Thread.ofVirtual().start(() -> {
 | 
			
		||||
                var totals = profile.dataSource().getCombinedAccountBalances();
 | 
			
		||||
                StringBuilder sb = new StringBuilder("Totals: ");
 | 
			
		||||
                for (var entry : totals.entrySet()) {
 | 
			
		||||
                    sb.append(CurrencyUtil.formatMoneyWithCurrencyPrefix(new MoneyValue(entry.getValue(), entry.getKey())));
 | 
			
		||||
                }
 | 
			
		||||
                Platform.runLater(() -> totalLabel.setText(sb.toString().strip()));
 | 
			
		||||
            });
 | 
			
		||||
            profile.dataSource().getCombinedAccountBalances()
 | 
			
		||||
                    .thenApply(CurrencyUtil::formatMoneyValues)
 | 
			
		||||
                    .thenAccept(s -> Platform.runLater(() -> totalLabel.setText("Totals: " + s)));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -86,6 +86,7 @@ public class EditAccountController implements RouteSelectionListener {
 | 
			
		|||
        accountTypeChoiceBox.getItems().add(AccountType.CHECKING);
 | 
			
		||||
        accountTypeChoiceBox.getItems().add(AccountType.SAVINGS);
 | 
			
		||||
        accountTypeChoiceBox.getItems().add(AccountType.CREDIT_CARD);
 | 
			
		||||
        accountTypeChoiceBox.getItems().add(AccountType.BROKERAGE);
 | 
			
		||||
        accountTypeChoiceBox.getSelectionModel().select(AccountType.CHECKING);
 | 
			
		||||
 | 
			
		||||
        initialBalanceContent.visibleProperty().bind(creatingNewAccount);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -106,19 +106,27 @@ public interface DataSource {
 | 
			
		|||
        return cf;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default Map<Currency, BigDecimal> getCombinedAccountBalances() {
 | 
			
		||||
        try (var accountRepo = getAccountRepository()) {
 | 
			
		||||
            List<Account> accounts = accountRepo.findAll(PageRequest.unpaged()).items();
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a list of combined total assets for each currency that's tracked,
 | 
			
		||||
     * ordered with highest assets first.
 | 
			
		||||
     * @return A future that resolves to the list of amounts for each currency.
 | 
			
		||||
     */
 | 
			
		||||
    default CompletableFuture<List<MoneyValue>> getCombinedAccountBalances() {
 | 
			
		||||
        return mapRepoAsync(AccountRepository.class, repo -> {
 | 
			
		||||
            List<Account> accounts = repo.findAll(PageRequest.unpaged()).items();
 | 
			
		||||
            Map<Currency, BigDecimal> totals = new HashMap<>();
 | 
			
		||||
            for (var account : accounts) {
 | 
			
		||||
                BigDecimal currencyTotal = totals.computeIfAbsent(account.getCurrency(), c -> BigDecimal.ZERO);
 | 
			
		||||
                BigDecimal accountBalance = accountRepo.deriveCurrentBalance(account.id);
 | 
			
		||||
                BigDecimal accountBalance = repo.deriveCurrentBalance(account.id);
 | 
			
		||||
                if (account.getType() == AccountType.CREDIT_CARD) accountBalance = accountBalance.negate();
 | 
			
		||||
                totals.put(account.getCurrency(), currencyTotal.add(accountBalance));
 | 
			
		||||
            }
 | 
			
		||||
            return totals;
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
            List<MoneyValue> values = new ArrayList<>(totals.size());
 | 
			
		||||
            for (var entry : totals.entrySet()) {
 | 
			
		||||
                values.add(new MoneyValue(entry.getValue(), entry.getKey()));
 | 
			
		||||
            }
 | 
			
		||||
            values.sort((m1, m2) -> m2.amount().compareTo(m1.amount()));
 | 
			
		||||
            return values;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -216,8 +216,8 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
 | 
			
		|||
            if (account == null) return;
 | 
			
		||||
            List<String> updateMessages = new ArrayList<>();
 | 
			
		||||
            if (account.getType() != type) {
 | 
			
		||||
                DbUtil.updateOne(conn, "UPDATE account SET account_type = ? WHERE id = ?", type, accountId);
 | 
			
		||||
                updateMessages.add(String.format("Updated account type from %s to %s.", account.getType().toString(), type.toString()));
 | 
			
		||||
                DbUtil.updateOne(conn, "UPDATE account SET account_type = ? WHERE id = ?", type.name(), accountId);
 | 
			
		||||
                updateMessages.add(String.format("Updated account type from %s to %s.", account.getType(), type));
 | 
			
		||||
            }
 | 
			
		||||
            if (!account.getAccountNumber().equals(accountNumber)) {
 | 
			
		||||
                DbUtil.updateOne(conn, "UPDATE account SET account_number = ? WHERE id = ?", accountNumber, accountId);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import com.andrewlalis.perfin.model.MoneyValue;
 | 
			
		|||
import java.math.BigDecimal;
 | 
			
		||||
import java.math.RoundingMode;
 | 
			
		||||
import java.text.NumberFormat;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
 | 
			
		||||
public class CurrencyUtil {
 | 
			
		||||
| 
						 | 
				
			
			@ -26,4 +27,14 @@ public class CurrencyUtil {
 | 
			
		|||
        BigDecimal displayValue = money.amount().setScale(money.currency().getDefaultFractionDigits(), RoundingMode.HALF_UP);
 | 
			
		||||
        return displayValue.toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String formatMoneyValues(List<MoneyValue> values) {
 | 
			
		||||
        StringBuilder sb = new StringBuilder();
 | 
			
		||||
        final int len = values.size();
 | 
			
		||||
        for (int i = 0; i < len; i++) {
 | 
			
		||||
            sb.append(formatMoneyWithCurrencyPrefix(values.get(i)));
 | 
			
		||||
            if (i < len - 1) sb.append(", ");
 | 
			
		||||
        }
 | 
			
		||||
        return sb.toString();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,8 @@ package com.andrewlalis.perfin.model;
 | 
			
		|||
public enum AccountType {
 | 
			
		||||
    CHECKING("Checking", true),
 | 
			
		||||
    SAVINGS("Savings", true),
 | 
			
		||||
    CREDIT_CARD("Credit Card", false);
 | 
			
		||||
    CREDIT_CARD("Credit Card", false),
 | 
			
		||||
    BROKERAGE("Brokerage", true);
 | 
			
		||||
 | 
			
		||||
    private final String name;
 | 
			
		||||
    private final boolean debitsPositive;
 | 
			
		||||
| 
						 | 
				
			
			@ -24,14 +25,4 @@ public enum AccountType {
 | 
			
		|||
    public String toString() {
 | 
			
		||||
        return name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static AccountType parse(String s) {
 | 
			
		||||
        s = s.strip().toUpperCase();
 | 
			
		||||
        return switch (s) {
 | 
			
		||||
            case "CHECKING" -> CHECKING;
 | 
			
		||||
            case "SAVINGS" -> SAVINGS;
 | 
			
		||||
            case "CREDIT CARD", "CREDITCARD" -> CREDIT_CARD;
 | 
			
		||||
            default -> throw new IllegalArgumentException("Invalid AccountType string: " + s);
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,7 +28,8 @@ public class AccountTile extends BorderPane {
 | 
			
		|||
    public static final Map<AccountType, String> ACCOUNT_TYPE_COLORS = Map.of(
 | 
			
		||||
            AccountType.CHECKING, "-fx-theme-account-type-checking",
 | 
			
		||||
            AccountType.SAVINGS, "-fx-theme-account-type-savings",
 | 
			
		||||
            AccountType.CREDIT_CARD, "-fx-theme-account-type-credit-card"
 | 
			
		||||
            AccountType.CREDIT_CARD, "-fx-theme-account-type-credit-card",
 | 
			
		||||
            AccountType.BROKERAGE, "-fx-theme-account-type-brokerage"
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    public AccountTile(Account account) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,10 +11,7 @@ import javafx.scene.Node;
 | 
			
		|||
import javafx.scene.control.Button;
 | 
			
		||||
import javafx.scene.control.Label;
 | 
			
		||||
import javafx.scene.control.ScrollPane;
 | 
			
		||||
import javafx.scene.layout.BorderPane;
 | 
			
		||||
import javafx.scene.layout.Pane;
 | 
			
		||||
import javafx.scene.layout.Priority;
 | 
			
		||||
import javafx.scene.layout.VBox;
 | 
			
		||||
import javafx.scene.layout.*;
 | 
			
		||||
 | 
			
		||||
import java.math.BigDecimal;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +22,7 @@ import static com.andrewlalis.perfin.PerfinApp.router;
 | 
			
		|||
 */
 | 
			
		||||
public class AccountsModule extends DashboardModule {
 | 
			
		||||
    private final VBox accountsVBox = new VBox();
 | 
			
		||||
    private final Label totalAssetsLabel = new Label();
 | 
			
		||||
 | 
			
		||||
    public AccountsModule(Pane parent) {
 | 
			
		||||
        super(parent);
 | 
			
		||||
| 
						 | 
				
			
			@ -48,6 +46,16 @@ public class AccountsModule extends DashboardModule {
 | 
			
		|||
                refreshButton
 | 
			
		||||
        ));
 | 
			
		||||
        this.getChildren().add(scrollPane);
 | 
			
		||||
 | 
			
		||||
        HBox footer = new HBox();
 | 
			
		||||
        footer.getStyleClass().addAll("std-padding", "std-spacing");
 | 
			
		||||
 | 
			
		||||
        totalAssetsLabel.getStyleClass().addAll("mono-font");
 | 
			
		||||
        HBox totalAssetsBox = new HBox(new Label("Total Tracked Assets: "), totalAssetsLabel);
 | 
			
		||||
        totalAssetsBox.getStyleClass().addAll("std-spacing");
 | 
			
		||||
        footer.getChildren().add(totalAssetsBox);
 | 
			
		||||
 | 
			
		||||
        this.getChildren().add(footer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -61,6 +69,14 @@ public class AccountsModule extends DashboardModule {
 | 
			
		|||
                accountsVBox.getChildren().clear();
 | 
			
		||||
                accountsVBox.getChildren().addAll(nodes);
 | 
			
		||||
            }));
 | 
			
		||||
        totalAssetsLabel.setText("Computing...");
 | 
			
		||||
        totalAssetsLabel.setDisable(true);
 | 
			
		||||
        Profile.getCurrent().dataSource().getCombinedAccountBalances()
 | 
			
		||||
                .thenApply(CurrencyUtil::formatMoneyValues)
 | 
			
		||||
                .thenAccept(s -> Platform.runLater(() -> {
 | 
			
		||||
                    totalAssetsLabel.setText(s);
 | 
			
		||||
                    totalAssetsLabel.setDisable(false);
 | 
			
		||||
                }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Node buildMiniAccountTile(Account account) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@ rather than with your own CSS.
 | 
			
		|||
    -fx-theme-account-type-checking: rgb(3, 127, 252);
 | 
			
		||||
    -fx-theme-account-type-savings: rgb(57, 158, 74);
 | 
			
		||||
    -fx-theme-account-type-credit-card: rgb(207, 8, 68);
 | 
			
		||||
    -fx-theme-account-type-brokerage: rgb(130, 17, 242);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.root {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue