Bug 21912: Add tests for Koha::Objects->search
[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 use t::lib::Mocks;
29 use t::lib::TestBuilder;
30
31 my $schema  = Koha::Database->new->schema;
32 my $builder = t::lib::TestBuilder->new;
33
34 subtest 'outstanding_debits() tests' => sub {
35
36     plan tests => 22;
37
38     $schema->storage->txn_begin;
39
40     my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
41     my $account = $patron->account;
42
43     my @generated_lines;
44     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 1 })->store;
45     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 2, amountoutstanding => 2 })->store;
46     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 3, amountoutstanding => 3 })->store;
47     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 4, amountoutstanding => 4 })->store;
48
49     my $lines     = $account->outstanding_debits();
50     my @lines_arr = $account->outstanding_debits();
51
52     is( ref($lines), 'Koha::Account::Lines', 'Called in scalar context, outstanding_debits returns a Koha::Account::Lines object' );
53     is( $lines->total_outstanding, 10, 'Outstandig debits total is correctly calculated' );
54
55     my $i = 0;
56     foreach my $line ( @{ $lines->as_list } ) {
57         my $fetched_line = Koha::Account::Lines->find( $generated_lines[$i]->id );
58         is_deeply( $line->unblessed, $fetched_line->unblessed, "Fetched line matches the generated one ($i)" );
59         is_deeply( $lines_arr[$i]->unblessed, $fetched_line->unblessed, "Fetched line matches the generated one ($i)" );
60         is( ref($lines_arr[$i]), 'Koha::Account::Line', 'outstanding_debits returns a list of Koha::Account::Line objects in list context' );
61         $i++;
62     }
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 => 15;
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
153     is( $account->balance, 0, 'Patron has no balance' );
154
155     # Disable logs
156     t::lib::Mocks::mock_preference( 'FinesLog', 0 );
157
158     my $line_1 = $account->add_credit(
159         {   amount      => 25,
160             description => 'Payment of 25',
161             library_id  => $patron->branchcode,
162             note        => 'not really important',
163             type        => 'payment',
164             user_id     => $patron->id
165         }
166     );
167
168     is( $account->balance, -25, 'Patron has a balance of -25' );
169     is( $schema->resultset('ActionLog')->count(), $action_logs + 0, 'No log was added' );
170     is( $schema->resultset('Statistic')->count(), $statistics + 1, 'Action added to statistics' );
171     is( $line_1->accounttype, $Koha::Account::account_type->{'payment'}, 'Account type is correctly set' );
172
173     # Enable logs
174     t::lib::Mocks::mock_preference( 'FinesLog', 1 );
175
176     my $sip_code = "1";
177     my $line_2 = $account->add_credit(
178         {   amount      => 37,
179             description => 'Payment of 37',
180             library_id  => $patron->branchcode,
181             note        => 'not really important',
182             user_id     => $patron->id,
183             sip         => $sip_code
184         }
185     );
186
187     is( $account->balance, -62, 'Patron has a balance of -25' );
188     is( $schema->resultset('ActionLog')->count(), $action_logs + 1, 'Log was added' );
189     is( $schema->resultset('Statistic')->count(), $statistics + 2, 'Action added to statistics' );
190     is( $line_2->accounttype, $Koha::Account::account_type->{'payment'} . $sip_code, 'Account type is correctly set' );
191
192     # offsets have the credit_id set to accountlines_id, and debit_id is undef
193     my $offset_1 = Koha::Account::Offsets->search({ credit_id => $line_1->id })->next;
194     my $offset_2 = Koha::Account::Offsets->search({ credit_id => $line_2->id })->next;
195
196     is( $offset_1->credit_id, $line_1->id, 'No debit_id is set for credits' );
197     is( $offset_1->debit_id, undef, 'No debit_id is set for credits' );
198     is( $offset_2->credit_id, $line_2->id, 'No debit_id is set for credits' );
199     is( $offset_2->debit_id, undef, 'No debit_id is set for credits' );
200
201     my $line_3 = $account->add_credit(
202         {   amount      => 20,
203             description => 'Manual credit applied',
204             library_id  => $patron->branchcode,
205             user_id     => $patron->id,
206             type        => 'forgiven'
207         }
208     );
209
210     is( $schema->resultset('ActionLog')->count(), $action_logs + 2, 'Log was added' );
211     is( $schema->resultset('Statistic')->count(), $statistics + 2, 'No action added to statistics, because of credit type' );
212
213     $schema->storage->txn_rollback;
214 };
215
216 subtest 'lines() tests' => sub {
217
218     plan tests => 1;
219
220     $schema->storage->txn_begin;
221
222     my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
223     my $account = $patron->account;
224
225     my @generated_lines;
226
227     # Add Credits
228     push @generated_lines, $account->add_credit({ amount => 1 });
229     push @generated_lines, $account->add_credit({ amount => 2 });
230     push @generated_lines, $account->add_credit({ amount => 3 });
231     push @generated_lines, $account->add_credit({ amount => 4 });
232
233     # Add Debits
234     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amountoutstanding => 1 })->store;
235     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amountoutstanding => 2 })->store;
236     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amountoutstanding => 3 })->store;
237     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amountoutstanding => 4 })->store;
238
239     # Paid Off
240     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amountoutstanding => 0 })->store;
241     push @generated_lines, Koha::Account::Line->new({ borrowernumber => $patron->id, amountoutstanding => 0 })->store;
242
243     my $lines = $account->lines;
244     is( $lines->_resultset->count, 10, "All accountlines (debits, credits and paid off) were fetched");
245
246     $schema->storage->txn_rollback;
247 };
248
249 subtest 'reconcile_balance' => sub {
250
251     plan tests => 4;
252
253     subtest 'more credit than debit' => sub {
254
255         plan tests => 6;
256
257         $schema->storage->txn_begin;
258
259         my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
260         my $account = $patron->account;
261
262         # Add Credits
263         $account->add_credit({ amount => 1 });
264         $account->add_credit({ amount => 2 });
265         $account->add_credit({ amount => 3 });
266         $account->add_credit({ amount => 4 });
267         $account->add_credit({ amount => 5 });
268
269         # Add Debits TODO: replace for calls to add_debit when time comes
270         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 1 })->store;
271         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 2, amountoutstanding => 2 })->store;
272         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 3, amountoutstanding => 3 })->store;
273         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 4, amountoutstanding => 4 })->store;
274
275         # Paid Off
276         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0 })->store;
277         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0 })->store;
278
279         is( $account->balance(), -5, "Account balance is -5" );
280         is( $account->outstanding_debits->total_outstanding, 10, 'Outstanding debits sum 10' );
281         is( $account->outstanding_credits->total_outstanding, -15, 'Outstanding credits sum -15' );
282
283         $account->reconcile_balance();
284
285         is( $account->balance(), -5, "Account balance is -5" );
286         is( $account->outstanding_debits->total_outstanding, 0, 'No outstanding debits' );
287         is( $account->outstanding_credits->total_outstanding, -5, 'Outstanding credits sum -5' );
288
289         $schema->storage->txn_rollback;
290     };
291
292     subtest 'same debit as credit' => sub {
293
294         plan tests => 6;
295
296         $schema->storage->txn_begin;
297
298         my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
299         my $account = $patron->account;
300
301         # Add Credits
302         $account->add_credit({ amount => 1 });
303         $account->add_credit({ amount => 2 });
304         $account->add_credit({ amount => 3 });
305         $account->add_credit({ amount => 4 });
306
307         # Add Debits TODO: replace for calls to add_debit when time comes
308         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 1 })->store;
309         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 2, amountoutstanding => 2 })->store;
310         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 3, amountoutstanding => 3 })->store;
311         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 4, amountoutstanding => 4 })->store;
312
313         # Paid Off
314         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0 })->store;
315         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0 })->store;
316
317         is( $account->balance(), 0, "Account balance is 0" );
318         is( $account->outstanding_debits->total_outstanding, 10, 'Outstanding debits sum 10' );
319         is( $account->outstanding_credits->total_outstanding, -10, 'Outstanding credits sum -10' );
320
321         $account->reconcile_balance();
322
323         is( $account->balance(), 0, "Account balance is 0" );
324         is( $account->outstanding_debits->total_outstanding, 0, 'No outstanding debits' );
325         is( $account->outstanding_credits->total_outstanding, 0, 'Outstanding credits sum 0' );
326
327         $schema->storage->txn_rollback;
328     };
329
330     subtest 'more debit than credit' => sub {
331
332         plan tests => 6;
333
334         $schema->storage->txn_begin;
335
336         my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
337         my $account = $patron->account;
338
339         # Add Credits
340         $account->add_credit({ amount => 1 });
341         $account->add_credit({ amount => 2 });
342         $account->add_credit({ amount => 3 });
343         $account->add_credit({ amount => 4 });
344
345         # Add Debits TODO: replace for calls to add_debit when time comes
346         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 1 })->store;
347         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 2, amountoutstanding => 2 })->store;
348         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 3, amountoutstanding => 3 })->store;
349         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 4, amountoutstanding => 4 })->store;
350         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 5, amountoutstanding => 5 })->store;
351
352         # Paid Off
353         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0 })->store;
354         Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 0 })->store;
355
356         is( $account->balance(), 5, "Account balance is 5" );
357         is( $account->outstanding_debits->total_outstanding, 15, 'Outstanding debits sum 15' );
358         is( $account->outstanding_credits->total_outstanding, -10, 'Outstanding credits sum -10' );
359
360         $account->reconcile_balance();
361
362         is( $account->balance(), 5, "Account balance is 5" );
363         is( $account->outstanding_debits->total_outstanding, 5, 'Outstanding debits sum 5' );
364         is( $account->outstanding_credits->total_outstanding, 0, 'Outstanding credits sum 0' );
365
366         $schema->storage->txn_rollback;
367     };
368
369     subtest 'credits are applied to older debits first' => sub {
370
371         plan tests => 9;
372
373         $schema->storage->txn_begin;
374
375         my $patron  = $builder->build_object({ class => 'Koha::Patrons' });
376         my $account = $patron->account;
377
378         # Add Credits
379         $account->add_credit({ amount => 1 });
380         $account->add_credit({ amount => 3 });
381
382         # Add Debits TODO: replace for calls to add_debit when time comes
383         my $debit_1 = Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 1, amountoutstanding => 1 })->store;
384         my $debit_2 = Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 2, amountoutstanding => 2 })->store;
385         my $debit_3 = Koha::Account::Line->new({ borrowernumber => $patron->id, amount => 3, amountoutstanding => 3 })->store;
386
387         is( $account->balance(), 2, "Account balance is 2" );
388         is( $account->outstanding_debits->total_outstanding, 6, 'Outstanding debits sum 6' );
389         is( $account->outstanding_credits->total_outstanding, -4, 'Outstanding credits sum -4' );
390
391         $account->reconcile_balance();
392
393         is( $account->balance(), 2, "Account balance is 2" );
394         is( $account->outstanding_debits->total_outstanding, 2, 'Outstanding debits sum 2' );
395         is( $account->outstanding_credits->total_outstanding, 0, 'Outstanding credits sum 0' );
396
397         $debit_1->discard_changes;
398         is( $debit_1->amountoutstanding + 0, 0, 'Old debit payed' );
399         $debit_2->discard_changes;
400         is( $debit_2->amountoutstanding + 0, 0, 'Old debit payed' );
401         $debit_3->discard_changes;
402         is( $debit_3->amountoutstanding + 0, 2, 'Newest debit only partially payed' );
403
404         $schema->storage->txn_rollback;
405     };
406 };