Skip to content

Commit

Permalink
[MODORDERSTOR-438] - Implement Create Batch Pieces API (#469)
Browse files Browse the repository at this point in the history
* [MODORDSTOR-438] - Batch create pieces API

* Cover tests
  • Loading branch information
azizbekxm authored Jan 30, 2025
1 parent 51103be commit addac8b
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 17 deletions.
11 changes: 11 additions & 0 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@
"pathPattern": "/orders-storage/pieces/{id}",
"permissionsRequired": ["orders-storage.pieces.item.delete"]
},
{
"methods": ["POST"],
"pathPattern": "/orders-storage/pieces-batch",
"permissionsRequired": ["orders-storage.pieces-batch.collection.post"]
},
{
"methods": ["PUT"],
"pathPattern": "/orders-storage/pieces-batch",
Expand Down Expand Up @@ -801,6 +806,11 @@
"displayName" : "orders-storage.pieces-item delete",
"description" : "Delete a piece"
},
{
"permissionName" : "orders-storage.pieces-batch.collection.post",
"displayName" : "Pieces batch create",
"description" : "Create a set of Pieces in a batch"
},
{
"permissionName" : "orders-storage.pieces-batch.collection.put",
"displayName" : "Pieces batch update",
Expand All @@ -816,6 +826,7 @@
"orders-storage.pieces.item.get",
"orders-storage.pieces.item.put",
"orders-storage.pieces.item.delete",
"orders-storage.pieces-batch.collection.post",
"orders-storage.pieces-batch.collection.put"
]
},
Expand Down
46 changes: 39 additions & 7 deletions ramls/pieces-batch.raml
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,51 @@ documentation:

types:
pieces-collection: !include acq-models/mod-orders-storage/schemas/piece_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}$
errors: !include raml-util/schemas/errors.schema

resourceTypes:
collection: !include raml-util/rtypes/collection.raml
collection-item: !include raml-util/rtypes/item-collection.raml
traits:

validate: !include raml-util/traits/validation.raml

/orders-storage/pieces-batch:
displayName: Process list of Pieces in a batch
description: Process list of Pieces in a batch APIs
is: [ validate ]
post:
description: Create batch pieces
body:
application/json:
type: pieces-collection
example:
strict: false
value: !include acq-models/mod-orders-storage/examples/piece_collection.sample
responses:
200:
description: "Returns processing result of the piece collection"
body:
application/json:
type: pieces-collection
example:
strict: false
value: !include acq-models/mod-orders-storage/examples/piece_collection.sample
422:
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:
application/json:
example:
strict: false
value: !include raml-util/examples/errors.sample
text/plain:
example: |
"unable to process object -- malformed JSON at 13:4"
500:
description: "Internal server error, e.g. due to misconfiguration"
body:
application/json:
example:
strict: false
value: !include raml-util/examples/errors.sample
text/plain:
example: "internal server error, contact administrator"
put:
description: "Update the list of Pieces in a batch"
body:
Expand Down
32 changes: 24 additions & 8 deletions src/main/java/org/folio/rest/impl/PiecesAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
import static org.folio.util.HelperUtils.extractEntityFields;
import static org.folio.util.MetadataUtils.populateMetadata;

import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import java.util.Map;

import javax.ws.rs.core.Response;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.dao.PostgresClientFactory;
Expand All @@ -28,12 +31,6 @@
import org.folio.spring.SpringContextUtil;
import org.springframework.beans.factory.annotation.Autowired;

import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;

public class PiecesAPI extends BaseApi implements OrdersStoragePieces, OrdersStoragePiecesBatch {

private static final Logger log = LogManager.getLogger();
Expand Down Expand Up @@ -112,6 +109,25 @@ public void putOrdersStoragePiecesById(String id, Piece entity, Map<String, Stri
});
}

@Override
public void postOrdersStoragePiecesBatch(PiecesCollection piecesCollection, Map<String, String> okapiHeaders,
Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
log.info("postOrdersStoragePiecesBatch:: Batch creating {} pieces", piecesCollection.getPieces().size());
pgClient
.withTrans(conn -> pieceService.createPieces(piecesCollection.getPieces(), conn, okapiHeaders)
.compose(pieces -> auditOutboxService.savePiecesOutboxLog(conn, pieces, PieceAuditEvent.Action.CREATE, okapiHeaders)))
.onComplete(ar -> {
if (ar.succeeded()) {
log.info("Create '{}' pieces completed", piecesCollection.getPieces());
auditOutboxService.processOutboxEventLogs(okapiHeaders);
asyncResultHandler.handle(buildResponseWithLocation(piecesCollection, getEndpoint(piecesCollection)));
} else {
log.error("Piece creation failed", ar.cause());
asyncResultHandler.handle(buildErrorResponse(ar.cause()));
}
});
}

@Override
public void putOrdersStoragePiecesBatch(PiecesCollection piecesCollection, Map<String, String> okapiHeaders,
Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
Expand Down
22 changes: 21 additions & 1 deletion src/main/java/org/folio/services/piece/PieceService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
import static org.folio.rest.persist.HelperUtils.getFullTableName;
import static org.folio.rest.persist.HelperUtils.getQueryValues;
import static org.folio.util.DbUtils.getEntitiesByField;
import static org.folio.util.HelperUtils.extractEntityFields;
import static org.folio.util.MetadataUtils.populateMetadata;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

Expand Down Expand Up @@ -82,6 +84,24 @@ public Future<String> createPiece(Conn conn, Piece piece) {
.onFailure(e -> log.error("createPiece:: Create piece failed: '{}'", piece.getId(), e));
}

public Future<List<Piece>> createPieces(List<Piece> pieces, Conn conn, Map<String, String> okapiHeaders) {
if (CollectionUtils.isEmpty(pieces)) {
log.warn("createPieces:: Pieces list is empty, skipping the create");
return Future.succeededFuture(List.of());
}
for (Piece piece : pieces) {
if (StringUtils.isBlank(piece.getId())) {
piece.setId(UUID.randomUUID().toString());
}
populateMetadata(piece::getMetadata, piece::withMetadata, okapiHeaders);
}
var pieceIds = extractEntityFields(pieces, Piece::getId);
return conn.saveBatch(PIECES_TABLE, pieces)
.map(rows -> pieces)
.onSuccess(ar -> log.info("createPieces:: Saved pieces: {}", pieceIds))
.onFailure(t -> log.error("createPieces:: Failed pieces: {}", pieceIds, t));
}

public Future<RowSet<Row>> updatePiece(Conn conn, Piece piece, String id) {
log.debug("updatePiece:: Updating piece: '{}'", id);
return conn.update(TableNames.PIECES_TABLE, piece, id)
Expand Down
23 changes: 22 additions & 1 deletion src/test/java/org/folio/rest/impl/PiecesAPITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

Expand Down Expand Up @@ -130,6 +129,28 @@ void putOrdersStoragePiecesBatch_shouldHandleEmptyPieceList() throws MalformedUR
assertEquals(0, events.size());
}

@Test
void postOrdersStoragePiecesBatch_shouldCreatePiecesSuccessfully() throws MalformedURLException {
log.info("--- mod-orders-storage piece test: batch create pieces");

pieceIds.add(UUID.randomUUID().toString());
pieceIds.add(UUID.randomUUID().toString());
var jsonPiece1 = getEntity(TestData.Piece.DEFAULT, pieceIds.get(0), "poLineId", poLineId, "titleId", titleId);
var jsonPiece2 = getEntity(TestData.Piece.DEFAULT, pieceIds.get(1), "poLineId", poLineId, "titleId", titleId);

var piece1 = new JsonObject(jsonPiece1).mapTo(Piece.class);
var piece2 = new JsonObject(jsonPiece2).mapTo(Piece.class);

var piecesCollection = new PiecesCollection().withPieces(List.of(piece1, piece2));
postData(PIECES_BATCH_ENDPOINT, Json.encode(piecesCollection), headers).then().statusCode(201);
callAuditOutboxApi(headers);

List<String> events = StorageTestSuite.checkKafkaEventSent(TENANT_NAME, AuditEventType.ACQ_PIECE_CHANGED.getTopicName(), 2, userId);
assertEquals(2, events.size());
checkPieceEventContent(events.get(0), PieceAuditEvent.Action.CREATE);
checkPieceEventContent(events.get(1), PieceAuditEvent.Action.CREATE);
}

private void prepareData() throws MalformedURLException {
postData(TestEntities.PURCHASE_ORDER.getEndpoint(), getEntity(TestData.PurchaseOrder.DEFAULT, orderId, "poNumber", "12345"), headers)
.then().statusCode(201);
Expand Down
22 changes: 22 additions & 0 deletions src/test/java/org/folio/services/piece/PieceServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.folio.models.TableNames.PIECES_TABLE;
import static org.folio.models.TableNames.PO_LINE_TABLE;
import static org.folio.rest.RestVerticle.OKAPI_HEADER_TENANT;
import static org.folio.rest.RestVerticle.OKAPI_USERID_HEADER;
import static org.folio.rest.utils.TenantApiTestUtil.deleteTenant;
import static org.folio.rest.utils.TenantApiTestUtil.prepareTenant;
import static org.hamcrest.CoreMatchers.is;
Expand All @@ -12,6 +13,7 @@
import static org.mockito.Mockito.verifyNoInteractions;

import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.logging.log4j.LogManager;
Expand Down Expand Up @@ -309,4 +311,24 @@ void shouldNotUpdatePiecesWhenEmpty(Vertx vertx, VertxTestContext testContext) {
});
});
}

@Test
void shouldNotCreatePiecesWhenEmpty(Vertx vertx, VertxTestContext testContext) {
var headers = Map.of(OKAPI_USERID_HEADER, UUID.randomUUID().toString());
new DBClient(vertx, TEST_TENANT).getPgClient().withConn(connection -> {
var conn = spy(connection);
List<Piece> piecesToUpdate = List.of();
var updatePiecesFuture = pieceService.createPieces(piecesToUpdate, conn, headers);

return testContext.assertComplete(updatePiecesFuture)
.onComplete(ar -> {
List<Piece> actPieces = ar.result();
testContext.verify(() -> {
assertThat(actPieces, is(piecesToUpdate));
verifyNoInteractions(conn);
});
testContext.completeNow();
});
});
}
}

0 comments on commit addac8b

Please sign in to comment.