diff --git a/invenio_app_ils/circulation/config.py b/invenio_app_ils/circulation/config.py index 5a26649b7..ae72dac37 100644 --- a/invenio_app_ils/circulation/config.py +++ b/invenio_app_ils/circulation/config.py @@ -74,8 +74,8 @@ ILS_CIRCULATION_NOTIFICATION_OVERDUE_REMINDER_INTERVAL = 3 #: The maximum duration of a loan request ILS_CIRCULATION_LOAN_REQUEST_DURATION_DAYS = 60 -#: Loan Request Offset Value -ILS_CIRCULATION_LOAD_REQUEST_OFFSET = 0 +#: The minimum number of days after which a loan can start from the date of request +ILS_CIRCULATION_LOAN_REQUEST_OFFSET = 0 #: Period of time in days, before loans expire, for notifications etc. ILS_CIRCULATION_LOAN_WILL_EXPIRE_DAYS = 7 #: Optional delivery methods when requesting a new loan. Set to empty object to diff --git a/invenio_app_ils/circulation/loaders/schemas/json/loan_request.py b/invenio_app_ils/circulation/loaders/schemas/json/loan_request.py index 71e0777b0..fcd22c889 100644 --- a/invenio_app_ils/circulation/loaders/schemas/json/loan_request.py +++ b/invenio_app_ils/circulation/loaders/schemas/json/loan_request.py @@ -68,7 +68,7 @@ class LoanRequestSchemaV1(LoanBaseSchemaV1): @validates_schema() def validates_schema(self, data, **kwargs): - """Validate schema delivery field.""" + """Validate schema fields.""" delivery = data.get("delivery") # if delivery methods is configured, it has to be a mandatory field if ( @@ -77,13 +77,24 @@ def validates_schema(self, data, **kwargs): ): raise ValidationError("Delivery is required.", "delivery") + if current_app.config["ILS_CIRCULATION_LOAN_REQUEST_OFFSET"] < 0: + raise ValidationError( + { + "ILS_CIRCULATION_LOAN_REQUEST_OFFSET": [ + "The minimum days after which a loan can start cannot be negative." + ] + } + ) + @post_load() def postload_checks(self, data, **kwargs): """Validate dates values.""" start = arrow.get(data["request_start_date"]).date() end = arrow.get(data["request_expire_date"]).date() duration_days = current_app.config["ILS_CIRCULATION_LOAN_REQUEST_DURATION_DAYS"] - loan_request_offset = timedelta(days=current_app.config["ILS_CIRCULATION_LOAD_REQUEST_OFFSET"]) + loan_request_offset = timedelta( + days=current_app.config["ILS_CIRCULATION_LOAN_REQUEST_OFFSET"] + ) duration = timedelta(days=duration_days) if end < start: @@ -108,11 +119,14 @@ def postload_checks(self, data, **kwargs): } ) elif end - start < loan_request_offset: - message = "The request end date can only be {} days after the request start date.".format(loan_request_offset.days) + message = ( + "The requested start date must be at least {} days from today.".format( + loan_request_offset.days + ) + ) raise ValidationError( { "request_start_date": [message], - "request_expire_date": [message], } ) return data diff --git a/tests/api/circulation/test_loan_request.py b/tests/api/circulation/test_loan_request.py index 729efc14e..3c0c9a849 100644 --- a/tests/api/circulation/test_loan_request.py +++ b/tests/api/circulation/test_loan_request.py @@ -226,3 +226,73 @@ def test_request_loan_with_active_loan_or_loan_request( params["transaction_user_pid"] = str(user.id) res = client.post(url, headers=json_headers, data=json.dumps(params)) assert res.status_code == 400 + + +def test_request_loan_minimum_days(app, client, json_headers, users, testdata): + """Test that a patron can only request a loan after a set minimum days""" + url = url_for("invenio_app_ils_circulation.loan_request") + user = user_login(client, "patron1", users) + app.config["ILS_CIRCULATION_LOAN_REQUEST_OFFSET"] = 4 + params = deepcopy(NEW_LOAN) + + now = arrow.utcnow() + start_date = now.date().isoformat() + params["request_start_date"] = start_date + params["transaction_user_pid"] = str(user.id) + + params["document_pid"] = "docid-4" + end_date = (now + timedelta(days=2)).date().isoformat() # FAIL + params["request_expire_date"] = end_date + res = client.post(url, headers=json_headers, data=json.dumps(params)) + assert res.status_code == 400 + assert res.get_json()["message"] == "Validation error." + + params["document_pid"] = "docid-5" + end_date = (now - timedelta(days=2)).date().isoformat() # FAIL + params["request_expire_date"] = end_date + res = client.post(url, headers=json_headers, data=json.dumps(params)) + assert res.status_code == 400 + assert res.get_json()["message"] == "Validation error." + + params["document_pid"] = "docid-6" + end_date = now.date().isoformat() # FAIL + params["request_expire_date"] = end_date + res = client.post(url, headers=json_headers, data=json.dumps(params)) + assert res.status_code == 400 + assert res.get_json()["message"] == "Validation error." + + params["document_pid"] = "docid-7" + end_date = (now + timedelta(days=4)).date().isoformat() # PASS + params["request_expire_date"] = end_date + res = client.post(url, headers=json_headers, data=json.dumps(params)) + assert res.status_code == 202 + loan = res.get_json()["metadata"] + assert loan["state"] == "PENDING" + assert loan["document_pid"] == params["document_pid"] + assert loan["transaction_date"] + + params["document_pid"] = "docid-8" + end_date = (now + timedelta(days=8)).date().isoformat() # PASS + params["request_expire_date"] = end_date + res = client.post(url, headers=json_headers, data=json.dumps(params)) + assert res.status_code == 202 + loan = res.get_json()["metadata"] + assert loan["state"] == "PENDING" + assert loan["document_pid"] == params["document_pid"] + assert loan["transaction_date"] + + app.config["ILS_CIRCULATION_LOAN_REQUEST_OFFSET"] = -3 + + params["document_pid"] = "docid-9" + end_date = now.date().isoformat() # FAIL + params["request_expire_date"] = end_date + res = client.post(url, headers=json_headers, data=json.dumps(params)) + assert res.status_code == 400 + assert res.get_json()["message"] == "Validation error." + + params["document_pid"] = "docid-10" + end_date = (now - timedelta(days=2)).date().isoformat() # FAIL + params["request_expire_date"] = end_date + res = client.post(url, headers=json_headers, data=json.dumps(params)) + assert res.status_code == 400 + assert res.get_json()["message"] == "Validation error."