invenio-app-ils 4.5.0__py2.py3-none-any.whl → 5.0.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- invenio_app_ils/__init__.py +1 -1
- invenio_app_ils/assets/semantic-ui/less/theme.config +103 -0
- invenio_app_ils/circulation/api.py +9 -5
- invenio_app_ils/circulation/config.py +1 -0
- invenio_app_ils/circulation/loaders/schemas/json/loan_checkout.py +1 -1
- invenio_app_ils/circulation/loaders/schemas/json/loan_request.py +2 -2
- invenio_app_ils/circulation/notifications/api.py +9 -1
- invenio_app_ils/circulation/notifications/messages.py +2 -1
- invenio_app_ils/circulation/templates/invenio_app_ils_circulation/notifications/update_dates.html +19 -0
- invenio_app_ils/cli.py +92 -64
- invenio_app_ils/config.py +10 -2
- invenio_app_ils/documents/loaders/jsonschemas/document.py +1 -1
- invenio_app_ils/eitems/loaders/jsonschemas/eitems.py +2 -2
- invenio_app_ils/patrons/anonymization.py +0 -4
- invenio_app_ils/series/loaders/jsonschemas/series.py +1 -1
- invenio_app_ils/webpack.py +27 -0
- {invenio_app_ils-4.5.0.dist-info → invenio_app_ils-5.0.0.dist-info}/METADATA +56 -43
- {invenio_app_ils-4.5.0.dist-info → invenio_app_ils-5.0.0.dist-info}/RECORD +24 -112
- {invenio_app_ils-4.5.0.dist-info → invenio_app_ils-5.0.0.dist-info}/entry_points.txt +3 -0
- {invenio_app_ils-4.5.0.dist-info → invenio_app_ils-5.0.0.dist-info}/top_level.txt +0 -1
- invenio_app_ils/notifications/admin.py +0 -57
- tests/__init__.py +0 -8
- tests/api/__init__.py +0 -8
- tests/api/acquisition/__init__.py +0 -8
- tests/api/acquisition/test_acq_orders_crud.py +0 -99
- tests/api/acquisition/test_acq_orders_permissions.py +0 -104
- tests/api/acquisition/test_acq_providers_permissions.py +0 -83
- tests/api/circulation/__init__.py +0 -8
- tests/api/circulation/test_expired_loans.py +0 -88
- tests/api/circulation/test_loan_bulk_extend.py +0 -169
- tests/api/circulation/test_loan_checkout.py +0 -422
- tests/api/circulation/test_loan_default_durations.py +0 -43
- tests/api/circulation/test_loan_document_resolver.py +0 -18
- tests/api/circulation/test_loan_extend.py +0 -122
- tests/api/circulation/test_loan_item_permissions.py +0 -135
- tests/api/circulation/test_loan_item_resolver.py +0 -26
- tests/api/circulation/test_loan_list_permissions.py +0 -98
- tests/api/circulation/test_loan_patron_resolver.py +0 -38
- tests/api/circulation/test_loan_request.py +0 -338
- tests/api/circulation/test_loan_update.py +0 -150
- tests/api/circulation/test_notifications.py +0 -221
- tests/api/conftest.py +0 -227
- tests/api/document_requests/__init__.py +0 -8
- tests/api/document_requests/test_document_requests.py +0 -131
- tests/api/document_requests/test_document_requests_permissions.py +0 -159
- tests/api/document_requests/test_notifications_filter.py +0 -35
- tests/api/ill/__init__.py +0 -8
- tests/api/ill/test_ill_brw_crud.py +0 -74
- tests/api/ill/test_ill_brw_reqs_patron_loan_create_action.py +0 -198
- tests/api/ill/test_ill_brw_reqs_patron_loan_extension_actions.py +0 -280
- tests/api/ill/test_ill_brw_reqs_permissions.py +0 -163
- tests/api/ill/test_ill_providers_permissions.py +0 -82
- tests/api/ils/__init__.py +0 -8
- tests/api/ils/documents/__init__.py +0 -8
- tests/api/ils/documents/test_document_crud.py +0 -57
- tests/api/ils/documents/test_document_permissions.py +0 -100
- tests/api/ils/documents/test_document_resolvers.py +0 -35
- tests/api/ils/eitems/__init__.py +0 -8
- tests/api/ils/eitems/test_eitems_crud.py +0 -42
- tests/api/ils/eitems/test_eitems_permissions.py +0 -85
- tests/api/ils/eitems/test_files.py +0 -162
- tests/api/ils/items/__init__.py +0 -8
- tests/api/ils/items/test_apis.py +0 -43
- tests/api/ils/items/test_items_crud.py +0 -99
- tests/api/ils/items/test_items_permissions.py +0 -107
- tests/api/ils/records_relations/__init__.py +0 -8
- tests/api/ils/records_relations/helpers.py +0 -115
- tests/api/ils/records_relations/test_records_relations_parentchild.py +0 -560
- tests/api/ils/records_relations/test_records_relations_sequence.py +0 -294
- tests/api/ils/records_relations/test_records_relations_siblings.py +0 -751
- tests/api/ils/series/__init__.py +0 -8
- tests/api/ils/series/test_series_permissions.py +0 -95
- tests/api/ils/test_anonymization.py +0 -181
- tests/api/ils/test_apis.py +0 -76
- tests/api/ils/test_closures.py +0 -353
- tests/api/ils/test_errors.py +0 -125
- tests/api/ils/test_facets.py +0 -88
- tests/api/ils/test_internal_locations.py +0 -96
- tests/api/ils/test_loaders.py +0 -51
- tests/api/ils/test_metadata_extensions.py +0 -206
- tests/api/ils/test_notifications.py +0 -173
- tests/api/ils/test_notifications_mails.py +0 -37
- tests/api/ils/test_notifications_permissions.py +0 -55
- tests/api/ils/test_patrons.py +0 -102
- tests/api/ils/test_record_delete.py +0 -42
- tests/api/ils/test_record_permissions.py +0 -132
- tests/api/ils/test_resolvers.py +0 -205
- tests/api/ils/test_stats.py +0 -142
- tests/api/ils/test_tasks.py +0 -209
- tests/api/ils/test_vocabularies.py +0 -35
- tests/conftest.py +0 -221
- tests/data/acq_orders.json +0 -110
- tests/data/acq_providers.json +0 -12
- tests/data/document_requests.json +0 -77
- tests/data/documents.json +0 -238
- tests/data/eitems.json +0 -71
- tests/data/ill_borrowing_requests.json +0 -77
- tests/data/ill_providers.json +0 -12
- tests/data/internal_locations.json +0 -22
- tests/data/items.json +0 -304
- tests/data/loans.json +0 -133
- tests/data/loans_most_loaned.json +0 -128
- tests/data/locations.json +0 -26
- tests/data/series.json +0 -33
- tests/helpers.py +0 -107
- tests/templates/notifications/title_body.html +0 -8
- tests/templates/notifications/title_body_html.html +0 -13
- tests/templates/notifications/title_body_html_ctx.html +0 -13
- tests/templates/notifications/title_only.html +0 -3
- tests/test_post_logout_redirect.py +0 -23
- tests/test_version.py +0 -17
- /tests/templates/notifications/blank.html → /invenio_app_ils/assets/semantic-ui/templates/.gitkeep +0 -0
- {invenio_app_ils-4.5.0.dist-info → invenio_app_ils-5.0.0.dist-info}/WHEEL +0 -0
- {invenio_app_ils-4.5.0.dist-info → invenio_app_ils-5.0.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {invenio_app_ils-4.5.0.dist-info → invenio_app_ils-5.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,422 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Copyright (C) 2018-2025 CERN.
|
|
4
|
-
#
|
|
5
|
-
# invenio-app-ils is free software; you can redistribute it and/or modify it
|
|
6
|
-
# under the terms of the MIT License; see LICENSE file for more details.
|
|
7
|
-
|
|
8
|
-
"""Test loans item permissions."""
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
from copy import deepcopy
|
|
12
|
-
from datetime import timedelta
|
|
13
|
-
|
|
14
|
-
import arrow
|
|
15
|
-
from flask import url_for
|
|
16
|
-
from flask_principal import UserNeed
|
|
17
|
-
from invenio_access.permissions import Permission
|
|
18
|
-
from invenio_search import current_search
|
|
19
|
-
|
|
20
|
-
from invenio_app_ils.errors import (
|
|
21
|
-
LoanSelfCheckoutDocumentOverbooked,
|
|
22
|
-
LoanSelfCheckoutItemActiveLoan,
|
|
23
|
-
LoanSelfCheckoutItemInvalidStatus,
|
|
24
|
-
)
|
|
25
|
-
from invenio_app_ils.items.api import Item
|
|
26
|
-
from invenio_app_ils.items.serializers import item
|
|
27
|
-
from invenio_app_ils.proxies import current_app_ils
|
|
28
|
-
from tests.helpers import user_login, user_logout
|
|
29
|
-
|
|
30
|
-
NEW_LOAN = {
|
|
31
|
-
"item_pid": "CHANGE ME IN EACH TEST",
|
|
32
|
-
"document_pid": "docid-1",
|
|
33
|
-
"patron_pid": "3",
|
|
34
|
-
"transaction_location_pid": "locid-1",
|
|
35
|
-
"pickup_location_pid": "locid-1",
|
|
36
|
-
"delivery": {"method": "PICKUP"},
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
NEW_LOAN_REQUEST = {
|
|
40
|
-
"document_pid": "CHANGE ME IN EACH TEST",
|
|
41
|
-
"patron_pid": "3",
|
|
42
|
-
"transaction_location_pid": "locid-1",
|
|
43
|
-
"pickup_location_pid": "locid-1",
|
|
44
|
-
"delivery": {"method": "PICKUP"},
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def test_anonymous_cannot_request_loan_on_item(client, json_headers, testdata):
|
|
49
|
-
"""Test that anonymous users cannot request a loan on a item."""
|
|
50
|
-
url = url_for("invenio_app_ils_circulation.loan_checkout")
|
|
51
|
-
res = client.post(url, headers=json_headers, data=json.dumps(NEW_LOAN))
|
|
52
|
-
assert res.status_code == 401
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def test_librarian_can_checkout_item_for_user(client, json_headers, users, testdata):
|
|
56
|
-
"""Test that librarian can checkout a loan on a item for a patron."""
|
|
57
|
-
user_login(client, "librarian", users)
|
|
58
|
-
url = url_for("invenio_app_ils_circulation.loan_checkout")
|
|
59
|
-
params = deepcopy(NEW_LOAN)
|
|
60
|
-
params["transaction_user_pid"] = str(users["librarian"].id)
|
|
61
|
-
params["item_pid"] = dict(type="pitmid", value="itemid-60")
|
|
62
|
-
res = client.post(url, headers=json_headers, data=json.dumps(params))
|
|
63
|
-
assert res.status_code == 202
|
|
64
|
-
loan = res.get_json()["metadata"]
|
|
65
|
-
assert loan["state"] == "ITEM_ON_LOAN"
|
|
66
|
-
assert loan["item_pid"] == params["item_pid"]
|
|
67
|
-
assert loan["document_pid"] == params["document_pid"]
|
|
68
|
-
assert loan["patron_pid"] == params["patron_pid"]
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def test_force_checkout_specific_permissions(
|
|
72
|
-
app, client, json_headers, users, testdata
|
|
73
|
-
):
|
|
74
|
-
"""Test that only allowed users can perform a force checkout."""
|
|
75
|
-
default_factory = app.config["ILS_VIEWS_PERMISSIONS_FACTORY"]
|
|
76
|
-
librarian2 = users["librarian2"]
|
|
77
|
-
|
|
78
|
-
# override default permission factory to require specific permission for
|
|
79
|
-
# force-checkout action
|
|
80
|
-
def custom_views_permissions_factory(action):
|
|
81
|
-
if action == "circulation-loan-force-checkout":
|
|
82
|
-
# fake permission for a specific user
|
|
83
|
-
return Permission(UserNeed(librarian2.id))
|
|
84
|
-
else:
|
|
85
|
-
return default_factory(action)
|
|
86
|
-
|
|
87
|
-
app.config["ILS_VIEWS_PERMISSIONS_FACTORY"] = custom_views_permissions_factory
|
|
88
|
-
|
|
89
|
-
# prepare request
|
|
90
|
-
url = url_for("invenio_app_ils_circulation.loan_checkout")
|
|
91
|
-
params = deepcopy(NEW_LOAN)
|
|
92
|
-
params["force"] = True
|
|
93
|
-
params["item_pid"] = dict(type="pitmid", value="itemid-MISSING")
|
|
94
|
-
params["transaction_user_pid"] = str(librarian2.id)
|
|
95
|
-
|
|
96
|
-
# force-checkout as librarian should fail
|
|
97
|
-
user_login(client, "librarian", users)
|
|
98
|
-
res = client.post(url, headers=json_headers, data=json.dumps(params))
|
|
99
|
-
assert res.status_code == 403
|
|
100
|
-
|
|
101
|
-
# force-checkout as librarian2 should succeed
|
|
102
|
-
user_login(client, "librarian2", users)
|
|
103
|
-
res = client.post(url, headers=json_headers, data=json.dumps(params))
|
|
104
|
-
assert res.status_code == 202
|
|
105
|
-
loan = res.get_json()["metadata"]
|
|
106
|
-
assert loan["state"] == "ITEM_ON_LOAN"
|
|
107
|
-
assert loan["item_pid"] == params["item_pid"]
|
|
108
|
-
assert loan["document_pid"] == params["document_pid"]
|
|
109
|
-
assert loan["patron_pid"] == params["patron_pid"]
|
|
110
|
-
|
|
111
|
-
# restore default config
|
|
112
|
-
app.config["ILS_VIEWS_PERMISSIONS_FACTORY"] = default_factory
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def test_checkout_conditions_librarian(client, json_headers, users, testdata):
|
|
116
|
-
"""Test checkout conditions user."""
|
|
117
|
-
librarian = users["librarian"]
|
|
118
|
-
user_login(client, "librarian", users)
|
|
119
|
-
url = url_for("invenio_app_ils_circulation.loan_checkout")
|
|
120
|
-
|
|
121
|
-
params = deepcopy(NEW_LOAN)
|
|
122
|
-
params["item_pid"] = dict(type="pitmid", value="itemid-61")
|
|
123
|
-
params["transaction_user_pid"] = str(librarian.id)
|
|
124
|
-
res = client.post(url, headers=json_headers, data=json.dumps(params))
|
|
125
|
-
assert res.status_code == 202
|
|
126
|
-
|
|
127
|
-
params = deepcopy(NEW_LOAN)
|
|
128
|
-
params["item_pid"] = dict(type="pitmid", value="itemid-MISSING")
|
|
129
|
-
params["transaction_user_pid"] = str(librarian.id)
|
|
130
|
-
res = client.post(url, headers=json_headers, data=json.dumps(params))
|
|
131
|
-
# missing
|
|
132
|
-
assert res.status_code == 400
|
|
133
|
-
|
|
134
|
-
params = deepcopy(NEW_LOAN)
|
|
135
|
-
params["force"] = True
|
|
136
|
-
params["item_pid"] = dict(type="pitmid", value="itemid-MISSING")
|
|
137
|
-
params["transaction_user_pid"] = str(librarian.id)
|
|
138
|
-
res = client.post(url, headers=json_headers, data=json.dumps(params))
|
|
139
|
-
# missing but force
|
|
140
|
-
assert res.status_code == 202
|
|
141
|
-
item = Item.get_record_by_pid(params["item_pid"]["value"])
|
|
142
|
-
assert item["status"] == "CAN_CIRCULATE"
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def test_checkout_loader_start_end_dates(app, client, json_headers, users, testdata):
|
|
146
|
-
"""Test that start and end dates request parameters."""
|
|
147
|
-
librarian = users["librarian"]
|
|
148
|
-
user_login(client, "librarian", users)
|
|
149
|
-
item_pid = dict(type="pitmid", value="itemid-61")
|
|
150
|
-
url = url_for("invenio_app_ils_circulation.loan_checkout")
|
|
151
|
-
# fake duration for first item
|
|
152
|
-
loan_duration_timedelta = app.config["CIRCULATION_POLICIES"]["checkout"][
|
|
153
|
-
"duration_default"
|
|
154
|
-
](dict(item_pid=item_pid), dict())
|
|
155
|
-
now = arrow.utcnow()
|
|
156
|
-
|
|
157
|
-
# it should succeed when no start/end dates provided
|
|
158
|
-
start_date = now.date().isoformat()
|
|
159
|
-
end_date = (now + loan_duration_timedelta).date().isoformat()
|
|
160
|
-
|
|
161
|
-
params = deepcopy(NEW_LOAN)
|
|
162
|
-
params["item_pid"] = item_pid
|
|
163
|
-
params["transaction_user_pid"] = str(librarian.id)
|
|
164
|
-
res = client.post(url, headers=json_headers, data=json.dumps(params))
|
|
165
|
-
assert res.status_code == 202
|
|
166
|
-
loan = res.get_json()["metadata"]
|
|
167
|
-
assert loan["state"] == "ITEM_ON_LOAN"
|
|
168
|
-
assert loan["item_pid"] == params["item_pid"]
|
|
169
|
-
assert loan["start_date"] == start_date
|
|
170
|
-
assert loan["end_date"] == end_date
|
|
171
|
-
assert loan["transaction_date"]
|
|
172
|
-
|
|
173
|
-
# it should succeed when no end date provided
|
|
174
|
-
_start_date = now + timedelta(days=3)
|
|
175
|
-
start_date = _start_date.date().isoformat()
|
|
176
|
-
end_date = (_start_date + loan_duration_timedelta).date().isoformat()
|
|
177
|
-
|
|
178
|
-
params = deepcopy(NEW_LOAN)
|
|
179
|
-
params["item_pid"] = dict(type="pitmid", value="itemid-62")
|
|
180
|
-
params["start_date"] = start_date
|
|
181
|
-
params["transaction_user_pid"] = str(librarian.id)
|
|
182
|
-
res = client.post(url, headers=json_headers, data=json.dumps(params))
|
|
183
|
-
assert res.status_code == 202
|
|
184
|
-
loan = res.get_json()["metadata"]
|
|
185
|
-
assert loan["state"] == "ITEM_ON_LOAN"
|
|
186
|
-
assert loan["item_pid"] == params["item_pid"]
|
|
187
|
-
assert loan["start_date"] == start_date
|
|
188
|
-
assert loan["end_date"] == end_date
|
|
189
|
-
assert loan["transaction_date"]
|
|
190
|
-
|
|
191
|
-
start_date = (now + timedelta(days=3)).date().isoformat()
|
|
192
|
-
end_date = (now + timedelta(days=15)).date().isoformat()
|
|
193
|
-
|
|
194
|
-
# it should succeed when start/end dates provided
|
|
195
|
-
params = deepcopy(NEW_LOAN)
|
|
196
|
-
params["item_pid"] = dict(type="pitmid", value="itemid-63")
|
|
197
|
-
params["start_date"] = start_date
|
|
198
|
-
params["end_date"] = end_date
|
|
199
|
-
params["transaction_user_pid"] = str(librarian.id)
|
|
200
|
-
res = client.post(url, headers=json_headers, data=json.dumps(params))
|
|
201
|
-
assert res.status_code == 202
|
|
202
|
-
loan = res.get_json()["metadata"]
|
|
203
|
-
assert loan["state"] == "ITEM_ON_LOAN"
|
|
204
|
-
assert loan["item_pid"] == params["item_pid"]
|
|
205
|
-
assert loan["start_date"] == start_date
|
|
206
|
-
assert loan["end_date"] == end_date
|
|
207
|
-
assert loan["transaction_date"]
|
|
208
|
-
|
|
209
|
-
# it should fail when only end date provided
|
|
210
|
-
params = deepcopy(NEW_LOAN)
|
|
211
|
-
params["item_pid"] = dict(type="pitmid", value="itemid-63")
|
|
212
|
-
params["end_date"] = end_date
|
|
213
|
-
params["transaction_user_pid"] = str(librarian.id)
|
|
214
|
-
res = client.post(url, headers=json_headers, data=json.dumps(params))
|
|
215
|
-
assert res.status_code == 400
|
|
216
|
-
|
|
217
|
-
# it should fail when wrong date provided
|
|
218
|
-
start_date = (now + timedelta(days=3)).date().isoformat()
|
|
219
|
-
past_end_date = now - timedelta(days=30)
|
|
220
|
-
params = deepcopy(NEW_LOAN)
|
|
221
|
-
params["item_pid"] = dict(type="pitmid", value="itemid-63")
|
|
222
|
-
params["start_date"] = start_date
|
|
223
|
-
params["end_date"] = past_end_date.date().isoformat()
|
|
224
|
-
params["transaction_user_pid"] = str(librarian.id)
|
|
225
|
-
res = client.post(url, headers=json_headers, data=json.dumps(params))
|
|
226
|
-
assert res.status_code == 400
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def test_self_checkout_search(app, client, json_headers, users, testdata):
|
|
230
|
-
"""Test self-checkout search."""
|
|
231
|
-
app.config["ILS_SELF_CHECKOUT_ENABLED"] = True
|
|
232
|
-
|
|
233
|
-
# test that anonymous user cannot search for barcodes
|
|
234
|
-
url = url_for("invenio_app_ils_circulation.loan_self_checkout")
|
|
235
|
-
res = client.get(f"{url}?barcode=123456", headers=json_headers)
|
|
236
|
-
assert res.status_code == 401
|
|
237
|
-
|
|
238
|
-
user_login(client, "patron2", users)
|
|
239
|
-
|
|
240
|
-
# test the missing parameter error
|
|
241
|
-
url = url_for("invenio_app_ils_circulation.loan_self_checkout")
|
|
242
|
-
res = client.get(url, headers=json_headers)
|
|
243
|
-
assert res.status_code == 400
|
|
244
|
-
|
|
245
|
-
# test that authenticated user can search for barcodes, but it will return
|
|
246
|
-
# 400 when not found
|
|
247
|
-
unexisting_barcode = "123456"
|
|
248
|
-
res = client.get(f"{url}?barcode={unexisting_barcode}", headers=json_headers)
|
|
249
|
-
assert res.status_code == 400
|
|
250
|
-
|
|
251
|
-
# test that an error is returned when the item cannot circulate
|
|
252
|
-
in_binding_item_barcode = "123456789-74"
|
|
253
|
-
url = url_for("invenio_app_ils_circulation.loan_self_checkout")
|
|
254
|
-
res = client.get(f"{url}?barcode={in_binding_item_barcode}", headers=json_headers)
|
|
255
|
-
assert res.status_code == 400
|
|
256
|
-
# assert that the payload will contain the key error with a msg
|
|
257
|
-
response = res.get_json()
|
|
258
|
-
assert LoanSelfCheckoutItemInvalidStatus.description in response["message"]
|
|
259
|
-
assert LoanSelfCheckoutItemInvalidStatus.supportCode in response["supportCode"]
|
|
260
|
-
|
|
261
|
-
# test that no error is returned when the item is marked as missing
|
|
262
|
-
missing_item_barcode = "123456789-1"
|
|
263
|
-
missing_item_pid = "itemid-1"
|
|
264
|
-
url = url_for("invenio_app_ils_circulation.loan_self_checkout")
|
|
265
|
-
res = client.get(f"{url}?barcode={missing_item_barcode}", headers=json_headers)
|
|
266
|
-
assert res.status_code == 200
|
|
267
|
-
|
|
268
|
-
# assert item is no longer marked as missing
|
|
269
|
-
response = res.get_json()
|
|
270
|
-
item_pid = response["metadata"]["pid"]
|
|
271
|
-
assert item_pid == missing_item_pid
|
|
272
|
-
item = Item.get_record_by_pid(item_pid)
|
|
273
|
-
assert item["status"] == "CAN_CIRCULATE"
|
|
274
|
-
|
|
275
|
-
# create a loan on the same patron, and another one on another patron
|
|
276
|
-
user_login(client, "librarian", users)
|
|
277
|
-
url = url_for("invenio_app_ils_circulation.loan_checkout")
|
|
278
|
-
|
|
279
|
-
for item_pid, patron_pid in [
|
|
280
|
-
("itemid-60", "2"), # barcode 123456789-60
|
|
281
|
-
("itemid-61", "1"), # barcode 123456789-61
|
|
282
|
-
]:
|
|
283
|
-
params = deepcopy(NEW_LOAN)
|
|
284
|
-
params["transaction_user_pid"] = str(users["librarian"].id)
|
|
285
|
-
params["item_pid"] = dict(type="pitmid", value=item_pid)
|
|
286
|
-
params["patron_pid"] = patron_pid
|
|
287
|
-
res = client.post(url, headers=json_headers, data=json.dumps(params))
|
|
288
|
-
assert res.status_code == 202
|
|
289
|
-
|
|
290
|
-
# ensure new loans and related items are fully indexed
|
|
291
|
-
current_search.flush_and_refresh(index="*")
|
|
292
|
-
|
|
293
|
-
user_login(client, "patron2", users)
|
|
294
|
-
|
|
295
|
-
# test that an error is returned when the item is already on loan by the same user
|
|
296
|
-
on_loan_same_patron_barcode = "123456789-60"
|
|
297
|
-
url = url_for("invenio_app_ils_circulation.loan_self_checkout")
|
|
298
|
-
res = client.get(
|
|
299
|
-
f"{url}?barcode={on_loan_same_patron_barcode}", headers=json_headers
|
|
300
|
-
)
|
|
301
|
-
assert res.status_code == 400
|
|
302
|
-
# assert that the payload will contain the key error with a msg
|
|
303
|
-
response = res.get_json()
|
|
304
|
-
assert LoanSelfCheckoutItemActiveLoan.description in response["message"]
|
|
305
|
-
assert LoanSelfCheckoutItemActiveLoan.supportCode in response["supportCode"]
|
|
306
|
-
|
|
307
|
-
# test that an error is returned when the item is already on loan by another user
|
|
308
|
-
on_loan_other_patron_barcode = "123456789-61"
|
|
309
|
-
url = url_for("invenio_app_ils_circulation.loan_self_checkout")
|
|
310
|
-
res = client.get(
|
|
311
|
-
f"{url}?barcode={on_loan_other_patron_barcode}", headers=json_headers
|
|
312
|
-
)
|
|
313
|
-
assert res.status_code == 400
|
|
314
|
-
# assert that the payload will contain the key error with a msg
|
|
315
|
-
response = res.get_json()
|
|
316
|
-
assert LoanSelfCheckoutItemActiveLoan.description in response["message"]
|
|
317
|
-
assert LoanSelfCheckoutItemActiveLoan.supportCode in response["supportCode"]
|
|
318
|
-
|
|
319
|
-
# test happy path
|
|
320
|
-
available_barcode = "123456789-10"
|
|
321
|
-
url = url_for("invenio_app_ils_circulation.loan_self_checkout")
|
|
322
|
-
res = client.get(f"{url}?barcode={available_barcode}", headers=json_headers)
|
|
323
|
-
assert res.status_code == 200
|
|
324
|
-
response = res.get_json()
|
|
325
|
-
assert response["metadata"]["pid"] == "itemid-10"
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
def test_self_checkout(app, client, json_headers, users, testdata):
|
|
329
|
-
"""Test self-checkout."""
|
|
330
|
-
|
|
331
|
-
def _create_request(patron, document_pid):
|
|
332
|
-
url = url_for("invenio_app_ils_circulation.loan_request")
|
|
333
|
-
user = user_login(client, patron, users)
|
|
334
|
-
params = deepcopy(NEW_LOAN_REQUEST)
|
|
335
|
-
params["document_pid"] = document_pid
|
|
336
|
-
params["patron_pid"] = str(user.id)
|
|
337
|
-
params["transaction_user_pid"] = str(user.id)
|
|
338
|
-
res = client.post(url, headers=json_headers, data=json.dumps(params))
|
|
339
|
-
assert res.status_code == 202
|
|
340
|
-
current_search.flush_and_refresh(index="*")
|
|
341
|
-
|
|
342
|
-
def _self_checkout(patron, item_pid, document_pid):
|
|
343
|
-
url = url_for("invenio_app_ils_circulation.loan_self_checkout")
|
|
344
|
-
params = deepcopy(NEW_LOAN)
|
|
345
|
-
params["document_pid"] = document_pid
|
|
346
|
-
params["item_pid"] = dict(type="pitmid", value=item_pid)
|
|
347
|
-
params["patron_pid"] = str(patron.id)
|
|
348
|
-
params["transaction_user_pid"] = str(patron.id)
|
|
349
|
-
return client.post(url, headers=json_headers, data=json.dumps(params))
|
|
350
|
-
|
|
351
|
-
url = url_for("invenio_app_ils_circulation.loan_self_checkout")
|
|
352
|
-
|
|
353
|
-
app.config["ILS_SELF_CHECKOUT_ENABLED"] = False
|
|
354
|
-
|
|
355
|
-
# test a logged in user cannot self-checkout when the feature is disabled
|
|
356
|
-
patron2 = user_login(client, "patron2", users)
|
|
357
|
-
res = client.post(url, headers=json_headers)
|
|
358
|
-
assert res.status_code == 403
|
|
359
|
-
|
|
360
|
-
user_logout(client)
|
|
361
|
-
app.config["ILS_SELF_CHECKOUT_ENABLED"] = True
|
|
362
|
-
|
|
363
|
-
# test that anonymous user cannot self-checkout
|
|
364
|
-
res = client.post(url, headers=json_headers)
|
|
365
|
-
assert res.status_code == 401
|
|
366
|
-
|
|
367
|
-
# test overbooked books
|
|
368
|
-
# create multiple requests from different patrons
|
|
369
|
-
_create_request("patron1", "docid-15")
|
|
370
|
-
_create_request("patron3", "docid-15")
|
|
371
|
-
|
|
372
|
-
document_rec = current_app_ils.document_record_cls.get_record_by_pid("docid-15")
|
|
373
|
-
document = document_rec.replace_refs()
|
|
374
|
-
assert document["circulation"]["overbooked"]
|
|
375
|
-
|
|
376
|
-
# test that user cannot self-checkout an overbooked book, without having a request
|
|
377
|
-
patron2 = user_login(client, "patron2", users)
|
|
378
|
-
res = _self_checkout(patron2, "itemid-71", "docid-15")
|
|
379
|
-
assert res.status_code == 400
|
|
380
|
-
response = res.get_json()
|
|
381
|
-
assert LoanSelfCheckoutDocumentOverbooked.description in response["message"]
|
|
382
|
-
assert LoanSelfCheckoutDocumentOverbooked.supportCode in response["supportCode"]
|
|
383
|
-
|
|
384
|
-
# test that user cannot self-checkout an overbooked book, having a request
|
|
385
|
-
# create request from the same patron
|
|
386
|
-
_create_request("patron2", "docid-15")
|
|
387
|
-
patron2 = user_login(client, "patron2", users)
|
|
388
|
-
res = _self_checkout(patron2, "itemid-71", "docid-15")
|
|
389
|
-
assert res.status_code == 400
|
|
390
|
-
response = res.get_json()
|
|
391
|
-
assert LoanSelfCheckoutDocumentOverbooked.description in response["message"]
|
|
392
|
-
assert LoanSelfCheckoutDocumentOverbooked.supportCode in response["supportCode"]
|
|
393
|
-
|
|
394
|
-
# test that user can self-checkout having a prior request
|
|
395
|
-
_create_request("patron2", "docid-16")
|
|
396
|
-
|
|
397
|
-
patron2 = user_login(client, "patron2", users)
|
|
398
|
-
res = _self_checkout(patron2, "itemid-72", "docid-16")
|
|
399
|
-
assert res.status_code == 202
|
|
400
|
-
response = res.get_json()
|
|
401
|
-
assert response["metadata"]["delivery"]["method"] == "SELF-CHECKOUT"
|
|
402
|
-
|
|
403
|
-
# test that user can self-checkout without having a prior request, even if there
|
|
404
|
-
# are other requests on the book (but not overbooked)
|
|
405
|
-
_create_request("patron1", "docid-17")
|
|
406
|
-
|
|
407
|
-
patron2 = user_login(client, "patron2", users)
|
|
408
|
-
res = _self_checkout(patron2, "itemid-73", "docid-17")
|
|
409
|
-
assert res.status_code == 202
|
|
410
|
-
response = res.get_json()
|
|
411
|
-
assert response["metadata"]["delivery"]["method"] == "SELF-CHECKOUT"
|
|
412
|
-
|
|
413
|
-
# test self-checkout with item marked as missing raises no error and marks the item as CAN_CIRCULATE
|
|
414
|
-
missing_item_pid = "itemid-1"
|
|
415
|
-
app.config["ILS_SELF_CHECKOUT_ENABLED"] = True
|
|
416
|
-
patron2 = user_login(client, "patron2", users)
|
|
417
|
-
res = _self_checkout(patron2, missing_item_pid, "docid-1")
|
|
418
|
-
assert res.status_code == 202
|
|
419
|
-
response = res.get_json()
|
|
420
|
-
assert response["metadata"]["delivery"]["method"] == "SELF-CHECKOUT"
|
|
421
|
-
item = Item.get_record_by_pid(missing_item_pid)
|
|
422
|
-
assert item["status"] == "CAN_CIRCULATE"
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Copyright (C) 2020 CERN.
|
|
4
|
-
#
|
|
5
|
-
# invenio-app-ils is free software; you can redistribute it and/or modify it
|
|
6
|
-
# under the terms of the MIT License; see LICENSE file for more details.
|
|
7
|
-
|
|
8
|
-
"""Test loans default durations."""
|
|
9
|
-
|
|
10
|
-
from datetime import timedelta
|
|
11
|
-
|
|
12
|
-
import arrow
|
|
13
|
-
|
|
14
|
-
from invenio_app_ils.ill.api import (
|
|
15
|
-
circulation_default_extension_duration,
|
|
16
|
-
circulation_default_loan_duration,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
FAKE_LOAN_ITEM_NO_RESTRICTIONS = {"item_pid": {"type": "pitmid", "value": "itemid-1"}}
|
|
20
|
-
FAKE_LOAN_ITEM_ONE_WEEK = {"item_pid": {"type": "pitmid", "value": "itemid-2"}}
|
|
21
|
-
FAKE_LOAN_ITEM_TWO_WEEKS = {"item_pid": {"type": "pitmid", "value": "itemid-3"}}
|
|
22
|
-
FAKE_LOAN_ITEM_THREE_WEEKS = {"item_pid": {"type": "pitmid", "value": "itemid-4"}}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def test_loans_default_durations(testdata):
|
|
26
|
-
"""Test loans default durations."""
|
|
27
|
-
tomorrow = arrow.utcnow() + timedelta(days=1)
|
|
28
|
-
not_overdue_end_date = tomorrow.date().isoformat()
|
|
29
|
-
for duration_func in (
|
|
30
|
-
circulation_default_loan_duration,
|
|
31
|
-
circulation_default_extension_duration,
|
|
32
|
-
):
|
|
33
|
-
FAKE_LOAN_ITEM_NO_RESTRICTIONS["end_date"] = not_overdue_end_date
|
|
34
|
-
assert duration_func(FAKE_LOAN_ITEM_NO_RESTRICTIONS, None) == timedelta(weeks=4)
|
|
35
|
-
|
|
36
|
-
FAKE_LOAN_ITEM_ONE_WEEK["end_date"] = not_overdue_end_date
|
|
37
|
-
assert duration_func(FAKE_LOAN_ITEM_ONE_WEEK, None) == timedelta(weeks=1)
|
|
38
|
-
|
|
39
|
-
FAKE_LOAN_ITEM_TWO_WEEKS["end_date"] = not_overdue_end_date
|
|
40
|
-
assert duration_func(FAKE_LOAN_ITEM_TWO_WEEKS, None) == timedelta(weeks=2)
|
|
41
|
-
|
|
42
|
-
FAKE_LOAN_ITEM_THREE_WEEKS["end_date"] = not_overdue_end_date
|
|
43
|
-
assert duration_func(FAKE_LOAN_ITEM_THREE_WEEKS, None) == timedelta(weeks=3)
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Copyright (C) 2019 CERN.
|
|
4
|
-
#
|
|
5
|
-
# Invenio-Circulation is free software; you can redistribute it and/or modify
|
|
6
|
-
# it under the terms of the MIT License; see LICENSE file for more details.
|
|
7
|
-
|
|
8
|
-
"""Tests for loan item resolver."""
|
|
9
|
-
|
|
10
|
-
from invenio_circulation.api import Loan
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_loan_document_resolver(app, testdata):
|
|
14
|
-
"""Test item resolving from loan."""
|
|
15
|
-
loan_pid = testdata["loans"][1]["pid"]
|
|
16
|
-
loan = Loan.get_record_by_pid(loan_pid)
|
|
17
|
-
loan = loan.replace_refs()
|
|
18
|
-
assert loan["document"]["pid"] == loan["document_pid"]
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Copyright (C) 2019-2020 CERN.
|
|
4
|
-
#
|
|
5
|
-
# invenio-app-ils is free software; you can redistribute it and/or modify it
|
|
6
|
-
# under the terms of the MIT License; see LICENSE file for more details.
|
|
7
|
-
|
|
8
|
-
"""Test loan extend."""
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
from copy import deepcopy
|
|
12
|
-
from datetime import timedelta
|
|
13
|
-
|
|
14
|
-
import arrow
|
|
15
|
-
from flask import url_for
|
|
16
|
-
from invenio_circulation.api import Loan
|
|
17
|
-
from invenio_db import db
|
|
18
|
-
|
|
19
|
-
from invenio_app_ils.circulation.api import circulation_default_loan_duration_for_item
|
|
20
|
-
from invenio_app_ils.items.api import Item
|
|
21
|
-
from tests.helpers import user_login
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _checkout_loan_pid1(client, json_headers, users, loan_params):
|
|
25
|
-
"""Create an ongoing loan."""
|
|
26
|
-
|
|
27
|
-
def _checkout(loan_pid, params):
|
|
28
|
-
"""Perform checkout action."""
|
|
29
|
-
user_login(client, "librarian", users)
|
|
30
|
-
checkout_url = url_for(
|
|
31
|
-
"invenio_circulation_loan_actions.loanid_actions",
|
|
32
|
-
pid_value=loan_pid,
|
|
33
|
-
action="checkout",
|
|
34
|
-
)
|
|
35
|
-
resp = client.post(checkout_url, headers=json_headers, data=json.dumps(params))
|
|
36
|
-
assert resp.status_code == 202
|
|
37
|
-
return resp.get_json()
|
|
38
|
-
|
|
39
|
-
loan_pid = "loanid-1"
|
|
40
|
-
params = deepcopy(loan_params)
|
|
41
|
-
params["document_pid"] = "docid-1"
|
|
42
|
-
params["item_pid"]["value"] = "itemid-2"
|
|
43
|
-
|
|
44
|
-
return _checkout(loan_pid, params)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def test_loan_extend_permissions(client, json_headers, users, testdata, loan_params):
|
|
48
|
-
"""Test loan can be extended."""
|
|
49
|
-
params = deepcopy(loan_params)
|
|
50
|
-
del params["transaction_date"]
|
|
51
|
-
loan = _checkout_loan_pid1(client, json_headers, users, params)
|
|
52
|
-
|
|
53
|
-
tests = [
|
|
54
|
-
("admin", 202),
|
|
55
|
-
("librarian", 202),
|
|
56
|
-
("patron1", 202),
|
|
57
|
-
("patron3", 403),
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
for username, expected_resp_code in tests:
|
|
61
|
-
extend_url = loan["links"]["actions"]["extend"]
|
|
62
|
-
|
|
63
|
-
user_login(client, username, users)
|
|
64
|
-
res = client.post(
|
|
65
|
-
extend_url,
|
|
66
|
-
headers=json_headers,
|
|
67
|
-
data=json.dumps(params),
|
|
68
|
-
)
|
|
69
|
-
assert res.status_code == expected_resp_code
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def test_loan_extension_end_date(
|
|
73
|
-
app, client, json_headers, users, testdata, loan_params
|
|
74
|
-
):
|
|
75
|
-
"""Test loan end date after extension."""
|
|
76
|
-
params = deepcopy(loan_params)
|
|
77
|
-
del params["transaction_date"]
|
|
78
|
-
record = _checkout_loan_pid1(client, json_headers, users, params)
|
|
79
|
-
extend_url = record["links"]["actions"]["extend"]
|
|
80
|
-
loan = record["metadata"]
|
|
81
|
-
|
|
82
|
-
item = Item.get_record_by_pid(loan["item_pid"]["value"])
|
|
83
|
-
item_loan_duration = circulation_default_loan_duration_for_item(item)
|
|
84
|
-
|
|
85
|
-
user_login(client, "patron1", users)
|
|
86
|
-
|
|
87
|
-
def _set_loan_end_date(loan_pid, new_end_date):
|
|
88
|
-
"""Set loan end date."""
|
|
89
|
-
loan = Loan.get_record_by_pid(loan_pid)
|
|
90
|
-
loan["end_date"] = new_end_date.date().isoformat()
|
|
91
|
-
loan.commit()
|
|
92
|
-
db.session.commit()
|
|
93
|
-
|
|
94
|
-
def test_extension_end_date_loan_not_overdue(loan_pid):
|
|
95
|
-
"""Test that new end date is added to the current end date."""
|
|
96
|
-
now = arrow.utcnow()
|
|
97
|
-
new_end_date = now + timedelta(days=10) # loan is not overdue
|
|
98
|
-
_set_loan_end_date(loan_pid, new_end_date)
|
|
99
|
-
|
|
100
|
-
expected_end_date = new_end_date + item_loan_duration
|
|
101
|
-
|
|
102
|
-
res = client.post(extend_url, headers=json_headers, data=json.dumps(params))
|
|
103
|
-
assert res.status_code == 202
|
|
104
|
-
new_loan = res.get_json()["metadata"]
|
|
105
|
-
assert new_loan["end_date"] == expected_end_date.date().isoformat()
|
|
106
|
-
|
|
107
|
-
def test_extension_end_date_loan_overdue(loan_pid):
|
|
108
|
-
"""Test that new end date is added to today."""
|
|
109
|
-
now = arrow.utcnow()
|
|
110
|
-
|
|
111
|
-
new_end_date = now - timedelta(days=2) # loan is overdue since 2 days
|
|
112
|
-
_set_loan_end_date(loan_pid, new_end_date)
|
|
113
|
-
|
|
114
|
-
expected_end_date = now + item_loan_duration
|
|
115
|
-
res = client.post(extend_url, headers=json_headers, data=json.dumps(params))
|
|
116
|
-
assert res.status_code == 202
|
|
117
|
-
new_loan = res.get_json()["metadata"]
|
|
118
|
-
# loan new end date should start from now
|
|
119
|
-
assert new_loan["end_date"] == expected_end_date.date().isoformat()
|
|
120
|
-
|
|
121
|
-
test_extension_end_date_loan_not_overdue(loan["pid"])
|
|
122
|
-
test_extension_end_date_loan_overdue(loan["pid"])
|