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 );
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;
42 use C4::Items qw( AddItem );
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 method => 'check_out',
498 ui_method_icon => 'fa-upload',
503 =head3 _status_graph_union
505 my $status_graph = $illrequest->_status_graph_union($origin, $new_graph);
507 Return a new status_graph, the result of merging $origin & new_graph. This is
508 operation is a union over the sets defied by the two graphs.
510 Each entry in $new_graph is added to $origin. We do not provide a syntax for
511 'subtraction' of entries from $origin.
513 Whilst it is not intended that this works, you can override entries in $origin
514 with entries with the same key in $new_graph. This can lead to problematic
515 behaviour when $new_graph adds an entry, which modifies a dependent entry in
516 $origin, only for the entry in $origin to be replaced later with a new entry
519 NOTE: this procedure does not "re-link" entries in $origin or $new_graph,
520 i.e. each of the graphs need to be correct at the outset of the operation.
524 sub _status_graph_union {
525 my ( $self, $core_status_graph, $backend_status_graph ) = @_;
526 # Create new status graph with:
527 # - all core_status_graph
528 # - for-each each backend_status_graph
529 # + add to new status graph
530 # + for each core prev_action:
531 # * locate core_status
532 # * update next_actions with additional next action.
533 # + for each core next_action:
534 # * locate core_status
535 # * update prev_actions with additional prev action
537 my @core_status_ids = keys %{$core_status_graph};
538 my $status_graph = clone($core_status_graph);
540 foreach my $backend_status_key ( keys %{$backend_status_graph} ) {
541 my $backend_status = $backend_status_graph->{$backend_status_key};
542 # Add to new status graph
543 $status_graph->{$backend_status_key} = $backend_status;
544 # Update all core methods' next_actions.
545 foreach my $prev_action ( @{$backend_status->{prev_actions}} ) {
546 if ( grep { $prev_action eq $_ } @core_status_ids ) {
548 @{$status_graph->{$prev_action}->{next_actions}};
549 push @next_actions, $backend_status_key;
550 $status_graph->{$prev_action}->{next_actions}
554 # Update all core methods' prev_actions
555 foreach my $next_action ( @{$backend_status->{next_actions}} ) {
556 if ( grep { $next_action eq $_ } @core_status_ids ) {
558 @{$status_graph->{$next_action}->{prev_actions}};
559 push @prev_actions, $backend_status_key;
560 $status_graph->{$next_action}->{prev_actions}
566 return $status_graph;
573 my $capabilities = $illrequest->capabilities;
575 Return a hashref mapping methods to operation names supported by the queried
578 Example return value:
580 { create => "Create Request", confirm => "Progress Request" }
582 NOTE: this module suffers from a confusion in termninology:
584 in _backend_capability, the notion of capability refers to an optional feature
585 that is implemented in core, but might not be supported by a given backend.
587 in capabilities & custom_capability, capability refers to entries in the
588 status_graph (after union between backend and core).
590 The easiest way to fix this would be to fix the terminology in
591 capabilities & custom_capability and their callers.
596 my ( $self, $status ) = @_;
597 # Generate up to date status_graph
598 my $status_graph = $self->_status_graph_union(
599 $self->_core_status_graph,
600 $self->_backend->status_graph({
605 # Extract available actions from graph.
606 return $status_graph->{$status} if $status;
607 # Or return entire graph.
608 return $status_graph;
611 =head3 custom_capability
613 Return the result of invoking $CANDIDATE on this request's backend with
614 $PARAMS, or 0 if $CANDIDATE is an unknown method on backend.
616 NOTE: this module suffers from a confusion in termninology:
618 in _backend_capability, the notion of capability refers to an optional feature
619 that is implemented in core, but might not be supported by a given backend.
621 in capabilities & custom_capability, capability refers to entries in the
622 status_graph (after union between backend and core).
624 The easiest way to fix this would be to fix the terminology in
625 capabilities & custom_capability and their callers.
629 sub custom_capability {
630 my ( $self, $candidate, $params ) = @_;
631 foreach my $capability ( values %{$self->capabilities} ) {
632 if ( $candidate eq $capability->{method} ) {
634 $self->_backend->$candidate({
638 return $self->expandTemplate($response);
644 =head3 available_backends
646 Return a list of available backends.
650 sub available_backends {
651 my ( $self, $reduced ) = @_;
652 my $backends = $self->_config->available_backends($reduced);
656 =head3 available_actions
658 Return a list of available actions.
662 sub available_actions {
664 my $current_action = $self->capabilities($self->status);
665 my @available_actions = map { $self->capabilities($_) }
666 @{$current_action->{next_actions}};
667 return \@available_actions;
670 =head3 mark_completed
672 Mark a request as completed (status = COMP).
678 $self->status('COMP')->store;
679 $self->completed(DateTime->now)->store;
684 method => 'mark_completed',
690 =head2 backend_migrate
692 Migrate a request from one backend to another.
696 sub backend_migrate {
697 my ( $self, $params ) = @_;
699 my $response = $self->_backend_capability('migrate',{
703 return $self->expandTemplate($response) if $response;
707 =head2 backend_confirm
709 Confirm a request. The backend handles setting of mandatory fields in the commit stage:
715 =item * accessurl, cost (if available).
721 sub backend_confirm {
722 my ( $self, $params ) = @_;
724 my $response = $self->_backend->confirm({
728 return $self->expandTemplate($response);
731 =head3 backend_update_status
735 sub backend_update_status {
736 my ( $self, $params ) = @_;
737 return $self->expandTemplate($self->_backend->update_status($params));
740 =head3 backend_cancel
742 my $ILLResponse = $illRequest->backend_cancel;
744 The standard interface method allowing for request cancellation.
749 my ( $self, $params ) = @_;
751 my $result = $self->_backend->cancel({
756 return $self->expandTemplate($result);
761 my $renew_response = $illRequest->backend_renew;
763 The standard interface method allowing for request renewal queries.
769 return $self->expandTemplate(
770 $self->_backend->renew({
776 =head3 backend_create
778 my $create_response = $abstractILL->backend_create($params);
780 Return an array of Record objects created by querying our backend with
783 In the context of the other ILL methods, this is a special method: we only
784 pass it $params, as it does not yet have any other data associated with it.
789 my ( $self, $params ) = @_;
791 # Establish whether we need to do a generic copyright clearance.
792 if ($params->{opac}) {
793 if ( ( !$params->{stage} || $params->{stage} eq 'init' )
794 && C4::Context->preference("ILLModuleCopyrightClearance") ) {
800 stage => 'copyrightclearance',
803 backend => $self->_backend->name
806 } elsif ( defined $params->{stage}
807 && $params->{stage} eq 'copyrightclearance' ) {
808 $params->{stage} = 'init';
811 # First perform API action, then...
816 my $result = $self->_backend->create($args);
818 # ... simple case: we're not at 'commit' stage.
819 my $stage = $result->{stage};
820 return $self->expandTemplate($result)
821 unless ( 'commit' eq $stage );
823 # ... complex case: commit!
825 # Do we still have space for an ILL or should we queue?
826 my $permitted = $self->check_limits(
827 { patron => $self->patron }, { librarycode => $self->branchcode }
830 # Now augment our committed request.
832 $result->{permitted} = $permitted; # Queue request?
836 # ...Updating status!
837 $self->status('QUEUED')->store unless ( $permitted );
839 ## Handle Unmediated ILLs
841 # For the unmediated workflow we only need to delegate to our backend. If
842 # that backend supports unmediateld_ill, it will do its thing and return a
843 # proper response. If it doesn't then _backend_capability returns 0, so
844 # we keep the current result.
845 if ( C4::Context->preference("ILLModuleUnmediated") && $permitted ) {
846 my $unmediated_result = $self->_backend_capability(
850 $result = $unmediated_result if $unmediated_result;
853 return $self->expandTemplate($result);
856 =head3 expandTemplate
858 my $params = $abstract->expandTemplate($params);
860 Return a version of $PARAMS augmented with our required template path.
865 my ( $self, $params ) = @_;
866 my $backend = $self->_backend->name;
867 # Generate path to file to load
868 my $backend_dir = $self->_config->backend_dir;
869 my $backend_tmpl = join "/", $backend_dir, $backend;
870 my $intra_tmpl = join "/", $backend_tmpl, "intra-includes",
871 ( $params->{method}//q{} ) . ".inc";
872 my $opac_tmpl = join "/", $backend_tmpl, "opac-includes",
873 ( $params->{method}//q{} ) . ".inc";
875 $params->{template} = $intra_tmpl;
876 $params->{opac_template} = $opac_tmpl;
880 #### Abstract Imports
884 my $limit_rules = $abstract->getLimits( {
885 type => 'brw_cat' | 'branch',
889 Return the ILL limit rules for the supplied combination of type / value.
891 As the config may have no rules for this particular type / value combination,
892 or for the default, we must define fall-back values here.
897 my ( $self, $params ) = @_;
898 my $limits = $self->_config->getLimitRules($params->{type});
900 if ( defined $params->{value}
901 && defined $limits->{$params->{value}} ) {
902 return $limits->{$params->{value}};
905 return $limits->{default} || { count => -1, method => 'active' };
911 my $prefix = $abstract->getPrefix( {
912 branch => $branch_code
915 Return the ILL prefix as defined by our $params: either per borrower category,
916 per branch or the default.
921 my ( $self, $params ) = @_;
922 my $brn_prefixes = $self->_config->getPrefixes();
923 return $brn_prefixes->{$params->{branch}} || ""; # "the empty prefix"
928 my $type = $abstract->get_type();
930 Return a string representing the material type of this request or undef
936 my $attr = $self->illrequestattributes->find({ type => 'type'});
941 #### Illrequests Imports
945 my $ok = $illRequests->check_limits( {
946 borrower => $borrower,
947 branchcode => 'branchcode' | undef,
950 Given $PARAMS, a hashref containing a $borrower object and a $branchcode,
951 see whether we are still able to place ILLs.
953 LimitRules are derived from koha-conf.xml:
954 + default limit counts, and counting method
955 + branch specific limit counts & counting method
956 + borrower category specific limit counts & counting method
957 + err on the side of caution: a counting fail will cause fail, even if
958 the other counts passes.
963 my ( $self, $params ) = @_;
964 my $patron = $params->{patron};
965 my $branchcode = $params->{librarycode} || $patron->branchcode;
967 # Establish maximum number of allowed requests
968 my ( $branch_rules, $brw_rules ) = (
975 value => $patron->categorycode,
978 my ( $branch_limit, $brw_limit )
979 = ( $branch_rules->{count}, $brw_rules->{count} );
980 # Establish currently existing requests
981 my ( $branch_count, $brw_count ) = (
982 $self->_limit_counter(
983 $branch_rules->{method}, { branchcode => $branchcode }
985 $self->_limit_counter(
986 $brw_rules->{method}, { borrowernumber => $patron->borrowernumber }
991 # A limit of -1 means no limit exists.
992 # We return blocked if either branch limit or brw limit is reached.
993 if ( ( $branch_limit != -1 && $branch_limit <= $branch_count )
994 || ( $brw_limit != -1 && $brw_limit <= $brw_count ) ) {
1001 sub _limit_counter {
1002 my ( $self, $method, $target ) = @_;
1004 # Establish parameters of counts
1006 if ($method && $method eq 'annual') {
1007 $resultset = Koha::Illrequests->search({
1010 \"YEAR(placed) = YEAR(NOW())"
1013 } else { # assume 'active'
1014 # XXX: This status list is ugly. There should be a method in config
1016 my $where = { status => { -not_in => [ 'QUEUED', 'COMP' ] } };
1017 $resultset = Koha::Illrequests->search({ %{$target}, %{$where} });
1021 return $resultset->count;
1024 =head3 requires_moderation
1026 my $status = $illRequest->requires_moderation;
1028 Return the name of the status if moderation by staff is required; or 0
1033 sub requires_moderation {
1035 my $require_moderation = {
1036 'CANCREQ' => 'CANCREQ',
1038 return $require_moderation->{$self->status};
1043 my $stage_summary = $request->check_out;
1045 Handle the check_out method. The first stage involves gathering the required
1046 data from the user via a form, the second stage creates an item and tries to
1047 issue it to the patron. If successful, it notifies the patron, then it
1048 returns a summary of how things went
1053 my ( $self, $params ) = @_;
1055 # Objects required by the template
1056 my $itemtypes = Koha::ItemTypes->search(
1058 { order_by => ['description'] }
1060 my $libraries = Koha::Libraries->search(
1062 { order_by => ['branchcode'] }
1064 my $biblio = Koha::Biblios->find({
1065 biblionumber => $self->biblio_id
1067 # Find all statistical patrons
1068 my $statistical_patrons = Koha::Patrons->search(
1069 { 'category_type' => 'x' },
1070 { join => { 'categorycode' => 'borrowers' } }
1073 if (!$params->{stage} || $params->{stage} eq 'init') {
1074 # Present a form to gather the required data
1076 # We may be viewing this page having previously tried to issue
1077 # the item (in which case, we may already have created an item)
1078 # so we pass the biblio for this request
1080 method => 'check_out',
1083 itemtypes => $itemtypes,
1084 libraries => $libraries,
1085 statistical => $statistical_patrons,
1089 } elsif ($params->{stage} eq 'form') {
1090 # Validate what we've got and return with an error if we fail
1092 if (!$params->{item_type} || length $params->{item_type} == 0) {
1093 $errors->{item_type} = 1;
1095 if ($params->{inhouse} && length $params->{inhouse} > 0) {
1096 my $patron_count = Koha::Patrons->search({
1097 cardnumber => $params->{inhouse}
1099 if ($patron_count != 1) {
1100 $errors->{inhouse} = 1;
1104 # Check we don't have more than one item for this bib,
1105 # if we do, something very odd is going on
1106 # Having 1 is OK, it means we're likely trying to issue
1107 # following a previously failed attempt, the item exists
1109 my @items = $biblio->items->as_list;
1110 my $item_count = scalar @items;
1111 if ($item_count > 1) {
1112 $errors->{itemcount} = 1;
1115 # Failed validation, go back to the form
1118 method => 'check_out',
1122 statistical => $statistical_patrons,
1123 itemtypes => $itemtypes,
1124 libraries => $libraries,
1133 # Create an item if one doesn't already exist,
1134 # if one does, use that
1136 if ($item_count == 0) {
1138 homebranch => $params->{branchcode},
1139 holdingbranch => $params->{branchcode},
1140 location => $params->{branchcode},
1141 itype => $params->{item_type},
1142 barcode => 'ILL-' . $self->illrequest_id
1144 my (undef, undef, $item_no) =
1145 AddItem($item_hash, $self->biblio_id);
1146 $itemnumber = $item_no;
1148 $itemnumber = $items[0]->itemnumber;
1150 # Check we have an item before going forward
1153 method => 'check_out',
1157 itemtypes => $itemtypes,
1158 libraries => $libraries,
1159 statistical => $statistical_patrons,
1160 errors => { item_creation => 1 }
1167 # Gather what we need
1168 my $target_item = Koha::Items->find( $itemnumber );
1169 # Determine who we're issuing to
1170 my $patron = $params->{inhouse} && length $params->{inhouse} > 0 ?
1171 Koha::Patrons->find({ cardnumber => $params->{inhouse} }) :
1176 scalar $target_item->barcode
1178 if ($params->{duedate} && length $params->{duedate} > 0) {
1179 push @issue_args, $params->{duedate};
1181 # Check if we can check out
1182 my ( $error, $confirm, $alerts, $messages ) =
1183 C4::Circulation::CanBookBeIssued(@issue_args);
1185 # If we got anything back saying we can't check out,
1186 # return it to the template
1188 if ( $error && %{$error} ) { $problems->{error} = $error };
1189 if ( $confirm && %{$confirm} ) { $problems->{confirm} = $confirm };
1190 if ( $alerts && %{$alerts} ) { $problems->{alerts} = $alerts };
1191 if ( $messages && %{$messages} ) { $problems->{messages} = $messages };
1195 method => 'check_out',
1199 itemtypes => $itemtypes,
1200 libraries => $libraries,
1201 statistical => $statistical_patrons,
1204 check_out_errors => $problems
1209 # We can allegedly check out, so make it so
1210 # For some reason, AddIssue requires an unblessed Patron
1211 $issue_args[0] = $patron->unblessed;
1212 my $issue = C4::Circulation::AddIssue(@issue_args);
1214 if ($issue && %{$issue}) {
1215 # Update the request status
1216 $self->status('CHK')->store;
1218 method => 'check_out',
1219 stage => 'done_check_out',
1228 method => 'check_out',
1232 itemtypes => $itemtypes,
1233 libraries => $libraries,
1234 errors => { item_check_out => 1 }
1242 =head3 generic_confirm
1244 my $stage_summary = $illRequest->generic_confirm;
1246 Handle the generic_confirm extended method. The first stage involves creating
1247 a template email for the end user to edit in the browser. The second stage
1248 attempts to submit the email.
1252 sub generic_confirm {
1253 my ( $self, $params ) = @_;
1254 my $branch = Koha::Libraries->find($params->{current_branchcode})
1255 || die "Invalid current branchcode. Are you logged in as the database user?";
1256 if ( !$params->{stage}|| $params->{stage} eq 'init' ) {
1257 my $draft->{subject} = "ILL Request";
1258 $draft->{body} = <<EOF;
1261 We would like to request an interlibrary loan for a title matching the
1262 following description:
1266 my $details = $self->metadata;
1267 while (my ($title, $value) = each %{$details}) {
1268 $draft->{body} .= " - " . $title . ": " . $value . "\n"
1271 $draft->{body} .= <<EOF;
1273 Please let us know if you are able to supply this to us.
1279 my @address = map { $branch->$_ }
1280 qw/ branchname branchaddress1 branchaddress2 branchaddress3
1281 branchzip branchcity branchstate branchcountry branchphone
1284 foreach my $line ( @address ) {
1285 $address .= $line . "\n" if $line;
1288 $draft->{body} .= $address;
1290 my $partners = Koha::Patrons->search({
1291 categorycode => $self->_config->partner_code
1297 method => 'generic_confirm',
1301 partners => $partners,
1305 } elsif ( 'draft' eq $params->{stage} ) {
1306 # Create the to header
1307 my $to = $params->{partners};
1308 if ( defined $to ) {
1309 $to =~ s/^\x00//; # Strip leading NULLs
1310 $to =~ s/\x00/; /; # Replace others with '; '
1312 Koha::Exceptions::Ill::NoTargetEmail->throw(
1313 "No target email addresses found. Either select at least one partner or check your ILL partner library records.")
1315 # Create the from, replyto and sender headers
1316 my $from = $branch->branchemail;
1317 my $replyto = $branch->branchreplyto || $from;
1318 Koha::Exceptions::Ill::NoLibraryEmail->throw(
1319 "Your library has no usable email address. Please set it.")
1323 my $message = Koha::Email->new;
1324 my %mail = $message->create_message_headers(
1328 replyto => $replyto,
1329 subject => Encode::encode( "utf8", $params->{subject} ),
1330 message => Encode::encode( "utf8", $params->{body} ),
1331 contenttype => 'text/plain',
1335 my $result = sendmail(%mail);
1337 $self->status("GENREQ")->store;
1338 $self->_backend_capability(
1339 'set_requested_partners',
1349 method => 'generic_confirm',
1356 status => 'email_failed',
1357 message => $Mail::Sendmail::error,
1358 method => 'generic_confirm',
1363 die "Unknown stage, should not have happened."
1369 my $prefix = $record->id_prefix;
1371 Return the prefix appropriate for the current Illrequest as derived from the
1372 borrower and branch associated with this request's Status, and the config
1379 my $prefix = $self->getPrefix( {
1380 branch => $self->branchcode,
1382 $prefix .= "-" if ( $prefix );
1388 my $params = $illRequest->_censor($params);
1390 Return $params, modified to reflect our censorship requirements.
1395 my ( $self, $params ) = @_;
1396 my $censorship = $self->_config->censorship;
1397 $params->{censor_notes_staff} = $censorship->{censor_notes_staff}
1398 if ( $params->{opac} );
1399 $params->{display_reply_date} = ( $censorship->{censor_reply_date} ) ? 0 : 1;
1408 Overloaded I<store> method that, in addition to performing the 'store',
1409 possibly records the fact that something happened
1414 my ( $self, $attrs ) = @_;
1416 my $ret = $self->SUPER::store;
1418 $attrs->{log_origin} = 'core';
1420 if ($ret && defined $attrs) {
1421 my $logger = Koha::Illrequest::Logger->new;
1422 $logger->log_maybe({
1431 =head3 requested_partners
1433 my $partners_string = $illRequest->requested_partners;
1435 Return the string representing the email addresses of the partners to
1436 whom a request has been sent
1440 sub requested_partners {
1442 return $self->_backend_capability(
1443 'get_requested_partners',
1444 { request => $self }
1450 $json = $illrequest->TO_JSON
1452 Overloaded I<TO_JSON> method that takes care of inserting calculated values
1453 into the unblessed representation of the object.
1455 TODO: This method does nothing and is not called anywhere. However, bug 74325
1456 touches it, so keeping this for now until both this and bug 74325 are merged,
1457 at which point we can sort it out and remove it completely
1462 my ( $self, $embed ) = @_;
1464 my $object = $self->SUPER::TO_JSON();
1469 =head2 Internal methods
1476 return 'Illrequest';
1481 Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com>
1482 Andrew Isherwood <andrew.isherwood@ptfs-europe.com>