X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=Koha%2FIllrequest.pm;h=2eaa5accda4d4237d1d64e8603978147197ab274;hb=9d6d641d1f8b77271800f43bc027b651f9aea52b;hp=5e0a806f38bab121a3563c2fa573ab5c623ff055;hpb=a473e231f771ad088511ed9e8eb514fd28117a13;p=srvgit diff --git a/Koha/Illrequest.pm b/Koha/Illrequest.pm index 5e0a806f38..2eaa5accda 100644 --- a/Koha/Illrequest.pm +++ b/Koha/Illrequest.pm @@ -4,31 +4,28 @@ package Koha::Illrequest; # # This file is part of Koha. # -# Koha is free software; you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. # -# Koha is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# You should have received a copy of the GNU General Public License along with -# Koha; if not, write to the Free Software Foundation, Inc., 51 Franklin -# Street, Fifth Floor, Boston, MA 02110-1301 USA. +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . use Modern::Perl; -use Clone 'clone'; -use File::Basename qw( basename ); -use Encode qw( encode ); -use Mail::Sendmail; -use Try::Tiny; +use Clone qw( clone ); +use Try::Tiny qw( catch try ); use DateTime; +use C4::Letters; use Koha::Database; -use Koha::Email; +use Koha::DateUtils qw( dt_from_string ); use Koha::Exceptions::Ill; use Koha::Illcomments; use Koha::Illrequestattributes; @@ -36,6 +33,12 @@ use Koha::AuthorisedValue; use Koha::Illrequest::Logger; use Koha::Patron; use Koha::AuthorisedValues; +use Koha::Biblios; +use Koha::Items; +use Koha::ItemTypes; +use Koha::Libraries; + +use C4::Circulation qw( CanBookBeIssued AddIssue ); use base qw(Koha::Object); @@ -130,11 +133,14 @@ sub statusalias { # We can't know which result is the right one if there are multiple # ILLSTATUS authorised values with the same authorised_value column value # so we just use the first - return Koha::AuthorisedValues->search({ - branchcode => $self->branchcode, - category => 'ILLSTATUS', - authorised_value => $self->SUPER::status_alias - })->next; + return Koha::AuthorisedValues->search( + { + category => 'ILLSTATUS', + authorised_value => $self->SUPER::status_alias + }, + {}, + $self->branchcode + )->next; } =head3 illrequestattributes @@ -225,11 +231,15 @@ sub status_alias { # We can't know which result is the right one if there are multiple # ILLSTATUS authorised values with the same authorised_value column value # so we just use the first - my $alias = Koha::AuthorisedValues->search({ - branchcode => $self->branchcode, - category => 'ILLSTATUS', - authorised_value => $self->SUPER::status_alias - })->next; + my $alias = Koha::AuthorisedValues->search( + { + category => 'ILLSTATUS', + authorised_value => $self->SUPER::status_alias + }, + {}, + $self->branchcode + )->next; + if ($alias) { return $alias->authorised_value; } else { @@ -243,6 +253,7 @@ sub status_alias { Overloaded getter/setter for request status, also nullifies status_alias and records the fact that the status has changed +and sends a notice if appropriate =cut @@ -276,6 +287,10 @@ sub status { }); } delete $self->{previous_status}; + # If status has changed to cancellation requested, send a notice + if ($new_status eq 'CANCREQ') { + $self->send_staff_notice('ILL_REQUEST_CANCEL'); + } return $ret; } else { return $current_status; @@ -426,7 +441,7 @@ sub _core_status_graph { name => 'Requested', ui_method_name => 'Confirm request', method => 'confirm', - next_actions => [ 'REQREV', 'COMP' ], + next_actions => [ 'REQREV', 'COMP', 'CHK' ], ui_method_icon => 'fa-check', }, GENREQ => { @@ -435,7 +450,7 @@ sub _core_status_graph { name => 'Requested from partners', ui_method_name => 'Place request with partners', method => 'generic_confirm', - next_actions => [ 'COMP' ], + next_actions => [ 'COMP', 'CHK' ], ui_method_icon => 'fa-send-o', }, REQREV => { @@ -471,7 +486,7 @@ sub _core_status_graph { name => 'Completed', ui_method_name => 'Mark completed', method => 'mark_completed', - next_actions => [ ], + next_actions => [ 'CHK' ], ui_method_icon => 'fa-check', }, KILL => { @@ -483,6 +498,28 @@ sub _core_status_graph { next_actions => [ ], ui_method_icon => 'fa-trash', }, + CHK => { + prev_actions => [ 'REQ', 'GENREQ', 'COMP' ], + id => 'CHK', + name => 'Checked out', + ui_method_name => 'Check out', + needs_prefs => [ 'CirculateILL' ], + needs_perms => [ 'user_circulate_circulate_remaining_permissions' ], + # An array of functions that all must return true + needs_all => [ sub { my $r = shift; return $r->biblio; } ], + method => 'check_out', + next_actions => [ ], + ui_method_icon => 'fa-upload', + }, + RET => { + prev_actions => [ 'CHK' ], + id => 'RET', + name => 'Returned to library', + ui_method_name => 'Check in', + method => 'check_in', + next_actions => [ 'COMP' ], + ui_method_icon => 'fa-download', + } }; } @@ -662,7 +699,7 @@ Mark a request as completed (status = COMP). sub mark_completed { my ( $self ) = @_; $self->status('COMP')->store; - $self->completed(DateTime->now)->store; + $self->completed(dt_from_string())->store; return { error => 0, status => '', @@ -1024,53 +1061,247 @@ sub requires_moderation { return $require_moderation->{$self->status}; } -=head3 generic_confirm +=head3 biblio - my $stage_summary = $illRequest->generic_confirm; + my $biblio = $request->biblio; -Handle the generic_confirm extended method. The first stage involves creating -a template email for the end user to edit in the browser. The second stage -attempts to submit the email. +For a given request, return the biblio associated with it, +or undef if none exists =cut -sub generic_confirm { +sub biblio { + my ( $self ) = @_; + + return if !$self->biblio_id; + + return Koha::Biblios->find({ + biblionumber => $self->biblio_id + }); +} + +=head3 check_out + + my $stage_summary = $request->check_out; + +Handle the check_out method. The first stage involves gathering the required +data from the user via a form, the second stage creates an item and tries to +issue it to the patron. If successful, it notifies the patron, then it +returns a summary of how things went + +=cut + +sub check_out { my ( $self, $params ) = @_; - my $branch = Koha::Libraries->find($params->{current_branchcode}) - || die "Invalid current branchcode. Are you logged in as the database user?"; - if ( !$params->{stage}|| $params->{stage} eq 'init' ) { - my $draft->{subject} = "ILL Request"; - $draft->{body} = <search( + {}, + { order_by => ['description'] } + ); + my $libraries = Koha::Libraries->search( + {}, + { order_by => ['branchcode'] } + ); + my $biblio = $self->biblio; -EOF + # Find all statistical patrons + my $statistical_patrons = Koha::Patrons->search( + { 'category_type' => 'x' }, + { join => { 'categorycode' => 'borrowers' } } + ); - my $details = $self->metadata; - while (my ($title, $value) = each %{$details}) { - $draft->{body} .= " - " . $title . ": " . $value . "\n" - if $value; + if (!$params->{stage} || $params->{stage} eq 'init') { + # Present a form to gather the required data + # + # We may be viewing this page having previously tried to issue + # the item (in which case, we may already have created an item) + # so we pass the biblio for this request + return { + method => 'check_out', + stage => 'form', + value => { + itemtypes => $itemtypes, + libraries => $libraries, + statistical => $statistical_patrons, + biblio => $biblio + } + }; + } elsif ($params->{stage} eq 'form') { + # Validate what we've got and return with an error if we fail + my $errors = {}; + if (!$params->{item_type} || length $params->{item_type} == 0) { + $errors->{item_type} = 1; + } + if ($params->{inhouse} && length $params->{inhouse} > 0) { + my $patron_count = Koha::Patrons->search({ + cardnumber => $params->{inhouse} + })->count(); + if ($patron_count != 1) { + $errors->{inhouse} = 1; + } } - $draft->{body} .= <items->as_list; + my $item_count = scalar @items; + if ($item_count > 1) { + $errors->{itemcount} = 1; + } -Kind Regards + # Failed validation, go back to the form + if (%{$errors}) { + return { + method => 'check_out', + stage => 'form', + value => { + params => $params, + statistical => $statistical_patrons, + itemtypes => $itemtypes, + libraries => $libraries, + biblio => $biblio, + errors => $errors + } + }; + } -EOF + # Passed validation + # + # Create an item if one doesn't already exist, + # if one does, use that + my $itemnumber; + if ($item_count == 0) { + my $item_hash = { + biblionumber => $self->biblio_id, + homebranch => $params->{branchcode}, + holdingbranch => $params->{branchcode}, + location => $params->{branchcode}, + itype => $params->{item_type}, + barcode => 'ILL-' . $self->illrequest_id + }; + try { + my $item = Koha::Item->new($item_hash)->store; + $itemnumber = $item->itemnumber; + }; + } else { + $itemnumber = $items[0]->itemnumber; + } + # Check we have an item before going forward + if (!$itemnumber) { + return { + method => 'check_out', + stage => 'form', + value => { + params => $params, + itemtypes => $itemtypes, + libraries => $libraries, + statistical => $statistical_patrons, + errors => { item_creation => 1 } + } + }; + } - my @address = map { $branch->$_ } - qw/ branchname branchaddress1 branchaddress2 branchaddress3 - branchzip branchcity branchstate branchcountry branchphone - branchemail /; - my $address = ""; - foreach my $line ( @address ) { - $address .= $line . "\n" if $line; + # Do the check out + # + # Gather what we need + my $target_item = Koha::Items->find( $itemnumber ); + # Determine who we're issuing to + my $patron = $params->{inhouse} && length $params->{inhouse} > 0 ? + Koha::Patrons->find({ cardnumber => $params->{inhouse} }) : + $self->patron; + + my @issue_args = ( + $patron, + scalar $target_item->barcode + ); + if ($params->{duedate} && length $params->{duedate} > 0) { + push @issue_args, $params->{duedate}; + } + # Check if we can check out + my ( $error, $confirm, $alerts, $messages ) = + C4::Circulation::CanBookBeIssued(@issue_args); + + # If we got anything back saying we can't check out, + # return it to the template + my $problems = {}; + if ( $error && %{$error} ) { $problems->{error} = $error }; + if ( $confirm && %{$confirm} ) { $problems->{confirm} = $confirm }; + if ( $alerts && %{$alerts} ) { $problems->{alerts} = $alerts }; + if ( $messages && %{$messages} ) { $problems->{messages} = $messages }; + + if (%{$problems}) { + return { + method => 'check_out', + stage => 'form', + value => { + params => $params, + itemtypes => $itemtypes, + libraries => $libraries, + statistical => $statistical_patrons, + patron => $patron, + biblio => $biblio, + check_out_errors => $problems + } + }; + } + + # We can allegedly check out, so make it so + # For some reason, AddIssue requires an unblessed Patron + $issue_args[0] = $patron->unblessed; + my $issue = C4::Circulation::AddIssue(@issue_args); + + if ($issue) { + # Update the request status + $self->status('CHK')->store; + return { + method => 'check_out', + stage => 'done_check_out', + value => { + params => $params, + patron => $patron, + check_out => $issue + } + }; + } else { + return { + method => 'check_out', + stage => 'form', + value => { + params => $params, + itemtypes => $itemtypes, + libraries => $libraries, + errors => { item_check_out => 1 } + } + }; } + } + +} - $draft->{body} .= $address; +=head3 generic_confirm + + my $stage_summary = $illRequest->generic_confirm; + +Handle the generic_confirm extended method. The first stage involves creating +a template email for the end user to edit in the browser. The second stage +attempts to submit the email. + +=cut + +sub generic_confirm { + my ( $self, $params ) = @_; + my $branch = Koha::Libraries->find($params->{current_branchcode}) + || die "Invalid current branchcode. Are you logged in as the database user?"; + if ( !$params->{stage}|| $params->{stage} eq 'init' ) { + # Get the message body from the notice definition + my $letter = $self->get_notice({ + notice_code => 'ILL_PARTNER_REQ', + transport => 'email' + }); my $partners = Koha::Patrons->search({ categorycode => $self->_config->partner_code @@ -1082,7 +1313,10 @@ EOF method => 'generic_confirm', stage => 'draft', value => { - draft => $draft, + draft => { + subject => $letter->{title}, + body => $letter->{content} + }, partners => $partners, } }; @@ -1098,57 +1332,255 @@ EOF "No target email addresses found. Either select at least one partner or check your ILL partner library records.") if ( !$to ); # Create the from, replyto and sender headers - my $from = $branch->branchemail; - my $replyto = $branch->branchreplyto || $from; + my $from = $branch->from_email_address; + my $replyto = $branch->inbound_ill_address; Koha::Exceptions::Ill::NoLibraryEmail->throw( "Your library has no usable email address. Please set it.") if ( !$from ); - # Create the email - my $message = Koha::Email->new; - my %mail = $message->create_message_headers( - { - to => $to, - from => $from, - replyto => $replyto, - subject => Encode::encode( "utf8", $params->{subject} ), - message => Encode::encode( "utf8", $params->{body} ), - contenttype => 'text/plain', + # So we get a notice hashref, then substitute the possibly + # modified title and body from the draft stage + my $letter = $self->get_notice({ + notice_code => 'ILL_PARTNER_REQ', + transport => 'email' + }); + $letter->{title} = $params->{subject}; + $letter->{content} = $params->{body}; + + # Queue the notice + my $params = { + letter => $letter, + borrowernumber => $self->borrowernumber, + message_transport_type => 'email', + to_address => $to, + from_address => $from, + reply_address => $replyto + }; + + if ($letter) { + my $result = C4::Letters::EnqueueLetter($params); + if ( $result ) { + $self->status("GENREQ")->store; + $self->_backend_capability( + 'set_requested_partners', + { + request => $self, + to => $to + } + ); + return { + error => 0, + status => '', + message => '', + method => 'generic_confirm', + stage => 'commit', + next => 'illview', + }; } - ); - # Send it - my $result = sendmail(%mail); - if ( $result ) { - $self->status("GENREQ")->store; - $self->_backend_capability( - 'set_requested_partners', - { - request => $self, - to => $to - } - ); - return { - error => 0, - status => '', - message => '', - method => 'generic_confirm', - stage => 'commit', - next => 'illview', - }; - } else { - return { - error => 1, - status => 'email_failed', - message => $Mail::Sendmail::error, - method => 'generic_confirm', - stage => 'draft', - }; } + return { + error => 1, + status => 'email_failed', + message => 'Email queueing failed', + method => 'generic_confirm', + stage => 'draft', + }; } else { die "Unknown stage, should not have happened." } } +=head3 send_patron_notice + + my $result = $request->send_patron_notice($notice_code); + +Send a specified notice regarding this request to a patron + +=cut + +sub send_patron_notice { + my ( $self, $notice_code ) = @_; + + # We need a notice code + if (!$notice_code) { + return { + error => 'notice_no_type' + }; + } + + # Map from the notice code to the messaging preference + my %message_name = ( + ILL_PICKUP_READY => 'Ill_ready', + ILL_REQUEST_UNAVAIL => 'Ill_unavailable' + ); + + # Get the patron's messaging preferences + my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences({ + borrowernumber => $self->borrowernumber, + message_name => $message_name{$notice_code} + }); + my @transports = keys %{ $borrower_preferences->{transports} }; + + # Notice should come from the library where the request was placed, + # not the patrons home library + my $branch = Koha::Libraries->find($self->branchcode); + my $from_address = $branch->from_email_address; + my $reply_address = $branch->inbound_ill_address; + + # Send the notice to the patron via the chosen transport methods + # and record the results + my @success = (); + my @fail = (); + for my $transport (@transports) { + my $letter = $self->get_notice({ + notice_code => $notice_code, + transport => $transport + }); + if ($letter) { + my $result = C4::Letters::EnqueueLetter({ + letter => $letter, + borrowernumber => $self->borrowernumber, + message_transport_type => $transport, + from_address => $from_address, + reply_address => $reply_address + }); + if ($result) { + push @success, $transport; + } else { + push @fail, $transport; + } + } else { + push @fail, $transport; + } + } + if (scalar @success > 0) { + my $logger = Koha::Illrequest::Logger->new; + $logger->log_patron_notice({ + request => $self, + notice_code => $notice_code + }); + } + return { + result => { + success => \@success, + fail => \@fail + } + }; +} + +=head3 send_staff_notice + + my $result = $request->send_staff_notice($notice_code); + +Send a specified notice regarding this request to staff + +=cut + +sub send_staff_notice { + my ( $self, $notice_code ) = @_; + + # We need a notice code + if (!$notice_code) { + return { + error => 'notice_no_type' + }; + } + + # Get the staff notices that have been assigned for sending in + # the syspref + my $staff_to_send = C4::Context->preference('ILLSendStaffNotices') // q{}; + + # If it hasn't been enabled in the syspref, we don't want to send it + if ($staff_to_send !~ /\b$notice_code\b/) { + return { + error => 'notice_not_enabled' + }; + } + + my $letter = $self->get_notice({ + notice_code => $notice_code, + transport => 'email' + }); + + # Try and get an address to which to send staff notices + my $branch = Koha::Libraries->find($self->branchcode); + my $to_address = $branch->inbound_ill_address; + my $from_address = $branch->inbound_ill_address; + + my $params = { + letter => $letter, + borrowernumber => $self->borrowernumber, + message_transport_type => 'email', + from_address => $from_address + }; + + if ($to_address) { + $params->{to_address} = $to_address; + } else { + return { + error => 'notice_no_create' + }; + } + + if ($letter) { + C4::Letters::EnqueueLetter($params) + or warn "can't enqueue letter $letter"; + return { + success => 'notice_queued' + }; + } else { + return { + error => 'notice_no_create' + }; + } +} + +=head3 get_notice + + my $notice = $request->get_notice($params); + +Return a compiled notice hashref for the passed notice code +and transport type + +=cut + +sub get_notice { + my ( $self, $params ) = @_; + + my $title = $self->illrequestattributes->find( + { type => 'title' } + ); + my $author = $self->illrequestattributes->find( + { type => 'author' } + ); + my $metahash = $self->metadata; + my @metaarray = (); + while (my($key, $value) = each %{$metahash}) { + push @metaarray, "- $key: $value" if $value; + } + my $metastring = join("\n", @metaarray); + my $letter = C4::Letters::GetPreparedLetter( + module => 'ill', + letter_code => $params->{notice_code}, + branchcode => $self->branchcode, + message_transport_type => $params->{transport}, + lang => $self->patron->lang, + tables => { + illrequests => $self->illrequest_id, + borrowers => $self->borrowernumber, + biblio => $self->biblio_id, + branches => $self->branchcode, + }, + substitute => { + ill_bib_title => $title ? $title->value : '', + ill_bib_author => $author ? $author->value : '', + ill_full_metadata => $metastring + } + ); + + return $letter; +} + =head3 id_prefix my $prefix = $record->id_prefix;