--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
'./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'},
},
"/patrons/{borrowernumber}": {
"$ref": "paths/patrons.json#/~1patrons~1{borrowernumber}"
+ },
+ "/illrequests": {
+ "$ref": "paths/illrequests.json#/~1illrequests"
}
}
--- /dev/null
+{
+ "/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"
+ }
+ }
+ }
+ }
+}
<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>
--- /dev/null
+#!/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');
+}
#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;
+}
[% 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 %]
--- /dev/null
+[% 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 %]
[%- 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 -%]
--- /dev/null
+[% 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' %]
<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 -->
[% 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 %]
--- /dev/null
+[% 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 %]
[% 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 -->
[% 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 -->
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;
--- /dev/null
+#!/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;
--- /dev/null
+#!/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;
--- /dev/null
+#!/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;
--- /dev/null
+#!/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;
--- /dev/null
+#!/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;