Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MODFIN-391]. Minimize amount of requests to retrieve transactions for ledger #443

Merged
merged 9 commits into from
Jan 9, 2025
25 changes: 25 additions & 0 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,17 @@
}
]
},
{
"id": "finance-storage.transaction-totals",
"version": "1.0",
"handlers": [
{
"methods": ["GET"],
"pathPattern": "/finance-storage/transaction-totals",
"permissionsRequired": ["finance-storage.transaction-totals.collection.get"]
}
]
},
{
"id": "finance-storage.fund-update-logs",
"version": "1.0",
Expand Down Expand Up @@ -984,6 +995,19 @@
"finance-storage.transactions.batch.execute"
]
},
{
"permissionName" : "finance-storage.transaction-totals.collection.get",
"displayName" : "finance-storage.transaction-totals.collection get",
"description" : "Get collection of transaction totals"
},
{
"permissionName" : "finance-storage.transaction-totals.all",
"displayName" : "All transaction totals perms",
"description" : "All permissions for the transaction total",
"subPermissions" : [
"finance-storage.transaction-totals.collection.get"
]
},
{
"permissionName" : "finance-storage.fund-update-logs.collection.get",
"displayName" : "fund-update-logs-collection get",
Expand Down Expand Up @@ -1053,6 +1077,7 @@
"finance-storage.groups.all",
"finance-storage.ledgers.all",
"finance-storage.transactions.all",
"finance-storage.transaction-totals.all",
"finance-storage.fund-types.all",
"finance-storage.fund-update-logs.all",
"finance-storage.finance-data.all"
Expand Down
37 changes: 37 additions & 0 deletions ramls/transaction_totals.raml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#%RAML 1.0
title: "mod-finance-storage"
baseUri: https://github.com/folio-org/mod-finance-storage
version: v4

documentation:
- title: mod-finance-storage (Transactions)
content: <b>Read API used to manage transaction totals.</b>

types:
errors: !include raml-util/schemas/errors.schema
transaction-total: !include acq-models/mod-finance/schemas/transaction_total.json
transaction-total-collection: !include acq-models/mod-finance/schemas/transaction_total_collection.json
UUID:
type: string
pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$

traits:
pageable: !include raml-util/traits/pageable.raml
searchable: !include raml-util/traits/searchable.raml
validate: !include raml-util/traits/validation.raml

resourceTypes:
collection-get: !include raml-util/rtypes/collection-get.raml
collection-item-get: !include raml-util/rtypes/item-collection-get-with-json-response.raml

/finance-storage/transaction-totals:
type:
collection-get:
exampleCollection: !include acq-models/mod-finance/examples/transaction_total_collection.sample
schemaCollection: transaction-total-collection
get:
description: Get list of transaction totals
is: [
searchable: {description: "with valid searchable fields: for example code", example: "[\"code\", \"MEDGRANT\", \"=\"]"},
pageable
]
26 changes: 26 additions & 0 deletions src/main/java/org/folio/rest/impl/TransactionTotalApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.folio.rest.impl;

import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import org.folio.rest.annotations.Validate;
import org.folio.rest.jaxrs.model.TransactionTotal;
import org.folio.rest.jaxrs.model.TransactionTotalCollection;
import org.folio.rest.jaxrs.resource.FinanceStorageTransactionTotals;
import org.folio.rest.persist.PgUtil;

import javax.ws.rs.core.Response;
import java.util.Map;

public class TransactionTotalApi implements FinanceStorageTransactionTotals {

public static final String TRANSACTION_TOTALS_VIEW = "transaction_totals_view";

@Override
@Validate
public void getFinanceStorageTransactionTotals(String query, String totalRecords, int offset, int limit,
Map<String, String> okapiHeaders, Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
PgUtil.get(TRANSACTION_TOTALS_VIEW, TransactionTotal.class, TransactionTotalCollection.class, query, offset, limit, okapiHeaders, vertxContext,
FinanceStorageTransactionTotals.GetFinanceStorageTransactionTotalsResponse.class, asyncResultHandler);
}
}
5 changes: 5 additions & 0 deletions src/main/resources/templates/db_scripts/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@
"run": "after",
"snippetPath": "all_finance_data_view.sql",
"fromModuleVersion": "mod-finance-storage-8.8.0"
},
{
"run": "after",
"snippetPath": "transaction_totals_view.sql",
"fromModuleVersion": "mod-finance-storage-8.8.0"
}
],
"tables": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
CREATE OR REPLACE VIEW ${myuniversity}_${mymodule}.transaction_totals_view AS
with allocations_without_initial_entry as(
select c2.id
from(
select row_number() over(partition by c1.fiscalyearid, c1.tofundid order by c1.creation_date) rn, *
from ${myuniversity}_${mymodule}.transaction c1
where (c1.jsonb->>'transactionType') = 'Allocation'
and c1.fromfundid is null
) c2
where c2.rn > 1
)

select t4.fiscal_year_id id,
jsonb_build_object(
'fiscalYearId', t4.fiscal_year_id,
'transactionType', t4.transaction_type,
'currency', t4.currency,
'fromFundId', t4.from_fund_id,
'toFundId', t4.to_fund_id,
'amount', t4.amount
) jsonb
from(
select (t3.jsonb->>'fiscalYearId') fiscal_year_id,
(t3.jsonb->>'transactionType') transaction_type,
(t3.jsonb->>'currency') currency,
(t3.jsonb->>'fromFundId') from_fund_id,
(t3.jsonb->>'toFundId') to_fund_id,
coalesce(sum((t3.jsonb->>'amount')::numeric), 0) amount
from(
select t1.*
from ${myuniversity}_${mymodule}.transaction t1
left join allocations_without_initial_entry c3 on c3.id = t1.id
where (t1.jsonb->>'transactionType') = 'Allocation'
and ((t1.fromfundid is null and c3.id = t1.id) or (t1.fromfundid is not null))
union all
select t2.*
from ${myuniversity}_${mymodule}.transaction t2
where (t2.jsonb->>'transactionType') in('Transfer', 'Rollover transfer')
) t3
group by (t3.jsonb->>'fiscalYearId'),
(t3.jsonb->>'transactionType'),
(t3.jsonb->>'currency'),
(t3.jsonb->>'fromFundId'),
(t3.jsonb->>'toFundId')
) t4
;
4 changes: 4 additions & 0 deletions src/test/java/org/folio/StorageTestSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.folio.rest.impl.LedgerRolloverBudgetTest;
import org.folio.rest.impl.TenantSampleDataTest;
import org.folio.rest.impl.TransactionTest;
import org.folio.rest.impl.TransactionTotalApiTest;
import org.folio.rest.jaxrs.model.TenantJob;
import org.folio.rest.persist.PostgresClient;
import org.folio.rest.tools.utils.NetworkUtils;
Expand Down Expand Up @@ -224,4 +225,7 @@ class FinanceDataApiTestNested extends FinanceDataApiTest {}

@Nested
class FinanceDataServiceTestNested extends FinanceDataServiceTest {}

@Nested
class TransactionTotalApiTestNested extends TransactionTotalApiTest {}
}
2 changes: 1 addition & 1 deletion src/test/java/org/folio/rest/impl/FinanceDataApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public void positive_testUpdateFinanceData() {
fyFinanceData.setBudgetAllowableExpenditure(expectedNumber);
fyFinanceData.setBudgetAllowableEncumbrance(expectedNumber);

var updatedCollection = new FyFinanceDataCollection().withFyFinanceData(List.of(fyFinanceData)).withTotalRecords(1);
var updatedCollection = new FyFinanceDataCollection().withFyFinanceData(List.of(fyFinanceData)).withUpdateType(FyFinanceDataCollection.UpdateType.COMMIT).withTotalRecords(1);

// Update finance data as a bulk
var updateResponse = putData(FINANCE_DATA_ENDPOINT, JsonObject.mapFrom(updatedCollection).encodePrettily(), TENANT_HEADER);
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/org/folio/rest/impl/TransactionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class TransactionTest extends TestBase {
protected static final Header TRANSACTION_TENANT_HEADER = new Header(OKAPI_HEADER_TENANT, TRANSACTION_TEST_TENANT);

private static final String BATCH_TRANSACTION_SAMPLE = "data/transactions/batch/batch_with_patch.json";
private static final String BATCH_TRANSACTION_ENDPOINT = "/finance-storage/transactions/batch-all-or-nothing";
protected static final String BATCH_TRANSACTION_ENDPOINT = "/finance-storage/transactions/batch-all-or-nothing";
private static final String TRANSACTION_ENDPOINT_BY_ID = "/finance-storage/transactions/{id}";
private static TenantJob tenantJob;

Expand Down
108 changes: 108 additions & 0 deletions src/test/java/org/folio/rest/impl/TransactionTotalApiTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.folio.rest.impl;

import io.vertx.junit5.VertxExtension;
import org.apache.commons.lang3.tuple.Pair;
import org.folio.rest.jaxrs.model.Batch;
import org.folio.rest.jaxrs.model.TenantJob;
import org.folio.rest.jaxrs.model.Transaction;
import org.folio.rest.jaxrs.resource.FinanceStorageTransactionTotals;
import org.folio.rest.persist.HelperUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import java.util.List;
import java.util.UUID;

import static org.folio.rest.impl.TransactionTest.BATCH_TRANSACTION_ENDPOINT;
import static org.folio.rest.impl.TransactionTest.TRANSACTION_TENANT_HEADER;
import static org.folio.rest.utils.TenantApiTestUtil.deleteTenant;
import static org.folio.rest.utils.TenantApiTestUtil.prepareTenant;
import static org.folio.rest.utils.TenantApiTestUtil.purge;
import static org.folio.rest.utils.TestEntities.BUDGET;
import static org.folio.rest.utils.TestEntities.FISCAL_YEAR;
import static org.folio.rest.utils.TestEntities.FUND;
import static org.folio.rest.utils.TestEntities.LEDGER;
import static org.hamcrest.Matchers.equalTo;

@ExtendWith(VertxExtension.class)
public class TransactionTotalApiTest extends TestBase {

private static final String TRANSACTION_TOTALS_ENDPOINT = HelperUtils.getEndpoint(FinanceStorageTransactionTotals.class);

private static TenantJob tenantJob;

@BeforeEach
void prepareData() {
tenantJob = prepareTenant(TRANSACTION_TENANT_HEADER, false, true);
}

@AfterEach
void deleteData() {
purge(TRANSACTION_TENANT_HEADER);
}

@AfterAll
public static void after() {
deleteTenant(tenantJob, TRANSACTION_TENANT_HEADER);
}

@Test
void getFinanceStorageTransactionTotals() {
givenTestData(TRANSACTION_TENANT_HEADER,
Pair.of(FISCAL_YEAR, FISCAL_YEAR.getPathToSampleFile()),
Pair.of(LEDGER, LEDGER.getPathToSampleFile()),
Pair.of(FUND, FUND.getPathToSampleFile()),
Pair.of(BUDGET, BUDGET.getPathToSampleFile()));

var initialAllocation = new Transaction()
.withId(UUID.randomUUID().toString())
.withCurrency("USD")
.withToFundId(FUND.getId())
.withTransactionType(Transaction.TransactionType.ALLOCATION)
.withAmount(50000.0)
.withFiscalYearId(FISCAL_YEAR.getId())
.withSource(Transaction.Source.USER);
var increaseAllocation = new Transaction()
.withId(UUID.randomUUID().toString())
.withCurrency("USD")
.withToFundId(FUND.getId())
.withTransactionType(Transaction.TransactionType.ALLOCATION)
.withAmount(20000.0)
.withFiscalYearId(FISCAL_YEAR.getId())
.withSource(Transaction.Source.USER);
var decreaseAllocation = new Transaction()
.withId(UUID.randomUUID().toString())
.withCurrency("USD")
.withFromFundId(FUND.getId())
.withTransactionType(Transaction.TransactionType.ALLOCATION)
.withAmount(5000.0)
.withFiscalYearId(FISCAL_YEAR.getId())
.withSource(Transaction.Source.USER);

var batch = new Batch()
.withTransactionsToCreate(List.of(initialAllocation, increaseAllocation, decreaseAllocation));
postData(BATCH_TRANSACTION_ENDPOINT, valueAsString(batch), TRANSACTION_TENANT_HEADER)
.then().statusCode(204);

// We expect 1 entry: 20000 (50000 is intentionally ignored)
var toFundQuery = String.format("?query=(fiscalYearId==%s AND transactionType==(Allocation OR Transfer OR Rollover transfer)) AND toFundId==%s", FISCAL_YEAR.getId(), FUND.getId());
getData(TRANSACTION_TOTALS_ENDPOINT + toFundQuery, TRANSACTION_TENANT_HEADER)
.then()
.log().all()
.statusCode(200)
.body("transactionTotals[0].amount", equalTo(20000.0F))
.body("totalRecords", equalTo(1));

// We expect 1 entry: -5000
var fromFundQuery = String.format("?query=(fiscalYearId==%s AND transactionType==(Allocation OR Transfer OR Rollover transfer)) AND fromFundId==%s", FISCAL_YEAR.getId(), FUND.getId());
getData(TRANSACTION_TOTALS_ENDPOINT + fromFundQuery, TRANSACTION_TENANT_HEADER)
.then()
.log().all()
.statusCode(200)
.body("transactionTotals[0].amount", equalTo(5000.0F))
.body("totalRecords", equalTo(1));
}
}
Loading