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