X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=Koha%2FAccount.pm;h=304d0e2743cbfd49ade62ebe9550b2038d740173;hb=d3ff671f215398ad51a6ce550c33c0c6ca3aa30a;hp=d74f9487db1d86ae47b8bf1c8bd9be9dcfe6ca71;hpb=1c225986879648c326408b3ad10b3bcb498b2271;p=srvgit diff --git a/Koha/Account.pm b/Koha/Account.pm index d74f9487db..304d0e2743 100644 --- a/Koha/Account.pm +++ b/Koha/Account.pm @@ -21,12 +21,16 @@ use Modern::Perl; use Carp; use Data::Dumper; +use List::MoreUtils qw( uniq ); +use C4::Circulation qw( ReturnLostItem ); +use C4::Letters; use C4::Log qw( logaction ); use C4::Stats qw( UpdateStats ); -use Koha::Account::Line; +use Koha::Patrons; use Koha::Account::Lines; +use Koha::Account::Offsets; use Koha::DateUtils qw( dt_from_string ); =head1 NAME @@ -49,11 +53,14 @@ This method allows payments to be made against fees/fines Koha::Account->new( { patron_id => $borrowernumber } )->pay( { - amount => $amount, - sip => $sipmode, - note => $note, - library_id => $branchcode, - lines => $lines, # Arrayref of Koha::Account::Line objects to pay + amount => $amount, + sip => $sipmode, + note => $note, + description => $description, + library_id => $branchcode, + lines => $lines, # Arrayref of Koha::Account::Line objects to pay + account_type => $type, # accounttype code + offset_type => $offset_type, # offset type code } ); @@ -62,23 +69,25 @@ Koha::Account->new( { patron_id => $borrowernumber } )->pay( sub pay { my ( $self, $params ) = @_; - my $amount = $params->{amount}; - my $sip = $params->{sip}; - my $note = $params->{note} || q{}; - my $library_id = $params->{library_id}; - my $lines = $params->{lines}, + my $amount = $params->{amount}; + my $sip = $params->{sip}; + my $description = $params->{description}; + my $note = $params->{note} || q{}; + my $library_id = $params->{library_id}; + my $lines = $params->{lines}; + my $type = $params->{type} || 'payment'; + my $payment_type = $params->{payment_type} || undef; + my $account_type = $params->{account_type}; + my $offset_type = $params->{offset_type} || $type eq 'writeoff' ? 'Writeoff' : 'Payment'; my $userenv = C4::Context->userenv; + my $patron = Koha::Patrons->find( $self->{patron_id} ); + # We should remove accountno, it is no longer needed - my $last = Koha::Account::Lines->search( - { - borrowernumber => $self->{patron_id} - }, - { - order_by => 'accountno' - } - )->next(); + my $last = $self->lines->search( + {}, + { order_by => 'accountno' } )->next(); my $accountno = $last ? $last->accountno + 1 : 1; my $manager_id = $userenv ? $userenv->{number} : 0; @@ -88,6 +97,8 @@ sub pay { 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 = @@ -100,11 +111,20 @@ sub pay { $fine->amountoutstanding($new_amountoutstanding)->store(); $balance_remaining = $balance_remaining - $amount_to_pay; - if ( $fine->accounttype && ( $fine->accounttype eq 'Rep' || $fine->accounttype eq 'L' ) ) + if ( $fine->itemnumber && $fine->accounttype && ( $fine->accounttype eq 'Rep' || $fine->accounttype eq 'L' ) ) { 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', @@ -131,9 +151,8 @@ sub pay { # than the what was owed on the given line. In that case pay down other # lines with remaining balance. my @outstanding_fines; - @outstanding_fines = Koha::Account::Lines->search( + @outstanding_fines = $self->lines->search( { - borrowernumber => $self->{patron_id}, amountoutstanding => { '>' => 0 }, } ) if $balance_remaining > 0; @@ -148,13 +167,22 @@ sub pay { $fine->amountoutstanding( $old_amountoutstanding - $amount_to_pay ); $fine->store(); + 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', + action => "fee_$type", borrowernumber => $fine->borrowernumber, old_amountoutstanding => $old_amountoutstanding, new_amountoutstanding => $fine->amountoutstanding, @@ -173,7 +201,12 @@ sub pay { last unless $balance_remaining > 0; } - my $account_type = defined($sip) ? "Pay$sip" : 'Pay'; + $account_type ||= + $type eq 'writeoff' ? 'W' + : defined($sip) ? "Pay$sip" + : 'Pay'; + + $description ||= $type eq 'writeoff' ? 'Writeoff' : q{}; my $payment = Koha::Account::Line->new( { @@ -181,20 +214,26 @@ sub pay { accountno => $accountno, date => dt_from_string(), amount => 0 - $amount, - description => q{}, + description => $description, accounttype => $account_type, + payment_type => $payment_type, amountoutstanding => 0 - $balance_remaining, manager_id => $manager_id, note => $note, } )->store(); + foreach my $o ( @account_offsets ) { + $o->credit_id( $payment->id() ); + $o->store(); + } + $library_id ||= $userenv ? $userenv->{'branch'} : undef; UpdateStats( { branch => $library_id, - type => 'payment', + type => $type, amount => $amount, borrowernumber => $self->{patron_id}, accountno => $accountno, @@ -207,12 +246,12 @@ sub pay { $self->{patron_id}, Dumper( { - action => 'create_payment', + action => "create_$type", borrowernumber => $self->{patron_id}, accountno => $accountno, amount => 0 - $amount, amountoutstanding => 0 - $balance_remaining, - accounttype => 'Pay', + accounttype => $account_type, accountlines_paid => \@fines_paid, manager_id => $manager_id, } @@ -220,9 +259,153 @@ sub pay { ); } + if ( C4::Context->preference('UseEmailReceipts') ) { + if ( + my $letter = C4::Letters::GetPreparedLetter( + module => 'circulation', + letter_code => uc("ACCOUNT_$type"), + message_transport_type => 'email', + lang => $patron->lang, + tables => { + borrowers => $self->{patron_id}, + branches => $self->{library_id}, + }, + substitute => { + credit => $payment, + offsets => \@account_offsets, + }, + ) + ) + { + C4::Letters::EnqueueLetter( + { + letter => $letter, + borrowernumber => $self->{patron_id}, + message_transport_type => 'email', + } + ) or warn "can't enqueue letter $letter"; + } + } + return $payment->id; } +=head3 add_credit + +This method allows adding credits to a patron's account + +my $credit_line = Koha::Account->new({ patron_id => $patron_id })->add_credit( + { + amount => $amount, + description => $description, + note => $note, + user_id => $user_id, + library_id => $library_id, + sip => $sip, + payment_type => $payment_type, + type => $credit_type, + item_id => $item_id + } +); + +$credit_type can be any of: + - 'credit' + - 'payment' + - 'forgiven' + - 'lost_item_return' + - 'writeoff' + +=cut + +sub add_credit { + + my ( $self, $params ) = @_; + + # amount is passed as a positive value, but we store credit as negative values + my $amount = $params->{amount} * -1; + my $description = $params->{description} // q{}; + my $note = $params->{note} // q{}; + my $user_id = $params->{user_id}; + my $library_id = $params->{library_id}; + my $sip = $params->{sip}; + my $payment_type = $params->{payment_type}; + my $type = $params->{type} || 'payment'; + my $item_id = $params->{item_id}; + + my $schema = Koha::Database->new->schema; + + my $account_type = $Koha::Account::account_type->{$type}; + $account_type .= $sip + if defined $sip && + $type eq 'payment'; + + my $line; + + $schema->txn_do( + sub { + # We should remove accountno, it is no longer needed + my $last = $self->lines->search( + {}, + { order_by => 'accountno' } )->next(); + my $accountno = $last ? $last->accountno + 1 : 1; + + # Insert the account line + $line = Koha::Account::Line->new( + { borrowernumber => $self->{patron_id}, + date => \'NOW()', + amount => $amount, + description => $description, + accounttype => $account_type, + amountoutstanding => $amount, + payment_type => $payment_type, + note => $note, + manager_id => $user_id, + itemnumber => $item_id + } + )->store(); + + # Record the account offset + my $account_offset = Koha::Account::Offset->new( + { credit_id => $line->id, + type => $Koha::Account::offset_type->{$type}, + amount => $amount + } + )->store(); + + UpdateStats( + { branch => $library_id, + type => $type, + amount => $amount, + borrowernumber => $self->{patron_id}, + accountno => $accountno, + } + ) if grep { $type eq $_ } ('payment', 'writeoff') ; + + if ( C4::Context->preference("FinesLog") ) { + logaction( + "FINES", 'CREATE', + $self->{patron_id}, + Dumper( + { action => "create_$type", + borrowernumber => $self->{patron_id}, + accountno => $accountno, + amount => $amount, + description => $description, + amountoutstanding => $amount, + accounttype => $account_type, + note => $note, + itemnumber => $item_id, + manager_id => $user_id, + } + ) + ); + } + } + ); + + return $line; +} + =head3 balance my $balance = $self->balance @@ -233,22 +416,160 @@ Return the balance (sum of amountoutstanding columns) sub balance { my ($self) = @_; - my $fines = Koha::Account::Lines->search( + return $self->lines->total_outstanding; +} + +=head3 outstanding_debits + +my $lines = Koha::Account->new({ patron_id => $patron_id })->outstanding_debits; + +=cut + +sub outstanding_debits { + my ($self) = @_; + + return $self->lines->search( { - borrowernumber => $self->{patron_id}, + amount => { '>' => 0 }, + amountoutstanding => { '>' => 0 } + } + ); +} + +=head3 outstanding_credits + +my $lines = Koha::Account->new({ patron_id => $patron_id })->outstanding_credits; + +=cut + +sub outstanding_credits { + my ($self) = @_; + + return $self->lines->search( + { + amount => { '<' => 0 }, + amountoutstanding => { '<' => 0 } + } + ); +} + +=head3 non_issues_charges + +my $non_issues_charges = $self->non_issues_charges + +Calculates amount immediately owing by the patron - non-issue charges. + +Charges exempt from non-issue are: +* Res (holds) if HoldsInNoissuesCharge syspref is set to false +* Rent (rental) if RentalsInNoissuesCharge syspref is set to false +* Manual invoices if ManInvInNoissuesCharge syspref is set to false + +=cut + +sub non_issues_charges { + my ($self) = @_; + + # FIXME REMOVE And add a warning in the about page + update DB if length(MANUAL_INV) > 5 + my $ACCOUNT_TYPE_LENGTH = 5; # this is plain ridiculous... + + my @not_fines; + push @not_fines, 'Res' + unless C4::Context->preference('HoldsInNoissuesCharge'); + push @not_fines, 'Rent' + unless C4::Context->preference('RentalsInNoissuesCharge'); + unless ( C4::Context->preference('ManInvInNoissuesCharge') ) { + my $dbh = C4::Context->dbh; + push @not_fines, + @{ + $dbh->selectcol_arrayref(q| + SELECT authorised_value FROM authorised_values WHERE category = 'MANUAL_INV' + |) + }; + } + @not_fines = map { substr( $_, 0, $ACCOUNT_TYPE_LENGTH ) } uniq(@not_fines); + + return $self->lines->search( + { + accounttype => { -not_in => \@not_fines } }, + )->total_outstanding; +} + +=head3 lines + +my $lines = $self->lines; + +Return all credits and debits for the user, outstanding or otherwise + +=cut + +sub lines { + my ($self) = @_; + + return Koha::Account::Lines->search( { - select => [ { sum => 'amountoutstanding' } ], - as => ['total_amountoutstanding'], + borrowernumber => $self->{patron_id}, } ); - return $fines->count - ? $fines->next->get_column('total_amountoutstanding') - : 0; +} + +=head3 reconcile_balance + +$account->reconcile_balance(); + +Find outstanding credits and use them to pay outstanding debits. +Currently, this implicitly uses the 'First In First Out' rule for +applying credits against debits. + +=cut + +sub reconcile_balance { + my ($self) = @_; + + my $outstanding_debits = $self->outstanding_debits; + my $outstanding_credits = $self->outstanding_credits; + + while ( $outstanding_debits->total_outstanding > 0 + and my $credit = $outstanding_credits->next ) + { + # there's both outstanding debits and credits + $credit->apply( { debits => $outstanding_debits } ); # applying credit, no special offset + + $outstanding_debits = $self->outstanding_debits; + + } + + return $self; } 1; +=head2 Name mappings + +=head3 $offset_type + +=cut + +our $offset_type = { + 'credit' => 'Manual Credit', + 'forgiven' => 'Writeoff', + 'lost_item_return' => 'Lost Item', + 'payment' => 'Payment', + 'writeoff' => 'Writeoff' +}; + +=head3 $account_type + +=cut + +our $account_type = { + 'credit' => 'C', + 'forgiven' => 'FOR', + 'lost_item_return' => 'CR', + 'payment' => 'Pay', + 'writeoff' => 'W' +}; + =head1 AUTHOR Kyle M Hall