diff --git a/data/role_policies.json b/data/role_policies.json index 9994bd0d26..f6b2f2dc87 100644 --- a/data/role_policies.json +++ b/data/role_policies.json @@ -680,39 +680,48 @@ ], "stat-access": [ "pro_full_permissions", - "pro_statistic_manager" + "pro_statistic_manager", + "pro_library_administrator" ], "stat-search": [ "pro_full_permissions", - "pro_statistic_manager" + "pro_statistic_manager", + "pro_library_administrator" ], "stat-read": [ "pro_full_permissions", - "pro_statistic_manager" + "pro_statistic_manager", + "pro_library_administrator" ], "stat_cfg-access": [ "pro_full_permissions", - "pro_statistic_manager" + "pro_statistic_manager", + "pro_library_administrator" ], "stat_cfg-search": [ "pro_full_permissions", - "pro_statistic_manager" + "pro_statistic_manager", + "pro_library_administrator" ], "stat_cfg-read": [ "pro_full_permissions", - "pro_statistic_manager" + "pro_statistic_manager", + "pro_library_administrator" ], "stat_cfg-create": [ "pro_full_permissions", - "pro_statistic_manager" + "pro_statistic_manager", + "pro_library_administrator" ], "stat_cfg-update": [ "pro_full_permissions", - "pro_statistic_manager" + "pro_statistic_manager", + "pro_library_administrator" ], "stat_cfg-delete": [ "pro_full_permissions", - "pro_statistic_manager" + "pro_statistic_manager", + "pro_library_administrator" ], "tmpl-access": [ "pro_full_permissions", @@ -831,14 +840,17 @@ ], "locent-update": [ "pro_full_permissions", - "pro_entity_manager" + "pro_entity_manager", + "pro_library_administrator" ], "locent-create": [ "pro_full_permissions", - "pro_entity_manager" + "pro_entity_manager", + "pro_library_administrator" ], "locent-delete": [ "pro_full_permissions", - "pro_entity_manager" + "pro_entity_manager", + "pro_library_administrator" ] } diff --git a/data/stats_cfg.json b/data/stats_cfg.json index d9e911d282..5ee161365b 100644 --- a/data/stats_cfg.json +++ b/data/stats_cfg.json @@ -1,7 +1,7 @@ [ { "pid": "1", - "name": "organisation 1, no distributions", + "name": "library 1, no distributions", "description": "Statistics configuration with no distributions", "category": { "type": "catalogue", @@ -9,15 +9,15 @@ "type": "number_of_documents" } }, - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/1" }, "frequency": "month", "is_active": true }, { "pid": "2", - "name": "organisation 1, circulation, number of checkouts, time range month", + "name": "library 1, circulation, number of checkouts, time range month", "description": "Statistics configuration with 1 distribution", "category": { "type": "circulation", @@ -28,15 +28,15 @@ ] } }, - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/1" }, "frequency": "month", "is_active": true }, { "pid": "3", - "name": "organisation 1, number of checkouts, library", + "name": "library 2, number of checkouts, library", "description": "Statistics configuration with 1 distribution", "category": { "type": "circulation", @@ -47,15 +47,15 @@ ] } }, - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/2" }, "frequency": "month", "is_active": true }, { "pid": "4", - "name": "organisation 1, 2 distributions", + "name": "library 2, 2 distributions", "description": "Statistics configuration with 2 distributions", "category": { "type": "circulation", @@ -67,15 +67,15 @@ ] } }, - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/2" }, "frequency": "month", "is_active": true }, { "pid": "5", - "name": "organisation 1, 2 distributions, period 1 year", + "name": "library 3, 2 distributions, period 1 year", "description": "Statistics configuration with 2 distributions", "category": { "type": "circulation", @@ -88,15 +88,15 @@ "period": "year" } }, - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/4" }, "frequency": "month", "is_active": true }, { "pid": "6", - "name": "organisation 1, 2 distributions, status disabled", + "name": "library 3, 2 distributions, status disabled", "description": "Statistics configuration with 2 distributions", "category": { "type": "circulation", @@ -108,15 +108,15 @@ ] } }, - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/4" }, "frequency": "month", "is_active": false }, { "pid": "7", - "name": "organisation 2, no distributions", + "name": "library 5, no distributions", "description": "Statistics configuration with no distributions", "category": { "type": "catalogue", @@ -124,15 +124,15 @@ "type": "number_of_documents" } }, - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/2" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/5" }, "frequency": "month", "is_active": true }, { "pid": "8", - "name": "organisation 2, 1 distribution", + "name": "library 5, 1 distribution", "description": "Statistics configuration with 1 distribution", "category": { "type": "circulation", @@ -143,15 +143,15 @@ ] } }, - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/2" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/5" }, "frequency": "month", "is_active": true }, { "pid": "9", - "name": "organisation 2, 1 distribution", + "name": "library 5, 1 distribution", "description": "Statistics configuration with 1 distribution", "category": { "type": "circulation", @@ -162,15 +162,15 @@ ] } }, - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/2" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/5" }, "frequency": "month", "is_active": true }, { "pid": "10", - "name": "organisation 2, 2 distributions", + "name": "library 5, 2 distributions", "description": "Statistics configuration with 2 distributions", "category": { "type": "circulation", @@ -182,15 +182,15 @@ ] } }, - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/2" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/5" }, "frequency": "month", "is_active": true }, { "pid": "11", - "name": "organisation 2, 2 distributions, period 1 year", + "name": "library 5, 2 distributions, period 1 year", "description": "Statistics configuration with 2 distributions", "category": { "type": "circulation", @@ -203,15 +203,15 @@ "period": "year" } }, - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/2" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/5" }, "frequency": "month", "is_active": true }, { "pid": "12", - "name": "organisation 2, 2 distributions, status disabled", + "name": "library 5, 2 distributions, status disabled", "description": "Statistics configuration with 1 distribution", "category": { "type": "circulation", @@ -223,10 +223,10 @@ ] } }, - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/2" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/5" }, "frequency": "month", "is_active": false } -] +] \ No newline at end of file diff --git a/data/users.json b/data/users.json index 1c0c6270f0..19a568c201 100644 --- a/data/users.json +++ b/data/users.json @@ -160,7 +160,8 @@ "pro_read_only", "pro_catalog_manager", "pro_circulation_manager", - "pro_user_manager" + "pro_user_manager", + "pro_statistic_manager" ], "libraries": [ { diff --git a/rero_ils/config.py b/rero_ils/config.py index b5ff221dec..995e3f056d 100644 --- a/rero_ils/config.py +++ b/rero_ils/config.py @@ -839,10 +839,12 @@ def _(x): 'json': 'application/json' }, search_serializers_aliases={ - 'json': 'application/json' + 'json': 'application/json', + 'rero+json': 'application/json', }, search_serializers={ - 'application/json': 'rero_ils.modules.serializers:json_v1_search' + 'application/json': 'rero_ils.modules.serializers:json_v1_search', + 'application/rero+json': 'rero_ils.modules.stats_cfg.serializers:json_search' }, list_route='/stats_cfg/', record_loaders={ @@ -2336,11 +2338,23 @@ def _(x): field='category.type', size=RERO_ILS_AGGREGATION_SIZE.get( 'stats_cfg', RERO_ILS_DEFAULT_AGGREGATION_SIZE) + ), + aggs=dict( + indicator=dict(terms=dict(field='category.indicator.type', size=DOCUMENTS_AGGREGATION_SIZE)) ) - ) + ), + frequency=dict( + terms=dict(field='frequency', size=DOCUMENTS_AGGREGATION_SIZE), + + ), + library=dict(terms=dict(field='library.pid', size=RERO_ILS_DEFAULT_AGGREGATION_SIZE)) ), filters={ - _('category'): and_term_filter('category.type') + _('category'): and_term_filter('category.type'), + _('indicator'): and_term_filter('category.indicator.type'), + _('frequency'): and_term_filter('frequency'), + _('library'): and_term_filter('library.pid'), + _('active'): and_term_filter('is_active') } ) ) @@ -2909,7 +2923,7 @@ def _(x): template='rero_ils/detailed_view_stats.html', record_class='rero_ils.modules.stats.api.api:Stat', view_imp='rero_ils.modules.stats.views.stats_view_method', - permission_factory_imp='rero_ils.permissions.admin_permission_factory', + permission_factory_imp='rero_ils.modules.stats.permissions:stats_ui_permission_factory', ) } diff --git a/rero_ils/modules/libraries/api.py b/rero_ils/modules/libraries/api.py index f83385b913..33aa81e481 100644 --- a/rero_ils/modules/libraries/api.py +++ b/rero_ils/modules/libraries/api.py @@ -24,6 +24,7 @@ import pytz from dateutil import parser from dateutil.rrule import FREQNAMES, rrule +from elasticsearch_dsl import Q from flask_babelex import gettext as _ from rero_ils.modules.api import IlsRecord, IlsRecordsIndexer, IlsRecordsSearch @@ -31,6 +32,7 @@ from rero_ils.modules.locations.api import LocationsSearch from rero_ils.modules.minters import id_minter from rero_ils.modules.providers import Provider +from rero_ils.modules.stats_cfg.api import StatsConfigurationSearch from rero_ils.modules.users.models import UserRole from rero_ils.modules.utils import date_string_to_utc, \ extracted_data_from_ref, sorted_pids, strtotime @@ -377,6 +379,11 @@ def get_links_to_me(self, get_pids=False): AcqReceiptsSearch from rero_ils.modules.patrons.api import PatronsSearch links = {} + stat_cfg_query = StatsConfigurationSearch()\ + .filter( + Q('term', library__pid=self.pid) | + Q('term', filter_by_libraries__pid=self.pid) + ) location_query = LocationsSearch() \ .filter('term', library__pid=self.pid) patron_query = PatronsSearch() \ @@ -388,16 +395,20 @@ def get_links_to_me(self, get_pids=False): locations = sorted_pids(location_query) librarians = sorted_pids(patron_query) receipts = sorted_pids(receipt_query) + stats_cfg = sorted_pids(stat_cfg_query) else: locations = location_query.count() librarians = patron_query.count() receipts = receipt_query.count() + stats_cfg = stat_cfg_query.count() if locations: links['locations'] = locations if librarians: links['patrons'] = librarians if receipts: links['acq_receipts'] = receipts + if stats_cfg: + links['stats_cfg'] = stats_cfg return links def reasons_not_to_delete(self): diff --git a/rero_ils/modules/operation_logs/es_templates/v7/operation_logs.json b/rero_ils/modules/operation_logs/es_templates/v7/operation_logs.json index c12da2580c..0348be84f0 100644 --- a/rero_ils/modules/operation_logs/es_templates/v7/operation_logs.json +++ b/rero_ils/modules/operation_logs/es_templates/v7/operation_logs.json @@ -195,7 +195,12 @@ "type": "keyword" }, "location_name": { - "type": "text" + "type": "text", + "fields": { + "raw": { + "type": "keyword" + } + } } } }, diff --git a/rero_ils/modules/stats/api/indicators/__init__.py b/rero_ils/modules/stats/api/indicators/__init__.py index 34045cbd53..76f4a9c09b 100644 --- a/rero_ils/modules/stats/api/indicators/__init__.py +++ b/rero_ils/modules/stats/api/indicators/__init__.py @@ -22,3 +22,4 @@ from .circulation import * from .others import * from .patron import * +from .requests import * diff --git a/rero_ils/modules/stats/api/indicators/circulation.py b/rero_ils/modules/stats/api/indicators/circulation.py index 4fc2ed2fd7..b0f2125d11 100644 --- a/rero_ils/modules/stats/api/indicators/circulation.py +++ b/rero_ils/modules/stats/api/indicators/circulation.py @@ -118,6 +118,11 @@ def aggregation(self, distribution): 'terms', field='loan.item.library_pid', size=self.cfg.aggs_size + ), + 'owning_location': A( + 'terms', + field='loan.item.holding.location_name.raw', + size=self.cfg.aggs_size ) } return cfg[distribution] @@ -141,6 +146,7 @@ def label(self, distribution, bucket): 'patron_postal_code': lambda: bucket.key, 'transaction_channel': lambda: bucket.key, 'owning_library': lambda: - f'{self.cfg.libraries[bucket.key]} ({bucket.key})' + f'{self.cfg.libraries[bucket.key]} ({bucket.key})', + 'owning_location': lambda: bucket.key, } return cfg[distribution]() diff --git a/rero_ils/modules/stats/api/indicators/others.py b/rero_ils/modules/stats/api/indicators/others.py index a3bf26d677..df0a5927f3 100644 --- a/rero_ils/modules/stats/api/indicators/others.py +++ b/rero_ils/modules/stats/api/indicators/others.py @@ -55,7 +55,7 @@ def aggregation(self, distribution): :returns: an elasticsearch aggregation object """ cfg = { - 'library': A( + 'owning_library': A( 'terms', field='holdings.organisation.library_pid', size=self.cfg.aggs_size, @@ -91,7 +91,7 @@ def label(self, distribution, bucket): :rtype: str """ cfg = { - 'library': lambda: + 'owning_library': lambda: f'{self.cfg.libraries[bucket.key]} ({bucket.key})', 'created_month': lambda: bucket.key_as_string, 'created_year': lambda: bucket.key_as_string, @@ -124,7 +124,7 @@ def aggregation(self, distribution): :returns: an elasticsearch aggregation object """ cfg = { - 'library': A( + 'owning_library': A( 'terms', field='library.pid', size=self.cfg.aggs_size, @@ -154,7 +154,7 @@ def label(self, distribution, bucket): :rtype: str """ cfg = { - 'library': lambda: + 'owning_library': lambda: f'{self.cfg.libraries[bucket.key]} ({bucket.key})', 'created_month': lambda: bucket.key_as_string, 'created_year': lambda: bucket.key_as_string @@ -185,13 +185,13 @@ def aggregation(self, distribution): :returns: an elasticsearch aggregation object """ cfg = { - 'library': A( + 'owning_library': A( 'terms', field='library.pid', size=self.cfg.aggs_size, include=self.cfg.lib_pids ), - 'location': A( + 'owning_location': A( 'terms', field='location.pid', size=self.cfg.aggs_size, @@ -202,6 +202,16 @@ def aggregation(self, distribution): field='type', size=self.cfg.aggs_size ), + 'document_type': A( + 'terms', + field='document.document_type.main_type', + size=self.cfg.aggs_size + ), + 'document_subtype': A( + 'terms', + field='document.document_type.subtype', + size=self.cfg.aggs_size + ), 'created_month': A( 'date_histogram', field='_created', @@ -226,11 +236,13 @@ def label(self, distribution, bucket): :rtype: str """ cfg = { - 'library': lambda: + 'owning_library': lambda: f'{self.cfg.libraries[bucket.key]} ({bucket.key})', - 'location': lambda: + 'owning_location': lambda: f'{self.cfg.locations[bucket.key]} ({bucket.key})', 'type': lambda: bucket.key, + 'document_type': lambda: bucket.key, + 'document_subtype': lambda: bucket.key, 'created_month': lambda: bucket.key_as_string, 'created_year': lambda: bucket.key_as_string } @@ -265,7 +277,7 @@ def aggregation(self, distribution): :returns: an elasticsearch aggregation object """ cfg = { - 'library': A( + 'owning_library': A( 'terms', field='record.library_pid', size=self.cfg.aggs_size @@ -294,7 +306,7 @@ def label(self, distribution, bucket): :rtype: str """ cfg = { - 'library': lambda: + 'owning_library': lambda: f'{self.cfg.libraries[bucket.key]} ({bucket.key})', 'action_month': lambda: bucket.key_as_string, 'action_year': lambda: bucket.key_as_string diff --git a/rero_ils/modules/stats/api/indicators/requests.py b/rero_ils/modules/stats/api/indicators/requests.py new file mode 100644 index 0000000000..01e6e65eed --- /dev/null +++ b/rero_ils/modules/stats/api/indicators/requests.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2019-2023 RERO +# Copyright (C) 2019-2023 UCLouvain +# +# 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 . + +"""Circulation Requests Indicator Report Configuration.""" + + +from elasticsearch_dsl.aggs import A + +from .circulation import NumberOfCirculationCfg + + +class NumberOfRequestsCfg(NumberOfCirculationCfg): + """Number of circulation action based on trigger.""" + + def aggregation(self, distribution): + """Elasticsearch Aggregation configuration to compute distributions. + + :param distrubtion: str - report distrubtion name + :returns: an elasticsearch aggregation object + """ + cfg = { + 'pickup_location': A( + 'terms', + field='loan.pickup_location.pid', + size=self.cfg.aggs_size + ) + } + if agg := cfg.get(distribution): + return agg + return super().aggregation(distribution) + + def label(self, distribution, bucket): + """Column/Raw label transformations. + + :param distrubtion: str - the report distrubtion name + :param bucket: the elasticsearch aggregation bucket + :returns: the label + :rtype: str + """ + cfg = { + 'pickup_location': lambda: + f'{self.cfg.locations[bucket.key]} ({bucket.key})' + } + if label_fn := cfg.get(distribution): + return label_fn() + return super().label(distribution=distribution, bucket=bucket) diff --git a/rero_ils/modules/stats/api/report.py b/rero_ils/modules/stats/api/report.py index 3d89f9cb3f..a85ad58327 100644 --- a/rero_ils/modules/stats/api/report.py +++ b/rero_ils/modules/stats/api/report.py @@ -25,11 +25,13 @@ from rero_ils.modules.libraries.api import LibrariesSearch from rero_ils.modules.locations.api import LocationsSearch from rero_ils.modules.patron_types.api import PatronTypesSearch +from rero_ils.modules.stats_cfg.api import StatConfiguration from rero_ils.modules.utils import extracted_data_from_ref from .indicators import NumberOfActivePatronsCfg, NumberOfCirculationCfg, \ NumberOfDeletedItemsCfg, NumberOfDocumentsCfg, NumberOfILLRequests, \ - NumberOfItemsCfg, NumberOfPatronsCfg, NumberOfSerialHoldingsCfg + NumberOfItemsCfg, NumberOfPatronsCfg, NumberOfRequestsCfg, \ + NumberOfSerialHoldingsCfg from ..api.api import Stat from ..models import StatType @@ -42,13 +44,15 @@ def __init__(self, config): Set variables to create report. """ + if not isinstance(config, StatConfiguration): + config = StatConfiguration(data=config) self.config = config self.is_active = config.get('is_active', False) self.indicator = config['category']['indicator']['type'] self.period = config['category']['indicator'].get('period') self.distributions = config[ 'category']['indicator'].get('distributions', []) - self.org_pid = extracted_data_from_ref(config['organisation']) + self.org_pid = config.organisation_pid self.filter_by_libraries = [] for library in config.get('filter_by_libraries', []): self.filter_by_libraries.append(extracted_data_from_ref(library)) @@ -88,8 +92,8 @@ def indicator_cfg(self): 'number_of_checkins': NumberOfCirculationCfg(self, 'checkin'), 'number_of_checkouts': NumberOfCirculationCfg(self, 'checkout'), 'number_of_extends': NumberOfCirculationCfg(self, 'extend'), - 'number_of_requests': NumberOfCirculationCfg(self, 'request'), - 'number_of_validate_requests': NumberOfCirculationCfg( + 'number_of_requests': NumberOfRequestsCfg(self, 'request'), + 'number_of_validate_requests': NumberOfRequestsCfg( self, 'validate_request'), 'number_of_patrons': NumberOfPatronsCfg(self), 'number_of_active_patrons': NumberOfActivePatronsCfg(self) diff --git a/rero_ils/modules/stats/extensions.py b/rero_ils/modules/stats/extensions.py index 2073933b60..b8237e406b 100644 --- a/rero_ils/modules/stats/extensions.py +++ b/rero_ils/modules/stats/extensions.py @@ -20,7 +20,9 @@ from invenio_records.extensions import RecordExtension +from rero_ils.modules.libraries.api import Library from rero_ils.modules.patrons.api import current_librarian +from rero_ils.modules.utils import extracted_data_from_ref from .models import StatType @@ -41,8 +43,14 @@ def pre_dump(self, record, data, dumper=None): :param dumper: the dumper class used to dump the record. """ # to filter the search list results - if org := record.get('config', {}).get('organisation'): - record['organisation'] = org + if lib := record.get('config', {}).get('library'): + lib_pid = ( + lib.get('pid') + or extracted_data_from_ref(lib.get('$ref'))) + org_pid = Library.get_record_by_pid(lib_pid).organisation_pid + record['organisation'] = { + 'pid': org_pid + } if not current_librarian: return diff --git a/rero_ils/modules/stats/mappings/v7/stats/stat-v0.0.1.json b/rero_ils/modules/stats/mappings/v7/stats/stat-v0.0.1.json index 703d3298d8..d0ba4fca63 100644 --- a/rero_ils/modules/stats/mappings/v7/stats/stat-v0.0.1.json +++ b/rero_ils/modules/stats/mappings/v7/stats/stat-v0.0.1.json @@ -15,9 +15,6 @@ "organisation": { "type": "object", "properties": { - "type": { - "type": "keyword" - }, "pid": { "type": "keyword" } @@ -49,7 +46,7 @@ "is_active": { "type": "boolean" }, - "organisation": { + "library": { "type": "object", "properties": { "type": { diff --git a/rero_ils/modules/stats_cfg/api.py b/rero_ils/modules/stats_cfg/api.py index 843d75d8e1..869fd51d97 100644 --- a/rero_ils/modules/stats_cfg/api.py +++ b/rero_ils/modules/stats_cfg/api.py @@ -22,7 +22,9 @@ from rero_ils.modules.fetchers import id_fetcher from rero_ils.modules.minters import id_minter from rero_ils.modules.providers import Provider +from rero_ils.modules.utils import extracted_data_from_ref +from .dumpers import indexer_dumper from .models import StatCfgIdentifier, StatCfgMetadata # provider @@ -58,6 +60,7 @@ class StatConfiguration(IlsRecord): fetcher = stat_cfg_id_fetcher provider = StatCfgProvider model_cls = StatCfgMetadata + enable_jsonref = False def get_links_to_me(self, get_pids=False): """Record links. @@ -92,16 +95,28 @@ def reasons_not_to_delete(self): """ cannot_delete = {} # It is not possible to delete configuration if there are reports. - links = self.get_links_to_me(self.pid) + links = self.get_links_to_me() if links: cannot_delete['links'] = links return cannot_delete + @property + def organisation_pid(self): + """Shortcut for organisation pid.""" + library = extracted_data_from_ref(self.get('library'), data='record') + return library.organisation_pid + + @property + def library_pid(self): + """Shortcut for library pid.""" + return extracted_data_from_ref(self.get('library')) + class StatsConfigurationIndexer(IlsRecordsIndexer): """Indexing stats configuration in Elasticsearch.""" record_cls = StatConfiguration + record_dumper = indexer_dumper def bulk_index(self, record_id_iterator): """Bulk index records. diff --git a/rero_ils/modules/stats_cfg/dumpers.py b/rero_ils/modules/stats_cfg/dumpers.py new file mode 100644 index 0000000000..b0fe883085 --- /dev/null +++ b/rero_ils/modules/stats_cfg/dumpers.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2019-2022 RERO +# Copyright (C) 2019-2022 UCLouvain +# +# 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 . + +"""Indexing dumper.""" + +from invenio_records.dumpers import Dumper + +from rero_ils.modules.commons.dumpers import MultiDumper, ReplaceRefsDumper + + +class IndexerDumper(Dumper): + """Stat configuration dumper.""" + + def dump(self, record, data): + """Dump a stat configuration. + + Adds the organisation information. + + :param record: The record to dump. + :param data: The initial dump data passed in by ``record.dumps()``. + """ + data['organisation'] = dict(pid=record.organisation_pid) + return data + + +# dumper used for indexing +indexer_dumper = MultiDumper(dumpers=[ + # make a fresh copy + Dumper(), + ReplaceRefsDumper(), + IndexerDumper() +]) diff --git a/rero_ils/modules/stats_cfg/jsonschemas/stats_cfg/stat_cfg-v0.0.1.json b/rero_ils/modules/stats_cfg/jsonschemas/stats_cfg/stat_cfg-v0.0.1.json index 91c39a2120..db3cfc0257 100644 --- a/rero_ils/modules/stats_cfg/jsonschemas/stats_cfg/stat_cfg-v0.0.1.json +++ b/rero_ils/modules/stats_cfg/jsonschemas/stats_cfg/stat_cfg-v0.0.1.json @@ -17,7 +17,7 @@ "name", "frequency", "is_active", - "organisation", + "library", "category" ], "properties": { @@ -68,11 +68,11 @@ "options": [ { "value": "month", - "label": "month" + "label": "monthly" }, { "value": "year", - "label": "year" + "label": "yearly" } ] } @@ -80,13 +80,13 @@ }, "is_active": { "title": "Active", - "description": "Is the configuration active.", + "description": "Is the configuration active?", "type": "boolean", "default": true }, "filter_by_libraries": { "title": "Filter numbers by libraries", - "description": "If enabled, calculate stats only for the resources belonging to the selected libraries. If disabled, stats are calculated for the whole organisation.", + "description": "If disabled, stats are calculated for the whole organisation. If enabled, calculate stats only for the resources belonging to the selected libraries.", "type": "array", "uniqueItems": true, "minItems": 0, @@ -108,9 +108,8 @@ } } }, - "organisation": { - "title": "Organisation", - "description": "The system librarian's organisation.", + "library": { + "title": "Library", "type": "object", "additionalProperties": false, "required": [ @@ -118,9 +117,9 @@ ], "properties": { "$ref": { - "title": "Organisation URI", + "title": "Library URI", "type": "string", - "pattern": "^https://bib.rero.ch/api/organisations/.*?$" + "pattern": "^https://bib.rero.ch/api/libraries/.*?$" } } }, @@ -195,7 +194,7 @@ "enum": [ "created_month", "created_year", - "library", + "owning_library", "imported" ] }, @@ -215,8 +214,8 @@ "label": "created_year" }, { - "value": "library", - "label": "library" + "value": "owning_library", + "label": "owning_library" }, { "value": "imported", @@ -267,7 +266,7 @@ "enum": [ "created_month", "created_year", - "library" + "owning_library" ] }, "widget": { @@ -286,8 +285,8 @@ "label": "created_year" }, { - "value": "library", - "label": "library" + "value": "owning_library", + "label": "owning_library" } ] } @@ -334,8 +333,8 @@ "enum": [ "created_month", "created_year", - "library", - "location", + "owning_library", + "owning_location", "type" ] }, @@ -355,16 +354,16 @@ "label": "created_year" }, { - "value": "library", - "label": "library" + "value": "owning_library", + "label": "owning_library" }, { - "value": "location", - "label": "location" + "value": "owning_location", + "label": "owning_location" }, { "value": "type", - "label": "type" + "label": "type (standard/issue)" } ] } @@ -378,8 +377,8 @@ "additionalProperties": false, "propertiesOrder": [ "type", - "period", - "distributions" + "distributions", + "period" ], "required": [ "type" @@ -412,7 +411,9 @@ "enum": [ "action_month", "action_year", - "library" + "owning_library", + "item_location", + "type" ] }, "widget": { @@ -431,16 +432,16 @@ "label": "action_year" }, { - "value": "library", - "label": "library" + "value": "owning_library", + "label": "owning_library" }, { "value": "item_location", - "label": "item_location" + "label": "owning_location" }, { "value": "type", - "label": "type" + "label": "type (standard/issue)" } ] } @@ -490,8 +491,7 @@ "additionalProperties": false, "propertiesOrder": [ "type", - "distributions", - "period" + "distributions" ], "required": [ "type" @@ -548,16 +548,13 @@ }, { "value": "status", - "label": "status" + "label": "request_status" } ] } } } ] - }, - "period": { - "$ref": "#/definitions/period" } } }, @@ -831,6 +828,7 @@ "definitions": { "distributions": { "title": "Distributions", + "description": "Up to 2 filters by which to distribute the data in the generated tables. The first value is the lines; the second value is the columns.", "type": "array", "minItems": 0, "default": [], @@ -839,7 +837,7 @@ }, "period": { "title": "Period", - "description": "Time range of data used to calculate the statistics report.", + "description": "The time range to filter the data relevant to a specific period: latest month or latest year. Leave empty to not filter the data. Example: process the number of checkouts performed in the last month.", "type": "string", "enum": [ "month", @@ -873,6 +871,7 @@ "transaction_month", "transaction_year", "owning_library", + "owning_location", "transaction_location", "patron_type", "patron_age", @@ -893,13 +892,17 @@ "label": "transaction_month" }, { - "value": "transaction_yaer", - "label": "transaction_yaer" + "value": "transaction_year", + "label": "transaction_year" }, { "value": "owning_library", "label": "owning_library" }, + { + "value": "owning_location", + "label": "owning_location" + }, { "value": "transaction_location", "label": "transaction_location" @@ -958,7 +961,7 @@ "options": [ { "value": "created_month", - "label": "create_month" + "label": "created_month" }, { "value": "created_year", @@ -970,7 +973,7 @@ }, { "value": "type", - "label": "type" + "label": "patron_type" }, { "value": "postal_code", diff --git a/rero_ils/modules/stats_cfg/mappings/v7/stats_cfg/stat_cfg-v0.0.1.json b/rero_ils/modules/stats_cfg/mappings/v7/stats_cfg/stat_cfg-v0.0.1.json index 451af492d8..1c2a0c458e 100644 --- a/rero_ils/modules/stats_cfg/mappings/v7/stats_cfg/stat_cfg-v0.0.1.json +++ b/rero_ils/modules/stats_cfg/mappings/v7/stats_cfg/stat_cfg-v0.0.1.json @@ -27,6 +27,14 @@ "type": "boolean" }, "organisation": { + "type": "object", + "properties": { + "pid": { + "type": "keyword" + } + } + }, + "library": { "type": "object", "properties": { "type": { diff --git a/rero_ils/modules/stats_cfg/permissions.py b/rero_ils/modules/stats_cfg/permissions.py index 0cbcc6aa10..523fbd08c0 100644 --- a/rero_ils/modules/stats_cfg/permissions.py +++ b/rero_ils/modules/stats_cfg/permissions.py @@ -20,6 +20,7 @@ from invenio_access import action_factory from rero_ils.modules.permissions import AllowedByAction, \ + AllowedByActionRestrictByManageableLibrary, \ AllowedByActionRestrictByOrganisation, RecordPermissionPolicy # Actions to control statistics configuration policies for CRUD operations @@ -37,5 +38,5 @@ class StatisticsConfigurationPermissionPolicy(RecordPermissionPolicy): can_search = [AllowedByAction(search_action)] can_read = [AllowedByActionRestrictByOrganisation(read_action)] can_create = [AllowedByActionRestrictByOrganisation(create_action)] - can_update = [AllowedByActionRestrictByOrganisation(update_action)] - can_delete = [AllowedByActionRestrictByOrganisation(delete_action)] + can_update = [AllowedByActionRestrictByManageableLibrary(update_action)] + can_delete = [AllowedByActionRestrictByManageableLibrary(delete_action)] diff --git a/rero_ils/modules/stats_cfg/serializers/__init__.py b/rero_ils/modules/stats_cfg/serializers/__init__.py new file mode 100644 index 0000000000..72f6791bca --- /dev/null +++ b/rero_ils/modules/stats_cfg/serializers/__init__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2019-2022 RERO +# Copyright (C) 2019-2022 UCLouvain +# +# 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 . + +"""Stat configuration serialization.""" + + +from rero_ils.modules.serializers import RecordSchemaJSONV1, search_responsify + +from .json import StatsCfgJSONSerializer + +__all__ = [ + 'json_search' +] + +"""JSON serializer.""" +_json = StatsCfgJSONSerializer(RecordSchemaJSONV1) +json_search = search_responsify(_json, 'application/rero+json') diff --git a/rero_ils/modules/stats_cfg/serializers/json.py b/rero_ils/modules/stats_cfg/serializers/json.py new file mode 100644 index 0000000000..4ae4221cf8 --- /dev/null +++ b/rero_ils/modules/stats_cfg/serializers/json.py @@ -0,0 +1,39 @@ +# -*- 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 . + +"""Statistics configuration serialization.""" + +from rero_ils.modules.libraries.api import LibrariesSearch +from rero_ils.modules.serializers import JSONSerializer +from rero_ils.modules.serializers.mixins import PostprocessorMixin + + +class StatsCfgJSONSerializer(JSONSerializer, PostprocessorMixin): + """Mixin serializing records as JSON.""" + + def _postprocess_search_aggregations(self, aggregations: dict) -> None: + """Post-process aggregations from a search result. + + :param aggregations: the dictionary representing ElasticSearch + aggregations section. + """ + JSONSerializer.enrich_bucket_with_data( + aggregations.get('library', {}).get('buckets', []), + LibrariesSearch, 'name' + ) + + super()._postprocess_search_aggregations(aggregations) diff --git a/scripts/setup b/scripts/setup index ac9ad8d0dc..77d6db7c6b 100755 --- a/scripts/setup +++ b/scripts/setup @@ -649,6 +649,7 @@ fi info_msg "Collect statistics" eval ${PREFIX} invenio reroils stats collect billing +eval ${PREFIX} invenio reroils stats collect librarian eval ${PREFIX} invenio reroils stats report collect-all month eval ${PREFIX} invenio reroils stats report collect-all year diff --git a/tests/api/stats/conftest.py b/tests/api/stats/conftest.py index 786252a2ff..12e17bf7b5 100644 --- a/tests/api/stats/conftest.py +++ b/tests/api/stats/conftest.py @@ -23,6 +23,7 @@ from rero_ils.modules.stats.api.api import Stat from rero_ils.modules.stats.api.librarian import StatsForLibrarian from rero_ils.modules.stats.api.pricing import StatsForPricing +from rero_ils.modules.stats.api.report import StatsReport @pytest.fixture(scope='module') @@ -58,3 +59,11 @@ def stats_librarian(item_lib_martigny, item_lib_fully, item_lib_sion): dbcommit=True, reindex=True ) + + +@pytest.fixture(scope='module') +def stats_report_martigny(stats_cfg_martigny): + """Stats fixture for librarian.""" + stat_report = StatsReport(stats_cfg_martigny) + values = stat_report.collect() + yield stat_report.create_stat(values) diff --git a/tests/api/stats/test_stats_rest.py b/tests/api/stats/test_stats_rest.py index 9aae03f245..94a75b5629 100644 --- a/tests/api/stats/test_stats_rest.py +++ b/tests/api/stats/test_stats_rest.py @@ -110,3 +110,33 @@ def test_stats_librarian_data( assert not filtered_stat_libs.difference(manageable_libs) from invenio_db import db db.session.rollback() + + +@mock.patch('invenio_records_rest.views.verify_record_permission', + mock.MagicMock(return_value=VerifyRecordPermissionPatch)) +def test_stats_report_get(client, stats_report_martigny, csv_header): + """Test record retrieval.""" + item_url = url_for( + 'invenio_records_rest.stat_item', pid_value=stats_report_martigny.pid) + res = client.get(item_url) + assert res.status_code == 200 + assert res.headers['ETag'] + data = get_json(res) + for k in ['created', 'updated', 'metadata', 'links']: + assert k in data + # Check self links + res = client.get(to_relative_url(data['links']['self'])) + assert res.status_code == 200 + + # CSV format + params = {'pid_value': stats_report_martigny.pid, 'format': 'csv'} + item_url = url_for('invenio_records_rest.stat_item', **params) + res = client.get(item_url, headers=csv_header) + assert res.status_code == 200 + data = get_csv(res) + assert data + list_url = url_for('invenio_records_rest.stat_list') + res = client.get(list_url) + assert res.status_code == 200 + data = get_json(res) + assert data['hits']['hits'] diff --git a/tests/api/stats_cfg/test_stats_cfg_permissions.py b/tests/api/stats_cfg/test_stats_cfg_permissions.py index 3fa18937a1..bb9b2f68d1 100644 --- a/tests/api/stats_cfg/test_stats_cfg_permissions.py +++ b/tests/api/stats_cfg/test_stats_cfg_permissions.py @@ -27,7 +27,7 @@ def test_stats_cfg_permissions( patron_martigny, stats_cfg_martigny, stats_cfg_sion, - librarian_martigny, system_librarian_martigny + librarian_martigny, system_librarian_martigny, librarian_saxon, lib_saxon ): """Test statistics configuration permissions class.""" @@ -71,6 +71,32 @@ def test_stats_cfg_permissions( 'delete': False }, stats_cfg_martigny) + # Librarian with the right role + # cannot update or delete a config of an other lib + login_user(librarian_saxon.user) + check_permission(StatisticsConfigurationPermissionPolicy, { + 'search': True, + 'read': True, + 'create': True, + 'update': False, + 'delete': False + }, stats_cfg_martigny) + + # Librarian with the right role + # can update or delete a config of this library + stats_cfg_martigny.update( + dict( + library={ + '$ref': f'https://bib.test.rero.ch/libraries/{lib_saxon.pid}' + })) + check_permission(StatisticsConfigurationPermissionPolicy, { + 'search': True, + 'read': True, + 'create': True, + 'update': True, + 'delete': True + }, stats_cfg_martigny) + # System librarian with specific role # - search/read: any items login_user(system_librarian_martigny.user) diff --git a/tests/data/data.json b/tests/data/data.json index 94d02530b7..9e5a71d0a5 100644 --- a/tests/data/data.json +++ b/tests/data/data.json @@ -4381,7 +4381,8 @@ "pro_read_only", "pro_catalog_manager", "pro_circulation_manager", - "pro_user_manager" + "pro_user_manager", + "pro_statistic_manager" ], "password": "Pw123456" }, @@ -5196,8 +5197,8 @@ "pid": "stats_cfg1", "name": "Statistics configuration for organisation 1", "description": "test", - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "frequency": "month", "is_active": true, @@ -5207,7 +5208,7 @@ "type": "number_of_documents", "distributions": [ "created_month", - "library" + "owning_library" ] } } @@ -5217,8 +5218,8 @@ "pid": "stats_cfg2", "name": "Statistics configuration for organisation 2", "description": "test", - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org2" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib4" }, "frequency": "month", "is_active": true, @@ -5228,7 +5229,7 @@ "type": "number_of_documents", "distributions": [ "created_month", - "library" + "owning_library" ] } } diff --git a/tests/ui/stats/test_stats_report.py b/tests/ui/stats/test_stats_report.py index e01fe26022..b2a8f2964a 100644 --- a/tests/ui/stats/test_stats_report.py +++ b/tests/ui/stats/test_stats_report.py @@ -26,13 +26,13 @@ from rero_ils.modules.stats.api.report import StatsReport -def test_stats_report_create(org_martigny, document): +def test_stats_report_create(lib_martigny, document): """Test the stat report creation.""" cfg = { "$schema": "https://bib.rero.ch/schemas/stats_cfg/stat_cfg-v0.0.1.json", - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": f"https://bib.rero.ch/api/libraries/{lib_martigny.pid}" }, "is_active": True, "pid": "1", @@ -42,7 +42,7 @@ def test_stats_report_create(org_martigny, document): "type": "catalogue", "indicator": { "type": "number_of_documents", - "distributions": ['library'] + "distributions": ['owning_library'] } } } @@ -58,16 +58,16 @@ def test_stats_report_create(org_martigny, document): )) -def test_stats_report_range(app): +def test_stats_report_range(app, lib_martigny): """Test the report range period.""" cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": f"https://bib.rero.ch/api/libraries/{lib_martigny.pid}" }, "category": { "indicator": { "type": "number_of_documents", - "distributions": ["library"] + "distributions": ["owning_library"] } } } diff --git a/tests/ui/stats/test_stats_report_n_deleted_items.py b/tests/ui/stats/test_stats_report_n_deleted_items.py index 2104b89a6d..e7af6d0432 100644 --- a/tests/ui/stats/test_stats_report_n_deleted_items.py +++ b/tests/ui/stats/test_stats_report_n_deleted_items.py @@ -78,8 +78,8 @@ def test_stats_report_number_of_deleted_items( }, refresh=True) # no distributions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -93,8 +93,8 @@ def test_stats_report_number_of_deleted_items( # no distributions with filters lib_pid = lib_martigny_bourg.pid cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "filter_by_libraries": [{ @@ -110,14 +110,14 @@ def test_stats_report_number_of_deleted_items( # one distrubtions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_deleted_items", - "distributions": ["library"] + "distributions": ["owning_library"] } } } @@ -128,14 +128,14 @@ def test_stats_report_number_of_deleted_items( # two distributions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_deleted_items", - "distributions": ["library", "action_month"] + "distributions": ["owning_library", "action_month"] } } } @@ -147,14 +147,14 @@ def test_stats_report_number_of_deleted_items( # reverse distrubtions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_deleted_items", - "distributions": ["action_month", "library"] + "distributions": ["action_month", "owning_library"] } } } @@ -170,14 +170,14 @@ def test_stats_report_number_of_deleted_items( # year cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_deleted_items", - "distributions": ["action_year", "library"] + "distributions": ["action_year", "owning_library"] } } } @@ -193,15 +193,15 @@ def test_stats_report_number_of_deleted_items( # limit by period cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_deleted_items", "period": "year", - "distributions": ["library"] + "distributions": ["owning_library"] } } } diff --git a/tests/ui/stats/test_stats_report_n_docs.py b/tests/ui/stats/test_stats_report_n_docs.py index 3d185c7215..3a6365de96 100644 --- a/tests/ui/stats/test_stats_report_n_docs.py +++ b/tests/ui/stats/test_stats_report_n_docs.py @@ -68,8 +68,8 @@ def test_stats_report_number_of_documents( # no distributions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -83,8 +83,8 @@ def test_stats_report_number_of_documents( # no distributions with filters lib_pid = lib_martigny_bourg.pid cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "filter_by_libraries": [{ @@ -100,14 +100,14 @@ def test_stats_report_number_of_documents( # one distrubtions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_documents", - "distributions": ["library"] + "distributions": ["owning_library"] } } } @@ -118,14 +118,14 @@ def test_stats_report_number_of_documents( # two distributions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_documents", - "distributions": ["library", "created_month"] + "distributions": ["owning_library", "created_month"] } } } @@ -137,14 +137,14 @@ def test_stats_report_number_of_documents( # reverse distrubtions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_documents", - "distributions": ["created_month", "library"] + "distributions": ["created_month", "owning_library"] } } } @@ -160,14 +160,14 @@ def test_stats_report_number_of_documents( # by year cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_documents", - "distributions": ["created_year", "library"] + "distributions": ["created_year", "owning_library"] } } } @@ -183,14 +183,14 @@ def test_stats_report_number_of_documents( # imported cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_documents", - "distributions": ["library", "imported"] + "distributions": ["owning_library", "imported"] } } } @@ -202,14 +202,14 @@ def test_stats_report_number_of_documents( # reverse imported cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_documents", - "distributions": ["imported", "library"] + "distributions": ["imported", "owning_library"] } } } diff --git a/tests/ui/stats/test_stats_report_n_ill_requests.py b/tests/ui/stats/test_stats_report_n_ill_requests.py index 493a2d3d01..06dc1b639e 100644 --- a/tests/ui/stats/test_stats_report_n_ill_requests.py +++ b/tests/ui/stats/test_stats_report_n_ill_requests.py @@ -18,9 +18,7 @@ """Stats Report tests for number of ill requests.""" -from datetime import datetime -import mock from invenio_search import current_search_client as es from rero_ils.modules.stats.api.report import StatsReport @@ -40,8 +38,8 @@ def test_stats_report_number_of_ill_requests( f'({loc_public_martigny_bourg.pid})' # no data cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -85,8 +83,8 @@ def test_stats_report_number_of_ill_requests( # no distributions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -100,8 +98,8 @@ def test_stats_report_number_of_ill_requests( # no distributions with filters lib_pid = lib_martigny_bourg.pid cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "filter_by_libraries": [{ @@ -115,28 +113,10 @@ def test_stats_report_number_of_ill_requests( } assert StatsReport(cfg).collect() == [[1]] - cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" - }, - "is_active": True, - "category": { - "indicator": { - "period": "year", - "type": "number_of_ill_requests" - } - } - } - - with mock.patch( - 'rero_ils.modules.stats.api.report.datetime' - ) as mock_datetime: - mock_datetime.now.return_value = datetime(year=2024, month=1, day=1) - assert StatsReport(cfg).collect() == [[2]] # one distrubtions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -154,8 +134,8 @@ def test_stats_report_number_of_ill_requests( # two distributions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -174,8 +154,8 @@ def test_stats_report_number_of_ill_requests( # reverse distrubtions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -198,8 +178,8 @@ def test_stats_report_number_of_ill_requests( # year cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -222,8 +202,8 @@ def test_stats_report_number_of_ill_requests( # type cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { diff --git a/tests/ui/stats/test_stats_report_n_items.py b/tests/ui/stats/test_stats_report_n_items.py index d932bbf284..0d8e230785 100644 --- a/tests/ui/stats/test_stats_report_n_items.py +++ b/tests/ui/stats/test_stats_report_n_items.py @@ -30,8 +30,8 @@ def test_stats_report_number_of_items( """Test the number of items.""" # no data cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -48,35 +48,62 @@ def test_stats_report_number_of_items( 'type': 'standard', 'organisation': {'pid': org_martigny.pid}, 'library': {'pid': lib_martigny.pid}, - 'location': {'pid': loc_public_martigny.pid} + 'location': {'pid': loc_public_martigny.pid}, + 'document': { + 'document_type': [{ + 'main_type': 'docmaintype_book', + 'subtype': 'docsubtype_other_book' + }] + } }) es.index(index='items', id='2', body={ '_created': "2023-02-01", 'type': 'issue', 'organisation': {'pid': org_martigny.pid}, 'library': {'pid': lib_martigny.pid}, - 'location': {'pid': loc_restricted_martigny.pid} + 'location': {'pid': loc_restricted_martigny.pid}, + 'document': { + 'document_type': [{ + 'main_type': 'docmaintype_book', + 'subtype': 'docsubtype_other_book' + }] + } + }) es.index(index='items', id='3', body={ '_created': "2024-01-01", 'type': 'provisional', 'organisation': {'pid': org_martigny.pid}, 'library': {'pid': lib_martigny_bourg.pid}, - 'location': {'pid': loc_public_martigny_bourg.pid} + 'location': {'pid': loc_public_martigny_bourg.pid}, + 'document': { + 'document_type': [{ + 'main_type': 'docmaintype_book', + 'subtype': 'docsubtype_other_book' + }] + } + }) es.index(index='items', id='4', body={ '_created': "2024-01-01", 'type': 'standard', 'organisation': {'pid': org_sion.pid}, 'library': {'pid': lib_sion.pid}, - 'location': {'pid': loc_public_sion.pid} + 'location': {'pid': loc_public_sion.pid}, + 'document': { + 'document_type': [{ + 'main_type': 'docmaintype_book', + 'subtype': 'docsubtype_other_book' + }] + } + }) es.indices.refresh(index='items') # no distributions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -90,8 +117,8 @@ def test_stats_report_number_of_items( # no distributions with filters lib_pid = lib_martigny_bourg.pid cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "filter_by_libraries": [{ @@ -107,14 +134,14 @@ def test_stats_report_number_of_items( # one distrubtions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_items", - "distributions": ["library"] + "distributions": ["owning_library"] } } } @@ -125,14 +152,14 @@ def test_stats_report_number_of_items( # two distributions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_items", - "distributions": ["library", "created_month"] + "distributions": ["owning_library", "created_month"] } } } @@ -144,14 +171,14 @@ def test_stats_report_number_of_items( # reverse distrubtions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_items", - "distributions": ["created_month", "library"] + "distributions": ["created_month", "owning_library"] } } } @@ -167,14 +194,14 @@ def test_stats_report_number_of_items( # year cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_items", - "distributions": ["created_year", "library"] + "distributions": ["created_year", "owning_library"] } } } @@ -190,14 +217,14 @@ def test_stats_report_number_of_items( # type cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_items", - "distributions": ["type", "library"] + "distributions": ["type", "owning_library"] } } } @@ -214,14 +241,14 @@ def test_stats_report_number_of_items( # location/type cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_items", - "distributions": ["type", "location"] + "distributions": ["type", "owning_location"] } } } @@ -243,3 +270,37 @@ def test_stats_report_number_of_items( ['provisional', 1, 0, 0], ['standard', 0, 1, 0] ] + + # doc types + cfg = { + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" + }, + "is_active": True, + "category": { + "indicator": { + "type": "number_of_items", + "distributions": ["document_type"] + } + } + } + assert StatsReport(cfg).collect() == [ + ['docmaintype_book', 3] + ] + + # doc subtypes + cfg = { + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" + }, + "is_active": True, + "category": { + "indicator": { + "type": "number_of_items", + "distributions": ["document_subtype"] + } + } + } + assert StatsReport(cfg).collect() == [ + ['docsubtype_other_book', 3] + ] diff --git a/tests/ui/stats/test_stats_report_n_patrons.py b/tests/ui/stats/test_stats_report_n_patrons.py index 72aa18e1dd..0647fc3eee 100644 --- a/tests/ui/stats/test_stats_report_n_patrons.py +++ b/tests/ui/stats/test_stats_report_n_patrons.py @@ -35,8 +35,8 @@ def test_stats_report_number_of_patrons( """Test the number of patrons and active patrons.""" # no data cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -93,8 +93,8 @@ def test_stats_report_number_of_patrons( # no distributions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -107,8 +107,8 @@ def test_stats_report_number_of_patrons( # gender cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -124,8 +124,8 @@ def test_stats_report_number_of_patrons( ] # birth year cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -141,8 +141,8 @@ def test_stats_report_number_of_patrons( ] # patron type cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -162,8 +162,8 @@ def test_stats_report_number_of_patrons( ] # postal code cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -179,8 +179,8 @@ def test_stats_report_number_of_patrons( ] # role cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -196,8 +196,8 @@ def test_stats_report_number_of_patrons( ] # gender month cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -215,8 +215,8 @@ def test_stats_report_number_of_patrons( # gender year cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -277,8 +277,8 @@ def test_stats_report_number_of_patrons( # active patrons cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -298,8 +298,8 @@ def test_stats_report_number_of_patrons( # active patrons lib_pid = lib_martigny_bourg.pid cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "filter_by_libraries": [{ diff --git a/tests/ui/stats/test_stats_report_n_serial_holdings.py b/tests/ui/stats/test_stats_report_n_serial_holdings.py index f2f8e57e6f..d233ac751d 100644 --- a/tests/ui/stats/test_stats_report_n_serial_holdings.py +++ b/tests/ui/stats/test_stats_report_n_serial_holdings.py @@ -29,8 +29,8 @@ def test_stats_report_number_of_serial_holdings( """Test the number of serials.""" # no data cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -70,8 +70,8 @@ def test_stats_report_number_of_serial_holdings( # no distributions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -85,8 +85,8 @@ def test_stats_report_number_of_serial_holdings( # no distributions with filters lib_pid = lib_martigny_bourg.pid cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "filter_by_libraries": [{ @@ -102,14 +102,14 @@ def test_stats_report_number_of_serial_holdings( # one distrubtions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_serial_holdings", - "distributions": ["library"] + "distributions": ["owning_library"] } } } @@ -120,14 +120,14 @@ def test_stats_report_number_of_serial_holdings( # two distributions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_serial_holdings", - "distributions": ["library", "created_month"] + "distributions": ["owning_library", "created_month"] } } } @@ -139,14 +139,14 @@ def test_stats_report_number_of_serial_holdings( # reverse distrubtions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_serial_holdings", - "distributions": ["created_month", "library"] + "distributions": ["created_month", "owning_library"] } } } @@ -162,14 +162,14 @@ def test_stats_report_number_of_serial_holdings( # reverse distrubtions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { "indicator": { "type": "number_of_serial_holdings", - "distributions": ["created_year", "library"] + "distributions": ["created_year", "owning_library"] } } } diff --git a/tests/ui/stats/test_stats_report_number_of_ciculation.py b/tests/ui/stats/test_stats_report_number_of_ciculation.py index 9d9aa4ca92..67e5229191 100644 --- a/tests/ui/stats/test_stats_report_number_of_ciculation.py +++ b/tests/ui/stats/test_stats_report_number_of_ciculation.py @@ -86,8 +86,8 @@ def test_stats_report_circulation_trigger( }, refresh=True) cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -99,8 +99,8 @@ def test_stats_report_circulation_trigger( assert StatsReport(cfg).collect() == [[2]] lib_pid = lib_martigny_bourg.pid cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "filter_by_libraries": [{ @@ -251,8 +251,8 @@ def test_stats_report_number_of_checkins( # no distributions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -265,8 +265,8 @@ def test_stats_report_number_of_checkins( # limit by period cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -284,8 +284,8 @@ def test_stats_report_number_of_checkins( # one distrubtions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -301,8 +301,8 @@ def test_stats_report_number_of_checkins( ] # two distributions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -320,8 +320,8 @@ def test_stats_report_number_of_checkins( # reverse distrubtions cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -343,8 +343,8 @@ def test_stats_report_number_of_checkins( # year cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -366,8 +366,8 @@ def test_stats_report_number_of_checkins( # patron type cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -384,8 +384,8 @@ def test_stats_report_number_of_checkins( # patron age cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -402,8 +402,8 @@ def test_stats_report_number_of_checkins( # postal code cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -420,8 +420,8 @@ def test_stats_report_number_of_checkins( # patron type cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -438,8 +438,8 @@ def test_stats_report_number_of_checkins( # patron type cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -456,8 +456,8 @@ def test_stats_report_number_of_checkins( # transaction channel cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -474,8 +474,8 @@ def test_stats_report_number_of_checkins( # owning library cfg = { - "organisation": { - "$ref": "https://bib.rero.ch/api/organisations/org1" + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, "is_active": True, "category": { @@ -489,3 +489,105 @@ def test_stats_report_number_of_checkins( [f'{lib_martigny_bourg.get("name")} ({lib_martigny_bourg.pid})', 1], [f'{lib_martigny.get("name")} ({lib_martigny.pid})', 1] ] + + # owning location + cfg = { + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" + }, + "is_active": True, + "category": { + "indicator": { + "type": "number_of_checkins", + "distributions": ["owning_location"] + } + } + } + assert StatsReport(cfg).collect() == [ + [loc_public_martigny_bourg['name'], 1], + [loc_public_martigny['name'], 1] + ] + + +def test_stats_report_number_of_requests( + lib_martigny, lib_martigny_bourg, loc_public_martigny, + loc_public_martigny_bourg): + """Test the number of circulation checkins operations.""" + label_loc_pub_martigny = f'{lib_martigny["name"]} / '\ + f'{loc_public_martigny["name"]} ({loc_public_martigny.pid})' + label_loc_pub_martigny_bourg = f'{lib_martigny_bourg["name"]} / '\ + f'{loc_public_martigny_bourg["name"]} '\ + f'({loc_public_martigny_bourg.pid})' + + # fixtures + es.index(index='operation_logs-2020', id='1', body={ + "date": "2023-01-01", + "loan": { + "trigger": "request", + "item": { + "document": { + "type": "docsubtype_other_book" + }, + "library_pid": lib_martigny.pid, + "holding": { + "location_name": loc_public_martigny["name"] + } + }, + "transaction_location": {"pid": loc_public_martigny.pid}, + "pickup_location": {"pid": loc_public_martigny.pid}, + "transaction_channel": "sip2", + "patron": { + "age": 13, + "type": "Usager.ère moins de 14 ans", + "postal_code": "1920" + } + }, + "record": { + "type": "loan", + } + }, refresh=True) + + es.index(index='operation_logs-2020', id='2', body={ + "date": "2024-01-01", + "loan": { + "trigger": "request", + "item": { + "document": { + "type": "ebook" + }, + "library_pid": lib_martigny_bourg.pid, + "holding": { + "location_name": loc_public_martigny_bourg["name"] + } + }, + "transaction_location": {"pid": loc_public_martigny_bourg.pid}, + "pickup_location": {"pid": loc_public_martigny_bourg.pid}, + "transaction_channel": "system", + "patron": { + "age": 30, + "type": "Usager.ère plus de 18 ans", + "postal_code": "1930" + } + }, + "record": { + "type": "loan", + } + }, refresh=True) + + # pickup location + cfg = { + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" + }, + "is_active": True, + "category": { + "indicator": { + "type": "number_of_requests", + "distributions": ["pickup_location"] + } + } + } + assert StatsReport(cfg).collect() == [ + [label_loc_pub_martigny_bourg, 1], + [label_loc_pub_martigny, 1] + ] diff --git a/tests/ui/stats/test_stats_tasks.py b/tests/ui/stats/test_stats_tasks.py new file mode 100644 index 0000000000..e4c21f7d6f --- /dev/null +++ b/tests/ui/stats/test_stats_tasks.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# +# RERO ILS +# Copyright (C) 2023 RERO +# Copyright (C) 2023 UCL +# +# 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 . + +"""Stats Report tests celery tasks.""" + +from rero_ils.modules.stats.tasks import collect_stats_reports + + +def test_stats_task_report(stats_cfg_martigny): + """Test stat task reports generation.""" + res = collect_stats_reports('year') + assert not res + + res = collect_stats_reports('month') + assert len(res) > 0 diff --git a/tests/unit/test_stats_cfg_jsonschema.py b/tests/unit/test_stats_cfg_jsonschema.py index dabeaba4bb..a65b7454d2 100644 --- a/tests/unit/test_stats_cfg_jsonschema.py +++ b/tests/unit/test_stats_cfg_jsonschema.py @@ -45,8 +45,8 @@ def test_valid_circulation_n_docs(stats_cfg_schema): 'name': 'foo', 'description': 'bar', 'frequency': 'month', - 'organisation': { - '$ref': 'https://bib.rero.ch/api/organisations/org1' + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, 'category': { 'type': 'catalogue', @@ -56,7 +56,8 @@ def test_valid_circulation_n_docs(stats_cfg_schema): }, 'is_active': True } - for dist in ['created_month', 'created_year', 'imported', 'library']: + for dist in ['created_month', 'created_year', 'imported', + 'owning_library']: data['category']['indicator']['distributions'] = [dist] validate(data, stats_cfg_schema) @@ -74,8 +75,8 @@ def test_valid_circulation_n_serial_holdings(stats_cfg_schema): 'name': 'foo', 'description': 'bar', 'frequency': 'month', - 'organisation': { - '$ref': 'https://bib.rero.ch/api/organisations/org1' + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, 'category': { 'type': 'catalogue', @@ -85,7 +86,7 @@ def test_valid_circulation_n_serial_holdings(stats_cfg_schema): }, 'is_active': True } - for dist in ['created_month', 'created_year', 'library']: + for dist in ['created_month', 'created_year', 'owning_library']: data['category']['indicator']['distributions'] = [dist] validate(data, stats_cfg_schema) @@ -103,8 +104,8 @@ def test_valid_circulation_n_items(stats_cfg_schema): 'name': 'foo', 'description': 'bar', 'frequency': 'month', - 'organisation': { - '$ref': 'https://bib.rero.ch/api/organisations/org1' + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, 'category': { 'type': 'catalogue', @@ -114,9 +115,8 @@ def test_valid_circulation_n_items(stats_cfg_schema): }, 'is_active': True } - for dist in [ - 'created_month', 'created_year', 'library', 'location', 'type' - ]: + for dist in ['created_month', 'created_year', 'owning_library', + 'owning_location', 'type']: data['category']['indicator']['distributions'] = [dist] validate(data, stats_cfg_schema) @@ -134,8 +134,8 @@ def test_valid_circulation_n_patrons(stats_cfg_schema): 'name': 'foo', 'description': 'bar', 'frequency': 'month', - 'organisation': { - '$ref': 'https://bib.rero.ch/api/organisations/org1' + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, 'category': { 'type': 'user_management', @@ -166,8 +166,8 @@ def test_valid_circulation_n_active_patrons(stats_cfg_schema): 'name': 'foo', 'description': 'bar', 'frequency': 'month', - 'organisation': { - '$ref': 'https://bib.rero.ch/api/organisations/org1' + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, 'category': { 'type': 'user_management', @@ -200,8 +200,8 @@ def test_valid_circulation_n_deleted_items(stats_cfg_schema): 'name': 'foo', 'description': 'bar', 'frequency': 'month', - 'organisation': { - '$ref': 'https://bib.rero.ch/api/organisations/org1' + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, 'category': { 'type': 'catalogue', @@ -213,7 +213,7 @@ def test_valid_circulation_n_deleted_items(stats_cfg_schema): } for period in ['year', 'month']: data['category']['indicator']['period'] = period - for dist in ['action_month', 'action_year', 'library']: + for dist in ['action_month', 'action_year', 'owning_library']: data['category']['indicator']['distributions'] = [dist] validate(data, stats_cfg_schema) @@ -234,9 +234,9 @@ def test_valid_circulation_n_ill_requests(stats_cfg_schema): 'pid': 'statcfg1', 'name': 'foo', 'description': 'bar', - 'frequency': 'month', - 'organisation': { - '$ref': 'https://bib.rero.ch/api/organisations/org1' + "frequency": "month", + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, 'category': { 'type': 'circulation', @@ -246,19 +246,13 @@ def test_valid_circulation_n_ill_requests(stats_cfg_schema): }, 'is_active': True } - for period in ['year', 'month']: - data['category']['indicator']['period'] = period - for dist in [ - 'created_month', 'created_year', 'pickup_location', 'status' - ]: - data['category']['indicator']['distributions'] = [dist] - validate(data, stats_cfg_schema) - - data['category']['indicator']['distributions'] = ["foo"] - with pytest.raises(ValidationError): + for dist in [ + 'created_month', 'created_year', 'pickup_location', 'status' + ]: + data['category']['indicator']['distributions'] = [dist] validate(data, stats_cfg_schema) - data['category']['indicator']['period'] = 'day' + data['category']['indicator']['distributions'] = ["foo"] with pytest.raises(ValidationError): validate(data, stats_cfg_schema) @@ -272,8 +266,8 @@ def test_valid_circulation_n_circulations(stats_cfg_schema): 'name': 'foo', 'description': 'bar', 'frequency': 'month', - 'organisation': { - '$ref': 'https://bib.rero.ch/api/organisations/org1' + "library": { + "$ref": "https://bib.rero.ch/api/libraries/lib1" }, 'category': { 'type': 'circulation',