Bug 7317: Handle backend absense more gracefuly
[koha.git] / Koha / Illrequest.pm
1 package Koha::Illrequest;
2
3 # Copyright PTFS Europe 2016
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 3 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
15 # details.
16 #
17 # You should have received a copy of the GNU General Public License along with
18 # Koha; if not, write to the Free Software Foundation, Inc., 51 Franklin
19 # Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21 use Modern::Perl;
22
23 use Clone 'clone';
24 use File::Basename qw/basename/;
25 use Koha::Database;
26 use Koha::Email;
27 use Koha::Exceptions::Ill;
28 use Koha::Illrequest;
29 use Koha::Illrequestattributes;
30 use Koha::Patron;
31 use Mail::Sendmail;
32 use Try::Tiny;
33
34 use base qw(Koha::Object);
35
36 =head1 NAME
37
38 Koha::Illrequest - Koha Illrequest Object class
39
40 =head1 (Re)Design
41
42 An ILLRequest consists of two parts; the Illrequest Koha::Object, and a series
43 of related Illrequestattributes.
44
45 The former encapsulates the basic necessary information that any ILL requires
46 to be usable in Koha.  The latter is a set of additional properties used by
47 one of the backends.
48
49 The former subsumes the legacy "Status" object.  The latter remains
50 encapsulated in the "Record" object.
51
52 TODO:
53
54 - Anything invoking the ->status method; annotated with:
55   + # Old use of ->status !
56
57 =head1 API
58
59 =head2 Backend API Response Principles
60
61 All methods should return a hashref in the following format:
62
63 =over
64
65 =item * error
66
67 This should be set to 1 if an error was encountered.
68
69 =item * status
70
71 The status should be a string from the list of statuses detailed below.
72
73 =item * message
74
75 The message is a free text field that can be passed on to the end user.
76
77 =item * value
78
79 The value returned by the method.
80
81 =back
82
83 =head2 Interface Status Messages
84
85 =over
86
87 =item * branch_address_incomplete
88
89 An interface request has determined branch address details are incomplete.
90
91 =item * cancel_success
92
93 The interface's cancel_request method was successful in cancelling the
94 Illrequest using the API.
95
96 =item * cancel_fail
97
98 The interface's cancel_request method failed to cancel the Illrequest using
99 the API.
100
101 =item * unavailable
102
103 The interface's request method returned saying that the desired item is not
104 available for request.
105
106 =back
107
108 =head2 Class methods
109
110 =head3 illrequestattributes
111
112 =cut
113
114 sub illrequestattributes {
115     my ( $self ) = @_;
116     return Koha::Illrequestattributes->_new_from_dbic(
117         scalar $self->_result->illrequestattributes
118     );
119 }
120
121 =head3 patron
122
123 =cut
124
125 sub patron {
126     my ( $self ) = @_;
127     return Koha::Patron->_new_from_dbic(
128         scalar $self->_result->borrowernumber
129     );
130 }
131
132 =head3 load_backend
133
134 Require "Base.pm" from the relevant ILL backend.
135
136 =cut
137
138 sub load_backend {
139     my ( $self, $backend_id ) = @_;
140
141     my @raw = qw/Koha Illbackends/; # Base Path
142
143     my $backend_name = $backend_id || $self->backend;
144
145     unless ( defined $backend_name && $backend_name ne '' ) {
146         Koha::Exceptions::Ill::InvalidBackendId->throw(
147             "An invalid backend ID was requested ('')");
148     }
149
150     my $location = join "/", @raw, $backend_name, "Base.pm";    # File to load
151     my $backend_class = join "::", @raw, $backend_name, "Base"; # Package name
152     require $location;
153     $self->{_my_backend} = $backend_class->new({ config => $self->_config });
154     return $self;
155 }
156
157
158 =head3 _backend
159
160     my $backend = $abstract->_backend($new_backend);
161     my $backend = $abstract->_backend;
162
163 Getter/Setter for our API object.
164
165 =cut
166
167 sub _backend {
168     my ( $self, $backend ) = @_;
169     $self->{_my_backend} = $backend if ( $backend );
170     # Dynamically load our backend object, as late as possible.
171     $self->load_backend unless ( $self->{_my_backend} );
172     return $self->{_my_backend};
173 }
174
175 =head3 _backend_capability
176
177     my $backend_capability_result = $self->_backend_capability($name, $args);
178
179 This is a helper method to invoke optional capabilities in the backend.  If
180 the capability named by $name is not supported, return 0, else invoke it,
181 passing $args along with the invocation, and return its return value.
182
183 NOTE: this module suffers from a confusion in termninology:
184
185 in _backend_capability, the notion of capability refers to an optional feature
186 that is implemented in core, but might not be supported by a given backend.
187
188 in capabilities & custom_capability, capability refers to entries in the
189 status_graph (after union between backend and core).
190
191 The easiest way to fix this would be to fix the terminology in
192 capabilities & custom_capability and their callers.
193
194 =cut
195
196 sub _backend_capability {
197     my ( $self, $name, $args ) = @_;
198     my $capability = 0;
199     try {
200         $capability = $self->_backend->capabilities($name);
201     } catch {
202         return 0;
203     };
204     if ( $capability ) {
205         return &{$capability}($args);
206     } else {
207         return 0;
208     }
209 }
210
211 =head3 _config
212
213     my $config = $abstract->_config($config);
214     my $config = $abstract->_config;
215
216 Getter/Setter for our config object.
217
218 =cut
219
220 sub _config {
221     my ( $self, $config ) = @_;
222     $self->{_my_config} = $config if ( $config );
223     # Load our config object, as late as possible.
224     unless ( $self->{_my_config} ) {
225         $self->{_my_config} = Koha::Illrequest::Config->new;
226     }
227     return $self->{_my_config};
228 }
229
230 =head3 metadata
231
232 =cut
233
234 sub metadata {
235     my ( $self ) = @_;
236     return $self->_backend->metadata($self);
237 }
238
239 =head3 _core_status_graph
240
241     my $core_status_graph = $illrequest->_core_status_graph;
242
243 Returns ILL module's default status graph.  A status graph defines the list of
244 available actions at any stage in the ILL workflow.  This is for instance used
245 by the perl script & template to generate the correct buttons to display to
246 the end user at any given point.
247
248 =cut
249
250 sub _core_status_graph {
251     my ( $self ) = @_;
252     return {
253         NEW => {
254             prev_actions => [ ],                           # Actions containing buttons
255                                                            # leading to this status
256             id             => 'NEW',                       # ID of this status
257             name           => 'New request',               # UI name of this status
258             ui_method_name => 'New request',               # UI name of method leading
259                                                            # to this status
260             method         => 'create',                    # method to this status
261             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ], # buttons to add to all
262                                                            # requests with this status
263             ui_method_icon => 'fa-plus',                   # UI Style class
264         },
265         REQ => {
266             prev_actions   => [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ],
267             id             => 'REQ',
268             name           => 'Requested',
269             ui_method_name => 'Confirm request',
270             method         => 'confirm',
271             next_actions   => [ 'REQREV', 'COMP' ],
272             ui_method_icon => 'fa-check',
273         },
274         GENREQ => {
275             prev_actions   => [ 'NEW', 'REQREV' ],
276             id             => 'GENREQ',
277             name           => 'Requested from partners',
278             ui_method_name => 'Place request with partners',
279             method         => 'generic_confirm',
280             next_actions   => [ 'COMP' ],
281             ui_method_icon => 'fa-send-o',
282         },
283         REQREV => {
284             prev_actions   => [ 'REQ' ],
285             id             => 'REQREV',
286             name           => 'Request reverted',
287             ui_method_name => 'Revert Request',
288             method         => 'cancel',
289             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ],
290             ui_method_icon => 'fa-times',
291         },
292         QUEUED => {
293             prev_actions   => [ ],
294             id             => 'QUEUED',
295             name           => 'Queued request',
296             ui_method_name => 0,
297             method         => 0,
298             next_actions   => [ 'REQ', 'KILL' ],
299             ui_method_icon => 0,
300         },
301         CANCREQ => {
302             prev_actions   => [ 'NEW' ],
303             id             => 'CANCREQ',
304             name           => 'Cancellation requested',
305             ui_method_name => 0,
306             method         => 0,
307             next_actions   => [ 'KILL', 'REQ' ],
308             ui_method_icon => 0,
309         },
310         COMP => {
311             prev_actions   => [ 'REQ' ],
312             id             => 'COMP',
313             name           => 'Completed',
314             ui_method_name => 'Mark completed',
315             method         => 'mark_completed',
316             next_actions   => [ ],
317             ui_method_icon => 'fa-check',
318         },
319         KILL => {
320             prev_actions   => [ 'QUEUED', 'REQREV', 'NEW', 'CANCREQ' ],
321             id             => 'KILL',
322             name           => 0,
323             ui_method_name => 'Delete request',
324             method         => 'delete',
325             next_actions   => [ ],
326             ui_method_icon => 'fa-trash',
327         },
328     };
329 }
330
331 =head3 _core_status_graph
332
333     my $status_graph = $illrequest->_core_status_graph($origin, $new_graph);
334
335 Return a new status_graph, the result of merging $origin & new_graph.  This is
336 operation is a union over the sets defied by the two graphs.
337
338 Each entry in $new_graph is added to $origin.  We do not provide a syntax for
339 'subtraction' of entries from $origin.
340
341 Whilst it is not intended that this works, you can override entries in $origin
342 with entries with the same key in $new_graph.  This can lead to problematic
343 behaviour when $new_graph adds an entry, which modifies a dependent entry in
344 $origin, only for the entry in $origin to be replaced later with a new entry
345 from $new_graph.
346
347 NOTE: this procedure does not "re-link" entries in $origin or $new_graph,
348 i.e. each of the graphs need to be correct at the outset of the operation.
349
350 =cut
351
352 sub _status_graph_union {
353     my ( $self, $core_status_graph, $backend_status_graph ) = @_;
354     # Create new status graph with:
355     # - all core_status_graph
356     # - for-each each backend_status_graph
357     #   + add to new status graph
358     #   + for each core prev_action:
359     #     * locate core_status
360     #     * update next_actions with additional next action.
361     #   + for each core next_action:
362     #     * locate core_status
363     #     * update prev_actions with additional prev action
364
365     my @core_status_ids = keys %{$core_status_graph};
366     my $status_graph = clone($core_status_graph);
367
368     foreach my $backend_status_key ( keys %{$backend_status_graph} ) {
369         my $backend_status = $backend_status_graph->{$backend_status_key};
370         # Add to new status graph
371         $status_graph->{$backend_status_key} = $backend_status;
372         # Update all core methods' next_actions.
373         foreach my $prev_action ( @{$backend_status->{prev_actions}} ) {
374             if ( grep $prev_action, @core_status_ids ) {
375                 my @next_actions =
376                      @{$status_graph->{$prev_action}->{next_actions}};
377                 push @next_actions, $backend_status_key;
378                 $status_graph->{$prev_action}->{next_actions}
379                     = \@next_actions;
380             }
381         }
382         # Update all core methods' prev_actions
383         foreach my $next_action ( @{$backend_status->{next_actions}} ) {
384             if ( grep $next_action, @core_status_ids ) {
385                 my @prev_actions =
386                      @{$status_graph->{$next_action}->{prev_actions}};
387                 push @prev_actions, $backend_status_key;
388                 $status_graph->{$next_action}->{prev_actions}
389                     = \@prev_actions;
390             }
391         }
392     }
393
394     return $status_graph;
395 }
396
397 ### Core API methods
398
399 =head3 capabilities
400
401     my $capabilities = $illrequest->capabilities;
402
403 Return a hashref mapping methods to operation names supported by the queried
404 backend.
405
406 Example return value:
407
408     { create => "Create Request", confirm => "Progress Request" }
409
410 NOTE: this module suffers from a confusion in termninology:
411
412 in _backend_capability, the notion of capability refers to an optional feature
413 that is implemented in core, but might not be supported by a given backend.
414
415 in capabilities & custom_capability, capability refers to entries in the
416 status_graph (after union between backend and core).
417
418 The easiest way to fix this would be to fix the terminology in
419 capabilities & custom_capability and their callers.
420
421 =cut
422
423 sub capabilities {
424     my ( $self, $status ) = @_;
425     # Generate up to date status_graph
426     my $status_graph = $self->_status_graph_union(
427         $self->_core_status_graph,
428         $self->_backend->status_graph({
429             request => $self,
430             other   => {}
431         })
432     );
433     # Extract available actions from graph.
434     return $status_graph->{$status} if $status;
435     # Or return entire graph.
436     return $status_graph;
437 }
438
439 =head3 custom_capability
440
441 Return the result of invoking $CANDIDATE on this request's backend with
442 $PARAMS, or 0 if $CANDIDATE is an unknown method on backend.
443
444 NOTE: this module suffers from a confusion in termninology:
445
446 in _backend_capability, the notion of capability refers to an optional feature
447 that is implemented in core, but might not be supported by a given backend.
448
449 in capabilities & custom_capability, capability refers to entries in the
450 status_graph (after union between backend and core).
451
452 The easiest way to fix this would be to fix the terminology in
453 capabilities & custom_capability and their callers.
454
455 =cut
456
457 sub custom_capability {
458     my ( $self, $candidate, $params ) = @_;
459     foreach my $capability ( values %{$self->capabilities} ) {
460         if ( $candidate eq $capability->{method} ) {
461             my $response =
462                 $self->_backend->$candidate({
463                     request    => $self,
464                     other      => $params,
465                 });
466             return $self->expandTemplate($response);
467         }
468     }
469     return 0;
470 }
471
472 =head3 available_backends
473
474 Return a list of available backends.
475
476 =cut
477
478 sub available_backends {
479     my ( $self ) = @_;
480     my @backends = $self->_config->available_backends;
481     return \@backends;
482 }
483
484 =head3 available_actions
485
486 Return a list of available actions.
487
488 =cut
489
490 sub available_actions {
491     my ( $self ) = @_;
492     my $current_action = $self->capabilities($self->status);
493     my @available_actions = map { $self->capabilities($_) }
494         @{$current_action->{next_actions}};
495     return \@available_actions;
496 }
497
498 =head3 mark_completed
499
500 Mark a request as completed (status = COMP).
501
502 =cut
503
504 sub mark_completed {
505     my ( $self ) = @_;
506     $self->status('COMP')->store;
507     return {
508         error   => 0,
509         status  => '',
510         message => '',
511         method  => 'mark_completed',
512         stage   => 'commit',
513         next    => 'illview',
514     };
515 }
516
517 =head2 backend_confirm
518
519 Confirm a request. The backend handles setting of mandatory fields in the commit stage:
520
521 =over
522
523 =item * orderid
524
525 =item * accessurl, cost (if available).
526
527 =back
528
529 =cut
530
531 sub backend_confirm {
532     my ( $self, $params ) = @_;
533
534     my $response = $self->_backend->confirm({
535             request    => $self,
536             other      => $params,
537         });
538     return $self->expandTemplate($response);
539 }
540
541 =head3 backend_update_status
542
543 =cut
544
545 sub backend_update_status {
546     my ( $self, $params ) = @_;
547     return $self->expandTemplate($self->_backend->update_status($params));
548 }
549
550 =head3 backend_cancel
551
552     my $ILLResponse = $illRequest->backend_cancel;
553
554 The standard interface method allowing for request cancellation.
555
556 =cut
557
558 sub backend_cancel {
559     my ( $self, $params ) = @_;
560
561     my $result = $self->_backend->cancel({
562         request => $self,
563         other => $params
564     });
565
566     return $self->expandTemplate($result);
567 }
568
569 =head3 backend_renew
570
571     my $renew_response = $illRequest->backend_renew;
572
573 The standard interface method allowing for request renewal queries.
574
575 =cut
576
577 sub backend_renew {
578     my ( $self ) = @_;
579     return $self->expandTemplate(
580         $self->_backend->renew({
581             request    => $self,
582         })
583     );
584 }
585
586 =head3 backend_create
587
588     my $create_response = $abstractILL->backend_create($params);
589
590 Return an array of Record objects created by querying our backend with
591 a Search query.
592
593 In the context of the other ILL methods, this is a special method: we only
594 pass it $params, as it does not yet have any other data associated with it.
595
596 =cut
597
598 sub backend_create {
599     my ( $self, $params ) = @_;
600
601     # Establish whether we need to do a generic copyright clearance.
602     if ( ( !$params->{stage} || $params->{stage} eq 'init' )
603              && C4::Context->preference("ILLModuleCopyrightClearance") ) {
604         return {
605             error   => 0,
606             status  => '',
607             message => '',
608             method  => 'create',
609             stage   => 'copyrightclearance',
610             value   => {
611                 backend => $self->_backend->name
612             }
613         };
614     } elsif ( $params->{stage} eq 'copyrightclearance' ) {
615         $params->{stage} = 'init';
616     }
617
618     # First perform API action, then...
619     my $args = {
620         request => $self,
621         other   => $params,
622     };
623     my $result = $self->_backend->create($args);
624
625     # ... simple case: we're not at 'commit' stage.
626     my $stage = $result->{stage};
627     return $self->expandTemplate($result)
628         unless ( 'commit' eq $stage );
629
630     # ... complex case: commit!
631
632     # Do we still have space for an ILL or should we queue?
633     my $permitted = $self->check_limits(
634         { patron => $self->patron }, { librarycode => $self->branchcode }
635     );
636
637     # Now augment our committed request.
638
639     $result->{permitted} = $permitted;             # Queue request?
640
641     # This involves...
642
643     # ...Updating status!
644     $self->status('QUEUED')->store unless ( $permitted );
645
646     return $self->expandTemplate($result);
647 }
648
649 =head3 expandTemplate
650
651     my $params = $abstract->expandTemplate($params);
652
653 Return a version of $PARAMS augmented with our required template path.
654
655 =cut
656
657 sub expandTemplate {
658     my ( $self, $params ) = @_;
659     my $backend = $self->_backend->name;
660     # Generate path to file to load
661     my $backend_dir = $self->_config->backend_dir;
662     my $backend_tmpl = join "/", $backend_dir, $backend;
663     my $intra_tmpl =  join "/", $backend_tmpl, "intra-includes",
664         $params->{method} . ".inc";
665     my $opac_tmpl =  join "/", $backend_tmpl, "opac-includes",
666         $params->{method} . ".inc";
667     # Set files to load
668     $params->{template} = $intra_tmpl;
669     $params->{opac_template} = $opac_tmpl;
670     return $params;
671 }
672
673 #### Abstract Imports
674
675 =head3 getLimits
676
677     my $limit_rules = $abstract->getLimits( {
678         type  => 'brw_cat' | 'branch',
679         value => $value
680     } );
681
682 Return the ILL limit rules for the supplied combination of type / value.
683
684 As the config may have no rules for this particular type / value combination,
685 or for the default, we must define fall-back values here.
686
687 =cut
688
689 sub getLimits {
690     my ( $self, $params ) = @_;
691     my $limits = $self->_config->getLimitRules($params->{type});
692
693     return $limits->{$params->{value}}
694         || $limits->{default}
695         || { count => -1, method => 'active' };
696 }
697
698 =head3 getPrefix
699
700     my $prefix = $abstract->getPrefix( {
701         brw_cat => $brw_cat,
702         branch  => $branch_code,
703     } );
704
705 Return the ILL prefix as defined by our $params: either per borrower category,
706 per branch or the default.
707
708 =cut
709
710 sub getPrefix {
711     my ( $self, $params ) = @_;
712     my $brn_prefixes = $self->_config->getPrefixes('branch');
713     my $brw_prefixes = $self->_config->getPrefixes('brw_cat');
714
715     return $brw_prefixes->{$params->{brw_cat}}
716         || $brn_prefixes->{$params->{branch}}
717         || $brw_prefixes->{default}
718         || "";                  # "the empty prefix"
719 }
720
721 #### Illrequests Imports
722
723 =head3 check_limits
724
725     my $ok = $illRequests->check_limits( {
726         borrower   => $borrower,
727         branchcode => 'branchcode' | undef,
728     } );
729
730 Given $PARAMS, a hashref containing a $borrower object and a $branchcode,
731 see whether we are still able to place ILLs.
732
733 LimitRules are derived from koha-conf.xml:
734  + default limit counts, and counting method
735  + branch specific limit counts & counting method
736  + borrower category specific limit counts & counting method
737  + err on the side of caution: a counting fail will cause fail, even if
738    the other counts passes.
739
740 =cut
741
742 sub check_limits {
743     my ( $self, $params ) = @_;
744     my $patron     = $params->{patron};
745     my $branchcode = $params->{librarycode} || $patron->branchcode;
746
747     # Establish maximum number of allowed requests
748     my ( $branch_rules, $brw_rules ) = (
749         $self->getLimits( {
750             type => 'branch',
751             value => $branchcode
752         } ),
753         $self->getLimits( {
754             type => 'brw_cat',
755             value => $patron->categorycode,
756         } ),
757     );
758     my ( $branch_limit, $brw_limit )
759         = ( $branch_rules->{count}, $brw_rules->{count} );
760     # Establish currently existing requests
761     my ( $branch_count, $brw_count ) = (
762         $self->_limit_counter(
763             $branch_rules->{method}, { branchcode => $branchcode }
764         ),
765         $self->_limit_counter(
766             $brw_rules->{method}, { borrowernumber => $patron->borrowernumber }
767         ),
768     );
769
770     # Compare and return
771     # A limit of -1 means no limit exists.
772     # We return blocked if either branch limit or brw limit is reached.
773     if ( ( $branch_limit != -1 && $branch_limit <= $branch_count )
774              || ( $brw_limit != -1 && $brw_limit <= $brw_count ) ) {
775         return 0;
776     } else {
777         return 1;
778     }
779 }
780
781 sub _limit_counter {
782     my ( $self, $method, $target ) = @_;
783
784     # Establish parameters of counts
785     my $resultset;
786     if ($method && $method eq 'annual') {
787         $resultset = Koha::Illrequests->search({
788             -and => [
789                 %{$target},
790                 \"YEAR(placed) = YEAR(NOW())"
791             ]
792         });
793     } else {                    # assume 'active'
794         # XXX: This status list is ugly. There should be a method in config
795         # to return these.
796         my $where = { status => { -not_in => [ 'QUEUED', 'COMP' ] } };
797         $resultset = Koha::Illrequests->search({ %{$target}, %{$where} });
798     }
799
800     # Fetch counts
801     return $resultset->count;
802 }
803
804 =head3 requires_moderation
805
806     my $status = $illRequest->requires_moderation;
807
808 Return the name of the status if moderation by staff is required; or 0
809 otherwise.
810
811 =cut
812
813 sub requires_moderation {
814     my ( $self ) = @_;
815     my $require_moderation = {
816         'CANCREQ' => 'CANCREQ',
817     };
818     return $require_moderation->{$self->status};
819 }
820
821 =head3 generic_confirm
822
823     my $stage_summary = $illRequest->generic_confirm;
824
825 Handle the generic_confirm extended method.  The first stage involves creating
826 a template email for the end user to edit in the browser.  The second stage
827 attempts to submit the email.
828
829 =cut
830
831 sub generic_confirm {
832     my ( $self, $params ) = @_;
833     my $branch = Koha::Libraries->find($params->{current_branchcode})
834         || die "Invalid current branchcode. Are you logged in as the database user?";
835     if ( !$params->{stage}|| $params->{stage} eq 'init' ) {
836         my $draft->{subject} = "ILL Request";
837         $draft->{body} = <<EOF;
838 Dear Sir/Madam,
839
840     We would like to request an interlibrary loan for a title matching the
841 following description:
842
843 EOF
844
845         my $details = $self->metadata;
846         while (my ($title, $value) = each %{$details}) {
847             $draft->{body} .= "  - " . $title . ": " . $value . "\n"
848                 if $value;
849         }
850         $draft->{body} .= <<EOF;
851
852 Please let us know if you are able to supply this to us.
853
854 Kind Regards
855
856 EOF
857
858         my @address = map { $branch->$_ }
859             qw/ branchname branchaddress1 branchaddress2 branchaddress3
860                 branchzip branchcity branchstate branchcountry branchphone
861                 branchemail /;
862         my $address = "";
863         foreach my $line ( @address ) {
864             $address .= $line . "\n" if $line;
865         }
866
867         $draft->{body} .= $address;
868
869         my $partners = Koha::Patrons->search({
870             categorycode => $self->_config->partner_code
871         });
872         return {
873             error   => 0,
874             status  => '',
875             message => '',
876             method  => 'generic_confirm',
877             stage   => 'draft',
878             value   => {
879                 draft    => $draft,
880                 partners => $partners,
881             }
882         };
883
884     } elsif ( 'draft' eq $params->{stage} ) {
885         # Create the to header
886         my $to = $params->{partners};
887         $to =~ s/^\x00//;       # Strip leading NULLs
888         $to =~ s/\x00/; /;      # Replace others with '; '
889         die "No target email addresses found. Either select at least one partner or check your ILL partner library records." if ( !$to );
890         # Create the from, replyto and sender headers
891         my $from = $branch->branchemail;
892         my $replyto = $branch->branchreplyto || $from;
893         die "Your branch has no email address. Please set it."
894             if ( !$from );
895         # Create the email
896         my $message = Koha::Email->new;
897         my %mail = $message->create_message_headers(
898             {
899                 to          => $to,
900                 from        => $from,
901                 replyto     => $replyto,
902                 subject     => Encode::encode( "utf8", $params->{subject} ),
903                 message     => Encode::encode( "utf8", $params->{body} ),
904                 contenttype => 'text/plain',
905             }
906         );
907         # Send it
908         my $result = sendmail(%mail);
909         if ( $result ) {
910             $self->status("GENREQ")->store;
911             return {
912                 error   => 0,
913                 status  => '',
914                 message => '',
915                 method  => 'generic_confirm',
916                 stage   => 'commit',
917                 next    => 'illview',
918             };
919         } else {
920             return {
921                 error   => 1,
922                 status  => 'email_failed',
923                 message => $Mail::Sendmail::error,
924                 method  => 'generic_confirm',
925                 stage   => 'draft',
926             };
927         }
928     } else {
929         die "Unknown stage, should not have happened."
930     }
931 }
932
933 =head3 id_prefix
934
935     my $prefix = $record->id_prefix;
936
937 Return the prefix appropriate for the current Illrequest as derived from the
938 borrower and branch associated with this request's Status, and the config
939 file.
940
941 =cut
942
943 sub id_prefix {
944     my ( $self ) = @_;
945     my $brw = $self->patron;
946     my $brw_cat = "dummy";
947     $brw_cat = $brw->categorycode
948         unless ( 'HASH' eq ref($brw) && $brw->{deleted} );
949     my $prefix = $self->getPrefix( {
950         brw_cat => $brw_cat,
951         branch  => $self->branchcode,
952     } );
953     $prefix .= "-" if ( $prefix );
954     return $prefix;
955 }
956
957 =head3 _censor
958
959     my $params = $illRequest->_censor($params);
960
961 Return $params, modified to reflect our censorship requirements.
962
963 =cut
964
965 sub _censor {
966     my ( $self, $params ) = @_;
967     my $censorship = $self->_config->censorship;
968     $params->{censor_notes_staff} = $censorship->{censor_notes_staff}
969         if ( $params->{opac} );
970     $params->{display_reply_date} = ( $censorship->{censor_reply_date} ) ? 0 : 1;
971
972     return $params;
973 }
974
975 =head3 TO_JSON
976
977     $json = $illrequest->TO_JSON
978
979 Overloaded I<TO_JSON> method that takes care of inserting calculated values
980 into the unblessed representation of the object.
981
982 =cut
983
984 sub TO_JSON {
985     my ( $self, $embed ) = @_;
986
987     my $object = $self->SUPER::TO_JSON();
988     $object->{id_prefix} = $self->id_prefix;
989
990     if ( scalar (keys %$embed) ) {
991         # Augment the request response with patron details if appropriate
992         if ( $embed->{patron} ) {
993             my $patron = $self->patron;
994             $object->{patron} = {
995                 firstname  => $patron->firstname,
996                 surname    => $patron->surname,
997                 cardnumber => $patron->cardnumber
998             };
999         }
1000         # Augment the request response with metadata details if appropriate
1001         if ( $embed->{metadata} ) {
1002             $object->{metadata} = $self->metadata;
1003         }
1004         # Augment the request response with status details if appropriate
1005         if ( $embed->{capabilities} ) {
1006             $object->{capabilities} = $self->capabilities;
1007         }
1008         # Augment the request response with library details if appropriate
1009         if ( $embed->{branch} ) {
1010             $object->{branch} = Koha::Libraries->find(
1011                 $self->branchcode
1012             )->TO_JSON;
1013         }
1014     }
1015
1016     return $object;
1017 }
1018
1019 =head2 Internal methods
1020
1021 =head3 _type
1022
1023 =cut
1024
1025 sub _type {
1026     return 'Illrequest';
1027 }
1028
1029 =head1 AUTHOR
1030
1031 Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com>
1032
1033 =cut
1034
1035 1;