Bug 19817: Remove local help files + edit help feature
[koha.git] / C4 / Serials.pm
blob0eb699fc452dc7813a3ba62aa4153cac4feb53cc
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;
39 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
41 # Define statuses
42 use constant {
43 EXPECTED => 1,
44 ARRIVED => 2,
45 LATE => 3,
46 MISSING => 4,
47 MISSING_NEVER_RECIEVED => 41,
48 MISSING_SOLD_OUT => 42,
49 MISSING_DAMAGED => 43,
50 MISSING_LOST => 44,
51 NOT_ISSUED => 5,
52 DELETED => 6,
53 CLAIMED => 7,
54 STOPPED => 8,
57 use constant MISSING_STATUSES => (
58 MISSING, MISSING_NEVER_RECIEVED,
59 MISSING_SOLD_OUT, MISSING_DAMAGED,
60 MISSING_LOST
63 BEGIN {
64 require Exporter;
65 @ISA = qw(Exporter);
66 @EXPORT = qw(
67 &NewSubscription &ModSubscription &DelSubscription
68 &GetSubscription &CountSubscriptionFromBiblionumber &GetSubscriptionsFromBiblionumber
69 &SearchSubscriptions
70 &GetFullSubscriptionsFromBiblionumber &GetFullSubscription &ModSubscriptionHistory
71 &HasSubscriptionStrictlyExpired &HasSubscriptionExpired &GetExpirationDate &abouttoexpire
72 &GetSubscriptionHistoryFromSubscriptionId
74 &GetNextSeq &GetSeq &NewIssue &GetSerials
75 &GetLatestSerials &ModSerialStatus &GetNextDate &GetSerials2
76 &ReNewSubscription &GetLateOrMissingIssues
77 &GetSerialInformation &AddItem2Serial
78 &PrepareSerialsData &GetNextExpected &ModNextExpected
79 &GetPreviousSerialid
81 &GetSuppliersWithLateIssues
82 &getroutinglist &delroutingmember &addroutingmember
83 &reorder_members
84 &check_routing &updateClaim
85 &CountIssues
86 HasItems
87 &subscriptionCurrentlyOnOrder
92 =head1 NAME
94 C4::Serials - Serials Module Functions
96 =head1 SYNOPSIS
98 use C4::Serials;
100 =head1 DESCRIPTION
102 Functions for handling subscriptions, claims routing etc.
105 =head1 SUBROUTINES
107 =head2 GetSuppliersWithLateIssues
109 $supplierlist = GetSuppliersWithLateIssues()
111 this function get all suppliers with late issues.
113 return :
114 an array_ref of suppliers each entry is a hash_ref containing id and name
115 the array is in name order
117 =cut
119 sub GetSuppliersWithLateIssues {
120 my $dbh = C4::Context->dbh;
121 my $statuses = join(',', ( LATE, MISSING_STATUSES, CLAIMED ) );
122 my $query = qq|
123 SELECT DISTINCT id, name
124 FROM subscription
125 LEFT JOIN serial ON serial.subscriptionid=subscription.subscriptionid
126 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
127 WHERE id > 0
128 AND (
129 (planneddate < now() AND serial.status=1)
130 OR serial.STATUS IN ( $statuses )
132 AND subscription.closed = 0
133 ORDER BY name|;
134 return $dbh->selectall_arrayref($query, { Slice => {} });
137 =head2 GetSubscriptionHistoryFromSubscriptionId
139 $history = GetSubscriptionHistoryFromSubscriptionId($subscriptionid);
141 This function returns the subscription history as a hashref
143 =cut
145 sub GetSubscriptionHistoryFromSubscriptionId {
146 my ($subscriptionid) = @_;
148 return unless $subscriptionid;
150 my $dbh = C4::Context->dbh;
151 my $query = qq|
152 SELECT *
153 FROM subscriptionhistory
154 WHERE subscriptionid = ?
156 my $sth = $dbh->prepare($query);
157 $sth->execute($subscriptionid);
158 my $results = $sth->fetchrow_hashref;
159 $sth->finish;
161 return $results;
164 =head2 GetSerialInformation
166 $data = GetSerialInformation($serialid);
167 returns a hash_ref containing :
168 items : items marcrecord (can be an array)
169 serial table field
170 subscription table field
171 + information about subscription expiration
173 =cut
175 sub GetSerialInformation {
176 my ($serialid) = @_;
177 my $dbh = C4::Context->dbh;
178 my $query = qq|
179 SELECT serial.*, serial.notes as sernotes, serial.status as serstatus,subscription.*,subscription.subscriptionid as subsid
180 FROM serial LEFT JOIN subscription ON subscription.subscriptionid=serial.subscriptionid
181 WHERE serialid = ?
183 my $rq = $dbh->prepare($query);
184 $rq->execute($serialid);
185 my $data = $rq->fetchrow_hashref;
187 # create item information if we have serialsadditems for this subscription
188 if ( $data->{'serialsadditems'} ) {
189 my $queryitem = $dbh->prepare("SELECT itemnumber from serialitems where serialid=?");
190 $queryitem->execute($serialid);
191 my $itemnumbers = $queryitem->fetchall_arrayref( [0] );
192 require C4::Items;
193 if ( scalar(@$itemnumbers) > 0 ) {
194 foreach my $itemnum (@$itemnumbers) {
196 #It is ASSUMED that GetMarcItem ALWAYS WORK...
197 #Maybe GetMarcItem should return values on failure
198 $debug and warn "itemnumber :$itemnum->[0], bibnum :" . $data->{'biblionumber'};
199 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, $itemnum->[0], $data );
200 $itemprocessed->{'itemnumber'} = $itemnum->[0];
201 $itemprocessed->{'itemid'} = $itemnum->[0];
202 $itemprocessed->{'serialid'} = $serialid;
203 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
204 push @{ $data->{'items'} }, $itemprocessed;
206 } else {
207 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, '', $data );
208 $itemprocessed->{'itemid'} = "N$serialid";
209 $itemprocessed->{'serialid'} = $serialid;
210 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
211 $itemprocessed->{'countitems'} = 0;
212 push @{ $data->{'items'} }, $itemprocessed;
215 $data->{ "status" . $data->{'serstatus'} } = 1;
216 $data->{'subscriptionexpired'} = HasSubscriptionExpired( $data->{'subscriptionid'} ) && $data->{'status'} == 1;
217 $data->{'abouttoexpire'} = abouttoexpire( $data->{'subscriptionid'} );
218 $data->{cannotedit} = not can_edit_subscription( $data );
219 return $data;
222 =head2 AddItem2Serial
224 $rows = AddItem2Serial($serialid,$itemnumber);
225 Adds an itemnumber to Serial record
226 returns the number of rows affected
228 =cut
230 sub AddItem2Serial {
231 my ( $serialid, $itemnumber ) = @_;
233 return unless ($serialid and $itemnumber);
235 my $dbh = C4::Context->dbh;
236 my $rq = $dbh->prepare("INSERT INTO `serialitems` SET serialid=? , itemnumber=?");
237 $rq->execute( $serialid, $itemnumber );
238 return $rq->rows;
241 =head2 GetSubscription
243 $subs = GetSubscription($subscriptionid)
244 this function returns the subscription which has $subscriptionid as id.
245 return :
246 a hashref. This hash contains
247 subscription, subscriptionhistory, aqbooksellers.name, biblio.title
249 =cut
251 sub GetSubscription {
252 my ($subscriptionid) = @_;
253 my $dbh = C4::Context->dbh;
254 my $query = qq(
255 SELECT subscription.*,
256 subscriptionhistory.*,
257 aqbooksellers.name AS aqbooksellername,
258 biblio.title AS bibliotitle,
259 subscription.biblionumber as bibnum
260 FROM subscription
261 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
262 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
263 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
264 WHERE subscription.subscriptionid = ?
267 $debug and warn "query : $query\nsubsid :$subscriptionid";
268 my $sth = $dbh->prepare($query);
269 $sth->execute($subscriptionid);
270 my $subscription = $sth->fetchrow_hashref;
272 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
274 # Add additional fields to the subscription into a new key "additional_fields"
275 my $additional_field_values = Koha::AdditionalField->fetch_all_values({
276 tablename => 'subscription',
277 record_id => $subscriptionid,
279 $subscription->{additional_fields} = $additional_field_values->{$subscriptionid};
281 return $subscription;
284 =head2 GetFullSubscription
286 $array_ref = GetFullSubscription($subscriptionid)
287 this function reads the serial table.
289 =cut
291 sub GetFullSubscription {
292 my ($subscriptionid) = @_;
294 return unless ($subscriptionid);
296 my $dbh = C4::Context->dbh;
297 my $query = qq|
298 SELECT serial.serialid,
299 serial.serialseq,
300 serial.planneddate,
301 serial.publisheddate,
302 serial.publisheddatetext,
303 serial.status,
304 serial.notes as notes,
305 year(IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate)) as year,
306 aqbooksellers.name as aqbooksellername,
307 biblio.title as bibliotitle,
308 subscription.branchcode AS branchcode,
309 subscription.subscriptionid AS subscriptionid
310 FROM serial
311 LEFT JOIN subscription ON
312 (serial.subscriptionid=subscription.subscriptionid )
313 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
314 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
315 WHERE serial.subscriptionid = ?
316 ORDER BY year DESC,
317 IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate) DESC,
318 serial.subscriptionid
320 $debug and warn "GetFullSubscription query: $query";
321 my $sth = $dbh->prepare($query);
322 $sth->execute($subscriptionid);
323 my $subscriptions = $sth->fetchall_arrayref( {} );
324 my $cannotedit = not can_edit_subscription( $subscriptions->[0] ) if scalar @$subscriptions;
325 for my $subscription ( @$subscriptions ) {
326 $subscription->{cannotedit} = $cannotedit;
328 return $subscriptions;
331 =head2 PrepareSerialsData
333 $array_ref = PrepareSerialsData($serialinfomation)
334 where serialinformation is a hashref array
336 =cut
338 sub PrepareSerialsData {
339 my ($lines) = @_;
341 return unless ($lines);
343 my %tmpresults;
344 my $year;
345 my @res;
346 my $startdate;
347 my $aqbooksellername;
348 my $bibliotitle;
349 my @loopissues;
350 my $first;
351 my $previousnote = "";
353 foreach my $subs (@{$lines}) {
354 for my $datefield ( qw(publisheddate planneddate) ) {
355 # handle 0000-00-00 dates
356 if (defined $subs->{$datefield} and $subs->{$datefield} =~ m/^00/) {
357 $subs->{$datefield} = undef;
360 $subs->{ "status" . $subs->{'status'} } = 1;
361 if ( grep { $_ == $subs->{status} } ( EXPECTED, LATE, MISSING_STATUSES, CLAIMED ) ) {
362 $subs->{"checked"} = 1;
365 if ( $subs->{'year'} && $subs->{'year'} ne "" ) {
366 $year = $subs->{'year'};
367 } else {
368 $year = "manage";
370 if ( $tmpresults{$year} ) {
371 push @{ $tmpresults{$year}->{'serials'} }, $subs;
372 } else {
373 $tmpresults{$year} = {
374 'year' => $year,
375 'aqbooksellername' => $subs->{'aqbooksellername'},
376 'bibliotitle' => $subs->{'bibliotitle'},
377 'serials' => [$subs],
378 'first' => $first,
382 foreach my $key ( sort { $b cmp $a } keys %tmpresults ) {
383 push @res, $tmpresults{$key};
385 return \@res;
388 =head2 GetSubscriptionsFromBiblionumber
390 $array_ref = GetSubscriptionsFromBiblionumber($biblionumber)
391 this function get the subscription list. it reads the subscription table.
392 return :
393 reference to an array of subscriptions which have the biblionumber given on input arg.
394 each element of this array is a hashref containing
395 startdate, histstartdate,opacnote,missinglist,recievedlist,periodicity,status & enddate
397 =cut
399 sub GetSubscriptionsFromBiblionumber {
400 my ($biblionumber) = @_;
402 return unless ($biblionumber);
404 my $dbh = C4::Context->dbh;
405 my $query = qq(
406 SELECT subscription.*,
407 branches.branchname,
408 subscriptionhistory.*,
409 aqbooksellers.name AS aqbooksellername,
410 biblio.title AS bibliotitle
411 FROM subscription
412 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
413 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
414 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
415 LEFT JOIN branches ON branches.branchcode=subscription.branchcode
416 WHERE subscription.biblionumber = ?
418 my $sth = $dbh->prepare($query);
419 $sth->execute($biblionumber);
420 my @res;
421 while ( my $subs = $sth->fetchrow_hashref ) {
422 $subs->{startdate} = output_pref( { dt => dt_from_string( $subs->{startdate} ), dateonly => 1 } );
423 $subs->{histstartdate} = output_pref( { dt => dt_from_string( $subs->{histstartdate} ), dateonly => 1 } );
424 if ( defined $subs->{histenddate} ) {
425 $subs->{histenddate} = output_pref( { dt => dt_from_string( $subs->{histenddate} ), dateonly => 1 } );
426 } else {
427 $subs->{histenddate} = "";
429 $subs->{opacnote} =~ s/\n/\<br\/\>/g;
430 $subs->{missinglist} =~ s/\n/\<br\/\>/g;
431 $subs->{recievedlist} =~ s/\n/\<br\/\>/g;
432 $subs->{ "periodicity" . $subs->{periodicity} } = 1;
433 $subs->{ "numberpattern" . $subs->{numberpattern} } = 1;
434 $subs->{ "status" . $subs->{'status'} } = 1;
436 if (not defined $subs->{enddate} ) {
437 $subs->{enddate} = '';
438 } else {
439 $subs->{enddate} = output_pref( { dt => dt_from_string( $subs->{enddate}), dateonly => 1 } );
441 $subs->{'abouttoexpire'} = abouttoexpire( $subs->{'subscriptionid'} );
442 $subs->{'subscriptionexpired'} = HasSubscriptionExpired( $subs->{'subscriptionid'} );
443 $subs->{cannotedit} = not can_edit_subscription( $subs );
444 push @res, $subs;
446 return \@res;
449 =head2 GetFullSubscriptionsFromBiblionumber
451 $array_ref = GetFullSubscriptionsFromBiblionumber($biblionumber)
452 this function reads the serial table.
454 =cut
456 sub GetFullSubscriptionsFromBiblionumber {
457 my ($biblionumber) = @_;
458 my $dbh = C4::Context->dbh;
459 my $query = qq|
460 SELECT serial.serialid,
461 serial.serialseq,
462 serial.planneddate,
463 serial.publisheddate,
464 serial.publisheddatetext,
465 serial.status,
466 serial.notes as notes,
467 year(IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate)) as year,
468 biblio.title as bibliotitle,
469 subscription.branchcode AS branchcode,
470 subscription.subscriptionid AS subscriptionid
471 FROM serial
472 LEFT JOIN subscription ON
473 (serial.subscriptionid=subscription.subscriptionid)
474 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
475 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
476 WHERE subscription.biblionumber = ?
477 ORDER BY year DESC,
478 IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate) DESC,
479 serial.subscriptionid
481 my $sth = $dbh->prepare($query);
482 $sth->execute($biblionumber);
483 my $subscriptions = $sth->fetchall_arrayref( {} );
484 my $cannotedit = not can_edit_subscription( $subscriptions->[0] ) if scalar @$subscriptions;
485 for my $subscription ( @$subscriptions ) {
486 $subscription->{cannotedit} = $cannotedit;
488 return $subscriptions;
491 =head2 SearchSubscriptions
493 @results = SearchSubscriptions($args);
495 This function returns a list of hashrefs, one for each subscription
496 that meets the conditions specified by the $args hashref.
498 The valid search fields are:
500 biblionumber
501 title
502 issn
504 callnumber
505 location
506 publisher
507 bookseller
508 branch
509 expiration_date
510 closed
512 The expiration_date search field is special; it specifies the maximum
513 subscription expiration date.
515 =cut
517 sub SearchSubscriptions {
518 my ( $args ) = @_;
520 my $additional_fields = $args->{additional_fields} // [];
521 my $matching_record_ids_for_additional_fields = [];
522 if ( @$additional_fields ) {
523 $matching_record_ids_for_additional_fields = Koha::AdditionalField->get_matching_record_ids({
524 fields => $additional_fields,
525 tablename => 'subscription',
526 exact_match => 0,
528 return () unless @$matching_record_ids_for_additional_fields;
531 my $query = q|
532 SELECT
533 subscription.notes AS publicnotes,
534 subscriptionhistory.*,
535 subscription.*,
536 biblio.notes AS biblionotes,
537 biblio.title,
538 biblio.author,
539 biblio.biblionumber,
540 aqbooksellers.name AS vendorname,
541 biblioitems.issn
542 FROM subscription
543 LEFT JOIN subscriptionhistory USING(subscriptionid)
544 LEFT JOIN biblio ON biblio.biblionumber = subscription.biblionumber
545 LEFT JOIN biblioitems ON biblioitems.biblionumber = subscription.biblionumber
546 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
548 $query .= q| WHERE 1|;
549 my @where_strs;
550 my @where_args;
551 if( $args->{biblionumber} ) {
552 push @where_strs, "biblio.biblionumber = ?";
553 push @where_args, $args->{biblionumber};
556 if( $args->{title} ){
557 my @words = split / /, $args->{title};
558 my (@strs, @args);
559 foreach my $word (@words) {
560 push @strs, "biblio.title LIKE ?";
561 push @args, "%$word%";
563 if (@strs) {
564 push @where_strs, '(' . join (' AND ', @strs) . ')';
565 push @where_args, @args;
568 if( $args->{issn} ){
569 push @where_strs, "biblioitems.issn LIKE ?";
570 push @where_args, "%$args->{issn}%";
572 if( $args->{ean} ){
573 push @where_strs, "biblioitems.ean LIKE ?";
574 push @where_args, "%$args->{ean}%";
576 if ( $args->{callnumber} ) {
577 push @where_strs, "subscription.callnumber LIKE ?";
578 push @where_args, "%$args->{callnumber}%";
580 if( $args->{publisher} ){
581 push @where_strs, "biblioitems.publishercode LIKE ?";
582 push @where_args, "%$args->{publisher}%";
584 if( $args->{bookseller} ){
585 push @where_strs, "aqbooksellers.name LIKE ?";
586 push @where_args, "%$args->{bookseller}%";
588 if( $args->{branch} ){
589 push @where_strs, "subscription.branchcode = ?";
590 push @where_args, "$args->{branch}";
592 if ( $args->{location} ) {
593 push @where_strs, "subscription.location = ?";
594 push @where_args, "$args->{location}";
596 if ( $args->{expiration_date} ) {
597 push @where_strs, "subscription.enddate <= ?";
598 push @where_args, "$args->{expiration_date}";
600 if( defined $args->{closed} ){
601 push @where_strs, "subscription.closed = ?";
602 push @where_args, "$args->{closed}";
605 if(@where_strs){
606 $query .= ' AND ' . join(' AND ', @where_strs);
608 if ( @$additional_fields ) {
609 $query .= ' AND subscriptionid IN ('
610 . join( ', ', @$matching_record_ids_for_additional_fields )
611 . ')';
614 $query .= " ORDER BY " . $args->{orderby} if $args->{orderby};
616 my $dbh = C4::Context->dbh;
617 my $sth = $dbh->prepare($query);
618 $sth->execute(@where_args);
619 my $results = $sth->fetchall_arrayref( {} );
621 for my $subscription ( @$results ) {
622 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
623 $subscription->{cannotdisplay} = not can_show_subscription( $subscription );
625 my $additional_field_values = Koha::AdditionalField->fetch_all_values({
626 record_id => $subscription->{subscriptionid},
627 tablename => 'subscription'
629 $subscription->{additional_fields} = $additional_field_values->{$subscription->{subscriptionid}};
632 return @$results;
636 =head2 GetSerials
638 ($totalissues,@serials) = GetSerials($subscriptionid);
639 this function gets every serial not arrived for a given subscription
640 as well as the number of issues registered in the database (all types)
641 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
643 FIXME: We should return \@serials.
645 =cut
647 sub GetSerials {
648 my ( $subscriptionid, $count ) = @_;
650 return unless $subscriptionid;
652 my $dbh = C4::Context->dbh;
654 # status = 2 is "arrived"
655 my $counter = 0;
656 $count = 5 unless ($count);
657 my @serials;
658 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES, NOT_ISSUED ) );
659 my $query = "SELECT serialid,serialseq, status, publisheddate,
660 publisheddatetext, planneddate,notes, routingnotes
661 FROM serial
662 WHERE subscriptionid = ? AND status NOT IN ( $statuses )
663 ORDER BY IF(publisheddate<>'0000-00-00',publisheddate,planneddate) DESC";
664 my $sth = $dbh->prepare($query);
665 $sth->execute($subscriptionid);
667 while ( my $line = $sth->fetchrow_hashref ) {
668 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
669 for my $datefield ( qw( planneddate publisheddate) ) {
670 if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
671 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
672 } else {
673 $line->{$datefield} = q{};
676 push @serials, $line;
679 # OK, now add the last 5 issues arrives/missing
680 $query = "SELECT serialid,serialseq, status, planneddate, publisheddate,
681 publisheddatetext, notes, routingnotes
682 FROM serial
683 WHERE subscriptionid = ?
684 AND status IN ( $statuses )
685 ORDER BY IF(publisheddate<>'0000-00-00',publisheddate,planneddate) DESC
687 $sth = $dbh->prepare($query);
688 $sth->execute($subscriptionid);
689 while ( ( my $line = $sth->fetchrow_hashref ) && $counter < $count ) {
690 $counter++;
691 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
692 for my $datefield ( qw( planneddate publisheddate) ) {
693 if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
694 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
695 } else {
696 $line->{$datefield} = q{};
700 push @serials, $line;
703 $query = "SELECT count(*) FROM serial WHERE subscriptionid=?";
704 $sth = $dbh->prepare($query);
705 $sth->execute($subscriptionid);
706 my ($totalissues) = $sth->fetchrow;
707 return ( $totalissues, @serials );
710 =head2 GetSerials2
712 @serials = GetSerials2($subscriptionid,$statuses);
713 this function returns every serial waited for a given subscription
714 as well as the number of issues registered in the database (all types)
715 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
717 $statuses is an arrayref of statuses and is mandatory.
719 =cut
721 sub GetSerials2 {
722 my ( $subscription, $statuses ) = @_;
724 return unless ($subscription and @$statuses);
726 my $dbh = C4::Context->dbh;
727 my $query = q|
728 SELECT serialid,serialseq, status, planneddate, publisheddate,
729 publisheddatetext, notes, routingnotes
730 FROM serial
731 WHERE subscriptionid=?
733 . q| AND status IN (| . join( ",", ('?') x @$statuses ) . q|)|
734 . q|
735 ORDER BY publisheddate,serialid DESC
737 $debug and warn "GetSerials2 query: $query";
738 my $sth = $dbh->prepare($query);
739 $sth->execute( $subscription, @$statuses );
740 my @serials;
742 while ( my $line = $sth->fetchrow_hashref ) {
743 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
744 # Format dates for display
745 for my $datefield ( qw( planneddate publisheddate ) ) {
746 if (!defined($line->{$datefield}) || $line->{$datefield} =~m/^00/) {
747 $line->{$datefield} = q{};
749 else {
750 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
753 push @serials, $line;
755 return @serials;
758 =head2 GetLatestSerials
760 \@serials = GetLatestSerials($subscriptionid,$limit)
761 get the $limit's latest serials arrived or missing for a given subscription
762 return :
763 a ref to an array which contains all of the latest serials stored into a hash.
765 =cut
767 sub GetLatestSerials {
768 my ( $subscriptionid, $limit ) = @_;
770 return unless ($subscriptionid and $limit);
772 my $dbh = C4::Context->dbh;
774 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES ) );
775 my $strsth = "SELECT serialid,serialseq, status, planneddate, publisheddate, notes
776 FROM serial
777 WHERE subscriptionid = ?
778 AND status IN ($statuses)
779 ORDER BY publisheddate DESC LIMIT 0,$limit
781 my $sth = $dbh->prepare($strsth);
782 $sth->execute($subscriptionid);
783 my @serials;
784 while ( my $line = $sth->fetchrow_hashref ) {
785 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
786 $line->{planneddate} = output_pref( { dt => dt_from_string( $line->{planneddate} ), dateonly => 1 } );
787 $line->{publisheddate} = output_pref( { dt => dt_from_string( $line->{publisheddate} ), dateonly => 1 } );
788 push @serials, $line;
791 return \@serials;
794 =head2 GetPreviousSerialid
796 $serialid = GetPreviousSerialid($subscriptionid, $nth)
797 get the $nth's previous serial for the given subscriptionid
798 return :
799 the serialid
801 =cut
803 sub GetPreviousSerialid {
804 my ( $subscriptionid, $nth ) = @_;
805 $nth ||= 1;
806 my $dbh = C4::Context->dbh;
807 my $return = undef;
809 # Status 2: Arrived
810 my $strsth = "SELECT serialid
811 FROM serial
812 WHERE subscriptionid = ?
813 AND status = 2
814 ORDER BY serialid DESC LIMIT $nth,1
816 my $sth = $dbh->prepare($strsth);
817 $sth->execute($subscriptionid);
818 my @serials;
819 my $line = $sth->fetchrow_hashref;
820 $return = $line->{'serialid'} if ($line);
822 return $return;
825 =head2 GetNextSeq
827 my (
828 $nextseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
829 $newinnerloop1, $newinnerloop2, $newinnerloop3
830 ) = GetNextSeq( $subscription, $pattern, $planneddate );
832 $subscription is a hashref containing all the attributes of the table
833 'subscription'.
834 $pattern is a hashref containing all the attributes of the table
835 'subscription_numberpatterns'.
836 $planneddate is a date string in iso format.
837 This function get the next issue for the subscription given on input arg
839 =cut
841 sub GetNextSeq {
842 my ($subscription, $pattern, $planneddate) = @_;
844 return unless ($subscription and $pattern);
846 my ( $newlastvalue1, $newlastvalue2, $newlastvalue3,
847 $newinnerloop1, $newinnerloop2, $newinnerloop3 );
848 my $count = 1;
850 if ($subscription->{'skip_serialseq'}) {
851 my @irreg = split /;/, $subscription->{'irregularity'};
852 if(@irreg > 0) {
853 my $irregularities = {};
854 $irregularities->{$_} = 1 foreach(@irreg);
855 my $issueno = GetFictiveIssueNumber($subscription, $planneddate) + 1;
856 while($irregularities->{$issueno}) {
857 $count++;
858 $issueno++;
863 my $numberingmethod = $pattern->{numberingmethod};
864 my $calculated = "";
865 if ($numberingmethod) {
866 $calculated = $numberingmethod;
867 my $locale = $subscription->{locale};
868 $newlastvalue1 = $subscription->{lastvalue1} || 0;
869 $newlastvalue2 = $subscription->{lastvalue2} || 0;
870 $newlastvalue3 = $subscription->{lastvalue3} || 0;
871 $newinnerloop1 = $subscription->{innerloop1} || 0;
872 $newinnerloop2 = $subscription->{innerloop2} || 0;
873 $newinnerloop3 = $subscription->{innerloop3} || 0;
874 my %calc;
875 foreach(qw/X Y Z/) {
876 $calc{$_} = 1 if ($numberingmethod =~ /\{$_\}/);
879 for(my $i = 0; $i < $count; $i++) {
880 if($calc{'X'}) {
881 # check if we have to increase the new value.
882 $newinnerloop1 += 1;
883 if ($newinnerloop1 >= $pattern->{every1}) {
884 $newinnerloop1 = 0;
885 $newlastvalue1 += $pattern->{add1};
887 # reset counter if needed.
888 $newlastvalue1 = $pattern->{setto1} if ($newlastvalue1 > $pattern->{whenmorethan1});
890 if($calc{'Y'}) {
891 # check if we have to increase the new value.
892 $newinnerloop2 += 1;
893 if ($newinnerloop2 >= $pattern->{every2}) {
894 $newinnerloop2 = 0;
895 $newlastvalue2 += $pattern->{add2};
897 # reset counter if needed.
898 $newlastvalue2 = $pattern->{setto2} if ($newlastvalue2 > $pattern->{whenmorethan2});
900 if($calc{'Z'}) {
901 # check if we have to increase the new value.
902 $newinnerloop3 += 1;
903 if ($newinnerloop3 >= $pattern->{every3}) {
904 $newinnerloop3 = 0;
905 $newlastvalue3 += $pattern->{add3};
907 # reset counter if needed.
908 $newlastvalue3 = $pattern->{setto3} if ($newlastvalue3 > $pattern->{whenmorethan3});
911 if($calc{'X'}) {
912 my $newlastvalue1string = _numeration( $newlastvalue1, $pattern->{numbering1}, $locale );
913 $calculated =~ s/\{X\}/$newlastvalue1string/g;
915 if($calc{'Y'}) {
916 my $newlastvalue2string = _numeration( $newlastvalue2, $pattern->{numbering2}, $locale );
917 $calculated =~ s/\{Y\}/$newlastvalue2string/g;
919 if($calc{'Z'}) {
920 my $newlastvalue3string = _numeration( $newlastvalue3, $pattern->{numbering3}, $locale );
921 $calculated =~ s/\{Z\}/$newlastvalue3string/g;
925 return ($calculated,
926 $newlastvalue1, $newlastvalue2, $newlastvalue3,
927 $newinnerloop1, $newinnerloop2, $newinnerloop3);
930 =head2 GetSeq
932 $calculated = GetSeq($subscription, $pattern)
933 $subscription is a hashref containing all the attributes of the table 'subscription'
934 $pattern is a hashref containing all the attributes of the table 'subscription_numberpatterns'
935 this function transforms {X},{Y},{Z} to 150,0,0 for example.
936 return:
937 the sequence in string format
939 =cut
941 sub GetSeq {
942 my ($subscription, $pattern) = @_;
944 return unless ($subscription and $pattern);
946 my $locale = $subscription->{locale};
948 my $calculated = $pattern->{numberingmethod};
950 my $newlastvalue1 = $subscription->{'lastvalue1'} || 0;
951 $newlastvalue1 = _numeration($newlastvalue1, $pattern->{numbering1}, $locale) if ($pattern->{numbering1}); # reset counter if needed.
952 $calculated =~ s/\{X\}/$newlastvalue1/g;
954 my $newlastvalue2 = $subscription->{'lastvalue2'} || 0;
955 $newlastvalue2 = _numeration($newlastvalue2, $pattern->{numbering2}, $locale) if ($pattern->{numbering2}); # reset counter if needed.
956 $calculated =~ s/\{Y\}/$newlastvalue2/g;
958 my $newlastvalue3 = $subscription->{'lastvalue3'} || 0;
959 $newlastvalue3 = _numeration($newlastvalue3, $pattern->{numbering3}, $locale) if ($pattern->{numbering3}); # reset counter if needed.
960 $calculated =~ s/\{Z\}/$newlastvalue3/g;
961 return $calculated;
964 =head2 GetExpirationDate
966 $enddate = GetExpirationDate($subscriptionid, [$startdate])
968 this function return the next expiration date for a subscription given on input args.
970 return
971 the enddate or undef
973 =cut
975 sub GetExpirationDate {
976 my ( $subscriptionid, $startdate ) = @_;
978 return unless ($subscriptionid);
980 my $dbh = C4::Context->dbh;
981 my $subscription = GetSubscription($subscriptionid);
982 my $enddate;
984 # we don't do the same test if the subscription is based on X numbers or on X weeks/months
985 $enddate = $startdate || $subscription->{startdate};
986 my @date = split( /-/, $enddate );
988 return if ( scalar(@date) != 3 || not check_date(@date) );
990 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
991 if ( $frequency and $frequency->{unit} ) {
993 # If Not Irregular
994 if ( my $length = $subscription->{numberlength} ) {
996 #calculate the date of the last issue.
997 for ( my $i = 1 ; $i <= $length ; $i++ ) {
998 $enddate = GetNextDate( $subscription, $enddate );
1000 } elsif ( $subscription->{monthlength} ) {
1001 if ( $$subscription{startdate} ) {
1002 my @enddate = Add_Delta_YM( $date[0], $date[1], $date[2], 0, $subscription->{monthlength} );
1003 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1005 } elsif ( $subscription->{weeklength} ) {
1006 if ( $$subscription{startdate} ) {
1007 my @date = split( /-/, $subscription->{startdate} );
1008 my @enddate = Add_Delta_Days( $date[0], $date[1], $date[2], $subscription->{weeklength} * 7 );
1009 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1011 } else {
1012 $enddate = $subscription->{enddate};
1014 return $enddate;
1015 } else {
1016 return $subscription->{enddate};
1020 =head2 CountSubscriptionFromBiblionumber
1022 $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber)
1023 this returns a count of the subscriptions for a given biblionumber
1024 return :
1025 the number of subscriptions
1027 =cut
1029 sub CountSubscriptionFromBiblionumber {
1030 my ($biblionumber) = @_;
1032 return unless ($biblionumber);
1034 my $dbh = C4::Context->dbh;
1035 my $query = "SELECT count(*) FROM subscription WHERE biblionumber=?";
1036 my $sth = $dbh->prepare($query);
1037 $sth->execute($biblionumber);
1038 my $subscriptionsnumber = $sth->fetchrow;
1039 return $subscriptionsnumber;
1042 =head2 ModSubscriptionHistory
1044 ModSubscriptionHistory($subscriptionid,$histstartdate,$enddate,$recievedlist,$missinglist,$opacnote,$librariannote);
1046 this function modifies the history of a subscription. Put your new values on input arg.
1047 returns the number of rows affected
1049 =cut
1051 sub ModSubscriptionHistory {
1052 my ( $subscriptionid, $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote ) = @_;
1054 return unless ($subscriptionid);
1056 my $dbh = C4::Context->dbh;
1057 my $query = "UPDATE subscriptionhistory
1058 SET histstartdate=?,histenddate=?,recievedlist=?,missinglist=?,opacnote=?,librariannote=?
1059 WHERE subscriptionid=?
1061 my $sth = $dbh->prepare($query);
1062 $receivedlist =~ s/^; // if $receivedlist;
1063 $missinglist =~ s/^; // if $missinglist;
1064 $opacnote =~ s/^; // if $opacnote;
1065 $sth->execute( $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote, $subscriptionid );
1066 return $sth->rows;
1069 =head2 ModSerialStatus
1071 ModSerialStatus($serialid, $serialseq, $planneddate, $publisheddate,
1072 $publisheddatetext, $status, $notes);
1074 This function modify the serial status. Serial status is a number.(eg 2 is "arrived")
1075 Note : if we change from "waited" to something else,then we will have to create a new "waited" entry
1077 =cut
1079 sub ModSerialStatus {
1080 my ($serialid, $serialseq, $planneddate, $publisheddate, $publisheddatetext,
1081 $status, $notes) = @_;
1083 return unless ($serialid);
1085 #It is a usual serial
1086 # 1st, get previous status :
1087 my $dbh = C4::Context->dbh;
1088 my $query = "SELECT serial.subscriptionid,serial.status,subscription.periodicity
1089 FROM serial, subscription
1090 WHERE serial.subscriptionid=subscription.subscriptionid
1091 AND serialid=?";
1092 my $sth = $dbh->prepare($query);
1093 $sth->execute($serialid);
1094 my ( $subscriptionid, $oldstatus, $periodicity ) = $sth->fetchrow;
1095 my $frequency = GetSubscriptionFrequency($periodicity);
1097 # change status & update subscriptionhistory
1098 my $val;
1099 if ( $status == DELETED ) {
1100 DelIssue( { 'serialid' => $serialid, 'subscriptionid' => $subscriptionid, 'serialseq' => $serialseq } );
1101 } else {
1103 my $query = '
1104 UPDATE serial
1105 SET serialseq = ?, publisheddate = ?, publisheddatetext = ?,
1106 planneddate = ?, status = ?, notes = ?
1107 WHERE serialid = ?
1109 $sth = $dbh->prepare($query);
1110 $sth->execute( $serialseq, $publisheddate, $publisheddatetext,
1111 $planneddate, $status, $notes, $serialid );
1112 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1113 $sth = $dbh->prepare($query);
1114 $sth->execute($subscriptionid);
1115 my $val = $sth->fetchrow_hashref;
1116 unless ( $val->{manualhistory} ) {
1117 $query = "SELECT missinglist,recievedlist FROM subscriptionhistory WHERE subscriptionid=?";
1118 $sth = $dbh->prepare($query);
1119 $sth->execute($subscriptionid);
1120 my ( $missinglist, $recievedlist ) = $sth->fetchrow;
1122 if ( $status == ARRIVED || ($oldstatus == ARRIVED && $status != ARRIVED) ) {
1123 $recievedlist .= "; $serialseq"
1124 if ($recievedlist !~ /(^|;)\s*$serialseq(?=;|$)/);
1127 # in case serial has been previously marked as missing
1128 if (grep /$status/, (EXPECTED, ARRIVED, LATE, CLAIMED)) {
1129 $missinglist=~ s/(^|;)\s*$serialseq(?=;|$)//g;
1132 $missinglist .= "; $serialseq"
1133 if ( ( grep { $_ == $status } ( MISSING_STATUSES ) ) && ( $missinglist !~/(^|;)\s*$serialseq(?=;|$)/ ) );
1134 $missinglist .= "; not issued $serialseq"
1135 if ( $status == NOT_ISSUED && $missinglist !~ /(^|;)\s*$serialseq(?=;|$)/ );
1137 $query = "UPDATE subscriptionhistory SET recievedlist=?, missinglist=? WHERE subscriptionid=?";
1138 $sth = $dbh->prepare($query);
1139 $recievedlist =~ s/^; //;
1140 $missinglist =~ s/^; //;
1141 $sth->execute( $recievedlist, $missinglist, $subscriptionid );
1145 # create new expected entry if needed (ie : was "expected" and has changed)
1146 my $otherIssueExpected = scalar findSerialsByStatus(EXPECTED, $subscriptionid);
1147 if ( !$otherIssueExpected && $oldstatus == EXPECTED && $status != EXPECTED ) {
1148 my $subscription = GetSubscription($subscriptionid);
1149 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1151 # next issue number
1152 my (
1153 $newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
1154 $newinnerloop1, $newinnerloop2, $newinnerloop3
1156 = GetNextSeq( $subscription, $pattern, $publisheddate );
1158 # next date (calculated from actual date & frequency parameters)
1159 my $nextpublisheddate = GetNextDate($subscription, $publisheddate, 1);
1160 my $nextpubdate = $nextpublisheddate;
1161 $query = "UPDATE subscription SET lastvalue1=?, lastvalue2=?, lastvalue3=?, innerloop1=?, innerloop2=?, innerloop3=?
1162 WHERE subscriptionid = ?";
1163 $sth = $dbh->prepare($query);
1164 $sth->execute( $newlastvalue1, $newlastvalue2, $newlastvalue3, $newinnerloop1, $newinnerloop2, $newinnerloop3, $subscriptionid );
1166 NewIssue( $newserialseq, $subscriptionid, $subscription->{'biblionumber'}, 1, $nextpubdate, $nextpubdate );
1168 # check if an alert must be sent... (= a letter is defined & status became "arrived"
1169 if ( $subscription->{letter} && $status == ARRIVED && $oldstatus != ARRIVED ) {
1170 require C4::Letters;
1171 C4::Letters::SendAlerts( 'issue', $serialid, $subscription->{letter} );
1175 return;
1178 =head2 GetNextExpected
1180 $nextexpected = GetNextExpected($subscriptionid)
1182 Get the planneddate for the current expected issue of the subscription.
1184 returns a hashref:
1186 $nextexepected = {
1187 serialid => int
1188 planneddate => ISO date
1191 =cut
1193 sub GetNextExpected {
1194 my ($subscriptionid) = @_;
1196 my $dbh = C4::Context->dbh;
1197 my $query = qq{
1198 SELECT *
1199 FROM serial
1200 WHERE subscriptionid = ?
1201 AND status = ?
1202 LIMIT 1
1204 my $sth = $dbh->prepare($query);
1206 # Each subscription has only one 'expected' issue.
1207 $sth->execute( $subscriptionid, EXPECTED );
1208 my $nextissue = $sth->fetchrow_hashref;
1209 if ( !$nextissue ) {
1210 $query = qq{
1211 SELECT *
1212 FROM serial
1213 WHERE subscriptionid = ?
1214 ORDER BY publisheddate DESC
1215 LIMIT 1
1217 $sth = $dbh->prepare($query);
1218 $sth->execute($subscriptionid);
1219 $nextissue = $sth->fetchrow_hashref;
1221 foreach(qw/planneddate publisheddate/) {
1222 if ( !defined $nextissue->{$_} ) {
1223 # or should this default to 1st Jan ???
1224 $nextissue->{$_} = strftime( '%Y-%m-%d', localtime );
1226 $nextissue->{$_} = ($nextissue->{$_} ne '0000-00-00')
1227 ? $nextissue->{$_}
1228 : undef;
1231 return $nextissue;
1234 =head2 ModNextExpected
1236 ModNextExpected($subscriptionid,$date)
1238 Update the planneddate for the current expected issue of the subscription.
1239 This will modify all future prediction results.
1241 C<$date> is an ISO date.
1243 returns 0
1245 =cut
1247 sub ModNextExpected {
1248 my ( $subscriptionid, $date ) = @_;
1249 my $dbh = C4::Context->dbh;
1251 #FIXME: Would expect to only set planneddate, but we set both on new issue creation, so updating it here
1252 my $sth = $dbh->prepare('UPDATE serial SET planneddate=?,publisheddate=? WHERE subscriptionid=? AND status=?');
1254 # Each subscription has only one 'expected' issue.
1255 $sth->execute( $date, $date, $subscriptionid, EXPECTED );
1256 return 0;
1260 =head2 GetSubscriptionIrregularities
1262 =over 4
1264 =item @irreg = &GetSubscriptionIrregularities($subscriptionid);
1265 get the list of irregularities for a subscription
1267 =back
1269 =cut
1271 sub GetSubscriptionIrregularities {
1272 my $subscriptionid = shift;
1274 return unless $subscriptionid;
1276 my $dbh = C4::Context->dbh;
1277 my $query = qq{
1278 SELECT irregularity
1279 FROM subscription
1280 WHERE subscriptionid = ?
1282 my $sth = $dbh->prepare($query);
1283 $sth->execute($subscriptionid);
1285 my ($result) = $sth->fetchrow_array;
1286 my @irreg = split /;/, $result;
1288 return @irreg;
1291 =head2 ModSubscription
1293 this function modifies a subscription. Put all new values on input args.
1294 returns the number of rows affected
1296 =cut
1298 sub ModSubscription {
1299 my (
1300 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
1301 $periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
1302 $numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
1303 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
1304 $biblionumber, $callnumber, $notes, $letter, $manualhistory,
1305 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1306 $graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
1307 $itemtype, $previousitemtype
1308 ) = @_;
1310 my $dbh = C4::Context->dbh;
1311 my $query = "UPDATE subscription
1312 SET librarian=?, branchcode=?, aqbooksellerid=?, cost=?, aqbudgetid=?,
1313 startdate=?, periodicity=?, firstacquidate=?, irregularity=?,
1314 numberpattern=?, locale=?, numberlength=?, weeklength=?, monthlength=?,
1315 lastvalue1=?, innerloop1=?, lastvalue2=?, innerloop2=?,
1316 lastvalue3=?, innerloop3=?, status=?, biblionumber=?,
1317 callnumber=?, notes=?, letter=?, manualhistory=?,
1318 internalnotes=?, serialsadditems=?, staffdisplaycount=?,
1319 opacdisplaycount=?, graceperiod=?, location = ?, enddate=?,
1320 skip_serialseq=?, itemtype=?, previousitemtype=?
1321 WHERE subscriptionid = ?";
1323 my $sth = $dbh->prepare($query);
1324 $sth->execute(
1325 $auser, $branchcode, $aqbooksellerid, $cost,
1326 $aqbudgetid, $startdate, $periodicity, $firstacquidate,
1327 $irregularity, $numberpattern, $locale, $numberlength,
1328 $weeklength, $monthlength, $lastvalue1, $innerloop1,
1329 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3,
1330 $status, $biblionumber, $callnumber, $notes,
1331 $letter, ($manualhistory ? $manualhistory : 0),
1332 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1333 $graceperiod, $location, $enddate, $skip_serialseq,
1334 $itemtype, $previousitemtype,
1335 $subscriptionid
1337 my $rows = $sth->rows;
1339 logaction( "SERIAL", "MODIFY", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1340 return $rows;
1343 =head2 NewSubscription
1345 $subscriptionid = &NewSubscription($auser,branchcode,$aqbooksellerid,$cost,$aqbudgetid,$biblionumber,
1346 $startdate,$periodicity,$numberlength,$weeklength,$monthlength,
1347 $lastvalue1,$innerloop1,$lastvalue2,$innerloop2,$lastvalue3,$innerloop3,
1348 $status, $notes, $letter, $firstacquidate, $irregularity, $numberpattern,
1349 $locale, $callnumber, $manualhistory, $internalnotes, $serialsadditems,
1350 $staffdisplaycount, $opacdisplaycount, $graceperiod, $location, $enddate,
1351 $skip_serialseq, $itemtype, $previousitemtype);
1353 Create a new subscription with value given on input args.
1355 return :
1356 the id of this new subscription
1358 =cut
1360 sub NewSubscription {
1361 my (
1362 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1363 $startdate, $periodicity, $numberlength, $weeklength, $monthlength,
1364 $lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
1365 $innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
1366 $numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
1367 $serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
1368 $location, $enddate, $skip_serialseq, $itemtype, $previousitemtype
1369 ) = @_;
1370 my $dbh = C4::Context->dbh;
1372 #save subscription (insert into database)
1373 my $query = qq|
1374 INSERT INTO subscription
1375 (librarian, branchcode, aqbooksellerid, cost, aqbudgetid,
1376 biblionumber, startdate, periodicity, numberlength, weeklength,
1377 monthlength, lastvalue1, innerloop1, lastvalue2, innerloop2,
1378 lastvalue3, innerloop3, status, notes, letter, firstacquidate,
1379 irregularity, numberpattern, locale, callnumber,
1380 manualhistory, internalnotes, serialsadditems, staffdisplaycount,
1381 opacdisplaycount, graceperiod, location, enddate, skip_serialseq,
1382 itemtype, previousitemtype)
1383 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
1385 my $sth = $dbh->prepare($query);
1386 $sth->execute(
1387 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1388 $startdate, $periodicity, $numberlength, $weeklength,
1389 $monthlength, $lastvalue1, $innerloop1, $lastvalue2, $innerloop2,
1390 $lastvalue3, $innerloop3, $status, $notes, $letter,
1391 $firstacquidate, $irregularity, $numberpattern, $locale, $callnumber,
1392 $manualhistory, $internalnotes, $serialsadditems, $staffdisplaycount,
1393 $opacdisplaycount, $graceperiod, $location, $enddate, $skip_serialseq,
1394 $itemtype, $previousitemtype
1397 my $subscriptionid = $dbh->{'mysql_insertid'};
1398 unless ($enddate) {
1399 $enddate = GetExpirationDate( $subscriptionid, $startdate );
1400 $query = qq|
1401 UPDATE subscription
1402 SET enddate=?
1403 WHERE subscriptionid=?
1405 $sth = $dbh->prepare($query);
1406 $sth->execute( $enddate, $subscriptionid );
1409 # then create the 1st expected number
1410 $query = qq(
1411 INSERT INTO subscriptionhistory
1412 (biblionumber, subscriptionid, histstartdate, missinglist, recievedlist)
1413 VALUES (?,?,?, '', '')
1415 $sth = $dbh->prepare($query);
1416 $sth->execute( $biblionumber, $subscriptionid, $startdate);
1418 # reread subscription to get a hash (for calculation of the 1st issue number)
1419 my $subscription = GetSubscription($subscriptionid);
1420 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1422 # calculate issue number
1423 my $serialseq = GetSeq($subscription, $pattern) || q{};
1425 Koha::Serial->new(
1427 serialseq => $serialseq,
1428 serialseq_x => $subscription->{'lastvalue1'},
1429 serialseq_y => $subscription->{'lastvalue2'},
1430 serialseq_z => $subscription->{'lastvalue3'},
1431 subscriptionid => $subscriptionid,
1432 biblionumber => $biblionumber,
1433 status => EXPECTED,
1434 planneddate => $firstacquidate,
1435 publisheddate => $firstacquidate,
1437 )->store();
1439 logaction( "SERIAL", "ADD", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1441 #set serial flag on biblio if not already set.
1442 my $biblio = Koha::Biblios->find( $biblionumber );
1443 if ( $biblio and !$biblio->serial ) {
1444 my $record = GetMarcBiblio({ biblionumber => $biblionumber });
1445 my ( $tag, $subf ) = GetMarcFromKohaField( 'biblio.serial', $biblio->frameworkcode );
1446 if ($tag) {
1447 eval { $record->field($tag)->update( $subf => 1 ); };
1449 ModBiblio( $record, $biblionumber, $biblio->frameworkcode );
1451 return $subscriptionid;
1454 =head2 ReNewSubscription
1456 ReNewSubscription($subscriptionid,$user,$startdate,$numberlength,$weeklength,$monthlength,$note)
1458 this function renew a subscription with values given on input args.
1460 =cut
1462 sub ReNewSubscription {
1463 my ( $subscriptionid, $user, $startdate, $numberlength, $weeklength, $monthlength, $note ) = @_;
1464 my $dbh = C4::Context->dbh;
1465 my $subscription = GetSubscription($subscriptionid);
1466 my $query = qq|
1467 SELECT *
1468 FROM biblio
1469 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
1470 WHERE biblio.biblionumber=?
1472 my $sth = $dbh->prepare($query);
1473 $sth->execute( $subscription->{biblionumber} );
1474 my $biblio = $sth->fetchrow_hashref;
1476 if ( C4::Context->preference("RenewSerialAddsSuggestion") ) {
1477 require C4::Suggestions;
1478 C4::Suggestions::NewSuggestion(
1479 { 'suggestedby' => $user,
1480 'title' => $subscription->{bibliotitle},
1481 'author' => $biblio->{author},
1482 'publishercode' => $biblio->{publishercode},
1483 'note' => $biblio->{note},
1484 'biblionumber' => $subscription->{biblionumber}
1489 $numberlength ||= 0; # Should not we raise an exception instead?
1490 $weeklength ||= 0;
1492 # renew subscription
1493 $query = qq|
1494 UPDATE subscription
1495 SET startdate=?,numberlength=?,weeklength=?,monthlength=?,reneweddate=NOW()
1496 WHERE subscriptionid=?
1498 $sth = $dbh->prepare($query);
1499 $sth->execute( $startdate, $numberlength, $weeklength, $monthlength, $subscriptionid );
1500 my $enddate = GetExpirationDate($subscriptionid);
1501 $debug && warn "enddate :$enddate";
1502 $query = qq|
1503 UPDATE subscription
1504 SET enddate=?
1505 WHERE subscriptionid=?
1507 $sth = $dbh->prepare($query);
1508 $sth->execute( $enddate, $subscriptionid );
1510 logaction( "SERIAL", "RENEW", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1511 return;
1514 =head2 NewIssue
1516 NewIssue($serialseq,$subscriptionid,$biblionumber,$status, $planneddate, $publisheddate, $notes)
1518 Create a new issue stored on the database.
1519 Note : we have to update the recievedlist and missinglist on subscriptionhistory for this subscription.
1520 returns the serial id
1522 =cut
1524 sub NewIssue {
1525 my ( $serialseq, $subscriptionid, $biblionumber, $status, $planneddate,
1526 $publisheddate, $publisheddatetext, $notes ) = @_;
1527 ### FIXME biblionumber CAN be provided by subscriptionid. So Do we STILL NEED IT ?
1529 return unless ($subscriptionid);
1531 my $schema = Koha::Database->new()->schema();
1533 my $subscription = Koha::Subscriptions->find( $subscriptionid );
1535 my $serial = Koha::Serial->new(
1537 serialseq => $serialseq,
1538 serialseq_x => $subscription->lastvalue1(),
1539 serialseq_y => $subscription->lastvalue2(),
1540 serialseq_z => $subscription->lastvalue3(),
1541 subscriptionid => $subscriptionid,
1542 biblionumber => $biblionumber,
1543 status => $status,
1544 planneddate => $planneddate,
1545 publisheddate => $publisheddate,
1546 publisheddatetext => $publisheddatetext,
1547 notes => $notes,
1549 )->store();
1551 my $serialid = $serial->id();
1553 my $subscription_history = Koha::Subscription::Histories->find($subscriptionid);
1554 my $missinglist = $subscription_history->missinglist();
1555 my $recievedlist = $subscription_history->recievedlist();
1557 if ( $status == ARRIVED ) {
1558 ### TODO Add a feature that improves recognition and description.
1559 ### As such count (serialseq) i.e. : N18,2(N19),N20
1560 ### Would use substr and index But be careful to previous presence of ()
1561 $recievedlist .= "; $serialseq" unless ( index( $recievedlist, $serialseq ) > 0 );
1563 if ( grep { /^$status$/ } (MISSING_STATUSES) ) {
1564 $missinglist .= "; $serialseq" unless ( index( $missinglist, $serialseq ) > 0 );
1567 $recievedlist =~ s/^; //;
1568 $missinglist =~ s/^; //;
1570 $subscription_history->recievedlist($recievedlist);
1571 $subscription_history->missinglist($missinglist);
1572 $subscription_history->store();
1574 return $serialid;
1577 =head2 HasSubscriptionStrictlyExpired
1579 1 or 0 = HasSubscriptionStrictlyExpired($subscriptionid)
1581 the subscription has stricly expired when today > the end subscription date
1583 return :
1584 1 if true, 0 if false, -1 if the expiration date is not set.
1586 =cut
1588 sub HasSubscriptionStrictlyExpired {
1590 # Getting end of subscription date
1591 my ($subscriptionid) = @_;
1593 return unless ($subscriptionid);
1595 my $dbh = C4::Context->dbh;
1596 my $subscription = GetSubscription($subscriptionid);
1597 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1599 # If the expiration date is set
1600 if ( $expirationdate != 0 ) {
1601 my ( $endyear, $endmonth, $endday ) = split( '-', $expirationdate );
1603 # Getting today's date
1604 my ( $nowyear, $nowmonth, $nowday ) = Today();
1606 # if today's date > expiration date, then the subscription has stricly expired
1607 if ( Delta_Days( $nowyear, $nowmonth, $nowday, $endyear, $endmonth, $endday ) < 0 ) {
1608 return 1;
1609 } else {
1610 return 0;
1612 } else {
1614 # There are some cases where the expiration date is not set
1615 # As we can't determine if the subscription has expired on a date-basis,
1616 # we return -1;
1617 return -1;
1621 =head2 HasSubscriptionExpired
1623 $has_expired = HasSubscriptionExpired($subscriptionid)
1625 the subscription has expired when the next issue to arrive is out of subscription limit.
1627 return :
1628 0 if the subscription has not expired
1629 1 if the subscription has expired
1630 2 if has subscription does not have a valid expiration date set
1632 =cut
1634 sub HasSubscriptionExpired {
1635 my ($subscriptionid) = @_;
1637 return unless ($subscriptionid);
1639 my $dbh = C4::Context->dbh;
1640 my $subscription = GetSubscription($subscriptionid);
1641 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1642 if ( $frequency and $frequency->{unit} ) {
1643 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1644 if (!defined $expirationdate) {
1645 $expirationdate = q{};
1647 my $query = qq|
1648 SELECT max(planneddate)
1649 FROM serial
1650 WHERE subscriptionid=?
1652 my $sth = $dbh->prepare($query);
1653 $sth->execute($subscriptionid);
1654 my ($res) = $sth->fetchrow;
1655 if (!$res || $res=~m/^0000/) {
1656 return 0;
1658 my @res = split( /-/, $res );
1659 my @endofsubscriptiondate = split( /-/, $expirationdate );
1660 return 2 if ( scalar(@res) != 3 || scalar(@endofsubscriptiondate) != 3 || not check_date(@res) || not check_date(@endofsubscriptiondate) );
1661 return 1
1662 if ( ( @endofsubscriptiondate && Delta_Days( $res[0], $res[1], $res[2], $endofsubscriptiondate[0], $endofsubscriptiondate[1], $endofsubscriptiondate[2] ) <= 0 )
1663 || ( !$res ) );
1664 return 0;
1665 } else {
1666 # Irregular
1667 if ( $subscription->{'numberlength'} ) {
1668 my $countreceived = countissuesfrom( $subscriptionid, $subscription->{'startdate'} );
1669 return 1 if ( $countreceived > $subscription->{'numberlength'} );
1670 return 0;
1671 } else {
1672 return 0;
1675 return 0; # Notice that you'll never get here.
1678 =head2 DelSubscription
1680 DelSubscription($subscriptionid)
1681 this function deletes subscription which has $subscriptionid as id.
1683 =cut
1685 sub DelSubscription {
1686 my ($subscriptionid) = @_;
1687 my $dbh = C4::Context->dbh;
1688 $dbh->do("DELETE FROM subscription WHERE subscriptionid=?", undef, $subscriptionid);
1689 $dbh->do("DELETE FROM subscriptionhistory WHERE subscriptionid=?", undef, $subscriptionid);
1690 $dbh->do("DELETE FROM serial WHERE subscriptionid=?", undef, $subscriptionid);
1692 my $afs = Koha::AdditionalField->all({tablename => 'subscription'});
1693 foreach my $af (@$afs) {
1694 $af->delete_values({record_id => $subscriptionid});
1697 logaction( "SERIAL", "DELETE", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1700 =head2 DelIssue
1702 DelIssue($serialseq,$subscriptionid)
1703 this function deletes an issue which has $serialseq and $subscriptionid given on input arg.
1705 returns the number of rows affected
1707 =cut
1709 sub DelIssue {
1710 my ($dataissue) = @_;
1711 my $dbh = C4::Context->dbh;
1712 ### TODO Add itemdeletion. Would need to get itemnumbers. Should be in a pref ?
1714 my $query = qq|
1715 DELETE FROM serial
1716 WHERE serialid= ?
1717 AND subscriptionid= ?
1719 my $mainsth = $dbh->prepare($query);
1720 $mainsth->execute( $dataissue->{'serialid'}, $dataissue->{'subscriptionid'} );
1722 #Delete element from subscription history
1723 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1724 my $sth = $dbh->prepare($query);
1725 $sth->execute( $dataissue->{'subscriptionid'} );
1726 my $val = $sth->fetchrow_hashref;
1727 unless ( $val->{manualhistory} ) {
1728 my $query = qq|
1729 SELECT * FROM subscriptionhistory
1730 WHERE subscriptionid= ?
1732 my $sth = $dbh->prepare($query);
1733 $sth->execute( $dataissue->{'subscriptionid'} );
1734 my $data = $sth->fetchrow_hashref;
1735 my $serialseq = $dataissue->{'serialseq'};
1736 $data->{'missinglist'} =~ s/\b$serialseq\b//;
1737 $data->{'recievedlist'} =~ s/\b$serialseq\b//;
1738 my $strsth = "UPDATE subscriptionhistory SET " . join( ",", map { join( "=", $_, $dbh->quote( $data->{$_} ) ) } keys %$data ) . " WHERE subscriptionid=?";
1739 $sth = $dbh->prepare($strsth);
1740 $sth->execute( $dataissue->{'subscriptionid'} );
1743 return $mainsth->rows;
1746 =head2 GetLateOrMissingIssues
1748 @issuelist = GetLateMissingIssues($supplierid,$serialid)
1750 this function selects missing issues on database - where serial.status = MISSING* or serial.status = LATE or planneddate<now
1752 return :
1753 the issuelist as an array of hash refs. Each element of this array contains
1754 name,title,planneddate,serialseq,serial.subscriptionid from tables : subscription, serial & biblio
1756 =cut
1758 sub GetLateOrMissingIssues {
1759 my ( $supplierid, $serialid, $order ) = @_;
1761 return unless ( $supplierid or $serialid );
1763 my $dbh = C4::Context->dbh;
1765 my $sth;
1766 my $byserial = '';
1767 if ($serialid) {
1768 $byserial = "and serialid = " . $serialid;
1770 if ($order) {
1771 $order .= ", title";
1772 } else {
1773 $order = "title";
1775 my $missing_statuses_string = join ',', (MISSING_STATUSES);
1776 if ($supplierid) {
1777 $sth = $dbh->prepare(
1778 "SELECT
1779 serialid, aqbooksellerid, name,
1780 biblio.title, biblioitems.issn, planneddate, serialseq,
1781 serial.status, serial.subscriptionid, claimdate, claims_count,
1782 subscription.branchcode
1783 FROM serial
1784 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1785 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1786 LEFT JOIN biblioitems ON subscription.biblionumber=biblioitems.biblionumber
1787 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1788 WHERE subscription.subscriptionid = serial.subscriptionid
1789 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1790 AND subscription.aqbooksellerid=$supplierid
1791 $byserial
1792 ORDER BY $order"
1794 } else {
1795 $sth = $dbh->prepare(
1796 "SELECT
1797 serialid, aqbooksellerid, name,
1798 biblio.title, planneddate, serialseq,
1799 serial.status, serial.subscriptionid, claimdate, claims_count,
1800 subscription.branchcode
1801 FROM serial
1802 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1803 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1804 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1805 WHERE subscription.subscriptionid = serial.subscriptionid
1806 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1807 $byserial
1808 ORDER BY $order"
1811 $sth->execute( EXPECTED, LATE, CLAIMED );
1812 my @issuelist;
1813 while ( my $line = $sth->fetchrow_hashref ) {
1815 if ($line->{planneddate} && $line->{planneddate} !~/^0+\-/) {
1816 $line->{planneddateISO} = $line->{planneddate};
1817 $line->{planneddate} = output_pref( { dt => dt_from_string( $line->{"planneddate"} ), dateonly => 1 } );
1819 if ($line->{claimdate} && $line->{claimdate} !~/^0+\-/) {
1820 $line->{claimdateISO} = $line->{claimdate};
1821 $line->{claimdate} = output_pref( { dt => dt_from_string( $line->{"claimdate"} ), dateonly => 1 } );
1823 $line->{"status".$line->{status}} = 1;
1825 my $additional_field_values = Koha::AdditionalField->fetch_all_values({
1826 record_id => $line->{subscriptionid},
1827 tablename => 'subscription'
1829 %$line = ( %$line, additional_fields => $additional_field_values->{$line->{subscriptionid}} );
1831 push @issuelist, $line;
1833 return @issuelist;
1836 =head2 updateClaim
1838 &updateClaim($serialid)
1840 this function updates the time when a claim is issued for late/missing items
1842 called from claims.pl file
1844 =cut
1846 sub updateClaim {
1847 my ($serialids) = @_;
1848 return unless $serialids;
1849 unless ( ref $serialids ) {
1850 $serialids = [ $serialids ];
1852 my $dbh = C4::Context->dbh;
1853 return $dbh->do(q|
1854 UPDATE serial
1855 SET claimdate = NOW(),
1856 claims_count = claims_count + 1,
1857 status = ?
1858 WHERE serialid in (| . join( q|,|, (q|?|) x @$serialids ) . q|)|,
1859 {}, CLAIMED, @$serialids );
1862 =head2 check_routing
1864 $result = &check_routing($subscriptionid)
1866 this function checks to see if a serial has a routing list and returns the count of routingid
1867 used to show either an 'add' or 'edit' link
1869 =cut
1871 sub check_routing {
1872 my ($subscriptionid) = @_;
1874 return unless ($subscriptionid);
1876 my $dbh = C4::Context->dbh;
1877 my $sth = $dbh->prepare(
1878 "SELECT count(routingid) routingids FROM subscription LEFT JOIN subscriptionroutinglist
1879 ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
1880 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC
1883 $sth->execute($subscriptionid);
1884 my $line = $sth->fetchrow_hashref;
1885 my $result = $line->{'routingids'};
1886 return $result;
1889 =head2 addroutingmember
1891 addroutingmember($borrowernumber,$subscriptionid)
1893 this function takes a borrowernumber and subscriptionid and adds the member to the
1894 routing list for that serial subscription and gives them a rank on the list
1895 of either 1 or highest current rank + 1
1897 =cut
1899 sub addroutingmember {
1900 my ( $borrowernumber, $subscriptionid ) = @_;
1902 return unless ($borrowernumber and $subscriptionid);
1904 my $rank;
1905 my $dbh = C4::Context->dbh;
1906 my $sth = $dbh->prepare( "SELECT max(ranking) rank FROM subscriptionroutinglist WHERE subscriptionid = ?" );
1907 $sth->execute($subscriptionid);
1908 while ( my $line = $sth->fetchrow_hashref ) {
1909 if ( $line->{'rank'} > 0 ) {
1910 $rank = $line->{'rank'} + 1;
1911 } else {
1912 $rank = 1;
1915 $sth = $dbh->prepare( "INSERT INTO subscriptionroutinglist (subscriptionid,borrowernumber,ranking) VALUES (?,?,?)" );
1916 $sth->execute( $subscriptionid, $borrowernumber, $rank );
1919 =head2 reorder_members
1921 reorder_members($subscriptionid,$routingid,$rank)
1923 this function is used to reorder the routing list
1925 it takes the routingid of the member one wants to re-rank and the rank it is to move to
1926 - it gets all members on list puts their routingid's into an array
1927 - removes the one in the array that is $routingid
1928 - then reinjects $routingid at point indicated by $rank
1929 - then update the database with the routingids in the new order
1931 =cut
1933 sub reorder_members {
1934 my ( $subscriptionid, $routingid, $rank ) = @_;
1935 my $dbh = C4::Context->dbh;
1936 my $sth = $dbh->prepare( "SELECT * FROM subscriptionroutinglist WHERE subscriptionid = ? ORDER BY ranking ASC" );
1937 $sth->execute($subscriptionid);
1938 my @result;
1939 while ( my $line = $sth->fetchrow_hashref ) {
1940 push( @result, $line->{'routingid'} );
1943 # To find the matching index
1944 my $i;
1945 my $key = -1; # to allow for 0 being a valid response
1946 for ( $i = 0 ; $i < @result ; $i++ ) {
1947 if ( $routingid == $result[$i] ) {
1948 $key = $i; # save the index
1949 last;
1953 # if index exists in array then move it to new position
1954 if ( $key > -1 && $rank > 0 ) {
1955 my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
1956 my $moving_item = splice( @result, $key, 1 );
1957 splice( @result, $new_rank, 0, $moving_item );
1959 for ( my $j = 0 ; $j < @result ; $j++ ) {
1960 my $sth = $dbh->prepare( "UPDATE subscriptionroutinglist SET ranking = '" . ( $j + 1 ) . "' WHERE routingid = '" . $result[$j] . "'" );
1961 $sth->execute;
1963 return;
1966 =head2 delroutingmember
1968 delroutingmember($routingid,$subscriptionid)
1970 this function either deletes one member from routing list if $routingid exists otherwise
1971 deletes all members from the routing list
1973 =cut
1975 sub delroutingmember {
1977 # if $routingid exists then deletes that row otherwise deletes all with $subscriptionid
1978 my ( $routingid, $subscriptionid ) = @_;
1979 my $dbh = C4::Context->dbh;
1980 if ($routingid) {
1981 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE routingid = ?");
1982 $sth->execute($routingid);
1983 reorder_members( $subscriptionid, $routingid );
1984 } else {
1985 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE subscriptionid = ?");
1986 $sth->execute($subscriptionid);
1988 return;
1991 =head2 getroutinglist
1993 @routinglist = getroutinglist($subscriptionid)
1995 this gets the info from the subscriptionroutinglist for $subscriptionid
1997 return :
1998 the routinglist as an array. Each element of the array contains a hash_ref containing
1999 routingid - a unique id, borrowernumber, ranking, and biblionumber of subscription
2001 =cut
2003 sub getroutinglist {
2004 my ($subscriptionid) = @_;
2005 my $dbh = C4::Context->dbh;
2006 my $sth = $dbh->prepare(
2007 'SELECT routingid, borrowernumber, ranking, biblionumber
2008 FROM subscription
2009 JOIN subscriptionroutinglist ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
2010 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC'
2012 $sth->execute($subscriptionid);
2013 my $routinglist = $sth->fetchall_arrayref({});
2014 return @{$routinglist};
2017 =head2 countissuesfrom
2019 $result = countissuesfrom($subscriptionid,$startdate)
2021 Returns a count of serial rows matching the given subsctiptionid
2022 with published date greater than startdate
2024 =cut
2026 sub countissuesfrom {
2027 my ( $subscriptionid, $startdate ) = @_;
2028 my $dbh = C4::Context->dbh;
2029 my $query = qq|
2030 SELECT count(*)
2031 FROM serial
2032 WHERE subscriptionid=?
2033 AND serial.publisheddate>?
2035 my $sth = $dbh->prepare($query);
2036 $sth->execute( $subscriptionid, $startdate );
2037 my ($countreceived) = $sth->fetchrow;
2038 return $countreceived;
2041 =head2 CountIssues
2043 $result = CountIssues($subscriptionid)
2045 Returns a count of serial rows matching the given subsctiptionid
2047 =cut
2049 sub CountIssues {
2050 my ($subscriptionid) = @_;
2051 my $dbh = C4::Context->dbh;
2052 my $query = qq|
2053 SELECT count(*)
2054 FROM serial
2055 WHERE subscriptionid=?
2057 my $sth = $dbh->prepare($query);
2058 $sth->execute($subscriptionid);
2059 my ($countreceived) = $sth->fetchrow;
2060 return $countreceived;
2063 =head2 HasItems
2065 $result = HasItems($subscriptionid)
2067 returns a count of items from serial matching the subscriptionid
2069 =cut
2071 sub HasItems {
2072 my ($subscriptionid) = @_;
2073 my $dbh = C4::Context->dbh;
2074 my $query = q|
2075 SELECT COUNT(serialitems.itemnumber)
2076 FROM serial
2077 LEFT JOIN serialitems USING(serialid)
2078 WHERE subscriptionid=? AND serialitems.serialid IS NOT NULL
2080 my $sth=$dbh->prepare($query);
2081 $sth->execute($subscriptionid);
2082 my ($countitems)=$sth->fetchrow_array();
2083 return $countitems;
2086 =head2 abouttoexpire
2088 $result = abouttoexpire($subscriptionid)
2090 this function alerts you to the penultimate issue for a serial subscription
2092 returns 1 - if this is the penultimate issue
2093 returns 0 - if not
2095 =cut
2097 sub abouttoexpire {
2098 my ($subscriptionid) = @_;
2099 my $dbh = C4::Context->dbh;
2100 my $subscription = GetSubscription($subscriptionid);
2101 my $per = $subscription->{'periodicity'};
2102 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($per);
2103 if ($frequency and $frequency->{unit}){
2105 my $expirationdate = GetExpirationDate($subscriptionid);
2107 my ($res) = $dbh->selectrow_array('select max(planneddate) from serial where subscriptionid = ?', undef, $subscriptionid);
2108 my $nextdate = GetNextDate($subscription, $res);
2110 # only compare dates if both dates exist.
2111 if ($nextdate and $expirationdate) {
2112 if(Date::Calc::Delta_Days(
2113 split( /-/, $nextdate ),
2114 split( /-/, $expirationdate )
2115 ) <= 0) {
2116 return 1;
2120 } elsif ($subscription->{numberlength}>0) {
2121 return (countissuesfrom($subscriptionid,$subscription->{'startdate'}) >=$subscription->{numberlength}-1);
2124 return 0;
2127 =head2 GetFictiveIssueNumber
2129 $issueno = GetFictiveIssueNumber($subscription, $publishedate);
2131 Get the position of the issue published at $publisheddate, considering the
2132 first issue (at firstacquidate) is at position 1, the next is at position 2, etc...
2133 This issuenumber doesn't take into account irregularities, so, for instance, if the 3rd
2134 issue is declared as 'irregular' (will be skipped at receipt), the next issue number
2135 will be 4, not 3. It's why it is called 'fictive'. It is NOT a serial seq, and is not
2136 depending on how many rows are in serial table.
2137 The issue number calculation is based on subscription frequency, first acquisition
2138 date, and $publisheddate.
2140 Returns undef when called for irregular frequencies.
2142 The routine is used to skip irregularities when calculating the next issue
2143 date (in GetNextDate) or the next issue number (in GetNextSeq).
2145 =cut
2147 sub GetFictiveIssueNumber {
2148 my ($subscription, $publisheddate) = @_;
2150 my $frequency = GetSubscriptionFrequency($subscription->{'periodicity'});
2151 my $unit = $frequency->{unit} ? lc $frequency->{'unit'} : undef;
2152 return if !$unit;
2153 my $issueno;
2155 my ( $year, $month, $day ) = split /-/, $publisheddate;
2156 my ( $fa_year, $fa_month, $fa_day ) = split /-/, $subscription->{'firstacquidate'};
2157 my $delta = _delta_units( [$fa_year, $fa_month, $fa_day], [$year, $month, $day], $unit );
2159 if( $frequency->{'unitsperissue'} == 1 ) {
2160 $issueno = $delta * $frequency->{'issuesperunit'} + $subscription->{'countissuesperunit'};
2161 } else { # issuesperunit == 1
2162 $issueno = 1 + int( $delta / $frequency->{'unitsperissue'} );
2164 return $issueno;
2167 sub _delta_units {
2168 my ( $date1, $date2, $unit ) = @_;
2169 # date1 and date2 are array refs in the form [ yy, mm, dd ]
2171 if( $unit eq 'day' ) {
2172 return Delta_Days( @$date1, @$date2 );
2173 } elsif( $unit eq 'week' ) {
2174 return int( Delta_Days( @$date1, @$date2 ) / 7 );
2177 # In case of months or years, this is a wrapper around N_Delta_YMD.
2178 # Note that N_Delta_YMD returns 29 days between e.g. 22-2-72 and 22-3-72
2179 # while we expect 1 month.
2180 my @delta = N_Delta_YMD( @$date1, @$date2 );
2181 if( $delta[2] > 27 ) {
2182 # Check if we could add a month
2183 my @jump = Add_Delta_YM( @$date1, $delta[0], 1 + $delta[1] );
2184 if( Delta_Days( @jump, @$date2 ) >= 0 ) {
2185 $delta[1]++;
2188 if( $delta[1] >= 12 ) {
2189 $delta[0]++;
2190 $delta[1] -= 12;
2192 # if unit is year, we only return full years
2193 return $unit eq 'month' ? $delta[0] * 12 + $delta[1] : $delta[0];
2196 sub _get_next_date_day {
2197 my ($subscription, $freqdata, $year, $month, $day) = @_;
2199 my @newissue; # ( yy, mm, dd )
2200 # We do not need $delta_days here, since it would be zero where used
2202 if( $freqdata->{issuesperunit} == 1 ) {
2203 # Add full days
2204 @newissue = Add_Delta_Days(
2205 $year, $month, $day, $freqdata->{"unitsperissue"} );
2206 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2207 # Add zero days
2208 @newissue = ( $year, $month, $day );
2209 $subscription->{countissuesperunit}++;
2210 } else {
2211 # We finished a cycle of issues within a unit.
2212 # No subtraction of zero needed, just add one day
2213 @newissue = Add_Delta_Days( $year, $month, $day, 1 );
2214 $subscription->{countissuesperunit} = 1;
2216 return @newissue;
2219 sub _get_next_date_week {
2220 my ($subscription, $freqdata, $year, $month, $day) = @_;
2222 my @newissue; # ( yy, mm, dd )
2223 my $delta_days = int( 7 / $freqdata->{issuesperunit} );
2225 if( $freqdata->{issuesperunit} == 1 ) {
2226 # Add full weeks (of 7 days)
2227 @newissue = Add_Delta_Days(
2228 $year, $month, $day, 7 * $freqdata->{"unitsperissue"} );
2229 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2230 # Add rounded number of days based on frequency.
2231 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2232 $subscription->{countissuesperunit}++;
2233 } else {
2234 # We finished a cycle of issues within a unit.
2235 # Subtract delta * (issues - 1), add 1 week
2236 @newissue = Add_Delta_Days( $year, $month, $day,
2237 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2238 @newissue = Add_Delta_Days( @newissue, 7 );
2239 $subscription->{countissuesperunit} = 1;
2241 return @newissue;
2244 sub _get_next_date_month {
2245 my ($subscription, $freqdata, $year, $month, $day) = @_;
2247 my @newissue; # ( yy, mm, dd )
2248 my $delta_days = int( 30 / $freqdata->{issuesperunit} );
2250 if( $freqdata->{issuesperunit} == 1 ) {
2251 # Add full months
2252 @newissue = Add_Delta_YM(
2253 $year, $month, $day, 0, $freqdata->{"unitsperissue"} );
2254 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2255 # Add rounded number of days based on frequency.
2256 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2257 $subscription->{countissuesperunit}++;
2258 } else {
2259 # We finished a cycle of issues within a unit.
2260 # Subtract delta * (issues - 1), add 1 month
2261 @newissue = Add_Delta_Days( $year, $month, $day,
2262 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2263 @newissue = Add_Delta_YM( @newissue, 0, 1 );
2264 $subscription->{countissuesperunit} = 1;
2266 return @newissue;
2269 sub _get_next_date_year {
2270 my ($subscription, $freqdata, $year, $month, $day) = @_;
2272 my @newissue; # ( yy, mm, dd )
2273 my $delta_days = int( 365 / $freqdata->{issuesperunit} );
2275 if( $freqdata->{issuesperunit} == 1 ) {
2276 # Add full years
2277 @newissue = Add_Delta_YM( $year, $month, $day, $freqdata->{"unitsperissue"}, 0 );
2278 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2279 # Add rounded number of days based on frequency.
2280 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2281 $subscription->{countissuesperunit}++;
2282 } else {
2283 # We finished a cycle of issues within a unit.
2284 # Subtract delta * (issues - 1), add 1 year
2285 @newissue = Add_Delta_Days( $year, $month, $day, -$delta_days * ($freqdata->{issuesperunit} - 1) );
2286 @newissue = Add_Delta_YM( @newissue, 1, 0 );
2287 $subscription->{countissuesperunit} = 1;
2289 return @newissue;
2292 =head2 GetNextDate
2294 $resultdate = GetNextDate($publisheddate,$subscription)
2296 this function it takes the publisheddate and will return the next issue's date
2297 and will skip dates if there exists an irregularity.
2298 $publisheddate has to be an ISO date
2299 $subscription is a hashref containing at least 'periodicity', 'firstacquidate', 'irregularity', and 'countissuesperunit'
2300 $updatecount is a boolean value which, when set to true, update the 'countissuesperunit' in database
2301 - eg if periodicity is monthly and $publisheddate is 2007-02-10 but if March and April is to be
2302 skipped then the returned date will be 2007-05-10
2304 return :
2305 $resultdate - then next date in the sequence (ISO date)
2307 Return undef if subscription is irregular
2309 =cut
2311 sub GetNextDate {
2312 my ( $subscription, $publisheddate, $updatecount ) = @_;
2314 return unless $subscription and $publisheddate;
2316 my $freqdata = GetSubscriptionFrequency($subscription->{'periodicity'});
2318 if ($freqdata->{'unit'}) {
2319 my ( $year, $month, $day ) = split /-/, $publisheddate;
2321 # Process an irregularity Hash
2322 # Suppose that irregularities are stored in a string with this structure
2323 # irreg1;irreg2;irreg3
2324 # where irregX is the number of issue which will not be received
2325 # (the first issue takes the number 1, the 2nd the number 2 and so on)
2326 my %irregularities;
2327 if ( $subscription->{irregularity} ) {
2328 my @irreg = split /;/, $subscription->{'irregularity'} ;
2329 foreach my $irregularity (@irreg) {
2330 $irregularities{$irregularity} = 1;
2334 # Get the 'fictive' next issue number
2335 # It is used to check if next issue is an irregular issue.
2336 my $issueno = GetFictiveIssueNumber($subscription, $publisheddate) + 1;
2338 # Then get the next date
2339 my $unit = lc $freqdata->{'unit'};
2340 if ($unit eq 'day') {
2341 while ($irregularities{$issueno}) {
2342 ($year, $month, $day) = _get_next_date_day($subscription,
2343 $freqdata, $year, $month, $day);
2344 $issueno++;
2346 ($year, $month, $day) = _get_next_date_day($subscription, $freqdata,
2347 $year, $month, $day);
2349 elsif ($unit eq 'week') {
2350 while ($irregularities{$issueno}) {
2351 ($year, $month, $day) = _get_next_date_week($subscription,
2352 $freqdata, $year, $month, $day);
2353 $issueno++;
2355 ($year, $month, $day) = _get_next_date_week($subscription,
2356 $freqdata, $year, $month, $day);
2358 elsif ($unit eq 'month') {
2359 while ($irregularities{$issueno}) {
2360 ($year, $month, $day) = _get_next_date_month($subscription,
2361 $freqdata, $year, $month, $day);
2362 $issueno++;
2364 ($year, $month, $day) = _get_next_date_month($subscription,
2365 $freqdata, $year, $month, $day);
2367 elsif ($unit eq 'year') {
2368 while ($irregularities{$issueno}) {
2369 ($year, $month, $day) = _get_next_date_year($subscription,
2370 $freqdata, $year, $month, $day);
2371 $issueno++;
2373 ($year, $month, $day) = _get_next_date_year($subscription,
2374 $freqdata, $year, $month, $day);
2377 if ($updatecount){
2378 my $dbh = C4::Context->dbh;
2379 my $query = qq{
2380 UPDATE subscription
2381 SET countissuesperunit = ?
2382 WHERE subscriptionid = ?
2384 my $sth = $dbh->prepare($query);
2385 $sth->execute($subscription->{'countissuesperunit'}, $subscription->{'subscriptionid'});
2388 return sprintf("%04d-%02d-%02d", $year, $month, $day);
2392 =head2 _numeration
2394 $string = &_numeration($value,$num_type,$locale);
2396 _numeration returns the string corresponding to $value in the num_type
2397 num_type can take :
2398 -dayname
2399 -dayabrv
2400 -monthname
2401 -monthabrv
2402 -season
2403 -seasonabrv
2405 =cut
2407 sub _numeration {
2408 my ($value, $num_type, $locale) = @_;
2409 $value ||= 0;
2410 $num_type //= '';
2411 $locale ||= 'en';
2412 my $string;
2413 if ( $num_type =~ /^dayname$/ or $num_type =~ /^dayabrv$/ ) {
2414 # 1970-11-01 was a Sunday
2415 $value = $value % 7;
2416 my $dt = DateTime->new(
2417 year => 1970,
2418 month => 11,
2419 day => $value + 1,
2420 locale => $locale,
2422 $string = $num_type =~ /^dayname$/
2423 ? $dt->strftime("%A")
2424 : $dt->strftime("%a");
2425 } elsif ( $num_type =~ /^monthname$/ or $num_type =~ /^monthabrv$/ ) {
2426 $value = $value % 12;
2427 my $dt = DateTime->new(
2428 year => 1970,
2429 month => $value + 1,
2430 locale => $locale,
2432 $string = $num_type =~ /^monthname$/
2433 ? $dt->strftime("%B")
2434 : $dt->strftime("%b");
2435 } elsif ( $num_type =~ /^season$/ ) {
2436 my @seasons= qw( Spring Summer Fall Winter );
2437 $value = $value % 4;
2438 $string = $seasons[$value];
2439 } elsif ( $num_type =~ /^seasonabrv$/ ) {
2440 my @seasonsabrv= qw( Spr Sum Fal Win );
2441 $value = $value % 4;
2442 $string = $seasonsabrv[$value];
2443 } else {
2444 $string = $value;
2447 return $string;
2450 =head2 CloseSubscription
2452 Close a subscription given a subscriptionid
2454 =cut
2456 sub CloseSubscription {
2457 my ( $subscriptionid ) = @_;
2458 return unless $subscriptionid;
2459 my $dbh = C4::Context->dbh;
2460 my $sth = $dbh->prepare( q{
2461 UPDATE subscription
2462 SET closed = 1
2463 WHERE subscriptionid = ?
2464 } );
2465 $sth->execute( $subscriptionid );
2467 # Set status = missing when status = stopped
2468 $sth = $dbh->prepare( q{
2469 UPDATE serial
2470 SET status = ?
2471 WHERE subscriptionid = ?
2472 AND status = ?
2473 } );
2474 $sth->execute( STOPPED, $subscriptionid, EXPECTED );
2477 =head2 ReopenSubscription
2479 Reopen a subscription given a subscriptionid
2481 =cut
2483 sub ReopenSubscription {
2484 my ( $subscriptionid ) = @_;
2485 return unless $subscriptionid;
2486 my $dbh = C4::Context->dbh;
2487 my $sth = $dbh->prepare( q{
2488 UPDATE subscription
2489 SET closed = 0
2490 WHERE subscriptionid = ?
2491 } );
2492 $sth->execute( $subscriptionid );
2494 # Set status = expected when status = stopped
2495 $sth = $dbh->prepare( q{
2496 UPDATE serial
2497 SET status = ?
2498 WHERE subscriptionid = ?
2499 AND status = ?
2500 } );
2501 $sth->execute( EXPECTED, $subscriptionid, STOPPED );
2504 =head2 subscriptionCurrentlyOnOrder
2506 $bool = subscriptionCurrentlyOnOrder( $subscriptionid );
2508 Return 1 if subscription is currently on order else 0.
2510 =cut
2512 sub subscriptionCurrentlyOnOrder {
2513 my ( $subscriptionid ) = @_;
2514 my $dbh = C4::Context->dbh;
2515 my $query = qq|
2516 SELECT COUNT(*) FROM aqorders
2517 WHERE subscriptionid = ?
2518 AND datereceived IS NULL
2519 AND datecancellationprinted IS NULL
2521 my $sth = $dbh->prepare( $query );
2522 $sth->execute($subscriptionid);
2523 return $sth->fetchrow_array;
2526 =head2 can_claim_subscription
2528 $can = can_claim_subscription( $subscriptionid[, $userid] );
2530 Return 1 if the subscription can be claimed by the current logged user (or a given $userid), else 0.
2532 =cut
2534 sub can_claim_subscription {
2535 my ( $subscription, $userid ) = @_;
2536 return _can_do_on_subscription( $subscription, $userid, 'claim_serials' );
2539 =head2 can_edit_subscription
2541 $can = can_edit_subscription( $subscriptionid[, $userid] );
2543 Return 1 if the subscription can be edited by the current logged user (or a given $userid), else 0.
2545 =cut
2547 sub can_edit_subscription {
2548 my ( $subscription, $userid ) = @_;
2549 return _can_do_on_subscription( $subscription, $userid, 'edit_subscription' );
2552 =head2 can_show_subscription
2554 $can = can_show_subscription( $subscriptionid[, $userid] );
2556 Return 1 if the subscription can be shown by the current logged user (or a given $userid), else 0.
2558 =cut
2560 sub can_show_subscription {
2561 my ( $subscription, $userid ) = @_;
2562 return _can_do_on_subscription( $subscription, $userid, '*' );
2565 sub _can_do_on_subscription {
2566 my ( $subscription, $userid, $permission ) = @_;
2567 return 0 unless C4::Context->userenv;
2568 my $flags = C4::Context->userenv->{flags};
2569 $userid ||= C4::Context->userenv->{'id'};
2571 if ( C4::Context->preference('IndependentBranches') ) {
2572 return 1
2573 if C4::Context->IsSuperLibrarian()
2575 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2576 or (
2577 C4::Auth::haspermission( $userid,
2578 { serials => $permission } )
2579 and ( not defined $subscription->{branchcode}
2580 or $subscription->{branchcode} eq ''
2581 or $subscription->{branchcode} eq
2582 C4::Context->userenv->{'branch'} )
2585 else {
2586 return 1
2587 if C4::Context->IsSuperLibrarian()
2589 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2590 or C4::Auth::haspermission(
2591 $userid, { serials => $permission }
2595 return 0;
2598 =head2 findSerialsByStatus
2600 @serials = findSerialsByStatus($status, $subscriptionid);
2602 Returns an array of serials matching a given status and subscription id.
2604 =cut
2606 sub findSerialsByStatus {
2607 my ( $status, $subscriptionid ) = @_;
2608 my $dbh = C4::Context->dbh;
2609 my $query = q| SELECT * from serial
2610 WHERE status = ?
2611 AND subscriptionid = ?
2613 my $serials = $dbh->selectall_arrayref( $query, { Slice => {} }, $status, $subscriptionid );
2614 return @$serials;
2618 __END__
2620 =head1 AUTHOR
2622 Koha Development Team <http://koha-community.org/>
2624 =cut