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