Added better validation for creating transactions.
This commit is contained in:
parent
69322620ca
commit
c648c899cd
|
@ -26,7 +26,6 @@ public class PerfinApp extends Application {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) {
|
public void start(Stage stage) {
|
||||||
// TODO: Cleanup the splash screen logic!
|
|
||||||
SplashScreenStage splashStage = new SplashScreenStage("Loading", SceneUtil.load("/startup-splash-screen.fxml"));
|
SplashScreenStage splashStage = new SplashScreenStage("Loading", SceneUtil.load("/startup-splash-screen.fxml"));
|
||||||
splashStage.show();
|
splashStage.show();
|
||||||
defineRoutes();
|
defineRoutes();
|
||||||
|
|
|
@ -16,6 +16,7 @@ import java.time.DateTimeException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ public class CreateTransactionController implements RouteSelectionListener {
|
||||||
@FXML public TextField amountField;
|
@FXML public TextField amountField;
|
||||||
@FXML public ChoiceBox<Currency> currencyChoiceBox;
|
@FXML public ChoiceBox<Currency> currencyChoiceBox;
|
||||||
@FXML public TextArea descriptionField;
|
@FXML public TextArea descriptionField;
|
||||||
|
@FXML public Label descriptionErrorLabel;
|
||||||
|
|
||||||
@FXML public ComboBox<Account> linkDebitAccountComboBox;
|
@FXML public ComboBox<Account> linkDebitAccountComboBox;
|
||||||
@FXML public ComboBox<Account> linkCreditAccountComboBox;
|
@FXML public ComboBox<Account> linkCreditAccountComboBox;
|
||||||
|
@ -41,6 +43,15 @@ public class CreateTransactionController implements RouteSelectionListener {
|
||||||
timestampInvalidLabel.setVisible(parsedTimestamp == null);
|
timestampInvalidLabel.setVisible(parsedTimestamp == null);
|
||||||
timestampFutureLabel.setVisible(parsedTimestamp != null && parsedTimestamp.isAfter(LocalDateTime.now()));
|
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.managedProperty().bind(linkedAccountsErrorLabel.visibleProperty());
|
||||||
linkedAccountsErrorLabel.visibleProperty().bind(linkedAccountsErrorLabel.textProperty().isNotEmpty());
|
linkedAccountsErrorLabel.visibleProperty().bind(linkedAccountsErrorLabel.textProperty().isNotEmpty());
|
||||||
linkDebitAccountComboBox.valueProperty().addListener((observable, oldValue, newValue) -> onLinkedAccountsUpdated());
|
linkDebitAccountComboBox.valueProperty().addListener((observable, oldValue, newValue) -> onLinkedAccountsUpdated());
|
||||||
|
@ -59,18 +70,28 @@ public class CreateTransactionController implements RouteSelectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML public void save() {
|
@FXML public void save() {
|
||||||
// TODO: Validate data!
|
var validationMessages = validateFormData();
|
||||||
|
if (!validationMessages.isEmpty()) {
|
||||||
LocalDateTime timestamp = parseTimestamp();
|
Alert alert = new Alert(
|
||||||
BigDecimal amount = new BigDecimal(amountField.getText());
|
Alert.AlertType.WARNING,
|
||||||
Currency currency = currencyChoiceBox.getValue();
|
"There are some issues with your data:\n\n" +
|
||||||
String description = descriptionField.getText().strip();
|
validationMessages.stream()
|
||||||
Map<Long, AccountEntry.Type> affectedAccounts = getSelectedAccounts();
|
.map(s -> "- " + s)
|
||||||
Transaction transaction = new Transaction(timestamp, amount, currency, description);
|
.collect(Collectors.joining("\n\n"))
|
||||||
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
|
);
|
||||||
repo.insert(transaction, affectedAccounts);
|
alert.show();
|
||||||
});
|
} else {
|
||||||
router.navigateBackAndClear();
|
LocalDateTime timestamp = parseTimestamp();
|
||||||
|
BigDecimal amount = new BigDecimal(amountField.getText());
|
||||||
|
Currency currency = currencyChoiceBox.getValue();
|
||||||
|
String description = descriptionField.getText() == null ? null : descriptionField.getText().strip();
|
||||||
|
Map<Long, AccountEntry.Type> 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() {
|
@FXML public void cancel() {
|
||||||
|
@ -85,6 +106,7 @@ public class CreateTransactionController implements RouteSelectionListener {
|
||||||
private void resetForm() {
|
private void resetForm() {
|
||||||
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
|
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
|
||||||
amountField.setText("0");
|
amountField.setText("0");
|
||||||
|
descriptionField.setText(null);
|
||||||
Thread.ofVirtual().start(() -> {
|
Thread.ofVirtual().start(() -> {
|
||||||
Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
|
Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
|
||||||
var currencies = repo.findAllUsedCurrencies().stream()
|
var currencies = repo.findAllUsedCurrencies().stream()
|
||||||
|
@ -92,7 +114,6 @@ public class CreateTransactionController implements RouteSelectionListener {
|
||||||
.toList();
|
.toList();
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
currencyChoiceBox.getItems().setAll(currencies);
|
currencyChoiceBox.getItems().setAll(currencies);
|
||||||
// TODO: cache most-recent currency for the app (maybe for different contexts).
|
|
||||||
currencyChoiceBox.getSelectionModel().selectFirst();
|
currencyChoiceBox.getSelectionModel().selectFirst();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -159,4 +180,29 @@ public class CreateTransactionController implements RouteSelectionListener {
|
||||||
linkedAccountsErrorLabel.setText(null);
|
linkedAccountsErrorLabel.setText(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<String> validateFormData() {
|
||||||
|
List<String> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,9 @@
|
||||||
</VBox>
|
</VBox>
|
||||||
<VBox>
|
<VBox>
|
||||||
<Label text="Description" labelFor="${descriptionField}" styleClass="bold-text"/>
|
<Label text="Description" labelFor="${descriptionField}" styleClass="bold-text"/>
|
||||||
<Label text="Maximum of 256 characters." styleClass="small-text"/>
|
<Label text="Maximum of 255 characters." styleClass="small-text"/>
|
||||||
<TextArea fx:id="descriptionField" styleClass="mono-font"/>
|
<TextArea fx:id="descriptionField" styleClass="mono-font" wrapText="true"/>
|
||||||
|
<Label fx:id="descriptionErrorLabel" styleClass="error-text" wrapText="true"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
<VBox>
|
<VBox>
|
||||||
<HBox spacing="3">
|
<HBox spacing="3">
|
||||||
|
|
Loading…
Reference in New Issue