diff --git a/NEWS.md b/NEWS.md
index be2409b2..2adbf07e 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,6 @@
+## 3.1.2 2024-04-22
+* [SIP2-169](https://issues.folio.org/browse/SIP2-169) Correct issue with incorrect values for items in transit from checkin response (backport)
+
## 3.1.1 2023-11-06
* [SIP2-178](https://issues.folio.org/browse/SIP2-178) Vert.x 4.4.6 fixing Netty HTTP/2 DoS (CVE-2023-44487)
diff --git a/pom.xml b/pom.xml
index f7194fe1..06b13709 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
org.folio
edge-sip2
- 3.1.2-SNAPSHOT
+ 3.1.3-SNAPSHOT
Standard Interchange Protocol v2 (SIP2)
https://github.com/folio-org/edge-sip2
Support for SIP2 in FOLIO. This allow self service circulation and patron services stations to perform supported operations in FOLIO.
diff --git a/src/main/java/org/folio/edge/sip2/repositories/CirculationRepository.java b/src/main/java/org/folio/edge/sip2/repositories/CirculationRepository.java
index b4d50c83..28ce645b 100644
--- a/src/main/java/org/folio/edge/sip2/repositories/CirculationRepository.java
+++ b/src/main/java/org/folio/edge/sip2/repositories/CirculationRepository.java
@@ -53,6 +53,7 @@ public class CirculationRepository {
public static final String TITLE = "title";
public static final String ITEM_BARCODE = "itemBarcode";
public static final String SERVICE_POINT_ID = "servicePointId";
+ public static final String ITEM_ID = "itemId";
private final IResourceProvider resourceProvider;
private final PasswordVerifier passwordVerifier;
private final Clock clock;
@@ -102,20 +103,21 @@ public Future performCheckinCommand(Checkin checkin, SessionDat
.compose(resource -> {
log.info("performCheckinCommand resource:{}", resource);
JsonObject resourceJson = resource.getResource();
-
+ log.debug("performCheckinCommand resource json is {}",
+ () -> resourceJson != null ? resourceJson.encode() : "null");
+ JsonObject valuesJson = extractCheckinValues(resourceJson);
+ log.debug("valuesJson is {}", valuesJson.encode());
final Future getRequestsResult = resourceJson != null
- ? getRequestsByItemId(itemIdentifier, null, null,
+ ? getRequestsByItemId(valuesJson.getString(ITEM_ID), null, null,
null, sessionData) : Future.succeededFuture(null);
return getRequestsResult
.compose(requestsJson -> {
- JsonObject valuesJson = extractCheckinValues(resourceJson);
+ log.debug("JSON from getRequestsByItemId is {}",
+ () -> requestsJson != null ? requestsJson.encode() : "null");
MediaType mediaType = getMediaType(valuesJson.getJsonObject("itemMaterialTypeJson"));
JsonArray requestArray =
requestsJson != null ? requestsJson.getJsonArray("requests") : null;
- JsonObject request = requestArray != null && !requestArray.isEmpty()
- ? requestArray.getJsonObject(0) : null;
- final String requestState =
- request != null ? request.getString("requestType") : null;
+ final String requestState = getRequestState(requestArray);
final boolean inTransit = valuesJson.getString("itemStatus") != null
&& valuesJson.getString("itemStatus").equals("In transit");
final boolean holdItem = requestState != null && requestState.equals("Hold");
@@ -357,7 +359,7 @@ public Future getRequestsByItemId(String itemId, String requestType,
SessionData sessionData) {
final Map headers = getBaseHeaders();
- final RequestsRequestData requestsRequestData = new RequestsRequestData("itemId", itemId,
+ final RequestsRequestData requestsRequestData = new RequestsRequestData(ITEM_ID, itemId,
requestType, startItem, endItem, headers, sessionData);
final Future result = resourceProvider.retrieveResource(requestsRequestData);
@@ -886,7 +888,7 @@ private MediaType getMediaType(JsonObject materialTypeJson) {
return mediaType;
}
- private String getAlertType(boolean inTransit, boolean holdItem, boolean recallItem) {
+ protected static String getAlertType(boolean inTransit, boolean holdItem, boolean recallItem) {
String alertType;
if (inTransit || holdItem || recallItem) {
if (!inTransit) {
@@ -906,6 +908,7 @@ private JsonObject extractCheckinValues(JsonObject resourceJson) {
JsonObject valuesJson = new JsonObject();
JsonObject itemJson = resourceJson != null ? resourceJson.getJsonObject("item")
: null;
+ valuesJson.put(ITEM_ID, itemJson != null ? itemJson.getString("id") : null);
valuesJson.put("callNumber", itemJson != null ? itemJson.getString("callNumber")
: null);
JsonObject itemStatusJson = itemJson != null ? itemJson.getJsonObject("status")
@@ -922,4 +925,15 @@ private JsonObject extractCheckinValues(JsonObject resourceJson) {
return valuesJson;
}
+
+ private String getRequestState(JsonArray requestArray) {
+ if (requestArray == null || requestArray.isEmpty()) {
+ return null;
+ }
+ JsonObject request = requestArray.getJsonObject(0);
+ if (request == null) {
+ return null;
+ }
+ return request.getString("requestType");
+ }
}
diff --git a/src/main/java/org/folio/edge/sip2/repositories/ConfigurationRepository.java b/src/main/java/org/folio/edge/sip2/repositories/ConfigurationRepository.java
index 047cb9e6..eb517b8c 100644
--- a/src/main/java/org/folio/edge/sip2/repositories/ConfigurationRepository.java
+++ b/src/main/java/org/folio/edge/sip2/repositories/ConfigurationRepository.java
@@ -167,6 +167,8 @@ private ACSStatusBuilder setACSConfig(LinkedHashMap sets,
}
private void addLocaleConfig(JsonObject config, SessionData sessionData) {
+ log.debug("Adding locale config with config {}",
+ () -> config != null ? config.encode() : "(null)");
if (config != null) {
sessionData.setTimeZone(config.getString("timezone"));
String currencyConfig = config.getString("currency") != null
diff --git a/src/main/resources/templates/CheckinResponse.ftl b/src/main/resources/templates/CheckinResponse.ftl
index 06d92946..323c0b11 100644
--- a/src/main/resources/templates/CheckinResponse.ftl
+++ b/src/main/resources/templates/CheckinResponse.ftl
@@ -41,3 +41,5 @@
<@lib.callNumber value=checkinResponse.callNumber!""/>
<#-- alert type: fixed-length optional field (extension) -->
<@lib.alertType value=checkinResponse.alertType!""/>
+<#-- pickup service point: variable-length optional field (extension) -->
+<@lib.pickupServicePoint value=checkinResponse.pickupServicePoint!""/>
diff --git a/src/main/resources/templates/lib.ftl b/src/main/resources/templates/lib.ftl
index 3fcbbd4c..ac1535ff 100644
--- a/src/main/resources/templates/lib.ftl
+++ b/src/main/resources/templates/lib.ftl
@@ -702,4 +702,8 @@
<@variableLengthField id="FF" value=value/>
#macro>
+<#macro pickupServicePoint value>
+ <@variableLengthField id="CT" value=value/>
+#macro>
+
diff --git a/src/test/java/org/folio/edge/sip2/handlers/CheckinHandlerTests.java b/src/test/java/org/folio/edge/sip2/handlers/CheckinHandlerTests.java
index 22477b2f..b15eaeb1 100644
--- a/src/test/java/org/folio/edge/sip2/handlers/CheckinHandlerTests.java
+++ b/src/test/java/org/folio/edge/sip2/handlers/CheckinHandlerTests.java
@@ -38,6 +38,7 @@ public void canExecuteASampleCheckinUsingHandler(
final String institutionId = "diku";
final String itemIdentifier = "1234567890";
final String currentLocation = UUID.randomUUID().toString();
+ final String servicePoint = "Circ Desk 1";
final Checkin checkin = Checkin.builder()
.noBlock(FALSE)
.transactionDate(OffsetDateTime.now())
@@ -61,6 +62,7 @@ public void canExecuteASampleCheckinUsingHandler(
.itemIdentifier(itemIdentifier)
.permanentLocation("Main Library")
.callNumber("23457799")
+ .pickupServicePoint(servicePoint)
.build()));
final CheckinHandler handler = new CheckinHandler(mockCirculationRepository,
@@ -72,7 +74,8 @@ public void canExecuteASampleCheckinUsingHandler(
testContext.succeeding(sipMessage -> testContext.verify(() -> {
final String expectedString = "101YUN"
+ TestUtils.getFormattedLocalDateTime(OffsetDateTime.now(clock))
- + "AO" + institutionId + "|AB" + itemIdentifier + "|AQMain Library|CS23457799|CV|";
+ + "AO" + institutionId + "|AB" + itemIdentifier + "|AQMain Library|CS23457799|CV|"
+ + "CT" + servicePoint + "|";
assertEquals(expectedString, sipMessage);
@@ -123,7 +126,7 @@ public void canExecuteASampleFailedCheckinUsingHandler(
testContext.succeeding(sipMessage -> testContext.verify(() -> {
final String expectedString = "100YUN"
+ TestUtils.getFormattedLocalDateTime(OffsetDateTime.now(clock))
- + "AO" + institutionId + "|AB" + itemIdentifier + "|AQ|CS|CV|";
+ + "AO" + institutionId + "|AB" + itemIdentifier + "|AQ|CS|CV|CT|";
assertEquals(expectedString, sipMessage);
diff --git a/src/test/java/org/folio/edge/sip2/repositories/CirculationRepositoryTests.java b/src/test/java/org/folio/edge/sip2/repositories/CirculationRepositoryTests.java
index 10ad2e78..c111acea 100644
--- a/src/test/java/org/folio/edge/sip2/repositories/CirculationRepositoryTests.java
+++ b/src/test/java/org/folio/edge/sip2/repositories/CirculationRepositoryTests.java
@@ -107,9 +107,10 @@ void canCheckin(Vertx vertx,
.put("location", new JsonObject()
.put("name", "Main Library"))
.put("materialType", new JsonObject()
- .put("name", "book")))
+ .put("name", "book"))
.put("inTransitDestinationServicePoint", new JsonObject()
- .put("name", "Annex"));
+ .put("name", "Annex"))
+ );
final JsonObject getRequestsResponseJson = new JsonObject()
@@ -150,6 +151,7 @@ void canCheckin(Vertx vertx,
assertNull(checkinResponse.getScreenMessage());
assertNull(checkinResponse.getPrintLine());
assertEquals(callNumber, checkinResponse.getCallNumber());
+ assertEquals("Annex", checkinResponse.getPickupServicePoint());
testContext.completeNow();
})));
@@ -1507,4 +1509,20 @@ void cannotGetRequestsByUserId(Vertx vertx,
testContext.completeNow();
})));
}
+
+ @Test
+ void testGetAlertType() {
+ assertEquals("01", CirculationRepository.getAlertType(
+ false, true, false));
+ assertEquals("01", CirculationRepository.getAlertType(
+ false, false, true));
+ assertEquals("02", CirculationRepository.getAlertType(
+ true, false, true));
+ assertEquals("02", CirculationRepository.getAlertType(
+ true, true, false));
+ assertEquals("04", CirculationRepository.getAlertType(
+ true, false, false));
+ assertNull(CirculationRepository.getAlertType(
+ false, false, false));
+ }
}
diff --git a/src/test/java/org/folio/edge/sip2/repositories/ConfigurationRepositoryTests.java b/src/test/java/org/folio/edge/sip2/repositories/ConfigurationRepositoryTests.java
index 6e3a12e4..1bcfba55 100644
--- a/src/test/java/org/folio/edge/sip2/repositories/ConfigurationRepositoryTests.java
+++ b/src/test/java/org/folio/edge/sip2/repositories/ConfigurationRepositoryTests.java
@@ -152,5 +152,35 @@ public void canRetrieveTenantConfiguration(
testContext.completeNow();
})));
}
+
+ @Test
+ public void canRetrieveLocaleConfigurationWithAlternateCurrency(
+ Vertx vertx,
+ VertxTestContext testContext, @Mock Clock clock) {
+
+ List> configParamsList = new ArrayList<>();
+ LinkedHashMap configParamsSet = new LinkedHashMap<>();
+ configParamsSet.put("module", "edge-sip2");
+ configParamsSet.put("configName", "acsTenantConfig");
+ String configKey = String.format("%s.%s.%s", "ORG", "localeSettings", "null");
+
+ configParamsList.add(configParamsSet);
+
+ IResourceProvider resourceProvider =
+ new DefaultResourceProvider("json/DefaultACSConfigurationNonDefaultedCurrency.json");
+ ConfigurationRepository configRepo = new ConfigurationRepository(resourceProvider, clock);
+
+ configRepo.retrieveConfigurations(TestUtils.getMockedSessionData(),
+ configParamsList).onComplete(
+ testContext.succeeding(testTenantConfig -> testContext.verify(() -> {
+ assertNotNull(testTenantConfig);
+
+ JsonObject config = testTenantConfig.get(configKey);
+
+ assertEquals("EUR", config.getString("currency"));
+
+ testContext.completeNow();
+ })));
+ }
}
diff --git a/src/test/resources/json/DefaultACSConfigurationNonDefaultedCurrency.json b/src/test/resources/json/DefaultACSConfigurationNonDefaultedCurrency.json
new file mode 100644
index 00000000..a2800d9e
--- /dev/null
+++ b/src/test/resources/json/DefaultACSConfigurationNonDefaultedCurrency.json
@@ -0,0 +1,31 @@
+{
+ "configs": [
+ {
+ "id": "acd6dae1-b1ae-4780-8e06-993fbed00061",
+ "module": "edge-sip2",
+ "configName": "selfCheckoutConfig.TL01",
+ "enabled": true,
+ "value": "{\"timeoutPeriod\": 5,\"retriesAllowed\": 3,\"terminalDelimeter\" : \"\\r\",\"fieldDelimeter\" : \"|\",\"errorDetectionEnabled\" : true,\"charset\" : \"IBM850\",\"SCtimeZone\" : \"CEST\",\"checkinOk\": true,\"checkoutOk\": false,\"acsRenewalPolicy\": true,\"maxPrintWidth\" : 200,\"libraryName\": \"diku\",\"terminalLocation\": \"TL01\"}"
+ },
+ {
+ "id": "fb0ba4d3-6591-4a2c-99a7-d4009d711b4a",
+ "module": "edge-sip2",
+ "configName": "acsTenantConfig",
+ "enabled": true,
+ "value":"{\"tenantId\": \"dikutest\",\"supportedMessages\": [{\"messageName\": \"PATRON_STATUS_REQUEST\",\"isSupported\": \"Y\"},{\"messageName\": \"CHECKOUT\",\"isSupported\": \"N\"},{\"messageName\": \"CHECKIN\",\"isSupported\": \"N\"},{\"messageName\": \"BLOCK_PATRON\",\"isSupported\": \"N\"},{\"messageName\": \"SC_ACS_STATUS\",\"isSupported\": \"Y\"},{\"messageName\": \"LOGIN\",\"isSupported\": \"Y\"},{\"messageName\": \"PATRON_INFORMATION\",\"isSupported\": \"N\"},{\"messageName\": \"END_PATRON_SESSION\",\"isSupported\": \"N\"},{\"messageName\": \"FEE_PAID\",\"isSupported\": \"N\"},{\"messageName\": \"ITEM_INFORMATION\",\"isSupported\": \"N\"},{\"messageName\": \"ITEM_STATUS_UPDATE\",\"isSupported\": \"N\"},{\"messageName\": \"PATRON_ENABLE\",\"isSupported\": \"N\"},{\"messageName\": \"HOLD\",\"isSupported\": \"N\"},{\"messageName\": \"RENEW\",\"isSupported\": \"Y\"},{\"messageName\": \"RENEW_ALL\",\"isSupported\": \"N\"}],\"onlineStatus\": true,\"statusUpdateOk\": false,\"offlineOk\": false,\"protocolVersion\": \"1.23\",\"screenMessage\": \"screenMessages\",\"printLine\": \"line\",\"localTimeZone\" : \"CEST\",\"holdItemsLimit\" : 25,\"overdueItemsLimit\" : 5,\"feeLimit\" : 100,\"currencyType\" : \"Krona\",\"language\" : \"en\"}"
+ },
+ {
+ "id": "4e9b45dd-ed0b-4426-b75e-248e39e5b5d2",
+ "module": "ORG",
+ "configName": "localeSettings",
+ "enabled": true,
+ "value": "{\"locale\":\"en-SE\",\"timezone\":\"Etc/UTC\",\"currency\":\"EUR\"}"
+ }
+ ],
+ "totalRecords": 3,
+ "resultInfo": {
+ "totalRecords": 3,
+ "facets": [],
+ "diagnostics": []
+ }
+}