a757f5247a650db1f9eaa9ebda9c00c24b024de7
[koha.git] / t / db_dependent / Koha / Account.t
1 #!/usr/bin/perl
2
3 # Copyright 2018 Koha Development team
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 Test::More tests => 11;
23 use Test::MockModule;
24 use Test::Exception;
25
26 use Koha::Account;
27 use Koha::Account::Lines;
28 use Koha::Account::Offsets;
29
30
31 use t::lib::Mocks;
32 use t::lib::TestBuilder;
33
34 my $schema  = Koha::Database->new->schema;
35 my $builder = t::lib::TestBuilder->new;
36 C4::Context->interface('commandline');
37
38 subtest 'new' => sub {
39
40     plan tests => 2;
41
42     $schema->storage->txn_begin;
43
44     throws_ok { Koha::Account->new(); } qr/No patron id passed in!/, 'Croaked on bad call to new';
45
46     my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
47     my $account = Koha::Account->new( { patron_id => $patron->borrowernumber } );
48     is( defined $account, 1, "Account is defined" );
49
50     $schema->storage->txn_rollback;
51 };
52
53 subtest 'outstanding_debits() tests' => sub {
54
55     plan tests => 22;
56
57     $schema->storage->txn_begin;
58
59     my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
60     my $account = $patron->account;
61
62     my @generated_lines;
63     push @generated_lines, $account->add_debit({ amount => 1, interface => 'commandline', type => 'overdue' });
64     push @generated_lines, $account->add_debit({ amount => 2, interface => 'commandline', type => 'overdue' });
65     push @generated_lines, $account->add_debit({ amount => 3, interface => 'commandline', type => 'overdue' });
66     push @generated_lines, $account->add_debit({ amount => 4, interface => 'commandline', type => 'overdue' });
67
68     my $lines     = $account->outstanding_debits();
69     my @lines_arr = $account->outstanding_debits();
70
71     is( ref($lines), 'Koha::Account::Lines', 'Called in scalar context, outstanding_debits returns a Koha::Account::Lines object' );
72     is( $lines->total_outstanding, 10, 'Outstandig debits total is correctly calculated' );
73
74     my $i = 0;
75     foreach my $line ( @{ $lines->as_list } ) {
76         my $fetched_line = Koha::Account::Lines->find( $generated_lines[$i]->id );
77         is_deeply( $line->unblessed, $fetched_line->unblessed, "Fetched line matches the generated one ($i)" );
78         is_deeply( $lines_arr[$i]->unblessed, $fetched_line->unblessed, "Fetched line matches the generated one ($i)" );
79         is( ref($lines_arr[$i]), 'Koha::Account::Line', 'outstanding_debits returns a list of Koha::Account::Line objects in list context' );
80         $i++;
81     }
82     my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
83     Koha::Account::Line->new({ borrowernumber => $patron_2->id, amountoutstanding => -2, interface => 'commandline' })->store;
84     my $just_one = Koha::Account::Line->new({ borrowernumber => $patron_2->id, amount => 3, amountoutstanding =>  3, interface => 'commandline' })->store;
85     Koha::Account::Line->new({ borrowernumber => $patron_2->id, amount => -6, amountoutstanding => -6, interface => 'commandline' })->store;
86     $lines = $patron_2->account->outstanding_debits();
87     is( $lines->total_outstanding, 3, "Total if some outstanding debits and some credits is only debits" );
88     is( $lines->count, 1, "With 1 outstanding debits, we get back a Lines object with 1 lines" );
89     my $the_line = Koha::Account::Lines->find( $just_one->id );
90     is_deeply( $the_line->unblessed, $lines->next->unblessed, "We get back the one correct line");
91
92     my $patron_3  = $builder->build_object({ class => 'Koha::Patrons' });
93     my $account_3 = $patron_3->account;
94     $account_3->add_credit( { amount => 2,   interface => 'commandline' } );
95     $account_3->add_credit( { amount => 20,  interface => 'commandline' } );
96     $account_3->add_credit( { amount => 200, interface => 'commandline' } );
97     $lines = $account_3->outstanding_debits();
98     is( $lines->total_outstanding, 0, "Total if no outstanding debits total is 0" );
99     is( $lines->count, 0, "With 0 outstanding debits, we get back a Lines object with 0 lines" );
100
101     my $patron_4  = $builder->build_object({ class => 'Koha::Patrons' });
102     my $account_4 = $patron_4->account;
103     $lines = $account_4->outstanding_debits();
104     is( $lines->total_outstanding, 0, "Total if no outstanding debits is 0" );
105     is( $lines->count, 0, "With no outstanding debits, we get back a Lines object with 0 lines" );
106
107     # create a pathological credit with amountoutstanding > 0 (BZ 14591)
108     Koha::Account::Line->new({ borrowernumber => $patron_4->id, amount => -3, amountoutstanding => 3, interface => 'commandline' })->store();
109     $lines = $account_4->outstanding_debits();
110     is( $lines->count, 0, 'No credits are confused with debits because of the amountoutstanding value' );
111
112     $schema->storage->txn_rollback;
113 };
114
115 subtest 'outstanding_credits() tests' => sub {
116
117     plan tests => 17;
118
119     $schema->storage->txn_begin;
120
121     my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
122     my $account = $patron->account;
123
124     my @generated_lines;
125     push @generated_lines, $account->add_credit({ amount => 1, interface => 'commandline' });
126     push @generated_lines, $account->add_credit({ amount => 2, interface => 'commandline' });
127     push @generated_lines, $account->add_credit({ amount => 3, interface => 'commandline' });
128     push @generated_lines, $account->add_credit({ amount => 4, interface => 'commandline' });
129
130     my $lines     = $account->outstanding_credits();
131     my @lines_arr = $account->outstanding_credits();
132
133     is( ref($lines), 'Koha::Account::Lines', 'Called in scalar context, outstanding_credits returns a Koha::Account::Lines object' );
134     is( $lines->total_outstanding, -10, 'Outstandig credits total is correctly calculated' );
135
136     my $i = 0;
137     foreach my $line ( @{ $lines->as_list } ) {
138         my $fetched_line = Koha::Account::Lines->find( $generated_lines[$i]->id );
139         is_deeply( $line->unblessed, $fetched_line->unblessed, "Fetched line matches the generated one ($i)" );
140         is_deeply( $lines_arr[$i]->unblessed, $fetched_line->unblessed, "Fetched line matches the generated one ($i)" );
141         is( ref($lines_arr[$i]), 'Koha::Account::Line', 'outstanding_debits returns a list of Koha::Account::Line objects in list context' );
142         $i++;
143     }
144
145     my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
146     $account  = $patron_2->account;
147     $lines       = $account->outstanding_credits();
148     is( $lines->total_outstanding, 0, "Total if no outstanding credits is 0" );
149     is( $lines->count, 0, "With no outstanding credits, we get back a Lines object with 0 lines" );
150
151     # create a pathological debit with amountoutstanding < 0 (BZ 14591)
152     Koha::Account::Line->new({ borrowernumber => $patron_2->id, amount => 2, amountoutstanding => -3, interface => 'commandline' })->store();
153     $lines = $account->outstanding_credits();
154     is( $lines->count, 0, 'No debits are confused with credits because of the amountoutstanding value' );
155
156     $schema->storage->txn_rollback;
157 };
158
159 subtest 'add_credit() tests' => sub {
160
161     plan tests => 17;
162
163     $schema->storage->txn_begin;
164
165     # delete logs and statistics
166     my $action_logs = $schema->resultset('ActionLog')->search()->count;
167     my $statistics = $schema->resultset('Statistic')->search()->count;
168
169     my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
170     my $account = Koha::Account->new( { patron_id => $patron->borrowernumber } );
171
172     is( $account->balance, 0, 'Patron has no balance' );
173
174     # Disable logs
175     t::lib::Mocks::mock_preference( 'FinesLog', 0 );
176
177     throws_ok {
178         $account->add_credit(
179             {   amount      => 25,
180                 description => 'Payment of 25',
181                 library_id  => $patron->branchcode,
182                 note        => 'not really important',
183                 type        => 'payment',
184                 user_id     => $patron->id
185             }
186         );
187     }
188     'Koha::Exceptions::MissingParameter', 'Exception thrown if interface parameter missing';
189
190     my $line_1 = $account->add_credit(
191         {   amount      => 25,
192             description => 'Payment of 25',
193             library_id  => $patron->branchcode,
194             note        => 'not really important',
195             type        => 'payment',
196             user_id     => $patron->id,
197             interface   => 'commandline'
198         }
199     );
200
201     is( $account->balance, -25, 'Patron has a balance of -25' );
202     is( $schema->resultset('ActionLog')->count(), $action_logs + 0, 'No log was added' );
203     is( $schema->resultset('Statistic')->count(), $statistics + 1, 'Action added to statistics' );
204     is( $line_1->accounttype, $Koha::Account::account_type_credit->{'payment'}, 'Account type is correctly set' );
205
206     # Enable logs
207     t::lib::Mocks::mock_preference( 'FinesLog', 1 );
208
209     my $line_2 = $account->add_credit(
210         {   amount      => 37,
211             description => 'Payment of 37',
212             library_id  => $patron->branchcode,
213             note        => 'not really important',
214             user_id     => $patron->id,
215             interface   => 'commandline'
216         }
217     );
218
219     is( $account->balance, -62, 'Patron has a balance of -25' );
220     is( $schema->resultset('ActionLog')->count(), $action_logs + 1, 'Log was added' );
221     is( $schema->resultset('Statistic')->count(), $statistics + 2, 'Action added to statistics' );
222     is( $line_2->accounttype, $Koha::Account::account_type_credit->{'payment'}, 'Account type is correctly set' );
223
224     # offsets have the credit_id set to accountlines_id, and debit_id is undef
225     my $offset_1 = Koha::Account::Offsets->search({ credit_id => $line_1->id })->next;
226     my $offset_2 = Koha::Account::Offsets->search({ credit_id => $line_2->id })->next;
227
228     is( $offset_1->credit_id, $line_1->id, 'No debit_id is set for credits' );
229     is( $offset_1->debit_id, undef, 'No debit_id is set for credits' );
230     is( $offset_2->credit_id, $line_2->id, 'No debit_id is set for credits' );
231     is( $offset_2->debit_id, undef, 'No debit_id is set for credits' );
232
233     my $line_3 = $account->add_credit(
234         {
235             amount      => 20,
236             description => 'Manual credit applied',
237             library_id  => $patron->branchcode,
238             user_id     => $patron->id,
239             type        => 'forgiven',
240             interface   => 'commandline'
241         }
242     );
243
244     is( $schema->resultset('ActionLog')->count(), $action_logs + 2, 'Log was added' );
245     is( $schema->resultset('Statistic')->count(), $statistics + 2, 'No action added to statistics, because of credit type' );
246
247     # Enable cash registers
248     t::lib::Mocks::mock_preference( 'UseCashRegisters', 1 );
249     throws_ok {
250         $account->add_credit(
251             {
252                 amount       => 20,
253                 description  => 'Cash payment without cash register',
254                 library_id   => $patron->branchcode,
255                 user_id      => $patron->id,
256                 payment_type => 'CASH',
257                 interface    => 'intranet'
258             }
259         );
260     }
261     'Koha::Exceptions::Account::RegisterRequired',
262       'Exception thrown for UseCashRegisters:1 + payment_type:CASH + cash_register:undef';
263
264     # Disable cash registers
265     t::lib::Mocks::mock_preference( 'UseCashRegisters', 1 );
266
267     $schema->storage->txn_rollback;
268 };
269
270 subtest 'add_debit() tests' => sub {
271
272     plan tests => 14;
273
274     $schema->storage->txn_begin;
275
276     # delete logs and statistics
277     my $action_logs = $schema->resultset('ActionLog')->search()->count;
278     my $statistics  = $schema->resultset('Statistic')->search()->count;
279
280     my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
281     my $account =
282       Koha::Account->new( { patron_id => $patron->borrowernumber } );
283
284     is( $account->balance, 0, 'Patron has no balance' );
285
286     throws_ok {
287     $account->add_debit(
288         {
289             amount      => -5,
290             description => 'amount validation failure',
291             library_id  => $patron->branchcode,
292             note        => 'this should fail anyway',
293             type        => 'rent',
294             user_id     => $patron->id,
295             interface   => 'commandline'
296         }
297     ); } 'Koha::Exceptions::Account::AmountNotPositive', 'Expected validation exception thrown (amount)';
298
299     throws_ok {
300     $account->add_debit(
301         {
302             amount      => 5,
303             description => 'type validation failure',
304             library_id  => $patron->branchcode,
305             note        => 'this should fail anyway',
306             type        => 'failure',
307             user_id     => $patron->id,
308             interface   => 'commandline'
309         }
310     ); } 'Koha::Exceptions::Account::UnrecognisedType', 'Expected validation exception thrown (type)';
311
312     throws_ok {
313     $account->add_debit(
314         {
315             amount      => 25,
316             description => 'Rental charge of 25',
317             library_id  => $patron->branchcode,
318             note        => 'not really important',
319             type        => 'rent',
320             user_id     => $patron->id
321         }
322     ); } 'Koha::Exceptions::MissingParameter', 'Exception thrown if interface parameter missing';
323
324     # Disable logs
325     t::lib::Mocks::mock_preference( 'FinesLog', 0 );
326
327     my $line_1 = $account->add_debit(
328         {
329             amount      => 25,
330             description => 'Rental charge of 25',
331             library_id  => $patron->branchcode,
332             note        => 'not really important',
333             type        => 'rent',
334             user_id     => $patron->id,
335             interface   => 'commandline'
336         }
337     );
338
339     is( $account->balance, 25, 'Patron has a balance of 25' );
340     is(
341         $schema->resultset('ActionLog')->count(),
342         $action_logs + 0,
343         'No log was added'
344     );
345     is(
346         $line_1->accounttype,
347         $Koha::Account::account_type_debit->{'rent'},
348         'Account type is correctly set'
349     );
350
351     # Enable logs
352     t::lib::Mocks::mock_preference( 'FinesLog', 1 );
353
354     my $line_2   = $account->add_debit(
355         {
356             amount      => 37,
357             description => 'Rental charge of 37',
358             library_id  => $patron->branchcode,
359             note        => 'not really important',
360             type        => 'rent',
361             user_id     => $patron->id,
362             interface   => 'commandline'
363         }
364     );
365
366     is( $account->balance, 62, 'Patron has a balance of 62' );
367     is(
368         $schema->resultset('ActionLog')->count(),
369         $action_logs + 1,
370         'Log was added'
371     );
372     is(
373         $line_2->accounttype,
374         $Koha::Account::account_type_debit->{'rent'},
375         'Account type is correctly set'
376     );
377
378     # offsets have the debit_id set to accountlines_id, and credit_id is undef
379     my $offset_1 =
380       Koha::Account::Offsets->search( { debit_id => $line_1->id } )->next;
381     my $offset_2 =
382       Koha::Account::Offsets->search( { debit_id => $line_2->id } )->next;
383
384     is( $offset_1->debit_id,  $line_1->id, 'debit_id is set for debit 1' );
385     is( $offset_1->credit_id, undef,       'credit_id is not set for debit 1' );
386     is( $offset_2->debit_id,  $line_2->id, 'debit_id is set for debit 2' );
387     is( $offset_2->credit_id, undef,       'credit_id is not set for debit 2' );
388
389     $schema->storage->txn_rollback;
390 };
391
392 subtest 'lines() tests' => sub {
393
394     plan tests => 1;
395
396     $schema->storage->txn_begin;
397
398     my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
399     my $account = $patron->account;
400
401     # Add Credits
402     $account->add_credit({ amount => 1, interface => 'commandline' });
403     $account->add_credit({ amount => 2, interface => 'commandline' });
404     $account->add_credit({ amount => 3, interface => 'commandline' });
405     $account->add_credit({ amount => 4, interface => 'commandline' });
406
407     # Add Debits
408     $account->add_debit({ amount => 1, interface => 'commandline', type => 'overdue' });
409     $account->add_debit({ amount => 2, interface => 'commandline', type => 'overdue' });
410     $account->add_debit({ amount => 3, interface => 'commandline', type => 'overdue' });
411     $account->add_debit({ amount => 4, interface => 'commandline', type => 'overdue' });
412
413     # Paid Off
414     $account->add_credit( { amount => 1, interface => 'commandline' } )
415         ->apply( { debits => [ $account->outstanding_debits->as_list ] } );
416
417     my $lines = $account->lines;
418     is( $lines->_resultset->count, 9, "All accountlines (debits, credits and paid off) were fetched");
419
420     $schema->storage->txn_rollback;
421 };
422
423 subtest 'reconcile_balance' => sub {
424
425     plan tests => 4;
426
427     subtest 'more credit than debit' => sub {
428
429         plan tests => 6;
430
431         $schema->storage->txn_begin;
432
433         my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
434         my $account = $patron->account;
435
436         # Add Credits
437         $account->add_credit({ amount => 1, interface => 'commandline' });
438         $account->add_credit({ amount => 2, interface => 'commandline' });
439         $account->add_credit({ amount => 3, interface => 'commandline' });
440         $account->add_credit({ amount => 4, interface => 'commandline' });
441         $account->add_credit({ amount => 5, interface => 'commandline' });
442
443         # Add Debits
444         $account->add_debit({ amount => 1, interface => 'commandline', type => 'overdue' });
445         $account->add_debit({ amount => 2, interface => 'commandline', type => 'overdue' });
446         $account->add_debit({ amount => 3, interface => 'commandline', type => 'overdue' });
447         $account->add_debit({ amount => 4, interface => 'commandline', type => 'overdue' });
448
449         # Paid Off
450         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0, interface => 'commandline' })->store;
451         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0, interface => 'commandline' })->store;
452
453         is( $account->balance(), -5, "Account balance is -5" );
454         is( $account->outstanding_debits->total_outstanding, 10, 'Outstanding debits sum 10' );
455         is( $account->outstanding_credits->total_outstanding, -15, 'Outstanding credits sum -15' );
456
457         $account->reconcile_balance();
458
459         is( $account->balance(), -5, "Account balance is -5" );
460         is( $account->outstanding_debits->total_outstanding, 0, 'No outstanding debits' );
461         is( $account->outstanding_credits->total_outstanding, -5, 'Outstanding credits sum -5' );
462
463         $schema->storage->txn_rollback;
464     };
465
466     subtest 'same debit as credit' => sub {
467
468         plan tests => 6;
469
470         $schema->storage->txn_begin;
471
472         my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
473         my $account = $patron->account;
474
475         # Add Credits
476         $account->add_credit({ amount => 1, interface => 'commandline' });
477         $account->add_credit({ amount => 2, interface => 'commandline' });
478         $account->add_credit({ amount => 3, interface => 'commandline' });
479         $account->add_credit({ amount => 4, interface => 'commandline' });
480
481         # Add Debits
482         $account->add_debit({ amount => 1, interface => 'commandline', type => 'overdue' });
483         $account->add_debit({ amount => 2, interface => 'commandline', type => 'overdue' });
484         $account->add_debit({ amount => 3, interface => 'commandline', type => 'overdue' });
485         $account->add_debit({ amount => 4, interface => 'commandline', type => 'overdue' });
486
487         # Paid Off
488         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0, interface => 'commandline' })->store;
489         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0, interface => 'commandline' })->store;
490
491         is( $account->balance(), 0, "Account balance is 0" );
492         is( $account->outstanding_debits->total_outstanding, 10, 'Outstanding debits sum 10' );
493         is( $account->outstanding_credits->total_outstanding, -10, 'Outstanding credits sum -10' );
494
495         $account->reconcile_balance();
496
497         is( $account->balance(), 0, "Account balance is 0" );
498         is( $account->outstanding_debits->total_outstanding, 0, 'No outstanding debits' );
499         is( $account->outstanding_credits->total_outstanding, 0, 'Outstanding credits sum 0' );
500
501         $schema->storage->txn_rollback;
502     };
503
504     subtest 'more debit than credit' => sub {
505
506         plan tests => 6;
507
508         $schema->storage->txn_begin;
509
510         my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
511         my $account = $patron->account;
512
513         # Add Credits
514         $account->add_credit({ amount => 1, interface => 'commandline' });
515         $account->add_credit({ amount => 2, interface => 'commandline' });
516         $account->add_credit({ amount => 3, interface => 'commandline' });
517         $account->add_credit({ amount => 4, interface => 'commandline' });
518
519         # Add Debits
520         $account->add_debit({ amount => 1, interface => 'commandline', type => 'overdue' });
521         $account->add_debit({ amount => 2, interface => 'commandline', type => 'overdue' });
522         $account->add_debit({ amount => 3, interface => 'commandline', type => 'overdue' });
523         $account->add_debit({ amount => 4, interface => 'commandline', type => 'overdue' });
524         $account->add_debit({ amount => 5, interface => 'commandline', type => 'overdue' });
525
526         # Paid Off
527         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0, interface => 'commandline' })->store;
528         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0, interface => 'commandline' })->store;
529
530         is( $account->balance(), 5, "Account balance is 5" );
531         is( $account->outstanding_debits->total_outstanding, 15, 'Outstanding debits sum 15' );
532         is( $account->outstanding_credits->total_outstanding, -10, 'Outstanding credits sum -10' );
533
534         $account->reconcile_balance();
535
536         is( $account->balance(), 5, "Account balance is 5" );
537         is( $account->outstanding_debits->total_outstanding, 5, 'Outstanding debits sum 5' );
538         is( $account->outstanding_credits->total_outstanding, 0, 'Outstanding credits sum 0' );
539
540         $schema->storage->txn_rollback;
541     };
542
543     subtest 'credits are applied to older debits first' => sub {
544
545         plan tests => 9;
546
547         $schema->storage->txn_begin;
548
549         my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
550         my $account = $patron->account;
551
552         # Add Credits
553         $account->add_credit({ amount => 1, interface => 'commandline' });
554         $account->add_credit({ amount => 3, interface => 'commandline' });
555
556         # Add Debits
557         my $debit_1 = $account->add_debit({ amount => 1, interface => 'commandline', type => 'overdue' });
558         my $debit_2 = $account->add_debit({ amount => 2, interface => 'commandline', type => 'overdue' });
559         my $debit_3 = $account->add_debit({ amount => 3, interface => 'commandline', type => 'overdue' });
560
561         is( $account->balance(), 2, "Account balance is 2" );
562         is( $account->outstanding_debits->total_outstanding, 6, 'Outstanding debits sum 6' );
563         is( $account->outstanding_credits->total_outstanding, -4, 'Outstanding credits sum -4' );
564
565         $account->reconcile_balance();
566
567         is( $account->balance(), 2, "Account balance is 2" );
568         is( $account->outstanding_debits->total_outstanding, 2, 'Outstanding debits sum 2' );
569         is( $account->outstanding_credits->total_outstanding, 0, 'Outstanding credits sum 0' );
570
571         $debit_1->discard_changes;
572         is( $debit_1->amountoutstanding + 0, 0, 'Old debit payed' );
573         $debit_2->discard_changes;
574         is( $debit_2->amountoutstanding + 0, 0, 'Old debit payed' );
575         $debit_3->discard_changes;
576         is( $debit_3->amountoutstanding + 0, 2, 'Newest debit only partially payed' );
577
578         $schema->storage->txn_rollback;
579     };
580 };
581
582 subtest 'pay() tests' => sub {
583
584     plan tests => 3;
585
586     $schema->storage->txn_begin;
587
588     my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
589     my $library = $builder->build_object({ class => 'Koha::Libraries' });
590     my $account = $patron->account;
591
592     my $context = Test::MockModule->new('C4::Context');
593     $context->mock( 'userenv', { branch => $library->id } );
594
595     my $credit_1_id = $account->pay({ amount => 200 });
596     my $credit_1    = Koha::Account::Lines->find( $credit_1_id );
597
598     is( $credit_1->branchcode, undef, 'No branchcode is set if library_id was not passed' );
599
600     my $credit_2_id = $account->pay({ amount => 150, library_id => $library->id });
601     my $credit_2    = Koha::Account::Lines->find( $credit_2_id );
602
603     is( $credit_2->branchcode, $library->id, 'branchcode set because library_id was passed' );
604
605     # Enable cash registers
606     t::lib::Mocks::mock_preference( 'UseCashRegisters', 1 );
607     throws_ok {
608         $account->pay(
609             {
610                 amount       => 20,
611                 payment_type => 'CASH',
612                 interface    => 'intranet'
613             }
614         );
615     }
616     'Koha::Exceptions::Account::RegisterRequired',
617       'Exception thrown for UseCashRegisters:1 + payment_type:CASH + cash_register:undef';
618
619     # Disable cash registers
620     t::lib::Mocks::mock_preference( 'UseCashRegisters', 1 );
621
622     $schema->storage->txn_rollback;
623 };
624
625 subtest 'pay() handles lost items when paying a specific lost fee' => sub {
626
627     plan tests => 4;
628
629     $schema->storage->txn_begin;
630
631     my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
632     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
633     my $account = $patron->account;
634
635     my $context = Test::MockModule->new('C4::Context');
636     $context->mock( 'userenv', { branch => $library->id } );
637
638     my $biblio = $builder->build_sample_biblio();
639     my $item =
640       $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
641
642     my $checkout = Koha::Checkout->new(
643         {
644             borrowernumber => $patron->id,
645             itemnumber     => $item->id,
646             date_due       => \'NOW()',
647             branchcode     => $patron->branchcode,
648             issuedate      => \'NOW()',
649         }
650     )->store();
651
652     $item->itemlost('1')->store();
653
654     my $accountline = Koha::Account::Line->new(
655         {
656             issue_id       => $checkout->id,
657             borrowernumber => $patron->id,
658             itemnumber     => $item->id,
659             date           => \'NOW()',
660             accounttype    => 'LOST',
661             interface      => 'cli',
662             amount => '1',
663             amountoutstanding => '1',
664         }
665     )->store();
666
667     $account->pay(
668         {
669             amount     => "0.500000",
670             library_id => $library->id,
671             lines      => [$accountline],
672         }
673     );
674
675     $accountline = Koha::Account::Lines->find( $accountline->id );
676     is( $accountline->amountoutstanding, '0.500000', 'Account line was paid down by half' );
677
678     $checkout = Koha::Checkouts->find( $checkout->id );
679     ok( $checkout, 'Item still checked out to patron' );
680
681     $account->pay(
682         {
683             amount     => "0.500000",
684             library_id => $library->id,
685             lines      => [$accountline],
686         }
687     );
688
689     $accountline = Koha::Account::Lines->find( $accountline->id );
690     is( $accountline->amountoutstanding, '0.000000', 'Account line was paid down by half' );
691
692     $checkout = Koha::Checkouts->find( $checkout->id );
693     ok( !$checkout, 'Item was removed from patron account' );
694
695     $schema->storage->txn_rollback;
696 };
697
698 subtest 'pay() handles lost items when paying by amount ( not specifying the lost fee )' => sub {
699
700     plan tests => 4;
701
702     $schema->storage->txn_begin;
703
704     my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
705     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
706     my $account = $patron->account;
707
708     my $context = Test::MockModule->new('C4::Context');
709     $context->mock( 'userenv', { branch => $library->id } );
710
711     my $biblio = $builder->build_sample_biblio();
712     my $item =
713       $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
714
715     my $checkout = Koha::Checkout->new(
716         {
717             borrowernumber => $patron->id,
718             itemnumber     => $item->id,
719             date_due       => \'NOW()',
720             branchcode     => $patron->branchcode,
721             issuedate      => \'NOW()',
722         }
723     )->store();
724
725     $item->itemlost('1')->store();
726
727     my $accountline = Koha::Account::Line->new(
728         {
729             issue_id       => $checkout->id,
730             borrowernumber => $patron->id,
731             itemnumber     => $item->id,
732             date           => \'NOW()',
733             accounttype    => 'LOST',
734             interface      => 'cli',
735             amount => '1',
736             amountoutstanding => '1',
737         }
738     )->store();
739
740     $account->pay(
741         {
742             amount     => "0.500000",
743             library_id => $library->id,
744         }
745     );
746
747     $accountline = Koha::Account::Lines->find( $accountline->id );
748     is( $accountline->amountoutstanding, '0.500000', 'Account line was paid down by half' );
749
750     $checkout = Koha::Checkouts->find( $checkout->id );
751     ok( $checkout, 'Item still checked out to patron' );
752
753     $account->pay(
754         {
755             amount     => "0.500000",
756             library_id => $library->id,
757         }
758     );
759
760     $accountline = Koha::Account::Lines->find( $accountline->id );
761     is( $accountline->amountoutstanding, '0.000000', 'Account line was paid down by half' );
762
763     $checkout = Koha::Checkouts->find( $checkout->id );
764     ok( !$checkout, 'Item was removed from patron account' );
765
766     $schema->storage->txn_rollback;
767 };
768
769 subtest 'Koha::Account::Line::apply() handles lost items' => sub {
770
771     plan tests => 4;
772
773     $schema->storage->txn_begin;
774
775     my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
776     my $library = $builder->build_object( { class => 'Koha::Libraries' } );
777     my $account = $patron->account;
778
779     my $context = Test::MockModule->new('C4::Context');
780     $context->mock( 'userenv', { branch => $library->id } );
781
782     my $biblio = $builder->build_sample_biblio();
783     my $item =
784       $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
785
786     my $checkout = Koha::Checkout->new(
787         {
788             borrowernumber => $patron->id,
789             itemnumber     => $item->id,
790             date_due       => \'NOW()',
791             branchcode     => $patron->branchcode,
792             issuedate      => \'NOW()',
793         }
794     )->store();
795
796     $item->itemlost('1')->store();
797
798     my $debit = Koha::Account::Line->new(
799         {
800             issue_id          => $checkout->id,
801             borrowernumber    => $patron->id,
802             itemnumber        => $item->id,
803             date              => \'NOW()',
804             accounttype       => 'LOST',
805             interface         => 'cli',
806             amount            => '1',
807             amountoutstanding => '1',
808         }
809     )->store();
810
811     my $credit = Koha::Account::Line->new(
812         {
813             borrowernumber    => $patron->id,
814             date              => '1900-01-01',
815             amount            => "-0.500000",
816             amountoutstanding => "-0.500000",
817             interface         => 'commandline'
818         }
819     )->store();
820     my $debits = $account->outstanding_debits;
821     $credit->apply({ debits => [ $debits->as_list ] });
822
823     $debit = Koha::Account::Lines->find( $debit->id );
824     is( $debit->amountoutstanding, '0.500000', 'Account line was paid down by half' );
825
826     $checkout = Koha::Checkouts->find( $checkout->id );
827     ok( $checkout, 'Item still checked out to patron' );
828
829     $credit = Koha::Account::Line->new(
830         {
831             borrowernumber    => $patron->id,
832             date              => '1900-01-01',
833             amount            => "-0.500000",
834             amountoutstanding => "-0.500000",
835             interface         => 'commandline'
836         }
837     )->store();
838     $debits = $account->outstanding_debits;
839     $credit->apply({ debits => [ $debits->as_list ] });
840
841     $debit = Koha::Account::Lines->find( $debit->id );
842     is( $debit->amountoutstanding, '0.000000', 'Account line was paid down by half' );
843
844     $checkout = Koha::Checkouts->find( $checkout->id );
845     ok( !$checkout, 'Item was removed from patron account' );
846
847     $schema->storage->txn_rollback;
848 };