From 2a47d93c979eebcb8a5de32a702ebbb52c5cde1d Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Tue, 26 Dec 2023 13:47:27 -0500 Subject: [PATCH] Added editing, saving, and deleting accounts. --- pom.xml | 2 +- .../perfin/control/AccountViewController.java | 17 ++++++ .../perfin/control/EditAccountController.java | 61 ++++++++++++------- .../perfin/control/MainViewController.java | 2 +- .../perfin/data/AccountRepository.java | 4 +- .../andrewlalis/perfin/data/DataSource.java | 3 + .../com/andrewlalis/perfin/data/DbUtil.java | 9 +++ .../andrewlalis/perfin/data/SqlRunnable.java | 8 +++ .../perfin/data/ThrowableConsumer.java | 6 ++ .../data/impl/JdbcAccountRepository.java | 32 ++++++++++ .../com/andrewlalis/perfin/model/Account.java | 16 +++++ .../andrewlalis/perfin/model/AccountType.java | 17 +++++- src/main/resources/account-view.fxml | 10 +-- src/main/resources/main-view.fxml | 1 - src/main/resources/style/account-view.css | 8 +++ 15 files changed, 163 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/andrewlalis/perfin/data/SqlRunnable.java create mode 100644 src/main/java/com/andrewlalis/perfin/data/ThrowableConsumer.java diff --git a/pom.xml b/pom.xml index ae13057..8504fba 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ com.andrewlalis javafx-scene-router - 1.4.0 + 1.5.1 diff --git a/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java b/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java index f755632..7c375aa 100644 --- a/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java @@ -2,12 +2,15 @@ package com.andrewlalis.perfin.control; import com.andrewlalis.javafx_scene_router.RouteSelectionListener; import com.andrewlalis.perfin.model.Account; +import com.andrewlalis.perfin.model.Profile; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.TextField; import java.time.format.DateTimeFormatter; +import static com.andrewlalis.perfin.PerfinApp.router; + public class AccountViewController implements RouteSelectionListener { private Account account; @@ -32,4 +35,18 @@ public class AccountViewController implements RouteSelectionListener { accountCurrencyField.setText(account.getCurrency().getDisplayName()); accountCreatedAtField.setText(account.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); } + + @FXML + public void goToEditPage() { + router.navigate("edit-account", account); + } + + @FXML + public void deleteAccount() { + Profile.getCurrent().getDataSource().useAccountRepository(repo -> { + repo.delete(account); + }); + router.getHistory().clear(); + router.navigate("accounts"); + } } diff --git a/src/main/java/com/andrewlalis/perfin/control/EditAccountController.java b/src/main/java/com/andrewlalis/perfin/control/EditAccountController.java index d65a0e9..16d6ec6 100644 --- a/src/main/java/com/andrewlalis/perfin/control/EditAccountController.java +++ b/src/main/java/com/andrewlalis/perfin/control/EditAccountController.java @@ -25,7 +25,11 @@ public class EditAccountController implements RouteSelectionListener { @FXML public ComboBox accountCurrencyComboBox; @FXML - public ChoiceBox accountTypeChoiceBox; + public ChoiceBox accountTypeChoiceBox; + + private boolean editingNewAccount() { + return account == null; + } @FXML public void initialize() { @@ -39,16 +43,16 @@ public class EditAccountController implements RouteSelectionListener { } accountCurrencyComboBox.getSelectionModel().select(Currency.getInstance("USD")); - accountTypeChoiceBox.getItems().add("Checking"); - accountTypeChoiceBox.getItems().add("Savings"); - accountTypeChoiceBox.getItems().add("Credit Card"); - accountTypeChoiceBox.getSelectionModel().select("Checking"); + accountTypeChoiceBox.getItems().add(AccountType.CHECKING); + accountTypeChoiceBox.getItems().add(AccountType.SAVINGS); + accountTypeChoiceBox.getItems().add(AccountType.CREDIT_CARD); + accountTypeChoiceBox.getSelectionModel().select(AccountType.CHECKING); } @Override public void onRouteSelected(Object context) { this.account = (Account) context; - if (account == null) { + if (editingNewAccount()) { titleLabel.setText("Editing New Account"); } else { titleLabel.setText("Editing Account: " + account.getName()); @@ -58,26 +62,38 @@ public class EditAccountController implements RouteSelectionListener { @FXML public void save() { - if (account == null) { - // If we're editing a new account. - String name = accountNameField.getText().strip(); - String number = accountNumberField.getText().strip(); - AccountType type = AccountType.parse(accountTypeChoiceBox.getValue()); - Currency currency = accountCurrencyComboBox.getValue(); - Account newAccount = new Account(type, number, name, currency); - Profile.getCurrent().getDataSource().getAccountRepository().insert(newAccount); + try (var accountRepo = Profile.getCurrent().getDataSource().getAccountRepository()) { + if (editingNewAccount()) { + String name = accountNameField.getText().strip(); + String number = accountNumberField.getText().strip(); + AccountType type = accountTypeChoiceBox.getValue(); + Currency currency = accountCurrencyComboBox.getValue(); + Account newAccount = new Account(type, number, name, currency); + long id = accountRepo.insert(newAccount); + Account savedAccount = accountRepo.findById(id).orElseThrow(); - // Once we create the new account, go to the account. - router.getHistory().clear(); - router.navigate("accounts"); - } else { - throw new IllegalStateException("Not implemented."); + // Once we create the new account, go to the account. + router.getHistory().clear(); + router.navigate("account", savedAccount); + } else { + System.out.println("Updating account " + account.getName()); + account.setName(accountNameField.getText().strip()); + account.setAccountNumber(accountNumberField.getText().strip()); + account.setType(accountTypeChoiceBox.getValue()); + account.setCurrency(accountCurrencyComboBox.getValue()); + accountRepo.update(account); + Account updatedAccount = accountRepo.findById(account.getId()).orElseThrow(); + router.getHistory().clear(); + router.navigate("account", updatedAccount); + } + } catch (Exception e) { + e.printStackTrace(); } } @FXML public void cancel() { - router.navigateBack(); + router.navigateBackAndClear(); } public void resetForm() { @@ -87,7 +103,10 @@ public class EditAccountController implements RouteSelectionListener { accountTypeChoiceBox.getSelectionModel().selectFirst(); accountCurrencyComboBox.getSelectionModel().select(Currency.getInstance("USD")); } else { - // TODO: Set to original account. + accountNameField.setText(account.getName()); + accountNumberField.setText(account.getAccountNumber()); + accountTypeChoiceBox.getSelectionModel().select(account.getType()); + accountCurrencyComboBox.getSelectionModel().select(account.getCurrency()); } } } diff --git a/src/main/java/com/andrewlalis/perfin/control/MainViewController.java b/src/main/java/com/andrewlalis/perfin/control/MainViewController.java index 02f7045..34da78d 100644 --- a/src/main/java/com/andrewlalis/perfin/control/MainViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/MainViewController.java @@ -21,7 +21,6 @@ public class MainViewController { public void initialize() { AnchorPaneRouterView routerView = (AnchorPaneRouterView) router.getView(); mainContainer.setCenter(routerView.getAnchorPane()); - routerView.getAnchorPane().setStyle("-fx-border-color: orange;"); // Set up a simple breadcrumb display in the top bar. BindingUtil.mapContent( breadcrumbHBox.getChildren(), @@ -40,6 +39,7 @@ public class MainViewController { @FXML public void goToAccounts() { + router.getHistory().clear(); router.navigate("accounts"); } diff --git a/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java b/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java index d565045..9dce9c0 100644 --- a/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java @@ -6,9 +6,11 @@ import java.math.BigDecimal; import java.util.List; import java.util.Optional; -public interface AccountRepository { +public interface AccountRepository extends AutoCloseable { long insert(Account account); List findAll(); Optional findById(long id); BigDecimal deriveCurrentBalance(long id); + void update(Account account); + void delete(Account account); } diff --git a/src/main/java/com/andrewlalis/perfin/data/DataSource.java b/src/main/java/com/andrewlalis/perfin/data/DataSource.java index f91e141..5e5c5a3 100644 --- a/src/main/java/com/andrewlalis/perfin/data/DataSource.java +++ b/src/main/java/com/andrewlalis/perfin/data/DataSource.java @@ -2,4 +2,7 @@ package com.andrewlalis.perfin.data; public interface DataSource { AccountRepository getAccountRepository(); + default void useAccountRepository(ThrowableConsumer repoConsumer) { + DbUtil.useClosable(this::getAccountRepository, repoConsumer); + } } diff --git a/src/main/java/com/andrewlalis/perfin/data/DbUtil.java b/src/main/java/com/andrewlalis/perfin/data/DbUtil.java index 2fe29cf..608f588 100644 --- a/src/main/java/com/andrewlalis/perfin/data/DbUtil.java +++ b/src/main/java/com/andrewlalis/perfin/data/DbUtil.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.Supplier; public final class DbUtil { private DbUtil() {} @@ -90,4 +91,12 @@ public final class DbUtil { public static LocalDateTime utcLDTFromTimestamp(Timestamp ts) { return ts.toInstant().atOffset(ZoneOffset.UTC).toLocalDateTime(); } + + public static void useClosable(Supplier supplier, ThrowableConsumer consumer) { + try (T t = supplier.get()) { + consumer.accept(t); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/src/main/java/com/andrewlalis/perfin/data/SqlRunnable.java b/src/main/java/com/andrewlalis/perfin/data/SqlRunnable.java new file mode 100644 index 0000000..fd5f0fc --- /dev/null +++ b/src/main/java/com/andrewlalis/perfin/data/SqlRunnable.java @@ -0,0 +1,8 @@ +package com.andrewlalis.perfin.data; + +import java.sql.SQLException; + +@FunctionalInterface +public interface SqlRunnable { + void run() throws SQLException; +} diff --git a/src/main/java/com/andrewlalis/perfin/data/ThrowableConsumer.java b/src/main/java/com/andrewlalis/perfin/data/ThrowableConsumer.java new file mode 100644 index 0000000..ffc7fcf --- /dev/null +++ b/src/main/java/com/andrewlalis/perfin/data/ThrowableConsumer.java @@ -0,0 +1,6 @@ +package com.andrewlalis.perfin.data; + +@FunctionalInterface +public interface ThrowableConsumer { + void accept(T value) throws Exception; +} diff --git a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountRepository.java b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountRepository.java index 9d47453..269e929 100644 --- a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountRepository.java @@ -2,6 +2,7 @@ package com.andrewlalis.perfin.data.impl; import com.andrewlalis.perfin.data.AccountRepository; import com.andrewlalis.perfin.data.DbUtil; +import com.andrewlalis.perfin.data.UncheckedSqlException; import com.andrewlalis.perfin.model.Account; import com.andrewlalis.perfin.model.AccountType; @@ -46,6 +47,32 @@ public record JdbcAccountRepository(Connection conn) implements AccountRepositor return BigDecimal.valueOf(0, 4); } + @Override + public void update(Account account) { + DbUtil.updateOne( + conn, + "UPDATE account SET name = ?, account_number = ?, currency = ?, account_type = ? WHERE id = ?", + List.of( + account.getName(), + account.getAccountNumber(), + account.getCurrency().getCurrencyCode(), + account.getType().name(), + account.getId() + ) + ); + } + + @Override + public void delete(Account account) { + try (var stmt = conn.prepareStatement("DELETE FROM account WHERE id = ?")) { + stmt.setLong(1, account.getId()); + int rows = stmt.executeUpdate(); + if (rows != 1) throw new SQLException("Affected " + rows + " rows instead of expected 1."); + } catch (SQLException e) { + throw new UncheckedSqlException(e); + } + } + private static Account parseAccount(ResultSet rs) throws SQLException { long id = rs.getLong("id"); LocalDateTime createdAt = DbUtil.utcLDTFromTimestamp(rs.getTimestamp("created_at")); @@ -55,4 +82,9 @@ public record JdbcAccountRepository(Connection conn) implements AccountRepositor Currency currency = Currency.getInstance(rs.getString("currency")); return new Account(id, createdAt, type, accountNumber, name, currency); } + + @Override + public void close() throws Exception { + conn.close(); + } } diff --git a/src/main/java/com/andrewlalis/perfin/model/Account.java b/src/main/java/com/andrewlalis/perfin/model/Account.java index 6970096..a62b2c2 100644 --- a/src/main/java/com/andrewlalis/perfin/model/Account.java +++ b/src/main/java/com/andrewlalis/perfin/model/Account.java @@ -48,6 +48,22 @@ public class Account { return currency; } + public void setType(AccountType type) { + this.type = type; + } + + public void setAccountNumber(String accountNumber) { + this.accountNumber = accountNumber; + } + + public void setName(String name) { + this.name = name; + } + + public void setCurrency(Currency currency) { + this.currency = currency; + } + public LocalDateTime getCreatedAt() { return createdAt; } diff --git a/src/main/java/com/andrewlalis/perfin/model/AccountType.java b/src/main/java/com/andrewlalis/perfin/model/AccountType.java index ab70752..2de9ba3 100644 --- a/src/main/java/com/andrewlalis/perfin/model/AccountType.java +++ b/src/main/java/com/andrewlalis/perfin/model/AccountType.java @@ -1,9 +1,20 @@ package com.andrewlalis.perfin.model; public enum AccountType { - CHECKING, - SAVINGS, - CREDIT_CARD; + CHECKING("Checking"), + SAVINGS("Savings"), + CREDIT_CARD("Credit Card"); + + private final String name; + + AccountType(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } public static AccountType parse(String s) { s = s.strip().toUpperCase(); diff --git a/src/main/resources/account-view.fxml b/src/main/resources/account-view.fxml index 4a23cb0..0a251ef 100644 --- a/src/main/resources/account-view.fxml +++ b/src/main/resources/account-view.fxml @@ -8,13 +8,12 @@ fx:controller="com.andrewlalis.perfin.control.AccountViewController" stylesheets="@style/account-view.css" styleClass="main-container" - style="-fx-border-color: green;" >
- +
- -