Bug 22509: (RM follow-up) Add use of Koha::Script base class
[koha.git] / C4 / Serials.pm
blob44c3cb958eee15a5bc8aa9131cb905d42fcc8c49
1 package C4::Serials;
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>.
21 use Modern::Perl;
23 use C4::Auth qw(haspermission);
24 use C4::Context;
25 use DateTime;
26 use Date::Calc qw(:all);
27 use POSIX qw(strftime);
28 use C4::Biblio;
29 use C4::Log; # logaction
30 use C4::Debug;
31 use C4::Serials::Frequency;
32 use C4::Serials::Numberpattern;
33 use Koha::AdditionalFieldValues;
34 use Koha::DateUtils;
35 use Koha::Serial;
36 use Koha::Subscriptions;
37 use Koha::Subscription::Histories;
38 use Koha::SharedContent;
40 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
42 # Define statuses
43 use constant {
44 EXPECTED => 1,
45 ARRIVED => 2,
46 LATE => 3,
47 MISSING => 4,
48 MISSING_NEVER_RECIEVED => 41,
49 MISSING_SOLD_OUT => 42,
50 MISSING_DAMAGED => 43,
51 MISSING_LOST => 44,
52 NOT_ISSUED => 5,
53 DELETED => 6,
54 CLAIMED => 7,
55 STOPPED => 8,
58 use constant MISSING_STATUSES => (
59 MISSING, MISSING_NEVER_RECIEVED,
60 MISSING_SOLD_OUT, MISSING_DAMAGED,
61 MISSING_LOST
64 BEGIN {
65 require Exporter;
66 @ISA = qw(Exporter);
67 @EXPORT = qw(
68 &NewSubscription &ModSubscription &DelSubscription
69 &GetSubscription &CountSubscriptionFromBiblionumber &GetSubscriptionsFromBiblionumber
70 &SearchSubscriptions
71 &GetFullSubscriptionsFromBiblionumber &GetFullSubscription &ModSubscriptionHistory
72 &HasSubscriptionStrictlyExpired &HasSubscriptionExpired &GetExpirationDate &abouttoexpire
73 &GetSubscriptionHistoryFromSubscriptionId
75 &GetNextSeq &GetSeq &NewIssue &GetSerials
76 &GetLatestSerials &ModSerialStatus &GetNextDate &GetSerials2
77 &ReNewSubscription &GetLateOrMissingIssues
78 &GetSerialInformation &AddItem2Serial
79 &PrepareSerialsData &GetNextExpected &ModNextExpected
80 &GetPreviousSerialid
82 &GetSuppliersWithLateIssues
83 &getroutinglist &delroutingmember &addroutingmember
84 &reorder_members
85 &check_routing &updateClaim
86 &CountIssues
87 HasItems
88 &subscriptionCurrentlyOnOrder
93 =head1 NAME
95 C4::Serials - Serials Module Functions
97 =head1 SYNOPSIS
99 use C4::Serials;
101 =head1 DESCRIPTION
103 Functions for handling subscriptions, claims routing etc.
106 =head1 SUBROUTINES
108 =head2 GetSuppliersWithLateIssues
110 $supplierlist = GetSuppliersWithLateIssues()
112 this function get all suppliers with late issues.
114 return :
115 an array_ref of suppliers each entry is a hash_ref containing id and name
116 the array is in name order
118 =cut
120 sub GetSuppliersWithLateIssues {
121 my $dbh = C4::Context->dbh;
122 my $statuses = join(',', ( LATE, MISSING_STATUSES, CLAIMED ) );
123 my $query = qq|
124 SELECT DISTINCT id, name
125 FROM subscription
126 LEFT JOIN serial ON serial.subscriptionid=subscription.subscriptionid
127 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
128 WHERE id > 0
129 AND (
130 (planneddate < now() AND serial.status=1)
131 OR serial.STATUS IN ( $statuses )
133 AND subscription.closed = 0
134 ORDER BY name|;
135 return $dbh->selectall_arrayref($query, { Slice => {} });
138 =head2 GetSubscriptionHistoryFromSubscriptionId
140 $history = GetSubscriptionHistoryFromSubscriptionId($subscriptionid);
142 This function returns the subscription history as a hashref
144 =cut
146 sub GetSubscriptionHistoryFromSubscriptionId {
147 my ($subscriptionid) = @_;
149 return unless $subscriptionid;
151 my $dbh = C4::Context->dbh;
152 my $query = qq|
153 SELECT *
154 FROM subscriptionhistory
155 WHERE subscriptionid = ?
157 my $sth = $dbh->prepare($query);
158 $sth->execute($subscriptionid);
159 my $results = $sth->fetchrow_hashref;
160 $sth->finish;
162 return $results;
165 =head2 GetSerialInformation
167 $data = GetSerialInformation($serialid);
168 returns a hash_ref containing :
169 items : items marcrecord (can be an array)
170 serial table field
171 subscription table field
172 + information about subscription expiration
174 =cut
176 sub GetSerialInformation {
177 my ($serialid) = @_;
178 my $dbh = C4::Context->dbh;
179 my $query = qq|
180 SELECT serial.*, serial.notes as sernotes, serial.status as serstatus,subscription.*,subscription.subscriptionid as subsid
181 FROM serial LEFT JOIN subscription ON subscription.subscriptionid=serial.subscriptionid
182 WHERE serialid = ?
184 my $rq = $dbh->prepare($query);
185 $rq->execute($serialid);
186 my $data = $rq->fetchrow_hashref;
188 # create item information if we have serialsadditems for this subscription
189 if ( $data->{'serialsadditems'} ) {
190 my $queryitem = $dbh->prepare("SELECT itemnumber from serialitems where serialid=?");
191 $queryitem->execute($serialid);
192 my $itemnumbers = $queryitem->fetchall_arrayref( [0] );
193 require C4::Items;
194 if ( scalar(@$itemnumbers) > 0 ) {
195 foreach my $itemnum (@$itemnumbers) {
197 #It is ASSUMED that GetMarcItem ALWAYS WORK...
198 #Maybe GetMarcItem should return values on failure
199 $debug and warn "itemnumber :$itemnum->[0], bibnum :" . $data->{'biblionumber'};
200 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, $itemnum->[0], $data );
201 $itemprocessed->{'itemnumber'} = $itemnum->[0];
202 $itemprocessed->{'itemid'} = $itemnum->[0];
203 $itemprocessed->{'serialid'} = $serialid;
204 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
205 push @{ $data->{'items'} }, $itemprocessed;
207 } else {
208 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, '', $data );
209 $itemprocessed->{'itemid'} = "N$serialid";
210 $itemprocessed->{'serialid'} = $serialid;
211 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
212 $itemprocessed->{'countitems'} = 0;
213 push @{ $data->{'items'} }, $itemprocessed;
216 $data->{ "status" . $data->{'serstatus'} } = 1;
217 $data->{'subscriptionexpired'} = HasSubscriptionExpired( $data->{'subscriptionid'} ) && $data->{'status'} == 1;
218 $data->{'abouttoexpire'} = abouttoexpire( $data->{'subscriptionid'} );
219 $data->{cannotedit} = not can_edit_subscription( $data );
220 return $data;
223 =head2 AddItem2Serial
225 $rows = AddItem2Serial($serialid,$itemnumber);
226 Adds an itemnumber to Serial record
227 returns the number of rows affected
229 =cut
231 sub AddItem2Serial {
232 my ( $serialid, $itemnumber ) = @_;
234 return unless ($serialid and $itemnumber);
236 my $dbh = C4::Context->dbh;
237 my $rq = $dbh->prepare("INSERT INTO `serialitems` SET serialid=? , itemnumber=?");
238 $rq->execute( $serialid, $itemnumber );
239 return $rq->rows;
242 =head2 GetSubscription
244 $subs = GetSubscription($subscriptionid)
245 this function returns the subscription which has $subscriptionid as id.
246 return :
247 a hashref. This hash contains
248 subscription, subscriptionhistory, aqbooksellers.name, biblio.title
250 =cut
252 sub GetSubscription {
253 my ($subscriptionid) = @_;
254 my $dbh = C4::Context->dbh;
255 my $query = qq(
256 SELECT subscription.*,
257 subscriptionhistory.*,
258 aqbooksellers.name AS aqbooksellername,
259 biblio.title AS bibliotitle,
260 subscription.biblionumber as bibnum
261 FROM subscription
262 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
263 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
264 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
265 WHERE subscription.subscriptionid = ?
268 $debug and warn "query : $query\nsubsid :$subscriptionid";
269 my $sth = $dbh->prepare($query);
270 $sth->execute($subscriptionid);
271 my $subscription = $sth->fetchrow_hashref;
273 return unless $subscription;
275 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
277 if ( my $mana_id = $subscription->{mana_id} ) {
278 my $mana_subscription = Koha::SharedContent::get_entity_by_id(
279 'subscription', $mana_id, {usecomments => 1});
280 $subscription->{comments} = $mana_subscription->{data}->{comments};
283 return $subscription;
286 =head2 GetFullSubscription
288 $array_ref = GetFullSubscription($subscriptionid)
289 this function reads the serial table.
291 =cut
293 sub GetFullSubscription {
294 my ($subscriptionid) = @_;
296 return unless ($subscriptionid);
298 my $dbh = C4::Context->dbh;
299 my $query = qq|
300 SELECT serial.serialid,
301 serial.serialseq,
302 serial.planneddate,
303 serial.publisheddate,
304 serial.publisheddatetext,
305 serial.status,
306 serial.notes as notes,
307 year(IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate)) as year,
308 aqbooksellers.name as aqbooksellername,
309 biblio.title as bibliotitle,
310 subscription.branchcode AS branchcode,
311 subscription.subscriptionid AS subscriptionid
312 FROM serial
313 LEFT JOIN subscription ON
314 (serial.subscriptionid=subscription.subscriptionid )
315 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
316 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
317 WHERE serial.subscriptionid = ?
318 ORDER BY year DESC,
319 IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate) DESC,
320 serial.subscriptionid
322 $debug and warn "GetFullSubscription query: $query";
323 my $sth = $dbh->prepare($query);
324 $sth->execute($subscriptionid);
325 my $subscriptions = $sth->fetchall_arrayref( {} );
326 my $cannotedit = not can_edit_subscription( $subscriptions->[0] ) if scalar @$subscriptions;
327 for my $subscription ( @$subscriptions ) {
328 $subscription->{cannotedit} = $cannotedit;
330 return $subscriptions;
333 =head2 PrepareSerialsData
335 $array_ref = PrepareSerialsData($serialinfomation)
336 where serialinformation is a hashref array
338 =cut
340 sub PrepareSerialsData {
341 my ($lines) = @_;
343 return unless ($lines);
345 my %tmpresults;
346 my $year;
347 my @res;
348 my $startdate;
349 my $aqbooksellername;
350 my $bibliotitle;
351 my @loopissues;
352 my $first;
353 my $previousnote = "";
355 foreach my $subs (@{$lines}) {
356 for my $datefield ( qw(publisheddate planneddate) ) {
357 # handle 0000-00-00 dates
358 if (defined $subs->{$datefield} and $subs->{$datefield} =~ m/^00/) {
359 $subs->{$datefield} = undef;
362 $subs->{ "status" . $subs->{'status'} } = 1;
363 if ( grep { $_ == $subs->{status} } ( EXPECTED, LATE, MISSING_STATUSES, CLAIMED ) ) {
364 $subs->{"checked"} = 1;
367 if ( $subs->{'year'} && $subs->{'year'} ne "" ) {
368 $year = $subs->{'year'};
369 } else {
370 $year = "manage";
372 if ( $tmpresults{$year} ) {
373 push @{ $tmpresults{$year}->{'serials'} }, $subs;
374 } else {
375 $tmpresults{$year} = {
376 'year' => $year,
377 'aqbooksellername' => $subs->{'aqbooksellername'},
378 'bibliotitle' => $subs->{'bibliotitle'},
379 'serials' => [$subs],
380 'first' => $first,
384 foreach my $key ( sort { $b cmp $a } keys %tmpresults ) {
385 push @res, $tmpresults{$key};
387 return \@res;
390 =head2 GetSubscriptionsFromBiblionumber
392 $array_ref = GetSubscriptionsFromBiblionumber($biblionumber)
393 this function get the subscription list. it reads the subscription table.
394 return :
395 reference to an array of subscriptions which have the biblionumber given on input arg.
396 each element of this array is a hashref containing
397 startdate, histstartdate,opacnote,missinglist,recievedlist,periodicity,status & enddate
399 =cut
401 sub GetSubscriptionsFromBiblionumber {
402 my ($biblionumber) = @_;
404 return unless ($biblionumber);
406 my $dbh = C4::Context->dbh;
407 my $query = qq(
408 SELECT subscription.*,
409 branches.branchname,
410 subscriptionhistory.*,
411 aqbooksellers.name AS aqbooksellername,
412 biblio.title AS bibliotitle
413 FROM subscription
414 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
415 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
416 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
417 LEFT JOIN branches ON branches.branchcode=subscription.branchcode
418 WHERE subscription.biblionumber = ?
420 my $sth = $dbh->prepare($query);
421 $sth->execute($biblionumber);
422 my @res;
423 while ( my $subs = $sth->fetchrow_hashref ) {
424 $subs->{startdate} = output_pref( { dt => dt_from_string( $subs->{startdate} ), dateonly => 1 } );
425 $subs->{histstartdate} = output_pref( { dt => dt_from_string( $subs->{histstartdate} ), dateonly => 1 } );
426 if ( defined $subs->{histenddate} ) {
427 $subs->{histenddate} = output_pref( { dt => dt_from_string( $subs->{histenddate} ), dateonly => 1 } );
428 } else {
429 $subs->{histenddate} = "";
431 $subs->{opacnote} //= "";
432 $subs->{opacnote} =~ s/\n/\<br\/\>/g;
433 $subs->{missinglist} =~ s/\n/\<br\/\>/g;
434 $subs->{recievedlist} =~ s/\n/\<br\/\>/g;
435 $subs->{ "periodicity" . $subs->{periodicity} } = 1;
436 $subs->{ "numberpattern" . $subs->{numberpattern} } = 1;
437 $subs->{ "status" . $subs->{'status'} } = 1;
439 if (not defined $subs->{enddate} ) {
440 $subs->{enddate} = '';
441 } else {
442 $subs->{enddate} = output_pref( { dt => dt_from_string( $subs->{enddate}), dateonly => 1 } );
444 $subs->{'abouttoexpire'} = abouttoexpire( $subs->{'subscriptionid'} );
445 $subs->{'subscriptionexpired'} = HasSubscriptionExpired( $subs->{'subscriptionid'} );
446 $subs->{cannotedit} = not can_edit_subscription( $subs );
447 push @res, $subs;
449 return \@res;
452 =head2 GetFullSubscriptionsFromBiblionumber
454 $array_ref = GetFullSubscriptionsFromBiblionumber($biblionumber)
455 this function reads the serial table.
457 =cut
459 sub GetFullSubscriptionsFromBiblionumber {
460 my ($biblionumber) = @_;
461 my $dbh = C4::Context->dbh;
462 my $query = qq|
463 SELECT serial.serialid,
464 serial.serialseq,
465 serial.planneddate,
466 serial.publisheddate,
467 serial.publisheddatetext,
468 serial.status,
469 serial.notes as notes,
470 year(IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate)) as year,
471 biblio.title as bibliotitle,
472 subscription.branchcode AS branchcode,
473 subscription.subscriptionid AS subscriptionid
474 FROM serial
475 LEFT JOIN subscription ON
476 (serial.subscriptionid=subscription.subscriptionid)
477 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
478 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
479 WHERE subscription.biblionumber = ?
480 ORDER BY year DESC,
481 IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate) DESC,
482 serial.subscriptionid
484 my $sth = $dbh->prepare($query);
485 $sth->execute($biblionumber);
486 my $subscriptions = $sth->fetchall_arrayref( {} );
487 my $cannotedit = not can_edit_subscription( $subscriptions->[0] ) if scalar @$subscriptions;
488 for my $subscription ( @$subscriptions ) {
489 $subscription->{cannotedit} = $cannotedit;
491 return $subscriptions;
494 =head2 SearchSubscriptions
496 @results = SearchSubscriptions($args);
498 This function returns a list of hashrefs, one for each subscription
499 that meets the conditions specified by the $args hashref.
501 The valid search fields are:
503 biblionumber
504 title
505 issn
507 callnumber
508 location
509 publisher
510 bookseller
511 branch
512 expiration_date
513 closed
515 The expiration_date search field is special; it specifies the maximum
516 subscription expiration date.
518 =cut
520 sub SearchSubscriptions {
521 my ( $args ) = @_;
523 my $additional_fields = $args->{additional_fields} // [];
524 my $matching_record_ids_for_additional_fields = [];
525 if ( @$additional_fields ) {
526 my @subscriptions = Koha::Subscriptions->filter_by_additional_fields($additional_fields);
528 return () unless @subscriptions;
530 $matching_record_ids_for_additional_fields = [ map {
531 $_->subscriptionid
532 } @subscriptions ];
535 my $query = q|
536 SELECT
537 subscription.notes AS publicnotes,
538 subscriptionhistory.*,
539 subscription.*,
540 biblio.notes AS biblionotes,
541 biblio.title,
542 biblio.author,
543 biblio.biblionumber,
544 aqbooksellers.name AS vendorname,
545 biblioitems.issn
546 FROM subscription
547 LEFT JOIN subscriptionhistory USING(subscriptionid)
548 LEFT JOIN biblio ON biblio.biblionumber = subscription.biblionumber
549 LEFT JOIN biblioitems ON biblioitems.biblionumber = subscription.biblionumber
550 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
552 $query .= q| WHERE 1|;
553 my @where_strs;
554 my @where_args;
555 if( $args->{biblionumber} ) {
556 push @where_strs, "biblio.biblionumber = ?";
557 push @where_args, $args->{biblionumber};
560 if( $args->{title} ){
561 my @words = split / /, $args->{title};
562 my (@strs, @args);
563 foreach my $word (@words) {
564 push @strs, "biblio.title LIKE ?";
565 push @args, "%$word%";
567 if (@strs) {
568 push @where_strs, '(' . join (' AND ', @strs) . ')';
569 push @where_args, @args;
572 if( $args->{issn} ){
573 push @where_strs, "biblioitems.issn LIKE ?";
574 push @where_args, "%$args->{issn}%";
576 if( $args->{ean} ){
577 push @where_strs, "biblioitems.ean LIKE ?";
578 push @where_args, "%$args->{ean}%";
580 if ( $args->{callnumber} ) {
581 push @where_strs, "subscription.callnumber LIKE ?";
582 push @where_args, "%$args->{callnumber}%";
584 if( $args->{publisher} ){
585 push @where_strs, "biblioitems.publishercode LIKE ?";
586 push @where_args, "%$args->{publisher}%";
588 if( $args->{bookseller} ){
589 push @where_strs, "aqbooksellers.name LIKE ?";
590 push @where_args, "%$args->{bookseller}%";
592 if( $args->{branch} ){
593 push @where_strs, "subscription.branchcode = ?";
594 push @where_args, "$args->{branch}";
596 if ( $args->{location} ) {
597 push @where_strs, "subscription.location = ?";
598 push @where_args, "$args->{location}";
600 if ( $args->{expiration_date} ) {
601 push @where_strs, "subscription.enddate <= ?";
602 push @where_args, "$args->{expiration_date}";
604 if( defined $args->{closed} ){
605 push @where_strs, "subscription.closed = ?";
606 push @where_args, "$args->{closed}";
609 if(@where_strs){
610 $query .= ' AND ' . join(' AND ', @where_strs);
612 if ( @$additional_fields ) {
613 $query .= ' AND subscriptionid IN ('
614 . join( ', ', @$matching_record_ids_for_additional_fields )
615 . ')';
618 $query .= " ORDER BY " . $args->{orderby} if $args->{orderby};
620 my $dbh = C4::Context->dbh;
621 my $sth = $dbh->prepare($query);
622 $sth->execute(@where_args);
623 my $results = $sth->fetchall_arrayref( {} );
625 for my $subscription ( @$results ) {
626 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
627 $subscription->{cannotdisplay} = not can_show_subscription( $subscription );
629 my $subscription_object = Koha::Subscriptions->find($subscription->{subscriptionid});
630 $subscription->{additional_fields} = { map { $_->field->name => $_->value }
631 $subscription_object->additional_field_values->as_list };
635 return @$results;
639 =head2 GetSerials
641 ($totalissues,@serials) = GetSerials($subscriptionid);
642 this function gets every serial not arrived for a given subscription
643 as well as the number of issues registered in the database (all types)
644 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
646 FIXME: We should return \@serials.
648 =cut
650 sub GetSerials {
651 my ( $subscriptionid, $count ) = @_;
653 return unless $subscriptionid;
655 my $dbh = C4::Context->dbh;
657 # status = 2 is "arrived"
658 my $counter = 0;
659 $count = 5 unless ($count);
660 my @serials;
661 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES, NOT_ISSUED ) );
662 my $query = "SELECT serialid,serialseq, status, publisheddate,
663 publisheddatetext, planneddate,notes, routingnotes
664 FROM serial
665 WHERE subscriptionid = ? AND status NOT IN ( $statuses )
666 ORDER BY IF(publisheddate<>'0000-00-00',publisheddate,planneddate) DESC";
667 my $sth = $dbh->prepare($query);
668 $sth->execute($subscriptionid);
670 while ( my $line = $sth->fetchrow_hashref ) {
671 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
672 for my $datefield ( qw( planneddate publisheddate) ) {
673 if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
674 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
675 } else {
676 $line->{$datefield} = q{};
679 push @serials, $line;
682 # OK, now add the last 5 issues arrives/missing
683 $query = "SELECT serialid,serialseq, status, planneddate, publisheddate,
684 publisheddatetext, notes, routingnotes
685 FROM serial
686 WHERE subscriptionid = ?
687 AND status IN ( $statuses )
688 ORDER BY IF(publisheddate<>'0000-00-00',publisheddate,planneddate) DESC
690 $sth = $dbh->prepare($query);
691 $sth->execute($subscriptionid);
692 while ( ( my $line = $sth->fetchrow_hashref ) && $counter < $count ) {
693 $counter++;
694 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
695 for my $datefield ( qw( planneddate publisheddate) ) {
696 if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
697 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
698 } else {
699 $line->{$datefield} = q{};
703 push @serials, $line;
706 $query = "SELECT count(*) FROM serial WHERE subscriptionid=?";
707 $sth = $dbh->prepare($query);
708 $sth->execute($subscriptionid);
709 my ($totalissues) = $sth->fetchrow;
710 return ( $totalissues, @serials );
713 =head2 GetSerials2
715 @serials = GetSerials2($subscriptionid,$statuses);
716 this function returns every serial waited for a given subscription
717 as well as the number of issues registered in the database (all types)
718 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
720 $statuses is an arrayref of statuses and is mandatory.
722 =cut
724 sub GetSerials2 {
725 my ( $subscription, $statuses ) = @_;
727 return unless ($subscription and @$statuses);
729 my $dbh = C4::Context->dbh;
730 my $query = q|
731 SELECT serialid,serialseq, status, planneddate, publisheddate,
732 publisheddatetext, notes, routingnotes
733 FROM serial
734 WHERE subscriptionid=?
736 . q| AND status IN (| . join( ",", ('?') x @$statuses ) . q|)|
737 . q|
738 ORDER BY publisheddate,serialid DESC
740 $debug and warn "GetSerials2 query: $query";
741 my $sth = $dbh->prepare($query);
742 $sth->execute( $subscription, @$statuses );
743 my @serials;
745 while ( my $line = $sth->fetchrow_hashref ) {
746 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
747 # Format dates for display
748 for my $datefield ( qw( planneddate publisheddate ) ) {
749 if (!defined($line->{$datefield}) || $line->{$datefield} =~m/^00/) {
750 $line->{$datefield} = q{};
752 else {
753 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
756 push @serials, $line;
758 return @serials;
761 =head2 GetLatestSerials
763 \@serials = GetLatestSerials($subscriptionid,$limit)
764 get the $limit's latest serials arrived or missing for a given subscription
765 return :
766 a ref to an array which contains all of the latest serials stored into a hash.
768 =cut
770 sub GetLatestSerials {
771 my ( $subscriptionid, $limit ) = @_;
773 return unless ($subscriptionid and $limit);
775 my $dbh = C4::Context->dbh;
777 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES ) );
778 my $strsth = "SELECT serialid,serialseq, status, planneddate, publisheddate, notes
779 FROM serial
780 WHERE subscriptionid = ?
781 AND status IN ($statuses)
782 ORDER BY publisheddate DESC LIMIT 0,$limit
784 my $sth = $dbh->prepare($strsth);
785 $sth->execute($subscriptionid);
786 my @serials;
787 while ( my $line = $sth->fetchrow_hashref ) {
788 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
789 push @serials, $line;
792 return \@serials;
795 =head2 GetPreviousSerialid
797 $serialid = GetPreviousSerialid($subscriptionid, $nth)
798 get the $nth's previous serial for the given subscriptionid
799 return :
800 the serialid
802 =cut
804 sub GetPreviousSerialid {
805 my ( $subscriptionid, $nth ) = @_;
806 $nth ||= 1;
807 my $dbh = C4::Context->dbh;
808 my $return = undef;
810 # Status 2: Arrived
811 my $strsth = "SELECT serialid
812 FROM serial
813 WHERE subscriptionid = ?
814 AND status = 2
815 ORDER BY serialid DESC LIMIT $nth,1
817 my $sth = $dbh->prepare($strsth);
818 $sth->execute($subscriptionid);
819 my @serials;
820 my $line = $sth->fetchrow_hashref;
821 $return = $line->{'serialid'} if ($line);
823 return $return;
826 =head2 GetNextSeq
828 my (
829 $nextseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
830 $newinnerloop1, $newinnerloop2, $newinnerloop3
831 ) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate );
833 $subscription is a hashref containing all the attributes of the table
834 'subscription'.
835 $pattern is a hashref containing all the attributes of the table
836 'subscription_numberpatterns'.
837 $frequency is a hashref containing all the attributes of the table 'subscription_frequencies'
838 $planneddate is a date string in iso format.
839 This function get the next issue for the subscription given on input arg
841 =cut
843 sub GetNextSeq {
844 my ($subscription, $pattern, $frequency, $planneddate) = @_;
846 return unless ($subscription and $pattern);
848 my ( $newlastvalue1, $newlastvalue2, $newlastvalue3,
849 $newinnerloop1, $newinnerloop2, $newinnerloop3 );
850 my $count = 1;
852 if ($subscription->{'skip_serialseq'}) {
853 my @irreg = split /;/, $subscription->{'irregularity'};
854 if(@irreg > 0) {
855 my $irregularities = {};
856 $irregularities->{$_} = 1 foreach(@irreg);
857 my $issueno = GetFictiveIssueNumber($subscription, $planneddate, $frequency) + 1;
858 while($irregularities->{$issueno}) {
859 $count++;
860 $issueno++;
865 my $numberingmethod = $pattern->{numberingmethod};
866 my $calculated = "";
867 if ($numberingmethod) {
868 $calculated = $numberingmethod;
869 my $locale = $subscription->{locale};
870 $newlastvalue1 = $subscription->{lastvalue1} || 0;
871 $newlastvalue2 = $subscription->{lastvalue2} || 0;
872 $newlastvalue3 = $subscription->{lastvalue3} || 0;
873 $newinnerloop1 = $subscription->{innerloop1} || 0;
874 $newinnerloop2 = $subscription->{innerloop2} || 0;
875 $newinnerloop3 = $subscription->{innerloop3} || 0;
876 my %calc;
877 foreach(qw/X Y Z/) {
878 $calc{$_} = 1 if ($numberingmethod =~ /\{$_\}/);
881 for(my $i = 0; $i < $count; $i++) {
882 if($calc{'X'}) {
883 # check if we have to increase the new value.
884 $newinnerloop1 += 1;
885 if ($newinnerloop1 >= $pattern->{every1}) {
886 $newinnerloop1 = 0;
887 $newlastvalue1 += $pattern->{add1};
889 # reset counter if needed.
890 $newlastvalue1 = $pattern->{setto1} if ($newlastvalue1 > $pattern->{whenmorethan1});
892 if($calc{'Y'}) {
893 # check if we have to increase the new value.
894 $newinnerloop2 += 1;
895 if ($newinnerloop2 >= $pattern->{every2}) {
896 $newinnerloop2 = 0;
897 $newlastvalue2 += $pattern->{add2};
899 # reset counter if needed.
900 $newlastvalue2 = $pattern->{setto2} if ($newlastvalue2 > $pattern->{whenmorethan2});
902 if($calc{'Z'}) {
903 # check if we have to increase the new value.
904 $newinnerloop3 += 1;
905 if ($newinnerloop3 >= $pattern->{every3}) {
906 $newinnerloop3 = 0;
907 $newlastvalue3 += $pattern->{add3};
909 # reset counter if needed.
910 $newlastvalue3 = $pattern->{setto3} if ($newlastvalue3 > $pattern->{whenmorethan3});
913 if($calc{'X'}) {
914 my $newlastvalue1string = _numeration( $newlastvalue1, $pattern->{numbering1}, $locale );
915 $calculated =~ s/\{X\}/$newlastvalue1string/g;
917 if($calc{'Y'}) {
918 my $newlastvalue2string = _numeration( $newlastvalue2, $pattern->{numbering2}, $locale );
919 $calculated =~ s/\{Y\}/$newlastvalue2string/g;
921 if($calc{'Z'}) {
922 my $newlastvalue3string = _numeration( $newlastvalue3, $pattern->{numbering3}, $locale );
923 $calculated =~ s/\{Z\}/$newlastvalue3string/g;
927 return ($calculated,
928 $newlastvalue1, $newlastvalue2, $newlastvalue3,
929 $newinnerloop1, $newinnerloop2, $newinnerloop3);
932 =head2 GetSeq
934 $calculated = GetSeq($subscription, $pattern)
935 $subscription is a hashref containing all the attributes of the table 'subscription'
936 $pattern is a hashref containing all the attributes of the table 'subscription_numberpatterns'
937 this function transforms {X},{Y},{Z} to 150,0,0 for example.
938 return:
939 the sequence in string format
941 =cut
943 sub GetSeq {
944 my ($subscription, $pattern) = @_;
946 return unless ($subscription and $pattern);
948 my $locale = $subscription->{locale};
950 my $calculated = $pattern->{numberingmethod};
952 my $newlastvalue1 = $subscription->{'lastvalue1'} || 0;
953 $newlastvalue1 = _numeration($newlastvalue1, $pattern->{numbering1}, $locale) if ($pattern->{numbering1}); # reset counter if needed.
954 $calculated =~ s/\{X\}/$newlastvalue1/g;
956 my $newlastvalue2 = $subscription->{'lastvalue2'} || 0;
957 $newlastvalue2 = _numeration($newlastvalue2, $pattern->{numbering2}, $locale) if ($pattern->{numbering2}); # reset counter if needed.
958 $calculated =~ s/\{Y\}/$newlastvalue2/g;
960 my $newlastvalue3 = $subscription->{'lastvalue3'} || 0;
961 $newlastvalue3 = _numeration($newlastvalue3, $pattern->{numbering3}, $locale) if ($pattern->{numbering3}); # reset counter if needed.
962 $calculated =~ s/\{Z\}/$newlastvalue3/g;
963 return $calculated;
966 =head2 GetExpirationDate
968 $enddate = GetExpirationDate($subscriptionid, [$startdate])
970 this function return the next expiration date for a subscription given on input args.
972 return
973 the enddate or undef
975 =cut
977 sub GetExpirationDate {
978 my ( $subscriptionid, $startdate ) = @_;
980 return unless ($subscriptionid);
982 my $dbh = C4::Context->dbh;
983 my $subscription = GetSubscription($subscriptionid);
984 my $enddate;
986 # we don't do the same test if the subscription is based on X numbers or on X weeks/months
987 $enddate = $startdate || $subscription->{startdate};
988 my @date = split( /-/, $enddate );
990 return if ( scalar(@date) != 3 || not check_date(@date) );
992 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
993 if ( $frequency and $frequency->{unit} ) {
995 # If Not Irregular
996 if ( my $length = $subscription->{numberlength} ) {
998 #calculate the date of the last issue.
999 for ( my $i = 1 ; $i <= $length ; $i++ ) {
1000 $enddate = GetNextDate( $subscription, $enddate, $frequency );
1002 } elsif ( $subscription->{monthlength} ) {
1003 if ( $$subscription{startdate} ) {
1004 my @enddate = Add_Delta_YM( $date[0], $date[1], $date[2], 0, $subscription->{monthlength} );
1005 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1007 } elsif ( $subscription->{weeklength} ) {
1008 if ( $$subscription{startdate} ) {
1009 my @date = split( /-/, $subscription->{startdate} );
1010 my @enddate = Add_Delta_Days( $date[0], $date[1], $date[2], $subscription->{weeklength} * 7 );
1011 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1013 } else {
1014 $enddate = $subscription->{enddate};
1016 return $enddate;
1017 } else {
1018 return $subscription->{enddate};
1022 =head2 CountSubscriptionFromBiblionumber
1024 $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber)
1025 this returns a count of the subscriptions for a given biblionumber
1026 return :
1027 the number of subscriptions
1029 =cut
1031 sub CountSubscriptionFromBiblionumber {
1032 my ($biblionumber) = @_;
1034 return unless ($biblionumber);
1036 my $dbh = C4::Context->dbh;
1037 my $query = "SELECT count(*) FROM subscription WHERE biblionumber=?";
1038 my $sth = $dbh->prepare($query);
1039 $sth->execute($biblionumber);
1040 my $subscriptionsnumber = $sth->fetchrow;
1041 return $subscriptionsnumber;
1044 =head2 ModSubscriptionHistory
1046 ModSubscriptionHistory($subscriptionid,$histstartdate,$enddate,$recievedlist,$missinglist,$opacnote,$librariannote);
1048 this function modifies the history of a subscription. Put your new values on input arg.
1049 returns the number of rows affected
1051 =cut
1053 sub ModSubscriptionHistory {
1054 my ( $subscriptionid, $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote ) = @_;
1056 return unless ($subscriptionid);
1058 my $dbh = C4::Context->dbh;
1059 my $query = "UPDATE subscriptionhistory
1060 SET histstartdate=?,histenddate=?,recievedlist=?,missinglist=?,opacnote=?,librariannote=?
1061 WHERE subscriptionid=?
1063 my $sth = $dbh->prepare($query);
1064 $receivedlist =~ s/^; // if $receivedlist;
1065 $missinglist =~ s/^; // if $missinglist;
1066 $opacnote =~ s/^; // if $opacnote;
1067 $sth->execute( $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote, $subscriptionid );
1068 return $sth->rows;
1071 =head2 ModSerialStatus
1073 ModSerialStatus($serialid, $serialseq, $planneddate, $publisheddate,
1074 $publisheddatetext, $status, $notes);
1076 This function modify the serial status. Serial status is a number.(eg 2 is "arrived")
1077 Note : if we change from "waited" to something else,then we will have to create a new "waited" entry
1079 =cut
1081 sub ModSerialStatus {
1082 my ($serialid, $serialseq, $planneddate, $publisheddate, $publisheddatetext,
1083 $status, $notes) = @_;
1085 return unless ($serialid);
1087 #It is a usual serial
1088 # 1st, get previous status :
1089 my $dbh = C4::Context->dbh;
1090 my $query = "SELECT serial.subscriptionid,serial.status,subscription.periodicity,serial.routingnotes
1091 FROM serial, subscription
1092 WHERE serial.subscriptionid=subscription.subscriptionid
1093 AND serialid=?";
1094 my $sth = $dbh->prepare($query);
1095 $sth->execute($serialid);
1096 my ( $subscriptionid, $oldstatus, $periodicity, $routingnotes ) = $sth->fetchrow;
1097 my $frequency = GetSubscriptionFrequency($periodicity);
1099 # change status & update subscriptionhistory
1100 my $val;
1101 if ( $status == DELETED ) {
1102 DelIssue( { 'serialid' => $serialid, 'subscriptionid' => $subscriptionid, 'serialseq' => $serialseq } );
1103 } else {
1104 my $query = '
1105 UPDATE serial
1106 SET serialseq = ?, publisheddate = ?, publisheddatetext = ?,
1107 planneddate = ?, status = ?, notes = ?, routingnotes = ?
1108 WHERE serialid = ?
1110 $sth = $dbh->prepare($query);
1111 $sth->execute( $serialseq, $publisheddate, $publisheddatetext,
1112 $planneddate, $status, $notes, $routingnotes, $serialid );
1113 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1114 $sth = $dbh->prepare($query);
1115 $sth->execute($subscriptionid);
1116 my $val = $sth->fetchrow_hashref;
1117 unless ( $val->{manualhistory} ) {
1118 $query = "SELECT missinglist,recievedlist FROM subscriptionhistory WHERE subscriptionid=?";
1119 $sth = $dbh->prepare($query);
1120 $sth->execute($subscriptionid);
1121 my ( $missinglist, $recievedlist ) = $sth->fetchrow;
1123 if ( $status == ARRIVED || ($oldstatus == ARRIVED && $status != ARRIVED) ) {
1124 $recievedlist .= "; $serialseq"
1125 if ($recievedlist !~ /(^|;)\s*$serialseq(?=;|$)/);
1128 # in case serial has been previously marked as missing
1129 if (grep /$status/, (EXPECTED, ARRIVED, LATE, CLAIMED)) {
1130 $missinglist=~ s/(^|;)\s*$serialseq(?=;|$)//g;
1133 $missinglist .= "; $serialseq"
1134 if ( ( grep { $_ == $status } ( MISSING_STATUSES ) ) && ( $missinglist !~/(^|;)\s*$serialseq(?=;|$)/ ) );
1135 $missinglist .= "; not issued $serialseq"
1136 if ( $status == NOT_ISSUED && $missinglist !~ /(^|;)\s*$serialseq(?=;|$)/ );
1138 $query = "UPDATE subscriptionhistory SET recievedlist=?, missinglist=? WHERE subscriptionid=?";
1139 $sth = $dbh->prepare($query);
1140 $recievedlist =~ s/^; //;
1141 $missinglist =~ s/^; //;
1142 $sth->execute( $recievedlist, $missinglist, $subscriptionid );
1146 # create new expected entry if needed (ie : was "expected" and has changed)
1147 my $otherIssueExpected = scalar findSerialsByStatus(EXPECTED, $subscriptionid);
1148 if ( !$otherIssueExpected && $oldstatus == EXPECTED && $status != EXPECTED ) {
1149 my $subscription = GetSubscription($subscriptionid);
1150 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1151 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1153 # next issue number
1154 my (
1155 $newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
1156 $newinnerloop1, $newinnerloop2, $newinnerloop3
1158 = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate );
1160 # next date (calculated from actual date & frequency parameters)
1161 my $nextpublisheddate = GetNextDate($subscription, $publisheddate, $frequency, 1);
1162 my $nextpubdate = $nextpublisheddate;
1163 $query = "UPDATE subscription SET lastvalue1=?, lastvalue2=?, lastvalue3=?, innerloop1=?, innerloop2=?, innerloop3=?
1164 WHERE subscriptionid = ?";
1165 $sth = $dbh->prepare($query);
1166 $sth->execute( $newlastvalue1, $newlastvalue2, $newlastvalue3, $newinnerloop1, $newinnerloop2, $newinnerloop3, $subscriptionid );
1167 NewIssue( $newserialseq, $subscriptionid, $subscription->{'biblionumber'}, 1, $nextpubdate, $nextpubdate, undef, $notes, $routingnotes );
1168 # check if an alert must be sent... (= a letter is defined & status became "arrived"
1169 if ( $subscription->{letter} && $status == ARRIVED && $oldstatus != ARRIVED ) {
1170 require C4::Letters;
1171 C4::Letters::SendAlerts( 'issue', $serialid, $subscription->{letter} );
1175 return;
1178 =head2 GetNextExpected
1180 $nextexpected = GetNextExpected($subscriptionid)
1182 Get the planneddate for the current expected issue of the subscription.
1184 returns a hashref:
1186 $nextexepected = {
1187 serialid => int
1188 planneddate => ISO date
1191 =cut
1193 sub GetNextExpected {
1194 my ($subscriptionid) = @_;
1196 my $dbh = C4::Context->dbh;
1197 my $query = qq{
1198 SELECT *
1199 FROM serial
1200 WHERE subscriptionid = ?
1201 AND status = ?
1202 LIMIT 1
1204 my $sth = $dbh->prepare($query);
1206 # Each subscription has only one 'expected' issue.
1207 $sth->execute( $subscriptionid, EXPECTED );
1208 my $nextissue = $sth->fetchrow_hashref;
1209 if ( !$nextissue ) {
1210 $query = qq{
1211 SELECT *
1212 FROM serial
1213 WHERE subscriptionid = ?
1214 ORDER BY publisheddate DESC
1215 LIMIT 1
1217 $sth = $dbh->prepare($query);
1218 $sth->execute($subscriptionid);
1219 $nextissue = $sth->fetchrow_hashref;
1221 foreach(qw/planneddate publisheddate/) {
1222 if ( !defined $nextissue->{$_} ) {
1223 # or should this default to 1st Jan ???
1224 $nextissue->{$_} = strftime( '%Y-%m-%d', localtime );
1226 $nextissue->{$_} = ($nextissue->{$_} ne '0000-00-00')
1227 ? $nextissue->{$_}
1228 : undef;
1231 return $nextissue;
1234 =head2 ModNextExpected
1236 ModNextExpected($subscriptionid,$date)
1238 Update the planneddate for the current expected issue of the subscription.
1239 This will modify all future prediction results.
1241 C<$date> is an ISO date.
1243 returns 0
1245 =cut
1247 sub ModNextExpected {
1248 my ( $subscriptionid, $date ) = @_;
1249 my $dbh = C4::Context->dbh;
1251 #FIXME: Would expect to only set planneddate, but we set both on new issue creation, so updating it here
1252 my $sth = $dbh->prepare('UPDATE serial SET planneddate=?,publisheddate=? WHERE subscriptionid=? AND status=?');
1254 # Each subscription has only one 'expected' issue.
1255 $sth->execute( $date, $date, $subscriptionid, EXPECTED );
1256 return 0;
1260 =head2 GetSubscriptionIrregularities
1262 =over 4
1264 =item @irreg = &GetSubscriptionIrregularities($subscriptionid);
1265 get the list of irregularities for a subscription
1267 =back
1269 =cut
1271 sub GetSubscriptionIrregularities {
1272 my $subscriptionid = shift;
1274 return unless $subscriptionid;
1276 my $dbh = C4::Context->dbh;
1277 my $query = qq{
1278 SELECT irregularity
1279 FROM subscription
1280 WHERE subscriptionid = ?
1282 my $sth = $dbh->prepare($query);
1283 $sth->execute($subscriptionid);
1285 my ($result) = $sth->fetchrow_array;
1286 my @irreg = split /;/, $result;
1288 return @irreg;
1291 =head2 ModSubscription
1293 this function modifies a subscription. Put all new values on input args.
1294 returns the number of rows affected
1296 =cut
1298 sub ModSubscription {
1299 my (
1300 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
1301 $periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
1302 $numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
1303 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
1304 $biblionumber, $callnumber, $notes, $letter, $manualhistory,
1305 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1306 $graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
1307 $itemtype, $previousitemtype, $mana_id
1308 ) = @_;
1310 my $dbh = C4::Context->dbh;
1311 my $query = "UPDATE subscription
1312 SET librarian=?, branchcode=?, aqbooksellerid=?, cost=?, aqbudgetid=?,
1313 startdate=?, periodicity=?, firstacquidate=?, irregularity=?,
1314 numberpattern=?, locale=?, numberlength=?, weeklength=?, monthlength=?,
1315 lastvalue1=?, innerloop1=?, lastvalue2=?, innerloop2=?,
1316 lastvalue3=?, innerloop3=?, status=?, biblionumber=?,
1317 callnumber=?, notes=?, letter=?, manualhistory=?,
1318 internalnotes=?, serialsadditems=?, staffdisplaycount=?,
1319 opacdisplaycount=?, graceperiod=?, location = ?, enddate=?,
1320 skip_serialseq=?, itemtype=?, previousitemtype=?, mana_id=?
1321 WHERE subscriptionid = ?";
1323 my $sth = $dbh->prepare($query);
1324 $sth->execute(
1325 $auser, $branchcode, $aqbooksellerid, $cost,
1326 $aqbudgetid, $startdate, $periodicity, $firstacquidate,
1327 $irregularity, $numberpattern, $locale, $numberlength,
1328 $weeklength, $monthlength, $lastvalue1, $innerloop1,
1329 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3,
1330 $status, $biblionumber, $callnumber, $notes,
1331 $letter, ($manualhistory ? $manualhistory : 0),
1332 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1333 $graceperiod, $location, $enddate, $skip_serialseq,
1334 $itemtype, $previousitemtype, $mana_id,
1335 $subscriptionid
1337 my $rows = $sth->rows;
1339 logaction( "SERIAL", "MODIFY", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1340 return $rows;
1343 =head2 NewSubscription
1345 $subscriptionid = &NewSubscription($auser,branchcode,$aqbooksellerid,$cost,$aqbudgetid,$biblionumber,
1346 $startdate,$periodicity,$numberlength,$weeklength,$monthlength,
1347 $lastvalue1,$innerloop1,$lastvalue2,$innerloop2,$lastvalue3,$innerloop3,
1348 $status, $notes, $letter, $firstacquidate, $irregularity, $numberpattern,
1349 $locale, $callnumber, $manualhistory, $internalnotes, $serialsadditems,
1350 $staffdisplaycount, $opacdisplaycount, $graceperiod, $location, $enddate,
1351 $skip_serialseq, $itemtype, $previousitemtype);
1353 Create a new subscription with value given on input args.
1355 return :
1356 the id of this new subscription
1358 =cut
1360 sub NewSubscription {
1361 my (
1362 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1363 $startdate, $periodicity, $numberlength, $weeklength, $monthlength,
1364 $lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
1365 $innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
1366 $numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
1367 $serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
1368 $location, $enddate, $skip_serialseq, $itemtype, $previousitemtype, $mana_id
1369 ) = @_;
1370 my $dbh = C4::Context->dbh;
1372 my $subscription = Koha::Subscription->new(
1374 librarian => $auser,
1375 branchcode => $branchcode,
1376 aqbooksellerid => $aqbooksellerid,
1377 cost => $cost,
1378 aqbudgetid => $aqbudgetid,
1379 biblionumber => $biblionumber,
1380 startdate => $startdate,
1381 periodicity => $periodicity,
1382 numberlength => $numberlength,
1383 weeklength => $weeklength,
1384 monthlength => $monthlength,
1385 lastvalue1 => $lastvalue1,
1386 innerloop1 => $innerloop1,
1387 lastvalue2 => $lastvalue2,
1388 innerloop2 => $innerloop2,
1389 lastvalue3 => $lastvalue3,
1390 innerloop3 => $innerloop3,
1391 status => $status,
1392 notes => $notes,
1393 letter => $letter,
1394 firstacquidate => $firstacquidate,
1395 irregularity => $irregularity,
1396 numberpattern => $numberpattern,
1397 locale => $locale,
1398 callnumber => $callnumber,
1399 manualhistory => $manualhistory,
1400 internalnotes => $internalnotes,
1401 serialsadditems => $serialsadditems,
1402 staffdisplaycount => $staffdisplaycount,
1403 opacdisplaycount => $opacdisplaycount,
1404 graceperiod => $graceperiod,
1405 location => $location,
1406 enddate => $enddate,
1407 skip_serialseq => $skip_serialseq,
1408 itemtype => $itemtype,
1409 previousitemtype => $previousitemtype,
1410 mana_id => $mana_id,
1412 )->store;
1413 $subscription->discard_changes;
1414 my $subscriptionid = $subscription->subscriptionid;
1415 my ( $query, $sth );
1416 unless ($enddate) {
1417 $enddate = GetExpirationDate( $subscriptionid, $startdate );
1418 $query = qq|
1419 UPDATE subscription
1420 SET enddate=?
1421 WHERE subscriptionid=?
1423 $sth = $dbh->prepare($query);
1424 $sth->execute( $enddate, $subscriptionid );
1427 # then create the 1st expected number
1428 $query = qq(
1429 INSERT INTO subscriptionhistory
1430 (biblionumber, subscriptionid, histstartdate, missinglist, recievedlist)
1431 VALUES (?,?,?, '', '')
1433 $sth = $dbh->prepare($query);
1434 $sth->execute( $biblionumber, $subscriptionid, $startdate);
1436 # reread subscription to get a hash (for calculation of the 1st issue number)
1437 $subscription = GetSubscription($subscriptionid); # We should not do that
1438 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1440 # calculate issue number
1441 my $serialseq = GetSeq($subscription, $pattern) || q{};
1443 Koha::Serial->new(
1445 serialseq => $serialseq,
1446 serialseq_x => $subscription->{'lastvalue1'},
1447 serialseq_y => $subscription->{'lastvalue2'},
1448 serialseq_z => $subscription->{'lastvalue3'},
1449 subscriptionid => $subscriptionid,
1450 biblionumber => $biblionumber,
1451 status => EXPECTED,
1452 planneddate => $firstacquidate,
1453 publisheddate => $firstacquidate,
1455 )->store();
1457 logaction( "SERIAL", "ADD", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1459 #set serial flag on biblio if not already set.
1460 my $biblio = Koha::Biblios->find( $biblionumber );
1461 if ( $biblio and !$biblio->serial ) {
1462 my $record = GetMarcBiblio({ biblionumber => $biblionumber });
1463 my ( $tag, $subf ) = GetMarcFromKohaField( 'biblio.serial', $biblio->frameworkcode );
1464 if ($tag) {
1465 eval { $record->field($tag)->update( $subf => 1 ); };
1467 ModBiblio( $record, $biblionumber, $biblio->frameworkcode );
1469 return $subscriptionid;
1472 =head2 ReNewSubscription
1474 ReNewSubscription($subscriptionid,$user,$startdate,$numberlength,$weeklength,$monthlength,$note)
1476 this function renew a subscription with values given on input args.
1478 =cut
1480 sub ReNewSubscription {
1481 my ( $subscriptionid, $user, $startdate, $numberlength, $weeklength, $monthlength, $note ) = @_;
1482 my $dbh = C4::Context->dbh;
1483 my $subscription = GetSubscription($subscriptionid);
1484 my $query = qq|
1485 SELECT *
1486 FROM biblio
1487 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
1488 WHERE biblio.biblionumber=?
1490 my $sth = $dbh->prepare($query);
1491 $sth->execute( $subscription->{biblionumber} );
1492 my $biblio = $sth->fetchrow_hashref;
1494 if ( C4::Context->preference("RenewSerialAddsSuggestion") ) {
1495 require C4::Suggestions;
1496 C4::Suggestions::NewSuggestion(
1497 { 'suggestedby' => $user,
1498 'title' => $subscription->{bibliotitle},
1499 'author' => $biblio->{author},
1500 'publishercode' => $biblio->{publishercode},
1501 'note' => $biblio->{note},
1502 'biblionumber' => $subscription->{biblionumber}
1507 $numberlength ||= 0; # Should not we raise an exception instead?
1508 $weeklength ||= 0;
1510 # renew subscription
1511 $query = qq|
1512 UPDATE subscription
1513 SET startdate=?,numberlength=?,weeklength=?,monthlength=?,reneweddate=NOW()
1514 WHERE subscriptionid=?
1516 $sth = $dbh->prepare($query);
1517 $sth->execute( $startdate, $numberlength, $weeklength, $monthlength, $subscriptionid );
1518 my $enddate = GetExpirationDate($subscriptionid);
1519 $debug && warn "enddate :$enddate";
1520 $query = qq|
1521 UPDATE subscription
1522 SET enddate=?
1523 WHERE subscriptionid=?
1525 $sth = $dbh->prepare($query);
1526 $sth->execute( $enddate, $subscriptionid );
1528 logaction( "SERIAL", "RENEW", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1529 return;
1532 =head2 NewIssue
1534 NewIssue($serialseq,$subscriptionid,$biblionumber,$status, $planneddate, $publisheddate, $notes, $routingnotes)
1536 Create a new issue stored on the database.
1537 Note : we have to update the recievedlist and missinglist on subscriptionhistory for this subscription.
1538 returns the serial id
1540 =cut
1542 sub NewIssue {
1543 my ( $serialseq, $subscriptionid, $biblionumber, $status, $planneddate,
1544 $publisheddate, $publisheddatetext, $notes, $routingnotes ) = @_;
1545 ### FIXME biblionumber CAN be provided by subscriptionid. So Do we STILL NEED IT ?
1547 return unless ($subscriptionid);
1549 my $schema = Koha::Database->new()->schema();
1551 my $subscription = Koha::Subscriptions->find( $subscriptionid );
1553 my $serial = Koha::Serial->new(
1555 serialseq => $serialseq,
1556 serialseq_x => $subscription->lastvalue1(),
1557 serialseq_y => $subscription->lastvalue2(),
1558 serialseq_z => $subscription->lastvalue3(),
1559 subscriptionid => $subscriptionid,
1560 biblionumber => $biblionumber,
1561 status => $status,
1562 planneddate => $planneddate,
1563 publisheddate => $publisheddate,
1564 publisheddatetext => $publisheddatetext,
1565 notes => $notes,
1566 routingnotes => $routingnotes
1568 )->store();
1570 my $serialid = $serial->id();
1572 my $subscription_history = Koha::Subscription::Histories->find($subscriptionid);
1573 my $missinglist = $subscription_history->missinglist();
1574 my $recievedlist = $subscription_history->recievedlist();
1576 if ( $status == ARRIVED ) {
1577 ### TODO Add a feature that improves recognition and description.
1578 ### As such count (serialseq) i.e. : N18,2(N19),N20
1579 ### Would use substr and index But be careful to previous presence of ()
1580 $recievedlist .= "; $serialseq" unless ( index( $recievedlist, $serialseq ) > 0 );
1582 if ( grep { /^$status$/ } (MISSING_STATUSES) ) {
1583 $missinglist .= "; $serialseq" unless ( index( $missinglist, $serialseq ) > 0 );
1586 $recievedlist =~ s/^; //;
1587 $missinglist =~ s/^; //;
1589 $subscription_history->recievedlist($recievedlist);
1590 $subscription_history->missinglist($missinglist);
1591 $subscription_history->store();
1593 return $serialid;
1596 =head2 HasSubscriptionStrictlyExpired
1598 1 or 0 = HasSubscriptionStrictlyExpired($subscriptionid)
1600 the subscription has stricly expired when today > the end subscription date
1602 return :
1603 1 if true, 0 if false, -1 if the expiration date is not set.
1605 =cut
1607 sub HasSubscriptionStrictlyExpired {
1609 # Getting end of subscription date
1610 my ($subscriptionid) = @_;
1612 return unless ($subscriptionid);
1614 my $dbh = C4::Context->dbh;
1615 my $subscription = GetSubscription($subscriptionid);
1616 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1618 # If the expiration date is set
1619 if ( $expirationdate != 0 ) {
1620 my ( $endyear, $endmonth, $endday ) = split( '-', $expirationdate );
1622 # Getting today's date
1623 my ( $nowyear, $nowmonth, $nowday ) = Today();
1625 # if today's date > expiration date, then the subscription has stricly expired
1626 if ( Delta_Days( $nowyear, $nowmonth, $nowday, $endyear, $endmonth, $endday ) < 0 ) {
1627 return 1;
1628 } else {
1629 return 0;
1631 } else {
1633 # There are some cases where the expiration date is not set
1634 # As we can't determine if the subscription has expired on a date-basis,
1635 # we return -1;
1636 return -1;
1640 =head2 HasSubscriptionExpired
1642 $has_expired = HasSubscriptionExpired($subscriptionid)
1644 the subscription has expired when the next issue to arrive is out of subscription limit.
1646 return :
1647 0 if the subscription has not expired
1648 1 if the subscription has expired
1649 2 if has subscription does not have a valid expiration date set
1651 =cut
1653 sub HasSubscriptionExpired {
1654 my ($subscriptionid) = @_;
1656 return unless ($subscriptionid);
1658 my $dbh = C4::Context->dbh;
1659 my $subscription = GetSubscription($subscriptionid);
1660 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1661 if ( $frequency and $frequency->{unit} ) {
1662 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1663 if (!defined $expirationdate) {
1664 $expirationdate = q{};
1666 my $query = qq|
1667 SELECT max(planneddate)
1668 FROM serial
1669 WHERE subscriptionid=?
1671 my $sth = $dbh->prepare($query);
1672 $sth->execute($subscriptionid);
1673 my ($res) = $sth->fetchrow;
1674 if (!$res || $res=~m/^0000/) {
1675 return 0;
1677 my @res = split( /-/, $res );
1678 my @endofsubscriptiondate = split( /-/, $expirationdate );
1679 return 2 if ( scalar(@res) != 3 || scalar(@endofsubscriptiondate) != 3 || not check_date(@res) || not check_date(@endofsubscriptiondate) );
1680 return 1
1681 if ( ( @endofsubscriptiondate && Delta_Days( $res[0], $res[1], $res[2], $endofsubscriptiondate[0], $endofsubscriptiondate[1], $endofsubscriptiondate[2] ) <= 0 )
1682 || ( !$res ) );
1683 return 0;
1684 } else {
1685 # Irregular
1686 if ( $subscription->{'numberlength'} ) {
1687 my $countreceived = countissuesfrom( $subscriptionid, $subscription->{'startdate'} );
1688 return 1 if ( $countreceived > $subscription->{'numberlength'} );
1689 return 0;
1690 } else {
1691 return 0;
1694 return 0; # Notice that you'll never get here.
1697 =head2 DelSubscription
1699 DelSubscription($subscriptionid)
1700 this function deletes subscription which has $subscriptionid as id.
1702 =cut
1704 sub DelSubscription {
1705 my ($subscriptionid) = @_;
1706 my $dbh = C4::Context->dbh;
1707 $dbh->do("DELETE FROM subscription WHERE subscriptionid=?", undef, $subscriptionid);
1708 $dbh->do("DELETE FROM subscriptionhistory WHERE subscriptionid=?", undef, $subscriptionid);
1709 $dbh->do("DELETE FROM serial WHERE subscriptionid=?", undef, $subscriptionid);
1711 Koha::AdditionalFieldValues->search({
1712 'field.tablename' => 'subscription',
1713 'me.record_id' => $subscriptionid,
1714 }, { join => 'field' })->delete;
1716 logaction( "SERIAL", "DELETE", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1719 =head2 DelIssue
1721 DelIssue($serialseq,$subscriptionid)
1722 this function deletes an issue which has $serialseq and $subscriptionid given on input arg.
1724 returns the number of rows affected
1726 =cut
1728 sub DelIssue {
1729 my ($dataissue) = @_;
1730 my $dbh = C4::Context->dbh;
1731 ### TODO Add itemdeletion. Would need to get itemnumbers. Should be in a pref ?
1733 my $query = qq|
1734 DELETE FROM serial
1735 WHERE serialid= ?
1736 AND subscriptionid= ?
1738 my $mainsth = $dbh->prepare($query);
1739 $mainsth->execute( $dataissue->{'serialid'}, $dataissue->{'subscriptionid'} );
1741 #Delete element from subscription history
1742 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1743 my $sth = $dbh->prepare($query);
1744 $sth->execute( $dataissue->{'subscriptionid'} );
1745 my $val = $sth->fetchrow_hashref;
1746 unless ( $val->{manualhistory} ) {
1747 my $query = qq|
1748 SELECT * FROM subscriptionhistory
1749 WHERE subscriptionid= ?
1751 my $sth = $dbh->prepare($query);
1752 $sth->execute( $dataissue->{'subscriptionid'} );
1753 my $data = $sth->fetchrow_hashref;
1754 my $serialseq = $dataissue->{'serialseq'};
1755 $data->{'missinglist'} =~ s/\b$serialseq\b//;
1756 $data->{'recievedlist'} =~ s/\b$serialseq\b//;
1757 my $strsth = "UPDATE subscriptionhistory SET " . join( ",", map { join( "=", $_, $dbh->quote( $data->{$_} ) ) } keys %$data ) . " WHERE subscriptionid=?";
1758 $sth = $dbh->prepare($strsth);
1759 $sth->execute( $dataissue->{'subscriptionid'} );
1762 return $mainsth->rows;
1765 =head2 GetLateOrMissingIssues
1767 @issuelist = GetLateMissingIssues($supplierid,$serialid)
1769 this function selects missing issues on database - where serial.status = MISSING* or serial.status = LATE or planneddate<now
1771 return :
1772 the issuelist as an array of hash refs. Each element of this array contains
1773 name,title,planneddate,serialseq,serial.subscriptionid from tables : subscription, serial & biblio
1775 =cut
1777 sub GetLateOrMissingIssues {
1778 my ( $supplierid, $serialid, $order ) = @_;
1780 return unless ( $supplierid or $serialid );
1782 my $dbh = C4::Context->dbh;
1784 my $sth;
1785 my $byserial = '';
1786 if ($serialid) {
1787 $byserial = "and serialid = " . $serialid;
1789 if ($order) {
1790 $order .= ", title";
1791 } else {
1792 $order = "title";
1794 my $missing_statuses_string = join ',', (MISSING_STATUSES);
1795 if ($supplierid) {
1796 $sth = $dbh->prepare(
1797 "SELECT
1798 serialid, aqbooksellerid, name,
1799 biblio.title, biblioitems.issn, planneddate, serialseq,
1800 serial.status, serial.subscriptionid, claimdate, claims_count,
1801 subscription.branchcode
1802 FROM serial
1803 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1804 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1805 LEFT JOIN biblioitems ON subscription.biblionumber=biblioitems.biblionumber
1806 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1807 WHERE subscription.subscriptionid = serial.subscriptionid
1808 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1809 AND subscription.aqbooksellerid=$supplierid
1810 $byserial
1811 ORDER BY $order"
1813 } else {
1814 $sth = $dbh->prepare(
1815 "SELECT
1816 serialid, aqbooksellerid, name,
1817 biblio.title, planneddate, serialseq,
1818 serial.status, serial.subscriptionid, claimdate, claims_count,
1819 subscription.branchcode
1820 FROM serial
1821 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1822 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1823 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1824 WHERE subscription.subscriptionid = serial.subscriptionid
1825 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1826 $byserial
1827 ORDER BY $order"
1830 $sth->execute( EXPECTED, LATE, CLAIMED );
1831 my @issuelist;
1832 while ( my $line = $sth->fetchrow_hashref ) {
1834 if ($line->{planneddate} && $line->{planneddate} !~/^0+\-/) {
1835 $line->{planneddateISO} = $line->{planneddate};
1836 $line->{planneddate} = output_pref( { dt => dt_from_string( $line->{"planneddate"} ), dateonly => 1 } );
1838 if ($line->{claimdate} && $line->{claimdate} !~/^0+\-/) {
1839 $line->{claimdateISO} = $line->{claimdate};
1840 $line->{claimdate} = output_pref( { dt => dt_from_string( $line->{"claimdate"} ), dateonly => 1 } );
1842 $line->{"status".$line->{status}} = 1;
1844 my $subscription_object = Koha::Subscriptions->find($line->{subscriptionid});
1845 $line->{additional_fields} = { map { $_->field->name => $_->value }
1846 $subscription_object->additional_field_values->as_list };
1848 push @issuelist, $line;
1850 return @issuelist;
1853 =head2 updateClaim
1855 &updateClaim($serialid)
1857 this function updates the time when a claim is issued for late/missing items
1859 called from claims.pl file
1861 =cut
1863 sub updateClaim {
1864 my ($serialids) = @_;
1865 return unless $serialids;
1866 unless ( ref $serialids ) {
1867 $serialids = [ $serialids ];
1869 my $dbh = C4::Context->dbh;
1870 return $dbh->do(q|
1871 UPDATE serial
1872 SET claimdate = NOW(),
1873 claims_count = claims_count + 1,
1874 status = ?
1875 WHERE serialid in (| . join( q|,|, (q|?|) x @$serialids ) . q|)|,
1876 {}, CLAIMED, @$serialids );
1879 =head2 check_routing
1881 $result = &check_routing($subscriptionid)
1883 this function checks to see if a serial has a routing list and returns the count of routingid
1884 used to show either an 'add' or 'edit' link
1886 =cut
1888 sub check_routing {
1889 my ($subscriptionid) = @_;
1891 return unless ($subscriptionid);
1893 my $dbh = C4::Context->dbh;
1894 my $sth = $dbh->prepare(
1895 "SELECT count(routingid) routingids FROM subscription LEFT JOIN subscriptionroutinglist
1896 ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
1897 WHERE subscription.subscriptionid = ? GROUP BY routingid ORDER BY ranking ASC
1900 $sth->execute($subscriptionid);
1901 my $line = $sth->fetchrow_hashref;
1902 my $result = $line->{'routingids'};
1903 return $result;
1906 =head2 addroutingmember
1908 addroutingmember($borrowernumber,$subscriptionid)
1910 this function takes a borrowernumber and subscriptionid and adds the member to the
1911 routing list for that serial subscription and gives them a rank on the list
1912 of either 1 or highest current rank + 1
1914 =cut
1916 sub addroutingmember {
1917 my ( $borrowernumber, $subscriptionid ) = @_;
1919 return unless ($borrowernumber and $subscriptionid);
1921 my $rank;
1922 my $dbh = C4::Context->dbh;
1923 my $sth = $dbh->prepare( "SELECT max(ranking) rank FROM subscriptionroutinglist WHERE subscriptionid = ?" );
1924 $sth->execute($subscriptionid);
1925 while ( my $line = $sth->fetchrow_hashref ) {
1926 if ( $line->{'rank'} > 0 ) {
1927 $rank = $line->{'rank'} + 1;
1928 } else {
1929 $rank = 1;
1932 $sth = $dbh->prepare( "INSERT INTO subscriptionroutinglist (subscriptionid,borrowernumber,ranking) VALUES (?,?,?)" );
1933 $sth->execute( $subscriptionid, $borrowernumber, $rank );
1936 =head2 reorder_members
1938 reorder_members($subscriptionid,$routingid,$rank)
1940 this function is used to reorder the routing list
1942 it takes the routingid of the member one wants to re-rank and the rank it is to move to
1943 - it gets all members on list puts their routingid's into an array
1944 - removes the one in the array that is $routingid
1945 - then reinjects $routingid at point indicated by $rank
1946 - then update the database with the routingids in the new order
1948 =cut
1950 sub reorder_members {
1951 my ( $subscriptionid, $routingid, $rank ) = @_;
1952 my $dbh = C4::Context->dbh;
1953 my $sth = $dbh->prepare( "SELECT * FROM subscriptionroutinglist WHERE subscriptionid = ? ORDER BY ranking ASC" );
1954 $sth->execute($subscriptionid);
1955 my @result;
1956 while ( my $line = $sth->fetchrow_hashref ) {
1957 push( @result, $line->{'routingid'} );
1960 # To find the matching index
1961 my $i;
1962 my $key = -1; # to allow for 0 being a valid response
1963 for ( $i = 0 ; $i < @result ; $i++ ) {
1964 if ( $routingid == $result[$i] ) {
1965 $key = $i; # save the index
1966 last;
1970 # if index exists in array then move it to new position
1971 if ( $key > -1 && $rank > 0 ) {
1972 my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
1973 my $moving_item = splice( @result, $key, 1 );
1974 splice( @result, $new_rank, 0, $moving_item );
1976 for ( my $j = 0 ; $j < @result ; $j++ ) {
1977 my $sth = $dbh->prepare( "UPDATE subscriptionroutinglist SET ranking = '" . ( $j + 1 ) . "' WHERE routingid = '" . $result[$j] . "'" );
1978 $sth->execute;
1980 return;
1983 =head2 delroutingmember
1985 delroutingmember($routingid,$subscriptionid)
1987 this function either deletes one member from routing list if $routingid exists otherwise
1988 deletes all members from the routing list
1990 =cut
1992 sub delroutingmember {
1994 # if $routingid exists then deletes that row otherwise deletes all with $subscriptionid
1995 my ( $routingid, $subscriptionid ) = @_;
1996 my $dbh = C4::Context->dbh;
1997 if ($routingid) {
1998 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE routingid = ?");
1999 $sth->execute($routingid);
2000 reorder_members( $subscriptionid, $routingid );
2001 } else {
2002 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE subscriptionid = ?");
2003 $sth->execute($subscriptionid);
2005 return;
2008 =head2 getroutinglist
2010 @routinglist = getroutinglist($subscriptionid)
2012 this gets the info from the subscriptionroutinglist for $subscriptionid
2014 return :
2015 the routinglist as an array. Each element of the array contains a hash_ref containing
2016 routingid - a unique id, borrowernumber, ranking, and biblionumber of subscription
2018 =cut
2020 sub getroutinglist {
2021 my ($subscriptionid) = @_;
2022 my $dbh = C4::Context->dbh;
2023 my $sth = $dbh->prepare(
2024 'SELECT routingid, borrowernumber, ranking, biblionumber
2025 FROM subscription
2026 JOIN subscriptionroutinglist ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
2027 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC'
2029 $sth->execute($subscriptionid);
2030 my $routinglist = $sth->fetchall_arrayref({});
2031 return @{$routinglist};
2034 =head2 countissuesfrom
2036 $result = countissuesfrom($subscriptionid,$startdate)
2038 Returns a count of serial rows matching the given subsctiptionid
2039 with published date greater than startdate
2041 =cut
2043 sub countissuesfrom {
2044 my ( $subscriptionid, $startdate ) = @_;
2045 my $dbh = C4::Context->dbh;
2046 my $query = qq|
2047 SELECT count(*)
2048 FROM serial
2049 WHERE subscriptionid=?
2050 AND serial.publisheddate>?
2052 my $sth = $dbh->prepare($query);
2053 $sth->execute( $subscriptionid, $startdate );
2054 my ($countreceived) = $sth->fetchrow;
2055 return $countreceived;
2058 =head2 CountIssues
2060 $result = CountIssues($subscriptionid)
2062 Returns a count of serial rows matching the given subsctiptionid
2064 =cut
2066 sub CountIssues {
2067 my ($subscriptionid) = @_;
2068 my $dbh = C4::Context->dbh;
2069 my $query = qq|
2070 SELECT count(*)
2071 FROM serial
2072 WHERE subscriptionid=?
2074 my $sth = $dbh->prepare($query);
2075 $sth->execute($subscriptionid);
2076 my ($countreceived) = $sth->fetchrow;
2077 return $countreceived;
2080 =head2 HasItems
2082 $result = HasItems($subscriptionid)
2084 returns a count of items from serial matching the subscriptionid
2086 =cut
2088 sub HasItems {
2089 my ($subscriptionid) = @_;
2090 my $dbh = C4::Context->dbh;
2091 my $query = q|
2092 SELECT COUNT(serialitems.itemnumber)
2093 FROM serial
2094 LEFT JOIN serialitems USING(serialid)
2095 WHERE subscriptionid=? AND serialitems.serialid IS NOT NULL
2097 my $sth=$dbh->prepare($query);
2098 $sth->execute($subscriptionid);
2099 my ($countitems)=$sth->fetchrow_array();
2100 return $countitems;
2103 =head2 abouttoexpire
2105 $result = abouttoexpire($subscriptionid)
2107 this function alerts you to the penultimate issue for a serial subscription
2109 returns 1 - if this is the penultimate issue
2110 returns 0 - if not
2112 =cut
2114 sub abouttoexpire {
2115 my ($subscriptionid) = @_;
2116 my $dbh = C4::Context->dbh;
2117 my $subscription = GetSubscription($subscriptionid);
2118 my $per = $subscription->{'periodicity'};
2119 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($per);
2120 if ($frequency and $frequency->{unit}){
2122 my $expirationdate = GetExpirationDate($subscriptionid);
2124 my ($res) = $dbh->selectrow_array('select max(planneddate) from serial where subscriptionid = ?', undef, $subscriptionid);
2125 my $nextdate = GetNextDate($subscription, $res, $frequency);
2127 # only compare dates if both dates exist.
2128 if ($nextdate and $expirationdate) {
2129 if(Date::Calc::Delta_Days(
2130 split( /-/, $nextdate ),
2131 split( /-/, $expirationdate )
2132 ) <= 0) {
2133 return 1;
2137 } elsif ($subscription->{numberlength}>0) {
2138 return (countissuesfrom($subscriptionid,$subscription->{'startdate'}) >=$subscription->{numberlength}-1);
2141 return 0;
2144 =head2 GetFictiveIssueNumber
2146 $issueno = GetFictiveIssueNumber($subscription, $publishedate, $frequency);
2148 Get the position of the issue published at $publisheddate, considering the
2149 first issue (at firstacquidate) is at position 1, the next is at position 2, etc...
2150 This issuenumber doesn't take into account irregularities, so, for instance, if the 3rd
2151 issue is declared as 'irregular' (will be skipped at receipt), the next issue number
2152 will be 4, not 3. It's why it is called 'fictive'. It is NOT a serial seq, and is not
2153 depending on how many rows are in serial table.
2154 The issue number calculation is based on subscription frequency, first acquisition
2155 date, and $publisheddate.
2157 Returns undef when called for irregular frequencies.
2159 The routine is used to skip irregularities when calculating the next issue
2160 date (in GetNextDate) or the next issue number (in GetNextSeq).
2162 =cut
2164 sub GetFictiveIssueNumber {
2165 my ($subscription, $publisheddate, $frequency) = @_;
2167 my $unit = $frequency->{unit} ? lc $frequency->{'unit'} : undef;
2168 return if !$unit;
2169 my $issueno;
2171 my ( $year, $month, $day ) = split /-/, $publisheddate;
2172 my ( $fa_year, $fa_month, $fa_day ) = split /-/, $subscription->{'firstacquidate'};
2173 my $delta = _delta_units( [$fa_year, $fa_month, $fa_day], [$year, $month, $day], $unit );
2175 if( $frequency->{'unitsperissue'} == 1 ) {
2176 $issueno = $delta * $frequency->{'issuesperunit'} + $subscription->{'countissuesperunit'};
2177 } else { # issuesperunit == 1
2178 $issueno = 1 + int( $delta / $frequency->{'unitsperissue'} );
2180 return $issueno;
2183 sub _delta_units {
2184 my ( $date1, $date2, $unit ) = @_;
2185 # date1 and date2 are array refs in the form [ yy, mm, dd ]
2187 if( $unit eq 'day' ) {
2188 return Delta_Days( @$date1, @$date2 );
2189 } elsif( $unit eq 'week' ) {
2190 return int( Delta_Days( @$date1, @$date2 ) / 7 );
2193 # In case of months or years, this is a wrapper around N_Delta_YMD.
2194 # Note that N_Delta_YMD returns 29 days between e.g. 22-2-72 and 22-3-72
2195 # while we expect 1 month.
2196 my @delta = N_Delta_YMD( @$date1, @$date2 );
2197 if( $delta[2] > 27 ) {
2198 # Check if we could add a month
2199 my @jump = Add_Delta_YM( @$date1, $delta[0], 1 + $delta[1] );
2200 if( Delta_Days( @jump, @$date2 ) >= 0 ) {
2201 $delta[1]++;
2204 if( $delta[1] >= 12 ) {
2205 $delta[0]++;
2206 $delta[1] -= 12;
2208 # if unit is year, we only return full years
2209 return $unit eq 'month' ? $delta[0] * 12 + $delta[1] : $delta[0];
2212 sub _get_next_date_day {
2213 my ($subscription, $freqdata, $year, $month, $day) = @_;
2215 my @newissue; # ( yy, mm, dd )
2216 # We do not need $delta_days here, since it would be zero where used
2218 if( $freqdata->{issuesperunit} == 1 ) {
2219 # Add full days
2220 @newissue = Add_Delta_Days(
2221 $year, $month, $day, $freqdata->{"unitsperissue"} );
2222 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2223 # Add zero days
2224 @newissue = ( $year, $month, $day );
2225 $subscription->{countissuesperunit}++;
2226 } else {
2227 # We finished a cycle of issues within a unit.
2228 # No subtraction of zero needed, just add one day
2229 @newissue = Add_Delta_Days( $year, $month, $day, 1 );
2230 $subscription->{countissuesperunit} = 1;
2232 return @newissue;
2235 sub _get_next_date_week {
2236 my ($subscription, $freqdata, $year, $month, $day) = @_;
2238 my @newissue; # ( yy, mm, dd )
2239 my $delta_days = int( 7 / $freqdata->{issuesperunit} );
2241 if( $freqdata->{issuesperunit} == 1 ) {
2242 # Add full weeks (of 7 days)
2243 @newissue = Add_Delta_Days(
2244 $year, $month, $day, 7 * $freqdata->{"unitsperissue"} );
2245 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2246 # Add rounded number of days based on frequency.
2247 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2248 $subscription->{countissuesperunit}++;
2249 } else {
2250 # We finished a cycle of issues within a unit.
2251 # Subtract delta * (issues - 1), add 1 week
2252 @newissue = Add_Delta_Days( $year, $month, $day,
2253 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2254 @newissue = Add_Delta_Days( @newissue, 7 );
2255 $subscription->{countissuesperunit} = 1;
2257 return @newissue;
2260 sub _get_next_date_month {
2261 my ($subscription, $freqdata, $year, $month, $day) = @_;
2263 my @newissue; # ( yy, mm, dd )
2264 my $delta_days = int( 30 / $freqdata->{issuesperunit} );
2266 if( $freqdata->{issuesperunit} == 1 ) {
2267 # Add full months
2268 @newissue = Add_Delta_YM(
2269 $year, $month, $day, 0, $freqdata->{"unitsperissue"} );
2270 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2271 # Add rounded number of days based on frequency.
2272 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2273 $subscription->{countissuesperunit}++;
2274 } else {
2275 # We finished a cycle of issues within a unit.
2276 # Subtract delta * (issues - 1), add 1 month
2277 @newissue = Add_Delta_Days( $year, $month, $day,
2278 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2279 @newissue = Add_Delta_YM( @newissue, 0, 1 );
2280 $subscription->{countissuesperunit} = 1;
2282 return @newissue;
2285 sub _get_next_date_year {
2286 my ($subscription, $freqdata, $year, $month, $day) = @_;
2288 my @newissue; # ( yy, mm, dd )
2289 my $delta_days = int( 365 / $freqdata->{issuesperunit} );
2291 if( $freqdata->{issuesperunit} == 1 ) {
2292 # Add full years
2293 @newissue = Add_Delta_YM( $year, $month, $day, $freqdata->{"unitsperissue"}, 0 );
2294 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2295 # Add rounded number of days based on frequency.
2296 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2297 $subscription->{countissuesperunit}++;
2298 } else {
2299 # We finished a cycle of issues within a unit.
2300 # Subtract delta * (issues - 1), add 1 year
2301 @newissue = Add_Delta_Days( $year, $month, $day, -$delta_days * ($freqdata->{issuesperunit} - 1) );
2302 @newissue = Add_Delta_YM( @newissue, 1, 0 );
2303 $subscription->{countissuesperunit} = 1;
2305 return @newissue;
2308 =head2 GetNextDate
2310 $resultdate = GetNextDate($publisheddate,$subscription,$freqdata,$updatecount)
2312 this function it takes the publisheddate and will return the next issue's date
2313 and will skip dates if there exists an irregularity.
2314 $publisheddate has to be an ISO date
2315 $subscription is a hashref containing at least 'firstacquidate', 'irregularity', and 'countissuesperunit'
2316 $frequency is a hashref containing frequency informations
2317 $updatecount is a boolean value which, when set to true, update the 'countissuesperunit' in database
2318 - eg if periodicity is monthly and $publisheddate is 2007-02-10 but if March and April is to be
2319 skipped then the returned date will be 2007-05-10
2321 return :
2322 $resultdate - then next date in the sequence (ISO date)
2324 Return undef if subscription is irregular
2326 =cut
2328 sub GetNextDate {
2329 my ( $subscription, $publisheddate, $freqdata, $updatecount ) = @_;
2331 return unless $subscription and $publisheddate;
2334 if ($freqdata->{'unit'}) {
2335 my ( $year, $month, $day ) = split /-/, $publisheddate;
2337 # Process an irregularity Hash
2338 # Suppose that irregularities are stored in a string with this structure
2339 # irreg1;irreg2;irreg3
2340 # where irregX is the number of issue which will not be received
2341 # (the first issue takes the number 1, the 2nd the number 2 and so on)
2342 my %irregularities;
2343 if ( $subscription->{irregularity} ) {
2344 my @irreg = split /;/, $subscription->{'irregularity'} ;
2345 foreach my $irregularity (@irreg) {
2346 $irregularities{$irregularity} = 1;
2350 # Get the 'fictive' next issue number
2351 # It is used to check if next issue is an irregular issue.
2352 my $issueno = GetFictiveIssueNumber($subscription, $publisheddate, $freqdata) + 1;
2354 # Then get the next date
2355 my $unit = lc $freqdata->{'unit'};
2356 if ($unit eq 'day') {
2357 while ($irregularities{$issueno}) {
2358 ($year, $month, $day) = _get_next_date_day($subscription,
2359 $freqdata, $year, $month, $day);
2360 $issueno++;
2362 ($year, $month, $day) = _get_next_date_day($subscription, $freqdata,
2363 $year, $month, $day);
2365 elsif ($unit eq 'week') {
2366 while ($irregularities{$issueno}) {
2367 ($year, $month, $day) = _get_next_date_week($subscription,
2368 $freqdata, $year, $month, $day);
2369 $issueno++;
2371 ($year, $month, $day) = _get_next_date_week($subscription,
2372 $freqdata, $year, $month, $day);
2374 elsif ($unit eq 'month') {
2375 while ($irregularities{$issueno}) {
2376 ($year, $month, $day) = _get_next_date_month($subscription,
2377 $freqdata, $year, $month, $day);
2378 $issueno++;
2380 ($year, $month, $day) = _get_next_date_month($subscription,
2381 $freqdata, $year, $month, $day);
2383 elsif ($unit eq 'year') {
2384 while ($irregularities{$issueno}) {
2385 ($year, $month, $day) = _get_next_date_year($subscription,
2386 $freqdata, $year, $month, $day);
2387 $issueno++;
2389 ($year, $month, $day) = _get_next_date_year($subscription,
2390 $freqdata, $year, $month, $day);
2393 if ($updatecount){
2394 my $dbh = C4::Context->dbh;
2395 my $query = qq{
2396 UPDATE subscription
2397 SET countissuesperunit = ?
2398 WHERE subscriptionid = ?
2400 my $sth = $dbh->prepare($query);
2401 $sth->execute($subscription->{'countissuesperunit'}, $subscription->{'subscriptionid'});
2404 return sprintf("%04d-%02d-%02d", $year, $month, $day);
2408 =head2 _numeration
2410 $string = &_numeration($value,$num_type,$locale);
2412 _numeration returns the string corresponding to $value in the num_type
2413 num_type can take :
2414 -dayname
2415 -dayabrv
2416 -monthname
2417 -monthabrv
2418 -season
2419 -seasonabrv
2421 =cut
2423 sub _numeration {
2424 my ($value, $num_type, $locale) = @_;
2425 $value ||= 0;
2426 $num_type //= '';
2427 $locale ||= 'en';
2428 my $string;
2429 if ( $num_type =~ /^dayname$/ or $num_type =~ /^dayabrv$/ ) {
2430 # 1970-11-01 was a Sunday
2431 $value = $value % 7;
2432 my $dt = DateTime->new(
2433 year => 1970,
2434 month => 11,
2435 day => $value + 1,
2436 locale => $locale,
2438 $string = $num_type =~ /^dayname$/
2439 ? $dt->strftime("%A")
2440 : $dt->strftime("%a");
2441 } elsif ( $num_type =~ /^monthname$/ or $num_type =~ /^monthabrv$/ ) {
2442 $value = $value % 12;
2443 my $dt = DateTime->new(
2444 year => 1970,
2445 month => $value + 1,
2446 locale => $locale,
2448 $string = $num_type =~ /^monthname$/
2449 ? $dt->strftime("%B")
2450 : $dt->strftime("%b");
2451 } elsif ( $num_type =~ /^season$/ ) {
2452 my @seasons= qw( Spring Summer Fall Winter );
2453 $value = $value % 4;
2454 $string = $seasons[$value];
2455 } elsif ( $num_type =~ /^seasonabrv$/ ) {
2456 my @seasonsabrv= qw( Spr Sum Fal Win );
2457 $value = $value % 4;
2458 $string = $seasonsabrv[$value];
2459 } else {
2460 $string = $value;
2463 return $string;
2466 =head2 CloseSubscription
2468 Close a subscription given a subscriptionid
2470 =cut
2472 sub CloseSubscription {
2473 my ( $subscriptionid ) = @_;
2474 return unless $subscriptionid;
2475 my $dbh = C4::Context->dbh;
2476 my $sth = $dbh->prepare( q{
2477 UPDATE subscription
2478 SET closed = 1
2479 WHERE subscriptionid = ?
2480 } );
2481 $sth->execute( $subscriptionid );
2483 # Set status = missing when status = stopped
2484 $sth = $dbh->prepare( q{
2485 UPDATE serial
2486 SET status = ?
2487 WHERE subscriptionid = ?
2488 AND status = ?
2489 } );
2490 $sth->execute( STOPPED, $subscriptionid, EXPECTED );
2493 =head2 ReopenSubscription
2495 Reopen a subscription given a subscriptionid
2497 =cut
2499 sub ReopenSubscription {
2500 my ( $subscriptionid ) = @_;
2501 return unless $subscriptionid;
2502 my $dbh = C4::Context->dbh;
2503 my $sth = $dbh->prepare( q{
2504 UPDATE subscription
2505 SET closed = 0
2506 WHERE subscriptionid = ?
2507 } );
2508 $sth->execute( $subscriptionid );
2510 # Set status = expected when status = stopped
2511 $sth = $dbh->prepare( q{
2512 UPDATE serial
2513 SET status = ?
2514 WHERE subscriptionid = ?
2515 AND status = ?
2516 } );
2517 $sth->execute( EXPECTED, $subscriptionid, STOPPED );
2520 =head2 subscriptionCurrentlyOnOrder
2522 $bool = subscriptionCurrentlyOnOrder( $subscriptionid );
2524 Return 1 if subscription is currently on order else 0.
2526 =cut
2528 sub subscriptionCurrentlyOnOrder {
2529 my ( $subscriptionid ) = @_;
2530 my $dbh = C4::Context->dbh;
2531 my $query = qq|
2532 SELECT COUNT(*) FROM aqorders
2533 WHERE subscriptionid = ?
2534 AND datereceived IS NULL
2535 AND datecancellationprinted IS NULL
2537 my $sth = $dbh->prepare( $query );
2538 $sth->execute($subscriptionid);
2539 return $sth->fetchrow_array;
2542 =head2 can_claim_subscription
2544 $can = can_claim_subscription( $subscriptionid[, $userid] );
2546 Return 1 if the subscription can be claimed by the current logged user (or a given $userid), else 0.
2548 =cut
2550 sub can_claim_subscription {
2551 my ( $subscription, $userid ) = @_;
2552 return _can_do_on_subscription( $subscription, $userid, 'claim_serials' );
2555 =head2 can_edit_subscription
2557 $can = can_edit_subscription( $subscriptionid[, $userid] );
2559 Return 1 if the subscription can be edited by the current logged user (or a given $userid), else 0.
2561 =cut
2563 sub can_edit_subscription {
2564 my ( $subscription, $userid ) = @_;
2565 return _can_do_on_subscription( $subscription, $userid, 'edit_subscription' );
2568 =head2 can_show_subscription
2570 $can = can_show_subscription( $subscriptionid[, $userid] );
2572 Return 1 if the subscription can be shown by the current logged user (or a given $userid), else 0.
2574 =cut
2576 sub can_show_subscription {
2577 my ( $subscription, $userid ) = @_;
2578 return _can_do_on_subscription( $subscription, $userid, '*' );
2581 sub _can_do_on_subscription {
2582 my ( $subscription, $userid, $permission ) = @_;
2583 return 0 unless C4::Context->userenv;
2584 my $flags = C4::Context->userenv->{flags};
2585 $userid ||= C4::Context->userenv->{'id'};
2587 if ( C4::Context->preference('IndependentBranches') ) {
2588 return 1
2589 if C4::Context->IsSuperLibrarian()
2591 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2592 or (
2593 C4::Auth::haspermission( $userid,
2594 { serials => $permission } )
2595 and ( not defined $subscription->{branchcode}
2596 or $subscription->{branchcode} eq ''
2597 or $subscription->{branchcode} eq
2598 C4::Context->userenv->{'branch'} )
2601 else {
2602 return 1
2603 if C4::Context->IsSuperLibrarian()
2605 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2606 or C4::Auth::haspermission(
2607 $userid, { serials => $permission }
2611 return 0;
2614 =head2 findSerialsByStatus
2616 @serials = findSerialsByStatus($status, $subscriptionid);
2618 Returns an array of serials matching a given status and subscription id.
2620 =cut
2622 sub findSerialsByStatus {
2623 my ( $status, $subscriptionid ) = @_;
2624 my $dbh = C4::Context->dbh;
2625 my $query = q| SELECT * from serial
2626 WHERE status = ?
2627 AND subscriptionid = ?
2629 my $serials = $dbh->selectall_arrayref( $query, { Slice => {} }, $status, $subscriptionid );
2630 return @$serials;
2634 __END__
2636 =head1 AUTHOR
2638 Koha Development Team <http://koha-community.org/>
2640 =cut