Bug 20581: Provide status_alias on IllRequest
[koha.git] / Koha / Illrequest.pm
1 package Koha::Illrequest;
2
3 # Copyright PTFS Europe 2016
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 3 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
15 # details.
16 #
17 # You should have received a copy of the GNU General Public License along with
18 # Koha; if not, write to the Free Software Foundation, Inc., 51 Franklin
19 # Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21 use Modern::Perl;
22
23 use Clone 'clone';
24 use File::Basename qw( basename );
25 use Encode qw( encode );
26 use Mail::Sendmail;
27 use Try::Tiny;
28
29 use Koha::Database;
30 use Koha::Email;
31 use Koha::Exceptions::Ill;
32 use Koha::Illcomments;
33 use Koha::Illrequestattributes;
34 use Koha::AuthorisedValue;
35 use Koha::Patron;
36
37 use base qw(Koha::Object);
38
39 =head1 NAME
40
41 Koha::Illrequest - Koha Illrequest Object class
42
43 =head1 (Re)Design
44
45 An ILLRequest consists of two parts; the Illrequest Koha::Object, and a series
46 of related Illrequestattributes.
47
48 The former encapsulates the basic necessary information that any ILL requires
49 to be usable in Koha.  The latter is a set of additional properties used by
50 one of the backends.
51
52 The former subsumes the legacy "Status" object.  The latter remains
53 encapsulated in the "Record" object.
54
55 TODO:
56
57 - Anything invoking the ->status method; annotated with:
58   + # Old use of ->status !
59
60 =head1 API
61
62 =head2 Backend API Response Principles
63
64 All methods should return a hashref in the following format:
65
66 =over
67
68 =item * error
69
70 This should be set to 1 if an error was encountered.
71
72 =item * status
73
74 The status should be a string from the list of statuses detailed below.
75
76 =item * message
77
78 The message is a free text field that can be passed on to the end user.
79
80 =item * value
81
82 The value returned by the method.
83
84 =back
85
86 =head2 Interface Status Messages
87
88 =over
89
90 =item * branch_address_incomplete
91
92 An interface request has determined branch address details are incomplete.
93
94 =item * cancel_success
95
96 The interface's cancel_request method was successful in cancelling the
97 Illrequest using the API.
98
99 =item * cancel_fail
100
101 The interface's cancel_request method failed to cancel the Illrequest using
102 the API.
103
104 =item * unavailable
105
106 The interface's request method returned saying that the desired item is not
107 available for request.
108
109 =back
110
111 =head2 Class methods
112
113 =head3 statusalias
114
115 =cut
116
117 sub statusalias {
118     my ( $self ) = @_;
119     return $self->status_alias ?
120         Koha::AuthorisedValue->_new_from_dbic(
121             scalar $self->_result->status_alias
122         ) :
123         undef;
124 }
125
126 =head3 illrequestattributes
127
128 =cut
129
130 sub illrequestattributes {
131     my ( $self ) = @_;
132     return Koha::Illrequestattributes->_new_from_dbic(
133         scalar $self->_result->illrequestattributes
134     );
135 }
136
137 =head3 illcomments
138
139 =cut
140
141 sub illcomments {
142     my ( $self ) = @_;
143     return Koha::Illcomments->_new_from_dbic(
144         scalar $self->_result->illcomments
145     );
146 }
147
148 =head3 patron
149
150 =cut
151
152 sub patron {
153     my ( $self ) = @_;
154     return Koha::Patron->_new_from_dbic(
155         scalar $self->_result->borrowernumber
156     );
157 }
158
159 =head3 status
160
161 Overloaded getter/setter for request status,
162 also nullifies status_alias
163
164 =cut
165
166 sub status {
167     my ( $self, $newval) = @_;
168     if ($newval) {
169         $self->status_alias(undef);
170         return $self->SUPER::status($newval);
171     }
172     return $self->SUPER::status;
173 }
174
175 =head3 load_backend
176
177 Require "Base.pm" from the relevant ILL backend.
178
179 =cut
180
181 sub load_backend {
182     my ( $self, $backend_id ) = @_;
183
184     my @raw = qw/Koha Illbackends/; # Base Path
185
186     my $backend_name = $backend_id || $self->backend;
187
188     unless ( defined $backend_name && $backend_name ne '' ) {
189         Koha::Exceptions::Ill::InvalidBackendId->throw(
190             "An invalid backend ID was requested ('')");
191     }
192
193     my $location = join "/", @raw, $backend_name, "Base.pm";    # File to load
194     my $backend_class = join "::", @raw, $backend_name, "Base"; # Package name
195     require $location;
196     $self->{_my_backend} = $backend_class->new({ config => $self->_config });
197     return $self;
198 }
199
200
201 =head3 _backend
202
203     my $backend = $abstract->_backend($new_backend);
204     my $backend = $abstract->_backend;
205
206 Getter/Setter for our API object.
207
208 =cut
209
210 sub _backend {
211     my ( $self, $backend ) = @_;
212     $self->{_my_backend} = $backend if ( $backend );
213     # Dynamically load our backend object, as late as possible.
214     $self->load_backend unless ( $self->{_my_backend} );
215     return $self->{_my_backend};
216 }
217
218 =head3 _backend_capability
219
220     my $backend_capability_result = $self->_backend_capability($name, $args);
221
222 This is a helper method to invoke optional capabilities in the backend.  If
223 the capability named by $name is not supported, return 0, else invoke it,
224 passing $args along with the invocation, and return its return value.
225
226 NOTE: this module suffers from a confusion in termninology:
227
228 in _backend_capability, the notion of capability refers to an optional feature
229 that is implemented in core, but might not be supported by a given backend.
230
231 in capabilities & custom_capability, capability refers to entries in the
232 status_graph (after union between backend and core).
233
234 The easiest way to fix this would be to fix the terminology in
235 capabilities & custom_capability and their callers.
236
237 =cut
238
239 sub _backend_capability {
240     my ( $self, $name, $args ) = @_;
241     my $capability = 0;
242     try {
243         $capability = $self->_backend->capabilities($name);
244     } catch {
245         return 0;
246     };
247     if ( $capability ) {
248         return &{$capability}($args);
249     } else {
250         return 0;
251     }
252 }
253
254 =head3 _config
255
256     my $config = $abstract->_config($config);
257     my $config = $abstract->_config;
258
259 Getter/Setter for our config object.
260
261 =cut
262
263 sub _config {
264     my ( $self, $config ) = @_;
265     $self->{_my_config} = $config if ( $config );
266     # Load our config object, as late as possible.
267     unless ( $self->{_my_config} ) {
268         $self->{_my_config} = Koha::Illrequest::Config->new;
269     }
270     return $self->{_my_config};
271 }
272
273 =head3 metadata
274
275 =cut
276
277 sub metadata {
278     my ( $self ) = @_;
279     return $self->_backend->metadata($self);
280 }
281
282 =head3 _core_status_graph
283
284     my $core_status_graph = $illrequest->_core_status_graph;
285
286 Returns ILL module's default status graph.  A status graph defines the list of
287 available actions at any stage in the ILL workflow.  This is for instance used
288 by the perl script & template to generate the correct buttons to display to
289 the end user at any given point.
290
291 =cut
292
293 sub _core_status_graph {
294     my ( $self ) = @_;
295     return {
296         NEW => {
297             prev_actions => [ ],                           # Actions containing buttons
298                                                            # leading to this status
299             id             => 'NEW',                       # ID of this status
300             name           => 'New request',               # UI name of this status
301             ui_method_name => 'New request',               # UI name of method leading
302                                                            # to this status
303             method         => 'create',                    # method to this status
304             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ], # buttons to add to all
305                                                            # requests with this status
306             ui_method_icon => 'fa-plus',                   # UI Style class
307         },
308         REQ => {
309             prev_actions   => [ 'NEW', 'REQREV', 'QUEUED', 'CANCREQ' ],
310             id             => 'REQ',
311             name           => 'Requested',
312             ui_method_name => 'Confirm request',
313             method         => 'confirm',
314             next_actions   => [ 'REQREV', 'COMP' ],
315             ui_method_icon => 'fa-check',
316         },
317         GENREQ => {
318             prev_actions   => [ 'NEW', 'REQREV' ],
319             id             => 'GENREQ',
320             name           => 'Requested from partners',
321             ui_method_name => 'Place request with partners',
322             method         => 'generic_confirm',
323             next_actions   => [ 'COMP' ],
324             ui_method_icon => 'fa-send-o',
325         },
326         REQREV => {
327             prev_actions   => [ 'REQ' ],
328             id             => 'REQREV',
329             name           => 'Request reverted',
330             ui_method_name => 'Revert Request',
331             method         => 'cancel',
332             next_actions   => [ 'REQ', 'GENREQ', 'KILL' ],
333             ui_method_icon => 'fa-times',
334         },
335         QUEUED => {
336             prev_actions   => [ ],
337             id             => 'QUEUED',
338             name           => 'Queued request',
339             ui_method_name => 0,
340             method         => 0,
341             next_actions   => [ 'REQ', 'KILL' ],
342             ui_method_icon => 0,
343         },
344         CANCREQ => {
345             prev_actions   => [ 'NEW' ],
346             id             => 'CANCREQ',
347             name           => 'Cancellation requested',
348             ui_method_name => 0,
349             method         => 0,
350             next_actions   => [ 'KILL', 'REQ' ],
351             ui_method_icon => 0,
352         },
353         COMP => {
354             prev_actions   => [ 'REQ' ],
355             id             => 'COMP',
356             name           => 'Completed',
357             ui_method_name => 'Mark completed',
358             method         => 'mark_completed',
359             next_actions   => [ ],
360             ui_method_icon => 'fa-check',
361         },
362         KILL => {
363             prev_actions   => [ 'QUEUED', 'REQREV', 'NEW', 'CANCREQ' ],
364             id             => 'KILL',
365             name           => 0,
366             ui_method_name => 'Delete request',
367             method         => 'delete',
368             next_actions   => [ ],
369             ui_method_icon => 'fa-trash',
370         },
371     };
372 }
373
374 =head3 _core_status_graph
375
376     my $status_graph = $illrequest->_core_status_graph($origin, $new_graph);
377
378 Return a new status_graph, the result of merging $origin & new_graph.  This is
379 operation is a union over the sets defied by the two graphs.
380
381 Each entry in $new_graph is added to $origin.  We do not provide a syntax for
382 'subtraction' of entries from $origin.
383
384 Whilst it is not intended that this works, you can override entries in $origin
385 with entries with the same key in $new_graph.  This can lead to problematic
386 behaviour when $new_graph adds an entry, which modifies a dependent entry in
387 $origin, only for the entry in $origin to be replaced later with a new entry
388 from $new_graph.
389
390 NOTE: this procedure does not "re-link" entries in $origin or $new_graph,
391 i.e. each of the graphs need to be correct at the outset of the operation.
392
393 =cut
394
395 sub _status_graph_union {
396     my ( $self, $core_status_graph, $backend_status_graph ) = @_;
397     # Create new status graph with:
398     # - all core_status_graph
399     # - for-each each backend_status_graph
400     #   + add to new status graph
401     #   + for each core prev_action:
402     #     * locate core_status
403     #     * update next_actions with additional next action.
404     #   + for each core next_action:
405     #     * locate core_status
406     #     * update prev_actions with additional prev action
407
408     my @core_status_ids = keys %{$core_status_graph};
409     my $status_graph = clone($core_status_graph);
410
411     foreach my $backend_status_key ( keys %{$backend_status_graph} ) {
412         my $backend_status = $backend_status_graph->{$backend_status_key};
413         # Add to new status graph
414         $status_graph->{$backend_status_key} = $backend_status;
415         # Update all core methods' next_actions.
416         foreach my $prev_action ( @{$backend_status->{prev_actions}} ) {
417             if ( grep $prev_action, @core_status_ids ) {
418                 my @next_actions =
419                      @{$status_graph->{$prev_action}->{next_actions}};
420                 push @next_actions, $backend_status_key;
421                 $status_graph->{$prev_action}->{next_actions}
422                     = \@next_actions;
423             }
424         }
425         # Update all core methods' prev_actions
426         foreach my $next_action ( @{$backend_status->{next_actions}} ) {
427             if ( grep $next_action, @core_status_ids ) {
428                 my @prev_actions =
429                      @{$status_graph->{$next_action}->{prev_actions}};
430                 push @prev_actions, $backend_status_key;
431                 $status_graph->{$next_action}->{prev_actions}
432                     = \@prev_actions;
433             }
434         }
435     }
436
437     return $status_graph;
438 }
439
440 ### Core API methods
441
442 =head3 capabilities
443
444     my $capabilities = $illrequest->capabilities;
445
446 Return a hashref mapping methods to operation names supported by the queried
447 backend.
448
449 Example return value:
450
451     { create => "Create Request", confirm => "Progress Request" }
452
453 NOTE: this module suffers from a confusion in termninology:
454
455 in _backend_capability, the notion of capability refers to an optional feature
456 that is implemented in core, but might not be supported by a given backend.
457
458 in capabilities & custom_capability, capability refers to entries in the
459 status_graph (after union between backend and core).
460
461 The easiest way to fix this would be to fix the terminology in
462 capabilities & custom_capability and their callers.
463
464 =cut
465
466 sub capabilities {
467     my ( $self, $status ) = @_;
468     # Generate up to date status_graph
469     my $status_graph = $self->_status_graph_union(
470         $self->_core_status_graph,
471         $self->_backend->status_graph({
472             request => $self,
473             other   => {}
474         })
475     );
476     # Extract available actions from graph.
477     return $status_graph->{$status} if $status;
478     # Or return entire graph.
479     return $status_graph;
480 }
481
482 =head3 custom_capability
483
484 Return the result of invoking $CANDIDATE on this request's backend with
485 $PARAMS, or 0 if $CANDIDATE is an unknown method on backend.
486
487 NOTE: this module suffers from a confusion in termninology:
488
489 in _backend_capability, the notion of capability refers to an optional feature
490 that is implemented in core, but might not be supported by a given backend.
491
492 in capabilities & custom_capability, capability refers to entries in the
493 status_graph (after union between backend and core).
494
495 The easiest way to fix this would be to fix the terminology in
496 capabilities & custom_capability and their callers.
497
498 =cut
499
500 sub custom_capability {
501     my ( $self, $candidate, $params ) = @_;
502     foreach my $capability ( values %{$self->capabilities} ) {
503         if ( $candidate eq $capability->{method} ) {
504             my $response =
505                 $self->_backend->$candidate({
506                     request    => $self,
507                     other      => $params,
508                 });
509             return $self->expandTemplate($response);
510         }
511     }
512     return 0;
513 }
514
515 =head3 available_backends
516
517 Return a list of available backends.
518
519 =cut
520
521 sub available_backends {
522     my ( $self ) = @_;
523     my $backends = $self->_config->available_backends;
524     return $backends;
525 }
526
527 =head3 available_actions
528
529 Return a list of available actions.
530
531 =cut
532
533 sub available_actions {
534     my ( $self ) = @_;
535     my $current_action = $self->capabilities($self->status);
536     my @available_actions = map { $self->capabilities($_) }
537         @{$current_action->{next_actions}};
538     return \@available_actions;
539 }
540
541 =head3 mark_completed
542
543 Mark a request as completed (status = COMP).
544
545 =cut
546
547 sub mark_completed {
548     my ( $self ) = @_;
549     $self->status('COMP')->store;
550     return {
551         error   => 0,
552         status  => '',
553         message => '',
554         method  => 'mark_completed',
555         stage   => 'commit',
556         next    => 'illview',
557     };
558 }
559
560 =head2 backend_migrate
561
562 Migrate a request from one backend to another.
563
564 =cut
565
566 sub backend_migrate {
567     my ( $self, $params ) = @_;
568
569     my $response = $self->_backend_capability('migrate',{
570             request    => $self,
571             other      => $params,
572         });
573     return $self->expandTemplate($response) if $response;
574     return $response;
575 }
576
577 =head2 backend_confirm
578
579 Confirm a request. The backend handles setting of mandatory fields in the commit stage:
580
581 =over
582
583 =item * orderid
584
585 =item * accessurl, cost (if available).
586
587 =back
588
589 =cut
590
591 sub backend_confirm {
592     my ( $self, $params ) = @_;
593
594     my $response = $self->_backend->confirm({
595             request    => $self,
596             other      => $params,
597         });
598     return $self->expandTemplate($response);
599 }
600
601 =head3 backend_update_status
602
603 =cut
604
605 sub backend_update_status {
606     my ( $self, $params ) = @_;
607     return $self->expandTemplate($self->_backend->update_status($params));
608 }
609
610 =head3 backend_cancel
611
612     my $ILLResponse = $illRequest->backend_cancel;
613
614 The standard interface method allowing for request cancellation.
615
616 =cut
617
618 sub backend_cancel {
619     my ( $self, $params ) = @_;
620
621     my $result = $self->_backend->cancel({
622         request => $self,
623         other => $params
624     });
625
626     return $self->expandTemplate($result);
627 }
628
629 =head3 backend_renew
630
631     my $renew_response = $illRequest->backend_renew;
632
633 The standard interface method allowing for request renewal queries.
634
635 =cut
636
637 sub backend_renew {
638     my ( $self ) = @_;
639     return $self->expandTemplate(
640         $self->_backend->renew({
641             request    => $self,
642         })
643     );
644 }
645
646 =head3 backend_create
647
648     my $create_response = $abstractILL->backend_create($params);
649
650 Return an array of Record objects created by querying our backend with
651 a Search query.
652
653 In the context of the other ILL methods, this is a special method: we only
654 pass it $params, as it does not yet have any other data associated with it.
655
656 =cut
657
658 sub backend_create {
659     my ( $self, $params ) = @_;
660
661     # Establish whether we need to do a generic copyright clearance.
662     if ($params->{opac}) {
663         if ( ( !$params->{stage} || $params->{stage} eq 'init' )
664                 && C4::Context->preference("ILLModuleCopyrightClearance") ) {
665             return {
666                 error   => 0,
667                 status  => '',
668                 message => '',
669                 method  => 'create',
670                 stage   => 'copyrightclearance',
671                 value   => {
672                     backend => $self->_backend->name
673                 }
674             };
675         } elsif (     defined $params->{stage}
676                 && $params->{stage} eq 'copyrightclearance' ) {
677             $params->{stage} = 'init';
678         }
679     }
680     # First perform API action, then...
681     my $args = {
682         request => $self,
683         other   => $params,
684     };
685     my $result = $self->_backend->create($args);
686
687     # ... simple case: we're not at 'commit' stage.
688     my $stage = $result->{stage};
689     return $self->expandTemplate($result)
690         unless ( 'commit' eq $stage );
691
692     # ... complex case: commit!
693
694     # Do we still have space for an ILL or should we queue?
695     my $permitted = $self->check_limits(
696         { patron => $self->patron }, { librarycode => $self->branchcode }
697     );
698
699     # Now augment our committed request.
700
701     $result->{permitted} = $permitted;             # Queue request?
702
703     # This involves...
704
705     # ...Updating status!
706     $self->status('QUEUED')->store unless ( $permitted );
707
708     return $self->expandTemplate($result);
709 }
710
711 =head3 expandTemplate
712
713     my $params = $abstract->expandTemplate($params);
714
715 Return a version of $PARAMS augmented with our required template path.
716
717 =cut
718
719 sub expandTemplate {
720     my ( $self, $params ) = @_;
721     my $backend = $self->_backend->name;
722     # Generate path to file to load
723     my $backend_dir = $self->_config->backend_dir;
724     my $backend_tmpl = join "/", $backend_dir, $backend;
725     my $intra_tmpl =  join "/", $backend_tmpl, "intra-includes",
726         $params->{method} . ".inc";
727     my $opac_tmpl =  join "/", $backend_tmpl, "opac-includes",
728         $params->{method} . ".inc";
729     # Set files to load
730     $params->{template} = $intra_tmpl;
731     $params->{opac_template} = $opac_tmpl;
732     return $params;
733 }
734
735 #### Abstract Imports
736
737 =head3 getLimits
738
739     my $limit_rules = $abstract->getLimits( {
740         type  => 'brw_cat' | 'branch',
741         value => $value
742     } );
743
744 Return the ILL limit rules for the supplied combination of type / value.
745
746 As the config may have no rules for this particular type / value combination,
747 or for the default, we must define fall-back values here.
748
749 =cut
750
751 sub getLimits {
752     my ( $self, $params ) = @_;
753     my $limits = $self->_config->getLimitRules($params->{type});
754
755     if (     defined $params->{value}
756           && defined $limits->{$params->{value}} ) {
757             return $limits->{$params->{value}};
758     }
759     else {
760         return $limits->{default} || { count => -1, method => 'active' };
761     }
762 }
763
764 =head3 getPrefix
765
766     my $prefix = $abstract->getPrefix( {
767         branch  => $branch_code
768     } );
769
770 Return the ILL prefix as defined by our $params: either per borrower category,
771 per branch or the default.
772
773 =cut
774
775 sub getPrefix {
776     my ( $self, $params ) = @_;
777     my $brn_prefixes = $self->_config->getPrefixes();
778     return $brn_prefixes->{$params->{branch}} || ""; # "the empty prefix"
779 }
780
781 =head3 get_type
782
783     my $type = $abstract->get_type();
784
785 Return a string representing the material type of this request or undef
786
787 =cut
788
789 sub get_type {
790     my ($self) = @_;
791     my $attr = $self->illrequestattributes->find({ type => 'type'});
792     return if !$attr;
793     return $attr->value;
794 };
795
796 #### Illrequests Imports
797
798 =head3 check_limits
799
800     my $ok = $illRequests->check_limits( {
801         borrower   => $borrower,
802         branchcode => 'branchcode' | undef,
803     } );
804
805 Given $PARAMS, a hashref containing a $borrower object and a $branchcode,
806 see whether we are still able to place ILLs.
807
808 LimitRules are derived from koha-conf.xml:
809  + default limit counts, and counting method
810  + branch specific limit counts & counting method
811  + borrower category specific limit counts & counting method
812  + err on the side of caution: a counting fail will cause fail, even if
813    the other counts passes.
814
815 =cut
816
817 sub check_limits {
818     my ( $self, $params ) = @_;
819     my $patron     = $params->{patron};
820     my $branchcode = $params->{librarycode} || $patron->branchcode;
821
822     # Establish maximum number of allowed requests
823     my ( $branch_rules, $brw_rules ) = (
824         $self->getLimits( {
825             type => 'branch',
826             value => $branchcode
827         } ),
828         $self->getLimits( {
829             type => 'brw_cat',
830             value => $patron->categorycode,
831         } ),
832     );
833     my ( $branch_limit, $brw_limit )
834         = ( $branch_rules->{count}, $brw_rules->{count} );
835     # Establish currently existing requests
836     my ( $branch_count, $brw_count ) = (
837         $self->_limit_counter(
838             $branch_rules->{method}, { branchcode => $branchcode }
839         ),
840         $self->_limit_counter(
841             $brw_rules->{method}, { borrowernumber => $patron->borrowernumber }
842         ),
843     );
844
845     # Compare and return
846     # A limit of -1 means no limit exists.
847     # We return blocked if either branch limit or brw limit is reached.
848     if ( ( $branch_limit != -1 && $branch_limit <= $branch_count )
849              || ( $brw_limit != -1 && $brw_limit <= $brw_count ) ) {
850         return 0;
851     } else {
852         return 1;
853     }
854 }
855
856 sub _limit_counter {
857     my ( $self, $method, $target ) = @_;
858
859     # Establish parameters of counts
860     my $resultset;
861     if ($method && $method eq 'annual') {
862         $resultset = Koha::Illrequests->search({
863             -and => [
864                 %{$target},
865                 \"YEAR(placed) = YEAR(NOW())"
866             ]
867         });
868     } else {                    # assume 'active'
869         # XXX: This status list is ugly. There should be a method in config
870         # to return these.
871         my $where = { status => { -not_in => [ 'QUEUED', 'COMP' ] } };
872         $resultset = Koha::Illrequests->search({ %{$target}, %{$where} });
873     }
874
875     # Fetch counts
876     return $resultset->count;
877 }
878
879 =head3 requires_moderation
880
881     my $status = $illRequest->requires_moderation;
882
883 Return the name of the status if moderation by staff is required; or 0
884 otherwise.
885
886 =cut
887
888 sub requires_moderation {
889     my ( $self ) = @_;
890     my $require_moderation = {
891         'CANCREQ' => 'CANCREQ',
892     };
893     return $require_moderation->{$self->status};
894 }
895
896 =head3 generic_confirm
897
898     my $stage_summary = $illRequest->generic_confirm;
899
900 Handle the generic_confirm extended method.  The first stage involves creating
901 a template email for the end user to edit in the browser.  The second stage
902 attempts to submit the email.
903
904 =cut
905
906 sub generic_confirm {
907     my ( $self, $params ) = @_;
908     my $branch = Koha::Libraries->find($params->{current_branchcode})
909         || die "Invalid current branchcode. Are you logged in as the database user?";
910     if ( !$params->{stage}|| $params->{stage} eq 'init' ) {
911         my $draft->{subject} = "ILL Request";
912         $draft->{body} = <<EOF;
913 Dear Sir/Madam,
914
915     We would like to request an interlibrary loan for a title matching the
916 following description:
917
918 EOF
919
920         my $details = $self->metadata;
921         while (my ($title, $value) = each %{$details}) {
922             $draft->{body} .= "  - " . $title . ": " . $value . "\n"
923                 if $value;
924         }
925         $draft->{body} .= <<EOF;
926
927 Please let us know if you are able to supply this to us.
928
929 Kind Regards
930
931 EOF
932
933         my @address = map { $branch->$_ }
934             qw/ branchname branchaddress1 branchaddress2 branchaddress3
935                 branchzip branchcity branchstate branchcountry branchphone
936                 branchemail /;
937         my $address = "";
938         foreach my $line ( @address ) {
939             $address .= $line . "\n" if $line;
940         }
941
942         $draft->{body} .= $address;
943
944         my $partners = Koha::Patrons->search({
945             categorycode => $self->_config->partner_code
946         });
947         return {
948             error   => 0,
949             status  => '',
950             message => '',
951             method  => 'generic_confirm',
952             stage   => 'draft',
953             value   => {
954                 draft    => $draft,
955                 partners => $partners,
956             }
957         };
958
959     } elsif ( 'draft' eq $params->{stage} ) {
960         # Create the to header
961         my $to = $params->{partners};
962         if ( defined $to ) {
963             $to =~ s/^\x00//;       # Strip leading NULLs
964             $to =~ s/\x00/; /;      # Replace others with '; '
965         }
966         Koha::Exceptions::Ill::NoTargetEmail->throw(
967             "No target email addresses found. Either select at least one partner or check your ILL partner library records.")
968           if ( !$to );
969         # Create the from, replyto and sender headers
970         my $from = $branch->branchemail;
971         my $replyto = $branch->branchreplyto || $from;
972         Koha::Exceptions::Ill::NoLibraryEmail->throw(
973             "Your library has no usable email address. Please set it.")
974           if ( !$from );
975
976         # Create the email
977         my $message = Koha::Email->new;
978         my %mail = $message->create_message_headers(
979             {
980                 to          => $to,
981                 from        => $from,
982                 replyto     => $replyto,
983                 subject     => Encode::encode( "utf8", $params->{subject} ),
984                 message     => Encode::encode( "utf8", $params->{body} ),
985                 contenttype => 'text/plain',
986             }
987         );
988         # Send it
989         my $result = sendmail(%mail);
990         if ( $result ) {
991             $self->status("GENREQ")->store;
992             return {
993                 error   => 0,
994                 status  => '',
995                 message => '',
996                 method  => 'generic_confirm',
997                 stage   => 'commit',
998                 next    => 'illview',
999             };
1000         } else {
1001             return {
1002                 error   => 1,
1003                 status  => 'email_failed',
1004                 message => $Mail::Sendmail::error,
1005                 method  => 'generic_confirm',
1006                 stage   => 'draft',
1007             };
1008         }
1009     } else {
1010         die "Unknown stage, should not have happened."
1011     }
1012 }
1013
1014 =head3 id_prefix
1015
1016     my $prefix = $record->id_prefix;
1017
1018 Return the prefix appropriate for the current Illrequest as derived from the
1019 borrower and branch associated with this request's Status, and the config
1020 file.
1021
1022 =cut
1023
1024 sub id_prefix {
1025     my ( $self ) = @_;
1026     my $prefix = $self->getPrefix( {
1027         branch  => $self->branchcode,
1028     } );
1029     $prefix .= "-" if ( $prefix );
1030     return $prefix;
1031 }
1032
1033 =head3 _censor
1034
1035     my $params = $illRequest->_censor($params);
1036
1037 Return $params, modified to reflect our censorship requirements.
1038
1039 =cut
1040
1041 sub _censor {
1042     my ( $self, $params ) = @_;
1043     my $censorship = $self->_config->censorship;
1044     $params->{censor_notes_staff} = $censorship->{censor_notes_staff}
1045         if ( $params->{opac} );
1046     $params->{display_reply_date} = ( $censorship->{censor_reply_date} ) ? 0 : 1;
1047
1048     return $params;
1049 }
1050
1051 =head3 TO_JSON
1052
1053     $json = $illrequest->TO_JSON
1054
1055 Overloaded I<TO_JSON> method that takes care of inserting calculated values
1056 into the unblessed representation of the object.
1057
1058 TODO: This method does nothing and is not called anywhere. However, bug 74325
1059 touches it, so keeping this for now until both this and bug 74325 are merged,
1060 at which point we can sort it out and remove it completely
1061
1062 =cut
1063
1064 sub TO_JSON {
1065     my ( $self, $embed ) = @_;
1066
1067     my $object = $self->SUPER::TO_JSON();
1068
1069     return $object;
1070 }
1071
1072 =head2 Internal methods
1073
1074 =head3 _type
1075
1076 =cut
1077
1078 sub _type {
1079     return 'Illrequest';
1080 }
1081
1082 =head1 AUTHOR
1083
1084 Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com>
1085
1086 =cut
1087
1088 1;