Skip to content

Commit adedd28

Browse files
[MODORDERS-1209]. Merge master
2 parents dd14556 + a96c130 commit adedd28

File tree

20 files changed

+569
-316
lines changed

20 files changed

+569
-316
lines changed

descriptors/ModuleDescriptor-template.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,31 @@
627627
"invoice.invoice-lines.item.put"
628628
]
629629
},
630+
{
631+
"methods": ["PUT"],
632+
"pathPattern": "/orders/pieces-batch/status",
633+
"permissionsRequired": ["orders.pieces.collection.put"],
634+
"modulePermissions": [
635+
"orders-storage.pieces.item.put",
636+
"orders-storage.pieces.collection.get",
637+
"orders-storage.po-lines.item.get",
638+
"orders-storage.po-lines.item.put",
639+
"orders-storage.po-lines.collection.get",
640+
"orders-storage.purchase-orders.item.get",
641+
"orders-storage.purchase-orders.item.put",
642+
"orders-storage.titles.collection.get",
643+
"finance.funds.item.get",
644+
"finance.ledgers.current-fiscal-year.item.get",
645+
"finance.transactions.collection.get",
646+
"finance.transactions.batch.execute",
647+
"inventory.items.item.get",
648+
"inventory.items.item.put",
649+
"inventory.items.collection.get",
650+
"inventory-storage.holdings.collection.get",
651+
"acquisitions-units-storage.units.collection.get",
652+
"acquisitions-units-storage.memberships.collection.get"
653+
]
654+
},
630655
{
631656
"methods": ["GET"],
632657
"pathPattern": "/orders/pieces-requests",
@@ -1540,6 +1565,11 @@
15401565
"displayName": "orders - delete an existing piece record",
15411566
"description": "Delete an existing piece"
15421567
},
1568+
{
1569+
"permissionName": "orders.pieces.collection.put",
1570+
"displayName": "orders - batch update piece statuses",
1571+
"description": "Batch update piece statuses"
1572+
},
15431573
{
15441574
"permissionName": "orders.piece-requests.collection.get",
15451575
"displayName": "Orders - Get piece requests",
@@ -1551,6 +1581,7 @@
15511581
"description" : "All permissions for the pieces",
15521582
"subPermissions" : [
15531583
"orders.pieces.collection.get",
1584+
"orders.pieces.collection.put",
15541585
"orders.pieces.item.post",
15551586
"orders.pieces.item.get",
15561587
"orders.pieces.item.put",

ramls/pieces-batch.raml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#%RAML 1.0
2+
title: Pieces Batch Operations
3+
baseUri: https://github.com/folio-org/mod-orders
4+
version: v1
5+
protocols: [ HTTP, HTTPS ]
6+
7+
documentation:
8+
- title: Endpoint for batch operations on Pieces
9+
content: <b>Endpoint for batch operations on Pieces</b>
10+
11+
types:
12+
piece-batch-status-collection: !include acq-models/mod-orders/schemas/pieceStatusBatchCollection.json
13+
14+
/orders/pieces-batch:
15+
/status:
16+
put:
17+
description: Batch status update for pieces
18+
body:
19+
application/json:
20+
type: piece-batch-status-collection
21+
example:
22+
strict: false
23+
value: !include acq-models/mod-orders/examples/pieceStatusBatchCollection.sample
24+
responses:
25+
204:
26+
description: "Piece records successfully updated"
27+
400:
28+
description: "Bad request"
29+
body:
30+
application/json:
31+
example:
32+
strict: false
33+
value: !include examples/errors_400.sample
34+
text/plain:
35+
example: "Unable to update pieces - Bad request"
36+
404:
37+
description: "Pieces do not exist"
38+
body:
39+
text/plain:
40+
example: "Following pieces are not found: [id1, id2]"
41+
500:
42+
description: "Internal server error, e.g. due to misconfiguration"
43+
body:
44+
application/json:
45+
example:
46+
strict: false
47+
value: !include examples/errors_500.sample
48+
text/plain:
49+
example: "Unable to update pieces - Internal server error, e.g. due to misconfiguration"

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -641,13 +641,12 @@ PieceUpdateFlowPoLineService pieceUpdateFlowPoLineService(PurchaseOrderStorageSe
641641
}
642642

643643
@Bean
644-
PieceUpdateFlowManager pieceUpdateFlowManager(PieceStorageService pieceStorageService, PieceService pieceService, ProtectionService protectionService,
645-
PieceUpdateFlowPoLineService pieceUpdateFlowPoLineService, PieceUpdateFlowInventoryManager pieceUpdateFlowInventoryManager,
646-
BasePieceFlowHolderBuilder basePieceFlowHolderBuilder, DefaultPieceFlowsValidator defaultPieceFlowsValidator,
647-
PurchaseOrderLineService purchaseOrderLineService) {
648-
return new PieceUpdateFlowManager(pieceStorageService, pieceService, protectionService, pieceUpdateFlowPoLineService,
649-
pieceUpdateFlowInventoryManager, basePieceFlowHolderBuilder, defaultPieceFlowsValidator,
650-
purchaseOrderLineService);
644+
PieceUpdateFlowManager pieceUpdateFlowManager(PieceStorageService pieceStorageService, PieceService pieceService, TitlesService titlesService,
645+
ProtectionService protectionService, PieceUpdateFlowPoLineService pieceUpdateFlowPoLineService,
646+
PieceUpdateFlowInventoryManager pieceUpdateFlowInventoryManager, BasePieceFlowHolderBuilder basePieceFlowHolderBuilder,
647+
DefaultPieceFlowsValidator defaultPieceFlowsValidator, PurchaseOrderLineService purchaseOrderLineService) {
648+
return new PieceUpdateFlowManager(pieceStorageService, pieceService, titlesService, protectionService, pieceUpdateFlowPoLineService,
649+
pieceUpdateFlowInventoryManager, basePieceFlowHolderBuilder, defaultPieceFlowsValidator, purchaseOrderLineService);
651650
}
652651

653652
@Bean

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
import org.folio.rest.jaxrs.model.Error;
7171
import org.folio.rest.jaxrs.model.Errors;
7272
import org.folio.rest.jaxrs.model.FundDistribution;
73+
import org.folio.rest.jaxrs.model.Location;
7374
import org.folio.rest.jaxrs.model.Physical;
7475
import org.folio.rest.jaxrs.model.PoLine;
7576
import org.folio.rest.jaxrs.model.PoLineCollection;
@@ -736,13 +737,22 @@ private Future<Void> validateUserUnaffiliatedLocationUpdates(CompositePoLine upd
736737
return getUserTenantsIfNeeded(requestContext)
737738
.compose(userTenants -> {
738739
if (CollectionUtils.isEmpty(userTenants)) {
740+
logger.info("validateUserUnaffiliatedLocationUpdates:: User tenants is empty");
739741
return Future.succeededFuture();
740742
}
741743
var storageUnaffiliatedLocations = extractUnaffiliatedLocations(storedPoLine.getLocations(), userTenants);
742744
var updatedUnaffiliatedLocations = extractUnaffiliatedLocations(updatedPoLine.getLocations(), userTenants);
745+
logger.info("validateUserUnaffiliatedLocationUpdates:: Found unaffiliated POL location tenant ids, poLineId: '{}', stored: '{}', updated: '{}'",
746+
updatedPoLine.getId(),
747+
storageUnaffiliatedLocations.stream().map(Location::getTenantId).distinct().toList(),
748+
updatedUnaffiliatedLocations.stream().map(Location::getTenantId).distinct().toList());
743749
if (!SetUtils.isEqualSet(storageUnaffiliatedLocations, updatedUnaffiliatedLocations)) {
750+
logger.info("validateUserUnaffiliatedLocationUpdates:: User is not affiliated with all locations on the POL, poLineId: '{}'",
751+
updatedPoLine.getId());
744752
return Future.failedFuture(new HttpException(422, ErrorCodes.LOCATION_UPDATE_WITHOUT_AFFILIATION));
745753
}
754+
logger.info("validateUserUnaffiliatedLocationUpdates:: User is affiliated with all locations on the POL, poLineId: '{}'",
755+
updatedPoLine.getId());
746756
return Future.succeededFuture();
747757
});
748758
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.folio.models.pieces;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
import org.folio.rest.jaxrs.model.Piece;
6+
7+
import java.util.List;
8+
9+
@AllArgsConstructor
10+
public class PieceBatchStatusUpdateHolder extends BasePieceFlowHolder {
11+
12+
@Getter
13+
private Piece.ReceivingStatus receivingStatus;
14+
@Getter
15+
private List<Piece> pieces;
16+
private String poLineId;
17+
18+
@Override
19+
public String getOrderLineId() {
20+
return poLineId;
21+
}
22+
23+
@Override
24+
public String getTitleId() {
25+
return null;
26+
}
27+
28+
}
Lines changed: 38 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,162 +1,88 @@
11
package org.folio.orders.events.handlers;
22

3-
import static org.folio.helper.CheckinReceivePiecesHelper.EXPECTED_STATUSES;
4-
import static org.folio.helper.CheckinReceivePiecesHelper.RECEIVED_STATUSES;
5-
import static org.folio.orders.utils.ResourcePathResolver.PIECES_STORAGE;
6-
import static org.folio.orders.utils.ResourcePathResolver.resourcesPath;
7-
import static org.folio.rest.jaxrs.model.PoLine.ReceiptStatus.AWAITING_RECEIPT;
8-
import static org.folio.rest.jaxrs.model.PoLine.ReceiptStatus.FULLY_RECEIVED;
9-
import static org.folio.rest.jaxrs.model.PoLine.ReceiptStatus.PARTIALLY_RECEIVED;
3+
import static org.folio.orders.events.utils.EventUtils.getPoLineId;
4+
import static org.folio.orders.utils.HelperUtils.getOkapiHeaders;
5+
import static org.folio.service.orders.utils.StatusUtils.calculatePoLineReceiptStatus;
106

11-
import java.util.ArrayList;
127
import java.util.List;
138
import java.util.Map;
149

15-
import org.apache.commons.collections4.CollectionUtils;
1610
import org.folio.helper.BaseHelper;
1711
import org.folio.orders.utils.HelperUtils;
1812
import org.folio.orders.utils.PoLineCommonUtil;
19-
import org.folio.rest.core.RestClient;
2013
import org.folio.rest.core.models.RequestContext;
2114
import org.folio.rest.jaxrs.model.Piece;
22-
import org.folio.rest.jaxrs.model.Piece.ReceivingStatus;
23-
import org.folio.rest.jaxrs.model.PieceCollection;
2415
import org.folio.rest.jaxrs.model.PoLine;
25-
import org.folio.rest.jaxrs.model.PoLine.ReceiptStatus;
2616
import org.folio.service.orders.PurchaseOrderLineService;
17+
import org.folio.service.pieces.PieceStorageService;
2718
import org.springframework.beans.factory.annotation.Autowired;
2819
import org.springframework.stereotype.Component;
2920

3021
import io.vertx.core.Future;
3122
import io.vertx.core.Handler;
32-
import io.vertx.core.Promise;
3323
import io.vertx.core.Vertx;
3424
import io.vertx.core.eventbus.Message;
3525
import io.vertx.core.json.JsonArray;
3626
import io.vertx.core.json.JsonObject;
37-
import one.util.streamex.StreamEx;
3827

3928
@Component("receiptStatusHandler")
4029
public class ReceiptStatusConsistency extends BaseHelper implements Handler<Message<JsonObject>> {
4130

42-
private static final int LIMIT = Integer.MAX_VALUE;
43-
private static final String PIECES_ENDPOINT = resourcesPath(PIECES_STORAGE) + "?query=poLineId==%s&limit=%s";
44-
31+
private final PieceStorageService pieceStorageService;
4532
private final PurchaseOrderLineService purchaseOrderLineService;
4633

4734

4835
@Autowired
49-
public ReceiptStatusConsistency(Vertx vertx, PurchaseOrderLineService purchaseOrderLineService) {
36+
public ReceiptStatusConsistency(Vertx vertx, PieceStorageService pieceStorageService, PurchaseOrderLineService purchaseOrderLineService) {
5037
super(vertx.getOrCreateContext());
38+
this.pieceStorageService = pieceStorageService;
5139
this.purchaseOrderLineService = purchaseOrderLineService;
5240
}
5341

5442
@Override
5543
public void handle(Message<JsonObject> message) {
56-
JsonObject messageFromEventBus = message.body();
57-
44+
var messageFromEventBus = message.body();
5845
logger.info("Received message body: {}", messageFromEventBus);
5946

60-
Map<String, String> okapiHeaders = org.folio.orders.utils.HelperUtils.getOkapiHeaders(message);
47+
var okapiHeaders = getOkapiHeaders(message);
6148
var requestContext = new RequestContext(ctx, okapiHeaders);
62-
List<Future<Void>> futures = new ArrayList<>();
63-
Promise<Void> promise = Promise.promise();
64-
futures.add(promise.future());
65-
66-
String poLineIdUpdate = messageFromEventBus.getString("poLineIdUpdate");
67-
String query = String.format(PIECES_ENDPOINT, poLineIdUpdate, LIMIT);
68-
69-
// 1. Get all pieces for poLineId
70-
getPieces(query, requestContext)
71-
.compose(listOfPieces ->
72-
// 2. Get PoLine for the poLineId which will be used to calculate PoLineReceiptStatus
73-
purchaseOrderLineService.getOrderLineById(poLineIdUpdate, requestContext)
74-
.map(poLine -> {
75-
if (PoLineCommonUtil.isCancelledOrOngoingStatus(poLine)) {
76-
promise.complete();
77-
return null;
78-
}
79-
ReceiptStatus receivingStatus = calculatePoLineReceiptStatus(poLine, listOfPieces);
80-
boolean statusUpdated = purchaseOrderLineService.updatePoLineReceiptStatusWithoutSave(poLine, receivingStatus);
81-
if (statusUpdated) {
82-
purchaseOrderLineService.saveOrderLine(poLine, requestContext)
83-
.map(aVoid -> {
84-
// send event to update order status
85-
updateOrderStatus(poLine, okapiHeaders, requestContext);
86-
promise.complete();
87-
return null;
88-
})
89-
.onFailure(e -> {
90-
logger.error("The error updating poLine by id {}", poLineIdUpdate, e);
91-
promise.fail(e);
92-
});
93-
} else {
94-
promise.complete();
95-
}
96-
return null;
97-
})
98-
.onFailure(e -> {
99-
logger.error("The error getting poLine by id {}", poLineIdUpdate, e);
100-
promise.fail(e);
101-
}))
102-
.onFailure(e -> {
103-
logger.error("The error happened getting all pieces by poLine {}", poLineIdUpdate, e);
104-
promise.fail(e);
105-
});
10649

107-
// Now wait for all operations to be completed and send reply
108-
completeAllFutures(futures, message);
109-
}
50+
var poLineId = getPoLineId(messageFromEventBus);
51+
var future = pieceStorageService.getPiecesByLineId(poLineId, requestContext)
52+
.compose(pieces -> purchaseOrderLineService.getOrderLineById(poLineId, requestContext)
53+
.compose(poLine -> updatePoLineAndOrderStatuses(pieces, poLine, requestContext))
54+
.onFailure(e -> logger.error("Exception occurred while fetching PoLine by id: '{}'", poLineId, e)))
55+
.onFailure(e -> logger.error("Exception occurred while fetching pieces by PoLine id: '{}'", poLineId, e));
11056

111-
private void updateOrderStatus(PoLine poLine, Map<String, String> okapiHeaders, RequestContext requestContext) {
112-
List<JsonObject> poIds = StreamEx
113-
.of(poLine)
114-
.map(PoLine::getPurchaseOrderId)
115-
.distinct()
116-
.map(orderId -> new JsonObject().put(ORDER_ID, orderId))
117-
.toList();
118-
JsonObject messageContent = new JsonObject();
119-
messageContent.put(OKAPI_HEADERS, okapiHeaders);
120-
// Collect order ids which should be processed
121-
messageContent.put(EVENT_PAYLOAD, new JsonArray(poIds));
122-
HelperUtils.sendEvent(MessageAddress.RECEIVE_ORDER_STATUS_UPDATE, messageContent, requestContext);
57+
completeAllFutures(List.of(future), message);
12358
}
12459

125-
private ReceiptStatus calculatePoLineReceiptStatus(PoLine poLine, List<Piece> pieces) {
126-
127-
if (CollectionUtils.isEmpty(pieces)) {
128-
logger.info("No pieces processed - receipt status unchanged for PO Line '{}'", poLine.getId());
129-
return poLine.getReceiptStatus();
60+
private Future<Void> updatePoLineAndOrderStatuses(List<Piece> pieces, PoLine poLine, RequestContext requestContext) {
61+
if (PoLineCommonUtil.isCancelledOrOngoingStatus(poLine)) {
62+
logger.info("updatePoLineAndOrderStatuses:: PoLine with id: '{}' has status: '{}', skipping...", poLine.getId(), poLine.getReceiptStatus());
63+
return Future.succeededFuture();
13064
}
131-
132-
long expectedQty = getPiecesQuantityByPoLineAndStatus(EXPECTED_STATUSES, pieces);
133-
return calculatePoLineReceiptStatus(expectedQty, pieces);
134-
}
135-
136-
private ReceiptStatus calculatePoLineReceiptStatus(long expectedPiecesQuantity, List<Piece> pieces) {
137-
if (expectedPiecesQuantity == 0) {
138-
logger.info("calculatePoLineReceiptStatus:: Fully received");
139-
return FULLY_RECEIVED;
140-
}
141-
142-
if (StreamEx.of(pieces).anyMatch(piece -> RECEIVED_STATUSES.contains(piece.getReceivingStatus()))) {
143-
logger.info("calculatePoLineReceiptStatus:: Partially Received - In case there is at least one successfully received piece");
144-
return PARTIALLY_RECEIVED;
65+
var newStatus = pieces.isEmpty()
66+
? poLine.getReceiptStatus()
67+
: calculatePoLineReceiptStatus(poLine.getId(), pieces);
68+
boolean statusUpdated = purchaseOrderLineService.updatePoLineReceiptStatusWithoutSave(poLine, newStatus);
69+
if (!statusUpdated) {
70+
logger.info("updatePoLineAndOrderStatuses:: PoLine receipt status is not updated, skipping...");
71+
return Future.succeededFuture();
14572
}
146-
147-
logger.info("calculatePoLineReceiptStatus::Pieces were rolled-back to Expected, checking if there is any Received piece in the storage");
148-
long receivedQty = getPiecesQuantityByPoLineAndStatus(RECEIVED_STATUSES, pieces);
149-
return receivedQty == 0 ? AWAITING_RECEIPT : PARTIALLY_RECEIVED;
73+
return purchaseOrderLineService.saveOrderLine(poLine, requestContext)
74+
.compose(v -> updateOrderStatus(poLine, okapiHeaders, requestContext))
75+
.onSuccess(v -> logger.info("updatePoLineAndOrderStatuses:: Order '{}' and PoLine '{}' updated successfully", poLine.getId(), poLine.getPurchaseOrderId()))
76+
.onFailure(e -> logger.error("Exception occurred while updating Order '{}' and PoLine '{}'", poLine.getId(), poLine.getPurchaseOrderId(), e));
15077
}
15178

152-
private long getPiecesQuantityByPoLineAndStatus(List<ReceivingStatus> receivingStatuses, List<Piece> pieces) {
153-
return pieces.stream()
154-
.filter(piece -> receivingStatuses.contains(piece.getReceivingStatus()))
155-
.count();
79+
private Future<Void> updateOrderStatus(PoLine poLine, Map<String, String> okapiHeaders, RequestContext requestContext) {
80+
var messageContent = JsonObject.of(
81+
OKAPI_HEADERS, okapiHeaders,
82+
EVENT_PAYLOAD, JsonArray.of(JsonObject.of(ORDER_ID, poLine.getPurchaseOrderId()))
83+
);
84+
HelperUtils.sendEvent(MessageAddress.RECEIVE_ORDER_STATUS_UPDATE, messageContent, requestContext);
85+
return Future.succeededFuture();
15686
}
15787

158-
Future<List<Piece>> getPieces(String endpoint, RequestContext requestContext) {
159-
return new RestClient().get(endpoint, PieceCollection.class, requestContext)
160-
.map(PieceCollection::getPieces);
161-
}
16288
}

0 commit comments

Comments
 (0)