Added updates to use and show asset value of brokerage accounts.

This commit is contained in:
Andrew Lalis 2024-07-10 17:05:02 -04:00
parent ec6bc83353
commit f23d2c85a9
12 changed files with 189 additions and 59 deletions

View File

@ -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

View File

@ -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());

View File

@ -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));
Profile.getCurrent().dataSource().useRepoAsync(AccountRepository.class, repo -> {
BigDecimal value = repo.deriveCurrentBalance(account.id);
Platform.runLater(() -> balanceField.setText(
CurrencyUtil.formatMoneyAsBasicNumber(new MoneyValue(value, account.getCurrency()))
));
});
balanceField.setText(null);
if (ctx.type() == BalanceRecordType.CASH) {
Profile.getCurrent().dataSource().useRepoAsync(AccountRepository.class, repo -> {
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(

View File

@ -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);
}

View File

@ -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()) {

View File

@ -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

View File

@ -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 -> {

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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,34 +16,40 @@
</top>
<center>
<VBox>
<!-- Main account properties and actions -->
<FlowPane styleClass="std-padding,std-spacing">
<!-- Main account properties. -->
<PropertiesPane vgap="5" hgap="5">
<Label text="Name" styleClass="bold-text"/>
<Label fx:id="accountNameLabel"/>
<!-- Main account properties. -->
<PropertiesPane vgap="5" hgap="5" styleClass="std-padding,std-spacing">
<Label text="Name" styleClass="bold-text"/>
<Label fx:id="accountNameLabel"/>
<Label text="Number" styleClass="bold-text"/>
<Label fx:id="accountNumberLabel" styleClass="mono-font"/>
<Label text="Number" styleClass="bold-text"/>
<Label fx:id="accountNumberLabel" styleClass="mono-font"/>
<Label text="Currency" styleClass="bold-text"/>
<Label fx:id="accountCurrencyLabel"/>
<Label text="Currency" styleClass="bold-text"/>
<Label fx:id="accountCurrencyLabel"/>
<Label text="Created At" styleClass="bold-text"/>
<Label fx:id="accountCreatedAtLabel" styleClass="mono-font"/>
<Label text="Created At" styleClass="bold-text"/>
<Label fx:id="accountCreatedAtLabel" styleClass="mono-font"/>
<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 text="Current Balance" styleClass="bold-text"/>
<VBox>
<Label fx:id="accountBalanceLabel" styleClass="mono-font"/>
</PropertiesPane>
</FlowPane>
<Label
styleClass="small-font,secondary-color-fill"
>Derived using nearest recorded balance and transactions.</Label>
</VBox>
</PropertiesPane>
<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"/>

View File

@ -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"/>