From c648c899cd020f8e710d5b65bd3bebe7ccbba86c Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Thu, 28 Dec 2023 11:55:10 -0500 Subject: [PATCH] Added better validation for creating transactions. --- .../com/andrewlalis/perfin/PerfinApp.java | 1 - .../control/CreateTransactionController.java | 72 +++++++++++++++---- src/main/resources/create-transaction.fxml | 5 +- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/andrewlalis/perfin/PerfinApp.java b/src/main/java/com/andrewlalis/perfin/PerfinApp.java index d4a8135..e2ab789 100644 --- a/src/main/java/com/andrewlalis/perfin/PerfinApp.java +++ b/src/main/java/com/andrewlalis/perfin/PerfinApp.java @@ -26,7 +26,6 @@ public class PerfinApp extends Application { @Override public void start(Stage stage) { - // TODO: Cleanup the splash screen logic! SplashScreenStage splashStage = new SplashScreenStage("Loading", SceneUtil.load("/startup-splash-screen.fxml")); splashStage.show(); defineRoutes(); diff --git a/src/main/java/com/andrewlalis/perfin/control/CreateTransactionController.java b/src/main/java/com/andrewlalis/perfin/control/CreateTransactionController.java index 27ab5a7..3ca4b07 100644 --- a/src/main/java/com/andrewlalis/perfin/control/CreateTransactionController.java +++ b/src/main/java/com/andrewlalis/perfin/control/CreateTransactionController.java @@ -16,6 +16,7 @@ import java.time.DateTimeException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.stream.Collectors; import static com.andrewlalis.perfin.PerfinApp.router; @@ -27,6 +28,7 @@ public class CreateTransactionController implements RouteSelectionListener { @FXML public TextField amountField; @FXML public ChoiceBox currencyChoiceBox; @FXML public TextArea descriptionField; + @FXML public Label descriptionErrorLabel; @FXML public ComboBox linkDebitAccountComboBox; @FXML public ComboBox linkCreditAccountComboBox; @@ -41,6 +43,15 @@ public class CreateTransactionController implements RouteSelectionListener { timestampInvalidLabel.setVisible(parsedTimestamp == null); timestampFutureLabel.setVisible(parsedTimestamp != null && parsedTimestamp.isAfter(LocalDateTime.now())); }); + descriptionErrorLabel.managedProperty().bind(descriptionErrorLabel.visibleProperty()); + descriptionErrorLabel.visibleProperty().bind(descriptionErrorLabel.textProperty().isNotEmpty()); + descriptionField.textProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null && newValue.length() > 255) { + descriptionErrorLabel.setText("Description is too long."); + } else { + descriptionErrorLabel.setText(null); + } + }); linkedAccountsErrorLabel.managedProperty().bind(linkedAccountsErrorLabel.visibleProperty()); linkedAccountsErrorLabel.visibleProperty().bind(linkedAccountsErrorLabel.textProperty().isNotEmpty()); linkDebitAccountComboBox.valueProperty().addListener((observable, oldValue, newValue) -> onLinkedAccountsUpdated()); @@ -59,18 +70,28 @@ public class CreateTransactionController implements RouteSelectionListener { } @FXML public void save() { - // TODO: Validate data! - - LocalDateTime timestamp = parseTimestamp(); - BigDecimal amount = new BigDecimal(amountField.getText()); - Currency currency = currencyChoiceBox.getValue(); - String description = descriptionField.getText().strip(); - Map affectedAccounts = getSelectedAccounts(); - Transaction transaction = new Transaction(timestamp, amount, currency, description); - Profile.getCurrent().getDataSource().useTransactionRepository(repo -> { - repo.insert(transaction, affectedAccounts); - }); - router.navigateBackAndClear(); + var validationMessages = validateFormData(); + if (!validationMessages.isEmpty()) { + Alert alert = new Alert( + Alert.AlertType.WARNING, + "There are some issues with your data:\n\n" + + validationMessages.stream() + .map(s -> "- " + s) + .collect(Collectors.joining("\n\n")) + ); + alert.show(); + } else { + LocalDateTime timestamp = parseTimestamp(); + BigDecimal amount = new BigDecimal(amountField.getText()); + Currency currency = currencyChoiceBox.getValue(); + String description = descriptionField.getText() == null ? null : descriptionField.getText().strip(); + Map affectedAccounts = getSelectedAccounts(); + Transaction transaction = new Transaction(timestamp, amount, currency, description); + Profile.getCurrent().getDataSource().useTransactionRepository(repo -> { + repo.insert(transaction, affectedAccounts); + }); + router.navigateBackAndClear(); + } } @FXML public void cancel() { @@ -85,6 +106,7 @@ public class CreateTransactionController implements RouteSelectionListener { private void resetForm() { timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT)); amountField.setText("0"); + descriptionField.setText(null); Thread.ofVirtual().start(() -> { Profile.getCurrent().getDataSource().useAccountRepository(repo -> { var currencies = repo.findAllUsedCurrencies().stream() @@ -92,7 +114,6 @@ public class CreateTransactionController implements RouteSelectionListener { .toList(); Platform.runLater(() -> { currencyChoiceBox.getItems().setAll(currencies); - // TODO: cache most-recent currency for the app (maybe for different contexts). currencyChoiceBox.getSelectionModel().selectFirst(); }); }); @@ -159,4 +180,29 @@ public class CreateTransactionController implements RouteSelectionListener { linkedAccountsErrorLabel.setText(null); } } + + private List validateFormData() { + List errorMessages = new ArrayList<>(); + if (parseTimestamp() == null) errorMessages.add("Invalid or missing timestamp."); + if (descriptionField.getText() != null && descriptionField.getText().strip().length() > 255) { + errorMessages.add("Description is too long."); + } + try { + BigDecimal value = new BigDecimal(amountField.getText()); + if (value.compareTo(BigDecimal.ZERO) <= 0) { + errorMessages.add("Amount should be a positive number."); + } + } catch (NumberFormatException e) { + errorMessages.add("Invalid or missing amount."); + } + Account debitAccount = linkDebitAccountComboBox.getValue(); + Account creditAccount = linkCreditAccountComboBox.getValue(); + if (debitAccount == null && creditAccount == null) { + errorMessages.add("At least one account must be linked to this transaction."); + } + if (debitAccount != null && creditAccount != null && debitAccount.getId() == creditAccount.getId()) { + errorMessages.add("Credit and debit accounts cannot be the same."); + } + return errorMessages; + } } diff --git a/src/main/resources/create-transaction.fxml b/src/main/resources/create-transaction.fxml index 5eac923..7d2a9d9 100644 --- a/src/main/resources/create-transaction.fxml +++ b/src/main/resources/create-transaction.fxml @@ -26,8 +26,9 @@