diff --git a/NEWS.md b/NEWS.md
index c1b89cea92..95cd9bea97 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,22 @@
+## 24.3.2 2024-11-30
+* Add mod-settings permission blocks (CIRC-2185)
+* Fix automated patron blocks permission issue (CIRC-2185)
+* Remove new mod-settings permissions (CIRC-2183)
+* Fetch TLR settings from mod-config as fallback (CIRC-2171)
+* Update API versions (CIRC-2153)
+* Change ECS Primary request validation (CIRC-2151)
+* Allow operation replace for instance with no items (CIRC-2137)
+* Search title-level requests by both `itemId` and `instanceId` during check-in and check-out (CIRC-2125)
+* Allowed SP endpoint should support `patronGroupId` parameter (CIRC-2116)
+* Return empty result when search doesn't find anything (CIRC-2117)
+* Pass additional `includeRoutingServicePoints` parameter when needed (CIRC-2109)
+* Fetch item details across tenants (CIRC-2101)
+* Create a facade for instance search (CIRC-2072)
+* Fetch TLR settings from mod-settings (CIRC-2081)
+* Add `ecsRequestRouting` parameter to allowed-service-points (CIRC-2051)
+* Fix snapshot version (CIRC-2161)
+* Review and cleanup Module Descriptors for mod-circulation (CIRC-2139)
+
## 24.3.1 2024-11-27
* Patron notices for the trigger “Item recalled” not sent if the item is not 1st in the title request queue (CIRC-2168)
diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index 590b2da834..c87bad9224 100644
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -132,11 +132,11 @@
"methods": ["POST"],
"pathPattern": "/circulation/loans/{id}/change-due-date",
"permissionsRequired": [
- "circulation.loans.change-due-date.post",
- "configuration.entries.collection.get"
+ "circulation.loans.change-due-date.post"
],
"modulePermissions": [
- "modperms.circulation.loans.change-due-date.post"
+ "perms.circulation.loans.change-due-date.post",
+ "configuration.entries.collection.get"
]
}
]
@@ -152,7 +152,7 @@
"circulation.loans.claim-item-returned.post"
],
"modulePermissions": [
- "modperms.circulation.loans.claim-item-returned.post"
+ "perms.circulation.loans.claim-item-returned.post"
]
},
{
@@ -167,6 +167,22 @@
}
]
},
+ {
+ "id": "instance-items",
+ "version": "0.1",
+ "handlers": [
+ {
+ "methods": ["GET"],
+ "pathPattern": "/circulation/items-by-instance",
+ "permissionsRequired": [
+ "circulation.items-by-instance.get"
+ ],
+ "modulePermissions": [
+ "search.instances.collection.get"
+ ]
+ }
+ ]
+ },
{
"id": "add-info",
"version": "0.1",
@@ -199,9 +215,9 @@
"modperms.circulation.check-out-by-barcode.post"
],
"permissionsDesired": [
- "circulation.override-patron-block",
- "circulation.override-item-limit-block",
- "circulation.override-item-not-loanable-block"
+ "circulation.override-patron-block.post",
+ "circulation.override-item-limit-block.post",
+ "circulation.override-item-not-loanable-block.post"
]
},
{
@@ -225,11 +241,11 @@
"circulation.renew-by-barcode.post"
],
"modulePermissions": [
- "circulation.renew-loan"
+ "circulation.renew-loan.all"
],
"permissionsDesired": [
- "circulation.override-patron-block",
- "circulation.override-renewal-block"
+ "circulation.override-patron-block.post",
+ "circulation.override-renewal-block.post"
]
},
{
@@ -241,7 +257,7 @@
"circulation.renew-by-id.post"
],
"modulePermissions": [
- "circulation.renew-loan"
+ "circulation.renew-loan.all"
]
},
{
@@ -337,7 +353,7 @@
"circulation.requests.item.post"
],
"permissionsDesired" : [
- "circulation.override-patron-block"
+ "circulation.override-patron-block.post"
],
"modulePermissions": [
"modperms.circulation.requests.item.post"
@@ -397,7 +413,7 @@
],
"pathPattern": "/circulation/requests/queue/item/{id}",
"permissionsRequired": [
- "circulation.requests.queue.collection.get"
+ "circulation.requests.queue-item.collection.get"
],
"modulePermissions": [
"modperms.circulation.requests.queue.collection.get"
@@ -409,7 +425,7 @@
],
"pathPattern": "/circulation/requests/queue/item/{id}/reorder",
"permissionsRequired": [
- "circulation.requests.queue.reorder.collection.post"
+ "circulation.requests.queue.item-reorder.collection.post"
],
"modulePermissions": [
"modperms.circulation.requests.queue.reorder.collection.post"
@@ -421,7 +437,7 @@
],
"pathPattern": "/circulation/requests/queue/instance/{id}",
"permissionsRequired": [
- "circulation.requests.queue.collection.get"
+ "circulation.requests.queue-instance.collection.get"
],
"modulePermissions": [
"modperms.circulation.requests.queue.collection.get"
@@ -433,7 +449,7 @@
],
"pathPattern": "/circulation/requests/queue/instance/{id}/reorder",
"permissionsRequired": [
- "circulation.requests.queue.reorder.collection.post"
+ "circulation.requests.queue.instance-reorder.collection.post"
],
"modulePermissions": [
"modperms.circulation.requests.queue.reorder.collection.post"
@@ -632,7 +648,7 @@
"circulation-storage.circulation-rules.get",
"configuration.entries.item.get",
"configuration.entries.collection.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"circulation-storage.loan-policies.collection.get",
"users.item.get",
"users.collection.get",
@@ -649,7 +665,7 @@
},
{
"id": "allowed-service-points",
- "version": "1.0",
+ "version": "1.1",
"handlers": [
{
"methods": [
@@ -669,7 +685,7 @@
"circulation-storage.request-policies.collection.get",
"inventory-storage.items.item.get",
"inventory-storage.items.collection.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"inventory-storage.service-points.item.get",
"inventory-storage.service-points.collection.get",
"inventory-storage.holdings.item.get",
@@ -677,7 +693,10 @@
"inventory-storage.instances.item.get",
"inventory-storage.instances.collection.get",
"configuration.entries.item.get",
- "configuration.entries.collection.get"
+ "configuration.entries.collection.get",
+ "mod-settings.entries.item.get",
+ "mod-settings.entries.collection.get",
+ "mod-settings.global.read.circulation"
]
}
]
@@ -769,7 +788,7 @@
"patron-action-session-storage.patron-action-sessions.item.get",
"patron-action-session-storage.patron-action-sessions.collection.get",
"circulation-storage.loans.collection.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"circulation-storage.loan-policies.collection.get",
"users.item.get",
"users.collection.get",
@@ -799,7 +818,7 @@
"actual-cost-record-storage.actual-cost-records.item.put",
"accounts.item.get",
"accounts.collection.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"lost-item-fees-policies.item.get",
"lost-item-fees-policies.collection.get",
"circulation-storage.loans.item.get",
@@ -821,7 +840,7 @@
"circulation-storage.loans.item.get",
"circulation-storage.loan-policies.item.get",
"circulation-storage.loan-policies.collection.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"circulation.rules.loan-policy.get",
"configuration.entries.collection.get",
"patron-notice.post",
@@ -853,7 +872,7 @@
"circulation-storage.loan-policies.item.get",
"circulation-storage.loan-policies.collection.get",
"circulation-storage.fixed-due-date-schedules.collection.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"circulation.rules.loan-policy.get",
"configuration.entries.collection.get",
"patron-notice.post",
@@ -877,7 +896,7 @@
"scheduled-notice-storage.scheduled-notices.collection.get",
"scheduled-notice-storage.scheduled-notices.item.delete",
"scheduled-notice-storage.scheduled-notices.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"circulation-storage.loans.collection.get",
"circulation-storage.requests.item.get",
"circulation-storage.requests.collection.get",
@@ -919,7 +938,7 @@
"circulation-storage.loans.item.get",
"circulation-storage.loans.collection.get",
"circulation-storage.circulation-rules.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -955,7 +974,7 @@
"circulation-storage.loans.item.get",
"circulation-storage.loans.collection.get",
"circulation-storage.circulation-rules.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"circulation-storage.loans-history.collection.get",
"users.item.get",
"users.collection.get",
@@ -990,7 +1009,7 @@
"circulation-storage.loans.item.get",
"circulation-storage.loans.collection.get",
"circulation-storage.circulation-rules.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -1023,7 +1042,7 @@
"circulation.rules.request-policy.get",
"circulation.rules.notice-policy.get",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -1054,7 +1073,7 @@
"circulation-storage.patron-notice-policies.collection.get",
"circulation-storage.patron-notice-policies.item.get",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"lost-item-fees-policies.item.get",
"lost-item-fees-policies.collection.get",
"pubsub.publish.post",
@@ -1077,7 +1096,7 @@
"circulation-storage.loans.item.get",
"circulation-storage.loans.collection.get",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"lost-item-fees-policies.item.get",
"lost-item-fees-policies.collection.get",
"owners.collection.get",
@@ -1115,7 +1134,7 @@
"circulation-storage.loan-policies.item.get",
"circulation-storage.loan-policies.collection.get",
"overdue-fines-policies.item.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"circulation.rules.loan-policy.get",
"configuration.entries.collection.get",
"patron-notice.post",
@@ -1153,7 +1172,7 @@
],
"pathPattern": "/circulation/handlers/loan-related-fee-fine-closed",
"permissionsRequired": [
- "pubsub.events.post"
+ "circulation.handlers.loan-related-fee-fine-closed.post"
],
"modulePermissions": [
"modperms.circulation.handlers.loan-related-fee-fine-closed.post",
@@ -1166,7 +1185,7 @@
],
"pathPattern": "/circulation/handlers/fee-fine-balance-changed",
"permissionsRequired": [
- "pubsub.events.post"
+ "circulation.handlers.fee-fine-balance-changed.post"
],
"modulePermissions": [
"modperms.circulation.handlers.fee-fine-balance-changed.post"
@@ -1355,9 +1374,15 @@
"description": "create print event logs"
},
{
- "permissionName": "circulation.requests.queue.reorder.collection.post",
+ "permissionName": "circulation.requests.queue.item-reorder.collection.post",
"displayName": "circulation - reorder queue for an item",
- "description": "change request positions in queue for an item"
+ "description": "change request positions in queue for an item",
+ "replaces": ["circulation.requests.queue.reorder.collection.post"]
+ },
+ {
+ "permissionName": "circulation.requests.queue.instance-reorder.collection.post",
+ "displayName": "circulation - reorder queue for an instance",
+ "description": "change request positions in queue for an instance"
},
{
"permissionName": "circulation.check-out-by-barcode.post",
@@ -1535,7 +1560,13 @@
"description": "move individual request to another item"
},
{
- "permissionName": "circulation.requests.queue.collection.get",
+ "permissionName": "circulation.requests.queue-instance.collection.get",
+ "displayName": "circulation - request queue for an instance",
+ "description": "get request queue for an instance",
+ "replaces": ["circulation.requests.queue.collection.get"]
+ },
+ {
+ "permissionName": "circulation.requests.queue-item.collection.get",
"displayName": "circulation - request queue for an item",
"description": "get request queue for an item"
},
@@ -1570,24 +1601,28 @@
"description": "end patron action session"
},
{
- "permissionName": "circulation.override-renewal-block",
+ "permissionName": "circulation.override-renewal-block.post",
"displayName": "circulation - override renewal block",
- "description": "renewal block override"
+ "description": "renewal block override",
+ "replaces": ["circulation.override-renewal-block"]
},
{
- "permissionName": "circulation.override-item-limit-block",
+ "permissionName": "circulation.override-item-limit-block.post",
"displayName": "circulation - override item limit block",
- "description": "item limit block override"
+ "description": "item limit block override",
+ "replaces": ["circulation.override-item-limit-block"]
},
{
- "permissionName": "circulation.override-item-not-loanable-block",
+ "permissionName": "circulation.override-item-not-loanable-block.post",
"displayName": "circulation - override item not loanable block",
- "description": "item not loanable block override"
+ "description": "item not loanable block override",
+ "replaces": ["circulation.override-item-not-loanable-block.post"]
},
{
- "permissionName": "circulation.override-patron-block",
+ "permissionName": "circulation.override-patron-block.post",
"displayName": "circulation - override patron block",
- "description": "patron block override"
+ "description": "patron block override",
+ "replaces": ["circulation.override-patron-block"]
},
{
"permissionName": "circulation.requests.allowed-service-points.get",
@@ -1595,14 +1630,9 @@
"description": "get allowed pickup service points for request"
},
{
- "permissionName": "mod-settings.global.write.mod-circulation",
- "displayName": "circulation settings - Create configuration",
- "description": "To create new configuration in mod settings"
- },
- {
- "permissionName": "mod-settings.global.read.mod-circulation",
- "displayName": "circulation settings - Read configuration",
- "description": "To read the configuration from mod settings."
+ "permissionName": "circulation.items-by-instance.get",
+ "displayName": "circulation - get items by instance",
+ "description": "get items by instance"
},
{
"permissionName": "circulation.settings.collection.get",
@@ -1629,6 +1659,36 @@
"displayName": "circulation - delete circulation setting",
"description": "delete circulation setting by ID"
},
+ {
+ "permissionName": "circulation.handlers.fee-fine-balance-changed.post",
+ "displayName": "circulation - fee/fine balance changed",
+ "description": "fee/fine balance changed"
+ },
+ {
+ "permissionName": "circulation.handlers.loan-related-fee-fine-closed.post",
+ "displayName": "circulation - loan related fee/fine closed",
+ "description": "loan related fee/fine closed"
+ },
+ {
+ "permissionName": "mod-settings.global.read.mod-circulation",
+ "displayName": "mod-circulation settings - read",
+ "description": "To read mod-circulation settings from mod-settings"
+ },
+ {
+ "permissionName": "mod-settings.global.write.mod-circulation",
+ "displayName": "mod-circulation settings - write",
+ "description": "To create and edit mod-circulation settings in mod-settings"
+ },
+ {
+ "permissionName": "mod-settings.global.read.circulation",
+ "displayName": "circulation functionality settings - read",
+ "description": "To read circulation functionality settings from mod-settings"
+ },
+ {
+ "permissionName": "mod-settings.global.write.circulation",
+ "displayName": "circulation functionality settings - write",
+ "description": "To create and edit circulation functionality settings in mod-settings"
+ },
{
"permissionName": "circulation.all",
"displayName": "circulation - all permissions",
@@ -1663,14 +1723,18 @@
"circulation.requests.item.delete",
"circulation.requests.item.move.post",
"circulation.requests.collection.delete",
- "circulation.requests.queue.collection.get",
+ "circulation.requests.queue-instance.collection.get",
+ "circulation.requests.queue-item.collection.get",
"circulation.requests.queue.reorder.collection.post",
"circulation.requests.instances.item.post",
"circulation.requests.hold-shelf-clearance-report.get",
"circulation.requests.allowed-service-points.get",
"circulation.inventory.items-in-transit-report.get",
"circulation.pick-slips.get",
- "circulation.search-slips.get"
+ "circulation.search-slips.get",
+ "circulation.handlers.loan-related-fee-fine-closed.post",
+ "circulation.handlers.fee-fine-balance-changed.post",
+ "circulation.items-by-instance.get"
]
},
{
@@ -1678,7 +1742,7 @@
"displayName" : "module permissions for one op",
"description" : "to reduce X-Okapi-Token size",
"subPermissions": [
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"circulation-storage.requests.item.get",
"circulation-storage.requests.collection.get",
"users.collection.get",
@@ -1696,7 +1760,7 @@
"circulation-storage.loans.item.put",
"inventory-storage.items.item.get",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"lost-item-fees-policies.item.get",
"accounts.collection.get",
"users.item.get",
@@ -1724,7 +1788,7 @@
"inventory-storage.items.item.get",
"inventory-storage.items.collection.get",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"lost-item-fees-policies.item.get",
"lost-item-fees-policies.collection.get",
"owners.collection.get",
@@ -1763,9 +1827,9 @@
"circulation-storage.cancellation-reasons.item.get",
"circulation-storage.fixed-due-date-schedules.collection.get",
"circulation.rules.notice-policy.get",
- "circulation.internal.apply-rules",
+ "circulation.internal.apply-rules.execute",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -1776,6 +1840,10 @@
"configuration.entries.collection.get",
"calendar.endpoint.calendars.surroundingOpenings.get",
"calendar.endpoint.calendars.allOpenings.get",
+ "mod-settings.entries.item.get",
+ "mod-settings.entries.collection.get",
+ "mod-settings.global.read.circulation",
+ "calendar.endpoint.dates.get",
"pubsub.publish.post",
"circulation-storage.loans-history.collection.get"
],
@@ -1808,7 +1876,7 @@
"circulation.rules.request-policy.get",
"inventory-storage.items.item.put",
"circulation-item.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -1829,7 +1897,8 @@
"checkout-lock-storage.checkout-locks.item.delete",
"mod-settings.entries.collection.get",
"mod-settings.entries.item.get",
- "mod-settings.global.read.mod-circulation"
+ "mod-settings.global.read.mod-circulation",
+ "mod-settings.global.read.circulation"
],
"visible": false
},
@@ -1856,7 +1925,7 @@
"circulation.rules.request-policy.get",
"inventory-storage.items.item.put",
"circulation-item.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -1881,8 +1950,10 @@
"scheduled-notice-storage.scheduled-notices.item.delete",
"scheduled-notice-storage.scheduled-notices.collection.delete",
"scheduled-notice-storage.scheduled-notices.item.post",
- "accounts.refund.post",
- "accounts.cancel.post",
+ "feesfines.accounts.refund.item.post",
+ "feesfines.accounts-bulk.refund.item.post",
+ "feesfines.accounts.cancel.item.post",
+ "feesfines.accounts-bulk.cancel.item.post",
"configuration.entries.collection.get",
"calendar.endpoint.calendars.surroundingOpenings.get",
"calendar.endpoint.calendars.allOpenings.get",
@@ -1891,12 +1962,15 @@
"actual-cost-fee-fine-cancel.post",
"departments.item.get",
"departments.collection.get",
- "circulation-storage.loans-history.collection.get"
+ "circulation-storage.loans-history.collection.get",
+ "mod-settings.entries.item.get",
+ "mod-settings.entries.collection.get",
+ "mod-settings.global.read.circulation"
],
"visible": false
},
{
- "permissionName": "circulation.renew-loan",
+ "permissionName": "circulation.renew-loan.all",
"displayName" : "Renew a loan",
"description" : "Permissions needed to renew a loan",
"subPermissions": [
@@ -1916,7 +1990,7 @@
"circulation.rules.request-policy.get",
"circulation.rules.notice-policy.get",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -1924,6 +1998,9 @@
"calendar.endpoint.calendars.surroundingOpenings.get",
"calendar.endpoint.calendars.allOpenings.get",
"configuration.entries.collection.get",
+ "mod-settings.entries.item.get",
+ "mod-settings.entries.collection.get",
+ "mod-settings.global.read.circulation",
"scheduled-notice-storage.scheduled-notices.collection.delete",
"scheduled-notice-storage.scheduled-notices.item.post",
"patron-notice.post",
@@ -1934,9 +2011,10 @@
"feefineactions.item.get",
"owners.collection.get",
"accounts.item.post",
- "accounts.cancel.post",
+ "feesfines.accounts.cancel.item.post",
+ "feesfines.accounts-bulk.cancel.item.post",
"pubsub.publish.post",
- "automated-patron-blocks.collection.get",
+ "patron-blocks.automated-patron-blocks.collection.get",
"scheduled-notice-storage.scheduled-notices.item.delete",
"overdue-fines-policies.item.get",
"lost-item-fees-policies.item.get",
@@ -1947,10 +2025,11 @@
"actual-cost-fee-fine-cancel.post",
"circulation-storage.loans-history.collection.get"
],
- "visible": false
+ "visible": false,
+ "replaces": ["circulation.renew-loan"]
},
{
- "permissionName": "modperms.circulation.loans.anonymize",
+ "permissionName": "perms.circulation.loans.anonymize.all",
"displayName" : "module permissions for one op",
"description" : "to reduce X-Okapi-Token size",
"subPermissions": [
@@ -1969,7 +2048,7 @@
"circulation.rules.loan-policy.get",
"circulation.rules.request-policy.get",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -1984,7 +2063,8 @@
"pubsub.publish.post",
"circulation-storage.loans-history.collection.get"
],
- "visible": false
+ "visible": false,
+ "replaces": ["modperms.circulation.loans.anonymize"]
},
{
"permissionName": "modperms.circulation.loans.item.post",
@@ -2008,7 +2088,7 @@
"circulation.rules.loan-policy.get",
"circulation.rules.request-policy.get",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"addresstypes.collection.get",
"proxiesfor.collection.get",
@@ -2024,7 +2104,7 @@
"circulation-storage.loans.collection.get",
"circulation-storage.loan-policies.item.get",
"circulation-storage.loan-policies.collection.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.collection.get",
"users.item.get",
"addresstypes.collection.get",
@@ -2046,7 +2126,7 @@
"circulation-storage.loans.item.get",
"circulation-storage.loan-policies.item.get",
"circulation-storage.loan-policies.collection.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"accounts.collection.get",
"usergroups.collection.get",
"usergroups.item.get",
@@ -2065,7 +2145,7 @@
"circulation-storage.requests.collection.get",
"circulation-storage.loans.collection.get",
"circulation-storage.loans.item.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -2095,7 +2175,7 @@
"circulation.rules.loan-policy.get",
"circulation.rules.notice-policy.get",
"circulation.rules.request-policy.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"inventory-storage.items.item.put",
"users.item.get",
"users.collection.get",
@@ -2109,9 +2189,12 @@
"scheduled-notice-storage.scheduled-notices.collection.delete",
"scheduled-notice-storage.scheduled-notices.item.post",
"configuration.entries.collection.get",
+ "mod-settings.entries.item.get",
+ "mod-settings.entries.collection.get",
+ "mod-settings.global.read.circulation",
"manualblocks.collection.get",
"pubsub.publish.post",
- "automated-patron-blocks.collection.get",
+ "patron-blocks.automated-patron-blocks.collection.get",
"circulation-storage.loans-history.collection.get",
"overdue-fines-policies.item.get",
"overdue-fines-policies.collection.get"
@@ -2127,7 +2210,7 @@
"circulation-storage.requests.collection.get",
"circulation-storage.loans.collection.get",
"circulation-storage.loans.item.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -2145,7 +2228,7 @@
"circulation-storage.requests.collection.get",
"circulation-storage.loans.collection.get",
"circulation-storage.loans.item.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -2181,7 +2264,7 @@
"circulation.rules.request-policy.get",
"circulation.rules.notice-policy.get",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -2190,11 +2273,14 @@
"proxiesfor.collection.get",
"patron-notice.post",
"configuration.entries.collection.get",
+ "mod-settings.entries.item.get",
+ "mod-settings.entries.collection.get",
+ "mod-settings.global.read.circulation",
"scheduled-notice-storage.scheduled-notices.collection.delete",
"scheduled-notice-storage.scheduled-notices.item.post",
"pubsub.publish.post",
"manualblocks.collection.get",
- "automated-patron-blocks.collection.get",
+ "patron-blocks.automated-patron-blocks.collection.get",
"circulation-storage.loans-history.collection.get"
],
"visible": false
@@ -2208,7 +2294,7 @@
"circulation-storage.requests.collection.get",
"circulation-storage.loans.collection.get",
"circulation-storage.loans.item.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -2227,13 +2313,18 @@
"circulation-storage.requests.collection.get",
"circulation-storage.loans.collection.get",
"circulation-storage.loans.item.get",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
"usergroups.collection.get",
"usergroups.item.get",
- "pubsub.publish.post"
+ "pubsub.publish.post",
+ "configuration.entries.collection.get",
+ "configuration.entries.item.get",
+ "mod-settings.entries.item.get",
+ "mod-settings.entries.collection.get",
+ "mod-settings.global.read.circulation"
],
"visible": false
},
@@ -2258,7 +2349,7 @@
"circulation.rules.request-policy.get",
"circulation.rules.notice-policy.get",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.collection.get",
@@ -2269,10 +2360,13 @@
"calendar.endpoint.calendars.surroundingOpenings.get",
"calendar.endpoint.calendars.allOpenings.get",
"configuration.entries.collection.get",
+ "mod-settings.entries.item.get",
+ "mod-settings.entries.collection.get",
+ "mod-settings.global.read.circulation",
"scheduled-notice-storage.scheduled-notices.collection.delete",
"scheduled-notice-storage.scheduled-notices.item.post",
"manualblocks.collection.get",
- "automated-patron-blocks.collection.get",
+ "patron-blocks.automated-patron-blocks.collection.get",
"pubsub.publish.post",
"circulation-storage.fixed-due-date-schedules.collection.get",
"circulation-storage.loans-history.collection.get"
@@ -2287,7 +2381,7 @@
"circulation-storage.loans.item.get",
"circulation-storage.loans.item.put",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"lost-item-fees-policies.item.get",
"owners.collection.get",
"feefines.collection.get",
@@ -2308,7 +2402,7 @@
"visible": false
},
{
- "permissionName": "modperms.circulation.loans.change-due-date.post",
+ "permissionName": "perms.circulation.loans.change-due-date.post",
"displayName": "module permissions for one op",
"description": "to reduce X-Okapi-Token size",
"subPermissions": [
@@ -2319,14 +2413,19 @@
"circulation-storage.loans.item.put",
"circulation-storage.patron-notice-policies.item.get",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"scheduled-notice-storage.scheduled-notices.collection.delete",
"scheduled-notice-storage.scheduled-notices.item.post",
"users.item.get",
"addresstypes.collection.get",
"pubsub.publish.post",
"patron-notice.post",
- "circulation-storage.loans-history.collection.get"
+ "circulation-storage.loans-history.collection.get",
+ "configuration.entries.collection.get",
+ "configuration.entries.item.get",
+ "mod-settings.entries.item.get",
+ "mod-settings.entries.collection.get",
+ "mod-settings.global.read.circulation"
],
"visible": false
},
@@ -2337,7 +2436,7 @@
"subPermissions": [
"circulation-storage.loans.item.get",
"circulation-storage.loans.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"addresstypes.collection.get",
"pubsub.publish.post"
@@ -2345,14 +2444,14 @@
"visible": false
},
{
- "permissionName": "modperms.circulation.loans.claim-item-returned.post",
+ "permissionName": "perms.circulation.loans.claim-item-returned.post",
"displayName": "module permissions for one op",
"description": "to reduce X-Okapi-Token size",
"subPermissions": [
"circulation-storage.loans.item.get",
"circulation-storage.loans.item.put",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"addresstypes.collection.get",
"notes.collection.get",
@@ -2360,7 +2459,8 @@
"note.types.collection.get",
"pubsub.publish.post"
],
- "visible": false
+ "visible": false,
+ "replaces": ["modperms.circulation.loans.claim-item-returned.post"]
},
{
"permissionName": "modperms.circulation.loans.declare-claimed-returned-item-as-missing.post",
@@ -2370,7 +2470,7 @@
"circulation-storage.loans.item.get",
"circulation-storage.loans.item.put",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"users.item.get",
"addresstypes.collection.get",
"notes.item.post",
@@ -2399,7 +2499,7 @@
"circulation.rules.request-policy.get",
"circulation.rules.notice-policy.get",
"inventory-storage.items.item.put",
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"proxiesfor.collection.get",
"proxiesfor.collection.get",
"users.item.get",
@@ -2420,7 +2520,7 @@
"displayName": "module permissions for one op",
"description": "to reduce X-Okapi-Token size",
"subPermissions": [
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"circulation-storage.requests.item.get",
"circulation-storage.requests.collection.get",
"circulation-storage.loans.collection.get",
@@ -2433,7 +2533,7 @@
"displayName": "module permissions for one op",
"description": "to reduce X-Okapi-Token size",
"subPermissions": [
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"circulation-storage.requests.item.get",
"circulation-storage.requests.collection.get",
"users.item.get",
@@ -2448,7 +2548,7 @@
"displayName": "module permissions for one op",
"description": "to reduce X-Okapi-Token size",
"subPermissions": [
- "circulation.internal.fetch-items",
+ "circulation.internal.fetch-items.collection.get",
"circulation-storage.requests.item.get",
"circulation-storage.requests.collection.get",
"inventory-storage.holdings.item.get",
@@ -2463,7 +2563,7 @@
"visible": false
},
{
- "permissionName": "circulation.internal.fetch-items",
+ "permissionName": "circulation.internal.fetch-items.collection.get",
"displayName" : "Fetch item(s)",
"description" : "Internal permission set for fetching item(s)",
"subPermissions": [
@@ -2492,10 +2592,11 @@
"inventory-storage.identifier-types.item.get",
"inventory-storage.identifier-types.collection.get"
],
- "visible": false
+ "visible": false,
+ "replaces": ["circulation.internal.fetch-items"]
},
{
- "permissionName": "circulation.internal.apply-rules",
+ "permissionName": "circulation.internal.apply-rules.execute",
"displayName" : "Apply circulation rules",
"description" : "Internal permission set for applying circulation rules",
"subPermissions": [
@@ -2509,7 +2610,8 @@
"circulation-storage.patron-notice-policies.item.get",
"circulation-storage.patron-notice-policies.collection.get"
],
- "visible": false
+ "visible": false,
+ "replaces": ["circulation.internal.apply-rules"]
}
],
"launchDescriptor": {
diff --git a/pom.xml b/pom.xml
index 4572bafd77..3821e1f663 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0
mod-circulation
org.folio
- 24.3.2-SNAPSHOT
+ 24.3.3-SNAPSHOT
Apache License 2.0
@@ -33,6 +33,9 @@
1.18.28
6.1.5
3.9.1
+
+
+ 1.0.1
@@ -300,7 +303,7 @@
https://github.com/folio-org/mod-inventory
scm:git:git://github.com:folio-org/mod-inventory.git
scm:git:git@github.com:folio-org/mod-inventory.git
- v24.3.0
+ HEAD
@@ -518,6 +521,21 @@
false
+
+ org.folio
+ folio-module-descriptor-validator
+ ${folio-module-descriptor-validator.version}
+
+
+
+ validate
+
+
+
+
+ false
+
+
@@ -529,4 +547,14 @@
+
+
+
+ folio-nexus
+ FOLIO Maven repository
+ https://repository.folio.org/repository/maven-folio
+
+
+
+
diff --git a/ramls/circulation.raml b/ramls/circulation.raml
index ea45ea2779..dc8287451f 100644
--- a/ramls/circulation.raml
+++ b/ramls/circulation.raml
@@ -337,6 +337,14 @@ resourceTypes:
description: "Instance ID"
pattern: "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[1-5][a-fA-F0-9]{3}-[89abAB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$"
required: false
+ useStubItem:
+ description: "When true, allows to apply circulation rules based on patron group only"
+ type: boolean
+ required: false
+ ecsRequestRouting:
+ description: "When true, returns only service points with ecsRequestRouting"
+ type: boolean
+ required: false
responses:
200:
description: "List of allowed service points was retrieved successfully"
diff --git a/src/main/java/org/folio/circulation/CirculationVerticle.java b/src/main/java/org/folio/circulation/CirculationVerticle.java
index 4956ddb53a..837d519891 100644
--- a/src/main/java/org/folio/circulation/CirculationVerticle.java
+++ b/src/main/java/org/folio/circulation/CirculationVerticle.java
@@ -21,6 +21,7 @@
import org.folio.circulation.resources.FeeFineNotRealTimeScheduledNoticeProcessingResource;
import org.folio.circulation.resources.FeeFineScheduledNoticeProcessingResource;
import org.folio.circulation.resources.HealthResource;
+import org.folio.circulation.resources.ItemsByInstanceResource;
import org.folio.circulation.resources.ItemsInTransitResource;
import org.folio.circulation.resources.LoanAnonymizationResource;
import org.folio.circulation.resources.LoanCirculationRulesEngineResource;
@@ -94,6 +95,7 @@ public void start(Promise startFuture) {
new RequestCollectionResource(client).register(router);
new RequestQueueResource(client).register(router);
new RequestByInstanceIdResource(client).register(router);
+ new ItemsByInstanceResource(client).register(router);
new RequestHoldShelfClearanceResource(
"/circulation/requests-reports/hold-shelf-clearance/:servicePointId", client)
diff --git a/src/main/java/org/folio/circulation/domain/AllowedServicePointsRequest.java b/src/main/java/org/folio/circulation/domain/AllowedServicePointsRequest.java
index 1e4bc603ef..c0ec5b2f06 100644
--- a/src/main/java/org/folio/circulation/domain/AllowedServicePointsRequest.java
+++ b/src/main/java/org/folio/circulation/domain/AllowedServicePointsRequest.java
@@ -17,8 +17,12 @@ public class AllowedServicePointsRequest {
private Request.Operation operation;
private String requesterId;
+ private String patronGroupId;
private String instanceId;
private String itemId;
+ private String requestId;
+ private boolean useStubItem;
+ private boolean ecsRequestRouting;
public boolean isForTitleLevelRequest() {
return instanceId != null;
@@ -27,7 +31,6 @@ public boolean isForTitleLevelRequest() {
public boolean isForItemLevelRequest() {
return itemId != null;
}
- private String requestId;
public AllowedServicePointsRequest updateWithRequestInformation(Request request) {
log.debug("updateWithRequestInformation:: parameters request: {}", request);
diff --git a/src/main/java/org/folio/circulation/domain/CreateRequestService.java b/src/main/java/org/folio/circulation/domain/CreateRequestService.java
index 3893a9dd3a..66ec8fff43 100644
--- a/src/main/java/org/folio/circulation/domain/CreateRequestService.java
+++ b/src/main/java/org/folio/circulation/domain/CreateRequestService.java
@@ -227,6 +227,11 @@ private CompletableFuture> checkPolicy(
boolean tlrFeatureEnabled = request.getTlrSettingsConfiguration().isTitleLevelRequestsFeatureEnabled();
if (tlrFeatureEnabled && request.isTitleLevel() && request.isHold()) {
+ if (request.getEcsRequestPhase() == EcsRequestPhase.PRIMARY) {
+ log.warn("checkPolicy:: ECS TLR primary Hold detected, skipping policy check");
+ return ofAsync(() -> records);
+ }
+
log.info("checkPolicy:: checking policy for title-level hold");
return completedFuture(checkPolicyForTitleLevelHold(records));
}
diff --git a/src/main/java/org/folio/circulation/domain/EcsRequestPhase.java b/src/main/java/org/folio/circulation/domain/EcsRequestPhase.java
new file mode 100644
index 0000000000..8272931b2e
--- /dev/null
+++ b/src/main/java/org/folio/circulation/domain/EcsRequestPhase.java
@@ -0,0 +1,37 @@
+package org.folio.circulation.domain;
+
+import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;
+
+import java.util.Arrays;
+
+public enum EcsRequestPhase {
+ NONE(""),
+ PRIMARY("Primary"),
+ SECONDARY("Secondary");
+
+ public final String value;
+
+ public static EcsRequestPhase from(String value) {
+ return Arrays.stream(values())
+ .filter(status -> status.nameMatches(value))
+ .findFirst()
+ .orElse(NONE);
+ }
+
+ EcsRequestPhase(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public boolean nameMatches(String value) {
+ return equalsIgnoreCase(getValue(), value);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+}
diff --git a/src/main/java/org/folio/circulation/domain/Item.java b/src/main/java/org/folio/circulation/domain/Item.java
index d58a83346b..5fd69c5255 100644
--- a/src/main/java/org/folio/circulation/domain/Item.java
+++ b/src/main/java/org/folio/circulation/domain/Item.java
@@ -447,6 +447,17 @@ public String getDcbItemTitle() {
return getProperty(itemRepresentation, "instanceTitle");
}
+ public String getTenantId() {
+ return getProperty(itemRepresentation, "tenantId");
+ }
+
+ public Item changeTenantId(String tenantId) {
+ if (itemRepresentation != null) {
+ write(itemRepresentation, "tenantId", tenantId);
+ }
+ return this;
+ }
+
public boolean isAtLocation(String locationCode) {
return locationCode != null && getLocation() != null && (
locationCode.equals(getLocation().getCode()) ||
diff --git a/src/main/java/org/folio/circulation/domain/MoveRequestService.java b/src/main/java/org/folio/circulation/domain/MoveRequestService.java
index 3077016093..d13be99cb9 100644
--- a/src/main/java/org/folio/circulation/domain/MoveRequestService.java
+++ b/src/main/java/org/folio/circulation/domain/MoveRequestService.java
@@ -10,6 +10,7 @@
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;
import org.folio.circulation.infrastructure.storage.requests.RequestRepository;
@@ -27,13 +28,14 @@ public class MoveRequestService {
private final ConfigurationRepository configurationRepository;
private final EventPublisher eventPublisher;
private final RequestQueueRepository requestQueueRepository;
+ private final SettingsRepository settingsRepository;
private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
public MoveRequestService(RequestRepository requestRepository, RequestPolicyRepository requestPolicyRepository,
UpdateUponRequest updateUponRequest, MoveRequestProcessAdapter moveRequestHelper,
RequestLoanValidator requestLoanValidator, RequestNoticeSender requestNoticeSender,
ConfigurationRepository configurationRepository, EventPublisher eventPublisher,
- RequestQueueRepository requestQueueRepository) {
+ RequestQueueRepository requestQueueRepository, SettingsRepository settingsRepository) {
this.requestRepository = requestRepository;
this.requestPolicyRepository = requestPolicyRepository;
@@ -44,11 +46,12 @@ public MoveRequestService(RequestRepository requestRepository, RequestPolicyRepo
this.configurationRepository = configurationRepository;
this.eventPublisher = eventPublisher;
this.requestQueueRepository = requestQueueRepository;
+ this.settingsRepository = settingsRepository;
}
public CompletableFuture> moveRequest(
RequestAndRelatedRecords requestAndRelatedRecords, Request originalRequest) {
- return configurationRepository.lookupTlrSettings()
+ return settingsRepository.lookupTlrSettings()
.thenApply(r -> r.map(requestAndRelatedRecords::withTlrSettings))
.thenApply(r -> r.next(RequestServiceUtility::refuseTlrProcessingWhenFeatureIsDisabled))
.thenApply(r -> r.next(records -> RequestServiceUtility.refuseMovingToOrFromHoldTlr(records,
diff --git a/src/main/java/org/folio/circulation/domain/Request.java b/src/main/java/org/folio/circulation/domain/Request.java
index 4075fdf50d..597cda08ef 100644
--- a/src/main/java/org/folio/circulation/domain/Request.java
+++ b/src/main/java/org/folio/circulation/domain/Request.java
@@ -18,6 +18,7 @@
import static org.folio.circulation.domain.representations.RequestProperties.CANCELLATION_REASON_NAME;
import static org.folio.circulation.domain.representations.RequestProperties.CANCELLATION_REASON_PUBLIC_DESCRIPTION;
import static org.folio.circulation.domain.representations.RequestProperties.ITEM_LOCATION_CODE;
+import static org.folio.circulation.domain.representations.RequestProperties.ECS_REQUEST_PHASE;
import static org.folio.circulation.domain.representations.RequestProperties.HOLDINGS_RECORD_ID;
import static org.folio.circulation.domain.representations.RequestProperties.HOLD_SHELF_EXPIRATION_DATE;
import static org.folio.circulation.domain.representations.RequestProperties.INSTANCE_ID;
@@ -269,6 +270,10 @@ public RequestType getRequestType() {
return RequestType.from(getProperty(requestRepresentation, REQUEST_TYPE));
}
+ public EcsRequestPhase getEcsRequestPhase() {
+ return EcsRequestPhase.from(getProperty(requestRepresentation, ECS_REQUEST_PHASE));
+ }
+
boolean allowedForItem() {
return RequestTypeItemStatusWhiteList.canCreateRequestForItem(getItem().getStatus(), getRequestType());
}
diff --git a/src/main/java/org/folio/circulation/domain/SearchInstance.java b/src/main/java/org/folio/circulation/domain/SearchInstance.java
new file mode 100644
index 0000000000..809a121181
--- /dev/null
+++ b/src/main/java/org/folio/circulation/domain/SearchInstance.java
@@ -0,0 +1,50 @@
+package org.folio.circulation.domain;
+
+import static org.folio.circulation.support.json.JsonObjectArrayPropertyFetcher.mapToList;
+import static org.folio.circulation.support.json.JsonPropertyWriter.write;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.folio.circulation.domain.representations.ItemSummaryRepresentation;
+import org.folio.circulation.storage.mappers.ItemMapper;
+
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+import lombok.NonNull;
+import lombok.ToString;
+import lombok.Value;
+
+@Value
+@ToString(onlyExplicitlyIncluded = true)
+public class SearchInstance {
+
+ private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
+ JsonObject representation;
+ String id;
+ @NonNull Collection- items;
+
+ public static SearchInstance from(JsonObject representation) {
+ return new SearchInstance(representation, representation.getString("id"), mapItems(representation));
+ }
+
+ private static List
- mapItems(JsonObject representation) {
+ return mapToList(representation, "items", new ItemMapper()::toDomain);
+ }
+
+ public SearchInstance changeItems(Collection
- items) {
+ JsonArray itemsArray = new JsonArray();
+ for (Item item : items) {
+ itemsArray.add(new ItemSummaryRepresentation().createItemSummary(item));
+ }
+ write(representation, "items", itemsArray);
+ return new SearchInstance(representation, id, items);
+ }
+
+ public JsonObject toJson() {
+ return representation;
+ }
+}
diff --git a/src/main/java/org/folio/circulation/domain/configuration/TlrSettingsConfiguration.java b/src/main/java/org/folio/circulation/domain/configuration/TlrSettingsConfiguration.java
index 75b12913ee..a16b61c95f 100644
--- a/src/main/java/org/folio/circulation/domain/configuration/TlrSettingsConfiguration.java
+++ b/src/main/java/org/folio/circulation/domain/configuration/TlrSettingsConfiguration.java
@@ -11,12 +11,14 @@
import io.vertx.core.json.JsonObject;
import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
@AllArgsConstructor
@Getter
@ToString(onlyExplicitlyIncluded = true)
+@EqualsAndHashCode
public class TlrSettingsConfiguration {
protected static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
diff --git a/src/main/java/org/folio/circulation/domain/override/OverridableBlockType.java b/src/main/java/org/folio/circulation/domain/override/OverridableBlockType.java
index f28b744411..8880db0086 100644
--- a/src/main/java/org/folio/circulation/domain/override/OverridableBlockType.java
+++ b/src/main/java/org/folio/circulation/domain/override/OverridableBlockType.java
@@ -9,15 +9,15 @@
@RequiredArgsConstructor
public enum OverridableBlockType {
PATRON_BLOCK("patronBlock",
- OkapiPermissions.of("circulation.override-patron-block")),
+ OkapiPermissions.of("circulation.override-patron-block.post")),
ITEM_LIMIT_BLOCK("itemLimitBlock",
- OkapiPermissions.of("circulation.override-item-limit-block")),
+ OkapiPermissions.of("circulation.override-item-limit-block.post")),
ITEM_NOT_LOANABLE_BLOCK("itemNotLoanableBlock",
- OkapiPermissions.of("circulation.override-item-not-loanable-block")),
+ OkapiPermissions.of("circulation.override-item-not-loanable-block.post")),
RENEWAL_BLOCK("renewalBlock",
- OkapiPermissions.of("circulation.override-renewal-block")),
+ OkapiPermissions.of("circulation.override-renewal-block.post")),
RENEWAL_DUE_DATE_REQUIRED_BLOCK("renewalDueDateRequiredBlock",
- OkapiPermissions.of("circulation.override-renewal-block"));
+ OkapiPermissions.of("circulation.override-renewal-block.post"));
private final String name;
private final OkapiPermissions requiredOverridePermissions;
@@ -25,4 +25,4 @@ public enum OverridableBlockType {
public OkapiPermissions getMissingOverridePermissions(OkapiPermissions existingPermissions) {
return requiredOverridePermissions.getAllNotContainedIn(existingPermissions);
}
-}
\ No newline at end of file
+}
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 f18255537b..1dde6fc960 100644
--- a/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java
+++ b/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java
@@ -46,6 +46,7 @@ public JsonObject createItemSummary(Item item) {
write(itemSummary, "copyNumber", item.getCopyNumber());
write(itemSummary, CALL_NUMBER_COMPONENTS,
createCallNumberComponents(item.getCallNumberComponents()));
+ write(itemSummary, "tenantId", item.getTenantId());
JsonObject status = new JsonObject()
.put("name", item.getStatus().getValue());
diff --git a/src/main/java/org/folio/circulation/domain/representations/RequestProperties.java b/src/main/java/org/folio/circulation/domain/representations/RequestProperties.java
index 830f2109d7..2be39bd4a0 100644
--- a/src/main/java/org/folio/circulation/domain/representations/RequestProperties.java
+++ b/src/main/java/org/folio/circulation/domain/representations/RequestProperties.java
@@ -9,6 +9,7 @@ private RequestProperties() { }
public static final String HOLDINGS_RECORD_ID = "holdingsRecordId";
public static final String REQUEST_LEVEL = "requestLevel";
public static final String REQUEST_TYPE = "requestType";
+ public static final String ECS_REQUEST_PHASE = "ecsRequestPhase";
public static final String PROXY_USER_ID = "proxyUserId";
public static final String POSITION = "position";
public static final String HOLD_SHELF_EXPIRATION_DATE = "holdShelfExpirationDate";
diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/ConfigurationRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/ConfigurationRepository.java
index 52a974504d..992628921e 100644
--- a/src/main/java/org/folio/circulation/infrastructure/storage/ConfigurationRepository.java
+++ b/src/main/java/org/folio/circulation/infrastructure/storage/ConfigurationRepository.java
@@ -20,7 +20,9 @@
import org.folio.circulation.support.results.Result;
import io.vertx.core.json.JsonObject;
+import lombok.extern.log4j.Log4j2;
+@Log4j2
public class ConfigurationRepository {
private static final String CONFIGS_KEY = "configs";
private static final String MODULE_NAME_KEY = "module";
@@ -50,6 +52,7 @@ public CompletableFuture> lookupSessionTimeout() {
}
public CompletableFuture> lookupTlrSettings() {
+ log.info("lookupTlrSettings:: fetching TLR configuration");
Result queryResult = defineModuleNameAndConfigNameFilter(
"SETTINGS", "TLR");
diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/SearchRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/SearchRepository.java
new file mode 100644
index 0000000000..ed441ce461
--- /dev/null
+++ b/src/main/java/org/folio/circulation/infrastructure/storage/SearchRepository.java
@@ -0,0 +1,94 @@
+package org.folio.circulation.infrastructure.storage;
+
+import static org.folio.circulation.support.StringUtil.urlEncode;
+import static org.folio.circulation.support.results.Result.emptyAsync;
+import static org.folio.circulation.support.results.Result.failed;
+import static org.folio.circulation.support.results.ResultBinding.flatMapResult;
+import static org.folio.circulation.support.results.ResultBinding.mapResult;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.folio.circulation.domain.Item;
+import org.folio.circulation.domain.MultipleRecords;
+import org.folio.circulation.domain.SearchInstance;
+import org.folio.circulation.infrastructure.storage.inventory.ItemRepository;
+import org.folio.circulation.support.AsyncCoordinationUtil;
+import org.folio.circulation.support.BadRequestFailure;
+import org.folio.circulation.support.Clients;
+import org.folio.circulation.support.CollectionResourceClient;
+import org.folio.circulation.support.http.client.Response;
+import org.folio.circulation.support.http.server.WebContext;
+import org.folio.circulation.support.results.Result;
+
+import io.vertx.core.http.HttpClient;
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public class SearchRepository {
+
+ private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
+ private final WebContext webContext;
+ private final HttpClient httpClient;
+ private final CollectionResourceClient searchClient;
+
+ public SearchRepository(WebContext webContext, HttpClient httpClient) {
+ this.webContext = webContext;
+ this.httpClient = httpClient;
+ this.searchClient = Clients.create(webContext, httpClient).searchClient();
+ }
+
+ public CompletableFuture> getInstanceWithItems(List queryParams) {
+ log.debug("getInstanceWithItems:: query {}", queryParams);
+ if (queryParams.isEmpty()) {
+ return CompletableFuture.completedFuture(failed(new BadRequestFailure(
+ "query is empty")));
+ }
+ return searchClient.getManyWithQueryStringParameters(Map.of("expandAll",
+ "true", "query", urlEncode(queryParams.get(0))))
+ .thenApply(flatMapResult(this::mapResponseToInstances))
+ .thenApply(mapResult(MultipleRecords::firstOrNull))
+ .thenCompose(r -> r.after(this::updateItemDetails));
+ }
+
+ private Result> mapResponseToInstances(Response response) {
+ return MultipleRecords.from(response, SearchInstance::from, "instances");
+ }
+
+ private CompletableFuture> updateItemDetails(SearchInstance searchInstance) {
+ log.debug("updateItemDetails:: searchInstance {}", () -> searchInstance);
+ if (searchInstance == null || searchInstance.getId() == null) {
+ log.info("updateItemDetails:: searchInstance is empty");
+ return emptyAsync();
+ }
+
+ Map> itemsByTenant = searchInstance.getItems()
+ .stream()
+ .collect(Collectors.groupingBy(Item::getTenantId));
+
+ log.info("updateItemDetails:: fetching item details from tenants: {}", itemsByTenant::keySet);
+
+ return AsyncCoordinationUtil.allOf(itemsByTenant, this::fetchItemDetails)
+ .thenApply(r -> r.map(lists -> lists.stream().flatMap(Collection::stream).toList()))
+ .thenApply(r -> r.map(searchInstance::changeItems));
+ }
+
+ private CompletableFuture>> fetchItemDetails(String tenantId, List
- items) {
+ ItemRepository itemRepository = new ItemRepository(Clients.create(webContext, httpClient, tenantId));
+
+ return AsyncCoordinationUtil.allOf(items, item -> fetchItemDetails(item, itemRepository));
+ }
+
+ private CompletableFuture> fetchItemDetails(Item searchItem,
+ ItemRepository itemRepository) {
+
+ return itemRepository.fetchById(searchItem.getItemId())
+ .thenApply(r -> r.map(item -> item.changeTenantId(searchItem.getTenantId())));
+ }
+}
diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/ServicePointRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/ServicePointRepository.java
index fe9ec5c7e1..7225491566 100644
--- a/src/main/java/org/folio/circulation/infrastructure/storage/ServicePointRepository.java
+++ b/src/main/java/org/folio/circulation/infrastructure/storage/ServicePointRepository.java
@@ -41,7 +41,15 @@ public class ServicePointRepository {
private final CollectionResourceClient servicePointsStorageClient;
public ServicePointRepository(Clients clients) {
- servicePointsStorageClient = clients.servicePointsStorage();
+ this(clients, false);
+ }
+
+ public ServicePointRepository(Clients clients, boolean includeRoutingServicePoints) {
+ if (includeRoutingServicePoints) {
+ servicePointsStorageClient = clients.routingServicePointsStorage();
+ } else {
+ servicePointsStorageClient = clients.servicePointsStorage();
+ }
}
public CompletableFuture> getServicePointById(UUID id) {
@@ -206,22 +214,24 @@ public CompletableFuture>> findServicePointsById
.thenApply(r -> r.map(MultipleRecords::getRecords));
}
- public CompletableFuture>> fetchPickupLocationServicePoints() {
+ public CompletableFuture>> fetchServicePointsByIndexName(
+ String indexName) {
+
return createServicePointsFetcher().find(MultipleCqlIndexValuesCriteria.builder()
- .indexName("pickupLocation")
+ .indexName(indexName)
.indexOperator(CqlQuery::matchAny)
.value("true")
.build())
.thenApply(r -> r.map(MultipleRecords::getRecords));
}
- public CompletableFuture>> fetchPickupLocationServicePointsByIds(
- Set ids) {
+ public CompletableFuture>>
+ fetchPickupLocationServicePointsByIdsAndIndexName(Set ids, String indexName) {
- log.debug("filterIdsByServicePointsAndPickupLocationExistence:: parameters ids: {}",
- () -> collectionAsString(ids));
+ log.debug("filterIdsByServicePointsAndPickupLocationExistence:: parameters ids: {}, " +
+ "indexName: {}", () -> collectionAsString(ids), () -> indexName);
- Result pickupLocationQuery = exactMatch("pickupLocation", "true");
+ Result pickupLocationQuery = exactMatch(indexName, "true");
return createServicePointsFetcher().findByIdIndexAndQuery(ids, "id", pickupLocationQuery)
.thenApply(r -> r.map(MultipleRecords::getRecords));
diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/SettingsRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/SettingsRepository.java
index 89f2e845ba..cd738cd94b 100644
--- a/src/main/java/org/folio/circulation/infrastructure/storage/SettingsRepository.java
+++ b/src/main/java/org/folio/circulation/infrastructure/storage/SettingsRepository.java
@@ -6,6 +6,7 @@
import org.folio.circulation.domain.Configuration;
import org.folio.circulation.domain.MultipleRecords;
import org.folio.circulation.domain.configuration.CheckoutLockConfiguration;
+import org.folio.circulation.domain.configuration.TlrSettingsConfiguration;
import org.folio.circulation.support.Clients;
import org.folio.circulation.support.GetManyRecordsClient;
import org.folio.circulation.support.http.client.CqlQuery;
@@ -13,28 +14,31 @@
import org.folio.circulation.support.results.Result;
import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
+import static java.util.function.Function.identity;
import static org.folio.circulation.support.http.client.CqlQuery.exactMatch;
+import static org.folio.circulation.support.http.client.CqlQuery.exactMatchAny;
+import static org.folio.circulation.support.results.Result.ofAsync;
import static org.folio.circulation.support.results.Result.succeeded;
public class SettingsRepository {
private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
private final GetManyRecordsClient settingsClient;
+ private final ConfigurationRepository configurationRepository;
public SettingsRepository(Clients clients) {
settingsClient = clients.settingsStorageClient();
+ configurationRepository = new ConfigurationRepository(clients);
}
public CompletableFuture> lookUpCheckOutLockSettings() {
+ log.debug("lookUpCheckOutLockSettings:: fetching checkout lock settings");
try {
- log.debug("lookUpCheckOutLockSettings:: fetching checkout lock settings");
- final Result moduleQuery = exactMatch("scope", "mod-circulation");
- final Result configNameQuery = exactMatch("key", "checkoutLockFeature");
-
- return moduleQuery.combine(configNameQuery, CqlQuery::and)
- .after(cqlQuery -> settingsClient.getMany(cqlQuery, PageLimit.noLimit()))
- .thenApply(result -> result.next(response -> MultipleRecords.from(response, Configuration::new, "items")))
+ return fetchSettings("mod-circulation", "checkoutLockFeature")
+ .thenApply(r -> r.map(records -> records.mapRecords(Configuration::new)))
.thenApply(r -> r.map(r1 -> r1.getRecords().stream().findFirst()
.map(Configuration::getValue)
.map(JsonObject::new)
@@ -49,4 +53,40 @@ public CompletableFuture> lookUpCheckOutLockSe
return CompletableFuture.completedFuture(succeeded(CheckoutLockConfiguration.from(new JsonObject())));
}
}
+
+ public CompletableFuture> lookupTlrSettings() {
+ log.info("lookupTlrSettings:: fetching TLR settings");
+ return fetchSettings("circulation", List.of("generalTlr", "regularTlr"))
+ .thenApply(r -> r.map(SettingsRepository::extractAndMergeValues))
+ .thenCompose(r -> r.after(this::buildTlrSettings));
+ }
+
+ private CompletableFuture>> fetchSettings(String scope, String key) {
+ return fetchSettings(scope, List.of(key));
+ }
+
+ private CompletableFuture>> fetchSettings(String scope,
+ Collection keys) {
+
+ return exactMatch("scope", scope)
+ .combine(exactMatchAny("key", keys), CqlQuery::and)
+ .after(query -> settingsClient.getMany(query, PageLimit.noLimit()))
+ .thenApply(r -> r.next(response -> MultipleRecords.from(response, identity(), "items")));
+ }
+
+ private static JsonObject extractAndMergeValues(MultipleRecords entries) {
+ return entries.getRecords()
+ .stream()
+ .map(rec -> rec.getJsonObject("value"))
+ .reduce(new JsonObject(), JsonObject::mergeIn);
+ }
+
+ private CompletableFuture> buildTlrSettings(JsonObject tlrSettings) {
+ if (tlrSettings.isEmpty()) {
+ log.info("getTlrSettings:: failed to find TLR settings, falling back to legacy configuration");
+ return configurationRepository.lookupTlrSettings();
+ }
+
+ return ofAsync(TlrSettingsConfiguration.from(tlrSettings));
+ }
}
diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestPolicyRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestPolicyRepository.java
index 60f35c780d..42a1fe5abf 100644
--- a/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestPolicyRepository.java
+++ b/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestPolicyRepository.java
@@ -15,6 +15,7 @@
import java.util.Collection;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
@@ -74,18 +75,18 @@ public CompletableFuture> lookupRequestPolicies(Request request)
public CompletableFuture> lookupRequestPolicy(Item item, User user) {
log.debug("lookupRequestPolicy:: parameters item: {}, user: {}", item, user);
return lookupRequestPolicyId(item, user)
- .thenComposeAsync(r -> r.after(this::lookupRequestPolicy))
+ .thenComposeAsync(r -> r.after(this::lookupRequestPolicyById))
.thenApply(result -> result.map(RequestPolicy::from));
}
public CompletableFuture>>> lookupRequestPolicies(
- Collection
- items, User user) {
+ Collection
- items, String patronGroupId) {
- log.debug("lookupRequestPolicies:: parameters items: {}, user: {}",
- items::size, () -> asJson(user));
+ log.debug("lookupRequestPolicies:: parameters items: {}, patronGroupId: {}",
+ items::size, () -> asJson(patronGroupId));
Map> criteriaMap = items.stream()
- .map(item -> new CirculationRuleCriteria(item, user))
+ .map(item -> new CirculationRuleCriteria(item, patronGroupId))
.collect(toMap(identity(), criteria -> Set.of(criteria.getItem()), itemsMergeOperator()));
return allOf(criteriaMap.entrySet(), entry -> lookupRequestPolicyId(entry.getKey())
@@ -95,12 +96,21 @@ public CompletableFuture>>> lookupRequestPol
.thenCompose(r -> r.after(this::lookupRequestPolicies));
}
+ public CompletableFuture> lookupRequestPolicy(String patronGroupId) {
+ // Circulation rules need to be executed with the patron group parameter only.
+ // All the item-related parameters should be random UUIDs.
+ return lookupRequestPolicyId(UUID.randomUUID().toString(), patronGroupId,
+ UUID.randomUUID().toString(), UUID.randomUUID().toString())
+ .thenCompose(r -> r.after(this::lookupRequestPolicyById))
+ .thenApply(result -> result.map(RequestPolicy::from));
+ }
+
private BinaryOperator> itemsMergeOperator() {
return (items1, items2) -> Stream.concat(items1.stream(), items2.stream())
.collect(Collectors.toSet());
}
- private CompletableFuture> lookupRequestPolicy(
+ private CompletableFuture> lookupRequestPolicyById(
String requestPolicyId) {
log.debug("lookupRequestPolicy:: parameters requestPolicyId: {}", requestPolicyId);
diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestQueueRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestQueueRepository.java
index bcfa69eec3..e4ecf9fdcb 100644
--- a/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestQueueRepository.java
+++ b/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestQueueRepository.java
@@ -1,5 +1,6 @@
package org.folio.circulation.infrastructure.storage.requests;
+import static java.util.Collections.emptyList;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static org.folio.circulation.domain.RequestLevel.ITEM;
import static org.folio.circulation.domain.RequestLevel.TITLE;
@@ -7,13 +8,16 @@
import static org.folio.circulation.support.http.client.CqlQuery.exactMatch;
import static org.folio.circulation.support.http.client.CqlQuery.exactMatchAny;
import static org.folio.circulation.support.http.client.PageLimit.oneThousand;
+import static org.folio.circulation.support.results.Result.ofAsync;
import static org.folio.circulation.support.results.Result.succeeded;
import static org.folio.circulation.support.results.ResultBinding.mapResult;
-import static org.folio.circulation.support.utils.LogUtil.collectionAsString;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@@ -60,9 +64,10 @@ public CompletableFuture> get(RequestAndRelated
public CompletableFuture> getQueue(TlrSettingsConfiguration tlrSettings,
String instanceId, String itemId) {
- return tlrSettings != null && tlrSettings.isTitleLevelRequestsFeatureEnabled()
- ? getByInstanceId(instanceId)
- : getByItemId(itemId);
+ boolean isTlrEnabled = tlrSettings != null && tlrSettings.isTitleLevelRequestsFeatureEnabled();
+ log.info("getQueue:: TLR feature is {}", isTlrEnabled ? "enabled" : "disabled");
+
+ return isTlrEnabled ? getByInstanceId(instanceId) : getByItemId(itemId);
}
public CompletableFuture> get(RenewalContext context) {
@@ -74,33 +79,47 @@ public CompletableFuture> get(RenewalContext context) {
).thenApply(result -> result.map(context::withRequestQueue));
}
+ public CompletableFuture> getByInstanceIdAndItemId(String instanceId,
+ String itemId) {
+
+ return get(itemId, instanceId, EnumSet.of(ITEM, TITLE));
+ }
+
public CompletableFuture> getByInstanceId(String instanceId) {
- return get("instanceId", instanceId, List.of(ITEM, TITLE));
+ return get(null, instanceId, EnumSet.of(ITEM, TITLE));
}
public CompletableFuture> getByItemId(String itemId) {
- return get("itemId", itemId, List.of(ITEM));
+ return get(itemId, null, EnumSet.of(ITEM));
}
- private CompletableFuture> get(String idFieldName, String id,
- Collection requestLevels) {
+ private CompletableFuture> get(String itemId, String instanceId,
+ EnumSet requestLevels) {
- log.debug("get:: parameters idFieldName: {}, id: {}, requestLevels: {}",
- () -> idFieldName, () -> id, () -> collectionAsString(requestLevels));
+ Map filters = new HashMap<>();
+ if (itemId != null) {
+ filters.put("itemId", itemId);
+ }
+ if (instanceId != null) {
+ filters.put("instanceId", instanceId);
+ }
+ if (filters.isEmpty()) {
+ log.info("get:: itemId and instanceId are null, returning an empty queue");
+ return ofAsync(new RequestQueue(emptyList()));
+ }
List requestLevelStrings = requestLevels.stream()
.map(RequestLevel::getValue)
.collect(Collectors.toList());
- final Result itemIdQuery = exactMatch(idFieldName, id);
final Result statusQuery = exactMatchAny("status", RequestStatus.openStates());
final Result requestLevelQuery = exactMatchAny("requestLevel", requestLevelStrings);
- return itemIdQuery.combine(statusQuery, CqlQuery::and)
+ return CqlQuery.exactMatchAny(filters)
+ .combine(statusQuery, CqlQuery::and)
.combine(requestLevelQuery, CqlQuery::and)
.map(q -> q.sortBy(ascending("position")))
- .after(query -> requestRepository.findBy(query,
- MAXIMUM_SUPPORTED_REQUEST_QUEUE_SIZE))
+ .after(q -> requestRepository.findBy(q, MAXIMUM_SUPPORTED_REQUEST_QUEUE_SIZE))
.thenApply(r -> r.map(MultipleRecords::getRecords))
.thenApply(r -> r.map(RequestQueue::new));
}
diff --git a/src/main/java/org/folio/circulation/resources/AllowedServicePointsResource.java b/src/main/java/org/folio/circulation/resources/AllowedServicePointsResource.java
index 97fee14762..1e96d2b916 100644
--- a/src/main/java/org/folio/circulation/resources/AllowedServicePointsResource.java
+++ b/src/main/java/org/folio/circulation/resources/AllowedServicePointsResource.java
@@ -54,7 +54,8 @@ private void get(RoutingContext routingContext) {
ofAsync(routingContext)
.thenApply(r -> r.next(AllowedServicePointsResource::buildRequest))
- .thenCompose(r -> r.after(new AllowedServicePointsService(clients)::getAllowedServicePoints))
+ .thenCompose(r -> r.after(request -> new AllowedServicePointsService(
+ clients, request.isEcsRequestRouting()).getAllowedServicePoints(request)))
.thenApply(r -> r.map(AllowedServicePointsResource::toJson))
.thenApply(r -> r.map(JsonHttpResponse::ok))
.exceptionally(CommonFailures::failedDueToServerError)
@@ -68,62 +69,58 @@ private static Result buildRequest(RoutingContext r
.map(String::toUpperCase)
.map(Request.Operation::valueOf)
.orElse(null);
-
- AllowedServicePointsRequest request = new AllowedServicePointsRequest(operation,
- queryParams.get("requesterId"), queryParams.get("instanceId"), queryParams.get("itemId"),
- queryParams.get("requestId"));
-
- return validateAllowedServicePointsRequest(request);
- }
-
- private static Result validateAllowedServicePointsRequest(
- AllowedServicePointsRequest allowedServicePointsRequest) {
-
- log.debug("validateAllowedServicePointsRequest:: parameters allowedServicePointsRequest: {}",
- allowedServicePointsRequest);
-
- Request.Operation operation = allowedServicePointsRequest.getOperation();
- String requesterId = allowedServicePointsRequest.getRequesterId();
- String instanceId = allowedServicePointsRequest.getInstanceId();
- String itemId = allowedServicePointsRequest.getItemId();
- String requestId = allowedServicePointsRequest.getRequestId();
+ String requesterId = queryParams.get("requesterId");
+ String instanceId = queryParams.get("instanceId");
+ String itemId = queryParams.get("itemId");
+ String requestId = queryParams.get("requestId");
+ String useStubItem = queryParams.get("useStubItem");
+ String ecsRequestRouting = queryParams.get("ecsRequestRouting");
+ String patronGroupId = queryParams.get("patronGroupId");
List errors = new ArrayList<>();
// Checking UUID validity
if (requesterId != null && !isUuid(requesterId)) {
- log.warn("Requester ID is not a valid UUID: {}", requesterId);
+ log.warn("buildRequest:: Requester ID is not a valid UUID: {}",
+ requesterId);
errors.add(String.format("Requester ID is not a valid UUID: %s.", requesterId));
}
+ if (patronGroupId != null && !isUuid(patronGroupId)) {
+ log.warn("buildRequest:: Patron Group ID is not a valid UUID: {}", patronGroupId);
+ errors.add(String.format("Patron Group ID is not a valid UUID: %s.",
+ patronGroupId));
+ }
+
if (instanceId != null && !isUuid(instanceId)) {
- log.warn("Instance ID is not a valid UUID: {}", requesterId);
+ log.warn("buildRequest:: Instance ID is not a valid UUID: {}", instanceId);
errors.add(String.format("Instance ID is not a valid UUID: %s.", instanceId));
}
if (itemId != null && !isUuid(itemId)) {
- log.warn("Item ID is not a valid UUID: {}", itemId);
+ log.warn("buildRequest:: Item ID is not a valid UUID: {}", itemId);
errors.add(String.format("Item ID is not a valid UUID: %s.", itemId));
}
if (requestId != null && !isUuid(requestId)) {
- log.warn("Request ID is not a valid UUID: {}", requestId);
+ log.warn("buildRequest:: Request ID is not a valid UUID: {}", requestId);
errors.add(String.format("Request ID is not a valid UUID: %s.", requestId));
}
-
+ validateBoolean(useStubItem, "useStubItem", errors);
+ validateBoolean(ecsRequestRouting, "ecsRequestRouting", errors);
// Checking parameter combinations
boolean allowedCombinationOfParametersDetected = false;
- if (operation == Request.Operation.CREATE && requesterId != null && instanceId != null &&
+ if (operation == Request.Operation.CREATE && (requesterId != null || patronGroupId != null) && instanceId != null &&
itemId == null && requestId == null) {
log.info("validateAllowedServicePointsRequest:: TLR request creation case");
allowedCombinationOfParametersDetected = true;
}
- if (operation == Request.Operation.CREATE && requesterId != null && instanceId == null &&
+ if (operation == Request.Operation.CREATE && (requesterId != null || patronGroupId != null) && instanceId == null &&
itemId != null && requestId == null) {
log.info("validateAllowedServicePointsRequest:: ILR request creation case");
@@ -155,7 +152,17 @@ private static Result validateAllowedServicePointsR
return failed(new BadRequestFailure(errorMessage));
}
- return succeeded(allowedServicePointsRequest);
+ return succeeded(new AllowedServicePointsRequest(operation, requesterId,
+ patronGroupId, instanceId, itemId, requestId, Boolean.parseBoolean(useStubItem),
+ Boolean.parseBoolean(ecsRequestRouting)));
+ }
+
+ private static void validateBoolean(String parameter, String parameterName, List errors) {
+ if (parameter != null && !"true".equals(parameter) && !"false".equals(parameter)) {
+ log.warn("validateBoolean:: {} is not a valid boolean: {}",
+ parameterName, parameter);
+ errors.add(String.format("%s is not a valid boolean: %s.", parameterName, parameter));
+ }
}
private static JsonObject toJson(Map> allowedServicePoints) {
diff --git a/src/main/java/org/folio/circulation/resources/ChangeDueDateResource.java b/src/main/java/org/folio/circulation/resources/ChangeDueDateResource.java
index ec49593d85..0e43fcbc24 100644
--- a/src/main/java/org/folio/circulation/resources/ChangeDueDateResource.java
+++ b/src/main/java/org/folio/circulation/resources/ChangeDueDateResource.java
@@ -3,7 +3,9 @@
import static java.util.concurrent.CompletableFuture.completedFuture;
import static org.folio.circulation.domain.representations.ChangeDueDateRequest.DUE_DATE;
import static org.folio.circulation.domain.representations.LoanProperties.ITEM_ID;
-import static org.folio.circulation.resources.handlers.error.CirculationErrorType.*;
+import static org.folio.circulation.resources.handlers.error.CirculationErrorType.FAILED_TO_FETCH_USER;
+import static org.folio.circulation.resources.handlers.error.CirculationErrorType.FAILED_TO_FIND_SINGLE_OPEN_LOAN;
+import static org.folio.circulation.resources.handlers.error.CirculationErrorType.ITEM_DOES_NOT_EXIST;
import static org.folio.circulation.support.ValidationErrorFailure.singleValidationError;
import static org.folio.circulation.support.json.JsonPropertyFetcher.getDateTimeProperty;
import static org.folio.circulation.support.results.MappingFunctions.toFixedValue;
@@ -26,7 +28,7 @@
import org.folio.circulation.domain.representations.ChangeDueDateRequest;
import org.folio.circulation.domain.validation.ItemStatusValidator;
import org.folio.circulation.domain.validation.LoanValidator;
-import org.folio.circulation.infrastructure.storage.ConfigurationRepository;
+import org.folio.circulation.infrastructure.storage.SettingsRepository;
import org.folio.circulation.infrastructure.storage.inventory.ItemRepository;
import org.folio.circulation.infrastructure.storage.loans.LoanRepository;
import org.folio.circulation.infrastructure.storage.loans.OverdueFinePolicyRepository;
@@ -80,6 +82,8 @@ private CompletableFuture> processChangeDueDate(
final var itemRepository = new ItemRepository(clients);
final var userRepository = new UserRepository(clients);
final var loanRepository = new LoanRepository(clients, itemRepository, userRepository);
+ final var settingsRepository = new SettingsRepository(clients);
+
final WebContext webContext = new WebContext(routingContext);
final OkapiPermissions okapiPermissions = OkapiPermissions.from(webContext.getHeaders());
@@ -103,13 +107,12 @@ private CompletableFuture> processChangeDueDate(
final LoanNoticeSender loanNoticeSender = LoanNoticeSender.using(clients, loanRepository);
- final ConfigurationRepository configurationRepository = new ConfigurationRepository(clients);
log.info("starting change due date process for loan {}", request.getLoanId());
return succeeded(request)
.after(r -> getExistingLoan(loanRepository, r))
.thenApply(LoanValidator::refuseWhenLoanIsClosed)
.thenApply(this::toLoanAndRelatedRecords)
- .thenComposeAsync(r -> r.combineAfter(configurationRepository::lookupTlrSettings,
+ .thenComposeAsync(r -> r.combineAfter(settingsRepository::lookupTlrSettings,
LoanAndRelatedRecords::withTlrSettings))
.thenComposeAsync(r -> r.after(requestQueueRepository::get))
.thenApply(itemStatusValidator::refuseWhenItemStatusDoesNotAllowDueDateChange)
diff --git a/src/main/java/org/folio/circulation/resources/CheckInByBarcodeResource.java b/src/main/java/org/folio/circulation/resources/CheckInByBarcodeResource.java
index be2089da03..192485e7d2 100644
--- a/src/main/java/org/folio/circulation/resources/CheckInByBarcodeResource.java
+++ b/src/main/java/org/folio/circulation/resources/CheckInByBarcodeResource.java
@@ -16,6 +16,7 @@
import org.folio.circulation.domain.representations.CheckInByBarcodeResponse;
import org.folio.circulation.domain.validation.CheckInValidators;
import org.folio.circulation.infrastructure.storage.ConfigurationRepository;
+import org.folio.circulation.infrastructure.storage.SettingsRepository;
import org.folio.circulation.infrastructure.storage.inventory.ItemRepository;
import org.folio.circulation.infrastructure.storage.loans.LoanRepository;
import org.folio.circulation.infrastructure.storage.requests.RequestQueueRepository;
@@ -78,6 +79,7 @@ private void checkIn(RoutingContext routingContext) {
final RequestNoticeSender requestNoticeSender = RequestNoticeSender.using(clients);
final ConfigurationRepository configurationRepository = new ConfigurationRepository(clients);
+ final SettingsRepository settingsRepository = new SettingsRepository(clients);
refuseWhenLoggedInUserNotPresent(context)
.next(notUsed -> checkInRequestResult)
@@ -87,7 +89,7 @@ private void checkIn(RoutingContext routingContext) {
.withItemStatusBeforeCheckIn(item.getStatus()))
.thenApply(checkInValidators::refuseWhenItemIsNotAllowedForCheckIn)
.thenApply(checkInValidators::refuseWhenClaimedReturnedIsNotResolved)
- .thenComposeAsync(r -> r.combineAfter(configurationRepository::lookupTlrSettings,
+ .thenComposeAsync(r -> r.combineAfter(settingsRepository::lookupTlrSettings,
CheckInContext::withTlrSettings))
.thenComposeAsync(r -> r.combineAfter(configurationRepository::findTimeZoneConfiguration,
CheckInContext::withTimeZone))
diff --git a/src/main/java/org/folio/circulation/resources/CheckInProcessAdapter.java b/src/main/java/org/folio/circulation/resources/CheckInProcessAdapter.java
index 5179afe894..d9ec1c876d 100644
--- a/src/main/java/org/folio/circulation/resources/CheckInProcessAdapter.java
+++ b/src/main/java/org/folio/circulation/resources/CheckInProcessAdapter.java
@@ -179,7 +179,8 @@ CompletableFuture> getRequestQueue(CheckInContext context)
return requestQueueRepository.getByItemId(context.getItem().getItemId());
}
else {
- return requestQueueRepository.getByInstanceId(context.getItem().getInstanceId());
+ return requestQueueRepository.getByInstanceIdAndItemId(context.getItem().getInstanceId(),
+ context.getItem().getItemId());
}
}
diff --git a/src/main/java/org/folio/circulation/resources/CheckOutByBarcodeResource.java b/src/main/java/org/folio/circulation/resources/CheckOutByBarcodeResource.java
index a4f804b70b..57e144f47f 100644
--- a/src/main/java/org/folio/circulation/resources/CheckOutByBarcodeResource.java
+++ b/src/main/java/org/folio/circulation/resources/CheckOutByBarcodeResource.java
@@ -22,6 +22,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.circulation.domain.CheckOutLock;
+import org.folio.circulation.domain.Item;
import org.folio.circulation.domain.Loan;
import org.folio.circulation.domain.LoanAndRelatedRecords;
import org.folio.circulation.domain.LoanRepresentation;
@@ -154,9 +155,10 @@ private void checkOut(RoutingContext routingContext) {
.thenApply(validators::refuseWhenItemIsAlreadyCheckedOut)
.thenApply(validators::refuseWhenItemIsNotAllowedForCheckOut)
.thenComposeAsync(validators::refuseWhenItemHasOpenLoans)
- .thenComposeAsync(r -> r.combineAfter(configurationRepository::lookupTlrSettings,
+ .thenComposeAsync(r -> r.combineAfter(settingsRepository::lookupTlrSettings,
LoanAndRelatedRecords::withTlrSettings))
- .thenComposeAsync(r -> r.after(requestQueueRepository::get))
+ .thenComposeAsync(r -> r.combineAfter(l -> getRequestQueue(l, requestQueueRepository),
+ LoanAndRelatedRecords::withRequestQueue))
.thenCompose(validators::refuseWhenRequestedByAnotherPatron)
.thenComposeAsync(r -> r.after(l -> lookupLoanPolicy(l, loanPolicyRepository, errorHandler)))
.thenComposeAsync(validators::refuseWhenItemLimitIsReached)
@@ -390,4 +392,14 @@ private Result calculateDefaultInitialDueDate(
.map(loan::changeDueDate)
.map(loanAndRelatedRecords::withLoan);
}
+
+ private CompletableFuture> getRequestQueue(
+ LoanAndRelatedRecords loanAndRelatedRecords, RequestQueueRepository requestQueueRepository) {
+
+ Item item = loanAndRelatedRecords.getItem();
+
+ return loanAndRelatedRecords.getTlrSettings().isTitleLevelRequestsFeatureEnabled()
+ ? requestQueueRepository.getByInstanceIdAndItemId(item.getInstanceId(), item.getItemId())
+ : requestQueueRepository.getByItemId(item.getItemId());
+ }
}
diff --git a/src/main/java/org/folio/circulation/resources/ItemsByInstanceResource.java b/src/main/java/org/folio/circulation/resources/ItemsByInstanceResource.java
new file mode 100644
index 0000000000..381808e951
--- /dev/null
+++ b/src/main/java/org/folio/circulation/resources/ItemsByInstanceResource.java
@@ -0,0 +1,46 @@
+package org.folio.circulation.resources;
+
+import java.lang.invoke.MethodHandles;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.folio.circulation.domain.SearchInstance;
+import org.folio.circulation.infrastructure.storage.SearchRepository;
+import org.folio.circulation.support.http.server.JsonHttpResponse;
+import org.folio.circulation.support.http.server.WebContext;
+
+import io.vertx.core.http.HttpClient;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.RoutingContext;
+
+public class ItemsByInstanceResource extends Resource {
+
+ private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
+
+ public ItemsByInstanceResource(HttpClient client) {
+ super(client);
+ }
+
+ @Override
+ public void register(Router router) {
+ router.get("/circulation/items-by-instance")
+ .handler(this::getInstanceItems);
+ }
+
+ private void getInstanceItems(RoutingContext routingContext) {
+ final WebContext context = new WebContext(routingContext);
+ new SearchRepository(context, client).getInstanceWithItems(routingContext.queryParam("query"))
+ .thenApply(r -> r.map(this::toJson))
+ .thenApply(r -> r.map(JsonHttpResponse::ok))
+ .thenAccept(context::writeResultToHttpResponse);
+ }
+
+ private JsonObject toJson(SearchInstance searchInstance) {
+ log.debug("toJson:: searchInstance: {}", () -> searchInstance);
+ if (searchInstance != null) {
+ return searchInstance.toJson();
+ }
+ return new JsonObject();
+ }
+}
diff --git a/src/main/java/org/folio/circulation/resources/RequestByInstanceIdResource.java b/src/main/java/org/folio/circulation/resources/RequestByInstanceIdResource.java
index 67c8409e99..36d9ba7ab8 100644
--- a/src/main/java/org/folio/circulation/resources/RequestByInstanceIdResource.java
+++ b/src/main/java/org/folio/circulation/resources/RequestByInstanceIdResource.java
@@ -63,7 +63,7 @@
import org.folio.circulation.domain.validation.ProxyRelationshipValidator;
import org.folio.circulation.domain.validation.RequestLoanValidator;
import org.folio.circulation.domain.validation.ServicePointPickupLocationValidator;
-import org.folio.circulation.infrastructure.storage.ConfigurationRepository;
+import org.folio.circulation.infrastructure.storage.SettingsRepository;
import org.folio.circulation.infrastructure.storage.loans.LoanRepository;
import org.folio.circulation.infrastructure.storage.requests.RequestQueueRepository;
import org.folio.circulation.resources.handlers.error.FailFastErrorHandler;
@@ -121,7 +121,7 @@ private void createInstanceLevelRequests(RoutingContext routingContext) {
final var requestBody = routingContext.getBodyAsJson();
- new ConfigurationRepository(clients).lookupTlrSettings()
+ new SettingsRepository(clients).lookupTlrSettings()
.thenCompose(r -> r.after(config -> buildAndPlaceRequests(clients, eventPublisher,
repositories, itemFinder, config, requestBody)))
.thenApply(r -> r.map(RequestAndRelatedRecords::getRequest))
diff --git a/src/main/java/org/folio/circulation/resources/RequestCollectionResource.java b/src/main/java/org/folio/circulation/resources/RequestCollectionResource.java
index aab1681c96..a222818735 100644
--- a/src/main/java/org/folio/circulation/resources/RequestCollectionResource.java
+++ b/src/main/java/org/folio/circulation/resources/RequestCollectionResource.java
@@ -8,6 +8,10 @@
import static org.folio.circulation.support.results.MappingFunctions.toFixedValue;
import static org.folio.circulation.support.results.MappingFunctions.when;
+import java.lang.invoke.MethodHandles;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.folio.circulation.domain.CreateRequestService;
import org.folio.circulation.domain.MoveRequestProcessAdapter;
import org.folio.circulation.domain.MoveRequestService;
@@ -30,6 +34,7 @@
import org.folio.circulation.infrastructure.storage.CalendarRepository;
import org.folio.circulation.infrastructure.storage.ConfigurationRepository;
import org.folio.circulation.infrastructure.storage.ServicePointRepository;
+import org.folio.circulation.infrastructure.storage.SettingsRepository;
import org.folio.circulation.infrastructure.storage.inventory.ItemRepository;
import org.folio.circulation.infrastructure.storage.loans.LoanPolicyRepository;
import org.folio.circulation.infrastructure.storage.loans.LoanRepository;
@@ -56,6 +61,8 @@
import io.vertx.ext.web.RoutingContext;
public class RequestCollectionResource extends CollectionResource {
+ private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
+
public RequestCollectionResource(HttpClient client) {
super(client, "/circulation/requests");
}
@@ -73,6 +80,8 @@ void create(RoutingContext routingContext) {
final var representation = routingContext.getBodyAsJson();
+ log.debug("create:: {}", representation);
+
final var eventPublisher = new EventPublisher(routingContext);
RequestRelatedRepositories repositories = new RequestRelatedRepositories(clients);
@@ -272,6 +281,7 @@ void move(RoutingContext routingContext) {
final var loanPolicyRepository = new LoanPolicyRepository(clients);
final var requestPolicyRepository = new RequestPolicyRepository(clients);
final var configurationRepository = new ConfigurationRepository(clients);
+ final var settingsRepository = new SettingsRepository(clients);
final var updateUponRequest = new UpdateUponRequest(new UpdateItem(itemRepository,
new RequestQueueService(requestPolicyRepository, loanPolicyRepository)),
@@ -287,7 +297,7 @@ void move(RoutingContext routingContext) {
requestRepository, requestPolicyRepository,
updateUponRequest, moveRequestProcessAdapter, new RequestLoanValidator(new ItemByInstanceIdFinder(clients.holdingsStorage(), itemRepository), loanRepository),
RequestNoticeSender.using(clients), configurationRepository, eventPublisher,
- requestQueueRepository);
+ requestQueueRepository, settingsRepository);
fromFutureResult(requestRepository.getById(id))
.map(request -> request.withOperation(Request.Operation.MOVE))
diff --git a/src/main/java/org/folio/circulation/resources/RequestFromRepresentationService.java b/src/main/java/org/folio/circulation/resources/RequestFromRepresentationService.java
index a11b86d231..9860f9b454 100644
--- a/src/main/java/org/folio/circulation/resources/RequestFromRepresentationService.java
+++ b/src/main/java/org/folio/circulation/resources/RequestFromRepresentationService.java
@@ -51,6 +51,7 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.folio.circulation.domain.EcsRequestPhase;
import org.folio.circulation.domain.Item;
import org.folio.circulation.domain.Loan;
import org.folio.circulation.domain.MultipleRecords;
@@ -66,6 +67,7 @@
import org.folio.circulation.domain.validation.ServicePointPickupLocationValidator;
import org.folio.circulation.infrastructure.storage.ConfigurationRepository;
import org.folio.circulation.infrastructure.storage.ServicePointRepository;
+import org.folio.circulation.infrastructure.storage.SettingsRepository;
import org.folio.circulation.infrastructure.storage.inventory.HoldingsRepository;
import org.folio.circulation.infrastructure.storage.inventory.InstanceRepository;
import org.folio.circulation.infrastructure.storage.inventory.ItemRepository;
@@ -95,6 +97,7 @@ class RequestFromRepresentationService {
private final LoanRepository loanRepository;
private final ServicePointRepository servicePointRepository;
private final ConfigurationRepository configurationRepository;
+ private final SettingsRepository settingsRepository;
private final RequestPolicyRepository requestPolicyRepository;
private final ProxyRelationshipValidator proxyRelationshipValidator;
private final ServicePointPickupLocationValidator pickupLocationValidator;
@@ -118,6 +121,7 @@ public RequestFromRepresentationService(Request.Operation operation,
this.loanRepository = repositories.getLoanRepository();
this.servicePointRepository = repositories.getServicePointRepository();
this.configurationRepository = repositories.getConfigurationRepository();
+ this.settingsRepository = repositories.getSettingsRepository();
this.requestPolicyRepository = repositories.getRequestPolicyRepository();
this.proxyRelationshipValidator = proxyRelationshipValidator;
@@ -129,7 +133,7 @@ public RequestFromRepresentationService(Request.Operation operation,
CompletableFuture> getRequestFrom(JsonObject representation) {
- return configurationRepository.lookupTlrSettings()
+ return settingsRepository.lookupTlrSettings()
.thenCompose(r -> r.after(tlrSettings -> initRequest(operation, tlrSettings, representation)))
.thenApply(r -> r.next(this::validateStatus))
.thenApply(r -> r.next(this::validateRequestLevel))
@@ -249,7 +253,12 @@ private CompletableFuture> fetchItemAndLoan(
Request request = records.getRequest();
Function>>
itemAndLoanFetchingFunction;
- if (request.isTitleLevel() && request.isPage()) {
+ log.info("fetchItemAndLoan:: Request phase is {}", request.getEcsRequestPhase().value);
+ if (request.getEcsRequestPhase() == EcsRequestPhase.PRIMARY) {
+ log.info("fetchItemAndLoan:: Primary ECS request detected, using default item fetcher");
+ itemAndLoanFetchingFunction = this::fetchItemAndLoanDefault;
+ }
+ else if (request.isTitleLevel() && request.isPage()) {
itemAndLoanFetchingFunction = this::fetchItemAndLoanForPageTlr;
}
else if (request.isTitleLevel() && request.isRecall()) {
@@ -534,6 +543,11 @@ private Result refuseToCreateTlrLinkedToAnItem(Result request)
}
private Result validateAbsenceOfItemLinkInTlr(Request request) {
+ if (request.getEcsRequestPhase() == EcsRequestPhase.PRIMARY) {
+ log.info("validateAbsenceOfItemLinkInTlr:: Primary ECS request detected, skipping");
+ return of(() -> request);
+ }
+
String itemId = request.getItemId();
String holdingsRecordId = request.getHoldingsRecordId();
diff --git a/src/main/java/org/folio/circulation/resources/RequestQueueResource.java b/src/main/java/org/folio/circulation/resources/RequestQueueResource.java
index 3696483058..059eb284c5 100644
--- a/src/main/java/org/folio/circulation/resources/RequestQueueResource.java
+++ b/src/main/java/org/folio/circulation/resources/RequestQueueResource.java
@@ -27,6 +27,7 @@
import org.folio.circulation.infrastructure.storage.CalendarRepository;
import org.folio.circulation.infrastructure.storage.ConfigurationRepository;
import org.folio.circulation.infrastructure.storage.ServicePointRepository;
+import org.folio.circulation.infrastructure.storage.SettingsRepository;
import org.folio.circulation.infrastructure.storage.inventory.ItemRepository;
import org.folio.circulation.infrastructure.storage.loans.LoanRepository;
import org.folio.circulation.infrastructure.storage.requests.RequestQueueRepository;
@@ -97,10 +98,7 @@ private void getQueue(RoutingContext routingContext, RequestQueueType requestQue
final RequestRepresentation requestRepresentation = new RequestRepresentation();
- CompletableFuture> requestQueue = getRequestQueueByType(routingContext,
- requestQueueType, requestQueueRepository);
-
- requestQueue
+ getRequestQueueByType(routingContext, requestQueueType, requestQueueRepository)
.thenApply(r -> r.map(queue -> new MultipleRecords<>(queue.getRequests(), queue.size())))
.thenApply(r -> r.map(requests ->
requests.asJson(requestRepresentation::extendedRepresentation, "requests")))
@@ -125,15 +123,14 @@ private void reorderQueue(RoutingContext routingContext, RequestQueueType reques
final var requestRepository = RequestRepository.using(clients,
itemRepository, userRepository, loanRepository);
final var configurationRepository = new ConfigurationRepository(clients);
+ final var settingsRepository = new SettingsRepository(clients);
final var requestQueueRepository = new RequestQueueRepository(requestRepository);
final UpdateRequestQueue updateRequestQueue = new UpdateRequestQueue(
requestQueueRepository, requestRepository, new ServicePointRepository(clients),
configurationRepository, RequestQueueService.using(clients), new CalendarRepository(clients));
- getRequestQueueByType(routingContext, requestQueueType, requestQueueRepository);
-
- validateTlrFeatureStatus(configurationRepository, requestQueueType, idParamValue)
+ validateTlrFeatureStatus(settingsRepository, requestQueueType, idParamValue)
.thenCompose(r -> r.after(tlrSettings ->
getRequestQueueByType(routingContext, requestQueueType, requestQueueRepository)))
.thenApply(r -> r.map(reorderContext::withRequestQueue))
@@ -152,10 +149,10 @@ requestQueueRepository, requestRepository, new ServicePointRepository(clients),
}
private CompletableFuture> validateTlrFeatureStatus(
- ConfigurationRepository configurationRepository, RequestQueueType requestQueueType,
+ SettingsRepository settingsRepository, RequestQueueType requestQueueType,
String idParamValue) {
- return configurationRepository.lookupTlrSettings()
+ return settingsRepository.lookupTlrSettings()
.thenApply(r -> r.failWhen(
tlrSettings -> succeeded(
requestQueueType == FOR_INSTANCE ^ tlrSettings.isTitleLevelRequestsFeatureEnabled()),
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 4d60a026aa..e7e278c757 100644
--- a/src/main/java/org/folio/circulation/resources/renewal/RenewalResource.java
+++ b/src/main/java/org/folio/circulation/resources/renewal/RenewalResource.java
@@ -70,6 +70,7 @@
import org.folio.circulation.infrastructure.storage.AutomatedPatronBlocksRepository;
import org.folio.circulation.infrastructure.storage.CalendarRepository;
import org.folio.circulation.infrastructure.storage.ConfigurationRepository;
+import org.folio.circulation.infrastructure.storage.SettingsRepository;
import org.folio.circulation.infrastructure.storage.feesandfines.FeeFineOwnerRepository;
import org.folio.circulation.infrastructure.storage.feesandfines.FeeFineRepository;
import org.folio.circulation.infrastructure.storage.inventory.ItemRepository;
@@ -150,6 +151,7 @@ private void renew(RoutingContext routingContext) {
final LoanRepresentation loanRepresentation = new LoanRepresentation();
final ConfigurationRepository configurationRepository = new ConfigurationRepository(clients);
+ final SettingsRepository settingsRepository = new SettingsRepository(clients);
final LoanScheduledNoticeService scheduledNoticeService = LoanScheduledNoticeService.using(clients);
final ReminderFeeScheduledNoticeService scheduledRemindersService = new ReminderFeeScheduledNoticeService(clients);
@@ -187,7 +189,7 @@ private void renew(RoutingContext routingContext) {
.thenCompose(r -> r.after(ctx -> lookupOverdueFinePolicy(ctx, overdueFinePolicyRepository, errorHandler)))
.thenComposeAsync(r -> r.after(ctx -> blockRenewalOfItemsWithReminderFees(ctx, errorHandler)))
.thenCompose(r -> r.after(ctx -> lookupLoanPolicy(ctx, loanPolicyRepository, errorHandler)))
- .thenCompose(r -> r.combineAfter(configurationRepository::lookupTlrSettings,
+ .thenCompose(r -> r.combineAfter(settingsRepository::lookupTlrSettings,
RenewalContext::withTlrSettings))
.thenComposeAsync(r -> r.after(
ctx -> lookupRequestQueue(ctx, requestQueueRepository, errorHandler)))
diff --git a/src/main/java/org/folio/circulation/rules/CirculationRuleCriteria.java b/src/main/java/org/folio/circulation/rules/CirculationRuleCriteria.java
index 3052577f4f..29f1d903d5 100644
--- a/src/main/java/org/folio/circulation/rules/CirculationRuleCriteria.java
+++ b/src/main/java/org/folio/circulation/rules/CirculationRuleCriteria.java
@@ -1,7 +1,6 @@
package org.folio.circulation.rules;
import org.folio.circulation.domain.Item;
-import org.folio.circulation.domain.User;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -20,11 +19,11 @@ public class CirculationRuleCriteria {
@EqualsAndHashCode.Exclude
private Item item;
- public CirculationRuleCriteria(@NonNull Item item, @NonNull User user) {
+ public CirculationRuleCriteria(@NonNull Item item, @NonNull String patronGroupId) {
this.materialTypeId = item.getMaterialTypeId();
this.loanTypeId = item.getLoanTypeId();
this.locationId = item.getEffectiveLocationId();
- this.patronGroupId = user.getPatronGroupId();
+ this.patronGroupId = patronGroupId;
this.item = item;
}
}
diff --git a/src/main/java/org/folio/circulation/services/AllowedServicePointsService.java b/src/main/java/org/folio/circulation/services/AllowedServicePointsService.java
index 45fff76324..ef86ae8b28 100644
--- a/src/main/java/org/folio/circulation/services/AllowedServicePointsService.java
+++ b/src/main/java/org/folio/circulation/services/AllowedServicePointsService.java
@@ -20,6 +20,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -40,8 +41,8 @@
import org.folio.circulation.domain.User;
import org.folio.circulation.domain.configuration.TlrSettingsConfiguration;
import org.folio.circulation.domain.policy.RequestPolicy;
-import org.folio.circulation.infrastructure.storage.ConfigurationRepository;
import org.folio.circulation.infrastructure.storage.ServicePointRepository;
+import org.folio.circulation.infrastructure.storage.SettingsRepository;
import org.folio.circulation.infrastructure.storage.inventory.InstanceRepository;
import org.folio.circulation.infrastructure.storage.inventory.ItemRepository;
import org.folio.circulation.infrastructure.storage.requests.RequestPolicyRepository;
@@ -57,24 +58,28 @@
public class AllowedServicePointsService {
private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
+ private static final String ECS_REQUEST_ROUTING_INDEX_NAME = "ecsRequestRouting";
+ private static final String PICKUP_LOCATION_INDEX_NAME = "pickupLocation";
private final ItemRepository itemRepository;
private final UserRepository userRepository;
private final RequestRepository requestRepository;
private final RequestPolicyRepository requestPolicyRepository;
private final ServicePointRepository servicePointRepository;
private final ItemByInstanceIdFinder itemFinder;
- private final ConfigurationRepository configurationRepository;
+ private final SettingsRepository settingsRepository;
private final InstanceRepository instanceRepository;
+ private final String indexName;
- public AllowedServicePointsService(Clients clients) {
+ public AllowedServicePointsService(Clients clients, boolean isEcsRequestRouting) {
itemRepository = new ItemRepository(clients);
userRepository = new UserRepository(clients);
requestRepository = new RequestRepository(clients);
requestPolicyRepository = new RequestPolicyRepository(clients);
- servicePointRepository = new ServicePointRepository(clients);
- configurationRepository = new ConfigurationRepository(clients);
+ servicePointRepository = new ServicePointRepository(clients, isEcsRequestRouting);
+ settingsRepository = new SettingsRepository(clients);
instanceRepository = new InstanceRepository(clients);
itemFinder = new ItemByInstanceIdFinder(clients.holdingsStorage(), itemRepository);
+ indexName = isEcsRequestRouting ? ECS_REQUEST_ROUTING_INDEX_NAME : PICKUP_LOCATION_INDEX_NAME;
}
public CompletableFuture>>>
@@ -85,8 +90,9 @@ public AllowedServicePointsService(Clients clients) {
return ofAsync(request)
.thenCompose(r -> r.after(this::fetchInstance))
.thenCompose(r -> r.after(this::fetchRequest))
- .thenCompose(r -> r.after(this::fetchUser))
- .thenCompose(r -> r.after(user -> getAllowedServicePoints(request, user)));
+ .thenCompose(r -> r.after(this::getPatronGroupId))
+ .thenCompose(r -> r.after(patronGroupId -> getAllowedServicePoints(request,
+ patronGroupId)));
}
private CompletableFuture> fetchInstance(
@@ -123,38 +129,51 @@ private CompletableFuture> fetchRequest(
.thenApply(r -> r.map(allowedServicePointsRequest::updateWithRequestInformation));
}
- private CompletableFuture> fetchUser(AllowedServicePointsRequest request) {
+ private CompletableFuture> getPatronGroupId(AllowedServicePointsRequest request) {
+
+ if (request.getPatronGroupId() != null) {
+ return ofAsync(request.getPatronGroupId());
+ }
+
final String userId = request.getRequesterId();
return userRepository.getUser(userId)
.thenApply(r -> r.failWhen(
user -> succeeded(user == null),
- user -> notFoundValidationFailure(userId, User.class)));
+ user -> notFoundValidationFailure(userId, User.class)))
+ .thenApply(result -> result.map(User::getPatronGroupId));
}
private CompletableFuture>>>
- getAllowedServicePoints(AllowedServicePointsRequest request, User user) {
+ getAllowedServicePoints(AllowedServicePointsRequest request, String patronGroupId) {
- log.debug("getAllowedServicePoints:: parameters request: {}, user: {}", request, user);
+ log.debug("getAllowedServicePoints:: parameters request: {}, patronGroupId: {}", request, patronGroupId);
return fetchItems(request)
- .thenCompose(r -> r.after(items -> getAllowedServicePoints(request, user, items)));
+ .thenCompose(r -> r.after(items -> getAllowedServicePoints(request, patronGroupId, items)));
}
private CompletableFuture>>>
- getAllowedServicePoints(AllowedServicePointsRequest request, User user, Collection
- items) {
-
- if (items.isEmpty() && request.isForTitleLevelRequest()) {
- log.info("getAllowedServicePoints:: requested instance has no items");
- return getAllowedServicePointsForTitleWithNoItems(request);
- }
+ getAllowedServicePoints(AllowedServicePointsRequest request, String patronGroupId,
+ Collection
- items) {
BiFunction, CompletableFuture>>>> mappingFunction = request.isImplyingItemStatusIgnore()
? this::extractAllowedServicePointsIgnoringItemStatus
: this::extractAllowedServicePointsConsideringItemStatus;
- return requestPolicyRepository.lookupRequestPolicies(items, user)
+ if (request.isUseStubItem()) {
+ return requestPolicyRepository.lookupRequestPolicy(patronGroupId)
+ .thenCompose(r -> r.after(policy -> extractAllowedServicePointsIgnoringItemStatus(
+ policy, new HashSet<>())));
+ }
+
+ if (items.isEmpty() && request.isForTitleLevelRequest()) {
+ log.info("getAllowedServicePoints:: requested instance has no items");
+ return getAllowedServicePointsForTitleWithNoItems(request);
+ }
+
+ return requestPolicyRepository.lookupRequestPolicies(items, patronGroupId)
.thenCompose(r -> r.after(policies -> allOf(policies, mappingFunction)))
.thenApply(r -> r.map(this::combineAllowedServicePoints));
// TODO: remove irrelevant request types for REPLACE
@@ -165,7 +184,7 @@ private CompletableFuture> fetchUser(AllowedServicePointsRequest re
if (request.isForTitleLevelRequest() && request.getOperation() == CREATE) {
log.info("getAllowedServicePointsForTitleWithNoItems:: checking TLR settings");
- return configurationRepository.lookupTlrSettings()
+ return settingsRepository.lookupTlrSettings()
.thenCompose(r -> r.after(this::considerTlrSettings));
}
@@ -353,7 +372,7 @@ private Map> combineAllowedServicePoints(
}
private CompletableFuture>> fetchAllowedServicePoints() {
- return servicePointRepository.fetchPickupLocationServicePoints()
+ return servicePointRepository.fetchServicePointsByIndexName(indexName)
.thenApply(r -> r.map(servicePoints -> servicePoints.stream()
.map(AllowedServicePoint::new)
.collect(Collectors.toSet())));
@@ -365,7 +384,7 @@ private CompletableFuture>> fetchPickupLocationS
log.debug("filterIdsByServicePointsAndPickupLocationExistence:: parameters ids: {}",
() -> collectionAsString(ids));
- return servicePointRepository.fetchPickupLocationServicePointsByIds(ids)
+ return servicePointRepository.fetchPickupLocationServicePointsByIdsAndIndexName(ids, indexName)
.thenApply(servicePointsResult -> servicePointsResult
.map(servicePoints -> servicePoints.stream()
.map(AllowedServicePoint::new)
diff --git a/src/main/java/org/folio/circulation/services/ItemForTlrService.java b/src/main/java/org/folio/circulation/services/ItemForTlrService.java
index 49e19e6ff9..f4d9b66d61 100644
--- a/src/main/java/org/folio/circulation/services/ItemForTlrService.java
+++ b/src/main/java/org/folio/circulation/services/ItemForTlrService.java
@@ -1,5 +1,6 @@
package org.folio.circulation.services;
+import static java.lang.String.format;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
@@ -18,6 +19,7 @@
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
+import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -42,6 +44,11 @@ public static ItemForTlrService using(RequestRelatedRepositories repositories) {
}
public List
- findAvailablePageableItems(Request request) {
+ log.info("findAvailablePageableItems:: instance items: {}",
+ () -> request.getInstanceItems().stream()
+ .map(item -> format("(%s, %s, %s)", item.getItemId(), item.getBarcode(), item.getStatus().getValue()))
+ .collect(Collectors.joining(", "))
+ );
return request.getInstanceItems()
.stream()
diff --git a/src/main/java/org/folio/circulation/services/RequestQueueService.java b/src/main/java/org/folio/circulation/services/RequestQueueService.java
index d110aee4fd..32d3a2b4b3 100644
--- a/src/main/java/org/folio/circulation/services/RequestQueueService.java
+++ b/src/main/java/org/folio/circulation/services/RequestQueueService.java
@@ -74,7 +74,8 @@ private CompletableFuture> isItemLevelRequestFulfillableByItem(I
protected CompletableFuture> isTitleLevelRequestFulfillableByItem(Item item,
Request request) {
- if (!StringUtils.equals(request.getInstanceId(), item.getInstanceId())) {
+ if (!StringUtils.equals(request.getItemId(), item.getItemId()) &&
+ !StringUtils.equals(request.getInstanceId(), item.getInstanceId())) {
return ofAsync(false);
}
diff --git a/src/main/java/org/folio/circulation/storage/ItemByInstanceIdFinder.java b/src/main/java/org/folio/circulation/storage/ItemByInstanceIdFinder.java
index 7cdeebf960..511e443125 100644
--- a/src/main/java/org/folio/circulation/storage/ItemByInstanceIdFinder.java
+++ b/src/main/java/org/folio/circulation/storage/ItemByInstanceIdFinder.java
@@ -7,23 +7,29 @@
import static org.folio.circulation.support.json.JsonKeys.byId;
import static org.folio.circulation.support.results.Result.succeeded;
+import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.folio.circulation.domain.Item;
import org.folio.circulation.domain.MultipleRecords;
import org.folio.circulation.infrastructure.storage.inventory.ItemRepository;
import org.folio.circulation.support.FindWithCqlQuery;
import org.folio.circulation.support.GetManyRecordsClient;
import org.folio.circulation.support.http.client.CqlQuery;
+import org.folio.circulation.support.http.client.PageLimit;
import org.folio.circulation.support.results.Result;
import io.vertx.core.json.JsonObject;
public class ItemByInstanceIdFinder {
+ private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
private final GetManyRecordsClient holdingsStorageClient;
private final ItemRepository itemRepository;
@@ -41,7 +47,8 @@ public CompletableFuture>> getItemsByInstanceId(UUID ins
final FindWithCqlQuery fetcher = findWithCqlQuery(
holdingsStorageClient, "holdingsRecords", identity());
- return fetcher.findByQuery(CqlQuery.exactMatch("instanceId", instanceId.toString()))
+ return fetcher.findByQuery(CqlQuery.exactMatch("instanceId", instanceId.toString()),
+ PageLimit.oneThousand())
.thenCompose(r -> getItems(r, failWhenNoHoldingsRecordsFound));
}
@@ -50,6 +57,10 @@ private CompletableFuture>> getItems(
boolean failWhenNoHoldingsRecordsFound) {
return holdingsRecordsResult.after(holdingsRecords -> {
+ log.info("getItems:: holdings records: {}", holdingsRecords.getRecords().stream()
+ .map(h -> h.getString("id"))
+ .collect(Collectors.joining(", ")));
+
if (holdingsRecords == null || holdingsRecords.isEmpty()) {
if (failWhenNoHoldingsRecordsFound) {
return completedFuture(failedValidation(
diff --git a/src/main/java/org/folio/circulation/support/Clients.java b/src/main/java/org/folio/circulation/support/Clients.java
index 99be88183c..90b4dcb4a3 100644
--- a/src/main/java/org/folio/circulation/support/Clients.java
+++ b/src/main/java/org/folio/circulation/support/Clients.java
@@ -4,7 +4,9 @@
import org.folio.circulation.rules.CirculationRulesProcessor;
import org.folio.circulation.services.PubSubPublishingService;
+import org.folio.circulation.support.http.client.IncludeRoutingServicePoints;
import org.folio.circulation.support.http.client.OkapiHttpClient;
+import org.folio.circulation.support.http.client.QueryParameter;
import org.folio.circulation.support.http.server.WebContext;
import io.vertx.core.http.HttpClient;
@@ -40,6 +42,7 @@ public class Clients {
private final CollectionResourceClient circulationRulesStorageClient;
private final CollectionResourceClient requestPoliciesStorageClient;
private final CollectionResourceClient servicePointsStorageClient;
+ private final CollectionResourceClient routingServicePointsStorageClient;
private final CollectionResourceClient calendarStorageClient;
private final CollectionResourceClient patronGroupsStorageClient;
private final CollectionResourceClient patronNoticePolicesStorageClient;
@@ -68,6 +71,7 @@ public class Clients {
private final CollectionResourceClient departmentClient;
private final CollectionResourceClient checkOutLockStorageClient;
private final CollectionResourceClient circulationItemClient;
+ private final CollectionResourceClient searchClient;
private final GetManyRecordsClient settingsStorageClient;
private final CollectionResourceClient circulationSettingsStorageClient;
private final CollectionResourceClient printEventsStorageClient;
@@ -77,6 +81,10 @@ public static Clients create(WebContext context, HttpClient httpClient) {
return new Clients(context.createHttpClient(httpClient), context);
}
+ public static Clients create(WebContext context, HttpClient httpClient, String tenantId) {
+ return new Clients(context.createHttpClient(httpClient, tenantId), context);
+ }
+
private Clients(OkapiHttpClient client, WebContext context) {
try {
requestsStorageClient = createRequestsStorageClient(client, context);
@@ -110,6 +118,8 @@ private Clients(OkapiHttpClient client, WebContext context) {
requestPoliciesStorageClient = createRequestPoliciesStorageClient(client, context);
fixedDueDateSchedulesStorageClient = createFixedDueDateSchedulesStorageClient(client, context);
servicePointsStorageClient = createServicePointsStorageClient(client, context);
+ routingServicePointsStorageClient = createServicePointsStorageWithCustomParam(client,
+ context, IncludeRoutingServicePoints.enabled());
patronGroupsStorageClient = createPatronGroupsStorageClient(client, context);
calendarStorageClient = createCalendarStorageClient(client, context);
patronNoticePolicesStorageClient = createPatronNoticePolicesStorageClient(client, context);
@@ -139,6 +149,7 @@ private Clients(OkapiHttpClient client, WebContext context) {
checkOutLockStorageClient = createCheckoutLockClient(client, context);
settingsStorageClient = createSettingsStorageClient(client, context);
circulationItemClient = createCirculationItemClient(client, context);
+ searchClient = createSearchClient(client, context);
circulationSettingsStorageClient = createCirculationSettingsStorageClient(client, context);
printEventsStorageClient = createPrintEventsStorageClient(client, context);
@@ -244,6 +255,10 @@ public CollectionResourceClient servicePointsStorage() {
return servicePointsStorageClient;
}
+ public CollectionResourceClient routingServicePointsStorage() {
+ return routingServicePointsStorageClient;
+ }
+
public CollectionResourceClient patronGroupsStorage() {
return patronGroupsStorageClient;
}
@@ -380,6 +395,10 @@ public CollectionResourceClient circulationItemClient() {
return circulationItemClient;
}
+ public CollectionResourceClient searchClient() {
+ return searchClient;
+ }
+
public CollectionResourceClient circulationSettingsStorageClient() {
return circulationSettingsStorageClient;
}
@@ -396,6 +415,14 @@ private static CollectionResourceClient getCollectionResourceClient(
return new CollectionResourceClient(client, context.getOkapiBasedUrl(path));
}
+ private static CollectionResourceClient getCollectionResourceClientWithCustomParam(
+ OkapiHttpClient client, WebContext context, String path, QueryParameter customParam)
+ throws MalformedURLException {
+
+ return new CustomParamCollectionResourceClient(client, context.getOkapiBasedUrl(path),
+ customParam);
+ }
+
public CollectionResourceClient noticeTemplatesClient() {
return noticeTemplatesClient;
}
@@ -638,6 +665,14 @@ private CollectionResourceClient createServicePointsStorageClient(
return getCollectionResourceClient(client, context, "/service-points");
}
+ private CollectionResourceClient createServicePointsStorageWithCustomParam(
+ OkapiHttpClient client, WebContext context, QueryParameter customParam)
+ throws MalformedURLException {
+
+ return getCollectionResourceClientWithCustomParam(client, context, "/service-points",
+ customParam);
+ }
+
private CollectionResourceClient createPatronGroupsStorageClient(
OkapiHttpClient client, WebContext context)
throws MalformedURLException {
@@ -815,6 +850,12 @@ private CollectionResourceClient createCirculationItemClient(
return getCollectionResourceClient(client, context, "/circulation-item");
}
+ private CollectionResourceClient createSearchClient(
+ OkapiHttpClient client, WebContext context) throws MalformedURLException {
+
+ return getCollectionResourceClient(client, context, "/search/instances");
+ }
+
private CollectionResourceClient createCirculationSettingsStorageClient(
OkapiHttpClient client, WebContext context) throws MalformedURLException {
diff --git a/src/main/java/org/folio/circulation/support/CollectionResourceClient.java b/src/main/java/org/folio/circulation/support/CollectionResourceClient.java
index a5c827108c..0b3db44c9b 100644
--- a/src/main/java/org/folio/circulation/support/CollectionResourceClient.java
+++ b/src/main/java/org/folio/circulation/support/CollectionResourceClient.java
@@ -18,8 +18,8 @@
import io.vertx.core.json.JsonObject;
public class CollectionResourceClient implements GetManyRecordsClient {
- private final OkapiHttpClient client;
- private final URL collectionRoot;
+ final OkapiHttpClient client;
+ final URL collectionRoot;
public CollectionResourceClient(OkapiHttpClient client, URL collectionRoot) {
this.collectionRoot = collectionRoot;
@@ -109,7 +109,7 @@ public CompletableFuture> getMany(CqlQuery cqlQuery,
return client.get(collectionRoot, cqlQuery, pageLimit, offset);
}
- private String individualRecordUrl(String id) {
+ String individualRecordUrl(String id) {
return format("%s/%s", collectionRoot, id);
}
}
diff --git a/src/main/java/org/folio/circulation/support/CustomParamCollectionResourceClient.java b/src/main/java/org/folio/circulation/support/CustomParamCollectionResourceClient.java
new file mode 100644
index 0000000000..2c765e3acf
--- /dev/null
+++ b/src/main/java/org/folio/circulation/support/CustomParamCollectionResourceClient.java
@@ -0,0 +1,46 @@
+package org.folio.circulation.support;
+
+import java.net.URL;
+import java.util.concurrent.CompletableFuture;
+
+import org.folio.circulation.support.http.client.CqlQuery;
+import org.folio.circulation.support.http.client.Offset;
+import org.folio.circulation.support.http.client.OkapiHttpClient;
+import org.folio.circulation.support.http.client.PageLimit;
+import org.folio.circulation.support.http.client.QueryParameter;
+import org.folio.circulation.support.http.client.Response;
+import org.folio.circulation.support.results.Result;
+
+public class CustomParamCollectionResourceClient extends CollectionResourceClient {
+
+ private QueryParameter customQueryParameter;
+
+ public CustomParamCollectionResourceClient(OkapiHttpClient client, URL collectionRoot,
+ QueryParameter customQueryParameter) {
+
+ super(client, collectionRoot);
+ this.customQueryParameter = customQueryParameter;
+ }
+
+ @Override
+ public CompletableFuture> get() {
+ return client.get(collectionRoot.toString(), customQueryParameter);
+ }
+
+ @Override
+ public CompletableFuture> get(PageLimit pageLimit) {
+ return client.get(collectionRoot, pageLimit, customQueryParameter);
+ }
+
+ @Override
+ public CompletableFuture> get(String id) {
+ return client.get(individualRecordUrl(id), customQueryParameter);
+ }
+
+ @Override
+ public CompletableFuture> getMany(CqlQuery cqlQuery,
+ PageLimit pageLimit, Offset offset) {
+
+ return client.get(collectionRoot, cqlQuery, pageLimit, offset, customQueryParameter);
+ }
+}
diff --git a/src/main/java/org/folio/circulation/support/http/client/CqlQuery.java b/src/main/java/org/folio/circulation/support/http/client/CqlQuery.java
index e8f87d6e0a..368d09e170 100644
--- a/src/main/java/org/folio/circulation/support/http/client/CqlQuery.java
+++ b/src/main/java/org/folio/circulation/support/http/client/CqlQuery.java
@@ -13,7 +13,9 @@
import java.net.URLEncoder;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.folio.circulation.support.CqlSortBy;
@@ -69,6 +71,20 @@ public static Result exactMatchAny(String indexName, Collection exactMatchAny(Map indicesToValues) {
+ String rawQuery = indicesToValues.entrySet()
+ .stream()
+ .filter(entry -> entry.getValue() != null)
+ .map(entry -> String.format("%s==\"%s\"", entry.getKey(), entry.getValue()))
+ .collect(Collectors.joining(" or "));
+
+ if (rawQuery.isEmpty()) {
+ return failedDueToServerError("Cannot generate empty CQL query");
+ }
+
+ return Result.of(() -> new CqlQuery("(" + rawQuery + ")", none()));
+ }
+
/**
* Uses greater than ('>'), as not equals operator ('<>') is not supported in CQL at present
*/
diff --git a/src/main/java/org/folio/circulation/support/http/client/IncludeRoutingServicePoints.java b/src/main/java/org/folio/circulation/support/http/client/IncludeRoutingServicePoints.java
new file mode 100644
index 0000000000..ff9d531cad
--- /dev/null
+++ b/src/main/java/org/folio/circulation/support/http/client/IncludeRoutingServicePoints.java
@@ -0,0 +1,33 @@
+package org.folio.circulation.support.http.client;
+
+import static java.lang.String.format;
+
+public class IncludeRoutingServicePoints implements QueryParameter {
+
+ private static final String PARAM_NAME = "includeRoutingServicePoints";
+ private final Boolean value;
+
+ public static IncludeRoutingServicePoints enabled() {
+ return new IncludeRoutingServicePoints(true);
+ }
+
+ private IncludeRoutingServicePoints(Boolean value) {
+ this.value = value;
+ }
+
+ @Override
+ public void consume(QueryStringParameterConsumer consumer) {
+ if (value != null) {
+ consumer.consume(PARAM_NAME, value.toString());
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (value == null) {
+ return format("No %s", PARAM_NAME);
+ }
+
+ return format("%s = \"%s\"", PARAM_NAME, value);
+ }
+}
diff --git a/src/main/java/org/folio/circulation/support/http/client/VertxWebClientOkapiHttpClient.java b/src/main/java/org/folio/circulation/support/http/client/VertxWebClientOkapiHttpClient.java
index e4815a30bf..a203f4386f 100644
--- a/src/main/java/org/folio/circulation/support/http/client/VertxWebClientOkapiHttpClient.java
+++ b/src/main/java/org/folio/circulation/support/http/client/VertxWebClientOkapiHttpClient.java
@@ -27,7 +27,9 @@
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;
import io.vertx.core.http.HttpMethod;
+import lombok.extern.log4j.Log4j2;
+@Log4j2
public class VertxWebClientOkapiHttpClient implements OkapiHttpClient {
private static final Duration DEFAULT_TIMEOUT = Duration.of(20, SECONDS);
private static final String ACCEPT = HttpHeaderNames.ACCEPT.toString();
@@ -184,6 +186,7 @@ public CompletableFuture> delete(String url,
}
private HttpRequest withStandardHeaders(HttpRequest request) {
+ log.debug("withStandardHeaders:: url={}, tenantId={}", request.uri(), tenantId);
return request
.putHeader(ACCEPT, "application/json, text/plain")
.putHeader(OKAPI_URL, okapiUrl.toString())
diff --git a/src/main/java/org/folio/circulation/support/http/server/WebContext.java b/src/main/java/org/folio/circulation/support/http/server/WebContext.java
index a38173b438..df6ec49778 100644
--- a/src/main/java/org/folio/circulation/support/http/server/WebContext.java
+++ b/src/main/java/org/folio/circulation/support/http/server/WebContext.java
@@ -76,6 +76,10 @@ public URL getOkapiBasedUrl(String path) throws MalformedURLException {
}
public OkapiHttpClient createHttpClient(HttpClient httpClient) {
+ return createHttpClient(httpClient, getTenantId());
+ }
+
+ public OkapiHttpClient createHttpClient(HttpClient httpClient, String tenantId) {
URL okapiUrl;
try {
@@ -86,7 +90,7 @@ public OkapiHttpClient createHttpClient(HttpClient httpClient) {
}
return VertxWebClientOkapiHttpClient.createClientUsing(httpClient,
- okapiUrl, getTenantId(), getOkapiToken(), getUserId(),
+ okapiUrl, tenantId, getOkapiToken(), getUserId(),
getRequestId());
}
diff --git a/src/main/java/org/folio/circulation/support/request/RequestRelatedRepositories.java b/src/main/java/org/folio/circulation/support/request/RequestRelatedRepositories.java
index a7360d40c4..72b4c86b3c 100644
--- a/src/main/java/org/folio/circulation/support/request/RequestRelatedRepositories.java
+++ b/src/main/java/org/folio/circulation/support/request/RequestRelatedRepositories.java
@@ -2,6 +2,7 @@
import org.folio.circulation.infrastructure.storage.ConfigurationRepository;
import org.folio.circulation.infrastructure.storage.ServicePointRepository;
+import org.folio.circulation.infrastructure.storage.SettingsRepository;
import org.folio.circulation.infrastructure.storage.inventory.HoldingsRepository;
import org.folio.circulation.infrastructure.storage.inventory.InstanceRepository;
import org.folio.circulation.infrastructure.storage.inventory.ItemRepository;
@@ -28,6 +29,7 @@ public class RequestRelatedRepositories {
private RequestQueueRepository requestQueueRepository;
private RequestPolicyRepository requestPolicyRepository;
private ConfigurationRepository configurationRepository;
+ private SettingsRepository settingsRepository;
private ServicePointRepository servicePointRepository;
private LocationRepository locationRepository;
@@ -43,6 +45,7 @@ public RequestRelatedRepositories(Clients clients) {
requestQueueRepository = new RequestQueueRepository(requestRepository);
requestPolicyRepository = new RequestPolicyRepository(clients);
configurationRepository = new ConfigurationRepository(clients);
+ settingsRepository = new SettingsRepository(clients);
servicePointRepository = new ServicePointRepository(clients);
locationRepository = LocationRepository.using(clients);
}
diff --git a/src/test/java/api/ItemsByInstanceResourceTest.java b/src/test/java/api/ItemsByInstanceResourceTest.java
new file mode 100644
index 0000000000..08d072d432
--- /dev/null
+++ b/src/test/java/api/ItemsByInstanceResourceTest.java
@@ -0,0 +1,96 @@
+package api;
+
+import static api.support.APITestContext.clearTempTenantId;
+import static api.support.APITestContext.setTempTenantId;
+import static api.support.http.InterfaceUrls.itemsByInstanceUrl;
+import static api.support.matchers.JsonObjectMatcher.hasJsonPath;
+import static org.folio.HttpStatus.HTTP_OK;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.iterableWithSize;
+import static org.hamcrest.core.Is.is;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.folio.circulation.support.http.client.Response;
+import org.junit.jupiter.api.Test;
+
+import api.support.APITests;
+import api.support.builders.SearchInstanceBuilder;
+import api.support.http.IndividualResource;
+import api.support.http.ResourceClient;
+import api.support.matchers.UUIDMatcher;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+
+class ItemsByInstanceResourceTest extends APITests {
+
+ private static final String TENANT_ID_COLLEGE = "college";
+ private static final String TENANT_ID_UNIVERSITY = "university";
+
+ @Test
+ void canGetInstanceById() {
+ IndividualResource instance = instancesFixture.basedUponDunkirk();
+ UUID instanceId = instance.getId();
+
+ // create item in tenant "college"
+ setTempTenantId(TENANT_ID_COLLEGE);
+ IndividualResource collegeLocation = locationsFixture.mainFloor();
+ IndividualResource collegeHoldings = holdingsFixture.defaultWithHoldings(instanceId);
+ IndividualResource collegeItem = itemsFixture.createItemWithHoldingsAndLocation(
+ collegeHoldings.getId(), collegeLocation.getId());
+ clearTempTenantId();
+
+ // create item in tenant "university"
+ setTempTenantId(TENANT_ID_UNIVERSITY);
+ IndividualResource universityLocation = locationsFixture.thirdFloor();
+ IndividualResource universityHoldings = holdingsFixture.defaultWithHoldings(instanceId);
+ IndividualResource universityItem = itemsFixture.createItemWithHoldingsAndLocation(
+ universityHoldings.getId(), universityLocation.getId());
+ clearTempTenantId();
+
+ // make sure neither item exists in current tenant
+ assertThat(itemsFixture.getById(collegeItem.getId()).getResponse().getStatusCode(), is(404));
+ assertThat(itemsFixture.getById(universityItem.getId()).getResponse().getStatusCode(), is(404));
+
+ List searchItems = List.of(
+ collegeItem.getJson().put("tenantId", TENANT_ID_COLLEGE),
+ universityItem.getJson().put("tenantId", TENANT_ID_UNIVERSITY));
+
+ JsonObject searchInstance = new SearchInstanceBuilder(instance.getJson())
+ .withItems(searchItems)
+ .create();
+
+ ResourceClient.forSearchClient().create(searchInstance);
+ Response response = get(String.format("query=(id==%s)", instanceId), 200);
+ JsonObject responseJson = response.getJson();
+ JsonArray items = responseJson.getJsonArray("items");
+
+ assertThat(responseJson.getString("id"), UUIDMatcher.is(instanceId));
+ assertThat(items, iterableWithSize(2));
+ assertThat(items, hasItem(allOf(
+ hasJsonPath("id", UUIDMatcher.is(collegeItem.getId())),
+ hasJsonPath("tenantId", is(TENANT_ID_COLLEGE)))));
+ assertThat(items, hasItem(allOf(
+ hasJsonPath("id", UUIDMatcher.is(universityItem.getId())),
+ hasJsonPath("tenantId", is(TENANT_ID_UNIVERSITY)))));
+ }
+
+ @Test
+ void canGetEmptyResult() {
+ UUID instanceId = UUID.randomUUID();
+
+ ResourceClient.forSearchClient().replace(instanceId, new JsonObject());
+ Response response = get(String.format("query=(id==%s)", instanceId), HTTP_OK.toInt());
+ JsonObject responseJson = response.getJson();
+
+ assertThat(responseJson.isEmpty(), is(true));
+ }
+
+ private Response get(String query, int expectedStatusCode) {
+ return restAssuredClient.get(itemsByInstanceUrl(query), expectedStatusCode,
+ "items-by-instance-request");
+ }
+}
diff --git a/src/test/java/api/loans/CheckInByBarcodeTests.java b/src/test/java/api/loans/CheckInByBarcodeTests.java
index 48973a4ff5..6df1466060 100644
--- a/src/test/java/api/loans/CheckInByBarcodeTests.java
+++ b/src/test/java/api/loans/CheckInByBarcodeTests.java
@@ -1639,7 +1639,7 @@ void availableNoticeIsSentUponCheckInWhenRequesterBarcodeWasChanged() {
@Test
void linkItemToHoldTLRWithHoldShelfWhenCheckedInItemThenFulfilledWithSuccess(){
reconfigureTlrFeature(TlrFeatureStatus.NOT_CONFIGURED);
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
UUID instanceId = instancesFixture.basedUponDunkirk().getId();
IndividualResource defaultWithHoldings = holdingsFixture.defaultWithHoldings(instanceId);
IndividualResource checkedOutItem = itemsClient.create(buildCheckedOutItemWithHoldingRecordsId(defaultWithHoldings.getId()));
@@ -1666,7 +1666,7 @@ void linkItemToHoldTLRWithHoldShelfWhenCheckedInItemThenFulfilledWithSuccess(){
@Test
void checkInItemWhenServicePointHasChangedToNoPickupLocation() {
- configurationsFixture.enableTlrFeature();
+ reconfigureTlrFeature(TlrFeatureStatus.ENABLED);
var instanceId = instancesFixture.basedUponDunkirk().getId();
var defaultWithHoldings = holdingsFixture.defaultWithHoldings(instanceId);
var checkedOutItem = itemsClient.create(buildCheckedOutItemWithHoldingRecordsId(
@@ -1683,7 +1683,7 @@ void checkInItemWhenServicePointHasChangedToNoPickupLocation() {
ServicePointBuilder changedServicePoint = new ServicePointBuilder(
servicePointsFixture.cd1().getId(), servicePointName, servicePointCode, discoveryDisplayName,
- description, shelvingLagTime, Boolean.FALSE, null, KEEP_THE_CURRENT_DUE_DATE.name());
+ description, shelvingLagTime, Boolean.FALSE, null, KEEP_THE_CURRENT_DUE_DATE.name(), false);
// Update existing service point
servicePointsFixture.update(servicePointCode, changedServicePoint);
@@ -1715,7 +1715,7 @@ void checkInItemWhenServicePointHasChangedToNoPickupLocation() {
@Test
void linkItemToHoldTLRWithDeliveryWhenCheckedInThenFulfilledWithSuccess(){
reconfigureTlrFeature(TlrFeatureStatus.NOT_CONFIGURED);
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
UUID instanceId = instancesFixture.basedUponDunkirk().getId();
IndividualResource defaultWithHoldings = holdingsFixture.defaultWithHoldings(instanceId);
IndividualResource checkedOutItem = itemsClient.create(buildCheckedOutItemWithHoldingRecordsId(defaultWithHoldings.getId()));
@@ -1741,7 +1741,7 @@ void linkItemToHoldTLRWithDeliveryWhenCheckedInThenFulfilledWithSuccess(){
@Test
void requestsShouldChangePositionWhenTheyGoInFulfillmentOnCheckIn() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
List items = itemsFixture.createMultipleItemsForTheSameInstance(3);
ItemResource firstItem = items.get(0);
@@ -1792,7 +1792,7 @@ void requestsShouldChangePositionWhenTheyGoInFulfillmentOnCheckIn() {
@Test
void canCheckinItemWhenRequestForAnotherItemOfSameInstanceExists() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
List items = itemsFixture.createMultipleItemsForTheSameInstance(2);
ItemResource firstItem = items.get(0);
@@ -1813,7 +1813,7 @@ void canCheckinItemWhenRequestForAnotherItemOfSameInstanceExists() {
@Test
void canFulFillRecallRequestWhenCheckInAnotherItemOfSameInstance() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
List items = itemsFixture.createMultipleItemsForTheSameInstance(2);
ItemResource firstItem = items.get(0);
ItemResource secondItem = items.get(1);
@@ -1839,7 +1839,7 @@ void canFulFillRecallRequestWhenCheckInAnotherItemOfSameInstance() {
@Test
void canFulFillRecallRequestWhenCheckInAnotherItemOfSameInstanceWithMultipleRecallRequests() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
List items = itemsFixture.createMultipleItemsForTheSameInstance(3);
ItemResource firstItem = items.get(0);
ItemResource secondItem = items.get(1);
@@ -1880,7 +1880,7 @@ void canFulFillRecallRequestWhenCheckInAnotherItemOfSameInstanceWithMultipleReca
@Test
void shouldNotLinkTitleLevelHoldRequestToAnItemUponCheckInWhenItemIsNonRequestable() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
ItemResource item = itemsFixture.basedUponNod();
checkOutFixture.checkOutByBarcode(item, usersFixture.rebecca());
IndividualResource request = requestsFixture.placeTitleLevelRequest(HOLD, item.getInstanceId(),
@@ -1910,7 +1910,7 @@ void shouldNotLinkTitleLevelHoldRequestToAnItemUponCheckInWhenItemIsNonRequestab
@Test
void shouldNotLinkTitleLevelHoldRequestToAnItemUponCheckInWhenItemIsNonLoanable() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
ItemResource item = itemsFixture.basedUponNod();
checkOutFixture.checkOutByBarcode(item, usersFixture.rebecca());
IndividualResource request = requestsFixture.placeTitleLevelRequest(HOLD, item.getInstanceId(),
@@ -1926,7 +1926,7 @@ void shouldNotLinkTitleLevelHoldRequestToAnItemUponCheckInWhenItemIsNonLoanable(
@Test
void shouldNotLinkTitleLevelRecallRequestToNewItemUponCheckInWhenItemIsNonRequestable() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
UUID canCirculateLoanTypeId = loanTypesFixture.canCirculate().getId();
UUID readingRoomLoanTypeId = loanTypesFixture.readingRoom().getId();
@@ -1972,7 +1972,7 @@ void shouldNotLinkTitleLevelRecallRequestToNewItemUponCheckInWhenItemIsNonReques
@Test
void shouldNotLinkTitleLevelRecallRequestToNewItemUponCheckInWhenItemIsNonLoanable() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
UUID canCirculateLoanTypeId = loanTypesFixture.canCirculate().getId();
UUID readingRoomLoanTypeId = loanTypesFixture.readingRoom().getId();
diff --git a/src/test/java/api/loans/CheckOutByBarcodeTests.java b/src/test/java/api/loans/CheckOutByBarcodeTests.java
index a827a13001..d1da178711 100644
--- a/src/test/java/api/loans/CheckOutByBarcodeTests.java
+++ b/src/test/java/api/loans/CheckOutByBarcodeTests.java
@@ -36,6 +36,7 @@
import static api.support.matchers.RequestMatchers.hasPosition;
import static api.support.matchers.RequestMatchers.isClosedFilled;
import static api.support.matchers.RequestMatchers.isOpenAwaitingPickup;
+import static api.support.matchers.RequestMatchers.isOpenInTransit;
import static api.support.matchers.ResponseStatusCodeMatcher.hasStatus;
import static api.support.matchers.TextDateTimeMatcher.isEquivalentTo;
import static api.support.matchers.TextDateTimeMatcher.withinSecondsAfter;
@@ -141,11 +142,11 @@ class CheckOutByBarcodeTests extends APITests {
private static final ZonedDateTime TEST_DUE_DATE =
ZonedDateTime.of(2019, 4, 20, 11, 30, 0, 0, UTC);
public static final String OVERRIDE_ITEM_NOT_LOANABLE_BLOCK_PERMISSION =
- "circulation.override-item-not-loanable-block";
+ "circulation.override-item-not-loanable-block.post";
public static final String OVERRIDE_PATRON_BLOCK_PERMISSION =
- "circulation.override-patron-block";
+ "circulation.override-patron-block.post";
public static final String OVERRIDE_ITEM_LIMIT_BLOCK_PERMISSION =
- "circulation.override-item-limit-block";
+ "circulation.override-item-limit-block.post";
public static final String INSUFFICIENT_OVERRIDE_PERMISSIONS =
"Insufficient override permissions";
private static final String TEST_COMMENT = "Some comment";
@@ -2492,7 +2493,7 @@ void canCheckOutUsingAlternateCheckoutRollingLoanPolicy() {
@ParameterizedTest
@EnumSource(value = TlrFeatureStatus.class, names = {"DISABLED", "NOT_CONFIGURED"})
void titleLevelRequestIsIgnoredWhenTlrFeatureIsNotEnabled(TlrFeatureStatus tlrFeatureStatus) {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
ItemResource item = itemsFixture.basedUponNod();
UserResource borrower = usersFixture.steve();
@@ -2519,7 +2520,7 @@ void titleLevelRequestIsIgnoredWhenTlrFeatureIsNotEnabled(TlrFeatureStatus tlrFe
"Title, Title"
})
void canFulfilPageAndHoldRequestsWithMixedLevels(String pageRequestLevel, String holdRequestLevel) {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
ItemResource item = itemsFixture.basedUponNod();
UserResource firstRequester = usersFixture.steve();
@@ -2582,7 +2583,7 @@ void canFulfilPageAndHoldRequestsWithMixedLevels(String pageRequestLevel, String
@Test
void canCheckoutItemWhenTitleLevelPageRequestsExistForDifferentItemsOfSameInstance() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
List items = itemsFixture.createMultipleItemsForTheSameInstance(4);
UUID instanceId = items.stream().findAny().orElseThrow().getInstanceId();
@@ -2613,7 +2614,7 @@ void canCheckoutItemWhenTitleLevelPageRequestsExistForDifferentItemsOfSameInstan
void cannotCheckoutItemWhenTitleLevelPageRequestExistsForSameItem(
String firstRequestLevel, String secondRequestLevel) {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
List items = itemsFixture.createMultipleItemsForTheSameInstance(2);
ItemResource randomItem = items.stream().findAny().orElseThrow();
@@ -2752,6 +2753,45 @@ void concurrentCheckoutsWhenCheckoutLockFeatureEnabled() throws InterruptedExcep
contains("Patron has reached maximum limit of 1 items for loan type")));
}
+ @Test
+ void circulationItemCheckOutUpdatesPrimaryEcsRequestStatus() {
+ settingsFixture.enableTlrFeature();
+ UUID itemId = UUID.randomUUID();
+ String itemBarcode = "item_barcode";
+ UUID pickupServicePointId = servicePointsFixture.cd1().getId();
+ UserResource requester = usersFixture.steve();
+ IndividualResource realInstance = instancesFixture.basedUponDunkirk();
+
+ // place title-level hold on instance with no items
+ IndividualResource initialRequest = requestsFixture.placeTitleLevelHoldShelfRequest(
+ realInstance.getId(), requester, ZonedDateTime.now(), pickupServicePointId);
+ UUID requestId = initialRequest.getId();
+
+ // create circulation item which has same ID as the "real" item, but different holdingsId, instanceId, etc.
+ UUID dcbInstanceId = UUID.randomUUID();
+ IndividualResource dcbHoldings = holdingsFixture.defaultWithHoldings(dcbInstanceId);
+ final IndividualResource circulationItem = circulationItemsFixture.createCirculationItem(
+ itemId, itemBarcode, dcbHoldings.getId(), locationsFixture.mainFloor().getId(), "DCB instance");
+
+ // update request same way DCB does it when a borrowing transaction is created
+ requestsStorageClient.replace(requestId,
+ requestsStorageClient.get(requestId)
+ .getJson()
+ .put("itemId", itemId.toString())
+ .put("holdingsRecordId", dcbHoldings.getId().toString())
+ .put("item", new JsonObject().put("barcode", itemBarcode)));
+
+ UUID randomServicePointId = servicePointsFixture.cd2().getId();
+ checkInFixture.checkInByBarcode(circulationItem, randomServicePointId);
+ assertThat(requestsFixture.getById(requestId).getJson(), isOpenInTransit());
+
+ checkInFixture.checkInByBarcode(circulationItem, pickupServicePointId);
+ assertThat(requestsFixture.getById(requestId).getJson(), isOpenAwaitingPickup());
+
+ checkOutFixture.checkOutByBarcode(circulationItem, requester);
+ assertThat(requestsFixture.getById(requestId).getJson(), isClosedFilled());
+ }
+
private IndividualResource placeRequest(String requestLevel, ItemResource item,
IndividualResource requester) {
diff --git a/src/test/java/api/loans/ReminderFeeTests.java b/src/test/java/api/loans/ReminderFeeTests.java
index a127cfbf83..139be3905b 100644
--- a/src/test/java/api/loans/ReminderFeeTests.java
+++ b/src/test/java/api/loans/ReminderFeeTests.java
@@ -58,7 +58,7 @@ class ReminderFeeTests extends APITests {
private UUID remindersTwoDaysBetweenNotOnClosedDaysPolicyId;
- private static final String OVERRIDE_RENEWAL_BLOCK_PERMISSION = "circulation.override-renewal-block";
+ private static final String OVERRIDE_RENEWAL_BLOCK_PERMISSION = "circulation.override-renewal-block.post";
@BeforeEach
void beforeEach() {
diff --git a/src/test/java/api/loans/RenewalAPITests.java b/src/test/java/api/loans/RenewalAPITests.java
index 5501f05c7c..b77c3a4770 100644
--- a/src/test/java/api/loans/RenewalAPITests.java
+++ b/src/test/java/api/loans/RenewalAPITests.java
@@ -91,7 +91,6 @@
import org.junit.jupiter.params.provider.ValueSource;
import api.support.APITests;
-import api.support.TlrFeatureStatus;
import api.support.builders.CheckOutByBarcodeRequestBuilder;
import api.support.builders.ClaimItemReturnedRequestBuilder;
import api.support.builders.FeeFineBuilder;
@@ -114,7 +113,6 @@
import api.support.http.ItemResource;
import api.support.http.OkapiHeaders;
import api.support.http.ResourceClient;
-import api.support.http.UserResource;
import api.support.matchers.OverdueFineMatcher;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
@@ -123,10 +121,10 @@
public abstract class RenewalAPITests extends APITests {
public static final String PATRON_BLOCK_NAME = "patronBlock";
private static final String TEST_COMMENT = "Some comment";
- private static final String OVERRIDE_PATRON_BLOCK_PERMISSION = "circulation.override-patron-block";
+ private static final String OVERRIDE_PATRON_BLOCK_PERMISSION = "circulation.override-patron-block.post";
public static final String OVERRIDE_ITEM_LIMIT_BLOCK_PERMISSION =
- "circulation.override-item-limit-block";
- private static final String OVERRIDE_RENEWAL_BLOCK_PERMISSION = "circulation.override-renewal-block";
+ "circulation.override-item-limit-block.post";
+ private static final String OVERRIDE_RENEWAL_BLOCK_PERMISSION = "circulation.override-renewal-block.post";
private static final String RENEWED_THROUGH_OVERRIDE = "renewedThroughOverride";
private static final String PATRON_WAS_BLOCKED_MESSAGE = "Patron blocked from renewing";
private static final String ITEM_IS_DECLARED_LOST = "item is Declared lost";
diff --git a/src/test/java/api/loans/scenarios/ChangeDueDateAPITests.java b/src/test/java/api/loans/scenarios/ChangeDueDateAPITests.java
index 9c84c684dc..509f1229d5 100644
--- a/src/test/java/api/loans/scenarios/ChangeDueDateAPITests.java
+++ b/src/test/java/api/loans/scenarios/ChangeDueDateAPITests.java
@@ -436,7 +436,7 @@ void dueDateChangeShouldNotUnsetRenewalFlagValueWhenTlrFeatureEnabled() {
assertThat(recalledLoan.getJson().getBoolean("dueDateChangedByRecall"), equalTo(true));
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
requestsClient.create(new RequestBuilder()
.recall()
.titleRequestLevel()
@@ -496,7 +496,7 @@ void dueDateChangeShouldUnsetRenewalFlagValueWhenTlrFeatureDisabledOrNotConfigur
assertThat(recalledLoan.getJson().getBoolean("dueDateChangedByRecall"), equalTo(true));
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
requestsClient.create(new RequestBuilder()
.recall()
diff --git a/src/test/java/api/loans/scenarios/CheckoutWithRequestScenarioTests.java b/src/test/java/api/loans/scenarios/CheckoutWithRequestScenarioTests.java
index 6332320521..efcd458121 100644
--- a/src/test/java/api/loans/scenarios/CheckoutWithRequestScenarioTests.java
+++ b/src/test/java/api/loans/scenarios/CheckoutWithRequestScenarioTests.java
@@ -165,7 +165,7 @@ void checkingOutWithHoldRequestAppliesAlternatePeriodAndScheduledForFixedPolicy(
@Test
void alternatePeriodShouldBeAppliedWhenRequestQueueContainsHoldTlr() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
List items = itemsFixture.createMultipleItemsForTheSameInstance(2);
var firstItem = items.get(0);
var secondItem = items.get(1);
@@ -245,7 +245,7 @@ void alternatePeriodShouldBeAppliedWhenRequestQueueContainsHoldTlr() {
@Test
void alternatePeriodShouldNotBeAppliedWhenRequestQueueContainsHoldIlrForDifferentItem() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
List items = itemsFixture.createMultipleItemsForTheSameInstance(2);
var firstItem = items.get(0);
var secondItem = items.get(1);
diff --git a/src/test/java/api/queue/RequestQueueResourceTest.java b/src/test/java/api/queue/RequestQueueResourceTest.java
index 98d70164b0..04fb3cffa8 100644
--- a/src/test/java/api/queue/RequestQueueResourceTest.java
+++ b/src/test/java/api/queue/RequestQueueResourceTest.java
@@ -303,7 +303,7 @@ void shouldGetRequestQueueForItemSuccessfully() {
@Test
void shouldGetRequestQueueForInstanceSuccessfully() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
UUID isbnIdentifierId = identifierTypesFixture.isbn().getId();
String isbnValue = "9780866989427";
diff --git a/src/test/java/api/requests/AllowedServicePointsAPITests.java b/src/test/java/api/requests/AllowedServicePointsAPITests.java
index e6995df3f4..189f25927c 100644
--- a/src/test/java/api/requests/AllowedServicePointsAPITests.java
+++ b/src/test/java/api/requests/AllowedServicePointsAPITests.java
@@ -22,6 +22,7 @@
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.iterableWithSize;
import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.core.Is.is;
import java.time.ZonedDateTime;
import java.util.ArrayList;
@@ -137,6 +138,7 @@ void shouldReturnListOfAllowedServicePointsForRequest(RequestType requestType,
List allowedSpInResponse) {
var requesterId = usersFixture.steve().getId().toString();
+ var patronGroupId = patronGroupsFixture.regular().getId().toString();
var items = itemsFixture.createMultipleItemForTheSameInstance(1,
List.of(ib -> ib.withStatus(itemStatus.getValue())));
var item = items.get(0);
@@ -154,8 +156,18 @@ void shouldReturnListOfAllowedServicePointsForRequest(RequestType requestType,
.collect(Collectors.toSet()));
var response = requestLevel == TITLE
- ? get("create", requesterId, instanceId, null, null, HttpStatus.SC_OK).getJson()
- : get("create", requesterId, null, itemId, null, HttpStatus.SC_OK).getJson();
+ ? get("create", requesterId, null, instanceId, null, null, null, null,
+ HttpStatus.SC_OK).getJson()
+ : get("create", requesterId, null, null, itemId, null, null, null,
+ HttpStatus.SC_OK).getJson();
+
+ assertThat(response, allowedServicePointMatcher(Map.of(requestType, allowedSpInResponse)));
+
+ response = requestLevel == TITLE
+ ? get("create", null, patronGroupId, instanceId, null, null, null, null,
+ HttpStatus.SC_OK).getJson()
+ : get("create", null, patronGroupId, null, itemId, null, null, null,
+ HttpStatus.SC_OK).getJson();
assertThat(response, allowedServicePointMatcher(Map.of(requestType, allowedSpInResponse)));
}
@@ -202,7 +214,7 @@ void shouldReturnListOfAllowedServicePointsForRequestReplacement(
.map(UUID::fromString)
.collect(Collectors.toSet()));
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
var pickupLocationId = allowedSpByPolicy.stream()
.findFirst()
.map(AllowedServicePoint::getId)
@@ -223,11 +235,28 @@ void shouldReturnListOfAllowedServicePointsForRequestReplacement(
var requestId = request == null ? null : request.getId().toString();
var response =
- get("replace", null, null, null, requestId, HttpStatus.SC_OK).getJson();
+ get("replace", null, null, null, null, requestId, null, null,
+ HttpStatus.SC_OK).getJson();
assertThat(response, allowedServicePointMatcher(Map.of(requestType, allowedSpInResponse)));
}
+ @Test
+ void shouldReturnListOfAllowedServicePointsForHoldRequestReplacementWhenInstanceHasNoItems() {
+ settingsFixture.configureTlrFeature(true, false, null, null, null);
+ var requester = usersFixture.steve();
+ var instanceId = instancesFixture.basedUponDunkirk().getId();
+ var servicePointId = servicePointsFixture.cd1().getId();
+ setRequestPolicyWithAllowedServicePoints(HOLD, Set.of(servicePointId));
+ IndividualResource request = requestsFixture.placeTitleLevelHoldShelfRequest(
+ instanceId, requester, ZonedDateTime.now(), servicePointId);
+
+ var response = get("replace", null, null, null, null, request.getId().toString(), "true", null,
+ HttpStatus.SC_OK).getJson();
+ var expectedServicePoint = new AllowedServicePoint(servicePointId.toString(), "Circ Desk 1");
+ assertThat(response, allowedServicePointMatcher(Map.of(HOLD, List.of(expectedServicePoint))));
+ }
+
public static Object[] shouldReturnOnlyExistingAllowedServicePointForRequestParameters() {
String sp1Id = randomId();
String sp2Id = randomId();
@@ -494,7 +523,7 @@ void allPickupLocationsAreReturnedForTitleLevelHoldWhenItIsDisabledAndInstanceHa
boolean instanceHasHoldings) {
// allow TLR-holds for instances with no holdings/items
- configurationsFixture.configureTlrFeature(true, false, null, null, null);
+ settingsFixture.configureTlrFeature(true, false, null, null, null);
IndividualResource sp1 = servicePointsFixture.cd1(); // pickup location
IndividualResource sp2 = servicePointsFixture.cd2(); // pickup location
@@ -515,7 +544,7 @@ void allPickupLocationsAreReturnedForTitleLevelHoldWhenItIsDisabledAndInstanceHa
@Test
void noAllowedServicePointsAreReturnedForTitleLevelHoldWhenItIsDisabledAndInstanceHasItems() {
// allow TLR-holds for instances with no holdings/items
- configurationsFixture.configureTlrFeature(true, false, null, null, null);
+ settingsFixture.configureTlrFeature(true, false, null, null, null);
IndividualResource sp1 = servicePointsFixture.cd1(); // pickup location
servicePointsFixture.cd2(); // pickup location
@@ -653,8 +682,8 @@ void allowedServicePointsAreSortedByName() {
@Test
void getReplaceFailsWhenRequestDoesNotExist() {
String requestId = randomId();
- Response response = get("replace", null, null, null, requestId,
- HttpStatus.SC_UNPROCESSABLE_ENTITY);
+ Response response = get("replace", null, null, null, null, requestId, null,
+ null, HttpStatus.SC_UNPROCESSABLE_ENTITY);
assertThat(response.getJson(), hasErrorWith(hasMessage(
String.format("Request with ID %s was not found", requestId))));
}
@@ -663,8 +692,8 @@ void getReplaceFailsWhenRequestDoesNotExist() {
void getMoveFailsWhenRequestDoesNotExist() {
String requestId = randomId();
String itemId = itemsFixture.basedUponNod().getId().toString();
- Response response = get("move", null, null, itemId, requestId,
- HttpStatus.SC_UNPROCESSABLE_ENTITY);
+ Response response = get("move", null, null, null, itemId, requestId, null,
+ null, HttpStatus.SC_UNPROCESSABLE_ENTITY);
assertThat(response.getJson(), hasErrorWith(hasMessage(
String.format("Request with ID %s was not found", requestId))));
}
@@ -703,7 +732,7 @@ void shouldReturnListOfAllowedServicePointsForRequestMove(RequestLevel requestLe
setRequestPolicyWithAllowedServicePoints(PAGE, Set.of(sp1Uuid));
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
IndividualResource request = requestsFixture.place(new RequestBuilder()
.withRequestType(PAGE.toString())
@@ -725,62 +754,158 @@ void shouldReturnListOfAllowedServicePointsForRequestMove(RequestLevel requestLe
// Valid "move" request
var moveResponse =
- get("move", null, null, itemToMoveToId, requestId, HttpStatus.SC_OK).getJson();
+ get("move", null, null, null, itemToMoveToId, requestId, null, null,
+ HttpStatus.SC_OK).getJson();
assertThat(moveResponse, allowedServicePointMatcher(Map.of(HOLD, List.of(sp2))));
// Invalid "move" requests
- var invalidMoveResponse1 = get("move", null, null, null, requestId,
- HttpStatus.SC_BAD_REQUEST);
+ var invalidMoveResponse1 = get("move", null, null, null, null, requestId,
+ null, null, HttpStatus.SC_BAD_REQUEST);
assertThat(invalidMoveResponse1.getBody(), equalTo("Invalid combination of query parameters"));
- var invalidMoveResponse2 = get("move", null, null, itemToMoveToId, null,
- HttpStatus.SC_BAD_REQUEST);
+ var invalidMoveResponse2 = get("move", null, null, null, itemToMoveToId,
+ null,
+ null, null, HttpStatus.SC_BAD_REQUEST);
assertThat(invalidMoveResponse2.getBody(), equalTo("Invalid combination of query parameters"));
- var invalidMoveResponse3 = get("move", null, null, null, null,
- HttpStatus.SC_BAD_REQUEST);
+ var invalidMoveResponse3 = get("move", null, null, null, null, null,
+ null, null, HttpStatus.SC_BAD_REQUEST);
assertThat(invalidMoveResponse3.getBody(), equalTo("Invalid combination of query parameters"));
- var invalidMoveResponse4 = get("move", requesterId, null, itemToMoveToId, requestId,
- HttpStatus.SC_BAD_REQUEST);
+ var invalidMoveResponse4 = get("move", requesterId,null, null, itemToMoveToId, requestId,
+ null, null, HttpStatus.SC_BAD_REQUEST);
assertThat(invalidMoveResponse4.getBody(), equalTo("Invalid combination of query parameters"));
- var invalidMoveResponse5 = get("move", null, instanceId, itemToMoveToId, requestId,
- HttpStatus.SC_BAD_REQUEST);
+ var invalidMoveResponse5 = get("move", null, null, instanceId,
+ itemToMoveToId, requestId,
+ null, null, HttpStatus.SC_BAD_REQUEST);
assertThat(invalidMoveResponse5.getBody(), equalTo("Invalid combination of query parameters"));
// Valid "replace" request
var replaceResponse =
- get("replace", null, null, null, requestId, HttpStatus.SC_OK).getJson();
+ get("replace", null, null, null, null, requestId, null, null,
+ HttpStatus.SC_OK).getJson();
assertThat(replaceResponse, allowedServicePointMatcher(Map.of(HOLD, List.of(sp2))));
// Invalid "replace" requests
- var invalidReplaceResponse1 = get("replace", null, null, null, null,
- HttpStatus.SC_BAD_REQUEST);
+ var invalidReplaceResponse1 = get("replace", null, null, null, null, null,
+ null, null, HttpStatus.SC_BAD_REQUEST);
assertThat(invalidReplaceResponse1.getBody(),
equalTo("Invalid combination of query parameters"));
- var invalidReplaceResponse2 = get("replace", requesterId, null, null, requestId,
- HttpStatus.SC_BAD_REQUEST);
+ var invalidReplaceResponse2 = get("replace", requesterId,null, null, null, requestId,
+ null, null, HttpStatus.SC_BAD_REQUEST);
assertThat(invalidReplaceResponse2.getBody(),
equalTo("Invalid combination of query parameters"));
- var invalidReplaceResponse3 = get("replace", null, instanceId, null, requestId,
- HttpStatus.SC_BAD_REQUEST);
+ var invalidReplaceResponse3 = get("replace", null, null, instanceId, null
+ , requestId,
+ null, null, HttpStatus.SC_BAD_REQUEST);
assertThat(invalidReplaceResponse3.getBody(),
equalTo("Invalid combination of query parameters"));
- var invalidReplaceResponse4 = get("replace", null, null, requestedItemId, requestId,
- HttpStatus.SC_BAD_REQUEST);
+ var invalidReplaceResponse4 = get("replace", null, null, null,
+ requestedItemId, requestId,
+ null, null, HttpStatus.SC_BAD_REQUEST);
assertThat(invalidReplaceResponse4.getBody(),
equalTo("Invalid combination of query parameters"));
- var invalidReplaceResponse5 = get("replace", requesterId, instanceId,
- requestedItemId, requestId, HttpStatus.SC_BAD_REQUEST);
+ var invalidReplaceResponse5 = get("replace", requesterId, null, instanceId,
+ requestedItemId, requestId, null, null, HttpStatus.SC_BAD_REQUEST);
assertThat(invalidReplaceResponse5.getBody(),
equalTo("Invalid combination of query parameters"));
}
+ @Test
+ void shouldUseStubItemParameterInCirculationRuleMatchingWhenPresent() {
+ var requesterId = usersFixture.steve().getId().toString();
+ var instanceId = itemsFixture.createMultipleItemsForTheSameInstance(2).get(0)
+ .getInstanceId().toString();
+ var cd1 = servicePointsFixture.cd1();
+ var cd2 = servicePointsFixture.cd2();
+ var cd4 = servicePointsFixture.cd4();
+ var cd5 = servicePointsFixture.cd5();
+ final UUID book = materialTypesFixture.book().getId();
+ final UUID patronGroup = patronGroupsFixture.regular().getId();
+ circulationRulesFixture.updateCirculationRules(createRules("m " + book +
+ "+ g " + patronGroup, "g " + patronGroup));
+
+ var response = getCreateOp(requesterId, instanceId, null, "true", null, HttpStatus.SC_OK)
+ .getJson();
+ assertThat(response, hasNoJsonPath(PAGE.getValue()));
+ JsonArray allowedServicePoints = response.getJsonArray(HOLD.getValue());
+ assertServicePointsMatch(allowedServicePoints, List.of(cd1, cd2, cd4, cd5));
+ allowedServicePoints = response.getJsonArray(RECALL.getValue());
+ assertServicePointsMatch(allowedServicePoints, List.of(cd1, cd2, cd4, cd5));
+
+ response = getCreateOp(requesterId, instanceId, null, "false", null, HttpStatus.SC_OK)
+ .getJson();
+ assertThat(response, hasNoJsonPath(HOLD.getValue()));
+ assertThat(response, hasNoJsonPath(RECALL.getValue()));
+ allowedServicePoints = response.getJsonArray(PAGE.getValue());
+ assertServicePointsMatch(allowedServicePoints, List.of(cd1, cd2, cd4, cd5));
+
+ response = getCreateOp(requesterId, instanceId, null, HttpStatus.SC_OK).getJson();
+ assertThat(response, hasNoJsonPath(HOLD.getValue()));
+ assertThat(response, hasNoJsonPath(RECALL.getValue()));
+ allowedServicePoints = response.getJsonArray(PAGE.getValue());
+ assertServicePointsMatch(allowedServicePoints, List.of(cd1, cd2, cd4, cd5));
+ }
+
+ @Test
+ void shouldReturnErrorIfUseStubItemIsInvalid() {
+ Response errorResponse = getCreateOp(UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(), null, "invalid", null,
+ HttpStatus.SC_BAD_REQUEST);
+ assertThat(errorResponse.getBody(), is("useStubItem is not a valid boolean: invalid."));
+ }
+
+ @Test
+ void shouldConsiderEcsRequestRoutingParameterForAllowedServicePoints() {
+ var requesterId = usersFixture.steve().getId().toString();
+ var instanceId = itemsFixture.createMultipleItemsForTheSameInstance(2).get(0)
+ .getInstanceId().toString();
+ var cd1 = servicePointsFixture.cd1();
+ var cd2 = servicePointsFixture.cd2();
+ var cd4 = servicePointsFixture.cd4();
+ var cd11 = servicePointsFixture.cd11();
+
+ final Map> allowedServicePointsInPolicy = new HashMap<>();
+ allowedServicePointsInPolicy.put(PAGE, Set.of(cd1.getId(), cd2.getId(), cd11.getId()));
+ allowedServicePointsInPolicy.put(HOLD, Set.of(cd4.getId(), cd2.getId(), cd11.getId()));
+ var requestPolicy = requestPoliciesFixture.createRequestPolicyWithAllowedServicePoints(
+ allowedServicePointsInPolicy, PAGE, HOLD);
+ policiesActivation.use(PoliciesToActivate.builder().requestPolicy(requestPolicy));
+
+ var response = getCreateOp(requesterId, instanceId, null, "false", "true",
+ HttpStatus.SC_OK).getJson();
+ JsonArray allowedServicePoints = response.getJsonArray(PAGE.getValue());
+ assertServicePointsMatch(allowedServicePoints, List.of(cd11));
+ assertThat(response, hasNoJsonPath(HOLD.getValue()));
+ assertThat(response, hasNoJsonPath(RECALL.getValue()));
+
+ response = getCreateOp(requesterId, instanceId, null, "false", "false",
+ HttpStatus.SC_OK).getJson();
+ allowedServicePoints = response.getJsonArray(PAGE.getValue());
+ assertServicePointsMatch(allowedServicePoints, List.of(cd1, cd2));
+ assertThat(response, hasNoJsonPath(HOLD.getValue()));
+ assertThat(response, hasNoJsonPath(RECALL.getValue()));
+
+ response = getCreateOp(requesterId, instanceId, null, "false", null,
+ HttpStatus.SC_OK).getJson();
+ allowedServicePoints = response.getJsonArray(PAGE.getValue());
+ assertServicePointsMatch(allowedServicePoints, List.of(cd1, cd2));
+ assertThat(response, hasNoJsonPath(HOLD.getValue()));
+ assertThat(response, hasNoJsonPath(RECALL.getValue()));
+ }
+
+ @Test
+ void shouldReturnErrorIfEcsRequestRoutingIsInvalid() {
+ Response errorResponse = getCreateOp(UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(), null, null, "invalid", HttpStatus.SC_BAD_REQUEST);
+ assertThat(errorResponse.getBody(), is("ecsRequestRouting is not a valid boolean: invalid."));
+ }
+
private void assertServicePointsMatch(JsonArray response,
List expectedServicePoints) {
@@ -805,24 +930,38 @@ private void assertServicePointsMatch(JsonArray response,
.map(sp -> sp.getJson().getString("name")).toArray(String[]::new)));
}
+ private Response getCreateOp(String requesterId, String instanceId, String itemId,
+ String useStubItem, String ecsRequestRouting, int expectedStatusCode) {
+
+ return get("create", requesterId,null, instanceId, itemId, null, useStubItem,
+ ecsRequestRouting, expectedStatusCode);
+ }
+
private Response getCreateOp(String requesterId, String instanceId, String itemId,
int expectedStatusCode) {
- return get("create", requesterId, instanceId, itemId, null, expectedStatusCode);
+ return get("create", requesterId, null, instanceId, itemId, null, null, null,
+ expectedStatusCode);
}
private Response getReplaceOp(String requestId, int expectedStatusCode) {
- return get("replace", null, null, null, requestId, expectedStatusCode);
+ return get("replace", null, null, null, null, requestId, null, null,
+ expectedStatusCode);
}
- private Response get(String operation, String requesterId, String instanceId, String itemId,
- String requestId, int expectedStatusCode) {
+ private Response get(String operation, String requesterId,
+ String patronGroupId, String instanceId, String itemId,
+ String requestId, String useStubItem, String ecsRequestRouting,
+ int expectedStatusCode) {
List queryParams = new ArrayList<>();
queryParams.add(namedParameter("operation", operation));
if (requesterId != null) {
queryParams.add(namedParameter("requesterId", requesterId));
}
+ if (patronGroupId != null) {
+ queryParams.add(namedParameter("patronGroupId", patronGroupId));
+ }
if (instanceId != null) {
queryParams.add(namedParameter("instanceId", instanceId));
}
@@ -832,6 +971,12 @@ private Response get(String operation, String requesterId, String instanceId, St
if (requestId != null) {
queryParams.add(namedParameter("requestId", requestId));
}
+ if (useStubItem != null) {
+ queryParams.add(namedParameter("useStubItem", useStubItem));
+ }
+ if (ecsRequestRouting != null) {
+ queryParams.add(namedParameter("ecsRequestRouting", ecsRequestRouting));
+ }
return restAssuredClient.get(allowedServicePointsUrl(), queryParams, expectedStatusCode,
"allowed-service-points");
@@ -854,4 +999,21 @@ private ServicePointBuilder servicePointBuilder() {
.withPickupLocation(TRUE)
.withHoldShelfExpriyPeriod(30, "Days");
}
+
+ private String createRules(String firstRuleCondition, String secondRuleCondition) {
+ final var loanPolicy = loanPoliciesFixture.canCirculateRolling().getId().toString();
+ final var allowAllRequestPolicy = requestPoliciesFixture.allowAllRequestPolicy()
+ .getId().toString();
+ final var holdAndRecallRequestPolicy = requestPoliciesFixture.allowHoldAndRecallRequestPolicy()
+ .getId().toString();
+ final var noticePolicy = noticePoliciesFixture.activeNotice().getId().toString();
+ final var overdueFinePolicy = overdueFinePoliciesFixture.facultyStandard().getId().toString();
+ final var lostItemFeePolicy = lostItemFeePoliciesFixture.facultyStandard().getId().toString();
+
+ return String.join("\n",
+ "priority: t, s, c, b, a, m, g",
+ "fallback-policy: l " + loanPolicy + " r " + allowAllRequestPolicy + " n " + noticePolicy + " o " + overdueFinePolicy + " i " + lostItemFeePolicy,
+ firstRuleCondition + " : l " + loanPolicy + " r " + allowAllRequestPolicy + " n " + noticePolicy + " o " + overdueFinePolicy + " i " + lostItemFeePolicy,
+ secondRuleCondition + " : l " + loanPolicy + " r " + holdAndRecallRequestPolicy + " n " + noticePolicy + " o " + overdueFinePolicy + " i " + lostItemFeePolicy);
+ }
}
diff --git a/src/test/java/api/requests/RequestsAPICreationTests.java b/src/test/java/api/requests/RequestsAPICreationTests.java
index 21985ad0fc..0d302544b6 100644
--- a/src/test/java/api/requests/RequestsAPICreationTests.java
+++ b/src/test/java/api/requests/RequestsAPICreationTests.java
@@ -182,7 +182,7 @@ public class RequestsAPICreationTests extends APITests {
private static final UUID CANCELLATION_TEMPLATE_ID_FROM_TLR_SETTINGS = UUID.randomUUID();
public static final String CREATE_REQUEST_PERMISSION = "circulation.requests.item.post";
- public static final String OVERRIDE_PATRON_BLOCK_PERMISSION = "circulation.override-patron-block";
+ public static final String OVERRIDE_PATRON_BLOCK_PERMISSION = "circulation.override-patron-block.post";
public static final OkapiHeaders HEADERS_WITH_ALL_OVERRIDE_PERMISSIONS =
buildOkapiHeadersWithPermissions(CREATE_REQUEST_PERMISSION, OVERRIDE_PATRON_BLOCK_PERMISSION);
public static final OkapiHeaders HEADERS_WITHOUT_OVERRIDE_PERMISSIONS =
@@ -194,7 +194,7 @@ public class RequestsAPICreationTests extends APITests {
@AfterEach
public void afterEach() {
mockClockManagerToReturnDefaultDateTime();
- configurationsFixture.deleteTlrFeatureConfig();
+ settingsFixture.deleteTlrFeatureSettings();
}
@Test
@@ -476,7 +476,7 @@ void cannotCreateItemLevelRequestForUnknownInstance(String tlrFeatureStatus,
@ParameterizedTest
@CsvSource({"Page", "Hold", "Recall"})
void cannotCreateTitleLevelRequestForUnknownInstance(String requestType) {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
UUID patronId = usersFixture.charlotte().getId();
final UUID pickupServicePointId = servicePointsFixture.cd1().getId();
@@ -524,7 +524,7 @@ void cannotCreateTitleLevelRequestForUnknownInstance(String requestType) {
})
void cannotCreateRequestForUnknownItem(String tlrFeatureEnabledString, String requestType) {
if (Boolean.parseBoolean(tlrFeatureEnabledString)) {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
}
IndividualResource instance = instancesFixture.basedUponDunkirk();
@@ -612,7 +612,7 @@ void canCreateTitleLevelRequestWhenTlrEnabled() {
final var items = itemsFixture.createMultipleItemsForTheSameInstance(2);
UUID instanceId = items.get(0).getInstanceId();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
IndividualResource requestResource = requestsClient.create(new RequestBuilder()
.page()
@@ -646,7 +646,7 @@ void createTitleLevelRequestWhenTlrEnabledSetLocation(String locationCode) {
final var items = itemsFixture.createMultipleItemsForTheSameInstance(2);
UUID instanceId = items.get(0).getInstanceId();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
IndividualResource requestResource = requestsClient.create(new RequestBuilder()
.page()
@@ -670,7 +670,7 @@ void createTitleLevelRequestWhenTlrEnabledSetLocationNoItems() {
final var items = itemsFixture.createMultipleItemsForTheSameInstance(2);
UUID instanceId = items.get(0).getInstanceId();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
Response response = requestsClient.attemptCreate(
new RequestBuilder()
@@ -697,7 +697,7 @@ void cannotCreateRequestWithNonExistentRequestLevelWhenTlrEnabled() {
ItemResource item = itemsFixture.basedUponSmallAngryPlanet();
UUID instanceId = item.getInstanceId();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
Response postResponse = requestsClient.attemptCreate(new RequestBuilder()
.recall()
@@ -804,7 +804,7 @@ void cannotCreateTlrWhenUserAlreadyRequestedAnItemFromTheSameTitle() {
@ParameterizedTest
@EnumSource(value = RequestType.class, names = {"HOLD", "RECALL"})
void cannotCreateHoldTlrWhenAvailableItemForInstance(RequestType requestType) {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
List items = itemsFixture.createMultipleItemsForTheSameInstance(2);
ItemResource item = items.get(0);
@@ -1438,7 +1438,7 @@ void canCreatePagedRequestWhenItemStatusIsAvailable() {
void cannotCreateTitleLevelPagedRequestIfThereAreNoAvailableItems() {
UUID patronId = usersFixture.charlotte().getId();
final UUID pickupServicePointId = servicePointsFixture.cd1().getId();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
UUID instanceId = instancesFixture.basedUponDunkirk().getId();
IndividualResource defaultWithHoldings = holdingsFixture.defaultWithHoldings(instanceId);
@@ -1470,7 +1470,7 @@ void canCreateTlrRecallWhenAvailableItemExistsButPageIsNotAllowedByPolicy() {
overdueFinePoliciesFixture.facultyStandard().getId(),
lostItemFeePoliciesFixture.facultyStandard().getId());
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
final var items = itemsFixture.createMultipleItemsForTheSameInstance(2);
var instanceId = items.get(0).getInstanceId();
@@ -1492,7 +1492,7 @@ void canCreateTlrRecallWhenAvailableItemExistsButPageIsNotAllowedByPolicy() {
void canCreateTitleLevelPagedRequest() {
UUID patronId = usersFixture.charlotte().getId();
final UUID pickupServicePointId = servicePointsFixture.cd1().getId();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
IndividualResource uponDunkirkInstance = instancesFixture.basedUponDunkirk();
UUID instanceId = uponDunkirkInstance.getId();
@@ -1524,7 +1524,7 @@ void canCreateTitleLevelPagedRequest() {
void canHaveUserBarcodeInCheckInPublishedEventAfterTitleLevelRequest() {
UUID patronId = usersFixture.charlotte().getId();
final UUID pickupServicePointId = servicePointsFixture.cd1().getId();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
IndividualResource uponDunkirkInstance = instancesFixture.basedUponDunkirk();
UUID instanceId = uponDunkirkInstance.getId();
@@ -1557,7 +1557,7 @@ void cannotCreateItemLevelRequestIfTitleLevelRequestForInstanceAlreadyCreated()
UUID patronId = usersFixture.charlotte().getId();
UUID pickupServicePointId = servicePointsFixture.cd1().getId();
UUID instanceId = UUID.randomUUID();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
buildItem(instanceId, "111");
requestsClient.create(buildPageTitleLevelRequest(patronId, pickupServicePointId, instanceId));
@@ -1579,7 +1579,7 @@ void cannotCreateTitleLevelRequestIfItemLevelRequestAlreadyCreated() {
UUID patronId = usersFixture.charlotte().getId();
UUID pickupServicePointId = servicePointsFixture.cd1().getId();
UUID instanceId = UUID.randomUUID();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
buildItem(instanceId, "111");
ItemResource secondItem = buildItem(instanceId, "222");
@@ -1601,7 +1601,7 @@ void canCreateItemLevelRequestAndTitleLevelRequestForDifferentInstances() {
UUID patronId = usersFixture.charlotte().getId();
UUID pickupServicePointId = servicePointsFixture.cd1().getId();
UUID instanceId= UUID.randomUUID();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
buildItem(instanceId, "111");
requestsClient.create(buildPageTitleLevelRequest(patronId, pickupServicePointId,
@@ -1621,7 +1621,7 @@ void cannotCreateTwoTitleLevelRequestsForSameInstance() {
UUID userId = usersFixture.charlotte().getId();
UUID pickupServicePointId = servicePointsFixture.cd1().getId();
UUID instanceId = UUID.randomUUID();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
buildItem(instanceId, "111");
buildItem(instanceId, "222");
@@ -1643,7 +1643,7 @@ void cannotCreateTwoItemLevelRequestsForSameItem() {
UUID userId = usersFixture.charlotte().getId();
UUID pickupServicePointId = servicePointsFixture.cd1().getId();
UUID instanceId = UUID.randomUUID();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
ItemResource item = buildItem(instanceId, "111");
requestsClient.create(buildItemLevelRequest(userId, pickupServicePointId,
@@ -1761,7 +1761,7 @@ void canCreateRecallRequestWhenItemIsCheckedOut() {
@Test
void tlrRecallShouldPickItemWithLoanWithNextClosestDueDateIfAnotherRecallRequestExists() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
var londonZoneId = ZoneId.of("Europe/London");
var items = itemsFixture.createMultipleItemsForTheSameInstance(3);
var firstItem = items.get(0);
@@ -1787,7 +1787,7 @@ void tlrRecallShouldPickItemWithLoanWithNextClosestDueDateIfAnotherRecallRequest
@Test
void tlrRecallShouldPickRecalledLoanWithClosestDueDateIfThereAreNoNotRecalledLoansAndSameAmountOfRecalls() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
var londonZoneId = ZoneId.of("Europe/London");
var items = itemsFixture.createMultipleItemsForTheSameInstance(4);
var firstItem = items.get(0);
@@ -1833,7 +1833,7 @@ void tlrRecallWithoutLoanShouldPickRecallableItemFromRequestedInstance(String it
IndividualResource inTransitPickupServicePoint = servicePointsFixture.cd2();
UUID instanceId = instancesFixture.basedUponDunkirk().getId();
IndividualResource defaultWithHoldings = holdingsFixture.defaultWithHoldings(instanceId);
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
if (itemStatus.equals("Paged")) {
itemsClient.create(new ItemBuilder()
.forHolding(defaultWithHoldings.getId())
@@ -1877,7 +1877,7 @@ void tlrRecallShouldFailWhenRequestHasNoLoanOrRecallableItem(String itemStatus)
IndividualResource requestPickupServicePoint = servicePointsFixture.cd1();
UUID instanceId = instancesFixture.basedUponDunkirk().getId();
IndividualResource defaultWithHoldings = holdingsFixture.defaultWithHoldings(instanceId);
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
itemsFixture.basedUponDunkirk(holdingBuilder -> holdingBuilder,
instanceBuilder -> instanceBuilder.withId(instanceId),
itemBuilder -> itemBuilder
@@ -2138,7 +2138,7 @@ void canCreateHoldRequestWhenItemIsMissing() {
"Lost and paid", "Paged", "In process (non-requestable)", "Intellectual item", "Unavailable",
"Restricted", "Unknown", "Awaiting delivery", "Order closed"})
void canCreateTlrHoldRequestWhenInstanceHasItemsWithStatusAllowedForHold(String itemStatus) {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
final ItemResource item = itemsFixture.basedUponSmallAngryPlanet(
builder -> builder.withStatus(itemStatus));
IndividualResource response = requestsFixture.placeTitleLevelHoldShelfRequest(item.getInstanceId(),
@@ -3307,7 +3307,7 @@ void cannotCreateItemLevelRequestWithoutInstanceId() {
@Test
void cannotCreateTitleLevelRequestWithoutInstanceId() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
ItemResource item = itemsFixture.basedUponNod();
@@ -3364,7 +3364,7 @@ void cannotCreateRequestWithItemIdButNoHoldingsRecordId(RequestLevel requestLeve
@Test
void recallTlrRequestShouldBeAppliedToLoanWithClosestDueDate() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
UUID pickupServicePointId = servicePointsFixture.cd1().getId();
UUID instanceId = UUID.randomUUID();
@@ -3439,7 +3439,7 @@ void recallTlrRequestShouldBeAppliedToLoanWithClosestDueDate() {
void statusOfTlrRequestShouldBeChangedIfAssociatedItemCheckedIn() {
UUID pickupServicePointId = servicePointsFixture.cd1().getId();
UUID instanceId = UUID.randomUUID();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
ItemResource firstItem = buildItem(instanceId, "111");
ItemResource secondItem = buildItem(instanceId, "222");
@@ -3467,7 +3467,7 @@ void statusOfTlrRequestShouldBeChangedIfAssociatedItemCheckedIn() {
@Test
void awaitingPickupNoticeShouldBeSentDuringCheckInWhenItemCreatedAfterHoldTlr() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
NoticePolicyBuilder noticePolicy = new NoticePolicyBuilder()
.withName("Policy with available notice")
@@ -3499,7 +3499,7 @@ void awaitingPickupNoticeShouldBeSentDuringCheckInWhenItemCreatedAfterHoldTlr()
@Test
void awaitingPickupNoticeShouldBeSentDuringCheckInWhenItemIsReturnedAndTlrHoldExists() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
NoticePolicyBuilder noticePolicy = new NoticePolicyBuilder()
.withName("Policy with available notice")
@@ -3529,7 +3529,7 @@ void awaitingPickupNoticeShouldBeSentDuringCheckInWhenItemIsReturnedAndTlrHoldEx
@Test
void awaitingPickupNoticesShouldBeSentToMultiplePatronsDuringPagedItemsCheckIn() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
NoticePolicyBuilder noticePolicy = new NoticePolicyBuilder()
.withName("Policy with available notice")
.withLoanNotices(Collections.singletonList(new NoticeConfigurationBuilder()
@@ -3645,7 +3645,7 @@ void pageRequestShouldNotBeCreatedIffulfillmentPreferenceIsNotValid(String fulfi
@Test
void itemCheckOutShouldNotAffectRequestAssociatedWithAnotherItemOfInstance() {
UUID instanceId = UUID.randomUUID();
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
ItemResource firstItem = buildItem(instanceId, "111");
ItemResource secondItem = buildItem(instanceId, "222");
ZonedDateTime requestDate = ZonedDateTime.of(2021, 7, 22, 10, 22, 54, 0, UTC);
@@ -3680,7 +3680,7 @@ void itemCheckOutShouldNotAffectRequestAssociatedWithAnotherItemOfInstance() {
@Test
void itemCheckOutRecallRequestCreationShouldProduceNotice() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
JsonObject recallToLoaneeConfiguration = new NoticeConfigurationBuilder()
.withTemplateId(UUID.randomUUID())
.withEventType(NoticeEventType.ITEM_RECALLED.getRepresentation())
@@ -3723,7 +3723,7 @@ void itemCheckOutRecallRequestCreationShouldProduceNotice() {
@Test
void itemCheckOutRecallCancelAgainRecallRequestCreationShouldProduceNotice() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
JsonObject recallToLoaneeConfiguration = new NoticeConfigurationBuilder()
.withTemplateId(UUID.randomUUID())
.withEventType(NoticeEventType.ITEM_RECALLED.getRepresentation())
@@ -3767,7 +3767,7 @@ void itemCheckOutRecallCancelAgainRecallRequestCreationShouldProduceNotice() {
@Test
void shouldTriggerNoticesForTitleLevelRecall() {
// Enable the Title Level Request feature
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
// Configure recall notice for the loan owner (borrower)
JsonObject recallToLoaneeConfiguration = new NoticeConfigurationBuilder()
@@ -3858,7 +3858,7 @@ private void verifyNumberOfNoticeEventsForUser(UUID userId, int expectedNoticeEv
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void titleLevelPageRequestIsCreatedForItemClosestToPickupServicePoint(int testCase) {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
UUID pickupServicePointId = servicePointsFixture.create(new ServicePointBuilder(
"Pickup service point", "PICKUP", "Display name")
@@ -3960,13 +3960,117 @@ void titleLevelPageRequestIsCreatedForItemClosestToPickupServicePoint(int testCa
assertThat(request.getJson().getString("itemId"), is(expectedItemId));
}
+ @Test
+ void primaryTlrCreationSkipsClosestServicePointLogicAndPoliciesIgnoredForHoldTlr() {
+ settingsFixture.configureTlrFeature(true, true, null, null, null);
+
+ policiesActivation.use(new RequestPolicyBuilder(
+ UUID.randomUUID(),
+ List.of(PAGE),
+ "Test request policy",
+ "Test description",
+ null
+ ));
+
+ UUID pickupServicePointId = servicePointsFixture.create(new ServicePointBuilder(
+ "Pickup service point", "PICKUP", "Display name")
+ .withPickupLocation(Boolean.TRUE))
+ .getId();
+
+ UUID anotherServicePointId = servicePointsFixture.create(new ServicePointBuilder(
+ "Another service point", "OTHER", "Display name")
+ .withPickupLocation(Boolean.TRUE))
+ .getId();
+
+ UUID institutionId = locationsFixture.createInstitution("Institution").getId();
+ UUID campusIdA = locationsFixture.createCampus("Campus A", institutionId).getId();
+ UUID campusIdB = locationsFixture.createCampus("Campus B", institutionId).getId();
+ UUID libraryIdA1 = locationsFixture.createLibrary("Library A1", campusIdA).getId();
+ UUID libraryIdB1 = locationsFixture.createLibrary("Library B1", campusIdB).getId();
+
+ UUID sameLibraryLocationId = locationsFixture.createLocation(new LocationBuilder()
+ .withName("Location in same library")
+ .withCode("3")
+ .forInstitution(institutionId)
+ .forCampus(campusIdA)
+ .forLibrary(libraryIdA1)
+ .withPrimaryServicePoint(anotherServicePointId)
+ .servedBy(anotherServicePointId))
+ .getId();
+
+ UUID anotherLibraryLocationId = locationsFixture.createLocation(new LocationBuilder()
+ .withName("Location in another library")
+ .withCode("3")
+ .forInstitution(institutionId)
+ .forCampus(campusIdB)
+ .forLibrary(libraryIdB1)
+ .withPrimaryServicePoint(anotherServicePointId)
+ .servedBy(anotherServicePointId))
+ .getId();
+
+ UUID instanceId = instancesFixture.basedUponDunkirk().getId();
+ UUID holdingsId = holdingsFixture.defaultWithHoldings(instanceId).getId();
+
+ // Closest item
+ UUID closestItemId = itemsFixture.basedUponDunkirkWithCustomHoldingAndLocation(
+ holdingsId, sameLibraryLocationId).getId();
+
+ UUID expectedItemId = itemsFixture.basedUponDunkirkWithCustomHoldingAndLocation(
+ holdingsId, anotherLibraryLocationId).getId();
+
+ var requestBuilder = new RequestBuilder()
+ .page()
+ .fulfillToHoldShelf()
+ .titleRequestLevel()
+ .withInstanceId(instanceId)
+ .withItemId(expectedItemId)
+ .withHoldingsRecordId(holdingsId)
+ .withRequestDate(ZonedDateTime.now())
+ .withRequesterId(usersFixture.steve().getId())
+ .withPickupServicePointId(pickupServicePointId);
+
+ // Request without ECS phase should fail
+ requestsFixture.attemptPlace(requestBuilder);
+
+ // The same request with Primary ECS phase should succeed because validation is skipped
+ IndividualResource request = requestsFixture.place(
+ requestBuilder.withEcsRequestPhase("Primary"));
+
+ assertThat(request.getJson().getString("itemId"), is(expectedItemId));
+
+ // To make sure there are no Available items left
+ requestsFixture.place(
+ requestBuilder
+ .withRequesterId(usersFixture.jessica().getId())
+ .withItemId(closestItemId)
+ .withEcsRequestPhase("Primary"));
+
+ // Placing TLR Hold request
+ var requestBuilderTlrHold = new RequestBuilder()
+ .hold()
+ .fulfillToHoldShelf()
+ .titleRequestLevel()
+ .withInstanceId(instanceId)
+ .withNoHoldingsRecordId()
+ .withNoItemId()
+ .withRequestDate(ZonedDateTime.now())
+ .withRequesterId(usersFixture.steve().getId())
+ .withPickupServicePointId(pickupServicePointId);
+
+ // Request without ECS phase should fail
+ requestsFixture.attemptPlace(requestBuilderTlrHold);
+
+ // The same request with Primary ECS phase should succeed because policy check is skipped
+ requestsFixture.place(requestBuilderTlrHold.withEcsRequestPhase("Primary"));
+ }
+
@Test
void pageTlrSucceedsWhenClosestAvailableItemIsNotPageable() {
// Page TLR should succeed when multiple available items exist, but the closest item to the
// pickup service point is not requestable. At the same time, other available and requestable
// items exist.
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
circulationRulesFixture.updateCirculationRules(differentRequestPoliciesBasedOnMaterialType());
UUID pickupServicePointId = servicePointsFixture.create(new ServicePointBuilder(
@@ -4065,7 +4169,7 @@ void holdTlrShouldSucceedWhenAvailableItemsExistButTheyAreNotPageable() {
// Hold TLR should be created when available items of the same instance exist, but all of them
// are not pageable due to the request policy
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
circulationRulesFixture.updateCirculationRules(differentRequestPoliciesBasedOnMaterialType());
IndividualResource instance = instancesFixture.basedUponDunkirk();
@@ -4110,7 +4214,7 @@ void holdAndRecallTlrShouldFailWhenAvailablePageableItemsExist(RequestType type)
// Hold TLR should fail when available items of the same instance exist and some of those
// items are pageable (request policy allows page requests)
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
circulationRulesFixture.updateCirculationRules(differentRequestPoliciesBasedOnMaterialType());
IndividualResource instance = instancesFixture.basedUponDunkirk();
@@ -4163,7 +4267,7 @@ void holdAndRecallTlrShouldFailWhenAvailablePageableItemsExist(RequestType type)
@Test
void recallTlrShouldNotBeCreatedForInstanceWithOnlyAgedToLostItem() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
UUID pickupServicePointId = servicePointsFixture.cd1().getId();
UUID instanceId = UUID.randomUUID();
@@ -4179,7 +4283,7 @@ void recallTlrShouldNotBeCreatedForInstanceWithOnlyAgedToLostItem() {
@Test
void recallTlrShouldBeCreatedForOnOrderItemIfInstanceHasOnOrderAndDeclaredLostItems() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
useLostItemPolicy(lostItemFeePoliciesFixture.chargeFee().getId());
UUID pickupServicePointId = servicePointsFixture.cd1().getId();
UUID instanceId = UUID.randomUUID();
@@ -4218,7 +4322,7 @@ void recallTlrShouldBeCreatedForOnOrderItemIfInstanceHasOnOrderAndDeclaredLostIt
@Test
void recallTlrShouldNotBeCreatedForInstanceWithOnlyDeclaredLostItem() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
useLostItemPolicy(lostItemFeePoliciesFixture.chargeFee().getId());
UUID instanceId = UUID.randomUUID();
@@ -4239,7 +4343,7 @@ void recallTlrShouldNotBeCreatedForInstanceWithOnlyDeclaredLostItem() {
@Test
void recallTlrShouldNotBeCreatedForInstanceWithOnlyClaimedReturnedItem() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
useLostItemPolicy(lostItemFeePoliciesFixture.chargeFee().getId());
UUID instanceId = UUID.randomUUID();
@@ -4260,7 +4364,7 @@ void recallTlrShouldNotBeCreatedForInstanceWithOnlyClaimedReturnedItem() {
@Test
void recallTlrShouldFailWhenNotAllowedByPolicy() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
circulationRulesFixture.updateCirculationRules(differentRequestPoliciesBasedOnMaterialType());
IndividualResource instance = instancesFixture.basedUponDunkirk();
@@ -4301,7 +4405,7 @@ void recallTlrShouldFailWhenNotAllowedByPolicy() {
@Test
void holdTlrShouldSucceedEvenWhenPolicyDoesNotAllowHolds() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
circulationRulesFixture.updateCirculationRules(differentRequestPoliciesBasedOnMaterialType());
IndividualResource instance = instancesFixture.basedUponDunkirk();
@@ -4343,7 +4447,7 @@ void holdTlrShouldSucceedEvenWhenPolicyDoesNotAllowHolds() {
@Test
void titleLevelHoldFailsWhenItShouldFollowCirculationRulesAndNoneOfInstanceItemsAreAllowedForHold() {
// enable TLR feature and make Hold requests respect circulation rules
- configurationsFixture.configureTlrFeature(true, true, null, null, null);
+ settingsFixture.configureTlrFeature(true, true, null, null, null);
IndividualResource book = materialTypesFixture.book();
IndividualResource video = materialTypesFixture.videoRecording();
@@ -4371,7 +4475,7 @@ void titleLevelHoldFailsWhenItShouldFollowCirculationRulesAndNoneOfInstanceItems
@Test
void titleLevelHoldIsPlacedWhenItShouldFollowCirculationRulesAndOneOfInstanceItemsIsAllowedForHold() {
// enable TLR feature and make Hold requests respect circulation rules
- configurationsFixture.configureTlrFeature(true, true, null, null, null);
+ settingsFixture.configureTlrFeature(true, true, null, null, null);
IndividualResource book = materialTypesFixture.book();
IndividualResource video = materialTypesFixture.videoRecording();
@@ -4395,7 +4499,7 @@ void titleLevelHoldIsPlacedWhenItShouldFollowCirculationRulesAndOneOfInstanceIte
@Test
void titleLevelHoldIsPlacedWhenItCanIgnoreCirculationRulesAndNoneOfInstanceItemsAreAllowedForHold() {
// enable TLR feature and make Hold requests ignore circulation rules
- configurationsFixture.configureTlrFeature(true, false, null, null, null);
+ settingsFixture.configureTlrFeature(true, false, null, null, null);
IndividualResource book = materialTypesFixture.book();
IndividualResource video = materialTypesFixture.videoRecording();
@@ -4456,7 +4560,7 @@ void itemLevelRequestIsNotPlacedWhenRequestPolicyDisallowsRequestedPickupService
void titleLevelRequestIsNotPlacedWhenRequestPolicyDisallowsRequestedPickupServicePoint(
RequestType requestType) {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
final UUID requestPolicyId = UUID.randomUUID();
policiesActivation.use(new RequestPolicyBuilder(
@@ -4752,7 +4856,7 @@ void canCreateHoldTlrForInstanceWithNoHoldingsRecords() {
@Test
void recallTlrShouldSucceedWhenItNeedsToPickLeastRecalledLoanAndRequestsWithNoLoansAreInTheQueueScenario1() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
final var patron1 = usersFixture.charlotte();
final var patron2 = usersFixture.jessica();
@@ -4851,7 +4955,7 @@ void recallTlrShouldSucceedWhenItNeedsToPickLeastRecalledLoanAndRequestsWithNoLo
@Test
void recallTlrShouldSucceedWhenItNeedsToPickLeastRecalledLoanAndRequestsWithNoLoansAreInTheQueueScenario2() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
final var patron1 = usersFixture.charlotte();
final var patron2 = usersFixture.jessica();
diff --git a/src/test/java/api/requests/RequestsAPILoanHistoryTests.java b/src/test/java/api/requests/RequestsAPILoanHistoryTests.java
index 4767c605d4..85c129025a 100644
--- a/src/test/java/api/requests/RequestsAPILoanHistoryTests.java
+++ b/src/test/java/api/requests/RequestsAPILoanHistoryTests.java
@@ -45,7 +45,7 @@ void creatingRecallRequestChangesTheOpenLoanForTheSameItem() {
@Test
void checkOutShouldNotTruncateLoanIfRecallRequestExistsForAnotherItemOfTheSameInstanceIfTlrIsEnabled() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
final var items = itemsFixture.createMultipleItemsForTheSameInstance(2);
var steve = usersFixture.steve();
var charlotte = usersFixture.charlotte();
diff --git a/src/test/java/api/requests/RequestsAPILoanRenewalTests.java b/src/test/java/api/requests/RequestsAPILoanRenewalTests.java
index 20434ac2be..5b82140520 100644
--- a/src/test/java/api/requests/RequestsAPILoanRenewalTests.java
+++ b/src/test/java/api/requests/RequestsAPILoanRenewalTests.java
@@ -302,7 +302,7 @@ void forbidRenewalLoanByIdWhenLoanProfileIsFixedFirstRequestInQueueIsHoldAndRene
@Test
void allowRenewalWhenFirstRequestInQueueIsItemLevelHoldForDifferentItemOfSameInstance() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
loanPolicyWithRollingProfileAndRenewingIsForbiddenWhenHoldIsPending();
List items = itemsFixture.createMultipleItemsForTheSameInstance(2);
ItemResource itemForLoan = items.get(0);
@@ -316,7 +316,7 @@ void allowRenewalWhenFirstRequestInQueueIsItemLevelHoldForDifferentItemOfSameIns
@Test
void allowRenewalWhenFirstRequestInQueueIsTitleLevelHoldForDifferentItemOfSameInstance() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
loanPolicyWithRollingProfileAndRenewingIsForbiddenWhenHoldIsPending();
List items = itemsFixture.createMultipleItemsForTheSameInstance(2);
ItemResource itemForLoan = items.get(0);
@@ -334,7 +334,7 @@ void allowRenewalWhenFirstRequestInQueueIsTitleLevelHoldForDifferentItemOfSameIn
@Test
void forbidRenewalWhenFirstRequestInQueueIsTitleLevelHoldWithoutItemId() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
loanPolicyWithRollingProfileAndRenewingIsForbiddenWhenHoldIsPending();
ItemResource item = itemsFixture.basedUponNod();
UserResource borrower = usersFixture.charlotte();
@@ -348,7 +348,7 @@ void forbidRenewalWhenFirstRequestInQueueIsTitleLevelHoldWithoutItemId() {
@Test
void alternateLoanPeriodIsNotUsedWhenFirstRequestInQueueIsItemLevelHoldForDifferentItemOfSameInstance() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
useRollingPolicyWithRenewingAllowedForHoldingRequest(); // base loan period - 3 weeks, alternate - 4 weeks
List items = itemsFixture.createMultipleItemsForTheSameInstance(2);
ItemResource itemForLoan = items.get(0);
@@ -364,7 +364,7 @@ void alternateLoanPeriodIsNotUsedWhenFirstRequestInQueueIsItemLevelHoldForDiffer
@Test
void alternateLoanPeriodIsNotUsedForRenewalWhenFirstRequestInQueueIsTitleLevelHoldForDifferentItemOfSameInstance() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
useRollingPolicyWithRenewingAllowedForHoldingRequest();
List items = itemsFixture.createMultipleItemsForTheSameInstance(2);
ItemResource itemForLoan = items.get(0);
@@ -384,7 +384,7 @@ void alternateLoanPeriodIsNotUsedForRenewalWhenFirstRequestInQueueIsTitleLevelHo
@Test
void alternateLoanPeriodIsUsedForRenewalWhenFirstRequestInQueueIsTitleLevelHoldWithoutItemId() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
useRollingPolicyWithRenewingAllowedForHoldingRequest(); // base loan period - 3 weeks, alternate - 4 weeks
ItemResource item = itemsFixture.basedUponNod();
UUID instanceId = item.getInstanceId();
@@ -400,7 +400,7 @@ void alternateLoanPeriodIsUsedForRenewalWhenFirstRequestInQueueIsTitleLevelHoldW
@Test
void forbidRenewalWhenTitleLevelRecallRequestExistsForSameItem() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
ItemResource item = itemsFixture.basedUponNod();
UserResource borrower = usersFixture.james();
checkOutFixture.checkOutByBarcode(item, borrower);
@@ -418,7 +418,7 @@ void forbidRenewalWhenTitleLevelRecallRequestExistsForSameItem() {
@Test
void allowRenewalWhenTitleLevelRecallRequestExistsForDifferentItemOfSameInstance() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
List items = itemsFixture.createMultipleItemsForTheSameInstance(2);
ItemResource itemForLoan = items.get(0);
ItemResource itemForRequest = items.get(1);
@@ -598,7 +598,7 @@ void forbidRenewalOverrideWhenRecallIsForDifferentItemOfSameInstance() {
@Test
void forbidRenewalOverrideWhenTitleLevelRecallRequestExistsForDifferentItemOfSameInstance() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
List items = itemsFixture.createMultipleItemsForTheSameInstance(2);
ItemResource itemForLoan = items.get(0);
ItemResource itemForRequest = items.get(1);
diff --git a/src/test/java/api/requests/RequestsAPIRetrievalTests.java b/src/test/java/api/requests/RequestsAPIRetrievalTests.java
index 3c71fcc441..bac64efab1 100644
--- a/src/test/java/api/requests/RequestsAPIRetrievalTests.java
+++ b/src/test/java/api/requests/RequestsAPIRetrievalTests.java
@@ -243,7 +243,7 @@ void canGetARequestById() {
@Test
void titleLevelRequestRetrievalById() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
UUID isbnIdentifierId = identifierTypesFixture.isbn().getId();
String isbnValue = "9780866989427";
diff --git a/src/test/java/api/requests/RequestsAPIUpdatingTests.java b/src/test/java/api/requests/RequestsAPIUpdatingTests.java
index a0483f1712..ba382cb4f7 100644
--- a/src/test/java/api/requests/RequestsAPIUpdatingTests.java
+++ b/src/test/java/api/requests/RequestsAPIUpdatingTests.java
@@ -866,7 +866,7 @@ void shouldUseCurrentUserIdAsSourceWhenSendingUpdateRequestMessage() {
@Test
void editingRecallTlrShouldNotChangeRecalledItem() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
IndividualResource patron1 = usersFixture.steve();
IndividualResource patron2 = usersFixture.rebecca();
diff --git a/src/test/java/api/requests/StaffSlipsTests.java b/src/test/java/api/requests/StaffSlipsTests.java
index d4be9194cc..190143a52d 100644
--- a/src/test/java/api/requests/StaffSlipsTests.java
+++ b/src/test/java/api/requests/StaffSlipsTests.java
@@ -546,7 +546,7 @@ void responseContainsSlipsWhenServicePointHasManyLocations(SlipsType slipsType)
@Test
void responseContainsSearchSlipsForTLR() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
var servicePointId = servicePointsFixture.cd1().getId();
var steve = usersFixture.steve();
var instance = instancesFixture.basedUponDunkirk();
diff --git a/src/test/java/api/requests/scenarios/HoldShelfFulfillmentTests.java b/src/test/java/api/requests/scenarios/HoldShelfFulfillmentTests.java
index ede4fbf442..91dceb4c8e 100644
--- a/src/test/java/api/requests/scenarios/HoldShelfFulfillmentTests.java
+++ b/src/test/java/api/requests/scenarios/HoldShelfFulfillmentTests.java
@@ -24,8 +24,6 @@
import java.time.ZonedDateTime;
import java.util.List;
import java.util.UUID;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
import org.folio.circulation.support.http.client.Response;
import org.folio.circulation.support.utils.ClockUtil;
@@ -38,14 +36,13 @@
import api.support.APITests;
import api.support.TlrFeatureStatus;
import api.support.builders.CheckInByBarcodeRequestBuilder;
-import api.support.builders.InstanceBuilder;
import api.support.http.IndividualResource;
import api.support.http.ItemResource;
class HoldShelfFulfillmentTests extends APITests {
@AfterEach
public void afterEach() {
- configurationsFixture.deleteTlrFeatureConfig();
+ settingsFixture.deleteTlrFeatureSettings();
}
@ParameterizedTest
@@ -84,7 +81,7 @@ void itemIsReadyForPickUpWhenCheckedInAtPickupServicePoint(TlrFeatureStatus tlrF
@ParameterizedTest
@ValueSource(ints = {1, 2})
void tlrRequestIsPositionedCorrectlyInUnifiedQueue(int checkedInItemNumber) {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
final IndividualResource pickupServicePoint = servicePointsFixture.cd1();
@@ -222,7 +219,7 @@ void canBeCheckedOutToRequestingPatronWhenReadyForPickup(TlrFeatureStatus tlrFea
@Test
void canBeCheckedOutToPatronRequestingTitleWhenReadyForPickup() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
final IndividualResource pickupServicePoint = servicePointsFixture.cd1();
@@ -288,7 +285,7 @@ void checkInAtDifferentServicePointPlacesItemInTransit(TlrFeatureStatus tlrFeatu
@Test
void checkInItemWithTlrRequestAtDifferentServicePointPlacesItemInTransit() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
final IndividualResource pickupServicePoint = servicePointsFixture.cd1();
final IndividualResource checkInServicePoint = servicePointsFixture.cd2();
@@ -363,7 +360,7 @@ void canBeCheckedOutToRequestingPatronWhenInTransit(TlrFeatureStatus tlrFeatureS
@Test
void canCheckoutItemForTitleLevelRequestWhenInTransit() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
final IndividualResource pickupServicePoint = servicePointsFixture.cd1();
final IndividualResource checkInServicePoint = servicePointsFixture.cd2();
@@ -430,7 +427,7 @@ void itemIsReadyForPickUpWhenCheckedInAtPickupServicePointAfterTransit(TlrFeatur
@Test
void itemWithTlrRequestIsReadyForPickUpWhenCheckedInAtPickupServicePointAfterTransit() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
final IndividualResource pickupServicePoint = servicePointsFixture.cd1();
final IndividualResource checkInServicePoint = servicePointsFixture.cd2();
@@ -508,7 +505,7 @@ void cannotCheckOutToOtherPatronWhenRequestIsAwaitingPickup(TlrFeatureStatus tlr
@Test
void cannotCheckOutToOtherPatronWhenTlrRequestIsAwaitingPickup() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
ItemResource smallAngryPlanet = itemsFixture.basedUponSmallAngryPlanet();
IndividualResource james = usersFixture.james();
@@ -580,7 +577,7 @@ void cannotCheckOutToOtherPatronWhenRequestIsInTransitForPickup(TlrFeatureStatus
@Test
void cannotCheckOutToOtherPatronWhenTlrRequestIsInTransitForPickup() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
final IndividualResource requestServicePoint = servicePointsFixture.cd1();
final IndividualResource checkInServicePoint = servicePointsFixture.cd2();
diff --git a/src/test/java/api/requests/scenarios/LoanDueDatesAfterRecallTests.java b/src/test/java/api/requests/scenarios/LoanDueDatesAfterRecallTests.java
index e8e3022147..8ab2ea5b64 100644
--- a/src/test/java/api/requests/scenarios/LoanDueDatesAfterRecallTests.java
+++ b/src/test/java/api/requests/scenarios/LoanDueDatesAfterRecallTests.java
@@ -285,7 +285,7 @@ void recallRequestWithMGDAndRDValuesChangesDueDateToMGDWithCLDDM() {
setFallbackPolicies(canCirculateRollingPolicy);
- servicePointsFixture.create(new ServicePointBuilder(checkOutServicePointId, "CLDDM Desk", "clddm", "CLDDM Desk Test", null, null, TRUE, null, null));
+ servicePointsFixture.create(new ServicePointBuilder(checkOutServicePointId, "CLDDM Desk", "clddm", "CLDDM Desk Test", null, null, TRUE, null, null, null));
// We use the loan date to calculate the minimum guaranteed due date (MGD)
final ZonedDateTime loanDate =
diff --git a/src/test/java/api/requests/scenarios/MoveRequestTests.java b/src/test/java/api/requests/scenarios/MoveRequestTests.java
index 7a53868f15..2f9a30856c 100644
--- a/src/test/java/api/requests/scenarios/MoveRequestTests.java
+++ b/src/test/java/api/requests/scenarios/MoveRequestTests.java
@@ -184,7 +184,7 @@ void canMoveRequestFromOneItemCopyToAnother() {
@Test
void itemShouldRemainPagedIfHoldCreatedAfterRequestHasBeenMovedToAnotherItem() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
val items = itemsFixture.createMultipleItemsForTheSameInstance(2);
val firstItem = items.get(0);
@@ -208,7 +208,7 @@ void itemShouldRemainPagedIfHoldCreatedAfterRequestHasBeenMovedToAnotherItem() {
@Test
void canMovePageTlrToAvailableItem() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
val firstItem = itemsFixture.basedUponSmallAngryPlanet("89809");
val pageIlrForFirstItem = requestsFixture.placeTitleLevelPageRequest(firstItem.getInstanceId(),
@@ -227,7 +227,7 @@ void canMovePageTlrToAvailableItem() {
@Test
void canMovePageTlrToRecall() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
val firstItem = itemsFixture.basedUponSmallAngryPlanet("89809");
val pageTlrForFirstItem = requestsFixture.placeTitleLevelPageRequest(firstItem.getInstanceId(),
usersFixture.james());
@@ -249,7 +249,7 @@ void canMovePageTlrToRecall() {
@Test
void canMoveRecallTlrToAnotherItem() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
val items = itemsFixture.createMultipleItemsForTheSameInstance(2);
val firstItem = items.get(0);
@@ -269,7 +269,7 @@ void canMoveRecallTlrToAnotherItem() {
@Test
void canMoveRecallTlrToPage() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
val items = itemsFixture.createMultipleItemsForTheSameInstance(2);
val firstItem = items.get(0);
@@ -289,7 +289,7 @@ void canMoveRecallTlrToPage() {
@Test
void whenRequestIsMovedItemShouldBecomeAvailableIfThereAreNoRequestsInTheQueueForThisItemIfTlrIsEnabled() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
val items = itemsFixture.createMultipleItemsForTheSameInstance(2);
val firstItem = items.get(0);
@@ -308,7 +308,7 @@ void whenRequestIsMovedItemShouldBecomeAvailableIfThereAreNoRequestsInTheQueueFo
@Test
void whenRequestIsMovedPositionsShouldBeConsistentWhenTlrIsEnabled() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
val items = itemsFixture.createMultipleItemsForTheSameInstance(3);
@@ -369,7 +369,7 @@ void whenRequestIsMovedPositionsShouldBeConsistentWhenTlrIsEnabled() {
@Test
void cannotMoveRequestToAnItemFromDifferentInstance() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
val nod = itemsFixture.basedUponNod();
val uprooted = itemsFixture.basedUponUprooted();
@@ -387,7 +387,7 @@ void cannotMoveRequestToAnItemFromDifferentInstance() {
@Test
void cannotMoveToOrFromHoldTlr() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
val items = itemsFixture.createMultipleItemsForTheSameInstance(2);
val firstItem = items.get(0);
@@ -416,7 +416,7 @@ void cannotMoveToOrFromHoldTlr() {
@Test
void cannotMoveTlrToTheSameItem() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
val item = itemsFixture.basedUponNod();
val jessica = usersFixture.jessica();
@@ -435,7 +435,7 @@ void cannotMoveTlrToTheSameItem() {
@Test
void cannotMoveTlrWhenFeatureIsDisabled() {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
val items = itemsFixture.createMultipleItemsForTheSameInstance(2);
val firstItem = items.get(0);
@@ -446,7 +446,7 @@ void cannotMoveTlrWhenFeatureIsDisabled() {
val firstItemHoldTlr = requestsFixture.placeTitleLevelHoldShelfRequest(firstItem.getInstanceId(),
usersFixture.james());
- configurationsFixture.disableTlrFeature();
+ settingsFixture.disableTlrFeature();
Response response = requestsFixture.attemptMove(new MoveRequestBuilder(firstItemHoldTlr.getId(),
secondItem.getId()));
diff --git a/src/test/java/api/support/APITestContext.java b/src/test/java/api/support/APITestContext.java
index 167f11e91e..c007163b71 100644
--- a/src/test/java/api/support/APITestContext.java
+++ b/src/test/java/api/support/APITestContext.java
@@ -9,6 +9,7 @@
import java.net.URL;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
+import java.util.Optional;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
@@ -40,6 +41,7 @@ public class APITestContext {
private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
public static final String TENANT_ID = "test_tenant";
+ public static String tempTenantId;
private static String USER_ID = "79ff2a8b-d9c3-5b39-ad4a-0a84025ab085";
private static final String TOKEN = "eyJhbGciOiJIUzUxMiJ9eyJzdWIiOiJhZG1pbiIsInVzZXJfaWQiOiI3OWZmMmE4Yi1kOWMzLTViMzktYWQ0YS0wYTg0MDI1YWIwODUiLCJ0ZW5hbnQiOiJ0ZXN0X3RlbmFudCJ9BShwfHcNClt5ZXJ8ImQTMQtAM1sQEnhsfWNmXGsYVDpuaDN3RVQ9";
@@ -58,7 +60,7 @@ public class APITestContext {
private static final int PORT = nextFreePort();
private static String fakeOkapiDeploymentId;
- private static Boolean useOkapiForStorage;
+ private static Boolean useOkapiForStorage = false;
private static Boolean useOkapiForInitialRequests;
static String getToken() {
@@ -66,7 +68,15 @@ static String getToken() {
}
public static String getTenantId() {
- return TENANT_ID;
+ return Optional.ofNullable(tempTenantId).orElse(TENANT_ID);
+ }
+
+ public static void setTempTenantId(String tenantId) {
+ tempTenantId = tenantId;
+ }
+
+ public static void clearTempTenantId() {
+ setTempTenantId(null);
}
public static String getUserId() {
diff --git a/src/test/java/api/support/APITests.java b/src/test/java/api/support/APITests.java
index 6935d04e64..22d039de7d 100644
--- a/src/test/java/api/support/APITests.java
+++ b/src/test/java/api/support/APITests.java
@@ -24,6 +24,17 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
+import api.support.fixtures.SearchInstanceFixture;
+import org.junit.Assert;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.testcontainers.containers.KafkaContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import api.support.fakes.FakeModNotify;
+import api.support.fakes.FakePubSub;
+import api.support.fakes.FakeStorageModule;
import api.support.fixtures.AddInfoFixture;
import api.support.fixtures.AddressTypesFixture;
import api.support.fixtures.AgeToLostFixture;
@@ -36,7 +47,6 @@
import api.support.fixtures.CirculationItemsFixture;
import api.support.fixtures.CirculationRulesFixture;
import api.support.fixtures.ClaimItemReturnedFixture;
-import api.support.fixtures.ConfigurationsFixture;
import api.support.fixtures.DeclareLostFixtures;
import api.support.fixtures.DepartmentFixture;
import api.support.fixtures.EndPatronSessionClient;
@@ -70,16 +80,6 @@
import api.support.fixtures.TenantActivationFixture;
import api.support.fixtures.UserManualBlocksFixture;
import api.support.fixtures.UsersFixture;
-import org.junit.Assert;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
-import org.testcontainers.containers.KafkaContainer;
-import org.testcontainers.utility.DockerImageName;
-
-import api.support.fakes.FakeModNotify;
-import api.support.fakes.FakePubSub;
-import api.support.fakes.FakeStorageModule;
import api.support.fixtures.policies.PoliciesActivationFixture;
import api.support.http.IndividualResource;
import api.support.http.ResourceClient;
@@ -238,9 +238,6 @@ public abstract class APITests {
protected final AddressTypesFixture addressTypesFixture
= new AddressTypesFixture(ResourceClient.forAddressTypes());
- protected final ConfigurationsFixture configurationsFixture =
- new ConfigurationsFixture(configClient);
-
protected final PatronGroupsFixture patronGroupsFixture
= new PatronGroupsFixture(patronGroupsClient);
@@ -306,6 +303,7 @@ public abstract class APITests {
protected final DepartmentFixture departmentFixture = new DepartmentFixture();
protected final CheckOutLockFixture checkOutLockFixture = new CheckOutLockFixture();
protected final SettingsFixture settingsFixture = new SettingsFixture();
+ protected final SearchInstanceFixture searchFixture = new SearchInstanceFixture();
protected APITests() {
this(true, false);
@@ -452,13 +450,13 @@ protected void mockClockManagerToReturnDefaultDateTime() {
protected void reconfigureTlrFeature(TlrFeatureStatus tlrFeatureStatus) {
if (tlrFeatureStatus == TlrFeatureStatus.ENABLED) {
- configurationsFixture.enableTlrFeature();
+ settingsFixture.enableTlrFeature();
}
else if (tlrFeatureStatus == TlrFeatureStatus.DISABLED) {
- configurationsFixture.disableTlrFeature();
+ settingsFixture.disableTlrFeature();
}
else {
- configurationsFixture.deleteTlrFeatureConfig();
+ settingsFixture.deleteTlrFeatureSettings();
}
}
@@ -474,15 +472,15 @@ protected void reconfigureTlrFeature(TlrFeatureStatus tlrFeatureStatus,
UUID cancellationTemplateId, UUID expirationTemplateId) {
if (tlrFeatureStatus == TlrFeatureStatus.ENABLED) {
- configurationsFixture.configureTlrFeature(true, tlrHoldShouldFollowCirculationRules,
+ settingsFixture.configureTlrFeature(true, tlrHoldShouldFollowCirculationRules,
confirmationTemplateId, cancellationTemplateId, expirationTemplateId);
}
else if (tlrFeatureStatus == TlrFeatureStatus.DISABLED) {
- configurationsFixture.configureTlrFeature(false, tlrHoldShouldFollowCirculationRules,
+ settingsFixture.configureTlrFeature(false, tlrHoldShouldFollowCirculationRules,
confirmationTemplateId, cancellationTemplateId, expirationTemplateId);
}
else {
- configurationsFixture.deleteTlrFeatureConfig();
+ settingsFixture.deleteTlrFeatureSettings();
}
}
diff --git a/src/test/java/api/support/RestAssuredConfiguration.java b/src/test/java/api/support/RestAssuredConfiguration.java
index bae905f073..e14bf8db7f 100644
--- a/src/test/java/api/support/RestAssuredConfiguration.java
+++ b/src/test/java/api/support/RestAssuredConfiguration.java
@@ -27,7 +27,7 @@ public static RequestSpecification standardHeaders(OkapiHeaders okapiHeaders) {
final HashMap headers = new HashMap<>();
headers.put(OKAPI_URL, okapiHeaders.getUrl().toString());
- headers.put(TENANT, okapiHeaders.getTenantId());
+ headers.put(TENANT, APITestContext.getTenantId());
headers.put(TOKEN, okapiHeaders.getToken());
headers.put(REQUEST_ID, okapiHeaders.getRequestId());
headers.put(OKAPI_PERMISSIONS, okapiHeaders.getOkapiPermissions());
diff --git a/src/test/java/api/support/builders/InstanceBuilder.java b/src/test/java/api/support/builders/InstanceBuilder.java
index 2d9c80ca73..b849c16a34 100644
--- a/src/test/java/api/support/builders/InstanceBuilder.java
+++ b/src/test/java/api/support/builders/InstanceBuilder.java
@@ -27,7 +27,7 @@ public InstanceBuilder(String title, UUID instanceTypeId) {
this(UUID.randomUUID(), title, instanceTypeId);
}
- private InstanceBuilder(UUID id, String title, UUID instanceTypeId) {
+ public InstanceBuilder(UUID id, String title, UUID instanceTypeId) {
this(id, title, new JsonArray(), instanceTypeId, Collections.emptyList(), new JsonArray(), new JsonArray());
}
diff --git a/src/test/java/api/support/builders/RequestBuilder.java b/src/test/java/api/support/builders/RequestBuilder.java
index c9eea6fa95..288249e756 100644
--- a/src/test/java/api/support/builders/RequestBuilder.java
+++ b/src/test/java/api/support/builders/RequestBuilder.java
@@ -64,6 +64,7 @@ public class RequestBuilder extends JsonBuilder implements Builder {
private final Tags tags;
private final String patronComments;
private final BlockOverrides blockOverrides;
+ private final String ecsRequestPhase;
private final String itemLocationCode;
private final PrintDetails printDetails;
@@ -94,6 +95,7 @@ public RequestBuilder() {
null,
null,
null,
+ null,
null);
}
@@ -126,6 +128,7 @@ public static RequestBuilder from(IndividualResource response) {
new Tags((toStream(representation.getJsonObject("tags"), "tagList").collect(toList()))),
getProperty(representation, "patronComments"),
null,
+ getProperty(representation, "ecsRequestPhase"),
getProperty(representation, ITEM_LOCATION_CODE),
PrintDetails.fromRepresentation(representation)
);
@@ -199,6 +202,10 @@ public JsonObject create() {
}
}
+ if (ecsRequestPhase != null) {
+ put(request, "ecsRequestPhase", ecsRequestPhase);
+ }
+
if (printDetails != null) {
put(request, "printDetails", printDetails.toJsonObject());
}
diff --git a/src/test/java/api/support/builders/SearchInstanceBuilder.java b/src/test/java/api/support/builders/SearchInstanceBuilder.java
new file mode 100644
index 0000000000..92e67bdeb3
--- /dev/null
+++ b/src/test/java/api/support/builders/SearchInstanceBuilder.java
@@ -0,0 +1,24 @@
+package api.support.builders;
+
+import java.util.List;
+
+import io.vertx.core.json.JsonObject;
+
+public class SearchInstanceBuilder extends JsonBuilder implements Builder {
+
+ private final JsonObject searchInstance;
+
+ public SearchInstanceBuilder(JsonObject searchInstance) {
+ this.searchInstance = searchInstance;
+ }
+
+ @Override
+ public JsonObject create() {
+ return searchInstance;
+ }
+
+ public SearchInstanceBuilder withItems(List items) {
+ put(searchInstance, "items", items);
+ return new SearchInstanceBuilder(searchInstance);
+ }
+}
diff --git a/src/test/java/api/support/builders/ServicePointBuilder.java b/src/test/java/api/support/builders/ServicePointBuilder.java
index e2fb90ec3f..480f354a67 100644
--- a/src/test/java/api/support/builders/ServicePointBuilder.java
+++ b/src/test/java/api/support/builders/ServicePointBuilder.java
@@ -18,6 +18,7 @@ public class ServicePointBuilder extends JsonBuilder implements Builder {
private final JsonObject holdShelfExpiryPeriod;
private final String holdShelfClosedLibraryDateManagement;
+ private final Boolean ecsRequestRouting;
public ServicePointBuilder(
UUID id,
@@ -28,7 +29,8 @@ public ServicePointBuilder(
Integer shelvingLagTime,
Boolean pickupLocation,
JsonObject holdShelfExpiryPeriod,
- String holdShelfClosedLibraryDateManagement) {
+ String holdShelfClosedLibraryDateManagement,
+ Boolean ecsRequestRouting) {
this.id = id;
this.name = name;
this.code = code;
@@ -38,6 +40,7 @@ public ServicePointBuilder(
this.pickupLocation = pickupLocation;
this.holdShelfExpiryPeriod = holdShelfExpiryPeriod;
this.holdShelfClosedLibraryDateManagement = holdShelfClosedLibraryDateManagement;
+ this.ecsRequestRouting = ecsRequestRouting;
}
public ServicePointBuilder(String name, String code, String discoveryDisplayName) {
@@ -50,6 +53,7 @@ public ServicePointBuilder(String name, String code, String discoveryDisplayName
null,
false,
null,
+ null,
null);
}
@@ -64,8 +68,9 @@ public static ServicePointBuilder from(IndividualResource response) {
getIntegerProperty(representation, "shelvingLagTime", null),
getBooleanProperty(representation, "pickupLocation"),
getObjectProperty(representation, "holdShelfExpiryPeriod"),
- getProperty(representation, "holdShelfClosedLibraryDateManagement")
- );
+ getProperty(representation, "holdShelfClosedLibraryDateManagement"),
+ getBooleanProperty(representation, "ecsRequestRouting")
+ );
}
@Override
@@ -80,6 +85,7 @@ public JsonObject create() {
put(servicePoint, "pickupLocation", this.pickupLocation);
put(servicePoint, "holdShelfExpiryPeriod", this.holdShelfExpiryPeriod);
put(servicePoint, "holdShelfClosedLibraryDateManagement", this.holdShelfClosedLibraryDateManagement);
+ put(servicePoint, "ecsRequestRouting", this.ecsRequestRouting);
return servicePoint;
}
@@ -94,7 +100,8 @@ public ServicePointBuilder withId(UUID newId) {
this.shelvingLagTime,
this.pickupLocation,
this.holdShelfExpiryPeriod,
- this.holdShelfClosedLibraryDateManagement);
+ this.holdShelfClosedLibraryDateManagement,
+ this.ecsRequestRouting);
}
public ServicePointBuilder withName(String newName) {
@@ -107,7 +114,8 @@ public ServicePointBuilder withName(String newName) {
this.shelvingLagTime,
this.pickupLocation,
this.holdShelfExpiryPeriod,
- this.holdShelfClosedLibraryDateManagement);
+ this.holdShelfClosedLibraryDateManagement,
+ this.ecsRequestRouting);
}
public ServicePointBuilder withCode(String newCode) {
@@ -120,7 +128,8 @@ public ServicePointBuilder withCode(String newCode) {
this.shelvingLagTime,
this.pickupLocation,
this.holdShelfExpiryPeriod,
- this.holdShelfClosedLibraryDateManagement);
+ this.holdShelfClosedLibraryDateManagement,
+ this.ecsRequestRouting);
}
public ServicePointBuilder withDiscoveryDisplayName(String newDiscoveryDisplayName) {
@@ -133,7 +142,8 @@ public ServicePointBuilder withDiscoveryDisplayName(String newDiscoveryDisplayNa
this.shelvingLagTime,
this.pickupLocation,
this.holdShelfExpiryPeriod,
- this.holdShelfClosedLibraryDateManagement);
+ this.holdShelfClosedLibraryDateManagement,
+ this.ecsRequestRouting);
}
public ServicePointBuilder withDescription(String newDescription) {
@@ -146,7 +156,8 @@ public ServicePointBuilder withDescription(String newDescription) {
this.shelvingLagTime,
this.pickupLocation,
this.holdShelfExpiryPeriod,
- this.holdShelfClosedLibraryDateManagement);
+ this.holdShelfClosedLibraryDateManagement,
+ this.ecsRequestRouting);
}
public ServicePointBuilder withShelvingLagTime(Integer newShelvingLagTime) {
@@ -159,7 +170,8 @@ public ServicePointBuilder withShelvingLagTime(Integer newShelvingLagTime) {
newShelvingLagTime,
this.pickupLocation,
this.holdShelfExpiryPeriod,
- this.holdShelfClosedLibraryDateManagement);
+ this.holdShelfClosedLibraryDateManagement,
+ this.ecsRequestRouting);
}
public ServicePointBuilder withPickupLocation(Boolean newPickupLocation) {
@@ -172,7 +184,8 @@ public ServicePointBuilder withPickupLocation(Boolean newPickupLocation) {
this.shelvingLagTime,
newPickupLocation,
this.holdShelfExpiryPeriod,
- this.holdShelfClosedLibraryDateManagement);
+ this.holdShelfClosedLibraryDateManagement,
+ this.ecsRequestRouting);
}
public ServicePointBuilder withHoldShelfExpriyPeriod(int duration, String intervalId) {
@@ -190,7 +203,8 @@ public ServicePointBuilder withHoldShelfExpriyPeriod(int duration, String interv
this.shelvingLagTime,
this.pickupLocation,
holdShelfExpiryPeriod,
- this.holdShelfClosedLibraryDateManagement);
+ this.holdShelfClosedLibraryDateManagement,
+ this.ecsRequestRouting);
}
public ServicePointBuilder withholdShelfClosedLibraryDateManagement(String expirationDateManagement) {
@@ -203,6 +217,21 @@ public ServicePointBuilder withholdShelfClosedLibraryDateManagement(String expir
this.shelvingLagTime,
this.pickupLocation,
this.holdShelfExpiryPeriod,
- expirationDateManagement);
+ expirationDateManagement,
+ this.ecsRequestRouting);
+ }
+
+ public ServicePointBuilder withEcsRequestRouting(Boolean ecsRequestRouting) {
+ return new ServicePointBuilder(
+ this.id,
+ this.name,
+ this.code,
+ this.discoveryDisplayName,
+ this.description,
+ this.shelvingLagTime,
+ this.pickupLocation,
+ this.holdShelfExpiryPeriod,
+ this.holdShelfClosedLibraryDateManagement,
+ ecsRequestRouting);
}
}
diff --git a/src/test/java/api/support/builders/SettingsBuilder.java b/src/test/java/api/support/builders/SettingsBuilder.java
index dd954c07aa..380b52fef2 100644
--- a/src/test/java/api/support/builders/SettingsBuilder.java
+++ b/src/test/java/api/support/builders/SettingsBuilder.java
@@ -8,7 +8,7 @@ public class SettingsBuilder implements Builder {
private final JsonObject representation;
- public SettingsBuilder(UUID id, String scope, String key, String value) {
+ public SettingsBuilder(UUID id, String scope, String key, Object value) {
this.representation = new JsonObject()
.put("id", id)
.put("scope", scope)
diff --git a/src/test/java/api/support/fakes/FakeCQLToJSONInterpreter.java b/src/test/java/api/support/fakes/FakeCQLToJSONInterpreter.java
index 5f9808ea09..8abd49675e 100644
--- a/src/test/java/api/support/fakes/FakeCQLToJSONInterpreter.java
+++ b/src/test/java/api/support/fakes/FakeCQLToJSONInterpreter.java
@@ -14,13 +14,34 @@
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.folio.circulation.support.http.server.WebContext;
import io.vertx.core.json.JsonObject;
public class FakeCQLToJSONInterpreter {
private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
+ public List execute(Collection records, String query,
+ WebContext context) {
+
+ var initiallyFilteredRecords = execute(records, query);
+
+ // Routing SP filtering
+ String includeRoutingServicePointsParam = context.getStringParameter(
+ "includeRoutingServicePoints");
+ if (Boolean.parseBoolean(includeRoutingServicePointsParam)) {
+ return records.stream()
+ .filter(json -> json.containsKey("ecsRequestRouting")
+ ? json.getBoolean("ecsRequestRouting")
+ : false)
+ .toList();
+ }
+
+ return initiallyFilteredRecords;
+ }
+
public List execute(Collection records, String query) {
+
final var queryAndSort = splitQueryAndSort(query);
final var cqlPredicate = new CqlPredicate(queryAndSort.left);
diff --git a/src/test/java/api/support/fakes/FakeOkapi.java b/src/test/java/api/support/fakes/FakeOkapi.java
index 1ed5b8f5b0..5a6756dabd 100644
--- a/src/test/java/api/support/fakes/FakeOkapi.java
+++ b/src/test/java/api/support/fakes/FakeOkapi.java
@@ -23,11 +23,11 @@
import java.util.Objects;
import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.folio.circulation.support.ValidationErrorFailure;
import org.folio.circulation.support.http.client.OkapiHttpClient;
import org.folio.circulation.support.results.Result;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
@@ -231,6 +231,12 @@ public void start(Promise startFuture) throws IOException {
FakeCalendarOkapi.registerCalendarSurroundingDates(router);
registerFakeStorageLoansAnonymize(router);
+ new FakeSearchModule().register(router);
+ new FakeStorageModuleBuilder()
+ .withRecordName(FakeSearchModule.RECORD_TYPE_NAME)
+ .withRootPath("/search/instances")
+ .create().register(router);
+
new FakeStorageModuleBuilder()
.withRecordName("institution")
.withRootPath("/location-units/institutions")
@@ -276,6 +282,7 @@ public void start(Promise startFuture) throws IOException {
.withRootPath("/service-points")
.withRequiredProperties("name", "code", "discoveryDisplayName")
.withUniqueProperties("name")
+ .withQueryParameters("includeRoutingServicePoints")
.withChangeMetadata()
.disallowCollectionDelete()
.create()
@@ -406,7 +413,6 @@ public void start(Promise startFuture) throws IOException {
.withRootPath("/settings/entries")
.withCollectionPropertyName("items")
.withChangeMetadata()
- .withRecordConstraint(this::userHasAlreadyAcquiredLock)
.create().register(router);
new FakeStorageModuleBuilder()
diff --git a/src/test/java/api/support/fakes/FakeSearchModule.java b/src/test/java/api/support/fakes/FakeSearchModule.java
new file mode 100644
index 0000000000..e4c4eafd45
--- /dev/null
+++ b/src/test/java/api/support/fakes/FakeSearchModule.java
@@ -0,0 +1,103 @@
+package api.support.fakes;
+
+import static java.lang.String.format;
+import static org.folio.circulation.support.results.CommonFailures.failedDueToServerError;
+
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.folio.circulation.support.http.server.ClientErrorResponse;
+import org.folio.circulation.support.http.server.WebContext;
+import org.folio.circulation.support.results.Result;
+
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpServerResponse;
+import io.vertx.core.json.Json;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.RoutingContext;
+import lombok.SneakyThrows;
+
+public class FakeSearchModule {
+
+ private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
+ public static final String RECORD_TYPE_NAME = "search-instance";
+ private static final String ID_REGEXP = "id\\s*(==|!=|>|>=|<|<=|\\|=|\\|=)\\s*" +
+ "([a-f0-9\\-]+)";
+ private final Storage storage;
+ private final Pattern idPattern;
+ private static final String ROOT_PATH = "/search/instances";
+
+ public FakeSearchModule() {
+ this.idPattern = Pattern.compile(ID_REGEXP);
+ this.storage = Storage.getStorage();
+ }
+
+ @SneakyThrows
+ public void register(Router router) {
+ router.get("/search/instances").handler(this::getById);
+ }
+
+ private void getById(RoutingContext routingContext) {
+ WebContext context = new WebContext(routingContext);
+
+ Result idParsingResult = getIdParameter(routingContext);
+
+ if (idParsingResult.failed()) {
+ idParsingResult.cause().writeTo(routingContext.response());
+ return;
+ }
+
+ Map resourcesForTenant = getResourcesForTenant(context);
+
+ final String id = idParsingResult.value().toString();
+
+ if (resourcesForTenant.containsKey(id)) {
+ final JsonObject resourceRepresentation = resourcesForTenant.get(id);
+
+ final JsonObject searchResult = new JsonObject();
+
+ searchResult.put("totalRecords", 1);
+ searchResult.put("instances", List.of(resourceRepresentation));
+
+ log.debug("Found {} resource: {}", RECORD_TYPE_NAME,
+ searchResult.encodePrettily());
+
+ HttpServerResponse response = routingContext.response();
+ Buffer buffer = Buffer.buffer(Json.encodePrettily(searchResult), "UTF-8");
+
+ response.setStatusCode(200);
+ response.putHeader("content-type", "application/json; charset=utf-8");
+ response.putHeader("content-length", Integer.toString(buffer.length()));
+
+ response.write(buffer);
+ response.end();
+ }
+ else {
+ log.debug("Failed to find {} resource: {}", RECORD_TYPE_NAME,
+ idParsingResult);
+
+ ClientErrorResponse.notFound(routingContext.response());
+ }
+ }
+
+ private Result getIdParameter(RoutingContext routingContext) {
+ final String query = routingContext.request().getParam("query");
+ Matcher matcher = idPattern.matcher(query);
+ String id = matcher.find() ? matcher.group(2) : null;
+
+ return Result.of(() -> UUID.fromString(id))
+ .mapFailure(r -> failedDueToServerError(format(
+ "ID parameter \"%s\" is not a valid UUID", id)));
+ }
+
+ private Map getResourcesForTenant(WebContext context) {
+ return storage.getTenantResources(ROOT_PATH, context.getTenantId());
+ }
+}
diff --git a/src/test/java/api/support/fakes/FakeStorageModule.java b/src/test/java/api/support/fakes/FakeStorageModule.java
index 2224d6b95a..882be300aa 100644
--- a/src/test/java/api/support/fakes/FakeStorageModule.java
+++ b/src/test/java/api/support/fakes/FakeStorageModule.java
@@ -320,7 +320,7 @@ private void getById(RoutingContext routingContext) {
Result idParsingResult = getIdParameter(routingContext);
- if(idParsingResult.failed()) {
+ if (idParsingResult.failed()) {
idParsingResult.cause().writeTo(routingContext.response());
return;
}
@@ -329,7 +329,7 @@ private void getById(RoutingContext routingContext) {
final String id = idParsingResult.value().toString();
- if(resourcesForTenant.containsKey(id)) {
+ if (resourcesForTenant.containsKey(id)) {
final JsonObject resourceRepresentation = resourcesForTenant.get(id);
log.debug("Found {} resource: {}", recordTypeName,
@@ -378,8 +378,8 @@ private void getMany(RoutingContext routingContext) {
Map resourcesForTenant = getResourcesForTenant(context);
- List filteredItems = new FakeCQLToJSONInterpreter()
- .execute(resourcesForTenant.values(), query);
+ List filteredItems = getFakeCQLToJSONInterpreter()
+ .execute(resourcesForTenant.values(), query, context);
List pagedItems = filteredItems.stream()
.skip(offset)
@@ -410,6 +410,10 @@ private void getMany(RoutingContext routingContext) {
response.end();
}
+ FakeCQLToJSONInterpreter getFakeCQLToJSONInterpreter() {
+ return new FakeCQLToJSONInterpreter();
+ }
+
private void empty(RoutingContext routingContext) {
WebContext context = new WebContext(routingContext);
@@ -432,7 +436,7 @@ private void deleteMany(RoutingContext routingContext) {
Map resourcesForTenant = getResourcesForTenant(context);
- new FakeCQLToJSONInterpreter()
+ getFakeCQLToJSONInterpreter()
.execute(resourcesForTenant.values(), query)
.forEach(item -> resourcesForTenant.remove(item.getString("id")));
@@ -446,7 +450,7 @@ private void delete(RoutingContext routingContext) {
Map resourcesForTenant = getResourcesForTenant(context);
- if(resourcesForTenant.containsKey(id)) {
+ if (resourcesForTenant.containsKey(id)) {
resourcesForTenant.remove(id);
noContent().writeTo(routingContext.response());
@@ -633,7 +637,8 @@ private void checkForUnexpectedQueryParameters(RoutingContext routingContext) {
boolean isValidParameter = queryParameter.contains("query") ||
queryParameter.contains("offset") ||
isContainsQueryParameter(queryParameter) ||
- queryParameter.contains("limit");
+ queryParameter.contains("limit") ||
+ queryParameter.contains("expandAll");
return !isValidParameter;
})
diff --git a/src/test/java/api/support/fixtures/CirculationItemsFixture.java b/src/test/java/api/support/fixtures/CirculationItemsFixture.java
index 2444d9d89e..74c248a9c4 100644
--- a/src/test/java/api/support/fixtures/CirculationItemsFixture.java
+++ b/src/test/java/api/support/fixtures/CirculationItemsFixture.java
@@ -21,9 +21,18 @@ public CirculationItemsFixture(
}
public IndividualResource createCirculationItem(String barcode, UUID holdingId, UUID locationId, String instanceTitle) {
- CirculationItemsBuilder circulationItemsBuilder = new CirculationItemsBuilder().withBarcode(barcode).withHoldingId(holdingId)
- .withLoanType(loanTypesFixture.canCirculate().getId()).withMaterialType(materialTypesFixture.book().getId())
- .withLocationId(locationId).withInstanceTitle(instanceTitle);
+ return createCirculationItem(UUID.randomUUID(), barcode, holdingId, locationId, instanceTitle);
+ }
+
+ public IndividualResource createCirculationItem(UUID itemId, String barcode, UUID holdingId, UUID locationId, String instanceTitle) {
+ CirculationItemsBuilder circulationItemsBuilder = new CirculationItemsBuilder()
+ .withItemId(itemId)
+ .withBarcode(barcode)
+ .withHoldingId(holdingId)
+ .withLoanType(loanTypesFixture.canCirculate().getId())
+ .withMaterialType(materialTypesFixture.book().getId())
+ .withLocationId(locationId)
+ .withInstanceTitle(instanceTitle);
return circulationItemClient.create(circulationItemsBuilder);
}
diff --git a/src/test/java/api/support/fixtures/ConfigurationExample.java b/src/test/java/api/support/fixtures/ConfigurationExample.java
index 5f7b9c7011..e06ac904c9 100644
--- a/src/test/java/api/support/fixtures/ConfigurationExample.java
+++ b/src/test/java/api/support/fixtures/ConfigurationExample.java
@@ -2,10 +2,7 @@
import static org.folio.circulation.support.json.JsonPropertyWriter.write;
-import java.util.UUID;
-
import api.support.builders.ConfigRecordBuilder;
-import api.support.builders.TlrSettingsConfigurationBuilder;
import io.vertx.core.json.JsonObject;
public class ConfigurationExample {
@@ -40,37 +37,6 @@ public static ConfigRecordBuilder schedulerNoticesLimitConfiguration(String limi
DEFAULT_NOTIFICATION_SCHEDULER_CONFIG_NAME, limit);
}
- public static ConfigRecordBuilder tlrFeatureEnabled() {
- return new ConfigRecordBuilder("SETTINGS", "TLR",
- new TlrSettingsConfigurationBuilder()
- .withTitleLevelRequestsFeatureEnabled(true)
- .create()
- .encodePrettily());
- }
-
- public static ConfigRecordBuilder tlrFeatureDisabled() {
- return new ConfigRecordBuilder("SETTINGS", "TLR",
- new TlrSettingsConfigurationBuilder()
- .withTitleLevelRequestsFeatureEnabled(false)
- .create()
- .encodePrettily());
- }
-
- public static ConfigRecordBuilder tlrFeatureConfiguration(boolean isTlrEnabled,
- boolean holdShouldFollowCirculationRules, UUID confirmationTemplateId,
- UUID cancellationTemplateId, UUID expirationTemplateId) {
-
- return new ConfigRecordBuilder("SETTINGS", "TLR",
- new TlrSettingsConfigurationBuilder()
- .withTitleLevelRequestsFeatureEnabled(isTlrEnabled)
- .withTlrHoldShouldFollowCirculationRules(holdShouldFollowCirculationRules)
- .withConfirmationPatronNoticeTemplateId(confirmationTemplateId)
- .withCancellationPatronNoticeTemplateId(cancellationTemplateId)
- .withExpirationPatronNoticeTemplateId(expirationTemplateId)
- .create()
- .encodePrettily());
- }
-
private static JsonObject combinedTimeZoneConfig(String timezone) {
final JsonObject encodedValue = new JsonObject();
write(encodedValue, "locale", US_LOCALE);
diff --git a/src/test/java/api/support/fixtures/ConfigurationsFixture.java b/src/test/java/api/support/fixtures/ConfigurationsFixture.java
deleted file mode 100644
index f42e903052..0000000000
--- a/src/test/java/api/support/fixtures/ConfigurationsFixture.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package api.support.fixtures;
-
-import java.util.UUID;
-
-import api.support.http.ResourceClient;
-
-public class ConfigurationsFixture {
- private final ResourceClient client;
- private UUID tlrConfigurationEntryId = null;
-
- public ConfigurationsFixture(ResourceClient client) {
- this.client = client;
- }
-
- public void enableTlrFeature() {
- deleteTlrFeatureConfig();
- tlrConfigurationEntryId = client.create(ConfigurationExample.tlrFeatureEnabled()).getId();
- }
-
- public void disableTlrFeature() {
- deleteTlrFeatureConfig();
- tlrConfigurationEntryId = client.create(ConfigurationExample.tlrFeatureDisabled()).getId();
- }
-
- public void deleteTlrFeatureConfig() {
- if (tlrConfigurationEntryId != null) {
- client.delete(tlrConfigurationEntryId);
- tlrConfigurationEntryId = null;
- }
- }
-
- public void configureTlrFeature(boolean isTlrFeatureEnabled, boolean tlrHoldShouldFollowCirculationRules,
- UUID confirmationTemplateId, UUID cancellationTemplateId, UUID expirationTemplateId) {
-
- deleteTlrFeatureConfig();
- tlrConfigurationEntryId = client.create(ConfigurationExample.tlrFeatureConfiguration(
- isTlrFeatureEnabled, tlrHoldShouldFollowCirculationRules, confirmationTemplateId,
- cancellationTemplateId, expirationTemplateId))
- .getId();
- }
-}
diff --git a/src/test/java/api/support/fixtures/SearchInstanceFixture.java b/src/test/java/api/support/fixtures/SearchInstanceFixture.java
new file mode 100644
index 0000000000..e1bb144753
--- /dev/null
+++ b/src/test/java/api/support/fixtures/SearchInstanceFixture.java
@@ -0,0 +1,26 @@
+package api.support.fixtures;
+
+import java.util.List;
+import java.util.UUID;
+
+import api.support.builders.InstanceBuilder;
+import api.support.builders.SearchInstanceBuilder;
+import api.support.http.ItemResource;
+import api.support.http.ResourceClient;
+
+public class SearchInstanceFixture {
+
+ private final ResourceClient searchClient;
+
+ public SearchInstanceFixture() {
+ this.searchClient = ResourceClient.forSearchClient();
+ }
+
+ public void basedUponDunkirk(UUID instanceId, ItemResource itemResource) {
+ SearchInstanceBuilder builder = new SearchInstanceBuilder(
+ new InstanceBuilder(
+ "Dunkirk", UUID.randomUUID()).withId(instanceId).create())
+ .withItems(List.of(itemResource.getJson()));
+ searchClient.create(builder);
+ }
+}
diff --git a/src/test/java/api/support/fixtures/ServicePointExamples.java b/src/test/java/api/support/fixtures/ServicePointExamples.java
index 05dc048a41..3267a39341 100644
--- a/src/test/java/api/support/fixtures/ServicePointExamples.java
+++ b/src/test/java/api/support/fixtures/ServicePointExamples.java
@@ -79,4 +79,12 @@ static ServicePointBuilder basedUponCircDesk10() {
.withholdShelfClosedLibraryDateManagement(ExpirationDateManagement.MOVE_TO_THE_END_OF_THE_NEXT_OPEN_DAY.name())
.withHoldShelfExpriyPeriod(6, "Months");
}
+
+ static ServicePointBuilder basedUponCircDesk11() {
+ return new ServicePointBuilder("Circ Desk 11", "cd11",
+ "Circulation Desk -- Back Entrance")
+ .withPickupLocation(FALSE)
+ .withEcsRequestRouting(TRUE)
+ .withHoldShelfExpriyPeriod(6, "Months");
+ }
}
diff --git a/src/test/java/api/support/fixtures/ServicePointsFixture.java b/src/test/java/api/support/fixtures/ServicePointsFixture.java
index 52df0996c4..7cb92a7886 100644
--- a/src/test/java/api/support/fixtures/ServicePointsFixture.java
+++ b/src/test/java/api/support/fixtures/ServicePointsFixture.java
@@ -2,6 +2,7 @@
import static api.support.fixtures.ServicePointExamples.basedUponCircDesk1;
import static api.support.fixtures.ServicePointExamples.basedUponCircDesk10;
+import static api.support.fixtures.ServicePointExamples.basedUponCircDesk11;
import static api.support.fixtures.ServicePointExamples.basedUponCircDesk2;
import static api.support.fixtures.ServicePointExamples.basedUponCircDesk3;
import static api.support.fixtures.ServicePointExamples.basedUponCircDesk4;
@@ -81,6 +82,11 @@ public IndividualResource cd10() {
return create(basedUponCircDesk10());
}
+ public IndividualResource cd11() {
+
+ return create(basedUponCircDesk11());
+ }
+
public IndividualResource create(ServicePointBuilder builder) {
return servicePointRecordCreator.createIfAbsent(builder);
diff --git a/src/test/java/api/support/fixtures/SettingsFixture.java b/src/test/java/api/support/fixtures/SettingsFixture.java
index 23166fcf83..41220f073d 100644
--- a/src/test/java/api/support/fixtures/SettingsFixture.java
+++ b/src/test/java/api/support/fixtures/SettingsFixture.java
@@ -1,12 +1,15 @@
package api.support.fixtures;
+import java.util.List;
+import java.util.UUID;
+
import api.support.builders.SettingsBuilder;
import api.support.http.ResourceClient;
import io.vertx.core.json.JsonObject;
-import java.util.UUID;
-
public class SettingsFixture {
+ private static final UUID GENERAL_TLR_SETTINGS_ID = UUID.randomUUID();
+ private static final UUID REGULAR_TLR_SETTINGS_ID = UUID.randomUUID();
private final ResourceClient settingsClient;
@@ -27,4 +30,58 @@ private SettingsBuilder buildCheckoutLockFeatureSettings(boolean checkoutFeature
.encodePrettily()
);
}
+
+ public void enableTlrFeature() {
+ createGeneralTlrSettings(true, false);
+ }
+
+ public void disableTlrFeature() {
+ createGeneralTlrSettings(false, false);
+ }
+
+ public void deleteTlrFeatureSettings() {
+ settingsClient.delete(GENERAL_TLR_SETTINGS_ID);
+ settingsClient.delete(REGULAR_TLR_SETTINGS_ID);
+ }
+
+ public void configureTlrFeature(boolean isTlrFeatureEnabled, boolean tlrHoldShouldFollowCirculationRules,
+ UUID confirmationTemplateId, UUID cancellationTemplateId, UUID expirationTemplateId) {
+
+ deleteTlrFeatureSettings();
+ createGeneralTlrSettings(isTlrFeatureEnabled, tlrHoldShouldFollowCirculationRules);
+ createRegularTlrSettings(confirmationTemplateId, cancellationTemplateId, expirationTemplateId);
+ }
+
+ private void createGeneralTlrSettings(boolean isTlrFeatureEnabled,
+ boolean tlrHoldShouldFollowCirculationRules) {
+
+ JsonObject value = new JsonObject()
+ .put("titleLevelRequestsFeatureEnabled", isTlrFeatureEnabled)
+ .put("createTitleLevelRequestsByDefault", false)
+ .put("tlrHoldShouldFollowCirculationRules", tlrHoldShouldFollowCirculationRules);
+
+ SettingsBuilder builder = new SettingsBuilder(GENERAL_TLR_SETTINGS_ID, "circulation",
+ "generalTlr", value);
+
+ settingsClient.create(builder);
+ }
+
+ private void createRegularTlrSettings(UUID confirmationTemplateId, UUID cancellationTemplateId,
+ UUID expirationTemplateId) {
+
+ JsonObject regularTlrValue = new JsonObject()
+ .put("cancellationPatronNoticeTemplateId", cancellationTemplateId)
+ .put("confirmationPatronNoticeTemplateId", confirmationTemplateId)
+ .put("expirationPatronNoticeTemplateId", expirationTemplateId);
+
+ SettingsBuilder builder = new SettingsBuilder(REGULAR_TLR_SETTINGS_ID, "circulation",
+ "regularTlr", regularTlrValue);
+
+ settingsClient.create(builder);
+ }
+
+ public List getAll() {
+ return settingsClient.getAll();
+ }
+
}
diff --git a/src/test/java/api/support/http/InterfaceUrls.java b/src/test/java/api/support/http/InterfaceUrls.java
index 23c5878ecc..c2f012900c 100644
--- a/src/test/java/api/support/http/InterfaceUrls.java
+++ b/src/test/java/api/support/http/InterfaceUrls.java
@@ -334,6 +334,13 @@ public static URL settingsStorageUrl() {
return APITestContext.viaOkapiModuleUrl("/settings/entries");
}
+ public static URL searchUrl(String subPath) {
+ return APITestContext.viaOkapiModuleUrl("/search/instances" + subPath);
+ }
+
+ public static URL itemsByInstanceUrl(String subPath) {
+ return circulationModuleUrl("/circulation/items-by-instance?" + subPath);
+ }
public static URL circulationSettingsUrl(String subPath) {
return circulationModuleUrl("/circulation/settings" + subPath);
}
diff --git a/src/test/java/api/support/http/ResourceClient.java b/src/test/java/api/support/http/ResourceClient.java
index 73893b3b33..47469e8813 100644
--- a/src/test/java/api/support/http/ResourceClient.java
+++ b/src/test/java/api/support/http/ResourceClient.java
@@ -272,6 +272,10 @@ public static ResourceClient forActualCostRecordsStorage() {
return new ResourceClient(InterfaceUrls::actualCostRecordsStorageUrl, "actualCostRecords");
}
+ public static ResourceClient forSearchClient() {
+ return new ResourceClient(InterfaceUrls::searchUrl, "instances");
+ }
+
public static ResourceClient forCirculationSettings() {
return new ResourceClient(InterfaceUrls::circulationSettingsUrl, "circulationSettings");
}
diff --git a/src/test/java/api/support/matchers/ValidationErrorMatchers.java b/src/test/java/api/support/matchers/ValidationErrorMatchers.java
index 36796055b6..5c197e4a14 100644
--- a/src/test/java/api/support/matchers/ValidationErrorMatchers.java
+++ b/src/test/java/api/support/matchers/ValidationErrorMatchers.java
@@ -238,7 +238,7 @@ public static Matcher isInsufficientPermissionsError(
public static Matcher isInsufficientPermissionsToOverridePatronBlockError() {
return isInsufficientPermissionsError("patronBlock",
- List.of("circulation.override-patron-block"));
+ List.of("circulation.override-patron-block.post"));
}
}
diff --git a/src/test/java/api/support/utl/BlockOverridesUtils.java b/src/test/java/api/support/utl/BlockOverridesUtils.java
index 805b26ff2e..76f9121cfb 100644
--- a/src/test/java/api/support/utl/BlockOverridesUtils.java
+++ b/src/test/java/api/support/utl/BlockOverridesUtils.java
@@ -13,8 +13,8 @@
import io.vertx.core.json.JsonObject;
public final class BlockOverridesUtils {
- public static final String OVERRIDE_RENEWAL_PERMISSION = "circulation.override-renewal-block";
- public static final String OVERRIDE_PATRON_BLOCK_PERMISSION = "circulation.override-patron-block";
+ public static final String OVERRIDE_RENEWAL_PERMISSION = "circulation.override-renewal-block.post";
+ public static final String OVERRIDE_PATRON_BLOCK_PERMISSION = "circulation.override-patron-block.post";
private static final String OVERRIDABLE_BLOCK = "overridableBlock";
public static List getMissingPermissions(Response response) {
diff --git a/src/test/java/org/folio/circulation/infrastructure/storage/SettingsRepositoryTest.java b/src/test/java/org/folio/circulation/infrastructure/storage/SettingsRepositoryTest.java
index f3c7c60c92..ff17621af8 100644
--- a/src/test/java/org/folio/circulation/infrastructure/storage/SettingsRepositoryTest.java
+++ b/src/test/java/org/folio/circulation/infrastructure/storage/SettingsRepositoryTest.java
@@ -2,6 +2,9 @@
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
+import lombok.SneakyThrows;
+
+import org.folio.circulation.domain.configuration.TlrSettingsConfiguration;
import org.folio.circulation.support.Clients;
import org.folio.circulation.support.CollectionResourceClient;
import org.folio.circulation.support.ServerErrorFailure;
@@ -13,11 +16,16 @@
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import static org.folio.circulation.support.results.Result.ofAsync;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
class SettingsRepositoryTest {
@@ -58,6 +66,93 @@ void testFetchSettingsWhenSettingsApiThrowError() throws ExecutionException, Int
assertFalse(res.isCheckOutLockFeatureEnabled());
}
+ @Test
+ @SneakyThrows
+ void fetchTlrSettings() {
+ Clients clients = mock(Clients.class);
+ CollectionResourceClient settingsClient = mock(CollectionResourceClient.class);
+ CollectionResourceClient configurationClient = mock(CollectionResourceClient.class);
+
+ JsonObject mockSettingsResponse = new JsonObject()
+ .put("items", new JsonArray()
+ .add(new JsonObject()
+ .put("id", UUID.randomUUID().toString())
+ .put("scope", "circulation")
+ .put("key", "generalTlr")
+ .put("value", new JsonObject()
+ .put("titleLevelRequestsFeatureEnabled", true)
+ .put("createTitleLevelRequestsByDefault", true)
+ .put("tlrHoldShouldFollowCirculationRules", true))))
+ .put("resultInfo", new JsonObject()
+ .put("totalRecords", 0)
+ .put("diagnostics", new JsonArray()));
+
+ when(clients.settingsStorageClient()).thenReturn(settingsClient);
+ when(clients.configurationStorageClient()).thenReturn(configurationClient);
+ when(settingsClient.getMany(any(), any()))
+ .thenReturn(ofAsync(new Response(200, mockSettingsResponse.encode(), "application/json")));
+
+ TlrSettingsConfiguration actualResult = new SettingsRepository(clients)
+ .lookupTlrSettings()
+ .get(30, TimeUnit.SECONDS)
+ .value();
+
+ assertEquals(new TlrSettingsConfiguration(true, true, true, null, null, null), actualResult);
+ verify(settingsClient).getMany(any(), any());
+ verifyNoInteractions(configurationClient);
+ }
+
+ @Test
+ @SneakyThrows
+ void fallBackToLegacyConfigurationWhenTlrSettingsAreNotFound() {
+ Clients clients = mock(Clients.class);
+ CollectionResourceClient settingsClient = mock(CollectionResourceClient.class);
+ CollectionResourceClient configurationClient = mock(CollectionResourceClient.class);
+
+ JsonObject mockEmptySettingsResponse = new JsonObject()
+ .put("items", new JsonArray())
+ .put("resultInfo", new JsonObject()
+ .put("totalRecords", 0)
+ .put("diagnostics", new JsonArray()));
+
+ JsonObject mockConfigurationResponse = new JsonObject()
+ .put("configs", new JsonArray().add(
+ new JsonObject()
+ .put("id", UUID.randomUUID().toString())
+ .put("module", "SETTINGS")
+ .put("configName", "TLR")
+ .put("enabled", true)
+ .put("value", new JsonObject()
+ .put("titleLevelRequestsFeatureEnabled", true)
+ .put("createTitleLevelRequestsByDefault", true)
+ .put("tlrHoldShouldFollowCirculationRules", true)
+ .put("confirmationPatronNoticeTemplateId", null)
+ .put("cancellationPatronNoticeTemplateId", null)
+ .put("expirationPatronNoticeTemplateId", null)
+ .encode())))
+ .put("totalRecords", 1)
+ .put("resultInfo", new JsonObject()
+ .put("totalRecords", 1)
+ .put("facets", new JsonArray())
+ .put("diagnostics", new JsonArray()));
+
+ when(clients.settingsStorageClient()).thenReturn(settingsClient);
+ when(clients.configurationStorageClient()).thenReturn(configurationClient);
+ when(settingsClient.getMany(any(), any()))
+ .thenReturn(ofAsync(new Response(200, mockEmptySettingsResponse.encode(), "application/json")));
+ when(configurationClient.getMany(any(), any()))
+ .thenReturn(ofAsync(new Response(200, mockConfigurationResponse.encode(), "application/json")));
+
+ TlrSettingsConfiguration actualResult = new SettingsRepository(clients)
+ .lookupTlrSettings()
+ .get(30, TimeUnit.SECONDS)
+ .value();
+
+ assertEquals(new TlrSettingsConfiguration(true, true, true, null, null, null), actualResult);
+ verify(settingsClient).getMany(any(), any());
+ verify(configurationClient).getMany(any(), any());
+ }
+
private JsonObject createCheckoutLockJsonResponse(boolean checkoutFeatureFlag) {
JsonObject checkoutLockResponseJson = new JsonObject();
checkoutLockResponseJson.put("id", UUID.randomUUID())
diff --git a/src/test/java/org/folio/circulation/support/http/client/CqlQueryTests.java b/src/test/java/org/folio/circulation/support/http/client/CqlQueryTests.java
index 349b294343..0ddc0e11a7 100644
--- a/src/test/java/org/folio/circulation/support/http/client/CqlQueryTests.java
+++ b/src/test/java/org/folio/circulation/support/http/client/CqlQueryTests.java
@@ -2,6 +2,7 @@
import static java.lang.String.format;
import static java.util.Arrays.asList;
+import static java.util.Collections.emptyMap;
import static org.folio.circulation.support.CqlSortBy.ascending;
import static org.folio.circulation.support.http.client.CqlQuery.exactMatch;
import static org.folio.circulation.support.http.client.CqlQuery.exactMatchAny;
@@ -12,15 +13,20 @@
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
import java.time.ZonedDateTime;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
import org.folio.circulation.support.CqlSortBy;
import org.folio.circulation.support.CqlSortClause;
import org.folio.circulation.support.ServerErrorFailure;
import org.folio.circulation.support.results.Result;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
class CqlQueryTests {
@Test
@@ -153,4 +159,32 @@ void shouldBeEqualToQueryWithSameDefinition() {
assertThat(firstQuery.equals(secondQuery), is(true));
}
+
+ @ParameterizedTest
+ @CsvSource(nullValues={"null"}, value = {
+ "index1, value1, index2, value2, (index1==\"value1\" or index2==\"value2\")",
+ "null, null, index2, value2, (index2==\"value2\")",
+ "index1, value1, null, null, (index1==\"value1\")",
+ })
+ void canMatchByAnyIndex(String index1, String value1, String index2, String value2,
+ String expectedResult) {
+
+ Map filters = new HashMap<>();
+ filters.put(index1, value1);
+ filters.put(index2, value2);
+
+ String actualResult = exactMatchAny(filters)
+ .value()
+ .asText();
+ assertThat(actualResult, equalTo(expectedResult));
+ }
+
+ @Test
+ void exactMatchAnyFailsWhenListOfIndicesIsEmpty() {
+ Result result = exactMatchAny(emptyMap());
+ assertThat("Failed result expected", result.failed());
+ assertThat(result.cause(), instanceOf(ServerErrorFailure.class));
+ assertThat(((ServerErrorFailure) result.cause()).getReason(),
+ equalTo("Cannot generate empty CQL query"));
+ }
}