Bug 12502: Add vendor note, internal note, ordernumber and isbn to late orders page
[koha.git] / C4 / Serials.pm
1 package C4::Serials;
2
3 # Copyright 2000-2002 Katipo Communications
4 # Parts Copyright 2010 Biblibre
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21 use Modern::Perl;
22
23 use C4::Auth qw(haspermission);
24 use C4::Context;
25 use DateTime;
26 use Date::Calc qw(:all);
27 use POSIX qw(strftime);
28 use C4::Biblio;
29 use C4::Log;    # logaction
30 use C4::Debug;
31 use C4::Serials::Frequency;
32 use C4::Serials::Numberpattern;
33 use Koha::AdditionalFieldValues;
34 use Koha::DateUtils;
35 use Koha::Serial;
36 use Koha::Subscriptions;
37 use Koha::Subscription::Histories;
38 use Koha::SharedContent;
39
40 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
41
42 # Define statuses
43 use constant {
44     EXPECTED               => 1,
45     ARRIVED                => 2,
46     LATE                   => 3,
47     MISSING                => 4,
48     MISSING_NEVER_RECIEVED => 41,
49     MISSING_SOLD_OUT       => 42,
50     MISSING_DAMAGED        => 43,
51     MISSING_LOST           => 44,
52     NOT_ISSUED             => 5,
53     DELETED                => 6,
54     CLAIMED                => 7,
55     STOPPED                => 8,
56 };
57
58 use constant MISSING_STATUSES => (
59     MISSING,          MISSING_NEVER_RECIEVED,
60     MISSING_SOLD_OUT, MISSING_DAMAGED,
61     MISSING_LOST
62 );
63
64 BEGIN {
65     require Exporter;
66     @ISA    = qw(Exporter);
67     @EXPORT = qw(
68       &NewSubscription    &ModSubscription    &DelSubscription
69       &GetSubscription    &CountSubscriptionFromBiblionumber      &GetSubscriptionsFromBiblionumber
70       &SearchSubscriptions
71       &GetFullSubscriptionsFromBiblionumber   &GetFullSubscription &ModSubscriptionHistory
72       &HasSubscriptionStrictlyExpired &HasSubscriptionExpired &GetExpirationDate &abouttoexpire
73       &GetSubscriptionHistoryFromSubscriptionId
74
75       &GetNextSeq &GetSeq &NewIssue           &GetSerials
76       &GetLatestSerials   &ModSerialStatus    &GetNextDate       &GetSerials2
77       &ReNewSubscription  &GetLateOrMissingIssues
78       &GetSerialInformation                   &AddItem2Serial
79       &PrepareSerialsData &GetNextExpected    &ModNextExpected
80       &GetPreviousSerialid
81
82       &GetSuppliersWithLateIssues
83       &getroutinglist     &delroutingmember   &addroutingmember
84       &reorder_members
85       &check_routing &updateClaim
86       &CountIssues
87       HasItems
88       &subscriptionCurrentlyOnOrder
89
90     );
91 }
92
93 =head1 NAME
94
95 C4::Serials - Serials Module Functions
96
97 =head1 SYNOPSIS
98
99   use C4::Serials;
100
101 =head1 DESCRIPTION
102
103 Functions for handling subscriptions, claims routing etc.
104
105
106 =head1 SUBROUTINES
107
108 =head2 GetSuppliersWithLateIssues
109
110 $supplierlist = GetSuppliersWithLateIssues()
111
112 this function get all suppliers with late issues.
113
114 return :
115 an array_ref of suppliers each entry is a hash_ref containing id and name
116 the array is in name order
117
118 =cut
119
120 sub GetSuppliersWithLateIssues {
121     my $dbh   = C4::Context->dbh;
122     my $statuses = join(',', ( LATE, MISSING_STATUSES, CLAIMED ) );
123     my $query = qq|
124     SELECT DISTINCT id, name
125     FROM            subscription
126     LEFT JOIN       serial ON serial.subscriptionid=subscription.subscriptionid
127     LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
128     WHERE id > 0
129         AND (
130             (planneddate < now() AND serial.status=1)
131             OR serial.STATUS IN ( $statuses )
132         )
133         AND subscription.closed = 0
134     ORDER BY name|;
135     return $dbh->selectall_arrayref($query, { Slice => {} });
136 }
137
138 =head2 GetSubscriptionHistoryFromSubscriptionId
139
140 $history = GetSubscriptionHistoryFromSubscriptionId($subscriptionid);
141
142 This function returns the subscription history as a hashref
143
144 =cut
145
146 sub GetSubscriptionHistoryFromSubscriptionId {
147     my ($subscriptionid) = @_;
148
149     return unless $subscriptionid;
150
151     my $dbh   = C4::Context->dbh;
152     my $query = qq|
153         SELECT *
154         FROM   subscriptionhistory
155         WHERE  subscriptionid = ?
156     |;
157     my $sth = $dbh->prepare($query);
158     $sth->execute($subscriptionid);
159     my $results = $sth->fetchrow_hashref;
160     $sth->finish;
161
162     return $results;
163 }
164
165 =head2 GetSerialInformation
166
167 $data = GetSerialInformation($serialid);
168 returns a hash_ref containing :
169   items : items marcrecord (can be an array)
170   serial table field
171   subscription table field
172   + information about subscription expiration
173
174 =cut
175
176 sub GetSerialInformation {
177     my ($serialid) = @_;
178     my $dbh        = C4::Context->dbh;
179     my $query      = qq|
180         SELECT serial.*, serial.notes as sernotes, serial.status as serstatus,subscription.*,subscription.subscriptionid as subsid
181         FROM   serial LEFT JOIN subscription ON subscription.subscriptionid=serial.subscriptionid
182         WHERE  serialid = ?
183     |;
184     my $rq = $dbh->prepare($query);
185     $rq->execute($serialid);
186     my $data = $rq->fetchrow_hashref;
187
188     # create item information if we have serialsadditems for this subscription
189     if ( $data->{'serialsadditems'} ) {
190         my $queryitem = $dbh->prepare("SELECT itemnumber from serialitems where serialid=?");
191         $queryitem->execute($serialid);
192         my $itemnumbers = $queryitem->fetchall_arrayref( [0] );
193         require C4::Items;
194         if ( scalar(@$itemnumbers) > 0 ) {
195             foreach my $itemnum (@$itemnumbers) {
196
197                 #It is ASSUMED that GetMarcItem ALWAYS WORK...
198                 #Maybe GetMarcItem should return values on failure
199                 $debug and warn "itemnumber :$itemnum->[0], bibnum :" . $data->{'biblionumber'};
200                 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, $itemnum->[0], $data );
201                 $itemprocessed->{'itemnumber'}   = $itemnum->[0];
202                 $itemprocessed->{'itemid'}       = $itemnum->[0];
203                 $itemprocessed->{'serialid'}     = $serialid;
204                 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
205                 push @{ $data->{'items'} }, $itemprocessed;
206             }
207         } else {
208             my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, '', $data );
209             $itemprocessed->{'itemid'}       = "N$serialid";
210             $itemprocessed->{'serialid'}     = $serialid;
211             $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
212             $itemprocessed->{'countitems'}   = 0;
213             push @{ $data->{'items'} }, $itemprocessed;
214         }
215     }
216     $data->{ "status" . $data->{'serstatus'} } = 1;
217     $data->{'subscriptionexpired'} = HasSubscriptionExpired( $data->{'subscriptionid'} ) && $data->{'status'} == 1;
218     $data->{'abouttoexpire'} = abouttoexpire( $data->{'subscriptionid'} );
219     $data->{cannotedit} = not can_edit_subscription( $data );
220     return $data;
221 }
222
223 =head2 AddItem2Serial
224
225 $rows = AddItem2Serial($serialid,$itemnumber);
226 Adds an itemnumber to Serial record
227 returns the number of rows affected
228
229 =cut
230
231 sub AddItem2Serial {
232     my ( $serialid, $itemnumber ) = @_;
233
234     return unless ($serialid and $itemnumber);
235
236     my $dbh = C4::Context->dbh;
237     my $rq  = $dbh->prepare("INSERT INTO `serialitems` SET serialid=? , itemnumber=?");
238     $rq->execute( $serialid, $itemnumber );
239     return $rq->rows;
240 }
241
242 =head2 GetSubscription
243
244 $subs = GetSubscription($subscriptionid)
245 this function returns the subscription which has $subscriptionid as id.
246 return :
247 a hashref. This hash contains
248 subscription, subscriptionhistory, aqbooksellers.name, biblio.title
249
250 =cut
251
252 sub GetSubscription {
253     my ($subscriptionid) = @_;
254     my $dbh              = C4::Context->dbh;
255     my $query            = qq(
256         SELECT  subscription.*,
257                 subscriptionhistory.*,
258                 aqbooksellers.name AS aqbooksellername,
259                 biblio.title AS bibliotitle,
260                 subscription.biblionumber as bibnum
261        FROM subscription
262        LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
263        LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
264        LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
265        WHERE subscription.subscriptionid = ?
266     );
267
268     $debug and warn "query : $query\nsubsid :$subscriptionid";
269     my $sth = $dbh->prepare($query);
270     $sth->execute($subscriptionid);
271     my $subscription = $sth->fetchrow_hashref;
272
273     return unless $subscription;
274
275     $subscription->{cannotedit} = not can_edit_subscription( $subscription );
276
277     if ( my $mana_id = $subscription->{mana_id} ) {
278         my $mana_subscription = Koha::SharedContent::get_entity_by_id(
279             'subscription', $mana_id, {usecomments => 1});
280         $subscription->{comments} = $mana_subscription->{data}->{comments};
281     }
282
283     return $subscription;
284 }
285
286 =head2 GetFullSubscription
287
288    $array_ref = GetFullSubscription($subscriptionid)
289    this function reads the serial table.
290
291 =cut
292
293 sub GetFullSubscription {
294     my ($subscriptionid) = @_;
295
296     return unless ($subscriptionid);
297
298     my $dbh              = C4::Context->dbh;
299     my $query            = qq|
300   SELECT    serial.serialid,
301             serial.serialseq,
302             serial.planneddate, 
303             serial.publisheddate, 
304             serial.publisheddatetext,
305             serial.status, 
306             serial.notes as notes,
307             year(IF(serial.publisheddate IS NULL,serial.publisheddate,serial.planneddate)) as year,
308             aqbooksellers.name as aqbooksellername,
309             biblio.title as bibliotitle,
310             subscription.branchcode AS branchcode,
311             subscription.subscriptionid AS subscriptionid
312   FROM      serial 
313   LEFT JOIN subscription ON 
314           (serial.subscriptionid=subscription.subscriptionid )
315   LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id 
316   LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber 
317   WHERE     serial.subscriptionid = ? 
318   ORDER BY year DESC,
319           IF(serial.publisheddate IS NULL,serial.publisheddate,serial.planneddate) DESC,
320           serial.subscriptionid
321           |;
322     $debug and warn "GetFullSubscription query: $query";
323     my $sth = $dbh->prepare($query);
324     $sth->execute($subscriptionid);
325     my $subscriptions = $sth->fetchall_arrayref( {} );
326     my $cannotedit = not can_edit_subscription( $subscriptions->[0] ) if scalar @$subscriptions;
327     for my $subscription ( @$subscriptions ) {
328         $subscription->{cannotedit} = $cannotedit;
329     }
330     return $subscriptions;
331 }
332
333 =head2 PrepareSerialsData
334
335    $array_ref = PrepareSerialsData($serialinfomation)
336    where serialinformation is a hashref array
337
338 =cut
339
340 sub PrepareSerialsData {
341     my ($lines) = @_;
342
343     return unless ($lines);
344
345     my %tmpresults;
346     my $year;
347     my @res;
348     my $startdate;
349     my $aqbooksellername;
350     my $bibliotitle;
351     my @loopissues;
352     my $first;
353     my $previousnote = "";
354
355     foreach my $subs (@{$lines}) {
356         for my $datefield ( qw(publisheddate planneddate) ) {
357             # handle 0000-00-00 dates
358             if (defined $subs->{$datefield} and $subs->{$datefield} =~ m/^00/) {
359                 $subs->{$datefield} = undef;
360             }
361         }
362         $subs->{ "status" . $subs->{'status'} } = 1;
363         if ( grep { $_ == $subs->{status} } ( EXPECTED, LATE, MISSING_STATUSES, CLAIMED ) ) {
364             $subs->{"checked"} = 1;
365         }
366
367         if ( $subs->{'year'} && $subs->{'year'} ne "" ) {
368             $year = $subs->{'year'};
369         } else {
370             $year = "manage";
371         }
372         if ( $tmpresults{$year} ) {
373             push @{ $tmpresults{$year}->{'serials'} }, $subs;
374         } else {
375             $tmpresults{$year} = {
376                 'year'             => $year,
377                 'aqbooksellername' => $subs->{'aqbooksellername'},
378                 'bibliotitle'      => $subs->{'bibliotitle'},
379                 'serials'          => [$subs],
380                 'first'            => $first,
381             };
382         }
383     }
384     foreach my $key ( sort { $b cmp $a } keys %tmpresults ) {
385         push @res, $tmpresults{$key};
386     }
387     return \@res;
388 }
389
390 =head2 GetSubscriptionsFromBiblionumber
391
392 $array_ref = GetSubscriptionsFromBiblionumber($biblionumber)
393 this function get the subscription list. it reads the subscription table.
394 return :
395 reference to an array of subscriptions which have the biblionumber given on input arg.
396 each element of this array is a hashref containing
397 startdate, histstartdate,opacnote,missinglist,recievedlist,periodicity,status & enddate
398
399 =cut
400
401 sub GetSubscriptionsFromBiblionumber {
402     my ($biblionumber) = @_;
403
404     return unless ($biblionumber);
405
406     my $dbh            = C4::Context->dbh;
407     my $query          = qq(
408         SELECT subscription.*,
409                branches.branchname,
410                subscriptionhistory.*,
411                aqbooksellers.name AS aqbooksellername,
412                biblio.title AS bibliotitle
413        FROM subscription
414        LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
415        LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
416        LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
417        LEFT JOIN branches ON branches.branchcode=subscription.branchcode
418        WHERE subscription.biblionumber = ?
419     );
420     my $sth = $dbh->prepare($query);
421     $sth->execute($biblionumber);
422     my @res;
423     while ( my $subs = $sth->fetchrow_hashref ) {
424         $subs->{startdate}     = output_pref( { dt => dt_from_string( $subs->{startdate} ),     dateonly => 1 } );
425         $subs->{histstartdate} = output_pref( { dt => dt_from_string( $subs->{histstartdate} ), dateonly => 1 } );
426         if ( defined $subs->{histenddate} ) {
427            $subs->{histenddate}   = output_pref( { dt => dt_from_string( $subs->{histenddate} ),   dateonly => 1 } );
428         } else {
429             $subs->{histenddate} = "";
430         }
431         $subs->{opacnote}     //= "";
432         $subs->{ "periodicity" . $subs->{periodicity} }     = 1;
433         $subs->{ "numberpattern" . $subs->{numberpattern} } = 1;
434         $subs->{ "status" . $subs->{'status'} }             = 1;
435
436         if (not defined $subs->{enddate} ) {
437             $subs->{enddate} = '';
438         } else {
439             $subs->{enddate} = output_pref( { dt => dt_from_string( $subs->{enddate}), dateonly => 1 } );
440         }
441         $subs->{'abouttoexpire'}       = abouttoexpire( $subs->{'subscriptionid'} );
442         $subs->{'subscriptionexpired'} = HasSubscriptionExpired( $subs->{'subscriptionid'} );
443         $subs->{cannotedit} = not can_edit_subscription( $subs );
444         push @res, $subs;
445     }
446     return \@res;
447 }
448
449 =head2 GetFullSubscriptionsFromBiblionumber
450
451    $array_ref = GetFullSubscriptionsFromBiblionumber($biblionumber)
452    this function reads the serial table.
453
454 =cut
455
456 sub GetFullSubscriptionsFromBiblionumber {
457     my ($biblionumber) = @_;
458     my $dbh            = C4::Context->dbh;
459     my $query          = qq|
460   SELECT    serial.serialid,
461             serial.serialseq,
462             serial.planneddate, 
463             serial.publisheddate, 
464             serial.publisheddatetext,
465             serial.status, 
466             serial.notes as notes,
467             year(IF(serial.publisheddate IS NULL,serial.publisheddate,serial.planneddate)) as year,
468             biblio.title as bibliotitle,
469             subscription.branchcode AS branchcode,
470             subscription.subscriptionid AS subscriptionid
471   FROM      serial 
472   LEFT JOIN subscription ON 
473           (serial.subscriptionid=subscription.subscriptionid)
474   LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id 
475   LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber 
476   WHERE     subscription.biblionumber = ? 
477   ORDER BY year DESC,
478           IF(serial.publisheddate IS NULL,serial.publisheddate,serial.planneddate) DESC,
479           serial.subscriptionid
480           |;
481     my $sth = $dbh->prepare($query);
482     $sth->execute($biblionumber);
483     my $subscriptions = $sth->fetchall_arrayref( {} );
484     my $cannotedit = not can_edit_subscription( $subscriptions->[0] ) if scalar @$subscriptions;
485     for my $subscription ( @$subscriptions ) {
486         $subscription->{cannotedit} = $cannotedit;
487     }
488     return $subscriptions;
489 }
490
491 =head2 SearchSubscriptions
492
493   @results = SearchSubscriptions($args);
494
495 This function returns a list of hashrefs, one for each subscription
496 that meets the conditions specified by the $args hashref.
497
498 The valid search fields are:
499
500   biblionumber
501   title
502   issn
503   ean
504   callnumber
505   location
506   publisher
507   bookseller
508   branch
509   expiration_date
510   closed
511
512 The expiration_date search field is special; it specifies the maximum
513 subscription expiration date.
514
515 =cut
516
517 sub SearchSubscriptions {
518     my ( $args ) = @_;
519
520     my $additional_fields = $args->{additional_fields} // [];
521     my $matching_record_ids_for_additional_fields = [];
522     if ( @$additional_fields ) {
523         my @subscriptions = Koha::Subscriptions->filter_by_additional_fields($additional_fields);
524
525         return () unless @subscriptions;
526
527         $matching_record_ids_for_additional_fields = [ map {
528             $_->subscriptionid
529         } @subscriptions ];
530     }
531
532     my $query = q|
533         SELECT
534             subscription.notes AS publicnotes,
535             subscriptionhistory.*,
536             subscription.*,
537             biblio.notes AS biblionotes,
538             biblio.title,
539             biblio.author,
540             biblio.biblionumber,
541             aqbooksellers.name AS vendorname,
542             biblioitems.issn
543         FROM subscription
544             LEFT JOIN subscriptionhistory USING(subscriptionid)
545             LEFT JOIN biblio ON biblio.biblionumber = subscription.biblionumber
546             LEFT JOIN biblioitems ON biblioitems.biblionumber = subscription.biblionumber
547             LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
548     |;
549     $query .= q| WHERE 1|;
550     my @where_strs;
551     my @where_args;
552     if( $args->{biblionumber} ) {
553         push @where_strs, "biblio.biblionumber = ?";
554         push @where_args, $args->{biblionumber};
555     }
556
557     if( $args->{title} ){
558         my @words = split / /, $args->{title};
559         my (@strs, @args);
560         foreach my $word (@words) {
561             push @strs, "biblio.title LIKE ?";
562             push @args, "%$word%";
563         }
564         if (@strs) {
565             push @where_strs, '(' . join (' AND ', @strs) . ')';
566             push @where_args, @args;
567         }
568     }
569     if( $args->{issn} ){
570         push @where_strs, "biblioitems.issn LIKE ?";
571         push @where_args, "%$args->{issn}%";
572     }
573     if( $args->{ean} ){
574         push @where_strs, "biblioitems.ean LIKE ?";
575         push @where_args, "%$args->{ean}%";
576     }
577     if ( $args->{callnumber} ) {
578         push @where_strs, "subscription.callnumber LIKE ?";
579         push @where_args, "%$args->{callnumber}%";
580     }
581     if( $args->{publisher} ){
582         push @where_strs, "biblioitems.publishercode LIKE ?";
583         push @where_args, "%$args->{publisher}%";
584     }
585     if( $args->{bookseller} ){
586         push @where_strs, "aqbooksellers.name LIKE ?";
587         push @where_args, "%$args->{bookseller}%";
588     }
589     if( $args->{branch} ){
590         push @where_strs, "subscription.branchcode = ?";
591         push @where_args, "$args->{branch}";
592     }
593     if ( $args->{location} ) {
594         push @where_strs, "subscription.location = ?";
595         push @where_args, "$args->{location}";
596     }
597     if ( $args->{expiration_date} ) {
598         push @where_strs, "subscription.enddate <= ?";
599         push @where_args, "$args->{expiration_date}";
600     }
601     if( defined $args->{closed} ){
602         push @where_strs, "subscription.closed = ?";
603         push @where_args, "$args->{closed}";
604     }
605
606     if(@where_strs){
607         $query .= ' AND ' . join(' AND ', @where_strs);
608     }
609     if ( @$additional_fields ) {
610         $query .= ' AND subscriptionid IN ('
611             . join( ', ', @$matching_record_ids_for_additional_fields )
612         . ')';
613     }
614
615     $query .= " ORDER BY " . $args->{orderby} if $args->{orderby};
616
617     my $dbh = C4::Context->dbh;
618     my $sth = $dbh->prepare($query);
619     $sth->execute(@where_args);
620     my $results =  $sth->fetchall_arrayref( {} );
621
622     for my $subscription ( @$results ) {
623         $subscription->{cannotedit} = not can_edit_subscription( $subscription );
624         $subscription->{cannotdisplay} = not can_show_subscription( $subscription );
625
626         my $subscription_object = Koha::Subscriptions->find($subscription->{subscriptionid});
627         $subscription->{additional_fields} = { map { $_->field->name => $_->value }
628             $subscription_object->additional_field_values->as_list };
629
630     }
631
632     return @$results;
633 }
634
635
636 =head2 GetSerials
637
638 ($totalissues,@serials) = GetSerials($subscriptionid);
639 this function gets every serial not arrived for a given subscription
640 as well as the number of issues registered in the database (all types)
641 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
642
643 FIXME: We should return \@serials.
644
645 =cut
646
647 sub GetSerials {
648     my ( $subscriptionid, $count ) = @_;
649
650     return unless $subscriptionid;
651
652     my $dbh = C4::Context->dbh;
653
654     # status = 2 is "arrived"
655     my $counter = 0;
656     $count = 5 unless ($count);
657     my @serials;
658     my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES, NOT_ISSUED ) );
659     my $query = "SELECT serialid,serialseq, status, publisheddate,
660         publisheddatetext, planneddate,notes, routingnotes
661                         FROM   serial
662                         WHERE  subscriptionid = ? AND status NOT IN ( $statuses )
663                         ORDER BY IF(publisheddate IS NULL,planneddate,publisheddate) DESC";
664     my $sth = $dbh->prepare($query);
665     $sth->execute($subscriptionid);
666
667     while ( my $line = $sth->fetchrow_hashref ) {
668         $line->{ "status" . $line->{status} } = 1;                                         # fills a "statusX" value, used for template status select list
669         for my $datefield ( qw( planneddate publisheddate) ) {
670             if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
671                 $line->{$datefield} =  output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
672             } else {
673                 $line->{$datefield} = q{};
674             }
675         }
676         push @serials, $line;
677     }
678
679     # OK, now add the last 5 issues arrives/missing
680     $query = "SELECT   serialid,serialseq, status, planneddate, publisheddate,
681         publisheddatetext, notes, routingnotes
682        FROM     serial
683        WHERE    subscriptionid = ?
684        AND      status IN ( $statuses )
685        ORDER BY IF(publisheddate IS NULL,planneddate,publisheddate) DESC
686       ";
687     $sth = $dbh->prepare($query);
688     $sth->execute($subscriptionid);
689     while ( ( my $line = $sth->fetchrow_hashref ) && $counter < $count ) {
690         $counter++;
691         $line->{ "status" . $line->{status} } = 1;                                         # fills a "statusX" value, used for template status select list
692         for my $datefield ( qw( planneddate publisheddate) ) {
693             if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
694                 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
695             } else {
696                 $line->{$datefield} = q{};
697             }
698         }
699
700         push @serials, $line;
701     }
702
703     $query = "SELECT count(*) FROM serial WHERE subscriptionid=?";
704     $sth   = $dbh->prepare($query);
705     $sth->execute($subscriptionid);
706     my ($totalissues) = $sth->fetchrow;
707     return ( $totalissues, @serials );
708 }
709
710 =head2 GetSerials2
711
712 @serials = GetSerials2($subscriptionid,$statuses);
713 this function returns every serial waited for a given subscription
714 as well as the number of issues registered in the database (all types)
715 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
716
717 $statuses is an arrayref of statuses and is mandatory.
718
719 =cut
720
721 sub GetSerials2 {
722     my ( $subscription, $statuses ) = @_;
723
724     return unless ($subscription and @$statuses);
725
726     my $dbh   = C4::Context->dbh;
727     my $query = q|
728                  SELECT serialid,serialseq, status, planneddate, publisheddate,
729                     publisheddatetext, notes, routingnotes
730                  FROM     serial 
731                  WHERE    subscriptionid=?
732             |
733             . q| AND status IN (| . join( ",", ('?') x @$statuses ) . q|)|
734             . q|
735                  ORDER BY publisheddate,serialid DESC
736     |;
737     $debug and warn "GetSerials2 query: $query";
738     my $sth = $dbh->prepare($query);
739     $sth->execute( $subscription, @$statuses );
740     my @serials;
741
742     while ( my $line = $sth->fetchrow_hashref ) {
743         $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
744         # Format dates for display
745         for my $datefield ( qw( planneddate publisheddate ) ) {
746             if (!defined($line->{$datefield}) || $line->{$datefield} =~m/^00/) {
747                 $line->{$datefield} = q{};
748             }
749             else {
750                 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
751             }
752         }
753         push @serials, $line;
754     }
755     return @serials;
756 }
757
758 =head2 GetLatestSerials
759
760 \@serials = GetLatestSerials($subscriptionid,$limit)
761 get the $limit's latest serials arrived or missing for a given subscription
762 return :
763 a ref to an array which contains all of the latest serials stored into a hash.
764
765 =cut
766
767 sub GetLatestSerials {
768     my ( $subscriptionid, $limit ) = @_;
769
770     return unless ($subscriptionid and $limit);
771
772     my $dbh = C4::Context->dbh;
773
774     my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES ) );
775     my $strsth = "SELECT   serialid,serialseq, status, planneddate, publisheddate, notes
776                         FROM     serial
777                         WHERE    subscriptionid = ?
778                         AND      status IN ($statuses)
779                         ORDER BY publisheddate DESC LIMIT 0,$limit
780                 ";
781     my $sth = $dbh->prepare($strsth);
782     $sth->execute($subscriptionid);
783     my @serials;
784     while ( my $line = $sth->fetchrow_hashref ) {
785         $line->{ "status" . $line->{status} } = 1;                        # fills a "statusX" value, used for template status select list
786         push @serials, $line;
787     }
788
789     return \@serials;
790 }
791
792 =head2 GetPreviousSerialid
793
794 $serialid = GetPreviousSerialid($subscriptionid, $nth)
795 get the $nth's previous serial for the given subscriptionid
796 return :
797 the serialid
798
799 =cut
800
801 sub GetPreviousSerialid {
802     my ( $subscriptionid, $nth ) = @_;
803     $nth ||= 1;
804     my $dbh = C4::Context->dbh;
805     my $return = undef;
806
807     # Status 2: Arrived
808     my $strsth = "SELECT   serialid
809                         FROM     serial
810                         WHERE    subscriptionid = ?
811                         AND      status = 2
812                         ORDER BY serialid DESC LIMIT $nth,1
813                 ";
814     my $sth = $dbh->prepare($strsth);
815     $sth->execute($subscriptionid);
816     my @serials;
817     my $line = $sth->fetchrow_hashref;
818     $return = $line->{'serialid'} if ($line);
819
820     return $return;
821 }
822
823 =head2 GetNextSeq
824
825     my (
826         $nextseq,       $newlastvalue1, $newlastvalue2, $newlastvalue3,
827         $newinnerloop1, $newinnerloop2, $newinnerloop3
828     ) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate );
829
830 $subscription is a hashref containing all the attributes of the table
831 'subscription'.
832 $pattern is a hashref containing all the attributes of the table
833 'subscription_numberpatterns'.
834 $frequency is a hashref containing all the attributes of the table 'subscription_frequencies'
835 $planneddate is a date string in iso format.
836 This function get the next issue for the subscription given on input arg
837
838 =cut
839
840 sub GetNextSeq {
841     my ($subscription, $pattern, $frequency, $planneddate) = @_;
842
843     return unless ($subscription and $pattern);
844
845     my ( $newlastvalue1, $newlastvalue2, $newlastvalue3,
846     $newinnerloop1, $newinnerloop2, $newinnerloop3 );
847     my $count = 1;
848
849     if ($subscription->{'skip_serialseq'}) {
850         my @irreg = split /;/, $subscription->{'irregularity'};
851         if(@irreg > 0) {
852             my $irregularities = {};
853             $irregularities->{$_} = 1 foreach(@irreg);
854             my $issueno = GetFictiveIssueNumber($subscription, $planneddate, $frequency) + 1;
855             while($irregularities->{$issueno}) {
856                 $count++;
857                 $issueno++;
858             }
859         }
860     }
861
862     my $numberingmethod = $pattern->{numberingmethod};
863     my $calculated = "";
864     if ($numberingmethod) {
865         $calculated    = $numberingmethod;
866         my $locale = $subscription->{locale};
867         $newlastvalue1 = $subscription->{lastvalue1} || 0;
868         $newlastvalue2 = $subscription->{lastvalue2} || 0;
869         $newlastvalue3 = $subscription->{lastvalue3} || 0;
870         $newinnerloop1 = $subscription->{innerloop1} || 0;
871         $newinnerloop2 = $subscription->{innerloop2} || 0;
872         $newinnerloop3 = $subscription->{innerloop3} || 0;
873         my %calc;
874         foreach(qw/X Y Z/) {
875             $calc{$_} = 1 if ($numberingmethod =~ /\{$_\}/);
876         }
877
878         for(my $i = 0; $i < $count; $i++) {
879             if($calc{'X'}) {
880                 # check if we have to increase the new value.
881                 $newinnerloop1 += 1;
882                 if ($newinnerloop1 >= $pattern->{every1}) {
883                     $newinnerloop1  = 0;
884                     $newlastvalue1 += $pattern->{add1};
885                 }
886                 # reset counter if needed.
887                 $newlastvalue1 = $pattern->{setto1} if ($newlastvalue1 > $pattern->{whenmorethan1});
888             }
889             if($calc{'Y'}) {
890                 # check if we have to increase the new value.
891                 $newinnerloop2 += 1;
892                 if ($newinnerloop2 >= $pattern->{every2}) {
893                     $newinnerloop2  = 0;
894                     $newlastvalue2 += $pattern->{add2};
895                 }
896                 # reset counter if needed.
897                 $newlastvalue2 = $pattern->{setto2} if ($newlastvalue2 > $pattern->{whenmorethan2});
898             }
899             if($calc{'Z'}) {
900                 # check if we have to increase the new value.
901                 $newinnerloop3 += 1;
902                 if ($newinnerloop3 >= $pattern->{every3}) {
903                     $newinnerloop3  = 0;
904                     $newlastvalue3 += $pattern->{add3};
905                 }
906                 # reset counter if needed.
907                 $newlastvalue3 = $pattern->{setto3} if ($newlastvalue3 > $pattern->{whenmorethan3});
908             }
909         }
910         if($calc{'X'}) {
911             my $newlastvalue1string = _numeration( $newlastvalue1, $pattern->{numbering1}, $locale );
912             $calculated =~ s/\{X\}/$newlastvalue1string/g;
913         }
914         if($calc{'Y'}) {
915             my $newlastvalue2string = _numeration( $newlastvalue2, $pattern->{numbering2}, $locale );
916             $calculated =~ s/\{Y\}/$newlastvalue2string/g;
917         }
918         if($calc{'Z'}) {
919             my $newlastvalue3string = _numeration( $newlastvalue3, $pattern->{numbering3}, $locale );
920             $calculated =~ s/\{Z\}/$newlastvalue3string/g;
921         }
922     }
923
924     return ($calculated,
925             $newlastvalue1, $newlastvalue2, $newlastvalue3,
926             $newinnerloop1, $newinnerloop2, $newinnerloop3);
927 }
928
929 =head2 GetSeq
930
931 $calculated = GetSeq($subscription, $pattern)
932 $subscription is a hashref containing all the attributes of the table 'subscription'
933 $pattern is a hashref containing all the attributes of the table 'subscription_numberpatterns'
934 this function transforms {X},{Y},{Z} to 150,0,0 for example.
935 return:
936 the sequence in string format
937
938 =cut
939
940 sub GetSeq {
941     my ($subscription, $pattern) = @_;
942
943     return unless ($subscription and $pattern);
944
945     my $locale = $subscription->{locale};
946
947     my $calculated = $pattern->{numberingmethod};
948
949     my $newlastvalue1 = $subscription->{'lastvalue1'} || 0;
950     $newlastvalue1 = _numeration($newlastvalue1, $pattern->{numbering1}, $locale) if ($pattern->{numbering1}); # reset counter if needed.
951     $calculated =~ s/\{X\}/$newlastvalue1/g;
952
953     my $newlastvalue2 = $subscription->{'lastvalue2'} || 0;
954     $newlastvalue2 = _numeration($newlastvalue2, $pattern->{numbering2}, $locale) if ($pattern->{numbering2}); # reset counter if needed.
955     $calculated =~ s/\{Y\}/$newlastvalue2/g;
956
957     my $newlastvalue3 = $subscription->{'lastvalue3'} || 0;
958     $newlastvalue3 = _numeration($newlastvalue3, $pattern->{numbering3}, $locale) if ($pattern->{numbering3}); # reset counter if needed.
959     $calculated =~ s/\{Z\}/$newlastvalue3/g;
960     return $calculated;
961 }
962
963 =head2 GetExpirationDate
964
965 $enddate = GetExpirationDate($subscriptionid, [$startdate])
966
967 this function return the next expiration date for a subscription given on input args.
968
969 return
970 the enddate or undef
971
972 =cut
973
974 sub GetExpirationDate {
975     my ( $subscriptionid, $startdate ) = @_;
976
977     return unless ($subscriptionid);
978
979     my $dbh          = C4::Context->dbh;
980     my $subscription = GetSubscription($subscriptionid);
981     my $enddate;
982
983     # we don't do the same test if the subscription is based on X numbers or on X weeks/months
984     $enddate = $startdate || $subscription->{startdate};
985     my @date = split( /-/, $enddate );
986
987     return if ( scalar(@date) != 3 || not check_date(@date) );
988
989     my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
990     if ( $frequency and $frequency->{unit} ) {
991
992         # If Not Irregular
993         if ( my $length = $subscription->{numberlength} ) {
994
995             #calculate the date of the last issue.
996             for ( my $i = 1 ; $i <= $length ; $i++ ) {
997                 $enddate = GetNextDate( $subscription, $enddate, $frequency );
998             }
999         } elsif ( $subscription->{monthlength} ) {
1000             if ( $$subscription{startdate} ) {
1001                 my @enddate = Add_Delta_YM( $date[0], $date[1], $date[2], 0, $subscription->{monthlength} );
1002                 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1003             }
1004         } elsif ( $subscription->{weeklength} ) {
1005             if ( $$subscription{startdate} ) {
1006                 my @date = split( /-/, $subscription->{startdate} );
1007                 my @enddate = Add_Delta_Days( $date[0], $date[1], $date[2], $subscription->{weeklength} * 7 );
1008                 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1009             }
1010         } else {
1011             $enddate = $subscription->{enddate};
1012         }
1013         return $enddate;
1014     } else {
1015         return $subscription->{enddate};
1016     }
1017 }
1018
1019 =head2 CountSubscriptionFromBiblionumber
1020
1021 $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber)
1022 this returns a count of the subscriptions for a given biblionumber
1023 return :
1024 the number of subscriptions
1025
1026 =cut
1027
1028 sub CountSubscriptionFromBiblionumber {
1029     my ($biblionumber) = @_;
1030
1031     return unless ($biblionumber);
1032
1033     my $dbh            = C4::Context->dbh;
1034     my $query          = "SELECT count(*) FROM subscription WHERE biblionumber=?";
1035     my $sth            = $dbh->prepare($query);
1036     $sth->execute($biblionumber);
1037     my $subscriptionsnumber = $sth->fetchrow;
1038     return $subscriptionsnumber;
1039 }
1040
1041 =head2 ModSubscriptionHistory
1042
1043 ModSubscriptionHistory($subscriptionid,$histstartdate,$enddate,$recievedlist,$missinglist,$opacnote,$librariannote);
1044
1045 this function modifies the history of a subscription. Put your new values on input arg.
1046 returns the number of rows affected
1047
1048 =cut
1049
1050 sub ModSubscriptionHistory {
1051     my ( $subscriptionid, $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote ) = @_;
1052
1053     return unless ($subscriptionid);
1054
1055     my $dbh   = C4::Context->dbh;
1056     my $query = "UPDATE subscriptionhistory 
1057                     SET histstartdate=?,histenddate=?,recievedlist=?,missinglist=?,opacnote=?,librariannote=?
1058                     WHERE subscriptionid=?
1059                 ";
1060     my $sth = $dbh->prepare($query);
1061     $receivedlist =~ s/^; // if $receivedlist;
1062     $missinglist  =~ s/^; // if $missinglist;
1063     $opacnote     =~ s/^; // if $opacnote;
1064     $sth->execute( $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote, $subscriptionid );
1065     return $sth->rows;
1066 }
1067
1068 =head2 ModSerialStatus
1069
1070     ModSerialStatus($serialid, $serialseq, $planneddate, $publisheddate,
1071         $publisheddatetext, $status, $notes);
1072
1073 This function modify the serial status. Serial status is a number.(eg 2 is "arrived")
1074 Note : if we change from "waited" to something else,then we will have to create a new "waited" entry
1075
1076 =cut
1077
1078 sub ModSerialStatus {
1079     my ($serialid, $serialseq, $planneddate, $publisheddate, $publisheddatetext,
1080         $status, $notes) = @_;
1081
1082     return unless ($serialid);
1083
1084     #It is a usual serial
1085     # 1st, get previous status :
1086     my $dbh   = C4::Context->dbh;
1087     my $query = "SELECT serial.subscriptionid,serial.status,subscription.periodicity,serial.routingnotes
1088         FROM serial, subscription
1089         WHERE serial.subscriptionid=subscription.subscriptionid
1090             AND serialid=?";
1091     my $sth   = $dbh->prepare($query);
1092     $sth->execute($serialid);
1093     my ( $subscriptionid, $oldstatus, $periodicity, $routingnotes ) = $sth->fetchrow;
1094     my $frequency = GetSubscriptionFrequency($periodicity);
1095
1096     # change status & update subscriptionhistory
1097     my $val;
1098     if ( $status == DELETED ) {
1099         DelIssue( { 'serialid' => $serialid, 'subscriptionid' => $subscriptionid, 'serialseq' => $serialseq } );
1100     } else {
1101         my $query = '
1102             UPDATE serial
1103             SET serialseq = ?, publisheddate = ?, publisheddatetext = ?,
1104                 planneddate = ?, status = ?, notes = ?, routingnotes = ?
1105             WHERE  serialid = ?
1106         ';
1107         $sth = $dbh->prepare($query);
1108         $sth->execute( $serialseq, $publisheddate, $publisheddatetext,
1109             $planneddate, $status, $notes, $routingnotes, $serialid );
1110         $query = "SELECT * FROM   subscription WHERE  subscriptionid = ?";
1111         $sth   = $dbh->prepare($query);
1112         $sth->execute($subscriptionid);
1113         my $val = $sth->fetchrow_hashref;
1114         unless ( $val->{manualhistory} ) {
1115             $query = "SELECT missinglist,recievedlist FROM subscriptionhistory WHERE  subscriptionid=?";
1116             $sth   = $dbh->prepare($query);
1117             $sth->execute($subscriptionid);
1118             my ( $missinglist, $recievedlist ) = $sth->fetchrow;
1119
1120             if ( $status == ARRIVED || ($oldstatus == ARRIVED && $status != ARRIVED) ) {
1121                 $recievedlist .= "; $serialseq"
1122                     if ($recievedlist !~ /(^|;)\s*$serialseq(?=;|$)/);
1123             }
1124
1125             # in case serial has been previously marked as missing
1126             if (grep /$status/, (EXPECTED, ARRIVED, LATE, CLAIMED)) {
1127                 $missinglist=~ s/(^|;)\s*$serialseq(?=;|$)//g;
1128             }
1129
1130             $missinglist .= "; $serialseq"
1131                 if ( ( grep { $_ == $status } ( MISSING_STATUSES ) ) && ( $missinglist !~/(^|;)\s*$serialseq(?=;|$)/ ) );
1132             $missinglist .= "; not issued $serialseq"
1133                 if ( $status == NOT_ISSUED && $missinglist !~ /(^|;)\s*$serialseq(?=;|$)/ );
1134
1135             $query = "UPDATE subscriptionhistory SET recievedlist=?, missinglist=? WHERE  subscriptionid=?";
1136             $sth   = $dbh->prepare($query);
1137             $recievedlist =~ s/^; //;
1138             $missinglist  =~ s/^; //;
1139             $sth->execute( $recievedlist, $missinglist, $subscriptionid );
1140         }
1141     }
1142
1143     # create new expected entry if needed (ie : was "expected" and has changed)
1144     my $otherIssueExpected = scalar findSerialsByStatus(EXPECTED, $subscriptionid);
1145     if ( !$otherIssueExpected && $oldstatus == EXPECTED && $status != EXPECTED ) {
1146         my $subscription = GetSubscription($subscriptionid);
1147         my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1148         my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1149
1150         # next issue number
1151         my (
1152             $newserialseq,  $newlastvalue1, $newlastvalue2, $newlastvalue3,
1153             $newinnerloop1, $newinnerloop2, $newinnerloop3
1154           )
1155           = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate );
1156
1157         # next date (calculated from actual date & frequency parameters)
1158         my $nextpublisheddate = GetNextDate($subscription, $publisheddate, $frequency, 1);
1159         my $nextpubdate = $nextpublisheddate;
1160         $query = "UPDATE subscription SET lastvalue1=?, lastvalue2=?, lastvalue3=?, innerloop1=?, innerloop2=?, innerloop3=?
1161                     WHERE  subscriptionid = ?";
1162         $sth = $dbh->prepare($query);
1163         $sth->execute( $newlastvalue1, $newlastvalue2, $newlastvalue3, $newinnerloop1, $newinnerloop2, $newinnerloop3, $subscriptionid );
1164         my $newnote = C4::Context->preference('PreserveSerialNotes') ? $notes : "";
1165         NewIssue( $newserialseq, $subscriptionid, $subscription->{'biblionumber'}, 1, $nextpubdate, $nextpubdate, undef, $newnote, $routingnotes );
1166         # check if an alert must be sent... (= a letter is defined & status became "arrived"
1167         if ( $subscription->{letter} && $status == ARRIVED && $oldstatus != ARRIVED ) {
1168             require C4::Letters;
1169             C4::Letters::SendAlerts( 'issue', $serialid, $subscription->{letter} );
1170         }
1171     }
1172
1173     return;
1174 }
1175
1176 =head2 GetNextExpected
1177
1178 $nextexpected = GetNextExpected($subscriptionid)
1179
1180 Get the planneddate for the current expected issue of the subscription.
1181
1182 returns a hashref:
1183
1184 $nextexepected = {
1185     serialid => int
1186     planneddate => ISO date
1187     }
1188
1189 =cut
1190
1191 sub GetNextExpected {
1192     my ($subscriptionid) = @_;
1193
1194     my $dbh = C4::Context->dbh;
1195     my $query = qq{
1196         SELECT *
1197         FROM serial
1198         WHERE subscriptionid = ?
1199           AND status = ?
1200         LIMIT 1
1201     };
1202     my $sth = $dbh->prepare($query);
1203
1204     # Each subscription has only one 'expected' issue.
1205     $sth->execute( $subscriptionid, EXPECTED );
1206     my $nextissue = $sth->fetchrow_hashref;
1207     if ( !$nextissue ) {
1208         $query = qq{
1209             SELECT *
1210             FROM serial
1211             WHERE subscriptionid = ?
1212             ORDER BY publisheddate DESC
1213             LIMIT 1
1214         };
1215         $sth = $dbh->prepare($query);
1216         $sth->execute($subscriptionid);
1217         $nextissue = $sth->fetchrow_hashref;
1218     }
1219     foreach(qw/planneddate publisheddate/) {
1220         if ( !defined $nextissue->{$_} ) {
1221             # or should this default to 1st Jan ???
1222             $nextissue->{$_} = strftime( '%Y-%m-%d', localtime );
1223         }
1224         $nextissue->{$_} = ($nextissue->{$_} ne '0000-00-00')
1225                          ? $nextissue->{$_}
1226                          : undef;
1227     }
1228
1229     return $nextissue;
1230 }
1231
1232 =head2 ModNextExpected
1233
1234 ModNextExpected($subscriptionid,$date)
1235
1236 Update the planneddate for the current expected issue of the subscription.
1237 This will modify all future prediction results.  
1238
1239 C<$date> is an ISO date.
1240
1241 returns 0
1242
1243 =cut
1244
1245 sub ModNextExpected {
1246     my ( $subscriptionid, $date ) = @_;
1247     my $dbh = C4::Context->dbh;
1248
1249     #FIXME: Would expect to only set planneddate, but we set both on new issue creation, so updating it here
1250     my $sth = $dbh->prepare('UPDATE serial SET planneddate=?,publisheddate=? WHERE subscriptionid=? AND status=?');
1251
1252     # Each subscription has only one 'expected' issue.
1253     $sth->execute( $date, $date, $subscriptionid, EXPECTED );
1254     return 0;
1255
1256 }
1257
1258 =head2 GetSubscriptionIrregularities
1259
1260 =over 4
1261
1262 =item @irreg = &GetSubscriptionIrregularities($subscriptionid);
1263 get the list of irregularities for a subscription
1264
1265 =back
1266
1267 =cut
1268
1269 sub GetSubscriptionIrregularities {
1270     my $subscriptionid = shift;
1271
1272     return unless $subscriptionid;
1273
1274     my $dbh = C4::Context->dbh;
1275     my $query = qq{
1276         SELECT irregularity
1277         FROM subscription
1278         WHERE subscriptionid = ?
1279     };
1280     my $sth = $dbh->prepare($query);
1281     $sth->execute($subscriptionid);
1282
1283     my ($result) = $sth->fetchrow_array;
1284     my @irreg = split /;/, $result;
1285
1286     return @irreg;
1287 }
1288
1289 =head2 ModSubscription
1290
1291 this function modifies a subscription. Put all new values on input args.
1292 returns the number of rows affected
1293
1294 =cut
1295
1296 sub ModSubscription {
1297     my (
1298     $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
1299     $periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
1300     $numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
1301     $lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
1302     $biblionumber, $callnumber, $notes, $letter, $manualhistory,
1303     $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1304     $graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
1305     $itemtype, $previousitemtype, $mana_id
1306     ) = @_;
1307
1308     my $dbh   = C4::Context->dbh;
1309     my $query = "UPDATE subscription
1310         SET librarian=?, branchcode=?, aqbooksellerid=?, cost=?, aqbudgetid=?,
1311             startdate=?, periodicity=?, firstacquidate=?, irregularity=?,
1312             numberpattern=?, locale=?, numberlength=?, weeklength=?, monthlength=?,
1313             lastvalue1=?, innerloop1=?, lastvalue2=?, innerloop2=?,
1314             lastvalue3=?, innerloop3=?, status=?, biblionumber=?,
1315             callnumber=?, notes=?, letter=?, manualhistory=?,
1316             internalnotes=?, serialsadditems=?, staffdisplaycount=?,
1317             opacdisplaycount=?, graceperiod=?, location = ?, enddate=?,
1318             skip_serialseq=?, itemtype=?, previousitemtype=?, mana_id=?
1319         WHERE subscriptionid = ?";
1320
1321     my $sth = $dbh->prepare($query);
1322     $sth->execute(
1323         $auser,           $branchcode,     $aqbooksellerid, $cost,
1324         $aqbudgetid,      $startdate,      $periodicity,    $firstacquidate,
1325         $irregularity,    $numberpattern,  $locale,         $numberlength,
1326         $weeklength,      $monthlength,    $lastvalue1,     $innerloop1,
1327         $lastvalue2,      $innerloop2,     $lastvalue3,     $innerloop3,
1328         $status,          $biblionumber,   $callnumber,     $notes,
1329         $letter,          ($manualhistory ? $manualhistory : 0),
1330         $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1331         $graceperiod,     $location,       $enddate,        $skip_serialseq,
1332         $itemtype,        $previousitemtype, $mana_id,
1333         $subscriptionid
1334     );
1335     my $rows = $sth->rows;
1336
1337     logaction( "SERIAL", "MODIFY", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1338     return $rows;
1339 }
1340
1341 =head2 NewSubscription
1342
1343 $subscriptionid = &NewSubscription($auser,branchcode,$aqbooksellerid,$cost,$aqbudgetid,$biblionumber,
1344     $startdate,$periodicity,$numberlength,$weeklength,$monthlength,
1345     $lastvalue1,$innerloop1,$lastvalue2,$innerloop2,$lastvalue3,$innerloop3,
1346     $status, $notes, $letter, $firstacquidate, $irregularity, $numberpattern,
1347     $locale, $callnumber, $manualhistory, $internalnotes, $serialsadditems,
1348     $staffdisplaycount, $opacdisplaycount, $graceperiod, $location, $enddate,
1349     $skip_serialseq, $itemtype, $previousitemtype);
1350
1351 Create a new subscription with value given on input args.
1352
1353 return :
1354 the id of this new subscription
1355
1356 =cut
1357
1358 sub NewSubscription {
1359     my (
1360     $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1361     $startdate, $periodicity, $numberlength, $weeklength, $monthlength,
1362     $lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
1363     $innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
1364     $numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
1365     $serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
1366     $location, $enddate, $skip_serialseq, $itemtype, $previousitemtype, $mana_id
1367     ) = @_;
1368     my $dbh = C4::Context->dbh;
1369
1370     my $subscription = Koha::Subscription->new(
1371         {
1372             librarian         => $auser,
1373             branchcode        => $branchcode,
1374             aqbooksellerid    => $aqbooksellerid,
1375             cost              => $cost,
1376             aqbudgetid        => $aqbudgetid,
1377             biblionumber      => $biblionumber,
1378             startdate         => $startdate,
1379             periodicity       => $periodicity,
1380             numberlength      => $numberlength,
1381             weeklength        => $weeklength,
1382             monthlength       => $monthlength,
1383             lastvalue1        => $lastvalue1,
1384             innerloop1        => $innerloop1,
1385             lastvalue2        => $lastvalue2,
1386             innerloop2        => $innerloop2,
1387             lastvalue3        => $lastvalue3,
1388             innerloop3        => $innerloop3,
1389             status            => $status,
1390             notes             => $notes,
1391             letter            => $letter,
1392             firstacquidate    => $firstacquidate,
1393             irregularity      => $irregularity,
1394             numberpattern     => $numberpattern,
1395             locale            => $locale,
1396             callnumber        => $callnumber,
1397             manualhistory     => $manualhistory,
1398             internalnotes     => $internalnotes,
1399             serialsadditems   => $serialsadditems,
1400             staffdisplaycount => $staffdisplaycount,
1401             opacdisplaycount  => $opacdisplaycount,
1402             graceperiod       => $graceperiod,
1403             location          => $location,
1404             enddate           => $enddate,
1405             skip_serialseq    => $skip_serialseq,
1406             itemtype          => $itemtype,
1407             previousitemtype  => $previousitemtype,
1408             mana_id           => $mana_id,
1409         }
1410     )->store;
1411     $subscription->discard_changes;
1412     my $subscriptionid = $subscription->subscriptionid;
1413     my ( $query, $sth );
1414     unless ($enddate) {
1415         $enddate = GetExpirationDate( $subscriptionid, $startdate );
1416         $query = qq|
1417             UPDATE subscription
1418             SET    enddate=?
1419             WHERE  subscriptionid=?
1420         |;
1421         $sth = $dbh->prepare($query);
1422         $sth->execute( $enddate, $subscriptionid );
1423     }
1424
1425     # then create the 1st expected number
1426     $query = qq(
1427         INSERT INTO subscriptionhistory
1428             (biblionumber, subscriptionid, histstartdate, missinglist, recievedlist)
1429         VALUES (?,?,?, '', '')
1430         );
1431     $sth = $dbh->prepare($query);
1432     $sth->execute( $biblionumber, $subscriptionid, $startdate);
1433
1434     # reread subscription to get a hash (for calculation of the 1st issue number)
1435     $subscription = GetSubscription($subscriptionid); # We should not do that
1436     my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1437
1438     # calculate issue number
1439     my $serialseq = GetSeq($subscription, $pattern) || q{};
1440
1441     Koha::Serial->new(
1442         {
1443             serialseq      => $serialseq,
1444             serialseq_x    => $subscription->{'lastvalue1'},
1445             serialseq_y    => $subscription->{'lastvalue2'},
1446             serialseq_z    => $subscription->{'lastvalue3'},
1447             subscriptionid => $subscriptionid,
1448             biblionumber   => $biblionumber,
1449             status         => EXPECTED,
1450             planneddate    => $firstacquidate,
1451             publisheddate  => $firstacquidate,
1452         }
1453     )->store();
1454
1455     logaction( "SERIAL", "ADD", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1456
1457     #set serial flag on biblio if not already set.
1458     my $biblio = Koha::Biblios->find( $biblionumber );
1459     if ( $biblio and !$biblio->serial ) {
1460         my $record = GetMarcBiblio({ biblionumber => $biblionumber });
1461         my ( $tag, $subf ) = GetMarcFromKohaField( 'biblio.serial' );
1462         if ($tag) {
1463             eval { $record->field($tag)->update( $subf => 1 ); };
1464         }
1465         ModBiblio( $record, $biblionumber, $biblio->frameworkcode );
1466     }
1467     return $subscriptionid;
1468 }
1469
1470 =head2 ReNewSubscription
1471
1472 ReNewSubscription($params);
1473
1474 $params is a hashref with the following keys: subscriptionid, user, startdate, numberlength, weeklength, monthlength, note, branchcode
1475
1476 this function renew a subscription with values given on input args.
1477
1478 =cut
1479
1480 sub ReNewSubscription {
1481     my ( $params ) = @_;
1482     my $subscriptionid = $params->{subscriptionid};
1483     my $user           = $params->{user};
1484     my $startdate      = $params->{startdate};
1485     my $numberlength   = $params->{numberlength};
1486     my $weeklength     = $params->{weeklength};
1487     my $monthlength    = $params->{monthlength};
1488     my $note           = $params->{note};
1489     my $branchcode     = $params->{branchcode};
1490
1491     my $dbh          = C4::Context->dbh;
1492     my $subscription = GetSubscription($subscriptionid);
1493     my $query        = qq|
1494          SELECT *
1495          FROM   biblio 
1496          LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
1497          WHERE    biblio.biblionumber=?
1498      |;
1499     my $sth = $dbh->prepare($query);
1500     $sth->execute( $subscription->{biblionumber} );
1501     my $biblio = $sth->fetchrow_hashref;
1502
1503     if ( C4::Context->preference("RenewSerialAddsSuggestion") ) {
1504         require C4::Suggestions;
1505         C4::Suggestions::NewSuggestion(
1506             {   'suggestedby'   => $user,
1507                 'title'         => $subscription->{bibliotitle},
1508                 'author'        => $biblio->{author},
1509                 'publishercode' => $biblio->{publishercode},
1510                 'note'          => $note,
1511                 'biblionumber'  => $subscription->{biblionumber},
1512                 'branchcode'    => $branchcode,
1513             }
1514         );
1515     }
1516
1517     $numberlength ||= 0; # Should not we raise an exception instead?
1518     $weeklength   ||= 0;
1519
1520     # renew subscription
1521     $query = qq|
1522         UPDATE subscription
1523         SET    startdate=?,numberlength=?,weeklength=?,monthlength=?,reneweddate=NOW()
1524         WHERE  subscriptionid=?
1525     |;
1526     $sth = $dbh->prepare($query);
1527     $sth->execute( $startdate, $numberlength, $weeklength, $monthlength, $subscriptionid );
1528     my $enddate = GetExpirationDate($subscriptionid);
1529         $debug && warn "enddate :$enddate";
1530     $query = qq|
1531         UPDATE subscription
1532         SET    enddate=?
1533         WHERE  subscriptionid=?
1534     |;
1535     $sth = $dbh->prepare($query);
1536     $sth->execute( $enddate, $subscriptionid );
1537
1538     logaction( "SERIAL", "RENEW", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1539     return;
1540 }
1541
1542 =head2 NewIssue
1543
1544 NewIssue($serialseq,$subscriptionid,$biblionumber,$status, $planneddate, $publisheddate, $notes, $routingnotes)
1545
1546 Create a new issue stored on the database.
1547 Note : we have to update the recievedlist and missinglist on subscriptionhistory for this subscription.
1548 returns the serial id
1549
1550 =cut
1551
1552 sub NewIssue {
1553     my ( $serialseq, $subscriptionid, $biblionumber, $status, $planneddate,
1554         $publisheddate, $publisheddatetext, $notes, $routingnotes ) = @_;
1555     ### FIXME biblionumber CAN be provided by subscriptionid. So Do we STILL NEED IT ?
1556
1557     return unless ($subscriptionid);
1558
1559     my $schema = Koha::Database->new()->schema();
1560
1561     my $subscription = Koha::Subscriptions->find( $subscriptionid );
1562
1563     my $serial = Koha::Serial->new(
1564         {
1565             serialseq         => $serialseq,
1566             serialseq_x       => $subscription->lastvalue1(),
1567             serialseq_y       => $subscription->lastvalue2(),
1568             serialseq_z       => $subscription->lastvalue3(),
1569             subscriptionid    => $subscriptionid,
1570             biblionumber      => $biblionumber,
1571             status            => $status,
1572             planneddate       => $planneddate,
1573             publisheddate     => $publisheddate,
1574             publisheddatetext => $publisheddatetext,
1575             notes             => $notes,
1576             routingnotes      => $routingnotes
1577         }
1578     )->store();
1579
1580     my $serialid = $serial->id();
1581
1582     my $subscription_history = Koha::Subscription::Histories->find($subscriptionid);
1583     my $missinglist = $subscription_history->missinglist();
1584     my $recievedlist = $subscription_history->recievedlist();
1585
1586     if ( $status == ARRIVED ) {
1587         ### TODO Add a feature that improves recognition and description.
1588         ### As such count (serialseq) i.e. : N18,2(N19),N20
1589         ### Would use substr and index But be careful to previous presence of ()
1590         $recievedlist .= "; $serialseq" unless ( index( $recievedlist, $serialseq ) > 0 );
1591     }
1592     if ( grep { /^$status$/ } (MISSING_STATUSES) ) {
1593         $missinglist .= "; $serialseq" unless ( index( $missinglist, $serialseq ) > 0 );
1594     }
1595
1596     $recievedlist =~ s/^; //;
1597     $missinglist  =~ s/^; //;
1598
1599     $subscription_history->recievedlist($recievedlist);
1600     $subscription_history->missinglist($missinglist);
1601     $subscription_history->store();
1602
1603     return $serialid;
1604 }
1605
1606 =head2 HasSubscriptionStrictlyExpired
1607
1608 1 or 0 = HasSubscriptionStrictlyExpired($subscriptionid)
1609
1610 the subscription has stricly expired when today > the end subscription date 
1611
1612 return :
1613 1 if true, 0 if false, -1 if the expiration date is not set.
1614
1615 =cut
1616
1617 sub HasSubscriptionStrictlyExpired {
1618
1619     # Getting end of subscription date
1620     my ($subscriptionid) = @_;
1621
1622     return unless ($subscriptionid);
1623
1624     my $dbh              = C4::Context->dbh;
1625     my $subscription     = GetSubscription($subscriptionid);
1626     my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1627
1628     # If the expiration date is set
1629     if ( $expirationdate != 0 ) {
1630         my ( $endyear, $endmonth, $endday ) = split( '-', $expirationdate );
1631
1632         # Getting today's date
1633         my ( $nowyear, $nowmonth, $nowday ) = Today();
1634
1635         # if today's date > expiration date, then the subscription has stricly expired
1636         if ( Delta_Days( $nowyear, $nowmonth, $nowday, $endyear, $endmonth, $endday ) < 0 ) {
1637             return 1;
1638         } else {
1639             return 0;
1640         }
1641     } else {
1642
1643         # There are some cases where the expiration date is not set
1644         # As we can't determine if the subscription has expired on a date-basis,
1645         # we return -1;
1646         return -1;
1647     }
1648 }
1649
1650 =head2 HasSubscriptionExpired
1651
1652 $has_expired = HasSubscriptionExpired($subscriptionid)
1653
1654 the subscription has expired when the next issue to arrive is out of subscription limit.
1655
1656 return :
1657 0 if the subscription has not expired
1658 1 if the subscription has expired
1659 2 if has subscription does not have a valid expiration date set
1660
1661 =cut
1662
1663 sub HasSubscriptionExpired {
1664     my ($subscriptionid) = @_;
1665
1666     return unless ($subscriptionid);
1667
1668     my $dbh              = C4::Context->dbh;
1669     my $subscription     = GetSubscription($subscriptionid);
1670     my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1671     if ( $frequency and $frequency->{unit} ) {
1672         my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1673         if (!defined $expirationdate) {
1674             $expirationdate = q{};
1675         }
1676         my $query          = qq|
1677             SELECT max(planneddate)
1678             FROM   serial
1679             WHERE  subscriptionid=?
1680       |;
1681         my $sth = $dbh->prepare($query);
1682         $sth->execute($subscriptionid);
1683         my ($res) = $sth->fetchrow;
1684         if (!$res || $res=~m/^0000/) {
1685             return 0;
1686         }
1687         my @res                   = split( /-/, $res );
1688         my @endofsubscriptiondate = split( /-/, $expirationdate );
1689         return 2 if ( scalar(@res) != 3 || scalar(@endofsubscriptiondate) != 3 || not check_date(@res) || not check_date(@endofsubscriptiondate) );
1690         return 1
1691           if ( ( @endofsubscriptiondate && Delta_Days( $res[0], $res[1], $res[2], $endofsubscriptiondate[0], $endofsubscriptiondate[1], $endofsubscriptiondate[2] ) <= 0 )
1692             || ( !$res ) );
1693         return 0;
1694     } else {
1695         # Irregular
1696         if ( $subscription->{'numberlength'} ) {
1697             my $countreceived = countissuesfrom( $subscriptionid, $subscription->{'startdate'} );
1698             return 1 if ( $countreceived > $subscription->{'numberlength'} );
1699             return 0;
1700         } else {
1701             return 0;
1702         }
1703     }
1704     return 0;    # Notice that you'll never get here.
1705 }
1706
1707 =head2 DelSubscription
1708
1709 DelSubscription($subscriptionid)
1710 this function deletes subscription which has $subscriptionid as id.
1711
1712 =cut
1713
1714 sub DelSubscription {
1715     my ($subscriptionid) = @_;
1716     my $dbh = C4::Context->dbh;
1717     $dbh->do("DELETE FROM subscription WHERE subscriptionid=?", undef, $subscriptionid);
1718     $dbh->do("DELETE FROM subscriptionhistory WHERE subscriptionid=?", undef, $subscriptionid);
1719     $dbh->do("DELETE FROM serial WHERE subscriptionid=?", undef, $subscriptionid);
1720
1721     Koha::AdditionalFieldValues->search({
1722         'field.tablename' => 'subscription',
1723         'me.record_id' => $subscriptionid,
1724     }, { join => 'field' })->delete;
1725
1726     logaction( "SERIAL", "DELETE", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1727 }
1728
1729 =head2 DelIssue
1730
1731 DelIssue($serialseq,$subscriptionid)
1732 this function deletes an issue which has $serialseq and $subscriptionid given on input arg.
1733
1734 returns the number of rows affected
1735
1736 =cut
1737
1738 sub DelIssue {
1739     my ($dataissue) = @_;
1740     my $dbh = C4::Context->dbh;
1741     ### TODO Add itemdeletion. Would need to get itemnumbers. Should be in a pref ?
1742
1743     my $query = qq|
1744         DELETE FROM serial
1745         WHERE       serialid= ?
1746         AND         subscriptionid= ?
1747     |;
1748     my $mainsth = $dbh->prepare($query);
1749     $mainsth->execute( $dataissue->{'serialid'}, $dataissue->{'subscriptionid'} );
1750
1751     #Delete element from subscription history
1752     $query = "SELECT * FROM   subscription WHERE  subscriptionid = ?";
1753     my $sth = $dbh->prepare($query);
1754     $sth->execute( $dataissue->{'subscriptionid'} );
1755     my $val = $sth->fetchrow_hashref;
1756     unless ( $val->{manualhistory} ) {
1757         my $query = qq|
1758           SELECT * FROM subscriptionhistory
1759           WHERE       subscriptionid= ?
1760       |;
1761         my $sth = $dbh->prepare($query);
1762         $sth->execute( $dataissue->{'subscriptionid'} );
1763         my $data      = $sth->fetchrow_hashref;
1764         my $serialseq = $dataissue->{'serialseq'};
1765         $data->{'missinglist'}  =~ s/\b$serialseq\b//;
1766         $data->{'recievedlist'} =~ s/\b$serialseq\b//;
1767         my $strsth = "UPDATE subscriptionhistory SET " . join( ",", map { join( "=", $_, $dbh->quote( $data->{$_} ) ) } keys %$data ) . " WHERE subscriptionid=?";
1768         $sth = $dbh->prepare($strsth);
1769         $sth->execute( $dataissue->{'subscriptionid'} );
1770     }
1771
1772     return $mainsth->rows;
1773 }
1774
1775 =head2 GetLateOrMissingIssues
1776
1777 @issuelist = GetLateMissingIssues($supplierid,$serialid)
1778
1779 this function selects missing issues on database - where serial.status = MISSING* or serial.status = LATE or planneddate<now
1780
1781 return :
1782 the issuelist as an array of hash refs. Each element of this array contains 
1783 name,title,planneddate,serialseq,serial.subscriptionid from tables : subscription, serial & biblio
1784
1785 =cut
1786
1787 sub GetLateOrMissingIssues {
1788     my ( $supplierid, $serialid, $order ) = @_;
1789
1790     return unless ( $supplierid or $serialid );
1791
1792     my $dbh = C4::Context->dbh;
1793
1794     my $sth;
1795     my $byserial = '';
1796     if ($serialid) {
1797         $byserial = "and serialid = " . $serialid;
1798     }
1799     if ($order) {
1800         $order .= ", title";
1801     } else {
1802         $order = "title";
1803     }
1804     my $missing_statuses_string = join ',', (MISSING_STATUSES);
1805     if ($supplierid) {
1806         $sth = $dbh->prepare(
1807             "SELECT
1808                 serialid,      aqbooksellerid,        name,
1809                 biblio.title,  biblioitems.issn,      planneddate,    serialseq,
1810                 serial.status, serial.subscriptionid, claimdate, claims_count,
1811                 subscription.branchcode
1812             FROM      serial
1813                 LEFT JOIN subscription  ON serial.subscriptionid=subscription.subscriptionid
1814                 LEFT JOIN biblio        ON subscription.biblionumber=biblio.biblionumber
1815                 LEFT JOIN biblioitems   ON subscription.biblionumber=biblioitems.biblionumber
1816                 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1817                 WHERE subscription.subscriptionid = serial.subscriptionid
1818                 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1819                 AND subscription.aqbooksellerid=$supplierid
1820                 $byserial
1821                 ORDER BY $order"
1822         );
1823     } else {
1824         $sth = $dbh->prepare(
1825             "SELECT
1826             serialid,      aqbooksellerid,         name,
1827             biblio.title,  planneddate,           serialseq,
1828                 serial.status, serial.subscriptionid, claimdate, claims_count,
1829                 subscription.branchcode
1830             FROM serial
1831                 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1832                 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1833                 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1834                 WHERE subscription.subscriptionid = serial.subscriptionid
1835                         AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1836                 $byserial
1837                 ORDER BY $order"
1838         );
1839     }
1840     $sth->execute( EXPECTED, LATE, CLAIMED );
1841     my @issuelist;
1842     while ( my $line = $sth->fetchrow_hashref ) {
1843
1844         if ($line->{planneddate} && $line->{planneddate} !~/^0+\-/) {
1845             $line->{planneddateISO} = $line->{planneddate};
1846             $line->{planneddate} = output_pref( { dt => dt_from_string( $line->{"planneddate"} ), dateonly => 1 } );
1847         }
1848         if ($line->{claimdate} && $line->{claimdate} !~/^0+\-/) {
1849             $line->{claimdateISO} = $line->{claimdate};
1850             $line->{claimdate}   = output_pref( { dt => dt_from_string( $line->{"claimdate"} ), dateonly => 1 } );
1851         }
1852         $line->{"status".$line->{status}}   = 1;
1853
1854         my $subscription_object = Koha::Subscriptions->find($line->{subscriptionid});
1855         $line->{additional_fields} = { map { $_->field->name => $_->value }
1856             $subscription_object->additional_field_values->as_list };
1857
1858         push @issuelist, $line;
1859     }
1860     return @issuelist;
1861 }
1862
1863 =head2 updateClaim
1864
1865 &updateClaim($serialid)
1866
1867 this function updates the time when a claim is issued for late/missing items
1868
1869 called from claims.pl file
1870
1871 =cut
1872
1873 sub updateClaim {
1874     my ($serialids) = @_;
1875     return unless $serialids;
1876     unless ( ref $serialids ) {
1877         $serialids = [ $serialids ];
1878     }
1879     my $dbh = C4::Context->dbh;
1880     return $dbh->do(q|
1881         UPDATE serial
1882         SET claimdate = NOW(),
1883             claims_count = claims_count + 1,
1884             status = ?
1885         WHERE serialid in (| . join( q|,|, (q|?|) x @$serialids ) . q|)|,
1886         {}, CLAIMED, @$serialids );
1887 }
1888
1889 =head2 check_routing
1890
1891 $result = &check_routing($subscriptionid)
1892
1893 this function checks to see if a serial has a routing list and returns the count of routingid
1894 used to show either an 'add' or 'edit' link
1895
1896 =cut
1897
1898 sub check_routing {
1899     my ($subscriptionid) = @_;
1900
1901     return unless ($subscriptionid);
1902
1903     my $dbh              = C4::Context->dbh;
1904     my $sth              = $dbh->prepare(
1905         "SELECT count(routingid) routingids FROM subscription LEFT JOIN subscriptionroutinglist 
1906                               ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
1907                               WHERE subscription.subscriptionid = ? GROUP BY routingid ORDER BY ranking ASC
1908                               "
1909     );
1910     $sth->execute($subscriptionid);
1911     my $line   = $sth->fetchrow_hashref;
1912     my $result = $line->{'routingids'};
1913     return $result;
1914 }
1915
1916 =head2 addroutingmember
1917
1918 addroutingmember($borrowernumber,$subscriptionid)
1919
1920 this function takes a borrowernumber and subscriptionid and adds the member to the
1921 routing list for that serial subscription and gives them a rank on the list
1922 of either 1 or highest current rank + 1
1923
1924 =cut
1925
1926 sub addroutingmember {
1927     my ( $borrowernumber, $subscriptionid ) = @_;
1928
1929     return unless ($borrowernumber and $subscriptionid);
1930
1931     my $rank;
1932     my $dbh = C4::Context->dbh;
1933     my $sth = $dbh->prepare( "SELECT max(ranking) rank FROM subscriptionroutinglist WHERE subscriptionid = ?" );
1934     $sth->execute($subscriptionid);
1935     while ( my $line = $sth->fetchrow_hashref ) {
1936         if ( $line->{'rank'} > 0 ) {
1937             $rank = $line->{'rank'} + 1;
1938         } else {
1939             $rank = 1;
1940         }
1941     }
1942     $sth = $dbh->prepare( "INSERT INTO subscriptionroutinglist (subscriptionid,borrowernumber,ranking) VALUES (?,?,?)" );
1943     $sth->execute( $subscriptionid, $borrowernumber, $rank );
1944 }
1945
1946 =head2 reorder_members
1947
1948 reorder_members($subscriptionid,$routingid,$rank)
1949
1950 this function is used to reorder the routing list
1951
1952 it takes the routingid of the member one wants to re-rank and the rank it is to move to
1953 - it gets all members on list puts their routingid's into an array
1954 - removes the one in the array that is $routingid
1955 - then reinjects $routingid at point indicated by $rank
1956 - then update the database with the routingids in the new order
1957
1958 =cut
1959
1960 sub reorder_members {
1961     my ( $subscriptionid, $routingid, $rank ) = @_;
1962     my $dbh = C4::Context->dbh;
1963     my $sth = $dbh->prepare( "SELECT * FROM subscriptionroutinglist WHERE subscriptionid = ? ORDER BY ranking ASC" );
1964     $sth->execute($subscriptionid);
1965     my @result;
1966     while ( my $line = $sth->fetchrow_hashref ) {
1967         push( @result, $line->{'routingid'} );
1968     }
1969
1970     # To find the matching index
1971     my $i;
1972     my $key = -1;    # to allow for 0 being a valid response
1973     for ( $i = 0 ; $i < @result ; $i++ ) {
1974         if ( $routingid == $result[$i] ) {
1975             $key = $i;    # save the index
1976             last;
1977         }
1978     }
1979
1980     # if index exists in array then move it to new position
1981     if ( $key > -1 && $rank > 0 ) {
1982         my $new_rank = $rank - 1;                       # $new_rank is what you want the new index to be in the array
1983         my $moving_item = splice( @result, $key, 1 );
1984         splice( @result, $new_rank, 0, $moving_item );
1985     }
1986     for ( my $j = 0 ; $j < @result ; $j++ ) {
1987         my $sth = $dbh->prepare( "UPDATE subscriptionroutinglist SET ranking = '" . ( $j + 1 ) . "' WHERE routingid = '" . $result[$j] . "'" );
1988         $sth->execute;
1989     }
1990     return;
1991 }
1992
1993 =head2 delroutingmember
1994
1995 delroutingmember($routingid,$subscriptionid)
1996
1997 this function either deletes one member from routing list if $routingid exists otherwise
1998 deletes all members from the routing list
1999
2000 =cut
2001
2002 sub delroutingmember {
2003
2004     # if $routingid exists then deletes that row otherwise deletes all with $subscriptionid
2005     my ( $routingid, $subscriptionid ) = @_;
2006     my $dbh = C4::Context->dbh;
2007     if ($routingid) {
2008         my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE routingid = ?");
2009         $sth->execute($routingid);
2010         reorder_members( $subscriptionid, $routingid );
2011     } else {
2012         my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE subscriptionid = ?");
2013         $sth->execute($subscriptionid);
2014     }
2015     return;
2016 }
2017
2018 =head2 getroutinglist
2019
2020 @routinglist = getroutinglist($subscriptionid)
2021
2022 this gets the info from the subscriptionroutinglist for $subscriptionid
2023
2024 return :
2025 the routinglist as an array. Each element of the array contains a hash_ref containing
2026 routingid - a unique id, borrowernumber, ranking, and biblionumber of subscription
2027
2028 =cut
2029
2030 sub getroutinglist {
2031     my ($subscriptionid) = @_;
2032     my $dbh              = C4::Context->dbh;
2033     my $sth              = $dbh->prepare(
2034         'SELECT routingid, borrowernumber, ranking, biblionumber
2035             FROM subscription 
2036             JOIN subscriptionroutinglist ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
2037             WHERE subscription.subscriptionid = ? ORDER BY ranking ASC'
2038     );
2039     $sth->execute($subscriptionid);
2040     my $routinglist = $sth->fetchall_arrayref({});
2041     return @{$routinglist};
2042 }
2043
2044 =head2 countissuesfrom
2045
2046 $result = countissuesfrom($subscriptionid,$startdate)
2047
2048 Returns a count of serial rows matching the given subsctiptionid
2049 with published date greater than startdate
2050
2051 =cut
2052
2053 sub countissuesfrom {
2054     my ( $subscriptionid, $startdate ) = @_;
2055     my $dbh   = C4::Context->dbh;
2056     my $query = qq|
2057             SELECT count(*)
2058             FROM   serial
2059             WHERE  subscriptionid=?
2060             AND serial.publisheddate>?
2061         |;
2062     my $sth = $dbh->prepare($query);
2063     $sth->execute( $subscriptionid, $startdate );
2064     my ($countreceived) = $sth->fetchrow;
2065     return $countreceived;
2066 }
2067
2068 =head2 CountIssues
2069
2070 $result = CountIssues($subscriptionid)
2071
2072 Returns a count of serial rows matching the given subsctiptionid
2073
2074 =cut
2075
2076 sub CountIssues {
2077     my ($subscriptionid) = @_;
2078     my $dbh              = C4::Context->dbh;
2079     my $query            = qq|
2080             SELECT count(*)
2081             FROM   serial
2082             WHERE  subscriptionid=?
2083         |;
2084     my $sth = $dbh->prepare($query);
2085     $sth->execute($subscriptionid);
2086     my ($countreceived) = $sth->fetchrow;
2087     return $countreceived;
2088 }
2089
2090 =head2 HasItems
2091
2092 $result = HasItems($subscriptionid)
2093
2094 returns a count of items from serial matching the subscriptionid
2095
2096 =cut
2097
2098 sub HasItems {
2099     my ($subscriptionid) = @_;
2100     my $dbh              = C4::Context->dbh;
2101     my $query = q|
2102             SELECT COUNT(serialitems.itemnumber)
2103             FROM   serial 
2104                         LEFT JOIN serialitems USING(serialid)
2105             WHERE  subscriptionid=? AND serialitems.serialid IS NOT NULL
2106         |;
2107     my $sth=$dbh->prepare($query);
2108     $sth->execute($subscriptionid);
2109     my ($countitems)=$sth->fetchrow_array();
2110     return $countitems;  
2111 }
2112
2113 =head2 abouttoexpire
2114
2115 $result = abouttoexpire($subscriptionid)
2116
2117 this function alerts you to the penultimate issue for a serial subscription
2118
2119 returns 1 - if this is the penultimate issue
2120 returns 0 - if not
2121
2122 =cut
2123
2124 sub abouttoexpire {
2125     my ($subscriptionid) = @_;
2126     my $dbh              = C4::Context->dbh;
2127     my $subscription     = GetSubscription($subscriptionid);
2128     my $per = $subscription->{'periodicity'};
2129     my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($per);
2130     if ($frequency and $frequency->{unit}){
2131
2132         my $expirationdate = GetExpirationDate($subscriptionid);
2133
2134         my ($res) = $dbh->selectrow_array('select max(planneddate) from serial where subscriptionid = ?', undef, $subscriptionid);
2135         my $nextdate = GetNextDate($subscription, $res, $frequency);
2136
2137         # only compare dates if both dates exist.
2138         if ($nextdate and $expirationdate) {
2139             if(Date::Calc::Delta_Days(
2140                 split( /-/, $nextdate ),
2141                 split( /-/, $expirationdate )
2142             ) <= 0) {
2143                 return 1;
2144             }
2145         }
2146
2147     } elsif ( $subscription->{numberlength} && $subscription->{numberlength}>0) {
2148         return (countissuesfrom($subscriptionid,$subscription->{'startdate'}) >=$subscription->{numberlength}-1);
2149     }
2150
2151     return 0;
2152 }
2153
2154 =head2 GetFictiveIssueNumber
2155
2156 $issueno = GetFictiveIssueNumber($subscription, $publishedate, $frequency);
2157
2158 Get the position of the issue published at $publisheddate, considering the
2159 first issue (at firstacquidate) is at position 1, the next is at position 2, etc...
2160 This issuenumber doesn't take into account irregularities, so, for instance, if the 3rd
2161 issue is declared as 'irregular' (will be skipped at receipt), the next issue number
2162 will be 4, not 3. It's why it is called 'fictive'. It is NOT a serial seq, and is not
2163 depending on how many rows are in serial table.
2164 The issue number calculation is based on subscription frequency, first acquisition
2165 date, and $publisheddate.
2166
2167 Returns undef when called for irregular frequencies.
2168
2169 The routine is used to skip irregularities when calculating the next issue
2170 date (in GetNextDate) or the next issue number (in GetNextSeq).
2171
2172 =cut
2173
2174 sub GetFictiveIssueNumber {
2175     my ($subscription, $publisheddate, $frequency) = @_;
2176
2177     my $unit = $frequency->{unit} ? lc $frequency->{'unit'} : undef;
2178     return if !$unit;
2179     my $issueno;
2180
2181     my ( $year, $month, $day ) = split /-/, $publisheddate;
2182     my ( $fa_year, $fa_month, $fa_day ) = split /-/, $subscription->{'firstacquidate'};
2183     my $delta = _delta_units( [$fa_year, $fa_month, $fa_day], [$year, $month, $day], $unit );
2184
2185     if( $frequency->{'unitsperissue'} == 1 ) {
2186         $issueno = $delta * $frequency->{'issuesperunit'} + $subscription->{'countissuesperunit'};
2187     } else { # issuesperunit == 1
2188         $issueno = 1 + int( $delta / $frequency->{'unitsperissue'} );
2189     }
2190     return $issueno;
2191 }
2192
2193 sub _delta_units {
2194     my ( $date1, $date2, $unit ) = @_;
2195     # date1 and date2 are array refs in the form [ yy, mm, dd ]
2196
2197     if( $unit eq 'day' ) {
2198         return Delta_Days( @$date1, @$date2 );
2199     } elsif( $unit eq 'week' ) {
2200         return int( Delta_Days( @$date1, @$date2 ) / 7 );
2201     }
2202
2203     # In case of months or years, this is a wrapper around N_Delta_YMD.
2204     # Note that N_Delta_YMD returns 29 days between e.g. 22-2-72 and 22-3-72
2205     # while we expect 1 month.
2206     my @delta = N_Delta_YMD( @$date1, @$date2 );
2207     if( $delta[2] > 27 ) {
2208         # Check if we could add a month
2209         my @jump = Add_Delta_YM( @$date1, $delta[0], 1 + $delta[1] );
2210         if( Delta_Days( @jump, @$date2 ) >= 0 ) {
2211             $delta[1]++;
2212         }
2213     }
2214     if( $delta[1] >= 12 ) {
2215         $delta[0]++;
2216         $delta[1] -= 12;
2217     }
2218     # if unit is year, we only return full years
2219     return $unit eq 'month' ? $delta[0] * 12 + $delta[1] : $delta[0];
2220 }
2221
2222 sub _get_next_date_day {
2223     my ($subscription, $freqdata, $year, $month, $day) = @_;
2224
2225     my @newissue; # ( yy, mm, dd )
2226     # We do not need $delta_days here, since it would be zero where used
2227
2228     if( $freqdata->{issuesperunit} == 1 ) {
2229         # Add full days
2230         @newissue = Add_Delta_Days(
2231             $year, $month, $day, $freqdata->{"unitsperissue"} );
2232     } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2233         # Add zero days
2234         @newissue = ( $year, $month, $day );
2235         $subscription->{countissuesperunit}++;
2236     } else {
2237         # We finished a cycle of issues within a unit.
2238         # No subtraction of zero needed, just add one day
2239         @newissue = Add_Delta_Days( $year, $month, $day, 1 );
2240         $subscription->{countissuesperunit} = 1;
2241     }
2242     return @newissue;
2243 }
2244
2245 sub _get_next_date_week {
2246     my ($subscription, $freqdata, $year, $month, $day) = @_;
2247
2248     my @newissue; # ( yy, mm, dd )
2249     my $delta_days = int( 7 / $freqdata->{issuesperunit} );
2250
2251     if( $freqdata->{issuesperunit} == 1 ) {
2252         # Add full weeks (of 7 days)
2253         @newissue = Add_Delta_Days(
2254             $year, $month, $day, 7 * $freqdata->{"unitsperissue"} );
2255     } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2256         # Add rounded number of days based on frequency.
2257         @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2258         $subscription->{countissuesperunit}++;
2259     } else {
2260         # We finished a cycle of issues within a unit.
2261         # Subtract delta * (issues - 1), add 1 week
2262         @newissue = Add_Delta_Days( $year, $month, $day,
2263             -$delta_days * ($freqdata->{issuesperunit} - 1) );
2264         @newissue = Add_Delta_Days( @newissue, 7 );
2265         $subscription->{countissuesperunit} = 1;
2266     }
2267     return @newissue;
2268 }
2269
2270 sub _get_next_date_month {
2271     my ($subscription, $freqdata, $year, $month, $day) = @_;
2272
2273     my @newissue; # ( yy, mm, dd )
2274     my $delta_days = int( 30 / $freqdata->{issuesperunit} );
2275
2276     if( $freqdata->{issuesperunit} == 1 ) {
2277         # Add full months
2278         @newissue = Add_Delta_YM(
2279             $year, $month, $day, 0, $freqdata->{"unitsperissue"} );
2280     } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2281         # Add rounded number of days based on frequency.
2282         @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2283         $subscription->{countissuesperunit}++;
2284     } else {
2285         # We finished a cycle of issues within a unit.
2286         # Subtract delta * (issues - 1), add 1 month
2287         @newissue = Add_Delta_Days( $year, $month, $day,
2288             -$delta_days * ($freqdata->{issuesperunit} - 1) );
2289         @newissue = Add_Delta_YM( @newissue, 0, 1 );
2290         $subscription->{countissuesperunit} = 1;
2291     }
2292     return @newissue;
2293 }
2294
2295 sub _get_next_date_year {
2296     my ($subscription, $freqdata, $year, $month, $day) = @_;
2297
2298     my @newissue; # ( yy, mm, dd )
2299     my $delta_days = int( 365 / $freqdata->{issuesperunit} );
2300
2301     if( $freqdata->{issuesperunit} == 1 ) {
2302         # Add full years
2303         @newissue = Add_Delta_YM( $year, $month, $day, $freqdata->{"unitsperissue"}, 0 );
2304     } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2305         # Add rounded number of days based on frequency.
2306         @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2307         $subscription->{countissuesperunit}++;
2308     } else {
2309         # We finished a cycle of issues within a unit.
2310         # Subtract delta * (issues - 1), add 1 year
2311         @newissue = Add_Delta_Days( $year, $month, $day, -$delta_days * ($freqdata->{issuesperunit} - 1) );
2312         @newissue = Add_Delta_YM( @newissue, 1, 0 );
2313         $subscription->{countissuesperunit} = 1;
2314     }
2315     return @newissue;
2316 }
2317
2318 =head2 GetNextDate
2319
2320 $resultdate = GetNextDate($publisheddate,$subscription,$freqdata,$updatecount)
2321
2322 this function it takes the publisheddate and will return the next issue's date
2323 and will skip dates if there exists an irregularity.
2324 $publisheddate has to be an ISO date
2325 $subscription is a hashref containing at least 'firstacquidate', 'irregularity', and 'countissuesperunit'
2326 $frequency is a hashref containing frequency informations
2327 $updatecount is a boolean value which, when set to true, update the 'countissuesperunit' in database
2328 - eg if periodicity is monthly and $publisheddate is 2007-02-10 but if March and April is to be
2329 skipped then the returned date will be 2007-05-10
2330
2331 return :
2332 $resultdate - then next date in the sequence (ISO date)
2333
2334 Return undef if subscription is irregular
2335
2336 =cut
2337
2338 sub GetNextDate {
2339     my ( $subscription, $publisheddate, $freqdata, $updatecount ) = @_;
2340
2341     return unless $subscription and $publisheddate;
2342
2343
2344     if ($freqdata->{'unit'}) {
2345         my ( $year, $month, $day ) = split /-/, $publisheddate;
2346
2347         # Process an irregularity Hash
2348         # Suppose that irregularities are stored in a string with this structure
2349         # irreg1;irreg2;irreg3
2350         # where irregX is the number of issue which will not be received
2351         # (the first issue takes the number 1, the 2nd the number 2 and so on)
2352         my %irregularities;
2353         if ( $subscription->{irregularity} ) {
2354             my @irreg = split /;/, $subscription->{'irregularity'} ;
2355             foreach my $irregularity (@irreg) {
2356                 $irregularities{$irregularity} = 1;
2357             }
2358         }
2359
2360         # Get the 'fictive' next issue number
2361         # It is used to check if next issue is an irregular issue.
2362         my $issueno = GetFictiveIssueNumber($subscription, $publisheddate, $freqdata) + 1;
2363
2364         # Then get the next date
2365         my $unit = lc $freqdata->{'unit'};
2366         if ($unit eq 'day') {
2367             while ($irregularities{$issueno}) {
2368                 ($year, $month, $day) = _get_next_date_day($subscription,
2369                     $freqdata, $year, $month, $day);
2370                 $issueno++;
2371             }
2372             ($year, $month, $day) = _get_next_date_day($subscription, $freqdata,
2373                 $year, $month, $day);
2374         }
2375         elsif ($unit eq 'week') {
2376             while ($irregularities{$issueno}) {
2377                 ($year, $month, $day) = _get_next_date_week($subscription,
2378                     $freqdata, $year, $month, $day);
2379                 $issueno++;
2380             }
2381             ($year, $month, $day) = _get_next_date_week($subscription,
2382                 $freqdata, $year, $month, $day);
2383         }
2384         elsif ($unit eq 'month') {
2385             while ($irregularities{$issueno}) {
2386                 ($year, $month, $day) = _get_next_date_month($subscription,
2387                     $freqdata, $year, $month, $day);
2388                 $issueno++;
2389             }
2390             ($year, $month, $day) = _get_next_date_month($subscription,
2391                 $freqdata, $year, $month, $day);
2392         }
2393         elsif ($unit eq 'year') {
2394             while ($irregularities{$issueno}) {
2395                 ($year, $month, $day) = _get_next_date_year($subscription,
2396                     $freqdata, $year, $month, $day);
2397                 $issueno++;
2398             }
2399             ($year, $month, $day) = _get_next_date_year($subscription,
2400                 $freqdata, $year, $month, $day);
2401         }
2402
2403         if ($updatecount){
2404             my $dbh = C4::Context->dbh;
2405             my $query = qq{
2406                 UPDATE subscription
2407                 SET countissuesperunit = ?
2408                 WHERE subscriptionid = ?
2409             };
2410             my $sth = $dbh->prepare($query);
2411             $sth->execute($subscription->{'countissuesperunit'}, $subscription->{'subscriptionid'});
2412         }
2413
2414         return sprintf("%04d-%02d-%02d", $year, $month, $day);
2415     }
2416 }
2417
2418 =head2 _numeration
2419
2420   $string = &_numeration($value,$num_type,$locale);
2421
2422 _numeration returns the string corresponding to $value in the num_type
2423 num_type can take :
2424     -dayname
2425     -dayabrv
2426     -monthname
2427     -monthabrv
2428     -season
2429     -seasonabrv
2430
2431 =cut
2432
2433 sub _numeration {
2434     my ($value, $num_type, $locale) = @_;
2435     $value ||= 0;
2436     $num_type //= '';
2437     $locale ||= 'en';
2438     my $string;
2439     if ( $num_type =~ /^dayname$/ or $num_type =~ /^dayabrv$/ ) {
2440         # 1970-11-01 was a Sunday
2441         $value = $value % 7;
2442         my $dt = DateTime->new(
2443             year    => 1970,
2444             month   => 11,
2445             day     => $value + 1,
2446             locale  => $locale,
2447         );
2448         $string = $num_type =~ /^dayname$/
2449             ? $dt->strftime("%A")
2450             : $dt->strftime("%a");
2451     } elsif ( $num_type =~ /^monthname$/ or $num_type =~ /^monthabrv$/ ) {
2452         $value = $value % 12;
2453         my $dt = DateTime->new(
2454             year    => 1970,
2455             month   => $value + 1,
2456             locale  => $locale,
2457         );
2458         $string = $num_type =~ /^monthname$/
2459             ? $dt->strftime("%B")
2460             : $dt->strftime("%b");
2461     } elsif ( $num_type =~ /^season$/ ) {
2462         my @seasons= qw( Spring Summer Fall Winter );
2463         $value = $value % 4;
2464         $string = $seasons[$value];
2465     } elsif ( $num_type =~ /^seasonabrv$/ ) {
2466         my @seasonsabrv= qw( Spr Sum Fal Win );
2467         $value = $value % 4;
2468         $string = $seasonsabrv[$value];
2469     } else {
2470         $string = $value;
2471     }
2472
2473     return $string;
2474 }
2475
2476 =head2 CloseSubscription
2477
2478 Close a subscription given a subscriptionid
2479
2480 =cut
2481
2482 sub CloseSubscription {
2483     my ( $subscriptionid ) = @_;
2484     return unless $subscriptionid;
2485     my $dbh = C4::Context->dbh;
2486     my $sth = $dbh->prepare( q{
2487         UPDATE subscription
2488         SET closed = 1
2489         WHERE subscriptionid = ?
2490     } );
2491     $sth->execute( $subscriptionid );
2492
2493     # Set status = missing when status = stopped
2494     $sth = $dbh->prepare( q{
2495         UPDATE serial
2496         SET status = ?
2497         WHERE subscriptionid = ?
2498         AND status = ?
2499     } );
2500     $sth->execute( STOPPED, $subscriptionid, EXPECTED );
2501 }
2502
2503 =head2 ReopenSubscription
2504
2505 Reopen a subscription given a subscriptionid
2506
2507 =cut
2508
2509 sub ReopenSubscription {
2510     my ( $subscriptionid ) = @_;
2511     return unless $subscriptionid;
2512     my $dbh = C4::Context->dbh;
2513     my $sth = $dbh->prepare( q{
2514         UPDATE subscription
2515         SET closed = 0
2516         WHERE subscriptionid = ?
2517     } );
2518     $sth->execute( $subscriptionid );
2519
2520     # Set status = expected when status = stopped
2521     $sth = $dbh->prepare( q{
2522         UPDATE serial
2523         SET status = ?
2524         WHERE subscriptionid = ?
2525         AND status = ?
2526     } );
2527     $sth->execute( EXPECTED, $subscriptionid, STOPPED );
2528 }
2529
2530 =head2 subscriptionCurrentlyOnOrder
2531
2532     $bool = subscriptionCurrentlyOnOrder( $subscriptionid );
2533
2534 Return 1 if subscription is currently on order else 0.
2535
2536 =cut
2537
2538 sub subscriptionCurrentlyOnOrder {
2539     my ( $subscriptionid ) = @_;
2540     my $dbh = C4::Context->dbh;
2541     my $query = qq|
2542         SELECT COUNT(*) FROM aqorders
2543         WHERE subscriptionid = ?
2544             AND datereceived IS NULL
2545             AND datecancellationprinted IS NULL
2546     |;
2547     my $sth = $dbh->prepare( $query );
2548     $sth->execute($subscriptionid);
2549     return $sth->fetchrow_array;
2550 }
2551
2552 =head2 can_claim_subscription
2553
2554     $can = can_claim_subscription( $subscriptionid[, $userid] );
2555
2556 Return 1 if the subscription can be claimed by the current logged user (or a given $userid), else 0.
2557
2558 =cut
2559
2560 sub can_claim_subscription {
2561     my ( $subscription, $userid ) = @_;
2562     return _can_do_on_subscription( $subscription, $userid, 'claim_serials' );
2563 }
2564
2565 =head2 can_edit_subscription
2566
2567     $can = can_edit_subscription( $subscriptionid[, $userid] );
2568
2569 Return 1 if the subscription can be edited by the current logged user (or a given $userid), else 0.
2570
2571 =cut
2572
2573 sub can_edit_subscription {
2574     my ( $subscription, $userid ) = @_;
2575     return _can_do_on_subscription( $subscription, $userid, 'edit_subscription' );
2576 }
2577
2578 =head2 can_show_subscription
2579
2580     $can = can_show_subscription( $subscriptionid[, $userid] );
2581
2582 Return 1 if the subscription can be shown by the current logged user (or a given $userid), else 0.
2583
2584 =cut
2585
2586 sub can_show_subscription {
2587     my ( $subscription, $userid ) = @_;
2588     return _can_do_on_subscription( $subscription, $userid, '*' );
2589 }
2590
2591 sub _can_do_on_subscription {
2592     my ( $subscription, $userid, $permission ) = @_;
2593     return 0 unless C4::Context->userenv;
2594     my $flags = C4::Context->userenv->{flags};
2595     $userid ||= C4::Context->userenv->{'id'};
2596
2597     if ( C4::Context->preference('IndependentBranches') ) {
2598         return 1
2599           if C4::Context->IsSuperLibrarian()
2600               or
2601               C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2602               or (
2603                   C4::Auth::haspermission( $userid,
2604                       { serials => $permission } )
2605                   and (  not defined $subscription->{branchcode}
2606                       or $subscription->{branchcode} eq ''
2607                       or $subscription->{branchcode} eq
2608                       C4::Context->userenv->{'branch'} )
2609               );
2610     }
2611     else {
2612         return 1
2613           if C4::Context->IsSuperLibrarian()
2614               or
2615               C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2616               or C4::Auth::haspermission(
2617                   $userid, { serials => $permission }
2618               ),
2619         ;
2620     }
2621     return 0;
2622 }
2623
2624 =head2 findSerialsByStatus
2625
2626     @serials = findSerialsByStatus($status, $subscriptionid);
2627
2628     Returns an array of serials matching a given status and subscription id.
2629
2630 =cut
2631
2632 sub findSerialsByStatus {
2633     my ( $status, $subscriptionid ) = @_;
2634     my $dbh   = C4::Context->dbh;
2635     my $query = q| SELECT * from serial
2636                     WHERE status = ?
2637                     AND subscriptionid = ?
2638                 |;
2639     my $serials = $dbh->selectall_arrayref( $query, { Slice => {} }, $status, $subscriptionid );
2640     return @$serials;
2641 }
2642
2643 1;
2644 __END__
2645
2646 =head1 AUTHOR
2647
2648 Koha Development Team <http://koha-community.org/>
2649
2650 =cut