From 188feb121149c49072440a3fc779a8a126afae39 Mon Sep 17 00:00:00 2001 From: Alex Arnaud Date: Thu, 26 Nov 2015 11:00:22 +0100 Subject: [PATCH] Bug 15261: Verify if checkouts/reserves requests periods overlap... MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ... with existing reserves When checking out or placing hold, we should check if an existing reserve whose period overlap exists. A user place an hold from opac whose requested period overlap an existing reserve period => prevent reserve, A librarian place an hold from staff whose requested period overlap an existing reserve period => Warn librarian (Ask for confirmation), A librarian make a checkout from staff whose requested period overlap an existing reserve period => Warn librarian (Ask for confirmation). Test plan: Enable syspref: AllowHoldDateInFuture OPACAllowHoldDateInFuture PreventChechoutOnSameReservePeriod and PreventReservesOnSamePeriod 1 (staff side): Place a hold on title (which has only one items) level with start date and expiration date. Place another hold (also title level) with period overlaping this reserve. Check you are warned about an existing reserve 2 (staff side): Place a hold on title (which has more than one items) level with start date and expiration date. Place another hold (also title level) with period overlaping this reserve. Check you are NOT warned about an existing reserve. Because it remains at least one item not reserved. 3 (staff side): Place a hold on item level with start date and expiration date. Place another hold on item level with period overlaping this reserve. Check you are warned about an existing reserve. 4 (opac side): Do the same than for staff side. Instead of a warn, reserve is prevented. 5: Place a hold on title (which has only one items) level with start date and expiration date. Try to checkout the unique item from this title with period overlaping the reserve period. Check you are warned about an existing reserve 6: Place a hold on title (which has more than one items) level with start date and expiration date. Checkout an item from this title with period overlaping the reserve period. Check you are NOT warned about an existing reserve. 7: Place a hold on item level with start date and expiration date. Checkout this item period overlaping the reserve period. Check you are warned about an existing reserve Rebased on master (2016-03-11) Rebased on master (2016-06-23) Rebased on master (2017-03-23) Rebased on master (2018-03-15) Rebased on master (2019-04-18) Signed-off-by: Séverine QUEUNE Rebased-by: Alex Arnaud --- C4/Circulation.pm | 23 +++ C4/Reserves.pm | 48 +++++ Koha/DateUtils.pm | 44 ++++- circ/circulation.pl | 4 + ...entchechoutonsamereserveperiod_syspref.sql | 1 + ...dd_preventreservesonsameperiod_syspref.sql | 1 + installer/data/mysql/sysprefs.sql | 4 +- .../admin/preferences/circulation.pref | 12 ++ .../prog/en/modules/circ/circulation.tt | 2 + .../prog/en/modules/reserve/placerequest.tt | 66 +++++++ opac/opac-reserve.pl | 9 + reserve/placerequest.pl | 165 ++++++++++-------- t/db_dependent/Circulation/CanBookBeIssued.t | 106 +++++++++++ t/db_dependent/Reserves/ReserveDate.t | 108 ++++++++++++ 14 files changed, 519 insertions(+), 74 deletions(-) create mode 100644 installer/data/mysql/atomicupdate/bug_15261-add_preventchechoutonsamereserveperiod_syspref.sql create mode 100644 installer/data/mysql/atomicupdate/bug_15261-add_preventreservesonsameperiod_syspref.sql create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/reserve/placerequest.tt create mode 100644 t/db_dependent/Circulation/CanBookBeIssued.t create mode 100644 t/db_dependent/Reserves/ReserveDate.t diff --git a/C4/Circulation.pm b/C4/Circulation.pm index fb798c8ba9..6afc851da0 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -1032,6 +1032,7 @@ sub CanBookBeIssued { $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber; $needsconfirmation{'resbranchcode'} = $res->{branchcode}; $needsconfirmation{'reswaitingdate'} = $res->{'waitingdate'}; + $needsconfirmation{resreserveid} = $res->{reserve_id}; } elsif ( $restype eq "Reserved" ) { # The item is on reserve for someone else. @@ -1042,9 +1043,31 @@ sub CanBookBeIssued { $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber; $needsconfirmation{'resbranchcode'} = $patron->branchcode; $needsconfirmation{'resreservedate'} = $res->{reservedate}; + $needsconfirmation{resreserveid} = $res->{reserve_id}; } } } + + my $now = dt_from_string(); + my $preventCheckoutOnSameReservePeriod = + C4::Context->preference("PreventCheckoutOnSameReservePeriod"); + my $reserves_on_same_period = + ReservesOnSamePeriod($item_object->biblionumber, $item_object->itemnumber, $now->ymd, $duedate->ymd); + if ($preventCheckoutOnSameReservePeriod && $reserves_on_same_period) { + my $reserve = $reserves_on_same_period->[0]; + my $patron = Koha::Patrons->find( $reserve->{borrowernumber} ); + my $branchname = Koha::Libraries->find($reserve->{branchcode})->branchname; + + $needsconfirmation{RESERVED} = 1; + $needsconfirmation{resfirstname} = $patron->firstname; + $needsconfirmation{ressurname} = $patron->surname; + $needsconfirmation{rescardnumber} = $patron->cardnumber; + $needsconfirmation{resborrowernumber} = $patron->borrowernumber; + $needsconfirmation{resbranchname} = $branchname; + $needsconfirmation{resreservedate} = $reserve->{reservedate}; + $needsconfirmation{resreserveid} = $reserve->{reserve_id}; + } + } ## CHECK AGE RESTRICTION diff --git a/C4/Reserves.pm b/C4/Reserves.pm index 2a77c3dc5b..d0643d9885 100644 --- a/C4/Reserves.pm +++ b/C4/Reserves.pm @@ -133,6 +133,7 @@ BEGIN { &SuspendAll &GetReservesControlBranch + &ReservesOnSamePeriod IsItemOnHoldAndFound @@ -2174,6 +2175,53 @@ sub GetHoldRule { return $sth->fetchrow_hashref(); } +=head2 ReservesOnSamePeriod + + my $reserve = ReservesOnSamePeriod( $biblionumber, $itemnumber, $resdate, $expdate); + + Return the reserve that match the period ($resdate => $expdate), + undef if no reserve match. + +=cut + +sub ReservesOnSamePeriod { + my ($biblionumber, $itemnumber, $resdate, $expdate) = @_; + + unless ($resdate && $expdate) { + return; + } + + my @reserves = Koha::Holds->search({ biblionumber => $biblionumber }); + + $resdate = output_pref({ str => $resdate, dateonly => 1, dateformat => 'iso' }); + $expdate = output_pref({ str => $expdate, dateonly => 1, dateformat => 'iso' }); + + my @reserves_overlaps; + foreach my $reserve ( @reserves ) { + + unless ($reserve->reservedate && $reserve->expirationdate) { + next; + } + + if (date_ranges_overlap($resdate, $expdate, + $reserve->reservedate, + $reserve->expirationdate)) { + + # If reserve is item level and the requested periods overlap. + if ($itemnumber && $reserve->itemnumber == $itemnumber ) { + return [$reserve->unblessed]; + } + push @reserves_overlaps, $reserve->unblessed; + } + } + + if ( @reserves_overlaps >= Koha::Items->search({ biblionumber => $biblionumber })->count() ) { + return \@reserves_overlaps; + } + + return; +} + =head1 AUTHOR Koha Development Team diff --git a/Koha/DateUtils.pm b/Koha/DateUtils.pm index 459a5a89d7..04dd01fc77 100644 --- a/Koha/DateUtils.pm +++ b/Koha/DateUtils.pm @@ -24,7 +24,7 @@ use Koha::Exceptions; use base 'Exporter'; our @EXPORT = ( - qw( dt_from_string output_pref format_sqldatetime ) + qw( dt_from_string output_pref format_sqldatetime date_ranges_overlap ) ); =head1 DateUtils @@ -322,4 +322,46 @@ sub format_sqldatetime { return q{}; } +=head2 date_ranges_overlap + + $bool = date_ranges_overlap($start1, $end1, $start2, $end2); + + Tells if first range ($start1 => $end1) overlaps + the second one ($start2 => $end2) + +=cut + +sub date_ranges_overlap { + my ($start1, $end1, $start2, $end2) = @_; + + $start1 = dt_from_string( $start1, 'iso' ); + $end1 = dt_from_string( $end1, 'iso' ); + $start2 = dt_from_string( $start2, 'iso' ); + $end2 = dt_from_string( $end2, 'iso' ); + + if ( + # Start of range 2 is in the range 1. + ( + DateTime->compare($start2, $start1) >= 0 + && DateTime->compare($start2, $end1) <= 0 + ) + || + # End of range 2 is in the range 1. + ( + DateTime->compare($end2, $start1) >= 0 + && DateTime->compare($end2, $end1) <= 0 + ) + || + # Range 2 start before and end after range 1. + ( + DateTime->compare($start2, $start1) < 0 + && DateTime->compare($end2, $end1) > 0 + ) + ) { + return 1; + } + + return; +} + 1; diff --git a/circ/circulation.pl b/circ/circulation.pl index 74c42d8c04..73a14ff05f 100755 --- a/circ/circulation.pl +++ b/circ/circulation.pl @@ -430,6 +430,10 @@ if (@$barcodes) { } unless($confirm_required) { my $switch_onsite_checkout = exists $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED}; + if ( $cancelreserve eq 'cancel' ) { + CancelReserve({ reserve_id => $query->param('reserveid') }); + } + $cancelreserve = $cancelreserve eq 'revert' ? 'revert' : undef; my $issue = AddIssue( $patron->unblessed, $barcode, $datedue, $cancelreserve, undef, undef, { onsite_checkout => $onsite_checkout, auto_renew => $session->param('auto_renew'), switch_onsite_checkout => $switch_onsite_checkout, } ); $template_params->{issue} = $issue; $session->clear('auto_renew'); diff --git a/installer/data/mysql/atomicupdate/bug_15261-add_preventchechoutonsamereserveperiod_syspref.sql b/installer/data/mysql/atomicupdate/bug_15261-add_preventchechoutonsamereserveperiod_syspref.sql new file mode 100644 index 0000000000..d91ae3f5b0 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_15261-add_preventchechoutonsamereserveperiod_syspref.sql @@ -0,0 +1 @@ +INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES ('PreventCheckoutOnSameReservePeriod', '0', 'Prevent to checkout a document if a reserve on same period exists', NULL, 'YesNo'); \ No newline at end of file diff --git a/installer/data/mysql/atomicupdate/bug_15261-add_preventreservesonsameperiod_syspref.sql b/installer/data/mysql/atomicupdate/bug_15261-add_preventreservesonsameperiod_syspref.sql new file mode 100644 index 0000000000..6f694865f5 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_15261-add_preventreservesonsameperiod_syspref.sql @@ -0,0 +1 @@ +INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES ('PreventReservesOnSamePeriod', '0', 'Prevent to hold a document if a reserve on same period exists', NULL, 'YesNo'); \ No newline at end of file diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql index 82a8402a36..1f33770412 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -665,5 +665,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('XSLTListsDisplay','default','','Enable XSLT stylesheet control over lists pages display on intranet','Free'), ('XSLTResultsDisplay','default','','Enable XSL stylesheet control over results page display on intranet','Free'), ('z3950AuthorAuthFields','701,702,700',NULL,'Define the MARC biblio fields for Personal Name Authorities to fill biblio.author','free'), -('z3950NormalizeAuthor','0','','If ON, Personal Name Authorities will replace authors in biblio.author','YesNo') +('z3950NormalizeAuthor','0','','If ON, Personal Name Authorities will replace authors in biblio.author','YesNo'), +('PreventCheckoutOnSameReservePeriod','0','','Prevent to checkout a document if a reserve on same period exists','YesNo'), +('PreventReservesOnSamePeriod','0','','Prevent to hold a document if a reserve on same period exists','YesNo') ; diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref index 199809f139..64940bcae3 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref @@ -498,6 +498,12 @@ Circulation: - "
ccode: [NEWFIC,NULL,DVD]" - "
itype: [NEWBK,\"\"]" - "
Note: the word 'NULL' can be used to block renewal on undefined fields, while an empty string \"\" will block on an empty (but defined) field." + - + - pref: PreventCheckoutOnSameReservePeriod + choices: + yes: Do + no: "Don't" + - If yes, checkouts periods can't overlap with a reserve period. Checkin Policy: - - pref: HoldsAutoFill @@ -801,6 +807,12 @@ Circulation: - pref: UpdateItemWhenLostFromHoldList type: textarea syntax: text/x-yaml + - + - pref: PreventReservesOnSamePeriod + choices: + yes: Do + no: "Don't" + - If yes, Reserves periods for the same document can't overlap. Interlibrary Loans: - - pref: ILLModule diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt index 5fb4889bed..63306406bd 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt @@ -213,6 +213,7 @@ [% IF ( RESERVED ) %]

+

[% END %] @@ -221,6 +222,7 @@


+

diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/placerequest.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/placerequest.tt new file mode 100644 index 0000000000..907cc1e66c --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/placerequest.tt @@ -0,0 +1,66 @@ +[% INCLUDE 'doc-head-open.inc' %] + Koha › Circulation › Holds › Confirm holds +[% INCLUDE 'doc-head-close.inc' %] + + +[% INCLUDE 'header.inc' %] +[% INCLUDE 'circ-search.inc' %] + + + +
+ +
+
+
+ +

Confirm holds

+ +
+

+ Some of the reserves you are trying to do overlaps with existing reserves. + Please confirm you want to proceed. +

+
+ +
+ + [% IF multi_hold %] + + + [% ELSE %] + + [% END %] + + + + + + + [% FOREACH biblionumber IN overlap_reserves.keys %] + [% input_id = "confirm_$biblionumber" %] +
+ + [% IF (overlap_reserves.$biblionumber.checkitem) %] + + [% END %] + + +
+ [% END %] + + +
+ +
+
+ +
+[% INCLUDE 'intranet-bottom.inc' %] diff --git a/opac/opac-reserve.pl b/opac/opac-reserve.pl index 56c5bc2695..15629426da 100755 --- a/opac/opac-reserve.pl +++ b/opac/opac-reserve.pl @@ -282,6 +282,15 @@ if ( $query->param('place_reserve') ) { # Inserts a null into the 'itemnumber' field of 'reserves' table. $itemNum = undef; } + + if ($canreserve) { + if (C4::Context->preference("PreventReservesOnSamePeriod") && + ReservesOnSamePeriod($biblioNum, $itemNum, $startdate, $expiration_date)) { + $canreserve = 0; + $failed_holds++; + } + } + my $notes = $query->param('notes_'.$biblioNum)||''; if ( $maxreserves diff --git a/reserve/placerequest.pl b/reserve/placerequest.pl index 89c591d55b..b54562d464 100755 --- a/reserve/placerequest.pl +++ b/reserve/placerequest.pl @@ -30,99 +30,127 @@ use C4::Output; use C4::Reserves; use C4::Circulation; use C4::Members; -use C4::Auth qw/checkauth/; +use C4::Auth; use Koha::Items; use Koha::Patrons; my $input = CGI->new(); -checkauth($input, 0, { reserveforothers => 'place_holds' }, 'intranet'); - -my @bibitems = $input->multi_param('biblioitem'); -my @reqbib = $input->multi_param('reqbib'); -my $biblionumber = $input->param('biblionumber'); -my $borrowernumber = $input->param('borrowernumber'); -my $notes = $input->param('notes'); -my $branch = $input->param('pickup'); -my $startdate = $input->param('reserve_date') || ''; -my @rank = $input->multi_param('rank-request'); -my $type = $input->param('type'); -my $title = $input->param('title'); -my $checkitem = $input->param('checkitem'); +my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user( + { + template_name => "reserve/placerequest.tt", + query => $input, + type => "intranet", + authnotrequired => 0, + flagsrequired => { reserveforothers => 'place_holds' }, + } +); + +my $biblionumber=$input->param('biblionumber'); +my $borrowernumber=$input->param('borrowernumber'); +my $notes=$input->param('notes'); +my $branch=$input->param('pickup'); +my $startdate=$input->param('reserve_date') || ''; +my @rank=$input->param('rank-request'); +my $title=$input->param('title'); +my $checkitem=$input->param('checkitem'); my $expirationdate = $input->param('expiration_date'); my $itemtype = $input->param('itemtype') || undef; - -my $borrower = Koha::Patrons->find( $borrowernumber ); -$borrower = $borrower->unblessed if $borrower; +my $confirm = $input->param('confirm'); +my @confirm_biblionumbers = $input->param('confirm_biblionumbers'); my $multi_hold = $input->param('multi_hold'); my $biblionumbers = $multi_hold ? $input->param('biblionumbers') : ($biblionumber . '/'); my $bad_bibs = $input->param('bad_bibs'); my $holds_to_place_count = $input->param('holds_to_place_count') || 1; +my $borrower = Koha::Patrons->find( $borrowernumber ); +$borrower = $borrower->unblessed if $borrower; +unless ($borrower) { + print $input->header(); + print "Invalid borrower number please try again"; + exit; +} + my %bibinfos = (); my @biblionumbers = split '/', $biblionumbers; foreach my $bibnum (@biblionumbers) { - my %bibinfo = (); + my %bibinfo; $bibinfo{title} = $input->param("title_$bibnum"); + $bibinfo{rank} = $input->param("rank_$bibnum"); $bibinfos{$bibnum} = \%bibinfo; } my $found; -if ( $type eq 'str8' && $borrower ) { - - foreach my $biblionumber ( keys %bibinfos ) { - my $count = @bibitems; - @bibitems = sort @bibitems; - my $i2 = 1; - my @realbi; - $realbi[0] = $bibitems[0]; - for ( my $i = 1 ; $i < $count ; $i++ ) { - my $i3 = $i2 - 1; - if ( $realbi[$i3] ne $bibitems[$i] ) { - $realbi[$i2] = $bibitems[$i]; - $i2++; - } +my $overlap_reserves = {}; +foreach my $biblionumber (keys %bibinfos) { + next if ($confirm && !grep { $_ eq $biblionumber } @confirm_biblionumbers); + + my ($reserve_title, $reserve_rank); + + my $can_override = C4::Context->preference('AllowHoldPolicyOverride'); + if (defined $checkitem && $checkitem ne '') { + + my $item = Koha::Items->find($checkitem); + + if ($item->biblionumber ne $biblionumber) { + $biblionumber = $item->biblionumber; } - my $can_override = C4::Context->preference('AllowHoldPolicyOverride'); - if ( defined $checkitem && $checkitem ne '' ) { - - my $item = Koha::Items->find($checkitem); - - if ( $item->biblionumber ne $biblionumber ) { - $biblionumber = $item->biblionumber; - } - - my $can_item_be_reserved = CanItemBeReserved($borrower->{'borrowernumber'}, $item->itemnumber, $branch)->{status}; - - if ( $can_item_be_reserved eq 'OK' || ( $can_item_be_reserved ne 'itemAlreadyOnHold' && $can_override ) ) { - AddReserve( $branch, $borrower->{'borrowernumber'}, - $biblionumber, \@realbi, $rank[0], $startdate, $expirationdate, $notes, $title, - $checkitem, $found, $itemtype ); - } - - } elsif ($multi_hold) { - my $bibinfo = $bibinfos{$biblionumber}; - if ( $can_override || CanBookBeReserved($borrower->{'borrowernumber'}, $biblionumber)->{status} eq 'OK' ) { - AddReserve($branch,$borrower->{'borrowernumber'},$biblionumber,[$biblionumber], - $bibinfo->{rank},$startdate,$expirationdate,$notes,$bibinfo->{title},$checkitem,$found); - } - } else { - # place a request on 1st available - for ( my $i = 0 ; $i < $holds_to_place_count ; $i++ ) { - if ( $can_override || CanBookBeReserved($borrower->{'borrowernumber'}, $biblionumber)->{status} eq 'OK' ) { - AddReserve( $branch, $borrower->{'borrowernumber'}, - $biblionumber, \@realbi, $rank[0], $startdate, $expirationdate, $notes, $title, - $checkitem, $found, $itemtype ); - } - } + my $can_item_be_reserved = CanItemBeReserved($borrower->{'borrowernumber'}, $item->itemnumber, $branch)->{status}; + + if ( $can_item_be_reserved eq 'OK' || ( $can_item_be_reserved ne 'itemAlreadyOnHold' && $can_override ) ) { + AddReserve( $branch, $borrower->{'borrowernumber'}, + $biblionumber, \@realbi, $rank[0], $startdate, $expirationdate, $notes, $title, + $checkitem, $found, $itemtype ); } + + } elsif ($multi_hold) { + my $bibinfo = $bibinfos{$biblionumber}; + $reserve_rank = $bibinfo->{rank}; + $reserve_title = $bibinfo->{title}; + } else { + $reserve_rank = $rank[0]; + $reserve_title = $title; } + if (!$confirm && + ReservesOnSamePeriod($biblionumber, $checkitem, $startdate, $expirationdate) && + C4::Context->preference("PreventReservesOnSamePeriod")) { + $overlap_reserves->{$biblionumber} = { + title => $reserve_title , + checkitem => $checkitem, + rank => $reserve_rank + }; + next; + } + + if ( $can_override || CanBookBeReserved($borrower->{borrowernumber}, $biblionumber)->{status} eq 'OK' ) { + AddReserve($branch, $borrower->{'borrowernumber'}, $biblionumber, undef, + $reserve_rank, $startdate, $expirationdate, $notes, $reserve_title, + $checkitem, $found); + } +} + +if (scalar keys %$overlap_reserves) { + $template->param( + borrowernumber => $borrowernumber, + biblionumbers => $biblionumbers, + biblionumber => $biblionumber, + overlap_reserves => $overlap_reserves, + reserve_date => $startdate, + expiration_date => $expirationdate, + notes => $notes, + rank_request => \@rank, + pickup => $branch, + multi_hold => $multi_hold, + ); + + output_html_with_http_headers $input, $cookie, $template->output; +} else { if ($multi_hold) { if ($bad_bibs) { $biblionumbers .= $bad_bibs; @@ -132,12 +160,5 @@ if ( $type eq 'str8' && $borrower ) { else { print $input->redirect("request.pl?biblionumber=$biblionumber"); } -} -elsif ( $borrowernumber eq '' ) { - print $input->header(); - print "Invalid borrower number please try again"; - - # Not sure that Dump() does HTML escaping. Use firebug or something to trace - # instead. - #print $input->Dump; + exit; } diff --git a/t/db_dependent/Circulation/CanBookBeIssued.t b/t/db_dependent/Circulation/CanBookBeIssued.t new file mode 100644 index 0000000000..fad19eea13 --- /dev/null +++ b/t/db_dependent/Circulation/CanBookBeIssued.t @@ -0,0 +1,106 @@ +#!/usr/bin/env perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Test::More tests => 1; +use C4::Members; +use C4::Reserves; +use C4::Circulation; +use Koha::DateUtils; + +use t::lib::TestBuilder; + +my $schema = Koha::Database->new->schema; +$schema->storage->txn_begin; + +my $builder = t::lib::TestBuilder->new(); + +subtest 'Tests for CanBookBeIssued with overlap reserves' => sub { + plan tests => 6; + + my $categorycode = $builder->build({ source => 'Category' })->{ categorycode }; + my $branch = $builder->build({ source => 'Branch' }); + my $branchcode = $branch->{branchcode}; + + my $borrower = $builder->build_object({ + class => 'Koha::Patrons', + value => { + branchcode => $branchcode, + categorycode => $categorycode, + } + }); + my $borrowernumber = $borrower->borrowernumber; + + my $biblio = $builder->build({source => 'Biblio'}); + my $biblioitem = $builder->build({ + source => 'Biblioitem', + value => { + biblionumber => $biblio->{biblionumber}, + }, + }); + my $item = $builder->build({ + source => 'Item', + value => { + biblionumber => $biblio->{biblionumber}, + biblioitemnumber => $biblioitem->{biblioitemnumber}, + withdrawn => 0, + itemlost => 0, + notforloan => 0, + }, + }); + + + my $startdate = dt_from_string(); + $startdate->add_duration(DateTime::Duration->new(days => 4)); + my $expdate = $startdate->clone(); + $expdate->add_duration(DateTime::Duration->new(days => 10)); + + my $reserveid = AddReserve($branchcode, $borrowernumber, + $item->{biblionumber}, undef, 1, $startdate->ymd(), $expdate->ymd, + undef, undef, undef, undef); + + my $non_overlap_duedate = dt_from_string(); + $non_overlap_duedate->add_duration(DateTime::Duration->new(days => 2)); + my ($error, $question, $alerts ) = + CanBookBeIssued($borrower, $item->{barcode}, $non_overlap_duedate, 1, 0); + + is_deeply($error, {}, ""); + is_deeply($question, {}, ""); + is_deeply($alerts, {}, ""); + + my $overlap_duedate = dt_from_string(); + $overlap_duedate->add_duration(DateTime::Duration->new(days => 5)); + ($error, $question, $alerts ) = + CanBookBeIssued($borrower, $item->{barcode}, $overlap_duedate, 1, 0); + + is_deeply($error, {}, ""); + my $expected = { + RESERVED => 1, + resfirstname => $borrower->firstname, + ressurname => $borrower->surname, + rescardnumber => $borrower->cardnumber, + resborrowernumber => $borrower->borrowernumber, + resbranchname => $branch->{branchname}, + resreservedate => $startdate->ymd, + resreserveid => $reserveid, + }; + is_deeply($question, $expected, ""); + is_deeply($alerts, {}, ""); +}; + +$schema->storage->txn_rollback; diff --git a/t/db_dependent/Reserves/ReserveDate.t b/t/db_dependent/Reserves/ReserveDate.t new file mode 100644 index 0000000000..4d7f8afc4a --- /dev/null +++ b/t/db_dependent/Reserves/ReserveDate.t @@ -0,0 +1,108 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Test::More tests => 6; +use Test::Warn; + +use MARC::Record; +use DateTime::Duration; + +use C4::Biblio; +use C4::Items; +use C4::Members; +use C4::Circulation; +use Koha::Holds; +use t::lib::TestBuilder; + +use Koha::DateUtils; + + +use_ok('C4::Reserves'); + +my $dbh = C4::Context->dbh; + +# Start transaction +$dbh->{AutoCommit} = 0; +$dbh->{RaiseError} = 1; + +my $builder = t::lib::TestBuilder->new(); +my $categorycode = $builder->build({ source => 'Category' })->{ categorycode }; +my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode }; + +my $borrower = $builder->build({ + source => 'Borrower', + value => { + branchcode => $branchcode, + categorycode => $categorycode, + } +}); + +my $borrower2 = $builder->build({ + source => 'Borrower', + value => { + branchcode => $branchcode, + categorycode => $categorycode, + } +}); + +my $borrowernumber = $borrower->{borrowernumber}; +my $borrowernumber2 = $borrower2->{borrowernumber}; + +# Create a helper biblio +my $biblio = MARC::Record->new(); +my $title = 'Alone in the Dark'; +my $author = 'Karen Rose'; +if( C4::Context->preference('marcflavour') eq 'UNIMARC' ) { + $biblio->append_fields( + MARC::Field->new('600', '', '1', a => $author), + MARC::Field->new('200', '', '', a => $title), + ); +} +else { + $biblio->append_fields( + MARC::Field->new('100', '', '', a => $author), + MARC::Field->new('245', '', '', a => $title), + ); +} +my ($bibnum, $bibitemnum); +($bibnum, $title, $bibitemnum) = AddBiblio($biblio, ''); + +my ($item_bibnum, $item_bibitemnum, $itemnumber) = AddItem({ homebranch => $branchcode, holdingbranch => $branchcode, barcode => '333' } , $bibnum); + +C4::Context->set_preference('AllowHoldDateInFuture', 1); + +AddReserve($branchcode, $borrowernumber, $bibnum, + $bibitemnum, 1, '2015-11-01', '2015-11-20', undef, + undef, undef, undef); + +is(ReservesOnSamePeriod($bibnum, undef, '2015-11-25', '2015-11-30'), undef, "Period doesn't overlaps"); + +ok(ReservesOnSamePeriod($bibnum, undef, '2015-11-02', '2015-11-10'), "Period overlaps"); + +my ($item_bibnum2, $item_bibitemnum2, $itemnumber2) = AddItem({ homebranch => $branchcode, holdingbranch => $branchcode, barcode => '444' } , $bibnum); +is(ReservesOnSamePeriod($bibnum, undef, '2015-11-02', '2015-11-10'), undef, "Period overlaps but there is 2 items"); + +AddReserve($branchcode, $borrowernumber2, $bibnum, + $bibitemnum, 1, '2016-02-01', '2016-02-10', undef, + undef, $itemnumber, undef); +is(ReservesOnSamePeriod($bibnum, $itemnumber, '02/12/2015', '10/12/2015'), undef, "Period on item does not overlap (with metric date format)"); + +my $reserve = ReservesOnSamePeriod($bibnum, $itemnumber, '2016-01-31', '2016-02-05'); +is($reserve->[0]->{itemnumber}, $itemnumber, 'Period on item overlaps'); +$dbh->rollback; -- 2.20.1