Bug 31281: Use correct reply-to email when sending overdue mails
[koha.git] / C4 / RotatingCollections.pm
1 package C4::RotatingCollections;
2
3 # $Id: RotatingCollections.pm,v 0.1 2007/04/20 kylemhall
4
5 # This package is inteded to keep track of what library
6 # Items of a certain collection should be at.
7
8 # Copyright 2007 Kyle Hall
9 #
10 # This file is part of Koha.
11 #
12 # Koha is free software; you can redistribute it and/or modify it
13 # under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
16 #
17 # Koha is distributed in the hope that it will be useful, but
18 # WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with Koha; if not, see <http://www.gnu.org/licenses>.
24
25 use Modern::Perl;
26
27 use C4::Context;
28 use C4::Reserves qw(CheckReserves);
29 use Koha::Database;
30
31 use Try::Tiny qw( catch try );
32
33 use vars qw(@ISA @EXPORT);
34
35
36 =head1 NAME
37
38 C4::RotatingCollections - Functions for managing rotating collections
39
40 =head1 FUNCTIONS
41
42 =cut
43
44 BEGIN {
45     require Exporter;
46     @ISA    = qw( Exporter );
47     @EXPORT = qw(
48       CreateCollection
49       UpdateCollection
50       DeleteCollection
51
52       GetItemsInCollection
53
54       GetCollection
55       GetCollections
56
57       AddItemToCollection
58       RemoveItemFromCollection
59       TransferCollection
60
61       GetCollectionItemBranches
62       isItemInAnyCollection
63       isItemInThisCollection
64     );
65 }
66
67 =head2  CreateCollection
68  ( $success, $errorcode, $errormessage ) = CreateCollection( $title, $description );
69
70 Creates a new collection
71
72  Input:
73    $title: short description of the club or service
74    $description: long description of the club or service
75
76  Output:
77    $success: 1 if all database operations were successful, 0 otherwise
78    $errorCode: Code for reason of failure, good for translating errors in templates
79    $errorMessage: English description of error
80
81 =cut
82
83 sub CreateCollection {
84     my ( $title, $description ) = @_;
85
86     my $schema = Koha::Database->new()->schema();
87     my $duplicate_titles = $schema->resultset('Collection')->count({ colTitle => $title });
88
89     ## Check for all necessary parameters
90     if ( !$title ) {
91         return ( 0, 1, "NO_TITLE" );
92     } elsif ( $duplicate_titles ) {
93         return ( 0, 2, "DUPLICATE_TITLE" );
94     }
95
96     $description ||= q{};
97
98     my $success = 1;
99
100     my $dbh = C4::Context->dbh;
101
102     my $sth;
103     $sth = $dbh->prepare(
104         "INSERT INTO collections ( colId, colTitle, colDesc )
105                         VALUES ( NULL, ?, ? )"
106     );
107     $sth->execute( $title, $description ) or return ( 0, 3, $sth->errstr() );
108
109     return 1;
110
111 }
112
113 =head2 UpdateCollection
114
115  ( $success, $errorcode, $errormessage ) = UpdateCollection( $colId, $title, $description );
116
117 Updates a collection
118
119  Input:
120    $colId: id of the collection to be updated
121    $title: short description of the club or service
122    $description: long description of the club or service
123
124  Output:
125    $success: 1 if all database operations were successful, 0 otherwise
126    $errorCode: Code for reason of failure, good for translating errors in templates
127    $errorMessage: English description of error
128
129 =cut
130
131 sub UpdateCollection {
132     my ( $colId, $title, $description ) = @_;
133
134     my $schema = Koha::Database->new()->schema();
135     my $duplicate_titles = $schema->resultset('Collection')->count({ colTitle => $title,  -not => { colId => $colId } });
136
137     ## Check for all necessary parameters
138     if ( !$colId ) {
139         return ( 0, 1, "NO_ID" );
140     }
141     if ( !$title ) {
142         return ( 0, 2, "NO_TITLE" );
143     }
144     if ( $duplicate_titles ) {
145         return ( 0, 3, "DUPLICATE_TITLE" );
146     }
147
148     my $dbh = C4::Context->dbh;
149
150     $description ||= q{};
151
152     my $sth;
153     $sth = $dbh->prepare(
154         "UPDATE collections
155                         SET 
156                         colTitle = ?, colDesc = ? 
157                         WHERE colId = ?"
158     );
159     $sth->execute( $title, $description, $colId )
160       or return ( 0, 4, $sth->errstr() );
161
162     return 1;
163
164 }
165
166 =head2 DeleteCollection
167
168  ( $success, $errorcode, $errormessage ) = DeleteCollection( $colId );
169
170 Deletes a collection of the given id
171
172  Input:
173    $colId : id of the Archetype to be deleted
174
175  Output:
176    $success: 1 if all database operations were successful, 0 otherwise
177    $errorCode: Code for reason of failure, good for translating errors in templates
178    $errorMessage: English description of error
179
180 =cut
181
182 sub DeleteCollection {
183     my ($colId) = @_;
184
185     ## Parameter check
186     if ( !$colId ) {
187         return ( 0, 1, "NO_ID" );
188     }
189
190     my $dbh = C4::Context->dbh;
191
192     my $sth;
193
194     $sth = $dbh->prepare("DELETE FROM collections WHERE colId = ?");
195     $sth->execute($colId) or return ( 0, 4, $sth->errstr() );
196
197     return 1;
198 }
199
200 =head2 GetCollections
201
202  $collections = GetCollections();
203
204 Returns data about all collections
205
206  Output:
207   On Success:
208    $results: Reference to an array of associated arrays
209   On Failure:
210    $errorCode: Code for reason of failure, good for translating errors in templates
211    $errorMessage: English description of error
212
213 =cut
214
215 sub GetCollections {
216
217     my $dbh = C4::Context->dbh;
218
219     my $sth = $dbh->prepare("SELECT * FROM collections");
220     $sth->execute() or return ( 1, $sth->errstr() );
221
222     my @results;
223     while ( my $row = $sth->fetchrow_hashref ) {
224         push( @results, $row );
225     }
226
227     return \@results;
228 }
229
230 =head2 GetItemsInCollection
231
232  ( $results, $success, $errorcode, $errormessage ) = GetItemsInCollection( $colId );
233
234 Returns information about the items in the given collection
235
236  Input:
237    $colId: The id of the collection
238
239  Output:
240    $results: Reference to an array of associated arrays
241    $success: 1 if all database operations were successful, 0 otherwise
242    $errorCode: Code for reason of failure, good for translating errors in templates
243    $errorMessage: English description of error
244
245 =cut
246
247 sub GetItemsInCollection {
248     my ($colId) = @_;
249
250     ## Parameter check
251     if ( !$colId ) {
252         return ( 0, 0, 1, "NO_ID" );
253     }
254
255     my $dbh = C4::Context->dbh;
256
257     my $sth = $dbh->prepare(
258         "SELECT
259                              biblio.title,
260                              biblio.biblionumber,
261                              items.itemcallnumber,
262                              items.barcode
263                            FROM collections, collections_tracking, items, biblio
264                            WHERE collections.colId = collections_tracking.colId
265                            AND collections_tracking.itemnumber = items.itemnumber
266                            AND items.biblionumber = biblio.biblionumber
267                            AND collections.colId = ? ORDER BY biblio.title"
268     );
269     $sth->execute($colId) or return ( 0, 0, 2, $sth->errstr() );
270
271     my @results;
272     while ( my $row = $sth->fetchrow_hashref ) {
273         push( @results, $row );
274     }
275
276     return \@results;
277 }
278
279 =head2 GetCollection
280
281  ( $colId, $colTitle, $colDesc, $colBranchcode ) = GetCollection( $colId );
282
283 Returns information about a collection
284
285  Input:
286    $colId: Id of the collection
287  Output:
288    $colId, $colTitle, $colDesc, $colBranchcode
289
290 =cut
291
292 sub GetCollection {
293     my ($colId) = @_;
294
295     my $dbh = C4::Context->dbh;
296
297     my ( $sth, @results );
298     $sth = $dbh->prepare("SELECT * FROM collections WHERE colId = ?");
299     $sth->execute($colId) or return 0;
300
301     my $row = $sth->fetchrow_hashref;
302
303     return (
304         $$row{'colId'},   $$row{'colTitle'},
305         $$row{'colDesc'}, $$row{'colBranchcode'}
306     );
307
308 }
309
310 =head2 AddItemToCollection
311
312  ( $success, $errorcode, $errormessage ) = AddItemToCollection( $colId, $itemnumber );
313
314 Adds an item to a rotating collection.
315
316  Input:
317    $colId: Collection to add the item to.
318    $itemnumber: Item to be added to the collection
319  Output:
320    $success: 1 if all database operations were successful, 0 otherwise
321    $errorCode: Code for reason of failure, good for translating errors in templates
322    $errorMessage: English description of error
323
324 =cut
325
326 sub AddItemToCollection {
327     my ( $colId, $itemnumber ) = @_;
328
329     ## Check for all necessary parameters
330     if ( !$colId ) {
331         return ( 0, 1, "NO_ID" );
332     }
333     if ( !$itemnumber ) {
334         return ( 0, 2, "NO_ITEM" );
335     }
336
337     if ( isItemInThisCollection( $itemnumber, $colId ) ) {
338         return ( 0, 2, "IN_COLLECTION" );
339     }
340     elsif ( isItemInAnyCollection($itemnumber) ) {
341         return ( 0, 3, "IN_COLLECTION_OTHER" );
342     }
343
344     my $dbh = C4::Context->dbh;
345
346     my $sth;
347     $sth = $dbh->prepare("
348         INSERT INTO collections_tracking (
349             colId,
350             itemnumber
351         ) VALUES ( ?, ? )
352     ");
353     $sth->execute( $colId, $itemnumber ) or return ( 0, 3, $sth->errstr() );
354
355     return 1;
356
357 }
358
359 =head2  RemoveItemFromCollection
360
361  ( $success, $errorcode, $errormessage ) = RemoveItemFromCollection( $colId, $itemnumber );
362
363 Removes an item to a collection
364
365  Input:
366    $colId: Collection to add the item to.
367    $itemnumber: Item to be removed from collection
368
369  Output:
370    $success: 1 if all database operations were successful, 0 otherwise
371    $errorCode: Code for reason of failure, good for translating errors in templates
372    $errorMessage: English description of error
373
374 =cut
375
376 sub RemoveItemFromCollection {
377     my ( $colId, $itemnumber ) = @_;
378
379     ## Check for all necessary parameters
380     if ( !$itemnumber ) {
381         return ( 0, 2, "NO_ITEM" );
382     }
383
384     if ( !isItemInThisCollection( $itemnumber, $colId ) ) {
385         return ( 0, 2, "NOT_IN_COLLECTION" );
386     }
387
388     my $dbh = C4::Context->dbh;
389
390     my $sth;
391     $sth = $dbh->prepare(
392         "DELETE FROM collections_tracking
393                         WHERE itemnumber = ?"
394     );
395     $sth->execute($itemnumber) or return ( 0, 3, $sth->errstr() );
396
397     return 1;
398 }
399
400 =head2 TransferCollection
401
402  ( $success, $messages ) = TransferCollection( $colId, $colBranchcode );
403
404 Transfers a collection to another branch
405
406  Input:
407    $colId: id of the collection to be updated
408    $colBranchcode: branch where collection is moving to
409
410  Output:
411    $success: 1 if all database operations were successful, 0 otherwise
412    $messages: Arrayref of messages for user feedback
413
414 =cut
415
416 sub TransferCollection {
417     my ( $colId, $colBranchcode ) = @_;
418
419     ## Check for all necessary parameters
420     if ( !$colId ) {
421         return ( 0, [{ type => 'error', code => 'NO_ID' }] );
422     }
423     if ( !$colBranchcode ) {
424         return ( 0, [{ type => 'error', code => 'NO_BRANCHCODE' }] );
425     }
426
427     my $dbh = C4::Context->dbh;
428
429     my $sth;
430     $sth = $dbh->prepare(
431         "UPDATE collections
432                         SET 
433                         colBranchcode = ? 
434                         WHERE colId = ?"
435     );
436     $sth->execute( $colBranchcode, $colId ) or return 0;
437     my $to_library = Koha::Libraries->find( $colBranchcode );
438
439     $sth = $dbh->prepare(q{
440         SELECT items.itemnumber, items.barcode FROM collections_tracking
441         LEFT JOIN items ON collections_tracking.itemnumber = items.itemnumber
442         LEFT JOIN issues ON items.itemnumber = issues.itemnumber
443         WHERE issues.borrowernumber IS NULL
444           AND collections_tracking.colId = ?
445     });
446     $sth->execute($colId) or return 0;
447     my $messages;
448     while ( my $item = $sth->fetchrow_hashref ) {
449         my $item_object = Koha::Items->find( $item->{itemnumber} );
450         try {
451             $item_object->request_transfer(
452                 {
453                     to            => $to_library,
454                     reason        => 'RotatingCollection',
455                     ignore_limits => 0
456                 }
457             );    # Request transfer
458         }
459         catch {
460             if ( $_->isa('Koha::Exceptions::Item::Transfer::InQueue') ) {
461                 my $exception      = $_;
462                 my $found_transfer = $_->transfer;
463                 if (   $found_transfer->in_transit
464                     || $found_transfer->reason eq 'Reserve' )
465                 {
466                     my $transfer = $item_object->request_transfer(
467                         {
468                             to            => $to_library,
469                             reason        => "RotatingCollection",
470                             ignore_limits => 0,
471                             enqueue       => 1
472                         }
473                     );    # Queue transfer
474                     push @{$messages},
475                       {
476                         type           => 'alert',
477                         code           => 'enqueued',
478                         item           => $item_object,
479                         found_transfer => $found_transfer
480                       };
481                 }
482                 else {
483                     my $transfer = $item_object->request_transfer(
484                         {
485                             to            => $to_library,
486                             reason        => "RotatingCollection",
487                             ignore_limits => 0,
488                             replace       => 1
489                         }
490                     );    # Replace transfer
491                     # NOTE: If we just replaced a StockRotationAdvance,
492                     # it will get enqueued afresh on the next cron run
493                 }
494             }
495             elsif ( $_->isa('Koha::Exceptions::Item::Transfer::Limit') ) {
496                 push @{$messages},
497                   {
498                     type => 'error',
499                     code => 'limits',
500                     item => $item_object
501                   };
502             }
503             else {
504                 $_->rethrow();
505             }
506         };
507     }
508
509     return (1, $messages);
510 }
511
512 =head2 GetCollectionItemBranches
513
514   my ( $holdingBranch, $collectionBranch ) = GetCollectionItemBranches( $itemnumber );
515
516 =cut
517
518 sub GetCollectionItemBranches {
519     my ($itemnumber) = @_;
520
521     if ( !$itemnumber ) {
522         return;
523     }
524
525     my $dbh = C4::Context->dbh;
526
527     my ( $sth, @results );
528     $sth = $dbh->prepare(
529 "SELECT holdingbranch, colBranchcode FROM items, collections, collections_tracking
530                         WHERE items.itemnumber = collections_tracking.itemnumber
531                         AND collections.colId = collections_tracking.colId
532                         AND items.itemnumber = ?"
533     );
534     $sth->execute($itemnumber);
535
536     my $row = $sth->fetchrow_hashref;
537
538     return ( $$row{'holdingbranch'}, $$row{'colBranchcode'}, );
539 }
540
541 =head2 isItemInThisCollection
542
543   $inCollection = isItemInThisCollection( $itemnumber, $colId );
544
545 =cut
546
547 sub isItemInThisCollection {
548     my ( $itemnumber, $colId ) = @_;
549
550     my $dbh = C4::Context->dbh;
551
552     my $sth = $dbh->prepare(
553 "SELECT COUNT(*) as inCollection FROM collections_tracking WHERE itemnumber = ? AND colId = ?"
554     );
555     $sth->execute( $itemnumber, $colId ) or return (0);
556
557     my $row = $sth->fetchrow_hashref;
558
559     return $$row{'inCollection'};
560 }
561
562 =head2 isItemInAnyCollection
563
564   my $inCollection = isItemInAnyCollection( $itemnumber );
565
566 =cut
567
568 sub isItemInAnyCollection {
569     my ($itemnumber) = @_;
570
571     my $dbh = C4::Context->dbh;
572
573     my $sth = $dbh->prepare(
574         "SELECT itemnumber FROM collections_tracking JOIN collections USING (colId) WHERE itemnumber = ?");
575     $sth->execute($itemnumber) or return (0);
576
577     my $row = $sth->fetchrow_hashref;
578
579     $itemnumber = $row->{itemnumber};
580     if ($itemnumber) {
581         return 1;
582     }
583     else {
584         return 0;
585     }
586 }
587
588 1;
589
590 __END__
591
592 =head1 AUTHOR
593
594 Kyle Hall <kylemhall@gmail.com>
595
596 =cut