Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pr 1829 #792

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class Status implements MultiTenant<Status> {
public static final String PATRON_REQUEST_WILL_SUPPLY = "REQ_WILL_SUPPLY";
public static final String PATRON_REQUEST_DELIVERED = "REQ_DELIVERED";
public static final String PATRON_REQUEST_COMPLETE = "REQ_COMPLETE";
public static final String PATRON_REQUEST_REREQUESTED = "REQ_REREQUESTED";

public static final String REQUESTER_LOANED_DIGITALLY = "REQ_LOANED_DIGITALLY";

Expand Down
28 changes: 28 additions & 0 deletions service/grails-app/services/org/olf/rs/RerequestService.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.olf.rs

/**
* Handle creating a new request from an existing one
*/


public class RerequestService {
public static List<String> preserveFields = ['author','edition','isbn','isRequester','issn','issue','neededBy','numberOfPages','oclcNumber','patronEmail','patronGivenName','patronIdentifier','patronNote','patronReference','patronSurname','patronType','pickLocation','pickupLocationSlug','placeOfPublication','publicationDate','publisher','requestingInstitutionSymbol','sponsoringBody','startPage','stateModel','subtitle','systemInstanceIdentifier','title','volume'];

public createNewRequestFromExisting(PatronRequest originalRequest, List<String> copyFields, Map<String, Object> changeSet) {
PatronRequest newRequest = new PatronRequest();
copyFields.each {
if (changeSet.containsKey(it)) {
newRequest[it] = changeSet.get(it);
} else {
newRequest[it] = originalRequest[it];
}
}
originalRequest.succeededBy = newRequest;
newRequest.precededBy = originalRequest;
newRequest.save();
originalRequest.save();

return newRequest;
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
package org.olf.rs.statemodel.actions.iso18626;
package org.olf.rs.statemodel.actions.iso18626

import org.olf.rs.RerequestService
import org.olf.rs.SettingsService
import org.olf.rs.statemodel.StateModel;

import java.util.regex.Matcher;

Expand All @@ -18,8 +22,11 @@ import org.olf.rs.referenceData.SettingsData;
public abstract class ActionISO18626RequesterService extends ActionISO18626Service {

private static final String VOLUME_STATUS_AWAITING_TEMPORARY_ITEM_CREATION = 'awaiting_temporary_item_creation';
private static final String SETTING_YES = 'yes';

StatusService statusService;
SettingsService settingsService;
RerequestService rerequestService;

@Override
ActionResultDetails performAction(PatronRequest request, Object parameters, ActionResultDetails actionResultDetails) {
Expand Down Expand Up @@ -138,19 +145,45 @@ public abstract class ActionISO18626RequesterService extends ActionISO18626Servi
}

if (incomingStatus != null) {
handleStatusChange(request, incomingStatus, actionResultDetails);
handleStatusChange(request, incomingStatus, parameters, actionResultDetails);
}

return(actionResultDetails);
}

// ISO18626 states are RequestReceived, ExpectToSupply, WillSupply, Loaned Overdue, Recalled, RetryPossible, Unfilled, CopyCompleted, LoanCompleted, CompletedWithoutReturn and Cancelled
private void handleStatusChange(PatronRequest request, Map statusInfo, ActionResultDetails actionResultDetails) {
private void handleStatusChange(PatronRequest request, Map statusInfo, Object parameters, ActionResultDetails actionResultDetails) {
log.debug("handleStatusChange(${request.id},${statusInfo})");

if (statusInfo.status) {
// Set the qualifier on the result
actionResultDetails.qualifier = statusInfo.status;

// Special case for Unfilled
if (request.stateModel.shortcode.equalsIgnoreCase(StateModel.MODEL_REQUESTER)) {
if (statusInfo.status == "Unfilled") {
log.debug("Handling Unfilled status");
if (parameters.messageInfo.reasonUnfilled == "transfer") {
log.debug("Unfilled result with reason 'transfer'");
String pattern = /transferToCluster:(.+?)(#seq:.+#)?/
String note = parameters.messageInfo.note;
if (note) {
def matcher = note =~ pattern;
if (matcher.matches()) {
String newCluster = matcher.group(1);
log.debug("Pattern match for transferring request to new cluster ${newCluster}");
if (settingsService.hasSettingValue(SettingsData.SETTING_AUTO_REREQUEST, SETTING_YES)) {
//Trigger Re-Request here
actionResultDetails.qualifier = "UnfilledTransfer"; //To transition to Rerequested state
PatronRequest newRequest = rerequestService.createNewRequestFromExisting(request, RerequestService.preserveFields, ["systemInstanceIdentifier":newCluster]);
}
} else {
log.debug("reasonUnfilled was 'transfer', but a valid cluster id was not found in note: ${note}");
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.olf.rs.statemodel.actions.iso18626

import org.olf.rs.PatronRequest
import org.olf.rs.RerequestService
import org.olf.rs.SettingsService
import org.olf.rs.iso18626.ReasonForMessage
import org.olf.rs.statemodel.ActionEventResultQualifier
import org.olf.rs.statemodel.ActionResult
Expand All @@ -16,13 +18,16 @@ import org.olf.rs.statemodel.StateModel
*/
public class ActionPatronRequestISO18626StatusChangeService extends ActionISO18626RequesterService {

SettingsService settingsService;

@Override
String name() {
return(ReasonForMessage.MESSAGE_REASON_STATUS_CHANGE)
}

@Override
ActionResultDetails performAction(PatronRequest request, Object parameters, ActionResultDetails actionResultDetails) {
log.debug("ActionPatronRequestISO18626StatusChangeService performAction()");
// We have a hack where we use this message to verify that the last one sent was actually received or not
if (!checkForLastSequence(request, parameters.messageInfo?.note, actionResultDetails)) {
// A normal message
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.olf.rs.statemodel.events

import org.olf.rs.PatronRequest
import org.olf.rs.referenceData.RefdataValueData
import org.olf.rs.statemodel.EventFetchRequestMethod
import org.olf.rs.statemodel.EventResultDetails

class EventStatusReqEndOfRotaReviewedIndService extends EventTriggerNoticesService {

@Override
String name() {
return(Events.EVENT_STATUS_REQ_END_OF_ROTA_REVIEWED_INDICATION);
}

@Override
EventFetchRequestMethod fetchRequestMethod() {
return(EventFetchRequestMethod.PAYLOAD_ID);
}

@Override
EventResultDetails processEvent(PatronRequest request, Map eventData, EventResultDetails eventResultDetails) {
triggerNotice(request, RefdataValueData.NOTICE_TRIGGER_END_OF_ROTA_REVIEWED);

return eventResultDetails;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.olf.rs.statemodel.events;
package org.olf.rs.statemodel.events

import org.olf.rs.PatronRequest
import org.olf.rs.statemodel.EventResultDetails;
import org.olf.rs.statemodel.Events;

/**
Expand All @@ -9,6 +11,11 @@ import org.olf.rs.statemodel.Events;
*/
public class EventStatusReqUnfilledIndService extends EventSendToNextLenderService {

@Override
EventResultDetails processEvent(PatronRequest request, Map eventData, EventResultDetails eventResultDetails) {
super.processEvent(request, eventData, eventResultDetails);
}

@Override
String name() {
return(Events.EVENT_STATUS_REQ_UNFILLED_INDICATION);
Expand Down
116 changes: 113 additions & 3 deletions service/src/integration-test/groovy/org/olf/RSLifecycleSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ class RSLifecycleSpec extends TestBase {

def cleanup() {
}

/*
// For the given tenant, block up to timeout ms until the given request is found in the given state
private String waitForRequestState(String tenant, long timeout, String patron_reference, String required_state) {
long start_time = System.currentTimeMillis();
Expand Down Expand Up @@ -173,6 +173,61 @@ class RSLifecycleSpec extends TestBase {

return request_id;
}
*/
private String waitForRequestState(String tenant, long timeout, String patron_reference, String required_state) {
Map params = [
'max':'100',
'offset':'0',
'match':'patronReference',
'term':patron_reference
];
return waitForRequestStateParams(tenant, timeout, params, required_state);
}

private String waitForRequestStateById(String tenant, long timeout, String id, String required_state) {
Map params = [
'max':'1',
'offset':'0',
'match':'id',
'term':id
];
return waitForRequestStateParams(tenant, timeout, params, required_state);
}

private String waitForRequestStateParams(String tenant, long timeout, Map params, String required_state) {
long start_time = System.currentTimeMillis();
String request_id = null;
String request_state = null;
long elapsed = 0;
while ( ( required_state != request_state ) &&
( elapsed < timeout ) ) {

setHeaders(['X-Okapi-Tenant': tenant]);
// https://east-okapi.folio-dev.indexdata.com/rs/patronrequests?filters=isRequester%3D%3Dtrue&match=patronGivenName&perPage=100&sort=dateCreated%3Bdesc&stats=true&term=Michelle
def resp = doGet("${baseUrl}rs/patronrequests",
params)
if (resp?.size() == 1) {
request_id = resp[0].id
request_state = resp[0].state?.code
} else {
log.debug("waitForRequestState: Request with params ${params} not found");
}

if (required_state != request_state) {
// Request not found OR not yet in required state
log.debug("Not yet found.. sleeping");
Thread.sleep(1000);
}
elapsed = System.currentTimeMillis() - start_time
}
log.debug("Found request on tenant ${tenant} with params ${params} in state ${request_state} after ${elapsed} milliseconds");

if ( required_state != request_state ) {
throw new Exception("Expected ${required_state} but timed out waiting, current state is ${request_state}");
}

return request_id;
}

// For the given tenant fetch the specified request
private Map fetchRequest(String tenant, String requestId) {
Expand Down Expand Up @@ -325,7 +380,7 @@ class RSLifecycleSpec extends TestBase {

where:
tenant_id | changes_needed | changes_needed_hidden
'RSInstOne' | [ 'auto_responder_status':'off', 'auto_responder_cancel': 'off', 'routing_adapter':'static', 'static_routes':'SYMBOL:ISIL:RST3,SYMBOL:ISIL:RST2'] | ['requester_returnables_state_model':'PatronRequest', 'responder_returnables_state_model':'Responder', 'requester_non_returnables_state_model':'NonreturnableRequester', 'responder_non_returnables_state_model':'NonreturnableResponder', 'requester_digital_returnables_state_model':'DigitalReturnableRequester', 'state_model_responder_cdl':'CDLResponder']
'RSInstOne' | [ 'auto_responder_status':'off', 'auto_responder_cancel': 'off', 'routing_adapter':'static', 'static_routes':'SYMBOL:ISIL:RST3,SYMBOL:ISIL:RST2', 'auto_rerequest':'yes'] | ['requester_returnables_state_model':'PatronRequest', 'responder_returnables_state_model':'Responder', 'requester_non_returnables_state_model':'NonreturnableRequester', 'responder_non_returnables_state_model':'NonreturnableResponder', 'requester_digital_returnables_state_model':'DigitalReturnableRequester', 'state_model_responder_cdl':'CDLResponder']
'RSInstTwo' | [ 'auto_responder_status':'off', 'auto_responder_cancel': 'off', 'routing_adapter':'static', 'static_routes':'SYMBOL:ISIL:RST1,SYMBOL:ISIL:RST3'] | ['requester_returnables_state_model':'PatronRequest', 'responder_returnables_state_model':'Responder', 'requester_non_returnables_state_model':'NonreturnableRequester', 'responder_non_returnables_state_model':'NonreturnableResponder', 'requester_digital_returnables_state_model':'DigitalReturnableRequester', 'state_model_responder_cdl':'CDLResponder']
'RSInstThree' | [ 'auto_responder_status':'off', 'auto_responder_cancel': 'off', 'routing_adapter':'static', 'static_routes':'SYMBOL:ISIL:RST1'] | ['requester_returnables_state_model':'PatronRequest', 'responder_returnables_state_model':'Responder', 'requester_non_returnables_state_model':'NonreturnableRequester', 'responder_non_returnables_state_model':'NonreturnableResponder', 'requester_digital_returnables_state_model':'DigitalReturnableRequester', 'state_model_responder_cdl':'CDLResponder']

Expand Down Expand Up @@ -820,6 +875,14 @@ class RSLifecycleSpec extends TestBase {
"RSInstOne" | "RSInstThree" | 7 | false | "nrSupplierCannotSupply.json" | Status.PATRON_REQUEST_REQUEST_SENT_TO_SUPPLIER | Status.RESPONDER_UNFILLED | "RSInstTwo" | Status.RESPONDER_IDLE | "URL" | "Copy" | "{}"
"RSInstOne" | null | 8 | true | "null" | Status.PATRON_REQUEST_REQUEST_SENT_TO_SUPPLIER | null | "RSInstThree" | Status.RESPONDER_IDLE | "URL" | "Copy" | null
"RSInstOne" | "RSInstThree" | 8 | true | "nrRequesterCancel.json" | Status.PATRON_REQUEST_CANCELLED | Status.RESPONDER_CANCELLED | null | null | null | null | "{}"
// "RSInstOne" | null | 9 | true | null | Status.PATRON_REQUEST_REQUEST_SENT_TO_SUPPLIER | null | "RSInstThree" | Status.RESPONDER_IDLE | null | null | null
// "RSInstOne" | "RSInstThree" | 9 | false | "supplierAnswerYes.json" | Status.PATRON_REQUEST_EXPECTS_TO_SUPPLY | Status.RESPONDER_NEW_AWAIT_PULL_SLIP | null | null | null | null | "{}"
// "RSInstOne" | "RSInstThree" | 9 | false | "supplierCannotSupplyTransfer.json" | Status.PATRON_REQUEST_REREQUESTED | Status.RESPONDER_UNFILLED | null | null | null | null | "{}"
// "RSInstThree" | null | 10 | true | null | Status.PATRON_REQUEST_REQUEST_SENT_TO_SUPPLIER | null | "RSInstOne" | Status.RESPONDER_IDLE | null | null | null
// "RSInstThree" | "RSInstOne" | 10 | false | "supplierCannotSupply.json" | Status.PATRON_REQUEST_END_OF_ROTA | Status.RESPONDER_UNFILLED | "RSInstOne" | Status.RESPONDER_IDLE | null | null | "{}"



}

void "test Dynamic Groovy"() {
Expand Down Expand Up @@ -1852,7 +1915,7 @@ class DosomethingSimple {
}

void "Test transmission of copyright and publication type to supplier"(
String copyrightType, String publicationType, String patronIdentifier) {
String copyrightType, String publicationType, String patronIdentifier) {
String requesterTenantId = "RSInstOne";
String responderTenantId = "RSInstThree";
String patronReference = 'ref-' + patronIdentifier + randomCrap(6);
Expand Down Expand Up @@ -1895,4 +1958,51 @@ class DosomethingSimple {
// Look for responder request w/ patron reference
// check for copyright and publication type in responder request
}

void "Test automatic rerequest to different cluster id"() {
String patronIdentifier = "ABCD-EFG-HIJK-0001";
String requesterTenantId = "RSInstOne";
String responderTenantId = "RSInstThree";
String requestTitle = "YA Bad Book";
String requestAuthor = "Mr. Boringman";
String requestSymbol = "ISIL:RST1"
String patronReference = "ref-" + patronIdentifier + randomCrap(6);

when: "do the thing"

Map request = [
requestingInstitutionSymbol: requestSymbol,
title: requestTitle,
author: requestAuthor,
patronIdentifier: patronIdentifier,
isRequester: true,
patronReference: patronReference,
systemInstanceIdentifier: "123-456-789",
tags: ['RS-REREQUEST-TEST-1']
];

setHeaders([ 'X-Okapi-Tenant': requesterTenantId ]);
doPost("${baseUrl}/rs/patronrequests".toString(), request);

String requesterRequestId = waitForRequestState(requesterTenantId, 10000, patronReference, Status.PATRON_REQUEST_REQUEST_SENT_TO_SUPPLIER);

String responderRequestId = waitForRequestState(responderTenantId, 10000, patronReference, Status.RESPONDER_IDLE);

String jsonPayload = new File("src/integration-test/resources/scenarios/supplierCannotSupplyTransfer.json").text;
String performActionUrl = "${baseUrl}/rs/patronrequests/${responderRequestId}/performAction".toString();
log.debug("Posting supplierCannotSupplyTransfer payload to ${performActionUrl}");
doPost(performActionUrl, jsonPayload);

waitForRequestStateById(responderTenantId, 10000, responderRequestId, Status.RESPONDER_UNFILLED);

waitForRequestStateById(requesterTenantId, 10000, requesterRequestId, Status.PATRON_REQUEST_REREQUESTED);



then:
assert(true);



}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"action": "supplierCannotSupply",
"actionParams": {
"reason": "transfer",
"note": "transferToCluster:2334455667"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@ public class ActionEventResultData {
nextActionEvent: null
];

private static Map requesterISO18626UnfilledTransfer = [
code: 'requesterISO18626UnfilledTransfer',
description: 'An incoming ISO-18626 message for the requester has said that the status is Unfilled, but the reason is "transfer"',
result: true,
status: Status.PATRON_REQUEST_REREQUESTED,
qualifier: 'UnfilledTransfer',
saveRestoreState: null,
updateRotaLocation: true,
nextActionEvent: null
];

private static Map responderISO18626AgreeConditions = [
code: 'responderISO18626AgreeConditions',
description: 'Requester has said they want to agree to the conditions',
Expand Down Expand Up @@ -1001,6 +1012,7 @@ public class ActionEventResultData {
requesterISO18626Conditional,
requesterISO18626Loaned,
requesterISO18626Unfilled,
requesterISO18626UnfilledTransfer,
defaultNoStatusChangeOK
]
];
Expand Down Expand Up @@ -1047,6 +1059,7 @@ public class ActionEventResultData {
requesterISO18626Conditional,
requesterISO18626ExpectToSupply,
requesterISO18626Unfilled,
requesterISO18626UnfilledTransfer,
defaultNoStatusChangeOK
]
];
Expand Down
Loading
Loading