Added credit limit and general purpose credit card properties to only credit card accounts.
This commit is contained in:
parent
2abbd6ca43
commit
b74119a233
|
@ -36,6 +36,7 @@ public class AccountViewController implements RouteSelectionListener {
|
||||||
private final ObservableValue<Boolean> accountArchived = accountProperty.map(a -> a != null && a.isArchived());
|
private final ObservableValue<Boolean> accountArchived = accountProperty.map(a -> a != null && a.isArchived());
|
||||||
private final StringProperty balanceTextProperty = new SimpleStringProperty(null);
|
private final StringProperty balanceTextProperty = new SimpleStringProperty(null);
|
||||||
private final StringProperty assetValueTextProperty = new SimpleStringProperty(null);
|
private final StringProperty assetValueTextProperty = new SimpleStringProperty(null);
|
||||||
|
private final StringProperty creditLimitTextProperty = new SimpleStringProperty(null);
|
||||||
|
|
||||||
@FXML public Label titleLabel;
|
@FXML public Label titleLabel;
|
||||||
@FXML public Label accountNameLabel;
|
@FXML public Label accountNameLabel;
|
||||||
|
@ -45,6 +46,8 @@ public class AccountViewController implements RouteSelectionListener {
|
||||||
@FXML public Label accountBalanceLabel;
|
@FXML public Label accountBalanceLabel;
|
||||||
@FXML public PropertiesPane assetValuePane;
|
@FXML public PropertiesPane assetValuePane;
|
||||||
@FXML public Label latestAssetsValueLabel;
|
@FXML public Label latestAssetsValueLabel;
|
||||||
|
@FXML public PropertiesPane creditCardPropertiesPane;
|
||||||
|
@FXML public Label creditLimitLabel;
|
||||||
@FXML public PropertiesPane descriptionPane;
|
@FXML public PropertiesPane descriptionPane;
|
||||||
@FXML public Text accountDescriptionText;
|
@FXML public Text accountDescriptionText;
|
||||||
|
|
||||||
|
@ -65,10 +68,15 @@ public class AccountViewController implements RouteSelectionListener {
|
||||||
var hasDescription = accountProperty.map(a -> a.getDescription() != null);
|
var hasDescription = accountProperty.map(a -> a.getDescription() != null);
|
||||||
BindingUtil.bindManagedAndVisible(descriptionPane, hasDescription);
|
BindingUtil.bindManagedAndVisible(descriptionPane, hasDescription);
|
||||||
accountBalanceLabel.textProperty().bind(balanceTextProperty);
|
accountBalanceLabel.textProperty().bind(balanceTextProperty);
|
||||||
|
|
||||||
var isBrokerageAccount = accountProperty.map(a -> a.getType() == AccountType.BROKERAGE);
|
var isBrokerageAccount = accountProperty.map(a -> a.getType() == AccountType.BROKERAGE);
|
||||||
BindingUtil.bindManagedAndVisible(assetValuePane, isBrokerageAccount);
|
BindingUtil.bindManagedAndVisible(assetValuePane, isBrokerageAccount);
|
||||||
latestAssetsValueLabel.textProperty().bind(assetValueTextProperty);
|
latestAssetsValueLabel.textProperty().bind(assetValueTextProperty);
|
||||||
|
|
||||||
|
var isCreditCardAccount = accountProperty.map(a -> a.getType() == AccountType.CREDIT_CARD);
|
||||||
|
BindingUtil.bindManagedAndVisible(creditCardPropertiesPane, isCreditCardAccount);
|
||||||
|
creditLimitLabel.textProperty().bind(creditLimitTextProperty);
|
||||||
|
|
||||||
actionsBox.getChildren().forEach(node -> {
|
actionsBox.getChildren().forEach(node -> {
|
||||||
Button button = (Button) node;
|
Button button = (Button) node;
|
||||||
ObservableValue<Boolean> buttonDisabled = accountArchived;
|
ObservableValue<Boolean> buttonDisabled = accountArchived;
|
||||||
|
@ -126,6 +134,22 @@ public class AccountViewController implements RouteSelectionListener {
|
||||||
repo -> repo.getNearestAssetValue(account.id)
|
repo -> repo.getNearestAssetValue(account.id)
|
||||||
).thenApply(value -> CurrencyUtil.formatMoney(new MoneyValue(value, account.getCurrency())))
|
).thenApply(value -> CurrencyUtil.formatMoney(new MoneyValue(value, account.getCurrency())))
|
||||||
.thenAccept(text -> Platform.runLater(() -> assetValueTextProperty.set(text)));
|
.thenAccept(text -> Platform.runLater(() -> assetValueTextProperty.set(text)));
|
||||||
|
} else if (account.getType() == AccountType.CREDIT_CARD) {
|
||||||
|
Profile.getCurrent().dataSource().mapRepoAsync(
|
||||||
|
AccountRepository.class,
|
||||||
|
repo -> repo.getCreditCardProperties(account.id)
|
||||||
|
).thenAccept(props -> Platform.runLater(() -> {
|
||||||
|
if (props == null) {
|
||||||
|
creditLimitTextProperty.set("No credit card info.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.creditLimit() == null) {
|
||||||
|
creditLimitTextProperty.set("No credit limit set.");
|
||||||
|
} else {
|
||||||
|
MoneyValue money = new MoneyValue(props.creditLimit(), account.getCurrency());
|
||||||
|
creditLimitTextProperty.set(CurrencyUtil.formatMoney(money));
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
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.AccountRepository;
|
||||||
import com.andrewlalis.perfin.data.util.CurrencyUtil;
|
import com.andrewlalis.perfin.data.util.CurrencyUtil;
|
||||||
import com.andrewlalis.perfin.model.*;
|
import com.andrewlalis.perfin.model.*;
|
||||||
|
import com.andrewlalis.perfin.view.BindingUtil;
|
||||||
import com.andrewlalis.perfin.view.component.PropertiesPane;
|
import com.andrewlalis.perfin.view.component.PropertiesPane;
|
||||||
import com.andrewlalis.perfin.view.component.validation.ValidationApplier;
|
import com.andrewlalis.perfin.view.component.validation.ValidationApplier;
|
||||||
import com.andrewlalis.perfin.view.component.validation.validators.CurrencyAmountValidator;
|
import com.andrewlalis.perfin.view.component.validation.validators.CurrencyAmountValidator;
|
||||||
import com.andrewlalis.perfin.view.component.validation.validators.PredicateValidator;
|
import com.andrewlalis.perfin.view.component.validation.validators.PredicateValidator;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.BooleanExpression;
|
import javafx.beans.binding.BooleanExpression;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
@ -35,6 +38,7 @@ public class EditAccountController implements RouteSelectionListener {
|
||||||
@FXML public TextField accountNumberField;
|
@FXML public TextField accountNumberField;
|
||||||
@FXML public ComboBox<Currency> accountCurrencyComboBox;
|
@FXML public ComboBox<Currency> accountCurrencyComboBox;
|
||||||
@FXML public ChoiceBox<AccountType> accountTypeChoiceBox;
|
@FXML public ChoiceBox<AccountType> accountTypeChoiceBox;
|
||||||
|
@FXML public TextField creditLimitField;
|
||||||
@FXML public TextArea descriptionField;
|
@FXML public TextArea descriptionField;
|
||||||
@FXML public PropertiesPane initialBalanceContent;
|
@FXML public PropertiesPane initialBalanceContent;
|
||||||
@FXML public TextField initialBalanceField;
|
@FXML public TextField initialBalanceField;
|
||||||
|
@ -57,12 +61,25 @@ public class EditAccountController implements RouteSelectionListener {
|
||||||
new CurrencyAmountValidator(() -> accountCurrencyComboBox.getValue(), true, false)
|
new CurrencyAmountValidator(() -> accountCurrencyComboBox.getValue(), true, false)
|
||||||
).attachToTextField(initialBalanceField, accountCurrencyComboBox.valueProperty());
|
).attachToTextField(initialBalanceField, accountCurrencyComboBox.valueProperty());
|
||||||
|
|
||||||
|
var isEditingCreditCardAccount = accountTypeChoiceBox.valueProperty().isEqualTo(AccountType.CREDIT_CARD);
|
||||||
|
BindingUtil.bindManagedAndVisible(creditLimitField, isEditingCreditCardAccount);
|
||||||
|
var creditLimitValid = new ValidationApplier<>(new CurrencyAmountValidator(
|
||||||
|
() -> accountCurrencyComboBox.getValue(),
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)).validatedInitially().attachToTextField(creditLimitField)
|
||||||
|
.or(isEditingCreditCardAccount.not());
|
||||||
|
|
||||||
var descriptionValid = new ValidationApplier<>(new PredicateValidator<String>()
|
var descriptionValid = new ValidationApplier<>(new PredicateValidator<String>()
|
||||||
.addPredicate(s -> s == null || s.strip().length() <= Account.DESCRIPTION_MAX_LENGTH, "Description is too long.")
|
.addPredicate(s -> s == null || s.strip().length() <= Account.DESCRIPTION_MAX_LENGTH, "Description is too long.")
|
||||||
).attach(descriptionField, descriptionField.textProperty());
|
).attach(descriptionField, descriptionField.textProperty());
|
||||||
|
|
||||||
// Combine validity of all fields for an expression that determines if the whole form is valid.
|
// Combine validity of all fields for an expression that determines if the whole form is valid.
|
||||||
BooleanExpression formValid = nameValid.and(numberValid).and(balanceValid.or(creatingNewAccount.not())).and(descriptionValid);
|
BooleanExpression formValid = nameValid
|
||||||
|
.and(numberValid)
|
||||||
|
.and(balanceValid.or(creatingNewAccount.not()))
|
||||||
|
.and(descriptionValid)
|
||||||
|
.and(creditLimitValid);
|
||||||
saveButton.disableProperty().bind(formValid.not());
|
saveButton.disableProperty().bind(formValid.not());
|
||||||
|
|
||||||
List<Currency> priorityCurrencies = Stream.of("USD", "EUR", "GBP", "CAD", "AUD")
|
List<Currency> priorityCurrencies = Stream.of("USD", "EUR", "GBP", "CAD", "AUD")
|
||||||
|
@ -111,6 +128,10 @@ public class EditAccountController implements RouteSelectionListener {
|
||||||
description = description.strip();
|
description = description.strip();
|
||||||
if (description.isBlank()) description = null;
|
if (description.isBlank()) description = null;
|
||||||
}
|
}
|
||||||
|
BigDecimal creditLimit = null;
|
||||||
|
if (type == AccountType.CREDIT_CARD && creditLimitField.getText() != null && !creditLimitField.getText().isBlank()) {
|
||||||
|
creditLimit = new BigDecimal(creditLimitField.getText());
|
||||||
|
}
|
||||||
try (
|
try (
|
||||||
var accountRepo = Profile.getCurrent().dataSource().getAccountRepository();
|
var accountRepo = Profile.getCurrent().dataSource().getAccountRepository();
|
||||||
var balanceRepo = Profile.getCurrent().dataSource().getBalanceRecordRepository()
|
var balanceRepo = Profile.getCurrent().dataSource().getBalanceRecordRepository()
|
||||||
|
@ -130,12 +151,18 @@ public class EditAccountController implements RouteSelectionListener {
|
||||||
if (success) {
|
if (success) {
|
||||||
long id = accountRepo.insert(type, number, name, currency, description);
|
long id = accountRepo.insert(type, number, name, currency, description);
|
||||||
balanceRepo.insert(LocalDateTime.now(ZoneOffset.UTC), id, BalanceRecordType.CASH, initialBalance, currency, attachments);
|
balanceRepo.insert(LocalDateTime.now(ZoneOffset.UTC), id, BalanceRecordType.CASH, initialBalance, currency, attachments);
|
||||||
|
if (type == AccountType.CREDIT_CARD && creditLimit != null) {
|
||||||
|
accountRepo.saveCreditCardProperties(new CreditCardProperties(id, creditLimit));
|
||||||
|
}
|
||||||
// Once we create the new account, go to the account.
|
// Once we create the new account, go to the account.
|
||||||
Account newAccount = accountRepo.findById(id).orElseThrow();
|
Account newAccount = accountRepo.findById(id).orElseThrow();
|
||||||
router.replace("account", newAccount);
|
router.replace("account", newAccount);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
accountRepo.update(account.id, type, number, name, currency, description);
|
accountRepo.update(account.id, type, number, name, currency, description);
|
||||||
|
if (type == AccountType.CREDIT_CARD) {
|
||||||
|
accountRepo.saveCreditCardProperties(new CreditCardProperties(account.id, creditLimit));
|
||||||
|
}
|
||||||
Account updatedAccount = accountRepo.findById(account.id).orElseThrow();
|
Account updatedAccount = accountRepo.findById(account.id).orElseThrow();
|
||||||
router.replace("account", updatedAccount);
|
router.replace("account", updatedAccount);
|
||||||
}
|
}
|
||||||
|
@ -158,12 +185,28 @@ public class EditAccountController implements RouteSelectionListener {
|
||||||
accountCurrencyComboBox.getSelectionModel().select(Currency.getInstance("USD"));
|
accountCurrencyComboBox.getSelectionModel().select(Currency.getInstance("USD"));
|
||||||
initialBalanceField.setText(String.format("%.02f", 0f));
|
initialBalanceField.setText(String.format("%.02f", 0f));
|
||||||
descriptionField.setText(null);
|
descriptionField.setText(null);
|
||||||
|
|
||||||
|
creditLimitField.setText(null);
|
||||||
} else {
|
} else {
|
||||||
accountNameField.setText(account.getName());
|
accountNameField.setText(account.getName());
|
||||||
accountNumberField.setText(account.getAccountNumber());
|
accountNumberField.setText(account.getAccountNumber());
|
||||||
accountTypeChoiceBox.getSelectionModel().select(account.getType());
|
accountTypeChoiceBox.getSelectionModel().select(account.getType());
|
||||||
accountCurrencyComboBox.getSelectionModel().select(account.getCurrency());
|
accountCurrencyComboBox.getSelectionModel().select(account.getCurrency());
|
||||||
descriptionField.setText(account.getDescription());
|
descriptionField.setText(account.getDescription());
|
||||||
|
|
||||||
|
// Fetch the account's credit limit if it's a credit card account.
|
||||||
|
if (account.getType() == AccountType.CREDIT_CARD) {
|
||||||
|
Profile.getCurrent().dataSource().mapRepoAsync(
|
||||||
|
AccountRepository.class,
|
||||||
|
repo -> repo.getCreditCardProperties(account.id)
|
||||||
|
).thenAccept(props -> Platform.runLater(() -> {
|
||||||
|
if (props != null && props.creditLimit() != null) {
|
||||||
|
creditLimitField.setText(CurrencyUtil.formatMoneyAsBasicNumber(new MoneyValue(props.creditLimit(), account.getCurrency())));
|
||||||
|
} else {
|
||||||
|
creditLimitField.setText(null);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ 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.model.Account;
|
import com.andrewlalis.perfin.model.Account;
|
||||||
import com.andrewlalis.perfin.model.AccountType;
|
import com.andrewlalis.perfin.model.AccountType;
|
||||||
|
import com.andrewlalis.perfin.model.CreditCardProperties;
|
||||||
import com.andrewlalis.perfin.model.Timestamped;
|
import com.andrewlalis.perfin.model.Timestamped;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
@ -23,6 +24,8 @@ public interface AccountRepository extends Repository, AutoCloseable {
|
||||||
List<Account> findTopNRecentlyActive(int n, int daysSinceLastActive);
|
List<Account> findTopNRecentlyActive(int n, int daysSinceLastActive);
|
||||||
List<Account> findAllByCurrency(Currency currency);
|
List<Account> findAllByCurrency(Currency currency);
|
||||||
Optional<Account> findById(long id);
|
Optional<Account> findById(long id);
|
||||||
|
CreditCardProperties getCreditCardProperties(long id);
|
||||||
|
void saveCreditCardProperties(CreditCardProperties properties);
|
||||||
void update(long accountId, AccountType type, String accountNumber, String name, Currency currency, String description);
|
void update(long accountId, AccountType type, String accountNumber, String name, Currency currency, String description);
|
||||||
void delete(Account account);
|
void delete(Account account);
|
||||||
void archive(long accountId);
|
void archive(long accountId);
|
||||||
|
|
|
@ -25,7 +25,6 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
|
||||||
@Override
|
@Override
|
||||||
public long insert(AccountType type, String accountNumber, String name, Currency currency, String description) {
|
public long insert(AccountType type, String accountNumber, String name, Currency currency, String description) {
|
||||||
return DbUtil.doTransaction(conn, () -> {
|
return DbUtil.doTransaction(conn, () -> {
|
||||||
|
|
||||||
long accountId = DbUtil.insertOne(
|
long accountId = DbUtil.insertOne(
|
||||||
conn,
|
conn,
|
||||||
"INSERT INTO account (created_at, account_type, account_number, name, currency, description) VALUES (?, ?, ?, ?, ?, ?)",
|
"INSERT INTO account (created_at, account_type, account_number, name, currency, description) VALUES (?, ?, ?, ?, ?, ?)",
|
||||||
|
@ -36,6 +35,10 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
|
||||||
currency.getCurrencyCode(),
|
currency.getCurrencyCode(),
|
||||||
description
|
description
|
||||||
);
|
);
|
||||||
|
// If it's a credit card account, preemptively create a credit card properties record.
|
||||||
|
if (type == AccountType.CREDIT_CARD) {
|
||||||
|
saveCreditCardProperties(new CreditCardProperties(accountId, null));
|
||||||
|
}
|
||||||
// Insert a history item indicating the creation of the account.
|
// Insert a history item indicating the creation of the account.
|
||||||
HistoryRepository historyRepo = new JdbcHistoryRepository(conn);
|
HistoryRepository historyRepo = new JdbcHistoryRepository(conn);
|
||||||
long historyId = historyRepo.getOrCreateHistoryForAccount(accountId);
|
long historyId = historyRepo.getOrCreateHistoryForAccount(accountId);
|
||||||
|
@ -113,6 +116,48 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
|
||||||
return DbUtil.findById(conn, "SELECT * FROM account WHERE id = ?", id, JdbcAccountRepository::parseAccount);
|
return DbUtil.findById(conn, "SELECT * FROM account WHERE id = ?", id, JdbcAccountRepository::parseAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CreditCardProperties getCreditCardProperties(long id) {
|
||||||
|
AccountType accountType = getAccountType(id);
|
||||||
|
if (accountType != AccountType.CREDIT_CARD) return null;
|
||||||
|
Optional<CreditCardProperties> optionalProperties = DbUtil.findOne(
|
||||||
|
conn,
|
||||||
|
"SELECT * FROM credit_card_account_properties WHERE account_id = ?",
|
||||||
|
List.of(id),
|
||||||
|
JdbcAccountRepository::parseCreditCardProperties
|
||||||
|
);
|
||||||
|
if (optionalProperties.isPresent()) return optionalProperties.get();
|
||||||
|
// No properties were found for the credit card account, so create an empty properties.
|
||||||
|
CreditCardProperties defaultProperties = new CreditCardProperties(id, null);
|
||||||
|
saveCreditCardProperties(defaultProperties);
|
||||||
|
return defaultProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveCreditCardProperties(CreditCardProperties properties) {
|
||||||
|
AccountType accountType = getAccountType(properties.accountId());
|
||||||
|
if (accountType != AccountType.CREDIT_CARD) return;
|
||||||
|
CreditCardProperties existingProperties = DbUtil.findOne(
|
||||||
|
conn,
|
||||||
|
"SELECT * FROM credit_card_account_properties WHERE account_id = ?",
|
||||||
|
List.of(properties.accountId()),
|
||||||
|
JdbcAccountRepository::parseCreditCardProperties
|
||||||
|
).orElse(null);
|
||||||
|
if (existingProperties != null) {
|
||||||
|
DbUtil.updateOne(
|
||||||
|
conn,
|
||||||
|
"UPDATE credit_card_account_properties SET credit_limit = ? WHERE account_id = ?",
|
||||||
|
properties.creditLimit(), properties.accountId()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
DbUtil.updateOne(
|
||||||
|
conn,
|
||||||
|
"INSERT INTO credit_card_account_properties (account_id, credit_limit) VALUES (?, ?)",
|
||||||
|
properties.accountId(), properties.creditLimit()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BigDecimal deriveCashBalance(long accountId, Instant timestamp) {
|
public BigDecimal deriveCashBalance(long accountId, Instant timestamp) {
|
||||||
// First find the account itself, since its properties influence the balance.
|
// First find the account itself, since its properties influence the balance.
|
||||||
|
@ -254,7 +299,10 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(Account account) {
|
public void delete(Account account) {
|
||||||
DbUtil.updateOne(conn, "DELETE FROM account WHERE id = ?", List.of(account.id));
|
DbUtil.doTransaction(conn, () -> {
|
||||||
|
DbUtil.update(conn, "DELETE FROM credit_card_account_properties WHERE account_id = ?", account.id);
|
||||||
|
DbUtil.updateOne(conn, "DELETE FROM account WHERE id = ?", account.id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -289,6 +337,12 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
|
||||||
return new Account(id, createdAt, archived, type, accountNumber, name, currency, description);
|
return new Account(id, createdAt, archived, type, accountNumber, name, currency, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CreditCardProperties parseCreditCardProperties(ResultSet rs) throws SQLException {
|
||||||
|
long accountId = rs.getLong("account_id");
|
||||||
|
BigDecimal creditLimit = rs.getBigDecimal("credit_limit");
|
||||||
|
return new CreditCardProperties(accountId, creditLimit);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws Exception {
|
public void close() throws Exception {
|
||||||
conn.close();
|
conn.close();
|
||||||
|
@ -310,4 +364,11 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
|
||||||
}
|
}
|
||||||
return balance;
|
return balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AccountType getAccountType(long id) {
|
||||||
|
String accountTypeStr = DbUtil.findOne(conn, "SELECT account_type FROM account WHERE id = ?", List.of(id), rs -> rs.getString(1))
|
||||||
|
.orElse(null);
|
||||||
|
if (accountTypeStr == null) return null;
|
||||||
|
return AccountType.valueOf(accountTypeStr.toUpperCase());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ public class JdbcDataSourceFactory implements DataSourceFactory {
|
||||||
* This value should be one higher than the
|
* This value should be one higher than the
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public static final int SCHEMA_VERSION = 5;
|
public static final int SCHEMA_VERSION = 6;
|
||||||
|
|
||||||
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));
|
||||||
|
|
|
@ -20,6 +20,7 @@ public class Migrations {
|
||||||
migrations.put(2, new PlainSQLMigration("/sql/migration/M002_RefactorHistories.sql"));
|
migrations.put(2, new PlainSQLMigration("/sql/migration/M002_RefactorHistories.sql"));
|
||||||
migrations.put(3, new PlainSQLMigration("/sql/migration/M003_AddLineItemCategoryAndAccountDescription.sql"));
|
migrations.put(3, new PlainSQLMigration("/sql/migration/M003_AddLineItemCategoryAndAccountDescription.sql"));
|
||||||
migrations.put(4, new PlainSQLMigration("/sql/migration/M004_AddBrokerageValueRecords.sql"));
|
migrations.put(4, new PlainSQLMigration("/sql/migration/M004_AddBrokerageValueRecords.sql"));
|
||||||
|
migrations.put(5, new PlainSQLMigration("/sql/migration/M005_AddCreditCardLimit.sql"));
|
||||||
return migrations;
|
return migrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,8 +113,13 @@ public final class DbUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updateOne(Connection conn, String query, List<Object> args) {
|
public static void updateOne(Connection conn, String query, List<Object> args) {
|
||||||
Object[] argsArray = args.toArray();
|
try (var stmt = conn.prepareStatement(query)) {
|
||||||
updateOne(conn, query, argsArray);
|
setArgs(stmt, args);
|
||||||
|
int updateCount = stmt.executeUpdate();
|
||||||
|
if (updateCount != 1) throw new UncheckedSqlException("Update count is " + updateCount + "; expected 1.");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new UncheckedSqlException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updateOne(Connection conn, String query, Object... args) {
|
public static void updateOne(Connection conn, String query, Object... args) {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.andrewlalis.perfin.model;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
public record CreditCardProperties(
|
||||||
|
long accountId,
|
||||||
|
BigDecimal creditLimit
|
||||||
|
) {}
|
|
@ -49,6 +49,11 @@
|
||||||
</VBox>
|
</VBox>
|
||||||
</PropertiesPane>
|
</PropertiesPane>
|
||||||
|
|
||||||
|
<PropertiesPane vgap="5" hgap="5" fx:id="creditCardPropertiesPane" styleClass="std-padding,std-spacing">
|
||||||
|
<Label text="Credit Limit" styleClass="bold-text" labelFor="${creditLimitLabel}"/>
|
||||||
|
<Label fx:id="creditLimitLabel" styleClass="mono-font"/>
|
||||||
|
</PropertiesPane>
|
||||||
|
|
||||||
<PropertiesPane vgap="5" hgap="5" fx:id="descriptionPane" styleClass="std-padding,std-spacing">
|
<PropertiesPane vgap="5" hgap="5" fx:id="descriptionPane" styleClass="std-padding,std-spacing">
|
||||||
<Label text="Description" styleClass="bold-text" labelFor="${accountDescriptionText}"/>
|
<Label text="Description" styleClass="bold-text" labelFor="${accountDescriptionText}"/>
|
||||||
<TextFlow maxWidth="500"><Text fx:id="accountDescriptionText"/></TextFlow>
|
<TextFlow maxWidth="500"><Text fx:id="accountDescriptionText"/></TextFlow>
|
||||||
|
|
|
@ -33,6 +33,9 @@
|
||||||
<Label text="Account Type" styleClass="bold-text"/>
|
<Label text="Account Type" styleClass="bold-text"/>
|
||||||
<ChoiceBox fx:id="accountTypeChoiceBox"/>
|
<ChoiceBox fx:id="accountTypeChoiceBox"/>
|
||||||
|
|
||||||
|
<Label text="Credit Limit" styleClass="bold-text" managed="${creditLimitField.managed}" visible="${creditLimitField.visible}"/>
|
||||||
|
<TextField fx:id="creditLimitField" styleClass="mono-font"/>
|
||||||
|
|
||||||
<Label text="Description" styleClass="bold-text"/>
|
<Label text="Description" styleClass="bold-text"/>
|
||||||
<TextArea
|
<TextArea
|
||||||
fx:id="descriptionField"
|
fx:id="descriptionField"
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
This migration adds a new table for credit-card specific properties.
|
||||||
|
*/
|
||||||
|
|
||||||
|
CREATE TABLE credit_card_account_properties (
|
||||||
|
account_id BIGINT PRIMARY KEY,
|
||||||
|
credit_limit NUMERIC(12, 4) NULL,
|
||||||
|
CONSTRAINT fk_credit_card_account_properties_account
|
||||||
|
FOREIGN KEY (account_id) REFERENCES account(id)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
);
|
|
@ -17,6 +17,14 @@ CREATE TABLE account (
|
||||||
description VARCHAR(255) DEFAULT NULL
|
description VARCHAR(255) DEFAULT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE credit_card_account_properties (
|
||||||
|
account_id BIGINT PRIMARY KEY,
|
||||||
|
credit_limit NUMERIC(12, 4) NULL,
|
||||||
|
CONSTRAINT fk_credit_card_account_properties_account
|
||||||
|
FOREIGN KEY (account_id) REFERENCES account(id)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE attachment (
|
CREATE TABLE attachment (
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
uploaded_at TIMESTAMP NOT NULL,
|
uploaded_at TIMESTAMP NOT NULL,
|
||||||
|
|
Loading…
Reference in New Issue