diff --git a/pom.xml b/pom.xml index 936b884..07d9d7d 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,7 @@ 2.2.224 + org.junit.jupiter junit-jupiter-api @@ -62,6 +63,19 @@ 5.10.0 test + + + + org.slf4j + slf4j-api + 2.0.10 + + + ch.qos.logback + logback-classic + 1.4.12 + runtime + diff --git a/src/main/java/com/andrewlalis/perfin/PerfinApp.java b/src/main/java/com/andrewlalis/perfin/PerfinApp.java index 54426b4..e10b067 100644 --- a/src/main/java/com/andrewlalis/perfin/PerfinApp.java +++ b/src/main/java/com/andrewlalis/perfin/PerfinApp.java @@ -2,6 +2,8 @@ package com.andrewlalis.perfin; import com.andrewlalis.javafx_scene_router.AnchorPaneRouterView; import com.andrewlalis.javafx_scene_router.SceneRouter; +import com.andrewlalis.perfin.control.Popups; +import com.andrewlalis.perfin.data.DataSourceInitializationException; import com.andrewlalis.perfin.model.Profile; import com.andrewlalis.perfin.view.ImageCache; import com.andrewlalis.perfin.view.SceneUtil; @@ -89,6 +91,11 @@ public class PerfinApp extends Application { private static void loadLastUsedProfile(Consumer msgConsumer) throws Exception { msgConsumer.accept("Loading the most recent profile."); - Profile.loadLast(); + try { + Profile.loadLast(); + } catch (DataSourceInitializationException e) { + Popups.error(e.getMessage()); + throw e; + } } } \ No newline at end of file diff --git a/src/main/java/com/andrewlalis/perfin/control/AccountsViewController.java b/src/main/java/com/andrewlalis/perfin/control/AccountsViewController.java index 17fdfe4..0228df5 100644 --- a/src/main/java/com/andrewlalis/perfin/control/AccountsViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/AccountsViewController.java @@ -1,11 +1,12 @@ package com.andrewlalis.perfin.control; import com.andrewlalis.javafx_scene_router.RouteSelectionListener; -import com.andrewlalis.perfin.view.component.AccountTile; -import com.andrewlalis.perfin.data.util.CurrencyUtil; import com.andrewlalis.perfin.data.pagination.PageRequest; import com.andrewlalis.perfin.data.pagination.Sort; +import com.andrewlalis.perfin.data.util.CurrencyUtil; +import com.andrewlalis.perfin.model.MoneyValue; import com.andrewlalis.perfin.model.Profile; +import com.andrewlalis.perfin.view.component.AccountTile; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -13,10 +14,6 @@ import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.FlowPane; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.Currency; - import static com.andrewlalis.perfin.PerfinApp.router; public class AccountsViewController implements RouteSelectionListener { @@ -63,9 +60,7 @@ public class AccountsViewController implements RouteSelectionListener { var totals = profile.getDataSource().getCombinedAccountBalances(); StringBuilder sb = new StringBuilder("Totals: "); for (var entry : totals.entrySet()) { - Currency cur = entry.getKey(); - BigDecimal value = entry.getValue().setScale(cur.getDefaultFractionDigits(), RoundingMode.HALF_UP); - sb.append(cur.getCurrencyCode()).append(' ').append(CurrencyUtil.formatMoney(value, cur)).append(' '); + sb.append(CurrencyUtil.formatMoneyWithCurrencyPrefix(new MoneyValue(entry.getValue(), entry.getKey()))); } Platform.runLater(() -> totalLabel.setText(sb.toString().strip())); }); diff --git a/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java b/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java index da48bed..c94a13d 100644 --- a/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java +++ b/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java @@ -5,6 +5,7 @@ import com.andrewlalis.perfin.data.util.CurrencyUtil; import com.andrewlalis.perfin.data.util.DateUtil; import com.andrewlalis.perfin.data.util.FileUtil; import com.andrewlalis.perfin.model.Account; +import com.andrewlalis.perfin.model.MoneyValue; import com.andrewlalis.perfin.model.Profile; import com.andrewlalis.perfin.view.component.FileSelectionArea; import javafx.application.Platform; @@ -39,7 +40,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener { Profile.getCurrent().getDataSource().useAccountRepository(repo -> { BigDecimal value = repo.deriveCurrentBalance(account.getId()); Platform.runLater(() -> balanceField.setText( - CurrencyUtil.formatMoneyAsBasicNumber(value, account.getCurrency()) + CurrencyUtil.formatMoneyAsBasicNumber(new MoneyValue(value, account.getCurrency())) )); }); }); diff --git a/src/main/java/com/andrewlalis/perfin/control/ProfilesViewController.java b/src/main/java/com/andrewlalis/perfin/control/ProfilesViewController.java index 21507d9..1027a26 100644 --- a/src/main/java/com/andrewlalis/perfin/control/ProfilesViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/ProfilesViewController.java @@ -1,6 +1,7 @@ package com.andrewlalis.perfin.control; import com.andrewlalis.perfin.PerfinApp; +import com.andrewlalis.perfin.data.DataSourceInitializationException; import com.andrewlalis.perfin.data.util.FileUtil; import com.andrewlalis.perfin.model.Profile; import com.andrewlalis.perfin.view.ProfilesStage; @@ -124,6 +125,9 @@ public class ProfilesViewController { e.printStackTrace(System.err); Popups.error("Failed to load profile: " + e.getMessage()); return false; + } catch (DataSourceInitializationException e) { + Popups.error("Failed to initialize the profile's data: " + e.getMessage()); + return false; } } diff --git a/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java b/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java index 3aae013..d7b738c 100644 --- a/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java @@ -44,7 +44,7 @@ public class TransactionViewController { this.transaction = transaction; if (transaction == null) return; titleLabel.setText("Transaction #" + transaction.getId()); - amountLabel.setText(CurrencyUtil.formatMoney(transaction.getAmount(), transaction.getCurrency())); + amountLabel.setText(CurrencyUtil.formatMoney(transaction.getMoneyAmount())); timestampLabel.setText(DateUtil.formatUTCAsLocalWithZone(transaction.getTimestamp())); descriptionLabel.setText(transaction.getDescription()); diff --git a/src/main/java/com/andrewlalis/perfin/data/DataSource.java b/src/main/java/com/andrewlalis/perfin/data/DataSource.java index 2956c11..c35366f 100644 --- a/src/main/java/com/andrewlalis/perfin/data/DataSource.java +++ b/src/main/java/com/andrewlalis/perfin/data/DataSource.java @@ -5,6 +5,7 @@ import com.andrewlalis.perfin.data.util.CurrencyUtil; import com.andrewlalis.perfin.data.util.DbUtil; import com.andrewlalis.perfin.data.util.ThrowableConsumer; import com.andrewlalis.perfin.model.Account; +import com.andrewlalis.perfin.model.MoneyValue; import javafx.application.Platform; import java.math.BigDecimal; @@ -52,12 +53,11 @@ public interface DataSource { // Utility methods: default void getAccountBalanceText(Account account, Consumer balanceConsumer) { - Thread.ofVirtual().start(() -> { - useAccountRepository(repo -> { - BigDecimal balance = repo.deriveCurrentBalance(account.getId()); - Platform.runLater(() -> balanceConsumer.accept(CurrencyUtil.formatMoney(balance, account.getCurrency()))); - }); - }); + Thread.ofVirtual().start(() -> useAccountRepository(repo -> { + BigDecimal balance = repo.deriveCurrentBalance(account.getId()); + MoneyValue money = new MoneyValue(balance, account.getCurrency()); + Platform.runLater(() -> balanceConsumer.accept(CurrencyUtil.formatMoney(money))); + })); } default Map getCombinedAccountBalances() { diff --git a/src/main/java/com/andrewlalis/perfin/data/DataSourceInitializationException.java b/src/main/java/com/andrewlalis/perfin/data/DataSourceInitializationException.java new file mode 100644 index 0000000..3c8305a --- /dev/null +++ b/src/main/java/com/andrewlalis/perfin/data/DataSourceInitializationException.java @@ -0,0 +1,11 @@ +package com.andrewlalis.perfin.data; + +public class DataSourceInitializationException extends Exception { + public DataSourceInitializationException(String message) { + super(message); + } + + public DataSourceInitializationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcDataSourceFactory.java b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcDataSourceFactory.java new file mode 100644 index 0000000..f1a7201 --- /dev/null +++ b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcDataSourceFactory.java @@ -0,0 +1,130 @@ +package com.andrewlalis.perfin.data.impl; + +import com.andrewlalis.perfin.data.DataSource; +import com.andrewlalis.perfin.data.DataSourceInitializationException; +import com.andrewlalis.perfin.data.util.FileUtil; +import com.andrewlalis.perfin.model.Profile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.List; + +/** + * Component that's responsible for obtaining a JDBC data source for a profile. + */ +public class JdbcDataSourceFactory { + private static final Logger log = LoggerFactory.getLogger(JdbcDataSourceFactory.class); + + /** + * The version of schema that this app is compatible with. If a profile is + * loaded with an old schema version, then we'll migrate to the latest. If + * the profile has a newer schema version, we'll exit and prompt the user + * to update their app. + */ + public static final int SCHEMA_VERSION = 1; + + public DataSource getDataSource(String profileName) throws DataSourceInitializationException { + final boolean dbExists = Files.exists(getDatabaseFile(profileName)); + if (!dbExists) { + log.info("Creating new database for profile {}.", profileName); + createNewDatabase(profileName); + } else { + int loadedSchemaVersion; + try { + loadedSchemaVersion = getSchemaVersion(profileName); + } catch (IOException e) { + log.error("Failed to load schema version.", e); + throw new DataSourceInitializationException("Failed to determine database schema version.", e); + } + log.debug("Database loaded for profile {} has schema version {}.", profileName, loadedSchemaVersion); + if (loadedSchemaVersion < SCHEMA_VERSION) { + log.debug("Schema version {} is lower than the app's version {}. Performing migration.", loadedSchemaVersion, SCHEMA_VERSION); + // TODO: Do migration + } else if (loadedSchemaVersion > SCHEMA_VERSION) { + log.debug("Schema version {} is higher than the app's version {}. Cannot continue.", loadedSchemaVersion, SCHEMA_VERSION); + throw new DataSourceInitializationException("Profile " + profileName + " has a database with an unsupported schema version."); + } + } + return new JdbcDataSource(getJdbcUrl(profileName), Profile.getContentDir(profileName)); + } + + private void createNewDatabase(String profileName) throws DataSourceInitializationException { + log.info("Creating new database for profile {}.", profileName); + JdbcDataSource dataSource = new JdbcDataSource(getJdbcUrl(profileName), Profile.getContentDir(profileName)); + try ( + InputStream in = JdbcDataSourceFactory.class.getResourceAsStream("/sql/schema.sql"); + Connection conn = dataSource.getConnection() + ) { + if (in == null) throw new IOException("Could not load database schema SQL file."); + String schemaStr = new String(in.readAllBytes(), StandardCharsets.UTF_8); + List statements = Arrays.stream(schemaStr.split(";")) + .map(String::strip).filter(s -> !s.isBlank()).toList(); + for (String statementText : statements) { + try (Statement stmt = conn.createStatement()) { + stmt.executeUpdate(statementText); + } + } + try { + writeCurrentSchemaVersion(profileName); + } catch (IOException e) { + log.warn("Failed to write current schema version to file.", e); + } + } catch (IOException e) { + log.error("IO Exception when trying to create database.", e); + FileUtil.deleteIfPossible(getDatabaseFile(profileName)); + throw new DataSourceInitializationException("Failed to read SQL data to create database schema.", e); + } catch (SQLException e) { + log.error("SQL Exception when trying to create database.", e); + FileUtil.deleteIfPossible(getDatabaseFile(profileName)); + throw new DataSourceInitializationException("Failed to create the database due to an SQL error.", e); + } + if (!testConnection(dataSource)) { + FileUtil.deleteIfPossible(getDatabaseFile(profileName)); + throw new DataSourceInitializationException("Testing the database connection failed."); + } + } + + private boolean testConnection(JdbcDataSource dataSource) { + try (var conn = dataSource.getConnection(); var stmt = conn.createStatement()) { + return stmt.execute("SELECT 1;"); + } catch (SQLException e) { + log.error("JDBC database connection failed.", e); + return false; + } + } + + private static Path getDatabaseFile(String profileName) { + return Profile.getDir(profileName).resolve("database.mv.db"); + } + + private static String getJdbcUrl(String profileName) { + String dbPathAbs = getDatabaseFile(profileName).toAbsolutePath().toString(); + return "jdbc:h2:" + dbPathAbs.substring(0, dbPathAbs.length() - 6); + } + + private static Path getSchemaVersionFile(String profileName) { + return Profile.getDir(profileName).resolve(".jdbc-schema-version.txt"); + } + + private static int getSchemaVersion(String profileName) throws IOException { + if (Files.exists(getSchemaVersionFile(profileName))) { + return Integer.parseInt(Files.readString(getSchemaVersionFile(profileName))); + } else { + writeCurrentSchemaVersion(profileName); + return SCHEMA_VERSION; + } + } + + private static void writeCurrentSchemaVersion(String profileName) throws IOException { + Files.writeString(getSchemaVersionFile(profileName), Integer.toString(SCHEMA_VERSION)); + } +} diff --git a/src/main/java/com/andrewlalis/perfin/data/util/CurrencyUtil.java b/src/main/java/com/andrewlalis/perfin/data/util/CurrencyUtil.java index 3f7a1dc..010482e 100644 --- a/src/main/java/com/andrewlalis/perfin/data/util/CurrencyUtil.java +++ b/src/main/java/com/andrewlalis/perfin/data/util/CurrencyUtil.java @@ -1,22 +1,27 @@ package com.andrewlalis.perfin.data.util; +import com.andrewlalis.perfin.model.MoneyValue; + import java.math.BigDecimal; import java.math.RoundingMode; import java.text.NumberFormat; -import java.util.Currency; public class CurrencyUtil { - public static String formatMoney(BigDecimal amount, Currency currency) { + public static String formatMoney(MoneyValue money) { NumberFormat nf = NumberFormat.getCurrencyInstance(); - nf.setCurrency(currency); - nf.setMaximumFractionDigits(currency.getDefaultFractionDigits()); - nf.setMinimumFractionDigits(currency.getDefaultFractionDigits()); - BigDecimal displayValue = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_UP); + nf.setCurrency(money.currency()); + nf.setMaximumFractionDigits(money.currency().getDefaultFractionDigits()); + nf.setMinimumFractionDigits(money.currency().getDefaultFractionDigits()); + BigDecimal displayValue = money.amount().setScale(money.currency().getDefaultFractionDigits(), RoundingMode.HALF_UP); return nf.format(displayValue); } - public static String formatMoneyAsBasicNumber(BigDecimal amount, Currency currency) { - BigDecimal displayValue = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_UP); + public static String formatMoneyWithCurrencyPrefix(MoneyValue money) { + return money.currency().getCurrencyCode() + ' ' + formatMoney(money); + } + + public static String formatMoneyAsBasicNumber(MoneyValue money) { + BigDecimal displayValue = money.amount().setScale(money.currency().getDefaultFractionDigits(), RoundingMode.HALF_UP); return displayValue.toString(); } } diff --git a/src/main/java/com/andrewlalis/perfin/data/util/FileUtil.java b/src/main/java/com/andrewlalis/perfin/data/util/FileUtil.java index 8f811f7..540481d 100644 --- a/src/main/java/com/andrewlalis/perfin/data/util/FileUtil.java +++ b/src/main/java/com/andrewlalis/perfin/data/util/FileUtil.java @@ -1,6 +1,8 @@ package com.andrewlalis.perfin.data.util; import javafx.stage.FileChooser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -12,6 +14,8 @@ import java.util.HashMap; import java.util.Map; public class FileUtil { + private static final Logger log = LoggerFactory.getLogger(FileUtil.class); + public static Map MIMETYPES = new HashMap<>(); static { MIMETYPES.put(".pdf", "application/pdf"); @@ -31,6 +35,14 @@ public class FileUtil { MIMETYPES.put(".tiff", "image/tiff"); } + public static void deleteIfPossible(Path file) { + try { + Files.deleteIfExists(file); + } catch (IOException e) { + log.error("Failed to delete file " + file, e); + } + } + public static void deleteDirRecursive(Path startDir) throws IOException { Files.walkFileTree(startDir, new SimpleFileVisitor<>() { @Override diff --git a/src/main/java/com/andrewlalis/perfin/model/AccountEntry.java b/src/main/java/com/andrewlalis/perfin/model/AccountEntry.java index fbf34e0..74d3001 100644 --- a/src/main/java/com/andrewlalis/perfin/model/AccountEntry.java +++ b/src/main/java/com/andrewlalis/perfin/model/AccountEntry.java @@ -85,4 +85,8 @@ public class AccountEntry { public BigDecimal getSignedAmount() { return type == Type.DEBIT ? amount : amount.negate(); } + + public MoneyValue getMoneyValue() { + return new MoneyValue(amount, currency); + } } diff --git a/src/main/java/com/andrewlalis/perfin/model/BalanceRecord.java b/src/main/java/com/andrewlalis/perfin/model/BalanceRecord.java index f5d795d..ed3b6fe 100644 --- a/src/main/java/com/andrewlalis/perfin/model/BalanceRecord.java +++ b/src/main/java/com/andrewlalis/perfin/model/BalanceRecord.java @@ -44,4 +44,8 @@ public class BalanceRecord { public Currency getCurrency() { return currency; } + + public MoneyValue getMoneyAmount() { + return new MoneyValue(balance, currency); + } } diff --git a/src/main/java/com/andrewlalis/perfin/model/MoneyValue.java b/src/main/java/com/andrewlalis/perfin/model/MoneyValue.java new file mode 100644 index 0000000..1115bc5 --- /dev/null +++ b/src/main/java/com/andrewlalis/perfin/model/MoneyValue.java @@ -0,0 +1,11 @@ +package com.andrewlalis.perfin.model; + +import java.math.BigDecimal; +import java.util.Currency; + +/** + * An amount of money of a certain currency. + * @param amount The amount of money. + * @param currency The currency of the money. + */ +public record MoneyValue(BigDecimal amount, Currency currency) {} diff --git a/src/main/java/com/andrewlalis/perfin/model/Profile.java b/src/main/java/com/andrewlalis/perfin/model/Profile.java index 83e9ad7..dc380c2 100644 --- a/src/main/java/com/andrewlalis/perfin/model/Profile.java +++ b/src/main/java/com/andrewlalis/perfin/model/Profile.java @@ -2,14 +2,18 @@ package com.andrewlalis.perfin.model; import com.andrewlalis.perfin.PerfinApp; import com.andrewlalis.perfin.data.DataSource; -import com.andrewlalis.perfin.data.impl.JdbcDataSource; +import com.andrewlalis.perfin.data.DataSourceInitializationException; +import com.andrewlalis.perfin.data.impl.JdbcDataSourceFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.sql.SQLException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; import java.util.function.Consumer; /** @@ -30,6 +34,8 @@ import java.util.function.Consumer; *

*/ public class Profile { + private static final Logger log = LoggerFactory.getLogger(Profile.class); + private static Profile current; private static final List> profileLoadListeners = new ArrayList<>(); @@ -67,10 +73,6 @@ public class Profile { return getDir(name).resolve("settings.properties"); } - public static Path getDatabaseFile(String name) { - return getDir(name).resolve("database.mv.db"); - } - public static Profile getCurrent() { return current; } @@ -89,6 +91,7 @@ public class Profile { .map(path -> path.getFileName().toString()) .sorted().toList(); } catch (IOException e) { + log.error("Failed to get a list of available profiles.", e); return Collections.emptyList(); } } @@ -100,8 +103,7 @@ public class Profile { String s = Files.readString(lastProfileFile).strip().toLowerCase(); if (!s.isBlank()) return s; } catch (IOException e) { - System.err.println("Failed to read " + lastProfileFile); - e.printStackTrace(System.err); + log.error("Failed to read " + lastProfileFile, e); } } return "default"; @@ -112,16 +114,15 @@ public class Profile { try { Files.writeString(lastProfileFile, name); } catch (IOException e) { - System.err.println("Failed to write " + lastProfileFile); - e.printStackTrace(System.err); + log.error("Failed to write " + lastProfileFile, e); } } - public static void loadLast() throws Exception { + public static void loadLast() throws IOException, DataSourceInitializationException { load(getLastProfile()); } - public static void load(String name) throws IOException { + public static void load(String name) throws IOException, DataSourceInitializationException { if (Files.notExists(getDir(name))) { initProfileDir(name); } @@ -129,7 +130,7 @@ public class Profile { try (var in = Files.newInputStream(getSettingsFile(name))) { settings.load(in); } - current = new Profile(name, settings, initJdbcDataSource(name)); + current = new Profile(name, settings, new JdbcDataSourceFactory().getDataSource(name)); saveLastProfile(current.getName()); for (var c : profileLoadListeners) { c.accept(current); @@ -144,38 +145,6 @@ public class Profile { copyResourceFile("/text/contentDirReadme.txt", getContentDir(name).resolve("README.txt")); } - private static DataSource initJdbcDataSource(String name) throws IOException { - String databaseFilename = getDatabaseFile(name).toAbsolutePath().toString(); - String jdbcUrl = "jdbc:h2:" + databaseFilename.substring(0, databaseFilename.length() - 6); - boolean exists = Files.exists(getDatabaseFile(name)); - JdbcDataSource dataSource = new JdbcDataSource(jdbcUrl, getContentDir(name)); - if (!exists) {// Initialize the datasource using schema.sql. - try (var in = Profile.class.getResourceAsStream("/sql/schema.sql"); var conn = dataSource.getConnection()) { - if (in == null) throw new IOException("Could not load /sql/schema.sql"); - String schemaStr = new String(in.readAllBytes(), StandardCharsets.UTF_8); - List statements = Arrays.stream(schemaStr.split(";")) - .map(String::strip).filter(s -> !s.isBlank()).toList(); - for (var statementStr : statements) { - try (var stmt = conn.createStatement()) { - stmt.executeUpdate(statementStr); - System.out.println("Executed update:\n" + statementStr + "\n-----"); - } - } - } catch (SQLException e) { - Files.deleteIfExists(getDatabaseFile(name)); - throw new IOException("Failed to initialize database.", e); - } - } - // Test the datasource before returning it. - try (var conn = dataSource.getConnection(); var s = conn.createStatement()) { - boolean success = s.execute("SELECT 1;"); - if (!success) throw new IOException("Failed to execute DB test statement."); - } catch (SQLException e) { - throw new IOException(e); - } - return dataSource; - } - private static void copyResourceFile(String resource, Path dest) throws IOException { try ( var in = Profile.class.getResourceAsStream(resource); diff --git a/src/main/java/com/andrewlalis/perfin/model/Transaction.java b/src/main/java/com/andrewlalis/perfin/model/Transaction.java index d3704c7..78627f8 100644 --- a/src/main/java/com/andrewlalis/perfin/model/Transaction.java +++ b/src/main/java/com/andrewlalis/perfin/model/Transaction.java @@ -46,6 +46,10 @@ public class Transaction { return description; } + public MoneyValue getMoneyAmount() { + return new MoneyValue(amount, currency); + } + @Override public boolean equals(Object other) { return other instanceof Transaction tx && id == tx.id; diff --git a/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryItemTile.java b/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryItemTile.java index 84bc8bb..9f4f5e7 100644 --- a/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryItemTile.java +++ b/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryItemTile.java @@ -42,7 +42,7 @@ public class AccountHistoryItemTile extends BorderPane { } private Node buildAccountEntryItem(AccountEntry entry) { - Text amountText = new Text(CurrencyUtil.formatMoney(entry.getSignedAmount(), entry.getCurrency())); + Text amountText = new Text(CurrencyUtil.formatMoneyWithCurrencyPrefix(entry.getMoneyValue())); Hyperlink transactionLink = new Hyperlink("Transaction #" + entry.getTransactionId()); transactionLink.setOnAction(event -> router.navigate( "transactions", @@ -56,7 +56,7 @@ public class AccountHistoryItemTile extends BorderPane { } private Node buildBalanceRecordItem(BalanceRecord balanceRecord) { - Text amountText = new Text(CurrencyUtil.formatMoney(balanceRecord.getBalance(), balanceRecord.getCurrency())); - return new TextFlow(new Text("Balance record added with value of "), amountText); + Text amountText = new Text(CurrencyUtil.formatMoneyWithCurrencyPrefix(balanceRecord.getMoneyAmount())); + return new TextFlow(new Text("Balance record #" + balanceRecord.getId() + " added with value of "), amountText); } } diff --git a/src/main/java/com/andrewlalis/perfin/view/component/DataSourcePaginationControls.java b/src/main/java/com/andrewlalis/perfin/view/component/DataSourcePaginationControls.java index 427122e..f2944c2 100644 --- a/src/main/java/com/andrewlalis/perfin/view/component/DataSourcePaginationControls.java +++ b/src/main/java/com/andrewlalis/perfin/view/component/DataSourcePaginationControls.java @@ -57,7 +57,6 @@ public class DataSourcePaginationControls extends BorderPane { pageText.setTextAlignment(TextAlignment.CENTER); BorderPane pageTextContainer = new BorderPane(pageText); BorderPane.setAlignment(pageText, Pos.CENTER); - pageTextContainer.setStyle("-fx-border-color: blue;"); Button previousPageButton = new Button("Previous Page"); diff --git a/src/main/java/com/andrewlalis/perfin/view/component/TransactionTile.java b/src/main/java/com/andrewlalis/perfin/view/component/TransactionTile.java index 833881a..9978d0a 100644 --- a/src/main/java/com/andrewlalis/perfin/view/component/TransactionTile.java +++ b/src/main/java/com/andrewlalis/perfin/view/component/TransactionTile.java @@ -55,7 +55,7 @@ public class TransactionTile extends BorderPane { } private Node getHeader(Transaction transaction) { - Label currencyLabel = new Label(CurrencyUtil.formatMoney(transaction.getAmount(), transaction.getCurrency())); + Label currencyLabel = new Label(CurrencyUtil.formatMoney(transaction.getMoneyAmount())); currencyLabel.setStyle("-fx-font-family: monospace;"); HBox headerHBox = new HBox( currencyLabel diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index f70e4dc..21d0a50 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -9,6 +9,8 @@ module com.andrewlalis.perfin { requires java.sql; + requires org.slf4j; + exports com.andrewlalis.perfin to javafx.graphics; exports com.andrewlalis.perfin.view to javafx.graphics; exports com.andrewlalis.perfin.model to javafx.graphics;