Refactored account history.
This commit is contained in:
		
							parent
							
								
									3493003588
								
							
						
					
					
						commit
						8f36380e21
					
				| 
						 | 
				
			
			@ -1,12 +1,12 @@
 | 
			
		|||
package com.andrewlalis.perfin.control;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.HistoryRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DateUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.Account;
 | 
			
		||||
import com.andrewlalis.perfin.model.Profile;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.AccountHistoryItem;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.HistoryItem;
 | 
			
		||||
import com.andrewlalis.perfin.view.component.AccountHistoryItemTile;
 | 
			
		||||
import javafx.application.Platform;
 | 
			
		||||
import javafx.beans.binding.BooleanExpression;
 | 
			
		||||
| 
						 | 
				
			
			@ -131,20 +131,15 @@ public class AccountViewController implements RouteSelectionListener {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @FXML public void loadMoreHistory() {
 | 
			
		||||
        Profile.getCurrent().dataSource().useRepoAsync(AccountHistoryItemRepository.class, repo -> {
 | 
			
		||||
            List<AccountHistoryItem> historyItems = repo.findMostRecentForAccount(
 | 
			
		||||
                    account.id,
 | 
			
		||||
                    loadHistoryFrom,
 | 
			
		||||
                    historyLoadSize
 | 
			
		||||
            );
 | 
			
		||||
            if (historyItems.size() < historyLoadSize) {
 | 
			
		||||
        Profile.getCurrent().dataSource().useRepoAsync(HistoryRepository.class, repo -> {
 | 
			
		||||
            long historyId = repo.getOrCreateHistoryForAccount(account.id);
 | 
			
		||||
            List<HistoryItem> items = repo.getNItemsBefore(historyId, historyLoadSize, loadHistoryFrom);
 | 
			
		||||
            if (items.size() < historyLoadSize) {
 | 
			
		||||
                Platform.runLater(() -> loadMoreHistoryButton.setDisable(true));
 | 
			
		||||
            } else {
 | 
			
		||||
                loadHistoryFrom = historyItems.getLast().getTimestamp();
 | 
			
		||||
                loadHistoryFrom = items.getLast().getTimestamp();
 | 
			
		||||
            }
 | 
			
		||||
            List<? extends Node> nodes = historyItems.stream()
 | 
			
		||||
                    .map(item -> AccountHistoryItemTile.forItem(item, repo, this))
 | 
			
		||||
                    .toList();
 | 
			
		||||
            List<? extends Node> nodes = items.stream().map(AccountHistoryItemTile::forItem).toList();
 | 
			
		||||
            Platform.runLater(() -> historyItemsVBox.getChildren().addAll(nodes));
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,25 +0,0 @@
 | 
			
		|||
package com.andrewlalis.perfin.data;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DateUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.AccountEntry;
 | 
			
		||||
import com.andrewlalis.perfin.model.BalanceRecord;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.AccountHistoryItem;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
public interface AccountHistoryItemRepository extends Repository, AutoCloseable {
 | 
			
		||||
    void recordAccountEntry(LocalDateTime timestamp, long accountId, long entryId);
 | 
			
		||||
    void recordBalanceRecord(LocalDateTime timestamp, long accountId, long recordId);
 | 
			
		||||
    void recordText(LocalDateTime timestamp, long accountId, String text);
 | 
			
		||||
    List<AccountHistoryItem> findMostRecentForAccount(long accountId, LocalDateTime utcTimestamp, int count);
 | 
			
		||||
    default Optional<AccountHistoryItem> getMostRecentForAccount(long accountId) {
 | 
			
		||||
        var items = findMostRecentForAccount(accountId, DateUtil.nowAsUTC(), 1);
 | 
			
		||||
        if (items.isEmpty()) return Optional.empty();
 | 
			
		||||
        return Optional.of(items.getFirst());
 | 
			
		||||
    }
 | 
			
		||||
    String getTextItem(long itemId);
 | 
			
		||||
    AccountEntry getAccountEntryItem(long itemId);
 | 
			
		||||
    BalanceRecord getBalanceRecordItem(long itemId);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -33,7 +33,7 @@ public interface DataSource {
 | 
			
		|||
    TransactionVendorRepository getTransactionVendorRepository();
 | 
			
		||||
    TransactionCategoryRepository getTransactionCategoryRepository();
 | 
			
		||||
    AttachmentRepository getAttachmentRepository();
 | 
			
		||||
    AccountHistoryItemRepository getAccountHistoryItemRepository();
 | 
			
		||||
    HistoryRepository getHistoryRepository();
 | 
			
		||||
 | 
			
		||||
    // Repository helper methods:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -86,7 +86,7 @@ public interface DataSource {
 | 
			
		|||
                TransactionVendorRepository.class, this::getTransactionVendorRepository,
 | 
			
		||||
                TransactionCategoryRepository.class, this::getTransactionCategoryRepository,
 | 
			
		||||
                AttachmentRepository.class, this::getAttachmentRepository,
 | 
			
		||||
                AccountHistoryItemRepository.class, this::getAccountHistoryItemRepository
 | 
			
		||||
                HistoryRepository.class, this::getHistoryRepository
 | 
			
		||||
        );
 | 
			
		||||
        return (Supplier<R>) repoSuppliers.get(type);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
package com.andrewlalis.perfin.data;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.data.pagination.Page;
 | 
			
		||||
import com.andrewlalis.perfin.data.pagination.PageRequest;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DateUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.HistoryItem;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.HistoryTextItem;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.time.ZoneOffset;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public interface HistoryRepository extends Repository, AutoCloseable {
 | 
			
		||||
    long getOrCreateHistoryForAccount(long accountId);
 | 
			
		||||
    long getOrCreateHistoryForTransaction(long transactionId);
 | 
			
		||||
    void deleteHistoryForAccount(long accountId);
 | 
			
		||||
    void deleteHistoryForTransaction(long transactionId);
 | 
			
		||||
 | 
			
		||||
    HistoryTextItem addTextItem(long historyId, LocalDateTime utcTimestamp, String description);
 | 
			
		||||
    default HistoryTextItem addTextItem(long historyId, String description) {
 | 
			
		||||
        return addTextItem(historyId, DateUtil.nowAsUTC(), description);
 | 
			
		||||
    }
 | 
			
		||||
    Page<HistoryItem> getItems(long historyId, PageRequest pagination);
 | 
			
		||||
    List<HistoryItem> getNItemsBefore(long historyId, int n, LocalDateTime timestamp);
 | 
			
		||||
    default List<HistoryItem> getNItemsBeforeNow(long historyId, int n) {
 | 
			
		||||
        return getNItemsBefore(historyId, n, LocalDateTime.now(ZoneOffset.UTC));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
package com.andrewlalis.perfin.data.impl;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountEntryRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.HistoryRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DbUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.AccountEntry;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -31,8 +31,9 @@ public record JdbcAccountEntryRepository(Connection conn) implements AccountEntr
 | 
			
		|||
                )
 | 
			
		||||
        );
 | 
			
		||||
        // Insert an entry into the account's history.
 | 
			
		||||
        AccountHistoryItemRepository historyRepo = new JdbcAccountHistoryItemRepository(conn);
 | 
			
		||||
        historyRepo.recordAccountEntry(timestamp, accountId, entryId);
 | 
			
		||||
        HistoryRepository historyRepo = new JdbcHistoryRepository(conn);
 | 
			
		||||
        long historyId = historyRepo.getOrCreateHistoryForAccount(accountId);
 | 
			
		||||
        historyRepo.addTextItem(historyId, timestamp, "Entry #" + entryId + " added as a " + type.name() + " from Transaction #" + transactionId + ".");
 | 
			
		||||
        return entryId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,120 +0,0 @@
 | 
			
		|||
package com.andrewlalis.perfin.data.impl;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DbUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.AccountEntry;
 | 
			
		||||
import com.andrewlalis.perfin.model.BalanceRecord;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.AccountHistoryItem;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.AccountHistoryItemType;
 | 
			
		||||
 | 
			
		||||
import java.sql.Connection;
 | 
			
		||||
import java.sql.ResultSet;
 | 
			
		||||
import java.sql.SQLException;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public record JdbcAccountHistoryItemRepository(Connection conn) implements AccountHistoryItemRepository {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void recordAccountEntry(LocalDateTime timestamp, long accountId, long entryId) {
 | 
			
		||||
        long itemId = insertHistoryItem(timestamp, accountId, AccountHistoryItemType.ACCOUNT_ENTRY);
 | 
			
		||||
        DbUtil.insertOne(
 | 
			
		||||
                conn,
 | 
			
		||||
                "INSERT INTO account_history_item_account_entry (item_id, entry_id) VALUES (?, ?)",
 | 
			
		||||
                List.of(itemId, entryId)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void recordBalanceRecord(LocalDateTime timestamp, long accountId, long recordId) {
 | 
			
		||||
        long itemId = insertHistoryItem(timestamp, accountId, AccountHistoryItemType.BALANCE_RECORD);
 | 
			
		||||
        DbUtil.insertOne(
 | 
			
		||||
                conn,
 | 
			
		||||
                "INSERT INTO account_history_item_balance_record (item_id, record_id) VALUES (?, ?)",
 | 
			
		||||
                List.of(itemId, recordId)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void recordText(LocalDateTime timestamp, long accountId, String text) {
 | 
			
		||||
        long itemId = insertHistoryItem(timestamp, accountId, AccountHistoryItemType.TEXT);
 | 
			
		||||
        DbUtil.insertOne(
 | 
			
		||||
                conn,
 | 
			
		||||
                "INSERT INTO account_history_item_text (item_id, description) VALUES (?, ?)",
 | 
			
		||||
                List.of(itemId, text)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<AccountHistoryItem> findMostRecentForAccount(long accountId, LocalDateTime utcTimestamp, int count) {
 | 
			
		||||
        return DbUtil.findAll(
 | 
			
		||||
                conn,
 | 
			
		||||
                "SELECT * FROM account_history_item WHERE account_id = ? AND timestamp < ? ORDER BY timestamp DESC LIMIT " + count,
 | 
			
		||||
                List.of(accountId, DbUtil.timestampFromUtcLDT(utcTimestamp)),
 | 
			
		||||
                JdbcAccountHistoryItemRepository::parseHistoryItem
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getTextItem(long itemId) {
 | 
			
		||||
        return DbUtil.findOne(
 | 
			
		||||
                conn,
 | 
			
		||||
                "SELECT description FROM account_history_item_text WHERE item_id = ?",
 | 
			
		||||
                List.of(itemId),
 | 
			
		||||
                rs -> rs.getString(1)
 | 
			
		||||
        ).orElse(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public AccountEntry getAccountEntryItem(long itemId) {
 | 
			
		||||
        return DbUtil.findOne(
 | 
			
		||||
                conn,
 | 
			
		||||
                """
 | 
			
		||||
                        SELECT *
 | 
			
		||||
                        FROM account_entry
 | 
			
		||||
                        LEFT JOIN account_history_item_account_entry h ON h.entry_id = account_entry.id
 | 
			
		||||
                        WHERE h.item_id = ?""",
 | 
			
		||||
                List.of(itemId),
 | 
			
		||||
                JdbcAccountEntryRepository::parse
 | 
			
		||||
        ).orElse(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public BalanceRecord getBalanceRecordItem(long itemId) {
 | 
			
		||||
        return DbUtil.findOne(
 | 
			
		||||
                conn,
 | 
			
		||||
                """
 | 
			
		||||
                        SELECT *
 | 
			
		||||
                        FROM balance_record
 | 
			
		||||
                        LEFT JOIN account_history_item_balance_record h ON h.record_id = balance_record.id
 | 
			
		||||
                        WHERE h.item_id = ?""",
 | 
			
		||||
                List.of(itemId),
 | 
			
		||||
                JdbcBalanceRecordRepository::parse
 | 
			
		||||
        ).orElse(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void close() throws Exception {
 | 
			
		||||
        conn.close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static AccountHistoryItem parseHistoryItem(ResultSet rs) throws SQLException {
 | 
			
		||||
        return new AccountHistoryItem(
 | 
			
		||||
                rs.getLong("id"),
 | 
			
		||||
                DbUtil.utcLDTFromTimestamp(rs.getTimestamp("timestamp")),
 | 
			
		||||
                rs.getLong("account_id"),
 | 
			
		||||
                AccountHistoryItemType.valueOf(rs.getString("type"))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private long insertHistoryItem(LocalDateTime timestamp, long accountId, AccountHistoryItemType type) {
 | 
			
		||||
        return DbUtil.insertOne(
 | 
			
		||||
                conn,
 | 
			
		||||
                "INSERT INTO account_history_item (timestamp, account_id, type) VALUES (?, ?, ?)",
 | 
			
		||||
                List.of(
 | 
			
		||||
                        DbUtil.timestampFromUtcLDT(timestamp),
 | 
			
		||||
                        accountId,
 | 
			
		||||
                        type.name()
 | 
			
		||||
                )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +1,8 @@
 | 
			
		|||
package com.andrewlalis.perfin.data.impl;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountEntryRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.BalanceRecordRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.EntityNotFoundException;
 | 
			
		||||
import com.andrewlalis.perfin.data.*;
 | 
			
		||||
import com.andrewlalis.perfin.data.pagination.Page;
 | 
			
		||||
import com.andrewlalis.perfin.data.pagination.PageRequest;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DateUtil;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DbUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.Account;
 | 
			
		||||
import com.andrewlalis.perfin.model.AccountEntry;
 | 
			
		||||
| 
						 | 
				
			
			@ -43,8 +39,9 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
 | 
			
		|||
                    )
 | 
			
		||||
            );
 | 
			
		||||
            // Insert a history item indicating the creation of the account.
 | 
			
		||||
            var historyRepo = new JdbcAccountHistoryItemRepository(conn);
 | 
			
		||||
            historyRepo.recordText(DateUtil.nowAsUTC(), accountId, "Account added to your Perfin profile.");
 | 
			
		||||
            HistoryRepository historyRepo = new JdbcHistoryRepository(conn);
 | 
			
		||||
            long historyId = historyRepo.getOrCreateHistoryForAccount(accountId);
 | 
			
		||||
            historyRepo.addTextItem(historyId, "Account added to your Perfin profile.");
 | 
			
		||||
            return accountId;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -59,11 +56,12 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
 | 
			
		|||
        return DbUtil.findAll(
 | 
			
		||||
                conn,
 | 
			
		||||
                """
 | 
			
		||||
                SELECT DISTINCT ON (account.id) account.*, ahi.timestamp AS _
 | 
			
		||||
                SELECT DISTINCT ON (account.id) account.*, hi.timestamp AS _
 | 
			
		||||
                FROM account
 | 
			
		||||
                LEFT OUTER JOIN account_history_item ahi ON ahi.account_id = account.id
 | 
			
		||||
                LEFT OUTER JOIN history_account ha ON ha.account_id = account.id
 | 
			
		||||
                LEFT OUTER JOIN history_item hi ON hi.history_id = ha.history_id
 | 
			
		||||
                WHERE NOT account.archived
 | 
			
		||||
                ORDER BY ahi.timestamp DESC, account.created_at DESC""",
 | 
			
		||||
                ORDER BY hi.timestamp DESC, account.created_at DESC""",
 | 
			
		||||
                JdbcAccountRepository::parseAccount
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +158,9 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
 | 
			
		|||
    public void archive(long accountId) {
 | 
			
		||||
        DbUtil.doTransaction(conn, () -> {
 | 
			
		||||
            DbUtil.updateOne(conn, "UPDATE account SET archived = TRUE WHERE id = ?", List.of(accountId));
 | 
			
		||||
            new JdbcAccountHistoryItemRepository(conn).recordText(DateUtil.nowAsUTC(), accountId, "Account has been archived.");
 | 
			
		||||
            HistoryRepository historyRepo = new JdbcHistoryRepository(conn);
 | 
			
		||||
            long historyId = historyRepo.getOrCreateHistoryForAccount(accountId);
 | 
			
		||||
            historyRepo.addTextItem(historyId, "Account has been archived.");
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -168,7 +168,9 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
 | 
			
		|||
    public void unarchive(long accountId) {
 | 
			
		||||
        DbUtil.doTransaction(conn, () -> {
 | 
			
		||||
            DbUtil.updateOne(conn, "UPDATE account SET archived = FALSE WHERE id = ?", List.of(accountId));
 | 
			
		||||
            new JdbcAccountHistoryItemRepository(conn).recordText(DateUtil.nowAsUTC(), accountId, "Account has been unarchived.");
 | 
			
		||||
            HistoryRepository historyRepo = new JdbcHistoryRepository(conn);
 | 
			
		||||
            long historyId = historyRepo.getOrCreateHistoryForAccount(accountId);
 | 
			
		||||
            historyRepo.addTextItem(historyId, "Account has been unarchived.");
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,13 @@
 | 
			
		|||
package com.andrewlalis.perfin.data.impl;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.AttachmentRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.BalanceRecordRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.HistoryRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.CurrencyUtil;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DbUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.Attachment;
 | 
			
		||||
import com.andrewlalis.perfin.model.BalanceRecord;
 | 
			
		||||
import com.andrewlalis.perfin.model.MoneyValue;
 | 
			
		||||
 | 
			
		||||
import java.math.BigDecimal;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,8 +38,9 @@ public record JdbcBalanceRecordRepository(Connection conn, Path contentDir) impl
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // Add a history item entry.
 | 
			
		||||
            AccountHistoryItemRepository historyRepo = new JdbcAccountHistoryItemRepository(conn);
 | 
			
		||||
            historyRepo.recordBalanceRecord(utcTimestamp, accountId, recordId);
 | 
			
		||||
            HistoryRepository historyRepo = new JdbcHistoryRepository(conn);
 | 
			
		||||
            long historyId = historyRepo.getOrCreateHistoryForAccount(accountId);
 | 
			
		||||
            historyRepo.addTextItem(historyId, utcTimestamp, "Balance Record #" + recordId + " added with a value of " + CurrencyUtil.formatMoneyWithCurrencyPrefix(new MoneyValue(balance, currency)));
 | 
			
		||||
            return recordId;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -65,7 +65,7 @@ public class JdbcDataSource implements DataSource {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public AccountHistoryItemRepository getAccountHistoryItemRepository() {
 | 
			
		||||
        return new JdbcAccountHistoryItemRepository(getConnection());
 | 
			
		||||
    public HistoryRepository getHistoryRepository() {
 | 
			
		||||
        return new JdbcHistoryRepository(getConnection());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,7 +35,7 @@ public class JdbcDataSourceFactory implements DataSourceFactory {
 | 
			
		|||
     * the profile has a newer schema version, we'll exit and prompt the user
 | 
			
		||||
     * to update their app.
 | 
			
		||||
     */
 | 
			
		||||
    public static final int SCHEMA_VERSION = 2;
 | 
			
		||||
    public static final int SCHEMA_VERSION = 3;
 | 
			
		||||
 | 
			
		||||
    public DataSource getDataSource(String profileName) throws ProfileLoadException {
 | 
			
		||||
        final boolean dbExists = Files.exists(getDatabaseFile(profileName));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,125 @@
 | 
			
		|||
package com.andrewlalis.perfin.data.impl;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.data.HistoryRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.pagination.Page;
 | 
			
		||||
import com.andrewlalis.perfin.data.pagination.PageRequest;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DateUtil;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DbUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.HistoryItem;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.HistoryTextItem;
 | 
			
		||||
 | 
			
		||||
import java.sql.Connection;
 | 
			
		||||
import java.sql.ResultSet;
 | 
			
		||||
import java.sql.SQLException;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public record JdbcHistoryRepository(Connection conn) implements HistoryRepository {
 | 
			
		||||
    @Override
 | 
			
		||||
    public long getOrCreateHistoryForAccount(long accountId) {
 | 
			
		||||
        return getOrCreateHistoryForEntity(accountId, "history_account", "account_id");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public long getOrCreateHistoryForTransaction(long transactionId) {
 | 
			
		||||
        return getOrCreateHistoryForEntity(transactionId, "history_transaction", "transaction_id");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private long getOrCreateHistoryForEntity(long entityId, String joinTableName, String joinColumn) {
 | 
			
		||||
        String selectQuery = "SELECT history_id FROM " + joinTableName + " WHERE " + joinColumn + " = ?";
 | 
			
		||||
        var optionalHistoryId = DbUtil.findById(conn, selectQuery, entityId, rs -> rs.getLong(1));
 | 
			
		||||
        if (optionalHistoryId.isPresent()) return optionalHistoryId.get();
 | 
			
		||||
        long historyId = DbUtil.insertOne(conn, "INSERT INTO history () VALUES ()");
 | 
			
		||||
        String insertQuery = "INSERT INTO " + joinTableName + " (" + joinColumn + ", history_id) VALUES (?, ?)";
 | 
			
		||||
        DbUtil.updateOne(conn, insertQuery, entityId, historyId);
 | 
			
		||||
        return historyId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void deleteHistoryForAccount(long accountId) {
 | 
			
		||||
        deleteHistoryForEntity(accountId, "history_account", "account_id");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void deleteHistoryForTransaction(long transactionId) {
 | 
			
		||||
        deleteHistoryForEntity(transactionId, "history_transaction", "transaction_id");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void deleteHistoryForEntity(long entityId, String joinTableName, String joinColumn) {
 | 
			
		||||
        String selectQuery = "SELECT history_id FROM " + joinTableName + " WHERE " + joinColumn + " = ?";
 | 
			
		||||
        var optionalHistoryId = DbUtil.findById(conn, selectQuery, entityId, rs -> rs.getLong(1));
 | 
			
		||||
        if (optionalHistoryId.isPresent()) {
 | 
			
		||||
            long historyId = optionalHistoryId.get();
 | 
			
		||||
            DbUtil.updateOne(conn, "DELETE FROM history WHERE id = ?", historyId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public HistoryTextItem addTextItem(long historyId, LocalDateTime utcTimestamp, String description) {
 | 
			
		||||
        long itemId = insertHistoryItem(historyId, utcTimestamp, HistoryItem.TYPE_TEXT);
 | 
			
		||||
        DbUtil.updateOne(
 | 
			
		||||
                conn,
 | 
			
		||||
                "INSERT INTO history_item_text (id, description) VALUES (?, ?)",
 | 
			
		||||
                itemId,
 | 
			
		||||
                description
 | 
			
		||||
        );
 | 
			
		||||
        return new HistoryTextItem(itemId, historyId, utcTimestamp, description);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private long insertHistoryItem(long historyId, LocalDateTime timestamp, String type) {
 | 
			
		||||
        return DbUtil.insertOne(
 | 
			
		||||
                conn,
 | 
			
		||||
                "INSERT INTO history_item (history_id, timestamp, type) VALUES (?, ?, ?)",
 | 
			
		||||
                historyId,
 | 
			
		||||
                DbUtil.timestampFromUtcLDT(timestamp),
 | 
			
		||||
                type
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Page<HistoryItem> getItems(long historyId, PageRequest pagination) {
 | 
			
		||||
        return DbUtil.findAll(
 | 
			
		||||
                conn,
 | 
			
		||||
                "SELECT * FROM history_item WHERE history_id = ?",
 | 
			
		||||
                pagination,
 | 
			
		||||
                List.of(historyId),
 | 
			
		||||
                JdbcHistoryRepository::parseItem
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<HistoryItem> getNItemsBefore(long historyId, int n, LocalDateTime timestamp) {
 | 
			
		||||
        return DbUtil.findAll(
 | 
			
		||||
                conn,
 | 
			
		||||
                """
 | 
			
		||||
                        SELECT *
 | 
			
		||||
                        FROM history_item
 | 
			
		||||
                        WHERE history_id = ? AND timestamp <= ?
 | 
			
		||||
                        ORDER BY timestamp DESC""",
 | 
			
		||||
                List.of(historyId, DbUtil.timestampFromUtcLDT(timestamp)),
 | 
			
		||||
                JdbcHistoryRepository::parseItem
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void close() throws Exception {
 | 
			
		||||
        conn.close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static HistoryItem parseItem(ResultSet rs) throws SQLException {
 | 
			
		||||
        long id = rs.getLong(1);
 | 
			
		||||
        long historyId = rs.getLong(2);
 | 
			
		||||
        LocalDateTime timestamp = DbUtil.utcLDTFromTimestamp(rs.getTimestamp(3));
 | 
			
		||||
        String type = rs.getString(4);
 | 
			
		||||
        if (type.equalsIgnoreCase(HistoryItem.TYPE_TEXT)) {
 | 
			
		||||
            String description = DbUtil.findOne(
 | 
			
		||||
                    rs.getStatement().getConnection(),
 | 
			
		||||
                    "SELECT description FROM history_item_text WHERE id = ?",
 | 
			
		||||
                    List.of(id),
 | 
			
		||||
                    r -> r.getString(1)
 | 
			
		||||
            ).orElseThrow();
 | 
			
		||||
            return new HistoryTextItem(id, historyId, timestamp, description);
 | 
			
		||||
        }
 | 
			
		||||
        throw new SQLException("Unknown history item type: " + type);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ package com.andrewlalis.perfin.data.impl;
 | 
			
		|||
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountEntryRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.AttachmentRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.HistoryRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.TransactionRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.pagination.Page;
 | 
			
		||||
import com.andrewlalis.perfin.data.pagination.PageRequest;
 | 
			
		||||
| 
						 | 
				
			
			@ -386,9 +387,9 @@ public record JdbcTransactionRepository(Connection conn, Path contentDir) implem
 | 
			
		|||
 | 
			
		||||
            // Add a text history item to any linked accounts detailing the changes.
 | 
			
		||||
            String updateMessageStr = "Transaction #" + tx.id + " was updated:\n" + String.join("\n", updateMessages);
 | 
			
		||||
            var historyRepo = new JdbcAccountHistoryItemRepository(conn);
 | 
			
		||||
            linkedAccounts.ifCredit(acc -> historyRepo.recordText(DateUtil.nowAsUTC(), acc.id, updateMessageStr));
 | 
			
		||||
            linkedAccounts.ifDebit(acc -> historyRepo.recordText(DateUtil.nowAsUTC(), acc.id, updateMessageStr));
 | 
			
		||||
            HistoryRepository historyRepo = new JdbcHistoryRepository(conn);
 | 
			
		||||
            long historyId = historyRepo.getOrCreateHistoryForTransaction(id);
 | 
			
		||||
            historyRepo.addTextItem(historyId, updateMessageStr);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@ public class Migrations {
 | 
			
		|||
    public static Map<Integer, Migration> getMigrations() {
 | 
			
		||||
        final Map<Integer, Migration> migrations = new HashMap<>();
 | 
			
		||||
        migrations.put(1, new PlainSQLMigration("/sql/migration/M001_AddTransactionProperties.sql"));
 | 
			
		||||
        migrations.put(2, new PlainSQLMigration("/sql/migration/M002_RefactorHistories.sql"));
 | 
			
		||||
        return migrations;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,36 +0,0 @@
 | 
			
		|||
package com.andrewlalis.perfin.model.history;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.model.IdEntity;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The base class representing account history items, a read-only record of an
 | 
			
		||||
 * account's data and changes over time. The type of history item determines
 | 
			
		||||
 * what exactly it means, and could be something like an account entry, balance
 | 
			
		||||
 * record, or modifications to the account's properties.
 | 
			
		||||
 */
 | 
			
		||||
public class AccountHistoryItem extends IdEntity {
 | 
			
		||||
    private final LocalDateTime timestamp;
 | 
			
		||||
    private final long accountId;
 | 
			
		||||
    private final AccountHistoryItemType type;
 | 
			
		||||
 | 
			
		||||
    public AccountHistoryItem(long id, LocalDateTime timestamp, long accountId, AccountHistoryItemType type) {
 | 
			
		||||
        super(id);
 | 
			
		||||
        this.timestamp = timestamp;
 | 
			
		||||
        this.accountId = accountId;
 | 
			
		||||
        this.type = type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public LocalDateTime getTimestamp() {
 | 
			
		||||
        return timestamp;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getAccountId() {
 | 
			
		||||
        return accountId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public AccountHistoryItemType getType() {
 | 
			
		||||
        return type;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +0,0 @@
 | 
			
		|||
package com.andrewlalis.perfin.model.history;
 | 
			
		||||
 | 
			
		||||
public enum AccountHistoryItemType {
 | 
			
		||||
    TEXT,
 | 
			
		||||
    ACCOUNT_ENTRY,
 | 
			
		||||
    BALANCE_RECORD
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
package com.andrewlalis.perfin.model.history;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.model.IdEntity;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a single polymorphic history item. The item's "type" attribute
 | 
			
		||||
 * tells where to find additional type-specific data.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class HistoryItem extends IdEntity {
 | 
			
		||||
    public static final String TYPE_TEXT = "TEXT";
 | 
			
		||||
 | 
			
		||||
    private final long historyId;
 | 
			
		||||
    private final LocalDateTime timestamp;
 | 
			
		||||
    private final String type;
 | 
			
		||||
 | 
			
		||||
    public HistoryItem(long id, long historyId, LocalDateTime timestamp, String type) {
 | 
			
		||||
        super(id);
 | 
			
		||||
        this.historyId = historyId;
 | 
			
		||||
        this.timestamp = timestamp;
 | 
			
		||||
        this.type = type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getHistoryId() {
 | 
			
		||||
        return historyId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public LocalDateTime getTimestamp() {
 | 
			
		||||
        return timestamp;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getType() {
 | 
			
		||||
        return type;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
package com.andrewlalis.perfin.model.history;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
 | 
			
		||||
public class HistoryTextItem extends HistoryItem {
 | 
			
		||||
    private final String description;
 | 
			
		||||
 | 
			
		||||
    public HistoryTextItem(long id, long historyId, LocalDateTime timestamp, String description) {
 | 
			
		||||
        super(id, historyId, timestamp, HistoryItem.TYPE_TEXT);
 | 
			
		||||
        this.description = description;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getDescription() {
 | 
			
		||||
        return description;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,36 +0,0 @@
 | 
			
		|||
package com.andrewlalis.perfin.view.component;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.control.TransactionsViewController;
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.CurrencyUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.AccountEntry;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.AccountHistoryItem;
 | 
			
		||||
import javafx.scene.control.Hyperlink;
 | 
			
		||||
import javafx.scene.text.Text;
 | 
			
		||||
import javafx.scene.text.TextFlow;
 | 
			
		||||
 | 
			
		||||
import static com.andrewlalis.perfin.PerfinApp.router;
 | 
			
		||||
 | 
			
		||||
public class AccountHistoryAccountEntryTile extends AccountHistoryItemTile {
 | 
			
		||||
    public AccountHistoryAccountEntryTile(AccountHistoryItem item, AccountHistoryItemRepository repo) {
 | 
			
		||||
        super(item);
 | 
			
		||||
        AccountEntry entry = repo.getAccountEntryItem(item.id);
 | 
			
		||||
        if (entry == null) {
 | 
			
		||||
            setCenter(new TextFlow(new Text("Deleted account entry because of deleted transaction.")));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Text amountText = new Text(CurrencyUtil.formatMoneyWithCurrencyPrefix(entry.getMoneyValue()));
 | 
			
		||||
        Hyperlink transactionLink = new Hyperlink("Transaction #" + entry.getTransactionId());
 | 
			
		||||
        transactionLink.setOnAction(event -> router.navigate(
 | 
			
		||||
                "transactions",
 | 
			
		||||
                new TransactionsViewController.RouteContext(entry.getTransactionId())
 | 
			
		||||
        ));
 | 
			
		||||
        var text = new TextFlow(
 | 
			
		||||
                transactionLink,
 | 
			
		||||
                new Text("posted as a " + entry.getType().name().toLowerCase() + " to this account, with a value of "),
 | 
			
		||||
                amountText
 | 
			
		||||
        );
 | 
			
		||||
        setCenter(text);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,31 +0,0 @@
 | 
			
		|||
package com.andrewlalis.perfin.view.component;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.control.AccountViewController;
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.CurrencyUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.BalanceRecord;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.AccountHistoryItem;
 | 
			
		||||
import javafx.scene.control.Hyperlink;
 | 
			
		||||
import javafx.scene.text.Text;
 | 
			
		||||
import javafx.scene.text.TextFlow;
 | 
			
		||||
 | 
			
		||||
import static com.andrewlalis.perfin.PerfinApp.router;
 | 
			
		||||
 | 
			
		||||
public class AccountHistoryBalanceRecordTile extends AccountHistoryItemTile {
 | 
			
		||||
    public AccountHistoryBalanceRecordTile(AccountHistoryItem item, AccountHistoryItemRepository repo, AccountViewController controller) {
 | 
			
		||||
        super(item);
 | 
			
		||||
        BalanceRecord balanceRecord = repo.getBalanceRecordItem(item.id);
 | 
			
		||||
        if (balanceRecord == null) {
 | 
			
		||||
            setCenter(new TextFlow(new Text("Deleted balance record was added.")));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Text amountText = new Text(CurrencyUtil.formatMoneyWithCurrencyPrefix(balanceRecord.getMoneyAmount()));
 | 
			
		||||
        var text = new TextFlow(new Text("Balance record #" + balanceRecord.id + " added with value of "), amountText);
 | 
			
		||||
        setCenter(text);
 | 
			
		||||
 | 
			
		||||
        Hyperlink viewLink = new Hyperlink("View this balance record");
 | 
			
		||||
        viewLink.setOnAction(event -> router.navigate("balance-record", balanceRecord));
 | 
			
		||||
        setBottom(viewLink);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +1,8 @@
 | 
			
		|||
package com.andrewlalis.perfin.view.component;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.control.AccountViewController;
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DateUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.AccountHistoryItem;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.HistoryItem;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.HistoryTextItem;
 | 
			
		||||
import javafx.scene.control.Label;
 | 
			
		||||
import javafx.scene.layout.BorderPane;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +10,7 @@ import javafx.scene.layout.BorderPane;
 | 
			
		|||
 * A tile that shows a brief bit of information about an account history item.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class AccountHistoryItemTile extends BorderPane {
 | 
			
		||||
    public AccountHistoryItemTile(AccountHistoryItem item) {
 | 
			
		||||
    public AccountHistoryItemTile(HistoryItem item) {
 | 
			
		||||
        getStyleClass().add("tile");
 | 
			
		||||
 | 
			
		||||
        Label timestampLabel = new Label(DateUtil.formatUTCAsLocalWithZone(item.getTimestamp()));
 | 
			
		||||
| 
						 | 
				
			
			@ -20,14 +19,11 @@ public abstract class AccountHistoryItemTile extends BorderPane {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public static AccountHistoryItemTile forItem(
 | 
			
		||||
            AccountHistoryItem item,
 | 
			
		||||
            AccountHistoryItemRepository repo,
 | 
			
		||||
            AccountViewController controller
 | 
			
		||||
            HistoryItem item
 | 
			
		||||
    ) {
 | 
			
		||||
        return switch (item.getType()) {
 | 
			
		||||
            case TEXT -> new AccountHistoryTextTile(item, repo);
 | 
			
		||||
            case ACCOUNT_ENTRY -> new AccountHistoryAccountEntryTile(item, repo);
 | 
			
		||||
            case BALANCE_RECORD -> new AccountHistoryBalanceRecordTile(item, repo, controller);
 | 
			
		||||
        };
 | 
			
		||||
        if (item instanceof HistoryTextItem t) {
 | 
			
		||||
            return new AccountHistoryTextTile(t);
 | 
			
		||||
        }
 | 
			
		||||
        throw new RuntimeException("Unsupported history item type: " + item.getType());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,14 +1,12 @@
 | 
			
		|||
package com.andrewlalis.perfin.view.component;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.AccountHistoryItem;
 | 
			
		||||
import com.andrewlalis.perfin.model.history.HistoryTextItem;
 | 
			
		||||
import javafx.scene.text.Text;
 | 
			
		||||
import javafx.scene.text.TextFlow;
 | 
			
		||||
 | 
			
		||||
public class AccountHistoryTextTile extends AccountHistoryItemTile {
 | 
			
		||||
    public AccountHistoryTextTile(AccountHistoryItem item, AccountHistoryItemRepository repo) {
 | 
			
		||||
    public AccountHistoryTextTile(HistoryTextItem item) {
 | 
			
		||||
        super(item);
 | 
			
		||||
        String text = repo.getTextItem(item.id);
 | 
			
		||||
        setCenter(new TextFlow(new Text(text)));
 | 
			
		||||
        setCenter(new TextFlow(new Text(item.getDescription())));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,4 +19,5 @@ module com.andrewlalis.perfin {
 | 
			
		|||
    opens com.andrewlalis.perfin.view to javafx.fxml;
 | 
			
		||||
    opens com.andrewlalis.perfin.view.component to javafx.fxml;
 | 
			
		||||
    opens com.andrewlalis.perfin.view.component.validation to javafx.fxml;
 | 
			
		||||
    exports com.andrewlalis.perfin.model.history to javafx.graphics;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,61 @@
 | 
			
		|||
/*
 | 
			
		||||
Migration to clean up history entities so that they are easier to work with, and
 | 
			
		||||
less prone to errors.
 | 
			
		||||
 | 
			
		||||
- Removes existing account history items.
 | 
			
		||||
- Adds a generic history table and history items that are linked to a history.
 | 
			
		||||
- Adds history links to accounts and transactions.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
CREATE TABLE history (
 | 
			
		||||
    id BIGINT PRIMARY KEY AUTO_INCREMENT
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE history_item (
 | 
			
		||||
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
 | 
			
		||||
    history_id BIGINT NOT NULL,
 | 
			
		||||
    timestamp TIMESTAMP NOT NULL,
 | 
			
		||||
    type VARCHAR(63) NOT NULL,
 | 
			
		||||
    CONSTRAINT fk_history_item_history
 | 
			
		||||
        FOREIGN KEY (history_id) REFERENCES history(id)
 | 
			
		||||
            ON UPDATE CASCADE ON DELETE CASCADE
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE history_item_text (
 | 
			
		||||
    id BIGINT NOT NULL,
 | 
			
		||||
    description VARCHAR(255) NOT NULL,
 | 
			
		||||
    CONSTRAINT fk_history_item_text_pk
 | 
			
		||||
        FOREIGN KEY (id) REFERENCES history_item(id)
 | 
			
		||||
            ON UPDATE CASCADE ON DELETE CASCADE
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE history_account (
 | 
			
		||||
    account_id BIGINT NOT NULL,
 | 
			
		||||
    history_id BIGINT NOT NULL,
 | 
			
		||||
    PRIMARY KEY (account_id, history_id),
 | 
			
		||||
    CONSTRAINT fk_history_account_account
 | 
			
		||||
        FOREIGN KEY (account_id) REFERENCES account(id)
 | 
			
		||||
            ON UPDATE CASCADE ON DELETE CASCADE,
 | 
			
		||||
    CONSTRAINT fk_history_account_history
 | 
			
		||||
        FOREIGN KEY (history_id) REFERENCES history(id)
 | 
			
		||||
            ON UPDATE CASCADE ON DELETE CASCADE
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE history_transaction (
 | 
			
		||||
    transaction_id BIGINT NOT NULL,
 | 
			
		||||
    history_id BIGINT NOT NULL,
 | 
			
		||||
    PRIMARY KEY (transaction_id, history_id),
 | 
			
		||||
    CONSTRAINT fk_history_transaction_transaction
 | 
			
		||||
        FOREIGN KEY (transaction_id) REFERENCES transaction(id)
 | 
			
		||||
            ON UPDATE CASCADE ON DELETE CASCADE,
 | 
			
		||||
    CONSTRAINT fk_history_transaction_history
 | 
			
		||||
        FOREIGN KEY (history_id) REFERENCES history(id)
 | 
			
		||||
            ON UPDATE CASCADE ON DELETE CASCADE
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS account_history_item_text;
 | 
			
		||||
DROP TABLE IF EXISTS account_history_item_account_entry;
 | 
			
		||||
DROP TABLE IF EXISTS account_history_item_balance_record;
 | 
			
		||||
DROP TABLE IF EXISTS account_history_item;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -134,42 +134,49 @@ CREATE TABLE balance_record_attachment (
 | 
			
		|||
            ON UPDATE CASCADE ON DELETE CASCADE
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE account_history_item (
 | 
			
		||||
/* HISTORY */
 | 
			
		||||
CREATE TABLE history (
 | 
			
		||||
    id BIGINT PRIMARY KEY AUTO_INCREMENT
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE history_item (
 | 
			
		||||
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
 | 
			
		||||
    history_id BIGINT NOT NULL,
 | 
			
		||||
    timestamp TIMESTAMP NOT NULL,
 | 
			
		||||
    account_id BIGINT NOT NULL,
 | 
			
		||||
    type VARCHAR(63) NOT NULL,
 | 
			
		||||
    CONSTRAINT fk_account_history_item_account
 | 
			
		||||
        FOREIGN KEY (account_id) REFERENCES account(id)
 | 
			
		||||
    CONSTRAINT fk_history_item_history
 | 
			
		||||
        FOREIGN KEY (history_id) REFERENCES history(id)
 | 
			
		||||
            ON UPDATE CASCADE ON DELETE CASCADE
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE account_history_item_text (
 | 
			
		||||
    item_id BIGINT NOT NULL PRIMARY KEY,
 | 
			
		||||
CREATE TABLE history_item_text (
 | 
			
		||||
    id BIGINT PRIMARY KEY,
 | 
			
		||||
    description VARCHAR(255) NOT NULL,
 | 
			
		||||
    CONSTRAINT fk_account_history_item_text_pk
 | 
			
		||||
        FOREIGN KEY (item_id) REFERENCES account_history_item(id)
 | 
			
		||||
    CONSTRAINT fk_history_item_text_pk
 | 
			
		||||
        FOREIGN KEY (id) REFERENCES history_item(id)
 | 
			
		||||
            ON UPDATE CASCADE ON DELETE CASCADE
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE account_history_item_account_entry (
 | 
			
		||||
    item_id BIGINT NOT NULL PRIMARY KEY,
 | 
			
		||||
    entry_id BIGINT NOT NULL,
 | 
			
		||||
    CONSTRAINT fk_account_history_item_account_entry_pk
 | 
			
		||||
        FOREIGN KEY (item_id) REFERENCES account_history_item(id)
 | 
			
		||||
CREATE TABLE history_account (
 | 
			
		||||
    account_id BIGINT NOT NULL,
 | 
			
		||||
    history_id BIGINT NOT NULL,
 | 
			
		||||
    PRIMARY KEY (account_id, history_id),
 | 
			
		||||
    CONSTRAINT fk_history_account_account
 | 
			
		||||
        FOREIGN KEY (account_id) REFERENCES account(id)
 | 
			
		||||
            ON UPDATE CASCADE ON DELETE CASCADE,
 | 
			
		||||
    CONSTRAINT fk_account_history_item_account_entry
 | 
			
		||||
        FOREIGN KEY (entry_id) REFERENCES account_entry(id)
 | 
			
		||||
    CONSTRAINT fk_history_account_history
 | 
			
		||||
        FOREIGN KEY (history_id) REFERENCES history(id)
 | 
			
		||||
            ON UPDATE CASCADE ON DELETE CASCADE
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE account_history_item_balance_record (
 | 
			
		||||
    item_id BIGINT NOT NULL PRIMARY KEY,
 | 
			
		||||
    record_id BIGINT NOT NULL,
 | 
			
		||||
    CONSTRAINT fk_account_history_item_balance_record_pk
 | 
			
		||||
        FOREIGN KEY (item_id) REFERENCES account_history_item(id)
 | 
			
		||||
CREATE TABLE history_transaction (
 | 
			
		||||
    transaction_id BIGINT NOT NULL,
 | 
			
		||||
    history_id BIGINT NOT NULL,
 | 
			
		||||
    PRIMARY KEY (transaction_id, history_id),
 | 
			
		||||
    CONSTRAINT fk_history_transaction_transaction
 | 
			
		||||
        FOREIGN KEY (transaction_id) REFERENCES transaction(id)
 | 
			
		||||
            ON UPDATE CASCADE ON DELETE CASCADE,
 | 
			
		||||
    CONSTRAINT fk_account_history_item_balance_record
 | 
			
		||||
        FOREIGN KEY (record_id) REFERENCES balance_record(id)
 | 
			
		||||
    CONSTRAINT fk_history_transaction_history
 | 
			
		||||
        FOREIGN KEY (history_id) REFERENCES history(id)
 | 
			
		||||
            ON UPDATE CASCADE ON DELETE CASCADE
 | 
			
		||||
);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue