3 # Copyright 2016 ByWater Solutions
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
23 use Data::Dumper qw( Dumper );
24 use Try::Tiny qw( catch try );
26 use C4::Circulation qw( ReturnLostItem CanBookBeRenewed AddRenewal );
28 use C4::Log qw( logaction );
29 use C4::Stats qw( UpdateStats );
30 use C4::Overdues qw(GetFine);
33 use Koha::Account::Lines;
34 use Koha::Account::Offsets;
35 use Koha::Account::DebitTypes;
37 use Koha::Exceptions::Account;
42 Koha::Accounts - Module for managing payments and fees for patrons
47 my ( $class, $params ) = @_;
49 Carp::croak("No patron id passed in!") unless $params->{patron_id};
51 return bless( $params, $class );
56 This method allows payments to be made against fees/fines
58 Koha::Account->new( { patron_id => $borrowernumber } )->pay(
62 description => $description,
63 library_id => $branchcode,
64 lines => $lines, # Arrayref of Koha::Account::Line objects to pay
65 credit_type => $type, # credit_type_code code
66 item_id => $itemnumber, # pass the itemnumber if this is a credit pertianing to a specific item (i.e LOST_FOUND)
73 my ( $self, $params ) = @_;
75 my $amount = $params->{amount};
76 my $description = $params->{description};
77 my $note = $params->{note} || q{};
78 my $library_id = $params->{library_id};
79 my $lines = $params->{lines};
80 my $type = $params->{type} || 'PAYMENT';
81 my $payment_type = $params->{payment_type} || undef;
82 my $cash_register = $params->{cash_register};
83 my $item_id = $params->{item_id};
85 my $userenv = C4::Context->userenv;
87 my $manager_id = $userenv ? $userenv->{number} : undef;
88 my $interface = $params ? ( $params->{interface} || C4::Context->interface ) : C4::Context->interface;
89 my $payment = $self->payin_amount(
91 interface => $interface,
94 payment_type => $payment_type,
95 cash_register => $cash_register,
96 user_id => $manager_id,
97 library_id => $library_id,
99 description => $description,
105 # NOTE: Pay historically always applied as much credit as it could to all
106 # existing outstanding debits, whether passed specific debits or otherwise.
107 if ( $payment->amountoutstanding ) {
110 { debits => [ $self->outstanding_debits->as_list ] } );
113 my $patron = Koha::Patrons->find( $self->{patron_id} );
114 my @account_offsets = $payment->credit_offsets({ type => 'APPLY' })->as_list;
115 if ( C4::Context->preference('UseEmailReceipts') ) {
117 my $letter = C4::Letters::GetPreparedLetter(
118 module => 'circulation',
119 letter_code => uc("ACCOUNT_$type"),
120 message_transport_type => 'email',
121 lang => $patron->lang,
123 borrowers => $self->{patron_id},
124 branches => $library_id,
128 offsets => \@account_offsets,
133 C4::Letters::EnqueueLetter(
136 borrowernumber => $self->{patron_id},
137 message_transport_type => 'email',
139 ) or warn "can't enqueue letter $letter";
143 my $renew_outcomes = [];
144 for my $message ( @{$payment->object_messages} ) {
145 push @{$renew_outcomes}, $message->payload;
148 return { payment_id => $payment->id, renew_result => $renew_outcomes };
153 This method allows adding credits to a patron's account
155 my $credit_line = Koha::Account->new({ patron_id => $patron_id })->add_credit(
158 description => $description,
161 interface => $interface,
162 library_id => $library_id,
163 payment_type => $payment_type,
164 type => $credit_type,
169 $credit_type can be any of:
183 my ( $self, $params ) = @_;
185 # check for mandatory params
186 my @mandatory = ( 'interface', 'amount' );
187 for my $param (@mandatory) {
188 unless ( defined( $params->{$param} ) ) {
189 Koha::Exceptions::MissingParameter->throw(
190 error => "The $param parameter is mandatory" );
194 # amount should always be passed as a positive value
195 my $amount = $params->{amount} * -1;
196 unless ( $amount < 0 ) {
197 Koha::Exceptions::Account::AmountNotPositive->throw(
198 error => 'Debit amount passed is not positive' );
201 my $description = $params->{description} // q{};
202 my $note = $params->{note} // q{};
203 my $user_id = $params->{user_id};
204 my $interface = $params->{interface};
205 my $library_id = $params->{library_id};
206 my $cash_register = $params->{cash_register};
207 my $payment_type = $params->{payment_type};
208 my $credit_type = $params->{type} || 'PAYMENT';
209 my $item_id = $params->{item_id};
211 Koha::Exceptions::Account::RegisterRequired->throw()
212 if ( C4::Context->preference("UseCashRegisters")
213 && defined($payment_type)
214 && ( $payment_type eq 'CASH' || $payment_type eq 'SIP00' )
215 && !defined($cash_register) );
218 my $schema = Koha::Database->new->schema;
223 # Insert the account line
224 $line = Koha::Account::Line->new(
226 borrowernumber => $self->{patron_id},
229 description => $description,
230 credit_type_code => $credit_type,
231 amountoutstanding => $amount,
232 payment_type => $payment_type,
234 manager_id => $user_id,
235 interface => $interface,
236 branchcode => $library_id,
237 register_id => $cash_register,
238 itemnumber => $item_id,
242 # Record the account offset
243 my $account_offset = Koha::Account::Offset->new(
245 credit_id => $line->id,
247 amount => $amount * -1
251 C4::Stats::UpdateStats(
253 branch => $library_id,
254 type => lc($credit_type),
256 borrowernumber => $self->{patron_id},
258 ) if grep { $credit_type eq $_ } ( 'PAYMENT', 'WRITEOFF' );
261 'after_account_action',
263 action => "add_credit",
265 type => lc($credit_type),
266 line => $line->get_from_storage, #TODO Seems unneeded
271 if ( C4::Context->preference("FinesLog") ) {
277 action => "create_$credit_type",
278 borrowernumber => $self->{patron_id},
280 description => $description,
281 amountoutstanding => $amount,
282 credit_type_code => $credit_type,
284 itemnumber => $item_id,
285 manager_id => $user_id,
286 branchcode => $library_id,
296 if ( ref($_) eq 'Koha::Exceptions::Object::FKConstraint' ) {
297 if ( $_->broken_fk eq 'credit_type_code' ) {
298 Koha::Exceptions::Account::UnrecognisedType->throw(
299 error => 'Type of credit not recognised' );
312 my $credit = $account->payin_amount(
315 type => $credit_type,
316 payment_type => $payment_type,
317 cash_register => $register_id,
318 interface => $interface,
319 library_id => $branchcode,
320 user_id => $staff_id,
321 debits => $debit_lines,
322 description => $description,
327 This method allows an amount to be paid into a patrons account and immediately applied against debts.
329 You can optionally pass a debts parameter which consists of an arrayref of Koha::Account::Line debit lines.
331 $credit_type can be any of:
339 my ( $self, $params ) = @_;
341 # check for mandatory params
342 my @mandatory = ( 'interface', 'amount', 'type' );
343 for my $param (@mandatory) {
344 unless ( defined( $params->{$param} ) ) {
345 Koha::Exceptions::MissingParameter->throw(
346 error => "The $param parameter is mandatory" );
350 # Check for mandatory register
351 Koha::Exceptions::Account::RegisterRequired->throw()
352 if ( C4::Context->preference("UseCashRegisters")
353 && defined( $params->{payment_type} )
354 && ( $params->{payment_type} eq 'CASH' || $params->{payment_type} eq 'SIP00' )
355 && !defined($params->{cash_register}) );
357 # amount should always be passed as a positive value
358 my $amount = $params->{amount};
359 unless ( $amount > 0 ) {
360 Koha::Exceptions::Account::AmountNotPositive->throw(
361 error => 'Payin amount passed is not positive' );
365 my $schema = Koha::Database->new->schema;
370 $credit = $self->add_credit($params);
372 # Offset debts passed first
373 if ( exists( $params->{debits} ) ) {
374 $credit = $credit->apply(
376 debits => $params->{debits}
381 # Offset against remaining balance if AutoReconcile
382 if ( C4::Context->preference("AccountAutoReconcile")
383 && $credit->amountoutstanding != 0 )
385 $credit = $credit->apply(
387 debits => [ $self->outstanding_debits->as_list ]
399 This method allows adding debits to a patron's account
401 my $debit_line = Koha::Account->new({ patron_id => $patron_id })->add_debit(
404 description => $description,
407 interface => $interface,
408 library_id => $library_id,
410 transaction_type => $transaction_type,
411 cash_register => $register_id,
413 issue_id => $issue_id
417 $debit_type can be any of:
437 my ( $self, $params ) = @_;
439 # check for mandatory params
440 my @mandatory = ( 'interface', 'type', 'amount' );
441 for my $param (@mandatory) {
442 unless ( defined( $params->{$param} ) ) {
443 Koha::Exceptions::MissingParameter->throw(
444 error => "The $param parameter is mandatory" );
448 # check for cash register if using cash
449 Koha::Exceptions::Account::RegisterRequired->throw()
450 if ( C4::Context->preference("UseCashRegisters")
451 && defined( $params->{transaction_type} )
452 && ( $params->{transaction_type} eq 'CASH' || $params->{payment_type} eq 'SIP00' )
453 && !defined( $params->{cash_register} ) );
455 # amount should always be a positive value
456 my $amount = $params->{amount};
457 unless ( $amount > 0 ) {
458 Koha::Exceptions::Account::AmountNotPositive->throw(
459 error => 'Debit amount passed is not positive' );
462 my $description = $params->{description} // q{};
463 my $note = $params->{note} // q{};
464 my $user_id = $params->{user_id};
465 my $interface = $params->{interface};
466 my $library_id = $params->{library_id};
467 my $cash_register = $params->{cash_register};
468 my $debit_type = $params->{type};
469 my $transaction_type = $params->{transaction_type};
470 my $item_id = $params->{item_id};
471 my $issue_id = $params->{issue_id};
474 my $schema = Koha::Database->new->schema;
479 # Insert the account line
480 $line = Koha::Account::Line->new(
482 borrowernumber => $self->{patron_id},
485 description => $description,
486 debit_type_code => $debit_type,
487 amountoutstanding => $amount,
488 payment_type => $transaction_type,
490 manager_id => $user_id,
491 interface => $interface,
492 itemnumber => $item_id,
493 issue_id => $issue_id,
494 branchcode => $library_id,
495 register_id => $cash_register,
497 $debit_type eq 'OVERDUE'
498 ? ( status => 'UNRETURNED' )
504 # Record the account offset
505 my $account_offset = Koha::Account::Offset->new(
507 debit_id => $line->id,
513 if ( C4::Context->preference("FinesLog") ) {
519 action => "create_$debit_type",
520 borrowernumber => $self->{patron_id},
522 description => $description,
523 amountoutstanding => $amount,
524 debit_type_code => $debit_type,
526 itemnumber => $item_id,
527 manager_id => $user_id,
537 if ( ref($_) eq 'Koha::Exceptions::Object::FKConstraint' ) {
538 if ( $_->broken_fk eq 'debit_type_code' ) {
539 Koha::Exceptions::Account::UnrecognisedType->throw(
540 error => 'Type of debit not recognised' );
553 my $debit = $account->payout_amount(
555 payout_type => $payout_type,
556 register_id => $register_id,
557 staff_id => $staff_id,
558 interface => 'intranet',
560 credits => $credit_lines
564 This method allows an amount to be paid out from a patrons account against outstanding credits.
566 $payout_type can be any of the defined payment_types:
571 my ( $self, $params ) = @_;
573 # Check for mandatory parameters
575 ( 'interface', 'staff_id', 'branch', 'payout_type', 'amount' );
576 for my $param (@mandatory) {
577 unless ( defined( $params->{$param} ) ) {
578 Koha::Exceptions::MissingParameter->throw(
579 error => "The $param parameter is mandatory" );
583 # Check for mandatory register
584 Koha::Exceptions::Account::RegisterRequired->throw()
585 if ( C4::Context->preference("UseCashRegisters")
586 && ( $params->{payout_type} eq 'CASH' || $params->{payout_type} eq 'SIP00' )
587 && !defined($params->{cash_register}) );
589 # Amount should always be passed as a positive value
590 my $amount = $params->{amount};
591 unless ( $amount > 0 ) {
592 Koha::Exceptions::Account::AmountNotPositive->throw(
593 error => 'Payout amount passed is not positive' );
596 # Amount should always be less than or equal to outstanding credit
598 my $outstanding_credits =
599 exists( $params->{credits} )
601 : $self->outstanding_credits->as_list;
602 for my $credit ( @{$outstanding_credits} ) {
603 $outstanding += $credit->amountoutstanding;
605 $outstanding = $outstanding * -1;
606 Koha::Exceptions::ParameterTooHigh->throw( error =>
607 "Amount to payout ($amount) is higher than amountoutstanding ($outstanding)"
608 ) unless ( $outstanding >= $amount );
611 my $schema = Koha::Database->new->schema;
615 # A 'payout' is a 'debit'
616 $payout = $self->add_debit(
618 amount => $params->{amount},
620 transaction_type => $params->{payout_type},
621 amountoutstanding => $params->{amount},
622 user_id => $params->{staff_id},
623 interface => $params->{interface},
624 branchcode => $params->{branch},
625 cash_register => $params->{cash_register}
629 # Offset against credits
630 for my $credit ( @{$outstanding_credits} ) {
631 $credit->apply( { debits => [$payout] } );
632 $payout->discard_changes;
633 last if $payout->amountoutstanding == 0;
637 $payout->status('PAID')->store;
646 my $balance = $self->balance
648 Return the balance (sum of amountoutstanding columns)
654 return $self->lines->total_outstanding;
657 =head3 outstanding_debits
659 my $lines = Koha::Account->new({ patron_id => $patron_id })->outstanding_debits;
661 It returns the debit lines with outstanding amounts for the patron.
663 It returns a Koha::Account::Lines iterator.
667 sub outstanding_debits {
670 return $self->lines->search(
672 amount => { '>' => 0 },
673 amountoutstanding => { '>' => 0 }
678 =head3 outstanding_credits
680 my $lines = Koha::Account->new({ patron_id => $patron_id })->outstanding_credits;
682 It returns the credit lines with outstanding amounts for the patron.
684 It returns a Koha::Account::Lines iterator.
688 sub outstanding_credits {
691 return $self->lines->search(
693 amount => { '<' => 0 },
694 amountoutstanding => { '<' => 0 }
699 =head3 non_issues_charges
701 my $non_issues_charges = $self->non_issues_charges
703 Calculates amount immediately owing by the patron - non-issue charges.
705 Charges exempt from non-issue are:
706 * Res (holds) if HoldsInNoissuesCharge syspref is set to false
707 * Rent (rental) if RentalsInNoissuesCharge syspref is set to false
708 * Manual invoices if ManInvInNoissuesCharge syspref is set to false
712 sub non_issues_charges {
715 #NOTE: With bug 23049 these preferences could be moved to being attached
716 #to individual debit types to give more flexability and specificity.
718 push @not_fines, 'RESERVE'
719 unless C4::Context->preference('HoldsInNoissuesCharge');
720 push @not_fines, ( 'RENT', 'RENT_DAILY', 'RENT_RENEW', 'RENT_DAILY_RENEW' )
721 unless C4::Context->preference('RentalsInNoissuesCharge');
722 unless ( C4::Context->preference('ManInvInNoissuesCharge') ) {
723 my @man_inv = Koha::Account::DebitTypes->search({ is_system => 0 })->get_column('code');
724 push @not_fines, @man_inv;
727 return $self->lines->search(
729 debit_type_code => { -not_in => \@not_fines }
731 )->total_outstanding;
736 my $lines = $self->lines;
738 Return all credits and debits for the user, outstanding or otherwise
745 return Koha::Account::Lines->search(
747 borrowernumber => $self->{patron_id},
752 =head3 reconcile_balance
754 $account->reconcile_balance();
756 Find outstanding credits and use them to pay outstanding debits.
757 Currently, this implicitly uses the 'First In First Out' rule for
758 applying credits against debits.
762 sub reconcile_balance {
765 my $outstanding_debits = $self->outstanding_debits;
766 my $outstanding_credits = $self->outstanding_credits;
768 while ( $outstanding_debits->total_outstanding > 0
769 and my $credit = $outstanding_credits->next )
771 # there's both outstanding debits and credits
772 $credit->apply( { debits => [ $outstanding_debits->as_list ] } ); # applying credit, no special offset
774 $outstanding_debits = $self->outstanding_debits;
787 Kyle M Hall <kyle.m.hall@gmail.com>
788 Tomás Cohen Arazi <tomascohen@gmail.com>
789 Martin Renvoize <martin.renvoize@ptfs-europe.com>