Bug 28489: Modify sessions.a_session from longtext to longblob
[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 'clone';
23 use File::Basename qw( basename );
24 use Encode qw( encode );
25 use Try::Tiny;
26 use DateTime;
27
28 use C4::Letters;
29 use C4::Members;
30 use Koha::Database;
31 use Koha::DateUtils qw/ dt_from_string /;
32 use Koha::Exceptions::Ill;
33 use Koha::Illcomments;
34 use Koha::Illrequestattributes;
35 use Koha::AuthorisedValue;
36 use Koha::Illrequest::Logger;
37 use Koha::Patron;
38 use Koha::AuthorisedValues;
39 use Koha::Biblios;
40 use Koha::Items;
41 use Koha::ItemTypes;
42 use Koha::Libraries;
43
44 use C4::Circulation qw( CanBookBeIssued AddIssue  );
45
46 use base qw(Koha::Object);
47
48 =head1 NAME
49
50 Koha::Illrequest - Koha Illrequest Object class
51
52 =head1 (Re)Design
53
54 An ILLRequest consists of two parts; the Illrequest Koha::Object, and a series
55 of related Illrequestattributes.
56
57 The former encapsulates the basic necessary information that any ILL requires
58 to be usable in Koha.  The latter is a set of additional properties used by
59 one of the backends.
60
61 The former subsumes the legacy "Status" object.  The latter remains
62 encapsulated in the "Record" object.
63
64 TODO:
65
66 - Anything invoking the ->status method; annotated with:
67   + # Old use of ->status !
68
69 =head1 API
70
71 =head2 Backend API Response Principles
72
73 All methods should return a hashref in the following format:
74
75 =over
76
77 =item * error
78
79 This should be set to 1 if an error was encountered.
80
81 =item * status
82
83 The status should be a string from the list of statuses detailed below.
84
85 =item * message
86
87 The message is a free text field that can be passed on to the end user.
88
89 =item * value
90
91 The value returned by the method.
92
93 =back
94
95 =head2 Interface Status Messages
96
97 =over
98
99 =item * branch_address_incomplete
100
101 An interface request has determined branch address details are incomplete.
102
103 =item * cancel_success
104
105 The interface's cancel_request method was successful in cancelling the
106 Illrequest using the API.
107
108 =item * cancel_fail
109
110 The interface's cancel_request method failed to cancel the Illrequest using
111 the API.
112
113 =item * unavailable
114
115 The interface's request method returned saying that the desired item is not
116 available for request.
117
118 =back
119
120 =head2 Class methods
121
122 =head3 statusalias
123
124     my $statusalias = $request->statusalias;
125
126 Returns a request's status alias, as a Koha::AuthorisedValue instance
127 or implicit undef. This is distinct from status_alias, which only returns
128 the value in the status_alias column, this method returns the entire
129 AuthorisedValue object
130
131 =cut
132
133 sub statusalias {
134     my ( $self ) = @_;
135     return unless $self->status_alias;
136     # We can't know which result is the right one if there are multiple
137     # ILLSTATUS authorised values with the same authorised_value column value
138     # so we just use the first
139     return Koha::AuthorisedValues->search(
140         {
141             category         => 'ILLSTATUS',
142             authorised_value => $self->SUPER::status_alias
143         },
144         {},
145         $self->branchcode
146     )->next;
147 }
148
149 =head3 illrequestattributes
150
151 =cut
152
153 sub illrequestattributes {
154     my ( $self ) = @_;
155     return Koha::Illrequestattributes->_new_from_dbic(
156         scalar $self->_result->illrequestattributes
157     );
158 }
159
160 =head3 illcomments
161
162 =cut
163
164 sub illcomments {
165     my ( $self ) = @_;
166     return Koha::Illcomments->_new_from_dbic(
167         scalar $self->_result->illcomments
168     );
169 }
170
171 =head3 logs
172
173 =cut
174
175 sub logs {
176     my ( $self ) = @_;
177     my $logger = Koha::Illrequest::Logger->new;
178     return $logger->get_request_logs($self);
179 }
180
181 =head3 patron
182
183 =cut
184
185 sub patron {
186     my ( $self ) = @_;
187     return Koha::Patron->_new_from_dbic(
188         scalar $self->_result->borrowernumber
189     );
190 }
191
192 =head3 status_alias
193
194     $Illrequest->status_alias(143);
195
196 Overloaded getter/setter for status_alias,
197 that only returns authorised values from the
198 correct category and records the fact that the status has changed
199
200 =cut
201
202 sub status_alias {
203     my ($self, $new_status_alias) = @_;
204
205     my $current_status_alias = $self->SUPER::status_alias;
206
207     if ($new_status_alias) {
208         # Keep a record of the previous status before we change it,
209         # we might need it
210         $self->{previous_status} = $current_status_alias ?
211             $current_status_alias :
212             scalar $self->status;
213         # This is hackery to enable us to undefine
214         # status_alias, since we need to have an overloaded
215         # status_alias method to get us around the problem described
216         # here:
217         # https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=20581#c156
218         # We need a way of accepting implied undef, so we can nullify
219         # the status_alias column, when called from $self->status
220         my $val = $new_status_alias eq "-1" ? undef : $new_status_alias;
221         my $ret = $self->SUPER::status_alias($val);
222         my $val_to_log = $val ? $new_status_alias : scalar $self->status;
223         if ($ret) {
224             my $logger = Koha::Illrequest::Logger->new;
225             $logger->log_status_change({
226                 request => $self,
227                 value   => $val_to_log
228             });
229         } else {
230             delete $self->{previous_status};
231         }
232         return $ret;
233     }
234     # We can't know which result is the right one if there are multiple
235     # ILLSTATUS authorised values with the same authorised_value column value
236     # so we just use the first
237     my $alias = Koha::AuthorisedValues->search(
238         {
239             category         => 'ILLSTATUS',
240             authorised_value => $self->SUPER::status_alias
241         },
242         {},
243         $self->branchcode
244     )->next;
245
246     if ($alias) {
247         return $alias->authorised_value;
248     } else {
249         return;
250     }
251 }
252
253 =head3 status
254
255     $Illrequest->status('CANREQ');
256
257 Overloaded getter/setter for request status,
258 also nullifies status_alias and records the fact that the status has changed
259 and sends a notice if appropriate
260
261 =cut
262
263 sub status {
264     my ( $self, $new_status) = @_;
265
266     my $current_status = $self->SUPER::status;
267     my $current_status_alias = $self->SUPER::status_alias;
268
269     if ($new_status) {
270         # Keep a record of the previous status before we change it,
271         # we might need it
272         $self->{previous_status} = $current_status_alias ?
273             $current_status_alias :
274             $current_status;
275         my $ret = $self->SUPER::status($new_status)->store;
276         if ($current_status_alias) {
277             # This is hackery to enable us to undefine
278             # status_alias, since we need to have an overloaded
279             # status_alias method to get us around the problem described
280             # here:
281             # https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=20581#c156
282             # We need a way of passing implied undef to nullify status_alias
283             # so we pass -1, which is special cased in the overloaded setter
284             $self->status_alias("-1");
285         } else {
286             my $logger = Koha::Illrequest::Logger->new;
287             $logger->log_status_change({
288                 request => $self,
289                 value   => $new_status
290             });
291         }
292         delete $self->{previous_status};
293         # If status has changed to cancellation requested, send a notice
294         if ($new_status eq 'CANCREQ') {
295             $self->send_staff_notice('ILL_REQUEST_CANCEL');
296         }
297         return $ret;
298     } else {
299         return $current_status;
300     }
301 }
302
303 =head3 load_backend
304
305 Require "Base.pm" from the relevant ILL backend.
306
307 =cut
308
309 sub load_backend {
310     my ( $self, $backend_id ) = @_;
311
312     my @raw = qw/Koha Illbackends/; # Base Path
313
314     my $backend_name = $backend_id || $self->backend;
315
316     unless ( defined $backend_name && $backend_name ne '' ) {
317         Koha::Exceptions::Ill::InvalidBackendId->throw(
318             "An invalid backend ID was requested ('')");
319     }
320
321     my $location = join "/", @raw, $backend_name, "Base.pm";    # File to load
322     my $backend_class = join "::", @raw, $backend_name, "Base"; # Package name
323     require $location;
324     $self->{_my_backend} = $backend_class->new({
325         config => $self->_config,
326         logger => Koha::Illrequest::Logger->new
327     });
328     return $self;
329 }
330
331
332 =head3 _backend
333
334     my $backend = $abstract->_backend($new_backend);
335     my $backend = $abstract->_backend;
336
337 Getter/Setter for our API object.
338
339 =cut
340
341 sub _backend {
342     my ( $self, $backend ) = @_;
343     $self->{_my_backend} = $backend if ( $backend );
344     # Dynamically load our backend object, as late as possible.
345     $self->load_backend unless ( $self->{_my_backend} );
346     return $self->{_my_backend};
347 }
348
349 =head3 _backend_capability
350
351     my $backend_capability_result = $self->_backend_capability($name, $args);
352
353 This is a helper method to invoke optional capabilities in the backend.  If
354 the capability named by $name is not supported, return 0, else invoke it,
355 passing $args along with the invocation, and return its return value.
356
357 NOTE: this module suffers from a confusion in termninology:
358
359 in _backend_capability, the notion of capability refers to an optional feature
360 that is implemented in core, but might not be supported by a given backend.
361
362 in capabilities & custom_capability, capability refers to entries in the
363 status_graph (after union between backend and core).
364
365 The easiest way to fix this would be to fix the terminology in
366 capabilities & custom_capability and their callers.
367
368 =cut
369
370 sub _backend_capability {
371     my ( $self, $name, $args ) = @_;
372     my $capability = 0;
373     # See if capability is defined in backend
374     try {
375         $capability = $self->_backend->capabilities($name);
376     } catch {
377         return 0;
378     };
379     # Try to invoke it
380     if ( $capability && ref($capability) eq 'CODE' ) {
381         return &{$capability}($args);
382     } else {
383         return 0;
384     }
385 }
386
387 =head3 _config
388
389     my $config = $abstract->_config($config);
390     my $config = $abstract->_config;
391
392 Getter/Setter for our config object.
393
394 =cut
395
396 sub _config {
397     my ( $self, $config ) = @_;
398     $self->{_my_config} = $config if ( $config );
399     # Load our config object, as late as possible.
400     unless ( $self->{_my_config} ) {
401         $self->{_my_config} = Koha::Illrequest::Config->new;
402     }
403     return $self->{_my_config};
404 }
405
406 =head3 metadata
407
408 =cut
409
410 sub metadata {
411     my ( $self ) = @_;
412     return $self->_backend->metadata($self);
413 }
414
415 =head3 _core_status_graph
416
417     my $core_status_graph = $illrequest->_core_status_graph;
418
419 Returns ILL module's default status graph.  A status graph defines the list of
420 available actions at any stage in the ILL workflow.  This is for instance used
421 by the perl script & template to generate the correct buttons to display to
422 the end user at any given point.
423
424 =cut
425
426 sub _core_status_graph {
427     my ( $self ) = @_;
428     return {
429         NEW => {
430             prev_actions => [ ],                           # Actions containing buttons
431                                                            # leading to this status
432             id             => 'NEW',                       # ID of this status
433             name           => 'New request',               # UI name of this status
434             ui_method_name => 'New request',               # UI name of method leading
435                                                            # to this status
436             method         => 'create',                    # method to this status
437             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ], # buttons to add to all
438                                                            # requests with this status
439             ui_method_icon => 'fa-plus',                   # UI Style class
440         },
441         REQ => {
442             prev_actions   => [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ],
443             id             => 'REQ',
444             name           => 'Requested',
445             ui_method_name => 'Confirm request',
446             method         => 'confirm',
447             next_actions   => [ 'REQREV', 'COMP', 'CHK' ],
448             ui_method_icon => 'fa-check',
449         },
450         GENREQ => {
451             prev_actions   => [ 'NEW', 'REQREV' ],
452             id             => 'GENREQ',
453             name           => 'Requested from partners',
454             ui_method_name => 'Place request with partners',
455             method         => 'generic_confirm',
456             next_actions   => [ 'COMP', 'CHK' ],
457             ui_method_icon => 'fa-send-o',
458         },
459         REQREV => {
460             prev_actions   => [ 'REQ' ],
461             id             => 'REQREV',
462             name           => 'Request reverted',
463             ui_method_name => 'Revert Request',
464             method         => 'cancel',
465             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ],
466             ui_method_icon => 'fa-times',
467         },
468         QUEUED => {
469             prev_actions   => [ ],
470             id             => 'QUEUED',
471             name           => 'Queued request',
472             ui_method_name => 0,
473             method         => 0,
474             next_actions   => [ 'REQ', 'KILL' ],
475             ui_method_icon => 0,
476         },
477         CANCREQ => {
478             prev_actions   => [ 'NEW' ],
479             id             => 'CANCREQ',
480             name           => 'Cancellation requested',
481             ui_method_name => 0,
482             method         => 0,
483             next_actions   => [ 'KILL', 'REQ' ],
484             ui_method_icon => 0,
485         },
486         COMP => {
487             prev_actions   => [ 'REQ' ],
488             id             => 'COMP',
489             name           => 'Completed',
490             ui_method_name => 'Mark completed',
491             method         => 'mark_completed',
492             next_actions   => [ 'CHK' ],
493             ui_method_icon => 'fa-check',
494         },
495         KILL => {
496             prev_actions   => [ 'QUEUED', 'REQREV', 'NEW', 'CANCREQ' ],
497             id             => 'KILL',
498             name           => 0,
499             ui_method_name => 'Delete request',
500             method         => 'delete',
501             next_actions   => [ ],
502             ui_method_icon => 'fa-trash',
503         },
504         CHK => {
505             prev_actions   => [ 'REQ', 'GENREQ', 'COMP' ],
506             id             => 'CHK',
507             name           => 'Checked out',
508             ui_method_name => 'Check out',
509             needs_prefs    => [ 'CirculateILL' ],
510             needs_perms    => [ 'user_circulate_circulate_remaining_permissions' ],
511             # An array of functions that all must return true
512             needs_all      => [ sub { my $r = shift;  return $r->biblio; } ],
513             method         => 'check_out',
514             next_actions   => [ ],
515             ui_method_icon => 'fa-upload',
516         },
517         RET => {
518             prev_actions   => [ 'CHK' ],
519             id             => 'RET',
520             name           => 'Returned to library',
521             ui_method_name => 'Check in',
522             method         => 'check_in',
523             next_actions   => [ 'COMP' ],
524             ui_method_icon => 'fa-download',
525         }
526     };
527 }
528
529 =head3 _status_graph_union
530
531     my $status_graph = $illrequest->_status_graph_union($origin, $new_graph);
532
533 Return a new status_graph, the result of merging $origin & new_graph.  This is
534 operation is a union over the sets defied by the two graphs.
535
536 Each entry in $new_graph is added to $origin.  We do not provide a syntax for
537 'subtraction' of entries from $origin.
538
539 Whilst it is not intended that this works, you can override entries in $origin
540 with entries with the same key in $new_graph.  This can lead to problematic
541 behaviour when $new_graph adds an entry, which modifies a dependent entry in
542 $origin, only for the entry in $origin to be replaced later with a new entry
543 from $new_graph.
544
545 NOTE: this procedure does not "re-link" entries in $origin or $new_graph,
546 i.e. each of the graphs need to be correct at the outset of the operation.
547
548 =cut
549
550 sub _status_graph_union {
551     my ( $self, $core_status_graph, $backend_status_graph ) = @_;
552     # Create new status graph with:
553     # - all core_status_graph
554     # - for-each each backend_status_graph
555     #   + add to new status graph
556     #   + for each core prev_action:
557     #     * locate core_status
558     #     * update next_actions with additional next action.
559     #   + for each core next_action:
560     #     * locate core_status
561     #     * update prev_actions with additional prev action
562
563     my @core_status_ids = keys %{$core_status_graph};
564     my $status_graph = clone($core_status_graph);
565
566     foreach my $backend_status_key ( keys %{$backend_status_graph} ) {
567         my $backend_status = $backend_status_graph->{$backend_status_key};
568         # Add to new status graph
569         $status_graph->{$backend_status_key} = $backend_status;
570         # Update all core methods' next_actions.
571         foreach my $prev_action ( @{$backend_status->{prev_actions}} ) {
572             if ( grep { $prev_action eq $_ } @core_status_ids ) {
573                 my @next_actions =
574                      @{$status_graph->{$prev_action}->{next_actions}};
575                 push @next_actions, $backend_status_key;
576                 $status_graph->{$prev_action}->{next_actions}
577                     = \@next_actions;
578             }
579         }
580         # Update all core methods' prev_actions
581         foreach my $next_action ( @{$backend_status->{next_actions}} ) {
582             if ( grep { $next_action eq $_ } @core_status_ids ) {
583                 my @prev_actions =
584                      @{$status_graph->{$next_action}->{prev_actions}};
585                 push @prev_actions, $backend_status_key;
586                 $status_graph->{$next_action}->{prev_actions}
587                     = \@prev_actions;
588             }
589         }
590     }
591
592     return $status_graph;
593 }
594
595 ### Core API methods
596
597 =head3 capabilities
598
599     my $capabilities = $illrequest->capabilities;
600
601 Return a hashref mapping methods to operation names supported by the queried
602 backend.
603
604 Example return value:
605
606     { create => "Create Request", confirm => "Progress Request" }
607
608 NOTE: this module suffers from a confusion in termninology:
609
610 in _backend_capability, the notion of capability refers to an optional feature
611 that is implemented in core, but might not be supported by a given backend.
612
613 in capabilities & custom_capability, capability refers to entries in the
614 status_graph (after union between backend and core).
615
616 The easiest way to fix this would be to fix the terminology in
617 capabilities & custom_capability and their callers.
618
619 =cut
620
621 sub capabilities {
622     my ( $self, $status ) = @_;
623     # Generate up to date status_graph
624     my $status_graph = $self->_status_graph_union(
625         $self->_core_status_graph,
626         $self->_backend->status_graph({
627             request => $self,
628             other   => {}
629         })
630     );
631     # Extract available actions from graph.
632     return $status_graph->{$status} if $status;
633     # Or return entire graph.
634     return $status_graph;
635 }
636
637 =head3 custom_capability
638
639 Return the result of invoking $CANDIDATE on this request's backend with
640 $PARAMS, or 0 if $CANDIDATE is an unknown method on backend.
641
642 NOTE: this module suffers from a confusion in termninology:
643
644 in _backend_capability, the notion of capability refers to an optional feature
645 that is implemented in core, but might not be supported by a given backend.
646
647 in capabilities & custom_capability, capability refers to entries in the
648 status_graph (after union between backend and core).
649
650 The easiest way to fix this would be to fix the terminology in
651 capabilities & custom_capability and their callers.
652
653 =cut
654
655 sub custom_capability {
656     my ( $self, $candidate, $params ) = @_;
657     foreach my $capability ( values %{$self->capabilities} ) {
658         if ( $candidate eq $capability->{method} ) {
659             my $response =
660                 $self->_backend->$candidate({
661                     request    => $self,
662                     other      => $params,
663                 });
664             return $self->expandTemplate($response);
665         }
666     }
667     return 0;
668 }
669
670 =head3 available_backends
671
672 Return a list of available backends.
673
674 =cut
675
676 sub available_backends {
677     my ( $self, $reduced ) = @_;
678     my $backends = $self->_config->available_backends($reduced);
679     return $backends;
680 }
681
682 =head3 available_actions
683
684 Return a list of available actions.
685
686 =cut
687
688 sub available_actions {
689     my ( $self ) = @_;
690     my $current_action = $self->capabilities($self->status);
691     my @available_actions = map { $self->capabilities($_) }
692         @{$current_action->{next_actions}};
693     return \@available_actions;
694 }
695
696 =head3 mark_completed
697
698 Mark a request as completed (status = COMP).
699
700 =cut
701
702 sub mark_completed {
703     my ( $self ) = @_;
704     $self->status('COMP')->store;
705     $self->completed(dt_from_string())->store;
706     return {
707         error   => 0,
708         status  => '',
709         message => '',
710         method  => 'mark_completed',
711         stage   => 'commit',
712         next    => 'illview',
713     };
714 }
715
716 =head2 backend_migrate
717
718 Migrate a request from one backend to another.
719
720 =cut
721
722 sub backend_migrate {
723     my ( $self, $params ) = @_;
724
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->branchemail;
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->branchemail;
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     while (my($key, $value) = each %{$metahash}) {
1562         push @metaarray, "- $key: $value" if $value;
1563     }
1564     my $metastring = join("\n", @metaarray);
1565     my $letter = C4::Letters::GetPreparedLetter(
1566         module                 => 'ill',
1567         letter_code            => $params->{notice_code},
1568         branchcode             => $self->branchcode,
1569         message_transport_type => $params->{transport},
1570         lang                   => $self->patron->lang,
1571         tables                 => {
1572             illrequests => $self->illrequest_id,
1573             borrowers   => $self->borrowernumber,
1574             biblio      => $self->biblio_id,
1575             branches    => $self->branchcode,
1576         },
1577         substitute  => {
1578             ill_bib_title      => $title ? $title->value : '',
1579             ill_bib_author     => $author ? $author->value : '',
1580             ill_full_metadata  => $metastring
1581         }
1582     );
1583
1584     return $letter;
1585 }
1586
1587 =head3 id_prefix
1588
1589     my $prefix = $record->id_prefix;
1590
1591 Return the prefix appropriate for the current Illrequest as derived from the
1592 borrower and branch associated with this request's Status, and the config
1593 file.
1594
1595 =cut
1596
1597 sub id_prefix {
1598     my ( $self ) = @_;
1599     my $prefix = $self->getPrefix( {
1600         branch  => $self->branchcode,
1601     } );
1602     $prefix .= "-" if ( $prefix );
1603     return $prefix;
1604 }
1605
1606 =head3 _censor
1607
1608     my $params = $illRequest->_censor($params);
1609
1610 Return $params, modified to reflect our censorship requirements.
1611
1612 =cut
1613
1614 sub _censor {
1615     my ( $self, $params ) = @_;
1616     my $censorship = $self->_config->censorship;
1617     $params->{censor_notes_staff} = $censorship->{censor_notes_staff}
1618         if ( $params->{opac} );
1619     $params->{display_reply_date} = ( $censorship->{censor_reply_date} ) ? 0 : 1;
1620
1621     return $params;
1622 }
1623
1624 =head3 store
1625
1626     $Illrequest->store;
1627
1628 Overloaded I<store> method that, in addition to performing the 'store',
1629 possibly records the fact that something happened
1630
1631 =cut
1632
1633 sub store {
1634     my ( $self, $attrs ) = @_;
1635
1636     my $ret = $self->SUPER::store;
1637
1638     $attrs->{log_origin} = 'core';
1639
1640     if ($ret && defined $attrs) {
1641         my $logger = Koha::Illrequest::Logger->new;
1642         $logger->log_maybe({
1643             request => $self,
1644             attrs   => $attrs
1645         });
1646     }
1647
1648     return $ret;
1649 }
1650
1651 =head3 requested_partners
1652
1653     my $partners_string = $illRequest->requested_partners;
1654
1655 Return the string representing the email addresses of the partners to
1656 whom a request has been sent
1657
1658 =cut
1659
1660 sub requested_partners {
1661     my ( $self ) = @_;
1662     return $self->_backend_capability(
1663         'get_requested_partners',
1664         { request => $self }
1665     );
1666 }
1667
1668 =head3 TO_JSON
1669
1670     $json = $illrequest->TO_JSON
1671
1672 Overloaded I<TO_JSON> method that takes care of inserting calculated values
1673 into the unblessed representation of the object.
1674
1675 TODO: This method does nothing and is not called anywhere. However, bug 74325
1676 touches it, so keeping this for now until both this and bug 74325 are merged,
1677 at which point we can sort it out and remove it completely
1678
1679 =cut
1680
1681 sub TO_JSON {
1682     my ( $self, $embed ) = @_;
1683
1684     my $object = $self->SUPER::TO_JSON();
1685
1686     return $object;
1687 }
1688
1689 =head2 Internal methods
1690
1691 =head3 _type
1692
1693 =cut
1694
1695 sub _type {
1696     return 'Illrequest';
1697 }
1698
1699 =head1 AUTHOR
1700
1701 Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com>
1702 Andrew Isherwood <andrew.isherwood@ptfs-europe.com>
1703
1704 =cut
1705
1706 1;