diff --git a/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java b/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java
index 508844f..c437454 100644
--- a/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java
+++ b/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java
@@ -35,7 +35,7 @@ public class AccountViewController implements RouteSelectionListener {
@Override
public void onRouteSelected(Object context) {
account = (Account) context;
- titleLabel.setText("Account #" + account.getId());
+ titleLabel.setText("Account #" + account.id);
accountNameField.setText(account.getName());
accountNumberField.setText(account.getAccountNumber());
@@ -100,7 +100,7 @@ public class AccountViewController implements RouteSelectionListener {
Thread.ofVirtual().start(() -> {
try (var historyRepo = Profile.getCurrent().getDataSource().getAccountHistoryItemRepository()) {
List historyItems = historyRepo.findMostRecentForAccount(
- account.getId(),
+ account.id,
loadHistoryFrom,
historyLoadSize
);
diff --git a/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java b/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java
index c94a13d..56f8d17 100644
--- a/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java
+++ b/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java
@@ -38,7 +38,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
Thread.ofVirtual().start(() -> {
Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
- BigDecimal value = repo.deriveCurrentBalance(account.getId());
+ BigDecimal value = repo.deriveCurrentBalance(account.id);
Platform.runLater(() -> balanceField.setText(
CurrencyUtil.formatMoneyAsBasicNumber(new MoneyValue(value, account.getCurrency()))
));
@@ -54,7 +54,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
BigDecimal reportedBalance = new BigDecimal(balanceField.getText());
repo.insert(
DateUtil.localToUTC(localTimestamp),
- account.getId(),
+ account.id,
reportedBalance,
account.getCurrency(),
attachmentSelectionArea.getSelectedFiles()
diff --git a/src/main/java/com/andrewlalis/perfin/control/CreateTransactionController.java b/src/main/java/com/andrewlalis/perfin/control/CreateTransactionController.java
index c0a4ba6..3f78824 100644
--- a/src/main/java/com/andrewlalis/perfin/control/CreateTransactionController.java
+++ b/src/main/java/com/andrewlalis/perfin/control/CreateTransactionController.java
@@ -197,7 +197,7 @@ public class CreateTransactionController implements RouteSelectionListener {
Account creditAccount = linkCreditAccountComboBox.getValue();
if (debitAccount == null && creditAccount == null) {
linkedAccountsErrorLabel.setText("At least one credit or debit account must be linked to the transaction for it to have any effect.");
- } else if (debitAccount != null && creditAccount != null && debitAccount.getId() == creditAccount.getId()) {
+ } else if (debitAccount != null && debitAccount.equals(creditAccount)) {
linkedAccountsErrorLabel.setText("Cannot link the same account to both credit and debit.");
} else {
linkedAccountsErrorLabel.setText(null);
@@ -223,7 +223,7 @@ public class CreateTransactionController implements RouteSelectionListener {
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()) {
+ if (debitAccount != null && debitAccount.equals(creditAccount)) {
errorMessages.add("Credit and debit accounts cannot be the same.");
}
return errorMessages;
diff --git a/src/main/java/com/andrewlalis/perfin/control/EditAccountController.java b/src/main/java/com/andrewlalis/perfin/control/EditAccountController.java
index 8fd18f9..f6c572d 100644
--- a/src/main/java/com/andrewlalis/perfin/control/EditAccountController.java
+++ b/src/main/java/com/andrewlalis/perfin/control/EditAccountController.java
@@ -12,6 +12,8 @@ import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.nio.file.Path;
@@ -23,6 +25,8 @@ import java.util.stream.Stream;
import static com.andrewlalis.perfin.PerfinApp.router;
public class EditAccountController implements RouteSelectionListener {
+ private static final Logger log = LoggerFactory.getLogger(EditAccountController.class);
+
private Account account;
private final BooleanProperty creatingNewAccount = new SimpleBooleanProperty(false);
@@ -102,18 +106,19 @@ public class EditAccountController implements RouteSelectionListener {
router.navigate("account", newAccount);
}
} else {
- System.out.println("Updating account " + account.getName());
+ log.debug("Updating account {}", account.id);
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();
+ Account updatedAccount = accountRepo.findById(account.id).orElseThrow();
router.getHistory().clear();
router.navigate("account", updatedAccount);
}
} catch (Exception e) {
- e.printStackTrace(System.err);
+ log.error("Failed to save (or update) account " + account.id, e);
+ Popups.error("Failed to save the account: " + e.getMessage());
}
}
diff --git a/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java b/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java
index d7b738c..4b56611 100644
--- a/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java
+++ b/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java
@@ -43,7 +43,7 @@ public class TransactionViewController {
public void setTransaction(Transaction transaction) {
this.transaction = transaction;
if (transaction == null) return;
- titleLabel.setText("Transaction #" + transaction.getId());
+ titleLabel.setText("Transaction #" + transaction.id);
amountLabel.setText(CurrencyUtil.formatMoney(transaction.getMoneyAmount()));
timestampLabel.setText(DateUtil.formatUTCAsLocalWithZone(transaction.getTimestamp()));
descriptionLabel.setText(transaction.getDescription());
@@ -52,7 +52,7 @@ public class TransactionViewController {
configureAccountLinkBindings(creditAccountLink);
Thread.ofVirtual().start(() -> {
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
- CreditAndDebitAccounts accounts = repo.findLinkedAccounts(transaction.getId());
+ CreditAndDebitAccounts accounts = repo.findLinkedAccounts(transaction.id);
Platform.runLater(() -> {
accounts.ifDebit(acc -> {
debitAccountLink.setText(acc.getShortName());
@@ -70,7 +70,7 @@ public class TransactionViewController {
attachmentsContainer.visibleProperty().bind(new SimpleListProperty<>(attachmentsList).emptyProperty().not());
Thread.ofVirtual().start(() -> {
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
- List attachments = repo.findAttachments(transaction.getId());
+ List attachments = repo.findAttachments(transaction.id);
Platform.runLater(() -> attachmentsList.setAll(attachments));
});
});
@@ -94,7 +94,7 @@ public class TransactionViewController {
if (confirm) {
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
// TODO: Delete attachments first!
- repo.delete(transaction.getId());
+ repo.delete(transaction.id);
router.getHistory().clear();
router.navigate("transactions");
});
diff --git a/src/main/java/com/andrewlalis/perfin/control/TransactionsViewController.java b/src/main/java/com/andrewlalis/perfin/control/TransactionsViewController.java
index ead3f71..d77b7ad 100644
--- a/src/main/java/com/andrewlalis/perfin/control/TransactionsViewController.java
+++ b/src/main/java/com/andrewlalis/perfin/control/TransactionsViewController.java
@@ -100,7 +100,7 @@ public class TransactionsViewController implements RouteSelectionListener {
Thread.ofVirtual().start(() -> {
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
repo.findById(ctx.selectedTransactionId).ifPresent(tx -> {
- long offset = repo.countAllAfter(tx.getId());
+ long offset = repo.countAllAfter(tx.id);
int pageNumber = (int) (offset / DEFAULT_ITEMS_PER_PAGE) + 1;
paginationControls.setPage(pageNumber).thenRun(() -> selectedTransaction.set(tx));
});
diff --git a/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java b/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java
index 078ca34..fffa253 100644
--- a/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java
+++ b/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java
@@ -23,9 +23,9 @@ public interface AccountRepository extends AutoCloseable {
void delete(Account account);
void archive(Account account);
- BigDecimal deriveBalance(long id, Instant timestamp);
- default BigDecimal deriveCurrentBalance(long id) {
- return deriveBalance(id, Instant.now(Clock.systemUTC()));
+ BigDecimal deriveBalance(long accountId, Instant timestamp);
+ default BigDecimal deriveCurrentBalance(long accountId) {
+ return deriveBalance(accountId, Instant.now(Clock.systemUTC()));
}
Set findAllUsedCurrencies();
}
diff --git a/src/main/java/com/andrewlalis/perfin/data/DataSource.java b/src/main/java/com/andrewlalis/perfin/data/DataSource.java
index c35366f..f2351e1 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.AccountType;
import com.andrewlalis.perfin.model.MoneyValue;
import javafx.application.Platform;
@@ -29,32 +30,32 @@ public interface DataSource {
Path getContentDir();
AccountRepository getAccountRepository();
+ BalanceRecordRepository getBalanceRecordRepository();
+ TransactionRepository getTransactionRepository();
+ AttachmentRepository getAttachmentRepository();
+ AccountHistoryItemRepository getAccountHistoryItemRepository();
+
default void useAccountRepository(ThrowableConsumer repoConsumer) {
DbUtil.useClosable(this::getAccountRepository, repoConsumer);
}
- BalanceRecordRepository getBalanceRecordRepository();
default void useBalanceRecordRepository(ThrowableConsumer repoConsumer) {
DbUtil.useClosable(this::getBalanceRecordRepository, repoConsumer);
}
- TransactionRepository getTransactionRepository();
default void useTransactionRepository(ThrowableConsumer repoConsumer) {
DbUtil.useClosable(this::getTransactionRepository, repoConsumer);
}
- AttachmentRepository getAttachmentRepository();
default void useAttachmentRepository(ThrowableConsumer repoConsumer) {
DbUtil.useClosable(this::getAttachmentRepository, repoConsumer);
}
- AccountHistoryItemRepository getAccountHistoryItemRepository();
-
// Utility methods:
default void getAccountBalanceText(Account account, Consumer balanceConsumer) {
Thread.ofVirtual().start(() -> useAccountRepository(repo -> {
- BigDecimal balance = repo.deriveCurrentBalance(account.getId());
+ BigDecimal balance = repo.deriveCurrentBalance(account.id);
MoneyValue money = new MoneyValue(balance, account.getCurrency());
Platform.runLater(() -> balanceConsumer.accept(CurrencyUtil.formatMoney(money)));
}));
@@ -66,7 +67,9 @@ public interface DataSource {
Map totals = new HashMap<>();
for (var account : accounts) {
BigDecimal currencyTotal = totals.computeIfAbsent(account.getCurrency(), c -> BigDecimal.ZERO);
- totals.put(account.getCurrency(), currencyTotal.add(accountRepo.deriveCurrentBalance(account.getId())));
+ BigDecimal accountBalance = accountRepo.deriveCurrentBalance(account.id);
+ if (account.getType() == AccountType.CREDIT_CARD) accountBalance = accountBalance.negate();
+ totals.put(account.getCurrency(), currencyTotal.add(accountBalance));
}
return totals;
} catch (Exception e) {
diff --git a/src/main/java/com/andrewlalis/perfin/data/EntityNotFoundException.java b/src/main/java/com/andrewlalis/perfin/data/EntityNotFoundException.java
new file mode 100644
index 0000000..45b5571
--- /dev/null
+++ b/src/main/java/com/andrewlalis/perfin/data/EntityNotFoundException.java
@@ -0,0 +1,11 @@
+package com.andrewlalis.perfin.data;
+
+/**
+ * Exception that's thrown when an entity of a certain type is not found
+ * when it was expected that it would be.
+ */
+public class EntityNotFoundException extends RuntimeException {
+ public EntityNotFoundException(Class> type, Object id) {
+ super("Entity of type " + type.getName() + " with id " + id + " was not found.");
+ }
+}
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 17cda4f..031895d 100644
--- a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountRepository.java
+++ b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountRepository.java
@@ -1,6 +1,7 @@
package com.andrewlalis.perfin.data.impl;
import com.andrewlalis.perfin.data.AccountRepository;
+import com.andrewlalis.perfin.data.EntityNotFoundException;
import com.andrewlalis.perfin.data.pagination.Page;
import com.andrewlalis.perfin.data.pagination.PageRequest;
import com.andrewlalis.perfin.data.util.DateUtil;
@@ -66,59 +67,53 @@ public record JdbcAccountRepository(Connection conn) implements AccountRepositor
}
@Override
- public BigDecimal deriveBalance(long id, Instant timestamp) {
+ public BigDecimal deriveBalance(long accountId, Instant timestamp) {
+ // First find the account itself, since its properties influence the balance.
+ Account account = findById(accountId).orElse(null);
+ if (account == null) throw new EntityNotFoundException(Account.class, accountId);
// Find the most recent balance record before timestamp.
Optional closestPastRecord = DbUtil.findOne(
conn,
"SELECT * FROM balance_record WHERE account_id = ? AND timestamp <= ? ORDER BY timestamp DESC LIMIT 1",
- List.of(id, DbUtil.timestampFromInstant(timestamp)),
+ List.of(accountId, DbUtil.timestampFromInstant(timestamp)),
JdbcBalanceRecordRepository::parse
);
if (closestPastRecord.isPresent()) {
// Then find any entries on the account since that balance record and the timestamp.
- List accountEntries = DbUtil.findAll(
+ List entriesAfterRecord = DbUtil.findAll(
conn,
"SELECT * FROM account_entry WHERE account_id = ? AND timestamp >= ? AND timestamp <= ? ORDER BY timestamp ASC",
List.of(
- id,
+ accountId,
DbUtil.timestampFromUtcLDT(closestPastRecord.get().getTimestamp()),
DbUtil.timestampFromInstant(timestamp)
),
JdbcAccountEntryRepository::parse
);
- // Apply all entries to the most recent known balance to obtain the balance at this point.
- BigDecimal currentBalance = closestPastRecord.get().getBalance();
- for (var entry : accountEntries) {
- currentBalance = currentBalance.add(entry.getSignedAmount());
- }
- return currentBalance;
+ return computeBalanceWithEntriesAfter(account, closestPastRecord.get(), entriesAfterRecord);
} else {
// There is no balance record present before the given timestamp. Try and find the closest one after.
Optional closestFutureRecord = DbUtil.findOne(
conn,
"SELECT * FROM balance_record WHERE account_id = ? AND timestamp >= ? ORDER BY timestamp ASC LIMIT 1",
- List.of(id, DbUtil.timestampFromInstant(timestamp)),
+ List.of(accountId, DbUtil.timestampFromInstant(timestamp)),
JdbcBalanceRecordRepository::parse
);
if (closestFutureRecord.isEmpty()) {
- throw new IllegalStateException("No balance record exists for account " + id);
+ throw new IllegalStateException("No balance record exists for account " + accountId);
}
// Now find any entries on the account from the timestamp until that balance record.
- List accountEntries = DbUtil.findAll(
+ List entriesBeforeRecord = DbUtil.findAll(
conn,
"SELECT * FROM account_entry WHERE account_id = ? AND timestamp <= ? AND timestamp >= ? ORDER BY timestamp DESC",
List.of(
- id,
+ accountId,
DbUtil.timestampFromUtcLDT(closestFutureRecord.get().getTimestamp()),
DbUtil.timestampFromInstant(timestamp)
),
JdbcAccountEntryRepository::parse
);
- BigDecimal currentBalance = closestFutureRecord.get().getBalance();
- for (var entry : accountEntries) {
- currentBalance = currentBalance.subtract(entry.getSignedAmount());
- }
- return currentBalance;
+ return computeBalanceWithEntriesBefore(account, closestFutureRecord.get(), entriesBeforeRecord);
}
}
@@ -141,19 +136,19 @@ public record JdbcAccountRepository(Connection conn) implements AccountRepositor
account.getAccountNumber(),
account.getCurrency().getCurrencyCode(),
account.getType().name(),
- account.getId()
+ account.id
)
);
}
@Override
public void delete(Account account) {
- DbUtil.updateOne(conn, "DELETE FROM account WHERE id = ?", List.of(account.getId()));
+ DbUtil.updateOne(conn, "DELETE FROM account WHERE id = ?", List.of(account.id));
}
@Override
public void archive(Account account) {
- DbUtil.updateOne(conn, "UPDATE account SET archived = TRUE WHERE id = ?", List.of(account.getId()));
+ DbUtil.updateOne(conn, "UPDATE account SET archived = TRUE WHERE id = ?", List.of(account.id));
}
public static Account parseAccount(ResultSet rs) throws SQLException {
@@ -171,4 +166,20 @@ public record JdbcAccountRepository(Connection conn) implements AccountRepositor
public void close() throws Exception {
conn.close();
}
+
+ private BigDecimal computeBalanceWithEntriesAfter(Account account, BalanceRecord balanceRecord, List entriesAfterRecord) {
+ BigDecimal balance = balanceRecord.getBalance();
+ for (AccountEntry entry : entriesAfterRecord) {
+ balance = balance.add(entry.getEffectiveValue(account.getType()));
+ }
+ return balance;
+ }
+
+ private BigDecimal computeBalanceWithEntriesBefore(Account account, BalanceRecord balanceRecord, List entriesBeforeRecord) {
+ BigDecimal balance = balanceRecord.getBalance();
+ for (AccountEntry entry : entriesBeforeRecord) {
+ balance = balance.subtract(entry.getEffectiveValue(account.getType()));
+ }
+ return balance;
+ }
}
diff --git a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcBalanceRecordRepository.java b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcBalanceRecordRepository.java
index 6ded9ba..4a69161 100644
--- a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcBalanceRecordRepository.java
+++ b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcBalanceRecordRepository.java
@@ -30,7 +30,7 @@ public record JdbcBalanceRecordRepository(Connection conn, Path contentDir) impl
try (var stmt = conn.prepareStatement("INSERT INTO balance_record_attachment(balance_record_id, attachment_id) VALUES (?, ?)")) {
for (var attachmentPath : attachments) {
Attachment attachment = attachmentRepo.insert(attachmentPath);
- DbUtil.setArgs(stmt, recordId, attachment.getId());
+ DbUtil.setArgs(stmt, recordId, attachment.id);
stmt.executeUpdate();
}
}
diff --git a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcTransactionRepository.java b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcTransactionRepository.java
index ed3ddd1..0bbe25e 100644
--- a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcTransactionRepository.java
+++ b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcTransactionRepository.java
@@ -36,15 +36,15 @@ public record JdbcTransactionRepository(Connection conn, Path contentDir) implem
);
// 2. Insert linked account entries.
AccountEntryRepository accountEntryRepository = new JdbcAccountEntryRepository(conn);
- linkedAccounts.ifDebit(acc -> accountEntryRepository.insert(utcTimestamp, acc.getId(), txId, amount, AccountEntry.Type.DEBIT, currency));
- linkedAccounts.ifCredit(acc -> accountEntryRepository.insert(utcTimestamp, acc.getId(), txId, amount, AccountEntry.Type.CREDIT, currency));
+ linkedAccounts.ifDebit(acc -> accountEntryRepository.insert(utcTimestamp, acc.id, txId, amount, AccountEntry.Type.DEBIT, currency));
+ linkedAccounts.ifCredit(acc -> accountEntryRepository.insert(utcTimestamp, acc.id, txId, amount, AccountEntry.Type.CREDIT, currency));
// 3. Add attachments.
AttachmentRepository attachmentRepo = new JdbcAttachmentRepository(conn, contentDir);
try (var stmt = conn.prepareStatement("INSERT INTO transaction_attachment (transaction_id, attachment_id) VALUES (?, ?)")) {
for (var attachmentPath : attachments) {
Attachment attachment = attachmentRepo.insert(attachmentPath);
// Insert the link-table entry.
- DbUtil.setArgs(stmt, txId, attachment.getId());
+ DbUtil.setArgs(stmt, txId, attachment.id);
stmt.executeUpdate();
}
}
diff --git a/src/main/java/com/andrewlalis/perfin/model/Account.java b/src/main/java/com/andrewlalis/perfin/model/Account.java
index f974117..83146a4 100644
--- a/src/main/java/com/andrewlalis/perfin/model/Account.java
+++ b/src/main/java/com/andrewlalis/perfin/model/Account.java
@@ -7,8 +7,7 @@ import java.util.Currency;
* The representation of a physical account of some sort (checking, savings,
* credit-card, etc.).
*/
-public class Account {
- private final long id;
+public class Account extends IdEntity {
private final LocalDateTime createdAt;
private final boolean archived;
@@ -18,7 +17,7 @@ public class Account {
private Currency currency;
public Account(long id, LocalDateTime createdAt, boolean archived, AccountType type, String accountNumber, String name, Currency currency) {
- this.id = id;
+ super(id);
this.createdAt = createdAt;
this.archived = archived;
this.type = type;
@@ -73,10 +72,6 @@ public class Account {
return createdAt;
}
- public long getId() {
- return id;
- }
-
public boolean isArchived() {
return archived;
}
diff --git a/src/main/java/com/andrewlalis/perfin/model/AccountEntry.java b/src/main/java/com/andrewlalis/perfin/model/AccountEntry.java
index 74d3001..80b0ac2 100644
--- a/src/main/java/com/andrewlalis/perfin/model/AccountEntry.java
+++ b/src/main/java/com/andrewlalis/perfin/model/AccountEntry.java
@@ -30,13 +30,12 @@ import java.util.Currency;
* all those extra accounts would be a burden to casual users.
*
*/
-public class AccountEntry {
+public class AccountEntry extends IdEntity {
public enum Type {
CREDIT,
DEBIT
}
- private final long id;
private final LocalDateTime timestamp;
private final long accountId;
private final long transactionId;
@@ -45,7 +44,7 @@ public class AccountEntry {
private final Currency currency;
public AccountEntry(long id, LocalDateTime timestamp, long accountId, long transactionId, BigDecimal amount, Type type, Currency currency) {
- this.id = id;
+ super(id);
this.timestamp = timestamp;
this.accountId = accountId;
this.transactionId = transactionId;
@@ -54,10 +53,6 @@ public class AccountEntry {
this.currency = currency;
}
- public long getId() {
- return id;
- }
-
public LocalDateTime getTimestamp() {
return timestamp;
}
@@ -82,11 +77,19 @@ public class AccountEntry {
return currency;
}
- public BigDecimal getSignedAmount() {
- return type == Type.DEBIT ? amount : amount.negate();
- }
-
public MoneyValue getMoneyValue() {
return new MoneyValue(amount, currency);
}
+
+ /**
+ * Gets the effective value of this entry for the account's type.
+ * @param accountType The type of the account.
+ * @return The effective value of this entry, either positive or negative.
+ */
+ public BigDecimal getEffectiveValue(AccountType accountType) {
+ return switch (accountType) {
+ case CHECKING, SAVINGS -> type == Type.DEBIT ? amount : amount.negate();
+ case CREDIT_CARD -> type == Type.DEBIT ? amount.negate() : amount;
+ };
+ }
}
diff --git a/src/main/java/com/andrewlalis/perfin/model/Attachment.java b/src/main/java/com/andrewlalis/perfin/model/Attachment.java
index 282e86e..774490f 100644
--- a/src/main/java/com/andrewlalis/perfin/model/Attachment.java
+++ b/src/main/java/com/andrewlalis/perfin/model/Attachment.java
@@ -11,25 +11,20 @@ import java.time.format.DateTimeFormatter;
* entity, like a receipt attached to a transaction, or a bank statement to an
* account balance record.
*/
-public class Attachment {
- private final long id;
+public class Attachment extends IdEntity {
private final LocalDateTime timestamp;
private final String identifier;
private final String filename;
private final String contentType;
public Attachment(long id, LocalDateTime timestamp, String identifier, String filename, String contentType) {
- this.id = id;
+ super(id);
this.timestamp = timestamp;
this.identifier = identifier;
this.filename = filename;
this.contentType = contentType;
}
- public long getId() {
- return id;
- }
-
public LocalDateTime getTimestamp() {
return timestamp;
}
diff --git a/src/main/java/com/andrewlalis/perfin/model/BalanceRecord.java b/src/main/java/com/andrewlalis/perfin/model/BalanceRecord.java
index ed3b6fe..38e4841 100644
--- a/src/main/java/com/andrewlalis/perfin/model/BalanceRecord.java
+++ b/src/main/java/com/andrewlalis/perfin/model/BalanceRecord.java
@@ -9,26 +9,20 @@ import java.util.Currency;
* used as a sanity check for ensuring that an account's entries add up to the
* correct balance.
*/
-public class BalanceRecord {
- private final long id;
+public class BalanceRecord extends IdEntity {
private final LocalDateTime timestamp;
-
private final long accountId;
private final BigDecimal balance;
private final Currency currency;
public BalanceRecord(long id, LocalDateTime timestamp, long accountId, BigDecimal balance, Currency currency) {
- this.id = id;
+ super(id);
this.timestamp = timestamp;
this.accountId = accountId;
this.balance = balance;
this.currency = currency;
}
- public long getId() {
- return id;
- }
-
public LocalDateTime getTimestamp() {
return timestamp;
}
diff --git a/src/main/java/com/andrewlalis/perfin/model/IdEntity.java b/src/main/java/com/andrewlalis/perfin/model/IdEntity.java
new file mode 100644
index 0000000..a39c8a7
--- /dev/null
+++ b/src/main/java/com/andrewlalis/perfin/model/IdEntity.java
@@ -0,0 +1,30 @@
+package com.andrewlalis.perfin.model;
+
+/**
+ * Base class for all entities that are identified by an id.
+ */
+public abstract class IdEntity {
+ /**
+ * The unique identifier for this entity. It distinguishes this entity from
+ * all others of its type.
+ */
+ public final long id;
+
+ /**
+ * Constructs the entity with a given id.
+ * @param id The id to use.
+ */
+ protected IdEntity(long id) {
+ this.id = id;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof IdEntity e && e.id == this.id;
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(id);
+ }
+}
diff --git a/src/main/java/com/andrewlalis/perfin/model/Transaction.java b/src/main/java/com/andrewlalis/perfin/model/Transaction.java
index 78627f8..2b451d9 100644
--- a/src/main/java/com/andrewlalis/perfin/model/Transaction.java
+++ b/src/main/java/com/andrewlalis/perfin/model/Transaction.java
@@ -10,26 +10,20 @@ import java.util.Currency;
* actual positive/negative effect is determined by the associated account
* entries that apply this transaction's amount to one or more accounts.
*/
-public class Transaction {
- private final long id;
+public class Transaction extends IdEntity {
private final LocalDateTime timestamp;
-
private final BigDecimal amount;
private final Currency currency;
private final String description;
public Transaction(long id, LocalDateTime timestamp, BigDecimal amount, Currency currency, String description) {
- this.id = id;
+ super(id);
this.timestamp = timestamp;
this.amount = amount;
this.currency = currency;
this.description = description;
}
- public long getId() {
- return id;
- }
-
public LocalDateTime getTimestamp() {
return timestamp;
}
@@ -49,9 +43,4 @@ public class Transaction {
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/AccountHistoryBalanceRecordTile.java b/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryBalanceRecordTile.java
index 11cb5fa..8d11d31 100644
--- a/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryBalanceRecordTile.java
+++ b/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryBalanceRecordTile.java
@@ -22,7 +22,7 @@ public class AccountHistoryBalanceRecordTile extends AccountHistoryItemTile {
}
Text amountText = new Text(CurrencyUtil.formatMoneyWithCurrencyPrefix(balanceRecord.getMoneyAmount()));
- var text = new TextFlow(new Text("Balance record #" + balanceRecord.getId() + " added with value of "), amountText);
+ var text = new TextFlow(new Text("Balance record #" + balanceRecord.id + " added with value of "), amountText);
setCenter(text);
Hyperlink deleteLink = new Hyperlink("Delete this balance record");
@@ -30,7 +30,7 @@ public class AccountHistoryBalanceRecordTile extends AccountHistoryItemTile {
boolean confirm = Popups.confirm("Are you sure you want to delete this balance record? It will be removed permanently, and cannot be undone.");
if (confirm) {
Profile.getCurrent().getDataSource().useBalanceRecordRepository(balanceRecordRepo -> {
- balanceRecordRepo.deleteById(balanceRecord.getId());
+ balanceRecordRepo.deleteById(balanceRecord.id);
Platform.runLater(controller::reloadHistory);
});
}
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 9978d0a..baf6fcb 100644
--- a/src/main/java/com/andrewlalis/perfin/view/component/TransactionTile.java
+++ b/src/main/java/com/andrewlalis/perfin/view/component/TransactionTile.java
@@ -107,7 +107,7 @@ public class TransactionTile extends BorderPane {
CompletableFuture cf = new CompletableFuture<>();
Thread.ofVirtual().start(() -> {
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
- CreditAndDebitAccounts accounts = repo.findLinkedAccounts(transaction.getId());
+ CreditAndDebitAccounts accounts = repo.findLinkedAccounts(transaction.id);
cf.complete(accounts);
});
});
diff --git a/src/main/resources/sql/migration/M1_AddBalanceRecordDeleted.sql b/src/main/resources/sql/migration/M1_AddBalanceRecordDeleted.sql
deleted file mode 100644
index 5740723..0000000
--- a/src/main/resources/sql/migration/M1_AddBalanceRecordDeleted.sql
+++ /dev/null
@@ -1,5 +0,0 @@
-ALTER TABLE balance_record
- ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE AFTER currency;
-
-ALTER TABLE account_entry
- ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE AFTER currency;
diff --git a/src/test/java/module-info.java b/src/test/java/module-info.java
deleted file mode 100644
index 1121bfd..0000000
--- a/src/test/java/module-info.java
+++ /dev/null
@@ -1,3 +0,0 @@
-module com.andrewlalis.perfin_test {
- requires org.junit.jupiter.api;
-}
\ No newline at end of file