From 51cab8b4bfa0351314d51501f729414c46c5d5e3 Mon Sep 17 00:00:00 2001 From: Bertrand Zuchuat Date: Wed, 7 Jun 2023 08:45:40 +0200 Subject: [PATCH] item: add circulation information * Closes #2965. Co-Authored-by: Bertrand Zuchuat --- rero_ils/modules/items/api/circulation.py | 2 + rero_ils/modules/items/serializers/json.py | 17 +++ tests/api/items/test_items_serializer.py | 164 +++++++++++++++++++++ tests/api/test_serializers.py | 117 +-------------- 4 files changed, 185 insertions(+), 115 deletions(-) create mode 100644 tests/api/items/test_items_serializer.py diff --git a/rero_ils/modules/items/api/circulation.py b/rero_ils/modules/items/api/circulation.py index cfefb19ef1..9a367c38c7 100644 --- a/rero_ils/modules/items/api/circulation.py +++ b/rero_ils/modules/items/api/circulation.py @@ -1318,6 +1318,8 @@ def available(self): return False if self.circulation_category.get('negative_availability'): return False + if self.temp_item_type_negative_availability: + return False if self.is_issue and self.issue_status != ItemIssueStatus.RECEIVED: return False return True diff --git a/rero_ils/modules/items/serializers/json.py b/rero_ils/modules/items/serializers/json.py index 698005a84e..67b7f0af61 100644 --- a/rero_ils/modules/items/serializers/json.py +++ b/rero_ils/modules/items/serializers/json.py @@ -36,6 +36,16 @@ class ItemsJSONSerializer(JSONSerializer, CachedDataSerializerMixin): def _postprocess_search_hit(self, hit: dict) -> None: """Post-process each hit of a search result.""" + def _set_item_type_circulation_information(metadata, pid): + """Get Item type circulation information. + + :param: metadata: The item record. + :param: pid: the item type pid. + """ + record = self.get_resource(ItemTypesSearch(), pid) or {} + if circulation := record.get('circulation_information'): + metadata['item_type']['circulation_information'] = circulation + metadata = hit.get('metadata', {}) doc_pid = metadata.get('document').get('pid') document = self.get_resource(DocumentsSearch(), doc_pid) @@ -78,6 +88,13 @@ def _postprocess_search_hit(self, hit: dict) -> None: location = self.get_resource(LocationsSearch(), location_pid) metadata['location']['name'] = location.get('name') + # Try to serialize circulation information from best possible + # related `ItemType` resource if exists. + if itty_pid := metadata.get('temporary_item_type', {}).get('pid'): + itty_rec = self.get_resource(ItemTypesSearch(), itty_pid) or {} + if circulation := itty_rec.get('circulation_information'): + metadata['item_type']['circulation_information'] = circulation + super()._postprocess_search_hit(hit) def _postprocess_search_aggregations(self, aggregations: dict) -> None: diff --git a/tests/api/items/test_items_serializer.py b/tests/api/items/test_items_serializer.py new file mode 100644 index 0000000000..77710c27db --- /dev/null +++ b/tests/api/items/test_items_serializer.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2019-2023 RERO +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Tests REST items Serializer.""" + +from datetime import datetime + +from flask import url_for +from utils import get_csv, login_user + +from rero_ils.modules.utils import get_ref_for_pid + + +def test_serializers( + client, + db, + item_lib_martigny, # on shelf + document, + item_lib_fully, # on loan + csv_header, + json_header, + rero_json_header, + librarian_martigny, + loan_due_soon_martigny, + loc_public_martigny, + item_type_standard_martigny, + item_type_on_site_martigny +): + """Test Serializers.""" + login_user(client, librarian_martigny) + + item = item_lib_martigny + loc_ref = get_ref_for_pid('locations', loc_public_martigny.pid) + item.setdefault('temporary_location', {})['$ref'] = loc_ref + item.commit() + item.reindex() + + item_url = url_for( + 'invenio_records_rest.item_item', pid_value=item_lib_fully.pid) + response = client.get(item_url, headers=json_header) + assert response.status_code == 200 + assert response.json['metadata'].get('item_type', {}).get('$ref') + + item_url = url_for( + 'invenio_records_rest.item_item', pid_value=item_lib_martigny.pid) + response = client.get(item_url, headers=json_header) + assert response.status_code == 200 + assert response.json['metadata'].get('item_type', {}).get('$ref') + + item_url = url_for( + 'invenio_records_rest.item_item', + pid_value=item_lib_fully.pid, resolve=1) + response = client.get(item_url, headers=json_header) + data = response.json + assert data['metadata'].get('item_type', {}).get('pid') + # test if all key exist into response with a value + for key in ['created', 'updated', 'id', 'links', 'metadata']: + assert key in data + assert data[key] + + list_url = url_for('invenio_records_rest.item_list') + response = client.get(list_url, headers=rero_json_header) + data = response.json['hits']['hits'] + # check the due date: it should be an ISO date + item_with_due_date = [ + res['metadata'] for res in data + if res['metadata'].get('availability', {}).get('due_date') + ][0] + due_date = item_with_due_date['availability']['due_date'] + assert datetime.fromisoformat(due_date).year is not None + assert response.status_code == 200 + + list_url = url_for('api_item.inventory_search') + response = client.get(list_url, headers=csv_header) + assert response.status_code == 200 + data = get_csv(response) + assert data + fields = [ + 'item_pid', 'item_create_date', 'document_pid', 'document_title', + 'document_creator', 'document_main_type', 'document_sub_type', + 'document_masked', 'document_isbn', 'document_issn', + 'document_series_statement', 'document_edition_statement', + 'document_publication_year', 'document_publisher', + 'document_local_field_1', 'document_local_field_2', + 'document_local_field_3', 'document_local_field_4', + 'document_local_field_5', 'document_local_field_6', + 'document_local_field_7', 'document_local_field_8', + 'document_local_field_9', 'document_local_field_10', + 'item_acquisition_date', 'item_barcode', 'item_call_number', + 'item_second_call_number', 'item_legacy_checkout_count', + 'item_type', 'item_library_name', 'item_location_name', + 'item_pac_code', 'item_holding_pid', 'item_price', 'item_status', + 'item_item_type', 'item_general_note', 'item_staff_note', + 'item_checkin_note', 'item_checkout_note', 'item_acquisition_note', + 'item_binding_note', 'item_condition_note', 'item_patrimonial_note', + 'item_provenance_note', 'temporary_item_type', + 'temporary_item_type_expiry_date', 'item_masked', + 'item_enumerationAndChronology', 'item_local_field_1', + 'item_local_field_2', 'item_local_field_3', 'item_local_field_4', + 'item_local_field_5', 'item_local_field_6', 'item_local_field_7', + 'item_local_field_8', 'item_local_field_9', 'item_local_field_10', + 'issue_status', 'issue_status_date', 'issue_claims_count', + 'issue_expected_date', 'issue_regular', 'item_checkouts_count', + 'item_renewals_count', 'last_transaction_date', 'last_checkout_date' + ] + for field in fields: + assert field in data + + # test provisionActivity without type bf:Publication + document['provisionActivity'][0]['type'] = 'bf:Manufacture' + document.commit() + document.reindex() + + list_url = url_for('api_item.inventory_search') + response = client.get(list_url, headers=csv_header) + assert response.status_code == 200 + data = get_csv(response) + assert data + + document['provisionActivity'][0]['type'] = 'bf:Publication' + db.session.rollback() + # restore initial item on Elasticsearch + item.reindex() + + # with temporary_item_type + item_type = item_type_on_site_martigny + circulation = [ + {'label': 'On site DE', 'language': 'de'}, + {'label': 'On site EN', 'language': 'en'}, + {'label': 'On site FR', 'language': 'fr'}, + {'label': 'On site IT', 'language': 'it'} + ] + item_type['circulation_information'] = circulation + item_type.commit() + item_type.reindex() + item['temporary_item_type'] = { + '$ref': get_ref_for_pid('itty', item_type.pid) + } + item.commit() + item.reindex() + + list_url = url_for('invenio_records_rest.item_list', q=f'pid:{item.pid}') + response = client.get(list_url, headers=rero_json_header) + data = response.json['hits']['hits'] + assert circulation == \ + data[0]['metadata']['item_type']['circulation_information'] + + db.session.rollback() + item_type.reindex() + item.reindex() diff --git a/tests/api/test_serializers.py b/tests/api/test_serializers.py index 61126246f0..0a817fdf46 100644 --- a/tests/api/test_serializers.py +++ b/tests/api/test_serializers.py @@ -17,17 +17,14 @@ """Tests Serializers.""" -from datetime import datetime - import mock from flask import url_for -from utils import VerifyRecordPermissionPatch, flush_index, get_csv, \ - get_json, item_record_to_a_specific_loan_state, login_user +from utils import VerifyRecordPermissionPatch, flush_index, get_json, \ + item_record_to_a_specific_loan_state, login_user from rero_ils.modules.loans.models import LoanState from rero_ils.modules.locations.api import LocationsSearch from rero_ils.modules.operation_logs.api import OperationLogsSearch -from rero_ils.modules.utils import get_ref_for_pid def test_operation_logs_serializers( @@ -123,116 +120,6 @@ def test_document_and_holdings_serializers( assert record.get('circulation_category', {}).get('name') -def test_items_serializers( - client, - item_lib_martigny, # on shelf - document, - item_lib_fully, # on loan - csv_header, - json_header, - rero_json_header, - patron_martigny, - librarian_martigny, - librarian_sion, - loan_pending_martigny, - loan_due_soon_martigny, - coll_martigny_1, - loc_public_martigny -): - """Test record retrieval.""" - login_user(client, librarian_martigny) - - item = item_lib_martigny - loc_ref = get_ref_for_pid('locations', loc_public_martigny.pid) - item.setdefault('temporary_location', {})['$ref'] = loc_ref - item.update(item, dbcommit=True, reindex=True) - - item_url = url_for( - 'invenio_records_rest.item_item', pid_value=item_lib_fully.pid) - response = client.get(item_url, headers=json_header) - assert response.status_code == 200 - data = get_json(response) - assert data['metadata'].get('item_type', {}).get('$ref') - - item_url = url_for( - 'invenio_records_rest.item_item', pid_value=item_lib_martigny.pid) - response = client.get(item_url, headers=json_header) - assert response.status_code == 200 - data = get_json(response) - assert data['metadata'].get('item_type', {}).get('$ref') - - item_url = url_for( - 'invenio_records_rest.item_item', - pid_value=item_lib_fully.pid, resolve=1) - response = client.get(item_url, headers=json_header) - data = get_json(response) - assert data['metadata'].get('item_type', {}).get('pid') - # test if all key exist into response with a value - for key in ['created', 'updated', 'id', 'links', 'metadata']: - assert key in data - assert data[key] - - list_url = url_for('invenio_records_rest.item_list') - response = client.get(list_url, headers=rero_json_header) - data = get_json(response)['hits']['hits'] - # check the due date: it should be an ISO date - item_with_due_date = [ - res['metadata'] for res in data - if res['metadata'].get('availability', {}).get('due_date') - ][0] - due_date = item_with_due_date['availability']['due_date'] - assert datetime.fromisoformat(due_date).year is not None - assert response.status_code == 200 - - list_url = url_for('api_item.inventory_search') - response = client.get(list_url, headers=csv_header) - assert response.status_code == 200 - data = get_csv(response) - assert data - fields = [ - 'item_pid', 'item_create_date', 'document_pid', 'document_title', - 'document_creator', 'document_main_type', 'document_sub_type', - 'document_masked', 'document_isbn', 'document_issn', - 'document_series_statement', 'document_edition_statement', - 'document_publication_year', 'document_publisher', - 'document_local_field_1', 'document_local_field_2', - 'document_local_field_3', 'document_local_field_4', - 'document_local_field_5', 'document_local_field_6', - 'document_local_field_7', 'document_local_field_8', - 'document_local_field_9', 'document_local_field_10', - 'item_acquisition_date', 'item_barcode', 'item_call_number', - 'item_second_call_number', 'item_legacy_checkout_count', - 'item_type', 'item_library_name', 'item_location_name', - 'item_pac_code', 'item_holding_pid', 'item_price', 'item_status', - 'item_item_type', 'item_general_note', 'item_staff_note', - 'item_checkin_note', 'item_checkout_note', 'item_acquisition_note', - 'item_binding_note', 'item_condition_note', 'item_patrimonial_note', - 'item_provenance_note', 'temporary_item_type', - 'temporary_item_type_expiry_date', 'item_masked', - 'item_enumerationAndChronology', 'item_local_field_1', - 'item_local_field_2', 'item_local_field_3', 'item_local_field_4', - 'item_local_field_5', 'item_local_field_6', 'item_local_field_7', - 'item_local_field_8', 'item_local_field_9', 'item_local_field_10', - 'issue_status', 'issue_status_date', 'issue_claims_count', - 'issue_expected_date', 'issue_regular', 'item_checkouts_count', - 'item_renewals_count', 'last_transaction_date', 'last_checkout_date' - ] - assert all(field in data for field in fields) - - # test provisionActivity without type bf:Publication - document['provisionActivity'][0]['type'] = 'bf:Manufacture' - document.update(document, True, True, True) - - list_url = url_for('api_item.inventory_search') - response = client.get(list_url, headers=csv_header) - assert response.status_code == 200 - data = get_csv(response) - assert data - - document['provisionActivity'][0]['type'] = 'bf:Publication' - document.update(document, True, True, True) - - def test_loans_serializers( client, rero_json_header,