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;
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;
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(
}
}
+ my $renew_outcomes = [];
+ for my $message ( @{$payment->messages} ) {
+ push @{$renew_outcomes}, $message->payload;
+ }
+
return { payment_id => $payment->id, renew_result => $renew_outcomes };
}
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
- RENT_RENEW
- RENT_DAILY_RENEW
- RESERVE
+ - PAYOUT
=cut
}
}
+ # 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 ) {
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;
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' )
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
'RENT_RENEW' => 'Rental Fee',
'RENT_DAILY_RENEW' => 'Rental Fee',
'OVERDUE' => 'OVERDUE',
- 'RESERVE_EXPIRED' => 'Hold Expired'
+ 'RESERVE_EXPIRED' => 'Hold Expired',
+ 'PAYOUT' => 'PAYOUT',
};
=head1 AUTHORS