Skip to content
Merged
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 24.5.0-SNAPSHOT 2025-xx-xx
* Replace deprecated mod-configuration with mod-settings for fetching tenant's locale settings ([CIRC-2295](https://folio-org.atlassian.net/browse/CIRC-2295))

## 24.4.0 2025-03-12
* Patron notices for the trigger “Item recalled” not sent if the item is not 1st in the title request queue (CIRC-2168)
* Fix automated patron blocks permission issue (CIRC-2185)
Expand Down
58 changes: 29 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,17 +265,17 @@ Below is a short summary summary of most of the validation checks performed when

Each includes an example of the error message provided and the parameter key included with the error.

|Check|Example Message|Parameter Key|Notes|
|---|---|---|---|
|Item does not exist|No item with barcode 036000291452 exists|itemBarcode| |
|Holding does not exist| | |otherwise it is not possible to lookup circulation rules|
|Item is already checked out|Item is already checked out|itemBarcode| |
|Existing open loan for item|Cannot check out item that already has an open loan|itemBarcode| |
|Proxy relationship is valid|Cannot check out item via proxy when relationship is invalid| |only if proxying|
|User must be requesting user|User checking out must be requester awaiting pickup|userBarcode|if there is an outstanding fulfillable request for item|
|User does not exist|Could not find user with matching barcode|userBarcode| |
|User needs to be active and not expired|Cannot check out to inactive user|userBarcode| |
|Proxy user needs to be active and not expired|Cannot check out via inactive proxying user|proxyUserBarcode|only if proxying|
| Check | Example Message | Parameter Key | Notes |
|-----------------------------------------------|--------------------------------------------------------------|------------------|----------------------------------------------------------|
| Item does not exist | No item with barcode 036000291452 exists | itemBarcode | |
| Holding does not exist | | | otherwise it is not possible to lookup circulation rules |
| Item is already checked out | Item is already checked out | itemBarcode | |
| Existing open loan for item | Cannot check out item that already has an open loan | itemBarcode | |
| Proxy relationship is valid | Cannot check out item via proxy when relationship is invalid | | only if proxying |
| User must be requesting user | User checking out must be requester awaiting pickup | userBarcode | if there is an outstanding fulfillable request for item |
| User does not exist | Could not find user with matching barcode | userBarcode | |
| User needs to be active and not expired | Cannot check out to inactive user | userBarcode | |
| Proxy user needs to be active and not expired | Cannot check out via inactive proxying user | proxyUserBarcode | only if proxying |

### Renew By Barcode

Expand Down Expand Up @@ -404,19 +404,19 @@ based on the patron's patron group and the item's material type, loan type, and
During the circulation process an item can change between a variety of states,
below is a table describing the most common states defined at the moment.

| Name | Description |
|---|---|
| Available | This item is available to be lent to a patron |
| Checked out | This item is currently checked out to a patron |
| Awaiting pickup | This item is awaiting pickup by a patron who has a request at the top of the queue|
| Name | Description |
|-----------------|------------------------------------------------------------------------------------|
| Available | This item is available to be lent to a patron |
| Checked out | This item is currently checked out to a patron |
| Awaiting pickup | This item is awaiting pickup by a patron who has a request at the top of the queue |

### Request Status

| Name | Description |
|---|---|
| Open - Not yet filled | The requested item is not yet available to the requesting user |
| Open - Awaiting pickup | The item is available to the requesting user |
| Closed - Filled | |
| Name | Description |
|------------------------|----------------------------------------------------------------|
| Open - Not yet filled | The requested item is not yet available to the requesting user |
| Open - Awaiting pickup | The item is available to the requesting user |
| Closed - Filled | |

### Storing Information from Other Records

Expand Down Expand Up @@ -530,7 +530,7 @@ content-length: 230
}
```
### Configuration setting for CheckoutLock Feature
To enable this feature for a tenant, we need to add the below configuration in mod-settings. See https://issues.folio.org/browse/UXPROD-3515 to know more about this feature.
To enable this feature for a tenant, we need to add the below configuration in mod-settings. See [UXPROD-3515](https://issues.folio.org/browse/UXPROD-3515) to know more about this feature.

#### Permissions
To make a post call to mod-settings, user should have below permissions.
Expand All @@ -551,19 +551,19 @@ POST https://{okapi-location}/settings/entries
```

| parameter | Type | Description |
|---------|-------------|-------------------------------------------------------------------------------------------------------|
| `id` | UUID | id should be provided of type UUID. |
| `scope` | String | Scope should be the module name. Here, it will be "mod-circulation" |
| `key` | String | Key should be feature name which we are enabling the settings. Here, it will be "checkoutLockFeature" |
| `value` | Json Object | Settings for checkout lock feature |
|-----------|-------------|-------------------------------------------------------------------------------------------------------|
| `id` | UUID | id should be provided of type UUID. |
| `scope` | String | Scope should be the module name. Here, it will be "mod-circulation" |
| `key` | String | Key should be feature name which we are enabling the settings. Here, it will be "checkoutLockFeature" |
| `value` | Json Object | Settings for checkout lock feature |


| Value options | Type | Description |
|------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `checkOutLockFeatureEnabled` | boolean | Indicates whether or not to enable this feature for the tenant. Default value is false(disabled). |
| `noOfRetryAttempts` | int | The maximum number of times to retry the lock ackquiring process during checkout. Once the retry is exhausted, system will return error. Default value is 30. |
| `retryInterval` | int | The amount of time to wait between retries in milliseconds. Default value is 250. |
| `lockTtl` | int | Maximum amount of time(milliseconds) that the lock should exist for a patron. After this time, the lock will gets deleted and lock will be provided for another request. Default value is 3000 |
| `retryInterval` | int | The amount of time to wait between retries in milliseconds. Default value is 250. |
| `lockTtl` | int | Maximum amount of time(milliseconds) that the lock should exist for a patron. After this time, the lock will gets deleted and lock will be provided for another request. Default value is 3000 |

## Additional Information

Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,20 @@
package org.folio.circulation.domain;

import static org.folio.circulation.domain.MultipleRecords.from;

import java.lang.invoke.MethodHandles;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Collection;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import io.vertx.core.json.JsonObject;

public class ConfigurationService {
private final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());

private static final int DEFAULT_SCHEDULED_NOTICES_PROCESSING_LIMIT = 100;
private static final int DEFAULT_CHECKOUT_TIMEOUT_DURATION_IN_MINUTES = 3;
private static final ZoneId DEFAULT_DATE_TIME_ZONE = ZoneOffset.UTC;
private static final String TIMEZONE_KEY = "timezone";
private static final String RECORDS_NAME = "configs";
private static final String CHECKOUT_TIMEOUT_DURATION_KEY = "checkoutTimeoutDuration";
private static final String CHECKOUT_TIMEOUT_KEY = "checkoutTimeout";

ZoneId findDateTimeZone(JsonObject representation) {
return from(representation, Configuration::new, RECORDS_NAME)
.map(MultipleRecords::getRecords)
.map(this::findDateTimeZone)
.orElse(DEFAULT_DATE_TIME_ZONE);
}

public ZoneId findDateTimeZone(Collection<Configuration> configurations) {
final ZoneId chosenTimeZone = configurations.stream()
.map(this::applyTimeZone)
.findFirst()
.orElse(DEFAULT_DATE_TIME_ZONE);

log.debug("findDateTimeZone:: timezone={}", chosenTimeZone);

return chosenTimeZone;
}

public Integer findSchedulerNoticesLimit(Collection<Configuration> configurations) {
final Integer noticesLimit = configurations.stream()
.map(this::applySchedulerNoticesLimit)
Expand Down Expand Up @@ -94,18 +67,4 @@ private Integer applySchedulerNoticesLimit(Configuration config) {
? Integer.valueOf(value)
: DEFAULT_SCHEDULED_NOTICES_PROCESSING_LIMIT;
}

private ZoneId applyTimeZone(Configuration config) {
String value = config.getValue();
return StringUtils.isBlank(value)
? DEFAULT_DATE_TIME_ZONE
: parseDateTimeZone(value);
}

private ZoneId parseDateTimeZone(String value) {
String timezone = new JsonObject(value).getString(TIMEZONE_KEY);
return StringUtils.isBlank(timezone)
? DEFAULT_DATE_TIME_ZONE
: ZoneId.of(timezone);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public CompletableFuture<Result<RequestAndRelatedRecords>> createRequest(
log.debug("createRequest:: parameters requestAndRelatedRecords: {}", () -> requestAndRelatedRecords);

final var requestRepository = repositories.getRequestRepository();
final var configurationRepository = repositories.getConfigurationRepository();
final var settingsRepository = repositories.getSettingsRepository();
final var automatedBlocksValidator = requestBlockValidators.getAutomatedPatronBlocksValidator();
final var manualBlocksValidator = requestBlockValidators.getManualPatronBlocksValidator();

Expand All @@ -103,7 +103,7 @@ public CompletableFuture<Result<RequestAndRelatedRecords>> createRequest(
.thenComposeAsync(r -> r.after(when(this::shouldCheckItem, this::checkItem, this::doNothing)))
.thenComposeAsync(r -> r.after(this::checkPolicy))
.thenApply(r -> r.next(this::refuseHoldOrRecallTlrWhenPageableItemExists))
.thenComposeAsync(r -> r.combineAfter(configurationRepository::findTimeZoneConfiguration,
.thenComposeAsync(r -> r.combineAfter(settingsRepository::lookupTimeZoneSettings,
RequestAndRelatedRecords::withTimeZone))
.thenApply(r -> r.next(errorHandler::failWithValidationErrors))
.thenComposeAsync(r -> r.after(updateUponRequest.updateItem::onRequestCreateOrUpdate))
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/org/folio/circulation/domain/Instance.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
@ToString(onlyExplicitlyIncluded = true)
public class Instance {
public static Instance unknown() {
return new Instance(null, null,null, emptyList(), emptyList(), emptyList(), emptyList(), emptyList());
return new Instance(null, null,null, emptyList(), emptyList(), emptyList(), emptyList(), emptyList(), emptyList());
}

@ToString.Include
Expand All @@ -27,6 +27,7 @@ public static Instance unknown() {
@NonNull Collection<Publication> publication;
@NonNull Collection<String> editions;
@NonNull Collection<String> physicalDescriptions;
@NonNull Collection<SeriesStatement> series;

public Stream<String> getContributorNames() {
return contributors.stream()
Expand All @@ -41,6 +42,11 @@ public String getPrimaryContributorName() {
.orElse(null);
}

public Stream<String> getSeriesStatementValues() {
return series.stream()
.map(SeriesStatement::getValue);
}

// TODO: replace this stub with proper implementation
public boolean isNotFound() {
return id == null;
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/folio/circulation/domain/Item.java
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ public Collection<String> getAdministrativeNotes() {
return description.getAdministrativeNotes();
}

public Collection<String> getSeriesStatementValues() {
return instance.getSeriesStatementValues().toList();
}

private ServicePoint getPrimaryServicePoint() {
return location.getPrimaryServicePoint();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@Value
public class ItemDescription {
public static ItemDescription unknown() {
return new ItemDescription(null, null, null, null, null, null, null, null, List.of(), null, List.of());
return new ItemDescription(null, null, null, null, null, null, null, null, List.of(), null, List.of(), List.of());
}

String barcode;
Expand All @@ -23,4 +23,5 @@ public static ItemDescription unknown() {
@NonNull Collection<String> yearCaption;
String accessionNumber;
@NonNull Collection<String> administrativeNotes;
@NonNull Collection<String> seriesStatements;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.circulation.domain.validation.RequestLoanValidator;
import org.folio.circulation.infrastructure.storage.ConfigurationRepository;
import org.folio.circulation.infrastructure.storage.SettingsRepository;
import org.folio.circulation.infrastructure.storage.requests.RequestPolicyRepository;
import org.folio.circulation.infrastructure.storage.requests.RequestQueueRepository;
Expand All @@ -25,7 +24,6 @@ public class MoveRequestService {
private final MoveRequestProcessAdapter moveRequestProcessAdapter;
private final RequestLoanValidator requestLoanValidator;
private final RequestNoticeSender requestNoticeSender;
private final ConfigurationRepository configurationRepository;
private final EventPublisher eventPublisher;
private final RequestQueueRepository requestQueueRepository;
private final SettingsRepository settingsRepository;
Expand All @@ -34,16 +32,15 @@ public class MoveRequestService {
public MoveRequestService(RequestRepository requestRepository, RequestPolicyRepository requestPolicyRepository,
UpdateUponRequest updateUponRequest, MoveRequestProcessAdapter moveRequestHelper,
RequestLoanValidator requestLoanValidator, RequestNoticeSender requestNoticeSender,
ConfigurationRepository configurationRepository, EventPublisher eventPublisher,
RequestQueueRepository requestQueueRepository, SettingsRepository settingsRepository) {
EventPublisher eventPublisher, RequestQueueRepository requestQueueRepository,
SettingsRepository settingsRepository) {

this.requestRepository = requestRepository;
this.requestPolicyRepository = requestPolicyRepository;
this.updateUponRequest = updateUponRequest;
this.moveRequestProcessAdapter = moveRequestHelper;
this.requestLoanValidator = requestLoanValidator;
this.requestNoticeSender = requestNoticeSender;
this.configurationRepository = configurationRepository;
this.eventPublisher = eventPublisher;
this.requestQueueRepository = requestQueueRepository;
this.settingsRepository = settingsRepository;
Expand All @@ -61,7 +58,7 @@ public CompletableFuture<Result<RequestAndRelatedRecords>> moveRequest(
.thenComposeAsync(r -> r.after(requestQueueRepository::get))
.thenApply(r -> r.map(this::pagedRequestIfDestinationItemAvailable))
.thenCompose(r -> r.after(this::validateUpdateRequest))
.thenComposeAsync(r -> r.combineAfter(configurationRepository::findTimeZoneConfiguration,
.thenComposeAsync(r -> r.combineAfter(settingsRepository::lookupTimeZoneSettings,
RequestAndRelatedRecords::withTimeZone))
.thenCompose(r -> r.after(updateUponRequest.updateRequestQueue::onMovedTo))
.thenComposeAsync(r -> r.after(this::updateRelatedObjects))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.folio.circulation.domain;

import lombok.Value;

@Value
public class SeriesStatement {
String authorityId;
String value;
}
Loading