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

[MODFISTO - 501] - Implement endpoint to save FY finance data in bulk #439

Merged
merged 13 commits into from
Nov 29, 2024
Merged
25 changes: 22 additions & 3 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,11 @@
"methods": ["GET"],
"pathPattern": "/finance-storage/finance-data",
"permissionsRequired": ["finance-storage.finance-data.collection.get"]
},
{
"methods": ["PUT"],
"pathPattern": "/finance-storage/finance-data",
"permissionsRequired": ["finance-storage.finance-data.collection.put"]
}
]
},
Expand Down Expand Up @@ -1018,8 +1023,22 @@
},
{
"permissionName": "finance-storage.finance-data.collection.get",
"displayName": "all finance-data for fiscal year",
"description": "Get collection of finance data for particular fiscal year"
"displayName": "all finance-data",
"description": "Get collection of finance data"
},
{
"permissionName": "finance-storage.finance-data.collection.put",
"displayName": "Update finance-data as a bulk",
"description": "Update collection of finance data"
},
{
"permissionName": "finance-storage.finance-data.all",
"displayName": "All finance-data perms",
"description": "All permissions for the finance data",
"subPermissions": [
"finance-storage.finance-data.collection.get",
"finance-storage.finance-data.collection.put"
]
},
{
"permissionName" : "finance.module.all",
Expand All @@ -1036,7 +1055,7 @@
"finance-storage.transactions.all",
"finance-storage.fund-types.all",
"finance-storage.fund-update-logs.all",
"finance-storage.finance-data.collection.get"
"finance-storage.finance-data.all"
]
}
],
Expand Down
34 changes: 33 additions & 1 deletion ramls/finance-data.raml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ types:
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
pageable: !include raml-util/traits/pageable.raml
searchable: !include raml-util/traits/searchable.raml
validate: !include raml-util/traits/validation.raml

Expand All @@ -33,3 +33,35 @@ resourceTypes:
searchable: { description: "with valid searchable fields: for example fiscalYearId", example: "[\"fiscalYearId\", \"7a4c4d30-3b63-4102-8e2d-3ee5792d7d02\", \"=\"]" },
pageable
]
put:
description: Update finance, budget as a bulk
is: [ validate ]
body:
application/json:
type: fy-finance-data-collection
example: !include acq-models/mod-finance/examples/fy_finance_data_collection.sample
responses:
204:
description: "Items successfully updated"
404:
description: "One or more items not found"
body:
text/plain:
example: |
"One or more items not found"
400:
description: "Bad request, e.g. malformed request body or query parameter. Details of the error (e.g. name of the parameter or line/character number with malformed data) provided in the response."
body:
text/plain:
example: |
"unable to update items -- malformed JSON at 13:4"
409:
description: "Optimistic locking version conflict"
body:
text/plain:
example: "version conflict"
500:
description: "Internal server error, e.g. due to misconfiguration"
body:
text/plain:
example: "internal server error, contact administrator"
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 @@ -21,6 +21,7 @@
import org.folio.service.budget.BudgetService;
import org.folio.service.budget.RolloverBudgetExpenseClassTotalsService;
import org.folio.service.email.EmailService;
import org.folio.service.financedata.FinanceDataService;
import org.folio.service.fiscalyear.FiscalYearService;
import org.folio.service.fund.FundService;
import org.folio.service.fund.StorageFundService;
Expand Down Expand Up @@ -164,4 +165,9 @@ RolloverBudgetExpenseClassTotalsService rolloverBudgetExpenseClassTotalsService(
TemporaryEncumbranceService temporaryEncumbranceService) {
return new RolloverBudgetExpenseClassTotalsService(budgetExpenseClassService, temporaryEncumbranceService);
}

@Bean
public FinanceDataService financeDataService(FundService fundService, BudgetService budgetService) {
return new FinanceDataService(fundService, budgetService);
}
}
2 changes: 1 addition & 1 deletion src/main/java/org/folio/dao/budget/BudgetDAO.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public interface BudgetDAO {

Future<List<Budget>> getBudgetsBySql(String sql, Tuple params, DBConn conn);

Future<List<Budget>> getBudgets(Criterion criterion, DBConn conn);
Future<List<Budget>> getBudgetsByCriterion(Criterion criterion, DBConn conn);

Future<Budget> getBudgetById(String id, DBConn conn);

Expand Down
6 changes: 3 additions & 3 deletions src/main/java/org/folio/dao/budget/BudgetPostgresDAO.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public Future<Void> updateBatchBudgets(List<Budget> budgets, DBConn conn) {
List<String> ids = budgets.stream().map(Budget::getId).toList();
logger.debug("Trying update batch budgets, ids={}", ids);
return conn.updateBatch(BUDGET_TABLE, budgets)
.onSuccess(rowSet -> logger.info("Updated {} batch budgets", budgets.size()))
.onSuccess(rowSet -> logger.info("updateBatchBudgets:: Updated {} batch budgets", budgets.size()))
.onFailure(e -> logger.error("Update batch budgets by failed, ids={}", ids, e))
.mapEmpty();
}
Expand All @@ -42,7 +42,7 @@ public Future<Integer> updateBatchBudgetsBySql(String sql, DBConn conn) {
logger.debug("Trying update batch budgets by query: {}", sql);
return conn.execute(sql)
.map(SqlResult::rowCount)
.onSuccess(rowCount -> logger.info("Updated {} batch budgets", rowCount))
.onSuccess(rowCount -> logger.info("updateBatchBudgetsBySql:: Updated {} batch budgets", rowCount))
.onFailure(e -> logger.error("Update batch budgets by query: {} failed", sql, e));
}

Expand All @@ -67,7 +67,7 @@ public Future<List<Budget>> getBudgetsBySql(String sql, Tuple params, DBConn con
* @param conn : db connection
*/
@Override
public Future<List<Budget>> getBudgets(Criterion criterion, DBConn conn) {
public Future<List<Budget>> getBudgetsByCriterion(Criterion criterion, DBConn conn) {
logger.debug("Trying to get budgets by query: {}", criterion);
return conn.get(BUDGET_TABLE, Budget.class, criterion, false)
.map(results -> {
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/folio/dao/fund/FundDAO.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@
public interface FundDAO {
Future<Fund> getFundById(String id, DBConn conn);
Future<List<Fund>> getFundsByIds(List<String> ids, DBConn conn);
Future<Boolean> isFundStatusChanged(Fund fund, DBConn conn);
Future<Void> updateRelatedCurrentFYBudgets(Fund fund, DBConn conn);
Future<Void> updateFund(Fund fund, DBConn conn);
Future<Void> updateFunds(List<Fund> funds, DBConn conn);
}
44 changes: 44 additions & 0 deletions src/main/java/org/folio/dao/fund/FundPostgresDAO.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package org.folio.dao.fund;

import static org.folio.rest.impl.BudgetAPI.BUDGET_TABLE;
import static org.folio.rest.impl.FiscalYearAPI.FISCAL_YEAR_TABLE;
import static org.folio.rest.impl.FundAPI.FUND_TABLE;
import static org.folio.rest.persist.HelperUtils.getFullTableName;

import io.vertx.sqlclient.Tuple;
import java.util.Collections;
import java.util.List;

import java.util.UUID;
import org.folio.rest.exception.HttpException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand All @@ -21,6 +26,12 @@
public class FundPostgresDAO implements FundDAO {
private static final Logger logger = LogManager.getLogger();
private static final String FUND_NOT_FOUND = "Fund not found, id=%s";
private static final String QUERY_UPDATE_CURRENT_FY_BUDGET =
"UPDATE %s SET jsonb = jsonb_set(jsonb,'{budgetStatus}', $1) " +
"WHERE((fundId=$2) " +
"AND (budget.fiscalYearId IN " +
"(SELECT id FROM %s WHERE current_date between (jsonb->>'periodStart')::timestamp " +
"AND (jsonb->>'periodEnd')::timestamp)));";

@Override
public Future<Fund> getFundById(String id, DBConn conn) {
Expand Down Expand Up @@ -49,6 +60,39 @@ public Future<List<Fund>> getFundsByIds(List<String> ids, DBConn conn) {
return getFundsByCriterion(criterionBuilder.build(), conn);
}

@Override
public Future<Boolean> isFundStatusChanged(Fund fund, DBConn conn) {
return getFundById(fund.getId(), conn)
.map(existingFund -> existingFund.getFundStatus() != fund.getFundStatus());
}

@Override
public Future<Void> updateRelatedCurrentFYBudgets(Fund fund, DBConn conn) {
String fullBudgetTableName = getFullTableName(conn.getTenantId(), BUDGET_TABLE);
String fullFYTableName = getFullTableName(conn.getTenantId(), FISCAL_YEAR_TABLE);
String updateQuery = String.format(QUERY_UPDATE_CURRENT_FY_BUDGET, fullBudgetTableName, fullFYTableName);

return conn.execute(updateQuery, Tuple.of(fund.getFundStatus().value(), UUID.fromString(fund.getId())))
.mapEmpty();
}

@Override
public Future<Void> updateFund(Fund fund, DBConn conn) {
logger.debug("Trying to update finance storage fund by id {}", fund.getId());
return conn.update(FUND_TABLE, fund, fund.getId())
.onSuccess(x -> logger.info("Fund record '{}' was successfully updated", fund.getId()))
.mapEmpty();
}

@Override
public Future<Void> updateFunds(List<Fund> funds, DBConn conn) {
List<String> fundIds = funds.stream().map(Fund::getId).toList();
logger.debug("Trying to update finance storage funds: '{}'", fundIds);
return conn.updateBatch(FUND_TABLE, funds)
.onSuccess(x -> logger.info("Funds '{}' was successfully updated", fundIds))
.mapEmpty();
}

private Future<List<Fund>> getFundsByCriterion(Criterion criterion, DBConn conn) {
logger.debug("Trying to get funds by criterion = {}", criterion);
return conn.get(FUND_TABLE, Fund.class, criterion, false)
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/org/folio/rest/impl/FinanceDataApi.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,49 @@
package org.folio.rest.impl;


import static io.vertx.core.Future.succeededFuture;
import static org.folio.rest.jaxrs.resource.FinanceStorageFinanceData.PutFinanceStorageFinanceDataResponse.respond204;

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

import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import org.folio.rest.core.model.RequestContext;
import org.folio.rest.jaxrs.model.FyFinanceData;
import org.folio.rest.jaxrs.model.FyFinanceDataCollection;
import org.folio.rest.jaxrs.resource.FinanceStorageFinanceData;
import org.folio.rest.persist.PgUtil;
import org.folio.rest.util.ResponseUtils;
import org.folio.service.financedata.FinanceDataService;
import org.folio.spring.SpringContextUtil;
import org.springframework.beans.factory.annotation.Autowired;

public class FinanceDataApi implements FinanceStorageFinanceData {

private static final String FINANCE_DATA_VIEW = "finance_data_view";

@Autowired
private FinanceDataService financeDataService;

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

@Override
public void getFinanceStorageFinanceData(String query, String totalRecords, int offset, int limit, Map<String, String> okapiHeaders,
Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
PgUtil.get(FINANCE_DATA_VIEW, FyFinanceData.class, FyFinanceDataCollection.class, query, offset, limit,
okapiHeaders, vertxContext, GetFinanceStorageFinanceDataResponse.class, asyncResultHandler);
}

@Override
public void putFinanceStorageFinanceData(FyFinanceDataCollection entity, Map<String, String> okapiHeaders,
Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
financeDataService.update(entity, new RequestContext(vertxContext, okapiHeaders))
.onSuccess(v -> asyncResultHandler.handle(succeededFuture(respond204())))
.onFailure(ResponseUtils::handleFailure);
}
}
Loading