Allow filtering by category to also filter by child categories.
Build and Deploy API / build-and-deploy (push) Successful in 1m44s Details

This commit is contained in:
andrewlalis 2025-12-01 20:10:36 -05:00
parent 6415311925
commit 3368cfa96c
2 changed files with 38 additions and 4 deletions

View File

@ -223,9 +223,8 @@ class SqliteTransactionRepository : TransactionRepository {
// 1. Get the total count of transactions that match the search filters.
qb.select("COUNT (DISTINCT txn.id)");
applyFilters(qb, request);
applyFilters(qb, request, new SqliteTransactionCategoryRepository(db));
string countQuery = qb.build();
// writeln(countQuery);
Statement countStmt = db.prepare(countQuery);
qb.applyArgBindings(countStmt);
auto countResult = countStmt.execute();

View File

@ -14,17 +14,23 @@ import std.uri;
import std.uni;
import util.sqlite;
import transaction.data;
/**
* Applies a set of filters to a query builder for searching over transactions.
* Params:
* qb = The query builder to add WHERE clauses and argument bindings to.
* request = The request to get filter options from.
* categoryRepo = Repository for fetching category data, which might be
* needed if the user is filtering by a parent category.
*/
void applyFilters(ref QueryBuilder qb, in ServerHttpRequest request) {
void applyFilters(
ref QueryBuilder qb,
in ServerHttpRequest request,
TransactionCategoryRepository categoryRepo
) {
applyPropertyInFilter!string(qb, request, "tags.tag", "tag");
applyPropertyInFilter!ulong(qb, request, "vendor.id", "vendor");
applyPropertyInFilter!ulong(qb, request, "category.id", "category");
applyPropertyInFilter!string(qb, request, "txn.currency", "currency");
applyPropertyInFilter!ulong(qb, request, "account_credit.id", "credited-account");
applyPropertyInFilter!ulong(qb, request, "account_debit.id", "debited-account");
@ -45,6 +51,25 @@ void applyFilters(ref QueryBuilder qb, in ServerHttpRequest request) {
});
}
// Separate filter that handles the hierarchical category relationship, so
// if a parent category is filtered, all children are also included.
if (request.hasParam("category")) {
ulong[] categoryIds = request.getParamValues!ulong("category");
auto app = appender!(ulong[]);
foreach (id; categoryIds) {
app ~= getAllPossibleCategoryIds(id, categoryRepo);
}
ulong[] allPossibleIds = app[];
if (allPossibleIds.length > 0) {
qb.where("(" ~ "txn.category_id = ?".repeat(allPossibleIds.length).join(" OR ") ~ ")");
qb.withArgBinding((ref stmt, ref idx) {
foreach (value; allPossibleIds) {
stmt.bind(idx++, value);
}
});
}
}
if (request.hasParam("min-amount")) {
ulong[] values = request.getParamValues!ulong("min-amount");
if (values.length > 0) {
@ -140,3 +165,13 @@ private T[] getParamValues(T = string)(in ServerHttpRequest request, string key)
}
return [];
}
private ulong[] getAllPossibleCategoryIds(ulong parentId, TransactionCategoryRepository categoryRepo) {
auto app = appender!(ulong[]);
app ~= parentId;
foreach (child; categoryRepo.findAllByParentId(Optional!ulong.of(parentId))) {
app ~= child.id;
app ~= getAllPossibleCategoryIds(child.id, categoryRepo);
}
return app[];
}