Refactored account history.
This commit is contained in:
parent
3493003588
commit
8f36380e21
|
@ -1,12 +1,12 @@
|
||||||
package com.andrewlalis.perfin.control;
|
package com.andrewlalis.perfin.control;
|
||||||
|
|
||||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||||
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
|
|
||||||
import com.andrewlalis.perfin.data.AccountRepository;
|
import com.andrewlalis.perfin.data.AccountRepository;
|
||||||
|
import com.andrewlalis.perfin.data.HistoryRepository;
|
||||||
import com.andrewlalis.perfin.data.util.DateUtil;
|
import com.andrewlalis.perfin.data.util.DateUtil;
|
||||||
import com.andrewlalis.perfin.model.Account;
|
import com.andrewlalis.perfin.model.Account;
|
||||||
import com.andrewlalis.perfin.model.Profile;
|
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 com.andrewlalis.perfin.view.component.AccountHistoryItemTile;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.BooleanExpression;
|
import javafx.beans.binding.BooleanExpression;
|
||||||
|
@ -131,20 +131,15 @@ public class AccountViewController implements RouteSelectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML public void loadMoreHistory() {
|
@FXML public void loadMoreHistory() {
|
||||||
Profile.getCurrent().dataSource().useRepoAsync(AccountHistoryItemRepository.class, repo -> {
|
Profile.getCurrent().dataSource().useRepoAsync(HistoryRepository.class, repo -> {
|
||||||
List<AccountHistoryItem> historyItems = repo.findMostRecentForAccount(
|
long historyId = repo.getOrCreateHistoryForAccount(account.id);
|
||||||
account.id,
|
List<HistoryItem> items = repo.getNItemsBefore(historyId, historyLoadSize, loadHistoryFrom);
|
||||||
loadHistoryFrom,
|
if (items.size() < historyLoadSize) {
|
||||||
historyLoadSize
|
|
||||||
);
|
|
||||||
if (historyItems.size() < historyLoadSize) {
|
|
||||||
Platform.runLater(() -> loadMoreHistoryButton.setDisable(true));
|
Platform.runLater(() -> loadMoreHistoryButton.setDisable(true));
|
||||||
} else {
|
} else {
|
||||||
loadHistoryFrom = historyItems.getLast().getTimestamp();
|
loadHistoryFrom = items.getLast().getTimestamp();
|
||||||
}
|
}
|
||||||
List<? extends Node> nodes = historyItems.stream()
|
List<? extends Node> nodes = items.stream().map(AccountHistoryItemTile::forItem).toList();
|
||||||
.map(item -> AccountHistoryItemTile.forItem(item, repo, this))
|
|
||||||
.toList();
|
|
||||||
Platform.runLater(() -> historyItemsVBox.getChildren().addAll(nodes));
|
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();
|
TransactionVendorRepository getTransactionVendorRepository();
|
||||||
TransactionCategoryRepository getTransactionCategoryRepository();
|
TransactionCategoryRepository getTransactionCategoryRepository();
|
||||||
AttachmentRepository getAttachmentRepository();
|
AttachmentRepository getAttachmentRepository();
|
||||||
AccountHistoryItemRepository getAccountHistoryItemRepository();
|
HistoryRepository getHistoryRepository();
|
||||||
|
|
||||||
// Repository helper methods:
|
// Repository helper methods:
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ public interface DataSource {
|
||||||
TransactionVendorRepository.class, this::getTransactionVendorRepository,
|
TransactionVendorRepository.class, this::getTransactionVendorRepository,
|
||||||
TransactionCategoryRepository.class, this::getTransactionCategoryRepository,
|
TransactionCategoryRepository.class, this::getTransactionCategoryRepository,
|
||||||
AttachmentRepository.class, this::getAttachmentRepository,
|
AttachmentRepository.class, this::getAttachmentRepository,
|
||||||
AccountHistoryItemRepository.class, this::getAccountHistoryItemRepository
|
HistoryRepository.class, this::getHistoryRepository
|
||||||
);
|
);
|
||||||
return (Supplier<R>) repoSuppliers.get(type);
|
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;
|
package com.andrewlalis.perfin.data.impl;
|
||||||
|
|
||||||
import com.andrewlalis.perfin.data.AccountEntryRepository;
|
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.data.util.DbUtil;
|
||||||
import com.andrewlalis.perfin.model.AccountEntry;
|
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.
|
// Insert an entry into the account's history.
|
||||||
AccountHistoryItemRepository historyRepo = new JdbcAccountHistoryItemRepository(conn);
|
HistoryRepository historyRepo = new JdbcHistoryRepository(conn);
|
||||||
historyRepo.recordAccountEntry(timestamp, accountId, entryId);
|
long historyId = historyRepo.getOrCreateHistoryForAccount(accountId);
|
||||||
|
historyRepo.addTextItem(historyId, timestamp, "Entry #" + entryId + " added as a " + type.name() + " from Transaction #" + transactionId + ".");
|
||||||
return entryId;
|
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;
|
package com.andrewlalis.perfin.data.impl;
|
||||||
|
|
||||||
import com.andrewlalis.perfin.data.AccountEntryRepository;
|
import com.andrewlalis.perfin.data.*;
|
||||||
import com.andrewlalis.perfin.data.AccountRepository;
|
|
||||||
import com.andrewlalis.perfin.data.BalanceRecordRepository;
|
|
||||||
import com.andrewlalis.perfin.data.EntityNotFoundException;
|
|
||||||
import com.andrewlalis.perfin.data.pagination.Page;
|
import com.andrewlalis.perfin.data.pagination.Page;
|
||||||
import com.andrewlalis.perfin.data.pagination.PageRequest;
|
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.data.util.DbUtil;
|
||||||
import com.andrewlalis.perfin.model.Account;
|
import com.andrewlalis.perfin.model.Account;
|
||||||
import com.andrewlalis.perfin.model.AccountEntry;
|
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.
|
// Insert a history item indicating the creation of the account.
|
||||||
var historyRepo = new JdbcAccountHistoryItemRepository(conn);
|
HistoryRepository historyRepo = new JdbcHistoryRepository(conn);
|
||||||
historyRepo.recordText(DateUtil.nowAsUTC(), accountId, "Account added to your Perfin profile.");
|
long historyId = historyRepo.getOrCreateHistoryForAccount(accountId);
|
||||||
|
historyRepo.addTextItem(historyId, "Account added to your Perfin profile.");
|
||||||
return accountId;
|
return accountId;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -59,11 +56,12 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
|
||||||
return DbUtil.findAll(
|
return DbUtil.findAll(
|
||||||
conn,
|
conn,
|
||||||
"""
|
"""
|
||||||
SELECT DISTINCT ON (account.id) account.*, ahi.timestamp AS _
|
SELECT DISTINCT ON (account.id) account.*, hi.timestamp AS _
|
||||||
FROM account
|
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
|
WHERE NOT account.archived
|
||||||
ORDER BY ahi.timestamp DESC, account.created_at DESC""",
|
ORDER BY hi.timestamp DESC, account.created_at DESC""",
|
||||||
JdbcAccountRepository::parseAccount
|
JdbcAccountRepository::parseAccount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -160,7 +158,9 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
|
||||||
public void archive(long accountId) {
|
public void archive(long accountId) {
|
||||||
DbUtil.doTransaction(conn, () -> {
|
DbUtil.doTransaction(conn, () -> {
|
||||||
DbUtil.updateOne(conn, "UPDATE account SET archived = TRUE WHERE id = ?", List.of(accountId));
|
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) {
|
public void unarchive(long accountId) {
|
||||||
DbUtil.doTransaction(conn, () -> {
|
DbUtil.doTransaction(conn, () -> {
|
||||||
DbUtil.updateOne(conn, "UPDATE account SET archived = FALSE WHERE id = ?", List.of(accountId));
|
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;
|
package com.andrewlalis.perfin.data.impl;
|
||||||
|
|
||||||
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
|
|
||||||
import com.andrewlalis.perfin.data.AttachmentRepository;
|
import com.andrewlalis.perfin.data.AttachmentRepository;
|
||||||
import com.andrewlalis.perfin.data.BalanceRecordRepository;
|
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.data.util.DbUtil;
|
||||||
import com.andrewlalis.perfin.model.Attachment;
|
import com.andrewlalis.perfin.model.Attachment;
|
||||||
import com.andrewlalis.perfin.model.BalanceRecord;
|
import com.andrewlalis.perfin.model.BalanceRecord;
|
||||||
|
import com.andrewlalis.perfin.model.MoneyValue;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -36,8 +38,9 @@ public record JdbcBalanceRecordRepository(Connection conn, Path contentDir) impl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add a history item entry.
|
// Add a history item entry.
|
||||||
AccountHistoryItemRepository historyRepo = new JdbcAccountHistoryItemRepository(conn);
|
HistoryRepository historyRepo = new JdbcHistoryRepository(conn);
|
||||||
historyRepo.recordBalanceRecord(utcTimestamp, accountId, recordId);
|
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;
|
return recordId;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ public class JdbcDataSource implements DataSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AccountHistoryItemRepository getAccountHistoryItemRepository() {
|
public HistoryRepository getHistoryRepository() {
|
||||||
return new JdbcAccountHistoryItemRepository(getConnection());
|
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
|
* the profile has a newer schema version, we'll exit and prompt the user
|
||||||
* to update their app.
|
* 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 {
|
public DataSource getDataSource(String profileName) throws ProfileLoadException {
|
||||||
final boolean dbExists = Files.exists(getDatabaseFile(profileName));
|
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.AccountEntryRepository;
|
||||||
import com.andrewlalis.perfin.data.AttachmentRepository;
|
import com.andrewlalis.perfin.data.AttachmentRepository;
|
||||||
|
import com.andrewlalis.perfin.data.HistoryRepository;
|
||||||
import com.andrewlalis.perfin.data.TransactionRepository;
|
import com.andrewlalis.perfin.data.TransactionRepository;
|
||||||
import com.andrewlalis.perfin.data.pagination.Page;
|
import com.andrewlalis.perfin.data.pagination.Page;
|
||||||
import com.andrewlalis.perfin.data.pagination.PageRequest;
|
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.
|
// Add a text history item to any linked accounts detailing the changes.
|
||||||
String updateMessageStr = "Transaction #" + tx.id + " was updated:\n" + String.join("\n", updateMessages);
|
String updateMessageStr = "Transaction #" + tx.id + " was updated:\n" + String.join("\n", updateMessages);
|
||||||
var historyRepo = new JdbcAccountHistoryItemRepository(conn);
|
HistoryRepository historyRepo = new JdbcHistoryRepository(conn);
|
||||||
linkedAccounts.ifCredit(acc -> historyRepo.recordText(DateUtil.nowAsUTC(), acc.id, updateMessageStr));
|
long historyId = historyRepo.getOrCreateHistoryForTransaction(id);
|
||||||
linkedAccounts.ifDebit(acc -> historyRepo.recordText(DateUtil.nowAsUTC(), acc.id, updateMessageStr));
|
historyRepo.addTextItem(historyId, updateMessageStr);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ public class Migrations {
|
||||||
public static Map<Integer, Migration> getMigrations() {
|
public static Map<Integer, Migration> getMigrations() {
|
||||||
final Map<Integer, Migration> migrations = new HashMap<>();
|
final Map<Integer, Migration> migrations = new HashMap<>();
|
||||||
migrations.put(1, new PlainSQLMigration("/sql/migration/M001_AddTransactionProperties.sql"));
|
migrations.put(1, new PlainSQLMigration("/sql/migration/M001_AddTransactionProperties.sql"));
|
||||||
|
migrations.put(2, new PlainSQLMigration("/sql/migration/M002_RefactorHistories.sql"));
|
||||||
return migrations;
|
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;
|
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.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.control.Label;
|
||||||
import javafx.scene.layout.BorderPane;
|
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.
|
* A tile that shows a brief bit of information about an account history item.
|
||||||
*/
|
*/
|
||||||
public abstract class AccountHistoryItemTile extends BorderPane {
|
public abstract class AccountHistoryItemTile extends BorderPane {
|
||||||
public AccountHistoryItemTile(AccountHistoryItem item) {
|
public AccountHistoryItemTile(HistoryItem item) {
|
||||||
getStyleClass().add("tile");
|
getStyleClass().add("tile");
|
||||||
|
|
||||||
Label timestampLabel = new Label(DateUtil.formatUTCAsLocalWithZone(item.getTimestamp()));
|
Label timestampLabel = new Label(DateUtil.formatUTCAsLocalWithZone(item.getTimestamp()));
|
||||||
|
@ -20,14 +19,11 @@ public abstract class AccountHistoryItemTile extends BorderPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AccountHistoryItemTile forItem(
|
public static AccountHistoryItemTile forItem(
|
||||||
AccountHistoryItem item,
|
HistoryItem item
|
||||||
AccountHistoryItemRepository repo,
|
|
||||||
AccountViewController controller
|
|
||||||
) {
|
) {
|
||||||
return switch (item.getType()) {
|
if (item instanceof HistoryTextItem t) {
|
||||||
case TEXT -> new AccountHistoryTextTile(item, repo);
|
return new AccountHistoryTextTile(t);
|
||||||
case ACCOUNT_ENTRY -> new AccountHistoryAccountEntryTile(item, repo);
|
}
|
||||||
case BALANCE_RECORD -> new AccountHistoryBalanceRecordTile(item, repo, controller);
|
throw new RuntimeException("Unsupported history item type: " + item.getType());
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
package com.andrewlalis.perfin.view.component;
|
package com.andrewlalis.perfin.view.component;
|
||||||
|
|
||||||
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
|
import com.andrewlalis.perfin.model.history.HistoryTextItem;
|
||||||
import com.andrewlalis.perfin.model.history.AccountHistoryItem;
|
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.scene.text.TextFlow;
|
import javafx.scene.text.TextFlow;
|
||||||
|
|
||||||
public class AccountHistoryTextTile extends AccountHistoryItemTile {
|
public class AccountHistoryTextTile extends AccountHistoryItemTile {
|
||||||
public AccountHistoryTextTile(AccountHistoryItem item, AccountHistoryItemRepository repo) {
|
public AccountHistoryTextTile(HistoryTextItem item) {
|
||||||
super(item);
|
super(item);
|
||||||
String text = repo.getTextItem(item.id);
|
setCenter(new TextFlow(new Text(item.getDescription())));
|
||||||
setCenter(new TextFlow(new Text(text)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,4 +19,5 @@ module com.andrewlalis.perfin {
|
||||||
opens com.andrewlalis.perfin.view to javafx.fxml;
|
opens com.andrewlalis.perfin.view to javafx.fxml;
|
||||||
opens com.andrewlalis.perfin.view.component to javafx.fxml;
|
opens com.andrewlalis.perfin.view.component to javafx.fxml;
|
||||||
opens com.andrewlalis.perfin.view.component.validation 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
|
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,
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
history_id BIGINT NOT NULL,
|
||||||
timestamp TIMESTAMP NOT NULL,
|
timestamp TIMESTAMP NOT NULL,
|
||||||
account_id BIGINT NOT NULL,
|
|
||||||
type VARCHAR(63) NOT NULL,
|
type VARCHAR(63) NOT NULL,
|
||||||
CONSTRAINT fk_account_history_item_account
|
CONSTRAINT fk_history_item_history
|
||||||
FOREIGN KEY (account_id) REFERENCES account(id)
|
FOREIGN KEY (history_id) REFERENCES history(id)
|
||||||
ON UPDATE CASCADE ON DELETE CASCADE
|
ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE account_history_item_text (
|
CREATE TABLE history_item_text (
|
||||||
item_id BIGINT NOT NULL PRIMARY KEY,
|
id BIGINT PRIMARY KEY,
|
||||||
description VARCHAR(255) NOT NULL,
|
description VARCHAR(255) NOT NULL,
|
||||||
CONSTRAINT fk_account_history_item_text_pk
|
CONSTRAINT fk_history_item_text_pk
|
||||||
FOREIGN KEY (item_id) REFERENCES account_history_item(id)
|
FOREIGN KEY (id) REFERENCES history_item(id)
|
||||||
ON UPDATE CASCADE ON DELETE CASCADE
|
ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE account_history_item_account_entry (
|
CREATE TABLE history_account (
|
||||||
item_id BIGINT NOT NULL PRIMARY KEY,
|
account_id BIGINT NOT NULL,
|
||||||
entry_id BIGINT NOT NULL,
|
history_id BIGINT NOT NULL,
|
||||||
CONSTRAINT fk_account_history_item_account_entry_pk
|
PRIMARY KEY (account_id, history_id),
|
||||||
FOREIGN KEY (item_id) REFERENCES account_history_item(id)
|
CONSTRAINT fk_history_account_account
|
||||||
|
FOREIGN KEY (account_id) REFERENCES account(id)
|
||||||
ON UPDATE CASCADE ON DELETE CASCADE,
|
ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
CONSTRAINT fk_account_history_item_account_entry
|
CONSTRAINT fk_history_account_history
|
||||||
FOREIGN KEY (entry_id) REFERENCES account_entry(id)
|
FOREIGN KEY (history_id) REFERENCES history(id)
|
||||||
ON UPDATE CASCADE ON DELETE CASCADE
|
ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE account_history_item_balance_record (
|
CREATE TABLE history_transaction (
|
||||||
item_id BIGINT NOT NULL PRIMARY KEY,
|
transaction_id BIGINT NOT NULL,
|
||||||
record_id BIGINT NOT NULL,
|
history_id BIGINT NOT NULL,
|
||||||
CONSTRAINT fk_account_history_item_balance_record_pk
|
PRIMARY KEY (transaction_id, history_id),
|
||||||
FOREIGN KEY (item_id) REFERENCES account_history_item(id)
|
CONSTRAINT fk_history_transaction_transaction
|
||||||
|
FOREIGN KEY (transaction_id) REFERENCES transaction(id)
|
||||||
ON UPDATE CASCADE ON DELETE CASCADE,
|
ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
CONSTRAINT fk_account_history_item_balance_record
|
CONSTRAINT fk_history_transaction_history
|
||||||
FOREIGN KEY (record_id) REFERENCES balance_record(id)
|
FOREIGN KEY (history_id) REFERENCES history(id)
|
||||||
ON UPDATE CASCADE ON DELETE CASCADE
|
ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue