Skip to content

Commit

Permalink
[MODFISTO - 501] - Implement endpoint to save FY finance data in bulk (
Browse files Browse the repository at this point in the history
…#439)

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

* Implement endpoint to save FY finance data in bulk

* Improved update as a bulk, and added unit tests

* Added FinanceDataServiceTest and improvements

* minor update

* added null check and fixed sonar issue

* Remove set of allocated field

* Fix test

* Fix log

* Added ledgerId, ledgerCode, and groupId, groupCode

* improved response handling and removed initial allocation field set

* Fixed finance_data_view and improved method names

* Fixed error response handling
  • Loading branch information
azizbekxm authored Nov 29, 2024
1 parent 133b918 commit 11f3013
Show file tree
Hide file tree
Showing 19 changed files with 576 additions and 103 deletions.
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

0 comments on commit 11f3013

Please sign in to comment.