1 package Koha
::Illrequest
;
3 # Copyright PTFS Europe 2016,2018
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
23 use File
::Basename
qw( basename );
24 use Encode
qw( encode );
29 use Koha
::DateUtils qw
/ dt_from_string /;
31 use Koha
::Exceptions
::Ill
;
32 use Koha
::Illcomments
;
33 use Koha
::Illrequestattributes
;
34 use Koha
::AuthorisedValue
;
35 use Koha
::Illrequest
::Logger
;
37 use Koha
::AuthorisedValues
;
43 use C4
::Circulation
qw( CanBookBeIssued AddIssue );
45 use base
qw(Koha::Object);
49 Koha::Illrequest - Koha Illrequest Object class
53 An ILLRequest consists of two parts; the Illrequest Koha::Object, and a series
54 of related Illrequestattributes.
56 The former encapsulates the basic necessary information that any ILL requires
57 to be usable in Koha. The latter is a set of additional properties used by
60 The former subsumes the legacy "Status" object. The latter remains
61 encapsulated in the "Record" object.
65 - Anything invoking the ->status method; annotated with:
66 + # Old use of ->status !
70 =head2 Backend API Response Principles
72 All methods should return a hashref in the following format:
78 This should be set to 1 if an error was encountered.
82 The status should be a string from the list of statuses detailed below.
86 The message is a free text field that can be passed on to the end user.
90 The value returned by the method.
94 =head2 Interface Status Messages
98 =item * branch_address_incomplete
100 An interface request has determined branch address details are incomplete.
102 =item * cancel_success
104 The interface's cancel_request method was successful in cancelling the
105 Illrequest using the API.
109 The interface's cancel_request method failed to cancel the Illrequest using
114 The interface's request method returned saying that the desired item is not
115 available for request.
123 my $statusalias = $request->statusalias;
125 Returns a request's status alias, as a Koha::AuthorisedValue instance
126 or implicit undef. This is distinct from status_alias, which only returns
127 the value in the status_alias column, this method returns the entire
128 AuthorisedValue object
134 return unless $self->status_alias;
135 # We can't know which result is the right one if there are multiple
136 # ILLSTATUS authorised values with the same authorised_value column value
137 # so we just use the first
138 return Koha
::AuthorisedValues
->search({
139 branchcode
=> $self->branchcode,
140 category
=> 'ILLSTATUS',
141 authorised_value
=> $self->SUPER::status_alias
145 =head3 illrequestattributes
149 sub illrequestattributes
{
151 return Koha
::Illrequestattributes
->_new_from_dbic(
152 scalar $self->_result->illrequestattributes
162 return Koha
::Illcomments
->_new_from_dbic(
163 scalar $self->_result->illcomments
173 my $logger = Koha
::Illrequest
::Logger
->new;
174 return $logger->get_request_logs($self);
183 return Koha
::Patron
->_new_from_dbic(
184 scalar $self->_result->borrowernumber
190 $Illrequest->status_alias(143);
192 Overloaded getter/setter for status_alias,
193 that only returns authorised values from the
194 correct category and records the fact that the status has changed
199 my ($self, $new_status_alias) = @_;
201 my $current_status_alias = $self->SUPER::status_alias
;
203 if ($new_status_alias) {
204 # Keep a record of the previous status before we change it,
206 $self->{previous_status
} = $current_status_alias ?
207 $current_status_alias :
208 scalar $self->status;
209 # This is hackery to enable us to undefine
210 # status_alias, since we need to have an overloaded
211 # status_alias method to get us around the problem described
213 # https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=20581#c156
214 # We need a way of accepting implied undef, so we can nullify
215 # the status_alias column, when called from $self->status
216 my $val = $new_status_alias eq "-1" ?
undef : $new_status_alias;
217 my $ret = $self->SUPER::status_alias
($val);
218 my $val_to_log = $val ?
$new_status_alias : scalar $self->status;
220 my $logger = Koha
::Illrequest
::Logger
->new;
221 $logger->log_status_change({
226 delete $self->{previous_status
};
230 # We can't know which result is the right one if there are multiple
231 # ILLSTATUS authorised values with the same authorised_value column value
232 # so we just use the first
233 my $alias = Koha
::AuthorisedValues
->search({
234 branchcode
=> $self->branchcode,
235 category
=> 'ILLSTATUS',
236 authorised_value
=> $self->SUPER::status_alias
239 return $alias->authorised_value;
247 $Illrequest->status('CANREQ');
249 Overloaded getter/setter for request status,
250 also nullifies status_alias and records the fact that the status has changed
255 my ( $self, $new_status) = @_;
257 my $current_status = $self->SUPER::status
;
258 my $current_status_alias = $self->SUPER::status_alias
;
261 # Keep a record of the previous status before we change it,
263 $self->{previous_status
} = $current_status_alias ?
264 $current_status_alias :
266 my $ret = $self->SUPER::status
($new_status)->store;
267 if ($current_status_alias) {
268 # This is hackery to enable us to undefine
269 # status_alias, since we need to have an overloaded
270 # status_alias method to get us around the problem described
272 # https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=20581#c156
273 # We need a way of passing implied undef to nullify status_alias
274 # so we pass -1, which is special cased in the overloaded setter
275 $self->status_alias("-1");
277 my $logger = Koha
::Illrequest
::Logger
->new;
278 $logger->log_status_change({
283 delete $self->{previous_status
};
286 return $current_status;
292 Require "Base.pm" from the relevant ILL backend.
297 my ( $self, $backend_id ) = @_;
299 my @raw = qw
/Koha Illbackends/; # Base Path
301 my $backend_name = $backend_id || $self->backend;
303 unless ( defined $backend_name && $backend_name ne '' ) {
304 Koha
::Exceptions
::Ill
::InvalidBackendId
->throw(
305 "An invalid backend ID was requested ('')");
308 my $location = join "/", @raw, $backend_name, "Base.pm"; # File to load
309 my $backend_class = join "::", @raw, $backend_name, "Base"; # Package name
311 $self->{_my_backend
} = $backend_class->new({
312 config
=> $self->_config,
313 logger
=> Koha
::Illrequest
::Logger
->new
321 my $backend = $abstract->_backend($new_backend);
322 my $backend = $abstract->_backend;
324 Getter/Setter for our API object.
329 my ( $self, $backend ) = @_;
330 $self->{_my_backend
} = $backend if ( $backend );
331 # Dynamically load our backend object, as late as possible.
332 $self->load_backend unless ( $self->{_my_backend
} );
333 return $self->{_my_backend
};
336 =head3 _backend_capability
338 my $backend_capability_result = $self->_backend_capability($name, $args);
340 This is a helper method to invoke optional capabilities in the backend. If
341 the capability named by $name is not supported, return 0, else invoke it,
342 passing $args along with the invocation, and return its return value.
344 NOTE: this module suffers from a confusion in termninology:
346 in _backend_capability, the notion of capability refers to an optional feature
347 that is implemented in core, but might not be supported by a given backend.
349 in capabilities & custom_capability, capability refers to entries in the
350 status_graph (after union between backend and core).
352 The easiest way to fix this would be to fix the terminology in
353 capabilities & custom_capability and their callers.
357 sub _backend_capability
{
358 my ( $self, $name, $args ) = @_;
360 # See if capability is defined in backend
362 $capability = $self->_backend->capabilities($name);
367 if ( $capability && ref($capability) eq 'CODE' ) {
368 return &{$capability}($args);
376 my $config = $abstract->_config($config);
377 my $config = $abstract->_config;
379 Getter/Setter for our config object.
384 my ( $self, $config ) = @_;
385 $self->{_my_config
} = $config if ( $config );
386 # Load our config object, as late as possible.
387 unless ( $self->{_my_config
} ) {
388 $self->{_my_config
} = Koha
::Illrequest
::Config
->new;
390 return $self->{_my_config
};
399 return $self->_backend->metadata($self);
402 =head3 _core_status_graph
404 my $core_status_graph = $illrequest->_core_status_graph;
406 Returns ILL module's default status graph. A status graph defines the list of
407 available actions at any stage in the ILL workflow. This is for instance used
408 by the perl script & template to generate the correct buttons to display to
409 the end user at any given point.
413 sub _core_status_graph
{
417 prev_actions
=> [ ], # Actions containing buttons
418 # leading to this status
419 id
=> 'NEW', # ID of this status
420 name
=> 'New request', # UI name of this status
421 ui_method_name
=> 'New request', # UI name of method leading
423 method
=> 'create', # method to this status
424 next_actions
=> [ 'REQ', 'GENREQ', 'KILL' ], # buttons to add to all
425 # requests with this status
426 ui_method_icon
=> 'fa-plus', # UI Style class
429 prev_actions
=> [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ],
432 ui_method_name
=> 'Confirm request',
434 next_actions
=> [ 'REQREV', 'COMP', 'CHK' ],
435 ui_method_icon
=> 'fa-check',
438 prev_actions
=> [ 'NEW', 'REQREV' ],
440 name
=> 'Requested from partners',
441 ui_method_name
=> 'Place request with partners',
442 method
=> 'generic_confirm',
443 next_actions
=> [ 'COMP', 'CHK' ],
444 ui_method_icon
=> 'fa-send-o',
447 prev_actions
=> [ 'REQ' ],
449 name
=> 'Request reverted',
450 ui_method_name
=> 'Revert Request',
452 next_actions
=> [ 'REQ', 'GENREQ', 'KILL' ],
453 ui_method_icon
=> 'fa-times',
458 name
=> 'Queued request',
461 next_actions
=> [ 'REQ', 'KILL' ],
465 prev_actions
=> [ 'NEW' ],
467 name
=> 'Cancellation requested',
470 next_actions
=> [ 'KILL', 'REQ' ],
474 prev_actions
=> [ 'REQ' ],
477 ui_method_name
=> 'Mark completed',
478 method
=> 'mark_completed',
479 next_actions
=> [ 'CHK' ],
480 ui_method_icon
=> 'fa-check',
483 prev_actions
=> [ 'QUEUED', 'REQREV', 'NEW', 'CANCREQ' ],
486 ui_method_name
=> 'Delete request',
489 ui_method_icon
=> 'fa-trash',
492 prev_actions
=> [ 'REQ', 'GENREQ', 'COMP' ],
494 name
=> 'Checked out',
495 ui_method_name
=> 'Check out',
496 needs_prefs
=> [ 'CirculateILL' ],
497 needs_perms
=> [ 'user_circulate_circulate_remaining_permissions' ],
498 # An array of functions that all must return true
499 needs_all
=> [ sub { my $r = shift; return $r->biblio; } ],
500 method
=> 'check_out',
502 ui_method_icon
=> 'fa-upload',
505 prev_actions
=> [ 'CHK' ],
507 name
=> 'Returned to library',
508 ui_method_name
=> 'Check in',
509 method
=> 'check_in',
510 next_actions
=> [ 'COMP' ],
511 ui_method_icon
=> 'fa-download',
516 =head3 _status_graph_union
518 my $status_graph = $illrequest->_status_graph_union($origin, $new_graph);
520 Return a new status_graph, the result of merging $origin & new_graph. This is
521 operation is a union over the sets defied by the two graphs.
523 Each entry in $new_graph is added to $origin. We do not provide a syntax for
524 'subtraction' of entries from $origin.
526 Whilst it is not intended that this works, you can override entries in $origin
527 with entries with the same key in $new_graph. This can lead to problematic
528 behaviour when $new_graph adds an entry, which modifies a dependent entry in
529 $origin, only for the entry in $origin to be replaced later with a new entry
532 NOTE: this procedure does not "re-link" entries in $origin or $new_graph,
533 i.e. each of the graphs need to be correct at the outset of the operation.
537 sub _status_graph_union
{
538 my ( $self, $core_status_graph, $backend_status_graph ) = @_;
539 # Create new status graph with:
540 # - all core_status_graph
541 # - for-each each backend_status_graph
542 # + add to new status graph
543 # + for each core prev_action:
544 # * locate core_status
545 # * update next_actions with additional next action.
546 # + for each core next_action:
547 # * locate core_status
548 # * update prev_actions with additional prev action
550 my @core_status_ids = keys %{$core_status_graph};
551 my $status_graph = clone
($core_status_graph);
553 foreach my $backend_status_key ( keys %{$backend_status_graph} ) {
554 my $backend_status = $backend_status_graph->{$backend_status_key};
555 # Add to new status graph
556 $status_graph->{$backend_status_key} = $backend_status;
557 # Update all core methods' next_actions.
558 foreach my $prev_action ( @
{$backend_status->{prev_actions
}} ) {
559 if ( grep { $prev_action eq $_ } @core_status_ids ) {
561 @
{$status_graph->{$prev_action}->{next_actions
}};
562 push @next_actions, $backend_status_key;
563 $status_graph->{$prev_action}->{next_actions
}
567 # Update all core methods' prev_actions
568 foreach my $next_action ( @
{$backend_status->{next_actions
}} ) {
569 if ( grep { $next_action eq $_ } @core_status_ids ) {
571 @
{$status_graph->{$next_action}->{prev_actions
}};
572 push @prev_actions, $backend_status_key;
573 $status_graph->{$next_action}->{prev_actions
}
579 return $status_graph;
586 my $capabilities = $illrequest->capabilities;
588 Return a hashref mapping methods to operation names supported by the queried
591 Example return value:
593 { create => "Create Request", confirm => "Progress Request" }
595 NOTE: this module suffers from a confusion in termninology:
597 in _backend_capability, the notion of capability refers to an optional feature
598 that is implemented in core, but might not be supported by a given backend.
600 in capabilities & custom_capability, capability refers to entries in the
601 status_graph (after union between backend and core).
603 The easiest way to fix this would be to fix the terminology in
604 capabilities & custom_capability and their callers.
609 my ( $self, $status ) = @_;
610 # Generate up to date status_graph
611 my $status_graph = $self->_status_graph_union(
612 $self->_core_status_graph,
613 $self->_backend->status_graph({
618 # Extract available actions from graph.
619 return $status_graph->{$status} if $status;
620 # Or return entire graph.
621 return $status_graph;
624 =head3 custom_capability
626 Return the result of invoking $CANDIDATE on this request's backend with
627 $PARAMS, or 0 if $CANDIDATE is an unknown method on backend.
629 NOTE: this module suffers from a confusion in termninology:
631 in _backend_capability, the notion of capability refers to an optional feature
632 that is implemented in core, but might not be supported by a given backend.
634 in capabilities & custom_capability, capability refers to entries in the
635 status_graph (after union between backend and core).
637 The easiest way to fix this would be to fix the terminology in
638 capabilities & custom_capability and their callers.
642 sub custom_capability
{
643 my ( $self, $candidate, $params ) = @_;
644 foreach my $capability ( values %{$self->capabilities} ) {
645 if ( $candidate eq $capability->{method
} ) {
647 $self->_backend->$candidate({
651 return $self->expandTemplate($response);
657 =head3 available_backends
659 Return a list of available backends.
663 sub available_backends
{
664 my ( $self, $reduced ) = @_;
665 my $backends = $self->_config->available_backends($reduced);
669 =head3 available_actions
671 Return a list of available actions.
675 sub available_actions
{
677 my $current_action = $self->capabilities($self->status);
678 my @available_actions = map { $self->capabilities($_) }
679 @
{$current_action->{next_actions
}};
680 return \
@available_actions;
683 =head3 mark_completed
685 Mark a request as completed (status = COMP).
691 $self->status('COMP')->store;
692 $self->completed(dt_from_string
())->store;
697 method
=> 'mark_completed',
703 =head2 backend_migrate
705 Migrate a request from one backend to another.
709 sub backend_migrate
{
710 my ( $self, $params ) = @_;
712 my $response = $self->_backend_capability('migrate',{
716 return $self->expandTemplate($response) if $response;
720 =head2 backend_confirm
722 Confirm a request. The backend handles setting of mandatory fields in the commit stage:
728 =item * accessurl, cost (if available).
734 sub backend_confirm
{
735 my ( $self, $params ) = @_;
737 my $response = $self->_backend->confirm({
741 return $self->expandTemplate($response);
744 =head3 backend_update_status
748 sub backend_update_status
{
749 my ( $self, $params ) = @_;
750 return $self->expandTemplate($self->_backend->update_status($params));
753 =head3 backend_cancel
755 my $ILLResponse = $illRequest->backend_cancel;
757 The standard interface method allowing for request cancellation.
762 my ( $self, $params ) = @_;
764 my $result = $self->_backend->cancel({
769 return $self->expandTemplate($result);
774 my $renew_response = $illRequest->backend_renew;
776 The standard interface method allowing for request renewal queries.
782 return $self->expandTemplate(
783 $self->_backend->renew({
789 =head3 backend_create
791 my $create_response = $abstractILL->backend_create($params);
793 Return an array of Record objects created by querying our backend with
796 In the context of the other ILL methods, this is a special method: we only
797 pass it $params, as it does not yet have any other data associated with it.
802 my ( $self, $params ) = @_;
804 # Establish whether we need to do a generic copyright clearance.
805 if ($params->{opac
}) {
806 if ( ( !$params->{stage
} || $params->{stage
} eq 'init' )
807 && C4
::Context
->preference("ILLModuleCopyrightClearance") ) {
813 stage
=> 'copyrightclearance',
816 backend
=> $self->_backend->name
819 } elsif ( defined $params->{stage
}
820 && $params->{stage
} eq 'copyrightclearance' ) {
821 $params->{stage
} = 'init';
824 # First perform API action, then...
829 my $result = $self->_backend->create($args);
831 # ... simple case: we're not at 'commit' stage.
832 my $stage = $result->{stage
};
833 return $self->expandTemplate($result)
834 unless ( 'commit' eq $stage );
836 # ... complex case: commit!
838 # Do we still have space for an ILL or should we queue?
839 my $permitted = $self->check_limits(
840 { patron
=> $self->patron }, { librarycode
=> $self->branchcode }
843 # Now augment our committed request.
845 $result->{permitted
} = $permitted; # Queue request?
849 # ...Updating status!
850 $self->status('QUEUED')->store unless ( $permitted );
852 ## Handle Unmediated ILLs
854 # For the unmediated workflow we only need to delegate to our backend. If
855 # that backend supports unmediateld_ill, it will do its thing and return a
856 # proper response. If it doesn't then _backend_capability returns 0, so
857 # we keep the current result.
858 if ( C4
::Context
->preference("ILLModuleUnmediated") && $permitted ) {
859 my $unmediated_result = $self->_backend_capability(
863 $result = $unmediated_result if $unmediated_result;
866 return $self->expandTemplate($result);
869 =head3 expandTemplate
871 my $params = $abstract->expandTemplate($params);
873 Return a version of $PARAMS augmented with our required template path.
878 my ( $self, $params ) = @_;
879 my $backend = $self->_backend->name;
880 # Generate path to file to load
881 my $backend_dir = $self->_config->backend_dir;
882 my $backend_tmpl = join "/", $backend_dir, $backend;
883 my $intra_tmpl = join "/", $backend_tmpl, "intra-includes",
884 ( $params->{method
}//q{} ) . ".inc";
885 my $opac_tmpl = join "/", $backend_tmpl, "opac-includes",
886 ( $params->{method
}//q{} ) . ".inc";
888 $params->{template
} = $intra_tmpl;
889 $params->{opac_template
} = $opac_tmpl;
893 #### Abstract Imports
897 my $limit_rules = $abstract->getLimits( {
898 type => 'brw_cat' | 'branch',
902 Return the ILL limit rules for the supplied combination of type / value.
904 As the config may have no rules for this particular type / value combination,
905 or for the default, we must define fall-back values here.
910 my ( $self, $params ) = @_;
911 my $limits = $self->_config->getLimitRules($params->{type
});
913 if ( defined $params->{value
}
914 && defined $limits->{$params->{value
}} ) {
915 return $limits->{$params->{value
}};
918 return $limits->{default} || { count
=> -1, method
=> 'active' };
924 my $prefix = $abstract->getPrefix( {
925 branch => $branch_code
928 Return the ILL prefix as defined by our $params: either per borrower category,
929 per branch or the default.
934 my ( $self, $params ) = @_;
935 my $brn_prefixes = $self->_config->getPrefixes();
936 return $brn_prefixes->{$params->{branch
}} || ""; # "the empty prefix"
941 my $type = $abstract->get_type();
943 Return a string representing the material type of this request or undef
949 my $attr = $self->illrequestattributes->find({ type
=> 'type'});
954 #### Illrequests Imports
958 my $ok = $illRequests->check_limits( {
959 borrower => $borrower,
960 branchcode => 'branchcode' | undef,
963 Given $PARAMS, a hashref containing a $borrower object and a $branchcode,
964 see whether we are still able to place ILLs.
966 LimitRules are derived from koha-conf.xml:
967 + default limit counts, and counting method
968 + branch specific limit counts & counting method
969 + borrower category specific limit counts & counting method
970 + err on the side of caution: a counting fail will cause fail, even if
971 the other counts passes.
976 my ( $self, $params ) = @_;
977 my $patron = $params->{patron
};
978 my $branchcode = $params->{librarycode
} || $patron->branchcode;
980 # Establish maximum number of allowed requests
981 my ( $branch_rules, $brw_rules ) = (
988 value
=> $patron->categorycode,
991 my ( $branch_limit, $brw_limit )
992 = ( $branch_rules->{count
}, $brw_rules->{count
} );
993 # Establish currently existing requests
994 my ( $branch_count, $brw_count ) = (
995 $self->_limit_counter(
996 $branch_rules->{method
}, { branchcode
=> $branchcode }
998 $self->_limit_counter(
999 $brw_rules->{method
}, { borrowernumber
=> $patron->borrowernumber }
1003 # Compare and return
1004 # A limit of -1 means no limit exists.
1005 # We return blocked if either branch limit or brw limit is reached.
1006 if ( ( $branch_limit != -1 && $branch_limit <= $branch_count )
1007 || ( $brw_limit != -1 && $brw_limit <= $brw_count ) ) {
1014 sub _limit_counter
{
1015 my ( $self, $method, $target ) = @_;
1017 # Establish parameters of counts
1019 if ($method && $method eq 'annual') {
1020 $resultset = Koha
::Illrequests
->search({
1023 \"YEAR
(placed
) = YEAR
(NOW
())"
1026 } else { # assume 'active'
1027 # XXX: This status list is ugly. There should be a method in config
1029 my $where = { status => { -not_in => [ 'QUEUED', 'COMP' ] } };
1030 $resultset = Koha::Illrequests->search({ %{$target}, %{$where} });
1034 return $resultset->count;
1037 =head3 requires_moderation
1039 my $status = $illRequest->requires_moderation;
1041 Return the name of the status if moderation by staff is required; or 0
1046 sub requires_moderation {
1048 my $require_moderation = {
1049 'CANCREQ' => 'CANCREQ',
1051 return $require_moderation->{$self->status};
1056 my $biblio = $request->biblio;
1058 For a given request, return the biblio associated with it,
1059 or undef if none exists
1066 return if !$self->biblio_id;
1068 return Koha::Biblios->find({
1069 biblionumber => $self->biblio_id
1075 my $stage_summary = $request->check_out;
1077 Handle the check_out method. The first stage involves gathering the required
1078 data from the user via a form, the second stage creates an item and tries to
1079 issue it to the patron. If successful, it notifies the patron, then it
1080 returns a summary of how things went
1085 my ( $self, $params ) = @_;
1087 # Objects required by the template
1088 my $itemtypes = Koha::ItemTypes->search(
1090 { order_by => ['description'] }
1092 my $libraries = Koha::Libraries->search(
1094 { order_by => ['branchcode'] }
1096 my $biblio = $self->biblio;
1098 # Find all statistical patrons
1099 my $statistical_patrons = Koha::Patrons->search(
1100 { 'category_type' => 'x' },
1101 { join => { 'categorycode' => 'borrowers' } }
1104 if (!$params->{stage} || $params->{stage} eq 'init') {
1105 # Present a form to gather the required data
1107 # We may be viewing this page having previously tried to issue
1108 # the item (in which case, we may already have created an item)
1109 # so we pass the biblio for this request
1111 method => 'check_out',
1114 itemtypes => $itemtypes,
1115 libraries => $libraries,
1116 statistical => $statistical_patrons,
1120 } elsif ($params->{stage} eq 'form') {
1121 # Validate what we've got and return with an error if we fail
1123 if (!$params->{item_type} || length $params->{item_type} == 0) {
1124 $errors->{item_type} = 1;
1126 if ($params->{inhouse} && length $params->{inhouse} > 0) {
1127 my $patron_count = Koha::Patrons->search({
1128 cardnumber => $params->{inhouse}
1130 if ($patron_count != 1) {
1131 $errors->{inhouse} = 1;
1135 # Check we don't have more than one item for this bib,
1136 # if we do, something very odd is going on
1137 # Having 1 is OK, it means we're likely trying to issue
1138 # following a previously failed attempt, the item exists
1140 my @items = $biblio->items->as_list;
1141 my $item_count = scalar @items;
1142 if ($item_count > 1) {
1143 $errors->{itemcount} = 1;
1146 # Failed validation, go back to the form
1149 method => 'check_out',
1153 statistical => $statistical_patrons,
1154 itemtypes => $itemtypes,
1155 libraries => $libraries,
1164 # Create an item if one doesn't already exist,
1165 # if one does, use that
1167 if ($item_count == 0) {
1169 biblionumber => $self->biblio_id,
1170 homebranch => $params->{branchcode},
1171 holdingbranch => $params->{branchcode},
1172 location => $params->{branchcode},
1173 itype => $params->{item_type},
1174 barcode => 'ILL-' . $self->illrequest_id
1177 my $item = Koha::Item->new($item_hash)->store;
1178 $itemnumber = $item->itemnumber;
1181 $itemnumber = $items[0]->itemnumber;
1183 # Check we have an item before going forward
1186 method => 'check_out',
1190 itemtypes => $itemtypes,
1191 libraries => $libraries,
1192 statistical => $statistical_patrons,
1193 errors => { item_creation => 1 }
1200 # Gather what we need
1201 my $target_item = Koha::Items->find( $itemnumber );
1202 # Determine who we're issuing to
1203 my $patron = $params->{inhouse} && length $params->{inhouse} > 0 ?
1204 Koha::Patrons->find({ cardnumber => $params->{inhouse} }) :
1209 scalar $target_item->barcode
1211 if ($params->{duedate} && length $params->{duedate} > 0) {
1212 push @issue_args, $params->{duedate};
1214 # Check if we can check out
1215 my ( $error, $confirm, $alerts, $messages ) =
1216 C4::Circulation::CanBookBeIssued(@issue_args);
1218 # If we got anything back saying we can't check out,
1219 # return it to the template
1221 if ( $error && %{$error} ) { $problems->{error} = $error };
1222 if ( $confirm && %{$confirm} ) { $problems->{confirm} = $confirm };
1223 if ( $alerts && %{$alerts} ) { $problems->{alerts} = $alerts };
1224 if ( $messages && %{$messages} ) { $problems->{messages} = $messages };
1228 method => 'check_out',
1232 itemtypes => $itemtypes,
1233 libraries => $libraries,
1234 statistical => $statistical_patrons,
1237 check_out_errors => $problems
1242 # We can allegedly check out, so make it so
1243 # For some reason, AddIssue requires an unblessed Patron
1244 $issue_args[0] = $patron->unblessed;
1245 my $issue = C4::Circulation::AddIssue(@issue_args);
1248 # Update the request status
1249 $self->status('CHK')->store;
1251 method => 'check_out',
1252 stage => 'done_check_out',
1261 method => 'check_out',
1265 itemtypes => $itemtypes,
1266 libraries => $libraries,
1267 errors => { item_check_out => 1 }
1275 =head3 generic_confirm
1277 my $stage_summary = $illRequest->generic_confirm;
1279 Handle the generic_confirm extended method. The first stage involves creating
1280 a template email for the end user to edit in the browser. The second stage
1281 attempts to submit the email.
1285 sub generic_confirm {
1286 my ( $self, $params ) = @_;
1287 my $library = Koha::Libraries->find($params->{current_branchcode})
1288 || die "Invalid current branchcode
. Are you logged
in as the database user?
";
1289 if ( !$params->{stage}|| $params->{stage} eq 'init' ) {
1290 my $draft->{subject} = "ILL Request
";
1291 $draft->{body} = <<EOF;
1294 We would like to request an interlibrary loan for a title matching the
1295 following description:
1299 my $details = $self->metadata;
1300 while (my ($title, $value) = each %{$details}) {
1301 $draft->{body
} .= " - " . $title . ": " . $value . "\n"
1304 $draft->{body
} .= <<EOF;
1306 Please let us know if you are able to supply this to us.
1312 my @address = map { $library->$_ }
1313 qw
/ branchname branchaddress1 branchaddress2 branchaddress3
1314 branchzip branchcity branchstate branchcountry branchphone
1317 foreach my $line ( @address ) {
1318 $address .= $line . "\n" if $line;
1321 $draft->{body
} .= $address;
1323 my $partners = Koha
::Patrons
->search({
1324 categorycode
=> $self->_config->partner_code
1330 method
=> 'generic_confirm',
1334 partners
=> $partners,
1338 } elsif ( 'draft' eq $params->{stage
} ) {
1339 # Create the to header
1340 my $to = $params->{partners
};
1341 if ( defined $to ) {
1342 $to =~ s/^\x00//; # Strip leading NULLs
1343 $to =~ s/\x00/; /; # Replace others with '; '
1345 Koha
::Exceptions
::Ill
::NoTargetEmail
->throw(
1346 "No target email addresses found. Either select at least one partner or check your ILL partner library records.")
1348 # Create the from, replyto and sender headers
1349 my $from = $library->branchemail;
1350 my $reply_to = $library->branchreplyto || $from;
1351 Koha
::Exceptions
::Ill
::NoLibraryEmail
->throw(
1352 "Your library has no usable email address. Please set it.")
1356 my $email = Koha
::Email
->create(
1360 reply_to
=> $reply_to,
1361 subject
=> $params->{subject
},
1362 text_body
=> $params->{body
},
1369 $email->send_or_die({ transport
=> $library->smtp_server->transport });
1371 $self->status("GENREQ")->store;
1372 $self->_backend_capability(
1373 'set_requested_partners',
1383 method
=> 'generic_confirm',
1391 status
=> 'email_failed',
1393 method
=> 'generic_confirm',
1398 die "Unknown stage, should not have happened."
1404 my $prefix = $record->id_prefix;
1406 Return the prefix appropriate for the current Illrequest as derived from the
1407 borrower and branch associated with this request's Status, and the config
1414 my $prefix = $self->getPrefix( {
1415 branch
=> $self->branchcode,
1417 $prefix .= "-" if ( $prefix );
1423 my $params = $illRequest->_censor($params);
1425 Return $params, modified to reflect our censorship requirements.
1430 my ( $self, $params ) = @_;
1431 my $censorship = $self->_config->censorship;
1432 $params->{censor_notes_staff
} = $censorship->{censor_notes_staff
}
1433 if ( $params->{opac
} );
1434 $params->{display_reply_date
} = ( $censorship->{censor_reply_date
} ) ?
0 : 1;
1443 Overloaded I<store> method that, in addition to performing the 'store',
1444 possibly records the fact that something happened
1449 my ( $self, $attrs ) = @_;
1451 my $ret = $self->SUPER::store
;
1453 $attrs->{log_origin
} = 'core';
1455 if ($ret && defined $attrs) {
1456 my $logger = Koha
::Illrequest
::Logger
->new;
1457 $logger->log_maybe({
1466 =head3 requested_partners
1468 my $partners_string = $illRequest->requested_partners;
1470 Return the string representing the email addresses of the partners to
1471 whom a request has been sent
1475 sub requested_partners
{
1477 return $self->_backend_capability(
1478 'get_requested_partners',
1479 { request
=> $self }
1485 $json = $illrequest->TO_JSON
1487 Overloaded I<TO_JSON> method that takes care of inserting calculated values
1488 into the unblessed representation of the object.
1490 TODO: This method does nothing and is not called anywhere. However, bug 74325
1491 touches it, so keeping this for now until both this and bug 74325 are merged,
1492 at which point we can sort it out and remove it completely
1497 my ( $self, $embed ) = @_;
1499 my $object = $self->SUPER::TO_JSON
();
1504 =head2 Internal methods
1511 return 'Illrequest';
1516 Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com>
1517 Andrew Isherwood <andrew.isherwood@ptfs-europe.com>