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