Bug 26274: Add cashups api routes
[koha.git] / t / db_dependent / Koha / Cash / Register / Cashup.t
1 #!/usr/bin/perl
2
3 # Copyright 2020 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 use Test::More tests => 3;
22
23 use Koha::Database;
24
25 use t::lib::TestBuilder;
26
27 my $builder = t::lib::TestBuilder->new;
28 my $schema  = Koha::Database->new->schema;
29
30 subtest 'manager' => sub {
31     plan tests => 2;
32
33     $schema->storage->txn_begin;
34
35     my $manager = $builder->build_object( { class => 'Koha::Patrons' } );
36     my $cashup = $builder->build_object(
37         {
38             class => 'Koha::Cash::Register::Cashups',
39             value => { manager_id => $manager->borrowernumber },
40         }
41     );
42
43     is( ref( $cashup->manager ),
44         'Koha::Patron',
45         'Koha::Cash::Register::Cashup->manager should return a Koha::Patron' );
46
47     is( $cashup->manager->id, $manager->id,
48         'Koha::Cash::Register::Cashup->manager returns the correct Koha::Patron'
49     );
50
51     $schema->storage->txn_rollback;
52
53 };
54
55 subtest 'register' => sub {
56     plan tests => 2;
57
58     $schema->storage->txn_begin;
59
60     my $register =
61       $builder->build_object( { class => 'Koha::Cash::Registers' } );
62     my $cashup = $builder->build_object(
63         {
64             class => 'Koha::Cash::Register::Cashups',
65             value => { register_id => $register->id },
66         }
67     );
68
69     is(
70         ref( $cashup->register ),
71         'Koha::Cash::Register',
72 'Koha::Cash::Register::Cashup->register should return a Koha::Cash::Register'
73     );
74
75     is( $cashup->register->id, $register->id,
76 'Koha::Cash::Register::Cashup->register returns the correct Koha::Cash::Register'
77     );
78
79     $schema->storage->txn_rollback;
80
81 };
82
83 subtest 'summary' => sub {
84     plan tests => 29;
85
86     $schema->storage->txn_begin;
87
88     my $register =
89       $builder->build_object( { class => 'Koha::Cash::Registers' } );
90     my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
91     my $manager = $builder->build_object( { class => 'Koha::Patrons' } );
92     my $account = $patron->account;
93     my $expected_total   = 0;
94     my $expected_income_total   = 0;
95     my $expected_income_grouped = [];
96     my $expected_payout_total   = 0;
97     my $expected_payout_grouped = [];
98
99     # Transaction 1 (Fine (1.00) + Payment (-1.00))
100     my $fine1 = $account->add_debit(
101         {
102             amount    => '1.00',
103             type      => 'OVERDUE',
104             interface => 'cron'
105         }
106     );
107     $fine1->date( \'NOW() - INTERVAL 20 MINUTE' )->store;
108
109     my $payment1 = $account->pay(
110         {
111             cash_register => $register->id,
112             amount        => '1.00',
113             credit_type   => 'PAYMENT',
114             lines         => [$fine1]
115         }
116     );
117     $payment1 = Koha::Account::Lines->find( $payment1->{payment_id} );
118     $payment1->date( \'NOW() - INTERVAL 15 MINUTE' )->store;
119     $expected_income_total += '1.00';
120
121     # Overdue of 1.0 fully paid
122     unshift @{$expected_income_grouped},
123       {
124         debit_type_code => 'OVERDUE',
125         total           => '1',
126         debit_type      => { description => 'Overdue fine' }
127       };
128
129     # Transaction 2 (Account (1.00) + Lost (0.50) + Payment (-1.50))
130     my $account1 = $account->add_debit(
131         {
132             amount    => '1.00',
133             type      => 'ACCOUNT',
134             interface => 'cron'
135         }
136     );
137     $account1->date( \'NOW() - INTERVAL 13 MINUTE' )->store;
138     my $lost1 = $account->add_debit(
139         {
140             amount    => '0.50',
141             type      => 'LOST',
142             interface => 'cron'
143         }
144     );
145     $lost1->date( \'NOW() - INTERVAL 13 MINUTE' )->store;
146     my $payment2 = $account->pay(
147         {
148             cash_register => $register->id,
149             amount        => '1.50',
150             credit_type   => 'PAYMENT',
151             lines         => [ $account1, $lost1 ]
152         }
153     );
154     $payment2 = Koha::Account::Lines->find( $payment2->{payment_id} );
155     $payment2->date( \'NOW() - INTERVAL 13 MINUTE' )->store;
156     $expected_income_total += '1.5';
157
158     # Lost charge of 0.5 fully paid
159     unshift @{$expected_income_grouped},
160       {
161         debit_type_code => 'LOST',
162         total           => '0.5',
163         debit_type      => { description => 'Lost item' }
164       };
165
166     # Account fee of 1.0 fully paid
167     unshift @{$expected_income_grouped},
168       {
169         debit_type_code => 'ACCOUNT',
170         total           => '1',
171         debit_type      => { description => 'Account creation fee' }
172       };
173
174     # Transaction 3 (Refund (-0.50) + Payout (0.50))
175     $lost1->discard_changes;
176     my $refund1 = $lost1->reduce(
177         {
178             amount         => '0.50',
179             reduction_type => 'REFUND',
180             interface      => 'cron'
181         }
182     );
183     $refund1->date( \'NOW() - INTERVAL 13 MINUTE' )->store;
184
185     my $payout1 = $refund1->payout(
186         {
187             cash_register => $register->id,
188             amount        => '0.50',
189             payout_type   => 'CASH',
190             interface     => 'intranet',
191             staff_id      => $manager->borrowernumber,
192             branch        => $manager->branchcode
193         }
194     );
195     $payout1->date( \'NOW() - INTERVAL 13 MINUTE' )->store;
196     $expected_payout_total += '0.5';
197
198     # Lost fee of 0.50 fully refunded
199     unshift @{$expected_payout_grouped},
200       {
201         'total'       => '0.5',
202         'credit_type' => {
203             'description' => 'A refund applied to a patrons fine'
204         },
205         'credit_type_code' => 'REFUND'
206       };
207
208     $expected_total += $expected_income_total;
209     $expected_total -= $expected_payout_total;
210
211     diag("Cashup 1");
212     my $cashup1 =
213       $register->add_cashup( { manager_id => $manager->id, amount => '2.00' } );
214
215     my $summary = $cashup1->summary;
216
217     is( $summary->{from_date}, undef, "from_date is undefined if there is only one recorded" );
218     is( $summary->{to_date}, $cashup1->timestamp, "to_date equals cashup timestamp" );
219     is( ref( $summary->{income_grouped} ), 'ARRAY', "income_grouped contains an arrayref" );
220     is( scalar @{ $summary->{income_grouped} }, 3, "income_grouped contains 3 transactions" );
221     is_deeply( $summary->{income_grouped}, $expected_income_grouped, "income_grouped arrayref is correct" );
222     is( $summary->{income_total}, $expected_income_total, "income_total is correct" );
223
224     is( ref( $summary->{payout_grouped} ), 'ARRAY', "payout_grouped contains an arrayref" );
225     is( scalar @{ $summary->{payout_grouped} }, 1, "payout_grouped contains 1 transaction" );
226     is_deeply( $summary->{payout_grouped}, $expected_payout_grouped, "payout_grouped arrayref is correct" );
227     is( $summary->{payout_total}, $expected_payout_total, "payout_total is correct" );
228     is( $summary->{total}, $expected_total,"total equals expected_total" );
229
230     # Backdate cashup1 so we can add a new cashup to check 'previous'
231     $cashup1->timestamp( \'NOW() - INTERVAL 12 MINUTE' )->store();
232     $cashup1->discard_changes;
233     $expected_total = 0;
234     $expected_income_total   = 0;
235     $expected_income_grouped = [];
236     $expected_payout_total   = 0;
237     $expected_payout_grouped = [];
238
239     # Transaction 4 ( Fine (2.75) + Partial payment (-2.00) )
240     my $fine2 = $account->add_debit(
241         {
242             amount    => '2.75',
243             type      => 'OVERDUE',
244             interface => 'cron'
245         }
246     );
247     $fine2->date( \'NOW() - INTERVAL 10 MINUTE' )->store;
248
249     my $payment3 = $account->pay(
250         {
251             cash_register => $register->id,
252             amount        => '2.00',
253             credit_type   => 'PAYMENT',
254             lines         => [$fine2]
255         }
256     );
257     $payment3 = Koha::Account::Lines->find( $payment3->{payment_id} );
258     $payment3->date( \'NOW() - INTERVAL 10 MINUTE' )->store;
259     $expected_income_total += '2.00';
260
261     unshift @{$expected_income_grouped},
262       {
263         debit_type_code => 'OVERDUE',
264         total           => '-2.000000' * -1,
265         debit_type      => { 'description' => 'Overdue fine' }
266       };
267
268     $expected_total += $expected_income_total;
269     $expected_total -= $expected_payout_total;
270
271     diag("Cashup 2");
272     my $cashup2 =
273       $register->add_cashup( { manager_id => $manager->id, amount => '2.00' } );
274
275     $summary = $cashup2->summary;
276
277     is( $summary->{from_date}, $cashup1->timestamp, "from_date returns the timestamp of the previous cashup cashup" );
278     is( $summary->{to_date}, $cashup2->timestamp, "to_date equals cashup timestamp" );
279     is( ref( $summary->{income_grouped} ), 'ARRAY', "income_grouped contains Koha::Account::Lines" );
280     is( scalar @{ $summary->{income_grouped} }, 1, "income_grouped contains 1 transaction" );
281     is_deeply( $summary->{income_grouped}, $expected_income_grouped, "income_grouped arrayref is correct for partial payment" );
282     is( ref( $summary->{payout_grouped} ), 'ARRAY', "payout_grouped contains Koha::Account::Lines" );
283     is( scalar @{ $summary->{payout_grouped} }, 0, "payout_grouped contains 0 transactions" );
284     is_deeply( $summary->{payout_grouped}, $expected_payout_grouped, "payout_grouped arrayref is correct" );
285     is( $summary->{total}, $expected_total, "total equals expected_total" );
286
287     # Backdate cashup2 so we can add a new cashup to check
288     $cashup2->timestamp( \'NOW() - INTERVAL 6 MINUTE' )->store();
289     $cashup2->discard_changes;
290     $expected_total = 0;
291     $expected_income_total   = 0;
292     $expected_income_grouped = [];
293     $expected_payout_total   = 0;
294     $expected_payout_grouped = [];
295
296     # Transaction 5 (Refund (-1) + Payout (1))
297     $account1->discard_changes;
298     my $refund2 = $account1->reduce(
299         {
300             amount         => '1.00',
301             reduction_type => 'REFUND',
302             interface      => 'cron'
303         }
304     );
305     $refund2->date( \'NOW() - INTERVAL 3 MINUTE' )->store;
306
307     my $payout2 = $refund2->payout(
308         {
309             cash_register => $register->id,
310             amount        => '1.00',
311             payout_type   => 'CASH',
312             interface     => 'intranet',
313             staff_id      => $manager->borrowernumber,
314             branch        => $manager->branchcode
315         }
316     );
317     $payout2->date( \'NOW() - INTERVAL 3 MINUTE' )->store;
318     $expected_payout_total += '1.00';
319
320     # Account fee of 1.00 fully refunded (Accross cashup boundary)
321     unshift @{$expected_payout_grouped},
322       {
323         'total'       => '1',
324         'credit_type' => {
325             'description' => 'A refund applied to a patrons fine'
326         },
327         'credit_type_code' => 'REFUND'
328       };
329
330     $expected_total += $expected_income_total;
331     $expected_total -= $expected_payout_total;
332
333     diag("Cashup 3");
334     my $cashup3 =
335       $register->add_cashup( { manager_id => $manager->id, amount => '2.00' } );
336
337     $summary = $cashup3->summary;
338
339     is( $summary->{from_date}, $cashup2->timestamp, "from_date returns the timestamp of the previous cashup cashup" );
340     is( $summary->{to_date}, $cashup3->timestamp, "to_date equals cashup timestamp" );
341     is( ref( $summary->{income_grouped} ), 'ARRAY', "income_grouped contains Koha::Account::Lines" );
342     is( scalar @{ $summary->{income_grouped} }, 0, "income_grouped contains 1 transaction" );
343     is_deeply( $summary->{income_grouped}, $expected_income_grouped, "income_grouped arrayref is correct for partial payment" );
344     is( ref( $summary->{payout_grouped} ), 'ARRAY', "payout_grouped contains Koha::Account::Lines" );
345     is( scalar @{ $summary->{payout_grouped} }, 1, "payout_grouped contains 0 transactions" );
346     is_deeply( $summary->{payout_grouped}, $expected_payout_grouped, "payout_grouped arrayref is correct" );
347     is( $summary->{total}, $expected_total, "total equals expected_total" );
348
349     $schema->storage->txn_rollback;
350 };
351
352 1;