invenio-app-ils 4.1.0__py2.py3-none-any.whl → 4.3.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/circulation/api.py +123 -26
- invenio_app_ils/circulation/config.py +14 -2
- invenio_app_ils/circulation/loaders/__init__.py +2 -0
- invenio_app_ils/circulation/loaders/schemas/json/loan_request.py +2 -1
- invenio_app_ils/circulation/loaders/schemas/json/loan_self_checkout.py +19 -0
- invenio_app_ils/circulation/notifications/messages.py +1 -0
- invenio_app_ils/circulation/serializers/__init__.py +1 -0
- invenio_app_ils/circulation/serializers/response.py +1 -0
- invenio_app_ils/circulation/templates/invenio_app_ils_circulation/notifications/self_checkout.html +19 -0
- invenio_app_ils/circulation/views.py +100 -12
- invenio_app_ils/documents/jsonresolvers/document_stock.py +1 -1
- invenio_app_ils/documents/loaders/jsonschemas/document.py +1 -1
- invenio_app_ils/eitems/loaders/jsonschemas/eitems.py +1 -1
- invenio_app_ils/errors.py +103 -4
- invenio_app_ils/items/api.py +13 -1
- invenio_app_ils/items/loaders/jsonschemas/items.py +1 -1
- invenio_app_ils/items/search.py +13 -0
- invenio_app_ils/items/serializers/item.py +4 -4
- invenio_app_ils/patrons/anonymization.py +39 -9
- invenio_app_ils/permissions.py +49 -36
- invenio_app_ils/records/views.py +1 -1
- invenio_app_ils/records_relations/api.py +1 -0
- invenio_app_ils/relations/api.py +6 -1
- invenio_app_ils/series/loaders/jsonschemas/series.py +1 -1
- {invenio_app_ils-4.1.0.dist-info → invenio_app_ils-4.3.0.dist-info}/METADATA +16 -5
- {invenio_app_ils-4.1.0.dist-info → invenio_app_ils-4.3.0.dist-info}/RECORD +35 -34
- {invenio_app_ils-4.1.0.dist-info → invenio_app_ils-4.3.0.dist-info}/WHEEL +1 -1
- {invenio_app_ils-4.1.0.dist-info → invenio_app_ils-4.3.0.dist-info}/entry_points.txt +0 -1
- tests/api/circulation/test_loan_checkout.py +171 -66
- tests/api/ils/items/test_items_crud.py +46 -4
- tests/data/items.json +49 -27
- tests/api/ils/items/test_items_update.py +0 -40
- {invenio_app_ils-4.1.0.dist-info → invenio_app_ils-4.3.0.dist-info}/AUTHORS.rst +0 -0
- {invenio_app_ils-4.1.0.dist-info → invenio_app_ils-4.3.0.dist-info}/LICENSE +0 -0
- {invenio_app_ils-4.1.0.dist-info → invenio_app_ils-4.3.0.dist-info}/top_level.txt +0 -0
invenio_app_ils/errors.py
CHANGED
|
@@ -98,11 +98,17 @@ class RecordHasReferencesError(IlsException):
|
|
|
98
98
|
self.record_id = record_id
|
|
99
99
|
|
|
100
100
|
|
|
101
|
+
class ItemCannotCirculateError(IlsException):
|
|
102
|
+
"""The item cannot circulate."""
|
|
103
|
+
|
|
104
|
+
description = "This item cannot circulate."
|
|
105
|
+
|
|
106
|
+
|
|
101
107
|
class ItemHasActiveLoanError(IlsException):
|
|
102
108
|
"""The item which we are trying to update has an active loan."""
|
|
103
109
|
|
|
104
110
|
description = (
|
|
105
|
-
"Could not update item because it has an active loan with
|
|
111
|
+
"Could not update item because it has an active loan with pid: {loan_pid}."
|
|
106
112
|
)
|
|
107
113
|
|
|
108
114
|
def __init__(self, loan_pid, **kwargs):
|
|
@@ -126,6 +132,7 @@ class PatronNotFoundError(IlsException):
|
|
|
126
132
|
class PatronHasLoanOnItemError(IlsException):
|
|
127
133
|
"""A patron already has an active loan or a loan request on an item."""
|
|
128
134
|
|
|
135
|
+
code = 400
|
|
129
136
|
description = "Patron '{0}' has already an active loan on item '{1}:{2}'"
|
|
130
137
|
|
|
131
138
|
def __init__(self, patron_pid, item_pid, **kwargs):
|
|
@@ -135,6 +142,8 @@ class PatronHasLoanOnItemError(IlsException):
|
|
|
135
142
|
:param prop: Missing property from loan request.
|
|
136
143
|
"""
|
|
137
144
|
super().__init__(**kwargs)
|
|
145
|
+
self.patron_pid = patron_pid
|
|
146
|
+
self.item_pid = item_pid
|
|
138
147
|
self.description = self.description.format(
|
|
139
148
|
patron_pid, item_pid["type"], item_pid["value"]
|
|
140
149
|
)
|
|
@@ -143,6 +152,7 @@ class PatronHasLoanOnItemError(IlsException):
|
|
|
143
152
|
class PatronHasRequestOnDocumentError(IlsException):
|
|
144
153
|
"""A patron already has a loan request on a document."""
|
|
145
154
|
|
|
155
|
+
code = 400
|
|
146
156
|
description = (
|
|
147
157
|
"Patron '{patron_pid}' has already a loan "
|
|
148
158
|
"request on document '{document_pid}'"
|
|
@@ -163,6 +173,7 @@ class PatronHasRequestOnDocumentError(IlsException):
|
|
|
163
173
|
class PatronHasLoanOnDocumentError(IlsException):
|
|
164
174
|
"""A patron already has an active loan on a document."""
|
|
165
175
|
|
|
176
|
+
code = 400
|
|
166
177
|
description = (
|
|
167
178
|
"Patron '{patron_pid}' has already an active loan "
|
|
168
179
|
"on document '{document_pid}'"
|
|
@@ -194,9 +205,54 @@ class LoanCheckoutByPatronForbidden(IlsException):
|
|
|
194
205
|
)
|
|
195
206
|
|
|
196
207
|
|
|
208
|
+
class LoanSelfCheckoutItemUnavailable(IlsException):
|
|
209
|
+
"""A patron cannot self-checkout an item."""
|
|
210
|
+
|
|
211
|
+
code = 400
|
|
212
|
+
description = "This literature is not available for self-checkout. Please contact the library for more information."
|
|
213
|
+
|
|
214
|
+
def get_body(self, environ=None, scope=None):
|
|
215
|
+
"""Get the request body."""
|
|
216
|
+
body = dict(
|
|
217
|
+
status=self.code,
|
|
218
|
+
message=self.get_description(environ),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if self.supportCode:
|
|
222
|
+
body["supportCode"] = self.supportCode
|
|
223
|
+
|
|
224
|
+
return json.dumps(body)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class LoanSelfCheckoutItemInvalidStatus(LoanSelfCheckoutItemUnavailable):
|
|
228
|
+
"""A patron cannot self-checkout an item that cannot circulate."""
|
|
229
|
+
|
|
230
|
+
supportCode = "SELF-CHECKOUT-001"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class LoanSelfCheckoutDocumentOverbooked(LoanSelfCheckoutItemUnavailable):
|
|
234
|
+
"""A patron cannot self-checkout an item for an overbooked document."""
|
|
235
|
+
|
|
236
|
+
supportCode = "SELF-CHECKOUT-002"
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class LoanSelfCheckoutItemActiveLoan(LoanSelfCheckoutItemUnavailable):
|
|
240
|
+
"""A patron cannot self-checkout an item that cannot circulate."""
|
|
241
|
+
|
|
242
|
+
supportCode = "SELF-CHECKOUT-003"
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class LoanSelfCheckoutItemNotFound(LoanSelfCheckoutItemUnavailable):
|
|
246
|
+
"""A patron cannot self-checkout an item because item with provided barcode doesn't exist."""
|
|
247
|
+
|
|
248
|
+
supportCode = "SELF-CHECKOUT-004"
|
|
249
|
+
description = "Literature not found. Please try again with another barcode or contact the library."
|
|
250
|
+
|
|
251
|
+
|
|
197
252
|
class NotImplementedConfigurationError(IlsException):
|
|
198
253
|
"""Exception raised when function is not implemented."""
|
|
199
254
|
|
|
255
|
+
code = 500
|
|
200
256
|
description = (
|
|
201
257
|
"Function is not implemented. Implement this function in your module "
|
|
202
258
|
"and pass it to the config variable"
|
|
@@ -211,15 +267,20 @@ class NotImplementedConfigurationError(IlsException):
|
|
|
211
267
|
class MissingRequiredParameterError(IlsException):
|
|
212
268
|
"""Exception raised when required parameter is missing."""
|
|
213
269
|
|
|
270
|
+
code = 400
|
|
271
|
+
|
|
214
272
|
|
|
215
273
|
class InvalidParameterError(IlsException):
|
|
216
274
|
"""Exception raised when an invalid parameter is has been given."""
|
|
217
275
|
|
|
276
|
+
code = 400
|
|
277
|
+
|
|
218
278
|
|
|
219
279
|
class DocumentNotFoundError(IlsException):
|
|
220
280
|
"""Raised when a document could not be found."""
|
|
221
281
|
|
|
222
|
-
|
|
282
|
+
code = 404
|
|
283
|
+
description = "Document PID '{}' was not found."
|
|
223
284
|
|
|
224
285
|
def __init__(self, document_pid, **kwargs):
|
|
225
286
|
"""Initialize exception."""
|
|
@@ -227,10 +288,38 @@ class DocumentNotFoundError(IlsException):
|
|
|
227
288
|
self.description = self.description.format(document_pid)
|
|
228
289
|
|
|
229
290
|
|
|
291
|
+
class ItemNotFoundError(IlsException):
|
|
292
|
+
"""Raised when an item could not be found."""
|
|
293
|
+
|
|
294
|
+
code = 404
|
|
295
|
+
description = "Item not found."
|
|
296
|
+
|
|
297
|
+
def __init__(self, pid=None, barcode=None, **kwargs):
|
|
298
|
+
"""Initialize exception."""
|
|
299
|
+
super().__init__(**kwargs)
|
|
300
|
+
if pid:
|
|
301
|
+
self.description += " PID: {}".format(pid)
|
|
302
|
+
if barcode:
|
|
303
|
+
self.description += " Barcode: {}".format(barcode)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class MultipleItemsBarcodeFoundError(IlsException):
|
|
307
|
+
"""Raised when multiple items with the same barcode has been found."""
|
|
308
|
+
|
|
309
|
+
code = 500
|
|
310
|
+
description = "Multiple items with barcode {} found."
|
|
311
|
+
|
|
312
|
+
def __init__(self, barcode, **kwargs):
|
|
313
|
+
"""Initialize exception."""
|
|
314
|
+
super().__init__(**kwargs)
|
|
315
|
+
self.description = self.description.format(barcode)
|
|
316
|
+
|
|
317
|
+
|
|
230
318
|
class LocationNotFoundError(IlsException):
|
|
231
319
|
"""Raised when a location could not be found."""
|
|
232
320
|
|
|
233
|
-
|
|
321
|
+
code = 404
|
|
322
|
+
description = "Location PID '{}' was not found."
|
|
234
323
|
|
|
235
324
|
def __init__(self, location_pid, **kwargs):
|
|
236
325
|
"""Initialize exception."""
|
|
@@ -241,7 +330,8 @@ class LocationNotFoundError(IlsException):
|
|
|
241
330
|
class InternalLocationNotFoundError(IlsException):
|
|
242
331
|
"""Raised when an internal location could not be found."""
|
|
243
332
|
|
|
244
|
-
|
|
333
|
+
code = 404
|
|
334
|
+
description = "Internal Location PID '{}' was not found."
|
|
245
335
|
|
|
246
336
|
def __init__(self, internal_location_pid, **kwargs):
|
|
247
337
|
"""Initialize exception."""
|
|
@@ -252,6 +342,7 @@ class InternalLocationNotFoundError(IlsException):
|
|
|
252
342
|
class UnknownItemPidTypeError(IlsException):
|
|
253
343
|
"""Raised when the given item PID type is unknown."""
|
|
254
344
|
|
|
345
|
+
code = 400
|
|
255
346
|
description = "Unknown Item PID type '{}'"
|
|
256
347
|
|
|
257
348
|
def __init__(self, pid_type, **kwargs):
|
|
@@ -293,6 +384,14 @@ class DocumentRequestError(IlsException):
|
|
|
293
384
|
super().__init__(description=description)
|
|
294
385
|
|
|
295
386
|
|
|
387
|
+
class DocumentOverbookedError(IlsException):
|
|
388
|
+
"""Raised when a document is overbooked."""
|
|
389
|
+
|
|
390
|
+
def __init__(self, description):
|
|
391
|
+
"""Initialize exception."""
|
|
392
|
+
super().__init__(description=description)
|
|
393
|
+
|
|
394
|
+
|
|
296
395
|
class VocabularyError(IlsException):
|
|
297
396
|
"""Generic vocabulary exception."""
|
|
298
397
|
|
invenio_app_ils/items/api.py
CHANGED
|
@@ -160,15 +160,27 @@ class Item(IlsRecord):
|
|
|
160
160
|
)
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
@classmethod
|
|
164
|
+
def enforce_constraints(cls, data, **kwargs):
|
|
165
|
+
"""Enforce constraints.
|
|
166
|
+
|
|
167
|
+
:param data (dict): dict that can be mutated to enforce constraints.
|
|
168
|
+
"""
|
|
169
|
+
# barcode is a required field and it should be always uppercase
|
|
170
|
+
data["barcode"] = data["barcode"].upper()
|
|
171
|
+
|
|
163
172
|
@classmethod
|
|
164
173
|
def create(cls, data, id_=None, **kwargs):
|
|
165
174
|
"""Create Item record."""
|
|
166
175
|
cls.build_resolver_fields(data)
|
|
167
|
-
|
|
176
|
+
cls.enforce_constraints(data, **kwargs)
|
|
177
|
+
created = super().create(data, id_=id_, **kwargs)
|
|
178
|
+
return created
|
|
168
179
|
|
|
169
180
|
def update(self, *args, **kwargs):
|
|
170
181
|
"""Update Item record."""
|
|
171
182
|
super().update(*args, **kwargs)
|
|
183
|
+
self.enforce_constraints(self)
|
|
172
184
|
self.build_resolver_fields(self)
|
|
173
185
|
|
|
174
186
|
def delete(self, **kwargs):
|
|
@@ -15,8 +15,8 @@ from invenio_app_ils.records.loaders.schemas.changed_by import (
|
|
|
15
15
|
ChangedBySchema,
|
|
16
16
|
set_changed_by,
|
|
17
17
|
)
|
|
18
|
-
from invenio_app_ils.records.loaders.schemas.price import PriceSchema
|
|
19
18
|
from invenio_app_ils.records.loaders.schemas.identifiers import IdentifierSchema
|
|
19
|
+
from invenio_app_ils.records.loaders.schemas.price import PriceSchema
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class ISBNSchema(Schema):
|
invenio_app_ils/items/search.py
CHANGED
|
@@ -41,6 +41,19 @@ class ItemSearch(RecordsSearch):
|
|
|
41
41
|
|
|
42
42
|
return search
|
|
43
43
|
|
|
44
|
+
def search_by_barcode(self, barcode, filter_states=None, exclude_states=None):
|
|
45
|
+
"""Retrieve items matching the given barcode."""
|
|
46
|
+
search = self
|
|
47
|
+
|
|
48
|
+
search = search.filter("term", barcode=barcode)
|
|
49
|
+
|
|
50
|
+
if filter_states:
|
|
51
|
+
search = search.filter("terms", status=filter_states)
|
|
52
|
+
elif exclude_states:
|
|
53
|
+
search = search.exclude("terms", status=exclude_states)
|
|
54
|
+
|
|
55
|
+
return search
|
|
56
|
+
|
|
44
57
|
def search_by_internal_location_pid(
|
|
45
58
|
self,
|
|
46
59
|
internal_location_pid=None,
|
|
@@ -49,7 +49,7 @@ class ItemCSVSerializer(CSVSerializer):
|
|
|
49
49
|
pid, record, links_factory=links_factory, **kwargs
|
|
50
50
|
)
|
|
51
51
|
filter_circulation(item)
|
|
52
|
-
field_cover_metadata(item.get(
|
|
52
|
+
field_cover_metadata(item.get("metadata", {}).get("document", {}))
|
|
53
53
|
return item
|
|
54
54
|
|
|
55
55
|
def transform_search_hit(self, pid, record_hit, links_factory=None, **kwargs):
|
|
@@ -58,7 +58,7 @@ class ItemCSVSerializer(CSVSerializer):
|
|
|
58
58
|
pid, record_hit, links_factory=links_factory, **kwargs
|
|
59
59
|
)
|
|
60
60
|
filter_circulation(hit)
|
|
61
|
-
field_cover_metadata(hit.get(
|
|
61
|
+
field_cover_metadata(hit.get("metadata", {}).get("document", {}))
|
|
62
62
|
return hit
|
|
63
63
|
|
|
64
64
|
|
|
@@ -71,7 +71,7 @@ class ItemJSONSerializer(JSONSerializer):
|
|
|
71
71
|
pid, record, links_factory=links_factory, **kwargs
|
|
72
72
|
)
|
|
73
73
|
filter_circulation(item)
|
|
74
|
-
field_cover_metadata(item.get(
|
|
74
|
+
field_cover_metadata(item.get("metadata", {}).get("document", {}))
|
|
75
75
|
return item
|
|
76
76
|
|
|
77
77
|
def transform_search_hit(self, pid, record_hit, links_factory=None, **kwargs):
|
|
@@ -80,5 +80,5 @@ class ItemJSONSerializer(JSONSerializer):
|
|
|
80
80
|
pid, record_hit, links_factory=links_factory, **kwargs
|
|
81
81
|
)
|
|
82
82
|
filter_circulation(hit)
|
|
83
|
-
field_cover_metadata(hit.get(
|
|
83
|
+
field_cover_metadata(hit.get("metadata", {}).get("document", {}))
|
|
84
84
|
return hit
|
|
@@ -12,18 +12,22 @@ from copy import deepcopy
|
|
|
12
12
|
|
|
13
13
|
from flask import current_app
|
|
14
14
|
from invenio_accounts.models import LoginInformation, SessionActivity, User, userrole
|
|
15
|
+
from invenio_circulation.pidstore.pids import CIRCULATION_LOAN_PID_TYPE
|
|
15
16
|
from invenio_circulation.proxies import current_circulation
|
|
16
17
|
from invenio_db import db
|
|
17
18
|
from invenio_oauthclient.models import RemoteAccount, RemoteToken, UserIdentity
|
|
18
19
|
from invenio_search.engine import search as inv_search
|
|
19
20
|
from invenio_userprofiles.models import UserProfile
|
|
20
21
|
|
|
22
|
+
from invenio_app_ils.acquisition.api import ORDER_PID_TYPE
|
|
21
23
|
from invenio_app_ils.acquisition.proxies import current_ils_acq
|
|
22
24
|
from invenio_app_ils.circulation.search import (
|
|
23
25
|
get_active_loans_by_patron_pid,
|
|
24
26
|
get_loans_by_patron_pid,
|
|
25
27
|
)
|
|
28
|
+
from invenio_app_ils.document_requests.api import DOCUMENT_REQUEST_PID_TYPE
|
|
26
29
|
from invenio_app_ils.errors import AnonymizationActiveLoansError, PatronNotFoundError
|
|
30
|
+
from invenio_app_ils.ill.api import BORROWING_REQUEST_PID_TYPE
|
|
27
31
|
from invenio_app_ils.ill.proxies import current_ils_ill
|
|
28
32
|
from invenio_app_ils.notifications.models import NotificationsLogs
|
|
29
33
|
from invenio_app_ils.patrons.api import get_patron_or_unknown_dump
|
|
@@ -106,9 +110,32 @@ def anonymize_patron_data(patron_pid, force=False):
|
|
|
106
110
|
cls = current_app.config["ILS_PATRON_ANONYMOUS_CLASS"]
|
|
107
111
|
anonymous_patron_fields = cls().dumps_loader()
|
|
108
112
|
|
|
113
|
+
Loan = current_circulation.loan_record_cls
|
|
114
|
+
BorrowingRequest = current_ils_ill.borrowing_request_record_cls
|
|
115
|
+
DocumentRequest = current_app_ils.document_request_record_cls
|
|
116
|
+
Order = current_ils_acq.order_record_cls
|
|
117
|
+
|
|
118
|
+
anonymized_records = {
|
|
119
|
+
CIRCULATION_LOAN_PID_TYPE: {
|
|
120
|
+
"indexer": current_circulation.loan_indexer(),
|
|
121
|
+
"records": [],
|
|
122
|
+
},
|
|
123
|
+
BORROWING_REQUEST_PID_TYPE: {
|
|
124
|
+
"indexer": current_ils_ill.borrowing_request_indexer_cls(),
|
|
125
|
+
"records": [],
|
|
126
|
+
},
|
|
127
|
+
DOCUMENT_REQUEST_PID_TYPE: {
|
|
128
|
+
"indexer": current_app_ils.document_request_indexer,
|
|
129
|
+
"records": [],
|
|
130
|
+
},
|
|
131
|
+
ORDER_PID_TYPE: {
|
|
132
|
+
"indexer": current_ils_acq.order_indexer,
|
|
133
|
+
"records": [],
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
|
|
109
137
|
patron_loans = get_loans_by_patron_pid(patron_pid).scan()
|
|
110
138
|
|
|
111
|
-
Loan = current_circulation.loan_record_cls
|
|
112
139
|
indices = 0
|
|
113
140
|
for hit in patron_loans:
|
|
114
141
|
loan = Loan.get_record_by_pid(hit.pid)
|
|
@@ -131,7 +158,7 @@ def anonymize_patron_data(patron_pid, force=False):
|
|
|
131
158
|
loan["patron_pid"] = anonymous_patron_fields["pid"]
|
|
132
159
|
loan["patron"] = anonymous_patron_fields
|
|
133
160
|
loan.commit()
|
|
134
|
-
|
|
161
|
+
anonymized_records[CIRCULATION_LOAN_PID_TYPE]["records"].append(loan)
|
|
135
162
|
indices += 1
|
|
136
163
|
|
|
137
164
|
BorrowingRequestsSearch = current_ils_ill.borrowing_request_search_cls
|
|
@@ -139,14 +166,12 @@ def anonymize_patron_data(patron_pid, force=False):
|
|
|
139
166
|
BorrowingRequestsSearch().search_by_patron_pid(patron_pid).scan()
|
|
140
167
|
)
|
|
141
168
|
|
|
142
|
-
BorrowingRequest = current_ils_ill.borrowing_request_record_cls
|
|
143
|
-
indexer = current_ils_ill.borrowing_request_indexer_cls()
|
|
144
169
|
for hit in patron_borrowing_requests:
|
|
145
170
|
borrowing_request = BorrowingRequest.get_record_by_pid(hit.pid)
|
|
146
171
|
borrowing_request["patron"] = anonymous_patron_fields
|
|
147
172
|
borrowing_request["patron_pid"] = anonymous_patron_fields["pid"]
|
|
148
173
|
borrowing_request.commit()
|
|
149
|
-
|
|
174
|
+
anonymized_records[BORROWING_REQUEST_PID_TYPE]["records"].append(borrowing_request)
|
|
150
175
|
indices += 1
|
|
151
176
|
|
|
152
177
|
DocumentRequestSearch = current_app_ils.document_request_search_cls
|
|
@@ -154,7 +179,6 @@ def anonymize_patron_data(patron_pid, force=False):
|
|
|
154
179
|
DocumentRequestSearch().search_by_patron_pid(patron_pid).scan()
|
|
155
180
|
)
|
|
156
181
|
|
|
157
|
-
DocumentRequest = current_app_ils.document_request_record_cls
|
|
158
182
|
for hit in patron_document_requests:
|
|
159
183
|
document_request = DocumentRequest.get_record_by_pid(hit.pid)
|
|
160
184
|
if document_request["state"] == "PENDING":
|
|
@@ -163,19 +187,18 @@ def anonymize_patron_data(patron_pid, force=False):
|
|
|
163
187
|
document_request["patron"] = anonymous_patron_fields
|
|
164
188
|
document_request["patron_pid"] = anonymous_patron_fields["pid"]
|
|
165
189
|
document_request.commit()
|
|
166
|
-
|
|
190
|
+
anonymized_records[ORDER_PID_TYPE]["records"].append(document_request)
|
|
167
191
|
indices += 1
|
|
168
192
|
|
|
169
193
|
patron_acquisitions = OrderSearch().search_by_patron_pid(patron_pid).scan()
|
|
170
194
|
|
|
171
|
-
Order = current_ils_acq.order_record_cls
|
|
172
195
|
for hit in patron_acquisitions:
|
|
173
196
|
acquisition = Order.get_record_by_pid(hit.pid)
|
|
174
197
|
for line in acquisition["order_lines"]:
|
|
175
198
|
if line.get("patron_pid") == patron_pid:
|
|
176
199
|
line["patron_pid"] = anonymous_patron_fields["pid"]
|
|
177
200
|
acquisition.commit()
|
|
178
|
-
|
|
201
|
+
anonymized_records[Order._pid_type]["records"].append(acquisition)
|
|
179
202
|
indices += 1
|
|
180
203
|
|
|
181
204
|
# delete rows from db
|
|
@@ -185,6 +208,13 @@ def anonymize_patron_data(patron_pid, force=False):
|
|
|
185
208
|
notifications = anonymize_patron_in_notification_logs(patron_pid)
|
|
186
209
|
|
|
187
210
|
db.session.commit()
|
|
211
|
+
|
|
212
|
+
# index all after committing to DB, to ensure that no errors occurred.
|
|
213
|
+
for value in anonymized_records.values():
|
|
214
|
+
indexer, records = value["indexer"], value["records"]
|
|
215
|
+
for record in records:
|
|
216
|
+
indexer.index(record)
|
|
217
|
+
|
|
188
218
|
if patron:
|
|
189
219
|
try:
|
|
190
220
|
patron_indexer = current_app_ils.patron_indexer
|
invenio_app_ils/permissions.py
CHANGED
|
@@ -132,27 +132,32 @@ def patron_owner_permission(record):
|
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
def loan_checkout_permission(*args, **kwargs):
|
|
135
|
-
"""
|
|
135
|
+
"""Loan checkout permissions checks.
|
|
136
|
+
|
|
137
|
+
Allow admins and librarians to checkout, patrons to self-checkout when enabled.
|
|
138
|
+
"""
|
|
136
139
|
if not has_request_context():
|
|
137
|
-
#
|
|
140
|
+
# CLI or Celery task
|
|
138
141
|
return backoffice_permission()
|
|
142
|
+
|
|
139
143
|
if current_user.is_anonymous:
|
|
140
144
|
abort(401)
|
|
141
145
|
|
|
142
146
|
is_admin_or_librarian = backoffice_permission().allows(g.identity)
|
|
143
147
|
if is_admin_or_librarian:
|
|
144
148
|
return backoffice_permission()
|
|
149
|
+
|
|
150
|
+
# ensure that only the loan's patron can do operations on this loan
|
|
145
151
|
if len(args):
|
|
146
152
|
loan = args[0]
|
|
147
153
|
else:
|
|
148
|
-
loan = kwargs
|
|
149
|
-
is_patron_current_user = current_user.id == int(loan
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
and is_patron_current_user
|
|
153
|
-
):
|
|
154
|
+
loan = kwargs["record"]
|
|
155
|
+
is_patron_current_user = current_user.id == int(loan["patron_pid"])
|
|
156
|
+
|
|
157
|
+
if current_app.config["ILS_SELF_CHECKOUT_ENABLED"] and is_patron_current_user:
|
|
154
158
|
return authenticated_user_permission()
|
|
155
|
-
|
|
159
|
+
|
|
160
|
+
raise LoanCheckoutByPatronForbidden(int(loan["patron_pid"]), current_user.id)
|
|
156
161
|
|
|
157
162
|
|
|
158
163
|
class PatronOwnerPermission(Permission):
|
|
@@ -163,36 +168,44 @@ class PatronOwnerPermission(Permission):
|
|
|
163
168
|
super().__init__(UserNeed(int(record["patron_pid"])), backoffice_access_action)
|
|
164
169
|
|
|
165
170
|
|
|
171
|
+
_is_authenticated_user = [
|
|
172
|
+
"circulation-loan-request",
|
|
173
|
+
"patron-loans",
|
|
174
|
+
"bulk-loan-extension",
|
|
175
|
+
]
|
|
176
|
+
_is_backoffice_permission = [
|
|
177
|
+
"circulation-loan-force-checkout",
|
|
178
|
+
"circulation-overdue-loan-notification",
|
|
179
|
+
"circulation-loan-update-dates",
|
|
180
|
+
"relations-create",
|
|
181
|
+
"relations-delete",
|
|
182
|
+
"stats-most-loaned",
|
|
183
|
+
"document-request-actions",
|
|
184
|
+
"bucket-create",
|
|
185
|
+
"ill-brwreq-patron-loan-create",
|
|
186
|
+
"ill-brwreq-patron-loan-extension-accept",
|
|
187
|
+
"ill-brwreq-patron-loan-extension-decline",
|
|
188
|
+
"send-notification-to-patron",
|
|
189
|
+
]
|
|
190
|
+
_is_patron_owner_permission = [
|
|
191
|
+
"document-request-decline",
|
|
192
|
+
"ill-brwreq-patron-loan-extension-request",
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
|
|
166
196
|
def views_permissions_factory(action):
|
|
167
197
|
"""Return ILS views permissions factory."""
|
|
168
|
-
|
|
169
|
-
"circulation-loan-request",
|
|
170
|
-
"patron-loans",
|
|
171
|
-
"bulk-loan-extension",
|
|
172
|
-
]
|
|
173
|
-
is_backoffice_permission = [
|
|
174
|
-
"circulation-loan-checkout",
|
|
175
|
-
"circulation-loan-force-checkout",
|
|
176
|
-
"circulation-overdue-loan-notification",
|
|
177
|
-
"circulation-loan-update-dates",
|
|
178
|
-
"relations-create",
|
|
179
|
-
"relations-delete",
|
|
180
|
-
"stats-most-loaned",
|
|
181
|
-
"document-request-actions",
|
|
182
|
-
"bucket-create",
|
|
183
|
-
"ill-brwreq-patron-loan-create",
|
|
184
|
-
"ill-brwreq-patron-loan-extension-accept",
|
|
185
|
-
"ill-brwreq-patron-loan-extension-decline",
|
|
186
|
-
"send-notification-to-patron",
|
|
187
|
-
]
|
|
188
|
-
is_patron_owner_permission = [
|
|
189
|
-
"document-request-decline",
|
|
190
|
-
"ill-brwreq-patron-loan-extension-request",
|
|
191
|
-
]
|
|
192
|
-
if action in is_authenticated_user:
|
|
198
|
+
if action in _is_authenticated_user:
|
|
193
199
|
return authenticated_user_permission()
|
|
194
|
-
elif action in
|
|
200
|
+
elif action in _is_backoffice_permission:
|
|
195
201
|
return backoffice_permission()
|
|
196
|
-
elif action in
|
|
202
|
+
elif action in _is_patron_owner_permission:
|
|
197
203
|
return PatronOwnerPermission
|
|
204
|
+
elif action == "circulation-loan-checkout":
|
|
205
|
+
return backoffice_permission()
|
|
206
|
+
elif (
|
|
207
|
+
action == "circulation-loan-self-checkout"
|
|
208
|
+
and current_app.config["ILS_SELF_CHECKOUT_ENABLED"]
|
|
209
|
+
):
|
|
210
|
+
return authenticated_user_permission()
|
|
198
211
|
return deny_all()
|
invenio_app_ils/records/views.py
CHANGED
|
@@ -20,7 +20,7 @@ from invenio_app_ils.errors import StatsError
|
|
|
20
20
|
from invenio_app_ils.permissions import backoffice_permission
|
|
21
21
|
from invenio_app_ils.records.permissions import RecordPermission
|
|
22
22
|
from invenio_app_ils.series.api import SERIES_PID_TYPE
|
|
23
|
-
from invenio_app_ils.signals import
|
|
23
|
+
from invenio_app_ils.signals import file_downloaded, record_viewed
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def create_document_stats_blueprint(app):
|
invenio_app_ils/relations/api.py
CHANGED
|
@@ -17,7 +17,12 @@ from sqlalchemy import and_, or_
|
|
|
17
17
|
from invenio_app_ils.errors import RecordRelationsError
|
|
18
18
|
|
|
19
19
|
ILS_RELATION_TYPE = namedtuple(
|
|
20
|
-
"IlsRelationType",
|
|
20
|
+
"IlsRelationType",
|
|
21
|
+
RelationType._fields
|
|
22
|
+
+ (
|
|
23
|
+
"relation_class",
|
|
24
|
+
"sort_by",
|
|
25
|
+
),
|
|
21
26
|
)
|
|
22
27
|
|
|
23
28
|
LANGUAGE_RELATION = ILS_RELATION_TYPE(
|
|
@@ -21,10 +21,10 @@ from invenio_app_ils.records.loaders.schemas.changed_by import (
|
|
|
21
21
|
ChangedBySchema,
|
|
22
22
|
set_changed_by,
|
|
23
23
|
)
|
|
24
|
+
from invenio_app_ils.records.loaders.schemas.identifiers import IdentifierSchema
|
|
24
25
|
from invenio_app_ils.records.loaders.schemas.preserve_cover_metadata import (
|
|
25
26
|
preserve_cover_metadata,
|
|
26
27
|
)
|
|
27
|
-
from invenio_app_ils.records.loaders.schemas.identifiers import IdentifierSchema
|
|
28
28
|
from invenio_app_ils.series.api import Series
|
|
29
29
|
|
|
30
30
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: invenio-app-ils
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.0
|
|
4
4
|
Summary: Invenio Integrated Library System.
|
|
5
5
|
Home-page: https://github.com/inveniosoftware/invenio-app-ils
|
|
6
6
|
Author: CERN
|
|
@@ -16,7 +16,7 @@ Requires-Dist: invenio-app<1.4.0,>=1.3.4
|
|
|
16
16
|
Requires-Dist: invenio-db[mysql,postgresql]<2.0.0,>=1.0.14
|
|
17
17
|
Requires-Dist: invenio-base<1.3.0,>=1.2.11
|
|
18
18
|
Requires-Dist: invenio-cache<2.0.0,>=1.1.1
|
|
19
|
-
Requires-Dist: invenio-celery<
|
|
19
|
+
Requires-Dist: invenio-celery<2.0.0,>=1.2.4
|
|
20
20
|
Requires-Dist: invenio-config<1.1.0,>=1.0.3
|
|
21
21
|
Requires-Dist: invenio-i18n<3.0.0,>=2.0.0
|
|
22
22
|
Requires-Dist: invenio-admin<1.5.0,>=1.4.0
|
|
@@ -101,6 +101,19 @@ https://invenioils.docs.cern.ch
|
|
|
101
101
|
Changes
|
|
102
102
|
=======
|
|
103
103
|
|
|
104
|
+
Version 4.3.0 (released 2024-11-19)
|
|
105
|
+
|
|
106
|
+
- self-checkout: use dedicated endpoints for the entire workflow for better
|
|
107
|
+
permissions check and error handling.
|
|
108
|
+
Add a new loan transition and delivery method for self-checkout.
|
|
109
|
+
- anonymization: ensure that re-indexing is happening after the commit to the db,
|
|
110
|
+
to avoid premature re-indexing (and therefore index conflict version)
|
|
111
|
+
when db rollback happens.
|
|
112
|
+
|
|
113
|
+
Version 4.2.0 (released 2024-11-04)
|
|
114
|
+
|
|
115
|
+
- self-checkout: barcode is now always uppercased to make searches case-insensitive
|
|
116
|
+
|
|
104
117
|
Version 4.1.0 (released 2024-10-21)
|
|
105
118
|
|
|
106
119
|
- search: apply the same search analyzers to the fields that needs to be searchable.
|
|
@@ -148,7 +161,7 @@ Version 2.0.0rc9 (released 2024-04-25)
|
|
|
148
161
|
Version 2.0.0rc8 (released 2024-04-04)
|
|
149
162
|
|
|
150
163
|
- records_relation: Simplify sorting
|
|
151
|
-
- records_relations: Use sort_by
|
|
164
|
+
- records_relations: Use sort_by parameter from configs instead
|
|
152
165
|
- relations: Add functionality to sort json refs by relation_type
|
|
153
166
|
- tests: circulation: Add new location for testing closures
|
|
154
167
|
- circulation: loan_request: Fix dates comparison in get_offset_duration
|
|
@@ -563,5 +576,3 @@ Version 1.0.0a4 (released 2020-06-19)
|
|
|
563
576
|
Version 1.0.0a0 (released 2020-06-05)
|
|
564
577
|
|
|
565
578
|
- Initial public release.
|
|
566
|
-
|
|
567
|
-
|