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-379] - Create API for retrieve finance data #262

Merged
merged 11 commits into from
Nov 20, 2024
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
Loading