Added total spent amount to vendor page, and added analytics method to get total vendor spend.
This commit is contained in:
parent
77f2966291
commit
1898783c56
|
@ -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<String>()
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Pair<TransactionCategory, BigDecimal>> getIncomeByCategory(TimestampRange range, Currency currency);
|
||||
List<Pair<TransactionCategory, BigDecimal>> getIncomeByRootCategory(TimestampRange range, Currency currency);
|
||||
List<Pair<TransactionVendor, BigDecimal>> 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<MoneyValue> getVendorSpend(TimestampRange range, long vendorId);
|
||||
}
|
||||
|
|
|
@ -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<MoneyValue> 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();
|
||||
|
|
|
@ -11,24 +11,45 @@
|
|||
<Label text="Edit Vendor" styleClass="bold-text,large-font,std-padding"/>
|
||||
</top>
|
||||
<center>
|
||||
<VBox>
|
||||
<PropertiesPane hgap="5" vgap="5" styleClass="std-padding" maxWidth="500">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="NEVER" halignment="LEFT" minWidth="150"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" halignment="RIGHT"/>
|
||||
</columnConstraints>
|
||||
<ScrollPane fitToWidth="true" fitToHeight="true">
|
||||
<VBox style="-fx-max-width: 500px;">
|
||||
<!-- Basic properties -->
|
||||
<PropertiesPane hgap="5" vgap="5" styleClass="std-padding">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="NEVER" halignment="LEFT" minWidth="150"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" halignment="RIGHT"/>
|
||||
</columnConstraints>
|
||||
|
||||
<Label text="Name" labelFor="${nameField}"/>
|
||||
<TextField fx:id="nameField"/>
|
||||
<Label text="Name" labelFor="${nameField}" styleClass="bold-text"/>
|
||||
<TextField fx:id="nameField"/>
|
||||
|
||||
<Label text="Description" labelFor="${descriptionField}"/>
|
||||
<TextArea fx:id="descriptionField" wrapText="true"/>
|
||||
</PropertiesPane>
|
||||
<Separator/>
|
||||
<HBox styleClass="std-padding,std-spacing" alignment="CENTER_RIGHT">
|
||||
<Button text="Save" fx:id="saveButton" onAction="#save"/>
|
||||
<Button text="Cancel" onAction="#cancel"/>
|
||||
</HBox>
|
||||
</VBox>
|
||||
<Label text="Description" labelFor="${descriptionField}" styleClass="bold-text"/>
|
||||
<TextArea
|
||||
fx:id="descriptionField"
|
||||
wrapText="true"
|
||||
style="-fx-pref-height: 100px; -fx-min-height: 100px;"
|
||||
/>
|
||||
</PropertiesPane>
|
||||
|
||||
<!-- Some stats about the vendor -->
|
||||
<PropertiesPane hgap="5" vgap="5" styleClass="std-padding">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="NEVER" halignment="LEFT" minWidth="150"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" halignment="RIGHT"/>
|
||||
</columnConstraints>
|
||||
|
||||
<Label text="Total Spent" labelFor="${totalSpentField}" styleClass="bold-text"/>
|
||||
<Label fx:id="totalSpentField" styleClass="mono-font"/>
|
||||
</PropertiesPane>
|
||||
|
||||
|
||||
<!-- Buttons -->
|
||||
<Separator/>
|
||||
<HBox styleClass="std-padding,std-spacing" alignment="CENTER_RIGHT">
|
||||
<Button text="Save" fx:id="saveButton" onAction="#save"/>
|
||||
<Button text="Cancel" onAction="#cancel"/>
|
||||
</HBox>
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
</center>
|
||||
</BorderPane>
|
||||
|
|
Loading…
Reference in New Issue