Added updates to use and show asset value of brokerage accounts.
This commit is contained in:
parent
ec6bc83353
commit
f23d2c85a9
|
@ -4,9 +4,7 @@ import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
|||
import com.andrewlalis.perfin.data.AccountRepository;
|
||||
import com.andrewlalis.perfin.data.util.CurrencyUtil;
|
||||
import com.andrewlalis.perfin.data.util.DateUtil;
|
||||
import com.andrewlalis.perfin.model.Account;
|
||||
import com.andrewlalis.perfin.model.MoneyValue;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
import com.andrewlalis.perfin.model.*;
|
||||
import com.andrewlalis.perfin.view.BindingUtil;
|
||||
import com.andrewlalis.perfin.view.component.AccountHistoryView;
|
||||
import com.andrewlalis.perfin.view.component.PropertiesPane;
|
||||
|
@ -14,7 +12,10 @@ import com.andrewlalis.perfin.view.component.validation.ValidationApplier;
|
|||
import com.andrewlalis.perfin.view.component.validation.validators.PredicateValidator;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
|
@ -23,7 +24,10 @@ import javafx.scene.control.Label;
|
|||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
import java.time.*;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||
|
||||
|
@ -31,6 +35,7 @@ public class AccountViewController implements RouteSelectionListener {
|
|||
private final ObjectProperty<Account> accountProperty = new SimpleObjectProperty<>(null);
|
||||
private final ObservableValue<Boolean> accountArchived = accountProperty.map(a -> a != null && a.isArchived());
|
||||
private final StringProperty balanceTextProperty = new SimpleStringProperty(null);
|
||||
private final StringProperty assetValueTextProperty = new SimpleStringProperty(null);
|
||||
|
||||
@FXML public Label titleLabel;
|
||||
@FXML public Label accountNameLabel;
|
||||
|
@ -38,6 +43,8 @@ public class AccountViewController implements RouteSelectionListener {
|
|||
@FXML public Label accountCurrencyLabel;
|
||||
@FXML public Label accountCreatedAtLabel;
|
||||
@FXML public Label accountBalanceLabel;
|
||||
@FXML public PropertiesPane assetValuePane;
|
||||
@FXML public Label latestAssetsValueLabel;
|
||||
@FXML public PropertiesPane descriptionPane;
|
||||
@FXML public Text accountDescriptionText;
|
||||
|
||||
|
@ -58,14 +65,23 @@ public class AccountViewController implements RouteSelectionListener {
|
|||
var hasDescription = accountProperty.map(a -> a.getDescription() != null);
|
||||
BindingUtil.bindManagedAndVisible(descriptionPane, hasDescription);
|
||||
accountBalanceLabel.textProperty().bind(balanceTextProperty);
|
||||
var isBrokerageAccount = accountProperty.map(a -> a.getType() == AccountType.BROKERAGE);
|
||||
BindingUtil.bindManagedAndVisible(assetValuePane, isBrokerageAccount);
|
||||
latestAssetsValueLabel.textProperty().bind(assetValueTextProperty);
|
||||
|
||||
actionsBox.getChildren().forEach(node -> {
|
||||
Button button = (Button) node;
|
||||
ObservableValue<Boolean> buttonActive = accountArchived;
|
||||
ObservableValue<Boolean> buttonDisabled = accountArchived;
|
||||
if (button.getText().equalsIgnoreCase("Unarchive")) {
|
||||
buttonActive = BooleanExpression.booleanExpression(buttonActive).not();
|
||||
buttonDisabled = BooleanExpression.booleanExpression(buttonDisabled).not();
|
||||
}
|
||||
button.disableProperty().bind(buttonActive);
|
||||
if (button.getText().equalsIgnoreCase("Record Asset Value")) {
|
||||
buttonDisabled = BooleanExpression.booleanExpression(
|
||||
accountProperty.map(Account::getType)
|
||||
.map(t -> !t.equals(AccountType.BROKERAGE))
|
||||
).or(BooleanExpression.booleanExpression(accountArchived));
|
||||
}
|
||||
button.disableProperty().bind(buttonDisabled);
|
||||
button.managedProperty().bind(button.visibleProperty());
|
||||
button.visibleProperty().bind(button.disableProperty().not());
|
||||
});
|
||||
|
@ -81,7 +97,7 @@ public class AccountViewController implements RouteSelectionListener {
|
|||
.toInstant();
|
||||
Profile.getCurrent().dataSource().mapRepoAsync(
|
||||
AccountRepository.class,
|
||||
repo -> repo.deriveBalance(getAccount().id, timestamp)
|
||||
repo -> repo.deriveCashBalance(getAccount().id, timestamp)
|
||||
).thenAccept(balance -> Platform.runLater(() -> {
|
||||
String msg = String.format(
|
||||
"Your balance as of %s is %s, according to Perfin's data.",
|
||||
|
@ -97,12 +113,20 @@ public class AccountViewController implements RouteSelectionListener {
|
|||
public void onRouteSelected(Object context) {
|
||||
accountHistory.clear();
|
||||
balanceTextProperty.set(null);
|
||||
assetValueTextProperty.set(null);
|
||||
if (context instanceof Account account) {
|
||||
this.accountProperty.set(account);
|
||||
accountHistory.setAccountId(account.id);
|
||||
accountHistory.loadMoreHistory();
|
||||
Profile.getCurrent().dataSource().getAccountBalanceText(account)
|
||||
.thenAccept(s -> Platform.runLater(() -> balanceTextProperty.set(s)));
|
||||
if (account.getType() == AccountType.BROKERAGE) {
|
||||
Profile.getCurrent().dataSource().mapRepoAsync(
|
||||
AccountRepository.class,
|
||||
repo -> repo.getNearestAssetValue(account.id)
|
||||
).thenApply(value -> CurrencyUtil.formatMoney(new MoneyValue(value, account.getCurrency())))
|
||||
.thenAccept(text -> Platform.runLater(() -> assetValueTextProperty.set(text)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,7 +136,11 @@ public class AccountViewController implements RouteSelectionListener {
|
|||
}
|
||||
|
||||
@FXML public void goToCreateBalanceRecord() {
|
||||
router.navigate("create-balance-record", getAccount());
|
||||
router.navigate("create-balance-record", new CreateBalanceRecordController.RouteContext(getAccount(), BalanceRecordType.CASH));
|
||||
}
|
||||
|
||||
@FXML public void goToCreateAssetRecord() {
|
||||
router.navigate("create-balance-record", new CreateBalanceRecordController.RouteContext(getAccount(), BalanceRecordType.ASSETS));
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
|
|
@ -24,6 +24,7 @@ public class BalanceRecordViewController implements RouteSelectionListener {
|
|||
|
||||
@FXML public Label titleLabel;
|
||||
|
||||
@FXML public Label typeLabel;
|
||||
@FXML public Label timestampLabel;
|
||||
@FXML public Label balanceLabel;
|
||||
@FXML public Label currencyLabel;
|
||||
|
@ -38,6 +39,7 @@ public class BalanceRecordViewController implements RouteSelectionListener {
|
|||
this.balanceRecord = (BalanceRecord) context;
|
||||
if (balanceRecord == null) return;
|
||||
titleLabel.setText("Balance Record #" + balanceRecord.id);
|
||||
typeLabel.setText(balanceRecord.getType().toString());
|
||||
timestampLabel.setText(DateUtil.formatUTCAsLocalWithZone(balanceRecord.getTimestamp()));
|
||||
balanceLabel.setText(CurrencyUtil.formatMoney(balanceRecord.getMoneyAmount()));
|
||||
currencyLabel.setText(balanceRecord.getCurrency().getDisplayName());
|
||||
|
|
|
@ -35,6 +35,8 @@ import static com.andrewlalis.perfin.PerfinApp.router;
|
|||
* account.
|
||||
*/
|
||||
public class CreateBalanceRecordController implements RouteSelectionListener {
|
||||
public record RouteContext (Account account, BalanceRecordType type) {}
|
||||
|
||||
@FXML public TextField timestampField;
|
||||
@FXML public TextField balanceField;
|
||||
@FXML public Label balanceWarningLabel;
|
||||
|
@ -44,6 +46,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
|
|||
@FXML public Button saveButton;
|
||||
|
||||
private Account account;
|
||||
private BalanceRecordType type = BalanceRecordType.CASH;
|
||||
|
||||
@FXML public void initialize() {
|
||||
var timestampValid = new ValidationApplier<>((ValidationFunction<String>) input -> {
|
||||
|
@ -62,7 +65,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
|
|||
balanceWarningLabel.managedProperty().bind(balanceWarningLabel.visibleProperty());
|
||||
balanceWarningLabel.visibleProperty().set(false);
|
||||
balanceField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (!balanceValidator.validate(newValue).isValid() || !timestampValid.get()) {
|
||||
if (!balanceValidator.validate(newValue).isValid() || !timestampValid.get() || type != BalanceRecordType.CASH) {
|
||||
balanceWarningLabel.visibleProperty().set(false);
|
||||
return;
|
||||
}
|
||||
|
@ -70,7 +73,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
|
|||
LocalDateTime localTimestamp = LocalDateTime.parse(timestampField.getText(), DateUtil.DEFAULT_DATETIME_FORMAT);
|
||||
LocalDateTime utcTimestamp = DateUtil.localToUTC(localTimestamp);
|
||||
Profile.getCurrent().dataSource().useRepoAsync(AccountRepository.class, repo -> {
|
||||
BigDecimal derivedBalance = repo.deriveBalance(account.id, utcTimestamp.toInstant(ZoneOffset.UTC));
|
||||
BigDecimal derivedBalance = repo.deriveCashBalance(account.id, utcTimestamp.toInstant(ZoneOffset.UTC));
|
||||
boolean balancesMatch = reportedBalance.setScale(derivedBalance.scale(), RoundingMode.HALF_UP).equals(derivedBalance);
|
||||
Platform.runLater(() -> balanceWarningLabel.visibleProperty().set(!balancesMatch));
|
||||
});
|
||||
|
@ -82,14 +85,19 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
|
|||
|
||||
@Override
|
||||
public void onRouteSelected(Object context) {
|
||||
this.account = (Account) context;
|
||||
RouteContext ctx = (RouteContext) context;
|
||||
this.account = ctx.account();
|
||||
this.type = ctx.type();
|
||||
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
|
||||
balanceField.setText(null);
|
||||
if (ctx.type() == BalanceRecordType.CASH) {
|
||||
Profile.getCurrent().dataSource().useRepoAsync(AccountRepository.class, repo -> {
|
||||
BigDecimal value = repo.deriveCurrentBalance(account.id);
|
||||
BigDecimal value = repo.deriveCurrentCashBalance(account.id);
|
||||
Platform.runLater(() -> balanceField.setText(
|
||||
CurrencyUtil.formatMoneyAsBasicNumber(new MoneyValue(value, account.getCurrency()))
|
||||
));
|
||||
});
|
||||
}
|
||||
attachmentSelectionArea.clear();
|
||||
}
|
||||
|
||||
|
@ -97,17 +105,26 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
|
|||
LocalDateTime localTimestamp = LocalDateTime.parse(timestampField.getText(), DateUtil.DEFAULT_DATETIME_FORMAT);
|
||||
BigDecimal reportedBalance = new BigDecimal(balanceField.getText());
|
||||
|
||||
boolean confirm = Popups.confirm(timestampField, "Are you sure that you want to record the balance of account\n%s\nas %s,\nas of %s?".formatted(
|
||||
String valueNoun = switch (type) {
|
||||
case CASH -> "cash balance";
|
||||
case ASSETS -> "asset value";
|
||||
};
|
||||
|
||||
boolean confirm = Popups.confirm(timestampField, "Are you sure that you want to record the %s of account\n%s\nas %s,\nas of %s?".formatted(
|
||||
valueNoun,
|
||||
account.getShortName(),
|
||||
CurrencyUtil.formatMoneyWithCurrencyPrefix(new MoneyValue(reportedBalance, account.getCurrency())),
|
||||
localTimestamp.atZone(ZoneId.systemDefault()).format(DateUtil.DEFAULT_DATETIME_FORMAT_WITH_ZONE)
|
||||
));
|
||||
if (confirm && confirmIfInconsistentBalance(reportedBalance, DateUtil.localToUTC(localTimestamp))) {
|
||||
if (
|
||||
confirm &&
|
||||
(type != BalanceRecordType.CASH || confirmIfInconsistentBalance(reportedBalance, DateUtil.localToUTC(localTimestamp)))
|
||||
) {
|
||||
Profile.getCurrent().dataSource().useRepo(BalanceRecordRepository.class, repo -> {
|
||||
repo.insert(
|
||||
DateUtil.localToUTC(localTimestamp),
|
||||
account.id,
|
||||
BalanceRecordType.CASH,
|
||||
type,
|
||||
reportedBalance,
|
||||
account.getCurrency(),
|
||||
attachmentSelectionArea.getSelectedPaths()
|
||||
|
@ -124,7 +141,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
|
|||
private boolean confirmIfInconsistentBalance(BigDecimal reportedBalance, LocalDateTime utcTimestamp) {
|
||||
BigDecimal currentDerivedBalance = Profile.getCurrent().dataSource().mapRepo(
|
||||
AccountRepository.class,
|
||||
repo -> repo.deriveBalance(account.id, utcTimestamp.toInstant(ZoneOffset.UTC))
|
||||
repo -> repo.deriveCashBalance(account.id, utcTimestamp.toInstant(ZoneOffset.UTC))
|
||||
);
|
||||
if (!reportedBalance.setScale(currentDerivedBalance.scale(), RoundingMode.HALF_UP).equals(currentDerivedBalance)) {
|
||||
String msg = "The balance you reported (%s) doesn't match the balance that Perfin derived from your account's transactions (%s). It's encouraged to go back and add any missing transactions first, but you may proceed now if you understand the consequences of an inconsistent account balance history.\n\nAre you absolutely sure you want to create this balance record?".formatted(
|
||||
|
|
|
@ -28,10 +28,15 @@ public interface AccountRepository extends Repository, AutoCloseable {
|
|||
void archive(long accountId);
|
||||
void unarchive(long accountId);
|
||||
|
||||
BigDecimal deriveBalance(long accountId, Instant timestamp);
|
||||
default BigDecimal deriveCurrentBalance(long accountId) {
|
||||
return deriveBalance(accountId, Instant.now(Clock.systemUTC()));
|
||||
BigDecimal deriveCashBalance(long accountId, Instant timestamp);
|
||||
default BigDecimal deriveCurrentCashBalance(long accountId) {
|
||||
return deriveCashBalance(accountId, Instant.now(Clock.systemUTC()));
|
||||
}
|
||||
BigDecimal getNearestAssetValue(long accountId, Instant timestamp);
|
||||
default BigDecimal getNearestAssetValue(long accountId) {
|
||||
return getNearestAssetValue(accountId, Instant.now(Clock.systemUTC()));
|
||||
}
|
||||
|
||||
Set<Currency> findAllUsedCurrencies();
|
||||
List<Timestamped> findEventsBefore(long accountId, LocalDateTime utcTimestamp, int maxResults);
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ public interface DataSource {
|
|||
default CompletableFuture<String> getAccountBalanceText(Account account) {
|
||||
CompletableFuture<String> cf = new CompletableFuture<>();
|
||||
mapRepoAsync(AccountRepository.class, repo -> {
|
||||
BigDecimal balance = repo.deriveCurrentBalance(account.id);
|
||||
BigDecimal balance = repo.deriveCurrentCashBalance(account.id);
|
||||
MoneyValue money = new MoneyValue(balance, account.getCurrency());
|
||||
return CurrencyUtil.formatMoney(money);
|
||||
}).thenAccept(s -> Platform.runLater(() -> cf.complete(s)));
|
||||
|
@ -123,9 +123,11 @@ public interface DataSource {
|
|||
Map<Currency, BigDecimal> totals = new HashMap<>();
|
||||
for (var account : accounts) {
|
||||
BigDecimal currencyTotal = totals.computeIfAbsent(account.getCurrency(), c -> BigDecimal.ZERO);
|
||||
BigDecimal accountBalance = repo.deriveBalance(account.id, timestamp);
|
||||
BigDecimal accountBalance = repo.deriveCashBalance(account.id, timestamp);
|
||||
BigDecimal accountAssetsValue = repo.getNearestAssetValue(account.id, timestamp);
|
||||
if (account.getType() == AccountType.CREDIT_CARD) accountBalance = accountBalance.negate();
|
||||
totals.put(account.getCurrency(), currencyTotal.add(accountBalance));
|
||||
BigDecimal accountTotal = accountBalance.add(accountAssetsValue);
|
||||
totals.put(account.getCurrency(), currencyTotal.add(accountTotal));
|
||||
}
|
||||
List<MoneyValue> values = new ArrayList<>(totals.size());
|
||||
for (var entry : totals.entrySet()) {
|
||||
|
|
|
@ -114,7 +114,7 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal deriveBalance(long accountId, Instant timestamp) {
|
||||
public BigDecimal deriveCashBalance(long accountId, Instant timestamp) {
|
||||
// First find the account itself, since its properties influence the balance.
|
||||
Account account = findById(accountId).orElse(null);
|
||||
if (account == null) throw new EntityNotFoundException(Account.class, accountId);
|
||||
|
@ -152,6 +152,15 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getNearestAssetValue(long accountId, Instant timestamp) {
|
||||
LocalDateTime utcTimestamp = timestamp.atZone(ZoneOffset.UTC).toLocalDateTime();
|
||||
BalanceRecordRepository balanceRecordRepo = new JdbcBalanceRecordRepository(conn, contentDir);
|
||||
Optional<BalanceRecord> mostRecentRecord = balanceRecordRepo.findClosestBefore(accountId, BalanceRecordType.ASSETS, utcTimestamp);
|
||||
if (mostRecentRecord.isEmpty()) return BigDecimal.ZERO;
|
||||
return mostRecentRecord.get().getBalance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Currency> findAllUsedCurrencies() {
|
||||
return new HashSet<>(DbUtil.findAll(
|
||||
|
@ -177,7 +186,7 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
|
|||
LEFT JOIN history_account ha ON history_item.history_id = ha.history_id
|
||||
UNION ALL
|
||||
SELECT id, timestamp, 'BALANCE_RECORD' AS type, account_id
|
||||
FROM balance_record WHERE type = 'CASH'
|
||||
FROM balance_record
|
||||
)
|
||||
WHERE account_id = ? AND timestamp < ?
|
||||
ORDER BY timestamp DESC
|
||||
|
|
|
@ -120,9 +120,13 @@ public class AccountHistoryView extends ScrollPane {
|
|||
case BalanceRecord br -> {
|
||||
Hyperlink brLink = new Hyperlink("Balance Record #" + br.id);
|
||||
brLink.setOnAction(event -> router.navigate("balance-record", br));
|
||||
String phrase = switch(br.getType()) {
|
||||
case CASH -> "a cash value";
|
||||
case ASSETS -> "an asset value";
|
||||
};
|
||||
return CompletableFuture.completedFuture(new AccountHistoryTile(br.getTimestamp(), new TextFlow(
|
||||
brLink,
|
||||
new Text("added with a value of %s.".formatted(CurrencyUtil.formatMoney(br.getMoneyAmount())))
|
||||
new Text("added with %s of %s.".formatted(phrase, CurrencyUtil.formatMoney(br.getMoneyAmount())))
|
||||
)));
|
||||
}
|
||||
default -> {
|
||||
|
|
|
@ -113,7 +113,7 @@ public class AccountSelectionBox extends ComboBox<Account> {
|
|||
nameLabel.setText(item.getName() + " (" + item.getAccountNumberSuffix() + ")");
|
||||
if (showBalanceProp.get()) {
|
||||
Profile.getCurrent().dataSource().useRepoAsync(AccountRepository.class, repo -> {
|
||||
BigDecimal balance = repo.deriveCurrentBalance(item.id);
|
||||
BigDecimal balance = repo.deriveCurrentCashBalance(item.id);
|
||||
Platform.runLater(() -> {
|
||||
balanceLabel.setText(CurrencyUtil.formatMoney(new MoneyValue(balance, item.getCurrency())));
|
||||
balanceLabel.setVisible(true);
|
||||
|
|
|
@ -17,6 +17,7 @@ import javafx.scene.layout.HBox;
|
|||
import javafx.scene.layout.Priority;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||
|
@ -83,7 +84,7 @@ public class AccountTile extends BorderPane {
|
|||
balanceLabel.getStyleClass().addAll("mono-font");
|
||||
balanceLabel.setDisable(true);
|
||||
Profile.getCurrent().dataSource().useRepoAsync(AccountRepository.class, repo -> {
|
||||
BigDecimal balance = repo.deriveCurrentBalance(account.id);
|
||||
BigDecimal balance = repo.deriveCurrentCashBalance(account.id);
|
||||
String text = CurrencyUtil.formatMoney(new MoneyValue(balance, account.getCurrency()));
|
||||
Platform.runLater(() -> {
|
||||
balanceLabel.setText(text);
|
||||
|
@ -104,6 +105,32 @@ public class AccountTile extends BorderPane {
|
|||
newPropertyLabel("Current Balance"),
|
||||
balanceLabel
|
||||
);
|
||||
|
||||
if (account.getType() == AccountType.BROKERAGE) {
|
||||
Label assetValueLabel = new Label("Computing assets value...");
|
||||
assetValueLabel.getStyleClass().addAll("mono-font");
|
||||
assetValueLabel.setDisable(true);
|
||||
|
||||
Profile.getCurrent().dataSource().useRepoAsync(AccountRepository.class, repo -> {
|
||||
BigDecimal assetValue = repo.getNearestAssetValue(account.id);
|
||||
String text = CurrencyUtil.formatMoney(new MoneyValue(assetValue, account.getCurrency()));
|
||||
Platform.runLater(() -> {
|
||||
assetValueLabel.setText(text);
|
||||
if (account.getType().areDebitsPositive() && assetValue.compareTo(BigDecimal.ZERO) < 0) {
|
||||
assetValueLabel.getStyleClass().add("negative-color-text-fill");
|
||||
} else if (!account.getType().areDebitsPositive() && assetValue.compareTo(BigDecimal.ZERO) < 0) {
|
||||
assetValueLabel.getStyleClass().add("positive-color-text-fill");
|
||||
}
|
||||
assetValueLabel.setDisable(false);
|
||||
});
|
||||
});
|
||||
|
||||
propertiesPane.getChildren().addAll(
|
||||
newPropertyLabel("Latest Assets Value"),
|
||||
assetValueLabel
|
||||
);
|
||||
}
|
||||
|
||||
return propertiesPane;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.andrewlalis.perfin.view.component.module;
|
|||
import com.andrewlalis.perfin.data.AccountRepository;
|
||||
import com.andrewlalis.perfin.data.util.CurrencyUtil;
|
||||
import com.andrewlalis.perfin.model.Account;
|
||||
import com.andrewlalis.perfin.model.AccountType;
|
||||
import com.andrewlalis.perfin.model.MoneyValue;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
import com.andrewlalis.perfin.view.component.AccountTile;
|
||||
|
@ -91,13 +92,17 @@ public class AccountsModule extends DashboardModule {
|
|||
Label typeLabel = new Label(account.getType().toString());
|
||||
typeLabel.getStyleClass().add("bold-text");
|
||||
typeLabel.setStyle("-fx-text-fill: " + AccountTile.ACCOUNT_TYPE_COLORS.get(account.getType()));
|
||||
|
||||
VBox rightSideVBox = new VBox();
|
||||
rightSideVBox.getStyleClass().addAll("std-spacing");
|
||||
Label balanceLabel = new Label("Computing balance...");
|
||||
balanceLabel.getStyleClass().addAll("mono-font");
|
||||
balanceLabel.setDisable(true);
|
||||
rightSideVBox.getChildren().add(balanceLabel);
|
||||
|
||||
Profile.getCurrent().dataSource().mapRepoAsync(
|
||||
AccountRepository.class,
|
||||
repo -> repo.deriveCurrentBalance(account.id)
|
||||
repo -> repo.deriveCurrentCashBalance(account.id)
|
||||
).thenAccept(bal -> Platform.runLater(() -> {
|
||||
String text = CurrencyUtil.formatMoneyWithCurrencyPrefix(new MoneyValue(bal, account.getCurrency()));
|
||||
balanceLabel.setText(text);
|
||||
|
@ -109,9 +114,29 @@ public class AccountsModule extends DashboardModule {
|
|||
balanceLabel.setDisable(false);
|
||||
}));
|
||||
|
||||
if (account.getType() == AccountType.BROKERAGE) {
|
||||
Label assetValueLabel = new Label("Computing assets value...");
|
||||
assetValueLabel.getStyleClass().addAll("mono-font");
|
||||
assetValueLabel.setDisable(true);
|
||||
rightSideVBox.getChildren().add(assetValueLabel);
|
||||
Profile.getCurrent().dataSource().mapRepoAsync(
|
||||
AccountRepository.class,
|
||||
repo -> repo.getNearestAssetValue(account.id)
|
||||
).thenAccept(value -> Platform.runLater(() -> {
|
||||
String text = CurrencyUtil.formatMoneyWithCurrencyPrefix(new MoneyValue(value, account.getCurrency()));
|
||||
assetValueLabel.setText(text + " in assets");
|
||||
if (account.getType().areDebitsPositive() && value.compareTo(BigDecimal.ZERO) < 0) {
|
||||
assetValueLabel.getStyleClass().add("negative-color-text-fill");
|
||||
} else if (!account.getType().areDebitsPositive() && value.compareTo(BigDecimal.ZERO) < 0) {
|
||||
assetValueLabel.getStyleClass().add("positive-color-text-fill");
|
||||
}
|
||||
assetValueLabel.setDisable(false);
|
||||
}));
|
||||
}
|
||||
|
||||
VBox contentBox = new VBox(nameLabel, numberLabel, typeLabel);
|
||||
borderPane.setCenter(contentBox);
|
||||
borderPane.setRight(balanceLabel);
|
||||
borderPane.setRight(rightSideVBox);
|
||||
return borderPane;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
<?import com.andrewlalis.perfin.view.component.PropertiesPane?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.text.TextFlow?>
|
||||
<BorderPane
|
||||
xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
|
@ -15,10 +16,8 @@
|
|||
</top>
|
||||
<center>
|
||||
<VBox>
|
||||
<!-- Main account properties and actions -->
|
||||
<FlowPane styleClass="std-padding,std-spacing">
|
||||
<!-- Main account properties. -->
|
||||
<PropertiesPane vgap="5" hgap="5">
|
||||
<PropertiesPane vgap="5" hgap="5" styleClass="std-padding,std-spacing">
|
||||
<Label text="Name" styleClass="bold-text"/>
|
||||
<Label fx:id="accountNameLabel"/>
|
||||
|
||||
|
@ -31,18 +30,26 @@
|
|||
<Label text="Created At" styleClass="bold-text"/>
|
||||
<Label fx:id="accountCreatedAtLabel" styleClass="mono-font"/>
|
||||
|
||||
<Label text="Current Balance" styleClass="bold-text"/>
|
||||
<VBox>
|
||||
<Label text="Current Balance" styleClass="bold-text" fx:id="balanceLabel"/>
|
||||
<Text
|
||||
styleClass="small-font,secondary-color-fill"
|
||||
wrappingWidth="${balanceLabel.width}"
|
||||
>Computed using the last recorded balance and all transactions since.</Text>
|
||||
</VBox>
|
||||
<Label fx:id="accountBalanceLabel" styleClass="mono-font"/>
|
||||
<Label
|
||||
styleClass="small-font,secondary-color-fill"
|
||||
>Derived using nearest recorded balance and transactions.</Label>
|
||||
</VBox>
|
||||
</PropertiesPane>
|
||||
</FlowPane>
|
||||
|
||||
<PropertiesPane vgap="5" hgap="5" fx:id="descriptionPane">
|
||||
<PropertiesPane vgap="5" hgap="5" fx:id="assetValuePane" styleClass="std-padding,std-spacing">
|
||||
<Label text="Latest Assets Value" styleClass="bold-text" labelFor="${latestAssetsValueLabel}"/>
|
||||
<VBox>
|
||||
<Label fx:id="latestAssetsValueLabel" styleClass="mono-font"/>
|
||||
<Label
|
||||
styleClass="small-font,secondary-color-fill"
|
||||
>Derived using nearest recorded asset value.</Label>
|
||||
</VBox>
|
||||
</PropertiesPane>
|
||||
|
||||
<PropertiesPane vgap="5" hgap="5" fx:id="descriptionPane" styleClass="std-padding,std-spacing">
|
||||
<Label text="Description" styleClass="bold-text" labelFor="${accountDescriptionText}"/>
|
||||
<TextFlow maxWidth="500"><Text fx:id="accountDescriptionText"/></TextFlow>
|
||||
</PropertiesPane>
|
||||
|
@ -51,6 +58,7 @@
|
|||
<HBox fx:id="actionsBox" styleClass="std-padding,std-spacing,small-font">
|
||||
<Button text="Edit" onAction="#goToEditPage"/>
|
||||
<Button text="Record Balance" onAction="#goToCreateBalanceRecord"/>
|
||||
<Button text="Record Asset Value" onAction="#goToCreateAssetRecord"/>
|
||||
<Button text="Archive" onAction="#archiveAccount"/>
|
||||
<Button text="Delete" onAction="#deleteAccount"/>
|
||||
<Button text="Unarchive" onAction="#unarchiveAccount"/>
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
<ColumnConstraints hgrow="ALWAYS" halignment="LEFT"/>
|
||||
</columnConstraints>
|
||||
|
||||
<Label text="Type" styleClass="bold-text"/>
|
||||
<Label fx:id="typeLabel" styleClass="mono-font"/>
|
||||
|
||||
<Label text="Timestamp" styleClass="bold-text"/>
|
||||
<Label fx:id="timestampLabel" styleClass="mono-font"/>
|
||||
|
||||
|
|
Loading…
Reference in New Issue