Bug 30735: Fix filtering by patron attribute with AV in overdues report
[koha.git] / C4 / CourseReserves.pm
1 package C4::CourseReserves;
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18 use Modern::Perl;
19
20 use List::MoreUtils qw( any );
21
22 use C4::Context;
23 use C4::Circulation qw( GetOpenIssue );
24
25 use Koha::Courses;
26 use Koha::Course::Instructors;
27 use Koha::Course::Items;
28 use Koha::Course::Reserves;
29
30 use vars qw(@FIELDS);
31 our (@ISA, @EXPORT_OK);
32 BEGIN {
33     require Exporter;
34     @ISA       = qw(Exporter);
35     @EXPORT_OK = qw(
36       GetCourse
37       ModCourse
38       GetCourses
39       DelCourse
40
41       GetCourseInstructors
42       ModCourseInstructors
43
44       GetCourseItem
45       ModCourseItem
46
47       GetCourseReserve
48       ModCourseReserve
49       GetCourseReserves
50       DelCourseReserve
51
52       SearchCourses
53
54       GetItemCourseReservesInfo
55     );
56
57     @FIELDS = ( 'itype', 'ccode', 'homebranch', 'holdingbranch', 'location' );
58 }
59
60 =head1 NAME
61
62 C4::CourseReserves - Koha course reserves module
63
64 =head1 SYNOPSIS
65
66 use C4::CourseReserves;
67
68 =head1 DESCRIPTION
69
70 This module deals with course reserves.
71
72 =head1 FUNCTIONS
73
74 =head2 GetCourse
75
76     $course = GetCourse( $course_id );
77
78 =cut
79
80 sub GetCourse {
81     my ($course_id) = @_;
82
83     my $course = Koha::Courses->find( $course_id );
84     return unless $course;
85     $course = $course->unblessed;
86
87     my $dbh = C4::Context->dbh;
88     my $query = "
89         SELECT b.* FROM course_instructors ci
90         LEFT JOIN borrowers b ON ( ci.borrowernumber = b.borrowernumber )
91         WHERE course_id =  ?
92     ";
93     my $sth = $dbh->prepare($query);
94     $sth->execute($course_id);
95     $course->{'instructors'} = $sth->fetchall_arrayref( {} );
96
97     return $course;
98 }
99
100 =head2 ModCourse
101
102     ModCourse( [ course_id => $id ] [, course_name => $course_name ] [etc...] );
103
104 =cut
105
106 sub ModCourse {
107     my (%params) = @_;
108
109     my $dbh = C4::Context->dbh;
110
111     my $course_id;
112     if ( defined $params{'course_id'} ) {
113         $course_id = $params{'course_id'};
114         delete $params{'course_id'};
115     }
116
117     my @query_keys;
118     my @query_values;
119
120     my $query;
121
122     $query .= ($course_id) ? ' UPDATE ' : ' INSERT ';
123     $query .= ' courses SET ';
124
125     foreach my $key ( keys %params ) {
126         push( @query_keys,   "$key=?" );
127         push( @query_values, $params{$key} );
128     }
129     $query .= join( ',', @query_keys );
130
131     if ($course_id) {
132         $query .= " WHERE course_id = ?";
133         push( @query_values, $course_id );
134     }
135
136     $dbh->do( $query, undef, @query_values );
137
138     $course_id = $course_id
139       || $dbh->last_insert_id( undef, undef, 'courses', 'course_id' );
140
141     EnableOrDisableCourseItems(
142         course_id => $course_id,
143         enabled   => $params{'enabled'}
144     );
145
146     return $course_id;
147 }
148
149 =head2 GetCourses
150
151   @courses = GetCourses( [ fieldname => $value ] [, fieldname2 => $value2 ] [etc...] );
152
153 =cut
154
155 sub GetCourses {
156     my (%params) = @_;
157
158     my @query_keys;
159     my @query_values;
160
161     my $query = "
162         SELECT c.course_id, c.department, c.course_number, c.section, c.course_name, c.term, c.staff_note, c.public_note, c.students_count, c.enabled, c.timestamp
163         FROM courses c
164         LEFT JOIN course_reserves ON course_reserves.course_id = c.course_id
165         LEFT JOIN course_items ON course_items.ci_id = course_reserves.ci_id
166     ";
167
168     if ( keys %params ) {
169
170         $query .= " WHERE ";
171
172         foreach my $key ( keys %params ) {
173             push( @query_keys,   " $key LIKE ? " );
174             push( @query_values, $params{$key} );
175         }
176
177         $query .= join( ' AND ', @query_keys );
178     }
179
180     $query .= " GROUP BY c.course_id, c.department, c.course_number, c.section, c.course_name, c.term, c.staff_note, c.public_note, c.students_count, c.enabled, c.timestamp ";
181
182     my $dbh = C4::Context->dbh;
183     my $sth = $dbh->prepare($query);
184     $sth->execute(@query_values);
185
186     my $courses = $sth->fetchall_arrayref( {} );
187
188     foreach my $c (@$courses) {
189         $c->{'instructors'} = GetCourseInstructors( $c->{'course_id'} );
190     }
191
192     return $courses;
193 }
194
195 =head2 DelCourse
196
197   DelCourse( $course_id );
198
199 =cut
200
201 sub DelCourse {
202     my ($course_id) = @_;
203
204     my $course_reserves = GetCourseReserves( course_id => $course_id );
205
206     foreach my $res (@$course_reserves) {
207         DelCourseReserve( cr_id => $res->{'cr_id'} );
208     }
209
210     my $query = "
211         DELETE FROM course_instructors
212         WHERE course_id = ?
213     ";
214     C4::Context->dbh->do( $query, undef, $course_id );
215
216     $query = "
217         DELETE FROM courses
218         WHERE course_id = ?
219     ";
220     C4::Context->dbh->do( $query, undef, $course_id );
221 }
222
223 =head2 EnableOrDisableCourseItems
224
225   EnableOrDisableCourseItems( course_id => $course_id, enabled => $enabled );
226
227   For each item on reserve for this course,
228   if the course item has no active course reserves,
229   swap the fields for the item to make it 'normal'
230   again.
231
232   enabled => 'yes' to enable course items
233   enabled => 'no' to disable course items
234
235 =cut
236
237 sub EnableOrDisableCourseItems {
238     my (%params) = @_;
239
240     my $course_id = $params{'course_id'};
241     my $enabled = $params{'enabled'} || 0;
242
243     my $lookfor = ( $enabled eq 'yes' ) ? 'no' : 'yes';
244
245     return unless ( $course_id && $enabled );
246     return unless ( $enabled eq 'yes' || $enabled eq 'no' );
247
248     my $course_reserves = GetCourseReserves( course_id => $course_id );
249
250     if ( $enabled eq 'yes' ) {
251         foreach my $course_reserve (@$course_reserves) {
252             if (CountCourseReservesForItem(
253                     ci_id   => $course_reserve->{'ci_id'},
254                     enabled => 'yes'
255                 )
256               ) {
257                 EnableOrDisableCourseItem(
258                     ci_id   => $course_reserve->{'ci_id'},
259                 );
260             }
261         }
262     }
263     if ( $enabled eq 'no' ) {
264         foreach my $course_reserve (@$course_reserves) {
265             unless (
266                 CountCourseReservesForItem(
267                     ci_id   => $course_reserve->{'ci_id'},
268                     enabled => 'yes'
269                 )
270               ) {
271                 EnableOrDisableCourseItem(
272                     ci_id   => $course_reserve->{'ci_id'},
273                 );
274             }
275         }
276     }
277 }
278
279 =head2 EnableOrDisableCourseItem
280
281     EnableOrDisableCourseItem( ci_id => $ci_id );
282
283 =cut
284
285 sub EnableOrDisableCourseItem {
286     my (%params) = @_;
287
288     my $ci_id   = $params{'ci_id'};
289
290     return unless ( $ci_id );
291
292     my $course_item = GetCourseItem( ci_id => $ci_id );
293
294     my $info = $course_item->{itemnumber} ? GetItemCourseReservesInfo( itemnumber => $course_item->{itemnumber} ) : GetItemCourseReservesInfo( biblionumber => $course_item->{biblionumber} );
295
296     my $enabled = any { $_->{course}->{enabled} eq 'yes' } @$info;
297     $enabled = $enabled ? 'yes' : 'no';
298
299     ## We don't want to 'enable' an already enabled item,
300     ## or disable and already disabled item,
301     ## as that would cause the fields to swap
302     if ( $course_item->{'enabled'} ne $enabled ) {
303         _SwapAllFields($ci_id, $enabled );
304
305         my $query = "
306             UPDATE course_items
307             SET enabled = ?
308             WHERE ci_id = ?
309         ";
310
311         C4::Context->dbh->do( $query, undef, $enabled, $ci_id );
312
313     }
314
315 }
316
317 =head2 GetCourseInstructors
318
319     @$borrowers = GetCourseInstructors( $course_id );
320
321 =cut
322
323 sub GetCourseInstructors {
324     my ($course_id) = @_;
325
326     my $query = "
327         SELECT * FROM borrowers
328         RIGHT JOIN course_instructors ON ( course_instructors.borrowernumber = borrowers.borrowernumber )
329         WHERE course_instructors.course_id = ?
330     ";
331
332     my $dbh = C4::Context->dbh;
333     my $sth = $dbh->prepare($query);
334     $sth->execute($course_id);
335
336     return $sth->fetchall_arrayref( {} );
337 }
338
339 =head2 ModCourseInstructors
340
341     ModCourseInstructors( mode => $mode, course_id => $course_id, [ cardnumbers => $cardnumbers ] OR [ borrowernumbers => $borrowernumbers  );
342
343     $mode can be 'replace', 'add', or 'delete'
344
345     $cardnumbers and $borrowernumbers are both references to arrays
346
347     Use either cardnumbers or borrowernumber, but not both.
348
349 =cut
350
351 sub ModCourseInstructors {
352     my (%params) = @_;
353
354     my $course_id       = $params{'course_id'};
355     my $mode            = $params{'mode'};
356     my $cardnumbers     = $params{'cardnumbers'};
357     my $borrowernumbers = $params{'borrowernumbers'};
358
359     return unless ($course_id);
360     return
361       unless ( $mode eq 'replace'
362         || $mode eq 'add'
363         || $mode eq 'delete' );
364     return unless ( $cardnumbers || $borrowernumbers );
365     return if ( $cardnumbers && $borrowernumbers );
366
367     my ( @cardnumbers, @borrowernumbers );
368     @cardnumbers = @$cardnumbers if ( ref($cardnumbers) eq 'ARRAY' );
369     @borrowernumbers = @$borrowernumbers
370       if ( ref($borrowernumbers) eq 'ARRAY' );
371
372     my $field  = (@cardnumbers) ? 'cardnumber' : 'borrowernumber';
373     my @fields = (@cardnumbers) ? @cardnumbers : @borrowernumbers;
374     my $placeholders = join( ',', ('?') x scalar @fields );
375
376     my $dbh = C4::Context->dbh;
377
378     $dbh->do( "DELETE FROM course_instructors WHERE course_id = ?", undef, $course_id )
379       if ( $mode eq 'replace' );
380
381     my $query;
382
383     if ( $mode eq 'add' || $mode eq 'replace' ) {
384         $query = "
385             INSERT INTO course_instructors ( course_id, borrowernumber )
386             SELECT ?, borrowernumber
387             FROM borrowers
388             WHERE $field IN ( $placeholders )
389         ";
390     } else {
391         $query = "
392             DELETE FROM course_instructors
393             WHERE course_id = ?
394             AND borrowernumber IN (
395                 SELECT borrowernumber FROM borrowers WHERE $field IN ( $placeholders )
396             )
397         ";
398     }
399
400     my $sth = $dbh->prepare($query);
401
402     $sth->execute( $course_id, @fields ) if (@fields);
403 }
404
405 =head2 GetCourseItem {
406
407   Given one of biblionumber, itenumber, or ci_id, returns hashref of the course_items values
408
409   $course_item = GetCourseItem( itemnumber => $itemnumber [, ci_id => $ci_id ] );
410   $course_item = GetCourseItem( biblionumber => $biblionumber [, ci_id => $ci_id ]);
411   $course_item = GetCourseItem( ci_id => $ci_id );
412
413 =cut
414
415 sub GetCourseItem {
416     my (%params) = @_;
417
418     my $ci_id      = $params{'ci_id'};
419     my $itemnumber = $params{'itemnumber'};
420     my $biblionumber = $params{'biblionumber'};
421
422     return unless ( $itemnumber xor $biblionumber xor $ci_id );
423
424     my ( $field, $value );
425     if ( $itemnumber ) {
426         $field = 'itemnumber';
427         $value = $itemnumber;
428     } elsif ( $biblionumber ) {
429         $field = 'biblionumber';
430         $value = $biblionumber;
431     } else {
432         $field = 'ci_id';
433         $value = $ci_id;
434     }
435
436     my $query = "SELECT * FROM course_items WHERE $field = ?";
437     my $dbh   = C4::Context->dbh;
438     my $sth   = $dbh->prepare($query);
439     $sth->execute($value);
440
441     my $course_item = $sth->fetchrow_hashref();
442
443     if ($course_item) {
444         $query = "SELECT * FROM course_reserves WHERE ci_id = ?";
445         $sth   = $dbh->prepare($query);
446         $sth->execute( $course_item->{'ci_id'} );
447         my $course_reserves = $sth->fetchall_arrayref( {} );
448
449         $course_item->{'course_reserves'} = $course_reserves
450           if ($course_reserves);
451     }
452     return $course_item;
453 }
454
455 =head2 ModCourseItem {
456
457   ModCourseItem( %params );
458
459   Creates or modifies an existing course item. Must be passed either an itemnumber or biblionumber parameter
460
461 =cut
462
463 sub ModCourseItem {
464     my (%params) = @_;
465
466     my $itemnumber = $params{'itemnumber'};
467     my $biblionumber = $params{'biblionumber'};
468
469     return unless ($itemnumber xor $biblionumber);
470
471     my $course_item = $itemnumber ? GetCourseItem( itemnumber => $itemnumber ) : GetCourseItem( biblionumber => $biblionumber );
472
473     if ( $itemnumber and !$biblionumber ) {
474         $biblionumber = Koha::Items->find( $itemnumber )->biblionumber;
475         $params{biblionumber} = $biblionumber;
476     }
477
478     my $ci_id;
479
480     if ($course_item) {
481         $ci_id = $course_item->{'ci_id'};
482
483         _UpdateCourseItem(
484             ci_id       => $ci_id,
485             course_item => $course_item,
486             %params
487         );
488     } else {
489         $ci_id = _AddCourseItem(%params);
490     }
491
492     return $ci_id;
493
494 }
495
496 =head2 _AddCourseItem
497
498     my $ci_id = _AddCourseItem( %params );
499
500 =cut
501
502 sub _AddCourseItem {
503     my (%params) = @_;
504
505     $params{homebranch} ||= undef; # Can't be empty string, FK constraint
506     $params{holdingbranch} ||= undef; # Can't be empty string, FK constraint
507
508     my %data = map { $_ => $params{$_} } @FIELDS;
509     my %enabled = map { $_ . "_enabled" => $params{ $_ . "_enabled" } } @FIELDS;
510
511     my $ci = Koha::Course::Item->new(
512         {
513             itemnumber => $params{itemnumber},
514             biblionumber => $params{biblionumber},
515             %data,
516             %enabled,
517         }
518     )->store();
519
520     return $ci->id;
521 }
522
523 =head2 _UpdateCourseItem
524
525   _UpdateCourseItem( %params );
526
527 =cut
528
529 sub _UpdateCourseItem {
530     my (%params) = @_;
531
532     my $ci_id         = $params{'ci_id'};
533     my $course_item   = $params{'course_item'};
534
535     $params{homebranch} ||= undef; # Can't be empty string, FK constraint
536     $params{holdingbranch} ||= undef; # Can't be empty string, FK constraint
537
538     return unless ( $ci_id || $course_item );
539
540     $course_item = Koha::Course::Items->find( $ci_id || $course_item->{ci_id} );
541
542     my %data = map { $_ => $params{$_} } @FIELDS;
543     my %enabled = map { $_ . "_enabled" => $params{ $_ . "_enabled" } } @FIELDS;
544
545     if ( $course_item->itemnumber ) {
546         # biblio-level course items don't store any of these fields
547         my $item = Koha::Items->find( $course_item->itemnumber );
548
549         # Handle updates to changed fields for a course item, both adding and removing
550         if ( $course_item->is_enabled ) {
551             my $item_fields = {};
552
553             for my $field ( @FIELDS ) {
554
555                 my $field_enabled = $field . '_enabled';
556                 my $field_storage = $field . '_storage';
557
558                 # Find newly enabled field and add item value to storage
559                 if ( $params{$field_enabled} && !$course_item->$field_enabled ) {
560                     $enabled{$field_storage} = $item->$field;
561                     $item_fields->{$field}   = $params{$field};
562                 }
563                 # Find newly disabled field and copy the storage value to the item, unset storage value
564                 elsif ( !$params{$field_enabled} && $course_item->$field_enabled ) {
565                     $item_fields->{$field}   = $course_item->$field_storage;
566                     $enabled{$field_storage} = undef;
567                 }
568                 # The field was already enabled, copy the incoming value to the item.
569                 # The "original" ( when not on course reserve ) value is already in the storage field
570                 elsif ( $course_item->$field_enabled) {
571                     $item_fields->{$field} = $params{$field};
572                 }
573             }
574
575             $item->set( $item_fields )->store
576                 if keys %$item_fields;
577         }
578     }
579
580     $course_item->update( { %data, %enabled } );
581
582 }
583
584 =head2 _RevertFields
585
586     _RevertFields( ci_id => $ci_id, fields => \@fields_to_revert );
587
588     Copies fields from course item storage back to the actual item
589
590 =cut
591
592 sub _RevertFields {
593     my (%params) = @_;
594
595     my $ci_id = $params{'ci_id'};
596
597     return unless $ci_id;
598
599     my $course_item = Koha::Course::Items->find( $ci_id );
600
601     my $item_fields = {};
602     $item_fields->{itype}         = $course_item->itype_storage         if $course_item->itype_enabled;
603     $item_fields->{ccode}         = $course_item->ccode_storage         if $course_item->ccode_enabled;
604     $item_fields->{location}      = $course_item->location_storage      if $course_item->location_enabled;
605     $item_fields->{homebranch} = $course_item->homebranch_storage if $course_item->homebranch_enabled;
606     $item_fields->{holdingbranch} = $course_item->holdingbranch_storage if $course_item->holdingbranch_enabled;
607
608     Koha::Items->find( $course_item->itemnumber )
609                ->set( $item_fields )
610                ->store
611         if keys %$item_fields;
612
613     $course_item->itype_storage(undef);
614     $course_item->ccode_storage(undef);
615     $course_item->location_storage(undef);
616     $course_item->homebranch_storage(undef);
617     $course_item->holdingbranch_storage(undef);
618     $course_item->store();
619 }
620
621 =head2 _SwapAllFields
622
623     _SwapAllFields( $ci_id );
624
625 =cut
626
627 sub _SwapAllFields {
628     my ( $ci_id, $enabled ) = @_;
629
630     my $course_item = Koha::Course::Items->find( $ci_id );
631     my $item = Koha::Items->find( $course_item->itemnumber );
632
633     if ( $enabled eq 'yes' ) { # Copy item fields to course item storage, course item fields to item
634         $course_item->itype_storage( $item->effective_itemtype )    if $course_item->itype_enabled;
635         $course_item->ccode_storage( $item->ccode )                 if $course_item->ccode_enabled;
636         $course_item->location_storage( $item->location )           if $course_item->location_enabled;
637         $course_item->homebranch_storage( $item->homebranch )       if $course_item->homebranch_enabled;
638         $course_item->holdingbranch_storage( $item->holdingbranch ) if $course_item->holdingbranch_enabled;
639         $course_item->store();
640
641         my $item_fields = {};
642         $item_fields->{itype}         = $course_item->itype         if $course_item->itype_enabled;
643         $item_fields->{ccode}         = $course_item->ccode         if $course_item->ccode_enabled;
644         $item_fields->{location}      = $course_item->location      if $course_item->location_enabled;
645         $item_fields->{homebranch}    = $course_item->homebranch    if $course_item->homebranch_enabled;
646         $item_fields->{holdingbranch} = $course_item->holdingbranch if $course_item->holdingbranch_enabled;
647
648         Koha::Items->find( $course_item->itemnumber )
649                    ->set( $item_fields )
650                    ->store
651             if keys %$item_fields;
652
653     } else { # Copy course item storage to item
654         my $item_fields = {};
655         $item_fields->{itype}         = $course_item->itype_storage         if $course_item->itype_enabled;
656         $item_fields->{ccode}         = $course_item->ccode_storage         if $course_item->ccode_enabled;
657         $item_fields->{location}      = $course_item->location_storage      if $course_item->location_enabled;
658         $item_fields->{homebranch}    = $course_item->homebranch_storage    if $course_item->homebranch_enabled;
659         $item_fields->{holdingbranch} = $course_item->holdingbranch_storage if $course_item->holdingbranch_enabled;
660
661         Koha::Items->find( $course_item->itemnumber )
662                    ->set( $item_fields )
663                    ->store
664             if keys %$item_fields;
665
666         $course_item->itype_storage(undef);
667         $course_item->ccode_storage(undef);
668         $course_item->location_storage(undef);
669         $course_item->homebranch_storage(undef);
670         $course_item->holdingbranch_storage(undef);
671         $course_item->store();
672     }
673 }
674
675 =head2 GetCourseItems {
676
677   $course_items = GetCourseItems(
678       [course_id => $course_id]
679       [, itemnumber => $itemnumber ]
680   );
681
682 =cut
683
684 sub GetCourseItems {
685     my (%params) = @_;
686
687     my $course_id  = $params{'course_id'};
688     my $itemnumber = $params{'itemnumber'};
689
690     return unless ($course_id);
691
692     my @query_keys;
693     my @query_values;
694
695     my $query = "SELECT * FROM course_items";
696
697     if ( keys %params ) {
698
699         $query .= " WHERE ";
700
701         foreach my $key ( keys %params ) {
702             push( @query_keys,   " $key LIKE ? " );
703             push( @query_values, $params{$key} );
704         }
705
706         $query .= join( ' AND ', @query_keys );
707     }
708
709     my $dbh = C4::Context->dbh;
710     my $sth = $dbh->prepare($query);
711     $sth->execute(@query_values);
712
713     return $sth->fetchall_arrayref( {} );
714 }
715
716 =head2 DelCourseItem {
717
718   DelCourseItem( ci_id => $cr_id );
719
720 =cut
721
722 sub DelCourseItem {
723     my (%params) = @_;
724
725     my $ci_id = $params{'ci_id'};
726
727     return unless ($ci_id);
728
729     my $course_item = Koha::Course::Items->find( $ci_id );
730     return unless $course_item;
731
732     _RevertFields( ci_id => $ci_id ) if $course_item->enabled eq 'yes';
733
734     my $query = "
735         DELETE FROM course_items
736         WHERE ci_id = ?
737     ";
738     C4::Context->dbh->do( $query, undef, $ci_id );
739 }
740
741 =head2 GetCourseReserve {
742
743   $course_item = GetCourseReserve( %params );
744
745 =cut
746
747 sub GetCourseReserve {
748     my (%params) = @_;
749
750     my $cr_id     = $params{'cr_id'};
751     my $course_id = $params{'course_id'};
752     my $ci_id     = $params{'ci_id'};
753
754     return unless ( $cr_id || ( $course_id && $ci_id ) );
755
756     my $dbh = C4::Context->dbh;
757     my $sth;
758
759     if ($cr_id) {
760         my $query = "
761             SELECT * FROM course_reserves
762             WHERE cr_id = ?
763         ";
764         $sth = $dbh->prepare($query);
765         $sth->execute($cr_id);
766     } else {
767         my $query = "
768             SELECT * FROM course_reserves
769             WHERE course_id = ? AND ci_id = ?
770         ";
771         $sth = $dbh->prepare($query);
772         $sth->execute( $course_id, $ci_id );
773     }
774
775     my $course_reserve = $sth->fetchrow_hashref();
776     return $course_reserve;
777 }
778
779 =head2 ModCourseReserve
780
781     $id = ModCourseReserve( %params );
782
783 =cut
784
785 sub ModCourseReserve {
786     my (%params) = @_;
787
788     my $course_id   = $params{'course_id'};
789     my $ci_id       = $params{'ci_id'};
790     my $staff_note  = $params{'staff_note'};
791     my $public_note = $params{'public_note'};
792
793     return unless ( $course_id && $ci_id );
794
795     my $course_reserve = GetCourseReserve( course_id => $course_id, ci_id => $ci_id );
796     my $cr_id;
797
798     my $dbh = C4::Context->dbh;
799
800     if ($course_reserve) {
801         $cr_id = $course_reserve->{'cr_id'};
802
803         my $query = "
804             UPDATE course_reserves
805             SET staff_note = ?, public_note = ?
806             WHERE cr_id = ?
807         ";
808         $dbh->do( $query, undef, $staff_note, $public_note, $cr_id );
809     } else {
810         my $query = "
811             INSERT INTO course_reserves SET
812             course_id = ?,
813             ci_id = ?,
814             staff_note = ?,
815             public_note = ?
816         ";
817         $dbh->do( $query, undef, $course_id, $ci_id, $staff_note, $public_note );
818         $cr_id = $dbh->last_insert_id( undef, undef, 'course_reserves', 'cr_id' );
819     }
820
821     EnableOrDisableCourseItem(
822         ci_id   => $params{'ci_id'},
823     );
824
825     return $cr_id;
826 }
827
828 =head2 GetCourseReserves {
829
830   $course_reserves = GetCourseReserves( %params );
831
832   Required:
833       course_id OR ci_id
834   Optional:
835       include_items   => 1,
836       include_count   => 1,
837       include_courses => 1,
838
839 =cut
840
841 sub GetCourseReserves {
842     my (%params) = @_;
843
844     my $course_id       = $params{'course_id'};
845     my $ci_id           = $params{'ci_id'};
846     my $include_items   = $params{'include_items'};
847     my $include_count   = $params{'include_count'};
848     my $include_courses = $params{'include_courses'};
849
850     return unless ( $course_id || $ci_id );
851
852     my $field = ($course_id) ? 'course_id' : 'ci_id';
853     my $value = ($course_id) ? $course_id  : $ci_id;
854
855     my $query = "
856         SELECT cr.*, ci.itemnumber, ci.biblionumber
857         FROM course_reserves cr, course_items ci
858         WHERE cr.$field = ?
859         AND cr.ci_id = ci.ci_id
860     ";
861     my $dbh = C4::Context->dbh;
862     my $sth = $dbh->prepare($query);
863     $sth->execute($value);
864
865     my $course_reserves = $sth->fetchall_arrayref( {} );
866
867     if ($include_items) {
868         foreach my $cr (@$course_reserves) {
869             my $item = Koha::Items->find( $cr->{itemnumber}, { prefetch => ['biblio','biblioitem'] });
870             my $biblio = $cr->{itemnumber} ? $item->biblio : Koha::Biblios->find( $cr->{biblionumber}, { prefetch => ['biblioitem'] });
871             my $biblioitem = $biblio->biblioitem;
872             $cr->{'course_item'} = GetCourseItem( ci_id => $cr->{'ci_id'} );
873             $cr->{'item'}        = $item;
874             $cr->{'biblio'}      = $biblio;
875             $cr->{'biblioitem'}  = $biblioitem;
876             $cr->{'issue'}       = GetOpenIssue( $cr->{'itemnumber'} );
877         }
878     }
879
880     if ($include_count) {
881         foreach my $cr (@$course_reserves) {
882             $cr->{'reserves_count'} = CountCourseReservesForItem( ci_id => $cr->{'ci_id'} );
883         }
884     }
885
886     if ($include_courses) {
887         foreach my $cr (@$course_reserves) {
888             $cr->{'courses'} = $cr->{itemnumber} ? GetCourses( itemnumber => $cr->{'itemnumber'} ) : GetCourses( biblionumber => $cr->{biblionumber} );
889         }
890     }
891
892     return $course_reserves;
893 }
894
895 =head2 DelCourseReserve {
896
897   DelCourseReserve( cr_id => $cr_id );
898
899 =cut
900
901 sub DelCourseReserve {
902     my (%params) = @_;
903
904     my $cr_id = $params{'cr_id'};
905
906     return unless ($cr_id);
907
908     my $dbh = C4::Context->dbh;
909
910     my $course_reserve = GetCourseReserve( cr_id => $cr_id );
911
912     my $query = "
913         DELETE FROM course_reserves
914         WHERE cr_id = ?
915     ";
916     $dbh->do( $query, undef, $cr_id );
917
918     ## If there are no other course reserves for this item
919     ## delete the course_item as well
920     unless ( CountCourseReservesForItem( ci_id => $course_reserve->{'ci_id'} ) ) {
921         DelCourseItem( ci_id => $course_reserve->{'ci_id'} );
922     }
923
924 }
925
926 =head2 GetItemCourseReservesInfo
927
928     my $arrayref = GetItemCourseReservesInfo( itemnumber => $itemnumber );
929     my $arrayref = GetItemCourseReservesInfo( biblionumber => $biblionumber );
930
931     For a given itemnumber or biblionumber, returns an arrayref of reserves hashrefs,
932     with a course hashref under the key 'course'
933
934 =cut
935
936 sub GetItemCourseReservesInfo {
937     my (%params) = @_;
938
939     my $itemnumber = $params{'itemnumber'};
940     my $biblionumber = $params{'biblionumber'};
941
942     return unless ($itemnumber xor $biblionumber);
943
944     my $course_item = $itemnumber ? GetCourseItem( itemnumber => $itemnumber ) : GetCourseItem( biblionumber => $biblionumber );
945
946     return unless ( keys %$course_item );
947
948     my $course_reserves = GetCourseReserves( ci_id => $course_item->{'ci_id'} );
949
950     foreach my $cr (@$course_reserves) {
951         $cr->{'course'} = GetCourse( $cr->{'course_id'} );
952     }
953
954     return $course_reserves;
955 }
956
957 =head2 CountCourseReservesForItem
958
959     $bool = CountCourseReservesForItem( %params );
960
961     ci_id - course_item id
962     OR
963     itemnumber - course_item itemnumber
964     OR
965     biblionumber - course_item biblionumber
966
967     enabled = 'yes' or 'no'
968     Optional, if not supplied, counts reserves
969     for both enabled and disabled courses
970
971 =cut
972
973 sub CountCourseReservesForItem {
974     my (%params) = @_;
975
976     my $ci_id      = $params{'ci_id'};
977     my $itemnumber = $params{'itemnumber'};
978     my $enabled    = $params{'enabled'};
979     my $biblionumber = $params{'biblionumber'};
980
981     return unless ( $ci_id xor $itemnumber xor $biblionumber );
982
983     my $course_item = GetCourseItem( ci_id => $ci_id ) || GetCourseItem( itemnumber => $itemnumber ) || GetCourseItem( biblionumber => $biblionumber );
984
985     my @params = ( $course_item->{'ci_id'} );
986     push( @params, $enabled ) if ($enabled);
987
988     my $query = "
989         SELECT COUNT(*) AS count
990         FROM course_reserves cr
991         LEFT JOIN courses c ON ( c.course_id = cr.course_id )
992         WHERE ci_id = ?
993     ";
994     $query .= "AND c.enabled = ?" if ($enabled);
995
996     my $dbh = C4::Context->dbh;
997     my $sth = $dbh->prepare($query);
998     $sth->execute(@params);
999
1000     my $row = $sth->fetchrow_hashref();
1001
1002     return $row->{'count'};
1003 }
1004
1005 =head2 SearchCourses
1006
1007     my $courses = SearchCourses( term => $search_term, enabled => 'yes' );
1008
1009 =cut
1010
1011 sub SearchCourses {
1012     my (%params) = @_;
1013
1014     my $term = $params{'term'};
1015
1016     my $enabled = $params{'enabled'} || '%';
1017
1018     my @params;
1019     my $query = "
1020         SELECT c.course_id, c.department, c.course_number, c.section, c.course_name, c.term, c.staff_note, c.public_note, c.students_count, c.enabled, c.timestamp
1021         FROM courses c
1022         LEFT JOIN course_instructors ci
1023             ON ( c.course_id = ci.course_id )
1024         LEFT JOIN borrowers b
1025             ON ( ci.borrowernumber = b.borrowernumber )
1026         LEFT JOIN authorised_values av
1027             ON ( c.department = av.authorised_value )
1028         WHERE
1029             ( av.category = 'DEPARTMENT' OR av.category = 'TERM' )
1030             AND
1031             (
1032                 department LIKE ? OR
1033                 course_number LIKE ? OR
1034                 section LIKE ? OR
1035                 course_name LIKE ? OR
1036                 term LIKE ? OR
1037                 public_note LIKE ? OR
1038                 CONCAT(surname,' ',firstname) LIKE ? OR
1039                 CONCAT(firstname,' ',surname) LIKE ? OR
1040                 lib LIKE ? OR
1041                 lib_opac LIKE ?
1042            )
1043            AND
1044            c.enabled LIKE ?
1045         GROUP BY c.course_id, c.department, c.course_number, c.section, c.course_name, c.term, c.staff_note, c.public_note, c.students_count, c.enabled, c.timestamp
1046     ";
1047
1048     $term //= '';
1049     $term   = "%$term%";
1050     @params = ($term) x 10;
1051
1052     $query .= " ORDER BY department, course_number, section, term, course_name ";
1053
1054     my $dbh = C4::Context->dbh;
1055     my $sth = $dbh->prepare($query);
1056
1057     $sth->execute( @params, $enabled );
1058
1059     my $courses = $sth->fetchall_arrayref( {} );
1060
1061     foreach my $c (@$courses) {
1062         $c->{'instructors'} = GetCourseInstructors( $c->{'course_id'} );
1063     }
1064
1065     return $courses;
1066 }
1067
1068 sub whoami  { ( caller(1) )[3] }
1069 sub whowasi { ( caller(2) )[3] }
1070
1071 sub stringify_params {
1072     my (%params) = @_;
1073
1074     my $string = "\n";
1075
1076     foreach my $key ( keys %params ) {
1077         $string .= "    $key => " . $params{$key} . "\n";
1078     }
1079
1080     return "( $string )";
1081 }
1082
1083 1;
1084
1085 =head1 AUTHOR
1086
1087 Kyle M Hall <kyle@bywatersolutions.com>
1088
1089 =cut