Bug 15395: Allow correct handling of plural translation
[koha.git] / C4 / Serials.pm
blob91498ee262e87787a7b104184fd83cd785ef45f4
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 return unless $subscription;
274 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
276 # Add additional fields to the subscription into a new key "additional_fields"
277 my $additional_field_values = Koha::AdditionalField->fetch_all_values({
278 tablename => 'subscription',
279 record_id => $subscriptionid,
281 $subscription->{additional_fields} = $additional_field_values->{$subscriptionid};
283 return $subscription;
286 =head2 GetFullSubscription
288 $array_ref = GetFullSubscription($subscriptionid)
289 this function reads the serial table.
291 =cut
293 sub GetFullSubscription {
294 my ($subscriptionid) = @_;
296 return unless ($subscriptionid);
298 my $dbh = C4::Context->dbh;
299 my $query = qq|
300 SELECT serial.serialid,
301 serial.serialseq,
302 serial.planneddate,
303 serial.publisheddate,
304 serial.publisheddatetext,
305 serial.status,
306 serial.notes as notes,
307 year(IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate)) as year,
308 aqbooksellers.name as aqbooksellername,
309 biblio.title as bibliotitle,
310 subscription.branchcode AS branchcode,
311 subscription.subscriptionid AS subscriptionid
312 FROM serial
313 LEFT JOIN subscription ON
314 (serial.subscriptionid=subscription.subscriptionid )
315 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
316 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
317 WHERE serial.subscriptionid = ?
318 ORDER BY year DESC,
319 IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate) DESC,
320 serial.subscriptionid
322 $debug and warn "GetFullSubscription query: $query";
323 my $sth = $dbh->prepare($query);
324 $sth->execute($subscriptionid);
325 my $subscriptions = $sth->fetchall_arrayref( {} );
326 my $cannotedit = not can_edit_subscription( $subscriptions->[0] ) if scalar @$subscriptions;
327 for my $subscription ( @$subscriptions ) {
328 $subscription->{cannotedit} = $cannotedit;
330 return $subscriptions;
333 =head2 PrepareSerialsData
335 $array_ref = PrepareSerialsData($serialinfomation)
336 where serialinformation is a hashref array
338 =cut
340 sub PrepareSerialsData {
341 my ($lines) = @_;
343 return unless ($lines);
345 my %tmpresults;
346 my $year;
347 my @res;
348 my $startdate;
349 my $aqbooksellername;
350 my $bibliotitle;
351 my @loopissues;
352 my $first;
353 my $previousnote = "";
355 foreach my $subs (@{$lines}) {
356 for my $datefield ( qw(publisheddate planneddate) ) {
357 # handle 0000-00-00 dates
358 if (defined $subs->{$datefield} and $subs->{$datefield} =~ m/^00/) {
359 $subs->{$datefield} = undef;
362 $subs->{ "status" . $subs->{'status'} } = 1;
363 if ( grep { $_ == $subs->{status} } ( EXPECTED, LATE, MISSING_STATUSES, CLAIMED ) ) {
364 $subs->{"checked"} = 1;
367 if ( $subs->{'year'} && $subs->{'year'} ne "" ) {
368 $year = $subs->{'year'};
369 } else {
370 $year = "manage";
372 if ( $tmpresults{$year} ) {
373 push @{ $tmpresults{$year}->{'serials'} }, $subs;
374 } else {
375 $tmpresults{$year} = {
376 'year' => $year,
377 'aqbooksellername' => $subs->{'aqbooksellername'},
378 'bibliotitle' => $subs->{'bibliotitle'},
379 'serials' => [$subs],
380 'first' => $first,
384 foreach my $key ( sort { $b cmp $a } keys %tmpresults ) {
385 push @res, $tmpresults{$key};
387 return \@res;
390 =head2 GetSubscriptionsFromBiblionumber
392 $array_ref = GetSubscriptionsFromBiblionumber($biblionumber)
393 this function get the subscription list. it reads the subscription table.
394 return :
395 reference to an array of subscriptions which have the biblionumber given on input arg.
396 each element of this array is a hashref containing
397 startdate, histstartdate,opacnote,missinglist,recievedlist,periodicity,status & enddate
399 =cut
401 sub GetSubscriptionsFromBiblionumber {
402 my ($biblionumber) = @_;
404 return unless ($biblionumber);
406 my $dbh = C4::Context->dbh;
407 my $query = qq(
408 SELECT subscription.*,
409 branches.branchname,
410 subscriptionhistory.*,
411 aqbooksellers.name AS aqbooksellername,
412 biblio.title AS bibliotitle
413 FROM subscription
414 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
415 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
416 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
417 LEFT JOIN branches ON branches.branchcode=subscription.branchcode
418 WHERE subscription.biblionumber = ?
420 my $sth = $dbh->prepare($query);
421 $sth->execute($biblionumber);
422 my @res;
423 while ( my $subs = $sth->fetchrow_hashref ) {
424 $subs->{startdate} = output_pref( { dt => dt_from_string( $subs->{startdate} ), dateonly => 1 } );
425 $subs->{histstartdate} = output_pref( { dt => dt_from_string( $subs->{histstartdate} ), dateonly => 1 } );
426 if ( defined $subs->{histenddate} ) {
427 $subs->{histenddate} = output_pref( { dt => dt_from_string( $subs->{histenddate} ), dateonly => 1 } );
428 } else {
429 $subs->{histenddate} = "";
431 $subs->{opacnote} =~ s/\n/\<br\/\>/g;
432 $subs->{missinglist} =~ s/\n/\<br\/\>/g;
433 $subs->{recievedlist} =~ s/\n/\<br\/\>/g;
434 $subs->{ "periodicity" . $subs->{periodicity} } = 1;
435 $subs->{ "numberpattern" . $subs->{numberpattern} } = 1;
436 $subs->{ "status" . $subs->{'status'} } = 1;
438 if (not defined $subs->{enddate} ) {
439 $subs->{enddate} = '';
440 } else {
441 $subs->{enddate} = output_pref( { dt => dt_from_string( $subs->{enddate}), dateonly => 1 } );
443 $subs->{'abouttoexpire'} = abouttoexpire( $subs->{'subscriptionid'} );
444 $subs->{'subscriptionexpired'} = HasSubscriptionExpired( $subs->{'subscriptionid'} );
445 $subs->{cannotedit} = not can_edit_subscription( $subs );
446 push @res, $subs;
448 return \@res;
451 =head2 GetFullSubscriptionsFromBiblionumber
453 $array_ref = GetFullSubscriptionsFromBiblionumber($biblionumber)
454 this function reads the serial table.
456 =cut
458 sub GetFullSubscriptionsFromBiblionumber {
459 my ($biblionumber) = @_;
460 my $dbh = C4::Context->dbh;
461 my $query = qq|
462 SELECT serial.serialid,
463 serial.serialseq,
464 serial.planneddate,
465 serial.publisheddate,
466 serial.publisheddatetext,
467 serial.status,
468 serial.notes as notes,
469 year(IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate)) as year,
470 biblio.title as bibliotitle,
471 subscription.branchcode AS branchcode,
472 subscription.subscriptionid AS subscriptionid
473 FROM serial
474 LEFT JOIN subscription ON
475 (serial.subscriptionid=subscription.subscriptionid)
476 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
477 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
478 WHERE subscription.biblionumber = ?
479 ORDER BY year DESC,
480 IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate) DESC,
481 serial.subscriptionid
483 my $sth = $dbh->prepare($query);
484 $sth->execute($biblionumber);
485 my $subscriptions = $sth->fetchall_arrayref( {} );
486 my $cannotedit = not can_edit_subscription( $subscriptions->[0] ) if scalar @$subscriptions;
487 for my $subscription ( @$subscriptions ) {
488 $subscription->{cannotedit} = $cannotedit;
490 return $subscriptions;
493 =head2 SearchSubscriptions
495 @results = SearchSubscriptions($args);
497 This function returns a list of hashrefs, one for each subscription
498 that meets the conditions specified by the $args hashref.
500 The valid search fields are:
502 biblionumber
503 title
504 issn
506 callnumber
507 location
508 publisher
509 bookseller
510 branch
511 expiration_date
512 closed
514 The expiration_date search field is special; it specifies the maximum
515 subscription expiration date.
517 =cut
519 sub SearchSubscriptions {
520 my ( $args ) = @_;
522 my $additional_fields = $args->{additional_fields} // [];
523 my $matching_record_ids_for_additional_fields = [];
524 if ( @$additional_fields ) {
525 $matching_record_ids_for_additional_fields = Koha::AdditionalField->get_matching_record_ids({
526 fields => $additional_fields,
527 tablename => 'subscription',
528 exact_match => 0,
530 return () unless @$matching_record_ids_for_additional_fields;
533 my $query = q|
534 SELECT
535 subscription.notes AS publicnotes,
536 subscriptionhistory.*,
537 subscription.*,
538 biblio.notes AS biblionotes,
539 biblio.title,
540 biblio.author,
541 biblio.biblionumber,
542 aqbooksellers.name AS vendorname,
543 biblioitems.issn
544 FROM subscription
545 LEFT JOIN subscriptionhistory USING(subscriptionid)
546 LEFT JOIN biblio ON biblio.biblionumber = subscription.biblionumber
547 LEFT JOIN biblioitems ON biblioitems.biblionumber = subscription.biblionumber
548 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
550 $query .= q| WHERE 1|;
551 my @where_strs;
552 my @where_args;
553 if( $args->{biblionumber} ) {
554 push @where_strs, "biblio.biblionumber = ?";
555 push @where_args, $args->{biblionumber};
558 if( $args->{title} ){
559 my @words = split / /, $args->{title};
560 my (@strs, @args);
561 foreach my $word (@words) {
562 push @strs, "biblio.title LIKE ?";
563 push @args, "%$word%";
565 if (@strs) {
566 push @where_strs, '(' . join (' AND ', @strs) . ')';
567 push @where_args, @args;
570 if( $args->{issn} ){
571 push @where_strs, "biblioitems.issn LIKE ?";
572 push @where_args, "%$args->{issn}%";
574 if( $args->{ean} ){
575 push @where_strs, "biblioitems.ean LIKE ?";
576 push @where_args, "%$args->{ean}%";
578 if ( $args->{callnumber} ) {
579 push @where_strs, "subscription.callnumber LIKE ?";
580 push @where_args, "%$args->{callnumber}%";
582 if( $args->{publisher} ){
583 push @where_strs, "biblioitems.publishercode LIKE ?";
584 push @where_args, "%$args->{publisher}%";
586 if( $args->{bookseller} ){
587 push @where_strs, "aqbooksellers.name LIKE ?";
588 push @where_args, "%$args->{bookseller}%";
590 if( $args->{branch} ){
591 push @where_strs, "subscription.branchcode = ?";
592 push @where_args, "$args->{branch}";
594 if ( $args->{location} ) {
595 push @where_strs, "subscription.location = ?";
596 push @where_args, "$args->{location}";
598 if ( $args->{expiration_date} ) {
599 push @where_strs, "subscription.enddate <= ?";
600 push @where_args, "$args->{expiration_date}";
602 if( defined $args->{closed} ){
603 push @where_strs, "subscription.closed = ?";
604 push @where_args, "$args->{closed}";
607 if(@where_strs){
608 $query .= ' AND ' . join(' AND ', @where_strs);
610 if ( @$additional_fields ) {
611 $query .= ' AND subscriptionid IN ('
612 . join( ', ', @$matching_record_ids_for_additional_fields )
613 . ')';
616 $query .= " ORDER BY " . $args->{orderby} if $args->{orderby};
618 my $dbh = C4::Context->dbh;
619 my $sth = $dbh->prepare($query);
620 $sth->execute(@where_args);
621 my $results = $sth->fetchall_arrayref( {} );
623 for my $subscription ( @$results ) {
624 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
625 $subscription->{cannotdisplay} = not can_show_subscription( $subscription );
627 my $additional_field_values = Koha::AdditionalField->fetch_all_values({
628 record_id => $subscription->{subscriptionid},
629 tablename => 'subscription'
631 $subscription->{additional_fields} = $additional_field_values->{$subscription->{subscriptionid}};
634 return @$results;
638 =head2 GetSerials
640 ($totalissues,@serials) = GetSerials($subscriptionid);
641 this function gets every serial not arrived for a given subscription
642 as well as the number of issues registered in the database (all types)
643 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
645 FIXME: We should return \@serials.
647 =cut
649 sub GetSerials {
650 my ( $subscriptionid, $count ) = @_;
652 return unless $subscriptionid;
654 my $dbh = C4::Context->dbh;
656 # status = 2 is "arrived"
657 my $counter = 0;
658 $count = 5 unless ($count);
659 my @serials;
660 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES, NOT_ISSUED ) );
661 my $query = "SELECT serialid,serialseq, status, publisheddate,
662 publisheddatetext, planneddate,notes, routingnotes
663 FROM serial
664 WHERE subscriptionid = ? AND status NOT IN ( $statuses )
665 ORDER BY IF(publisheddate<>'0000-00-00',publisheddate,planneddate) DESC";
666 my $sth = $dbh->prepare($query);
667 $sth->execute($subscriptionid);
669 while ( my $line = $sth->fetchrow_hashref ) {
670 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
671 for my $datefield ( qw( planneddate publisheddate) ) {
672 if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
673 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
674 } else {
675 $line->{$datefield} = q{};
678 push @serials, $line;
681 # OK, now add the last 5 issues arrives/missing
682 $query = "SELECT serialid,serialseq, status, planneddate, publisheddate,
683 publisheddatetext, notes, routingnotes
684 FROM serial
685 WHERE subscriptionid = ?
686 AND status IN ( $statuses )
687 ORDER BY IF(publisheddate<>'0000-00-00',publisheddate,planneddate) DESC
689 $sth = $dbh->prepare($query);
690 $sth->execute($subscriptionid);
691 while ( ( my $line = $sth->fetchrow_hashref ) && $counter < $count ) {
692 $counter++;
693 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
694 for my $datefield ( qw( planneddate publisheddate) ) {
695 if ($line->{$datefield} && $line->{$datefield}!~m/^00/) {
696 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
697 } else {
698 $line->{$datefield} = q{};
702 push @serials, $line;
705 $query = "SELECT count(*) FROM serial WHERE subscriptionid=?";
706 $sth = $dbh->prepare($query);
707 $sth->execute($subscriptionid);
708 my ($totalissues) = $sth->fetchrow;
709 return ( $totalissues, @serials );
712 =head2 GetSerials2
714 @serials = GetSerials2($subscriptionid,$statuses);
715 this function returns every serial waited for a given subscription
716 as well as the number of issues registered in the database (all types)
717 this number is used to see if a subscription can be deleted (=it must have only 1 issue)
719 $statuses is an arrayref of statuses and is mandatory.
721 =cut
723 sub GetSerials2 {
724 my ( $subscription, $statuses ) = @_;
726 return unless ($subscription and @$statuses);
728 my $dbh = C4::Context->dbh;
729 my $query = q|
730 SELECT serialid,serialseq, status, planneddate, publisheddate,
731 publisheddatetext, notes, routingnotes
732 FROM serial
733 WHERE subscriptionid=?
735 . q| AND status IN (| . join( ",", ('?') x @$statuses ) . q|)|
736 . q|
737 ORDER BY publisheddate,serialid DESC
739 $debug and warn "GetSerials2 query: $query";
740 my $sth = $dbh->prepare($query);
741 $sth->execute( $subscription, @$statuses );
742 my @serials;
744 while ( my $line = $sth->fetchrow_hashref ) {
745 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
746 # Format dates for display
747 for my $datefield ( qw( planneddate publisheddate ) ) {
748 if (!defined($line->{$datefield}) || $line->{$datefield} =~m/^00/) {
749 $line->{$datefield} = q{};
751 else {
752 $line->{$datefield} = output_pref( { dt => dt_from_string( $line->{$datefield} ), dateonly => 1 } );
755 push @serials, $line;
757 return @serials;
760 =head2 GetLatestSerials
762 \@serials = GetLatestSerials($subscriptionid,$limit)
763 get the $limit's latest serials arrived or missing for a given subscription
764 return :
765 a ref to an array which contains all of the latest serials stored into a hash.
767 =cut
769 sub GetLatestSerials {
770 my ( $subscriptionid, $limit ) = @_;
772 return unless ($subscriptionid and $limit);
774 my $dbh = C4::Context->dbh;
776 my $statuses = join( ',', ( ARRIVED, MISSING_STATUSES ) );
777 my $strsth = "SELECT serialid,serialseq, status, planneddate, publisheddate, notes
778 FROM serial
779 WHERE subscriptionid = ?
780 AND status IN ($statuses)
781 ORDER BY publisheddate DESC LIMIT 0,$limit
783 my $sth = $dbh->prepare($strsth);
784 $sth->execute($subscriptionid);
785 my @serials;
786 while ( my $line = $sth->fetchrow_hashref ) {
787 $line->{ "status" . $line->{status} } = 1; # fills a "statusX" value, used for template status select list
788 $line->{planneddate} = output_pref( { dt => dt_from_string( $line->{planneddate} ), dateonly => 1 } );
789 $line->{publisheddate} = output_pref( { dt => dt_from_string( $line->{publisheddate} ), dateonly => 1 } );
790 push @serials, $line;
793 return \@serials;
796 =head2 GetPreviousSerialid
798 $serialid = GetPreviousSerialid($subscriptionid, $nth)
799 get the $nth's previous serial for the given subscriptionid
800 return :
801 the serialid
803 =cut
805 sub GetPreviousSerialid {
806 my ( $subscriptionid, $nth ) = @_;
807 $nth ||= 1;
808 my $dbh = C4::Context->dbh;
809 my $return = undef;
811 # Status 2: Arrived
812 my $strsth = "SELECT serialid
813 FROM serial
814 WHERE subscriptionid = ?
815 AND status = 2
816 ORDER BY serialid DESC LIMIT $nth,1
818 my $sth = $dbh->prepare($strsth);
819 $sth->execute($subscriptionid);
820 my @serials;
821 my $line = $sth->fetchrow_hashref;
822 $return = $line->{'serialid'} if ($line);
824 return $return;
827 =head2 GetNextSeq
829 my (
830 $nextseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
831 $newinnerloop1, $newinnerloop2, $newinnerloop3
832 ) = GetNextSeq( $subscription, $pattern, $planneddate );
834 $subscription is a hashref containing all the attributes of the table
835 'subscription'.
836 $pattern is a hashref containing all the attributes of the table
837 'subscription_numberpatterns'.
838 $planneddate is a date string in iso format.
839 This function get the next issue for the subscription given on input arg
841 =cut
843 sub GetNextSeq {
844 my ($subscription, $pattern, $planneddate) = @_;
846 return unless ($subscription and $pattern);
848 my ( $newlastvalue1, $newlastvalue2, $newlastvalue3,
849 $newinnerloop1, $newinnerloop2, $newinnerloop3 );
850 my $count = 1;
852 if ($subscription->{'skip_serialseq'}) {
853 my @irreg = split /;/, $subscription->{'irregularity'};
854 if(@irreg > 0) {
855 my $irregularities = {};
856 $irregularities->{$_} = 1 foreach(@irreg);
857 my $issueno = GetFictiveIssueNumber($subscription, $planneddate) + 1;
858 while($irregularities->{$issueno}) {
859 $count++;
860 $issueno++;
865 my $numberingmethod = $pattern->{numberingmethod};
866 my $calculated = "";
867 if ($numberingmethod) {
868 $calculated = $numberingmethod;
869 my $locale = $subscription->{locale};
870 $newlastvalue1 = $subscription->{lastvalue1} || 0;
871 $newlastvalue2 = $subscription->{lastvalue2} || 0;
872 $newlastvalue3 = $subscription->{lastvalue3} || 0;
873 $newinnerloop1 = $subscription->{innerloop1} || 0;
874 $newinnerloop2 = $subscription->{innerloop2} || 0;
875 $newinnerloop3 = $subscription->{innerloop3} || 0;
876 my %calc;
877 foreach(qw/X Y Z/) {
878 $calc{$_} = 1 if ($numberingmethod =~ /\{$_\}/);
881 for(my $i = 0; $i < $count; $i++) {
882 if($calc{'X'}) {
883 # check if we have to increase the new value.
884 $newinnerloop1 += 1;
885 if ($newinnerloop1 >= $pattern->{every1}) {
886 $newinnerloop1 = 0;
887 $newlastvalue1 += $pattern->{add1};
889 # reset counter if needed.
890 $newlastvalue1 = $pattern->{setto1} if ($newlastvalue1 > $pattern->{whenmorethan1});
892 if($calc{'Y'}) {
893 # check if we have to increase the new value.
894 $newinnerloop2 += 1;
895 if ($newinnerloop2 >= $pattern->{every2}) {
896 $newinnerloop2 = 0;
897 $newlastvalue2 += $pattern->{add2};
899 # reset counter if needed.
900 $newlastvalue2 = $pattern->{setto2} if ($newlastvalue2 > $pattern->{whenmorethan2});
902 if($calc{'Z'}) {
903 # check if we have to increase the new value.
904 $newinnerloop3 += 1;
905 if ($newinnerloop3 >= $pattern->{every3}) {
906 $newinnerloop3 = 0;
907 $newlastvalue3 += $pattern->{add3};
909 # reset counter if needed.
910 $newlastvalue3 = $pattern->{setto3} if ($newlastvalue3 > $pattern->{whenmorethan3});
913 if($calc{'X'}) {
914 my $newlastvalue1string = _numeration( $newlastvalue1, $pattern->{numbering1}, $locale );
915 $calculated =~ s/\{X\}/$newlastvalue1string/g;
917 if($calc{'Y'}) {
918 my $newlastvalue2string = _numeration( $newlastvalue2, $pattern->{numbering2}, $locale );
919 $calculated =~ s/\{Y\}/$newlastvalue2string/g;
921 if($calc{'Z'}) {
922 my $newlastvalue3string = _numeration( $newlastvalue3, $pattern->{numbering3}, $locale );
923 $calculated =~ s/\{Z\}/$newlastvalue3string/g;
927 return ($calculated,
928 $newlastvalue1, $newlastvalue2, $newlastvalue3,
929 $newinnerloop1, $newinnerloop2, $newinnerloop3);
932 =head2 GetSeq
934 $calculated = GetSeq($subscription, $pattern)
935 $subscription is a hashref containing all the attributes of the table 'subscription'
936 $pattern is a hashref containing all the attributes of the table 'subscription_numberpatterns'
937 this function transforms {X},{Y},{Z} to 150,0,0 for example.
938 return:
939 the sequence in string format
941 =cut
943 sub GetSeq {
944 my ($subscription, $pattern) = @_;
946 return unless ($subscription and $pattern);
948 my $locale = $subscription->{locale};
950 my $calculated = $pattern->{numberingmethod};
952 my $newlastvalue1 = $subscription->{'lastvalue1'} || 0;
953 $newlastvalue1 = _numeration($newlastvalue1, $pattern->{numbering1}, $locale) if ($pattern->{numbering1}); # reset counter if needed.
954 $calculated =~ s/\{X\}/$newlastvalue1/g;
956 my $newlastvalue2 = $subscription->{'lastvalue2'} || 0;
957 $newlastvalue2 = _numeration($newlastvalue2, $pattern->{numbering2}, $locale) if ($pattern->{numbering2}); # reset counter if needed.
958 $calculated =~ s/\{Y\}/$newlastvalue2/g;
960 my $newlastvalue3 = $subscription->{'lastvalue3'} || 0;
961 $newlastvalue3 = _numeration($newlastvalue3, $pattern->{numbering3}, $locale) if ($pattern->{numbering3}); # reset counter if needed.
962 $calculated =~ s/\{Z\}/$newlastvalue3/g;
963 return $calculated;
966 =head2 GetExpirationDate
968 $enddate = GetExpirationDate($subscriptionid, [$startdate])
970 this function return the next expiration date for a subscription given on input args.
972 return
973 the enddate or undef
975 =cut
977 sub GetExpirationDate {
978 my ( $subscriptionid, $startdate ) = @_;
980 return unless ($subscriptionid);
982 my $dbh = C4::Context->dbh;
983 my $subscription = GetSubscription($subscriptionid);
984 my $enddate;
986 # we don't do the same test if the subscription is based on X numbers or on X weeks/months
987 $enddate = $startdate || $subscription->{startdate};
988 my @date = split( /-/, $enddate );
990 return if ( scalar(@date) != 3 || not check_date(@date) );
992 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
993 if ( $frequency and $frequency->{unit} ) {
995 # If Not Irregular
996 if ( my $length = $subscription->{numberlength} ) {
998 #calculate the date of the last issue.
999 for ( my $i = 1 ; $i <= $length ; $i++ ) {
1000 $enddate = GetNextDate( $subscription, $enddate );
1002 } elsif ( $subscription->{monthlength} ) {
1003 if ( $$subscription{startdate} ) {
1004 my @enddate = Add_Delta_YM( $date[0], $date[1], $date[2], 0, $subscription->{monthlength} );
1005 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1007 } elsif ( $subscription->{weeklength} ) {
1008 if ( $$subscription{startdate} ) {
1009 my @date = split( /-/, $subscription->{startdate} );
1010 my @enddate = Add_Delta_Days( $date[0], $date[1], $date[2], $subscription->{weeklength} * 7 );
1011 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1013 } else {
1014 $enddate = $subscription->{enddate};
1016 return $enddate;
1017 } else {
1018 return $subscription->{enddate};
1022 =head2 CountSubscriptionFromBiblionumber
1024 $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber)
1025 this returns a count of the subscriptions for a given biblionumber
1026 return :
1027 the number of subscriptions
1029 =cut
1031 sub CountSubscriptionFromBiblionumber {
1032 my ($biblionumber) = @_;
1034 return unless ($biblionumber);
1036 my $dbh = C4::Context->dbh;
1037 my $query = "SELECT count(*) FROM subscription WHERE biblionumber=?";
1038 my $sth = $dbh->prepare($query);
1039 $sth->execute($biblionumber);
1040 my $subscriptionsnumber = $sth->fetchrow;
1041 return $subscriptionsnumber;
1044 =head2 ModSubscriptionHistory
1046 ModSubscriptionHistory($subscriptionid,$histstartdate,$enddate,$recievedlist,$missinglist,$opacnote,$librariannote);
1048 this function modifies the history of a subscription. Put your new values on input arg.
1049 returns the number of rows affected
1051 =cut
1053 sub ModSubscriptionHistory {
1054 my ( $subscriptionid, $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote ) = @_;
1056 return unless ($subscriptionid);
1058 my $dbh = C4::Context->dbh;
1059 my $query = "UPDATE subscriptionhistory
1060 SET histstartdate=?,histenddate=?,recievedlist=?,missinglist=?,opacnote=?,librariannote=?
1061 WHERE subscriptionid=?
1063 my $sth = $dbh->prepare($query);
1064 $receivedlist =~ s/^; // if $receivedlist;
1065 $missinglist =~ s/^; // if $missinglist;
1066 $opacnote =~ s/^; // if $opacnote;
1067 $sth->execute( $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote, $subscriptionid );
1068 return $sth->rows;
1071 =head2 ModSerialStatus
1073 ModSerialStatus($serialid, $serialseq, $planneddate, $publisheddate,
1074 $publisheddatetext, $status, $notes);
1076 This function modify the serial status. Serial status is a number.(eg 2 is "arrived")
1077 Note : if we change from "waited" to something else,then we will have to create a new "waited" entry
1079 =cut
1081 sub ModSerialStatus {
1082 my ($serialid, $serialseq, $planneddate, $publisheddate, $publisheddatetext,
1083 $status, $notes) = @_;
1085 return unless ($serialid);
1087 #It is a usual serial
1088 # 1st, get previous status :
1089 my $dbh = C4::Context->dbh;
1090 my $query = "SELECT serial.subscriptionid,serial.status,subscription.periodicity
1091 FROM serial, subscription
1092 WHERE serial.subscriptionid=subscription.subscriptionid
1093 AND serialid=?";
1094 my $sth = $dbh->prepare($query);
1095 $sth->execute($serialid);
1096 my ( $subscriptionid, $oldstatus, $periodicity ) = $sth->fetchrow;
1097 my $frequency = GetSubscriptionFrequency($periodicity);
1099 # change status & update subscriptionhistory
1100 my $val;
1101 if ( $status == DELETED ) {
1102 DelIssue( { 'serialid' => $serialid, 'subscriptionid' => $subscriptionid, 'serialseq' => $serialseq } );
1103 } else {
1105 my $query = '
1106 UPDATE serial
1107 SET serialseq = ?, publisheddate = ?, publisheddatetext = ?,
1108 planneddate = ?, status = ?, notes = ?
1109 WHERE serialid = ?
1111 $sth = $dbh->prepare($query);
1112 $sth->execute( $serialseq, $publisheddate, $publisheddatetext,
1113 $planneddate, $status, $notes, $serialid );
1114 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1115 $sth = $dbh->prepare($query);
1116 $sth->execute($subscriptionid);
1117 my $val = $sth->fetchrow_hashref;
1118 unless ( $val->{manualhistory} ) {
1119 $query = "SELECT missinglist,recievedlist FROM subscriptionhistory WHERE subscriptionid=?";
1120 $sth = $dbh->prepare($query);
1121 $sth->execute($subscriptionid);
1122 my ( $missinglist, $recievedlist ) = $sth->fetchrow;
1124 if ( $status == ARRIVED || ($oldstatus == ARRIVED && $status != ARRIVED) ) {
1125 $recievedlist .= "; $serialseq"
1126 if ($recievedlist !~ /(^|;)\s*$serialseq(?=;|$)/);
1129 # in case serial has been previously marked as missing
1130 if (grep /$status/, (EXPECTED, ARRIVED, LATE, CLAIMED)) {
1131 $missinglist=~ s/(^|;)\s*$serialseq(?=;|$)//g;
1134 $missinglist .= "; $serialseq"
1135 if ( ( grep { $_ == $status } ( MISSING_STATUSES ) ) && ( $missinglist !~/(^|;)\s*$serialseq(?=;|$)/ ) );
1136 $missinglist .= "; not issued $serialseq"
1137 if ( $status == NOT_ISSUED && $missinglist !~ /(^|;)\s*$serialseq(?=;|$)/ );
1139 $query = "UPDATE subscriptionhistory SET recievedlist=?, missinglist=? WHERE subscriptionid=?";
1140 $sth = $dbh->prepare($query);
1141 $recievedlist =~ s/^; //;
1142 $missinglist =~ s/^; //;
1143 $sth->execute( $recievedlist, $missinglist, $subscriptionid );
1147 # create new expected entry if needed (ie : was "expected" and has changed)
1148 my $otherIssueExpected = scalar findSerialsByStatus(EXPECTED, $subscriptionid);
1149 if ( !$otherIssueExpected && $oldstatus == EXPECTED && $status != EXPECTED ) {
1150 my $subscription = GetSubscription($subscriptionid);
1151 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1153 # next issue number
1154 my (
1155 $newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
1156 $newinnerloop1, $newinnerloop2, $newinnerloop3
1158 = GetNextSeq( $subscription, $pattern, $publisheddate );
1160 # next date (calculated from actual date & frequency parameters)
1161 my $nextpublisheddate = GetNextDate($subscription, $publisheddate, 1);
1162 my $nextpubdate = $nextpublisheddate;
1163 $query = "UPDATE subscription SET lastvalue1=?, lastvalue2=?, lastvalue3=?, innerloop1=?, innerloop2=?, innerloop3=?
1164 WHERE subscriptionid = ?";
1165 $sth = $dbh->prepare($query);
1166 $sth->execute( $newlastvalue1, $newlastvalue2, $newlastvalue3, $newinnerloop1, $newinnerloop2, $newinnerloop3, $subscriptionid );
1168 NewIssue( $newserialseq, $subscriptionid, $subscription->{'biblionumber'}, 1, $nextpubdate, $nextpubdate );
1170 # check if an alert must be sent... (= a letter is defined & status became "arrived"
1171 if ( $subscription->{letter} && $status == ARRIVED && $oldstatus != ARRIVED ) {
1172 require C4::Letters;
1173 C4::Letters::SendAlerts( 'issue', $serialid, $subscription->{letter} );
1177 return;
1180 =head2 GetNextExpected
1182 $nextexpected = GetNextExpected($subscriptionid)
1184 Get the planneddate for the current expected issue of the subscription.
1186 returns a hashref:
1188 $nextexepected = {
1189 serialid => int
1190 planneddate => ISO date
1193 =cut
1195 sub GetNextExpected {
1196 my ($subscriptionid) = @_;
1198 my $dbh = C4::Context->dbh;
1199 my $query = qq{
1200 SELECT *
1201 FROM serial
1202 WHERE subscriptionid = ?
1203 AND status = ?
1204 LIMIT 1
1206 my $sth = $dbh->prepare($query);
1208 # Each subscription has only one 'expected' issue.
1209 $sth->execute( $subscriptionid, EXPECTED );
1210 my $nextissue = $sth->fetchrow_hashref;
1211 if ( !$nextissue ) {
1212 $query = qq{
1213 SELECT *
1214 FROM serial
1215 WHERE subscriptionid = ?
1216 ORDER BY publisheddate DESC
1217 LIMIT 1
1219 $sth = $dbh->prepare($query);
1220 $sth->execute($subscriptionid);
1221 $nextissue = $sth->fetchrow_hashref;
1223 foreach(qw/planneddate publisheddate/) {
1224 if ( !defined $nextissue->{$_} ) {
1225 # or should this default to 1st Jan ???
1226 $nextissue->{$_} = strftime( '%Y-%m-%d', localtime );
1228 $nextissue->{$_} = ($nextissue->{$_} ne '0000-00-00')
1229 ? $nextissue->{$_}
1230 : undef;
1233 return $nextissue;
1236 =head2 ModNextExpected
1238 ModNextExpected($subscriptionid,$date)
1240 Update the planneddate for the current expected issue of the subscription.
1241 This will modify all future prediction results.
1243 C<$date> is an ISO date.
1245 returns 0
1247 =cut
1249 sub ModNextExpected {
1250 my ( $subscriptionid, $date ) = @_;
1251 my $dbh = C4::Context->dbh;
1253 #FIXME: Would expect to only set planneddate, but we set both on new issue creation, so updating it here
1254 my $sth = $dbh->prepare('UPDATE serial SET planneddate=?,publisheddate=? WHERE subscriptionid=? AND status=?');
1256 # Each subscription has only one 'expected' issue.
1257 $sth->execute( $date, $date, $subscriptionid, EXPECTED );
1258 return 0;
1262 =head2 GetSubscriptionIrregularities
1264 =over 4
1266 =item @irreg = &GetSubscriptionIrregularities($subscriptionid);
1267 get the list of irregularities for a subscription
1269 =back
1271 =cut
1273 sub GetSubscriptionIrregularities {
1274 my $subscriptionid = shift;
1276 return unless $subscriptionid;
1278 my $dbh = C4::Context->dbh;
1279 my $query = qq{
1280 SELECT irregularity
1281 FROM subscription
1282 WHERE subscriptionid = ?
1284 my $sth = $dbh->prepare($query);
1285 $sth->execute($subscriptionid);
1287 my ($result) = $sth->fetchrow_array;
1288 my @irreg = split /;/, $result;
1290 return @irreg;
1293 =head2 ModSubscription
1295 this function modifies a subscription. Put all new values on input args.
1296 returns the number of rows affected
1298 =cut
1300 sub ModSubscription {
1301 my (
1302 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
1303 $periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
1304 $numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
1305 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
1306 $biblionumber, $callnumber, $notes, $letter, $manualhistory,
1307 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1308 $graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
1309 $itemtype, $previousitemtype
1310 ) = @_;
1312 my $dbh = C4::Context->dbh;
1313 my $query = "UPDATE subscription
1314 SET librarian=?, branchcode=?, aqbooksellerid=?, cost=?, aqbudgetid=?,
1315 startdate=?, periodicity=?, firstacquidate=?, irregularity=?,
1316 numberpattern=?, locale=?, numberlength=?, weeklength=?, monthlength=?,
1317 lastvalue1=?, innerloop1=?, lastvalue2=?, innerloop2=?,
1318 lastvalue3=?, innerloop3=?, status=?, biblionumber=?,
1319 callnumber=?, notes=?, letter=?, manualhistory=?,
1320 internalnotes=?, serialsadditems=?, staffdisplaycount=?,
1321 opacdisplaycount=?, graceperiod=?, location = ?, enddate=?,
1322 skip_serialseq=?, itemtype=?, previousitemtype=?
1323 WHERE subscriptionid = ?";
1325 my $sth = $dbh->prepare($query);
1326 $sth->execute(
1327 $auser, $branchcode, $aqbooksellerid, $cost,
1328 $aqbudgetid, $startdate, $periodicity, $firstacquidate,
1329 $irregularity, $numberpattern, $locale, $numberlength,
1330 $weeklength, $monthlength, $lastvalue1, $innerloop1,
1331 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3,
1332 $status, $biblionumber, $callnumber, $notes,
1333 $letter, ($manualhistory ? $manualhistory : 0),
1334 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1335 $graceperiod, $location, $enddate, $skip_serialseq,
1336 $itemtype, $previousitemtype,
1337 $subscriptionid
1339 my $rows = $sth->rows;
1341 logaction( "SERIAL", "MODIFY", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1342 return $rows;
1345 =head2 NewSubscription
1347 $subscriptionid = &NewSubscription($auser,branchcode,$aqbooksellerid,$cost,$aqbudgetid,$biblionumber,
1348 $startdate,$periodicity,$numberlength,$weeklength,$monthlength,
1349 $lastvalue1,$innerloop1,$lastvalue2,$innerloop2,$lastvalue3,$innerloop3,
1350 $status, $notes, $letter, $firstacquidate, $irregularity, $numberpattern,
1351 $locale, $callnumber, $manualhistory, $internalnotes, $serialsadditems,
1352 $staffdisplaycount, $opacdisplaycount, $graceperiod, $location, $enddate,
1353 $skip_serialseq, $itemtype, $previousitemtype);
1355 Create a new subscription with value given on input args.
1357 return :
1358 the id of this new subscription
1360 =cut
1362 sub NewSubscription {
1363 my (
1364 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1365 $startdate, $periodicity, $numberlength, $weeklength, $monthlength,
1366 $lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
1367 $innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
1368 $numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
1369 $serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
1370 $location, $enddate, $skip_serialseq, $itemtype, $previousitemtype
1371 ) = @_;
1372 my $dbh = C4::Context->dbh;
1374 #save subscription (insert into database)
1375 my $query = qq|
1376 INSERT INTO subscription
1377 (librarian, branchcode, aqbooksellerid, cost, aqbudgetid,
1378 biblionumber, startdate, periodicity, numberlength, weeklength,
1379 monthlength, lastvalue1, innerloop1, lastvalue2, innerloop2,
1380 lastvalue3, innerloop3, status, notes, letter, firstacquidate,
1381 irregularity, numberpattern, locale, callnumber,
1382 manualhistory, internalnotes, serialsadditems, staffdisplaycount,
1383 opacdisplaycount, graceperiod, location, enddate, skip_serialseq,
1384 itemtype, previousitemtype)
1385 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
1387 my $sth = $dbh->prepare($query);
1388 $sth->execute(
1389 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1390 $startdate, $periodicity, $numberlength, $weeklength,
1391 $monthlength, $lastvalue1, $innerloop1, $lastvalue2, $innerloop2,
1392 $lastvalue3, $innerloop3, $status, $notes, $letter,
1393 $firstacquidate, $irregularity, $numberpattern, $locale, $callnumber,
1394 $manualhistory, $internalnotes, $serialsadditems, $staffdisplaycount,
1395 $opacdisplaycount, $graceperiod, $location, $enddate, $skip_serialseq,
1396 $itemtype, $previousitemtype
1399 my $subscriptionid = $dbh->{'mysql_insertid'};
1400 unless ($enddate) {
1401 $enddate = GetExpirationDate( $subscriptionid, $startdate );
1402 $query = qq|
1403 UPDATE subscription
1404 SET enddate=?
1405 WHERE subscriptionid=?
1407 $sth = $dbh->prepare($query);
1408 $sth->execute( $enddate, $subscriptionid );
1411 # then create the 1st expected number
1412 $query = qq(
1413 INSERT INTO subscriptionhistory
1414 (biblionumber, subscriptionid, histstartdate, missinglist, recievedlist)
1415 VALUES (?,?,?, '', '')
1417 $sth = $dbh->prepare($query);
1418 $sth->execute( $biblionumber, $subscriptionid, $startdate);
1420 # reread subscription to get a hash (for calculation of the 1st issue number)
1421 my $subscription = GetSubscription($subscriptionid);
1422 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1424 # calculate issue number
1425 my $serialseq = GetSeq($subscription, $pattern) || q{};
1427 Koha::Serial->new(
1429 serialseq => $serialseq,
1430 serialseq_x => $subscription->{'lastvalue1'},
1431 serialseq_y => $subscription->{'lastvalue2'},
1432 serialseq_z => $subscription->{'lastvalue3'},
1433 subscriptionid => $subscriptionid,
1434 biblionumber => $biblionumber,
1435 status => EXPECTED,
1436 planneddate => $firstacquidate,
1437 publisheddate => $firstacquidate,
1439 )->store();
1441 logaction( "SERIAL", "ADD", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1443 #set serial flag on biblio if not already set.
1444 my $biblio = Koha::Biblios->find( $biblionumber );
1445 if ( $biblio and !$biblio->serial ) {
1446 my $record = GetMarcBiblio({ biblionumber => $biblionumber });
1447 my ( $tag, $subf ) = GetMarcFromKohaField( 'biblio.serial', $biblio->frameworkcode );
1448 if ($tag) {
1449 eval { $record->field($tag)->update( $subf => 1 ); };
1451 ModBiblio( $record, $biblionumber, $biblio->frameworkcode );
1453 return $subscriptionid;
1456 =head2 ReNewSubscription
1458 ReNewSubscription($subscriptionid,$user,$startdate,$numberlength,$weeklength,$monthlength,$note)
1460 this function renew a subscription with values given on input args.
1462 =cut
1464 sub ReNewSubscription {
1465 my ( $subscriptionid, $user, $startdate, $numberlength, $weeklength, $monthlength, $note ) = @_;
1466 my $dbh = C4::Context->dbh;
1467 my $subscription = GetSubscription($subscriptionid);
1468 my $query = qq|
1469 SELECT *
1470 FROM biblio
1471 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
1472 WHERE biblio.biblionumber=?
1474 my $sth = $dbh->prepare($query);
1475 $sth->execute( $subscription->{biblionumber} );
1476 my $biblio = $sth->fetchrow_hashref;
1478 if ( C4::Context->preference("RenewSerialAddsSuggestion") ) {
1479 require C4::Suggestions;
1480 C4::Suggestions::NewSuggestion(
1481 { 'suggestedby' => $user,
1482 'title' => $subscription->{bibliotitle},
1483 'author' => $biblio->{author},
1484 'publishercode' => $biblio->{publishercode},
1485 'note' => $biblio->{note},
1486 'biblionumber' => $subscription->{biblionumber}
1491 $numberlength ||= 0; # Should not we raise an exception instead?
1492 $weeklength ||= 0;
1494 # renew subscription
1495 $query = qq|
1496 UPDATE subscription
1497 SET startdate=?,numberlength=?,weeklength=?,monthlength=?,reneweddate=NOW()
1498 WHERE subscriptionid=?
1500 $sth = $dbh->prepare($query);
1501 $sth->execute( $startdate, $numberlength, $weeklength, $monthlength, $subscriptionid );
1502 my $enddate = GetExpirationDate($subscriptionid);
1503 $debug && warn "enddate :$enddate";
1504 $query = qq|
1505 UPDATE subscription
1506 SET enddate=?
1507 WHERE subscriptionid=?
1509 $sth = $dbh->prepare($query);
1510 $sth->execute( $enddate, $subscriptionid );
1512 logaction( "SERIAL", "RENEW", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1513 return;
1516 =head2 NewIssue
1518 NewIssue($serialseq,$subscriptionid,$biblionumber,$status, $planneddate, $publisheddate, $notes)
1520 Create a new issue stored on the database.
1521 Note : we have to update the recievedlist and missinglist on subscriptionhistory for this subscription.
1522 returns the serial id
1524 =cut
1526 sub NewIssue {
1527 my ( $serialseq, $subscriptionid, $biblionumber, $status, $planneddate,
1528 $publisheddate, $publisheddatetext, $notes ) = @_;
1529 ### FIXME biblionumber CAN be provided by subscriptionid. So Do we STILL NEED IT ?
1531 return unless ($subscriptionid);
1533 my $schema = Koha::Database->new()->schema();
1535 my $subscription = Koha::Subscriptions->find( $subscriptionid );
1537 my $serial = Koha::Serial->new(
1539 serialseq => $serialseq,
1540 serialseq_x => $subscription->lastvalue1(),
1541 serialseq_y => $subscription->lastvalue2(),
1542 serialseq_z => $subscription->lastvalue3(),
1543 subscriptionid => $subscriptionid,
1544 biblionumber => $biblionumber,
1545 status => $status,
1546 planneddate => $planneddate,
1547 publisheddate => $publisheddate,
1548 publisheddatetext => $publisheddatetext,
1549 notes => $notes,
1551 )->store();
1553 my $serialid = $serial->id();
1555 my $subscription_history = Koha::Subscription::Histories->find($subscriptionid);
1556 my $missinglist = $subscription_history->missinglist();
1557 my $recievedlist = $subscription_history->recievedlist();
1559 if ( $status == ARRIVED ) {
1560 ### TODO Add a feature that improves recognition and description.
1561 ### As such count (serialseq) i.e. : N18,2(N19),N20
1562 ### Would use substr and index But be careful to previous presence of ()
1563 $recievedlist .= "; $serialseq" unless ( index( $recievedlist, $serialseq ) > 0 );
1565 if ( grep { /^$status$/ } (MISSING_STATUSES) ) {
1566 $missinglist .= "; $serialseq" unless ( index( $missinglist, $serialseq ) > 0 );
1569 $recievedlist =~ s/^; //;
1570 $missinglist =~ s/^; //;
1572 $subscription_history->recievedlist($recievedlist);
1573 $subscription_history->missinglist($missinglist);
1574 $subscription_history->store();
1576 return $serialid;
1579 =head2 HasSubscriptionStrictlyExpired
1581 1 or 0 = HasSubscriptionStrictlyExpired($subscriptionid)
1583 the subscription has stricly expired when today > the end subscription date
1585 return :
1586 1 if true, 0 if false, -1 if the expiration date is not set.
1588 =cut
1590 sub HasSubscriptionStrictlyExpired {
1592 # Getting end of subscription date
1593 my ($subscriptionid) = @_;
1595 return unless ($subscriptionid);
1597 my $dbh = C4::Context->dbh;
1598 my $subscription = GetSubscription($subscriptionid);
1599 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1601 # If the expiration date is set
1602 if ( $expirationdate != 0 ) {
1603 my ( $endyear, $endmonth, $endday ) = split( '-', $expirationdate );
1605 # Getting today's date
1606 my ( $nowyear, $nowmonth, $nowday ) = Today();
1608 # if today's date > expiration date, then the subscription has stricly expired
1609 if ( Delta_Days( $nowyear, $nowmonth, $nowday, $endyear, $endmonth, $endday ) < 0 ) {
1610 return 1;
1611 } else {
1612 return 0;
1614 } else {
1616 # There are some cases where the expiration date is not set
1617 # As we can't determine if the subscription has expired on a date-basis,
1618 # we return -1;
1619 return -1;
1623 =head2 HasSubscriptionExpired
1625 $has_expired = HasSubscriptionExpired($subscriptionid)
1627 the subscription has expired when the next issue to arrive is out of subscription limit.
1629 return :
1630 0 if the subscription has not expired
1631 1 if the subscription has expired
1632 2 if has subscription does not have a valid expiration date set
1634 =cut
1636 sub HasSubscriptionExpired {
1637 my ($subscriptionid) = @_;
1639 return unless ($subscriptionid);
1641 my $dbh = C4::Context->dbh;
1642 my $subscription = GetSubscription($subscriptionid);
1643 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1644 if ( $frequency and $frequency->{unit} ) {
1645 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1646 if (!defined $expirationdate) {
1647 $expirationdate = q{};
1649 my $query = qq|
1650 SELECT max(planneddate)
1651 FROM serial
1652 WHERE subscriptionid=?
1654 my $sth = $dbh->prepare($query);
1655 $sth->execute($subscriptionid);
1656 my ($res) = $sth->fetchrow;
1657 if (!$res || $res=~m/^0000/) {
1658 return 0;
1660 my @res = split( /-/, $res );
1661 my @endofsubscriptiondate = split( /-/, $expirationdate );
1662 return 2 if ( scalar(@res) != 3 || scalar(@endofsubscriptiondate) != 3 || not check_date(@res) || not check_date(@endofsubscriptiondate) );
1663 return 1
1664 if ( ( @endofsubscriptiondate && Delta_Days( $res[0], $res[1], $res[2], $endofsubscriptiondate[0], $endofsubscriptiondate[1], $endofsubscriptiondate[2] ) <= 0 )
1665 || ( !$res ) );
1666 return 0;
1667 } else {
1668 # Irregular
1669 if ( $subscription->{'numberlength'} ) {
1670 my $countreceived = countissuesfrom( $subscriptionid, $subscription->{'startdate'} );
1671 return 1 if ( $countreceived > $subscription->{'numberlength'} );
1672 return 0;
1673 } else {
1674 return 0;
1677 return 0; # Notice that you'll never get here.
1680 =head2 DelSubscription
1682 DelSubscription($subscriptionid)
1683 this function deletes subscription which has $subscriptionid as id.
1685 =cut
1687 sub DelSubscription {
1688 my ($subscriptionid) = @_;
1689 my $dbh = C4::Context->dbh;
1690 $dbh->do("DELETE FROM subscription WHERE subscriptionid=?", undef, $subscriptionid);
1691 $dbh->do("DELETE FROM subscriptionhistory WHERE subscriptionid=?", undef, $subscriptionid);
1692 $dbh->do("DELETE FROM serial WHERE subscriptionid=?", undef, $subscriptionid);
1694 my $afs = Koha::AdditionalField->all({tablename => 'subscription'});
1695 foreach my $af (@$afs) {
1696 $af->delete_values({record_id => $subscriptionid});
1699 logaction( "SERIAL", "DELETE", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1702 =head2 DelIssue
1704 DelIssue($serialseq,$subscriptionid)
1705 this function deletes an issue which has $serialseq and $subscriptionid given on input arg.
1707 returns the number of rows affected
1709 =cut
1711 sub DelIssue {
1712 my ($dataissue) = @_;
1713 my $dbh = C4::Context->dbh;
1714 ### TODO Add itemdeletion. Would need to get itemnumbers. Should be in a pref ?
1716 my $query = qq|
1717 DELETE FROM serial
1718 WHERE serialid= ?
1719 AND subscriptionid= ?
1721 my $mainsth = $dbh->prepare($query);
1722 $mainsth->execute( $dataissue->{'serialid'}, $dataissue->{'subscriptionid'} );
1724 #Delete element from subscription history
1725 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1726 my $sth = $dbh->prepare($query);
1727 $sth->execute( $dataissue->{'subscriptionid'} );
1728 my $val = $sth->fetchrow_hashref;
1729 unless ( $val->{manualhistory} ) {
1730 my $query = qq|
1731 SELECT * FROM subscriptionhistory
1732 WHERE subscriptionid= ?
1734 my $sth = $dbh->prepare($query);
1735 $sth->execute( $dataissue->{'subscriptionid'} );
1736 my $data = $sth->fetchrow_hashref;
1737 my $serialseq = $dataissue->{'serialseq'};
1738 $data->{'missinglist'} =~ s/\b$serialseq\b//;
1739 $data->{'recievedlist'} =~ s/\b$serialseq\b//;
1740 my $strsth = "UPDATE subscriptionhistory SET " . join( ",", map { join( "=", $_, $dbh->quote( $data->{$_} ) ) } keys %$data ) . " WHERE subscriptionid=?";
1741 $sth = $dbh->prepare($strsth);
1742 $sth->execute( $dataissue->{'subscriptionid'} );
1745 return $mainsth->rows;
1748 =head2 GetLateOrMissingIssues
1750 @issuelist = GetLateMissingIssues($supplierid,$serialid)
1752 this function selects missing issues on database - where serial.status = MISSING* or serial.status = LATE or planneddate<now
1754 return :
1755 the issuelist as an array of hash refs. Each element of this array contains
1756 name,title,planneddate,serialseq,serial.subscriptionid from tables : subscription, serial & biblio
1758 =cut
1760 sub GetLateOrMissingIssues {
1761 my ( $supplierid, $serialid, $order ) = @_;
1763 return unless ( $supplierid or $serialid );
1765 my $dbh = C4::Context->dbh;
1767 my $sth;
1768 my $byserial = '';
1769 if ($serialid) {
1770 $byserial = "and serialid = " . $serialid;
1772 if ($order) {
1773 $order .= ", title";
1774 } else {
1775 $order = "title";
1777 my $missing_statuses_string = join ',', (MISSING_STATUSES);
1778 if ($supplierid) {
1779 $sth = $dbh->prepare(
1780 "SELECT
1781 serialid, aqbooksellerid, name,
1782 biblio.title, biblioitems.issn, planneddate, serialseq,
1783 serial.status, serial.subscriptionid, claimdate, claims_count,
1784 subscription.branchcode
1785 FROM serial
1786 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1787 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1788 LEFT JOIN biblioitems ON subscription.biblionumber=biblioitems.biblionumber
1789 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1790 WHERE subscription.subscriptionid = serial.subscriptionid
1791 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1792 AND subscription.aqbooksellerid=$supplierid
1793 $byserial
1794 ORDER BY $order"
1796 } else {
1797 $sth = $dbh->prepare(
1798 "SELECT
1799 serialid, aqbooksellerid, name,
1800 biblio.title, planneddate, serialseq,
1801 serial.status, serial.subscriptionid, claimdate, claims_count,
1802 subscription.branchcode
1803 FROM serial
1804 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1805 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1806 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1807 WHERE subscription.subscriptionid = serial.subscriptionid
1808 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1809 $byserial
1810 ORDER BY $order"
1813 $sth->execute( EXPECTED, LATE, CLAIMED );
1814 my @issuelist;
1815 while ( my $line = $sth->fetchrow_hashref ) {
1817 if ($line->{planneddate} && $line->{planneddate} !~/^0+\-/) {
1818 $line->{planneddateISO} = $line->{planneddate};
1819 $line->{planneddate} = output_pref( { dt => dt_from_string( $line->{"planneddate"} ), dateonly => 1 } );
1821 if ($line->{claimdate} && $line->{claimdate} !~/^0+\-/) {
1822 $line->{claimdateISO} = $line->{claimdate};
1823 $line->{claimdate} = output_pref( { dt => dt_from_string( $line->{"claimdate"} ), dateonly => 1 } );
1825 $line->{"status".$line->{status}} = 1;
1827 my $additional_field_values = Koha::AdditionalField->fetch_all_values({
1828 record_id => $line->{subscriptionid},
1829 tablename => 'subscription'
1831 %$line = ( %$line, additional_fields => $additional_field_values->{$line->{subscriptionid}} );
1833 push @issuelist, $line;
1835 return @issuelist;
1838 =head2 updateClaim
1840 &updateClaim($serialid)
1842 this function updates the time when a claim is issued for late/missing items
1844 called from claims.pl file
1846 =cut
1848 sub updateClaim {
1849 my ($serialids) = @_;
1850 return unless $serialids;
1851 unless ( ref $serialids ) {
1852 $serialids = [ $serialids ];
1854 my $dbh = C4::Context->dbh;
1855 return $dbh->do(q|
1856 UPDATE serial
1857 SET claimdate = NOW(),
1858 claims_count = claims_count + 1,
1859 status = ?
1860 WHERE serialid in (| . join( q|,|, (q|?|) x @$serialids ) . q|)|,
1861 {}, CLAIMED, @$serialids );
1864 =head2 check_routing
1866 $result = &check_routing($subscriptionid)
1868 this function checks to see if a serial has a routing list and returns the count of routingid
1869 used to show either an 'add' or 'edit' link
1871 =cut
1873 sub check_routing {
1874 my ($subscriptionid) = @_;
1876 return unless ($subscriptionid);
1878 my $dbh = C4::Context->dbh;
1879 my $sth = $dbh->prepare(
1880 "SELECT count(routingid) routingids FROM subscription LEFT JOIN subscriptionroutinglist
1881 ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
1882 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC
1885 $sth->execute($subscriptionid);
1886 my $line = $sth->fetchrow_hashref;
1887 my $result = $line->{'routingids'};
1888 return $result;
1891 =head2 addroutingmember
1893 addroutingmember($borrowernumber,$subscriptionid)
1895 this function takes a borrowernumber and subscriptionid and adds the member to the
1896 routing list for that serial subscription and gives them a rank on the list
1897 of either 1 or highest current rank + 1
1899 =cut
1901 sub addroutingmember {
1902 my ( $borrowernumber, $subscriptionid ) = @_;
1904 return unless ($borrowernumber and $subscriptionid);
1906 my $rank;
1907 my $dbh = C4::Context->dbh;
1908 my $sth = $dbh->prepare( "SELECT max(ranking) rank FROM subscriptionroutinglist WHERE subscriptionid = ?" );
1909 $sth->execute($subscriptionid);
1910 while ( my $line = $sth->fetchrow_hashref ) {
1911 if ( $line->{'rank'} > 0 ) {
1912 $rank = $line->{'rank'} + 1;
1913 } else {
1914 $rank = 1;
1917 $sth = $dbh->prepare( "INSERT INTO subscriptionroutinglist (subscriptionid,borrowernumber,ranking) VALUES (?,?,?)" );
1918 $sth->execute( $subscriptionid, $borrowernumber, $rank );
1921 =head2 reorder_members
1923 reorder_members($subscriptionid,$routingid,$rank)
1925 this function is used to reorder the routing list
1927 it takes the routingid of the member one wants to re-rank and the rank it is to move to
1928 - it gets all members on list puts their routingid's into an array
1929 - removes the one in the array that is $routingid
1930 - then reinjects $routingid at point indicated by $rank
1931 - then update the database with the routingids in the new order
1933 =cut
1935 sub reorder_members {
1936 my ( $subscriptionid, $routingid, $rank ) = @_;
1937 my $dbh = C4::Context->dbh;
1938 my $sth = $dbh->prepare( "SELECT * FROM subscriptionroutinglist WHERE subscriptionid = ? ORDER BY ranking ASC" );
1939 $sth->execute($subscriptionid);
1940 my @result;
1941 while ( my $line = $sth->fetchrow_hashref ) {
1942 push( @result, $line->{'routingid'} );
1945 # To find the matching index
1946 my $i;
1947 my $key = -1; # to allow for 0 being a valid response
1948 for ( $i = 0 ; $i < @result ; $i++ ) {
1949 if ( $routingid == $result[$i] ) {
1950 $key = $i; # save the index
1951 last;
1955 # if index exists in array then move it to new position
1956 if ( $key > -1 && $rank > 0 ) {
1957 my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
1958 my $moving_item = splice( @result, $key, 1 );
1959 splice( @result, $new_rank, 0, $moving_item );
1961 for ( my $j = 0 ; $j < @result ; $j++ ) {
1962 my $sth = $dbh->prepare( "UPDATE subscriptionroutinglist SET ranking = '" . ( $j + 1 ) . "' WHERE routingid = '" . $result[$j] . "'" );
1963 $sth->execute;
1965 return;
1968 =head2 delroutingmember
1970 delroutingmember($routingid,$subscriptionid)
1972 this function either deletes one member from routing list if $routingid exists otherwise
1973 deletes all members from the routing list
1975 =cut
1977 sub delroutingmember {
1979 # if $routingid exists then deletes that row otherwise deletes all with $subscriptionid
1980 my ( $routingid, $subscriptionid ) = @_;
1981 my $dbh = C4::Context->dbh;
1982 if ($routingid) {
1983 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE routingid = ?");
1984 $sth->execute($routingid);
1985 reorder_members( $subscriptionid, $routingid );
1986 } else {
1987 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE subscriptionid = ?");
1988 $sth->execute($subscriptionid);
1990 return;
1993 =head2 getroutinglist
1995 @routinglist = getroutinglist($subscriptionid)
1997 this gets the info from the subscriptionroutinglist for $subscriptionid
1999 return :
2000 the routinglist as an array. Each element of the array contains a hash_ref containing
2001 routingid - a unique id, borrowernumber, ranking, and biblionumber of subscription
2003 =cut
2005 sub getroutinglist {
2006 my ($subscriptionid) = @_;
2007 my $dbh = C4::Context->dbh;
2008 my $sth = $dbh->prepare(
2009 'SELECT routingid, borrowernumber, ranking, biblionumber
2010 FROM subscription
2011 JOIN subscriptionroutinglist ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
2012 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC'
2014 $sth->execute($subscriptionid);
2015 my $routinglist = $sth->fetchall_arrayref({});
2016 return @{$routinglist};
2019 =head2 countissuesfrom
2021 $result = countissuesfrom($subscriptionid,$startdate)
2023 Returns a count of serial rows matching the given subsctiptionid
2024 with published date greater than startdate
2026 =cut
2028 sub countissuesfrom {
2029 my ( $subscriptionid, $startdate ) = @_;
2030 my $dbh = C4::Context->dbh;
2031 my $query = qq|
2032 SELECT count(*)
2033 FROM serial
2034 WHERE subscriptionid=?
2035 AND serial.publisheddate>?
2037 my $sth = $dbh->prepare($query);
2038 $sth->execute( $subscriptionid, $startdate );
2039 my ($countreceived) = $sth->fetchrow;
2040 return $countreceived;
2043 =head2 CountIssues
2045 $result = CountIssues($subscriptionid)
2047 Returns a count of serial rows matching the given subsctiptionid
2049 =cut
2051 sub CountIssues {
2052 my ($subscriptionid) = @_;
2053 my $dbh = C4::Context->dbh;
2054 my $query = qq|
2055 SELECT count(*)
2056 FROM serial
2057 WHERE subscriptionid=?
2059 my $sth = $dbh->prepare($query);
2060 $sth->execute($subscriptionid);
2061 my ($countreceived) = $sth->fetchrow;
2062 return $countreceived;
2065 =head2 HasItems
2067 $result = HasItems($subscriptionid)
2069 returns a count of items from serial matching the subscriptionid
2071 =cut
2073 sub HasItems {
2074 my ($subscriptionid) = @_;
2075 my $dbh = C4::Context->dbh;
2076 my $query = q|
2077 SELECT COUNT(serialitems.itemnumber)
2078 FROM serial
2079 LEFT JOIN serialitems USING(serialid)
2080 WHERE subscriptionid=? AND serialitems.serialid IS NOT NULL
2082 my $sth=$dbh->prepare($query);
2083 $sth->execute($subscriptionid);
2084 my ($countitems)=$sth->fetchrow_array();
2085 return $countitems;
2088 =head2 abouttoexpire
2090 $result = abouttoexpire($subscriptionid)
2092 this function alerts you to the penultimate issue for a serial subscription
2094 returns 1 - if this is the penultimate issue
2095 returns 0 - if not
2097 =cut
2099 sub abouttoexpire {
2100 my ($subscriptionid) = @_;
2101 my $dbh = C4::Context->dbh;
2102 my $subscription = GetSubscription($subscriptionid);
2103 my $per = $subscription->{'periodicity'};
2104 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($per);
2105 if ($frequency and $frequency->{unit}){
2107 my $expirationdate = GetExpirationDate($subscriptionid);
2109 my ($res) = $dbh->selectrow_array('select max(planneddate) from serial where subscriptionid = ?', undef, $subscriptionid);
2110 my $nextdate = GetNextDate($subscription, $res);
2112 # only compare dates if both dates exist.
2113 if ($nextdate and $expirationdate) {
2114 if(Date::Calc::Delta_Days(
2115 split( /-/, $nextdate ),
2116 split( /-/, $expirationdate )
2117 ) <= 0) {
2118 return 1;
2122 } elsif ($subscription->{numberlength}>0) {
2123 return (countissuesfrom($subscriptionid,$subscription->{'startdate'}) >=$subscription->{numberlength}-1);
2126 return 0;
2129 =head2 GetFictiveIssueNumber
2131 $issueno = GetFictiveIssueNumber($subscription, $publishedate);
2133 Get the position of the issue published at $publisheddate, considering the
2134 first issue (at firstacquidate) is at position 1, the next is at position 2, etc...
2135 This issuenumber doesn't take into account irregularities, so, for instance, if the 3rd
2136 issue is declared as 'irregular' (will be skipped at receipt), the next issue number
2137 will be 4, not 3. It's why it is called 'fictive'. It is NOT a serial seq, and is not
2138 depending on how many rows are in serial table.
2139 The issue number calculation is based on subscription frequency, first acquisition
2140 date, and $publisheddate.
2142 Returns undef when called for irregular frequencies.
2144 The routine is used to skip irregularities when calculating the next issue
2145 date (in GetNextDate) or the next issue number (in GetNextSeq).
2147 =cut
2149 sub GetFictiveIssueNumber {
2150 my ($subscription, $publisheddate) = @_;
2152 my $frequency = GetSubscriptionFrequency($subscription->{'periodicity'});
2153 my $unit = $frequency->{unit} ? lc $frequency->{'unit'} : undef;
2154 return if !$unit;
2155 my $issueno;
2157 my ( $year, $month, $day ) = split /-/, $publisheddate;
2158 my ( $fa_year, $fa_month, $fa_day ) = split /-/, $subscription->{'firstacquidate'};
2159 my $delta = _delta_units( [$fa_year, $fa_month, $fa_day], [$year, $month, $day], $unit );
2161 if( $frequency->{'unitsperissue'} == 1 ) {
2162 $issueno = $delta * $frequency->{'issuesperunit'} + $subscription->{'countissuesperunit'};
2163 } else { # issuesperunit == 1
2164 $issueno = 1 + int( $delta / $frequency->{'unitsperissue'} );
2166 return $issueno;
2169 sub _delta_units {
2170 my ( $date1, $date2, $unit ) = @_;
2171 # date1 and date2 are array refs in the form [ yy, mm, dd ]
2173 if( $unit eq 'day' ) {
2174 return Delta_Days( @$date1, @$date2 );
2175 } elsif( $unit eq 'week' ) {
2176 return int( Delta_Days( @$date1, @$date2 ) / 7 );
2179 # In case of months or years, this is a wrapper around N_Delta_YMD.
2180 # Note that N_Delta_YMD returns 29 days between e.g. 22-2-72 and 22-3-72
2181 # while we expect 1 month.
2182 my @delta = N_Delta_YMD( @$date1, @$date2 );
2183 if( $delta[2] > 27 ) {
2184 # Check if we could add a month
2185 my @jump = Add_Delta_YM( @$date1, $delta[0], 1 + $delta[1] );
2186 if( Delta_Days( @jump, @$date2 ) >= 0 ) {
2187 $delta[1]++;
2190 if( $delta[1] >= 12 ) {
2191 $delta[0]++;
2192 $delta[1] -= 12;
2194 # if unit is year, we only return full years
2195 return $unit eq 'month' ? $delta[0] * 12 + $delta[1] : $delta[0];
2198 sub _get_next_date_day {
2199 my ($subscription, $freqdata, $year, $month, $day) = @_;
2201 my @newissue; # ( yy, mm, dd )
2202 # We do not need $delta_days here, since it would be zero where used
2204 if( $freqdata->{issuesperunit} == 1 ) {
2205 # Add full days
2206 @newissue = Add_Delta_Days(
2207 $year, $month, $day, $freqdata->{"unitsperissue"} );
2208 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2209 # Add zero days
2210 @newissue = ( $year, $month, $day );
2211 $subscription->{countissuesperunit}++;
2212 } else {
2213 # We finished a cycle of issues within a unit.
2214 # No subtraction of zero needed, just add one day
2215 @newissue = Add_Delta_Days( $year, $month, $day, 1 );
2216 $subscription->{countissuesperunit} = 1;
2218 return @newissue;
2221 sub _get_next_date_week {
2222 my ($subscription, $freqdata, $year, $month, $day) = @_;
2224 my @newissue; # ( yy, mm, dd )
2225 my $delta_days = int( 7 / $freqdata->{issuesperunit} );
2227 if( $freqdata->{issuesperunit} == 1 ) {
2228 # Add full weeks (of 7 days)
2229 @newissue = Add_Delta_Days(
2230 $year, $month, $day, 7 * $freqdata->{"unitsperissue"} );
2231 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2232 # Add rounded number of days based on frequency.
2233 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2234 $subscription->{countissuesperunit}++;
2235 } else {
2236 # We finished a cycle of issues within a unit.
2237 # Subtract delta * (issues - 1), add 1 week
2238 @newissue = Add_Delta_Days( $year, $month, $day,
2239 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2240 @newissue = Add_Delta_Days( @newissue, 7 );
2241 $subscription->{countissuesperunit} = 1;
2243 return @newissue;
2246 sub _get_next_date_month {
2247 my ($subscription, $freqdata, $year, $month, $day) = @_;
2249 my @newissue; # ( yy, mm, dd )
2250 my $delta_days = int( 30 / $freqdata->{issuesperunit} );
2252 if( $freqdata->{issuesperunit} == 1 ) {
2253 # Add full months
2254 @newissue = Add_Delta_YM(
2255 $year, $month, $day, 0, $freqdata->{"unitsperissue"} );
2256 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2257 # Add rounded number of days based on frequency.
2258 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2259 $subscription->{countissuesperunit}++;
2260 } else {
2261 # We finished a cycle of issues within a unit.
2262 # Subtract delta * (issues - 1), add 1 month
2263 @newissue = Add_Delta_Days( $year, $month, $day,
2264 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2265 @newissue = Add_Delta_YM( @newissue, 0, 1 );
2266 $subscription->{countissuesperunit} = 1;
2268 return @newissue;
2271 sub _get_next_date_year {
2272 my ($subscription, $freqdata, $year, $month, $day) = @_;
2274 my @newissue; # ( yy, mm, dd )
2275 my $delta_days = int( 365 / $freqdata->{issuesperunit} );
2277 if( $freqdata->{issuesperunit} == 1 ) {
2278 # Add full years
2279 @newissue = Add_Delta_YM( $year, $month, $day, $freqdata->{"unitsperissue"}, 0 );
2280 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2281 # Add rounded number of days based on frequency.
2282 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2283 $subscription->{countissuesperunit}++;
2284 } else {
2285 # We finished a cycle of issues within a unit.
2286 # Subtract delta * (issues - 1), add 1 year
2287 @newissue = Add_Delta_Days( $year, $month, $day, -$delta_days * ($freqdata->{issuesperunit} - 1) );
2288 @newissue = Add_Delta_YM( @newissue, 1, 0 );
2289 $subscription->{countissuesperunit} = 1;
2291 return @newissue;
2294 =head2 GetNextDate
2296 $resultdate = GetNextDate($publisheddate,$subscription)
2298 this function it takes the publisheddate and will return the next issue's date
2299 and will skip dates if there exists an irregularity.
2300 $publisheddate has to be an ISO date
2301 $subscription is a hashref containing at least 'periodicity', 'firstacquidate', 'irregularity', and 'countissuesperunit'
2302 $updatecount is a boolean value which, when set to true, update the 'countissuesperunit' in database
2303 - eg if periodicity is monthly and $publisheddate is 2007-02-10 but if March and April is to be
2304 skipped then the returned date will be 2007-05-10
2306 return :
2307 $resultdate - then next date in the sequence (ISO date)
2309 Return undef if subscription is irregular
2311 =cut
2313 sub GetNextDate {
2314 my ( $subscription, $publisheddate, $updatecount ) = @_;
2316 return unless $subscription and $publisheddate;
2318 my $freqdata = GetSubscriptionFrequency($subscription->{'periodicity'});
2320 if ($freqdata->{'unit'}) {
2321 my ( $year, $month, $day ) = split /-/, $publisheddate;
2323 # Process an irregularity Hash
2324 # Suppose that irregularities are stored in a string with this structure
2325 # irreg1;irreg2;irreg3
2326 # where irregX is the number of issue which will not be received
2327 # (the first issue takes the number 1, the 2nd the number 2 and so on)
2328 my %irregularities;
2329 if ( $subscription->{irregularity} ) {
2330 my @irreg = split /;/, $subscription->{'irregularity'} ;
2331 foreach my $irregularity (@irreg) {
2332 $irregularities{$irregularity} = 1;
2336 # Get the 'fictive' next issue number
2337 # It is used to check if next issue is an irregular issue.
2338 my $issueno = GetFictiveIssueNumber($subscription, $publisheddate) + 1;
2340 # Then get the next date
2341 my $unit = lc $freqdata->{'unit'};
2342 if ($unit eq 'day') {
2343 while ($irregularities{$issueno}) {
2344 ($year, $month, $day) = _get_next_date_day($subscription,
2345 $freqdata, $year, $month, $day);
2346 $issueno++;
2348 ($year, $month, $day) = _get_next_date_day($subscription, $freqdata,
2349 $year, $month, $day);
2351 elsif ($unit eq 'week') {
2352 while ($irregularities{$issueno}) {
2353 ($year, $month, $day) = _get_next_date_week($subscription,
2354 $freqdata, $year, $month, $day);
2355 $issueno++;
2357 ($year, $month, $day) = _get_next_date_week($subscription,
2358 $freqdata, $year, $month, $day);
2360 elsif ($unit eq 'month') {
2361 while ($irregularities{$issueno}) {
2362 ($year, $month, $day) = _get_next_date_month($subscription,
2363 $freqdata, $year, $month, $day);
2364 $issueno++;
2366 ($year, $month, $day) = _get_next_date_month($subscription,
2367 $freqdata, $year, $month, $day);
2369 elsif ($unit eq 'year') {
2370 while ($irregularities{$issueno}) {
2371 ($year, $month, $day) = _get_next_date_year($subscription,
2372 $freqdata, $year, $month, $day);
2373 $issueno++;
2375 ($year, $month, $day) = _get_next_date_year($subscription,
2376 $freqdata, $year, $month, $day);
2379 if ($updatecount){
2380 my $dbh = C4::Context->dbh;
2381 my $query = qq{
2382 UPDATE subscription
2383 SET countissuesperunit = ?
2384 WHERE subscriptionid = ?
2386 my $sth = $dbh->prepare($query);
2387 $sth->execute($subscription->{'countissuesperunit'}, $subscription->{'subscriptionid'});
2390 return sprintf("%04d-%02d-%02d", $year, $month, $day);
2394 =head2 _numeration
2396 $string = &_numeration($value,$num_type,$locale);
2398 _numeration returns the string corresponding to $value in the num_type
2399 num_type can take :
2400 -dayname
2401 -dayabrv
2402 -monthname
2403 -monthabrv
2404 -season
2405 -seasonabrv
2407 =cut
2409 sub _numeration {
2410 my ($value, $num_type, $locale) = @_;
2411 $value ||= 0;
2412 $num_type //= '';
2413 $locale ||= 'en';
2414 my $string;
2415 if ( $num_type =~ /^dayname$/ or $num_type =~ /^dayabrv$/ ) {
2416 # 1970-11-01 was a Sunday
2417 $value = $value % 7;
2418 my $dt = DateTime->new(
2419 year => 1970,
2420 month => 11,
2421 day => $value + 1,
2422 locale => $locale,
2424 $string = $num_type =~ /^dayname$/
2425 ? $dt->strftime("%A")
2426 : $dt->strftime("%a");
2427 } elsif ( $num_type =~ /^monthname$/ or $num_type =~ /^monthabrv$/ ) {
2428 $value = $value % 12;
2429 my $dt = DateTime->new(
2430 year => 1970,
2431 month => $value + 1,
2432 locale => $locale,
2434 $string = $num_type =~ /^monthname$/
2435 ? $dt->strftime("%B")
2436 : $dt->strftime("%b");
2437 } elsif ( $num_type =~ /^season$/ ) {
2438 my @seasons= qw( Spring Summer Fall Winter );
2439 $value = $value % 4;
2440 $string = $seasons[$value];
2441 } elsif ( $num_type =~ /^seasonabrv$/ ) {
2442 my @seasonsabrv= qw( Spr Sum Fal Win );
2443 $value = $value % 4;
2444 $string = $seasonsabrv[$value];
2445 } else {
2446 $string = $value;
2449 return $string;
2452 =head2 CloseSubscription
2454 Close a subscription given a subscriptionid
2456 =cut
2458 sub CloseSubscription {
2459 my ( $subscriptionid ) = @_;
2460 return unless $subscriptionid;
2461 my $dbh = C4::Context->dbh;
2462 my $sth = $dbh->prepare( q{
2463 UPDATE subscription
2464 SET closed = 1
2465 WHERE subscriptionid = ?
2466 } );
2467 $sth->execute( $subscriptionid );
2469 # Set status = missing when status = stopped
2470 $sth = $dbh->prepare( q{
2471 UPDATE serial
2472 SET status = ?
2473 WHERE subscriptionid = ?
2474 AND status = ?
2475 } );
2476 $sth->execute( STOPPED, $subscriptionid, EXPECTED );
2479 =head2 ReopenSubscription
2481 Reopen a subscription given a subscriptionid
2483 =cut
2485 sub ReopenSubscription {
2486 my ( $subscriptionid ) = @_;
2487 return unless $subscriptionid;
2488 my $dbh = C4::Context->dbh;
2489 my $sth = $dbh->prepare( q{
2490 UPDATE subscription
2491 SET closed = 0
2492 WHERE subscriptionid = ?
2493 } );
2494 $sth->execute( $subscriptionid );
2496 # Set status = expected when status = stopped
2497 $sth = $dbh->prepare( q{
2498 UPDATE serial
2499 SET status = ?
2500 WHERE subscriptionid = ?
2501 AND status = ?
2502 } );
2503 $sth->execute( EXPECTED, $subscriptionid, STOPPED );
2506 =head2 subscriptionCurrentlyOnOrder
2508 $bool = subscriptionCurrentlyOnOrder( $subscriptionid );
2510 Return 1 if subscription is currently on order else 0.
2512 =cut
2514 sub subscriptionCurrentlyOnOrder {
2515 my ( $subscriptionid ) = @_;
2516 my $dbh = C4::Context->dbh;
2517 my $query = qq|
2518 SELECT COUNT(*) FROM aqorders
2519 WHERE subscriptionid = ?
2520 AND datereceived IS NULL
2521 AND datecancellationprinted IS NULL
2523 my $sth = $dbh->prepare( $query );
2524 $sth->execute($subscriptionid);
2525 return $sth->fetchrow_array;
2528 =head2 can_claim_subscription
2530 $can = can_claim_subscription( $subscriptionid[, $userid] );
2532 Return 1 if the subscription can be claimed by the current logged user (or a given $userid), else 0.
2534 =cut
2536 sub can_claim_subscription {
2537 my ( $subscription, $userid ) = @_;
2538 return _can_do_on_subscription( $subscription, $userid, 'claim_serials' );
2541 =head2 can_edit_subscription
2543 $can = can_edit_subscription( $subscriptionid[, $userid] );
2545 Return 1 if the subscription can be edited by the current logged user (or a given $userid), else 0.
2547 =cut
2549 sub can_edit_subscription {
2550 my ( $subscription, $userid ) = @_;
2551 return _can_do_on_subscription( $subscription, $userid, 'edit_subscription' );
2554 =head2 can_show_subscription
2556 $can = can_show_subscription( $subscriptionid[, $userid] );
2558 Return 1 if the subscription can be shown by the current logged user (or a given $userid), else 0.
2560 =cut
2562 sub can_show_subscription {
2563 my ( $subscription, $userid ) = @_;
2564 return _can_do_on_subscription( $subscription, $userid, '*' );
2567 sub _can_do_on_subscription {
2568 my ( $subscription, $userid, $permission ) = @_;
2569 return 0 unless C4::Context->userenv;
2570 my $flags = C4::Context->userenv->{flags};
2571 $userid ||= C4::Context->userenv->{'id'};
2573 if ( C4::Context->preference('IndependentBranches') ) {
2574 return 1
2575 if C4::Context->IsSuperLibrarian()
2577 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2578 or (
2579 C4::Auth::haspermission( $userid,
2580 { serials => $permission } )
2581 and ( not defined $subscription->{branchcode}
2582 or $subscription->{branchcode} eq ''
2583 or $subscription->{branchcode} eq
2584 C4::Context->userenv->{'branch'} )
2587 else {
2588 return 1
2589 if C4::Context->IsSuperLibrarian()
2591 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2592 or C4::Auth::haspermission(
2593 $userid, { serials => $permission }
2597 return 0;
2600 =head2 findSerialsByStatus
2602 @serials = findSerialsByStatus($status, $subscriptionid);
2604 Returns an array of serials matching a given status and subscription id.
2606 =cut
2608 sub findSerialsByStatus {
2609 my ( $status, $subscriptionid ) = @_;
2610 my $dbh = C4::Context->dbh;
2611 my $query = q| SELECT * from serial
2612 WHERE status = ?
2613 AND subscriptionid = ?
2615 my $serials = $dbh->selectall_arrayref( $query, { Slice => {} }, $status, $subscriptionid );
2616 return @$serials;
2620 __END__
2622 =head1 AUTHOR
2624 Koha Development Team <http://koha-community.org/>
2626 =cut