From 94c67c439eb89b519a56198df122ee1f6b5ac93d Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Tue, 2 Aug 2022 11:35:51 +0200 Subject: [PATCH] Bug 32030: Create eHolding titles from a list Add the ability to create new titles and attach them to a package. The MARC to KBART2 mapping is the following (based on https://github.com/adambuttrick/marc_to_kbart/blob/master/convert.py): publication_title = biblio.title print_identifier = 020$a||020$z||022$a||022$y online_identifier = 020$a||020$z||022$a||022$y date_first_issue_online = 866$a (before '-') date_last_issue_online = 866$a (after '-') num_first_vol_online = 863$a (before '-') num_last_vol_online = 863$a (after '-') num_first_issue_online = ? num_last_issue_online = ? title_url = 856$u first_author = biblio.first_author embargo_info = ? coverage_depth = title_url ? 'fulltext' : 'print' notes = $852$z publisher_name = 260$b publication_type = ? date_monograph_published_print = ? date_monograph_published_online = ? monograph_volume = ? monograph_edition = ? first_editor = ? parent_publication_title_id = ? preceeding_publication_title_id = ? access_type = ? Note that title is not created (and so the resource) if a title from this package already has a link to this bibliographic record. Is that correct, or should we create another resource? Should the import screen also have "start date" and "end date" to set for the resource? QA note: Ideally we would like to fetch the list from the REST API but the routes are not there yet. Signed-off-by: Jonathan Field Signed-off-by: Martin Renvoize Signed-off-by: Kyle M Hall Signed-off-by: Tomas Cohen Arazi --- Koha/BackgroundJob.pm | 1 + .../CreateEHoldingsFromBiblios.pm | 227 ++++++++++++++++++ Koha/REST/V1/ERM/EHoldings/Titles.pm | 11 + Koha/REST/V1/ERM/EHoldings/Titles/Local.pm | 39 +++ .../swagger/paths/erm_eholdings_titles.yaml | 66 +++++ api/v1/swagger/swagger.yaml | 4 +- .../create_eholdings_from_biblios.inc | 43 ++++ .../prog/en/modules/admin/background_jobs.tt | 4 + .../intranet-tmpl/prog/en/modules/erm/erm.tt | 2 + .../ERM/EHoldingsLocalTitlesFormImport.vue | 141 +++++++++++ .../ERM/EHoldingsLocalTitlesToolbar.vue | 5 + koha-tmpl/intranet-tmpl/prog/js/vue/fetch.js | 4 +- koha-tmpl/intranet-tmpl/prog/js/vue/routes.js | 24 ++ 13 files changed, 568 insertions(+), 3 deletions(-) create mode 100644 Koha/BackgroundJob/CreateEHoldingsFromBiblios.pm create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/create_eholdings_from_biblios.inc create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsLocalTitlesFormImport.vue diff --git a/Koha/BackgroundJob.pm b/Koha/BackgroundJob.pm index cb0f9adcea..442ba77975 100644 --- a/Koha/BackgroundJob.pm +++ b/Koha/BackgroundJob.pm @@ -419,6 +419,7 @@ sub core_types_to_classes { batch_item_record_deletion => 'Koha::BackgroundJob::BatchDeleteItem', batch_item_record_modification => 'Koha::BackgroundJob::BatchUpdateItem', batch_hold_cancel => 'Koha::BackgroundJob::BatchCancelHold', + create_eholdings_from_biblios => 'Koha::BackgroundJob::CreateEHoldingsFromBiblios', update_elastic_index => 'Koha::BackgroundJob::UpdateElasticIndex', update_holds_queue_for_biblios => 'Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue', stage_marc_for_import => 'Koha::BackgroundJob::StageMARCForImport', diff --git a/Koha/BackgroundJob/CreateEHoldingsFromBiblios.pm b/Koha/BackgroundJob/CreateEHoldingsFromBiblios.pm new file mode 100644 index 0000000000..6ad61f3318 --- /dev/null +++ b/Koha/BackgroundJob/CreateEHoldingsFromBiblios.pm @@ -0,0 +1,227 @@ +package Koha::BackgroundJob::CreateEHoldingsFromBiblios; + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use JSON qw( decode_json encode_json ); +use Try::Tiny; + +use Koha::Biblios; +use Koha::DateUtils qw( dt_from_string ); +use Koha::ERM::EHoldings::Titles; +use Koha::ERM::EHoldings::Resources; + +use C4::Context; + +use base 'Koha::BackgroundJob'; + +=head1 NAME + +CreateEHoldingsFromBiblios - Create new eHoldings titles from biblios + +This is a subclass of Koha::BackgroundJob. + +=head1 API + +=head2 Class methods + +=head3 job_type + +Define the job type of this job. + +=cut + +sub job_type { + return 'create_eholdings_from_biblios'; +} + +=head3 process + +Process the import. + +=cut + +sub process { + my ( $self, $args ) = @_; + + if ( $self->status eq 'cancelled' ) { + return; + } + + my $job_progress = 0; + $self->started_on(dt_from_string) + ->progress($job_progress) + ->status('started') + ->store; + + my @messages; + my @record_ids = @{ $args->{record_ids} }; + my $package_id = $args->{package_id}; + + my $package = Koha::ERM::EHoldings::Packages->find($package_id); + unless ( $package ) { + push @messages, { + type => 'error', + code => 'package_do_not_exist', + package_id => $package_id, + }; + } + + my $report = { + total_records => scalar @record_ids, + total_success => 0, + }; + my $fix_coverage = sub { + my $coverage = shift || q{}; + my @coverages = split '-', $coverage; + return ($coverages[0], (@coverages > 1 ? $coverages[1] : q{})); + }; + + my %existing_biblio_ids = map { + my $resource = $_; + map { $_->biblio_id => $resource->resource_id } $resource->title + } $package->resources->as_list; + + RECORD_IDS: for my $biblio_id ( sort { $a <=> $b } @record_ids ) { + + last if $self->get_from_storage->status eq 'cancelled'; + + next unless $biblio_id; + + try { + if ( grep { $_ eq $biblio_id } keys %existing_biblio_ids ) { + push @messages, + { + type => 'warning', + code => 'biblio_already_exists', + biblio_id => $biblio_id, + resource_id => $existing_biblio_ids{$biblio_id}, + }; + return; + } + my $biblio = Koha::Biblios->find($biblio_id); + my $record = $biblio->metadata->record; + my $publication_title = $biblio->title; + my $print_identifier = + $record->subfield( '020', 'a' ) + || $record->subfield( '020', 'z' ) + || $record->subfield( '022', 'a' ) + || $record->subfield( '022', 'y' ); + my $online_identifier = $print_identifier; + my ( $date_first_issue_online, $date_last_issue_online ) = + $fix_coverage->( $record->subfield( '866', 'a' ) ); + my ( $num_first_vol_online, $num_last_vol_online ) = + fix_coverage->( $record->subfield( '863', 'a' ) ); + my ( $num_first_issue_online, $num_last_issue_online ) = + ( '', '' ); # FIXME ? + my $title_url = $record->subfield( '856', 'u' ); + my $first_author = $biblio->author; + my $embargo_info = ''; # FIXME ? + my $coverage_depth = $title_url ? 'fulltext' : 'print'; + my $notes = $record->subfield( '852', 'z' ); + my $publisher_name = $record->subfield( '260', 'b' ); + my $publication_type = ''; # FIXME ? + my $date_monograph_published_print = ''; # FIXME ? + my $date_monograph_published_online = ''; # FIXME ? + my $monograph_volume = ''; # FIXME ? + my $monograph_edition = ''; # FIXME ? + my $first_editor = ''; # FIXME ? + my $parent_publication_title_id = ''; # FIXME ? + my $preceeding_publication_title_id = ''; # FIXME ? + my $access_type = ''; # FIXME ? + + my $eholding_title = { + biblio_id => $biblio_id, + publication_title => $publication_title, + print_identifier => $print_identifier, + online_identifier => $online_identifier, + date_first_issue_online => $date_first_issue_online, + num_first_vol_online => $num_first_vol_online, + num_first_issue_online => $num_first_issue_online, + date_last_issue_online => $date_last_issue_online, + num_last_vol_online => $num_last_vol_online, + num_last_issue_online => $num_last_issue_online, + title_url => $title_url, + first_author => $first_author, + embargo_info => $embargo_info, + coverage_depth => $coverage_depth, + notes => $notes, + publisher_name => $publisher_name, + publication_type => $publication_type, + date_monograph_published_print => $date_monograph_published_print, + date_monograph_published_online => $date_monograph_published_online, + monograph_volume => $monograph_volume, + monograph_edition => $monograph_edition, + first_editor => $first_editor, + parent_publication_title_id => $parent_publication_title_id, + preceeding_publication_title_id => $preceeding_publication_title_id, + access_type => $access_type, + }; + $eholding_title = Koha::ERM::EHoldings::Title->new($eholding_title)->store; + Koha::ERM::EHoldings::Resource->new({ title_id => $eholding_title->title_id, package_id => $package_id })->store; + + $report->{total_success}++; + } catch { + push @messages, { + type => 'error', + code => 'eholding_not_created', + error => $_, + }; + }; + $self->progress( ++$job_progress )->store; + } + + my $job_data = decode_json $self->data; + $job_data->{messages} = \@messages; + $job_data->{report} = $report; + + $self->ended_on(dt_from_string) + ->data(encode_json $job_data); + $self->status('finished') if $self->status ne 'cancelled'; + $self->store; +} + +=head3 enqueue + +Enqueue the new job + +=cut + +sub enqueue { + my ( $self, $args) = @_; + + return unless exists $args->{package_id}; + return unless exists $args->{record_ids}; + + $self->SUPER::enqueue({ + job_size => scalar @{$args->{record_ids}}, + job_args => $args, + queue => 'long_tasks', + }); +} + +=head3 additional_report + +=cut + +sub additional_report { + my ($self) = @_; + + my $loggedinuser = C4::Context->userenv ? C4::Context->userenv->{'number'} : undef; + return {}; +} + +1; diff --git a/Koha/REST/V1/ERM/EHoldings/Titles.pm b/Koha/REST/V1/ERM/EHoldings/Titles.pm index d48d6155e9..8eecf3fea4 100644 --- a/Koha/REST/V1/ERM/EHoldings/Titles.pm +++ b/Koha/REST/V1/ERM/EHoldings/Titles.pm @@ -111,4 +111,15 @@ sub delete { } } +=head3 import_from_list + +Controller function that handles importing bibliographic record as local eholdings + +=cut + +sub import_from_list { + my $c = shift->openapi->valid_input or return; + + return Koha::REST::V1::ERM::EHoldings::Titles::Local::import_from_list($c); +} 1; diff --git a/Koha/REST/V1/ERM/EHoldings/Titles/Local.pm b/Koha/REST/V1/ERM/EHoldings/Titles/Local.pm index 51e6307d1a..5194463034 100644 --- a/Koha/REST/V1/ERM/EHoldings/Titles/Local.pm +++ b/Koha/REST/V1/ERM/EHoldings/Titles/Local.pm @@ -20,6 +20,7 @@ use Modern::Perl; use Mojo::Base 'Mojolicious::Controller'; use Koha::ERM::EHoldings::Titles; +use Koha::BackgroundJob::CreateEHoldingsFromBiblios; use Scalar::Util qw( blessed ); use Try::Tiny qw( catch try ); @@ -238,4 +239,42 @@ sub delete { }; } +=head3 import_from_list + +=cut + +sub import_from_list { + my $c = shift->openapi->valid_input or return; + + my $body = $c->validation->param('body'); + my $list_id = $body->{list_id}; + my $package_id = $body->{package_id}; + + my $list = Koha::Virtualshelves->find($list_id); + my $patron = $c->stash('koha.user'); + + unless ( $list && $list->owner == $c->stash('koha.user')->borrowernumber ) { + return $c->render( + status => 404, + openapi => { error => "List not found" } + ); + } + + + return try { + + my @biblionumbers = $list->get_contents->get_column('biblionumber'); + my $params = { record_ids => \@biblionumbers, package_id => $package_id }; + my $job_id = Koha::BackgroundJob::CreateEHoldingsFromBiblios->new->enqueue( $params); + + return $c->render( + status => 201, + openapi => { job_id => $job_id } + ); + } + catch { + $c->unhandled_exception($_); + }; +} + 1; diff --git a/api/v1/swagger/paths/erm_eholdings_titles.yaml b/api/v1/swagger/paths/erm_eholdings_titles.yaml index 56344f6831..44010189ab 100644 --- a/api/v1/swagger/paths/erm_eholdings_titles.yaml +++ b/api/v1/swagger/paths/erm_eholdings_titles.yaml @@ -387,3 +387,69 @@ x-koha-authorization: permissions: erm: 1 + +/erm/eholdings/local/titles/import: + post: + x-mojo-to: ERM::EHoldings::Titles#import_from_list + operationId: importErmEHoldingsTitles + tags: + - eholdings + summary: Import local titles + consumes: + - application/json + produces: + - application/json + parameters: + - description: The list_id of the list to import + in: body + name: body + required: true + schema: + type: object + properties: + list_id: + type: string + package_id: + type: string + responses: + 201: + description: Successfully enqueued the import job + schema: + type: object + properties: + job_id: + type: string + additionalProperties: false + 400: + description: Bad parameter + schema: + $ref: "../swagger.yaml#/definitions/error" + 401: + description: Authentication required + schema: + $ref: "../swagger.yaml#/definitions/error" + 403: + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + 404: + description: Ressource not found + schema: + $ref: "../swagger.yaml#/definitions/error" + 409: + description: Conflict in creating resource + schema: + $ref: "../swagger.yaml#/definitions/error" + 500: + description: |- + Internal server error. Possible `error_code` attribute values: + * `internal_server_error` + schema: + $ref: "../swagger.yaml#/definitions/error" + 503: + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + erm: 1 diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index 0bb636ea78..f77984fb0f 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -174,7 +174,9 @@ paths: "/erm/agreements/{agreement_id}/documents/{document_id}/file/content": $ref: "./paths/erm_documents.yaml#/~1erm~1agreements~1{agreement_id}~1documents~1{document_id}~1file~1content" "/erm/eholdings/{provider}/titles": - $ref: ./paths/erm_eholdings_titles.yaml#/~1erm~1eholdings~1{provider}~1titles + $ref: "./paths/erm_eholdings_titles.yaml#/~1erm~1eholdings~1{provider}~1titles" + /erm/eholdings/local/titles/import: + $ref: ./paths/erm_eholdings_titles.yaml#/~1erm~1eholdings~1local~1titles~1import "/erm/eholdings/{provider}/titles/{title_id}": $ref: "./paths/erm_eholdings_titles.yaml#/~1erm~1eholdings~1{provider}~1titles~1{title_id}" "/erm/eholdings/{provider}/titles/{title_id}/resources": diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/create_eholdings_from_biblios.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/create_eholdings_from_biblios.inc new file mode 100644 index 0000000000..068108d319 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/create_eholdings_from_biblios.inc @@ -0,0 +1,43 @@ +[% BLOCK report %] + [% SET report = job.report %] + [% IF report && job.status != 'started' && job.status != 'new' %] + [% IF report.total_records == report.total_success %] +
+ All eHolding titles have been created successfully! +
+ [% ELSIF report.total_success == 0 %] +
+ No eHolding titles have been created. An error occurred. +
+ [% ELSE %] +
+ [% report.total_success | html %] / [% report.total_records | html %] eHolding titles have been created successfully but some errors occurred. +
+ [% END %] + [% END %] +[% END %] + +[% BLOCK detail %] + [% FOR m IN job.messages %] +
+ [% IF m.type == 'success' %] + + [% ELSIF m.type == 'warning' %] + + [% ELSIF m.type == 'error' %] + + [% END %] + [% SWITCH m.code %] + [% CASE 'package_do_not_exist' %] + The package #[% m.package_id | html %] does not exist. + [% CASE 'biblio_already_exists' %] + The bibliographic record ([% m.biblio_id | html %]) already exists in this package (resource #[% m.resource_id | html %]) + [% CASE 'eholding_not_created' %] + eHolding title cannot be created from bibliographic record #[% m.biblio_id | html %], encountered the following error: [% m.error | html %]. + [% END %] +
+ [% END %] +[% END %] + +[% BLOCK js %] +[% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt index fca6753b33..2a18857b1b 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt @@ -209,6 +209,10 @@ '_id': 'batch_hold_cancel', '_str': _("Batch hold cancellation") }, + { + '_id': 'create_eholdings_from_biblios', + '_str': _("Create eHolding titles") + }, { '_id': 'update_elastic_index', '_str': _("Update Elasticsearch index") diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/erm/erm.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/erm/erm.tt index 225a5b2083..4cfa3f646a 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/erm/erm.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/erm/erm.tt @@ -56,6 +56,8 @@ const lang = "[% lang || 'en' | html %]"; + const logged_in_user_lists = [% To.json(logged_in_user.virtualshelves.unblessed) | $raw %]; + [% Asset.js("js/vue/dist/main.js") | $raw %] diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsLocalTitlesFormImport.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsLocalTitlesFormImport.vue new file mode 100644 index 0000000000..132beb1fae --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsLocalTitlesFormImport.vue @@ -0,0 +1,141 @@ + + + + diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsLocalTitlesToolbar.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsLocalTitlesToolbar.vue index c0e13684e1..b1dec1e7ff 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsLocalTitlesToolbar.vue +++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsLocalTitlesToolbar.vue @@ -3,6 +3,11 @@ > {{ $t("New title") }} +   + + {{ $t("Import from list") }}