6d26418cd9f18928efeb7a88d7a1c2965eef4c0a
[koha.git] / 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                     if (!grep(/^$backend_status_key$/, @next_actions));
574                 $status_graph->{$prev_action}->{next_actions}
575                     = \@next_actions;
576             }
577         }
578         # Update all core methods' prev_actions
579         foreach my $next_action ( @{$backend_status->{next_actions}} ) {
580             if ( grep { $next_action eq $_ } @core_status_ids ) {
581                 my @prev_actions =
582                      @{$status_graph->{$next_action}->{prev_actions}};
583                 push @prev_actions, $backend_status_key
584                     if (!grep(/^$backend_status_key$/, @prev_actions));
585                 $status_graph->{$next_action}->{prev_actions}
586                     = \@prev_actions;
587             }
588         }
589     }
590
591     return $status_graph;
592 }
593
594 ### Core API methods
595
596 =head3 capabilities
597
598     my $capabilities = $illrequest->capabilities;
599
600 Return a hashref mapping methods to operation names supported by the queried
601 backend.
602
603 Example return value:
604
605     { create => "Create Request", confirm => "Progress Request" }
606
607 NOTE: this module suffers from a confusion in termninology:
608
609 in _backend_capability, the notion of capability refers to an optional feature
610 that is implemented in core, but might not be supported by a given backend.
611
612 in capabilities & custom_capability, capability refers to entries in the
613 status_graph (after union between backend and core).
614
615 The easiest way to fix this would be to fix the terminology in
616 capabilities & custom_capability and their callers.
617
618 =cut
619
620 sub capabilities {
621     my ( $self, $status ) = @_;
622     # Generate up to date status_graph
623     my $status_graph = $self->_status_graph_union(
624         $self->_core_status_graph,
625         $self->_backend->status_graph({
626             request => $self,
627             other   => {}
628         })
629     );
630     # Extract available actions from graph.
631     return $status_graph->{$status} if $status;
632     # Or return entire graph.
633     return $status_graph;
634 }
635
636 =head3 custom_capability
637
638 Return the result of invoking $CANDIDATE on this request's backend with
639 $PARAMS, or 0 if $CANDIDATE is an unknown method on backend.
640
641 NOTE: this module suffers from a confusion in termninology:
642
643 in _backend_capability, the notion of capability refers to an optional feature
644 that is implemented in core, but might not be supported by a given backend.
645
646 in capabilities & custom_capability, capability refers to entries in the
647 status_graph (after union between backend and core).
648
649 The easiest way to fix this would be to fix the terminology in
650 capabilities & custom_capability and their callers.
651
652 =cut
653
654 sub custom_capability {
655     my ( $self, $candidate, $params ) = @_;
656     foreach my $capability ( values %{$self->capabilities} ) {
657         if ( $candidate eq $capability->{method} ) {
658             my $response =
659                 $self->_backend->$candidate({
660                     request    => $self,
661                     other      => $params,
662                 });
663             return $self->expandTemplate($response);
664         }
665     }
666     return 0;
667 }
668
669 =head3 available_backends
670
671 Return a list of available backends.
672
673 =cut
674
675 sub available_backends {
676     my ( $self, $reduced ) = @_;
677     my $backends = $self->_config->available_backends($reduced);
678     return $backends;
679 }
680
681 =head3 available_actions
682
683 Return a list of available actions.
684
685 =cut
686
687 sub available_actions {
688     my ( $self ) = @_;
689     my $current_action = $self->capabilities($self->status);
690     my @available_actions = map { $self->capabilities($_) }
691         @{$current_action->{next_actions}};
692     return \@available_actions;
693 }
694
695 =head3 mark_completed
696
697 Mark a request as completed (status = COMP).
698
699 =cut
700
701 sub mark_completed {
702     my ( $self ) = @_;
703     $self->status('COMP')->store;
704     $self->completed(dt_from_string())->store;
705     return {
706         error   => 0,
707         status  => '',
708         message => '',
709         method  => 'mark_completed',
710         stage   => 'commit',
711         next    => 'illview',
712     };
713 }
714
715 =head2 backend_migrate
716
717 Migrate a request from one backend to another.
718
719 =cut
720
721 sub backend_migrate {
722     my ( $self, $params ) = @_;
723     # Set the request's backend to be the destination backend
724     $self->load_backend($params->{backend});
725     my $response = $self->_backend_capability('migrate',{
726             request    => $self,
727             other      => $params,
728         });
729     return $self->expandTemplate($response) if $response;
730     return $response;
731 }
732
733 =head2 backend_confirm
734
735 Confirm a request. The backend handles setting of mandatory fields in the commit stage:
736
737 =over
738
739 =item * orderid
740
741 =item * accessurl, cost (if available).
742
743 =back
744
745 =cut
746
747 sub backend_confirm {
748     my ( $self, $params ) = @_;
749
750     my $response = $self->_backend->confirm({
751             request    => $self,
752             other      => $params,
753         });
754     return $self->expandTemplate($response);
755 }
756
757 =head3 backend_update_status
758
759 =cut
760
761 sub backend_update_status {
762     my ( $self, $params ) = @_;
763     return $self->expandTemplate($self->_backend->update_status($params));
764 }
765
766 =head3 backend_cancel
767
768     my $ILLResponse = $illRequest->backend_cancel;
769
770 The standard interface method allowing for request cancellation.
771
772 =cut
773
774 sub backend_cancel {
775     my ( $self, $params ) = @_;
776
777     my $result = $self->_backend->cancel({
778         request => $self,
779         other => $params
780     });
781
782     return $self->expandTemplate($result);
783 }
784
785 =head3 backend_renew
786
787     my $renew_response = $illRequest->backend_renew;
788
789 The standard interface method allowing for request renewal queries.
790
791 =cut
792
793 sub backend_renew {
794     my ( $self ) = @_;
795     return $self->expandTemplate(
796         $self->_backend->renew({
797             request    => $self,
798         })
799     );
800 }
801
802 =head3 backend_create
803
804     my $create_response = $abstractILL->backend_create($params);
805
806 Return an array of Record objects created by querying our backend with
807 a Search query.
808
809 In the context of the other ILL methods, this is a special method: we only
810 pass it $params, as it does not yet have any other data associated with it.
811
812 =cut
813
814 sub backend_create {
815     my ( $self, $params ) = @_;
816
817     # Establish whether we need to do a generic copyright clearance.
818     if ($params->{opac}) {
819         if ( ( !$params->{stage} || $params->{stage} eq 'init' )
820                 && C4::Context->preference("ILLModuleCopyrightClearance") ) {
821             return {
822                 error   => 0,
823                 status  => '',
824                 message => '',
825                 method  => 'create',
826                 stage   => 'copyrightclearance',
827                 value   => {
828                     other   => $params,
829                     backend => $self->_backend->name
830                 }
831             };
832         } elsif (     defined $params->{stage}
833                 && $params->{stage} eq 'copyrightclearance' ) {
834             $params->{stage} = 'init';
835         }
836     }
837     # First perform API action, then...
838     my $args = {
839         request => $self,
840         other   => $params,
841     };
842     my $result = $self->_backend->create($args);
843
844     # ... simple case: we're not at 'commit' stage.
845     my $stage = $result->{stage};
846     return $self->expandTemplate($result)
847         unless ( 'commit' eq $stage );
848
849     # ... complex case: commit!
850
851     # Do we still have space for an ILL or should we queue?
852     my $permitted = $self->check_limits(
853         { patron => $self->patron }, { librarycode => $self->branchcode }
854     );
855
856     # Now augment our committed request.
857
858     $result->{permitted} = $permitted;             # Queue request?
859
860     # This involves...
861
862     # ...Updating status!
863     $self->status('QUEUED')->store unless ( $permitted );
864
865     ## Handle Unmediated ILLs
866
867     # For the unmediated workflow we only need to delegate to our backend. If
868     # that backend supports unmediateld_ill, it will do its thing and return a
869     # proper response.  If it doesn't then _backend_capability returns 0, so
870     # we keep the current result.
871     if ( C4::Context->preference("ILLModuleUnmediated") && $permitted ) {
872         my $unmediated_result = $self->_backend_capability(
873             'unmediated_ill',
874             $args
875         );
876         $result = $unmediated_result if $unmediated_result;
877     }
878
879     return $self->expandTemplate($result);
880 }
881
882 =head3 expandTemplate
883
884     my $params = $abstract->expandTemplate($params);
885
886 Return a version of $PARAMS augmented with our required template path.
887
888 =cut
889
890 sub expandTemplate {
891     my ( $self, $params ) = @_;
892     my $backend = $self->_backend->name;
893     # Generate path to file to load
894     my $backend_dir = $self->_config->backend_dir;
895     my $backend_tmpl = join "/", $backend_dir, $backend;
896     my $intra_tmpl =  join "/", $backend_tmpl, "intra-includes",
897         ( $params->{method}//q{} ) . ".inc";
898     my $opac_tmpl =  join "/", $backend_tmpl, "opac-includes",
899         ( $params->{method}//q{} ) . ".inc";
900     # Set files to load
901     $params->{template} = $intra_tmpl;
902     $params->{opac_template} = $opac_tmpl;
903     return $params;
904 }
905
906 #### Abstract Imports
907
908 =head3 getLimits
909
910     my $limit_rules = $abstract->getLimits( {
911         type  => 'brw_cat' | 'branch',
912         value => $value
913     } );
914
915 Return the ILL limit rules for the supplied combination of type / value.
916
917 As the config may have no rules for this particular type / value combination,
918 or for the default, we must define fall-back values here.
919
920 =cut
921
922 sub getLimits {
923     my ( $self, $params ) = @_;
924     my $limits = $self->_config->getLimitRules($params->{type});
925
926     if (     defined $params->{value}
927           && defined $limits->{$params->{value}} ) {
928             return $limits->{$params->{value}};
929     }
930     else {
931         return $limits->{default} || { count => -1, method => 'active' };
932     }
933 }
934
935 =head3 getPrefix
936
937     my $prefix = $abstract->getPrefix( {
938         branch  => $branch_code
939     } );
940
941 Return the ILL prefix as defined by our $params: either per borrower category,
942 per branch or the default.
943
944 =cut
945
946 sub getPrefix {
947     my ( $self, $params ) = @_;
948     my $brn_prefixes = $self->_config->getPrefixes();
949     return $brn_prefixes->{$params->{branch}} || ""; # "the empty prefix"
950 }
951
952 =head3 get_type
953
954     my $type = $abstract->get_type();
955
956 Return a string representing the material type of this request or undef
957
958 =cut
959
960 sub get_type {
961     my ($self) = @_;
962     my $attr = $self->illrequestattributes->find({ type => 'type'});
963     return if !$attr;
964     return $attr->value;
965 };
966
967 #### Illrequests Imports
968
969 =head3 check_limits
970
971     my $ok = $illRequests->check_limits( {
972         borrower   => $borrower,
973         branchcode => 'branchcode' | undef,
974     } );
975
976 Given $PARAMS, a hashref containing a $borrower object and a $branchcode,
977 see whether we are still able to place ILLs.
978
979 LimitRules are derived from koha-conf.xml:
980  + default limit counts, and counting method
981  + branch specific limit counts & counting method
982  + borrower category specific limit counts & counting method
983  + err on the side of caution: a counting fail will cause fail, even if
984    the other counts passes.
985
986 =cut
987
988 sub check_limits {
989     my ( $self, $params ) = @_;
990     my $patron     = $params->{patron};
991     my $branchcode = $params->{librarycode} || $patron->branchcode;
992
993     # Establish maximum number of allowed requests
994     my ( $branch_rules, $brw_rules ) = (
995         $self->getLimits( {
996             type => 'branch',
997             value => $branchcode
998         } ),
999         $self->getLimits( {
1000             type => 'brw_cat',
1001             value => $patron->categorycode,
1002         } ),
1003     );
1004     my ( $branch_limit, $brw_limit )
1005         = ( $branch_rules->{count}, $brw_rules->{count} );
1006     # Establish currently existing requests
1007     my ( $branch_count, $brw_count ) = (
1008         $self->_limit_counter(
1009             $branch_rules->{method}, { branchcode => $branchcode }
1010         ),
1011         $self->_limit_counter(
1012             $brw_rules->{method}, { borrowernumber => $patron->borrowernumber }
1013         ),
1014     );
1015
1016     # Compare and return
1017     # A limit of -1 means no limit exists.
1018     # We return blocked if either branch limit or brw limit is reached.
1019     if ( ( $branch_limit != -1 && $branch_limit <= $branch_count )
1020              || ( $brw_limit != -1 && $brw_limit <= $brw_count ) ) {
1021         return 0;
1022     } else {
1023         return 1;
1024     }
1025 }
1026
1027 sub _limit_counter {
1028     my ( $self, $method, $target ) = @_;
1029
1030     # Establish parameters of counts
1031     my $resultset;
1032     if ($method && $method eq 'annual') {
1033         $resultset = Koha::Illrequests->search({
1034             -and => [
1035                 %{$target},
1036                 \"YEAR(placed) = YEAR(NOW())"
1037             ]
1038         });
1039     } else {                    # assume 'active'
1040         # XXX: This status list is ugly. There should be a method in config
1041         # to return these.
1042         my $where = { status => { -not_in => [ 'QUEUED', 'COMP' ] } };
1043         $resultset = Koha::Illrequests->search({ %{$target}, %{$where} });
1044     }
1045
1046     # Fetch counts
1047     return $resultset->count;
1048 }
1049
1050 =head3 requires_moderation
1051
1052     my $status = $illRequest->requires_moderation;
1053
1054 Return the name of the status if moderation by staff is required; or 0
1055 otherwise.
1056
1057 =cut
1058
1059 sub requires_moderation {
1060     my ( $self ) = @_;
1061     my $require_moderation = {
1062         'CANCREQ' => 'CANCREQ',
1063     };
1064     return $require_moderation->{$self->status};
1065 }
1066
1067 =head3 biblio
1068
1069     my $biblio = $request->biblio;
1070
1071 For a given request, return the biblio associated with it,
1072 or undef if none exists
1073
1074 =cut
1075
1076 sub biblio {
1077     my ( $self ) = @_;
1078
1079     return if !$self->biblio_id;
1080
1081     return Koha::Biblios->find({
1082         biblionumber => $self->biblio_id
1083     });
1084 }
1085
1086 =head3 check_out
1087
1088     my $stage_summary = $request->check_out;
1089
1090 Handle the check_out method. The first stage involves gathering the required
1091 data from the user via a form, the second stage creates an item and tries to
1092 issue it to the patron. If successful, it notifies the patron, then it
1093 returns a summary of how things went
1094
1095 =cut
1096
1097 sub check_out {
1098     my ( $self, $params ) = @_;
1099
1100     # Objects required by the template
1101     my $itemtypes = Koha::ItemTypes->search(
1102         {},
1103         { order_by => ['description'] }
1104     );
1105     my $libraries = Koha::Libraries->search(
1106         {},
1107         { order_by => ['branchcode'] }
1108     );
1109     my $biblio = $self->biblio;
1110
1111     # Find all statistical patrons
1112     my $statistical_patrons = Koha::Patrons->search(
1113         { 'category_type' => 'x' },
1114         { join => { 'categorycode' => 'borrowers' } }
1115     );
1116
1117     if (!$params->{stage} || $params->{stage} eq 'init') {
1118         # Present a form to gather the required data
1119         #
1120         # We may be viewing this page having previously tried to issue
1121         # the item (in which case, we may already have created an item)
1122         # so we pass the biblio for this request
1123         return {
1124             method  => 'check_out',
1125             stage   => 'form',
1126             value   => {
1127                 itemtypes   => $itemtypes,
1128                 libraries   => $libraries,
1129                 statistical => $statistical_patrons,
1130                 biblio      => $biblio
1131             }
1132         };
1133     } elsif ($params->{stage} eq 'form') {
1134         # Validate what we've got and return with an error if we fail
1135         my $errors = {};
1136         if (!$params->{item_type} || length $params->{item_type} == 0) {
1137             $errors->{item_type} = 1;
1138         }
1139         if ($params->{inhouse} && length $params->{inhouse} > 0) {
1140             my $patron_count = Koha::Patrons->search({
1141                 cardnumber => $params->{inhouse}
1142             })->count();
1143             if ($patron_count != 1) {
1144                 $errors->{inhouse} = 1;
1145             }
1146         }
1147
1148         # Check we don't have more than one item for this bib,
1149         # if we do, something very odd is going on
1150         # Having 1 is OK, it means we're likely trying to issue
1151         # following a previously failed attempt, the item exists
1152         # so we'll use it
1153         my @items = $biblio->items->as_list;
1154         my $item_count = scalar @items;
1155         if ($item_count > 1) {
1156             $errors->{itemcount} = 1;
1157         }
1158
1159         # Failed validation, go back to the form
1160         if (%{$errors}) {
1161             return {
1162                 method  => 'check_out',
1163                 stage   => 'form',
1164                 value   => {
1165                     params      => $params,
1166                     statistical => $statistical_patrons,
1167                     itemtypes   => $itemtypes,
1168                     libraries   => $libraries,
1169                     biblio      => $biblio,
1170                     errors      => $errors
1171                 }
1172             };
1173         }
1174
1175         # Passed validation
1176         #
1177         # Create an item if one doesn't already exist,
1178         # if one does, use that
1179         my $itemnumber;
1180         if ($item_count == 0) {
1181             my $item_hash = {
1182                 biblionumber  => $self->biblio_id,
1183                 homebranch    => $params->{branchcode},
1184                 holdingbranch => $params->{branchcode},
1185                 location      => $params->{branchcode},
1186                 itype         => $params->{item_type},
1187                 barcode       => 'ILL-' . $self->illrequest_id
1188             };
1189             try {
1190                 my $item = Koha::Item->new($item_hash)->store;
1191                 $itemnumber = $item->itemnumber;
1192             };
1193         } else {
1194             $itemnumber = $items[0]->itemnumber;
1195         }
1196         # Check we have an item before going forward
1197         if (!$itemnumber) {
1198             return {
1199                 method  => 'check_out',
1200                 stage   => 'form',
1201                 value   => {
1202                     params      => $params,
1203                     itemtypes   => $itemtypes,
1204                     libraries   => $libraries,
1205                     statistical => $statistical_patrons,
1206                     errors      => { item_creation => 1 }
1207                 }
1208             };
1209         }
1210
1211         # Do the check out
1212         #
1213         # Gather what we need
1214         my $target_item = Koha::Items->find( $itemnumber );
1215         # Determine who we're issuing to
1216         my $patron = $params->{inhouse} && length $params->{inhouse} > 0 ?
1217             Koha::Patrons->find({ cardnumber => $params->{inhouse} }) :
1218             $self->patron;
1219
1220         my @issue_args = (
1221             $patron,
1222             scalar $target_item->barcode
1223         );
1224         if ($params->{duedate} && length $params->{duedate} > 0) {
1225             push @issue_args, $params->{duedate};
1226         }
1227         # Check if we can check out
1228         my ( $error, $confirm, $alerts, $messages ) =
1229             C4::Circulation::CanBookBeIssued(@issue_args);
1230
1231         # If we got anything back saying we can't check out,
1232         # return it to the template
1233         my $problems = {};
1234         if ( $error && %{$error} ) { $problems->{error} = $error };
1235         if ( $confirm && %{$confirm} ) { $problems->{confirm} = $confirm };
1236         if ( $alerts && %{$alerts} ) { $problems->{alerts} = $alerts };
1237         if ( $messages && %{$messages} ) { $problems->{messages} = $messages };
1238
1239         if (%{$problems}) {
1240             return {
1241                 method  => 'check_out',
1242                 stage   => 'form',
1243                 value   => {
1244                     params           => $params,
1245                     itemtypes        => $itemtypes,
1246                     libraries        => $libraries,
1247                     statistical      => $statistical_patrons,
1248                     patron           => $patron,
1249                     biblio           => $biblio,
1250                     check_out_errors => $problems
1251                 }
1252             };
1253         }
1254
1255         # We can allegedly check out, so make it so
1256         # For some reason, AddIssue requires an unblessed Patron
1257         $issue_args[0] = $patron->unblessed;
1258         my $issue = C4::Circulation::AddIssue(@issue_args);
1259
1260         if ($issue) {
1261             # Update the request status
1262             $self->status('CHK')->store;
1263             return {
1264                 method  => 'check_out',
1265                 stage   => 'done_check_out',
1266                 value   => {
1267                     params    => $params,
1268                     patron    => $patron,
1269                     check_out => $issue
1270                 }
1271             };
1272         } else {
1273             return {
1274                 method  => 'check_out',
1275                 stage   => 'form',
1276                 value   => {
1277                     params    => $params,
1278                     itemtypes => $itemtypes,
1279                     libraries => $libraries,
1280                     errors    => { item_check_out => 1 }
1281                 }
1282             };
1283         }
1284     }
1285
1286 }
1287
1288 =head3 generic_confirm
1289
1290     my $stage_summary = $illRequest->generic_confirm;
1291
1292 Handle the generic_confirm extended method.  The first stage involves creating
1293 a template email for the end user to edit in the browser.  The second stage
1294 attempts to submit the email.
1295
1296 =cut
1297
1298 sub generic_confirm {
1299     my ( $self, $params ) = @_;
1300     my $branch = Koha::Libraries->find($params->{current_branchcode})
1301         || die "Invalid current branchcode. Are you logged in as the database user?";
1302     if ( !$params->{stage}|| $params->{stage} eq 'init' ) {
1303         # Get the message body from the notice definition
1304         my $letter = $self->get_notice({
1305             notice_code => 'ILL_PARTNER_REQ',
1306             transport   => 'email'
1307         });
1308
1309         my $partners = Koha::Patrons->search({
1310             categorycode => $self->_config->partner_code
1311         });
1312         return {
1313             error   => 0,
1314             status  => '',
1315             message => '',
1316             method  => 'generic_confirm',
1317             stage   => 'draft',
1318             value   => {
1319                 draft => {
1320                     subject => $letter->{title},
1321                     body    => $letter->{content}
1322                 },
1323                 partners => $partners,
1324             }
1325         };
1326
1327     } elsif ( 'draft' eq $params->{stage} ) {
1328         # Create the to header
1329         my $to = $params->{partners};
1330         if ( defined $to ) {
1331             $to =~ s/^\x00//;       # Strip leading NULLs
1332             $to =~ s/\x00/; /;      # Replace others with '; '
1333         }
1334         Koha::Exceptions::Ill::NoTargetEmail->throw(
1335             "No target email addresses found. Either select at least one partner or check your ILL partner library records.")
1336           if ( !$to );
1337         # Create the from, replyto and sender headers
1338         my $from = $branch->from_email_address;
1339         my $replyto = $branch->inbound_ill_address;
1340         Koha::Exceptions::Ill::NoLibraryEmail->throw(
1341             "Your library has no usable email address. Please set it.")
1342           if ( !$from );
1343
1344         # So we get a notice hashref, then substitute the possibly
1345         # modified title and body from the draft stage
1346         my $letter = $self->get_notice({
1347             notice_code => 'ILL_PARTNER_REQ',
1348             transport   => 'email'
1349         });
1350         $letter->{title} = $params->{subject};
1351         $letter->{content} = $params->{body};
1352
1353         # Queue the notice
1354         my $params = {
1355             letter                 => $letter,
1356             borrowernumber         => $self->borrowernumber,
1357             message_transport_type => 'email',
1358             to_address             => $to,
1359             from_address           => $from,
1360             reply_address          => $replyto
1361         };
1362
1363         if ($letter) {
1364             my $result = C4::Letters::EnqueueLetter($params);
1365             if ( $result ) {
1366                 $self->status("GENREQ")->store;
1367                 $self->_backend_capability(
1368                     'set_requested_partners',
1369                     {
1370                         request => $self,
1371                         to => $to
1372                     }
1373                 );
1374                 return {
1375                     error   => 0,
1376                     status  => '',
1377                     message => '',
1378                     method  => 'generic_confirm',
1379                     stage   => 'commit',
1380                     next    => 'illview',
1381                 };
1382             }
1383         }
1384         return {
1385             error   => 1,
1386             status  => 'email_failed',
1387             message => 'Email queueing failed',
1388             method  => 'generic_confirm',
1389             stage   => 'draft',
1390         };
1391     } else {
1392         die "Unknown stage, should not have happened."
1393     }
1394 }
1395
1396 =head3 send_patron_notice
1397
1398     my $result = $request->send_patron_notice($notice_code);
1399
1400 Send a specified notice regarding this request to a patron
1401
1402 =cut
1403
1404 sub send_patron_notice {
1405     my ( $self, $notice_code ) = @_;
1406
1407     # We need a notice code
1408     if (!$notice_code) {
1409         return {
1410             error => 'notice_no_type'
1411         };
1412     }
1413
1414     # Map from the notice code to the messaging preference
1415     my %message_name = (
1416         ILL_PICKUP_READY   => 'Ill_ready',
1417         ILL_REQUEST_UNAVAIL => 'Ill_unavailable'
1418     );
1419
1420     # Get the patron's messaging preferences
1421     my $borrower_preferences = C4::Members::Messaging::GetMessagingPreferences({
1422         borrowernumber => $self->borrowernumber,
1423         message_name   => $message_name{$notice_code}
1424     });
1425     my @transports = keys %{ $borrower_preferences->{transports} };
1426
1427     # Notice should come from the library where the request was placed,
1428     # not the patrons home library
1429     my $branch = Koha::Libraries->find($self->branchcode);
1430     my $from_address = $branch->from_email_address;
1431     my $reply_address = $branch->inbound_ill_address;
1432
1433     # Send the notice to the patron via the chosen transport methods
1434     # and record the results
1435     my @success = ();
1436     my @fail = ();
1437     for my $transport (@transports) {
1438         my $letter = $self->get_notice({
1439             notice_code => $notice_code,
1440             transport   => $transport
1441         });
1442         if ($letter) {
1443             my $result = C4::Letters::EnqueueLetter({
1444                 letter                 => $letter,
1445                 borrowernumber         => $self->borrowernumber,
1446                 message_transport_type => $transport,
1447                 from_address           => $from_address,
1448                 reply_address          => $reply_address
1449             });
1450             if ($result) {
1451                 push @success, $transport;
1452             } else {
1453                 push @fail, $transport;
1454             }
1455         } else {
1456             push @fail, $transport;
1457         }
1458     }
1459     if (scalar @success > 0) {
1460         my $logger = Koha::Illrequest::Logger->new;
1461         $logger->log_patron_notice({
1462             request => $self,
1463             notice_code => $notice_code
1464         });
1465     }
1466     return {
1467         result => {
1468             success => \@success,
1469             fail    => \@fail
1470         }
1471     };
1472 }
1473
1474 =head3 send_staff_notice
1475
1476     my $result = $request->send_staff_notice($notice_code);
1477
1478 Send a specified notice regarding this request to staff
1479
1480 =cut
1481
1482 sub send_staff_notice {
1483     my ( $self, $notice_code ) = @_;
1484
1485     # We need a notice code
1486     if (!$notice_code) {
1487         return {
1488             error => 'notice_no_type'
1489         };
1490     }
1491
1492     # Get the staff notices that have been assigned for sending in
1493     # the syspref
1494     my $staff_to_send = C4::Context->preference('ILLSendStaffNotices') // q{};
1495
1496     # If it hasn't been enabled in the syspref, we don't want to send it
1497     if ($staff_to_send !~ /\b$notice_code\b/) {
1498         return {
1499             error => 'notice_not_enabled'
1500         };
1501     }
1502
1503     my $letter = $self->get_notice({
1504         notice_code => $notice_code,
1505         transport   => 'email'
1506     });
1507
1508     # Try and get an address to which to send staff notices
1509     my $branch = Koha::Libraries->find($self->branchcode);
1510     my $to_address = $branch->inbound_ill_address;
1511     my $from_address = $branch->inbound_ill_address;
1512
1513     my $params = {
1514         letter                 => $letter,
1515         borrowernumber         => $self->borrowernumber,
1516         message_transport_type => 'email',
1517         from_address           => $from_address
1518     };
1519
1520     if ($to_address) {
1521         $params->{to_address} = $to_address;
1522     } else {
1523         return {
1524             error => 'notice_no_create'
1525         };
1526     }
1527
1528     if ($letter) {
1529         C4::Letters::EnqueueLetter($params)
1530             or warn "can't enqueue letter $letter";
1531         return {
1532             success => 'notice_queued'
1533         };
1534     } else {
1535         return {
1536             error => 'notice_no_create'
1537         };
1538     }
1539 }
1540
1541 =head3 get_notice
1542
1543     my $notice = $request->get_notice($params);
1544
1545 Return a compiled notice hashref for the passed notice code
1546 and transport type
1547
1548 =cut
1549
1550 sub get_notice {
1551     my ( $self, $params ) = @_;
1552
1553     my $title = $self->illrequestattributes->find(
1554         { type => 'title' }
1555     );
1556     my $author = $self->illrequestattributes->find(
1557         { type => 'author' }
1558     );
1559     my $metahash = $self->metadata;
1560     my @metaarray = ();
1561     foreach my $key (sort { lc $a cmp lc $b } keys %{$metahash}) {
1562         my $value = $metahash->{$key};
1563         push @metaarray, "- $key: $value" if $value;
1564     }
1565     my $metastring = join("\n", @metaarray);
1566     my $letter = C4::Letters::GetPreparedLetter(
1567         module                 => 'ill',
1568         letter_code            => $params->{notice_code},
1569         branchcode             => $self->branchcode,
1570         message_transport_type => $params->{transport},
1571         lang                   => $self->patron->lang,
1572         tables                 => {
1573             illrequests => $self->illrequest_id,
1574             borrowers   => $self->borrowernumber,
1575             biblio      => $self->biblio_id,
1576             branches    => $self->branchcode,
1577         },
1578         substitute  => {
1579             ill_bib_title      => $title ? $title->value : '',
1580             ill_bib_author     => $author ? $author->value : '',
1581             ill_full_metadata  => $metastring
1582         }
1583     );
1584
1585     return $letter;
1586 }
1587
1588 =head3 id_prefix
1589
1590     my $prefix = $record->id_prefix;
1591
1592 Return the prefix appropriate for the current Illrequest as derived from the
1593 borrower and branch associated with this request's Status, and the config
1594 file.
1595
1596 =cut
1597
1598 sub id_prefix {
1599     my ( $self ) = @_;
1600     my $prefix = $self->getPrefix( {
1601         branch  => $self->branchcode,
1602     } );
1603     $prefix .= "-" if ( $prefix );
1604     return $prefix;
1605 }
1606
1607 =head3 _censor
1608
1609     my $params = $illRequest->_censor($params);
1610
1611 Return $params, modified to reflect our censorship requirements.
1612
1613 =cut
1614
1615 sub _censor {
1616     my ( $self, $params ) = @_;
1617     my $censorship = $self->_config->censorship;
1618     $params->{censor_notes_staff} = $censorship->{censor_notes_staff}
1619         if ( $params->{opac} );
1620     $params->{display_reply_date} = ( $censorship->{censor_reply_date} ) ? 0 : 1;
1621
1622     return $params;
1623 }
1624
1625 =head3 store
1626
1627     $Illrequest->store;
1628
1629 Overloaded I<store> method that, in addition to performing the 'store',
1630 possibly records the fact that something happened
1631
1632 =cut
1633
1634 sub store {
1635     my ( $self, $attrs ) = @_;
1636
1637     my $ret = $self->SUPER::store;
1638
1639     $attrs->{log_origin} = 'core';
1640
1641     if ($ret && defined $attrs) {
1642         my $logger = Koha::Illrequest::Logger->new;
1643         $logger->log_maybe({
1644             request => $self,
1645             attrs   => $attrs
1646         });
1647     }
1648
1649     return $ret;
1650 }
1651
1652 =head3 requested_partners
1653
1654     my $partners_string = $illRequest->requested_partners;
1655
1656 Return the string representing the email addresses of the partners to
1657 whom a request has been sent
1658
1659 =cut
1660
1661 sub requested_partners {
1662     my ( $self ) = @_;
1663     return $self->_backend_capability(
1664         'get_requested_partners',
1665         { request => $self }
1666     );
1667 }
1668
1669 =head3 TO_JSON
1670
1671     $json = $illrequest->TO_JSON
1672
1673 Overloaded I<TO_JSON> method that takes care of inserting calculated values
1674 into the unblessed representation of the object.
1675
1676 TODO: This method does nothing and is not called anywhere. However, bug 74325
1677 touches it, so keeping this for now until both this and bug 74325 are merged,
1678 at which point we can sort it out and remove it completely
1679
1680 =cut
1681
1682 sub TO_JSON {
1683     my ( $self, $embed ) = @_;
1684
1685     my $object = $self->SUPER::TO_JSON();
1686
1687     return $object;
1688 }
1689
1690 =head2 Internal methods
1691
1692 =head3 _type
1693
1694 =cut
1695
1696 sub _type {
1697     return 'Illrequest';
1698 }
1699
1700 =head1 AUTHOR
1701
1702 Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com>
1703 Andrew Isherwood <andrew.isherwood@ptfs-europe.com>
1704
1705 =cut
1706
1707 1;