Bug 17600: Standardize our EXPORT_OK
[srvgit] / Koha / Illrequest.pm
1 package Koha::Illrequest;
2
3 # Copyright PTFS Europe 2016,2018
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21
22 use Clone qw( clone );
23 use Try::Tiny qw( catch try );
24 use DateTime;
25
26 use C4::Letters;
27 use Koha::Database;
28 use Koha::DateUtils qw( dt_from_string );
29 use Koha::Exceptions::Ill;
30 use Koha::Illcomments;
31 use Koha::Illrequestattributes;
32 use Koha::AuthorisedValue;
33 use Koha::Illrequest::Logger;
34 use Koha::Patron;
35 use Koha::AuthorisedValues;
36 use Koha::Biblios;
37 use Koha::Items;
38 use Koha::ItemTypes;
39 use Koha::Libraries;
40
41 use C4::Circulation qw( CanBookBeIssued AddIssue );
42
43 use base qw(Koha::Object);
44
45 =head1 NAME
46
47 Koha::Illrequest - Koha Illrequest Object class
48
49 =head1 (Re)Design
50
51 An ILLRequest consists of two parts; the Illrequest Koha::Object, and a series
52 of related Illrequestattributes.
53
54 The former encapsulates the basic necessary information that any ILL requires
55 to be usable in Koha.  The latter is a set of additional properties used by
56 one of the backends.
57
58 The former subsumes the legacy "Status" object.  The latter remains
59 encapsulated in the "Record" object.
60
61 TODO:
62
63 - Anything invoking the ->status method; annotated with:
64   + # Old use of ->status !
65
66 =head1 API
67
68 =head2 Backend API Response Principles
69
70 All methods should return a hashref in the following format:
71
72 =over
73
74 =item * error
75
76 This should be set to 1 if an error was encountered.
77
78 =item * status
79
80 The status should be a string from the list of statuses detailed below.
81
82 =item * message
83
84 The message is a free text field that can be passed on to the end user.
85
86 =item * value
87
88 The value returned by the method.
89
90 =back
91
92 =head2 Interface Status Messages
93
94 =over
95
96 =item * branch_address_incomplete
97
98 An interface request has determined branch address details are incomplete.
99
100 =item * cancel_success
101
102 The interface's cancel_request method was successful in cancelling the
103 Illrequest using the API.
104
105 =item * cancel_fail
106
107 The interface's cancel_request method failed to cancel the Illrequest using
108 the API.
109
110 =item * unavailable
111
112 The interface's request method returned saying that the desired item is not
113 available for request.
114
115 =back
116
117 =head2 Class methods
118
119 =head3 statusalias
120
121     my $statusalias = $request->statusalias;
122
123 Returns a request's status alias, as a Koha::AuthorisedValue instance
124 or implicit undef. This is distinct from status_alias, which only returns
125 the value in the status_alias column, this method returns the entire
126 AuthorisedValue object
127
128 =cut
129
130 sub statusalias {
131     my ( $self ) = @_;
132     return unless $self->status_alias;
133     # We can't know which result is the right one if there are multiple
134     # ILLSTATUS authorised values with the same authorised_value column value
135     # so we just use the first
136     return Koha::AuthorisedValues->search(
137         {
138             category         => 'ILLSTATUS',
139             authorised_value => $self->SUPER::status_alias
140         },
141         {},
142         $self->branchcode
143     )->next;
144 }
145
146 =head3 illrequestattributes
147
148 =cut
149
150 sub illrequestattributes {
151     my ( $self ) = @_;
152     return Koha::Illrequestattributes->_new_from_dbic(
153         scalar $self->_result->illrequestattributes
154     );
155 }
156
157 =head3 illcomments
158
159 =cut
160
161 sub illcomments {
162     my ( $self ) = @_;
163     return Koha::Illcomments->_new_from_dbic(
164         scalar $self->_result->illcomments
165     );
166 }
167
168 =head3 logs
169
170 =cut
171
172 sub logs {
173     my ( $self ) = @_;
174     my $logger = Koha::Illrequest::Logger->new;
175     return $logger->get_request_logs($self);
176 }
177
178 =head3 patron
179
180 =cut
181
182 sub patron {
183     my ( $self ) = @_;
184     return Koha::Patron->_new_from_dbic(
185         scalar $self->_result->borrowernumber
186     );
187 }
188
189 =head3 status_alias
190
191     $Illrequest->status_alias(143);
192
193 Overloaded getter/setter for status_alias,
194 that only returns authorised values from the
195 correct category and records the fact that the status has changed
196
197 =cut
198
199 sub status_alias {
200     my ($self, $new_status_alias) = @_;
201
202     my $current_status_alias = $self->SUPER::status_alias;
203
204     if ($new_status_alias) {
205         # Keep a record of the previous status before we change it,
206         # we might need it
207         $self->{previous_status} = $current_status_alias ?
208             $current_status_alias :
209             scalar $self->status;
210         # This is hackery to enable us to undefine
211         # status_alias, since we need to have an overloaded
212         # status_alias method to get us around the problem described
213         # here:
214         # https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=20581#c156
215         # We need a way of accepting implied undef, so we can nullify
216         # the status_alias column, when called from $self->status
217         my $val = $new_status_alias eq "-1" ? undef : $new_status_alias;
218         my $ret = $self->SUPER::status_alias($val);
219         my $val_to_log = $val ? $new_status_alias : scalar $self->status;
220         if ($ret) {
221             my $logger = Koha::Illrequest::Logger->new;
222             $logger->log_status_change({
223                 request => $self,
224                 value   => $val_to_log
225             });
226         } else {
227             delete $self->{previous_status};
228         }
229         return $ret;
230     }
231     # We can't know which result is the right one if there are multiple
232     # ILLSTATUS authorised values with the same authorised_value column value
233     # so we just use the first
234     my $alias = Koha::AuthorisedValues->search(
235         {
236             category         => 'ILLSTATUS',
237             authorised_value => $self->SUPER::status_alias
238         },
239         {},
240         $self->branchcode
241     )->next;
242
243     if ($alias) {
244         return $alias->authorised_value;
245     } else {
246         return;
247     }
248 }
249
250 =head3 status
251
252     $Illrequest->status('CANREQ');
253
254 Overloaded getter/setter for request status,
255 also nullifies status_alias and records the fact that the status has changed
256 and sends a notice if appropriate
257
258 =cut
259
260 sub status {
261     my ( $self, $new_status) = @_;
262
263     my $current_status = $self->SUPER::status;
264     my $current_status_alias = $self->SUPER::status_alias;
265
266     if ($new_status) {
267         # Keep a record of the previous status before we change it,
268         # we might need it
269         $self->{previous_status} = $current_status_alias ?
270             $current_status_alias :
271             $current_status;
272         my $ret = $self->SUPER::status($new_status)->store;
273         if ($current_status_alias) {
274             # This is hackery to enable us to undefine
275             # status_alias, since we need to have an overloaded
276             # status_alias method to get us around the problem described
277             # here:
278             # https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=20581#c156
279             # We need a way of passing implied undef to nullify status_alias
280             # so we pass -1, which is special cased in the overloaded setter
281             $self->status_alias("-1");
282         } else {
283             my $logger = Koha::Illrequest::Logger->new;
284             $logger->log_status_change({
285                 request => $self,
286                 value   => $new_status
287             });
288         }
289         delete $self->{previous_status};
290         # If status has changed to cancellation requested, send a notice
291         if ($new_status eq 'CANCREQ') {
292             $self->send_staff_notice('ILL_REQUEST_CANCEL');
293         }
294         return $ret;
295     } else {
296         return $current_status;
297     }
298 }
299
300 =head3 load_backend
301
302 Require "Base.pm" from the relevant ILL backend.
303
304 =cut
305
306 sub load_backend {
307     my ( $self, $backend_id ) = @_;
308
309     my @raw = qw/Koha Illbackends/; # Base Path
310
311     my $backend_name = $backend_id || $self->backend;
312
313     unless ( defined $backend_name && $backend_name ne '' ) {
314         Koha::Exceptions::Ill::InvalidBackendId->throw(
315             "An invalid backend ID was requested ('')");
316     }
317
318     my $location = join "/", @raw, $backend_name, "Base.pm";    # File to load
319     my $backend_class = join "::", @raw, $backend_name, "Base"; # Package name
320     require $location;
321     $self->{_my_backend} = $backend_class->new({
322         config => $self->_config,
323         logger => Koha::Illrequest::Logger->new
324     });
325     return $self;
326 }
327
328
329 =head3 _backend
330
331     my $backend = $abstract->_backend($new_backend);
332     my $backend = $abstract->_backend;
333
334 Getter/Setter for our API object.
335
336 =cut
337
338 sub _backend {
339     my ( $self, $backend ) = @_;
340     $self->{_my_backend} = $backend if ( $backend );
341     # Dynamically load our backend object, as late as possible.
342     $self->load_backend unless ( $self->{_my_backend} );
343     return $self->{_my_backend};
344 }
345
346 =head3 _backend_capability
347
348     my $backend_capability_result = $self->_backend_capability($name, $args);
349
350 This is a helper method to invoke optional capabilities in the backend.  If
351 the capability named by $name is not supported, return 0, else invoke it,
352 passing $args along with the invocation, and return its return value.
353
354 NOTE: this module suffers from a confusion in termninology:
355
356 in _backend_capability, the notion of capability refers to an optional feature
357 that is implemented in core, but might not be supported by a given backend.
358
359 in capabilities & custom_capability, capability refers to entries in the
360 status_graph (after union between backend and core).
361
362 The easiest way to fix this would be to fix the terminology in
363 capabilities & custom_capability and their callers.
364
365 =cut
366
367 sub _backend_capability {
368     my ( $self, $name, $args ) = @_;
369     my $capability = 0;
370     # See if capability is defined in backend
371     try {
372         $capability = $self->_backend->capabilities($name);
373     } catch {
374         return 0;
375     };
376     # Try to invoke it
377     if ( $capability && ref($capability) eq 'CODE' ) {
378         return &{$capability}($args);
379     } else {
380         return 0;
381     }
382 }
383
384 =head3 _config
385
386     my $config = $abstract->_config($config);
387     my $config = $abstract->_config;
388
389 Getter/Setter for our config object.
390
391 =cut
392
393 sub _config {
394     my ( $self, $config ) = @_;
395     $self->{_my_config} = $config if ( $config );
396     # Load our config object, as late as possible.
397     unless ( $self->{_my_config} ) {
398         $self->{_my_config} = Koha::Illrequest::Config->new;
399     }
400     return $self->{_my_config};
401 }
402
403 =head3 metadata
404
405 =cut
406
407 sub metadata {
408     my ( $self ) = @_;
409     return $self->_backend->metadata($self);
410 }
411
412 =head3 _core_status_graph
413
414     my $core_status_graph = $illrequest->_core_status_graph;
415
416 Returns ILL module's default status graph.  A status graph defines the list of
417 available actions at any stage in the ILL workflow.  This is for instance used
418 by the perl script & template to generate the correct buttons to display to
419 the end user at any given point.
420
421 =cut
422
423 sub _core_status_graph {
424     my ( $self ) = @_;
425     return {
426         NEW => {
427             prev_actions => [ ],                           # Actions containing buttons
428                                                            # leading to this status
429             id             => 'NEW',                       # ID of this status
430             name           => 'New request',               # UI name of this status
431             ui_method_name => 'New request',               # UI name of method leading
432                                                            # to this status
433             method         => 'create',                    # method to this status
434             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ], # buttons to add to all
435                                                            # requests with this status
436             ui_method_icon => 'fa-plus',                   # UI Style class
437         },
438         REQ => {
439             prev_actions   => [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ],
440             id             => 'REQ',
441             name           => 'Requested',
442             ui_method_name => 'Confirm request',
443             method         => 'confirm',
444             next_actions   => [ 'REQREV', 'COMP', 'CHK' ],
445             ui_method_icon => 'fa-check',
446         },
447         GENREQ => {
448             prev_actions   => [ 'NEW', 'REQREV' ],
449             id             => 'GENREQ',
450             name           => 'Requested from partners',
451             ui_method_name => 'Place request with partners',
452             method         => 'generic_confirm',
453             next_actions   => [ 'COMP', 'CHK' ],
454             ui_method_icon => 'fa-send-o',
455         },
456         REQREV => {
457             prev_actions   => [ 'REQ' ],
458             id             => 'REQREV',
459             name           => 'Request reverted',
460             ui_method_name => 'Revert Request',
461             method         => 'cancel',
462             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ],
463             ui_method_icon => 'fa-times',
464         },
465         QUEUED => {
466             prev_actions   => [ ],
467             id             => 'QUEUED',
468             name           => 'Queued request',
469             ui_method_name => 0,
470             method         => 0,
471             next_actions   => [ 'REQ', 'KILL' ],
472             ui_method_icon => 0,
473         },
474         CANCREQ => {
475             prev_actions   => [ 'NEW' ],
476             id             => 'CANCREQ',
477             name           => 'Cancellation requested',
478             ui_method_name => 0,
479             method         => 0,
480             next_actions   => [ 'KILL', 'REQ' ],
481             ui_method_icon => 0,
482         },
483         COMP => {
484             prev_actions   => [ 'REQ' ],
485             id             => 'COMP',
486             name           => 'Completed',
487             ui_method_name => 'Mark completed',
488             method         => 'mark_completed',
489             next_actions   => [ 'CHK' ],
490             ui_method_icon => 'fa-check',
491         },
492         KILL => {
493             prev_actions   => [ 'QUEUED', 'REQREV', 'NEW', 'CANCREQ' ],
494             id             => 'KILL',
495             name           => 0,
496             ui_method_name => 'Delete request',
497             method         => 'delete',
498             next_actions   => [ ],
499             ui_method_icon => 'fa-trash',
500         },
501         CHK => {
502             prev_actions   => [ 'REQ', 'GENREQ', 'COMP' ],
503             id             => 'CHK',
504             name           => 'Checked out',
505             ui_method_name => 'Check out',
506             needs_prefs    => [ 'CirculateILL' ],
507             needs_perms    => [ 'user_circulate_circulate_remaining_permissions' ],
508             # An array of functions that all must return true
509             needs_all      => [ sub { my $r = shift;  return $r->biblio; } ],
510             method         => 'check_out',
511             next_actions   => [ ],
512             ui_method_icon => 'fa-upload',
513         },
514         RET => {
515             prev_actions   => [ 'CHK' ],
516             id             => 'RET',
517             name           => 'Returned to library',
518             ui_method_name => 'Check in',
519             method         => 'check_in',
520             next_actions   => [ 'COMP' ],
521             ui_method_icon => 'fa-download',
522         }
523     };
524 }
525
526 =head3 _status_graph_union
527
528     my $status_graph = $illrequest->_status_graph_union($origin, $new_graph);
529
530 Return a new status_graph, the result of merging $origin & new_graph.  This is
531 operation is a union over the sets defied by the two graphs.
532
533 Each entry in $new_graph is added to $origin.  We do not provide a syntax for
534 'subtraction' of entries from $origin.
535
536 Whilst it is not intended that this works, you can override entries in $origin
537 with entries with the same key in $new_graph.  This can lead to problematic
538 behaviour when $new_graph adds an entry, which modifies a dependent entry in
539 $origin, only for the entry in $origin to be replaced later with a new entry
540 from $new_graph.
541
542 NOTE: this procedure does not "re-link" entries in $origin or $new_graph,
543 i.e. each of the graphs need to be correct at the outset of the operation.
544
545 =cut
546
547 sub _status_graph_union {
548     my ( $self, $core_status_graph, $backend_status_graph ) = @_;
549     # Create new status graph with:
550     # - all core_status_graph
551     # - for-each each backend_status_graph
552     #   + add to new status graph
553     #   + for each core prev_action:
554     #     * locate core_status
555     #     * update next_actions with additional next action.
556     #   + for each core next_action:
557     #     * locate core_status
558     #     * update prev_actions with additional prev action
559
560     my @core_status_ids = keys %{$core_status_graph};
561     my $status_graph = clone($core_status_graph);
562
563     foreach my $backend_status_key ( keys %{$backend_status_graph} ) {
564         my $backend_status = $backend_status_graph->{$backend_status_key};
565         # Add to new status graph
566         $status_graph->{$backend_status_key} = $backend_status;
567         # Update all core methods' next_actions.
568         foreach my $prev_action ( @{$backend_status->{prev_actions}} ) {
569             if ( grep { $prev_action eq $_ } @core_status_ids ) {
570                 my @next_actions =
571                      @{$status_graph->{$prev_action}->{next_actions}};
572                 push @next_actions, $backend_status_key;
573                 $status_graph->{$prev_action}->{next_actions}
574                     = \@next_actions;
575             }
576         }
577         # Update all core methods' prev_actions
578         foreach my $next_action ( @{$backend_status->{next_actions}} ) {
579             if ( grep { $next_action eq $_ } @core_status_ids ) {
580                 my @prev_actions =
581                      @{$status_graph->{$next_action}->{prev_actions}};
582                 push @prev_actions, $backend_status_key;
583                 $status_graph->{$next_action}->{prev_actions}
584                     = \@prev_actions;
585             }
586         }
587     }
588
589     return $status_graph;
590 }
591
592 ### Core API methods
593
594 =head3 capabilities
595
596     my $capabilities = $illrequest->capabilities;
597
598 Return a hashref mapping methods to operation names supported by the queried
599 backend.
600
601 Example return value:
602
603     { create => "Create Request", confirm => "Progress Request" }
604
605 NOTE: this module suffers from a confusion in termninology:
606
607 in _backend_capability, the notion of capability refers to an optional feature
608 that is implemented in core, but might not be supported by a given backend.
609
610 in capabilities & custom_capability, capability refers to entries in the
611 status_graph (after union between backend and core).
612
613 The easiest way to fix this would be to fix the terminology in
614 capabilities & custom_capability and their callers.
615
616 =cut
617
618 sub capabilities {
619     my ( $self, $status ) = @_;
620     # Generate up to date status_graph
621     my $status_graph = $self->_status_graph_union(
622         $self->_core_status_graph,
623         $self->_backend->status_graph({
624             request => $self,
625             other   => {}
626         })
627     );
628     # Extract available actions from graph.
629     return $status_graph->{$status} if $status;
630     # Or return entire graph.
631     return $status_graph;
632 }
633
634 =head3 custom_capability
635
636 Return the result of invoking $CANDIDATE on this request's backend with
637 $PARAMS, or 0 if $CANDIDATE is an unknown method on backend.
638
639 NOTE: this module suffers from a confusion in termninology:
640
641 in _backend_capability, the notion of capability refers to an optional feature
642 that is implemented in core, but might not be supported by a given backend.
643
644 in capabilities & custom_capability, capability refers to entries in the
645 status_graph (after union between backend and core).
646
647 The easiest way to fix this would be to fix the terminology in
648 capabilities & custom_capability and their callers.
649
650 =cut
651
652 sub custom_capability {
653     my ( $self, $candidate, $params ) = @_;
654     foreach my $capability ( values %{$self->capabilities} ) {
655         if ( $candidate eq $capability->{method} ) {
656             my $response =
657                 $self->_backend->$candidate({
658                     request    => $self,
659                     other      => $params,
660                 });
661             return $self->expandTemplate($response);
662         }
663     }
664     return 0;
665 }
666
667 =head3 available_backends
668
669 Return a list of available backends.
670
671 =cut
672
673 sub available_backends {
674     my ( $self, $reduced ) = @_;
675     my $backends = $self->_config->available_backends($reduced);
676     return $backends;
677 }
678
679 =head3 available_actions
680
681 Return a list of available actions.
682
683 =cut
684
685 sub available_actions {
686     my ( $self ) = @_;
687     my $current_action = $self->capabilities($self->status);
688     my @available_actions = map { $self->capabilities($_) }
689         @{$current_action->{next_actions}};
690     return \@available_actions;
691 }
692
693 =head3 mark_completed
694
695 Mark a request as completed (status = COMP).
696
697 =cut
698
699 sub mark_completed {
700     my ( $self ) = @_;
701     $self->status('COMP')->store;
702     $self->completed(dt_from_string())->store;
703     return {
704         error   => 0,
705         status  => '',
706         message => '',
707         method  => 'mark_completed',
708         stage   => 'commit',
709         next    => 'illview',
710     };
711 }
712
713 =head2 backend_migrate
714
715 Migrate a request from one backend to another.
716
717 =cut
718
719 sub backend_migrate {
720     my ( $self, $params ) = @_;
721
722     my $response = $self->_backend_capability('migrate',{
723             request    => $self,
724             other      => $params,
725         });
726     return $self->expandTemplate($response) if $response;
727     return $response;
728 }
729
730 =head2 backend_confirm
731
732 Confirm a request. The backend handles setting of mandatory fields in the commit stage:
733
734 =over
735
736 =item * orderid
737
738 =item * accessurl, cost (if available).
739
740 =back
741
742 =cut
743
744 sub backend_confirm {
745     my ( $self, $params ) = @_;
746
747     my $response = $self->_backend->confirm({
748             request    => $self,
749             other      => $params,
750         });
751     return $self->expandTemplate($response);
752 }
753
754 =head3 backend_update_status
755
756 =cut
757
758 sub backend_update_status {
759     my ( $self, $params ) = @_;
760     return $self->expandTemplate($self->_backend->update_status($params));
761 }
762
763 =head3 backend_cancel
764
765     my $ILLResponse = $illRequest->backend_cancel;
766
767 The standard interface method allowing for request cancellation.
768
769 =cut
770
771 sub backend_cancel {
772     my ( $self, $params ) = @_;
773
774     my $result = $self->_backend->cancel({
775         request => $self,
776         other => $params
777     });
778
779     return $self->expandTemplate($result);
780 }
781
782 =head3 backend_renew
783
784     my $renew_response = $illRequest->backend_renew;
785
786 The standard interface method allowing for request renewal queries.
787
788 =cut
789
790 sub backend_renew {
791     my ( $self ) = @_;
792     return $self->expandTemplate(
793         $self->_backend->renew({
794             request    => $self,
795         })
796     );
797 }
798
799 =head3 backend_create
800
801     my $create_response = $abstractILL->backend_create($params);
802
803 Return an array of Record objects created by querying our backend with
804 a Search query.
805
806 In the context of the other ILL methods, this is a special method: we only
807 pass it $params, as it does not yet have any other data associated with it.
808
809 =cut
810
811 sub backend_create {
812     my ( $self, $params ) = @_;
813
814     # Establish whether we need to do a generic copyright clearance.
815     if ($params->{opac}) {
816         if ( ( !$params->{stage} || $params->{stage} eq 'init' )
817                 && C4::Context->preference("ILLModuleCopyrightClearance") ) {
818             return {
819                 error   => 0,
820                 status  => '',
821                 message => '',
822                 method  => 'create',
823                 stage   => 'copyrightclearance',
824                 value   => {
825                     other   => $params,
826                     backend => $self->_backend->name
827                 }
828             };
829         } elsif (     defined $params->{stage}
830                 && $params->{stage} eq 'copyrightclearance' ) {
831             $params->{stage} = 'init';
832         }
833     }
834     # First perform API action, then...
835     my $args = {
836         request => $self,
837         other   => $params,
838     };
839     my $result = $self->_backend->create($args);
840
841     # ... simple case: we're not at 'commit' stage.
842     my $stage = $result->{stage};
843     return $self->expandTemplate($result)
844         unless ( 'commit' eq $stage );
845
846     # ... complex case: commit!
847
848     # Do we still have space for an ILL or should we queue?
849     my $permitted = $self->check_limits(
850         { patron => $self->patron }, { librarycode => $self->branchcode }
851     );
852
853     # Now augment our committed request.
854
855     $result->{permitted} = $permitted;             # Queue request?
856
857     # This involves...
858
859     # ...Updating status!
860     $self->status('QUEUED')->store unless ( $permitted );
861
862     ## Handle Unmediated ILLs
863
864     # For the unmediated workflow we only need to delegate to our backend. If
865     # that backend supports unmediateld_ill, it will do its thing and return a
866     # proper response.  If it doesn't then _backend_capability returns 0, so
867     # we keep the current result.
868     if ( C4::Context->preference("ILLModuleUnmediated") && $permitted ) {
869         my $unmediated_result = $self->_backend_capability(
870             'unmediated_ill',
871             $args
872         );
873         $result = $unmediated_result if $unmediated_result;
874     }
875
876     return $self->expandTemplate($result);
877 }
878
879 =head3 expandTemplate
880
881     my $params = $abstract->expandTemplate($params);
882
883 Return a version of $PARAMS augmented with our required template path.
884
885 =cut
886
887 sub expandTemplate {
888     my ( $self, $params ) = @_;
889     my $backend = $self->_backend->name;
890     # Generate path to file to load
891     my $backend_dir = $self->_config->backend_dir;
892     my $backend_tmpl = join "/", $backend_dir, $backend;
893     my $intra_tmpl =  join "/", $backend_tmpl, "intra-includes",
894         ( $params->{method}//q{} ) . ".inc";
895     my $opac_tmpl =  join "/", $backend_tmpl, "opac-includes",
896         ( $params->{method}//q{} ) . ".inc";
897     # Set files to load
898     $params->{template} = $intra_tmpl;
899     $params->{opac_template} = $opac_tmpl;
900     return $params;
901 }
902
903 #### Abstract Imports
904
905 =head3 getLimits
906
907     my $limit_rules = $abstract->getLimits( {
908         type  => 'brw_cat' | 'branch',
909         value => $value
910     } );
911
912 Return the ILL limit rules for the supplied combination of type / value.
913
914 As the config may have no rules for this particular type / value combination,
915 or for the default, we must define fall-back values here.
916
917 =cut
918
919 sub getLimits {
920     my ( $self, $params ) = @_;
921     my $limits = $self->_config->getLimitRules($params->{type});
922
923     if (     defined $params->{value}
924           && defined $limits->{$params->{value}} ) {
925             return $limits->{$params->{value}};
926     }
927     else {
928         return $limits->{default} || { count => -1, method => 'active' };
929     }
930 }
931
932 =head3 getPrefix
933
934     my $prefix = $abstract->getPrefix( {
935         branch  => $branch_code
936     } );
937
938 Return the ILL prefix as defined by our $params: either per borrower category,
939 per branch or the default.
940
941 =cut
942
943 sub getPrefix {
944     my ( $self, $params ) = @_;
945     my $brn_prefixes = $self->_config->getPrefixes();
946     return $brn_prefixes->{$params->{branch}} || ""; # "the empty prefix"
947 }
948
949 =head3 get_type
950
951     my $type = $abstract->get_type();
952
953 Return a string representing the material type of this request or undef
954
955 =cut
956
957 sub get_type {
958     my ($self) = @_;
959     my $attr = $self->illrequestattributes->find({ type => 'type'});
960     return if !$attr;
961     return $attr->value;
962 };
963
964 #### Illrequests Imports
965
966 =head3 check_limits
967
968     my $ok = $illRequests->check_limits( {
969         borrower   => $borrower,
970         branchcode => 'branchcode' | undef,
971     } );
972
973 Given $PARAMS, a hashref containing a $borrower object and a $branchcode,
974 see whether we are still able to place ILLs.
975
976 LimitRules are derived from koha-conf.xml:
977  + default limit counts, and counting method
978  + branch specific limit counts & counting method
979  + borrower category specific limit counts & counting method
980  + err on the side of caution: a counting fail will cause fail, even if
981    the other counts passes.
982
983 =cut
984
985 sub check_limits {
986     my ( $self, $params ) = @_;
987     my $patron     = $params->{patron};
988     my $branchcode = $params->{librarycode} || $patron->branchcode;
989
990     # Establish maximum number of allowed requests
991     my ( $branch_rules, $brw_rules ) = (
992         $self->getLimits( {
993             type => 'branch',
994             value => $branchcode
995         } ),
996         $self->getLimits( {
997             type => 'brw_cat',
998             value => $patron->categorycode,
999         } ),
1000     );
1001     my ( $branch_limit, $brw_limit )
1002         = ( $branch_rules->{count}, $brw_rules->{count} );
1003     # Establish currently existing requests
1004     my ( $branch_count, $brw_count ) = (
1005         $self->_limit_counter(
1006             $branch_rules->{method}, { branchcode => $branchcode }
1007         ),
1008         $self->_limit_counter(
1009             $brw_rules->{method}, { borrowernumber => $patron->borrowernumber }
1010         ),
1011     );
1012
1013     # Compare and return
1014     # A limit of -1 means no limit exists.
1015     # We return blocked if either branch limit or brw limit is reached.
1016     if ( ( $branch_limit != -1 && $branch_limit <= $branch_count )
1017              || ( $brw_limit != -1 && $brw_limit <= $brw_count ) ) {
1018         return 0;
1019     } else {
1020         return 1;
1021     }
1022 }
1023
1024 sub _limit_counter {
1025     my ( $self, $method, $target ) = @_;
1026
1027     # Establish parameters of counts
1028     my $resultset;
1029     if ($method && $method eq 'annual') {
1030         $resultset = Koha::Illrequests->search({
1031             -and => [
1032                 %{$target},
1033                 \"YEAR(placed) = YEAR(NOW())"
1034             ]
1035         });
1036     } else {                    # assume 'active'
1037         # XXX: This status list is ugly. There should be a method in config
1038         # to return these.
1039         my $where = { status => { -not_in => [ 'QUEUED', 'COMP' ] } };
1040         $resultset = Koha::Illrequests->search({ %{$target}, %{$where} });
1041     }
1042
1043     # Fetch counts
1044     return $resultset->count;
1045 }
1046
1047 =head3 requires_moderation
1048
1049     my $status = $illRequest->requires_moderation;
1050
1051 Return the name of the status if moderation by staff is required; or 0
1052 otherwise.
1053
1054 =cut
1055
1056 sub requires_moderation {
1057     my ( $self ) = @_;
1058     my $require_moderation = {
1059         'CANCREQ' => 'CANCREQ',
1060     };
1061     return $require_moderation->{$self->status};
1062 }
1063
1064 =head3 biblio
1065
1066     my $biblio = $request->biblio;
1067
1068 For a given request, return the biblio associated with it,
1069 or undef if none exists
1070
1071 =cut
1072
1073 sub biblio {
1074     my ( $self ) = @_;
1075
1076     return if !$self->biblio_id;
1077
1078     return Koha::Biblios->find({
1079         biblionumber => $self->biblio_id
1080     });
1081 }
1082
1083 =head3 check_out
1084
1085     my $stage_summary = $request->check_out;
1086
1087 Handle the check_out method. The first stage involves gathering the required
1088 data from the user via a form, the second stage creates an item and tries to
1089 issue it to the patron. If successful, it notifies the patron, then it
1090 returns a summary of how things went
1091
1092 =cut
1093
1094 sub check_out {
1095     my ( $self, $params ) = @_;
1096
1097     # Objects required by the template
1098     my $itemtypes = Koha::ItemTypes->search(
1099         {},
1100         { order_by => ['description'] }
1101     );
1102     my $libraries = Koha::Libraries->search(
1103         {},
1104         { order_by => ['branchcode'] }
1105     );
1106     my $biblio = $self->biblio;
1107
1108     # Find all statistical patrons
1109     my $statistical_patrons = Koha::Patrons->search(
1110         { 'category_type' => 'x' },
1111         { join => { 'categorycode' => 'borrowers' } }
1112     );
1113
1114     if (!$params->{stage} || $params->{stage} eq 'init') {
1115         # Present a form to gather the required data
1116         #
1117         # We may be viewing this page having previously tried to issue
1118         # the item (in which case, we may already have created an item)
1119         # so we pass the biblio for this request
1120         return {
1121             method  => 'check_out',
1122             stage   => 'form',
1123             value   => {
1124                 itemtypes   => $itemtypes,
1125                 libraries   => $libraries,
1126                 statistical => $statistical_patrons,
1127                 biblio      => $biblio
1128             }
1129         };
1130     } elsif ($params->{stage} eq 'form') {
1131         # Validate what we've got and return with an error if we fail
1132         my $errors = {};
1133         if (!$params->{item_type} || length $params->{item_type} == 0) {
1134             $errors->{item_type} = 1;
1135         }
1136         if ($params->{inhouse} && length $params->{inhouse} > 0) {
1137             my $patron_count = Koha::Patrons->search({
1138                 cardnumber => $params->{inhouse}
1139             })->count();
1140             if ($patron_count != 1) {
1141                 $errors->{inhouse} = 1;
1142             }
1143         }
1144
1145         # Check we don't have more than one item for this bib,
1146         # if we do, something very odd is going on
1147         # Having 1 is OK, it means we're likely trying to issue
1148         # following a previously failed attempt, the item exists
1149         # so we'll use it
1150         my @items = $biblio->items->as_list;
1151         my $item_count = scalar @items;
1152         if ($item_count > 1) {
1153             $errors->{itemcount} = 1;
1154         }
1155
1156         # Failed validation, go back to the form
1157         if (%{$errors}) {
1158             return {
1159                 method  => 'check_out',
1160                 stage   => 'form',
1161                 value   => {
1162                     params      => $params,
1163                     statistical => $statistical_patrons,
1164                     itemtypes   => $itemtypes,
1165                     libraries   => $libraries,
1166                     biblio      => $biblio,
1167                     errors      => $errors
1168                 }
1169             };
1170         }
1171
1172         # Passed validation
1173         #
1174         # Create an item if one doesn't already exist,
1175         # if one does, use that
1176         my $itemnumber;
1177         if ($item_count == 0) {
1178             my $item_hash = {
1179                 biblionumber  => $self->biblio_id,
1180                 homebranch    => $params->{branchcode},
1181                 holdingbranch => $params->{branchcode},
1182                 location      => $params->{branchcode},
1183                 itype         => $params->{item_type},
1184                 barcode       => 'ILL-' . $self->illrequest_id
1185             };
1186             try {
1187                 my $item = Koha::Item->new($item_hash)->store;
1188                 $itemnumber = $item->itemnumber;
1189             };
1190         } else {
1191             $itemnumber = $items[0]->itemnumber;
1192         }
1193         # Check we have an item before going forward
1194         if (!$itemnumber) {
1195             return {
1196                 method  => 'check_out',
1197                 stage   => 'form',
1198                 value   => {
1199                     params      => $params,
1200                     itemtypes   => $itemtypes,
1201                     libraries   => $libraries,
1202                     statistical => $statistical_patrons,
1203                     errors      => { item_creation => 1 }
1204                 }
1205             };
1206         }
1207
1208         # Do the check out
1209         #
1210         # Gather what we need
1211         my $target_item = Koha::Items->find( $itemnumber );
1212         # Determine who we're issuing to
1213         my $patron = $params->{inhouse} && length $params->{inhouse} > 0 ?
1214             Koha::Patrons->find({ cardnumber => $params->{inhouse} }) :
1215             $self->patron;
1216
1217         my @issue_args = (
1218             $patron,
1219             scalar $target_item->barcode
1220         );
1221         if ($params->{duedate} && length $params->{duedate} > 0) {
1222             push @issue_args, $params->{duedate};
1223         }
1224         # Check if we can check out
1225         my ( $error, $confirm, $alerts, $messages ) =
1226             C4::Circulation::CanBookBeIssued(@issue_args);
1227
1228         # If we got anything back saying we can't check out,
1229         # return it to the template
1230         my $problems = {};
1231         if ( $error && %{$error} ) { $problems->{error} = $error };
1232         if ( $confirm && %{$confirm} ) { $problems->{confirm} = $confirm };
1233         if ( $alerts && %{$alerts} ) { $problems->{alerts} = $alerts };
1234         if ( $messages && %{$messages} ) { $problems->{messages} = $messages };
1235
1236         if (%{$problems}) {
1237             return {
1238                 method  => 'check_out',
1239                 stage   => 'form',
1240                 value   => {
1241                     params           => $params,
1242                     itemtypes        => $itemtypes,
1243                     libraries        => $libraries,
1244                     statistical      => $statistical_patrons,
1245                     patron           => $patron,
1246                     biblio           => $biblio,
1247                     check_out_errors => $problems
1248                 }
1249             };
1250         }
1251
1252         # We can allegedly check out, so make it so
1253         # For some reason, AddIssue requires an unblessed Patron
1254         $issue_args[0] = $patron->unblessed;
1255         my $issue = C4::Circulation::AddIssue(@issue_args);
1256
1257         if ($issue) {
1258             # Update the request status
1259             $self->status('CHK')->store;
1260             return {
1261                 method  => 'check_out',
1262                 stage   => 'done_check_out',
1263                 value   => {
1264                     params    => $params,
1265                     patron    => $patron,
1266                     check_out => $issue
1267                 }
1268             };
1269         } else {
1270             return {
1271                 method  => 'check_out',
1272                 stage   => 'form',
1273                 value   => {
1274                     params    => $params,
1275                     itemtypes => $itemtypes,
1276                     libraries => $libraries,
1277                     errors    => { item_check_out => 1 }
1278                 }
1279             };
1280         }
1281     }
1282
1283 }
1284
1285 =head3 generic_confirm
1286
1287     my $stage_summary = $illRequest->generic_confirm;
1288
1289 Handle the generic_confirm extended method.  The first stage involves creating
1290 a template email for the end user to edit in the browser.  The second stage
1291 attempts to submit the email.
1292
1293 =cut
1294
1295 sub generic_confirm {
1296     my ( $self, $params ) = @_;
1297     my $branch = Koha::Libraries->find($params->{current_branchcode})
1298         || die "Invalid current branchcode. Are you logged in as the database user?";
1299     if ( !$params->{stage}|| $params->{stage} eq 'init' ) {
1300         # Get the message body from the notice definition
1301         my $letter = $self->get_notice({
1302             notice_code => 'ILL_PARTNER_REQ',
1303             transport   => 'email'
1304         });
1305
1306         my $partners = Koha::Patrons->search({
1307             categorycode => $self->_config->partner_code
1308         });
1309         return {
1310             error   => 0,
1311             status  => '',
1312             message => '',
1313             method  => 'generic_confirm',
1314             stage   => 'draft',
1315             value   => {
1316                 draft => {
1317                     subject => $letter->{title},
1318                     body    => $letter->{content}
1319                 },
1320                 partners => $partners,
1321             }
1322         };
1323
1324     } elsif ( 'draft' eq $params->{stage} ) {
1325         # Create the to header
1326         my $to = $params->{partners};
1327         if ( defined $to ) {
1328             $to =~ s/^\x00//;       # Strip leading NULLs
1329             $to =~ s/\x00/; /;      # Replace others with '; '
1330         }
1331         Koha::Exceptions::Ill::NoTargetEmail->throw(
1332             "No target email addresses found. Either select at least one partner or check your ILL partner library records.")
1333           if ( !$to );
1334         # Create the from, replyto and sender headers
1335         my $from = $branch->from_email_address;
1336         my $replyto = $branch->inbound_ill_address;
1337         Koha::Exceptions::Ill::NoLibraryEmail->throw(
1338             "Your library has no usable email address. Please set it.")
1339           if ( !$from );
1340
1341         # So we get a notice hashref, then substitute the possibly
1342         # modified title and body from the draft stage
1343         my $letter = $self->get_notice({
1344             notice_code => 'ILL_PARTNER_REQ',
1345             transport   => 'email'
1346         });
1347         $letter->{title} = $params->{subject};
1348         $letter->{content} = $params->{body};
1349
1350         # Queue the notice
1351         my $params = {
1352             letter                 => $letter,
1353             borrowernumber         => $self->borrowernumber,
1354             message_transport_type => 'email',
1355             to_address             => $to,
1356             from_address           => $from,
1357             reply_address          => $replyto
1358         };
1359
1360         if ($letter) {
1361             my $result = C4::Letters::EnqueueLetter($params);
1362             if ( $result ) {
1363                 $self->status("GENREQ")->store;
1364                 $self->_backend_capability(
1365                     'set_requested_partners',
1366                     {
1367                         request => $self,
1368                         to => $to
1369                     }
1370                 );
1371                 return {
1372                     error   => 0,
1373                     status  => '',
1374                     message => '',
1375                     method  => 'generic_confirm',
1376                     stage   => 'commit',
1377                     next    => 'illview',
1378                 };
1379             }
1380         }
1381         return {
1382             error   => 1,
1383             status  => 'email_failed',
1384             message => 'Email queueing failed',
1385             method  => 'generic_confirm',
1386             stage   => 'draft',
1387         };
1388     } else {
1389         die "Unknown stage, should not have happened."
1390     }
1391 }
1392
1393 =head3 send_patron_notice
1394
1395     my $result = $request->send_patron_notice($notice_code);
1396
1397 Send a specified notice regarding this request to a patron
1398
1399 =cut
1400
1401 sub send_patron_notice {
1402     my ( $self, $notice_code ) = @_;
1403
1404     # We need a notice code
1405     if (!$notice_code) {
1406         return {
1407             error => 'notice_no_type'
1408         };
1409     }
1410
1411     # Map from the notice code to the messaging preference
1412     my %message_name = (
1413         ILL_PICKUP_READY   => 'Ill_ready',
1414         ILL_REQUEST_UNAVAIL => 'Ill_unavailable'
1415     );
1416
1417     # Get the patron's messaging preferences
1418     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences({
1419         borrowernumber => $self->borrowernumber,
1420         message_name   => $message_name{$notice_code}
1421     });
1422     my @transports = keys %{ $borrower_preferences->{transports} };
1423
1424     # Notice should come from the library where the request was placed,
1425     # not the patrons home library
1426     my $branch = Koha::Libraries->find($self->branchcode);
1427     my $from_address = $branch->from_email_address;
1428     my $reply_address = $branch->inbound_ill_address;
1429
1430     # Send the notice to the patron via the chosen transport methods
1431     # and record the results
1432     my @success = ();
1433     my @fail = ();
1434     for my $transport (@transports) {
1435         my $letter = $self->get_notice({
1436             notice_code => $notice_code,
1437             transport   => $transport
1438         });
1439         if ($letter) {
1440             my $result = C4::Letters::EnqueueLetter({
1441                 letter                 => $letter,
1442                 borrowernumber         => $self->borrowernumber,
1443                 message_transport_type => $transport,
1444                 from_address           => $from_address,
1445                 reply_address          => $reply_address
1446             });
1447             if ($result) {
1448                 push @success, $transport;
1449             } else {
1450                 push @fail, $transport;
1451             }
1452         } else {
1453             push @fail, $transport;
1454         }
1455     }
1456     if (scalar @success > 0) {
1457         my $logger = Koha::Illrequest::Logger->new;
1458         $logger->log_patron_notice({
1459             request => $self,
1460             notice_code => $notice_code
1461         });
1462     }
1463     return {
1464         result => {
1465             success => \@success,
1466             fail    => \@fail
1467         }
1468     };
1469 }
1470
1471 =head3 send_staff_notice
1472
1473     my $result = $request->send_staff_notice($notice_code);
1474
1475 Send a specified notice regarding this request to staff
1476
1477 =cut
1478
1479 sub send_staff_notice {
1480     my ( $self, $notice_code ) = @_;
1481
1482     # We need a notice code
1483     if (!$notice_code) {
1484         return {
1485             error => 'notice_no_type'
1486         };
1487     }
1488
1489     # Get the staff notices that have been assigned for sending in
1490     # the syspref
1491     my $staff_to_send = C4::Context->preference('ILLSendStaffNotices') // q{};
1492
1493     # If it hasn't been enabled in the syspref, we don't want to send it
1494     if ($staff_to_send !~ /\b$notice_code\b/) {
1495         return {
1496             error => 'notice_not_enabled'
1497         };
1498     }
1499
1500     my $letter = $self->get_notice({
1501         notice_code => $notice_code,
1502         transport   => 'email'
1503     });
1504
1505     # Try and get an address to which to send staff notices
1506     my $branch = Koha::Libraries->find($self->branchcode);
1507     my $to_address = $branch->inbound_ill_address;
1508     my $from_address = $branch->inbound_ill_address;
1509
1510     my $params = {
1511         letter                 => $letter,
1512         borrowernumber         => $self->borrowernumber,
1513         message_transport_type => 'email',
1514         from_address           => $from_address
1515     };
1516
1517     if ($to_address) {
1518         $params->{to_address} = $to_address;
1519     } else {
1520         return {
1521             error => 'notice_no_create'
1522         };
1523     }
1524
1525     if ($letter) {
1526         C4::Letters::EnqueueLetter($params)
1527             or warn "can't enqueue letter $letter";
1528         return {
1529             success => 'notice_queued'
1530         };
1531     } else {
1532         return {
1533             error => 'notice_no_create'
1534         };
1535     }
1536 }
1537
1538 =head3 get_notice
1539
1540     my $notice = $request->get_notice($params);
1541
1542 Return a compiled notice hashref for the passed notice code
1543 and transport type
1544
1545 =cut
1546
1547 sub get_notice {
1548     my ( $self, $params ) = @_;
1549
1550     my $title = $self->illrequestattributes->find(
1551         { type => 'title' }
1552     );
1553     my $author = $self->illrequestattributes->find(
1554         { type => 'author' }
1555     );
1556     my $metahash = $self->metadata;
1557     my @metaarray = ();
1558     while (my($key, $value) = each %{$metahash}) {
1559         push @metaarray, "- $key: $value" if $value;
1560     }
1561     my $metastring = join("\n", @metaarray);
1562     my $letter = C4::Letters::GetPreparedLetter(
1563         module                 => 'ill',
1564         letter_code            => $params->{notice_code},
1565         branchcode             => $self->branchcode,
1566         message_transport_type => $params->{transport},
1567         lang                   => $self->patron->lang,
1568         tables                 => {
1569             illrequests => $self->illrequest_id,
1570             borrowers   => $self->borrowernumber,
1571             biblio      => $self->biblio_id,
1572             branches    => $self->branchcode,
1573         },
1574         substitute  => {
1575             ill_bib_title      => $title ? $title->value : '',
1576             ill_bib_author     => $author ? $author->value : '',
1577             ill_full_metadata  => $metastring
1578         }
1579     );
1580
1581     return $letter;
1582 }
1583
1584 =head3 id_prefix
1585
1586     my $prefix = $record->id_prefix;
1587
1588 Return the prefix appropriate for the current Illrequest as derived from the
1589 borrower and branch associated with this request's Status, and the config
1590 file.
1591
1592 =cut
1593
1594 sub id_prefix {
1595     my ( $self ) = @_;
1596     my $prefix = $self->getPrefix( {
1597         branch  => $self->branchcode,
1598     } );
1599     $prefix .= "-" if ( $prefix );
1600     return $prefix;
1601 }
1602
1603 =head3 _censor
1604
1605     my $params = $illRequest->_censor($params);
1606
1607 Return $params, modified to reflect our censorship requirements.
1608
1609 =cut
1610
1611 sub _censor {
1612     my ( $self, $params ) = @_;
1613     my $censorship = $self->_config->censorship;
1614     $params->{censor_notes_staff} = $censorship->{censor_notes_staff}
1615         if ( $params->{opac} );
1616     $params->{display_reply_date} = ( $censorship->{censor_reply_date} ) ? 0 : 1;
1617
1618     return $params;
1619 }
1620
1621 =head3 store
1622
1623     $Illrequest->store;
1624
1625 Overloaded I<store> method that, in addition to performing the 'store',
1626 possibly records the fact that something happened
1627
1628 =cut
1629
1630 sub store {
1631     my ( $self, $attrs ) = @_;
1632
1633     my $ret = $self->SUPER::store;
1634
1635     $attrs->{log_origin} = 'core';
1636
1637     if ($ret && defined $attrs) {
1638         my $logger = Koha::Illrequest::Logger->new;
1639         $logger->log_maybe({
1640             request => $self,
1641             attrs   => $attrs
1642         });
1643     }
1644
1645     return $ret;
1646 }
1647
1648 =head3 requested_partners
1649
1650     my $partners_string = $illRequest->requested_partners;
1651
1652 Return the string representing the email addresses of the partners to
1653 whom a request has been sent
1654
1655 =cut
1656
1657 sub requested_partners {
1658     my ( $self ) = @_;
1659     return $self->_backend_capability(
1660         'get_requested_partners',
1661         { request => $self }
1662     );
1663 }
1664
1665 =head3 TO_JSON
1666
1667     $json = $illrequest->TO_JSON
1668
1669 Overloaded I<TO_JSON> method that takes care of inserting calculated values
1670 into the unblessed representation of the object.
1671
1672 TODO: This method does nothing and is not called anywhere. However, bug 74325
1673 touches it, so keeping this for now until both this and bug 74325 are merged,
1674 at which point we can sort it out and remove it completely
1675
1676 =cut
1677
1678 sub TO_JSON {
1679     my ( $self, $embed ) = @_;
1680
1681     my $object = $self->SUPER::TO_JSON();
1682
1683     return $object;
1684 }
1685
1686 =head2 Internal methods
1687
1688 =head3 _type
1689
1690 =cut
1691
1692 sub _type {
1693     return 'Illrequest';
1694 }
1695
1696 =head1 AUTHOR
1697
1698 Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com>
1699 Andrew Isherwood <andrew.isherwood@ptfs-europe.com>
1700
1701 =cut
1702
1703 1;