3 # Copyright 2000-2002 Katipo Communications
4 # Parts Copyright 2010 Biblibre
6 # This file is part of Koha.
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
33 use POSIX qw( strftime );
34 use Scalar::Util qw( looks_like_number );
37 use C4::Auth qw( haspermission );
38 use C4::Biblio qw( GetMarcFromKohaField ModBiblio );
40 use C4::Log qw( logaction ); # logaction
41 use C4::Serials::Frequency qw( GetSubscriptionFrequency );
42 use C4::Serials::Numberpattern;
43 use Koha::AdditionalFieldValues;
46 use Koha::SharedContent;
47 use Koha::Subscription::Histories;
48 use Koha::Subscriptions;
49 use Koha::Suggestions;
57 MISSING_NEVER_RECIEVED => 41,
58 MISSING_SOLD_OUT => 42,
59 MISSING_DAMAGED => 43,
67 use constant MISSING_STATUSES => (
68 MISSING, MISSING_NEVER_RECIEVED,
69 MISSING_SOLD_OUT, MISSING_DAMAGED,
73 our (@ISA, @EXPORT_OK);
78 NewSubscription ModSubscription DelSubscription
79 GetSubscription CountSubscriptionFromBiblionumber GetSubscriptionsFromBiblionumber
81 GetFullSubscriptionsFromBiblionumber GetFullSubscription ModSubscriptionHistory
82 HasSubscriptionStrictlyExpired HasSubscriptionExpired GetExpirationDate abouttoexpire
84 GetSubscriptionHistoryFromSubscriptionId
86 GetNextSeq GetSeq NewIssue GetSerials
87 GetLatestSerials ModSerialStatus GetNextDate
88 CloseSubscription ReopenSubscription
89 subscriptionCurrentlyOnOrder
90 can_claim_subscription can_edit_subscription can_show_subscription
92 GetSubscriptionLength ReNewSubscription GetLateOrMissingIssues
93 GetSerialInformation AddItem2Serial
94 PrepareSerialsData GetNextExpected ModNextExpected
95 GetSubscriptionIrregularities
98 GetSuppliersWithLateIssues
99 getroutinglist delroutingmember addroutingmember
101 check_routing updateClaim
112 C4::Serials - Serials Module Functions
120 Functions for handling subscriptions, claims routing etc.
125 =head2 GetSuppliersWithLateIssues
127 $supplierlist = GetSuppliersWithLateIssues()
129 this function get all suppliers with late issues.
132 an array_ref of suppliers each entry is a hash_ref containing id and name
133 the array is in name order
137 sub GetSuppliersWithLateIssues {
138 my $dbh = C4::Context->dbh;
139 my $statuses = join(',', ( LATE, MISSING_STATUSES, CLAIMED ) );
141 SELECT DISTINCT id, name
143 LEFT JOIN serial ON serial.subscriptionid=subscription.subscriptionid
144 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
147 (planneddate < now() AND serial.status=1)
148 OR serial.STATUS IN ( $statuses )
150 AND subscription.closed = 0
152 return $dbh->selectall_arrayref($query, { Slice => {} });
155 =head2 GetSubscriptionHistoryFromSubscriptionId
157 $history = GetSubscriptionHistoryFromSubscriptionId($subscriptionid);
159 This function returns the subscription history as a hashref
163 sub GetSubscriptionHistoryFromSubscriptionId {
164 my ($subscriptionid) = @_;
166 return unless $subscriptionid;
168 my $dbh = C4::Context->dbh;
171 FROM subscriptionhistory
172 WHERE subscriptionid = ?
174 my $sth = $dbh->prepare($query);
175 $sth->execute($subscriptionid);
176 my $results = $sth->fetchrow_hashref;
182 =head2 GetSerialInformation
184 $data = GetSerialInformation($serialid);
185 returns a hash_ref containing :
186 items : items marcrecord (can be an array)
188 subscription table field
189 + information about subscription expiration
193 sub GetSerialInformation {
195 my $dbh = C4::Context->dbh;
197 SELECT serial.*, serial.notes as sernotes, serial.status as serstatus,subscription.*,subscription.subscriptionid as subsid
198 FROM serial LEFT JOIN subscription ON subscription.subscriptionid=serial.subscriptionid
201 my $rq = $dbh->prepare($query);
202 $rq->execute($serialid);
203 my $data = $rq->fetchrow_hashref;
205 # create item information if we have serialsadditems for this subscription
206 if ( $data->{'serialsadditems'} ) {
207 my $queryitem = $dbh->prepare("SELECT itemnumber from serialitems where serialid=?");
208 $queryitem->execute($serialid);
209 my $itemnumbers = $queryitem->fetchall_arrayref( [0] );
211 if ( scalar(@$itemnumbers) > 0 ) {
212 foreach my $itemnum (@$itemnumbers) {
214 #It is ASSUMED that GetMarcItem ALWAYS WORK...
215 #Maybe GetMarcItem should return values on failure
216 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, $itemnum->[0], $data );
217 $itemprocessed->{'itemnumber'} = $itemnum->[0];
218 $itemprocessed->{'itemid'} = $itemnum->[0];
219 $itemprocessed->{'serialid'} = $serialid;
220 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
221 push @{ $data->{'items'} }, $itemprocessed;
224 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, '', $data );
225 $itemprocessed->{'itemid'} = "N$serialid";
226 $itemprocessed->{'serialid'} = $serialid;
227 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
228 $itemprocessed->{'countitems'} = 0;
229 push @{ $data->{'items'} }, $itemprocessed;
232 $data->{ "status" . $data->{'serstatus'} } = 1;
233 $data->{'subscriptionexpired'} = HasSubscriptionExpired( $data->{'subscriptionid'} ) && $data->{'status'} == 1;
234 $data->{'abouttoexpire'} = abouttoexpire( $data->{'subscriptionid'} );
235 $data->{cannotedit} = not can_edit_subscription( $data );
239 =head2 AddItem2Serial
241 $rows = AddItem2Serial($serialid,$itemnumber);
242 Adds an itemnumber to Serial record
243 returns the number of rows affected
248 my ( $serialid, $itemnumber ) = @_;
250 return unless ($serialid and $itemnumber);
252 my $dbh = C4::Context->dbh;
253 my $rq = $dbh->prepare("INSERT INTO `serialitems` SET serialid=? , itemnumber=?");
254 $rq->execute( $serialid, $itemnumber );
258 =head2 GetSubscription
260 $subs = GetSubscription($subscriptionid)
261 this function returns the subscription which has $subscriptionid as id.
263 a hashref. This hash contains
264 subscription, subscriptionhistory, aqbooksellers.name, biblio.title
268 sub GetSubscription {
269 my ($subscriptionid) = @_;
270 my $dbh = C4::Context->dbh;
272 SELECT subscription.*,
273 subscriptionhistory.*,
274 aqbooksellers.name AS aqbooksellername,
275 biblio.title AS bibliotitle,
276 biblio.subtitle AS bibliosubtitle,
277 subscription.biblionumber as bibnum
279 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
280 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
281 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
282 WHERE subscription.subscriptionid = ?
285 my $sth = $dbh->prepare($query);
286 $sth->execute($subscriptionid);
287 my $subscription = $sth->fetchrow_hashref;
289 return unless $subscription;
291 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
293 if ( my $mana_id = $subscription->{mana_id} ) {
294 my $mana_subscription = Koha::SharedContent::get_entity_by_id(
295 'subscription', $mana_id, {usecomments => 1});
296 $subscription->{comments} = $mana_subscription->{data}->{comments};
299 return $subscription;
302 =head2 GetFullSubscription
304 $array_ref = GetFullSubscription($subscriptionid)
305 this function reads the serial table.
309 sub GetFullSubscription {
310 my ($subscriptionid) = @_;
312 return unless ($subscriptionid);
314 my $dbh = C4::Context->dbh;
316 SELECT serial.serialid,
319 serial.publisheddate,
320 serial.publisheddatetext,
322 serial.notes as notes,
323 year(IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate)) as year,
324 aqbooksellers.name as aqbooksellername,
325 biblio.title as bibliotitle,
326 subscription.branchcode AS branchcode,
327 subscription.subscriptionid AS subscriptionid
329 LEFT JOIN subscription ON
330 (serial.subscriptionid=subscription.subscriptionid )
331 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
332 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
333 WHERE serial.subscriptionid = ?
335 IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate) DESC,
336 serial.subscriptionid
338 my $sth = $dbh->prepare($query);
339 $sth->execute($subscriptionid);
340 my $subscriptions = $sth->fetchall_arrayref( {} );
341 if (scalar @$subscriptions) {
342 my $cannotedit = not can_edit_subscription( $subscriptions->[0] );
343 for my $subscription ( @$subscriptions ) {
344 $subscription->{cannotedit} = $cannotedit;
348 return $subscriptions;
351 =head2 PrepareSerialsData
353 $array_ref = PrepareSerialsData($serialinfomation)
354 where serialinformation is a hashref array
358 sub PrepareSerialsData {
361 return unless ($lines);
368 my $previousnote = "";
370 foreach my $subs (@{$lines}) {
371 $subs->{ "status" . $subs->{'status'} } = 1;
372 if ( grep { $_ == $subs->{status} } ( EXPECTED, LATE, MISSING_STATUSES, CLAIMED ) ) {
373 $subs->{"checked"} = 1;
376 if ( $subs->{'year'} && $subs->{'year'} ne "" ) {
377 $year = $subs->{'year'};
381 if ( $tmpresults{$year} ) {
382 push @{ $tmpresults{$year}->{'serials'} }, $subs;
384 $tmpresults{$year} = {
386 'aqbooksellername' => $subs->{'aqbooksellername'},
387 'bibliotitle' => $subs->{'bibliotitle'},
388 'serials' => [$subs],
393 foreach my $key ( sort { $b cmp $a } keys %tmpresults ) {
394 push @res, $tmpresults{$key};
399 =head2 GetSubscriptionsFromBiblionumber
401 $array_ref = GetSubscriptionsFromBiblionumber($biblionumber)
402 this function get the subscription list. it reads the subscription table.
404 reference to an array of subscriptions which have the biblionumber given on input arg.
405 each element of this array is a hashref containing
406 startdate, histstartdate,opacnote,missinglist,recievedlist,periodicity,status & enddate
410 sub GetSubscriptionsFromBiblionumber {
411 my ($biblionumber) = @_;
413 return unless ($biblionumber);
415 my $dbh = C4::Context->dbh;
417 SELECT subscription.*,
419 subscriptionhistory.*,
420 aqbooksellers.name AS aqbooksellername,
421 biblio.title AS bibliotitle
423 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
424 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
425 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
426 LEFT JOIN branches ON branches.branchcode=subscription.branchcode
427 WHERE subscription.biblionumber = ?
429 my $sth = $dbh->prepare($query);
430 $sth->execute($biblionumber);
432 while ( my $subs = $sth->fetchrow_hashref ) {
433 $subs->{opacnote} //= "";
434 $subs->{ "periodicity" . $subs->{periodicity} } = 1;
435 $subs->{ "numberpattern" . $subs->{numberpattern} } = 1;
436 $subs->{ "status" . $subs->{'status'} } = 1;
438 $subs->{'abouttoexpire'} = abouttoexpire( $subs->{'subscriptionid'} );
439 $subs->{'subscriptionexpired'} = HasSubscriptionExpired( $subs->{'subscriptionid'} );
440 $subs->{cannotedit} = not can_edit_subscription( $subs );
446 =head2 GetFullSubscriptionsFromBiblionumber
448 $array_ref = GetFullSubscriptionsFromBiblionumber($biblionumber)
449 this function reads the serial table.
453 sub GetFullSubscriptionsFromBiblionumber {
454 my ($biblionumber) = @_;
455 my $dbh = C4::Context->dbh;
457 SELECT serial.serialid,
460 serial.publisheddate,
461 serial.publisheddatetext,
463 serial.notes as notes,
464 year(IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate)) as year,
465 biblio.title as bibliotitle,
466 subscription.branchcode AS branchcode,
467 subscription.subscriptionid AS subscriptionid,
468 subscription.location AS location
470 LEFT JOIN subscription ON
471 (serial.subscriptionid=subscription.subscriptionid)
472 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
473 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
474 WHERE subscription.biblionumber = ?
476 IF(serial.publisheddate IS NULL,serial.planneddate,serial.publisheddate) DESC,
477 serial.subscriptionid
479 my $sth = $dbh->prepare($query);
480 $sth->execute($biblionumber);
481 my $subscriptions = $sth->fetchall_arrayref( {} );
482 if (scalar @$subscriptions) {
483 my $cannotedit = not can_edit_subscription( $subscriptions->[0] );
484 for my $subscription ( @$subscriptions ) {
485 $subscription->{cannotedit} = $cannotedit;
489 return $subscriptions;
492 =head2 SearchSubscriptions
494 @results = SearchSubscriptions($args);
496 This function returns a list of hashrefs, one for each subscription
497 that meets the conditions specified by the $args hashref.
499 The valid search fields are:
513 The expiration_date search field is special; it specifies the maximum
514 subscription expiration date.
518 sub SearchSubscriptions {
521 my $additional_fields = $args->{additional_fields} // [];
522 my $matching_record_ids_for_additional_fields = [];
523 if ( @$additional_fields ) {
524 my @subscriptions = Koha::Subscriptions->filter_by_additional_fields($additional_fields)->as_list;
526 return () unless @subscriptions;
528 $matching_record_ids_for_additional_fields = [ map {
535 subscription.notes AS publicnotes,
536 subscriptionhistory.*,
538 biblio.notes AS biblionotes,
543 aqbooksellers.name AS vendorname,
546 LEFT JOIN subscriptionhistory USING(subscriptionid)
547 LEFT JOIN biblio ON biblio.biblionumber = subscription.biblionumber
548 LEFT JOIN biblioitems ON biblioitems.biblionumber = subscription.biblionumber
549 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
551 $query .= q| WHERE 1|;
554 if( $args->{biblionumber} ) {
555 push @where_strs, "biblio.biblionumber = ?";
556 push @where_args, $args->{biblionumber};
559 if( $args->{title} ){
560 my @words = split / /, $args->{title};
562 foreach my $word (@words) {
563 push @strs, "biblio.title LIKE ?";
564 push @args, "%$word%";
567 push @where_strs, '(' . join (' AND ', @strs) . ')';
568 push @where_args, @args;
572 push @where_strs, "biblioitems.issn LIKE ?";
573 push @where_args, "%$args->{issn}%";
576 push @where_strs, "biblioitems.ean LIKE ?";
577 push @where_args, "%$args->{ean}%";
579 if ( $args->{callnumber} ) {
580 push @where_strs, "subscription.callnumber LIKE ?";
581 push @where_args, "%$args->{callnumber}%";
583 if( $args->{publisher} ){
584 push @where_strs, "biblioitems.publishercode LIKE ?";
585 push @where_args, "%$args->{publisher}%";
587 if( $args->{bookseller} ){
588 push @where_strs, "aqbooksellers.name LIKE ?";
589 push @where_args, "%$args->{bookseller}%";
591 if( $args->{branch} ){
592 push @where_strs, "subscription.branchcode = ?";
593 push @where_args, "$args->{branch}";
595 if ( $args->{location} ) {
596 push @where_strs, "subscription.location = ?";
597 push @where_args, "$args->{location}";
599 if ( $args->{expiration_date} ) {
600 push @where_strs, "subscription.enddate <= ?";
601 push @where_args, "$args->{expiration_date}";
603 if( defined $args->{closed} ){
604 push @where_strs, "subscription.closed = ?";
605 push @where_args, "$args->{closed}";
609 $query .= ' AND ' . join(' AND ', @where_strs);
611 if ( @$additional_fields ) {
612 $query .= ' AND subscriptionid IN ('
613 . join( ', ', @$matching_record_ids_for_additional_fields )
617 $query .= " ORDER BY " . $args->{orderby} if $args->{orderby};
619 my $dbh = C4::Context->dbh;
620 my $sth = $dbh->prepare($query);
621 $sth->execute(@where_args);
622 my $results = $sth->fetchall_arrayref( {} );
624 for my $subscription ( @$results ) {
625 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
626 $subscription->{cannotdisplay} = not can_show_subscription( $subscription );
628 my $subscription_object = Koha::Subscriptions->find($subscription->{subscriptionid});
629 $subscription->{additional_fields} = { map { $_->field->name => $_->value }
630 $subscription_object->additional_field_values->as_list };
640 ($totalissues,@serials) = GetSerials($subscriptionid);
641 this function gets every serial not arrived for a given subscription
642 as well as the number of issues registered in the database (all types)
643 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
645 FIXME: We should return \@serials.
650 my ( $subscriptionid, $count ) = @_;
652 return unless $subscriptionid;
654 my $dbh = C4::Context->dbh;
656 # status = 2 is "arrived"
658 $count = 5 unless ($count);
660 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES, NOT_ISSUED ) );
661 my $query = "SELECT serialid,serialseq, status, publisheddate,
662 publisheddatetext, planneddate,notes, routingnotes
664 WHERE subscriptionid = ? AND status NOT IN ( $statuses )
665 ORDER BY IF(publisheddate IS NULL,planneddate,publisheddate) DESC";
666 my $sth = $dbh->prepare($query);
667 $sth->execute($subscriptionid);
669 while ( my $line = $sth->fetchrow_hashref ) {
670 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
671 push @serials, $line;
674 # OK, now add the last 5 issues arrives/missing
675 $query = "SELECT serialid,serialseq, status, planneddate, publisheddate,
676 publisheddatetext, notes, routingnotes
678 WHERE subscriptionid = ?
679 AND status IN ( $statuses )
680 ORDER BY IF(publisheddate IS NULL,planneddate,publisheddate) DESC
682 $sth = $dbh->prepare($query);
683 $sth->execute($subscriptionid);
684 while ( ( my $line = $sth->fetchrow_hashref ) && $counter < $count ) {
686 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
688 push @serials, $line;
691 $query = "SELECT count(*) FROM serial WHERE subscriptionid=?";
692 $sth = $dbh->prepare($query);
693 $sth->execute($subscriptionid);
694 my ($totalissues) = $sth->fetchrow;
695 return ( $totalissues, @serials );
700 @serials = GetSerials2($subscriptionid,$statuses);
701 this function returns every serial waited for a given subscription
702 as well as the number of issues registered in the database (all types)
703 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
705 $statuses is an arrayref of statuses and is mandatory.
710 my ( $subscription, $statuses ) = @_;
712 return unless ($subscription and @$statuses);
714 my $dbh = C4::Context->dbh;
716 SELECT serialid,serialseq, status, planneddate, publisheddate,
717 publisheddatetext, notes, routingnotes
719 WHERE subscriptionid=?
721 . q| AND status IN (| . join( ",", ('?') x @$statuses ) . q|)|
723 ORDER BY publisheddate,serialid DESC
725 my $sth = $dbh->prepare($query);
726 $sth->execute( $subscription, @$statuses );
729 while ( my $line = $sth->fetchrow_hashref ) {
730 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
731 push @serials, $line;
736 =head2 GetLatestSerials
738 \@serials = GetLatestSerials($subscriptionid,$limit)
739 get the $limit's latest serials arrived or missing for a given subscription
741 a ref to an array which contains all of the latest serials stored into a hash.
745 sub GetLatestSerials {
746 my ( $subscriptionid, $limit ) = @_;
748 return unless ($subscriptionid and $limit);
750 my $dbh = C4::Context->dbh;
752 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES ) );
753 my $strsth = "SELECT serialid,serialseq, status, planneddate, publisheddate, publisheddatetext, notes
755 WHERE subscriptionid = ?
756 AND status IN ($statuses)
757 ORDER BY publisheddate DESC LIMIT 0,$limit
759 my $sth = $dbh->prepare($strsth);
760 $sth->execute($subscriptionid);
762 while ( my $line = $sth->fetchrow_hashref ) {
763 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
764 push @serials, $line;
770 =head2 GetPreviousSerialid
772 $serialid = GetPreviousSerialid($subscriptionid, $nth)
773 get the $nth's previous serial for the given subscriptionid
779 sub GetPreviousSerialid {
780 my ( $subscriptionid, $nth ) = @_;
782 my $dbh = C4::Context->dbh;
786 my $strsth = "SELECT serialid
788 WHERE subscriptionid = ?
790 ORDER BY serialid DESC LIMIT $nth,1
792 my $sth = $dbh->prepare($strsth);
793 $sth->execute($subscriptionid);
795 my $line = $sth->fetchrow_hashref;
796 $return = $line->{'serialid'} if ($line);
804 $nextseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
805 $newinnerloop1, $newinnerloop2, $newinnerloop3
806 ) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate );
808 $subscription is a hashref containing all the attributes of the table
810 $pattern is a hashref containing all the attributes of the table
811 'subscription_numberpatterns'.
812 $frequency is a hashref containing all the attributes of the table 'subscription_frequencies'
813 $planneddate is a date string in iso format.
814 This function get the next issue for the subscription given on input arg
819 my ($subscription, $pattern, $frequency, $planneddate) = @_;
821 return unless ($subscription and $pattern);
823 my ( $newlastvalue1, $newlastvalue2, $newlastvalue3,
824 $newinnerloop1, $newinnerloop2, $newinnerloop3 );
827 if ($subscription->{'skip_serialseq'}) {
828 my @irreg = split /;/, $subscription->{'irregularity'};
830 my $irregularities = {};
831 $irregularities->{$_} = 1 foreach(@irreg);
832 my $issueno = GetFictiveIssueNumber($subscription, $planneddate, $frequency) + 1;
833 while($irregularities->{$issueno}) {
840 my $numberingmethod = $pattern->{numberingmethod};
842 if ($numberingmethod) {
843 $calculated = $numberingmethod;
844 my $locale = $subscription->{locale};
845 $newlastvalue1 = $subscription->{lastvalue1} || 0;
846 $newlastvalue2 = $subscription->{lastvalue2} || 0;
847 $newlastvalue3 = $subscription->{lastvalue3} || 0;
848 $newinnerloop1 = $subscription->{innerloop1} || 0;
849 $newinnerloop2 = $subscription->{innerloop2} || 0;
850 $newinnerloop3 = $subscription->{innerloop3} || 0;
853 $calc{$_} = 1 if ($numberingmethod =~ /\{$_\}/);
856 for(my $i = 0; $i < $count; $i++) {
858 # check if we have to increase the new value.
860 if ($newinnerloop1 >= $pattern->{every1}) {
862 $newlastvalue1 += $pattern->{add1};
864 # reset counter if needed.
865 $newlastvalue1 = $pattern->{setto1} if ($newlastvalue1 > $pattern->{whenmorethan1});
868 # check if we have to increase the new value.
870 if ($newinnerloop2 >= $pattern->{every2}) {
872 $newlastvalue2 += $pattern->{add2};
874 # reset counter if needed.
875 $newlastvalue2 = $pattern->{setto2} if ($newlastvalue2 > $pattern->{whenmorethan2});
878 # check if we have to increase the new value.
880 if ($newinnerloop3 >= $pattern->{every3}) {
882 $newlastvalue3 += $pattern->{add3};
884 # reset counter if needed.
885 $newlastvalue3 = $pattern->{setto3} if ($newlastvalue3 > $pattern->{whenmorethan3});
889 my $newlastvalue1string = _numeration( $newlastvalue1, $pattern->{numbering1}, $locale );
890 $calculated =~ s/\{X\}/$newlastvalue1string/g;
893 my $newlastvalue2string = _numeration( $newlastvalue2, $pattern->{numbering2}, $locale );
894 $calculated =~ s/\{Y\}/$newlastvalue2string/g;
897 my $newlastvalue3string = _numeration( $newlastvalue3, $pattern->{numbering3}, $locale );
898 $calculated =~ s/\{Z\}/$newlastvalue3string/g;
904 $newlastvalue1, $newlastvalue2, $newlastvalue3,
905 $newinnerloop1, $newinnerloop2, $newinnerloop3);
910 $calculated = GetSeq($subscription, $pattern)
911 $subscription is a hashref containing all the attributes of the table 'subscription'
912 $pattern is a hashref containing all the attributes of the table 'subscription_numberpatterns'
913 this function transforms {X},{Y},{Z} to 150,0,0 for example.
915 the sequence in string format
920 my ($subscription, $pattern) = @_;
922 return unless ($subscription and $pattern);
924 my $locale = $subscription->{locale};
926 my $calculated = $pattern->{numberingmethod};
928 my $newlastvalue1 = $subscription->{'lastvalue1'} || 0;
929 $newlastvalue1 = _numeration($newlastvalue1, $pattern->{numbering1}, $locale) if ($pattern->{numbering1}); # reset counter if needed.
930 $calculated =~ s/\{X\}/$newlastvalue1/g;
932 my $newlastvalue2 = $subscription->{'lastvalue2'} || 0;
933 $newlastvalue2 = _numeration($newlastvalue2, $pattern->{numbering2}, $locale) if ($pattern->{numbering2}); # reset counter if needed.
934 $calculated =~ s/\{Y\}/$newlastvalue2/g;
936 my $newlastvalue3 = $subscription->{'lastvalue3'} || 0;
937 $newlastvalue3 = _numeration($newlastvalue3, $pattern->{numbering3}, $locale) if ($pattern->{numbering3}); # reset counter if needed.
938 $calculated =~ s/\{Z\}/$newlastvalue3/g;
942 =head2 GetExpirationDate
944 $enddate = GetExpirationDate($subscriptionid, [$startdate])
946 this function return the next expiration date for a subscription given on input args.
953 sub GetExpirationDate {
954 my ( $subscriptionid, $startdate ) = @_;
956 return unless ($subscriptionid);
958 my $dbh = C4::Context->dbh;
959 my $subscription = GetSubscription($subscriptionid);
962 # we don't do the same test if the subscription is based on X numbers or on X weeks/months
963 $enddate = $startdate || $subscription->{startdate};
964 my @date = split( /-/, $enddate );
966 return if ( scalar(@date) != 3 || not check_date(@date) );
968 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
969 if ( $frequency and $frequency->{unit} ) {
972 if ( my $length = $subscription->{numberlength} ) {
974 #calculate the date of the last issue.
975 for ( my $i = 1 ; $i <= $length ; $i++ ) {
976 $enddate = GetNextDate( $subscription, $enddate, $frequency );
978 } elsif ( $subscription->{monthlength} ) {
979 if ( $$subscription{startdate} ) {
980 my @enddate = Add_Delta_YM( $date[0], $date[1], $date[2], 0, $subscription->{monthlength} );
981 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
983 } elsif ( $subscription->{weeklength} ) {
984 if ( $$subscription{startdate} ) {
985 my @date = split( /-/, $subscription->{startdate} );
986 my @enddate = Add_Delta_Days( $date[0], $date[1], $date[2], $subscription->{weeklength} * 7 );
987 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
990 $enddate = $subscription->{enddate};
994 return $subscription->{enddate};
998 =head2 CountSubscriptionFromBiblionumber
1000 $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber)
1001 this returns a count of the subscriptions for a given biblionumber
1003 the number of subscriptions
1007 sub CountSubscriptionFromBiblionumber {
1008 my ($biblionumber) = @_;
1010 return unless ($biblionumber);
1012 my $dbh = C4::Context->dbh;
1013 my $query = "SELECT count(*) FROM subscription WHERE biblionumber=?";
1014 my $sth = $dbh->prepare($query);
1015 $sth->execute($biblionumber);
1016 my $subscriptionsnumber = $sth->fetchrow;
1017 return $subscriptionsnumber;
1020 =head2 ModSubscriptionHistory
1022 ModSubscriptionHistory($subscriptionid,$histstartdate,$enddate,$recievedlist,$missinglist,$opacnote,$librariannote);
1024 this function modifies the history of a subscription. Put your new values on input arg.
1025 returns the number of rows affected
1029 sub ModSubscriptionHistory {
1030 my ( $subscriptionid, $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote ) = @_;
1032 return unless ($subscriptionid);
1034 my $dbh = C4::Context->dbh;
1035 my $query = "UPDATE subscriptionhistory
1036 SET histstartdate=?,histenddate=?,recievedlist=?,missinglist=?,opacnote=?,librariannote=?
1037 WHERE subscriptionid=?
1039 my $sth = $dbh->prepare($query);
1040 $receivedlist =~ s/^; // if $receivedlist;
1041 $missinglist =~ s/^; // if $missinglist;
1042 $opacnote =~ s/^; // if $opacnote;
1043 $sth->execute( $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote, $subscriptionid );
1047 =head2 ModSerialStatus
1049 ModSerialStatus($serialid, $serialseq, $planneddate, $publisheddate,
1050 $publisheddatetext, $status, $notes);
1052 This function modify the serial status. Serial status is a number.(eg 2 is "arrived")
1053 Note : if we change from "waited" to something else,then we will have to create a new "waited" entry
1057 sub ModSerialStatus {
1058 my ($serialid, $serialseq, $planneddate, $publisheddate, $publisheddatetext,
1059 $status, $notes) = @_;
1061 return unless ($serialid);
1063 #It is a usual serial
1064 # 1st, get previous status :
1065 my $dbh = C4::Context->dbh;
1066 my $query = "SELECT serial.subscriptionid,serial.status,subscription.periodicity,serial.routingnotes
1067 FROM serial, subscription
1068 WHERE serial.subscriptionid=subscription.subscriptionid
1070 my $sth = $dbh->prepare($query);
1071 $sth->execute($serialid);
1072 my ( $subscriptionid, $oldstatus, $periodicity, $routingnotes ) = $sth->fetchrow;
1073 my $frequency = GetSubscriptionFrequency($periodicity);
1075 # change status & update subscriptionhistory
1077 if ( $status == DELETED ) {
1078 DelIssue( { 'serialid' => $serialid, 'subscriptionid' => $subscriptionid, 'serialseq' => $serialseq } );
1082 SET serialseq = ?, publisheddate = ?, publisheddatetext = ?,
1083 planneddate = ?, status = ?, notes = ?, routingnotes = ?
1086 $sth = $dbh->prepare($query);
1087 $sth->execute( $serialseq, $publisheddate, $publisheddatetext,
1088 $planneddate, $status, $notes, $routingnotes, $serialid );
1089 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1090 $sth = $dbh->prepare($query);
1091 $sth->execute($subscriptionid);
1092 my $val = $sth->fetchrow_hashref;
1093 unless ( $val->{manualhistory} ) {
1094 $query = "SELECT missinglist,recievedlist FROM subscriptionhistory WHERE subscriptionid=?";
1095 $sth = $dbh->prepare($query);
1096 $sth->execute($subscriptionid);
1097 my ( $missinglist, $recievedlist ) = $sth->fetchrow;
1099 if ( $status == ARRIVED || ($oldstatus == ARRIVED && $status != ARRIVED) ) {
1100 $recievedlist = _handle_seqno($serialseq, $recievedlist);
1103 # in case serial has been previously marked as missing
1104 if (grep /$status/, (EXPECTED, ARRIVED, LATE, CLAIMED)) {
1105 $missinglist = _handle_seqno($serialseq, $missinglist, 'REMOVE');
1108 $missinglist = _handle_seqno($serialseq, $missinglist) if grep { $_ == $status } MISSING_STATUSES;
1109 $missinglist .= "; not issued $serialseq" if $status == NOT_ISSUED and not _handle_seqno($serialseq, $missinglist, 'CHECK');
1111 $query = "UPDATE subscriptionhistory SET recievedlist=?, missinglist=? WHERE subscriptionid=?";
1112 $sth = $dbh->prepare($query);
1113 $recievedlist =~ s/^; //;
1114 $missinglist =~ s/^; //;
1115 $sth->execute( $recievedlist, $missinglist, $subscriptionid );
1119 # create new expected entry if needed (ie : was "expected" and has changed)
1120 my $otherIssueExpected = scalar findSerialsByStatus(EXPECTED, $subscriptionid);
1121 if ( !$otherIssueExpected && $oldstatus == EXPECTED && $status != EXPECTED ) {
1122 my $subscription = GetSubscription($subscriptionid);
1123 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1124 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1128 $newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
1129 $newinnerloop1, $newinnerloop2, $newinnerloop3
1131 = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate );
1133 # next date (calculated from actual date & frequency parameters)
1134 my $nextpublisheddate = GetNextDate($subscription, $publisheddate, $frequency, 1);
1135 my $nextpubdate = $nextpublisheddate;
1136 $query = "UPDATE subscription SET lastvalue1=?, lastvalue2=?, lastvalue3=?, innerloop1=?, innerloop2=?, innerloop3=?
1137 WHERE subscriptionid = ?";
1138 $sth = $dbh->prepare($query);
1139 $sth->execute( $newlastvalue1, $newlastvalue2, $newlastvalue3, $newinnerloop1, $newinnerloop2, $newinnerloop3, $subscriptionid );
1140 my $newnote = C4::Context->preference('PreserveSerialNotes') ? $notes : "";
1141 NewIssue( $newserialseq, $subscriptionid, $subscription->{'biblionumber'}, 1, $nextpubdate, $nextpubdate, undef, $newnote, $routingnotes );
1142 # check if an alert must be sent... (= a letter is defined & status became "arrived"
1143 if ( $subscription->{letter} && $status == ARRIVED && $oldstatus != ARRIVED ) {
1144 require C4::Letters;
1145 C4::Letters::SendAlerts( 'issue', $serialid, $subscription->{letter} );
1153 # Adds or removes seqno from list when needed; returns list
1154 # Or checks and returns true when present
1156 my ( $seq, $list, $op ) = @_; # op = ADD | REMOVE | CHECK (default: ADD)
1158 $seq_r =~ s/([()])/\\$1/g; # Adjust disturbing parentheses for regex, maybe extend in future
1160 if( !$op or $op eq 'ADD' ) {
1161 $list .= "; $seq" if $list !~ /(^|;)\s*$seq_r(?=;|$)/;
1162 } elsif( $op eq 'REMOVE' ) {
1163 $list=~ s/(^|;)\s*(not issued )?$seq_r(?=;|$)//g;
1165 return $list =~ /(^|;)\s*$seq_r(?=;|$)/ ? 1 : q{};
1170 =head2 GetNextExpected
1172 $nextexpected = GetNextExpected($subscriptionid)
1174 Get the planneddate for the current expected issue of the subscription.
1180 planneddate => ISO date
1185 sub GetNextExpected {
1186 my ($subscriptionid) = @_;
1188 my $dbh = C4::Context->dbh;
1192 WHERE subscriptionid = ?
1196 my $sth = $dbh->prepare($query);
1198 # Each subscription has only one 'expected' issue.
1199 $sth->execute( $subscriptionid, EXPECTED );
1200 my $nextissue = $sth->fetchrow_hashref;
1201 if ( !$nextissue ) {
1205 WHERE subscriptionid = ?
1206 ORDER BY publisheddate DESC
1209 $sth = $dbh->prepare($query);
1210 $sth->execute($subscriptionid);
1211 $nextissue = $sth->fetchrow_hashref;
1213 foreach(qw/planneddate publisheddate/) {
1214 # or should this default to 1st Jan ???
1215 $nextissue->{$_} //= strftime( '%Y-%m-%d', localtime );
1221 =head2 ModNextExpected
1223 ModNextExpected($subscriptionid,$date)
1225 Update the planneddate for the current expected issue of the subscription.
1226 This will modify all future prediction results.
1228 C<$date> is an ISO date.
1234 sub ModNextExpected {
1235 my ( $subscriptionid, $date ) = @_;
1236 my $dbh = C4::Context->dbh;
1238 #FIXME: Would expect to only set planneddate, but we set both on new issue creation, so updating it here
1239 my $sth = $dbh->prepare('UPDATE serial SET planneddate=?,publisheddate=? WHERE subscriptionid=? AND status=?');
1241 # Each subscription has only one 'expected' issue.
1242 $sth->execute( $date, $date, $subscriptionid, EXPECTED );
1247 =head2 GetSubscriptionIrregularities
1251 =item @irreg = &GetSubscriptionIrregularities($subscriptionid);
1252 get the list of irregularities for a subscription
1258 sub GetSubscriptionIrregularities {
1259 my $subscriptionid = shift;
1261 return unless $subscriptionid;
1263 my $dbh = C4::Context->dbh;
1267 WHERE subscriptionid = ?
1269 my $sth = $dbh->prepare($query);
1270 $sth->execute($subscriptionid);
1272 my ($result) = $sth->fetchrow_array;
1273 my @irreg = split /;/, $result;
1278 =head2 ModSubscription
1280 this function modifies a subscription. Put all new values on input args.
1281 returns the number of rows affected
1285 sub ModSubscription {
1287 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
1288 $periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
1289 $numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
1290 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
1291 $biblionumber, $callnumber, $notes, $letter, $manualhistory,
1292 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1293 $graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
1294 $itemtype, $previousitemtype, $mana_id, $ccode, $published_on_template
1297 my $subscription = Koha::Subscriptions->find($subscriptionid);
1300 librarian => $auser,
1301 branchcode => $branchcode,
1302 aqbooksellerid => $aqbooksellerid,
1304 aqbudgetid => $aqbudgetid,
1305 biblionumber => $biblionumber,
1306 startdate => $startdate,
1307 periodicity => $periodicity,
1308 numberlength => $numberlength,
1309 weeklength => $weeklength,
1310 monthlength => $monthlength,
1311 lastvalue1 => $lastvalue1,
1312 innerloop1 => $innerloop1,
1313 lastvalue2 => $lastvalue2,
1314 innerloop2 => $innerloop2,
1315 lastvalue3 => $lastvalue3,
1316 innerloop3 => $innerloop3,
1320 firstacquidate => $firstacquidate,
1321 irregularity => $irregularity,
1322 numberpattern => $numberpattern,
1324 callnumber => $callnumber,
1325 manualhistory => $manualhistory,
1326 internalnotes => $internalnotes,
1327 serialsadditems => $serialsadditems,
1328 staffdisplaycount => $staffdisplaycount,
1329 opacdisplaycount => $opacdisplaycount,
1330 graceperiod => $graceperiod,
1331 location => $location,
1332 enddate => $enddate,
1333 skip_serialseq => $skip_serialseq,
1334 itemtype => $itemtype,
1335 previousitemtype => $previousitemtype,
1336 mana_id => $mana_id,
1338 published_on_template => $published_on_template,
1341 # FIXME Must be $subscription->serials
1342 # FIXME We shouldn't need serial.subscription (instead use serial->subscription->biblionumber)
1343 Koha::Serials->search({ subscriptionid => $subscriptionid })->update({ biblionumber => $biblionumber });
1345 logaction( "SERIAL", "MODIFY", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1347 $subscription->discard_changes;
1348 return $subscription;
1351 =head2 NewSubscription
1353 $subscriptionid = &NewSubscription($auser,branchcode,$aqbooksellerid,$cost,$aqbudgetid,$biblionumber,
1354 $startdate,$periodicity,$numberlength,$weeklength,$monthlength,
1355 $lastvalue1,$innerloop1,$lastvalue2,$innerloop2,$lastvalue3,$innerloop3,
1356 $status, $notes, $letter, $firstacquidate, $irregularity, $numberpattern,
1357 $locale, $callnumber, $manualhistory, $internalnotes, $serialsadditems,
1358 $staffdisplaycount, $opacdisplaycount, $graceperiod, $location, $enddate,
1359 $skip_serialseq, $itemtype, $previousitemtype);
1361 Create a new subscription with value given on input args.
1364 the id of this new subscription
1368 sub NewSubscription {
1370 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1371 $startdate, $periodicity, $numberlength, $weeklength, $monthlength,
1372 $lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
1373 $innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
1374 $numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
1375 $serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
1376 $location, $enddate, $skip_serialseq, $itemtype, $previousitemtype, $mana_id, $ccode,
1377 $published_on_template,
1379 my $dbh = C4::Context->dbh;
1381 my $subscription = Koha::Subscription->new(
1383 librarian => $auser,
1384 branchcode => $branchcode,
1385 aqbooksellerid => $aqbooksellerid,
1387 aqbudgetid => $aqbudgetid,
1388 biblionumber => $biblionumber,
1389 startdate => $startdate,
1390 periodicity => $periodicity,
1391 numberlength => $numberlength,
1392 weeklength => $weeklength,
1393 monthlength => $monthlength,
1394 lastvalue1 => $lastvalue1,
1395 innerloop1 => $innerloop1,
1396 lastvalue2 => $lastvalue2,
1397 innerloop2 => $innerloop2,
1398 lastvalue3 => $lastvalue3,
1399 innerloop3 => $innerloop3,
1403 firstacquidate => $firstacquidate,
1404 irregularity => $irregularity,
1405 numberpattern => $numberpattern,
1407 callnumber => $callnumber,
1408 manualhistory => $manualhistory,
1409 internalnotes => $internalnotes,
1410 serialsadditems => $serialsadditems,
1411 staffdisplaycount => $staffdisplaycount,
1412 opacdisplaycount => $opacdisplaycount,
1413 graceperiod => $graceperiod,
1414 location => $location,
1415 enddate => $enddate,
1416 skip_serialseq => $skip_serialseq,
1417 itemtype => $itemtype,
1418 previousitemtype => $previousitemtype,
1419 mana_id => $mana_id,
1421 published_on_template => $published_on_template,
1424 $subscription->discard_changes;
1425 my $subscriptionid = $subscription->subscriptionid;
1426 my ( $query, $sth );
1428 $enddate = GetExpirationDate( $subscriptionid, $startdate );
1432 WHERE subscriptionid=?
1434 $sth = $dbh->prepare($query);
1435 $sth->execute( $enddate, $subscriptionid );
1438 # then create the 1st expected number
1440 INSERT INTO subscriptionhistory
1441 (biblionumber, subscriptionid, histstartdate, missinglist, recievedlist)
1442 VALUES (?,?,?, '', '')
1444 $sth = $dbh->prepare($query);
1445 $sth->execute( $biblionumber, $subscriptionid, $startdate);
1447 # reread subscription to get a hash (for calculation of the 1st issue number)
1448 $subscription = GetSubscription($subscriptionid); # We should not do that
1449 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1451 # calculate issue number
1452 my $serialseq = GetSeq($subscription, $pattern) || q{};
1456 serialseq => $serialseq,
1457 serialseq_x => $subscription->{'lastvalue1'},
1458 serialseq_y => $subscription->{'lastvalue2'},
1459 serialseq_z => $subscription->{'lastvalue3'},
1460 subscriptionid => $subscriptionid,
1461 biblionumber => $biblionumber,
1463 planneddate => $firstacquidate,
1464 publisheddate => $firstacquidate,
1468 logaction( "SERIAL", "ADD", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1470 #set serial flag on biblio if not already set.
1471 my $biblio = Koha::Biblios->find( $biblionumber );
1472 if ( $biblio and !$biblio->serial ) {
1473 my $record = $biblio->metadata->record;
1474 my ( $tag, $subf ) = GetMarcFromKohaField( 'biblio.serial' );
1476 eval { $record->field($tag)->update( $subf => 1 ); };
1478 ModBiblio( $record, $biblionumber, $biblio->frameworkcode );
1480 return $subscriptionid;
1483 =head2 GetSubscriptionLength
1485 my ($numberlength, $weeklength, $monthlength) = GetSubscriptionLength( $subtype, $sublength );
1487 This function calculates the subscription length.
1491 sub GetSubscriptionLength {
1492 my ($subtype, $length) = @_;
1494 return unless looks_like_number($length);
1498 $subtype eq 'issues' ? $length : 0,
1499 $subtype eq 'weeks' ? $length : 0,
1500 $subtype eq 'months' ? $length : 0,
1505 =head2 ReNewSubscription
1507 ReNewSubscription($params);
1509 $params is a hashref with the following keys: subscriptionid, user, startdate, numberlength, weeklength, monthlength, note, branchcode
1511 this function renew a subscription with values given on input args.
1515 sub ReNewSubscription {
1516 my ( $params ) = @_;
1517 my $subscriptionid = $params->{subscriptionid};
1518 my $user = $params->{user};
1519 my $startdate = $params->{startdate};
1520 my $numberlength = $params->{numberlength};
1521 my $weeklength = $params->{weeklength};
1522 my $monthlength = $params->{monthlength};
1523 my $note = $params->{note};
1524 my $branchcode = $params->{branchcode};
1526 my $dbh = C4::Context->dbh;
1527 my $subscription = GetSubscription($subscriptionid);
1531 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
1532 WHERE biblio.biblionumber=?
1534 my $sth = $dbh->prepare($query);
1535 $sth->execute( $subscription->{biblionumber} );
1536 my $biblio = $sth->fetchrow_hashref;
1538 if ( C4::Context->preference("RenewSerialAddsSuggestion") ) {
1539 Koha::Suggestion->new(
1541 'suggestedby' => $user,
1542 'title' => $subscription->{bibliotitle},
1543 'author' => $biblio->{author},
1544 'publishercode' => $biblio->{publishercode},
1546 'biblionumber' => $subscription->{biblionumber},
1547 'branchcode' => $branchcode,
1552 $numberlength ||= 0; # Should not we raise an exception instead?
1555 # renew subscription
1558 SET startdate=?,numberlength=?,weeklength=?,monthlength=?,reneweddate=NOW()
1559 WHERE subscriptionid=?
1561 $sth = $dbh->prepare($query);
1562 $sth->execute( $startdate, $numberlength, $weeklength, $monthlength, $subscriptionid );
1563 my $enddate = GetExpirationDate($subscriptionid);
1567 WHERE subscriptionid=?
1569 $sth = $dbh->prepare($query);
1570 $sth->execute( $enddate, $subscriptionid );
1572 logaction( "SERIAL", "RENEW", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1578 NewIssue($serialseq,$subscriptionid,$biblionumber,$status, $planneddate, $publisheddate, $notes, $routingnotes)
1580 Create a new issue stored on the database.
1581 Note : we have to update the recievedlist and missinglist on subscriptionhistory for this subscription.
1582 returns the serial id
1587 my ( $serialseq, $subscriptionid, $biblionumber, $status, $planneddate,
1588 $publisheddate, $publisheddatetext, $notes, $routingnotes ) = @_;
1589 ### FIXME biblionumber CAN be provided by subscriptionid. So Do we STILL NEED IT ?
1591 return unless ($subscriptionid);
1593 my $schema = Koha::Database->new()->schema();
1595 my $subscription = Koha::Subscriptions->find( $subscriptionid );
1597 if ( my $template = $subscription->published_on_template ) {
1598 # If we detect a TT opening tag, run string through Template Toolkit Processor
1599 if ( index( $template, '[%' ) != -1 ) { # Much faster than regex
1600 my $use_template_cache = C4::Context->config('template_cache_dir')
1601 && defined $ENV{GATEWAY_INTERFACE};
1603 my $tt = Template->new(
1607 PLUGIN_BASE => 'Koha::Template::Plugin',
1608 COMPILE_EXT => $use_template_cache ? '.ttc' : '',
1609 COMPILE_DIR => $use_template_cache ? C4::Context->config('template_cache_dir') : '',
1611 ENCODING => 'UTF-8',
1613 ) or die Template->error();
1615 my $schema = Koha::Database->new->schema;
1623 subscription => $subscription,
1624 serialseq => $serialseq,
1625 serialseq_x => $subscription->lastvalue1(),
1626 serialseq_y => $subscription->lastvalue2(),
1627 serialseq_z => $subscription->lastvalue3(),
1628 subscriptionid => $subscriptionid,
1629 biblionumber => $biblionumber,
1631 planneddate => $planneddate,
1632 publisheddate => $publisheddate,
1633 publisheddatetext => $publisheddatetext,
1635 routingnotes => $routingnotes,
1639 $publisheddatetext = $text;
1642 croak "ERROR PROCESSING TEMPLATE: $_ :: " . $template->error();
1645 $schema->txn_rollback;
1648 $publisheddatetext = $template;
1652 my $serial = Koha::Serial->new(
1654 serialseq => $serialseq,
1655 serialseq_x => $subscription->lastvalue1(),
1656 serialseq_y => $subscription->lastvalue2(),
1657 serialseq_z => $subscription->lastvalue3(),
1658 subscriptionid => $subscriptionid,
1659 biblionumber => $biblionumber,
1661 planneddate => $planneddate,
1662 publisheddate => $publisheddate,
1663 publisheddatetext => $publisheddatetext,
1665 routingnotes => $routingnotes,
1669 my $serialid = $serial->id();
1671 my $subscription_history = Koha::Subscription::Histories->find($subscriptionid);
1672 my $missinglist = $subscription_history->missinglist();
1673 my $recievedlist = $subscription_history->recievedlist();
1675 if ( $status == ARRIVED ) {
1676 ### TODO Add a feature that improves recognition and description.
1677 ### As such count (serialseq) i.e. : N18,2(N19),N20
1678 ### Would use substr and index But be careful to previous presence of ()
1679 $recievedlist .= "; $serialseq" unless ( index( $recievedlist, $serialseq ) > 0 );
1681 if ( grep { $_ eq $status } (MISSING_STATUSES) ) {
1682 $missinglist .= "; $serialseq" unless ( index( $missinglist, $serialseq ) > 0 );
1685 $recievedlist =~ s/^; //;
1686 $missinglist =~ s/^; //;
1688 $subscription_history->recievedlist($recievedlist);
1689 $subscription_history->missinglist($missinglist);
1690 $subscription_history->store();
1695 =head2 HasSubscriptionStrictlyExpired
1697 1 or 0 = HasSubscriptionStrictlyExpired($subscriptionid)
1699 the subscription has stricly expired when today > the end subscription date
1702 1 if true, 0 if false, -1 if the expiration date is not set.
1706 sub HasSubscriptionStrictlyExpired {
1708 # Getting end of subscription date
1709 my ($subscriptionid) = @_;
1711 return unless ($subscriptionid);
1713 my $dbh = C4::Context->dbh;
1714 my $subscription = GetSubscription($subscriptionid);
1715 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1717 # If the expiration date is set
1718 if ( $expirationdate != 0 ) {
1719 my ( $endyear, $endmonth, $endday ) = split( '-', $expirationdate );
1721 # Getting today's date
1722 my ( $nowyear, $nowmonth, $nowday ) = Today();
1724 # if today's date > expiration date, then the subscription has stricly expired
1725 if ( Delta_Days( $nowyear, $nowmonth, $nowday, $endyear, $endmonth, $endday ) < 0 ) {
1732 # There are some cases where the expiration date is not set
1733 # As we can't determine if the subscription has expired on a date-basis,
1739 =head2 HasSubscriptionExpired
1741 $has_expired = HasSubscriptionExpired($subscriptionid)
1743 the subscription has expired when the next issue to arrive is out of subscription limit.
1746 0 if the subscription has not expired
1747 1 if the subscription has expired
1748 2 if has subscription does not have a valid expiration date set
1752 sub HasSubscriptionExpired {
1753 my ($subscriptionid) = @_;
1755 return unless ($subscriptionid);
1757 my $dbh = C4::Context->dbh;
1758 my $subscription = GetSubscription($subscriptionid);
1759 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1760 if ( $frequency and $frequency->{unit} ) {
1761 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1762 if (!defined $expirationdate) {
1763 $expirationdate = q{};
1766 SELECT max(planneddate)
1768 WHERE subscriptionid=?
1770 my $sth = $dbh->prepare($query);
1771 $sth->execute($subscriptionid);
1772 my ($res) = $sth->fetchrow;
1773 if (!$res || $res=~m/^0000/) {
1776 my @res = split( /-/, $res );
1777 my @endofsubscriptiondate = split( /-/, $expirationdate );
1778 return 2 if ( scalar(@res) != 3 || scalar(@endofsubscriptiondate) != 3 || not check_date(@res) || not check_date(@endofsubscriptiondate) );
1780 if ( ( @endofsubscriptiondate && Delta_Days( $res[0], $res[1], $res[2], $endofsubscriptiondate[0], $endofsubscriptiondate[1], $endofsubscriptiondate[2] ) <= 0 )
1785 if ( $subscription->{'numberlength'} ) {
1786 my $countreceived = countissuesfrom( $subscriptionid, $subscription->{'startdate'} );
1787 return 1 if ( $countreceived > $subscription->{'numberlength'} );
1793 return 0; # Notice that you'll never get here.
1796 =head2 DelSubscription
1798 DelSubscription($subscriptionid)
1799 this function deletes subscription which has $subscriptionid as id.
1803 sub DelSubscription {
1804 my ($subscriptionid) = @_;
1805 my $dbh = C4::Context->dbh;
1806 $dbh->do("DELETE FROM subscription WHERE subscriptionid=?", undef, $subscriptionid);
1808 Koha::AdditionalFieldValues->search({
1809 'field.tablename' => 'subscription',
1810 'me.record_id' => $subscriptionid,
1811 }, { join => 'field' })->delete;
1813 logaction( "SERIAL", "DELETE", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1818 DelIssue($serialseq,$subscriptionid)
1819 this function deletes an issue which has $serialseq and $subscriptionid given on input arg.
1821 returns the number of rows affected
1826 my ($dataissue) = @_;
1827 my $dbh = C4::Context->dbh;
1828 ### TODO Add itemdeletion. Would need to get itemnumbers. Should be in a pref ?
1833 AND subscriptionid= ?
1835 my $mainsth = $dbh->prepare($query);
1836 $mainsth->execute( $dataissue->{'serialid'}, $dataissue->{'subscriptionid'} );
1838 #Delete element from subscription history
1839 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1840 my $sth = $dbh->prepare($query);
1841 $sth->execute( $dataissue->{'subscriptionid'} );
1842 my $val = $sth->fetchrow_hashref;
1843 unless ( $val->{manualhistory} ) {
1845 SELECT * FROM subscriptionhistory
1846 WHERE subscriptionid= ?
1848 my $sth = $dbh->prepare($query);
1849 $sth->execute( $dataissue->{'subscriptionid'} );
1850 my $data = $sth->fetchrow_hashref;
1851 my $serialseq = $dataissue->{'serialseq'};
1852 $data->{'missinglist'} =~ s/\b$serialseq\b//;
1853 $data->{'recievedlist'} =~ s/\b$serialseq\b//;
1854 my $strsth = "UPDATE subscriptionhistory SET " . join( ",", map { join( "=", $_, $dbh->quote( $data->{$_} ) ) } keys %$data ) . " WHERE subscriptionid=?";
1855 $sth = $dbh->prepare($strsth);
1856 $sth->execute( $dataissue->{'subscriptionid'} );
1859 return $mainsth->rows;
1862 =head2 GetLateOrMissingIssues
1864 @issuelist = GetLateMissingIssues($supplierid,$serialid)
1866 this function selects missing issues on database - where serial.status = MISSING* or serial.status = LATE or planneddate<now
1869 the issuelist as an array of hash refs. Each element of this array contains
1870 name,title,planneddate,serialseq,serial.subscriptionid from tables : subscription, serial & biblio
1874 sub GetLateOrMissingIssues {
1875 my ( $supplierid, $serialid, $order ) = @_;
1877 return unless ( $supplierid or $serialid );
1879 my $dbh = C4::Context->dbh;
1884 $byserial = "and serialid = " . $serialid;
1887 $order .= ", title";
1891 my $missing_statuses_string = join ',', (MISSING_STATUSES);
1893 $sth = $dbh->prepare(
1895 serialid, aqbooksellerid, name,
1896 biblio.title, biblioitems.issn, planneddate, serialseq,
1897 serial.status, serial.subscriptionid, claimdate, claims_count,
1898 subscription.branchcode, serial.publisheddate
1900 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1901 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1902 LEFT JOIN biblioitems ON subscription.biblionumber=biblioitems.biblionumber
1903 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1904 WHERE subscription.subscriptionid = serial.subscriptionid
1905 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1906 AND subscription.aqbooksellerid=$supplierid
1911 $sth = $dbh->prepare(
1913 serialid, aqbooksellerid, name,
1914 biblio.title, planneddate, serialseq,
1915 serial.status, serial.subscriptionid, claimdate, claims_count,
1916 subscription.branchcode, serial.publisheddate
1918 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1919 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1920 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1921 WHERE subscription.subscriptionid = serial.subscriptionid
1922 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1927 $sth->execute( EXPECTED, LATE, CLAIMED );
1929 while ( my $line = $sth->fetchrow_hashref ) {
1930 $line->{"status".$line->{status}} = 1;
1932 my $subscription_object = Koha::Subscriptions->find($line->{subscriptionid});
1933 $line->{additional_fields} = { map { $_->field->name => $_->value }
1934 $subscription_object->additional_field_values->as_list };
1936 push @issuelist, $line;
1943 &updateClaim($serialid)
1945 this function updates the time when a claim is issued for late/missing items
1947 called from claims.pl file
1952 my ($serialids) = @_;
1953 return unless $serialids;
1954 unless ( ref $serialids ) {
1955 $serialids = [ $serialids ];
1958 foreach my $serialid(@$serialids) {
1959 my $serial = Koha::Serials->find($serialid);
1961 C4::Serials::ModSerialStatus(
1964 $serial->planneddate,
1965 $serial->publisheddate,
1966 $serial->publisheddatetext,
1967 C4::Serials->CLAIMED,
1972 my $dbh = C4::Context->dbh;
1975 SET claimdate = NOW(),
1976 claims_count = claims_count + 1,
1978 WHERE serialid in (| . join( q|,|, (q|?|) x @$serialids ) . q|)|,
1979 {}, CLAIMED, @$serialids );
1982 =head2 check_routing
1984 $result = &check_routing($subscriptionid)
1986 this function checks to see if a serial has a routing list and returns the count of routingid
1987 used to show either an 'add' or 'edit' link
1992 my ($subscriptionid) = @_;
1994 return unless ($subscriptionid);
1996 my $dbh = C4::Context->dbh;
1997 my $sth = $dbh->prepare(
1998 "SELECT count(routingid) routingids FROM subscription LEFT JOIN subscriptionroutinglist
1999 ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
2000 WHERE subscription.subscriptionid = ? GROUP BY routingid ORDER BY ranking ASC
2003 $sth->execute($subscriptionid);
2004 my $line = $sth->fetchrow_hashref;
2005 my $result = $line->{'routingids'};
2009 =head2 addroutingmember
2011 addroutingmember($borrowernumber,$subscriptionid)
2013 this function takes a borrowernumber and subscriptionid and adds the member to the
2014 routing list for that serial subscription and gives them a rank on the list
2015 of either 1 or highest current rank + 1
2019 sub addroutingmember {
2020 my ( $borrowernumber, $subscriptionid ) = @_;
2022 return unless ($borrowernumber and $subscriptionid);
2025 my $dbh = C4::Context->dbh;
2026 my $sth = $dbh->prepare( "SELECT max(ranking) rank FROM subscriptionroutinglist WHERE subscriptionid = ?" );
2027 $sth->execute($subscriptionid);
2028 while ( my $line = $sth->fetchrow_hashref ) {
2029 if ( $line->{'rank'} > 0 ) {
2030 $rank = $line->{'rank'} + 1;
2035 $sth = $dbh->prepare( "INSERT INTO subscriptionroutinglist (subscriptionid,borrowernumber,ranking) VALUES (?,?,?)" );
2036 $sth->execute( $subscriptionid, $borrowernumber, $rank );
2039 =head2 reorder_members
2041 reorder_members($subscriptionid,$routingid,$rank)
2043 this function is used to reorder the routing list
2045 it takes the routingid of the member one wants to re-rank and the rank it is to move to
2046 - it gets all members on list puts their routingid's into an array
2047 - removes the one in the array that is $routingid
2048 - then reinjects $routingid at point indicated by $rank
2049 - then update the database with the routingids in the new order
2053 sub reorder_members {
2054 my ( $subscriptionid, $routingid, $rank ) = @_;
2055 my $dbh = C4::Context->dbh;
2056 my $sth = $dbh->prepare( "SELECT * FROM subscriptionroutinglist WHERE subscriptionid = ? ORDER BY ranking ASC" );
2057 $sth->execute($subscriptionid);
2059 while ( my $line = $sth->fetchrow_hashref ) {
2060 push( @result, $line->{'routingid'} );
2063 # To find the matching index
2065 my $key = -1; # to allow for 0 being a valid response
2066 for ( $i = 0 ; $i < @result ; $i++ ) {
2067 if ( $routingid == $result[$i] ) {
2068 $key = $i; # save the index
2073 # if index exists in array then move it to new position
2074 if ( $key > -1 && $rank > 0 ) {
2075 my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
2076 my $moving_item = splice( @result, $key, 1 );
2077 splice( @result, $new_rank, 0, $moving_item );
2079 for ( my $j = 0 ; $j < @result ; $j++ ) {
2080 my $sth = $dbh->prepare( "UPDATE subscriptionroutinglist SET ranking = '" . ( $j + 1 ) . "' WHERE routingid = '" . $result[$j] . "'" );
2086 =head2 delroutingmember
2088 delroutingmember($routingid,$subscriptionid)
2090 this function either deletes one member from routing list if $routingid exists otherwise
2091 deletes all members from the routing list
2095 sub delroutingmember {
2097 # if $routingid exists then deletes that row otherwise deletes all with $subscriptionid
2098 my ( $routingid, $subscriptionid ) = @_;
2099 my $dbh = C4::Context->dbh;
2101 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE routingid = ?");
2102 $sth->execute($routingid);
2103 reorder_members( $subscriptionid, $routingid );
2105 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE subscriptionid = ?");
2106 $sth->execute($subscriptionid);
2111 =head2 getroutinglist
2113 @routinglist = getroutinglist($subscriptionid)
2115 this gets the info from the subscriptionroutinglist for $subscriptionid
2118 the routinglist as an array. Each element of the array contains a hash_ref containing
2119 routingid - a unique id, borrowernumber, ranking, and biblionumber of subscription
2123 sub getroutinglist {
2124 my ($subscriptionid) = @_;
2125 my $dbh = C4::Context->dbh;
2126 my $sth = $dbh->prepare(
2127 'SELECT routingid, borrowernumber, ranking, biblionumber
2129 JOIN subscriptionroutinglist ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
2130 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC'
2132 $sth->execute($subscriptionid);
2133 my $routinglist = $sth->fetchall_arrayref({});
2134 return @{$routinglist};
2137 =head2 countissuesfrom
2139 $result = countissuesfrom($subscriptionid,$startdate)
2141 Returns a count of serial rows matching the given subsctiptionid
2142 with published date greater than startdate
2146 sub countissuesfrom {
2147 my ( $subscriptionid, $startdate ) = @_;
2148 my $dbh = C4::Context->dbh;
2152 WHERE subscriptionid=?
2153 AND serial.publisheddate>?
2155 my $sth = $dbh->prepare($query);
2156 $sth->execute( $subscriptionid, $startdate );
2157 my ($countreceived) = $sth->fetchrow;
2158 return $countreceived;
2163 $result = CountIssues($subscriptionid)
2165 Returns a count of serial rows matching the given subsctiptionid
2170 my ($subscriptionid) = @_;
2171 my $dbh = C4::Context->dbh;
2175 WHERE subscriptionid=?
2177 my $sth = $dbh->prepare($query);
2178 $sth->execute($subscriptionid);
2179 my ($countreceived) = $sth->fetchrow;
2180 return $countreceived;
2185 $result = HasItems($subscriptionid)
2187 returns a count of items from serial matching the subscriptionid
2192 my ($subscriptionid) = @_;
2193 my $dbh = C4::Context->dbh;
2195 SELECT COUNT(serialitems.itemnumber)
2197 LEFT JOIN serialitems USING(serialid)
2198 WHERE subscriptionid=? AND serialitems.serialid IS NOT NULL
2200 my $sth=$dbh->prepare($query);
2201 $sth->execute($subscriptionid);
2202 my ($countitems)=$sth->fetchrow_array();
2206 =head2 abouttoexpire
2208 $result = abouttoexpire($subscriptionid)
2210 this function alerts you to the penultimate issue for a serial subscription
2212 returns 1 - if this is the penultimate issue
2218 my ($subscriptionid) = @_;
2219 my $dbh = C4::Context->dbh;
2220 my $subscription = GetSubscription($subscriptionid);
2221 my $per = $subscription->{'periodicity'};
2222 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($per);
2223 if ($frequency and $frequency->{unit}){
2225 my $expirationdate = GetExpirationDate($subscriptionid);
2227 my ($res) = $dbh->selectrow_array('select max(planneddate) from serial where subscriptionid = ?', undef, $subscriptionid);
2228 my $nextdate = GetNextDate($subscription, $res, $frequency);
2230 # only compare dates if both dates exist.
2231 if ($nextdate and $expirationdate) {
2232 if(Date::Calc::Delta_Days(
2233 split( /-/, $nextdate ),
2234 split( /-/, $expirationdate )
2240 } elsif ( $subscription->{numberlength} && $subscription->{numberlength}>0) {
2241 return (countissuesfrom($subscriptionid,$subscription->{'startdate'}) >=$subscription->{numberlength}-1);
2247 =head2 GetFictiveIssueNumber
2249 $issueno = GetFictiveIssueNumber($subscription, $publishedate, $frequency);
2251 Get the position of the issue published at $publisheddate, considering the
2252 first issue (at firstacquidate) is at position 1, the next is at position 2, etc...
2253 This issuenumber doesn't take into account irregularities, so, for instance, if the 3rd
2254 issue is declared as 'irregular' (will be skipped at receipt), the next issue number
2255 will be 4, not 3. It's why it is called 'fictive'. It is NOT a serial seq, and is not
2256 depending on how many rows are in serial table.
2257 The issue number calculation is based on subscription frequency, first acquisition
2258 date, and $publisheddate.
2260 Returns undef when called for irregular frequencies.
2262 The routine is used to skip irregularities when calculating the next issue
2263 date (in GetNextDate) or the next issue number (in GetNextSeq).
2267 sub GetFictiveIssueNumber {
2268 my ($subscription, $publisheddate, $frequency) = @_;
2270 my $unit = $frequency->{unit} ? lc $frequency->{'unit'} : undef;
2274 my ( $year, $month, $day ) = split /-/, $publisheddate;
2275 my ( $fa_year, $fa_month, $fa_day ) = split /-/, $subscription->{'firstacquidate'};
2276 my $delta = _delta_units( [$fa_year, $fa_month, $fa_day], [$year, $month, $day], $unit );
2278 if( $frequency->{'unitsperissue'} == 1 ) {
2279 $issueno = $delta * $frequency->{'issuesperunit'} + $subscription->{'countissuesperunit'};
2280 } else { # issuesperunit == 1
2281 $issueno = 1 + int( $delta / $frequency->{'unitsperissue'} );
2287 my ( $date1, $date2, $unit ) = @_;
2288 # date1 and date2 are array refs in the form [ yy, mm, dd ]
2290 if( $unit eq 'day' ) {
2291 return Delta_Days( @$date1, @$date2 );
2292 } elsif( $unit eq 'week' ) {
2293 return int( Delta_Days( @$date1, @$date2 ) / 7 );
2296 # In case of months or years, this is a wrapper around N_Delta_YMD.
2297 # Note that N_Delta_YMD returns 29 days between e.g. 22-2-72 and 22-3-72
2298 # while we expect 1 month.
2299 my @delta = N_Delta_YMD( @$date1, @$date2 );
2300 if( $delta[2] > 27 ) {
2301 # Check if we could add a month
2302 my @jump = Add_Delta_YM( @$date1, $delta[0], 1 + $delta[1] );
2303 if( Delta_Days( @jump, @$date2 ) >= 0 ) {
2307 if( $delta[1] >= 12 ) {
2311 # if unit is year, we only return full years
2312 return $unit eq 'month' ? $delta[0] * 12 + $delta[1] : $delta[0];
2315 sub _get_next_date_day {
2316 my ($subscription, $freqdata, $year, $month, $day) = @_;
2318 my @newissue; # ( yy, mm, dd )
2319 # We do not need $delta_days here, since it would be zero where used
2321 if( $freqdata->{issuesperunit} == 1 ) {
2323 @newissue = Add_Delta_Days(
2324 $year, $month, $day, $freqdata->{"unitsperissue"} );
2325 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2327 @newissue = ( $year, $month, $day );
2328 $subscription->{countissuesperunit}++;
2330 # We finished a cycle of issues within a unit.
2331 # No subtraction of zero needed, just add one day
2332 @newissue = Add_Delta_Days( $year, $month, $day, 1 );
2333 $subscription->{countissuesperunit} = 1;
2338 sub _get_next_date_week {
2339 my ($subscription, $freqdata, $year, $month, $day) = @_;
2341 my @newissue; # ( yy, mm, dd )
2342 my $delta_days = int( 7 / $freqdata->{issuesperunit} );
2344 if( $freqdata->{issuesperunit} == 1 ) {
2345 # Add full weeks (of 7 days)
2346 @newissue = Add_Delta_Days(
2347 $year, $month, $day, 7 * $freqdata->{"unitsperissue"} );
2348 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2349 # Add rounded number of days based on frequency.
2350 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2351 $subscription->{countissuesperunit}++;
2353 # We finished a cycle of issues within a unit.
2354 # Subtract delta * (issues - 1), add 1 week
2355 @newissue = Add_Delta_Days( $year, $month, $day,
2356 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2357 @newissue = Add_Delta_Days( @newissue, 7 );
2358 $subscription->{countissuesperunit} = 1;
2363 sub _get_next_date_month {
2364 my ($subscription, $freqdata, $year, $month, $day) = @_;
2366 my @newissue; # ( yy, mm, dd )
2367 my $delta_days = int( 30 / $freqdata->{issuesperunit} );
2369 if( $freqdata->{issuesperunit} == 1 ) {
2371 @newissue = Add_Delta_YM(
2372 $year, $month, $day, 0, $freqdata->{"unitsperissue"} );
2373 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2374 # Add rounded number of days based on frequency.
2375 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2376 $subscription->{countissuesperunit}++;
2378 # We finished a cycle of issues within a unit.
2379 # Subtract delta * (issues - 1), add 1 month
2380 @newissue = Add_Delta_Days( $year, $month, $day,
2381 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2382 @newissue = Add_Delta_YM( @newissue, 0, 1 );
2383 $subscription->{countissuesperunit} = 1;
2388 sub _get_next_date_year {
2389 my ($subscription, $freqdata, $year, $month, $day) = @_;
2391 my @newissue; # ( yy, mm, dd )
2392 my $delta_days = int( 365 / $freqdata->{issuesperunit} );
2394 if( $freqdata->{issuesperunit} == 1 ) {
2396 @newissue = Add_Delta_YM( $year, $month, $day, $freqdata->{"unitsperissue"}, 0 );
2397 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2398 # Add rounded number of days based on frequency.
2399 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2400 $subscription->{countissuesperunit}++;
2402 # We finished a cycle of issues within a unit.
2403 # Subtract delta * (issues - 1), add 1 year
2404 @newissue = Add_Delta_Days( $year, $month, $day, -$delta_days * ($freqdata->{issuesperunit} - 1) );
2405 @newissue = Add_Delta_YM( @newissue, 1, 0 );
2406 $subscription->{countissuesperunit} = 1;
2413 $resultdate = GetNextDate($publisheddate,$subscription,$freqdata,$updatecount)
2415 this function it takes the publisheddate and will return the next issue's date
2416 and will skip dates if there exists an irregularity.
2417 $publisheddate has to be an ISO date
2418 $subscription is a hashref containing at least 'firstacquidate', 'irregularity', and 'countissuesperunit'
2419 $frequency is a hashref containing frequency informations
2420 $updatecount is a boolean value which, when set to true, update the 'countissuesperunit' in database
2421 - eg if periodicity is monthly and $publisheddate is 2007-02-10 but if March and April is to be
2422 skipped then the returned date will be 2007-05-10
2425 $resultdate - then next date in the sequence (ISO date)
2427 Return undef if subscription is irregular
2432 my ( $subscription, $publisheddate, $freqdata, $updatecount ) = @_;
2434 return unless $subscription and $publisheddate;
2437 if ($freqdata->{'unit'}) {
2438 my ( $year, $month, $day ) = split /-/, $publisheddate;
2440 # Process an irregularity Hash
2441 # Suppose that irregularities are stored in a string with this structure
2442 # irreg1;irreg2;irreg3
2443 # where irregX is the number of issue which will not be received
2444 # (the first issue takes the number 1, the 2nd the number 2 and so on)
2446 if ( $subscription->{irregularity} ) {
2447 my @irreg = split /;/, $subscription->{'irregularity'} ;
2448 foreach my $irregularity (@irreg) {
2449 $irregularities{$irregularity} = 1;
2453 # Get the 'fictive' next issue number
2454 # It is used to check if next issue is an irregular issue.
2455 my $issueno = GetFictiveIssueNumber($subscription, $publisheddate, $freqdata) + 1;
2457 # Then get the next date
2458 my $unit = lc $freqdata->{'unit'};
2459 if ($unit eq 'day') {
2460 while ($irregularities{$issueno}) {
2461 ($year, $month, $day) = _get_next_date_day($subscription,
2462 $freqdata, $year, $month, $day);
2465 ($year, $month, $day) = _get_next_date_day($subscription, $freqdata,
2466 $year, $month, $day);
2468 elsif ($unit eq 'week') {
2469 while ($irregularities{$issueno}) {
2470 ($year, $month, $day) = _get_next_date_week($subscription,
2471 $freqdata, $year, $month, $day);
2474 ($year, $month, $day) = _get_next_date_week($subscription,
2475 $freqdata, $year, $month, $day);
2477 elsif ($unit eq 'month') {
2478 while ($irregularities{$issueno}) {
2479 ($year, $month, $day) = _get_next_date_month($subscription,
2480 $freqdata, $year, $month, $day);
2483 ($year, $month, $day) = _get_next_date_month($subscription,
2484 $freqdata, $year, $month, $day);
2486 elsif ($unit eq 'year') {
2487 while ($irregularities{$issueno}) {
2488 ($year, $month, $day) = _get_next_date_year($subscription,
2489 $freqdata, $year, $month, $day);
2492 ($year, $month, $day) = _get_next_date_year($subscription,
2493 $freqdata, $year, $month, $day);
2497 my $dbh = C4::Context->dbh;
2500 SET countissuesperunit = ?
2501 WHERE subscriptionid = ?
2503 my $sth = $dbh->prepare($query);
2504 $sth->execute($subscription->{'countissuesperunit'}, $subscription->{'subscriptionid'});
2507 return sprintf("%04d-%02d-%02d", $year, $month, $day);
2513 $string = &_numeration($value,$num_type,$locale);
2515 _numeration returns the string corresponding to $value in the num_type
2527 my ($value, $num_type, $locale) = @_;
2532 if ( $num_type =~ /^dayname$/ or $num_type =~ /^dayabrv$/ ) {
2533 # 1970-11-01 was a Sunday
2534 $value = $value % 7;
2535 my $dt = DateTime->new(
2541 $string = $num_type =~ /^dayname$/
2542 ? $dt->strftime("%A")
2543 : $dt->strftime("%a");
2544 } elsif ( $num_type =~ /^monthname$/ or $num_type =~ /^monthabrv$/ ) {
2545 $value = $value % 12;
2546 my $dt = DateTime->new(
2548 month => $value + 1,
2551 $string = $num_type =~ /^monthname$/
2552 ? $dt->format_cldr( "LLLL" )
2553 : $dt->strftime("%b");
2554 } elsif ( $num_type =~ /^season$/ ) {
2555 my @seasons= qw( Spring Summer Fall Winter );
2556 $value = $value % 4;
2557 $string = $seasons[$value];
2558 } elsif ( $num_type =~ /^seasonabrv$/ ) {
2559 my @seasonsabrv= qw( Spr Sum Fal Win );
2560 $value = $value % 4;
2561 $string = $seasonsabrv[$value];
2569 =head2 CloseSubscription
2571 Close a subscription given a subscriptionid
2575 sub CloseSubscription {
2576 my ( $subscriptionid ) = @_;
2577 return unless $subscriptionid;
2578 my $dbh = C4::Context->dbh;
2579 my $sth = $dbh->prepare( q{
2582 WHERE subscriptionid = ?
2584 $sth->execute( $subscriptionid );
2586 # Set status = missing when status = stopped
2587 $sth = $dbh->prepare( q{
2590 WHERE subscriptionid = ?
2593 $sth->execute( STOPPED, $subscriptionid, EXPECTED );
2596 =head2 ReopenSubscription
2598 Reopen a subscription given a subscriptionid
2602 sub ReopenSubscription {
2603 my ( $subscriptionid ) = @_;
2604 return unless $subscriptionid;
2605 my $dbh = C4::Context->dbh;
2606 my $sth = $dbh->prepare( q{
2609 WHERE subscriptionid = ?
2611 $sth->execute( $subscriptionid );
2613 # Set status = expected when status = stopped
2614 $sth = $dbh->prepare( q{
2617 WHERE subscriptionid = ?
2620 $sth->execute( EXPECTED, $subscriptionid, STOPPED );
2623 =head2 subscriptionCurrentlyOnOrder
2625 $bool = subscriptionCurrentlyOnOrder( $subscriptionid );
2627 Return 1 if subscription is currently on order else 0.
2631 sub subscriptionCurrentlyOnOrder {
2632 my ( $subscriptionid ) = @_;
2633 my $dbh = C4::Context->dbh;
2635 SELECT COUNT(*) FROM aqorders
2636 WHERE subscriptionid = ?
2637 AND datereceived IS NULL
2638 AND datecancellationprinted IS NULL
2640 my $sth = $dbh->prepare( $query );
2641 $sth->execute($subscriptionid);
2642 return $sth->fetchrow_array;
2645 =head2 can_claim_subscription
2647 $can = can_claim_subscription( $subscriptionid[, $userid] );
2649 Return 1 if the subscription can be claimed by the current logged user (or a given $userid), else 0.
2653 sub can_claim_subscription {
2654 my ( $subscription, $userid ) = @_;
2655 return _can_do_on_subscription( $subscription, $userid, 'claim_serials' );
2658 =head2 can_edit_subscription
2660 $can = can_edit_subscription( $subscriptionid[, $userid] );
2662 Return 1 if the subscription can be edited by the current logged user (or a given $userid), else 0.
2666 sub can_edit_subscription {
2667 my ( $subscription, $userid ) = @_;
2668 return _can_do_on_subscription( $subscription, $userid, 'edit_subscription' );
2671 =head2 can_show_subscription
2673 $can = can_show_subscription( $subscriptionid[, $userid] );
2675 Return 1 if the subscription can be shown by the current logged user (or a given $userid), else 0.
2679 sub can_show_subscription {
2680 my ( $subscription, $userid ) = @_;
2681 return _can_do_on_subscription( $subscription, $userid, '*' );
2684 sub _can_do_on_subscription {
2685 my ( $subscription, $userid, $permission ) = @_;
2686 return 0 unless C4::Context->userenv;
2687 my $flags = C4::Context->userenv->{flags};
2688 $userid ||= C4::Context->userenv->{'id'};
2690 if ( C4::Context->preference('IndependentBranches') ) {
2692 if C4::Context->IsSuperLibrarian()
2694 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2696 C4::Auth::haspermission( $userid,
2697 { serials => $permission } )
2698 and ( not defined $subscription->{branchcode}
2699 or $subscription->{branchcode} eq ''
2700 or $subscription->{branchcode} eq
2701 C4::Context->userenv->{'branch'} )
2706 if C4::Context->IsSuperLibrarian()
2708 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2709 or C4::Auth::haspermission(
2710 $userid, { serials => $permission }
2717 =head2 findSerialsByStatus
2719 @serials = findSerialsByStatus($status, $subscriptionid);
2721 Returns an array of serials matching a given status and subscription id.
2725 sub findSerialsByStatus {
2726 my ( $status, $subscriptionid ) = @_;
2727 my $dbh = C4::Context->dbh;
2728 my $query = q| SELECT * from serial
2730 AND subscriptionid = ?
2732 my $serials = $dbh->selectall_arrayref( $query, { Slice => {} }, $status, $subscriptionid );
2741 Koha Development Team <http://koha-community.org/>