Bug 30484: Implement support for ILL request updates

This commit adds support for the concept of ILL request update notices.

- Adds a new Koha::Illrequest::SupplierUpdate class that is used to
encapsulate an update to a request, this update may come from a
supplier via a backend or from core ILL via, perhaps, a user action
- Adds a new Koha::Illrequest::SupplierUpdateProcessor base class that
can be subclassed in order to create a processor that can be passed an
update and act accordingly.
- Updates to Illrequest.pm to support the above classes and allow core
Koha to offer update processors
- A shell script to initiate a periodic process to check for updates
meeting given criteria and run the appropriate processors

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>

https://bugs.koha-community.org/show_bug.cgi?id=28909
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
This commit is contained in:
Andrew Isherwood 2022-04-08 16:09:30 +01:00 committed by Tomas Cohen Arazi
parent adf0094d66
commit 65a2fc2fa3
Signed by: tomascohen
GPG key ID: 0A272EA1B2F3C15F
4 changed files with 505 additions and 6 deletions

View file

@ -116,6 +116,33 @@ available for request.
=head2 Class methods
=head3 init_processors
$request->init_processors()
Initialises an empty processors arrayref
=cut
sub init_processors {
my ( $self ) = @_;
$self->{processors} = [];
}
=head3 push_processor
$request->push_processors(sub { ...something... });
Pushes a passed processor function into our processors arrayref
=cut
sub push_processor {
my ( $self, $processor ) = @_;
push @{$self->{processors}}, $processor;
}
=head3 statusalias
my $statusalias = $request->statusalias;
@ -371,6 +398,7 @@ sub _backend_capability {
try {
$capability = $self->_backend->capabilities($name);
} catch {
warn $_;
return 0;
};
# Try to invoke it
@ -896,6 +924,28 @@ sub backend_create {
return $self->expandTemplate($result);
}
=head3 backend_get_update
my $update = backend_get_update($request);
Given a request, returns an update in a prescribed
format that can then be passed to update parsers
=cut
sub backend_get_update {
my ( $self, $options ) = @_;
my $response = $self->_backend_capability(
'get_supplier_update',
{
request => $self,
%{$options}
}
);
return $response;
}
=head3 expandTemplate
my $params = $abstract->expandTemplate($params);
@ -1437,7 +1487,7 @@ Send a specified notice regarding this request to a patron
=cut
sub send_patron_notice {
my ( $self, $notice_code ) = @_;
my ( $self, $notice_code, $additional_text ) = @_;
# We need a notice code
if (!$notice_code) {
@ -1448,8 +1498,9 @@ sub send_patron_notice {
# Map from the notice code to the messaging preference
my %message_name = (
ILL_PICKUP_READY => 'Ill_ready',
ILL_REQUEST_UNAVAIL => 'Ill_unavailable'
ILL_PICKUP_READY => 'Ill_ready',
ILL_REQUEST_UNAVAIL => 'Ill_unavailable',
ILL_REQUEST_UPDATE => 'Ill_update'
);
# Get the patron's messaging preferences
@ -1471,8 +1522,9 @@ sub send_patron_notice {
my @fail = ();
for my $transport (@transports) {
my $letter = $self->get_notice({
notice_code => $notice_code,
transport => $transport
notice_code => $notice_code,
transport => $transport,
additional_text => $additional_text
});
if ($letter) {
my $result = C4::Letters::EnqueueLetter({
@ -1613,13 +1665,50 @@ sub get_notice {
substitute => {
ill_bib_title => $title ? $title->value : '',
ill_bib_author => $author ? $author->value : '',
ill_full_metadata => $metastring
ill_full_metadata => $metastring,
additional_text => $params->{additional_text}
}
);
return $letter;
}
=head3 attach_processors
Receive a Koha::Illrequest::SupplierUpdate and attach
any processors we have for it
=cut
sub attach_processors {
my ( $self, $update ) = @_;
foreach my $processor(@{$self->{processors}}) {
if (
$processor->{target_source_type} eq $update->{source_type} &&
$processor->{target_source_name} eq $update->{source_name}
) {
$update->attach_processor($processor);
}
}
}
=head3 append_to_note
append_to_note("Some text");
Append some text to the staff note
=cut
sub append_to_note {
my ($self, $text) = @_;
my $current = $self->notesstaff;
$text = ($current && length $current > 0) ? "$current\n\n$text" : $text;
$self->notesstaff($text)->store;
}
=head3 id_prefix
my $prefix = $record->id_prefix;

View file

@ -0,0 +1,111 @@
package Koha::Illrequest::SupplierUpdate;
# Copyright 2022 PTFS Europe Ltd
#
# 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;
=head1 NAME
Koha::Illrequest::SupplierUpdate - Represents a single request update from a supplier
=head1 SYNOPSIS
Object-oriented class that provides an object allowing us to interact with
an update from a supplier
=head1 DESCRIPTION
Object-oriented class that provides an object allowing us to interact with
an update from a supplier
=head1 API
=head2 Class Methods
=head3 new
my $update = Koha::Illrequest::SupplierUpdate->new(
$source_type,
$source_name,
$update
);
Create a new Koha::Illrequest::SupplierUpdate object.
=cut
sub new {
my ( $class, $source_type, $source_name, $update, $request ) = @_;
my $self = {};
$self->{source_type} = $source_type;
$self->{source_name} = $source_name;
$self->{update} = $update;
$self->{request} = $request;
$self->{processors} = [];
bless $self, $class;
return $self;
}
=head3 attach_processor
Koha::Illrequest::SupplierUpdate->attach_processor($processor);
Pushes a processor function onto the 'processors' arrayref
=cut
sub attach_processor {
my ( $self, $processor ) = @_;
push(@{$self->{processors}}, $processor);
}
=head3 run_processors
Koha::Illrequest::SupplierUpdate->run_processors();
Iterates all processors on this object and runs each
=cut
sub run_processors {
my ( $self, $options ) = @_;
my $results = [];
foreach my $processor(@{$self->{processors}}) {
my $processor_result = {
name => $processor->{name},
result => {
success => [],
error => []
}
};
$processor->run($self, $options, $processor_result->{result});
push @{$results}, $processor_result;
}
return $results;
}
=head1 AUTHOR
Andrew Isherwood <andrew.isherwood@ptfs-europe.com>
=cut
1;

View file

@ -0,0 +1,85 @@
package Koha::Illrequest::SupplierUpdateProcessor;
# Copyright 2022 PTFS Europe Ltd
#
# 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;
=head1 NAME
Koha::Illrequest::SupplierUpdateProcessor - Represents a SupplerUpdate processor
=head1 SYNOPSIS
Object-oriented class that provides an object allowing us to perform processing on
a SupplierUpdate
=head1 DESCRIPTION
Object-oriented base class that provides an object allowing us to perform processing on
a SupplierUpdate
** This class should not be directly instantiated, it should only be sub-classed **
=head1 API
=head2 Class Methods
=head3 new
my $processor = Koha::Illrequest::SupplierUpdateProcessor->new(
$target_source_type,
$target_source_name
);
Create a new Koha::Illrequest::SupplierUpdateProcessor object.
=cut
sub new {
my ( $class, $target_source_type, $target_source_name, $processor_name ) = @_;
my $self = {};
$self->{target_source_type} = $target_source_type;
$self->{target_source_name} = $target_source_name;
$self->{name} = $processor_name;
bless $self, $class;
return $self;
}
=head3 run
Koha::Illrequest::SupplierUpdateProcessor->run();
Runs the processor
=cut
sub run {
my ( $self ) = @_;
my ( $package, $filename ) = caller;
warn __PACKAGE__ . " run should only be invoked by a subclass\n";
}
=head1 AUTHOR
Andrew Isherwood <andrew.isherwood@ptfs-europe.com>
=cut
1;

214
misc/process_ill_updates.pl Executable file
View file

@ -0,0 +1,214 @@
#!/usr/bin/perl
# This file is part of Koha.
#
# Copyright (C) 2022 PTFS Europe
#
# 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 Getopt::Long qw( GetOptions );
use POSIX;
use Koha::Script;
use Koha::Illrequests;
# Command line option values
my $get_help = 0;
my $statuses = "";
my $status_alias = "";
my $status_to = "";
my $status_alias_to = "";
my $backend = "";
my $dry_run = 0;
my $delay = 0;
my $debug = 0;
my $options = GetOptions(
'h|help' => \$get_help,
'statuses:s' => \$statuses,
'status-alias:s' => \$status_alias,
'status-to:s' => \$status_to,
'status-alias-to:s' => \$status_alias_to,
'backend=s' => \$backend,
'dry-run' => \$dry_run,
'api-delay:i' => \$delay,
'debug' => \$debug
);
if ($get_help) {
get_help();
exit 1;
}
if (!$backend) {
print "No backend specified\n";
exit 0;
}
# First check we can proceed
my $cfg = Koha::Illrequest::Config->new;
my $backends = $cfg->available_backends;
my $has_branch = $cfg->has_branch;
my $backends_available = ( scalar @{$backends} > 0 );
if (!$has_branch || $backends_available == 0) {
print "Unable to proceed:\n";
print "Branch configured: $has_branch\n";
print "Backends available: $backends_available\n";
exit 0;
}
# Get all required requests
my @statuses_arr = split(/:/, $statuses);
my @status_alias_arr = split(/:/, $status_alias);
my $where = {
backend => $backend
};
if (scalar @statuses_arr > 0) {
my @or = grep(!/null/, @statuses_arr);
if (scalar @or < scalar @statuses_arr) {
push @or, undef;
}
$where->{status} = \@or;
}
if (scalar @status_alias_arr > 0) {
my @or = grep(!/null/, @status_alias_arr);
if (scalar @or < scalar @status_alias_arr) {
push @or, undef;
}
$where->{status_alias} = \@or;
}
debug_msg("DBIC WHERE:");
debug_msg($where);
my $requests = Koha::Illrequests->search($where);
debug_msg("Processing " . $requests->count . " requests");
# Create an options hashref to pass to processors
my $options_to_pass = {
dry_run => $dry_run,
status_to => $status_to,
status_alias_to => $status_alias_to,
delay => $delay,
debug => \&debug_msg
};
# The progress log
my $output = [];
while (my $request = $requests->next) {
debug_msg("- Request ID " . $request->illrequest_id);
my $update = $request->backend_get_update($options_to_pass);
# The log for this request
my $update_log = {
request_id => $request->illrequest_id,
processed_by => $request->_backend->name,
processors_run => []
};
if ($update) {
# Currently we make an assumption, this may need revisiting
# if we need to extend the functionality:
#
# Only the backend that originated the update will want to
# process it
#
# Since each backend's update format is different, it may
# be necessary for a backend to subclass Koha::Illrequest::SupplierUpdate
# so it can provide methods (corresponding to a generic interface) that
# return pertinent info to core ILL when it is processing updates
#
# Attach any request processors
$request->attach_processors($update);
# Attach any processors from this request's backend
$request->_backend->attach_processors($update);
my $processor_results = $update->run_processors($options_to_pass);
# Update our progress log
$update_log->{processors_run} = $processor_results;
}
push @{$output}, $update_log;
}
print_summary($output);
sub print_summary {
my ( $log ) = @_;
my $timestamp = POSIX::strftime("%d/%m/%Y %H:%M:%S\n", localtime);
print "Run details:\n";
foreach my $entry(@{$log}) {
my @processors_run = @{$entry->{processors_run}};
print "Request ID: " . $entry->{request_id} . "\n";
print " Processing by: " . $entry->{processed_by} . "\n";
print " Number of processors run: " . scalar @processors_run . "\n";
if (scalar @processors_run > 0) {
print " Processor details:\n";
foreach my $processor(@processors_run) {
print " Processor name: " . $processor->{name} . "\n";
print " Success messages: " . join(", ", @{$processor->{result}->{success}}) . "\n";
print " Error messages: " . join(", ", @{$processor->{result}->{error}}) . "\n";
}
}
}
print "Job completed at $timestamp\n====================================\n\n"
}
sub debug_msg {
my ( $msg ) = @_;
if (!$debug) {
return;
}
if (ref $msg eq 'HASH') {
use Data::Dumper;
$msg = Dumper $msg;
}
print STDERR "$msg\n";
}
sub get_help {
print <<"HELP";
$0: Fetch and process outstanding ILL updates
This script will fetch all requests that have the specified
statuses and run any applicable processor scripts on them.
For example, the RapidILL backend provides a processor script
that emails users when their requested electronic resource
request has been fulfilled
Parameters:
--statuses <statuses> specify the statuses a request must have in order to be processed,
statuses should be separated by a : e.g. REQ:COMP:NEW. A null value
can be specified by passing null, e.g. --statuses null
--status-aliases <status-aliases> specify the statuses aliases a request must have in order to be processed,
statuses should be separated by a : e.g. STA:OLD:PRE. A null value
can be specified by passing null, e.g. --status-aliases null
--status-to <status-to> specify the status a successfully processed request must be set to
after processing
--status-alias-to <status-alias-to> specify the status alias a successfully processed request must be set to
after processing
--dry-run only produce a run report, without actually doing anything permanent
--api-delay <seconds> if a processing script needs to make an API call, how long a pause
should be inserted between each API call
--debug print additional debugging info during run
--help or -h get help
HELP
}