Browse Source
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 <jonathan.field@ptfs-europe.com> Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com> Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>22.11.x
13 changed files with 568 additions and 3 deletions
@ -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 <http://www.gnu.org/licenses>. |
|||
|
|||
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; |
@ -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 %] |
|||
<div class="dialog message"> |
|||
All eHolding titles have been created successfully! |
|||
</div> |
|||
[% ELSIF report.total_success == 0 %] |
|||
<div class="dialog message"> |
|||
No eHolding titles have been created. An error occurred. |
|||
</div> |
|||
[% ELSE %] |
|||
<div class="dialog message"> |
|||
[% report.total_success | html %] / [% report.total_records | html %] eHolding titles have been created successfully but some errors occurred. |
|||
</div> |
|||
[% END %] |
|||
[% END %] |
|||
[% END %] |
|||
|
|||
[% BLOCK detail %] |
|||
[% FOR m IN job.messages %] |
|||
<div class="dialog message"> |
|||
[% IF m.type == 'success' %] |
|||
<i class="fa fa-check success"></i> |
|||
[% ELSIF m.type == 'warning' %] |
|||
<i class="fa fa-warning warn"></i> |
|||
[% ELSIF m.type == 'error' %] |
|||
<i class="fa fa-exclamation error"></i> |
|||
[% END %] |
|||
[% SWITCH m.code %] |
|||
[% CASE 'package_do_not_exist' %] |
|||
<span>The package #[% m.package_id | html %] does not exist.</span> |
|||
[% CASE 'biblio_already_exists' %] |
|||
<span>The bibliographic record ([% m.biblio_id | html %]) already exists in this package (<a href="/cgi-bin/koha/erm/eholdings/local/resources/[% m.resource_id | uri %]">resource #[% m.resource_id | html %]</a>)</span> |
|||
[% CASE 'eholding_not_created' %] |
|||
<span>eHolding title cannot be created from bibliographic record #[% m.biblio_id | html %], encountered the following error: [% m.error | html %].</span> |
|||
[% END %] |
|||
</div> |
|||
[% END %] |
|||
[% END %] |
|||
|
|||
[% BLOCK js %] |
|||
[% END %] |
@ -0,0 +1,141 @@ |
|||
<template> |
|||
<h2>{{ $t("Import from a list") }}</h2> |
|||
<div v-if="job_id" class="dialog message"> |
|||
{{ $t("Import in progress,") }} |
|||
<router-link :to="`/cgi-bin/koha/admin/background_jobs/${job_id}`"> |
|||
{{ $t("see job #%s").format(job_id) }} |
|||
</router-link> |
|||
</div> |
|||
<div id="package_list"> |
|||
{{ $t("To the following local package") }}: |
|||
<v-select |
|||
v-model="package_id" |
|||
label="name" |
|||
:reduce="(p) => p.package_id" |
|||
:options="packages" |
|||
:clearable="false" |
|||
> |
|||
</v-select> |
|||
</div> |
|||
<div id="import_list_result"> |
|||
<table :id="table_id"></table> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { setMessage, setError, setWarning } from "../../messages" |
|||
import { createVNode, render } from 'vue' |
|||
import { useDataTable } from "../../composables/datatables" |
|||
import { checkError, fetchLocalPackages } from '../../fetch.js' |
|||
|
|||
export default { |
|||
setup() { |
|||
const table_id = "list_list" |
|||
useDataTable(table_id) |
|||
|
|||
return { |
|||
table_id, |
|||
logged_in_user_lists, |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
job_id: null, |
|||
package_id: null, |
|||
packages: [], |
|||
} |
|||
}, |
|||
beforeCreate() { |
|||
fetchLocalPackages().then((packages) => { |
|||
this.packages = packages |
|||
if (this.packages.length) { |
|||
this.package_id = packages[0].package_id |
|||
} |
|||
}) |
|||
}, |
|||
methods: { |
|||
import_from_list: async function (list_id) { |
|||
if (!this.package_id) { |
|||
setError(this.$t("Cannot import, no package selected")) |
|||
return |
|||
} |
|||
if (!list_id) return |
|||
await fetch('/api/v1/erm/eholdings/local/titles/import', { |
|||
method: "POST", |
|||
body: JSON.stringify({ list_id, package_id: this.package_id }), |
|||
headers: { |
|||
'Accept': 'application/json', |
|||
'Content-Type': 'application/json' |
|||
}, |
|||
}) |
|||
.then(checkError) |
|||
.then( |
|||
(result) => { |
|||
this.job_id = result.job_id |
|||
}, |
|||
(error) => { |
|||
setError(error) |
|||
} |
|||
) |
|||
}, |
|||
build_datatable: function () { |
|||
let lists = this.logged_in_user_lists |
|||
let table_id = this.table_id |
|||
let import_from_list = this.import_from_list |
|||
$('#' + table_id).dataTable($.extend(true, {}, dataTablesDefaults, { |
|||
data: lists, |
|||
order: [[0, "asc"]], |
|||
autoWidth: false, |
|||
columns: [ |
|||
{ |
|||
title: __("Name"), |
|||
data: "shelfname", |
|||
searchable: true, |
|||
orderable: true, |
|||
width: '100%', |
|||
render: function (data, type, row, meta) { |
|||
return row.shelfname + ' (#' + row.shelfnumber + ')' |
|||
} |
|||
}, |
|||
{ |
|||
title: __("Actions"), |
|||
data: function (row, type, val, meta) { |
|||
return '<div class="actions"></div>' |
|||
}, |
|||
className: "actions noExport", |
|||
searchable: false, |
|||
orderable: false |
|||
} |
|||
], |
|||
drawCallback: function (settings) { |
|||
|
|||
var api = new $.fn.dataTable.Api(settings) |
|||
|
|||
$.each($(this).find("td .actions"), function (index, e) { |
|||
let tr = $(this).parent().parent() |
|||
let list_id = api.row(tr).data().shelfnumber |
|||
let importButton = createVNode("a", { |
|||
class: "btn btn-default btn-xs", role: "button", onClick: () => { |
|||
import_from_list(list_id) |
|||
} |
|||
}, |
|||
[createVNode("i", { class: "fa fa-download", 'aria-hidden': "true" }), __("Import")]) |
|||
|
|||
let n = createVNode('span', {}, [importButton]) |
|||
render(n, e) |
|||
}) |
|||
} |
|||
})) |
|||
}, |
|||
}, |
|||
mounted() { |
|||
this.build_datatable() |
|||
}, |
|||
name: "EHoldingsLocalTitlesFormImport", |
|||
} |
|||
</script> |
|||
<style scoped> |
|||
fieldset.rows label { |
|||
width: 25rem; |
|||
} |
|||
</style> |
Loading…
Reference in new issue