package Koha::Checkout; # Copyright ByWater Solutions 2015 # Copyright 2016 Koha Development Team # # 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 DateTime; use Try::Tiny qw( catch try ); use C4::Circulation qw( AddRenewal CanBookBeRenewed LostItem MarkIssueReturned ); use Koha::Booking; use Koha::Checkouts::Renewals; use Koha::Checkouts::ReturnClaims; use Koha::Database; use Koha::DateUtils qw( dt_from_string ); use Koha::ILL::ISO18626::Request; use Koha::Items; use Koha::Libraries; use base qw(Koha::Object); =head1 NAME Koha::Checkout - Koha Checkout object class =head1 API =head2 Class methods =cut =head3 is_overdue my $is_overdue = $checkout->is_overdue( [ $reference_dt ] ); Return 1 if the checkout is overdue. A reference date can be passed, in this case it will be used, otherwise today will be the reference date. =cut sub is_overdue { my ( $self, $dt ) = @_; $dt ||= dt_from_string(); my $is_overdue = DateTime->compare( dt_from_string( $self->date_due, 'sql' ), $dt ) == -1 ? 1 : 0; return $is_overdue; } =head3 item my $item = $checkout->item; Return the checked out item =cut sub item { my ($self) = @_; my $item_rs = $self->_result->item; return Koha::Item->_new_from_dbic($item_rs); } =head3 account_lines my $account_lines = $checkout->account_lines; Return the checked out account_lines =cut sub account_lines { my ($self) = @_; my $account_lines_rs = $self->_result->account_lines; return Koha::Account::Lines->_new_from_dbic($account_lines_rs); } =head3 overdue_fines my $fines = $checkout->overdue_fines; Return the account lines for just the overdue fines =cut sub overdue_fines { my ($self) = @_; my $account_lines_rs = $self->_result->account_lines->search( { debit_type_code => 'OVERDUE' } ); return Koha::Account::Lines->_new_from_dbic($account_lines_rs); } =head3 library my $library = $checkout->library; Return the library in which the transaction took place =cut sub library { my ($self) = @_; my $library_rs = $self->_result->library; return Koha::Library->_new_from_dbic($library_rs); } =head3 patron my $patron = $checkout->patron Return the patron for who the checkout has been done =cut sub patron { my ($self) = @_; my $patron_rs = $self->_result->patron; return Koha::Patron->_new_from_dbic($patron_rs); } =head3 iso18626_request Returns the related Koha::ILL::ISO18626::Request object for this checkout =cut sub iso18626_request { my ($self) = @_; my $rs = $self->_result->iso18626_request; return unless $rs; return Koha::ILL::ISO18626::Request->_new_from_dbic($rs); } =head3 issuer my $issuer = $checkout->issuer Return the patron by whom the checkout was done =cut sub issuer { my ($self) = @_; my $issuer_rs = $self->_result->issuer; return unless $issuer_rs; return Koha::Patron->_new_from_dbic($issuer_rs); } =head3 renewals my $renewals = $checkout->renewals; Return a Koha::Checkouts::Renewals set attached to this checkout =cut sub renewals { my ($self) = @_; my $renewals_rs = $self->_result->renewals; return unless $renewals_rs; return Koha::Checkouts::Renewals->_new_from_dbic($renewals_rs); } =head3 booking my $booking = $checkout->booking; Return the linked booking =cut sub booking { my ($self) = @_; my $booking_rs = $self->_result->booking; return unless $booking_rs; return Koha::Booking->_new_from_dbic($booking_rs); } =head3 attempt_auto_renew my ($success, $error, $updated) = $checkout->auto_renew({ confirm => 1 }); Attempt to automatically renew a book. Return error reason if it cannot be renewed. Also return whether a change has been made to avoid notifying on more than one attempt. If not passed confirm, we will only report and no changes will be made. =cut sub attempt_auto_renew { my ( $self, $params ) = @_; my $confirm = $params->{confirm} // 0; # CanBookBeRenewed returns 'auto_renew' when the renewal should be done by this script my ( $ok, $error ) = C4::Circulation::CanBookBeRenewed( $self->patron, $self, undef, 1 ); my $store_error; if ( $error eq 'auto_renew' || $error eq 'auto_renew_final' || $error eq 'auto_unseen_final' ) { if ($confirm) { my $date_due = C4::Circulation::AddRenewal( { borrowernumber => $self->borrowernumber, itemnumber => $self->itemnumber, branch => $self->branchcode, seen => 0, automatic => 1, } ); $store_error = $error eq 'auto_renew' ? undef : $error; $self->auto_renew_error($store_error)->store; } return ( 1, $store_error, 1 ); } else { my $updated = 0; if ( !$self->auto_renew_error || $error ne $self->auto_renew_error ) { $updated = 1 unless ( $self->auto_renew_error && ( $self->auto_renew_error eq 'auto_renew_final' && $error eq 'too_many' || $self->auto_renew_error eq 'auto_unseen_final' && $error eq 'too_unseen' ) ); $self->auto_renew_error($error)->store if $confirm; } return ( 0, $error, $updated ); } } =head3 to_api_mapping This method returns the mapping for representing a Koha::Checkout object on the API. =cut sub to_api_mapping { return { checkin_library => 'checkin_library_id', issue_id => 'checkout_id', borrowernumber => 'patron_id', itemnumber => 'item_id', booking_id => 'booking_id', date_due => 'due_date', branchcode => 'library_id', returndate => 'checkin_date', lastreneweddate => 'last_renewed_date', issuedate => 'checkout_date', notedate => 'note_date', noteseen => 'note_seen', }; } =head3 public_read_list This method returns the list of publicly readable database fields for both API and UI output purposes =cut sub public_read_list { return [ 'checkin_library', 'issue_id', 'borrowernumber', 'itemnumber', 'date_due', 'branchcode', 'returndate', 'lastreneweddate', 'issuedate', 'notedate', 'noteseen' ]; } =head3 claim_returned my $return_claim = $checkout->claim_returned(); This method sets the checkout as claimed return. It will: 1. Add a new row to the `return_claims` table 2. Set the item as lost using the 'ClaimReturnedLostValue' 3. Charge a fee depending on the value of ClaimReturnedChargeFee 3a. If set to charge, then accruing overdues will be halted 3b. If set to charge, then any existing transfers will be cancelled and the holding branch will be set back to 'frombranch'. 4. The issue will be marked as returned as per the 'MarkLostItemsAsReturned' preference =cut sub claim_returned { my ( $self, $params ) = @_; my $charge_lost_fee = $params->{charge_lost_fee}; my $refund_lost_fee = $params->{refund_lost_fee}; try { $self->_result->result_source->schema->txn_do( sub { my $claim = Koha::Checkouts::ReturnClaim->new( { issue_id => $self->id, itemnumber => $self->itemnumber, borrowernumber => $self->borrowernumber, notes => $params->{notes}, created_by => $params->{created_by}, created_on => dt_from_string, } )->store(); my $ClaimReturnedLostValue = C4::Context->preference('ClaimReturnedLostValue'); $self->item->itemlost($ClaimReturnedLostValue)->store unless $self->item->itemlost; my $ClaimReturnedChargeFee = C4::Context->preference('ClaimReturnedChargeFee'); $charge_lost_fee = $ClaimReturnedChargeFee eq 'charge' ? 1 : $ClaimReturnedChargeFee eq 'no_charge' ? 0 : $charge_lost_fee; # $ClaimReturnedChargeFee eq 'ask' if ($charge_lost_fee) { C4::Circulation::LostItem( $self->itemnumber, 'claim_returned' ); } elsif ( C4::Context->preference('MarkLostItemsAsReturned') =~ m/claim_returned/ ) { C4::Circulation::MarkIssueReturned( $self->borrowernumber, $self->itemnumber, undef, $self->patron->privacy ); } if ($refund_lost_fee) { $self->item->store( { refund_lost_fee => $refund_lost_fee } ); } return $claim; } ); } catch { if ( $_->isa('Koha::Exception') ) { $_->rethrow(); } else { # ? Koha::Exception->throw("Unhandled exception"); } }; } =head2 branch_for_fee_context my $branchcode = Koha::Checkout->branch_for_fee_context( fee_type => 'OVERDUE' | 'LOST' | 'PROCESSING', patron => $patron_obj, # Koha::Patron object or hashref item => $item_obj, # Koha::Item object or hashref issue => $issue_obj, # Koha::Checkout object or hashref (optional) ); Determines which branch's rules should apply to a fee based on system preferences and available context. For LOST/PROCESSING fees, honors LostChargesControl preference: - PatronLibrary: Uses patron's home branch - PickupLibrary: Uses checkout branch (falls back to patron, then item) - ItemHomeLibrary: Uses item's home or holding branch (per HomeOrHoldingBranch pref) For OVERDUE fees, honors CircControl preference (same values as above). B - fee_type: 'OVERDUE', 'LOST', or 'PROCESSING' (defaults to 'OVERDUE') - patron: Koha::Patron object or hashref with branchcode - item: Koha::Item object or hashref with homebranch/holdingbranch - issue: Koha::Checkout object or hashref with branchcode (optional) B A branchcode (string) or C if no branch can be determined. B Returning C is acceptable - calling code should handle this gracefully. When passed to Koha::Account->add_debit as library_id, undef indicates no specific branch context is available for the fee. =cut sub branch_for_fee_context { my ( $class, %args ) = @_; my $fee_type = $args{fee_type} // 'OVERDUE'; # Choose the controlling preference depending on fee type my $control_pref = ( $fee_type eq 'LOST' || $fee_type eq 'PROCESSING' ) ? C4::Context->preference('LostChargesControl') : C4::Context->preference('CircControl'); # Item branch selection (home vs holding) my $hoh_pref = C4::Context->preference('HomeOrHoldingBranch') // 'home'; my $use_holding = ( $hoh_pref =~ /holding/i ) ? 1 : 0; # Support Koha objects OR plain hashes my $patron_branch = eval { $args{patron}->branchcode } // ( $args{patron} && $args{patron}->{branchcode} ); my $issuing_branch = eval { $args{issue}->branchcode } // ( $args{issue} && $args{issue}->{branchcode} ); my $item_home = eval { $args{item}->homebranch } // ( $args{item} && $args{item}->{homebranch} ); my $item_holding = eval { $args{item}->holdingbranch } // ( $args{item} && $args{item}->{holdingbranch} ); my $item_branch = $use_holding ? ( $item_holding // $item_home ) : $item_home; # Normalize empty strings to undef so // works as intended for my $ref ( \$patron_branch, \$issuing_branch, \$item_home, \$item_holding, \$item_branch ) { $$ref = undef if defined $$ref && $$ref eq ''; } # Resolution order depends on controlling pref my $resolved = ( defined $control_pref && $control_pref eq 'PatronLibrary' ) ? $patron_branch : ( defined $control_pref && $control_pref eq 'PickupLibrary' ) ? ( $issuing_branch // $patron_branch // $item_branch ) : $item_branch; # ItemLibrary (default) # May return undef if no branch context is available - this is acceptable # and handled gracefully by Koha::Account->add_debit return $resolved; } =head2 Internal methods =head3 _type =cut sub _type { return 'Issue'; } =head1 AUTHOR Kyle M Hall Jonathan Druart =cut 1;