Skip to content

Commit

Permalink
Merge pull request #1367 from folio-org/CIRC-1967b
Browse files Browse the repository at this point in the history
CIRC-1907 Check in/Check out for the virtual item
  • Loading branch information
MagzhanArtykov authored Nov 6, 2023
2 parents b5a47a2 + 8bcc058 commit f4de02d
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 12 deletions.
10 changes: 10 additions & 0 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,12 @@
"version": "1.0"
}
],
"optional": [
{
"id": "circulation-item",
"version": "1.0"
}
],
"permissionSets": [
{
"permissionName": "circulation.requests.queue.reorder.collection.post",
Expand Down Expand Up @@ -1669,6 +1675,7 @@
"circulation.rules.loan-policy.get",
"circulation.rules.request-policy.get",
"inventory-storage.items.item.put",
"circulation-item-storage.items.item.put",
"circulation.internal.fetch-items",
"users.item.get",
"users.collection.get",
Expand Down Expand Up @@ -1716,6 +1723,7 @@
"circulation.rules.loan-policy.get",
"circulation.rules.request-policy.get",
"inventory-storage.items.item.put",
"circulation-item-storage.items.item.put",
"circulation.internal.fetch-items",
"users.item.get",
"users.collection.get",
Expand Down Expand Up @@ -2294,6 +2302,8 @@
"description" : "Internal permission set for fetching item(s)",
"subPermissions": [
"inventory-storage.items.item.get",
"circulation-item-storage.items.item.get",
"circulation-item-storage.items.collection.get",
"inventory-storage.items.collection.get",
"inventory-storage.holdings.item.get",
"inventory-storage.holdings.collection.get",
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/org/folio/circulation/domain/Item.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.folio.circulation.domain;

import static java.lang.String.format;
import static org.apache.commons.lang3.StringUtils.firstNonBlank;
import static org.folio.circulation.domain.ItemStatus.AVAILABLE;
import static org.folio.circulation.domain.ItemStatus.AWAITING_PICKUP;
Expand All @@ -11,6 +10,7 @@
import static org.folio.circulation.domain.ItemStatus.MISSING;
import static org.folio.circulation.domain.ItemStatus.PAGED;
import static org.folio.circulation.domain.representations.ItemProperties.STATUS_PROPERTY;
import static org.folio.circulation.support.json.JsonPropertyFetcher.getBooleanProperty;
import static org.folio.circulation.support.json.JsonPropertyFetcher.getNestedStringProperty;
import static org.folio.circulation.support.json.JsonPropertyWriter.write;

Expand Down Expand Up @@ -398,4 +398,8 @@ public Item withInTransitDestinationServicePoint(ServicePoint servicePoint) {
this.permanentLocation, servicePoint, this.changed, this.holdings,
this.instance, this.materialType, this.loanType, this.description);
}

public boolean isDcbItem(){
return getBooleanProperty(itemRepresentation, "dcbItem");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import static org.folio.circulation.support.json.JsonPropertyWriter.remove;
import static org.folio.circulation.support.json.JsonPropertyWriter.write;
import static org.folio.circulation.support.results.AsynchronousResultBindings.combineAfter;
import static org.folio.circulation.support.results.MappingFunctions.when;
import static org.folio.circulation.support.results.Result.ofAsync;
import static org.folio.circulation.support.results.Result.succeeded;
import static org.folio.circulation.support.results.ResultBinding.mapResult;
Expand All @@ -22,6 +23,7 @@
import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Function;
Expand Down Expand Up @@ -61,6 +63,7 @@ public class ItemRepository {
private final InstanceRepository instanceRepository;
private final HoldingsRepository holdingsRepository;
private final LoanTypeRepository loanTypeRepository;
private final CollectionResourceClient circulationItemClient;
private final IdentityMap identityMap = new IdentityMap(
item -> getProperty(item, "id"));

Expand All @@ -69,7 +72,7 @@ public ItemRepository(Clients clients) {
new ServicePointRepository(clients)),
new MaterialTypeRepository(clients), new InstanceRepository(clients),
new HoldingsRepository(clients.holdingsStorage()),
new LoanTypeRepository(clients.loanTypesStorage()));
new LoanTypeRepository(clients.loanTypesStorage()), clients.circulationItemClient());
}

public CompletableFuture<Result<Item>> fetchFor(ItemRelatedRecord itemRelatedRecord) {
Expand Down Expand Up @@ -115,7 +118,8 @@ public CompletableFuture<Result<Item>> updateItem(Item item) {
write(updatedItemRepresentation, LAST_CHECK_IN, lastCheckIn.toJson());
}

return itemsClient.put(item.getItemId(), updatedItemRepresentation)
return (item.isDcbItem() ? circulationItemClient : itemsClient)
.put(item.getItemId(), updatedItemRepresentation)
.thenApply(noContentRecordInterpreter(item)::flatMap)
.thenCompose(x -> ofAsync(() -> item));
}
Expand All @@ -141,14 +145,36 @@ private CompletableFuture<Result<Item>> getAvailableItem(

public CompletableFuture<Result<Item>> fetchByBarcode(String barcode) {
return fetchItemByBarcode(barcode)
.thenComposeAsync(itemResult -> itemResult.after(when(item -> ofAsync(item::isNotFound),
item -> fetchCirculationItemByBarcode(barcode), item -> completedFuture(itemResult))))
.thenComposeAsync(this::fetchItemRelatedRecords);
}

private CompletableFuture<Result<Item>> fetchCirculationItemByBarcode(String barcode) {
final var mapper = new ItemMapper();

return SingleRecordFetcher.jsonOrNull(circulationItemClient, "item")
.fetchWithQueryStringParameters(Map.of("barcode", barcode))
.thenApply(mapResult(identityMap::add))
.thenApply(r -> r.map(mapper::toDomain));
}

public CompletableFuture<Result<Item>> fetchById(String itemId) {
return fetchItem(itemId)
.thenComposeAsync(itemResult -> itemResult.after(when(item -> ofAsync(item::isNotFound),
item -> fetchCirculationItem(itemId), item -> completedFuture(itemResult))))
.thenComposeAsync(this::fetchItemRelatedRecords);
}

private CompletableFuture<Result<Item>> fetchCirculationItem(String id) {
final var mapper = new ItemMapper();

return SingleRecordFetcher.jsonOrNull(circulationItemClient, "item")
.fetch(id)
.thenApply(mapResult(identityMap::add))
.thenApply(r -> r.map(mapper::toDomain));
}

private CompletableFuture<Result<MultipleRecords<Item>>> fetchLocations(
Result<MultipleRecords<Item>> result) {

Expand Down
12 changes: 12 additions & 0 deletions src/main/java/org/folio/circulation/support/Clients.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public class Clients {
private final CollectionResourceClient actualCostFeeFineCancelClient;
private final CollectionResourceClient departmentClient;
private final CollectionResourceClient checkOutLockStorageClient;
private final CollectionResourceClient circulationItemClient;
private final GetManyRecordsClient settingsStorageClient;

public static Clients create(WebContext context, HttpClient httpClient) {
Expand Down Expand Up @@ -132,6 +133,7 @@ private Clients(OkapiHttpClient client, WebContext context) {
departmentClient = createDepartmentClient(client, context);
checkOutLockStorageClient = createCheckoutLockClient(client, context);
settingsStorageClient = createSettingsStorageClient(client, context);
circulationItemClient = createCirculationItemClient(client, context);
}
catch(MalformedURLException e) {
throw new InvalidOkapiLocationException(context.getOkapiLocation(), e);
Expand Down Expand Up @@ -362,6 +364,10 @@ public GetManyRecordsClient settingsStorageClient() {
return settingsStorageClient;
}

public CollectionResourceClient circulationItemClient() {
return circulationItemClient;
}

private static CollectionResourceClient getCollectionResourceClient(
OkapiHttpClient client, WebContext context,
String path)
Expand Down Expand Up @@ -777,6 +783,12 @@ private CollectionResourceClient createCheckoutLockClient(
return getCollectionResourceClient(client, context, "/check-out-lock-storage");
}

private CollectionResourceClient createCirculationItemClient(
OkapiHttpClient client, WebContext context) throws MalformedURLException {

return getCollectionResourceClient(client, context, "/circulation-item");
}

private GetManyRecordsClient createSettingsStorageClient(
OkapiHttpClient client, WebContext context)
throws MalformedURLException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.folio.circulation.support.http.client.Offset.noOffset;

import java.net.URL;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import org.folio.circulation.support.http.client.CqlQuery;
Expand Down Expand Up @@ -87,6 +88,13 @@ public CompletableFuture<Result<Response>> getManyWithRawQueryStringParameters(
return client.get(url);
}

public CompletableFuture<Result<Response>> getManyWithQueryStringParameters(Map<String, String> queryParameters) {
return getManyWithRawQueryStringParameters(queryParameters.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.reduce((a, b) -> a + "&" + b)
.orElse(""));
}

@Override
public CompletableFuture<Result<Response>> getMany(CqlQuery cqlQuery,
PageLimit pageLimit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import static org.folio.circulation.support.logging.LogMessageSanitizer.sanitizeLogParameter;
import static org.folio.circulation.support.results.Result.succeeded;
import static org.folio.circulation.support.results.ResultBinding.flatMapResult;
import static org.folio.circulation.support.utils.LogUtil.mapAsString;

import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;

Expand Down Expand Up @@ -60,14 +62,22 @@ public static SingleRecordFetcher<JsonObject> jsonOrNull(
}

public CompletableFuture<Result<T>> fetch(String id) {
if (log.isInfoEnabled()) {
log.info("Fetching {} with ID: {}", recordType, sanitizeLogParameter(id));
}
log.info("Fetching {} with ID: {}", () -> recordType, () -> sanitizeLogParameter(id));

requireNonNull(id, format("Cannot fetch single %s with null ID", recordType));

return client.get(id)
.thenApply(flatMapResult(interpreter::apply))
.exceptionally(CommonFailures::failedDueToServerError);
}

public CompletableFuture<Result<T>> fetchWithQueryStringParameters(Map<String, String> queryParameters) {
log.info("Fetching {} with query parameters: {}", () -> recordType, () -> mapAsString(queryParameters));

requireNonNull(queryParameters, format("Cannot fetch %s with null parameters", recordType));

return client.getManyWithQueryStringParameters(queryParameters)
.thenApply(flatMapResult(interpreter::apply))
.exceptionally(CommonFailures::failedDueToServerError);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import io.vertx.core.json.JsonArray;
import org.folio.circulation.domain.Holdings;
import org.folio.circulation.domain.Instance;
import org.folio.circulation.domain.Item;
Expand All @@ -36,7 +37,7 @@ class ItemRepositoryTests {
@Test
void canUpdateAnItemThatHasBeenFetched() {
final var itemsClient = mock(CollectionResourceClient.class);
final var repository = createRepository(itemsClient);
final var repository = createRepository(itemsClient, null);

final var itemId = UUID.randomUUID().toString();

Expand All @@ -62,7 +63,7 @@ void canUpdateAnItemThatHasBeenFetched() {

@Test
void cannotUpdateAnItemThatHasNotBeenFetched() {
final var repository = createRepository(null);
final var repository = createRepository(null, null);

final var notFetchedItem = dummyItem();

Expand All @@ -74,20 +75,69 @@ void cannotUpdateAnItemThatHasNotBeenFetched() {

@Test
void nullItemIsNotUpdated() {
final var repository = createRepository(null);
final var repository = createRepository(null, null);

final var updateResult = get(repository.updateItem(null));

assertThat(updateResult, succeeded());
assertThat(updateResult.value(), is(nullValue()));
}

@Test
void returnCirculationItemWhenNotFound() {
final var itemsClient = mock(CollectionResourceClient.class);
final var circulationItemsClient = mock(CollectionResourceClient.class);
final var repository = createRepository(itemsClient, circulationItemsClient);
final var itemId = UUID.randomUUID().toString();

final var circulationItemJson = new JsonObject()
.put("id", itemId)
.put("holdingsRecordId", UUID.randomUUID())
.put("effectiveLocationId", UUID.randomUUID()).toString();
final var emptyResult = new JsonObject()
.put("items", new JsonArray()).toString();

when(itemsClient.getMany(any(), any())).thenReturn(ofAsync(
() -> new Response(200, emptyResult, "application/json")));
when(circulationItemsClient.getManyWithQueryStringParameters(any())).thenReturn(ofAsync(
() -> new Response(200, circulationItemJson, "application/json")));

assertThat(get(repository.fetchByBarcode(itemId)).value().getItemId(), is(itemId));
}

@Test
void canUpdateCirculationItemThatHasBeenFetched(){
final var itemsClient = mock(CollectionResourceClient.class);
final var circulationItemsClient = mock(CollectionResourceClient.class);
final var repository = createRepository(itemsClient, circulationItemsClient);
final var itemId = UUID.randomUUID().toString();

final var circulationItemJson = new JsonObject()
.put("id", itemId)
.put("holdingsRecordId", UUID.randomUUID())
.put("effectiveLocationId", UUID.randomUUID())
.put("dcbItem", true);
final var emptyResult = new JsonObject()
.put("items", new JsonArray()).toString();

mockedClientGet(itemsClient, circulationItemJson.encodePrettily());
when(itemsClient.get(anyString())).thenReturn(ofAsync(
() -> new Response(200, emptyResult, "application/json")));
when(circulationItemsClient.put(any(), any())).thenReturn(ofAsync(
() -> new Response(204, circulationItemJson.toString(), "application/json")));

final var fetchedItem = get(repository.fetchById(itemId)).value();
final var updateResult = get(repository.updateItem(fetchedItem));

assertThat(updateResult, succeeded());
}

private void mockedClientGet(CollectionResourceClient client, String body) {
when(client.get(anyString())).thenReturn(Result.ofAsync(
when(client.get(anyString())).thenReturn(ofAsync(
() -> new Response(200, body, "application/json")));
}

private ItemRepository createRepository(CollectionResourceClient itemsClient) {
private ItemRepository createRepository(CollectionResourceClient itemsClient, CollectionResourceClient circulationItemClient) {
final var locationRepository = mock(LocationRepository.class);
final var materialTypeRepository = mock(MaterialTypeRepository.class);
final var instanceRepository = mock(InstanceRepository.class);
Expand All @@ -108,7 +158,7 @@ private ItemRepository createRepository(CollectionResourceClient itemsClient) {

return new ItemRepository(itemsClient, locationRepository,
materialTypeRepository, instanceRepository,
holdingsRepository, loanTypeRepository);
holdingsRepository, loanTypeRepository, circulationItemClient);
}

private Item dummyItem() {
Expand Down

0 comments on commit f4de02d

Please sign in to comment.