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