Bug 32030: Create eHolding titles from a list
authorJonathan Druart <jonathan.druart@bugs.koha-community.org>
Tue, 2 Aug 2022 09:35:51 +0000 (11:35 +0200)
committerTomas Cohen Arazi <tomascohen@theke.io>
Tue, 8 Nov 2022 12:44:20 +0000 (09:44 -0300)
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>
13 files changed:
Koha/BackgroundJob.pm
Koha/BackgroundJob/CreateEHoldingsFromBiblios.pm [new file with mode: 0644]
Koha/REST/V1/ERM/EHoldings/Titles.pm
Koha/REST/V1/ERM/EHoldings/Titles/Local.pm
api/v1/swagger/paths/erm_eholdings_titles.yaml
api/v1/swagger/swagger.yaml
koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/create_eholdings_from_biblios.inc [new file with mode: 0644]
koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt
koha-tmpl/intranet-tmpl/prog/en/modules/erm/erm.tt
koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsLocalTitlesFormImport.vue [new file with mode: 0644]
koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsLocalTitlesToolbar.vue
koha-tmpl/intranet-tmpl/prog/js/vue/fetch.js
koha-tmpl/intranet-tmpl/prog/js/vue/routes.js

index cb0f9ad..442ba77 100644 (file)
@@ -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 (file)
index 0000000..6ad61f3
--- /dev/null
@@ -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;
index d48d615..8eecf3f 100644 (file)
@@ -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;
index 51e6307..5194463 100644 (file)
@@ -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;
index 56344f6..4401018 100644 (file)
     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
index 0bb636e..f77984f 100644 (file)
@@ -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 (file)
index 0000000..068108d
--- /dev/null
@@ -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 %]
index fca6753..2a18857 100644 (file)
                 '_str': _("Batch hold cancellation")
             },
             {
+                '_id': 'create_eholdings_from_biblios',
+                '_str': _("Create eHolding titles")
+            },
+            {
                 '_id': 'update_elastic_index',
                 '_str': _("Update Elasticsearch index")
             },
index 225a5b2..4cfa3f6 100644 (file)
@@ -56,6 +56,8 @@
 
         const lang = "[% lang || 'en' | html %]";
 
+        const logged_in_user_lists = [% To.json(logged_in_user.virtualshelves.unblessed) | $raw %];
+
     </script>
 
     [% 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 (file)
index 0000000..132beb1
--- /dev/null
@@ -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>
index c0e1368..b1dec1e 100644 (file)
@@ -3,6 +3,11 @@
         ><font-awesome-icon icon="plus" />
         {{ $t("New title") }}</router-link
     >
+    &nbsp;
+    <router-link to="/cgi-bin/koha/erm/eholdings/local/titles/import" class="btn btn-default"
+        ><font-awesome-icon icon="plus" />
+        {{ $t("Import from list") }}</router-link
+>
 </template>
 
 <script>
index 865f557..10cd618 100644 (file)
@@ -367,7 +367,7 @@ export const fetchEBSCOResources = function () {
     return _fetchResources(apiUrl);
 };
 
-function checkError(response) {
+export const checkError = function(response) {
     if (response.status >= 200 && response.status <= 299) {
         return response.json();
     } else {
@@ -375,4 +375,4 @@ function checkError(response) {
         console.log(response);
         setError("%s (%s)".format(response.statusText, response.status));
     }
-}
+};
index 9803e14..42ba664 100644 (file)
@@ -10,6 +10,7 @@ import EHoldingsLocalHome from "./components/ERM/EHoldingsLocalHome.vue";
 import EHoldingsLocalPackagesFormAdd from "./components/ERM/EHoldingsLocalPackagesFormAdd.vue";
 import EHoldingsLocalTitlesFormConfirmDelete from "./components/ERM/EHoldingsLocalTitlesFormConfirmDelete.vue";
 import EHoldingsLocalTitlesFormAdd from "./components/ERM/EHoldingsLocalTitlesFormAdd.vue";
+import EHoldingsLocalTitlesFormImport from "./components/ERM/EHoldingsLocalTitlesFormImport.vue";
 import EHoldingsLocalPackagesList from "./components/ERM/EHoldingsLocalPackagesList.vue";
 import EHoldingsLocalPackagesShow from "./components/ERM/EHoldingsLocalPackagesShow.vue";
 import EHoldingsLocalPackagesFormConfirmDelete from "./components/ERM/EHoldingsLocalPackagesFormConfirmDelete.vue";
@@ -123,6 +124,14 @@ export const routes = [
         },
     },
     {
+        path: "/cgi-bin/koha/admin/background_jobs/:id",
+        beforeEnter(to, from, next) {
+            window.location.href =
+                "/cgi-bin/koha/admin/background_jobs.pl?op=view&id=" +
+                to.params.id;
+        },
+    },
+    {
         path: "/cgi-bin/koha/erm/erm.pl",
         component: ERMHome,
         meta: {
@@ -366,6 +375,21 @@ export const routes = [
                                         ),
                                 },
                             },
+                            {
+                                path: "import",
+                                component: EHoldingsLocalTitlesFormImport,
+                                meta: {
+                                    breadcrumb: () =>
+                                        build_breadcrumb(
+                                            [
+                                                breadcrumb_paths.eholdings_local,
+                                                breadcrumbs.eholdings.local
+                                                    .titles,
+                                            ],
+                                            "Import from a list" // $t("Import from a list")
+                                        ),
+                                },
+                            },
                         ],
                     },
                     {