Bug 21692: created a test for Koha::Account->new
[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 => 5;
23
24 use Koha::Account;
25 use Koha::Account::Lines;
26 use Koha::Account::Offsets;
27
28
29 use t::lib::Mocks;
30 use t::lib::TestBuilder;
31
32 my $schema  = Koha::Database->new->schema;
33 my $builder = t::lib::TestBuilder->new;
34
35 subtest 'outstanding_debits() tests' => sub {
36
37     plan tests => 22;
38
39     $schema->storage->txn_begin;
40
41     my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
42     my $account = $patron->account;
43
44     my @generated_lines;
45     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 1 })->store;
46     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 2, amountoutstanding => 2 })->store;
47     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 3, amountoutstanding => 3 })->store;
48     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 4, amountoutstanding => 4 })->store;
49
50     my $lines     = $account->outstanding_debits();
51     my @lines_arr = $account->outstanding_debits();
52
53     is( ref($lines), 'Koha::Account::Lines', 'Called in scalar context, outstanding_debits returns a Koha::Account::Lines object' );
54     is( $lines->total_outstanding, 10, 'Outstandig debits total is correctly calculated' );
55
56     my $i = 0;
57     foreach my $line ( @{ $lines->as_list } ) {
58         my $fetched_line = Koha::Account::Lines->find( $generated_lines[$i]->id );
59         is_deeply( $line->unblessed, $fetched_line->unblessed, "Fetched line matches the generated one ($i)" );
60         is_deeply( $lines_arr[$i]->unblessed, $fetched_line->unblessed, "Fetched line matches the generated one ($i)" );
61         is( ref($lines_arr[$i]), 'Koha::Account::Line', 'outstanding_debits returns a list of Koha::Account::Line objects in list context' );
62         $i++;
63     }
64     my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
65     Koha::Account::Line->new({ borrowernumber => $patron_2->id, amountoutstanding => -2 })->store;
66     my $just_one = Koha::Account::Line->new({ borrowernumber => $patron_2->id, amount => 3, amountoutstanding =>  3 })->store;
67     Koha::Account::Line->new({ borrowernumber => $patron_2->id, amount => -6, amountoutstanding => -6 })->store;
68     $lines = $patron_2->account->outstanding_debits();
69     is( $lines->total_outstanding, 3, "Total if some outstanding debits and some credits is only debits" );
70     is( $lines->count, 1, "With 1 outstanding debits, we get back a Lines object with 1 lines" );
71     my $the_line = Koha::Account::Lines->find( $just_one->id );
72     is_deeply( $the_line->unblessed, $lines->next->unblessed, "We get back the one correct line");
73
74     my $patron_3 = $builder->build_object({ class => 'Koha::Patrons' });
75     Koha::Account::Line->new({ borrowernumber => $patron_2->id, amount => -2,   amountoutstanding => -2 })->store;
76     Koha::Account::Line->new({ borrowernumber => $patron_2->id, amount => -20,  amountoutstanding => -20 })->store;
77     Koha::Account::Line->new({ borrowernumber => $patron_2->id, amount => -200, amountoutstanding => -200 })->store;
78     $lines = $patron_3->account->outstanding_debits();
79     is( $lines->total_outstanding, 0, "Total if no outstanding debits total is 0" );
80     is( $lines->count, 0, "With 0 outstanding debits, we get back a Lines object with 0 lines" );
81
82     my $patron_4  = $builder->build_object({ class => 'Koha::Patrons' });
83     my $account_4 = $patron_4->account;
84     $lines = $account_4->outstanding_debits();
85     is( $lines->total_outstanding, 0, "Total if no outstanding debits is 0" );
86     is( $lines->count, 0, "With no outstanding debits, we get back a Lines object with 0 lines" );
87
88     # create a pathological credit with amountoutstanding > 0 (BZ 14591)
89     Koha::Account::Line->new({ borrowernumber => $patron_4->id, amount => -3, amountoutstanding => 3 })->store();
90     $lines = $account_4->outstanding_debits();
91     is( $lines->count, 0, 'No credits are confused with debits because of the amountoutstanding value' );
92
93     $schema->storage->txn_rollback;
94 };
95
96 subtest 'outstanding_credits() tests' => sub {
97
98     plan tests => 17;
99
100     $schema->storage->txn_begin;
101
102     my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
103     my $account = $patron->account;
104
105     my @generated_lines;
106     push @generated_lines, $account->add_credit({ amount => 1 });
107     push @generated_lines, $account->add_credit({ amount => 2 });
108     push @generated_lines, $account->add_credit({ amount => 3 });
109     push @generated_lines, $account->add_credit({ amount => 4 });
110
111     my $lines     = $account->outstanding_credits();
112     my @lines_arr = $account->outstanding_credits();
113
114     is( ref($lines), 'Koha::Account::Lines', 'Called in scalar context, outstanding_credits returns a Koha::Account::Lines object' );
115     is( $lines->total_outstanding, -10, 'Outstandig credits total is correctly calculated' );
116
117     my $i = 0;
118     foreach my $line ( @{ $lines->as_list } ) {
119         my $fetched_line = Koha::Account::Lines->find( $generated_lines[$i]->id );
120         is_deeply( $line->unblessed, $fetched_line->unblessed, "Fetched line matches the generated one ($i)" );
121         is_deeply( $lines_arr[$i]->unblessed, $fetched_line->unblessed, "Fetched line matches the generated one ($i)" );
122         is( ref($lines_arr[$i]), 'Koha::Account::Line', 'outstanding_debits returns a list of Koha::Account::Line objects in list context' );
123         $i++;
124     }
125
126     my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
127     $account  = $patron_2->account;
128     $lines       = $account->outstanding_credits();
129     is( $lines->total_outstanding, 0, "Total if no outstanding credits is 0" );
130     is( $lines->count, 0, "With no outstanding credits, we get back a Lines object with 0 lines" );
131
132     # create a pathological debit with amountoutstanding < 0 (BZ 14591)
133     Koha::Account::Line->new({ borrowernumber => $patron_2->id, amount => 2, amountoutstanding => -3 })->store();
134     $lines = $account->outstanding_credits();
135     is( $lines->count, 0, 'No debits are confused with credits because of the amountoutstanding value' );
136
137     $schema->storage->txn_rollback;
138 };
139
140 subtest 'add_credit() tests' => sub {
141
142     plan tests => 16;
143
144     $schema->storage->txn_begin;
145
146     # delete logs and statistics
147     my $action_logs = $schema->resultset('ActionLog')->search()->count;
148     my $statistics = $schema->resultset('Statistic')->search()->count;
149
150     my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
151     my $account = Koha::Account->new( { patron_id => $patron->borrowernumber } );
152     is( defined $account, 1, "Account is defined" );
153
154     is( $account->balance, 0, 'Patron has no balance' );
155
156     # Disable logs
157     t::lib::Mocks::mock_preference( 'FinesLog', 0 );
158
159     my $line_1 = $account->add_credit(
160         {   amount      => 25,
161             description => 'Payment of 25',
162             library_id  => $patron->branchcode,
163             note        => 'not really important',
164             type        => 'payment',
165             user_id     => $patron->id
166         }
167     );
168
169     is( $account->balance, -25, 'Patron has a balance of -25' );
170     is( $schema->resultset('ActionLog')->count(), $action_logs + 0, 'No log was added' );
171     is( $schema->resultset('Statistic')->count(), $statistics + 1, 'Action added to statistics' );
172     is( $line_1->accounttype, $Koha::Account::account_type->{'payment'}, 'Account type is correctly set' );
173
174     # Enable logs
175     t::lib::Mocks::mock_preference( 'FinesLog', 1 );
176
177     my $sip_code = "1";
178     my $line_2 = $account->add_credit(
179         {   amount      => 37,
180             description => 'Payment of 37',
181             library_id  => $patron->branchcode,
182             note        => 'not really important',
183             user_id     => $patron->id,
184             sip         => $sip_code
185         }
186     );
187
188     is( $account->balance, -62, 'Patron has a balance of -25' );
189     is( $schema->resultset('ActionLog')->count(), $action_logs + 1, 'Log was added' );
190     is( $schema->resultset('Statistic')->count(), $statistics + 2, 'Action added to statistics' );
191     is( $line_2->accounttype, $Koha::Account::account_type->{'payment'} . $sip_code, 'Account type is correctly set' );
192
193     # offsets have the credit_id set to accountlines_id, and debit_id is undef
194     my $offset_1 = Koha::Account::Offsets->search({ credit_id => $line_1->id })->next;
195     my $offset_2 = Koha::Account::Offsets->search({ credit_id => $line_2->id })->next;
196
197     is( $offset_1->credit_id, $line_1->id, 'No debit_id is set for credits' );
198     is( $offset_1->debit_id, undef, 'No debit_id is set for credits' );
199     is( $offset_2->credit_id, $line_2->id, 'No debit_id is set for credits' );
200     is( $offset_2->debit_id, undef, 'No debit_id is set for credits' );
201
202     my $line_3 = $account->add_credit(
203         {   amount      => 20,
204             description => 'Manual credit applied',
205             library_id  => $patron->branchcode,
206             user_id     => $patron->id,
207             type        => 'forgiven'
208         }
209     );
210
211     is( $schema->resultset('ActionLog')->count(), $action_logs + 2, 'Log was added' );
212     is( $schema->resultset('Statistic')->count(), $statistics + 2, 'No action added to statistics, because of credit type' );
213
214     $schema->storage->txn_rollback;
215 };
216
217 subtest 'lines() tests' => sub {
218
219     plan tests => 1;
220
221     $schema->storage->txn_begin;
222
223     my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
224     my $account = $patron->account;
225
226     my @generated_lines;
227
228     # Add Credits
229     push @generated_lines, $account->add_credit({ amount => 1 });
230     push @generated_lines, $account->add_credit({ amount => 2 });
231     push @generated_lines, $account->add_credit({ amount => 3 });
232     push @generated_lines, $account->add_credit({ amount => 4 });
233
234     # Add Debits
235     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amountoutstanding => 1 })->store;
236     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amountoutstanding => 2 })->store;
237     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amountoutstanding => 3 })->store;
238     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amountoutstanding => 4 })->store;
239
240     # Paid Off
241     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amountoutstanding => 0 })->store;
242     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amountoutstanding => 0 })->store;
243
244     my $lines = $account->lines;
245     is( $lines->_resultset->count, 10, "All accountlines (debits, credits and paid off) were fetched");
246
247     $schema->storage->txn_rollback;
248 };
249
250 subtest 'reconcile_balance' => sub {
251
252     plan tests => 4;
253
254     subtest 'more credit than debit' => sub {
255
256         plan tests => 6;
257
258         $schema->storage->txn_begin;
259
260         my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
261         my $account = $patron->account;
262
263         # Add Credits
264         $account->add_credit({ amount => 1 });
265         $account->add_credit({ amount => 2 });
266         $account->add_credit({ amount => 3 });
267         $account->add_credit({ amount => 4 });
268         $account->add_credit({ amount => 5 });
269
270         # Add Debits TODO: replace for calls to add_debit when time comes
271         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 1 })->store;
272         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 2, amountoutstanding => 2 })->store;
273         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 3, amountoutstanding => 3 })->store;
274         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 4, amountoutstanding => 4 })->store;
275
276         # Paid Off
277         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0 })->store;
278         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0 })->store;
279
280         is( $account->balance(), -5, "Account balance is -5" );
281         is( $account->outstanding_debits->total_outstanding, 10, 'Outstanding debits sum 10' );
282         is( $account->outstanding_credits->total_outstanding, -15, 'Outstanding credits sum -15' );
283
284         $account->reconcile_balance();
285
286         is( $account->balance(), -5, "Account balance is -5" );
287         is( $account->outstanding_debits->total_outstanding, 0, 'No outstanding debits' );
288         is( $account->outstanding_credits->total_outstanding, -5, 'Outstanding credits sum -5' );
289
290         $schema->storage->txn_rollback;
291     };
292
293     subtest 'same debit as credit' => sub {
294
295         plan tests => 6;
296
297         $schema->storage->txn_begin;
298
299         my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
300         my $account = $patron->account;
301
302         # Add Credits
303         $account->add_credit({ amount => 1 });
304         $account->add_credit({ amount => 2 });
305         $account->add_credit({ amount => 3 });
306         $account->add_credit({ amount => 4 });
307
308         # Add Debits TODO: replace for calls to add_debit when time comes
309         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 1 })->store;
310         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 2, amountoutstanding => 2 })->store;
311         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 3, amountoutstanding => 3 })->store;
312         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 4, amountoutstanding => 4 })->store;
313
314         # Paid Off
315         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0 })->store;
316         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0 })->store;
317
318         is( $account->balance(), 0, "Account balance is 0" );
319         is( $account->outstanding_debits->total_outstanding, 10, 'Outstanding debits sum 10' );
320         is( $account->outstanding_credits->total_outstanding, -10, 'Outstanding credits sum -10' );
321
322         $account->reconcile_balance();
323
324         is( $account->balance(), 0, "Account balance is 0" );
325         is( $account->outstanding_debits->total_outstanding, 0, 'No outstanding debits' );
326         is( $account->outstanding_credits->total_outstanding, 0, 'Outstanding credits sum 0' );
327
328         $schema->storage->txn_rollback;
329     };
330
331     subtest 'more debit than credit' => sub {
332
333         plan tests => 6;
334
335         $schema->storage->txn_begin;
336
337         my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
338         my $account = $patron->account;
339
340         # Add Credits
341         $account->add_credit({ amount => 1 });
342         $account->add_credit({ amount => 2 });
343         $account->add_credit({ amount => 3 });
344         $account->add_credit({ amount => 4 });
345
346         # Add Debits TODO: replace for calls to add_debit when time comes
347         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 1 })->store;
348         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 2, amountoutstanding => 2 })->store;
349         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 3, amountoutstanding => 3 })->store;
350         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 4, amountoutstanding => 4 })->store;
351         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 5, amountoutstanding => 5 })->store;
352
353         # Paid Off
354         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0 })->store;
355         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0 })->store;
356
357         is( $account->balance(), 5, "Account balance is 5" );
358         is( $account->outstanding_debits->total_outstanding, 15, 'Outstanding debits sum 15' );
359         is( $account->outstanding_credits->total_outstanding, -10, 'Outstanding credits sum -10' );
360
361         $account->reconcile_balance();
362
363         is( $account->balance(), 5, "Account balance is 5" );
364         is( $account->outstanding_debits->total_outstanding, 5, 'Outstanding debits sum 5' );
365         is( $account->outstanding_credits->total_outstanding, 0, 'Outstanding credits sum 0' );
366
367         $schema->storage->txn_rollback;
368     };
369
370     subtest 'credits are applied to older debits first' => sub {
371
372         plan tests => 9;
373
374         $schema->storage->txn_begin;
375
376         my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
377         my $account = $patron->account;
378
379         # Add Credits
380         $account->add_credit({ amount => 1 });
381         $account->add_credit({ amount => 3 });
382
383         # Add Debits TODO: replace for calls to add_debit when time comes
384         my $debit_1 = Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 1 })->store;
385         my $debit_2 = Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 2, amountoutstanding => 2 })->store;
386         my $debit_3 = Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 3, amountoutstanding => 3 })->store;
387
388         is( $account->balance(), 2, "Account balance is 2" );
389         is( $account->outstanding_debits->total_outstanding, 6, 'Outstanding debits sum 6' );
390         is( $account->outstanding_credits->total_outstanding, -4, 'Outstanding credits sum -4' );
391
392         $account->reconcile_balance();
393
394         is( $account->balance(), 2, "Account balance is 2" );
395         is( $account->outstanding_debits->total_outstanding, 2, 'Outstanding debits sum 2' );
396         is( $account->outstanding_credits->total_outstanding, 0, 'Outstanding credits sum 0' );
397
398         $debit_1->discard_changes;
399         is( $debit_1->amountoutstanding + 0, 0, 'Old debit payed' );
400         $debit_2->discard_changes;
401         is( $debit_2->amountoutstanding + 0, 0, 'Old debit payed' );
402         $debit_3->discard_changes;
403         is( $debit_3->amountoutstanding + 0, 2, 'Newest debit only partially payed' );
404
405         $schema->storage->txn_rollback;
406     };
407 };