Bug 20581: (follow-up) Fix fallback status_alias
[koha.git] / C4 / Serials.pm
blobe929e219ef95b07a863175ad1c26e50c6c76eb8d
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::AdditionalField;
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 # Add additional fields to the subscription into a new key "additional_fields"
278 my $additional_field_values = Koha::AdditionalField->fetch_all_values({
279 tablename => 'subscription',
280 record_id => $subscriptionid,
282 $subscription->{additional_fields} = $additional_field_values->{$subscriptionid};
284 if ( my $mana_id = $subscription->{mana_id} ) {
285 my $mana_subscription = Koha::SharedContent::get_entity_by_id(
286 'subscription', $mana_id, {usecomments => 1});
287 $subscription->{comments} = $mana_subscription->{data}->{comments};
290 return $subscription;
293 =head2 GetFullSubscription
295 $array_ref = GetFullSubscription($subscriptionid)
296 this function reads the serial table.
298 =cut
300 sub GetFullSubscription {
301 my ($subscriptionid) = @_;
303 return unless ($subscriptionid);
305 my $dbh = C4::Context->dbh;
306 my $query = qq|
307 SELECT serial.serialid,
308 serial.serialseq,
309 serial.planneddate,
310 serial.publisheddate,
311 serial.publisheddatetext,
312 serial.status,
313 serial.notes as notes,
314 year(IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate)) as year,
315 aqbooksellers.name as aqbooksellername,
316 biblio.title as bibliotitle,
317 subscription.branchcode AS branchcode,
318 subscription.subscriptionid AS subscriptionid
319 FROM serial
320 LEFT JOIN subscription ON
321 (serial.subscriptionid=subscription.subscriptionid )
322 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
323 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
324 WHERE serial.subscriptionid = ?
325 ORDER BY year DESC,
326 IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate) DESC,
327 serial.subscriptionid
329 $debug and warn "GetFullSubscription query: $query";
330 my $sth = $dbh->prepare($query);
331 $sth->execute($subscriptionid);
332 my $subscriptions = $sth->fetchall_arrayref( {} );
333 my $cannotedit = not can_edit_subscription( $subscriptions->[0] ) if scalar @$subscriptions;
334 for my $subscription ( @$subscriptions ) {
335 $subscription->{cannotedit} = $cannotedit;
337 return $subscriptions;
340 =head2 PrepareSerialsData
342 $array_ref = PrepareSerialsData($serialinfomation)
343 where serialinformation is a hashref array
345 =cut
347 sub PrepareSerialsData {
348 my ($lines) = @_;
350 return unless ($lines);
352 my %tmpresults;
353 my $year;
354 my @res;
355 my $startdate;
356 my $aqbooksellername;
357 my $bibliotitle;
358 my @loopissues;
359 my $first;
360 my $previousnote = "";
362 foreach my $subs (@{$lines}) {
363 for my $datefield ( qw(publisheddate planneddate) ) {
364 # handle 0000-00-00 dates
365 if (defined $subs->{$datefield} and $subs->{$datefield} =~ m/^00/) {
366 $subs->{$datefield} = undef;
369 $subs->{ "status" . $subs->{'status'} } = 1;
370 if ( grep { $_ == $subs->{status} } ( EXPECTED, LATE, MISSING_STATUSES, CLAIMED ) ) {
371 $subs->{"checked"} = 1;
374 if ( $subs->{'year'} && $subs->{'year'} ne "" ) {
375 $year = $subs->{'year'};
376 } else {
377 $year = "manage";
379 if ( $tmpresults{$year} ) {
380 push @{ $tmpresults{$year}->{'serials'} }, $subs;
381 } else {
382 $tmpresults{$year} = {
383 'year' => $year,
384 'aqbooksellername' => $subs->{'aqbooksellername'},
385 'bibliotitle' => $subs->{'bibliotitle'},
386 'serials' => [$subs],
387 'first' => $first,
391 foreach my $key ( sort { $b cmp $a } keys %tmpresults ) {
392 push @res, $tmpresults{$key};
394 return \@res;
397 =head2 GetSubscriptionsFromBiblionumber
399 $array_ref = GetSubscriptionsFromBiblionumber($biblionumber)
400 this function get the subscription list. it reads the subscription table.
401 return :
402 reference to an array of subscriptions which have the biblionumber given on input arg.
403 each element of this array is a hashref containing
404 startdate, histstartdate,opacnote,missinglist,recievedlist,periodicity,status & enddate
406 =cut
408 sub GetSubscriptionsFromBiblionumber {
409 my ($biblionumber) = @_;
411 return unless ($biblionumber);
413 my $dbh = C4::Context->dbh;
414 my $query = qq(
415 SELECT subscription.*,
416 branches.branchname,
417 subscriptionhistory.*,
418 aqbooksellers.name AS aqbooksellername,
419 biblio.title AS bibliotitle
420 FROM subscription
421 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
422 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
423 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
424 LEFT JOIN branches ON branches.branchcode=subscription.branchcode
425 WHERE subscription.biblionumber = ?
427 my $sth = $dbh->prepare($query);
428 $sth->execute($biblionumber);
429 my @res;
430 while ( my $subs = $sth->fetchrow_hashref ) {
431 $subs->{startdate} = output_pref( { dt => dt_from_string( $subs->{startdate} ), dateonly => 1 } );
432 $subs->{histstartdate} = output_pref( { dt => dt_from_string( $subs->{histstartdate} ), dateonly => 1 } );
433 if ( defined $subs->{histenddate} ) {
434 $subs->{histenddate} = output_pref( { dt => dt_from_string( $subs->{histenddate} ), dateonly => 1 } );
435 } else {
436 $subs->{histenddate} = "";
438 $subs->{opacnote} =~ s/\n/\<br\/\>/g;
439 $subs->{missinglist} =~ s/\n/\<br\/\>/g;
440 $subs->{recievedlist} =~ s/\n/\<br\/\>/g;
441 $subs->{ "periodicity" . $subs->{periodicity} } = 1;
442 $subs->{ "numberpattern" . $subs->{numberpattern} } = 1;
443 $subs->{ "status" . $subs->{'status'} } = 1;
445 if (not defined $subs->{enddate} ) {
446 $subs->{enddate} = '';
447 } else {
448 $subs->{enddate} = output_pref( { dt => dt_from_string( $subs->{enddate}), dateonly => 1 } );
450 $subs->{'abouttoexpire'} = abouttoexpire( $subs->{'subscriptionid'} );
451 $subs->{'subscriptionexpired'} = HasSubscriptionExpired( $subs->{'subscriptionid'} );
452 $subs->{cannotedit} = not can_edit_subscription( $subs );
453 push @res, $subs;
455 return \@res;
458 =head2 GetFullSubscriptionsFromBiblionumber
460 $array_ref = GetFullSubscriptionsFromBiblionumber($biblionumber)
461 this function reads the serial table.
463 =cut
465 sub GetFullSubscriptionsFromBiblionumber {
466 my ($biblionumber) = @_;
467 my $dbh = C4::Context->dbh;
468 my $query = qq|
469 SELECT serial.serialid,
470 serial.serialseq,
471 serial.planneddate,
472 serial.publisheddate,
473 serial.publisheddatetext,
474 serial.status,
475 serial.notes as notes,
476 year(IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate)) as year,
477 biblio.title as bibliotitle,
478 subscription.branchcode AS branchcode,
479 subscription.subscriptionid AS subscriptionid
480 FROM serial
481 LEFT JOIN subscription ON
482 (serial.subscriptionid=subscription.subscriptionid)
483 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
484 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
485 WHERE subscription.biblionumber = ?
486 ORDER BY year DESC,
487 IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate) DESC,
488 serial.subscriptionid
490 my $sth = $dbh->prepare($query);
491 $sth->execute($biblionumber);
492 my $subscriptions = $sth->fetchall_arrayref( {} );
493 my $cannotedit = not can_edit_subscription( $subscriptions->[0] ) if scalar @$subscriptions;
494 for my $subscription ( @$subscriptions ) {
495 $subscription->{cannotedit} = $cannotedit;
497 return $subscriptions;
500 =head2 SearchSubscriptions
502 @results = SearchSubscriptions($args);
504 This function returns a list of hashrefs, one for each subscription
505 that meets the conditions specified by the $args hashref.
507 The valid search fields are:
509 biblionumber
510 title
511 issn
513 callnumber
514 location
515 publisher
516 bookseller
517 branch
518 expiration_date
519 closed
521 The expiration_date search field is special; it specifies the maximum
522 subscription expiration date.
524 =cut
526 sub SearchSubscriptions {
527 my ( $args ) = @_;
529 my $additional_fields = $args->{additional_fields} // [];
530 my $matching_record_ids_for_additional_fields = [];
531 if ( @$additional_fields ) {
532 $matching_record_ids_for_additional_fields = Koha::AdditionalField->get_matching_record_ids({
533 fields => $additional_fields,
534 tablename => 'subscription',
535 exact_match => 0,
537 return () unless @$matching_record_ids_for_additional_fields;
540 my $query = q|
541 SELECT
542 subscription.notes AS publicnotes,
543 subscriptionhistory.*,
544 subscription.*,
545 biblio.notes AS biblionotes,
546 biblio.title,
547 biblio.author,
548 biblio.biblionumber,
549 aqbooksellers.name AS vendorname,
550 biblioitems.issn
551 FROM subscription
552 LEFT JOIN subscriptionhistory USING(subscriptionid)
553 LEFT JOIN biblio ON biblio.biblionumber = subscription.biblionumber
554 LEFT JOIN biblioitems ON biblioitems.biblionumber = subscription.biblionumber
555 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
557 $query .= q| WHERE 1|;
558 my @where_strs;
559 my @where_args;
560 if( $args->{biblionumber} ) {
561 push @where_strs, "biblio.biblionumber = ?";
562 push @where_args, $args->{biblionumber};
565 if( $args->{title} ){
566 my @words = split / /, $args->{title};
567 my (@strs, @args);
568 foreach my $word (@words) {
569 push @strs, "biblio.title LIKE ?";
570 push @args, "%$word%";
572 if (@strs) {
573 push @where_strs, '(' . join (' AND ', @strs) . ')';
574 push @where_args, @args;
577 if( $args->{issn} ){
578 push @where_strs, "biblioitems.issn LIKE ?";
579 push @where_args, "%$args->{issn}%";
581 if( $args->{ean} ){
582 push @where_strs, "biblioitems.ean LIKE ?";
583 push @where_args, "%$args->{ean}%";
585 if ( $args->{callnumber} ) {
586 push @where_strs, "subscription.callnumber LIKE ?";
587 push @where_args, "%$args->{callnumber}%";
589 if( $args->{publisher} ){
590 push @where_strs, "biblioitems.publishercode LIKE ?";
591 push @where_args, "%$args->{publisher}%";
593 if( $args->{bookseller} ){
594 push @where_strs, "aqbooksellers.name LIKE ?";
595 push @where_args, "%$args->{bookseller}%";
597 if( $args->{branch} ){
598 push @where_strs, "subscription.branchcode = ?";
599 push @where_args, "$args->{branch}";
601 if ( $args->{location} ) {
602 push @where_strs, "subscription.location = ?";
603 push @where_args, "$args->{location}";
605 if ( $args->{expiration_date} ) {
606 push @where_strs, "subscription.enddate <= ?";
607 push @where_args, "$args->{expiration_date}";
609 if( defined $args->{closed} ){
610 push @where_strs, "subscription.closed = ?";
611 push @where_args, "$args->{closed}";
614 if(@where_strs){
615 $query .= ' AND ' . join(' AND ', @where_strs);
617 if ( @$additional_fields ) {
618 $query .= ' AND subscriptionid IN ('
619 . join( ', ', @$matching_record_ids_for_additional_fields )
620 . ')';
623 $query .= " ORDER BY " . $args->{orderby} if $args->{orderby};
625 my $dbh = C4::Context->dbh;
626 my $sth = $dbh->prepare($query);
627 $sth->execute(@where_args);
628 my $results = $sth->fetchall_arrayref( {} );
630 for my $subscription ( @$results ) {
631 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
632 $subscription->{cannotdisplay} = not can_show_subscription( $subscription );
634 my $additional_field_values = Koha::AdditionalField->fetch_all_values({
635 record_id => $subscription->{subscriptionid},
636 tablename => 'subscription'
638 $subscription->{additional_fields} = $additional_field_values->{$subscription->{subscriptionid}};
641 return @$results;
645 =head2 GetSerials
647 ($totalissues,@serials) = GetSerials($subscriptionid);
648 this function gets every serial not arrived for a given subscription
649 as well as the number of issues registered in the database (all types)
650 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
652 FIXME: We should return \@serials.
654 =cut
656 sub GetSerials {
657 my ( $subscriptionid, $count ) = @_;
659 return unless $subscriptionid;
661 my $dbh = C4::Context->dbh;
663 # status = 2 is "arrived"
664 my $counter = 0;
665 $count = 5 unless ($count);
666 my @serials;
667 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES, NOT_ISSUED ) );
668 my $query = "SELECT serialid,serialseq, status, publisheddate,
669 publisheddatetext, planneddate,notes, routingnotes
670 FROM serial
671 WHERE subscriptionid = ? AND status NOT IN ( $statuses )
672 ORDER BY IF(publisheddate<>'0000-00-00',publisheddate,planneddate) DESC";
673 my $sth = $dbh->prepare($query);
674 $sth->execute($subscriptionid);
676 while ( my $line = $sth->fetchrow_hashref ) {
677 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
678 for my $datefield ( qw( planneddate publisheddate) ) {
679 if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
680 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
681 } else {
682 $line->{$datefield} = q{};
685 push @serials, $line;
688 # OK, now add the last 5 issues arrives/missing
689 $query = "SELECT serialid,serialseq, status, planneddate, publisheddate,
690 publisheddatetext, notes, routingnotes
691 FROM serial
692 WHERE subscriptionid = ?
693 AND status IN ( $statuses )
694 ORDER BY IF(publisheddate<>'0000-00-00',publisheddate,planneddate) DESC
696 $sth = $dbh->prepare($query);
697 $sth->execute($subscriptionid);
698 while ( ( my $line = $sth->fetchrow_hashref ) && $counter < $count ) {
699 $counter++;
700 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
701 for my $datefield ( qw( planneddate publisheddate) ) {
702 if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
703 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
704 } else {
705 $line->{$datefield} = q{};
709 push @serials, $line;
712 $query = "SELECT count(*) FROM serial WHERE subscriptionid=?";
713 $sth = $dbh->prepare($query);
714 $sth->execute($subscriptionid);
715 my ($totalissues) = $sth->fetchrow;
716 return ( $totalissues, @serials );
719 =head2 GetSerials2
721 @serials = GetSerials2($subscriptionid,$statuses);
722 this function returns every serial waited for a given subscription
723 as well as the number of issues registered in the database (all types)
724 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
726 $statuses is an arrayref of statuses and is mandatory.
728 =cut
730 sub GetSerials2 {
731 my ( $subscription, $statuses ) = @_;
733 return unless ($subscription and @$statuses);
735 my $dbh = C4::Context->dbh;
736 my $query = q|
737 SELECT serialid,serialseq, status, planneddate, publisheddate,
738 publisheddatetext, notes, routingnotes
739 FROM serial
740 WHERE subscriptionid=?
742 . q| AND status IN (| . join( ",", ('?') x @$statuses ) . q|)|
743 . q|
744 ORDER BY publisheddate,serialid DESC
746 $debug and warn "GetSerials2 query: $query";
747 my $sth = $dbh->prepare($query);
748 $sth->execute( $subscription, @$statuses );
749 my @serials;
751 while ( my $line = $sth->fetchrow_hashref ) {
752 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
753 # Format dates for display
754 for my $datefield ( qw( planneddate publisheddate ) ) {
755 if (!defined($line->{$datefield}) || $line->{$datefield} =~m/^00/) {
756 $line->{$datefield} = q{};
758 else {
759 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
762 push @serials, $line;
764 return @serials;
767 =head2 GetLatestSerials
769 \@serials = GetLatestSerials($subscriptionid,$limit)
770 get the $limit's latest serials arrived or missing for a given subscription
771 return :
772 a ref to an array which contains all of the latest serials stored into a hash.
774 =cut
776 sub GetLatestSerials {
777 my ( $subscriptionid, $limit ) = @_;
779 return unless ($subscriptionid and $limit);
781 my $dbh = C4::Context->dbh;
783 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES ) );
784 my $strsth = "SELECT serialid,serialseq, status, planneddate, publisheddate, notes
785 FROM serial
786 WHERE subscriptionid = ?
787 AND status IN ($statuses)
788 ORDER BY publisheddate DESC LIMIT 0,$limit
790 my $sth = $dbh->prepare($strsth);
791 $sth->execute($subscriptionid);
792 my @serials;
793 while ( my $line = $sth->fetchrow_hashref ) {
794 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
795 $line->{planneddate} = output_pref( { dt => dt_from_string( $line->{planneddate} ), dateonly => 1 } );
796 $line->{publisheddate} = output_pref( { dt => dt_from_string( $line->{publisheddate} ), dateonly => 1 } );
797 push @serials, $line;
800 return \@serials;
803 =head2 GetPreviousSerialid
805 $serialid = GetPreviousSerialid($subscriptionid, $nth)
806 get the $nth's previous serial for the given subscriptionid
807 return :
808 the serialid
810 =cut
812 sub GetPreviousSerialid {
813 my ( $subscriptionid, $nth ) = @_;
814 $nth ||= 1;
815 my $dbh = C4::Context->dbh;
816 my $return = undef;
818 # Status 2: Arrived
819 my $strsth = "SELECT serialid
820 FROM serial
821 WHERE subscriptionid = ?
822 AND status = 2
823 ORDER BY serialid DESC LIMIT $nth,1
825 my $sth = $dbh->prepare($strsth);
826 $sth->execute($subscriptionid);
827 my @serials;
828 my $line = $sth->fetchrow_hashref;
829 $return = $line->{'serialid'} if ($line);
831 return $return;
834 =head2 GetNextSeq
836 my (
837 $nextseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
838 $newinnerloop1, $newinnerloop2, $newinnerloop3
839 ) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate );
841 $subscription is a hashref containing all the attributes of the table
842 'subscription'.
843 $pattern is a hashref containing all the attributes of the table
844 'subscription_numberpatterns'.
845 $frequency is a hashref containing all the attributes of the table 'subscription_frequencies'
846 $planneddate is a date string in iso format.
847 This function get the next issue for the subscription given on input arg
849 =cut
851 sub GetNextSeq {
852 my ($subscription, $pattern, $frequency, $planneddate) = @_;
854 return unless ($subscription and $pattern);
856 my ( $newlastvalue1, $newlastvalue2, $newlastvalue3,
857 $newinnerloop1, $newinnerloop2, $newinnerloop3 );
858 my $count = 1;
860 if ($subscription->{'skip_serialseq'}) {
861 my @irreg = split /;/, $subscription->{'irregularity'};
862 if(@irreg > 0) {
863 my $irregularities = {};
864 $irregularities->{$_} = 1 foreach(@irreg);
865 my $issueno = GetFictiveIssueNumber($subscription, $planneddate, $frequency) + 1;
866 while($irregularities->{$issueno}) {
867 $count++;
868 $issueno++;
873 my $numberingmethod = $pattern->{numberingmethod};
874 my $calculated = "";
875 if ($numberingmethod) {
876 $calculated = $numberingmethod;
877 my $locale = $subscription->{locale};
878 $newlastvalue1 = $subscription->{lastvalue1} || 0;
879 $newlastvalue2 = $subscription->{lastvalue2} || 0;
880 $newlastvalue3 = $subscription->{lastvalue3} || 0;
881 $newinnerloop1 = $subscription->{innerloop1} || 0;
882 $newinnerloop2 = $subscription->{innerloop2} || 0;
883 $newinnerloop3 = $subscription->{innerloop3} || 0;
884 my %calc;
885 foreach(qw/X Y Z/) {
886 $calc{$_} = 1 if ($numberingmethod =~ /\{$_\}/);
889 for(my $i = 0; $i < $count; $i++) {
890 if($calc{'X'}) {
891 # check if we have to increase the new value.
892 $newinnerloop1 += 1;
893 if ($newinnerloop1 >= $pattern->{every1}) {
894 $newinnerloop1 = 0;
895 $newlastvalue1 += $pattern->{add1};
897 # reset counter if needed.
898 $newlastvalue1 = $pattern->{setto1} if ($newlastvalue1 > $pattern->{whenmorethan1});
900 if($calc{'Y'}) {
901 # check if we have to increase the new value.
902 $newinnerloop2 += 1;
903 if ($newinnerloop2 >= $pattern->{every2}) {
904 $newinnerloop2 = 0;
905 $newlastvalue2 += $pattern->{add2};
907 # reset counter if needed.
908 $newlastvalue2 = $pattern->{setto2} if ($newlastvalue2 > $pattern->{whenmorethan2});
910 if($calc{'Z'}) {
911 # check if we have to increase the new value.
912 $newinnerloop3 += 1;
913 if ($newinnerloop3 >= $pattern->{every3}) {
914 $newinnerloop3 = 0;
915 $newlastvalue3 += $pattern->{add3};
917 # reset counter if needed.
918 $newlastvalue3 = $pattern->{setto3} if ($newlastvalue3 > $pattern->{whenmorethan3});
921 if($calc{'X'}) {
922 my $newlastvalue1string = _numeration( $newlastvalue1, $pattern->{numbering1}, $locale );
923 $calculated =~ s/\{X\}/$newlastvalue1string/g;
925 if($calc{'Y'}) {
926 my $newlastvalue2string = _numeration( $newlastvalue2, $pattern->{numbering2}, $locale );
927 $calculated =~ s/\{Y\}/$newlastvalue2string/g;
929 if($calc{'Z'}) {
930 my $newlastvalue3string = _numeration( $newlastvalue3, $pattern->{numbering3}, $locale );
931 $calculated =~ s/\{Z\}/$newlastvalue3string/g;
935 return ($calculated,
936 $newlastvalue1, $newlastvalue2, $newlastvalue3,
937 $newinnerloop1, $newinnerloop2, $newinnerloop3);
940 =head2 GetSeq
942 $calculated = GetSeq($subscription, $pattern)
943 $subscription is a hashref containing all the attributes of the table 'subscription'
944 $pattern is a hashref containing all the attributes of the table 'subscription_numberpatterns'
945 this function transforms {X},{Y},{Z} to 150,0,0 for example.
946 return:
947 the sequence in string format
949 =cut
951 sub GetSeq {
952 my ($subscription, $pattern) = @_;
954 return unless ($subscription and $pattern);
956 my $locale = $subscription->{locale};
958 my $calculated = $pattern->{numberingmethod};
960 my $newlastvalue1 = $subscription->{'lastvalue1'} || 0;
961 $newlastvalue1 = _numeration($newlastvalue1, $pattern->{numbering1}, $locale) if ($pattern->{numbering1}); # reset counter if needed.
962 $calculated =~ s/\{X\}/$newlastvalue1/g;
964 my $newlastvalue2 = $subscription->{'lastvalue2'} || 0;
965 $newlastvalue2 = _numeration($newlastvalue2, $pattern->{numbering2}, $locale) if ($pattern->{numbering2}); # reset counter if needed.
966 $calculated =~ s/\{Y\}/$newlastvalue2/g;
968 my $newlastvalue3 = $subscription->{'lastvalue3'} || 0;
969 $newlastvalue3 = _numeration($newlastvalue3, $pattern->{numbering3}, $locale) if ($pattern->{numbering3}); # reset counter if needed.
970 $calculated =~ s/\{Z\}/$newlastvalue3/g;
971 return $calculated;
974 =head2 GetExpirationDate
976 $enddate = GetExpirationDate($subscriptionid, [$startdate])
978 this function return the next expiration date for a subscription given on input args.
980 return
981 the enddate or undef
983 =cut
985 sub GetExpirationDate {
986 my ( $subscriptionid, $startdate ) = @_;
988 return unless ($subscriptionid);
990 my $dbh = C4::Context->dbh;
991 my $subscription = GetSubscription($subscriptionid);
992 my $enddate;
994 # we don't do the same test if the subscription is based on X numbers or on X weeks/months
995 $enddate = $startdate || $subscription->{startdate};
996 my @date = split( /-/, $enddate );
998 return if ( scalar(@date) != 3 || not check_date(@date) );
1000 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1001 if ( $frequency and $frequency->{unit} ) {
1003 # If Not Irregular
1004 if ( my $length = $subscription->{numberlength} ) {
1006 #calculate the date of the last issue.
1007 for ( my $i = 1 ; $i <= $length ; $i++ ) {
1008 $enddate = GetNextDate( $subscription, $enddate, $frequency );
1010 } elsif ( $subscription->{monthlength} ) {
1011 if ( $$subscription{startdate} ) {
1012 my @enddate = Add_Delta_YM( $date[0], $date[1], $date[2], 0, $subscription->{monthlength} );
1013 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1015 } elsif ( $subscription->{weeklength} ) {
1016 if ( $$subscription{startdate} ) {
1017 my @date = split( /-/, $subscription->{startdate} );
1018 my @enddate = Add_Delta_Days( $date[0], $date[1], $date[2], $subscription->{weeklength} * 7 );
1019 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1021 } else {
1022 $enddate = $subscription->{enddate};
1024 return $enddate;
1025 } else {
1026 return $subscription->{enddate};
1030 =head2 CountSubscriptionFromBiblionumber
1032 $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber)
1033 this returns a count of the subscriptions for a given biblionumber
1034 return :
1035 the number of subscriptions
1037 =cut
1039 sub CountSubscriptionFromBiblionumber {
1040 my ($biblionumber) = @_;
1042 return unless ($biblionumber);
1044 my $dbh = C4::Context->dbh;
1045 my $query = "SELECT count(*) FROM subscription WHERE biblionumber=?";
1046 my $sth = $dbh->prepare($query);
1047 $sth->execute($biblionumber);
1048 my $subscriptionsnumber = $sth->fetchrow;
1049 return $subscriptionsnumber;
1052 =head2 ModSubscriptionHistory
1054 ModSubscriptionHistory($subscriptionid,$histstartdate,$enddate,$recievedlist,$missinglist,$opacnote,$librariannote);
1056 this function modifies the history of a subscription. Put your new values on input arg.
1057 returns the number of rows affected
1059 =cut
1061 sub ModSubscriptionHistory {
1062 my ( $subscriptionid, $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote ) = @_;
1064 return unless ($subscriptionid);
1066 my $dbh = C4::Context->dbh;
1067 my $query = "UPDATE subscriptionhistory
1068 SET histstartdate=?,histenddate=?,recievedlist=?,missinglist=?,opacnote=?,librariannote=?
1069 WHERE subscriptionid=?
1071 my $sth = $dbh->prepare($query);
1072 $receivedlist =~ s/^; // if $receivedlist;
1073 $missinglist =~ s/^; // if $missinglist;
1074 $opacnote =~ s/^; // if $opacnote;
1075 $sth->execute( $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote, $subscriptionid );
1076 return $sth->rows;
1079 =head2 ModSerialStatus
1081 ModSerialStatus($serialid, $serialseq, $planneddate, $publisheddate,
1082 $publisheddatetext, $status, $notes);
1084 This function modify the serial status. Serial status is a number.(eg 2 is "arrived")
1085 Note : if we change from "waited" to something else,then we will have to create a new "waited" entry
1087 =cut
1089 sub ModSerialStatus {
1090 my ($serialid, $serialseq, $planneddate, $publisheddate, $publisheddatetext,
1091 $status, $notes) = @_;
1093 return unless ($serialid);
1095 #It is a usual serial
1096 # 1st, get previous status :
1097 my $dbh = C4::Context->dbh;
1098 my $query = "SELECT serial.subscriptionid,serial.status,subscription.periodicity
1099 FROM serial, subscription
1100 WHERE serial.subscriptionid=subscription.subscriptionid
1101 AND serialid=?";
1102 my $sth = $dbh->prepare($query);
1103 $sth->execute($serialid);
1104 my ( $subscriptionid, $oldstatus, $periodicity ) = $sth->fetchrow;
1105 my $frequency = GetSubscriptionFrequency($periodicity);
1107 # change status & update subscriptionhistory
1108 my $val;
1109 if ( $status == DELETED ) {
1110 DelIssue( { 'serialid' => $serialid, 'subscriptionid' => $subscriptionid, 'serialseq' => $serialseq } );
1111 } else {
1113 my $query = '
1114 UPDATE serial
1115 SET serialseq = ?, publisheddate = ?, publisheddatetext = ?,
1116 planneddate = ?, status = ?, notes = ?
1117 WHERE serialid = ?
1119 $sth = $dbh->prepare($query);
1120 $sth->execute( $serialseq, $publisheddate, $publisheddatetext,
1121 $planneddate, $status, $notes, $serialid );
1122 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1123 $sth = $dbh->prepare($query);
1124 $sth->execute($subscriptionid);
1125 my $val = $sth->fetchrow_hashref;
1126 unless ( $val->{manualhistory} ) {
1127 $query = "SELECT missinglist,recievedlist FROM subscriptionhistory WHERE subscriptionid=?";
1128 $sth = $dbh->prepare($query);
1129 $sth->execute($subscriptionid);
1130 my ( $missinglist, $recievedlist ) = $sth->fetchrow;
1132 if ( $status == ARRIVED || ($oldstatus == ARRIVED && $status != ARRIVED) ) {
1133 $recievedlist .= "; $serialseq"
1134 if ($recievedlist !~ /(^|;)\s*$serialseq(?=;|$)/);
1137 # in case serial has been previously marked as missing
1138 if (grep /$status/, (EXPECTED, ARRIVED, LATE, CLAIMED)) {
1139 $missinglist=~ s/(^|;)\s*$serialseq(?=;|$)//g;
1142 $missinglist .= "; $serialseq"
1143 if ( ( grep { $_ == $status } ( MISSING_STATUSES ) ) && ( $missinglist !~/(^|;)\s*$serialseq(?=;|$)/ ) );
1144 $missinglist .= "; not issued $serialseq"
1145 if ( $status == NOT_ISSUED && $missinglist !~ /(^|;)\s*$serialseq(?=;|$)/ );
1147 $query = "UPDATE subscriptionhistory SET recievedlist=?, missinglist=? WHERE subscriptionid=?";
1148 $sth = $dbh->prepare($query);
1149 $recievedlist =~ s/^; //;
1150 $missinglist =~ s/^; //;
1151 $sth->execute( $recievedlist, $missinglist, $subscriptionid );
1155 # create new expected entry if needed (ie : was "expected" and has changed)
1156 my $otherIssueExpected = scalar findSerialsByStatus(EXPECTED, $subscriptionid);
1157 if ( !$otherIssueExpected && $oldstatus == EXPECTED && $status != EXPECTED ) {
1158 my $subscription = GetSubscription($subscriptionid);
1159 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1160 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1162 # next issue number
1163 my (
1164 $newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
1165 $newinnerloop1, $newinnerloop2, $newinnerloop3
1167 = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate );
1169 # next date (calculated from actual date & frequency parameters)
1170 my $nextpublisheddate = GetNextDate($subscription, $publisheddate, $frequency, 1);
1171 my $nextpubdate = $nextpublisheddate;
1172 $query = "UPDATE subscription SET lastvalue1=?, lastvalue2=?, lastvalue3=?, innerloop1=?, innerloop2=?, innerloop3=?
1173 WHERE subscriptionid = ?";
1174 $sth = $dbh->prepare($query);
1175 $sth->execute( $newlastvalue1, $newlastvalue2, $newlastvalue3, $newinnerloop1, $newinnerloop2, $newinnerloop3, $subscriptionid );
1177 NewIssue( $newserialseq, $subscriptionid, $subscription->{'biblionumber'}, 1, $nextpubdate, $nextpubdate );
1179 # check if an alert must be sent... (= a letter is defined & status became "arrived"
1180 if ( $subscription->{letter} && $status == ARRIVED && $oldstatus != ARRIVED ) {
1181 require C4::Letters;
1182 C4::Letters::SendAlerts( 'issue', $serialid, $subscription->{letter} );
1186 return;
1189 =head2 GetNextExpected
1191 $nextexpected = GetNextExpected($subscriptionid)
1193 Get the planneddate for the current expected issue of the subscription.
1195 returns a hashref:
1197 $nextexepected = {
1198 serialid => int
1199 planneddate => ISO date
1202 =cut
1204 sub GetNextExpected {
1205 my ($subscriptionid) = @_;
1207 my $dbh = C4::Context->dbh;
1208 my $query = qq{
1209 SELECT *
1210 FROM serial
1211 WHERE subscriptionid = ?
1212 AND status = ?
1213 LIMIT 1
1215 my $sth = $dbh->prepare($query);
1217 # Each subscription has only one 'expected' issue.
1218 $sth->execute( $subscriptionid, EXPECTED );
1219 my $nextissue = $sth->fetchrow_hashref;
1220 if ( !$nextissue ) {
1221 $query = qq{
1222 SELECT *
1223 FROM serial
1224 WHERE subscriptionid = ?
1225 ORDER BY publisheddate DESC
1226 LIMIT 1
1228 $sth = $dbh->prepare($query);
1229 $sth->execute($subscriptionid);
1230 $nextissue = $sth->fetchrow_hashref;
1232 foreach(qw/planneddate publisheddate/) {
1233 if ( !defined $nextissue->{$_} ) {
1234 # or should this default to 1st Jan ???
1235 $nextissue->{$_} = strftime( '%Y-%m-%d', localtime );
1237 $nextissue->{$_} = ($nextissue->{$_} ne '0000-00-00')
1238 ? $nextissue->{$_}
1239 : undef;
1242 return $nextissue;
1245 =head2 ModNextExpected
1247 ModNextExpected($subscriptionid,$date)
1249 Update the planneddate for the current expected issue of the subscription.
1250 This will modify all future prediction results.
1252 C<$date> is an ISO date.
1254 returns 0
1256 =cut
1258 sub ModNextExpected {
1259 my ( $subscriptionid, $date ) = @_;
1260 my $dbh = C4::Context->dbh;
1262 #FIXME: Would expect to only set planneddate, but we set both on new issue creation, so updating it here
1263 my $sth = $dbh->prepare('UPDATE serial SET planneddate=?,publisheddate=? WHERE subscriptionid=? AND status=?');
1265 # Each subscription has only one 'expected' issue.
1266 $sth->execute( $date, $date, $subscriptionid, EXPECTED );
1267 return 0;
1271 =head2 GetSubscriptionIrregularities
1273 =over 4
1275 =item @irreg = &GetSubscriptionIrregularities($subscriptionid);
1276 get the list of irregularities for a subscription
1278 =back
1280 =cut
1282 sub GetSubscriptionIrregularities {
1283 my $subscriptionid = shift;
1285 return unless $subscriptionid;
1287 my $dbh = C4::Context->dbh;
1288 my $query = qq{
1289 SELECT irregularity
1290 FROM subscription
1291 WHERE subscriptionid = ?
1293 my $sth = $dbh->prepare($query);
1294 $sth->execute($subscriptionid);
1296 my ($result) = $sth->fetchrow_array;
1297 my @irreg = split /;/, $result;
1299 return @irreg;
1302 =head2 ModSubscription
1304 this function modifies a subscription. Put all new values on input args.
1305 returns the number of rows affected
1307 =cut
1309 sub ModSubscription {
1310 my (
1311 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
1312 $periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
1313 $numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
1314 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
1315 $biblionumber, $callnumber, $notes, $letter, $manualhistory,
1316 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1317 $graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
1318 $itemtype, $previousitemtype
1319 ) = @_;
1321 my $dbh = C4::Context->dbh;
1322 my $query = "UPDATE subscription
1323 SET librarian=?, branchcode=?, aqbooksellerid=?, cost=?, aqbudgetid=?,
1324 startdate=?, periodicity=?, firstacquidate=?, irregularity=?,
1325 numberpattern=?, locale=?, numberlength=?, weeklength=?, monthlength=?,
1326 lastvalue1=?, innerloop1=?, lastvalue2=?, innerloop2=?,
1327 lastvalue3=?, innerloop3=?, status=?, biblionumber=?,
1328 callnumber=?, notes=?, letter=?, manualhistory=?,
1329 internalnotes=?, serialsadditems=?, staffdisplaycount=?,
1330 opacdisplaycount=?, graceperiod=?, location = ?, enddate=?,
1331 skip_serialseq=?, itemtype=?, previousitemtype=?
1332 WHERE subscriptionid = ?";
1334 my $sth = $dbh->prepare($query);
1335 $sth->execute(
1336 $auser, $branchcode, $aqbooksellerid, $cost,
1337 $aqbudgetid, $startdate, $periodicity, $firstacquidate,
1338 $irregularity, $numberpattern, $locale, $numberlength,
1339 $weeklength, $monthlength, $lastvalue1, $innerloop1,
1340 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3,
1341 $status, $biblionumber, $callnumber, $notes,
1342 $letter, ($manualhistory ? $manualhistory : 0),
1343 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1344 $graceperiod, $location, $enddate, $skip_serialseq,
1345 $itemtype, $previousitemtype,
1346 $subscriptionid
1348 my $rows = $sth->rows;
1350 logaction( "SERIAL", "MODIFY", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1351 return $rows;
1354 =head2 NewSubscription
1356 $subscriptionid = &NewSubscription($auser,branchcode,$aqbooksellerid,$cost,$aqbudgetid,$biblionumber,
1357 $startdate,$periodicity,$numberlength,$weeklength,$monthlength,
1358 $lastvalue1,$innerloop1,$lastvalue2,$innerloop2,$lastvalue3,$innerloop3,
1359 $status, $notes, $letter, $firstacquidate, $irregularity, $numberpattern,
1360 $locale, $callnumber, $manualhistory, $internalnotes, $serialsadditems,
1361 $staffdisplaycount, $opacdisplaycount, $graceperiod, $location, $enddate,
1362 $skip_serialseq, $itemtype, $previousitemtype);
1364 Create a new subscription with value given on input args.
1366 return :
1367 the id of this new subscription
1369 =cut
1371 sub NewSubscription {
1372 my (
1373 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1374 $startdate, $periodicity, $numberlength, $weeklength, $monthlength,
1375 $lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
1376 $innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
1377 $numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
1378 $serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
1379 $location, $enddate, $skip_serialseq, $itemtype, $previousitemtype, $mana_id
1380 ) = @_;
1381 my $dbh = C4::Context->dbh;
1383 #save subscription (insert into database)
1384 my $query = qq|
1385 INSERT INTO subscription
1386 (librarian, branchcode, aqbooksellerid, cost, aqbudgetid,
1387 biblionumber, startdate, periodicity, numberlength, weeklength,
1388 monthlength, lastvalue1, innerloop1, lastvalue2, innerloop2,
1389 lastvalue3, innerloop3, status, notes, letter, firstacquidate,
1390 irregularity, numberpattern, locale, callnumber,
1391 manualhistory, internalnotes, serialsadditems, staffdisplaycount,
1392 opacdisplaycount, graceperiod, location, enddate, skip_serialseq,
1393 itemtype, previousitemtype, mana_id)
1394 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, ?)
1396 my $sth = $dbh->prepare($query);
1397 $sth->execute(
1398 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1399 $startdate, $periodicity, $numberlength, $weeklength,
1400 $monthlength, $lastvalue1, $innerloop1, $lastvalue2, $innerloop2,
1401 $lastvalue3, $innerloop3, $status, $notes, $letter,
1402 $firstacquidate, $irregularity, $numberpattern, $locale, $callnumber,
1403 $manualhistory, $internalnotes, $serialsadditems, $staffdisplaycount,
1404 $opacdisplaycount, $graceperiod, $location, $enddate, $skip_serialseq,
1405 $itemtype, $previousitemtype, $mana_id
1408 my $subscriptionid = $dbh->{'mysql_insertid'};
1409 unless ($enddate) {
1410 $enddate = GetExpirationDate( $subscriptionid, $startdate );
1411 $query = qq|
1412 UPDATE subscription
1413 SET enddate=?
1414 WHERE subscriptionid=?
1416 $sth = $dbh->prepare($query);
1417 $sth->execute( $enddate, $subscriptionid );
1420 # then create the 1st expected number
1421 $query = qq(
1422 INSERT INTO subscriptionhistory
1423 (biblionumber, subscriptionid, histstartdate, missinglist, recievedlist)
1424 VALUES (?,?,?, '', '')
1426 $sth = $dbh->prepare($query);
1427 $sth->execute( $biblionumber, $subscriptionid, $startdate);
1429 # reread subscription to get a hash (for calculation of the 1st issue number)
1430 my $subscription = GetSubscription($subscriptionid);
1431 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1433 # calculate issue number
1434 my $serialseq = GetSeq($subscription, $pattern) || q{};
1436 Koha::Serial->new(
1438 serialseq => $serialseq,
1439 serialseq_x => $subscription->{'lastvalue1'},
1440 serialseq_y => $subscription->{'lastvalue2'},
1441 serialseq_z => $subscription->{'lastvalue3'},
1442 subscriptionid => $subscriptionid,
1443 biblionumber => $biblionumber,
1444 status => EXPECTED,
1445 planneddate => $firstacquidate,
1446 publisheddate => $firstacquidate,
1448 )->store();
1450 logaction( "SERIAL", "ADD", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1452 #set serial flag on biblio if not already set.
1453 my $biblio = Koha::Biblios->find( $biblionumber );
1454 if ( $biblio and !$biblio->serial ) {
1455 my $record = GetMarcBiblio({ biblionumber => $biblionumber });
1456 my ( $tag, $subf ) = GetMarcFromKohaField( 'biblio.serial', $biblio->frameworkcode );
1457 if ($tag) {
1458 eval { $record->field($tag)->update( $subf => 1 ); };
1460 ModBiblio( $record, $biblionumber, $biblio->frameworkcode );
1462 return $subscriptionid;
1465 =head2 ReNewSubscription
1467 ReNewSubscription($subscriptionid,$user,$startdate,$numberlength,$weeklength,$monthlength,$note)
1469 this function renew a subscription with values given on input args.
1471 =cut
1473 sub ReNewSubscription {
1474 my ( $subscriptionid, $user, $startdate, $numberlength, $weeklength, $monthlength, $note ) = @_;
1475 my $dbh = C4::Context->dbh;
1476 my $subscription = GetSubscription($subscriptionid);
1477 my $query = qq|
1478 SELECT *
1479 FROM biblio
1480 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
1481 WHERE biblio.biblionumber=?
1483 my $sth = $dbh->prepare($query);
1484 $sth->execute( $subscription->{biblionumber} );
1485 my $biblio = $sth->fetchrow_hashref;
1487 if ( C4::Context->preference("RenewSerialAddsSuggestion") ) {
1488 require C4::Suggestions;
1489 C4::Suggestions::NewSuggestion(
1490 { 'suggestedby' => $user,
1491 'title' => $subscription->{bibliotitle},
1492 'author' => $biblio->{author},
1493 'publishercode' => $biblio->{publishercode},
1494 'note' => $biblio->{note},
1495 'biblionumber' => $subscription->{biblionumber}
1500 $numberlength ||= 0; # Should not we raise an exception instead?
1501 $weeklength ||= 0;
1503 # renew subscription
1504 $query = qq|
1505 UPDATE subscription
1506 SET startdate=?,numberlength=?,weeklength=?,monthlength=?,reneweddate=NOW()
1507 WHERE subscriptionid=?
1509 $sth = $dbh->prepare($query);
1510 $sth->execute( $startdate, $numberlength, $weeklength, $monthlength, $subscriptionid );
1511 my $enddate = GetExpirationDate($subscriptionid);
1512 $debug && warn "enddate :$enddate";
1513 $query = qq|
1514 UPDATE subscription
1515 SET enddate=?
1516 WHERE subscriptionid=?
1518 $sth = $dbh->prepare($query);
1519 $sth->execute( $enddate, $subscriptionid );
1521 logaction( "SERIAL", "RENEW", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1522 return;
1525 =head2 NewIssue
1527 NewIssue($serialseq,$subscriptionid,$biblionumber,$status, $planneddate, $publisheddate, $notes)
1529 Create a new issue stored on the database.
1530 Note : we have to update the recievedlist and missinglist on subscriptionhistory for this subscription.
1531 returns the serial id
1533 =cut
1535 sub NewIssue {
1536 my ( $serialseq, $subscriptionid, $biblionumber, $status, $planneddate,
1537 $publisheddate, $publisheddatetext, $notes ) = @_;
1538 ### FIXME biblionumber CAN be provided by subscriptionid. So Do we STILL NEED IT ?
1540 return unless ($subscriptionid);
1542 my $schema = Koha::Database->new()->schema();
1544 my $subscription = Koha::Subscriptions->find( $subscriptionid );
1546 my $serial = Koha::Serial->new(
1548 serialseq => $serialseq,
1549 serialseq_x => $subscription->lastvalue1(),
1550 serialseq_y => $subscription->lastvalue2(),
1551 serialseq_z => $subscription->lastvalue3(),
1552 subscriptionid => $subscriptionid,
1553 biblionumber => $biblionumber,
1554 status => $status,
1555 planneddate => $planneddate,
1556 publisheddate => $publisheddate,
1557 publisheddatetext => $publisheddatetext,
1558 notes => $notes,
1560 )->store();
1562 my $serialid = $serial->id();
1564 my $subscription_history = Koha::Subscription::Histories->find($subscriptionid);
1565 my $missinglist = $subscription_history->missinglist();
1566 my $recievedlist = $subscription_history->recievedlist();
1568 if ( $status == ARRIVED ) {
1569 ### TODO Add a feature that improves recognition and description.
1570 ### As such count (serialseq) i.e. : N18,2(N19),N20
1571 ### Would use substr and index But be careful to previous presence of ()
1572 $recievedlist .= "; $serialseq" unless ( index( $recievedlist, $serialseq ) > 0 );
1574 if ( grep { /^$status$/ } (MISSING_STATUSES) ) {
1575 $missinglist .= "; $serialseq" unless ( index( $missinglist, $serialseq ) > 0 );
1578 $recievedlist =~ s/^; //;
1579 $missinglist =~ s/^; //;
1581 $subscription_history->recievedlist($recievedlist);
1582 $subscription_history->missinglist($missinglist);
1583 $subscription_history->store();
1585 return $serialid;
1588 =head2 HasSubscriptionStrictlyExpired
1590 1 or 0 = HasSubscriptionStrictlyExpired($subscriptionid)
1592 the subscription has stricly expired when today > the end subscription date
1594 return :
1595 1 if true, 0 if false, -1 if the expiration date is not set.
1597 =cut
1599 sub HasSubscriptionStrictlyExpired {
1601 # Getting end of subscription date
1602 my ($subscriptionid) = @_;
1604 return unless ($subscriptionid);
1606 my $dbh = C4::Context->dbh;
1607 my $subscription = GetSubscription($subscriptionid);
1608 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1610 # If the expiration date is set
1611 if ( $expirationdate != 0 ) {
1612 my ( $endyear, $endmonth, $endday ) = split( '-', $expirationdate );
1614 # Getting today's date
1615 my ( $nowyear, $nowmonth, $nowday ) = Today();
1617 # if today's date > expiration date, then the subscription has stricly expired
1618 if ( Delta_Days( $nowyear, $nowmonth, $nowday, $endyear, $endmonth, $endday ) < 0 ) {
1619 return 1;
1620 } else {
1621 return 0;
1623 } else {
1625 # There are some cases where the expiration date is not set
1626 # As we can't determine if the subscription has expired on a date-basis,
1627 # we return -1;
1628 return -1;
1632 =head2 HasSubscriptionExpired
1634 $has_expired = HasSubscriptionExpired($subscriptionid)
1636 the subscription has expired when the next issue to arrive is out of subscription limit.
1638 return :
1639 0 if the subscription has not expired
1640 1 if the subscription has expired
1641 2 if has subscription does not have a valid expiration date set
1643 =cut
1645 sub HasSubscriptionExpired {
1646 my ($subscriptionid) = @_;
1648 return unless ($subscriptionid);
1650 my $dbh = C4::Context->dbh;
1651 my $subscription = GetSubscription($subscriptionid);
1652 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1653 if ( $frequency and $frequency->{unit} ) {
1654 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1655 if (!defined $expirationdate) {
1656 $expirationdate = q{};
1658 my $query = qq|
1659 SELECT max(planneddate)
1660 FROM serial
1661 WHERE subscriptionid=?
1663 my $sth = $dbh->prepare($query);
1664 $sth->execute($subscriptionid);
1665 my ($res) = $sth->fetchrow;
1666 if (!$res || $res=~m/^0000/) {
1667 return 0;
1669 my @res = split( /-/, $res );
1670 my @endofsubscriptiondate = split( /-/, $expirationdate );
1671 return 2 if ( scalar(@res) != 3 || scalar(@endofsubscriptiondate) != 3 || not check_date(@res) || not check_date(@endofsubscriptiondate) );
1672 return 1
1673 if ( ( @endofsubscriptiondate && Delta_Days( $res[0], $res[1], $res[2], $endofsubscriptiondate[0], $endofsubscriptiondate[1], $endofsubscriptiondate[2] ) <= 0 )
1674 || ( !$res ) );
1675 return 0;
1676 } else {
1677 # Irregular
1678 if ( $subscription->{'numberlength'} ) {
1679 my $countreceived = countissuesfrom( $subscriptionid, $subscription->{'startdate'} );
1680 return 1 if ( $countreceived > $subscription->{'numberlength'} );
1681 return 0;
1682 } else {
1683 return 0;
1686 return 0; # Notice that you'll never get here.
1689 =head2 DelSubscription
1691 DelSubscription($subscriptionid)
1692 this function deletes subscription which has $subscriptionid as id.
1694 =cut
1696 sub DelSubscription {
1697 my ($subscriptionid) = @_;
1698 my $dbh = C4::Context->dbh;
1699 $dbh->do("DELETE FROM subscription WHERE subscriptionid=?", undef, $subscriptionid);
1700 $dbh->do("DELETE FROM subscriptionhistory WHERE subscriptionid=?", undef, $subscriptionid);
1701 $dbh->do("DELETE FROM serial WHERE subscriptionid=?", undef, $subscriptionid);
1703 my $afs = Koha::AdditionalField->all({tablename => 'subscription'});
1704 foreach my $af (@$afs) {
1705 $af->delete_values({record_id => $subscriptionid});
1708 logaction( "SERIAL", "DELETE", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1711 =head2 DelIssue
1713 DelIssue($serialseq,$subscriptionid)
1714 this function deletes an issue which has $serialseq and $subscriptionid given on input arg.
1716 returns the number of rows affected
1718 =cut
1720 sub DelIssue {
1721 my ($dataissue) = @_;
1722 my $dbh = C4::Context->dbh;
1723 ### TODO Add itemdeletion. Would need to get itemnumbers. Should be in a pref ?
1725 my $query = qq|
1726 DELETE FROM serial
1727 WHERE serialid= ?
1728 AND subscriptionid= ?
1730 my $mainsth = $dbh->prepare($query);
1731 $mainsth->execute( $dataissue->{'serialid'}, $dataissue->{'subscriptionid'} );
1733 #Delete element from subscription history
1734 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1735 my $sth = $dbh->prepare($query);
1736 $sth->execute( $dataissue->{'subscriptionid'} );
1737 my $val = $sth->fetchrow_hashref;
1738 unless ( $val->{manualhistory} ) {
1739 my $query = qq|
1740 SELECT * FROM subscriptionhistory
1741 WHERE subscriptionid= ?
1743 my $sth = $dbh->prepare($query);
1744 $sth->execute( $dataissue->{'subscriptionid'} );
1745 my $data = $sth->fetchrow_hashref;
1746 my $serialseq = $dataissue->{'serialseq'};
1747 $data->{'missinglist'} =~ s/\b$serialseq\b//;
1748 $data->{'recievedlist'} =~ s/\b$serialseq\b//;
1749 my $strsth = "UPDATE subscriptionhistory SET " . join( ",", map { join( "=", $_, $dbh->quote( $data->{$_} ) ) } keys %$data ) . " WHERE subscriptionid=?";
1750 $sth = $dbh->prepare($strsth);
1751 $sth->execute( $dataissue->{'subscriptionid'} );
1754 return $mainsth->rows;
1757 =head2 GetLateOrMissingIssues
1759 @issuelist = GetLateMissingIssues($supplierid,$serialid)
1761 this function selects missing issues on database - where serial.status = MISSING* or serial.status = LATE or planneddate<now
1763 return :
1764 the issuelist as an array of hash refs. Each element of this array contains
1765 name,title,planneddate,serialseq,serial.subscriptionid from tables : subscription, serial & biblio
1767 =cut
1769 sub GetLateOrMissingIssues {
1770 my ( $supplierid, $serialid, $order ) = @_;
1772 return unless ( $supplierid or $serialid );
1774 my $dbh = C4::Context->dbh;
1776 my $sth;
1777 my $byserial = '';
1778 if ($serialid) {
1779 $byserial = "and serialid = " . $serialid;
1781 if ($order) {
1782 $order .= ", title";
1783 } else {
1784 $order = "title";
1786 my $missing_statuses_string = join ',', (MISSING_STATUSES);
1787 if ($supplierid) {
1788 $sth = $dbh->prepare(
1789 "SELECT
1790 serialid, aqbooksellerid, name,
1791 biblio.title, biblioitems.issn, planneddate, serialseq,
1792 serial.status, serial.subscriptionid, claimdate, claims_count,
1793 subscription.branchcode
1794 FROM serial
1795 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1796 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1797 LEFT JOIN biblioitems ON subscription.biblionumber=biblioitems.biblionumber
1798 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1799 WHERE subscription.subscriptionid = serial.subscriptionid
1800 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1801 AND subscription.aqbooksellerid=$supplierid
1802 $byserial
1803 ORDER BY $order"
1805 } else {
1806 $sth = $dbh->prepare(
1807 "SELECT
1808 serialid, aqbooksellerid, name,
1809 biblio.title, planneddate, serialseq,
1810 serial.status, serial.subscriptionid, claimdate, claims_count,
1811 subscription.branchcode
1812 FROM serial
1813 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1814 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1815 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1816 WHERE subscription.subscriptionid = serial.subscriptionid
1817 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1818 $byserial
1819 ORDER BY $order"
1822 $sth->execute( EXPECTED, LATE, CLAIMED );
1823 my @issuelist;
1824 while ( my $line = $sth->fetchrow_hashref ) {
1826 if ($line->{planneddate} && $line->{planneddate} !~/^0+\-/) {
1827 $line->{planneddateISO} = $line->{planneddate};
1828 $line->{planneddate} = output_pref( { dt => dt_from_string( $line->{"planneddate"} ), dateonly => 1 } );
1830 if ($line->{claimdate} && $line->{claimdate} !~/^0+\-/) {
1831 $line->{claimdateISO} = $line->{claimdate};
1832 $line->{claimdate} = output_pref( { dt => dt_from_string( $line->{"claimdate"} ), dateonly => 1 } );
1834 $line->{"status".$line->{status}} = 1;
1836 my $additional_field_values = Koha::AdditionalField->fetch_all_values({
1837 record_id => $line->{subscriptionid},
1838 tablename => 'subscription'
1840 %$line = ( %$line, additional_fields => $additional_field_values->{$line->{subscriptionid}} );
1842 push @issuelist, $line;
1844 return @issuelist;
1847 =head2 updateClaim
1849 &updateClaim($serialid)
1851 this function updates the time when a claim is issued for late/missing items
1853 called from claims.pl file
1855 =cut
1857 sub updateClaim {
1858 my ($serialids) = @_;
1859 return unless $serialids;
1860 unless ( ref $serialids ) {
1861 $serialids = [ $serialids ];
1863 my $dbh = C4::Context->dbh;
1864 return $dbh->do(q|
1865 UPDATE serial
1866 SET claimdate = NOW(),
1867 claims_count = claims_count + 1,
1868 status = ?
1869 WHERE serialid in (| . join( q|,|, (q|?|) x @$serialids ) . q|)|,
1870 {}, CLAIMED, @$serialids );
1873 =head2 check_routing
1875 $result = &check_routing($subscriptionid)
1877 this function checks to see if a serial has a routing list and returns the count of routingid
1878 used to show either an 'add' or 'edit' link
1880 =cut
1882 sub check_routing {
1883 my ($subscriptionid) = @_;
1885 return unless ($subscriptionid);
1887 my $dbh = C4::Context->dbh;
1888 my $sth = $dbh->prepare(
1889 "SELECT count(routingid) routingids FROM subscription LEFT JOIN subscriptionroutinglist
1890 ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
1891 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC
1894 $sth->execute($subscriptionid);
1895 my $line = $sth->fetchrow_hashref;
1896 my $result = $line->{'routingids'};
1897 return $result;
1900 =head2 addroutingmember
1902 addroutingmember($borrowernumber,$subscriptionid)
1904 this function takes a borrowernumber and subscriptionid and adds the member to the
1905 routing list for that serial subscription and gives them a rank on the list
1906 of either 1 or highest current rank + 1
1908 =cut
1910 sub addroutingmember {
1911 my ( $borrowernumber, $subscriptionid ) = @_;
1913 return unless ($borrowernumber and $subscriptionid);
1915 my $rank;
1916 my $dbh = C4::Context->dbh;
1917 my $sth = $dbh->prepare( "SELECT max(ranking) rank FROM subscriptionroutinglist WHERE subscriptionid = ?" );
1918 $sth->execute($subscriptionid);
1919 while ( my $line = $sth->fetchrow_hashref ) {
1920 if ( $line->{'rank'} > 0 ) {
1921 $rank = $line->{'rank'} + 1;
1922 } else {
1923 $rank = 1;
1926 $sth = $dbh->prepare( "INSERT INTO subscriptionroutinglist (subscriptionid,borrowernumber,ranking) VALUES (?,?,?)" );
1927 $sth->execute( $subscriptionid, $borrowernumber, $rank );
1930 =head2 reorder_members
1932 reorder_members($subscriptionid,$routingid,$rank)
1934 this function is used to reorder the routing list
1936 it takes the routingid of the member one wants to re-rank and the rank it is to move to
1937 - it gets all members on list puts their routingid's into an array
1938 - removes the one in the array that is $routingid
1939 - then reinjects $routingid at point indicated by $rank
1940 - then update the database with the routingids in the new order
1942 =cut
1944 sub reorder_members {
1945 my ( $subscriptionid, $routingid, $rank ) = @_;
1946 my $dbh = C4::Context->dbh;
1947 my $sth = $dbh->prepare( "SELECT * FROM subscriptionroutinglist WHERE subscriptionid = ? ORDER BY ranking ASC" );
1948 $sth->execute($subscriptionid);
1949 my @result;
1950 while ( my $line = $sth->fetchrow_hashref ) {
1951 push( @result, $line->{'routingid'} );
1954 # To find the matching index
1955 my $i;
1956 my $key = -1; # to allow for 0 being a valid response
1957 for ( $i = 0 ; $i < @result ; $i++ ) {
1958 if ( $routingid == $result[$i] ) {
1959 $key = $i; # save the index
1960 last;
1964 # if index exists in array then move it to new position
1965 if ( $key > -1 && $rank > 0 ) {
1966 my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
1967 my $moving_item = splice( @result, $key, 1 );
1968 splice( @result, $new_rank, 0, $moving_item );
1970 for ( my $j = 0 ; $j < @result ; $j++ ) {
1971 my $sth = $dbh->prepare( "UPDATE subscriptionroutinglist SET ranking = '" . ( $j + 1 ) . "' WHERE routingid = '" . $result[$j] . "'" );
1972 $sth->execute;
1974 return;
1977 =head2 delroutingmember
1979 delroutingmember($routingid,$subscriptionid)
1981 this function either deletes one member from routing list if $routingid exists otherwise
1982 deletes all members from the routing list
1984 =cut
1986 sub delroutingmember {
1988 # if $routingid exists then deletes that row otherwise deletes all with $subscriptionid
1989 my ( $routingid, $subscriptionid ) = @_;
1990 my $dbh = C4::Context->dbh;
1991 if ($routingid) {
1992 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE routingid = ?");
1993 $sth->execute($routingid);
1994 reorder_members( $subscriptionid, $routingid );
1995 } else {
1996 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE subscriptionid = ?");
1997 $sth->execute($subscriptionid);
1999 return;
2002 =head2 getroutinglist
2004 @routinglist = getroutinglist($subscriptionid)
2006 this gets the info from the subscriptionroutinglist for $subscriptionid
2008 return :
2009 the routinglist as an array. Each element of the array contains a hash_ref containing
2010 routingid - a unique id, borrowernumber, ranking, and biblionumber of subscription
2012 =cut
2014 sub getroutinglist {
2015 my ($subscriptionid) = @_;
2016 my $dbh = C4::Context->dbh;
2017 my $sth = $dbh->prepare(
2018 'SELECT routingid, borrowernumber, ranking, biblionumber
2019 FROM subscription
2020 JOIN subscriptionroutinglist ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
2021 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC'
2023 $sth->execute($subscriptionid);
2024 my $routinglist = $sth->fetchall_arrayref({});
2025 return @{$routinglist};
2028 =head2 countissuesfrom
2030 $result = countissuesfrom($subscriptionid,$startdate)
2032 Returns a count of serial rows matching the given subsctiptionid
2033 with published date greater than startdate
2035 =cut
2037 sub countissuesfrom {
2038 my ( $subscriptionid, $startdate ) = @_;
2039 my $dbh = C4::Context->dbh;
2040 my $query = qq|
2041 SELECT count(*)
2042 FROM serial
2043 WHERE subscriptionid=?
2044 AND serial.publisheddate>?
2046 my $sth = $dbh->prepare($query);
2047 $sth->execute( $subscriptionid, $startdate );
2048 my ($countreceived) = $sth->fetchrow;
2049 return $countreceived;
2052 =head2 CountIssues
2054 $result = CountIssues($subscriptionid)
2056 Returns a count of serial rows matching the given subsctiptionid
2058 =cut
2060 sub CountIssues {
2061 my ($subscriptionid) = @_;
2062 my $dbh = C4::Context->dbh;
2063 my $query = qq|
2064 SELECT count(*)
2065 FROM serial
2066 WHERE subscriptionid=?
2068 my $sth = $dbh->prepare($query);
2069 $sth->execute($subscriptionid);
2070 my ($countreceived) = $sth->fetchrow;
2071 return $countreceived;
2074 =head2 HasItems
2076 $result = HasItems($subscriptionid)
2078 returns a count of items from serial matching the subscriptionid
2080 =cut
2082 sub HasItems {
2083 my ($subscriptionid) = @_;
2084 my $dbh = C4::Context->dbh;
2085 my $query = q|
2086 SELECT COUNT(serialitems.itemnumber)
2087 FROM serial
2088 LEFT JOIN serialitems USING(serialid)
2089 WHERE subscriptionid=? AND serialitems.serialid IS NOT NULL
2091 my $sth=$dbh->prepare($query);
2092 $sth->execute($subscriptionid);
2093 my ($countitems)=$sth->fetchrow_array();
2094 return $countitems;
2097 =head2 abouttoexpire
2099 $result = abouttoexpire($subscriptionid)
2101 this function alerts you to the penultimate issue for a serial subscription
2103 returns 1 - if this is the penultimate issue
2104 returns 0 - if not
2106 =cut
2108 sub abouttoexpire {
2109 my ($subscriptionid) = @_;
2110 my $dbh = C4::Context->dbh;
2111 my $subscription = GetSubscription($subscriptionid);
2112 my $per = $subscription->{'periodicity'};
2113 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($per);
2114 if ($frequency and $frequency->{unit}){
2116 my $expirationdate = GetExpirationDate($subscriptionid);
2118 my ($res) = $dbh->selectrow_array('select max(planneddate) from serial where subscriptionid = ?', undef, $subscriptionid);
2119 my $nextdate = GetNextDate($subscription, $res, $frequency);
2121 # only compare dates if both dates exist.
2122 if ($nextdate and $expirationdate) {
2123 if(Date::Calc::Delta_Days(
2124 split( /-/, $nextdate ),
2125 split( /-/, $expirationdate )
2126 ) <= 0) {
2127 return 1;
2131 } elsif ($subscription->{numberlength}>0) {
2132 return (countissuesfrom($subscriptionid,$subscription->{'startdate'}) >=$subscription->{numberlength}-1);
2135 return 0;
2138 =head2 GetFictiveIssueNumber
2140 $issueno = GetFictiveIssueNumber($subscription, $publishedate, $frequency);
2142 Get the position of the issue published at $publisheddate, considering the
2143 first issue (at firstacquidate) is at position 1, the next is at position 2, etc...
2144 This issuenumber doesn't take into account irregularities, so, for instance, if the 3rd
2145 issue is declared as 'irregular' (will be skipped at receipt), the next issue number
2146 will be 4, not 3. It's why it is called 'fictive'. It is NOT a serial seq, and is not
2147 depending on how many rows are in serial table.
2148 The issue number calculation is based on subscription frequency, first acquisition
2149 date, and $publisheddate.
2151 Returns undef when called for irregular frequencies.
2153 The routine is used to skip irregularities when calculating the next issue
2154 date (in GetNextDate) or the next issue number (in GetNextSeq).
2156 =cut
2158 sub GetFictiveIssueNumber {
2159 my ($subscription, $publisheddate, $frequency) = @_;
2161 my $unit = $frequency->{unit} ? lc $frequency->{'unit'} : undef;
2162 return if !$unit;
2163 my $issueno;
2165 my ( $year, $month, $day ) = split /-/, $publisheddate;
2166 my ( $fa_year, $fa_month, $fa_day ) = split /-/, $subscription->{'firstacquidate'};
2167 my $delta = _delta_units( [$fa_year, $fa_month, $fa_day], [$year, $month, $day], $unit );
2169 if( $frequency->{'unitsperissue'} == 1 ) {
2170 $issueno = $delta * $frequency->{'issuesperunit'} + $subscription->{'countissuesperunit'};
2171 } else { # issuesperunit == 1
2172 $issueno = 1 + int( $delta / $frequency->{'unitsperissue'} );
2174 return $issueno;
2177 sub _delta_units {
2178 my ( $date1, $date2, $unit ) = @_;
2179 # date1 and date2 are array refs in the form [ yy, mm, dd ]
2181 if( $unit eq 'day' ) {
2182 return Delta_Days( @$date1, @$date2 );
2183 } elsif( $unit eq 'week' ) {
2184 return int( Delta_Days( @$date1, @$date2 ) / 7 );
2187 # In case of months or years, this is a wrapper around N_Delta_YMD.
2188 # Note that N_Delta_YMD returns 29 days between e.g. 22-2-72 and 22-3-72
2189 # while we expect 1 month.
2190 my @delta = N_Delta_YMD( @$date1, @$date2 );
2191 if( $delta[2] > 27 ) {
2192 # Check if we could add a month
2193 my @jump = Add_Delta_YM( @$date1, $delta[0], 1 + $delta[1] );
2194 if( Delta_Days( @jump, @$date2 ) >= 0 ) {
2195 $delta[1]++;
2198 if( $delta[1] >= 12 ) {
2199 $delta[0]++;
2200 $delta[1] -= 12;
2202 # if unit is year, we only return full years
2203 return $unit eq 'month' ? $delta[0] * 12 + $delta[1] : $delta[0];
2206 sub _get_next_date_day {
2207 my ($subscription, $freqdata, $year, $month, $day) = @_;
2209 my @newissue; # ( yy, mm, dd )
2210 # We do not need $delta_days here, since it would be zero where used
2212 if( $freqdata->{issuesperunit} == 1 ) {
2213 # Add full days
2214 @newissue = Add_Delta_Days(
2215 $year, $month, $day, $freqdata->{"unitsperissue"} );
2216 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2217 # Add zero days
2218 @newissue = ( $year, $month, $day );
2219 $subscription->{countissuesperunit}++;
2220 } else {
2221 # We finished a cycle of issues within a unit.
2222 # No subtraction of zero needed, just add one day
2223 @newissue = Add_Delta_Days( $year, $month, $day, 1 );
2224 $subscription->{countissuesperunit} = 1;
2226 return @newissue;
2229 sub _get_next_date_week {
2230 my ($subscription, $freqdata, $year, $month, $day) = @_;
2232 my @newissue; # ( yy, mm, dd )
2233 my $delta_days = int( 7 / $freqdata->{issuesperunit} );
2235 if( $freqdata->{issuesperunit} == 1 ) {
2236 # Add full weeks (of 7 days)
2237 @newissue = Add_Delta_Days(
2238 $year, $month, $day, 7 * $freqdata->{"unitsperissue"} );
2239 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2240 # Add rounded number of days based on frequency.
2241 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2242 $subscription->{countissuesperunit}++;
2243 } else {
2244 # We finished a cycle of issues within a unit.
2245 # Subtract delta * (issues - 1), add 1 week
2246 @newissue = Add_Delta_Days( $year, $month, $day,
2247 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2248 @newissue = Add_Delta_Days( @newissue, 7 );
2249 $subscription->{countissuesperunit} = 1;
2251 return @newissue;
2254 sub _get_next_date_month {
2255 my ($subscription, $freqdata, $year, $month, $day) = @_;
2257 my @newissue; # ( yy, mm, dd )
2258 my $delta_days = int( 30 / $freqdata->{issuesperunit} );
2260 if( $freqdata->{issuesperunit} == 1 ) {
2261 # Add full months
2262 @newissue = Add_Delta_YM(
2263 $year, $month, $day, 0, $freqdata->{"unitsperissue"} );
2264 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2265 # Add rounded number of days based on frequency.
2266 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2267 $subscription->{countissuesperunit}++;
2268 } else {
2269 # We finished a cycle of issues within a unit.
2270 # Subtract delta * (issues - 1), add 1 month
2271 @newissue = Add_Delta_Days( $year, $month, $day,
2272 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2273 @newissue = Add_Delta_YM( @newissue, 0, 1 );
2274 $subscription->{countissuesperunit} = 1;
2276 return @newissue;
2279 sub _get_next_date_year {
2280 my ($subscription, $freqdata, $year, $month, $day) = @_;
2282 my @newissue; # ( yy, mm, dd )
2283 my $delta_days = int( 365 / $freqdata->{issuesperunit} );
2285 if( $freqdata->{issuesperunit} == 1 ) {
2286 # Add full years
2287 @newissue = Add_Delta_YM( $year, $month, $day, $freqdata->{"unitsperissue"}, 0 );
2288 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2289 # Add rounded number of days based on frequency.
2290 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2291 $subscription->{countissuesperunit}++;
2292 } else {
2293 # We finished a cycle of issues within a unit.
2294 # Subtract delta * (issues - 1), add 1 year
2295 @newissue = Add_Delta_Days( $year, $month, $day, -$delta_days * ($freqdata->{issuesperunit} - 1) );
2296 @newissue = Add_Delta_YM( @newissue, 1, 0 );
2297 $subscription->{countissuesperunit} = 1;
2299 return @newissue;
2302 =head2 GetNextDate
2304 $resultdate = GetNextDate($publisheddate,$subscription,$freqdata,$updatecount)
2306 this function it takes the publisheddate and will return the next issue's date
2307 and will skip dates if there exists an irregularity.
2308 $publisheddate has to be an ISO date
2309 $subscription is a hashref containing at least 'firstacquidate', 'irregularity', and 'countissuesperunit'
2310 $frequency is a hashref containing frequency informations
2311 $updatecount is a boolean value which, when set to true, update the 'countissuesperunit' in database
2312 - eg if periodicity is monthly and $publisheddate is 2007-02-10 but if March and April is to be
2313 skipped then the returned date will be 2007-05-10
2315 return :
2316 $resultdate - then next date in the sequence (ISO date)
2318 Return undef if subscription is irregular
2320 =cut
2322 sub GetNextDate {
2323 my ( $subscription, $publisheddate, $freqdata, $updatecount ) = @_;
2325 return unless $subscription and $publisheddate;
2328 if ($freqdata->{'unit'}) {
2329 my ( $year, $month, $day ) = split /-/, $publisheddate;
2331 # Process an irregularity Hash
2332 # Suppose that irregularities are stored in a string with this structure
2333 # irreg1;irreg2;irreg3
2334 # where irregX is the number of issue which will not be received
2335 # (the first issue takes the number 1, the 2nd the number 2 and so on)
2336 my %irregularities;
2337 if ( $subscription->{irregularity} ) {
2338 my @irreg = split /;/, $subscription->{'irregularity'} ;
2339 foreach my $irregularity (@irreg) {
2340 $irregularities{$irregularity} = 1;
2344 # Get the 'fictive' next issue number
2345 # It is used to check if next issue is an irregular issue.
2346 my $issueno = GetFictiveIssueNumber($subscription, $publisheddate, $freqdata) + 1;
2348 # Then get the next date
2349 my $unit = lc $freqdata->{'unit'};
2350 if ($unit eq 'day') {
2351 while ($irregularities{$issueno}) {
2352 ($year, $month, $day) = _get_next_date_day($subscription,
2353 $freqdata, $year, $month, $day);
2354 $issueno++;
2356 ($year, $month, $day) = _get_next_date_day($subscription, $freqdata,
2357 $year, $month, $day);
2359 elsif ($unit eq 'week') {
2360 while ($irregularities{$issueno}) {
2361 ($year, $month, $day) = _get_next_date_week($subscription,
2362 $freqdata, $year, $month, $day);
2363 $issueno++;
2365 ($year, $month, $day) = _get_next_date_week($subscription,
2366 $freqdata, $year, $month, $day);
2368 elsif ($unit eq 'month') {
2369 while ($irregularities{$issueno}) {
2370 ($year, $month, $day) = _get_next_date_month($subscription,
2371 $freqdata, $year, $month, $day);
2372 $issueno++;
2374 ($year, $month, $day) = _get_next_date_month($subscription,
2375 $freqdata, $year, $month, $day);
2377 elsif ($unit eq 'year') {
2378 while ($irregularities{$issueno}) {
2379 ($year, $month, $day) = _get_next_date_year($subscription,
2380 $freqdata, $year, $month, $day);
2381 $issueno++;
2383 ($year, $month, $day) = _get_next_date_year($subscription,
2384 $freqdata, $year, $month, $day);
2387 if ($updatecount){
2388 my $dbh = C4::Context->dbh;
2389 my $query = qq{
2390 UPDATE subscription
2391 SET countissuesperunit = ?
2392 WHERE subscriptionid = ?
2394 my $sth = $dbh->prepare($query);
2395 $sth->execute($subscription->{'countissuesperunit'}, $subscription->{'subscriptionid'});
2398 return sprintf("%04d-%02d-%02d", $year, $month, $day);
2402 =head2 _numeration
2404 $string = &_numeration($value,$num_type,$locale);
2406 _numeration returns the string corresponding to $value in the num_type
2407 num_type can take :
2408 -dayname
2409 -dayabrv
2410 -monthname
2411 -monthabrv
2412 -season
2413 -seasonabrv
2415 =cut
2417 sub _numeration {
2418 my ($value, $num_type, $locale) = @_;
2419 $value ||= 0;
2420 $num_type //= '';
2421 $locale ||= 'en';
2422 my $string;
2423 if ( $num_type =~ /^dayname$/ or $num_type =~ /^dayabrv$/ ) {
2424 # 1970-11-01 was a Sunday
2425 $value = $value % 7;
2426 my $dt = DateTime->new(
2427 year => 1970,
2428 month => 11,
2429 day => $value + 1,
2430 locale => $locale,
2432 $string = $num_type =~ /^dayname$/
2433 ? $dt->strftime("%A")
2434 : $dt->strftime("%a");
2435 } elsif ( $num_type =~ /^monthname$/ or $num_type =~ /^monthabrv$/ ) {
2436 $value = $value % 12;
2437 my $dt = DateTime->new(
2438 year => 1970,
2439 month => $value + 1,
2440 locale => $locale,
2442 $string = $num_type =~ /^monthname$/
2443 ? $dt->strftime("%B")
2444 : $dt->strftime("%b");
2445 } elsif ( $num_type =~ /^season$/ ) {
2446 my @seasons= qw( Spring Summer Fall Winter );
2447 $value = $value % 4;
2448 $string = $seasons[$value];
2449 } elsif ( $num_type =~ /^seasonabrv$/ ) {
2450 my @seasonsabrv= qw( Spr Sum Fal Win );
2451 $value = $value % 4;
2452 $string = $seasonsabrv[$value];
2453 } else {
2454 $string = $value;
2457 return $string;
2460 =head2 CloseSubscription
2462 Close a subscription given a subscriptionid
2464 =cut
2466 sub CloseSubscription {
2467 my ( $subscriptionid ) = @_;
2468 return unless $subscriptionid;
2469 my $dbh = C4::Context->dbh;
2470 my $sth = $dbh->prepare( q{
2471 UPDATE subscription
2472 SET closed = 1
2473 WHERE subscriptionid = ?
2474 } );
2475 $sth->execute( $subscriptionid );
2477 # Set status = missing when status = stopped
2478 $sth = $dbh->prepare( q{
2479 UPDATE serial
2480 SET status = ?
2481 WHERE subscriptionid = ?
2482 AND status = ?
2483 } );
2484 $sth->execute( STOPPED, $subscriptionid, EXPECTED );
2487 =head2 ReopenSubscription
2489 Reopen a subscription given a subscriptionid
2491 =cut
2493 sub ReopenSubscription {
2494 my ( $subscriptionid ) = @_;
2495 return unless $subscriptionid;
2496 my $dbh = C4::Context->dbh;
2497 my $sth = $dbh->prepare( q{
2498 UPDATE subscription
2499 SET closed = 0
2500 WHERE subscriptionid = ?
2501 } );
2502 $sth->execute( $subscriptionid );
2504 # Set status = expected when status = stopped
2505 $sth = $dbh->prepare( q{
2506 UPDATE serial
2507 SET status = ?
2508 WHERE subscriptionid = ?
2509 AND status = ?
2510 } );
2511 $sth->execute( EXPECTED, $subscriptionid, STOPPED );
2514 =head2 subscriptionCurrentlyOnOrder
2516 $bool = subscriptionCurrentlyOnOrder( $subscriptionid );
2518 Return 1 if subscription is currently on order else 0.
2520 =cut
2522 sub subscriptionCurrentlyOnOrder {
2523 my ( $subscriptionid ) = @_;
2524 my $dbh = C4::Context->dbh;
2525 my $query = qq|
2526 SELECT COUNT(*) FROM aqorders
2527 WHERE subscriptionid = ?
2528 AND datereceived IS NULL
2529 AND datecancellationprinted IS NULL
2531 my $sth = $dbh->prepare( $query );
2532 $sth->execute($subscriptionid);
2533 return $sth->fetchrow_array;
2536 =head2 can_claim_subscription
2538 $can = can_claim_subscription( $subscriptionid[, $userid] );
2540 Return 1 if the subscription can be claimed by the current logged user (or a given $userid), else 0.
2542 =cut
2544 sub can_claim_subscription {
2545 my ( $subscription, $userid ) = @_;
2546 return _can_do_on_subscription( $subscription, $userid, 'claim_serials' );
2549 =head2 can_edit_subscription
2551 $can = can_edit_subscription( $subscriptionid[, $userid] );
2553 Return 1 if the subscription can be edited by the current logged user (or a given $userid), else 0.
2555 =cut
2557 sub can_edit_subscription {
2558 my ( $subscription, $userid ) = @_;
2559 return _can_do_on_subscription( $subscription, $userid, 'edit_subscription' );
2562 =head2 can_show_subscription
2564 $can = can_show_subscription( $subscriptionid[, $userid] );
2566 Return 1 if the subscription can be shown by the current logged user (or a given $userid), else 0.
2568 =cut
2570 sub can_show_subscription {
2571 my ( $subscription, $userid ) = @_;
2572 return _can_do_on_subscription( $subscription, $userid, '*' );
2575 sub _can_do_on_subscription {
2576 my ( $subscription, $userid, $permission ) = @_;
2577 return 0 unless C4::Context->userenv;
2578 my $flags = C4::Context->userenv->{flags};
2579 $userid ||= C4::Context->userenv->{'id'};
2581 if ( C4::Context->preference('IndependentBranches') ) {
2582 return 1
2583 if C4::Context->IsSuperLibrarian()
2585 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2586 or (
2587 C4::Auth::haspermission( $userid,
2588 { serials => $permission } )
2589 and ( not defined $subscription->{branchcode}
2590 or $subscription->{branchcode} eq ''
2591 or $subscription->{branchcode} eq
2592 C4::Context->userenv->{'branch'} )
2595 else {
2596 return 1
2597 if C4::Context->IsSuperLibrarian()
2599 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2600 or C4::Auth::haspermission(
2601 $userid, { serials => $permission }
2605 return 0;
2608 =head2 findSerialsByStatus
2610 @serials = findSerialsByStatus($status, $subscriptionid);
2612 Returns an array of serials matching a given status and subscription id.
2614 =cut
2616 sub findSerialsByStatus {
2617 my ( $status, $subscriptionid ) = @_;
2618 my $dbh = C4::Context->dbh;
2619 my $query = q| SELECT * from serial
2620 WHERE status = ?
2621 AND subscriptionid = ?
2623 my $serials = $dbh->selectall_arrayref( $query, { Slice => {} }, $status, $subscriptionid );
2624 return @$serials;
2628 __END__
2630 =head1 AUTHOR
2632 Koha Development Team <http://koha-community.org/>
2634 =cut