From 1d2c349aa9272e60946d6088b80c0ab0bf1b53c4 Mon Sep 17 00:00:00 2001 From: Janis Saldabols Date: Wed, 18 Sep 2024 13:15:01 +0300 Subject: [PATCH] CIRC-2141 Add new field to request schema --- ramls/request.json | 4 ++ .../org/folio/circulation/domain/Campus.java | 3 +- .../folio/circulation/domain/Institution.java | 3 +- .../org/folio/circulation/domain/Library.java | 3 +- .../org/folio/circulation/domain/Request.java | 5 ++ .../representations/RequestProperties.java | 1 + .../storage/inventory/LocationRepository.java | 30 +++++++++- .../services/ItemForTlrService.java | 23 +++++++- .../storage/mappers/CampusMapper.java | 3 +- .../storage/mappers/InstitutionMapper.java | 3 +- .../storage/mappers/LibraryMapper.java | 3 +- .../requests/RequestsAPICreationTests.java | 58 +++++++++++++++++++ .../api/support/builders/RequestBuilder.java | 7 ++- 13 files changed, 137 insertions(+), 9 deletions(-) diff --git a/ramls/request.json b/ramls/request.json index 9e38fe6c7f..ed410a3751 100644 --- a/ramls/request.json +++ b/ramls/request.json @@ -383,6 +383,10 @@ "description": "Request fields used for search", "type": "object", "$ref": "request-search-index.json" + }, + "itemLocationCode": { + "description": "Allow specifying item location when creating title-level requests", + "type": "string" } }, "additionalProperties": false, diff --git a/src/main/java/org/folio/circulation/domain/Campus.java b/src/main/java/org/folio/circulation/domain/Campus.java index 1b294cce79..dd1d88ffee 100644 --- a/src/main/java/org/folio/circulation/domain/Campus.java +++ b/src/main/java/org/folio/circulation/domain/Campus.java @@ -9,9 +9,10 @@ public static Campus unknown() { } public static Campus unknown(String id) { - return new Campus(id, null); + return new Campus(id, null, null); } String id; String name; + String code; } diff --git a/src/main/java/org/folio/circulation/domain/Institution.java b/src/main/java/org/folio/circulation/domain/Institution.java index 3d94f43ec4..216a0a8b1c 100644 --- a/src/main/java/org/folio/circulation/domain/Institution.java +++ b/src/main/java/org/folio/circulation/domain/Institution.java @@ -9,9 +9,10 @@ public static Institution unknown() { } public static Institution unknown(String id) { - return new Institution(id, null); + return new Institution(id, null, null); } String id; String name; + String code; } diff --git a/src/main/java/org/folio/circulation/domain/Library.java b/src/main/java/org/folio/circulation/domain/Library.java index 372f754731..d702165ab5 100644 --- a/src/main/java/org/folio/circulation/domain/Library.java +++ b/src/main/java/org/folio/circulation/domain/Library.java @@ -9,9 +9,10 @@ public static Library unknown() { } public static Library unknown(String id) { - return new Library(id, null); + return new Library(id, null, null); } String id; String name; + String code; } diff --git a/src/main/java/org/folio/circulation/domain/Request.java b/src/main/java/org/folio/circulation/domain/Request.java index 0774075a5b..ff7c057cd6 100644 --- a/src/main/java/org/folio/circulation/domain/Request.java +++ b/src/main/java/org/folio/circulation/domain/Request.java @@ -17,6 +17,7 @@ import static org.folio.circulation.domain.representations.RequestProperties.CANCELLATION_REASON_ID; import static org.folio.circulation.domain.representations.RequestProperties.CANCELLATION_REASON_NAME; import static org.folio.circulation.domain.representations.RequestProperties.CANCELLATION_REASON_PUBLIC_DESCRIPTION; +import static org.folio.circulation.domain.representations.RequestProperties.ITEM_LOCATION_CODE; import static org.folio.circulation.domain.representations.RequestProperties.HOLDINGS_RECORD_ID; import static org.folio.circulation.domain.representations.RequestProperties.HOLD_SHELF_EXPIRATION_DATE; import static org.folio.circulation.domain.representations.RequestProperties.INSTANCE_ID; @@ -377,6 +378,10 @@ public String getPatronComments() { return getProperty(requestRepresentation, "patronComments"); } + public String geItemLocationCode() { + return getProperty(requestRepresentation, ITEM_LOCATION_CODE); + } + public Request truncateRequestExpirationDateToTheEndOfTheDay(ZoneId zone) { ZonedDateTime requestExpirationDate = getRequestExpirationDate(); if (requestExpirationDate != null) { diff --git a/src/main/java/org/folio/circulation/domain/representations/RequestProperties.java b/src/main/java/org/folio/circulation/domain/representations/RequestProperties.java index cf3be67cb1..830f2109d7 100644 --- a/src/main/java/org/folio/circulation/domain/representations/RequestProperties.java +++ b/src/main/java/org/folio/circulation/domain/representations/RequestProperties.java @@ -21,4 +21,5 @@ private RequestProperties() { } public static final String REQUESTER_ID = "requesterId"; public static final String FULFILLMENT_PREFERENCE = "fulfillmentPreference"; public static final String PICKUP_SERVICE_POINT_ID = "pickupServicePointId"; + public static final String ITEM_LOCATION_CODE = "itemLocationCode"; } diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/LocationRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/LocationRepository.java index 52fe73dc2d..6099d95589 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/LocationRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/LocationRepository.java @@ -138,7 +138,9 @@ public CompletableFuture>> fetchLocations( new LocationMapper()::toDomain); return fetcher.findByIds(locationIds) - .thenCompose(this::loadLibrariesForLocations); + .thenCompose(this::loadLibrariesForLocations) + .thenCompose(this::loadCampusesForLocations) + .thenCompose(this::loadInstitutionsForLocations); } private CompletableFuture> loadLibrary(Location location) { @@ -208,6 +210,19 @@ public CompletableFuture>> getLibraries( .thenApply(mapResult(records -> records.toMap(Library::getId))); } + private CompletableFuture>> loadCampusesForLocations( + Result> multipleRecordsResult) { + + log.debug("loadCampusesForLocations:: parameters multipleRecordsResult: {}", + () -> resultAsString(multipleRecordsResult)); + + return multipleRecordsResult.combineAfter( + locations -> getCampuses(locations.getRecords()), (locations, campuses) -> + locations.mapRecords(location -> location.withCampus( + campuses.getOrDefault(location.getCampusId(), Campus.unknown(location.getCampusId()))))); + + } + public CompletableFuture>> getCampuses( Collection locations) { @@ -223,6 +238,19 @@ public CompletableFuture>> getCampuses( .thenApply(mapResult(records -> records.toMap(Campus::getId))); } + private CompletableFuture>> loadInstitutionsForLocations( + Result> multipleRecordsResult) { + + log.debug("loadInstitutionsForLocations:: parameters multipleRecordsResult: {}", + () -> resultAsString(multipleRecordsResult)); + + return multipleRecordsResult.combineAfter( + locations -> getInstitutions(locations.getRecords()), (locations, institutions) -> + locations.mapRecords(location -> location.withInstitution( + institutions.getOrDefault(location.getInstitutionId(), Institution.unknown(location.getInstitutionId()))))); + + } + public CompletableFuture>> getInstitutions( Collection locations) { diff --git a/src/main/java/org/folio/circulation/services/ItemForTlrService.java b/src/main/java/org/folio/circulation/services/ItemForTlrService.java index 3a306991a7..b8147312e6 100644 --- a/src/main/java/org/folio/circulation/services/ItemForTlrService.java +++ b/src/main/java/org/folio/circulation/services/ItemForTlrService.java @@ -4,6 +4,7 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static org.folio.circulation.domain.RequestType.PAGE; +import static org.folio.circulation.domain.representations.RequestProperties.ITEM_LOCATION_CODE; import static org.folio.circulation.domain.representations.RequestProperties.INSTANCE_ID; import static org.folio.circulation.support.ValidationErrorFailure.failedValidation; import static org.folio.circulation.support.results.Result.of; @@ -69,7 +70,27 @@ private Result> refusePageRequestWhenNoAvailablePageableItemsExist(Re return failedValidation(message, INSTANCE_ID, request.getInstanceId()); } - return of(() -> availablePageableItems); + List finalAvailablePageableItems; + if (request.geItemLocationCode() != null) { + finalAvailablePageableItems = availablePageableItems.stream() + .filter(item -> request.geItemLocationCode().equals(item.getLocation().getCode()) || + request.geItemLocationCode().equals(item.getLocation().getLibrary().getCode()) || + request.geItemLocationCode().equals(item.getLocation().getCampus().getCode()) || + request.geItemLocationCode().equals(item.getLocation().getInstitution().getCode()) + ) + .toList(); + if (finalAvailablePageableItems.isEmpty()) { + String message = "Cannot create page TLR for this instance ID - no pageable available " + + "items found in forced location"; + log.info("{}. Instance ID: {}, Forced location code {}", + message, request.getInstanceId(), request.geItemLocationCode()); + return failedValidation(message, ITEM_LOCATION_CODE, request.geItemLocationCode()); + } + } else { + finalAvailablePageableItems = availablePageableItems; + } + + return of(() -> finalAvailablePageableItems); } private static Item pickClosestItem(Collection requestedLocations, List availableItems) { diff --git a/src/main/java/org/folio/circulation/storage/mappers/CampusMapper.java b/src/main/java/org/folio/circulation/storage/mappers/CampusMapper.java index 12f44d5cf5..42ef65d610 100644 --- a/src/main/java/org/folio/circulation/storage/mappers/CampusMapper.java +++ b/src/main/java/org/folio/circulation/storage/mappers/CampusMapper.java @@ -9,6 +9,7 @@ public class CampusMapper { public Campus toDomain(JsonObject representation) { return new Campus(getProperty(representation, "id"), - getProperty(representation, "name")); + getProperty(representation, "name"), + getProperty(representation, "code")); } } diff --git a/src/main/java/org/folio/circulation/storage/mappers/InstitutionMapper.java b/src/main/java/org/folio/circulation/storage/mappers/InstitutionMapper.java index 061a5d1c4a..899d14fa9d 100644 --- a/src/main/java/org/folio/circulation/storage/mappers/InstitutionMapper.java +++ b/src/main/java/org/folio/circulation/storage/mappers/InstitutionMapper.java @@ -9,6 +9,7 @@ public class InstitutionMapper { public Institution toDomain(JsonObject representation) { return new Institution(getProperty(representation, "id"), - getProperty(representation, "name")); + getProperty(representation, "name"), + getProperty(representation, "code")); } } diff --git a/src/main/java/org/folio/circulation/storage/mappers/LibraryMapper.java b/src/main/java/org/folio/circulation/storage/mappers/LibraryMapper.java index ceb319c3d3..f509d1dff6 100644 --- a/src/main/java/org/folio/circulation/storage/mappers/LibraryMapper.java +++ b/src/main/java/org/folio/circulation/storage/mappers/LibraryMapper.java @@ -9,6 +9,7 @@ public class LibraryMapper { public Library toDomain(JsonObject representation) { return new Library(getProperty(representation, "id"), - getProperty(representation, "name")); + getProperty(representation, "name"), + getProperty(representation, "code")); } } diff --git a/src/test/java/api/requests/RequestsAPICreationTests.java b/src/test/java/api/requests/RequestsAPICreationTests.java index ae49db5a36..200cccc3d3 100644 --- a/src/test/java/api/requests/RequestsAPICreationTests.java +++ b/src/test/java/api/requests/RequestsAPICreationTests.java @@ -626,6 +626,64 @@ void canCreateTitleLevelRequestWhenTlrEnabled() { assertThat(publishedEvents.filterToList(byEventType("LOAN_DUE_DATE_CHANGED")), hasSize(0)); } + @ParameterizedTest + @CsvSource(value = { + "NU/JC/DL/3F", + "DLRC", + "JC", + "NU" + }) + void createTitleLevelRequestWhenTlrEnabledSetLocation(String locationCode) { + UUID patronId = usersFixture.charlotte().getId(); + final UUID pickupServicePointId = servicePointsFixture.cd1().getId(); + + final var items = itemsFixture.createMultipleItemsForTheSameInstance(2); + UUID instanceId = items.get(0).getInstanceId(); + + configurationsFixture.enableTlrFeature(); + + IndividualResource requestResource = requestsClient.create(new RequestBuilder() + .page() + .withNoHoldingsRecordId() + .withNoItemId() + .titleRequestLevel() + .withInstanceId(instanceId) + .withPickupServicePointId(pickupServicePointId) + .withRequesterId(patronId) + .withItemLocationCode(locationCode)); + + JsonObject request = requestResource.getJson(); + assertThat(request.getString("requestLevel"), is("Title")); + } + + @Test + void createTitleLevelRequestWhenTlrEnabledSetLocationNoItems() { + UUID patronId = usersFixture.charlotte().getId(); + final UUID pickupServicePointId = servicePointsFixture.cd1().getId(); + + final var items = itemsFixture.createMultipleItemsForTheSameInstance(2); + UUID instanceId = items.get(0).getInstanceId(); + + configurationsFixture.enableTlrFeature(); + + Response response = requestsClient.attemptCreate( + new RequestBuilder() + .page() + .withNoHoldingsRecordId() + .withNoItemId() + .titleRequestLevel() + .withInstanceId(instanceId) + .withPickupServicePointId(pickupServicePointId) + .withRequesterId(patronId) + .withItemLocationCode("DoesNotExist") + .create()); + + assertThat(response.getStatusCode(), is(422)); + assertThat(response.getJson(), hasErrorWith( + hasMessage("Cannot create page TLR for this instance ID - no pageable " + + "available items found in forced location"))); + } + @Test void cannotCreateRequestWithNonExistentRequestLevelWhenTlrEnabled() { UUID patronId = usersFixture.charlotte().getId(); diff --git a/src/test/java/api/support/builders/RequestBuilder.java b/src/test/java/api/support/builders/RequestBuilder.java index 2514482a1e..473ebf8184 100644 --- a/src/test/java/api/support/builders/RequestBuilder.java +++ b/src/test/java/api/support/builders/RequestBuilder.java @@ -3,6 +3,7 @@ import static api.support.utl.DateTimeUtils.getLocalDatePropertyForDateWithTime; import static java.time.ZoneOffset.UTC; import static java.util.stream.Collectors.toList; +import static org.folio.circulation.domain.representations.RequestProperties.ITEM_LOCATION_CODE; import static org.folio.circulation.support.json.JsonPropertyFetcher.getDateTimeProperty; import static org.folio.circulation.support.json.JsonPropertyFetcher.getIntegerProperty; import static org.folio.circulation.support.json.JsonPropertyFetcher.getLocalDateProperty; @@ -63,6 +64,7 @@ public class RequestBuilder extends JsonBuilder implements Builder { private final Tags tags; private final String patronComments; private final BlockOverrides blockOverrides; + private final String itemLocationCode; public RequestBuilder() { this(UUID.randomUUID(), @@ -89,6 +91,7 @@ public RequestBuilder() { null, null, null, + null, null); } @@ -120,7 +123,8 @@ public static RequestBuilder from(IndividualResource response) { getUUIDProperty(representation, "pickupServicePointId"), new Tags((toStream(representation.getJsonObject("tags"), "tagList").collect(toList()))), getProperty(representation, "patronComments"), - null + null, + getProperty(representation, ITEM_LOCATION_CODE) ); } @@ -149,6 +153,7 @@ public JsonObject create() { put(request, "cancelledDate", formatDateTimeOptional(cancelledDate)); put(request, "pickupServicePointId", this.pickupServicePointId); put(request, "patronComments", this.patronComments); + put(request, ITEM_LOCATION_CODE, this.itemLocationCode); if (itemSummary != null) { final JsonObject itemRepresentation = new JsonObject();