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;
45 use Koha::DateUtils qw( dt_from_string );
47 use Koha::SharedContent;
48 use Koha::Subscription::Histories;
49 use Koha::Subscriptions;
50 use Koha::Suggestions;
51 use Koha::TemplateUtils qw( process_tt );
59 MISSING_NEVER_RECIEVED => 41,
60 MISSING_SOLD_OUT => 42,
61 MISSING_DAMAGED => 43,
69 use constant MISSING_STATUSES => (
70 MISSING, MISSING_NEVER_RECIEVED,
71 MISSING_SOLD_OUT, MISSING_DAMAGED,
75 our (@ISA, @EXPORT_OK);
80 NewSubscription ModSubscription DelSubscription
81 GetSubscription CountSubscriptionFromBiblionumber GetSubscriptionsFromBiblionumber
83 GetFullSubscriptionsFromBiblionumber GetFullSubscription ModSubscriptionHistory
84 HasSubscriptionStrictlyExpired HasSubscriptionExpired GetExpirationDate abouttoexpire
86 GetSubscriptionHistoryFromSubscriptionId
88 GetNextSeq GetSeq NewIssue GetSerials
89 GetLatestSerials ModSerialStatus GetNextDate
90 CloseSubscription ReopenSubscription
91 subscriptionCurrentlyOnOrder
92 can_claim_subscription can_edit_subscription can_show_subscription
94 GetSubscriptionLength ReNewSubscription GetLateOrMissingIssues
95 GetSerialInformation AddItem2Serial
96 PrepareSerialsData GetNextExpected ModNextExpected
97 GetSubscriptionIrregularities
100 GetSuppliersWithLateIssues
101 getroutinglist delroutingmember addroutingmember
103 check_routing updateClaim
114 C4::Serials - Serials Module Functions
122 Functions for handling subscriptions, claims routing etc.
127 =head2 GetSuppliersWithLateIssues
129 $supplierlist = GetSuppliersWithLateIssues()
131 this function get all suppliers with late issues.
134 an array_ref of suppliers each entry is a hash_ref containing id and name
135 the array is in name order
139 sub GetSuppliersWithLateIssues {
140 my $dbh = C4::Context->dbh;
141 my $statuses = join(',', ( LATE, MISSING_STATUSES, CLAIMED ) );
143 SELECT DISTINCT id, name
145 LEFT JOIN serial ON serial.subscriptionid=subscription.subscriptionid
146 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
149 (planneddate < now() AND serial.status=1)
150 OR serial.STATUS IN ( $statuses )
152 AND subscription.closed = 0
154 return $dbh->selectall_arrayref($query, { Slice => {} });
157 =head2 GetSubscriptionHistoryFromSubscriptionId
159 $history = GetSubscriptionHistoryFromSubscriptionId($subscriptionid);
161 This function returns the subscription history as a hashref
165 sub GetSubscriptionHistoryFromSubscriptionId {
166 my ($subscriptionid) = @_;
168 return unless $subscriptionid;
170 my $dbh = C4::Context->dbh;
173 FROM subscriptionhistory
174 WHERE subscriptionid = ?
176 my $sth = $dbh->prepare($query);
177 $sth->execute($subscriptionid);
178 my $results = $sth->fetchrow_hashref;
184 =head2 GetSerialInformation
186 $data = GetSerialInformation($serialid);
187 returns a hash_ref containing :
188 items : items marcrecord (can be an array)
190 subscription table field
191 + information about subscription expiration
195 sub GetSerialInformation {
197 my $dbh = C4::Context->dbh;
199 SELECT serial.*, serial.notes as sernotes, serial.status as serstatus,subscription.*,subscription.subscriptionid as subsid
200 FROM serial LEFT JOIN subscription ON subscription.subscriptionid=serial.subscriptionid
203 my $rq = $dbh->prepare($query);
204 $rq->execute($serialid);
205 my $data = $rq->fetchrow_hashref;
207 # create item information if we have serialsadditems for this subscription
208 if ( $data->{'serialsadditems'} ) {
209 my $queryitem = $dbh->prepare("SELECT itemnumber from serialitems where serialid=?");
210 $queryitem->execute($serialid);
211 my $itemnumbers = $queryitem->fetchall_arrayref( [0] );
213 if ( scalar(@$itemnumbers) > 0 ) {
214 foreach my $itemnum (@$itemnumbers) {
216 #It is ASSUMED that GetMarcItem ALWAYS WORK...
217 #Maybe GetMarcItem should return values on failure
218 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, $itemnum->[0], $data );
219 $itemprocessed->{'itemnumber'} = $itemnum->[0];
220 $itemprocessed->{'itemid'} = $itemnum->[0];
221 $itemprocessed->{'serialid'} = $serialid;
222 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
223 push @{ $data->{'items'} }, $itemprocessed;
226 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, '', $data );
227 $itemprocessed->{'itemid'} = "N$serialid";
228 $itemprocessed->{'serialid'} = $serialid;
229 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
230 $itemprocessed->{'countitems'} = 0;
231 push @{ $data->{'items'} }, $itemprocessed;
234 $data->{ "status" . $data->{'serstatus'} } = 1;
235 $data->{'subscriptionexpired'} = HasSubscriptionExpired( $data->{'subscriptionid'} ) && $data->{'status'} == 1;
236 $data->{'abouttoexpire'} = abouttoexpire( $data->{'subscriptionid'} );
237 $data->{cannotedit} = not can_edit_subscription( $data );
241 =head2 AddItem2Serial
243 $rows = AddItem2Serial($serialid,$itemnumber);
244 Adds an itemnumber to Serial record
245 returns the number of rows affected
250 my ( $serialid, $itemnumber ) = @_;
252 return unless ($serialid and $itemnumber);
254 my $dbh = C4::Context->dbh;
255 my $rq = $dbh->prepare("INSERT INTO `serialitems` SET serialid=? , itemnumber=?");
256 $rq->execute( $serialid, $itemnumber );
260 =head2 GetSubscription
262 $subs = GetSubscription($subscriptionid)
263 this function returns the subscription which has $subscriptionid as id.
265 a hashref. This hash contains
266 subscription, subscriptionhistory, aqbooksellers.name, biblio.title
270 sub GetSubscription {
271 my ($subscriptionid) = @_;
272 my $dbh = C4::Context->dbh;
274 SELECT subscription.*,
275 subscriptionhistory.*,
276 aqbooksellers.name AS aqbooksellername,
277 biblio.title AS bibliotitle,
278 biblio.subtitle AS bibliosubtitle,
279 subscription.biblionumber as bibnum
281 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
282 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
283 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
284 WHERE subscription.subscriptionid = ?
287 my $sth = $dbh->prepare($query);
288 $sth->execute($subscriptionid);
289 my $subscription = $sth->fetchrow_hashref;
291 return unless $subscription;
293 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
295 if ( my $mana_id = $subscription->{mana_id} ) {
296 my $mana_subscription = Koha::SharedContent::get_entity_by_id(
297 'subscription', $mana_id, {usecomments => 1});
298 $subscription->{comments} = $mana_subscription->{data}->{comments};
301 return $subscription;
304 =head2 GetFullSubscription
306 $array_ref = GetFullSubscription($subscriptionid)
307 this function reads the serial table.
311 sub GetFullSubscription {
312 my ($subscriptionid) = @_;
314 return unless ($subscriptionid);
316 my $dbh = C4::Context->dbh;
318 SELECT serial.serialid,
321 serial.publisheddate,
322 serial.publisheddatetext,
324 serial.notes as notes,
325 year(IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate)) as year,
326 aqbooksellers.name as aqbooksellername,
327 biblio.title as bibliotitle,
328 subscription.branchcode AS branchcode,
329 subscription.subscriptionid AS subscriptionid
331 LEFT JOIN subscription ON
332 (serial.subscriptionid=subscription.subscriptionid )
333 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
334 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
335 WHERE serial.subscriptionid = ?
337 IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate) DESC,
338 serial.subscriptionid
340 my $sth = $dbh->prepare($query);
341 $sth->execute($subscriptionid);
342 my $subscriptions = $sth->fetchall_arrayref( {} );
343 if (scalar @$subscriptions) {
344 my $cannotedit = not can_edit_subscription( $subscriptions->[0] );
345 for my $subscription ( @$subscriptions ) {
346 $subscription->{cannotedit} = $cannotedit;
350 return $subscriptions;
353 =head2 PrepareSerialsData
355 $array_ref = PrepareSerialsData($serialinfomation)
356 where serialinformation is a hashref array
360 sub PrepareSerialsData {
363 return unless ($lines);
370 my $previousnote = "";
372 foreach my $subs (@{$lines}) {
373 $subs->{ "status" . $subs->{'status'} } = 1;
374 if ( grep { $_ == $subs->{status} } ( EXPECTED, LATE, MISSING_STATUSES, CLAIMED ) ) {
375 $subs->{"checked"} = 1;
378 if ( $subs->{'year'} && $subs->{'year'} ne "" ) {
379 $year = $subs->{'year'};
383 if ( $tmpresults{$year} ) {
384 push @{ $tmpresults{$year}->{'serials'} }, $subs;
386 $tmpresults{$year} = {
388 'aqbooksellername' => $subs->{'aqbooksellername'},
389 'bibliotitle' => $subs->{'bibliotitle'},
390 'serials' => [$subs],
395 foreach my $key ( sort { $b cmp $a } keys %tmpresults ) {
396 push @res, $tmpresults{$key};
401 =head2 GetSubscriptionsFromBiblionumber
403 $array_ref = GetSubscriptionsFromBiblionumber($biblionumber)
404 this function get the subscription list. it reads the subscription table.
406 reference to an array of subscriptions which have the biblionumber given on input arg.
407 each element of this array is a hashref containing
408 startdate, histstartdate,opacnote,missinglist,recievedlist,periodicity,status & enddate
412 sub GetSubscriptionsFromBiblionumber {
413 my ($biblionumber) = @_;
415 return unless ($biblionumber);
417 my $dbh = C4::Context->dbh;
419 SELECT subscription.*,
421 subscriptionhistory.*,
422 aqbooksellers.name AS aqbooksellername,
423 biblio.title AS bibliotitle
425 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
426 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
427 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
428 LEFT JOIN branches ON branches.branchcode=subscription.branchcode
429 WHERE subscription.biblionumber = ?
431 my $sth = $dbh->prepare($query);
432 $sth->execute($biblionumber);
434 while ( my $subs = $sth->fetchrow_hashref ) {
435 $subs->{opacnote} //= "";
436 $subs->{ "periodicity" . $subs->{periodicity} } = 1;
437 $subs->{ "numberpattern" . $subs->{numberpattern} } = 1;
438 $subs->{ "status" . $subs->{'status'} } = 1;
440 $subs->{'abouttoexpire'} = abouttoexpire( $subs->{'subscriptionid'} );
441 $subs->{'subscriptionexpired'} = HasSubscriptionExpired( $subs->{'subscriptionid'} );
442 $subs->{cannotedit} = not can_edit_subscription( $subs );
448 =head2 GetFullSubscriptionsFromBiblionumber
450 $array_ref = GetFullSubscriptionsFromBiblionumber($biblionumber)
451 this function reads the serial table.
455 sub GetFullSubscriptionsFromBiblionumber {
456 my ($biblionumber) = @_;
457 my $dbh = C4::Context->dbh;
459 SELECT serial.serialid,
462 serial.publisheddate,
463 serial.publisheddatetext,
465 serial.notes as notes,
466 year(IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate)) as year,
467 biblio.title as bibliotitle,
468 subscription.branchcode AS branchcode,
469 subscription.subscriptionid AS subscriptionid,
470 subscription.location AS location
472 LEFT JOIN subscription ON
473 (serial.subscriptionid=subscription.subscriptionid)
474 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
475 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
476 WHERE subscription.biblionumber = ?
478 IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate) DESC,
479 serial.subscriptionid
481 my $sth = $dbh->prepare($query);
482 $sth->execute($biblionumber);
483 my $subscriptions = $sth->fetchall_arrayref( {} );
484 if (scalar @$subscriptions) {
485 my $cannotedit = not can_edit_subscription( $subscriptions->[0] );
486 for my $subscription ( @$subscriptions ) {
487 $subscription->{cannotedit} = $cannotedit;
491 return $subscriptions;
494 =head2 SearchSubscriptions
496 @results = SearchSubscriptions($args);
498 This function returns a list of hashrefs, one for each subscription
499 that meets the conditions specified by the $args hashref.
501 The valid search fields are:
515 The expiration_date search field is special; it specifies the maximum
516 subscription expiration date.
520 sub SearchSubscriptions {
521 my ( $args, $params ) = @_;
525 my $additional_fields = $args->{additional_fields} // [];
526 my $matching_record_ids_for_additional_fields = [];
527 if ( @$additional_fields ) {
528 my @subscriptions = Koha::Subscriptions->filter_by_additional_fields($additional_fields)->as_list;
530 return () unless @subscriptions;
532 $matching_record_ids_for_additional_fields = [ map {
539 subscription.notes AS publicnotes,
540 subscriptionhistory.*,
542 biblio.notes AS biblionotes,
549 aqbooksellers.name AS vendorname,
552 LEFT JOIN subscriptionhistory USING(subscriptionid)
553 LEFT JOIN biblio ON biblio.biblionumber = subscription.biblionumber
554 LEFT JOIN biblioitems ON biblioitems.biblionumber = subscription.biblionumber
555 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
557 $query .= q| WHERE 1|;
560 if( $args->{biblionumber} ) {
561 push @where_strs, "biblio.biblionumber = ?";
562 push @where_args, $args->{biblionumber};
565 if( $args->{title} ){
566 my @words = split / /, $args->{title};
568 foreach my $word (@words) {
569 push @strs, "biblio.title LIKE ?";
570 push @args, "%$word%";
573 push @where_strs, '(' . join (' AND ', @strs) . ')';
574 push @where_args, @args;
578 push @where_strs, "biblioitems.issn LIKE ?";
579 push @where_args, "%$args->{issn}%";
582 push @where_strs, "biblioitems.ean LIKE ?";
583 push @where_args, "%$args->{ean}%";
585 if ( $args->{callnumber} ) {
586 push @where_strs, "subscription.callnumber LIKE ?";
587 push @where_args, "%$args->{callnumber}%";
589 if( $args->{publisher} ){
590 push @where_strs, "biblioitems.publishercode LIKE ?";
591 push @where_args, "%$args->{publisher}%";
593 if( $args->{bookseller} ){
594 push @where_strs, "aqbooksellers.name LIKE ?";
595 push @where_args, "%$args->{bookseller}%";
597 if( $args->{branch} ){
598 push @where_strs, "subscription.branchcode = ?";
599 push @where_args, "$args->{branch}";
601 if ( $args->{location} ) {
602 push @where_strs, "subscription.location = ?";
603 push @where_args, "$args->{location}";
605 if ( $args->{expiration_date} ) {
606 push @where_strs, "subscription.enddate <= ?";
607 push @where_args, "$args->{expiration_date}";
609 if( defined $args->{closed} ){
610 push @where_strs, "subscription.closed = ?";
611 push @where_args, "$args->{closed}";
615 $query .= ' AND ' . join(' AND ', @where_strs);
617 if ( @$additional_fields ) {
618 $query .= ' AND subscriptionid IN ('
619 . join( ', ', @$matching_record_ids_for_additional_fields )
623 $query .= " ORDER BY " . $args->{orderby} if $args->{orderby};
625 my $dbh = C4::Context->dbh;
626 my $sth = $dbh->prepare($query);
627 $sth->execute(@where_args);
628 my $results = $sth->fetchall_arrayref( {} );
630 my $total_results = @{$results};
632 if ( $params->{results_limit} && $total_results > $params->{results_limit} ) {
633 $results = [ splice( @{$results}, 0, $params->{results_limit} ) ];
636 for my $subscription ( @$results ) {
637 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
638 $subscription->{cannotdisplay} = not can_show_subscription( $subscription );
640 my $subscription_object = Koha::Subscriptions->find($subscription->{subscriptionid});
641 $subscription->{additional_fields} = { map { $_->field->name => $_->value }
642 $subscription_object->additional_field_values->as_list };
646 return wantarray ? @{$results} : { results => $results, total => $total_results };
652 ($totalissues,@serials) = GetSerials($subscriptionid);
653 this function gets every serial not arrived for a given subscription
654 as well as the number of issues registered in the database (all types)
655 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
657 FIXME: We should return \@serials.
662 my ( $subscriptionid, $count ) = @_;
664 return unless $subscriptionid;
666 my $dbh = C4::Context->dbh;
668 # status = 2 is "arrived"
670 $count = 5 unless ($count);
672 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES, NOT_ISSUED ) );
673 my $query = "SELECT serialid,serialseq, status, publisheddate,
674 publisheddatetext, planneddate,notes, routingnotes
676 WHERE subscriptionid = ? AND status NOT IN ( $statuses )
677 ORDER BY IF(publisheddate IS NULL,planneddate,publisheddate) DESC";
678 my $sth = $dbh->prepare($query);
679 $sth->execute($subscriptionid);
681 while ( my $line = $sth->fetchrow_hashref ) {
682 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
683 push @serials, $line;
686 # OK, now add the last 5 issues arrives/missing
687 $query = "SELECT serialid,serialseq, status, planneddate, publisheddate,
688 publisheddatetext, notes, routingnotes
690 WHERE subscriptionid = ?
691 AND status IN ( $statuses )
692 ORDER BY IF(publisheddate IS NULL,planneddate,publisheddate) DESC
694 $sth = $dbh->prepare($query);
695 $sth->execute($subscriptionid);
696 while ( ( my $line = $sth->fetchrow_hashref ) && $counter < $count ) {
698 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
700 push @serials, $line;
703 $query = "SELECT count(*) FROM serial WHERE subscriptionid=?";
704 $sth = $dbh->prepare($query);
705 $sth->execute($subscriptionid);
706 my ($totalissues) = $sth->fetchrow;
707 return ( $totalissues, @serials );
712 @serials = GetSerials2($subscriptionid,$statuses);
713 this function returns every serial waited for a given subscription
714 as well as the number of issues registered in the database (all types)
715 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
717 $statuses is an arrayref of statuses and is mandatory.
722 my ( $subscription, $statuses ) = @_;
724 return unless ($subscription and @$statuses);
726 my $dbh = C4::Context->dbh;
728 SELECT serialid,serialseq, status, planneddate, publisheddate,
729 publisheddatetext, notes, routingnotes
731 WHERE subscriptionid=?
733 . q| AND status IN (| . join( ",", ('?') x @$statuses ) . q|)|
735 ORDER BY publisheddate,serialid DESC
737 my $sth = $dbh->prepare($query);
738 $sth->execute( $subscription, @$statuses );
741 while ( my $line = $sth->fetchrow_hashref ) {
742 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
743 push @serials, $line;
748 =head2 GetLatestSerials
750 \@serials = GetLatestSerials($subscriptionid,$limit)
751 get the $limit's latest serials arrived or missing for a given subscription
753 a ref to an array which contains all of the latest serials stored into a hash.
757 sub GetLatestSerials {
758 my ( $subscriptionid, $limit ) = @_;
760 return unless ($subscriptionid and $limit);
762 my $dbh = C4::Context->dbh;
764 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES ) );
765 my $strsth = "SELECT serialid,serialseq, status, planneddate, publisheddate, publisheddatetext, notes
767 WHERE subscriptionid = ?
768 AND status IN ($statuses)
769 ORDER BY publisheddate DESC LIMIT 0,$limit
771 my $sth = $dbh->prepare($strsth);
772 $sth->execute($subscriptionid);
774 while ( my $line = $sth->fetchrow_hashref ) {
775 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
776 push @serials, $line;
782 =head2 GetPreviousSerialid
784 $serialid = GetPreviousSerialid($subscriptionid, $nth)
785 get the $nth's previous serial for the given subscriptionid
791 sub GetPreviousSerialid {
792 my ( $subscriptionid, $nth ) = @_;
794 my $dbh = C4::Context->dbh;
798 my $strsth = "SELECT serialid
800 WHERE subscriptionid = ?
802 ORDER BY serialid DESC LIMIT $nth,1
804 my $sth = $dbh->prepare($strsth);
805 $sth->execute($subscriptionid);
807 my $line = $sth->fetchrow_hashref;
808 $return = $line->{'serialid'} if ($line);
816 $nextseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
817 $newinnerloop1, $newinnerloop2, $newinnerloop3
818 ) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate, $count_forward );
820 $subscription is a hashref containing all the attributes of the table
822 $pattern is a hashref containing all the attributes of the table
823 'subscription_numberpatterns'.
824 $frequency is a hashref containing all the attributes of the table 'subscription_frequencies'
825 $planneddate is a date string in iso format.
826 $count_forward is the number of issues to count forward, defaults to 1 if omitted
827 This function get the next issue for the subscription given on input arg
832 my ($subscription, $pattern, $frequency, $planneddate, $count_forward) = @_;
834 return unless ($subscription and $pattern);
836 my ( $newlastvalue1, $newlastvalue2, $newlastvalue3,
837 $newinnerloop1, $newinnerloop2, $newinnerloop3 );
838 my $count = $count_forward || 1;
840 if ($subscription->{'skip_serialseq'}) {
841 my @irreg = split /;/, $subscription->{'irregularity'};
843 my $irregularities = {};
844 $irregularities->{$_} = 1 foreach(@irreg);
845 my $issueno = GetFictiveIssueNumber($subscription, $planneddate, $frequency) + 1;
846 while($irregularities->{$issueno}) {
853 my $numberingmethod = $pattern->{numberingmethod};
855 if ($numberingmethod) {
856 $calculated = $numberingmethod;
857 my $locale = $subscription->{locale};
858 $newlastvalue1 = $subscription->{lastvalue1} || 0;
859 $newlastvalue2 = $subscription->{lastvalue2} || 0;
860 $newlastvalue3 = $subscription->{lastvalue3} || 0;
861 $newinnerloop1 = $subscription->{innerloop1} || 0;
862 $newinnerloop2 = $subscription->{innerloop2} || 0;
863 $newinnerloop3 = $subscription->{innerloop3} || 0;
866 $calc{$_} = 1 if ($numberingmethod =~ /\{$_\}/);
869 for(my $i = 0; $i < $count; $i++) {
871 # check if we have to increase the new value.
873 if ($newinnerloop1 >= $pattern->{every1}) {
875 $newlastvalue1 += $pattern->{add1};
877 # reset counter if needed.
878 $newlastvalue1 = $pattern->{setto1} if ($newlastvalue1 > $pattern->{whenmorethan1});
881 # check if we have to increase the new value.
883 if ($newinnerloop2 >= $pattern->{every2}) {
885 $newlastvalue2 += $pattern->{add2};
887 # reset counter if needed.
888 $newlastvalue2 = $pattern->{setto2} if ($newlastvalue2 > $pattern->{whenmorethan2});
891 # check if we have to increase the new value.
893 if ($newinnerloop3 >= $pattern->{every3}) {
895 $newlastvalue3 += $pattern->{add3};
897 # reset counter if needed.
898 $newlastvalue3 = $pattern->{setto3} if ($newlastvalue3 > $pattern->{whenmorethan3});
902 my $newlastvalue1string = _numeration( $newlastvalue1, $pattern->{numbering1}, $locale );
903 $calculated =~ s/\{X\}/$newlastvalue1string/g;
906 my $newlastvalue2string = _numeration( $newlastvalue2, $pattern->{numbering2}, $locale );
907 $calculated =~ s/\{Y\}/$newlastvalue2string/g;
910 my $newlastvalue3string = _numeration( $newlastvalue3, $pattern->{numbering3}, $locale );
911 $calculated =~ s/\{Z\}/$newlastvalue3string/g;
914 my $dt = dt_from_string($planneddate);
915 $calculated =~ s/\{Month\}/$dt->month/eg;
916 $calculated =~ s/\{MonthName\}/$dt->month_name/eg;
917 $calculated =~ s/\{Year\}/$dt->year/eg;
918 $calculated =~ s/\{Day\}/$dt->day/eg;
919 $calculated =~ s/\{DayName\}/$dt->day_name/eg;
924 $newlastvalue1, $newlastvalue2, $newlastvalue3,
925 $newinnerloop1, $newinnerloop2, $newinnerloop3);
930 $calculated = GetSeq($subscription, $pattern)
931 $subscription is a hashref containing all the attributes of the table 'subscription'
932 $pattern is a hashref containing all the attributes of the table 'subscription_numberpatterns'
933 this function transforms {X},{Y},{Z} to 150,0,0 for example.
935 the sequence in string format
940 my ($subscription, $pattern) = @_;
942 return unless ($subscription and $pattern);
944 my $locale = $subscription->{locale};
946 my $calculated = $pattern->{numberingmethod};
948 my $newlastvalue1 = $subscription->{'lastvalue1'} || 0;
949 $newlastvalue1 = _numeration($newlastvalue1, $pattern->{numbering1}, $locale) if ($pattern->{numbering1}); # reset counter if needed.
950 $calculated =~ s/\{X\}/$newlastvalue1/g;
952 my $newlastvalue2 = $subscription->{'lastvalue2'} || 0;
953 $newlastvalue2 = _numeration($newlastvalue2, $pattern->{numbering2}, $locale) if ($pattern->{numbering2}); # reset counter if needed.
954 $calculated =~ s/\{Y\}/$newlastvalue2/g;
956 my $newlastvalue3 = $subscription->{'lastvalue3'} || 0;
957 $newlastvalue3 = _numeration($newlastvalue3, $pattern->{numbering3}, $locale) if ($pattern->{numbering3}); # reset counter if needed.
958 $calculated =~ s/\{Z\}/$newlastvalue3/g;
960 my $dt = dt_from_string( $subscription->{firstacquidate} );
961 $calculated =~ s/\{Month\}/$dt->month/eg;
962 $calculated =~ s/\{MonthName\}/$dt->month_name/eg;
963 $calculated =~ s/\{Year\}/$dt->year/eg;
964 $calculated =~ s/\{Day\}/$dt->day/eg;
965 $calculated =~ s/\{DayName\}/$dt->day_name/eg;
970 =head2 GetExpirationDate
972 $enddate = GetExpirationDate($subscriptionid, [$startdate])
974 this function return the next expiration date for a subscription given on input args.
981 sub GetExpirationDate {
982 my ( $subscriptionid, $startdate ) = @_;
984 return unless ($subscriptionid);
986 my $dbh = C4::Context->dbh;
987 my $subscription = GetSubscription($subscriptionid);
990 # we don't do the same test if the subscription is based on X numbers or on X weeks/months
991 $enddate = $startdate || $subscription->{startdate};
992 my @date = split( /-/, $enddate );
994 return if ( scalar(@date) != 3 || not check_date(@date) );
996 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
997 if ( $frequency and $frequency->{unit} ) {
1000 if ( my $length = $subscription->{numberlength} ) {
1002 #calculate the date of the last issue.
1003 for ( my $i = 1 ; $i <= $length ; $i++ ) {
1004 $enddate = GetNextDate( $subscription, $enddate, $frequency );
1006 } elsif ( $subscription->{monthlength} ) {
1007 if ( $$subscription{startdate} ) {
1008 my @enddate = Add_Delta_YM( $date[0], $date[1], $date[2], 0, $subscription->{monthlength} );
1009 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1011 } elsif ( $subscription->{weeklength} ) {
1012 if ( $$subscription{startdate} ) {
1013 my @date = split( /-/, $subscription->{startdate} );
1014 my @enddate = Add_Delta_Days( $date[0], $date[1], $date[2], $subscription->{weeklength} * 7 );
1015 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1018 $enddate = $subscription->{enddate};
1022 return $subscription->{enddate};
1026 =head2 CountSubscriptionFromBiblionumber
1028 $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber)
1029 this returns a count of the subscriptions for a given biblionumber
1031 the number of subscriptions
1035 sub CountSubscriptionFromBiblionumber {
1036 my ($biblionumber) = @_;
1038 return unless ($biblionumber);
1040 my $dbh = C4::Context->dbh;
1041 my $query = "SELECT count(*) FROM subscription WHERE biblionumber=?";
1042 my $sth = $dbh->prepare($query);
1043 $sth->execute($biblionumber);
1044 my $subscriptionsnumber = $sth->fetchrow;
1045 return $subscriptionsnumber;
1048 =head2 ModSubscriptionHistory
1050 ModSubscriptionHistory($subscriptionid,$histstartdate,$enddate,$recievedlist,$missinglist,$opacnote,$librariannote);
1052 this function modifies the history of a subscription. Put your new values on input arg.
1053 returns the number of rows affected
1057 sub ModSubscriptionHistory {
1058 my ( $subscriptionid, $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote ) = @_;
1060 return unless ($subscriptionid);
1062 my $dbh = C4::Context->dbh;
1063 my $query = "UPDATE subscriptionhistory
1064 SET histstartdate=?,histenddate=?,recievedlist=?,missinglist=?,opacnote=?,librariannote=?
1065 WHERE subscriptionid=?
1067 my $sth = $dbh->prepare($query);
1068 $receivedlist =~ s/^; // if $receivedlist;
1069 $missinglist =~ s/^; // if $missinglist;
1070 $opacnote =~ s/^; // if $opacnote;
1071 $sth->execute( $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote, $subscriptionid );
1075 =head2 ModSerialStatus
1077 ModSerialStatus($serialid, $serialseq, $planneddate, $publisheddate,
1078 $publisheddatetext, $status, $notes, $count_forward);
1080 This function modify the serial status. Serial status is a number.(eg 2 is "arrived")
1081 Note : if we change from "waited" to something else,then we will have to create a new "waited" entry
1085 sub ModSerialStatus {
1086 my ($serialid, $serialseq, $planneddate, $publisheddate, $publisheddatetext,
1087 $status, $notes, $count_forward) = @_;
1089 return unless ($serialid);
1091 my $count = $count_forward || 1;
1093 #It is a usual serial
1094 # 1st, get previous status :
1095 my $dbh = C4::Context->dbh;
1096 my $query = "SELECT serial.subscriptionid,serial.status,subscription.periodicity,serial.routingnotes
1097 FROM serial, subscription
1098 WHERE serial.subscriptionid=subscription.subscriptionid
1100 my $sth = $dbh->prepare($query);
1101 $sth->execute($serialid);
1102 my ( $subscriptionid, $oldstatus, $periodicity, $routingnotes ) = $sth->fetchrow;
1103 my $frequency = GetSubscriptionFrequency($periodicity);
1105 # change status & update subscriptionhistory
1107 if ( $status == DELETED ) {
1108 DelIssue( { 'serialid' => $serialid, 'subscriptionid' => $subscriptionid, 'serialseq' => $serialseq } );
1112 SET serialseq = ?, publisheddate = ?, publisheddatetext = ?,
1113 planneddate = ?, status = ?, notes = ?, routingnotes = ?
1116 $sth = $dbh->prepare($query);
1117 $sth->execute( $serialseq, $publisheddate, $publisheddatetext,
1118 $planneddate, $status, $notes, $routingnotes, $serialid );
1119 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1120 $sth = $dbh->prepare($query);
1121 $sth->execute($subscriptionid);
1122 my $val = $sth->fetchrow_hashref;
1123 unless ( $val->{manualhistory} ) {
1124 $query = "SELECT missinglist,recievedlist FROM subscriptionhistory WHERE subscriptionid=?";
1125 $sth = $dbh->prepare($query);
1126 $sth->execute($subscriptionid);
1127 my ( $missinglist, $recievedlist ) = $sth->fetchrow;
1129 if ( $status == ARRIVED || ($oldstatus == ARRIVED && $status != ARRIVED) ) {
1130 $recievedlist = _handle_seqno($serialseq, $recievedlist);
1133 # in case serial has been previously marked as missing
1134 if (grep /$status/, (EXPECTED, ARRIVED, LATE, CLAIMED)) {
1135 $missinglist = _handle_seqno($serialseq, $missinglist, 'REMOVE');
1138 $missinglist = _handle_seqno($serialseq, $missinglist) if grep { $_ == $status } MISSING_STATUSES;
1139 $missinglist .= "; not issued $serialseq" if $status == NOT_ISSUED and not _handle_seqno($serialseq, $missinglist, 'CHECK');
1141 $query = "UPDATE subscriptionhistory SET recievedlist=?, missinglist=? WHERE subscriptionid=?";
1142 $sth = $dbh->prepare($query);
1143 $recievedlist =~ s/^; //;
1144 $missinglist =~ s/^; //;
1145 $sth->execute( $recievedlist, $missinglist, $subscriptionid );
1149 # create new expected entry if needed (ie : was "expected" and has changed)
1150 my $otherIssueExpected = scalar findSerialsByStatus(EXPECTED, $subscriptionid);
1151 if ( !$otherIssueExpected && $oldstatus == EXPECTED && $status != EXPECTED ) {
1152 my $subscription = GetSubscription($subscriptionid);
1153 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1154 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1158 $newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
1159 $newinnerloop1, $newinnerloop2, $newinnerloop3
1161 = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate, $count );
1163 # next date (calculated from actual date & frequency parameters)
1164 my $nextpublisheddate = GetNextDate($subscription, $publisheddate, $frequency, 1);
1165 my $nextpubdate = $nextpublisheddate;
1166 $query = "UPDATE subscription SET lastvalue1=?, lastvalue2=?, lastvalue3=?, innerloop1=?, innerloop2=?, innerloop3=?
1167 WHERE subscriptionid = ?";
1168 $sth = $dbh->prepare($query);
1169 $sth->execute( $newlastvalue1, $newlastvalue2, $newlastvalue3, $newinnerloop1, $newinnerloop2, $newinnerloop3, $subscriptionid );
1170 my $newnote = C4::Context->preference('PreserveSerialNotes') ? $notes : "";
1171 NewIssue( $newserialseq, $subscriptionid, $subscription->{'biblionumber'}, 1, $nextpubdate, $nextpubdate, undef, $newnote, $routingnotes );
1172 # check if an alert must be sent... (= a letter is defined & status became "arrived"
1173 if ( $subscription->{letter} && $status == ARRIVED && $oldstatus != ARRIVED ) {
1174 require C4::Letters;
1175 C4::Letters::SendAlerts( 'issue', $serialid, $subscription->{letter} );
1183 # Adds or removes seqno from list when needed; returns list
1184 # Or checks and returns true when present
1186 my ( $seq, $list, $op ) = @_; # op = ADD | REMOVE | CHECK (default: ADD)
1188 $seq_r =~ s/([()])/\\$1/g; # Adjust disturbing parentheses for regex, maybe extend in future
1190 if( !$op or $op eq 'ADD' ) {
1191 $list .= "; $seq" if $list !~ /(^|;)\s*$seq_r(?=;|$)/;
1192 } elsif( $op eq 'REMOVE' ) {
1193 $list=~ s/(^|;)\s*(not issued )?$seq_r(?=;|$)//g;
1195 return $list =~ /(^|;)\s*$seq_r(?=;|$)/ ? 1 : q{};
1200 =head2 GetNextExpected
1202 $nextexpected = GetNextExpected($subscriptionid)
1204 Get the planneddate for the current expected issue of the subscription.
1210 planneddate => ISO date
1215 sub GetNextExpected {
1216 my ($subscriptionid) = @_;
1218 my $dbh = C4::Context->dbh;
1222 WHERE subscriptionid = ?
1226 my $sth = $dbh->prepare($query);
1228 # Each subscription has only one 'expected' issue.
1229 $sth->execute( $subscriptionid, EXPECTED );
1230 my $nextissue = $sth->fetchrow_hashref;
1231 if ( !$nextissue ) {
1235 WHERE subscriptionid = ?
1236 ORDER BY publisheddate DESC
1239 $sth = $dbh->prepare($query);
1240 $sth->execute($subscriptionid);
1241 $nextissue = $sth->fetchrow_hashref;
1243 foreach(qw/planneddate publisheddate/) {
1244 # or should this default to 1st Jan ???
1245 $nextissue->{$_} //= strftime( '%Y-%m-%d', localtime );
1251 =head2 ModNextExpected
1253 ModNextExpected($subscriptionid,$date)
1255 Update the planneddate for the current expected issue of the subscription.
1256 This will modify all future prediction results.
1258 C<$date> is an ISO date.
1264 sub ModNextExpected {
1265 my ( $subscriptionid, $date ) = @_;
1266 my $dbh = C4::Context->dbh;
1268 #FIXME: Would expect to only set planneddate, but we set both on new issue creation, so updating it here
1269 my $sth = $dbh->prepare('UPDATE serial SET planneddate=?,publisheddate=? WHERE subscriptionid=? AND status=?');
1271 # Each subscription has only one 'expected' issue.
1272 $sth->execute( $date, $date, $subscriptionid, EXPECTED );
1277 =head2 GetSubscriptionIrregularities
1281 =item @irreg = &GetSubscriptionIrregularities($subscriptionid);
1282 get the list of irregularities for a subscription
1288 sub GetSubscriptionIrregularities {
1289 my $subscriptionid = shift;
1291 return unless $subscriptionid;
1293 my $dbh = C4::Context->dbh;
1297 WHERE subscriptionid = ?
1299 my $sth = $dbh->prepare($query);
1300 $sth->execute($subscriptionid);
1302 my ($result) = $sth->fetchrow_array;
1303 my @irreg = split /;/, $result;
1308 =head2 ModSubscription
1310 this function modifies a subscription. Put all new values on input args.
1311 returns the number of rows affected
1315 sub ModSubscription {
1317 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
1318 $periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
1319 $numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
1320 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
1321 $biblionumber, $callnumber, $notes, $letter, $manualhistory,
1322 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1323 $graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
1324 $itemtype, $previousitemtype, $mana_id, $ccode, $published_on_template
1327 my $subscription = Koha::Subscriptions->find($subscriptionid);
1330 librarian => $auser,
1331 branchcode => $branchcode,
1332 aqbooksellerid => $aqbooksellerid,
1334 aqbudgetid => $aqbudgetid,
1335 biblionumber => $biblionumber,
1336 startdate => $startdate,
1337 periodicity => $periodicity,
1338 numberlength => $numberlength,
1339 weeklength => $weeklength,
1340 monthlength => $monthlength,
1341 lastvalue1 => $lastvalue1,
1342 innerloop1 => $innerloop1,
1343 lastvalue2 => $lastvalue2,
1344 innerloop2 => $innerloop2,
1345 lastvalue3 => $lastvalue3,
1346 innerloop3 => $innerloop3,
1350 firstacquidate => $firstacquidate,
1351 irregularity => $irregularity,
1352 numberpattern => $numberpattern,
1354 callnumber => $callnumber,
1355 manualhistory => $manualhistory,
1356 internalnotes => $internalnotes,
1357 serialsadditems => $serialsadditems,
1358 staffdisplaycount => $staffdisplaycount,
1359 opacdisplaycount => $opacdisplaycount,
1360 graceperiod => $graceperiod,
1361 location => $location,
1362 enddate => $enddate,
1363 skip_serialseq => $skip_serialseq,
1364 itemtype => $itemtype,
1365 previousitemtype => $previousitemtype,
1366 mana_id => $mana_id,
1368 published_on_template => $published_on_template,
1371 # FIXME Must be $subscription->serials
1372 # FIXME We shouldn't need serial.subscription (instead use serial->subscription->biblionumber)
1373 Koha::Serials->search({ subscriptionid => $subscriptionid })->update({ biblionumber => $biblionumber });
1375 logaction( "SERIAL", "MODIFY", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1377 $subscription->discard_changes;
1378 return $subscription;
1381 =head2 NewSubscription
1383 $subscriptionid = &NewSubscription($auser,branchcode,$aqbooksellerid,$cost,$aqbudgetid,$biblionumber,
1384 $startdate,$periodicity,$numberlength,$weeklength,$monthlength,
1385 $lastvalue1,$innerloop1,$lastvalue2,$innerloop2,$lastvalue3,$innerloop3,
1386 $status, $notes, $letter, $firstacquidate, $irregularity, $numberpattern,
1387 $locale, $callnumber, $manualhistory, $internalnotes, $serialsadditems,
1388 $staffdisplaycount, $opacdisplaycount, $graceperiod, $location, $enddate,
1389 $skip_serialseq, $itemtype, $previousitemtype);
1391 Create a new subscription with value given on input args.
1394 the id of this new subscription
1398 sub NewSubscription {
1400 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1401 $startdate, $periodicity, $numberlength, $weeklength, $monthlength,
1402 $lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
1403 $innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
1404 $numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
1405 $serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
1406 $location, $enddate, $skip_serialseq, $itemtype, $previousitemtype, $mana_id, $ccode,
1407 $published_on_template,
1409 my $dbh = C4::Context->dbh;
1411 my $subscription = Koha::Subscription->new(
1413 librarian => $auser,
1414 branchcode => $branchcode,
1415 aqbooksellerid => $aqbooksellerid,
1417 aqbudgetid => $aqbudgetid,
1418 biblionumber => $biblionumber,
1419 startdate => $startdate,
1420 periodicity => $periodicity,
1421 numberlength => $numberlength,
1422 weeklength => $weeklength,
1423 monthlength => $monthlength,
1424 lastvalue1 => $lastvalue1,
1425 innerloop1 => $innerloop1,
1426 lastvalue2 => $lastvalue2,
1427 innerloop2 => $innerloop2,
1428 lastvalue3 => $lastvalue3,
1429 innerloop3 => $innerloop3,
1433 firstacquidate => $firstacquidate,
1434 irregularity => $irregularity,
1435 numberpattern => $numberpattern,
1437 callnumber => $callnumber,
1438 manualhistory => $manualhistory,
1439 internalnotes => $internalnotes,
1440 serialsadditems => $serialsadditems,
1441 staffdisplaycount => $staffdisplaycount,
1442 opacdisplaycount => $opacdisplaycount,
1443 graceperiod => $graceperiod,
1444 location => $location,
1445 enddate => $enddate,
1446 skip_serialseq => $skip_serialseq,
1447 itemtype => $itemtype,
1448 previousitemtype => $previousitemtype,
1449 mana_id => $mana_id,
1451 published_on_template => $published_on_template,
1454 $subscription->discard_changes;
1455 my $subscriptionid = $subscription->subscriptionid;
1456 my ( $query, $sth );
1458 $enddate = GetExpirationDate( $subscriptionid, $startdate );
1462 WHERE subscriptionid=?
1464 $sth = $dbh->prepare($query);
1465 $sth->execute( $enddate, $subscriptionid );
1468 # then create the 1st expected number
1470 INSERT INTO subscriptionhistory
1471 (biblionumber, subscriptionid, histstartdate, missinglist, recievedlist)
1472 VALUES (?,?,?, '', '')
1474 $sth = $dbh->prepare($query);
1475 $sth->execute( $biblionumber, $subscriptionid, $startdate);
1477 # reread subscription to get a hash (for calculation of the 1st issue number)
1478 $subscription = GetSubscription($subscriptionid); # We should not do that
1479 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1481 # calculate issue number
1482 my $serialseq = GetSeq($subscription, $pattern) || q{};
1486 serialseq => $serialseq,
1487 serialseq_x => $subscription->{'lastvalue1'},
1488 serialseq_y => $subscription->{'lastvalue2'},
1489 serialseq_z => $subscription->{'lastvalue3'},
1490 subscriptionid => $subscriptionid,
1491 biblionumber => $biblionumber,
1493 planneddate => $firstacquidate,
1494 publisheddate => $firstacquidate,
1498 logaction( "SERIAL", "ADD", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1500 #set serial flag on biblio if not already set.
1501 my $biblio = Koha::Biblios->find( $biblionumber );
1502 if ( $biblio and !$biblio->serial ) {
1503 my $record = $biblio->metadata->record;
1504 my ( $tag, $subf ) = GetMarcFromKohaField( 'biblio.serial' );
1506 eval { $record->field($tag)->update( $subf => 1 ); };
1508 ModBiblio( $record, $biblionumber, $biblio->frameworkcode );
1510 return $subscriptionid;
1513 =head2 GetSubscriptionLength
1515 my ($numberlength, $weeklength, $monthlength) = GetSubscriptionLength( $subtype, $sublength );
1517 This function calculates the subscription length.
1521 sub GetSubscriptionLength {
1522 my ($subtype, $length) = @_;
1524 return unless looks_like_number($length);
1528 $subtype eq 'issues' ? $length : 0,
1529 $subtype eq 'weeks' ? $length : 0,
1530 $subtype eq 'months' ? $length : 0,
1535 =head2 ReNewSubscription
1537 ReNewSubscription($params);
1539 $params is a hashref with the following keys: subscriptionid, user, startdate, numberlength, weeklength, monthlength, note, branchcode
1541 this function renew a subscription with values given on input args.
1545 sub ReNewSubscription {
1546 my ( $params ) = @_;
1547 my $subscriptionid = $params->{subscriptionid};
1548 my $user = $params->{user};
1549 my $startdate = $params->{startdate};
1550 my $numberlength = $params->{numberlength};
1551 my $weeklength = $params->{weeklength};
1552 my $monthlength = $params->{monthlength};
1553 my $note = $params->{note};
1554 my $branchcode = $params->{branchcode};
1556 my $dbh = C4::Context->dbh;
1557 my $subscription = GetSubscription($subscriptionid);
1561 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
1562 WHERE biblio.biblionumber=?
1564 my $sth = $dbh->prepare($query);
1565 $sth->execute( $subscription->{biblionumber} );
1566 my $biblio = $sth->fetchrow_hashref;
1568 if ( C4::Context->preference("RenewSerialAddsSuggestion") ) {
1569 Koha::Suggestion->new(
1571 'suggestedby' => $user,
1572 'title' => $subscription->{bibliotitle},
1573 'author' => $biblio->{author},
1574 'publishercode' => $biblio->{publishercode},
1576 'biblionumber' => $subscription->{biblionumber},
1577 'branchcode' => $branchcode,
1582 $numberlength ||= 0; # Should not we raise an exception instead?
1585 # renew subscription
1588 SET startdate=?,numberlength=?,weeklength=?,monthlength=?,reneweddate=NOW()
1589 WHERE subscriptionid=?
1591 $sth = $dbh->prepare($query);
1592 $sth->execute( $startdate, $numberlength, $weeklength, $monthlength, $subscriptionid );
1593 my $enddate = GetExpirationDate($subscriptionid);
1597 WHERE subscriptionid=?
1599 $sth = $dbh->prepare($query);
1600 $sth->execute( $enddate, $subscriptionid );
1602 logaction( "SERIAL", "RENEW", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1608 NewIssue($serialseq,$subscriptionid,$biblionumber,$status, $planneddate, $publisheddate, $notes, $routingnotes)
1610 Create a new issue stored on the database.
1611 Note : we have to update the recievedlist and missinglist on subscriptionhistory for this subscription.
1612 returns the serial id
1617 my ( $serialseq, $subscriptionid, $biblionumber, $status, $planneddate,
1618 $publisheddate, $publisheddatetext, $notes, $routingnotes ) = @_;
1619 ### FIXME biblionumber CAN be provided by subscriptionid. So Do we STILL NEED IT ?
1621 return unless ($subscriptionid);
1623 my $schema = Koha::Database->new()->schema();
1625 my $subscription = Koha::Subscriptions->find( $subscriptionid );
1627 if ( my $template = $subscription->published_on_template ) {
1628 $publisheddatetext = process_tt(
1631 subscription => $subscription,
1632 serialseq => $serialseq,
1633 serialseq_x => $subscription->lastvalue1(),
1634 serialseq_y => $subscription->lastvalue2(),
1635 serialseq_z => $subscription->lastvalue3(),
1636 subscriptionid => $subscriptionid,
1637 biblionumber => $biblionumber,
1639 planneddate => $planneddate,
1640 publisheddate => $publisheddate,
1641 publisheddatetext => $publisheddatetext,
1643 routingnotes => $routingnotes,
1648 my $serial = Koha::Serial->new(
1650 serialseq => $serialseq,
1651 serialseq_x => $subscription->lastvalue1(),
1652 serialseq_y => $subscription->lastvalue2(),
1653 serialseq_z => $subscription->lastvalue3(),
1654 subscriptionid => $subscriptionid,
1655 biblionumber => $biblionumber,
1657 planneddate => $planneddate,
1658 publisheddate => $publisheddate,
1659 publisheddatetext => $publisheddatetext,
1661 routingnotes => $routingnotes,
1665 my $serialid = $serial->id();
1667 my $subscription_history = Koha::Subscription::Histories->find($subscriptionid);
1668 my $missinglist = $subscription_history->missinglist();
1669 my $recievedlist = $subscription_history->recievedlist();
1671 if ( $status == ARRIVED ) {
1672 ### TODO Add a feature that improves recognition and description.
1673 ### As such count (serialseq) i.e. : N18,2(N19),N20
1674 ### Would use substr and index But be careful to previous presence of ()
1675 $recievedlist .= "; $serialseq" unless ( index( $recievedlist, $serialseq ) > 0 );
1677 if ( grep { $_ eq $status } (MISSING_STATUSES) ) {
1678 $missinglist .= "; $serialseq" unless ( index( $missinglist, $serialseq ) > 0 );
1681 $recievedlist =~ s/^; //;
1682 $missinglist =~ s/^; //;
1684 $subscription_history->recievedlist($recievedlist);
1685 $subscription_history->missinglist($missinglist);
1686 $subscription_history->store();
1691 =head2 HasSubscriptionStrictlyExpired
1693 1 or 0 = HasSubscriptionStrictlyExpired($subscriptionid)
1695 the subscription has stricly expired when today > the end subscription date
1698 1 if true, 0 if false, -1 if the expiration date is not set.
1702 sub HasSubscriptionStrictlyExpired {
1704 # Getting end of subscription date
1705 my ($subscriptionid) = @_;
1707 return unless ($subscriptionid);
1709 my $dbh = C4::Context->dbh;
1710 my $subscription = GetSubscription($subscriptionid);
1711 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1713 # If the expiration date is set
1714 if ( $expirationdate != 0 ) {
1715 my ( $endyear, $endmonth, $endday ) = split( '-', $expirationdate );
1717 # Getting today's date
1718 my ( $nowyear, $nowmonth, $nowday ) = Today();
1720 # if today's date > expiration date, then the subscription has stricly expired
1721 if ( Delta_Days( $nowyear, $nowmonth, $nowday, $endyear, $endmonth, $endday ) < 0 ) {
1728 # There are some cases where the expiration date is not set
1729 # As we can't determine if the subscription has expired on a date-basis,
1735 =head2 HasSubscriptionExpired
1737 $has_expired = HasSubscriptionExpired($subscriptionid)
1739 the subscription has expired when the next issue to arrive is out of subscription limit.
1742 0 if the subscription has not expired
1743 1 if the subscription has expired
1744 2 if has subscription does not have a valid expiration date set
1748 sub HasSubscriptionExpired {
1749 my ($subscriptionid) = @_;
1751 return unless ($subscriptionid);
1753 my $dbh = C4::Context->dbh;
1754 my $subscription = GetSubscription($subscriptionid);
1755 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1756 if ( $frequency and $frequency->{unit} ) {
1757 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1758 if (!defined $expirationdate) {
1759 $expirationdate = q{};
1762 SELECT max(planneddate)
1764 WHERE subscriptionid=?
1766 my $sth = $dbh->prepare($query);
1767 $sth->execute($subscriptionid);
1768 my ($res) = $sth->fetchrow;
1769 if (!$res || $res=~m/^0000/) {
1772 my @res = split( /-/, $res );
1773 my @endofsubscriptiondate = split( /-/, $expirationdate );
1774 return 2 if ( scalar(@res) != 3 || scalar(@endofsubscriptiondate) != 3 || not check_date(@res) || not check_date(@endofsubscriptiondate) );
1776 if ( ( @endofsubscriptiondate && Delta_Days( $res[0], $res[1], $res[2], $endofsubscriptiondate[0], $endofsubscriptiondate[1], $endofsubscriptiondate[2] ) <= 0 )
1781 if ( $subscription->{'numberlength'} ) {
1782 my $countreceived = countissuesfrom( $subscriptionid, $subscription->{'startdate'} );
1783 return 1 if ( $countreceived > $subscription->{'numberlength'} );
1789 return 0; # Notice that you'll never get here.
1792 =head2 DelSubscription
1794 DelSubscription($subscriptionid)
1795 this function deletes subscription which has $subscriptionid as id.
1799 sub DelSubscription {
1800 my ($subscriptionid) = @_;
1801 my $dbh = C4::Context->dbh;
1802 $dbh->do("DELETE FROM subscription WHERE subscriptionid=?", undef, $subscriptionid);
1804 Koha::AdditionalFieldValues->search({
1805 'field.tablename' => 'subscription',
1806 'me.record_id' => $subscriptionid,
1807 }, { join => 'field' })->delete;
1809 logaction( "SERIAL", "DELETE", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1814 DelIssue($serialseq,$subscriptionid)
1815 this function deletes an issue which has $serialseq and $subscriptionid given on input arg.
1817 returns the number of rows affected
1822 my ($dataissue) = @_;
1823 my $dbh = C4::Context->dbh;
1824 ### TODO Add itemdeletion. Would need to get itemnumbers. Should be in a pref ?
1829 AND subscriptionid= ?
1831 my $mainsth = $dbh->prepare($query);
1832 $mainsth->execute( $dataissue->{'serialid'}, $dataissue->{'subscriptionid'} );
1834 #Delete element from subscription history
1835 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1836 my $sth = $dbh->prepare($query);
1837 $sth->execute( $dataissue->{'subscriptionid'} );
1838 my $val = $sth->fetchrow_hashref;
1839 unless ( $val->{manualhistory} ) {
1841 SELECT * FROM subscriptionhistory
1842 WHERE subscriptionid= ?
1844 my $sth = $dbh->prepare($query);
1845 $sth->execute( $dataissue->{'subscriptionid'} );
1846 my $data = $sth->fetchrow_hashref;
1847 my $serialseq = $dataissue->{'serialseq'};
1848 $data->{'missinglist'} =~ s/\b$serialseq\b//;
1849 $data->{'recievedlist'} =~ s/\b$serialseq\b//;
1850 my $strsth = "UPDATE subscriptionhistory SET " . join( ",", map { join( "=", $_, $dbh->quote( $data->{$_} ) ) } keys %$data ) . " WHERE subscriptionid=?";
1851 $sth = $dbh->prepare($strsth);
1852 $sth->execute( $dataissue->{'subscriptionid'} );
1855 return $mainsth->rows;
1858 =head2 GetLateOrMissingIssues
1860 @issuelist = GetLateMissingIssues($supplierid,$serialid)
1862 this function selects missing issues on database - where serial.status = MISSING* or serial.status = LATE or planneddate<now
1865 the issuelist as an array of hash refs. Each element of this array contains
1866 name,title,planneddate,serialseq,serial.subscriptionid from tables : subscription, serial & biblio
1870 sub GetLateOrMissingIssues {
1871 my ( $supplierid, $serialid, $order ) = @_;
1873 return unless ( $supplierid or $serialid );
1875 my $dbh = C4::Context->dbh;
1880 $byserial = "and serialid = " . $serialid;
1883 $order .= ", title";
1887 my $missing_statuses_string = join ',', (MISSING_STATUSES);
1889 $sth = $dbh->prepare(
1891 serialid, aqbooksellerid, name,
1892 biblio.title, biblioitems.issn, planneddate, serialseq,
1893 serial.status, serial.subscriptionid, claimdate, claims_count,
1894 subscription.branchcode, serial.publisheddate
1896 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1897 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1898 LEFT JOIN biblioitems ON subscription.biblionumber=biblioitems.biblionumber
1899 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1900 WHERE subscription.subscriptionid = serial.subscriptionid
1901 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1902 AND subscription.aqbooksellerid=$supplierid
1907 $sth = $dbh->prepare(
1909 serialid, aqbooksellerid, name,
1910 biblio.title, planneddate, serialseq,
1911 serial.status, serial.subscriptionid, claimdate, claims_count,
1912 subscription.branchcode, serial.publisheddate
1914 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1915 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1916 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1917 WHERE subscription.subscriptionid = serial.subscriptionid
1918 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1923 $sth->execute( EXPECTED, LATE, CLAIMED );
1925 while ( my $line = $sth->fetchrow_hashref ) {
1926 $line->{"status".$line->{status}} = 1;
1928 my $subscription_object = Koha::Subscriptions->find($line->{subscriptionid});
1929 $line->{additional_fields} = { map { $_->field->name => $_->value }
1930 $subscription_object->additional_field_values->as_list };
1932 push @issuelist, $line;
1939 &updateClaim($serialid)
1941 this function updates the time when a claim is issued for late/missing items
1943 called from claims.pl file
1948 my ($serialids) = @_;
1949 return unless $serialids;
1950 unless ( ref $serialids ) {
1951 $serialids = [ $serialids ];
1954 foreach my $serialid(@$serialids) {
1955 my $serial = Koha::Serials->find($serialid);
1957 C4::Serials::ModSerialStatus(
1960 $serial->planneddate,
1961 $serial->publisheddate,
1962 $serial->publisheddatetext,
1963 C4::Serials->CLAIMED,
1968 my $dbh = C4::Context->dbh;
1971 SET claimdate = NOW(),
1972 claims_count = claims_count + 1,
1974 WHERE serialid in (| . join( q|,|, (q|?|) x @$serialids ) . q|)|,
1975 {}, CLAIMED, @$serialids );
1978 =head2 check_routing
1980 $result = &check_routing($subscriptionid)
1982 this function checks to see if a serial has a routing list and returns the count of routingid
1983 used to show either an 'add' or 'edit' link
1988 my ($subscriptionid) = @_;
1990 return unless ($subscriptionid);
1992 my $dbh = C4::Context->dbh;
1993 my $sth = $dbh->prepare(
1994 "SELECT count(routingid) routingids FROM subscription LEFT JOIN subscriptionroutinglist
1995 ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
1996 WHERE subscription.subscriptionid = ? GROUP BY routingid ORDER BY ranking ASC
1999 $sth->execute($subscriptionid);
2000 my $line = $sth->fetchrow_hashref;
2001 my $result = $line->{'routingids'};
2005 =head2 addroutingmember
2007 addroutingmember($borrowernumber,$subscriptionid)
2009 this function takes a borrowernumber and subscriptionid and adds the member to the
2010 routing list for that serial subscription and gives them a rank on the list
2011 of either 1 or highest current rank + 1
2015 sub addroutingmember {
2016 my ( $borrowernumber, $subscriptionid ) = @_;
2018 return unless ($borrowernumber and $subscriptionid);
2021 my $dbh = C4::Context->dbh;
2022 my $sth = $dbh->prepare( "SELECT max(ranking) rank FROM subscriptionroutinglist WHERE subscriptionid = ?" );
2023 $sth->execute($subscriptionid);
2024 while ( my $line = $sth->fetchrow_hashref ) {
2025 if ( $line->{'rank'} > 0 ) {
2026 $rank = $line->{'rank'} + 1;
2031 $sth = $dbh->prepare( "INSERT INTO subscriptionroutinglist (subscriptionid,borrowernumber,ranking) VALUES (?,?,?)" );
2032 $sth->execute( $subscriptionid, $borrowernumber, $rank );
2035 =head2 reorder_members
2037 reorder_members($subscriptionid,$routingid,$rank)
2039 this function is used to reorder the routing list
2041 it takes the routingid of the member one wants to re-rank and the rank it is to move to
2042 - it gets all members on list puts their routingid's into an array
2043 - removes the one in the array that is $routingid
2044 - then reinjects $routingid at point indicated by $rank
2045 - then update the database with the routingids in the new order
2049 sub reorder_members {
2050 my ( $subscriptionid, $routingid, $rank ) = @_;
2051 my $dbh = C4::Context->dbh;
2052 my $sth = $dbh->prepare( "SELECT * FROM subscriptionroutinglist WHERE subscriptionid = ? ORDER BY ranking ASC" );
2053 $sth->execute($subscriptionid);
2055 while ( my $line = $sth->fetchrow_hashref ) {
2056 push( @result, $line->{'routingid'} );
2059 # To find the matching index
2061 my $key = -1; # to allow for 0 being a valid response
2062 for ( $i = 0 ; $i < @result ; $i++ ) {
2063 if ( $routingid == $result[$i] ) {
2064 $key = $i; # save the index
2069 # if index exists in array then move it to new position
2070 if ( $key > -1 && $rank > 0 ) {
2071 my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
2072 my $moving_item = splice( @result, $key, 1 );
2073 splice( @result, $new_rank, 0, $moving_item );
2075 for ( my $j = 0 ; $j < @result ; $j++ ) {
2076 my $sth = $dbh->prepare( "UPDATE subscriptionroutinglist SET ranking = '" . ( $j + 1 ) . "' WHERE routingid = '" . $result[$j] . "'" );
2082 =head2 delroutingmember
2084 delroutingmember($routingid,$subscriptionid)
2086 this function either deletes one member from routing list if $routingid exists otherwise
2087 deletes all members from the routing list
2091 sub delroutingmember {
2093 # if $routingid exists then deletes that row otherwise deletes all with $subscriptionid
2094 my ( $routingid, $subscriptionid ) = @_;
2095 my $dbh = C4::Context->dbh;
2097 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE routingid = ?");
2098 $sth->execute($routingid);
2099 reorder_members( $subscriptionid, $routingid );
2101 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE subscriptionid = ?");
2102 $sth->execute($subscriptionid);
2107 =head2 getroutinglist
2109 @routinglist = getroutinglist($subscriptionid)
2111 this gets the info from the subscriptionroutinglist for $subscriptionid
2114 the routinglist as an array. Each element of the array contains a hash_ref containing
2115 routingid - a unique id, borrowernumber, ranking, and biblionumber of subscription
2119 sub getroutinglist {
2120 my ($subscriptionid) = @_;
2121 my $dbh = C4::Context->dbh;
2122 my $sth = $dbh->prepare(
2123 'SELECT routingid, borrowernumber, ranking, biblionumber
2125 JOIN subscriptionroutinglist ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
2126 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC'
2128 $sth->execute($subscriptionid);
2129 my $routinglist = $sth->fetchall_arrayref({});
2130 return @{$routinglist};
2133 =head2 countissuesfrom
2135 $result = countissuesfrom($subscriptionid,$startdate)
2137 Returns a count of serial rows matching the given subsctiptionid
2138 with published date greater than startdate
2142 sub countissuesfrom {
2143 my ( $subscriptionid, $startdate ) = @_;
2144 my $dbh = C4::Context->dbh;
2148 WHERE subscriptionid=?
2149 AND serial.publisheddate>?
2151 my $sth = $dbh->prepare($query);
2152 $sth->execute( $subscriptionid, $startdate );
2153 my ($countreceived) = $sth->fetchrow;
2154 return $countreceived;
2159 $result = CountIssues($subscriptionid)
2161 Returns a count of serial rows matching the given subsctiptionid
2166 my ($subscriptionid) = @_;
2167 my $dbh = C4::Context->dbh;
2171 WHERE subscriptionid=?
2173 my $sth = $dbh->prepare($query);
2174 $sth->execute($subscriptionid);
2175 my ($countreceived) = $sth->fetchrow;
2176 return $countreceived;
2181 $result = HasItems($subscriptionid)
2183 returns a count of items from serial matching the subscriptionid
2188 my ($subscriptionid) = @_;
2189 my $dbh = C4::Context->dbh;
2191 SELECT COUNT(serialitems.itemnumber)
2193 LEFT JOIN serialitems USING(serialid)
2194 WHERE subscriptionid=? AND serialitems.serialid IS NOT NULL
2196 my $sth=$dbh->prepare($query);
2197 $sth->execute($subscriptionid);
2198 my ($countitems)=$sth->fetchrow_array();
2202 =head2 abouttoexpire
2204 $result = abouttoexpire($subscriptionid)
2206 this function alerts you to the penultimate issue for a serial subscription
2208 returns 1 - if this is the penultimate issue
2214 my ($subscriptionid) = @_;
2215 my $dbh = C4::Context->dbh;
2216 my $subscription = GetSubscription($subscriptionid);
2217 my $per = $subscription->{'periodicity'};
2218 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($per);
2219 if ($frequency and $frequency->{unit}){
2221 my $expirationdate = GetExpirationDate($subscriptionid);
2223 my ($res) = $dbh->selectrow_array('select max(planneddate) from serial where subscriptionid = ?', undef, $subscriptionid);
2224 my $nextdate = GetNextDate($subscription, $res, $frequency);
2226 # only compare dates if both dates exist.
2227 if ($nextdate and $expirationdate) {
2228 if(Date::Calc::Delta_Days(
2229 split( /-/, $nextdate ),
2230 split( /-/, $expirationdate )
2236 } elsif ( $subscription->{numberlength} && $subscription->{numberlength}>0) {
2237 return (countissuesfrom($subscriptionid,$subscription->{'startdate'}) >=$subscription->{numberlength}-1);
2243 =head2 GetFictiveIssueNumber
2245 $issueno = GetFictiveIssueNumber($subscription, $publishedate, $frequency);
2247 Get the position of the issue published at $publisheddate, considering the
2248 first issue (at firstacquidate) is at position 1, the next is at position 2, etc...
2249 This issuenumber doesn't take into account irregularities, so, for instance, if the 3rd
2250 issue is declared as 'irregular' (will be skipped at receipt), the next issue number
2251 will be 4, not 3. It's why it is called 'fictive'. It is NOT a serial seq, and is not
2252 depending on how many rows are in serial table.
2253 The issue number calculation is based on subscription frequency, first acquisition
2254 date, and $publisheddate.
2256 Returns undef when called for irregular frequencies.
2258 The routine is used to skip irregularities when calculating the next issue
2259 date (in GetNextDate) or the next issue number (in GetNextSeq).
2263 sub GetFictiveIssueNumber {
2264 my ($subscription, $publisheddate, $frequency) = @_;
2266 my $unit = $frequency->{unit} ? lc $frequency->{'unit'} : undef;
2270 my ( $year, $month, $day ) = split /-/, $publisheddate;
2271 my ( $fa_year, $fa_month, $fa_day ) = split /-/, $subscription->{'firstacquidate'};
2272 my $delta = _delta_units( [$fa_year, $fa_month, $fa_day], [$year, $month, $day], $unit );
2274 if( $frequency->{'unitsperissue'} == 1 ) {
2275 $issueno = $delta * $frequency->{'issuesperunit'} + $subscription->{'countissuesperunit'};
2276 } else { # issuesperunit == 1
2277 $issueno = 1 + int( $delta / $frequency->{'unitsperissue'} );
2283 my ( $date1, $date2, $unit ) = @_;
2284 # date1 and date2 are array refs in the form [ yy, mm, dd ]
2286 if( $unit eq 'day' ) {
2287 return Delta_Days( @$date1, @$date2 );
2288 } elsif( $unit eq 'week' ) {
2289 return int( Delta_Days( @$date1, @$date2 ) / 7 );
2292 # In case of months or years, this is a wrapper around N_Delta_YMD.
2293 # Note that N_Delta_YMD returns 29 days between e.g. 22-2-72 and 22-3-72
2294 # while we expect 1 month.
2295 my @delta = N_Delta_YMD( @$date1, @$date2 );
2296 if( $delta[2] > 27 ) {
2297 # Check if we could add a month
2298 my @jump = Add_Delta_YM( @$date1, $delta[0], 1 + $delta[1] );
2299 if( Delta_Days( @jump, @$date2 ) >= 0 ) {
2303 if( $delta[1] >= 12 ) {
2307 # if unit is year, we only return full years
2308 return $unit eq 'month' ? $delta[0] * 12 + $delta[1] : $delta[0];
2311 sub _get_next_date_day {
2312 my ($subscription, $freqdata, $year, $month, $day) = @_;
2314 my @newissue; # ( yy, mm, dd )
2315 # We do not need $delta_days here, since it would be zero where used
2317 if( $freqdata->{issuesperunit} == 1 ) {
2319 @newissue = Add_Delta_Days(
2320 $year, $month, $day, $freqdata->{"unitsperissue"} );
2321 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2323 @newissue = ( $year, $month, $day );
2324 $subscription->{countissuesperunit}++;
2326 # We finished a cycle of issues within a unit.
2327 # No subtraction of zero needed, just add one day
2328 @newissue = Add_Delta_Days( $year, $month, $day, 1 );
2329 $subscription->{countissuesperunit} = 1;
2334 sub _get_next_date_week {
2335 my ($subscription, $freqdata, $year, $month, $day) = @_;
2337 my @newissue; # ( yy, mm, dd )
2338 my $delta_days = int( 7 / $freqdata->{issuesperunit} );
2340 if( $freqdata->{issuesperunit} == 1 ) {
2341 # Add full weeks (of 7 days)
2342 @newissue = Add_Delta_Days(
2343 $year, $month, $day, 7 * $freqdata->{"unitsperissue"} );
2344 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2345 # Add rounded number of days based on frequency.
2346 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2347 $subscription->{countissuesperunit}++;
2349 # We finished a cycle of issues within a unit.
2350 # Subtract delta * (issues - 1), add 1 week
2351 @newissue = Add_Delta_Days( $year, $month, $day,
2352 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2353 @newissue = Add_Delta_Days( @newissue, 7 );
2354 $subscription->{countissuesperunit} = 1;
2359 sub _get_next_date_month {
2360 my ($subscription, $freqdata, $year, $month, $day) = @_;
2362 my @newissue; # ( yy, mm, dd )
2363 my $delta_days = int( 30 / $freqdata->{issuesperunit} );
2365 if( $freqdata->{issuesperunit} == 1 ) {
2367 @newissue = Add_Delta_YM(
2368 $year, $month, $day, 0, $freqdata->{"unitsperissue"} );
2369 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2370 # Add rounded number of days based on frequency.
2371 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2372 $subscription->{countissuesperunit}++;
2374 # We finished a cycle of issues within a unit.
2375 # Subtract delta * (issues - 1), add 1 month
2376 @newissue = Add_Delta_Days( $year, $month, $day,
2377 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2378 @newissue = Add_Delta_YM( @newissue, 0, 1 );
2379 $subscription->{countissuesperunit} = 1;
2384 sub _get_next_date_year {
2385 my ($subscription, $freqdata, $year, $month, $day) = @_;
2387 my @newissue; # ( yy, mm, dd )
2388 my $delta_days = int( 365 / $freqdata->{issuesperunit} );
2390 if( $freqdata->{issuesperunit} == 1 ) {
2392 @newissue = Add_Delta_YM( $year, $month, $day, $freqdata->{"unitsperissue"}, 0 );
2393 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2394 # Add rounded number of days based on frequency.
2395 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2396 $subscription->{countissuesperunit}++;
2398 # We finished a cycle of issues within a unit.
2399 # Subtract delta * (issues - 1), add 1 year
2400 @newissue = Add_Delta_Days( $year, $month, $day, -$delta_days * ($freqdata->{issuesperunit} - 1) );
2401 @newissue = Add_Delta_YM( @newissue, 1, 0 );
2402 $subscription->{countissuesperunit} = 1;
2409 $resultdate = GetNextDate($publisheddate,$subscription,$freqdata,$updatecount)
2411 this function it takes the publisheddate and will return the next issue's date
2412 and will skip dates if there exists an irregularity.
2413 $publisheddate has to be an ISO date
2414 $subscription is a hashref containing at least 'firstacquidate', 'irregularity', and 'countissuesperunit'
2415 $frequency is a hashref containing frequency informations
2416 $updatecount is a boolean value which, when set to true, update the 'countissuesperunit' in database
2417 - eg if periodicity is monthly and $publisheddate is 2007-02-10 but if March and April is to be
2418 skipped then the returned date will be 2007-05-10
2421 $resultdate - then next date in the sequence (ISO date)
2423 Return undef if subscription is irregular
2428 my ( $subscription, $publisheddate, $freqdata, $updatecount ) = @_;
2430 return unless $subscription and $publisheddate;
2433 if ($freqdata->{'unit'}) {
2434 my ( $year, $month, $day ) = split /-/, $publisheddate;
2436 # Process an irregularity Hash
2437 # Suppose that irregularities are stored in a string with this structure
2438 # irreg1;irreg2;irreg3
2439 # where irregX is the number of issue which will not be received
2440 # (the first issue takes the number 1, the 2nd the number 2 and so on)
2442 if ( $subscription->{irregularity} ) {
2443 my @irreg = split /;/, $subscription->{'irregularity'} ;
2444 foreach my $irregularity (@irreg) {
2445 $irregularities{$irregularity} = 1;
2449 # Get the 'fictive' next issue number
2450 # It is used to check if next issue is an irregular issue.
2451 my $issueno = GetFictiveIssueNumber($subscription, $publisheddate, $freqdata) + 1;
2453 # Then get the next date
2454 my $unit = lc $freqdata->{'unit'};
2455 if ($unit eq 'day') {
2456 while ($irregularities{$issueno}) {
2457 ($year, $month, $day) = _get_next_date_day($subscription,
2458 $freqdata, $year, $month, $day);
2461 ($year, $month, $day) = _get_next_date_day($subscription, $freqdata,
2462 $year, $month, $day);
2464 elsif ($unit eq 'week') {
2465 while ($irregularities{$issueno}) {
2466 ($year, $month, $day) = _get_next_date_week($subscription,
2467 $freqdata, $year, $month, $day);
2470 ($year, $month, $day) = _get_next_date_week($subscription,
2471 $freqdata, $year, $month, $day);
2473 elsif ($unit eq 'month') {
2474 while ($irregularities{$issueno}) {
2475 ($year, $month, $day) = _get_next_date_month($subscription,
2476 $freqdata, $year, $month, $day);
2479 ($year, $month, $day) = _get_next_date_month($subscription,
2480 $freqdata, $year, $month, $day);
2482 elsif ($unit eq 'year') {
2483 while ($irregularities{$issueno}) {
2484 ($year, $month, $day) = _get_next_date_year($subscription,
2485 $freqdata, $year, $month, $day);
2488 ($year, $month, $day) = _get_next_date_year($subscription,
2489 $freqdata, $year, $month, $day);
2493 my $dbh = C4::Context->dbh;
2496 SET countissuesperunit = ?
2497 WHERE subscriptionid = ?
2499 my $sth = $dbh->prepare($query);
2500 $sth->execute($subscription->{'countissuesperunit'}, $subscription->{'subscriptionid'});
2503 return sprintf("%04d-%02d-%02d", $year, $month, $day);
2509 $string = &_numeration($value,$num_type,$locale);
2511 _numeration returns the string corresponding to $value in the num_type
2523 my ($value, $num_type, $locale) = @_;
2528 if ( $num_type =~ /^dayname$/ or $num_type =~ /^dayabrv$/ ) {
2529 # 1970-11-01 was a Sunday
2530 $value = $value % 7;
2531 my $dt = DateTime->new(
2537 $string = $num_type =~ /^dayname$/
2538 ? $dt->strftime("%A")
2539 : $dt->strftime("%a");
2540 } elsif ( $num_type =~ /^monthname$/ or $num_type =~ /^monthabrv$/ ) {
2541 $value = $value % 12;
2542 my $dt = DateTime->new(
2544 month => $value + 1,
2547 $string = $num_type =~ /^monthname$/
2548 ? $dt->format_cldr( "LLLL" )
2549 : $dt->strftime("%b");
2550 } elsif ( $num_type =~ /^season$/ ) {
2551 my @seasons= qw( Spring Summer Fall Winter );
2552 $value = $value % 4;
2553 $string = $seasons[$value];
2554 } elsif ( $num_type =~ /^seasonabrv$/ ) {
2555 my @seasonsabrv= qw( Spr Sum Fal Win );
2556 $value = $value % 4;
2557 $string = $seasonsabrv[$value];
2565 =head2 CloseSubscription
2567 Close a subscription given a subscriptionid
2571 sub CloseSubscription {
2572 my ( $subscriptionid ) = @_;
2573 return unless $subscriptionid;
2574 my $dbh = C4::Context->dbh;
2575 my $sth = $dbh->prepare( q{
2578 WHERE subscriptionid = ?
2580 $sth->execute( $subscriptionid );
2582 # Set status = missing when status = stopped
2583 $sth = $dbh->prepare( q{
2586 WHERE subscriptionid = ?
2589 $sth->execute( STOPPED, $subscriptionid, EXPECTED );
2592 =head2 ReopenSubscription
2594 Reopen a subscription given a subscriptionid
2598 sub ReopenSubscription {
2599 my ( $subscriptionid ) = @_;
2600 return unless $subscriptionid;
2601 my $dbh = C4::Context->dbh;
2602 my $sth = $dbh->prepare( q{
2605 WHERE subscriptionid = ?
2607 $sth->execute( $subscriptionid );
2609 # Set status = expected when status = stopped
2610 $sth = $dbh->prepare( q{
2613 WHERE subscriptionid = ?
2616 $sth->execute( EXPECTED, $subscriptionid, STOPPED );
2619 =head2 subscriptionCurrentlyOnOrder
2621 $bool = subscriptionCurrentlyOnOrder( $subscriptionid );
2623 Return 1 if subscription is currently on order else 0.
2627 sub subscriptionCurrentlyOnOrder {
2628 my ( $subscriptionid ) = @_;
2629 my $dbh = C4::Context->dbh;
2631 SELECT COUNT(*) FROM aqorders
2632 WHERE subscriptionid = ?
2633 AND datereceived IS NULL
2634 AND datecancellationprinted IS NULL
2636 my $sth = $dbh->prepare( $query );
2637 $sth->execute($subscriptionid);
2638 return $sth->fetchrow_array;
2641 =head2 can_claim_subscription
2643 $can = can_claim_subscription( $subscriptionid[, $userid] );
2645 Return 1 if the subscription can be claimed by the current logged user (or a given $userid), else 0.
2649 sub can_claim_subscription {
2650 my ( $subscription, $userid ) = @_;
2651 return _can_do_on_subscription( $subscription, $userid, 'claim_serials' );
2654 =head2 can_edit_subscription
2656 $can = can_edit_subscription( $subscriptionid[, $userid] );
2658 Return 1 if the subscription can be edited by the current logged user (or a given $userid), else 0.
2662 sub can_edit_subscription {
2663 my ( $subscription, $userid ) = @_;
2664 return _can_do_on_subscription( $subscription, $userid, 'edit_subscription' );
2667 =head2 can_show_subscription
2669 $can = can_show_subscription( $subscriptionid[, $userid] );
2671 Return 1 if the subscription can be shown by the current logged user (or a given $userid), else 0.
2675 sub can_show_subscription {
2676 my ( $subscription, $userid ) = @_;
2677 return _can_do_on_subscription( $subscription, $userid, '*' );
2680 sub _can_do_on_subscription {
2681 my ( $subscription, $userid, $permission ) = @_;
2682 return 0 unless C4::Context->userenv;
2683 my $flags = C4::Context->userenv->{flags};
2684 $userid ||= C4::Context->userenv->{'id'};
2686 if ( C4::Context->preference('IndependentBranches') ) {
2688 if C4::Context->IsSuperLibrarian()
2690 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2692 C4::Auth::haspermission( $userid,
2693 { serials => $permission } )
2694 and ( not defined $subscription->{branchcode}
2695 or $subscription->{branchcode} eq ''
2696 or $subscription->{branchcode} eq
2697 C4::Context->userenv->{'branch'} )
2702 if C4::Context->IsSuperLibrarian()
2704 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2705 or C4::Auth::haspermission(
2706 $userid, { serials => $permission }
2713 =head2 findSerialsByStatus
2715 @serials = findSerialsByStatus($status, $subscriptionid);
2717 Returns an array of serials matching a given status and subscription id.
2721 sub findSerialsByStatus {
2722 my ( $status, $subscriptionid ) = @_;
2723 my $dbh = C4::Context->dbh;
2724 my $query = q| SELECT * from serial
2726 AND subscriptionid = ?
2728 my $serials = $dbh->selectall_arrayref( $query, { Slice => {} }, $status, $subscriptionid );
2737 Koha Development Team <http://koha-community.org/>