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::Circulation qw( CanBookBeIssued AddIssue );
44 use base qw(Koha::Object);
48 Koha::Illrequest - Koha Illrequest Object class
52 An ILLRequest consists of two parts; the Illrequest Koha::Object, and a series
53 of related Illrequestattributes.
55 The former encapsulates the basic necessary information that any ILL requires
56 to be usable in Koha. The latter is a set of additional properties used by
59 The former subsumes the legacy "Status" object. The latter remains
60 encapsulated in the "Record" object.
64 - Anything invoking the ->status method; annotated with:
65 + # Old use of ->status !
69 =head2 Backend API Response Principles
71 All methods should return a hashref in the following format:
77 This should be set to 1 if an error was encountered.
81 The status should be a string from the list of statuses detailed below.
85 The message is a free text field that can be passed on to the end user.
89 The value returned by the method.
93 =head2 Interface Status Messages
97 =item * branch_address_incomplete
99 An interface request has determined branch address details are incomplete.
101 =item * cancel_success
103 The interface's cancel_request method was successful in cancelling the
104 Illrequest using the API.
108 The interface's cancel_request method failed to cancel the Illrequest using
113 The interface's request method returned saying that the desired item is not
114 available for request.
122 my $statusalias = $request->statusalias;
124 Returns a request's status alias, as a Koha::AuthorisedValue instance
125 or implicit undef. This is distinct from status_alias, which only returns
126 the value in the status_alias column, this method returns the entire
127 AuthorisedValue object
133 return unless $self->status_alias;
134 # We can't know which result is the right one if there are multiple
135 # ILLSTATUS authorised values with the same authorised_value column value
136 # so we just use the first
137 return Koha::AuthorisedValues->search({
138 branchcode => $self->branchcode,
139 category => 'ILLSTATUS',
140 authorised_value => $self->SUPER::status_alias
144 =head3 illrequestattributes
148 sub illrequestattributes {
150 return Koha::Illrequestattributes->_new_from_dbic(
151 scalar $self->_result->illrequestattributes
161 return Koha::Illcomments->_new_from_dbic(
162 scalar $self->_result->illcomments
172 my $logger = Koha::Illrequest::Logger->new;
173 return $logger->get_request_logs($self);
182 return Koha::Patron->_new_from_dbic(
183 scalar $self->_result->borrowernumber
189 $Illrequest->status_alias(143);
191 Overloaded getter/setter for status_alias,
192 that only returns authorised values from the
193 correct category and records the fact that the status has changed
198 my ($self, $new_status_alias) = @_;
200 my $current_status_alias = $self->SUPER::status_alias;
202 if ($new_status_alias) {
203 # Keep a record of the previous status before we change it,
205 $self->{previous_status} = $current_status_alias ?
206 $current_status_alias :
207 scalar $self->status;
208 # This is hackery to enable us to undefine
209 # status_alias, since we need to have an overloaded
210 # status_alias method to get us around the problem described
212 # https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=20581#c156
213 # We need a way of accepting implied undef, so we can nullify
214 # the status_alias column, when called from $self->status
215 my $val = $new_status_alias eq "-1" ? undef : $new_status_alias;
216 my $ret = $self->SUPER::status_alias($val);
217 my $val_to_log = $val ? $new_status_alias : scalar $self->status;
219 my $logger = Koha::Illrequest::Logger->new;
220 $logger->log_status_change({
225 delete $self->{previous_status};
229 # We can't know which result is the right one if there are multiple
230 # ILLSTATUS authorised values with the same authorised_value column value
231 # so we just use the first
232 my $alias = Koha::AuthorisedValues->search({
233 branchcode => $self->branchcode,
234 category => 'ILLSTATUS',
235 authorised_value => $self->SUPER::status_alias
238 return $alias->authorised_value;
246 $Illrequest->status('CANREQ');
248 Overloaded getter/setter for request status,
249 also nullifies status_alias and records the fact that the status has changed
254 my ( $self, $new_status) = @_;
256 my $current_status = $self->SUPER::status;
257 my $current_status_alias = $self->SUPER::status_alias;
260 # Keep a record of the previous status before we change it,
262 $self->{previous_status} = $current_status_alias ?
263 $current_status_alias :
265 my $ret = $self->SUPER::status($new_status)->store;
266 if ($current_status_alias) {
267 # This is hackery to enable us to undefine
268 # status_alias, since we need to have an overloaded
269 # status_alias method to get us around the problem described
271 # https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=20581#c156
272 # We need a way of passing implied undef to nullify status_alias
273 # so we pass -1, which is special cased in the overloaded setter
274 $self->status_alias("-1");
276 my $logger = Koha::Illrequest::Logger->new;
277 $logger->log_status_change({
282 delete $self->{previous_status};
285 return $current_status;
291 Require "Base.pm" from the relevant ILL backend.
296 my ( $self, $backend_id ) = @_;
298 my @raw = qw/Koha Illbackends/; # Base Path
300 my $backend_name = $backend_id || $self->backend;
302 unless ( defined $backend_name && $backend_name ne '' ) {
303 Koha::Exceptions::Ill::InvalidBackendId->throw(
304 "An invalid backend ID was requested ('')");
307 my $location = join "/", @raw, $backend_name, "Base.pm"; # File to load
308 my $backend_class = join "::", @raw, $backend_name, "Base"; # Package name
310 $self->{_my_backend} = $backend_class->new({
311 config => $self->_config,
312 logger => Koha::Illrequest::Logger->new
320 my $backend = $abstract->_backend($new_backend);
321 my $backend = $abstract->_backend;
323 Getter/Setter for our API object.
328 my ( $self, $backend ) = @_;
329 $self->{_my_backend} = $backend if ( $backend );
330 # Dynamically load our backend object, as late as possible.
331 $self->load_backend unless ( $self->{_my_backend} );
332 return $self->{_my_backend};
335 =head3 _backend_capability
337 my $backend_capability_result = $self->_backend_capability($name, $args);
339 This is a helper method to invoke optional capabilities in the backend. If
340 the capability named by $name is not supported, return 0, else invoke it,
341 passing $args along with the invocation, and return its return value.
343 NOTE: this module suffers from a confusion in termninology:
345 in _backend_capability, the notion of capability refers to an optional feature
346 that is implemented in core, but might not be supported by a given backend.
348 in capabilities & custom_capability, capability refers to entries in the
349 status_graph (after union between backend and core).
351 The easiest way to fix this would be to fix the terminology in
352 capabilities & custom_capability and their callers.
356 sub _backend_capability {
357 my ( $self, $name, $args ) = @_;
359 # See if capability is defined in backend
361 $capability = $self->_backend->capabilities($name);
366 if ( $capability && ref($capability) eq 'CODE' ) {
367 return &{$capability}($args);
375 my $config = $abstract->_config($config);
376 my $config = $abstract->_config;
378 Getter/Setter for our config object.
383 my ( $self, $config ) = @_;
384 $self->{_my_config} = $config if ( $config );
385 # Load our config object, as late as possible.
386 unless ( $self->{_my_config} ) {
387 $self->{_my_config} = Koha::Illrequest::Config->new;
389 return $self->{_my_config};
398 return $self->_backend->metadata($self);
401 =head3 _core_status_graph
403 my $core_status_graph = $illrequest->_core_status_graph;
405 Returns ILL module's default status graph. A status graph defines the list of
406 available actions at any stage in the ILL workflow. This is for instance used
407 by the perl script & template to generate the correct buttons to display to
408 the end user at any given point.
412 sub _core_status_graph {
416 prev_actions => [ ], # Actions containing buttons
417 # leading to this status
418 id => 'NEW', # ID of this status
419 name => 'New request', # UI name of this status
420 ui_method_name => 'New request', # UI name of method leading
422 method => 'create', # method to this status
423 next_actions => [ 'REQ', 'GENREQ', 'KILL' ], # buttons to add to all
424 # requests with this status
425 ui_method_icon => 'fa-plus', # UI Style class
428 prev_actions => [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ],
431 ui_method_name => 'Confirm request',
433 next_actions => [ 'REQREV', 'COMP', 'CHK' ],
434 ui_method_icon => 'fa-check',
437 prev_actions => [ 'NEW', 'REQREV' ],
439 name => 'Requested from partners',
440 ui_method_name => 'Place request with partners',
441 method => 'generic_confirm',
442 next_actions => [ 'COMP', 'CHK' ],
443 ui_method_icon => 'fa-send-o',
446 prev_actions => [ 'REQ' ],
448 name => 'Request reverted',
449 ui_method_name => 'Revert Request',
451 next_actions => [ 'REQ', 'GENREQ', 'KILL' ],
452 ui_method_icon => 'fa-times',
457 name => 'Queued request',
460 next_actions => [ 'REQ', 'KILL' ],
464 prev_actions => [ 'NEW' ],
466 name => 'Cancellation requested',
469 next_actions => [ 'KILL', 'REQ' ],
473 prev_actions => [ 'REQ' ],
476 ui_method_name => 'Mark completed',
477 method => 'mark_completed',
478 next_actions => [ 'CHK' ],
479 ui_method_icon => 'fa-check',
482 prev_actions => [ 'QUEUED', 'REQREV', 'NEW', 'CANCREQ' ],
485 ui_method_name => 'Delete request',
488 ui_method_icon => 'fa-trash',
491 prev_actions => [ 'REQ', 'GENREQ', 'COMP' ],
493 name => 'Checked out',
494 ui_method_name => 'Check out',
495 needs_prefs => [ 'CirculateILL' ],
496 needs_perms => [ 'user_circulate_circulate_remaining_permissions' ],
497 # An array of functions that all must return true
498 needs_all => [ sub { my $r = shift; return $r->biblio; } ],
499 method => 'check_out',
501 ui_method_icon => 'fa-upload',
504 prev_actions => [ 'CHK' ],
506 name => 'Returned to library',
507 ui_method_name => 'Check in',
508 method => 'check_in',
509 next_actions => [ 'COMP' ],
510 ui_method_icon => 'fa-download',
515 =head3 _status_graph_union
517 my $status_graph = $illrequest->_status_graph_union($origin, $new_graph);
519 Return a new status_graph, the result of merging $origin & new_graph. This is
520 operation is a union over the sets defied by the two graphs.
522 Each entry in $new_graph is added to $origin. We do not provide a syntax for
523 'subtraction' of entries from $origin.
525 Whilst it is not intended that this works, you can override entries in $origin
526 with entries with the same key in $new_graph. This can lead to problematic
527 behaviour when $new_graph adds an entry, which modifies a dependent entry in
528 $origin, only for the entry in $origin to be replaced later with a new entry
531 NOTE: this procedure does not "re-link" entries in $origin or $new_graph,
532 i.e. each of the graphs need to be correct at the outset of the operation.
536 sub _status_graph_union {
537 my ( $self, $core_status_graph, $backend_status_graph ) = @_;
538 # Create new status graph with:
539 # - all core_status_graph
540 # - for-each each backend_status_graph
541 # + add to new status graph
542 # + for each core prev_action:
543 # * locate core_status
544 # * update next_actions with additional next action.
545 # + for each core next_action:
546 # * locate core_status
547 # * update prev_actions with additional prev action
549 my @core_status_ids = keys %{$core_status_graph};
550 my $status_graph = clone($core_status_graph);
552 foreach my $backend_status_key ( keys %{$backend_status_graph} ) {
553 my $backend_status = $backend_status_graph->{$backend_status_key};
554 # Add to new status graph
555 $status_graph->{$backend_status_key} = $backend_status;
556 # Update all core methods' next_actions.
557 foreach my $prev_action ( @{$backend_status->{prev_actions}} ) {
558 if ( grep { $prev_action eq $_ } @core_status_ids ) {
560 @{$status_graph->{$prev_action}->{next_actions}};
561 push @next_actions, $backend_status_key;
562 $status_graph->{$prev_action}->{next_actions}
566 # Update all core methods' prev_actions
567 foreach my $next_action ( @{$backend_status->{next_actions}} ) {
568 if ( grep { $next_action eq $_ } @core_status_ids ) {
570 @{$status_graph->{$next_action}->{prev_actions}};
571 push @prev_actions, $backend_status_key;
572 $status_graph->{$next_action}->{prev_actions}
578 return $status_graph;
585 my $capabilities = $illrequest->capabilities;
587 Return a hashref mapping methods to operation names supported by the queried
590 Example return value:
592 { create => "Create Request", confirm => "Progress Request" }
594 NOTE: this module suffers from a confusion in termninology:
596 in _backend_capability, the notion of capability refers to an optional feature
597 that is implemented in core, but might not be supported by a given backend.
599 in capabilities & custom_capability, capability refers to entries in the
600 status_graph (after union between backend and core).
602 The easiest way to fix this would be to fix the terminology in
603 capabilities & custom_capability and their callers.
608 my ( $self, $status ) = @_;
609 # Generate up to date status_graph
610 my $status_graph = $self->_status_graph_union(
611 $self->_core_status_graph,
612 $self->_backend->status_graph({
617 # Extract available actions from graph.
618 return $status_graph->{$status} if $status;
619 # Or return entire graph.
620 return $status_graph;
623 =head3 custom_capability
625 Return the result of invoking $CANDIDATE on this request's backend with
626 $PARAMS, or 0 if $CANDIDATE is an unknown method on backend.
628 NOTE: this module suffers from a confusion in termninology:
630 in _backend_capability, the notion of capability refers to an optional feature
631 that is implemented in core, but might not be supported by a given backend.
633 in capabilities & custom_capability, capability refers to entries in the
634 status_graph (after union between backend and core).
636 The easiest way to fix this would be to fix the terminology in
637 capabilities & custom_capability and their callers.
641 sub custom_capability {
642 my ( $self, $candidate, $params ) = @_;
643 foreach my $capability ( values %{$self->capabilities} ) {
644 if ( $candidate eq $capability->{method} ) {
646 $self->_backend->$candidate({
650 return $self->expandTemplate($response);
656 =head3 available_backends
658 Return a list of available backends.
662 sub available_backends {
663 my ( $self, $reduced ) = @_;
664 my $backends = $self->_config->available_backends($reduced);
668 =head3 available_actions
670 Return a list of available actions.
674 sub available_actions {
676 my $current_action = $self->capabilities($self->status);
677 my @available_actions = map { $self->capabilities($_) }
678 @{$current_action->{next_actions}};
679 return \@available_actions;
682 =head3 mark_completed
684 Mark a request as completed (status = COMP).
690 $self->status('COMP')->store;
691 $self->completed(DateTime->now)->store;
696 method => 'mark_completed',
702 =head2 backend_migrate
704 Migrate a request from one backend to another.
708 sub backend_migrate {
709 my ( $self, $params ) = @_;
711 my $response = $self->_backend_capability('migrate',{
715 return $self->expandTemplate($response) if $response;
719 =head2 backend_confirm
721 Confirm a request. The backend handles setting of mandatory fields in the commit stage:
727 =item * accessurl, cost (if available).
733 sub backend_confirm {
734 my ( $self, $params ) = @_;
736 my $response = $self->_backend->confirm({
740 return $self->expandTemplate($response);
743 =head3 backend_update_status
747 sub backend_update_status {
748 my ( $self, $params ) = @_;
749 return $self->expandTemplate($self->_backend->update_status($params));
752 =head3 backend_cancel
754 my $ILLResponse = $illRequest->backend_cancel;
756 The standard interface method allowing for request cancellation.
761 my ( $self, $params ) = @_;
763 my $result = $self->_backend->cancel({
768 return $self->expandTemplate($result);
773 my $renew_response = $illRequest->backend_renew;
775 The standard interface method allowing for request renewal queries.
781 return $self->expandTemplate(
782 $self->_backend->renew({
788 =head3 backend_create
790 my $create_response = $abstractILL->backend_create($params);
792 Return an array of Record objects created by querying our backend with
795 In the context of the other ILL methods, this is a special method: we only
796 pass it $params, as it does not yet have any other data associated with it.
801 my ( $self, $params ) = @_;
803 # Establish whether we need to do a generic copyright clearance.
804 if ($params->{opac}) {
805 if ( ( !$params->{stage} || $params->{stage} eq 'init' )
806 && C4::Context->preference("ILLModuleCopyrightClearance") ) {
812 stage => 'copyrightclearance',
815 backend => $self->_backend->name
818 } elsif ( defined $params->{stage}
819 && $params->{stage} eq 'copyrightclearance' ) {
820 $params->{stage} = 'init';
823 # First perform API action, then...
828 my $result = $self->_backend->create($args);
830 # ... simple case: we're not at 'commit' stage.
831 my $stage = $result->{stage};
832 return $self->expandTemplate($result)
833 unless ( 'commit' eq $stage );
835 # ... complex case: commit!
837 # Do we still have space for an ILL or should we queue?
838 my $permitted = $self->check_limits(
839 { patron => $self->patron }, { librarycode => $self->branchcode }
842 # Now augment our committed request.
844 $result->{permitted} = $permitted; # Queue request?
848 # ...Updating status!
849 $self->status('QUEUED')->store unless ( $permitted );
851 ## Handle Unmediated ILLs
853 # For the unmediated workflow we only need to delegate to our backend. If
854 # that backend supports unmediateld_ill, it will do its thing and return a
855 # proper response. If it doesn't then _backend_capability returns 0, so
856 # we keep the current result.
857 if ( C4::Context->preference("ILLModuleUnmediated") && $permitted ) {
858 my $unmediated_result = $self->_backend_capability(
862 $result = $unmediated_result if $unmediated_result;
865 return $self->expandTemplate($result);
868 =head3 expandTemplate
870 my $params = $abstract->expandTemplate($params);
872 Return a version of $PARAMS augmented with our required template path.
877 my ( $self, $params ) = @_;
878 my $backend = $self->_backend->name;
879 # Generate path to file to load
880 my $backend_dir = $self->_config->backend_dir;
881 my $backend_tmpl = join "/", $backend_dir, $backend;
882 my $intra_tmpl = join "/", $backend_tmpl, "intra-includes",
883 ( $params->{method}//q{} ) . ".inc";
884 my $opac_tmpl = join "/", $backend_tmpl, "opac-includes",
885 ( $params->{method}//q{} ) . ".inc";
887 $params->{template} = $intra_tmpl;
888 $params->{opac_template} = $opac_tmpl;
892 #### Abstract Imports
896 my $limit_rules = $abstract->getLimits( {
897 type => 'brw_cat' | 'branch',
901 Return the ILL limit rules for the supplied combination of type / value.
903 As the config may have no rules for this particular type / value combination,
904 or for the default, we must define fall-back values here.
909 my ( $self, $params ) = @_;
910 my $limits = $self->_config->getLimitRules($params->{type});
912 if ( defined $params->{value}
913 && defined $limits->{$params->{value}} ) {
914 return $limits->{$params->{value}};
917 return $limits->{default} || { count => -1, method => 'active' };
923 my $prefix = $abstract->getPrefix( {
924 branch => $branch_code
927 Return the ILL prefix as defined by our $params: either per borrower category,
928 per branch or the default.
933 my ( $self, $params ) = @_;
934 my $brn_prefixes = $self->_config->getPrefixes();
935 return $brn_prefixes->{$params->{branch}} || ""; # "the empty prefix"
940 my $type = $abstract->get_type();
942 Return a string representing the material type of this request or undef
948 my $attr = $self->illrequestattributes->find({ type => 'type'});
953 #### Illrequests Imports
957 my $ok = $illRequests->check_limits( {
958 borrower => $borrower,
959 branchcode => 'branchcode' | undef,
962 Given $PARAMS, a hashref containing a $borrower object and a $branchcode,
963 see whether we are still able to place ILLs.
965 LimitRules are derived from koha-conf.xml:
966 + default limit counts, and counting method
967 + branch specific limit counts & counting method
968 + borrower category specific limit counts & counting method
969 + err on the side of caution: a counting fail will cause fail, even if
970 the other counts passes.
975 my ( $self, $params ) = @_;
976 my $patron = $params->{patron};
977 my $branchcode = $params->{librarycode} || $patron->branchcode;
979 # Establish maximum number of allowed requests
980 my ( $branch_rules, $brw_rules ) = (
987 value => $patron->categorycode,
990 my ( $branch_limit, $brw_limit )
991 = ( $branch_rules->{count}, $brw_rules->{count} );
992 # Establish currently existing requests
993 my ( $branch_count, $brw_count ) = (
994 $self->_limit_counter(
995 $branch_rules->{method}, { branchcode => $branchcode }
997 $self->_limit_counter(
998 $brw_rules->{method}, { borrowernumber => $patron->borrowernumber }
1002 # Compare and return
1003 # A limit of -1 means no limit exists.
1004 # We return blocked if either branch limit or brw limit is reached.
1005 if ( ( $branch_limit != -1 && $branch_limit <= $branch_count )
1006 || ( $brw_limit != -1 && $brw_limit <= $brw_count ) ) {
1013 sub _limit_counter {
1014 my ( $self, $method, $target ) = @_;
1016 # Establish parameters of counts
1018 if ($method && $method eq 'annual') {
1019 $resultset = Koha::Illrequests->search({
1022 \"YEAR(placed) = YEAR(NOW())"
1025 } else { # assume 'active'
1026 # XXX: This status list is ugly. There should be a method in config
1028 my $where = { status => { -not_in => [ 'QUEUED', 'COMP' ] } };
1029 $resultset = Koha::Illrequests->search({ %{$target}, %{$where} });
1033 return $resultset->count;
1036 =head3 requires_moderation
1038 my $status = $illRequest->requires_moderation;
1040 Return the name of the status if moderation by staff is required; or 0
1045 sub requires_moderation {
1047 my $require_moderation = {
1048 'CANCREQ' => 'CANCREQ',
1050 return $require_moderation->{$self->status};
1055 my $biblio = $request->biblio;
1057 For a given request, return the biblio associated with it,
1058 or undef if none exists
1065 return if !$self->biblio_id;
1067 return Koha::Biblios->find({
1068 biblionumber => $self->biblio_id
1074 my $stage_summary = $request->check_out;
1076 Handle the check_out method. The first stage involves gathering the required
1077 data from the user via a form, the second stage creates an item and tries to
1078 issue it to the patron. If successful, it notifies the patron, then it
1079 returns a summary of how things went
1084 my ( $self, $params ) = @_;
1086 # Objects required by the template
1087 my $itemtypes = Koha::ItemTypes->search(
1089 { order_by => ['description'] }
1091 my $libraries = Koha::Libraries->search(
1093 { order_by => ['branchcode'] }
1095 my $biblio = $self->biblio;
1097 # Find all statistical patrons
1098 my $statistical_patrons = Koha::Patrons->search(
1099 { 'category_type' => 'x' },
1100 { join => { 'categorycode' => 'borrowers' } }
1103 if (!$params->{stage} || $params->{stage} eq 'init') {
1104 # Present a form to gather the required data
1106 # We may be viewing this page having previously tried to issue
1107 # the item (in which case, we may already have created an item)
1108 # so we pass the biblio for this request
1110 method => 'check_out',
1113 itemtypes => $itemtypes,
1114 libraries => $libraries,
1115 statistical => $statistical_patrons,
1119 } elsif ($params->{stage} eq 'form') {
1120 # Validate what we've got and return with an error if we fail
1122 if (!$params->{item_type} || length $params->{item_type} == 0) {
1123 $errors->{item_type} = 1;
1125 if ($params->{inhouse} && length $params->{inhouse} > 0) {
1126 my $patron_count = Koha::Patrons->search({
1127 cardnumber => $params->{inhouse}
1129 if ($patron_count != 1) {
1130 $errors->{inhouse} = 1;
1134 # Check we don't have more than one item for this bib,
1135 # if we do, something very odd is going on
1136 # Having 1 is OK, it means we're likely trying to issue
1137 # following a previously failed attempt, the item exists
1139 my @items = $biblio->items->as_list;
1140 my $item_count = scalar @items;
1141 if ($item_count > 1) {
1142 $errors->{itemcount} = 1;
1145 # Failed validation, go back to the form
1148 method => 'check_out',
1152 statistical => $statistical_patrons,
1153 itemtypes => $itemtypes,
1154 libraries => $libraries,
1163 # Create an item if one doesn't already exist,
1164 # if one does, use that
1166 if ($item_count == 0) {
1168 biblionumber => $self->biblio_id,
1169 homebranch => $params->{branchcode},
1170 holdingbranch => $params->{branchcode},
1171 location => $params->{branchcode},
1172 itype => $params->{item_type},
1173 barcode => 'ILL-' . $self->illrequest_id
1175 my $item = Koha::Item->new($item_hash);
1176 $itemnumber = $item->itemnumber;
1178 $itemnumber = $items[0]->itemnumber;
1180 # Check we have an item before going forward
1183 method => 'check_out',
1187 itemtypes => $itemtypes,
1188 libraries => $libraries,
1189 statistical => $statistical_patrons,
1190 errors => { item_creation => 1 }
1197 # Gather what we need
1198 my $target_item = Koha::Items->find( $itemnumber );
1199 # Determine who we're issuing to
1200 my $patron = $params->{inhouse} && length $params->{inhouse} > 0 ?
1201 Koha::Patrons->find({ cardnumber => $params->{inhouse} }) :
1206 scalar $target_item->barcode
1208 if ($params->{duedate} && length $params->{duedate} > 0) {
1209 push @issue_args, $params->{duedate};
1211 # Check if we can check out
1212 my ( $error, $confirm, $alerts, $messages ) =
1213 C4::Circulation::CanBookBeIssued(@issue_args);
1215 # If we got anything back saying we can't check out,
1216 # return it to the template
1218 if ( $error && %{$error} ) { $problems->{error} = $error };
1219 if ( $confirm && %{$confirm} ) { $problems->{confirm} = $confirm };
1220 if ( $alerts && %{$alerts} ) { $problems->{alerts} = $alerts };
1221 if ( $messages && %{$messages} ) { $problems->{messages} = $messages };
1225 method => 'check_out',
1229 itemtypes => $itemtypes,
1230 libraries => $libraries,
1231 statistical => $statistical_patrons,
1234 check_out_errors => $problems
1239 # We can allegedly check out, so make it so
1240 # For some reason, AddIssue requires an unblessed Patron
1241 $issue_args[0] = $patron->unblessed;
1242 my $issue = C4::Circulation::AddIssue(@issue_args);
1245 # Update the request status
1246 $self->status('CHK')->store;
1248 method => 'check_out',
1249 stage => 'done_check_out',
1258 method => 'check_out',
1262 itemtypes => $itemtypes,
1263 libraries => $libraries,
1264 errors => { item_check_out => 1 }
1272 =head3 generic_confirm
1274 my $stage_summary = $illRequest->generic_confirm;
1276 Handle the generic_confirm extended method. The first stage involves creating
1277 a template email for the end user to edit in the browser. The second stage
1278 attempts to submit the email.
1282 sub generic_confirm {
1283 my ( $self, $params ) = @_;
1284 my $branch = Koha::Libraries->find($params->{current_branchcode})
1285 || die "Invalid current branchcode. Are you logged in as the database user?";
1286 if ( !$params->{stage}|| $params->{stage} eq 'init' ) {
1287 my $draft->{subject} = "ILL Request";
1288 $draft->{body} = <<EOF;
1291 We would like to request an interlibrary loan for a title matching the
1292 following description:
1296 my $details = $self->metadata;
1297 while (my ($title, $value) = each %{$details}) {
1298 $draft->{body} .= " - " . $title . ": " . $value . "\n"
1301 $draft->{body} .= <<EOF;
1303 Please let us know if you are able to supply this to us.
1309 my @address = map { $branch->$_ }
1310 qw/ branchname branchaddress1 branchaddress2 branchaddress3
1311 branchzip branchcity branchstate branchcountry branchphone
1314 foreach my $line ( @address ) {
1315 $address .= $line . "\n" if $line;
1318 $draft->{body} .= $address;
1320 my $partners = Koha::Patrons->search({
1321 categorycode => $self->_config->partner_code
1327 method => 'generic_confirm',
1331 partners => $partners,
1335 } elsif ( 'draft' eq $params->{stage} ) {
1336 # Create the to header
1337 my $to = $params->{partners};
1338 if ( defined $to ) {
1339 $to =~ s/^\x00//; # Strip leading NULLs
1340 $to =~ s/\x00/; /; # Replace others with '; '
1342 Koha::Exceptions::Ill::NoTargetEmail->throw(
1343 "No target email addresses found. Either select at least one partner or check your ILL partner library records.")
1345 # Create the from, replyto and sender headers
1346 my $from = $branch->branchemail;
1347 my $replyto = $branch->branchreplyto || $from;
1348 Koha::Exceptions::Ill::NoLibraryEmail->throw(
1349 "Your library has no usable email address. Please set it.")
1353 my $message = Koha::Email->new;
1354 my %mail = $message->create_message_headers(
1358 replyto => $replyto,
1359 subject => Encode::encode( "utf8", $params->{subject} ),
1360 message => Encode::encode( "utf8", $params->{body} ),
1361 contenttype => 'text/plain',
1365 my $result = sendmail(%mail);
1367 $self->status("GENREQ")->store;
1368 $self->_backend_capability(
1369 'set_requested_partners',
1379 method => 'generic_confirm',
1386 status => 'email_failed',
1387 message => $Mail::Sendmail::error,
1388 method => 'generic_confirm',
1393 die "Unknown stage, should not have happened."
1399 my $prefix = $record->id_prefix;
1401 Return the prefix appropriate for the current Illrequest as derived from the
1402 borrower and branch associated with this request's Status, and the config
1409 my $prefix = $self->getPrefix( {
1410 branch => $self->branchcode,
1412 $prefix .= "-" if ( $prefix );
1418 my $params = $illRequest->_censor($params);
1420 Return $params, modified to reflect our censorship requirements.
1425 my ( $self, $params ) = @_;
1426 my $censorship = $self->_config->censorship;
1427 $params->{censor_notes_staff} = $censorship->{censor_notes_staff}
1428 if ( $params->{opac} );
1429 $params->{display_reply_date} = ( $censorship->{censor_reply_date} ) ? 0 : 1;
1438 Overloaded I<store> method that, in addition to performing the 'store',
1439 possibly records the fact that something happened
1444 my ( $self, $attrs ) = @_;
1446 my $ret = $self->SUPER::store;
1448 $attrs->{log_origin} = 'core';
1450 if ($ret && defined $attrs) {
1451 my $logger = Koha::Illrequest::Logger->new;
1452 $logger->log_maybe({
1461 =head3 requested_partners
1463 my $partners_string = $illRequest->requested_partners;
1465 Return the string representing the email addresses of the partners to
1466 whom a request has been sent
1470 sub requested_partners {
1472 return $self->_backend_capability(
1473 'get_requested_partners',
1474 { request => $self }
1480 $json = $illrequest->TO_JSON
1482 Overloaded I<TO_JSON> method that takes care of inserting calculated values
1483 into the unblessed representation of the object.
1485 TODO: This method does nothing and is not called anywhere. However, bug 74325
1486 touches it, so keeping this for now until both this and bug 74325 are merged,
1487 at which point we can sort it out and remove it completely
1492 my ( $self, $embed ) = @_;
1494 my $object = $self->SUPER::TO_JSON();
1499 =head2 Internal methods
1506 return 'Illrequest';
1511 Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com>
1512 Andrew Isherwood <andrew.isherwood@ptfs-europe.com>