From 6bdf70459efcc43fd207e63ff24478cde8c4e1a8 Mon Sep 17 00:00:00 2001 From: John Malconian Date: Thu, 22 Aug 2024 16:34:48 -0400 Subject: [PATCH 01/17] add ID dev workflows --- .github/workflows/k8s-deploy-latest.yml | 27 +++++ .github/workflows/mvn-dev-build-deploy.yml | 132 +++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 .github/workflows/k8s-deploy-latest.yml create mode 100644 .github/workflows/mvn-dev-build-deploy.yml diff --git a/.github/workflows/k8s-deploy-latest.yml b/.github/workflows/k8s-deploy-latest.yml new file mode 100644 index 0000000000..42f9b28419 --- /dev/null +++ b/.github/workflows/k8s-deploy-latest.yml @@ -0,0 +1,27 @@ +# Very simple workflow to deploy the *latest* published container image with the 'latest' tag. +# This does not post updated module descriptors to okapi, update permissions or enable +# new versions of a module with the tenant. If that is needed, it should be done manually +# via the Okapi API. + +name: k8s-deploy-latest + +env: + K8S_NAMESPACE: 'snapshot-dev' + K8S_DEPLOYMENT: 'mod-circulation-dev' + +on: + workflow_dispatch + +jobs: + k8s-deploy-latest: + + runs-on: ubuntu-latest + steps: + - name: Deploy latest to K8s + uses: actions-hub/kubectl@v1.21.2 + env: + KUBE_CONFIG: ${{ secrets.SNAPSHOT_DEV_SA_KUBECONFIG }} + with: + args: + -n ${{ env.K8S_NAMESPACE }} rollout restart deployment ${{ env.K8S_DEPLOYMENT }} + diff --git a/.github/workflows/mvn-dev-build-deploy.yml b/.github/workflows/mvn-dev-build-deploy.yml new file mode 100644 index 0000000000..a05e2db06f --- /dev/null +++ b/.github/workflows/mvn-dev-build-deploy.yml @@ -0,0 +1,132 @@ +name: mvn-dev-build-deploy + +on: + push: + pull_request: + types: [opened, synchronize, reopened] + workflow_dispatch: + +env: + PUBLISH_BRANCH: 'deployment' + OKAPI_URL: 'https://snapshot-dev-okapi.folio-dev.indexdata.com' + OKAPI_SECRET_USER: "${{ secrets.SNAPSHOT_DEV_OKAPI_USER }}" + OKAPI_SECRET_PASSWORD: "${{ secrets.SNAPSHOT_DEV_OKAPI_PASSWORD }}" + OK_SESSION: 'session1' + +jobs: + mvn-dev-build-deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + submodules: recursive + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: 'temurin' # Alternative distribution options are available. + + - name: Prepare okclient + run: git clone https://github.com/indexdata/okclient + + - name: Ensure OK and FOLIO login + # So do not proceed with other workflow steps if not available. + run: | + source okclient/ok.sh + OK -S ${{ env.OK_SESSION }} \ + -h ${{ env.OKAPI_URL }} \ + -t "supertenant" \ + -u ${{ env.OKAPI_SECRET_USER }} \ + -p ${{ env.OKAPI_SECRET_PASSWORD }} + OK -S ${{ env.OK_SESSION }} -x + + - name: Gather some variables + run: | + echo "MODULE_NAME=$(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout)" >> $GITHUB_ENV + echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + echo "CURRENT_BRANCH=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV + + - name: Set module version + run: | + echo "MODULE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)-${SHA_SHORT}" >> $GITHUB_ENV + + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Maven build + run: mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install org.jacoco:jacoco-maven-plugin:report + + - name: SQ analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=indexdata -Dsonar.projectKey=indexdata_${{ github.event.repository.name }} + + - name: Update ModuleDescriptor Id + run: | + if test -f "$MOD_DESCRIPTOR"; then + echo "Found $MOD_DESCRIPTOR" + cat <<< $(jq '.id = "${{ env.MODULE_NAME}}-${{ env.MODULE_VERSION }}"' $MOD_DESCRIPTOR) > $MOD_DESCRIPTOR + echo "MODULE_DESCRIPTOR=$MOD_DESCRIPTOR" >> $GITHUB_ENV + else + echo "Could not find $MOD_DESCRIPTOR" + exit 1 + fi + env: + MOD_DESCRIPTOR: './target/ModuleDescriptor.json' + + - name: Read ModuleDescriptor + id: moduleDescriptor + uses: juliangruber/read-file-action@v1 + with: + path: ${{ env.MODULE_DESCRIPTOR }} + + - name: Login to Index Data Docker Hub account + if: ${{ env.CURRENT_BRANCH == env.PUBLISH_BRANCH }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and publish Docker image + if: ${{ env.CURRENT_BRANCH == env.PUBLISH_BRANCH }} + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: indexdata/${{ env.MODULE_NAME }}:${{ env.MODULE_VERSION }},indexdata/${{ env.MODULE_NAME }}:latest + + - name: Publish ModuleDescriptor to Okapi + if: ${{ env.CURRENT_BRANCH == env.PUBLISH_BRANCH }} + run: | + source okclient/ok.sh + echo "Do login ..." + OK -S ${{ env.OK_SESSION }} \ + -h ${{ env.OKAPI_URL }} \ + -t "supertenant" \ + -u ${{ env.OKAPI_SECRET_USER }} \ + -p ${{ env.OKAPI_SECRET_PASSWORD }} + echo "Post the MD and report the response status ..." + OK -S ${{ env.OK_SESSION }} _/proxy/modules \ + -X post -f ${{ env.MODULE_DESCRIPTOR }} + declare -n NAMEREF_STATUS=${{ env.OK_SESSION }}_HTTPStatus + echo "Response status: $NAMEREF_STATUS" + echo "Do logout ..." + OK -S ${{ env.OK_SESSION }} -x + + - name: Print module version to job summary + run: | + echo "#### Module Version: ${{ env.MODULE_VERSION }}" >> $GITHUB_STEP_SUMMARY From f56ee44757e6852d2b2759a1d39da5cc23832fc9 Mon Sep 17 00:00:00 2001 From: David Crossley Date: Fri, 23 Aug 2024 19:51:51 +1000 Subject: [PATCH 02/17] Enable verbose debug both okclient and its curl DEVOPS-3370 --- .github/workflows/mvn-dev-build-deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/mvn-dev-build-deploy.yml b/.github/workflows/mvn-dev-build-deploy.yml index a05e2db06f..960c1f7753 100644 --- a/.github/workflows/mvn-dev-build-deploy.yml +++ b/.github/workflows/mvn-dev-build-deploy.yml @@ -115,6 +115,8 @@ jobs: source okclient/ok.sh echo "Do login ..." OK -S ${{ env.OK_SESSION }} \ + -v \ + -o '-v' \ -h ${{ env.OKAPI_URL }} \ -t "supertenant" \ -u ${{ env.OKAPI_SECRET_USER }} \ From d589efaa09cb5c0014cb9933e2a0f56e6b33bc8f Mon Sep 17 00:00:00 2001 From: David Crossley Date: Fri, 23 Aug 2024 20:05:44 +1000 Subject: [PATCH 03/17] Disable verbose okclient --- .github/workflows/mvn-dev-build-deploy.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/mvn-dev-build-deploy.yml b/.github/workflows/mvn-dev-build-deploy.yml index 960c1f7753..a05e2db06f 100644 --- a/.github/workflows/mvn-dev-build-deploy.yml +++ b/.github/workflows/mvn-dev-build-deploy.yml @@ -115,8 +115,6 @@ jobs: source okclient/ok.sh echo "Do login ..." OK -S ${{ env.OK_SESSION }} \ - -v \ - -o '-v' \ -h ${{ env.OKAPI_URL }} \ -t "supertenant" \ -u ${{ env.OKAPI_SECRET_USER }} \ From 086c70aa51c42db6de3c5409272af197fdac76c3 Mon Sep 17 00:00:00 2001 From: John Malconian Date: Wed, 28 Aug 2024 10:04:06 -0400 Subject: [PATCH 04/17] update kubectl action --- .github/workflows/k8s-deploy-latest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/k8s-deploy-latest.yml b/.github/workflows/k8s-deploy-latest.yml index 42f9b28419..fde161bd64 100644 --- a/.github/workflows/k8s-deploy-latest.yml +++ b/.github/workflows/k8s-deploy-latest.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Deploy latest to K8s - uses: actions-hub/kubectl@v1.21.2 + uses: actions-hub/kubectl@v1.29.3 env: KUBE_CONFIG: ${{ secrets.SNAPSHOT_DEV_SA_KUBECONFIG }} with: From bf8f9643d3be95c31c1616bfbed0e7794b4bce42 Mon Sep 17 00:00:00 2001 From: nielserik Date: Wed, 28 Aug 2024 16:37:24 +0200 Subject: [PATCH 05/17] CIRC-2136 Add support for floating collections --- .../org/folio/circulation/domain/Item.java | 48 ++- .../folio/circulation/domain/Location.java | 10 + .../folio/circulation/domain/UpdateItem.java | 7 +- .../storage/inventory/ItemRepository.java | 15 +- .../resources/CheckInByBarcodeResource.java | 3 + .../resources/CheckInProcessAdapter.java | 19 + .../storage/mappers/ItemMapper.java | 2 + .../storage/mappers/LocationMapper.java | 3 + .../loans/scenarios/FloatingItemsTests.java | 405 ++++++++++++++++++ .../api/support/builders/LocationBuilder.java | 50 ++- .../support/examples/LocationExamples.java | 13 + .../support/fixtures/LocationsFixture.java | 8 + .../InstanceRequestItemsComparerTests.java | 2 +- .../domain/LoanCheckInServiceTest.java | 4 +- .../domain/OverdueFineServiceTest.java | 2 +- .../inventory/ItemRepositoryTests.java | 3 +- .../circulation/rules/Text2DroolsTest.java | 1 + .../services/FeeFineFacadeTest.java | 2 +- 18 files changed, 565 insertions(+), 32 deletions(-) create mode 100644 src/test/java/api/loans/scenarios/FloatingItemsTests.java diff --git a/src/main/java/org/folio/circulation/domain/Item.java b/src/main/java/org/folio/circulation/domain/Item.java index c27a296ef1..9fa7a7e042 100644 --- a/src/main/java/org/folio/circulation/domain/Item.java +++ b/src/main/java/org/folio/circulation/domain/Item.java @@ -41,6 +41,9 @@ public class Item { @Getter private final String shelvingOrder; @NonNull private final Location permanentLocation; + + @Getter + private final Location floatDestinationLocation; private final ServicePoint inTransitDestinationServicePoint; private boolean changed; @@ -57,7 +60,7 @@ public static Item from(JsonObject representation) { } public Item(String id, JsonObject itemRepresentation, Location effectiveLocation, LastCheckIn lastCheckIn, CallNumberComponents callNumberComponents, - String shelvingOrder, Location permanentLocation, + String shelvingOrder, Location permanentLocation, Location floatDestinationLocation, ServicePoint inTransitDestinationServicePoint, boolean changed, Holdings holdings, Instance instance, MaterialType materialType, LoanType loanType, ItemDescription description) { @@ -69,6 +72,7 @@ public Item(String id, JsonObject itemRepresentation, Location effectiveLocation this.callNumberComponents = callNumberComponents; this.shelvingOrder = shelvingOrder; this.permanentLocation = permanentLocation; + this.floatDestinationLocation = floatDestinationLocation; this.inTransitDestinationServicePoint = inTransitDestinationServicePoint; this.changed = changed; this.holdings = holdings; @@ -348,59 +352,85 @@ public String getPermanentLocationId() { return firstNonBlank(permanentLocation.getId(), holdings.getPermanentLocationId()); } + public String getFloatDestinationLocationId() { + return floatDestinationLocation != null ? floatDestinationLocation.getId() : null; + } + + public boolean canFloatThroughCheckInServicePoint() { + return getLocation() != null && + getLocation().isFloatingCollection() + && getFloatDestinationLocation().getId() != null; + } + public Item withLocation(Location newLocation) { return new Item(this.id, this.itemRepresentation, newLocation, this.lastCheckIn, this.callNumberComponents, this.shelvingOrder, - this.permanentLocation, this.inTransitDestinationServicePoint, this.changed, + this.permanentLocation, this.floatDestinationLocation, + this.inTransitDestinationServicePoint, this.changed, this.holdings, this.instance, this.materialType, this.loanType, description); } public Item withMaterialType(@NonNull MaterialType materialType) { return new Item(this.id, this.itemRepresentation, this.location, this.lastCheckIn, this.callNumberComponents, this.shelvingOrder, - this.permanentLocation, this.inTransitDestinationServicePoint, this.changed, + this.permanentLocation, this.floatDestinationLocation, + this.inTransitDestinationServicePoint, this.changed, this.holdings, this.instance, materialType, this.loanType, this.description); } public Item withHoldings(@NonNull Holdings holdings) { return new Item(this.id, this.itemRepresentation, this.location, this.lastCheckIn, this.callNumberComponents, this.shelvingOrder, - this.permanentLocation, this.inTransitDestinationServicePoint, this.changed, + this.permanentLocation, this.floatDestinationLocation, + this.inTransitDestinationServicePoint, this.changed, holdings, this.instance, this.materialType, this.loanType, this.description); } public Item withInstance(@NonNull Instance instance) { return new Item(this.id, this.itemRepresentation, this.location, this.lastCheckIn, this.callNumberComponents, this.shelvingOrder, - this.permanentLocation, this.inTransitDestinationServicePoint, this.changed, + this.permanentLocation, this.floatDestinationLocation, + this.inTransitDestinationServicePoint, this.changed, this.holdings, instance, this.materialType, this.loanType, this.description); } public Item withLoanType(@NonNull LoanType loanType) { return new Item(this.id, this.itemRepresentation, this.location, this.lastCheckIn, this.callNumberComponents, this.shelvingOrder, - this.permanentLocation, this.inTransitDestinationServicePoint, this.changed, + this.permanentLocation, this.floatDestinationLocation, + this.inTransitDestinationServicePoint, this.changed, this.holdings, this.instance, this.materialType, loanType, this.description); } public Item withLastCheckIn(@NonNull LastCheckIn lastCheckIn) { return new Item(this.id, this.itemRepresentation, this.location, lastCheckIn, this.callNumberComponents, this.shelvingOrder, - this.permanentLocation, this.inTransitDestinationServicePoint, this.changed, + this.permanentLocation, this.floatDestinationLocation, + this.inTransitDestinationServicePoint, this.changed, this.holdings, this.instance, this.materialType, this.loanType, this.description); } public Item withPermanentLocation(Location permanentLocation) { return new Item(this.id, this.itemRepresentation, this.location, this.lastCheckIn, this.callNumberComponents, this.shelvingOrder, - permanentLocation, this.inTransitDestinationServicePoint, this.changed, + permanentLocation, this.floatDestinationLocation, + this.inTransitDestinationServicePoint, this.changed, + this.holdings, this.instance, this.materialType, this.loanType, this.description); + } + + public Item withFloatDestinationLocation(Location floatDestinationLocation) { + return new Item(this.id, this.itemRepresentation, this.location, + this.lastCheckIn, this.callNumberComponents, this.shelvingOrder, + this.permanentLocation, floatDestinationLocation, + this.inTransitDestinationServicePoint, this.changed, this.holdings, this.instance, this.materialType, this.loanType, this.description); } public Item withInTransitDestinationServicePoint(ServicePoint servicePoint) { return new Item(this.id, this.itemRepresentation, this.location, this.lastCheckIn, this.callNumberComponents, this.shelvingOrder, - this.permanentLocation, servicePoint, this.changed, this.holdings, + this.permanentLocation, this.floatDestinationLocation, + servicePoint, this.changed, this.holdings, this.instance, this.materialType, this.loanType, this.description); } diff --git a/src/main/java/org/folio/circulation/domain/Location.java b/src/main/java/org/folio/circulation/domain/Location.java index a052fc9a17..5143ae4af9 100644 --- a/src/main/java/org/folio/circulation/domain/Location.java +++ b/src/main/java/org/folio/circulation/domain/Location.java @@ -16,6 +16,7 @@ public class Location { String discoveryDisplayName; @NonNull Collection servicePointIds; UUID primaryServicePointId; + Boolean isFloatingCollection; @NonNull Institution institution; @NonNull Campus campus; @NonNull Library library; @@ -27,6 +28,7 @@ public static Location unknown() { public static Location unknown(String id) { return new Location(id, null, null, null, List.of(), null, + false, Institution.unknown(null), Campus.unknown(null), Library.unknown(null), ServicePoint.unknown()); } @@ -85,23 +87,31 @@ public ServicePoint getPrimaryServicePoint() { return primaryServicePoint; } + public boolean isFloatingCollection() { + return isFloatingCollection; + } + public Location withInstitution(Institution institution) { return new Location(id, name, code, discoveryDisplayName, servicePointIds, primaryServicePointId, + isFloatingCollection, institution, campus, library, primaryServicePoint); } public Location withCampus(Campus campus) { return new Location(id, name, code, discoveryDisplayName, servicePointIds, primaryServicePointId, + isFloatingCollection, institution, campus, library, primaryServicePoint); } public Location withLibrary(Library library) { return new Location(id, name, code, discoveryDisplayName, servicePointIds, primaryServicePointId, + isFloatingCollection, institution, campus, library, primaryServicePoint); } public Location withPrimaryServicePoint(ServicePoint servicePoint) { return new Location(id, name, code, discoveryDisplayName, servicePointIds, primaryServicePointId, + isFloatingCollection, institution, campus, library, servicePoint); } diff --git a/src/main/java/org/folio/circulation/domain/UpdateItem.java b/src/main/java/org/folio/circulation/domain/UpdateItem.java index 9ba0caa300..cbf459554a 100644 --- a/src/main/java/org/folio/circulation/domain/UpdateItem.java +++ b/src/main/java/org/folio/circulation/domain/UpdateItem.java @@ -57,11 +57,12 @@ private Result changeItemOnCheckIn(Item item, Request request, UUID checkI return changeItemWithOutstandingRequest(item, request, checkInServicePointId); } else { if(Optional.ofNullable(item.getLocation()) - .map(location -> location.homeLocationIsServedBy(checkInServicePointId)) + .map(location -> + location.homeLocationIsServedBy(checkInServicePointId) + || (item.canFloatThroughCheckInServicePoint())) .orElse(false)) { return succeeded(item.available()); - } - else { + } else { return succeeded(item.inTransitToHome()); } } diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepository.java index 3603768dda..9a5cb54d5d 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepository.java @@ -4,6 +4,7 @@ import static java.util.concurrent.CompletableFuture.supplyAsync; import static java.util.function.Function.identity; import static org.folio.circulation.domain.ItemStatus.AVAILABLE; +import static org.folio.circulation.domain.ItemStatus.IN_TRANSIT; import static org.folio.circulation.domain.MultipleRecords.CombinationMatchers.matchRecordsById; import static org.folio.circulation.domain.representations.ItemProperties.LAST_CHECK_IN; import static org.folio.circulation.domain.representations.ItemProperties.STATUS_PROPERTY; @@ -91,6 +92,7 @@ public CompletableFuture> updateItem(Item item) { log.debug("updateItem:: parameters item: {}", item); final String IN_TRANSIT_DESTINATION_SERVICE_POINT_ID = "inTransitDestinationServicePointId"; + final String TEMPORARY_LOCATION_ID = "temporaryLocationId"; if (item == null) { log.info("updateItem:: item is null"); @@ -108,8 +110,14 @@ public CompletableFuture> updateItem(Item item) { new JsonObject().put("name", item.getStatus().getValue())); remove(updatedItemRepresentation, IN_TRANSIT_DESTINATION_SERVICE_POINT_ID); - write(updatedItemRepresentation, IN_TRANSIT_DESTINATION_SERVICE_POINT_ID, - item.getInTransitDestinationServicePointId()); + if (item.isInStatus(IN_TRANSIT)) { + write(updatedItemRepresentation, IN_TRANSIT_DESTINATION_SERVICE_POINT_ID, + item.getInTransitDestinationServicePointId()); + } else if (item.canFloatThroughCheckInServicePoint()) { + remove(updatedItemRepresentation, TEMPORARY_LOCATION_ID); + write(updatedItemRepresentation, TEMPORARY_LOCATION_ID, + item.getFloatDestinationLocationId()); + } final var lastCheckIn = item.getLastCheckIn(); @@ -176,7 +184,8 @@ private CompletableFuture>> fetchLocations( return result.combineAfter(this::fetchLocations, (items, locations) -> items .combineRecords(locations, Item::getPermanentLocationId, Item::withPermanentLocation, null) - .combineRecords(locations, Item::getEffectiveLocationId, Item::withLocation, null)); + .combineRecords(locations, Item::getEffectiveLocationId, Item::withLocation, null) + .combineRecords(locations, Item::getFloatDestinationLocationId, Item::withFloatDestinationLocation, null)); } private CompletableFuture>> fetchLocations( diff --git a/src/main/java/org/folio/circulation/resources/CheckInByBarcodeResource.java b/src/main/java/org/folio/circulation/resources/CheckInByBarcodeResource.java index a8e75f09ee..be2089da03 100644 --- a/src/main/java/org/folio/circulation/resources/CheckInByBarcodeResource.java +++ b/src/main/java/org/folio/circulation/resources/CheckInByBarcodeResource.java @@ -105,6 +105,9 @@ private void checkIn(RoutingContext routingContext) { .thenComposeAsync(checkInLoan -> checkInLoan.combineAfter( processAdapter::updateRequestQueue, CheckInContext::withRequestQueue)) .thenComposeAsync(r -> r.after(processAdapter::findFulfillableRequest)) + .thenComposeAsync(checkInContextResult -> + checkInContextResult.combineAfter(processAdapter::findFloatingDestination, + CheckInContext::withItemAndUpdatedLoan)) .thenComposeAsync(updateRequestQueueResult -> updateRequestQueueResult.combineAfter( processAdapter::updateItem, CheckInContext::withItemAndUpdatedLoan)) .thenApply(handleItemStatus -> handleItemStatus.next( diff --git a/src/main/java/org/folio/circulation/resources/CheckInProcessAdapter.java b/src/main/java/org/folio/circulation/resources/CheckInProcessAdapter.java index 9e8b396bb4..88bda6bfb9 100644 --- a/src/main/java/org/folio/circulation/resources/CheckInProcessAdapter.java +++ b/src/main/java/org/folio/circulation/resources/CheckInProcessAdapter.java @@ -12,6 +12,7 @@ import org.apache.logging.log4j.Logger; import org.folio.circulation.domain.CheckInContext; import org.folio.circulation.domain.Item; +import org.folio.circulation.domain.Location; import org.folio.circulation.domain.Loan; import org.folio.circulation.domain.LoanCheckInService; import org.folio.circulation.domain.OverdueFineService; @@ -27,6 +28,7 @@ import org.folio.circulation.infrastructure.storage.feesandfines.FeeFineOwnerRepository; import org.folio.circulation.infrastructure.storage.feesandfines.FeeFineRepository; import org.folio.circulation.infrastructure.storage.inventory.ItemRepository; +import org.folio.circulation.infrastructure.storage.inventory.LocationRepository; import org.folio.circulation.infrastructure.storage.loans.LoanPolicyRepository; import org.folio.circulation.infrastructure.storage.loans.LoanRepository; import org.folio.circulation.infrastructure.storage.loans.OverdueFinePolicyRepository; @@ -57,6 +59,7 @@ class CheckInProcessAdapter { private final UpdateRequestQueue requestQueueUpdate; private final LoanRepository loanRepository; private final ServicePointRepository servicePointRepository; + private final LocationRepository locationRepository; private final UserRepository userRepository; private final AddressTypeRepository addressTypeRepository; private final LogCheckInService logCheckInService; @@ -75,6 +78,7 @@ class CheckInProcessAdapter { RequestQueueRepository requestQueueRepository, UpdateItem updateItem, UpdateRequestQueue requestQueueUpdate, LoanRepository loanRepository, ServicePointRepository servicePointRepository, + LocationRepository locationRepository, UserRepository userRepository, AddressTypeRepository addressTypeRepository, LogCheckInService logCheckInService, @@ -93,6 +97,7 @@ class CheckInProcessAdapter { this.requestQueueUpdate = requestQueueUpdate; this.loanRepository = loanRepository; this.servicePointRepository = servicePointRepository; + this.locationRepository = locationRepository; this.userRepository = userRepository; this.addressTypeRepository = addressTypeRepository; this.logCheckInService = logCheckInService; @@ -134,6 +139,7 @@ public static CheckInProcessAdapter newInstance(Clients clients, requestQueueRepository), loanRepository, new ServicePointRepository(clients), + LocationRepository.using(clients), userRepository, new AddressTypeRepository(clients), new LogCheckInService(clients), @@ -296,4 +302,17 @@ CompletableFuture> findFulfillableRequest(CheckInContext return requestQueueService.findRequestFulfillableByItem(context.getItem(), context.getRequestQueue()) .thenApply(r -> r.map(context::withHighestPriorityFulfillableRequest)); } + + CompletableFuture> findFloatingDestination(CheckInContext context) { + Item item = context.getItem(); + if (item.getLocation().isFloatingCollection()) { + return locationRepository.fetchLocationsForServicePoint(context.getCheckInServicePointId().toString()) + .thenApply(rLocations -> rLocations.map(locations -> locations.stream() + .filter(Location::isFloatingCollection).findFirst() + .map(item::withFloatDestinationLocation).orElse(item))); + } else { + return Result.ofAsync(item); + } + } + } diff --git a/src/main/java/org/folio/circulation/storage/mappers/ItemMapper.java b/src/main/java/org/folio/circulation/storage/mappers/ItemMapper.java index aa56840d65..9d4174a41d 100644 --- a/src/main/java/org/folio/circulation/storage/mappers/ItemMapper.java +++ b/src/main/java/org/folio/circulation/storage/mappers/ItemMapper.java @@ -20,6 +20,7 @@ import io.vertx.core.json.JsonObject; public class ItemMapper { + public Item toDomain(JsonObject representation) { return new Item(getProperty(representation, "id"), representation, Location.unknown(getProperty(representation, "effectiveLocationId")), @@ -27,6 +28,7 @@ public Item toDomain(JsonObject representation) { CallNumberComponents.fromItemJson(representation), getProperty(representation, "effectiveShelvingOrder"), Location.unknown(getProperty(representation, "permanentLocationId")), + Location.unknown(), getInTransitServicePoint(representation), false, Holdings.unknown(getProperty(representation, "holdingsRecordId")), Instance.unknown(), diff --git a/src/main/java/org/folio/circulation/storage/mappers/LocationMapper.java b/src/main/java/org/folio/circulation/storage/mappers/LocationMapper.java index 105267b7bb..bc004c233e 100644 --- a/src/main/java/org/folio/circulation/storage/mappers/LocationMapper.java +++ b/src/main/java/org/folio/circulation/storage/mappers/LocationMapper.java @@ -2,6 +2,8 @@ import static org.folio.circulation.support.json.JsonPropertyFetcher.getArrayProperty; import static org.folio.circulation.support.json.JsonPropertyFetcher.getProperty; +import static org.folio.circulation.support.json.JsonPropertyFetcher.getBooleanProperty; + import java.util.Collection; import java.util.Optional; @@ -24,6 +26,7 @@ public Location toDomain(JsonObject representation) { getProperty(representation, "discoveryDisplayName"), getServicePointIds(representation), getPrimaryServicePointId(representation), + getBooleanProperty(representation, "isFloatingCollection"), Institution.unknown(getProperty(representation, "institutionId")), Campus.unknown(getProperty(representation, "campusId")), Library.unknown(getProperty(representation, "libraryId")), diff --git a/src/test/java/api/loans/scenarios/FloatingItemsTests.java b/src/test/java/api/loans/scenarios/FloatingItemsTests.java new file mode 100644 index 0000000000..669144a8ee --- /dev/null +++ b/src/test/java/api/loans/scenarios/FloatingItemsTests.java @@ -0,0 +1,405 @@ +package api.loans.scenarios; + +import api.support.APITests; +import api.support.CheckInByBarcodeResponse; +import api.support.builders.CheckInByBarcodeRequestBuilder; +import api.support.builders.LocationBuilder; +import api.support.fixtures.ItemExamples; +import api.support.http.IndividualResource; +import api.support.http.ItemResource; +import io.vertx.core.json.JsonObject; +import org.folio.circulation.support.utils.ClockUtil; +import org.hamcrest.CoreMatchers; +import org.hamcrest.core.IsNull; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static api.support.matchers.UUIDMatcher.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class FloatingItemsTests extends APITests { + + @Test + void willSetFloatingItemsTemporaryLocationToFloatingCollectionAtServicePoint() { + + // Floating collection served by service point 'cd1'. + final IndividualResource floatingCollection = locationsFixture.floatingCollection(); + + // Another floating collection serviced by another service point + final IndividualResource servicePointTwo = servicePointsFixture.cd2(); + IndividualResource otherFloatingCollection = locationsFixture.createLocation( + new LocationBuilder() + .withName("Floating collection 2") + .forInstitution(UUID.randomUUID()) + .forCampus(UUID.randomUUID()) + .forLibrary(UUID.randomUUID()) + .withCode("FLOAT2") + .isFloatingCollection(true) + .withPrimaryServicePoint(servicePointTwo.getId())); + + final IndividualResource james = usersFixture.james(); + + final IndividualResource holdingsInFloatingLocation = + holdingsFixture.createHoldingsRecord(UUID.randomUUID(), floatingCollection.getId()); + + IndividualResource nod = itemsClient.create(ItemExamples.basedUponNod( + materialTypesFixture.book().getId(), + loanTypesFixture.canCirculate().getId()) + .withBarcode("565578437802") + .forHolding(holdingsInFloatingLocation.getId())); + + final IndividualResource loan = checkOutFixture.checkOutByBarcode(nod, james); + + final CheckInByBarcodeResponse checkInResponse = checkInFixture.checkInByBarcode( + new CheckInByBarcodeRequestBuilder().forItem(nod).at(servicePointTwo.getId())); + + JsonObject itemRepresentation = checkInResponse.getItem(); + + assertThat("item should be present in response", + itemRepresentation, IsNull.notNullValue()); + + assertThat("ID should be included for item", + itemRepresentation.getString("id"), is(nod.getId())); + + assertThat("barcode should be included for item", + itemRepresentation.getString("barcode"), CoreMatchers.is("565578437802")); + + assertThat("item status should be 'Available'", + itemRepresentation.getJsonObject("status").getString("name"), CoreMatchers.is("Available")); + + assertThat("available item should not have a destination", + itemRepresentation.containsKey("inTransitDestinationServicePointId"), + CoreMatchers.is(false)); + + JsonObject loanRepresentation = checkInResponse.getLoan(); + + assertThat("closed loan should be present in response", + loanRepresentation, IsNull.notNullValue()); + + assertThat("item (in loan) should not have a destination", + loanRepresentation.getJsonObject("item") + .containsKey("inTransitDestinationServicePointId"), CoreMatchers.is(false)); + + JsonObject updatedNod = itemsClient.getById(nod.getId()).getJson(); + + assertThat("stored item status should be 'Available'", + updatedNod.getJsonObject("status").getString("name"), CoreMatchers.is("Available")); + + assertThat("available item in storage should not have a destination", + updatedNod.containsKey("inTransitDestinationServicePointId"), CoreMatchers.is(false)); + + assertThat("available item's temporary location set to other floating collection", + updatedNod.getString("temporaryLocationId"), CoreMatchers.is(otherFloatingCollection.getId().toString())); + + final JsonObject storedLoan = loansStorageClient.getById(loan.getId()).getJson(); + + assertThat("stored loan status should be 'Closed'", + storedLoan.getJsonObject("status").getString("name"), CoreMatchers.is("Closed")); + + assertThat("item status snapshot in storage should be 'Available'", + storedLoan.getString("itemStatus"), CoreMatchers.is("Available")); + + assertThat("Checkin Service Point Id should be stored", + storedLoan.getString("checkinServicePointId"), is(servicePointTwo.getId())); + + } + + @Test + void willPutFloatingItemInTransitWhenCheckInServicePointServesNoFloatingCollection() { + final IndividualResource floatingCollection = locationsFixture.floatingCollection(); + + final IndividualResource servicePointTwo = servicePointsFixture.cd2(); + + // Location without floating collection, different service point + locationsFixture.createLocation( + new LocationBuilder() + .withName("Location 2") + .forInstitution(UUID.randomUUID()) + .forCampus(UUID.randomUUID()) + .forLibrary(UUID.randomUUID()) + .withCode("FLOAT2") + .isFloatingCollection(false) + .withPrimaryServicePoint(servicePointTwo.getId())); + + final IndividualResource james = usersFixture.james(); + + final IndividualResource holdingsInFloatingLocation = + holdingsFixture.createHoldingsRecord(UUID.randomUUID(), floatingCollection.getId()); + + IndividualResource nod = itemsClient.create(ItemExamples.basedUponNod( + materialTypesFixture.book().getId(), + loanTypesFixture.canCirculate().getId()) + .withBarcode("565578437802") + .forHolding(holdingsInFloatingLocation.getId())); + + final IndividualResource loan = checkOutFixture.checkOutByBarcode(nod, james); + + final CheckInByBarcodeResponse checkInResponse = checkInFixture.checkInByBarcode( + new CheckInByBarcodeRequestBuilder() + .forItem(nod) + .at(servicePointTwo.getId())); + + JsonObject itemRepresentation = checkInResponse.getItem(); + + System.out.println(itemsClient.get(nod).getJson().encodePrettily()); + + assertThat("item should be present in response", + itemRepresentation, IsNull.notNullValue()); + + assertThat("ID should be included for item", + itemRepresentation.getString("id"), is(nod.getId())); + + assertThat("barcode should be included for item", + itemRepresentation.getString("barcode"), CoreMatchers.is("565578437802")); + + assertThat("item status should be 'In transit'", + itemRepresentation.getJsonObject("status").getString("name"), CoreMatchers.is("In transit")); + + assertThat("item should have a destination", + itemRepresentation.containsKey("inTransitDestinationServicePointId"), + CoreMatchers.is(true)); + + JsonObject loanRepresentation = checkInResponse.getLoan(); + + assertThat("closed loan should be present in response", + loanRepresentation, IsNull.notNullValue()); + + assertThat("item (in loan) should have a destination", + loanRepresentation.getJsonObject("item") + .containsKey("inTransitDestinationServicePointId"), CoreMatchers.is(true)); + + JsonObject updatedNod = itemsClient.getById(nod.getId()).getJson(); + + assertThat("stored item status should be 'In transit'", + updatedNod.getJsonObject("status").getString("name"), CoreMatchers.is("In transit")); + + assertThat("item in storage should have a destination", + updatedNod.containsKey("inTransitDestinationServicePointId"), CoreMatchers.is(true)); + + final JsonObject storedLoan = loansStorageClient.getById(loan.getId()).getJson(); + + assertThat("stored loan status should be 'Closed'", + storedLoan.getJsonObject("status").getString("name"), CoreMatchers.is("Closed")); + + assertThat("item status snapshot in storage should be 'In transit'", + storedLoan.getString("itemStatus"), CoreMatchers.is("In transit")); + + assertThat("CheckIn Service Point id should be stored", + storedLoan.getString("checkinServicePointId"), is(servicePointTwo.getId())); + + } + + @Test + void willPutFloatingItemInTransitWhenHoldRequestWasIssuedAtDifferentServicePoint() { + final IndividualResource floatingCollection = locationsFixture.floatingCollection(); + + final IndividualResource servicePointTwo = servicePointsFixture.cd3(); + final IndividualResource pickUpServicePoint = servicePointsFixture.cd2(); + + + // Floating collection (location), different service point + locationsFixture.createLocation( + new LocationBuilder() + .withName("Floating collection 2") + .forInstitution(UUID.randomUUID()) + .forCampus(UUID.randomUUID()) + .forLibrary(UUID.randomUUID()) + .withCode("FLOAT2") + .isFloatingCollection(true) + .withPrimaryServicePoint(servicePointTwo.getId())); + + // Location without floating collection, with pickup service point + locationsFixture.createLocation( + new LocationBuilder() + .withName("Location 3") + .forInstitution(UUID.randomUUID()) + .forCampus(UUID.randomUUID()) + .forLibrary(UUID.randomUUID()) + .withCode("LOC3") + .isFloatingCollection(false) + .withPrimaryServicePoint(pickUpServicePoint.getId())); + + + final IndividualResource james = usersFixture.james(); + final IndividualResource jessica = usersFixture.jessica(); + + final IndividualResource instance = instancesFixture.basedUponDunkirk(); + final IndividualResource holdingsInFloatingLocation = + holdingsFixture.createHoldingsRecord(UUID.randomUUID(), floatingCollection.getId()); + + IndividualResource nod = itemsClient.create(ItemExamples.basedUponNod( + materialTypesFixture.book().getId(), + loanTypesFixture.canCirculate().getId()) + .withBarcode("565578437802") + .forHolding(holdingsInFloatingLocation.getId())); + + // Check out from floating collection + final IndividualResource loan = checkOutFixture.checkOutByBarcode(nod, james); + + requestsFixture.placeItemLevelHoldShelfRequest( + new ItemResource(nod, holdingsInFloatingLocation,instance), jessica, ClockUtil.getZonedDateTime(), pickUpServicePoint.getId()); + + // Check in at service point serving a floating collection. + final CheckInByBarcodeResponse checkInResponse = checkInFixture.checkInByBarcode( + new CheckInByBarcodeRequestBuilder() + .forItem(nod) + .at(servicePointTwo.getId())); + + JsonObject itemRepresentation = checkInResponse.getItem(); + + System.out.println(itemsClient.get(nod).getJson().encodePrettily()); + + assertThat("item should be present in response", + itemRepresentation, IsNull.notNullValue()); + + assertThat("ID should be included for item", + itemRepresentation.getString("id"), is(nod.getId())); + + assertThat("barcode should be included for item", + itemRepresentation.getString("barcode"), CoreMatchers.is("565578437802")); + + assertThat("item status should be 'In transit'", + itemRepresentation.getJsonObject("status").getString("name"), CoreMatchers.is("In transit")); + + assertThat("available item should have a destination", + itemRepresentation.containsKey("inTransitDestinationServicePointId"), + CoreMatchers.is(true)); + + JsonObject loanRepresentation = checkInResponse.getLoan(); + + assertThat("closed loan should be present in response", + loanRepresentation, IsNull.notNullValue()); + + assertThat("in transit item in storage should have a destination", + loanRepresentation.getJsonObject("item") + .getString("inTransitDestinationServicePointId"), + CoreMatchers.is(pickUpServicePoint.getId().toString())); + + assertThat("item (in loan) should have a destination", + loanRepresentation.getJsonObject("item") + .containsKey("inTransitDestinationServicePointId"), CoreMatchers.is(true)); + + JsonObject updatedNod = itemsClient.getById(nod.getId()).getJson(); + + assertThat("stored item status should be 'In transit'", + updatedNod.getJsonObject("status").getString("name"), CoreMatchers.is("In transit")); + + assertThat("in transit item in storage should have a destination", + updatedNod.getString("inTransitDestinationServicePointId"), + is(pickUpServicePoint.getId())); + + final JsonObject storedLoan = loansStorageClient.getById(loan.getId()).getJson(); + + assertThat("stored loan status should be 'Closed'", + storedLoan.getJsonObject("status").getString("name"), CoreMatchers.is("Closed")); + + assertThat("item status snapshot in storage should be 'In transit'", + storedLoan.getString("itemStatus"), CoreMatchers.is("In transit")); + + assertThat("Checkin Service Point Id should be stored", + storedLoan.getString("checkinServicePointId"), is(servicePointTwo.getId())); + + } + + + @Test + void willPutItemInTransitIfFloatingLocationWasOverriddenByNonFloatingLocation() { + // floating collection served by service point cd1. + final IndividualResource floatingCollection = locationsFixture.floatingCollection(); + + final IndividualResource servicePointTwo = servicePointsFixture.cd3(); + final IndividualResource pickUpServicePoint = servicePointsFixture.cd2(); + + // Floating collection (location), different service point + locationsFixture.createLocation( + new LocationBuilder() + .withName("Floating collection 2") + .forInstitution(UUID.randomUUID()) + .forCampus(UUID.randomUUID()) + .forLibrary(UUID.randomUUID()) + .withCode("FLOAT2") + .isFloatingCollection(true) + .withPrimaryServicePoint(servicePointTwo.getId())); + + // Location without floating collection, with pickup service point + IndividualResource location3 = locationsFixture.createLocation( + new LocationBuilder() + .withName("Location 3") + .forInstitution(UUID.randomUUID()) + .forCampus(UUID.randomUUID()) + .forLibrary(UUID.randomUUID()) + .withCode("LOC3") + .isFloatingCollection(false) + .withPrimaryServicePoint(pickUpServicePoint.getId())); + + final IndividualResource holdingsInFloatingLocation = + holdingsFixture.createHoldingsRecord(UUID.randomUUID(), floatingCollection.getId()); + + IndividualResource nod = itemsClient.create(ItemExamples.basedUponNod( + materialTypesFixture.book().getId(), + loanTypesFixture.canCirculate().getId()) + .withPermanentLocation(location3) + .withBarcode("565578437802") + .forHolding(holdingsInFloatingLocation.getId())); + + final IndividualResource james = usersFixture.james(); + + final IndividualResource loan = checkOutFixture.checkOutByBarcode(nod, james); + + final CheckInByBarcodeResponse checkInResponse = checkInFixture.checkInByBarcode( + new CheckInByBarcodeRequestBuilder() + .forItem(nod) + .at(servicePointTwo.getId())); + + JsonObject itemRepresentation = checkInResponse.getItem(); + + System.out.println(itemsClient.get(nod).getJson().encodePrettily()); + + assertThat("item should be present in response", + itemRepresentation, IsNull.notNullValue()); + + assertThat("ID should be included for item", + itemRepresentation.getString("id"), is(nod.getId())); + + assertThat("barcode should be included for item", + itemRepresentation.getString("barcode"), CoreMatchers.is("565578437802")); + + assertThat("item status should be 'In transit'", + itemRepresentation.getJsonObject("status").getString("name"), CoreMatchers.is("In transit")); + + assertThat("available item should have a destination", + itemRepresentation.containsKey("inTransitDestinationServicePointId"), + CoreMatchers.is(true)); + + JsonObject loanRepresentation = checkInResponse.getLoan(); + + assertThat("closed loan should be present in response", + loanRepresentation, IsNull.notNullValue()); + + assertThat("item (in loan) should have a destination", + loanRepresentation.getJsonObject("item") + .containsKey("inTransitDestinationServicePointId"), CoreMatchers.is(true)); + + JsonObject updatedNod = itemsClient.getById(nod.getId()).getJson(); + + assertThat("stored item status should be 'In transit'", + updatedNod.getJsonObject("status").getString("name"), CoreMatchers.is("In transit")); + + assertThat("in transit item in storage should have a destination", + updatedNod.getString("inTransitDestinationServicePointId"), + is(pickUpServicePoint.getId())); + + final JsonObject storedLoan = loansStorageClient.getById(loan.getId()).getJson(); + + assertThat("stored loan status should be 'Closed'", + storedLoan.getJsonObject("status").getString("name"), CoreMatchers.is("Closed")); + + assertThat("item status snapshot in storage should be 'In transit'", + storedLoan.getString("itemStatus"), CoreMatchers.is("In transit")); + + assertThat("Checkin Service Point Id should be stored", + storedLoan.getString("checkinServicePointId"), is(servicePointTwo.getId())); + + } +} diff --git a/src/test/java/api/support/builders/LocationBuilder.java b/src/test/java/api/support/builders/LocationBuilder.java index c12a2cc9a2..496d8c2f62 100644 --- a/src/test/java/api/support/builders/LocationBuilder.java +++ b/src/test/java/api/support/builders/LocationBuilder.java @@ -22,9 +22,10 @@ public class LocationBuilder extends JsonBuilder implements Builder { private final UUID primaryServicePointId; private final Set otherServicePointIds; private final String effectiveLocationPrimaryServicePointName; + private final Boolean isFloatingCollection; public LocationBuilder() { - this(null, null, null, null, null, null, null, new HashSet<>(), null); + this(null, null, null, null, null, null, null, new HashSet<>(), null, false); } private LocationBuilder( @@ -36,7 +37,8 @@ private LocationBuilder( UUID libraryId, UUID primaryServicePointId, Set otherServicePointIds, - String effectiveLocationPrimaryServicePointName) { + String effectiveLocationPrimaryServicePointName, + boolean isFloatingCollection) { if (otherServicePointIds == null) { otherServicePointIds = new HashSet<>(); @@ -51,6 +53,7 @@ private LocationBuilder( this.primaryServicePointId = primaryServicePointId; this.otherServicePointIds = otherServicePointIds; this.effectiveLocationPrimaryServicePointName = effectiveLocationPrimaryServicePointName; + this.isFloatingCollection = isFloatingCollection; } @Override @@ -75,6 +78,7 @@ public JsonObject create() { write(representation, "servicePointIds", mappedServicePointIds); write(representation, "effectiveLocationPrimaryServicePointName", effectiveLocationPrimaryServicePointName); } + write(representation, "isFloatingCollection", isFloatingCollection); return representation; } @@ -89,7 +93,8 @@ public LocationBuilder withName(String name) { this.libraryId, this.primaryServicePointId, this.otherServicePointIds, - this.effectiveLocationPrimaryServicePointName); + this.effectiveLocationPrimaryServicePointName, + this.isFloatingCollection); } public LocationBuilder withCode(String code) { @@ -102,7 +107,8 @@ public LocationBuilder withCode(String code) { this.libraryId, this.primaryServicePointId, this.otherServicePointIds, - this.effectiveLocationPrimaryServicePointName); + this.effectiveLocationPrimaryServicePointName, + this.isFloatingCollection); } public LocationBuilder forInstitution(UUID institutionId) { @@ -115,7 +121,8 @@ public LocationBuilder forInstitution(UUID institutionId) { this.libraryId, this.primaryServicePointId, this.otherServicePointIds, - this.effectiveLocationPrimaryServicePointName); + this.effectiveLocationPrimaryServicePointName, + this.isFloatingCollection); } public LocationBuilder forCampus(UUID campusId) { @@ -128,7 +135,8 @@ public LocationBuilder forCampus(UUID campusId) { this.libraryId, this.primaryServicePointId, this.otherServicePointIds, - this.effectiveLocationPrimaryServicePointName); + this.effectiveLocationPrimaryServicePointName, + this.isFloatingCollection); } public LocationBuilder forLibrary(UUID libraryId) { @@ -141,7 +149,8 @@ public LocationBuilder forLibrary(UUID libraryId) { libraryId, this.primaryServicePointId, this.otherServicePointIds, - this.effectiveLocationPrimaryServicePointName); + this.effectiveLocationPrimaryServicePointName, + this.isFloatingCollection); } public LocationBuilder withPrimaryServicePoint(UUID primaryServicePointId) { @@ -154,7 +163,8 @@ public LocationBuilder withPrimaryServicePoint(UUID primaryServicePointId) { this.libraryId, primaryServicePointId, this.otherServicePointIds, - this.effectiveLocationPrimaryServicePointName) + this.effectiveLocationPrimaryServicePointName, + this.isFloatingCollection) .servedBy(primaryServicePointId); } @@ -168,7 +178,8 @@ public LocationBuilder withDiscoveryDisplayName(String discoveryDisplayName) { this.libraryId, this.primaryServicePointId, this.otherServicePointIds, - this.effectiveLocationPrimaryServicePointName); + this.effectiveLocationPrimaryServicePointName, + this.isFloatingCollection); } public LocationBuilder withEffectiveLocationPrimaryServicePointName(String effectiveLocationPrimaryServicePointName) { @@ -181,10 +192,26 @@ public LocationBuilder withEffectiveLocationPrimaryServicePointName(String effec this.libraryId, primaryServicePointId, this.otherServicePointIds, - effectiveLocationPrimaryServicePointName) + effectiveLocationPrimaryServicePointName, + this.isFloatingCollection) .servedBy(primaryServicePointId); } + public LocationBuilder isFloatingCollection(boolean isFloatingCollection) { + return new LocationBuilder( + this.name, + this.code, + this.discoveryDisplayName, + this.institutionId, + this.campusId, + this.libraryId, + primaryServicePointId, + this.otherServicePointIds, + this.effectiveLocationPrimaryServicePointName, + isFloatingCollection + ); + } + public LocationBuilder servedBy(UUID servicePointId) { final HashSet servicePoints = new HashSet<>(otherServicePointIds); @@ -207,6 +234,7 @@ public LocationBuilder servedBy(Set servicePoints) { this.libraryId, this.primaryServicePointId, newServicePointIds, - this.effectiveLocationPrimaryServicePointName); + this.effectiveLocationPrimaryServicePointName, + this.isFloatingCollection); } } diff --git a/src/test/java/api/support/examples/LocationExamples.java b/src/test/java/api/support/examples/LocationExamples.java index 28811230d3..1b3b2f2c27 100644 --- a/src/test/java/api/support/examples/LocationExamples.java +++ b/src/test/java/api/support/examples/LocationExamples.java @@ -109,4 +109,17 @@ public LocationBuilder fourthFloorLocation() { .withPrimaryServicePoint(primaryServicePointId) .servedBy(otherServicePointIds); } + + public LocationBuilder floatingCollection() { + return new LocationBuilder() + .withName("Floating collection") + .withCode("NU/JC/DL/FC") + .withDiscoveryDisplayName("Floating collection") + .forInstitution(institutionId) + .forCampus(jubileeCampusId) + .forLibrary(djanoglyLibraryId) + .withPrimaryServicePoint(primaryServicePointId) + .isFloatingCollection(true); + } + } diff --git a/src/test/java/api/support/fixtures/LocationsFixture.java b/src/test/java/api/support/fixtures/LocationsFixture.java index 53b947260e..27a2fec2c3 100644 --- a/src/test/java/api/support/fixtures/LocationsFixture.java +++ b/src/test/java/api/support/fixtures/LocationsFixture.java @@ -68,6 +68,14 @@ public IndividualResource thirdFloor() { return locationRecordCreator.createIfAbsent( locationExamples.thirdFloor()); } + + public IndividualResource floatingCollection() { + final LocationExamples locationExamples = getLocationExamples(); + + return locationRecordCreator.createIfAbsent( + locationExamples.floatingCollection()); + } + public IndividualResource fourthServicePoint() { final LocationExamples locationExamples = getLocationExamplesForCd4(); diff --git a/src/test/java/org/folio/circulation/domain/InstanceRequestItemsComparerTests.java b/src/test/java/org/folio/circulation/domain/InstanceRequestItemsComparerTests.java index 81cf284ec4..fc93f354b9 100644 --- a/src/test/java/org/folio/circulation/domain/InstanceRequestItemsComparerTests.java +++ b/src/test/java/org/folio/circulation/domain/InstanceRequestItemsComparerTests.java @@ -251,7 +251,7 @@ private static Item createItem(UUID withServicePointId) { if (withServicePointId != null) { location = new Location(null, null, null, null, - List.of(withServicePointId), null, + List.of(withServicePointId), null, false, Institution.unknown(null), Campus.unknown(null), Library.unknown(null), ServicePoint.unknown(null)); } diff --git a/src/test/java/org/folio/circulation/domain/LoanCheckInServiceTest.java b/src/test/java/org/folio/circulation/domain/LoanCheckInServiceTest.java index 87024996d7..be02c84890 100644 --- a/src/test/java/org/folio/circulation/domain/LoanCheckInServiceTest.java +++ b/src/test/java/org/folio/circulation/domain/LoanCheckInServiceTest.java @@ -47,7 +47,7 @@ void isInHouseUseWhenNonPrimaryServicePointServesHomeLocation() { @NonNull UUID homeServicePointId = UUID.randomUUID(); Item item = Item.from(itemRepresentation) .withLocation(new Location(null, null, null, null, - List.of(checkInServicePoint), homeServicePointId, Institution.unknown(), + List.of(checkInServicePoint), homeServicePointId, false, Institution.unknown(), Campus.unknown(), Library.unknown(), ServicePoint.unknown(homeServicePointId.toString()))); @@ -106,7 +106,7 @@ void isNotInHouseUseWhenServicePointDoesNotServeHomeLocation() { private Location locationPrimarilyServing(@NonNull UUID homeServicePointId) { return new Location(null, null, null, null, - List.of(UUID.randomUUID()), homeServicePointId, Institution.unknown(), + List.of(UUID.randomUUID()), homeServicePointId, false, Institution.unknown(), Campus.unknown(), Library.unknown(), ServicePoint.unknown(homeServicePointId.toString())); } diff --git a/src/test/java/org/folio/circulation/domain/OverdueFineServiceTest.java b/src/test/java/org/folio/circulation/domain/OverdueFineServiceTest.java index 9856d01ea8..5c7a44d997 100644 --- a/src/test/java/org/folio/circulation/domain/OverdueFineServiceTest.java +++ b/src/test/java/org/folio/circulation/domain/OverdueFineServiceTest.java @@ -611,7 +611,7 @@ private Item createItem() { return Item.from(item) .withLocation(new Location(null, LOCATION_NAME, null, null, emptyList(), - SERVICE_POINT_ID, Institution.unknown(), Campus.unknown(), Library.unknown(), + SERVICE_POINT_ID, false, Institution.unknown(), Campus.unknown(), Library.unknown(), ServicePoint.unknown())) .withInstance(new Instance(UUID.randomUUID().toString(), TITLE, emptyList(), contributors, emptyList(), emptyList())) .withMaterialType(new MaterialType(ITEM_MATERIAL_TYPE_ID.toString(), ITEM_MATERIAL_TYPE_NAME, null)); diff --git a/src/test/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepositoryTests.java b/src/test/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepositoryTests.java index ce6afc117a..f32c4306b4 100644 --- a/src/test/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepositoryTests.java +++ b/src/test/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepositoryTests.java @@ -167,7 +167,8 @@ private ItemRepository createRepository(CollectionResourceClient itemsClient, Co } private Item dummyItem() { - return new Item(null, null, null, null, null, null, null, null, false, + return new Item(null, null, null, null, null, null, null, null, + null, false, Holdings.unknown(), Instance.unknown(), MaterialType.unknown(), LoanType.unknown(), ItemDescription.unknown()); } diff --git a/src/test/java/org/folio/circulation/rules/Text2DroolsTest.java b/src/test/java/org/folio/circulation/rules/Text2DroolsTest.java index 9e5512ab8e..f9ade91b9c 100644 --- a/src/test/java/org/folio/circulation/rules/Text2DroolsTest.java +++ b/src/test/java/org/folio/circulation/rules/Text2DroolsTest.java @@ -688,6 +688,7 @@ private MultiMap params(String itId, String ltId, String ptId, String lId) { private Location createLocation(String institutionId, String libraryId, String campusId) { return new Location(null, null, null, null, emptyList(), null, + false, Institution.unknown(institutionId), Campus.unknown(campusId), Library.unknown(libraryId), ServicePoint.unknown()); } diff --git a/src/test/java/org/folio/circulation/services/FeeFineFacadeTest.java b/src/test/java/org/folio/circulation/services/FeeFineFacadeTest.java index edb63950aa..d9c19a77af 100644 --- a/src/test/java/org/folio/circulation/services/FeeFineFacadeTest.java +++ b/src/test/java/org/folio/circulation/services/FeeFineFacadeTest.java @@ -124,7 +124,7 @@ void shouldForwardFailureIfAnAccountIsNotRefunded() throws Exception { private CreateAccountCommand.CreateAccountCommandBuilder createCommandBuilder() { final Item item = Item.from(new JsonObject()) .withLocation(new Location(null, "Main library", null, null, emptyList(), - null, Institution.unknown(), Campus.unknown(), + null, false, Institution.unknown(), Campus.unknown(), Library.unknown(), ServicePoint.unknown())); return CreateAccountCommand.builder() From e70f55b95e63317bab45d6d1fc527c4dd187ecfd Mon Sep 17 00:00:00 2001 From: Janis Saldabols Date: Fri, 6 Sep 2024 13:35:37 +0300 Subject: [PATCH 06/17] CIRC-2141 Allow specifying item location when creating title-level requests --- .../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 ++- 12 files changed, 133 insertions(+), 9 deletions(-) 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 d1cecb8085..1de0416426 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; @@ -379,6 +380,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 a7493260b7..35a023650a 100644 --- a/src/test/java/api/requests/RequestsAPICreationTests.java +++ b/src/test/java/api/requests/RequestsAPICreationTests.java @@ -631,6 +631,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(); From cbf3abdd648d5f771889862be34af3008d5ef061 Mon Sep 17 00:00:00 2001 From: Janis Saldabols Date: Wed, 11 Sep 2024 11:39:17 +0300 Subject: [PATCH 07/17] CIRC-2141 Add new field to request schema --- ramls/request.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ramls/request.json b/ramls/request.json index 1b80913dbb..64ca7d9088 100644 --- a/ramls/request.json +++ b/ramls/request.json @@ -423,6 +423,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, From 261420d129afe125cfae5de17e8c78fe2d41859c Mon Sep 17 00:00:00 2001 From: nielserik Date: Mon, 23 Sep 2024 19:59:58 +0200 Subject: [PATCH 08/17] CIRC-2136 fix for npe in check for floating -- when current process is not a check-in but a page request --- src/main/java/org/folio/circulation/domain/Item.java | 5 +++-- .../infrastructure/storage/inventory/ItemRepository.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/folio/circulation/domain/Item.java b/src/main/java/org/folio/circulation/domain/Item.java index 9fa7a7e042..ff6bbbdf9a 100644 --- a/src/main/java/org/folio/circulation/domain/Item.java +++ b/src/main/java/org/folio/circulation/domain/Item.java @@ -357,8 +357,9 @@ public String getFloatDestinationLocationId() { } public boolean canFloatThroughCheckInServicePoint() { - return getLocation() != null && - getLocation().isFloatingCollection() + return getLocation() != null + && getLocation().isFloatingCollection() + && getFloatDestinationLocation() != null && getFloatDestinationLocation().getId() != null; } diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepository.java index 9a5cb54d5d..ffe39a8bce 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepository.java @@ -113,7 +113,7 @@ public CompletableFuture> updateItem(Item item) { if (item.isInStatus(IN_TRANSIT)) { write(updatedItemRepresentation, IN_TRANSIT_DESTINATION_SERVICE_POINT_ID, item.getInTransitDestinationServicePointId()); - } else if (item.canFloatThroughCheckInServicePoint()) { + } else if (item.isInStatus(AVAILABLE) && item.canFloatThroughCheckInServicePoint()) { remove(updatedItemRepresentation, TEMPORARY_LOCATION_ID); write(updatedItemRepresentation, TEMPORARY_LOCATION_ID, item.getFloatDestinationLocationId()); From 396acd0a8082e15a7623d973b3ec6db62f7bf5c0 Mon Sep 17 00:00:00 2001 From: nielserik Date: Wed, 16 Oct 2024 21:16:36 +0200 Subject: [PATCH 09/17] CIRC-2136 fix query limit, locations-by-service-point look-up --- .../infrastructure/storage/inventory/LocationRepository.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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..9c95b5712b 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 @@ -40,6 +40,7 @@ import org.folio.circulation.support.SingleRecordFetcher; import org.folio.circulation.support.fetching.CqlQueryFinder; import org.folio.circulation.support.http.client.CqlQuery; +import org.folio.circulation.support.http.client.PageLimit; import org.folio.circulation.support.results.Result; public class LocationRepository { @@ -287,7 +288,7 @@ public CompletableFuture>> fetchLocationsForServiceP log.debug("fetchLocationsForServicePoint:: parameters servicePointId: {}", servicePointId); return new CqlQueryFinder<>(locationsStorageClient, "locations", new LocationMapper()::toDomain) - .findByQuery(CqlQuery.match("servicePointIds", servicePointId)) + .findByQuery(CqlQuery.match("servicePointIds", servicePointId), PageLimit.maximumLimit()) .thenApply(r -> r.map(MultipleRecords::getRecords)); } From e98235b3d00f7929172ce7d798fc595d6c41699f Mon Sep 17 00:00:00 2001 From: nielserik Date: Thu, 24 Oct 2024 11:18:06 +0200 Subject: [PATCH 10/17] CIRC-2136 set new location on check-in response and pick slip context. --- .../folio/circulation/domain/notice/TemplateContextUtil.java | 3 ++- .../domain/representations/ItemSummaryRepresentation.java | 5 ++++- .../infrastructure/storage/inventory/LocationRepository.java | 5 +++++ .../folio/circulation/resources/CheckInProcessAdapter.java | 4 +++- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java b/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java index 948a695327..213fe69013 100644 --- a/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java +++ b/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java @@ -211,7 +211,8 @@ private static JsonObject createItemContext(Item item) { .put("displaySummary", item.getDisplaySummary()) .put("descriptionOfPieces", item.getDescriptionOfPieces()); - Location location = item.getLocation(); + Location location = item.canFloatThroughCheckInServicePoint() ? + item.getFloatDestinationLocation() : item.getLocation(); if (location != null) { log.info("createItemContext:: location is not null"); diff --git a/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java b/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java index f9143cc197..8f02b05cc3 100644 --- a/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java +++ b/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java @@ -78,7 +78,10 @@ public JsonObject createItemSummary(Item item) { final Location location = item.getLocation(); - if (location != null) { + if (item.canFloatThroughCheckInServicePoint()) { + itemSummary.put("location", new JsonObject() + .put("name", item.getFloatDestinationLocation().getName())); + } else if (location != null) { log.info("createItemSummary:: location is not null"); itemSummary.put("location", new JsonObject() .put("name", location.getName())); 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 9c95b5712b..5ce2848381 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 @@ -88,6 +88,11 @@ public CompletableFuture> getPermanentLocation(Item item) { return getLocation(item, Item::getPermanentLocationId); } + public CompletableFuture> getFloatDestinationLocation(Item item) { + log.debug("getFloatDestinationLocation:: parameters item: {}", item); + return getLocation(item, Item::getFloatDestinationLocationId); + } + private CompletableFuture> getLocation(Item item, Function locationIdGetter) { log.debug("getLocation:: parameters item: {}", item); diff --git a/src/main/java/org/folio/circulation/resources/CheckInProcessAdapter.java b/src/main/java/org/folio/circulation/resources/CheckInProcessAdapter.java index 88bda6bfb9..5179afe894 100644 --- a/src/main/java/org/folio/circulation/resources/CheckInProcessAdapter.java +++ b/src/main/java/org/folio/circulation/resources/CheckInProcessAdapter.java @@ -309,7 +309,9 @@ CompletableFuture> findFloatingDestination(CheckInContext context) return locationRepository.fetchLocationsForServicePoint(context.getCheckInServicePointId().toString()) .thenApply(rLocations -> rLocations.map(locations -> locations.stream() .filter(Location::isFloatingCollection).findFirst() - .map(item::withFloatDestinationLocation).orElse(item))); + .map(item::withFloatDestinationLocation).orElse(item))) + .thenCompose(it -> locationRepository.getFloatDestinationLocation(it.value())) + .thenApply(location -> Result.succeeded(item.withFloatDestinationLocation(location.value()))); } else { return Result.ofAsync(item); } From f13ace965c775d3936852950306e9d78d7e23ea9 Mon Sep 17 00:00:00 2001 From: Christopher Hellen Date: Tue, 29 Oct 2024 17:02:26 -0500 Subject: [PATCH 11/17] corrected permissions changed in MODCAL-136 (#1504) --- descriptors/ModuleDescriptor-template.json | 24 ++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 06fd6ce50e..590b2da834 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -1133,7 +1133,8 @@ "accounts.item.post", "feefineactions.item.post", "circulation-storage.loans-history.collection.get", - "calendar.endpoint.dates.get" + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get" ], "schedule": { "cron": "1 0 * * *", @@ -1773,7 +1774,8 @@ "proxiesfor.collection.get", "patron-notice.post", "configuration.entries.collection.get", - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "pubsub.publish.post", "circulation-storage.loans-history.collection.get" ], @@ -1784,7 +1786,8 @@ "displayName" : "module permissions for one op", "description" : "to reduce X-Okapi-Token size", "subPermissions": [ - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "circulation-storage.loans.item.post", "circulation-storage.loans.item.get", "circulation-storage.loans.collection.get", @@ -1881,7 +1884,8 @@ "accounts.refund.post", "accounts.cancel.post", "configuration.entries.collection.get", - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "actual-cost-record-storage.actual-cost-records.collection.get", "actual-cost-record-storage.actual-cost-records.item.get", "actual-cost-fee-fine-cancel.post", @@ -1917,7 +1921,8 @@ "users.collection.get", "addresstypes.collection.get", "proxiesfor.collection.get", - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "configuration.entries.collection.get", "scheduled-notice-storage.scheduled-notices.collection.delete", "scheduled-notice-storage.scheduled-notices.item.post", @@ -2099,7 +2104,8 @@ "usergroups.item.get", "proxiesfor.collection.get", "patron-notice.post", - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "scheduled-notice-storage.scheduled-notices.collection.delete", "scheduled-notice-storage.scheduled-notices.item.post", "configuration.entries.collection.get", @@ -2155,7 +2161,8 @@ "displayName": "module permissions for one op", "description": "to reduce X-Okapi-Token size", "subPermissions": [ - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "circulation-storage.loan-policies.item.get", "circulation-storage.loans.item.get", "circulation-storage.loans.item.put", @@ -2259,7 +2266,8 @@ "usergroups.item.get", "proxiesfor.collection.get", "patron-notice.post", - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "configuration.entries.collection.get", "scheduled-notice-storage.scheduled-notices.collection.delete", "scheduled-notice-storage.scheduled-notices.item.post", From 91d09704b64d7d1307f127501a43e4ee746560f3 Mon Sep 17 00:00:00 2001 From: nielserik Date: Thu, 31 Oct 2024 14:37:56 +0100 Subject: [PATCH 12/17] CIRC-2136 set a new location in check-in response, pick slip only if item will float --- .../domain/notice/TemplateContextUtil.java | 3 ++- .../ItemSummaryRepresentation.java | 3 ++- .../loans/scenarios/FloatingItemsTests.java | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java b/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java index 213fe69013..450f745b07 100644 --- a/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java +++ b/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java @@ -24,6 +24,7 @@ import org.folio.circulation.domain.FeeFineAction; import org.folio.circulation.domain.Instance; import org.folio.circulation.domain.Item; +import org.folio.circulation.domain.ItemStatus; import org.folio.circulation.domain.Loan; import org.folio.circulation.domain.Location; import org.folio.circulation.domain.Request; @@ -211,7 +212,7 @@ private static JsonObject createItemContext(Item item) { .put("displaySummary", item.getDisplaySummary()) .put("descriptionOfPieces", item.getDescriptionOfPieces()); - Location location = item.canFloatThroughCheckInServicePoint() ? + Location location = (item.canFloatThroughCheckInServicePoint() && item.isInStatus(ItemStatus.AVAILABLE)) ? item.getFloatDestinationLocation() : item.getLocation(); if (location != null) { diff --git a/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java b/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java index 8f02b05cc3..f18255537b 100644 --- a/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java +++ b/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java @@ -12,6 +12,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.folio.circulation.domain.Item; +import org.folio.circulation.domain.ItemStatus; import org.folio.circulation.domain.Location; import org.folio.circulation.domain.ServicePoint; @@ -78,7 +79,7 @@ public JsonObject createItemSummary(Item item) { final Location location = item.getLocation(); - if (item.canFloatThroughCheckInServicePoint()) { + if (item.canFloatThroughCheckInServicePoint() && item.isInStatus(ItemStatus.AVAILABLE)) { itemSummary.put("location", new JsonObject() .put("name", item.getFloatDestinationLocation().getName())); } else if (location != null) { diff --git a/src/test/java/api/loans/scenarios/FloatingItemsTests.java b/src/test/java/api/loans/scenarios/FloatingItemsTests.java index 669144a8ee..6eccec7cf5 100644 --- a/src/test/java/api/loans/scenarios/FloatingItemsTests.java +++ b/src/test/java/api/loans/scenarios/FloatingItemsTests.java @@ -72,6 +72,15 @@ void willSetFloatingItemsTemporaryLocationToFloatingCollectionAtServicePoint() { itemRepresentation.containsKey("inTransitDestinationServicePointId"), CoreMatchers.is(false)); + assertThat( "The check-in response should display the item's new location.", + itemRepresentation.getJsonObject("location").getString("name"), CoreMatchers.is("Floating collection 2")); + + JsonObject staffSlipContext = checkInResponse.getStaffSlipContext(); + + assertThat( "The staff slip context should display the item's new location.", + staffSlipContext.getJsonObject("item").getString("effectiveLocationSpecific"), CoreMatchers.is("Floating collection 2")); + + JsonObject loanRepresentation = checkInResponse.getLoan(); assertThat("closed loan should be present in response", @@ -266,6 +275,14 @@ void willPutFloatingItemInTransitWhenHoldRequestWasIssuedAtDifferentServicePoint itemRepresentation.containsKey("inTransitDestinationServicePointId"), CoreMatchers.is(true)); + assertThat( "The check-in response should display the item's original location.", + itemRepresentation.getJsonObject("location").getString("name"), CoreMatchers.is("Floating collection")); + + JsonObject staffSlipContext = checkInResponse.getStaffSlipContext(); + + assertThat( "The staff slip context should display the item's original location.", + staffSlipContext.getJsonObject("item").getString("effectiveLocationSpecific"), CoreMatchers.is("Floating collection")); + JsonObject loanRepresentation = checkInResponse.getLoan(); assertThat("closed loan should be present in response", @@ -300,6 +317,7 @@ void willPutFloatingItemInTransitWhenHoldRequestWasIssuedAtDifferentServicePoint assertThat("Checkin Service Point Id should be stored", storedLoan.getString("checkinServicePointId"), is(servicePointTwo.getId())); + } From 07960482a3921b0fb0f62d453e1a173ae9ced2b7 Mon Sep 17 00:00:00 2001 From: nielserik Date: Thu, 31 Oct 2024 15:28:52 +0100 Subject: [PATCH 13/17] CIRC-2136 remove spurious workflows --- .github/workflows/k8s-deploy-latest.yml | 27 ----- .github/workflows/mvn-dev-build-deploy.yml | 132 --------------------- 2 files changed, 159 deletions(-) delete mode 100644 .github/workflows/k8s-deploy-latest.yml delete mode 100644 .github/workflows/mvn-dev-build-deploy.yml diff --git a/.github/workflows/k8s-deploy-latest.yml b/.github/workflows/k8s-deploy-latest.yml deleted file mode 100644 index fde161bd64..0000000000 --- a/.github/workflows/k8s-deploy-latest.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Very simple workflow to deploy the *latest* published container image with the 'latest' tag. -# This does not post updated module descriptors to okapi, update permissions or enable -# new versions of a module with the tenant. If that is needed, it should be done manually -# via the Okapi API. - -name: k8s-deploy-latest - -env: - K8S_NAMESPACE: 'snapshot-dev' - K8S_DEPLOYMENT: 'mod-circulation-dev' - -on: - workflow_dispatch - -jobs: - k8s-deploy-latest: - - runs-on: ubuntu-latest - steps: - - name: Deploy latest to K8s - uses: actions-hub/kubectl@v1.29.3 - env: - KUBE_CONFIG: ${{ secrets.SNAPSHOT_DEV_SA_KUBECONFIG }} - with: - args: - -n ${{ env.K8S_NAMESPACE }} rollout restart deployment ${{ env.K8S_DEPLOYMENT }} - diff --git a/.github/workflows/mvn-dev-build-deploy.yml b/.github/workflows/mvn-dev-build-deploy.yml deleted file mode 100644 index a05e2db06f..0000000000 --- a/.github/workflows/mvn-dev-build-deploy.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: mvn-dev-build-deploy - -on: - push: - pull_request: - types: [opened, synchronize, reopened] - workflow_dispatch: - -env: - PUBLISH_BRANCH: 'deployment' - OKAPI_URL: 'https://snapshot-dev-okapi.folio-dev.indexdata.com' - OKAPI_SECRET_USER: "${{ secrets.SNAPSHOT_DEV_OKAPI_USER }}" - OKAPI_SECRET_PASSWORD: "${{ secrets.SNAPSHOT_DEV_OKAPI_PASSWORD }}" - OK_SESSION: 'session1' - -jobs: - mvn-dev-build-deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - submodules: recursive - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: 17 - distribution: 'temurin' # Alternative distribution options are available. - - - name: Prepare okclient - run: git clone https://github.com/indexdata/okclient - - - name: Ensure OK and FOLIO login - # So do not proceed with other workflow steps if not available. - run: | - source okclient/ok.sh - OK -S ${{ env.OK_SESSION }} \ - -h ${{ env.OKAPI_URL }} \ - -t "supertenant" \ - -u ${{ env.OKAPI_SECRET_USER }} \ - -p ${{ env.OKAPI_SECRET_PASSWORD }} - OK -S ${{ env.OK_SESSION }} -x - - - name: Gather some variables - run: | - echo "MODULE_NAME=$(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout)" >> $GITHUB_ENV - echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - echo "CURRENT_BRANCH=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV - - - name: Set module version - run: | - echo "MODULE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)-${SHA_SHORT}" >> $GITHUB_ENV - - - name: Cache SonarCloud packages - uses: actions/cache@v4 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - - name: Cache Maven packages - uses: actions/cache@v4 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - - - name: Maven build - run: mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install org.jacoco:jacoco-maven-plugin:report - - - name: SQ analyze - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=indexdata -Dsonar.projectKey=indexdata_${{ github.event.repository.name }} - - - name: Update ModuleDescriptor Id - run: | - if test -f "$MOD_DESCRIPTOR"; then - echo "Found $MOD_DESCRIPTOR" - cat <<< $(jq '.id = "${{ env.MODULE_NAME}}-${{ env.MODULE_VERSION }}"' $MOD_DESCRIPTOR) > $MOD_DESCRIPTOR - echo "MODULE_DESCRIPTOR=$MOD_DESCRIPTOR" >> $GITHUB_ENV - else - echo "Could not find $MOD_DESCRIPTOR" - exit 1 - fi - env: - MOD_DESCRIPTOR: './target/ModuleDescriptor.json' - - - name: Read ModuleDescriptor - id: moduleDescriptor - uses: juliangruber/read-file-action@v1 - with: - path: ${{ env.MODULE_DESCRIPTOR }} - - - name: Login to Index Data Docker Hub account - if: ${{ env.CURRENT_BRANCH == env.PUBLISH_BRANCH }} - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and publish Docker image - if: ${{ env.CURRENT_BRANCH == env.PUBLISH_BRANCH }} - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: indexdata/${{ env.MODULE_NAME }}:${{ env.MODULE_VERSION }},indexdata/${{ env.MODULE_NAME }}:latest - - - name: Publish ModuleDescriptor to Okapi - if: ${{ env.CURRENT_BRANCH == env.PUBLISH_BRANCH }} - run: | - source okclient/ok.sh - echo "Do login ..." - OK -S ${{ env.OK_SESSION }} \ - -h ${{ env.OKAPI_URL }} \ - -t "supertenant" \ - -u ${{ env.OKAPI_SECRET_USER }} \ - -p ${{ env.OKAPI_SECRET_PASSWORD }} - echo "Post the MD and report the response status ..." - OK -S ${{ env.OK_SESSION }} _/proxy/modules \ - -X post -f ${{ env.MODULE_DESCRIPTOR }} - declare -n NAMEREF_STATUS=${{ env.OK_SESSION }}_HTTPStatus - echo "Response status: $NAMEREF_STATUS" - echo "Do logout ..." - OK -S ${{ env.OK_SESSION }} -x - - - name: Print module version to job summary - run: | - echo "#### Module Version: ${{ env.MODULE_VERSION }}" >> $GITHUB_STEP_SUMMARY From 750dfbddb837059d7672fa3830acd6513377744e Mon Sep 17 00:00:00 2001 From: MagzhanArtykov Date: Thu, 31 Oct 2024 22:42:51 +0500 Subject: [PATCH 14/17] Update NEWS --- NEWS.md | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9b58faeb74..fc0f2b0dc1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,34 @@ -## 24.3.0 - -* [CIRC-2156](https://folio-org.atlassian.net/browse/CIRC-2156) Upgrade "holdings-storage" to 8.0 +## 24.3.0 2024-10-31 + +* Support floating collections (CIRC-2136) +* Correct permissions change in MODCAL-136 (MODCAL-136) +* Upgrade to RMB v35.3.0 (CIRC-2163) +* Add TLR Hold requests handling to the print slips logic (CIRC-2134) +* Add support for interface instance-storage 11.0 (CIRC-2153) +* Upgrade the API version in the ModuleDescriptor-template.json (CIRC-2156) +* Delete obsolete notes permission (CIRC-2154) +* Extend the loan representation with additional fields required for Due date receipt (CIRC-2144) +* Revert CIRC-2100 PR and rework for PrintEventDetail pagination, searching, and sorting (CIRC-2148) +* Allow specifying item location when creating title-level requests (CIRC-2141) +* Bypass pickup SP check for Delivery requests (CIRC-2122) +* Fetch print details while fetching request details based on CQL query (CIRC-2100) +* Error message - after service point changes from `yes` to `no` as Pickup location (CIRC-1980) +* Implement POST API (CIRC-2099) +* Add test case on enableRequestPrintDetailsSetting (CIRC-2096) +* Create API wrapping settings CRUD (CIRC-2111) +* Update error response status and schema for request creation (CIRC-2104) +* Update interface holdings-storage to version 7.0 (CIRC-2095) +* Add isDcb flag value in the loan event for Check-out (CIRC-2084) +* Sort allowed service points by name (CIRC-2094) +* Fix Kafka configuration (CIRC-2037) +* Fix empty template token, `chargeDate`, in reminder notices (CIRC-2077) +* Support override of renewal block due to reminders (CIRC-2019) +* Keep due date when it is within recall return interval (CIRC-2043) +* Ignore `Loan related fee/fine closed` events when loan is already closed (CIRC-2066) +* Apply alternate loan period when multiple requests are in fulfillment in progress (CIRC-2026) +* Add missing permissions (CIRC-2070) +* Set returnDate for lost loans (CIRC-2044) +* Fix item details not fully populated when response contains more than 50 loans (CIRC-2059) ## 24.2.0 2024-03-21 From a183f92f1d606ce8e947be2b3b35a8929b847625 Mon Sep 17 00:00:00 2001 From: MagzhanArtykov Date: Thu, 31 Oct 2024 22:43:37 +0500 Subject: [PATCH 15/17] [maven-release-plugin] prepare release v24.3.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 964270022e..2642224859 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 mod-circulation org.folio - 24.3.0-SNAPSHOT + 24.3.0 Apache License 2.0 @@ -300,7 +300,7 @@ https://github.com/folio-org/mod-inventory scm:git:git://github.com:folio-org/mod-inventory.git scm:git:git@github.com:folio-org/mod-inventory.git - HEAD + v24.3.0 From 2a2438de7fa024e15ec7fde14f4918abbc29f19b Mon Sep 17 00:00:00 2001 From: MagzhanArtykov Date: Thu, 31 Oct 2024 22:43:38 +0500 Subject: [PATCH 16/17] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2642224859..6dd7672571 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 mod-circulation org.folio - 24.3.0 + 24.3.1-SNAPSHOT Apache License 2.0 @@ -300,7 +300,7 @@ https://github.com/folio-org/mod-inventory scm:git:git://github.com:folio-org/mod-inventory.git scm:git:git@github.com:folio-org/mod-inventory.git - v24.3.0 + HEAD From c4ccae93a6c277c2468a2780630a662e49dc5acd Mon Sep 17 00:00:00 2001 From: Alexander Kurash Date: Fri, 1 Nov 2024 19:56:45 +0200 Subject: [PATCH 17/17] CIRC-2161 Fix snapshot version (#1509) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6dd7672571..cb9a7e5828 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 mod-circulation org.folio - 24.3.1-SNAPSHOT + 24.4.0-SNAPSHOT Apache License 2.0