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