Bug 13895: Add API routes for checkouts retrieval and renewal
[koha.git] / C4 / Serials.pm
blobf8c17b918950e83b95e23b426901a0bfbb48579e
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 $line->{planneddate} = output_pref( { dt => dt_from_string( $line->{planneddate} ), dateonly => 1 } );
789 $line->{publisheddate} = output_pref( { dt => dt_from_string( $line->{publisheddate} ), dateonly => 1 } );
790 push @serials, $line;
793 return \@serials;
796 =head2 GetPreviousSerialid
798 $serialid = GetPreviousSerialid($subscriptionid, $nth)
799 get the $nth's previous serial for the given subscriptionid
800 return :
801 the serialid
803 =cut
805 sub GetPreviousSerialid {
806 my ( $subscriptionid, $nth ) = @_;
807 $nth ||= 1;
808 my $dbh = C4::Context->dbh;
809 my $return = undef;
811 # Status 2: Arrived
812 my $strsth = "SELECT serialid
813 FROM serial
814 WHERE subscriptionid = ?
815 AND status = 2
816 ORDER BY serialid DESC LIMIT $nth,1
818 my $sth = $dbh->prepare($strsth);
819 $sth->execute($subscriptionid);
820 my @serials;
821 my $line = $sth->fetchrow_hashref;
822 $return = $line->{'serialid'} if ($line);
824 return $return;
827 =head2 GetNextSeq
829 my (
830 $nextseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
831 $newinnerloop1, $newinnerloop2, $newinnerloop3
832 ) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate );
834 $subscription is a hashref containing all the attributes of the table
835 'subscription'.
836 $pattern is a hashref containing all the attributes of the table
837 'subscription_numberpatterns'.
838 $frequency is a hashref containing all the attributes of the table 'subscription_frequencies'
839 $planneddate is a date string in iso format.
840 This function get the next issue for the subscription given on input arg
842 =cut
844 sub GetNextSeq {
845 my ($subscription, $pattern, $frequency, $planneddate) = @_;
847 return unless ($subscription and $pattern);
849 my ( $newlastvalue1, $newlastvalue2, $newlastvalue3,
850 $newinnerloop1, $newinnerloop2, $newinnerloop3 );
851 my $count = 1;
853 if ($subscription->{'skip_serialseq'}) {
854 my @irreg = split /;/, $subscription->{'irregularity'};
855 if(@irreg > 0) {
856 my $irregularities = {};
857 $irregularities->{$_} = 1 foreach(@irreg);
858 my $issueno = GetFictiveIssueNumber($subscription, $planneddate, $frequency) + 1;
859 while($irregularities->{$issueno}) {
860 $count++;
861 $issueno++;
866 my $numberingmethod = $pattern->{numberingmethod};
867 my $calculated = "";
868 if ($numberingmethod) {
869 $calculated = $numberingmethod;
870 my $locale = $subscription->{locale};
871 $newlastvalue1 = $subscription->{lastvalue1} || 0;
872 $newlastvalue2 = $subscription->{lastvalue2} || 0;
873 $newlastvalue3 = $subscription->{lastvalue3} || 0;
874 $newinnerloop1 = $subscription->{innerloop1} || 0;
875 $newinnerloop2 = $subscription->{innerloop2} || 0;
876 $newinnerloop3 = $subscription->{innerloop3} || 0;
877 my %calc;
878 foreach(qw/X Y Z/) {
879 $calc{$_} = 1 if ($numberingmethod =~ /\{$_\}/);
882 for(my $i = 0; $i < $count; $i++) {
883 if($calc{'X'}) {
884 # check if we have to increase the new value.
885 $newinnerloop1 += 1;
886 if ($newinnerloop1 >= $pattern->{every1}) {
887 $newinnerloop1 = 0;
888 $newlastvalue1 += $pattern->{add1};
890 # reset counter if needed.
891 $newlastvalue1 = $pattern->{setto1} if ($newlastvalue1 > $pattern->{whenmorethan1});
893 if($calc{'Y'}) {
894 # check if we have to increase the new value.
895 $newinnerloop2 += 1;
896 if ($newinnerloop2 >= $pattern->{every2}) {
897 $newinnerloop2 = 0;
898 $newlastvalue2 += $pattern->{add2};
900 # reset counter if needed.
901 $newlastvalue2 = $pattern->{setto2} if ($newlastvalue2 > $pattern->{whenmorethan2});
903 if($calc{'Z'}) {
904 # check if we have to increase the new value.
905 $newinnerloop3 += 1;
906 if ($newinnerloop3 >= $pattern->{every3}) {
907 $newinnerloop3 = 0;
908 $newlastvalue3 += $pattern->{add3};
910 # reset counter if needed.
911 $newlastvalue3 = $pattern->{setto3} if ($newlastvalue3 > $pattern->{whenmorethan3});
914 if($calc{'X'}) {
915 my $newlastvalue1string = _numeration( $newlastvalue1, $pattern->{numbering1}, $locale );
916 $calculated =~ s/\{X\}/$newlastvalue1string/g;
918 if($calc{'Y'}) {
919 my $newlastvalue2string = _numeration( $newlastvalue2, $pattern->{numbering2}, $locale );
920 $calculated =~ s/\{Y\}/$newlastvalue2string/g;
922 if($calc{'Z'}) {
923 my $newlastvalue3string = _numeration( $newlastvalue3, $pattern->{numbering3}, $locale );
924 $calculated =~ s/\{Z\}/$newlastvalue3string/g;
928 return ($calculated,
929 $newlastvalue1, $newlastvalue2, $newlastvalue3,
930 $newinnerloop1, $newinnerloop2, $newinnerloop3);
933 =head2 GetSeq
935 $calculated = GetSeq($subscription, $pattern)
936 $subscription is a hashref containing all the attributes of the table 'subscription'
937 $pattern is a hashref containing all the attributes of the table 'subscription_numberpatterns'
938 this function transforms {X},{Y},{Z} to 150,0,0 for example.
939 return:
940 the sequence in string format
942 =cut
944 sub GetSeq {
945 my ($subscription, $pattern) = @_;
947 return unless ($subscription and $pattern);
949 my $locale = $subscription->{locale};
951 my $calculated = $pattern->{numberingmethod};
953 my $newlastvalue1 = $subscription->{'lastvalue1'} || 0;
954 $newlastvalue1 = _numeration($newlastvalue1, $pattern->{numbering1}, $locale) if ($pattern->{numbering1}); # reset counter if needed.
955 $calculated =~ s/\{X\}/$newlastvalue1/g;
957 my $newlastvalue2 = $subscription->{'lastvalue2'} || 0;
958 $newlastvalue2 = _numeration($newlastvalue2, $pattern->{numbering2}, $locale) if ($pattern->{numbering2}); # reset counter if needed.
959 $calculated =~ s/\{Y\}/$newlastvalue2/g;
961 my $newlastvalue3 = $subscription->{'lastvalue3'} || 0;
962 $newlastvalue3 = _numeration($newlastvalue3, $pattern->{numbering3}, $locale) if ($pattern->{numbering3}); # reset counter if needed.
963 $calculated =~ s/\{Z\}/$newlastvalue3/g;
964 return $calculated;
967 =head2 GetExpirationDate
969 $enddate = GetExpirationDate($subscriptionid, [$startdate])
971 this function return the next expiration date for a subscription given on input args.
973 return
974 the enddate or undef
976 =cut
978 sub GetExpirationDate {
979 my ( $subscriptionid, $startdate ) = @_;
981 return unless ($subscriptionid);
983 my $dbh = C4::Context->dbh;
984 my $subscription = GetSubscription($subscriptionid);
985 my $enddate;
987 # we don't do the same test if the subscription is based on X numbers or on X weeks/months
988 $enddate = $startdate || $subscription->{startdate};
989 my @date = split( /-/, $enddate );
991 return if ( scalar(@date) != 3 || not check_date(@date) );
993 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
994 if ( $frequency and $frequency->{unit} ) {
996 # If Not Irregular
997 if ( my $length = $subscription->{numberlength} ) {
999 #calculate the date of the last issue.
1000 for ( my $i = 1 ; $i <= $length ; $i++ ) {
1001 $enddate = GetNextDate( $subscription, $enddate, $frequency );
1003 } elsif ( $subscription->{monthlength} ) {
1004 if ( $$subscription{startdate} ) {
1005 my @enddate = Add_Delta_YM( $date[0], $date[1], $date[2], 0, $subscription->{monthlength} );
1006 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1008 } elsif ( $subscription->{weeklength} ) {
1009 if ( $$subscription{startdate} ) {
1010 my @date = split( /-/, $subscription->{startdate} );
1011 my @enddate = Add_Delta_Days( $date[0], $date[1], $date[2], $subscription->{weeklength} * 7 );
1012 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1014 } else {
1015 $enddate = $subscription->{enddate};
1017 return $enddate;
1018 } else {
1019 return $subscription->{enddate};
1023 =head2 CountSubscriptionFromBiblionumber
1025 $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber)
1026 this returns a count of the subscriptions for a given biblionumber
1027 return :
1028 the number of subscriptions
1030 =cut
1032 sub CountSubscriptionFromBiblionumber {
1033 my ($biblionumber) = @_;
1035 return unless ($biblionumber);
1037 my $dbh = C4::Context->dbh;
1038 my $query = "SELECT count(*) FROM subscription WHERE biblionumber=?";
1039 my $sth = $dbh->prepare($query);
1040 $sth->execute($biblionumber);
1041 my $subscriptionsnumber = $sth->fetchrow;
1042 return $subscriptionsnumber;
1045 =head2 ModSubscriptionHistory
1047 ModSubscriptionHistory($subscriptionid,$histstartdate,$enddate,$recievedlist,$missinglist,$opacnote,$librariannote);
1049 this function modifies the history of a subscription. Put your new values on input arg.
1050 returns the number of rows affected
1052 =cut
1054 sub ModSubscriptionHistory {
1055 my ( $subscriptionid, $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote ) = @_;
1057 return unless ($subscriptionid);
1059 my $dbh = C4::Context->dbh;
1060 my $query = "UPDATE subscriptionhistory
1061 SET histstartdate=?,histenddate=?,recievedlist=?,missinglist=?,opacnote=?,librariannote=?
1062 WHERE subscriptionid=?
1064 my $sth = $dbh->prepare($query);
1065 $receivedlist =~ s/^; // if $receivedlist;
1066 $missinglist =~ s/^; // if $missinglist;
1067 $opacnote =~ s/^; // if $opacnote;
1068 $sth->execute( $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote, $subscriptionid );
1069 return $sth->rows;
1072 =head2 ModSerialStatus
1074 ModSerialStatus($serialid, $serialseq, $planneddate, $publisheddate,
1075 $publisheddatetext, $status, $notes);
1077 This function modify the serial status. Serial status is a number.(eg 2 is "arrived")
1078 Note : if we change from "waited" to something else,then we will have to create a new "waited" entry
1080 =cut
1082 sub ModSerialStatus {
1083 my ($serialid, $serialseq, $planneddate, $publisheddate, $publisheddatetext,
1084 $status, $notes) = @_;
1086 return unless ($serialid);
1088 #It is a usual serial
1089 # 1st, get previous status :
1090 my $dbh = C4::Context->dbh;
1091 my $query = "SELECT serial.subscriptionid,serial.status,subscription.periodicity
1092 FROM serial, subscription
1093 WHERE serial.subscriptionid=subscription.subscriptionid
1094 AND serialid=?";
1095 my $sth = $dbh->prepare($query);
1096 $sth->execute($serialid);
1097 my ( $subscriptionid, $oldstatus, $periodicity ) = $sth->fetchrow;
1098 my $frequency = GetSubscriptionFrequency($periodicity);
1100 # change status & update subscriptionhistory
1101 my $val;
1102 if ( $status == DELETED ) {
1103 DelIssue( { 'serialid' => $serialid, 'subscriptionid' => $subscriptionid, 'serialseq' => $serialseq } );
1104 } else {
1106 my $query = '
1107 UPDATE serial
1108 SET serialseq = ?, publisheddate = ?, publisheddatetext = ?,
1109 planneddate = ?, status = ?, notes = ?
1110 WHERE serialid = ?
1112 $sth = $dbh->prepare($query);
1113 $sth->execute( $serialseq, $publisheddate, $publisheddatetext,
1114 $planneddate, $status, $notes, $serialid );
1115 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1116 $sth = $dbh->prepare($query);
1117 $sth->execute($subscriptionid);
1118 my $val = $sth->fetchrow_hashref;
1119 unless ( $val->{manualhistory} ) {
1120 $query = "SELECT missinglist,recievedlist FROM subscriptionhistory WHERE subscriptionid=?";
1121 $sth = $dbh->prepare($query);
1122 $sth->execute($subscriptionid);
1123 my ( $missinglist, $recievedlist ) = $sth->fetchrow;
1125 if ( $status == ARRIVED || ($oldstatus == ARRIVED && $status != ARRIVED) ) {
1126 $recievedlist .= "; $serialseq"
1127 if ($recievedlist !~ /(^|;)\s*$serialseq(?=;|$)/);
1130 # in case serial has been previously marked as missing
1131 if (grep /$status/, (EXPECTED, ARRIVED, LATE, CLAIMED)) {
1132 $missinglist=~ s/(^|;)\s*$serialseq(?=;|$)//g;
1135 $missinglist .= "; $serialseq"
1136 if ( ( grep { $_ == $status } ( MISSING_STATUSES ) ) && ( $missinglist !~/(^|;)\s*$serialseq(?=;|$)/ ) );
1137 $missinglist .= "; not issued $serialseq"
1138 if ( $status == NOT_ISSUED && $missinglist !~ /(^|;)\s*$serialseq(?=;|$)/ );
1140 $query = "UPDATE subscriptionhistory SET recievedlist=?, missinglist=? WHERE subscriptionid=?";
1141 $sth = $dbh->prepare($query);
1142 $recievedlist =~ s/^; //;
1143 $missinglist =~ s/^; //;
1144 $sth->execute( $recievedlist, $missinglist, $subscriptionid );
1148 # create new expected entry if needed (ie : was "expected" and has changed)
1149 my $otherIssueExpected = scalar findSerialsByStatus(EXPECTED, $subscriptionid);
1150 if ( !$otherIssueExpected && $oldstatus == EXPECTED && $status != EXPECTED ) {
1151 my $subscription = GetSubscription($subscriptionid);
1152 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1153 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1155 # next issue number
1156 my (
1157 $newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
1158 $newinnerloop1, $newinnerloop2, $newinnerloop3
1160 = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate );
1162 # next date (calculated from actual date & frequency parameters)
1163 my $nextpublisheddate = GetNextDate($subscription, $publisheddate, $frequency, 1);
1164 my $nextpubdate = $nextpublisheddate;
1165 $query = "UPDATE subscription SET lastvalue1=?, lastvalue2=?, lastvalue3=?, innerloop1=?, innerloop2=?, innerloop3=?
1166 WHERE subscriptionid = ?";
1167 $sth = $dbh->prepare($query);
1168 $sth->execute( $newlastvalue1, $newlastvalue2, $newlastvalue3, $newinnerloop1, $newinnerloop2, $newinnerloop3, $subscriptionid );
1170 NewIssue( $newserialseq, $subscriptionid, $subscription->{'biblionumber'}, 1, $nextpubdate, $nextpubdate );
1172 # check if an alert must be sent... (= a letter is defined & status became "arrived"
1173 if ( $subscription->{letter} && $status == ARRIVED && $oldstatus != ARRIVED ) {
1174 require C4::Letters;
1175 C4::Letters::SendAlerts( 'issue', $serialid, $subscription->{letter} );
1179 return;
1182 =head2 GetNextExpected
1184 $nextexpected = GetNextExpected($subscriptionid)
1186 Get the planneddate for the current expected issue of the subscription.
1188 returns a hashref:
1190 $nextexepected = {
1191 serialid => int
1192 planneddate => ISO date
1195 =cut
1197 sub GetNextExpected {
1198 my ($subscriptionid) = @_;
1200 my $dbh = C4::Context->dbh;
1201 my $query = qq{
1202 SELECT *
1203 FROM serial
1204 WHERE subscriptionid = ?
1205 AND status = ?
1206 LIMIT 1
1208 my $sth = $dbh->prepare($query);
1210 # Each subscription has only one 'expected' issue.
1211 $sth->execute( $subscriptionid, EXPECTED );
1212 my $nextissue = $sth->fetchrow_hashref;
1213 if ( !$nextissue ) {
1214 $query = qq{
1215 SELECT *
1216 FROM serial
1217 WHERE subscriptionid = ?
1218 ORDER BY publisheddate DESC
1219 LIMIT 1
1221 $sth = $dbh->prepare($query);
1222 $sth->execute($subscriptionid);
1223 $nextissue = $sth->fetchrow_hashref;
1225 foreach(qw/planneddate publisheddate/) {
1226 if ( !defined $nextissue->{$_} ) {
1227 # or should this default to 1st Jan ???
1228 $nextissue->{$_} = strftime( '%Y-%m-%d', localtime );
1230 $nextissue->{$_} = ($nextissue->{$_} ne '0000-00-00')
1231 ? $nextissue->{$_}
1232 : undef;
1235 return $nextissue;
1238 =head2 ModNextExpected
1240 ModNextExpected($subscriptionid,$date)
1242 Update the planneddate for the current expected issue of the subscription.
1243 This will modify all future prediction results.
1245 C<$date> is an ISO date.
1247 returns 0
1249 =cut
1251 sub ModNextExpected {
1252 my ( $subscriptionid, $date ) = @_;
1253 my $dbh = C4::Context->dbh;
1255 #FIXME: Would expect to only set planneddate, but we set both on new issue creation, so updating it here
1256 my $sth = $dbh->prepare('UPDATE serial SET planneddate=?,publisheddate=? WHERE subscriptionid=? AND status=?');
1258 # Each subscription has only one 'expected' issue.
1259 $sth->execute( $date, $date, $subscriptionid, EXPECTED );
1260 return 0;
1264 =head2 GetSubscriptionIrregularities
1266 =over 4
1268 =item @irreg = &GetSubscriptionIrregularities($subscriptionid);
1269 get the list of irregularities for a subscription
1271 =back
1273 =cut
1275 sub GetSubscriptionIrregularities {
1276 my $subscriptionid = shift;
1278 return unless $subscriptionid;
1280 my $dbh = C4::Context->dbh;
1281 my $query = qq{
1282 SELECT irregularity
1283 FROM subscription
1284 WHERE subscriptionid = ?
1286 my $sth = $dbh->prepare($query);
1287 $sth->execute($subscriptionid);
1289 my ($result) = $sth->fetchrow_array;
1290 my @irreg = split /;/, $result;
1292 return @irreg;
1295 =head2 ModSubscription
1297 this function modifies a subscription. Put all new values on input args.
1298 returns the number of rows affected
1300 =cut
1302 sub ModSubscription {
1303 my (
1304 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
1305 $periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
1306 $numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
1307 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
1308 $biblionumber, $callnumber, $notes, $letter, $manualhistory,
1309 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1310 $graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
1311 $itemtype, $previousitemtype, $mana_id
1312 ) = @_;
1314 my $dbh = C4::Context->dbh;
1315 my $query = "UPDATE subscription
1316 SET librarian=?, branchcode=?, aqbooksellerid=?, cost=?, aqbudgetid=?,
1317 startdate=?, periodicity=?, firstacquidate=?, irregularity=?,
1318 numberpattern=?, locale=?, numberlength=?, weeklength=?, monthlength=?,
1319 lastvalue1=?, innerloop1=?, lastvalue2=?, innerloop2=?,
1320 lastvalue3=?, innerloop3=?, status=?, biblionumber=?,
1321 callnumber=?, notes=?, letter=?, manualhistory=?,
1322 internalnotes=?, serialsadditems=?, staffdisplaycount=?,
1323 opacdisplaycount=?, graceperiod=?, location = ?, enddate=?,
1324 skip_serialseq=?, itemtype=?, previousitemtype=?, mana_id=?
1325 WHERE subscriptionid = ?";
1327 my $sth = $dbh->prepare($query);
1328 $sth->execute(
1329 $auser, $branchcode, $aqbooksellerid, $cost,
1330 $aqbudgetid, $startdate, $periodicity, $firstacquidate,
1331 $irregularity, $numberpattern, $locale, $numberlength,
1332 $weeklength, $monthlength, $lastvalue1, $innerloop1,
1333 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3,
1334 $status, $biblionumber, $callnumber, $notes,
1335 $letter, ($manualhistory ? $manualhistory : 0),
1336 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1337 $graceperiod, $location, $enddate, $skip_serialseq,
1338 $itemtype, $previousitemtype, $mana_id,
1339 $subscriptionid
1341 my $rows = $sth->rows;
1343 logaction( "SERIAL", "MODIFY", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1344 return $rows;
1347 =head2 NewSubscription
1349 $subscriptionid = &NewSubscription($auser,branchcode,$aqbooksellerid,$cost,$aqbudgetid,$biblionumber,
1350 $startdate,$periodicity,$numberlength,$weeklength,$monthlength,
1351 $lastvalue1,$innerloop1,$lastvalue2,$innerloop2,$lastvalue3,$innerloop3,
1352 $status, $notes, $letter, $firstacquidate, $irregularity, $numberpattern,
1353 $locale, $callnumber, $manualhistory, $internalnotes, $serialsadditems,
1354 $staffdisplaycount, $opacdisplaycount, $graceperiod, $location, $enddate,
1355 $skip_serialseq, $itemtype, $previousitemtype);
1357 Create a new subscription with value given on input args.
1359 return :
1360 the id of this new subscription
1362 =cut
1364 sub NewSubscription {
1365 my (
1366 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1367 $startdate, $periodicity, $numberlength, $weeklength, $monthlength,
1368 $lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
1369 $innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
1370 $numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
1371 $serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
1372 $location, $enddate, $skip_serialseq, $itemtype, $previousitemtype, $mana_id
1373 ) = @_;
1374 my $dbh = C4::Context->dbh;
1376 #save subscription (insert into database)
1377 my $query = qq|
1378 INSERT INTO subscription
1379 (librarian, branchcode, aqbooksellerid, cost, aqbudgetid,
1380 biblionumber, startdate, periodicity, numberlength, weeklength,
1381 monthlength, lastvalue1, innerloop1, lastvalue2, innerloop2,
1382 lastvalue3, innerloop3, status, notes, letter, firstacquidate,
1383 irregularity, numberpattern, locale, callnumber,
1384 manualhistory, internalnotes, serialsadditems, staffdisplaycount,
1385 opacdisplaycount, graceperiod, location, enddate, skip_serialseq,
1386 itemtype, previousitemtype, mana_id)
1387 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, ?)
1389 my $sth = $dbh->prepare($query);
1390 $sth->execute(
1391 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1392 $startdate, $periodicity, $numberlength, $weeklength,
1393 $monthlength, $lastvalue1, $innerloop1, $lastvalue2, $innerloop2,
1394 $lastvalue3, $innerloop3, $status, $notes, $letter,
1395 $firstacquidate, $irregularity, $numberpattern, $locale, $callnumber,
1396 $manualhistory, $internalnotes, $serialsadditems, $staffdisplaycount,
1397 $opacdisplaycount, $graceperiod, $location, $enddate, $skip_serialseq,
1398 $itemtype, $previousitemtype, $mana_id
1401 my $subscriptionid = $dbh->{'mysql_insertid'};
1402 unless ($enddate) {
1403 $enddate = GetExpirationDate( $subscriptionid, $startdate );
1404 $query = qq|
1405 UPDATE subscription
1406 SET enddate=?
1407 WHERE subscriptionid=?
1409 $sth = $dbh->prepare($query);
1410 $sth->execute( $enddate, $subscriptionid );
1413 # then create the 1st expected number
1414 $query = qq(
1415 INSERT INTO subscriptionhistory
1416 (biblionumber, subscriptionid, histstartdate, missinglist, recievedlist)
1417 VALUES (?,?,?, '', '')
1419 $sth = $dbh->prepare($query);
1420 $sth->execute( $biblionumber, $subscriptionid, $startdate);
1422 # reread subscription to get a hash (for calculation of the 1st issue number)
1423 my $subscription = GetSubscription($subscriptionid);
1424 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1426 # calculate issue number
1427 my $serialseq = GetSeq($subscription, $pattern) || q{};
1429 Koha::Serial->new(
1431 serialseq => $serialseq,
1432 serialseq_x => $subscription->{'lastvalue1'},
1433 serialseq_y => $subscription->{'lastvalue2'},
1434 serialseq_z => $subscription->{'lastvalue3'},
1435 subscriptionid => $subscriptionid,
1436 biblionumber => $biblionumber,
1437 status => EXPECTED,
1438 planneddate => $firstacquidate,
1439 publisheddate => $firstacquidate,
1441 )->store();
1443 logaction( "SERIAL", "ADD", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1445 #set serial flag on biblio if not already set.
1446 my $biblio = Koha::Biblios->find( $biblionumber );
1447 if ( $biblio and !$biblio->serial ) {
1448 my $record = GetMarcBiblio({ biblionumber => $biblionumber });
1449 my ( $tag, $subf ) = GetMarcFromKohaField( 'biblio.serial', $biblio->frameworkcode );
1450 if ($tag) {
1451 eval { $record->field($tag)->update( $subf => 1 ); };
1453 ModBiblio( $record, $biblionumber, $biblio->frameworkcode );
1455 return $subscriptionid;
1458 =head2 ReNewSubscription
1460 ReNewSubscription($subscriptionid,$user,$startdate,$numberlength,$weeklength,$monthlength,$note)
1462 this function renew a subscription with values given on input args.
1464 =cut
1466 sub ReNewSubscription {
1467 my ( $subscriptionid, $user, $startdate, $numberlength, $weeklength, $monthlength, $note ) = @_;
1468 my $dbh = C4::Context->dbh;
1469 my $subscription = GetSubscription($subscriptionid);
1470 my $query = qq|
1471 SELECT *
1472 FROM biblio
1473 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
1474 WHERE biblio.biblionumber=?
1476 my $sth = $dbh->prepare($query);
1477 $sth->execute( $subscription->{biblionumber} );
1478 my $biblio = $sth->fetchrow_hashref;
1480 if ( C4::Context->preference("RenewSerialAddsSuggestion") ) {
1481 require C4::Suggestions;
1482 C4::Suggestions::NewSuggestion(
1483 { 'suggestedby' => $user,
1484 'title' => $subscription->{bibliotitle},
1485 'author' => $biblio->{author},
1486 'publishercode' => $biblio->{publishercode},
1487 'note' => $biblio->{note},
1488 'biblionumber' => $subscription->{biblionumber}
1493 $numberlength ||= 0; # Should not we raise an exception instead?
1494 $weeklength ||= 0;
1496 # renew subscription
1497 $query = qq|
1498 UPDATE subscription
1499 SET startdate=?,numberlength=?,weeklength=?,monthlength=?,reneweddate=NOW()
1500 WHERE subscriptionid=?
1502 $sth = $dbh->prepare($query);
1503 $sth->execute( $startdate, $numberlength, $weeklength, $monthlength, $subscriptionid );
1504 my $enddate = GetExpirationDate($subscriptionid);
1505 $debug && warn "enddate :$enddate";
1506 $query = qq|
1507 UPDATE subscription
1508 SET enddate=?
1509 WHERE subscriptionid=?
1511 $sth = $dbh->prepare($query);
1512 $sth->execute( $enddate, $subscriptionid );
1514 logaction( "SERIAL", "RENEW", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1515 return;
1518 =head2 NewIssue
1520 NewIssue($serialseq,$subscriptionid,$biblionumber,$status, $planneddate, $publisheddate, $notes)
1522 Create a new issue stored on the database.
1523 Note : we have to update the recievedlist and missinglist on subscriptionhistory for this subscription.
1524 returns the serial id
1526 =cut
1528 sub NewIssue {
1529 my ( $serialseq, $subscriptionid, $biblionumber, $status, $planneddate,
1530 $publisheddate, $publisheddatetext, $notes ) = @_;
1531 ### FIXME biblionumber CAN be provided by subscriptionid. So Do we STILL NEED IT ?
1533 return unless ($subscriptionid);
1535 my $schema = Koha::Database->new()->schema();
1537 my $subscription = Koha::Subscriptions->find( $subscriptionid );
1539 my $serial = Koha::Serial->new(
1541 serialseq => $serialseq,
1542 serialseq_x => $subscription->lastvalue1(),
1543 serialseq_y => $subscription->lastvalue2(),
1544 serialseq_z => $subscription->lastvalue3(),
1545 subscriptionid => $subscriptionid,
1546 biblionumber => $biblionumber,
1547 status => $status,
1548 planneddate => $planneddate,
1549 publisheddate => $publisheddate,
1550 publisheddatetext => $publisheddatetext,
1551 notes => $notes,
1553 )->store();
1555 my $serialid = $serial->id();
1557 my $subscription_history = Koha::Subscription::Histories->find($subscriptionid);
1558 my $missinglist = $subscription_history->missinglist();
1559 my $recievedlist = $subscription_history->recievedlist();
1561 if ( $status == ARRIVED ) {
1562 ### TODO Add a feature that improves recognition and description.
1563 ### As such count (serialseq) i.e. : N18,2(N19),N20
1564 ### Would use substr and index But be careful to previous presence of ()
1565 $recievedlist .= "; $serialseq" unless ( index( $recievedlist, $serialseq ) > 0 );
1567 if ( grep { /^$status$/ } (MISSING_STATUSES) ) {
1568 $missinglist .= "; $serialseq" unless ( index( $missinglist, $serialseq ) > 0 );
1571 $recievedlist =~ s/^; //;
1572 $missinglist =~ s/^; //;
1574 $subscription_history->recievedlist($recievedlist);
1575 $subscription_history->missinglist($missinglist);
1576 $subscription_history->store();
1578 return $serialid;
1581 =head2 HasSubscriptionStrictlyExpired
1583 1 or 0 = HasSubscriptionStrictlyExpired($subscriptionid)
1585 the subscription has stricly expired when today > the end subscription date
1587 return :
1588 1 if true, 0 if false, -1 if the expiration date is not set.
1590 =cut
1592 sub HasSubscriptionStrictlyExpired {
1594 # Getting end of subscription date
1595 my ($subscriptionid) = @_;
1597 return unless ($subscriptionid);
1599 my $dbh = C4::Context->dbh;
1600 my $subscription = GetSubscription($subscriptionid);
1601 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1603 # If the expiration date is set
1604 if ( $expirationdate != 0 ) {
1605 my ( $endyear, $endmonth, $endday ) = split( '-', $expirationdate );
1607 # Getting today's date
1608 my ( $nowyear, $nowmonth, $nowday ) = Today();
1610 # if today's date > expiration date, then the subscription has stricly expired
1611 if ( Delta_Days( $nowyear, $nowmonth, $nowday, $endyear, $endmonth, $endday ) < 0 ) {
1612 return 1;
1613 } else {
1614 return 0;
1616 } else {
1618 # There are some cases where the expiration date is not set
1619 # As we can't determine if the subscription has expired on a date-basis,
1620 # we return -1;
1621 return -1;
1625 =head2 HasSubscriptionExpired
1627 $has_expired = HasSubscriptionExpired($subscriptionid)
1629 the subscription has expired when the next issue to arrive is out of subscription limit.
1631 return :
1632 0 if the subscription has not expired
1633 1 if the subscription has expired
1634 2 if has subscription does not have a valid expiration date set
1636 =cut
1638 sub HasSubscriptionExpired {
1639 my ($subscriptionid) = @_;
1641 return unless ($subscriptionid);
1643 my $dbh = C4::Context->dbh;
1644 my $subscription = GetSubscription($subscriptionid);
1645 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1646 if ( $frequency and $frequency->{unit} ) {
1647 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1648 if (!defined $expirationdate) {
1649 $expirationdate = q{};
1651 my $query = qq|
1652 SELECT max(planneddate)
1653 FROM serial
1654 WHERE subscriptionid=?
1656 my $sth = $dbh->prepare($query);
1657 $sth->execute($subscriptionid);
1658 my ($res) = $sth->fetchrow;
1659 if (!$res || $res=~m/^0000/) {
1660 return 0;
1662 my @res = split( /-/, $res );
1663 my @endofsubscriptiondate = split( /-/, $expirationdate );
1664 return 2 if ( scalar(@res) != 3 || scalar(@endofsubscriptiondate) != 3 || not check_date(@res) || not check_date(@endofsubscriptiondate) );
1665 return 1
1666 if ( ( @endofsubscriptiondate && Delta_Days( $res[0], $res[1], $res[2], $endofsubscriptiondate[0], $endofsubscriptiondate[1], $endofsubscriptiondate[2] ) <= 0 )
1667 || ( !$res ) );
1668 return 0;
1669 } else {
1670 # Irregular
1671 if ( $subscription->{'numberlength'} ) {
1672 my $countreceived = countissuesfrom( $subscriptionid, $subscription->{'startdate'} );
1673 return 1 if ( $countreceived > $subscription->{'numberlength'} );
1674 return 0;
1675 } else {
1676 return 0;
1679 return 0; # Notice that you'll never get here.
1682 =head2 DelSubscription
1684 DelSubscription($subscriptionid)
1685 this function deletes subscription which has $subscriptionid as id.
1687 =cut
1689 sub DelSubscription {
1690 my ($subscriptionid) = @_;
1691 my $dbh = C4::Context->dbh;
1692 $dbh->do("DELETE FROM subscription WHERE subscriptionid=?", undef, $subscriptionid);
1693 $dbh->do("DELETE FROM subscriptionhistory WHERE subscriptionid=?", undef, $subscriptionid);
1694 $dbh->do("DELETE FROM serial WHERE subscriptionid=?", undef, $subscriptionid);
1696 Koha::AdditionalFieldValues->search({
1697 'field.tablename' => 'subscription',
1698 'me.record_id' => $subscriptionid,
1699 }, { join => 'field' })->delete;
1701 logaction( "SERIAL", "DELETE", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1704 =head2 DelIssue
1706 DelIssue($serialseq,$subscriptionid)
1707 this function deletes an issue which has $serialseq and $subscriptionid given on input arg.
1709 returns the number of rows affected
1711 =cut
1713 sub DelIssue {
1714 my ($dataissue) = @_;
1715 my $dbh = C4::Context->dbh;
1716 ### TODO Add itemdeletion. Would need to get itemnumbers. Should be in a pref ?
1718 my $query = qq|
1719 DELETE FROM serial
1720 WHERE serialid= ?
1721 AND subscriptionid= ?
1723 my $mainsth = $dbh->prepare($query);
1724 $mainsth->execute( $dataissue->{'serialid'}, $dataissue->{'subscriptionid'} );
1726 #Delete element from subscription history
1727 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1728 my $sth = $dbh->prepare($query);
1729 $sth->execute( $dataissue->{'subscriptionid'} );
1730 my $val = $sth->fetchrow_hashref;
1731 unless ( $val->{manualhistory} ) {
1732 my $query = qq|
1733 SELECT * FROM subscriptionhistory
1734 WHERE subscriptionid= ?
1736 my $sth = $dbh->prepare($query);
1737 $sth->execute( $dataissue->{'subscriptionid'} );
1738 my $data = $sth->fetchrow_hashref;
1739 my $serialseq = $dataissue->{'serialseq'};
1740 $data->{'missinglist'} =~ s/\b$serialseq\b//;
1741 $data->{'recievedlist'} =~ s/\b$serialseq\b//;
1742 my $strsth = "UPDATE subscriptionhistory SET " . join( ",", map { join( "=", $_, $dbh->quote( $data->{$_} ) ) } keys %$data ) . " WHERE subscriptionid=?";
1743 $sth = $dbh->prepare($strsth);
1744 $sth->execute( $dataissue->{'subscriptionid'} );
1747 return $mainsth->rows;
1750 =head2 GetLateOrMissingIssues
1752 @issuelist = GetLateMissingIssues($supplierid,$serialid)
1754 this function selects missing issues on database - where serial.status = MISSING* or serial.status = LATE or planneddate<now
1756 return :
1757 the issuelist as an array of hash refs. Each element of this array contains
1758 name,title,planneddate,serialseq,serial.subscriptionid from tables : subscription, serial & biblio
1760 =cut
1762 sub GetLateOrMissingIssues {
1763 my ( $supplierid, $serialid, $order ) = @_;
1765 return unless ( $supplierid or $serialid );
1767 my $dbh = C4::Context->dbh;
1769 my $sth;
1770 my $byserial = '';
1771 if ($serialid) {
1772 $byserial = "and serialid = " . $serialid;
1774 if ($order) {
1775 $order .= ", title";
1776 } else {
1777 $order = "title";
1779 my $missing_statuses_string = join ',', (MISSING_STATUSES);
1780 if ($supplierid) {
1781 $sth = $dbh->prepare(
1782 "SELECT
1783 serialid, aqbooksellerid, name,
1784 biblio.title, biblioitems.issn, planneddate, serialseq,
1785 serial.status, serial.subscriptionid, claimdate, claims_count,
1786 subscription.branchcode
1787 FROM serial
1788 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1789 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1790 LEFT JOIN biblioitems ON subscription.biblionumber=biblioitems.biblionumber
1791 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1792 WHERE subscription.subscriptionid = serial.subscriptionid
1793 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1794 AND subscription.aqbooksellerid=$supplierid
1795 $byserial
1796 ORDER BY $order"
1798 } else {
1799 $sth = $dbh->prepare(
1800 "SELECT
1801 serialid, aqbooksellerid, name,
1802 biblio.title, planneddate, serialseq,
1803 serial.status, serial.subscriptionid, claimdate, claims_count,
1804 subscription.branchcode
1805 FROM serial
1806 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1807 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1808 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1809 WHERE subscription.subscriptionid = serial.subscriptionid
1810 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1811 $byserial
1812 ORDER BY $order"
1815 $sth->execute( EXPECTED, LATE, CLAIMED );
1816 my @issuelist;
1817 while ( my $line = $sth->fetchrow_hashref ) {
1819 if ($line->{planneddate} && $line->{planneddate} !~/^0+\-/) {
1820 $line->{planneddateISO} = $line->{planneddate};
1821 $line->{planneddate} = output_pref( { dt => dt_from_string( $line->{"planneddate"} ), dateonly => 1 } );
1823 if ($line->{claimdate} && $line->{claimdate} !~/^0+\-/) {
1824 $line->{claimdateISO} = $line->{claimdate};
1825 $line->{claimdate} = output_pref( { dt => dt_from_string( $line->{"claimdate"} ), dateonly => 1 } );
1827 $line->{"status".$line->{status}} = 1;
1829 my $subscription_object = Koha::Subscriptions->find($line->{subscriptionid});
1830 $line->{additional_fields} = { map { $_->field->name => $_->value }
1831 $subscription_object->additional_field_values->as_list };
1833 push @issuelist, $line;
1835 return @issuelist;
1838 =head2 updateClaim
1840 &updateClaim($serialid)
1842 this function updates the time when a claim is issued for late/missing items
1844 called from claims.pl file
1846 =cut
1848 sub updateClaim {
1849 my ($serialids) = @_;
1850 return unless $serialids;
1851 unless ( ref $serialids ) {
1852 $serialids = [ $serialids ];
1854 my $dbh = C4::Context->dbh;
1855 return $dbh->do(q|
1856 UPDATE serial
1857 SET claimdate = NOW(),
1858 claims_count = claims_count + 1,
1859 status = ?
1860 WHERE serialid in (| . join( q|,|, (q|?|) x @$serialids ) . q|)|,
1861 {}, CLAIMED, @$serialids );
1864 =head2 check_routing
1866 $result = &check_routing($subscriptionid)
1868 this function checks to see if a serial has a routing list and returns the count of routingid
1869 used to show either an 'add' or 'edit' link
1871 =cut
1873 sub check_routing {
1874 my ($subscriptionid) = @_;
1876 return unless ($subscriptionid);
1878 my $dbh = C4::Context->dbh;
1879 my $sth = $dbh->prepare(
1880 "SELECT count(routingid) routingids FROM subscription LEFT JOIN subscriptionroutinglist
1881 ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
1882 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC
1885 $sth->execute($subscriptionid);
1886 my $line = $sth->fetchrow_hashref;
1887 my $result = $line->{'routingids'};
1888 return $result;
1891 =head2 addroutingmember
1893 addroutingmember($borrowernumber,$subscriptionid)
1895 this function takes a borrowernumber and subscriptionid and adds the member to the
1896 routing list for that serial subscription and gives them a rank on the list
1897 of either 1 or highest current rank + 1
1899 =cut
1901 sub addroutingmember {
1902 my ( $borrowernumber, $subscriptionid ) = @_;
1904 return unless ($borrowernumber and $subscriptionid);
1906 my $rank;
1907 my $dbh = C4::Context->dbh;
1908 my $sth = $dbh->prepare( "SELECT max(ranking) rank FROM subscriptionroutinglist WHERE subscriptionid = ?" );
1909 $sth->execute($subscriptionid);
1910 while ( my $line = $sth->fetchrow_hashref ) {
1911 if ( $line->{'rank'} > 0 ) {
1912 $rank = $line->{'rank'} + 1;
1913 } else {
1914 $rank = 1;
1917 $sth = $dbh->prepare( "INSERT INTO subscriptionroutinglist (subscriptionid,borrowernumber,ranking) VALUES (?,?,?)" );
1918 $sth->execute( $subscriptionid, $borrowernumber, $rank );
1921 =head2 reorder_members
1923 reorder_members($subscriptionid,$routingid,$rank)
1925 this function is used to reorder the routing list
1927 it takes the routingid of the member one wants to re-rank and the rank it is to move to
1928 - it gets all members on list puts their routingid's into an array
1929 - removes the one in the array that is $routingid
1930 - then reinjects $routingid at point indicated by $rank
1931 - then update the database with the routingids in the new order
1933 =cut
1935 sub reorder_members {
1936 my ( $subscriptionid, $routingid, $rank ) = @_;
1937 my $dbh = C4::Context->dbh;
1938 my $sth = $dbh->prepare( "SELECT * FROM subscriptionroutinglist WHERE subscriptionid = ? ORDER BY ranking ASC" );
1939 $sth->execute($subscriptionid);
1940 my @result;
1941 while ( my $line = $sth->fetchrow_hashref ) {
1942 push( @result, $line->{'routingid'} );
1945 # To find the matching index
1946 my $i;
1947 my $key = -1; # to allow for 0 being a valid response
1948 for ( $i = 0 ; $i < @result ; $i++ ) {
1949 if ( $routingid == $result[$i] ) {
1950 $key = $i; # save the index
1951 last;
1955 # if index exists in array then move it to new position
1956 if ( $key > -1 && $rank > 0 ) {
1957 my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
1958 my $moving_item = splice( @result, $key, 1 );
1959 splice( @result, $new_rank, 0, $moving_item );
1961 for ( my $j = 0 ; $j < @result ; $j++ ) {
1962 my $sth = $dbh->prepare( "UPDATE subscriptionroutinglist SET ranking = '" . ( $j + 1 ) . "' WHERE routingid = '" . $result[$j] . "'" );
1963 $sth->execute;
1965 return;
1968 =head2 delroutingmember
1970 delroutingmember($routingid,$subscriptionid)
1972 this function either deletes one member from routing list if $routingid exists otherwise
1973 deletes all members from the routing list
1975 =cut
1977 sub delroutingmember {
1979 # if $routingid exists then deletes that row otherwise deletes all with $subscriptionid
1980 my ( $routingid, $subscriptionid ) = @_;
1981 my $dbh = C4::Context->dbh;
1982 if ($routingid) {
1983 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE routingid = ?");
1984 $sth->execute($routingid);
1985 reorder_members( $subscriptionid, $routingid );
1986 } else {
1987 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE subscriptionid = ?");
1988 $sth->execute($subscriptionid);
1990 return;
1993 =head2 getroutinglist
1995 @routinglist = getroutinglist($subscriptionid)
1997 this gets the info from the subscriptionroutinglist for $subscriptionid
1999 return :
2000 the routinglist as an array. Each element of the array contains a hash_ref containing
2001 routingid - a unique id, borrowernumber, ranking, and biblionumber of subscription
2003 =cut
2005 sub getroutinglist {
2006 my ($subscriptionid) = @_;
2007 my $dbh = C4::Context->dbh;
2008 my $sth = $dbh->prepare(
2009 'SELECT routingid, borrowernumber, ranking, biblionumber
2010 FROM subscription
2011 JOIN subscriptionroutinglist ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
2012 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC'
2014 $sth->execute($subscriptionid);
2015 my $routinglist = $sth->fetchall_arrayref({});
2016 return @{$routinglist};
2019 =head2 countissuesfrom
2021 $result = countissuesfrom($subscriptionid,$startdate)
2023 Returns a count of serial rows matching the given subsctiptionid
2024 with published date greater than startdate
2026 =cut
2028 sub countissuesfrom {
2029 my ( $subscriptionid, $startdate ) = @_;
2030 my $dbh = C4::Context->dbh;
2031 my $query = qq|
2032 SELECT count(*)
2033 FROM serial
2034 WHERE subscriptionid=?
2035 AND serial.publisheddate>?
2037 my $sth = $dbh->prepare($query);
2038 $sth->execute( $subscriptionid, $startdate );
2039 my ($countreceived) = $sth->fetchrow;
2040 return $countreceived;
2043 =head2 CountIssues
2045 $result = CountIssues($subscriptionid)
2047 Returns a count of serial rows matching the given subsctiptionid
2049 =cut
2051 sub CountIssues {
2052 my ($subscriptionid) = @_;
2053 my $dbh = C4::Context->dbh;
2054 my $query = qq|
2055 SELECT count(*)
2056 FROM serial
2057 WHERE subscriptionid=?
2059 my $sth = $dbh->prepare($query);
2060 $sth->execute($subscriptionid);
2061 my ($countreceived) = $sth->fetchrow;
2062 return $countreceived;
2065 =head2 HasItems
2067 $result = HasItems($subscriptionid)
2069 returns a count of items from serial matching the subscriptionid
2071 =cut
2073 sub HasItems {
2074 my ($subscriptionid) = @_;
2075 my $dbh = C4::Context->dbh;
2076 my $query = q|
2077 SELECT COUNT(serialitems.itemnumber)
2078 FROM serial
2079 LEFT JOIN serialitems USING(serialid)
2080 WHERE subscriptionid=? AND serialitems.serialid IS NOT NULL
2082 my $sth=$dbh->prepare($query);
2083 $sth->execute($subscriptionid);
2084 my ($countitems)=$sth->fetchrow_array();
2085 return $countitems;
2088 =head2 abouttoexpire
2090 $result = abouttoexpire($subscriptionid)
2092 this function alerts you to the penultimate issue for a serial subscription
2094 returns 1 - if this is the penultimate issue
2095 returns 0 - if not
2097 =cut
2099 sub abouttoexpire {
2100 my ($subscriptionid) = @_;
2101 my $dbh = C4::Context->dbh;
2102 my $subscription = GetSubscription($subscriptionid);
2103 my $per = $subscription->{'periodicity'};
2104 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($per);
2105 if ($frequency and $frequency->{unit}){
2107 my $expirationdate = GetExpirationDate($subscriptionid);
2109 my ($res) = $dbh->selectrow_array('select max(planneddate) from serial where subscriptionid = ?', undef, $subscriptionid);
2110 my $nextdate = GetNextDate($subscription, $res, $frequency);
2112 # only compare dates if both dates exist.
2113 if ($nextdate and $expirationdate) {
2114 if(Date::Calc::Delta_Days(
2115 split( /-/, $nextdate ),
2116 split( /-/, $expirationdate )
2117 ) <= 0) {
2118 return 1;
2122 } elsif ($subscription->{numberlength}>0) {
2123 return (countissuesfrom($subscriptionid,$subscription->{'startdate'}) >=$subscription->{numberlength}-1);
2126 return 0;
2129 =head2 GetFictiveIssueNumber
2131 $issueno = GetFictiveIssueNumber($subscription, $publishedate, $frequency);
2133 Get the position of the issue published at $publisheddate, considering the
2134 first issue (at firstacquidate) is at position 1, the next is at position 2, etc...
2135 This issuenumber doesn't take into account irregularities, so, for instance, if the 3rd
2136 issue is declared as 'irregular' (will be skipped at receipt), the next issue number
2137 will be 4, not 3. It's why it is called 'fictive'. It is NOT a serial seq, and is not
2138 depending on how many rows are in serial table.
2139 The issue number calculation is based on subscription frequency, first acquisition
2140 date, and $publisheddate.
2142 Returns undef when called for irregular frequencies.
2144 The routine is used to skip irregularities when calculating the next issue
2145 date (in GetNextDate) or the next issue number (in GetNextSeq).
2147 =cut
2149 sub GetFictiveIssueNumber {
2150 my ($subscription, $publisheddate, $frequency) = @_;
2152 my $unit = $frequency->{unit} ? lc $frequency->{'unit'} : undef;
2153 return if !$unit;
2154 my $issueno;
2156 my ( $year, $month, $day ) = split /-/, $publisheddate;
2157 my ( $fa_year, $fa_month, $fa_day ) = split /-/, $subscription->{'firstacquidate'};
2158 my $delta = _delta_units( [$fa_year, $fa_month, $fa_day], [$year, $month, $day], $unit );
2160 if( $frequency->{'unitsperissue'} == 1 ) {
2161 $issueno = $delta * $frequency->{'issuesperunit'} + $subscription->{'countissuesperunit'};
2162 } else { # issuesperunit == 1
2163 $issueno = 1 + int( $delta / $frequency->{'unitsperissue'} );
2165 return $issueno;
2168 sub _delta_units {
2169 my ( $date1, $date2, $unit ) = @_;
2170 # date1 and date2 are array refs in the form [ yy, mm, dd ]
2172 if( $unit eq 'day' ) {
2173 return Delta_Days( @$date1, @$date2 );
2174 } elsif( $unit eq 'week' ) {
2175 return int( Delta_Days( @$date1, @$date2 ) / 7 );
2178 # In case of months or years, this is a wrapper around N_Delta_YMD.
2179 # Note that N_Delta_YMD returns 29 days between e.g. 22-2-72 and 22-3-72
2180 # while we expect 1 month.
2181 my @delta = N_Delta_YMD( @$date1, @$date2 );
2182 if( $delta[2] > 27 ) {
2183 # Check if we could add a month
2184 my @jump = Add_Delta_YM( @$date1, $delta[0], 1 + $delta[1] );
2185 if( Delta_Days( @jump, @$date2 ) >= 0 ) {
2186 $delta[1]++;
2189 if( $delta[1] >= 12 ) {
2190 $delta[0]++;
2191 $delta[1] -= 12;
2193 # if unit is year, we only return full years
2194 return $unit eq 'month' ? $delta[0] * 12 + $delta[1] : $delta[0];
2197 sub _get_next_date_day {
2198 my ($subscription, $freqdata, $year, $month, $day) = @_;
2200 my @newissue; # ( yy, mm, dd )
2201 # We do not need $delta_days here, since it would be zero where used
2203 if( $freqdata->{issuesperunit} == 1 ) {
2204 # Add full days
2205 @newissue = Add_Delta_Days(
2206 $year, $month, $day, $freqdata->{"unitsperissue"} );
2207 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2208 # Add zero days
2209 @newissue = ( $year, $month, $day );
2210 $subscription->{countissuesperunit}++;
2211 } else {
2212 # We finished a cycle of issues within a unit.
2213 # No subtraction of zero needed, just add one day
2214 @newissue = Add_Delta_Days( $year, $month, $day, 1 );
2215 $subscription->{countissuesperunit} = 1;
2217 return @newissue;
2220 sub _get_next_date_week {
2221 my ($subscription, $freqdata, $year, $month, $day) = @_;
2223 my @newissue; # ( yy, mm, dd )
2224 my $delta_days = int( 7 / $freqdata->{issuesperunit} );
2226 if( $freqdata->{issuesperunit} == 1 ) {
2227 # Add full weeks (of 7 days)
2228 @newissue = Add_Delta_Days(
2229 $year, $month, $day, 7 * $freqdata->{"unitsperissue"} );
2230 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2231 # Add rounded number of days based on frequency.
2232 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2233 $subscription->{countissuesperunit}++;
2234 } else {
2235 # We finished a cycle of issues within a unit.
2236 # Subtract delta * (issues - 1), add 1 week
2237 @newissue = Add_Delta_Days( $year, $month, $day,
2238 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2239 @newissue = Add_Delta_Days( @newissue, 7 );
2240 $subscription->{countissuesperunit} = 1;
2242 return @newissue;
2245 sub _get_next_date_month {
2246 my ($subscription, $freqdata, $year, $month, $day) = @_;
2248 my @newissue; # ( yy, mm, dd )
2249 my $delta_days = int( 30 / $freqdata->{issuesperunit} );
2251 if( $freqdata->{issuesperunit} == 1 ) {
2252 # Add full months
2253 @newissue = Add_Delta_YM(
2254 $year, $month, $day, 0, $freqdata->{"unitsperissue"} );
2255 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2256 # Add rounded number of days based on frequency.
2257 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2258 $subscription->{countissuesperunit}++;
2259 } else {
2260 # We finished a cycle of issues within a unit.
2261 # Subtract delta * (issues - 1), add 1 month
2262 @newissue = Add_Delta_Days( $year, $month, $day,
2263 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2264 @newissue = Add_Delta_YM( @newissue, 0, 1 );
2265 $subscription->{countissuesperunit} = 1;
2267 return @newissue;
2270 sub _get_next_date_year {
2271 my ($subscription, $freqdata, $year, $month, $day) = @_;
2273 my @newissue; # ( yy, mm, dd )
2274 my $delta_days = int( 365 / $freqdata->{issuesperunit} );
2276 if( $freqdata->{issuesperunit} == 1 ) {
2277 # Add full years
2278 @newissue = Add_Delta_YM( $year, $month, $day, $freqdata->{"unitsperissue"}, 0 );
2279 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2280 # Add rounded number of days based on frequency.
2281 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2282 $subscription->{countissuesperunit}++;
2283 } else {
2284 # We finished a cycle of issues within a unit.
2285 # Subtract delta * (issues - 1), add 1 year
2286 @newissue = Add_Delta_Days( $year, $month, $day, -$delta_days * ($freqdata->{issuesperunit} - 1) );
2287 @newissue = Add_Delta_YM( @newissue, 1, 0 );
2288 $subscription->{countissuesperunit} = 1;
2290 return @newissue;
2293 =head2 GetNextDate
2295 $resultdate = GetNextDate($publisheddate,$subscription,$freqdata,$updatecount)
2297 this function it takes the publisheddate and will return the next issue's date
2298 and will skip dates if there exists an irregularity.
2299 $publisheddate has to be an ISO date
2300 $subscription is a hashref containing at least 'firstacquidate', 'irregularity', and 'countissuesperunit'
2301 $frequency is a hashref containing frequency informations
2302 $updatecount is a boolean value which, when set to true, update the 'countissuesperunit' in database
2303 - eg if periodicity is monthly and $publisheddate is 2007-02-10 but if March and April is to be
2304 skipped then the returned date will be 2007-05-10
2306 return :
2307 $resultdate - then next date in the sequence (ISO date)
2309 Return undef if subscription is irregular
2311 =cut
2313 sub GetNextDate {
2314 my ( $subscription, $publisheddate, $freqdata, $updatecount ) = @_;
2316 return unless $subscription and $publisheddate;
2319 if ($freqdata->{'unit'}) {
2320 my ( $year, $month, $day ) = split /-/, $publisheddate;
2322 # Process an irregularity Hash
2323 # Suppose that irregularities are stored in a string with this structure
2324 # irreg1;irreg2;irreg3
2325 # where irregX is the number of issue which will not be received
2326 # (the first issue takes the number 1, the 2nd the number 2 and so on)
2327 my %irregularities;
2328 if ( $subscription->{irregularity} ) {
2329 my @irreg = split /;/, $subscription->{'irregularity'} ;
2330 foreach my $irregularity (@irreg) {
2331 $irregularities{$irregularity} = 1;
2335 # Get the 'fictive' next issue number
2336 # It is used to check if next issue is an irregular issue.
2337 my $issueno = GetFictiveIssueNumber($subscription, $publisheddate, $freqdata) + 1;
2339 # Then get the next date
2340 my $unit = lc $freqdata->{'unit'};
2341 if ($unit eq 'day') {
2342 while ($irregularities{$issueno}) {
2343 ($year, $month, $day) = _get_next_date_day($subscription,
2344 $freqdata, $year, $month, $day);
2345 $issueno++;
2347 ($year, $month, $day) = _get_next_date_day($subscription, $freqdata,
2348 $year, $month, $day);
2350 elsif ($unit eq 'week') {
2351 while ($irregularities{$issueno}) {
2352 ($year, $month, $day) = _get_next_date_week($subscription,
2353 $freqdata, $year, $month, $day);
2354 $issueno++;
2356 ($year, $month, $day) = _get_next_date_week($subscription,
2357 $freqdata, $year, $month, $day);
2359 elsif ($unit eq 'month') {
2360 while ($irregularities{$issueno}) {
2361 ($year, $month, $day) = _get_next_date_month($subscription,
2362 $freqdata, $year, $month, $day);
2363 $issueno++;
2365 ($year, $month, $day) = _get_next_date_month($subscription,
2366 $freqdata, $year, $month, $day);
2368 elsif ($unit eq 'year') {
2369 while ($irregularities{$issueno}) {
2370 ($year, $month, $day) = _get_next_date_year($subscription,
2371 $freqdata, $year, $month, $day);
2372 $issueno++;
2374 ($year, $month, $day) = _get_next_date_year($subscription,
2375 $freqdata, $year, $month, $day);
2378 if ($updatecount){
2379 my $dbh = C4::Context->dbh;
2380 my $query = qq{
2381 UPDATE subscription
2382 SET countissuesperunit = ?
2383 WHERE subscriptionid = ?
2385 my $sth = $dbh->prepare($query);
2386 $sth->execute($subscription->{'countissuesperunit'}, $subscription->{'subscriptionid'});
2389 return sprintf("%04d-%02d-%02d", $year, $month, $day);
2393 =head2 _numeration
2395 $string = &_numeration($value,$num_type,$locale);
2397 _numeration returns the string corresponding to $value in the num_type
2398 num_type can take :
2399 -dayname
2400 -dayabrv
2401 -monthname
2402 -monthabrv
2403 -season
2404 -seasonabrv
2406 =cut
2408 sub _numeration {
2409 my ($value, $num_type, $locale) = @_;
2410 $value ||= 0;
2411 $num_type //= '';
2412 $locale ||= 'en';
2413 my $string;
2414 if ( $num_type =~ /^dayname$/ or $num_type =~ /^dayabrv$/ ) {
2415 # 1970-11-01 was a Sunday
2416 $value = $value % 7;
2417 my $dt = DateTime->new(
2418 year => 1970,
2419 month => 11,
2420 day => $value + 1,
2421 locale => $locale,
2423 $string = $num_type =~ /^dayname$/
2424 ? $dt->strftime("%A")
2425 : $dt->strftime("%a");
2426 } elsif ( $num_type =~ /^monthname$/ or $num_type =~ /^monthabrv$/ ) {
2427 $value = $value % 12;
2428 my $dt = DateTime->new(
2429 year => 1970,
2430 month => $value + 1,
2431 locale => $locale,
2433 $string = $num_type =~ /^monthname$/
2434 ? $dt->strftime("%B")
2435 : $dt->strftime("%b");
2436 } elsif ( $num_type =~ /^season$/ ) {
2437 my @seasons= qw( Spring Summer Fall Winter );
2438 $value = $value % 4;
2439 $string = $seasons[$value];
2440 } elsif ( $num_type =~ /^seasonabrv$/ ) {
2441 my @seasonsabrv= qw( Spr Sum Fal Win );
2442 $value = $value % 4;
2443 $string = $seasonsabrv[$value];
2444 } else {
2445 $string = $value;
2448 return $string;
2451 =head2 CloseSubscription
2453 Close a subscription given a subscriptionid
2455 =cut
2457 sub CloseSubscription {
2458 my ( $subscriptionid ) = @_;
2459 return unless $subscriptionid;
2460 my $dbh = C4::Context->dbh;
2461 my $sth = $dbh->prepare( q{
2462 UPDATE subscription
2463 SET closed = 1
2464 WHERE subscriptionid = ?
2465 } );
2466 $sth->execute( $subscriptionid );
2468 # Set status = missing when status = stopped
2469 $sth = $dbh->prepare( q{
2470 UPDATE serial
2471 SET status = ?
2472 WHERE subscriptionid = ?
2473 AND status = ?
2474 } );
2475 $sth->execute( STOPPED, $subscriptionid, EXPECTED );
2478 =head2 ReopenSubscription
2480 Reopen a subscription given a subscriptionid
2482 =cut
2484 sub ReopenSubscription {
2485 my ( $subscriptionid ) = @_;
2486 return unless $subscriptionid;
2487 my $dbh = C4::Context->dbh;
2488 my $sth = $dbh->prepare( q{
2489 UPDATE subscription
2490 SET closed = 0
2491 WHERE subscriptionid = ?
2492 } );
2493 $sth->execute( $subscriptionid );
2495 # Set status = expected when status = stopped
2496 $sth = $dbh->prepare( q{
2497 UPDATE serial
2498 SET status = ?
2499 WHERE subscriptionid = ?
2500 AND status = ?
2501 } );
2502 $sth->execute( EXPECTED, $subscriptionid, STOPPED );
2505 =head2 subscriptionCurrentlyOnOrder
2507 $bool = subscriptionCurrentlyOnOrder( $subscriptionid );
2509 Return 1 if subscription is currently on order else 0.
2511 =cut
2513 sub subscriptionCurrentlyOnOrder {
2514 my ( $subscriptionid ) = @_;
2515 my $dbh = C4::Context->dbh;
2516 my $query = qq|
2517 SELECT COUNT(*) FROM aqorders
2518 WHERE subscriptionid = ?
2519 AND datereceived IS NULL
2520 AND datecancellationprinted IS NULL
2522 my $sth = $dbh->prepare( $query );
2523 $sth->execute($subscriptionid);
2524 return $sth->fetchrow_array;
2527 =head2 can_claim_subscription
2529 $can = can_claim_subscription( $subscriptionid[, $userid] );
2531 Return 1 if the subscription can be claimed by the current logged user (or a given $userid), else 0.
2533 =cut
2535 sub can_claim_subscription {
2536 my ( $subscription, $userid ) = @_;
2537 return _can_do_on_subscription( $subscription, $userid, 'claim_serials' );
2540 =head2 can_edit_subscription
2542 $can = can_edit_subscription( $subscriptionid[, $userid] );
2544 Return 1 if the subscription can be edited by the current logged user (or a given $userid), else 0.
2546 =cut
2548 sub can_edit_subscription {
2549 my ( $subscription, $userid ) = @_;
2550 return _can_do_on_subscription( $subscription, $userid, 'edit_subscription' );
2553 =head2 can_show_subscription
2555 $can = can_show_subscription( $subscriptionid[, $userid] );
2557 Return 1 if the subscription can be shown by the current logged user (or a given $userid), else 0.
2559 =cut
2561 sub can_show_subscription {
2562 my ( $subscription, $userid ) = @_;
2563 return _can_do_on_subscription( $subscription, $userid, '*' );
2566 sub _can_do_on_subscription {
2567 my ( $subscription, $userid, $permission ) = @_;
2568 return 0 unless C4::Context->userenv;
2569 my $flags = C4::Context->userenv->{flags};
2570 $userid ||= C4::Context->userenv->{'id'};
2572 if ( C4::Context->preference('IndependentBranches') ) {
2573 return 1
2574 if C4::Context->IsSuperLibrarian()
2576 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2577 or (
2578 C4::Auth::haspermission( $userid,
2579 { serials => $permission } )
2580 and ( not defined $subscription->{branchcode}
2581 or $subscription->{branchcode} eq ''
2582 or $subscription->{branchcode} eq
2583 C4::Context->userenv->{'branch'} )
2586 else {
2587 return 1
2588 if C4::Context->IsSuperLibrarian()
2590 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2591 or C4::Auth::haspermission(
2592 $userid, { serials => $permission }
2596 return 0;
2599 =head2 findSerialsByStatus
2601 @serials = findSerialsByStatus($status, $subscriptionid);
2603 Returns an array of serials matching a given status and subscription id.
2605 =cut
2607 sub findSerialsByStatus {
2608 my ( $status, $subscriptionid ) = @_;
2609 my $dbh = C4::Context->dbh;
2610 my $query = q| SELECT * from serial
2611 WHERE status = ?
2612 AND subscriptionid = ?
2614 my $serials = $dbh->selectall_arrayref( $query, { Slice => {} }, $status, $subscriptionid );
2615 return @$serials;
2619 __END__
2621 =head1 AUTHOR
2623 Koha Development Team <http://koha-community.org/>
2625 =cut