Cleaned up entities, balance computation logic.

This commit is contained in:
Andrew Lalis 2024-01-04 09:54:06 -05:00
parent 087242396d
commit 4899d5e8b5
22 changed files with 135 additions and 107 deletions

View File

@ -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<AccountHistoryItem> historyItems = historyRepo.findMostRecentForAccount(
account.getId(),
account.id,
loadHistoryFrom,
historyLoadSize
);

View File

@ -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()

View File

@ -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;

View File

@ -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());
}
}

View File

@ -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<Attachment> attachments = repo.findAttachments(transaction.getId());
List<Attachment> 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");
});

View File

@ -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));
});

View File

@ -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<Currency> findAllUsedCurrencies();
}

View File

@ -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<AccountRepository> repoConsumer) {
DbUtil.useClosable(this::getAccountRepository, repoConsumer);
}
BalanceRecordRepository getBalanceRecordRepository();
default void useBalanceRecordRepository(ThrowableConsumer<BalanceRecordRepository> repoConsumer) {
DbUtil.useClosable(this::getBalanceRecordRepository, repoConsumer);
}
TransactionRepository getTransactionRepository();
default void useTransactionRepository(ThrowableConsumer<TransactionRepository> repoConsumer) {
DbUtil.useClosable(this::getTransactionRepository, repoConsumer);
}
AttachmentRepository getAttachmentRepository();
default void useAttachmentRepository(ThrowableConsumer<AttachmentRepository> repoConsumer) {
DbUtil.useClosable(this::getAttachmentRepository, repoConsumer);
}
AccountHistoryItemRepository getAccountHistoryItemRepository();
// Utility methods:
default void getAccountBalanceText(Account account, Consumer<String> 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<Currency, BigDecimal> 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) {

View File

@ -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.");
}
}

View File

@ -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<BalanceRecord> 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<AccountEntry> accountEntries = DbUtil.findAll(
List<AccountEntry> 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<BalanceRecord> 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<AccountEntry> accountEntries = DbUtil.findAll(
List<AccountEntry> 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<AccountEntry> 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<AccountEntry> entriesBeforeRecord) {
BigDecimal balance = balanceRecord.getBalance();
for (AccountEntry entry : entriesBeforeRecord) {
balance = balance.subtract(entry.getEffectiveValue(account.getType()));
}
return balance;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -30,13 +30,12 @@ import java.util.Currency;
* all those extra accounts would be a burden to casual users.
* </p>
*/
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;
};
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
});
}

View File

@ -107,7 +107,7 @@ public class TransactionTile extends BorderPane {
CompletableFuture<CreditAndDebitAccounts> 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);
});
});

View File

@ -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;

View File

@ -1,3 +0,0 @@
module com.andrewlalis.perfin_test {
requires org.junit.jupiter.api;
}