X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=Koha%2FAccount.pm;h=23252c733290555723028b10f1462b61709dc456;hb=9d6d641d1f8b77271800f43bc027b651f9aea52b;hp=6093fe7229ec31297d61589c37a1e30626d6d600;hpb=491147896d5cced31861243b862e403f09d62d98;p=srvgit diff --git a/Koha/Account.pm b/Koha/Account.pm index 6093fe7229..23252c7332 100644 --- a/Koha/Account.pm +++ b/Koha/Account.pm @@ -20,9 +20,8 @@ package Koha::Account; use Modern::Perl; use Carp; -use Data::Dumper; -use List::MoreUtils qw( uniq ); -use Try::Tiny; +use Data::Dumper qw( Dumper ); +use Try::Tiny qw( catch try ); use C4::Circulation qw( ReturnLostItem CanBookBeRenewed AddRenewal ); use C4::Letters; @@ -34,7 +33,6 @@ use Koha::Patrons; use Koha::Account::Lines; use Koha::Account::Offsets; use Koha::Account::DebitTypes; -use Koha::DateUtils qw( dt_from_string ); use Koha::Exceptions; use Koha::Exceptions::Account; @@ -81,232 +79,39 @@ sub pay { my $lines = $params->{lines}; my $type = $params->{type} || 'PAYMENT'; my $payment_type = $params->{payment_type} || undef; - my $credit_type = $params->{credit_type}; - my $offset_type = $params->{offset_type} || $type eq 'WRITEOFF' ? 'Writeoff' : 'Payment'; my $cash_register = $params->{cash_register}; my $item_id = $params->{item_id}; my $userenv = C4::Context->userenv; - $credit_type ||= - $type eq 'WRITEOFF' - ? 'WRITEOFF' - : 'PAYMENT'; - - my $patron = Koha::Patrons->find( $self->{patron_id} ); - my $manager_id = $userenv ? $userenv->{number} : undef; my $interface = $params ? ( $params->{interface} || C4::Context->interface ) : C4::Context->interface; - Koha::Exceptions::Account::RegisterRequired->throw() - if ( C4::Context->preference("UseCashRegisters") - && defined($payment_type) - && ( $payment_type eq 'CASH' ) - && !defined($cash_register) ); - - my @fines_paid; # List of account lines paid on with this payment - - # The outcome of any attempted item renewals as a result of fines being - # paid off - my $renew_outcomes = []; - - my $balance_remaining = $amount; # Set it now so we can adjust the amount if necessary - $balance_remaining ||= 0; - - my @account_offsets; - - # We were passed a specific line to pay - foreach my $fine ( @$lines ) { - my $amount_to_pay = - $fine->amountoutstanding > $balance_remaining - ? $balance_remaining - : $fine->amountoutstanding; - - my $old_amountoutstanding = $fine->amountoutstanding; - my $new_amountoutstanding = $old_amountoutstanding - $amount_to_pay; - $fine->amountoutstanding($new_amountoutstanding)->store(); - $balance_remaining = $balance_remaining - $amount_to_pay; - - # Attempt to renew the item associated with this debit if - # appropriate - if ($fine->is_renewable) { - # We're ignoring the definition of $interface above, by all - # accounts we can't rely on C4::Context::interface, so here - # we're only using what we've been explicitly passed - my $outcome = $fine->renew_item({ interface => $interface }); - push @{$renew_outcomes}, $outcome if $outcome; - } - - # Same logic exists in Koha::Account::Line::apply - if ( C4::Context->preference('MarkLostItemsAsReturned') =~ m|onpayment| - && $fine->debit_type_code - && $fine->debit_type_code eq 'LOST' - && $new_amountoutstanding == 0 - && $fine->itemnumber - && !( $credit_type eq 'LOST_FOUND' - && $item_id == $fine->itemnumber ) ) - { - C4::Circulation::ReturnLostItem( $self->{patron_id}, - $fine->itemnumber ); - } - - my $account_offset = Koha::Account::Offset->new( - { - debit_id => $fine->id, - type => $offset_type, - amount => $amount_to_pay * -1, - } - ); - push( @account_offsets, $account_offset ); - - if ( C4::Context->preference("FinesLog") ) { - logaction( - "FINES", 'MODIFY', - $self->{patron_id}, - Dumper( - { - action => 'fee_payment', - borrowernumber => $fine->borrowernumber, - old_amountoutstanding => $old_amountoutstanding, - new_amountoutstanding => 0, - amount_paid => $old_amountoutstanding, - accountlines_id => $fine->id, - manager_id => $manager_id, - note => $note, - } - ), - $interface - ); - push( @fines_paid, $fine->id ); - } - } - - # Were not passed a specific line to pay, or the payment was for more - # than the what was owed on the given line. In that case pay down other - # lines with remaining balance. - my @outstanding_fines; - @outstanding_fines = $self->lines->search( + my $payment = $self->payin_amount( { - amountoutstanding => { '>' => 0 }, - } - ) if $balance_remaining > 0; - - foreach my $fine (@outstanding_fines) { - my $amount_to_pay = - $fine->amountoutstanding > $balance_remaining - ? $balance_remaining - : $fine->amountoutstanding; - - my $old_amountoutstanding = $fine->amountoutstanding; - $fine->amountoutstanding( $old_amountoutstanding - $amount_to_pay ); - $fine->store(); - - # If we need to make a note of the item associated with this line, - # in order that we can potentially renew it, do so. - my $amt = $old_amountoutstanding - $amount_to_pay; - if ( $fine->is_renewable ) { - my $outcome = $fine->renew_item({ interface => $interface }); - push @{$renew_outcomes}, $outcome if $outcome; - } - - if ( C4::Context->preference('MarkLostItemsAsReturned') =~ m|onpayment| - && $fine->debit_type_code - && $fine->debit_type_code eq 'LOST' - && $fine->amountoutstanding == 0 - && $fine->itemnumber - && !( $credit_type eq 'LOST_FOUND' - && $item_id == $fine->itemnumber ) ) - { - C4::Circulation::ReturnLostItem( $self->{patron_id}, - $fine->itemnumber ); - } - - my $account_offset = Koha::Account::Offset->new( - { - debit_id => $fine->id, - type => $offset_type, - amount => $amount_to_pay * -1, - } - ); - push( @account_offsets, $account_offset ); - - if ( C4::Context->preference("FinesLog") ) { - logaction( - "FINES", 'MODIFY', - $self->{patron_id}, - Dumper( - { - action => "fee_$type", - borrowernumber => $fine->borrowernumber, - old_amountoutstanding => $old_amountoutstanding, - new_amountoutstanding => $fine->amountoutstanding, - amount_paid => $amount_to_pay, - accountlines_id => $fine->id, - manager_id => $manager_id, - note => $note, - } - ), - $interface - ); - push( @fines_paid, $fine->id ); - } - - $balance_remaining = $balance_remaining - $amount_to_pay; - last unless $balance_remaining > 0; - } - - $description ||= $type eq 'WRITEOFF' ? 'Writeoff' : q{}; - - my $payment = Koha::Account::Line->new( - { - borrowernumber => $self->{patron_id}, - date => dt_from_string(), - amount => 0 - $amount, - description => $description, - credit_type_code => $credit_type, - payment_type => $payment_type, - amountoutstanding => 0 - $balance_remaining, - manager_id => $manager_id, - interface => $interface, - branchcode => $library_id, - register_id => $cash_register, - note => $note, - itemnumber => $item_id, - } - )->store(); - - foreach my $o ( @account_offsets ) { - $o->credit_id( $payment->id() ); - $o->store(); - } - - C4::Stats::UpdateStats( - { - branch => $library_id, - type => lc($type), - amount => $amount, - borrowernumber => $self->{patron_id}, + interface => $interface, + type => $type, + amount => $amount, + payment_type => $payment_type, + cash_register => $cash_register, + user_id => $manager_id, + library_id => $library_id, + item_id => $item_id, + description => $description, + note => $note, + debits => $lines } ); - if ( C4::Context->preference("FinesLog") ) { - logaction( - "FINES", 'CREATE', - $self->{patron_id}, - Dumper( - { - action => "create_$type", - borrowernumber => $self->{patron_id}, - amount => 0 - $amount, - amountoutstanding => 0 - $balance_remaining, - credit_type_code => $credit_type, - accountlines_paid => \@fines_paid, - manager_id => $manager_id, - } - ), - $interface - ); + # NOTE: Pay historically always applied as much credit as it could to all + # existing outstanding debits, whether passed specific debits or otherwise. + if ( $payment->amountoutstanding ) { + $payment = + $payment->apply( + { debits => [ $self->outstanding_debits->as_list ] } ); } + my $patron = Koha::Patrons->find( $self->{patron_id} ); + my @account_offsets = $payment->debit_offsets; if ( C4::Context->preference('UseEmailReceipts') ) { if ( my $letter = C4::Letters::GetPreparedLetter( @@ -335,6 +140,11 @@ sub pay { } } + my $renew_outcomes = []; + for my $message ( @{$payment->messages} ) { + push @{$renew_outcomes}, $message->payload; + } + return { payment_id => $payment->id, renew_result => $renew_outcomes }; } @@ -485,23 +295,114 @@ sub add_credit { return $line; } +=head3 payin_amount + + my $credit = $account->payin_amount( + { + amount => $amount, + type => $credit_type, + payment_type => $payment_type, + cash_register => $register_id, + interface => $interface, + library_id => $branchcode, + user_id => $staff_id, + debits => $debit_lines, + description => $description, + note => $note + } + ); + +This method allows an amount to be paid into a patrons account and immediately applied against debts. + +You can optionally pass a debts parameter which consists of an arrayref of Koha::Account::Line debit lines. + +$credit_type can be any of: + - 'PAYMENT' + - 'WRITEOFF' + - 'FORGIVEN' + +=cut + +sub payin_amount { + my ( $self, $params ) = @_; + + # check for mandatory params + my @mandatory = ( 'interface', 'amount', 'type' ); + for my $param (@mandatory) { + unless ( defined( $params->{$param} ) ) { + Koha::Exceptions::MissingParameter->throw( + error => "The $param parameter is mandatory" ); + } + } + + # Check for mandatory register + Koha::Exceptions::Account::RegisterRequired->throw() + if ( C4::Context->preference("UseCashRegisters") + && defined( $params->{payment_type} ) + && ( $params->{payment_type} eq 'CASH' ) + && !defined($params->{cash_register}) ); + + # amount should always be passed as a positive value + my $amount = $params->{amount}; + unless ( $amount > 0 ) { + Koha::Exceptions::Account::AmountNotPositive->throw( + error => 'Payin amount passed is not positive' ); + } + + my $credit; + my $schema = Koha::Database->new->schema; + $schema->txn_do( + sub { + + # Add payin credit + $credit = $self->add_credit($params); + + # Offset debts passed first + if ( exists( $params->{debits} ) ) { + $credit = $credit->apply( + { + debits => $params->{debits}, + offset_type => $Koha::Account::offset_type->{$params->{type}} + } + ); + } + + # Offset against remaining balance if AutoReconcile + if ( C4::Context->preference("AccountAutoReconcile") + && $credit->amountoutstanding != 0 ) + { + $credit = $credit->apply( + { + debits => [ $self->outstanding_debits->as_list ], + offset_type => $Koha::Account::offset_type->{$params->{type}} + } + ); + } + } + ); + + return $credit; +} + =head3 add_debit This method allows adding debits to a patron's account -my $debit_line = Koha::Account->new({ patron_id => $patron_id })->add_debit( - { - amount => $amount, - description => $description, - note => $note, - user_id => $user_id, - interface => $interface, - library_id => $library_id, - type => $debit_type, - item_id => $item_id, - issue_id => $issue_id - } -); + my $debit_line = Koha::Account->new({ patron_id => $patron_id })->add_debit( + { + amount => $amount, + description => $description, + note => $note, + user_id => $user_id, + interface => $interface, + library_id => $library_id, + type => $debit_type, + transaction_type => $transaction_type, + cash_register => $register_id, + item_id => $item_id, + issue_id => $issue_id + } + ); $debit_type can be any of: - ACCOUNT @@ -517,6 +418,7 @@ $debit_type can be any of: - RENT_RENEW - RENT_DAILY_RENEW - RESERVE + - PAYOUT =cut @@ -533,6 +435,13 @@ sub add_debit { } } + # check for cash register if using cash + Koha::Exceptions::Account::RegisterRequired->throw() + if ( C4::Context->preference("UseCashRegisters") + && defined( $params->{transaction_type} ) + && ( $params->{transaction_type} eq 'CASH' ) + && !defined( $params->{cash_register} ) ); + # amount should always be a positive value my $amount = $params->{amount}; unless ( $amount > 0 ) { @@ -540,15 +449,17 @@ sub add_debit { error => 'Debit amount passed is not positive' ); } - my $description = $params->{description} // q{}; - my $note = $params->{note} // q{}; - my $user_id = $params->{user_id}; - my $interface = $params->{interface}; - my $library_id = $params->{library_id}; - my $debit_type = $params->{type}; - my $item_id = $params->{item_id}; - my $issue_id = $params->{issue_id}; - my $offset_type = $Koha::Account::offset_type->{$debit_type} // 'Manual Debit'; + my $description = $params->{description} // q{}; + my $note = $params->{note} // q{}; + my $user_id = $params->{user_id}; + my $interface = $params->{interface}; + my $library_id = $params->{library_id}; + my $cash_register = $params->{cash_register}; + my $debit_type = $params->{type}; + my $transaction_type = $params->{transaction_type}; + my $item_id = $params->{item_id}; + my $issue_id = $params->{issue_id}; + my $offset_type = $Koha::Account::offset_type->{$debit_type} // 'Manual Debit'; my $line; my $schema = Koha::Database->new->schema; @@ -565,13 +476,14 @@ sub add_debit { description => $description, debit_type_code => $debit_type, amountoutstanding => $amount, - payment_type => undef, + payment_type => $transaction_type, note => $note, manager_id => $user_id, interface => $interface, itemnumber => $item_id, issue_id => $issue_id, branchcode => $library_id, + register_id => $cash_register, ( $debit_type eq 'OVERDUE' ? ( status => 'UNRETURNED' ) @@ -627,6 +539,100 @@ sub add_debit { return $line; } +=head3 payout_amount + + my $debit = $account->payout_amount( + { + payout_type => $payout_type, + register_id => $register_id, + staff_id => $staff_id, + interface => 'intranet', + amount => $amount, + credits => $credit_lines + } + ); + +This method allows an amount to be paid out from a patrons account against outstanding credits. + +$payout_type can be any of the defined payment_types: + +=cut + +sub payout_amount { + my ( $self, $params ) = @_; + + # Check for mandatory parameters + my @mandatory = + ( 'interface', 'staff_id', 'branch', 'payout_type', 'amount' ); + for my $param (@mandatory) { + unless ( defined( $params->{$param} ) ) { + Koha::Exceptions::MissingParameter->throw( + error => "The $param parameter is mandatory" ); + } + } + + # Check for mandatory register + Koha::Exceptions::Account::RegisterRequired->throw() + if ( C4::Context->preference("UseCashRegisters") + && ( $params->{payout_type} eq 'CASH' ) + && !defined($params->{cash_register}) ); + + # Amount should always be passed as a positive value + my $amount = $params->{amount}; + unless ( $amount > 0 ) { + Koha::Exceptions::Account::AmountNotPositive->throw( + error => 'Payout amount passed is not positive' ); + } + + # Amount should always be less than or equal to outstanding credit + my $outstanding = 0; + my $outstanding_credits = + exists( $params->{credits} ) + ? $params->{credits} + : $self->outstanding_credits->as_list; + for my $credit ( @{$outstanding_credits} ) { + $outstanding += $credit->amountoutstanding; + } + $outstanding = $outstanding * -1; + Koha::Exceptions::ParameterTooHigh->throw( error => +"Amount to payout ($amount) is higher than amountoutstanding ($outstanding)" + ) unless ( $outstanding >= $amount ); + + my $payout; + my $schema = Koha::Database->new->schema; + $schema->txn_do( + sub { + + # A 'payout' is a 'debit' + $payout = $self->add_debit( + { + amount => $params->{amount}, + type => 'PAYOUT', + transaction_type => $params->{payout_type}, + amountoutstanding => $params->{amount}, + manager_id => $params->{staff_id}, + interface => $params->{interface}, + branchcode => $params->{branch}, + cash_register => $params->{cash_register} + } + ); + + # Offset against credits + for my $credit ( @{$outstanding_credits} ) { + $credit->apply( + { debits => [$payout], offset_type => 'PAYOUT' } ); + $payout->discard_changes; + last if $payout->amountoutstanding == 0; + } + + # Set payout as paid + $payout->status('PAID')->store; + } + ); + + return $payout; +} + =head3 balance my $balance = $self->balance @@ -791,7 +797,8 @@ our $offset_type = { 'RENT_RENEW' => 'Rental Fee', 'RENT_DAILY_RENEW' => 'Rental Fee', 'OVERDUE' => 'OVERDUE', - 'RESERVE_EXPIRED' => 'Hold Expired' + 'RESERVE_EXPIRED' => 'Hold Expired', + 'PAYOUT' => 'PAYOUT', }; =head1 AUTHORS