Bug 8326: Make repeatable and unique_id modifiable when editing patron attribute...
[koha.git] / t / db_dependent / Koha / Patron.t
1 #!/usr/bin/perl
2
3 # Copyright 2019 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 => 6;
23 use Test::Exception;
24
25 use Koha::Database;
26 use Koha::DateUtils qw(dt_from_string);
27 use Koha::Patrons;
28 use Koha::Patron::Relationships;
29
30 use t::lib::TestBuilder;
31 use t::lib::Mocks;
32
33 my $schema  = Koha::Database->new->schema;
34 my $builder = t::lib::TestBuilder->new;
35
36 subtest 'add_guarantor() tests' => sub {
37
38     plan tests => 6;
39
40     $schema->storage->txn_begin;
41
42     t::lib::Mocks::mock_preference( 'borrowerRelationship', 'father1|father2' );
43
44     my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' });
45     my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
46
47     throws_ok
48         { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber }); }
49         'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
50         'Exception is thrown as no relationship passed';
51
52     is( $patron_1->guarantee_relationships->count, 0, 'No guarantors added' );
53
54     throws_ok
55         { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father' }); }
56         'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
57         'Exception is thrown as a wrong relationship was passed';
58
59     is( $patron_1->guarantee_relationships->count, 0, 'No guarantors added' );
60
61     $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father1' });
62
63     my $guarantors = $patron_1->guarantor_relationships;
64
65     is( $guarantors->count, 1, 'No guarantors added' );
66
67     {
68         local *STDERR;
69         open STDERR, '>', '/dev/null';
70         throws_ok
71             { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father2' }); }
72             'Koha::Exceptions::Patron::Relationship::DuplicateRelationship',
73             'Exception is thrown for duplicated relationship';
74         close STDERR;
75     }
76
77     $schema->storage->txn_rollback;
78 };
79
80 subtest 'relationships_debt() tests' => sub {
81
82     plan tests => 168;
83
84     $schema->storage->txn_begin;
85
86     t::lib::Mocks::mock_preference( 'borrowerRelationship', 'parent' );
87
88     my $parent_1 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Parent 1" } });
89     my $parent_2 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Parent 2" } });
90     my $child_1 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Child 1" } });
91     my $child_2 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Child 2" } });
92
93     $child_1->add_guarantor({ guarantor_id => $parent_1->borrowernumber, relationship => 'parent' });
94     $child_1->add_guarantor({ guarantor_id => $parent_2->borrowernumber, relationship => 'parent' });
95     $child_2->add_guarantor({ guarantor_id => $parent_1->borrowernumber, relationship => 'parent' });
96     $child_2->add_guarantor({ guarantor_id => $parent_2->borrowernumber, relationship => 'parent' });
97
98     is( $child_1->guarantor_relationships->guarantors->count, 2, 'Child 1 has correct number of guarantors' );
99     is( $child_2->guarantor_relationships->guarantors->count, 2, 'Child 2 has correct number of guarantors' );
100     is( $parent_1->guarantee_relationships->guarantees->count, 2, 'Parent 1 has correct number of guarantees' );
101     is( $parent_2->guarantee_relationships->guarantees->count, 2, 'Parent 2 has correct number of guarantees' );
102
103     my $patrons = [ $parent_1, $parent_2, $child_1, $child_2 ];
104
105     # First test: No debt
106     my ($parent1_debt, $parent2_debt, $child1_debt, $child2_debt) = (0,0,0,0);
107     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
108
109     # Add debt to child_2
110     $child2_debt = 2;
111     $child_2->account->add_debit({ type => 'ACCOUNT', amount => $child2_debt, interface => 'commandline' });
112     is( $child_2->account->non_issues_charges, $child2_debt, 'Debt added to Child 2' );
113     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
114
115     $parent1_debt = 3;
116     $parent_1->account->add_debit({ type => 'ACCOUNT', amount => $parent1_debt, interface => 'commandline' });
117     is( $parent_1->account->non_issues_charges, $parent1_debt, 'Debt added to Parent 1' );
118     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
119
120     $parent2_debt = 5;
121     $parent_2->account->add_debit({ type => 'ACCOUNT', amount => $parent2_debt, interface => 'commandline' });
122     is( $parent_2->account->non_issues_charges, $parent2_debt, 'Parent 2 owes correct amount' );
123     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
124
125     $child1_debt = 7;
126     $child_1->account->add_debit({ type => 'ACCOUNT', amount => $child1_debt, interface => 'commandline' });
127     is( $child_1->account->non_issues_charges, $child1_debt, 'Child 1 owes correct amount' );
128     _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
129
130     $schema->storage->txn_rollback;
131 };
132
133 sub _test_combinations {
134     my ( $patrons, $parent1_debt, $parent2_debt, $child1_debt, $child2_debt ) = @_;
135
136     # Options
137     # P1 => P1 + C1 + C2 ( - P1 ) ( + P2 )
138     # P2 => P2 + C1 + C2 ( - P2 ) ( + P1 )
139     # C1 => P1 + P2 + C1 + C2 ( - C1 )
140     # C2 => P1 + P2 + C1 + C2 ( - C2 )
141
142 # 3 params, count from 0 to 7 in binary ( 3 places ) to get the set of switches, then do that 4 times, one for each parent and child
143     for my $i ( 0 .. 7 ) {
144         my ( $only_this_guarantor, $include_guarantors, $include_this_patron )
145           = split '', sprintf( "%03b", $i );
146         for my $patron ( @$patrons ) {
147             if ( $only_this_guarantor
148                 && !$patron->guarantee_relationships->count )
149             {
150                 throws_ok {
151                     $patron->relationships_debt(
152                         {
153                             only_this_guarantor => $only_this_guarantor,
154                             include_guarantors  => $include_guarantors,
155                             include_this_patron => $include_this_patron
156                         }
157                     );
158                 }
159                 'Koha::Exceptions::BadParameter',
160                   'Exception is thrown as patron is not a guarantor';
161
162             }
163             else {
164
165                 my $debt = 0;
166                 if ( $patron->firstname eq 'Parent 1' ) {
167                     $debt += $parent1_debt if ($include_this_patron && $include_guarantors);
168                     $debt += $child1_debt + $child2_debt;
169                     $debt += $parent2_debt unless ($only_this_guarantor || !$include_guarantors);
170                 }
171                 elsif ( $patron->firstname eq 'Parent 2' ) {
172                     $debt += $parent2_debt if ($include_this_patron & $include_guarantors);
173                     $debt += $child1_debt + $child2_debt;
174                     $debt += $parent1_debt unless ($only_this_guarantor || !$include_guarantors);
175                 }
176                 elsif ( $patron->firstname eq 'Child 1' ) {
177                     $debt += $child1_debt if ($include_this_patron);
178                     $debt += $child2_debt;
179                     $debt += $parent1_debt + $parent2_debt if ($include_guarantors);
180                 }
181                 else {
182                     $debt += $child2_debt if ($include_this_patron);
183                     $debt += $child1_debt;
184                     $debt += $parent1_debt + $parent2_debt if ($include_guarantors);
185                 }
186
187                 is(
188                     $patron->relationships_debt(
189                         {
190                             only_this_guarantor => $only_this_guarantor,
191                             include_guarantors  => $include_guarantors,
192                             include_this_patron => $include_this_patron
193                         }
194                     ),
195                     $debt,
196                     $patron->firstname
197                       . " debt of $debt calculated correctly for ( only_this_guarantor: $only_this_guarantor, include_guarantors: $include_guarantors, include_this_patron: $include_this_patron)"
198                 );
199             }
200         }
201     }
202 }
203
204 subtest 'add_enrolment_fee_if_needed() tests' => sub {
205
206     plan tests => 2;
207
208     subtest 'category has enrolment fee' => sub {
209         plan tests => 7;
210
211         $schema->storage->txn_begin;
212
213         my $category = $builder->build_object(
214             {
215                 class => 'Koha::Patron::Categories',
216                 value => {
217                     enrolmentfee => 20
218                 }
219             }
220         );
221
222         my $patron = $builder->build_object(
223             {
224                 class => 'Koha::Patrons',
225                 value => {
226                     categorycode => $category->categorycode
227                 }
228             }
229         );
230
231         my $enrollment_fee = $patron->add_enrolment_fee_if_needed();
232         is( $enrollment_fee * 1, 20, 'Enrolment fee amount is correct' );
233         my $account = $patron->account;
234         is( $patron->account->balance * 1, 20, 'Patron charged the enrolment fee' );
235         # second enrolment fee, new
236         $enrollment_fee = $patron->add_enrolment_fee_if_needed(0);
237         # third enrolment fee, renewal
238         $enrollment_fee = $patron->add_enrolment_fee_if_needed(1);
239         is( $patron->account->balance * 1, 60, 'Patron charged the enrolment fees' );
240
241         my @debits = $account->outstanding_debits;
242         is( scalar @debits, 3, '3 enrolment fees' );
243         is( $debits[0]->debit_type_code, 'ACCOUNT', 'Account type set correctly' );
244         is( $debits[1]->debit_type_code, 'ACCOUNT', 'Account type set correctly' );
245         is( $debits[2]->debit_type_code, 'ACCOUNT_RENEW', 'Account type set correctly' );
246
247         $schema->storage->txn_rollback;
248     };
249
250     subtest 'no enrolment fee' => sub {
251
252         plan tests => 3;
253
254         $schema->storage->txn_begin;
255
256         my $category = $builder->build_object(
257             {
258                 class => 'Koha::Patron::Categories',
259                 value => {
260                     enrolmentfee => 0
261                 }
262             }
263         );
264
265         my $patron = $builder->build_object(
266             {
267                 class => 'Koha::Patrons',
268                 value => {
269                     categorycode => $category->categorycode
270                 }
271             }
272         );
273
274         my $enrollment_fee = $patron->add_enrolment_fee_if_needed();
275         is( $enrollment_fee * 1, 0, 'No enrolment fee' );
276         my $account = $patron->account;
277         is( $patron->account->balance, 0, 'Patron not charged anything' );
278
279         my @debits = $account->outstanding_debits;
280         is( scalar @debits, 0, 'no debits' );
281
282         $schema->storage->txn_rollback;
283     };
284 };
285
286 subtest 'to_api() tests' => sub {
287
288     plan tests => 6;
289
290     $schema->storage->txn_begin;
291
292     my $patron_class = Test::MockModule->new('Koha::Patron');
293     $patron_class->mock(
294         'algo',
295         sub { return 'algo' }
296     );
297
298     my $patron = $builder->build_object(
299         {
300             class => 'Koha::Patrons',
301             value => {
302                 debarred => undef
303             }
304         }
305     );
306
307     my $restricted = $patron->to_api->{restricted};
308     ok( defined $restricted, 'restricted is defined' );
309     ok( !$restricted, 'debarred is undef, restricted evaluates to false' );
310
311     $patron->debarred( dt_from_string->add( days => 1 ) )->store->discard_changes;
312     $restricted = $patron->to_api->{restricted};
313     ok( defined $restricted, 'restricted is defined' );
314     ok( $restricted, 'debarred is defined, restricted evaluates to true' );
315
316     my $patron_json = $patron->to_api({ embed => { algo => {} } });
317     ok( exists $patron_json->{algo} );
318     is( $patron_json->{algo}, 'algo' );
319
320     $schema->storage->txn_rollback;
321 };
322
323 subtest 'login_attempts tests' => sub {
324     plan tests => 1;
325
326     $schema->storage->txn_begin;
327
328     my $patron = $builder->build_object(
329         {
330             class => 'Koha::Patrons',
331         }
332     );
333     my $patron_info = $patron->unblessed;
334     $patron->delete;
335     delete $patron_info->{login_attempts};
336     my $new_patron = Koha::Patron->new($patron_info)->store;
337     is( $new_patron->discard_changes->login_attempts, 0, "login_attempts defaults to 0 as expected");
338
339     $schema->storage->txn_rollback;
340 };
341
342 subtest 'is_superlibrarian() tests' => sub {
343
344     plan tests => 3;
345
346     $schema->storage->txn_begin;
347
348     my $patron = $builder->build_object(
349         {
350             class => 'Koha::Patrons',
351
352             value => {
353                 flags => 16
354             }
355         }
356     );
357
358     is( $patron->is_superlibrarian, 0, 'Patron is not a superlibrarian and the method returns the correct value' );
359
360     $patron->flags(1)->store->discard_changes;
361     is( $patron->is_superlibrarian, 1, 'Patron is a superlibrarian and the method returns the correct value' );
362
363     $patron->flags(0)->store->discard_changes;
364     is( $patron->is_superlibrarian, 0, 'Patron is not a superlibrarian and the method returns the correct value' );
365
366     $schema->storage->txn_rollback;
367 };