Bug 27636: Add payin_amount to Koha::Account
[koha-ffzg.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 payin_amount
489
490     my $credit = $account->payin_amount(
491         {
492             amount          => $amount,
493             credit_type     => $credit_type,
494             payment_type    => $payment_type,
495             cash_register   => $register_id,
496             interface       => $interface,
497             library_id      => $branchcode,
498             user_id         => $staff_id,
499             debits          => $debit_lines,
500             description     => $description,
501             note            => $note
502         }
503     );
504
505 This method allows an amount to be paid into a patrons account and immediately applied against debts.
506
507 You can optionally pass a debts parameter which consists of an arrayref of Koha::Account::Line debit lines.
508
509 $credit_type can be any of:
510   - 'PAYMENT'
511   - 'WRITEOFF'
512   - 'FORGIVEN'
513
514 =cut
515
516 sub payin_amount {
517     my ( $self, $params ) = @_;
518
519     # check for mandatory params
520     my @mandatory = ( 'interface', 'amount', 'type' );
521     for my $param (@mandatory) {
522         unless ( defined( $params->{$param} ) ) {
523             Koha::Exceptions::MissingParameter->throw(
524                 error => "The $param parameter is mandatory" );
525         }
526     }
527
528     # Check for mandatory register
529     Koha::Exceptions::Account::RegisterRequired->throw()
530       if ( C4::Context->preference("UseCashRegisters")
531         && defined( $params->{payment_type} )
532         && ( $params->{payment_type} eq 'CASH' )
533         && !defined($params->{cash_register}) );
534
535     # amount should always be passed as a positive value
536     my $amount = $params->{amount};
537     unless ( $amount > 0 ) {
538         Koha::Exceptions::Account::AmountNotPositive->throw(
539             error => 'Payin amount passed is not positive' );
540     }
541
542     my $credit;
543     my $schema = Koha::Database->new->schema;
544     $schema->txn_do(
545         sub {
546
547             # Add payin credit
548             $credit = $self->add_credit($params);
549
550             # Offset debts passed first
551             if ( exists( $params->{debits} ) ) {
552                 $credit = $credit->apply(
553                     {
554                         debits      => $params->{debits},
555                         offset_type => $params->{type}
556                     }
557                 );
558             }
559
560             # Offset against remaining balance if AutoReconcile
561             if ( C4::Context->preference("AccountAutoReconcile")
562                 && $credit->amountoutstanding != 0 )
563             {
564                 $credit = $credit->apply(
565                     {
566                         debits      => [ $self->outstanding_debits->as_list ],
567                         offset_type => $params->{type}
568                     }
569                 );
570             }
571         }
572     );
573
574     return $credit;
575 }
576
577 =head3 add_debit
578
579 This method allows adding debits to a patron's account
580
581     my $debit_line = Koha::Account->new({ patron_id => $patron_id })->add_debit(
582         {
583             amount           => $amount,
584             description      => $description,
585             note             => $note,
586             user_id          => $user_id,
587             interface        => $interface,
588             library_id       => $library_id,
589             type             => $debit_type,
590             transaction_type => $transaction_type,
591             cash_register    => $register_id,
592             item_id          => $item_id,
593             issue_id         => $issue_id
594         }
595     );
596
597 $debit_type can be any of:
598   - ACCOUNT
599   - ACCOUNT_RENEW
600   - RESERVE_EXPIRED
601   - LOST
602   - sundry
603   - NEW_CARD
604   - OVERDUE
605   - PROCESSING
606   - RENT
607   - RENT_DAILY
608   - RENT_RENEW
609   - RENT_DAILY_RENEW
610   - RESERVE
611   - PAYOUT
612
613 =cut
614
615 sub add_debit {
616
617     my ( $self, $params ) = @_;
618
619     # check for mandatory params
620     my @mandatory = ( 'interface', 'type', 'amount' );
621     for my $param (@mandatory) {
622         unless ( defined( $params->{$param} ) ) {
623             Koha::Exceptions::MissingParameter->throw(
624                 error => "The $param parameter is mandatory" );
625         }
626     }
627
628     # check for cash register if using cash
629     Koha::Exceptions::Account::RegisterRequired->throw()
630       if ( C4::Context->preference("UseCashRegisters")
631         && defined( $params->{transaction_type} )
632         && ( $params->{transaction_type} eq 'CASH' )
633         && !defined( $params->{cash_register} ) );
634
635     # amount should always be a positive value
636     my $amount = $params->{amount};
637     unless ( $amount > 0 ) {
638         Koha::Exceptions::Account::AmountNotPositive->throw(
639             error => 'Debit amount passed is not positive' );
640     }
641
642     my $description      = $params->{description} // q{};
643     my $note             = $params->{note} // q{};
644     my $user_id          = $params->{user_id};
645     my $interface        = $params->{interface};
646     my $library_id       = $params->{library_id};
647     my $cash_register    = $params->{cash_register};
648     my $debit_type       = $params->{type};
649     my $transaction_type = $params->{transaction_type};
650     my $item_id          = $params->{item_id};
651     my $issue_id         = $params->{issue_id};
652     my $offset_type      = $Koha::Account::offset_type->{$debit_type} // 'Manual Debit';
653
654     my $line;
655     my $schema = Koha::Database->new->schema;
656     try {
657         $schema->txn_do(
658             sub {
659
660                 # Insert the account line
661                 $line = Koha::Account::Line->new(
662                     {
663                         borrowernumber    => $self->{patron_id},
664                         date              => \'NOW()',
665                         amount            => $amount,
666                         description       => $description,
667                         debit_type_code   => $debit_type,
668                         amountoutstanding => $amount,
669                         payment_type      => $transaction_type,
670                         note              => $note,
671                         manager_id        => $user_id,
672                         interface         => $interface,
673                         itemnumber        => $item_id,
674                         issue_id          => $issue_id,
675                         branchcode        => $library_id,
676                         register_id       => $cash_register,
677                         (
678                             $debit_type eq 'OVERDUE'
679                             ? ( status => 'UNRETURNED' )
680                             : ()
681                         ),
682                     }
683                 )->store();
684
685                 # Record the account offset
686                 my $account_offset = Koha::Account::Offset->new(
687                     {
688                         debit_id => $line->id,
689                         type     => $offset_type,
690                         amount   => $amount
691                     }
692                 )->store();
693
694                 if ( C4::Context->preference("FinesLog") ) {
695                     logaction(
696                         "FINES", 'CREATE',
697                         $self->{patron_id},
698                         Dumper(
699                             {
700                                 action            => "create_$debit_type",
701                                 borrowernumber    => $self->{patron_id},
702                                 amount            => $amount,
703                                 description       => $description,
704                                 amountoutstanding => $amount,
705                                 debit_type_code   => $debit_type,
706                                 note              => $note,
707                                 itemnumber        => $item_id,
708                                 manager_id        => $user_id,
709                             }
710                         ),
711                         $interface
712                     );
713                 }
714             }
715         );
716     }
717     catch {
718         if ( ref($_) eq 'Koha::Exceptions::Object::FKConstraint' ) {
719             if ( $_->broken_fk eq 'debit_type_code' ) {
720                 Koha::Exceptions::Account::UnrecognisedType->throw(
721                     error => 'Type of debit not recognised' );
722             }
723             else {
724                 $_->rethrow;
725             }
726         }
727     };
728
729     return $line;
730 }
731
732 =head3 payout_amount
733
734     my $debit = $account->payout_amount(
735         {
736             payout_type => $payout_type,
737             register_id => $register_id,
738             staff_id    => $staff_id,
739             interface   => 'intranet',
740             amount      => $amount,
741             credits     => $credit_lines
742         }
743     );
744
745 This method allows an amount to be paid out from a patrons account against outstanding credits.
746
747 $payout_type can be any of the defined payment_types:
748
749 =cut
750
751 sub payout_amount {
752     my ( $self, $params ) = @_;
753
754     # Check for mandatory parameters
755     my @mandatory =
756       ( 'interface', 'staff_id', 'branch', 'payout_type', 'amount' );
757     for my $param (@mandatory) {
758         unless ( defined( $params->{$param} ) ) {
759             Koha::Exceptions::MissingParameter->throw(
760                 error => "The $param parameter is mandatory" );
761         }
762     }
763
764     # Check for mandatory register
765     Koha::Exceptions::Account::RegisterRequired->throw()
766       if ( C4::Context->preference("UseCashRegisters")
767         && ( $params->{payout_type} eq 'CASH' )
768         && !defined($params->{cash_register}) );
769
770     # Amount should always be passed as a positive value
771     my $amount = $params->{amount};
772     unless ( $amount > 0 ) {
773         Koha::Exceptions::Account::AmountNotPositive->throw(
774             error => 'Payout amount passed is not positive' );
775     }
776
777     # Amount should always be less than or equal to outstanding credit
778     my $outstanding = 0;
779     my $outstanding_credits =
780       exists( $params->{credits} )
781       ? $params->{credits}
782       : $self->outstanding_credits->as_list;
783     for my $credit ( @{$outstanding_credits} ) {
784         $outstanding += $credit->amountoutstanding;
785     }
786     $outstanding = $outstanding * -1;
787     Koha::Exceptions::ParameterTooHigh->throw( error =>
788 "Amount to payout ($amount) is higher than amountoutstanding ($outstanding)"
789     ) unless ( $outstanding >= $amount );
790
791     my $payout;
792     my $schema = Koha::Database->new->schema;
793     $schema->txn_do(
794         sub {
795
796             # A 'payout' is a 'debit'
797             $payout = $self->add_debit(
798                 {
799                     amount            => $params->{amount},
800                     type              => 'PAYOUT',
801                     transaction_type  => $params->{payout_type},
802                     amountoutstanding => $params->{amount},
803                     manager_id        => $params->{staff_id},
804                     interface         => $params->{interface},
805                     branchcode        => $params->{branch},
806                     cash_register     => $params->{cash_register}
807                 }
808             );
809
810             # Offset against credits
811             for my $credit ( @{$outstanding_credits} ) {
812                 $credit->apply(
813                     { debits => [$payout], offset_type => 'PAYOUT' } );
814                 $payout->discard_changes;
815                 last if $payout->amountoutstanding == 0;
816             }
817
818             # Set payout as paid
819             $payout->status('PAID')->store;
820         }
821     );
822
823     return $payout;
824 }
825
826 =head3 balance
827
828 my $balance = $self->balance
829
830 Return the balance (sum of amountoutstanding columns)
831
832 =cut
833
834 sub balance {
835     my ($self) = @_;
836     return $self->lines->total_outstanding;
837 }
838
839 =head3 outstanding_debits
840
841 my $lines = Koha::Account->new({ patron_id => $patron_id })->outstanding_debits;
842
843 It returns the debit lines with outstanding amounts for the patron.
844
845 In scalar context, it returns a Koha::Account::Lines iterator. In list context, it will
846 return a list of Koha::Account::Line objects.
847
848 =cut
849
850 sub outstanding_debits {
851     my ($self) = @_;
852
853     return $self->lines->search(
854         {
855             amount            => { '>' => 0 },
856             amountoutstanding => { '>' => 0 }
857         }
858     );
859 }
860
861 =head3 outstanding_credits
862
863 my $lines = Koha::Account->new({ patron_id => $patron_id })->outstanding_credits;
864
865 It returns the credit lines with outstanding amounts for the patron.
866
867 In scalar context, it returns a Koha::Account::Lines iterator. In list context, it will
868 return a list of Koha::Account::Line objects.
869
870 =cut
871
872 sub outstanding_credits {
873     my ($self) = @_;
874
875     return $self->lines->search(
876         {
877             amount            => { '<' => 0 },
878             amountoutstanding => { '<' => 0 }
879         }
880     );
881 }
882
883 =head3 non_issues_charges
884
885 my $non_issues_charges = $self->non_issues_charges
886
887 Calculates amount immediately owing by the patron - non-issue charges.
888
889 Charges exempt from non-issue are:
890 * Res (holds) if HoldsInNoissuesCharge syspref is set to false
891 * Rent (rental) if RentalsInNoissuesCharge syspref is set to false
892 * Manual invoices if ManInvInNoissuesCharge syspref is set to false
893
894 =cut
895
896 sub non_issues_charges {
897     my ($self) = @_;
898
899     #NOTE: With bug 23049 these preferences could be moved to being attached
900     #to individual debit types to give more flexability and specificity.
901     my @not_fines;
902     push @not_fines, 'RESERVE'
903       unless C4::Context->preference('HoldsInNoissuesCharge');
904     push @not_fines, ( 'RENT', 'RENT_DAILY', 'RENT_RENEW', 'RENT_DAILY_RENEW' )
905       unless C4::Context->preference('RentalsInNoissuesCharge');
906     unless ( C4::Context->preference('ManInvInNoissuesCharge') ) {
907         my @man_inv = Koha::Account::DebitTypes->search({ is_system => 0 })->get_column('code');
908         push @not_fines, @man_inv;
909     }
910
911     return $self->lines->search(
912         {
913             debit_type_code => { -not_in => \@not_fines }
914         },
915     )->total_outstanding;
916 }
917
918 =head3 lines
919
920 my $lines = $self->lines;
921
922 Return all credits and debits for the user, outstanding or otherwise
923
924 =cut
925
926 sub lines {
927     my ($self) = @_;
928
929     return Koha::Account::Lines->search(
930         {
931             borrowernumber => $self->{patron_id},
932         }
933     );
934 }
935
936 =head3 reconcile_balance
937
938 $account->reconcile_balance();
939
940 Find outstanding credits and use them to pay outstanding debits.
941 Currently, this implicitly uses the 'First In First Out' rule for
942 applying credits against debits.
943
944 =cut
945
946 sub reconcile_balance {
947     my ($self) = @_;
948
949     my $outstanding_debits  = $self->outstanding_debits;
950     my $outstanding_credits = $self->outstanding_credits;
951
952     while (     $outstanding_debits->total_outstanding > 0
953             and my $credit = $outstanding_credits->next )
954     {
955         # there's both outstanding debits and credits
956         $credit->apply( { debits => [ $outstanding_debits->as_list ] } );    # applying credit, no special offset
957
958         $outstanding_debits = $self->outstanding_debits;
959
960     }
961
962     return $self;
963 }
964
965 1;
966
967 =head2 Name mappings
968
969 =head3 $offset_type
970
971 =cut
972
973 our $offset_type = {
974     'CREDIT'           => 'Manual Credit',
975     'FORGIVEN'         => 'Writeoff',
976     'LOST_FOUND'       => 'Lost Item Found',
977     'OVERPAYMENT'      => 'Overpayment',
978     'PAYMENT'          => 'Payment',
979     'WRITEOFF'         => 'Writeoff',
980     'ACCOUNT'          => 'Account Fee',
981     'ACCOUNT_RENEW'    => 'Account Fee',
982     'RESERVE'          => 'Reserve Fee',
983     'PROCESSING'       => 'Processing Fee',
984     'LOST'             => 'Lost Item',
985     'RENT'             => 'Rental Fee',
986     'RENT_DAILY'       => 'Rental Fee',
987     'RENT_RENEW'       => 'Rental Fee',
988     'RENT_DAILY_RENEW' => 'Rental Fee',
989     'OVERDUE'          => 'OVERDUE',
990     'RESERVE_EXPIRED'  => 'Hold Expired',
991     'PAYOUT'           => 'PAYOUT',
992 };
993
994 =head1 AUTHORS
995
996 =encoding utf8
997
998 Kyle M Hall <kyle.m.hall@gmail.com>
999 Tomás Cohen Arazi <tomascohen@gmail.com>
1000 Martin Renvoize <martin.renvoize@ptfs-europe.com>
1001
1002 =cut