Added create-balance-record.fxml and associated logic.
This commit is contained in:
parent
e42f9507db
commit
b477e9ab3c
|
@ -26,9 +26,9 @@
|
||||||
inkscape:pagecheckerboard="1"
|
inkscape:pagecheckerboard="1"
|
||||||
inkscape:deskcolor="#505050"
|
inkscape:deskcolor="#505050"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:zoom="5.6568543"
|
inkscape:zoom="4"
|
||||||
inkscape:cx="3.2703689"
|
inkscape:cx="-0.99999999"
|
||||||
inkscape:cy="25.102291"
|
inkscape:cy="38.25"
|
||||||
inkscape:window-width="1920"
|
inkscape:window-width="1920"
|
||||||
inkscape:window-height="1025"
|
inkscape:window-height="1025"
|
||||||
inkscape:window-x="1080"
|
inkscape:window-x="1080"
|
||||||
|
@ -54,25 +54,25 @@
|
||||||
inkscape:label="Layer 1"
|
inkscape:label="Layer 1"
|
||||||
inkscape:groupmode="layer"
|
inkscape:groupmode="layer"
|
||||||
id="layer1">
|
id="layer1">
|
||||||
<rect
|
<g
|
||||||
|
id="logo-group">
|
||||||
|
<path
|
||||||
|
id="top-right-background"
|
||||||
style="fill:#346b23;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round"
|
style="fill:#346b23;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round"
|
||||||
id="rect1"
|
d="M 4.2333332 0 C 1.8880691 0 2.6202161e-17 1.8880691 0 4.2333332 L 0 12.7 C -1.2533172e-15 15.045264 1.8880691 16.933333 4.2333332 16.933333 L 12.7 16.933333 C 15.045264 16.933333 16.933333 15.045264 16.933333 12.7 L 16.933333 4.2333332 C 16.933333 1.8880691 15.045264 2.6202161e-17 12.7 0 L 4.2333332 0 z " />
|
||||||
width="16.933332"
|
|
||||||
height="16.933332"
|
|
||||||
x="-1.7763568e-15"
|
|
||||||
y="1.7763568e-15" />
|
|
||||||
<path
|
<path
|
||||||
|
id="bottom-right-background"
|
||||||
style="fill:#ca9c00;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round"
|
style="fill:#ca9c00;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round"
|
||||||
d="M -1.3919068e-7,16.933333 16.933333,3.7161866e-7 V 16.933333 H -1.3919068e-7"
|
d="M 15.696199 1.2371338 L 1.2371338 15.696199 C 2.002308 16.461373 3.0607012 16.933333 4.2333332 16.933333 L 12.7 16.933333 C 15.045264 16.933333 16.933333 15.045264 16.933333 12.7 L 16.933333 4.2333332 C 16.933333 3.0607012 16.461373 2.002308 15.696199 1.2371338 z " />
|
||||||
id="path2" />
|
|
||||||
<path
|
<path
|
||||||
id="path1"
|
id="lower-dollar"
|
||||||
style="fill:#346b23;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round"
|
style="fill:#346b23;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round"
|
||||||
d="m 13.000241,3.9330926 -1.304313,1.304313 c 0.0076,0.032651 0.01631,0.063696 0.02325,0.097152 L 13.398662,5.0162308 C 13.290974,4.6118006 13.157768,4.2511887 13.000237,3.9330929 Z M 9.537919,7.3954134 7.984009,8.9493237 V 13.384195 C 7.108943,13.339155 6.414126,13.117013 5.899382,12.718086 5.591151,12.475354 5.346639,12.158421 5.162993,11.77034 l -1.204061,1.204061 c 0.792283,1.097222 2.132722,1.678716 4.025077,1.741495 v 1.553911 h 1.235584 v -1.553911 c 1.441285,-0.05791 2.548094,-0.389429 3.320211,-0.994254 0.772117,-0.611259 1.158069,-1.437594 1.158069,-2.479952 0,-0.514745 -0.07422,-0.96528 -0.222209,-1.3513385 C 13.327675,9.4978588 13.10605,9.1533177 12.810072,8.8573396 12.514093,8.5613615 12.117989,8.2978085 11.622547,8.0661735 11.178148,7.8584026 10.474116,7.6342452 9.537919,7.3954134 Z M 9.219593,8.8573396 c 0.8815,0.2252008 1.482826,0.4314552 1.804541,0.6180501 0.321715,0.1801606 0.569381,0.4085543 0.743107,0.6852293 0.180161,0.276675 0.270268,0.627468 0.270268,1.052132 0,0.649865 -0.23803,1.164468 -0.714168,1.544092 -0.476139,0.37319 -1.177208,0.588563 -2.103748,0.646472 z" />
|
d="m 13.000241,3.9330926 -1.304313,1.304313 c 0.0076,0.032651 0.01631,0.063696 0.02325,0.097152 L 13.398662,5.0162308 C 13.290974,4.6118006 13.157768,4.2511887 13.000237,3.9330929 Z M 9.537919,7.3954134 7.984009,8.9493237 V 13.384195 C 7.108943,13.339155 6.414126,13.117013 5.899382,12.718086 5.591151,12.475354 5.346639,12.158421 5.162993,11.77034 l -1.204061,1.204061 c 0.792283,1.097222 2.132722,1.678716 4.025077,1.741495 v 1.553911 h 1.235584 v -1.553911 c 1.441285,-0.05791 2.548094,-0.389429 3.320211,-0.994254 0.772117,-0.611259 1.158069,-1.437594 1.158069,-2.479952 0,-0.514745 -0.07422,-0.96528 -0.222209,-1.3513385 C 13.327675,9.4978588 13.10605,9.1533177 12.810072,8.8573396 12.514093,8.5613615 12.117989,8.2978085 11.622547,8.0661735 11.178148,7.8584026 10.474116,7.6342452 9.537919,7.3954134 Z M 9.219593,8.8573396 c 0.8815,0.2252008 1.482826,0.4314552 1.804541,0.6180501 0.321715,0.1801606 0.569381,0.4085543 0.743107,0.6852293 0.180161,0.276675 0.270268,0.627468 0.270268,1.052132 0,0.649865 -0.23803,1.164468 -0.714168,1.544092 -0.476139,0.37319 -1.177208,0.588563 -2.103748,0.646472 z" />
|
||||||
<path
|
<path
|
||||||
id="text3"
|
id="upper-dollar"
|
||||||
style="font-size:8px;line-height:8.64px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect3);fill:#ca9c00;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round"
|
style="font-size:8px;line-height:8.64px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect3);fill:#ca9c00;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round"
|
||||||
d="M 25.689419 18.184344 L 25.689419 18.692161 C 25.145149 18.710391 24.729758 18.828758 24.4433 19.047508 C 24.159446 19.263654 24.017678 19.565737 24.017678 19.953758 C 24.017678 20.198549 24.064478 20.409564 24.158228 20.586647 C 24.254582 20.763731 24.404358 20.916037 24.607483 21.043641 C 24.813212 21.168641 25.134795 21.284479 25.572295 21.39125 L 25.689419 21.422622 L 25.689419 21.537864 L 26.318335 20.908948 C 26.274814 20.897846 26.235644 20.886962 26.189498 20.875694 L 26.189498 19.211691 C 26.473352 19.229921 26.698597 19.309289 26.865264 19.449914 C 27.023755 19.583641 27.132164 19.779351 27.191748 20.035536 L 27.719644 19.50764 C 27.611975 19.290229 27.475742 19.122396 27.310545 19.004632 C 27.047525 18.814528 26.673873 18.710391 26.189498 18.692161 L 26.189498 18.184344 L 25.689419 18.184344 z M 25.689419 19.203744 L 25.689419 20.817131 C 25.379523 20.736402 25.165978 20.663464 25.048791 20.59836 C 24.931603 20.530652 24.841696 20.447383 24.779196 20.348425 C 24.7193 20.246863 24.68947 20.121855 24.68947 19.973418 C 24.68947 19.736439 24.774108 19.552723 24.943379 19.422515 C 25.112649 19.289703 25.361294 19.216765 25.689419 19.203744 z M 24.431587 22.324898 L 23.767534 22.469421 C 23.822782 22.741711 23.9209 22.973823 24.060345 23.166938 L 24.547666 22.679617 C 24.497867 22.574382 24.458752 22.45654 24.431587 22.324898 z "
|
d="M 25.689419 18.184344 L 25.689419 18.692161 C 25.145149 18.710391 24.729758 18.828758 24.4433 19.047508 C 24.159446 19.263654 24.017678 19.565737 24.017678 19.953758 C 24.017678 20.198549 24.064478 20.409564 24.158228 20.586647 C 24.254582 20.763731 24.404358 20.916037 24.607483 21.043641 C 24.813212 21.168641 25.134795 21.284479 25.572295 21.39125 L 25.689419 21.422622 L 25.689419 21.537864 L 26.318335 20.908948 C 26.274814 20.897846 26.235644 20.886962 26.189498 20.875694 L 26.189498 19.211691 C 26.473352 19.229921 26.698597 19.309289 26.865264 19.449914 C 27.023755 19.583641 27.132164 19.779351 27.191748 20.035536 L 27.719644 19.50764 C 27.611975 19.290229 27.475742 19.122396 27.310545 19.004632 C 27.047525 18.814528 26.673873 18.710391 26.189498 18.692161 L 26.189498 18.184344 L 25.689419 18.184344 z M 25.689419 19.203744 L 25.689419 20.817131 C 25.379523 20.736402 25.165978 20.663464 25.048791 20.59836 C 24.931603 20.530652 24.841696 20.447383 24.779196 20.348425 C 24.7193 20.246863 24.68947 20.121855 24.68947 19.973418 C 24.68947 19.736439 24.774108 19.552723 24.943379 19.422515 C 25.112649 19.289703 25.361294 19.216765 25.689419 19.203744 z M 24.431587 22.324898 L 23.767534 22.469421 C 23.822782 22.741711 23.9209 22.973823 24.060345 23.166938 L 24.547666 22.679617 C 24.497867 22.574382 24.458752 22.45654 24.431587 22.324898 z "
|
||||||
transform="matrix(2.4707763,0,0,2.4707763,-55.488799,-44.26592)" />
|
transform="matrix(2.4707763,0,0,2.4707763,-55.488799,-44.26592)" />
|
||||||
</g>
|
</g>
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.4 KiB |
|
@ -44,6 +44,7 @@ public class PerfinApp extends Application {
|
||||||
splashScreen.showAndWait();
|
splashScreen.showAndWait();
|
||||||
if (splashScreen.isStartupSuccessful()) {
|
if (splashScreen.isStartupSuccessful()) {
|
||||||
stage.show();
|
stage.show();
|
||||||
|
stage.setMaximized(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +71,7 @@ public class PerfinApp extends Application {
|
||||||
mapResourceRoute("edit-account", "/edit-account.fxml");
|
mapResourceRoute("edit-account", "/edit-account.fxml");
|
||||||
mapResourceRoute("transactions", "/transactions-view.fxml");
|
mapResourceRoute("transactions", "/transactions-view.fxml");
|
||||||
mapResourceRoute("create-transaction", "/create-transaction.fxml");
|
mapResourceRoute("create-transaction", "/create-transaction.fxml");
|
||||||
|
mapResourceRoute("create-balance-record", "/create-balance-record.fxml");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,10 @@ public class AccountViewController implements RouteSelectionListener {
|
||||||
router.navigate("edit-account", account);
|
router.navigate("edit-account", account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML public void goToCreateBalanceRecord() {
|
||||||
|
router.navigate("create-balance-record", account);
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void archiveAccount() {
|
public void archiveAccount() {
|
||||||
var confirmResult = new Alert(
|
var confirmResult = new Alert(
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.andrewlalis.perfin.control;
|
||||||
|
|
||||||
|
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||||
|
import com.andrewlalis.perfin.data.util.CurrencyUtil;
|
||||||
|
import com.andrewlalis.perfin.data.util.DateUtil;
|
||||||
|
import com.andrewlalis.perfin.data.util.FileUtil;
|
||||||
|
import com.andrewlalis.perfin.model.Account;
|
||||||
|
import com.andrewlalis.perfin.model.Profile;
|
||||||
|
import com.andrewlalis.perfin.view.component.FileSelectionArea;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||||
|
|
||||||
|
public class CreateBalanceRecordController implements RouteSelectionListener {
|
||||||
|
@FXML public TextField timestampField;
|
||||||
|
@FXML public TextField balanceField;
|
||||||
|
@FXML public VBox attachmentsVBox;
|
||||||
|
private FileSelectionArea attachmentSelectionArea;
|
||||||
|
|
||||||
|
private Account account;
|
||||||
|
|
||||||
|
@FXML public void initialize() {
|
||||||
|
attachmentSelectionArea = new FileSelectionArea(FileUtil::newAttachmentsFileChooser, () -> attachmentsVBox.getScene().getWindow());
|
||||||
|
attachmentSelectionArea.allowMultiple.set(true);
|
||||||
|
attachmentsVBox.getChildren().add(attachmentSelectionArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRouteSelected(Object context) {
|
||||||
|
this.account = (Account) context;
|
||||||
|
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
|
||||||
|
Thread.ofVirtual().start(() -> {
|
||||||
|
Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
|
||||||
|
BigDecimal value = repo.deriveCurrentBalance(account.getId());
|
||||||
|
Platform.runLater(() -> balanceField.setText(
|
||||||
|
CurrencyUtil.formatMoneyAsBasicNumber(value, account.getCurrency())
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
attachmentSelectionArea.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML public void save() {
|
||||||
|
// TODO: Add validation.
|
||||||
|
Profile.getCurrent().getDataSource().useBalanceRecordRepository(repo -> {
|
||||||
|
LocalDateTime localTimestamp = LocalDateTime.parse(timestampField.getText(), DateUtil.DEFAULT_DATETIME_FORMAT);
|
||||||
|
BigDecimal reportedBalance = new BigDecimal(balanceField.getText());
|
||||||
|
repo.insert(
|
||||||
|
DateUtil.localToUTC(localTimestamp),
|
||||||
|
account.getId(),
|
||||||
|
reportedBalance,
|
||||||
|
account.getCurrency(),
|
||||||
|
attachmentSelectionArea.getSelectedFiles()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
router.navigateBackAndClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML public void cancel() {
|
||||||
|
router.navigateBackAndClear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,22 +2,17 @@ package com.andrewlalis.perfin.control;
|
||||||
|
|
||||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||||
import com.andrewlalis.perfin.data.util.DateUtil;
|
import com.andrewlalis.perfin.data.util.DateUtil;
|
||||||
|
import com.andrewlalis.perfin.data.util.FileUtil;
|
||||||
import com.andrewlalis.perfin.model.Account;
|
import com.andrewlalis.perfin.model.Account;
|
||||||
import com.andrewlalis.perfin.model.CreditAndDebitAccounts;
|
import com.andrewlalis.perfin.model.CreditAndDebitAccounts;
|
||||||
import com.andrewlalis.perfin.model.Profile;
|
import com.andrewlalis.perfin.model.Profile;
|
||||||
import com.andrewlalis.perfin.view.AccountComboBoxCellFactory;
|
import com.andrewlalis.perfin.view.AccountComboBoxCellFactory;
|
||||||
import com.andrewlalis.perfin.view.BindingUtil;
|
import com.andrewlalis.perfin.view.component.FileSelectionArea;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleListProperty;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.stage.FileChooser;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.DateTimeException;
|
import java.time.DateTimeException;
|
||||||
|
@ -45,9 +40,8 @@ public class CreateTransactionController implements RouteSelectionListener {
|
||||||
@FXML public ComboBox<Account> linkCreditAccountComboBox;
|
@FXML public ComboBox<Account> linkCreditAccountComboBox;
|
||||||
@FXML public Label linkedAccountsErrorLabel;
|
@FXML public Label linkedAccountsErrorLabel;
|
||||||
|
|
||||||
private final ObservableList<File> selectedAttachmentFiles = FXCollections.observableArrayList();
|
@FXML public VBox attachmentsVBox;
|
||||||
@FXML public VBox selectedFilesVBox;
|
private FileSelectionArea attachmentsSelectionArea;
|
||||||
@FXML public Label noSelectedFilesLabel;
|
|
||||||
|
|
||||||
@FXML public void initialize() {
|
@FXML public void initialize() {
|
||||||
// Setup error field validation.
|
// Setup error field validation.
|
||||||
|
@ -83,40 +77,13 @@ public class CreateTransactionController implements RouteSelectionListener {
|
||||||
updateLinkAccountComboBoxes(newValue);
|
updateLinkAccountComboBoxes(newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show the "no files selected" label when the list is empty. And sync the vbox with the selected files.
|
// Initialize the file selection area.
|
||||||
noSelectedFilesLabel.managedProperty().bind(noSelectedFilesLabel.visibleProperty());
|
attachmentsSelectionArea = new FileSelectionArea(
|
||||||
var filesListProp = new SimpleListProperty<>(selectedAttachmentFiles);
|
FileUtil::newAttachmentsFileChooser,
|
||||||
noSelectedFilesLabel.visibleProperty().bind(filesListProp.emptyProperty());
|
() -> attachmentsVBox.getScene().getWindow()
|
||||||
BindingUtil.mapContent(selectedFilesVBox.getChildren(), selectedAttachmentFiles, file -> {
|
|
||||||
Label filenameLabel = new Label(file.getName());
|
|
||||||
Button removeButton = new Button("Remove");
|
|
||||||
removeButton.setOnAction(event -> {
|
|
||||||
selectedAttachmentFiles.remove(file);
|
|
||||||
});
|
|
||||||
AnchorPane fileBox = new AnchorPane(filenameLabel, removeButton);
|
|
||||||
AnchorPane.setLeftAnchor(filenameLabel, 0.0);
|
|
||||||
AnchorPane.setRightAnchor(removeButton, 0.0);
|
|
||||||
return fileBox;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML public void selectAttachmentFile() {
|
|
||||||
FileChooser fileChooser = new FileChooser();
|
|
||||||
fileChooser.setTitle("Select Transaction Attachment(s)");
|
|
||||||
fileChooser.getExtensionFilters().addAll(
|
|
||||||
new FileChooser.ExtensionFilter(
|
|
||||||
"Attachment Files",
|
|
||||||
"*.pdf", "*.docx", "*.odt", "*.html", "*.txt", "*.md", "*.xml", "*.json",
|
|
||||||
"*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.bmp", "*.tiff"
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
List<File> files = fileChooser.showOpenMultipleDialog(amountField.getScene().getWindow());
|
attachmentsSelectionArea.allowMultiple.set(true);
|
||||||
if (files == null) return;
|
attachmentsVBox.getChildren().add(attachmentsSelectionArea);
|
||||||
for (var file : files) {
|
|
||||||
if (selectedAttachmentFiles.stream().noneMatch(f -> !f.equals(file) && f.getName().equals(file.getName()))) {
|
|
||||||
selectedAttachmentFiles.add(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML public void save() {
|
@FXML public void save() {
|
||||||
|
@ -136,7 +103,7 @@ public class CreateTransactionController implements RouteSelectionListener {
|
||||||
Currency currency = currencyChoiceBox.getValue();
|
Currency currency = currencyChoiceBox.getValue();
|
||||||
String description = descriptionField.getText() == null ? null : descriptionField.getText().strip();
|
String description = descriptionField.getText() == null ? null : descriptionField.getText().strip();
|
||||||
CreditAndDebitAccounts linkedAccounts = getSelectedAccounts();
|
CreditAndDebitAccounts linkedAccounts = getSelectedAccounts();
|
||||||
List<Path> attachments = selectedAttachmentFiles.stream().map(File::toPath).toList();
|
List<Path> attachments = attachmentsSelectionArea.getSelectedFiles();
|
||||||
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
|
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
|
||||||
repo.insert(
|
repo.insert(
|
||||||
utcTimestamp,
|
utcTimestamp,
|
||||||
|
@ -164,7 +131,7 @@ public class CreateTransactionController implements RouteSelectionListener {
|
||||||
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
|
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
|
||||||
amountField.setText("0");
|
amountField.setText("0");
|
||||||
descriptionField.setText(null);
|
descriptionField.setText(null);
|
||||||
selectedAttachmentFiles.clear();
|
attachmentsSelectionArea.clear();
|
||||||
Thread.ofVirtual().start(() -> {
|
Thread.ofVirtual().start(() -> {
|
||||||
Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
|
Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
|
||||||
var currencies = repo.findAllUsedCurrencies().stream()
|
var currencies = repo.findAllUsedCurrencies().stream()
|
||||||
|
|
|
@ -86,7 +86,10 @@ public class TransactionViewController {
|
||||||
"Are you sure you want to delete this transaction? This will " +
|
"Are you sure you want to delete this transaction? This will " +
|
||||||
"permanently remove the transaction and its effects on any linked " +
|
"permanently remove the transaction and its effects on any linked " +
|
||||||
"accounts, as well as remove any attachments from storage within " +
|
"accounts, as well as remove any attachments from storage within " +
|
||||||
"this app."
|
"this app.\n\n" +
|
||||||
|
"Note that incorrect or missing transactions can cause your " +
|
||||||
|
"account's balance to be incorrectly reported in Perfin, because " +
|
||||||
|
"it's derived from the most recent balance-record, and transactions."
|
||||||
);
|
);
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
|
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
package com.andrewlalis.perfin.data;
|
package com.andrewlalis.perfin.data;
|
||||||
|
|
||||||
|
import com.andrewlalis.perfin.data.util.DateUtil;
|
||||||
import com.andrewlalis.perfin.model.AccountEntry;
|
import com.andrewlalis.perfin.model.AccountEntry;
|
||||||
import com.andrewlalis.perfin.model.BalanceRecord;
|
import com.andrewlalis.perfin.model.BalanceRecord;
|
||||||
import com.andrewlalis.perfin.model.history.AccountHistoryItem;
|
import com.andrewlalis.perfin.model.history.AccountHistoryItem;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface AccountHistoryItemRepository extends AutoCloseable {
|
public interface AccountHistoryItemRepository extends AutoCloseable {
|
||||||
void recordAccountEntry(LocalDateTime timestamp, long accountId, long entryId);
|
void recordAccountEntry(LocalDateTime timestamp, long accountId, long entryId);
|
||||||
void recordBalanceRecord(LocalDateTime timestamp, long accountId, long recordId);
|
void recordBalanceRecord(LocalDateTime timestamp, long accountId, long recordId);
|
||||||
void recordText(LocalDateTime timestamp, long accountId, String text);
|
void recordText(LocalDateTime timestamp, long accountId, String text);
|
||||||
List<AccountHistoryItem> findMostRecentForAccount(long accountId, LocalDateTime utcTimestamp, int count);
|
List<AccountHistoryItem> findMostRecentForAccount(long accountId, LocalDateTime utcTimestamp, int count);
|
||||||
|
default Optional<AccountHistoryItem> getMostRecentForAccount(long accountId) {
|
||||||
|
var items = findMostRecentForAccount(accountId, DateUtil.nowAsUTC(), 1);
|
||||||
|
if (items.isEmpty()) return Optional.empty();
|
||||||
|
return Optional.of(items.getFirst());
|
||||||
|
}
|
||||||
String getTextItem(long itemId);
|
String getTextItem(long itemId);
|
||||||
AccountEntry getAccountEntryItem(long itemId);
|
AccountEntry getAccountEntryItem(long itemId);
|
||||||
BalanceRecord getBalanceRecordItem(long itemId);
|
BalanceRecord getBalanceRecordItem(long itemId);
|
||||||
|
|
|
@ -14,4 +14,9 @@ public class CurrencyUtil {
|
||||||
BigDecimal displayValue = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_UP);
|
BigDecimal displayValue = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_UP);
|
||||||
return nf.format(displayValue);
|
return nf.format(displayValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String formatMoneyAsBasicNumber(BigDecimal amount, Currency currency) {
|
||||||
|
BigDecimal displayValue = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_UP);
|
||||||
|
return displayValue.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package com.andrewlalis.perfin.data.util;
|
package com.andrewlalis.perfin.data.util;
|
||||||
|
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.FileVisitResult;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -54,4 +56,17 @@ public class FileUtil {
|
||||||
public static String getTypeSuffix(Path filePath) {
|
public static String getTypeSuffix(Path filePath) {
|
||||||
return getTypeSuffix(filePath.getFileName().toString());
|
return getTypeSuffix(filePath.getFileName().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static FileChooser newAttachmentsFileChooser() {
|
||||||
|
FileChooser fileChooser = new FileChooser();
|
||||||
|
fileChooser.setTitle("Select Attachments");
|
||||||
|
fileChooser.getExtensionFilters().addAll(
|
||||||
|
new FileChooser.ExtensionFilter(
|
||||||
|
"Attachment Files",
|
||||||
|
"*.pdf", "*.docx", "*.odt", "*.html", "*.txt", "*.md", "*.xml", "*.json",
|
||||||
|
"*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.bmp", "*.tiff"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return fileChooser;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,11 +42,9 @@ public class AccountHistoryItemTile extends BorderPane {
|
||||||
Text amountText = new Text(CurrencyUtil.formatMoney(entry.getSignedAmount(), entry.getCurrency()));
|
Text amountText = new Text(CurrencyUtil.formatMoney(entry.getSignedAmount(), entry.getCurrency()));
|
||||||
Hyperlink transactionLink = new Hyperlink("Transaction #" + entry.getTransactionId());
|
Hyperlink transactionLink = new Hyperlink("Transaction #" + entry.getTransactionId());
|
||||||
return new TextFlow(
|
return new TextFlow(
|
||||||
new Text("Entry added with value of "),
|
|
||||||
amountText,
|
|
||||||
new Text(", linked with "),
|
|
||||||
transactionLink,
|
transactionLink,
|
||||||
new Text(".")
|
new Text("posted as a " + entry.getType().name().toLowerCase() + " to this account, with a value of"),
|
||||||
|
amountText
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
package com.andrewlalis.perfin.view.component;
|
||||||
|
|
||||||
|
import com.andrewlalis.perfin.view.BindingUtil;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.ListProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleListProperty;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pane within which a user can select one or more files.
|
||||||
|
*/
|
||||||
|
public class FileSelectionArea extends VBox {
|
||||||
|
public final BooleanProperty allowMultiple = new SimpleBooleanProperty(false);
|
||||||
|
private final ObservableList<Path> selectedFiles = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
public FileSelectionArea(Supplier<FileChooser> fileChooserSupplier, Supplier<Window> windowSupplier) {
|
||||||
|
getStyleClass().addAll("std-padding", "std-spacing");
|
||||||
|
|
||||||
|
VBox filesVBox = new VBox();
|
||||||
|
filesVBox.getStyleClass().addAll("std-padding", "std-spacing");
|
||||||
|
BindingUtil.mapContent(filesVBox.getChildren(), selectedFiles, this::buildFileItem);
|
||||||
|
ListProperty<Path> selectedFilesProperty = new SimpleListProperty<>(selectedFiles);
|
||||||
|
|
||||||
|
Label noFilesLabel = new Label("No files selected.");
|
||||||
|
noFilesLabel.managedProperty().bind(noFilesLabel.visibleProperty());
|
||||||
|
noFilesLabel.visibleProperty().bind(selectedFilesProperty.emptyProperty());
|
||||||
|
|
||||||
|
Button selectFilesButton = new Button("Select files");
|
||||||
|
selectFilesButton.setOnAction(event -> onSelectFileClicked(fileChooserSupplier.get(), windowSupplier.get()));
|
||||||
|
selectFilesButton.disableProperty().bind(
|
||||||
|
allowMultiple.not().and(selectedFilesProperty.emptyProperty().not())
|
||||||
|
);
|
||||||
|
|
||||||
|
getChildren().addAll(
|
||||||
|
filesVBox,
|
||||||
|
noFilesLabel,
|
||||||
|
selectFilesButton
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Path> getSelectedFiles() {
|
||||||
|
return Collections.unmodifiableList(selectedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
selectedFiles.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node buildFileItem(Path path) {
|
||||||
|
Label filenameLabel = new Label(path.getFileName().toString());
|
||||||
|
filenameLabel.getStyleClass().addAll("mono-font");
|
||||||
|
Button removeButton = new Button("Remove");
|
||||||
|
removeButton.setOnAction(event -> selectedFiles.remove(path));
|
||||||
|
AnchorPane pane = new AnchorPane(filenameLabel, removeButton);
|
||||||
|
AnchorPane.setLeftAnchor(filenameLabel, 0.0);
|
||||||
|
AnchorPane.setTopAnchor(filenameLabel, 0.0);
|
||||||
|
AnchorPane.setBottomAnchor(filenameLabel, 0.0);
|
||||||
|
|
||||||
|
AnchorPane.setRightAnchor(removeButton, 0.0);
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSelectFileClicked(FileChooser fileChooser, Window owner) {
|
||||||
|
if (allowMultiple.get()) {
|
||||||
|
var files = fileChooser.showOpenMultipleDialog(owner);
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
Path path = file.toPath();
|
||||||
|
if (!selectedFiles.contains(path)) {
|
||||||
|
selectedFiles.add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
File file = fileChooser.showOpenDialog(owner);
|
||||||
|
if (file != null && !selectedFiles.contains(file.toPath())) {
|
||||||
|
selectedFiles.add(file.toPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,6 +53,7 @@
|
||||||
<VBox styleClass="std-padding,std-spacing">
|
<VBox styleClass="std-padding,std-spacing">
|
||||||
<Label text="Actions" styleClass="bold-text"/>
|
<Label text="Actions" styleClass="bold-text"/>
|
||||||
<Button text="Edit" onAction="#goToEditPage"/>
|
<Button text="Edit" onAction="#goToEditPage"/>
|
||||||
|
<Button text="Record Balance" onAction="#goToCreateBalanceRecord"/>
|
||||||
<Button text="Archive" onAction="#archiveAccount"/>
|
<Button text="Archive" onAction="#archiveAccount"/>
|
||||||
<Button text="Delete" onAction="#deleteAccount"/>
|
<Button text="Delete" onAction="#deleteAccount"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<BorderPane xmlns="http://javafx.com/javafx"
|
||||||
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="com.andrewlalis.perfin.control.CreateBalanceRecordController"
|
||||||
|
>
|
||||||
|
<top>
|
||||||
|
<HBox styleClass="std-padding,std-spacing">
|
||||||
|
<Label text="Create New Balance Record" styleClass="large-text,bold-text"/>
|
||||||
|
</HBox>
|
||||||
|
</top>
|
||||||
|
<center>
|
||||||
|
<VBox styleClass="std-padding,std-spacing">
|
||||||
|
<VBox>
|
||||||
|
<Label text="Timestamp" labelFor="${timestampField}"/>
|
||||||
|
<TextField fx:id="timestampField"/>
|
||||||
|
</VBox>
|
||||||
|
<VBox>
|
||||||
|
<Label text="Balance" labelFor="${balanceField}"/>
|
||||||
|
<TextField fx:id="balanceField"/>
|
||||||
|
</VBox>
|
||||||
|
<VBox fx:id="attachmentsVBox">
|
||||||
|
<Label text="Attachments"/>
|
||||||
|
</VBox>
|
||||||
|
</VBox>
|
||||||
|
</center>
|
||||||
|
<bottom>
|
||||||
|
<HBox styleClass="std-padding,std-spacing">
|
||||||
|
<Button text="Save" onAction="#save"/>
|
||||||
|
<Button text="Cancel" onAction="#cancel"/>
|
||||||
|
</HBox>
|
||||||
|
</bottom>
|
||||||
|
</BorderPane>
|
|
@ -53,21 +53,12 @@
|
||||||
</VBox>
|
</VBox>
|
||||||
</HBox>
|
</HBox>
|
||||||
<Label fx:id="linkedAccountsErrorLabel" styleClass="error-text" wrapText="true"/>
|
<Label fx:id="linkedAccountsErrorLabel" styleClass="error-text" wrapText="true"/>
|
||||||
<Label
|
|
||||||
text="Every transaction must be linked to one of your accounts. In the case of transfer between accounts, then both the sender and receiver accounts should be linked."
|
|
||||||
styleClass="small-text"
|
|
||||||
style="-fx-min-width: 100px; -fx-min-height: 100px; -fx-alignment: top-left"
|
|
||||||
wrapText="true"
|
|
||||||
VBox.vgrow="NEVER"
|
|
||||||
/>
|
|
||||||
</VBox>
|
</VBox>
|
||||||
<!-- Container for attachments -->
|
<!-- Container for attachments -->
|
||||||
<VBox>
|
<VBox fx:id="attachmentsVBox">
|
||||||
<Label text="Attachments" styleClass="bold-text"/>
|
<Label text="Attachments" styleClass="bold-text"/>
|
||||||
<Label text="Attach receipts, invoices, or other content to this transaction." styleClass="small-text" wrapText="true"/>
|
<Label text="Attach receipts, invoices, or other content to this transaction." styleClass="small-text" wrapText="true"/>
|
||||||
<VBox fx:id="selectedFilesVBox" styleClass="std-padding,std-spacing" VBox.vgrow="NEVER"/>
|
<!-- FileSelectionArea inserted here! -->
|
||||||
<Label text="No attachments selected." fx:id="noSelectedFilesLabel"/>
|
|
||||||
<Button text="Select attachments" onAction="#selectAttachmentFile"/>
|
|
||||||
</VBox>
|
</VBox>
|
||||||
</VBox>
|
</VBox>
|
||||||
</ScrollPane>
|
</ScrollPane>
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.7 KiB |
|
@ -47,7 +47,7 @@
|
||||||
</ScrollPane>
|
</ScrollPane>
|
||||||
</VBox>
|
</VBox>
|
||||||
<FlowPane styleClass="std-padding, std-spacing">
|
<FlowPane styleClass="std-padding, std-spacing">
|
||||||
<Button text="Delete" onAction="#deleteTransaction"/>
|
<Button text="Delete this transaction" onAction="#deleteTransaction"/>
|
||||||
</FlowPane>
|
</FlowPane>
|
||||||
</VBox>
|
</VBox>
|
||||||
</ScrollPane>
|
</ScrollPane>
|
||||||
|
|
Loading…
Reference in New Issue