X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=C4%2FCirculation.pm;h=a3b2f0269656bbee341c19e559f2977900c99b94;hb=3e8b9a652b86726f7154c1dac6adb2cc24c5b44f;hp=2046c3f20247affccd29ced0fe7c1449dbb1f87a;hpb=ae885b99f0f3fd1ec7cd6a38e200582b4db3c6d8;p=srvgit diff --git a/C4/Circulation.pm b/C4/Circulation.pm index 2046c3f202..a3b2f02696 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -52,7 +52,6 @@ use Koha::Database; use Koha::Libraries; use Koha::Account::Lines; use Koha::Holds; -use Koha::RefundLostItemFeeRules; use Koha::Account::Lines; use Koha::Account::Offsets; use Koha::Config::SysPrefs; @@ -62,6 +61,7 @@ use Koha::Checkouts::ReturnClaims; use Carp; use List::MoreUtils qw( uniq any ); use Scalar::Util qw( looks_like_number ); +use Try::Tiny; use Date::Calc qw( Today Today_and_Now @@ -251,18 +251,26 @@ sub decode { =head2 transferbook - ($dotransfer, $messages, $iteminformation) = &transferbook($newbranch, - $barcode, $ignore_reserves); + ($dotransfer, $messages, $iteminformation) = &transferbook({ + from_branch => $frombranch + to_branch => $tobranch, + barcode => $barcode, + ignore_reserves => $ignore_reserves, + trigger => $trigger + }); Transfers an item to a new branch. If the item is currently on loan, it is automatically returned before the actual transfer. -C<$newbranch> is the code for the branch to which the item should be transferred. +C<$fbr> is the code for the branch initiating the transfer. +C<$tbr> is the code for the branch to which the item should be transferred. C<$barcode> is the barcode of the item to be transferred. If C<$ignore_reserves> is true, C<&transferbook> ignores reserves. Otherwise, if an item is reserved, the transfer fails. +C<$trigger> is the enum value for what triggered the transfer. + Returns three values: =over @@ -304,11 +312,24 @@ The item was eligible to be transferred. Barring problems communicating with the =cut sub transferbook { - my ( $tbr, $barcode, $ignoreRs ) = @_; + my $params = shift; + my $tbr = $params->{to_branch}; + my $fbr = $params->{from_branch}; + my $ignoreRs = $params->{ignore_reserves}; + my $barcode = $params->{barcode}; + my $trigger = $params->{trigger}; my $messages; my $dotransfer = 1; my $item = Koha::Items->find( { barcode => $barcode } ); + Koha::Exceptions::MissingParameter->throw( + "Missing mandatory parameter: from_branch") + unless $fbr; + + Koha::Exceptions::MissingParameter->throw( + "Missing mandatory parameter: to_branch") + unless $tbr; + # bad barcode.. unless ( $item ) { $messages->{'BadBarcode'} = $barcode; @@ -319,7 +340,6 @@ sub transferbook { my $itemnumber = $item->itemnumber; # get branches of book... my $hbr = $item->homebranch; - my $fbr = $item->holdingbranch; # if using Branch Transfer Limits if ( C4::Context->preference("UseBranchTransferLimits") == 1 ) { @@ -354,14 +374,13 @@ sub transferbook { CheckReserves( $itemnumber ); if ( $resfound and not $ignoreRs ) { $resrec->{'ResFound'} = $resfound; - - # $messages->{'ResFound'} = $resrec; + $messages->{'ResFound'} = $resrec; $dotransfer = 1; } #actually do the transfer.... if ($dotransfer) { - ModItemTransfer( $itemnumber, $fbr, $tbr ); + ModItemTransfer( $itemnumber, $fbr, $tbr, $trigger ); # don't need to update MARC anymore, we do it in batch now $messages->{'WasTransfered'} = 1; @@ -380,13 +399,30 @@ sub TooMany { my $switch_onsite_checkout = $params->{switch_onsite_checkout} || 0; my $cat_borrower = $borrower->{'categorycode'}; my $dbh = C4::Context->dbh; - my $branch; - # Get which branchcode we need - $branch = _GetCircControlBranch($item_object->unblessed,$borrower); + # Get which branchcode we need + my $branch = _GetCircControlBranch($item_object->unblessed,$borrower); my $type = $item_object->effective_itemtype; + my ($type_object, $parent_type, $parent_maxissueqty_rule); + $type_object = Koha::ItemTypes->find( $type ); + $parent_type = $type_object->parent_type if $type_object; + my $child_types = Koha::ItemTypes->search({ parent_type => $type }); + # Find any children if we are a parent_type; + # given branch, patron category, and item type, determine # applicable issuing rule + + $parent_maxissueqty_rule = Koha::CirculationRules->get_effective_rule( + { + categorycode => $cat_borrower, + itemtype => $parent_type, + branchcode => $branch, + rule_name => 'maxissueqty', + } + ) if $parent_type; + # If the parent rule is for default type we discount it + $parent_maxissueqty_rule = undef if $parent_maxissueqty_rule && !defined $parent_maxissueqty_rule->itemtype; + my $maxissueqty_rule = Koha::CirculationRules->get_effective_rule( { categorycode => $cat_borrower, @@ -395,6 +431,7 @@ sub TooMany { rule_name => 'maxissueqty', } ); + my $maxonsiteissueqty_rule = Koha::CirculationRules->get_effective_rule( { categorycode => $cat_borrower, @@ -405,156 +442,128 @@ sub TooMany { ); + my $patron = Koha::Patrons->find($borrower->{borrowernumber}); # if a rule is found and has a loan limit set, count # how many loans the patron already has that meet that # rule - if (defined($maxissueqty_rule) and $maxissueqty_rule->rule_value ne '') { - my @bind_params; - my $count_query = q| - SELECT COUNT(*) AS total, COALESCE(SUM(onsite_checkout), 0) AS onsite_checkouts - FROM issues - JOIN items USING (itemnumber) - |; + if (defined($maxissueqty_rule) and $maxissueqty_rule->rule_value ne "") { - my $rule_itemtype = $maxissueqty_rule->itemtype; - unless ($rule_itemtype) { - # matching rule has the default item type, so count only - # those existing loans that don't fall under a more - # specific rule - if (C4::Context->preference('item-level_itypes')) { - $count_query .= " WHERE items.itype NOT IN ( - SELECT itemtype FROM circulation_rules - WHERE branchcode = ? - AND (categorycode = ? OR categorycode = ?) - AND itemtype IS NOT NULL - AND rule_name = 'maxissueqty' - ) "; + my $checkouts; + if ( $maxissueqty_rule->branchcode ) { + if ( C4::Context->preference('CircControl') eq 'PickupLibrary' ) { + $checkouts = $patron->checkouts->search( + { 'me.branchcode' => $maxissueqty_rule->branchcode } ); + } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') { + $checkouts = $patron->checkouts; # if branch is the patron's home branch, then count all loans by patron } else { - $count_query .= " JOIN biblioitems USING (biblionumber) - WHERE biblioitems.itemtype NOT IN ( - SELECT itemtype FROM circulation_rules - WHERE branchcode = ? - AND (categorycode = ? OR categorycode = ?) - AND itemtype IS NOT NULL - AND rule_name = 'maxissueqty' - ) "; + $checkouts = $patron->checkouts->search( + { 'item.homebranch' => $maxissueqty_rule->branchcode }, + { prefetch => 'item' } ); } - push @bind_params, $maxissueqty_rule->branchcode; - push @bind_params, $maxissueqty_rule->categorycode; - push @bind_params, $cat_borrower; } else { - # rule has specific item type, so count loans of that - # specific item type - if (C4::Context->preference('item-level_itypes')) { - $count_query .= " WHERE items.itype = ? "; - } else { - $count_query .= " JOIN biblioitems USING (biblionumber) - WHERE biblioitems.itemtype= ? "; - } - push @bind_params, $type; + $checkouts = $patron->checkouts; # if rule is not branch specific then count all loans by patron } + my $sum_checkouts; + my $rule_itemtype = $maxissueqty_rule->itemtype; + while ( my $c = $checkouts->next ) { + my $itemtype = $c->item->effective_itemtype; + my @types; + unless ( $rule_itemtype ) { + # matching rule has the default item type, so count only + # those existing loans that don't fall under a more + # specific rule + @types = Koha::CirculationRules->search( + { + branchcode => $maxissueqty_rule->branchcode, + categorycode => [ $maxissueqty_rule->categorycode, $cat_borrower ], + itemtype => { '!=' => undef }, + rule_name => 'maxissueqty' + } + )->get_column('itemtype'); - $count_query .= " AND borrowernumber = ? "; - push @bind_params, $borrower->{'borrowernumber'}; - my $rule_branch = $maxissueqty_rule->branchcode; - if ($rule_branch) { - if (C4::Context->preference('CircControl') eq 'PickupLibrary') { - $count_query .= " AND issues.branchcode = ? "; - push @bind_params, $rule_branch; - } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') { - ; # if branch is the patron's home branch, then count all loans by patron + next if grep {$_ eq $itemtype} @types; } else { - $count_query .= " AND items.homebranch = ? "; - push @bind_params, $rule_branch; + my @types; + if ( $parent_maxissueqty_rule ) { + # if we have a parent item type then we count loans of the + # specific item type or its siblings or parent + my $children = Koha::ItemTypes->search({ parent_type => $parent_type }); + @types = $children->get_column('itemtype'); + push @types, $parent_type; + } elsif ( $child_types ) { + # If we are a parent type, we need to count all child types and our own type + @types = $child_types->get_column('itemtype'); + push @types, $type; # And don't forget to count our own types + } else { push @types, $type; } # Otherwise only count the specific itemtype + + next unless grep {$_ eq $itemtype} @types; } + $sum_checkouts->{total}++; + $sum_checkouts->{onsite_checkouts}++ if $c->onsite_checkout; + $sum_checkouts->{itemtype}->{$itemtype}++; } - my ( $checkout_count, $onsite_checkout_count ) = $dbh->selectrow_array( $count_query, {}, @bind_params ); - - my $max_checkouts_allowed = $maxissueqty_rule ? $maxissueqty_rule->rule_value : undef; - my $max_onsite_checkouts_allowed = $maxonsiteissueqty_rule ? $maxonsiteissueqty_rule->rule_value : undef; - - if ( $onsite_checkout and $max_onsite_checkouts_allowed ne '' ) { - if ( $onsite_checkout_count >= $max_onsite_checkouts_allowed ) { - return { - reason => 'TOO_MANY_ONSITE_CHECKOUTS', - count => $onsite_checkout_count, - max_allowed => $max_onsite_checkouts_allowed, - } - } - } - if ( C4::Context->preference('ConsiderOnSiteCheckoutsAsNormalCheckouts') ) { - my $delta = $switch_onsite_checkout ? 1 : 0; - if ( $checkout_count >= $max_checkouts_allowed + $delta ) { - return { - reason => 'TOO_MANY_CHECKOUTS', - count => $checkout_count, - max_allowed => $max_checkouts_allowed, - }; - } - } elsif ( not $onsite_checkout ) { - if ( $checkout_count - $onsite_checkout_count >= $max_checkouts_allowed ) { - return { - reason => 'TOO_MANY_CHECKOUTS', - count => $checkout_count - $onsite_checkout_count, - max_allowed => $max_checkouts_allowed, - }; + my $checkout_count_type = $sum_checkouts->{itemtype}->{$type} || 0; + my $checkout_count = $sum_checkouts->{total} || 0; + my $onsite_checkout_count = $sum_checkouts->{onsite_checkouts} || 0; + + my $checkout_rules = { + checkout_count => $checkout_count, + onsite_checkout_count => $onsite_checkout_count, + onsite_checkout => $onsite_checkout, + max_checkouts_allowed => $maxissueqty_rule ? $maxissueqty_rule->rule_value : undef, + max_onsite_checkouts_allowed => $maxonsiteissueqty_rule ? $maxonsiteissueqty_rule->rule_value : undef, + switch_onsite_checkout => $switch_onsite_checkout, + }; + # If parent rules exists + if ( defined($parent_maxissueqty_rule) and defined($parent_maxissueqty_rule->rule_value) ){ + $checkout_rules->{max_checkouts_allowed} = $parent_maxissueqty_rule ? $parent_maxissueqty_rule->rule_value : undef; + my $qty_over = _check_max_qty($checkout_rules); + return $qty_over if defined $qty_over; + + # If the parent rule is less than or equal to the child, we only need check the parent + if( $maxissueqty_rule->rule_value < $parent_maxissueqty_rule->rule_value && defined($maxissueqty_rule->itemtype) ) { + $checkout_rules->{checkout_count} = $checkout_count_type; + $checkout_rules->{max_checkouts_allowed} = $maxissueqty_rule ? $maxissueqty_rule->rule_value : undef; + my $qty_over = _check_max_qty($checkout_rules); + return $qty_over if defined $qty_over; } + } else { + my $qty_over = _check_max_qty($checkout_rules); + return $qty_over if defined $qty_over; } } # Now count total loans against the limit for the branch my $branch_borrower_circ_rule = GetBranchBorrowerCircRule($branch, $cat_borrower); if (defined($branch_borrower_circ_rule->{patron_maxissueqty}) and $branch_borrower_circ_rule->{patron_maxissueqty} ne '') { - my @bind_params = (); - my $branch_count_query = q| - SELECT COUNT(*) AS total, COALESCE(SUM(onsite_checkout), 0) AS onsite_checkouts - FROM issues - JOIN items USING (itemnumber) - WHERE borrowernumber = ? - |; - push @bind_params, $borrower->{borrowernumber}; - - if (C4::Context->preference('CircControl') eq 'PickupLibrary') { - $branch_count_query .= " AND issues.branchcode = ? "; - push @bind_params, $branch; + my $checkouts; + if ( C4::Context->preference('CircControl') eq 'PickupLibrary' ) { + $checkouts = $patron->checkouts->search( + { 'me.branchcode' => $branch} ); } elsif (C4::Context->preference('CircControl') eq 'PatronLibrary') { ; # if branch is the patron's home branch, then count all loans by patron } else { - $branch_count_query .= " AND items.homebranch = ? "; - push @bind_params, $branch; + $checkouts = $patron->checkouts->search( + { 'item.homebranch' => $branch} ); } - my ( $checkout_count, $onsite_checkout_count ) = $dbh->selectrow_array( $branch_count_query, {}, @bind_params ); + + my $checkout_count = $checkouts->count; + my $onsite_checkout_count = $checkouts->search({ onsite_checkout => 1 })->count; my $max_checkouts_allowed = $branch_borrower_circ_rule->{patron_maxissueqty}; - my $max_onsite_checkouts_allowed = $branch_borrower_circ_rule->{patron_maxonsiteissueqty}; - - if ( $onsite_checkout and $max_onsite_checkouts_allowed ne '' ) { - if ( $onsite_checkout_count >= $max_onsite_checkouts_allowed ) { - return { - reason => 'TOO_MANY_ONSITE_CHECKOUTS', - count => $onsite_checkout_count, - max_allowed => $max_onsite_checkouts_allowed, - } - } - } - if ( C4::Context->preference('ConsiderOnSiteCheckoutsAsNormalCheckouts') ) { - my $delta = $switch_onsite_checkout ? 1 : 0; - if ( $checkout_count >= $max_checkouts_allowed + $delta ) { - return { - reason => 'TOO_MANY_CHECKOUTS', - count => $checkout_count, - max_allowed => $max_checkouts_allowed, - }; - } - } elsif ( not $onsite_checkout ) { - if ( $checkout_count - $onsite_checkout_count >= $max_checkouts_allowed ) { - return { - reason => 'TOO_MANY_CHECKOUTS', - count => $checkout_count - $onsite_checkout_count, - max_allowed => $max_checkouts_allowed, - }; + my $max_onsite_checkouts_allowed = $branch_borrower_circ_rule->{patron_maxonsiteissueqty} || undef; + + my $qty_over = _check_max_qty( + { + checkout_count => $checkout_count, + onsite_checkout_count => $onsite_checkout_count, + onsite_checkout => $onsite_checkout, + max_checkouts_allowed => $max_checkouts_allowed, + max_onsite_checkouts_allowed => $max_onsite_checkouts_allowed, + switch_onsite_checkout => $switch_onsite_checkout } - } + ); + return $qty_over if defined $qty_over; } if ( not defined( $maxissueqty_rule ) and not defined($branch_borrower_circ_rule->{patron_maxissueqty}) ) { @@ -565,6 +574,52 @@ sub TooMany { return; } +sub _check_max_qty { + my $params = shift; + my $checkout_count = $params->{checkout_count}; + my $onsite_checkout_count = $params->{onsite_checkout_count}; + my $onsite_checkout = $params->{onsite_checkout}; + my $max_checkouts_allowed = $params->{max_checkouts_allowed}; + my $max_onsite_checkouts_allowed = $params->{max_onsite_checkouts_allowed}; + my $switch_onsite_checkout = $params->{switch_onsite_checkout}; + + if ( $onsite_checkout and defined $max_onsite_checkouts_allowed ) { + if ( $max_onsite_checkouts_allowed eq '' ) { return; } + if ( $onsite_checkout_count >= $max_onsite_checkouts_allowed ) { + return { + reason => 'TOO_MANY_ONSITE_CHECKOUTS', + count => $onsite_checkout_count, + max_allowed => $max_onsite_checkouts_allowed, + }; + } + } + if ( C4::Context->preference('ConsiderOnSiteCheckoutsAsNormalCheckouts') ) { + if ( $max_checkouts_allowed eq '' ) { return; } + my $delta = $switch_onsite_checkout ? 1 : 0; + if ( $checkout_count >= $max_checkouts_allowed + $delta ) { + return { + reason => 'TOO_MANY_CHECKOUTS', + count => $checkout_count, + max_allowed => $max_checkouts_allowed, + }; + } + } + elsif ( not $onsite_checkout ) { + if ( $max_checkouts_allowed eq '' ) { return; } + if ( + $checkout_count - $onsite_checkout_count >= $max_checkouts_allowed ) + { + return { + reason => 'TOO_MANY_CHECKOUTS', + count => $checkout_count - $onsite_checkout_count, + max_allowed => $max_checkouts_allowed, + }; + } + } + + return; +} + =head2 CanBookBeIssued ( $issuingimpossible, $needsconfirmation, [ $alerts ] ) = CanBookBeIssued( $patron, @@ -704,7 +759,7 @@ sub CanBookBeIssued { if ($duedate && ref $duedate ne 'DateTime') { $duedate = dt_from_string($duedate); } - my $now = DateTime->now( time_zone => C4::Context->tz() ); + my $now = dt_from_string(); unless ( $duedate ) { my $issuedate = $now->clone(); @@ -842,6 +897,14 @@ sub CanBookBeIssued { } } + # Additional Materials Check + if ( C4::Context->preference("CircConfirmParts") ) { + my $no_of_parts = $item_object->materials || 0; + if ( $no_of_parts > 0 ) { + $needsconfirmation{additional_materials} = $no_of_parts; + } + } + # # CHECK IF BOOK ALREADY ISSUED TO THIS BORROWER # @@ -1037,6 +1100,7 @@ sub CanBookBeIssued { $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber; $needsconfirmation{'resbranchcode'} = $res->{branchcode}; $needsconfirmation{'reswaitingdate'} = $res->{'waitingdate'}; + $needsconfirmation{'reserve_id'} = $res->{reserve_id}; } elsif ( $restype eq "Reserved" ) { # The item is on reserve for someone else. @@ -1047,6 +1111,7 @@ sub CanBookBeIssued { $needsconfirmation{'resborrowernumber'} = $patron->borrowernumber; $needsconfirmation{'resbranchcode'} = $patron->branchcode; $needsconfirmation{'resreservedate'} = $res->{reservedate}; + $needsconfirmation{'reserve_id'} = $res->{reserve_id}; } } } @@ -1222,7 +1287,7 @@ sub checkHighHolds { } # Remove any items that are not holdable for this patron - @items = grep { CanItemBeReserved( $borrower->{borrowernumber}, $_->itemnumber )->{status} eq 'OK' } @items; + @items = grep { CanItemBeReserved( $borrower->{borrowernumber}, $_->itemnumber, undef, { ignore_found_holds => 1 } )->{status} eq 'OK' } @items; my $items_count = scalar @items; @@ -1235,11 +1300,18 @@ sub checkHighHolds { } } - my $issuedate = DateTime->now( time_zone => C4::Context->tz() ); - - my $calendar = Koha::Calendar->new( branchcode => $branchcode ); + my $issuedate = dt_from_string(); my $itype = $item_object->effective_itemtype; + my $daysmode = Koha::CirculationRules->get_effective_daysmode( + { + categorycode => $borrower->{categorycode}, + itemtype => $itype, + branchcode => $branchcode, + } + ); + my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => $daysmode ); + my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $borrower ); my $decreaseLoanHighHoldsDuration = C4::Context->preference('decreaseLoanHighHoldsDuration'); @@ -1314,7 +1386,7 @@ sub AddIssue { # $issuedate defaults to today. if ( !defined $issuedate ) { - $issuedate = DateTime->now( time_zone => C4::Context->tz() ); + $issuedate = dt_from_string(); } else { if ( ref $issuedate ne 'DateTime' ) { @@ -1444,35 +1516,12 @@ sub AddIssue { UpdateTotalIssues( $item_object->biblionumber, 1 ); } - ## If item was lost, it has now been found, reverse any list item charges if necessary. - if ( $item_object->itemlost ) { - if ( - Koha::RefundLostItemFeeRules->should_refund( - { - current_branch => C4::Context->userenv->{branch}, - item_home_branch => $item_object->homebranch, - item_holding_branch => $item_object->holdingbranch, - } - ) - ) - { - _FixAccountForLostAndFound( $item_object->itemnumber, undef, - $item_object->barcode ); - } - } - - ModItem( - { - issues => ( $item_object->issues || 0 ) + 1, - holdingbranch => C4::Context->userenv->{'branch'}, - itemlost => 0, - onloan => $datedue->ymd(), - datelastborrowed => DateTime->now( time_zone => C4::Context->tz() )->ymd(), - }, - $item_object->biblionumber, - $item_object->itemnumber, - { log_action => 0 } - ); + $item_object->issues( ( $item_object->issues || 0 ) + 1); + $item_object->holdingbranch(C4::Context->userenv->{'branch'}); + $item_object->itemlost(0); + $item_object->onloan($datedue->ymd()); + $item_object->datelastborrowed( dt_from_string()->ymd() ); + $item_object->store({log_action => 0}); ModDateLastSeen( $item_object->itemnumber ); # If it costs to borrow this book, charge it to the patron's account. @@ -1529,6 +1578,14 @@ sub AddIssue { $borrower->{'borrowernumber'}, $item_object->itemnumber, ) if C4::Context->preference("IssueLog"); + + Koha::Plugins->call('after_circ_action', { + action => 'checkout', + payload => { + type => ( $onsite_checkout ? 'onsite_checkout' : 'issue' ), + checkout => $issue->get_from_storage + } + }); } } return $issue; @@ -1545,50 +1602,6 @@ Get loan length for an itemtype, a borrower type and a branch sub GetLoanLength { my ( $categorycode, $itemtype, $branchcode ) = @_; - # Set search precedences - my @params = ( - { - categorycode => $categorycode, - itemtype => $itemtype, - branchcode => $branchcode, - }, - { - categorycode => $categorycode, - itemtype => undef, - branchcode => $branchcode, - }, - { - categorycode => undef, - itemtype => $itemtype, - branchcode => $branchcode, - }, - { - categorycode => undef, - itemtype => undef, - branchcode => $branchcode, - }, - { - categorycode => $categorycode, - itemtype => $itemtype, - branchcode => undef, - }, - { - categorycode => $categorycode, - itemtype => undef, - branchcode => undef, - }, - { - categorycode => undef, - itemtype => $itemtype, - branchcode => undef, - }, - { - categorycode => undef, - itemtype => undef, - branchcode => undef, - }, - ); - # Initialize default values my $rules = { issuelength => 0, @@ -1596,21 +1609,20 @@ sub GetLoanLength { lengthunit => 'days', }; - # Search for rules! - foreach my $rule_name (qw( issuelength renewalperiod lengthunit )) { - foreach my $params (@params) { - my $rule = Koha::CirculationRules->search( - { - rule_name => $rule_name, - %$params, - } - )->next(); + my $found = Koha::CirculationRules->get_effective_rules( { + branchcode => $branchcode, + categorycode => $categorycode, + itemtype => $itemtype, + rules => [ + 'issuelength', + 'renewalperiod', + 'lengthunit' + ], + } ); - if ($rule) { - $rules->{$rule_name} = $rule->rule_value; - last; - } - } + # Search for rules! + foreach my $rule_name (keys %$found) { + $rules->{$rule_name} = $found->{$rule_name}; } return $rules; @@ -1859,11 +1871,12 @@ sub AddReturn { undef $branch; } $branch = C4::Context->userenv->{'branch'} unless $branch; # we trust userenv to be a safe fallback/default + my $return_date_specified = !!$return_date; $return_date //= dt_from_string(); my $messages; my $patron; my $doreturn = 1; - my $validTransfert = 0; + my $validTransfer = 1; my $stat_type = 'return'; # get information on item @@ -1882,7 +1895,8 @@ sub AddReturn { . Dumper($issue->unblessed) . "\n"; } else { $messages->{'NotIssued'} = $barcode; - ModItem({ onloan => undef }, $item->biblionumber, $item->itemnumber) if defined $item->onloan; + $item->onloan(undef)->store if defined $item->onloan; + # even though item is not on loan, it may still be transferred; therefore, get current branch info $doreturn = 0; # No issue, no borrowernumber. ONLY if $doreturn, *might* you have a $borrower later. @@ -1893,12 +1907,12 @@ sub AddReturn { } } - my $item_unblessed = $item->unblessed; # full item data, but no borrowernumber or checkout info (no issue) my $hbr = GetBranchItemRule($item->homebranch, $itemtype)->{'returnbranch'} || "homebranch"; # get the proper branch to which to return the item my $returnbranch = $hbr ne 'noreturn' ? $item->$hbr : $branch; # if $hbr was "noreturn" or any other non-item table value, then it should 'float' (i.e. stay at this branch) + my $transfer_trigger = $hbr eq 'homebranch' ? 'ReturnToHome' : $hbr eq 'holdingbranch' ? 'ReturnToHolding' : undef; my $borrowernumber = $patron ? $patron->borrowernumber : undef; # we don't know if we had a borrower or not my $patron_unblessed = $patron ? $patron->unblessed : {}; @@ -1911,7 +1925,7 @@ sub AddReturn { if ($update_loc_rules->{_ALL_} eq '_BLANK_') { $update_loc_rules->{_ALL_} = ''; } if ( $item->location ne $update_loc_rules->{_ALL_}) { $messages->{'ItemLocationUpdated'} = { from => $item->location, to => $update_loc_rules->{_ALL_} }; - ModItem( { location => $update_loc_rules->{_ALL_} }, undef, $itemnumber ); + $item->location($update_loc_rules->{_ALL_})->store; } } else { @@ -1920,7 +1934,7 @@ sub AddReturn { if ( $update_loc_rules->{$key} eq '_BLANK_') { $update_loc_rules->{$key} = '' ;} if ( ($item->location eq $key && $item->location ne $update_loc_rules->{$key}) || ($key eq '_BLANK_' && $item->location eq '' && $update_loc_rules->{$key} ne '') ) { $messages->{'ItemLocationUpdated'} = { from => $item->location, to => $update_loc_rules->{$key} }; - ModItem( { location => $update_loc_rules->{$key} }, undef, $itemnumber ); + $item->location($update_loc_rules->{$key})->store; last; } } @@ -1939,7 +1953,7 @@ sub AddReturn { foreach my $key ( keys %$rules ) { if ( $item->notforloan eq $key ) { $messages->{'NotForLoanStatusUpdated'} = { from => $item->notforloan, to => $rules->{$key} }; - ModItem( { notforloan => $rules->{$key} }, undef, $itemnumber, { log_action => 0 } ); + $item->notforloan($rules->{$key})->store({ log_action => 0 }); last; } } @@ -1947,7 +1961,7 @@ sub AddReturn { } # check if the return is allowed at this branch - my ($returnallowed, $message) = CanBookBeReturned($item_unblessed, $branch); + my ($returnallowed, $message) = CanBookBeReturned($item->unblessed, $branch); unless ($returnallowed){ $messages->{'Wrongbranch'} = { Wrongbranch => $branch, @@ -1976,8 +1990,15 @@ sub AddReturn { MarkIssueReturned( $borrowernumber, $item->itemnumber, $return_date, $patron->privacy ); }; unless ( $@ ) { - if ( C4::Context->preference('CalculateFinesOnReturn') && !$item->itemlost ) { - _CalculateAndUpdateFine( { issue => $issue, item => $item_unblessed, borrower => $patron_unblessed, return_date => $return_date } ); + if ( + ( + C4::Context->preference('CalculateFinesOnReturn') + || ( $return_date_specified && C4::Context->preference('CalculateFinesOnBackdate') ) + ) + && !$item->itemlost + ) + { + _CalculateAndUpdateFine( { issue => $issue, item => $item->unblessed, borrower => $patron_unblessed, return_date => $return_date } ); } } else { carp "The checkin for the following issue failed, Please go to the about page, section 'data corrupted' to know how to fix this problem ($@)" . Dumper( $issue->unblessed ); @@ -1990,19 +2011,26 @@ sub AddReturn { } - ModItem( { onloan => undef }, $item->biblionumber, $item->itemnumber, { log_action => 0 } ); + $item->onloan(undef)->store({ log_action => 0 }); } # the holdingbranch is updated if the document is returned to another location. # this is always done regardless of whether the item was on loan or not - my $item_holding_branch = $item->holdingbranch; if ($item->holdingbranch ne $branch) { - UpdateHoldingbranch($branch, $item->itemnumber); - $item_unblessed->{'holdingbranch'} = $branch; # update item data holdingbranch too # FIXME I guess this is for the _debar_user_on_return call later + $item->holdingbranch($branch)->store; } + my $item_was_lost = $item->itemlost; my $leave_item_lost = C4::Context->preference("BlockReturnOfLostItems") ? 1 : 0; - ModDateLastSeen( $item->itemnumber, $leave_item_lost ); + my $updated_item = ModDateLastSeen( $item->itemnumber, $leave_item_lost ); # will unset itemlost if needed + + # fix up the accounts..... + if ( $item_was_lost ) { + $messages->{'WasLost'} = 1; + unless ( C4::Context->preference("BlockReturnOfLostItems") ) { + $messages->{'LostItemFeeRefunded'} = $updated_item->{_refunded}; + } + } # check if we have a transfer for this document my ($datesent,$frombranch,$tobranch) = GetTransfers( $item->itemnumber ); @@ -2010,49 +2038,28 @@ sub AddReturn { # if we have a transfer to do, we update the line of transfers with the datearrived my $is_in_rotating_collection = C4::RotatingCollections::isItemInAnyCollection( $item->itemnumber ); if ($datesent) { + # At this point we will either fill the transfer or it is a wrong transfer + # either way we should not now generate a new transfer + $validTransfer = 0; if ( $tobranch eq $branch ) { my $sth = C4::Context->dbh->prepare( "UPDATE branchtransfers SET datearrived = now() WHERE itemnumber= ? AND datearrived IS NULL" ); $sth->execute( $item->itemnumber ); - # if we have a reservation with valid transfer, we can set it's status to 'W' - C4::Reserves::ModReserveStatus($item->itemnumber, 'W'); } else { $messages->{'WrongTransfer'} = $tobranch; $messages->{'WrongTransferItem'} = $item->itemnumber; } - $validTransfert = 1; - } - - # fix up the accounts..... - if ( $item->itemlost ) { - $messages->{'WasLost'} = 1; - unless ( C4::Context->preference("BlockReturnOfLostItems") ) { - if ( - Koha::RefundLostItemFeeRules->should_refund( - { - current_branch => C4::Context->userenv->{branch}, - item_home_branch => $item->homebranch, - item_holding_branch => $item_holding_branch - } - ) - ) - { - _FixAccountForLostAndFound( $item->itemnumber, - $borrowernumber, $barcode ); - $messages->{'LostItemFeeRefunded'} = 1; - } - } } # fix up the overdues in accounts... if ($borrowernumber) { my $fix = _FixOverduesOnReturn( $borrowernumber, $item->itemnumber, $exemptfine, 'RETURNED' ); - defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $item->itemnumber...) failed!"; # zero is OK, check defined + defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, ".$item->itemnumber."...) failed!"; # zero is OK, check defined - if ( $issue and $issue->is_overdue ) { + if ( $issue and $issue->is_overdue($return_date) ) { # fix fine days - my ($debardate,$reminder) = _debar_user_on_return( $patron_unblessed, $item_unblessed, dt_from_string($issue->date_due), $return_date ); + my ($debardate,$reminder) = _debar_user_on_return( $patron_unblessed, $item->unblessed, dt_from_string($issue->date_due), $return_date ); if ($reminder){ $messages->{'PrevDebarred'} = $debardate; } else { @@ -2079,7 +2086,7 @@ sub AddReturn { my $lookahead= C4::Context->preference('ConfirmFutureHolds'); #number of days to look for future holds ($resfound, $resrec, undef) = C4::Reserves::CheckReserves( $item->itemnumber, undef, $lookahead ) unless ( $item->withdrawn ); # if a hold is found and is waiting at another branch, change the priority back to 1 and trigger the hold (this will trigger a transfer and update the hold status properly) - if ( $resfound eq "Waiting" and $branch ne $resrec->{branchcode} ) { + if ( $resfound and $resfound eq "Waiting" and $branch ne $resrec->{branchcode} ) { my $hold = C4::Reserves::RevertWaitingStatus( { itemnumber => $item->itemnumber } ); $resfound = 'Reserved'; $resrec = $hold->unblessed; @@ -2095,6 +2102,7 @@ sub AddReturn { type => $stat_type, itemnumber => $itemnumber, itemtype => $itemtype, + location => $item->location, borrowernumber => $borrowernumber, ccode => $item->ccode, }); @@ -2111,7 +2119,7 @@ sub AddReturn { if ($doreturn && $circulation_alert->is_enabled_for(\%conditions)) { SendCirculationAlert({ type => 'CHECKIN', - item => $item_unblessed, + item => $item->unblessed, borrower => $patron->unblessed, branch => $branch, }); @@ -2121,38 +2129,29 @@ sub AddReturn { if C4::Context->preference("ReturnLog"); } - # Remove any OVERDUES related debarment if the borrower has no overdues - if ( $borrowernumber - && $patron->debarred - && C4::Context->preference('AutoRemoveOverduesRestrictions') - && !Koha::Patrons->find( $borrowernumber )->has_overdues - && @{ GetDebarments({ borrowernumber => $borrowernumber, type => 'OVERDUES' }) } - ) { - DelUniqueDebarment({ borrowernumber => $borrowernumber, type => 'OVERDUES' }); + # Check if this item belongs to a biblio record that is attached to an + # ILL request, if it is we need to update the ILL request's status + if ( $doreturn and C4::Context->preference('CirculateILL')) { + my $request = Koha::Illrequests->find( + { biblio_id => $item->biblio->biblionumber } + ); + $request->status('RET') if $request; } - # Check if this item belongs to a biblio record that is attached to an - # ILL request, if it is we need to update the ILL request's status - if (C4::Context->preference('CirculateILL')) { - my $request = Koha::Illrequests->find( - { biblio_id => $item->biblio->biblionumber } - ); - $request->status('RET') if $request; - } - # Transfer to returnbranch if Automatic transfer set or append message NeedsTransfer - if (!$is_in_rotating_collection && ($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $returnbranch) and not $messages->{'WrongTransfer'}){ + if ($validTransfer && !$is_in_rotating_collection && ($doreturn or $messages->{'NotIssued'}) and !$resfound and ($branch ne $returnbranch) ){ my $BranchTransferLimitsType = C4::Context->preference("BranchTransferLimitsType") eq 'itemtype' ? 'effective_itemtype' : 'ccode'; if (C4::Context->preference("AutomaticItemReturn" ) or (C4::Context->preference("UseBranchTransferLimits") and ! IsBranchTransferAllowed($branch, $returnbranch, $item->$BranchTransferLimitsType ) )) { - $debug and warn sprintf "about to call ModItemTransfer(%s, %s, %s)", $item->itemnumber,$branch, $returnbranch; - $debug and warn "item: " . Dumper($item_unblessed); - ModItemTransfer($item->itemnumber, $branch, $returnbranch); + $debug and warn sprintf "about to call ModItemTransfer(%s, %s, %s, %s)", $item->itemnumber,$branch, $returnbranch, $transfer_trigger; + $debug and warn "item: " . Dumper($item->unblessed); + ModItemTransfer($item->itemnumber, $branch, $returnbranch, $transfer_trigger); $messages->{'WasTransfered'} = 1; } else { $messages->{'NeedsTransfer'} = $returnbranch; + $messages->{'TransferTrigger'} = $transfer_trigger; } } @@ -2169,6 +2168,17 @@ sub AddReturn { } } + if ( $doreturn and $issue ) { + my $checkin = Koha::Old::Checkouts->find($issue->id); + + Koha::Plugins->call('after_circ_action', { + action => 'checkin', + payload => { + checkout=> $checkin + } + }); + } + return ( $doreturn, $messages, $issue, ( $patron ? $patron->unblessed : {} )); } @@ -2218,6 +2228,8 @@ sub MarkIssueReturned { # FIXME Improve the return value and handle it from callers $schema->txn_do(sub { + my $patron = Koha::Patrons->find( $borrowernumber ); + # Update the returndate value if ( $returndate ) { $issue->returndate( $returndate )->store->discard_changes; # update and refetch @@ -2237,13 +2249,22 @@ sub MarkIssueReturned { # And finally delete the issue $issue->delete; - ModItem( { 'onloan' => undef }, undef, $itemnumber, { log_action => 0 } ); + $issue->item->onloan(undef)->store({ log_action => 0 }); if ( C4::Context->preference('StoreLastBorrower') ) { my $item = Koha::Items->find( $itemnumber ); - my $patron = Koha::Patrons->find( $borrowernumber ); $item->last_returned_by( $patron ); } + + # Remove any OVERDUES related debarment if the borrower has no overdues + if ( C4::Context->preference('AutoRemoveOverduesRestrictions') + && $patron->debarred + && !$patron->has_overdues + && @{ GetDebarments({ borrowernumber => $borrowernumber, type => 'OVERDUES' }) } + ) { + DelUniqueDebarment({ borrowernumber => $borrowernumber, type => 'OVERDUES' }); + } + }); return $issue_id; @@ -2426,9 +2447,12 @@ sub _FixOverduesOnReturn { return 0 unless $accountlines->count; # no warning, there's just nothing to fix my $accountline = $accountlines->next; - if ($exemptfine) { - my $amountoutstanding = $accountline->amountoutstanding; + my $payments = $accountline->credits; + my $amountoutstanding = $accountline->amountoutstanding; + if ( $accountline->amount == 0 && $payments->count == 0 ) { + $accountline->delete; + } elsif ($exemptfine && ($amountoutstanding != 0)) { my $account = Koha::Account->new({patron_id => $borrowernumber}); my $credit = $account->add_credit( { @@ -2443,109 +2467,23 @@ sub _FixOverduesOnReturn { $credit->apply({ debits => [ $accountline ], offset_type => 'Forgiven' }); - $accountline->status('FORGIVEN'); - if (C4::Context->preference("FinesLog")) { &logaction("FINES", 'MODIFY',$borrowernumber,"Overdue forgiven: item $item"); } + + $accountline->status('FORGIVEN'); + $accountline->store(); } else { $accountline->status($status); - } + $accountline->store(); - return $accountline->store(); + } } ); return $result; } -=head2 _FixAccountForLostAndFound - - &_FixAccountForLostAndFound($itemnumber, [$borrowernumber, $barcode]); - -Finds the most recent lost item charge for this item and refunds the borrower -appropriatly, taking into account any payments or writeoffs already applied -against the charge. - -Internal function, not exported, called only by AddReturn. - -=cut - -sub _FixAccountForLostAndFound { - my $itemnumber = shift or return; - my $borrowernumber = @_ ? shift : undef; - my $item_id = @_ ? shift : $itemnumber; # Send the barcode if you want that logged in the description - - my $credit; - - # check for charge made for lost book - my $accountlines = Koha::Account::Lines->search( - { - itemnumber => $itemnumber, - debit_type_code => 'LOST', - status => [ undef, { '<>' => 'FOUND' } ] - }, - { - order_by => { -desc => [ 'date', 'accountlines_id' ] } - } - ); - - return unless $accountlines->count > 0; - my $accountline = $accountlines->next; - my $total_to_refund = 0; - - return unless $accountline->borrowernumber; - my $patron = Koha::Patrons->find( $accountline->borrowernumber ); - return unless $patron; # Patron has been deleted, nobody to credit the return to - - my $account = $patron->account; - - # Use cases - if ( $accountline->amount > $accountline->amountoutstanding ) { - # some amount has been cancelled. collect the offsets that are not writeoffs - # this works because the only way to subtract from this kind of a debt is - # using the UI buttons 'Pay' and 'Write off' - my $credits_offsets = Koha::Account::Offsets->search({ - debit_id => $accountline->id, - credit_id => { '!=' => undef }, # it is not the debit itself - type => { '!=' => 'Writeoff' }, - amount => { '<' => 0 } # credits are negative on the DB - }); - - $total_to_refund = ( $credits_offsets->count > 0 ) - ? $credits_offsets->total * -1 # credits are negative on the DB - : 0; - } - - my $credit_total = $accountline->amountoutstanding + $total_to_refund; - - if ( $credit_total > 0 ) { - my $branchcode = C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef; - $credit = $account->add_credit( - { - amount => $credit_total, - description => 'Item found ' . $item_id, - type => 'LOST_FOUND', - interface => C4::Context->interface, - library_id => $branchcode, - item_id => $itemnumber - } - ); - - $credit->apply( { debits => [ $accountline ] } ); - } - - # Update the account status - $accountline->discard_changes->status('FOUND'); - $accountline->store; - - if ( defined $account and C4::Context->preference('AccountAutoReconcile') ) { - $account->reconcile_balance; - } - - return ($credit) ? $credit->id : undef; -} - =head2 _GetCircControlBranch my $circ_control_branch = _GetCircControlBranch($iteminfos, $borrower); @@ -2707,11 +2645,11 @@ already renewed the loan. $error will contain the reason the renewal can not pro =cut sub CanBookBeRenewed { - my ( $borrowernumber, $itemnumber, $override_limit ) = @_; + my ( $borrowernumber, $itemnumber, $override_limit, $cron ) = @_; my $dbh = C4::Context->dbh; my $renews = 1; - my $auto_renew = 0; + my $auto_renew = "no"; my $item = Koha::Items->find($itemnumber) or return ( 0, 'no_item' ); my $issue = $item->checkout or return ( 0, 'no_checkout' ); @@ -2753,7 +2691,7 @@ sub CanBookBeRenewed { return ( 0, 'overdue'); } - if ( $issue->auto_renew ) { + if ( $issue->auto_renew && $patron->autorenew_checkouts ) { if ( $patron->category->effective_BlockExpiredPatronOpacActions and $patron->is_expired ) { return ( 0, 'auto_account_expired' ); @@ -2807,30 +2745,38 @@ sub CanBookBeRenewed { $soonestrenewal->truncate( to => 'day' ); } - if ( $soonestrenewal > DateTime->now( time_zone => C4::Context->tz() ) ) + if ( $soonestrenewal > dt_from_string() ) { - return ( 0, "auto_too_soon" ) if $issue->auto_renew; - return ( 0, "too_soon" ); + $auto_renew = ($issue->auto_renew && $patron->autorenew_checkouts) ? "auto_too_soon" : "too_soon"; } - elsif ( $issue->auto_renew ) { - $auto_renew = 1; + elsif ( $issue->auto_renew && $patron->autorenew_checkouts ) { + $auto_renew = "ok"; } } # Fallback for automatic renewals: # If norenewalbefore is undef, don't renew before due date. - if ( $issue->auto_renew && !$auto_renew ) { + if ( $issue->auto_renew && $auto_renew eq "no" && $patron->autorenew_checkouts ) { my $now = dt_from_string; if ( $now >= dt_from_string( $issue->date_due, 'sql' ) ){ - $auto_renew = 1; + $auto_renew = "ok"; } else { - return ( 0, "auto_too_soon" ); + $auto_renew = "auto_too_soon"; } } } my ( $resfound, $resrec, undef ) = C4::Reserves::CheckReserves($itemnumber); + # If next hold is non priority, then check if any hold with priority (non_priority = 0) exists for the same biblionumber. + if ( $resfound && $resrec->{non_priority} ) { + $resfound = Koha::Holds->search( + { biblionumber => $resrec->{biblionumber}, non_priority => 0 } ) + ->count > 0; + } + + + # This item can fill one or more unfilled reserve, can those unfilled reserves # all be filled by other available items? if ( $resfound @@ -2894,8 +2840,15 @@ sub CanBookBeRenewed { } } } - return ( 0, "on_reserve" ) if $resfound; # '' when no hold was found - return ( 0, "auto_renew" ) if $auto_renew && !$override_limit; # 0 if auto-renewal should not succeed + if( $cron ) { #The cron wants to return 'too_soon' over 'on_reserve' + return ( 0, $auto_renew ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok"; + return ( 0, "on_reserve" ) if $resfound; # '' when no hold was found + } else { # For other purposes we want 'on_reserve' before 'too_soon' + return ( 0, "on_reserve" ) if $resfound; # '' when no hold was found + return ( 0, $auto_renew ) if $auto_renew =~ 'too_soon';#$auto_renew ne "no" && $auto_renew ne "ok"; + } + + return ( 0, "auto_renew" ) if $auto_renew eq "ok" && !$override_limit; # 0 if auto-renewal should not succeed return ( 1, undef ); } @@ -2934,7 +2887,7 @@ sub AddRenewal { my $itemnumber = shift or return; my $branch = shift; my $datedue = shift; - my $lastreneweddate = shift || DateTime->now(time_zone => C4::Context->tz); + my $lastreneweddate = shift || dt_from_string(); my $skipfinecalc = shift; my $item_object = Koha::Items->find($itemnumber) or return; @@ -2974,7 +2927,7 @@ sub AddRenewal { $datedue = (C4::Context->preference('RenewalPeriodBase') eq 'date_due') ? dt_from_string( $issue->date_due, 'sql' ) : - DateTime->now( time_zone => C4::Context->tz()); + dt_from_string(); $datedue = CalcDateDue($datedue, $itemtype, $circ_library->branchcode, $patron_unblessed, 'is a renewal'); } @@ -3000,7 +2953,9 @@ sub AddRenewal { # Update the renewal count on the item, and tell zebra to reindex $renews = ( $item_object->renewals || 0 ) + 1; - ModItem( { renewals => $renews, onloan => $datedue->strftime('%Y-%m-%d %H:%M')}, $item_object->biblionumber, $itemnumber, { log_action => 0 } ); + $item_object->renewals($renews); + $item_object->onloan($datedue); + $item_object->store({ log_action => 0 }); # Charge a new rental fee, if applicable my ( $charge, $type ) = GetIssuingCharges( $itemnumber, $borrowernumber ); @@ -3048,14 +3003,10 @@ sub AddRenewal { DelUniqueDebarment({ borrowernumber => $borrowernumber, type => 'OVERDUES' }); } - unless ( C4::Context->interface eq 'opac' ) { #if from opac we are obeying OpacRenewalBranch as calculated in opac-renew.pl - $branch = ( C4::Context->userenv && defined C4::Context->userenv->{branch} ) ? C4::Context->userenv->{branch} : $branch; - } - # Add the renewal to stats UpdateStats( { - branch => $branch, + branch => $item_object->renewal_branchcode({branch => $branch}), type => 'renew', amount => $charge, itemnumber => $itemnumber, @@ -3068,6 +3019,13 @@ sub AddRenewal { #Log the renewal logaction("CIRCULATION", "RENEWAL", $borrowernumber, $itemnumber) if C4::Context->preference("RenewalLog"); + + Koha::Plugins->call('after_circ_action', { + action => 'renewal', + payload => { + checkout => $issue->get_from_storage + } + }); }); return $datedue; @@ -3502,7 +3460,7 @@ sub SendCirculationAlert { # LOCK TABLES is not transaction-safe and implicitly commits any active transaction before attempting to lock the tables. # If the LOCK/UNLOCK statements are executed from tests, the current transaction will be committed. # To avoid that we need to guess if this code is execute from tests or not (yes it is a bit hacky) - my $do_not_lock = ( exists $ENV{_} && $ENV{_} =~ m|prove| ) || $ENV{KOHA_NO_TABLE_LOCKS}; + my $do_not_lock = ( exists $ENV{_} && $ENV{_} =~ m|prove| ) || $ENV{KOHA_TESTING}; for my $mtt (@transports) { my $letter = C4::Letters::GetPreparedLetter ( @@ -3561,20 +3519,7 @@ sub updateWrongTransfer { ModItemTransfer($itemNumber, $FromLibrary, $waitingAtLibrary); #third step changing holdingbranch of item - UpdateHoldingbranch($FromLibrary,$itemNumber); -} - -=head2 UpdateHoldingbranch - - $items = UpdateHoldingbranch($branch,$itmenumber); - -Simple methode for updating hodlingbranch in items BDD line - -=cut - -sub UpdateHoldingbranch { - my ( $branch,$itemnumber ) = @_; - ModItem({ holdingbranch => $branch }, undef, $itemnumber); + my $item = Koha::Items->find($itemNumber)->holdingbranch($FromLibrary)->store; } =head2 CalcDateDue @@ -3582,7 +3527,7 @@ sub UpdateHoldingbranch { $newdatedue = CalcDateDue($startdate,$itemtype,$branchcode,$borrower); this function calculates the due date given the start date and configured circulation rules, -checking against the holidays calendar as per the 'useDaysMode' syspref. +checking against the holidays calendar as per the daysmode circulation rule. C<$startdate> = DateTime object representing start date of loan period (assumed to be today) C<$itemtype> = itemtype code of item in question C<$branch> = location whose calendar to use @@ -3612,14 +3557,20 @@ sub CalcDateDue { $datedue = $startdate->clone; } } else { - $datedue = - DateTime->now( time_zone => C4::Context->tz() ) - ->truncate( to => 'minute' ); + $datedue = dt_from_string()->truncate( to => 'minute' ); } + my $daysmode = Koha::CirculationRules->get_effective_daysmode( + { + categorycode => $borrower->{categorycode}, + itemtype => $itemtype, + branchcode => $branch, + } + ); + # calculate the datedue as normal - if ( C4::Context->preference('useDaysMode') eq 'Days' ) + if ( $daysmode eq 'Days' ) { # ignoring calendar if ( $loanlength->{lengthunit} eq 'hours' ) { $datedue->add( hours => $loanlength->{$length_key} ); @@ -3636,7 +3587,7 @@ sub CalcDateDue { else { # days $dur = DateTime::Duration->new( days => $loanlength->{$length_key}); } - my $calendar = Koha::Calendar->new( branchcode => $branch ); + my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode ); $datedue = $calendar->addDate( $datedue, $dur, $loanlength->{lengthunit} ); if ($loanlength->{lengthunit} eq 'days') { $datedue->set_hour(23); @@ -3674,8 +3625,8 @@ sub CalcDateDue { $datedue = $expiry_dt->clone->set_time_zone( C4::Context->tz ); } } - if ( C4::Context->preference('useDaysMode') ne 'Days' ) { - my $calendar = Koha::Calendar->new( branchcode => $branch ); + if ( $daysmode ne 'Days' ) { + my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode ); if ( $calendar->is_holiday($datedue) ) { # Don't return on a closed day $datedue = $calendar->prev_open_days( $datedue, 1 ); @@ -3779,9 +3730,23 @@ sub ReturnLostItem{ MarkIssueReturned( $borrowernumber, $itemnum ); } +=head2 LostItem + + LostItem( $itemnumber, $mark_lost_from, $force_mark_returned, [$params] ); + +The final optional parameter, C<$params>, expected to contain +'skip_record_index' key, which relayed down to Koha::Item/store, +there it prevents calling of ModZebra index_records, +which takes most of the time in batch adds/deletes: index_records better +to be called later in C after the whole loop. + +$params: + skip_record_index => 1|0 + +=cut sub LostItem{ - my ($itemnumber, $mark_lost_from, $force_mark_returned) = @_; + my ($itemnumber, $mark_lost_from, $force_mark_returned, $params) = @_; unless ( $mark_lost_from ) { # Temporary check to avoid regressions @@ -3832,7 +3797,7 @@ sub LostItem{ #When item is marked lost automatically cancel its outstanding transfers and set items holdingbranch to the transfer source branch (frombranch) if (my ( $datesent,$frombranch,$tobranch ) = GetTransfers($itemnumber)) { - ModItem({holdingbranch => $frombranch}, undef, $itemnumber); + Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ skip_record_index => $params->{skip_record_index} }); } my $transferdeleted = DeleteTransfer($itemnumber); } @@ -3895,17 +3860,16 @@ sub ProcessOfflineReturn { my $itemnumber = $item->itemnumber; my $issue = GetOpenIssue( $itemnumber ); if ( $issue ) { + my $leave_item_lost = C4::Context->preference("BlockReturnOfLostItems") ? 1 : 0; + ModDateLastSeen( $itemnumber, $leave_item_lost ); MarkIssueReturned( $issue->{borrowernumber}, $itemnumber, $operation->{timestamp}, ); - ModItem( - { renewals => 0, onloan => undef }, - $issue->{'biblionumber'}, - $itemnumber, - { log_action => 0 } - ); + $item->renewals(0); + $item->onloan(undef); + $item->store({ log_action => 0 }); return "Success."; } else { return "Item not issued."; @@ -4101,7 +4065,7 @@ sub GetAgeRestriction { } #Get how many days the borrower has to reach the age restriction - my @Today = split /-/, DateTime->today->ymd(); + my @Today = split /-/, dt_from_string()->ymd(); my $daysToAgeRestriction = Date_to_Days(@alloweddate) - Date_to_Days(@Today); #Negative days means the borrower went past the age restriction age return ($restriction_year, $daysToAgeRestriction); @@ -4206,6 +4170,10 @@ sub GetTopIssues { return @$rows; } +=head2 Internal methods + +=cut + sub _CalculateAndUpdateFine { my ($params) = @_; @@ -4282,7 +4250,6 @@ sub _item_denied_renewal { return 0; } - 1; __END__