Bug 24300: (QA follow-up) Update payout_amount to use internal methods
[koha.git] / Koha / Account.pm
1 package Koha::Account;
2
3 # Copyright 2016 ByWater Solutions
4 #
5 # This file is part of Koha.
6 #
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.
11 #
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.
16 #
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>.
19
20 use Modern::Perl;
21
22 use Carp;
23 use Data::Dumper;
24 use List::MoreUtils qw( uniq );
25 use Try::Tiny;
26
27 use C4::Circulation qw( ReturnLostItem CanBookBeRenewed AddRenewal );
28 use C4::Letters;
29 use C4::Log qw( logaction );
30 use C4::Stats qw( UpdateStats );
31 use C4::Overdues qw(GetFine);
32
33 use Koha::Patrons;
34 use Koha::Account::Lines;
35 use Koha::Account::Offsets;
36 use Koha::Account::DebitTypes;
37 use Koha::DateUtils qw( dt_from_string );
38 use Koha::Exceptions;
39 use Koha::Exceptions::Account;
40
41 =head1 NAME
42
43 Koha::Accounts - Module for managing payments and fees for patrons
44
45 =cut
46
47 sub new {
48     my ( $class, $params ) = @_;
49
50     Carp::croak("No patron id passed in!") unless $params->{patron_id};
51
52     return bless( $params, $class );
53 }
54
55 =head2 pay
56
57 This method allows payments to be made against fees/fines
58
59 Koha::Account->new( { patron_id => $borrowernumber } )->pay(
60     {
61         amount      => $amount,
62         note        => $note,
63         description => $description,
64         library_id  => $branchcode,
65         lines       => $lines, # Arrayref of Koha::Account::Line objects to pay
66         credit_type => $type,  # credit_type_code code
67         offset_type => $offset_type,    # offset type code
68         item_id     => $itemnumber,     # pass the itemnumber if this is a credit pertianing to a specific item (i.e LOST_FOUND)
69     }
70 );
71
72 =cut
73
74 sub pay {
75     my ( $self, $params ) = @_;
76
77     my $amount        = $params->{amount};
78     my $description   = $params->{description};
79     my $note          = $params->{note} || q{};
80     my $library_id    = $params->{library_id};
81     my $lines         = $params->{lines};
82     my $type          = $params->{type} || 'PAYMENT';
83     my $payment_type  = $params->{payment_type} || undef;
84     my $credit_type   = $params->{credit_type};
85     my $offset_type   = $params->{offset_type} || $type eq 'WRITEOFF' ? 'Writeoff' : 'Payment';
86     my $cash_register = $params->{cash_register};
87     my $item_id       = $params->{item_id};
88
89     my $userenv = C4::Context->userenv;
90
91     $credit_type ||=
92       $type eq 'WRITEOFF'
93       ? 'WRITEOFF'
94       : 'PAYMENT';
95
96     my $patron = Koha::Patrons->find( $self->{patron_id} );
97
98     my $manager_id = $userenv ? $userenv->{number} : undef;
99     my $interface = $params ? ( $params->{interface} || C4::Context->interface ) : C4::Context->interface;
100     Koha::Exceptions::Account::RegisterRequired->throw()
101       if ( C4::Context->preference("UseCashRegisters")
102         && defined($payment_type)
103         && ( $payment_type eq 'CASH' )
104         && !defined($cash_register) );
105
106     my @fines_paid; # List of account lines paid on with this payment
107
108     # The outcome of any attempted item renewals as a result of fines being
109     # paid off
110     my $renew_outcomes = [];
111
112     my $balance_remaining = $amount; # Set it now so we can adjust the amount if necessary
113     $balance_remaining ||= 0;
114
115     my @account_offsets;
116
117     # We were passed a specific line to pay
118     foreach my $fine ( @$lines ) {
119         my $amount_to_pay =
120             $fine->amountoutstanding > $balance_remaining
121           ? $balance_remaining
122           : $fine->amountoutstanding;
123
124         my $old_amountoutstanding = $fine->amountoutstanding;
125         my $new_amountoutstanding = $old_amountoutstanding - $amount_to_pay;
126         $fine->amountoutstanding($new_amountoutstanding)->store();
127         $balance_remaining = $balance_remaining - $amount_to_pay;
128
129         # Attempt to renew the item associated with this debit if
130         # appropriate
131         if ($fine->is_renewable) {
132             # We're ignoring the definition of $interface above, by all
133             # accounts we can't rely on C4::Context::interface, so here
134             # we're only using what we've been explicitly passed
135             my $outcome = $fine->renew_item({ interface => $interface });
136             push @{$renew_outcomes}, $outcome if $outcome;
137         }
138
139         # Same logic exists in Koha::Account::Line::apply
140         if ( C4::Context->preference('MarkLostItemsAsReturned') =~ m|onpayment|
141             && $fine->debit_type_code
142             && $fine->debit_type_code eq 'LOST'
143             && $new_amountoutstanding == 0
144             && $fine->itemnumber
145             && !(  $credit_type eq 'LOST_FOUND'
146                 && $item_id == $fine->itemnumber ) )
147         {
148             C4::Circulation::ReturnLostItem( $self->{patron_id},
149                 $fine->itemnumber );
150         }
151
152         my $account_offset = Koha::Account::Offset->new(
153             {
154                 debit_id => $fine->id,
155                 type     => $offset_type,
156                 amount   => $amount_to_pay * -1,
157             }
158         );
159         push( @account_offsets, $account_offset );
160
161         if ( C4::Context->preference("FinesLog") ) {
162             logaction(
163                 "FINES", 'MODIFY',
164                 $self->{patron_id},
165                 Dumper(
166                     {
167                         action                => 'fee_payment',
168                         borrowernumber        => $fine->borrowernumber,
169                         old_amountoutstanding => $old_amountoutstanding,
170                         new_amountoutstanding => 0,
171                         amount_paid           => $old_amountoutstanding,
172                         accountlines_id       => $fine->id,
173                         manager_id            => $manager_id,
174                         note                  => $note,
175                     }
176                 ),
177                 $interface
178             );
179             push( @fines_paid, $fine->id );
180         }
181     }
182
183     # Were not passed a specific line to pay, or the payment was for more
184     # than the what was owed on the given line. In that case pay down other
185     # lines with remaining balance.
186     my @outstanding_fines;
187     @outstanding_fines = $self->lines->search(
188         {
189             amountoutstanding => { '>' => 0 },
190         }
191     ) if $balance_remaining > 0;
192
193     foreach my $fine (@outstanding_fines) {
194         my $amount_to_pay =
195             $fine->amountoutstanding > $balance_remaining
196           ? $balance_remaining
197           : $fine->amountoutstanding;
198
199         my $old_amountoutstanding = $fine->amountoutstanding;
200         $fine->amountoutstanding( $old_amountoutstanding - $amount_to_pay );
201         $fine->store();
202
203         # If we need to make a note of the item associated with this line,
204         # in order that we can potentially renew it, do so.
205         my $amt = $old_amountoutstanding - $amount_to_pay;
206         if ( $fine->is_renewable ) {
207             my $outcome = $fine->renew_item({ interface => $interface });
208             push @{$renew_outcomes}, $outcome if $outcome;
209         }
210
211         if ( C4::Context->preference('MarkLostItemsAsReturned') =~ m|onpayment|
212             && $fine->debit_type_code
213             && $fine->debit_type_code eq 'LOST'
214             && $fine->amountoutstanding == 0
215             && $fine->itemnumber
216             && !(  $credit_type eq 'LOST_FOUND'
217                 && $item_id == $fine->itemnumber ) )
218         {
219             C4::Circulation::ReturnLostItem( $self->{patron_id},
220                 $fine->itemnumber );
221         }
222
223         my $account_offset = Koha::Account::Offset->new(
224             {
225                 debit_id => $fine->id,
226                 type     => $offset_type,
227                 amount   => $amount_to_pay * -1,
228             }
229         );
230         push( @account_offsets, $account_offset );
231
232         if ( C4::Context->preference("FinesLog") ) {
233             logaction(
234                 "FINES", 'MODIFY',
235                 $self->{patron_id},
236                 Dumper(
237                     {
238                         action                => "fee_$type",
239                         borrowernumber        => $fine->borrowernumber,
240                         old_amountoutstanding => $old_amountoutstanding,
241                         new_amountoutstanding => $fine->amountoutstanding,
242                         amount_paid           => $amount_to_pay,
243                         accountlines_id       => $fine->id,
244                         manager_id            => $manager_id,
245                         note                  => $note,
246                     }
247                 ),
248                 $interface
249             );
250             push( @fines_paid, $fine->id );
251         }
252
253         $balance_remaining = $balance_remaining - $amount_to_pay;
254         last unless $balance_remaining > 0;
255     }
256
257     $description ||= $type eq 'WRITEOFF' ? 'Writeoff' : q{};
258
259     my $payment = Koha::Account::Line->new(
260         {
261             borrowernumber    => $self->{patron_id},
262             date              => dt_from_string(),
263             amount            => 0 - $amount,
264             description       => $description,
265             credit_type_code  => $credit_type,
266             payment_type      => $payment_type,
267             amountoutstanding => 0 - $balance_remaining,
268             manager_id        => $manager_id,
269             interface         => $interface,
270             branchcode        => $library_id,
271             register_id       => $cash_register,
272             note              => $note,
273             itemnumber        => $item_id,
274         }
275     )->store();
276
277     foreach my $o ( @account_offsets ) {
278         $o->credit_id( $payment->id() );
279         $o->store();
280     }
281
282     C4::Stats::UpdateStats(
283         {
284             branch         => $library_id,
285             type           => lc($type),
286             amount         => $amount,
287             borrowernumber => $self->{patron_id},
288         }
289     );
290
291     if ( C4::Context->preference("FinesLog") ) {
292         logaction(
293             "FINES", 'CREATE',
294             $self->{patron_id},
295             Dumper(
296                 {
297                     action            => "create_$type",
298                     borrowernumber    => $self->{patron_id},
299                     amount            => 0 - $amount,
300                     amountoutstanding => 0 - $balance_remaining,
301                     credit_type_code  => $credit_type,
302                     accountlines_paid => \@fines_paid,
303                     manager_id        => $manager_id,
304                 }
305             ),
306             $interface
307         );
308     }
309
310     if ( C4::Context->preference('UseEmailReceipts') ) {
311         if (
312             my $letter = C4::Letters::GetPreparedLetter(
313                 module                 => 'circulation',
314                 letter_code            => uc("ACCOUNT_$type"),
315                 message_transport_type => 'email',
316                 lang    => $patron->lang,
317                 tables => {
318                     borrowers       => $self->{patron_id},
319                     branches        => $library_id,
320                 },
321                 substitute => {
322                     credit => $payment,
323                     offsets => \@account_offsets,
324                 },
325               )
326           )
327         {
328             C4::Letters::EnqueueLetter(
329                 {
330                     letter                 => $letter,
331                     borrowernumber         => $self->{patron_id},
332                     message_transport_type => 'email',
333                 }
334             ) or warn "can't enqueue letter $letter";
335         }
336     }
337
338     return { payment_id => $payment->id, renew_result => $renew_outcomes };
339 }
340
341 =head3 add_credit
342
343 This method allows adding credits to a patron's account
344
345 my $credit_line = Koha::Account->new({ patron_id => $patron_id })->add_credit(
346     {
347         amount       => $amount,
348         description  => $description,
349         note         => $note,
350         user_id      => $user_id,
351         interface    => $interface,
352         library_id   => $library_id,
353         payment_type => $payment_type,
354         type         => $credit_type,
355         item_id      => $item_id
356     }
357 );
358
359 $credit_type can be any of:
360   - 'CREDIT'
361   - 'PAYMENT'
362   - 'FORGIVEN'
363   - 'LOST_FOUND'
364   - 'OVERPAYMENT'
365   - 'PAYMENT'
366   - 'WRITEOFF'
367
368 =cut
369
370 sub add_credit {
371
372     my ( $self, $params ) = @_;
373
374     # check for mandatory params
375     my @mandatory = ( 'interface', 'amount' );
376     for my $param (@mandatory) {
377         unless ( defined( $params->{$param} ) ) {
378             Koha::Exceptions::MissingParameter->throw(
379                 error => "The $param parameter is mandatory" );
380         }
381     }
382
383     # amount should always be passed as a positive value
384     my $amount = $params->{amount} * -1;
385     unless ( $amount < 0 ) {
386         Koha::Exceptions::Account::AmountNotPositive->throw(
387             error => 'Debit amount passed is not positive' );
388     }
389
390     my $description   = $params->{description} // q{};
391     my $note          = $params->{note} // q{};
392     my $user_id       = $params->{user_id};
393     my $interface     = $params->{interface};
394     my $library_id    = $params->{library_id};
395     my $cash_register = $params->{cash_register};
396     my $payment_type  = $params->{payment_type};
397     my $credit_type   = $params->{type} || 'PAYMENT';
398     my $item_id       = $params->{item_id};
399
400     Koha::Exceptions::Account::RegisterRequired->throw()
401       if ( C4::Context->preference("UseCashRegisters")
402         && defined($payment_type)
403         && ( $payment_type eq 'CASH' )
404         && !defined($cash_register) );
405
406     my $line;
407     my $schema = Koha::Database->new->schema;
408     try {
409         $schema->txn_do(
410             sub {
411
412                 # Insert the account line
413                 $line = Koha::Account::Line->new(
414                     {
415                         borrowernumber    => $self->{patron_id},
416                         date              => \'NOW()',
417                         amount            => $amount,
418                         description       => $description,
419                         credit_type_code  => $credit_type,
420                         amountoutstanding => $amount,
421                         payment_type      => $payment_type,
422                         note              => $note,
423                         manager_id        => $user_id,
424                         interface         => $interface,
425                         branchcode        => $library_id,
426                         register_id       => $cash_register,
427                         itemnumber        => $item_id,
428                     }
429                 )->store();
430
431                 # Record the account offset
432                 my $account_offset = Koha::Account::Offset->new(
433                     {
434                         credit_id => $line->id,
435                         type   => $Koha::Account::offset_type->{$credit_type} // $Koha::Account::offset_type->{CREDIT},
436                         amount => $amount
437                     }
438                 )->store();
439
440                 C4::Stats::UpdateStats(
441                     {
442                         branch         => $library_id,
443                         type           => lc($credit_type),
444                         amount         => $amount,
445                         borrowernumber => $self->{patron_id},
446                     }
447                 ) if grep { $credit_type eq $_ } ( 'PAYMENT', 'WRITEOFF' );
448
449                 if ( C4::Context->preference("FinesLog") ) {
450                     logaction(
451                         "FINES", 'CREATE',
452                         $self->{patron_id},
453                         Dumper(
454                             {
455                                 action            => "create_$credit_type",
456                                 borrowernumber    => $self->{patron_id},
457                                 amount            => $amount,
458                                 description       => $description,
459                                 amountoutstanding => $amount,
460                                 credit_type_code  => $credit_type,
461                                 note              => $note,
462                                 itemnumber        => $item_id,
463                                 manager_id        => $user_id,
464                                 branchcode        => $library_id,
465                             }
466                         ),
467                         $interface
468                     );
469                 }
470             }
471         );
472     }
473     catch {
474         if ( ref($_) eq 'Koha::Exceptions::Object::FKConstraint' ) {
475             if ( $_->broken_fk eq 'credit_type_code' ) {
476                 Koha::Exceptions::Account::UnrecognisedType->throw(
477                     error => 'Type of credit not recognised' );
478             }
479             else {
480                 $_->rethrow;
481             }
482         }
483     };
484
485     return $line;
486 }
487
488 =head3 add_debit
489
490 This method allows adding debits to a patron's account
491
492 my $debit_line = Koha::Account->new({ patron_id => $patron_id })->add_debit(
493     {
494         amount       => $amount,
495         description  => $description,
496         note         => $note,
497         user_id      => $user_id,
498         interface    => $interface,
499         library_id   => $library_id,
500         type         => $debit_type,
501         item_id      => $item_id,
502         issue_id     => $issue_id
503     }
504 );
505
506 $debit_type can be any of:
507   - ACCOUNT
508   - ACCOUNT_RENEW
509   - RESERVE_EXPIRED
510   - LOST
511   - sundry
512   - NEW_CARD
513   - OVERDUE
514   - PROCESSING
515   - RENT
516   - RENT_DAILY
517   - RENT_RENEW
518   - RENT_DAILY_RENEW
519   - RESERVE
520
521 =cut
522
523 sub add_debit {
524
525     my ( $self, $params ) = @_;
526
527     # check for mandatory params
528     my @mandatory = ( 'interface', 'type', 'amount' );
529     for my $param (@mandatory) {
530         unless ( defined( $params->{$param} ) ) {
531             Koha::Exceptions::MissingParameter->throw(
532                 error => "The $param parameter is mandatory" );
533         }
534     }
535
536     # amount should always be a positive value
537     my $amount = $params->{amount};
538     unless ( $amount > 0 ) {
539         Koha::Exceptions::Account::AmountNotPositive->throw(
540             error => 'Debit amount passed is not positive' );
541     }
542
543     my $description = $params->{description} // q{};
544     my $note        = $params->{note} // q{};
545     my $user_id     = $params->{user_id};
546     my $interface   = $params->{interface};
547     my $library_id  = $params->{library_id};
548     my $debit_type  = $params->{type};
549     my $item_id     = $params->{item_id};
550     my $issue_id    = $params->{issue_id};
551     my $offset_type = $Koha::Account::offset_type->{$debit_type} // 'Manual Debit';
552
553     my $line;
554     my $schema = Koha::Database->new->schema;
555     try {
556         $schema->txn_do(
557             sub {
558
559                 # Insert the account line
560                 $line = Koha::Account::Line->new(
561                     {
562                         borrowernumber    => $self->{patron_id},
563                         date              => \'NOW()',
564                         amount            => $amount,
565                         description       => $description,
566                         debit_type_code   => $debit_type,
567                         amountoutstanding => $amount,
568                         payment_type      => undef,
569                         note              => $note,
570                         manager_id        => $user_id,
571                         interface         => $interface,
572                         itemnumber        => $item_id,
573                         issue_id          => $issue_id,
574                         branchcode        => $library_id,
575                         (
576                             $debit_type eq 'OVERDUE'
577                             ? ( status => 'UNRETURNED' )
578                             : ()
579                         ),
580                     }
581                 )->store();
582
583                 # Record the account offset
584                 my $account_offset = Koha::Account::Offset->new(
585                     {
586                         debit_id => $line->id,
587                         type     => $offset_type,
588                         amount   => $amount
589                     }
590                 )->store();
591
592                 if ( C4::Context->preference("FinesLog") ) {
593                     logaction(
594                         "FINES", 'CREATE',
595                         $self->{patron_id},
596                         Dumper(
597                             {
598                                 action            => "create_$debit_type",
599                                 borrowernumber    => $self->{patron_id},
600                                 amount            => $amount,
601                                 description       => $description,
602                                 amountoutstanding => $amount,
603                                 debit_type_code   => $debit_type,
604                                 note              => $note,
605                                 itemnumber        => $item_id,
606                                 manager_id        => $user_id,
607                             }
608                         ),
609                         $interface
610                     );
611                 }
612             }
613         );
614     }
615     catch {
616         if ( ref($_) eq 'Koha::Exceptions::Object::FKConstraint' ) {
617             if ( $_->broken_fk eq 'debit_type_code' ) {
618                 Koha::Exceptions::Account::UnrecognisedType->throw(
619                     error => 'Type of debit not recognised' );
620             }
621             else {
622                 $_->rethrow;
623             }
624         }
625     };
626
627     return $line;
628 }
629
630 =head3 payout_amount
631
632     my $debit = $account->payout_amount(
633         {
634             payout_type => $payout_type,
635             register_id => $register_id,
636             staff_id    => $staff_id,
637             interface   => 'intranet',
638             amount      => $amount,
639             credits     => $credit_lines
640         }
641     );
642
643 This method allows an amount to be paid out from a patrons account against outstanding credits.
644
645 $payout_type can be any of the defined payment_types:
646
647 =cut
648
649 sub payout_amount {
650     my ( $self, $params ) = @_;
651
652     # Check for mandatory parameters
653     my @mandatory =
654       ( 'interface', 'staff_id', 'branch', 'payout_type', 'amount' );
655     for my $param (@mandatory) {
656         unless ( defined( $params->{$param} ) ) {
657             Koha::Exceptions::MissingParameter->throw(
658                 error => "The $param parameter is mandatory" );
659         }
660     }
661
662     # Check for mandatory register
663     Koha::Exceptions::Account::RegisterRequired->throw()
664       if ( C4::Context->preference("UseCashRegisters")
665         && ( $params->{payout_type} eq 'CASH' )
666         && !defined($params->{cash_register}) );
667
668     # Amount should always be passed as a positive value
669     my $amount = $params->{amount};
670     unless ( $amount > 0 ) {
671         Koha::Exceptions::Account::AmountNotPositive->throw(
672             error => 'Debit amount passed is not positive' );
673     }
674
675     # Amount should always be less than or equal to outstanding credit
676     my $outstanding = 0;
677     my $outstanding_credits =
678       exists( $params->{credits} )
679       ? $params->{credits}
680       : $self->outstanding_credits->as_list;
681     for my $credit ( @{$outstanding_credits} ) {
682         $outstanding += $credit->amountoutstanding;
683     }
684     $outstanding = $outstanding * -1;
685     Koha::Exceptions::ParameterTooHigh->throw( error =>
686 "Amount to payout ($amount) is higher than amountoutstanding ($outstanding)"
687     ) unless ( $outstanding >= $amount );
688
689     my $payout;
690     my $schema = Koha::Database->new->schema;
691     $schema->txn_do(
692         sub {
693
694             # A 'payout' is a 'debit'
695             $payout = $self->add_debit(
696                 {
697                     amount            => $params->{amount},
698                     type              => 'PAYOUT',
699                     payment_type      => $params->{payout_type},
700                     amountoutstanding => $params->{amount},
701                     manager_id        => $params->{staff_id},
702                     interface         => $params->{interface},
703                     branchcode        => $params->{branch},
704                     register_id       => $params->{cash_register}
705                 }
706             );
707
708             # Offset against credits
709             for my $credit ( @{$outstanding_credits} ) {
710                 $credit->apply(
711                     { debits => [$payout], offset_type => 'PAYOUT' } );
712                 $payout->discard_changes;
713             }
714
715             # Set payout as paid
716             $payout->status('PAID')->store;
717         }
718     );
719
720     return $payout;
721 }
722
723 =head3 balance
724
725 my $balance = $self->balance
726
727 Return the balance (sum of amountoutstanding columns)
728
729 =cut
730
731 sub balance {
732     my ($self) = @_;
733     return $self->lines->total_outstanding;
734 }
735
736 =head3 outstanding_debits
737
738 my $lines = Koha::Account->new({ patron_id => $patron_id })->outstanding_debits;
739
740 It returns the debit lines with outstanding amounts for the patron.
741
742 In scalar context, it returns a Koha::Account::Lines iterator. In list context, it will
743 return a list of Koha::Account::Line objects.
744
745 =cut
746
747 sub outstanding_debits {
748     my ($self) = @_;
749
750     return $self->lines->search(
751         {
752             amount            => { '>' => 0 },
753             amountoutstanding => { '>' => 0 }
754         }
755     );
756 }
757
758 =head3 outstanding_credits
759
760 my $lines = Koha::Account->new({ patron_id => $patron_id })->outstanding_credits;
761
762 It returns the credit lines with outstanding amounts for the patron.
763
764 In scalar context, it returns a Koha::Account::Lines iterator. In list context, it will
765 return a list of Koha::Account::Line objects.
766
767 =cut
768
769 sub outstanding_credits {
770     my ($self) = @_;
771
772     return $self->lines->search(
773         {
774             amount            => { '<' => 0 },
775             amountoutstanding => { '<' => 0 }
776         }
777     );
778 }
779
780 =head3 non_issues_charges
781
782 my $non_issues_charges = $self->non_issues_charges
783
784 Calculates amount immediately owing by the patron - non-issue charges.
785
786 Charges exempt from non-issue are:
787 * Res (holds) if HoldsInNoissuesCharge syspref is set to false
788 * Rent (rental) if RentalsInNoissuesCharge syspref is set to false
789 * Manual invoices if ManInvInNoissuesCharge syspref is set to false
790
791 =cut
792
793 sub non_issues_charges {
794     my ($self) = @_;
795
796     #NOTE: With bug 23049 these preferences could be moved to being attached
797     #to individual debit types to give more flexability and specificity.
798     my @not_fines;
799     push @not_fines, 'RESERVE'
800       unless C4::Context->preference('HoldsInNoissuesCharge');
801     push @not_fines, ( 'RENT', 'RENT_DAILY', 'RENT_RENEW', 'RENT_DAILY_RENEW' )
802       unless C4::Context->preference('RentalsInNoissuesCharge');
803     unless ( C4::Context->preference('ManInvInNoissuesCharge') ) {
804         my @man_inv = Koha::Account::DebitTypes->search({ is_system => 0 })->get_column('code');
805         push @not_fines, @man_inv;
806     }
807
808     return $self->lines->search(
809         {
810             debit_type_code => { -not_in => \@not_fines }
811         },
812     )->total_outstanding;
813 }
814
815 =head3 lines
816
817 my $lines = $self->lines;
818
819 Return all credits and debits for the user, outstanding or otherwise
820
821 =cut
822
823 sub lines {
824     my ($self) = @_;
825
826     return Koha::Account::Lines->search(
827         {
828             borrowernumber => $self->{patron_id},
829         }
830     );
831 }
832
833 =head3 reconcile_balance
834
835 $account->reconcile_balance();
836
837 Find outstanding credits and use them to pay outstanding debits.
838 Currently, this implicitly uses the 'First In First Out' rule for
839 applying credits against debits.
840
841 =cut
842
843 sub reconcile_balance {
844     my ($self) = @_;
845
846     my $outstanding_debits  = $self->outstanding_debits;
847     my $outstanding_credits = $self->outstanding_credits;
848
849     while (     $outstanding_debits->total_outstanding > 0
850             and my $credit = $outstanding_credits->next )
851     {
852         # there's both outstanding debits and credits
853         $credit->apply( { debits => [ $outstanding_debits->as_list ] } );    # applying credit, no special offset
854
855         $outstanding_debits = $self->outstanding_debits;
856
857     }
858
859     return $self;
860 }
861
862 1;
863
864 =head2 Name mappings
865
866 =head3 $offset_type
867
868 =cut
869
870 our $offset_type = {
871     'CREDIT'           => 'Manual Credit',
872     'FORGIVEN'         => 'Writeoff',
873     'LOST_FOUND'       => 'Lost Item Found',
874     'OVERPAYMENT'      => 'Overpayment',
875     'PAYMENT'          => 'Payment',
876     'WRITEOFF'         => 'Writeoff',
877     'ACCOUNT'          => 'Account Fee',
878     'ACCOUNT_RENEW'    => 'Account Fee',
879     'RESERVE'          => 'Reserve Fee',
880     'PROCESSING'       => 'Processing Fee',
881     'LOST'             => 'Lost Item',
882     'RENT'             => 'Rental Fee',
883     'RENT_DAILY'       => 'Rental Fee',
884     'RENT_RENEW'       => 'Rental Fee',
885     'RENT_DAILY_RENEW' => 'Rental Fee',
886     'OVERDUE'          => 'OVERDUE',
887     'RESERVE_EXPIRED'  => 'Hold Expired'
888 };
889
890 =head1 AUTHORS
891
892 =encoding utf8
893
894 Kyle M Hall <kyle.m.hall@gmail.com>
895 Tomás Cohen Arazi <tomascohen@gmail.com>
896 Martin Renvoize <martin.renvoize@ptfs-europe.com>
897
898 =cut