Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -277,14 +277,20 @@
"POST"
],
"pathPattern": "/circulation/pickup-by-barcode-for-use-at-location",
"permissionsRequired": []
"permissionsRequired": ["circulation.pickup-by-barcode-for-use-at-location.post"],
"modulePermissions": [
"circulation.usage-at-location.all"
]
},
{
"methods": [
"POST"
],
"pathPattern": "/circulation/hold-by-barcode-for-use-at-location",
"permissionsRequired": []
"permissionsRequired": ["circulation.hold-by-barcode-for-use-at-location.post"],
"modulePermissions": [
"circulation.usage-at-location.all"
]
},
{
"methods": [
Expand Down Expand Up @@ -1452,6 +1458,16 @@
"displayName": "circulation - renew loan using id",
"description": "renew a loan using IDs for item and loanee"
},
{
"permissionName": "circulation.pickup-by-barcode-for-use-at-location.post",
"displayName": "circulation - pick up from hold shelf for use at location",
"description": "pick up item of an existing loan from hold shelf for use at location (i.e. in reading room)"
},
{
"permissionName": "circulation.hold-by-barcode-for-use-at-location.post",
"displayName": "circulation - put item on hold shelf for another use at location",
"description": "put the item of an existing loan on the hold shelf for later use at location (i.e. in reading room)"
},
{
"permissionName": "circulation.loans.collection.get",
"displayName": "circulation - get loan collection",
Expand Down Expand Up @@ -1736,6 +1752,8 @@
"circulation.check-in-by-barcode.post",
"circulation.renew-by-barcode.post",
"circulation.renew-by-id.post",
"circulation.hold-by-barcode-for-use-at-location.post",
"circulation.pickup-by-barcode-for-use-at-location.post",
"circulation.loans.collection.get",
"circulation.loans.item.get",
"circulation.loans.item.post",
Expand Down Expand Up @@ -2116,6 +2134,23 @@
"visible": false,
"replaces": ["circulation.renew-loan"]
},
{
"permissionName": "circulation.usage-at-location.all",
"displayName" : "Put item on hold or pick it up for use at location",
"description" : "Permissions needed to take an item on or off hold shelf for use at location",
"subPermissions": [
"circulation-storage.loans.item.put",
"circulation-storage.loans.item.get",
"circulation-storage.loans.collection.get",
"circulation-storage.loan-policies.item.get",
"circulation-storage.loan-policies.collection.get",
"circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"pubsub.publish.post"
],
"visible": false
},
{
"permissionName": "perms.circulation.loans.anonymize.all",
"displayName" : "module permissions for one op",
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/folio/circulation/CirculationVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
import org.folio.circulation.resources.renewal.RenewByBarcodeResource;
import org.folio.circulation.resources.renewal.RenewByIdResource;
import org.folio.circulation.resources.foruseatlocation.HoldByBarcodeResource;
import org.folio.circulation.resources.foruseatlocation.PickUpByBarcodeResource;
import org.folio.circulation.resources.foruseatlocation.PickupByBarcodeResource;
import org.folio.circulation.support.logging.LogHelper;
import org.folio.circulation.support.logging.Logging;

Expand Down Expand Up @@ -100,7 +100,7 @@ public void start(Promise<Void> startFuture) {
new RenewByBarcodeResource(client).register(router);
new RenewByIdResource(client).register(router);
new HoldByBarcodeResource(client).register(router);
new PickUpByBarcodeResource(client).register(router);
new PickupByBarcodeResource(client).register(router);
new AllowedServicePointsResource(client).register(router);
new LoanCollectionResource(client).register(router);
new RequestCollectionResource(client).register(router);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ private void markHeld(RoutingContext routingContext) {
requestResult
.after(request -> findLoan(request, loanRepository, itemRepository, userRepository, errorHandler))
.thenApply(loan -> failWhenOpenLoanNotFoundForItem(loan, requestResult.value()))
.thenApply(loan -> failWhenOpenLoanIsNotForUseAtLocation(loan, requestResult.value()))
.thenApply(loanResult -> loanResult.map(loan -> loan.changeStatusOfUsageAtLocation(USAGE_STATUS_HELD)))
.thenApply(loanResult -> loanResult.map(loan -> loan.withAction(LoanAction.HELD_FOR_USE_AT_LOCATION)))
.thenCompose(loanResult -> loanResult.after(
Expand Down Expand Up @@ -90,20 +91,34 @@ protected CompletableFuture<Result<Loan>> findLoan(HoldByBarcodeRequest request,
);
}

private Result<Loan> failWhenOpenLoanNotFoundForItem (Result<Loan> loanResult, HoldByBarcodeRequest request) {
return loanResult.failWhen(this::loanIsNull, loan -> noOpenLoanFailure(request).get());
private static Result<Loan> failWhenOpenLoanNotFoundForItem (Result<Loan> loanResult, HoldByBarcodeRequest request) {
return loanResult.failWhen(HoldByBarcodeResource::loanIsNull, loan -> noOpenLoanFailure(request).get());
}

private Result<Boolean> loanIsNull (Loan loan) {
private Result<Loan> failWhenOpenLoanIsNotForUseAtLocation (Result<Loan> loanResult, HoldByBarcodeRequest request) {
return loanResult.failWhen(HoldByBarcodeResource::loanIsNotForUseAtLocation, loan -> loanIsNotForUseAtLocationFailure(request).get());
}

private static Result<Boolean> loanIsNull (Loan loan) {
return Result.succeeded(loan == null);
}

private static Result<Boolean> loanIsNotForUseAtLocation(Loan loan) {
return Result.succeeded(!loan.isForUseAtLocation());
}

private static Supplier<HttpFailure> noOpenLoanFailure(HoldByBarcodeRequest request) {
return () -> new BadRequestFailure(
format("No open loan found for the item barcode (%s)", request.getItemBarcode())
);
}

private static Supplier<HttpFailure> loanIsNotForUseAtLocationFailure(HoldByBarcodeRequest request) {
return () -> new BadRequestFailure(
format("The loan is open but is not for use at location, item barcode (%s)", request.getItemBarcode())
);
}

private HttpResponse toResponse(JsonObject body) {
return JsonHttpResponse.ok(body,
format("/circulation/loans/%s", body.getString("id")));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@

import static org.folio.circulation.resources.handlers.error.CirculationErrorType.FAILED_TO_FIND_SINGLE_OPEN_LOAN;

public class PickUpByBarcodeResource extends Resource {
public class PickupByBarcodeResource extends Resource {

private static final String rootPath = "/circulation/pickup-by-barcode-for-use-at-location";

public PickUpByBarcodeResource(HttpClient client) {
public PickupByBarcodeResource(HttpClient client) {
super(client);
}

Expand All @@ -63,6 +63,7 @@ private void markInUse(RoutingContext routingContext) {
pickupByBarcodeRequest
.after(request -> findLoan(request, loanRepository, itemRepository, userRepository, errorHandler))
.thenApply(loan -> failWhenOpenLoanForItemAndUserNotFound(loan, pickupByBarcodeRequest.value()))
.thenApply(loan -> failWhenOpenLoanIsNotForUseAtLocation(loan, pickupByBarcodeRequest.value()))
.thenApply(loanResult -> loanResult.map(loan ->
loan.changeStatusOfUsageAtLocation(USAGE_STATUS_IN_USE)
.withAction(LoanAction.PICKED_UP_FOR_USE_AT_LOCATION)))
Expand All @@ -89,24 +90,39 @@ protected CompletableFuture<Result<Loan>> findLoan(PickupByBarcodeRequest reques
.thenApply(r -> errorHandler.handleValidationResult(r, FAILED_TO_FIND_SINGLE_OPEN_LOAN, r.value()));
}

private HttpResponse toResponse(JsonObject body) {
return JsonHttpResponse.ok(body,
format("/circulation/loans/%s", body.getString("id")));
private static Result<Loan> failWhenOpenLoanForItemAndUserNotFound (Result<Loan> loanResult, PickupByBarcodeRequest request) {
return loanResult.failWhen(PickupByBarcodeResource::loanIsNull, loan -> noOpenLoanFailure(request).get());
}

private Result<Loan> failWhenOpenLoanForItemAndUserNotFound (Result<Loan> loanResult, PickupByBarcodeRequest request) {
return loanResult.failWhen(this::loanIsNull, loan -> noOpenLoanFailure(request).get());
private static Result<Loan> failWhenOpenLoanIsNotForUseAtLocation (Result<Loan> loanResult, PickupByBarcodeRequest request) {
return loanResult.failWhen(PickupByBarcodeResource::loanIsNotForUseAtLocation, loan -> loanIsNotForUseAtLocationFailure(request).get());
}

private Result<Boolean> loanIsNull (Loan loan) {
private static Result<Boolean> loanIsNull (Loan loan) {
return Result.succeeded(loan == null);
}

private static Result<Boolean> loanIsNotForUseAtLocation(Loan loan) {
return Result.succeeded(!loan.isForUseAtLocation());
}

private static Supplier<HttpFailure> noOpenLoanFailure(PickupByBarcodeRequest request) {
return () -> new BadRequestFailure(
format("No open loan found for item barcode (%s) and user (%s)",
request.getItemBarcode(), request.getUserBarcode())
);
}

private static Supplier<HttpFailure> loanIsNotForUseAtLocationFailure(PickupByBarcodeRequest request) {
return () -> new BadRequestFailure(
format("The loan is open but is not for use at location, item barcode (%s)", request.getItemBarcode())
);
}

private HttpResponse toResponse(JsonObject body) {
return JsonHttpResponse.ok(body,
format("/circulation/loans/%s", body.getString("id")));
}


}
38 changes: 38 additions & 0 deletions src/test/java/api/loans/LoansForUseAtLocationTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,25 @@ void holdWillFailWithDifferentItem() {
new HoldByBarcodeRequestBuilder("different-item"), 400);
}

@Test
void holdWillFailIfLoanIsNotForUseAtLocation() {
final LoanPolicyBuilder homeLoansPolicyBuilder = new LoanPolicyBuilder()
.withName("Home loans")
.withDescription("Policy for items that can be taken home")
.rolling(Period.days(30));

use(homeLoansPolicyBuilder);

checkOutFixture.checkOutByBarcode(
new CheckOutByBarcodeRequestBuilder()
.forItem(item)
.to(borrower)
.at(servicePointsFixture.cd1()));

holdForUseAtLocationFixture.holdForUseAtLocation(
new HoldByBarcodeRequestBuilder(item.getBarcode()), 400);
}

@Test
void holdWillFailWithIncompleteRequest() {
final LoanPolicyBuilder forUseAtLocationPolicyBuilder = new LoanPolicyBuilder()
Expand Down Expand Up @@ -211,6 +230,25 @@ void pickupWillFailWithIncompleteRequestObject() {
new PickupByBarcodeRequestBuilder(item.getBarcode(), null), 422);
}

@Test
void pickupWillFailIfLoanIsNotForUseAtLocation() {
final LoanPolicyBuilder homeLoansPolicyBuilder = new LoanPolicyBuilder()
.withName("Home loans")
.withDescription("Policy for items that can be taken home")
.rolling(Period.days(30));

use(homeLoansPolicyBuilder);

checkOutFixture.checkOutByBarcode(
new CheckOutByBarcodeRequestBuilder()
.forItem(item)
.to(borrower)
.at(servicePointsFixture.cd1()));

pickupForUseAtLocationFixture.pickupForUseAtLocation(
new PickupByBarcodeRequestBuilder(item.getBarcode(), borrower.getBarcode()), 400);
}

@Test
void willSetAtLocationUsageStatusToReturnedOnCheckIn() {
final LoanPolicyBuilder forUseAtLocationPolicyBuilder = new LoanPolicyBuilder()
Expand Down