diff --git a/src/main/java/com/andrewlalis/perfin/control/EditVendorController.java b/src/main/java/com/andrewlalis/perfin/control/EditVendorController.java index 3c18cb4..b52abdf 100644 --- a/src/main/java/com/andrewlalis/perfin/control/EditVendorController.java +++ b/src/main/java/com/andrewlalis/perfin/control/EditVendorController.java @@ -1,19 +1,25 @@ package com.andrewlalis.perfin.control; import com.andrewlalis.javafx_scene_router.RouteSelectionListener; +import com.andrewlalis.perfin.data.AnalyticsRepository; import com.andrewlalis.perfin.data.DataSource; +import com.andrewlalis.perfin.data.TimestampRange; import com.andrewlalis.perfin.data.TransactionVendorRepository; +import com.andrewlalis.perfin.data.util.CurrencyUtil; import com.andrewlalis.perfin.model.Profile; import com.andrewlalis.perfin.model.TransactionVendor; import com.andrewlalis.perfin.view.component.validation.ValidationApplier; import com.andrewlalis.perfin.view.component.validation.validators.PredicateValidator; +import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import java.util.Objects; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import static com.andrewlalis.perfin.PerfinApp.router; @@ -24,6 +30,8 @@ public class EditVendorController implements RouteSelectionListener { @FXML public TextArea descriptionField; @FXML public Button saveButton; + @FXML public Label totalSpentField; + @FXML public void initialize() { var nameValid = new ValidationApplier<>(new PredicateValidator() .addTerminalPredicate(s -> s != null && !s.isBlank(), "Name is required.") @@ -63,9 +71,19 @@ public class EditVendorController implements RouteSelectionListener { this.vendor = tv; nameField.setText(vendor.getName()); descriptionField.setText(vendor.getDescription()); + Profile.getCurrent().dataSource().mapRepoAsync( + AnalyticsRepository.class, + repo -> repo.getVendorSpend(TimestampRange.unbounded(), vendor.id) + ).thenAccept(amounts -> { + String text = amounts.stream() + .map(CurrencyUtil::formatMoney) + .collect(Collectors.joining(", ")); + Platform.runLater(() -> totalSpentField.setText(text.isBlank() ? "None" : text)); + }); } else { nameField.setText(null); descriptionField.setText(null); + totalSpentField.setText(null); } } diff --git a/src/main/java/com/andrewlalis/perfin/data/AnalyticsRepository.java b/src/main/java/com/andrewlalis/perfin/data/AnalyticsRepository.java index 6b92907..170723d 100644 --- a/src/main/java/com/andrewlalis/perfin/data/AnalyticsRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/AnalyticsRepository.java @@ -1,6 +1,7 @@ package com.andrewlalis.perfin.data; import com.andrewlalis.perfin.data.util.Pair; +import com.andrewlalis.perfin.model.MoneyValue; import com.andrewlalis.perfin.model.TransactionCategory; import com.andrewlalis.perfin.model.TransactionVendor; @@ -14,4 +15,13 @@ public interface AnalyticsRepository extends Repository, AutoCloseable { List> getIncomeByCategory(TimestampRange range, Currency currency); List> getIncomeByRootCategory(TimestampRange range, Currency currency); List> getSpendByVendor(TimestampRange range, Currency currency); + + /** + * Gets the amount spent, grouped by currency, on a specific vendor. + * @param range The time range to search in. + * @param vendorId The id of the vendor to search with. + * @return A list of money values with the total amount spent in each + * currency. An empty list is returned if no money is spent. + */ + List getVendorSpend(TimestampRange range, long vendorId); } diff --git a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAnalyticsRepository.java b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAnalyticsRepository.java index 55ace6c..1ee042c 100644 --- a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAnalyticsRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAnalyticsRepository.java @@ -5,6 +5,7 @@ import com.andrewlalis.perfin.data.TimestampRange; import com.andrewlalis.perfin.data.util.DbUtil; import com.andrewlalis.perfin.data.util.Pair; import com.andrewlalis.perfin.model.AccountEntry; +import com.andrewlalis.perfin.model.MoneyValue; import com.andrewlalis.perfin.model.TransactionCategory; import com.andrewlalis.perfin.model.TransactionVendor; import javafx.scene.paint.Color; @@ -72,6 +73,38 @@ public record JdbcAnalyticsRepository(Connection conn) implements AnalyticsRepos ); } + @Override + public List getVendorSpend(TimestampRange range, long vendorId) { + return DbUtil.findAll( + conn, + """ + SELECT + SUM(transaction.amount) AS total, + transaction.currency AS currency, + FROM transaction + WHERE + transaction.vendor_id = ? AND + transaction.timestamp >= ? AND + transaction.timestamp <= ? AND + '!exclude' NOT IN ( + SELECT tt.name + FROM transaction_tag tt + LEFT JOIN transaction_tag_join ttj ON tt.id = ttj.tag_id + WHERE ttj.transaction_id = transaction.id + ) AND + (SELECT COUNT(ae.id) FROM account_entry ae WHERE ae.transaction_id = transaction.id) = 1 AND + (SELECT COUNT(ae.id) FROM account_entry ae WHERE ae.transaction_id = transaction.id AND ae.type = 'CREDIT') = 1 + GROUP BY transaction.currency + ORDER BY total DESC""", + List.of(vendorId, range.start(), range.end()), + rs -> { + BigDecimal total = rs.getBigDecimal(1); + String currencyCode = rs.getString(2); + return new MoneyValue(total, Currency.getInstance(currencyCode)); + } + ); + } + @Override public void close() throws Exception { conn.close(); diff --git a/src/main/resources/edit-vendor.fxml b/src/main/resources/edit-vendor.fxml index 987466e..cf4169f 100644 --- a/src/main/resources/edit-vendor.fxml +++ b/src/main/resources/edit-vendor.fxml @@ -11,24 +11,45 @@