From 6a1a6ef96326bbcf56b2c5cb449568be1172089b Mon Sep 17 00:00:00 2001 From: JavokhirAbdullayev Date: Mon, 8 Jan 2024 12:46:40 +0500 Subject: [PATCH 1/3] add soft delete endpoint in Instances --- descriptors/ModuleDescriptor-template.json | 39 +++++++++++++------ ramls/inventory.raml | 22 +++++++++++ .../folio/inventory/resources/Instances.java | 29 ++++++++++++++ src/test/java/api/InstancesApiExamples.java | 26 +++++++++++++ 4 files changed, 105 insertions(+), 11 deletions(-) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 5e104ef32..1327022c9 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -24,7 +24,8 @@ "inventory-storage.instances.item.get", "inventory-storage.bound-with-parts.collection.get" ] - }, { + }, + { "methods": ["GET"], "pathPattern": "/inventory/items/{id}", "permissionsRequired": ["inventory.items.item.get"], @@ -297,7 +298,8 @@ "inventory-storage.bound-with-parts.collection.get", "users.item.get" ] - }, { + }, + { "methods": ["PUT"], "pathPattern": "/inventory/items/{id}", "permissionsRequired": ["inventory.items.item.put"], @@ -313,12 +315,14 @@ "pathPattern": "/inventory/items/{id}", "permissionsRequired": ["inventory.items.item.delete"], "modulePermissions": ["inventory-storage.items.item.delete"] - }, { + }, + { "methods": ["DELETE"], "pathPattern": "/inventory/items", "permissionsRequired": ["inventory.items.collection.delete"], "modulePermissions": ["inventory-storage.items.collection.delete"] - }, { + }, + { "methods": ["PUT"], "pathPattern": "/inventory/holdings/{id}", "permissionsRequired": ["inventory.holdings.item.put"], @@ -326,7 +330,8 @@ "inventory-storage.holdings.item.get", "inventory-storage.holdings.item.put" ] - }, { + }, + { "methods": ["GET"], "pathPattern": "/inventory/instances", "permissionsRequired": ["inventory.instances.collection.get"], @@ -339,7 +344,8 @@ "inventory-storage.holdings.collection.get", "inventory-storage.items.collection.get" ] - }, { + }, + { "methods": ["GET"], "pathPattern": "/inventory/items-by-holdings-id", "permissionsRequired": ["inventory.items.collection.get"], @@ -358,7 +364,8 @@ "inventory-storage.instances.collection.get", "inventory-storage.instances.item.get" ] - }, { + }, + { "methods": ["GET"], "pathPattern": "/inventory/instances/{id}", "permissionsRequired": ["inventory.instances.item.get"], @@ -370,7 +377,8 @@ "inventory-storage.holdings.collection.get", "inventory-storage.items.collection.get" ] - }, { + }, + { "methods": ["POST"], "pathPattern": "/inventory/instances", "permissionsRequired": ["inventory.instances.item.post"], @@ -386,7 +394,8 @@ "inventory-storage.instance-relationships.item.put", "inventory-storage.instance-relationships.item.delete" ] - }, { + }, + { "methods": ["PUT"], "pathPattern": "/inventory/instances/{id}", "permissionsRequired": ["inventory.instances.item.put"], @@ -405,16 +414,24 @@ "inventory-storage.instance-relationships.item.put", "inventory-storage.instance-relationships.item.delete" ] - }, { + }, + { "methods": ["DELETE"], "pathPattern": "/inventory/instances/{id}", "permissionsRequired": ["inventory.instances.item.delete"], "modulePermissions": ["inventory-storage.instances.item.delete"] - }, { + }, + { "methods": ["DELETE"], "pathPattern": "/inventory/instances", "permissionsRequired": ["inventory.instances.collection.delete"], "modulePermissions": ["inventory-storage.instances.collection.delete"] + }, + { + "methods": ["DELETE"], + "pathPattern": "/inventory/instances/{id}/mark-deleted", + "permissionsRequired": [], + "modulePermissions": [] } ] }, diff --git a/ramls/inventory.raml b/ramls/inventory.raml index c64e4bf3d..97a7bda50 100644 --- a/ramls/inventory.raml +++ b/ramls/inventory.raml @@ -368,4 +368,26 @@ resourceTypes: get: put: is: [validate] + /mark-deleted: + delete: + description: Toggle the suppression state of an instance record, affecting either instance and associated MARC record if present. + responses: + 204: + description: Instance marked as deleted. + 404: + description: "Instance not found" + body: + text/plain: + example: "Instance with such id not found" + 422: + description: "Validation error" + body: + application/json: + type: errors + 500: + description: "Internal server error" + body: + text/plain: + example: "Internal server error" + diff --git a/src/main/java/org/folio/inventory/resources/Instances.java b/src/main/java/org/folio/inventory/resources/Instances.java index ad6ef5bba..d791acc52 100644 --- a/src/main/java/org/folio/inventory/resources/Instances.java +++ b/src/main/java/org/folio/inventory/resources/Instances.java @@ -79,6 +79,7 @@ public void register(Router router) { router.get(INSTANCES_PATH + "/:id").handler(this::getById); router.put(INSTANCES_PATH + "/:id").handler(this::update); router.delete(INSTANCES_PATH + "/:id").handler(this::deleteById); + router.delete(INSTANCES_PATH + "/:id" + "/mark-deleted").handler(this::softDelete); } private void getAll(RoutingContext routingContext) { @@ -302,6 +303,34 @@ private void deleteById(RoutingContext routingContext) { FailureResponseConsumer.serverError(routingContext.response())); } + private void softDelete(RoutingContext routingContext) { + WebContext webContext = new WebContext(routingContext); + InstanceCollection instanceCollection = storage.getInstanceCollection(webContext); + instanceCollection.findById(routingContext.request().getParam("id"), + it -> { + Instance instance = it.getResult(); + if (instance != null) { + updateVisibility(instance, routingContext, instanceCollection); + if (isInstanceControlledByRecord(instance)) { + updateSuppressFromDiscoveryFlag(webContext, instance); // + } + } else { + ClientErrorResponse.notFound(routingContext.response()); + } + }, FailureResponseConsumer.serverError(routingContext.response())); + } + + private void updateVisibility(Instance instance, RoutingContext routingContext, InstanceCollection instanceCollection) { + instance.setDiscoverySuppress(true); + instance.setStaffSuppress(true); + instanceCollection.update(instance, v -> { + log.info("staffSuppress and discoverySuppress properties are set to true for instance {}", + instance.getId()); + noContent(routingContext.response()); + }, + FailureResponseConsumer.serverError(routingContext.response())); + } + private void getById(RoutingContext routingContext) { WebContext context = new WebContext(routingContext); diff --git a/src/test/java/api/InstancesApiExamples.java b/src/test/java/api/InstancesApiExamples.java index efe778ddc..1ce1d498b 100644 --- a/src/test/java/api/InstancesApiExamples.java +++ b/src/test/java/api/InstancesApiExamples.java @@ -734,6 +734,32 @@ public void canDeleteAnInstance() assertThat(getAllResponse.getJson().getInteger("totalRecords"), is(2)); } + @Test + @SneakyThrows + public void canSoftDeleteInstance() { + JsonObject instanceToDelete = createInstance(marcInstanceWithDefaultBlockedFields(UUID.randomUUID())); + + URL softDeleteUrl = new URL(String.format("%s/%s/%s", + ApiRoot.instances(), instanceToDelete.getString("id"), "mark-deleted" )); + + URL getByIdUrl = new URL(String.format("%s/%s", + ApiRoot.instances(), instanceToDelete.getString("id"))); + + final var deleteCompleted = okapiClient.delete(softDeleteUrl); + + Response deleteResponse = deleteCompleted.toCompletableFuture().get(5, SECONDS); + + assertThat(deleteResponse.getStatusCode(), is(204)); + assertThat(deleteResponse.hasBody(), is(false)); + + final var getCompleted = okapiClient.get(getByIdUrl); + + Response getResponse = getCompleted.toCompletableFuture().get(5, SECONDS); + + assertTrue(getResponse.getJson().getBoolean("staffSuppress")); + assertTrue(getResponse.getJson().getBoolean("discoverySuppress")); + } + @Test public void canGetAllInstances() throws InterruptedException, From b8ac2ec349e3c1c9706600b95810f6e67e93eb5f Mon Sep 17 00:00:00 2001 From: JavokhirAbdullayev Date: Mon, 8 Jan 2024 12:51:24 +0500 Subject: [PATCH 2/3] todo comment --- src/main/java/org/folio/inventory/resources/Instances.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/folio/inventory/resources/Instances.java b/src/main/java/org/folio/inventory/resources/Instances.java index d791acc52..fc79cad18 100644 --- a/src/main/java/org/folio/inventory/resources/Instances.java +++ b/src/main/java/org/folio/inventory/resources/Instances.java @@ -312,7 +312,7 @@ private void softDelete(RoutingContext routingContext) { if (instance != null) { updateVisibility(instance, routingContext, instanceCollection); if (isInstanceControlledByRecord(instance)) { - updateSuppressFromDiscoveryFlag(webContext, instance); // + updateSuppressFromDiscoveryFlag(webContext, instance); //todo: will be replaced by soft delete by instance } } else { ClientErrorResponse.notFound(routingContext.response()); From 0afb730174692aae40983a596dc7424ac90b8bfe Mon Sep 17 00:00:00 2001 From: JavokhirAbdullayev Date: Tue, 9 Jan 2024 00:17:52 +0500 Subject: [PATCH 3/3] upd news --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index bca9f2805..0dd683f03 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ ## 20.2.0-SNAPSHOT 2023-xx-xx * Inventory cannot process Holdings with virtual fields ([MODINV-941](https://issues.folio.org/browse/MODINV-941)) +* Set instance record as deleted ([MODINV-883](https://issues.folio.org/browse/MODINV-883)) ## 20.1.0 2023-10-13 * Update status when user attempts to update shared auth record from member tenant ([MODDATAIMP-926](https://issues.folio.org/browse/MODDATAIMP-926))