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.",