Bugzilla – Attachment 68545 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: Interlibrary loans framework for Koha.
Bug-7317-Interlibrary-loans-framework-for-Koha.patch (text/plain), 182.03 KB, created by
Tomás Cohen Arazi (tcohen)
on 2017-10-25 18:43:01 UTC
(
hide
)
Description:
Bug 7317: Interlibrary loans framework for Koha.
Filename:
MIME Type:
Creator:
Tomás Cohen Arazi (tcohen)
Created:
2017-10-25 18:43:01 UTC
Size:
182.03 KB
patch
obsolete
>From dc43d9985e021f9757bb249f79a3a8d6dbf8e89b 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: Interlibrary loans framework for Koha. > >This Commit is at the heart of adding an interlibrary loans framework >for Koha. The framework does not prescribe a particular workflow. >Instead it provides a general framework that can be extended & >implemented by individual backends whose responsibility it is to >implement a specific workflow. > >The module is largely self-sufficient: it adds new tables to the Koha >database and touches only a few files in the Koha source tree. > >Primarily, we add our files to the Makefile and the koha-conf.xml, >define ill paths for the REST API, and introduce links from the main >intranet, opac pages & user permissions. > >Outside of this we simply add new files & functionality. > >Signed-off-by: Magnus Enger <magnus@libriotech.no> >Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io> >--- > Koha/Illrequest.pm | 935 +++++++++++++++++++++ > Koha/Illrequest/Config.pm | 384 +++++++++ > Koha/Illrequestattribute.pm | 51 ++ > Koha/Illrequestattributes.pm | 55 ++ > Koha/Illrequests.pm | 97 +++ > Koha/REST/V1/Illrequests.pm | 85 ++ > Makefile.PL | 1 + > api/v1/swagger/paths.json | 3 + > api/v1/swagger/paths/illrequests.json | 98 +++ > etc/koha-conf.xml | 21 + > ill/ill-requests.pl | 252 ++++++ > koha-tmpl/intranet-tmpl/prog/css/staff-global.css | 92 ++ > .../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 | 698 +++++++++++++++ > .../intranet-tmpl/prog/en/modules/intranet-main.tt | 5 + > .../opac-tmpl/bootstrap/en/includes/usermenu.inc | 9 + > .../bootstrap/en/modules/opac-illrequests.tt | 221 +++++ > .../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/Config.t | 473 +++++++++++ > t/db_dependent/Illrequestattributes.t | 63 ++ > t/db_dependent/Illrequests.t | 792 +++++++++++++++++ > t/db_dependent/api/v1/illrequests.t | 136 +++ > 27 files changed, 4712 insertions(+), 17 deletions(-) > create mode 100644 Koha/Illrequest.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/Config.t > create mode 100644 t/db_dependent/Illrequestattributes.t > create mode 100644 t/db_dependent/Illrequests.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 0000000..12ef9aa >--- /dev/null >+++ b/Koha/Illrequest.pm >@@ -0,0 +1,935 @@ >+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::Illrequest; >+use Koha::Illrequestattributes; >+use Koha::Patron; >+use Mail::Sendmail; >+use Try::Tiny; >+ >+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'; >+} >+ >+sub illrequestattributes { >+ my ( $self ) = @_; >+ return Koha::Illrequestattributes->_new_from_dbic( >+ scalar $self->_result->illrequestattributes >+ ); >+} >+ >+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 _backend_capability >+ >+ my $backend_capability_result = $self->_backend_capability($name, $args); >+ >+This is a helper method to invoke optional capabilities in the backend. If >+the capability named by $name is not supported, return 0, else invoke it, >+passing $args along with the invocation, and return its return value. >+ >+NOTE: this module suffers from a confusion in termninology: >+ >+in _backend_capability, the notion of capability refers to an optional feature >+that is implemented in core, but might not be supported by a given backend. >+ >+in capabilities & custom_capability, capability refers to entries in the >+status_graph (after union between backend and core). >+ >+The easiest way to fix this would be to fix the terminology in >+capabilities & custom_capability and their callers. >+ >+=cut >+ >+sub _backend_capability { >+ my ( $self, $name, $args ) = @_; >+ my $capability = 0; >+ try { >+ $capability = $self->_backend->capabilities($name); >+ } catch { >+ return 0; >+ }; >+ if ( $capability ) { >+ return &{$capability}($args); >+ } else { >+ return 0; >+ } >+} >+ >+=head3 _config >+ >+ my $config = $abstract->_config($config); >+ my $config = $abstract->_config; >+ >+Getter/Setter for our config object. >+ >+=cut >+ >+sub _config { >+ my ( $self, $config ) = @_; >+ $self->{_my_config} = $config if ( $config ); >+ # Load our config object, as late as possible. >+ unless ( $self->{_my_config} ) { >+ $self->{_my_config} = Koha::Illrequest::Config->new; >+ } >+ return $self->{_my_config}; >+} >+ >+=head3 metadata >+ >+=cut >+ >+sub metadata { >+ my ( $self ) = @_; >+ return $self->_backend->metadata($self); >+} >+ >+=head3 _core_status_graph >+ >+ my $core_status_graph = $illrequest->_core_status_graph; >+ >+Returns ILL module's default status graph. A status graph defines the list of >+available actions at any stage in the ILL workflow. This is for instance used >+by the perl script & template to generate the correct buttons to display to >+the end user at any given point. >+ >+=cut >+ >+sub _core_status_graph { >+ my ( $self ) = @_; >+ return { >+ NEW => { >+ prev_actions => [ ], # Actions containing buttons >+ # leading to this status >+ id => 'NEW', # ID of this status >+ name => 'New request', # UI name of this status >+ ui_method_name => 'New request', # UI name of method leading >+ # to this status >+ method => 'create', # method to this status >+ next_actions => [ 'REQ', 'GENREQ', 'KILL' ], # buttons to add to all >+ # requests with this status >+ ui_method_icon => 'fa-plus', # UI Style class >+ }, >+ REQ => { >+ prev_actions => [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ], >+ id => 'REQ', >+ name => 'Requested', >+ ui_method_name => 'Confirm request', >+ method => 'confirm', >+ next_actions => [ 'REQREV', 'COMP' ], >+ 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 => [ '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 => [ 'NEW' ], >+ id => 'CANCREQ', >+ name => 'Cancellation requested', >+ ui_method_name => 0, >+ method => 0, >+ next_actions => [ 'KILL', 'REQ' ], >+ ui_method_icon => 0, >+ }, >+ COMP => { >+ prev_actions => [ 'REQ' ], >+ id => 'COMP', >+ name => 'Completed', >+ ui_method_name => 'Mark completed', >+ method => 'mark_completed', >+ next_actions => [ ], >+ ui_method_icon => 'fa-check', >+ }, >+ KILL => { >+ prev_actions => [ 'QUEUED', 'REQREV', 'NEW', 'CANCREQ' ], >+ id => 'KILL', >+ name => 0, >+ ui_method_name => 'Delete request', >+ method => 'delete', >+ next_actions => [ ], >+ ui_method_icon => 'fa-trash', >+ }, >+ }; >+} >+ >+=head3 _core_status_graph >+ >+ my $status_graph = $illrequest->_core_status_graph($origin, $new_graph); >+ >+Return a new status_graph, the result of merging $origin & new_graph. This is >+operation is a union over the sets defied by the two graphs. >+ >+Each entry in $new_graph is added to $origin. We do not provide a syntax for >+'subtraction' of entries from $origin. >+ >+Whilst it is not intended that this works, you can override entries in $origin >+with entries with the same key in $new_graph. This can lead to problematic >+behaviour when $new_graph adds an entry, which modifies a dependent entry in >+$origin, only for the entry in $origin to be replaced later with a new entry >+from $new_graph. >+ >+NOTE: this procedure does not "re-link" entries in $origin or $new_graph, >+i.e. each of the graphs need to be correct at the outset of the operation. >+ >+=cut >+ >+sub _status_graph_union { >+ my ( $self, $core_status_graph, $backend_status_graph ) = @_; >+ # Create new status graph with: >+ # - all core_status_graph >+ # - for-each each backend_status_graph >+ # + add to new status graph >+ # + for each core prev_action: >+ # * locate core_status >+ # * update next_actions with additional next action. >+ # + for each core next_action: >+ # * locate core_status >+ # * update prev_actions with additional prev action >+ >+ my @core_status_ids = keys %{$core_status_graph}; >+ my $status_graph = clone($core_status_graph); >+ >+ foreach my $backend_status_key ( keys %{$backend_status_graph} ) { >+ $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" } >+ >+NOTE: this module suffers from a confusion in termninology: >+ >+in _backend_capability, the notion of capability refers to an optional feature >+that is implemented in core, but might not be supported by a given backend. >+ >+in capabilities & custom_capability, capability refers to entries in the >+status_graph (after union between backend and core). >+ >+The easiest way to fix this would be to fix the terminology in >+capabilities & custom_capability and their callers. >+ >+=cut >+ >+sub capabilities { >+ my ( $self, $status ) = @_; >+ # Generate up to date status_graph >+ my $status_graph = $self->_status_graph_union( >+ $self->_core_status_graph, >+ $self->_backend->status_graph({ >+ request => $self, >+ other => {} >+ }) >+ ); >+ # Extract available actions from graph. >+ return $status_graph->{$status} if $status; >+ # Or return entire graph. >+ return $status_graph; >+} >+ >+=head3 custom_capability >+ >+Return the result of invoking $CANDIDATE on this request's backend with >+$PARAMS, or 0 if $CANDIDATE is an unknown method on backend. >+ >+NOTE: this module suffers from a confusion in termninology: >+ >+in _backend_capability, the notion of capability refers to an optional feature >+that is implemented in core, but might not be supported by a given backend. >+ >+in capabilities & custom_capability, capability refers to entries in the >+status_graph (after union between backend and core). >+ >+The easiest way to fix this would be to fix the terminology in >+capabilities & custom_capability and their callers. >+ >+=cut >+ >+sub custom_capability { >+ my ( $self, $candidate, $params ) = @_; >+ foreach my $capability ( values %{$self->capabilities} ) { >+ if ( $candidate eq $capability->{method} ) { >+ my $response = >+ $self->_backend->$candidate({ >+ request => $self, >+ other => $params, >+ }); >+ return $self->expandTemplate($response); >+ } >+ } >+ return 0; >+} >+ >+sub available_backends { >+ my ( $self ) = @_; >+ my $backend_dir = $self->_config->backend_dir; >+ my @backends = (); >+ @backends = <$backend_dir/*> if ( $backend_dir ); >+ @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 mark_completed { >+ my ( $self ) = @_; >+ $self->status('COMP')->store; >+ return { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'mark_completed', >+ stage => 'commit', >+ next => 'illview', >+ }; >+} >+ >+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 ) = @_; >+ >+ # Establish whether we need to do a generic copyright clearance. >+ if ( ( !$params->{stage} || $params->{stage} eq 'init' ) >+ && C4::Context->preference("ILLModuleCopyrightClearance") ) { >+ return { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'create', >+ stage => 'copyrightclearance', >+ value => { >+ backend => $self->_backend->name >+ } >+ }; >+ } elsif ( $params->{stage} eq 'copyrightclearance' ) { >+ $params->{stage} = 'init'; >+ } >+ >+ # First perform API action, then... >+ my $args = { >+ request => $self, >+ other => $params, >+ }; >+ my $result = $self->_backend->create($args); >+ >+ # ... simple case: we're not at 'commit' stage. >+ my $stage = $result->{stage}; >+ return $self->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 ); >+ >+ 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 getLimits >+ >+ my $limit_rules = $abstract->getLimits( { >+ type => 'brw_cat' | 'branch', >+ value => $value >+ } ); >+ >+Return the ILL limit rules for the supplied combination of type / value. >+ >+As the config may have no rules for this particular type / value combination, >+or for the default, we must define fall-back values here. >+ >+=cut >+ >+sub getLimits { >+ my ( $self, $params ) = @_; >+ my $limits = $self->_config->getLimitRules($params->{type}); >+ >+ 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 >+ >+sub check_limits { >+ my ( $self, $params ) = @_; >+ my $patron = $params->{patron}; >+ my $branchcode = $params->{librarycode} || $patron->branchcode; >+ >+ # Establish maximum number of allowed requests >+ my ( $branch_rules, $brw_rules ) = ( >+ $self->getLimits( { >+ type => 'branch', >+ value => $branchcode >+ } ), >+ $self->getLimits( { >+ type => 'brw_cat', >+ value => $patron->categorycode, >+ } ), >+ ); >+ my ( $branch_limit, $brw_limit ) >+ = ( $branch_rules->{count}, $brw_rules->{count} ); >+ # Establish currently existing requests >+ my ( $branch_count, $brw_count ) = ( >+ $self->_limit_counter( >+ $branch_rules->{method}, { branchcode => $branchcode } >+ ), >+ $self->_limit_counter( >+ $brw_rules->{method}, { borrowernumber => $patron->borrowernumber } >+ ), >+ ); >+ >+ # Compare and return >+ # A limit of -1 means no limit exists. >+ # We return blocked if either branch limit or brw limit is reached. >+ if ( ( $branch_limit != -1 && $branch_limit <= $branch_count ) >+ || ( $brw_limit != -1 && $brw_limit <= $brw_count ) ) { >+ return 0; >+ } else { >+ return 1; >+ } >+} >+ >+sub _limit_counter { >+ my ( $self, $method, $target ) = @_; >+ >+ # Establish parameters of counts >+ my $resultset; >+ if ($method && $method eq 'annual') { >+ $resultset = Koha::Illrequests->search({ >+ -and => [ >+ %{$target}, >+ \"YEAR(placed) = YEAR(NOW())" >+ ] >+ }); >+ } else { # assume 'active' >+ # XXX: This status list is ugly. There should be a method in config >+ # to return these. >+ $where = { status => { -not_in => [ 'QUEUED', 'COMP' ] } }; >+ $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 ) = @_; >+ my $branch = Koha::Libraries->find($params->{current_branchcode}) >+ || die "Invalid current branchcode. Are you logged in as the database user?"; >+ if ( !$params->{stage}|| $params->{stage} eq 'init' ) { >+ 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 @address = map { $branch->$_ } >+ qw/ branchname branchaddress1 branchaddress2 branchaddress3 >+ branchzip branchcity branchstate branchcountry branchphone >+ branchemail /; >+ my $address = ""; >+ foreach my $line ( @address ) { >+ $address .= $line . "\n" if $line; >+ } >+ >+ $draft->{body} .= $address; >+ >+ 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 >+ 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 ) = @_; >+ 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 ) = @_; >+ my $censorship = $self->_config->censorship; >+ $params->{censor_notes_staff} = $censorship->{censor_notes_staff} >+ if ( $params->{opac} ); >+ $params->{display_reply_date} = ( $censorship->{censor_reply_date} ) ? 0 : 1; >+ >+ return $params; >+} >+ >+=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 0000000..3bc78d1 >--- /dev/null >+++ b/Koha/Illrequest/Config.pm >@@ -0,0 +1,384 @@ >+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. >+ >+=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 ) = @_; >+ my $self = {}; >+ >+ $self->{configuration} = _load_configuration( >+ C4::Context->config("interlibrary_loans"), >+ C4::Context->preference("UnmediatedILL") >+ ); >+ >+ 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_code); >+ $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', >+ raw_config => $xml_config, >+ }; >+ >+ # 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} || 0; >+ $configuration->{censorship}->{censor_notes_staff} = 1 >+ if ( $staff_comments && 'hide' eq $staff_comments ); >+ my $reply_date = $xml_config->{reply_date} || 0; >+ $configuration->{censorship}->{censor_reply_date} = 1 >+ if ( $reply_date && 'hide' eq $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 of 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}; >+ die "TYPE should be either 'branch' or 'brw_cat' if ID is not 'default'." >+ if ( $id ne 'default' && ( $type ne 'branch' && $type ne 'brw_cat') ); >+ 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 0000000..16bc086 >--- /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 0000000..e05fa6e >--- /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 0000000..85cc711 >--- /dev/null >+++ b/Koha/Illrequests.pm >@@ -0,0 +1,97 @@ >+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); >+ >+ 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 0000000..0807eb1 >--- /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/Makefile.PL b/Makefile.PL >index af6ea2d..ada4224 100644 >--- a/Makefile.PL >+++ b/Makefile.PL >@@ -312,6 +312,7 @@ my $target_map = { > './etc/zebradb' => { target => 'ZEBRA_CONF_DIR', trimdir => -1 }, > './etc/pazpar2' => { target => 'PAZPAR2_CONF_DIR', trimdir => -1 }, > './help.pl' => 'INTRANET_CGI_DIR', >+ './ill' => 'INTRANET_CGI_DIR', > './installer-CPAN.pl' => 'NONE', > './installer' => 'INTRANET_CGI_DIR', > './errors' => {target => 'INTRANET_CGI_DIR'}, >diff --git a/api/v1/swagger/paths.json b/api/v1/swagger/paths.json >index 147e985..9c3a6d2 100644 >--- a/api/v1/swagger/paths.json >+++ b/api/v1/swagger/paths.json >@@ -22,5 +22,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 0000000..ddafd80 >--- /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 4ccee3a..067e978 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 0000000..26cb350 >--- /dev/null >+++ b/ill/ill-requests.pl >@@ -0,0 +1,252 @@ >+#!/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 'mark_completed' ) { >+ my $request = Koha::Illrequests->find($params->{illrequest_id}); >+ my $backend_result = $request->mark_completed($params); >+ $template->param( >+ whole => $backend_result, >+ request => $request, >+ ); >+ >+ # handle special commit rules & update type >+ handle_commit_maybe($backend_result, $request); >+ >+} 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, >+ ); >+ >+ # handle special commit rules & update type >+ handle_commit_maybe($backend_result, $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 3a26b0d..0897f32 100644 >--- a/koha-tmpl/intranet-tmpl/prog/css/staff-global.css >+++ b/koha-tmpl/intranet-tmpl/prog/css/staff-global.css >@@ -3031,3 +3031,95 @@ 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; >+} >+ >+#interlibraryloans #column-toggle, >+#interlibraryloans #reset-toggle { >+ margin: 15px 0; >+ line-height: 1.5em; >+ font-weight: 700; >+} >+ >+#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; >+} >+ >+table#ill-requests { >+ width: 100% !important; >+} >+ >+table#ill-requests th { >+ text-transform: capitalize; >+} >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 e621d64..4006f6c 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 0000000..3f541d9 >--- /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 7c6275a..4a1ce81 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 0000000..c013727 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt >@@ -0,0 +1,698 @@ >+[% USE Branches %] >+[% USE Koha %] >+ >+[% 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() { >+ >+ // Illview Datatable setup >+ >+ // Fields we don't want to display >+ var ignore = [ >+ 'accessurl', >+ 'backend', >+ 'completed', >+ 'branch', >+ 'capabilities', >+ 'cost', >+ 'medium', >+ 'notesopac', >+ 'notesstaff', >+ 'placed', >+ 'replied' >+ ]; >+ >+ // Fields we need to expand (flatten) >+ var expand = [ >+ 'metadata', >+ 'patron' >+ ]; >+ >+ // Expanded fields >+ // This is auto populated >+ var expanded = {}; >+ >+ // The core fields that should be displayed first >+ var core = [ >+ 'metadata_Author', >+ 'metadata_Title', >+ 'borrowername', >+ 'biblio_id', >+ 'branchcode', >+ 'status', >+ 'updated', >+ 'illrequest_id', >+ 'action' >+ ]; >+ >+ // Extra fields that we need to tack on to the end >+ var extra = [ 'action' ]; >+ >+ // Remove any fields we're ignoring >+ var removeIgnore = function(dataObj) { >+ dataObj.forEach(function(thisRow) { >+ ignore.forEach(function(thisIgnore) { >+ if (thisRow.hasOwnProperty(thisIgnore)) { >+ delete thisRow[thisIgnore]; >+ } >+ }); >+ }); >+ }; >+ >+ // Expand any fields we're expanding >+ var expandExpand = function(row) { >+ expand.forEach(function(thisExpand) { >+ if (row.hasOwnProperty(thisExpand)) { >+ if (!expanded.hasOwnProperty(thisExpand)) { >+ expanded[thisExpand] = []; >+ } >+ var expandObj = row[thisExpand]; >+ Object.keys(expandObj).forEach( >+ function(thisExpandCol) { >+ var expColName = thisExpand + '_' + thisExpandCol; >+ // Keep a list of fields that have been expanded >+ // so we can create toggle links for them >+ if (expanded[thisExpand].indexOf(expColName) == -1) { >+ expanded[thisExpand].push(expColName); >+ } >+ expandObj[expColName] = >+ expandObj[thisExpandCol]; >+ delete expandObj[thisExpandCol]; >+ } >+ ); >+ $.extend(true, row, expandObj); >+ delete row[thisExpand]; >+ } >+ }); >+ }; >+ >+ // Build a de-duped list of all column names >+ var allCols = {}; >+ core.map(function(thisCore) { >+ allCols[thisCore] = 1; >+ }); >+ var unionColumns = function(row) { >+ Object.keys(row).forEach(function(col) { >+ if (ignore.indexOf(col) == -1) { >+ allCols[col] = 1; >+ } >+ }); >+ }; >+ >+ // Some rows may not have fields that other rows have, >+ // so make sure all rows have the same fields >+ var fillMissing = function(row) { >+ Object.keys(allCols).forEach(function(thisCol) { >+ row[thisCol] = (!row.hasOwnProperty(thisCol)) ? >+ null : >+ row[thisCol]; >+ }); >+ } >+ >+ // Strip the expand prefix if it exists, we do this for display >+ var stripPrefix = function(value) { >+ expand.forEach(function(thisExpand) { >+ var regex = new RegExp(thisExpand + '_', 'g'); >+ value = value.replace(regex, ''); >+ }); >+ return value; >+ }; >+ >+ // Our 'render' function for borrowerlink >+ var createBorrowerLink = 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>'; >+ }; >+ >+ // Render function for request ID >+ var createRequestId = function(data, type, row) { >+ return row.id_prefix + row.illrequest_id; >+ }; >+ >+ // Render function for request status >+ var createStatus = function(data, type, row, meta) { >+ var origData = meta.settings.oInit.originalData; >+ if (origData.length > 0) { >+ return meta.settings.oInit.originalData[0].capabilities[ >+ row.status >+ ].name; >+ } else { >+ return ''; >+ } >+ }; >+ >+ // Render function for creating a row's action link >+ var createActionLink = 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>' >+ }; >+ >+ // Columns that require special treatment >+ var specialCols = { >+ action: { >+ name: '', >+ func: createActionLink >+ }, >+ borrowername: { >+ name: 'Borrower', >+ func: createBorrowerLink >+ }, >+ illrequest_id: { >+ name: 'Request number', >+ func: createRequestId >+ }, >+ status: { >+ name: 'Status', >+ func: createStatus >+ }, >+ biblio_id: { >+ name: 'Biblio number' >+ }, >+ branchcode: { >+ name: 'Branch code' >+ } >+ }; >+ >+ // Helper for handling prefilter column names >+ function toColumnName(myVal) { >+ return myVal >+ .replace(/^filter/, '') >+ .replace(/([A-Z])/g, "_$1") >+ .replace(/^_/,'').toLowerCase(); >+ }; >+ >+ // Toggle request attributes in Illview >+ $('#toggle_requestattributes').click(function() { >+ $('#requestattributes').toggleClass('content_hidden'); >+ }); >+ >+ // Filter partner list >+ $('#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(); >+ } >+ }); >+ }); >+ >+ // Get our data from the API and process it prior to passing >+ // it to datatables >+ var ajax = $.ajax( >+ '/api/v1/illrequests?embed=metadata,patron,capabilities,branch' >+ ).done(function() { >+ var data = JSON.parse(ajax.responseText); >+ // Make a copy, we'll be removing columns next and need >+ // to be able to refer to data that has been removed >+ var dataCopy = $.extend(true, [], data); >+ // Remove all columns we're not interested in >+ removeIgnore(dataCopy); >+ // Expand columns that need it and create an array >+ // of all column names >+ $.each(dataCopy, function(k, row) { >+ expandExpand(row); >+ unionColumns(row); >+ }); >+ // Append any extra columns we need to tag on >+ if (extra.length > 0) { >+ extra.forEach(function(thisExtra) { >+ allCols[thisExtra] = 1; >+ }); >+ }; >+ // Different requests will have different columns, >+ // make sure they all have the same >+ $.each(dataCopy, function(k, row) { >+ fillMissing(row); >+ }); >+ >+ // Assemble an array of column definitions for passing >+ // to datatables >+ var colData = []; >+ Object.keys(allCols).forEach(function(thisCol) { >+ // We may have defined a pretty name for this column >+ var colName = ( >+ specialCols.hasOwnProperty(thisCol) && >+ specialCols[thisCol].hasOwnProperty('name') >+ ) ? >+ specialCols[thisCol].name : >+ thisCol; >+ // Create the table header for this column >+ var str = '<th>' + stripPrefix(colName) + '</th>'; >+ $(str).appendTo('#illview-header'); >+ // Create the base column object >+ var colObj = { >+ name: thisCol, >+ className: thisCol >+ }; >+ // We may need to process the data going in this >+ // column, so do it if necessary >+ if ( >+ specialCols.hasOwnProperty(thisCol) && >+ specialCols[thisCol].hasOwnProperty('func') >+ ) { >+ colObj.render = specialCols[thisCol].func; >+ } else { >+ colObj.data = thisCol >+ } >+ colData.push(colObj); >+ }); >+ >+ // Create the toggle links for all metadata fields >+ var links = []; >+ expanded.metadata.forEach(function(thisExpanded) { >+ if (core.indexOf(thisExpanded) == -1) { >+ links.push( >+ '<a href="#" class="toggle-vis" data-column="' + >+ thisExpanded + '">' + stripPrefix(thisExpanded) + >+ '</a>' >+ ); >+ } >+ }); >+ $('#column-toggle').append(links.join(' | ')); >+ >+ // Initialise the datatable >+ var myTable = $('#ill-requests').DataTable($.extend(true, {}, dataTablesDefaults, { >+ aoColumnDefs: [ // Last column shouldn't be sortable or searchable >+ { >+ aTargets: [ 'action' ], >+ bSortable: false, >+ bSearchable: false >+ }, >+ ], >+ aaSorting: [[ 6, '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 >+ deferRender: true, // Improve performance on big datasets >+ data: dataCopy, >+ columns: colData, >+ originalData: data // Enable render functions to access >+ // our original data >+ })); >+ >+ // Reset columns to default >+ var resetColumns = function() { >+ Object.keys(allCols).forEach(function(thisCol) { >+ myTable.column(thisCol + ':name').visible(core.indexOf(thisCol) != -1); >+ }); >+ myTable.columns.adjust().draw(false); >+ }; >+ >+ // Handle the click event on a toggle link >+ $('a.toggle-vis').on('click', function(e) { >+ e.preventDefault(); >+ var column = myTable.column( >+ $(this).data('column') + ':name' >+ ); >+ column.visible(!column.visible()); >+ }); >+ >+ // Reset column toggling >+ $('#reset-toggle').click(function() { >+ resetColumns(); >+ }); >+ >+ // Handle a prefilter request and do the prefiltering >+ 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 filterName = toColumnName(thisFilter) + ':name'; >+ var regex = '^'+filters[thisFilter]+'$'; >+ console.log(regex); >+ myTable.columns(filterName).search(regex, true, false); >+ }); >+ myTable.draw(); >+ } >+ >+ // Initialise column hiding >+ resetColumns(); >+ >+ } >+ ); >+ >+ }); >+ //]]> >+</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> >+ <p> >+ <pre>[% whole.message %] ([% whole.status %])</pre> >+ </p> >+ [% END %] >+ >+ [% IF query_type == 'create' %] >+ <h1>New ILL request</h1> >+ [% IF whole.stage == 'copyrightclearance' %] >+ <div> >+ <p> >+ [% Koha.Preference('ILLModuleCopyrightClearance') %] >+ </p> >+ <a href="?method=create&stage=copyrightclearance&backend=[% whole.value.backend %]" >+ class="btn btn-sm btn-default btn-group"><i class="fa fa-check">Yes</i></a> >+ <a href="/cgi-bin/koha/ill/ill-requests.pl" >+ class="btn btn-sm btn-default btn-group"><i class="fa fa-times">No</i></a> >+ </div> >+ [% ELSE %] >+ [% PROCESS $whole.template %] >+ [% END %] >+ >+ [% ELSIF query_type == 'confirm' %] >+ <h1>Confirm ILL request</h1> >+ [% PROCESS $whole.template %] >+ >+ [% ELSIF query_type == 'cancel' and !whole.error %] >+ <h1>Cancel a 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> >+ >+ <div id="column-toggle"> >+ Toggle additional columns: >+ </div> >+ <div id="reset-toggle"><a href="#">Reset toggled columns</a></div> >+ >+ <table >+ [% FOREACH filter IN prefilters %] >+ data-filter-[% filter.name %]="[% filter.value %]" >+ [% END %] >+ id="ill-requests"> >+ <thead> >+ <tr id="illview-header"></tr> >+ </thead> >+ <tbody id="illview-body"> >+ </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 2a69dd9..32a1409 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 9afa331..7d27f22 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 0000000..e0448c1 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt >@@ -0,0 +1,221 @@ >+[% 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>Request placed</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.placed %]</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 == "NEW" %] >+ <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 8595f9a..eaa20b3 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 a5a7c70..11cf98e 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 63961e8..c4e55c2 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 0000000..9ff413a >--- /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/Config.t b/t/db_dependent/Illrequest/Config.t >new file mode 100644 >index 0000000..f44fa85 >--- /dev/null >+++ b/t/db_dependent/Illrequest/Config.t >@@ -0,0 +1,473 @@ >+#!/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 Koha::Database; >+use t::lib::Mocks; >+use t::lib::TestBuilder; >+use Test::MockObject; >+use Test::Exception; >+ >+use Test::More tests => 5; >+ >+my $schema = Koha::Database->new->schema; >+my $builder = t::lib::TestBuilder->new; >+use_ok('Koha::Illrequest::Config'); >+ >+my $base_limits = { >+ branch => { CPL => { count => 1, method => 'annual' } }, >+ brw_cat => { A => { count => -1, method => 'active' } }, >+ default => { count => 10, method => 'annual' }, >+}; >+ >+my $base_censorship = { censor_notes_staff => 1, censor_reply_date => 1 }; >+ >+subtest 'Basics' => sub { >+ >+ plan tests => 19; >+ >+ $schema->storage->txn_begin; >+ >+ t::lib::Mocks::mock_preference("UnmediatedILL", 0); >+ t::lib::Mocks::mock_config("interlibrary_loans", {}); >+ >+ my $config = Koha::Illrequest::Config->new; >+ isa_ok($config, "Koha::Illrequest::Config", >+ "Correctly create and load a config object."); >+ >+ # backend: >+ is($config->backend, undef, "backend: Undefined backend is undefined."); >+ is($config->backend("Mock"), "Mock", "backend: setter works."); >+ is($config->backend, "Mock", "backend: setter is persistent."); >+ >+ # backend_dir: >+ is($config->backend_dir, undef, "backend_dir: Undefined backend_dir is undefined."); >+ is($config->backend_dir("/tmp/"), "/tmp/", "backend_dir: setter works."); >+ is($config->backend_dir, "/tmp/", "backend_dir: setter is persistent."); >+ >+ # partner_code: >+ is($config->partner_code, "ILLLIBS", "partner_code: Undefined partner_code is undefined."); >+ is($config->partner_code("ILLLIBSTST"), "ILLLIBSTST", "partner_code: setter works."); >+ is($config->partner_code, "ILLLIBSTST", "partner_code: setter is persistent."); >+ >+ # limits: >+ is_deeply($config->limits, {}, "limits: Undefined limits is empty hash."); >+ is_deeply($config->limits($base_limits), $base_limits, "limits: setter works."); >+ is_deeply($config->limits, $base_limits, "limits: setter is persistent."); >+ >+ # censorship: >+ is_deeply($config->censorship, { censor_notes_staff => 0, censor_reply_date => 0 }, >+ "censorship: Undefined censorship is default values."); >+ is_deeply($config->censorship($base_censorship), $base_censorship, "censorship: setter works."); >+ is_deeply($config->censorship, $base_censorship, "censorship: setter is persistent."); >+ >+ # getLimitRules >+ dies_ok( sub { $config->getLimitRules("FOO") }, "getLimitRules: die if not correct type."); >+ is_deeply($config->getLimitRules("brw_cat"), { >+ A => { count => -1, method => 'active' }, >+ default => { count => 10, method => 'annual' }, >+ }, "getLimitRules: fetch brw_cat limits."); >+ is_deeply($config->getLimitRules("branch"), { >+ CPL => { count => 1, method => 'annual' }, >+ default => { count => 10, method => 'annual' }, >+ }, "getLimitRules: fetch brw_cat limits."); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+# _load_unit_config: >+ >+subtest '_load_unit_config' => sub { >+ >+ plan tests => 10; >+ >+ $schema->storage->txn_begin; >+ >+ my $config = Koha::Illrequest::Config->new; >+ >+ dies_ok( >+ sub { Koha::Illrequest::Config::_load_unit_config({ >+ id => 'durineadu', type => 'baz' >+ }) }, >+ "_load_unit_config: die if ID is not default, and type is not branch or brw_cat." >+ ); >+ is_deeply( >+ Koha::Illrequest::Config::_load_unit_config({ >+ unit => {}, id => 'default', config => {}, test => 1 >+ }), {}, "_load_unit_config: invocation without id returns unmodified config." >+ ); >+ >+ is_deeply( >+ Koha::Illrequest::Config::_load_unit_config({ >+ unit => { api_key => 'foo', api_auth => 'bar' }, >+ id => "CPL", type => 'branch', config => {} >+ }), >+ { credentials => { api_keys => { CPL => { api_key => 'foo', api_auth => 'bar' } } } }, >+ "_load_unit_config: add auth values." >+ ); >+ >+ # Populate request_limits >+ is_deeply( >+ Koha::Illrequest::Config::_load_unit_config({ >+ unit => { request_limit => [ 'heelo', 1234 ] }, >+ id => "CPL", type => 'branch', config => {} >+ }), {}, "_load_unit_config: invalid request_limit structure." >+ ); >+ is_deeply( >+ Koha::Illrequest::Config::_load_unit_config({ >+ unit => { request_limit => { method => 'eudiren', count => '-5465' } }, >+ id => "CPL", type => 'branch', config => {} >+ }), {}, "_load_unit_config: invalid method & count." >+ ); >+ is_deeply( >+ Koha::Illrequest::Config::_load_unit_config({ >+ unit => { request_limit => { method => 'annual', count => 6 } }, >+ id => "default", config => {} >+ }), >+ { limits => { default => { method => 'annual', count => 6 } } }, >+ "_load_unit_config: correct default request_limits." >+ ); >+ >+ # Populate prefix >+ is_deeply( >+ Koha::Illrequest::Config::_load_unit_config({ >+ unit => { prefix => 'Foo-ill' }, >+ id => "default", config => {} >+ }), >+ { prefixes => { default => 'Foo-ill' } }, >+ "_load_unit_config: correct default prefix." >+ ); >+ is_deeply( >+ Koha::Illrequest::Config::_load_unit_config({ >+ unit => { prefix => 'Foo-ill' }, >+ id => "A", config => {}, type => 'brw_cat' >+ }), >+ { prefixes => { brw_cat => { A => 'Foo-ill' } } }, >+ "_load_unit_config: correct brw_cat prefix." >+ ); >+ >+ # Populate digital_recipient >+ is_deeply( >+ Koha::Illrequest::Config::_load_unit_config({ >+ unit => { digital_recipient => 'borrower' }, >+ id => "default", config => {} >+ }), >+ { digital_recipients => { default => 'borrower' } }, >+ "_load_unit_config: correct default digital_recipient." >+ ); >+ is_deeply( >+ Koha::Illrequest::Config::_load_unit_config({ >+ unit => { digital_recipient => 'branch' }, >+ id => "A", config => {}, type => 'brw_cat' >+ }), >+ { digital_recipients => { brw_cat => { A => 'branch' } } }, >+ "_load_unit_config: correct brw_cat digital_recipient." >+ ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+# _load_configuration: >+ >+# We have already tested _load_unit_config, so we are reasonably confident >+# that the per-branch, per-borrower_category & default sections parsing is >+# good. >+# >+# Now we need to ensure that Arrays & Hashes are handled correctly, that >+# censorship & ill partners are loaded correctly and that the backend >+# directory is set correctly. >+ >+subtest '_load_configuration' => sub { >+ >+ plan tests => 9; >+ >+ $schema->storage->txn_begin; >+ >+ my $config = Koha::Illrequest::Config->new; >+ >+ # Return basic configuration >+ is_deeply( >+ Koha::Illrequest::Config::_load_configuration({}, 0), >+ { >+ backend_directory => undef, >+ censorship => { >+ censor_notes_staff => 0, >+ censor_reply_date => 0, >+ }, >+ limits => {}, >+ digital_recipients => {}, >+ prefixes => {}, >+ partner_code => 'ILLLIBS', >+ raw_config => {}, >+ }, >+ "load_configuration: return the base configuration." >+ ); >+ >+ # Return correct backend_dir >+ is_deeply( >+ Koha::Illrequest::Config::_load_configuration({ backend_directory => '/tmp/' }, 0), >+ { >+ backend_directory => '/tmp/', >+ censorship => { >+ censor_notes_staff => 0, >+ censor_reply_date => 0, >+ }, >+ limits => {}, >+ digital_recipients => {}, >+ prefixes => {}, >+ partner_code => 'ILLLIBS', >+ raw_config => { backend_directory => '/tmp/' }, >+ }, >+ "load_configuration: return the correct backend_dir." >+ ); >+ >+ # Map over branch configs >+ my $xml_config = { >+ backend_directory => '/tmp/', >+ branch => [ >+ { code => '1', request_limit => { method => 'annual', count => 1 } }, >+ { code => '2', prefix => '2-prefix' }, >+ { code => '3', digital_recipient => 'branch' } >+ ] >+ }; >+ is_deeply( >+ Koha::Illrequest::Config::_load_configuration($xml_config, 0), >+ { >+ backend_directory => '/tmp/', >+ censorship => { >+ censor_notes_staff => 0, >+ censor_reply_date => 0, >+ }, >+ limits => { branch => { 1 => { method => 'annual', count => 1 } } }, >+ digital_recipients => { branch => { 3 => 'branch' } }, >+ prefixes => { branch => { 2 => '2-prefix' } }, >+ partner_code => 'ILLLIBS', >+ raw_config => $xml_config, >+ }, >+ "load_configuration: multi branch config parsed correctly." >+ ); >+ # Single branch config >+ $xml_config = { >+ backend_directory => '/tmp/', >+ branch => { >+ code => '1', >+ request_limit => { method => 'annual', count => 1 }, >+ prefix => '2-prefix', >+ digital_recipient => 'branch', >+ } >+ }; >+ is_deeply( >+ Koha::Illrequest::Config::_load_configuration($xml_config, 0), >+ { >+ backend_directory => '/tmp/', >+ censorship => { >+ censor_notes_staff => 0, >+ censor_reply_date => 0, >+ }, >+ limits => { branch => { 1 => { method => 'annual', count => 1 } } }, >+ digital_recipients => { branch => { 1 => 'branch' } }, >+ prefixes => { branch => { 1 => '2-prefix' } }, >+ partner_code => 'ILLLIBS', >+ raw_config => $xml_config, >+ }, >+ "load_configuration: single branch config parsed correctly." >+ ); >+ >+ # Map over borrower_category settings >+ $xml_config = { >+ backend_directory => '/tmp/', >+ borrower_category => [ >+ { code => 'A', request_limit => { method => 'annual', count => 1 } }, >+ { code => 'B', prefix => '2-prefix' }, >+ { code => 'C', digital_recipient => 'branch' } >+ ] >+ }; >+ is_deeply( >+ Koha::Illrequest::Config::_load_configuration($xml_config, 0), >+ { >+ backend_directory => '/tmp/', >+ censorship => { >+ censor_notes_staff => 0, >+ censor_reply_date => 0, >+ }, >+ limits => { brw_cat => { A => { method => 'annual', count => 1 } } }, >+ digital_recipients => { brw_cat => { C => 'branch' } }, >+ prefixes => { brw_cat => { B => '2-prefix' } }, >+ partner_code => 'ILLLIBS', >+ raw_config => $xml_config, >+ }, >+ "load_configuration: multi borrower_category config parsed correctly." >+ ); >+ # Single borrower_category config >+ $xml_config = { >+ backend_directory => '/tmp/', >+ borrower_category => { >+ code => '1', >+ request_limit => { method => 'annual', count => 1 }, >+ prefix => '2-prefix', >+ digital_recipient => 'branch', >+ } >+ }; >+ is_deeply( >+ Koha::Illrequest::Config::_load_configuration($xml_config, 0), >+ { >+ backend_directory => '/tmp/', >+ censorship => { >+ censor_notes_staff => 0, >+ censor_reply_date => 0, >+ }, >+ limits => { brw_cat => { 1 => { method => 'annual', count => 1 } } }, >+ digital_recipients => { brw_cat => { 1 => 'branch' } }, >+ prefixes => { brw_cat => { 1 => '2-prefix' } }, >+ partner_code => 'ILLLIBS', >+ raw_config => $xml_config, >+ }, >+ "load_configuration: single borrower_category config parsed correctly." >+ ); >+ >+ # Default Configuration >+ $xml_config = { >+ backend_directory => '/tmp/', >+ request_limit => { method => 'annual', count => 1 }, >+ prefix => '2-prefix', >+ digital_recipient => 'branch', >+ }; >+ is_deeply( >+ Koha::Illrequest::Config::_load_configuration($xml_config, 0), >+ { >+ backend_directory => '/tmp/', >+ censorship => { >+ censor_notes_staff => 0, >+ censor_reply_date => 0, >+ }, >+ limits => { default => { method => 'annual', count => 1 } }, >+ digital_recipients => { default => 'branch' }, >+ prefixes => { default => '2-prefix' }, >+ partner_code => 'ILLLIBS', >+ raw_config => $xml_config, >+ }, >+ "load_configuration: parse the default configuration." >+ ); >+ >+ # Censorship >+ $xml_config = { >+ backend_directory => '/tmp/', >+ staff_request_comments => 'hide', >+ reply_date => 'hide' >+ }; >+ is_deeply( >+ Koha::Illrequest::Config::_load_configuration($xml_config, 0), >+ { >+ backend_directory => '/tmp/', >+ censorship => { >+ censor_notes_staff => 1, >+ censor_reply_date => 1, >+ }, >+ limits => {}, >+ digital_recipients => {}, >+ prefixes => {}, >+ partner_code => 'ILLLIBS', >+ raw_config => $xml_config, >+ }, >+ "load_configuration: parse censorship settings configuration." >+ ); >+ >+ # Partner library category >+ is_deeply( >+ Koha::Illrequest::Config::_load_configuration({ partner_code => 'FOOBAR' }), >+ { >+ backend_directory => undef, >+ censorship => { >+ censor_notes_staff => 0, >+ censor_reply_date => 0, >+ }, >+ limits => {}, >+ digital_recipients => {}, >+ prefixes => {}, >+ partner_code => 'FOOBAR', >+ raw_config => { partner_code => 'FOOBAR' }, >+ }, >+ "load_configuration: Set partner code." >+ ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+ >+subtest 'Final tests' => sub { >+ >+ plan tests => 10; >+ >+ $schema->storage->txn_begin; >+ >+ t::lib::Mocks::mock_preference("UnmediatedILL", 0); >+ t::lib::Mocks::mock_config("interlibrary_loans", {}); >+ >+ my $config = Koha::Illrequest::Config->new; >+ >+ # getPrefixes (error & undef): >+ dies_ok( sub { $config->getPrefixes("FOO") }, "getPrefixes: die if not correct type."); >+ is_deeply($config->getPrefixes("brw_cat"), { default => undef}, >+ "getPrefixes: Undefined brw_cat prefix is undefined."); >+ is_deeply($config->getPrefixes("branch"), { default => undef}, >+ "getPrefixes: Undefined branch prefix is undefined."); >+ >+ # getDigitalRecipients (error & undef): >+ dies_ok( sub { $config->getDigitalRecipients("FOO") }, >+ "getDigitalRecipients: die if not correct type."); >+ is_deeply($config->getDigitalRecipients("brw_cat"), { default => undef}, >+ "getDigitalRecipients: Undefined brw_cat dig rec is undefined."); >+ is_deeply($config->getDigitalRecipients("branch"), { default => undef}, >+ "getDigitalRecipients: Undefined branch dig rec is undefined."); >+ >+ $config->{configuration} = Koha::Illrequest::Config::_load_configuration({ >+ backend_directory => '/tmp/', >+ prefix => 'DEFAULT-prefix', >+ digital_recipient => 'branch', >+ borrower_category => [ >+ { code => 'B', prefix => '2-prefix' }, >+ { code => 'C', digital_recipient => 'branch' } >+ ], >+ branch => [ >+ { code => '1', prefix => 'T-prefix' }, >+ { code => '2', digital_recipient => 'borrower' } >+ ] >+ }, 0 >+ ); >+ >+ # getPrefixes (values): >+ is_deeply($config->getPrefixes("brw_cat"), >+ { B => '2-prefix', default => 'DEFAULT-prefix' }, >+ "getPrefixes: return configuration brw_cat prefixes."); >+ is_deeply($config->getPrefixes("branch"), >+ { 1 => 'T-prefix', default => 'DEFAULT-prefix' }, >+ "getPrefixes: return configuration branch prefixes."); >+ >+ # getDigitalRecipients (values): >+ is_deeply($config->getDigitalRecipients("brw_cat"), >+ { C => 'branch', default => 'branch' }, >+ "getDigitalRecipients: return brw_cat digital_recipients."); >+ is_deeply($config->getDigitalRecipients("branch"), >+ { 2 => 'borrower', default => 'branch' }, >+ "getDigitalRecipients: return branch digital_recipients."); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+ >+1; >diff --git a/t/db_dependent/Illrequestattributes.t b/t/db_dependent/Illrequestattributes.t >new file mode 100644 >index 0000000..ceb0474 >--- /dev/null >+++ b/t/db_dependent/Illrequestattributes.t >@@ -0,0 +1,63 @@ >+#!/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::Patrons; >+use t::lib::TestBuilder; >+ >+use Test::More tests => 3; >+ >+my $schema = Koha::Database->new->schema; >+use_ok('Koha::Illrequestattribute'); >+use_ok('Koha::Illrequestattributes'); >+ >+subtest 'Basic object tests' => sub { >+ >+ plan tests => 5; >+ >+ $schema->storage->txn_begin; >+ >+ my $builder = t::lib::TestBuilder->new; >+ >+ my $illrqattr = $builder->build({ source => 'Illrequestattribute' }); >+ >+ my $illrqattr_obj = Koha::Illrequestattributes->find( >+ $illrqattr->{illrequest_id}, >+ $illrqattr->{type} >+ ); >+ isa_ok($illrqattr_obj, 'Koha::Illrequestattribute', >+ "Correctly create and load an illrequestattribute object."); >+ is($illrqattr_obj->illrequest_id, $illrqattr->{illrequest_id}, >+ "Illrequest_id getter works."); >+ is($illrqattr_obj->type, $illrqattr->{type}, >+ "Type getter works."); >+ is($illrqattr_obj->value, $illrqattr->{value}, >+ "Value getter works."); >+ >+ $illrqattr_obj->delete; >+ >+ is(Koha::Illrequestattributes->search->count, 0, >+ "No attributes found after delete."); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+1; >diff --git a/t/db_dependent/Illrequests.t b/t/db_dependent/Illrequests.t >new file mode 100644 >index 0000000..34b9eda >--- /dev/null >+++ b/t/db_dependent/Illrequests.t >@@ -0,0 +1,792 @@ >+#!/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::Illrequest::Config; >+use Koha::Patrons; >+use t::lib::Mocks; >+use t::lib::TestBuilder; >+use Test::MockObject; >+use Test::Exception; >+ >+use Test::More tests => 10; >+ >+my $schema = Koha::Database->new->schema; >+my $builder = t::lib::TestBuilder->new; >+use_ok('Koha::Illrequest'); >+use_ok('Koha::Illrequests'); >+ >+subtest 'Basic object tests' => sub { >+ >+ plan tests => 21; >+ >+ $schema->storage->txn_begin; >+ >+ my $illrq = $builder->build({ source => 'Illrequest' }); >+ my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id}); >+ >+ isa_ok($illrq_obj, 'Koha::Illrequest', >+ "Correctly create and load an illrequest object."); >+ isa_ok($illrq_obj->_config, 'Koha::Illrequest::Config', >+ "Created a config object as part of Illrequest creation."); >+ >+ is($illrq_obj->illrequest_id, $illrq->{illrequest_id}, >+ "Illrequest_id getter works."); >+ is($illrq_obj->borrowernumber, $illrq->{borrowernumber}, >+ "Borrowernumber getter works."); >+ is($illrq_obj->biblio_id, $illrq->{biblio_id}, >+ "Biblio_Id getter works."); >+ is($illrq_obj->branchcode, $illrq->{branchcode}, >+ "Branchcode getter works."); >+ is($illrq_obj->status, $illrq->{status}, >+ "Status getter works."); >+ is($illrq_obj->placed, $illrq->{placed}, >+ "Placed getter works."); >+ is($illrq_obj->replied, $illrq->{replied}, >+ "Replied getter works."); >+ is($illrq_obj->updated, $illrq->{updated}, >+ "Updated getter works."); >+ is($illrq_obj->completed, $illrq->{completed}, >+ "Completed getter works."); >+ is($illrq_obj->medium, $illrq->{medium}, >+ "Medium getter works."); >+ is($illrq_obj->accessurl, $illrq->{accessurl}, >+ "Accessurl getter works."); >+ is($illrq_obj->cost, $illrq->{cost}, >+ "Cost getter works."); >+ is($illrq_obj->notesopac, $illrq->{notesopac}, >+ "Notesopac getter works."); >+ is($illrq_obj->notesstaff, $illrq->{notesstaff}, >+ "Notesstaff getter works."); >+ is($illrq_obj->orderid, $illrq->{orderid}, >+ "Orderid getter works."); >+ is($illrq_obj->backend, $illrq->{backend}, >+ "Backend getter works."); >+ >+ isnt($illrq_obj->status, 'COMP', >+ "ILL is not currently marked complete."); >+ $illrq_obj->mark_completed; >+ is($illrq_obj->status, 'COMP', >+ "ILL is now marked complete."); >+ >+ $illrq_obj->delete; >+ >+ is(Koha::Illrequests->search->count, 0, >+ "No illrequest found after delete."); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'Working with related objects' => sub { >+ >+ plan tests => 5; >+ >+ $schema->storage->txn_begin; >+ >+ my $patron = $builder->build({ source => 'Borrower' }); >+ my $illrq = $builder->build({ >+ source => 'Illrequest', >+ value => { borrowernumber => $patron->{borrowernumber} } >+ }); >+ my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id}); >+ >+ isa_ok($illrq_obj->patron, 'Koha::Patron', >+ "OK accessing related patron."); >+ >+ $builder->build({ >+ source => 'Illrequestattribute', >+ value => { illrequest_id => $illrq_obj->illrequest_id, type => 'X' } >+ }); >+ $builder->build({ >+ source => 'Illrequestattribute', >+ value => { illrequest_id => $illrq_obj->illrequest_id, type => 'Y' } >+ }); >+ $builder->build({ >+ source => 'Illrequestattribute', >+ value => { illrequest_id => $illrq_obj->illrequest_id, type => 'Z' } >+ }); >+ >+ is($illrq_obj->illrequestattributes->count, Koha::Illrequestattributes->search->count, >+ "Fetching expected number of Illrequestattributes for our request."); >+ >+ my $illrq1 = $builder->build({ source => 'Illrequest' }); >+ $builder->build({ >+ source => 'Illrequestattribute', >+ value => { illrequest_id => $illrq1->{illrequest_id}, type => 'X' } >+ }); >+ >+ is($illrq_obj->illrequestattributes->count + 1, Koha::Illrequestattributes->search->count, >+ "Fetching expected number of Illrequestattributes for our request."); >+ >+ $illrq_obj->delete; >+ is(Koha::Illrequestattributes->search->count, 1, >+ "Correct number of illrequestattributes after delete."); >+ >+ isa_ok(Koha::Patrons->find($patron->{borrowernumber}), 'Koha::Patron', >+ "Borrower was not deleted after illrq delete."); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'Status Graph tests' => sub { >+ >+ plan tests => 4; >+ >+ $schema->storage->txn_begin; >+ >+ my $illrq = $builder->build({source => 'Illrequest'}); >+ my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id}); >+ >+ # _core_status_graph tests: it's just a constant, so here we just make >+ # sure it returns a hashref. >+ is(ref $illrq_obj->_core_status_graph, "HASH", >+ "_core_status_graph returns a hash."); >+ >+ # _status_graph_union: let's try different merge operations. >+ # Identity operation >+ is_deeply( >+ $illrq_obj->_status_graph_union($illrq_obj->_core_status_graph, {}), >+ $illrq_obj->_core_status_graph, >+ "core_status_graph + null = core_status_graph" >+ ); >+ >+ # Simple addition >+ is_deeply( >+ $illrq_obj->_status_graph_union({}, $illrq_obj->_core_status_graph), >+ $illrq_obj->_core_status_graph, >+ "null + core_status_graph = core_status_graph" >+ ); >+ >+ # Correct merge behaviour >+ is_deeply( >+ $illrq_obj->_status_graph_union({ >+ REQ => { >+ prev_actions => [ ], >+ id => 'REQ', >+ next_actions => [ ], >+ }, >+ }, { >+ QER => { >+ prev_actions => [ 'REQ' ], >+ id => 'QER', >+ next_actions => [ 'REQ' ], >+ }, >+ }), >+ { >+ REQ => { >+ prev_actions => [ 'QER' ], >+ id => 'REQ', >+ next_actions => [ 'QER' ], >+ }, >+ QER => { >+ prev_actions => [ 'REQ' ], >+ id => 'QER', >+ next_actions => [ 'REQ' ], >+ }, >+ }, >+ "REQ atom + linking QER = cyclical status graph" >+ ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'Backend testing (mocks)' => sub { >+ >+ plan tests => 13; >+ >+ $schema->storage->txn_begin; >+ >+ # testing load_backend & available_backends requires that we have at least >+ # the Dummy plugin installed. load_backend & available_backends don't >+ # currently have tests as a result. >+ >+ my $backend = Test::MockObject->new; >+ $backend->set_isa('Koha::Illbackends::Mock'); >+ $backend->set_always('name', 'Mock'); >+ >+ my $patron = $builder->build({ source => 'Borrower' }); >+ my $illrq = $builder->build({ >+ source => 'Illrequest', >+ value => { borrowernumber => $patron->{borrowernumber} } >+ }); >+ my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id}); >+ >+ $illrq_obj->_backend($backend); >+ >+ isa_ok($illrq_obj->_backend, 'Koha::Illbackends::Mock', >+ "OK accessing mocked backend."); >+ >+ # _backend_capability tests: >+ # We need to test whether this optional feature of a mocked backend >+ # behaves as expected. >+ # 3 scenarios: feature not implemented, feature implemented, but requested >+ # capability is not provided by backend, & feature is implemented & >+ # capability exists. This method can be used to implement custom backend >+ # functionality, such as unmediated in the BLDSS backend (also see >+ # bugzilla 18837). >+ $backend->set_always('capabilities', undef); >+ is($illrq_obj->_backend_capability('Test'), 0, >+ "0 returned on Mock not implementing capabilities."); >+ >+ $backend->set_always('capabilities', 0); >+ is($illrq_obj->_backend_capability('Test'), 0, >+ "0 returned on Mock not implementing Test capability."); >+ >+ $backend->set_always('capabilities', sub { return 'bar'; } ); >+ is($illrq_obj->_backend_capability('Test'), 'bar', >+ "'bar' returned on Mock implementing Test capability."); >+ >+ # metadata test: we need to be sure that we return the arbitrary values >+ # from the backend. >+ $backend->mock( >+ 'metadata', >+ sub { >+ my ( $self, $rq ) = @_; >+ return { >+ ID => $rq->illrequest_id, >+ Title => $rq->patron->borrowernumber >+ } >+ } >+ ); >+ >+ is_deeply( >+ $illrq_obj->metadata, >+ { >+ ID => $illrq_obj->illrequest_id, >+ Title => $illrq_obj->patron->borrowernumber >+ }, >+ "Test metadata." >+ ); >+ >+ # capabilities: >+ >+ # No backend graph extension >+ $backend->set_always('status_graph', {}); >+ is_deeply($illrq_obj->capabilities('COMP'), >+ { >+ prev_actions => [ 'REQ' ], >+ id => 'COMP', >+ name => 'Completed', >+ ui_method_name => 'Mark completed', >+ method => 'mark_completed', >+ next_actions => [ ], >+ ui_method_icon => 'fa-check', >+ }, >+ "Dummy status graph for COMP."); >+ is($illrq_obj->capabilities('UNKNOWN'), undef, >+ "Dummy status graph for UNKNOWN."); >+ is_deeply($illrq_obj->capabilities(), >+ $illrq_obj->_core_status_graph, >+ "Dummy full status graph."); >+ # Simple backend graph extension >+ $backend->set_always('status_graph', >+ { >+ QER => { >+ prev_actions => [ 'REQ' ], >+ id => 'QER', >+ next_actions => [ 'REQ' ], >+ }, >+ }); >+ is_deeply($illrq_obj->capabilities('QER'), >+ { >+ prev_actions => [ 'REQ' ], >+ id => 'QER', >+ next_actions => [ 'REQ' ], >+ }, >+ "Simple status graph for QER."); >+ is($illrq_obj->capabilities('UNKNOWN'), undef, >+ "Simple status graph for UNKNOWN."); >+ is_deeply($illrq_obj->capabilities(), >+ $illrq_obj->_status_graph_union( >+ $illrq_obj->_core_status_graph, >+ { >+ QER => { >+ prev_actions => [ 'REQ' ], >+ id => 'QER', >+ next_actions => [ 'REQ' ], >+ }, >+ } >+ ), >+ "Simple full status graph."); >+ >+ # custom_capability: >+ >+ # No backend graph extension >+ $backend->set_always('status_graph', {}); >+ is($illrq_obj->custom_capability('unknown', {}), 0, >+ "Unknown candidate."); >+ >+ # Simple backend graph extension >+ $backend->set_always('status_graph', >+ { >+ ID => { >+ prev_actions => [ 'REQ' ], >+ id => 'ID', >+ method => 'identity', >+ next_actions => [ 'REQ' ], >+ }, >+ }); >+ $backend->mock('identity', >+ sub { my ( $self, $params ) = @_; return $params->{other}; }); >+ is($illrq_obj->custom_capability('identity', { test => 1 })->{test}, 1, >+ "Resolve identity custom_capability"); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+ >+subtest 'Backend core methods' => sub { >+ >+ plan tests => 16; >+ >+ $schema->storage->txn_begin; >+ >+ # Build infrastructure >+ my $backend = Test::MockObject->new; >+ $backend->set_isa('Koha::Illbackends::Mock'); >+ $backend->set_always('name', 'Mock'); >+ >+ my $config = Test::MockObject->new; >+ $config->set_always('backend_dir', "/tmp"); >+ $config->set_always('getLimitRules', >+ { default => { count => 0, method => 'active' } }); >+ >+ my $illrq = $builder->build({source => 'Illrequest'}); >+ my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id}); >+ $illrq_obj->_config($config); >+ $illrq_obj->_backend($backend); >+ >+ # expandTemplate: >+ is_deeply($illrq_obj->expandTemplate({ test => 1, method => "bar" }), >+ { >+ test => 1, >+ method => "bar", >+ template => "/tmp/Mock/intra-includes/bar.inc", >+ opac_template => "/tmp/Mock/opac-includes/bar.inc", >+ }, >+ "ExpandTemplate"); >+ >+ # backend_create >+ # we are testing simple cases. >+ $backend->set_series('create', >+ { stage => 'bar', method => 'create' }, >+ { stage => 'commit', method => 'create' }, >+ { stage => 'commit', method => 'create' }); >+ # Test Copyright Clearance >+ t::lib::Mocks::mock_preference("ILLModuleCopyrightClearance", "Test Copyright Clearance."); >+ is_deeply($illrq_obj->backend_create({test => 1}), >+ { >+ error => 0, >+ status => '', >+ message => '', >+ method => 'create', >+ stage => 'copyrightclearance', >+ value => { >+ backend => "Mock" >+ } >+ }, >+ "Backend create: copyright clearance."); >+ t::lib::Mocks::mock_preference("ILLModuleCopyrightClearance", ""); >+ # Test non-commit >+ is_deeply($illrq_obj->backend_create({test => 1}), >+ { >+ stage => 'bar', method => 'create', >+ template => "/tmp/Mock/intra-includes/create.inc", >+ opac_template => "/tmp/Mock/opac-includes/create.inc", >+ }, >+ "Backend create: arbitrary stage."); >+ # Test commit >+ is_deeply($illrq_obj->backend_create({test => 1}), >+ { >+ stage => 'commit', method => 'create', permitted => 0, >+ template => "/tmp/Mock/intra-includes/create.inc", >+ opac_template => "/tmp/Mock/opac-includes/create.inc", >+ }, >+ "Backend create: arbitrary stage, not permitted."); >+ is($illrq_obj->status, "QUEUED", "Backend create: queued if restricted."); >+ $config->set_always('getLimitRules', {}); >+ $illrq_obj->status('NEW'); >+ is_deeply($illrq_obj->backend_create({test => 1}), >+ { >+ stage => 'commit', method => 'create', permitted => 1, >+ template => "/tmp/Mock/intra-includes/create.inc", >+ opac_template => "/tmp/Mock/opac-includes/create.inc", >+ }, >+ "Backend create: arbitrary stage, permitted."); >+ is($illrq_obj->status, "NEW", "Backend create: not-queued."); >+ >+ # backend_renew >+ $backend->set_series('renew', { stage => 'bar', method => 'renew' }); >+ is_deeply($illrq_obj->backend_renew({test => 1}), >+ { >+ stage => 'bar', method => 'renew', >+ template => "/tmp/Mock/intra-includes/renew.inc", >+ opac_template => "/tmp/Mock/opac-includes/renew.inc", >+ }, >+ "Backend renew: arbitrary stage."); >+ >+ # backend_cancel >+ $backend->set_series('cancel', { stage => 'bar', method => 'cancel' }); >+ is_deeply($illrq_obj->backend_cancel({test => 1}), >+ { >+ stage => 'bar', method => 'cancel', >+ template => "/tmp/Mock/intra-includes/cancel.inc", >+ opac_template => "/tmp/Mock/opac-includes/cancel.inc", >+ }, >+ "Backend cancel: arbitrary stage."); >+ >+ # backend_update_status >+ $backend->set_series('update_status', { stage => 'bar', method => 'update_status' }); >+ is_deeply($illrq_obj->backend_update_status({test => 1}), >+ { >+ stage => 'bar', method => 'update_status', >+ template => "/tmp/Mock/intra-includes/update_status.inc", >+ opac_template => "/tmp/Mock/opac-includes/update_status.inc", >+ }, >+ "Backend update_status: arbitrary stage."); >+ >+ # backend_confirm >+ $backend->set_series('confirm', { stage => 'bar', method => 'confirm' }); >+ is_deeply($illrq_obj->backend_confirm({test => 1}), >+ { >+ stage => 'bar', method => 'confirm', >+ template => "/tmp/Mock/intra-includes/confirm.inc", >+ opac_template => "/tmp/Mock/opac-includes/confirm.inc", >+ }, >+ "Backend confirm: arbitrary stage."); >+ >+ $config->set_always('partner_code', "ILLTSTLIB"); >+ $backend->set_always('metadata', { Test => "Foobar" }); >+ my $illbrn = $builder->build({ >+ source => 'Branch', >+ value => { branchemail => "", branchreplyto => "" } >+ }); >+ my $partner1 = $builder->build({ >+ source => 'Borrower', >+ value => { categorycode => "ILLTSTLIB" }, >+ }); >+ my $partner2 = $builder->build({ >+ source => 'Borrower', >+ value => { categorycode => "ILLTSTLIB" }, >+ }); >+ my $gen_conf = $illrq_obj->generic_confirm({ >+ current_branchcode => $illbrn->{branchcode} >+ }); >+ isnt(index($gen_conf->{value}->{draft}->{body}, $backend->metadata->{Test}), -1, >+ "Generic confirm: draft contains metadata." >+ ); >+ is($gen_conf->{value}->{partners}->next->borrowernumber, $partner1->{borrowernumber}, >+ "Generic cofnirm: partner 1 is correct." >+ ); >+ is($gen_conf->{value}->{partners}->next->borrowernumber, $partner2->{borrowernumber}, >+ "Generic confirm: partner 2 is correct." >+ ); >+ >+ dies_ok { $illrq_obj->generic_confirm({ >+ current_branchcode => $illbrn->{branchcode}, >+ stage => 'draft' >+ }) } >+ "Generic confirm: missing to dies OK."; >+ >+ dies_ok { $illrq_obj->generic_confirm({ >+ current_branchcode => $illbrn->{branchcode}, >+ partners => $partner1->{email}, >+ stage => 'draft' >+ }) } >+ "Generic confirm: missing from dies OK."; >+ >+ $schema->storage->txn_rollback; >+}; >+ >+ >+subtest 'Helpers' => sub { >+ >+ plan tests => 9; >+ >+ $schema->storage->txn_begin; >+ >+ # Build infrastructure >+ my $backend = Test::MockObject->new; >+ $backend->set_isa('Koha::Illbackends::Mock'); >+ $backend->set_always('name', 'Mock'); >+ >+ my $config = Test::MockObject->new; >+ $config->set_always('backend_dir', "/tmp"); >+ >+ my $patron = $builder->build({ >+ source => 'Borrower', >+ value => { categorycode => "A" } >+ }); >+ my $illrq = $builder->build({ >+ source => 'Illrequest', >+ value => { branchcode => "CPL", borrowernumber => $patron->{borrowernumber} } >+ }); >+ my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id}); >+ $illrq_obj->_config($config); >+ $illrq_obj->_backend($backend); >+ >+ # getPrefix >+ $config->set_series('getPrefixes', >+ { CPL => "TEST", TSL => "BAR", default => "DEFAULT" }, >+ { A => "ATEST", C => "CBAR", default => "DEFAULT" }); >+ is($illrq_obj->getPrefix({ brw_cat => "C", branch => "CPL" }), "CBAR", >+ "getPrefix: brw_cat"); >+ $config->set_series('getPrefixes', >+ { CPL => "TEST", TSL => "BAR", default => "DEFAULT" }, >+ { A => "ATEST", C => "CBAR", default => "DEFAULT" }); >+ is($illrq_obj->getPrefix({ brw_cat => "UNKNOWN", branch => "CPL" }), "TEST", >+ "getPrefix: branch"); >+ $config->set_series('getPrefixes', >+ { CPL => "TEST", TSL => "BAR", default => "DEFAULT" }, >+ { A => "ATEST", C => "CBAR", default => "DEFAULT" }); >+ is($illrq_obj->getPrefix({ brw_cat => "UNKNOWN", branch => "UNKNOWN" }), "DEFAULT", >+ "getPrefix: default"); >+ $config->set_always('getPrefixes', {}); >+ is($illrq_obj->getPrefix({ brw_cat => "UNKNOWN", branch => "UNKNOWN" }), "", >+ "getPrefix: the empty prefix"); >+ >+ # id_prefix >+ $config->set_series('getPrefixes', >+ { CPL => "TEST", TSL => "BAR", default => "DEFAULT" }, >+ { A => "ATEST", C => "CBAR", default => "DEFAULT" }); >+ is($illrq_obj->id_prefix, "ATEST-", "id_prefix: brw_cat"); >+ $config->set_series('getPrefixes', >+ { CPL => "TEST", TSL => "BAR", default => "DEFAULT" }, >+ { AB => "ATEST", CD => "CBAR", default => "DEFAULT" }); >+ is($illrq_obj->id_prefix, "TEST-", "id_prefix: branch"); >+ $config->set_series('getPrefixes', >+ { CPLT => "TEST", TSLT => "BAR", default => "DEFAULT" }, >+ { AB => "ATEST", CD => "CBAR", default => "DEFAULT" }); >+ is($illrq_obj->id_prefix, "DEFAULT-", "id_prefix: default"); >+ >+ # requires_moderation >+ $illrq_obj->status('NEW')->store; >+ is($illrq_obj->requires_moderation, undef, "requires_moderation: No."); >+ $illrq_obj->status('CANCREQ')->store; >+ is($illrq_obj->requires_moderation, 'CANCREQ', "requires_moderation: Yes."); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+ >+subtest 'Censorship' => sub { >+ >+ plan tests => 2; >+ >+ $schema->storage->txn_begin; >+ >+ # Build infrastructure >+ my $backend = Test::MockObject->new; >+ $backend->set_isa('Koha::Illbackends::Mock'); >+ $backend->set_always('name', 'Mock'); >+ >+ my $config = Test::MockObject->new; >+ $config->set_always('backend_dir', "/tmp"); >+ >+ my $illrq = $builder->build({source => 'Illrequest'}); >+ my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id}); >+ $illrq_obj->_config($config); >+ $illrq_obj->_backend($backend); >+ >+ $config->set_always('censorship', { censor_notes_staff => 1, censor_reply_date => 0 }); >+ >+ my $censor_out = $illrq_obj->_censor({ foo => 'bar', baz => 564 }); >+ is_deeply($censor_out, { foo => 'bar', baz => 564, display_reply_date => 1 }, >+ "_censor: not OPAC, reply_date = 1"); >+ >+ $censor_out = $illrq_obj->_censor({ foo => 'bar', baz => 564, opac => 1 }); >+ is_deeply($censor_out, { >+ foo => 'bar', baz => 564, censor_notes_staff => 1, >+ display_reply_date => 1, opac => 1 >+ }, "_censor: notes_staff = 0, reply_date = 0"); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'Checking Limits' => sub { >+ >+ plan tests => 30; >+ >+ $schema->storage->txn_begin; >+ >+ # Build infrastructure >+ my $backend = Test::MockObject->new; >+ $backend->set_isa('Koha::Illbackends::Mock'); >+ $backend->set_always('name', 'Mock'); >+ >+ my $config = Test::MockObject->new; >+ $config->set_always('backend_dir', "/tmp"); >+ >+ my $illrq = $builder->build({source => 'Illrequest'}); >+ my $illrq_obj = Koha::Illrequests->find($illrq->{illrequest_id}); >+ $illrq_obj->_config($config); >+ $illrq_obj->_backend($backend); >+ >+ # getLimits >+ $config->set_series('getLimitRules', >+ { CPL => { count => 1, method => 'test' } }, >+ { default => { count => 0, method => 'active' } }); >+ is_deeply($illrq_obj->getLimits({ type => 'branch', value => "CPL" }), >+ { count => 1, method => 'test' }, >+ "getLimits: by value."); >+ is_deeply($illrq_obj->getLimits({ type => 'branch' }), >+ { count => 0, method => 'active' }, >+ "getLimits: by default."); >+ is_deeply($illrq_obj->getLimits({ type => 'branch', value => "CPL" }), >+ { count => -1, method => 'active' }, >+ "getLimits: by hard-coded."); >+ >+ #_limit_counter >+ is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }), >+ 1, "_limit_counter: Initial branch annual count."); >+ is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }), >+ 1, "_limit_counter: Initial branch active count."); >+ is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }), >+ 1, "_limit_counter: Initial patron annual count."); >+ is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }), >+ 1, "_limit_counter: Initial patron active count."); >+ $builder->build({ >+ source => 'Illrequest', >+ value => { >+ branchcode => $illrq_obj->branchcode, >+ borrowernumber => $illrq_obj->borrowernumber, >+ } >+ }); >+ is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }), >+ 2, "_limit_counter: Add a qualifying request for branch annual count."); >+ is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }), >+ 2, "_limit_counter: Add a qualifying request for branch active count."); >+ is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }), >+ 2, "_limit_counter: Add a qualifying request for patron annual count."); >+ is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }), >+ 2, "_limit_counter: Add a qualifying request for patron active count."); >+ $builder->build({ >+ source => 'Illrequest', >+ value => { >+ branchcode => $illrq_obj->branchcode, >+ borrowernumber => $illrq_obj->borrowernumber, >+ placed => "2005-05-31", >+ } >+ }); >+ is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }), >+ 2, "_limit_counter: Add an out-of-date branch request."); >+ is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }), >+ 3, "_limit_counter: Add a qualifying request for branch active count."); >+ is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }), >+ 2, "_limit_counter: Add an out-of-date patron request."); >+ is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }), >+ 3, "_limit_counter: Add a qualifying request for patron active count."); >+ $builder->build({ >+ source => 'Illrequest', >+ value => { >+ branchcode => $illrq_obj->branchcode, >+ borrowernumber => $illrq_obj->borrowernumber, >+ status => "COMP", >+ } >+ }); >+ is($illrq_obj->_limit_counter('annual', { branchcode => $illrq_obj->branchcode }), >+ 3, "_limit_counter: Add a qualifying request for branch annual count."); >+ is($illrq_obj->_limit_counter('active', { branchcode => $illrq_obj->branchcode }), >+ 3, "_limit_counter: Add a completed request for branch active count."); >+ is($illrq_obj->_limit_counter('annual', { borrowernumber => $illrq_obj->borrowernumber }), >+ 3, "_limit_counter: Add a qualifying request for patron annual count."); >+ is($illrq_obj->_limit_counter('active', { borrowernumber => $illrq_obj->borrowernumber }), >+ 3, "_limit_counter: Add a completed request for patron active count."); >+ >+ # check_limits: >+ >+ # We've tested _limit_counter, so all we need to test here is whether the >+ # current counts of 3 for each work as they should against different >+ # configuration declarations. >+ >+ # No limits >+ $config->set_always('getLimitRules', undef); >+ is($illrq_obj->check_limits({patron => $illrq_obj->patron, >+ librarycode => $illrq_obj->branchcode}), >+ 1, "check_limits: no configuration => no limits."); >+ >+ # Branch tests >+ $config->set_always('getLimitRules', >+ { $illrq_obj->branchcode => { count => 1, method => 'active' } }); >+ is($illrq_obj->check_limits({patron => $illrq_obj->patron, >+ librarycode => $illrq_obj->branchcode}), >+ 0, "check_limits: branch active limit exceeded."); >+ $config->set_always('getLimitRules', >+ { $illrq_obj->branchcode => { count => 1, method => 'annual' } }); >+ is($illrq_obj->check_limits({patron => $illrq_obj->patron, >+ librarycode => $illrq_obj->branchcode}), >+ 0, "check_limits: branch annual limit exceeded."); >+ $config->set_always('getLimitRules', >+ { $illrq_obj->branchcode => { count => 4, method => 'active' } }); >+ is($illrq_obj->check_limits({patron => $illrq_obj->patron, >+ librarycode => $illrq_obj->branchcode}), >+ 1, "check_limits: branch active limit OK."); >+ $config->set_always('getLimitRules', >+ { $illrq_obj->branchcode => { count => 4, method => 'annual' } }); >+ is($illrq_obj->check_limits({patron => $illrq_obj->patron, >+ librarycode => $illrq_obj->branchcode}), >+ 1, "check_limits: branch annual limit OK."); >+ >+ # Patron tests >+ $config->set_always('getLimitRules', >+ { $illrq_obj->patron->categorycode => { count => 1, method => 'active' } }); >+ is($illrq_obj->check_limits({patron => $illrq_obj->patron, >+ librarycode => $illrq_obj->branchcode}), >+ 0, "check_limits: patron category active limit exceeded."); >+ $config->set_always('getLimitRules', >+ { $illrq_obj->patron->categorycode => { count => 1, method => 'annual' } }); >+ is($illrq_obj->check_limits({patron => $illrq_obj->patron, >+ librarycode => $illrq_obj->branchcode}), >+ 0, "check_limits: patron category annual limit exceeded."); >+ $config->set_always('getLimitRules', >+ { $illrq_obj->patron->categorycode => { count => 4, method => 'active' } }); >+ is($illrq_obj->check_limits({patron => $illrq_obj->patron, >+ librarycode => $illrq_obj->branchcode}), >+ 1, "check_limits: patron category active limit OK."); >+ $config->set_always('getLimitRules', >+ { $illrq_obj->patron->categorycode => { count => 4, method => 'annual' } }); >+ is($illrq_obj->check_limits({patron => $illrq_obj->patron, >+ librarycode => $illrq_obj->branchcode}), >+ 1, "check_limits: patron category annual limit OK."); >+ >+ # One rule cancels the other >+ $config->set_series('getLimitRules', >+ # Branch rules allow request >+ { $illrq_obj->branchcode => { count => 4, method => 'active' } }, >+ # Patron rule forbids it >+ { $illrq_obj->patron->categorycode => { count => 1, method => 'annual' } }); >+ is($illrq_obj->check_limits({patron => $illrq_obj->patron, >+ librarycode => $illrq_obj->branchcode}), >+ 0, "check_limits: patron category veto overrides branch OK."); >+ $config->set_series('getLimitRules', >+ # Branch rules allow request >+ { $illrq_obj->branchcode => { count => 1, method => 'active' } }, >+ # Patron rule forbids it >+ { $illrq_obj->patron->categorycode => { count => 4, method => 'annual' } }); >+ is($illrq_obj->check_limits({patron => $illrq_obj->patron, >+ librarycode => $illrq_obj->branchcode}), >+ 0, "check_limits: branch veto overrides patron category OK."); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+1; >diff --git a/t/db_dependent/api/v1/illrequests.t b/t/db_dependent/api/v1/illrequests.t >new file mode 100644 >index 0000000..fb306ba >--- /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.7.4
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