2622 lines
73 KiB
Perl
2622 lines
73 KiB
Perl
package Koha::ILL::Request;
|
|
|
|
# Copyright PTFS Europe 2016,2018
|
|
#
|
|
# 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 <https://www.gnu.org/licenses>.
|
|
|
|
use Modern::Perl;
|
|
|
|
use Clone qw(clone);
|
|
use Try::Tiny qw(catch try);
|
|
use DateTime;
|
|
|
|
use C4::Letters;
|
|
use Mojo::Util qw(deprecated);
|
|
use File::Basename qw(dirname);
|
|
|
|
use Koha::AuthorisedValue;
|
|
use Koha::AuthorisedValues;
|
|
use Koha::Biblios;
|
|
use Koha::Cache::Memory::Lite;
|
|
use Koha::Database;
|
|
use Koha::DateUtils qw( dt_from_string );
|
|
use Koha::Exceptions::Ill;
|
|
use Koha::I18N qw(__);
|
|
use Koha::ILL::Backend::Standard;
|
|
use Koha::ILL::Batches;
|
|
use Koha::ILL::Comments;
|
|
use Koha::ILL::Request::Attributes;
|
|
use Koha::ILL::Request::Logger;
|
|
use Koha::ItemTypes;
|
|
use Koha::Items;
|
|
use Koha::Libraries;
|
|
use Koha::AdditionalContents;
|
|
use Koha::Patron;
|
|
use Koha::Statistic;
|
|
|
|
use C4::Circulation qw( CanBookBeIssued AddIssue );
|
|
|
|
use base qw(Koha::Object);
|
|
|
|
=head1 NAME
|
|
|
|
Koha::ILL::Request - Koha ILL request Object class
|
|
|
|
=head1 (Re)Design
|
|
|
|
An ILL request consists of two parts; the Koha::ILL::Request Koha::Object, and a series
|
|
of related Koha::ILL::Request::Attributes.
|
|
|
|
The former encapsulates the basic necessary information that any ILL requires
|
|
to be usable in Koha. The latter is a set of additional properties used by
|
|
one of the backends.
|
|
|
|
The former subsumes the legacy "Status" object. The latter remains
|
|
encapsulated in the "Record" object.
|
|
|
|
TODO:
|
|
|
|
- Anything invoking the ->status method; annotated with:
|
|
+ # Old use of ->status !
|
|
|
|
=head1 API
|
|
|
|
=head2 Backend API Response Principles
|
|
|
|
All methods should return a hashref in the following format:
|
|
|
|
=over
|
|
|
|
=item * error
|
|
|
|
This should be set to 1 if an error was encountered.
|
|
|
|
=item * status
|
|
|
|
The status should be a string from the list of statuses detailed below.
|
|
|
|
=item * message
|
|
|
|
The message is a free text field that can be passed on to the end user.
|
|
|
|
=item * value
|
|
|
|
The value returned by the method.
|
|
|
|
=back
|
|
|
|
=head2 Interface Status Messages
|
|
|
|
=over
|
|
|
|
=item * branch_address_incomplete
|
|
|
|
An interface request has determined branch address details are incomplete.
|
|
|
|
=item * cancel_success
|
|
|
|
The interface's cancel_request method was successful in cancelling the
|
|
ILL request using the API.
|
|
|
|
=item * cancel_fail
|
|
|
|
The interface's cancel_request method failed to cancel the ILL request using
|
|
the API.
|
|
|
|
=item * unavailable
|
|
|
|
The interface's request method returned saying that the desired item is not
|
|
available for request.
|
|
|
|
=back
|
|
|
|
=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 ill_batch
|
|
|
|
my $ill_batch = $request->ill_batch;
|
|
|
|
Returns the I<Koha::ILL::Batch> associated with the request
|
|
|
|
=cut
|
|
|
|
sub ill_batch {
|
|
my ($self) = @_;
|
|
|
|
my $ill_batch = $self->_result->ill_batch;
|
|
return unless $ill_batch;
|
|
return Koha::ILL::Batch->_new_from_dbic($ill_batch);
|
|
}
|
|
|
|
=head3 statusalias
|
|
|
|
my $statusalias = $request->statusalias;
|
|
|
|
Returns a request's status alias, as a Koha::AuthorisedValue instance
|
|
or implicit undef. This is distinct from status_alias, which only returns
|
|
the value in the status_alias column, this method returns the entire
|
|
AuthorisedValue object
|
|
|
|
=cut
|
|
|
|
sub statusalias {
|
|
my ($self) = @_;
|
|
return unless $self->status_alias;
|
|
|
|
# We can't know which result is the right one if there are multiple
|
|
# ILL_STATUS_ALIAS authorised values with the same authorised_value column value
|
|
# so we just use the first
|
|
return Koha::AuthorisedValues->search(
|
|
{
|
|
category => 'ILL_STATUS_ALIAS',
|
|
authorised_value => $self->get_column('status_alias'),
|
|
},
|
|
{},
|
|
$self->branchcode
|
|
)->next;
|
|
}
|
|
|
|
=head3 illrequestattributes
|
|
|
|
=cut
|
|
|
|
sub illrequestattributes {
|
|
deprecated 'illrequestattributes is DEPRECATED in favor of extended_attributes';
|
|
my ($self) = @_;
|
|
return Koha::ILL::Request::Attributes->_new_from_dbic( scalar $self->_result->illrequestattributes );
|
|
}
|
|
|
|
=head3 illcomments
|
|
|
|
=cut
|
|
|
|
sub illcomments {
|
|
my ($self) = @_;
|
|
return Koha::ILL::Comments->_new_from_dbic( scalar $self->_result->illcomments );
|
|
}
|
|
|
|
=head3 comments
|
|
|
|
my $ill_comments = $req->comments;
|
|
|
|
Returns a I<Koha::ILL::Comments> resultset for the linked comments.
|
|
|
|
=cut
|
|
|
|
sub comments {
|
|
my ($self) = @_;
|
|
return Koha::ILL::Comments->_new_from_dbic( scalar $self->_result->comments );
|
|
}
|
|
|
|
=head3 logs
|
|
|
|
=cut
|
|
|
|
sub logs {
|
|
my ($self) = @_;
|
|
my $logger = Koha::ILL::Request::Logger->new;
|
|
return $logger->get_request_logs($self);
|
|
}
|
|
|
|
=head3 patron
|
|
|
|
my $patron = $request->patron;
|
|
|
|
For a given request, return the linked I<Koha::Patron> object
|
|
associated with it, or undef if none exists
|
|
|
|
=cut
|
|
|
|
sub patron {
|
|
my ($self) = @_;
|
|
|
|
my $patron_rs = $self->_result->patron;
|
|
return unless $patron_rs;
|
|
return Koha::Patron->_new_from_dbic($patron_rs);
|
|
}
|
|
|
|
=head3 library
|
|
|
|
my $library = $request->library;
|
|
|
|
Returns the linked I<Koha::Library> object.
|
|
|
|
=cut
|
|
|
|
sub library {
|
|
my ($self) = @_;
|
|
|
|
return Koha::Library->_new_from_dbic( scalar $self->_result->library );
|
|
}
|
|
|
|
=head3 extended_attributes
|
|
|
|
my $extended_attributes = $request->extended_attributes;
|
|
|
|
my $extended_attributes = $request->extended_attributes([
|
|
{
|
|
type => 'type',
|
|
value => 'type_value',
|
|
},
|
|
{
|
|
type => 'type2',
|
|
value => 'type2_value',
|
|
},
|
|
]);
|
|
|
|
Getter or setter for extended attributes
|
|
|
|
Getter: Returns the linked I <Koha::ILL::Request::Attributes> resultset object.
|
|
|
|
Setter: Adds the supplied extended attributes to the request
|
|
|
|
=cut
|
|
|
|
sub extended_attributes {
|
|
my ( $self, $attributes ) = @_;
|
|
|
|
if ($attributes) {
|
|
my $schema = $self->_result->result_source->schema;
|
|
$schema->txn_do(
|
|
sub {
|
|
for my $attribute (@$attributes) {
|
|
$attribute->{backend} = $self->backend;
|
|
eval { $self->_result->add_to_illrequestattributes($attribute); };
|
|
if ($@) {
|
|
warn "Error adding attribute $attribute->{type}: $@";
|
|
}
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
my $rs = $self->_result->extended_attributes;
|
|
|
|
# We call search to use the filters in Koha::ILL::Request::Attributes->search
|
|
return Koha::ILL::Request::Attributes->_new_from_dbic($rs)->search;
|
|
}
|
|
|
|
=head3 status_alias
|
|
|
|
$request->status_alias(143);
|
|
|
|
Overloaded getter/setter for status_alias,
|
|
that only returns authorised values from the
|
|
correct category and records the fact that the status has changed
|
|
|
|
=cut
|
|
|
|
sub status_alias {
|
|
my ( $self, $new_status_alias ) = @_;
|
|
|
|
my $current_status_alias = $self->get_column('status_alias');
|
|
|
|
if ($new_status_alias) {
|
|
|
|
# Keep a record of the previous status before we change it,
|
|
# we might need it
|
|
$self->{previous_status} =
|
|
$current_status_alias
|
|
? $current_status_alias
|
|
: scalar $self->status;
|
|
|
|
# This is hackery to enable us to undefine
|
|
# status_alias, since we need to have an overloaded
|
|
# status_alias method to get us around the problem described
|
|
# here:
|
|
# https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=20581#c156
|
|
# We need a way of accepting implied undef, so we can nullify
|
|
# the status_alias column, when called from $self->status
|
|
my $val = $new_status_alias eq "-1" ? undef : $new_status_alias;
|
|
my $ret = $self->set( { status_alias => $val } );
|
|
my $val_to_log = $val ? $new_status_alias : scalar $self->status;
|
|
if ($ret) {
|
|
my $logger = Koha::ILL::Request::Logger->new;
|
|
$logger->log_status_change(
|
|
{
|
|
request => $self,
|
|
value => $val_to_log
|
|
}
|
|
);
|
|
} else {
|
|
delete $self->{previous_status};
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
# We can't know which result is the right one if there are multiple
|
|
# ILL_STATUS_ALIAS authorised values with the same authorised_value column value
|
|
# so we just use the first
|
|
my $alias = Koha::AuthorisedValues->search(
|
|
{
|
|
category => 'ILL_STATUS_ALIAS',
|
|
authorised_value => $current_status_alias
|
|
},
|
|
{},
|
|
$self->branchcode
|
|
)->next;
|
|
|
|
if ($alias) {
|
|
return $alias->authorised_value;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
=head3 status
|
|
|
|
$request->status('CANREQ');
|
|
|
|
Overloaded getter/setter for request status,
|
|
also nullifies status_alias and records the fact that the status has changed
|
|
and sends a notice if appropriate
|
|
|
|
=cut
|
|
|
|
sub status {
|
|
my ( $self, $new_status ) = @_;
|
|
|
|
my $current_status = $self->get_column('status');
|
|
my $current_status_alias = $self->get_column('status_alias');
|
|
|
|
if ($new_status) {
|
|
|
|
# Keep a record of the previous status before we change it,
|
|
# we might need it
|
|
$self->{previous_status} =
|
|
$current_status_alias
|
|
? $current_status_alias
|
|
: $current_status;
|
|
my $ret = $self->set( { status => $new_status } )->store;
|
|
if ($current_status_alias) {
|
|
|
|
# This is hackery to enable us to undefine
|
|
# status_alias, since we need to have an overloaded
|
|
# status_alias method to get us around the problem described
|
|
# here:
|
|
# https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=20581#c156
|
|
# We need a way of passing implied undef to nullify status_alias
|
|
# so we pass -1, which is special cased in the overloaded setter
|
|
$self->status_alias("-1");
|
|
} else {
|
|
my $logger = Koha::ILL::Request::Logger->new;
|
|
$logger->log_status_change(
|
|
{
|
|
request => $self,
|
|
value => $new_status
|
|
}
|
|
);
|
|
}
|
|
delete $self->{previous_status};
|
|
|
|
# If status has changed to cancellation requested, send a notice
|
|
if ( $new_status eq 'CANCREQ' ) {
|
|
$self->send_staff_notice('ILL_REQUEST_CANCEL');
|
|
}
|
|
return $ret;
|
|
} else {
|
|
return $current_status;
|
|
}
|
|
}
|
|
|
|
=head3 get_backend_plugin
|
|
|
|
my $backend_plugin = $self->get_backend_plugin($backend_name);
|
|
|
|
Returns the installed I<Koha::Plugin> corresponding to the given backend_id or undef if no plugin is found
|
|
|
|
=cut
|
|
|
|
sub get_backend_plugin {
|
|
my ( $self, $backend_id ) = @_;
|
|
|
|
if ( !$self->{_plugin} ) {
|
|
my $koha_plugins = Koha::Plugins->new();
|
|
my @backend_plugins = $koha_plugins
|
|
? Koha::Plugins->new()->GetPlugins(
|
|
{
|
|
method => 'ill_backend',
|
|
metadata => { name => $backend_id },
|
|
all => 1,
|
|
}
|
|
)
|
|
: ();
|
|
|
|
$self->{_plugin} = $backend_plugins[0]
|
|
if scalar @backend_plugins;
|
|
}
|
|
|
|
return $self->{_plugin};
|
|
}
|
|
|
|
=head3 load_backend
|
|
|
|
Require "Base.pm" from the relevant ILL backend.
|
|
|
|
=cut
|
|
|
|
sub load_backend {
|
|
my ( $self, $backend_id ) = @_;
|
|
|
|
my $backend_name = $backend_id || $self->backend;
|
|
|
|
unless ( defined $backend_name && $backend_name ne '' ) {
|
|
Koha::Exceptions::Ill::InvalidBackendId->throw("An invalid backend ID was requested ('')");
|
|
}
|
|
|
|
my $backend_params = {
|
|
config => $self->_config,
|
|
logger => Koha::ILL::Request::Logger->new
|
|
};
|
|
|
|
my $plugin;
|
|
if ( $backend_name ne 'Standard' ) {
|
|
|
|
# Find plugin implementing the backend for the request
|
|
$plugin = $self->get_backend_plugin($backend_name);
|
|
}
|
|
|
|
if ( $backend_name eq 'Standard' ) {
|
|
|
|
# Load the Standard core backend
|
|
$self->{_my_backend} = Koha::ILL::Backend::Standard->new($backend_params);
|
|
} elsif ($plugin) {
|
|
|
|
$self->{_my_backend} = $plugin->new_ill_backend($backend_params);
|
|
} elsif ($backend_name) {
|
|
|
|
# Fallback to loading through backend_dir config
|
|
my @raw = qw/Koha Illbackends/; # Base Path
|
|
my $location = join "/", @raw, $backend_name, "Base.pm"; # File to load
|
|
my $backend_class = join "::", @raw, $backend_name, "Base"; # Package name
|
|
require $location;
|
|
$self->{_my_backend} = $backend_class->new($backend_params);
|
|
}
|
|
|
|
return $self;
|
|
}
|
|
|
|
=head3 _backend
|
|
|
|
my $backend = $abstract->_backend($new_backend);
|
|
my $backend = $abstract->_backend;
|
|
|
|
Getter/Setter for our API object.
|
|
|
|
=cut
|
|
|
|
sub _backend {
|
|
my ( $self, $backend ) = @_;
|
|
$self->{_my_backend} = $backend if ($backend);
|
|
|
|
# Dynamically load our backend object, as late as possible.
|
|
$self->load_backend unless ( $self->{_my_backend} );
|
|
return $self->{_my_backend};
|
|
}
|
|
|
|
=head3 _backend_capability
|
|
|
|
my $backend_capability_result = $self->_backend_capability($name, $args);
|
|
|
|
This is a helper method to invoke optional capabilities in the backend. If
|
|
the capability named by $name is not supported, return 0, else invoke it,
|
|
passing $args along with the invocation, and return its return value.
|
|
|
|
NOTE: this module suffers from a confusion in termninology:
|
|
|
|
in _backend_capability, the notion of capability refers to an optional feature
|
|
that is implemented in core, but might not be supported by a given backend.
|
|
|
|
in capabilities & custom_capability, capability refers to entries in the
|
|
status_graph (after union between backend and core).
|
|
|
|
The easiest way to fix this would be to fix the terminology in
|
|
capabilities & custom_capability and their callers.
|
|
|
|
=cut
|
|
|
|
sub _backend_capability {
|
|
my ( $self, $name, $args ) = @_;
|
|
my $capability = 0;
|
|
|
|
# See if capability is defined in backend
|
|
try {
|
|
$capability = $self->_backend->capabilities($name);
|
|
} catch {
|
|
warn $_;
|
|
return 0;
|
|
};
|
|
|
|
# Try to invoke it
|
|
if ( $capability && ref($capability) eq 'CODE' ) {
|
|
return &{$capability}($args);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
=head3 _config
|
|
|
|
my $config = $abstract->_config($config);
|
|
my $config = $abstract->_config;
|
|
|
|
Getter/Setter for our config object.
|
|
|
|
=cut
|
|
|
|
sub _config {
|
|
my ( $self, $config ) = @_;
|
|
$self->{_my_config} = $config if ($config);
|
|
|
|
# Load our config object, as late as possible.
|
|
unless ( $self->{_my_config} ) {
|
|
$self->{_my_config} = Koha::ILL::Request::Config->new;
|
|
}
|
|
return $self->{_my_config};
|
|
}
|
|
|
|
=head3 metadata
|
|
|
|
=cut
|
|
|
|
sub metadata {
|
|
my ($self) = @_;
|
|
|
|
if ( $self->_backend->can('metadata') ) {
|
|
|
|
# Old way of loading backends: Previous expected method was 'metadata'
|
|
return $self->_backend->metadata($self);
|
|
} else {
|
|
|
|
# New way of loading backends (as plugins): New expected method is 'backend_metadata'
|
|
return $self->_backend->backend_metadata($self);
|
|
}
|
|
}
|
|
|
|
=head3 _core_status_graph
|
|
|
|
my $core_status_graph = $illrequest->_core_status_graph;
|
|
|
|
Returns ILL module's default status graph. A status graph defines the list of
|
|
available actions at any stage in the ILL workflow. This is for instance used
|
|
by the perl script & template to generate the correct buttons to display to
|
|
the end user at any given point.
|
|
|
|
=cut
|
|
|
|
sub _core_status_graph {
|
|
my ($self) = @_;
|
|
return {
|
|
NEW => {
|
|
prev_actions => [], # Actions containing buttons
|
|
# leading to this status
|
|
id => 'NEW', # ID of this status
|
|
name => __('New request'), # UI name of this status
|
|
ui_method_name => __('New request'), # UI name of method leading
|
|
# to this status
|
|
method => 'create', # method to this status
|
|
next_actions => [ 'REQ', 'GENREQ', 'KILL' ], # buttons to add to all
|
|
# requests with this status
|
|
ui_method_icon => 'fa-plus', # UI Style class
|
|
},
|
|
REQ => {
|
|
prev_actions => [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ],
|
|
id => 'REQ',
|
|
name => __('Requested'),
|
|
ui_method_name => __('Confirm request'),
|
|
method => 'confirm',
|
|
next_actions => [ 'REQREV', 'COMP', 'CHK' ],
|
|
ui_method_icon => 'fa-check',
|
|
},
|
|
GENREQ => {
|
|
prev_actions => [ 'NEW', 'REQREV' ],
|
|
id => 'GENREQ',
|
|
name => __('Requested from partners'),
|
|
ui_method_name => __('Place request with partners'),
|
|
method => 'generic_confirm',
|
|
next_actions => [ 'COMP', 'CHK', 'REQREV' ],
|
|
ui_method_icon => 'fa-paper-plane',
|
|
},
|
|
REQREV => {
|
|
prev_actions => [ 'REQ', 'GENREQ' ],
|
|
id => 'REQREV',
|
|
name => __('Request reverted'),
|
|
ui_method_name => __('Revert request'),
|
|
method => 'cancel',
|
|
next_actions => [ 'REQ', 'GENREQ', 'KILL' ],
|
|
ui_method_icon => 'fa-times',
|
|
},
|
|
QUEUED => {
|
|
prev_actions => [],
|
|
id => 'QUEUED',
|
|
name => __('Queued request'),
|
|
ui_method_name => 0,
|
|
method => 0,
|
|
next_actions => [ 'REQ', 'KILL' ],
|
|
ui_method_icon => 0,
|
|
},
|
|
CANCREQ => {
|
|
prev_actions => ['NEW'],
|
|
id => 'CANCREQ',
|
|
name => __('Cancellation requested'),
|
|
ui_method_name => 0,
|
|
method => 0,
|
|
next_actions => [ 'KILL', 'REQ' ],
|
|
ui_method_icon => 0,
|
|
},
|
|
COMP => {
|
|
prev_actions => ['REQ'],
|
|
id => 'COMP',
|
|
name => __('Completed'),
|
|
ui_method_name => __('Mark completed'),
|
|
method => 'mark_completed',
|
|
next_actions => ['CHK'],
|
|
ui_method_icon => 'fa-check',
|
|
},
|
|
KILL => {
|
|
prev_actions => [ 'QUEUED', 'REQREV', 'NEW', 'CANCREQ' ],
|
|
id => 'KILL',
|
|
name => 0,
|
|
ui_method_name => __('Delete request'),
|
|
method => 'delete',
|
|
next_actions => [],
|
|
ui_method_icon => 'fa-trash',
|
|
},
|
|
CHK => {
|
|
prev_actions => [ 'REQ', 'GENREQ', 'COMP' ],
|
|
id => 'CHK',
|
|
name => __('Checked out'),
|
|
ui_method_name => __('Check out'),
|
|
needs_prefs => ['CirculateILL'],
|
|
needs_perms => ['user_circulate_circulate_remaining_permissions'],
|
|
|
|
# An array of functions that all must return true
|
|
needs_all => [ sub { my $r = shift; return $r->biblio; } ],
|
|
method => 'check_out',
|
|
next_actions => [],
|
|
ui_method_icon => 'fa-upload',
|
|
},
|
|
RET => {
|
|
prev_actions => ['CHK'],
|
|
id => 'RET',
|
|
name => __('Returned to library'),
|
|
ui_method_name => __('Check in'),
|
|
method => 'check_in',
|
|
next_actions => ['COMP'],
|
|
ui_method_icon => 'fa-download',
|
|
}
|
|
};
|
|
}
|
|
|
|
=head3 _status_graph_union
|
|
|
|
my $status_graph = $illrequest->_status_graph_union($origin, $new_graph);
|
|
|
|
Return a new status_graph, the result of merging $origin & new_graph. This is
|
|
operation is a union over the sets defied by the two graphs.
|
|
|
|
Each entry in $new_graph is added to $origin. We do not provide a syntax for
|
|
'subtraction' of entries from $origin.
|
|
|
|
Whilst it is not intended that this works, you can override entries in $origin
|
|
with entries with the same key in $new_graph. This can lead to problematic
|
|
behaviour when $new_graph adds an entry, which modifies a dependent entry in
|
|
$origin, only for the entry in $origin to be replaced later with a new entry
|
|
from $new_graph.
|
|
|
|
NOTE: this procedure does not "re-link" entries in $origin or $new_graph,
|
|
i.e. each of the graphs need to be correct at the outset of the operation.
|
|
|
|
=cut
|
|
|
|
sub _status_graph_union {
|
|
my ( $self, $core_status_graph, $backend_status_graph ) = @_;
|
|
|
|
# Create new status graph with:
|
|
# - all core_status_graph
|
|
# - for-each each backend_status_graph
|
|
# + add to new status graph
|
|
# + for each core prev_action:
|
|
# * locate core_status
|
|
# * update next_actions with additional next action.
|
|
# + for each core next_action:
|
|
# * locate core_status
|
|
# * update prev_actions with additional prev action
|
|
|
|
my @core_status_ids = keys %{$core_status_graph};
|
|
my $status_graph = clone($core_status_graph);
|
|
|
|
foreach my $backend_status_key ( keys %{$backend_status_graph} ) {
|
|
my $backend_status = $backend_status_graph->{$backend_status_key};
|
|
|
|
# Add to new status graph
|
|
$status_graph->{$backend_status_key} = $backend_status;
|
|
|
|
# Update all core methods' next_actions.
|
|
foreach my $prev_action ( @{ $backend_status->{prev_actions} } ) {
|
|
if ( grep { $prev_action eq $_ } @core_status_ids ) {
|
|
my @next_actions =
|
|
@{ $status_graph->{$prev_action}->{next_actions} };
|
|
push @next_actions, $backend_status_key
|
|
if ( !grep( /^$backend_status_key$/, @next_actions ) );
|
|
$status_graph->{$prev_action}->{next_actions} = \@next_actions;
|
|
}
|
|
}
|
|
|
|
# Update all core methods' prev_actions
|
|
foreach my $next_action ( @{ $backend_status->{next_actions} } ) {
|
|
if ( grep { $next_action eq $_ } @core_status_ids ) {
|
|
my @prev_actions =
|
|
@{ $status_graph->{$next_action}->{prev_actions} };
|
|
push @prev_actions, $backend_status_key
|
|
if ( !grep( /^$backend_status_key$/, @prev_actions ) );
|
|
$status_graph->{$next_action}->{prev_actions} = \@prev_actions;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $status_graph;
|
|
}
|
|
|
|
### Core API methods
|
|
|
|
=head3 capabilities
|
|
|
|
my $capabilities = $illrequest->capabilities;
|
|
|
|
Return a hashref mapping methods to operation names supported by the queried
|
|
backend.
|
|
|
|
Example return value:
|
|
|
|
{ create => "Create Request", confirm => "Progress Request" }
|
|
|
|
NOTE: this module suffers from a confusion in termninology:
|
|
|
|
in _backend_capability, the notion of capability refers to an optional feature
|
|
that is implemented in core, but might not be supported by a given backend.
|
|
|
|
in capabilities & custom_capability, capability refers to entries in the
|
|
status_graph (after union between backend and core).
|
|
|
|
The easiest way to fix this would be to fix the terminology in
|
|
capabilities & custom_capability and their callers.
|
|
|
|
=cut
|
|
|
|
sub capabilities {
|
|
my ( $self, $status ) = @_;
|
|
|
|
# Generate up to date status_graph
|
|
my $status_graph = $self->_status_graph_union(
|
|
$self->_core_status_graph,
|
|
$self->_backend->status_graph(
|
|
{
|
|
request => $self,
|
|
other => {}
|
|
}
|
|
)
|
|
);
|
|
|
|
# Extract available actions from graph.
|
|
return $status_graph->{$status} if $status;
|
|
|
|
# Or return entire graph.
|
|
return $status_graph;
|
|
}
|
|
|
|
=head3 custom_capability
|
|
|
|
Return the result of invoking $CANDIDATE on this request's backend with
|
|
$PARAMS, or 0 if $CANDIDATE is an unknown method on backend.
|
|
|
|
NOTE: this module suffers from a confusion in termninology:
|
|
|
|
in _backend_capability, the notion of capability refers to an optional feature
|
|
that is implemented in core, but might not be supported by a given backend.
|
|
|
|
in capabilities & custom_capability, capability refers to entries in the
|
|
status_graph (after union between backend and core).
|
|
|
|
The easiest way to fix this would be to fix the terminology in
|
|
capabilities & custom_capability and their callers.
|
|
|
|
=cut
|
|
|
|
sub custom_capability {
|
|
my ( $self, $candidate, $params ) = @_;
|
|
foreach my $capability ( values %{ $self->capabilities } ) {
|
|
if ( $candidate eq $capability->{method} ) {
|
|
my $response = $self->_backend->$candidate(
|
|
{
|
|
request => $self,
|
|
other => $params,
|
|
}
|
|
);
|
|
return $self->expand_template($response);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
=head3 available_backends
|
|
|
|
Return a list of available backends.
|
|
|
|
=cut
|
|
|
|
sub available_backends {
|
|
my ( $self, $reduced ) = @_;
|
|
my $backends = $self->_config->available_backends($reduced);
|
|
return $backends;
|
|
}
|
|
|
|
=head3 available_actions
|
|
|
|
Return a list of available actions.
|
|
|
|
=cut
|
|
|
|
sub available_actions {
|
|
my ($self) = @_;
|
|
my $current_action = $self->capabilities( $self->status );
|
|
my @available_actions = map { $self->capabilities($_) } @{ $current_action->{next_actions} };
|
|
return \@available_actions;
|
|
}
|
|
|
|
=head3 mark_completed
|
|
|
|
Mark a request as completed (status = COMP).
|
|
|
|
=cut
|
|
|
|
sub mark_completed {
|
|
my ( $self, $params ) = @_;
|
|
|
|
my $stage = $params->{stage};
|
|
|
|
my $status_aliases_exist = Koha::AuthorisedValues->search( { category => 'ILL_STATUS_ALIAS' } )->count;
|
|
|
|
if ( ( !$stage || $stage eq 'init' ) && $status_aliases_exist ) {
|
|
return {
|
|
method => 'mark_completed',
|
|
};
|
|
} elsif ( !$stage || $stage eq 'complete' ) {
|
|
|
|
$self->status('COMP');
|
|
$self->status_alias( $params->{status_alias} ) if $params->{status_alias};
|
|
$self->completed( dt_from_string() );
|
|
$self->after_completed();
|
|
$self->store;
|
|
return {
|
|
stage => 'commit',
|
|
next => 'illview',
|
|
};
|
|
} else {
|
|
|
|
# Invalid stage, return error.
|
|
return {
|
|
error => 1,
|
|
status => 'unknown_stage',
|
|
message => '',
|
|
method => 'confirm',
|
|
stage => $params->{stage},
|
|
value => {},
|
|
};
|
|
}
|
|
}
|
|
|
|
=head2 backend_illview
|
|
|
|
View and manage an ILL request
|
|
|
|
=cut
|
|
|
|
sub backend_illview {
|
|
my ( $self, $params ) = @_;
|
|
|
|
my $response = $self->_backend_capability(
|
|
'illview',
|
|
{
|
|
request => $self,
|
|
other => $params,
|
|
}
|
|
);
|
|
return $self->expand_template($response) if $response;
|
|
return $response;
|
|
}
|
|
|
|
=head2 backend_migrate
|
|
|
|
Migrate a request from one backend to another.
|
|
|
|
=cut
|
|
|
|
sub backend_migrate {
|
|
my ( $self, $params ) = @_;
|
|
|
|
# Set the request's backend to be the destination backend
|
|
$self->load_backend( $params->{backend} );
|
|
my $response = $self->_backend_capability(
|
|
'migrate',
|
|
{
|
|
request => $self,
|
|
other => $params,
|
|
}
|
|
);
|
|
return $self->expand_template($response) if $response;
|
|
return $response;
|
|
}
|
|
|
|
=head2 backend_confirm
|
|
|
|
Confirm a request. The backend handles setting of mandatory fields in the commit stage:
|
|
|
|
=over
|
|
|
|
=item * orderid
|
|
|
|
=item * accessurl, cost (if available).
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub backend_confirm {
|
|
my ( $self, $params ) = @_;
|
|
|
|
my $response = $self->_backend->confirm(
|
|
{
|
|
request => $self,
|
|
other => $params,
|
|
}
|
|
);
|
|
return $self->expand_template($response);
|
|
}
|
|
|
|
=head3 backend_update_status
|
|
|
|
=cut
|
|
|
|
sub backend_update_status {
|
|
my ( $self, $params ) = @_;
|
|
return $self->expand_template( $self->_backend->update_status($params) );
|
|
}
|
|
|
|
=head3 backend_cancel
|
|
|
|
my $ILLResponse = $request->backend_cancel;
|
|
|
|
The standard interface method allowing for request cancellation.
|
|
|
|
=cut
|
|
|
|
sub backend_cancel {
|
|
my ( $self, $params ) = @_;
|
|
|
|
my $result = $self->_backend->cancel(
|
|
{
|
|
request => $self,
|
|
other => $params
|
|
}
|
|
);
|
|
|
|
return $self->expand_template($result);
|
|
}
|
|
|
|
=head3 backend_renew
|
|
|
|
my $renew_response = $request->backend_renew;
|
|
|
|
The standard interface method allowing for request renewal queries.
|
|
|
|
=cut
|
|
|
|
sub backend_renew {
|
|
my ($self) = @_;
|
|
return $self->expand_template(
|
|
$self->_backend->renew(
|
|
{
|
|
request => $self,
|
|
}
|
|
)
|
|
);
|
|
}
|
|
|
|
=head3 backend_create
|
|
|
|
my $create_response = $abstractILL->backend_create($params);
|
|
|
|
Return an array of Record objects created by querying our backend with
|
|
a Search query.
|
|
|
|
In the context of the other ILL methods, this is a special method: we only
|
|
pass it $params, as it does not yet have any other data associated with it.
|
|
|
|
=cut
|
|
|
|
sub backend_create {
|
|
my ( $self, $params, $loggedin_patron ) = @_;
|
|
|
|
# Establish whether we need to do a generic copyright clearance.
|
|
if ( $params->{opac} ) {
|
|
|
|
my $patron_branchcode = $loggedin_patron ? $loggedin_patron->branchcode : undef;
|
|
my $copyright_content = Koha::AdditionalContents->search_for_display(
|
|
{
|
|
category => 'html_customizations',
|
|
location => ['ILLModuleCopyrightClearance'],
|
|
lang => $params->{lang},
|
|
$patron_branchcode ? ( library_id => $patron_branchcode ) : (),
|
|
}
|
|
);
|
|
|
|
if ( ( !$params->{stage} || $params->{stage} eq 'init' )
|
|
&& $copyright_content->count )
|
|
{
|
|
return {
|
|
error => 0,
|
|
status => '',
|
|
message => '',
|
|
method => 'create',
|
|
stage => 'copyrightclearance',
|
|
value => {
|
|
other => $params,
|
|
backend => $self->_backend->name
|
|
}
|
|
};
|
|
} elsif ( defined $params->{stage}
|
|
&& $params->{stage} eq 'copyrightclearance' )
|
|
{
|
|
$params->{copyrightclearance_confirmed} = 1;
|
|
$params->{stage} = 'init';
|
|
}
|
|
}
|
|
|
|
# First perform API action, then...
|
|
my $args = {
|
|
request => $self,
|
|
other => $params,
|
|
};
|
|
my $result = $self->_backend->create($args);
|
|
|
|
# ... simple case: we're not at 'commit' stage.
|
|
my $stage = $result->{stage};
|
|
return $self->expand_template($result)
|
|
unless ( 'commit' eq $stage );
|
|
|
|
# ... complex case: commit!
|
|
|
|
# Do we still have space for an ILL or should we queue?
|
|
my $permitted = 1;
|
|
if ( $self->patron ) {
|
|
$permitted = $self->check_limits( { patron => $self->patron }, { librarycode => $self->branchcode } );
|
|
$result->{permitted} = $permitted;
|
|
$self->status('QUEUED')->store unless ($permitted);
|
|
}
|
|
|
|
## Handle Unmediated ILLs
|
|
|
|
# For the unmediated workflow we only need to delegate to our backend. If
|
|
# that backend supports unmediateld_ill, it will do its thing and return a
|
|
# proper response. If it doesn't then _backend_capability returns 0, so
|
|
# we keep the current result.
|
|
if ( C4::Context->preference("ILLModuleUnmediated") && $permitted ) {
|
|
my $unmediated_result = $self->_backend_capability(
|
|
'unmediated_ill',
|
|
$args
|
|
);
|
|
$result = $unmediated_result if $unmediated_result;
|
|
}
|
|
|
|
return $self->expand_template($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 expand_template
|
|
|
|
my $params = $abstract->expand_template($params);
|
|
|
|
Return a version of $PARAMS augmented with our required template path.
|
|
|
|
=cut
|
|
|
|
sub expand_template {
|
|
my ( $self, $params ) = @_;
|
|
my $backend = $self->_backend->name;
|
|
|
|
my $auto_priority = C4::Context->preference("AutoILLBackendPriority");
|
|
my $unauth_request = C4::Context->preference("ILLOpacUnauthenticatedRequest");
|
|
my $method = $params->{method} // q{};
|
|
my $template_name = "ill/backends/Standard/$method.inc";
|
|
|
|
if ( $backend eq 'Standard' || ( $method eq 'edititem' && $auto_priority ) ) {
|
|
$params->{template} = $template_name;
|
|
$params->{opac_template} = $template_name;
|
|
return $params;
|
|
}
|
|
|
|
if ( $params->{error} && $params->{status} eq 'failed_captcha' && $auto_priority && $unauth_request ) {
|
|
$params->{template} = $template_name;
|
|
$params->{opac_template} = $template_name;
|
|
$params->{stage} = 'form';
|
|
return $params;
|
|
}
|
|
|
|
my $plugin = $self->get_backend_plugin( $self->_backend->name );
|
|
|
|
# Generate path to file to load
|
|
my $backend_dir;
|
|
my $backend_tmpl;
|
|
|
|
if ($plugin) {
|
|
|
|
# New way of loading backends: Through plugins
|
|
$backend_dir = $plugin->bundle_path;
|
|
$backend_tmpl = $backend_dir;
|
|
|
|
} else {
|
|
|
|
# Old way of loading backends: Through backend_dir config
|
|
$backend_dir = $self->_config->backend_dir;
|
|
$backend_tmpl = join "/", $backend_dir, $backend;
|
|
}
|
|
|
|
my $intra_tmpl = join "/", $backend_tmpl, "intra-includes", "$method.inc";
|
|
my $opac_tmpl = join "/", $backend_tmpl, "opac-includes", "$method.inc";
|
|
|
|
# Set files to load
|
|
$params->{template} = $intra_tmpl;
|
|
$params->{opac_template} = $opac_tmpl;
|
|
|
|
return $params;
|
|
}
|
|
|
|
#### Abstract Imports
|
|
|
|
=head3 getLimits
|
|
|
|
my $limit_rules = $abstract->getLimits( {
|
|
type => 'brw_cat' | 'branch',
|
|
value => $value
|
|
} );
|
|
|
|
Return the ILL limit rules for the supplied combination of type / value.
|
|
|
|
As the config may have no rules for this particular type / value combination,
|
|
or for the default, we must define fall-back values here.
|
|
|
|
=cut
|
|
|
|
sub getLimits {
|
|
my ( $self, $params ) = @_;
|
|
my $limits = $self->_config->getLimitRules( $params->{type} );
|
|
|
|
if ( defined $params->{value}
|
|
&& defined $limits->{ $params->{value} } )
|
|
{
|
|
return $limits->{ $params->{value} };
|
|
} else {
|
|
return $limits->{default} || { count => -1, method => 'active' };
|
|
}
|
|
}
|
|
|
|
=head3 getPrefix
|
|
|
|
my $prefix = $abstract->getPrefix( {
|
|
branch => $branch_code
|
|
} );
|
|
|
|
Return the ILL prefix as defined by our $params: either per borrower category,
|
|
per branch or the default.
|
|
|
|
=cut
|
|
|
|
sub getPrefix {
|
|
my ( $self, $params ) = @_;
|
|
my $brn_prefixes = $self->_config->getPrefixes();
|
|
return $brn_prefixes->{ $params->{branch} } || ""; # "the empty prefix"
|
|
}
|
|
|
|
=head3 get_type
|
|
|
|
my $type = $abstract->get_type();
|
|
|
|
Return a string representing the material type of this request or undef
|
|
|
|
=cut
|
|
|
|
sub get_type {
|
|
my ($self) = @_;
|
|
my $attr = $self->extended_attributes->find( { type => 'type' } );
|
|
return if !$attr;
|
|
return $attr->value;
|
|
}
|
|
|
|
=head3 get_type_disclaimer_value
|
|
|
|
my $type = $abstract->get_type_disclaimer_value();
|
|
|
|
Return the value submitted in the type disclaimer workflow stage
|
|
|
|
=cut
|
|
|
|
sub get_type_disclaimer_value {
|
|
my ($self) = @_;
|
|
my $attr = $self->extended_attributes->find( { type => 'type_disclaimer_value' } );
|
|
return if !$attr;
|
|
return $attr->value;
|
|
}
|
|
|
|
=head3 set_copyright_clearance_confirmed
|
|
|
|
$request->set_copyright_clearance_confirmed(1);
|
|
|
|
Sets the copyright clearance confirmation status for the request.
|
|
|
|
=cut
|
|
|
|
sub set_copyright_clearance_confirmed {
|
|
my ( $self, $confirmed ) = @_;
|
|
|
|
# Normalize to boolean: 0 or 1
|
|
my $value = $confirmed ? 1 : 0;
|
|
|
|
$self->add_or_update_attributes( { copyrightclearance_confirmed => $value } );
|
|
|
|
return $self;
|
|
}
|
|
|
|
=head3 add_or_update_attributes
|
|
|
|
$request->add_or_update_attributes({
|
|
title => 'Book Title',
|
|
author => 'Author Name',
|
|
isbn => '1234567890'
|
|
});
|
|
|
|
Adds new attributes or updates existing ones. More efficient than extended_attributes()
|
|
for updating existing attributes as it avoids duplicate key errors.
|
|
|
|
=cut
|
|
|
|
sub add_or_update_attributes {
|
|
my ( $self, $attributes ) = @_;
|
|
|
|
return unless $attributes && ref($attributes) eq 'HASH';
|
|
|
|
my $schema = $self->_result->result_source->schema;
|
|
$schema->txn_do(
|
|
sub {
|
|
while ( my ( $type, $value ) = each %{$attributes} ) {
|
|
|
|
# Skip undefined or empty values
|
|
next unless defined $value && $value ne '';
|
|
|
|
my $attr = $self->extended_attributes->find( { type => $type } );
|
|
|
|
if ($attr) {
|
|
|
|
# Update existing attribute only if value changed
|
|
$attr->update( { value => $value } ) if $attr->value ne $value;
|
|
} else {
|
|
|
|
# Create new attribute
|
|
Koha::ILL::Request::Attribute->new(
|
|
{
|
|
illrequest_id => $self->id,
|
|
type => $type,
|
|
value => $value,
|
|
backend => $self->backend,
|
|
}
|
|
)->store;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
return $self;
|
|
}
|
|
|
|
=head3 get_copyright_clearance_confirmed
|
|
|
|
my $confirmed = $request->get_copyright_clearance_confirmed;
|
|
|
|
Returns 1 if copyright clearance has been confirmed, 0 otherwise.
|
|
|
|
=cut
|
|
|
|
sub get_copyright_clearance_confirmed {
|
|
my ($self) = @_;
|
|
my $attr = $self->extended_attributes->find( { type => 'copyrightclearance_confirmed' } );
|
|
return $attr ? ( $attr->value ? 1 : 0 ) : 0;
|
|
}
|
|
|
|
=head3 get_type_disclaimer_date
|
|
|
|
my $type = $abstract->type_disclaimer_date();
|
|
|
|
Return the time the disclaimer was submitted in the type disclaimer workflow stage
|
|
|
|
=cut
|
|
|
|
sub get_type_disclaimer_date {
|
|
my ($self) = @_;
|
|
my $attr = $self->extended_attributes->find( { type => 'type_disclaimer_date' } );
|
|
return if !$attr;
|
|
return $attr->value;
|
|
}
|
|
|
|
=head3 manager
|
|
|
|
my $manager = $request->manager();
|
|
|
|
Return the Koha::Patron object found by $self->managedby
|
|
|
|
=cut
|
|
|
|
sub manager {
|
|
my ($self) = @_;
|
|
|
|
my $manager_rs = $self->_result->manager;
|
|
return unless $manager_rs;
|
|
return Koha::Patron->_new_from_dbic($manager_rs);
|
|
}
|
|
|
|
=head3 check_limits
|
|
|
|
my $ok = $requests->check_limits( {
|
|
borrower => $borrower,
|
|
branchcode => 'branchcode' | undef,
|
|
} );
|
|
|
|
Given $PARAMS, a hashref containing a $borrower object and a $branchcode,
|
|
see whether we are still able to place ILLs.
|
|
|
|
LimitRules are derived from koha-conf.xml:
|
|
+ default limit counts, and counting method
|
|
+ branch specific limit counts & counting method
|
|
+ borrower category specific limit counts & counting method
|
|
+ err on the side of caution: a counting fail will cause fail, even if
|
|
the other counts passes.
|
|
|
|
=cut
|
|
|
|
sub check_limits {
|
|
my ( $self, $params ) = @_;
|
|
my $patron = $params->{patron};
|
|
my $branchcode = $params->{librarycode} || $patron->branchcode;
|
|
|
|
# Establish maximum number of allowed requests
|
|
my ( $branch_rules, $brw_rules ) = (
|
|
$self->getLimits(
|
|
{
|
|
type => 'branch',
|
|
value => $branchcode
|
|
}
|
|
),
|
|
$self->getLimits(
|
|
{
|
|
type => 'brw_cat',
|
|
value => $patron->categorycode,
|
|
}
|
|
),
|
|
);
|
|
my ( $branch_limit, $brw_limit ) =
|
|
( $branch_rules->{count}, $brw_rules->{count} );
|
|
|
|
# Establish currently existing requests
|
|
my ( $branch_count, $brw_count ) = (
|
|
$self->_limit_counter( $branch_rules->{method}, { branchcode => $branchcode } ),
|
|
$self->_limit_counter( $brw_rules->{method}, { borrowernumber => $patron->borrowernumber } ),
|
|
);
|
|
|
|
# Compare and return
|
|
# A limit of -1 means no limit exists.
|
|
# We return blocked if either branch limit or brw limit is reached.
|
|
if ( ( $branch_limit != -1 && $branch_limit <= $branch_count )
|
|
|| ( $brw_limit != -1 && $brw_limit <= $brw_count ) )
|
|
{
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
=head3 _limit_counter
|
|
|
|
my $count = $self->_limit_counter( $method, $target );
|
|
|
|
Internal helper that counts existing ILL requests for rate-limiting. C<$method>
|
|
is either C<'annual'> (requests placed this calendar year) or C<'active'>
|
|
(non-completed requests). C<$target> is a hashref of the search constraint,
|
|
e.g. C<{ borrowernumber => $n }> or C<{ branchcode => $b }>.
|
|
|
|
=cut
|
|
|
|
sub _limit_counter {
|
|
my ( $self, $method, $target ) = @_;
|
|
|
|
# Establish parameters of counts
|
|
my $resultset;
|
|
if ( $method && $method eq 'annual' ) {
|
|
$resultset = Koha::ILL::Requests->search(
|
|
{
|
|
-and => [
|
|
%{$target},
|
|
\"YEAR(placed) = YEAR(NOW())"
|
|
]
|
|
}
|
|
);
|
|
} else { # assume 'active'
|
|
# XXX: This status list is ugly. There should be a method in config
|
|
# to return these.
|
|
my $where = { status => { -not_in => [ 'QUEUED', 'COMP' ] } };
|
|
$resultset = Koha::ILL::Requests->search( { %{$target}, %{$where} } );
|
|
}
|
|
|
|
# Fetch counts
|
|
return $resultset->count;
|
|
}
|
|
|
|
=head3 requires_moderation
|
|
|
|
my $status = $request->requires_moderation;
|
|
|
|
Return the name of the status if moderation by staff is required; or 0
|
|
otherwise.
|
|
|
|
=cut
|
|
|
|
sub requires_moderation {
|
|
my ($self) = @_;
|
|
my $require_moderation = {
|
|
'CANCREQ' => 'CANCREQ',
|
|
};
|
|
return $require_moderation->{ $self->status };
|
|
}
|
|
|
|
=head3 biblio
|
|
|
|
my $biblio = $request->biblio;
|
|
|
|
For a given request, return the biblio associated with it,
|
|
or undef if none exists
|
|
|
|
=cut
|
|
|
|
sub biblio {
|
|
my ($self) = @_;
|
|
my $biblio_rs = $self->_result->biblio;
|
|
return unless $biblio_rs;
|
|
return Koha::Biblio->_new_from_dbic($biblio_rs);
|
|
}
|
|
|
|
=head3 check_out
|
|
|
|
my $stage_summary = $request->check_out;
|
|
|
|
Handle the check_out method. The first stage involves gathering the required
|
|
data from the user via a form, the second stage creates an item and tries to
|
|
issue it to the patron. If successful, it notifies the patron, then it
|
|
returns a summary of how things went
|
|
|
|
=cut
|
|
|
|
sub check_out {
|
|
my ( $self, $params ) = @_;
|
|
|
|
# Objects required by the template
|
|
my $itemtypes = Koha::ItemTypes->search(
|
|
{},
|
|
{ order_by => ['description'] }
|
|
);
|
|
my $libraries = Koha::Libraries->search(
|
|
{},
|
|
{ order_by => ['branchcode'] }
|
|
);
|
|
my $biblio = $self->biblio;
|
|
|
|
# Find all statistical patrons
|
|
my $statistical_patrons = Koha::Patrons->search(
|
|
{ 'category_type' => 'x' },
|
|
{ join => { 'categorycode' => 'borrowers' } }
|
|
);
|
|
|
|
if ( !$params->{stage} || $params->{stage} eq 'init' ) {
|
|
|
|
# Present a form to gather the required data
|
|
#
|
|
# We may be viewing this page having previously tried to issue
|
|
# the item (in which case, we may already have created an item)
|
|
# so we pass the biblio for this request
|
|
return {
|
|
method => 'check_out',
|
|
stage => 'form',
|
|
value => {
|
|
itemtypes => $itemtypes,
|
|
libraries => $libraries,
|
|
statistical => $statistical_patrons,
|
|
biblio => $biblio
|
|
}
|
|
};
|
|
} elsif ( $params->{stage} eq 'form' ) {
|
|
|
|
# Validate what we've got and return with an error if we fail
|
|
my $errors = {};
|
|
if ( !$params->{item_type} || length $params->{item_type} == 0 ) {
|
|
$errors->{item_type} = 1;
|
|
}
|
|
if ( $params->{inhouse} && length $params->{inhouse} > 0 ) {
|
|
my $patron_count = Koha::Patrons->search( { cardnumber => $params->{inhouse} } )->count();
|
|
if ( $patron_count != 1 ) {
|
|
$errors->{inhouse} = 1;
|
|
}
|
|
}
|
|
|
|
# Check we don't have more than one item for this bib,
|
|
# if we do, something very odd is going on
|
|
# Having 1 is OK, it means we're likely trying to issue
|
|
# following a previously failed attempt, the item exists
|
|
# so we'll use it
|
|
my @items = $biblio->items->as_list;
|
|
my $item_count = scalar @items;
|
|
if ( $item_count > 1 ) {
|
|
$errors->{itemcount} = 1;
|
|
}
|
|
|
|
# Failed validation, go back to the form
|
|
if ( %{$errors} ) {
|
|
return {
|
|
method => 'check_out',
|
|
stage => 'form',
|
|
value => {
|
|
params => $params,
|
|
statistical => $statistical_patrons,
|
|
itemtypes => $itemtypes,
|
|
libraries => $libraries,
|
|
biblio => $biblio,
|
|
errors => $errors
|
|
}
|
|
};
|
|
}
|
|
|
|
# Passed validation
|
|
#
|
|
# Create an item if one doesn't already exist,
|
|
# if one does, use that
|
|
my $itemnumber;
|
|
if ( $item_count == 0 ) {
|
|
my $item_hash = {
|
|
biblionumber => $self->biblio_id,
|
|
homebranch => $params->{branchcode},
|
|
holdingbranch => $params->{branchcode},
|
|
location => $params->{branchcode},
|
|
itype => $params->{item_type},
|
|
barcode => 'ILL-' . $self->illrequest_id
|
|
};
|
|
try {
|
|
my $item = Koha::Item->new($item_hash)->store;
|
|
$itemnumber = $item->itemnumber;
|
|
};
|
|
} else {
|
|
$itemnumber = $items[0]->itemnumber;
|
|
}
|
|
|
|
# Check we have an item before going forward
|
|
if ( !$itemnumber ) {
|
|
return {
|
|
method => 'check_out',
|
|
stage => 'form',
|
|
value => {
|
|
params => $params,
|
|
itemtypes => $itemtypes,
|
|
libraries => $libraries,
|
|
statistical => $statistical_patrons,
|
|
errors => { item_creation => 1 }
|
|
}
|
|
};
|
|
}
|
|
|
|
# Do the check out
|
|
#
|
|
# Gather what we need
|
|
my $target_item = Koha::Items->find($itemnumber);
|
|
|
|
# Determine who we're issuing to
|
|
my $patron =
|
|
$params->{inhouse} && length $params->{inhouse} > 0
|
|
? Koha::Patrons->find( { cardnumber => $params->{inhouse} } )
|
|
: $self->patron;
|
|
|
|
my @issue_args = (
|
|
$patron,
|
|
scalar $target_item->barcode
|
|
);
|
|
if ( $params->{duedate} && length $params->{duedate} > 0 ) {
|
|
push @issue_args, dt_from_string( $params->{duedate} );
|
|
}
|
|
|
|
# Check if we can check out
|
|
my ( $error, $confirm, $alerts, $messages ) = C4::Circulation::CanBookBeIssued(@issue_args);
|
|
|
|
# If we got anything back saying we can't check out,
|
|
# return it to the template
|
|
my $problems = {};
|
|
if ( $error && %{$error} ) { $problems->{error} = $error }
|
|
if ( $confirm && %{$confirm} ) { $problems->{confirm} = $confirm }
|
|
if ( $alerts && %{$alerts} ) { $problems->{alerts} = $alerts }
|
|
if ( $messages && %{$messages} ) { $problems->{messages} = $messages }
|
|
|
|
if ( %{$problems} ) {
|
|
return {
|
|
method => 'check_out',
|
|
stage => 'form',
|
|
value => {
|
|
params => $params,
|
|
itemtypes => $itemtypes,
|
|
libraries => $libraries,
|
|
statistical => $statistical_patrons,
|
|
patron => $patron,
|
|
biblio => $biblio,
|
|
check_out_errors => $problems
|
|
}
|
|
};
|
|
}
|
|
|
|
# We can allegedly check out, so make it so
|
|
# Mark as ILL checkout in the logs to track system-initiated checkouts.
|
|
# AddIssue signature is ( $patron, $barcode, $datedue, $cancelreserve,
|
|
# $issuedate, $sipmode, $params ) - pad to position 7 so the params
|
|
# hashref is not mistaken for $issuedate or $sipmode.
|
|
push @issue_args, undef while @issue_args < 6;
|
|
push @issue_args, { forced => ['ILL_CHECKOUT'] };
|
|
my $issue = C4::Circulation::AddIssue(@issue_args);
|
|
|
|
if ($issue) {
|
|
|
|
# Update the request status
|
|
$self->status('CHK')->store;
|
|
return {
|
|
method => 'check_out',
|
|
stage => 'done_check_out',
|
|
value => {
|
|
params => $params,
|
|
patron => $patron,
|
|
check_out => $issue
|
|
}
|
|
};
|
|
} else {
|
|
return {
|
|
method => 'check_out',
|
|
stage => 'form',
|
|
value => {
|
|
params => $params,
|
|
itemtypes => $itemtypes,
|
|
libraries => $libraries,
|
|
errors => { item_check_out => 1 }
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
=head3 generic_confirm
|
|
|
|
my $stage_summary = $request->generic_confirm;
|
|
|
|
Handle the generic_confirm extended method. The first stage involves creating
|
|
a template email for the end user to edit in the browser. The second stage
|
|
attempts to submit the email.
|
|
|
|
=cut
|
|
|
|
sub generic_confirm {
|
|
my ( $self, $params ) = @_;
|
|
my $branch = Koha::Libraries->find( $params->{current_branchcode} )
|
|
|| die "Invalid current branchcode. Are you logged in as the database user?";
|
|
if ( !$params->{stage} || $params->{stage} eq 'init' ) {
|
|
|
|
# Get the message body from the notice definition
|
|
my $letter = $self->get_notice(
|
|
{
|
|
notice_code => 'ILL_PARTNER_REQ',
|
|
transport => 'email'
|
|
}
|
|
);
|
|
|
|
my $partners = Koha::Patrons->search( { categorycode => $self->_config->partner_code } );
|
|
return {
|
|
error => 0,
|
|
status => '',
|
|
message => '',
|
|
method => 'generic_confirm',
|
|
stage => 'draft',
|
|
value => {
|
|
draft => {
|
|
subject => $letter->{title},
|
|
body => $letter->{content}
|
|
},
|
|
partners => $partners,
|
|
}
|
|
};
|
|
|
|
} elsif ( 'draft' eq $params->{stage} ) {
|
|
|
|
# Create the to header
|
|
my $to = $params->{partners};
|
|
if ( defined $to ) {
|
|
$to =~ s/^\x00//; # Strip leading NULLs
|
|
}
|
|
Koha::Exceptions::Ill::NoTargetEmail->throw(
|
|
"No target email addresses found. Either select at least one partner or check your ILL partner library records."
|
|
) if ( !$to );
|
|
|
|
# Take the null delimited string that we receive and create
|
|
# an array of associated patron objects
|
|
my @to_patrons = map { Koha::Patrons->find( { borrowernumber => $_ } ) } split( /\x00/, $to );
|
|
|
|
# Create the from, replyto and sender headers
|
|
my $from = $branch->from_email_address;
|
|
my $replyto = $branch->inbound_ill_address;
|
|
Koha::Exceptions::Ill::NoLibraryEmail->throw("Your library has no usable email address. Please set it.")
|
|
if ( !$from );
|
|
|
|
# So we get a notice hashref, then substitute the possibly
|
|
# modified title and body from the draft stage
|
|
my $letter = $self->get_notice(
|
|
{
|
|
notice_code => 'ILL_PARTNER_REQ',
|
|
transport => 'email'
|
|
}
|
|
);
|
|
$letter->{title} = $params->{subject};
|
|
$letter->{content} = $params->{body};
|
|
|
|
if ($letter) {
|
|
|
|
# Keep track of who received this notice
|
|
my @queued = ();
|
|
|
|
# Iterate our array of recipient patron objects
|
|
foreach my $patron (@to_patrons) {
|
|
|
|
# Create the params we pass to the notice
|
|
my $params = {
|
|
letter => $letter,
|
|
borrowernumber => $patron->borrowernumber,
|
|
message_transport_type => 'email',
|
|
to_address => $patron->email,
|
|
from_address => $from,
|
|
reply_address => $replyto
|
|
};
|
|
my $result = C4::Letters::EnqueueLetter($params);
|
|
if ($result) {
|
|
push @queued, $patron->email;
|
|
}
|
|
}
|
|
|
|
# If all notices were queued successfully,
|
|
# store that
|
|
if ( scalar @queued == scalar @to_patrons ) {
|
|
$self->status("GENREQ")->store;
|
|
$self->_backend_capability(
|
|
'set_requested_partners',
|
|
{
|
|
request => $self,
|
|
to => join( "; ", @queued )
|
|
}
|
|
);
|
|
return {
|
|
error => 0,
|
|
status => '',
|
|
message => '',
|
|
method => 'generic_confirm',
|
|
stage => 'commit',
|
|
next => 'illview',
|
|
};
|
|
}
|
|
|
|
}
|
|
return {
|
|
error => 1,
|
|
status => 'email_failed',
|
|
message => 'Email queueing failed',
|
|
method => 'generic_confirm',
|
|
stage => 'draft',
|
|
};
|
|
} else {
|
|
die "Unknown stage, should not have happened.";
|
|
}
|
|
}
|
|
|
|
=head3 send_patron_notice
|
|
|
|
my $result = $request->send_patron_notice($notice_code);
|
|
|
|
Send a specified notice regarding this request to a patron
|
|
|
|
=cut
|
|
|
|
sub send_patron_notice {
|
|
my ( $self, $notice_code, $additional_text ) = @_;
|
|
|
|
# We need a notice code
|
|
if ( !$notice_code ) {
|
|
return { error => 'notice_no_type' };
|
|
}
|
|
|
|
# Map from the notice code to the messaging preference
|
|
my %message_name = (
|
|
ILL_PICKUP_READY => 'Ill_ready',
|
|
ILL_REQUEST_UNAVAIL => 'Ill_unavailable',
|
|
ILL_REQUEST_UPDATE => 'Ill_update'
|
|
);
|
|
|
|
# Get the patron's messaging preferences
|
|
my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences(
|
|
{
|
|
borrowernumber => $self->borrowernumber,
|
|
message_name => $message_name{$notice_code}
|
|
}
|
|
);
|
|
my @transports = keys %{ $borrower_preferences->{transports} };
|
|
|
|
return { result => { fail => [ 'email', 'sms' ], success => [] } } unless @transports;
|
|
|
|
# Notice should come from the library where the request was placed,
|
|
# not the patrons home library
|
|
my $branch = Koha::Libraries->find( $self->branchcode );
|
|
my $from_address = $branch->from_email_address;
|
|
my $reply_address = $branch->inbound_ill_address;
|
|
|
|
# Send the notice to the patron via the chosen transport methods
|
|
# and record the results
|
|
my @success = ();
|
|
my @fail = ();
|
|
for my $transport (@transports) {
|
|
my $letter = $self->get_notice(
|
|
{
|
|
notice_code => $notice_code,
|
|
transport => $transport,
|
|
additional_text => $additional_text
|
|
}
|
|
);
|
|
if ($letter) {
|
|
my $result = C4::Letters::EnqueueLetter(
|
|
{
|
|
letter => $letter,
|
|
borrowernumber => $self->borrowernumber,
|
|
message_transport_type => $transport,
|
|
from_address => $from_address,
|
|
reply_address => $reply_address
|
|
}
|
|
);
|
|
if ($result) {
|
|
push @success, $transport;
|
|
} else {
|
|
push @fail, $transport;
|
|
}
|
|
} else {
|
|
push @fail, $transport;
|
|
}
|
|
}
|
|
if ( scalar @success > 0 ) {
|
|
my $logger = Koha::ILL::Request::Logger->new;
|
|
$logger->log_patron_notice(
|
|
{
|
|
request => $self,
|
|
notice_code => $notice_code
|
|
}
|
|
);
|
|
}
|
|
return {
|
|
result => {
|
|
success => \@success,
|
|
fail => \@fail
|
|
}
|
|
};
|
|
}
|
|
|
|
=head3 send_staff_notice
|
|
|
|
my $result = $request->send_staff_notice($notice_code);
|
|
|
|
Send a specified notice regarding this request to staff
|
|
|
|
=cut
|
|
|
|
sub send_staff_notice {
|
|
my ( $self, $notice_code ) = @_;
|
|
|
|
# We need a notice code
|
|
if ( !$notice_code ) {
|
|
return { error => 'notice_no_type' };
|
|
}
|
|
|
|
# Get the staff notices that have been assigned for sending in
|
|
# the syspref
|
|
my $staff_to_send = C4::Context->preference('ILLSendStaffNotices') // q{};
|
|
|
|
# If it hasn't been enabled in the syspref, we don't want to send it
|
|
if ( $staff_to_send !~ /\b$notice_code\b/ ) {
|
|
return { error => 'notice_not_enabled' };
|
|
}
|
|
|
|
my $letter = $self->get_notice(
|
|
{
|
|
notice_code => $notice_code,
|
|
transport => 'email'
|
|
}
|
|
);
|
|
|
|
# Try and get an address to which to send staff notices
|
|
my $branch = Koha::Libraries->find( $self->branchcode );
|
|
my $to_address = $branch->inbound_ill_address;
|
|
my $from_address = $branch->inbound_ill_address;
|
|
|
|
my $params = {
|
|
letter => $letter,
|
|
borrowernumber => $self->borrowernumber,
|
|
message_transport_type => 'email',
|
|
from_address => $from_address
|
|
};
|
|
|
|
if ($to_address) {
|
|
$params->{to_address} = $to_address;
|
|
} else {
|
|
return { error => 'notice_no_create' };
|
|
}
|
|
|
|
if ($letter) {
|
|
C4::Letters::EnqueueLetter($params)
|
|
or warn "can't enqueue letter $letter";
|
|
return { success => 'notice_queued' };
|
|
} else {
|
|
return { error => 'notice_no_create' };
|
|
}
|
|
}
|
|
|
|
=head3 get_notice
|
|
|
|
my $notice = $request->get_notice($params);
|
|
|
|
Return a compiled notice hashref for the passed notice code
|
|
and transport type
|
|
|
|
=cut
|
|
|
|
sub get_notice {
|
|
my ( $self, $params ) = @_;
|
|
|
|
my $title = $self->extended_attributes->find( { type => 'title' } );
|
|
my $author = $self->extended_attributes->find( { type => 'author' } );
|
|
my $metahash = $self->metadata;
|
|
my @metaarray = ();
|
|
foreach my $key ( sort { lc $a cmp lc $b } keys %{$metahash} ) {
|
|
my $value = $metahash->{$key};
|
|
push @metaarray, "- $key: $value" if $value;
|
|
}
|
|
my $metastring = join( "\n", @metaarray );
|
|
|
|
my $illrequestattributes = { map { $_->type => $_->value } $self->extended_attributes->as_list };
|
|
|
|
my $letter = C4::Letters::GetPreparedLetter(
|
|
module => 'ill',
|
|
letter_code => $params->{notice_code},
|
|
branchcode => $self->branchcode,
|
|
message_transport_type => $params->{transport},
|
|
lang => $self->patron ? $self->patron->lang : undef,
|
|
tables => {
|
|
illrequests => $self->illrequest_id,
|
|
borrowers => $self->borrowernumber,
|
|
biblio => $self->biblio_id,
|
|
branches => $self->branchcode,
|
|
},
|
|
substitute => {
|
|
ill_bib_title => $title ? $title->value : '',
|
|
ill_bib_author => $author ? $author->value : '',
|
|
ill_full_metadata => $metastring,
|
|
additional_text => $params->{additional_text},
|
|
illrequestattributes => $illrequestattributes,
|
|
}
|
|
);
|
|
|
|
return $letter;
|
|
}
|
|
|
|
=head3 attach_processors
|
|
|
|
Receive a Koha::ILL::Request::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 add_unauthenticated_data
|
|
|
|
add_unauthenticated_data($metadata);
|
|
|
|
Adds unauthenticated data as I<Koha::ILL::Request::Attributes>
|
|
|
|
=cut
|
|
|
|
sub add_unauthenticated_data {
|
|
my ( $self, $metadata ) = @_;
|
|
|
|
my $extended_attributes = $self->extended_attributes(
|
|
[
|
|
{
|
|
type => 'unauthenticated_first_name',
|
|
value => $metadata->{'unauthenticated_first_name'},
|
|
},
|
|
{
|
|
type => 'unauthenticated_last_name',
|
|
value => $metadata->{'unauthenticated_last_name'},
|
|
},
|
|
{
|
|
type => 'unauthenticated_email',
|
|
value => $metadata->{'unauthenticated_email'},
|
|
},
|
|
]
|
|
);
|
|
}
|
|
|
|
=head3 unauth_request_data_error
|
|
|
|
unauth_request_data_error($metadata);
|
|
|
|
Checks if unauthenticated request data errored, returns error_code if so, else 0
|
|
|
|
=cut
|
|
|
|
sub unauth_request_data_error {
|
|
my ($metadata) = @_;
|
|
|
|
return 0 unless C4::Context->preference("ILLOpacUnauthenticatedRequest");
|
|
return 'missing_unauth_data'
|
|
unless $metadata->{unauthenticated_first_name}
|
|
&& $metadata->{unauthenticated_last_name}
|
|
&& $metadata->{unauthenticated_email};
|
|
|
|
return 'failed_captcha' if $metadata->{failed_captcha};
|
|
return 0;
|
|
}
|
|
|
|
=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;
|
|
|
|
Return the prefix appropriate for the current ILL request as derived from the
|
|
borrower and branch associated with this request's Status, and the config
|
|
file.
|
|
|
|
=cut
|
|
|
|
sub id_prefix {
|
|
my ($self) = @_;
|
|
my $prefix = $self->getPrefix(
|
|
{
|
|
branch => $self->branchcode,
|
|
}
|
|
);
|
|
$prefix .= "-" if ($prefix);
|
|
return $prefix;
|
|
}
|
|
|
|
=head3 _censor
|
|
|
|
my $params = $request->_censor($params);
|
|
|
|
Return $params, modified to reflect our censorship requirements.
|
|
|
|
=cut
|
|
|
|
sub _censor {
|
|
my ( $self, $params ) = @_;
|
|
my $censorship = $self->_config->censorship;
|
|
$params->{censor_notes_staff} = $censorship->{censor_notes_staff}
|
|
if ( $params->{opac} );
|
|
$params->{display_reply_date} = ( $censorship->{censor_reply_date} ) ? 0 : 1;
|
|
|
|
return $params;
|
|
}
|
|
|
|
=head3 store
|
|
|
|
$request->store;
|
|
|
|
Overloaded I<store> method that, in addition to performing the 'store',
|
|
possibly records the fact that something happened
|
|
|
|
=cut
|
|
|
|
sub store {
|
|
my ( $self, $attrs ) = @_;
|
|
|
|
my %updated_columns = $self->_result->get_dirty_columns;
|
|
|
|
my @holds;
|
|
if ( $self->in_storage
|
|
and defined $updated_columns{'borrowernumber'}
|
|
and Koha::Patrons->find( $updated_columns{'borrowernumber'} ) )
|
|
{
|
|
# borrowernumber has changed
|
|
my $old_illreq = $self->get_from_storage;
|
|
@holds = Koha::Holds->search(
|
|
{
|
|
borrowernumber => $old_illreq->borrowernumber,
|
|
biblionumber => $self->biblio_id,
|
|
}
|
|
)->as_list
|
|
if $old_illreq;
|
|
}
|
|
|
|
my $ret = $self->SUPER::store;
|
|
|
|
if ( scalar @holds ) {
|
|
|
|
# move holds to the changed borrowernumber
|
|
foreach my $hold (@holds) {
|
|
$hold->borrowernumber( $updated_columns{'borrowernumber'} )->store;
|
|
}
|
|
}
|
|
|
|
$attrs->{log_origin} = 'core';
|
|
|
|
if ( $ret && defined $attrs ) {
|
|
my $logger = Koha::ILL::Request::Logger->new;
|
|
$logger->log_maybe(
|
|
{
|
|
request => $self,
|
|
attrs => $attrs
|
|
}
|
|
);
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
=head3 after_completed
|
|
|
|
$request->after_completed;
|
|
|
|
Actions to be done after the request has been completed
|
|
|
|
=cut
|
|
|
|
sub after_completed {
|
|
my ($self) = @_;
|
|
|
|
Koha::Statistic->new(
|
|
{
|
|
borrowernumber => $self->borrowernumber // undef,
|
|
branch => $self->branchcode,
|
|
categorycode => $self->patron ? $self->patron->categorycode : undef,
|
|
ccode => undef,
|
|
illrequest_id => $self->illrequest_id,
|
|
itemnumber => undef,
|
|
itemtype => undef,
|
|
location => undef,
|
|
type => 'illreq_comp',
|
|
}
|
|
)->store();
|
|
}
|
|
|
|
=head3 after_created
|
|
|
|
$request->after_created;
|
|
|
|
Actions to be done after the request has been fully created
|
|
|
|
=cut
|
|
|
|
sub after_created {
|
|
my ($self) = @_;
|
|
|
|
Koha::Statistic->new(
|
|
{
|
|
borrowernumber => $self->borrowernumber // undef,
|
|
branch => $self->branchcode,
|
|
categorycode => $self->patron ? $self->patron->categorycode : undef,
|
|
ccode => undef,
|
|
illrequest_id => $self->illrequest_id,
|
|
itemnumber => undef,
|
|
itemtype => undef,
|
|
location => undef,
|
|
type => 'illreq_created',
|
|
}
|
|
)->store();
|
|
}
|
|
|
|
=head3 requested_partners
|
|
|
|
my $partners_string = $request->requested_partners;
|
|
|
|
Return the string representing the email addresses of the partners to
|
|
whom a request has been sent
|
|
|
|
=cut
|
|
|
|
sub requested_partners {
|
|
my ($self) = @_;
|
|
return $self->_backend_capability(
|
|
'get_requested_partners',
|
|
{ request => $self }
|
|
);
|
|
}
|
|
|
|
=head3 TO_JSON
|
|
|
|
$json = $request->TO_JSON
|
|
|
|
Overloaded I<TO_JSON> method that takes care of inserting calculated values
|
|
into the unblessed representation of the object.
|
|
|
|
TODO: This method does nothing and is not called anywhere. However, bug 74325
|
|
touches it, so keeping this for now until both this and bug 74325 are merged,
|
|
at which point we can sort it out and remove it completely
|
|
|
|
=cut
|
|
|
|
sub TO_JSON {
|
|
my ( $self, $embed ) = @_;
|
|
|
|
my $object = $self->SUPER::TO_JSON();
|
|
|
|
return $object;
|
|
}
|
|
|
|
=head3 public_read_list
|
|
|
|
This method returns the list of publicly readable database fields for both API and UI output purposes
|
|
|
|
=cut
|
|
|
|
sub public_read_list {
|
|
return [
|
|
qw(
|
|
illrequest_id backend status status_alias extended_attributes placed updated
|
|
)
|
|
];
|
|
}
|
|
|
|
=head2 Internal methods
|
|
|
|
=head3 to_api_mapping
|
|
|
|
=cut
|
|
|
|
sub to_api_mapping {
|
|
return {
|
|
accessurl => 'access_url',
|
|
batch_id => 'ill_batch_id',
|
|
backend => 'ill_backend_id',
|
|
borrowernumber => 'patron_id',
|
|
branchcode => 'library_id',
|
|
completed => 'completed_date',
|
|
deleted_biblio_id => undef,
|
|
illrequest_id => 'ill_request_id',
|
|
notesopac => 'opac_notes',
|
|
notesstaff => 'staff_notes',
|
|
orderid => 'ill_backend_request_id',
|
|
placed => 'requested_date',
|
|
price_paid => 'paid_price',
|
|
replied => 'replied_date',
|
|
status_alias => 'status_av',
|
|
updated => 'timestamp',
|
|
};
|
|
}
|
|
|
|
=head3 strings_map
|
|
|
|
my $strings = $self->string_map({ [ public => 0|1 ] });
|
|
|
|
Returns a map of column name to string representations. Extra information
|
|
is returned depending on the column characteristics as shown below.
|
|
|
|
Accepts a param hashref where the I<public> key denotes whether we want the public
|
|
or staff client strings.
|
|
|
|
Example:
|
|
|
|
{
|
|
status => {
|
|
backend => 'backendName',
|
|
str => 'Status description',
|
|
type => 'ill_status',
|
|
},
|
|
status_alias => {
|
|
category => 'ILL_STATUS_ALIAS,
|
|
str => $value, # the AV description, depending on $params->{public}
|
|
type => 'av',
|
|
}
|
|
}
|
|
|
|
=cut
|
|
|
|
sub strings_map {
|
|
my ( $self, $params ) = @_;
|
|
|
|
my $cache = Koha::Cache::Memory::Lite->get_instance();
|
|
my $cache_key = 'ill:status_graph:' . $self->backend;
|
|
|
|
my $status_graph_union = $cache->get($cache_key);
|
|
unless ($status_graph_union) {
|
|
$status_graph_union = $self->capabilities;
|
|
$cache->set( $cache_key, $status_graph_union );
|
|
}
|
|
|
|
my $status_string =
|
|
( $self->status
|
|
&& exists $status_graph_union->{ $self->status }
|
|
&& defined $status_graph_union->{ $self->status }->{name} )
|
|
? $status_graph_union->{ $self->status }->{name}
|
|
: $self->status;
|
|
|
|
my $status_code =
|
|
( $self->status
|
|
&& exists $status_graph_union->{ $self->status }
|
|
&& defined $status_graph_union->{ $self->status }->{id} )
|
|
? $status_graph_union->{ $self->status }->{id}
|
|
: $self->status;
|
|
|
|
my $strings = {
|
|
status => {
|
|
backend => $self->backend, # the backend identifier
|
|
str => $status_string, # the status description, taken from the status graph
|
|
code => $status_code, # the status id, taken from the status graph
|
|
type => 'ill_status', # fixed type
|
|
}
|
|
};
|
|
|
|
my $status_alias = $self->statusalias;
|
|
if ($status_alias) {
|
|
$strings->{"status_alias"} = {
|
|
category => 'ILL_STATUS_ALIAS',
|
|
str => $params->{public} ? $status_alias->lib_opac : $status_alias->lib,
|
|
code => $status_alias->authorised_value,
|
|
type => 'av',
|
|
};
|
|
}
|
|
|
|
return $strings;
|
|
}
|
|
|
|
=head3 can_patron_place_ill_in_opac
|
|
|
|
my $can_patron_place_ill_in_opac = Koha::Illrequest->can_patron_place_ill_in_opac($patron);
|
|
|
|
Returns whether the given patron can place an ILL request in OPAC
|
|
|
|
=over
|
|
|
|
=item patron
|
|
|
|
Patron object
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub can_patron_place_ill_in_opac {
|
|
my ( $self, $patron ) = @_;
|
|
|
|
return 1 if C4::Context->preference('ILLOpacUnauthenticatedRequest') && !$patron;
|
|
|
|
return 0
|
|
unless $patron
|
|
&& $patron->_result->categorycode->can_place_ill_in_opac
|
|
&& !( $patron->is_expired
|
|
&& $patron->category->effective_BlockExpiredPatronOpacActions_contains('ill_request') );
|
|
return 1;
|
|
}
|
|
|
|
=head3 get_history_check_requests
|
|
|
|
$self->get_history_check_requests();
|
|
|
|
Returns a list of history check requests for this request
|
|
|
|
=cut
|
|
|
|
sub get_history_check_requests {
|
|
my ($self) = @_;
|
|
|
|
my $historycheck_attribute = $self->extended_attributes->find( { 'type' => 'historycheck_requests' } );
|
|
return unless $historycheck_attribute;
|
|
|
|
return Koha::ILL::Requests->search( { 'me.illrequest_id' => [ split qr{\|}, $historycheck_attribute->value ] } );
|
|
}
|
|
|
|
=head3 get_op_param_deprecation
|
|
|
|
my $op = $req->check_url_param_deprecation($params);
|
|
|
|
Issues a deprecation message for the given parameters, if needed.
|
|
Returns the appropriate operation based on the interface type.
|
|
|
|
=over
|
|
|
|
=item interface
|
|
|
|
The interface this is running on: 'opac' or 'intranet'
|
|
|
|
=back
|
|
|
|
=over
|
|
|
|
=item params
|
|
|
|
CGI parameters
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
sub get_op_param_deprecation {
|
|
my ( $self, $interface, $params ) = @_;
|
|
|
|
my $deprecation_message;
|
|
|
|
$deprecation_message = '"method" form param is DEPRECATED in favor of "op". ' if ( exists( $params->{'method'} ) );
|
|
|
|
if ( exists( $params->{'op'} ) && $params->{'op'} eq 'create'
|
|
|| exists( $params->{'method'} ) && $params->{'method'} eq 'create' )
|
|
{
|
|
$deprecation_message .= '"create" op is DEPRECATED in favor of "cud-create" or "add_form".';
|
|
}
|
|
|
|
$deprecation_message .= ' Used by ' . $params->{'backend'} . ' backend.'
|
|
if $params->{'backend'} && $deprecation_message;
|
|
|
|
deprecated $deprecation_message if $deprecation_message;
|
|
|
|
my $op;
|
|
if ( $interface eq 'opac' ) {
|
|
$op = $params->{'op'} // $params->{'method'} // 'list';
|
|
$op = 'cud-create' if $op eq 'create' || $op eq 'add_form';
|
|
} elsif ( $interface eq 'intranet' ) {
|
|
$op = $params->{op} // $params->{method} // 'illlist';
|
|
$op = 'cud-create' if $op eq 'create';
|
|
$op = 'cud-edit_action' if $op eq 'edit_action';
|
|
$op = 'cud-generic_confirm' if $op eq 'generic_confirm';
|
|
$op = 'cud-cancel' if $op eq 'cancel';
|
|
$op = 'cud-delete' if $op eq 'delete';
|
|
$op = 'cud-check_out' if $op eq 'check_out';
|
|
}
|
|
return $op;
|
|
}
|
|
|
|
=head3 get_staff_table_actions
|
|
|
|
my $ill_table_actions = $self->get_staff_table_actions;
|
|
|
|
Returns the table actions available in the Staff ILL list table
|
|
A total join of core static actions with custom actions provided by
|
|
installed plugins that implement the ill_table_actions hook
|
|
|
|
=cut
|
|
|
|
sub get_staff_table_actions {
|
|
my ( $self, $params ) = @_;
|
|
|
|
my $ill_table_actions = [
|
|
{
|
|
button_class => 'btn btn-default btn-sm',
|
|
button_link => '/cgi-bin/koha/ill/ill-requests.pl?op=illview&illrequest_id=',
|
|
append_column_data_to_link => 1,
|
|
button_link_translatable_text => 'ill_manage',
|
|
}
|
|
];
|
|
|
|
Koha::Plugins->call( 'ill_table_actions', \$ill_table_actions );
|
|
|
|
return $ill_table_actions;
|
|
}
|
|
|
|
=head3 _type
|
|
|
|
=cut
|
|
|
|
sub _type {
|
|
return 'Illrequest';
|
|
}
|
|
|
|
=head1 AUTHOR
|
|
|
|
Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com>
|
|
Andrew Isherwood <andrew.isherwood@ptfs-europe.com>
|
|
|
|
=cut
|
|
|
|
1;
|