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