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>.
23 use C4::Auth qw( haspermission );
34 use POSIX qw( strftime );
35 use C4::Biblio qw( GetMarcBiblio GetMarcFromKohaField ModBiblio );
36 use C4::Log qw( logaction ); # logaction
37 use C4::Serials::Frequency qw( GetSubscriptionFrequency );
38 use C4::Serials::Numberpattern;
39 use Koha::AdditionalFieldValues;
40 use Koha::DateUtils qw( dt_from_string output_pref );
42 use Koha::Subscriptions;
43 use Koha::Subscription::Histories;
44 use Koha::SharedContent;
45 use Scalar::Util qw( looks_like_number );
53 MISSING_NEVER_RECIEVED => 41,
54 MISSING_SOLD_OUT => 42,
55 MISSING_DAMAGED => 43,
63 use constant MISSING_STATUSES => (
64 MISSING, MISSING_NEVER_RECIEVED,
65 MISSING_SOLD_OUT, MISSING_DAMAGED,
69 our (@ISA, @EXPORT_OK);
74 NewSubscription ModSubscription DelSubscription
75 GetSubscription CountSubscriptionFromBiblionumber GetSubscriptionsFromBiblionumber
77 GetFullSubscriptionsFromBiblionumber GetFullSubscription ModSubscriptionHistory
78 HasSubscriptionStrictlyExpired HasSubscriptionExpired GetExpirationDate abouttoexpire
80 GetSubscriptionHistoryFromSubscriptionId
82 GetNextSeq GetSeq NewIssue GetSerials
83 GetLatestSerials ModSerialStatus GetNextDate
84 CloseSubscription ReopenSubscription
85 subscriptionCurrentlyOnOrder
86 can_claim_subscription can_edit_subscription can_show_subscription
88 GetSubscriptionLength ReNewSubscription GetLateOrMissingIssues
89 GetSerialInformation AddItem2Serial
90 PrepareSerialsData GetNextExpected ModNextExpected
91 GetSubscriptionIrregularities
94 GetSuppliersWithLateIssues
95 getroutinglist delroutingmember addroutingmember
97 check_routing updateClaim
108 C4::Serials - Serials Module Functions
116 Functions for handling subscriptions, claims routing etc.
121 =head2 GetSuppliersWithLateIssues
123 $supplierlist = GetSuppliersWithLateIssues()
125 this function get all suppliers with late issues.
128 an array_ref of suppliers each entry is a hash_ref containing id and name
129 the array is in name order
133 sub GetSuppliersWithLateIssues {
134 my $dbh = C4::Context->dbh;
135 my $statuses = join(',', ( LATE, MISSING_STATUSES, CLAIMED ) );
137 SELECT DISTINCT id, name
139 LEFT JOIN serial ON serial.subscriptionid=subscription.subscriptionid
140 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
143 (planneddate < now() AND serial.status=1)
144 OR serial.STATUS IN ( $statuses )
146 AND subscription.closed = 0
148 return $dbh->selectall_arrayref($query, { Slice => {} });
151 =head2 GetSubscriptionHistoryFromSubscriptionId
153 $history = GetSubscriptionHistoryFromSubscriptionId($subscriptionid);
155 This function returns the subscription history as a hashref
159 sub GetSubscriptionHistoryFromSubscriptionId {
160 my ($subscriptionid) = @_;
162 return unless $subscriptionid;
164 my $dbh = C4::Context->dbh;
167 FROM subscriptionhistory
168 WHERE subscriptionid = ?
170 my $sth = $dbh->prepare($query);
171 $sth->execute($subscriptionid);
172 my $results = $sth->fetchrow_hashref;
178 =head2 GetSerialInformation
180 $data = GetSerialInformation($serialid);
181 returns a hash_ref containing :
182 items : items marcrecord (can be an array)
184 subscription table field
185 + information about subscription expiration
189 sub GetSerialInformation {
191 my $dbh = C4::Context->dbh;
193 SELECT serial.*, serial.notes as sernotes, serial.status as serstatus,subscription.*,subscription.subscriptionid as subsid
194 FROM serial LEFT JOIN subscription ON subscription.subscriptionid=serial.subscriptionid
197 my $rq = $dbh->prepare($query);
198 $rq->execute($serialid);
199 my $data = $rq->fetchrow_hashref;
201 # create item information if we have serialsadditems for this subscription
202 if ( $data->{'serialsadditems'} ) {
203 my $queryitem = $dbh->prepare("SELECT itemnumber from serialitems where serialid=?");
204 $queryitem->execute($serialid);
205 my $itemnumbers = $queryitem->fetchall_arrayref( [0] );
207 if ( scalar(@$itemnumbers) > 0 ) {
208 foreach my $itemnum (@$itemnumbers) {
210 #It is ASSUMED that GetMarcItem ALWAYS WORK...
211 #Maybe GetMarcItem should return values on failure
212 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, $itemnum->[0], $data );
213 $itemprocessed->{'itemnumber'} = $itemnum->[0];
214 $itemprocessed->{'itemid'} = $itemnum->[0];
215 $itemprocessed->{'serialid'} = $serialid;
216 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
217 push @{ $data->{'items'} }, $itemprocessed;
220 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, '', $data );
221 $itemprocessed->{'itemid'} = "N$serialid";
222 $itemprocessed->{'serialid'} = $serialid;
223 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
224 $itemprocessed->{'countitems'} = 0;
225 push @{ $data->{'items'} }, $itemprocessed;
228 $data->{ "status" . $data->{'serstatus'} } = 1;
229 $data->{'subscriptionexpired'} = HasSubscriptionExpired( $data->{'subscriptionid'} ) && $data->{'status'} == 1;
230 $data->{'abouttoexpire'} = abouttoexpire( $data->{'subscriptionid'} );
231 $data->{cannotedit} = not can_edit_subscription( $data );
235 =head2 AddItem2Serial
237 $rows = AddItem2Serial($serialid,$itemnumber);
238 Adds an itemnumber to Serial record
239 returns the number of rows affected
244 my ( $serialid, $itemnumber ) = @_;
246 return unless ($serialid and $itemnumber);
248 my $dbh = C4::Context->dbh;
249 my $rq = $dbh->prepare("INSERT INTO `serialitems` SET serialid=? , itemnumber=?");
250 $rq->execute( $serialid, $itemnumber );
254 =head2 GetSubscription
256 $subs = GetSubscription($subscriptionid)
257 this function returns the subscription which has $subscriptionid as id.
259 a hashref. This hash contains
260 subscription, subscriptionhistory, aqbooksellers.name, biblio.title
264 sub GetSubscription {
265 my ($subscriptionid) = @_;
266 my $dbh = C4::Context->dbh;
268 SELECT subscription.*,
269 subscriptionhistory.*,
270 aqbooksellers.name AS aqbooksellername,
271 biblio.title AS bibliotitle,
272 biblio.subtitle AS bibliosubtitle,
273 subscription.biblionumber as bibnum
275 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
276 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
277 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
278 WHERE subscription.subscriptionid = ?
281 my $sth = $dbh->prepare($query);
282 $sth->execute($subscriptionid);
283 my $subscription = $sth->fetchrow_hashref;
285 return unless $subscription;
287 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
289 if ( my $mana_id = $subscription->{mana_id} ) {
290 my $mana_subscription = Koha::SharedContent::get_entity_by_id(
291 'subscription', $mana_id, {usecomments => 1});
292 $subscription->{comments} = $mana_subscription->{data}->{comments};
295 return $subscription;
298 =head2 GetFullSubscription
300 $array_ref = GetFullSubscription($subscriptionid)
301 this function reads the serial table.
305 sub GetFullSubscription {
306 my ($subscriptionid) = @_;
308 return unless ($subscriptionid);
310 my $dbh = C4::Context->dbh;
312 SELECT serial.serialid,
315 serial.publisheddate,
316 serial.publisheddatetext,
318 serial.notes as notes,
319 year(IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate)) as year,
320 aqbooksellers.name as aqbooksellername,
321 biblio.title as bibliotitle,
322 subscription.branchcode AS branchcode,
323 subscription.subscriptionid AS subscriptionid
325 LEFT JOIN subscription ON
326 (serial.subscriptionid=subscription.subscriptionid )
327 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
328 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
329 WHERE serial.subscriptionid = ?
331 IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate) DESC,
332 serial.subscriptionid
334 my $sth = $dbh->prepare($query);
335 $sth->execute($subscriptionid);
336 my $subscriptions = $sth->fetchall_arrayref( {} );
337 if (scalar @$subscriptions) {
338 my $cannotedit = not can_edit_subscription( $subscriptions->[0] );
339 for my $subscription ( @$subscriptions ) {
340 $subscription->{cannotedit} = $cannotedit;
344 return $subscriptions;
347 =head2 PrepareSerialsData
349 $array_ref = PrepareSerialsData($serialinfomation)
350 where serialinformation is a hashref array
354 sub PrepareSerialsData {
357 return unless ($lines);
364 my $previousnote = "";
366 foreach my $subs (@{$lines}) {
367 $subs->{ "status" . $subs->{'status'} } = 1;
368 if ( grep { $_ == $subs->{status} } ( EXPECTED, LATE, MISSING_STATUSES, CLAIMED ) ) {
369 $subs->{"checked"} = 1;
372 if ( $subs->{'year'} && $subs->{'year'} ne "" ) {
373 $year = $subs->{'year'};
377 if ( $tmpresults{$year} ) {
378 push @{ $tmpresults{$year}->{'serials'} }, $subs;
380 $tmpresults{$year} = {
382 'aqbooksellername' => $subs->{'aqbooksellername'},
383 'bibliotitle' => $subs->{'bibliotitle'},
384 'serials' => [$subs],
389 foreach my $key ( sort { $b cmp $a } keys %tmpresults ) {
390 push @res, $tmpresults{$key};
395 =head2 GetSubscriptionsFromBiblionumber
397 $array_ref = GetSubscriptionsFromBiblionumber($biblionumber)
398 this function get the subscription list. it reads the subscription table.
400 reference to an array of subscriptions which have the biblionumber given on input arg.
401 each element of this array is a hashref containing
402 startdate, histstartdate,opacnote,missinglist,recievedlist,periodicity,status & enddate
406 sub GetSubscriptionsFromBiblionumber {
407 my ($biblionumber) = @_;
409 return unless ($biblionumber);
411 my $dbh = C4::Context->dbh;
413 SELECT subscription.*,
415 subscriptionhistory.*,
416 aqbooksellers.name AS aqbooksellername,
417 biblio.title AS bibliotitle
419 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
420 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
421 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
422 LEFT JOIN branches ON branches.branchcode=subscription.branchcode
423 WHERE subscription.biblionumber = ?
425 my $sth = $dbh->prepare($query);
426 $sth->execute($biblionumber);
428 while ( my $subs = $sth->fetchrow_hashref ) {
429 $subs->{startdate} = output_pref( { dt => dt_from_string( $subs->{startdate} ), dateonly => 1 } );
430 $subs->{histstartdate} = output_pref( { dt => dt_from_string( $subs->{histstartdate} ), dateonly => 1 } );
431 if ( defined $subs->{histenddate} ) {
432 $subs->{histenddate} = output_pref( { dt => dt_from_string( $subs->{histenddate} ), dateonly => 1 } );
434 $subs->{histenddate} = "";
436 $subs->{opacnote} //= "";
437 $subs->{ "periodicity" . $subs->{periodicity} } = 1;
438 $subs->{ "numberpattern" . $subs->{numberpattern} } = 1;
439 $subs->{ "status" . $subs->{'status'} } = 1;
441 if (not defined $subs->{enddate} ) {
442 $subs->{enddate} = '';
444 $subs->{enddate} = output_pref( { dt => dt_from_string( $subs->{enddate}), dateonly => 1 } );
446 $subs->{'abouttoexpire'} = abouttoexpire( $subs->{'subscriptionid'} );
447 $subs->{'subscriptionexpired'} = HasSubscriptionExpired( $subs->{'subscriptionid'} );
448 $subs->{cannotedit} = not can_edit_subscription( $subs );
454 =head2 GetFullSubscriptionsFromBiblionumber
456 $array_ref = GetFullSubscriptionsFromBiblionumber($biblionumber)
457 this function reads the serial table.
461 sub GetFullSubscriptionsFromBiblionumber {
462 my ($biblionumber) = @_;
463 my $dbh = C4::Context->dbh;
465 SELECT serial.serialid,
468 serial.publisheddate,
469 serial.publisheddatetext,
471 serial.notes as notes,
472 year(IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate)) as year,
473 biblio.title as bibliotitle,
474 subscription.branchcode AS branchcode,
475 subscription.subscriptionid AS subscriptionid,
476 subscription.location AS location
478 LEFT JOIN subscription ON
479 (serial.subscriptionid=subscription.subscriptionid)
480 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
481 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
482 WHERE subscription.biblionumber = ?
484 IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate) DESC,
485 serial.subscriptionid
487 my $sth = $dbh->prepare($query);
488 $sth->execute($biblionumber);
489 my $subscriptions = $sth->fetchall_arrayref( {} );
490 if (scalar @$subscriptions) {
491 my $cannotedit = not can_edit_subscription( $subscriptions->[0] );
492 for my $subscription ( @$subscriptions ) {
493 $subscription->{cannotedit} = $cannotedit;
497 return $subscriptions;
500 =head2 SearchSubscriptions
502 @results = SearchSubscriptions($args);
504 This function returns a list of hashrefs, one for each subscription
505 that meets the conditions specified by the $args hashref.
507 The valid search fields are:
521 The expiration_date search field is special; it specifies the maximum
522 subscription expiration date.
526 sub SearchSubscriptions {
529 my $additional_fields = $args->{additional_fields} // [];
530 my $matching_record_ids_for_additional_fields = [];
531 if ( @$additional_fields ) {
532 my @subscriptions = Koha::Subscriptions->filter_by_additional_fields($additional_fields)->as_list;
534 return () unless @subscriptions;
536 $matching_record_ids_for_additional_fields = [ map {
543 subscription.notes AS publicnotes,
544 subscriptionhistory.*,
546 biblio.notes AS biblionotes,
550 aqbooksellers.name AS vendorname,
553 LEFT JOIN subscriptionhistory USING(subscriptionid)
554 LEFT JOIN biblio ON biblio.biblionumber = subscription.biblionumber
555 LEFT JOIN biblioitems ON biblioitems.biblionumber = subscription.biblionumber
556 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
558 $query .= q| WHERE 1|;
561 if( $args->{biblionumber} ) {
562 push @where_strs, "biblio.biblionumber = ?";
563 push @where_args, $args->{biblionumber};
566 if( $args->{title} ){
567 my @words = split / /, $args->{title};
569 foreach my $word (@words) {
570 push @strs, "biblio.title LIKE ?";
571 push @args, "%$word%";
574 push @where_strs, '(' . join (' AND ', @strs) . ')';
575 push @where_args, @args;
579 push @where_strs, "biblioitems.issn LIKE ?";
580 push @where_args, "%$args->{issn}%";
583 push @where_strs, "biblioitems.ean LIKE ?";
584 push @where_args, "%$args->{ean}%";
586 if ( $args->{callnumber} ) {
587 push @where_strs, "subscription.callnumber LIKE ?";
588 push @where_args, "%$args->{callnumber}%";
590 if( $args->{publisher} ){
591 push @where_strs, "biblioitems.publishercode LIKE ?";
592 push @where_args, "%$args->{publisher}%";
594 if( $args->{bookseller} ){
595 push @where_strs, "aqbooksellers.name LIKE ?";
596 push @where_args, "%$args->{bookseller}%";
598 if( $args->{branch} ){
599 push @where_strs, "subscription.branchcode = ?";
600 push @where_args, "$args->{branch}";
602 if ( $args->{location} ) {
603 push @where_strs, "subscription.location = ?";
604 push @where_args, "$args->{location}";
606 if ( $args->{expiration_date} ) {
607 push @where_strs, "subscription.enddate <= ?";
608 push @where_args, "$args->{expiration_date}";
610 if( defined $args->{closed} ){
611 push @where_strs, "subscription.closed = ?";
612 push @where_args, "$args->{closed}";
616 $query .= ' AND ' . join(' AND ', @where_strs);
618 if ( @$additional_fields ) {
619 $query .= ' AND subscriptionid IN ('
620 . join( ', ', @$matching_record_ids_for_additional_fields )
624 $query .= " ORDER BY " . $args->{orderby} if $args->{orderby};
626 my $dbh = C4::Context->dbh;
627 my $sth = $dbh->prepare($query);
628 $sth->execute(@where_args);
629 my $results = $sth->fetchall_arrayref( {} );
631 for my $subscription ( @$results ) {
632 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
633 $subscription->{cannotdisplay} = not can_show_subscription( $subscription );
635 my $subscription_object = Koha::Subscriptions->find($subscription->{subscriptionid});
636 $subscription->{additional_fields} = { map { $_->field->name => $_->value }
637 $subscription_object->additional_field_values->as_list };
647 ($totalissues,@serials) = GetSerials($subscriptionid);
648 this function gets every serial not arrived for a given subscription
649 as well as the number of issues registered in the database (all types)
650 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
652 FIXME: We should return \@serials.
657 my ( $subscriptionid, $count ) = @_;
659 return unless $subscriptionid;
661 my $dbh = C4::Context->dbh;
663 # status = 2 is "arrived"
665 $count = 5 unless ($count);
667 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES, NOT_ISSUED ) );
668 my $query = "SELECT serialid,serialseq, status, publisheddate,
669 publisheddatetext, planneddate,notes, routingnotes
671 WHERE subscriptionid = ? AND status NOT IN ( $statuses )
672 ORDER BY IF(publisheddate IS NULL,planneddate,publisheddate) DESC";
673 my $sth = $dbh->prepare($query);
674 $sth->execute($subscriptionid);
676 while ( my $line = $sth->fetchrow_hashref ) {
677 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
678 for my $datefield ( qw( planneddate publisheddate) ) {
679 if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
680 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
682 $line->{$datefield} = q{};
685 push @serials, $line;
688 # OK, now add the last 5 issues arrives/missing
689 $query = "SELECT serialid,serialseq, status, planneddate, publisheddate,
690 publisheddatetext, notes, routingnotes
692 WHERE subscriptionid = ?
693 AND status IN ( $statuses )
694 ORDER BY IF(publisheddate IS NULL,planneddate,publisheddate) DESC
696 $sth = $dbh->prepare($query);
697 $sth->execute($subscriptionid);
698 while ( ( my $line = $sth->fetchrow_hashref ) && $counter < $count ) {
700 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
701 for my $datefield ( qw( planneddate publisheddate) ) {
702 if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
703 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
705 $line->{$datefield} = q{};
709 push @serials, $line;
712 $query = "SELECT count(*) FROM serial WHERE subscriptionid=?";
713 $sth = $dbh->prepare($query);
714 $sth->execute($subscriptionid);
715 my ($totalissues) = $sth->fetchrow;
716 return ( $totalissues, @serials );
721 @serials = GetSerials2($subscriptionid,$statuses);
722 this function returns every serial waited for a given subscription
723 as well as the number of issues registered in the database (all types)
724 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
726 $statuses is an arrayref of statuses and is mandatory.
731 my ( $subscription, $statuses ) = @_;
733 return unless ($subscription and @$statuses);
735 my $dbh = C4::Context->dbh;
737 SELECT serialid,serialseq, status, planneddate, publisheddate,
738 publisheddatetext, notes, routingnotes
740 WHERE subscriptionid=?
742 . q| AND status IN (| . join( ",", ('?') x @$statuses ) . q|)|
744 ORDER BY publisheddate,serialid DESC
746 my $sth = $dbh->prepare($query);
747 $sth->execute( $subscription, @$statuses );
750 while ( my $line = $sth->fetchrow_hashref ) {
751 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
752 # Format dates for display
753 for my $datefield ( qw( planneddate publisheddate ) ) {
754 if (!defined($line->{$datefield}) || $line->{$datefield} =~m/^00/) {
755 $line->{$datefield} = q{};
758 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
761 push @serials, $line;
766 =head2 GetLatestSerials
768 \@serials = GetLatestSerials($subscriptionid,$limit)
769 get the $limit's latest serials arrived or missing for a given subscription
771 a ref to an array which contains all of the latest serials stored into a hash.
775 sub GetLatestSerials {
776 my ( $subscriptionid, $limit ) = @_;
778 return unless ($subscriptionid and $limit);
780 my $dbh = C4::Context->dbh;
782 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES ) );
783 my $strsth = "SELECT serialid,serialseq, status, planneddate, publisheddate, notes
785 WHERE subscriptionid = ?
786 AND status IN ($statuses)
787 ORDER BY publisheddate DESC LIMIT 0,$limit
789 my $sth = $dbh->prepare($strsth);
790 $sth->execute($subscriptionid);
792 while ( my $line = $sth->fetchrow_hashref ) {
793 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
794 push @serials, $line;
800 =head2 GetPreviousSerialid
802 $serialid = GetPreviousSerialid($subscriptionid, $nth)
803 get the $nth's previous serial for the given subscriptionid
809 sub GetPreviousSerialid {
810 my ( $subscriptionid, $nth ) = @_;
812 my $dbh = C4::Context->dbh;
816 my $strsth = "SELECT serialid
818 WHERE subscriptionid = ?
820 ORDER BY serialid DESC LIMIT $nth,1
822 my $sth = $dbh->prepare($strsth);
823 $sth->execute($subscriptionid);
825 my $line = $sth->fetchrow_hashref;
826 $return = $line->{'serialid'} if ($line);
834 $nextseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
835 $newinnerloop1, $newinnerloop2, $newinnerloop3
836 ) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate );
838 $subscription is a hashref containing all the attributes of the table
840 $pattern is a hashref containing all the attributes of the table
841 'subscription_numberpatterns'.
842 $frequency is a hashref containing all the attributes of the table 'subscription_frequencies'
843 $planneddate is a date string in iso format.
844 This function get the next issue for the subscription given on input arg
849 my ($subscription, $pattern, $frequency, $planneddate) = @_;
851 return unless ($subscription and $pattern);
853 my ( $newlastvalue1, $newlastvalue2, $newlastvalue3,
854 $newinnerloop1, $newinnerloop2, $newinnerloop3 );
857 if ($subscription->{'skip_serialseq'}) {
858 my @irreg = split /;/, $subscription->{'irregularity'};
860 my $irregularities = {};
861 $irregularities->{$_} = 1 foreach(@irreg);
862 my $issueno = GetFictiveIssueNumber($subscription, $planneddate, $frequency) + 1;
863 while($irregularities->{$issueno}) {
870 my $numberingmethod = $pattern->{numberingmethod};
872 if ($numberingmethod) {
873 $calculated = $numberingmethod;
874 my $locale = $subscription->{locale};
875 $newlastvalue1 = $subscription->{lastvalue1} || 0;
876 $newlastvalue2 = $subscription->{lastvalue2} || 0;
877 $newlastvalue3 = $subscription->{lastvalue3} || 0;
878 $newinnerloop1 = $subscription->{innerloop1} || 0;
879 $newinnerloop2 = $subscription->{innerloop2} || 0;
880 $newinnerloop3 = $subscription->{innerloop3} || 0;
883 $calc{$_} = 1 if ($numberingmethod =~ /\{$_\}/);
886 for(my $i = 0; $i < $count; $i++) {
888 # check if we have to increase the new value.
890 if ($newinnerloop1 >= $pattern->{every1}) {
892 $newlastvalue1 += $pattern->{add1};
894 # reset counter if needed.
895 $newlastvalue1 = $pattern->{setto1} if ($newlastvalue1 > $pattern->{whenmorethan1});
898 # check if we have to increase the new value.
900 if ($newinnerloop2 >= $pattern->{every2}) {
902 $newlastvalue2 += $pattern->{add2};
904 # reset counter if needed.
905 $newlastvalue2 = $pattern->{setto2} if ($newlastvalue2 > $pattern->{whenmorethan2});
908 # check if we have to increase the new value.
910 if ($newinnerloop3 >= $pattern->{every3}) {
912 $newlastvalue3 += $pattern->{add3};
914 # reset counter if needed.
915 $newlastvalue3 = $pattern->{setto3} if ($newlastvalue3 > $pattern->{whenmorethan3});
919 my $newlastvalue1string = _numeration( $newlastvalue1, $pattern->{numbering1}, $locale );
920 $calculated =~ s/\{X\}/$newlastvalue1string/g;
923 my $newlastvalue2string = _numeration( $newlastvalue2, $pattern->{numbering2}, $locale );
924 $calculated =~ s/\{Y\}/$newlastvalue2string/g;
927 my $newlastvalue3string = _numeration( $newlastvalue3, $pattern->{numbering3}, $locale );
928 $calculated =~ s/\{Z\}/$newlastvalue3string/g;
933 $newlastvalue1, $newlastvalue2, $newlastvalue3,
934 $newinnerloop1, $newinnerloop2, $newinnerloop3);
939 $calculated = GetSeq($subscription, $pattern)
940 $subscription is a hashref containing all the attributes of the table 'subscription'
941 $pattern is a hashref containing all the attributes of the table 'subscription_numberpatterns'
942 this function transforms {X},{Y},{Z} to 150,0,0 for example.
944 the sequence in string format
949 my ($subscription, $pattern) = @_;
951 return unless ($subscription and $pattern);
953 my $locale = $subscription->{locale};
955 my $calculated = $pattern->{numberingmethod};
957 my $newlastvalue1 = $subscription->{'lastvalue1'} || 0;
958 $newlastvalue1 = _numeration($newlastvalue1, $pattern->{numbering1}, $locale) if ($pattern->{numbering1}); # reset counter if needed.
959 $calculated =~ s/\{X\}/$newlastvalue1/g;
961 my $newlastvalue2 = $subscription->{'lastvalue2'} || 0;
962 $newlastvalue2 = _numeration($newlastvalue2, $pattern->{numbering2}, $locale) if ($pattern->{numbering2}); # reset counter if needed.
963 $calculated =~ s/\{Y\}/$newlastvalue2/g;
965 my $newlastvalue3 = $subscription->{'lastvalue3'} || 0;
966 $newlastvalue3 = _numeration($newlastvalue3, $pattern->{numbering3}, $locale) if ($pattern->{numbering3}); # reset counter if needed.
967 $calculated =~ s/\{Z\}/$newlastvalue3/g;
971 =head2 GetExpirationDate
973 $enddate = GetExpirationDate($subscriptionid, [$startdate])
975 this function return the next expiration date for a subscription given on input args.
982 sub GetExpirationDate {
983 my ( $subscriptionid, $startdate ) = @_;
985 return unless ($subscriptionid);
987 my $dbh = C4::Context->dbh;
988 my $subscription = GetSubscription($subscriptionid);
991 # we don't do the same test if the subscription is based on X numbers or on X weeks/months
992 $enddate = $startdate || $subscription->{startdate};
993 my @date = split( /-/, $enddate );
995 return if ( scalar(@date) != 3 || not check_date(@date) );
997 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
998 if ( $frequency and $frequency->{unit} ) {
1001 if ( my $length = $subscription->{numberlength} ) {
1003 #calculate the date of the last issue.
1004 for ( my $i = 1 ; $i <= $length ; $i++ ) {
1005 $enddate = GetNextDate( $subscription, $enddate, $frequency );
1007 } elsif ( $subscription->{monthlength} ) {
1008 if ( $$subscription{startdate} ) {
1009 my @enddate = Add_Delta_YM( $date[0], $date[1], $date[2], 0, $subscription->{monthlength} );
1010 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1012 } elsif ( $subscription->{weeklength} ) {
1013 if ( $$subscription{startdate} ) {
1014 my @date = split( /-/, $subscription->{startdate} );
1015 my @enddate = Add_Delta_Days( $date[0], $date[1], $date[2], $subscription->{weeklength} * 7 );
1016 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1019 $enddate = $subscription->{enddate};
1023 return $subscription->{enddate};
1027 =head2 CountSubscriptionFromBiblionumber
1029 $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber)
1030 this returns a count of the subscriptions for a given biblionumber
1032 the number of subscriptions
1036 sub CountSubscriptionFromBiblionumber {
1037 my ($biblionumber) = @_;
1039 return unless ($biblionumber);
1041 my $dbh = C4::Context->dbh;
1042 my $query = "SELECT count(*) FROM subscription WHERE biblionumber=?";
1043 my $sth = $dbh->prepare($query);
1044 $sth->execute($biblionumber);
1045 my $subscriptionsnumber = $sth->fetchrow;
1046 return $subscriptionsnumber;
1049 =head2 ModSubscriptionHistory
1051 ModSubscriptionHistory($subscriptionid,$histstartdate,$enddate,$recievedlist,$missinglist,$opacnote,$librariannote);
1053 this function modifies the history of a subscription. Put your new values on input arg.
1054 returns the number of rows affected
1058 sub ModSubscriptionHistory {
1059 my ( $subscriptionid, $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote ) = @_;
1061 return unless ($subscriptionid);
1063 my $dbh = C4::Context->dbh;
1064 my $query = "UPDATE subscriptionhistory
1065 SET histstartdate=?,histenddate=?,recievedlist=?,missinglist=?,opacnote=?,librariannote=?
1066 WHERE subscriptionid=?
1068 my $sth = $dbh->prepare($query);
1069 $receivedlist =~ s/^; // if $receivedlist;
1070 $missinglist =~ s/^; // if $missinglist;
1071 $opacnote =~ s/^; // if $opacnote;
1072 $sth->execute( $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote, $subscriptionid );
1076 =head2 ModSerialStatus
1078 ModSerialStatus($serialid, $serialseq, $planneddate, $publisheddate,
1079 $publisheddatetext, $status, $notes);
1081 This function modify the serial status. Serial status is a number.(eg 2 is "arrived")
1082 Note : if we change from "waited" to something else,then we will have to create a new "waited" entry
1086 sub ModSerialStatus {
1087 my ($serialid, $serialseq, $planneddate, $publisheddate, $publisheddatetext,
1088 $status, $notes) = @_;
1090 return unless ($serialid);
1092 #It is a usual serial
1093 # 1st, get previous status :
1094 my $dbh = C4::Context->dbh;
1095 my $query = "SELECT serial.subscriptionid,serial.status,subscription.periodicity,serial.routingnotes
1096 FROM serial, subscription
1097 WHERE serial.subscriptionid=subscription.subscriptionid
1099 my $sth = $dbh->prepare($query);
1100 $sth->execute($serialid);
1101 my ( $subscriptionid, $oldstatus, $periodicity, $routingnotes ) = $sth->fetchrow;
1102 my $frequency = GetSubscriptionFrequency($periodicity);
1104 # change status & update subscriptionhistory
1106 if ( $status == DELETED ) {
1107 DelIssue( { 'serialid' => $serialid, 'subscriptionid' => $subscriptionid, 'serialseq' => $serialseq } );
1111 SET serialseq = ?, publisheddate = ?, publisheddatetext = ?,
1112 planneddate = ?, status = ?, notes = ?, routingnotes = ?
1115 $sth = $dbh->prepare($query);
1116 $sth->execute( $serialseq, $publisheddate, $publisheddatetext,
1117 $planneddate, $status, $notes, $routingnotes, $serialid );
1118 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1119 $sth = $dbh->prepare($query);
1120 $sth->execute($subscriptionid);
1121 my $val = $sth->fetchrow_hashref;
1122 unless ( $val->{manualhistory} ) {
1123 $query = "SELECT missinglist,recievedlist FROM subscriptionhistory WHERE subscriptionid=?";
1124 $sth = $dbh->prepare($query);
1125 $sth->execute($subscriptionid);
1126 my ( $missinglist, $recievedlist ) = $sth->fetchrow;
1128 if ( $status == ARRIVED || ($oldstatus == ARRIVED && $status != ARRIVED) ) {
1129 $recievedlist = _handle_seqno($serialseq, $recievedlist);
1132 # in case serial has been previously marked as missing
1133 if (grep /$status/, (EXPECTED, ARRIVED, LATE, CLAIMED)) {
1134 $missinglist = _handle_seqno($serialseq, $missinglist, 'REMOVE');
1137 $missinglist = _handle_seqno($serialseq, $missinglist) if grep { $_ == $status } MISSING_STATUSES;
1138 $missinglist .= "; not issued $serialseq" if $status == NOT_ISSUED and not _handle_seqno($serialseq, $missinglist, 'CHECK');
1140 $query = "UPDATE subscriptionhistory SET recievedlist=?, missinglist=? WHERE subscriptionid=?";
1141 $sth = $dbh->prepare($query);
1142 $recievedlist =~ s/^; //;
1143 $missinglist =~ s/^; //;
1144 $sth->execute( $recievedlist, $missinglist, $subscriptionid );
1148 # create new expected entry if needed (ie : was "expected" and has changed)
1149 my $otherIssueExpected = scalar findSerialsByStatus(EXPECTED, $subscriptionid);
1150 if ( !$otherIssueExpected && $oldstatus == EXPECTED && $status != EXPECTED ) {
1151 my $subscription = GetSubscription($subscriptionid);
1152 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1153 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1157 $newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
1158 $newinnerloop1, $newinnerloop2, $newinnerloop3
1160 = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate );
1162 # next date (calculated from actual date & frequency parameters)
1163 my $nextpublisheddate = GetNextDate($subscription, $publisheddate, $frequency, 1);
1164 my $nextpubdate = $nextpublisheddate;
1165 $query = "UPDATE subscription SET lastvalue1=?, lastvalue2=?, lastvalue3=?, innerloop1=?, innerloop2=?, innerloop3=?
1166 WHERE subscriptionid = ?";
1167 $sth = $dbh->prepare($query);
1168 $sth->execute( $newlastvalue1, $newlastvalue2, $newlastvalue3, $newinnerloop1, $newinnerloop2, $newinnerloop3, $subscriptionid );
1169 my $newnote = C4::Context->preference('PreserveSerialNotes') ? $notes : "";
1170 NewIssue( $newserialseq, $subscriptionid, $subscription->{'biblionumber'}, 1, $nextpubdate, $nextpubdate, undef, $newnote, $routingnotes );
1171 # check if an alert must be sent... (= a letter is defined & status became "arrived"
1172 if ( $subscription->{letter} && $status == ARRIVED && $oldstatus != ARRIVED ) {
1173 require C4::Letters;
1174 C4::Letters::SendAlerts( 'issue', $serialid, $subscription->{letter} );
1182 # Adds or removes seqno from list when needed; returns list
1183 # Or checks and returns true when present
1185 my ( $seq, $list, $op ) = @_; # op = ADD | REMOVE | CHECK (default: ADD)
1187 $seq_r =~ s/([()])/\\$1/g; # Adjust disturbing parentheses for regex, maybe extend in future
1189 if( !$op or $op eq 'ADD' ) {
1190 $list .= "; $seq" if $list !~ /(^|;)\s*$seq_r(?=;|$)/;
1191 } elsif( $op eq 'REMOVE' ) {
1192 $list=~ s/(^|;)\s*(not issued )?$seq_r(?=;|$)//g;
1194 return $list =~ /(^|;)\s*$seq_r(?=;|$)/ ? 1 : q{};
1199 =head2 GetNextExpected
1201 $nextexpected = GetNextExpected($subscriptionid)
1203 Get the planneddate for the current expected issue of the subscription.
1209 planneddate => ISO date
1214 sub GetNextExpected {
1215 my ($subscriptionid) = @_;
1217 my $dbh = C4::Context->dbh;
1221 WHERE subscriptionid = ?
1225 my $sth = $dbh->prepare($query);
1227 # Each subscription has only one 'expected' issue.
1228 $sth->execute( $subscriptionid, EXPECTED );
1229 my $nextissue = $sth->fetchrow_hashref;
1230 if ( !$nextissue ) {
1234 WHERE subscriptionid = ?
1235 ORDER BY publisheddate DESC
1238 $sth = $dbh->prepare($query);
1239 $sth->execute($subscriptionid);
1240 $nextissue = $sth->fetchrow_hashref;
1242 foreach(qw/planneddate publisheddate/) {
1243 # or should this default to 1st Jan ???
1244 $nextissue->{$_} //= strftime( '%Y-%m-%d', localtime );
1250 =head2 ModNextExpected
1252 ModNextExpected($subscriptionid,$date)
1254 Update the planneddate for the current expected issue of the subscription.
1255 This will modify all future prediction results.
1257 C<$date> is an ISO date.
1263 sub ModNextExpected {
1264 my ( $subscriptionid, $date ) = @_;
1265 my $dbh = C4::Context->dbh;
1267 #FIXME: Would expect to only set planneddate, but we set both on new issue creation, so updating it here
1268 my $sth = $dbh->prepare('UPDATE serial SET planneddate=?,publisheddate=? WHERE subscriptionid=? AND status=?');
1270 # Each subscription has only one 'expected' issue.
1271 $sth->execute( $date, $date, $subscriptionid, EXPECTED );
1276 =head2 GetSubscriptionIrregularities
1280 =item @irreg = &GetSubscriptionIrregularities($subscriptionid);
1281 get the list of irregularities for a subscription
1287 sub GetSubscriptionIrregularities {
1288 my $subscriptionid = shift;
1290 return unless $subscriptionid;
1292 my $dbh = C4::Context->dbh;
1296 WHERE subscriptionid = ?
1298 my $sth = $dbh->prepare($query);
1299 $sth->execute($subscriptionid);
1301 my ($result) = $sth->fetchrow_array;
1302 my @irreg = split /;/, $result;
1307 =head2 ModSubscription
1309 this function modifies a subscription. Put all new values on input args.
1310 returns the number of rows affected
1314 sub ModSubscription {
1316 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
1317 $periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
1318 $numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
1319 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
1320 $biblionumber, $callnumber, $notes, $letter, $manualhistory,
1321 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1322 $graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
1323 $itemtype, $previousitemtype, $mana_id
1326 my $subscription = Koha::Subscriptions->find($subscriptionid);
1329 librarian => $auser,
1330 branchcode => $branchcode,
1331 aqbooksellerid => $aqbooksellerid,
1333 aqbudgetid => $aqbudgetid,
1334 biblionumber => $biblionumber,
1335 startdate => $startdate,
1336 periodicity => $periodicity,
1337 numberlength => $numberlength,
1338 weeklength => $weeklength,
1339 monthlength => $monthlength,
1340 lastvalue1 => $lastvalue1,
1341 innerloop1 => $innerloop1,
1342 lastvalue2 => $lastvalue2,
1343 innerloop2 => $innerloop2,
1344 lastvalue3 => $lastvalue3,
1345 innerloop3 => $innerloop3,
1349 firstacquidate => $firstacquidate,
1350 irregularity => $irregularity,
1351 numberpattern => $numberpattern,
1353 callnumber => $callnumber,
1354 manualhistory => $manualhistory,
1355 internalnotes => $internalnotes,
1356 serialsadditems => $serialsadditems,
1357 staffdisplaycount => $staffdisplaycount,
1358 opacdisplaycount => $opacdisplaycount,
1359 graceperiod => $graceperiod,
1360 location => $location,
1361 enddate => $enddate,
1362 skip_serialseq => $skip_serialseq,
1363 itemtype => $itemtype,
1364 previousitemtype => $previousitemtype,
1365 mana_id => $mana_id,
1368 # FIXME Must be $subscription->serials
1369 # FIXME We shouldn't need serial.subscription (instead use serial->subscription->biblionumber)
1370 Koha::Serials->search({ subscriptionid => $subscriptionid })->update({ biblionumber => $biblionumber });
1372 logaction( "SERIAL", "MODIFY", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1374 $subscription->discard_changes;
1375 return $subscription;
1378 =head2 NewSubscription
1380 $subscriptionid = &NewSubscription($auser,branchcode,$aqbooksellerid,$cost,$aqbudgetid,$biblionumber,
1381 $startdate,$periodicity,$numberlength,$weeklength,$monthlength,
1382 $lastvalue1,$innerloop1,$lastvalue2,$innerloop2,$lastvalue3,$innerloop3,
1383 $status, $notes, $letter, $firstacquidate, $irregularity, $numberpattern,
1384 $locale, $callnumber, $manualhistory, $internalnotes, $serialsadditems,
1385 $staffdisplaycount, $opacdisplaycount, $graceperiod, $location, $enddate,
1386 $skip_serialseq, $itemtype, $previousitemtype);
1388 Create a new subscription with value given on input args.
1391 the id of this new subscription
1395 sub NewSubscription {
1397 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1398 $startdate, $periodicity, $numberlength, $weeklength, $monthlength,
1399 $lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
1400 $innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
1401 $numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
1402 $serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
1403 $location, $enddate, $skip_serialseq, $itemtype, $previousitemtype, $mana_id
1405 my $dbh = C4::Context->dbh;
1407 my $subscription = Koha::Subscription->new(
1409 librarian => $auser,
1410 branchcode => $branchcode,
1411 aqbooksellerid => $aqbooksellerid,
1413 aqbudgetid => $aqbudgetid,
1414 biblionumber => $biblionumber,
1415 startdate => $startdate,
1416 periodicity => $periodicity,
1417 numberlength => $numberlength,
1418 weeklength => $weeklength,
1419 monthlength => $monthlength,
1420 lastvalue1 => $lastvalue1,
1421 innerloop1 => $innerloop1,
1422 lastvalue2 => $lastvalue2,
1423 innerloop2 => $innerloop2,
1424 lastvalue3 => $lastvalue3,
1425 innerloop3 => $innerloop3,
1429 firstacquidate => $firstacquidate,
1430 irregularity => $irregularity,
1431 numberpattern => $numberpattern,
1433 callnumber => $callnumber,
1434 manualhistory => $manualhistory,
1435 internalnotes => $internalnotes,
1436 serialsadditems => $serialsadditems,
1437 staffdisplaycount => $staffdisplaycount,
1438 opacdisplaycount => $opacdisplaycount,
1439 graceperiod => $graceperiod,
1440 location => $location,
1441 enddate => $enddate,
1442 skip_serialseq => $skip_serialseq,
1443 itemtype => $itemtype,
1444 previousitemtype => $previousitemtype,
1445 mana_id => $mana_id,
1448 $subscription->discard_changes;
1449 my $subscriptionid = $subscription->subscriptionid;
1450 my ( $query, $sth );
1452 $enddate = GetExpirationDate( $subscriptionid, $startdate );
1456 WHERE subscriptionid=?
1458 $sth = $dbh->prepare($query);
1459 $sth->execute( $enddate, $subscriptionid );
1462 # then create the 1st expected number
1464 INSERT INTO subscriptionhistory
1465 (biblionumber, subscriptionid, histstartdate, missinglist, recievedlist)
1466 VALUES (?,?,?, '', '')
1468 $sth = $dbh->prepare($query);
1469 $sth->execute( $biblionumber, $subscriptionid, $startdate);
1471 # reread subscription to get a hash (for calculation of the 1st issue number)
1472 $subscription = GetSubscription($subscriptionid); # We should not do that
1473 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1475 # calculate issue number
1476 my $serialseq = GetSeq($subscription, $pattern) || q{};
1480 serialseq => $serialseq,
1481 serialseq_x => $subscription->{'lastvalue1'},
1482 serialseq_y => $subscription->{'lastvalue2'},
1483 serialseq_z => $subscription->{'lastvalue3'},
1484 subscriptionid => $subscriptionid,
1485 biblionumber => $biblionumber,
1487 planneddate => $firstacquidate,
1488 publisheddate => $firstacquidate,
1492 logaction( "SERIAL", "ADD", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1494 #set serial flag on biblio if not already set.
1495 my $biblio = Koha::Biblios->find( $biblionumber );
1496 if ( $biblio and !$biblio->serial ) {
1497 my $record = GetMarcBiblio({ biblionumber => $biblionumber });
1498 my ( $tag, $subf ) = GetMarcFromKohaField( 'biblio.serial' );
1500 eval { $record->field($tag)->update( $subf => 1 ); };
1502 ModBiblio( $record, $biblionumber, $biblio->frameworkcode );
1504 return $subscriptionid;
1507 =head2 GetSubscriptionLength
1509 my ($numberlength, $weeklength, $monthlength) = GetSubscriptionLength( $subtype, $sublength );
1511 This function calculates the subscription length.
1515 sub GetSubscriptionLength {
1516 my ($subtype, $length) = @_;
1518 return unless looks_like_number($length);
1522 $subtype eq 'issues' ? $length : 0,
1523 $subtype eq 'weeks' ? $length : 0,
1524 $subtype eq 'months' ? $length : 0,
1529 =head2 ReNewSubscription
1531 ReNewSubscription($params);
1533 $params is a hashref with the following keys: subscriptionid, user, startdate, numberlength, weeklength, monthlength, note, branchcode
1535 this function renew a subscription with values given on input args.
1539 sub ReNewSubscription {
1540 my ( $params ) = @_;
1541 my $subscriptionid = $params->{subscriptionid};
1542 my $user = $params->{user};
1543 my $startdate = $params->{startdate};
1544 my $numberlength = $params->{numberlength};
1545 my $weeklength = $params->{weeklength};
1546 my $monthlength = $params->{monthlength};
1547 my $note = $params->{note};
1548 my $branchcode = $params->{branchcode};
1550 my $dbh = C4::Context->dbh;
1551 my $subscription = GetSubscription($subscriptionid);
1555 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
1556 WHERE biblio.biblionumber=?
1558 my $sth = $dbh->prepare($query);
1559 $sth->execute( $subscription->{biblionumber} );
1560 my $biblio = $sth->fetchrow_hashref;
1562 if ( C4::Context->preference("RenewSerialAddsSuggestion") ) {
1563 require C4::Suggestions;
1564 C4::Suggestions::NewSuggestion(
1565 { 'suggestedby' => $user,
1566 'title' => $subscription->{bibliotitle},
1567 'author' => $biblio->{author},
1568 'publishercode' => $biblio->{publishercode},
1570 'biblionumber' => $subscription->{biblionumber},
1571 'branchcode' => $branchcode,
1576 $numberlength ||= 0; # Should not we raise an exception instead?
1579 # renew subscription
1582 SET startdate=?,numberlength=?,weeklength=?,monthlength=?,reneweddate=NOW()
1583 WHERE subscriptionid=?
1585 $sth = $dbh->prepare($query);
1586 $sth->execute( $startdate, $numberlength, $weeklength, $monthlength, $subscriptionid );
1587 my $enddate = GetExpirationDate($subscriptionid);
1591 WHERE subscriptionid=?
1593 $sth = $dbh->prepare($query);
1594 $sth->execute( $enddate, $subscriptionid );
1596 logaction( "SERIAL", "RENEW", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1602 NewIssue($serialseq,$subscriptionid,$biblionumber,$status, $planneddate, $publisheddate, $notes, $routingnotes)
1604 Create a new issue stored on the database.
1605 Note : we have to update the recievedlist and missinglist on subscriptionhistory for this subscription.
1606 returns the serial id
1611 my ( $serialseq, $subscriptionid, $biblionumber, $status, $planneddate,
1612 $publisheddate, $publisheddatetext, $notes, $routingnotes ) = @_;
1613 ### FIXME biblionumber CAN be provided by subscriptionid. So Do we STILL NEED IT ?
1615 return unless ($subscriptionid);
1617 my $schema = Koha::Database->new()->schema();
1619 my $subscription = Koha::Subscriptions->find( $subscriptionid );
1621 my $serial = Koha::Serial->new(
1623 serialseq => $serialseq,
1624 serialseq_x => $subscription->lastvalue1(),
1625 serialseq_y => $subscription->lastvalue2(),
1626 serialseq_z => $subscription->lastvalue3(),
1627 subscriptionid => $subscriptionid,
1628 biblionumber => $biblionumber,
1630 planneddate => $planneddate,
1631 publisheddate => $publisheddate,
1632 publisheddatetext => $publisheddatetext,
1634 routingnotes => $routingnotes
1638 my $serialid = $serial->id();
1640 my $subscription_history = Koha::Subscription::Histories->find($subscriptionid);
1641 my $missinglist = $subscription_history->missinglist();
1642 my $recievedlist = $subscription_history->recievedlist();
1644 if ( $status == ARRIVED ) {
1645 ### TODO Add a feature that improves recognition and description.
1646 ### As such count (serialseq) i.e. : N18,2(N19),N20
1647 ### Would use substr and index But be careful to previous presence of ()
1648 $recievedlist .= "; $serialseq" unless ( index( $recievedlist, $serialseq ) > 0 );
1650 if ( grep { $_ eq $status } (MISSING_STATUSES) ) {
1651 $missinglist .= "; $serialseq" unless ( index( $missinglist, $serialseq ) > 0 );
1654 $recievedlist =~ s/^; //;
1655 $missinglist =~ s/^; //;
1657 $subscription_history->recievedlist($recievedlist);
1658 $subscription_history->missinglist($missinglist);
1659 $subscription_history->store();
1664 =head2 HasSubscriptionStrictlyExpired
1666 1 or 0 = HasSubscriptionStrictlyExpired($subscriptionid)
1668 the subscription has stricly expired when today > the end subscription date
1671 1 if true, 0 if false, -1 if the expiration date is not set.
1675 sub HasSubscriptionStrictlyExpired {
1677 # Getting end of subscription date
1678 my ($subscriptionid) = @_;
1680 return unless ($subscriptionid);
1682 my $dbh = C4::Context->dbh;
1683 my $subscription = GetSubscription($subscriptionid);
1684 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1686 # If the expiration date is set
1687 if ( $expirationdate != 0 ) {
1688 my ( $endyear, $endmonth, $endday ) = split( '-', $expirationdate );
1690 # Getting today's date
1691 my ( $nowyear, $nowmonth, $nowday ) = Today();
1693 # if today's date > expiration date, then the subscription has stricly expired
1694 if ( Delta_Days( $nowyear, $nowmonth, $nowday, $endyear, $endmonth, $endday ) < 0 ) {
1701 # There are some cases where the expiration date is not set
1702 # As we can't determine if the subscription has expired on a date-basis,
1708 =head2 HasSubscriptionExpired
1710 $has_expired = HasSubscriptionExpired($subscriptionid)
1712 the subscription has expired when the next issue to arrive is out of subscription limit.
1715 0 if the subscription has not expired
1716 1 if the subscription has expired
1717 2 if has subscription does not have a valid expiration date set
1721 sub HasSubscriptionExpired {
1722 my ($subscriptionid) = @_;
1724 return unless ($subscriptionid);
1726 my $dbh = C4::Context->dbh;
1727 my $subscription = GetSubscription($subscriptionid);
1728 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1729 if ( $frequency and $frequency->{unit} ) {
1730 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1731 if (!defined $expirationdate) {
1732 $expirationdate = q{};
1735 SELECT max(planneddate)
1737 WHERE subscriptionid=?
1739 my $sth = $dbh->prepare($query);
1740 $sth->execute($subscriptionid);
1741 my ($res) = $sth->fetchrow;
1742 if (!$res || $res=~m/^0000/) {
1745 my @res = split( /-/, $res );
1746 my @endofsubscriptiondate = split( /-/, $expirationdate );
1747 return 2 if ( scalar(@res) != 3 || scalar(@endofsubscriptiondate) != 3 || not check_date(@res) || not check_date(@endofsubscriptiondate) );
1749 if ( ( @endofsubscriptiondate && Delta_Days( $res[0], $res[1], $res[2], $endofsubscriptiondate[0], $endofsubscriptiondate[1], $endofsubscriptiondate[2] ) <= 0 )
1754 if ( $subscription->{'numberlength'} ) {
1755 my $countreceived = countissuesfrom( $subscriptionid, $subscription->{'startdate'} );
1756 return 1 if ( $countreceived > $subscription->{'numberlength'} );
1762 return 0; # Notice that you'll never get here.
1765 =head2 DelSubscription
1767 DelSubscription($subscriptionid)
1768 this function deletes subscription which has $subscriptionid as id.
1772 sub DelSubscription {
1773 my ($subscriptionid) = @_;
1774 my $dbh = C4::Context->dbh;
1775 $dbh->do("DELETE FROM subscription WHERE subscriptionid=?", undef, $subscriptionid);
1777 Koha::AdditionalFieldValues->search({
1778 'field.tablename' => 'subscription',
1779 'me.record_id' => $subscriptionid,
1780 }, { join => 'field' })->delete;
1782 logaction( "SERIAL", "DELETE", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1787 DelIssue($serialseq,$subscriptionid)
1788 this function deletes an issue which has $serialseq and $subscriptionid given on input arg.
1790 returns the number of rows affected
1795 my ($dataissue) = @_;
1796 my $dbh = C4::Context->dbh;
1797 ### TODO Add itemdeletion. Would need to get itemnumbers. Should be in a pref ?
1802 AND subscriptionid= ?
1804 my $mainsth = $dbh->prepare($query);
1805 $mainsth->execute( $dataissue->{'serialid'}, $dataissue->{'subscriptionid'} );
1807 #Delete element from subscription history
1808 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1809 my $sth = $dbh->prepare($query);
1810 $sth->execute( $dataissue->{'subscriptionid'} );
1811 my $val = $sth->fetchrow_hashref;
1812 unless ( $val->{manualhistory} ) {
1814 SELECT * FROM subscriptionhistory
1815 WHERE subscriptionid= ?
1817 my $sth = $dbh->prepare($query);
1818 $sth->execute( $dataissue->{'subscriptionid'} );
1819 my $data = $sth->fetchrow_hashref;
1820 my $serialseq = $dataissue->{'serialseq'};
1821 $data->{'missinglist'} =~ s/\b$serialseq\b//;
1822 $data->{'recievedlist'} =~ s/\b$serialseq\b//;
1823 my $strsth = "UPDATE subscriptionhistory SET " . join( ",", map { join( "=", $_, $dbh->quote( $data->{$_} ) ) } keys %$data ) . " WHERE subscriptionid=?";
1824 $sth = $dbh->prepare($strsth);
1825 $sth->execute( $dataissue->{'subscriptionid'} );
1828 return $mainsth->rows;
1831 =head2 GetLateOrMissingIssues
1833 @issuelist = GetLateMissingIssues($supplierid,$serialid)
1835 this function selects missing issues on database - where serial.status = MISSING* or serial.status = LATE or planneddate<now
1838 the issuelist as an array of hash refs. Each element of this array contains
1839 name,title,planneddate,serialseq,serial.subscriptionid from tables : subscription, serial & biblio
1843 sub GetLateOrMissingIssues {
1844 my ( $supplierid, $serialid, $order ) = @_;
1846 return unless ( $supplierid or $serialid );
1848 my $dbh = C4::Context->dbh;
1853 $byserial = "and serialid = " . $serialid;
1856 $order .= ", title";
1860 my $missing_statuses_string = join ',', (MISSING_STATUSES);
1862 $sth = $dbh->prepare(
1864 serialid, aqbooksellerid, name,
1865 biblio.title, biblioitems.issn, planneddate, serialseq,
1866 serial.status, serial.subscriptionid, claimdate, claims_count,
1867 subscription.branchcode
1869 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1870 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1871 LEFT JOIN biblioitems ON subscription.biblionumber=biblioitems.biblionumber
1872 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1873 WHERE subscription.subscriptionid = serial.subscriptionid
1874 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1875 AND subscription.aqbooksellerid=$supplierid
1880 $sth = $dbh->prepare(
1882 serialid, aqbooksellerid, name,
1883 biblio.title, planneddate, serialseq,
1884 serial.status, serial.subscriptionid, claimdate, claims_count,
1885 subscription.branchcode
1887 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1888 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1889 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1890 WHERE subscription.subscriptionid = serial.subscriptionid
1891 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1896 $sth->execute( EXPECTED, LATE, CLAIMED );
1898 while ( my $line = $sth->fetchrow_hashref ) {
1900 if ($line->{planneddate} && $line->{planneddate} !~/^0+\-/) {
1901 $line->{planneddateISO} = $line->{planneddate};
1902 $line->{planneddate} = output_pref( { dt => dt_from_string( $line->{"planneddate"} ), dateonly => 1 } );
1904 if ($line->{claimdate} && $line->{claimdate} !~/^0+\-/) {
1905 $line->{claimdateISO} = $line->{claimdate};
1906 $line->{claimdate} = output_pref( { dt => dt_from_string( $line->{"claimdate"} ), dateonly => 1 } );
1908 $line->{"status".$line->{status}} = 1;
1910 my $subscription_object = Koha::Subscriptions->find($line->{subscriptionid});
1911 $line->{additional_fields} = { map { $_->field->name => $_->value }
1912 $subscription_object->additional_field_values->as_list };
1914 push @issuelist, $line;
1921 &updateClaim($serialid)
1923 this function updates the time when a claim is issued for late/missing items
1925 called from claims.pl file
1930 my ($serialids) = @_;
1931 return unless $serialids;
1932 unless ( ref $serialids ) {
1933 $serialids = [ $serialids ];
1935 my $dbh = C4::Context->dbh;
1938 SET claimdate = NOW(),
1939 claims_count = claims_count + 1,
1941 WHERE serialid in (| . join( q|,|, (q|?|) x @$serialids ) . q|)|,
1942 {}, CLAIMED, @$serialids );
1945 =head2 check_routing
1947 $result = &check_routing($subscriptionid)
1949 this function checks to see if a serial has a routing list and returns the count of routingid
1950 used to show either an 'add' or 'edit' link
1955 my ($subscriptionid) = @_;
1957 return unless ($subscriptionid);
1959 my $dbh = C4::Context->dbh;
1960 my $sth = $dbh->prepare(
1961 "SELECT count(routingid) routingids FROM subscription LEFT JOIN subscriptionroutinglist
1962 ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
1963 WHERE subscription.subscriptionid = ? GROUP BY routingid ORDER BY ranking ASC
1966 $sth->execute($subscriptionid);
1967 my $line = $sth->fetchrow_hashref;
1968 my $result = $line->{'routingids'};
1972 =head2 addroutingmember
1974 addroutingmember($borrowernumber,$subscriptionid)
1976 this function takes a borrowernumber and subscriptionid and adds the member to the
1977 routing list for that serial subscription and gives them a rank on the list
1978 of either 1 or highest current rank + 1
1982 sub addroutingmember {
1983 my ( $borrowernumber, $subscriptionid ) = @_;
1985 return unless ($borrowernumber and $subscriptionid);
1988 my $dbh = C4::Context->dbh;
1989 my $sth = $dbh->prepare( "SELECT max(ranking) rank FROM subscriptionroutinglist WHERE subscriptionid = ?" );
1990 $sth->execute($subscriptionid);
1991 while ( my $line = $sth->fetchrow_hashref ) {
1992 if ( $line->{'rank'} > 0 ) {
1993 $rank = $line->{'rank'} + 1;
1998 $sth = $dbh->prepare( "INSERT INTO subscriptionroutinglist (subscriptionid,borrowernumber,ranking) VALUES (?,?,?)" );
1999 $sth->execute( $subscriptionid, $borrowernumber, $rank );
2002 =head2 reorder_members
2004 reorder_members($subscriptionid,$routingid,$rank)
2006 this function is used to reorder the routing list
2008 it takes the routingid of the member one wants to re-rank and the rank it is to move to
2009 - it gets all members on list puts their routingid's into an array
2010 - removes the one in the array that is $routingid
2011 - then reinjects $routingid at point indicated by $rank
2012 - then update the database with the routingids in the new order
2016 sub reorder_members {
2017 my ( $subscriptionid, $routingid, $rank ) = @_;
2018 my $dbh = C4::Context->dbh;
2019 my $sth = $dbh->prepare( "SELECT * FROM subscriptionroutinglist WHERE subscriptionid = ? ORDER BY ranking ASC" );
2020 $sth->execute($subscriptionid);
2022 while ( my $line = $sth->fetchrow_hashref ) {
2023 push( @result, $line->{'routingid'} );
2026 # To find the matching index
2028 my $key = -1; # to allow for 0 being a valid response
2029 for ( $i = 0 ; $i < @result ; $i++ ) {
2030 if ( $routingid == $result[$i] ) {
2031 $key = $i; # save the index
2036 # if index exists in array then move it to new position
2037 if ( $key > -1 && $rank > 0 ) {
2038 my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
2039 my $moving_item = splice( @result, $key, 1 );
2040 splice( @result, $new_rank, 0, $moving_item );
2042 for ( my $j = 0 ; $j < @result ; $j++ ) {
2043 my $sth = $dbh->prepare( "UPDATE subscriptionroutinglist SET ranking = '" . ( $j + 1 ) . "' WHERE routingid = '" . $result[$j] . "'" );
2049 =head2 delroutingmember
2051 delroutingmember($routingid,$subscriptionid)
2053 this function either deletes one member from routing list if $routingid exists otherwise
2054 deletes all members from the routing list
2058 sub delroutingmember {
2060 # if $routingid exists then deletes that row otherwise deletes all with $subscriptionid
2061 my ( $routingid, $subscriptionid ) = @_;
2062 my $dbh = C4::Context->dbh;
2064 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE routingid = ?");
2065 $sth->execute($routingid);
2066 reorder_members( $subscriptionid, $routingid );
2068 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE subscriptionid = ?");
2069 $sth->execute($subscriptionid);
2074 =head2 getroutinglist
2076 @routinglist = getroutinglist($subscriptionid)
2078 this gets the info from the subscriptionroutinglist for $subscriptionid
2081 the routinglist as an array. Each element of the array contains a hash_ref containing
2082 routingid - a unique id, borrowernumber, ranking, and biblionumber of subscription
2086 sub getroutinglist {
2087 my ($subscriptionid) = @_;
2088 my $dbh = C4::Context->dbh;
2089 my $sth = $dbh->prepare(
2090 'SELECT routingid, borrowernumber, ranking, biblionumber
2092 JOIN subscriptionroutinglist ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
2093 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC'
2095 $sth->execute($subscriptionid);
2096 my $routinglist = $sth->fetchall_arrayref({});
2097 return @{$routinglist};
2100 =head2 countissuesfrom
2102 $result = countissuesfrom($subscriptionid,$startdate)
2104 Returns a count of serial rows matching the given subsctiptionid
2105 with published date greater than startdate
2109 sub countissuesfrom {
2110 my ( $subscriptionid, $startdate ) = @_;
2111 my $dbh = C4::Context->dbh;
2115 WHERE subscriptionid=?
2116 AND serial.publisheddate>?
2118 my $sth = $dbh->prepare($query);
2119 $sth->execute( $subscriptionid, $startdate );
2120 my ($countreceived) = $sth->fetchrow;
2121 return $countreceived;
2126 $result = CountIssues($subscriptionid)
2128 Returns a count of serial rows matching the given subsctiptionid
2133 my ($subscriptionid) = @_;
2134 my $dbh = C4::Context->dbh;
2138 WHERE subscriptionid=?
2140 my $sth = $dbh->prepare($query);
2141 $sth->execute($subscriptionid);
2142 my ($countreceived) = $sth->fetchrow;
2143 return $countreceived;
2148 $result = HasItems($subscriptionid)
2150 returns a count of items from serial matching the subscriptionid
2155 my ($subscriptionid) = @_;
2156 my $dbh = C4::Context->dbh;
2158 SELECT COUNT(serialitems.itemnumber)
2160 LEFT JOIN serialitems USING(serialid)
2161 WHERE subscriptionid=? AND serialitems.serialid IS NOT NULL
2163 my $sth=$dbh->prepare($query);
2164 $sth->execute($subscriptionid);
2165 my ($countitems)=$sth->fetchrow_array();
2169 =head2 abouttoexpire
2171 $result = abouttoexpire($subscriptionid)
2173 this function alerts you to the penultimate issue for a serial subscription
2175 returns 1 - if this is the penultimate issue
2181 my ($subscriptionid) = @_;
2182 my $dbh = C4::Context->dbh;
2183 my $subscription = GetSubscription($subscriptionid);
2184 my $per = $subscription->{'periodicity'};
2185 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($per);
2186 if ($frequency and $frequency->{unit}){
2188 my $expirationdate = GetExpirationDate($subscriptionid);
2190 my ($res) = $dbh->selectrow_array('select max(planneddate) from serial where subscriptionid = ?', undef, $subscriptionid);
2191 my $nextdate = GetNextDate($subscription, $res, $frequency);
2193 # only compare dates if both dates exist.
2194 if ($nextdate and $expirationdate) {
2195 if(Date::Calc::Delta_Days(
2196 split( /-/, $nextdate ),
2197 split( /-/, $expirationdate )
2203 } elsif ( $subscription->{numberlength} && $subscription->{numberlength}>0) {
2204 return (countissuesfrom($subscriptionid,$subscription->{'startdate'}) >=$subscription->{numberlength}-1);
2210 =head2 GetFictiveIssueNumber
2212 $issueno = GetFictiveIssueNumber($subscription, $publishedate, $frequency);
2214 Get the position of the issue published at $publisheddate, considering the
2215 first issue (at firstacquidate) is at position 1, the next is at position 2, etc...
2216 This issuenumber doesn't take into account irregularities, so, for instance, if the 3rd
2217 issue is declared as 'irregular' (will be skipped at receipt), the next issue number
2218 will be 4, not 3. It's why it is called 'fictive'. It is NOT a serial seq, and is not
2219 depending on how many rows are in serial table.
2220 The issue number calculation is based on subscription frequency, first acquisition
2221 date, and $publisheddate.
2223 Returns undef when called for irregular frequencies.
2225 The routine is used to skip irregularities when calculating the next issue
2226 date (in GetNextDate) or the next issue number (in GetNextSeq).
2230 sub GetFictiveIssueNumber {
2231 my ($subscription, $publisheddate, $frequency) = @_;
2233 my $unit = $frequency->{unit} ? lc $frequency->{'unit'} : undef;
2237 my ( $year, $month, $day ) = split /-/, $publisheddate;
2238 my ( $fa_year, $fa_month, $fa_day ) = split /-/, $subscription->{'firstacquidate'};
2239 my $delta = _delta_units( [$fa_year, $fa_month, $fa_day], [$year, $month, $day], $unit );
2241 if( $frequency->{'unitsperissue'} == 1 ) {
2242 $issueno = $delta * $frequency->{'issuesperunit'} + $subscription->{'countissuesperunit'};
2243 } else { # issuesperunit == 1
2244 $issueno = 1 + int( $delta / $frequency->{'unitsperissue'} );
2250 my ( $date1, $date2, $unit ) = @_;
2251 # date1 and date2 are array refs in the form [ yy, mm, dd ]
2253 if( $unit eq 'day' ) {
2254 return Delta_Days( @$date1, @$date2 );
2255 } elsif( $unit eq 'week' ) {
2256 return int( Delta_Days( @$date1, @$date2 ) / 7 );
2259 # In case of months or years, this is a wrapper around N_Delta_YMD.
2260 # Note that N_Delta_YMD returns 29 days between e.g. 22-2-72 and 22-3-72
2261 # while we expect 1 month.
2262 my @delta = N_Delta_YMD( @$date1, @$date2 );
2263 if( $delta[2] > 27 ) {
2264 # Check if we could add a month
2265 my @jump = Add_Delta_YM( @$date1, $delta[0], 1 + $delta[1] );
2266 if( Delta_Days( @jump, @$date2 ) >= 0 ) {
2270 if( $delta[1] >= 12 ) {
2274 # if unit is year, we only return full years
2275 return $unit eq 'month' ? $delta[0] * 12 + $delta[1] : $delta[0];
2278 sub _get_next_date_day {
2279 my ($subscription, $freqdata, $year, $month, $day) = @_;
2281 my @newissue; # ( yy, mm, dd )
2282 # We do not need $delta_days here, since it would be zero where used
2284 if( $freqdata->{issuesperunit} == 1 ) {
2286 @newissue = Add_Delta_Days(
2287 $year, $month, $day, $freqdata->{"unitsperissue"} );
2288 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2290 @newissue = ( $year, $month, $day );
2291 $subscription->{countissuesperunit}++;
2293 # We finished a cycle of issues within a unit.
2294 # No subtraction of zero needed, just add one day
2295 @newissue = Add_Delta_Days( $year, $month, $day, 1 );
2296 $subscription->{countissuesperunit} = 1;
2301 sub _get_next_date_week {
2302 my ($subscription, $freqdata, $year, $month, $day) = @_;
2304 my @newissue; # ( yy, mm, dd )
2305 my $delta_days = int( 7 / $freqdata->{issuesperunit} );
2307 if( $freqdata->{issuesperunit} == 1 ) {
2308 # Add full weeks (of 7 days)
2309 @newissue = Add_Delta_Days(
2310 $year, $month, $day, 7 * $freqdata->{"unitsperissue"} );
2311 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2312 # Add rounded number of days based on frequency.
2313 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2314 $subscription->{countissuesperunit}++;
2316 # We finished a cycle of issues within a unit.
2317 # Subtract delta * (issues - 1), add 1 week
2318 @newissue = Add_Delta_Days( $year, $month, $day,
2319 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2320 @newissue = Add_Delta_Days( @newissue, 7 );
2321 $subscription->{countissuesperunit} = 1;
2326 sub _get_next_date_month {
2327 my ($subscription, $freqdata, $year, $month, $day) = @_;
2329 my @newissue; # ( yy, mm, dd )
2330 my $delta_days = int( 30 / $freqdata->{issuesperunit} );
2332 if( $freqdata->{issuesperunit} == 1 ) {
2334 @newissue = Add_Delta_YM(
2335 $year, $month, $day, 0, $freqdata->{"unitsperissue"} );
2336 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2337 # Add rounded number of days based on frequency.
2338 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2339 $subscription->{countissuesperunit}++;
2341 # We finished a cycle of issues within a unit.
2342 # Subtract delta * (issues - 1), add 1 month
2343 @newissue = Add_Delta_Days( $year, $month, $day,
2344 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2345 @newissue = Add_Delta_YM( @newissue, 0, 1 );
2346 $subscription->{countissuesperunit} = 1;
2351 sub _get_next_date_year {
2352 my ($subscription, $freqdata, $year, $month, $day) = @_;
2354 my @newissue; # ( yy, mm, dd )
2355 my $delta_days = int( 365 / $freqdata->{issuesperunit} );
2357 if( $freqdata->{issuesperunit} == 1 ) {
2359 @newissue = Add_Delta_YM( $year, $month, $day, $freqdata->{"unitsperissue"}, 0 );
2360 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2361 # Add rounded number of days based on frequency.
2362 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2363 $subscription->{countissuesperunit}++;
2365 # We finished a cycle of issues within a unit.
2366 # Subtract delta * (issues - 1), add 1 year
2367 @newissue = Add_Delta_Days( $year, $month, $day, -$delta_days * ($freqdata->{issuesperunit} - 1) );
2368 @newissue = Add_Delta_YM( @newissue, 1, 0 );
2369 $subscription->{countissuesperunit} = 1;
2376 $resultdate = GetNextDate($publisheddate,$subscription,$freqdata,$updatecount)
2378 this function it takes the publisheddate and will return the next issue's date
2379 and will skip dates if there exists an irregularity.
2380 $publisheddate has to be an ISO date
2381 $subscription is a hashref containing at least 'firstacquidate', 'irregularity', and 'countissuesperunit'
2382 $frequency is a hashref containing frequency informations
2383 $updatecount is a boolean value which, when set to true, update the 'countissuesperunit' in database
2384 - eg if periodicity is monthly and $publisheddate is 2007-02-10 but if March and April is to be
2385 skipped then the returned date will be 2007-05-10
2388 $resultdate - then next date in the sequence (ISO date)
2390 Return undef if subscription is irregular
2395 my ( $subscription, $publisheddate, $freqdata, $updatecount ) = @_;
2397 return unless $subscription and $publisheddate;
2400 if ($freqdata->{'unit'}) {
2401 my ( $year, $month, $day ) = split /-/, $publisheddate;
2403 # Process an irregularity Hash
2404 # Suppose that irregularities are stored in a string with this structure
2405 # irreg1;irreg2;irreg3
2406 # where irregX is the number of issue which will not be received
2407 # (the first issue takes the number 1, the 2nd the number 2 and so on)
2409 if ( $subscription->{irregularity} ) {
2410 my @irreg = split /;/, $subscription->{'irregularity'} ;
2411 foreach my $irregularity (@irreg) {
2412 $irregularities{$irregularity} = 1;
2416 # Get the 'fictive' next issue number
2417 # It is used to check if next issue is an irregular issue.
2418 my $issueno = GetFictiveIssueNumber($subscription, $publisheddate, $freqdata) + 1;
2420 # Then get the next date
2421 my $unit = lc $freqdata->{'unit'};
2422 if ($unit eq 'day') {
2423 while ($irregularities{$issueno}) {
2424 ($year, $month, $day) = _get_next_date_day($subscription,
2425 $freqdata, $year, $month, $day);
2428 ($year, $month, $day) = _get_next_date_day($subscription, $freqdata,
2429 $year, $month, $day);
2431 elsif ($unit eq 'week') {
2432 while ($irregularities{$issueno}) {
2433 ($year, $month, $day) = _get_next_date_week($subscription,
2434 $freqdata, $year, $month, $day);
2437 ($year, $month, $day) = _get_next_date_week($subscription,
2438 $freqdata, $year, $month, $day);
2440 elsif ($unit eq 'month') {
2441 while ($irregularities{$issueno}) {
2442 ($year, $month, $day) = _get_next_date_month($subscription,
2443 $freqdata, $year, $month, $day);
2446 ($year, $month, $day) = _get_next_date_month($subscription,
2447 $freqdata, $year, $month, $day);
2449 elsif ($unit eq 'year') {
2450 while ($irregularities{$issueno}) {
2451 ($year, $month, $day) = _get_next_date_year($subscription,
2452 $freqdata, $year, $month, $day);
2455 ($year, $month, $day) = _get_next_date_year($subscription,
2456 $freqdata, $year, $month, $day);
2460 my $dbh = C4::Context->dbh;
2463 SET countissuesperunit = ?
2464 WHERE subscriptionid = ?
2466 my $sth = $dbh->prepare($query);
2467 $sth->execute($subscription->{'countissuesperunit'}, $subscription->{'subscriptionid'});
2470 return sprintf("%04d-%02d-%02d", $year, $month, $day);
2476 $string = &_numeration($value,$num_type,$locale);
2478 _numeration returns the string corresponding to $value in the num_type
2490 my ($value, $num_type, $locale) = @_;
2495 if ( $num_type =~ /^dayname$/ or $num_type =~ /^dayabrv$/ ) {
2496 # 1970-11-01 was a Sunday
2497 $value = $value % 7;
2498 my $dt = DateTime->new(
2504 $string = $num_type =~ /^dayname$/
2505 ? $dt->strftime("%A")
2506 : $dt->strftime("%a");
2507 } elsif ( $num_type =~ /^monthname$/ or $num_type =~ /^monthabrv$/ ) {
2508 $value = $value % 12;
2509 my $dt = DateTime->new(
2511 month => $value + 1,
2514 $string = $num_type =~ /^monthname$/
2515 ? $dt->format_cldr( "LLLL" )
2516 : $dt->strftime("%b");
2517 } elsif ( $num_type =~ /^season$/ ) {
2518 my @seasons= qw( Spring Summer Fall Winter );
2519 $value = $value % 4;
2520 $string = $seasons[$value];
2521 } elsif ( $num_type =~ /^seasonabrv$/ ) {
2522 my @seasonsabrv= qw( Spr Sum Fal Win );
2523 $value = $value % 4;
2524 $string = $seasonsabrv[$value];
2532 =head2 CloseSubscription
2534 Close a subscription given a subscriptionid
2538 sub CloseSubscription {
2539 my ( $subscriptionid ) = @_;
2540 return unless $subscriptionid;
2541 my $dbh = C4::Context->dbh;
2542 my $sth = $dbh->prepare( q{
2545 WHERE subscriptionid = ?
2547 $sth->execute( $subscriptionid );
2549 # Set status = missing when status = stopped
2550 $sth = $dbh->prepare( q{
2553 WHERE subscriptionid = ?
2556 $sth->execute( STOPPED, $subscriptionid, EXPECTED );
2559 =head2 ReopenSubscription
2561 Reopen a subscription given a subscriptionid
2565 sub ReopenSubscription {
2566 my ( $subscriptionid ) = @_;
2567 return unless $subscriptionid;
2568 my $dbh = C4::Context->dbh;
2569 my $sth = $dbh->prepare( q{
2572 WHERE subscriptionid = ?
2574 $sth->execute( $subscriptionid );
2576 # Set status = expected when status = stopped
2577 $sth = $dbh->prepare( q{
2580 WHERE subscriptionid = ?
2583 $sth->execute( EXPECTED, $subscriptionid, STOPPED );
2586 =head2 subscriptionCurrentlyOnOrder
2588 $bool = subscriptionCurrentlyOnOrder( $subscriptionid );
2590 Return 1 if subscription is currently on order else 0.
2594 sub subscriptionCurrentlyOnOrder {
2595 my ( $subscriptionid ) = @_;
2596 my $dbh = C4::Context->dbh;
2598 SELECT COUNT(*) FROM aqorders
2599 WHERE subscriptionid = ?
2600 AND datereceived IS NULL
2601 AND datecancellationprinted IS NULL
2603 my $sth = $dbh->prepare( $query );
2604 $sth->execute($subscriptionid);
2605 return $sth->fetchrow_array;
2608 =head2 can_claim_subscription
2610 $can = can_claim_subscription( $subscriptionid[, $userid] );
2612 Return 1 if the subscription can be claimed by the current logged user (or a given $userid), else 0.
2616 sub can_claim_subscription {
2617 my ( $subscription, $userid ) = @_;
2618 return _can_do_on_subscription( $subscription, $userid, 'claim_serials' );
2621 =head2 can_edit_subscription
2623 $can = can_edit_subscription( $subscriptionid[, $userid] );
2625 Return 1 if the subscription can be edited by the current logged user (or a given $userid), else 0.
2629 sub can_edit_subscription {
2630 my ( $subscription, $userid ) = @_;
2631 return _can_do_on_subscription( $subscription, $userid, 'edit_subscription' );
2634 =head2 can_show_subscription
2636 $can = can_show_subscription( $subscriptionid[, $userid] );
2638 Return 1 if the subscription can be shown by the current logged user (or a given $userid), else 0.
2642 sub can_show_subscription {
2643 my ( $subscription, $userid ) = @_;
2644 return _can_do_on_subscription( $subscription, $userid, '*' );
2647 sub _can_do_on_subscription {
2648 my ( $subscription, $userid, $permission ) = @_;
2649 return 0 unless C4::Context->userenv;
2650 my $flags = C4::Context->userenv->{flags};
2651 $userid ||= C4::Context->userenv->{'id'};
2653 if ( C4::Context->preference('IndependentBranches') ) {
2655 if C4::Context->IsSuperLibrarian()
2657 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2659 C4::Auth::haspermission( $userid,
2660 { serials => $permission } )
2661 and ( not defined $subscription->{branchcode}
2662 or $subscription->{branchcode} eq ''
2663 or $subscription->{branchcode} eq
2664 C4::Context->userenv->{'branch'} )
2669 if C4::Context->IsSuperLibrarian()
2671 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2672 or C4::Auth::haspermission(
2673 $userid, { serials => $permission }
2680 =head2 findSerialsByStatus
2682 @serials = findSerialsByStatus($status, $subscriptionid);
2684 Returns an array of serials matching a given status and subscription id.
2688 sub findSerialsByStatus {
2689 my ( $status, $subscriptionid ) = @_;
2690 my $dbh = C4::Context->dbh;
2691 my $query = q| SELECT * from serial
2693 AND subscriptionid = ?
2695 my $serials = $dbh->selectall_arrayref( $query, { Slice => {} }, $status, $subscriptionid );
2704 Koha Development Team <http://koha-community.org/>