Bug 22947: Markup error in OPAC preferences file
[koha.git] / C4 / Serials.pm
blob4eb23dfa9e0a0fc8e31fd893d7392b813a3afeae
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} =~ s/\n/\<br\/\>/g;
432 $subs->{missinglist} =~ s/\n/\<br\/\>/g;
433 $subs->{recievedlist} =~ s/\n/\<br\/\>/g;
434 $subs->{ "periodicity" . $subs->{periodicity} } = 1;
435 $subs->{ "numberpattern" . $subs->{numberpattern} } = 1;
436 $subs->{ "status" . $subs->{'status'} } = 1;
438 if (not defined $subs->{enddate} ) {
439 $subs->{enddate} = '';
440 } else {
441 $subs->{enddate} = output_pref( { dt => dt_from_string( $subs->{enddate}), dateonly => 1 } );
443 $subs->{'abouttoexpire'} = abouttoexpire( $subs->{'subscriptionid'} );
444 $subs->{'subscriptionexpired'} = HasSubscriptionExpired( $subs->{'subscriptionid'} );
445 $subs->{cannotedit} = not can_edit_subscription( $subs );
446 push @res, $subs;
448 return \@res;
451 =head2 GetFullSubscriptionsFromBiblionumber
453 $array_ref = GetFullSubscriptionsFromBiblionumber($biblionumber)
454 this function reads the serial table.
456 =cut
458 sub GetFullSubscriptionsFromBiblionumber {
459 my ($biblionumber) = @_;
460 my $dbh = C4::Context->dbh;
461 my $query = qq|
462 SELECT serial.serialid,
463 serial.serialseq,
464 serial.planneddate,
465 serial.publisheddate,
466 serial.publisheddatetext,
467 serial.status,
468 serial.notes as notes,
469 year(IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate)) as year,
470 biblio.title as bibliotitle,
471 subscription.branchcode AS branchcode,
472 subscription.subscriptionid AS subscriptionid
473 FROM serial
474 LEFT JOIN subscription ON
475 (serial.subscriptionid=subscription.subscriptionid)
476 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
477 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
478 WHERE subscription.biblionumber = ?
479 ORDER BY year DESC,
480 IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate) DESC,
481 serial.subscriptionid
483 my $sth = $dbh->prepare($query);
484 $sth->execute($biblionumber);
485 my $subscriptions = $sth->fetchall_arrayref( {} );
486 my $cannotedit = not can_edit_subscription( $subscriptions->[0] ) if scalar @$subscriptions;
487 for my $subscription ( @$subscriptions ) {
488 $subscription->{cannotedit} = $cannotedit;
490 return $subscriptions;
493 =head2 SearchSubscriptions
495 @results = SearchSubscriptions($args);
497 This function returns a list of hashrefs, one for each subscription
498 that meets the conditions specified by the $args hashref.
500 The valid search fields are:
502 biblionumber
503 title
504 issn
506 callnumber
507 location
508 publisher
509 bookseller
510 branch
511 expiration_date
512 closed
514 The expiration_date search field is special; it specifies the maximum
515 subscription expiration date.
517 =cut
519 sub SearchSubscriptions {
520 my ( $args ) = @_;
522 my $additional_fields = $args->{additional_fields} // [];
523 my $matching_record_ids_for_additional_fields = [];
524 if ( @$additional_fields ) {
525 my @subscriptions = Koha::Subscriptions->filter_by_additional_fields($additional_fields);
527 return () unless @subscriptions;
529 $matching_record_ids_for_additional_fields = [ map {
530 $_->subscriptionid
531 } @subscriptions ];
534 my $query = q|
535 SELECT
536 subscription.notes AS publicnotes,
537 subscriptionhistory.*,
538 subscription.*,
539 biblio.notes AS biblionotes,
540 biblio.title,
541 biblio.author,
542 biblio.biblionumber,
543 aqbooksellers.name AS vendorname,
544 biblioitems.issn
545 FROM subscription
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|;
552 my @where_strs;
553 my @where_args;
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};
561 my (@strs, @args);
562 foreach my $word (@words) {
563 push @strs, "biblio.title LIKE ?";
564 push @args, "%$word%";
566 if (@strs) {
567 push @where_strs, '(' . join (' AND ', @strs) . ')';
568 push @where_args, @args;
571 if( $args->{issn} ){
572 push @where_strs, "biblioitems.issn LIKE ?";
573 push @where_args, "%$args->{issn}%";
575 if( $args->{ean} ){
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}";
608 if(@where_strs){
609 $query .= ' AND ' . join(' AND ', @where_strs);
611 if ( @$additional_fields ) {
612 $query .= ' AND subscriptionid IN ('
613 . join( ', ', @$matching_record_ids_for_additional_fields )
614 . ')';
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 };
634 return @$results;
638 =head2 GetSerials
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.
647 =cut
649 sub GetSerials {
650 my ( $subscriptionid, $count ) = @_;
652 return unless $subscriptionid;
654 my $dbh = C4::Context->dbh;
656 # status = 2 is "arrived"
657 my $counter = 0;
658 $count = 5 unless ($count);
659 my @serials;
660 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES, NOT_ISSUED ) );
661 my $query = "SELECT serialid,serialseq, status, publisheddate,
662 publisheddatetext, planneddate,notes, routingnotes
663 FROM serial
664 WHERE subscriptionid = ? AND status NOT IN ( $statuses )
665 ORDER BY IF(publisheddate<>'0000-00-00',publisheddate,planneddate) 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 for my $datefield ( qw( planneddate publisheddate) ) {
672 if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
673 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
674 } else {
675 $line->{$datefield} = q{};
678 push @serials, $line;
681 # OK, now add the last 5 issues arrives/missing
682 $query = "SELECT serialid,serialseq, status, planneddate, publisheddate,
683 publisheddatetext, notes, routingnotes
684 FROM serial
685 WHERE subscriptionid = ?
686 AND status IN ( $statuses )
687 ORDER BY IF(publisheddate<>'0000-00-00',publisheddate,planneddate) DESC
689 $sth = $dbh->prepare($query);
690 $sth->execute($subscriptionid);
691 while ( ( my $line = $sth->fetchrow_hashref ) && $counter < $count ) {
692 $counter++;
693 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
694 for my $datefield ( qw( planneddate publisheddate) ) {
695 if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
696 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
697 } else {
698 $line->{$datefield} = q{};
702 push @serials, $line;
705 $query = "SELECT count(*) FROM serial WHERE subscriptionid=?";
706 $sth = $dbh->prepare($query);
707 $sth->execute($subscriptionid);
708 my ($totalissues) = $sth->fetchrow;
709 return ( $totalissues, @serials );
712 =head2 GetSerials2
714 @serials = GetSerials2($subscriptionid,$statuses);
715 this function returns every serial waited for a given subscription
716 as well as the number of issues registered in the database (all types)
717 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
719 $statuses is an arrayref of statuses and is mandatory.
721 =cut
723 sub GetSerials2 {
724 my ( $subscription, $statuses ) = @_;
726 return unless ($subscription and @$statuses);
728 my $dbh = C4::Context->dbh;
729 my $query = q|
730 SELECT serialid,serialseq, status, planneddate, publisheddate,
731 publisheddatetext, notes, routingnotes
732 FROM serial
733 WHERE subscriptionid=?
735 . q| AND status IN (| . join( ",", ('?') x @$statuses ) . q|)|
736 . q|
737 ORDER BY publisheddate,serialid DESC
739 $debug and warn "GetSerials2 query: $query";
740 my $sth = $dbh->prepare($query);
741 $sth->execute( $subscription, @$statuses );
742 my @serials;
744 while ( my $line = $sth->fetchrow_hashref ) {
745 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
746 # Format dates for display
747 for my $datefield ( qw( planneddate publisheddate ) ) {
748 if (!defined($line->{$datefield}) || $line->{$datefield} =~m/^00/) {
749 $line->{$datefield} = q{};
751 else {
752 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
755 push @serials, $line;
757 return @serials;
760 =head2 GetLatestSerials
762 \@serials = GetLatestSerials($subscriptionid,$limit)
763 get the $limit's latest serials arrived or missing for a given subscription
764 return :
765 a ref to an array which contains all of the latest serials stored into a hash.
767 =cut
769 sub GetLatestSerials {
770 my ( $subscriptionid, $limit ) = @_;
772 return unless ($subscriptionid and $limit);
774 my $dbh = C4::Context->dbh;
776 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES ) );
777 my $strsth = "SELECT serialid,serialseq, status, planneddate, publisheddate, notes
778 FROM serial
779 WHERE subscriptionid = ?
780 AND status IN ($statuses)
781 ORDER BY publisheddate DESC LIMIT 0,$limit
783 my $sth = $dbh->prepare($strsth);
784 $sth->execute($subscriptionid);
785 my @serials;
786 while ( my $line = $sth->fetchrow_hashref ) {
787 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
788 push @serials, $line;
791 return \@serials;
794 =head2 GetPreviousSerialid
796 $serialid = GetPreviousSerialid($subscriptionid, $nth)
797 get the $nth's previous serial for the given subscriptionid
798 return :
799 the serialid
801 =cut
803 sub GetPreviousSerialid {
804 my ( $subscriptionid, $nth ) = @_;
805 $nth ||= 1;
806 my $dbh = C4::Context->dbh;
807 my $return = undef;
809 # Status 2: Arrived
810 my $strsth = "SELECT serialid
811 FROM serial
812 WHERE subscriptionid = ?
813 AND status = 2
814 ORDER BY serialid DESC LIMIT $nth,1
816 my $sth = $dbh->prepare($strsth);
817 $sth->execute($subscriptionid);
818 my @serials;
819 my $line = $sth->fetchrow_hashref;
820 $return = $line->{'serialid'} if ($line);
822 return $return;
825 =head2 GetNextSeq
827 my (
828 $nextseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
829 $newinnerloop1, $newinnerloop2, $newinnerloop3
830 ) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate );
832 $subscription is a hashref containing all the attributes of the table
833 'subscription'.
834 $pattern is a hashref containing all the attributes of the table
835 'subscription_numberpatterns'.
836 $frequency is a hashref containing all the attributes of the table 'subscription_frequencies'
837 $planneddate is a date string in iso format.
838 This function get the next issue for the subscription given on input arg
840 =cut
842 sub GetNextSeq {
843 my ($subscription, $pattern, $frequency, $planneddate) = @_;
845 return unless ($subscription and $pattern);
847 my ( $newlastvalue1, $newlastvalue2, $newlastvalue3,
848 $newinnerloop1, $newinnerloop2, $newinnerloop3 );
849 my $count = 1;
851 if ($subscription->{'skip_serialseq'}) {
852 my @irreg = split /;/, $subscription->{'irregularity'};
853 if(@irreg > 0) {
854 my $irregularities = {};
855 $irregularities->{$_} = 1 foreach(@irreg);
856 my $issueno = GetFictiveIssueNumber($subscription, $planneddate, $frequency) + 1;
857 while($irregularities->{$issueno}) {
858 $count++;
859 $issueno++;
864 my $numberingmethod = $pattern->{numberingmethod};
865 my $calculated = "";
866 if ($numberingmethod) {
867 $calculated = $numberingmethod;
868 my $locale = $subscription->{locale};
869 $newlastvalue1 = $subscription->{lastvalue1} || 0;
870 $newlastvalue2 = $subscription->{lastvalue2} || 0;
871 $newlastvalue3 = $subscription->{lastvalue3} || 0;
872 $newinnerloop1 = $subscription->{innerloop1} || 0;
873 $newinnerloop2 = $subscription->{innerloop2} || 0;
874 $newinnerloop3 = $subscription->{innerloop3} || 0;
875 my %calc;
876 foreach(qw/X Y Z/) {
877 $calc{$_} = 1 if ($numberingmethod =~ /\{$_\}/);
880 for(my $i = 0; $i < $count; $i++) {
881 if($calc{'X'}) {
882 # check if we have to increase the new value.
883 $newinnerloop1 += 1;
884 if ($newinnerloop1 >= $pattern->{every1}) {
885 $newinnerloop1 = 0;
886 $newlastvalue1 += $pattern->{add1};
888 # reset counter if needed.
889 $newlastvalue1 = $pattern->{setto1} if ($newlastvalue1 > $pattern->{whenmorethan1});
891 if($calc{'Y'}) {
892 # check if we have to increase the new value.
893 $newinnerloop2 += 1;
894 if ($newinnerloop2 >= $pattern->{every2}) {
895 $newinnerloop2 = 0;
896 $newlastvalue2 += $pattern->{add2};
898 # reset counter if needed.
899 $newlastvalue2 = $pattern->{setto2} if ($newlastvalue2 > $pattern->{whenmorethan2});
901 if($calc{'Z'}) {
902 # check if we have to increase the new value.
903 $newinnerloop3 += 1;
904 if ($newinnerloop3 >= $pattern->{every3}) {
905 $newinnerloop3 = 0;
906 $newlastvalue3 += $pattern->{add3};
908 # reset counter if needed.
909 $newlastvalue3 = $pattern->{setto3} if ($newlastvalue3 > $pattern->{whenmorethan3});
912 if($calc{'X'}) {
913 my $newlastvalue1string = _numeration( $newlastvalue1, $pattern->{numbering1}, $locale );
914 $calculated =~ s/\{X\}/$newlastvalue1string/g;
916 if($calc{'Y'}) {
917 my $newlastvalue2string = _numeration( $newlastvalue2, $pattern->{numbering2}, $locale );
918 $calculated =~ s/\{Y\}/$newlastvalue2string/g;
920 if($calc{'Z'}) {
921 my $newlastvalue3string = _numeration( $newlastvalue3, $pattern->{numbering3}, $locale );
922 $calculated =~ s/\{Z\}/$newlastvalue3string/g;
926 return ($calculated,
927 $newlastvalue1, $newlastvalue2, $newlastvalue3,
928 $newinnerloop1, $newinnerloop2, $newinnerloop3);
931 =head2 GetSeq
933 $calculated = GetSeq($subscription, $pattern)
934 $subscription is a hashref containing all the attributes of the table 'subscription'
935 $pattern is a hashref containing all the attributes of the table 'subscription_numberpatterns'
936 this function transforms {X},{Y},{Z} to 150,0,0 for example.
937 return:
938 the sequence in string format
940 =cut
942 sub GetSeq {
943 my ($subscription, $pattern) = @_;
945 return unless ($subscription and $pattern);
947 my $locale = $subscription->{locale};
949 my $calculated = $pattern->{numberingmethod};
951 my $newlastvalue1 = $subscription->{'lastvalue1'} || 0;
952 $newlastvalue1 = _numeration($newlastvalue1, $pattern->{numbering1}, $locale) if ($pattern->{numbering1}); # reset counter if needed.
953 $calculated =~ s/\{X\}/$newlastvalue1/g;
955 my $newlastvalue2 = $subscription->{'lastvalue2'} || 0;
956 $newlastvalue2 = _numeration($newlastvalue2, $pattern->{numbering2}, $locale) if ($pattern->{numbering2}); # reset counter if needed.
957 $calculated =~ s/\{Y\}/$newlastvalue2/g;
959 my $newlastvalue3 = $subscription->{'lastvalue3'} || 0;
960 $newlastvalue3 = _numeration($newlastvalue3, $pattern->{numbering3}, $locale) if ($pattern->{numbering3}); # reset counter if needed.
961 $calculated =~ s/\{Z\}/$newlastvalue3/g;
962 return $calculated;
965 =head2 GetExpirationDate
967 $enddate = GetExpirationDate($subscriptionid, [$startdate])
969 this function return the next expiration date for a subscription given on input args.
971 return
972 the enddate or undef
974 =cut
976 sub GetExpirationDate {
977 my ( $subscriptionid, $startdate ) = @_;
979 return unless ($subscriptionid);
981 my $dbh = C4::Context->dbh;
982 my $subscription = GetSubscription($subscriptionid);
983 my $enddate;
985 # we don't do the same test if the subscription is based on X numbers or on X weeks/months
986 $enddate = $startdate || $subscription->{startdate};
987 my @date = split( /-/, $enddate );
989 return if ( scalar(@date) != 3 || not check_date(@date) );
991 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
992 if ( $frequency and $frequency->{unit} ) {
994 # If Not Irregular
995 if ( my $length = $subscription->{numberlength} ) {
997 #calculate the date of the last issue.
998 for ( my $i = 1 ; $i <= $length ; $i++ ) {
999 $enddate = GetNextDate( $subscription, $enddate, $frequency );
1001 } elsif ( $subscription->{monthlength} ) {
1002 if ( $$subscription{startdate} ) {
1003 my @enddate = Add_Delta_YM( $date[0], $date[1], $date[2], 0, $subscription->{monthlength} );
1004 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1006 } elsif ( $subscription->{weeklength} ) {
1007 if ( $$subscription{startdate} ) {
1008 my @date = split( /-/, $subscription->{startdate} );
1009 my @enddate = Add_Delta_Days( $date[0], $date[1], $date[2], $subscription->{weeklength} * 7 );
1010 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1012 } else {
1013 $enddate = $subscription->{enddate};
1015 return $enddate;
1016 } else {
1017 return $subscription->{enddate};
1021 =head2 CountSubscriptionFromBiblionumber
1023 $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber)
1024 this returns a count of the subscriptions for a given biblionumber
1025 return :
1026 the number of subscriptions
1028 =cut
1030 sub CountSubscriptionFromBiblionumber {
1031 my ($biblionumber) = @_;
1033 return unless ($biblionumber);
1035 my $dbh = C4::Context->dbh;
1036 my $query = "SELECT count(*) FROM subscription WHERE biblionumber=?";
1037 my $sth = $dbh->prepare($query);
1038 $sth->execute($biblionumber);
1039 my $subscriptionsnumber = $sth->fetchrow;
1040 return $subscriptionsnumber;
1043 =head2 ModSubscriptionHistory
1045 ModSubscriptionHistory($subscriptionid,$histstartdate,$enddate,$recievedlist,$missinglist,$opacnote,$librariannote);
1047 this function modifies the history of a subscription. Put your new values on input arg.
1048 returns the number of rows affected
1050 =cut
1052 sub ModSubscriptionHistory {
1053 my ( $subscriptionid, $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote ) = @_;
1055 return unless ($subscriptionid);
1057 my $dbh = C4::Context->dbh;
1058 my $query = "UPDATE subscriptionhistory
1059 SET histstartdate=?,histenddate=?,recievedlist=?,missinglist=?,opacnote=?,librariannote=?
1060 WHERE subscriptionid=?
1062 my $sth = $dbh->prepare($query);
1063 $receivedlist =~ s/^; // if $receivedlist;
1064 $missinglist =~ s/^; // if $missinglist;
1065 $opacnote =~ s/^; // if $opacnote;
1066 $sth->execute( $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote, $subscriptionid );
1067 return $sth->rows;
1070 =head2 ModSerialStatus
1072 ModSerialStatus($serialid, $serialseq, $planneddate, $publisheddate,
1073 $publisheddatetext, $status, $notes);
1075 This function modify the serial status. Serial status is a number.(eg 2 is "arrived")
1076 Note : if we change from "waited" to something else,then we will have to create a new "waited" entry
1078 =cut
1080 sub ModSerialStatus {
1081 my ($serialid, $serialseq, $planneddate, $publisheddate, $publisheddatetext,
1082 $status, $notes) = @_;
1084 return unless ($serialid);
1086 #It is a usual serial
1087 # 1st, get previous status :
1088 my $dbh = C4::Context->dbh;
1089 my $query = "SELECT serial.subscriptionid,serial.status,subscription.periodicity
1090 FROM serial, subscription
1091 WHERE serial.subscriptionid=subscription.subscriptionid
1092 AND serialid=?";
1093 my $sth = $dbh->prepare($query);
1094 $sth->execute($serialid);
1095 my ( $subscriptionid, $oldstatus, $periodicity ) = $sth->fetchrow;
1096 my $frequency = GetSubscriptionFrequency($periodicity);
1098 # change status & update subscriptionhistory
1099 my $val;
1100 if ( $status == DELETED ) {
1101 DelIssue( { 'serialid' => $serialid, 'subscriptionid' => $subscriptionid, 'serialseq' => $serialseq } );
1102 } else {
1104 my $query = '
1105 UPDATE serial
1106 SET serialseq = ?, publisheddate = ?, publisheddatetext = ?,
1107 planneddate = ?, status = ?, notes = ?
1108 WHERE serialid = ?
1110 $sth = $dbh->prepare($query);
1111 $sth->execute( $serialseq, $publisheddate, $publisheddatetext,
1112 $planneddate, $status, $notes, $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 );
1168 NewIssue( $newserialseq, $subscriptionid, $subscription->{'biblionumber'}, 1, $nextpubdate, $nextpubdate );
1170 # check if an alert must be sent... (= a letter is defined & status became "arrived"
1171 if ( $subscription->{letter} && $status == ARRIVED && $oldstatus != ARRIVED ) {
1172 require C4::Letters;
1173 C4::Letters::SendAlerts( 'issue', $serialid, $subscription->{letter} );
1177 return;
1180 =head2 GetNextExpected
1182 $nextexpected = GetNextExpected($subscriptionid)
1184 Get the planneddate for the current expected issue of the subscription.
1186 returns a hashref:
1188 $nextexepected = {
1189 serialid => int
1190 planneddate => ISO date
1193 =cut
1195 sub GetNextExpected {
1196 my ($subscriptionid) = @_;
1198 my $dbh = C4::Context->dbh;
1199 my $query = qq{
1200 SELECT *
1201 FROM serial
1202 WHERE subscriptionid = ?
1203 AND status = ?
1204 LIMIT 1
1206 my $sth = $dbh->prepare($query);
1208 # Each subscription has only one 'expected' issue.
1209 $sth->execute( $subscriptionid, EXPECTED );
1210 my $nextissue = $sth->fetchrow_hashref;
1211 if ( !$nextissue ) {
1212 $query = qq{
1213 SELECT *
1214 FROM serial
1215 WHERE subscriptionid = ?
1216 ORDER BY publisheddate DESC
1217 LIMIT 1
1219 $sth = $dbh->prepare($query);
1220 $sth->execute($subscriptionid);
1221 $nextissue = $sth->fetchrow_hashref;
1223 foreach(qw/planneddate publisheddate/) {
1224 if ( !defined $nextissue->{$_} ) {
1225 # or should this default to 1st Jan ???
1226 $nextissue->{$_} = strftime( '%Y-%m-%d', localtime );
1228 $nextissue->{$_} = ($nextissue->{$_} ne '0000-00-00')
1229 ? $nextissue->{$_}
1230 : undef;
1233 return $nextissue;
1236 =head2 ModNextExpected
1238 ModNextExpected($subscriptionid,$date)
1240 Update the planneddate for the current expected issue of the subscription.
1241 This will modify all future prediction results.
1243 C<$date> is an ISO date.
1245 returns 0
1247 =cut
1249 sub ModNextExpected {
1250 my ( $subscriptionid, $date ) = @_;
1251 my $dbh = C4::Context->dbh;
1253 #FIXME: Would expect to only set planneddate, but we set both on new issue creation, so updating it here
1254 my $sth = $dbh->prepare('UPDATE serial SET planneddate=?,publisheddate=? WHERE subscriptionid=? AND status=?');
1256 # Each subscription has only one 'expected' issue.
1257 $sth->execute( $date, $date, $subscriptionid, EXPECTED );
1258 return 0;
1262 =head2 GetSubscriptionIrregularities
1264 =over 4
1266 =item @irreg = &GetSubscriptionIrregularities($subscriptionid);
1267 get the list of irregularities for a subscription
1269 =back
1271 =cut
1273 sub GetSubscriptionIrregularities {
1274 my $subscriptionid = shift;
1276 return unless $subscriptionid;
1278 my $dbh = C4::Context->dbh;
1279 my $query = qq{
1280 SELECT irregularity
1281 FROM subscription
1282 WHERE subscriptionid = ?
1284 my $sth = $dbh->prepare($query);
1285 $sth->execute($subscriptionid);
1287 my ($result) = $sth->fetchrow_array;
1288 my @irreg = split /;/, $result;
1290 return @irreg;
1293 =head2 ModSubscription
1295 this function modifies a subscription. Put all new values on input args.
1296 returns the number of rows affected
1298 =cut
1300 sub ModSubscription {
1301 my (
1302 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
1303 $periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
1304 $numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
1305 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
1306 $biblionumber, $callnumber, $notes, $letter, $manualhistory,
1307 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1308 $graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
1309 $itemtype, $previousitemtype, $mana_id
1310 ) = @_;
1312 my $dbh = C4::Context->dbh;
1313 my $query = "UPDATE subscription
1314 SET librarian=?, branchcode=?, aqbooksellerid=?, cost=?, aqbudgetid=?,
1315 startdate=?, periodicity=?, firstacquidate=?, irregularity=?,
1316 numberpattern=?, locale=?, numberlength=?, weeklength=?, monthlength=?,
1317 lastvalue1=?, innerloop1=?, lastvalue2=?, innerloop2=?,
1318 lastvalue3=?, innerloop3=?, status=?, biblionumber=?,
1319 callnumber=?, notes=?, letter=?, manualhistory=?,
1320 internalnotes=?, serialsadditems=?, staffdisplaycount=?,
1321 opacdisplaycount=?, graceperiod=?, location = ?, enddate=?,
1322 skip_serialseq=?, itemtype=?, previousitemtype=?, mana_id=?
1323 WHERE subscriptionid = ?";
1325 my $sth = $dbh->prepare($query);
1326 $sth->execute(
1327 $auser, $branchcode, $aqbooksellerid, $cost,
1328 $aqbudgetid, $startdate, $periodicity, $firstacquidate,
1329 $irregularity, $numberpattern, $locale, $numberlength,
1330 $weeklength, $monthlength, $lastvalue1, $innerloop1,
1331 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3,
1332 $status, $biblionumber, $callnumber, $notes,
1333 $letter, ($manualhistory ? $manualhistory : 0),
1334 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1335 $graceperiod, $location, $enddate, $skip_serialseq,
1336 $itemtype, $previousitemtype, $mana_id,
1337 $subscriptionid
1339 my $rows = $sth->rows;
1341 logaction( "SERIAL", "MODIFY", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1342 return $rows;
1345 =head2 NewSubscription
1347 $subscriptionid = &NewSubscription($auser,branchcode,$aqbooksellerid,$cost,$aqbudgetid,$biblionumber,
1348 $startdate,$periodicity,$numberlength,$weeklength,$monthlength,
1349 $lastvalue1,$innerloop1,$lastvalue2,$innerloop2,$lastvalue3,$innerloop3,
1350 $status, $notes, $letter, $firstacquidate, $irregularity, $numberpattern,
1351 $locale, $callnumber, $manualhistory, $internalnotes, $serialsadditems,
1352 $staffdisplaycount, $opacdisplaycount, $graceperiod, $location, $enddate,
1353 $skip_serialseq, $itemtype, $previousitemtype);
1355 Create a new subscription with value given on input args.
1357 return :
1358 the id of this new subscription
1360 =cut
1362 sub NewSubscription {
1363 my (
1364 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1365 $startdate, $periodicity, $numberlength, $weeklength, $monthlength,
1366 $lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
1367 $innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
1368 $numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
1369 $serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
1370 $location, $enddate, $skip_serialseq, $itemtype, $previousitemtype, $mana_id
1371 ) = @_;
1372 my $dbh = C4::Context->dbh;
1374 my $subscription = Koha::Subscription->new(
1376 librarian => $auser,
1377 branchcode => $branchcode,
1378 aqbooksellerid => $aqbooksellerid,
1379 cost => $cost,
1380 aqbudgetid => $aqbudgetid,
1381 biblionumber => $biblionumber,
1382 startdate => $startdate,
1383 periodicity => $periodicity,
1384 numberlength => $numberlength,
1385 weeklength => $weeklength,
1386 monthlength => $monthlength,
1387 lastvalue1 => $lastvalue1,
1388 innerloop1 => $innerloop1,
1389 lastvalue2 => $lastvalue2,
1390 innerloop2 => $innerloop2,
1391 lastvalue3 => $lastvalue3,
1392 innerloop3 => $innerloop3,
1393 status => $status,
1394 notes => $notes,
1395 letter => $letter,
1396 firstacquidate => $firstacquidate,
1397 irregularity => $irregularity,
1398 numberpattern => $numberpattern,
1399 locale => $locale,
1400 callnumber => $callnumber,
1401 manualhistory => $manualhistory,
1402 internalnotes => $internalnotes,
1403 serialsadditems => $serialsadditems,
1404 staffdisplaycount => $staffdisplaycount,
1405 opacdisplaycount => $opacdisplaycount,
1406 graceperiod => $graceperiod,
1407 location => $location,
1408 enddate => $enddate,
1409 skip_serialseq => $skip_serialseq,
1410 itemtype => $itemtype,
1411 previousitemtype => $previousitemtype,
1412 mana_id => $mana_id,
1414 )->store;
1415 $subscription->discard_changes;
1416 my $subscriptionid = $subscription->subscriptionid;
1417 my ( $query, $sth );
1418 unless ($enddate) {
1419 $enddate = GetExpirationDate( $subscriptionid, $startdate );
1420 $query = qq|
1421 UPDATE subscription
1422 SET enddate=?
1423 WHERE subscriptionid=?
1425 $sth = $dbh->prepare($query);
1426 $sth->execute( $enddate, $subscriptionid );
1429 # then create the 1st expected number
1430 $query = qq(
1431 INSERT INTO subscriptionhistory
1432 (biblionumber, subscriptionid, histstartdate, missinglist, recievedlist)
1433 VALUES (?,?,?, '', '')
1435 $sth = $dbh->prepare($query);
1436 $sth->execute( $biblionumber, $subscriptionid, $startdate);
1438 # reread subscription to get a hash (for calculation of the 1st issue number)
1439 $subscription = GetSubscription($subscriptionid); # We should not do that
1440 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1442 # calculate issue number
1443 my $serialseq = GetSeq($subscription, $pattern) || q{};
1445 Koha::Serial->new(
1447 serialseq => $serialseq,
1448 serialseq_x => $subscription->{'lastvalue1'},
1449 serialseq_y => $subscription->{'lastvalue2'},
1450 serialseq_z => $subscription->{'lastvalue3'},
1451 subscriptionid => $subscriptionid,
1452 biblionumber => $biblionumber,
1453 status => EXPECTED,
1454 planneddate => $firstacquidate,
1455 publisheddate => $firstacquidate,
1457 )->store();
1459 logaction( "SERIAL", "ADD", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1461 #set serial flag on biblio if not already set.
1462 my $biblio = Koha::Biblios->find( $biblionumber );
1463 if ( $biblio and !$biblio->serial ) {
1464 my $record = GetMarcBiblio({ biblionumber => $biblionumber });
1465 my ( $tag, $subf ) = GetMarcFromKohaField( 'biblio.serial', $biblio->frameworkcode );
1466 if ($tag) {
1467 eval { $record->field($tag)->update( $subf => 1 ); };
1469 ModBiblio( $record, $biblionumber, $biblio->frameworkcode );
1471 return $subscriptionid;
1474 =head2 ReNewSubscription
1476 ReNewSubscription($subscriptionid,$user,$startdate,$numberlength,$weeklength,$monthlength,$note)
1478 this function renew a subscription with values given on input args.
1480 =cut
1482 sub ReNewSubscription {
1483 my ( $subscriptionid, $user, $startdate, $numberlength, $weeklength, $monthlength, $note ) = @_;
1484 my $dbh = C4::Context->dbh;
1485 my $subscription = GetSubscription($subscriptionid);
1486 my $query = qq|
1487 SELECT *
1488 FROM biblio
1489 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
1490 WHERE biblio.biblionumber=?
1492 my $sth = $dbh->prepare($query);
1493 $sth->execute( $subscription->{biblionumber} );
1494 my $biblio = $sth->fetchrow_hashref;
1496 if ( C4::Context->preference("RenewSerialAddsSuggestion") ) {
1497 require C4::Suggestions;
1498 C4::Suggestions::NewSuggestion(
1499 { 'suggestedby' => $user,
1500 'title' => $subscription->{bibliotitle},
1501 'author' => $biblio->{author},
1502 'publishercode' => $biblio->{publishercode},
1503 'note' => $biblio->{note},
1504 'biblionumber' => $subscription->{biblionumber}
1509 $numberlength ||= 0; # Should not we raise an exception instead?
1510 $weeklength ||= 0;
1512 # renew subscription
1513 $query = qq|
1514 UPDATE subscription
1515 SET startdate=?,numberlength=?,weeklength=?,monthlength=?,reneweddate=NOW()
1516 WHERE subscriptionid=?
1518 $sth = $dbh->prepare($query);
1519 $sth->execute( $startdate, $numberlength, $weeklength, $monthlength, $subscriptionid );
1520 my $enddate = GetExpirationDate($subscriptionid);
1521 $debug && warn "enddate :$enddate";
1522 $query = qq|
1523 UPDATE subscription
1524 SET enddate=?
1525 WHERE subscriptionid=?
1527 $sth = $dbh->prepare($query);
1528 $sth->execute( $enddate, $subscriptionid );
1530 logaction( "SERIAL", "RENEW", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1531 return;
1534 =head2 NewIssue
1536 NewIssue($serialseq,$subscriptionid,$biblionumber,$status, $planneddate, $publisheddate, $notes)
1538 Create a new issue stored on the database.
1539 Note : we have to update the recievedlist and missinglist on subscriptionhistory for this subscription.
1540 returns the serial id
1542 =cut
1544 sub NewIssue {
1545 my ( $serialseq, $subscriptionid, $biblionumber, $status, $planneddate,
1546 $publisheddate, $publisheddatetext, $notes ) = @_;
1547 ### FIXME biblionumber CAN be provided by subscriptionid. So Do we STILL NEED IT ?
1549 return unless ($subscriptionid);
1551 my $schema = Koha::Database->new()->schema();
1553 my $subscription = Koha::Subscriptions->find( $subscriptionid );
1555 my $serial = Koha::Serial->new(
1557 serialseq => $serialseq,
1558 serialseq_x => $subscription->lastvalue1(),
1559 serialseq_y => $subscription->lastvalue2(),
1560 serialseq_z => $subscription->lastvalue3(),
1561 subscriptionid => $subscriptionid,
1562 biblionumber => $biblionumber,
1563 status => $status,
1564 planneddate => $planneddate,
1565 publisheddate => $publisheddate,
1566 publisheddatetext => $publisheddatetext,
1567 notes => $notes,
1569 )->store();
1571 my $serialid = $serial->id();
1573 my $subscription_history = Koha::Subscription::Histories->find($subscriptionid);
1574 my $missinglist = $subscription_history->missinglist();
1575 my $recievedlist = $subscription_history->recievedlist();
1577 if ( $status == ARRIVED ) {
1578 ### TODO Add a feature that improves recognition and description.
1579 ### As such count (serialseq) i.e. : N18,2(N19),N20
1580 ### Would use substr and index But be careful to previous presence of ()
1581 $recievedlist .= "; $serialseq" unless ( index( $recievedlist, $serialseq ) > 0 );
1583 if ( grep { /^$status$/ } (MISSING_STATUSES) ) {
1584 $missinglist .= "; $serialseq" unless ( index( $missinglist, $serialseq ) > 0 );
1587 $recievedlist =~ s/^; //;
1588 $missinglist =~ s/^; //;
1590 $subscription_history->recievedlist($recievedlist);
1591 $subscription_history->missinglist($missinglist);
1592 $subscription_history->store();
1594 return $serialid;
1597 =head2 HasSubscriptionStrictlyExpired
1599 1 or 0 = HasSubscriptionStrictlyExpired($subscriptionid)
1601 the subscription has stricly expired when today > the end subscription date
1603 return :
1604 1 if true, 0 if false, -1 if the expiration date is not set.
1606 =cut
1608 sub HasSubscriptionStrictlyExpired {
1610 # Getting end of subscription date
1611 my ($subscriptionid) = @_;
1613 return unless ($subscriptionid);
1615 my $dbh = C4::Context->dbh;
1616 my $subscription = GetSubscription($subscriptionid);
1617 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1619 # If the expiration date is set
1620 if ( $expirationdate != 0 ) {
1621 my ( $endyear, $endmonth, $endday ) = split( '-', $expirationdate );
1623 # Getting today's date
1624 my ( $nowyear, $nowmonth, $nowday ) = Today();
1626 # if today's date > expiration date, then the subscription has stricly expired
1627 if ( Delta_Days( $nowyear, $nowmonth, $nowday, $endyear, $endmonth, $endday ) < 0 ) {
1628 return 1;
1629 } else {
1630 return 0;
1632 } else {
1634 # There are some cases where the expiration date is not set
1635 # As we can't determine if the subscription has expired on a date-basis,
1636 # we return -1;
1637 return -1;
1641 =head2 HasSubscriptionExpired
1643 $has_expired = HasSubscriptionExpired($subscriptionid)
1645 the subscription has expired when the next issue to arrive is out of subscription limit.
1647 return :
1648 0 if the subscription has not expired
1649 1 if the subscription has expired
1650 2 if has subscription does not have a valid expiration date set
1652 =cut
1654 sub HasSubscriptionExpired {
1655 my ($subscriptionid) = @_;
1657 return unless ($subscriptionid);
1659 my $dbh = C4::Context->dbh;
1660 my $subscription = GetSubscription($subscriptionid);
1661 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1662 if ( $frequency and $frequency->{unit} ) {
1663 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1664 if (!defined $expirationdate) {
1665 $expirationdate = q{};
1667 my $query = qq|
1668 SELECT max(planneddate)
1669 FROM serial
1670 WHERE subscriptionid=?
1672 my $sth = $dbh->prepare($query);
1673 $sth->execute($subscriptionid);
1674 my ($res) = $sth->fetchrow;
1675 if (!$res || $res=~m/^0000/) {
1676 return 0;
1678 my @res = split( /-/, $res );
1679 my @endofsubscriptiondate = split( /-/, $expirationdate );
1680 return 2 if ( scalar(@res) != 3 || scalar(@endofsubscriptiondate) != 3 || not check_date(@res) || not check_date(@endofsubscriptiondate) );
1681 return 1
1682 if ( ( @endofsubscriptiondate && Delta_Days( $res[0], $res[1], $res[2], $endofsubscriptiondate[0], $endofsubscriptiondate[1], $endofsubscriptiondate[2] ) <= 0 )
1683 || ( !$res ) );
1684 return 0;
1685 } else {
1686 # Irregular
1687 if ( $subscription->{'numberlength'} ) {
1688 my $countreceived = countissuesfrom( $subscriptionid, $subscription->{'startdate'} );
1689 return 1 if ( $countreceived > $subscription->{'numberlength'} );
1690 return 0;
1691 } else {
1692 return 0;
1695 return 0; # Notice that you'll never get here.
1698 =head2 DelSubscription
1700 DelSubscription($subscriptionid)
1701 this function deletes subscription which has $subscriptionid as id.
1703 =cut
1705 sub DelSubscription {
1706 my ($subscriptionid) = @_;
1707 my $dbh = C4::Context->dbh;
1708 $dbh->do("DELETE FROM subscription WHERE subscriptionid=?", undef, $subscriptionid);
1709 $dbh->do("DELETE FROM subscriptionhistory WHERE subscriptionid=?", undef, $subscriptionid);
1710 $dbh->do("DELETE FROM serial WHERE subscriptionid=?", undef, $subscriptionid);
1712 Koha::AdditionalFieldValues->search({
1713 'field.tablename' => 'subscription',
1714 'me.record_id' => $subscriptionid,
1715 }, { join => 'field' })->delete;
1717 logaction( "SERIAL", "DELETE", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1720 =head2 DelIssue
1722 DelIssue($serialseq,$subscriptionid)
1723 this function deletes an issue which has $serialseq and $subscriptionid given on input arg.
1725 returns the number of rows affected
1727 =cut
1729 sub DelIssue {
1730 my ($dataissue) = @_;
1731 my $dbh = C4::Context->dbh;
1732 ### TODO Add itemdeletion. Would need to get itemnumbers. Should be in a pref ?
1734 my $query = qq|
1735 DELETE FROM serial
1736 WHERE serialid= ?
1737 AND subscriptionid= ?
1739 my $mainsth = $dbh->prepare($query);
1740 $mainsth->execute( $dataissue->{'serialid'}, $dataissue->{'subscriptionid'} );
1742 #Delete element from subscription history
1743 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1744 my $sth = $dbh->prepare($query);
1745 $sth->execute( $dataissue->{'subscriptionid'} );
1746 my $val = $sth->fetchrow_hashref;
1747 unless ( $val->{manualhistory} ) {
1748 my $query = qq|
1749 SELECT * FROM subscriptionhistory
1750 WHERE subscriptionid= ?
1752 my $sth = $dbh->prepare($query);
1753 $sth->execute( $dataissue->{'subscriptionid'} );
1754 my $data = $sth->fetchrow_hashref;
1755 my $serialseq = $dataissue->{'serialseq'};
1756 $data->{'missinglist'} =~ s/\b$serialseq\b//;
1757 $data->{'recievedlist'} =~ s/\b$serialseq\b//;
1758 my $strsth = "UPDATE subscriptionhistory SET " . join( ",", map { join( "=", $_, $dbh->quote( $data->{$_} ) ) } keys %$data ) . " WHERE subscriptionid=?";
1759 $sth = $dbh->prepare($strsth);
1760 $sth->execute( $dataissue->{'subscriptionid'} );
1763 return $mainsth->rows;
1766 =head2 GetLateOrMissingIssues
1768 @issuelist = GetLateMissingIssues($supplierid,$serialid)
1770 this function selects missing issues on database - where serial.status = MISSING* or serial.status = LATE or planneddate<now
1772 return :
1773 the issuelist as an array of hash refs. Each element of this array contains
1774 name,title,planneddate,serialseq,serial.subscriptionid from tables : subscription, serial & biblio
1776 =cut
1778 sub GetLateOrMissingIssues {
1779 my ( $supplierid, $serialid, $order ) = @_;
1781 return unless ( $supplierid or $serialid );
1783 my $dbh = C4::Context->dbh;
1785 my $sth;
1786 my $byserial = '';
1787 if ($serialid) {
1788 $byserial = "and serialid = " . $serialid;
1790 if ($order) {
1791 $order .= ", title";
1792 } else {
1793 $order = "title";
1795 my $missing_statuses_string = join ',', (MISSING_STATUSES);
1796 if ($supplierid) {
1797 $sth = $dbh->prepare(
1798 "SELECT
1799 serialid, aqbooksellerid, name,
1800 biblio.title, biblioitems.issn, planneddate, serialseq,
1801 serial.status, serial.subscriptionid, claimdate, claims_count,
1802 subscription.branchcode
1803 FROM serial
1804 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1805 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1806 LEFT JOIN biblioitems ON subscription.biblionumber=biblioitems.biblionumber
1807 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1808 WHERE subscription.subscriptionid = serial.subscriptionid
1809 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1810 AND subscription.aqbooksellerid=$supplierid
1811 $byserial
1812 ORDER BY $order"
1814 } else {
1815 $sth = $dbh->prepare(
1816 "SELECT
1817 serialid, aqbooksellerid, name,
1818 biblio.title, planneddate, serialseq,
1819 serial.status, serial.subscriptionid, claimdate, claims_count,
1820 subscription.branchcode
1821 FROM serial
1822 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1823 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1824 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1825 WHERE subscription.subscriptionid = serial.subscriptionid
1826 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1827 $byserial
1828 ORDER BY $order"
1831 $sth->execute( EXPECTED, LATE, CLAIMED );
1832 my @issuelist;
1833 while ( my $line = $sth->fetchrow_hashref ) {
1835 if ($line->{planneddate} && $line->{planneddate} !~/^0+\-/) {
1836 $line->{planneddateISO} = $line->{planneddate};
1837 $line->{planneddate} = output_pref( { dt => dt_from_string( $line->{"planneddate"} ), dateonly => 1 } );
1839 if ($line->{claimdate} && $line->{claimdate} !~/^0+\-/) {
1840 $line->{claimdateISO} = $line->{claimdate};
1841 $line->{claimdate} = output_pref( { dt => dt_from_string( $line->{"claimdate"} ), dateonly => 1 } );
1843 $line->{"status".$line->{status}} = 1;
1845 my $subscription_object = Koha::Subscriptions->find($line->{subscriptionid});
1846 $line->{additional_fields} = { map { $_->field->name => $_->value }
1847 $subscription_object->additional_field_values->as_list };
1849 push @issuelist, $line;
1851 return @issuelist;
1854 =head2 updateClaim
1856 &updateClaim($serialid)
1858 this function updates the time when a claim is issued for late/missing items
1860 called from claims.pl file
1862 =cut
1864 sub updateClaim {
1865 my ($serialids) = @_;
1866 return unless $serialids;
1867 unless ( ref $serialids ) {
1868 $serialids = [ $serialids ];
1870 my $dbh = C4::Context->dbh;
1871 return $dbh->do(q|
1872 UPDATE serial
1873 SET claimdate = NOW(),
1874 claims_count = claims_count + 1,
1875 status = ?
1876 WHERE serialid in (| . join( q|,|, (q|?|) x @$serialids ) . q|)|,
1877 {}, CLAIMED, @$serialids );
1880 =head2 check_routing
1882 $result = &check_routing($subscriptionid)
1884 this function checks to see if a serial has a routing list and returns the count of routingid
1885 used to show either an 'add' or 'edit' link
1887 =cut
1889 sub check_routing {
1890 my ($subscriptionid) = @_;
1892 return unless ($subscriptionid);
1894 my $dbh = C4::Context->dbh;
1895 my $sth = $dbh->prepare(
1896 "SELECT count(routingid) routingids FROM subscription LEFT JOIN subscriptionroutinglist
1897 ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
1898 WHERE subscription.subscriptionid = ? GROUP BY routingid ORDER BY ranking ASC
1901 $sth->execute($subscriptionid);
1902 my $line = $sth->fetchrow_hashref;
1903 my $result = $line->{'routingids'};
1904 return $result;
1907 =head2 addroutingmember
1909 addroutingmember($borrowernumber,$subscriptionid)
1911 this function takes a borrowernumber and subscriptionid and adds the member to the
1912 routing list for that serial subscription and gives them a rank on the list
1913 of either 1 or highest current rank + 1
1915 =cut
1917 sub addroutingmember {
1918 my ( $borrowernumber, $subscriptionid ) = @_;
1920 return unless ($borrowernumber and $subscriptionid);
1922 my $rank;
1923 my $dbh = C4::Context->dbh;
1924 my $sth = $dbh->prepare( "SELECT max(ranking) rank FROM subscriptionroutinglist WHERE subscriptionid = ?" );
1925 $sth->execute($subscriptionid);
1926 while ( my $line = $sth->fetchrow_hashref ) {
1927 if ( $line->{'rank'} > 0 ) {
1928 $rank = $line->{'rank'} + 1;
1929 } else {
1930 $rank = 1;
1933 $sth = $dbh->prepare( "INSERT INTO subscriptionroutinglist (subscriptionid,borrowernumber,ranking) VALUES (?,?,?)" );
1934 $sth->execute( $subscriptionid, $borrowernumber, $rank );
1937 =head2 reorder_members
1939 reorder_members($subscriptionid,$routingid,$rank)
1941 this function is used to reorder the routing list
1943 it takes the routingid of the member one wants to re-rank and the rank it is to move to
1944 - it gets all members on list puts their routingid's into an array
1945 - removes the one in the array that is $routingid
1946 - then reinjects $routingid at point indicated by $rank
1947 - then update the database with the routingids in the new order
1949 =cut
1951 sub reorder_members {
1952 my ( $subscriptionid, $routingid, $rank ) = @_;
1953 my $dbh = C4::Context->dbh;
1954 my $sth = $dbh->prepare( "SELECT * FROM subscriptionroutinglist WHERE subscriptionid = ? ORDER BY ranking ASC" );
1955 $sth->execute($subscriptionid);
1956 my @result;
1957 while ( my $line = $sth->fetchrow_hashref ) {
1958 push( @result, $line->{'routingid'} );
1961 # To find the matching index
1962 my $i;
1963 my $key = -1; # to allow for 0 being a valid response
1964 for ( $i = 0 ; $i < @result ; $i++ ) {
1965 if ( $routingid == $result[$i] ) {
1966 $key = $i; # save the index
1967 last;
1971 # if index exists in array then move it to new position
1972 if ( $key > -1 && $rank > 0 ) {
1973 my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
1974 my $moving_item = splice( @result, $key, 1 );
1975 splice( @result, $new_rank, 0, $moving_item );
1977 for ( my $j = 0 ; $j < @result ; $j++ ) {
1978 my $sth = $dbh->prepare( "UPDATE subscriptionroutinglist SET ranking = '" . ( $j + 1 ) . "' WHERE routingid = '" . $result[$j] . "'" );
1979 $sth->execute;
1981 return;
1984 =head2 delroutingmember
1986 delroutingmember($routingid,$subscriptionid)
1988 this function either deletes one member from routing list if $routingid exists otherwise
1989 deletes all members from the routing list
1991 =cut
1993 sub delroutingmember {
1995 # if $routingid exists then deletes that row otherwise deletes all with $subscriptionid
1996 my ( $routingid, $subscriptionid ) = @_;
1997 my $dbh = C4::Context->dbh;
1998 if ($routingid) {
1999 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE routingid = ?");
2000 $sth->execute($routingid);
2001 reorder_members( $subscriptionid, $routingid );
2002 } else {
2003 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE subscriptionid = ?");
2004 $sth->execute($subscriptionid);
2006 return;
2009 =head2 getroutinglist
2011 @routinglist = getroutinglist($subscriptionid)
2013 this gets the info from the subscriptionroutinglist for $subscriptionid
2015 return :
2016 the routinglist as an array. Each element of the array contains a hash_ref containing
2017 routingid - a unique id, borrowernumber, ranking, and biblionumber of subscription
2019 =cut
2021 sub getroutinglist {
2022 my ($subscriptionid) = @_;
2023 my $dbh = C4::Context->dbh;
2024 my $sth = $dbh->prepare(
2025 'SELECT routingid, borrowernumber, ranking, biblionumber
2026 FROM subscription
2027 JOIN subscriptionroutinglist ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
2028 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC'
2030 $sth->execute($subscriptionid);
2031 my $routinglist = $sth->fetchall_arrayref({});
2032 return @{$routinglist};
2035 =head2 countissuesfrom
2037 $result = countissuesfrom($subscriptionid,$startdate)
2039 Returns a count of serial rows matching the given subsctiptionid
2040 with published date greater than startdate
2042 =cut
2044 sub countissuesfrom {
2045 my ( $subscriptionid, $startdate ) = @_;
2046 my $dbh = C4::Context->dbh;
2047 my $query = qq|
2048 SELECT count(*)
2049 FROM serial
2050 WHERE subscriptionid=?
2051 AND serial.publisheddate>?
2053 my $sth = $dbh->prepare($query);
2054 $sth->execute( $subscriptionid, $startdate );
2055 my ($countreceived) = $sth->fetchrow;
2056 return $countreceived;
2059 =head2 CountIssues
2061 $result = CountIssues($subscriptionid)
2063 Returns a count of serial rows matching the given subsctiptionid
2065 =cut
2067 sub CountIssues {
2068 my ($subscriptionid) = @_;
2069 my $dbh = C4::Context->dbh;
2070 my $query = qq|
2071 SELECT count(*)
2072 FROM serial
2073 WHERE subscriptionid=?
2075 my $sth = $dbh->prepare($query);
2076 $sth->execute($subscriptionid);
2077 my ($countreceived) = $sth->fetchrow;
2078 return $countreceived;
2081 =head2 HasItems
2083 $result = HasItems($subscriptionid)
2085 returns a count of items from serial matching the subscriptionid
2087 =cut
2089 sub HasItems {
2090 my ($subscriptionid) = @_;
2091 my $dbh = C4::Context->dbh;
2092 my $query = q|
2093 SELECT COUNT(serialitems.itemnumber)
2094 FROM serial
2095 LEFT JOIN serialitems USING(serialid)
2096 WHERE subscriptionid=? AND serialitems.serialid IS NOT NULL
2098 my $sth=$dbh->prepare($query);
2099 $sth->execute($subscriptionid);
2100 my ($countitems)=$sth->fetchrow_array();
2101 return $countitems;
2104 =head2 abouttoexpire
2106 $result = abouttoexpire($subscriptionid)
2108 this function alerts you to the penultimate issue for a serial subscription
2110 returns 1 - if this is the penultimate issue
2111 returns 0 - if not
2113 =cut
2115 sub abouttoexpire {
2116 my ($subscriptionid) = @_;
2117 my $dbh = C4::Context->dbh;
2118 my $subscription = GetSubscription($subscriptionid);
2119 my $per = $subscription->{'periodicity'};
2120 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($per);
2121 if ($frequency and $frequency->{unit}){
2123 my $expirationdate = GetExpirationDate($subscriptionid);
2125 my ($res) = $dbh->selectrow_array('select max(planneddate) from serial where subscriptionid = ?', undef, $subscriptionid);
2126 my $nextdate = GetNextDate($subscription, $res, $frequency);
2128 # only compare dates if both dates exist.
2129 if ($nextdate and $expirationdate) {
2130 if(Date::Calc::Delta_Days(
2131 split( /-/, $nextdate ),
2132 split( /-/, $expirationdate )
2133 ) <= 0) {
2134 return 1;
2138 } elsif ($subscription->{numberlength}>0) {
2139 return (countissuesfrom($subscriptionid,$subscription->{'startdate'}) >=$subscription->{numberlength}-1);
2142 return 0;
2145 =head2 GetFictiveIssueNumber
2147 $issueno = GetFictiveIssueNumber($subscription, $publishedate, $frequency);
2149 Get the position of the issue published at $publisheddate, considering the
2150 first issue (at firstacquidate) is at position 1, the next is at position 2, etc...
2151 This issuenumber doesn't take into account irregularities, so, for instance, if the 3rd
2152 issue is declared as 'irregular' (will be skipped at receipt), the next issue number
2153 will be 4, not 3. It's why it is called 'fictive'. It is NOT a serial seq, and is not
2154 depending on how many rows are in serial table.
2155 The issue number calculation is based on subscription frequency, first acquisition
2156 date, and $publisheddate.
2158 Returns undef when called for irregular frequencies.
2160 The routine is used to skip irregularities when calculating the next issue
2161 date (in GetNextDate) or the next issue number (in GetNextSeq).
2163 =cut
2165 sub GetFictiveIssueNumber {
2166 my ($subscription, $publisheddate, $frequency) = @_;
2168 my $unit = $frequency->{unit} ? lc $frequency->{'unit'} : undef;
2169 return if !$unit;
2170 my $issueno;
2172 my ( $year, $month, $day ) = split /-/, $publisheddate;
2173 my ( $fa_year, $fa_month, $fa_day ) = split /-/, $subscription->{'firstacquidate'};
2174 my $delta = _delta_units( [$fa_year, $fa_month, $fa_day], [$year, $month, $day], $unit );
2176 if( $frequency->{'unitsperissue'} == 1 ) {
2177 $issueno = $delta * $frequency->{'issuesperunit'} + $subscription->{'countissuesperunit'};
2178 } else { # issuesperunit == 1
2179 $issueno = 1 + int( $delta / $frequency->{'unitsperissue'} );
2181 return $issueno;
2184 sub _delta_units {
2185 my ( $date1, $date2, $unit ) = @_;
2186 # date1 and date2 are array refs in the form [ yy, mm, dd ]
2188 if( $unit eq 'day' ) {
2189 return Delta_Days( @$date1, @$date2 );
2190 } elsif( $unit eq 'week' ) {
2191 return int( Delta_Days( @$date1, @$date2 ) / 7 );
2194 # In case of months or years, this is a wrapper around N_Delta_YMD.
2195 # Note that N_Delta_YMD returns 29 days between e.g. 22-2-72 and 22-3-72
2196 # while we expect 1 month.
2197 my @delta = N_Delta_YMD( @$date1, @$date2 );
2198 if( $delta[2] > 27 ) {
2199 # Check if we could add a month
2200 my @jump = Add_Delta_YM( @$date1, $delta[0], 1 + $delta[1] );
2201 if( Delta_Days( @jump, @$date2 ) >= 0 ) {
2202 $delta[1]++;
2205 if( $delta[1] >= 12 ) {
2206 $delta[0]++;
2207 $delta[1] -= 12;
2209 # if unit is year, we only return full years
2210 return $unit eq 'month' ? $delta[0] * 12 + $delta[1] : $delta[0];
2213 sub _get_next_date_day {
2214 my ($subscription, $freqdata, $year, $month, $day) = @_;
2216 my @newissue; # ( yy, mm, dd )
2217 # We do not need $delta_days here, since it would be zero where used
2219 if( $freqdata->{issuesperunit} == 1 ) {
2220 # Add full days
2221 @newissue = Add_Delta_Days(
2222 $year, $month, $day, $freqdata->{"unitsperissue"} );
2223 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2224 # Add zero days
2225 @newissue = ( $year, $month, $day );
2226 $subscription->{countissuesperunit}++;
2227 } else {
2228 # We finished a cycle of issues within a unit.
2229 # No subtraction of zero needed, just add one day
2230 @newissue = Add_Delta_Days( $year, $month, $day, 1 );
2231 $subscription->{countissuesperunit} = 1;
2233 return @newissue;
2236 sub _get_next_date_week {
2237 my ($subscription, $freqdata, $year, $month, $day) = @_;
2239 my @newissue; # ( yy, mm, dd )
2240 my $delta_days = int( 7 / $freqdata->{issuesperunit} );
2242 if( $freqdata->{issuesperunit} == 1 ) {
2243 # Add full weeks (of 7 days)
2244 @newissue = Add_Delta_Days(
2245 $year, $month, $day, 7 * $freqdata->{"unitsperissue"} );
2246 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2247 # Add rounded number of days based on frequency.
2248 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2249 $subscription->{countissuesperunit}++;
2250 } else {
2251 # We finished a cycle of issues within a unit.
2252 # Subtract delta * (issues - 1), add 1 week
2253 @newissue = Add_Delta_Days( $year, $month, $day,
2254 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2255 @newissue = Add_Delta_Days( @newissue, 7 );
2256 $subscription->{countissuesperunit} = 1;
2258 return @newissue;
2261 sub _get_next_date_month {
2262 my ($subscription, $freqdata, $year, $month, $day) = @_;
2264 my @newissue; # ( yy, mm, dd )
2265 my $delta_days = int( 30 / $freqdata->{issuesperunit} );
2267 if( $freqdata->{issuesperunit} == 1 ) {
2268 # Add full months
2269 @newissue = Add_Delta_YM(
2270 $year, $month, $day, 0, $freqdata->{"unitsperissue"} );
2271 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2272 # Add rounded number of days based on frequency.
2273 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2274 $subscription->{countissuesperunit}++;
2275 } else {
2276 # We finished a cycle of issues within a unit.
2277 # Subtract delta * (issues - 1), add 1 month
2278 @newissue = Add_Delta_Days( $year, $month, $day,
2279 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2280 @newissue = Add_Delta_YM( @newissue, 0, 1 );
2281 $subscription->{countissuesperunit} = 1;
2283 return @newissue;
2286 sub _get_next_date_year {
2287 my ($subscription, $freqdata, $year, $month, $day) = @_;
2289 my @newissue; # ( yy, mm, dd )
2290 my $delta_days = int( 365 / $freqdata->{issuesperunit} );
2292 if( $freqdata->{issuesperunit} == 1 ) {
2293 # Add full years
2294 @newissue = Add_Delta_YM( $year, $month, $day, $freqdata->{"unitsperissue"}, 0 );
2295 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2296 # Add rounded number of days based on frequency.
2297 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2298 $subscription->{countissuesperunit}++;
2299 } else {
2300 # We finished a cycle of issues within a unit.
2301 # Subtract delta * (issues - 1), add 1 year
2302 @newissue = Add_Delta_Days( $year, $month, $day, -$delta_days * ($freqdata->{issuesperunit} - 1) );
2303 @newissue = Add_Delta_YM( @newissue, 1, 0 );
2304 $subscription->{countissuesperunit} = 1;
2306 return @newissue;
2309 =head2 GetNextDate
2311 $resultdate = GetNextDate($publisheddate,$subscription,$freqdata,$updatecount)
2313 this function it takes the publisheddate and will return the next issue's date
2314 and will skip dates if there exists an irregularity.
2315 $publisheddate has to be an ISO date
2316 $subscription is a hashref containing at least 'firstacquidate', 'irregularity', and 'countissuesperunit'
2317 $frequency is a hashref containing frequency informations
2318 $updatecount is a boolean value which, when set to true, update the 'countissuesperunit' in database
2319 - eg if periodicity is monthly and $publisheddate is 2007-02-10 but if March and April is to be
2320 skipped then the returned date will be 2007-05-10
2322 return :
2323 $resultdate - then next date in the sequence (ISO date)
2325 Return undef if subscription is irregular
2327 =cut
2329 sub GetNextDate {
2330 my ( $subscription, $publisheddate, $freqdata, $updatecount ) = @_;
2332 return unless $subscription and $publisheddate;
2335 if ($freqdata->{'unit'}) {
2336 my ( $year, $month, $day ) = split /-/, $publisheddate;
2338 # Process an irregularity Hash
2339 # Suppose that irregularities are stored in a string with this structure
2340 # irreg1;irreg2;irreg3
2341 # where irregX is the number of issue which will not be received
2342 # (the first issue takes the number 1, the 2nd the number 2 and so on)
2343 my %irregularities;
2344 if ( $subscription->{irregularity} ) {
2345 my @irreg = split /;/, $subscription->{'irregularity'} ;
2346 foreach my $irregularity (@irreg) {
2347 $irregularities{$irregularity} = 1;
2351 # Get the 'fictive' next issue number
2352 # It is used to check if next issue is an irregular issue.
2353 my $issueno = GetFictiveIssueNumber($subscription, $publisheddate, $freqdata) + 1;
2355 # Then get the next date
2356 my $unit = lc $freqdata->{'unit'};
2357 if ($unit eq 'day') {
2358 while ($irregularities{$issueno}) {
2359 ($year, $month, $day) = _get_next_date_day($subscription,
2360 $freqdata, $year, $month, $day);
2361 $issueno++;
2363 ($year, $month, $day) = _get_next_date_day($subscription, $freqdata,
2364 $year, $month, $day);
2366 elsif ($unit eq 'week') {
2367 while ($irregularities{$issueno}) {
2368 ($year, $month, $day) = _get_next_date_week($subscription,
2369 $freqdata, $year, $month, $day);
2370 $issueno++;
2372 ($year, $month, $day) = _get_next_date_week($subscription,
2373 $freqdata, $year, $month, $day);
2375 elsif ($unit eq 'month') {
2376 while ($irregularities{$issueno}) {
2377 ($year, $month, $day) = _get_next_date_month($subscription,
2378 $freqdata, $year, $month, $day);
2379 $issueno++;
2381 ($year, $month, $day) = _get_next_date_month($subscription,
2382 $freqdata, $year, $month, $day);
2384 elsif ($unit eq 'year') {
2385 while ($irregularities{$issueno}) {
2386 ($year, $month, $day) = _get_next_date_year($subscription,
2387 $freqdata, $year, $month, $day);
2388 $issueno++;
2390 ($year, $month, $day) = _get_next_date_year($subscription,
2391 $freqdata, $year, $month, $day);
2394 if ($updatecount){
2395 my $dbh = C4::Context->dbh;
2396 my $query = qq{
2397 UPDATE subscription
2398 SET countissuesperunit = ?
2399 WHERE subscriptionid = ?
2401 my $sth = $dbh->prepare($query);
2402 $sth->execute($subscription->{'countissuesperunit'}, $subscription->{'subscriptionid'});
2405 return sprintf("%04d-%02d-%02d", $year, $month, $day);
2409 =head2 _numeration
2411 $string = &_numeration($value,$num_type,$locale);
2413 _numeration returns the string corresponding to $value in the num_type
2414 num_type can take :
2415 -dayname
2416 -dayabrv
2417 -monthname
2418 -monthabrv
2419 -season
2420 -seasonabrv
2422 =cut
2424 sub _numeration {
2425 my ($value, $num_type, $locale) = @_;
2426 $value ||= 0;
2427 $num_type //= '';
2428 $locale ||= 'en';
2429 my $string;
2430 if ( $num_type =~ /^dayname$/ or $num_type =~ /^dayabrv$/ ) {
2431 # 1970-11-01 was a Sunday
2432 $value = $value % 7;
2433 my $dt = DateTime->new(
2434 year => 1970,
2435 month => 11,
2436 day => $value + 1,
2437 locale => $locale,
2439 $string = $num_type =~ /^dayname$/
2440 ? $dt->strftime("%A")
2441 : $dt->strftime("%a");
2442 } elsif ( $num_type =~ /^monthname$/ or $num_type =~ /^monthabrv$/ ) {
2443 $value = $value % 12;
2444 my $dt = DateTime->new(
2445 year => 1970,
2446 month => $value + 1,
2447 locale => $locale,
2449 $string = $num_type =~ /^monthname$/
2450 ? $dt->strftime("%B")
2451 : $dt->strftime("%b");
2452 } elsif ( $num_type =~ /^season$/ ) {
2453 my @seasons= qw( Spring Summer Fall Winter );
2454 $value = $value % 4;
2455 $string = $seasons[$value];
2456 } elsif ( $num_type =~ /^seasonabrv$/ ) {
2457 my @seasonsabrv= qw( Spr Sum Fal Win );
2458 $value = $value % 4;
2459 $string = $seasonsabrv[$value];
2460 } else {
2461 $string = $value;
2464 return $string;
2467 =head2 CloseSubscription
2469 Close a subscription given a subscriptionid
2471 =cut
2473 sub CloseSubscription {
2474 my ( $subscriptionid ) = @_;
2475 return unless $subscriptionid;
2476 my $dbh = C4::Context->dbh;
2477 my $sth = $dbh->prepare( q{
2478 UPDATE subscription
2479 SET closed = 1
2480 WHERE subscriptionid = ?
2481 } );
2482 $sth->execute( $subscriptionid );
2484 # Set status = missing when status = stopped
2485 $sth = $dbh->prepare( q{
2486 UPDATE serial
2487 SET status = ?
2488 WHERE subscriptionid = ?
2489 AND status = ?
2490 } );
2491 $sth->execute( STOPPED, $subscriptionid, EXPECTED );
2494 =head2 ReopenSubscription
2496 Reopen a subscription given a subscriptionid
2498 =cut
2500 sub ReopenSubscription {
2501 my ( $subscriptionid ) = @_;
2502 return unless $subscriptionid;
2503 my $dbh = C4::Context->dbh;
2504 my $sth = $dbh->prepare( q{
2505 UPDATE subscription
2506 SET closed = 0
2507 WHERE subscriptionid = ?
2508 } );
2509 $sth->execute( $subscriptionid );
2511 # Set status = expected when status = stopped
2512 $sth = $dbh->prepare( q{
2513 UPDATE serial
2514 SET status = ?
2515 WHERE subscriptionid = ?
2516 AND status = ?
2517 } );
2518 $sth->execute( EXPECTED, $subscriptionid, STOPPED );
2521 =head2 subscriptionCurrentlyOnOrder
2523 $bool = subscriptionCurrentlyOnOrder( $subscriptionid );
2525 Return 1 if subscription is currently on order else 0.
2527 =cut
2529 sub subscriptionCurrentlyOnOrder {
2530 my ( $subscriptionid ) = @_;
2531 my $dbh = C4::Context->dbh;
2532 my $query = qq|
2533 SELECT COUNT(*) FROM aqorders
2534 WHERE subscriptionid = ?
2535 AND datereceived IS NULL
2536 AND datecancellationprinted IS NULL
2538 my $sth = $dbh->prepare( $query );
2539 $sth->execute($subscriptionid);
2540 return $sth->fetchrow_array;
2543 =head2 can_claim_subscription
2545 $can = can_claim_subscription( $subscriptionid[, $userid] );
2547 Return 1 if the subscription can be claimed by the current logged user (or a given $userid), else 0.
2549 =cut
2551 sub can_claim_subscription {
2552 my ( $subscription, $userid ) = @_;
2553 return _can_do_on_subscription( $subscription, $userid, 'claim_serials' );
2556 =head2 can_edit_subscription
2558 $can = can_edit_subscription( $subscriptionid[, $userid] );
2560 Return 1 if the subscription can be edited by the current logged user (or a given $userid), else 0.
2562 =cut
2564 sub can_edit_subscription {
2565 my ( $subscription, $userid ) = @_;
2566 return _can_do_on_subscription( $subscription, $userid, 'edit_subscription' );
2569 =head2 can_show_subscription
2571 $can = can_show_subscription( $subscriptionid[, $userid] );
2573 Return 1 if the subscription can be shown by the current logged user (or a given $userid), else 0.
2575 =cut
2577 sub can_show_subscription {
2578 my ( $subscription, $userid ) = @_;
2579 return _can_do_on_subscription( $subscription, $userid, '*' );
2582 sub _can_do_on_subscription {
2583 my ( $subscription, $userid, $permission ) = @_;
2584 return 0 unless C4::Context->userenv;
2585 my $flags = C4::Context->userenv->{flags};
2586 $userid ||= C4::Context->userenv->{'id'};
2588 if ( C4::Context->preference('IndependentBranches') ) {
2589 return 1
2590 if C4::Context->IsSuperLibrarian()
2592 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2593 or (
2594 C4::Auth::haspermission( $userid,
2595 { serials => $permission } )
2596 and ( not defined $subscription->{branchcode}
2597 or $subscription->{branchcode} eq ''
2598 or $subscription->{branchcode} eq
2599 C4::Context->userenv->{'branch'} )
2602 else {
2603 return 1
2604 if C4::Context->IsSuperLibrarian()
2606 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2607 or C4::Auth::haspermission(
2608 $userid, { serials => $permission }
2612 return 0;
2615 =head2 findSerialsByStatus
2617 @serials = findSerialsByStatus($status, $subscriptionid);
2619 Returns an array of serials matching a given status and subscription id.
2621 =cut
2623 sub findSerialsByStatus {
2624 my ( $status, $subscriptionid ) = @_;
2625 my $dbh = C4::Context->dbh;
2626 my $query = q| SELECT * from serial
2627 WHERE status = ?
2628 AND subscriptionid = ?
2630 my $serials = $dbh->selectall_arrayref( $query, { Slice => {} }, $status, $subscriptionid );
2631 return @$serials;
2635 __END__
2637 =head1 AUTHOR
2639 Koha Development Team <http://koha-community.org/>
2641 =cut