Bugzilla – Attachment 78550 Details for
Bug 21327
Add a Modular Koha Core design
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 21327: Add Core::Circulation
Bug-21327-Add-CoreCirculation.patch (text/plain), 34.40 KB, created by
Benjamin Rokseth
on 2018-09-10 21:44:28 UTC
(
hide
)
Description:
Bug 21327: Add Core::Circulation
Filename:
MIME Type:
Creator:
Benjamin Rokseth
Created:
2018-09-10 21:44:28 UTC
Size:
34.40 KB
patch
obsolete
>From 3eea2ffeee63fcffa860331edcb0b74ca62ef722 Mon Sep 17 00:00:00 2001 >From: Benjamin Rokseth <benjamin.rokseth@deichman.no> >Date: Sun, 9 Sep 2018 23:17:00 +0200 >Subject: [PATCH] Bug 21327: Add Core::Circulation > >This is the Circulation subclass of Core. It is a work in progress, but ideally >all neccessary subfunctions should be overridable, and it should consist of >a bare minimum of functionality for handling circulation. >It is a rewrite of C4::Circulation. > >To use, add this in the config section in koha-conf.xml and restart plack: > ><core_modules> > <circulation>Core::Circulation</circulation> ></core_modules> >--- > Core/Circulation.pm | 913 ++++++++++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 913 insertions(+) > create mode 100644 Core/Circulation.pm > >diff --git a/Core/Circulation.pm b/Core/Circulation.pm >new file mode 100644 >index 0000000..4910613 >--- /dev/null >+++ b/Core/Circulation.pm >@@ -0,0 +1,913 @@ >+package Core::Circulation; >+ >+use strict; >+use warnings; >+ >+use Carp; >+use Data::Dumper; >+use Try::Tiny; >+ >+use Koha::Items; >+use Koha::Account; >+ >+use Core::Exceptions; >+use parent qw(Core::Main Core::Prefs); >+ >+=head >+ Constructor >+ object args: >+ item : item object >+ library : library object >+ patron : patron object >+ prefs : preferences override hashmap >+ object accessors: >+ checkout : checkout object >+ messages : legacy messages hashmap (for ui/SIP) >+ iteminfo : legacy iteminfo hashmap (for ui/SIP) >+=cut >+sub new { >+ my ($class, $args) = @_; >+ ($args->{item} and $args->{library}) or die "Missing required arg item or library"; >+ >+ my $self = $class->SUPER::new(); >+ $self = { %$self, # Inherent anything from parent >+ error => undef, # Error return >+ item => $args->{item}, # Mandatory >+ library => $args->{library}, # Mandatory >+ patron => $args->{patron}, # Mandatory for checkouts >+ }; >+ $self->{library} or die "Should not proceed with circulation without library branch!"; >+ >+ # # Default prefs with overrides from args >+ $self->{prefs} = Core::Prefs->new($args->{prefs}); # TODO: Is this cached? If not, we should >+ $self->{checkout} = Koha::Checkouts->find({itemnumber => $self->{item}->itemnumber}); >+ $self->{messages} = {}; >+ $self->{iteminfo} = $self->{item}->unblessed; >+ >+ bless $self, $class; >+ >+ return $self; >+} >+ >+# Dummy override for testing >+sub testOverride { >+ print "TEST: Core::Circulation called without overrides"; >+} >+ >+=head >+ Checkin >+ optional args: returnDate >+=cut >+sub Checkin { >+ my ($self, $returnDate) = @_; >+ warn "Checking in item " . $self->{item}->itemnumber . "\n"; >+ my $t0 = Time::HiRes::time(); >+ $returnDate ||= _today(); >+ >+ $self->testOverride(); >+ >+ # CHECKOUT >+ if ($self->{checkout}) { >+ $self->{patron} = $self->{checkout}->patron; >+ $self->{iteminfo} = { %{$self->{iteminfo}}, %{$self->{checkout}->unblessed} }; >+ $self->{iteminfo}->{overdue} = $self->{checkout}->is_overdue; >+ >+ if ($self->{item}->itemlost) { >+ $self->{messages}->{WasLost} = 1; >+ if (Koha::RefundLostItemFeeRules->should_refund({ >+ current_branch => $self->{library}->branchcode, >+ item_home_branch => $self->{item}->homebranch, >+ item_holding_branch => $self->{item}->holdingbranch, >+ })) { >+ $self->fixAccountForLostAndReturned(); >+ $self->{messages}->{Itemlost} = $self->{item}->barcode; >+ $self->{messages}->{LostItemFeeRefunded} = 1; >+ } >+ } >+ $self->fixOverduesOnReturn(); >+ } else { # NOT CHECKED OUT, JUST FIX STATUSES, TRANSFERS AND RETURN >+ $self->{error} = Core::Exception::Circulation::CheckinError->new(error => "Unable to checkin\n"); >+ if ($self->{item}->withdrawn) { >+ $self->{messages}->{Withdrawn} = $self->{item}->barcode; >+ } else { >+ $self->{messages}->{NotIssued} = $self->{item}->barcode; >+ } >+ } >+ >+ # TRANSFER >+ my $transfer = $self->{item}->get_transfer; >+ if ($transfer) { >+ if ($transfer->tobranch eq $self->{library}->branchcode) { >+ # ARRIVED AT RIGHT BRANCH >+ $transfer->datearrived(_today())->store; >+ } else { >+ # WRONG BRANCH - Should we fix transfers at once? >+ $self->{messages}->{WrongTransfer} = $transfer->tobranch; >+ $self->{messages}->{WrongTransferItem} = $self->{item}->itemnumber; >+ } >+ } >+ >+ # HOLD >+ my $hold = $self->getPendingHold; >+ if ($hold) { >+ if ($hold->branchcode ne $self->{library}->branchcode) { >+ $self->{messages}->{NeedsTransfer} = $hold->branchcode; >+ } >+ # Weird message return yes, but this is how Koha templates expect it >+ $self->{messages}->{ResFound} = { >+ %{$hold->unblessed}, >+ ResFound => sub {$hold->is_waiting ? return "Waiting" : return "Reserved"}, >+ }; >+ } >+ >+ # WRONG BRANCH - CREATE TRANSFER IF NOT ONE ALREADY >+ if ($self->{library}->branchcode ne $self->{item}->homebranch and not $transfer) { >+ Koha::Item::Transfer->new({ >+ itemnumber => $self->{item}->itemnumber, >+ frombranch => $self->{library}->branchcode, >+ tobranch => $self->{item}->homebranch, >+ datesent => _today(), >+ })->store; >+ $self->{messages}->{WasTransfered} = 1; >+ # IF AutomaticItemReturn false - set NeedsTransfer >+ $self->{prefs}->{AutomaticItemReturn} and $self->{messages}->{NeedsTransfer} = $self->{item}->homebranch; >+ } >+ >+ # TODO: Rewrite to Core? >+ C4::Stats::UpdateStats({ >+ branch => $self->{library}->branchcode, >+ type => "return", >+ itemnumber => $self->{item}->itemnumber, >+ itemtype => $self->{item}->itype, >+ borrowernumber => $self->{patron} ? $self->{patron}->borrowernumber : undef, >+ ccode => $self->{item}->ccode, >+ }); >+ >+ # TODO: Borrower message (SendCirculationAlert) or HANDLE IN PrintSlip ? >+ $self->sendCirculationAlert(); >+ >+ $self->removeOverdueDebarments(); >+ >+ # RETURN >+ if ($self->{checkout}) { >+ $self->{checkout}->returndate($returnDate)->store; >+ if ($self->{checkout}->patron->privacy == 2) { >+ $self->{checkout}->borrowernumber(undef)->store; >+ } else { >+ $self->{patron} and $self->{item}->last_returned_by( $self->{patron} ); >+ } >+ # MERGE TABLES - PLEASE >+ my $old_checkout = Koha::Old::Checkout->new($self->{checkout}->unblessed)->store; >+ $self->{checkout}->delete; >+ # TODO: Needs to decide if $self->{checkout} should be emptied upon checkout or not >+ # As templates require checkout object we must leave it for now >+ # undef $self->{checkout}; >+ $self->{messages}->{WasReturned} = 1; >+ } else { >+ $self->{error} = Core::Exception::Circulation::CheckinError->new(error => "Unable to checkin\n"); >+ $self->{messages}->{WasReturned} = 0; >+ $self->{messages}->{DataCorrupted} = 1; >+ } >+ >+ # TODO: rewrite all methods to overridable circulation methods >+ # Transfer to returnbranch if Automatic transfer set or append message NeedsTransfer >+ my $returnbranch = $self->getBranchItemRule(); >+ my $is_in_rotating_collection = C4::RotatingCollections::isItemInAnyCollection( $self->{item}->itemnumber ); >+ >+ if (!$is_in_rotating_collection && $self->{messages}->{NotIssued} and !$hold and ($self->{item}->homebranch ne $returnbranch) and not $self->{messages}->{WrongTransfer}) { >+ if ( $self->{prefs}->{AutomaticItemReturn} or ( $self->{prefs}->{UseBranchTransferLimits} >+ and ! C4::Circulation::IsBranchTransferAllowed($self->{item}->homebranch, $returnbranch, $self->{item}->{$self->{prefs}->{BranchTransferLimitsType}} ) >+ )) { >+ # AutomaticItemReturn: transfer to returnbranch >+ $transfer and $transfer->set({ >+ datearrived => \"NOW()", >+ comments => undef, >+ }); >+ Koha::Item::Transfer->new({ >+ itemnumber => $self->{item}->itemnumber, >+ frombranch => $self->{library}->branchcode, >+ tobranch => $returnbranch, >+ datesent => _today(), >+ })->store; >+ $self->{prefs}->{ReturnToShelvingCart} and $self->cartToShelf(); >+ $self->{messages}->{WasTransfered} = 1; >+ } else { >+ $self->{messages}->{NeedsTransfer} = $returnbranch; >+ } >+ } >+ >+ # UPDATE ITEM >+ $self->{item}->set({ >+ holdingbranch => $returnbranch ? $returnbranch : $self->{library}->branchcode, >+ onloan => undef, >+ datelastseen => _today(), >+ itemlost => 0, >+ })->store; >+ >+ my $dt = Time::HiRes::time() - $t0; >+ printf STDERR "[%s] Checkin called : %s - elapsed %.3fs\n", ref $self, scalar(gmtime()), $dt; >+ return $self; >+} >+ >+# IN -> $self, $dateDue[, $cancelReserve, $issueDate, $sipMode] >+sub Checkout { >+ my ($self, $dateDue, $cancelReserve, $issueDate, $sipMode, $params) = @_; # Surely most params can be skipped here >+ $self->{patron} and $self->{library} or Core::Exception::Circulation::CheckinError->throw(error => "No checkout without patron and/or library!\n"); >+ >+ # extra params >+ my $onsite_checkout = $params && $params->{onsite_checkout} ? 1 : 0; >+ my $switch_onsite_checkout = $params && $params->{switch_onsite_checkout}; >+ my $auto_renew = $params && $params->{auto_renew}; >+ >+ my $t0 = Time::HiRes::time(); >+ warn "Checking out item " . $self->{item}->barcode . " for patron " . $self->{patron}->borrowernumber . "\n"; >+ $issueDate ||= _today(); >+ >+ # PREFS THAT APPLY >+ # AllowReturnToBranch >+ my $circControlBranch = $self->getCircControlBranch(); >+ >+ # ALREADY CHECKED OUT? >+ if ($self->{checkout}) { >+ if ($self->{checkout}->patron->borrowernumber eq $self->{patron}->borrowernumber) { >+ if ($self->canItemBeRenewed() and not $switch_onsite_checkout) { >+ $self->Renew( $dateDue ); >+ } >+ } else { >+ # Wrong checkout, return first? >+ if ($self->canItemBeReturned() and not $switch_onsite_checkout) { >+ $self->Checkin(); >+ } >+ } >+ } else { >+ >+ # TRANSFERS AND HOLDS >+ my $transfer = $self->{item}->get_transfer; >+ if ($transfer) { >+ if ($transfer->tobranch eq $self->{library}->branchcode) { >+ $transfer->datearrived(_today())->store; # Finish transfer >+ } else { >+ # Update transfer, or should we close and create new? >+ $transfer->frombranch($self->{library}->branchcode)->store; >+ } >+ } >+ >+ # TODO: C4::Circulation.pm:1317 >+ # MoveReserve( $item->{'itemnumber'}, $borrower->{'borrowernumber'}, $cancelreserve ); >+ # -> C4::Reserves.pm:1712 >+ # -> CheckReserves C4::Reserves.pm:626 >+ >+ # HOLD >+ my $hold = $self->getPendingHold(); >+ if ($hold) { >+ # Right patron - fill hold >+ if ($hold->borrowernumber == $self->{patron}->borrowernumber) { >+ $hold->set({found => "F", priority => 0}); >+ # Merge tables - PLEASE! >+ Koha::Old::Hold->new( $hold->unblessed() )->store(); >+ $hold->delete(); >+ } else { >+ # TODO: Checkout should be aborted here? >+ $self->{error} = Core::Exception::Circulation::CheckoutError->new("Unable to checkout - hold for another patron!"); >+ } >+ } >+ >+ # CIRCULATION RULES >+ my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule({ >+ categorycode => $self->{patron}->{categorycode}, >+ itemtype => $self->{item}->itype, >+ branchcode => $self->{library}->branchcode, >+ }); >+ >+ # CHECKOUT >+ # TODO: rewrite calculate dateDue to overridable module >+ unless ($dateDue) { >+ my $itype = $self->{item}->effective_itemtype; >+ $dateDue = C4::Circulation::CalcDateDue( $issueDate, $itype, $self->{library}->branchcode, $self->{patron}->unblessed, 1 ); >+ >+ } >+ $dateDue->truncate( to => "minute" ); >+ >+ # TODO: auto_renew and onsite_checkout >+ $self->{checkout} = Koha::Checkout->new({ >+ borrowernumber => $self->{patron}->borrowernumber, >+ itemnumber => $self->{item}->itemnumber, >+ issuedate => $issueDate, >+ date_due => $dateDue, >+ branchcode => $self->{library}->branchcode, >+ })->store; >+ >+ # Why was totalissues never set? >+ my $totalissues = $self->{item}->biblioitem->totalissues // 0; >+ $self->{item}->biblioitem->totalissues($totalissues + 1); >+ >+ # LOST ITEM REFUND >+ if ($self->{item}->itemlost) { >+ # REFUND LOST ITEM FEE >+ if (Koha::RefundLostItemFeeRules->should_refund({ >+ current_branch => $self->{library}->branchcode, >+ item_home_branch => $self->{item}->homebranch, >+ item_holding_branch => $self->{item}->holdingbranch, >+ })) { >+ $self->fixAccountForLostAndReturned(); >+ } >+ } >+ >+ # UPDATE ITEM >+ $self->{item}->set({ >+ issues => $self->{item}->issues + 1, >+ holdingbranch => $self->{library}->branchcode, >+ itemlost => 0, >+ onloan => $dateDue->ymd(), >+ datelastborrowed => _today(), >+ datelastseen => _today(), >+ })->store; >+ >+ C4::Stats::UpdateStats({ >+ branch => $self->{library}->branchcode, >+ type => "issue", >+ other => ( $sipMode ? "SIP-$sipMode" : "" ), >+ itemnumber => $self->{item}->itemnumber, >+ itemtype => $self->{item}->itype, >+ location => $self->{item}->location, >+ borrowernumber => $self->{patron}->borrowernumber, >+ ccode => $self->{item}->ccode, >+ }); >+ >+ # TODO: Borrower message (SendCirculationAlert) HANDLE IN PrintSlip ? >+ $self->sendCirculationAlert(); >+ >+ $self->removeOverdueDebarments(); >+ } >+ my $dt = Time::HiRes::time() - $t0; >+ printf STDERR "[%s] Checkout called : %s - elapsed %.3fs\n", ref $self, scalar(gmtime()), $dt; >+ return $self; >+ >+} >+ >+sub Renew { >+ my ($self, $dateDue, $lastRenewedDate) = @_; # Probably can skip most of these >+ $self->{patron} and $self->{library} or die "Do we really need patron and library?"; >+ warn "Renewing item " . $self->{item}->itemnumber . " for patron " . $self->{patron}->borrowernumber . "\n"; >+ my $t0 = Time::HiRes::time(); >+ >+ $self->{checkout} or die "No checkout, cannot renew"; >+ $lastRenewedDate ||= _today(); >+ >+ # PREFS THAT APPLY >+ # CalculateFinesOnReturn >+ # RenewalPeriodBase >+ # RenewalSendNotice >+ # CircControl >+ >+ $self->fixOverduesOnReturn(); >+ $self->canItemBeRenewed() or return $self; >+ >+ # default to checkout branch >+ my $circControlBranch = $self->getCircControlBranch(); >+ >+ # If the due date wasn't specified, calculate it by adding the >+ # book's loan length to today's date or the current due date >+ # based on the value of the RenewalPeriodBase syspref. >+ if ( defined $dateDue && ref $dateDue ne "DateTime" ) { >+ carp "Invalid date passed to Renew."; >+ return; >+ } >+ # TODO: Adjust to use calendar >+ unless ($dateDue) { >+ my $itemType = $self->{item}->effective_itemtype; >+ if ($self->{prefs}->{RenewalPeriodBase} eq "date_due") { >+ $dateDue = _mysqldate2dt($self->{checkout}->date_due); >+ } else { >+ $dateDue = _today(); >+ } >+ $dateDue = C4::Circulation::CalcDateDue($dateDue, $itemType, $circControlBranch, $self->{patron}->unblessed, 1); >+ } >+ >+ # UPDATE CHECKOUT AND ITEM >+ my $checkoutRenewals = $self->{checkout}->renewals || 0; >+ my $itemRenewals = $self->{item}->renewals || 0; >+ $self->{checkout}->set({ >+ date_due => _dt2mysqldate($dateDue), >+ lastreneweddate => $lastRenewedDate, >+ renewals => $checkoutRenewals + 1, >+ })->store; >+ >+ $self->{item}->set({ >+ renewals => $itemRenewals + 1, >+ onloan => _dt2mysqldate($dateDue), >+ })->store; >+ >+ # TODO: Renewal notice? >+ $self->removeOverdueDebarments(); >+ >+ # STATS >+ C4::Circulation::UpdateStats({ >+ branch => $self->{checkout}->branchcode, >+ type => "renew", >+ amount => undef, >+ itemnumber => $self->{item}->itemnumber, >+ itemtype => $self->{item}->itype, >+ location => $self->{item}->location, >+ borrowernumber => $self->{patron}->borrowernumber, >+ ccode => $self->{item}->ccode, >+ }); >+ my $dt = Time::HiRes::time() - $t0; >+ printf STDERR "[%s] Renew called : %s - elapsed %.3fs\n", ref $self, scalar(gmtime()), $dt; >+ return $self; >+} >+ >+=head >+ getBranchItemRule >+ use legacy C4::Circulation::GetBranchItemRule >+=cut >+sub getBranchItemRule { >+ my $self = shift; >+ use C4::Circulation; >+ my $hbr = C4::Circulation::GetBranchItemRule($self->{item}->homebranch, $self->{item}->itype)->{'returnbranch'} || "homebranch"; >+ return $self->{item}->{$hbr} || $self->{item}->homebranch ; >+} >+ >+=head2 $self->cartToShelf >+ >+ Set the current shelving location of the item record >+ to its stored permanent shelving location. This is >+ primarily used to indicate when an item whose current >+ location is a special processing ('PROC') or shelving cart >+ ('CART') location is back in the stacks. >+ >+=cut >+ >+sub cartToShelf { >+ my $self = shift; >+ if ($self->{item}->location eq "CART") { >+ $self->{item}->set({ location => $self->{item}->permanent_location })->store; >+ } >+ return $self; >+} >+ >+=head2 $self->getCircControlBranch >+ >+ Return the library code to be used to determine which circulation >+ policy applies to a transaction. Looks up the CircControl and >+ HomeOrHoldingBranch system preferences. >+ >+=cut >+ >+sub getCircControlBranch { >+ my $self = shift; >+ my $branch; >+ >+ if ($self->{prefs}->{CircControl} eq "PickupLibrary" and $self->{checkout}) { >+ $branch = $self->{checkout}->branchcode; # Why C4::Context->userenv->{'branch'} in C4::Circulation? >+ } elsif ($self->{prefs}->{CircControl} eq "PatronLibrary") { >+ $branch = $self->{patron}->branchcode; >+ } else { >+ my $branchfield = $self->{prefs}->{HomeOrHoldingBranch} || "homebranch"; >+ $branch = $self->{item}->{$branchfield} || $self->{item}->{homebranch}; >+ } >+ return $branch; >+} >+ >+=head $self->fixAccountForLostAndReturned >+ If item lost, refund and mark as lost returned >+=cut >+sub fixAccountForLostAndReturned { >+ my $self = shift; >+ my $acc = Koha::Account::Lines->search( >+ { >+ itemnumber => $self->{item}->itemnumber, >+ accounttype => { -in => [ "L", "Rep" ] }, >+ }, >+ { >+ order_by => { -desc => [ "date", "accountno" ] } >+ } >+ )->next(); >+ return unless $acc; >+ >+ $acc->accounttype("LR"); >+ $acc->store(); >+ >+ my $account = Koha::Account->new( { patron_id => $acc->borrowernumber } ); >+ my $credit_id = $account->pay( >+ { >+ amount => $acc->amount, >+ description => "Item Returned " . $self->{item}->barcode, >+ account_type => "CR", >+ offset_type => "Lost Item Return", >+ accounlines => [$acc], >+ >+ } >+ ); >+ >+ return $credit_id; >+} >+ >+=head >+ ref C4::Circulation.pm L2369 >+ From what I could decipher, this sets fine accounttype to F in normal cases, rest is handled in cronjobs >+ TODO: >+ if params exemptfine -> add line with credit and type "Forgiven" >+ if params dropbox -> add line with credit and type "Dropbox" >+ >+=cut >+sub fixOverduesOnReturn { >+ my $self = shift; >+ >+ $self->{patron} and $self->{item} or croak "Missing patron or item"; >+ >+ my $accountline = Koha::Account::Lines->search( >+ { >+ borrowernumber => $self->{patron}->borrowernumber, >+ itemnumber => $self->{item}->itemnumber, >+ -or => [ >+ accounttype => "FU", >+ accounttype => "O", >+ ], >+ } >+ )->next(); >+ $accountline and $accountline->accounttype("F")->store; >+ return $self; >+} >+ >+=head >+ ref C4::Circulation L676 >+ >+=cut >+sub canItemBeIssued { >+ my ($self, $dateDue, $ignoreReserves) = @_; >+ # my ( $borrower, $barcode, $duedate, $inprocess, $ignore_reserves, $params ) = @_; >+ my $needsconfirmation; # filled with problems that needs confirmations >+ my $issuingimpossible; # filled with problems that causes the issue to be IMPOSSIBLE >+ my $alerts; # filled with messages that shouldn't stop issuing, but the librarian should be aware of. >+ my $messages; # filled with information messages-> that should be displayed. >+ >+ # my $onsite_checkout = $params->{onsite_checkout} || 0; >+ # my $override_high_holds = $params->{override_high_holds} || 0; >+ >+ # PREFS >+ # noissuescharge >+ # AllowFineOverride >+ # AllFinesNeedOverride >+ # NoIssuesChargeGuarantees >+ # This should be returned before calling Core::Circulation >+ $self->{item}->itemnumber or return ($self, {UNKNOWN_BARCODE => 1}); >+ # TODO: should dateDue be automatically calculated? >+ # Makes no sense to ask for dateDue >+# unless ($dateDue) { >+# my $itype = $self->{item}->effective_itemtype; >+# $dateDue = C4::Circulation::CalcDateDue( _today(), $itype, $self->{library}->branchcode, $self->{patron}->unblessed, 1 ); >+ >+# } >+# $dateDue->truncate( to => "minute" ); >+ >+ # PATRON FLAGS >+ $self->{patron}->gonenoaddress and $issuingimpossible->{GNA} = 1; >+ $self->{patron}->lost and $issuingimpossible->{CARD_LOST} = 1; >+ $self->{patron}->debarred and $issuingimpossible->{DEBARRED} = 1; >+ $self->{patron}->is_expired and $issuingimpossible->{EXPIRED} = 1; >+ >+ # DEBTS >+ # Note: Koha::Account is different than other Koha Objects >+ my $debt = Koha::Account->new({patron_id => $self->{patron}->borrowernumber})->balance; >+ if ($debt > 0) { >+ # TODO: if $self->{prefs}->{IssuingInProcess} >+ if ($debt > $self->{prefs}->{noissuescharge} and not $self->{prefs}->{AllowFineOverride}) { >+ $issuingimpossible->{DEBT} = sprintf( "%.2f", $debt ); >+ } >+ } elsif ($self->{prefs}->{NoIssuesChargeGuarantees}) { >+ my $guarantees_charges; >+ foreach my $g ( @$self->{patron}->guarantees ) { >+ $guarantees_charges += Koha::Account->new({patron_id => $g->borrowernumber})->balance; >+ } >+ if ( $guarantees_charges > $self->{prefs}->{NoIssuesChargeGuarantees} and not $self->{prefs}->{AllowFineOverride}) { >+ $issuingimpossible->{DEBT_GUARANTEES} = sprintf( "%.2f", $$guarantees_charges ); >+ } >+ } >+ >+ # DEBARRED >+ if ( my $debarred_date = $self->{patron}->is_debarred ) { >+ if ($debarred_date eq "9999-12-31") { >+ $issuingimpossible->{USERBLOCKEDNOENDDATE} = $debarred_date; >+ } else { >+ $issuingimpossible->{USERBLOCKEDWITHENDDATE} = $debarred_date; >+ } >+ } elsif ( my $num_overdues = $self->{patron}->has_overdues ) { >+ if ( C4::Context->preference("OverduesBlockCirc") eq "block") { >+ $issuingimpossible->{USERBLOCKEDOVERDUE} = $num_overdues; >+ } >+ elsif ( C4::Context->preference("OverduesBlockCirc") eq "confirmation") { >+ $needsconfirmation->{USERBLOCKEDOVERDUE} = $num_overdues; >+ } >+ } >+ >+ # ALREADY CHECKED OUT >+ if ($self->{checkout}) { >+ # SAME PATRON >+ if ($self->{checkout}->borrowernumber eq $self->{patron}->borrowernumber ) { >+ if ( $self->{checkout}->onsite_checkout and $self->{prefs}->{SwitchOnSiteCheckouts}) { >+ $messages->{ONSITE_CHECKOUT_WILL_BE_SWITCHED} = 1; >+ } else { >+ my ($ok, $error) = $self->canItemBeRenewed(); >+ if ($ok) { >+ if ( $error eq "onsite_checkout" ) { >+ $issuingimpossible->{NO_RENEWAL_FOR_ONSITE_CHECKOUTS} = 1; >+ } else { >+ $issuingimpossible->{NO_MORE_RENEWALS} = 1; >+ } >+ } else { >+ $needsconfirmation->{RENEW_ISSUE} = 1; >+ } >+ } >+ # DIFFERENT PATRON >+ } else { >+ my ( $ok, $message ) = $self->canItemBeReturned(); >+ if ( $ok ) { >+ $needsconfirmation->{ISSUED_TO_ANOTHER} = 1; >+ # THESE ARE JUST SILLY - should be removed >+ $needsconfirmation->{issued_firstname} = $self->{checkout}->patron->firstname; >+ $needsconfirmation->{issued_surname} = $self->{checkout}->patron->surname; >+ $needsconfirmation->{issued_cardnumber} = $self->{checkout}->patron->cardnumber; >+ $needsconfirmation->{issued_borrowernumber} = $self->{checkout}->patron->borrowernumber; >+ } else { >+ $issuingimpossible->{RETURN_IMPOSSIBLE} = 1; >+ $issuingimpossible->{branch_to_return} = $message; >+ } >+ } >+ } >+ >+ # TODO: Too Many checkouts >+ # JB34 CHECKS IF BORROWERS DON'T HAVE ISSUE TOO MANY BOOKS >+ # >+ # my $switch_onsite_checkout = ( >+ # C4::Context->preference('SwitchOnSiteCheckouts') >+ # and $issue >+ # and $issue->onsite_checkout >+ # and $issue->borrowernumber == $borrower->{'borrowernumber'} ? 1 : 0 ); >+ # my $toomany = TooMany( $borrower, $item->{biblionumber}, $item, { onsite_checkout => $onsite_checkout, switch_onsite_checkout => $switch_onsite_checkout, } ); >+ # # if TooMany max_allowed returns 0 the user doesn't have permission to check out this book >+ # if ( $toomany && not exists $needsconfirmation->{RENEW_ISSUE} ) { >+ # if ( $toomany->{max_allowed} == 0 ) { >+ # $needsconfirmation->{PATRON_CANT} = 1; >+ # } >+ # if ( C4::Context->preference("AllowTooManyOverride") ) { >+ # $needsconfirmation->{TOO_MANY} = $toomany->{reason}; >+ # $needsconfirmation->{current_loan_count} = $toomany->{count}; >+ # $needsconfirmation->{max_loans_allowed} = $toomany->{max_allowed}; >+ # } else { >+ # $issuingimpossible->{TOO_MANY} = $toomany->{reason}; >+ # $issuingimpossible->{current_loan_count} = $toomany->{count}; >+ # $issuingimpossible->{max_loans_allowed} = $toomany->{max_allowed}; >+ # } >+ # } >+ >+ # CHECKPREVCHECKOUT: CHECK IF ITEM HAS EVER BEEN LENT TO PATRON >+ if ($self->{patron}->wants_check_for_previous_checkout and $self->{patron}->do_check_for_previous_checkout($self->{item})) { >+ $needsconfirmation->{PREVISSUE} = 1 >+ } >+ >+ # ITEM BASED RESTRICTIONS >+ # TODO: >+ # Age restriction >+ # pref item-level_itypes >+ # independentBranches >+ # rental charges >+ # decreaseLoanHighHolds >+ >+ $self->{item}->withdrawn > 0 and $issuingimpossible->{WTHDRAWN} = 1; >+ if ($self->{item}->restricted and $self->{item}->restricted > 0) { >+ $issuingimpossible->{RESTRICTED} = 1; >+ } >+ >+ if ($self->{item}->notforloan) { >+ if ($self->{prefs}->{AllowNotForLoanOverride}) { >+ $issuingimpossible->{NOT_FOR_LOAN} = 1; >+ $issuingimpossible->{item_notforloan} = $self->{item}->notforloan; # WHY THIS? >+ } else { >+ $needsconfirmation->{NOT_FOR_LOAN_FORCING} = 1; >+ $needsconfirmation->{item_notforloan} = $self->{item}->notforloan; # WHY THIS? >+ } >+ } >+ >+ if ( $self->{item}->itemlost and $self->{prefs}->{IssueLostItem} ne "nothing" ) { >+ my $av = Koha::AuthorisedValues->search({ category => 'LOST', authorised_value => $self->{item}->itemlost }); >+ my $code = $av->count ? $av->next->lib : ""; >+ ( $self->{prefs}->{IssueLostItem} eq "confirm" ) and $needsconfirmation->{ITEM_LOST} = $code; >+ ( $self->{prefs}->{IssueLostItem} eq "alert" ) and $alerts->{ITEM_LOST} = $code; # TODO: alert only used once, should be removed >+ } >+ >+ unless ( $ignoreReserves ) { >+ my $hold = $self->getPendingHold(); >+ if ($hold) { >+ if ($hold->is_waiting) { >+ $needsconfirmation->{RESERVE_WAITING} = 1; >+ $needsconfirmation->{'reswaitingdate'} = $hold->waitingdate; >+ } else { >+ $needsconfirmation->{RESERVED} = 1; >+ } >+ >+ $needsconfirmation->{'resfirstname'} = $hold->patron->firstname; >+ $needsconfirmation->{'ressurname'} = $hold->patron->surname; >+ $needsconfirmation->{'rescardnumber'} = $hold->patron->cardnumber; >+ $needsconfirmation->{'resborrowernumber'} = $hold->patron->borrowernumber; >+ $needsconfirmation->{'resbranchcode'} = $hold->patron->branchcode; >+ $needsconfirmation->{'resreservedate'} = $hold->reservedate; >+ } >+ } >+ >+ # TODO: consider if this is neccessary >+ if ( not $self->{prefs}->{AllowMultipleIssuesOnABiblio} and not $issuingimpossible->{NO_MORE_RENEWALS} and not $needsconfirmation->{RENEW_ISSUE}) { >+ # don't do the multiple loans per bib check if we've >+ # already determined that we've got a loan on the same item >+ unless ($self->{item}->biblio->subscriptions->count) { >+ my $checkouts = Koha::Checkouts->search({ >+ borrowernumber => $self->{patron}->borrowernumber, >+ biblionumber => $self->{item}->biblionumber, >+ }, { >+ join => 'item', >+ }); >+ if ( $checkouts->count ) { >+ $needsconfirmation->{BIBLIO_ALREADY_ISSUED} = 1; >+ } >+ } >+ } >+ >+ return ( $issuingimpossible, $needsconfirmation, $alerts, $messages ); >+} >+ >+=head >+ $self->canItemBeRenewed >+ if no holds binds item, allow renewal >+ args: >+ override => bool >+ return: >+ bool => can/cannot be renewed >+ msg => legacy error msg string >+=cut >+ >+sub canItemBeRenewed { >+ my ($self, $override) = @_; >+ $self->{checkout} or return ( 0, "no_checkout" ); >+ >+ # PREFS THAT MIGHT APPLY >+ # OverduesBlockRenewing >+ # RestrictionBlockRenewing >+ >+ # Too few items to fill holds means item cannot be renewed >+ if (not $self->canItemFillHold and not $override) { >+ return ( 0, "on_hold" ); >+ } >+ >+ # CIRCULATION RULES >+ my $circ_rule = Koha::IssuingRules->get_effective_issuing_rule( >+ { categorycode => $self->{patron}->categorycode, >+ itemtype => $self->{item}->itype, >+ branchcode => $self->{checkout}->branchcode, >+ } >+ ); >+ >+ my $renewals = $self->{checkout}->renewals || 0; >+ $circ_rule or return ( 0, "too_many" ); # MISSING CIRC RULE - too_many in original, but should perhaps be better worded >+ $circ_rule->renewalsallowed <= $renewals or return ( 0, "too_many" ); >+ >+ >+ $self->{patron}->is_debarred and return ( 0, "restriction" ); >+ ($self->{patron}->has_overdues or $self->{checkout}->is_overdue) and return ( 0, "overdue" ); >+ >+ # TODO: AUTORENEW - USE CASE? >+ >+ # NO RENEWAL BEFORE >+ if ( defined $circ_rule->norenewalbefore and $circ_rule->norenewalbefore ne "" ) { >+ my $dt = _mysqldate2dt($self->{checkout}->date_due); >+ my $soonestrenewal = $dt->subtract( $circ_rule->lengthunit => $circ_rule->norenewalbefore ); >+ if ( $soonestrenewal > DateTime->now() ) { >+ return ( 0, "too_soon" ); >+ } >+ } >+ return (1, undef); >+} >+ >+sub canItemBeReturned { >+ my $self = shift; >+ my $allowreturntobranch = $self->{prefs}->{AllowReturnToBranch}; >+ >+ # assume return is allowed to start >+ my $allowed = 1; >+ my $message; >+ >+ # identify all cases where return is forbidden >+ if ($allowreturntobranch eq "homebranch" && $self->{library}->branchcode ne $self->{item}->homebranch) { >+ $allowed = 0; >+ $message = $self->{item}->homebranch; >+ } elsif ($allowreturntobranch eq "holdingbranch" && $self->{library}->branchcode ne $self->{item}->holdingbranch) { >+ $allowed = 0; >+ $message = $self->{item}->holdingbranch; >+ } elsif ($allowreturntobranch eq "homeorholdingbranch" && $self->{library}->branchcode ne $self->{item}->homebranch >+ && $self->{library}->branchcode ne $self->{item}->holdingbranch) { >+ $allowed = 0; >+ $message = $self->{item}->homebranch; # FIXME: choice of homebranch is arbitrary >+ } >+ >+ return ($allowed, $message); >+} >+ >+=head >+ Check if item can fill a pending hold >+ reservable means: >+ - item not found (Waiting or in Transfer) >+ - item not lost, withdrawn >+ - item notforloan not code > 0 >+ - item not damaged (unless AllowHoldsOnDamagedItems) >+ - number of reservable items is greater than number of pending holds >+ - something something onshelfholds >+ >+ ref: C4::Reserves::CanItemBeReserved L282 >+=cut >+sub canItemFillHold { >+ my $self = shift; >+ >+ # PREFS >+ # AllowRenewalIfOtherItemsAvailable >+ # item-level_itypes >+ # IndependentBranches >+ # canreservefromotherbranches >+ # AllowHoldsOnDamagedItems >+ >+ my $reservableItems = Koha::Items->search({ >+ "me.biblionumber" => $self->{item}->biblionumber, >+ itemlost => 0, >+ withdrawn => 0, >+ notforloan => { "<=" => 0 }, >+ "reserves.found" => { "!=" => undef }, >+ }, >+ { join => "reserves" }); >+ >+ if ($reservableItems->count < $self->getPendingHoldsByBiblio->count) { >+ return 0; >+ } else { >+ return 1; >+ } >+} >+=head >+ return next hold, item or biblio level >+=cut >+sub getPendingHold { >+ my $self = shift; >+ my $itemholds = $self->{item}->current_holds; >+ my $biblioholds = Koha::Holds->search({biblionumber => $self->{item}->biblionumber, found => undef, suspend => 0}, { order_by => { -asc => "priority" }}); >+ my $hold = $itemholds->count ? $itemholds->next : $biblioholds->count ? $biblioholds->next : undef; >+ return $hold; >+} >+ >+=head >+ return pending holds on biblio level >+=cut >+sub getPendingHoldsByBiblio { >+ my $self = shift; >+ return Koha::Holds->search({biblionumber => $self->{item}->biblionumber, found => undef, suspend => 0}, { order_by => { -asc => "priority" }}); >+} >+ >+sub _mysqldate2dt { >+ my $datestring = shift; >+ my $strp = DateTime::Format::Strptime->new( pattern => "%Y-%m-%d %H:%M:%S" ); >+ return $strp->parse_datetime( $datestring ); >+} >+ >+sub _dt2mysqldate { >+ my $dt = shift; >+ if (ref $dt ne "DateTime") { >+ return $dt; >+ } else { >+ return $dt->strftime("%F %T"); >+ } >+} >+ >+sub _ymd2dt { >+ my $ymdstring = shift; >+ my $strp = DateTime::Format::Strptime->new( pattern => "%Y-%m-%d" ); >+ return $strp->parse_datetime( $ymdstring ); >+} >+ >+sub _today { >+ return DateTime->now()->ymd; >+} >+ >+sub sendCirculationAlert { >+ # TODO >+} >+ >+=head >+ Remove any OVERDUES related debarment if the borrower has no overdues >+=cut >+sub removeOverdueDebarments { >+ my $self = shift; >+ return unless $self->{patron}; >+ if ( $self->{patron}->is_debarred >+ && ! $self->{patron}->has_overdues >+ && @{ Koha::Patron::Debarments::GetDebarments({ borrowernumber => $self->{patron}->borrowernumber, type => "OVERDUES" }) } >+ ) { >+ Koha::Patron::Debarments::DelUniqueDebarment({ borrowernumber => $self->{patron}->borrowernumber, type => "OVERDUES" }); >+ } >+} >+ >+1; >-- >2.1.4
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 21327
:
78544
|
78545
|
78546
|
78547
|
78549
| 78550 |
78551
|
78552