3 # Copyright 2000-2002 Katipo Communications
4 # Parts Copyright 2010 Biblibre
6 # This file is part of Koha.
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.
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.
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>.
33 use POSIX qw( strftime );
34 use Scalar::Util qw( looks_like_number );
37 use C4::Auth qw( haspermission );
38 use C4::Biblio qw( GetMarcFromKohaField ModBiblio );
40 use C4::Log qw( logaction ); # logaction
41 use C4::Serials::Frequency qw( GetSubscriptionFrequency );
42 use C4::Serials::Numberpattern;
43 use Koha::AdditionalFieldValues;
46 use Koha::SharedContent;
47 use Koha::Subscription::Histories;
48 use Koha::Subscriptions;
49 use Koha::Suggestions;
50 use Koha::TemplateUtils qw( process_tt );
58 MISSING_NEVER_RECIEVED => 41,
59 MISSING_SOLD_OUT => 42,
60 MISSING_DAMAGED => 43,
68 use constant MISSING_STATUSES => (
69 MISSING, MISSING_NEVER_RECIEVED,
70 MISSING_SOLD_OUT, MISSING_DAMAGED,
74 our (@ISA, @EXPORT_OK);
79 NewSubscription ModSubscription DelSubscription
80 GetSubscription CountSubscriptionFromBiblionumber GetSubscriptionsFromBiblionumber
82 GetFullSubscriptionsFromBiblionumber GetFullSubscription ModSubscriptionHistory
83 HasSubscriptionStrictlyExpired HasSubscriptionExpired GetExpirationDate abouttoexpire
85 GetSubscriptionHistoryFromSubscriptionId
87 GetNextSeq GetSeq NewIssue GetSerials
88 GetLatestSerials ModSerialStatus GetNextDate
89 CloseSubscription ReopenSubscription
90 subscriptionCurrentlyOnOrder
91 can_claim_subscription can_edit_subscription can_show_subscription
93 GetSubscriptionLength ReNewSubscription GetLateOrMissingIssues
94 GetSerialInformation AddItem2Serial
95 PrepareSerialsData GetNextExpected ModNextExpected
96 GetSubscriptionIrregularities
99 GetSuppliersWithLateIssues
100 getroutinglist delroutingmember addroutingmember
102 check_routing updateClaim
113 C4::Serials - Serials Module Functions
121 Functions for handling subscriptions, claims routing etc.
126 =head2 GetSuppliersWithLateIssues
128 $supplierlist = GetSuppliersWithLateIssues()
130 this function get all suppliers with late issues.
133 an array_ref of suppliers each entry is a hash_ref containing id and name
134 the array is in name order
138 sub GetSuppliersWithLateIssues {
139 my $dbh = C4::Context->dbh;
140 my $statuses = join(',', ( LATE, MISSING_STATUSES, CLAIMED ) );
142 SELECT DISTINCT id, name
144 LEFT JOIN serial ON serial.subscriptionid=subscription.subscriptionid
145 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
148 (planneddate < now() AND serial.status=1)
149 OR serial.STATUS IN ( $statuses )
151 AND subscription.closed = 0
153 return $dbh->selectall_arrayref($query, { Slice => {} });
156 =head2 GetSubscriptionHistoryFromSubscriptionId
158 $history = GetSubscriptionHistoryFromSubscriptionId($subscriptionid);
160 This function returns the subscription history as a hashref
164 sub GetSubscriptionHistoryFromSubscriptionId {
165 my ($subscriptionid) = @_;
167 return unless $subscriptionid;
169 my $dbh = C4::Context->dbh;
172 FROM subscriptionhistory
173 WHERE subscriptionid = ?
175 my $sth = $dbh->prepare($query);
176 $sth->execute($subscriptionid);
177 my $results = $sth->fetchrow_hashref;
183 =head2 GetSerialInformation
185 $data = GetSerialInformation($serialid);
186 returns a hash_ref containing :
187 items : items marcrecord (can be an array)
189 subscription table field
190 + information about subscription expiration
194 sub GetSerialInformation {
196 my $dbh = C4::Context->dbh;
198 SELECT serial.*, serial.notes as sernotes, serial.status as serstatus,subscription.*,subscription.subscriptionid as subsid
199 FROM serial LEFT JOIN subscription ON subscription.subscriptionid=serial.subscriptionid
202 my $rq = $dbh->prepare($query);
203 $rq->execute($serialid);
204 my $data = $rq->fetchrow_hashref;
206 # create item information if we have serialsadditems for this subscription
207 if ( $data->{'serialsadditems'} ) {
208 my $queryitem = $dbh->prepare("SELECT itemnumber from serialitems where serialid=?");
209 $queryitem->execute($serialid);
210 my $itemnumbers = $queryitem->fetchall_arrayref( [0] );
212 if ( scalar(@$itemnumbers) > 0 ) {
213 foreach my $itemnum (@$itemnumbers) {
215 #It is ASSUMED that GetMarcItem ALWAYS WORK...
216 #Maybe GetMarcItem should return values on failure
217 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, $itemnum->[0], $data );
218 $itemprocessed->{'itemnumber'} = $itemnum->[0];
219 $itemprocessed->{'itemid'} = $itemnum->[0];
220 $itemprocessed->{'serialid'} = $serialid;
221 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
222 push @{ $data->{'items'} }, $itemprocessed;
225 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, '', $data );
226 $itemprocessed->{'itemid'} = "N$serialid";
227 $itemprocessed->{'serialid'} = $serialid;
228 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
229 $itemprocessed->{'countitems'} = 0;
230 push @{ $data->{'items'} }, $itemprocessed;
233 $data->{ "status" . $data->{'serstatus'} } = 1;
234 $data->{'subscriptionexpired'} = HasSubscriptionExpired( $data->{'subscriptionid'} ) && $data->{'status'} == 1;
235 $data->{'abouttoexpire'} = abouttoexpire( $data->{'subscriptionid'} );
236 $data->{cannotedit} = not can_edit_subscription( $data );
240 =head2 AddItem2Serial
242 $rows = AddItem2Serial($serialid,$itemnumber);
243 Adds an itemnumber to Serial record
244 returns the number of rows affected
249 my ( $serialid, $itemnumber ) = @_;
251 return unless ($serialid and $itemnumber);
253 my $dbh = C4::Context->dbh;
254 my $rq = $dbh->prepare("INSERT INTO `serialitems` SET serialid=? , itemnumber=?");
255 $rq->execute( $serialid, $itemnumber );
259 =head2 GetSubscription
261 $subs = GetSubscription($subscriptionid)
262 this function returns the subscription which has $subscriptionid as id.
264 a hashref. This hash contains
265 subscription, subscriptionhistory, aqbooksellers.name, biblio.title
269 sub GetSubscription {
270 my ($subscriptionid) = @_;
271 my $dbh = C4::Context->dbh;
273 SELECT subscription.*,
274 subscriptionhistory.*,
275 aqbooksellers.name AS aqbooksellername,
276 biblio.title AS bibliotitle,
277 biblio.subtitle AS bibliosubtitle,
278 subscription.biblionumber as bibnum
280 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
281 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
282 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
283 WHERE subscription.subscriptionid = ?
286 my $sth = $dbh->prepare($query);
287 $sth->execute($subscriptionid);
288 my $subscription = $sth->fetchrow_hashref;
290 return unless $subscription;
292 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
294 if ( my $mana_id = $subscription->{mana_id} ) {
295 my $mana_subscription = Koha::SharedContent::get_entity_by_id(
296 'subscription', $mana_id, {usecomments => 1});
297 $subscription->{comments} = $mana_subscription->{data}->{comments};
300 return $subscription;
303 =head2 GetFullSubscription
305 $array_ref = GetFullSubscription($subscriptionid)
306 this function reads the serial table.
310 sub GetFullSubscription {
311 my ($subscriptionid) = @_;
313 return unless ($subscriptionid);
315 my $dbh = C4::Context->dbh;
317 SELECT serial.serialid,
320 serial.publisheddate,
321 serial.publisheddatetext,
323 serial.notes as notes,
324 year(IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate)) as year,
325 aqbooksellers.name as aqbooksellername,
326 biblio.title as bibliotitle,
327 subscription.branchcode AS branchcode,
328 subscription.subscriptionid AS subscriptionid
330 LEFT JOIN subscription ON
331 (serial.subscriptionid=subscription.subscriptionid )
332 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
333 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
334 WHERE serial.subscriptionid = ?
336 IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate) DESC,
337 serial.subscriptionid
339 my $sth = $dbh->prepare($query);
340 $sth->execute($subscriptionid);
341 my $subscriptions = $sth->fetchall_arrayref( {} );
342 if (scalar @$subscriptions) {
343 my $cannotedit = not can_edit_subscription( $subscriptions->[0] );
344 for my $subscription ( @$subscriptions ) {
345 $subscription->{cannotedit} = $cannotedit;
349 return $subscriptions;
352 =head2 PrepareSerialsData
354 $array_ref = PrepareSerialsData($serialinfomation)
355 where serialinformation is a hashref array
359 sub PrepareSerialsData {
362 return unless ($lines);
369 my $previousnote = "";
371 foreach my $subs (@{$lines}) {
372 $subs->{ "status" . $subs->{'status'} } = 1;
373 if ( grep { $_ == $subs->{status} } ( EXPECTED, LATE, MISSING_STATUSES, CLAIMED ) ) {
374 $subs->{"checked"} = 1;
377 if ( $subs->{'year'} && $subs->{'year'} ne "" ) {
378 $year = $subs->{'year'};
382 if ( $tmpresults{$year} ) {
383 push @{ $tmpresults{$year}->{'serials'} }, $subs;
385 $tmpresults{$year} = {
387 'aqbooksellername' => $subs->{'aqbooksellername'},
388 'bibliotitle' => $subs->{'bibliotitle'},
389 'serials' => [$subs],
394 foreach my $key ( sort { $b cmp $a } keys %tmpresults ) {
395 push @res, $tmpresults{$key};
400 =head2 GetSubscriptionsFromBiblionumber
402 $array_ref = GetSubscriptionsFromBiblionumber($biblionumber)
403 this function get the subscription list. it reads the subscription table.
405 reference to an array of subscriptions which have the biblionumber given on input arg.
406 each element of this array is a hashref containing
407 startdate, histstartdate,opacnote,missinglist,recievedlist,periodicity,status & enddate
411 sub GetSubscriptionsFromBiblionumber {
412 my ($biblionumber) = @_;
414 return unless ($biblionumber);
416 my $dbh = C4::Context->dbh;
418 SELECT subscription.*,
420 subscriptionhistory.*,
421 aqbooksellers.name AS aqbooksellername,
422 biblio.title AS bibliotitle
424 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
425 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
426 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
427 LEFT JOIN branches ON branches.branchcode=subscription.branchcode
428 WHERE subscription.biblionumber = ?
430 my $sth = $dbh->prepare($query);
431 $sth->execute($biblionumber);
433 while ( my $subs = $sth->fetchrow_hashref ) {
434 $subs->{opacnote} //= "";
435 $subs->{ "periodicity" . $subs->{periodicity} } = 1;
436 $subs->{ "numberpattern" . $subs->{numberpattern} } = 1;
437 $subs->{ "status" . $subs->{'status'} } = 1;
439 $subs->{'abouttoexpire'} = abouttoexpire( $subs->{'subscriptionid'} );
440 $subs->{'subscriptionexpired'} = HasSubscriptionExpired( $subs->{'subscriptionid'} );
441 $subs->{cannotedit} = not can_edit_subscription( $subs );
447 =head2 GetFullSubscriptionsFromBiblionumber
449 $array_ref = GetFullSubscriptionsFromBiblionumber($biblionumber)
450 this function reads the serial table.
454 sub GetFullSubscriptionsFromBiblionumber {
455 my ($biblionumber) = @_;
456 my $dbh = C4::Context->dbh;
458 SELECT serial.serialid,
461 serial.publisheddate,
462 serial.publisheddatetext,
464 serial.notes as notes,
465 year(IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate)) as year,
466 biblio.title as bibliotitle,
467 subscription.branchcode AS branchcode,
468 subscription.subscriptionid AS subscriptionid,
469 subscription.location AS location
471 LEFT JOIN subscription ON
472 (serial.subscriptionid=subscription.subscriptionid)
473 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
474 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
475 WHERE subscription.biblionumber = ?
477 IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate) DESC,
478 serial.subscriptionid
480 my $sth = $dbh->prepare($query);
481 $sth->execute($biblionumber);
482 my $subscriptions = $sth->fetchall_arrayref( {} );
483 if (scalar @$subscriptions) {
484 my $cannotedit = not can_edit_subscription( $subscriptions->[0] );
485 for my $subscription ( @$subscriptions ) {
486 $subscription->{cannotedit} = $cannotedit;
490 return $subscriptions;
493 =head2 SearchSubscriptions
495 @results = SearchSubscriptions($args);
497 This function returns a list of hashrefs, one for each subscription
498 that meets the conditions specified by the $args hashref.
500 The valid search fields are:
514 The expiration_date search field is special; it specifies the maximum
515 subscription expiration date.
519 sub SearchSubscriptions {
522 my $additional_fields = $args->{additional_fields} // [];
523 my $matching_record_ids_for_additional_fields = [];
524 if ( @$additional_fields ) {
525 my @subscriptions = Koha::Subscriptions->filter_by_additional_fields($additional_fields)->as_list;
527 return () unless @subscriptions;
529 $matching_record_ids_for_additional_fields = [ map {
536 subscription.notes AS publicnotes,
537 subscriptionhistory.*,
539 biblio.notes AS biblionotes,
544 aqbooksellers.name AS vendorname,
547 LEFT JOIN subscriptionhistory USING(subscriptionid)
548 LEFT JOIN biblio ON biblio.biblionumber = subscription.biblionumber
549 LEFT JOIN biblioitems ON biblioitems.biblionumber = subscription.biblionumber
550 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
552 $query .= q| WHERE 1|;
555 if( $args->{biblionumber} ) {
556 push @where_strs, "biblio.biblionumber = ?";
557 push @where_args, $args->{biblionumber};
560 if( $args->{title} ){
561 my @words = split / /, $args->{title};
563 foreach my $word (@words) {
564 push @strs, "biblio.title LIKE ?";
565 push @args, "%$word%";
568 push @where_strs, '(' . join (' AND ', @strs) . ')';
569 push @where_args, @args;
573 push @where_strs, "biblioitems.issn LIKE ?";
574 push @where_args, "%$args->{issn}%";
577 push @where_strs, "biblioitems.ean LIKE ?";
578 push @where_args, "%$args->{ean}%";
580 if ( $args->{callnumber} ) {
581 push @where_strs, "subscription.callnumber LIKE ?";
582 push @where_args, "%$args->{callnumber}%";
584 if( $args->{publisher} ){
585 push @where_strs, "biblioitems.publishercode LIKE ?";
586 push @where_args, "%$args->{publisher}%";
588 if( $args->{bookseller} ){
589 push @where_strs, "aqbooksellers.name LIKE ?";
590 push @where_args, "%$args->{bookseller}%";
592 if( $args->{branch} ){
593 push @where_strs, "subscription.branchcode = ?";
594 push @where_args, "$args->{branch}";
596 if ( $args->{location} ) {
597 push @where_strs, "subscription.location = ?";
598 push @where_args, "$args->{location}";
600 if ( $args->{expiration_date} ) {
601 push @where_strs, "subscription.enddate <= ?";
602 push @where_args, "$args->{expiration_date}";
604 if( defined $args->{closed} ){
605 push @where_strs, "subscription.closed = ?";
606 push @where_args, "$args->{closed}";
610 $query .= ' AND ' . join(' AND ', @where_strs);
612 if ( @$additional_fields ) {
613 $query .= ' AND subscriptionid IN ('
614 . join( ', ', @$matching_record_ids_for_additional_fields )
618 $query .= " ORDER BY " . $args->{orderby} if $args->{orderby};
620 my $dbh = C4::Context->dbh;
621 my $sth = $dbh->prepare($query);
622 $sth->execute(@where_args);
623 my $results = $sth->fetchall_arrayref( {} );
625 for my $subscription ( @$results ) {
626 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
627 $subscription->{cannotdisplay} = not can_show_subscription( $subscription );
629 my $subscription_object = Koha::Subscriptions->find($subscription->{subscriptionid});
630 $subscription->{additional_fields} = { map { $_->field->name => $_->value }
631 $subscription_object->additional_field_values->as_list };
641 ($totalissues,@serials) = GetSerials($subscriptionid);
642 this function gets every serial not arrived for a given subscription
643 as well as the number of issues registered in the database (all types)
644 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
646 FIXME: We should return \@serials.
651 my ( $subscriptionid, $count ) = @_;
653 return unless $subscriptionid;
655 my $dbh = C4::Context->dbh;
657 # status = 2 is "arrived"
659 $count = 5 unless ($count);
661 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES, NOT_ISSUED ) );
662 my $query = "SELECT serialid,serialseq, status, publisheddate,
663 publisheddatetext, planneddate,notes, routingnotes
665 WHERE subscriptionid = ? AND status NOT IN ( $statuses )
666 ORDER BY IF(publisheddate IS NULL,planneddate,publisheddate) DESC";
667 my $sth = $dbh->prepare($query);
668 $sth->execute($subscriptionid);
670 while ( my $line = $sth->fetchrow_hashref ) {
671 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
672 push @serials, $line;
675 # OK, now add the last 5 issues arrives/missing
676 $query = "SELECT serialid,serialseq, status, planneddate, publisheddate,
677 publisheddatetext, notes, routingnotes
679 WHERE subscriptionid = ?
680 AND status IN ( $statuses )
681 ORDER BY IF(publisheddate IS NULL,planneddate,publisheddate) DESC
683 $sth = $dbh->prepare($query);
684 $sth->execute($subscriptionid);
685 while ( ( my $line = $sth->fetchrow_hashref ) && $counter < $count ) {
687 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
689 push @serials, $line;
692 $query = "SELECT count(*) FROM serial WHERE subscriptionid=?";
693 $sth = $dbh->prepare($query);
694 $sth->execute($subscriptionid);
695 my ($totalissues) = $sth->fetchrow;
696 return ( $totalissues, @serials );
701 @serials = GetSerials2($subscriptionid,$statuses);
702 this function returns every serial waited for a given subscription
703 as well as the number of issues registered in the database (all types)
704 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
706 $statuses is an arrayref of statuses and is mandatory.
711 my ( $subscription, $statuses ) = @_;
713 return unless ($subscription and @$statuses);
715 my $dbh = C4::Context->dbh;
717 SELECT serialid,serialseq, status, planneddate, publisheddate,
718 publisheddatetext, notes, routingnotes
720 WHERE subscriptionid=?
722 . q| AND status IN (| . join( ",", ('?') x @$statuses ) . q|)|
724 ORDER BY publisheddate,serialid DESC
726 my $sth = $dbh->prepare($query);
727 $sth->execute( $subscription, @$statuses );
730 while ( my $line = $sth->fetchrow_hashref ) {
731 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
732 push @serials, $line;
737 =head2 GetLatestSerials
739 \@serials = GetLatestSerials($subscriptionid,$limit)
740 get the $limit's latest serials arrived or missing for a given subscription
742 a ref to an array which contains all of the latest serials stored into a hash.
746 sub GetLatestSerials {
747 my ( $subscriptionid, $limit ) = @_;
749 return unless ($subscriptionid and $limit);
751 my $dbh = C4::Context->dbh;
753 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES ) );
754 my $strsth = "SELECT serialid,serialseq, status, planneddate, publisheddate, publisheddatetext, notes
756 WHERE subscriptionid = ?
757 AND status IN ($statuses)
758 ORDER BY publisheddate DESC LIMIT 0,$limit
760 my $sth = $dbh->prepare($strsth);
761 $sth->execute($subscriptionid);
763 while ( my $line = $sth->fetchrow_hashref ) {
764 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
765 push @serials, $line;
771 =head2 GetPreviousSerialid
773 $serialid = GetPreviousSerialid($subscriptionid, $nth)
774 get the $nth's previous serial for the given subscriptionid
780 sub GetPreviousSerialid {
781 my ( $subscriptionid, $nth ) = @_;
783 my $dbh = C4::Context->dbh;
787 my $strsth = "SELECT serialid
789 WHERE subscriptionid = ?
791 ORDER BY serialid DESC LIMIT $nth,1
793 my $sth = $dbh->prepare($strsth);
794 $sth->execute($subscriptionid);
796 my $line = $sth->fetchrow_hashref;
797 $return = $line->{'serialid'} if ($line);
805 $nextseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
806 $newinnerloop1, $newinnerloop2, $newinnerloop3
807 ) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate );
809 $subscription is a hashref containing all the attributes of the table
811 $pattern is a hashref containing all the attributes of the table
812 'subscription_numberpatterns'.
813 $frequency is a hashref containing all the attributes of the table 'subscription_frequencies'
814 $planneddate is a date string in iso format.
815 This function get the next issue for the subscription given on input arg
820 my ($subscription, $pattern, $frequency, $planneddate) = @_;
822 return unless ($subscription and $pattern);
824 my ( $newlastvalue1, $newlastvalue2, $newlastvalue3,
825 $newinnerloop1, $newinnerloop2, $newinnerloop3 );
828 if ($subscription->{'skip_serialseq'}) {
829 my @irreg = split /;/, $subscription->{'irregularity'};
831 my $irregularities = {};
832 $irregularities->{$_} = 1 foreach(@irreg);
833 my $issueno = GetFictiveIssueNumber($subscription, $planneddate, $frequency) + 1;
834 while($irregularities->{$issueno}) {
841 my $numberingmethod = $pattern->{numberingmethod};
843 if ($numberingmethod) {
844 $calculated = $numberingmethod;
845 my $locale = $subscription->{locale};
846 $newlastvalue1 = $subscription->{lastvalue1} || 0;
847 $newlastvalue2 = $subscription->{lastvalue2} || 0;
848 $newlastvalue3 = $subscription->{lastvalue3} || 0;
849 $newinnerloop1 = $subscription->{innerloop1} || 0;
850 $newinnerloop2 = $subscription->{innerloop2} || 0;
851 $newinnerloop3 = $subscription->{innerloop3} || 0;
854 $calc{$_} = 1 if ($numberingmethod =~ /\{$_\}/);
857 for(my $i = 0; $i < $count; $i++) {
859 # check if we have to increase the new value.
861 if ($newinnerloop1 >= $pattern->{every1}) {
863 $newlastvalue1 += $pattern->{add1};
865 # reset counter if needed.
866 $newlastvalue1 = $pattern->{setto1} if ($newlastvalue1 > $pattern->{whenmorethan1});
869 # check if we have to increase the new value.
871 if ($newinnerloop2 >= $pattern->{every2}) {
873 $newlastvalue2 += $pattern->{add2};
875 # reset counter if needed.
876 $newlastvalue2 = $pattern->{setto2} if ($newlastvalue2 > $pattern->{whenmorethan2});
879 # check if we have to increase the new value.
881 if ($newinnerloop3 >= $pattern->{every3}) {
883 $newlastvalue3 += $pattern->{add3};
885 # reset counter if needed.
886 $newlastvalue3 = $pattern->{setto3} if ($newlastvalue3 > $pattern->{whenmorethan3});
890 my $newlastvalue1string = _numeration( $newlastvalue1, $pattern->{numbering1}, $locale );
891 $calculated =~ s/\{X\}/$newlastvalue1string/g;
894 my $newlastvalue2string = _numeration( $newlastvalue2, $pattern->{numbering2}, $locale );
895 $calculated =~ s/\{Y\}/$newlastvalue2string/g;
898 my $newlastvalue3string = _numeration( $newlastvalue3, $pattern->{numbering3}, $locale );
899 $calculated =~ s/\{Z\}/$newlastvalue3string/g;
905 $newlastvalue1, $newlastvalue2, $newlastvalue3,
906 $newinnerloop1, $newinnerloop2, $newinnerloop3);
911 $calculated = GetSeq($subscription, $pattern)
912 $subscription is a hashref containing all the attributes of the table 'subscription'
913 $pattern is a hashref containing all the attributes of the table 'subscription_numberpatterns'
914 this function transforms {X},{Y},{Z} to 150,0,0 for example.
916 the sequence in string format
921 my ($subscription, $pattern) = @_;
923 return unless ($subscription and $pattern);
925 my $locale = $subscription->{locale};
927 my $calculated = $pattern->{numberingmethod};
929 my $newlastvalue1 = $subscription->{'lastvalue1'} || 0;
930 $newlastvalue1 = _numeration($newlastvalue1, $pattern->{numbering1}, $locale) if ($pattern->{numbering1}); # reset counter if needed.
931 $calculated =~ s/\{X\}/$newlastvalue1/g;
933 my $newlastvalue2 = $subscription->{'lastvalue2'} || 0;
934 $newlastvalue2 = _numeration($newlastvalue2, $pattern->{numbering2}, $locale) if ($pattern->{numbering2}); # reset counter if needed.
935 $calculated =~ s/\{Y\}/$newlastvalue2/g;
937 my $newlastvalue3 = $subscription->{'lastvalue3'} || 0;
938 $newlastvalue3 = _numeration($newlastvalue3, $pattern->{numbering3}, $locale) if ($pattern->{numbering3}); # reset counter if needed.
939 $calculated =~ s/\{Z\}/$newlastvalue3/g;
943 =head2 GetExpirationDate
945 $enddate = GetExpirationDate($subscriptionid, [$startdate])
947 this function return the next expiration date for a subscription given on input args.
954 sub GetExpirationDate {
955 my ( $subscriptionid, $startdate ) = @_;
957 return unless ($subscriptionid);
959 my $dbh = C4::Context->dbh;
960 my $subscription = GetSubscription($subscriptionid);
963 # we don't do the same test if the subscription is based on X numbers or on X weeks/months
964 $enddate = $startdate || $subscription->{startdate};
965 my @date = split( /-/, $enddate );
967 return if ( scalar(@date) != 3 || not check_date(@date) );
969 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
970 if ( $frequency and $frequency->{unit} ) {
973 if ( my $length = $subscription->{numberlength} ) {
975 #calculate the date of the last issue.
976 for ( my $i = 1 ; $i <= $length ; $i++ ) {
977 $enddate = GetNextDate( $subscription, $enddate, $frequency );
979 } elsif ( $subscription->{monthlength} ) {
980 if ( $$subscription{startdate} ) {
981 my @enddate = Add_Delta_YM( $date[0], $date[1], $date[2], 0, $subscription->{monthlength} );
982 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
984 } elsif ( $subscription->{weeklength} ) {
985 if ( $$subscription{startdate} ) {
986 my @date = split( /-/, $subscription->{startdate} );
987 my @enddate = Add_Delta_Days( $date[0], $date[1], $date[2], $subscription->{weeklength} * 7 );
988 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
991 $enddate = $subscription->{enddate};
995 return $subscription->{enddate};
999 =head2 CountSubscriptionFromBiblionumber
1001 $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber)
1002 this returns a count of the subscriptions for a given biblionumber
1004 the number of subscriptions
1008 sub CountSubscriptionFromBiblionumber {
1009 my ($biblionumber) = @_;
1011 return unless ($biblionumber);
1013 my $dbh = C4::Context->dbh;
1014 my $query = "SELECT count(*) FROM subscription WHERE biblionumber=?";
1015 my $sth = $dbh->prepare($query);
1016 $sth->execute($biblionumber);
1017 my $subscriptionsnumber = $sth->fetchrow;
1018 return $subscriptionsnumber;
1021 =head2 ModSubscriptionHistory
1023 ModSubscriptionHistory($subscriptionid,$histstartdate,$enddate,$recievedlist,$missinglist,$opacnote,$librariannote);
1025 this function modifies the history of a subscription. Put your new values on input arg.
1026 returns the number of rows affected
1030 sub ModSubscriptionHistory {
1031 my ( $subscriptionid, $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote ) = @_;
1033 return unless ($subscriptionid);
1035 my $dbh = C4::Context->dbh;
1036 my $query = "UPDATE subscriptionhistory
1037 SET histstartdate=?,histenddate=?,recievedlist=?,missinglist=?,opacnote=?,librariannote=?
1038 WHERE subscriptionid=?
1040 my $sth = $dbh->prepare($query);
1041 $receivedlist =~ s/^; // if $receivedlist;
1042 $missinglist =~ s/^; // if $missinglist;
1043 $opacnote =~ s/^; // if $opacnote;
1044 $sth->execute( $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote, $subscriptionid );
1048 =head2 ModSerialStatus
1050 ModSerialStatus($serialid, $serialseq, $planneddate, $publisheddate,
1051 $publisheddatetext, $status, $notes);
1053 This function modify the serial status. Serial status is a number.(eg 2 is "arrived")
1054 Note : if we change from "waited" to something else,then we will have to create a new "waited" entry
1058 sub ModSerialStatus {
1059 my ($serialid, $serialseq, $planneddate, $publisheddate, $publisheddatetext,
1060 $status, $notes) = @_;
1062 return unless ($serialid);
1064 #It is a usual serial
1065 # 1st, get previous status :
1066 my $dbh = C4::Context->dbh;
1067 my $query = "SELECT serial.subscriptionid,serial.status,subscription.periodicity,serial.routingnotes
1068 FROM serial, subscription
1069 WHERE serial.subscriptionid=subscription.subscriptionid
1071 my $sth = $dbh->prepare($query);
1072 $sth->execute($serialid);
1073 my ( $subscriptionid, $oldstatus, $periodicity, $routingnotes ) = $sth->fetchrow;
1074 my $frequency = GetSubscriptionFrequency($periodicity);
1076 # change status & update subscriptionhistory
1078 if ( $status == DELETED ) {
1079 DelIssue( { 'serialid' => $serialid, 'subscriptionid' => $subscriptionid, 'serialseq' => $serialseq } );
1083 SET serialseq = ?, publisheddate = ?, publisheddatetext = ?,
1084 planneddate = ?, status = ?, notes = ?, routingnotes = ?
1087 $sth = $dbh->prepare($query);
1088 $sth->execute( $serialseq, $publisheddate, $publisheddatetext,
1089 $planneddate, $status, $notes, $routingnotes, $serialid );
1090 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1091 $sth = $dbh->prepare($query);
1092 $sth->execute($subscriptionid);
1093 my $val = $sth->fetchrow_hashref;
1094 unless ( $val->{manualhistory} ) {
1095 $query = "SELECT missinglist,recievedlist FROM subscriptionhistory WHERE subscriptionid=?";
1096 $sth = $dbh->prepare($query);
1097 $sth->execute($subscriptionid);
1098 my ( $missinglist, $recievedlist ) = $sth->fetchrow;
1100 if ( $status == ARRIVED || ($oldstatus == ARRIVED && $status != ARRIVED) ) {
1101 $recievedlist = _handle_seqno($serialseq, $recievedlist);
1104 # in case serial has been previously marked as missing
1105 if (grep /$status/, (EXPECTED, ARRIVED, LATE, CLAIMED)) {
1106 $missinglist = _handle_seqno($serialseq, $missinglist, 'REMOVE');
1109 $missinglist = _handle_seqno($serialseq, $missinglist) if grep { $_ == $status } MISSING_STATUSES;
1110 $missinglist .= "; not issued $serialseq" if $status == NOT_ISSUED and not _handle_seqno($serialseq, $missinglist, 'CHECK');
1112 $query = "UPDATE subscriptionhistory SET recievedlist=?, missinglist=? WHERE subscriptionid=?";
1113 $sth = $dbh->prepare($query);
1114 $recievedlist =~ s/^; //;
1115 $missinglist =~ s/^; //;
1116 $sth->execute( $recievedlist, $missinglist, $subscriptionid );
1120 # create new expected entry if needed (ie : was "expected" and has changed)
1121 my $otherIssueExpected = scalar findSerialsByStatus(EXPECTED, $subscriptionid);
1122 if ( !$otherIssueExpected && $oldstatus == EXPECTED && $status != EXPECTED ) {
1123 my $subscription = GetSubscription($subscriptionid);
1124 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1125 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1129 $newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
1130 $newinnerloop1, $newinnerloop2, $newinnerloop3
1132 = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate );
1134 # next date (calculated from actual date & frequency parameters)
1135 my $nextpublisheddate = GetNextDate($subscription, $publisheddate, $frequency, 1);
1136 my $nextpubdate = $nextpublisheddate;
1137 $query = "UPDATE subscription SET lastvalue1=?, lastvalue2=?, lastvalue3=?, innerloop1=?, innerloop2=?, innerloop3=?
1138 WHERE subscriptionid = ?";
1139 $sth = $dbh->prepare($query);
1140 $sth->execute( $newlastvalue1, $newlastvalue2, $newlastvalue3, $newinnerloop1, $newinnerloop2, $newinnerloop3, $subscriptionid );
1141 my $newnote = C4::Context->preference('PreserveSerialNotes') ? $notes : "";
1142 NewIssue( $newserialseq, $subscriptionid, $subscription->{'biblionumber'}, 1, $nextpubdate, $nextpubdate, undef, $newnote, $routingnotes );
1143 # check if an alert must be sent... (= a letter is defined & status became "arrived"
1144 if ( $subscription->{letter} && $status == ARRIVED && $oldstatus != ARRIVED ) {
1145 require C4::Letters;
1146 C4::Letters::SendAlerts( 'issue', $serialid, $subscription->{letter} );
1154 # Adds or removes seqno from list when needed; returns list
1155 # Or checks and returns true when present
1157 my ( $seq, $list, $op ) = @_; # op = ADD | REMOVE | CHECK (default: ADD)
1159 $seq_r =~ s/([()])/\\$1/g; # Adjust disturbing parentheses for regex, maybe extend in future
1161 if( !$op or $op eq 'ADD' ) {
1162 $list .= "; $seq" if $list !~ /(^|;)\s*$seq_r(?=;|$)/;
1163 } elsif( $op eq 'REMOVE' ) {
1164 $list=~ s/(^|;)\s*(not issued )?$seq_r(?=;|$)//g;
1166 return $list =~ /(^|;)\s*$seq_r(?=;|$)/ ? 1 : q{};
1171 =head2 GetNextExpected
1173 $nextexpected = GetNextExpected($subscriptionid)
1175 Get the planneddate for the current expected issue of the subscription.
1181 planneddate => ISO date
1186 sub GetNextExpected {
1187 my ($subscriptionid) = @_;
1189 my $dbh = C4::Context->dbh;
1193 WHERE subscriptionid = ?
1197 my $sth = $dbh->prepare($query);
1199 # Each subscription has only one 'expected' issue.
1200 $sth->execute( $subscriptionid, EXPECTED );
1201 my $nextissue = $sth->fetchrow_hashref;
1202 if ( !$nextissue ) {
1206 WHERE subscriptionid = ?
1207 ORDER BY publisheddate DESC
1210 $sth = $dbh->prepare($query);
1211 $sth->execute($subscriptionid);
1212 $nextissue = $sth->fetchrow_hashref;
1214 foreach(qw/planneddate publisheddate/) {
1215 # or should this default to 1st Jan ???
1216 $nextissue->{$_} //= strftime( '%Y-%m-%d', localtime );
1222 =head2 ModNextExpected
1224 ModNextExpected($subscriptionid,$date)
1226 Update the planneddate for the current expected issue of the subscription.
1227 This will modify all future prediction results.
1229 C<$date> is an ISO date.
1235 sub ModNextExpected {
1236 my ( $subscriptionid, $date ) = @_;
1237 my $dbh = C4::Context->dbh;
1239 #FIXME: Would expect to only set planneddate, but we set both on new issue creation, so updating it here
1240 my $sth = $dbh->prepare('UPDATE serial SET planneddate=?,publisheddate=? WHERE subscriptionid=? AND status=?');
1242 # Each subscription has only one 'expected' issue.
1243 $sth->execute( $date, $date, $subscriptionid, EXPECTED );
1248 =head2 GetSubscriptionIrregularities
1252 =item @irreg = &GetSubscriptionIrregularities($subscriptionid);
1253 get the list of irregularities for a subscription
1259 sub GetSubscriptionIrregularities {
1260 my $subscriptionid = shift;
1262 return unless $subscriptionid;
1264 my $dbh = C4::Context->dbh;
1268 WHERE subscriptionid = ?
1270 my $sth = $dbh->prepare($query);
1271 $sth->execute($subscriptionid);
1273 my ($result) = $sth->fetchrow_array;
1274 my @irreg = split /;/, $result;
1279 =head2 ModSubscription
1281 this function modifies a subscription. Put all new values on input args.
1282 returns the number of rows affected
1286 sub ModSubscription {
1288 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
1289 $periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
1290 $numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
1291 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
1292 $biblionumber, $callnumber, $notes, $letter, $manualhistory,
1293 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1294 $graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
1295 $itemtype, $previousitemtype, $mana_id, $ccode, $published_on_template
1298 my $subscription = Koha::Subscriptions->find($subscriptionid);
1301 librarian => $auser,
1302 branchcode => $branchcode,
1303 aqbooksellerid => $aqbooksellerid,
1305 aqbudgetid => $aqbudgetid,
1306 biblionumber => $biblionumber,
1307 startdate => $startdate,
1308 periodicity => $periodicity,
1309 numberlength => $numberlength,
1310 weeklength => $weeklength,
1311 monthlength => $monthlength,
1312 lastvalue1 => $lastvalue1,
1313 innerloop1 => $innerloop1,
1314 lastvalue2 => $lastvalue2,
1315 innerloop2 => $innerloop2,
1316 lastvalue3 => $lastvalue3,
1317 innerloop3 => $innerloop3,
1321 firstacquidate => $firstacquidate,
1322 irregularity => $irregularity,
1323 numberpattern => $numberpattern,
1325 callnumber => $callnumber,
1326 manualhistory => $manualhistory,
1327 internalnotes => $internalnotes,
1328 serialsadditems => $serialsadditems,
1329 staffdisplaycount => $staffdisplaycount,
1330 opacdisplaycount => $opacdisplaycount,
1331 graceperiod => $graceperiod,
1332 location => $location,
1333 enddate => $enddate,
1334 skip_serialseq => $skip_serialseq,
1335 itemtype => $itemtype,
1336 previousitemtype => $previousitemtype,
1337 mana_id => $mana_id,
1339 published_on_template => $published_on_template,
1342 # FIXME Must be $subscription->serials
1343 # FIXME We shouldn't need serial.subscription (instead use serial->subscription->biblionumber)
1344 Koha::Serials->search({ subscriptionid => $subscriptionid })->update({ biblionumber => $biblionumber });
1346 logaction( "SERIAL", "MODIFY", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1348 $subscription->discard_changes;
1349 return $subscription;
1352 =head2 NewSubscription
1354 $subscriptionid = &NewSubscription($auser,branchcode,$aqbooksellerid,$cost,$aqbudgetid,$biblionumber,
1355 $startdate,$periodicity,$numberlength,$weeklength,$monthlength,
1356 $lastvalue1,$innerloop1,$lastvalue2,$innerloop2,$lastvalue3,$innerloop3,
1357 $status, $notes, $letter, $firstacquidate, $irregularity, $numberpattern,
1358 $locale, $callnumber, $manualhistory, $internalnotes, $serialsadditems,
1359 $staffdisplaycount, $opacdisplaycount, $graceperiod, $location, $enddate,
1360 $skip_serialseq, $itemtype, $previousitemtype);
1362 Create a new subscription with value given on input args.
1365 the id of this new subscription
1369 sub NewSubscription {
1371 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1372 $startdate, $periodicity, $numberlength, $weeklength, $monthlength,
1373 $lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
1374 $innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
1375 $numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
1376 $serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
1377 $location, $enddate, $skip_serialseq, $itemtype, $previousitemtype, $mana_id, $ccode,
1378 $published_on_template,
1380 my $dbh = C4::Context->dbh;
1382 my $subscription = Koha::Subscription->new(
1384 librarian => $auser,
1385 branchcode => $branchcode,
1386 aqbooksellerid => $aqbooksellerid,
1388 aqbudgetid => $aqbudgetid,
1389 biblionumber => $biblionumber,
1390 startdate => $startdate,
1391 periodicity => $periodicity,
1392 numberlength => $numberlength,
1393 weeklength => $weeklength,
1394 monthlength => $monthlength,
1395 lastvalue1 => $lastvalue1,
1396 innerloop1 => $innerloop1,
1397 lastvalue2 => $lastvalue2,
1398 innerloop2 => $innerloop2,
1399 lastvalue3 => $lastvalue3,
1400 innerloop3 => $innerloop3,
1404 firstacquidate => $firstacquidate,
1405 irregularity => $irregularity,
1406 numberpattern => $numberpattern,
1408 callnumber => $callnumber,
1409 manualhistory => $manualhistory,
1410 internalnotes => $internalnotes,
1411 serialsadditems => $serialsadditems,
1412 staffdisplaycount => $staffdisplaycount,
1413 opacdisplaycount => $opacdisplaycount,
1414 graceperiod => $graceperiod,
1415 location => $location,
1416 enddate => $enddate,
1417 skip_serialseq => $skip_serialseq,
1418 itemtype => $itemtype,
1419 previousitemtype => $previousitemtype,
1420 mana_id => $mana_id,
1422 published_on_template => $published_on_template,
1425 $subscription->discard_changes;
1426 my $subscriptionid = $subscription->subscriptionid;
1427 my ( $query, $sth );
1429 $enddate = GetExpirationDate( $subscriptionid, $startdate );
1433 WHERE subscriptionid=?
1435 $sth = $dbh->prepare($query);
1436 $sth->execute( $enddate, $subscriptionid );
1439 # then create the 1st expected number
1441 INSERT INTO subscriptionhistory
1442 (biblionumber, subscriptionid, histstartdate, missinglist, recievedlist)
1443 VALUES (?,?,?, '', '')
1445 $sth = $dbh->prepare($query);
1446 $sth->execute( $biblionumber, $subscriptionid, $startdate);
1448 # reread subscription to get a hash (for calculation of the 1st issue number)
1449 $subscription = GetSubscription($subscriptionid); # We should not do that
1450 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1452 # calculate issue number
1453 my $serialseq = GetSeq($subscription, $pattern) || q{};
1457 serialseq => $serialseq,
1458 serialseq_x => $subscription->{'lastvalue1'},
1459 serialseq_y => $subscription->{'lastvalue2'},
1460 serialseq_z => $subscription->{'lastvalue3'},
1461 subscriptionid => $subscriptionid,
1462 biblionumber => $biblionumber,
1464 planneddate => $firstacquidate,
1465 publisheddate => $firstacquidate,
1469 logaction( "SERIAL", "ADD", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1471 #set serial flag on biblio if not already set.
1472 my $biblio = Koha::Biblios->find( $biblionumber );
1473 if ( $biblio and !$biblio->serial ) {
1474 my $record = $biblio->metadata->record;
1475 my ( $tag, $subf ) = GetMarcFromKohaField( 'biblio.serial' );
1477 eval { $record->field($tag)->update( $subf => 1 ); };
1479 ModBiblio( $record, $biblionumber, $biblio->frameworkcode );
1481 return $subscriptionid;
1484 =head2 GetSubscriptionLength
1486 my ($numberlength, $weeklength, $monthlength) = GetSubscriptionLength( $subtype, $sublength );
1488 This function calculates the subscription length.
1492 sub GetSubscriptionLength {
1493 my ($subtype, $length) = @_;
1495 return unless looks_like_number($length);
1499 $subtype eq 'issues' ? $length : 0,
1500 $subtype eq 'weeks' ? $length : 0,
1501 $subtype eq 'months' ? $length : 0,
1506 =head2 ReNewSubscription
1508 ReNewSubscription($params);
1510 $params is a hashref with the following keys: subscriptionid, user, startdate, numberlength, weeklength, monthlength, note, branchcode
1512 this function renew a subscription with values given on input args.
1516 sub ReNewSubscription {
1517 my ( $params ) = @_;
1518 my $subscriptionid = $params->{subscriptionid};
1519 my $user = $params->{user};
1520 my $startdate = $params->{startdate};
1521 my $numberlength = $params->{numberlength};
1522 my $weeklength = $params->{weeklength};
1523 my $monthlength = $params->{monthlength};
1524 my $note = $params->{note};
1525 my $branchcode = $params->{branchcode};
1527 my $dbh = C4::Context->dbh;
1528 my $subscription = GetSubscription($subscriptionid);
1532 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
1533 WHERE biblio.biblionumber=?
1535 my $sth = $dbh->prepare($query);
1536 $sth->execute( $subscription->{biblionumber} );
1537 my $biblio = $sth->fetchrow_hashref;
1539 if ( C4::Context->preference("RenewSerialAddsSuggestion") ) {
1540 Koha::Suggestion->new(
1542 'suggestedby' => $user,
1543 'title' => $subscription->{bibliotitle},
1544 'author' => $biblio->{author},
1545 'publishercode' => $biblio->{publishercode},
1547 'biblionumber' => $subscription->{biblionumber},
1548 'branchcode' => $branchcode,
1553 $numberlength ||= 0; # Should not we raise an exception instead?
1556 # renew subscription
1559 SET startdate=?,numberlength=?,weeklength=?,monthlength=?,reneweddate=NOW()
1560 WHERE subscriptionid=?
1562 $sth = $dbh->prepare($query);
1563 $sth->execute( $startdate, $numberlength, $weeklength, $monthlength, $subscriptionid );
1564 my $enddate = GetExpirationDate($subscriptionid);
1568 WHERE subscriptionid=?
1570 $sth = $dbh->prepare($query);
1571 $sth->execute( $enddate, $subscriptionid );
1573 logaction( "SERIAL", "RENEW", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1579 NewIssue($serialseq,$subscriptionid,$biblionumber,$status, $planneddate, $publisheddate, $notes, $routingnotes)
1581 Create a new issue stored on the database.
1582 Note : we have to update the recievedlist and missinglist on subscriptionhistory for this subscription.
1583 returns the serial id
1588 my ( $serialseq, $subscriptionid, $biblionumber, $status, $planneddate,
1589 $publisheddate, $publisheddatetext, $notes, $routingnotes ) = @_;
1590 ### FIXME biblionumber CAN be provided by subscriptionid. So Do we STILL NEED IT ?
1592 return unless ($subscriptionid);
1594 my $schema = Koha::Database->new()->schema();
1596 my $subscription = Koha::Subscriptions->find( $subscriptionid );
1598 if ( my $template = $subscription->published_on_template ) {
1599 $publisheddatetext = process_tt(
1602 subscription => $subscription,
1603 serialseq => $serialseq,
1604 serialseq_x => $subscription->lastvalue1(),
1605 serialseq_y => $subscription->lastvalue2(),
1606 serialseq_z => $subscription->lastvalue3(),
1607 subscriptionid => $subscriptionid,
1608 biblionumber => $biblionumber,
1610 planneddate => $planneddate,
1611 publisheddate => $publisheddate,
1612 publisheddatetext => $publisheddatetext,
1614 routingnotes => $routingnotes,
1619 my $serial = Koha::Serial->new(
1621 serialseq => $serialseq,
1622 serialseq_x => $subscription->lastvalue1(),
1623 serialseq_y => $subscription->lastvalue2(),
1624 serialseq_z => $subscription->lastvalue3(),
1625 subscriptionid => $subscriptionid,
1626 biblionumber => $biblionumber,
1628 planneddate => $planneddate,
1629 publisheddate => $publisheddate,
1630 publisheddatetext => $publisheddatetext,
1632 routingnotes => $routingnotes,
1636 my $serialid = $serial->id();
1638 my $subscription_history = Koha::Subscription::Histories->find($subscriptionid);
1639 my $missinglist = $subscription_history->missinglist();
1640 my $recievedlist = $subscription_history->recievedlist();
1642 if ( $status == ARRIVED ) {
1643 ### TODO Add a feature that improves recognition and description.
1644 ### As such count (serialseq) i.e. : N18,2(N19),N20
1645 ### Would use substr and index But be careful to previous presence of ()
1646 $recievedlist .= "; $serialseq" unless ( index( $recievedlist, $serialseq ) > 0 );
1648 if ( grep { $_ eq $status } (MISSING_STATUSES) ) {
1649 $missinglist .= "; $serialseq" unless ( index( $missinglist, $serialseq ) > 0 );
1652 $recievedlist =~ s/^; //;
1653 $missinglist =~ s/^; //;
1655 $subscription_history->recievedlist($recievedlist);
1656 $subscription_history->missinglist($missinglist);
1657 $subscription_history->store();
1662 =head2 HasSubscriptionStrictlyExpired
1664 1 or 0 = HasSubscriptionStrictlyExpired($subscriptionid)
1666 the subscription has stricly expired when today > the end subscription date
1669 1 if true, 0 if false, -1 if the expiration date is not set.
1673 sub HasSubscriptionStrictlyExpired {
1675 # Getting end of subscription date
1676 my ($subscriptionid) = @_;
1678 return unless ($subscriptionid);
1680 my $dbh = C4::Context->dbh;
1681 my $subscription = GetSubscription($subscriptionid);
1682 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1684 # If the expiration date is set
1685 if ( $expirationdate != 0 ) {
1686 my ( $endyear, $endmonth, $endday ) = split( '-', $expirationdate );
1688 # Getting today's date
1689 my ( $nowyear, $nowmonth, $nowday ) = Today();
1691 # if today's date > expiration date, then the subscription has stricly expired
1692 if ( Delta_Days( $nowyear, $nowmonth, $nowday, $endyear, $endmonth, $endday ) < 0 ) {
1699 # There are some cases where the expiration date is not set
1700 # As we can't determine if the subscription has expired on a date-basis,
1706 =head2 HasSubscriptionExpired
1708 $has_expired = HasSubscriptionExpired($subscriptionid)
1710 the subscription has expired when the next issue to arrive is out of subscription limit.
1713 0 if the subscription has not expired
1714 1 if the subscription has expired
1715 2 if has subscription does not have a valid expiration date set
1719 sub HasSubscriptionExpired {
1720 my ($subscriptionid) = @_;
1722 return unless ($subscriptionid);
1724 my $dbh = C4::Context->dbh;
1725 my $subscription = GetSubscription($subscriptionid);
1726 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1727 if ( $frequency and $frequency->{unit} ) {
1728 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1729 if (!defined $expirationdate) {
1730 $expirationdate = q{};
1733 SELECT max(planneddate)
1735 WHERE subscriptionid=?
1737 my $sth = $dbh->prepare($query);
1738 $sth->execute($subscriptionid);
1739 my ($res) = $sth->fetchrow;
1740 if (!$res || $res=~m/^0000/) {
1743 my @res = split( /-/, $res );
1744 my @endofsubscriptiondate = split( /-/, $expirationdate );
1745 return 2 if ( scalar(@res) != 3 || scalar(@endofsubscriptiondate) != 3 || not check_date(@res) || not check_date(@endofsubscriptiondate) );
1747 if ( ( @endofsubscriptiondate && Delta_Days( $res[0], $res[1], $res[2], $endofsubscriptiondate[0], $endofsubscriptiondate[1], $endofsubscriptiondate[2] ) <= 0 )
1752 if ( $subscription->{'numberlength'} ) {
1753 my $countreceived = countissuesfrom( $subscriptionid, $subscription->{'startdate'} );
1754 return 1 if ( $countreceived > $subscription->{'numberlength'} );
1760 return 0; # Notice that you'll never get here.
1763 =head2 DelSubscription
1765 DelSubscription($subscriptionid)
1766 this function deletes subscription which has $subscriptionid as id.
1770 sub DelSubscription {
1771 my ($subscriptionid) = @_;
1772 my $dbh = C4::Context->dbh;
1773 $dbh->do("DELETE FROM subscription WHERE subscriptionid=?", undef, $subscriptionid);
1775 Koha::AdditionalFieldValues->search({
1776 'field.tablename' => 'subscription',
1777 'me.record_id' => $subscriptionid,
1778 }, { join => 'field' })->delete;
1780 logaction( "SERIAL", "DELETE", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1785 DelIssue($serialseq,$subscriptionid)
1786 this function deletes an issue which has $serialseq and $subscriptionid given on input arg.
1788 returns the number of rows affected
1793 my ($dataissue) = @_;
1794 my $dbh = C4::Context->dbh;
1795 ### TODO Add itemdeletion. Would need to get itemnumbers. Should be in a pref ?
1800 AND subscriptionid= ?
1802 my $mainsth = $dbh->prepare($query);
1803 $mainsth->execute( $dataissue->{'serialid'}, $dataissue->{'subscriptionid'} );
1805 #Delete element from subscription history
1806 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1807 my $sth = $dbh->prepare($query);
1808 $sth->execute( $dataissue->{'subscriptionid'} );
1809 my $val = $sth->fetchrow_hashref;
1810 unless ( $val->{manualhistory} ) {
1812 SELECT * FROM subscriptionhistory
1813 WHERE subscriptionid= ?
1815 my $sth = $dbh->prepare($query);
1816 $sth->execute( $dataissue->{'subscriptionid'} );
1817 my $data = $sth->fetchrow_hashref;
1818 my $serialseq = $dataissue->{'serialseq'};
1819 $data->{'missinglist'} =~ s/\b$serialseq\b//;
1820 $data->{'recievedlist'} =~ s/\b$serialseq\b//;
1821 my $strsth = "UPDATE subscriptionhistory SET " . join( ",", map { join( "=", $_, $dbh->quote( $data->{$_} ) ) } keys %$data ) . " WHERE subscriptionid=?";
1822 $sth = $dbh->prepare($strsth);
1823 $sth->execute( $dataissue->{'subscriptionid'} );
1826 return $mainsth->rows;
1829 =head2 GetLateOrMissingIssues
1831 @issuelist = GetLateMissingIssues($supplierid,$serialid)
1833 this function selects missing issues on database - where serial.status = MISSING* or serial.status = LATE or planneddate<now
1836 the issuelist as an array of hash refs. Each element of this array contains
1837 name,title,planneddate,serialseq,serial.subscriptionid from tables : subscription, serial & biblio
1841 sub GetLateOrMissingIssues {
1842 my ( $supplierid, $serialid, $order ) = @_;
1844 return unless ( $supplierid or $serialid );
1846 my $dbh = C4::Context->dbh;
1851 $byserial = "and serialid = " . $serialid;
1854 $order .= ", title";
1858 my $missing_statuses_string = join ',', (MISSING_STATUSES);
1860 $sth = $dbh->prepare(
1862 serialid, aqbooksellerid, name,
1863 biblio.title, biblioitems.issn, planneddate, serialseq,
1864 serial.status, serial.subscriptionid, claimdate, claims_count,
1865 subscription.branchcode, serial.publisheddate
1867 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1868 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1869 LEFT JOIN biblioitems ON subscription.biblionumber=biblioitems.biblionumber
1870 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1871 WHERE subscription.subscriptionid = serial.subscriptionid
1872 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1873 AND subscription.aqbooksellerid=$supplierid
1878 $sth = $dbh->prepare(
1880 serialid, aqbooksellerid, name,
1881 biblio.title, planneddate, serialseq,
1882 serial.status, serial.subscriptionid, claimdate, claims_count,
1883 subscription.branchcode, serial.publisheddate
1885 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1886 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1887 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1888 WHERE subscription.subscriptionid = serial.subscriptionid
1889 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1894 $sth->execute( EXPECTED, LATE, CLAIMED );
1896 while ( my $line = $sth->fetchrow_hashref ) {
1897 $line->{"status".$line->{status}} = 1;
1899 my $subscription_object = Koha::Subscriptions->find($line->{subscriptionid});
1900 $line->{additional_fields} = { map { $_->field->name => $_->value }
1901 $subscription_object->additional_field_values->as_list };
1903 push @issuelist, $line;
1910 &updateClaim($serialid)
1912 this function updates the time when a claim is issued for late/missing items
1914 called from claims.pl file
1919 my ($serialids) = @_;
1920 return unless $serialids;
1921 unless ( ref $serialids ) {
1922 $serialids = [ $serialids ];
1925 foreach my $serialid(@$serialids) {
1926 my $serial = Koha::Serials->find($serialid);
1928 C4::Serials::ModSerialStatus(
1931 $serial->planneddate,
1932 $serial->publisheddate,
1933 $serial->publisheddatetext,
1934 C4::Serials->CLAIMED,
1939 my $dbh = C4::Context->dbh;
1942 SET claimdate = NOW(),
1943 claims_count = claims_count + 1,
1945 WHERE serialid in (| . join( q|,|, (q|?|) x @$serialids ) . q|)|,
1946 {}, CLAIMED, @$serialids );
1949 =head2 check_routing
1951 $result = &check_routing($subscriptionid)
1953 this function checks to see if a serial has a routing list and returns the count of routingid
1954 used to show either an 'add' or 'edit' link
1959 my ($subscriptionid) = @_;
1961 return unless ($subscriptionid);
1963 my $dbh = C4::Context->dbh;
1964 my $sth = $dbh->prepare(
1965 "SELECT count(routingid) routingids FROM subscription LEFT JOIN subscriptionroutinglist
1966 ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
1967 WHERE subscription.subscriptionid = ? GROUP BY routingid ORDER BY ranking ASC
1970 $sth->execute($subscriptionid);
1971 my $line = $sth->fetchrow_hashref;
1972 my $result = $line->{'routingids'};
1976 =head2 addroutingmember
1978 addroutingmember($borrowernumber,$subscriptionid)
1980 this function takes a borrowernumber and subscriptionid and adds the member to the
1981 routing list for that serial subscription and gives them a rank on the list
1982 of either 1 or highest current rank + 1
1986 sub addroutingmember {
1987 my ( $borrowernumber, $subscriptionid ) = @_;
1989 return unless ($borrowernumber and $subscriptionid);
1992 my $dbh = C4::Context->dbh;
1993 my $sth = $dbh->prepare( "SELECT max(ranking) rank FROM subscriptionroutinglist WHERE subscriptionid = ?" );
1994 $sth->execute($subscriptionid);
1995 while ( my $line = $sth->fetchrow_hashref ) {
1996 if ( $line->{'rank'} > 0 ) {
1997 $rank = $line->{'rank'} + 1;
2002 $sth = $dbh->prepare( "INSERT INTO subscriptionroutinglist (subscriptionid,borrowernumber,ranking) VALUES (?,?,?)" );
2003 $sth->execute( $subscriptionid, $borrowernumber, $rank );
2006 =head2 reorder_members
2008 reorder_members($subscriptionid,$routingid,$rank)
2010 this function is used to reorder the routing list
2012 it takes the routingid of the member one wants to re-rank and the rank it is to move to
2013 - it gets all members on list puts their routingid's into an array
2014 - removes the one in the array that is $routingid
2015 - then reinjects $routingid at point indicated by $rank
2016 - then update the database with the routingids in the new order
2020 sub reorder_members {
2021 my ( $subscriptionid, $routingid, $rank ) = @_;
2022 my $dbh = C4::Context->dbh;
2023 my $sth = $dbh->prepare( "SELECT * FROM subscriptionroutinglist WHERE subscriptionid = ? ORDER BY ranking ASC" );
2024 $sth->execute($subscriptionid);
2026 while ( my $line = $sth->fetchrow_hashref ) {
2027 push( @result, $line->{'routingid'} );
2030 # To find the matching index
2032 my $key = -1; # to allow for 0 being a valid response
2033 for ( $i = 0 ; $i < @result ; $i++ ) {
2034 if ( $routingid == $result[$i] ) {
2035 $key = $i; # save the index
2040 # if index exists in array then move it to new position
2041 if ( $key > -1 && $rank > 0 ) {
2042 my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
2043 my $moving_item = splice( @result, $key, 1 );
2044 splice( @result, $new_rank, 0, $moving_item );
2046 for ( my $j = 0 ; $j < @result ; $j++ ) {
2047 my $sth = $dbh->prepare( "UPDATE subscriptionroutinglist SET ranking = '" . ( $j + 1 ) . "' WHERE routingid = '" . $result[$j] . "'" );
2053 =head2 delroutingmember
2055 delroutingmember($routingid,$subscriptionid)
2057 this function either deletes one member from routing list if $routingid exists otherwise
2058 deletes all members from the routing list
2062 sub delroutingmember {
2064 # if $routingid exists then deletes that row otherwise deletes all with $subscriptionid
2065 my ( $routingid, $subscriptionid ) = @_;
2066 my $dbh = C4::Context->dbh;
2068 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE routingid = ?");
2069 $sth->execute($routingid);
2070 reorder_members( $subscriptionid, $routingid );
2072 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE subscriptionid = ?");
2073 $sth->execute($subscriptionid);
2078 =head2 getroutinglist
2080 @routinglist = getroutinglist($subscriptionid)
2082 this gets the info from the subscriptionroutinglist for $subscriptionid
2085 the routinglist as an array. Each element of the array contains a hash_ref containing
2086 routingid - a unique id, borrowernumber, ranking, and biblionumber of subscription
2090 sub getroutinglist {
2091 my ($subscriptionid) = @_;
2092 my $dbh = C4::Context->dbh;
2093 my $sth = $dbh->prepare(
2094 'SELECT routingid, borrowernumber, ranking, biblionumber
2096 JOIN subscriptionroutinglist ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
2097 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC'
2099 $sth->execute($subscriptionid);
2100 my $routinglist = $sth->fetchall_arrayref({});
2101 return @{$routinglist};
2104 =head2 countissuesfrom
2106 $result = countissuesfrom($subscriptionid,$startdate)
2108 Returns a count of serial rows matching the given subsctiptionid
2109 with published date greater than startdate
2113 sub countissuesfrom {
2114 my ( $subscriptionid, $startdate ) = @_;
2115 my $dbh = C4::Context->dbh;
2119 WHERE subscriptionid=?
2120 AND serial.publisheddate>?
2122 my $sth = $dbh->prepare($query);
2123 $sth->execute( $subscriptionid, $startdate );
2124 my ($countreceived) = $sth->fetchrow;
2125 return $countreceived;
2130 $result = CountIssues($subscriptionid)
2132 Returns a count of serial rows matching the given subsctiptionid
2137 my ($subscriptionid) = @_;
2138 my $dbh = C4::Context->dbh;
2142 WHERE subscriptionid=?
2144 my $sth = $dbh->prepare($query);
2145 $sth->execute($subscriptionid);
2146 my ($countreceived) = $sth->fetchrow;
2147 return $countreceived;
2152 $result = HasItems($subscriptionid)
2154 returns a count of items from serial matching the subscriptionid
2159 my ($subscriptionid) = @_;
2160 my $dbh = C4::Context->dbh;
2162 SELECT COUNT(serialitems.itemnumber)
2164 LEFT JOIN serialitems USING(serialid)
2165 WHERE subscriptionid=? AND serialitems.serialid IS NOT NULL
2167 my $sth=$dbh->prepare($query);
2168 $sth->execute($subscriptionid);
2169 my ($countitems)=$sth->fetchrow_array();
2173 =head2 abouttoexpire
2175 $result = abouttoexpire($subscriptionid)
2177 this function alerts you to the penultimate issue for a serial subscription
2179 returns 1 - if this is the penultimate issue
2185 my ($subscriptionid) = @_;
2186 my $dbh = C4::Context->dbh;
2187 my $subscription = GetSubscription($subscriptionid);
2188 my $per = $subscription->{'periodicity'};
2189 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($per);
2190 if ($frequency and $frequency->{unit}){
2192 my $expirationdate = GetExpirationDate($subscriptionid);
2194 my ($res) = $dbh->selectrow_array('select max(planneddate) from serial where subscriptionid = ?', undef, $subscriptionid);
2195 my $nextdate = GetNextDate($subscription, $res, $frequency);
2197 # only compare dates if both dates exist.
2198 if ($nextdate and $expirationdate) {
2199 if(Date::Calc::Delta_Days(
2200 split( /-/, $nextdate ),
2201 split( /-/, $expirationdate )
2207 } elsif ( $subscription->{numberlength} && $subscription->{numberlength}>0) {
2208 return (countissuesfrom($subscriptionid,$subscription->{'startdate'}) >=$subscription->{numberlength}-1);
2214 =head2 GetFictiveIssueNumber
2216 $issueno = GetFictiveIssueNumber($subscription, $publishedate, $frequency);
2218 Get the position of the issue published at $publisheddate, considering the
2219 first issue (at firstacquidate) is at position 1, the next is at position 2, etc...
2220 This issuenumber doesn't take into account irregularities, so, for instance, if the 3rd
2221 issue is declared as 'irregular' (will be skipped at receipt), the next issue number
2222 will be 4, not 3. It's why it is called 'fictive'. It is NOT a serial seq, and is not
2223 depending on how many rows are in serial table.
2224 The issue number calculation is based on subscription frequency, first acquisition
2225 date, and $publisheddate.
2227 Returns undef when called for irregular frequencies.
2229 The routine is used to skip irregularities when calculating the next issue
2230 date (in GetNextDate) or the next issue number (in GetNextSeq).
2234 sub GetFictiveIssueNumber {
2235 my ($subscription, $publisheddate, $frequency) = @_;
2237 my $unit = $frequency->{unit} ? lc $frequency->{'unit'} : undef;
2241 my ( $year, $month, $day ) = split /-/, $publisheddate;
2242 my ( $fa_year, $fa_month, $fa_day ) = split /-/, $subscription->{'firstacquidate'};
2243 my $delta = _delta_units( [$fa_year, $fa_month, $fa_day], [$year, $month, $day], $unit );
2245 if( $frequency->{'unitsperissue'} == 1 ) {
2246 $issueno = $delta * $frequency->{'issuesperunit'} + $subscription->{'countissuesperunit'};
2247 } else { # issuesperunit == 1
2248 $issueno = 1 + int( $delta / $frequency->{'unitsperissue'} );
2254 my ( $date1, $date2, $unit ) = @_;
2255 # date1 and date2 are array refs in the form [ yy, mm, dd ]
2257 if( $unit eq 'day' ) {
2258 return Delta_Days( @$date1, @$date2 );
2259 } elsif( $unit eq 'week' ) {
2260 return int( Delta_Days( @$date1, @$date2 ) / 7 );
2263 # In case of months or years, this is a wrapper around N_Delta_YMD.
2264 # Note that N_Delta_YMD returns 29 days between e.g. 22-2-72 and 22-3-72
2265 # while we expect 1 month.
2266 my @delta = N_Delta_YMD( @$date1, @$date2 );
2267 if( $delta[2] > 27 ) {
2268 # Check if we could add a month
2269 my @jump = Add_Delta_YM( @$date1, $delta[0], 1 + $delta[1] );
2270 if( Delta_Days( @jump, @$date2 ) >= 0 ) {
2274 if( $delta[1] >= 12 ) {
2278 # if unit is year, we only return full years
2279 return $unit eq 'month' ? $delta[0] * 12 + $delta[1] : $delta[0];
2282 sub _get_next_date_day {
2283 my ($subscription, $freqdata, $year, $month, $day) = @_;
2285 my @newissue; # ( yy, mm, dd )
2286 # We do not need $delta_days here, since it would be zero where used
2288 if( $freqdata->{issuesperunit} == 1 ) {
2290 @newissue = Add_Delta_Days(
2291 $year, $month, $day, $freqdata->{"unitsperissue"} );
2292 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2294 @newissue = ( $year, $month, $day );
2295 $subscription->{countissuesperunit}++;
2297 # We finished a cycle of issues within a unit.
2298 # No subtraction of zero needed, just add one day
2299 @newissue = Add_Delta_Days( $year, $month, $day, 1 );
2300 $subscription->{countissuesperunit} = 1;
2305 sub _get_next_date_week {
2306 my ($subscription, $freqdata, $year, $month, $day) = @_;
2308 my @newissue; # ( yy, mm, dd )
2309 my $delta_days = int( 7 / $freqdata->{issuesperunit} );
2311 if( $freqdata->{issuesperunit} == 1 ) {
2312 # Add full weeks (of 7 days)
2313 @newissue = Add_Delta_Days(
2314 $year, $month, $day, 7 * $freqdata->{"unitsperissue"} );
2315 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2316 # Add rounded number of days based on frequency.
2317 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2318 $subscription->{countissuesperunit}++;
2320 # We finished a cycle of issues within a unit.
2321 # Subtract delta * (issues - 1), add 1 week
2322 @newissue = Add_Delta_Days( $year, $month, $day,
2323 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2324 @newissue = Add_Delta_Days( @newissue, 7 );
2325 $subscription->{countissuesperunit} = 1;
2330 sub _get_next_date_month {
2331 my ($subscription, $freqdata, $year, $month, $day) = @_;
2333 my @newissue; # ( yy, mm, dd )
2334 my $delta_days = int( 30 / $freqdata->{issuesperunit} );
2336 if( $freqdata->{issuesperunit} == 1 ) {
2338 @newissue = Add_Delta_YM(
2339 $year, $month, $day, 0, $freqdata->{"unitsperissue"} );
2340 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2341 # Add rounded number of days based on frequency.
2342 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2343 $subscription->{countissuesperunit}++;
2345 # We finished a cycle of issues within a unit.
2346 # Subtract delta * (issues - 1), add 1 month
2347 @newissue = Add_Delta_Days( $year, $month, $day,
2348 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2349 @newissue = Add_Delta_YM( @newissue, 0, 1 );
2350 $subscription->{countissuesperunit} = 1;
2355 sub _get_next_date_year {
2356 my ($subscription, $freqdata, $year, $month, $day) = @_;
2358 my @newissue; # ( yy, mm, dd )
2359 my $delta_days = int( 365 / $freqdata->{issuesperunit} );
2361 if( $freqdata->{issuesperunit} == 1 ) {
2363 @newissue = Add_Delta_YM( $year, $month, $day, $freqdata->{"unitsperissue"}, 0 );
2364 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2365 # Add rounded number of days based on frequency.
2366 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2367 $subscription->{countissuesperunit}++;
2369 # We finished a cycle of issues within a unit.
2370 # Subtract delta * (issues - 1), add 1 year
2371 @newissue = Add_Delta_Days( $year, $month, $day, -$delta_days * ($freqdata->{issuesperunit} - 1) );
2372 @newissue = Add_Delta_YM( @newissue, 1, 0 );
2373 $subscription->{countissuesperunit} = 1;
2380 $resultdate = GetNextDate($publisheddate,$subscription,$freqdata,$updatecount)
2382 this function it takes the publisheddate and will return the next issue's date
2383 and will skip dates if there exists an irregularity.
2384 $publisheddate has to be an ISO date
2385 $subscription is a hashref containing at least 'firstacquidate', 'irregularity', and 'countissuesperunit'
2386 $frequency is a hashref containing frequency informations
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
2392 $resultdate - then next date in the sequence (ISO date)
2394 Return undef if subscription is irregular
2399 my ( $subscription, $publisheddate, $freqdata, $updatecount ) = @_;
2401 return unless $subscription and $publisheddate;
2404 if ($freqdata->{'unit'}) {
2405 my ( $year, $month, $day ) = split /-/, $publisheddate;
2407 # Process an irregularity Hash
2408 # Suppose that irregularities are stored in a string with this structure
2409 # irreg1;irreg2;irreg3
2410 # where irregX is the number of issue which will not be received
2411 # (the first issue takes the number 1, the 2nd the number 2 and so on)
2413 if ( $subscription->{irregularity} ) {
2414 my @irreg = split /;/, $subscription->{'irregularity'} ;
2415 foreach my $irregularity (@irreg) {
2416 $irregularities{$irregularity} = 1;
2420 # Get the 'fictive' next issue number
2421 # It is used to check if next issue is an irregular issue.
2422 my $issueno = GetFictiveIssueNumber($subscription, $publisheddate, $freqdata) + 1;
2424 # Then get the next date
2425 my $unit = lc $freqdata->{'unit'};
2426 if ($unit eq 'day') {
2427 while ($irregularities{$issueno}) {
2428 ($year, $month, $day) = _get_next_date_day($subscription,
2429 $freqdata, $year, $month, $day);
2432 ($year, $month, $day) = _get_next_date_day($subscription, $freqdata,
2433 $year, $month, $day);
2435 elsif ($unit eq 'week') {
2436 while ($irregularities{$issueno}) {
2437 ($year, $month, $day) = _get_next_date_week($subscription,
2438 $freqdata, $year, $month, $day);
2441 ($year, $month, $day) = _get_next_date_week($subscription,
2442 $freqdata, $year, $month, $day);
2444 elsif ($unit eq 'month') {
2445 while ($irregularities{$issueno}) {
2446 ($year, $month, $day) = _get_next_date_month($subscription,
2447 $freqdata, $year, $month, $day);
2450 ($year, $month, $day) = _get_next_date_month($subscription,
2451 $freqdata, $year, $month, $day);
2453 elsif ($unit eq 'year') {
2454 while ($irregularities{$issueno}) {
2455 ($year, $month, $day) = _get_next_date_year($subscription,
2456 $freqdata, $year, $month, $day);
2459 ($year, $month, $day) = _get_next_date_year($subscription,
2460 $freqdata, $year, $month, $day);
2464 my $dbh = C4::Context->dbh;
2467 SET countissuesperunit = ?
2468 WHERE subscriptionid = ?
2470 my $sth = $dbh->prepare($query);
2471 $sth->execute($subscription->{'countissuesperunit'}, $subscription->{'subscriptionid'});
2474 return sprintf("%04d-%02d-%02d", $year, $month, $day);
2480 $string = &_numeration($value,$num_type,$locale);
2482 _numeration returns the string corresponding to $value in the num_type
2494 my ($value, $num_type, $locale) = @_;
2499 if ( $num_type =~ /^dayname$/ or $num_type =~ /^dayabrv$/ ) {
2500 # 1970-11-01 was a Sunday
2501 $value = $value % 7;
2502 my $dt = DateTime->new(
2508 $string = $num_type =~ /^dayname$/
2509 ? $dt->strftime("%A")
2510 : $dt->strftime("%a");
2511 } elsif ( $num_type =~ /^monthname$/ or $num_type =~ /^monthabrv$/ ) {
2512 $value = $value % 12;
2513 my $dt = DateTime->new(
2515 month => $value + 1,
2518 $string = $num_type =~ /^monthname$/
2519 ? $dt->format_cldr( "LLLL" )
2520 : $dt->strftime("%b");
2521 } elsif ( $num_type =~ /^season$/ ) {
2522 my @seasons= qw( Spring Summer Fall Winter );
2523 $value = $value % 4;
2524 $string = $seasons[$value];
2525 } elsif ( $num_type =~ /^seasonabrv$/ ) {
2526 my @seasonsabrv= qw( Spr Sum Fal Win );
2527 $value = $value % 4;
2528 $string = $seasonsabrv[$value];
2536 =head2 CloseSubscription
2538 Close a subscription given a subscriptionid
2542 sub CloseSubscription {
2543 my ( $subscriptionid ) = @_;
2544 return unless $subscriptionid;
2545 my $dbh = C4::Context->dbh;
2546 my $sth = $dbh->prepare( q{
2549 WHERE subscriptionid = ?
2551 $sth->execute( $subscriptionid );
2553 # Set status = missing when status = stopped
2554 $sth = $dbh->prepare( q{
2557 WHERE subscriptionid = ?
2560 $sth->execute( STOPPED, $subscriptionid, EXPECTED );
2563 =head2 ReopenSubscription
2565 Reopen a subscription given a subscriptionid
2569 sub ReopenSubscription {
2570 my ( $subscriptionid ) = @_;
2571 return unless $subscriptionid;
2572 my $dbh = C4::Context->dbh;
2573 my $sth = $dbh->prepare( q{
2576 WHERE subscriptionid = ?
2578 $sth->execute( $subscriptionid );
2580 # Set status = expected when status = stopped
2581 $sth = $dbh->prepare( q{
2584 WHERE subscriptionid = ?
2587 $sth->execute( EXPECTED, $subscriptionid, STOPPED );
2590 =head2 subscriptionCurrentlyOnOrder
2592 $bool = subscriptionCurrentlyOnOrder( $subscriptionid );
2594 Return 1 if subscription is currently on order else 0.
2598 sub subscriptionCurrentlyOnOrder {
2599 my ( $subscriptionid ) = @_;
2600 my $dbh = C4::Context->dbh;
2602 SELECT COUNT(*) FROM aqorders
2603 WHERE subscriptionid = ?
2604 AND datereceived IS NULL
2605 AND datecancellationprinted IS NULL
2607 my $sth = $dbh->prepare( $query );
2608 $sth->execute($subscriptionid);
2609 return $sth->fetchrow_array;
2612 =head2 can_claim_subscription
2614 $can = can_claim_subscription( $subscriptionid[, $userid] );
2616 Return 1 if the subscription can be claimed by the current logged user (or a given $userid), else 0.
2620 sub can_claim_subscription {
2621 my ( $subscription, $userid ) = @_;
2622 return _can_do_on_subscription( $subscription, $userid, 'claim_serials' );
2625 =head2 can_edit_subscription
2627 $can = can_edit_subscription( $subscriptionid[, $userid] );
2629 Return 1 if the subscription can be edited by the current logged user (or a given $userid), else 0.
2633 sub can_edit_subscription {
2634 my ( $subscription, $userid ) = @_;
2635 return _can_do_on_subscription( $subscription, $userid, 'edit_subscription' );
2638 =head2 can_show_subscription
2640 $can = can_show_subscription( $subscriptionid[, $userid] );
2642 Return 1 if the subscription can be shown by the current logged user (or a given $userid), else 0.
2646 sub can_show_subscription {
2647 my ( $subscription, $userid ) = @_;
2648 return _can_do_on_subscription( $subscription, $userid, '*' );
2651 sub _can_do_on_subscription {
2652 my ( $subscription, $userid, $permission ) = @_;
2653 return 0 unless C4::Context->userenv;
2654 my $flags = C4::Context->userenv->{flags};
2655 $userid ||= C4::Context->userenv->{'id'};
2657 if ( C4::Context->preference('IndependentBranches') ) {
2659 if C4::Context->IsSuperLibrarian()
2661 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2663 C4::Auth::haspermission( $userid,
2664 { serials => $permission } )
2665 and ( not defined $subscription->{branchcode}
2666 or $subscription->{branchcode} eq ''
2667 or $subscription->{branchcode} eq
2668 C4::Context->userenv->{'branch'} )
2673 if C4::Context->IsSuperLibrarian()
2675 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2676 or C4::Auth::haspermission(
2677 $userid, { serials => $permission }
2684 =head2 findSerialsByStatus
2686 @serials = findSerialsByStatus($status, $subscriptionid);
2688 Returns an array of serials matching a given status and subscription id.
2692 sub findSerialsByStatus {
2693 my ( $status, $subscriptionid ) = @_;
2694 my $dbh = C4::Context->dbh;
2695 my $query = q| SELECT * from serial
2697 AND subscriptionid = ?
2699 my $serials = $dbh->selectall_arrayref( $query, { Slice => {} }, $status, $subscriptionid );
2708 Koha Development Team <http://koha-community.org/>