invenio-app-ils 4.3.0__py2.py3-none-any.whl → 4.5.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.
@@ -7,6 +7,6 @@
7
7
 
8
8
  """invenio-app-ils."""
9
9
 
10
- __version__ = "4.3.0"
10
+ __version__ = "4.5.0"
11
11
 
12
12
  __all__ = ("__version__",)
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2018-2020 CERN.
3
+ # Copyright (C) 2018-2025 CERN.
4
4
  #
5
5
  # invenio-app-ils is free software; you can redistribute it and/or modify it
6
6
  # under the terms of the MIT License; see LICENSE file for more details.
@@ -78,9 +78,9 @@ def _validate_delivery(delivery):
78
78
  )
79
79
 
80
80
 
81
- def _set_item_to_can_circulate(item_pid):
81
+ def _set_item_to_can_circulate(item_pid_value):
82
82
  """Change the item status to CAN_CIRCULATE."""
83
- item = Item.get_record_by_pid(item_pid["value"])
83
+ item = Item.get_record_by_pid(item_pid_value)
84
84
  if item["status"] != "CAN_CIRCULATE":
85
85
  item["status"] = "CAN_CIRCULATE"
86
86
  item.commit()
@@ -245,7 +245,7 @@ def checkout_loan(
245
245
  _validate_delivery(optional_delivery)
246
246
 
247
247
  if force:
248
- _set_item_to_can_circulate(item_pid)
248
+ _set_item_to_can_circulate(item_pid["value"])
249
249
 
250
250
  return _checkout_loan(
251
251
  item_pid,
@@ -256,14 +256,19 @@ def checkout_loan(
256
256
  )
257
257
 
258
258
 
259
- def _ensure_item_loanable_via_self_checkout(item_pid):
259
+ def _ensure_item_loanable_via_self_checkout(item_pid_value):
260
260
  """Self-checkout: return loanable item or raise when not loanable.
261
261
 
262
262
  Implements the self-checkout rules to loan an item.
263
263
  """
264
- item = current_app_ils.item_record_cls.get_record_by_pid(item_pid)
264
+ item = current_app_ils.item_record_cls.get_record_by_pid(item_pid_value)
265
265
  item_dict = item.replace_refs()
266
266
 
267
+ if item_dict["status"] == "MISSING":
268
+ _set_item_to_can_circulate(item_pid_value)
269
+ item = current_app_ils.item_record_cls.get_record_by_pid(item_pid_value)
270
+ item_dict = item.replace_refs()
271
+
267
272
  if item_dict["status"] != "CAN_CIRCULATE":
268
273
  raise ItemCannotCirculateError()
269
274
 
@@ -1,13 +1,14 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2020 CERN.
3
+ # Copyright (C) 2020-2025 CERN.
4
4
  #
5
5
  # invenio-app-ils is free software; you can redistribute it and/or modify it
6
6
  # under the terms of the MIT License; see LICENSE file for more details.
7
7
 
8
8
  """ILS location closures API."""
9
9
 
10
- from datetime import timedelta
10
+ from datetime import date, timedelta
11
+ from calendar import day_name
11
12
 
12
13
  import arrow
13
14
 
@@ -64,3 +65,70 @@ def find_next_open_date(location_pid, date):
64
65
  "location %s is open after the given date %s."
65
66
  "Please check opening/closures dates." % (location_pid, date.isoformat())
66
67
  )
68
+
69
+
70
+ def get_closure_periods(location, start, end):
71
+ """
72
+ Return date ranges within a specified period when the location is closed.
73
+ """
74
+ if (
75
+ not location
76
+ or not isinstance(start, date)
77
+ or not isinstance(end, date)
78
+ or start > end
79
+ ):
80
+ raise ValueError("Invalid input parameters")
81
+
82
+ one_day_delta = timedelta(days=1)
83
+
84
+ weekday_name_to_index = {name.lower(): i for i, name in enumerate(day_name)}
85
+ closed_weekdays = set()
86
+ for weekday_rule in location.get("opening_weekdays", []):
87
+ if weekday_rule.get("is_open") is False:
88
+ weekday_str = weekday_rule.get("weekday", "").lower()
89
+ closed_weekdays.add(weekday_name_to_index[weekday_str])
90
+
91
+ exception_overrides = {}
92
+ for exception in location.get("opening_exceptions", []):
93
+ is_closed_override = not exception.get("is_open")
94
+
95
+ ex_start = date.fromisoformat(exception["start_date"])
96
+ ex_end = date.fromisoformat(exception["end_date"])
97
+
98
+ current_ex_date = ex_start
99
+ while current_ex_date <= ex_end:
100
+ exception_overrides[current_ex_date] = is_closed_override
101
+ current_ex_date += one_day_delta
102
+
103
+ closure_periods = []
104
+ closure_streak_start = None
105
+ current_date = start
106
+
107
+ while current_date <= end:
108
+ is_date_closed = False
109
+
110
+ override_status = exception_overrides.get(current_date)
111
+
112
+ if override_status is not None:
113
+ is_date_closed = override_status
114
+ else:
115
+ if current_date.weekday() in closed_weekdays:
116
+ is_date_closed = True
117
+
118
+ if is_date_closed:
119
+ if closure_streak_start is None:
120
+ closure_streak_start = current_date
121
+ else:
122
+ if closure_streak_start is not None:
123
+ closure_periods.append(
124
+ {"start": closure_streak_start, "end": current_date - one_day_delta}
125
+ )
126
+ closure_streak_start = None
127
+
128
+ current_date += one_day_delta
129
+
130
+ # Handle trailing closed period
131
+ if closure_streak_start is not None:
132
+ closure_periods.append({"start": closure_streak_start, "end": end})
133
+
134
+ return closure_periods
@@ -0,0 +1,16 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # This file is part of Invenio.
4
+ # Copyright (C) 2025-2025 CERN.
5
+ #
6
+ # Invenio is free software; you can redistribute it and/or modify it
7
+ # under the terms of the MIT License; see LICENSE file for more details.
8
+
9
+
10
+ from invenio_app_ils.closures.serializers.response import closure_periods_responsify
11
+ from invenio_app_ils.closures.serializers.schema import ClosurePeriodsV1
12
+
13
+
14
+ closure_periods_response = closure_periods_responsify(
15
+ ClosurePeriodsV1, "application/json"
16
+ )
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # This file is part of Invenio.
4
+ # Copyright (C) 2025-2025 CERN.
5
+ #
6
+ # Invenio is free software; you can redistribute it and/or modify it
7
+ # under the terms of the MIT License; see LICENSE file for more details.
8
+
9
+ """Invenio App ILS Closures response serializers."""
10
+
11
+ import json
12
+ from flask import current_app
13
+
14
+
15
+ def closure_periods_responsify(schema_class, mimetype):
16
+ """Closure periods response serializer.
17
+
18
+ :param schema_class: Schema instance.
19
+ :param mimetype: MIME type of response.
20
+ """
21
+
22
+ def view(data, code=200, headers=None):
23
+ """Generate the response object."""
24
+ response_data = schema_class().dump(data)
25
+
26
+ response = current_app.response_class(
27
+ json.dumps(response_data), mimetype=mimetype
28
+ )
29
+ response.status_code = code
30
+
31
+ if headers is not None:
32
+ response.headers.extend(headers)
33
+ return response
34
+
35
+ return view
@@ -0,0 +1,28 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # This file is part of Invenio.
4
+ # Copyright (C) 2025-2025 CERN.
5
+ #
6
+ # Invenio is free software; you can redistribute it and/or modify it
7
+ # under the terms of the MIT License; see LICENSE file for more details.
8
+
9
+ """Invenio App ILS Closures response schemas."""
10
+
11
+ from marshmallow import Schema, fields
12
+
13
+
14
+ class DateRangeSchema(Schema):
15
+ """Schema for a single closure period."""
16
+
17
+ start = fields.Date(required=True, description="Start date of the closure period.")
18
+ end = fields.Date(required=True, description="End date of the closure period.")
19
+
20
+
21
+ class ClosurePeriodsV1(Schema):
22
+ """Schema for a list of closure periods."""
23
+
24
+ closure_periods = fields.List(
25
+ fields.Nested(DateRangeSchema),
26
+ required=True,
27
+ description="List of closure date periods.",
28
+ )
@@ -0,0 +1,66 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # This file is part of Invenio.
4
+ # Copyright (C) 2025-2025 CERN.
5
+ #
6
+ # Invenio is free software; you can redistribute it and/or modify it
7
+ # under the terms of the MIT License; see LICENSE file for more details.
8
+
9
+ """Invenio App ILS Closure views."""
10
+
11
+ from flask import Blueprint, abort
12
+ from flask_login import current_user
13
+ from datetime import date
14
+ from invenio_records_rest.views import pass_record
15
+ from invenio_rest import ContentNegotiatedMethodView
16
+ from invenio_app_ils.locations.api import LOCATION_PID_TYPE
17
+ from invenio_app_ils.closures.serializers import closure_periods_response
18
+ from invenio_app_ils.closures.api import get_closure_periods
19
+ from invenio_app_ils.permissions import backoffice_permission
20
+ from invenio_app_ils.records.permissions import RecordPermission
21
+
22
+
23
+ def create_closures_blueprint(app):
24
+ """Create location closed periods blueprint."""
25
+ blueprint = Blueprint("invenio_app_ils_closures", __name__, url_prefix="")
26
+ endpoints = app.config["RECORDS_REST_ENDPOINTS"]
27
+
28
+ options = endpoints[LOCATION_PID_TYPE]
29
+ default_media_type = options.get("default_media_type", "")
30
+ closure_periods_serializers = {"application/json": closure_periods_response}
31
+
32
+ closure_periods_view = LocationClosurePeriodsResource.as_view(
33
+ LocationClosurePeriodsResource.view_name,
34
+ serializers=closure_periods_serializers,
35
+ default_media_type=default_media_type,
36
+ )
37
+ blueprint.add_url_rule(
38
+ "{0}/closure_periods/<int:year>".format(options["item_route"]),
39
+ view_func=closure_periods_view,
40
+ methods=["GET"],
41
+ )
42
+
43
+ return blueprint
44
+
45
+
46
+ class LocationClosurePeriodsResource(ContentNegotiatedMethodView):
47
+ """Returns the closure periods of a Location"""
48
+
49
+ view_name = "location_closure_periods"
50
+
51
+ @pass_record
52
+ def get(self, pid, record, year, **kwargs):
53
+ """Get the date ranges for which a location is closure based in the specified year"""
54
+
55
+ factory = RecordPermission(record, "read")
56
+ if not factory.is_public() and not backoffice_permission().can():
57
+ if not current_user.is_authenticated:
58
+ abort(401)
59
+ abort(403)
60
+
61
+ start_date = date(year, 1, 1)
62
+ end_date = date(year, 12, 31)
63
+
64
+ closure_periods = get_closure_periods(record, start_date, end_date)
65
+
66
+ return self.make_response({"closure_periods": closure_periods}, 200)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: invenio-app-ils
3
- Version: 4.3.0
3
+ Version: 4.5.0
4
4
  Summary: Invenio Integrated Library System.
5
5
  Home-page: https://github.com/inveniosoftware/invenio-app-ils
6
6
  Author: CERN
@@ -22,9 +22,9 @@ Requires-Dist: invenio-i18n<3.0.0,>=2.0.0
22
22
  Requires-Dist: invenio-admin<1.5.0,>=1.4.0
23
23
  Requires-Dist: invenio-assets<4.0.0,>=3.0.0
24
24
  Requires-Dist: invenio-formatter<3.0.0,>=2.0.0
25
- Requires-Dist: invenio-logging[sentry_sdk]<3.0.0,>=2.0.0
25
+ Requires-Dist: invenio-logging[sentry]<3.0.0,>=2.1.5
26
26
  Requires-Dist: invenio-mail<3.0.0,>=2.0.0
27
- Requires-Dist: invenio-rest<1.4.0,>=1.3.0
27
+ Requires-Dist: invenio-rest<1.6.0,>=1.5.0
28
28
  Requires-Dist: invenio-theme<3.0.0,>=2.0.0
29
29
  Requires-Dist: invenio-access<3.0.0,>=2.0.0
30
30
  Requires-Dist: invenio-accounts<4.0.0,>=3.0.0
@@ -53,11 +53,6 @@ Requires-Dist: itsdangerous<2.1,>=1.1
53
53
  Requires-Dist: SQLAlchemy<2.0.0,>=1.3.0
54
54
  Requires-Dist: Flask<2.3.0,>=2.2.0
55
55
  Requires-Dist: Werkzeug<2.3.0,>=2.2.0
56
- Provides-Extra: docs
57
- Provides-Extra: lorem
58
- Requires-Dist: lorem>=0.1.1; extra == "lorem"
59
- Provides-Extra: opensearch2
60
- Requires-Dist: invenio-search[opensearch2]<3.0.0,>=2.0.0; extra == "opensearch2"
61
56
  Provides-Extra: tests
62
57
  Requires-Dist: pytest-black>=0.3.0; extra == "tests"
63
58
  Requires-Dist: mock>=2.0.0; extra == "tests"
@@ -66,6 +61,12 @@ Requires-Dist: docker-services-cli>=0.6.0; extra == "tests"
66
61
  Requires-Dist: pydocstyle==6.1.1; extra == "tests"
67
62
  Requires-Dist: pytest-mock>=1.6.0; extra == "tests"
68
63
  Requires-Dist: sphinx>=5; extra == "tests"
64
+ Provides-Extra: lorem
65
+ Requires-Dist: lorem>=0.1.1; extra == "lorem"
66
+ Provides-Extra: opensearch2
67
+ Requires-Dist: invenio-search[opensearch2]<3.0.0,>=2.0.0; extra == "opensearch2"
68
+ Provides-Extra: docs
69
+ Dynamic: license-file
69
70
 
70
71
  ..
71
72
  Copyright (C) 2018-2020 CERN.
@@ -101,6 +102,16 @@ https://invenioils.docs.cern.ch
101
102
  Changes
102
103
  =======
103
104
 
105
+ Version 4.5.0 (released 2025-06-04)
106
+
107
+ - closures: added endpoint for closure periods in a year
108
+ - circulation: self-checkout of MISSING items marks them as CAN_CIRCULATE
109
+ - tests: Fix comment on running tests by bumping Postgres & adding param
110
+
111
+ Version 4.4.0 (released 2025-02-21)
112
+
113
+ - installation: pin invenio-logging
114
+
104
115
  Version 4.3.0 (released 2024-11-19)
105
116
 
106
117
  - self-checkout: use dedicated endpoints for the entire workflow for better
@@ -1,4 +1,4 @@
1
- invenio_app_ils/__init__.py,sha256=6gWxo9rLMZiagc6YQ3hrsD3hvU6dCPhFUpv2GiX99LI,285
1
+ invenio_app_ils/__init__.py,sha256=5FxyNegCXBoMVkuZG-tPYBUFB9w78RX2ImxZl6S7QRY,285
2
2
  invenio_app_ils/cli.py,sha256=wD-SSrkbEhsndI7N9E39p-T1MXj8LR8pvsWFH-JHHVA,57478
3
3
  invenio_app_ils/config.py,sha256=b-3nNceV4s93eS-AS3WEj9eMopROa2hEZ7BI5jDnlAg,40857
4
4
  invenio_app_ils/errors.py,sha256=HB_iWj-aYxzTzzO6hWb66mUPZdqpWYHgmr2H2t3j3wU,13293
@@ -35,7 +35,7 @@ invenio_app_ils/acquisition/mappings/v7/acq_orders/order-v1.0.0.json,sha256=KMaS
35
35
  invenio_app_ils/acquisition/schemas/__init__.py,sha256=HulLvvDz0W5owDGxVLIaastuZ4o6T5-MYGw0RPuY90g,233
36
36
  invenio_app_ils/acquisition/schemas/acq_orders/order-v1.0.0.json,sha256=_Kd3oX_7gJJQSqSc2TxQulXletYu0wEeTdQKkXr7kLs,5688
37
37
  invenio_app_ils/circulation/__init__.py,sha256=Gd0KAsGdhPdz0ACEQ9k8xSeOIxZr3xRK_FiE8U3RQWs,248
38
- invenio_app_ils/circulation/api.py,sha256=wUiRoPF7-tdUg-XsIhNfTSJ4-nF3sOxRTdxVF0JuDdo,14257
38
+ invenio_app_ils/circulation/api.py,sha256=ac-WrxQYZainV3QkOLyVzF4MVwfdcpRVXg-gd9Vi0tc,14495
39
39
  invenio_app_ils/circulation/config.py,sha256=aVl7kLA2fobQuQy2XR9J_fQMV-ZaVxE2niMSTirDQM0,11387
40
40
  invenio_app_ils/circulation/indexer.py,sha256=T24J5QqcB38LRJgirkAXL2tmFOucYToDRegxgFW96ag,3887
41
41
  invenio_app_ils/circulation/receivers.py,sha256=Ux6KTNbII3DHBvCUS0gxqbi6tNbm76_kbcaHtK0BsB4,2488
@@ -79,8 +79,12 @@ invenio_app_ils/circulation/templates/invenio_app_ils_circulation/notifications/
79
79
  invenio_app_ils/circulation/transitions/__init__.py,sha256=XaEYQVWOWhZ_0JPxTg0Lx53BmyWJXOfM3_nm2KipYhY,237
80
80
  invenio_app_ils/circulation/transitions/transitions.py,sha256=9W5h4RHm72wMQuY3hKgFHLKwnxN68gkN98RwBlCjsro,1361
81
81
  invenio_app_ils/closures/__init__.py,sha256=zJ2fI8Y34Vm-1uL9qLNeOQoqGKlhgfmiH8YcvvgnQP4,242
82
- invenio_app_ils/closures/api.py,sha256=phuH8_veqchQhgGpK8McsKA9qloX3w9E_Mkh_X-Yu00,2203
82
+ invenio_app_ils/closures/api.py,sha256=5i9C1_gDR0-HLeCtU4-aGcvosc9cSR3YVK6gpXJObdo,4441
83
83
  invenio_app_ils/closures/tasks.py,sha256=Uy8W1OPyuByLW4OFdSHVtH9sXmT2tP_uB57FxkV_Vag,4343
84
+ invenio_app_ils/closures/views.py,sha256=cWgCV58D7Pnsg56WLEcWBFQ97jEUgnNb-yvLvWRuvYk,2403
85
+ invenio_app_ils/closures/serializers/__init__.py,sha256=PT1NBZEFk3X7u-7JUQ0U6Pbv12wpD7gPCVIpsZAPdp4,494
86
+ invenio_app_ils/closures/serializers/response.py,sha256=kMSpqllro-UgUOquIRUWywqm77GNdJeeaaaYzpKVMnU,937
87
+ invenio_app_ils/closures/serializers/schema.py,sha256=yFQYCPgd-m3tZAk019FR8TDV0BXovYcKW39o7MLGeAc,816
84
88
  invenio_app_ils/demo_data/documents.json,sha256=n0urrP68azieRMjgwGvKWoJSNXyTwpVxqzS4RInN3Ig,6342
85
89
  invenio_app_ils/demo_data/items.json,sha256=J0IGbnIsFefgvGbgMWVCcvDIW5RJa48WoDPNBImUWKQ,1799
86
90
  invenio_app_ils/document_requests/__init__.py,sha256=yPDfvJcGPy0dJ0f_hsJ5OQ5EcQ-WJODFPLjuWmSXSdU,237
@@ -438,6 +442,8 @@ invenio_app_ils/vocabularies/sources/__init__.py,sha256=EMLoLQGiq9_qoZ4lKqO_J0u2
438
442
  invenio_app_ils/vocabularies/sources/base.py,sha256=kmNg85cjrxqc8VErFP2SUtlyo1PnmzOBQgfoSpfTPh4,1418
439
443
  invenio_app_ils/vocabularies/sources/json.py,sha256=KGLTJ4AX8zEDdkpi3v92oHGs-h2dUzHbNPSg99-j8o0,1021
440
444
  invenio_app_ils/vocabularies/sources/opendefinition.py,sha256=9zbRXuTr0i5lVLt596oUhsLQPiJVf9jiM9-C7T6zbXA,2151
445
+ invenio_app_ils-4.5.0.dist-info/licenses/AUTHORS.rst,sha256=BaXCGzdHCmiMOl4qtVlh1qrfy2ROMVOQp6ylzy1m0ww,212
446
+ invenio_app_ils-4.5.0.dist-info/licenses/LICENSE,sha256=9OdaPOAO1ZOJcRQ8BrGj7QAdaJc8SRSUgBtdom49MrI,1062
441
447
  tests/__init__.py,sha256=eweSnIIeVVFHlmAFG9zkUQ8EXJMYfx8S6DIbsMVzinM,236
442
448
  tests/conftest.py,sha256=vL0D8_Vj34eA1aHD5bzLITQ2_oNugnPoUzv1lIVAUbI,7154
443
449
  tests/helpers.py,sha256=_x3SB-P6YusPHJ-jnKSf5GtuevHcNaNQBVqKh9eZnJ0,3227
@@ -452,7 +458,7 @@ tests/api/acquisition/test_acq_providers_permissions.py,sha256=aVshFVUuKwUfjRZgD
452
458
  tests/api/circulation/__init__.py,sha256=AWnR9VZzm4IiO97eKJNn1LLtQJ1sRgjgrxChJ1Wxsuw,235
453
459
  tests/api/circulation/test_expired_loans.py,sha256=nVGu1H7G8KlPGatx_YG2NCEYpeniZnGkwL4gsg_uyp8,2798
454
460
  tests/api/circulation/test_loan_bulk_extend.py,sha256=jDYYyPXn5p6yn55pUVp4ivVSh9GcCvU5C-P8OlubMto,5606
455
- tests/api/circulation/test_loan_checkout.py,sha256=ndIi5eto0QwlkojF8O4ny98mCsIKNNEUcY1PFz_ETUc,16308
461
+ tests/api/circulation/test_loan_checkout.py,sha256=OFJI57lJZPzheJGuIiCukTzzkZePO2lrlphGlXKJT38,17512
456
462
  tests/api/circulation/test_loan_default_durations.py,sha256=SWQmAJwe5GSoji1Hibx6DjGorJQZ2tYfiHPkuPZKTJo,1681
457
463
  tests/api/circulation/test_loan_document_resolver.py,sha256=ZNu-otdQ8398ksTV2XdgpK3WcbeR8-X-qaRnlPM2_6w,556
458
464
  tests/api/circulation/test_loan_extend.py,sha256=vkpyss5XkzeHVuGUXU8eOlF9J4esSjrVr5oNx-nXqEU,4158
@@ -476,7 +482,7 @@ tests/api/ill/test_ill_providers_permissions.py,sha256=q07t8RQEQ_68H4n01r3lWoNyu
476
482
  tests/api/ils/__init__.py,sha256=9wpf3d3iO-AJ7rNFlnBciyU8QHD7RUp6ShxjZ8s4Gks,227
477
483
  tests/api/ils/test_anonymization.py,sha256=dnWioSC-598zH9-jeH3IxyWOJLVw3M6L7SnICJM3mMU,6650
478
484
  tests/api/ils/test_apis.py,sha256=2fqYcl6X8V5C8YpNqoVAwpHQH3SrerGZNcgwbanHmH4,2680
479
- tests/api/ils/test_closures.py,sha256=xQbpYHQLB5o0JUabdteMU0qTn44QRs3mNnIGFSGPedQ,10041
485
+ tests/api/ils/test_closures.py,sha256=o5WtrC9R88XAJYbvq76-3mhfL_SZhZ7yk7HObsX4XCc,11250
480
486
  tests/api/ils/test_errors.py,sha256=8_cVOU5K2jneUrYRGz4-6IyZUNIPoh92hNqA1er-ync,4313
481
487
  tests/api/ils/test_facets.py,sha256=ffig7FH6mnjaRoL1yELL2X06Q9yp3elmpkvtK6oLdo0,2766
482
488
  tests/api/ils/test_internal_locations.py,sha256=MdSEjloixYIUWdIvc-1vcLbPXClWrzVunpqCqJ81LLY,3517
@@ -519,7 +525,7 @@ tests/data/eitems.json,sha256=2qwmHZkTf718Jizmk7YAO1v_8gs4yOm4dytzmvD-asQ,2122
519
525
  tests/data/ill_borrowing_requests.json,sha256=XG3xQnBizpeXrgUsNHphqLA7wQKEYEdLbkYwTZdqOIM,1603
520
526
  tests/data/ill_providers.json,sha256=M1NPVcjyP3_3r4Ynrltf5ZJ8QReBVHzV2fYSVUmj5Nc,169
521
527
  tests/data/internal_locations.json,sha256=CMXYQ98d9Hhu7YnaQoxrUGAoyIe43kTikE3CIhXPq9E,396
522
- tests/data/items.json,sha256=CUH1RooPv3aI3xKTJ7yN_GKZ19Ap8Cv0dsT8ZcV6igY,8528
528
+ tests/data/items.json,sha256=JMz2ojY79t0_x_55Mjt4BEL2LP1KOj82jXx6o-vCvm4,8849
523
529
  tests/data/loans.json,sha256=xVTnEc9IuJdu0Fspd0HDNHM8ZO26lir3EEkwaAvTGIM,3668
524
530
  tests/data/loans_most_loaned.json,sha256=ut1bOCFHCShPv_HotAjVV59gfwNfjpxEahyPCOZmQgY,3039
525
531
  tests/data/locations.json,sha256=2-xvSY4x8MJsDzlK-aleXaJw6mPMaQe7rJqwX1YqxfM,1637
@@ -529,10 +535,8 @@ tests/templates/notifications/title_body.html,sha256=W-pa6eSm7glK9JEcTVo4G7yC3Q1
529
535
  tests/templates/notifications/title_body_html.html,sha256=wPDP-DpQ0c2AX3oIfLvJLRp7J_rMoesIc9nLx3apkDc,146
530
536
  tests/templates/notifications/title_body_html_ctx.html,sha256=qqfTZ9zKVXFqyXIcqt5BLHEJptRlUDPBPtLxPfTmNzQ,193
531
537
  tests/templates/notifications/title_only.html,sha256=aoZ0veSleRYdKPXWgN6fdXLaz3r-KAPzdfHSuHPR2Uo,45
532
- invenio_app_ils-4.3.0.dist-info/AUTHORS.rst,sha256=BaXCGzdHCmiMOl4qtVlh1qrfy2ROMVOQp6ylzy1m0ww,212
533
- invenio_app_ils-4.3.0.dist-info/LICENSE,sha256=9OdaPOAO1ZOJcRQ8BrGj7QAdaJc8SRSUgBtdom49MrI,1062
534
- invenio_app_ils-4.3.0.dist-info/METADATA,sha256=zJOoSimPLZBg6uHfY09dwtO9PDwNY5djlPNi8BQOmAE,16437
535
- invenio_app_ils-4.3.0.dist-info/WHEEL,sha256=0VNUDWQJzfRahYI3neAhz2UVbRCtztpN5dPHAGvmGXc,109
536
- invenio_app_ils-4.3.0.dist-info/entry_points.txt,sha256=dLh4XxQPtPYRl_VNqzNpRK4l_sv_RQEyAm57tBBL7Ic,7606
537
- invenio_app_ils-4.3.0.dist-info/top_level.txt,sha256=MQTU2NrM0if5YAyIsKmQ0k35skJ3IUyQT7eCD2IWiNQ,22
538
- invenio_app_ils-4.3.0.dist-info/RECORD,,
538
+ invenio_app_ils-4.5.0.dist-info/METADATA,sha256=GiTij7n__fnC2H4vz4pUnuwvUh3lIiEblB88lR8DZv4,16771
539
+ invenio_app_ils-4.5.0.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
540
+ invenio_app_ils-4.5.0.dist-info/entry_points.txt,sha256=iBVLFVqDZl-jiWvOfKldxDq9xsrbmI8GZhWhqsj8Ba0,7678
541
+ invenio_app_ils-4.5.0.dist-info/top_level.txt,sha256=MQTU2NrM0if5YAyIsKmQ0k35skJ3IUyQT7eCD2IWiNQ,22
542
+ invenio_app_ils-4.5.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.5.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -24,6 +24,7 @@ ils_rest = invenio_app_ils.ext:InvenioAppIlsREST
24
24
  [invenio_base.api_blueprints]
25
25
  ils_circulation = invenio_app_ils.circulation.views:create_circulation_blueprint
26
26
  ils_circulation_stats = invenio_app_ils.circulation.stats.views:create_circulation_stats_blueprint
27
+ ils_closures = invenio_app_ils.closures.views:create_closures_blueprint
27
28
  ils_document_request = invenio_app_ils.document_requests.views:create_document_request_action_blueprint
28
29
  ils_document_stats = invenio_app_ils.records.views:create_document_stats_blueprint
29
30
  ils_files = invenio_app_ils.files.views:create_files_blueprint
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2018-2020 CERN.
3
+ # Copyright (C) 2018-2025 CERN.
4
4
  #
5
5
  # invenio-app-ils is free software; you can redistribute it and/or modify it
6
6
  # under the terms of the MIT License; see LICENSE file for more details.
@@ -249,15 +249,29 @@ def test_self_checkout_search(app, client, json_headers, users, testdata):
249
249
  assert res.status_code == 400
250
250
 
251
251
  # test that an error is returned when the item cannot circulate
252
- missing_item_barcode = "123456789-1"
252
+ in_binding_item_barcode = "123456789-74"
253
253
  url = url_for("invenio_app_ils_circulation.loan_self_checkout")
254
- res = client.get(f"{url}?barcode={missing_item_barcode}", headers=json_headers)
254
+ res = client.get(f"{url}?barcode={in_binding_item_barcode}", headers=json_headers)
255
255
  assert res.status_code == 400
256
256
  # assert that the payload will contain the key error with a msg
257
257
  response = res.get_json()
258
258
  assert LoanSelfCheckoutItemInvalidStatus.description in response["message"]
259
259
  assert LoanSelfCheckoutItemInvalidStatus.supportCode in response["supportCode"]
260
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
+
261
275
  # create a loan on the same patron, and another one on another patron
262
276
  user_login(client, "librarian", users)
263
277
  url = url_for("invenio_app_ils_circulation.loan_checkout")
@@ -326,6 +340,7 @@ def test_self_checkout(app, client, json_headers, users, testdata):
326
340
  current_search.flush_and_refresh(index="*")
327
341
 
328
342
  def _self_checkout(patron, item_pid, document_pid):
343
+ url = url_for("invenio_app_ils_circulation.loan_self_checkout")
329
344
  params = deepcopy(NEW_LOAN)
330
345
  params["document_pid"] = document_pid
331
346
  params["item_pid"] = dict(type="pitmid", value=item_pid)
@@ -394,3 +409,14 @@ def test_self_checkout(app, client, json_headers, users, testdata):
394
409
  assert res.status_code == 202
395
410
  response = res.get_json()
396
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"
@@ -10,6 +10,7 @@
10
10
  import json
11
11
 
12
12
  import arrow
13
+ import pytest
13
14
  from flask import url_for
14
15
 
15
16
  from invenio_app_ils.closures.api import find_next_open_date
@@ -21,6 +22,7 @@ _LOCATION_PID = "locid-1"
21
22
  _LOCATION_NAME = "Location name"
22
23
  _ITEM_ENDPOINT = "invenio_records_rest.locid_item"
23
24
  _LIST_ENDPOINT = "invenio_records_rest.locid_list"
25
+ _CLOSURE_PERIODS_ENDPOINT = "invenio_app_ils_closures.location_closure_periods"
24
26
  _WEEKDAYS = [
25
27
  "monday",
26
28
  "tuesday",
@@ -261,7 +263,8 @@ def test_location_validation(client, json_headers, users, testdata):
261
263
  )
262
264
 
263
265
 
264
- def test_find_next_open_date(app, db, testdata):
266
+ @pytest.fixture
267
+ def location_closures_testdata(db, testdata):
265
268
  def _update_location_closures_data(closed_weekdays, exceptions):
266
269
  Location = current_app_ils.location_record_cls
267
270
  record = Location.get_record_by_pid(_LOCATION_PID)
@@ -273,13 +276,6 @@ def test_find_next_open_date(app, db, testdata):
273
276
 
274
277
  return record
275
278
 
276
- def _test(start_date, expected_next_open_date):
277
- next_open = find_next_open_date(_LOCATION_PID, _date_from_string(start_date))
278
- if expected_next_open_date:
279
- assert next_open == _date_from_string(expected_next_open_date)
280
- else:
281
- assert next_open is None
282
-
283
279
  """
284
280
  Mon. Tue. Wed. Thu. Fri. Sat. Sun.
285
281
  27 28 29 30 31 -01 -02
@@ -300,6 +296,15 @@ def test_find_next_open_date(app, db, testdata):
300
296
  ]
301
297
  _update_location_closures_data(closed_weekdays, exceptions)
302
298
 
299
+
300
+ def test_find_next_open_date(app, location_closures_testdata):
301
+ def _test(start_date, expected_next_open_date):
302
+ next_open = find_next_open_date(_LOCATION_PID, _date_from_string(start_date))
303
+ if expected_next_open_date:
304
+ assert next_open == _date_from_string(expected_next_open_date)
305
+ else:
306
+ assert next_open is None
307
+
303
308
  _test("2000-01-01", "2000-01-06")
304
309
  _test("2000-01-04", "2000-01-06")
305
310
  _test("2000-01-06", "2000-01-06")
@@ -313,3 +318,36 @@ def test_find_next_open_date(app, db, testdata):
313
318
  _test("2000-01-30", "2000-01-30")
314
319
 
315
320
  _test("2000-02-05", "2000-02-07")
321
+
322
+
323
+ def test_get_closure_periods(client, location_closures_testdata, json_headers):
324
+ """Test get closure periods."""
325
+ url = url_for(
326
+ _CLOSURE_PERIODS_ENDPOINT,
327
+ pid_value=_LOCATION_PID,
328
+ year=2000,
329
+ )
330
+ res = client.get(url, headers=json_headers)
331
+ assert res.status_code in _HTTP_OK
332
+
333
+ hits = json.loads(res.data.decode("utf-8"))["closure_periods"]
334
+ hits = [
335
+ (_date_from_string(hit["start"]), _date_from_string(hit["end"])) for hit in hits
336
+ ]
337
+
338
+ def _is_in_interval(date, interval):
339
+ return interval[0] <= date <= interval[1]
340
+
341
+ def _test(date, closed):
342
+ date = _date_from_string(date)
343
+ assert (
344
+ any(_is_in_interval(date, hit) for hit in hits) == closed
345
+ ), f"Date {date} is wrongfully marked as {'Open' if closed else 'Closed'}"
346
+
347
+ _test("2000-01-01", True)
348
+ _test("2000-01-06", False)
349
+ _test("2000-01-22", True)
350
+ _test("2000-01-23", True)
351
+ _test("2000-01-30", False)
352
+ _test("2000-02-01", False)
353
+ _test("2000-02-05", True)
tests/data/items.json CHANGED
@@ -289,5 +289,16 @@
289
289
  "medium": "NOT_SPECIFIED",
290
290
  "status": "CAN_CIRCULATE",
291
291
  "document": {}
292
+ },
293
+ {
294
+ "pid": "itemid-74",
295
+ "created_by": { "type": "script", "value": "demo" },
296
+ "barcode": "123456789-74",
297
+ "document_pid": "docid-17",
298
+ "internal_location_pid": "ilocid-1",
299
+ "circulation_restriction": "NO_RESTRICTION",
300
+ "medium": "NOT_SPECIFIED",
301
+ "status": "IN_BINDING",
302
+ "document": {}
292
303
  }
293
304
  ]