Bugzilla – Attachment 67244 Details for
Bug 7317
Add an Interlibrary Loan Module to Circulation and OPAC
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 7317: ILLModule
Bug-7317-ILLModule.patch (text/plain), 172.42 KB, created by
Jonathan Druart
on 2017-09-20 15:53:40 UTC
(
hide
)
Description:
Bug 7317: ILLModule
Filename:
MIME Type:
Creator:
Jonathan Druart
Created:
2017-09-20 15:53:40 UTC
Size:
172.42 KB
patch
obsolete
>From 322ea22094ae444a077a93ffaf406cd879043f4f Mon Sep 17 00:00:00 2001 >From: Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com> >Date: Fri, 3 Feb 2017 16:58:35 +0100 >Subject: [PATCH] Bug 7317: ILLModule > >* Koha/Illrequest.pm >* Koha/Illrequest/Config.pm >* Koha/Illrequestattribute.pm >* Koha/Illrequestattributes.pm >* Koha/Illrequests.pm >* Koha/REST/V1/Illrequests.pm >* ill/ill-requests.pl >* api/v1/swagger/paths.json >* api/v1/swagger/paths/illrequests.json >* etc/koha-conf.xml >* koha-tmpl/intranet-tmpl/prog/css/staff-global.css >* koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc >* koha-tmpl/intranet-tmpl/prog/en/includes/ill-toolbar.inc >* koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc >* koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt >* koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt >* koha-tmpl/opac-tmpl/bootstrap/en/includes/usermenu.inc >* koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt >* koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results-grouped.tt >* koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt >* koha-tmpl/opac-tmpl/bootstrap/less/opac.less >* opac/opac-illrequests.pl >* t/db_dependent/Illrequest.t >* t/db_dependent/Illrequest/Config.t >* t/db_dependent/Illrequest/Dummy.t >* t/db_dependent/api/v1/illrequests.t >--- > Koha/Illrequest.pm | 855 +++++++++++++++++++++ > Koha/Illrequest/Backend/Dummy/Base.pm | 576 ++++++++++++++ > Koha/Illrequest/Config.pm | 389 ++++++++++ > Koha/Illrequestattribute.pm | 51 ++ > Koha/Illrequestattributes.pm | 55 ++ > Koha/Illrequests.pm | 99 +++ > Koha/REST/V1/Illrequests.pm | 85 ++ > api/v1/swagger/paths.json | 3 + > api/v1/swagger/paths/illrequests.json | 98 +++ > etc/koha-conf.xml | 21 + > ill/ill-requests.pl | 238 ++++++ > koha-tmpl/intranet-tmpl/prog/css/staff-global.css | 77 ++ > .../intranet-tmpl/prog/en/includes/circ-menu.inc | 3 + > .../intranet-tmpl/prog/en/includes/ill-toolbar.inc | 24 + > .../intranet-tmpl/prog/en/includes/permissions.inc | 1 + > .../prog/en/modules/ill/ill-requests.tt | 487 ++++++++++++ > .../intranet-tmpl/prog/en/modules/intranet-main.tt | 5 + > .../opac-tmpl/bootstrap/en/includes/usermenu.inc | 9 + > .../bootstrap/en/modules/opac-illrequests.tt | 219 ++++++ > .../bootstrap/en/modules/opac-results-grouped.tt | 30 +- > .../opac-tmpl/bootstrap/en/modules/opac-results.tt | 33 +- > koha-tmpl/opac-tmpl/bootstrap/less/opac.less | 38 + > opac/opac-illrequests.pl | 129 ++++ > t/db_dependent/Illrequest.t | 544 +++++++++++++ > t/db_dependent/Illrequest/Config.t | 344 +++++++++ > t/db_dependent/Illrequest/Dummy.t | 378 +++++++++ > t/db_dependent/api/v1/illrequests.t | 136 ++++ > 27 files changed, 4910 insertions(+), 17 deletions(-) > create mode 100644 Koha/Illrequest.pm > create mode 100644 Koha/Illrequest/Backend/Dummy/Base.pm > create mode 100644 Koha/Illrequest/Config.pm > create mode 100644 Koha/Illrequestattribute.pm > create mode 100644 Koha/Illrequestattributes.pm > create mode 100644 Koha/Illrequests.pm > create mode 100644 Koha/REST/V1/Illrequests.pm > create mode 100644 api/v1/swagger/paths/illrequests.json > create mode 100755 ill/ill-requests.pl > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/ill-toolbar.inc > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt > create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt > create mode 100755 opac/opac-illrequests.pl > create mode 100644 t/db_dependent/Illrequest.t > create mode 100644 t/db_dependent/Illrequest/Config.t > create mode 100644 t/db_dependent/Illrequest/Dummy.t > create mode 100644 t/db_dependent/api/v1/illrequests.t > >diff --git a/Koha/Illrequest.pm b/Koha/Illrequest.pm >new file mode 100644 >index 0000000000..d975edef12 >--- /dev/null >+++ b/Koha/Illrequest.pm >@@ -0,0 +1,855 @@ >+package Koha::Illrequest; >+ >+# Copyright PTFS Europe 2016 >+# >+# 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, write to the Free Software Foundation, Inc., 51 Franklin >+# Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+# use Modern::Perl; >+ >+use Clone 'clone'; >+use File::Basename qw/basename/; >+use Koha::Database; >+use Koha::Email; >+use Koha::Illrequestattributes; >+use Koha::Patron; >+use Mail::Sendmail; >+ >+use base qw(Koha::Object); >+ >+=head1 NAME >+ >+Koha::Illrequest - Koha Illrequest Object class >+ >+=head1 (Re)Design >+ >+An ILLRequest consists of two parts; the Illrequest Koha::Object, and a series >+of related Illrequestattributes. >+ >+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: >+ >+=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. >+ >+=over >+ >+=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 >+Illrequest using the API. >+ >+=item * cancel_fail >+ >+The interface's cancel_request method failed to cancel the Illrequest using >+the API. >+ >+=item * unavailable >+ >+The interface's request method returned saying that the desired item is not >+available for request. >+ >+=head2 Class Methods >+ >+=cut >+ >+=head3 type >+ >+=cut >+ >+sub _type { >+ return 'Illrequest'; >+} >+ >+# XXX: Method to expose DBIx accessor for illrequestattributes relationship >+sub illrequestattributes { >+ my ( $self ) = @_; >+ return Koha::Illrequestattributes->_new_from_dbic( >+ scalar $self->_result->illrequestattributes >+ ); >+} >+ >+# XXX: Method to expose DBIx accessor for borrower relationship >+sub patron { >+ my ( $self ) = @_; >+ return Koha::Patron->_new_from_dbic( >+ scalar $self->_result->borrowernumber >+ ); >+} >+ >+sub load_backend { >+ my ( $self, $backend_id ) = @_; >+ >+ my @raw = qw/Koha Illbackends/; # Base Path >+ >+ my $backend_name = $backend_id || $self->backend; >+ $location = join "/", @raw, $backend_name, "Base.pm"; # File to load >+ $backend_class = join "::", @raw, $backend_name, "Base"; # Package name >+ require $location; >+ $self->{_my_backend} = $backend_class->new({ config => $self->_config }); >+ 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 _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::Illrequest::Config->new; >+ } >+ return $self->{_my_config}; >+} >+ >+=head3 metadata >+ >+=cut >+ >+sub metadata { >+ my ( $self ) = @_; >+ return $self->_backend->metadata($self); >+} >+ >+=head3 _core_status_graph >+ >+=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' ], >+ id => 'REQ', >+ name => 'Requested', >+ ui_method_name => 'Confirm request', >+ method => 'confirm', >+ next_actions => [ 'REQREV', 'CANCREQ' ], >+ 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' ], >+ ui_method_icon => 'fa-send-o', >+ }, >+ REQREV => { >+ prev_actions => [ 'CANCREQ', 'REQ' ], >+ 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 => [ 'REQ' ], >+ id => 'CANCREQ', >+ name => 'Cancellation requested', >+ ui_method_name => 0, >+ method => 0, >+ next_actions => [ 'REQREV' ], >+ ui_method_icon => 0, >+ }, >+ COMP => { >+ prev_actions => [ 'REQ' ], >+ id => 'COMP', >+ name => 'Completed', >+ ui_method_name => 0, >+ method => 0, >+ next_actions => [ ], >+ ui_method_icon => 0, >+ }, >+ KILL => { >+ prev_actions => [ 'QUEUED', 'REQREV', 'NEW' ], >+ id => 'KILL', >+ name => 0, >+ ui_method_name => 'Delete request', >+ method => 'delete', >+ next_actions => [ ], >+ ui_method_icon => 'fa-trash', >+ }, >+ }; >+} >+ >+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} ) { >+ $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, @core_status_ids ) { >+ my @next_actions = >+ @{$status_graph->{$prev_action}->{next_actions}}; >+ push @next_actions, $backend_status_key; >+ $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, @core_status_ids ) { >+ my @prev_actions = >+ @{$status_graph->{$next_action}->{prev_actions}}; >+ push @prev_actions, $backend_status_key; >+ $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" } >+ >+=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. >+ >+=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->expandTemplate($response); >+ } >+ } >+ return 0; >+} >+ >+sub available_backends { >+ my ( $self ) = @_; >+ my $backend_dir = $self->_config->backend_dir; >+ my @backends = (); >+ my @backends = <$backend_dir/*> if ( $backend_dir ); >+ my @backends = map { basename($_) } @backends; >+ return \@backends; >+} >+ >+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; >+} >+ >+sub backend_confirm { >+ my ( $self, $params ) = @_; >+ >+ # The backend handles setting of mandatory fields in the commit stage: >+ # - orderid >+ # - accessurl, cost (if available). >+ my $response = $self->_backend->confirm({ >+ request => $self, >+ other => $params, >+ }); >+ return $self->expandTemplate($response); >+} >+ >+sub backend_update_status { >+ my ( $self, $params ) = @_; >+ return $self->expandTemplate($self->_backend->update_status($params)); >+} >+ >+=head3 backend_cancel >+ >+ my $ILLResponse = $illRequest->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->expandTemplate($result); >+} >+ >+=head3 backend_renew >+ >+ my $renew_response = $illRequest->backend_renew; >+ >+The standard interface method allowing for request renewal queries. >+ >+=cut >+ >+sub backend_renew { >+ my ( $self ) = @_; >+ return $self->expandTemplate( >+ $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 ) = @_; >+ >+ # 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->expandTemplate($result) >+ unless ( 'commit' eq $stage ); >+ >+ # ... complex case: commit! >+ >+ # Do we still have space for an ILL or should we queue? >+ my $permitted = $self->check_limits( >+ { patron => $self->patron }, { librarycode => $self->branchcode } >+ ); >+ >+ # Now augment our committed request. >+ >+ $result->{permitted} = $permitted; # Queue request? >+ >+ # This involves... >+ >+ # ...Updating status! >+ $self->status('QUEUED')->store unless ( $permitted ); >+ >+ # FIXME: Fix Unmediated ILLs! >+ # Handle Unmediated ILLs >+ # if ( C4::Context->preference("UnmediatedILL") && $permitted >+ # # XXX: Why && result manual? >+ # && $result->{manual} ) { >+ # # FIXME: Also carry out privilege checks >+ # my ( $response, $new_rq ) = $self->place_request; # WTF? >+ # if ( $response ) { >+ # $result->{value}->{request} = $new_rq; >+ # return $result; >+ # } else { >+ # die "Placing the request failed."; >+ # } >+ # } else { >+ # $result->{value}->{request} = $request; >+ # return $result; >+ # } >+ >+ return $self->expandTemplate($result); >+} >+ >+=head3 expandTemplate >+ >+ my $params = $abstract->expandTemplate($params); >+ >+Return a version of $PARAMS augmented with our required template path. >+ >+=cut >+ >+sub expandTemplate { >+ my ( $self, $params ) = @_; >+ my $backend = $self->_backend->name; >+ # Generate path to file to load >+ my $backend_dir = $self->_config->backend_dir; >+ my $backend_tmpl = join "/", $backend_dir, $backend; >+ my $intra_tmpl = join "/", $backend_tmpl, "intra-includes", >+ $params->{method} . ".inc"; >+ my $opac_tmpl = join "/", $backend_tmpl, "opac-includes", >+ $params->{method} . ".inc"; >+ # Set files to load >+ $params->{template} = $intra_tmpl; >+ $params->{opac_template} = $opac_tmpl; >+ return $params; >+} >+ >+#### Abstract Imports >+ >+=head3 getCensorNotesStaff >+ >+ my $bool = $abstract->getCensorNotesStaff; >+ >+Return a boolean indicating whether we should be censoring staff notes or not, >+as determined by our configuration file. >+ >+=cut >+ >+sub getCensorNotesStaff { >+ my ( $self ) = @_; >+ my $censorship = $self->_config->censorship; >+ return $censorship->{censor_notes_staff}; >+} >+ >+=head3 getDisplayReplyDate >+ >+ my 0 = $abstract->getDisplayReplyDate; >+ >+Return a 0 if we want to hide it or 1 if not. >+ >+=cut >+ >+sub getDisplayReplyDate { >+ my ( $self ) = @_; >+ my $censorship = $self->_config->censorship; >+ # If censor is yes, don't display and vice versa. >+ return ( $censorship->{censor_reply_date} ) ? 0 : 1; >+} >+ >+=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 >+ >+# FIXME: Needs unit tests! >+sub getLimits { >+ my ( $self, $params ) = @_; >+ my $limits = $self->_config->getLimitRules($params->{type}); >+ >+ return $limits->{$params->{value}} >+ || $limits->{default} >+ || { count => -1, method => 'active' }; >+} >+ >+=head3 getPrefix >+ >+ my $prefix = $abstract->getPrefix( { >+ brw_cat => $brw_cat, >+ 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('branch'); >+ my $brw_prefixes = $self->_config->getPrefixes('brw_cat'); >+ >+ return $brw_prefixes->{$params->{brw_cat}} >+ || $brn_prefixes->{$params->{branch}} >+ || $brw_prefixes->{default} >+ || ""; # "the empty prefix" >+} >+ >+#### Illrequests Imports >+ >+=head3 check_limits >+ >+ my $ok = $illRequests->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 >+ >+# FIXME: Needs unit tests! >+sub check_limits { >+ my ( $self, $params ) = @_; >+ my $patron = $params->{patron}; >+ my $branchcode = $params->{librarycode} || $patron->branchcode; >+ >+ # Establish rules >+ my ( $branch_rules, $brw_rules ) = ( >+ $self->getLimits( { >+ type => 'branch', >+ value => $branchcode >+ } ), >+ $self->getLimits( { >+ type => 'brw_cat', >+ value => $patron->categorycode, >+ } ), >+ ); >+ # Almost there, but category code didn't quite work. >+ my ( $branch_limit, $brw_limit ) >+ = ( $branch_rules->{count}, $brw_rules->{count} ); >+ my ( $branch_count, $brw_count ) = ( >+ $self->_limit_counter( >+ $branch_rules->{method}, { branch_id => $branchcode } >+ ), >+ $self->_limit_counter( >+ $brw_rules->{method}, { borrower_id => $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; >+ } >+} >+ >+# FIXME: Needs unit tests! >+sub _limit_counter { >+ my ( $self, $method, $target ) = @_; >+ >+ # Establish parameters of counts >+ my $where; >+ if ($method && $method eq 'annual') { >+ $where = \"YEAR(placement_date) = YEAR(NOW())"; >+ } else { # assume 'active' >+ # FIXME: This status list is ugly. There should be a method in config >+ # to return these. >+ $where = { status => { -not_in => [ 'QUEUED', 'COMP' ] } }; >+ } >+ >+ # Create resultset >+ my $resultset = Koha::Illrequests->search({ %{$target}, %{$where} }); >+ >+ # Fetch counts >+ return $resultset->count; >+} >+ >+=head3 requires_moderation >+ >+ my $status = $illRequest->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 generic_confirm >+ >+ my $stage_summary = $illRequest->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 ) = @_; >+ if ( !$params->{stage}|| $params->{stage} eq 'init' ) { >+ my $draft->{subject} = "ILL Request"; >+ $draft->{body} = <<EOF; >+Dear Sir/Madam, >+ >+ We would like to request an interlibrary loan for a title matching the >+following description: >+ >+EOF >+ >+ my $details = $self->metadata; >+ while (my ($title, $value) = each %{$details}) { >+ $draft->{body} .= " - " . $title . ": " . $value . "\n" >+ if $value; >+ } >+ $draft->{body} .= <<EOF; >+ >+Please let us know if you are able to supply this to us. >+ >+Kind Regards >+EOF >+ >+ my $partners = Koha::Patrons->search({ >+ categorycode => $self->_config->partner_code >+ }); >+ return { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'generic_confirm', >+ stage => 'draft', >+ value => { >+ draft => $draft, >+ partners => $partners, >+ } >+ }; >+ >+ } elsif ( 'draft' eq $params->{stage} ) { >+ # Create the to header >+ my $to = $params->{partners}; >+ $to =~ s/^\x00//; # Strip leading NULLs >+ $to =~ s/\x00/; /; # Replace others with '; ' >+ die "No target email addresses found. Either select at least one partner or check your ILL partner library records." if ( !$to ); >+ # Create the from, replyto and sender headers >+ $branch = Koha::Libraries->find($params->{current_branchcode}) >+ || die "Invalid current branchcode. Are you logged in as the database user?"; >+ my $from = $branch->branchemail; >+ my $replyto = $branch->branchreplyto || $from; >+ die "Your branch has no email address. Please set it." >+ if ( !$from ); >+ # Create the email >+ my $message = Koha::Email->new; >+ my %mail = $message->create_message_headers( >+ { >+ to => $to, >+ from => $from, >+ replyto => $replyto, >+ subject => Encode::encode( "utf8", $params->{subject} ), >+ message => Encode::encode( "utf8", $params->{body} ), >+ contenttype => 'text/plain', >+ } >+ ); >+ # Send it >+ my $result = sendmail(%mail); >+ if ( $result ) { >+ $self->status("GENREQ")->store; >+ return { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'generic_confirm', >+ stage => 'commit', >+ next => 'illview', >+ }; >+ } else { >+ return { >+ error => 1, >+ status => 'email_failed', >+ message => $Mail::Sendmail::error, >+ method => 'generic_confirm', >+ stage => 'draft', >+ }; >+ } >+ } else { >+ die "Unknown stage, should not have happened." >+ } >+} >+ >+=head3 id_prefix >+ >+ my $prefix = $record->id_prefix; >+ >+Return the prefix appropriate for the current Illrequest as derived from the >+borrower and branch associated with this request's Status, and the config >+file. >+ >+=cut >+ >+sub id_prefix { >+ my ( $self ) = @_; >+ # FIXME: can we automatically use borrowernumber as borrower? >+ my $brw = $self->patron; >+ my $brw_cat = "dummy"; >+ $brw_cat = $brw->categorycode >+ unless ( 'HASH' eq ref($brw) && $brw->{deleted} ); >+ my $prefix = $self->getPrefix( { >+ brw_cat => $brw_cat, >+ branch => $self->branchcode, >+ } ); >+ $prefix .= "-" if ( $prefix ); >+ return $prefix; >+} >+ >+=head3 _censor >+ >+ my $params = $illRequest->_censor($params); >+ >+Return $params, modified to reflect our censorship requirements. >+ >+=cut >+ >+sub _censor { >+ my ( $self, $params ) = @_; >+ $params->{censor_notes_staff} = $self->getCensorNotesStaff >+ if ( $params->{opac} ); >+ $params->{display_reply_date} = $self->getDisplayReplyDate; >+ >+ return $params; >+} >+ >+=head1 AUTHOR >+ >+Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com> >+ >+=cut >+ >+1; >diff --git a/Koha/Illrequest/Backend/Dummy/Base.pm b/Koha/Illrequest/Backend/Dummy/Base.pm >new file mode 100644 >index 0000000000..b832bf8de1 >--- /dev/null >+++ b/Koha/Illrequest/Backend/Dummy/Base.pm >@@ -0,0 +1,576 @@ >+package Koha::Illrequest::Backend::Dummy::Base; >+ >+# Copyright PTFS Europe 2014 >+# >+# 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, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use Modern::Perl; >+use DateTime; >+use Koha::Illrequestattribute; >+ >+=head1 NAME >+ >+Koha::Illrequest::Backend::Dummy - Koha ILL Backend: Dummy >+ >+=head1 SYNOPSIS >+ >+Koha ILL implementation for the "Dummy" backend. >+ >+=head1 DESCRIPTION >+ >+=head2 Overview >+ >+We will be providing the Abstract interface which requires we implement the >+following methods: >+- create -> initial placement of the request for an ILL order >+- confirm -> confirm placement of the ILL order >+- list -> list all ILL Requests currently placed with the backend >+- renew -> request a currently borrowed ILL be renewed in the backend >+- update_status -> ILL module update hook: custom actions on status update >+- cancel -> request an already 'confirm'ed ILL order be cancelled >+- status -> request the current status of a confirmed ILL order >+ >+Each of the above methods will receive the following parameter from >+Illrequest.pm: >+ >+ { >+ request => $request, >+ other => $other, >+ } >+ >+where: >+ >+- $REQUEST is the Illrequest object in Koha. It's associated >+ Illrequestattributes can be accessed through the `illrequestattributes` >+ method. >+- $OTHER is any further data, generally provided through templates .INCs >+ >+Each of the above methods should return a hashref of the following format: >+ >+ return { >+ error => 0, >+ # ^------- 0|1 to indicate an error >+ status => 'result_code', >+ # ^------- Summary of the result of the operation >+ message => 'Human readable message.', >+ # ^------- Message, possibly to be displayed >+ # Normally messages are derived from status in INCLUDE. >+ # But can be used to pass API messages to the INCLUDE. >+ method => 'list', >+ # ^------- Name of the current method invoked. >+ # Used to load the appropriate INCLUDE. >+ stage => 'commit', >+ # ^------- The current stage of this method >+ # Used by INCLUDE to determine HTML to generate. >+ # 'commit' will result in final processing by Illrequest.pm. >+ next => 'illview'|'illlist', >+ # ^------- When stage is 'commit', should we move on to ILLVIEW the >+ # current request or ILLLIST all requests. >+ value => {}, >+ # ^------- A hashref containing an arbitrary return value that this >+ # backend wants to supply to its INCLUDE. >+ }; >+ >+=head2 On the Dummy backend >+ >+The Dummy backend is rather simple, but provides correctly formatted response >+values, that other backends can model themselves after. >+ >+The code is not DRY -- primarily so that each method can be looked at in >+isolation rather than having to familiarise oneself with helper procedures. >+ >+=head1 API >+ >+=head2 Class Methods >+ >+=cut >+ >+=head3 new >+ >+ my $backend = Koha::Illrequest::Backend::Dummy->new; >+ >+=cut >+ >+sub new { >+ # -> instantiate the backend >+ my ( $class ) = @_; >+ my $self = {}; >+ bless( $self, $class ); >+ return $self; >+} >+ >+=head3 _data_store >+ >+ my $request = $self->_data_store($id); >+ my $requests = $self->_data_store; >+ >+A mock of a data store. When passed no parameters it returns all entries. >+When passed one it will return the entry matched by its id. >+ >+=cut >+ >+sub _data_store { >+ my $data = { >+ 1234 => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ }, >+ 5678 => { >+ id => 5678, >+ title => "Interlibrary loans in Koha", >+ author => "A.N. Other", >+ }, >+ }; >+ # ID search >+ my ( $self, $id ) = @_; >+ return $data->{$id} if $id; >+ >+ # Full search >+ my @entries; >+ while ( my ( $k, $v ) = each %{$data} ) { >+ push @entries, $v; >+ } >+ return \@entries; >+} >+ >+=head3 create >+ >+ my $response = $backend->create({ >+ request => $requestdetails, >+ other => $other, >+ }); >+ >+This is the initial creation of the request. Generally this stage will be >+some form of search with the backend. >+ >+By and large we will not have useful $requestdetails (borrowernumber, >+branchcode, status, etc.). >+ >+$params is simply an additional slot for any further arbitrary values to pass >+to the backend. >+ >+This is an example of a multi-stage method. >+ >+=cut >+ >+sub create { >+ # -> initial placement of the request for an ILL order >+ my ( $self, $params ) = @_; >+ my $stage = $params->{other}->{stage}; >+ if ( !$stage || $stage eq 'init' ) { >+ # We simply need our template .INC to produce a search form. >+ return { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'create', >+ stage => 'search_form', >+ value => {}, >+ }; >+ } elsif ( $stage eq 'search_form' ) { >+ # Received search query in 'other'; perform search... >+ >+ # No-op on Dummy >+ >+ # and return results. >+ return { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'create', >+ stage => 'search_results', >+ value => { >+ borrowernumber => $params->{other}->{borrowernumber}, >+ branchcode => $params->{other}->{branchcode}, >+ medium => $params->{other}->{medium}, >+ candidates => $self->_data_store, >+ } >+ }; >+ } elsif ( $stage eq 'search_results' ) { >+ # We have a selection >+ my $id = $params->{other}->{id}; >+ >+ # -> select from backend... >+ my $request_details = $self->_data_store($id); >+ >+ # ...Populate Illrequest >+ my $request = $params->{request}; >+ $request->borrower_id($params->{other}->{borrowernumber}); >+ $request->branch_id($params->{other}->{branchcode}); >+ $request->medium($params->{other}->{medium}); >+ $request->status('NEW'); >+ $request->placed(DateTime->now); >+ $request->updated(DateTime->now); >+ $request->store; >+ # ...Populate Illrequestattributes >+ while ( my ( $type, $value ) = each %{$request_details} ) { >+ Koha::Illrequestattribute->new({ >+ illrequest_id => $request->illrequest_id, >+ type => $type, >+ value => $value, >+ })->store; >+ } >+ >+ # -> create response. >+ return { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'create', >+ stage => 'commit', >+ next => 'illview', >+ value => $request_details, >+ }; >+ } else { >+ # Invalid stage, return error. >+ return { >+ error => 1, >+ status => 'unknown_stage', >+ message => '', >+ method => 'create', >+ stage => $params->{stage}, >+ value => {}, >+ }; >+ } >+} >+ >+=head3 confirm >+ >+ my $response = $backend->confirm({ >+ request => $requestdetails, >+ other => $other, >+ }); >+ >+Confirm the placement of the previously "selected" request (by using the >+'create' method). >+ >+In this case we will generally use $request. >+This will be supplied at all times through Illrequest. $other may be supplied >+using templates. >+ >+=cut >+ >+sub confirm { >+ # -> confirm placement of the ILL order >+ my ( $self, $params ) = @_; >+ # Turn Illrequestattributes into a plain hashref >+ my $value = {}; >+ my $attributes = $params->{request}->illrequestattributes; >+ foreach my $attr (@{$attributes->as_list}) { >+ $value->{$attr->type} = $attr->value; >+ }; >+ # Submit request to backend... >+ >+ # No-op for Dummy >+ >+ # ...parse response... >+ $attributes->find_or_create({ type => "status", value => "On order" }); >+ my $request = $params->{request}; >+ $request->cost("30 GBP"); >+ $request->orderid($value->{id}); >+ $request->status("REQ"); >+ $request->accessurl("URL") if $value->{url}; >+ $request->store; >+ $value->{status} = "On order"; >+ $value->{cost} = "30 GBP"; >+ # ...then return our result: >+ return { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'confirm', >+ stage => 'commit', >+ next => 'illview', >+ value => $value, >+ }; >+} >+ >+=head3 list >+ >+ my $response = $backend->list({ >+ request => $requestdetails, >+ other => $other, >+ }; >+ >+Attempt to get a list of the currently registered requests with the backend. >+ >+Parameters are optional for this request. A backend may be supplied with >+details of a specific request (or a group of requests in $other), but equally >+no parameters might be provided at all. >+ >+Normally no parameters will be provided in the 'create' stage. After this, >+parameters may be provided using templates. >+ >+=cut >+ >+sub list { >+ # -> list all ILL Requests currently placed with the backend >+ # (we ignore all params provided) >+ my ( $self, $params ) = @_; >+ my $stage = $params->{other}->{stage}; >+ if ( !$stage || $stage eq 'init' ) { >+ return { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'list', >+ stage => 'list', >+ value => { >+ 1 => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ status => "On order", >+ cost => "30 GBP", >+ }, >+ }, >+ }; >+ } elsif ( $stage eq 'list' ) { >+ return { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'list', >+ stage => 'commit', >+ value => {}, >+ }; >+ } else { >+ # Invalid stage, return error. >+ return { >+ error => 1, >+ status => 'unknown_stage', >+ message => '', >+ method => 'create', >+ stage => $params->{stage}, >+ value => {}, >+ }; >+ } >+} >+ >+=head3 renew >+ >+ my $response = $backend->renew({ >+ request => $requestdetails, >+ other => $other, >+ }); >+ >+Attempt to renew a request that was supplied through backend and is currently >+in use by us. >+ >+We will generally use $request. This will be supplied at all times through >+Illrequest. $other may be supplied using templates. >+ >+=cut >+ >+sub renew { >+ # -> request a currently borrowed ILL be renewed in the backend >+ my ( $self, $params ) = @_; >+ # Turn Illrequestattributes into a plain hashref >+ my $value = {}; >+ my $attributes = $params->{request}->illrequestattributes; >+ foreach my $attr (@{$attributes->as_list}) { >+ $value->{$attr->type} = $attr->value; >+ }; >+ # Submit request to backend, parse response... >+ my ( $error, $status, $message ) = ( 0, '', '' ); >+ if ( !$value->{status} || $value->{status} eq 'On order' ) { >+ $error = 1; >+ $status = 'not_renewed'; >+ $message = 'Order not yet delivered.'; >+ } else { >+ $value->{status} = "Renewed"; >+ } >+ # ...then return our result: >+ return { >+ error => $error, >+ status => $status, >+ message => $message, >+ method => 'renew', >+ stage => 'commit', >+ value => $value, >+ }; >+} >+ >+=head3 update_status >+ >+ my $response = $backend->update_status({ >+ request => $requestdetails, >+ other => $other, >+ }); >+ >+Our Illmodule is handling a request to update the status of an Illrequest. As >+part of this we give the backend an opportunity to perform arbitrary actions >+on update to a new status. >+ >+We will provide $request. This will be supplied at all times through >+Illrequest. $other will contain entries for the old status and the new >+status, as well as other information provided from templates. >+ >+$old_status, $new_status. >+ >+=cut >+ >+sub update_status { >+ # -> ILL module update hook: custom actions on status update >+ my ( $self, $params ) = @_; >+ # Turn Illrequestattributes into a plain hashref >+ my $value = {}; >+ my $attributes = $params->{request}->illrequestattributes; >+ foreach my $attr (@{$attributes->as_list}) { >+ $value->{$attr->type} = $attr->value; >+ }; >+ # Submit request to backend, parse response... >+ my ( $error, $status, $message ) = (0, '', ''); >+ my $old = $params->{other}->{old_status}; >+ my $new = $params->{other}->{new_status}; >+ if ( !$new || $new eq 'ERR' ) { >+ ( $error, $status, $message ) = ( >+ 1, 'failed_update_hook', >+ 'Fake reason for failing to perform update operation.' >+ ); >+ } >+ return { >+ error => $error, >+ status => $status, >+ message => $message, >+ method => 'update_status', >+ stage => 'commit', >+ value => $value, >+ }; >+} >+ >+=head3 cancel >+ >+ my $response = $backend->cancel({ >+ request => $requestdetails, >+ other => $other, >+ }); >+ >+We will attempt to cancel a request that was confirmed. >+ >+We will generally use $request. This will be supplied at all times through >+Illrequest. $other may be supplied using templates. >+ >+=cut >+ >+sub cancel { >+ # -> request an already 'confirm'ed ILL order be cancelled >+ my ( $self, $params ) = @_; >+ # Turn Illrequestattributes into a plain hashref >+ my $value = {}; >+ my $attributes = $params->{request}->illrequestattributes; >+ foreach my $attr (@{$attributes->as_list}) { >+ $value->{$attr->type} = $attr->value; >+ }; >+ # Submit request to backend, parse response... >+ my ( $error, $status, $message ) = (0, '', ''); >+ if ( !$value->{status} ) { >+ ( $error, $status, $message ) = ( >+ 1, 'unknown_request', 'Cannot cancel an unknown request.' >+ ); >+ } else { >+ $attributes->find({ type => "status" })->delete; >+ $params->{request}->status("REQREV"); >+ $params->{request}->cost(undef); >+ $params->{request}->orderid(undef); >+ $params->{request}->store; >+ } >+ return { >+ error => $error, >+ status => $status, >+ message => $message, >+ method => 'cancel', >+ stage => 'commit', >+ value => $value, >+ }; >+} >+ >+=head3 status >+ >+ my $response = $backend->create({ >+ request => $requestdetails, >+ other => $other, >+ }); >+ >+We will try to retrieve the status of a specific request. >+ >+We will generally use $request. This will be supplied at all times through >+Illrequest. $other may be supplied using templates. >+ >+=cut >+ >+sub status { >+ # -> request the current status of a confirmed ILL order >+ my ( $self, $params ) = @_; >+ my $value = {}; >+ my $stage = $params->{other}->{stage}; >+ my ( $error, $status, $message ) = (0, '', ''); >+ if ( !$stage || $stage eq 'init' ) { >+ # Generate status result >+ # Turn Illrequestattributes into a plain hashref >+ my $attributes = $params->{request}->illrequestattributes; >+ foreach my $attr (@{$attributes->as_list}) { >+ $value->{$attr->type} = $attr->value; >+ } >+ ; >+ # Submit request to backend, parse response... >+ if ( !$value->{status} ) { >+ ( $error, $status, $message ) = ( >+ 1, 'unknown_request', 'Cannot query status of an unknown request.' >+ ); >+ } >+ return { >+ error => $error, >+ status => $status, >+ message => $message, >+ method => 'status', >+ stage => 'status', >+ value => $value, >+ }; >+ >+ } elsif ( $stage eq 'status') { >+ # No more to do for method. Return to illlist. >+ return { >+ error => $error, >+ status => $status, >+ message => $message, >+ method => 'status', >+ stage => 'commit', >+ next => 'illlist', >+ value => {}, >+ }; >+ >+ } else { >+ # Invalid stage, return error. >+ return { >+ error => 1, >+ status => 'unknown_stage', >+ message => '', >+ method => 'create', >+ stage => $params->{stage}, >+ value => {}, >+ }; >+ } >+} >+ >+=head1 AUTHOR >+ >+Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com> >+ >+=cut >+ >+1; >diff --git a/Koha/Illrequest/Config.pm b/Koha/Illrequest/Config.pm >new file mode 100644 >index 0000000000..159e5e5429 >--- /dev/null >+++ b/Koha/Illrequest/Config.pm >@@ -0,0 +1,389 @@ >+package Koha::Illrequest::Config; >+ >+# Copyright 2013,2014 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, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use Modern::Perl; >+use C4::Context; >+ >+=head1 NAME >+ >+Koha::Illrequest::Config - Koha ILL Configuration Object >+ >+=head1 SYNOPSIS >+ >+Object-oriented class that giving access to the illconfig data derived >+from ill/config.yaml. >+ >+=head1 DESCRIPTION >+ >+Config object providing abstract representation of the expected XML >+returned by ILL API. >+ >+In particular the config object uses a YAML file, whose path is >+defined by <illconfig> in koha-conf.xml. That YAML file provides the >+data structure exposed in this object. >+ >+By default the configured data structure complies with fields used by >+the British Library Interlibrary Loan DSS API. >+ >+The config file also provides mappings for Record Object accessors. >+ >+FIXME: illcomm: In general Config should be split into two parts: >+those that are high-level and those that are backend specific. The >+latter should not get specific accessors, but rather a generic >+accessor, that takes a setting name as string parameter. >+ >+=head1 API >+ >+=head2 Class Methods >+ >+=head3 new >+ >+ my $config = Koha::Illrequest::Config->new(); >+ >+Create a new Koha::Illrequest::Config object, with mapping data loaded from the >+ILL configuration file. >+ >+=cut >+ >+sub new { >+ my ( $class, $test ) = @_; >+ my $self = {}; >+ >+ $self->{configuration} = _load_configuration( >+ C4::Context->config("interlibrary_loans"), >+ C4::Context->preference("UnmediatedILL") >+ ) unless ( $test ); >+ >+ bless $self, $class; >+ >+ return $self; >+} >+ >+=head3 backend >+ >+ $backend = $config->backend($name); >+ $backend = $config->backend; >+ >+Standard setter/accessor for our backend. >+ >+=cut >+ >+sub backend { >+ my ( $self, $new ) = @_; >+ $self->{configuration}->{backend} = $new if $new; >+ return $self->{configuration}->{backend}; >+} >+ >+=head3 backend_dir >+ >+ $backend_dir = $config->backend_dir($new_path); >+ $backend_dir = $config->backend_dir; >+ >+Standard setter/accessor for our backend_directory. >+ >+=cut >+ >+sub backend_dir { >+ my ( $self, $new ) = @_; >+ $self->{configuration}->{backend_directory} = $new if $new; >+ return $self->{configuration}->{backend_directory}; >+} >+ >+=head3 partner_code >+ >+ $partner_code = $config->partner_code($new_path); >+ $partner_code = $config->partner_code; >+ >+Standard setter/accessor for our partner_code. >+ >+=cut >+ >+sub partner_code { >+ my ( $self, $new ) = @_; >+ $self->{configuration}->{partner_code} = $new if $new; >+ return $self->{configuration}->{partner_code}; >+} >+ >+=head3 limits >+ >+ $limits = $config->limits($limitshash); >+ $limits = $config->limits; >+ >+Standard setter/accessor for our limits. No parsing is performed on >+$LIMITSHASH, so caution should be exercised when using this setter. >+ >+=cut >+ >+sub limits { >+ my ( $self, $new ) = @_; >+ $self->{configuration}->{limits} = $new if $new; >+ return $self->{configuration}->{limits}; >+} >+ >+=head3 getPrefixes >+ >+ my $prefixes = $config->getPrefixes('brw_cat' | 'branch'); >+ >+Return the prefix for ILLs defined by our config. >+ >+=cut >+ >+sub getPrefixes { >+ my ( $self, $type ) = @_; >+ die "Unexpected type." unless ( $type eq 'brw_cat' || $type eq 'branch' ); >+ my $values = $self->{configuration}->{prefixes}->{$type}; >+ $values->{default} = $self->{configuration}->{prefixes}->{default}; >+ return $values; >+} >+ >+=head3 getLimitRules >+ >+ my $rules = $config->getLimitRules('brw_cat' | 'branch') >+ >+Return the hash of ILL limit rules defined by our config. >+ >+=cut >+ >+sub getLimitRules { >+ my ( $self, $type ) = @_; >+ die "Unexpected type." unless ( $type eq 'brw_cat' || $type eq 'branch' ); >+ my $values = $self->{configuration}->{limits}->{$type}; >+ $values->{default} = $self->{configuration}->{limits}->{default}; >+ return $values; >+} >+ >+=head3 getDigitalRecipients >+ >+ my $recipient_rules= $config->getDigitalRecipients('brw_cat' | 'branch'); >+ >+Return the hash of digital_recipient settings defined by our config. >+ >+=cut >+ >+sub getDigitalRecipients { >+ my ( $self, $type ) = @_; >+ die "Unexpected type." unless ( $type eq 'brw_cat' || $type eq 'branch' ); >+ my $values = $self->{configuration}->{digital_recipients}->{$type}; >+ $values->{default} = >+ $self->{configuration}->{digital_recipients}->{default}; >+ return $values; >+} >+ >+=head3 censorship >+ >+ my $censoredValues = $config->censorship($hash); >+ my $censoredValues = $config->censorship; >+ >+Standard setter/accessor for our limits. No parsing is performed on $HASH, so >+caution should be exercised when using this setter. >+ >+Return our censorship values for the OPAC as loaded from the koha-conf.xml, or >+the fallback value (no censorship). >+ >+=cut >+ >+sub censorship { >+ my ( $self, $new ) = @_; >+ $self->{configuration}->{censorship} = $new if $new; >+ return $self->{configuration}->{censorship}; >+} >+ >+=head3 _load_configuration >+ >+ my $configuration = $config->_load_configuration($config_from_xml); >+ >+Read the configuration values passed as the parameter, and populate a hashref >+suitable for use with these. >+ >+A key task performed here is the parsing of the input in the configuration >+file to ensure we have only valid input there. >+ >+=cut >+ >+sub _load_configuration { >+ my ( $xml_config, $unmediated ) = @_; >+ my $xml_backend_dir = $xml_config->{backend_directory}; >+ >+ # Default data structure to be returned >+ my $configuration = { >+ backend_directory => $xml_backend_dir, >+ censorship => { >+ censor_notes_staff => 0, >+ censor_reply_date => 0, >+ }, >+ limits => {}, >+ digital_recipients => {}, >+ prefixes => {}, >+ partner_code => 'ILLLIBS', >+ }; >+ >+ # Per Branch Configuration >+ my $branches = $xml_config->{branch}; >+ if ( ref($branches) eq "ARRAY" ) { >+ # Multiple branch overrides defined >+ map { >+ _load_unit_config({ >+ unit => $_, >+ id => $_->{code}, >+ config => $configuration, >+ type => 'branch' >+ }) >+ } @{$branches}; >+ } elsif ( ref($branches) eq "HASH" ) { >+ # Single branch override defined >+ _load_unit_config({ >+ unit => $branches, >+ id => $branches->{code}, >+ config => $configuration, >+ type => 'branch' >+ }); >+ } >+ >+ # Per Borrower Category Configuration >+ my $brw_cats = $xml_config->{borrower_category}; >+ if ( ref($brw_cats) eq "ARRAY" ) { >+ # Multiple borrower category overrides defined >+ map { >+ _load_unit_config({ >+ unit => $_, >+ id => $_->{code}, >+ config => $configuration, >+ type => 'brw_cat' >+ }) >+ } @{$brw_cats}; >+ } elsif ( ref($brw_cats) eq "HASH" ) { >+ # Single branch override defined >+ _load_unit_config({ >+ unit => $brw_cats, >+ id => $brw_cats->{code}, >+ config => $configuration, >+ type => 'brw_cat' >+ }); >+ } >+ >+ # Default Configuration >+ _load_unit_config({ >+ unit => $xml_config, >+ id => 'default', >+ config => $configuration >+ }); >+ >+ # Censorship >+ my $staff_comments = $xml_config->{staff_request_comments}; >+ $configuration->{censorship}->{censor_notes_staff} = 1 >+ if ( $staff_comments && 'hide' eq $staff_comments ); >+ my $reply_date = $xml_config->{reply_date}; >+ if ( 'hide' eq $reply_date ) { >+ $configuration->{censorship}->{censor_reply_date} = 1; >+ } else { >+ $configuration->{censorship}->{censor_reply_date} = $reply_date; >+ } >+ >+ # ILL Partners >+ $configuration->{partner_code} = $xml_config->{partner_code} || 'ILLLIBS'; >+ >+ die "No DEFAULT_FORMATS has been defined in koha-conf.xml, but UNMEDIATEDILL is active." >+ if ( $unmediated && !$configuration->{default_formats}->{default} ); >+ >+ return $configuration; >+} >+ >+=head3 _load_unit_config >+ >+ my $configuration->{part} = _load_unit_config($params); >+ >+$PARAMS is a hashref with the following elements: >+- unit: the part of the configuration we are parsing. >+- id: the name within which we will store the parsed unit in config. >+- config: the configuration we are augmenting. >+- type: the type config unit we are parsing. Assumed to be 'default'. >+ >+Read `unit', and augment `config' with these under `id'. >+ >+This is a helper for _load_configuration. >+ >+A key task performed here is the parsing of the input in the configuration >+file to ensure we have only valid input there. >+ >+=cut >+ >+sub _load_unit_config { >+ my ( $params ) = @_; >+ my $unit = $params->{unit}; >+ my $id = $params->{id}; >+ my $config = $params->{config}; >+ my $type = $params->{type}; >+ return $config unless $id; >+ >+ if ( $unit->{api_key} && $unit->{api_auth} ) { >+ $config->{credentials}->{api_keys}->{$id} = { >+ api_key => $unit->{api_key}, >+ api_auth => $unit->{api_auth}, >+ }; >+ } >+ # Add request_limit rules. >+ # METHOD := 'annual' || 'active' >+ # COUNT := x >= -1 >+ if ( ref $unit->{request_limit} eq 'HASH' ) { >+ my $method = $unit->{request_limit}->{method}; >+ my $count = $unit->{request_limit}->{count}; >+ if ( 'default' eq $id ) { >+ $config->{limits}->{$id}->{method} = $method >+ if ( $method && ( 'annual' eq $method || 'active' eq $method ) ); >+ $config->{limits}->{$id}->{count} = $count >+ if ( $count && ( -1 <= $count ) ); >+ } else { >+ $config->{limits}->{$type}->{$id}->{method} = $method >+ if ( $method && ( 'annual' eq $method || 'active' eq $method ) ); >+ $config->{limits}->{$type}->{$id}->{count} = $count >+ if ( $count && ( -1 <= $count ) ); >+ } >+ } >+ >+ # Add prefix rules. >+ # PREFIX := string >+ if ( $unit->{prefix} ) { >+ if ( 'default' eq $id ) { >+ $config->{prefixes}->{$id} = $unit->{prefix}; >+ } else { >+ $config->{prefixes}->{$type}->{$id} = $unit->{prefix}; >+ } >+ } >+ >+ # Add digital_recipient rules. >+ # DIGITAL_RECIPIENT := borrower || branch (defaults to borrower) >+ if ( $unit->{digital_recipient} ) { >+ if ( 'default' eq $id ) { >+ $config->{digital_recipients}->{$id} = $unit->{digital_recipient}; >+ } else { >+ $config->{digital_recipients}->{$type}->{$id} = >+ $unit->{digital_recipient}; >+ } >+ } >+ >+ return $config; >+} >+ >+=head1 AUTHOR >+ >+Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com> >+ >+=cut >+ >+1; >diff --git a/Koha/Illrequestattribute.pm b/Koha/Illrequestattribute.pm >new file mode 100644 >index 0000000000..16bc0867a5 >--- /dev/null >+++ b/Koha/Illrequestattribute.pm >@@ -0,0 +1,51 @@ >+package Koha::Illrequestattribute; >+ >+# Copyright PTFS Europe 2016 >+# >+# 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, write to the Free Software Foundation, Inc., 51 Franklin >+# Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use Modern::Perl; >+ >+use Koha::Database; >+ >+use base qw(Koha::Object); >+ >+=head1 NAME >+ >+Koha::Illrequestattribute - Koha Illrequestattribute Object class >+ >+=head1 API >+ >+=head2 Class Methods >+ >+=cut >+ >+=head3 type >+ >+=cut >+ >+sub _type { >+ return 'Illrequestattribute'; >+} >+ >+=head1 AUTHOR >+ >+Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com> >+ >+=cut >+ >+1; >diff --git a/Koha/Illrequestattributes.pm b/Koha/Illrequestattributes.pm >new file mode 100644 >index 0000000000..e05fa6e478 >--- /dev/null >+++ b/Koha/Illrequestattributes.pm >@@ -0,0 +1,55 @@ >+package Koha::Illrequestattributes; >+ >+# Copyright PTFS Europe 2016 >+# >+# 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, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use Modern::Perl; >+ >+use Koha::Database; >+use Koha::Illrequestattribute; >+ >+use base qw(Koha::Objects); >+ >+=head1 NAME >+ >+Koha::Illrequestattributes - Koha Illrequestattributes Object class >+ >+=head1 API >+ >+=head2 Class Methods >+ >+=cut >+ >+=head3 type >+ >+=cut >+ >+sub _type { >+ return 'Illrequestattribute'; >+} >+ >+sub object_class { >+ return 'Koha::Illrequestattribute'; >+} >+ >+=head1 AUTHOR >+ >+Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com> >+ >+=cut >+ >+1; >diff --git a/Koha/Illrequests.pm b/Koha/Illrequests.pm >new file mode 100644 >index 0000000000..4bfd865cf3 >--- /dev/null >+++ b/Koha/Illrequests.pm >@@ -0,0 +1,99 @@ >+package Koha::Illrequests; >+ >+# Copyright PTFS Europe 2016 >+# >+# 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, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use Modern::Perl; >+ >+use Koha::Database; >+use Koha::Illrequest; >+use Koha::Illrequest::Config; >+ >+use base qw(Koha::Objects); >+ >+=head1 NAME >+ >+Koha::Illrequests - Koha Illrequests Object class >+ >+=head1 API >+ >+=head2 Class Methods >+ >+=cut >+ >+=head3 type >+ >+=cut >+ >+sub _type { >+ return 'Illrequest'; >+} >+ >+sub object_class { >+ return 'Koha::Illrequest'; >+} >+ >+##### To be implemented Facade >+ >+=head3 new >+ >+ my $illRequests = Koha::Illrequests->new(); >+ >+Create an ILLREQUESTS object, a singleton through which we can interact with >+ILLREQUEST objects stored in the database or search for ILL candidates at API >+backends. >+ >+=cut >+ >+sub new { >+ my ( $class, $attributes ) = @_; >+ >+ my $self = $class->SUPER::new($class, $attributes); >+ >+ # FIXME: Unless we can inject config into Illrequest objects, strip this >+ # out. >+ my $config = Koha::Illrequest::Config->new; # <- Necessary >+ $self->{_config} = $config; # <- Necessary >+ >+ return $self; >+} >+ >+=head3 search_incomplete >+ >+ my $requests = $illRequests->search_incomplete; >+ >+A specialised version of `search`, returning all requests currently >+not considered completed. >+ >+=cut >+ >+sub search_incomplete { >+ my ( $self ) = @_; >+ $self->search( { >+ status => [ >+ -and => { '!=', 'COMP' }, { '!=', 'GENCOMP' } >+ ] >+ } ); >+} >+ >+=head1 AUTHOR >+ >+Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com> >+ >+=cut >+ >+1; >diff --git a/Koha/REST/V1/Illrequests.pm b/Koha/REST/V1/Illrequests.pm >new file mode 100644 >index 0000000000..0807eb1bf6 >--- /dev/null >+++ b/Koha/REST/V1/Illrequests.pm >@@ -0,0 +1,85 @@ >+package Koha::REST::V1::Illrequests; >+ >+# 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, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use Modern::Perl; >+ >+use Mojo::Base 'Mojolicious::Controller'; >+ >+use Koha::Illrequests; >+use Koha::Library; >+ >+sub list { >+ my ($c, $args, $cb) = @_; >+ >+ my $filter; >+ $args //= {}; >+ my $output = []; >+ >+ # Create a hash where all keys are embedded values >+ # Enables easy checking >+ my %embed; >+ if (defined $args->{embed}) { >+ %embed = map { $_ => 1 } @{$args->{embed}}; >+ delete $args->{embed}; >+ } >+ >+ for my $filter_param ( keys %$args ) { >+ my @values = split(/,/, $args->{$filter_param}); >+ $filter->{$filter_param} = \@values; >+ } >+ >+ my $requests = Koha::Illrequests->search($filter); >+ >+ while (my $request = $requests->next) { >+ my $unblessed = $request->unblessed; >+ # Add the request's id_prefix >+ $unblessed->{id_prefix} = $request->id_prefix; >+ # Augment the request response with patron details >+ # if appropriate >+ if (defined $embed{patron}) { >+ my $patron = $request->patron; >+ $unblessed->{patron} = { >+ firstname => $patron->firstname, >+ surname => $patron->surname, >+ cardnumber => $patron->cardnumber >+ }; >+ } >+ # Augment the request response with metadata details >+ # if appropriate >+ if (defined $embed{metadata}) { >+ $unblessed->{metadata} = $request->metadata; >+ } >+ # Augment the request response with status details >+ # if appropriate >+ if (defined $embed{capabilities}) { >+ $unblessed->{capabilities} = $request->capabilities; >+ } >+ # Augment the request response with branch details >+ # if appropriate >+ if (defined $embed{branch}) { >+ $unblessed->{branch} = Koha::Libraries->find( >+ $request->branchcode >+ )->unblessed; >+ } >+ push @{$output}, $unblessed >+ } >+ >+ return $c->$cb( $output, 200 ); >+ >+} >+ >+1; >diff --git a/api/v1/swagger/paths.json b/api/v1/swagger/paths.json >index b1fcf40f5e..608d7177b1 100644 >--- a/api/v1/swagger/paths.json >+++ b/api/v1/swagger/paths.json >@@ -16,5 +16,8 @@ > }, > "/patrons/{borrowernumber}": { > "$ref": "paths/patrons.json#/~1patrons~1{borrowernumber}" >+ }, >+ "/illrequests": { >+ "$ref": "paths/illrequests.json#/~1illrequests" > } > } >diff --git a/api/v1/swagger/paths/illrequests.json b/api/v1/swagger/paths/illrequests.json >new file mode 100644 >index 0000000000..ddafd80216 >--- /dev/null >+++ b/api/v1/swagger/paths/illrequests.json >@@ -0,0 +1,98 @@ >+{ >+ "/illrequests": { >+ "get": { >+ "x-mojo-controller": "Koha::REST::V1::Illrequests", >+ "operationId": "list", >+ "tags": ["illrequests"], >+ "parameters": [{ >+ "name": "embed", >+ "in": "query", >+ "description": "Additional objects that should be embedded in the response", >+ "required": false, >+ "type": "array", >+ "collectionFormat": "csv", >+ "items": { >+ "type": "string", >+ "enum": [ >+ "patron", >+ "branch", >+ "capabilities" >+ ] >+ } >+ }, { >+ "name": "backend", >+ "in": "query", >+ "description": "The name of a ILL backend", >+ "required": false, >+ "type": "string" >+ }, { >+ "name": "orderid", >+ "in": "query", >+ "description": "The order ID of a request", >+ "required": false, >+ "type": "string" >+ }, { >+ "name": "biblio_id", >+ "in": "query", >+ "description": "The biblio ID associated with a request", >+ "required": false, >+ "type": "integer" >+ }, { >+ "name": "borrower_id", >+ "in": "query", >+ "description": "The borrower ID associated with a request", >+ "required": false, >+ "type": "integer" >+ }, { >+ "name": "completed", >+ "in": "query", >+ "description": "The date the request was considered completed", >+ "required": false, >+ "type": "string" >+ }, { >+ "name": "status", >+ "in": "query", >+ "description": "A full status string e.g. REQREV", >+ "required": false, >+ "type": "string" >+ }, { >+ "name": "medium", >+ "in": "query", >+ "description": "The medium of the requested item", >+ "required": false, >+ "type": "string" >+ }, { >+ "name": "updated", >+ "in": "query", >+ "description": "The last updated date of the request", >+ "required": false, >+ "type": "string" >+ }, { >+ "name": "placed", >+ "in": "query", >+ "description": "The date the request was placed", >+ "required": false, >+ "type": "string" >+ }, { >+ "name": "branch_id", >+ "in": "query", >+ "description": "The ID of the pickup branch", >+ "required": false, >+ "type": "string" >+ }], >+ "produces": [ >+ "application/json" >+ ], >+ "responses": { >+ "200": { >+ "description": "OK" >+ } >+ }, >+ "x-koha-authorization": { >+ "permissions": { >+ "borrowers": "1" >+ } >+ } >+ } >+ } >+} >diff --git a/etc/koha-conf.xml b/etc/koha-conf.xml >index 4ccee3a4a1..067e97854b 100644 >--- a/etc/koha-conf.xml >+++ b/etc/koha-conf.xml >@@ -153,5 +153,26 @@ __PAZPAR2_TOGGLE_XML_POST__ > <plack_max_requests>50</plack_max_requests> > <plack_workers>2</plack_workers> > >+ <interlibrary_loans> >+ <!-- Path to where Illbackends are located on the system >+ - This setting should normally not be touched --> >+ <backend_directory>__PERL_MODULE_DIR__/Koha/Illbackends</backend_directory> >+ <!-- How should we treat staff comments? >+ - hide: don't show in OPAC >+ - show: show in OPAC --> >+ <staff_request_comments>hide</staff_request_comments> >+ <!-- How should we treat the reply_date field? >+ - hide: don't show this field in the UI >+ - any other string: show, with this label --> >+ <reply_date>hide</reply_date> >+ <!-- Where should digital ILLs be sent? >+ - borrower: send it straight to the borrower email >+ - branch: send the ILL to the branch email --> >+ <digital_recipient>branch</digital_recipient> >+ <!-- What patron category should we use for p2p ILL requests? >+ - By default this is set to 'ILLLIBS' --> >+ <partner_code>ILLLIBS</partner_code> >+ </interlibrary_loans> >+ > </config> > </yazgfs> >diff --git a/ill/ill-requests.pl b/ill/ill-requests.pl >new file mode 100755 >index 0000000000..a267a8db4d >--- /dev/null >+++ b/ill/ill-requests.pl >@@ -0,0 +1,238 @@ >+#!/usr/bin/perl >+ >+# Copyright 2013 PTFS-Europe Ltd and Mark Gavillet >+# Copyright 2014 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, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use Modern::Perl; >+ >+use CGI; >+ >+use C4::Auth; >+use C4::Output; >+use Koha::AuthorisedValues; >+use Koha::Illrequests; >+use Koha::Libraries; >+ >+my $cgi = CGI->new; >+my $illRequests = Koha::Illrequests->new; >+ >+# Grab all passed data >+# 'our' since Plack changes the scoping >+# of 'my' >+our $params = $cgi->Vars(); >+ >+my $op = $params->{method} || 'illlist'; >+ >+my ( $template, $patronnumber, $cookie ) = get_template_and_user( { >+ template_name => 'ill/ill-requests.tt', >+ query => $cgi, >+ type => 'intranet', >+ flagsrequired => { ill => '*' }, >+} ); >+ >+if ( $op eq 'illview' ) { >+ # View the details of an ILL >+ my $request = Koha::Illrequests->find($params->{illrequest_id}); >+ >+ $template->param( >+ request => $request >+ ); >+ >+} elsif ( $op eq 'create' ) { >+ # We're in the process of creating a request >+ my $request = Koha::Illrequest->new >+ ->load_backend($params->{backend}); >+ my $backend_result = $request->backend_create($params); >+ $template->param( >+ whole => $backend_result, >+ request => $request >+ ); >+ handle_commit_maybe($backend_result, $request); >+ >+} elsif ( $op eq 'confirm' ) { >+ # Backend 'confirm' method >+ # confirm requires a specific request, so first, find it. >+ my $request = Koha::Illrequests->find($params->{illrequest_id}); >+ my $backend_result = $request->backend_confirm($params); >+ $template->param( >+ whole => $backend_result, >+ request => $request, >+ ); >+ >+ # handle special commit rules & update type >+ handle_commit_maybe($backend_result, $request); >+ >+} elsif ( $op eq 'cancel' ) { >+ # Backend 'cancel' method >+ # cancel requires a specific request, so first, find it. >+ my $request = Koha::Illrequests->find($params->{illrequest_id}); >+ my $backend_result = $request->backend_cancel($params); >+ $template->param( >+ whole => $backend_result, >+ request => $request, >+ ); >+ >+ # handle special commit rules & update type >+ handle_commit_maybe($backend_result, $request); >+ >+} elsif ( $op eq 'edit_action' ) { >+ # Handle edits to the Illrequest object. >+ # (not the Illrequestattributes) >+ # We simulate the API for backend requests for uniformity. >+ # So, init: >+ my $request = Koha::Illrequests->find($params->{illrequest_id}); >+ if ( !$params->{stage} ) { >+ my $backend_result = { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'edit_action', >+ stage => 'init', >+ next => '', >+ value => {} >+ }; >+ $template->param( >+ whole => $backend_result, >+ request => $request >+ ); >+ } else { >+ # Commit: >+ # Save the changes >+ $request->borrowernumber($params->{borrowernumber}); >+ $request->biblio_id($params->{biblio_id}); >+ $request->branchcode($params->{branchcode}); >+ $request->notesopac($params->{notesopac}); >+ $request->notesstaff($params->{notesstaff}); >+ $request->store; >+ my $backend_result = { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'edit_action', >+ stage => 'commit', >+ next => 'illlist', >+ value => {} >+ }; >+ handle_commit_maybe($backend_result, $request); >+ } >+ >+} elsif ( $op eq 'moderate_action' ) { >+ # Moderate action is required for an ILL submodule / syspref. >+ # Currently still needs to be implemented. >+ redirect_to_list(); >+ >+} elsif ( $op eq 'delete_confirm') { >+ my $request = Koha::Illrequests->find($params->{illrequest_id}); >+ >+ $template->param( >+ request => $request >+ ); >+ >+} elsif ( $op eq 'delete' ) { >+ >+ # Check if the request is confirmed, if not, redirect >+ # to the confirmation view >+ if ($params->{confirmed} == 1) { >+ # We simply delete the request... >+ my $request = Koha::Illrequests->find( >+ $params->{illrequest_id} >+ )->delete; >+ # ... then return to list view. >+ redirect_to_list(); >+ } else { >+ print $cgi->redirect( >+ "/cgi-bin/koha/ill/ill-requests.pl?" . >+ "method=delete_confirm&illrequest_id=" . >+ $params->{illrequest_id}); >+ } >+ >+} elsif ( $op eq 'generic_confirm' ) { >+ my $request = Koha::Illrequests->find($params->{illrequest_id}); >+ $params->{current_branchcode} = C4::Context->mybranch; >+ my $backend_result = $request->generic_confirm($params); >+ $template->param( >+ whole => $backend_result, >+ request => $request, >+ ); >+ >+ # handle special commit rules & update type >+ handle_commit_maybe($backend_result, $request); >+ >+} elsif ( $op eq 'illlist') { >+ # Display all current ILLs >+ my $requests = $illRequests->search(); >+ >+ $template->param( >+ requests => $requests >+ ); >+ >+ # If we receive a pre-filter, make it available to the template >+ my $possible_filters = ['borrowernumber']; >+ my $active_filters = []; >+ foreach my $filter(@{$possible_filters}) { >+ if ($params->{$filter}) { >+ push @{$active_filters}, >+ { name => $filter, value => $params->{$filter}}; >+ } >+ } >+ if (scalar @{$active_filters} > 0) { >+ $template->param( >+ prefilters => $active_filters >+ ); >+ } >+} else { >+ my $request = Koha::Illrequests->find($params->{illrequest_id}); >+ my $backend_result = $request->custom_capability($op, $params); >+ $template->param( >+ whole => $backend_result, >+ request => $request, >+ ); >+} >+ >+# Get a list of backends >+my $ir = Koha::Illrequest->new; >+ >+$template->param( >+ backends => $ir->available_backends, >+ media => [ "Book", "Article", "Journal" ], >+ query_type => $op, >+ branches => Koha::Libraries->search->unblessed, >+ here_link => "/cgi-bin/koha/ill/ill-requests.pl" >+); >+ >+output_html_with_http_headers( $cgi, $cookie, $template->output ); >+ >+sub handle_commit_maybe { >+ my ( $backend_result, $request ) = @_; >+ # We need to special case 'commit' >+ if ( $backend_result->{stage} eq 'commit' ) { >+ if ( $backend_result->{next} eq 'illview' ) { >+ # Redirect to a view of the newly created request >+ print $cgi->redirect( >+ '/cgi-bin/koha/ill/ill-requests.pl?method=illview&illrequest_id='. >+ $request->id >+ ); >+ } else { >+ # Redirect to a requests list view >+ redirect_to_list(); >+ } >+ } >+} >+ >+sub redirect_to_list { >+ print $cgi->redirect('/cgi-bin/koha/ill/ill-requests.pl'); >+} >diff --git a/koha-tmpl/intranet-tmpl/prog/css/staff-global.css b/koha-tmpl/intranet-tmpl/prog/css/staff-global.css >index 784fc3e406..e86043d2f9 100644 >--- a/koha-tmpl/intranet-tmpl/prog/css/staff-global.css >+++ b/koha-tmpl/intranet-tmpl/prog/css/staff-global.css >@@ -3019,3 +3019,80 @@ fieldset.rows + fieldset.action { > #patron_search #filters { > display: none; > } >+ >+#interlibraryloans h1 { >+ margin: 1em 0; >+} >+ >+#interlibraryloans h2 { >+ margin-bottom: 20px; >+} >+ >+#interlibraryloans h3 { >+ margin-top: 20px; >+} >+ >+#interlibraryloans .bg-info { >+ overflow: auto; >+ position: relative; >+} >+ >+#interlibraryloans #search-summary { >+ -webkit-transform: translateY(-50%); >+ -ms-transform: translateY(-50%); >+ -o-transform: translateY(-50%); >+ transform: translateY(-50%); >+ position: absolute; >+ top: 50%; >+} >+ >+#interlibraryloans .format h5 { >+ margin-top: 20px; >+} >+ >+#interlibraryloans .format li { >+ list-style: none; >+} >+ >+#interlibraryloans .format h4 { >+ margin-bottom: 20px; >+} >+ >+#interlibraryloans .format input { >+ margin: 10px 0; >+} >+ >+#interlibraryloans #freeform-fields .custom-name { >+ width: 9em; >+ margin-right: 1em; >+ text-align: right; >+} >+ >+#interlibraryloans #freeform-fields .delete-new-field { >+ margin-left: 1em; >+} >+ >+#interlibraryloans #add-new-fields { >+ margin: 1em; >+} >+ >+#ill-view-panel { >+ margin-top: 15px; >+} >+ >+#ill-view-panel h3 { >+ margin-bottom: 10px; >+} >+ >+#ill-view-panel h4 { >+ margin-bottom: 20px; >+} >+ >+#ill-view-panel .rows div { >+ height: 1em; >+ margin-bottom: 1em; >+} >+ >+#ill-view-panel #requestattributes .label { >+ width: auto; >+} >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc >index e621d6409d..4006f6c65a 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc >@@ -112,5 +112,8 @@ > [% IF Koha.Preference('HouseboundModule') %] > [% IF houseboundview %]<li class="active">[% ELSE %]<li>[% END %]<a href="/cgi-bin/koha/members/housebound.pl?borrowernumber=[% borrowernumber %]">Housebound</a></li> > [% END %] >+ [% IF Koha.Preference('ILLModule') %] >+ <li><a href="/cgi-bin/koha/ill/ill-requests.pl?borrowernumber=[% borrowernumber %]">Interlibrary loans</a></li> >+ [% END %] > </ul></div> > [% END %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/ill-toolbar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/ill-toolbar.inc >new file mode 100644 >index 0000000000..3f541d9283 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/ill-toolbar.inc >@@ -0,0 +1,24 @@ >+[% USE Koha %] >+[% IF Koha.Preference('ILLModule ') %] >+ <div id="toolbar" class="btn-toolbar"> >+ [% IF backends.size > 1 %] >+ <div class="dropdown btn-group"> >+ <button class="btn btn-sm btn-default dropdown-toggle" type="button" id="ill-backend-dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> >+ <i class="fa fa-plus"></i> New ILL request <span class="caret"></span> >+ </button> >+ <ul class="dropdown-menu" aria-labelledby="ill-backend-dropdown"> >+ [% FOREACH backend IN backends %] >+ <li><a href="/cgi-bin/koha/ill/ill-requests.pl?method=create&backend=[% backend %]">[% backend %]</a></li> >+ [% END %] >+ </ul> >+ </div> >+ [% ELSE %] >+ <a id="ill-new" class="btn btn-sm btn-default" href="/cgi-bin/koha/ill/ill-requests.pl?method=create&backend=[% backends.0 %]"> >+ <i class="fa fa-plus"></i> New ILL request >+ </a> >+ [% END %] >+ <a id="ill-list" class="btn btn-sm btn-default btn-group" href="/cgi-bin/koha/ill/ill-requests.pl"> >+ <i class="fa fa-list"></i> List requests >+ </a> >+ </div> >+[% END %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc >index 7c6275ab51..4a1ce81440 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc >@@ -20,6 +20,7 @@ > [%- CASE 'plugins' -%]<span>Koha plugins</span> > [%- CASE 'lists' -%]<span>Lists</span> > [%- CASE 'clubs' -%]<span>Patron clubs</span> >+ [%- CASE 'ill' -%]<span>Create and modify Interlibrary loan requests</span> > [%- END -%] > [%- END -%] > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt >new file mode 100644 >index 0000000000..201a9776b1 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt >@@ -0,0 +1,487 @@ >+[% USE Branches %] >+ >+[% INCLUDE 'doc-head-open.inc' %] >+<title>Koha › ILL requests ›</title> >+[% INCLUDE 'doc-head-close.inc' %] >+<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.tablesorter.min.js"></script> >+<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.checkboxes.min.js"></script> >+<link rel="stylesheet" type="text/css" href="[% interface %]/[% theme %]/css/datatables.css"> >+[% INCLUDE 'datatables.inc' %] >+<script type="text/javascript"> >+ //<![CDATA[ >+ $(document).ready(function() { >+ var myTable = $("#ill-requests").DataTable($.extend(true, {}, dataTablesDefaults, { >+ "aoColumnDefs": [ // Last column shouldn't be sortable or searchable >+ { "aTargets": [ -1 ], "bSortable": false, "bSearchable": false }, >+ ], >+ "aaSorting": [[ 8, "desc" ]], // Default sort, updated descending >+ "processing": true, // Display a message when manipulating >+ "language": { >+ "loadingRecords": "Please wait - loading requests...", >+ "zeroRecords": "No requests were found" >+ }, >+ "iDisplayLength": 10, // 10 results per page >+ "sPaginationType": "full_numbers", // Pagination display >+ "sAjaxDataProp": "", // Data is in the root object of the response >+ "deferRender": true, // Improve performance on big datasets >+ "ajax": { >+ url: "/api/v1/illrequests?embed=metadata,patron,capabilities,branch", >+ cache: true >+ }, >+ "columns": [ >+ { >+ data: 'orderid', >+ className: 'orderid' >+ }, >+ { >+ render: function(data, type, row) { >+ return ( >+ row.metadata.hasOwnProperty('Author') && >+ row.metadata.Author.length > 0 >+ ) ? row.metadata.Author : 'N/A'; >+ }, >+ className: 'title' >+ }, >+ { >+ render: function(data, type, row) { >+ return ( >+ row.metadata.hasOwnProperty('Title') && >+ row.metadata.Title.length > 0 >+ ) ? row.metadata.Title : 'N/A'; >+ }, >+ className: 'title' >+ }, >+ { >+ render: function(data, type, row) { >+ return '<a title="View borrower details" ' + >+ 'href="/cgi-bin/koha/members/moremember.pl?' + >+ 'borrowernumber='+row.borrowernumber+'">' + >+ row.patron.firstname + ' ' + row.patron.surname + >+ '</a>'; >+ }, >+ className: 'borrower_name' >+ }, >+ { >+ data: 'borrowernumber', >+ className: 'borrowernumber' >+ }, >+ { >+ data: 'biblio_id', >+ className: 'biblio_id' >+ }, >+ { >+ data: 'branch.branchname', >+ className: 'branch_name' >+ }, >+ { >+ render: function(data, type, row) { >+ return row.capabilities[row.status].name; >+ }, >+ className: 'status' >+ }, >+ { >+ data: 'updated', >+ className: 'updated' >+ }, >+ { >+ data: 'medium', >+ className: 'medium' >+ }, >+ { >+ data: 'cost', >+ className: 'cost' >+ }, >+ { >+ render: function(data, type, row) { >+ return row.id_prefix + row.illrequest_id; >+ }, >+ className: 'request_id' >+ }, >+ { >+ data: null, >+ render: function(data, type, row) { >+ return '<a class="btn btn-default btn-sm" ' + >+ 'href="/cgi-bin/koha/ill/ill-requests.pl?' + >+ 'method=illview&illrequest_id=' + >+ row.illrequest_id + >+ '">Manage request</a>' + >+ '</div>' >+ } >+ } >+ ] >+ })); >+ var filters = $('#ill-requests').data(); >+ if (typeof filters !== 'undefined') { >+ var filterNames = Object.keys(filters).filter( >+ function(thisData) { >+ return thisData.match(/^filter/); >+ } >+ ); >+ filterNames.forEach(function(thisFilter) { >+ var filterClass = "." + toColumnName(thisFilter); >+ var regex = '^'+filters[thisFilter]+'$'; >+ myTable.columns(filterClass).search(regex, true, false); >+ }); >+ myTable.draw(); >+ } >+ >+ function toColumnName(myVal) { >+ return myVal >+ .replace(/^filter/, '') >+ .replace(/([A-Z])/g, "_$1") >+ .replace(/^_/,'').toLowerCase(); >+ }; >+ >+ $('#toggle_requestattributes').click(function() { >+ $('#requestattributes').toggleClass('content_hidden'); >+ }); >+ >+ $('#partner_filter').keyup(function() { >+ var needle = $('#partner_filter').val(); >+ $('#partners > option').each(function() { >+ var regex = new RegExp(needle, 'i'); >+ if ( >+ needle.length == 0 || >+ $(this).is(':selected') || >+ $(this).text().match(regex) >+ ) { >+ $(this).show(); >+ } else { >+ $(this).hide(); >+ } >+ }); >+ }); >+ >+ }); >+ //]]> >+</script> >+</head> >+ >+<body id="acq_suggestion" class="acq"> >+[% INCLUDE 'header.inc' %] >+[% INCLUDE 'cat-search.inc' %] >+ >+<div id="breadcrumbs"> >+ <a href="/cgi-bin/koha/mainpage.pl">Home</a> › >+ [% IF query_type == 'create' %] >+ <a href=[% parent %]>ILL requests</a> › New request >+ [% ELSIF query_type == 'status' %] >+ <a href=[% parent %]>ILL requests</a> › Status >+ [% ELSE %] >+ ILL requests >+ [% END %] >+</div> >+ >+<div id="doc3" class="yui-t2"> >+ <div id="bd"> >+ <div id="yui-main"> >+ <div id="interlibraryloans" class="yui-b"> >+ [% INCLUDE 'ill-toolbar.inc' %] >+ >+ [% IF whole.error %] >+ <h1>Error performing operation</h1> >+ <!-- Dispatch on Status --> >+ <p>We encountered an error:</p> >+ <ol> >+ <li>[% whole.status %]</li> >+ <li>[% whole.message %]</li> >+ </ol> >+ [% END %] >+ >+ [% IF query_type == 'create' %] >+ <h1>New ILL request</h1> >+ [% PROCESS $whole.template %] >+ >+ [% ELSIF query_type == 'confirm' %] >+ <h1>Confirm ILL request</h1> >+ [% PROCESS $whole.template %] >+ >+ [% ELSIF query_type == 'cancel' %] >+ <h1>Cancel an confirmed request</h1> >+ [% PROCESS $whole.template %] >+ >+ [% ELSIF query_type == 'generic_confirm' %] >+ <h1>Place request with partner libraries</h1> >+ <!-- Start of GENERIC_EMAIL case --> >+ [% IF whole.value.partners %] >+ [% ill_url = here_link _ "?method=illview&illrequest_id=" _ request.illrequest_id %] >+ <form method="POST" action=[% here_link %]> >+ <fieldset class="rows"> >+ <legend>Interlibrary loan request details</legend> >+ <ol> >+ <li> >+ <label for="partner_filter">Filter partner libraries:</label> >+ <input type="text" id="partner_filter"> >+ </li> >+ <li> >+ <label for="partners">Select partner libraries:</label> >+ <select size="5" multiple="true" id="partners" >+ name="partners"> >+ [% FOREACH partner IN whole.value.partners %] >+ <option value=[% partner.email %]> >+ [% partner.branchcode _ " - " _ partner.surname %] >+ </option> >+ [% END %] >+ </select> >+ >+ </li> >+ <li> >+ <label for="subject">Subject Line</label> >+ <input type="text" name="subject" >+ id="subject" type="text" >+ value="[% whole.value.draft.subject %]"/> >+ </li> >+ <li> >+ <label for="body">Email text:</label> >+ <textarea name="body" id="body" rows="20" cols="80">[% whole.value.draft.body %]</textarea> >+ </li> >+ </ol> >+ <input type="hidden" value="generic_confirm" name="method"> >+ <input type="hidden" value="draft" name="stage"> >+ <input type="hidden" value="[% request.illrequest_id %]" name="illrequest_id"> >+ </fieldset> >+ <fieldset class="action"> >+ <input type="submit" class="btn btn-default" value="Send email"/> >+ <span><a href="[% ill_url %]" title="Return to request details">Cancel</a></span> >+ </fieldset> >+ </form> >+ [% ELSE %] >+ <fieldset class="rows"> >+ <legend>Interlibrary loan request details</legend> >+ <p>No partners have been defined yet. Please create appropriate patron records (by default ILLLIBS category).</p> >+ <p>Be sure to provide email addresses for these patrons.</p> >+ <p><span><a href="[% ill_url %]" title="Return to request details">Cancel</a></span></p> >+ </fieldset> >+ [% END %] >+ <!-- generic_confirm ends here --> >+ >+ [% ELSIF query_type == 'edit_action' %] >+ <form method="POST" action=[% here_link %]> >+ <fieldset class="rows"> >+ <legend>Request details</legend> >+ <ol> >+ <li class="borrowernumber"> >+ <label for="borrowernumber">Borrower number:</label> >+ <input name="borrowernumber" id="borrowernumber" type="text" value="[% request.borrowernumber %]"> >+ </li> >+ <li class="biblio_id"> >+ <label for="biblio_id" class="biblio_id">Biblio number:</label> >+ <input name="biblio_id" id="biblio_id" type="text" value="[% request.biblio_id %]"> >+ </li> >+ <li class="branchcode"> >+ <label for="branchcode" class="branchcode">Branch:</label> >+ <select name="branchcode" id="branch"> >+ [% FOREACH branch IN branches %] >+ [% IF ( branch.branchcode == request.branchcode ) %] >+ <option value="[% branch.branchcode %]" selected="selected"> >+ [% branch.branchname %] >+ </option> >+ [% ELSE %] >+ <option value="[% branch.branchcode %]"> >+ [% branch.branchname %] >+ </option> >+ [% END %] >+ [% END %] >+ </select> >+ </li> >+ <li class="status"> >+ <label class="status">Status:</label> >+ [% stat = request.status %] >+ [% request.capabilities.$stat.name %] >+ </li> >+ <li class="updated"> >+ <label class="updated">Last updated:</label> >+ [% request.updated %] >+ </li> >+ <li class="medium"> >+ <label class="medium">Request type:</label> >+ [% request.medium %] >+ </li> >+ <li class="cost"> >+ <label class="cost">Cost:</label> >+ [% request.cost %] >+ </li> >+ <li class="req_id"> >+ <label class="req_id">Request number:</label> >+ [% request.id_prefix _ request.illrequest_id %] >+ </li> >+ <li class="notesstaff"> >+ <label for="notesstaff" class="notesstaff">Staff notes:</label> >+ <textarea name="notesstaff" id="notesstaff" rows="5">[% request.notesstaff %]</textarea> >+ </li> >+ <li class="notesopac"> >+ <label for="notesopac" class="notesopac">Opac notes:</label> >+ <textarea name="notesopac" id="notesopac" rows="5">[% request.notesopac %]</textarea> >+ </li> >+ </ol> >+ </fieldset> >+ <fieldset class="action"> >+ <input type="hidden" value="edit_action" name="method"> >+ <input type="hidden" value="form" name="stage"> >+ <input type="hidden" value="[% request.illrequest_id %]" name="illrequest_id"> >+ <input type="submit" value="Submit"> >+ <a class="cancel" href="/cgi-bin/koha/ill/ill-requests.pl?method=illview&illrequest_id=[% request.id %]">Cancel</a> >+ </fieldset> >+ </form> >+ >+ [% ELSIF query_type == 'delete_confirm' %] >+ >+ <div class="dialog alert"> >+ <h3>Are you sure you wish to delete this request?</h3> >+ <p> >+ <a class="btn btn-default btn-sm approve" href="?method=delete&illrequest_id=[% request.id %]&confirmed=1"><i class="fa fa-fw fa-check"></i>Yes</a> >+ <a class="btn btn-default btn-sm deny" href="?method=illview&illrequest_id=[% request.id %]"><i class="fa fa-fw fa-remove"></i>No</a> >+ </p> >+ </div> >+ >+ >+ [% ELSIF query_type == 'illview' %] >+ [% actions = request.available_actions %] >+ [% capabilities = request.capabilities %] >+ [% req_status = request.status %] >+ <h1>Manage ILL request</h1> >+ <div id="toolbar" class="btn-toolbar"> >+ <a title="Edit request" id="ill-toolbar-btn-edit-action" class="btn btn-sm btn-default" href="/cgi-bin/koha/ill/ill-requests.pl?method=edit_action&illrequest_id=[% request.illrequest_id %]"> >+ <span class="fa fa-pencil"></span> >+ Edit request >+ </a> >+ [% FOREACH action IN actions %] >+ [% IF action.method != 0 %] >+ <a title="[% action.ui_method_name %]" id="ill-toolbar-btn-[% action.id | lower %]" class="btn btn-sm btn-default" href="/cgi-bin/koha/ill/ill-requests.pl?method=[% action.method %]&illrequest_id=[% request.illrequest_id %]"> >+ <span class="fa [% action.ui_method_icon %]"></span> >+ [% action.ui_method_name %] >+ </a> >+ [% END %] >+ [% END %] >+ </div> >+ <div id="ill-view-panel" class="panel panel-default"> >+ <div class="panel-heading"> >+ <h3>Request details</h3> >+ </div> >+ <div class="panel-body"> >+ <h4>Details from library</h4> >+ <div class="rows"> >+ <div class="orderid"> >+ <span class="label orderid">Order ID:</span> >+ [% request.orderid || "N/A" %] >+ </div> >+ <div class="borrowernumber"> >+ <span class="label borrowernumber">Borrower:</span> >+ [% borrowerlink = "/cgi-bin/koha/members/moremember.pl" >+ _ "?borrowernumber=" _ request.patron.borrowernumber %] >+ <a href="[% borrowerlink %]" title="View borrower details"> >+ [% request.patron.firstname _ " " >+ _ request.patron.surname _ " [" >+ _ request.patron.cardnumber >+ _ "]" %] >+ </a> >+ </div> >+ >+ <div class="biblio_id"> >+ <span class="label biblio_id">Biblio number:</span> >+ [% request.biblio_id || "N/A" %] >+ </div> >+ <div class="branchcode"> >+ <span class="label branchcode">Branch:</span> >+ [% Branches.GetName(request.branchcode) %] >+ </div> >+ <div class="status"> >+ <span class="label status">Status:</span> >+ [% capabilities.$req_status.name %] >+ </div> >+ <div class="updated"> >+ <span class="label updated">Last updated:</span> >+ [% request.updated %] >+ </div> >+ <div class="medium"> >+ <span class="label medium">Request type:</span> >+ [% request.medium %] >+ </div> >+ <div class="cost"> >+ <span class="label cost">Cost:</span> >+ [% request.cost || "N/A" %] >+ </div> >+ <div class="req_id"> >+ <span class="label req_id">Request number:</span> >+ [% request.id_prefix _ request.illrequest_id %] >+ </div> >+ <div class="notesstaff"> >+ <span class="label notes_staff">Staff notes:</span> >+ <pre>[% request.notesstaff %]</pre> >+ </div> >+ <div class="notesopac"> >+ <span class="label notes_opac">Notes:</span> >+ <pre>[% request.notesopac %]</pre> >+ </div> >+ </div> >+ <div class="rows"> >+ <h4>Details from supplier ([% request.backend %])</h4> >+ [% FOREACH meta IN request.metadata %] >+ <div class="requestmeta-[% meta.key %]"> >+ <span class="label">[% meta.key %]:</span> >+ [% meta.value %] >+ </div> >+ [% END %] >+ </div> >+ <div class="rows"> >+ <h3><a id="toggle_requestattributes" href="#">Toggle full supplier metadata</a></h3> >+ <div id="requestattributes" class="content_hidden"> >+ [% FOREACH attr IN request.illrequestattributes %] >+ <div class="requestattr-[% attr.type %]"> >+ <span class="label">[% attr.type %]:</span> >+ [% attr.value %] >+ </div> >+ [% END %] >+ </div> >+ >+ </div> >+ </div> >+ </div> >+ >+ [% ELSIF query_type == 'illlist' %] >+ <!-- illlist --> >+ <h1>View ILL requests</h1> >+ <div id="results"> >+ <h3>Details for all requests</h3> >+ <table >+ [% FOREACH filter IN prefilters %] >+ data-filter-[% filter.name %]="[% filter.value %]" >+ [% END %] >+ id="ill-requests"> >+ <thead> >+ <tr> >+ <th id="orderid">Order ID:</th> >+ <th id="author">Author:</th> >+ <th id="title">Title:</th> >+ <th id="borrower_name">Borrower:</th> >+ <th id="borrowernumber">Borrower number:</th> >+ <th id="biblio_id">Biblio number:</th> >+ <th id="branchcode">Branch:</th> >+ <th id="status">Status:</th> >+ <th id="updated">Last updated:</th> >+ <th id="medium">Request type:</th> >+ <th id="cost">Cost:</th> >+ <th id="req_id">Request number:</th> >+ <th id="link">Manage request:</th> >+ </tr> >+ </thead> >+ <tbody> >+ </tbody> >+ </table> >+ </div> >+ [% ELSE %] >+ <!-- Custom Backend Action --> >+ [% INCLUDE $whole.template %] >+ >+ [% END %] >+ </div> >+ </div> >+ </div> >+</div> >+ >+[% TRY %] >+[% PROCESS backend_jsinclude %] >+[% CATCH %] >+[% END %] >+ >+[% INCLUDE 'intranet-bottom.inc' %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt >index 2a69dd9efc..32a1409689 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt >@@ -63,6 +63,11 @@ > <li> > <a class="icon_general icon_authorities" href="/cgi-bin/koha/authorities/authorities-home.pl">Authorities</a> > </li> >+ [% IF Koha.Preference('ILLModule') %] >+ <li> >+ <a class="icon_general icon_ill" href="/cgi-bin/koha/ill/ill-requests.pl">ILL requests</a> >+ </li> >+ [% END %] > </ul> > </div><!-- /area-list-left --> > </div><!-- /yui-u first --> >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/usermenu.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/usermenu.inc >index 9afa331dc3..7d27f22d75 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/usermenu.inc >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/usermenu.inc >@@ -104,6 +104,15 @@ > [% END %] > <a href="/cgi-bin/koha/opac-discharge.pl">ask for a discharge</a></li> > [% END %] >+ >+ [% IF Koha.Preference( 'ILLModule' ) == 1 %] >+ [% IF ( illrequestsview ) %] >+ <li class="active"> >+ [% ELSE %] >+ <li> >+ [% END %] >+ <a href="/cgi-bin/koha/opac-illrequests.pl">your interlibrary loan requests</a></li> >+ [% END %] > </ul> > </div> > [% END %] >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt >new file mode 100644 >index 0000000000..0c4600fd87 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt >@@ -0,0 +1,219 @@ >+[% USE Koha %] >+[% USE Branches %] >+[% INCLUDE 'doc-head-open.inc' %] >+<title>[% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha online[% END %] catalog › Your Interlibrary loan requests</title>[% INCLUDE 'doc-head-close.inc' %] >+[% BLOCK cssinclude %][% END %] >+</head> >+[% INCLUDE 'bodytag.inc' bodyid='opac-illrequests' bodyclass='scrollto' %] >+[% BLOCK messages %] >+ [% IF message == "1" %] >+ <div class="alert alert-success" role="alert">Request updated</div> >+ [% ELSIF message == "2" %] >+ <div class="alert alert-success" role="alert">Request placed</div> >+ [% END %] >+[% END %] >+[% INCLUDE 'masthead.inc' %] >+<div class="main"> >+ <ul class="breadcrumb noprint"> >+ <li><a href="/cgi-bin/koha/opac-main.pl">Home</a> <span class="divider">›</span></li> >+ [% IF ( loggedinusername ) %] >+ <li><a href="/cgi-bin/koha/opac-user.pl">[% USER_INFO.title %] [% USER_INFO.firstname %] [% USER_INFO.surname %]</a> <span class="divider">›</span></li> >+ [% END %] >+ >+ [% IF method != 'list' %] >+ <li><a href="/cgi-bin/koha/opac-illrequests.pl">Interlibrary loan requests</a> <span class="divider">›</span></li> >+ [% IF method == 'create' %] >+ <li>New Interlibrary loan request</li> >+ [% ELSIF method == 'view' %] >+ <li>View Interlibrary loan request</li> >+ [% END %] >+ [% ELSE %] >+ <li>Interlibrary loan requests</li> >+ [% END %] >+ >+ </ul> <!-- / .breadcrumb --> >+ >+<div class="container-fluid"> >+ <div class="row-fluid"> >+ [% IF ( OpacNav||loggedinusername ) && !print %] >+ <div class="span2"> >+ <div id="navigation"> >+ [% INCLUDE 'navigation.inc' IsPatronPage=1 %] >+ </div> >+ </div> >+ [% END %] >+ >+ [% IF ( OpacNav||loggedinusername ) %] >+ <div class="span10"> >+ [% ELSE %] >+ <div class="span12"> >+ [% END %] >+ <div id="illrequests" class="maincontent"> >+ [% IF method == 'create' %] >+ <h2>New Interlibrary loan request</h2> >+ [% INCLUDE messages %] >+ [% IF backends %] >+ <form method="post" id="illrequestcreate-form" novalidate="novalidate"> >+ <fieldset class="rows"> >+ <label for="backend">Provider:</label> >+ <select name="backend"> >+ [% FOREACH backend IN backends %] >+ <option value="[% backend %]">[% backend %]</option> >+ [% END %] >+ </select> >+ </fieldset> >+ <fieldset class="action"> >+ <input type="hidden" name="method" value="create"> >+ <input type="submit" name="create_select_backend" value="Next »"> >+ </fieldset> >+ </form> >+ [% ELSE %] >+ [% PROCESS $whole.opac_template %] >+ [% END %] >+ [% ELSIF method == 'list' %] >+ <h2>Interlibrary loan requests</h2> >+ [% INCLUDE messages %] >+ >+ <div id="illrequests-create-button" class="dropdown btn-group"> >+ [% IF backends.size > 1 %] >+ <button class="btn btn-default dropdown-toggle" type="button" id="ill-backend-dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> >+ <i class="fa fa-plus"></i> Create a new request <span class="caret"></span> >+ </button> >+ <ul id="backend-dropdown-options" class="dropdown-menu nojs" aria-labelledby="ill-backend-dropdown"> >+ [% FOREACH backend IN backends %] >+ <li><a href="/cgi-bin/koha/opac-illrequests.pl?method=create&backend=[% backend %]">[% backend %]</a></li> >+ [% END %] >+ </ul> >+ [% ELSE %] >+ <a id="ill-new" class="btn btn-default" href="/cgi-bin/koha/opac-illrequests.pl?method=create&backend=[% backends.0 %]"> >+ <i class="fa fa-plus"></i> Create a new request >+ </a> >+ [% END %] >+ </div> >+ >+ <table id="illrequestlist" class="table table-bordered table-striped"> >+ <thead> >+ <tr> >+ <th>Author</th> >+ <th>Title</th> >+ <th>Requested from</th> >+ <th>Request type</th> >+ <th>Status</th> >+ <th>Last updated</th> >+ <th></th> >+ </tr> >+ </thead> >+ <tbody> >+ [% FOREACH request IN requests %] >+ [% status = request.status %] >+ <tr> >+ <td>[% request.metadata.Author || 'N/A' %]</td> >+ <td>[% request.metadata.Title || 'N/A' %]</td> >+ <td>[% request.backend %]</td> >+ <td>[% request.medium %]</td> >+ <td>[% request.capabilities.$status.name %]</td> >+ <td>[% request.updated %]</td> >+ <td> >+ <a href="/cgi-bin/koha/opac-illrequests.pl?method=view&illrequest_id=[% request.id %]" class="btn btn-default btn-small pull-right">View</a> >+ </td> >+ </tr> >+ [% END %] >+ </tbody> >+ </table> >+ [% ELSIF method == 'view' %] >+ <h2>View Interlibrary loan request</h2> >+ [% INCLUDE messages %] >+ [% status = request.status %] >+ <form method="post" action="?method=update" id="illrequestupdate-form" novalidate="novalidate"> >+ <fieldset class="rows"> >+ <legend id="library_legend">Details from library</legend> >+ <ol> >+ <li> >+ <label for="backend">Requested from:</label> >+ [% request.backend %] >+ </li> >+ [% IF request.biblio_id %] >+ <li> >+ <label for="biblio">Requested item:</label> >+ <a href="/cgi-bin/koha/opac-detail.pl?biblionumber=[% request.biblio_id %]">Click here to view</a> >+ </li> >+ [% END %] >+ <li> >+ <label for="branchcode">Collection library:</label> >+ [% Branches.GetName(request.branchcode) %] >+ </li> >+ <li> >+ <label for="status">Status:</label> >+ [% request.capabilities.$status.name %] >+ </li> >+ <li> >+ <label for="medium">Request type:</label> >+ [% request.medium %] >+ </li> >+ <li> >+ <label for="placed">Request placed:</label> >+ [% request.placed %] >+ </li> >+ <li> >+ <label for="updated">Last updated:</label> >+ [% request.updated %] >+ </li> >+ <li> >+ <label for="notesopac">Notes:</label> >+ [% IF !request.completed %] >+ <textarea name="notesopac" rows="5" cols="50">[% request.notesopac %]</textarea> >+ [% ELSE %] >+ [% request.notesopac %] >+ [% END %] >+ </li> >+ </ol> >+ </fieldset> >+ <div class="rows"> >+ <legend id="backend_legend">Details from [% request.backend %]</legend> >+ [% FOREACH meta IN request.metadata %] >+ <div class="requestattr-[% meta.key %]"> >+ <span class="label">[% meta.key %]:</span> >+ [% meta.value || 'N/A' %] >+ </div> >+ [% END %] >+ </div> >+ <fieldset class="action illrequest-actions"> >+ <input type="hidden" name="illrequest_id" value="[% request.illrequest_id %]"> >+ <input type="hidden" name="method" value="update"> >+ [% IF !request.completed %] >+ [% IF request.status != "CANCREQ" %] >+ <a class="cancel-illrequest btn btn-danger" href="/cgi-bin/koha/opac-illrequests.pl?method=cancreq&illrequest_id=[% request.illrequest_id %]">Request cancellation</a> >+ [% END %] >+ <input type="submit" class="update-illrequest btn btn-default" value="Submit modifications"> >+ [% END %] >+ <span class="cancel"><a href="/cgi-bin/koha/opac-illrequests.pl">Cancel</a></span> >+ </fieldset> >+ </form> >+ [% END %] >+ </div> <!-- / .maincontent --> >+ </div> <!-- / .span10/12 --> >+ </div> <!-- / .row-fluid --> >+ </div> <!-- / .container-fluid --> >+</div> <!-- / .main --> >+ >+[% INCLUDE 'opac-bottom.inc' %] >+ >+[% BLOCK jsinclude %] >+[% INCLUDE 'datatables.inc' %] >+<script type="text/javascript"> >+ //<![CDATA[ >+ $("#illrequestlist").dataTable($.extend(true, {}, dataTablesDefaults, { >+ "aoColumnDefs": [ >+ { "aTargets": [ -1 ], "bSortable": false, "bSearchable": false } >+ ], >+ "aaSorting": [[ 3, "desc" ]], >+ "deferRender": true >+ })); >+ $("#backend-dropdown-options").removeClass("nojs"); >+ //]]> >+</script> >+[% TRY %] >+[% PROCESS backend_jsinclude %] >+[% CATCH %] >+[% END %] >+[% END %] >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results-grouped.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results-grouped.tt >index 8595f9ae8c..eaa20b30a4 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results-grouped.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results-grouped.tt >@@ -253,14 +253,30 @@ href="/cgi-bin/koha/opac-rss.pl?[% query_cgi %][% limit_cgi |html %]" /> > [% INCLUDE 'page-numbers.inc' %] > [% END # / IF total %] > >- [% IF Koha.Preference( 'suggestion' ) == 1 %] >- [% IF Koha.Preference( 'AnonSuggestions' ) == 1 %] >- <div class="suggestion">Not finding what you're looking for?<br /> Make a <a href="/cgi-bin/koha/opac-suggestions.pl?op=add">purchase suggestion</a></div> >- [% ELSE %] >- [% IF ( loggedinusername ) %]<div class="suggestion">Not finding what you're looking for?<br /> Make a <a href="/cgi-bin/koha/opac-suggestions.pl?op=add">purchase suggestion</a></div>[% END %] >- [% END %] >+ [% IF >+ Koha.Preference( 'suggestion' ) == 1 && >+ ( >+ Koha.Preference( 'AnonSuggestions' ) == 1 || >+ loggedinusername || >+ Koha.Preference( 'ILLModule' ) == 1 >+ ) >+ %] >+ <div class="suggestion"> >+ Not finding what you're looking for? >+ <ul> >+ [% IF Koha.Preference( 'AnonSuggestions' ) == 1 %] >+ <li>Make a <a href="/cgi-bin/koha/opac-suggestions.pl?op=add">purchase suggestion</a></li> >+ [% ELSE %] >+ [% IF ( loggedinusername ) %] >+ <li>Make a <a href="/cgi-bin/koha/opac-suggestions.pl?op=add">purchase suggestion</a></li> >+ [% END %] >+ [% END %] >+ [% IF Koha.Preference( 'ILLModule' ) == 1 && loggedinusername %] >+ <li>Make an <a href="/cgi-bin/koha/opac-illrequests.pl?op=create">Interlibrary loan request</a></li> >+ [% END %] >+ </ul> >+ </div> > [% END %] >- > </div> <!-- / #grouped-results --> > </div> <!-- /.span10/12 --> > </div> <!-- / .row-fluid --> >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt >index a5a7c707f5..11cf98e317 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt >@@ -562,16 +562,29 @@ > > [% END # / IF total %] > >- [% IF Koha.Preference( 'suggestion' ) == 1 %] >- [% IF Koha.Preference( 'AnonSuggestions' ) == 1 %] >- <div class="suggestion">Not finding what you're looking for?<br /> Make a <a href="/cgi-bin/koha/opac-suggestions.pl?op=add">purchase suggestion</a></div> >- [% ELSE %] >- [% IF ( loggedinusername ) %] >- <div class="suggestion"> >- Not finding what you're looking for?<br /> Make a <a href="/cgi-bin/koha/opac-suggestions.pl?op=add">purchase suggestion</a> >- </div> >- [% END %] >- [% END %] >+ [% IF >+ Koha.Preference( 'suggestion' ) == 1 && >+ ( >+ Koha.Preference( 'AnonSuggestions' ) == 1 || >+ loggedinusername || >+ Koha.Preference( 'ILLModule' ) == 1 >+ ) >+ %] >+ <div class="suggestion"> >+ Not finding what you're looking for? >+ <ul> >+ [% IF Koha.Preference( 'AnonSuggestions' ) == 1 %] >+ <li>Make a <a href="/cgi-bin/koha/opac-suggestions.pl?op=add">purchase suggestion</a></li> >+ [% ELSE %] >+ [% IF ( loggedinusername ) %] >+ <li>Make a <a href="/cgi-bin/koha/opac-suggestions.pl?op=add">purchase suggestion</a></li> >+ [% END %] >+ [% END %] >+ [% IF Koha.Preference( 'ILLModule' ) == 1 && loggedinusername %] >+ <li>Make an <a href="/cgi-bin/koha/opac-illrequests.pl?op=create">Interlibrary loan request</a></li> >+ [% END %] >+ </ul> >+ </div> > [% END %] > </div> <!-- / #userresults --> > </div> <!-- /.span10/12 --> >diff --git a/koha-tmpl/opac-tmpl/bootstrap/less/opac.less b/koha-tmpl/opac-tmpl/bootstrap/less/opac.less >index 63961e8835..c4e55c224a 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/less/opac.less >+++ b/koha-tmpl/opac-tmpl/bootstrap/less/opac.less >@@ -2508,6 +2508,44 @@ a.reviewlink:visited { > font-size: 90%; > } > >+#illrequests { >+ .illrequest-actions { >+ .btn, >+ .cancel { >+ margin-right: 5px; >+ } >+ padding-top: 20px; >+ margin-bottom: 20px; >+ } >+ #illrequests-create-button { >+ margin-bottom: 20px; >+ } >+ .bg-info { >+ overflow: auto; >+ position: relative; >+ } >+ .bg-info { >+ #search-summary { >+ -webkit-transform: translateY(-50%); >+ -ms-transform: translateY(-50%); >+ -o-transform: translateY(-50%); >+ transform: translateY(-50%); >+ position: absolute; >+ top: 50%; >+ } >+ >+ } >+ #freeform-fields .custom-name { >+ float: left; >+ width: 8em; >+ margin-right: 1em; >+ text-align: right; >+ } >+ .dropdown:hover .dropdown-menu.nojs { >+ display: block; >+ } >+} >+ > #dc_fieldset { > border: 1px solid #dddddd; > border-width: 1px; >diff --git a/opac/opac-illrequests.pl b/opac/opac-illrequests.pl >new file mode 100755 >index 0000000000..9ff413adf7 >--- /dev/null >+++ b/opac/opac-illrequests.pl >@@ -0,0 +1,129 @@ >+#!/usr/bin/perl >+ >+# Copyright 2017 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; >+ >+use CGI qw ( -utf8 ); >+use C4::Auth; >+use C4::Koha; >+use C4::Output; >+ >+use Koha::Illrequests; >+use Koha::Libraries; >+use Koha::Patrons; >+ >+my $query = new CGI; >+ >+# Grab all passed data >+# 'our' since Plack changes the scoping >+# of 'my' >+our $params = $query->Vars(); >+ >+# if illrequests is disabled, leave immediately >+if ( ! C4::Context->preference('ILLModule') ) { >+ print $query->redirect("/cgi-bin/koha/errors/404.pl"); >+ exit; >+} >+ >+my ( $template, $loggedinuser, $cookie ) = get_template_and_user({ >+ template_name => "opac-illrequests.tt", >+ query => $query, >+ type => "opac", >+ authnotrequired => ( C4::Context->preference("OpacPublic") ? 1 : 0 ), >+}); >+ >+my $op = $params->{'method'} || 'list'; >+ >+if ( $op eq 'list' ) { >+ >+ my $requests = Koha::Illrequests->search( >+ { borrowernumber => $loggedinuser } >+ ); >+ my $req = Koha::Illrequest->new; >+ $template->param( >+ requests => $requests, >+ backends => $req->available_backends >+ ); >+ >+} elsif ( $op eq 'view') { >+ my $request = Koha::Illrequests->find({ >+ borrowernumber => $loggedinuser, >+ illrequest_id => $params->{illrequest_id} >+ }); >+ $template->param( >+ request => $request >+ ); >+ >+} elsif ( $op eq 'update') { >+ my $request = Koha::Illrequests->find({ >+ borrowernumber => $loggedinuser, >+ illrequest_id => $params->{illrequest_id} >+ }); >+ $request->notesopac($params->{notesopac})->store; >+ print $query->redirect( >+ '/cgi-bin/koha/opac-illrequests.pl?method=view&illrequest_id=' . >+ $params->{illrequest_id} . >+ '&message=1' >+ ); >+} elsif ( $op eq 'cancreq') { >+ my $request = Koha::Illrequests->find({ >+ borrowernumber => $loggedinuser, >+ illrequest_id => $params->{illrequest_id} >+ }); >+ $request->status('CANCREQ')->store; >+ print $query->redirect( >+ '/cgi-bin/koha/opac-illrequests.pl?method=view&illrequest_id=' . >+ $params->{illrequest_id} . >+ '&message=1' >+ ); >+ >+} elsif ( $op eq 'create' ) { >+ if (!$params->{backend}) { >+ my $req = Koha::Illrequest->new; >+ $template->param( >+ backends => $req->available_backends >+ ); >+ } else { >+ my $request = Koha::Illrequest->new >+ ->load_backend($params->{backend}); >+ $params->{cardnumber} = Koha::Patrons->find({ >+ borrowernumber => $loggedinuser >+ })->cardnumber; >+ my $backend_result = $request->backend_create($params); >+ $template->param( >+ media => [ "Book", "Article", "Journal" ], >+ branches => Koha::Libraries->search->unblessed, >+ whole => $backend_result, >+ request => $request >+ ); >+ if ($backend_result->{stage} eq 'commit') { >+ print $query->redirect('/cgi-bin/koha/opac-illrequests.pl?message=2'); >+ } >+ } >+ >+ >+} >+ >+$template->param( >+ message => $params->{message}, >+ illrequestsview => 1, >+ method => $op >+); >+ >+output_html_with_http_headers $query, $cookie, $template->output; >diff --git a/t/db_dependent/Illrequest.t b/t/db_dependent/Illrequest.t >new file mode 100644 >index 0000000000..5fd5f0b81f >--- /dev/null >+++ b/t/db_dependent/Illrequest.t >@@ -0,0 +1,544 @@ >+#!/usr/bin/perl >+# >+# 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 2 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, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA >+# >+ >+use Modern::Perl; >+ >+use File::Basename qw/basename/; >+use Koha::Database; >+use Koha::Illrequestattributes; >+use Koha::Patrons; >+use t::lib::TestBuilder; >+ >+use Test::More tests => 44; >+ >+# We want to test the Koha IllRequest object. At its core it's a simple >+# Koha::Object, mapping to the ill_request table. >+# >+# This object will supersede the Status object in ILLModule. >+# >+# We must ensure perfect backward compatibility between the current model and >+# the Status less model. >+ >+use_ok('Koha::Illrequest'); >+use_ok('Koha::Illrequests'); >+ >+my $schema = Koha::Database->new->schema; >+$schema->storage->txn_begin; >+ >+my $builder = t::lib::TestBuilder->new; >+ >+my $patron = $builder->build({ source => 'Borrower' }); >+my $branch = $builder->build({ source => 'Branch' }); >+ >+my $illRequest = $builder->build({ >+ source => 'Illrequest', >+ value => { >+ borrowernumber => $patron->{borrowernumber}, >+ branch => $branch->{branchcode}, >+ biblionumber => 0, >+ status => 'NEW', >+ completion_date => 0, >+ reqtype => 'book', >+ } >+}); >+ >+my $illObject = Koha::Illrequests->find($illRequest->{illrequest_id}); >+ >+isa_ok($illObject, "Koha::Illrequest"); >+ >+# Test delete works correctly. >+my $illRequestDelete = $builder->build({ >+ source => 'Illrequest', >+ value => { >+ borrowernumber => $patron->{borrowernumber}, >+ branch => $branch->{branchcode}, >+ biblionumber => 0, >+ status => 'NEW', >+ completion_date => 0, >+ reqtype => 'book', >+ } >+}); >+sub ill_req_search { >+ return Koha::Illrequestattributes->search({ >+ illrequest_id => $illRequestDelete->{illrequest_id} >+ })->count; >+} >+ >+is(ill_req_search, 0, "Correctly not found matching Illrequestattributes."); >+# XXX: For some reason test builder can't build Illrequestattributes. >+my $illReqAttr = Koha::Illrequestattribute->new({ >+ illrequest_id => $illRequestDelete->{illrequest_id}, >+ type => "test", >+ value => "Hello World" >+})->store; >+is(ill_req_search, 1, "We have found a matching Illrequestattribute."); >+ >+Koha::Illrequests->find($illRequestDelete->{illrequest_id})->delete; >+is( >+ Koha::Illrequests->find($illRequestDelete->{illrequest_id}), >+ undef, >+ "Correctly deleted Illrequest." >+); >+is(ill_req_search, 0, "Correctly deleted Illrequestattributes."); >+ >+# Test Accessing of related records. >+ >+# # TODO the conclusion from being able to use one_to_many? we no longer need >+# # the Record object: simply pass the `ill_request_attributes` resultset >+# # whenever one would pass a Record. >+ >+my $illRequest2 = $builder->build({ >+ source => 'Illrequest', >+ value => { >+ borrower_id => $patron->{borrowernumber}, >+ branch_id => $branch->{branchcode}, >+ biblio_id => 0, >+ status => 'NEW', >+ completed => 0, >+ medium => 'book', >+ } >+}); >+my $illReqAttr2 = Koha::Illrequestattribute->new({ >+ illrequest_id => $illRequest2->{illrequest_id}, >+ type => "test2", >+ value => "Hello World" >+})->store; >+my $illReqAttr3 = Koha::Illrequestattribute->new({ >+ illrequest_id => $illRequest2->{illrequest_id}, >+ type => "test3", >+ value => "Hello Space" >+})->store; >+ >+my $illRequestAttributes = Koha::Illrequests >+ ->find($illRequest2->{illrequest_id})->illrequestattributes; >+ >+isa_ok($illRequestAttributes, "Koha::Illrequestattributes"); >+ >+is($illRequestAttributes->count, 2, "Able to search related."); >+ >+# Test loading of 'Config'. >+ >+my $rqConfigTest = Koha::Illrequest->new({ >+ borrower_id => $patron->{borrowernumber}, >+ branch_id => $branch->{branchcode}, >+}); >+ >+isa_ok($rqConfigTest->_config, "Koha::Illrequest::Config"); >+ >+# Test loading of 'Dummy' backend. >+ >+my $rqBackendTest = Koha::Illrequest->new({ >+ borrower_id => $patron->{borrowernumber}, >+ branch_id => $branch->{branchcode}, >+})->store; >+ >+$rqBackendTest->_config->backend("Dummy"); >+$rqBackendTest->_config->limits({ default => { count => -1 } }); >+isa_ok($rqBackendTest->_backend, "Koha::Illbackends::Dummy::Base"); >+ >+# Test use of 'Dummy' Backend. >+ >+## Test backend_update_status >+ >+# FIXME: This breaks transparancy of ->status method! >+eval { $rqBackendTest->status("ERR") }; >+ok($@, "status: Test for status error on hook fail."); >+ >+# FIXME: Will need to test this on new illRequest to not pollute rest of >+# tests. >+ >+# is($rqBackendTest->status("NEW")->status, "NEW", "status: Setter works >+# OK."); >+# is($rqBackendTest->status(null), null, "status: Unsetter works OK."); >+ >+## Test backend_create >+ >+is( >+ $rqBackendTest->status, >+ undef, >+ "backend_create: Test our status initiates correctly." >+); >+ >+# Request a search form >+my $created_rq = $rqBackendTest->backend_create({ >+ stage => "search_form", >+ method => "create", >+}); >+ >+is( $created_rq->{stage}, 'search_results', >+ "backend_create: search_results stage." ); >+ >+# Request search results >+# FIXME: fails because of missing patron / branch info. >+# $created_rq = $rqBackendTest->backend_create({ >+# stage => "search_results", >+# method => "create", >+# other => { search => "interlibrary loans" }, >+# }); >+ >+# is_deeply( >+# $created_rq, >+# { >+# error => 0, >+# status => '', >+# message => '', >+# method => 'create', >+# stage => 'search_results', >+# template => 'ill/Dummy/create.inc', >+# value => [ >+# { >+# id => 1234, >+# title => "Ordering ILLs using Koha", >+# author => "A.N. Other", >+# }, >+# { >+# id => 5678, >+# title => "Interlibrary loans in Koha", >+# author => "A.N. Other", >+# }, >+# ], >+# } >+# , >+# "backend_create: search_results stage." >+# ); >+ >+# # Create the request >+# $created_rq = $rqBackendTest->backend_create({ >+# stage => "commit", >+# method => "create", >+# other => { >+# id => 1234, >+# title => "Ordering ILLs using Koha", >+# author => "A.N. Other", >+# }, >+# }); >+ >+# while ( my ( $field, $value ) = each %{$created_rq} ) { >+# isnt($value, undef, "backend_create: key '$field' exists"); >+# }; >+ >+# is( >+# $rqBackendTest->status, >+# "NEW", >+# "backend_create: Test our status was updated." >+# ); >+ >+# cmp_ok( >+# $rqBackendTest->illrequestattributes->count, >+# "==", >+# 3, >+# "backend_create: Ensure we have correctly stored our new attributes." >+# ); >+ >+# ## Test backend_list >+ >+# is_deeply( >+# $rqBackendTest->backend_list->{value}, >+# { >+# 1 => { >+# id => 1234, >+# title => "Ordering ILLs using Koha", >+# author => "A.N. Other", >+# status => "On order", >+# cost => "30 GBP", >+# } >+# }, >+# "backend_list: Retrieve our list of requested requests." >+# ); >+ >+# ## Test backend_renew >+ >+# ok( >+# $rqBackendTest->backend_renew->{error}, >+# "backend_renew: Error for invalid request." >+# ); >+# is_deeply( >+# $rqBackendTest->backend_renew->{value}, >+# { >+# id => 1234, >+# title => "Ordering ILLs using Koha", >+# author => "A.N. Other", >+# }, >+# "backend_renew: Renew request." >+# ); >+ >+# ## Test backend_confirm >+ >+# my $rqBackendTestConfirmed = $rqBackendTest->backend_confirm; >+# is( >+# $rqBackendTest->status, >+# "REQ", >+# "backend_commit: Confirm status update correct." >+# ); >+# is( >+# $rqBackendTest->orderid, >+# 1234, >+# "backend_commit: Confirm orderid populated correctly." >+# ); >+ >+# ## Test backend_status >+ >+# is( >+# $rqBackendTest->backend_status->{error}, >+# 0, >+# "backend_status: error for invalid request." >+# ); >+# is_deeply( >+# $rqBackendTest->backend_status->{value}, >+# { >+# id => 1234, >+# status => "On order", >+# title => "Ordering ILLs using Koha", >+# author => "A.N. Other", >+# }, >+# "backend_status: Retrieve the status of request." >+# ); >+ >+# # Now test trying to get status on non-confirmed request. >+my $rqBackendTestUnconfirmed = Koha::Illrequest->new({ >+ borrower_id => $patron->{borrowernumber}, >+ branch_id => $branch->{branchcode}, >+})->store; >+$rqBackendTestUnconfirmed->_config->backend("Dummy"); >+$rqBackendTestUnconfirmed->_config->limits({ default => { count => -1 } }); >+ >+$rqBackendTestUnconfirmed->backend_create({ >+ stage => "commit", >+ method => "create", >+ other => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ }, >+}); >+is( >+ $rqBackendTestUnconfirmed->backend_status->{error}, >+ 1, >+ "backend_status: error for invalid request." >+); >+ >+## Test backend_cancel >+ >+# is( >+# $rqBackendTest->backend_cancel->{error}, >+# 0, >+# "backend_cancel: Successfully cancelling request." >+# ); >+# is_deeply( >+# $rqBackendTest->backend_cancel->{value}, >+# { >+# id => 1234, >+# title => "Ordering ILLs using Koha", >+# author => "A.N. Other", >+# }, >+# "backend_cancel: Cancel request." >+# ); >+ >+# Now test trying to cancel non-confirmed request. >+is( >+ $rqBackendTestUnconfirmed->backend_cancel->{error}, >+ 1, >+ "backend_cancel: error for invalid request." >+); >+is_deeply( >+ $rqBackendTestUnconfirmed->backend_cancel->{value}, >+ {}, >+ "backend_cancel: Cancel request." >+); >+ >+# Test Helpers >+ >+## Test getCensorNotesStaff >+ >+is($rqBackendTest->getCensorNotesStaff, 1, "getCensorNotesStaff: Public."); >+$rqBackendTest->_config->censorship({ >+ censor_notes_staff => 0, >+ censor_reply_date => 0, >+}); >+is($rqBackendTest->getCensorNotesStaff, 0, "getCensorNotesStaff: Censored."); >+ >+## Test getCensorNotesStaff >+ >+is($rqBackendTest->getDisplayReplyDate, 1, "getDisplayReplyDate: Yes."); >+$rqBackendTest->_config->censorship({ >+ censor_notes_staff => 0, >+ censor_reply_date => 1, >+}); >+is($rqBackendTest->getDisplayReplyDate, 0, "getDisplayReplyDate: No."); >+ >+# FIXME: These should be handled by the templates. >+# # Test Output Helpers >+ >+# ## Test getStatusSummary >+ >+# $rqBackendTest->medium("Book")->store; >+# is_deeply( >+# $rqBackendTest->getStatusSummary({brw => 0}), >+# { >+# biblionumber => ["Biblio Number", undef], >+# borrowernumber => ["Borrower Number", $patron->{borrowernumber}], >+# id => ["Request Number", $rqBackendTest->illrequest_id], >+# prefix_id => ["Request Number", $rqBackendTest->illrequest_id], >+# reqtype => ["Request Type", "Book"], >+# status => ["Status", "REQREV"], >+# }, >+# "getStatusSummary: Without Borrower." >+# ); >+ >+# is_deeply( >+# $rqBackendTest->getStatusSummary({brw => 1}), >+# { >+# biblionumber => ["Biblio Number", undef], >+# borrower => ["Borrower", Koha::Patrons->find($patron->{borrowernumber})], >+# id => ["Request Number", $rqBackendTest->illrequest_id], >+# prefix_id => ["Request Number", $rqBackendTest->illrequest_id], >+# reqtype => ["Request Type", "Book"], >+# status => ["Status", "REQREV"], >+# }, >+# "getStatusSummary: With Borrower." >+# ); >+ >+# ## Test getFullStatus >+ >+# is_deeply( >+# $rqBackendTest->getFullStatus({brw => 0}), >+# { >+# biblionumber => ["Biblio Number", undef], >+# borrowernumber => ["Borrower Number", $patron->{borrowernumber}], >+# id => ["Request Number", $rqBackendTest->illrequest_id], >+# prefix_id => ["Request Number", $rqBackendTest->illrequest_id], >+# reqtype => ["Request Type", "Book"], >+# status => ["Status", "REQREV"], >+# placement_date => ["Placement Date", $rqBackendTest->placed], >+# completion_date => ["Completion Date", $rqBackendTest->completed], >+# ts => ["Timestamp", $rqBackendTest->updated], >+# branch => ["Branch", $rqBackendTest->branch_id], >+# }, >+# "getFullStatus: Without Borrower." >+# ); >+ >+# is_deeply( >+# $rqBackendTest->getFullStatus({brw => 1}), >+# { >+# biblionumber => ["Biblio Number", undef], >+# borrower => ["Borrower", Koha::Patrons->find($patron->{borrowernumber})], >+# id => ["Request Number", $rqBackendTest->illrequest_id], >+# prefix_id => ["Request Number", $rqBackendTest->illrequest_id], >+# reqtype => ["Request Type", "Book"], >+# status => ["Status", "REQREV"], >+# placement_date => ["Placement Date", $rqBackendTest->placed], >+# completion_date => ["Completion Date", $rqBackendTest->completed], >+# ts => ["Timestamp", $rqBackendTest->updated], >+# branch => ["Branch", $rqBackendTest->branch_id], >+# }, >+# "getFullStatus: With Borrower." >+# ); >+ >+## Test available_backends >+subtest 'available_backends' => sub { >+ plan tests => 1; >+ >+ my $rq = Koha::Illrequest->new({ >+ borrower_id => $patron->{borrowernumber}, >+ branch_id => $branch->{branchcode}, >+ })->store; >+ >+ my @backends = (); >+ my $backenddir = $rq->_config->backend_dir; >+ @backends = <$backenddir/*> if ( $backenddir ); >+ @backends = map { basename($_) } @backends; >+ is_deeply(\@backends, $rq->available_backends, >+ "Correctly identify available backends."); >+ >+}; >+ >+## Test capabilities >+ >+my $rqCapTest = Koha::Illrequest->new({ >+ borrower_id => $patron->{borrowernumber}, >+ branch_id => $branch->{branchcode}, >+})->store; >+ >+is( keys %{$rqCapTest->_core_status_graph}, >+ @{[ 'NEW', 'REQ', 'REVREQ', 'QUEUED', 'CANCREQ', 'COMP', 'KILL' ]}, >+ "Complete list of core statuses." ); >+ >+my $union = $rqCapTest->_status_graph_union( >+ $rqCapTest->_core_status_graph, >+ { >+ TEST => { >+ prev_actions => [ 'COMP' ], >+ id => 'TEST', >+ name => "Test", >+ ui_method_name => "Perform test", >+ method => 'test', >+ next_actions => [ 'NEW' ] >+ }, >+ BLAH => { >+ prev_actions => [ 'COMP' ], >+ id => 'BLAH', >+ name => "BLAH", >+ ui_method_name => "Perform test", >+ method => 'test', >+ next_actions => [ 'NEW' ] >+ }, >+ } >+); >+ok( ( grep 'BLAH', @{$union->{COMP}->{next_actions}} and >+ grep 'TEST', @{$union->{COMP}->{next_actions}} ), >+ "next_actions: updated." ); >+ok( ( grep 'BLAH', @{$union->{NEW}->{prev_actions}} and >+ grep 'TEST', @{$union->{NEW}->{prev_actions}} ), >+ "next_actions: updated." ); >+ >+## Test available_backends >+subtest 'available_actions' => sub { >+ plan tests => 1; >+ >+ my $rq = Koha::Illrequest->new({ >+ borrower_id => $patron->{borrowernumber}, >+ branch_id => $branch->{branchcode}, >+ status => 'NEW', >+ })->store; >+ >+ is_deeply( >+ $rq->available_actions, >+ [ >+ { >+ prev_actions => [ 'NEW', 'REQREV', 'QUEUED' ], >+ id => 'REQ', >+ name => 'Requested', >+ ui_method_name => 'Create request', >+ method => 'confirm', >+ next_actions => [ 'REQREV' ], >+ }, >+ { >+ prev_actions => [ 'CANCREQ', 'QUEUED', 'REQREV', 'NEW' ], >+ id => 'KILL', >+ name => 0, >+ ui_method_name => 'Delete request', >+ method => 'delete', >+ next_actions => [ ], >+ } >+ ] >+ ); >+}; >+ >+$schema->storage->txn_rollback; >+ >+1; >diff --git a/t/db_dependent/Illrequest/Config.t b/t/db_dependent/Illrequest/Config.t >new file mode 100644 >index 0000000000..df2bcac7aa >--- /dev/null >+++ b/t/db_dependent/Illrequest/Config.t >@@ -0,0 +1,344 @@ >+#!/usr/bin/perl >+ >+# 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 Test::Exception; >+use Test::More; >+use Test::Warn; >+ >+# Some data structures that will be repeatedly referenced >+my $defaults = { >+ api_key => "564euie", >+ api_auth => "unidaenudvnled", >+}; >+my $application = { >+ key => "6546uedrun", >+ auth => "edutrineadue", >+}; >+# Simulate $from_xml >+my $params = { >+ application => $application, >+ backend => "Dummy", >+ configuration => $defaults, >+ reply_date => "hide", >+ staff_request_comments => "hide", >+}; >+my $first_branch = { >+ code => "test", api_key => "dphügnpgüffq", api_auth => "udrend" >+}; >+my $second_branch = { >+ code => "second", >+ api_key => "eduirn", >+ api_auth => "eudtireand", >+ request_limit => { count => "5" }, >+}; >+ >+BEGIN { >+ use_ok('Koha::Illrequest::Config'); >+} >+ >+my $config = Koha::Illrequest::Config->new(1); # with test_mode enabled. >+isa_ok($config, 'Koha::Illrequest::Config'); >+ >+# _load_configuration >+is_deeply( >+ Koha::Illrequest::Config::_load_configuration($params), >+ { >+ api_url => 'http://apitest.bldss.bl.uk', >+ backend => 'Dummy', >+ censorship => { >+ censor_notes_staff => 1, >+ censor_reply_date => 1, >+ }, >+ credentials => { >+ api_application => $application, >+ api_keys => { default => $defaults }, >+ }, >+ default_formats => {}, >+ digital_recipients => {}, >+ library_privileges => {}, >+ limits => {}, >+ prefixes => {}, >+ spec_file => undef, >+ }, >+ "Basic _load_configuration" >+); >+ >+$params->{configuration}->{request_limit}->{count} = 10; >+is_deeply( >+ Koha::Illrequest::Config::_load_configuration($params), >+ { >+ api_url => 'http://apitest.bldss.bl.uk', >+ backend => 'Dummy', >+ censorship => { >+ censor_notes_staff => 1, >+ censor_reply_date => 1, >+ }, >+ credentials => { >+ api_application => $application, >+ api_keys => { >+ default => { >+ api_auth => $defaults->{api_auth}, >+ api_key => $defaults->{api_key}, >+ } >+ }, >+ }, >+ default_formats => {}, >+ digital_recipients => {}, >+ library_privileges => {}, >+ limits => { default => { count => 10 } }, >+ prefixes => {}, >+ spec_file => undef, >+ }, >+ "Basic _load_configuration, with limit" >+); >+ >+$params->{configuration}->{branch} = $first_branch; >+is_deeply( >+ Koha::Illrequest::Config::_load_configuration($params), >+ { >+ api_url => 'http://apitest.bldss.bl.uk', >+ backend => 'Dummy', >+ censorship => { >+ censor_notes_staff => 1, >+ censor_reply_date => 1, >+ }, >+ credentials => { >+ api_keys => { >+ default => { >+ api_key => $defaults->{api_key}, >+ api_auth => $defaults->{api_auth}, >+ }, >+ $first_branch->{code} => { >+ api_key => $first_branch->{api_key}, >+ api_auth => $first_branch->{api_auth}, >+ }, >+ }, >+ api_application => $application, >+ }, >+ default_formats => {}, >+ digital_recipients => {}, >+ library_privileges => {}, >+ limits => { default => { count => 10 } }, >+ prefixes => {}, >+ spec_file => undef, >+ }, >+ "Single Branch _load_configuration" >+); >+ >+$params->{configuration}->{branch} = [ $first_branch, $second_branch ]; >+is_deeply( >+ Koha::Illrequest::Config::_load_configuration($params), >+ { >+ api_url => 'http://apitest.bldss.bl.uk', >+ credentials => { >+ api_keys => { >+ default => { >+ api_key => $defaults->{api_key}, >+ api_auth => $defaults->{api_auth}, >+ }, >+ $first_branch->{code} => { >+ api_key => $first_branch->{api_key}, >+ api_auth => $first_branch->{api_auth}, >+ }, >+ $second_branch->{code} => { >+ api_key => $second_branch->{api_key}, >+ api_auth => $second_branch->{api_auth}, >+ }, >+ }, >+ api_application => $application, >+ }, >+ backend => 'Dummy', >+ censorship => { >+ censor_notes_staff => 1, >+ censor_reply_date => 1, >+ }, >+ default_formats => {}, >+ digital_recipients => {}, >+ library_privileges => {}, >+ limits => { >+ default => {count => 10 }, >+ branch => { >+ $second_branch->{code} => {count => 5 }, >+ }, >+ }, >+ prefixes => {}, >+ spec_file => undef, >+ }, >+ "Multi Branch _load_configuration" >+); >+ >+dies_ok { Koha::Illrequest::Config::_load_configuration($params, 1) } >+ "Unmediated, missing config _load_configuration"; >+ >+$params->{configuration}->{default_formats} = { >+ format => 1, quality => 1, quantity => 1, service => 1, speed => 1 >+}; >+is_deeply( >+ Koha::Illrequest::Config::_load_configuration($params, 1), >+ { >+ api_url => 'http://apitest.bldss.bl.uk', >+ backend => 'Dummy', >+ censorship => { >+ censor_notes_staff => 1, >+ censor_reply_date => 1, >+ }, >+ credentials => { >+ api_keys => { >+ default => { >+ api_key => $defaults->{api_key}, >+ api_auth => $defaults->{api_auth}, >+ }, >+ $first_branch->{code} => { >+ api_key => $first_branch->{api_key}, >+ api_auth => $first_branch->{api_auth}, >+ }, >+ $second_branch->{code} => { >+ api_key => $second_branch->{api_key}, >+ api_auth => $second_branch->{api_auth}, >+ }, >+ }, >+ api_application => $application, >+ }, >+ default_formats => { >+ default => { >+ format => 1, >+ quality => 1, >+ quantity => 1, >+ service => 1, >+ speed => 1 >+ } >+ }, >+ digital_recipients => {}, >+ library_privileges => {}, >+ limits => { >+ default => {count => 10 }, >+ branch => { >+ $second_branch->{code} => {count => 5 }, >+ }, >+ }, >+ prefixes => {}, >+ spec_file => undef, >+ }, >+ "default_formats, default only _load_configuration" >+); >+ >+# getDefaultFormats >+dies_ok { $config->getLimitRules('wrongType') } >+ "Faulty getDefaultFormats"; >+ >+$config->{configuration} = >+ Koha::Illrequest::Config::_load_configuration($params); >+is_deeply( >+ $config->getDefaultFormats('brw_cat'), >+ { >+ default => { >+ format => 1, >+ quality => 1, >+ quantity => 1, >+ service => 1, >+ speed => 1 >+ } >+ }, >+ "Default getDefaultFormats" >+); >+ >+# getLimitRules >+dies_ok { $config->getLimitRules('wrongType') } >+ "Faulty getLimitRules"; >+ >+$config->{configuration} = >+ Koha::Illrequest::Config::_load_configuration($params); >+is_deeply( >+ $config->getLimitRules('branch'), >+ { >+ default => { count => 10 }, >+ second => { count => 5 }, >+ }, >+ "second branch getLimitRules" >+); >+ >+is_deeply( >+ $config->getLimitRules('brw_cat'), >+ { >+ default => { count => 10 }, >+ }, >+ "empty brw_cat getLimitRules" >+); >+ >+# getCredentials >+$params = { >+ application => $application, >+ backend => 'Dummy', >+ configuration => {}, >+ reply_date => "hide", >+ staff_request_comments => "hide", >+}; >+$config->{configuration} = >+ Koha::Illrequest::Config::_load_configuration($params); >+is_deeply( >+ $config->getCredentials, >+ { >+ api_key => 0, >+ api_key_auth => 0, >+ api_application => $application->{key}, >+ api_application_auth => $application->{auth}, >+ }, >+ "getCredentials, no creds, just App." >+); >+ >+$params->{configuration} = $defaults; >+$config->{configuration} = >+ Koha::Illrequest::Config::_load_configuration($params), >+is_deeply( >+ $config->getCredentials, >+ { >+ api_key => $defaults->{api_key}, >+ api_key_auth => $defaults->{api_auth}, >+ api_application => $application->{key}, >+ api_application_auth => $application->{auth}, >+ }, >+ "getCredentials, default creds & App." >+); >+ >+$params->{configuration}->{branch} = $first_branch; >+$config->{configuration} = >+ Koha::Illrequest::Config::_load_configuration($params), >+is_deeply( >+ $config->getCredentials($first_branch->{code}), >+ { >+ api_key => $first_branch->{api_key}, >+ api_key_auth => $first_branch->{api_auth}, >+ api_application => $application->{key}, >+ api_application_auth => $application->{auth}, >+ }, >+ "getCredentials, $first_branch->{code} creds & App." >+); >+ >+is_deeply( >+ $config->getCredentials("random"), >+ { >+ api_key => $defaults->{api_key}, >+ api_key_auth => $defaults->{api_auth}, >+ api_application => $application->{key}, >+ api_application_auth => $application->{auth}, >+ }, >+ "getCredentials, fallback creds & app." >+); >+ >+done_testing; >diff --git a/t/db_dependent/Illrequest/Dummy.t b/t/db_dependent/Illrequest/Dummy.t >new file mode 100644 >index 0000000000..38b5fac648 >--- /dev/null >+++ b/t/db_dependent/Illrequest/Dummy.t >@@ -0,0 +1,378 @@ >+#!/usr/bin/perl >+# >+# 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 2 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, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA >+# >+ >+use Modern::Perl; >+ >+use Koha::Database; >+use Koha::Illrequests; >+use t::lib::TestBuilder; >+ >+use Test::More tests => 15; >+ >+# This is a set of basic tests for the Dummy backend, largely to provide >+# sanity checks for testing at the higher level Illrequest.pm level. >+# >+# The Dummy backend is rather simple, but provides correctly formatted >+# response values, that other backends can model themselves after. >+ >+use_ok('Koha::Illrequest::Backend::Dummy'); >+ >+my $backend = Koha::Illrequest::Backend::Dummy->new; >+ >+isa_ok($backend, 'Koha::Illrequest::Backend::Dummy'); >+ >+ >+my $schema = Koha::Database->new->schema; >+$schema->storage->txn_begin; >+ >+my $builder = t::lib::TestBuilder->new; >+ >+my $patron = $builder->build({ source => 'Borrower' }); >+my $branch = $builder->build({ source => 'Branch' }); >+ >+my $illRequest = $builder->build({ >+ source => 'Illrequest', >+ value => { >+ borrowernumber => $patron->{borrowernumber}, >+ branch => $branch->{branchcode}, >+ } >+}); >+my $mock_request = Koha::Illrequests->find($illRequest->{illrequest_id}); >+$mock_request->_config->backend("Dummy"); >+$mock_request->_config->limits({ default => { count => -1 } }); >+ >+# Test Create >+my $rq = $backend->create({ >+ request => $mock_request, >+ method => 'create', >+ stage => 'search_form', >+ other => undef, >+}); >+ >+is_deeply( >+ $rq, >+ { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'create', >+ stage => 'search_form', >+ value => {}, >+ }, >+ "Search_Form stage of create method." >+); >+ >+$rq = $backend->create({ >+ request => $mock_request, >+ method => 'create', >+ stage => 'search_results', >+ other => { search => "interlibrary loans" }, >+}); >+ >+is_deeply( >+ $rq, >+ { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'create', >+ stage => 'search_results', >+ value => [ >+ { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ }, >+ { >+ id => 5678, >+ title => "Interlibrary loans in Koha", >+ author => "A.N. Other", >+ }, >+ ], >+ }, >+ "Search_Results stage of create method." >+); >+ >+$rq = $backend->create({ >+ request => $mock_request, >+ method => 'create', >+ stage => 'commit', >+ other => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ }, >+}); >+ >+is_deeply( >+ $rq, >+ { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'create', >+ stage => 'commit', >+ value => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other" >+ }, >+ }, >+ "Commit stage of create method." >+); >+ >+$rq = $backend->create({ >+ request => $mock_request, >+ method => 'create', >+ stage => 'unknown_stage', >+ other => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ }, >+}); >+ >+is_deeply( >+ $rq, >+ { >+ error => 1, >+ status => 'unknown_stage', >+ message => '', >+ method => 'create', >+ stage => 'unknown_stage', >+ value => {}, >+ }, >+ "Commit stage of create method." >+); >+ >+# Test Confirm >+ >+$rq = $backend->confirm({ >+ request => $mock_request, >+ other => undef, >+}); >+ >+is_deeply( >+ $rq, >+ { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'confirm', >+ stage => 'commit', >+ value => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ status => "On order", >+ cost => "30 GBP", >+ }, >+ }, >+ "Basic confirm method." >+); >+ >+# Test List >+ >+is_deeply( >+ $backend->list, >+ { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'list', >+ stage => 'commit', >+ value => { >+ 1 => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ status => "On order", >+ cost => "30 GBP", >+ }, >+ }, >+ }, >+ "Basic list method." >+); >+ >+# Test Renew >+ >+is_deeply( >+ $backend->renew({ >+ request => $mock_request, >+ other => undef, >+ }), >+ { >+ error => 1, >+ status => 'not_renewed', >+ message => 'Order not yet delivered.', >+ method => 'renew', >+ stage => 'commit', >+ value => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ status => "On order", >+ }, >+ }, >+ "Basic renew method." >+); >+ >+Koha::Illrequestattributes->find({ >+ illrequest_id => $mock_request->illrequest_id, >+ type => "status" >+})->set({ value => "Delivered" })->store; >+ >+is_deeply( >+ $backend->renew({ >+ request => $mock_request, >+ other => undef, >+ }), >+ { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'renew', >+ stage => 'commit', >+ value => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ status => "Renewed", >+ }, >+ }, >+ "Modified renew method." >+); >+ >+# Test Update_Status >+ >+is_deeply( >+ $backend->update_status({ >+ request => $mock_request, >+ other => undef, >+ }), >+ { >+ error => 1, >+ status => 'failed_update_hook', >+ message => 'Fake reason for failing to perform update operation.', >+ method => 'update_status', >+ stage => 'commit', >+ value => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ status => "Delivered", >+ }, >+ }, >+ "Basic update_status method." >+); >+ >+# FIXME: Perhaps we should add a test checking for specific status code >+# transitions. >+ >+# Test Cancel >+ >+is_deeply( >+ $backend->cancel({ >+ request => $mock_request, >+ other => undef, >+ }), >+ { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'cancel', >+ stage => 'commit', >+ value => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ status => "Delivered", >+ }, >+ }, >+ "Basic cancel method." >+); >+ >+is_deeply( >+ $backend->cancel({ >+ request => $mock_request, >+ other => undef, >+ }), >+ { >+ error => 1, >+ status => 'unknown_request', >+ message => 'Cannot cancel an unknown request.', >+ method => 'cancel', >+ stage => 'commit', >+ value => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ }, >+ }, >+ "Attempt to cancel an unconfirmed request." >+); >+ >+# Test Status >+ >+is_deeply( >+ $backend->status({ >+ request => $mock_request, >+ other => undef, >+ }), >+ { >+ error => 1, >+ status => 'unknown_request', >+ message => 'Cannot query status of an unknown request.', >+ method => 'status', >+ stage => 'commit', >+ value => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ }, >+ }, >+ "Attempt to get status of an unconfirmed request." >+); >+ >+$rq = $backend->confirm({ >+ request => $mock_request, >+ other => undef, >+}); >+ >+is_deeply( >+ $backend->status({ >+ request => $mock_request, >+ other => undef, >+ }), >+ { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'status', >+ stage => 'commit', >+ value => { >+ id => 1234, >+ title => "Ordering ILLs using Koha", >+ author => "A.N. Other", >+ status => "On order", >+ }, >+ }, >+ "Basic status method." >+); >+ >+1; >diff --git a/t/db_dependent/api/v1/illrequests.t b/t/db_dependent/api/v1/illrequests.t >new file mode 100644 >index 0000000000..fb306ba144 >--- /dev/null >+++ b/t/db_dependent/api/v1/illrequests.t >@@ -0,0 +1,136 @@ >+#!/usr/bin/env perl >+ >+# 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, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use Modern::Perl; >+ >+use Test::More tests => 1; >+use Test::Mojo; >+use Test::Warn; >+ >+use t::lib::TestBuilder; >+use t::lib::Mocks; >+ >+use C4::Auth; >+use Koha::Illrequests; >+ >+my $schema = Koha::Database->new->schema; >+my $builder = t::lib::TestBuilder->new; >+ >+# FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling >+# this affects the other REST api tests >+t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' ); >+ >+my $remote_address = '127.0.0.1'; >+my $t = Test::Mojo->new('Koha::REST::V1'); >+ >+subtest 'list() tests' => sub { >+ >+ plan tests => 6; >+ >+ $schema->storage->txn_begin; >+ >+ Koha::Illrequests->search->delete; >+ my ( $borrowernumber, $session_id ) = >+ create_user_and_session( { authorized => 1 } ); >+ >+ ## Authorized user tests >+ # No requests, so empty array should be returned >+ my $tx = $t->ua->build_tx( GET => '/api/v1/illrequests' ); >+ $tx->req->cookies( { name => 'CGISESSID', value => $session_id } ); >+ $tx->req->env( { REMOTE_ADDR => $remote_address } ); >+ $t->request_ok($tx)->status_is(200)->json_is( [] ); >+ >+# my $city_country = 'France'; >+# my $city = $builder->build( >+# { source => 'City', value => { city_country => $city_country } } ); >+# >+# # One city created, should get returned >+# $tx = $t->ua->build_tx( GET => '/api/v1/cities' ); >+# $tx->req->cookies( { name => 'CGISESSID', value => $session_id } ); >+# $tx->req->env( { REMOTE_ADDR => $remote_address } ); >+# $t->request_ok($tx)->status_is(200)->json_is( [$city] ); >+# >+# my $another_city = $builder->build( >+# { source => 'City', value => { city_country => $city_country } } ); >+# my $city_with_another_country = $builder->build( { source => 'City' } ); >+# >+# # Two cities created, they should both be returned >+# $tx = $t->ua->build_tx( GET => '/api/v1/cities' ); >+# $tx->req->cookies( { name => 'CGISESSID', value => $session_id } ); >+# $tx->req->env( { REMOTE_ADDR => $remote_address } ); >+# $t->request_ok($tx)->status_is(200) >+# ->json_is( [ $city, $another_city, $city_with_another_country ] ); >+# >+# # Filtering works, two cities sharing city_country >+# $tx = >+# $t->ua->build_tx( GET => "/api/v1/cities?city_country=" . $city_country ); >+# $tx->req->cookies( { name => 'CGISESSID', value => $session_id } ); >+# $tx->req->env( { REMOTE_ADDR => $remote_address } ); >+# my $result = >+# $t->request_ok($tx)->status_is(200)->json_is( [ $city, $another_city ] ); >+# >+# $tx = $t->ua->build_tx( >+# GET => "/api/v1/cities?city_name=" . $city->{city_name} ); >+# $tx->req->cookies( { name => 'CGISESSID', value => $session_id } ); >+# $tx->req->env( { REMOTE_ADDR => $remote_address } ); >+# $t->request_ok($tx)->status_is(200)->json_is( [$city] ); >+ >+ # Warn on unsupported query parameter >+ $tx = $t->ua->build_tx( GET => '/api/v1/illrequests?request_blah=blah' ); >+ $tx->req->cookies( { name => 'CGISESSID', value => $session_id } ); >+ $tx->req->env( { REMOTE_ADDR => $remote_address } ); >+ $t->request_ok($tx)->status_is(400)->json_is( >+ [{ path => '/query/request_blah', message => 'Malformed query string'}] >+ ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+sub create_user_and_session { >+ >+ my $args = shift; >+ my $flags = ( $args->{authorized} ) ? $args->{authorized} : 0; >+ my $dbh = C4::Context->dbh; >+ >+ my $user = $builder->build( >+ { >+ source => 'Borrower', >+ value => { >+ flags => $flags >+ } >+ } >+ ); >+ >+ # Create a session for the authorized user >+ my $session = C4::Auth::get_session(''); >+ $session->param( 'number', $user->{borrowernumber} ); >+ $session->param( 'id', $user->{userid} ); >+ $session->param( 'ip', '127.0.0.1' ); >+ $session->param( 'lasttime', time() ); >+ $session->flush; >+ >+ if ( $args->{authorized} ) { >+ $dbh->do( " >+ INSERT INTO user_permissions (borrowernumber,module_bit,code) >+ VALUES (?,3,'parameters_remaining_permissions')", undef, >+ $user->{borrowernumber} ); >+ } >+ >+ return ( $user->{borrowernumber}, $session->id ); >+} >+ >+1; >-- >2.11.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 7317
:
59893
|
59894
|
59895
|
59920
|
60485
|
60486
|
60487
|
60488
|
63081
|
63082
|
63083
|
63084
|
63085
|
63086
|
63087
|
63088
|
63089
|
63090
|
63113
|
63175
|
63176
|
63354
|
63421
|
63472
|
63475
|
65469
|
65470
|
65471
|
65472
|
65473
|
65474
|
65475
|
65476
|
66266
|
67242
|
67243
|
67244
|
67245
|
67246
|
67247
|
67248
|
67249
|
67250
|
67251
|
67252
|
67253
|
67254
|
67255
|
67256
|
67257
|
67258
|
67259
|
67260
|
67261
|
67262
|
67263
|
67264
|
67281
|
67282
|
67283
|
67284
|
67285
|
67286
|
67287
|
67288
|
67289
|
67290
|
67291
|
67292
|
67293
|
67294
|
67295
|
67296
|
67297
|
67298
|
67299
|
67300
|
67301
|
67302
|
67303
|
68185
|
68186
|
68328
|
68391
|
68406
|
68407
|
68408
|
68409
|
68418
|
68444
|
68445
|
68483
|
68484
|
68485
|
68487
|
68516
|
68517
|
68518
|
68519
|
68520
|
68521
|
68522
|
68523
|
68524
|
68525
|
68542
|
68543
|
68544
|
68545
|
68546
|
68547
|
68548
|
68549
|
68550
|
68551
|
68552
|
68558
|
68559
|
68560
|
68561
|
68562
|
68563
|
68564
|
68565
|
68566
|
68567
|
68858
|
68874
|
68877
|
68878
|
68885
|
68886
|
68921
|
68922
|
68926
|
68927
|
68928
|
68929
|
68931
|
68932
|
68933
|
68988
|
68989
|
68999
|
69000
|
69006
|
69008
|
69009
|
69012
|
69014
|
69044
|
69045
|
69046
|
69056
|
69059
|
69060
|
69061
|
69063
|
69064
|
69065
|
69066
|
69069
|
69070
|
69071
|
69074