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