diff --git a/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java b/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java
index 0361415..01bddc1 100644
--- a/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java
+++ b/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java
@@ -15,9 +15,11 @@ import com.andrewlalis.perfin.view.component.validation.validators.CurrencyAmoun
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
+import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeParseException;
@@ -27,6 +29,7 @@ import static com.andrewlalis.perfin.PerfinApp.router;
public class CreateBalanceRecordController implements RouteSelectionListener {
@FXML public TextField timestampField;
@FXML public TextField balanceField;
+ @FXML public Label balanceWarningLabel;
private FileSelectionArea attachmentSelectionArea;
@FXML public PropertiesPane propertiesPane;
@@ -44,9 +47,25 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
}
}).validatedInitially().attachToTextField(timestampField);
- var balanceValid = new ValidationApplier<>(
- new CurrencyAmountValidator(() -> account == null ? null : account.getCurrency(), true, false)
- ).validatedInitially().attachToTextField(balanceField);
+ var balanceValidator = new CurrencyAmountValidator(() -> account == null ? null : account.getCurrency(), true, false);
+ var balanceValid = new ValidationApplier<>(balanceValidator)
+ .validatedInitially().attachToTextField(balanceField);
+
+ balanceWarningLabel.managedProperty().bind(balanceWarningLabel.visibleProperty());
+ balanceWarningLabel.visibleProperty().set(false);
+ balanceField.textProperty().addListener((observable, oldValue, newValue) -> {
+ if (!balanceValidator.validate(newValue).isValid()) {
+ balanceWarningLabel.visibleProperty().set(false);
+ return;
+ }
+ BigDecimal reportedBalance = new BigDecimal(newValue);
+ Thread.ofVirtual().start(() -> Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
+ BigDecimal derivedBalance = repo.deriveCurrentBalance(account.id);
+ Platform.runLater(() -> balanceWarningLabel.visibleProperty().set(
+ !reportedBalance.setScale(derivedBalance.scale(), RoundingMode.HALF_UP).equals(derivedBalance)
+ ));
+ }));
+ });
var formValid = timestampValid.and(balanceValid);
saveButton.disableProperty().bind(formValid.not());
@@ -64,30 +83,25 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
public void onRouteSelected(Object context) {
this.account = (Account) context;
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
- Thread.ofVirtual().start(() -> {
- Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
- BigDecimal value = repo.deriveCurrentBalance(account.id);
- Platform.runLater(() -> balanceField.setText(
- CurrencyUtil.formatMoneyAsBasicNumber(new MoneyValue(value, account.getCurrency()))
- ));
- });
- });
+ Thread.ofVirtual().start(() -> Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
+ BigDecimal value = repo.deriveCurrentBalance(account.id);
+ Platform.runLater(() -> balanceField.setText(
+ CurrencyUtil.formatMoneyAsBasicNumber(new MoneyValue(value, account.getCurrency()))
+ ));
+ }));
attachmentSelectionArea.clear();
}
@FXML public void save() {
LocalDateTime localTimestamp = LocalDateTime.parse(timestampField.getText(), DateUtil.DEFAULT_DATETIME_FORMAT);
BigDecimal reportedBalance = new BigDecimal(balanceField.getText());
+
boolean confirm = Popups.confirm("Are you sure that you want to record the balance of account\n%s\nas %s,\nas of %s?".formatted(
account.getShortName(),
CurrencyUtil.formatMoneyWithCurrencyPrefix(new MoneyValue(reportedBalance, account.getCurrency())),
localTimestamp.atZone(ZoneId.systemDefault()).format(DateUtil.DEFAULT_DATETIME_FORMAT_WITH_ZONE)
));
- if (confirm) {
- Profile.getCurrent().getDataSource().useAccountRepository(accountRepo -> {
- BigDecimal currentDerivedBalance = accountRepo.deriveCurrentBalance(account.id);
-
- });
+ if (confirm && confirmIfInconsistentBalance(reportedBalance)) {
Profile.getCurrent().getDataSource().useBalanceRecordRepository(repo -> {
repo.insert(
DateUtil.localToUTC(localTimestamp),
@@ -104,4 +118,21 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
@FXML public void cancel() {
router.navigateBackAndClear();
}
+
+ private boolean confirmIfInconsistentBalance(BigDecimal reportedBalance) {
+ BigDecimal currentDerivedBalance;
+ try (var accountRepo = Profile.getCurrent().getDataSource().getAccountRepository()) {
+ currentDerivedBalance = accountRepo.deriveCurrentBalance(account.id);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ 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(
+ CurrencyUtil.formatMoney(new MoneyValue(reportedBalance, account.getCurrency())),
+ CurrencyUtil.formatMoney(new MoneyValue(currentDerivedBalance, account.getCurrency()))
+ );
+ return Popups.confirm(msg);
+ }
+ return true;
+ }
}
diff --git a/src/main/java/com/andrewlalis/perfin/data/AccountEntryRepository.java b/src/main/java/com/andrewlalis/perfin/data/AccountEntryRepository.java
index 794885f..4e1d4c0 100644
--- a/src/main/java/com/andrewlalis/perfin/data/AccountEntryRepository.java
+++ b/src/main/java/com/andrewlalis/perfin/data/AccountEntryRepository.java
@@ -1,6 +1,5 @@
package com.andrewlalis.perfin.data;
-import com.andrewlalis.perfin.data.pagination.Sort;
import com.andrewlalis.perfin.model.AccountEntry;
import java.math.BigDecimal;
diff --git a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountEntryRepository.java b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountEntryRepository.java
index 44cc93f..18fdd6f 100644
--- a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountEntryRepository.java
+++ b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountEntryRepository.java
@@ -2,7 +2,6 @@ package com.andrewlalis.perfin.data.impl;
import com.andrewlalis.perfin.data.AccountEntryRepository;
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
-import com.andrewlalis.perfin.data.pagination.Sort;
import com.andrewlalis.perfin.data.util.DbUtil;
import com.andrewlalis.perfin.model.AccountEntry;
diff --git a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcDataSource.java b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcDataSource.java
index 08f46c3..5296a2a 100644
--- a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcDataSource.java
+++ b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcDataSource.java
@@ -36,7 +36,7 @@ public class JdbcDataSource implements DataSource {
@Override
public AccountRepository getAccountRepository() {
- return new JdbcAccountRepository(getConnection());
+ return new JdbcAccountRepository(getConnection(), contentDir);
}
@Override
diff --git a/src/main/resources/create-balance-record.fxml b/src/main/resources/create-balance-record.fxml
index 53cf526..8bc2f29 100644
--- a/src/main/resources/create-balance-record.fxml
+++ b/src/main/resources/create-balance-record.fxml
@@ -39,7 +39,16 @@
-
+
+
+
+
+
diff --git a/src/main/resources/style/base.css b/src/main/resources/style/base.css
index 23a62d3..c8abf58 100644
--- a/src/main/resources/style/base.css
+++ b/src/main/resources/style/base.css
@@ -12,6 +12,7 @@ rather than with your own CSS.
-fx-theme-background-3: rgb(220, 220, 220);
-fx-theme-negative: rgb(247, 37, 69);
-fx-theme-positive: rgb(43, 196, 77);
+ -fx-theme-warning: rgb(250, 177, 2);
}
.root {
@@ -127,6 +128,10 @@ rather than with your own CSS.
-fx-fill: -fx-theme-positive;
}
+.warning-color-text-fill {
+ -fx-text-fill: -fx-theme-warning;
+}
+
/* DEBUG BORDERS */
.debug-border-1 {
-fx-border-color: red;