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