Add Transaction Properties #15
|
@ -3,7 +3,9 @@ package com.andrewlalis.perfin;
|
||||||
import com.andrewlalis.javafx_scene_router.AnchorPaneRouterView;
|
import com.andrewlalis.javafx_scene_router.AnchorPaneRouterView;
|
||||||
import com.andrewlalis.javafx_scene_router.SceneRouter;
|
import com.andrewlalis.javafx_scene_router.SceneRouter;
|
||||||
import com.andrewlalis.perfin.data.ProfileLoadException;
|
import com.andrewlalis.perfin.data.ProfileLoadException;
|
||||||
|
import com.andrewlalis.perfin.data.impl.JdbcDataSourceFactory;
|
||||||
import com.andrewlalis.perfin.model.Profile;
|
import com.andrewlalis.perfin.model.Profile;
|
||||||
|
import com.andrewlalis.perfin.model.ProfileLoader;
|
||||||
import com.andrewlalis.perfin.view.ImageCache;
|
import com.andrewlalis.perfin.view.ImageCache;
|
||||||
import com.andrewlalis.perfin.view.SceneUtil;
|
import com.andrewlalis.perfin.view.SceneUtil;
|
||||||
import com.andrewlalis.perfin.view.StartupSplashScreen;
|
import com.andrewlalis.perfin.view.StartupSplashScreen;
|
||||||
|
@ -29,6 +31,7 @@ public class PerfinApp extends Application {
|
||||||
private static final Logger log = LoggerFactory.getLogger(PerfinApp.class);
|
private static final Logger log = LoggerFactory.getLogger(PerfinApp.class);
|
||||||
public static final Path APP_DIR = Path.of(System.getProperty("user.home", "."), ".perfin");
|
public static final Path APP_DIR = Path.of(System.getProperty("user.home", "."), ".perfin");
|
||||||
public static PerfinApp instance;
|
public static PerfinApp instance;
|
||||||
|
public static ProfileLoader profileLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The router that's used for navigating between different "pages" in the application.
|
* The router that's used for navigating between different "pages" in the application.
|
||||||
|
@ -48,6 +51,7 @@ public class PerfinApp extends Application {
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) {
|
public void start(Stage stage) {
|
||||||
instance = this;
|
instance = this;
|
||||||
|
profileLoader = new ProfileLoader(stage, new JdbcDataSourceFactory());
|
||||||
loadFonts();
|
loadFonts();
|
||||||
var splashScreen = new StartupSplashScreen(List.of(
|
var splashScreen = new StartupSplashScreen(List.of(
|
||||||
PerfinApp::defineRoutes,
|
PerfinApp::defineRoutes,
|
||||||
|
@ -112,9 +116,10 @@ public class PerfinApp extends Application {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void loadLastUsedProfile(Consumer<String> msgConsumer) throws Exception {
|
private static void loadLastUsedProfile(Consumer<String> msgConsumer) throws Exception {
|
||||||
msgConsumer.accept("Loading the most recent profile.");
|
String lastProfile = ProfileLoader.getLastProfile();
|
||||||
|
msgConsumer.accept("Loading the most recent profile: \"" + lastProfile + "\".");
|
||||||
try {
|
try {
|
||||||
Profile.loadLast();
|
Profile.setCurrent(profileLoader.load(lastProfile));
|
||||||
} catch (ProfileLoadException e) {
|
} catch (ProfileLoadException e) {
|
||||||
msgConsumer.accept("Failed to load the profile: " + e.getMessage());
|
msgConsumer.accept("Failed to load the profile: " + e.getMessage());
|
||||||
throw e;
|
throw e;
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class AccountViewController implements RouteSelectionListener {
|
||||||
accountNumberLabel.setText(account.getAccountNumber());
|
accountNumberLabel.setText(account.getAccountNumber());
|
||||||
accountCurrencyLabel.setText(account.getCurrency().getDisplayName());
|
accountCurrencyLabel.setText(account.getCurrency().getDisplayName());
|
||||||
accountCreatedAtLabel.setText(DateUtil.formatUTCAsLocalWithZone(account.getCreatedAt()));
|
accountCreatedAtLabel.setText(DateUtil.formatUTCAsLocalWithZone(account.getCreatedAt()));
|
||||||
Profile.getCurrent().getDataSource().getAccountBalanceText(account)
|
Profile.getCurrent().dataSource().getAccountBalanceText(account)
|
||||||
.thenAccept(accountBalanceLabel::setText);
|
.thenAccept(accountBalanceLabel::setText);
|
||||||
|
|
||||||
reloadHistory();
|
reloadHistory();
|
||||||
|
@ -96,7 +96,7 @@ public class AccountViewController implements RouteSelectionListener {
|
||||||
"later if you need to."
|
"later if you need to."
|
||||||
);
|
);
|
||||||
if (confirmResult) {
|
if (confirmResult) {
|
||||||
Profile.getCurrent().getDataSource().useRepo(AccountRepository.class, repo -> repo.archive(account.id));
|
Profile.getCurrent().dataSource().useRepo(AccountRepository.class, repo -> repo.archive(account.id));
|
||||||
router.replace("accounts");
|
router.replace("accounts");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ public class AccountViewController implements RouteSelectionListener {
|
||||||
"status?"
|
"status?"
|
||||||
);
|
);
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
Profile.getCurrent().getDataSource().useRepo(AccountRepository.class, repo -> repo.unarchive(account.id));
|
Profile.getCurrent().dataSource().useRepo(AccountRepository.class, repo -> repo.unarchive(account.id));
|
||||||
router.replace("accounts");
|
router.replace("accounts");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,13 +122,13 @@ public class AccountViewController implements RouteSelectionListener {
|
||||||
"want to hide it."
|
"want to hide it."
|
||||||
);
|
);
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
Profile.getCurrent().getDataSource().useRepo(AccountRepository.class, repo -> repo.delete(account));
|
Profile.getCurrent().dataSource().useRepo(AccountRepository.class, repo -> repo.delete(account));
|
||||||
router.replace("accounts");
|
router.replace("accounts");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML public void loadMoreHistory() {
|
@FXML public void loadMoreHistory() {
|
||||||
Profile.getCurrent().getDataSource().useRepoAsync(AccountHistoryItemRepository.class, repo -> {
|
Profile.getCurrent().dataSource().useRepoAsync(AccountHistoryItemRepository.class, repo -> {
|
||||||
List<AccountHistoryItem> historyItems = repo.findMostRecentForAccount(
|
List<AccountHistoryItem> historyItems = repo.findMostRecentForAccount(
|
||||||
account.id,
|
account.id,
|
||||||
loadHistoryFrom,
|
loadHistoryFrom,
|
||||||
|
|
|
@ -49,7 +49,7 @@ public class AccountsViewController implements RouteSelectionListener {
|
||||||
|
|
||||||
public void refreshAccounts() {
|
public void refreshAccounts() {
|
||||||
Profile.whenLoaded(profile -> {
|
Profile.whenLoaded(profile -> {
|
||||||
profile.getDataSource().useRepoAsync(AccountRepository.class, repo -> {
|
profile.dataSource().useRepoAsync(AccountRepository.class, repo -> {
|
||||||
List<Account> accounts = repo.findAllOrderedByRecentHistory();
|
List<Account> accounts = repo.findAllOrderedByRecentHistory();
|
||||||
Platform.runLater(() -> accountsPane.getChildren()
|
Platform.runLater(() -> accountsPane.getChildren()
|
||||||
.setAll(accounts.stream()
|
.setAll(accounts.stream()
|
||||||
|
@ -59,7 +59,7 @@ public class AccountsViewController implements RouteSelectionListener {
|
||||||
});
|
});
|
||||||
// Compute grand totals!
|
// Compute grand totals!
|
||||||
Thread.ofVirtual().start(() -> {
|
Thread.ofVirtual().start(() -> {
|
||||||
var totals = profile.getDataSource().getCombinedAccountBalances();
|
var totals = profile.dataSource().getCombinedAccountBalances();
|
||||||
StringBuilder sb = new StringBuilder("Totals: ");
|
StringBuilder sb = new StringBuilder("Totals: ");
|
||||||
for (var entry : totals.entrySet()) {
|
for (var entry : totals.entrySet()) {
|
||||||
sb.append(CurrencyUtil.formatMoneyWithCurrencyPrefix(new MoneyValue(entry.getValue(), entry.getKey())));
|
sb.append(CurrencyUtil.formatMoneyWithCurrencyPrefix(new MoneyValue(entry.getValue(), entry.getKey())));
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class BalanceRecordViewController implements RouteSelectionListener {
|
||||||
timestampLabel.setText(DateUtil.formatUTCAsLocalWithZone(balanceRecord.getTimestamp()));
|
timestampLabel.setText(DateUtil.formatUTCAsLocalWithZone(balanceRecord.getTimestamp()));
|
||||||
balanceLabel.setText(CurrencyUtil.formatMoney(balanceRecord.getMoneyAmount()));
|
balanceLabel.setText(CurrencyUtil.formatMoney(balanceRecord.getMoneyAmount()));
|
||||||
currencyLabel.setText(balanceRecord.getCurrency().getDisplayName());
|
currencyLabel.setText(balanceRecord.getCurrency().getDisplayName());
|
||||||
Profile.getCurrent().getDataSource().useRepoAsync(BalanceRecordRepository.class, repo -> {
|
Profile.getCurrent().dataSource().useRepoAsync(BalanceRecordRepository.class, repo -> {
|
||||||
List<Attachment> attachments = repo.findAttachments(balanceRecord.id);
|
List<Attachment> attachments = repo.findAttachments(balanceRecord.id);
|
||||||
Platform.runLater(() -> attachmentsViewPane.setAttachments(attachments));
|
Platform.runLater(() -> attachmentsViewPane.setAttachments(attachments));
|
||||||
});
|
});
|
||||||
|
@ -50,7 +50,7 @@ public class BalanceRecordViewController implements RouteSelectionListener {
|
||||||
@FXML public void delete() {
|
@FXML public void delete() {
|
||||||
boolean confirm = Popups.confirm("Are you sure you want to delete this balance record? This may have an effect on the derived balance of your account, as shown in Perfin.");
|
boolean confirm = Popups.confirm("Are you sure you want to delete this balance record? This may have an effect on the derived balance of your account, as shown in Perfin.");
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
Profile.getCurrent().getDataSource().useRepo(BalanceRecordRepository.class, repo -> repo.deleteById(balanceRecord.id));
|
Profile.getCurrent().dataSource().useRepo(BalanceRecordRepository.class, repo -> repo.deleteById(balanceRecord.id));
|
||||||
router.navigateBackAndClear();
|
router.navigateBackAndClear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BigDecimal reportedBalance = new BigDecimal(newValue);
|
BigDecimal reportedBalance = new BigDecimal(newValue);
|
||||||
Profile.getCurrent().getDataSource().useRepoAsync(AccountRepository.class, repo -> {
|
Profile.getCurrent().dataSource().useRepoAsync(AccountRepository.class, repo -> {
|
||||||
BigDecimal derivedBalance = repo.deriveCurrentBalance(account.id);
|
BigDecimal derivedBalance = repo.deriveCurrentBalance(account.id);
|
||||||
Platform.runLater(() -> balanceWarningLabel.visibleProperty().set(
|
Platform.runLater(() -> balanceWarningLabel.visibleProperty().set(
|
||||||
!reportedBalance.setScale(derivedBalance.scale(), RoundingMode.HALF_UP).equals(derivedBalance)
|
!reportedBalance.setScale(derivedBalance.scale(), RoundingMode.HALF_UP).equals(derivedBalance)
|
||||||
|
@ -76,7 +76,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
|
||||||
public void onRouteSelected(Object context) {
|
public void onRouteSelected(Object context) {
|
||||||
this.account = (Account) context;
|
this.account = (Account) context;
|
||||||
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
|
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
|
||||||
Profile.getCurrent().getDataSource().useRepoAsync(AccountRepository.class, repo -> {
|
Profile.getCurrent().dataSource().useRepoAsync(AccountRepository.class, repo -> {
|
||||||
BigDecimal value = repo.deriveCurrentBalance(account.id);
|
BigDecimal value = repo.deriveCurrentBalance(account.id);
|
||||||
Platform.runLater(() -> balanceField.setText(
|
Platform.runLater(() -> balanceField.setText(
|
||||||
CurrencyUtil.formatMoneyAsBasicNumber(new MoneyValue(value, account.getCurrency()))
|
CurrencyUtil.formatMoneyAsBasicNumber(new MoneyValue(value, account.getCurrency()))
|
||||||
|
@ -95,7 +95,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
|
||||||
localTimestamp.atZone(ZoneId.systemDefault()).format(DateUtil.DEFAULT_DATETIME_FORMAT_WITH_ZONE)
|
localTimestamp.atZone(ZoneId.systemDefault()).format(DateUtil.DEFAULT_DATETIME_FORMAT_WITH_ZONE)
|
||||||
));
|
));
|
||||||
if (confirm && confirmIfInconsistentBalance(reportedBalance)) {
|
if (confirm && confirmIfInconsistentBalance(reportedBalance)) {
|
||||||
Profile.getCurrent().getDataSource().useRepo(BalanceRecordRepository.class, repo -> {
|
Profile.getCurrent().dataSource().useRepo(BalanceRecordRepository.class, repo -> {
|
||||||
repo.insert(
|
repo.insert(
|
||||||
DateUtil.localToUTC(localTimestamp),
|
DateUtil.localToUTC(localTimestamp),
|
||||||
account.id,
|
account.id,
|
||||||
|
@ -113,7 +113,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean confirmIfInconsistentBalance(BigDecimal reportedBalance) {
|
private boolean confirmIfInconsistentBalance(BigDecimal reportedBalance) {
|
||||||
BigDecimal currentDerivedBalance = Profile.getCurrent().getDataSource().mapRepo(
|
BigDecimal currentDerivedBalance = Profile.getCurrent().dataSource().mapRepo(
|
||||||
AccountRepository.class,
|
AccountRepository.class,
|
||||||
repo -> repo.deriveCurrentBalance(account.id)
|
repo -> repo.deriveCurrentBalance(account.id)
|
||||||
);
|
);
|
||||||
|
|
|
@ -106,8 +106,8 @@ public class EditAccountController implements RouteSelectionListener {
|
||||||
@FXML
|
@FXML
|
||||||
public void save() {
|
public void save() {
|
||||||
try (
|
try (
|
||||||
var accountRepo = Profile.getCurrent().getDataSource().getAccountRepository();
|
var accountRepo = Profile.getCurrent().dataSource().getAccountRepository();
|
||||||
var balanceRepo = Profile.getCurrent().getDataSource().getBalanceRecordRepository()
|
var balanceRepo = Profile.getCurrent().dataSource().getBalanceRecordRepository()
|
||||||
) {
|
) {
|
||||||
if (creatingNewAccount.get()) {
|
if (creatingNewAccount.get()) {
|
||||||
String name = accountNameField.getText().strip();
|
String name = accountNameField.getText().strip();
|
||||||
|
|
|
@ -111,7 +111,7 @@ public class EditTransactionController implements RouteSelectionListener {
|
||||||
List<Attachment> existingAttachments = attachmentsSelectionArea.getSelectedAttachments();
|
List<Attachment> existingAttachments = attachmentsSelectionArea.getSelectedAttachments();
|
||||||
final long idToNavigate;
|
final long idToNavigate;
|
||||||
if (transaction == null) {
|
if (transaction == null) {
|
||||||
idToNavigate = Profile.getCurrent().getDataSource().mapRepo(
|
idToNavigate = Profile.getCurrent().dataSource().mapRepo(
|
||||||
TransactionRepository.class,
|
TransactionRepository.class,
|
||||||
repo -> repo.insert(
|
repo -> repo.insert(
|
||||||
utcTimestamp,
|
utcTimestamp,
|
||||||
|
@ -123,7 +123,7 @@ public class EditTransactionController implements RouteSelectionListener {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Profile.getCurrent().getDataSource().useRepo(
|
Profile.getCurrent().dataSource().useRepo(
|
||||||
TransactionRepository.class,
|
TransactionRepository.class,
|
||||||
repo -> repo.update(
|
repo -> repo.update(
|
||||||
transaction.id,
|
transaction.id,
|
||||||
|
@ -165,8 +165,8 @@ public class EditTransactionController implements RouteSelectionListener {
|
||||||
container.setDisable(true);
|
container.setDisable(true);
|
||||||
Thread.ofVirtual().start(() -> {
|
Thread.ofVirtual().start(() -> {
|
||||||
try (
|
try (
|
||||||
var accountRepo = Profile.getCurrent().getDataSource().getAccountRepository();
|
var accountRepo = Profile.getCurrent().dataSource().getAccountRepository();
|
||||||
var transactionRepo = Profile.getCurrent().getDataSource().getTransactionRepository()
|
var transactionRepo = Profile.getCurrent().dataSource().getTransactionRepository()
|
||||||
) {
|
) {
|
||||||
// First fetch all the data.
|
// First fetch all the data.
|
||||||
List<Currency> currencies = accountRepo.findAllUsedCurrencies().stream()
|
List<Currency> currencies = accountRepo.findAllUsedCurrencies().stream()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.andrewlalis.perfin.PerfinApp;
|
||||||
import com.andrewlalis.perfin.data.ProfileLoadException;
|
import com.andrewlalis.perfin.data.ProfileLoadException;
|
||||||
import com.andrewlalis.perfin.data.util.FileUtil;
|
import com.andrewlalis.perfin.data.util.FileUtil;
|
||||||
import com.andrewlalis.perfin.model.Profile;
|
import com.andrewlalis.perfin.model.Profile;
|
||||||
|
import com.andrewlalis.perfin.model.ProfileLoader;
|
||||||
import com.andrewlalis.perfin.view.ProfilesStage;
|
import com.andrewlalis.perfin.view.ProfilesStage;
|
||||||
import com.andrewlalis.perfin.view.component.validation.ValidationApplier;
|
import com.andrewlalis.perfin.view.component.validation.ValidationApplier;
|
||||||
import com.andrewlalis.perfin.view.component.validation.validators.PredicateValidator;
|
import com.andrewlalis.perfin.view.component.validation.validators.PredicateValidator;
|
||||||
|
@ -44,7 +45,7 @@ public class ProfilesViewController {
|
||||||
@FXML public void addProfile() {
|
@FXML public void addProfile() {
|
||||||
String name = newProfileNameField.getText();
|
String name = newProfileNameField.getText();
|
||||||
boolean valid = Profile.validateName(name);
|
boolean valid = Profile.validateName(name);
|
||||||
if (valid && !Profile.getAvailableProfiles().contains(name)) {
|
if (valid && !ProfileLoader.getAvailableProfiles().contains(name)) {
|
||||||
boolean confirm = Popups.confirm("Are you sure you want to add a new profile named \"" + name + "\"?");
|
boolean confirm = Popups.confirm("Are you sure you want to add a new profile named \"" + name + "\"?");
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
if (openProfile(name, false)) {
|
if (openProfile(name, false)) {
|
||||||
|
@ -56,8 +57,8 @@ public class ProfilesViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshAvailableProfiles() {
|
private void refreshAvailableProfiles() {
|
||||||
List<String> profileNames = Profile.getAvailableProfiles();
|
List<String> profileNames = ProfileLoader.getAvailableProfiles();
|
||||||
String currentProfile = Profile.getCurrent() == null ? null : Profile.getCurrent().getName();
|
String currentProfile = Profile.getCurrent() == null ? null : Profile.getCurrent().name();
|
||||||
List<Node> nodes = new ArrayList<>(profileNames.size());
|
List<Node> nodes = new ArrayList<>(profileNames.size());
|
||||||
for (String profileName : profileNames) {
|
for (String profileName : profileNames) {
|
||||||
boolean isCurrent = profileName.equals(currentProfile);
|
boolean isCurrent = profileName.equals(currentProfile);
|
||||||
|
@ -104,7 +105,7 @@ public class ProfilesViewController {
|
||||||
private boolean openProfile(String name, boolean showPopup) {
|
private boolean openProfile(String name, boolean showPopup) {
|
||||||
log.info("Opening profile \"{}\".", name);
|
log.info("Opening profile \"{}\".", name);
|
||||||
try {
|
try {
|
||||||
Profile.load(name);
|
PerfinApp.profileLoader.load(name);
|
||||||
ProfilesStage.closeView();
|
ProfilesStage.closeView();
|
||||||
router.replace("accounts");
|
router.replace("accounts");
|
||||||
if (showPopup) Popups.message("The profile \"" + name + "\" has been loaded.");
|
if (showPopup) Popups.message("The profile \"" + name + "\" has been loaded.");
|
||||||
|
@ -123,11 +124,11 @@ public class ProfilesViewController {
|
||||||
try {
|
try {
|
||||||
FileUtil.deleteDirRecursive(Profile.getDir(name));
|
FileUtil.deleteDirRecursive(Profile.getDir(name));
|
||||||
// Reset the app's "last profile" to the default if it was the deleted profile.
|
// Reset the app's "last profile" to the default if it was the deleted profile.
|
||||||
if (Profile.getLastProfile().equals(name)) {
|
if (ProfileLoader.getLastProfile().equals(name)) {
|
||||||
Profile.saveLastProfile("default");
|
ProfileLoader.saveLastProfile("default");
|
||||||
}
|
}
|
||||||
// If the current profile was deleted, switch to the default.
|
// If the current profile was deleted, switch to the default.
|
||||||
if (Profile.getCurrent() != null && Profile.getCurrent().getName().equals(name)) {
|
if (Profile.getCurrent() != null && Profile.getCurrent().name().equals(name)) {
|
||||||
openProfile("default", true);
|
openProfile("default", true);
|
||||||
}
|
}
|
||||||
refreshAvailableProfiles();
|
refreshAvailableProfiles();
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class TransactionViewController {
|
||||||
amountLabel.setText(CurrencyUtil.formatMoney(transaction.getMoneyAmount()));
|
amountLabel.setText(CurrencyUtil.formatMoney(transaction.getMoneyAmount()));
|
||||||
timestampLabel.setText(DateUtil.formatUTCAsLocalWithZone(transaction.getTimestamp()));
|
timestampLabel.setText(DateUtil.formatUTCAsLocalWithZone(transaction.getTimestamp()));
|
||||||
descriptionLabel.setText(transaction.getDescription());
|
descriptionLabel.setText(transaction.getDescription());
|
||||||
Profile.getCurrent().getDataSource().useRepoAsync(TransactionRepository.class, repo -> {
|
Profile.getCurrent().dataSource().useRepoAsync(TransactionRepository.class, repo -> {
|
||||||
CreditAndDebitAccounts accounts = repo.findLinkedAccounts(transaction.id);
|
CreditAndDebitAccounts accounts = repo.findLinkedAccounts(transaction.id);
|
||||||
List<Attachment> attachments = repo.findAttachments(transaction.id);
|
List<Attachment> attachments = repo.findAttachments(transaction.id);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
|
@ -81,7 +81,7 @@ public class TransactionViewController {
|
||||||
"it's derived from the most recent balance-record, and transactions."
|
"it's derived from the most recent balance-record, and transactions."
|
||||||
);
|
);
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
Profile.getCurrent().getDataSource().useRepo(TransactionRepository.class, repo -> repo.delete(transaction.id));
|
Profile.getCurrent().dataSource().useRepo(TransactionRepository.class, repo -> repo.delete(transaction.id));
|
||||||
router.replace("transactions");
|
router.replace("transactions");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class TransactionsViewController implements RouteSelectionListener {
|
||||||
@Override
|
@Override
|
||||||
public Page<? extends Node> fetchPage(PageRequest pagination) throws Exception {
|
public Page<? extends Node> fetchPage(PageRequest pagination) throws Exception {
|
||||||
Account accountFilter = filterByAccountComboBox.getValue();
|
Account accountFilter = filterByAccountComboBox.getValue();
|
||||||
try (var repo = Profile.getCurrent().getDataSource().getTransactionRepository()) {
|
try (var repo = Profile.getCurrent().dataSource().getTransactionRepository()) {
|
||||||
Page<Transaction> result;
|
Page<Transaction> result;
|
||||||
if (accountFilter == null) {
|
if (accountFilter == null) {
|
||||||
result = repo.findAll(pagination);
|
result = repo.findAll(pagination);
|
||||||
|
@ -80,7 +80,7 @@ public class TransactionsViewController implements RouteSelectionListener {
|
||||||
@Override
|
@Override
|
||||||
public int getTotalCount() throws Exception {
|
public int getTotalCount() throws Exception {
|
||||||
Account accountFilter = filterByAccountComboBox.getValue();
|
Account accountFilter = filterByAccountComboBox.getValue();
|
||||||
try (var repo = Profile.getCurrent().getDataSource().getTransactionRepository()) {
|
try (var repo = Profile.getCurrent().dataSource().getTransactionRepository()) {
|
||||||
if (accountFilter == null) {
|
if (accountFilter == null) {
|
||||||
return (int) repo.countAll();
|
return (int) repo.countAll();
|
||||||
} else {
|
} else {
|
||||||
|
@ -124,7 +124,7 @@ public class TransactionsViewController implements RouteSelectionListener {
|
||||||
transactionsVBox.getChildren().clear(); // Clear the transactions before reload initially.
|
transactionsVBox.getChildren().clear(); // Clear the transactions before reload initially.
|
||||||
|
|
||||||
// Refresh account filter options.
|
// Refresh account filter options.
|
||||||
Profile.getCurrent().getDataSource().useRepoAsync(AccountRepository.class, repo -> {
|
Profile.getCurrent().dataSource().useRepoAsync(AccountRepository.class, repo -> {
|
||||||
List<Account> accounts = repo.findAll(PageRequest.unpaged(Sort.asc("name"))).items();
|
List<Account> accounts = repo.findAll(PageRequest.unpaged(Sort.asc("name"))).items();
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
filterByAccountComboBox.setAccounts(accounts);
|
filterByAccountComboBox.setAccounts(accounts);
|
||||||
|
@ -135,7 +135,7 @@ public class TransactionsViewController implements RouteSelectionListener {
|
||||||
|
|
||||||
// If a transaction id is given in the route context, navigate to the page it's on and select it.
|
// If a transaction id is given in the route context, navigate to the page it's on and select it.
|
||||||
if (context instanceof RouteContext ctx && ctx.selectedTransactionId != null) {
|
if (context instanceof RouteContext ctx && ctx.selectedTransactionId != null) {
|
||||||
Profile.getCurrent().getDataSource().useRepoAsync(TransactionRepository.class, repo -> {
|
Profile.getCurrent().dataSource().useRepoAsync(TransactionRepository.class, repo -> {
|
||||||
repo.findById(ctx.selectedTransactionId).ifPresent(tx -> {
|
repo.findById(ctx.selectedTransactionId).ifPresent(tx -> {
|
||||||
long offset = repo.countAllAfter(tx.id);
|
long offset = repo.countAllAfter(tx.id);
|
||||||
int pageNumber = (int) (offset / paginationControls.getItemsPerPage()) + 1;
|
int pageNumber = (int) (offset / paginationControls.getItemsPerPage()) + 1;
|
||||||
|
@ -161,7 +161,7 @@ public class TransactionsViewController implements RouteSelectionListener {
|
||||||
File file = fileChooser.showSaveDialog(detailPanel.getScene().getWindow());
|
File file = fileChooser.showSaveDialog(detailPanel.getScene().getWindow());
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
try (
|
try (
|
||||||
var repo = Profile.getCurrent().getDataSource().getTransactionRepository();
|
var repo = Profile.getCurrent().dataSource().getTransactionRepository();
|
||||||
var out = new PrintWriter(file, StandardCharsets.UTF_8)
|
var out = new PrintWriter(file, StandardCharsets.UTF_8)
|
||||||
) {
|
) {
|
||||||
out.println("id,utc-timestamp,amount,currency,description");
|
out.println("id,utc-timestamp,amount,currency,description");
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.andrewlalis.perfin.data;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that defines the data source factory, a component responsible for
|
||||||
|
* obtaining a data source, and performing some introspection around that data
|
||||||
|
* source before one is obtained.
|
||||||
|
*/
|
||||||
|
public interface DataSourceFactory {
|
||||||
|
DataSource getDataSource(String profileName) throws ProfileLoadException;
|
||||||
|
|
||||||
|
enum SchemaStatus {
|
||||||
|
UP_TO_DATE,
|
||||||
|
NEEDS_MIGRATION,
|
||||||
|
INCOMPATIBLE
|
||||||
|
}
|
||||||
|
SchemaStatus getSchemaStatus(String profileName) throws IOException;
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package com.andrewlalis.perfin.data.impl;
|
package com.andrewlalis.perfin.data.impl;
|
||||||
|
|
||||||
import com.andrewlalis.perfin.data.DataSource;
|
import com.andrewlalis.perfin.data.DataSource;
|
||||||
|
import com.andrewlalis.perfin.data.DataSourceFactory;
|
||||||
import com.andrewlalis.perfin.data.ProfileLoadException;
|
import com.andrewlalis.perfin.data.ProfileLoadException;
|
||||||
import com.andrewlalis.perfin.data.impl.migration.Migration;
|
import com.andrewlalis.perfin.data.impl.migration.Migration;
|
||||||
import com.andrewlalis.perfin.data.impl.migration.Migrations;
|
import com.andrewlalis.perfin.data.impl.migration.Migrations;
|
||||||
|
@ -23,7 +24,7 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* Component that's responsible for obtaining a JDBC data source for a profile.
|
* Component that's responsible for obtaining a JDBC data source for a profile.
|
||||||
*/
|
*/
|
||||||
public class JdbcDataSourceFactory {
|
public class JdbcDataSourceFactory implements DataSourceFactory {
|
||||||
private static final Logger log = LoggerFactory.getLogger(JdbcDataSourceFactory.class);
|
private static final Logger log = LoggerFactory.getLogger(JdbcDataSourceFactory.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,6 +60,13 @@ public class JdbcDataSourceFactory {
|
||||||
return new JdbcDataSource(getJdbcUrl(profileName), Profile.getContentDir(profileName));
|
return new JdbcDataSource(getJdbcUrl(profileName), Profile.getContentDir(profileName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SchemaStatus getSchemaStatus(String profileName) throws IOException {
|
||||||
|
int existingSchemaVersion = getSchemaVersion(profileName);
|
||||||
|
if (existingSchemaVersion == SCHEMA_VERSION) return SchemaStatus.UP_TO_DATE;
|
||||||
|
if (existingSchemaVersion < SCHEMA_VERSION) return SchemaStatus.NEEDS_MIGRATION;
|
||||||
|
return SchemaStatus.INCOMPATIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
private void createNewDatabase(String profileName) throws ProfileLoadException {
|
private void createNewDatabase(String profileName) throws ProfileLoadException {
|
||||||
log.info("Creating new database for profile {}.", profileName);
|
log.info("Creating new database for profile {}.", profileName);
|
||||||
JdbcDataSource dataSource = new JdbcDataSource(getJdbcUrl(profileName), Profile.getContentDir(profileName));
|
JdbcDataSource dataSource = new JdbcDataSource(getJdbcUrl(profileName), Profile.getContentDir(profileName));
|
||||||
|
|
|
@ -2,23 +2,16 @@ package com.andrewlalis.perfin.model;
|
||||||
|
|
||||||
import com.andrewlalis.perfin.PerfinApp;
|
import com.andrewlalis.perfin.PerfinApp;
|
||||||
import com.andrewlalis.perfin.data.DataSource;
|
import com.andrewlalis.perfin.data.DataSource;
|
||||||
import com.andrewlalis.perfin.data.ProfileLoadException;
|
|
||||||
import com.andrewlalis.perfin.data.impl.JdbcDataSourceFactory;
|
|
||||||
import com.andrewlalis.perfin.data.util.FileUtil;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.lang.ref.WeakReference;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.HashSet;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static com.andrewlalis.perfin.data.util.FileUtil.copyResourceFile;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A profile is essentially a complete set of data that the application can
|
* A profile is essentially a complete set of data that the application can
|
||||||
* operate on, sort of like a save file or user account. The profile contains
|
* operate on, sort of like a save file or user account. The profile contains
|
||||||
|
@ -36,34 +29,17 @@ import static com.andrewlalis.perfin.data.util.FileUtil.copyResourceFile;
|
||||||
* unloaded.
|
* unloaded.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public class Profile {
|
public record Profile(String name, Properties settings, DataSource dataSource) {
|
||||||
private static final Logger log = LoggerFactory.getLogger(Profile.class);
|
private static final Logger log = LoggerFactory.getLogger(Profile.class);
|
||||||
|
|
||||||
private static Profile current;
|
private static Profile current;
|
||||||
private static final List<Consumer<Profile>> profileLoadListeners = new ArrayList<>();
|
private static final Set<WeakReference<Consumer<Profile>>> currentProfileListeners = new HashSet<>();
|
||||||
|
|
||||||
private final String name;
|
@Override
|
||||||
private final Properties settings;
|
public String toString() {
|
||||||
private final DataSource dataSource;
|
|
||||||
|
|
||||||
private Profile(String name, Properties settings, DataSource dataSource) {
|
|
||||||
this.name = name;
|
|
||||||
this.settings = settings;
|
|
||||||
this.dataSource = dataSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Properties getSettings() {
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataSource getDataSource() {
|
|
||||||
return dataSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Path getDir(String name) {
|
public static Path getDir(String name) {
|
||||||
return PerfinApp.APP_DIR.resolve(name);
|
return PerfinApp.APP_DIR.resolve(name);
|
||||||
}
|
}
|
||||||
|
@ -80,79 +56,22 @@ public class Profile {
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setCurrent(Profile profile) {
|
||||||
|
current = profile;
|
||||||
|
for (var ref : currentProfileListeners) {
|
||||||
|
Consumer<Profile> consumer = ref.get();
|
||||||
|
if (consumer != null) {
|
||||||
|
consumer.accept(profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentProfileListeners.removeIf(ref -> ref.get() == null);
|
||||||
|
}
|
||||||
|
|
||||||
public static void whenLoaded(Consumer<Profile> consumer) {
|
public static void whenLoaded(Consumer<Profile> consumer) {
|
||||||
if (current != null) {
|
if (current != null) {
|
||||||
consumer.accept(current);
|
consumer.accept(current);
|
||||||
} else {
|
|
||||||
profileLoadListeners.add(consumer);
|
|
||||||
}
|
}
|
||||||
}
|
currentProfileListeners.add(new WeakReference<>(consumer));
|
||||||
|
|
||||||
public static List<String> getAvailableProfiles() {
|
|
||||||
try (var files = Files.list(PerfinApp.APP_DIR)) {
|
|
||||||
return files.filter(Files::isDirectory)
|
|
||||||
.map(path -> path.getFileName().toString())
|
|
||||||
.sorted().toList();
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Failed to get a list of available profiles.", e);
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getLastProfile() {
|
|
||||||
Path lastProfileFile = PerfinApp.APP_DIR.resolve("last-profile.txt");
|
|
||||||
if (Files.exists(lastProfileFile)) {
|
|
||||||
try {
|
|
||||||
String s = Files.readString(lastProfileFile).strip().toLowerCase();
|
|
||||||
if (!s.isBlank()) return s;
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Failed to read " + lastProfileFile, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "default";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void saveLastProfile(String name) {
|
|
||||||
Path lastProfileFile = PerfinApp.APP_DIR.resolve("last-profile.txt");
|
|
||||||
try {
|
|
||||||
Files.writeString(lastProfileFile, name);
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Failed to write " + lastProfileFile, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void loadLast() throws ProfileLoadException {
|
|
||||||
load(getLastProfile());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void load(String name) throws ProfileLoadException {
|
|
||||||
if (Files.notExists(getDir(name))) {
|
|
||||||
try {
|
|
||||||
initProfileDir(name);
|
|
||||||
} catch (IOException e) {
|
|
||||||
FileUtil.deleteIfPossible(getDir(name));
|
|
||||||
throw new ProfileLoadException("Failed to initialize new profile directory.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Properties settings = new Properties();
|
|
||||||
try (var in = Files.newInputStream(getSettingsFile(name))) {
|
|
||||||
settings.load(in);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ProfileLoadException("Failed to load profile settings.", e);
|
|
||||||
}
|
|
||||||
current = new Profile(name, settings, new JdbcDataSourceFactory().getDataSource(name));
|
|
||||||
saveLastProfile(current.getName());
|
|
||||||
for (var c : profileLoadListeners) {
|
|
||||||
c.accept(current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void initProfileDir(String name) throws IOException {
|
|
||||||
Files.createDirectory(getDir(name));
|
|
||||||
copyResourceFile("/text/profileDirReadme.txt", getDir(name).resolve("README.txt"));
|
|
||||||
copyResourceFile("/text/defaultProfileSettings.properties", getSettingsFile(name));
|
|
||||||
Files.createDirectory(getContentDir(name));
|
|
||||||
copyResourceFile("/text/contentDirReadme.txt", getContentDir(name).resolve("README.txt"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean validateName(String name) {
|
public static boolean validateName(String name) {
|
||||||
|
@ -160,9 +79,4 @@ public class Profile {
|
||||||
name.matches("\\w+") &&
|
name.matches("\\w+") &&
|
||||||
name.toLowerCase().equals(name);
|
name.toLowerCase().equals(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package com.andrewlalis.perfin.model;
|
||||||
|
|
||||||
|
import com.andrewlalis.perfin.PerfinApp;
|
||||||
|
import com.andrewlalis.perfin.data.DataSourceFactory;
|
||||||
|
import com.andrewlalis.perfin.data.ProfileLoadException;
|
||||||
|
import com.andrewlalis.perfin.data.util.FileUtil;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import static com.andrewlalis.perfin.data.util.FileUtil.copyResourceFile;
|
||||||
|
|
||||||
|
public class ProfileLoader {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ProfileLoader.class);
|
||||||
|
|
||||||
|
private final Window window;
|
||||||
|
private final DataSourceFactory dataSourceFactory;
|
||||||
|
|
||||||
|
public ProfileLoader(Window window, DataSourceFactory dataSourceFactory) {
|
||||||
|
this.window = window;
|
||||||
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Profile load(String name) throws ProfileLoadException {
|
||||||
|
if (Files.notExists(Profile.getDir(name))) {
|
||||||
|
try {
|
||||||
|
initProfileDir(name);
|
||||||
|
} catch (IOException e) {
|
||||||
|
FileUtil.deleteIfPossible(Profile.getDir(name));
|
||||||
|
throw new ProfileLoadException("Failed to initialize new profile directory.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Properties settings = new Properties();
|
||||||
|
try (var in = Files.newInputStream(Profile.getSettingsFile(name))) {
|
||||||
|
settings.load(in);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ProfileLoadException("Failed to load profile settings.", e);
|
||||||
|
}
|
||||||
|
return new Profile(name, settings, dataSourceFactory.getDataSource(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> getAvailableProfiles() {
|
||||||
|
try (var files = Files.list(PerfinApp.APP_DIR)) {
|
||||||
|
return files.filter(Files::isDirectory)
|
||||||
|
.map(path -> path.getFileName().toString())
|
||||||
|
.sorted().toList();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Failed to get a list of available profiles.", e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLastProfile() {
|
||||||
|
Path lastProfileFile = PerfinApp.APP_DIR.resolve("last-profile.txt");
|
||||||
|
if (Files.exists(lastProfileFile)) {
|
||||||
|
try {
|
||||||
|
String s = Files.readString(lastProfileFile).strip().toLowerCase();
|
||||||
|
if (!s.isBlank()) return s;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Failed to read " + lastProfileFile, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void saveLastProfile(String name) {
|
||||||
|
Path lastProfileFile = PerfinApp.APP_DIR.resolve("last-profile.txt");
|
||||||
|
try {
|
||||||
|
Files.writeString(lastProfileFile, name);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Failed to write " + lastProfileFile, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
private static void initProfileDir(String name) throws IOException {
|
||||||
|
Files.createDirectory(Profile.getDir(name));
|
||||||
|
copyResourceFile("/text/profileDirReadme.txt", Profile.getDir(name).resolve("README.txt"));
|
||||||
|
copyResourceFile("/text/defaultProfileSettings.properties", Profile.getSettingsFile(name));
|
||||||
|
Files.createDirectory(Profile.getContentDir(name));
|
||||||
|
copyResourceFile("/text/contentDirReadme.txt", Profile.getContentDir(name).resolve("README.txt"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,7 +110,7 @@ public class AccountSelectionBox extends ComboBox<Account> {
|
||||||
|
|
||||||
nameLabel.setText(item.getName() + " (" + item.getAccountNumberSuffix() + ")");
|
nameLabel.setText(item.getName() + " (" + item.getAccountNumberSuffix() + ")");
|
||||||
if (showBalanceProp.get()) {
|
if (showBalanceProp.get()) {
|
||||||
Profile.getCurrent().getDataSource().useRepoAsync(AccountRepository.class, repo -> {
|
Profile.getCurrent().dataSource().useRepoAsync(AccountRepository.class, repo -> {
|
||||||
BigDecimal balance = repo.deriveCurrentBalance(item.id);
|
BigDecimal balance = repo.deriveCurrentBalance(item.id);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
balanceLabel.setText(CurrencyUtil.formatMoney(new MoneyValue(balance, item.getCurrency())));
|
balanceLabel.setText(CurrencyUtil.formatMoney(new MoneyValue(balance, item.getCurrency())));
|
||||||
|
|
|
@ -81,7 +81,7 @@ public class AccountTile extends BorderPane {
|
||||||
Label balanceLabel = new Label("Computing balance...");
|
Label balanceLabel = new Label("Computing balance...");
|
||||||
balanceLabel.getStyleClass().addAll("mono-font");
|
balanceLabel.getStyleClass().addAll("mono-font");
|
||||||
balanceLabel.setDisable(true);
|
balanceLabel.setDisable(true);
|
||||||
Profile.getCurrent().getDataSource().useRepoAsync(AccountRepository.class, repo -> {
|
Profile.getCurrent().dataSource().useRepoAsync(AccountRepository.class, repo -> {
|
||||||
BigDecimal balance = repo.deriveCurrentBalance(account.id);
|
BigDecimal balance = repo.deriveCurrentBalance(account.id);
|
||||||
String text = CurrencyUtil.formatMoney(new MoneyValue(balance, account.getCurrency()));
|
String text = CurrencyUtil.formatMoney(new MoneyValue(balance, account.getCurrency()));
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class AttachmentPreview extends BorderPane {
|
||||||
boolean showDocIcon = true;
|
boolean showDocIcon = true;
|
||||||
Set<String> imageTypes = Set.of("image/png", "image/jpeg", "image/gif", "image/bmp");
|
Set<String> imageTypes = Set.of("image/png", "image/jpeg", "image/gif", "image/bmp");
|
||||||
if (imageTypes.contains(attachment.getContentType())) {
|
if (imageTypes.contains(attachment.getContentType())) {
|
||||||
try (var in = Files.newInputStream(attachment.getPath(Profile.getContentDir(Profile.getCurrent().getName())))) {
|
try (var in = Files.newInputStream(attachment.getPath(Profile.getContentDir(Profile.getCurrent().name())))) {
|
||||||
Image img = new Image(in, IMAGE_SIZE, IMAGE_SIZE, true, true);
|
Image img = new Image(in, IMAGE_SIZE, IMAGE_SIZE, true, true);
|
||||||
contentContainer.setCenter(new ImageView(img));
|
contentContainer.setCenter(new ImageView(img));
|
||||||
showDocIcon = false;
|
showDocIcon = false;
|
||||||
|
@ -69,7 +69,7 @@ public class AttachmentPreview extends BorderPane {
|
||||||
this.setCenter(stackPane);
|
this.setCenter(stackPane);
|
||||||
this.setOnMouseClicked(event -> {
|
this.setOnMouseClicked(event -> {
|
||||||
if (this.isHover()) {
|
if (this.isHover()) {
|
||||||
Path filePath = attachment.getPath(Profile.getContentDir(Profile.getCurrent().getName()));
|
Path filePath = attachment.getPath(Profile.getContentDir(Profile.getCurrent().name()));
|
||||||
PerfinApp.instance.getHostServices().showDocument(filePath.toAbsolutePath().toUri().toString());
|
PerfinApp.instance.getHostServices().showDocument(filePath.toAbsolutePath().toUri().toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -100,7 +100,7 @@ public class TransactionTile extends BorderPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<CreditAndDebitAccounts> getCreditAndDebitAccounts(Transaction transaction) {
|
private CompletableFuture<CreditAndDebitAccounts> getCreditAndDebitAccounts(Transaction transaction) {
|
||||||
return Profile.getCurrent().getDataSource().mapRepoAsync(
|
return Profile.getCurrent().dataSource().mapRepoAsync(
|
||||||
TransactionRepository.class,
|
TransactionRepository.class,
|
||||||
repo -> repo.findLinkedAccounts(transaction.id)
|
repo -> repo.findLinkedAccounts(transaction.id)
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue