diff --git a/.github/workflows/api-doc.yml b/.github/workflows/api-doc.yml index a308c9a80d..d776de3629 100644 --- a/.github/workflows/api-doc.yml +++ b/.github/workflows/api-doc.yml @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.REF }} submodules: recursive diff --git a/.github/workflows/api-lint.yml b/.github/workflows/api-lint.yml index de292dedc9..2949e1b770 100644 --- a/.github/workflows/api-lint.yml +++ b/.github/workflows/api-lint.yml @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive - name: Prepare folio-tools diff --git a/.github/workflows/api-schema-lint.yml b/.github/workflows/api-schema-lint.yml index 7a08b5acfe..1c7b0518b2 100644 --- a/.github/workflows/api-schema-lint.yml +++ b/.github/workflows/api-schema-lint.yml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive - name: Prepare folio-tools diff --git a/NEWS.md b/NEWS.md index 1475e3a785..ca649b1d18 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,55 @@ +## 24.2.1 2024-03-29 + +* Fix item details not fully populated when response contains more than 50 loans (CIRC-2059) + +## 24.2.0 2024-03-21 + +* Update `feesfines` interface version to 19.0 (CIRC-1914) +* Logging in `org.folio.circulation.domain` package (CIRC-1813) +* Logging in circulation domain: D-L (CIRC-1909) +* Logging in circulation domain: M-U (CIRC-1910) +* Logging in circulation resources: O-T (CIRC-1896) +* Check-in/check-out for virtual item (CIRC-1907) +* Revert CIRC-1793 - remove support for token `loan.additionalInfo` in patron notices (CIRC-1942) +* Partial revert of CIRC-1527: bring back the correct delay for scheduled Aged To Lost Fee charging job (CIRC-1947) +* User primary address Country token fetches the Country code in the patron notices (CIRC-1931) +* Add support for Actual Cost Records properties lost in deserialization (CIRC-1769) +* Search-slips endpoint skeleton (CIRC-1932) +* Drools 7.74.1, xstream 1.4.20 (CIRC-1954) +* Fix Hold Shelf Expiration to respect Closed Library Dates (CIRC-1893) +* Add missing permission set for allowed service points endpoint (CIRC-1953) +* Upgrade to RMB 35.1.1, Vert.x 4.4.6, Log4j 2.20.0, mod-pubsub-client 2.11.2 (CIRC-1962) +* Fix test `OverduePeriodCalculatorServiceTest#getOpeningDayDurationTest` +* Request delivery staff slip: Requester country token information displayed wrong `stripes-components.countries` (CIRC-1955) +* Add field `loan.additionalInfo` to notice context (CIRC-1946) +* Asynchronously publish the ITEM_CHECKED_OUT event and ignore the result (CIRC-1950) +* Handle circulation rules update events (CIRC-1958) +* Skip account creation for reminders without a fee (CIRC-1970) +* Fix returning same error code for different error messages (CIRC-1961) +* Do not refresh circulation rules cache on GET and PUT `/circulation/rules` (CIRC-1977) +* Print `pendingLibraryCode` in DCB CheckIn Slips (CIRC-1981) +* The option to print picks slips doesn't activate (CIRC-1994) +* Reminders schedule around closed days (CIRC-1920) +* Use query-based endpoint for to fetch items by barcode (CIRC-1999) +* Pass tenant's timezone in the payload while creating circ log entries (CIRC-2006) +* Set `reminderFee` as loan action (CIRC-1984) +* Implementation for Search Slips API (CIRC-1933) +* Reschedule reminders on renewal (CIRC-1968) +* Unit tests for DCB changes (CIRC-1988) +* Optionally refuse renewal of items with reminders (CIRC-1923) +* Fix incorrect information in the Circulation log for the user barcode for In-house use (CIRC-1985) +* Returning DCB title in response if the item is a virtual item (CIRC-2029) +* Update reminder scheduler to handle printed notices (CIRC-1925) +* Combine items with related records during pagination process (CIRC-2018) +* Upgrade the Actions used by API-related GitHub Workflows (FOLIO-3944) +* Fix patron information in circulation log for check-ins that are not tied to a loan or request (CIRC-2045) +* Add new `displaySummarry` field to the Item schema (CIRC-2036) +* Do not send "Aged to lost" notice for closed loan (CIRC-1965) +* Reschedule reminder notices on recall (CIRC-2030) +* Reschedule reminder notices on due date change (CIRC-2005) +* Upgrade RMB to v35.2.0 (CIRC-2048) +* Support circulationItem for edge-patron requests for DCB Integration (CIRC-1966) + ## 24.0.0 2023-10-11 * Fix NPE when user without barcode tries to check out an item that is already requested (CIRC-1550) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 98e4237619..7c3518710b 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -1996,7 +1996,9 @@ "manualblocks.collection.get", "pubsub.publish.post", "automated-patron-blocks.collection.get", - "circulation-storage.loans-history.collection.get" + "circulation-storage.loans-history.collection.get", + "overdue-fines-policies.item.get", + "overdue-fines-policies.collection.get" ], "visible": false }, @@ -2291,7 +2293,9 @@ "pubsub.publish.post", "configuration.entries.collection.get", "patron-notice.post", - "circulation-storage.loans-history.collection.get" + "circulation-storage.loans-history.collection.get", + "overdue-fines-policies.item.get", + "overdue-fines-policies.collection.get" ], "visible": false }, diff --git a/pom.xml b/pom.xml index 2992bd24de..b5f9a652f2 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 mod-circulation org.folio - 24.1.0-SNAPSHOT + 24.2.2-SNAPSHOT Apache License 2.0 @@ -25,13 +25,13 @@ 4.11.1 7.74.1.Final - 35.1.1 - 4.4.6 - 2.20.0 + 35.2.0 + 4.5.5 + 2.23.1 UTF-8 UTF-8 1.18.28 - 5.3.24 + 6.1.5 3.9.1 diff --git a/ramls/check-in-by-barcode-response.json b/ramls/check-in-by-barcode-response.json index 241777c834..32a97978a6 100644 --- a/ramls/check-in-by-barcode-response.json +++ b/ramls/check-in-by-barcode-response.json @@ -99,6 +99,10 @@ "description": "Volume is intended for monographs when a multipart monograph (e.g. a biography of George Bernard Shaw in three volumes).", "type": "string" }, + "displaySummary": { + "description": "Display summary about the item", + "type": "string" + }, "inTransitDestinationServicePointId": { "description": "Service point an item is intended to be transited to (should only be present when in transit)", "type": "string", diff --git a/ramls/items-in-transit.json b/ramls/items-in-transit.json index 07a0fa333b..ff836ad512 100644 --- a/ramls/items-in-transit.json +++ b/ramls/items-in-transit.json @@ -52,6 +52,10 @@ "description": "The volume of the item", "type": "string" }, + "displaySummary": { + "description": "Display summary about the item", + "type": "string" + }, "yearCaption": { "description": "The year caption of the item", "type": "array", diff --git a/ramls/loan.json b/ramls/loan.json index 5a8d7bc1d5..125f2d1798 100644 --- a/ramls/loan.json +++ b/ramls/loan.json @@ -239,6 +239,10 @@ "type": "string", "readonly": true }, + "displaySummary": { + "description": "Display summary about the item", + "type": "string" + }, "callNumberComponents": { "description": "Elements of a full call number", "$ref": "schema/call-number-components.json", diff --git a/src/main/java/org/folio/circulation/domain/ActualCostRecord.java b/src/main/java/org/folio/circulation/domain/ActualCostRecord.java index a219a8e29a..552669f629 100644 --- a/src/main/java/org/folio/circulation/domain/ActualCostRecord.java +++ b/src/main/java/org/folio/circulation/domain/ActualCostRecord.java @@ -72,6 +72,7 @@ public static class ActualCostRecordItem { private final String volume; private final String enumeration; private final String chronology; + private final String displaySummary; private final String copyNumber; } diff --git a/src/main/java/org/folio/circulation/domain/Item.java b/src/main/java/org/folio/circulation/domain/Item.java index fef9d339d8..c27a296ef1 100644 --- a/src/main/java/org/folio/circulation/domain/Item.java +++ b/src/main/java/org/folio/circulation/domain/Item.java @@ -243,6 +243,10 @@ public String getChronology() { return description.getChronology(); } + public String getDisplaySummary() { + return description.getDisplaySummary(); + } + public String getNumberOfPieces() { return description.getNumberOfPieces(); } diff --git a/src/main/java/org/folio/circulation/domain/ItemDescription.java b/src/main/java/org/folio/circulation/domain/ItemDescription.java index 55d60c745a..a5d98e6f6d 100644 --- a/src/main/java/org/folio/circulation/domain/ItemDescription.java +++ b/src/main/java/org/folio/circulation/domain/ItemDescription.java @@ -9,7 +9,7 @@ @Value public class ItemDescription { public static ItemDescription unknown() { - return new ItemDescription(null, null, null, null, null, null, null, List.of()); + return new ItemDescription(null, null, null, null, null, null, null, null, List.of()); } String barcode; @@ -19,5 +19,6 @@ public static ItemDescription unknown() { String chronology; String numberOfPieces; String descriptionOfPieces; + String displaySummary; @NonNull Collection yearCaption; } diff --git a/src/main/java/org/folio/circulation/domain/Loan.java b/src/main/java/org/folio/circulation/domain/Loan.java index 3cfe4827b3..d89ac21eb5 100644 --- a/src/main/java/org/folio/circulation/domain/Loan.java +++ b/src/main/java/org/folio/circulation/domain/Loan.java @@ -501,6 +501,7 @@ public Loan overrideRenewal(ZonedDateTime dueDate, String basedUponLoanPolicyId, changeDueDate(dueDate); incrementRenewalCount(); changeActionComment(actionComment); + resetReminders(); return this; } @@ -743,6 +744,7 @@ public FeeAmount getRemainingFeeFineAmount() { public void closeLoanAsLostAndPaid() { log.debug("closeLoanAsLostAndPaid:: "); closeLoan(CLOSED_LOAN); + changeReturnDate(ClockUtil.getZonedDateTime()); changeItemStatusForItemAndLoan(ItemStatus.LOST_AND_PAID); } diff --git a/src/main/java/org/folio/circulation/domain/LoanRepresentation.java b/src/main/java/org/folio/circulation/domain/LoanRepresentation.java index a1cd823d34..05ea36fccb 100644 --- a/src/main/java/org/folio/circulation/domain/LoanRepresentation.java +++ b/src/main/java/org/folio/circulation/domain/LoanRepresentation.java @@ -1,11 +1,7 @@ package org.folio.circulation.domain; import static java.util.Objects.isNull; -import static org.folio.circulation.domain.representations.LoanProperties.BORROWER; -import static org.folio.circulation.domain.representations.LoanProperties.LOAN_POLICY; -import static org.folio.circulation.domain.representations.LoanProperties.LOST_ITEM_POLICY; -import static org.folio.circulation.domain.representations.LoanProperties.OVERDUE_FINE_POLICY; -import static org.folio.circulation.domain.representations.LoanProperties.PATRON_GROUP_ID_AT_CHECKOUT; +import static org.folio.circulation.domain.representations.LoanProperties.*; import static org.folio.circulation.support.json.JsonPropertyWriter.write; import java.lang.invoke.MethodHandles; @@ -13,6 +9,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.folio.circulation.domain.policy.Policy; +import org.folio.circulation.domain.policy.RemindersPolicy; import org.folio.circulation.domain.representations.ItemSummaryRepresentation; import org.folio.circulation.domain.representations.LoanProperties; import org.folio.circulation.resources.context.RenewalContext; @@ -59,6 +56,13 @@ public JsonObject extendedLoan(Loan loan) { extendedRepresentation.remove(BORROWER); } + if (loan.getOverdueFinePolicy().isReminderFeesPolicy() + && loan.getLastReminderFeeBilledNumber() != null) { + extendedRepresentation.getJsonObject(REMINDERS).put( + "renewalBlocked", + !loan.getOverdueFinePolicy().getRemindersPolicy().getAllowRenewalOfItemsWithReminderFees()); + } + addPolicy(extendedRepresentation, loan.getLoanPolicy(), LOAN_POLICY); addPolicy(extendedRepresentation, loan.getOverdueFinePolicy(), OVERDUE_FINE_POLICY); addPolicy(extendedRepresentation, loan.getLostItemPolicy(), LOST_ITEM_POLICY); diff --git a/src/main/java/org/folio/circulation/domain/MultipleRecords.java b/src/main/java/org/folio/circulation/domain/MultipleRecords.java index fcb48896b1..43cf488288 100644 --- a/src/main/java/org/folio/circulation/domain/MultipleRecords.java +++ b/src/main/java/org/folio/circulation/domain/MultipleRecords.java @@ -4,6 +4,7 @@ import static java.util.stream.Stream.concat; import static org.folio.circulation.support.json.JsonObjectArrayPropertyFetcher.mapToList; import static org.folio.circulation.support.results.Result.succeeded; +import static org.folio.circulation.support.utils.LogUtil.mapAsString; import static org.folio.circulation.support.utils.LogUtil.multipleRecordsAsString; import java.lang.invoke.MethodHandles; @@ -79,6 +80,19 @@ public MultipleRecords combineRecords(MultipleRecords otherRecords, .firstOrElse(defaultOtherRecord))); } + /** + * Avoids looping through the elements of otherRecords + */ + public MultipleRecords combineRecords(Map otherRecordsMap, + Function keyMapper, BiFunction combiner, R defaultOtherRecord) { + + log.debug("combineRecords:: parameters otherRecordsMap: {}", + () -> mapAsString(otherRecordsMap)); + + return mapRecords(mainRecord -> combiner.apply(mainRecord, + otherRecordsMap.getOrDefault(keyMapper.apply(mainRecord), defaultOtherRecord))); + } + public T firstOrNull() { return firstOrElse(null); } @@ -137,7 +151,6 @@ public MultipleRecords filter(Predicate predicate) { .collect(Collectors.toList()); final int numberOfFilteredOutRecords = totalRecords - filteredRecords.size(); - log.info("filter:: totalRecords: {}", totalRecords); return new MultipleRecords<>(filteredRecords, totalRecords - numberOfFilteredOutRecords); } @@ -155,6 +168,10 @@ public Collection getRecords() { return records; } + public Map getRecordsMap(Function keyMapper) { + return records.stream().collect(Collectors.toMap(keyMapper, identity())); + } + public Integer getTotalRecords() { return totalRecords; } diff --git a/src/main/java/org/folio/circulation/domain/RequestRepresentation.java b/src/main/java/org/folio/circulation/domain/RequestRepresentation.java index 005c2654f8..f7bb0af944 100644 --- a/src/main/java/org/folio/circulation/domain/RequestRepresentation.java +++ b/src/main/java/org/folio/circulation/domain/RequestRepresentation.java @@ -80,6 +80,7 @@ private static void addItemProperties(JsonObject request, Item item) { } write(itemSummary, "volume", item.getVolume()); write(itemSummary, "chronology", item.getChronology()); + write(itemSummary, "displaySummary", item.getDisplaySummary()); ItemStatus status = item.getStatus(); if (status != null) { diff --git a/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java b/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java index 46c6ab3e3d..948a695327 100644 --- a/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java +++ b/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java @@ -208,6 +208,7 @@ private static JsonObject createItemContext(Item item) { .put("loanType", item.getLoanTypeName()) .put("copy", copyNumber) .put("numberOfPieces", item.getNumberOfPieces()) + .put("displaySummary", item.getDisplaySummary()) .put("descriptionOfPieces", item.getDescriptionOfPieces()); Location location = item.getLocation(); diff --git a/src/main/java/org/folio/circulation/domain/notice/schedule/LoanScheduledNoticeHandler.java b/src/main/java/org/folio/circulation/domain/notice/schedule/LoanScheduledNoticeHandler.java index febd1d2237..ead1c2b950 100644 --- a/src/main/java/org/folio/circulation/domain/notice/schedule/LoanScheduledNoticeHandler.java +++ b/src/main/java/org/folio/circulation/domain/notice/schedule/LoanScheduledNoticeHandler.java @@ -173,10 +173,6 @@ private boolean agedToLostNoticeIsNotRelevant(ScheduledNoticeContext context) { ScheduledNotice notice = context.getNotice(); List logMessages = new ArrayList<>(); - if (!isRecurringAfterNotice(notice)) { - return false; - } - if (loan.hasItemWithAnyStatus(DECLARED_LOST, CLAIMED_RETURNED)) { logMessages.add( String.format("Recurring notice for item in status \"%s\"", loan.getItemStatus())); @@ -201,10 +197,6 @@ private boolean agedToLostNoticeIsNotRelevant(ScheduledNoticeContext context) { return true; } - private static boolean isRecurringAfterNotice(ScheduledNotice notice) { - return notice.getConfiguration().isRecurring() && notice.getConfiguration().hasAfterTiming(); - } - private static boolean nextRecurringNoticeIsNotRelevant(ScheduledNotice notice, Loan loan) { ScheduledNoticeConfig noticeConfig = notice.getConfiguration(); diff --git a/src/main/java/org/folio/circulation/domain/policy/LoanPolicy.java b/src/main/java/org/folio/circulation/domain/policy/LoanPolicy.java index 558d0fb3d7..26d9039b3a 100644 --- a/src/main/java/org/folio/circulation/domain/policy/LoanPolicy.java +++ b/src/main/java/org/folio/circulation/domain/policy/LoanPolicy.java @@ -11,7 +11,9 @@ import static org.folio.circulation.support.json.JsonPropertyFetcher.getProperty; import static org.folio.circulation.support.results.Result.succeeded; import static org.folio.circulation.support.utils.DateTimeUtil.isAfterMillis; +import static org.folio.circulation.support.utils.LogUtil.resultAsString; +import java.lang.invoke.MethodHandles; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collections; @@ -20,8 +22,9 @@ import java.util.Optional; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.folio.circulation.domain.Loan; -import org.folio.circulation.domain.Request; import org.folio.circulation.domain.RequestQueue; import org.folio.circulation.domain.RequestStatus; import org.folio.circulation.domain.RequestType; @@ -37,6 +40,8 @@ @ToString(onlyExplicitlyIncluded = true) public class LoanPolicy extends Policy { + private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass()); + private static final String LOANS_POLICY_KEY = "loansPolicy"; private static final String PERIOD_KEY = "period"; @@ -88,7 +93,8 @@ public static LoanPolicy unknown(String id) { public Result calculateInitialDueDate(Loan loan, RequestQueue requestQueue) { final ZonedDateTime systemTime = ClockUtil.getZonedDateTime(); - return determineStrategy(requestQueue, false, false, systemTime).calculateDueDate(loan); + return determineStrategy(requestQueue, false, false, systemTime, loan.getItemId()) + .calculateDueDate(loan); } public boolean hasRenewalPeriod() { @@ -139,11 +145,8 @@ public Integer getRenewalLimit() { return getIntegerProperty(getRenewalsPolicy(), "numberAllowed", 0); } - public DueDateStrategy determineStrategy( - RequestQueue requestQueue, - boolean isRenewal, - boolean isRenewalWithHoldRequest, - ZonedDateTime systemDate) { + public DueDateStrategy determineStrategy(RequestQueue requestQueue, boolean isRenewal, + boolean isRenewalWithHoldRequest, ZonedDateTime systemDate, String itemId) { final JsonObject loansPolicy = getLoansPolicy(); final JsonObject renewalsPolicy = getRenewalsPolicy(); @@ -163,7 +166,7 @@ systemDate, getRenewFrom(), getRenewalPeriod(loansPolicy, renewalsPolicy, isRene else { boolean useAlternatePeriod = false; Period rollingPeriod = getPeriod(loansPolicy); - if(isAlternatePeriod(requestQueue)) { + if (isAlternatePeriod(requestQueue, itemId)) { rollingPeriod = getPeriod(holds, ALTERNATE_CHECKOUT_LOAN_PERIOD_KEY); useAlternatePeriod = true; } @@ -178,7 +181,7 @@ else if(isFixed(loansPolicy)) { getRenewalFixedDueDateSchedules(), systemDate, this::loanPolicyValidationError); } else { - if(isAlternatePeriod(requestQueue)) { + if (isAlternatePeriod(requestQueue, itemId)) { return new RollingCheckOutDueDateStrategy(getId(), getName(), getPeriod(holds, ALTERNATE_CHECKOUT_LOAN_PERIOD_KEY), fixedDueDateSchedules, this::loanPolicyValidationError, false); @@ -199,23 +202,17 @@ private ValidationError loanPolicyValidationError(String message) { return RenewalValidator.loanPolicyValidationError(this, message); } - private boolean isAlternatePeriod(RequestQueue requestQueue) { - final JsonObject holds = getHolds(); - if(Objects.isNull(requestQueue) - || !holds.containsKey(ALTERNATE_CHECKOUT_LOAN_PERIOD_KEY)) { + private boolean isAlternatePeriod(RequestQueue requestQueue, String itemId) { + if (Objects.isNull(requestQueue) || !getHolds().containsKey( + ALTERNATE_CHECKOUT_LOAN_PERIOD_KEY)) { + return false; } - Optional potentialRequest = requestQueue.getRequests().stream().skip(1).findFirst(); - boolean isAlternateDueDateSchedule = false; - if(potentialRequest.isPresent()) { - Request request = potentialRequest.get(); - boolean isHold = request.getRequestType() == RequestType.HOLD; - boolean isOpenNotYetFilled = request.getStatus() == RequestStatus.OPEN_NOT_YET_FILLED; - if(isHold && isOpenNotYetFilled) { - isAlternateDueDateSchedule = true; - } - } - return isAlternateDueDateSchedule; + + return requestQueue.getRequests().stream() + .anyMatch(request -> request.getRequestType() == RequestType.HOLD && + request.getStatus() == RequestStatus.OPEN_NOT_YET_FILLED && + (!request.hasItem() || itemId.equals(request.getItemId()))); } private JsonObject getLoansPolicy() { @@ -470,16 +467,35 @@ private Result determineDueDate(Result minimumGuar return minimumGuaranteedDueDateResult.combine(recallDueDateResult, (minimumGuaranteedDueDate, recallDueDate) -> { - if (loan.isOverdue() && !allowRecallsToExtendOverdueLoans()) { + log.debug("determineDueDate:: parameters minimumGuaranteedDueDateResult: {}, " + + "recallDueDateResult: {}, loan: {}", () -> resultAsString(minimumGuaranteedDueDateResult), + () -> resultAsString(recallDueDateResult), () -> loan); - return loan.getDueDate(); + ZonedDateTime currentDueDate = loan.getDueDate(); + + if (loan.isOverdue() && !allowRecallsToExtendOverdueLoans()) { + log.info("determineDueDate:: loan is overdue and allowRecallsToExtendOverdueLoans is " + + "disabled - keeping current due date"); + return currentDueDate; } - if (minimumGuaranteedDueDate == null || - isAfterMillis(recallDueDate, minimumGuaranteedDueDate)) { - return recallDueDate; + if (isAfterMillis(recallDueDate, currentDueDate) && !allowRecallsToExtendOverdueLoans()) { + log.info("determineDueDate:: current due date is before recall due date and " + + "allowRecallsToExtendOverdueLoans is disabled - keeping current due date"); + return currentDueDate; } else { - return minimumGuaranteedDueDate; + if (minimumGuaranteedDueDate == null || + isAfterMillis(recallDueDate, minimumGuaranteedDueDate)) { + + log.info("determineDueDate:: minimum guaranteed period doesn't exist or recall due " + + "date is after minimum guaranteed due date - changing due date to recall due date"); + return recallDueDate; + } else { + log.info("determineDueDate:: minimum guaranteed period exists and recall due " + + "date is before minimum guaranteed due date - changing due date to minimum " + + "guaranteed due date"); + return minimumGuaranteedDueDate; + } } }); } diff --git a/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java b/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java index c689058e42..efb1db2699 100644 --- a/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java +++ b/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java @@ -39,6 +39,7 @@ public JsonObject createItemSummary(Item item) { write(itemSummary, "callNumber", item.getCallNumber()); write(itemSummary, "enumeration", item.getEnumeration()); write(itemSummary, "chronology", item.getChronology()); + write(itemSummary, "displaySummary", item.getDisplaySummary()); write(itemSummary, "volume", item.getVolume()); write(itemSummary, "copyNumber", item.getCopyNumber()); write(itemSummary, CALL_NUMBER_COMPONENTS, diff --git a/src/main/java/org/folio/circulation/domain/representations/ItemsInTransitReport.java b/src/main/java/org/folio/circulation/domain/representations/ItemsInTransitReport.java index 0b684ab4ce..d8a48e6fe1 100644 --- a/src/main/java/org/folio/circulation/domain/representations/ItemsInTransitReport.java +++ b/src/main/java/org/folio/circulation/domain/representations/ItemsInTransitReport.java @@ -112,6 +112,7 @@ private JsonObject buildEntry(Item item) { write(entry, "contributors", mapContributorNamesToJson(item)); write(entry, "callNumber", item.getCallNumber()); write(entry, "enumeration", item.getEnumeration()); + write(entry, "displaySummary", item.getDisplaySummary()); write(entry, "volume", item.getVolume()); write(entry, "yearCaption", item.getYearCaption()); writeNamedObject(entry, "status", ofNullable(item.getStatus()) diff --git a/src/main/java/org/folio/circulation/domain/validation/RenewalOfItemsWithReminderFeesValidator.java b/src/main/java/org/folio/circulation/domain/validation/RenewalOfItemsWithReminderFeesValidator.java index 24eaffb509..410debac04 100644 --- a/src/main/java/org/folio/circulation/domain/validation/RenewalOfItemsWithReminderFeesValidator.java +++ b/src/main/java/org/folio/circulation/domain/validation/RenewalOfItemsWithReminderFeesValidator.java @@ -3,6 +3,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.folio.circulation.domain.Loan; +import org.folio.circulation.domain.override.BlockOverrides; import org.folio.circulation.domain.policy.OverdueFinePolicy; import org.folio.circulation.resources.context.RenewalContext; import org.folio.circulation.support.HttpFailure; @@ -14,6 +15,7 @@ import static java.util.concurrent.CompletableFuture.completedFuture; import static org.folio.circulation.support.ValidationErrorFailure.singleValidationError; +import static org.folio.circulation.support.json.JsonPropertyFetcher.getObjectProperty; import static org.folio.circulation.support.results.Result.failed; import static org.folio.circulation.support.results.Result.succeeded; @@ -27,13 +29,12 @@ public CompletableFuture> blockRenewalIfReminderFeesExist Result blockRenewalIfRuledByRemindersFeePolicy(RenewalContext renewalContext) { log.debug("blockRenewalIfRuledByRemindersFeePolicy:: parameters: renewalContext: {}", renewalContext); - - Loan loan = renewalContext.getLoan(); - Integer lastFeeBilledCount = loan.getLastReminderFeeBilledNumber(); - OverdueFinePolicy overdueFinePolicy = loan.getOverdueFinePolicy(); - Boolean allowRenewalWithReminders = overdueFinePolicy.getRemindersPolicy().getAllowRenewalOfItemsWithReminderFees(); - - if ((lastFeeBilledCount != null && lastFeeBilledCount > 0) && Boolean.FALSE.equals(allowRenewalWithReminders)) { + BlockOverrides overrides = BlockOverrides.from(getObjectProperty(renewalContext.getRenewalRequest(), "overrideBlocks")); + final boolean overrideRenewalBlock = overrides.getRenewalBlockOverride().isRequested(); + final boolean overrideRenewalBlockDueDateRequired = overrides.getRenewalDueDateRequiredBlockOverride().isRequested(); + if (overrideRenewalBlock || overrideRenewalBlockDueDateRequired) { + return succeeded(renewalContext); + } else if (renewalBlockedDueToReminders(renewalContext)) { String reason = "Renewals not allowed for loans with reminders."; log.info("createBlockedRenewalDueToReminderFeesPolicyError:: {}", reason); return failed(createBlockedRenewalDueToReminderFeesPolicyError("Renewals not allowed for loans with reminders.", reason)); @@ -48,4 +49,19 @@ private HttpFailure createBlockedRenewalDueToReminderFeesPolicyError(String mess return singleValidationError(new ValidationError(message, "reason", reason)); } + + private static boolean renewalBlockedDueToReminders(RenewalContext renewalContext) { + Loan loan = renewalContext.getLoan(); + return loanHasReminders(loan) && policyBlocksRenewalWithReminders(loan); + } + + private static Boolean loanHasReminders(Loan loan) { + return loan.getLastReminderFeeBilledNumber() != null && loan.getLastReminderFeeBilledNumber() > 0; + } + + private static boolean policyBlocksRenewalWithReminders (Loan loan) { + OverdueFinePolicy overdueFinePolicy = loan.getOverdueFinePolicy(); + Boolean allowRenewalWithReminders = overdueFinePolicy.getRemindersPolicy().getAllowRenewalOfItemsWithReminderFees(); + return Boolean.FALSE.equals(allowRenewalWithReminders); + } } diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/HoldingsRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/HoldingsRepository.java index 30a144e7be..22ce77055a 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/HoldingsRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/HoldingsRepository.java @@ -19,6 +19,7 @@ import io.vertx.core.json.JsonObject; public class HoldingsRepository { + private static final String HOLDINGS_RECORDS = "holdingsRecords"; private final CollectionResourceClient holdingsClient; public HoldingsRepository(CollectionResourceClient holdingsClient) { @@ -42,7 +43,7 @@ CompletableFuture>> fetchByInstanceId(String in final var mapper = new HoldingsMapper(); final var holdingsRecordFetcher = findWithCqlQuery( - holdingsClient, "holdingsRecords", mapper::toDomain); + holdingsClient, HOLDINGS_RECORDS, mapper::toDomain); return holdingsRecordFetcher.findByQuery(exactMatch("instanceId", instanceId)); } @@ -52,7 +53,7 @@ CompletableFuture>> fetchByIds( final var mapper = new HoldingsMapper(); - return findWithMultipleCqlIndexValues(holdingsClient, "holdingsRecords", + return findWithMultipleCqlIndexValues(holdingsClient, HOLDINGS_RECORDS, mapper::toDomain) .findByIds(holdingsRecordIds); } diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/InstanceRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/InstanceRepository.java index 8c4b81d232..0c3f44ebb3 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/InstanceRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/InstanceRepository.java @@ -27,6 +27,7 @@ import org.folio.circulation.support.results.Result; public class InstanceRepository { + private static final String INSTANCES = "instances"; private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass()); private final CollectionResourceClient instancesClient; @@ -55,8 +56,7 @@ public CompletableFuture>> fetchByIds( InstanceMapper mapper = new InstanceMapper(); - return findWithMultipleCqlIndexValues(instancesClient, "instances", - mapper::toDomain) + return findWithMultipleCqlIndexValues(instancesClient, INSTANCES, mapper::toDomain) .findByIds(instanceIds); } @@ -79,7 +79,7 @@ public CompletableFuture>> findInstancesForReque InstanceMapper mapper = new InstanceMapper(); - return findWithMultipleCqlIndexValues(instancesClient, "instances", mapper::toDomain) + return findWithMultipleCqlIndexValues(instancesClient, INSTANCES, mapper::toDomain) .findByIds(instanceIdsToFetch) .thenApply(multipleInstancesResult -> multipleInstancesResult.next( multipleInstances -> { diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepository.java index 5a0fc559ae..3603768dda 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/ItemRepository.java @@ -1,6 +1,7 @@ package org.folio.circulation.infrastructure.storage.inventory; import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.supplyAsync; import static java.util.function.Function.identity; import static org.folio.circulation.domain.ItemStatus.AVAILABLE; import static org.folio.circulation.domain.MultipleRecords.CombinationMatchers.matchRecordsById; @@ -23,6 +24,7 @@ import java.lang.invoke.MethodHandles; import java.util.Collection; import java.util.HashSet; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.BiFunction; import java.util.function.Function; @@ -173,13 +175,11 @@ private CompletableFuture>> fetchLocations( return result.combineAfter(this::fetchLocations, (items, locations) -> items - .combineRecords(locations, matchRecordsById(Item::getPermanentLocationId, Location::getId), - Item::withPermanentLocation, null) - .combineRecords(locations, matchRecordsById(Item::getEffectiveLocationId, Location::getId), - Item::withLocation, null)); + .combineRecords(locations, Item::getPermanentLocationId, Item::withPermanentLocation, null) + .combineRecords(locations, Item::getEffectiveLocationId, Item::withLocation, null)); } - private CompletableFuture>> fetchLocations( + private CompletableFuture>> fetchLocations( MultipleRecords items) { final var locationIds = items.toKeys(Item::getEffectiveLocationId); @@ -190,17 +190,18 @@ private CompletableFuture>> fetchLocations( allLocationIds.addAll(locationIds); allLocationIds.addAll(permanentLocationIds); - return locationRepository.fetchLocations(allLocationIds); + return locationRepository.fetchLocations(allLocationIds) + .thenApply(r -> r.map(records -> records.getRecordsMap(Location::getId))); } private CompletableFuture>> fetchMaterialTypes( Result> result) { - return result.after(items -> - materialTypeRepository.getMaterialTypes(items) + return supplyAsync(() -> result.after(items -> materialTypeRepository.getMaterialTypes(items) + .thenApply(r -> r.map(records -> records.getRecordsMap(MaterialType::getId))) .thenApply(mapResult(materialTypes -> items.combineRecords(materialTypes, - matchRecordsById(Item::getMaterialTypeId, MaterialType::getId), - Item::withMaterialType, MaterialType.unknown())))); + Item::getMaterialTypeId, Item::withMaterialType, MaterialType.unknown()))))) + .thenCompose(Function.identity()); } private CompletableFuture>> fetchLoanTypes( @@ -209,10 +210,11 @@ private CompletableFuture>> fetchLoanTypes( return result.after(items -> { final var loanTypeIdsToFetch = items.toKeys(Item::getLoanTypeId); - return loanTypeRepository.findByIds(loanTypeIdsToFetch) + return supplyAsync(() -> loanTypeRepository.findByIds(loanTypeIdsToFetch) + .thenApply(r -> r.map(records -> records.getRecordsMap(LoanType::getId))) .thenApply(mapResult(loanTypes -> items.combineRecords(loanTypes, - matchRecordsById(Item::getLoanTypeId, LoanType::getId), - Item::withLoanType, LoanType.unknown()))); + Item::getLoanTypeId, Item::withLoanType, LoanType.unknown())))) + .thenCompose(Function.identity()); }); } @@ -222,10 +224,11 @@ private CompletableFuture>> fetchInstances( return result.after(items -> { final var instanceIds = items.toKeys(Item::getInstanceId); - return instanceRepository.fetchByIds(instanceIds) + return supplyAsync(() -> instanceRepository.fetchByIds(instanceIds) + .thenApply(r -> r.map(records -> records.getRecordsMap(Instance::getId))) .thenApply(mapResult(instances -> items.combineRecords(instances, - matchRecordsById(Item::getInstanceId, Instance::getId), - Item::withInstance, Instance.unknown()))); + Item::getInstanceId, Item::withInstance, Instance.unknown())))) + .thenCompose(Function.identity()); }); } @@ -235,10 +238,11 @@ private CompletableFuture>> fetchHoldingsRecords( return result.after(items -> { final var holdingsIds = items.toKeys(Item::getHoldingsRecordId); - return holdingsRepository.fetchByIds(holdingsIds) + return supplyAsync(() -> holdingsRepository.fetchByIds(holdingsIds) + .thenApply(r -> r.map(records -> records.getRecordsMap(Holdings::getId))) .thenApply(mapResult(holdings -> items.combineRecords(holdings, - matchRecordsById(Item::getHoldingsRecordId, Holdings::getId), - Item::withHoldings, Holdings.unknown()))); + Item::getHoldingsRecordId, Item::withHoldings, Holdings.unknown())))) + .thenCompose(Function.identity()); }); } diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/LoanTypeRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/LoanTypeRepository.java index ad358663c1..d9085446d3 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/LoanTypeRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/LoanTypeRepository.java @@ -15,6 +15,7 @@ import org.folio.circulation.support.results.Result; public class LoanTypeRepository { + private static final String LOAN_TYPES = "loantypes"; public final CollectionResourceClient loanTypesClient; public LoanTypeRepository(CollectionResourceClient loanTypesClient) { @@ -34,8 +35,7 @@ public CompletableFuture> fetchById(String id) { CompletableFuture>> findByIds(Set ids) { final var mapper = new LoanTypeMapper(); - return findWithMultipleCqlIndexValues(loanTypesClient, - "loantypes", mapper::toDomain) + return findWithMultipleCqlIndexValues(loanTypesClient, LOAN_TYPES, mapper::toDomain) .findByIds(ids); } } diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/MaterialTypeRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/MaterialTypeRepository.java index 06424981ec..08828cb40d 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/MaterialTypeRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/MaterialTypeRepository.java @@ -21,6 +21,7 @@ public class MaterialTypeRepository { private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass()); + private static final String MATERIAL_TYPES = "mtypes"; private final CollectionResourceClient materialTypesStorageClient; public MaterialTypeRepository(Clients clients) { @@ -55,7 +56,7 @@ public CompletableFuture>> getMaterialTypes final var materialTypeIds = inventoryRecords.toKeys(Item::getMaterialTypeId); final var fetcher - = findWithMultipleCqlIndexValues(materialTypesStorageClient, "mtypes", mapper::toDomain); + = findWithMultipleCqlIndexValues(materialTypesStorageClient, MATERIAL_TYPES, mapper::toDomain); return fetcher.findByIds(materialTypeIds); } diff --git a/src/main/java/org/folio/circulation/resources/RenewalValidator.java b/src/main/java/org/folio/circulation/resources/RenewalValidator.java index 6b4e583f88..f7bede794e 100644 --- a/src/main/java/org/folio/circulation/resources/RenewalValidator.java +++ b/src/main/java/org/folio/circulation/resources/RenewalValidator.java @@ -103,7 +103,8 @@ public static ValidationError errorForNotMatchingOverrideCases(LoanPolicy loanPo "renewal date falls outside of the date ranges in the loan policy, " + "items cannot be renewed when there is an active recall request, " + DECLARED_LOST_ITEM_RENEWED_ERROR + ", item is Aged to lost, " + - "renewal would not change the due date"; + "renewal would not change the due date, " + + "loan has reminder fees"; return loanPolicyValidationError(loanPolicy, reason); } diff --git a/src/main/java/org/folio/circulation/resources/renewal/RenewalResource.java b/src/main/java/org/folio/circulation/resources/renewal/RenewalResource.java index cd4753aa40..d3780208da 100644 --- a/src/main/java/org/folio/circulation/resources/renewal/RenewalResource.java +++ b/src/main/java/org/folio/circulation/resources/renewal/RenewalResource.java @@ -492,6 +492,10 @@ private Result overrideRenewal(Loan loan, ZonedDateTime systemDate, return processRenewal(newDueDateResult, loan, comment); } + if (loan.getLastReminderFeeBilledNumber() != null && loan.getLastReminderFeeBilledNumber()>0) { + return processRenewal(newDueDateResult, loan, comment); + } + return failedValidation(errorForNotMatchingOverrideCases(loanPolicy)); } catch (Exception e) { @@ -537,7 +541,8 @@ private Result calculateNewDueDate(ZonedDateTime overrideDueDate, private Result calculateProposedDueDate(Loan loan, ZonedDateTime systemDate) { return loan.getLoanPolicy() - .determineStrategy(null, true, false, systemDate).calculateDueDate(loan); + .determineStrategy(null, true, false, systemDate, loan.getItemId()) + .calculateDueDate(loan); } private boolean newDueDateAfterCurrentDueDate(Loan loan, Result proposedDueDateResult) { @@ -665,7 +670,7 @@ private Result calculateNewDueDate(Loan loan, RequestQueue reques final var loanPolicy = loan.getLoanPolicy(); final var isRenewalWithHoldRequest = firstRequestForLoanedItemIsHold(requestQueue, loan); - return loanPolicy.determineStrategy(null, true, isRenewalWithHoldRequest, systemDate) + return loanPolicy.determineStrategy(null, true, isRenewalWithHoldRequest, systemDate, loan.getItemId()) .calculateDueDate(loan); } diff --git a/src/main/java/org/folio/circulation/services/CloseLoanWithLostItemService.java b/src/main/java/org/folio/circulation/services/CloseLoanWithLostItemService.java index d85062664a..6b9ba98c83 100644 --- a/src/main/java/org/folio/circulation/services/CloseLoanWithLostItemService.java +++ b/src/main/java/org/folio/circulation/services/CloseLoanWithLostItemService.java @@ -49,7 +49,7 @@ public CloseLoanWithLostItemService(LoanRepository loanRepository, ItemRepositor } public CompletableFuture> closeLoanAsLostAndPaid(Loan loan) { - if (loan == null || !loan.isItemLost()) { + if (loan == null || loan.isClosed() || !loan.isItemLost()) { return emptyAsync(); } diff --git a/src/main/java/org/folio/circulation/services/EventPublisher.java b/src/main/java/org/folio/circulation/services/EventPublisher.java index 16b1ae30a5..3a34524b04 100644 --- a/src/main/java/org/folio/circulation/services/EventPublisher.java +++ b/src/main/java/org/folio/circulation/services/EventPublisher.java @@ -28,6 +28,7 @@ import static org.folio.circulation.support.AsyncCoordinationUtil.allOf; import static org.folio.circulation.support.json.JsonPropertyWriter.write; import static org.folio.circulation.support.results.CommonFailures.failedDueToServerError; +import static org.folio.circulation.support.results.Result.emptyAsync; import static org.folio.circulation.support.results.Result.ofAsync; import static org.folio.circulation.support.results.Result.succeeded; import static org.folio.circulation.support.utils.ClockUtil.getZonedDateTime; @@ -124,23 +125,16 @@ public CompletableFuture> publishItemCheckedOutEve } public CompletableFuture> publishItemCheckedInEvents( - CheckInContext checkInContext, UserRepository userRepository, LoanRepository loanRepository) { - - runAsync(() -> userRepository.getUser(checkInContext.getLoggedInUserId()) - .thenCombineAsync(loanRepository.findLastLoanForItem(checkInContext.getItem().getItemId()), (userResult, lastLoan) -> { - if (nonNull(lastLoan.value())) { - return userRepository.getUser(lastLoan.value().getUserId()) - .thenApply(userFromLastLoan -> Result.succeeded(pubSubPublishingService.publishEvent(LOG_RECORD.name(), - mapToCheckInLogEventContent(checkInContext, userResult.value(), - checkInContext.isInHouseUse() ? null : userFromLastLoan.value())))); - } - return userResult.after(loggedInUser -> CompletableFuture.completedFuture( - Result.succeeded(pubSubPublishingService.publishEvent(LOG_RECORD.name(), - mapToCheckInLogEventContent(checkInContext, loggedInUser, null))))); - })); + CheckInContext context, UserRepository userRepository, LoanRepository loanRepository) { - if (checkInContext.getLoan() != null) { - Loan loan = checkInContext.getLoan(); + runAsync(() -> userRepository.getUser(context.getLoggedInUserId()) + .thenCompose(r1 -> r1.after(loggedInUser -> getUserForLastLoan(context, userRepository, loanRepository) + .thenCompose(r -> r.after(userFromLastLoan -> pubSubPublishingService.publishEvent(LOG_RECORD.name(), + mapToCheckInLogEventContent(context, loggedInUser, userFromLastLoan)).thenApply(Result::succeeded))))) + ); + + if (context.getLoan() != null) { + Loan loan = context.getLoan(); JsonObject payloadJsonObject = new JsonObject(); write(payloadJsonObject, USER_ID_FIELD, loan.getUserId()); @@ -148,10 +142,33 @@ public CompletableFuture> publishItemCheckedInEvents( write(payloadJsonObject, RETURN_DATE_FIELD, loan.getReturnDate()); return pubSubPublishingService.publishEvent(ITEM_CHECKED_IN.name(), payloadJsonObject.encode()) - .handle((result, error) -> handlePublishEventError(error, checkInContext)); + .handle((result, error) -> handlePublishEventError(error, context)); + } + + return completedFuture(succeeded(context)); + } + + private CompletableFuture> getUserForLastLoan(CheckInContext context, + UserRepository userRepository, LoanRepository loanRepository) { + + if (context.isInHouseUse() || context.getRequestQueue().getRequests().isEmpty()) { + return emptyAsync(); + } + + return loanRepository.findLastLoanForItem(context.getItem().getItemId()) + .thenCompose(r -> r.after(lastLoan -> getUserForLastLoan(context, lastLoan, userRepository, loanRepository))); + } + + private CompletableFuture> getUserForLastLoan(CheckInContext context, Loan lastLoan, + UserRepository userRepository, LoanRepository loanRepository) { + + if (lastLoan == null) { + return emptyAsync(); } - return completedFuture(succeeded(checkInContext)); + return userRepository.getUser(lastLoan.getUserId()) + .thenCompose(r1 -> r1.after(userFromLastLoan -> loanRepository.findOpenLoanForItem(context.getItem()) + .thenApply(r2 -> r2.map(openLoan -> openLoan == null ? null : userFromLastLoan)))); } public CompletableFuture> publishDeclaredLostEvent(Loan loan) { diff --git a/src/main/java/org/folio/circulation/services/LostItemFeeChargingService.java b/src/main/java/org/folio/circulation/services/LostItemFeeChargingService.java index d63a2d7099..98fde67018 100644 --- a/src/main/java/org/folio/circulation/services/LostItemFeeChargingService.java +++ b/src/main/java/org/folio/circulation/services/LostItemFeeChargingService.java @@ -24,13 +24,10 @@ import org.folio.circulation.domain.FeeFine; import org.folio.circulation.domain.FeeFineOwner; import org.folio.circulation.domain.Loan; -import org.folio.circulation.domain.Location; import org.folio.circulation.domain.policy.lostitem.LostItemPolicy; import org.folio.circulation.domain.policy.lostitem.itemfee.AutomaticallyChargeableFee; -import org.folio.circulation.domain.representations.DeclareItemLostRequest; import org.folio.circulation.infrastructure.storage.ActualCostRecordRepository; import org.folio.circulation.infrastructure.storage.ServicePointRepository; -import org.folio.circulation.infrastructure.storage.feesandfines.FeeFineOwnerRepository; import org.folio.circulation.infrastructure.storage.feesandfines.FeeFineRepository; import org.folio.circulation.infrastructure.storage.inventory.IdentifierTypeRepository; import org.folio.circulation.infrastructure.storage.inventory.LocationRepository; diff --git a/src/main/java/org/folio/circulation/services/actualcostrecord/ActualCostRecordService.java b/src/main/java/org/folio/circulation/services/actualcostrecord/ActualCostRecordService.java index 5f07823b09..bf035041aa 100644 --- a/src/main/java/org/folio/circulation/services/actualcostrecord/ActualCostRecordService.java +++ b/src/main/java/org/folio/circulation/services/actualcostrecord/ActualCostRecordService.java @@ -207,6 +207,7 @@ private ActualCostRecord buildActualCostRecord(ActualCostRecordContext context) .withVolume(item.getVolume()) .withChronology(item.getChronology()) .withEnumeration(item.getEnumeration()) + .withDisplaySummary(item.getDisplaySummary()) .withCopyNumber(item.getCopyNumber())) .withInstance(new ActualCostRecordInstance() .withId(instance.getId()) diff --git a/src/main/java/org/folio/circulation/services/agedtolost/ChargeLostFeesWhenAgedToLostService.java b/src/main/java/org/folio/circulation/services/agedtolost/ChargeLostFeesWhenAgedToLostService.java index 67f8a55bd4..4f6f2372cf 100644 --- a/src/main/java/org/folio/circulation/services/agedtolost/ChargeLostFeesWhenAgedToLostService.java +++ b/src/main/java/org/folio/circulation/services/agedtolost/ChargeLostFeesWhenAgedToLostService.java @@ -53,9 +53,9 @@ import org.folio.circulation.infrastructure.storage.loans.LostItemPolicyRepository; import org.folio.circulation.infrastructure.storage.users.PatronGroupRepository; import org.folio.circulation.infrastructure.storage.users.UserRepository; -import org.folio.circulation.services.actualcostrecord.ActualCostRecordService; import org.folio.circulation.services.EventPublisher; import org.folio.circulation.services.FeeFineFacade; +import org.folio.circulation.services.actualcostrecord.ActualCostRecordService; import org.folio.circulation.services.support.CreateAccountCommand; import org.folio.circulation.support.Clients; import org.folio.circulation.support.fetching.PageableFetcher; diff --git a/src/main/java/org/folio/circulation/storage/mappers/ActualCostRecordMapper.java b/src/main/java/org/folio/circulation/storage/mappers/ActualCostRecordMapper.java index ecc59157c3..4216ca13c6 100644 --- a/src/main/java/org/folio/circulation/storage/mappers/ActualCostRecordMapper.java +++ b/src/main/java/org/folio/circulation/storage/mappers/ActualCostRecordMapper.java @@ -75,6 +75,7 @@ public static JsonObject toJson(ActualCostRecord actualCostRecord) { write(itemJson, "volume", item.getVolume()); write(itemJson, "enumeration", item.getEnumeration()); write(itemJson, "chronology", item.getChronology()); + write(itemJson, "displaySummary", item.getDisplaySummary()); write(itemJson, "copyNumber", item.getCopyNumber()); write(itemJson, "effectiveCallNumberComponents", createCallNumberComponents(item.getEffectiveCallNumberComponents())); @@ -152,6 +153,7 @@ public static ActualCostRecord toDomain(JsonObject representation) { .withVolume(getProperty(item, "volume")) .withEnumeration(getProperty(item, "enumeration")) .withChronology(getProperty(item, "chronology")) + .withDisplaySummary(getProperty(item, "displaySummary")) .withCopyNumber(getProperty(item, "copyNumber")) .withEffectiveCallNumberComponents(CallNumberComponents.fromItemJson(item)), new ActualCostRecordInstance() diff --git a/src/main/java/org/folio/circulation/storage/mappers/ItemMapper.java b/src/main/java/org/folio/circulation/storage/mappers/ItemMapper.java index 0005469185..aa56840d65 100644 --- a/src/main/java/org/folio/circulation/storage/mappers/ItemMapper.java +++ b/src/main/java/org/folio/circulation/storage/mappers/ItemMapper.java @@ -43,6 +43,7 @@ private ItemDescription getDescription(JsonObject representation) { getProperty(representation, "chronology"), getProperty(representation, "numberOfPieces"), getProperty(representation, "descriptionOfPieces"), + getProperty(representation, "displaySummary"), toStream(representation, "yearCaption") .collect(Collectors.toList())); } diff --git a/src/main/java/org/folio/circulation/support/FindWithMultipleCqlIndexValues.java b/src/main/java/org/folio/circulation/support/FindWithMultipleCqlIndexValues.java index f300f2578c..7ef36a54f8 100644 --- a/src/main/java/org/folio/circulation/support/FindWithMultipleCqlIndexValues.java +++ b/src/main/java/org/folio/circulation/support/FindWithMultipleCqlIndexValues.java @@ -9,7 +9,7 @@ import org.folio.circulation.support.results.Result; public interface FindWithMultipleCqlIndexValues { - CompletableFuture>> findByIds( + CompletableFuture>> findByIds( Collection ids); CompletableFuture>> findByIdIndexAndQuery( diff --git a/src/main/java/org/folio/circulation/support/fetching/CqlIndexValuesFinder.java b/src/main/java/org/folio/circulation/support/fetching/CqlIndexValuesFinder.java index 8e3bb544ab..b0d2583875 100644 --- a/src/main/java/org/folio/circulation/support/fetching/CqlIndexValuesFinder.java +++ b/src/main/java/org/folio/circulation/support/fetching/CqlIndexValuesFinder.java @@ -4,10 +4,10 @@ import static java.util.stream.Collectors.collectingAndThen; import static org.apache.commons.collections4.ListUtils.partition; import static org.folio.circulation.domain.MultipleRecords.empty; -import static org.folio.circulation.support.results.Result.of; import static org.folio.circulation.support.fetching.MultipleCqlIndexValuesCriteria.byId; import static org.folio.circulation.support.fetching.MultipleCqlIndexValuesCriteria.byIndex; import static org.folio.circulation.support.http.client.PageLimit.maximumLimit; +import static org.folio.circulation.support.results.Result.of; import java.util.ArrayList; import java.util.Collection; @@ -18,8 +18,8 @@ import org.folio.circulation.domain.MultipleRecords; import org.folio.circulation.support.FindWithCqlQuery; import org.folio.circulation.support.FindWithMultipleCqlIndexValues; -import org.folio.circulation.support.results.Result; import org.folio.circulation.support.http.client.CqlQuery; +import org.folio.circulation.support.results.Result; import lombok.val; diff --git a/src/test/java/api/handlers/CloseAgedToLostLoanWhenLostItemFeesAreClosedApiTests.java b/src/test/java/api/handlers/CloseAgedToLostLoanWhenLostItemFeesAreClosedApiTests.java index 0df74fb8c4..6cdae78a1d 100644 --- a/src/test/java/api/handlers/CloseAgedToLostLoanWhenLostItemFeesAreClosedApiTests.java +++ b/src/test/java/api/handlers/CloseAgedToLostLoanWhenLostItemFeesAreClosedApiTests.java @@ -22,6 +22,7 @@ import org.folio.circulation.domain.Loan; import org.folio.circulation.domain.policy.Period; import org.folio.circulation.services.agedtolost.LoanToChargeFees; +import org.folio.circulation.support.utils.ClockUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -47,13 +48,17 @@ void shouldCloseLoanWhenAllFeesClosed() { feeFineAccountFixture.payLostItemFee(loan.getId()); feeFineAccountFixture.payLostItemProcessingFee(loan.getId()); + var returnDate = ClockUtil.getZonedDateTime(); + mockClockManagerToReturnFixedDateTime(returnDate); eventSubscribersFixture.publishLoanRelatedFeeFineClosedEvent(loan.getId()); - assertThatLoanIsClosedAsLostAndPaid(); + JsonObject loan = assertThatLoanIsClosedAsLostAndPaid(); + assertThat(loan.getString("returnDate"), is(returnDate.toString())); + mockClockManagerToReturnDefaultDateTime(); List loanClosedEvents = getPublishedEventsAsList(byEventType(LOAN_CLOSED)); assertThat(loanClosedEvents, hasSize(1)); - assertThat(loanClosedEvents.get(0), isValidLoanClosedEvent(loan.getJson())); + assertThat(loanClosedEvents.get(0), isValidLoanClosedEvent(loan)); } @Test @@ -224,9 +229,15 @@ void shouldCloseLoanWhenActualCostRecordIsCancelled() { item = result.getItem(); loan = result.getLoan(); + + var returnDate = ClockUtil.getZonedDateTime(); + mockClockManagerToReturnFixedDateTime(returnDate); cancelActualCostRecord(); + eventSubscribersFixture.publishLoanRelatedFeeFineClosedEvent(loan.getId()); - assertThatLoanIsClosedAsLostAndPaid(); + JsonObject loan = assertThatLoanIsClosedAsLostAndPaid(); + assertThat(loan.getString("returnDate"), is(returnDate.toString())); + mockClockManagerToReturnDefaultDateTime(); } @Test diff --git a/src/test/java/api/handlers/CloseDeclaredLostLoanWhenLostItemFeesAreClosedApiTests.java b/src/test/java/api/handlers/CloseDeclaredLostLoanWhenLostItemFeesAreClosedApiTests.java index f63fb42f21..70033a03e6 100644 --- a/src/test/java/api/handlers/CloseDeclaredLostLoanWhenLostItemFeesAreClosedApiTests.java +++ b/src/test/java/api/handlers/CloseDeclaredLostLoanWhenLostItemFeesAreClosedApiTests.java @@ -21,6 +21,7 @@ import org.folio.circulation.domain.ItemStatus; import org.folio.circulation.domain.policy.Period; import org.folio.circulation.support.http.client.Response; +import org.folio.circulation.support.utils.ClockUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -54,12 +55,17 @@ void shouldCloseLoanWhenAllFeesClosed() { feeFineAccountFixture.payLostItemFee(loan.getId()); feeFineAccountFixture.payLostItemProcessingFee(loan.getId()); + var returnDate = ClockUtil.getZonedDateTime(); + mockClockManagerToReturnFixedDateTime(returnDate); eventSubscribersFixture.publishLoanRelatedFeeFineClosedEvent(loan.getId()); - assertThatLoanIsClosedAsLostAndPaid(); + + JsonObject loan = assertThatLoanIsClosedAsLostAndPaid(); + assertThat(loan.getString("returnDate"), is(returnDate.toString())); + mockClockManagerToReturnDefaultDateTime(); List loanClosedEvents = getPublishedEventsAsList(byEventType(LOAN_CLOSED)); assertThat(loanClosedEvents, hasSize(1)); - assertThat(loanClosedEvents.get(0), isValidLoanClosedEvent(loan.getJson())); + assertThat(loanClosedEvents.get(0), isValidLoanClosedEvent(loan)); } @Test @@ -241,14 +247,8 @@ void shouldIgnoreErrorWhenNonExistentLoanIdProvided() { void shouldNotPublishLoanClosedEventWhenLoanIsOriginallyClosed() { feeFineAccountFixture.payLostItemFee(loan.getId()); feeFineAccountFixture.payLostItemProcessingFee(loan.getId()); - - JsonObject loanToClose = loansStorageClient.get(loan).getJson(); - loanToClose.getJsonObject("status").put("name", "Closed"); - loansStorageClient.replace(loan.getId(), loanToClose); - + checkInFixture.checkInByBarcode(item); eventSubscribersFixture.publishLoanRelatedFeeFineClosedEvent(loan.getId()); - assertThatLoanIsClosedAsLostAndPaid(); - assertThat(getPublishedEventsAsList(byEventType(LOAN_CLOSED)), empty()); } diff --git a/src/test/java/api/handlers/CloseLostLoanWhenLostItemFeesAreClosed.java b/src/test/java/api/handlers/CloseLostLoanWhenLostItemFeesAreClosed.java index 245bfb8253..0608ffef1b 100644 --- a/src/test/java/api/handlers/CloseLostLoanWhenLostItemFeesAreClosed.java +++ b/src/test/java/api/handlers/CloseLostLoanWhenLostItemFeesAreClosed.java @@ -7,6 +7,7 @@ import static api.support.matchers.LoanMatchers.isClosed; import static api.support.matchers.LoanMatchers.isOpen; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.time.ZonedDateTime; import java.util.UUID; @@ -78,9 +79,13 @@ protected void payLostItemActualCostFeeAndProcessingFeeAndCheckThatLoanIsClosed( assertThatLoanIsClosedAsLostAndPaid(); } - protected void assertThatLoanIsClosedAsLostAndPaid() { - assertThat(loansFixture.getLoanById(loan.getId()).getJson(), isClosed()); + protected JsonObject assertThatLoanIsClosedAsLostAndPaid() { + JsonObject loan = loansFixture.getLoanById(this.loan.getId()).getJson(); + assertThat(loan, isClosed()); + assertNotNull(loan.getString("returnDate")); assertThat(itemsClient.getById(item.getId()).getJson(), isLostAndPaid()); + + return loan; } protected void assertThatLoanIsOpenAndLost() { diff --git a/src/test/java/api/loans/AgedToLostScheduledNoticesProcessingTests.java b/src/test/java/api/loans/AgedToLostScheduledNoticesProcessingTests.java index 9d09dac7fe..3b03369630 100644 --- a/src/test/java/api/loans/AgedToLostScheduledNoticesProcessingTests.java +++ b/src/test/java/api/loans/AgedToLostScheduledNoticesProcessingTests.java @@ -293,6 +293,25 @@ void shouldStopSendingAgedToLostNoticesOnceItemIsRenewedThroughOverride() { verifyNumberOfPublishedEvents(NOTICE_ERROR, 0); } + @Test + void shouldRemoveAgedToLostNoticeIfLoanIsClosed() { + AgeToLostResult agedToLostLoan = createOneTimeAgedToLostNotice(); + checkInFixture.checkInByBarcode(agedToLostLoan.getItem()); + + assertThat(itemsFixture.getById(agedToLostLoan.getItemId()).getJson(), isAvailable()); + assertThat(loansFixture.getLoanById(agedToLostLoan.getLoanId()).getJson(), isClosed()); + verifyNumberOfScheduledNotices(1); + + final ZonedDateTime firstRunTime = TIMING_PERIOD.plusDate(getAgedToLostDate(agedToLostLoan)); + scheduledNoticeProcessingClient.runLoanNoticesProcessing( + RECURRENCE_PERIOD.plusDate(firstRunTime).plusMinutes(1)); + + verifyNumberOfSentNotices(0); + verifyNumberOfScheduledNotices(0); + verifyNumberOfPublishedEvents(NOTICE, 0); + verifyNumberOfPublishedEvents(NOTICE_ERROR, 0); + } + private AgeToLostResult createRecurringAgedToLostNotice() { val agedToLostLoan = ageToLostFixture.createAgedToLostLoan( new NoticePolicyBuilder() @@ -312,6 +331,24 @@ private AgeToLostResult createRecurringAgedToLostNotice() { return agedToLostLoan; } + private AgeToLostResult createOneTimeAgedToLostNotice() { + val agedToLostLoan = ageToLostFixture.createAgedToLostLoan( + new NoticePolicyBuilder() + .active() + .withName("Aged to lost notice policy") + .withLoanNotices(Collections.singletonList(new NoticeConfigurationBuilder() + .withAgedToLostEvent() + .withTemplateId(AFTER_RECURRING_TEMPLATE_ID) + .withAfterTiming(TIMING_PERIOD) + .create()))); + + Awaitility.await() + .atMost(1, TimeUnit.SECONDS) + .until(scheduledNoticesClient::getAll, hasSize(1)); + + return agedToLostLoan; + } + @Test void patronNoticesForForAgedToLostFineAdjustmentsAreCreatedAndProcessed() { LostItemFeePolicyBuilder lostItemFeePolicyBuilder = lostItemFeePoliciesFixture diff --git a/src/test/java/api/loans/CheckInByBarcodeTests.java b/src/test/java/api/loans/CheckInByBarcodeTests.java index 525a619fc3..6b8ee96495 100644 --- a/src/test/java/api/loans/CheckInByBarcodeTests.java +++ b/src/test/java/api/loans/CheckInByBarcodeTests.java @@ -120,7 +120,6 @@ class CheckInByBarcodeTests extends APITests { private final static String OPEN_NOT_YET_FILLED = "Open - Not yet filled"; private final static String OPEN_AWAITING_PICKUP = "Open - Awaiting pickup"; private final static String OPEN_AWAITING_DELIVERY = "Open - Awaiting delivery"; - private final static String REQUEST_POSITION = "position"; private static final String LOAN_INFO_ADDED = "testing patron info"; public CheckInByBarcodeTests() { @@ -141,7 +140,8 @@ void canCloseAnOpenLoanByCheckingInTheItem() { .withTemporaryLocation(homeLocation.getId()) .withEnumeration("v.70:no.1-6") .withChronology("1987:Jan.-June") - .withVolume("testVolume")); + .withVolume("testVolume") + .withDisplaySummary("test displaySummary")); final IndividualResource loan = checkOutFixture.checkOutByBarcode(nod, james, ZonedDateTime.of(2018, 3, 1, 13, 25, 46, 0, UTC)); @@ -208,6 +208,9 @@ void canCloseAnOpenLoanByCheckingInTheItem() { assertThat("has item chronology", itemFromResponse.getString("chronology"), is("1987:Jan.-June")); + assertThat("has item displaySummary", + itemFromResponse.getString("displaySummary"), is("test displaySummary")); + assertThat("has item volume", itemFromResponse.getString("volume"), is("testVolume")); @@ -1840,11 +1843,19 @@ void shouldNotLinkTitleLevelHoldRequestToAnItemUponCheckInWhenItemIsNonRequestab overdueFinePoliciesFixture.noOverdueFine().getId(), lostItemFeePoliciesFixture.facultyStandard().getId()); - checkInFixture.checkInByBarcode(item); + CheckInByBarcodeResponse checkInResponse = checkInFixture.checkInByBarcode(item); JsonObject requestAfterCheckIn = requestsFixture.getById(request.getId()).getJson(); assertThat(requestAfterCheckIn.getString("itemId"), nullValue()); assertThat(requestAfterCheckIn, RequestMatchers.isOpenNotYetFilled()); + + final var publishedEvents = waitAtMost(2, SECONDS) + .until(FakePubSub::getPublishedEvents, hasSize(5)); + final var checkedInEvent = publishedEvents.findFirst(byEventType(ITEM_CHECKED_IN.name())); + assertThat(checkedInEvent, isValidItemCheckedInEvent(checkInResponse.getLoan())); + final var checkInLogEvent = publishedEvents.findFirst(byLogEventType(CHECK_IN.value())); + assertThat(checkInLogEvent, isValidCheckInLogEvent(checkInResponse.getLoan())); + } @Test diff --git a/src/test/java/api/loans/LoanAPITests.java b/src/test/java/api/loans/LoanAPITests.java index e42fb4eb9c..faa2b5abdb 100644 --- a/src/test/java/api/loans/LoanAPITests.java +++ b/src/test/java/api/loans/LoanAPITests.java @@ -34,11 +34,14 @@ import java.net.HttpURLConnection; import java.time.Period; import java.time.ZonedDateTime; +import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import org.awaitility.Awaitility; @@ -70,7 +73,8 @@ void canCreateALoan() { item -> item .withEnumeration("v.70:no.1-6") .withChronology("1987:Jan.-June") - .withVolume("testVolume")); + .withVolume("testVolume") + .withDisplaySummary("testDisplaySummary")); UUID itemId = smallAngryPlanet.getId(); @@ -142,6 +146,9 @@ void canCreateALoan() { assertThat("has item volume", loan.getJsonObject("item").getString("volume"), is("testVolume")); + assertThat("has item displaySummary", + loan.getJsonObject("item").getString("displaySummary"), is("testDisplaySummary")); + JsonArray contributors = loan.getJsonObject("item").getJsonArray("contributors"); assertThat("item has a single contributor", @@ -1665,6 +1672,84 @@ void canGetPagedLoansWhenIdQueryWouldExceedQueryStringLengthLimit() { queryLoans(100); } + @Test + void canGetMultiplePagesOfLoans() { + var numberOfItems = 200; + var itemAdditionalProperties = IntStream.range(0, numberOfItems) + .boxed() + .map(num -> (Function) itemBuilder -> itemBuilder + .withEnumeration(format("testEnumeration-%d", num)) + .withChronology(format("testChronology-%d", num)) + .withVolume(format("testVolume-%d", num)) + .withDisplaySummary(format("testDisplaySummary-%d", num)) + .withCopyNumber(format("testCopyNumber-%d", num))) + .toList(); + List items = itemsFixture.createMultipleItemsOnePerInstance(numberOfItems, + itemAdditionalProperties); + items.forEach(checkOutFixture::checkOutByBarcode); + var loans = loansFixture.getLoans(limit(numberOfItems)); + + loans.forEach(loan -> { + var item = itemsFixture.getById(UUID.fromString(loan.getJsonObject("item").getString("id"))); + assertThat("ID is taken from item", loan.getJsonObject("item").containsKey("id"), is(true)); + + assertThat("title is taken from item", + loan.getJsonObject("item").getString("title"), + is("The Long Way to a Small, Angry Planet")); + + assertThat("barcode is taken from item", + loan.getJsonObject("item").getString("barcode"), + is(item.getBarcode())); + + assertThat("call number is 123456", loan.getJsonObject("item") + .getString("callNumber"), is("123456")); + + assertThat(loan.getJsonObject("item").encode() + " contains 'materialType'", + loan.getJsonObject("item").containsKey("materialType"), is(true)); + + assertThat("materialType is book", loan.getJsonObject("item") + .getJsonObject("materialType").getString("name"), is("Book")); + + assertThat("item has contributors", + loan.getJsonObject("item").containsKey("contributors"), is(true)); + + JsonArray contributors = loan.getJsonObject("item").getJsonArray("contributors"); + assertThat("item has a single contributor", contributors.size(), is(1)); + + assertThat("Becky Chambers is a contributor", + contributors.getJsonObject(0).getString("name"), is("Chambers, Becky")); + + assertThat("has item status", + loan.getJsonObject("item").containsKey("status"), is(true)); + + assertThat("status is taken from item", + loan.getJsonObject("item").getJsonObject("status").getString("name"), + is("Checked out")); + + assertThat("has item location", + loan.getJsonObject("item").containsKey("location"), is(true)); + + assertThat("has item enumeration", loan.getJsonObject("item").getString("enumeration"), + is(item.getJson().getString("enumeration"))); + + assertThat("has item chronology", loan.getJsonObject("item").getString("chronology"), + is(item.getJson().getString("chronology"))); + + assertThat("has item volume", loan.getJsonObject("item").getString("volume"), + is(item.getJson().getString("volume"))); + + assertThat("has item display summary", loan.getJsonObject("item").getString("displaySummary"), + is(item.getJson().getString("displaySummary"))); + + assertThat("has item copy number", loan.getJsonObject("item").getString("copyNumber"), + is(item.getJson().getString("copyNumber"))); + + assertThat("location is taken from holding", + loan.getJsonObject("item").getJsonObject("location").getString("name"), + is("3rd Floor")); + }); + } + private void createLoans(int total) { final IndividualResource mainFloor = locationsFixture.mainFloor(); for(int i = 0; i < total; i++) { diff --git a/src/test/java/api/loans/OverrideRenewByBarcodeTests.java b/src/test/java/api/loans/OverrideRenewByBarcodeTests.java index eb8b2bd95b..b6d4f0b1f1 100644 --- a/src/test/java/api/loans/OverrideRenewByBarcodeTests.java +++ b/src/test/java/api/loans/OverrideRenewByBarcodeTests.java @@ -521,7 +521,8 @@ void cannotOverrideRenewalWhenLoanDoesNotMatchAnyOfOverrideCases() { "renewal date falls outside of the date ranges in the loan policy, " + "items cannot be renewed when there is an active recall request, " + "item is Declared lost, item is Aged to lost, " + - "renewal would not change the due date")))); + "renewal would not change the due date, " + + "loan has reminder fees")))); } @Test diff --git a/src/test/java/api/loans/ReminderFeeTests.java b/src/test/java/api/loans/ReminderFeeTests.java index 5b1c43242d..9d88528e01 100644 --- a/src/test/java/api/loans/ReminderFeeTests.java +++ b/src/test/java/api/loans/ReminderFeeTests.java @@ -733,6 +733,7 @@ void willResetRemindersRescheduleNoticeOnRenewal() { !loanAfterRenewal.containsKey("reminders")); } + @Test void willResetRemindersRescheduleNoticeOnRecall() { useFallbackPolicies( diff --git a/src/test/java/api/loans/agetolost/ScheduledAgeToLostFeeChargingApiTest.java b/src/test/java/api/loans/agetolost/ScheduledAgeToLostFeeChargingApiTest.java index e207200dce..87fb375c29 100644 --- a/src/test/java/api/loans/agetolost/ScheduledAgeToLostFeeChargingApiTest.java +++ b/src/test/java/api/loans/agetolost/ScheduledAgeToLostFeeChargingApiTest.java @@ -2,9 +2,13 @@ import static api.support.PubsubPublisherTestUtils.assertThatPublishedLoanLogRecordEventsAreValid; import static api.support.builders.DeclareItemLostRequestBuilder.forLoan; +import static api.support.http.CqlQuery.queryFromTemplate; import static api.support.matchers.AccountMatchers.isOpen; +import static api.support.matchers.AccountMatchers.isPaidFully; import static api.support.matchers.ActualCostRecordMatchers.isActualCostRecord; import static api.support.matchers.ItemMatchers.isAgedToLost; +import static api.support.matchers.ItemMatchers.isAvailable; +import static api.support.matchers.ItemMatchers.isCheckedOut; import static api.support.matchers.ItemMatchers.isDeclaredLost; import static api.support.matchers.ItemMatchers.isLostAndPaid; import static api.support.matchers.JsonObjectMatcher.hasJsonPath; @@ -25,6 +29,7 @@ import static org.folio.circulation.support.utils.ClockUtil.getZonedDateTime; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.iterableWithSize; @@ -40,10 +45,12 @@ import org.awaitility.Awaitility; import org.folio.circulation.domain.ItemLossType; import org.folio.circulation.domain.policy.Period; +import org.folio.circulation.domain.policy.lostitem.ChargeAmountType; import org.hamcrest.Matcher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import api.support.MultipleJsonRecords; import api.support.builders.FeeFineOwnerBuilder; import api.support.builders.ItemBuilder; import api.support.builders.LostItemFeePolicyBuilder; @@ -617,6 +624,77 @@ void shouldNotScheduleNoticeIfFeeFineBalanceChangedEventWithoutLoanId() { assertThat(scheduledNoticesClient.getAll(), hasSize(0)); } + @Test + void closingLostItemFeeDoesNotAffectMoreRecentLoanForSameItem() { + ItemResource item = itemsFixture.basedUponNod(); + UUID itemId = item.getId(); + UserResource firstPatron = usersFixture.steve(); + UserResource secondPatron = usersFixture.james(); + + policiesActivation.use(PoliciesToActivate.builder().lostItemPolicy( + lostItemFeePoliciesFixture.create( + new LostItemFeePolicyBuilder() + .withName("Processing fee only, no refund on check-in") + .withItemAgedToLostAfterOverdue(Period.minutes(1)) + .withPatronBilledAfterItemAgedToLost(Period.minutes(1)) + .withNoChargeAmountItem() + .withLostItemProcessingFee(1.0) + .withChargeAmountItemPatron(true) + .doNotRefundProcessingFeeWhenReturned() + ))); + + // create first loan and declare item lost + CheckOutResource firstLoan = checkOutFixture.checkOutByBarcode(item, firstPatron); + UUID firstLoanId = firstLoan.getId(); + assertThat(itemsFixture.getById(itemId).getJson(), isCheckedOut()); + declareLostFixtures.declareItemLost(firstLoanId); + + assertThat(feeFineAccountFixture.getAccounts(firstLoanId), hasSize(1)); + assertThat(firstLoan, hasLostItemProcessingFee(isOpen(1.0))); + assertThat(itemsFixture.getById(itemId).getJson(), isDeclaredLost()); + assertThat(loansFixture.getLoanById(firstLoanId).getJson(), isOpen()); + + // check in first loan + checkInFixture.checkInByBarcode(item); + int firstLoanHistorySizeAfterCheckIn = getLoanHistory(firstLoanId).size(); + + assertThat(feeFineAccountFixture.getAccounts(firstLoanId), hasSize(1)); + assertThat(firstLoan, hasLostItemProcessingFee(isOpen(1.0))); + assertThat(itemsFixture.getById(itemId).getJson(), isAvailable()); + assertThat(loansFixture.getLoanById(firstLoanId).getJson(), isClosed()); + + // create second loan and declare item lost + CheckOutResource secondLoan = checkOutFixture.checkOutByBarcode(item, secondPatron); + UUID secondLoanId = secondLoan.getId(); + assertThat(itemsFixture.getById(itemId).getJson(), isCheckedOut()); + declareLostFixtures.declareItemLost(secondLoanId); + + assertThat(feeFineAccountFixture.getAccounts(secondLoanId), hasSize(1)); + assertThat(secondLoan, hasLostItemProcessingFee(isOpen(1.0))); + assertThat(itemsFixture.getById(itemId).getJson(), isDeclaredLost()); + assertThat(loansFixture.getLoanById(firstLoanId).getJson(), isClosed()); + assertThat(loansFixture.getLoanById(secondLoanId).getJson(), isOpen()); + + // pay lost item fee for first loan + feeFineAccountFixture.payLostItemProcessingFee(firstLoanId); + eventSubscribersFixture.publishLoanRelatedFeeFineClosedEvent(firstLoanId); + + assertThat(getLoanHistory(firstLoanId).size(), equalTo(firstLoanHistorySizeAfterCheckIn)); + assertThat(firstLoan, hasLostItemProcessingFee(allOf(isClosed(), isPaidFully()))); + assertThat(itemsFixture.getById(itemId).getJson(), isDeclaredLost()); + assertThat(loansFixture.getLoanById(firstLoanId).getJson(), isClosed()); + assertThat(loansFixture.getLoanById(secondLoanId).getJson(), isOpen()); + + // pay lost item fee for second loan + feeFineAccountFixture.payLostItemProcessingFee(secondLoanId); + eventSubscribersFixture.publishLoanRelatedFeeFineClosedEvent(secondLoanId); + + assertThat(secondLoan, hasLostItemProcessingFee(allOf(isClosed(), isPaidFully()))); + assertThat(itemsFixture.getById(itemId).getJson(), isLostAndPaid()); + assertThat(loansFixture.getLoanById(firstLoanId).getJson(), isClosed()); + assertThat(loansFixture.getLoanById(secondLoanId).getJson(), isClosed()); + } + private Map checkoutTenItems() { val loanToFeeMap = new LinkedHashMap(); @@ -681,4 +759,9 @@ private NoticePolicyBuilder createNoticePolicyWithAgedToLostChargedNotice() { .withUponAtTiming() .create())); } + + private MultipleJsonRecords getLoanHistory(UUID loanId) { + return loanHistoryClient.getMany( + queryFromTemplate("loan.id=\"%s\" sortBy createdDate/sort.descending", loanId)); + } } diff --git a/src/test/java/api/loans/scenarios/CheckoutWithRequestScenarioTests.java b/src/test/java/api/loans/scenarios/CheckoutWithRequestScenarioTests.java index f156540874..6332320521 100644 --- a/src/test/java/api/loans/scenarios/CheckoutWithRequestScenarioTests.java +++ b/src/test/java/api/loans/scenarios/CheckoutWithRequestScenarioTests.java @@ -4,11 +4,13 @@ import static api.support.matchers.TextDateTimeMatcher.isEquivalentTo; import static java.time.ZoneOffset.UTC; import static org.folio.circulation.domain.policy.DueDateManagement.KEEP_THE_CURRENT_DUE_DATE; +import static org.folio.circulation.domain.policy.Period.days; import static org.folio.circulation.domain.policy.Period.weeks; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import java.time.ZonedDateTime; +import java.util.List; import java.util.UUID; import org.folio.circulation.support.http.client.Response; @@ -19,6 +21,7 @@ import api.support.builders.FixedDueDateSchedulesBuilder; import api.support.builders.LoanPolicyBuilder; import api.support.builders.RequestBuilder; +import api.support.http.CqlQuery; import api.support.http.IndividualResource; import api.support.http.ItemResource; @@ -159,4 +162,141 @@ void checkingOutWithHoldRequestAppliesAlternatePeriodAndScheduledForFixedPolicy( assertThat(changedItem.getJson().getJsonObject("status").getString("name"), is("Checked out")); } + + @Test + void alternatePeriodShouldBeAppliedWhenRequestQueueContainsHoldTlr() { + configurationsFixture.enableTlrFeature(); + List items = itemsFixture.createMultipleItemsForTheSameInstance(2); + var firstItem = items.get(0); + var secondItem = items.get(1); + var instanceId = firstItem.getInstanceId(); + + var james = usersFixture.james(); + var charlotte = usersFixture.charlotte(); + var steve = usersFixture.steve(); + var pickupServicePointId = servicePointsFixture.cd1().getId(); + + var loanPolicy = loanPoliciesFixture.create(new LoanPolicyBuilder() + .withName("Limited loan period for items with hold requests") + .rolling(days(5)) + .withAlternateCheckoutLoanPeriod(days(1)) + .withClosedLibraryDueDateManagement(KEEP_THE_CURRENT_DUE_DATE.getValue())); + useFallbackPolicies( + loanPolicy.getId(), + requestPoliciesFixture.allowPageAndHoldRequestPolicy().getId(), + noticePoliciesFixture.inactiveNotice().getId(), + overdueFinePoliciesFixture.facultyStandard().getId(), + lostItemFeePoliciesFixture.facultyStandard().getId()); + + requestsClient.create(new RequestBuilder() + .page() + .titleRequestLevel() + .withNoItemId() + .withNoHoldingsRecordId() + .withInstanceId(instanceId) + .withPickupServicePointId(pickupServicePointId) + .by(charlotte)); + requestsClient.create(new RequestBuilder() + .page() + .titleRequestLevel() + .withNoItemId() + .withNoHoldingsRecordId() + .withInstanceId(instanceId) + .withPickupServicePointId(pickupServicePointId) + .by(james)); + requestsClient.create(new RequestBuilder() + .hold() + .titleRequestLevel() + .withNoItemId() + .withNoHoldingsRecordId() + .withInstanceId(instanceId) + .withPickupServicePointId(pickupServicePointId) + .by(steve)); + + checkInFixture.checkInByBarcode(firstItem); + checkInFixture.checkInByBarcode(secondItem); + + String firstRequesterBarcode = requestsClient.getMany(CqlQuery.exactMatch( + "itemId", firstItem.getId().toString())).getFirst().getJsonObject("requester") + .getString("barcode"); + ZonedDateTime loanDate = ZonedDateTime.of(2024, 1, 1, 11, 0, 0, 0, UTC); + final IndividualResource firstLoan = checkOutFixture.checkOutByBarcode( + new CheckOutByBarcodeRequestBuilder() + .forItem(firstItem) + .to(firstRequesterBarcode) + .at(pickupServicePointId) + .on(loanDate)); + assertThat(firstLoan.getJson().getString("dueDate"), isEquivalentTo( + ZonedDateTime.of(2024, 1, 2, 23, 59, 59, 0, UTC))); + + String secondRequesterBarcode = requestsClient.getMany(CqlQuery.exactMatch( + "itemId", secondItem.getId().toString())).getFirst().getJsonObject("requester") + .getString("barcode"); + loanDate = ZonedDateTime.of(2024, 1, 10, 11, 0, 0, 0, UTC); + final IndividualResource secondLoan = checkOutFixture.checkOutByBarcode( + new CheckOutByBarcodeRequestBuilder() + .forItem(secondItem) + .to(secondRequesterBarcode) + .at(pickupServicePointId) + .on(loanDate)); + assertThat(secondLoan.getJson().getString("dueDate"), isEquivalentTo( + ZonedDateTime.of(2024, 1, 11, 23, 59, 59, 0, UTC))); + } + + @Test + void alternatePeriodShouldNotBeAppliedWhenRequestQueueContainsHoldIlrForDifferentItem() { + configurationsFixture.enableTlrFeature(); + List items = itemsFixture.createMultipleItemsForTheSameInstance(2); + var firstItem = items.get(0); + var secondItem = items.get(1); + var instanceId = firstItem.getInstanceId(); + + var james = usersFixture.james(); + var charlotte = usersFixture.charlotte(); + var pickupServicePointId = servicePointsFixture.cd1().getId(); + + var loanPolicy = loanPoliciesFixture.create(new LoanPolicyBuilder() + .withName("Limited loan period for items with hold requests") + .rolling(days(5)) + .withAlternateCheckoutLoanPeriod(days(1)) + .withClosedLibraryDueDateManagement(KEEP_THE_CURRENT_DUE_DATE.getValue())); + useFallbackPolicies( + loanPolicy.getId(), + requestPoliciesFixture.allowPageAndHoldRequestPolicy().getId(), + noticePoliciesFixture.inactiveNotice().getId(), + overdueFinePoliciesFixture.facultyStandard().getId(), + lostItemFeePoliciesFixture.facultyStandard().getId()); + + checkOutFixture.checkOutByBarcode(secondItem); + requestsClient.create(new RequestBuilder() + .page() + .titleRequestLevel() + .withNoItemId() + .withNoHoldingsRecordId() + .withInstanceId(instanceId) + .withPickupServicePointId(pickupServicePointId) + .by(charlotte)); + requestsClient.create(new RequestBuilder() + .hold() + .itemRequestLevel() + .withItemId(secondItem.getId()) + .withInstanceId(secondItem.getInstanceId()) + .withPickupServicePointId(pickupServicePointId) + .by(james)); + + checkInFixture.checkInByBarcode(firstItem); + + String firstRequesterBarcode = requestsClient.getMany(CqlQuery.exactMatch( + "itemId", firstItem.getId().toString())).getFirst().getJsonObject("requester") + .getString("barcode"); + ZonedDateTime loanDate = ZonedDateTime.of(2024, 1, 1, 11, 0, 0, 0, UTC); + final IndividualResource firstLoan = checkOutFixture.checkOutByBarcode( + new CheckOutByBarcodeRequestBuilder() + .forItem(firstItem) + .to(firstRequesterBarcode) + .at(pickupServicePointId) + .on(loanDate)); + assertThat(firstLoan.getJson().getString("dueDate"), isEquivalentTo( + ZonedDateTime.of(2024, 1, 6, 23, 59, 59, 0, UTC))); + } } diff --git a/src/test/java/api/requests/RequestsAPICreationTests.java b/src/test/java/api/requests/RequestsAPICreationTests.java index 716ce6a2ab..ae49db5a36 100644 --- a/src/test/java/api/requests/RequestsAPICreationTests.java +++ b/src/test/java/api/requests/RequestsAPICreationTests.java @@ -313,6 +313,7 @@ void canCreateARequest() { assertThat(requestItem.getString("enumeration"), is("enumeration1")); assertThat(requestItem.getString("chronology"), is("chronology")); assertThat(requestItem.getString("volume"), is("vol.1")); + assertThat(requestItem.getString("displaySummary"), is("displaySummary")); JsonArray identifiers = requestInstance.getJsonArray("identifiers"); assertThat(identifiers, notNullValue()); @@ -1481,8 +1482,9 @@ void canHaveUserBarcodeInCheckInPublishedEventAfterTitleLevelRequest() { checkInFixture.checkInByBarcode(item); checkOutFixture.checkOutByBarcode(item, usersFixture.charlotte()).getJson(); - FakePubSub.getPublishedEvents().stream().map(event -> new JsonObject(event.getString("eventPayload"))) - .filter(event -> event.containsKey("logEventType") && event.getString("logEventType").equals("CHECK_IN_EVENT")) + FakePubSub.getPublishedEvents().stream().map(event -> new JsonObject(event.getString("eventPayload"))) + .filter(event -> event.containsKey("logEventType") && event.getString("logEventType").equals("CHECK_IN_EVENT") + && !event.containsKey("requests")) .forEach(event -> assertTrue(event.containsKey("userBarcode"))); } diff --git a/src/test/java/api/requests/RequestsAPILoanRenewalTests.java b/src/test/java/api/requests/RequestsAPILoanRenewalTests.java index 1071b1cd0b..20434ac2be 100644 --- a/src/test/java/api/requests/RequestsAPILoanRenewalTests.java +++ b/src/test/java/api/requests/RequestsAPILoanRenewalTests.java @@ -592,7 +592,8 @@ void forbidRenewalOverrideWhenRecallIsForDifferentItemOfSameInstance() { "renewal date falls outside of the date ranges in the loan policy, " + "items cannot be renewed when there is an active recall request, " + "item is Declared lost, item is Aged to lost, " + - "renewal would not change the due date")))); + "renewal would not change the due date, " + + "loan has reminder fees")))); } @Test @@ -623,7 +624,8 @@ void forbidRenewalOverrideWhenTitleLevelRecallRequestExistsForDifferentItemOfSam "renewal date falls outside of the date ranges in the loan policy, " + "items cannot be renewed when there is an active recall request, " + "item is Declared lost, item is Aged to lost, " + - "renewal would not change the due date")))); + "renewal would not change the due date, " + + "loan has reminder fees")))); } @Test diff --git a/src/test/java/api/requests/StaffSlipsTests.java b/src/test/java/api/requests/StaffSlipsTests.java index 22fb0cb198..083101558f 100644 --- a/src/test/java/api/requests/StaffSlipsTests.java +++ b/src/test/java/api/requests/StaffSlipsTests.java @@ -268,6 +268,7 @@ void responseContainsSlipWithAllAvailableTokens(String countryCode, String prima assertEquals(item.getEnumeration(), itemContext.getString("enumeration")); assertEquals(item.getVolume(), itemContext.getString("volume")); assertEquals(item.getChronology(), itemContext.getString("chronology")); + assertEquals(item.getDisplaySummary(), itemContext.getString("displaySummary")); assertEquals(yearCaptionsToken, itemContext.getString("yearCaption")); assertEquals(materialTypeName, itemContext.getString("materialType")); assertEquals(loanTypeName, itemContext.getString("loanType")); diff --git a/src/test/java/api/requests/scenarios/LoanDueDatesAfterRecallTests.java b/src/test/java/api/requests/scenarios/LoanDueDatesAfterRecallTests.java index e038b56595..e8e3022147 100644 --- a/src/test/java/api/requests/scenarios/LoanDueDatesAfterRecallTests.java +++ b/src/test/java/api/requests/scenarios/LoanDueDatesAfterRecallTests.java @@ -116,7 +116,7 @@ void recallRequestWithNoPolicyValuesChangesDueDateToSystemDate() { } @Test - void recallRequestWithMGDAndRDValuesChangesDueDateToRD() { + void recallRequestWithMGDAndRDValuesShouldNotChangeDueDateToRD() { final IndividualResource smallAngryPlanet = itemsFixture.basedUponSmallAngryPlanet(); final IndividualResource requestServicePoint = servicePointsFixture.cd1(); final IndividualResource steve = usersFixture.steve(); @@ -143,12 +143,12 @@ void recallRequestWithMGDAndRDValuesChangesDueDateToRD() { final JsonObject storedLoan = loansStorageClient.getById(loan.getId()).getJson(); - assertThat("due date should not be the original date", - storedLoan.getString("dueDate"), not(originalDueDate)); + assertThat("due date should be the original date", storedLoan.getString("dueDate"), + is(originalDueDate)); - final String expectedDueDate = formatDateTime(getZonedDateTime().plusMonths(2)); - assertThat("due date should be in 2 months", - storedLoan.getString("dueDate"), is(expectedDueDate)); + final String expectedDueDate = formatDateTime(getZonedDateTime().plusWeeks(3)); + assertThat("due date should be in 3 weeks", storedLoan.getString("dueDate"), + is(expectedDueDate)); } @Test @@ -367,7 +367,7 @@ void loanPolicyWithInvalidMGDOrRDPeriodValuesReturnsErrorOnRecallCreation( } @Test - void initialLoanDueDateOnCreateWithPrexistingRequests() { + void initialLoanDueDateOnCreateWithPrexistingRequestsIsBeforeRecallInterval() { final IndividualResource smallAngryPlanet = itemsFixture.basedUponSmallAngryPlanet(); final IndividualResource requestServicePoint = servicePointsFixture.cd1(); @@ -403,12 +403,12 @@ void initialLoanDueDateOnCreateWithPrexistingRequests() { final JsonObject storedLoan = loansStorageClient.getById(loan.getId()).getJson(); - final String expectedDueDate = formatDateTime(ZonedDateTime - .of(getZonedDateTime().plusMonths(2).toLocalDate(), - LocalTime.MIDNIGHT.minusSeconds(1),getZoneId())); + final String expectedDueDate = formatDateTime(ZonedDateTime.of( + getZonedDateTime().plusWeeks(3).toLocalDate(), LocalTime.MIDNIGHT.minusSeconds(1), + getZoneId())); - assertThat("due date should be in 2 months (recall return interval)", - storedLoan.getString("dueDate"), is(expectedDueDate)); + assertThat("due date should be in 3 weeks (loan period) since it is before the recall " + + "return interval", storedLoan.getString("dueDate"), is(expectedDueDate)); } @Test @@ -674,11 +674,11 @@ void secondRecallRequestWithRDTruncationInPlaceDoesNotChangeDueDate() { JsonObject storedLoan = loansStorageClient.getById(loan.getId()).getJson(); final String recalledDueDate = storedLoan.getString("dueDate"); - assertThat("due date after recall should not be the original date", - recalledDueDate, not(originalDueDate)); + assertThat("due date after recall should be the original date", + recalledDueDate, is(originalDueDate)); - final String expectedDueDate = formatDateTime(getZonedDateTime().plusMonths(2)); - assertThat("due date after recall should be in 2 months", + final String expectedDueDate = formatDateTime(getZonedDateTime().plusWeeks(3)); + assertThat("due date after recall should be in 3 weeks", storedLoan.getString("dueDate"), is(expectedDueDate)); setClock(Clock.offset(getClock(), Duration.ofDays(7))); @@ -687,7 +687,7 @@ void secondRecallRequestWithRDTruncationInPlaceDoesNotChangeDueDate() { getZonedDateTime(), requestServicePoint.getId(), "Recall"); storedLoan = loansStorageClient.getById(loan.getId()).getJson(); - assertThat("second recall should not change the due date (2 months)", + assertThat("second recall should not change the due date (3 weeks)", storedLoan.getString("dueDate"), is(recalledDueDate)); } @@ -702,11 +702,12 @@ void secondRecallRequestWithRDTruncationInPlaceAndLoanOverdueDoesNotChangeDueDat final LoanPolicyBuilder canCirculateRollingPolicy = new LoanPolicyBuilder() .withName("Can Circulate Rolling With Recalls") .withDescription("Can circulate item With Recalls") - .rolling(Period.weeks(3)) + .rolling(Period.days(65)) .unlimitedRenewals() .renewFromSystemDate() - .withRecallsMinimumGuaranteedLoanPeriod(Period.weeks(2)) - .withRecallsRecallReturnInterval(Period.months(2)); + .withAllowRecallsToExtendOverdueLoans(false) + .withRecallsMinimumGuaranteedLoanPeriod(Period.days(15)) + .withRecallsRecallReturnInterval(Period.days(60)); setFallbackPolicies(canCirculateRollingPolicy); @@ -724,8 +725,8 @@ void secondRecallRequestWithRDTruncationInPlaceAndLoanOverdueDoesNotChangeDueDat assertThat("due date after recall should not be the original date", recalledDueDate, not(originalDueDate)); - final String expectedDueDate = formatDateTime(getZonedDateTime().plusMonths(2)); - assertThat("due date after recall should be in 2 months", + final String expectedDueDate = formatDateTime(getZonedDateTime().plusDays(60)); + assertThat("due date after recall should be in 60 days", storedLoan.getString("dueDate"), is(expectedDueDate)); // Move the fixed clock so that the loan is now overdue @@ -735,7 +736,7 @@ void secondRecallRequestWithRDTruncationInPlaceAndLoanOverdueDoesNotChangeDueDat getZonedDateTime(), requestServicePoint.getId(), "Recall"); storedLoan = loansStorageClient.getById(loan.getId()).getJson(); - assertThat("second recall should not change the due date (2 months)", + assertThat("second recall should not change the due date (60 days)", storedLoan.getString("dueDate"), is(recalledDueDate)); } @@ -861,7 +862,6 @@ void shouldExtendLoanDueDateByAlternatePeriodWhenOverdueLoanIsRecalledAndPolicyA .forItem(smallAngryPlanet) .fulfillToHoldShelf() .by(usersFixture.jessica()) - .fulfillToHoldShelf() .withPickupServicePointId(servicePointsFixture.cd1().getId())); final JsonObject storedLoan = loansStorageClient.getById(loan.getId()).getJson(); @@ -876,6 +876,40 @@ void shouldExtendLoanDueDateByAlternatePeriodWhenOverdueLoanIsRecalledAndPolicyA verifyLogEventDueDateChangedMessage(publishedLoanLogEvents.get(1), loan, expectedLoanDueDate); } + @Test + void shouldNotExtendLoanDueDateWhenCurrentDueDateIsBeforeRecallDueDate() { + final IndividualResource smallAngryPlanet = itemsFixture.basedUponSmallAngryPlanet(); + + final Period loanPeriod = Period.days(2); + setFallbackPolicies(new LoanPolicyBuilder() + .withName("Can Circulate Rolling With Recalls") + .withDescription("Can circulate item With Recalls") + .rolling(loanPeriod) + .unlimitedRenewals() + .renewFromSystemDate() + .withRecallsMinimumGuaranteedLoanPeriod(Period.days(10)) + .withRecallsRecallReturnInterval(Period.days(5))); + + final ZonedDateTime loanCreateDate = getZonedDateTime().minusDays(1); + final ZonedDateTime expectedLoanDueDate = loanPeriod.plusDate(loanCreateDate); + + final IndividualResource loan = checkOutFixture.checkOutByBarcode( + smallAngryPlanet, usersFixture.steve(), loanCreateDate); + + requestsFixture.place(new RequestBuilder() + .recall() + .forItem(smallAngryPlanet) + .fulfillToHoldShelf() + .by(usersFixture.jessica()) + .withPickupServicePointId(servicePointsFixture.cd1().getId())); + + final var updatedLoan = loansStorageClient.getById(loan.getId()); + + // verify that loan due date hasn't changed + assertThat(updatedLoan.getJson(), + hasJsonPath("dueDate", isEquivalentTo(expectedLoanDueDate))); + } + private void verifyLogEventDueDateChangedMessage(JsonObject eventLogJsonObject, IndividualResource loan, ZonedDateTime expectedLoanDueDate) { diff --git a/src/test/java/api/requests/scenarios/MoveRequestPolicyTests.java b/src/test/java/api/requests/scenarios/MoveRequestPolicyTests.java index ef5da3e317..45d2de5d3d 100644 --- a/src/test/java/api/requests/scenarios/MoveRequestPolicyTests.java +++ b/src/test/java/api/requests/scenarios/MoveRequestPolicyTests.java @@ -289,7 +289,7 @@ void moveRecallRequestWithExistingRecallsAndWithNoPolicyValuesChangesDueDateToSy } @Test - void moveRecallRequestWithoutExistingRecallsAndWithMGDAndRDValuesChangesDueDateToRD() { + void moveRecallRequestWithoutExistingRecallsAndWithMGDAndRDValuesDoesNotChangeDueDate() { List items = itemsFixture.createMultipleItemsForTheSameInstance(2); final IndividualResource itemToMoveTo = items.get(0); final IndividualResource itemToMoveFrom = items.get(1); @@ -345,11 +345,83 @@ void moveRecallRequestWithoutExistingRecallsAndWithMGDAndRDValuesChangesDueDateT final JsonObject storedLoan = loansStorageClient.getById(loan.getId()).getJson(); + assertThat("due date is not the original date", + storedLoan.getString("dueDate"), is(originalDueDate)); + + final String expectedDueDate = formatDateTime(getZonedDateTime().plusWeeks(3)); + assertThat("due date is not the original due date (3 weeks)", + storedLoan.getString("dueDate"), is(expectedDueDate)); + + assertThat("move recall request notice has not been sent", + FakeModNotify.getSentPatronNotices(), hasSize(2)); + + verifyNumberOfSentNotices(2); + verifyNumberOfPublishedEvents(NOTICE, 2); + verifyNumberOfPublishedEvents(NOTICE_ERROR, 0); + } + + @Test + void moveRecallRequestWithoutExistingRecallsAndWithMGDAndRDValuesChangesDueDateToRD() { + List items = itemsFixture.createMultipleItemsForTheSameInstance(2); + final IndividualResource itemToMoveTo = items.get(0); + final IndividualResource itemToMoveFrom = items.get(1); + final IndividualResource steve = usersFixture.steve(); + final IndividualResource charlotte = usersFixture.charlotte(); + final IndividualResource jessica = usersFixture.jessica(); + + final LoanPolicyBuilder canCirculateRollingPolicy = new LoanPolicyBuilder() + .withName("Can Circulate Rolling With Recalls") + .withDescription("Can circulate item With Recalls") + .rolling(Period.months(2)) + .unlimitedRenewals() + .renewFromSystemDate() + .withRecallsMinimumGuaranteedLoanPeriod(Period.weeks(2)) + .withRecallsRecallReturnInterval(Period.weeks(3)); + + final IndividualResource loanPolicy = loanPoliciesFixture.create(canCirculateRollingPolicy); + + useFallbackPolicies(loanPolicy.getId(), + requestPoliciesFixture.allowAllRequestPolicy().getId(), + noticePoliciesFixture.create(noticePolicy).getId(), + overdueFinePoliciesFixture.facultyStandard().getId(), + lostItemFeePoliciesFixture.facultyStandard().getId()); + + final IndividualResource loan = checkOutFixture.checkOutByBarcode( + itemToMoveTo, steve, getZonedDateTime()); + + final String originalDueDate = loan.getJson().getString("dueDate"); + + // charlotte checks out itemToMoveFrom + checkOutFixture.checkOutByBarcode(itemToMoveFrom, charlotte); + + // jessica places recall request on itemToMoveFrom + IndividualResource requestByJessica = requestsFixture.placeItemLevelHoldShelfRequest( + itemToMoveFrom, jessica, getZonedDateTime(), RequestType.RECALL.getValue()); + + // One notice for the recall is expected + verifyNumberOfSentNotices(1); + verifyNumberOfPublishedEvents(NOTICE, 1); + verifyNumberOfPublishedEvents(NOTICE_ERROR, 0); + + // move jessica's recall request from itemToMoveFrom to itemToMoveTo + IndividualResource moveRequest = requestsFixture.move(new MoveRequestBuilder( + requestByJessica.getId(), + itemToMoveTo.getId(), + RequestType.RECALL.getValue())); + + assertThat("Move request should have correct item id", + moveRequest.getJson().getString("itemId"), is(itemToMoveTo.getId().toString())); + + assertThat("Move request should have correct type", + moveRequest.getJson().getString("requestType"), is(RequestType.RECALL.getValue())); + + final JsonObject storedLoan = loansStorageClient.getById(loan.getId()).getJson(); + assertThat("due date is the original date", storedLoan.getString("dueDate"), not(originalDueDate)); - final String expectedDueDate = formatDateTime(getZonedDateTime().plusMonths(2)); - assertThat("due date is not the recall due date (2 months)", + final String expectedDueDate = formatDateTime(getZonedDateTime().plusWeeks(3)); + assertThat("due date is not the recall due date", storedLoan.getString("dueDate"), is(expectedDueDate)); assertThat("move recall request notice has not been sent", @@ -361,7 +433,7 @@ void moveRecallRequestWithoutExistingRecallsAndWithMGDAndRDValuesChangesDueDateT } @Test - void moveRecallRequestWithExistingRecallsAndWithMGDAndRDValuesChangesDueDateToRD() { + void moveRecallRequestWithExistingRecallsAndWithMGDAndRDValuesDoesNotChangeDueDate() { List items = itemsFixture.createMultipleItemsForTheSameInstance(2); final IndividualResource itemToMoveTo = items.get(0); final IndividualResource itemToMoveFrom = items.get(1); @@ -397,11 +469,95 @@ void moveRecallRequestWithExistingRecallsAndWithMGDAndRDValuesChangesDueDateToRD JsonObject storedLoan = loansStorageClient.getById(loan.getId()).getJson(); - assertThat("due date is the original date", - storedLoan.getString("dueDate"), not(originalDueDate)); + assertThat("due date is not the original date", + storedLoan.getString("dueDate"), is(originalDueDate)); + + final String expectedDueDate = formatDateTime(getZonedDateTime().plusWeeks(3)); + assertThat("due date is not the original due date (3 weeks)", + storedLoan.getString("dueDate"), is(expectedDueDate)); + + // charlotte checks out itemToMoveFrom + checkOutFixture.checkOutByBarcode(itemToMoveFrom, charlotte); + + // jessica places recall request on itemToMoveFrom + IndividualResource requestByJessica = requestsFixture.placeItemLevelHoldShelfRequest( + itemToMoveFrom, jessica, getZonedDateTime(), RequestType.RECALL.getValue()); + + // There should be 2 notices for each recall + waitAtMost(1, SECONDS) + .until(() -> patronNoticesForRecipientWasSent(steve)); + + waitAtMost(1, SECONDS) + .until(() -> patronNoticesForRecipientWasSent(charlotte)); + + verifyNumberOfSentNotices(2); + verifyNumberOfPublishedEvents(NOTICE, 2); + verifyNumberOfPublishedEvents(NOTICE_ERROR, 0); + + // move jessica's recall request from itemToMoveFrom to itemToMoveTo + IndividualResource moveRequest = requestsFixture.move(new MoveRequestBuilder( + requestByJessica.getId(), + itemToMoveTo.getId(), + RequestType.RECALL.getValue())); + + assertThat("Move request should have correct item id", + moveRequest.getJson().getString("itemId"), is(itemToMoveTo.getId().toString())); + + assertThat("Move request should have correct type", + moveRequest.getJson().getString("requestType"), is(RequestType.RECALL.getValue())); + + storedLoan = loansStorageClient.getById(loan.getId()).getJson(); + + assertThat("due date has changed", + storedLoan.getString("dueDate"), is(expectedDueDate)); + + assertThat("move recall request unexpectedly sent another patron notice", + FakeModNotify.getSentPatronNotices(), hasSize(2)); + + verifyNumberOfSentNotices(2); + verifyNumberOfPublishedEvents(NOTICE, 2); + verifyNumberOfPublishedEvents(NOTICE_ERROR, 0); + } + + @Test + void moveRecallRequestWithExistingRecallsAndWithMGDAndRDValuesChangesDueDateToRD() { + List items = itemsFixture.createMultipleItemsForTheSameInstance(2); + final IndividualResource itemToMoveTo = items.get(0); + final IndividualResource itemToMoveFrom = items.get(1); + final IndividualResource steve = usersFixture.steve(); + final IndividualResource charlotte = usersFixture.charlotte(); + final IndividualResource jessica = usersFixture.jessica(); + + final LoanPolicyBuilder canCirculateRollingPolicy = new LoanPolicyBuilder() + .withName("Can Circulate Rolling With Recalls") + .withDescription("Can circulate item With Recalls") + .rolling(Period.months(2)) + .unlimitedRenewals() + .renewFromSystemDate() + .withRecallsMinimumGuaranteedLoanPeriod(Period.weeks(2)) + .withRecallsRecallReturnInterval(Period.weeks(3)); + + final IndividualResource loanPolicy = loanPoliciesFixture.create(canCirculateRollingPolicy); + + useFallbackPolicies(loanPolicy.getId(), + requestPoliciesFixture.allowAllRequestPolicy().getId(), + noticePoliciesFixture.create(noticePolicy).getId(), + overdueFinePoliciesFixture.facultyStandard().getId(), + lostItemFeePoliciesFixture.facultyStandard().getId()); + + final IndividualResource loan = checkOutFixture.checkOutByBarcode( + itemToMoveTo, steve, getZonedDateTime()); + + final String originalDueDate = loan.getJson().getString("dueDate"); + + // charlotte places recall request on itemToMoveTo + requestsFixture.placeItemLevelHoldShelfRequest( + itemToMoveTo, charlotte, getZonedDateTime().minusHours(1), RequestType.RECALL.getValue()); + + JsonObject storedLoan = loansStorageClient.getById(loan.getId()).getJson(); - final String expectedDueDate = formatDateTime(getZonedDateTime().plusMonths(2)); - assertThat("due date is not the recall due date (2 months)", + final String expectedDueDate = formatDateTime(getZonedDateTime().plusWeeks(3)); + assertThat("due date is not the original due date", storedLoan.getString("dueDate"), is(expectedDueDate)); // charlotte checks out itemToMoveFrom diff --git a/src/test/java/api/support/builders/CheckOutByBarcodeRequestBuilder.java b/src/test/java/api/support/builders/CheckOutByBarcodeRequestBuilder.java index b3a9e88244..59e6180283 100644 --- a/src/test/java/api/support/builders/CheckOutByBarcodeRequestBuilder.java +++ b/src/test/java/api/support/builders/CheckOutByBarcodeRequestBuilder.java @@ -70,6 +70,16 @@ public CheckOutByBarcodeRequestBuilder to(IndividualResource loanee) { this.overrideBlocks); } + public CheckOutByBarcodeRequestBuilder to(String userBarcode) { + return new CheckOutByBarcodeRequestBuilder( + this.itemBarcode, + userBarcode, + this.proxyBarcode, + this.loanDate, + this.servicePointId, + this.overrideBlocks); + } + public CheckOutByBarcodeRequestBuilder on(ZonedDateTime loanDate) { return new CheckOutByBarcodeRequestBuilder( this.itemBarcode, diff --git a/src/test/java/api/support/builders/ItemBuilder.java b/src/test/java/api/support/builders/ItemBuilder.java index aa3b0946ed..f6443f117c 100644 --- a/src/test/java/api/support/builders/ItemBuilder.java +++ b/src/test/java/api/support/builders/ItemBuilder.java @@ -40,13 +40,14 @@ public class ItemBuilder extends JsonBuilder implements Builder { private List yearCaption; private String volume; private final String chronology; + private String displaySummary; private String numberOfPieces; private String descriptionOfPieces; public ItemBuilder() { this(UUID.randomUUID(), null, "565578437802", AVAILABLE, null, null, null, null, null, null, null, null, null, null, null, Collections.emptyList(), - null, null, null); + null, null, null, null); } private ItemBuilder( @@ -67,6 +68,7 @@ private ItemBuilder( String volume, List yearCaption, String chronology, + String displaySummary, String numberOfPieces, String descriptionOfPieces) { @@ -87,6 +89,7 @@ private ItemBuilder( this.volume = volume; this.yearCaption = yearCaption; this.chronology = chronology; + this.displaySummary = displaySummary; this.numberOfPieces = numberOfPieces; this.descriptionOfPieces = descriptionOfPieces; } @@ -112,6 +115,7 @@ public JsonObject create() { put(itemRequest, "volume", volume); put(itemRequest, "yearCaption", yearCaption); put(itemRequest, "chronology", chronology); + put(itemRequest, "displaySummary", displaySummary); put(itemRequest, "numberOfPieces", numberOfPieces); put(itemRequest, "descriptionOfPieces", descriptionOfPieces); @@ -181,6 +185,7 @@ public ItemBuilder withStatus(String status) { this.volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -204,6 +209,7 @@ public ItemBuilder withBarcode(String barcode) { this.volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -235,6 +241,7 @@ public ItemBuilder withPermanentLocation(UUID locationId) { this.volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -266,6 +273,7 @@ public ItemBuilder withTemporaryLocation(UUID locationId) { this.volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -293,6 +301,7 @@ public ItemBuilder forHolding(UUID holdingId) { this.volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -316,6 +325,7 @@ public ItemBuilder withMaterialType(UUID materialTypeId) { this.volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -339,6 +349,7 @@ public ItemBuilder withPermanentLoanType(UUID loanTypeId) { this.volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -362,6 +373,7 @@ public ItemBuilder withTemporaryLoanType(UUID loanTypeId) { this.volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -385,6 +397,7 @@ public ItemBuilder withId(UUID id) { this.volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -408,6 +421,7 @@ public ItemBuilder withEnumeration(String enumeration) { this.volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -431,6 +445,7 @@ public ItemBuilder withCopyNumber(String copyNumber) { this.volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -458,6 +473,7 @@ public ItemBuilder withCallNumber( this.volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -481,6 +497,7 @@ public ItemBuilder withVolume(String volume) { volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -504,6 +521,7 @@ public ItemBuilder withYearCaption(List yearCaption) { this.volume, yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -527,6 +545,31 @@ public ItemBuilder withChronology(String chronology) { this.volume, this.yearCaption, chronology, + this.displaySummary, + this.numberOfPieces, + this.descriptionOfPieces); + } + + public ItemBuilder withDisplaySummary(String displaySummary) { + return new ItemBuilder( + this.id, + this.holdingId, + this.barcode, + this.status, + this.permanentLocationId, + this.temporaryLocationId, + this.materialTypeId, + this.permanentLoanTypeId, + this.temporaryLoanTypeId, + this.enumeration, + this.copyNumber, + this.itemLevelCallNumber, + this.itemLevelCallNumberPrefix, + this.itemLevelCallNumberSuffix, + this.volume, + this.yearCaption, + this.chronology, + displaySummary, this.numberOfPieces, this.descriptionOfPieces); } @@ -550,6 +593,7 @@ public ItemBuilder withNumberOfPieces(String numberOfPieces) { this.volume, this.yearCaption, this.chronology, + this.displaySummary, numberOfPieces, this.descriptionOfPieces); } @@ -573,6 +617,7 @@ public ItemBuilder withDescriptionOfPieces(String descriptionOfPieces) { this.volume, this.yearCaption, this.chronology, + this.displaySummary, this.numberOfPieces, descriptionOfPieces); } diff --git a/src/test/java/api/support/fixtures/FeeFineAccountFixture.java b/src/test/java/api/support/fixtures/FeeFineAccountFixture.java index 12e9008687..e0e502522b 100644 --- a/src/test/java/api/support/fixtures/FeeFineAccountFixture.java +++ b/src/test/java/api/support/fixtures/FeeFineAccountFixture.java @@ -5,8 +5,10 @@ import static api.support.http.ResourceClient.forFeeFineActions; import static java.lang.String.format; +import java.util.Collection; import java.util.UUID; +import api.support.http.CqlQuery; import api.support.http.IndividualResource; import api.support.builders.AccountBuilder; @@ -163,6 +165,12 @@ public IndividualResource createLostItemFeeActualCostAccount(double amount, return account; } + public Collection getAccounts(UUID loanId) { + return accountsClient.getMany(CqlQuery.exactMatch("loanId", loanId.toString())) + .stream() + .toList(); + } + private JsonObject getLostItemFeeAccount(UUID loanId) { return accountsClient.getMany(exactMatch("loanId", loanId.toString()) .and(exactMatch("feeFineType", "Lost item fee"))) diff --git a/src/test/java/api/support/fixtures/ItemsFixture.java b/src/test/java/api/support/fixtures/ItemsFixture.java index da00079b16..f7957f9cb3 100644 --- a/src/test/java/api/support/fixtures/ItemsFixture.java +++ b/src/test/java/api/support/fixtures/ItemsFixture.java @@ -399,6 +399,22 @@ public List createMultipleItemForTheSameInstance(int size, .collect(Collectors.toList()); } + public List createMultipleItemsOnePerInstance(int size, + List> itemAdditionalProperties) { + + if (itemAdditionalProperties.size() != size) { + throw new AssertionError("Number of item additional properties should be equal to the size param"); + } + + return IntStream.range(0, size) + .mapToObj(num -> basedUponSmallAngryPlanet( + identity(), + identity(), + itemBuilder -> itemAdditionalProperties.get(num) + .apply(itemBuilder.withBarcode("0000" + num)))) + .toList(); + } + private UUID booksInstanceTypeId() { final JsonObject booksInstanceType = new JsonObject(); @@ -424,7 +440,8 @@ public Function addCallNumberStringComponents(String p .withCallNumber(prefix + "itCn", prefix + "itCnPrefix", prefix + "itCnSuffix") .withEnumeration(prefix + "enumeration1") .withChronology(prefix + "chronology") - .withVolume(prefix + "vol.1"); + .withVolume(prefix + "vol.1") + .withDisplaySummary(prefix + "displaySummary"); } public Function addCallNumberStringComponents() { diff --git a/src/test/java/api/support/fixtures/RequestPoliciesFixture.java b/src/test/java/api/support/fixtures/RequestPoliciesFixture.java index a8c97e4dac..b3d3dd1771 100644 --- a/src/test/java/api/support/fixtures/RequestPoliciesFixture.java +++ b/src/test/java/api/support/fixtures/RequestPoliciesFixture.java @@ -56,6 +56,15 @@ public IndividualResource allowAllRequestPolicy(UUID id) { return requestPolicyRecordCreator.createIfAbsent(new RequestPolicyBuilder(types, id)); } + public IndividualResource allowPageAndHoldRequestPolicy() { + ArrayList types = new ArrayList<>(); + types.add(RequestType.HOLD); + types.add(RequestType.PAGE); + final RequestPolicyBuilder allowPageAndHoldPolicy = new RequestPolicyBuilder(types); + + return requestPolicyRecordCreator.createIfAbsent(allowPageAndHoldPolicy); + } + public IndividualResource customRequestPolicy(ArrayList types) { final RequestPolicyBuilder customPolicy = new RequestPolicyBuilder(types); diff --git a/src/test/java/api/support/http/ResourceClient.java b/src/test/java/api/support/http/ResourceClient.java index 558516054e..844fff542d 100644 --- a/src/test/java/api/support/http/ResourceClient.java +++ b/src/test/java/api/support/http/ResourceClient.java @@ -302,7 +302,7 @@ public IndividualResource create(Builder builder) { public IndividualResource create(JsonObject representation) { - return new IndividualResource(restAssuredClient.post(representation, + return new IndividualResource(restAssuredClient.post(representation, rootUrl(), 201, "create-record")); } diff --git a/src/test/java/api/support/matchers/AccountMatchers.java b/src/test/java/api/support/matchers/AccountMatchers.java index 5c04c46d4f..f315e49acf 100644 --- a/src/test/java/api/support/matchers/AccountMatchers.java +++ b/src/test/java/api/support/matchers/AccountMatchers.java @@ -51,6 +51,13 @@ public static Matcher isPaidFully(double amount) { hasJsonPath("paymentStatus.name", "Paid fully")); } + public static Matcher isPaidFully() { + return allOf( + hasJsonPath("remaining", 0.0), + hasJsonPath("status.name", "Closed"), + hasJsonPath("paymentStatus.name", "Paid fully")); + } + public static Matcher isAccount(double amount, double remaining, String status, String paymentStatus, String feeFineType, UUID userId, UUID loanId) { return allOf( diff --git a/src/test/java/api/support/matchers/EventMatchers.java b/src/test/java/api/support/matchers/EventMatchers.java index f92a9d69ad..eb4fd93a62 100644 --- a/src/test/java/api/support/matchers/EventMatchers.java +++ b/src/test/java/api/support/matchers/EventMatchers.java @@ -102,7 +102,8 @@ public static Matcher isValidCheckInLogEvent(JsonObject checkedInLoa hasJsonPath("itemId", is(checkedInLoan.getString("itemId"))), hasJsonPath("zoneId", is("Z")), hasJsonPath("itemBarcode", is(checkedInLoan.getJsonObject("item").getString("barcode"))), - hasJsonPath("itemStatusName", is(checkedInLoan.getJsonObject("item").getJsonObject("status").getString("name"))) + hasJsonPath("itemStatusName", is(checkedInLoan.getJsonObject("item").getJsonObject("status").getString("name"))), + hasJsonPath("userBarcode", is(checkedInLoan.getJsonObject("borrower").getString("barcode"))) ))), isLogRecordEventType()); } diff --git a/src/test/java/org/folio/circulation/support/http/client/VertxWebClientOkapiHttpClientTests.java b/src/test/java/org/folio/circulation/support/http/client/VertxWebClientOkapiHttpClientTests.java index b8ad7838a9..2a63405201 100644 --- a/src/test/java/org/folio/circulation/support/http/client/VertxWebClientOkapiHttpClientTests.java +++ b/src/test/java/org/folio/circulation/support/http/client/VertxWebClientOkapiHttpClientTests.java @@ -225,7 +225,7 @@ public void failsWhenGetTimesOut() assertThat(cause.getReason(), containsString( "The timeout period of 500ms has been exceeded while executing " + - "GET /record for server localhost")); + "GET /record for server")); } //TODO: Maybe replace this with a filter extension diff --git a/src/test/resources/item-storage-8-9.json b/src/test/resources/item-storage-8-9.json index 4a224b5683..12b776a9e6 100644 --- a/src/test/resources/item-storage-8-9.json +++ b/src/test/resources/item-storage-8-9.json @@ -100,6 +100,10 @@ "type": "string", "description": "Chronology is the descriptive information for the dating scheme of a serial." }, + "displaySummary": { + "description": "Display summary about the item", + "type": "string" + }, "yearCaption": { "type": "array", "description": "In multipart monographs, a caption is a character(s) used to label a level of chronology, e.g., year 1985.",