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