Bug 30719: ILL Batches

- Add batch column to requests table
- Establish if there are any availability or metadata enrichment plugins and pass that to the template
- Verify if we have any backend that can support batches, if not, don't show the option
- Updates to the ILL toolbar
- New ILL batch modal
- New Koha classes
- API specs

Co-authored-by: Andrew Isherwood <andrew.isherwood@ptfs-europe.com>
Signed-off-by: Edith Speller <Edith.Speller@ukhsa.gov.uk>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
This commit is contained in:
Pedro Amorim 2023-05-12 17:06:01 +00:00 committed by Tomas Cohen Arazi
parent 5bf5860370
commit a5dbafec5b
No known key found for this signature in database
GPG Key ID: 0A272EA1B2F3C15F
21 changed files with 2625 additions and 11 deletions

196
Koha/Illbatch.pm Normal file
View File

@ -0,0 +1,196 @@
package Koha::Illbatch;
# Copyright PTFS Europe 2022
#
# 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 Koha::Database;
use Koha::Illrequest::Logger;
use Koha::IllbatchStatus;
use JSON qw( to_json );
use base qw(Koha::Object);
=head1 NAME
Koha::Illbatch - Koha Illbatch Object class
=head2 Class methods
=head3 status
my $status = Koha::Illbatch->status;
Return the status object associated with this batch
=cut
sub status {
my ( $self ) = @_;
return Koha::IllbatchStatus->_new_from_dbic(
scalar $self->_result->statuscode
);
}
=head3 patron
my $patron = Koha::Illbatch->patron;
Return the patron object associated with this batch
=cut
sub patron {
my ( $self ) = @_;
return Koha::Patron->_new_from_dbic(
scalar $self->_result->borrowernumber
);
}
=head3 branch
my $branch = Koha::Illbatch->branch;
Return the branch object associated with this batch
=cut
sub branch {
my ( $self ) = @_;
return Koha::Library->_new_from_dbic(
scalar $self->_result->branchcode
);
}
=head3 requests_count
my $requests_count = Koha::Illbatch->requests_count;
Return the number of requests associated with this batch
=cut
sub requests_count {
my ( $self ) = @_;
return Koha::Illrequests->search({
batch_id => $self->id
})->count;
}
=head3 create_and_log
$batch->create_and_log;
Log batch creation following storage
=cut
sub create_and_log {
my ( $self ) = @_;
$self->store;
my $logger = Koha::Illrequest::Logger->new;
$logger->log_something({
modulename => 'ILL',
actionname => 'batch_create',
objectnumber => $self->id,
infos => to_json({})
});
}
=head3 update_and_log
$batch->update_and_log;
Log batch update following storage
=cut
sub update_and_log {
my ( $self, $params ) = @_;
my $before = {
name => $self->name,
branchcode => $self->branchcode
};
$self->set( $params );
my $update = $self->store;
my $after = {
name => $self->name,
branchcode => $self->branchcode
};
my $logger = Koha::Illrequest::Logger->new;
$logger->log_something({
modulename => 'ILL',
actionname => 'batch_update',
objectnumber => $self->id,
infos => to_json({
before => $before,
after => $after
})
});
}
=head3 delete_and_log
$batch->delete_and_log;
Log batch delete
=cut
sub delete_and_log {
my ( $self ) = @_;
my $logger = Koha::Illrequest::Logger->new;
$logger->log_something({
modulename => 'ILL',
actionname => 'batch_delete',
objectnumber => $self->id,
infos => to_json({})
});
$self->delete;
}
=head2 Internal methods
=head3 _type
my $type = Koha::Illbatch->_type;
Return this object's type
=cut
sub _type {
return 'Illbatch';
}
=head1 AUTHOR
Andrew Isherwood <andrew.isherwood@ptfs-europe.com>
=cut
1;

61
Koha/Illbatches.pm Normal file
View File

@ -0,0 +1,61 @@
package Koha::Illbatches;
# Copyright PTFS Europe 2022
#
# 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 Koha::Database;
use Koha::Illbatch;
use base qw(Koha::Objects);
=head1 NAME
Koha::Illbatches - Koha Illbatches Object class
=head2 Internal methods
=head3 _type
my $type = Koha::Illbatches->_type;
Return this object's type
=cut
sub _type {
return 'Illbatch';
}
=head3 object_class
my $class = Koha::Illbatches->object_class;
Return this object's class name
=cut
sub object_class {
return 'Koha::Illbatch';
}
=head1 AUTHOR
Andrew Isherwood <andrew.isherwood@ptfs-europe.com>
=cut
1;

256
Koha/REST/V1/Illbatches.pm Normal file
View File

@ -0,0 +1,256 @@
package Koha::REST::V1::Illbatches;
# 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 Mojo::Base 'Mojolicious::Controller';
use Koha::Illbatches;
use Koha::IllbatchStatuses;
use Koha::Illrequests;
use Try::Tiny qw( catch try );
=head1 NAME
Koha::REST::V1::Illbatches
=head2 Operations
=head3 list
Return a list of available ILL batches
=cut
sub list {
my $c = shift->openapi->valid_input;
#FIXME: This should be $c->objects-search
my @batches = Koha::Illbatches->search()->as_list;
#FIXME: Below should be coming from $c->objects accessors
# Get all patrons associated with all our batches
# in one go
my $patrons = {};
foreach my $batch(@batches) {
my $patron_id = $batch->borrowernumber;
$patrons->{$patron_id} = 1
};
my @patron_ids = keys %{$patrons};
my $patron_results = Koha::Patrons->search({
borrowernumber => { -in => \@patron_ids }
});
# Get all branches associated with all our batches
# in one go
my $branches = {};
foreach my $batch(@batches) {
my $branch_id = $batch->branchcode;
$branches->{$branch_id} = 1
};
my @branchcodes = keys %{$branches};
my $branch_results = Koha::Libraries->search({
branchcode => { -in => \@branchcodes }
});
# Get all batch statuses associated with all our batches
# in one go
my $statuses = {};
foreach my $batch(@batches) {
my $code = $batch->statuscode;
$statuses->{$code} = 1
};
my @statuscodes = keys %{$statuses};
my $status_results = Koha::IllbatchStatuses->search({
code => { -in => \@statuscodes }
});
# Populate the response
my @to_return = ();
foreach my $it_batch(@batches) {
my $patron = $patron_results->find({ borrowernumber => $it_batch->borrowernumber});
my $branch = $branch_results->find({ branchcode => $it_batch->branchcode });
my $status = $status_results->find({ code => $it_batch->statuscode });
push @to_return, {
%{$it_batch->unblessed},
patron => $patron,
branch => $branch,
status => $status,
requests_count => $it_batch->requests_count
};
}
return $c->render( status => 200, openapi => \@to_return );
}
=head3 get
Get one batch
=cut
sub get {
my $c = shift->openapi->valid_input;
my $batchid = $c->validation->param('illbatch_id');
my $batch = Koha::Illbatches->find($batchid);
if (not defined $batch) {
return $c->render(
status => 404,
openapi => { error => "ILL batch not found" }
);
}
return $c->render(
status => 200,
openapi => {
%{$batch->unblessed},
patron => $batch->patron->unblessed,
branch => $batch->branch->unblessed,
status => $batch->status->unblessed,
requests_count => $batch->requests_count
}
);
}
=head3 add
Add a new batch
=cut
sub add {
my $c = shift->openapi->valid_input or return;
my $body = $c->validation->param('body');
# We receive cardnumber, so we need to look up the corresponding
# borrowernumber
my $patron = Koha::Patrons->find({ cardnumber => $body->{cardnumber} });
if ( not defined $patron ) {
return $c->render(
status => 404,
openapi => { error => "Patron with cardnumber " . $body->{cardnumber} . " not found" }
);
}
delete $body->{cardnumber};
$body->{borrowernumber} = $patron->borrowernumber;
return try {
my $batch = Koha::Illbatch->new( $body );
$batch->create_and_log;
$c->res->headers->location( $c->req->url->to_string . '/' . $batch->id );
my $ret = {
%{$batch->unblessed},
patron => $batch->patron->unblessed,
branch => $batch->branch->unblessed,
status => $batch->status->unblessed,
requests_count => 0
};
return $c->render(
status => 201,
openapi => $ret
);
}
catch {
if ( blessed $_ ) {
if ( $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
return $c->render(
status => 409,
openapi => { error => "A batch named " . $body->{name} . " already exists" }
);
}
}
$c->unhandled_exception($_);
};
}
=head3 update
Update a batch
=cut
sub update {
my $c = shift->openapi->valid_input or return;
my $batch = Koha::Illbatches->find( $c->validation->param('illbatch_id') );
if ( not defined $batch ) {
return $c->render(
status => 404,
openapi => { error => "ILL batch not found" }
);
}
my $params = $c->req->json;
delete $params->{cardnumber};
return try {
$batch->update_and_log( $params );
my $ret = {
%{$batch->unblessed},
patron => $batch->patron->unblessed,
branch => $batch->branch->unblessed,
status => $batch->status->unblessed,
requests_count => $batch->requests_count
};
return $c->render(
status => 200,
openapi => $ret
);
}
catch {
$c->unhandled_exception($_);
};
}
=head3 delete
Delete a batch
=cut
sub delete {
my $c = shift->openapi->valid_input or return;
my $batch = Koha::Illbatches->find( $c->validation->param( 'illbatch_id' ) );
if ( not defined $batch ) {
return $c->render( status => 404, openapi => { error => "ILL batch not found" } );
}
return try {
$batch->delete_and_log;
return $c->render( status => 204, openapi => '');
}
catch {
$c->unhandled_exception($_);
};
}
1;

View File

@ -835,6 +835,8 @@ modules:
columns:
-
columnname: ill_request_id
-
columnname: batch
-
columnname: metadata_author
-

View File

@ -0,0 +1,48 @@
---
type: object
properties:
id:
type: string
description: Internal ILL batch identifier
name:
type: string
description: Name of the ILL batch
backend:
type: string
description: Backend name
cardnumber:
type: string
description: Card number of the patron of the ILL batch
borrowernumber:
type: string
description: Borrower number of the patron of the ILL batch
branchcode:
type: string
description: Branch code of the branch of the ILL batch
patron:
type:
- object
- "null"
description: The patron associated with the batch
branch:
type:
- object
- "null"
description: The branch associated with the batch
statuscode:
type: string
description: Code of the status of the ILL batch
status:
type:
- object
- "null"
description: The status associated with the batch
requests_count:
type: string
description: The number of requests in this batch
additionalProperties: false
required:
- name
- backend
- branchcode
- statuscode

View File

@ -0,0 +1,5 @@
---
type: array
items:
$ref: "illbatch.yaml"
additionalProperties: false

View File

@ -0,0 +1,236 @@
---
/illbatches:
get:
x-mojo-to: Illbatches#list
operationId: listIllbatches
tags:
- illbatches
summary: List ILL batches
parameters: []
produces:
- application/json
responses:
"200":
description: A list of ILL batches
schema:
$ref: "../swagger.yaml#/definitions/illbatches"
"401":
description: Authentication required
schema:
$ref: "../swagger.yaml#/definitions/error"
"403":
description: Access forbidden
schema:
$ref: "../swagger.yaml#/definitions/error"
"404":
description: ILL batches not found
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:
ill: "1"
post:
x-mojo-to: Illbatches#add
operationId: addIllbatch
tags:
- illbatches
summary: Add ILL batch
parameters:
- name: body
in: body
description: A JSON object containing informations about the new batch
required: true
schema:
$ref: "../swagger.yaml#/definitions/illbatch"
produces:
- application/json
responses:
"201":
description: Batch added
schema:
$ref: "../swagger.yaml#/definitions/illbatch"
"400":
description: Bad request
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: Patron with given cardnumber 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:
ill: "1"
"/illbatches/{illbatch_id}":
get:
x-mojo-to: Illbatches#get
operationId: getIllbatches
tags:
- illbatches
summary: Get ILL batch
parameters:
- name: illbatch_id
in: path
description: ILL batch id/name/contents
required: true
type: string
produces:
- application/json
responses:
"200":
description: An ILL batch
schema:
$ref: "../swagger.yaml#/definitions/illbatch"
"401":
description: Authentication required
schema:
$ref: "../swagger.yaml#/definitions/error"
"403":
description: Access forbidden
schema:
$ref: "../swagger.yaml#/definitions/error"
"404":
description: ILL batch not found
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:
ill: "1"
put:
x-mojo-to: Illbatches#update
operationId: updateIllBatch
tags:
- illbatches
summary: Update batch
parameters:
- $ref: "../swagger.yaml#/parameters/illbatch_id_pp"
- name: body
in: body
description: A JSON object containing information on the batch
required: true
schema:
$ref: "../swagger.yaml#/definitions/illbatch"
consumes:
- application/json
produces:
- application/json
responses:
"200":
description: An ILL batch
schema:
$ref: "../swagger.yaml#/definitions/illbatch"
"400":
description: Bad request
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: ILL batch not found
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:
ill: "1"
delete:
x-mojo-to: Illbatches#delete
operationId: deleteBatch
tags:
- illbatches
summary: Delete ILL batch
parameters:
- $ref: "../swagger.yaml#/parameters/illbatch_id_pp"
produces:
- application/json
responses:
"204":
description: ILL batch deleted
schema:
type: string
"401":
description: Authentication required
schema:
$ref: "../swagger.yaml#/definitions/error"
"403":
description: Access forbidden
schema:
$ref: "../swagger.yaml#/definitions/error"
"404":
description: ILL batch not found
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:
ill: "1"

View File

@ -27,10 +27,12 @@ use Koha::Notice::Templates;
use Koha::AuthorisedValues;
use Koha::Illcomment;
use Koha::Illrequests;
use Koha::Illbatches;
use Koha::Illrequest::Workflow::Availability;
use Koha::Illrequest::Workflow::TypeDisclaimer;
use Koha::Libraries;
use Koha::Token;
use Koha::Plugins;
use Try::Tiny qw( catch try );
use URI::Escape qw( uri_escape_utf8 );
@ -66,10 +68,27 @@ my $has_branch = $cfg->has_branch;
my $backends_available = ( scalar @{$backends} > 0 );
$template->param(
backends_available => $backends_available,
has_branch => $has_branch
has_branch => $has_branch,
have_batch => have_batch_backends($backends)
);
if ( $backends_available ) {
# Establish what metadata enrichment plugins we have available
my $enrichment_services = get_metadata_enrichment();
if (scalar @{$enrichment_services} > 0) {
$template->param(
metadata_enrichment_services => encode_json($enrichment_services)
);
}
# Establish whether we have any availability services that can provide availability
# for the batch identifier types we support
my $batch_availability_services = get_ill_availability($enrichment_services);
if (scalar @{$batch_availability_services} > 0) {
$template->param(
batch_availability_services => encode_json($batch_availability_services)
);
}
if ( $op eq 'illview' ) {
# View the details of an ILL
my $request = Koha::Illrequests->find($params->{illrequest_id});
@ -138,8 +157,8 @@ if ( $backends_available ) {
}
$template->param(
whole => $backend_result,
request => $request
whole => $backend_result,
request => $request
);
handle_commit_maybe($backend_result, $request);
}
@ -205,6 +224,9 @@ if ( $backends_available ) {
# We simulate the API for backend requests for uniformity.
# So, init:
my $request = Koha::Illrequests->find($params->{illrequest_id});
my $batches = Koha::Illbatches->search(undef, {
order_by => { -asc => 'name' }
});
if ( !$params->{stage} ) {
my $backend_result = {
error => 0,
@ -217,13 +239,15 @@ if ( $backends_available ) {
};
$template->param(
whole => $backend_result,
request => $request
request => $request,
batches => $batches
);
} else {
# Commit:
# Save the changes
$request->borrowernumber($params->{borrowernumber});
$request->biblio_id($params->{biblio_id});
$request->batch_id($params->{batch_id});
$request->branchcode($params->{branchcode});
$request->price_paid($params->{price_paid});
$request->notesopac($params->{notesopac});
@ -356,7 +380,7 @@ if ( $backends_available ) {
} elsif ( $op eq 'illlist') {
# If we receive a pre-filter, make it available to the template
my $possible_filters = ['borrowernumber'];
my $possible_filters = ['borrowernumber', 'batch_id'];
my $active_filters = {};
foreach my $filter(@{$possible_filters}) {
if ($params->{$filter}) {
@ -375,6 +399,17 @@ if ( $backends_available ) {
$template->param(
prefilters => join("&", @tpl_arr)
);
if ($active_filters->{batch_id}) {
my $batch_id = $active_filters->{batch_id};
if ($batch_id) {
my $batch = Koha::Illbatches->find($batch_id);
$template->param(
batch => $batch
);
}
}
} elsif ( $op eq "save_comment" ) {
die "Wrong CSRF token" unless Koha::Token->new->check_csrf({
session_id => scalar $cgi->cookie('CGISESSID'),
@ -409,6 +444,10 @@ if ( $backends_available ) {
scalar $params->{illrequest_id} . $append
);
exit;
} elsif ( $op eq "batch_list" ) {
# Do not remove, it prevents us falling through to the 'else'
} elsif ( $op eq "batch_create" ) {
# Do not remove, it prevents us falling through to the 'else'
} else {
my $request = Koha::Illrequests->find($params->{illrequest_id});
my $backend_result = $request->custom_capability($op, $params);
@ -466,3 +505,59 @@ sub redirect_to_list {
print $cgi->redirect('/cgi-bin/koha/ill/ill-requests.pl');
exit;
}
# Do any of the available backends provide batch requesting
sub have_batch_backends {
my ( $backends ) = @_;
my @have_batch = ();
foreach my $backend(@{$backends}) {
my $can_batch = can_batch($backend);
if ($can_batch) {
push @have_batch, $backend;
}
}
return \@have_batch;
}
# Does a given backend provide batch requests
# FIXME: This should be moved to Koha::Illbackend
sub can_batch {
my ( $backend ) = @_;
my $request = Koha::Illrequest->new->load_backend( $backend );
return $request->_backend_capability( 'provides_batch_requests' );
}
# Get available metadata enrichment plugins
sub get_metadata_enrichment {
my @candidates = Koha::Plugins->new()->GetPlugins({
method => 'provides_api'
});
my @services = ();
foreach my $plugin(@candidates) {
my $supported = $plugin->provides_api();
if ($supported->{type} eq 'search') {
push @services, $supported;
}
}
return \@services;
}
# Get ILL availability plugins that can help us with the batch identifier types
# we support
sub get_ill_availability {
my ( $services ) = @_;
my $id_types = {};
foreach my $service(@{$services}) {
foreach my $id_supported(keys %{$service->{identifiers_supported}}) {
$id_types->{$id_supported} = 1;
}
}
my $availability = Koha::Illrequest::Workflow::Availability->new($id_types);
return $availability->get_services({
ui_context => 'staff'
});
}

View File

@ -3706,6 +3706,112 @@ input.renew {
}
#interlibraryloans {
.ill-toolbar {
display: flex;
}
#ill-batch {
flex-grow: 1;
display: flex;
justify-content: flex-end;
gap: 5px;
}
#ill-batch-requests {
.action-buttons {
display: flex;
gap: 5px;
justify-content: center;
}
}
#ill-batch-modal {
.modal-footer {
display: flex;
& > * {
flex: 1;
}
#lhs {
text-align: left;
}
}
#create-progress {
margin-top: 17px;
}
.fetch-failed {
background-color: rgba(255,0,0,0.1);
& > * {
background-color: inherit;
}
}
.progress {
margin-bottom: 0;
margin-top: 17px;
}
#create-requests {
display: flex;
justify-content: flex-end;
}
.action-column {
text-align: center;
& > * {
margin-left: 5px;
}
& > *:first-child {
margin-left: 0;
}
}
.metadata-row:not(:first-child) {
margin-top: 0.5em;
}
.metadata-label {
font-weight: 600;
}
.more-less {
text-align: right;
margin: 2px 0;
}
}
#batch-form {
legend {
margin-bottom: 2em;
}
textarea {
width: 100%;
min-height: 100px;
padding: 5px;
resize: vertical;
}
#new-batch-form {
display: flex;
gap: 20px;
}
li#process-button {
display: flex;
justify-content: flex-end;
}
#textarea-metadata {
padding: 0 15px;
display: flex;
justify-content: space-between;
}
#textarea-errors {
display: flex;
flex-direction: column;
gap: 10px;
padding: 20px 15px 10px
}
.batch-modal-actions {
text-align: center;
}
fieldset {
border: 2px solid #b9d8d9;
}
}
#dataPreviewLabel {
margin: .3em 0;
}

View File

@ -0,0 +1,38 @@
<!-- ill-batch-table-strings.inc -->
<script>
var ill_batch_add = _("Add new batch");
var ill_batch_update = _("Update batch");
var ill_batch_none = _("None");
var ill_batch_retrieving_metadata = _("Retrieving metadata");
var ill_batch_api_fail = _("Unable to retrieve batch details");
var ill_batch_statuses_api_fail = _("Unable to retrieve batch statuses");
var ill_batch_api_request_fail = _("Unable to create local request");
var ill_batch_requests_api_fail = _("Unable to retrieve batch requests");
var ill_batch_unknown = _("Unknown");
var ill_batch_doi = _("DOI");
var ill_batch_pmid = _("PubMed ID");
var ill_populate_waiting = _("Retrieving...");
var ill_populate_failed = _("Failed to retrieve");
var ill_button_remove = _("Remove");
var ill_batch_create_api_fail = _("Unable to create batch request");
var ill_batch_update_api_fail = _("Unable to updatecreate batch request");
var ill_batch_item_remove = _("Are you sure you want to remove this item from the batch");
var ill_batch_create_cancel_button = _("Close");
var ill_batch_metadata_more = _("More");
var ill_batch_metadata_less = _("Less");
var ill_batch_available_via = _("Available via");
var ill_batch_metadata = {
'doi': _("DOI"),
'pmid': _("PubMed ID"),
'issn': _("ISSN"),
'title': _("Title"),
'year': _("Year"),
'issue': _("Issue"),
'pages': _("Pages"),
'publisher': _("Publisher"),
'article_title': _("Article title"),
'article_author': _("Article author"),
'volume': _("Volume")
};
</script>
<!-- / ill-batch-table-strings.inc -->

View File

@ -0,0 +1,97 @@
<div id="ill-batch-details" style="display:none"></div>
<div id="ill-batch-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="ill-batch-modal-label" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="ill-batch-modal-label"></h3>
</div>
<div class="modal-body">
<div id="batch-form">
<form id="new-batch-form">
<fieldset class="rows">
<legend id="legend">Batch details</legend>
<ol>
<li id="batch_name">
<label class="required" for="name">Batch name:</label>
<input type="text" autocomplete="off" name="name" id="name" type="text"
value="" />
</li>
<li id="batch_patron">
<label class="required" for="batchcardnumber">Card number, username or surname:</label>
<input type="text" autocomplete="off" name="batchcardnumber" id="batchcardnumber" type="text" value="" />
<span id="patron_link"></span>
</li>
<li id="batch_branch">
<label class="required" for="branchcode">Library:</label>
<select id="branchcode" name="branchcode">
[% PROCESS options_for_libraries libraries => Branches.all( selected => branchcode ) %]
</select>
</li>
<li id="batch_statuscode" style="display:none">
<label class="required" for="statuscode">Status:</label>
<select id="statuscode" name="statuscode"></select>
</li>
</ol>
</fieldset>
<fieldset id="add_batch_items" class="rows" style="display:none">
<legend id="legend">Add batch items</legend>
<div id="textarea-metadata">
<div id="supported">Supported identifiers: <span id="supported_identifiers"></span></div>
<div id="row_count">Row count: <span id="row_count_value"></span></div>
</div>
<div id="textarea-errors" style="display:none" class="error">
<div id="duplicates" style="display:none">The following duplicates were found, these have been de-duplicated: <span id="dupelist"></span></div>
<div id="badidentifiers" style="display:none">The following unknown identifiers were found, it was not possible to establish their type: <span id="badids"></span></div>
</div>
<ol>
<li>
<textarea id="identifiers_input" placeholder="Enter identifiers, one per line"></textarea>
</li>
<li id="process-button">
<button id="process_button" disabled aria-disabled="true" type="button">Process identifiers</button>
</li>
</ol>
</fieldset>
</form>
<div id="create-progress" class="alert alert-info" role="alert" style="display:none">
<span id="progress-label"><strong></strong></span> -
Items processed: <span id="processed_count">0</span> out of <span id="processed_total">0</span>.
Items failed: <span id="processed_failed">0</span>.
<div class="progress">
<div id="processed_progress_bar" class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="min-width: 2em;">
0%
</div>
</div>
</div>
<table id="identifier-table" style="display:none">
<thead>
<tr id="identifier-table-header">
<th scope="col">Identifier type</th>
<th scope="col">Identifier value</th>
<th scope="col">Metadata</th>
<th scope="col">Request ID</th>
<th scope="col">Request Status</th>
<th scope="col"></th>
</tr>
</thead>
<tbody id="identifier-table-body">
</tbody>
</table>
</div>
<div id="create-requests" style="display:none">
<button id="create-requests-button" type="button" class="btn btn-primary" aria-label="Add items to batch">Add items to batch</button>
</div>
</div>
<div class="modal-footer">
<div id="lhs">
<button class="btn btn-default" data-dismiss="modal" aria-hidden="false">Cancel</button>
</div>
<div id="rhs">
<button id="button_create_batch" class="btn btn-default" aria-hidden="true" disabled>Continue</button>
<button id="button_finish" disabled type="button" class="btn btn-default" aria-hidden="true">Finish and view batch</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,9 @@
<!-- ill-batch-table-strings.inc -->
<script>
var ill_batch_borrower_details = _("View borrower details");
var ill_batch_edit = _("Edit");
var ill_batch_delete = _("Delete");
var ill_batch_confirm_delete = _("Are you sure you want to delete this batch? All attached requests will be detached.");
var ill_batch_delete_fail = _("Unable to delete batch");
</script>
<!-- / ill-batch-table-strings.inc -->

View File

@ -0,0 +1,19 @@
[% IF query_type == "batch_list" %]
<div>
<table id="ill-batch-requests">
<thead>
<tr id="ill-batch-header">
<th scope="col">Batch ID</th>
<th scope="col">Name</th>
<th scope="col">Number of requests</th>
<th scope="col">Status</th>
<th scope="col">Patron</th>
<th scope="col">Branch</th>
<th scope="col"></th>
</tr>
</thead>
<tbody id="ill-batch-body">
</tbody>
</table>
</div>
[% END %]

View File

@ -1,5 +1,6 @@
<!-- ill-list-table-strings.inc -->
<script>
var ill_borrower_details = _("View patron details");
var ill_manage = _("Manage request");
var ill_manage_select_backend_first = _("Select a backend first");
var ill_all_statuses = _("All statuses");

View File

@ -1,11 +1,14 @@
[% IF patron.borrowernumber %]
<table id="ill-requests-patron-[% patron.borrowernumber | html %]">
[% ELSIF batch.id %]
<table id="ill-requests-batch-[% batch.id | html %]">
[% ELSE %]
<table id="ill-requests">
[% END %]
<thead>
<tr id="ill_requests_header">
<th scope="col">Request ID</th>
<th scope="col">Batch</th>
<th scope="col" data-datatype="related-object" data-related="extended_attributes" data-related-key="type" data-related-value="author" data-related-search-on="value">Author</th>
<th scope="col" data-datatype="related-object" data-related="extended_attributes" data-related-key="type" data-related-value="title" data-related-search-on="value">Title</th>
<th scope="col" data-datatype="related-object" data-related="extended_attributes" data-related-key="type" data-related-value="article_title" data-related-search-on="value">Article title</th>

View File

@ -1,7 +1,7 @@
[% INCLUDE 'blocking_errors.inc' %]
[% USE Koha %]
[% IF Koha.Preference('ILLModule ') && CAN_user_ill %]
<div id="toolbar" class="btn-toolbar">
[% IF Koha.Preference('ILLModule') && CAN_user_ill %]
<div id="toolbar" class="btn-toolbar ill-toolbar">
[% IF backends_available %]
[% IF backends.size > 1 %]
<div class="dropdown btn-group">
@ -33,5 +33,22 @@
<i class="fa fa-list"></i> List requests
</a>
[% END %]
[% IF have_batch.size > 0 && metadata_enrichment_services %]
<div id="ill-batch">
<div class="dropdown btn-group">
<button class="btn btn-default dropdown-toggle" type="button" id="ill-batch-backend-dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> New ILL batch request <span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="ill-batch-backend-dropdown">
[% FOREACH backend IN have_batch %]
<li><a href="#" role="button" onclick="window.openBatchModal(null, '[% backend | html %]')">[% backend | html %]</a></li>
[% END %]
</ul>
</div>
<a class="btn btn-default" type="button" href="/cgi-bin/koha/ill/ill-requests.pl?method=batch_list" %]">
<i class="fa fa-tasks"></i> Batch requests</span>
</a>
</div>
[% END %]
</div>
[% END %]

View File

@ -124,6 +124,7 @@
<div class="dialog message">ILL module configuration problem. Take a look at the <a href="/cgi-bin/koha/about.pl#sysinfo_panel">about page</a></div>
[% ELSE %]
[% INCLUDE 'ill-toolbar.inc' %]
[% INCLUDE 'ill-batch-modal.inc' %]
[% IF whole.error %]
<h1>Error performing operation</h1>
@ -437,6 +438,23 @@
[% END %]
</select>
</li>
[% IF batches.count > 0 %]
<li class="batch">
<label class="batch_label">Batch:</label>
<select id="batch_id" name="batch_id">
<option value="">
[% FOREACH batch IN batches %]
[% IF batch.id == request.batch_id %]
<option value="[% batch.id | html %]" selected>
[% ELSE %]
<option value="[% batch.id | html %]">
[% END %]
[% batch.name | html %]
</option>
[% END %]
</select>
</li>
[% END %]
<li class="updated">
<label class="updated">Last updated:</label>
[% request.updated | $KohaDates with_hours => 1 %]
@ -648,6 +666,14 @@
[% END %]
[% END %]
</li>
[% IF request.batch > 0 %]
<li class="batch">
<span class="label batch">Batch:</span>
<a href="/cgi-bin/koha/ill/ill-requests.pl?batch_id=[% request.batch.id | html %]">
[% request.batch.name | html %]
</a>
</li>
[% END %]
<li class="updated">
<span class="label updated">Last updated:</span>
[% request.updated | $KohaDates with_hours => 1 %]
@ -779,7 +805,12 @@
[% ELSIF query_type == 'illlist' %]
<!-- illlist -->
<h1>View ILL requests</h1>
<h1>
View ILL requests
[% IF batch %]
for batch "[% batch.name | html %]"
[% END %]
</h1>
<div id="results" class="page-section">
<h2>Details for all requests</h2>
[% INCLUDE 'ill-list-table.inc' %]
@ -858,6 +889,8 @@
</fieldset>
</form>
</div>
[% ELSIF query_type == 'batch_list' || query_type == 'batch_create' %]
[% INCLUDE 'ill-batch.inc' %]
[% ELSE %]
<!-- Custom Backend Action -->
[% PROCESS $whole.template %]
@ -877,6 +910,18 @@
[% INCLUDE 'columns_settings.inc' %]
[% INCLUDE 'calendar.inc' %]
[% INCLUDE 'select2.inc' %]
[% IF metadata_enrichment_services %]
<script>
var metadata_enrichment_services = [% metadata_enrichment_services | $raw %];
</script>
<script>
[% IF batch_availability_services %]
var batch_availability_services = [% batch_availability_services | $raw %];
[% ELSE %]
var batch_availability_services = [];
[% END %]
</script>
[% END %]
<script>
var prefilters = '[% prefilters | $raw %]';
// Set column settings
@ -901,7 +946,12 @@
});
</script>
[% INCLUDE 'ill-list-table-strings.inc' %]
[% INCLUDE 'ill-batch-table-strings.inc' %]
[% INCLUDE 'ill-batch-modal-strings.inc' %]
[% Asset.js("js/ill-list-table.js") | $raw %]
[% Asset.js("js/ill-batch.js") | $raw %]
[% Asset.js("js/ill-batch-table.js") | $raw %]
[% Asset.js("js/ill-batch-modal.js") | $raw %]
[% IF (query_type == 'availability' || query_type == 'generic_confirm') && Koha.Preference('ILLCheckAvailability') %]
[% Asset.js("js/ill-availability.js") | $raw %]
[% END %]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,216 @@
(function () {
var table;
var batchesProxy;
window.addEventListener('load', onload);
function onload() {
// Only proceed if appropriate
if (!document.getElementById('ill-batch-requests')) return;
// A proxy that will give us some element of reactivity to
// changes in our list of batches
batchesProxy = new Proxy(
{ data: [] },
{
get: function (obj, prop) {
return obj[prop];
},
set: function (obj, prop, value) {
obj[prop] = value;
updateTable();
}
}
);
// Initialise the Datatable, binding it to our proxy object
table = initTable();
// Do the initial data population
window.doBatchApiRequest()
.then(function (response) {
return response.json();
})
.then(function (data) {
batchesProxy.data = data;
});
// Clean up any event listeners we added
window.addEventListener('beforeunload', removeEventListeners);
};
// Initialise the Datatable
// FIXME: This should be a kohaTable not KohaTable
var initTable = function () {
return KohaTable("ill-batch-requests", {
data: batchesProxy.data,
columns: [
{
data: 'id',
width: '10%'
},
{
data: 'name',
render: createName,
width: '30%'
},
{
data: 'requests_count',
width: '10%'
},
{
data: 'status',
render: createStatus,
width: '10%'
},
{
data: 'patron',
render: createPatronLink,
width: '10%'
},
{
data: 'branch',
render: createBranch,
width: '20%'
},
{
render: createActions,
width: '10%',
orderable: false
}
],
processing: true,
deferRender: true,
drawCallback: addEventListeners
});
}
// A render function for branch name
var createBranch = function (data) {
return data.branchname;
};
// A render function for batch name
var createName = function (x, y, data) {
var a = document.createElement('a');
a.setAttribute('href', '/cgi-bin/koha/ill/ill-requests.pl?batch_id=' + data.id);
a.setAttribute('title', data.name);
a.textContent = data.name;
return a.outerHTML;
};
// A render function for batch status
var createStatus = function (x, y, data) {
return data.status.name;
};
// A render function for our patron link
var createPatronLink = function (data) {
var link = document.createElement('a');
link.setAttribute('title', ill_batch_borrower_details);
link.setAttribute('href', '/cgi-bin/koha/members/moremember.pl?borrowernumber=' + data.borrowernumber);
var displayText = [data.firstname, data.surname].join(' ') + ' ( ' + data.cardnumber + ' )';
link.appendChild(document.createTextNode(displayText));
return link.outerHTML;
};
// A render function for our row action buttons
var createActions = function (data, type, row) {
var div = document.createElement('div');
div.setAttribute('class', 'action-buttons');
var editButton = document.createElement('button');
editButton.setAttribute('type', 'button');
editButton.setAttribute('class', 'editButton btn btn-xs btn-default');
editButton.setAttribute('data-batch-id', row.id);
editButton.appendChild(document.createTextNode(ill_batch_edit));
var deleteButton = document.createElement('button');
deleteButton.setAttribute('type', 'button');
deleteButton.setAttribute('class', 'deleteButton btn btn-xs btn-danger');
deleteButton.setAttribute('data-batch-id', row.id);
deleteButton.appendChild(document.createTextNode(ill_batch_delete));
div.appendChild(editButton);
div.appendChild(deleteButton);
return div.outerHTML;
};
// Add event listeners to our row action buttons
var addEventListeners = function () {
var del = document.querySelectorAll('.deleteButton');
del.forEach(function (el) {
el.addEventListener('click', handleDeleteClick);
});
var edit = document.querySelectorAll('.editButton');
edit.forEach(function (elEdit) {
elEdit.addEventListener('click', handleEditClick);
});
};
// Remove all added event listeners
var removeEventListeners = function () {
var del = document.querySelectorAll('.deleteButton');
del.forEach(function (el) {
el.removeEventListener('click', handleDeleteClick);
});
window.removeEventListener('load', onload);
window.removeEventListener('beforeunload', removeEventListeners);
};
// Handle "Delete" clicks
var handleDeleteClick = function(e) {
var el = e.srcElement;
if (confirm(ill_batch_confirm_delete)) {
deleteBatch(el);
}
};
// Handle "Edit" clicks
var handleEditClick = function(e) {
var el = e.srcElement;
var id = el.dataset.batchId;
window.openBatchModal(id);
};
// Delete a batch
// - Make the API call
// - Handle errors
// - Update our proxy data
var deleteBatch = function (el) {
var id = el.dataset.batchId;
doBatchApiRequest(
'/' + id,
{ method: 'DELETE' }
)
.then(function (response) {
if (!response.ok) {
window.handleApiError(ill_batch_delete_fail);
} else {
removeBatch(el.dataset.batchId);
}
})
.catch(function (response) {
window.handleApiError(ill_batch_delete_fail);
})
};
// Remove a batch from our proxy data
var removeBatch = function(id) {
batchesProxy.data = batchesProxy.data.filter(function (batch) {
return batch.id != id;
});
};
// Redraw the table
var updateTable = function () {
table.api()
.clear()
.rows.add(batchesProxy.data)
.draw();
};
})();

View File

@ -0,0 +1,49 @@
(function () {
// Enable the modal to be opened from anywhere
// If we're working with an existing batch, set the ID so the
// modal can access it
window.openBatchModal = function (id, backend) {
var idEl = document.getElementById('ill-batch-details');
idEl.dataset.backend = backend;
if (id) {
idEl.dataset.batchId = id;
}
$('#ill-batch-modal').modal({ show: true });
};
// Make a batch API call, returning the resulting promise
window.doBatchApiRequest = function (url, options) {
var batchListApi = '/api/v1/illbatches';
var fullUrl = batchListApi + (url ? url : '');
return doApiRequest(fullUrl, options);
};
// Make a "create local ILL submission" call
window.doCreateSubmission = function (body, options) {
options = Object.assign(
options || {},
{
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify(body)
}
);
return doApiRequest(
'/api/v1/ill/requests',
options
)
}
// Make an API call, returning the resulting promise
window.doApiRequest = function (url, options) {
return fetch(url, options);
};
// Display an API error
window.handleApiError = function (error) {
alert(error);
};
})();

View File

@ -51,7 +51,7 @@ $(document).ready(function() {
return '';
}
// At the moment, the only prefilter possible is borrowernumber
// Possible prefilters: borrowernumber, batch_id
// see ill/ill-requests.pl and members/ill-requests.pl
let additional_prefilters = {};
if(prefilters){
@ -63,6 +63,7 @@ $(document).ready(function() {
}
let borrower_prefilter = additional_prefilters['borrowernumber'] || null;
let batch_id_prefilter = additional_prefilters['batch_id'] || null;
let additional_filters = {
"me.backend": function(){
@ -78,6 +79,9 @@ $(document).ready(function() {
"me.borrowernumber": function(){
return borrower_prefilter ? { "=": borrower_prefilter } : "";
},
"me.batch_id": function(){
return batch_id_prefilter ? { "=": batch_id_prefilter } : "";
},
"-or": function(){
let patron = $("#illfilter_patron").val();
let status = $("#illfilter_status").val();
@ -119,7 +123,7 @@ $(document).ready(function() {
return filters;
},
"me.placed": function(){
if ( Object.keys(additional_prefilters).length ) return "";
if (Object.keys(additional_prefilters).length && borrower_prefilter) return "";
let placed_start = $('#illfilter_dateplaced_start').get(0)._flatpickr.selectedDates[0];
let placed_end = $('#illfilter_dateplaced_end').get(0)._flatpickr.selectedDates[0];
if (!placed_start && !placed_end) return "";
@ -129,7 +133,7 @@ $(document).ready(function() {
}
},
"me.updated": function(){
if (Object.keys(additional_prefilters).length) return "";
if (Object.keys(additional_prefilters).length && borrower_prefilter) return "";
let updated_start = $('#illfilter_datemodified_start').get(0)._flatpickr.selectedDates[0];
let updated_end = $('#illfilter_datemodified_end').get(0)._flatpickr.selectedDates[0];
if (!updated_start && !updated_end) return "";
@ -175,8 +179,11 @@ $(document).ready(function() {
};
let table_id = "#ill-requests";
if (borrower_prefilter) {
table_id += "-patron-" + borrower_prefilter;
} else if ( batch_id_prefilter ){
table_id += "-batch-" + batch_id_prefilter;
}
var ill_requests_table = $(table_id).kohaTable({
@ -188,6 +195,7 @@ $(document).ready(function() {
'biblio',
'comments+count',
'extended_attributes',
'batch',
'library',
'id_prefix',
'patron'
@ -206,6 +214,19 @@ $(document).ready(function() {
'">' + escape_str(row.id_prefix) + escape_str(data) + '</a>';
}
},
{
"data": "batch.name", // batch
"orderable": false,
"render": function(data, type, row, meta) {
return row.batch ?
'<a href="/cgi-bin/koha/ill/ill-requests.pl?batch_id=' +
row.batch_id +
'">' +
row.batch.name +
'</a>'
: "";
}
},
{
"data": "", // author
"orderable": false,