Bug 20434: Update UNIMARC framework - auth (GENRE/FORM)
[koha.git] / C4 / Serials.pm
blob1841397dc4ffe647d2af78f8deddb2a599d84f04
1 package C4::Serials;
3 # Copyright 2000-2002 Katipo Communications
4 # Parts Copyright 2010 Biblibre
6 # This file is part of Koha.
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
21 use Modern::Perl;
23 use C4::Auth qw(haspermission);
24 use C4::Context;
25 use DateTime;
26 use Date::Calc qw(:all);
27 use POSIX qw(strftime);
28 use C4::Biblio;
29 use C4::Log; # logaction
30 use C4::Debug;
31 use C4::Serials::Frequency;
32 use C4::Serials::Numberpattern;
33 use Koha::AdditionalFieldValues;
34 use Koha::DateUtils;
35 use Koha::Serial;
36 use Koha::Subscriptions;
37 use Koha::Subscription::Histories;
38 use Koha::SharedContent;
40 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
42 # Define statuses
43 use constant {
44 EXPECTED => 1,
45 ARRIVED => 2,
46 LATE => 3,
47 MISSING => 4,
48 MISSING_NEVER_RECIEVED => 41,
49 MISSING_SOLD_OUT => 42,
50 MISSING_DAMAGED => 43,
51 MISSING_LOST => 44,
52 NOT_ISSUED => 5,
53 DELETED => 6,
54 CLAIMED => 7,
55 STOPPED => 8,
58 use constant MISSING_STATUSES => (
59 MISSING, MISSING_NEVER_RECIEVED,
60 MISSING_SOLD_OUT, MISSING_DAMAGED,
61 MISSING_LOST
64 BEGIN {
65 require Exporter;
66 @ISA = qw(Exporter);
67 @EXPORT = qw(
68 &NewSubscription &ModSubscription &DelSubscription
69 &GetSubscription &CountSubscriptionFromBiblionumber &GetSubscriptionsFromBiblionumber
70 &SearchSubscriptions
71 &GetFullSubscriptionsFromBiblionumber &GetFullSubscription &ModSubscriptionHistory
72 &HasSubscriptionStrictlyExpired &HasSubscriptionExpired &GetExpirationDate &abouttoexpire
73 &GetSubscriptionHistoryFromSubscriptionId
75 &GetNextSeq &GetSeq &NewIssue &GetSerials
76 &GetLatestSerials &ModSerialStatus &GetNextDate &GetSerials2
77 &ReNewSubscription &GetLateOrMissingIssues
78 &GetSerialInformation &AddItem2Serial
79 &PrepareSerialsData &GetNextExpected &ModNextExpected
80 &GetPreviousSerialid
82 &GetSuppliersWithLateIssues
83 &getroutinglist &delroutingmember &addroutingmember
84 &reorder_members
85 &check_routing &updateClaim
86 &CountIssues
87 HasItems
88 &subscriptionCurrentlyOnOrder
93 =head1 NAME
95 C4::Serials - Serials Module Functions
97 =head1 SYNOPSIS
99 use C4::Serials;
101 =head1 DESCRIPTION
103 Functions for handling subscriptions, claims routing etc.
106 =head1 SUBROUTINES
108 =head2 GetSuppliersWithLateIssues
110 $supplierlist = GetSuppliersWithLateIssues()
112 this function get all suppliers with late issues.
114 return :
115 an array_ref of suppliers each entry is a hash_ref containing id and name
116 the array is in name order
118 =cut
120 sub GetSuppliersWithLateIssues {
121 my $dbh = C4::Context->dbh;
122 my $statuses = join(',', ( LATE, MISSING_STATUSES, CLAIMED ) );
123 my $query = qq|
124 SELECT DISTINCT id, name
125 FROM subscription
126 LEFT JOIN serial ON serial.subscriptionid=subscription.subscriptionid
127 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
128 WHERE id > 0
129 AND (
130 (planneddate < now() AND serial.status=1)
131 OR serial.STATUS IN ( $statuses )
133 AND subscription.closed = 0
134 ORDER BY name|;
135 return $dbh->selectall_arrayref($query, { Slice => {} });
138 =head2 GetSubscriptionHistoryFromSubscriptionId
140 $history = GetSubscriptionHistoryFromSubscriptionId($subscriptionid);
142 This function returns the subscription history as a hashref
144 =cut
146 sub GetSubscriptionHistoryFromSubscriptionId {
147 my ($subscriptionid) = @_;
149 return unless $subscriptionid;
151 my $dbh = C4::Context->dbh;
152 my $query = qq|
153 SELECT *
154 FROM subscriptionhistory
155 WHERE subscriptionid = ?
157 my $sth = $dbh->prepare($query);
158 $sth->execute($subscriptionid);
159 my $results = $sth->fetchrow_hashref;
160 $sth->finish;
162 return $results;
165 =head2 GetSerialInformation
167 $data = GetSerialInformation($serialid);
168 returns a hash_ref containing :
169 items : items marcrecord (can be an array)
170 serial table field
171 subscription table field
172 + information about subscription expiration
174 =cut
176 sub GetSerialInformation {
177 my ($serialid) = @_;
178 my $dbh = C4::Context->dbh;
179 my $query = qq|
180 SELECT serial.*, serial.notes as sernotes, serial.status as serstatus,subscription.*,subscription.subscriptionid as subsid
181 FROM serial LEFT JOIN subscription ON subscription.subscriptionid=serial.subscriptionid
182 WHERE serialid = ?
184 my $rq = $dbh->prepare($query);
185 $rq->execute($serialid);
186 my $data = $rq->fetchrow_hashref;
188 # create item information if we have serialsadditems for this subscription
189 if ( $data->{'serialsadditems'} ) {
190 my $queryitem = $dbh->prepare("SELECT itemnumber from serialitems where serialid=?");
191 $queryitem->execute($serialid);
192 my $itemnumbers = $queryitem->fetchall_arrayref( [0] );
193 require C4::Items;
194 if ( scalar(@$itemnumbers) > 0 ) {
195 foreach my $itemnum (@$itemnumbers) {
197 #It is ASSUMED that GetMarcItem ALWAYS WORK...
198 #Maybe GetMarcItem should return values on failure
199 $debug and warn "itemnumber :$itemnum->[0], bibnum :" . $data->{'biblionumber'};
200 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, $itemnum->[0], $data );
201 $itemprocessed->{'itemnumber'} = $itemnum->[0];
202 $itemprocessed->{'itemid'} = $itemnum->[0];
203 $itemprocessed->{'serialid'} = $serialid;
204 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
205 push @{ $data->{'items'} }, $itemprocessed;
207 } else {
208 my $itemprocessed = C4::Items::PrepareItemrecordDisplay( $data->{'biblionumber'}, '', $data );
209 $itemprocessed->{'itemid'} = "N$serialid";
210 $itemprocessed->{'serialid'} = $serialid;
211 $itemprocessed->{'biblionumber'} = $data->{'biblionumber'};
212 $itemprocessed->{'countitems'} = 0;
213 push @{ $data->{'items'} }, $itemprocessed;
216 $data->{ "status" . $data->{'serstatus'} } = 1;
217 $data->{'subscriptionexpired'} = HasSubscriptionExpired( $data->{'subscriptionid'} ) && $data->{'status'} == 1;
218 $data->{'abouttoexpire'} = abouttoexpire( $data->{'subscriptionid'} );
219 $data->{cannotedit} = not can_edit_subscription( $data );
220 return $data;
223 =head2 AddItem2Serial
225 $rows = AddItem2Serial($serialid,$itemnumber);
226 Adds an itemnumber to Serial record
227 returns the number of rows affected
229 =cut
231 sub AddItem2Serial {
232 my ( $serialid, $itemnumber ) = @_;
234 return unless ($serialid and $itemnumber);
236 my $dbh = C4::Context->dbh;
237 my $rq = $dbh->prepare("INSERT INTO `serialitems` SET serialid=? , itemnumber=?");
238 $rq->execute( $serialid, $itemnumber );
239 return $rq->rows;
242 =head2 GetSubscription
244 $subs = GetSubscription($subscriptionid)
245 this function returns the subscription which has $subscriptionid as id.
246 return :
247 a hashref. This hash contains
248 subscription, subscriptionhistory, aqbooksellers.name, biblio.title
250 =cut
252 sub GetSubscription {
253 my ($subscriptionid) = @_;
254 my $dbh = C4::Context->dbh;
255 my $query = qq(
256 SELECT subscription.*,
257 subscriptionhistory.*,
258 aqbooksellers.name AS aqbooksellername,
259 biblio.title AS bibliotitle,
260 subscription.biblionumber as bibnum
261 FROM subscription
262 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
263 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
264 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
265 WHERE subscription.subscriptionid = ?
268 $debug and warn "query : $query\nsubsid :$subscriptionid";
269 my $sth = $dbh->prepare($query);
270 $sth->execute($subscriptionid);
271 my $subscription = $sth->fetchrow_hashref;
273 return unless $subscription;
275 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
277 if ( my $mana_id = $subscription->{mana_id} ) {
278 my $mana_subscription = Koha::SharedContent::get_entity_by_id(
279 'subscription', $mana_id, {usecomments => 1});
280 $subscription->{comments} = $mana_subscription->{data}->{comments};
283 return $subscription;
286 =head2 GetFullSubscription
288 $array_ref = GetFullSubscription($subscriptionid)
289 this function reads the serial table.
291 =cut
293 sub GetFullSubscription {
294 my ($subscriptionid) = @_;
296 return unless ($subscriptionid);
298 my $dbh = C4::Context->dbh;
299 my $query = qq|
300 SELECT serial.serialid,
301 serial.serialseq,
302 serial.planneddate,
303 serial.publisheddate,
304 serial.publisheddatetext,
305 serial.status,
306 serial.notes as notes,
307 year(IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate)) as year,
308 aqbooksellers.name as aqbooksellername,
309 biblio.title as bibliotitle,
310 subscription.branchcode AS branchcode,
311 subscription.subscriptionid AS subscriptionid
312 FROM serial
313 LEFT JOIN subscription ON
314 (serial.subscriptionid=subscription.subscriptionid )
315 LEFT JOIN aqbooksellers on subscription.aqbooksellerid=aqbooksellers.id
316 LEFT JOIN biblio on biblio.biblionumber=subscription.biblionumber
317 WHERE serial.subscriptionid = ?
318 ORDER BY year DESC,
319 IF(serial.publisheddate="00-00-0000",serial.planneddate,serial.publisheddate) DESC,
320 serial.subscriptionid
322 $debug and warn "GetFullSubscription query: $query";
323 my $sth = $dbh->prepare($query);
324 $sth->execute($subscriptionid);
325 my $subscriptions = $sth->fetchall_arrayref( {} );
326 my $cannotedit = not can_edit_subscription( $subscriptions->[0] ) if scalar @$subscriptions;
327 for my $subscription ( @$subscriptions ) {
328 $subscription->{cannotedit} = $cannotedit;
330 return $subscriptions;
333 =head2 PrepareSerialsData
335 $array_ref = PrepareSerialsData($serialinfomation)
336 where serialinformation is a hashref array
338 =cut
340 sub PrepareSerialsData {
341 my ($lines) = @_;
343 return unless ($lines);
345 my %tmpresults;
346 my $year;
347 my @res;
348 my $startdate;
349 my $aqbooksellername;
350 my $bibliotitle;
351 my @loopissues;
352 my $first;
353 my $previousnote = "";
355 foreach my $subs (@{$lines}) {
356 for my $datefield ( qw(publisheddate planneddate) ) {
357 # handle 0000-00-00 dates
358 if (defined $subs->{$datefield} and $subs->{$datefield} =~ m/^00/) {
359 $subs->{$datefield} = undef;
362 $subs->{ "status" . $subs->{'status'} } = 1;
363 if ( grep { $_ == $subs->{status} } ( EXPECTED, LATE, MISSING_STATUSES, CLAIMED ) ) {
364 $subs->{"checked"} = 1;
367 if ( $subs->{'year'} && $subs->{'year'} ne "" ) {
368 $year = $subs->{'year'};
369 } else {
370 $year = "manage";
372 if ( $tmpresults{$year} ) {
373 push @{ $tmpresults{$year}->{'serials'} }, $subs;
374 } else {
375 $tmpresults{$year} = {
376 'year' => $year,
377 'aqbooksellername' => $subs->{'aqbooksellername'},
378 'bibliotitle' => $subs->{'bibliotitle'},
379 'serials' => [$subs],
380 'first' => $first,
384 foreach my $key ( sort { $b cmp $a } keys %tmpresults ) {
385 push @res, $tmpresults{$key};
387 return \@res;
390 =head2 GetSubscriptionsFromBiblionumber
392 $array_ref = GetSubscriptionsFromBiblionumber($biblionumber)
393 this function get the subscription list. it reads the subscription table.
394 return :
395 reference to an array of subscriptions which have the biblionumber given on input arg.
396 each element of this array is a hashref containing
397 startdate, histstartdate,opacnote,missinglist,recievedlist,periodicity,status & enddate
399 =cut
401 sub GetSubscriptionsFromBiblionumber {
402 my ($biblionumber) = @_;
404 return unless ($biblionumber);
406 my $dbh = C4::Context->dbh;
407 my $query = qq(
408 SELECT subscription.*,
409 branches.branchname,
410 subscriptionhistory.*,
411 aqbooksellers.name AS aqbooksellername,
412 biblio.title AS bibliotitle
413 FROM subscription
414 LEFT JOIN subscriptionhistory ON subscription.subscriptionid=subscriptionhistory.subscriptionid
415 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid=aqbooksellers.id
416 LEFT JOIN biblio ON biblio.biblionumber=subscription.biblionumber
417 LEFT JOIN branches ON branches.branchcode=subscription.branchcode
418 WHERE subscription.biblionumber = ?
420 my $sth = $dbh->prepare($query);
421 $sth->execute($biblionumber);
422 my @res;
423 while ( my $subs = $sth->fetchrow_hashref ) {
424 $subs->{startdate} = output_pref( { dt => dt_from_string( $subs->{startdate} ), dateonly => 1 } );
425 $subs->{histstartdate} = output_pref( { dt => dt_from_string( $subs->{histstartdate} ), dateonly => 1 } );
426 if ( defined $subs->{histenddate} ) {
427 $subs->{histenddate} = output_pref( { dt => dt_from_string( $subs->{histenddate} ), dateonly => 1 } );
428 } else {
429 $subs->{histenddate} = "";
431 $subs->{opacnote} //= "";
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 my @subscriptions = Koha::Subscriptions->filter_by_additional_fields($additional_fields);
525 return () unless @subscriptions;
527 $matching_record_ids_for_additional_fields = [ map {
528 $_->subscriptionid
529 } @subscriptions ];
532 my $query = q|
533 SELECT
534 subscription.notes AS publicnotes,
535 subscriptionhistory.*,
536 subscription.*,
537 biblio.notes AS biblionotes,
538 biblio.title,
539 biblio.author,
540 biblio.biblionumber,
541 aqbooksellers.name AS vendorname,
542 biblioitems.issn
543 FROM subscription
544 LEFT JOIN subscriptionhistory USING(subscriptionid)
545 LEFT JOIN biblio ON biblio.biblionumber = subscription.biblionumber
546 LEFT JOIN biblioitems ON biblioitems.biblionumber = subscription.biblionumber
547 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
549 $query .= q| WHERE 1|;
550 my @where_strs;
551 my @where_args;
552 if( $args->{biblionumber} ) {
553 push @where_strs, "biblio.biblionumber = ?";
554 push @where_args, $args->{biblionumber};
557 if( $args->{title} ){
558 my @words = split / /, $args->{title};
559 my (@strs, @args);
560 foreach my $word (@words) {
561 push @strs, "biblio.title LIKE ?";
562 push @args, "%$word%";
564 if (@strs) {
565 push @where_strs, '(' . join (' AND ', @strs) . ')';
566 push @where_args, @args;
569 if( $args->{issn} ){
570 push @where_strs, "biblioitems.issn LIKE ?";
571 push @where_args, "%$args->{issn}%";
573 if( $args->{ean} ){
574 push @where_strs, "biblioitems.ean LIKE ?";
575 push @where_args, "%$args->{ean}%";
577 if ( $args->{callnumber} ) {
578 push @where_strs, "subscription.callnumber LIKE ?";
579 push @where_args, "%$args->{callnumber}%";
581 if( $args->{publisher} ){
582 push @where_strs, "biblioitems.publishercode LIKE ?";
583 push @where_args, "%$args->{publisher}%";
585 if( $args->{bookseller} ){
586 push @where_strs, "aqbooksellers.name LIKE ?";
587 push @where_args, "%$args->{bookseller}%";
589 if( $args->{branch} ){
590 push @where_strs, "subscription.branchcode = ?";
591 push @where_args, "$args->{branch}";
593 if ( $args->{location} ) {
594 push @where_strs, "subscription.location = ?";
595 push @where_args, "$args->{location}";
597 if ( $args->{expiration_date} ) {
598 push @where_strs, "subscription.enddate <= ?";
599 push @where_args, "$args->{expiration_date}";
601 if( defined $args->{closed} ){
602 push @where_strs, "subscription.closed = ?";
603 push @where_args, "$args->{closed}";
606 if(@where_strs){
607 $query .= ' AND ' . join(' AND ', @where_strs);
609 if ( @$additional_fields ) {
610 $query .= ' AND subscriptionid IN ('
611 . join( ', ', @$matching_record_ids_for_additional_fields )
612 . ')';
615 $query .= " ORDER BY " . $args->{orderby} if $args->{orderby};
617 my $dbh = C4::Context->dbh;
618 my $sth = $dbh->prepare($query);
619 $sth->execute(@where_args);
620 my $results = $sth->fetchall_arrayref( {} );
622 for my $subscription ( @$results ) {
623 $subscription->{cannotedit} = not can_edit_subscription( $subscription );
624 $subscription->{cannotdisplay} = not can_show_subscription( $subscription );
626 my $subscription_object = Koha::Subscriptions->find($subscription->{subscriptionid});
627 $subscription->{additional_fields} = { map { $_->field->name => $_->value }
628 $subscription_object->additional_field_values->as_list };
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 push @serials, $line;
789 return \@serials;
792 =head2 GetPreviousSerialid
794 $serialid = GetPreviousSerialid($subscriptionid, $nth)
795 get the $nth's previous serial for the given subscriptionid
796 return :
797 the serialid
799 =cut
801 sub GetPreviousSerialid {
802 my ( $subscriptionid, $nth ) = @_;
803 $nth ||= 1;
804 my $dbh = C4::Context->dbh;
805 my $return = undef;
807 # Status 2: Arrived
808 my $strsth = "SELECT serialid
809 FROM serial
810 WHERE subscriptionid = ?
811 AND status = 2
812 ORDER BY serialid DESC LIMIT $nth,1
814 my $sth = $dbh->prepare($strsth);
815 $sth->execute($subscriptionid);
816 my @serials;
817 my $line = $sth->fetchrow_hashref;
818 $return = $line->{'serialid'} if ($line);
820 return $return;
823 =head2 GetNextSeq
825 my (
826 $nextseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
827 $newinnerloop1, $newinnerloop2, $newinnerloop3
828 ) = GetNextSeq( $subscription, $pattern, $frequency, $planneddate );
830 $subscription is a hashref containing all the attributes of the table
831 'subscription'.
832 $pattern is a hashref containing all the attributes of the table
833 'subscription_numberpatterns'.
834 $frequency is a hashref containing all the attributes of the table 'subscription_frequencies'
835 $planneddate is a date string in iso format.
836 This function get the next issue for the subscription given on input arg
838 =cut
840 sub GetNextSeq {
841 my ($subscription, $pattern, $frequency, $planneddate) = @_;
843 return unless ($subscription and $pattern);
845 my ( $newlastvalue1, $newlastvalue2, $newlastvalue3,
846 $newinnerloop1, $newinnerloop2, $newinnerloop3 );
847 my $count = 1;
849 if ($subscription->{'skip_serialseq'}) {
850 my @irreg = split /;/, $subscription->{'irregularity'};
851 if(@irreg > 0) {
852 my $irregularities = {};
853 $irregularities->{$_} = 1 foreach(@irreg);
854 my $issueno = GetFictiveIssueNumber($subscription, $planneddate, $frequency) + 1;
855 while($irregularities->{$issueno}) {
856 $count++;
857 $issueno++;
862 my $numberingmethod = $pattern->{numberingmethod};
863 my $calculated = "";
864 if ($numberingmethod) {
865 $calculated = $numberingmethod;
866 my $locale = $subscription->{locale};
867 $newlastvalue1 = $subscription->{lastvalue1} || 0;
868 $newlastvalue2 = $subscription->{lastvalue2} || 0;
869 $newlastvalue3 = $subscription->{lastvalue3} || 0;
870 $newinnerloop1 = $subscription->{innerloop1} || 0;
871 $newinnerloop2 = $subscription->{innerloop2} || 0;
872 $newinnerloop3 = $subscription->{innerloop3} || 0;
873 my %calc;
874 foreach(qw/X Y Z/) {
875 $calc{$_} = 1 if ($numberingmethod =~ /\{$_\}/);
878 for(my $i = 0; $i < $count; $i++) {
879 if($calc{'X'}) {
880 # check if we have to increase the new value.
881 $newinnerloop1 += 1;
882 if ($newinnerloop1 >= $pattern->{every1}) {
883 $newinnerloop1 = 0;
884 $newlastvalue1 += $pattern->{add1};
886 # reset counter if needed.
887 $newlastvalue1 = $pattern->{setto1} if ($newlastvalue1 > $pattern->{whenmorethan1});
889 if($calc{'Y'}) {
890 # check if we have to increase the new value.
891 $newinnerloop2 += 1;
892 if ($newinnerloop2 >= $pattern->{every2}) {
893 $newinnerloop2 = 0;
894 $newlastvalue2 += $pattern->{add2};
896 # reset counter if needed.
897 $newlastvalue2 = $pattern->{setto2} if ($newlastvalue2 > $pattern->{whenmorethan2});
899 if($calc{'Z'}) {
900 # check if we have to increase the new value.
901 $newinnerloop3 += 1;
902 if ($newinnerloop3 >= $pattern->{every3}) {
903 $newinnerloop3 = 0;
904 $newlastvalue3 += $pattern->{add3};
906 # reset counter if needed.
907 $newlastvalue3 = $pattern->{setto3} if ($newlastvalue3 > $pattern->{whenmorethan3});
910 if($calc{'X'}) {
911 my $newlastvalue1string = _numeration( $newlastvalue1, $pattern->{numbering1}, $locale );
912 $calculated =~ s/\{X\}/$newlastvalue1string/g;
914 if($calc{'Y'}) {
915 my $newlastvalue2string = _numeration( $newlastvalue2, $pattern->{numbering2}, $locale );
916 $calculated =~ s/\{Y\}/$newlastvalue2string/g;
918 if($calc{'Z'}) {
919 my $newlastvalue3string = _numeration( $newlastvalue3, $pattern->{numbering3}, $locale );
920 $calculated =~ s/\{Z\}/$newlastvalue3string/g;
924 return ($calculated,
925 $newlastvalue1, $newlastvalue2, $newlastvalue3,
926 $newinnerloop1, $newinnerloop2, $newinnerloop3);
929 =head2 GetSeq
931 $calculated = GetSeq($subscription, $pattern)
932 $subscription is a hashref containing all the attributes of the table 'subscription'
933 $pattern is a hashref containing all the attributes of the table 'subscription_numberpatterns'
934 this function transforms {X},{Y},{Z} to 150,0,0 for example.
935 return:
936 the sequence in string format
938 =cut
940 sub GetSeq {
941 my ($subscription, $pattern) = @_;
943 return unless ($subscription and $pattern);
945 my $locale = $subscription->{locale};
947 my $calculated = $pattern->{numberingmethod};
949 my $newlastvalue1 = $subscription->{'lastvalue1'} || 0;
950 $newlastvalue1 = _numeration($newlastvalue1, $pattern->{numbering1}, $locale) if ($pattern->{numbering1}); # reset counter if needed.
951 $calculated =~ s/\{X\}/$newlastvalue1/g;
953 my $newlastvalue2 = $subscription->{'lastvalue2'} || 0;
954 $newlastvalue2 = _numeration($newlastvalue2, $pattern->{numbering2}, $locale) if ($pattern->{numbering2}); # reset counter if needed.
955 $calculated =~ s/\{Y\}/$newlastvalue2/g;
957 my $newlastvalue3 = $subscription->{'lastvalue3'} || 0;
958 $newlastvalue3 = _numeration($newlastvalue3, $pattern->{numbering3}, $locale) if ($pattern->{numbering3}); # reset counter if needed.
959 $calculated =~ s/\{Z\}/$newlastvalue3/g;
960 return $calculated;
963 =head2 GetExpirationDate
965 $enddate = GetExpirationDate($subscriptionid, [$startdate])
967 this function return the next expiration date for a subscription given on input args.
969 return
970 the enddate or undef
972 =cut
974 sub GetExpirationDate {
975 my ( $subscriptionid, $startdate ) = @_;
977 return unless ($subscriptionid);
979 my $dbh = C4::Context->dbh;
980 my $subscription = GetSubscription($subscriptionid);
981 my $enddate;
983 # we don't do the same test if the subscription is based on X numbers or on X weeks/months
984 $enddate = $startdate || $subscription->{startdate};
985 my @date = split( /-/, $enddate );
987 return if ( scalar(@date) != 3 || not check_date(@date) );
989 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
990 if ( $frequency and $frequency->{unit} ) {
992 # If Not Irregular
993 if ( my $length = $subscription->{numberlength} ) {
995 #calculate the date of the last issue.
996 for ( my $i = 1 ; $i <= $length ; $i++ ) {
997 $enddate = GetNextDate( $subscription, $enddate, $frequency );
999 } elsif ( $subscription->{monthlength} ) {
1000 if ( $$subscription{startdate} ) {
1001 my @enddate = Add_Delta_YM( $date[0], $date[1], $date[2], 0, $subscription->{monthlength} );
1002 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1004 } elsif ( $subscription->{weeklength} ) {
1005 if ( $$subscription{startdate} ) {
1006 my @date = split( /-/, $subscription->{startdate} );
1007 my @enddate = Add_Delta_Days( $date[0], $date[1], $date[2], $subscription->{weeklength} * 7 );
1008 $enddate = sprintf( "%04d-%02d-%02d", $enddate[0], $enddate[1], $enddate[2] );
1010 } else {
1011 $enddate = $subscription->{enddate};
1013 return $enddate;
1014 } else {
1015 return $subscription->{enddate};
1019 =head2 CountSubscriptionFromBiblionumber
1021 $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber)
1022 this returns a count of the subscriptions for a given biblionumber
1023 return :
1024 the number of subscriptions
1026 =cut
1028 sub CountSubscriptionFromBiblionumber {
1029 my ($biblionumber) = @_;
1031 return unless ($biblionumber);
1033 my $dbh = C4::Context->dbh;
1034 my $query = "SELECT count(*) FROM subscription WHERE biblionumber=?";
1035 my $sth = $dbh->prepare($query);
1036 $sth->execute($biblionumber);
1037 my $subscriptionsnumber = $sth->fetchrow;
1038 return $subscriptionsnumber;
1041 =head2 ModSubscriptionHistory
1043 ModSubscriptionHistory($subscriptionid,$histstartdate,$enddate,$recievedlist,$missinglist,$opacnote,$librariannote);
1045 this function modifies the history of a subscription. Put your new values on input arg.
1046 returns the number of rows affected
1048 =cut
1050 sub ModSubscriptionHistory {
1051 my ( $subscriptionid, $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote ) = @_;
1053 return unless ($subscriptionid);
1055 my $dbh = C4::Context->dbh;
1056 my $query = "UPDATE subscriptionhistory
1057 SET histstartdate=?,histenddate=?,recievedlist=?,missinglist=?,opacnote=?,librariannote=?
1058 WHERE subscriptionid=?
1060 my $sth = $dbh->prepare($query);
1061 $receivedlist =~ s/^; // if $receivedlist;
1062 $missinglist =~ s/^; // if $missinglist;
1063 $opacnote =~ s/^; // if $opacnote;
1064 $sth->execute( $histstartdate, $enddate, $receivedlist, $missinglist, $opacnote, $librariannote, $subscriptionid );
1065 return $sth->rows;
1068 =head2 ModSerialStatus
1070 ModSerialStatus($serialid, $serialseq, $planneddate, $publisheddate,
1071 $publisheddatetext, $status, $notes);
1073 This function modify the serial status. Serial status is a number.(eg 2 is "arrived")
1074 Note : if we change from "waited" to something else,then we will have to create a new "waited" entry
1076 =cut
1078 sub ModSerialStatus {
1079 my ($serialid, $serialseq, $planneddate, $publisheddate, $publisheddatetext,
1080 $status, $notes) = @_;
1082 return unless ($serialid);
1084 #It is a usual serial
1085 # 1st, get previous status :
1086 my $dbh = C4::Context->dbh;
1087 my $query = "SELECT serial.subscriptionid,serial.status,subscription.periodicity,serial.routingnotes
1088 FROM serial, subscription
1089 WHERE serial.subscriptionid=subscription.subscriptionid
1090 AND serialid=?";
1091 my $sth = $dbh->prepare($query);
1092 $sth->execute($serialid);
1093 my ( $subscriptionid, $oldstatus, $periodicity, $routingnotes ) = $sth->fetchrow;
1094 my $frequency = GetSubscriptionFrequency($periodicity);
1096 # change status & update subscriptionhistory
1097 my $val;
1098 if ( $status == DELETED ) {
1099 DelIssue( { 'serialid' => $serialid, 'subscriptionid' => $subscriptionid, 'serialseq' => $serialseq } );
1100 } else {
1101 my $query = '
1102 UPDATE serial
1103 SET serialseq = ?, publisheddate = ?, publisheddatetext = ?,
1104 planneddate = ?, status = ?, notes = ?, routingnotes = ?
1105 WHERE serialid = ?
1107 $sth = $dbh->prepare($query);
1108 $sth->execute( $serialseq, $publisheddate, $publisheddatetext,
1109 $planneddate, $status, $notes, $routingnotes, $serialid );
1110 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1111 $sth = $dbh->prepare($query);
1112 $sth->execute($subscriptionid);
1113 my $val = $sth->fetchrow_hashref;
1114 unless ( $val->{manualhistory} ) {
1115 $query = "SELECT missinglist,recievedlist FROM subscriptionhistory WHERE subscriptionid=?";
1116 $sth = $dbh->prepare($query);
1117 $sth->execute($subscriptionid);
1118 my ( $missinglist, $recievedlist ) = $sth->fetchrow;
1120 if ( $status == ARRIVED || ($oldstatus == ARRIVED && $status != ARRIVED) ) {
1121 $recievedlist .= "; $serialseq"
1122 if ($recievedlist !~ /(^|;)\s*$serialseq(?=;|$)/);
1125 # in case serial has been previously marked as missing
1126 if (grep /$status/, (EXPECTED, ARRIVED, LATE, CLAIMED)) {
1127 $missinglist=~ s/(^|;)\s*$serialseq(?=;|$)//g;
1130 $missinglist .= "; $serialseq"
1131 if ( ( grep { $_ == $status } ( MISSING_STATUSES ) ) && ( $missinglist !~/(^|;)\s*$serialseq(?=;|$)/ ) );
1132 $missinglist .= "; not issued $serialseq"
1133 if ( $status == NOT_ISSUED && $missinglist !~ /(^|;)\s*$serialseq(?=;|$)/ );
1135 $query = "UPDATE subscriptionhistory SET recievedlist=?, missinglist=? WHERE subscriptionid=?";
1136 $sth = $dbh->prepare($query);
1137 $recievedlist =~ s/^; //;
1138 $missinglist =~ s/^; //;
1139 $sth->execute( $recievedlist, $missinglist, $subscriptionid );
1143 # create new expected entry if needed (ie : was "expected" and has changed)
1144 my $otherIssueExpected = scalar findSerialsByStatus(EXPECTED, $subscriptionid);
1145 if ( !$otherIssueExpected && $oldstatus == EXPECTED && $status != EXPECTED ) {
1146 my $subscription = GetSubscription($subscriptionid);
1147 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1148 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1150 # next issue number
1151 my (
1152 $newserialseq, $newlastvalue1, $newlastvalue2, $newlastvalue3,
1153 $newinnerloop1, $newinnerloop2, $newinnerloop3
1155 = GetNextSeq( $subscription, $pattern, $frequency, $publisheddate );
1157 # next date (calculated from actual date & frequency parameters)
1158 my $nextpublisheddate = GetNextDate($subscription, $publisheddate, $frequency, 1);
1159 my $nextpubdate = $nextpublisheddate;
1160 $query = "UPDATE subscription SET lastvalue1=?, lastvalue2=?, lastvalue3=?, innerloop1=?, innerloop2=?, innerloop3=?
1161 WHERE subscriptionid = ?";
1162 $sth = $dbh->prepare($query);
1163 $sth->execute( $newlastvalue1, $newlastvalue2, $newlastvalue3, $newinnerloop1, $newinnerloop2, $newinnerloop3, $subscriptionid );
1164 my $newnote = C4::Context->preference('PreserveSerialNotes') ? $notes : "";
1165 NewIssue( $newserialseq, $subscriptionid, $subscription->{'biblionumber'}, 1, $nextpubdate, $nextpubdate, undef, $newnote, $routingnotes );
1166 # check if an alert must be sent... (= a letter is defined & status became "arrived"
1167 if ( $subscription->{letter} && $status == ARRIVED && $oldstatus != ARRIVED ) {
1168 require C4::Letters;
1169 C4::Letters::SendAlerts( 'issue', $serialid, $subscription->{letter} );
1173 return;
1176 =head2 GetNextExpected
1178 $nextexpected = GetNextExpected($subscriptionid)
1180 Get the planneddate for the current expected issue of the subscription.
1182 returns a hashref:
1184 $nextexepected = {
1185 serialid => int
1186 planneddate => ISO date
1189 =cut
1191 sub GetNextExpected {
1192 my ($subscriptionid) = @_;
1194 my $dbh = C4::Context->dbh;
1195 my $query = qq{
1196 SELECT *
1197 FROM serial
1198 WHERE subscriptionid = ?
1199 AND status = ?
1200 LIMIT 1
1202 my $sth = $dbh->prepare($query);
1204 # Each subscription has only one 'expected' issue.
1205 $sth->execute( $subscriptionid, EXPECTED );
1206 my $nextissue = $sth->fetchrow_hashref;
1207 if ( !$nextissue ) {
1208 $query = qq{
1209 SELECT *
1210 FROM serial
1211 WHERE subscriptionid = ?
1212 ORDER BY publisheddate DESC
1213 LIMIT 1
1215 $sth = $dbh->prepare($query);
1216 $sth->execute($subscriptionid);
1217 $nextissue = $sth->fetchrow_hashref;
1219 foreach(qw/planneddate publisheddate/) {
1220 if ( !defined $nextissue->{$_} ) {
1221 # or should this default to 1st Jan ???
1222 $nextissue->{$_} = strftime( '%Y-%m-%d', localtime );
1224 $nextissue->{$_} = ($nextissue->{$_} ne '0000-00-00')
1225 ? $nextissue->{$_}
1226 : undef;
1229 return $nextissue;
1232 =head2 ModNextExpected
1234 ModNextExpected($subscriptionid,$date)
1236 Update the planneddate for the current expected issue of the subscription.
1237 This will modify all future prediction results.
1239 C<$date> is an ISO date.
1241 returns 0
1243 =cut
1245 sub ModNextExpected {
1246 my ( $subscriptionid, $date ) = @_;
1247 my $dbh = C4::Context->dbh;
1249 #FIXME: Would expect to only set planneddate, but we set both on new issue creation, so updating it here
1250 my $sth = $dbh->prepare('UPDATE serial SET planneddate=?,publisheddate=? WHERE subscriptionid=? AND status=?');
1252 # Each subscription has only one 'expected' issue.
1253 $sth->execute( $date, $date, $subscriptionid, EXPECTED );
1254 return 0;
1258 =head2 GetSubscriptionIrregularities
1260 =over 4
1262 =item @irreg = &GetSubscriptionIrregularities($subscriptionid);
1263 get the list of irregularities for a subscription
1265 =back
1267 =cut
1269 sub GetSubscriptionIrregularities {
1270 my $subscriptionid = shift;
1272 return unless $subscriptionid;
1274 my $dbh = C4::Context->dbh;
1275 my $query = qq{
1276 SELECT irregularity
1277 FROM subscription
1278 WHERE subscriptionid = ?
1280 my $sth = $dbh->prepare($query);
1281 $sth->execute($subscriptionid);
1283 my ($result) = $sth->fetchrow_array;
1284 my @irreg = split /;/, $result;
1286 return @irreg;
1289 =head2 ModSubscription
1291 this function modifies a subscription. Put all new values on input args.
1292 returns the number of rows affected
1294 =cut
1296 sub ModSubscription {
1297 my (
1298 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $startdate,
1299 $periodicity, $firstacquidate, $irregularity, $numberpattern, $locale,
1300 $numberlength, $weeklength, $monthlength, $lastvalue1, $innerloop1,
1301 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3, $status,
1302 $biblionumber, $callnumber, $notes, $letter, $manualhistory,
1303 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1304 $graceperiod, $location, $enddate, $subscriptionid, $skip_serialseq,
1305 $itemtype, $previousitemtype, $mana_id
1306 ) = @_;
1308 my $dbh = C4::Context->dbh;
1309 my $query = "UPDATE subscription
1310 SET librarian=?, branchcode=?, aqbooksellerid=?, cost=?, aqbudgetid=?,
1311 startdate=?, periodicity=?, firstacquidate=?, irregularity=?,
1312 numberpattern=?, locale=?, numberlength=?, weeklength=?, monthlength=?,
1313 lastvalue1=?, innerloop1=?, lastvalue2=?, innerloop2=?,
1314 lastvalue3=?, innerloop3=?, status=?, biblionumber=?,
1315 callnumber=?, notes=?, letter=?, manualhistory=?,
1316 internalnotes=?, serialsadditems=?, staffdisplaycount=?,
1317 opacdisplaycount=?, graceperiod=?, location = ?, enddate=?,
1318 skip_serialseq=?, itemtype=?, previousitemtype=?, mana_id=?
1319 WHERE subscriptionid = ?";
1321 my $sth = $dbh->prepare($query);
1322 $sth->execute(
1323 $auser, $branchcode, $aqbooksellerid, $cost,
1324 $aqbudgetid, $startdate, $periodicity, $firstacquidate,
1325 $irregularity, $numberpattern, $locale, $numberlength,
1326 $weeklength, $monthlength, $lastvalue1, $innerloop1,
1327 $lastvalue2, $innerloop2, $lastvalue3, $innerloop3,
1328 $status, $biblionumber, $callnumber, $notes,
1329 $letter, ($manualhistory ? $manualhistory : 0),
1330 $internalnotes, $serialsadditems, $staffdisplaycount, $opacdisplaycount,
1331 $graceperiod, $location, $enddate, $skip_serialseq,
1332 $itemtype, $previousitemtype, $mana_id,
1333 $subscriptionid
1335 my $rows = $sth->rows;
1337 logaction( "SERIAL", "MODIFY", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1338 return $rows;
1341 =head2 NewSubscription
1343 $subscriptionid = &NewSubscription($auser,branchcode,$aqbooksellerid,$cost,$aqbudgetid,$biblionumber,
1344 $startdate,$periodicity,$numberlength,$weeklength,$monthlength,
1345 $lastvalue1,$innerloop1,$lastvalue2,$innerloop2,$lastvalue3,$innerloop3,
1346 $status, $notes, $letter, $firstacquidate, $irregularity, $numberpattern,
1347 $locale, $callnumber, $manualhistory, $internalnotes, $serialsadditems,
1348 $staffdisplaycount, $opacdisplaycount, $graceperiod, $location, $enddate,
1349 $skip_serialseq, $itemtype, $previousitemtype);
1351 Create a new subscription with value given on input args.
1353 return :
1354 the id of this new subscription
1356 =cut
1358 sub NewSubscription {
1359 my (
1360 $auser, $branchcode, $aqbooksellerid, $cost, $aqbudgetid, $biblionumber,
1361 $startdate, $periodicity, $numberlength, $weeklength, $monthlength,
1362 $lastvalue1, $innerloop1, $lastvalue2, $innerloop2, $lastvalue3,
1363 $innerloop3, $status, $notes, $letter, $firstacquidate, $irregularity,
1364 $numberpattern, $locale, $callnumber, $manualhistory, $internalnotes,
1365 $serialsadditems, $staffdisplaycount, $opacdisplaycount, $graceperiod,
1366 $location, $enddate, $skip_serialseq, $itemtype, $previousitemtype, $mana_id
1367 ) = @_;
1368 my $dbh = C4::Context->dbh;
1370 my $subscription = Koha::Subscription->new(
1372 librarian => $auser,
1373 branchcode => $branchcode,
1374 aqbooksellerid => $aqbooksellerid,
1375 cost => $cost,
1376 aqbudgetid => $aqbudgetid,
1377 biblionumber => $biblionumber,
1378 startdate => $startdate,
1379 periodicity => $periodicity,
1380 numberlength => $numberlength,
1381 weeklength => $weeklength,
1382 monthlength => $monthlength,
1383 lastvalue1 => $lastvalue1,
1384 innerloop1 => $innerloop1,
1385 lastvalue2 => $lastvalue2,
1386 innerloop2 => $innerloop2,
1387 lastvalue3 => $lastvalue3,
1388 innerloop3 => $innerloop3,
1389 status => $status,
1390 notes => $notes,
1391 letter => $letter,
1392 firstacquidate => $firstacquidate,
1393 irregularity => $irregularity,
1394 numberpattern => $numberpattern,
1395 locale => $locale,
1396 callnumber => $callnumber,
1397 manualhistory => $manualhistory,
1398 internalnotes => $internalnotes,
1399 serialsadditems => $serialsadditems,
1400 staffdisplaycount => $staffdisplaycount,
1401 opacdisplaycount => $opacdisplaycount,
1402 graceperiod => $graceperiod,
1403 location => $location,
1404 enddate => $enddate,
1405 skip_serialseq => $skip_serialseq,
1406 itemtype => $itemtype,
1407 previousitemtype => $previousitemtype,
1408 mana_id => $mana_id,
1410 )->store;
1411 $subscription->discard_changes;
1412 my $subscriptionid = $subscription->subscriptionid;
1413 my ( $query, $sth );
1414 unless ($enddate) {
1415 $enddate = GetExpirationDate( $subscriptionid, $startdate );
1416 $query = qq|
1417 UPDATE subscription
1418 SET enddate=?
1419 WHERE subscriptionid=?
1421 $sth = $dbh->prepare($query);
1422 $sth->execute( $enddate, $subscriptionid );
1425 # then create the 1st expected number
1426 $query = qq(
1427 INSERT INTO subscriptionhistory
1428 (biblionumber, subscriptionid, histstartdate, missinglist, recievedlist)
1429 VALUES (?,?,?, '', '')
1431 $sth = $dbh->prepare($query);
1432 $sth->execute( $biblionumber, $subscriptionid, $startdate);
1434 # reread subscription to get a hash (for calculation of the 1st issue number)
1435 $subscription = GetSubscription($subscriptionid); # We should not do that
1436 my $pattern = C4::Serials::Numberpattern::GetSubscriptionNumberpattern($subscription->{numberpattern});
1438 # calculate issue number
1439 my $serialseq = GetSeq($subscription, $pattern) || q{};
1441 Koha::Serial->new(
1443 serialseq => $serialseq,
1444 serialseq_x => $subscription->{'lastvalue1'},
1445 serialseq_y => $subscription->{'lastvalue2'},
1446 serialseq_z => $subscription->{'lastvalue3'},
1447 subscriptionid => $subscriptionid,
1448 biblionumber => $biblionumber,
1449 status => EXPECTED,
1450 planneddate => $firstacquidate,
1451 publisheddate => $firstacquidate,
1453 )->store();
1455 logaction( "SERIAL", "ADD", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1457 #set serial flag on biblio if not already set.
1458 my $biblio = Koha::Biblios->find( $biblionumber );
1459 if ( $biblio and !$biblio->serial ) {
1460 my $record = GetMarcBiblio({ biblionumber => $biblionumber });
1461 my ( $tag, $subf ) = GetMarcFromKohaField( 'biblio.serial' );
1462 if ($tag) {
1463 eval { $record->field($tag)->update( $subf => 1 ); };
1465 ModBiblio( $record, $biblionumber, $biblio->frameworkcode );
1467 return $subscriptionid;
1470 =head2 ReNewSubscription
1472 ReNewSubscription($subscriptionid,$user,$startdate,$numberlength,$weeklength,$monthlength,$note)
1474 this function renew a subscription with values given on input args.
1476 =cut
1478 sub ReNewSubscription {
1479 my ( $subscriptionid, $user, $startdate, $numberlength, $weeklength, $monthlength, $note ) = @_;
1480 my $dbh = C4::Context->dbh;
1481 my $subscription = GetSubscription($subscriptionid);
1482 my $query = qq|
1483 SELECT *
1484 FROM biblio
1485 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
1486 WHERE biblio.biblionumber=?
1488 my $sth = $dbh->prepare($query);
1489 $sth->execute( $subscription->{biblionumber} );
1490 my $biblio = $sth->fetchrow_hashref;
1492 if ( C4::Context->preference("RenewSerialAddsSuggestion") ) {
1493 require C4::Suggestions;
1494 C4::Suggestions::NewSuggestion(
1495 { 'suggestedby' => $user,
1496 'title' => $subscription->{bibliotitle},
1497 'author' => $biblio->{author},
1498 'publishercode' => $biblio->{publishercode},
1499 'note' => $biblio->{note},
1500 'biblionumber' => $subscription->{biblionumber}
1505 $numberlength ||= 0; # Should not we raise an exception instead?
1506 $weeklength ||= 0;
1508 # renew subscription
1509 $query = qq|
1510 UPDATE subscription
1511 SET startdate=?,numberlength=?,weeklength=?,monthlength=?,reneweddate=NOW()
1512 WHERE subscriptionid=?
1514 $sth = $dbh->prepare($query);
1515 $sth->execute( $startdate, $numberlength, $weeklength, $monthlength, $subscriptionid );
1516 my $enddate = GetExpirationDate($subscriptionid);
1517 $debug && warn "enddate :$enddate";
1518 $query = qq|
1519 UPDATE subscription
1520 SET enddate=?
1521 WHERE subscriptionid=?
1523 $sth = $dbh->prepare($query);
1524 $sth->execute( $enddate, $subscriptionid );
1526 logaction( "SERIAL", "RENEW", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1527 return;
1530 =head2 NewIssue
1532 NewIssue($serialseq,$subscriptionid,$biblionumber,$status, $planneddate, $publisheddate, $notes, $routingnotes)
1534 Create a new issue stored on the database.
1535 Note : we have to update the recievedlist and missinglist on subscriptionhistory for this subscription.
1536 returns the serial id
1538 =cut
1540 sub NewIssue {
1541 my ( $serialseq, $subscriptionid, $biblionumber, $status, $planneddate,
1542 $publisheddate, $publisheddatetext, $notes, $routingnotes ) = @_;
1543 ### FIXME biblionumber CAN be provided by subscriptionid. So Do we STILL NEED IT ?
1545 return unless ($subscriptionid);
1547 my $schema = Koha::Database->new()->schema();
1549 my $subscription = Koha::Subscriptions->find( $subscriptionid );
1551 my $serial = Koha::Serial->new(
1553 serialseq => $serialseq,
1554 serialseq_x => $subscription->lastvalue1(),
1555 serialseq_y => $subscription->lastvalue2(),
1556 serialseq_z => $subscription->lastvalue3(),
1557 subscriptionid => $subscriptionid,
1558 biblionumber => $biblionumber,
1559 status => $status,
1560 planneddate => $planneddate,
1561 publisheddate => $publisheddate,
1562 publisheddatetext => $publisheddatetext,
1563 notes => $notes,
1564 routingnotes => $routingnotes
1566 )->store();
1568 my $serialid = $serial->id();
1570 my $subscription_history = Koha::Subscription::Histories->find($subscriptionid);
1571 my $missinglist = $subscription_history->missinglist();
1572 my $recievedlist = $subscription_history->recievedlist();
1574 if ( $status == ARRIVED ) {
1575 ### TODO Add a feature that improves recognition and description.
1576 ### As such count (serialseq) i.e. : N18,2(N19),N20
1577 ### Would use substr and index But be careful to previous presence of ()
1578 $recievedlist .= "; $serialseq" unless ( index( $recievedlist, $serialseq ) > 0 );
1580 if ( grep { /^$status$/ } (MISSING_STATUSES) ) {
1581 $missinglist .= "; $serialseq" unless ( index( $missinglist, $serialseq ) > 0 );
1584 $recievedlist =~ s/^; //;
1585 $missinglist =~ s/^; //;
1587 $subscription_history->recievedlist($recievedlist);
1588 $subscription_history->missinglist($missinglist);
1589 $subscription_history->store();
1591 return $serialid;
1594 =head2 HasSubscriptionStrictlyExpired
1596 1 or 0 = HasSubscriptionStrictlyExpired($subscriptionid)
1598 the subscription has stricly expired when today > the end subscription date
1600 return :
1601 1 if true, 0 if false, -1 if the expiration date is not set.
1603 =cut
1605 sub HasSubscriptionStrictlyExpired {
1607 # Getting end of subscription date
1608 my ($subscriptionid) = @_;
1610 return unless ($subscriptionid);
1612 my $dbh = C4::Context->dbh;
1613 my $subscription = GetSubscription($subscriptionid);
1614 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1616 # If the expiration date is set
1617 if ( $expirationdate != 0 ) {
1618 my ( $endyear, $endmonth, $endday ) = split( '-', $expirationdate );
1620 # Getting today's date
1621 my ( $nowyear, $nowmonth, $nowday ) = Today();
1623 # if today's date > expiration date, then the subscription has stricly expired
1624 if ( Delta_Days( $nowyear, $nowmonth, $nowday, $endyear, $endmonth, $endday ) < 0 ) {
1625 return 1;
1626 } else {
1627 return 0;
1629 } else {
1631 # There are some cases where the expiration date is not set
1632 # As we can't determine if the subscription has expired on a date-basis,
1633 # we return -1;
1634 return -1;
1638 =head2 HasSubscriptionExpired
1640 $has_expired = HasSubscriptionExpired($subscriptionid)
1642 the subscription has expired when the next issue to arrive is out of subscription limit.
1644 return :
1645 0 if the subscription has not expired
1646 1 if the subscription has expired
1647 2 if has subscription does not have a valid expiration date set
1649 =cut
1651 sub HasSubscriptionExpired {
1652 my ($subscriptionid) = @_;
1654 return unless ($subscriptionid);
1656 my $dbh = C4::Context->dbh;
1657 my $subscription = GetSubscription($subscriptionid);
1658 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($subscription->{periodicity});
1659 if ( $frequency and $frequency->{unit} ) {
1660 my $expirationdate = $subscription->{enddate} || GetExpirationDate($subscriptionid);
1661 if (!defined $expirationdate) {
1662 $expirationdate = q{};
1664 my $query = qq|
1665 SELECT max(planneddate)
1666 FROM serial
1667 WHERE subscriptionid=?
1669 my $sth = $dbh->prepare($query);
1670 $sth->execute($subscriptionid);
1671 my ($res) = $sth->fetchrow;
1672 if (!$res || $res=~m/^0000/) {
1673 return 0;
1675 my @res = split( /-/, $res );
1676 my @endofsubscriptiondate = split( /-/, $expirationdate );
1677 return 2 if ( scalar(@res) != 3 || scalar(@endofsubscriptiondate) != 3 || not check_date(@res) || not check_date(@endofsubscriptiondate) );
1678 return 1
1679 if ( ( @endofsubscriptiondate && Delta_Days( $res[0], $res[1], $res[2], $endofsubscriptiondate[0], $endofsubscriptiondate[1], $endofsubscriptiondate[2] ) <= 0 )
1680 || ( !$res ) );
1681 return 0;
1682 } else {
1683 # Irregular
1684 if ( $subscription->{'numberlength'} ) {
1685 my $countreceived = countissuesfrom( $subscriptionid, $subscription->{'startdate'} );
1686 return 1 if ( $countreceived > $subscription->{'numberlength'} );
1687 return 0;
1688 } else {
1689 return 0;
1692 return 0; # Notice that you'll never get here.
1695 =head2 DelSubscription
1697 DelSubscription($subscriptionid)
1698 this function deletes subscription which has $subscriptionid as id.
1700 =cut
1702 sub DelSubscription {
1703 my ($subscriptionid) = @_;
1704 my $dbh = C4::Context->dbh;
1705 $dbh->do("DELETE FROM subscription WHERE subscriptionid=?", undef, $subscriptionid);
1706 $dbh->do("DELETE FROM subscriptionhistory WHERE subscriptionid=?", undef, $subscriptionid);
1707 $dbh->do("DELETE FROM serial WHERE subscriptionid=?", undef, $subscriptionid);
1709 Koha::AdditionalFieldValues->search({
1710 'field.tablename' => 'subscription',
1711 'me.record_id' => $subscriptionid,
1712 }, { join => 'field' })->delete;
1714 logaction( "SERIAL", "DELETE", $subscriptionid, "" ) if C4::Context->preference("SubscriptionLog");
1717 =head2 DelIssue
1719 DelIssue($serialseq,$subscriptionid)
1720 this function deletes an issue which has $serialseq and $subscriptionid given on input arg.
1722 returns the number of rows affected
1724 =cut
1726 sub DelIssue {
1727 my ($dataissue) = @_;
1728 my $dbh = C4::Context->dbh;
1729 ### TODO Add itemdeletion. Would need to get itemnumbers. Should be in a pref ?
1731 my $query = qq|
1732 DELETE FROM serial
1733 WHERE serialid= ?
1734 AND subscriptionid= ?
1736 my $mainsth = $dbh->prepare($query);
1737 $mainsth->execute( $dataissue->{'serialid'}, $dataissue->{'subscriptionid'} );
1739 #Delete element from subscription history
1740 $query = "SELECT * FROM subscription WHERE subscriptionid = ?";
1741 my $sth = $dbh->prepare($query);
1742 $sth->execute( $dataissue->{'subscriptionid'} );
1743 my $val = $sth->fetchrow_hashref;
1744 unless ( $val->{manualhistory} ) {
1745 my $query = qq|
1746 SELECT * FROM subscriptionhistory
1747 WHERE subscriptionid= ?
1749 my $sth = $dbh->prepare($query);
1750 $sth->execute( $dataissue->{'subscriptionid'} );
1751 my $data = $sth->fetchrow_hashref;
1752 my $serialseq = $dataissue->{'serialseq'};
1753 $data->{'missinglist'} =~ s/\b$serialseq\b//;
1754 $data->{'recievedlist'} =~ s/\b$serialseq\b//;
1755 my $strsth = "UPDATE subscriptionhistory SET " . join( ",", map { join( "=", $_, $dbh->quote( $data->{$_} ) ) } keys %$data ) . " WHERE subscriptionid=?";
1756 $sth = $dbh->prepare($strsth);
1757 $sth->execute( $dataissue->{'subscriptionid'} );
1760 return $mainsth->rows;
1763 =head2 GetLateOrMissingIssues
1765 @issuelist = GetLateMissingIssues($supplierid,$serialid)
1767 this function selects missing issues on database - where serial.status = MISSING* or serial.status = LATE or planneddate<now
1769 return :
1770 the issuelist as an array of hash refs. Each element of this array contains
1771 name,title,planneddate,serialseq,serial.subscriptionid from tables : subscription, serial & biblio
1773 =cut
1775 sub GetLateOrMissingIssues {
1776 my ( $supplierid, $serialid, $order ) = @_;
1778 return unless ( $supplierid or $serialid );
1780 my $dbh = C4::Context->dbh;
1782 my $sth;
1783 my $byserial = '';
1784 if ($serialid) {
1785 $byserial = "and serialid = " . $serialid;
1787 if ($order) {
1788 $order .= ", title";
1789 } else {
1790 $order = "title";
1792 my $missing_statuses_string = join ',', (MISSING_STATUSES);
1793 if ($supplierid) {
1794 $sth = $dbh->prepare(
1795 "SELECT
1796 serialid, aqbooksellerid, name,
1797 biblio.title, biblioitems.issn, planneddate, serialseq,
1798 serial.status, serial.subscriptionid, claimdate, claims_count,
1799 subscription.branchcode
1800 FROM serial
1801 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1802 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1803 LEFT JOIN biblioitems ON subscription.biblionumber=biblioitems.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 AND subscription.aqbooksellerid=$supplierid
1808 $byserial
1809 ORDER BY $order"
1811 } else {
1812 $sth = $dbh->prepare(
1813 "SELECT
1814 serialid, aqbooksellerid, name,
1815 biblio.title, planneddate, serialseq,
1816 serial.status, serial.subscriptionid, claimdate, claims_count,
1817 subscription.branchcode
1818 FROM serial
1819 LEFT JOIN subscription ON serial.subscriptionid=subscription.subscriptionid
1820 LEFT JOIN biblio ON subscription.biblionumber=biblio.biblionumber
1821 LEFT JOIN aqbooksellers ON subscription.aqbooksellerid = aqbooksellers.id
1822 WHERE subscription.subscriptionid = serial.subscriptionid
1823 AND (serial.STATUS IN ($missing_statuses_string) OR ((planneddate < now() AND serial.STATUS = ?) OR serial.STATUS = ? OR serial.STATUS = ?))
1824 $byserial
1825 ORDER BY $order"
1828 $sth->execute( EXPECTED, LATE, CLAIMED );
1829 my @issuelist;
1830 while ( my $line = $sth->fetchrow_hashref ) {
1832 if ($line->{planneddate} && $line->{planneddate} !~/^0+\-/) {
1833 $line->{planneddateISO} = $line->{planneddate};
1834 $line->{planneddate} = output_pref( { dt => dt_from_string( $line->{"planneddate"} ), dateonly => 1 } );
1836 if ($line->{claimdate} && $line->{claimdate} !~/^0+\-/) {
1837 $line->{claimdateISO} = $line->{claimdate};
1838 $line->{claimdate} = output_pref( { dt => dt_from_string( $line->{"claimdate"} ), dateonly => 1 } );
1840 $line->{"status".$line->{status}} = 1;
1842 my $subscription_object = Koha::Subscriptions->find($line->{subscriptionid});
1843 $line->{additional_fields} = { map { $_->field->name => $_->value }
1844 $subscription_object->additional_field_values->as_list };
1846 push @issuelist, $line;
1848 return @issuelist;
1851 =head2 updateClaim
1853 &updateClaim($serialid)
1855 this function updates the time when a claim is issued for late/missing items
1857 called from claims.pl file
1859 =cut
1861 sub updateClaim {
1862 my ($serialids) = @_;
1863 return unless $serialids;
1864 unless ( ref $serialids ) {
1865 $serialids = [ $serialids ];
1867 my $dbh = C4::Context->dbh;
1868 return $dbh->do(q|
1869 UPDATE serial
1870 SET claimdate = NOW(),
1871 claims_count = claims_count + 1,
1872 status = ?
1873 WHERE serialid in (| . join( q|,|, (q|?|) x @$serialids ) . q|)|,
1874 {}, CLAIMED, @$serialids );
1877 =head2 check_routing
1879 $result = &check_routing($subscriptionid)
1881 this function checks to see if a serial has a routing list and returns the count of routingid
1882 used to show either an 'add' or 'edit' link
1884 =cut
1886 sub check_routing {
1887 my ($subscriptionid) = @_;
1889 return unless ($subscriptionid);
1891 my $dbh = C4::Context->dbh;
1892 my $sth = $dbh->prepare(
1893 "SELECT count(routingid) routingids FROM subscription LEFT JOIN subscriptionroutinglist
1894 ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
1895 WHERE subscription.subscriptionid = ? GROUP BY routingid ORDER BY ranking ASC
1898 $sth->execute($subscriptionid);
1899 my $line = $sth->fetchrow_hashref;
1900 my $result = $line->{'routingids'};
1901 return $result;
1904 =head2 addroutingmember
1906 addroutingmember($borrowernumber,$subscriptionid)
1908 this function takes a borrowernumber and subscriptionid and adds the member to the
1909 routing list for that serial subscription and gives them a rank on the list
1910 of either 1 or highest current rank + 1
1912 =cut
1914 sub addroutingmember {
1915 my ( $borrowernumber, $subscriptionid ) = @_;
1917 return unless ($borrowernumber and $subscriptionid);
1919 my $rank;
1920 my $dbh = C4::Context->dbh;
1921 my $sth = $dbh->prepare( "SELECT max(ranking) rank FROM subscriptionroutinglist WHERE subscriptionid = ?" );
1922 $sth->execute($subscriptionid);
1923 while ( my $line = $sth->fetchrow_hashref ) {
1924 if ( $line->{'rank'} > 0 ) {
1925 $rank = $line->{'rank'} + 1;
1926 } else {
1927 $rank = 1;
1930 $sth = $dbh->prepare( "INSERT INTO subscriptionroutinglist (subscriptionid,borrowernumber,ranking) VALUES (?,?,?)" );
1931 $sth->execute( $subscriptionid, $borrowernumber, $rank );
1934 =head2 reorder_members
1936 reorder_members($subscriptionid,$routingid,$rank)
1938 this function is used to reorder the routing list
1940 it takes the routingid of the member one wants to re-rank and the rank it is to move to
1941 - it gets all members on list puts their routingid's into an array
1942 - removes the one in the array that is $routingid
1943 - then reinjects $routingid at point indicated by $rank
1944 - then update the database with the routingids in the new order
1946 =cut
1948 sub reorder_members {
1949 my ( $subscriptionid, $routingid, $rank ) = @_;
1950 my $dbh = C4::Context->dbh;
1951 my $sth = $dbh->prepare( "SELECT * FROM subscriptionroutinglist WHERE subscriptionid = ? ORDER BY ranking ASC" );
1952 $sth->execute($subscriptionid);
1953 my @result;
1954 while ( my $line = $sth->fetchrow_hashref ) {
1955 push( @result, $line->{'routingid'} );
1958 # To find the matching index
1959 my $i;
1960 my $key = -1; # to allow for 0 being a valid response
1961 for ( $i = 0 ; $i < @result ; $i++ ) {
1962 if ( $routingid == $result[$i] ) {
1963 $key = $i; # save the index
1964 last;
1968 # if index exists in array then move it to new position
1969 if ( $key > -1 && $rank > 0 ) {
1970 my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
1971 my $moving_item = splice( @result, $key, 1 );
1972 splice( @result, $new_rank, 0, $moving_item );
1974 for ( my $j = 0 ; $j < @result ; $j++ ) {
1975 my $sth = $dbh->prepare( "UPDATE subscriptionroutinglist SET ranking = '" . ( $j + 1 ) . "' WHERE routingid = '" . $result[$j] . "'" );
1976 $sth->execute;
1978 return;
1981 =head2 delroutingmember
1983 delroutingmember($routingid,$subscriptionid)
1985 this function either deletes one member from routing list if $routingid exists otherwise
1986 deletes all members from the routing list
1988 =cut
1990 sub delroutingmember {
1992 # if $routingid exists then deletes that row otherwise deletes all with $subscriptionid
1993 my ( $routingid, $subscriptionid ) = @_;
1994 my $dbh = C4::Context->dbh;
1995 if ($routingid) {
1996 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE routingid = ?");
1997 $sth->execute($routingid);
1998 reorder_members( $subscriptionid, $routingid );
1999 } else {
2000 my $sth = $dbh->prepare("DELETE FROM subscriptionroutinglist WHERE subscriptionid = ?");
2001 $sth->execute($subscriptionid);
2003 return;
2006 =head2 getroutinglist
2008 @routinglist = getroutinglist($subscriptionid)
2010 this gets the info from the subscriptionroutinglist for $subscriptionid
2012 return :
2013 the routinglist as an array. Each element of the array contains a hash_ref containing
2014 routingid - a unique id, borrowernumber, ranking, and biblionumber of subscription
2016 =cut
2018 sub getroutinglist {
2019 my ($subscriptionid) = @_;
2020 my $dbh = C4::Context->dbh;
2021 my $sth = $dbh->prepare(
2022 'SELECT routingid, borrowernumber, ranking, biblionumber
2023 FROM subscription
2024 JOIN subscriptionroutinglist ON subscription.subscriptionid = subscriptionroutinglist.subscriptionid
2025 WHERE subscription.subscriptionid = ? ORDER BY ranking ASC'
2027 $sth->execute($subscriptionid);
2028 my $routinglist = $sth->fetchall_arrayref({});
2029 return @{$routinglist};
2032 =head2 countissuesfrom
2034 $result = countissuesfrom($subscriptionid,$startdate)
2036 Returns a count of serial rows matching the given subsctiptionid
2037 with published date greater than startdate
2039 =cut
2041 sub countissuesfrom {
2042 my ( $subscriptionid, $startdate ) = @_;
2043 my $dbh = C4::Context->dbh;
2044 my $query = qq|
2045 SELECT count(*)
2046 FROM serial
2047 WHERE subscriptionid=?
2048 AND serial.publisheddate>?
2050 my $sth = $dbh->prepare($query);
2051 $sth->execute( $subscriptionid, $startdate );
2052 my ($countreceived) = $sth->fetchrow;
2053 return $countreceived;
2056 =head2 CountIssues
2058 $result = CountIssues($subscriptionid)
2060 Returns a count of serial rows matching the given subsctiptionid
2062 =cut
2064 sub CountIssues {
2065 my ($subscriptionid) = @_;
2066 my $dbh = C4::Context->dbh;
2067 my $query = qq|
2068 SELECT count(*)
2069 FROM serial
2070 WHERE subscriptionid=?
2072 my $sth = $dbh->prepare($query);
2073 $sth->execute($subscriptionid);
2074 my ($countreceived) = $sth->fetchrow;
2075 return $countreceived;
2078 =head2 HasItems
2080 $result = HasItems($subscriptionid)
2082 returns a count of items from serial matching the subscriptionid
2084 =cut
2086 sub HasItems {
2087 my ($subscriptionid) = @_;
2088 my $dbh = C4::Context->dbh;
2089 my $query = q|
2090 SELECT COUNT(serialitems.itemnumber)
2091 FROM serial
2092 LEFT JOIN serialitems USING(serialid)
2093 WHERE subscriptionid=? AND serialitems.serialid IS NOT NULL
2095 my $sth=$dbh->prepare($query);
2096 $sth->execute($subscriptionid);
2097 my ($countitems)=$sth->fetchrow_array();
2098 return $countitems;
2101 =head2 abouttoexpire
2103 $result = abouttoexpire($subscriptionid)
2105 this function alerts you to the penultimate issue for a serial subscription
2107 returns 1 - if this is the penultimate issue
2108 returns 0 - if not
2110 =cut
2112 sub abouttoexpire {
2113 my ($subscriptionid) = @_;
2114 my $dbh = C4::Context->dbh;
2115 my $subscription = GetSubscription($subscriptionid);
2116 my $per = $subscription->{'periodicity'};
2117 my $frequency = C4::Serials::Frequency::GetSubscriptionFrequency($per);
2118 if ($frequency and $frequency->{unit}){
2120 my $expirationdate = GetExpirationDate($subscriptionid);
2122 my ($res) = $dbh->selectrow_array('select max(planneddate) from serial where subscriptionid = ?', undef, $subscriptionid);
2123 my $nextdate = GetNextDate($subscription, $res, $frequency);
2125 # only compare dates if both dates exist.
2126 if ($nextdate and $expirationdate) {
2127 if(Date::Calc::Delta_Days(
2128 split( /-/, $nextdate ),
2129 split( /-/, $expirationdate )
2130 ) <= 0) {
2131 return 1;
2135 } elsif ($subscription->{numberlength}>0) {
2136 return (countissuesfrom($subscriptionid,$subscription->{'startdate'}) >=$subscription->{numberlength}-1);
2139 return 0;
2142 =head2 GetFictiveIssueNumber
2144 $issueno = GetFictiveIssueNumber($subscription, $publishedate, $frequency);
2146 Get the position of the issue published at $publisheddate, considering the
2147 first issue (at firstacquidate) is at position 1, the next is at position 2, etc...
2148 This issuenumber doesn't take into account irregularities, so, for instance, if the 3rd
2149 issue is declared as 'irregular' (will be skipped at receipt), the next issue number
2150 will be 4, not 3. It's why it is called 'fictive'. It is NOT a serial seq, and is not
2151 depending on how many rows are in serial table.
2152 The issue number calculation is based on subscription frequency, first acquisition
2153 date, and $publisheddate.
2155 Returns undef when called for irregular frequencies.
2157 The routine is used to skip irregularities when calculating the next issue
2158 date (in GetNextDate) or the next issue number (in GetNextSeq).
2160 =cut
2162 sub GetFictiveIssueNumber {
2163 my ($subscription, $publisheddate, $frequency) = @_;
2165 my $unit = $frequency->{unit} ? lc $frequency->{'unit'} : undef;
2166 return if !$unit;
2167 my $issueno;
2169 my ( $year, $month, $day ) = split /-/, $publisheddate;
2170 my ( $fa_year, $fa_month, $fa_day ) = split /-/, $subscription->{'firstacquidate'};
2171 my $delta = _delta_units( [$fa_year, $fa_month, $fa_day], [$year, $month, $day], $unit );
2173 if( $frequency->{'unitsperissue'} == 1 ) {
2174 $issueno = $delta * $frequency->{'issuesperunit'} + $subscription->{'countissuesperunit'};
2175 } else { # issuesperunit == 1
2176 $issueno = 1 + int( $delta / $frequency->{'unitsperissue'} );
2178 return $issueno;
2181 sub _delta_units {
2182 my ( $date1, $date2, $unit ) = @_;
2183 # date1 and date2 are array refs in the form [ yy, mm, dd ]
2185 if( $unit eq 'day' ) {
2186 return Delta_Days( @$date1, @$date2 );
2187 } elsif( $unit eq 'week' ) {
2188 return int( Delta_Days( @$date1, @$date2 ) / 7 );
2191 # In case of months or years, this is a wrapper around N_Delta_YMD.
2192 # Note that N_Delta_YMD returns 29 days between e.g. 22-2-72 and 22-3-72
2193 # while we expect 1 month.
2194 my @delta = N_Delta_YMD( @$date1, @$date2 );
2195 if( $delta[2] > 27 ) {
2196 # Check if we could add a month
2197 my @jump = Add_Delta_YM( @$date1, $delta[0], 1 + $delta[1] );
2198 if( Delta_Days( @jump, @$date2 ) >= 0 ) {
2199 $delta[1]++;
2202 if( $delta[1] >= 12 ) {
2203 $delta[0]++;
2204 $delta[1] -= 12;
2206 # if unit is year, we only return full years
2207 return $unit eq 'month' ? $delta[0] * 12 + $delta[1] : $delta[0];
2210 sub _get_next_date_day {
2211 my ($subscription, $freqdata, $year, $month, $day) = @_;
2213 my @newissue; # ( yy, mm, dd )
2214 # We do not need $delta_days here, since it would be zero where used
2216 if( $freqdata->{issuesperunit} == 1 ) {
2217 # Add full days
2218 @newissue = Add_Delta_Days(
2219 $year, $month, $day, $freqdata->{"unitsperissue"} );
2220 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2221 # Add zero days
2222 @newissue = ( $year, $month, $day );
2223 $subscription->{countissuesperunit}++;
2224 } else {
2225 # We finished a cycle of issues within a unit.
2226 # No subtraction of zero needed, just add one day
2227 @newissue = Add_Delta_Days( $year, $month, $day, 1 );
2228 $subscription->{countissuesperunit} = 1;
2230 return @newissue;
2233 sub _get_next_date_week {
2234 my ($subscription, $freqdata, $year, $month, $day) = @_;
2236 my @newissue; # ( yy, mm, dd )
2237 my $delta_days = int( 7 / $freqdata->{issuesperunit} );
2239 if( $freqdata->{issuesperunit} == 1 ) {
2240 # Add full weeks (of 7 days)
2241 @newissue = Add_Delta_Days(
2242 $year, $month, $day, 7 * $freqdata->{"unitsperissue"} );
2243 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2244 # Add rounded number of days based on frequency.
2245 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2246 $subscription->{countissuesperunit}++;
2247 } else {
2248 # We finished a cycle of issues within a unit.
2249 # Subtract delta * (issues - 1), add 1 week
2250 @newissue = Add_Delta_Days( $year, $month, $day,
2251 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2252 @newissue = Add_Delta_Days( @newissue, 7 );
2253 $subscription->{countissuesperunit} = 1;
2255 return @newissue;
2258 sub _get_next_date_month {
2259 my ($subscription, $freqdata, $year, $month, $day) = @_;
2261 my @newissue; # ( yy, mm, dd )
2262 my $delta_days = int( 30 / $freqdata->{issuesperunit} );
2264 if( $freqdata->{issuesperunit} == 1 ) {
2265 # Add full months
2266 @newissue = Add_Delta_YM(
2267 $year, $month, $day, 0, $freqdata->{"unitsperissue"} );
2268 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2269 # Add rounded number of days based on frequency.
2270 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2271 $subscription->{countissuesperunit}++;
2272 } else {
2273 # We finished a cycle of issues within a unit.
2274 # Subtract delta * (issues - 1), add 1 month
2275 @newissue = Add_Delta_Days( $year, $month, $day,
2276 -$delta_days * ($freqdata->{issuesperunit} - 1) );
2277 @newissue = Add_Delta_YM( @newissue, 0, 1 );
2278 $subscription->{countissuesperunit} = 1;
2280 return @newissue;
2283 sub _get_next_date_year {
2284 my ($subscription, $freqdata, $year, $month, $day) = @_;
2286 my @newissue; # ( yy, mm, dd )
2287 my $delta_days = int( 365 / $freqdata->{issuesperunit} );
2289 if( $freqdata->{issuesperunit} == 1 ) {
2290 # Add full years
2291 @newissue = Add_Delta_YM( $year, $month, $day, $freqdata->{"unitsperissue"}, 0 );
2292 } elsif ( $subscription->{countissuesperunit} < $freqdata->{issuesperunit} ) {
2293 # Add rounded number of days based on frequency.
2294 @newissue = Add_Delta_Days( $year, $month, $day, $delta_days );
2295 $subscription->{countissuesperunit}++;
2296 } else {
2297 # We finished a cycle of issues within a unit.
2298 # Subtract delta * (issues - 1), add 1 year
2299 @newissue = Add_Delta_Days( $year, $month, $day, -$delta_days * ($freqdata->{issuesperunit} - 1) );
2300 @newissue = Add_Delta_YM( @newissue, 1, 0 );
2301 $subscription->{countissuesperunit} = 1;
2303 return @newissue;
2306 =head2 GetNextDate
2308 $resultdate = GetNextDate($publisheddate,$subscription,$freqdata,$updatecount)
2310 this function it takes the publisheddate and will return the next issue's date
2311 and will skip dates if there exists an irregularity.
2312 $publisheddate has to be an ISO date
2313 $subscription is a hashref containing at least 'firstacquidate', 'irregularity', and 'countissuesperunit'
2314 $frequency is a hashref containing frequency informations
2315 $updatecount is a boolean value which, when set to true, update the 'countissuesperunit' in database
2316 - eg if periodicity is monthly and $publisheddate is 2007-02-10 but if March and April is to be
2317 skipped then the returned date will be 2007-05-10
2319 return :
2320 $resultdate - then next date in the sequence (ISO date)
2322 Return undef if subscription is irregular
2324 =cut
2326 sub GetNextDate {
2327 my ( $subscription, $publisheddate, $freqdata, $updatecount ) = @_;
2329 return unless $subscription and $publisheddate;
2332 if ($freqdata->{'unit'}) {
2333 my ( $year, $month, $day ) = split /-/, $publisheddate;
2335 # Process an irregularity Hash
2336 # Suppose that irregularities are stored in a string with this structure
2337 # irreg1;irreg2;irreg3
2338 # where irregX is the number of issue which will not be received
2339 # (the first issue takes the number 1, the 2nd the number 2 and so on)
2340 my %irregularities;
2341 if ( $subscription->{irregularity} ) {
2342 my @irreg = split /;/, $subscription->{'irregularity'} ;
2343 foreach my $irregularity (@irreg) {
2344 $irregularities{$irregularity} = 1;
2348 # Get the 'fictive' next issue number
2349 # It is used to check if next issue is an irregular issue.
2350 my $issueno = GetFictiveIssueNumber($subscription, $publisheddate, $freqdata) + 1;
2352 # Then get the next date
2353 my $unit = lc $freqdata->{'unit'};
2354 if ($unit eq 'day') {
2355 while ($irregularities{$issueno}) {
2356 ($year, $month, $day) = _get_next_date_day($subscription,
2357 $freqdata, $year, $month, $day);
2358 $issueno++;
2360 ($year, $month, $day) = _get_next_date_day($subscription, $freqdata,
2361 $year, $month, $day);
2363 elsif ($unit eq 'week') {
2364 while ($irregularities{$issueno}) {
2365 ($year, $month, $day) = _get_next_date_week($subscription,
2366 $freqdata, $year, $month, $day);
2367 $issueno++;
2369 ($year, $month, $day) = _get_next_date_week($subscription,
2370 $freqdata, $year, $month, $day);
2372 elsif ($unit eq 'month') {
2373 while ($irregularities{$issueno}) {
2374 ($year, $month, $day) = _get_next_date_month($subscription,
2375 $freqdata, $year, $month, $day);
2376 $issueno++;
2378 ($year, $month, $day) = _get_next_date_month($subscription,
2379 $freqdata, $year, $month, $day);
2381 elsif ($unit eq 'year') {
2382 while ($irregularities{$issueno}) {
2383 ($year, $month, $day) = _get_next_date_year($subscription,
2384 $freqdata, $year, $month, $day);
2385 $issueno++;
2387 ($year, $month, $day) = _get_next_date_year($subscription,
2388 $freqdata, $year, $month, $day);
2391 if ($updatecount){
2392 my $dbh = C4::Context->dbh;
2393 my $query = qq{
2394 UPDATE subscription
2395 SET countissuesperunit = ?
2396 WHERE subscriptionid = ?
2398 my $sth = $dbh->prepare($query);
2399 $sth->execute($subscription->{'countissuesperunit'}, $subscription->{'subscriptionid'});
2402 return sprintf("%04d-%02d-%02d", $year, $month, $day);
2406 =head2 _numeration
2408 $string = &_numeration($value,$num_type,$locale);
2410 _numeration returns the string corresponding to $value in the num_type
2411 num_type can take :
2412 -dayname
2413 -dayabrv
2414 -monthname
2415 -monthabrv
2416 -season
2417 -seasonabrv
2419 =cut
2421 sub _numeration {
2422 my ($value, $num_type, $locale) = @_;
2423 $value ||= 0;
2424 $num_type //= '';
2425 $locale ||= 'en';
2426 my $string;
2427 if ( $num_type =~ /^dayname$/ or $num_type =~ /^dayabrv$/ ) {
2428 # 1970-11-01 was a Sunday
2429 $value = $value % 7;
2430 my $dt = DateTime->new(
2431 year => 1970,
2432 month => 11,
2433 day => $value + 1,
2434 locale => $locale,
2436 $string = $num_type =~ /^dayname$/
2437 ? $dt->strftime("%A")
2438 : $dt->strftime("%a");
2439 } elsif ( $num_type =~ /^monthname$/ or $num_type =~ /^monthabrv$/ ) {
2440 $value = $value % 12;
2441 my $dt = DateTime->new(
2442 year => 1970,
2443 month => $value + 1,
2444 locale => $locale,
2446 $string = $num_type =~ /^monthname$/
2447 ? $dt->strftime("%B")
2448 : $dt->strftime("%b");
2449 } elsif ( $num_type =~ /^season$/ ) {
2450 my @seasons= qw( Spring Summer Fall Winter );
2451 $value = $value % 4;
2452 $string = $seasons[$value];
2453 } elsif ( $num_type =~ /^seasonabrv$/ ) {
2454 my @seasonsabrv= qw( Spr Sum Fal Win );
2455 $value = $value % 4;
2456 $string = $seasonsabrv[$value];
2457 } else {
2458 $string = $value;
2461 return $string;
2464 =head2 CloseSubscription
2466 Close a subscription given a subscriptionid
2468 =cut
2470 sub CloseSubscription {
2471 my ( $subscriptionid ) = @_;
2472 return unless $subscriptionid;
2473 my $dbh = C4::Context->dbh;
2474 my $sth = $dbh->prepare( q{
2475 UPDATE subscription
2476 SET closed = 1
2477 WHERE subscriptionid = ?
2478 } );
2479 $sth->execute( $subscriptionid );
2481 # Set status = missing when status = stopped
2482 $sth = $dbh->prepare( q{
2483 UPDATE serial
2484 SET status = ?
2485 WHERE subscriptionid = ?
2486 AND status = ?
2487 } );
2488 $sth->execute( STOPPED, $subscriptionid, EXPECTED );
2491 =head2 ReopenSubscription
2493 Reopen a subscription given a subscriptionid
2495 =cut
2497 sub ReopenSubscription {
2498 my ( $subscriptionid ) = @_;
2499 return unless $subscriptionid;
2500 my $dbh = C4::Context->dbh;
2501 my $sth = $dbh->prepare( q{
2502 UPDATE subscription
2503 SET closed = 0
2504 WHERE subscriptionid = ?
2505 } );
2506 $sth->execute( $subscriptionid );
2508 # Set status = expected when status = stopped
2509 $sth = $dbh->prepare( q{
2510 UPDATE serial
2511 SET status = ?
2512 WHERE subscriptionid = ?
2513 AND status = ?
2514 } );
2515 $sth->execute( EXPECTED, $subscriptionid, STOPPED );
2518 =head2 subscriptionCurrentlyOnOrder
2520 $bool = subscriptionCurrentlyOnOrder( $subscriptionid );
2522 Return 1 if subscription is currently on order else 0.
2524 =cut
2526 sub subscriptionCurrentlyOnOrder {
2527 my ( $subscriptionid ) = @_;
2528 my $dbh = C4::Context->dbh;
2529 my $query = qq|
2530 SELECT COUNT(*) FROM aqorders
2531 WHERE subscriptionid = ?
2532 AND datereceived IS NULL
2533 AND datecancellationprinted IS NULL
2535 my $sth = $dbh->prepare( $query );
2536 $sth->execute($subscriptionid);
2537 return $sth->fetchrow_array;
2540 =head2 can_claim_subscription
2542 $can = can_claim_subscription( $subscriptionid[, $userid] );
2544 Return 1 if the subscription can be claimed by the current logged user (or a given $userid), else 0.
2546 =cut
2548 sub can_claim_subscription {
2549 my ( $subscription, $userid ) = @_;
2550 return _can_do_on_subscription( $subscription, $userid, 'claim_serials' );
2553 =head2 can_edit_subscription
2555 $can = can_edit_subscription( $subscriptionid[, $userid] );
2557 Return 1 if the subscription can be edited by the current logged user (or a given $userid), else 0.
2559 =cut
2561 sub can_edit_subscription {
2562 my ( $subscription, $userid ) = @_;
2563 return _can_do_on_subscription( $subscription, $userid, 'edit_subscription' );
2566 =head2 can_show_subscription
2568 $can = can_show_subscription( $subscriptionid[, $userid] );
2570 Return 1 if the subscription can be shown by the current logged user (or a given $userid), else 0.
2572 =cut
2574 sub can_show_subscription {
2575 my ( $subscription, $userid ) = @_;
2576 return _can_do_on_subscription( $subscription, $userid, '*' );
2579 sub _can_do_on_subscription {
2580 my ( $subscription, $userid, $permission ) = @_;
2581 return 0 unless C4::Context->userenv;
2582 my $flags = C4::Context->userenv->{flags};
2583 $userid ||= C4::Context->userenv->{'id'};
2585 if ( C4::Context->preference('IndependentBranches') ) {
2586 return 1
2587 if C4::Context->IsSuperLibrarian()
2589 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2590 or (
2591 C4::Auth::haspermission( $userid,
2592 { serials => $permission } )
2593 and ( not defined $subscription->{branchcode}
2594 or $subscription->{branchcode} eq ''
2595 or $subscription->{branchcode} eq
2596 C4::Context->userenv->{'branch'} )
2599 else {
2600 return 1
2601 if C4::Context->IsSuperLibrarian()
2603 C4::Auth::haspermission( $userid, { serials => 'superserials' } )
2604 or C4::Auth::haspermission(
2605 $userid, { serials => $permission }
2609 return 0;
2612 =head2 findSerialsByStatus
2614 @serials = findSerialsByStatus($status, $subscriptionid);
2616 Returns an array of serials matching a given status and subscription id.
2618 =cut
2620 sub findSerialsByStatus {
2621 my ( $status, $subscriptionid ) = @_;
2622 my $dbh = C4::Context->dbh;
2623 my $query = q| SELECT * from serial
2624 WHERE status = ?
2625 AND subscriptionid = ?
2627 my $serials = $dbh->selectall_arrayref( $query, { Slice => {} }, $status, $subscriptionid );
2628 return @$serials;
2632 __END__
2634 =head1 AUTHOR
2636 Koha Development Team <http://koha-community.org/>
2638 =cut