Skip to content

Commit

Permalink
[MODFIN-379] - Create API for retrieve finance data (#262)
Browse files Browse the repository at this point in the history
* [MODFIN-379] - Create API for fund updates logs

* Use without acq Units restrictions

* [MODFIN-388] - Implement endpoint to return all finance data for FY through acqUnitRestriciton

* Added to api test suite

* Fixed acqUnitsRestriction and added tests

* remove unused variable

* fix unit test

* change acq-models to master

* Fix acqUnitRestriction cql

* Add required module permissions

* Add comment
  • Loading branch information
azizbekxm authored Nov 20, 2024
1 parent 3b5dde8 commit f981adc
Show file tree
Hide file tree
Showing 14 changed files with 531 additions and 13 deletions.
53 changes: 47 additions & 6 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -752,27 +752,58 @@
{
"methods": ["GET"],
"pathPattern": "/finance/fund-update-logs",
"permissionsRequired": ["finance.fund-update-logs.collection.get"]
"permissionsRequired": ["finance.fund-update-logs.collection.get"],
"modulePermissions": [
"finance-storage.fund-update-logs.collection.get"
]
},
{
"methods": ["GET"],
"pathPattern": "/finance/fund-update-logs/{id}",
"permissionsRequired": ["finance.fund-update-logs.item.get"]
"permissionsRequired": ["finance.fund-update-logs.item.get"],
"modulePermissions": [
"finance-storage.fund-update-logs.item.get"
]
},
{
"methods": ["POST"],
"pathPattern": "/finance/fund-update-logs",
"permissionsRequired": ["finance.fund-update-logs.item.post"]
"permissionsRequired": ["finance.fund-update-logs.item.post"],
"modulePermissions": [
"finance-storage.fund-update-logs.item.post"
]
},
{
"methods": ["PUT"],
"pathPattern": "/finance/fund-update-logs/{id}",
"permissionsRequired": ["finance.fund-update-logs.item.put"]
"permissionsRequired": ["finance.fund-update-logs.item.put"],
"modulePermissions": [
"finance-storage.fund-update-logs.item.put"
]
},
{
"methods": ["DELETE"],
"pathPattern": "/finance/fund-update-logs/{id}",
"permissionsRequired": ["finance.fund-update-logs.item.delete"]
"permissionsRequired": ["finance.fund-update-logs.item.delete"],
"modulePermissions": [
"finance-storage.fund-update-logs.item.delete"
]
}
]
},
{
"id": "finance.finance-data",
"version": "1.0",
"handlers": [
{
"methods": ["GET"],
"pathPattern": "/finance/finance-data",
"permissionsRequired": ["finance.finance-data.collection.get"],
"modulePermissions": [
"finance-storage.finance-data.collection.get",
"acquisitions-units-storage.units.collection.get",
"acquisitions-units-storage.memberships.collection.get"
]
}
]
},
Expand Down Expand Up @@ -853,6 +884,10 @@
{
"id": "finance-storage.fund-update-logs",
"version":"1.0"
},
{
"id": "finance-storage.finance-data",
"version":"1.0"
}
],
"optional": [
Expand Down Expand Up @@ -1338,6 +1373,11 @@
"finance.fund-update-logs.item.delete"
]
},
{
"permissionName": "finance.finance-data.collection.get",
"displayName": "Finances - get finance data collection",
"description": "Get finance data collection"
},
{
"permissionName": "finance.all",
"displayName": "Finance module - all permissions",
Expand All @@ -1357,7 +1397,8 @@
"finance.calculate-exchange.item.get",
"finance.expense-classes.all",
"finance.acquisitions-units-assignments.all",
"finance.fund-update-logs.all"
"finance.fund-update-logs.all",
"finance.finance-data.collection.get"
],
"visible": false
},
Expand Down
37 changes: 37 additions & 0 deletions ramls/finance-data.raml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#%RAML 1.0
title: Finance - finance data
version: v1
protocols: [ HTTP, HTTPS ]
baseUri: https://github.com/folio-org/mod-finance

documentation:
- title: Finance data APIs
content: This documents the API calls that can be made to manage finance data

types:
errors: !include raml-util/schemas/errors.schema
fy-finance-data-collection: !include acq-models/mod-finance/schemas/fy_finance_data_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

/finance/finance-data:
type:
collection-get:
exampleCollection: !include acq-models/mod-finance/examples/fy_finance_data_collection.sample
schemaCollection: fy-finance-data-collection
get:
description: Get finance data
is: [
searchable: { description: "with valid searchable fields: for example fiscalYearId", example: "[\"fiscalYearId\", \"7a4c4d30-3b63-4102-8e2d-3ee5792d7d02\", \"=\"]" },
pageable
]

6 changes: 6 additions & 0 deletions src/main/java/org/folio/config/ServicesConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.folio.services.budget.RecalculateBudgetService;
import org.folio.services.budget.CreateBudgetService;
import org.folio.services.configuration.ConfigurationEntriesService;
import org.folio.services.financedata.FinanceDataService;
import org.folio.services.fiscalyear.FiscalYearApiService;
import org.folio.services.fiscalyear.FiscalYearService;
import org.folio.services.fund.FundCodeExpenseClassesService;
Expand Down Expand Up @@ -216,4 +217,9 @@ FundCodeExpenseClassesService fundCodeExpenseClassesService(BudgetService budget
FundUpdateLogService fundUpdateLogService(RestClient restClient) {
return new FundUpdateLogService(restClient);
}

@Bean
FinanceDataService financeDataService(RestClient restClient, AcqUnitsService acqUnitsService) {
return new FinanceDataService(restClient, acqUnitsService);
}
}
37 changes: 37 additions & 0 deletions src/main/java/org/folio/rest/impl/FinanceDataApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.folio.rest.impl;

import static io.vertx.core.Future.succeededFuture;

import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import java.util.Map;
import javax.ws.rs.core.Response;
import org.folio.rest.annotations.Validate;
import org.folio.rest.core.models.RequestContext;
import org.folio.rest.jaxrs.resource.FinanceFinanceData;
import org.folio.services.financedata.FinanceDataService;
import org.folio.spring.SpringContextUtil;
import org.springframework.beans.factory.annotation.Autowired;

public class FinanceDataApi extends BaseApi implements FinanceFinanceData {

@Autowired
private FinanceDataService financeDataService;

public FinanceDataApi() {
SpringContextUtil.autowireDependencies(this, Vertx.currentContext());
}

@Override
@Validate
public void getFinanceFinanceData(String query, String totalRecords, int offset, int limit,
Map<String, String> okapiHeaders, Handler<AsyncResult<Response>> asyncResultHandler,
Context vertxContext) {
financeDataService.getFinanceDataWithAcqUnitsRestriction(query, offset, limit,
new RequestContext(vertxContext, okapiHeaders))
.onSuccess(financeData -> asyncResultHandler.handle(succeededFuture(buildOkResponse(financeData))))
.onFailure(fail -> handleErrorResponse(asyncResultHandler, fail));
}
}
2 changes: 2 additions & 0 deletions src/main/java/org/folio/rest/util/ResourcePathResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ private ResourcePathResolver() {
}

public static final String BUDGETS_STORAGE = "budgets";
public static final String FINANCE_DATA_STORAGE = "financeData";
public static final String FUNDS_STORAGE = "funds";
public static final String FUND_TYPES = "fundTypes";
public static final String FUND_UPDATE_LOGS = "fundUpdateLogs";
Expand Down Expand Up @@ -38,6 +39,7 @@ private ResourcePathResolver() {
static {
Map<String, String> apis = new HashMap<>();
apis.put(BUDGETS_STORAGE, "/finance-storage/budgets");
apis.put(FINANCE_DATA_STORAGE, "/finance-storage/finance-data");
apis.put(FUNDS_STORAGE, "/finance-storage/funds");
apis.put(FUND_TYPES, "/finance-storage/fund-types");
apis.put(FUND_UPDATE_LOGS, "/finance-storage/fund-update-logs");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.folio.services.financedata;

import static org.folio.rest.util.HelperUtils.combineCqlExpressions;
import static org.folio.rest.util.ResourcePathResolver.FINANCE_DATA_STORAGE;
import static org.folio.rest.util.ResourcePathResolver.resourcesPath;

import io.vertx.core.Future;
import org.apache.commons.lang3.StringUtils;
import org.folio.rest.core.RestClient;
import org.folio.rest.core.models.RequestContext;
import org.folio.rest.core.models.RequestEntry;
import org.folio.rest.jaxrs.model.FyFinanceDataCollection;
import org.folio.services.protection.AcqUnitsService;

public class FinanceDataService {
private final RestClient restClient;
private final AcqUnitsService acqUnitsService;

public FinanceDataService(RestClient restClient, AcqUnitsService acqUnitsService) {
this.restClient = restClient;
this.acqUnitsService = acqUnitsService;
}

public Future<FyFinanceDataCollection> getFinanceDataWithAcqUnitsRestriction(String query, int offset, int limit,
RequestContext requestContext) {
return acqUnitsService.buildAcqUnitsCqlClauseForFinanceData(requestContext)
.map(clause -> StringUtils.isEmpty(query) ? clause : combineCqlExpressions("and", clause, query))
.compose(effectiveQuery -> getFinanceData(effectiveQuery, offset, limit, requestContext));
}

private Future<FyFinanceDataCollection> getFinanceData(String query, int offset, int limit, RequestContext requestContext) {
var requestEntry = new RequestEntry(resourcesPath(FINANCE_DATA_STORAGE))
.withOffset(offset)
.withLimit(limit)
.withQuery(query);
return restClient.get(requestEntry.buildEndpoint(), FyFinanceDataCollection.class, requestContext);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

public final class AcqUnitConstants {
public static final String ACQUISITIONS_UNIT_IDS = "acqUnitIds";
public static final String FD_FUND_ACQUISITIONS_UNIT_IDS = "fundAcqUnitIds"; // for finance data view table
public static final String FD_BUDGET_ACQUISITIONS_UNIT_IDS = "budgetAcqUnitIds"; // for finance data view table
public static final String NO_ACQ_UNIT_ASSIGNED_CQL = "cql.allRecords=1 not " + ACQUISITIONS_UNIT_IDS + " <> []";
public static final String NO_FD_FUND_UNIT_ASSIGNED_CQL = "cql.allRecords=1 not " + FD_FUND_ACQUISITIONS_UNIT_IDS + " <> []";
public static final String NO_FD_BUDGET_UNIT_ASSIGNED_CQL = "cql.allRecords=1 not " + FD_BUDGET_ACQUISITIONS_UNIT_IDS + " <> []";
public static final String IS_DELETED_PROP = "isDeleted";
public static final String ALL_UNITS_CQL = IS_DELETED_PROP + "=*";
public static final String ACTIVE_UNITS_CQL = IS_DELETED_PROP + "==false";
Expand Down
32 changes: 30 additions & 2 deletions src/main/java/org/folio/services/protection/AcqUnitsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
import static org.folio.rest.util.ResourcePathResolver.resourcesPath;
import static org.folio.services.protection.AcqUnitConstants.ACQUISITIONS_UNIT_IDS;
import static org.folio.services.protection.AcqUnitConstants.ACTIVE_UNITS_CQL;
import static org.folio.services.protection.AcqUnitConstants.FD_BUDGET_ACQUISITIONS_UNIT_IDS;
import static org.folio.services.protection.AcqUnitConstants.FD_FUND_ACQUISITIONS_UNIT_IDS;
import static org.folio.services.protection.AcqUnitConstants.IS_DELETED_PROP;
import static org.folio.services.protection.AcqUnitConstants.NO_ACQ_UNIT_ASSIGNED_CQL;
import static org.folio.services.protection.AcqUnitConstants.NO_FD_BUDGET_UNIT_ASSIGNED_CQL;
import static org.folio.services.protection.AcqUnitConstants.NO_FD_FUND_UNIT_ASSIGNED_CQL;

import java.util.List;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -60,7 +64,31 @@ public Future<String> buildAcqUnitsCqlClause(RequestContext requestContext) {
if (ids.isEmpty()) {
return NO_ACQ_UNIT_ASSIGNED_CQL;
}
return String.format("%s or (%s)", convertIdsToCqlQuery(ids, ACQUISITIONS_UNIT_IDS, false), NO_ACQ_UNIT_ASSIGNED_CQL);
return String.format("%s or (%s)",
convertIdsToCqlQuery(ids, ACQUISITIONS_UNIT_IDS, false),
NO_ACQ_UNIT_ASSIGNED_CQL);
});
}

public Future<String> buildAcqUnitsCqlClauseForFinanceData(RequestContext requestContext) {
return getAcqUnitIdsForSearch(requestContext)
.map(ids -> {
if (ids.isEmpty()) {
return String.format("(%s and %s)", NO_FD_FUND_UNIT_ASSIGNED_CQL, NO_FD_BUDGET_UNIT_ASSIGNED_CQL);
}
return String.format("(" +
"(%s and %s) or " + // Case 1: Both fund and budget have matching acqUnits
"(%s and %s) or " + // Case 2: Fund has matching acqUnit and budget is empty
"(%s and %s) or " + // Case 3: Fund is empty and budget has matching acqUnit
"(%s and %s))", // Case 4: Both fund and budget are empty
convertIdsToCqlQuery(ids, FD_FUND_ACQUISITIONS_UNIT_IDS, false),
convertIdsToCqlQuery(ids, FD_BUDGET_ACQUISITIONS_UNIT_IDS, false),
convertIdsToCqlQuery(ids, FD_FUND_ACQUISITIONS_UNIT_IDS, false),
NO_FD_BUDGET_UNIT_ASSIGNED_CQL,
NO_FD_FUND_UNIT_ASSIGNED_CQL,
convertIdsToCqlQuery(ids, FD_BUDGET_ACQUISITIONS_UNIT_IDS, false),
NO_FD_FUND_UNIT_ASSIGNED_CQL,
NO_FD_BUDGET_UNIT_ASSIGNED_CQL);
});
}

Expand Down Expand Up @@ -102,6 +130,6 @@ private Future<List<String>> getOpenForReadAcqUnitIds(RequestContext requestCont
log.debug("{} acq units with 'protectRead==false' are found: {}", ids.size(), StreamEx.of(ids).joining(", "));
}
return ids;
});
});
}
}
7 changes: 7 additions & 0 deletions src/test/java/org/folio/ApiTestSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.folio.rest.impl.EncumbrancesTest;
import org.folio.rest.impl.EntitiesCrudBasicsTest;
import org.folio.rest.impl.ExchangeTest;
import org.folio.rest.impl.FinanceDataApiTest;
import org.folio.rest.impl.FiscalYearTest;
import org.folio.rest.impl.FundCodeExpenseClassesApiTest;
import org.folio.rest.impl.FundsApiTest;
Expand All @@ -32,6 +33,7 @@
import org.folio.services.budget.BudgetServiceTest;
import org.folio.services.budget.CreateBudgetServiceTest;
import org.folio.services.budget.RecalculateBudgetServiceTest;
import org.folio.services.financedata.FinanceDataServiceTest;
import org.folio.services.fiscalyear.FiscalYearApiServiceTest;
import org.folio.services.fiscalyear.FiscalYearServiceTest;
import org.folio.services.fund.FundCodeExpenseClassesServiceTest;
Expand Down Expand Up @@ -244,4 +246,9 @@ class GroupServiceNested extends GroupServiceTest {}
@Nested
class RecalculateBudgetServiceTestNested extends RecalculateBudgetServiceTest {}

@Nested
class FinanceDataApiTestNested extends FinanceDataApiTest {}

@Nested
class FinanceDataServiceTestNested extends FinanceDataServiceTest {}
}
5 changes: 2 additions & 3 deletions src/test/java/org/folio/rest/impl/EntitiesCrudBasicsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsEqual.equalTo;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -265,7 +264,7 @@ void testGetRecordByIdNotFound(TestEntities testEntity) {

@ParameterizedTest
@MethodSource("getTestEntitiesWithPostEndpoint")
void testPostRecord(TestEntities testEntity) throws IOException {
void testPostRecord(TestEntities testEntity) {
logger.info("=== Test create {} record ===", testEntity.name());

JsonObject record = testEntity.getMockObject();
Expand Down Expand Up @@ -293,7 +292,7 @@ record = JsonObject.mapFrom(t);

@ParameterizedTest
@MethodSource("getTestEntitiesWithPostEndpoint")
void testPostRecordServerError(TestEntities testEntity) throws IOException {
void testPostRecordServerError(TestEntities testEntity) {
logger.info("=== Test create {} record - Internal Server Error ===", testEntity.name());

Headers headers = RestTestUtils.prepareHeaders(TestConfig.X_OKAPI_URL, ERROR_X_OKAPI_TENANT);
Expand Down
Loading

0 comments on commit f981adc

Please sign in to comment.