Refactor creating a balance record.
This commit is contained in:
parent
c02e5d3fc6
commit
5f692bf8e2
|
@ -8,28 +8,56 @@ import com.andrewlalis.perfin.model.Account;
|
||||||
import com.andrewlalis.perfin.model.MoneyValue;
|
import com.andrewlalis.perfin.model.MoneyValue;
|
||||||
import com.andrewlalis.perfin.model.Profile;
|
import com.andrewlalis.perfin.model.Profile;
|
||||||
import com.andrewlalis.perfin.view.component.FileSelectionArea;
|
import com.andrewlalis.perfin.view.component.FileSelectionArea;
|
||||||
|
import com.andrewlalis.perfin.view.component.PropertiesPane;
|
||||||
|
import com.andrewlalis.perfin.view.component.validation.ValidationApplier;
|
||||||
|
import com.andrewlalis.perfin.view.component.validation.ValidationResult;
|
||||||
|
import com.andrewlalis.perfin.view.component.validation.validators.CurrencyAmountValidator;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
|
||||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||||
|
|
||||||
public class CreateBalanceRecordController implements RouteSelectionListener {
|
public class CreateBalanceRecordController implements RouteSelectionListener {
|
||||||
@FXML public TextField timestampField;
|
@FXML public TextField timestampField;
|
||||||
@FXML public TextField balanceField;
|
@FXML public TextField balanceField;
|
||||||
@FXML public VBox attachmentsVBox;
|
|
||||||
private FileSelectionArea attachmentSelectionArea;
|
private FileSelectionArea attachmentSelectionArea;
|
||||||
|
@FXML public PropertiesPane propertiesPane;
|
||||||
|
|
||||||
|
@FXML public Button saveButton;
|
||||||
|
|
||||||
private Account account;
|
private Account account;
|
||||||
|
|
||||||
@FXML public void initialize() {
|
@FXML public void initialize() {
|
||||||
attachmentSelectionArea = new FileSelectionArea(FileUtil::newAttachmentsFileChooser, () -> attachmentsVBox.getScene().getWindow());
|
var timestampValid = new ValidationApplier<String>(input -> {
|
||||||
|
try {
|
||||||
|
DateUtil.DEFAULT_DATETIME_FORMAT.parse(input);
|
||||||
|
return ValidationResult.valid();
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
return ValidationResult.of("Invalid timestamp format.");
|
||||||
|
}
|
||||||
|
}).validatedInitially().attachToTextField(timestampField);
|
||||||
|
|
||||||
|
var balanceValid = new ValidationApplier<>(
|
||||||
|
new CurrencyAmountValidator(() -> account == null ? null : account.getCurrency(), true, false)
|
||||||
|
).validatedInitially().attachToTextField(balanceField);
|
||||||
|
|
||||||
|
var formValid = timestampValid.and(balanceValid);
|
||||||
|
saveButton.disableProperty().bind(formValid.not());
|
||||||
|
|
||||||
|
// Manually append the attachment selection area to the end of the properties pane.
|
||||||
|
attachmentSelectionArea = new FileSelectionArea(
|
||||||
|
FileUtil::newAttachmentsFileChooser,
|
||||||
|
() -> timestampField.getScene().getWindow()
|
||||||
|
);
|
||||||
attachmentSelectionArea.allowMultiple.set(true);
|
attachmentSelectionArea.allowMultiple.set(true);
|
||||||
attachmentsVBox.getChildren().add(attachmentSelectionArea);
|
propertiesPane.getChildren().addLast(attachmentSelectionArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -48,19 +76,29 @@ public class CreateBalanceRecordController implements RouteSelectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML public void save() {
|
@FXML public void save() {
|
||||||
// TODO: Add validation.
|
LocalDateTime localTimestamp = LocalDateTime.parse(timestampField.getText(), DateUtil.DEFAULT_DATETIME_FORMAT);
|
||||||
Profile.getCurrent().getDataSource().useBalanceRecordRepository(repo -> {
|
BigDecimal reportedBalance = new BigDecimal(balanceField.getText());
|
||||||
LocalDateTime localTimestamp = LocalDateTime.parse(timestampField.getText(), DateUtil.DEFAULT_DATETIME_FORMAT);
|
boolean confirm = Popups.confirm("Are you sure that you want to record the balance of account\n%s\nas %s,\nas of %s?".formatted(
|
||||||
BigDecimal reportedBalance = new BigDecimal(balanceField.getText());
|
account.getShortName(),
|
||||||
repo.insert(
|
CurrencyUtil.formatMoneyWithCurrencyPrefix(new MoneyValue(reportedBalance, account.getCurrency())),
|
||||||
DateUtil.localToUTC(localTimestamp),
|
localTimestamp.atZone(ZoneId.systemDefault()).format(DateUtil.DEFAULT_DATETIME_FORMAT_WITH_ZONE)
|
||||||
account.id,
|
));
|
||||||
reportedBalance,
|
if (confirm) {
|
||||||
account.getCurrency(),
|
Profile.getCurrent().getDataSource().useAccountRepository(accountRepo -> {
|
||||||
attachmentSelectionArea.getSelectedFiles()
|
BigDecimal currentDerivedBalance = accountRepo.deriveCurrentBalance(account.id);
|
||||||
);
|
|
||||||
});
|
});
|
||||||
router.navigateBackAndClear();
|
Profile.getCurrent().getDataSource().useBalanceRecordRepository(repo -> {
|
||||||
|
repo.insert(
|
||||||
|
DateUtil.localToUTC(localTimestamp),
|
||||||
|
account.id,
|
||||||
|
reportedBalance,
|
||||||
|
account.getCurrency(),
|
||||||
|
attachmentSelectionArea.getSelectedFiles()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
router.navigateBackAndClear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML public void cancel() {
|
@FXML public void cancel() {
|
||||||
|
|
|
@ -46,9 +46,6 @@ public class CreateTransactionController implements RouteSelectionListener {
|
||||||
|
|
||||||
@FXML public Button saveButton;
|
@FXML public Button saveButton;
|
||||||
|
|
||||||
public CreateTransactionController() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML public void initialize() {
|
@FXML public void initialize() {
|
||||||
// Setup error field validation.
|
// Setup error field validation.
|
||||||
var timestampValid = new ValidationApplier<>(new PredicateValidator<String>()
|
var timestampValid = new ValidationApplier<>(new PredicateValidator<String>()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.andrewlalis.perfin.data;
|
package com.andrewlalis.perfin.data;
|
||||||
|
|
||||||
|
import com.andrewlalis.perfin.data.pagination.Sort;
|
||||||
import com.andrewlalis.perfin.model.AccountEntry;
|
import com.andrewlalis.perfin.model.AccountEntry;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
@ -17,4 +18,5 @@ public interface AccountEntryRepository extends AutoCloseable {
|
||||||
Currency currency
|
Currency currency
|
||||||
);
|
);
|
||||||
List<AccountEntry> findAllByAccountId(long accountId);
|
List<AccountEntry> findAllByAccountId(long accountId);
|
||||||
|
List<AccountEntry> findAllByAccountIdBetween(long accountId, LocalDateTime utcMin, LocalDateTime utcMax);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,12 @@ import java.nio.file.Path;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Currency;
|
import java.util.Currency;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface BalanceRecordRepository extends AutoCloseable {
|
public interface BalanceRecordRepository extends AutoCloseable {
|
||||||
long insert(LocalDateTime utcTimestamp, long accountId, BigDecimal balance, Currency currency, List<Path> attachments);
|
long insert(LocalDateTime utcTimestamp, long accountId, BigDecimal balance, Currency currency, List<Path> attachments);
|
||||||
BalanceRecord findLatestByAccountId(long accountId);
|
BalanceRecord findLatestByAccountId(long accountId);
|
||||||
|
Optional<BalanceRecord> findClosestBefore(long accountId, LocalDateTime utcTimestamp);
|
||||||
|
Optional<BalanceRecord> findClosestAfter(long accountId, LocalDateTime utcTimestamp);
|
||||||
void deleteById(long id);
|
void deleteById(long id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.AccountHistoryItemRepository;
|
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
|
||||||
|
import com.andrewlalis.perfin.data.pagination.Sort;
|
||||||
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;
|
||||||
|
|
||||||
|
@ -46,6 +47,20 @@ public record JdbcAccountEntryRepository(Connection conn) implements AccountEntr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AccountEntry> findAllByAccountIdBetween(long accountId, LocalDateTime utcMin, LocalDateTime utcMax) {
|
||||||
|
return DbUtil.findAll(
|
||||||
|
conn,
|
||||||
|
"SELECT * FROM account_entry WHERE account_id = ? AND timestamp >= ? AND timestamp <= ? ORDER BY timestamp ASC",
|
||||||
|
List.of(
|
||||||
|
accountId,
|
||||||
|
DbUtil.timestampFromUtcLDT(utcMin),
|
||||||
|
DbUtil.timestampFromUtcLDT(utcMax)
|
||||||
|
),
|
||||||
|
JdbcAccountEntryRepository::parse
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws Exception {
|
public void close() throws Exception {
|
||||||
conn.close();
|
conn.close();
|
||||||
|
|
|
@ -1,6 +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.AccountRepository;
|
import com.andrewlalis.perfin.data.AccountRepository;
|
||||||
|
import com.andrewlalis.perfin.data.BalanceRecordRepository;
|
||||||
import com.andrewlalis.perfin.data.EntityNotFoundException;
|
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;
|
||||||
|
@ -10,16 +12,22 @@ import com.andrewlalis.perfin.model.Account;
|
||||||
import com.andrewlalis.perfin.model.AccountEntry;
|
import com.andrewlalis.perfin.model.AccountEntry;
|
||||||
import com.andrewlalis.perfin.model.AccountType;
|
import com.andrewlalis.perfin.model.AccountType;
|
||||||
import com.andrewlalis.perfin.model.BalanceRecord;
|
import com.andrewlalis.perfin.model.BalanceRecord;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public record JdbcAccountRepository(Connection conn) implements AccountRepository {
|
public record JdbcAccountRepository(Connection conn, Path contentDir) implements AccountRepository {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(JdbcAccountRepository.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long insert(AccountType type, String accountNumber, String name, Currency currency) {
|
public long insert(AccountType type, String accountNumber, String name, Currency currency) {
|
||||||
return DbUtil.doTransaction(conn, () -> {
|
return DbUtil.doTransaction(conn, () -> {
|
||||||
|
@ -84,49 +92,37 @@ public record JdbcAccountRepository(Connection conn) implements AccountRepositor
|
||||||
// First find the account itself, since its properties influence the balance.
|
// First find the account itself, since its properties influence the balance.
|
||||||
Account account = findById(accountId).orElse(null);
|
Account account = findById(accountId).orElse(null);
|
||||||
if (account == null) throw new EntityNotFoundException(Account.class, accountId);
|
if (account == null) throw new EntityNotFoundException(Account.class, accountId);
|
||||||
|
LocalDateTime utcTimestamp = timestamp.atZone(ZoneOffset.UTC).toLocalDateTime();
|
||||||
|
BalanceRecordRepository balanceRecordRepo = new JdbcBalanceRecordRepository(conn, contentDir);
|
||||||
|
AccountEntryRepository accountEntryRepo = new JdbcAccountEntryRepository(conn);
|
||||||
// Find the most recent balance record before timestamp.
|
// Find the most recent balance record before timestamp.
|
||||||
Optional<BalanceRecord> closestPastRecord = DbUtil.findOne(
|
Optional<BalanceRecord> closestPastRecord = balanceRecordRepo.findClosestBefore(account.id, utcTimestamp);
|
||||||
conn,
|
|
||||||
"SELECT * FROM balance_record WHERE account_id = ? AND timestamp <= ? ORDER BY timestamp DESC LIMIT 1",
|
|
||||||
List.of(accountId, DbUtil.timestampFromInstant(timestamp)),
|
|
||||||
JdbcBalanceRecordRepository::parse
|
|
||||||
);
|
|
||||||
if (closestPastRecord.isPresent()) {
|
if (closestPastRecord.isPresent()) {
|
||||||
// Then find any entries on the account since that balance record and the timestamp.
|
// Then find any entries on the account since that balance record and the timestamp.
|
||||||
List<AccountEntry> entriesAfterRecord = DbUtil.findAll(
|
List<AccountEntry> entriesBetweenRecentRecordAndNow = accountEntryRepo.findAllByAccountIdBetween(
|
||||||
conn,
|
account.id,
|
||||||
"SELECT * FROM account_entry WHERE account_id = ? AND timestamp >= ? AND timestamp <= ? ORDER BY timestamp ASC",
|
closestPastRecord.get().getTimestamp(),
|
||||||
List.of(
|
utcTimestamp
|
||||||
accountId,
|
|
||||||
DbUtil.timestampFromUtcLDT(closestPastRecord.get().getTimestamp()),
|
|
||||||
DbUtil.timestampFromInstant(timestamp)
|
|
||||||
),
|
|
||||||
JdbcAccountEntryRepository::parse
|
|
||||||
);
|
);
|
||||||
return computeBalanceWithEntriesAfter(account, closestPastRecord.get(), entriesAfterRecord);
|
return computeBalanceWithEntries(account.getType(), closestPastRecord.get(), entriesBetweenRecentRecordAndNow);
|
||||||
} else {
|
} else {
|
||||||
// There is no balance record present before the given timestamp. Try and find the closest one after.
|
// There is no balance record present before the given timestamp. Try and find the closest one after.
|
||||||
Optional<BalanceRecord> closestFutureRecord = DbUtil.findOne(
|
Optional<BalanceRecord> closestFutureRecord = balanceRecordRepo.findClosestAfter(account.id, utcTimestamp);
|
||||||
conn,
|
if (closestFutureRecord.isPresent()) {
|
||||||
"SELECT * FROM balance_record WHERE account_id = ? AND timestamp >= ? ORDER BY timestamp ASC LIMIT 1",
|
// Now find any entries on the account from the timestamp until that balance record.
|
||||||
List.of(accountId, DbUtil.timestampFromInstant(timestamp)),
|
List<AccountEntry> entriesBetweenNowAndFutureRecord = accountEntryRepo.findAllByAccountIdBetween(
|
||||||
JdbcBalanceRecordRepository::parse
|
account.id,
|
||||||
);
|
utcTimestamp,
|
||||||
if (closestFutureRecord.isEmpty()) {
|
closestFutureRecord.get().getTimestamp()
|
||||||
throw new IllegalStateException("No balance record exists for account " + accountId);
|
);
|
||||||
|
return computeBalanceWithEntries(account.getType(), closestFutureRecord.get(), entriesBetweenNowAndFutureRecord);
|
||||||
|
} else {
|
||||||
|
// No balance records exist for the account! Assume balance of 0 when the account was created.
|
||||||
|
log.warn("No balance record exists for account {}! Assuming balance was 0 at account creation.", account.getShortName());
|
||||||
|
BalanceRecord placeholder = new BalanceRecord(-1, account.getCreatedAt(), account.id, BigDecimal.ZERO, account.getCurrency());
|
||||||
|
List<AccountEntry> entriesSinceAccountCreated = accountEntryRepo.findAllByAccountIdBetween(account.id, account.getCreatedAt(), utcTimestamp);
|
||||||
|
return computeBalanceWithEntries(account.getType(), placeholder, entriesSinceAccountCreated);
|
||||||
}
|
}
|
||||||
// Now find any entries on the account from the timestamp until that balance record.
|
|
||||||
List<AccountEntry> entriesBeforeRecord = DbUtil.findAll(
|
|
||||||
conn,
|
|
||||||
"SELECT * FROM account_entry WHERE account_id = ? AND timestamp <= ? AND timestamp >= ? ORDER BY timestamp DESC",
|
|
||||||
List.of(
|
|
||||||
accountId,
|
|
||||||
DbUtil.timestampFromUtcLDT(closestFutureRecord.get().getTimestamp()),
|
|
||||||
DbUtil.timestampFromInstant(timestamp)
|
|
||||||
),
|
|
||||||
JdbcAccountEntryRepository::parse
|
|
||||||
);
|
|
||||||
return computeBalanceWithEntriesBefore(account, closestFutureRecord.get(), entriesBeforeRecord);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,18 +187,19 @@ public record JdbcAccountRepository(Connection conn) implements AccountRepositor
|
||||||
conn.close();
|
conn.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal computeBalanceWithEntriesAfter(Account account, BalanceRecord balanceRecord, List<AccountEntry> entriesAfterRecord) {
|
private BigDecimal computeBalanceWithEntries(AccountType accountType, BalanceRecord balanceRecord, List<AccountEntry> entries) {
|
||||||
BigDecimal balance = balanceRecord.getBalance();
|
List<AccountEntry> entriesBeforeRecord = entries.stream()
|
||||||
for (AccountEntry entry : entriesAfterRecord) {
|
.filter(entry -> entry.getTimestamp().isBefore(balanceRecord.getTimestamp()))
|
||||||
balance = balance.add(entry.getEffectiveValue(account.getType()));
|
.toList();
|
||||||
}
|
List<AccountEntry> entriesAfterRecord = entries.stream()
|
||||||
return balance;
|
.filter(entry -> entry.getTimestamp().isAfter(balanceRecord.getTimestamp()))
|
||||||
}
|
.toList();
|
||||||
|
|
||||||
private BigDecimal computeBalanceWithEntriesBefore(Account account, BalanceRecord balanceRecord, List<AccountEntry> entriesBeforeRecord) {
|
|
||||||
BigDecimal balance = balanceRecord.getBalance();
|
BigDecimal balance = balanceRecord.getBalance();
|
||||||
for (AccountEntry entry : entriesBeforeRecord) {
|
for (AccountEntry entry : entriesBeforeRecord) {
|
||||||
balance = balance.subtract(entry.getEffectiveValue(account.getType()));
|
balance = balance.subtract(entry.getEffectiveValue(accountType));
|
||||||
|
}
|
||||||
|
for (AccountEntry entry : entriesAfterRecord) {
|
||||||
|
balance = balance.add(entry.getEffectiveValue(accountType));
|
||||||
}
|
}
|
||||||
return balance;
|
return balance;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import java.sql.SQLException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Currency;
|
import java.util.Currency;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public record JdbcBalanceRecordRepository(Connection conn, Path contentDir) implements BalanceRecordRepository {
|
public record JdbcBalanceRecordRepository(Connection conn, Path contentDir) implements BalanceRecordRepository {
|
||||||
@Override
|
@Override
|
||||||
|
@ -51,6 +52,26 @@ public record JdbcBalanceRecordRepository(Connection conn, Path contentDir) impl
|
||||||
).orElse(null);
|
).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<BalanceRecord> findClosestBefore(long accountId, LocalDateTime utcTimestamp) {
|
||||||
|
return DbUtil.findOne(
|
||||||
|
conn,
|
||||||
|
"SELECT * FROM balance_record WHERE account_id = ? AND timestamp <= ? ORDER BY timestamp DESC LIMIT 1",
|
||||||
|
List.of(accountId, DbUtil.timestampFromUtcLDT(utcTimestamp)),
|
||||||
|
JdbcBalanceRecordRepository::parse
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<BalanceRecord> findClosestAfter(long accountId, LocalDateTime utcTimestamp) {
|
||||||
|
return DbUtil.findOne(
|
||||||
|
conn,
|
||||||
|
"SELECT * FROM balance_record WHERE account_id = ? AND timestamp >= ? ORDER BY timestamp ASC LIMIT 1",
|
||||||
|
List.of(accountId, DbUtil.timestampFromUtcLDT(utcTimestamp)),
|
||||||
|
JdbcBalanceRecordRepository::parse
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteById(long id) {
|
public void deleteById(long id) {
|
||||||
DbUtil.updateOne(conn, "DELETE FROM balance_record WHERE id = ?", List.of(id));
|
DbUtil.updateOne(conn, "DELETE FROM balance_record WHERE id = ?", List.of(id));
|
||||||
|
|
|
@ -1,35 +1,54 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import com.andrewlalis.perfin.view.component.PropertiesPane?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.text.Text?>
|
||||||
|
<?import javafx.scene.text.TextFlow?>
|
||||||
<BorderPane xmlns="http://javafx.com/javafx"
|
<BorderPane xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:controller="com.andrewlalis.perfin.control.CreateBalanceRecordController"
|
fx:controller="com.andrewlalis.perfin.control.CreateBalanceRecordController"
|
||||||
|
stylesheets="@style/base.css"
|
||||||
>
|
>
|
||||||
<top>
|
<top>
|
||||||
<HBox styleClass="std-padding,std-spacing">
|
<Label text="Create New Balance Record" styleClass="large-text,bold-text,std-padding"/>
|
||||||
<Label text="Create New Balance Record" styleClass="large-text,bold-text"/>
|
|
||||||
</HBox>
|
|
||||||
</top>
|
</top>
|
||||||
<center>
|
<center>
|
||||||
<VBox styleClass="std-padding,std-spacing">
|
<VBox styleClass="std-padding,std-spacing" style="-fx-max-width: 500px;" BorderPane.alignment="TOP_LEFT">
|
||||||
<VBox>
|
<VBox styleClass="padding-extra,spacing-extra,small-font">
|
||||||
<Label text="Timestamp" labelFor="${timestampField}"/>
|
<TextFlow>
|
||||||
<TextField fx:id="timestampField"/>
|
<Text styleClass="secondary-color-fill">
|
||||||
</VBox>
|
Create a new recording of the current balance of your
|
||||||
<VBox>
|
account. This will serve as a sort of key-frame; a true,
|
||||||
<Label text="Balance" labelFor="${balanceField}"/>
|
known balance that Perfin will use to derive your account's
|
||||||
<TextField fx:id="balanceField"/>
|
current balance by applying recent transactions to it.
|
||||||
</VBox>
|
</Text>
|
||||||
<VBox fx:id="attachmentsVBox">
|
</TextFlow>
|
||||||
<Label text="Attachments"/>
|
<TextFlow>
|
||||||
|
<Text styleClass="secondary-color-fill">
|
||||||
|
Therefore, it's important to make balance records
|
||||||
|
periodically so that Perfin's calculations are accurate, and
|
||||||
|
it serves as a nice sanity-check to make sure all your
|
||||||
|
transactions add up.
|
||||||
|
</Text>
|
||||||
|
</TextFlow>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
||||||
|
<PropertiesPane vgap="5" hgap="5" fx:id="propertiesPane">
|
||||||
|
<Label text="Timestamp" labelFor="${timestampField}" styleClass="bold-text"/>
|
||||||
|
<TextField fx:id="timestampField" styleClass="mono-font"/>
|
||||||
|
|
||||||
|
<Label text="Balance" labelFor="${balanceField}" styleClass="bold-text"/>
|
||||||
|
<TextField fx:id="balanceField" styleClass="mono-font"/>
|
||||||
|
|
||||||
|
<Label text="Attachments" styleClass="bold-text"/>
|
||||||
|
</PropertiesPane>
|
||||||
|
|
||||||
|
<Separator/>
|
||||||
|
<HBox styleClass="std-padding,std-spacing">
|
||||||
|
<Button text="Save" fx:id="saveButton" onAction="#save"/>
|
||||||
|
<Button text="Cancel" onAction="#cancel"/>
|
||||||
|
</HBox>
|
||||||
</VBox>
|
</VBox>
|
||||||
</center>
|
</center>
|
||||||
<bottom>
|
|
||||||
<HBox styleClass="std-padding,std-spacing">
|
|
||||||
<Button text="Save" onAction="#save"/>
|
|
||||||
<Button text="Cancel" onAction="#cancel"/>
|
|
||||||
</HBox>
|
|
||||||
</bottom>
|
|
||||||
</BorderPane>
|
</BorderPane>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
>
|
>
|
||||||
<center>
|
<center>
|
||||||
<ScrollPane fitToWidth="true" fitToHeight="true">
|
<ScrollPane fitToWidth="true" fitToHeight="true">
|
||||||
<VBox style="-fx-max-width: 400px;">
|
<VBox style="-fx-max-width: 500px;">
|
||||||
<!-- Basic properties -->
|
<!-- Basic properties -->
|
||||||
<PropertiesPane hgap="5" vgap="5" styleClass="std-padding">
|
<PropertiesPane hgap="5" vgap="5" styleClass="std-padding">
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
</HBox>
|
</HBox>
|
||||||
</top>
|
</top>
|
||||||
<center>
|
<center>
|
||||||
<VBox style="-fx-max-width: 400px;" BorderPane.alignment="TOP_LEFT">
|
<VBox style="-fx-max-width: 500px;" BorderPane.alignment="TOP_LEFT">
|
||||||
<PropertiesPane hgap="5" vgap="5" styleClass="std-padding">
|
<PropertiesPane hgap="5" vgap="5" styleClass="std-padding">
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints hgrow="NEVER" halignment="LEFT" minWidth="150"/>
|
<ColumnConstraints hgrow="NEVER" halignment="LEFT" minWidth="150"/>
|
||||||
|
|
|
@ -25,11 +25,16 @@ rather than with your own CSS.
|
||||||
-fx-font-family: "JetBrains Mono", monospace;
|
-fx-font-family: "JetBrains Mono", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.largest-font {
|
||||||
|
-fx-font-size: 24px;
|
||||||
|
}
|
||||||
.large-font {
|
.large-font {
|
||||||
-fx-font-size: 18px;
|
-fx-font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small-font {
|
.small-font {
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
}
|
||||||
|
.smallest-font {
|
||||||
-fx-font-size: 10px;
|
-fx-font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +106,9 @@ rather than with your own CSS.
|
||||||
-fx-text-fill: -fx-theme-text;
|
-fx-text-fill: -fx-theme-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.secondary-color-text-fill {
|
||||||
|
-fx-text-fill: -fx-theme-text-secondary;
|
||||||
|
}
|
||||||
.secondary-color-fill {
|
.secondary-color-fill {
|
||||||
-fx-fill: -fx-theme-text-secondary;
|
-fx-fill: -fx-theme-text-secondary;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue