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