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 pickupServicePoint value> + <@variableLengthField id="CT" value=value/> + + 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": [] + } +}