Skip to content

Commit be2e414

Browse files
authored
MODORDERS-1254. BE - Allow user to view PO value for any related fiscal year (#1170)
* MODORDERS-1254. BE - Allow user to view PO value for any related fiscal year * MODORDERS-1254. Code review improvements * MODORDERS-1254. Fix sonar complains * MODORDERS-1254. Fix sonar complains
1 parent 493e169 commit be2e414

16 files changed

Lines changed: 546 additions & 55 deletions

descriptors/ModuleDescriptor-template.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,21 @@
135135
"finance-storage.ledgers.collection.get",
136136
"inventory-storage.holdings.collection.get"
137137
]
138+
},
139+
{
140+
"methods": [
141+
"GET"
142+
],
143+
"pathPattern": "/orders/composite-orders/{id}/fiscal-years",
144+
"permissionsRequired": [
145+
"orders.fiscal-years.collection.get"
146+
],
147+
"modulePermissions": [
148+
"finance.transactions.collection.get",
149+
"finance.fiscal-years.collection.get",
150+
"finance.fiscal-years.item.get",
151+
"mod-settings.global.read.stripes-core.prefs.manage"
152+
]
138153
}
139154
]
140155
},
@@ -1561,6 +1576,11 @@
15611576
"displayName": "orders - re-encumber an order",
15621577
"description": "Re-encumber an order"
15631578
},
1579+
{
1580+
"permissionName": "orders.fiscal-years.collection.get",
1581+
"displayName": "orders - get available fiscal years",
1582+
"description": "Get available fiscal years for an order"
1583+
},
15641584
{
15651585
"permissionName": "orders.po-lines.collection.get",
15661586
"displayName": "Orders - get collection of PO lines",
@@ -2170,6 +2190,7 @@
21702190
"orders.configuration.prefixes.all",
21712191
"orders.configuration.suffixes.all",
21722192
"orders.re-encumber.item.post",
2193+
"orders.fiscal-years.collection.get",
21732194
"orders.rollover.item.post",
21742195
"orders.holding-summary.collection.get",
21752196
"orders.acquisition-methods.all",

ramls/order.raml

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ types:
1212
composite-purchase-order: !include acq-models/mod-orders/schemas/composite_purchase_order.json
1313
purchase-order-collection: !include acq-models/mod-orders-storage/schemas/purchase_order_collection.json
1414
purchase_order: !include acq-models/mod-orders-storage/schemas/purchase_order.json
15+
fiscal-year-collection: !include acq-models/mod-finance/schemas/fiscal_year_collection.json
1516
errors: !include raml-util/schemas/errors.schema
1617

1718
UUID:
@@ -54,6 +55,12 @@ resourceTypes:
5455
is: [validate]
5556
get:
5657
description: Return a purchase order with given {id}
58+
queryParameters:
59+
fiscalYearId:
60+
displayName: Fiscal Year ID
61+
type: UUID
62+
required: false
63+
description: Filter order by fiscal year ID
5764
put:
5865
description: |
5966
Update a purchase order with given {id}
@@ -102,4 +109,42 @@ resourceTypes:
102109
value: !include raml-util/examples/errors.sample
103110
text/plain:
104111
example: "Internal server error, contact administrator"
105-
112+
/fiscal-years:
113+
get:
114+
description: Get available fiscal years for a purchase order
115+
responses:
116+
200:
117+
description: "Fiscal years retrieved successfully"
118+
body:
119+
application/json:
120+
type: fiscal-year-collection
121+
example:
122+
strict: false
123+
value: !include acq-models/mod-finance/examples/fiscal_year_collection.sample
124+
400:
125+
description: "Bad request"
126+
body:
127+
application/json:
128+
example:
129+
strict: false
130+
value: !include raml-util/examples/errors.sample
131+
text/plain:
132+
example: "Bad request"
133+
404:
134+
description: "Order not found"
135+
body:
136+
application/json:
137+
example:
138+
strict: false
139+
value: !include raml-util/examples/errors.sample
140+
text/plain:
141+
example: "Order not found"
142+
500:
143+
description: "Internal server error"
144+
body:
145+
application/json:
146+
example:
147+
strict: false
148+
value: !include raml-util/examples/errors.sample
149+
text/plain:
150+
example: "Internal server error, contact administrator"

src/main/java/org/folio/config/ApplicationConfig.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import org.folio.service.orders.PurchaseOrderStorageService;
8282
import org.folio.service.orders.ReEncumbranceHoldersBuilder;
8383
import org.folio.service.orders.CompositeOrderTotalFieldsPopulateService;
84+
import org.folio.service.orders.OrderFiscalYearService;
8485
import org.folio.service.orders.flows.update.open.OpenCompositeOrderFlowValidator;
8586
import org.folio.service.orders.flows.update.open.OpenCompositeOrderHolderBuilder;
8687
import org.folio.service.orders.flows.update.open.OpenCompositeOrderInventoryService;
@@ -877,4 +878,9 @@ CirculationRequestsRetriever circulationRequestsRetriever(PieceStorageService pi
877878
SettingsRetriever settingsRetriever(RestClient restClient) {
878879
return new SettingsRetriever(restClient);
879880
}
881+
882+
@Bean
883+
OrderFiscalYearService orderFiscalYearService(TransactionService transactionService, FiscalYearService fiscalYearService) {
884+
return new OrderFiscalYearService(transactionService, fiscalYearService);
885+
}
880886
}

src/main/java/org/folio/helper/PurchaseOrderHelper.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,10 +388,11 @@ public Future<Void> deleteOrder(String orderId, RequestContext requestContext) {
388388
/**
389389
* Gets purchase order by id
390390
*
391-
* @param orderId purchase order uuid
391+
* @param orderId purchase order uuid
392+
* @param fiscalYearId fiscal year id for fetching totals
392393
* @return completable future with {@link CompositePurchaseOrder} on success or an exception if processing fails
393394
*/
394-
public Future<CompositePurchaseOrder> getCompositeOrder(String orderId, RequestContext requestContext) {
395+
public Future<CompositePurchaseOrder> getCompositeOrder(String orderId, String fiscalYearId, RequestContext requestContext) {
395396
Promise<CompositePurchaseOrder> promise = Promise.promise();
396397
purchaseOrderStorageService.getPurchaseOrderByIdAsJson(orderId, requestContext)
397398
.map(HelperUtils::convertToCompositePurchaseOrder)
@@ -407,7 +408,8 @@ public Future<CompositePurchaseOrder> getCompositeOrder(String orderId, RequestC
407408
populateInstanceId(linesIdTitles, compPO.getPoLines());
408409
return null;
409410
})
410-
.compose(po -> combinedPopulateService.populate(new CompositeOrderRetrieveHolder(compPO), requestContext)))
411+
.compose(po -> combinedPopulateService.populate(new CompositeOrderRetrieveHolder(compPO)
412+
.withFiscalYearId(fiscalYearId), requestContext)))
411413
.map(CompositeOrderRetrieveHolder::getOrder))
412414
.onSuccess(promise::complete)
413415
.onFailure(t -> {

src/main/java/org/folio/models/CompositeOrderRetrieveHolder.java

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,32 @@
11
package org.folio.models;
22

3-
import java.util.Optional;
4-
5-
import org.folio.rest.acq.model.finance.FiscalYear;
3+
import lombok.Data;
64
import org.folio.rest.jaxrs.model.CompositePurchaseOrder;
75

6+
@Data
87
public class CompositeOrderRetrieveHolder {
98
private CompositePurchaseOrder order;
10-
private FiscalYear fiscalYear;
9+
private String fiscalYearId;
10+
private String fiscalYearCurrency;
1111

1212
public CompositeOrderRetrieveHolder(CompositePurchaseOrder order) {
1313
this.order = order;
1414
}
1515

16-
public FiscalYear getFiscalYear() {
17-
return fiscalYear;
18-
}
19-
20-
public CompositeOrderRetrieveHolder withFiscalYear(FiscalYear fiscalYear) {
21-
this.fiscalYear = fiscalYear;
16+
public CompositeOrderRetrieveHolder withFiscalYearId(String fiscalYearId) {
17+
this.fiscalYearId = fiscalYearId;
2218
return this;
2319
}
2420

25-
public CompositePurchaseOrder getOrder() {
26-
return order;
21+
public CompositeOrderRetrieveHolder withFiscalYearCurrency(String currency) {
22+
this.fiscalYearCurrency = currency;
23+
return this;
2724
}
2825

2926
public String getOrderId() {
3027
return order.getId();
3128
}
3229

33-
public String getFiscalYearId() {
34-
return Optional.ofNullable(fiscalYear).map(FiscalYear::getId).orElse(null);
35-
}
36-
3730
public CompositeOrderRetrieveHolder withTotalEncumbered(double transactionsTotal) {
3831
order.setTotalEncumbered(transactionsTotal);
3932
return this;

src/main/java/org/folio/rest/impl/OrdersApi.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.folio.rest.jaxrs.model.LedgerFiscalYearRollover;
1717
import org.folio.rest.jaxrs.resource.OrdersCompositeOrders;
1818
import org.folio.rest.jaxrs.resource.OrdersRollover;
19+
import org.folio.service.orders.OrderFiscalYearService;
1920
import org.folio.service.orders.OrderReEncumberService;
2021
import org.folio.service.orders.OrderRolloverService;
2122
import org.folio.spring.SpringContextUtil;
@@ -34,6 +35,8 @@ public class OrdersApi extends BaseApi implements OrdersCompositeOrders, OrdersR
3435
private OrderReEncumberService orderReEncumberService;
3536
@Autowired
3637
private PurchaseOrderHelper purchaseOrderHelper;
38+
@Autowired
39+
private OrderFiscalYearService orderFiscalYearService;
3740

3841
public OrdersApi(Vertx vertx, String tenantId) {
3942
SpringContextUtil.autowireDependencies(this, Vertx.currentContext());
@@ -51,10 +54,10 @@ public void deleteOrdersCompositeOrdersById(String id, Map<String, String> okapi
5154

5255
@Override
5356
@Validate
54-
public void getOrdersCompositeOrdersById(String id, Map<String, String> okapiHeaders,
57+
public void getOrdersCompositeOrdersById(String id, String fiscalYearId, Map<String, String> okapiHeaders,
5558
Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
5659

57-
purchaseOrderHelper.getCompositeOrder(id, new RequestContext(vertxContext, okapiHeaders))
60+
purchaseOrderHelper.getCompositeOrder(id, fiscalYearId, new RequestContext(vertxContext, okapiHeaders))
5861
.onSuccess(order -> asyncResultHandler.handle(succeededFuture(buildOkResponse(order))))
5962
.onFailure(t -> handleErrorResponse(asyncResultHandler, t));
6063
}
@@ -109,4 +112,12 @@ public void postOrdersRollover(LedgerFiscalYearRollover ledgerFYRollover, Map<St
109112
.onSuccess(v -> asyncResultHandler.handle(succeededFuture(buildNoContentResponse())))
110113
.onFailure(fail -> handleErrorResponse(asyncResultHandler, fail));
111114
}
115+
116+
@Override
117+
@Validate
118+
public void getOrdersCompositeOrdersFiscalYearsById(String id, Map<String, String> okapiHeaders, Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
119+
orderFiscalYearService.getAvailableFiscalYears(id, new RequestContext(vertxContext, okapiHeaders))
120+
.onSuccess(fiscalYears -> asyncResultHandler.handle(succeededFuture(buildOkResponse(fiscalYears))))
121+
.onFailure(t -> handleErrorResponse(asyncResultHandler, t));
122+
}
112123
}

src/main/java/org/folio/service/finance/FiscalYearService.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
package org.folio.service.finance;
22

3+
import static one.util.streamex.StreamEx.ofSubLists;
34
import static org.folio.orders.utils.CacheUtils.buildAsyncCache;
5+
import static org.folio.orders.utils.HelperUtils.collectResultsOnSuccess;
6+
import static org.folio.orders.utils.QueryUtils.convertIdsToCqlQuery;
47
import static org.folio.orders.utils.ResourcePathResolver.FISCAL_YEARS;
58
import static org.folio.orders.utils.ResourcePathResolver.LEDGER_CURRENT_FISCAL_YEAR;
69
import static org.folio.orders.utils.ResourcePathResolver.resourceByIdPath;
710
import static org.folio.orders.utils.ResourcePathResolver.resourcesPath;
11+
import static org.folio.rest.RestConstants.MAX_IDS_FOR_GET_RQ_15;
812
import static org.folio.rest.core.exceptions.ErrorCodes.CURRENT_FISCAL_YEAR_NOT_FOUND;
913

1014
import java.time.Instant;
1115
import java.time.ZoneId;
16+
import java.util.ArrayList;
17+
import java.util.Collection;
1218
import java.util.Collections;
1319
import java.util.Date;
20+
import java.util.LinkedHashSet;
1421
import java.util.List;
1522
import java.util.Objects;
23+
import java.util.Set;
1624
import java.util.concurrent.CompletionException;
25+
import java.util.stream.Collectors;
1726

1827
import com.github.benmanes.caffeine.cache.AsyncCache;
1928
import io.vertx.core.Vertx;
@@ -103,12 +112,32 @@ private Future<String> getSeriesByFiscalYearId(String fiscalYearId, RequestConte
103112
.toCompletionStage().toCompletableFuture()));
104113
}
105114

106-
private Future<FiscalYear> getFiscalYearById(String fiscalYearId, RequestContext requestContext) {
115+
public Future<FiscalYear> getFiscalYearById(String fiscalYearId, RequestContext requestContext) {
107116
var requestEntry = new RequestEntry(FISCAL_YEAR_BY_ID_ENDPOINT).withId(fiscalYearId);
108117
return restClient.get(requestEntry, FiscalYear.class, requestContext)
109118
.onFailure(t -> log.error("Unable to fetch fiscal year by id: {}", fiscalYearId, t));
110119
}
111120

121+
public Future<List<FiscalYear>> getAllFiscalYears(Collection<String> fiscalYearIds, RequestContext requestContext) {
122+
Set<String> uniqueFiscalYearIds = new LinkedHashSet<>(fiscalYearIds);
123+
return collectResultsOnSuccess(
124+
ofSubLists(new ArrayList<>(uniqueFiscalYearIds), MAX_IDS_FOR_GET_RQ_15)
125+
.map(ids-> getAllFiscalYearsByIds(ids, requestContext))
126+
.toList())
127+
.map(lists -> lists.stream()
128+
.flatMap(Collection::stream)
129+
.toList());
130+
}
131+
132+
private Future<List<FiscalYear>> getAllFiscalYearsByIds(Collection<String> ids, RequestContext requestContext) {
133+
String query = convertIdsToCqlQuery(ids);
134+
RequestEntry requestEntry = new RequestEntry(FISCAL_YEARS_ENDPOINT).withQuery(query)
135+
.withLimit(MAX_IDS_FOR_GET_RQ_15)
136+
.withOffset(0);
137+
return restClient.get(requestEntry, FiscalYearCollection.class, requestContext)
138+
.map(FiscalYearCollection::getFiscalYears);
139+
}
140+
112141
private Future<String> getCurrentFiscalYearForSeries(String series, RequestContext requestContext) {
113142
var cacheKey = CACHE_KEY_TEMPLATE.formatted(series, TenantTool.tenantId(requestContext.getHeaders()));
114143
return Future.fromCompletionStage(currentFiscalYearCacheBySeries.get(cacheKey, (key, executor) ->
Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package org.folio.service.orders;
22

33
import java.util.Objects;
4-
import java.util.concurrent.CompletionException;
54

5+
import org.apache.commons.collections4.CollectionUtils;
6+
import org.apache.commons.lang3.StringUtils;
67
import org.folio.models.CompositeOrderRetrieveHolder;
8+
import org.folio.rest.acq.model.finance.FiscalYear;
79
import org.folio.rest.core.exceptions.HttpException;
810
import org.folio.rest.core.models.RequestContext;
911
import org.folio.rest.jaxrs.model.FundDistribution;
@@ -19,24 +21,45 @@ public CompositeOrderRetrieveHolderBuilder(FiscalYearService fiscalYearService)
1921
}
2022

2123
public Future<CompositeOrderRetrieveHolder> withCurrentFiscalYear(CompositeOrderRetrieveHolder holder,
22-
RequestContext requestContext) {
24+
RequestContext requestContext) {
25+
return getFiscalYear(holder, requestContext)
26+
.map(fiscalYear -> {
27+
if (fiscalYear != null) {
28+
holder.withFiscalYearId(fiscalYear.getId());
29+
holder.withFiscalYearCurrency(fiscalYear.getCurrency());
30+
}
31+
return holder;
32+
})
33+
.otherwise(t -> handleFiscalYearError(holder, t));
34+
}
35+
36+
private Future<FiscalYear> getFiscalYear(CompositeOrderRetrieveHolder holder, RequestContext requestContext) {
37+
if (StringUtils.isNotBlank(holder.getFiscalYearId())) {
38+
return fiscalYearService.getFiscalYearById(holder.getFiscalYearId(), requestContext);
39+
}
40+
41+
if (holder.getOrder() == null || CollectionUtils.isEmpty(holder.getOrder().getPoLines())) {
42+
return Future.succeededFuture(null);
43+
}
44+
2345
return holder.getOrder()
2446
.getPoLines()
2547
.stream()
26-
.flatMap(poLine -> poLine.getFundDistribution()
27-
.stream())
48+
.flatMap(poLine -> poLine.getFundDistribution().stream())
49+
.filter(Objects::nonNull)
2850
.map(FundDistribution::getFundId)
51+
.filter(StringUtils::isNotBlank)
2952
.findFirst()
30-
.map(fundId -> fiscalYearService.getCurrentFiscalYearByFundId(fundId, requestContext)
31-
.map(holder::withFiscalYear)
32-
.otherwise(t -> {
33-
Throwable cause = Objects.nonNull(t.getCause()) ? t.getCause() : t;
34-
if (cause instanceof HttpException && ((HttpException) cause).getCode() == 404) {
35-
return holder;
36-
}
37-
throw new CompletionException(cause);
38-
}))
39-
.orElseGet(() -> Future.succeededFuture(holder));
53+
.map(fundId -> fiscalYearService.getCurrentFiscalYearByFundId(fundId, requestContext))
54+
.orElseGet(() -> Future.succeededFuture(null));
55+
}
56+
57+
private CompositeOrderRetrieveHolder handleFiscalYearError(CompositeOrderRetrieveHolder holder, Throwable t) {
58+
Throwable cause = Objects.nonNull(t.getCause()) ? t.getCause() : t;
59+
if (cause instanceof HttpException && ((HttpException) cause).getCode() == 404) {
60+
return holder;
61+
}
62+
throw new HttpException(500, cause.getMessage(), cause);
4063
}
4164

4265
}

0 commit comments

Comments
 (0)