3 # Copyright 2019 Koha Development team
5 # This file is part of Koha
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.
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.
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>.
22 use Test::More tests => 31;
27 use Koha::CirculationRules;
29 use Koha::DateUtils qw(dt_from_string);
30 use Koha::ArticleRequests;
32 use Koha::List::Patron qw(AddPatronList AddPatronsToList);
33 use Koha::Patron::Relationships;
34 use C4::Circulation qw( AddIssue AddReturn );
36 use t::lib::TestBuilder;
39 my $schema = Koha::Database->new->schema;
40 my $builder = t::lib::TestBuilder->new;
42 subtest 'Accessor tests' => sub {
44 $schema->storage->txn_begin;
46 my $object = Koha::Patron->new( { surname => 'Test Patron' } );
47 is( $object->surname(), 'Test Patron', "Accessor returns correct value" );
48 $object->surname('Test Patron Surname');
49 is( $object->surname(), 'Test Patron Surname', "Accessor returns correct value after set" );
51 my $object2 = Koha::Patron->new( { surname => 'Test Patron 2' } );
52 is( $object2->surname(), 'Test Patron 2', "Accessor returns correct value" );
53 $object2->surname('Test Patron Surname 2');
54 is( $object2->surname(), 'Test Patron Surname 2', "Accessor returns correct value after set" );
57 $ret = $object2->set( { surname => "Test Patron Surname 3", firstname => "Test Firstname" } );
58 ok( ref($ret) eq 'Koha::Patron', "Set returns object on success" );
59 is( $object2->surname(), "Test Patron Surname 3", "Set sets first field correctly" );
60 is( $object2->firstname(), "Test Firstname", "Set sets second field correctly" );
62 our $patron = Koha::Patron->new(
64 borrowernumber => '12345',
65 cardnumber => '1234567890',
66 surname => 'mySurname',
67 firstname => 'myFirstname',
69 othernames => 'myOthernames',
71 streetnumber => '100',
73 address => 'my personal address',
74 address2 => 'my adress2',
79 email => 'mySurname.myFirstname@email.com',
80 phone => '0402872934',
81 mobile => '0627884632',
83 emailpro => 'myEmailPro@email.com',
84 phonepro => '0402873334',
85 B_streetnumber => '101',
86 B_streettype => 'myB_streettype',
87 B_address => 'myB_address',
88 B_address2 => 'myB_address2',
90 B_state => 'myB_state',
92 B_country => 'myB_country',
93 B_email => 'myB_email',
94 B_phone => '0678353935',
95 dateofbirth => '1990-07-16',
96 branchcode => 'myBranCode',
97 categorycode => 'myCatCode',
98 dateenrolled => '2015-03-19',
99 dateexpiry => '2016-03-19',
100 gonenoaddress => '0',
102 debarred => '2015-04-19',
103 debarredcomment => 'You are debarred',
104 borrowernotes => 'borrowernotes',
106 password => 'hfkurhfe976634èj!',
109 opacnote => 'myOpacnote',
110 contactnote => 'myContactnote',
113 altcontactfirstname => 'myAltcontactfirstname',
114 altcontactsurname => 'myAltcontactsurname',
115 altcontactaddress1 => 'myAltcontactaddress1',
116 altcontactaddress2 => 'myAltcontactaddress2',
117 altcontactaddress3 => 'myAltcontactaddress3',
118 altcontactstate => 'myAltcontactstate',
119 altcontactzipcode => '465843',
120 altcontactcountry => 'myOtherCountry',
121 altcontactphone => 'myOtherphone',
122 smsalertnumber => '0683027346',
127 subtest 'Accessor tests after new' => sub {
129 is( $patron->borrowernumber, '12345', 'borrowernumber accessor returns correct value' );
130 is( $patron->cardnumber, '1234567890', 'cardnumber accessor returns correct value' );
131 is( $patron->surname, 'mySurname', 'surname accessor returns correct value' );
132 is( $patron->firstname, 'myFirstname', 'firstname accessor returns correct value' );
133 is( $patron->title, 'Mr.', 'title accessor returns correct value' );
134 is( $patron->othernames, 'myOthernames', 'othernames accessor returns correct value' );
135 is( $patron->initials, 'MM', 'initials accessor returns correct value' );
136 is( $patron->streetnumber, '100', 'streetnumber accessor returns correct value' );
137 is( $patron->streettype, 'Blvd', 'streettype accessor returns correct value' );
138 is( $patron->address, 'my personal address', 'address accessor returns correct value' );
139 is( $patron->address2, 'my adress2', 'address2 accessor returns correct value' );
140 is( $patron->city, 'Marseille', 'city accessor returns correct value' );
141 is( $patron->state, 'mystate', 'state accessor returns correct value' );
142 is( $patron->zipcode, '13006', 'zipcode accessor returns correct value' );
143 is( $patron->country, 'France', 'country accessor returns correct value' );
144 is( $patron->email, 'mySurname.myFirstname@email.com', 'email accessor returns correct value' );
145 is( $patron->phone, '0402872934', 'phone accessor returns correct value' );
146 is( $patron->mobile, '0627884632', 'mobile accessor returns correct value' );
147 is( $patron->fax, '0402872935', 'fax accessor returns correct value' );
148 is( $patron->emailpro, 'myEmailPro@email.com', 'emailpro accessor returns correct value' );
149 is( $patron->phonepro, '0402873334', 'phonepro accessor returns correct value' );
150 is( $patron->B_streetnumber, '101', 'B_streetnumber accessor returns correct value' );
151 is( $patron->B_streettype, 'myB_streettype', 'B_streettype accessor returns correct value' );
152 is( $patron->B_address, 'myB_address', 'B_address accessor returns correct value' );
153 is( $patron->B_address2, 'myB_address2', 'B_address2 accessor returns correct value' );
154 is( $patron->B_city, 'myB_city', 'B_city accessor returns correct value' );
155 is( $patron->B_state, 'myB_state', 'B_state accessor returns correct value' );
156 is( $patron->B_zipcode, '23456', 'B_zipcode accessor returns correct value' );
157 is( $patron->B_country, 'myB_country', 'B_country accessor returns correct value' );
158 is( $patron->B_email, 'myB_email', 'B_email accessor returns correct value' );
159 is( $patron->B_phone, '0678353935', 'B_phone accessor returns correct value' );
160 is( $patron->dateofbirth, '1990-07-16', 'dateofbirth accessor returns correct value' );
161 is( $patron->branchcode, 'myBranCode', 'branchcode accessor returns correct value' );
162 is( $patron->categorycode, 'myCatCode', 'categorycode accessor returns correct value' );
163 is( $patron->dateenrolled, '2015-03-19', 'dateenrolled accessor returns correct value' );
164 is( $patron->dateexpiry, '2016-03-19', 'dateexpiry accessor returns correct value' );
165 is( $patron->gonenoaddress, '0', 'gonenoaddress accessor returns correct value' );
166 is( $patron->lost, '0', 'lost accessor returns correct value' );
167 is( $patron->debarred, '2015-04-19', 'debarred accessor returns correct value' );
168 is( $patron->debarredcomment, 'You are debarred', 'debarredcomment accessor returns correct value' );
169 is( $patron->borrowernotes, 'borrowernotes', 'borrowernotes accessor returns correct value' );
170 is( $patron->sex, 'M', 'sex accessor returns correct value' );
171 is( $patron->password, 'hfkurhfe976634èj!', 'password accessor returns correct value' );
172 is( $patron->flags, '55555', 'flags accessor returns correct value' );
173 is( $patron->userid, '87987', 'userid accessor returns correct value' );
174 is( $patron->opacnote, 'myOpacnote', 'opacnote accessor returns correct value' );
175 is( $patron->contactnote, 'myContactnote', 'contactnote accessor returns correct value' );
176 is( $patron->sort1, 'mySort1', 'sort1 accessor returns correct value' );
177 is( $patron->sort2, 'mySort2', 'sort2 accessor returns correct value' );
179 $patron->altcontactfirstname, 'myAltcontactfirstname',
180 'altcontactfirstname accessor returns correct value'
182 is( $patron->altcontactsurname, 'myAltcontactsurname', 'altcontactsurname accessor returns correct value' );
183 is( $patron->altcontactaddress1, 'myAltcontactaddress1', 'altcontactaddress1 accessor returns correct value' );
184 is( $patron->altcontactaddress2, 'myAltcontactaddress2', 'altcontactaddress2 accessor returns correct value' );
185 is( $patron->altcontactaddress3, 'myAltcontactaddress3', 'altcontactaddress3 accessor returns correct value' );
186 is( $patron->altcontactstate, 'myAltcontactstate', 'altcontactstate accessor returns correct value' );
187 is( $patron->altcontactzipcode, '465843', 'altcontactzipcode accessor returns correct value' );
188 is( $patron->altcontactcountry, 'myOtherCountry', 'altcontactcountry accessor returns correct value' );
189 is( $patron->altcontactphone, 'myOtherphone', 'altcontactphone accessor returns correct value' );
190 is( $patron->smsalertnumber, '0683027346', 'smsalertnumber accessor returns correct value' );
191 is( $patron->privacy, '667788', 'privacy accessor returns correct value' );
194 subtest 'Accessor tests after set' => sub {
199 borrowernumber => '12346',
200 cardnumber => '1234567891',
201 surname => 'SmySurname',
202 firstname => 'SmyFirstname',
204 othernames => 'SmyOthernames',
206 streetnumber => '200',
208 address => 'Smy personal address',
209 address2 => 'Smy adress2',
214 email => 'SmySurname.myFirstname@email.com',
215 phone => '0402872935',
216 mobile => '0627884633',
218 emailpro => 'SmyEmailPro@email.com',
219 phonepro => '0402873335',
220 B_streetnumber => '102',
221 B_streettype => 'SmyB_streettype',
222 B_address => 'SmyB_address',
223 B_address2 => 'SmyB_address2',
224 B_city => 'SmyB_city',
225 B_state => 'SmyB_state',
226 B_zipcode => '12333',
227 B_country => 'SmyB_country',
228 B_email => 'SmyB_email',
229 B_phone => '0678353936',
230 dateofbirth => '1991-07-16',
231 branchcode => 'SmyBranCode',
232 categorycode => 'SmyCatCode',
233 dateenrolled => '2014-03-19',
234 dateexpiry => '2017-03-19',
235 gonenoaddress => '1',
237 debarred => '2016-04-19',
238 debarredcomment => 'You are still debarred',
239 borrowernotes => 'Sborrowernotes',
241 password => 'zerzerzer#',
244 opacnote => 'SmyOpacnote',
245 contactnote => 'SmyContactnote',
248 altcontactfirstname => 'SmyAltcontactfirstname',
249 altcontactsurname => 'SmyAltcontactsurname',
250 altcontactaddress1 => 'SmyAltcontactaddress1',
251 altcontactaddress2 => 'SmyAltcontactaddress2',
252 altcontactaddress3 => 'SmyAltcontactaddress3',
253 altcontactstate => 'SmyAltcontactstate',
254 altcontactzipcode => '565843',
255 altcontactcountry => 'SmyOtherCountry',
256 altcontactphone => 'SmyOtherphone',
257 smsalertnumber => '0683027347',
262 is( $patron->borrowernumber, '12346', 'borrowernumber field set ok' );
263 is( $patron->cardnumber, '1234567891', 'cardnumber field set ok' );
264 is( $patron->surname, 'SmySurname', 'surname field set ok' );
265 is( $patron->firstname, 'SmyFirstname', 'firstname field set ok' );
266 is( $patron->title, 'Mme.', 'title field set ok' );
267 is( $patron->othernames, 'SmyOthernames', 'othernames field set ok' );
268 is( $patron->initials, 'SS', 'initials field set ok' );
269 is( $patron->streetnumber, '200', 'streetnumber field set ok' );
270 is( $patron->streettype, 'Rue', 'streettype field set ok' );
271 is( $patron->address, 'Smy personal address', 'address field set ok' );
272 is( $patron->address2, 'Smy adress2', 'address2 field set ok' );
273 is( $patron->city, 'Lyon', 'city field set ok' );
274 is( $patron->state, 'Smystate', 'state field set ok' );
275 is( $patron->zipcode, '69000', 'zipcode field set ok' );
276 is( $patron->country, 'France', 'country field set ok' );
277 is( $patron->email, 'SmySurname.myFirstname@email.com', 'email field set ok' );
278 is( $patron->phone, '0402872935', 'phone field set ok' );
279 is( $patron->mobile, '0627884633', 'mobile field set ok' );
280 is( $patron->fax, '0402872936', 'fax field set ok' );
281 is( $patron->emailpro, 'SmyEmailPro@email.com', 'emailpro field set ok' );
282 is( $patron->phonepro, '0402873335', 'phonepro field set ok' );
283 is( $patron->B_streetnumber, '102', 'B_streetnumber field set ok' );
284 is( $patron->B_streettype, 'SmyB_streettype', 'B_streettype field set ok' );
285 is( $patron->B_address, 'SmyB_address', 'B_address field set ok' );
286 is( $patron->B_address2, 'SmyB_address2', 'B_address2 field set ok' );
287 is( $patron->B_city, 'SmyB_city', 'B_city field set ok' );
288 is( $patron->B_state, 'SmyB_state', 'B_state field set ok' );
289 is( $patron->B_zipcode, '12333', 'B_zipcode field set ok' );
290 is( $patron->B_country, 'SmyB_country', 'B_country field set ok' );
291 is( $patron->B_email, 'SmyB_email', 'B_email field set ok' );
292 is( $patron->B_phone, '0678353936', 'B_phone field set ok' );
293 is( $patron->dateofbirth, '1991-07-16', 'dateofbirth field set ok' );
294 is( $patron->branchcode, 'SmyBranCode', 'branchcode field set ok' );
295 is( $patron->categorycode, 'SmyCatCode', 'categorycode field set ok' );
296 is( $patron->dateenrolled, '2014-03-19', 'dateenrolled field set ok' );
297 is( $patron->dateexpiry, '2017-03-19', 'dateexpiry field set ok' );
298 is( $patron->gonenoaddress, '1', 'gonenoaddress field set ok' );
299 is( $patron->lost, '1', 'lost field set ok' );
300 is( $patron->debarred, '2016-04-19', 'debarred field set ok' );
301 is( $patron->debarredcomment, 'You are still debarred', 'debarredcomment field set ok' );
302 is( $patron->borrowernotes, 'Sborrowernotes', 'borrowernotes field set ok' );
303 is( $patron->sex, 'F', 'sex field set ok' );
304 is( $patron->password, 'zerzerzer#', 'password field set ok' );
305 is( $patron->flags, '666666', 'flags field set ok' );
306 is( $patron->userid, '98233', 'userid field set ok' );
307 is( $patron->opacnote, 'SmyOpacnote', 'opacnote field set ok' );
308 is( $patron->contactnote, 'SmyContactnote', 'contactnote field set ok' );
309 is( $patron->sort1, 'SmySort1', 'sort1 field set ok' );
310 is( $patron->sort2, 'SmySort2', 'sort2 field set ok' );
311 is( $patron->altcontactfirstname, 'SmyAltcontactfirstname', 'altcontactfirstname field set ok' );
312 is( $patron->altcontactsurname, 'SmyAltcontactsurname', 'altcontactsurname field set ok' );
313 is( $patron->altcontactaddress1, 'SmyAltcontactaddress1', 'altcontactaddress1 field set ok' );
314 is( $patron->altcontactaddress2, 'SmyAltcontactaddress2', 'altcontactaddress2 field set ok' );
315 is( $patron->altcontactaddress3, 'SmyAltcontactaddress3', 'altcontactaddress3 field set ok' );
316 is( $patron->altcontactstate, 'SmyAltcontactstate', 'altcontactstate field set ok' );
317 is( $patron->altcontactzipcode, '565843', 'altcontactzipcode field set ok' );
318 is( $patron->altcontactcountry, 'SmyOtherCountry', 'altcontactcountry field set ok' );
319 is( $patron->altcontactphone, 'SmyOtherphone', 'altcontactphone field set ok' );
320 is( $patron->smsalertnumber, '0683027347', 'smsalertnumber field set ok' );
321 is( $patron->privacy, '667789', 'privacy field set ok' );
325 subtest 'is_active' => sub {
327 $schema->storage->txn_begin;
329 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
330 throws_ok { $patron->is_active } 'Koha::Exceptions::MissingParameter', 'Called without params';
333 $patron->dateexpiry( dt_from_string->subtract( days => 1 ) )->lastseen(undef)->store;
334 is( $patron->is_active( { days => 1 } ), 0, 'Expired patron is not active' );
335 $patron->dateexpiry(undef)->store;
336 is( $patron->is_active( { days => 1 } ), 1, 'Expiry date removed' );
339 $patron->anonymized(1)->store;
340 is( $patron->is_active( { days => 1 } ), 0, 'Anonymized patron is not active' );
341 $patron->anonymized(0)->store;
343 # Change enrolled date now
344 $patron->dateenrolled('2020-01-01')->store;
345 is( $patron->is_active( { days => 1 } ), 0, 'No recent enrollment and lastseen still empty: not active' );
346 $patron->dateenrolled( dt_from_string() )->store;
347 is( $patron->is_active( { days => 1 } ), 1, 'Enrolled today: active' );
349 # Check lastseen, test days parameter
350 t::lib::Mocks::mock_preference( 'TrackLastPatronActivityTriggers', 'login' );
351 $patron->dateenrolled('2020-01-01')->store;
352 $patron->update_lastseen('login');
353 is( $patron->is_active( { days => 1 } ), 1, 'Just logged in' );
354 my $ago = dt_from_string->subtract( days => 2 );
355 $patron->lastseen($ago)->store;
356 is( $patron->is_active( { days => 1 } ), 0, 'Not active since yesterday' );
357 is( $patron->is_active( { days => 3 } ), 1, 'Active within last 3 days' );
358 # test since parameter
359 my $dt = $ago->clone->add( hours => 1 );
360 is( $patron->is_active( { since => $dt } ), 0, 'Inactive since ago + 1 hour' );
361 $dt = $ago->clone->subtract( hours => 1 );
362 is( $patron->is_active( { since => $dt } ), 1, 'Active since ago - 1 hour' );
363 # test weeks parameter
364 is( $patron->is_active( { weeks => 1 } ), 1, 'Active within last week' );
366 $schema->storage->txn_rollback;
369 subtest 'add_guarantor() tests' => sub {
373 $schema->storage->txn_begin;
375 t::lib::Mocks::mock_preference( 'borrowerRelationship', 'father1|father2' );
377 my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' });
378 my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
381 { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber }); }
382 'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
383 'Exception is thrown as no relationship passed';
385 is( $patron_1->guarantee_relationships->count, 0, 'No guarantors added' );
388 { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father' }); }
389 'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
390 'Exception is thrown as a wrong relationship was passed';
392 is( $patron_1->guarantee_relationships->count, 0, 'No guarantors added' );
394 $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father1' });
396 my $guarantors = $patron_1->guarantor_relationships;
398 is( $guarantors->count, 1, 'No guarantors added' );
402 open STDERR, '>', '/dev/null';
404 { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father2' }); }
405 'Koha::Exceptions::Patron::Relationship::DuplicateRelationship',
406 'Exception is thrown for duplicated relationship';
410 $schema->storage->txn_rollback;
413 subtest 'relationships_debt() tests' => sub {
417 $schema->storage->txn_begin;
419 t::lib::Mocks::mock_preference( 'borrowerRelationship', 'parent' );
421 my $parent_1 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Parent 1" } });
422 my $parent_2 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Parent 2" } });
423 my $child_1 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => " Child 1" } });
424 my $child_2 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => " Child 2" } });
426 $child_1->add_guarantor({ guarantor_id => $parent_1->borrowernumber, relationship => 'parent' });
427 $child_1->add_guarantor({ guarantor_id => $parent_2->borrowernumber, relationship => 'parent' });
428 $child_2->add_guarantor({ guarantor_id => $parent_1->borrowernumber, relationship => 'parent' });
429 $child_2->add_guarantor({ guarantor_id => $parent_2->borrowernumber, relationship => 'parent' });
431 is( $child_1->guarantor_relationships->guarantors->count, 2, 'Child 1 has correct number of guarantors' );
432 is( $child_2->guarantor_relationships->guarantors->count, 2, 'Child 2 has correct number of guarantors' );
433 is( $parent_1->guarantee_relationships->guarantees->count, 2, 'Parent 1 has correct number of guarantees' );
434 is( $parent_2->guarantee_relationships->guarantees->count, 2, 'Parent 2 has correct number of guarantees' );
436 my $patrons = [ $parent_1, $parent_2, $child_1, $child_2 ];
438 # First test: No debt
439 my ($parent1_debt, $parent2_debt, $child1_debt, $child2_debt) = (0,0,0,0);
440 _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
442 # Add debt to child_2
444 $child_2->account->add_debit({ type => 'ACCOUNT', amount => $child2_debt, interface => 'commandline' });
445 is( $child_2->account->non_issues_charges, $child2_debt, 'Debt added to Child 2' );
446 _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
449 $parent_1->account->add_debit({ type => 'ACCOUNT', amount => $parent1_debt, interface => 'commandline' });
450 is( $parent_1->account->non_issues_charges, $parent1_debt, 'Debt added to Parent 1' );
451 _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
454 $parent_2->account->add_debit({ type => 'ACCOUNT', amount => $parent2_debt, interface => 'commandline' });
455 is( $parent_2->account->non_issues_charges, $parent2_debt, 'Parent 2 owes correct amount' );
456 _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
459 $child_1->account->add_debit({ type => 'ACCOUNT', amount => $child1_debt, interface => 'commandline' });
460 is( $child_1->account->non_issues_charges, $child1_debt, 'Child 1 owes correct amount' );
461 _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
463 $schema->storage->txn_rollback;
466 sub _test_combinations {
467 my ( $patrons, $parent1_debt, $parent2_debt, $child1_debt, $child2_debt ) = @_;
468 note("Testing with parent 1 debt $parent1_debt | Parent 2 debt $parent2_debt | Child 1 debt $child1_debt | Child 2 debt $child2_debt");
470 # P1 => P1 + C1 + C2 ( - P1 ) ( + P2 )
471 # P2 => P2 + C1 + C2 ( - P2 ) ( + P1 )
472 # C1 => P1 + P2 + C1 + C2 ( - C1 )
473 # C2 => P1 + P2 + C1 + C2 ( - C2 )
475 # 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
476 for my $i ( 0 .. 7 ) {
477 my ( $only_this_guarantor, $include_guarantors, $include_this_patron )
478 = split '', sprintf( "%03b", $i );
479 note("---------------------");
480 for my $patron ( @$patrons ) {
481 if ( $only_this_guarantor
482 && !$patron->guarantee_relationships->count )
485 $patron->relationships_debt(
487 only_this_guarantor => $only_this_guarantor,
488 include_guarantors => $include_guarantors,
489 include_this_patron => $include_this_patron
493 'Koha::Exceptions::BadParameter',
494 'Exception is thrown as patron is not a guarantor';
500 if ( $patron->firstname eq 'Parent 1' ) {
501 $debt += $parent1_debt if ($include_this_patron && $include_guarantors);
502 $debt += $child1_debt + $child2_debt;
503 $debt += $parent2_debt unless ($only_this_guarantor || !$include_guarantors);
505 elsif ( $patron->firstname eq 'Parent 2' ) {
506 $debt += $parent2_debt if ($include_this_patron & $include_guarantors);
507 $debt += $child1_debt + $child2_debt;
508 $debt += $parent1_debt unless ($only_this_guarantor || !$include_guarantors);
510 elsif ( $patron->firstname eq ' Child 1' ) {
511 $debt += $child1_debt if ($include_this_patron);
512 $debt += $child2_debt;
513 $debt += $parent1_debt + $parent2_debt if ($include_guarantors);
516 $debt += $child2_debt if ($include_this_patron);
517 $debt += $child1_debt;
518 $debt += $parent1_debt + $parent2_debt if ($include_guarantors);
522 $patron->relationships_debt(
524 only_this_guarantor => $only_this_guarantor,
525 include_guarantors => $include_guarantors,
526 include_this_patron => $include_this_patron
531 . " debt of " . sprintf('%02d',$debt) . " calculated correctly for ( only_this_guarantor: $only_this_guarantor, include_guarantors: $include_guarantors, include_this_patron: $include_this_patron)"
538 subtest 'add_enrolment_fee_if_needed() tests' => sub {
542 subtest 'category has enrolment fee' => sub {
545 $schema->storage->txn_begin;
547 my $category = $builder->build_object(
549 class => 'Koha::Patron::Categories',
556 my $patron = $builder->build_object(
558 class => 'Koha::Patrons',
560 categorycode => $category->categorycode
565 my $enrollment_fee = $patron->add_enrolment_fee_if_needed();
566 is( $enrollment_fee * 1, 20, 'Enrolment fee amount is correct' );
567 my $account = $patron->account;
568 is( $patron->account->balance * 1, 20, 'Patron charged the enrolment fee' );
569 # second enrolment fee, new
570 $enrollment_fee = $patron->add_enrolment_fee_if_needed(0);
571 # third enrolment fee, renewal
572 $enrollment_fee = $patron->add_enrolment_fee_if_needed(1);
573 is( $patron->account->balance * 1, 60, 'Patron charged the enrolment fees' );
575 my @debits = $account->outstanding_debits->as_list;
576 is( scalar @debits, 3, '3 enrolment fees' );
577 is( $debits[0]->debit_type_code, 'ACCOUNT', 'Account type set correctly' );
578 is( $debits[1]->debit_type_code, 'ACCOUNT', 'Account type set correctly' );
579 is( $debits[2]->debit_type_code, 'ACCOUNT_RENEW', 'Account type set correctly' );
581 $schema->storage->txn_rollback;
584 subtest 'no enrolment fee' => sub {
588 $schema->storage->txn_begin;
590 my $category = $builder->build_object(
592 class => 'Koha::Patron::Categories',
599 my $patron = $builder->build_object(
601 class => 'Koha::Patrons',
603 categorycode => $category->categorycode
608 my $enrollment_fee = $patron->add_enrolment_fee_if_needed();
609 is( $enrollment_fee * 1, 0, 'No enrolment fee' );
610 my $account = $patron->account;
611 is( $patron->account->balance, 0, 'Patron not charged anything' );
613 my @debits = $account->outstanding_debits->as_list;
614 is( scalar @debits, 0, 'no debits' );
616 $schema->storage->txn_rollback;
620 subtest 'messaging_preferences() tests' => sub {
623 $schema->storage->txn_begin;
625 my $mtt = $builder->build_object({
626 class => 'Koha::Patron::MessagePreference::Transport::Types'
628 my $attribute = $builder->build_object({
629 class => 'Koha::Patron::MessagePreference::Attributes'
631 my $branchcode = $builder->build({
632 source => 'Branch' })->{branchcode};
633 my $letter = $builder->build_object({
634 class => 'Koha::Notice::Templates',
638 message_transport_type => $mtt->message_transport_type
642 Koha::Patron::MessagePreference::Transport->new({
643 message_attribute_id => $attribute->message_attribute_id,
644 message_transport_type => $mtt->message_transport_type,
646 letter_module => $letter->module,
647 letter_code => $letter->code,
650 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
652 my $preference = Koha::Patron::MessagePreference->new({
653 borrowernumber => $patron->borrowernumber,
654 message_attribute_id => $attribute->message_attribute_id,
656 days_in_advance => undef,
659 my $messaging_preferences = $patron->messaging_preferences();
660 is($messaging_preferences->count, 1, 'Found one preference');
662 my $messaging_preference = $messaging_preferences->next;
663 is($messaging_preference->borrowernumber, $patron->borrowernumber);
664 is($messaging_preference->message_attribute_id, $attribute->message_attribute_id);
665 is($messaging_preference->wants_digest, 0);
666 is($messaging_preference->days_in_advance, undef);
668 $schema->storage->txn_rollback;
671 subtest 'to_api() tests' => sub {
675 $schema->storage->txn_begin;
677 my $patron_class = Test::MockModule->new('Koha::Patron');
680 sub { return 'algo' }
683 my $patron = $builder->build_object(
685 class => 'Koha::Patrons',
686 value => { debarred => undef }
690 my $consumer = $builder->build_object(
692 class => 'Koha::Patrons',
696 my $restricted = $patron->to_api( { user => $consumer } )->{restricted};
697 ok( defined $restricted, 'restricted is defined' );
698 ok( !$restricted, 'debarred is undef, restricted evaluates to false' );
700 $patron->debarred( dt_from_string->add( days => 1 ) )->store->discard_changes;
701 $restricted = $patron->to_api( { user => $consumer } )->{restricted};
702 ok( defined $restricted, 'restricted is defined' );
703 ok( $restricted, 'debarred is defined, restricted evaluates to true' );
705 my $patron_json = $patron->to_api( { embed => { algo => {} }, user => $consumer } );
706 ok( exists $patron_json->{algo} );
707 is( $patron_json->{algo}, 'algo' );
709 $schema->storage->txn_rollback;
712 subtest 'login_attempts tests' => sub {
715 $schema->storage->txn_begin;
717 my $patron = $builder->build_object(
719 class => 'Koha::Patrons',
722 my $patron_info = $patron->unblessed;
724 delete $patron_info->{login_attempts};
725 my $new_patron = Koha::Patron->new($patron_info)->store;
726 is( $new_patron->discard_changes->login_attempts, 0, "login_attempts defaults to 0 as expected");
728 $schema->storage->txn_rollback;
731 subtest 'is_superlibrarian() tests' => sub {
735 $schema->storage->txn_begin;
737 my $patron = $builder->build_object(
739 class => 'Koha::Patrons',
747 is( $patron->is_superlibrarian, 0, 'Patron is not a superlibrarian and the method returns the correct value' );
749 $patron->flags(1)->store->discard_changes;
750 is( $patron->is_superlibrarian, 1, 'Patron is a superlibrarian and the method returns the correct value' );
752 $patron->flags(0)->store->discard_changes;
753 is( $patron->is_superlibrarian, 0, 'Patron is not a superlibrarian and the method returns the correct value' );
755 $schema->storage->txn_rollback;
758 subtest 'extended_attributes' => sub {
762 my $schema = Koha::Database->new->schema;
763 $schema->storage->txn_begin;
765 Koha::Patron::Attribute::Types->search->delete;
767 my $patron_1 = $builder->build_object({class=> 'Koha::Patrons'});
768 my $patron_2 = $builder->build_object({class=> 'Koha::Patrons'});
770 t::lib::Mocks::mock_userenv({ patron => $patron_1 });
772 my $attribute_type1 = Koha::Patron::Attribute::Type->new(
775 description => 'my description1',
779 my $attribute_type2 = Koha::Patron::Attribute::Type->new(
782 description => 'my description2',
784 staff_searchable => 1
788 my $new_library = $builder->build( { source => 'Branch' } );
789 my $attribute_type_limited = Koha::Patron::Attribute::Type->new(
790 { code => 'my code3', description => 'my description3' } )->store;
791 $attribute_type_limited->library_limits( [ $new_library->{branchcode} ] );
793 my $attributes_for_1 = [
795 attribute => 'my attribute1',
796 code => $attribute_type1->code(),
799 attribute => 'my attribute2',
800 code => $attribute_type2->code(),
803 attribute => 'my attribute limited',
804 code => $attribute_type_limited->code(),
808 my $attributes_for_2 = [
810 attribute => 'my attribute12',
811 code => $attribute_type1->code(),
814 attribute => 'my attribute limited 2',
815 code => $attribute_type_limited->code(),
819 my $extended_attributes = $patron_1->extended_attributes;
820 is( ref($extended_attributes), 'Koha::Patron::Attributes', 'Koha::Patron->extended_attributes must return a Koha::Patron::Attribute set' );
821 is( $extended_attributes->count, 0, 'There should not be attribute yet');
823 $patron_1->extended_attributes->filter_by_branch_limitations->delete;
824 $patron_2->extended_attributes->filter_by_branch_limitations->delete;
825 $patron_1->extended_attributes($attributes_for_1);
826 $patron_2->extended_attributes($attributes_for_2);
828 my $extended_attributes_for_1 = $patron_1->extended_attributes;
829 is( $extended_attributes_for_1->count, 3, 'There should be 3 attributes now for patron 1');
831 my $extended_attributes_for_2 = $patron_2->extended_attributes;
832 is( $extended_attributes_for_2->count, 2, 'There should be 2 attributes now for patron 2');
834 my $attribute_12 = $extended_attributes_for_2->search({ code => $attribute_type1->code })->next;
835 is( $attribute_12->attribute, 'my attribute12', 'search by code should return the correct attribute' );
837 $attribute_12 = $patron_2->get_extended_attribute( $attribute_type1->code );
838 is( $attribute_12->attribute, 'my attribute12', 'Koha::Patron->get_extended_attribute should return the correct attribute value' );
840 my $expected_attributes_for_2 = [
842 code => $attribute_type1->code(),
843 attribute => 'my attribute12',
846 code => $attribute_type_limited->code(),
847 attribute => 'my attribute limited 2',
850 # Sorting them by code
851 $expected_attributes_for_2 = [ sort { $a->{code} cmp $b->{code} } @$expected_attributes_for_2 ];
852 my @extended_attributes_for_2 = $extended_attributes_for_2->as_list;
857 code => $extended_attributes_for_2[0]->code,
858 attribute => $extended_attributes_for_2[0]->attribute
861 code => $extended_attributes_for_2[1]->code,
862 attribute => $extended_attributes_for_2[1]->attribute
865 $expected_attributes_for_2
868 # TODO - What about multiple? POD explains the problem
869 my $non_existent = $patron_2->get_extended_attribute( 'not_exist' );
870 is( $non_existent, undef, 'Koha::Patron->get_extended_attribute must return undef if the attribute does not exist' );
872 # Test branch limitations
873 t::lib::Mocks::mock_userenv({ patron => $patron_2 });
875 $extended_attributes_for_1 = $patron_1->extended_attributes;
876 is( $extended_attributes_for_1->count, 3, 'There should be 2 attributes for patron 1, the limited one should be returned');
879 $extended_attributes_for_1 = $patron_1->extended_attributes->filter_by_branch_limitations;
880 is( $extended_attributes_for_1->count, 2, 'There should be 2 attributes for patron 1, the limited one should be returned');
883 my $limited_value = $patron_1->get_extended_attribute( $attribute_type_limited->code );
884 is( $limited_value->attribute, 'my attribute limited', );
886 ## Do we need a filtered?
887 #$limited_value = $patron_1->get_extended_attribute( $attribute_type_limited->code );
888 #is( $limited_value, undef, );
890 $schema->storage->txn_rollback;
892 subtest 'non-repeatable attributes tests' => sub {
896 $schema->storage->txn_begin;
897 Koha::Patron::Attribute::Types->search->delete;
899 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
900 my $attribute_type = $builder->build_object(
902 class => 'Koha::Patron::Attribute::Types',
903 value => { repeatable => 0 }
907 is( $patron->extended_attributes->count, 0, 'Patron has no extended attributes' );
911 $patron->extended_attributes(
913 { code => $attribute_type->code, attribute => 'a' },
914 { code => $attribute_type->code, attribute => 'b' }
918 'Koha::Exceptions::Patron::Attribute::NonRepeatable',
919 'Exception thrown on non-repeatable attribute';
921 is( $patron->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
923 $schema->storage->txn_rollback;
927 subtest 'unique attributes tests' => sub {
931 $schema->storage->txn_begin;
932 Koha::Patron::Attribute::Types->search->delete;
934 my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' });
935 my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
937 my $attribute_type_1 = $builder->build_object(
939 class => 'Koha::Patron::Attribute::Types',
940 value => { unique_id => 1 }
944 my $attribute_type_2 = $builder->build_object(
946 class => 'Koha::Patron::Attribute::Types',
947 value => { unique_id => 0 }
951 is( $patron_1->extended_attributes->count, 0, 'patron_1 has no extended attributes' );
952 is( $patron_2->extended_attributes->count, 0, 'patron_2 has no extended attributes' );
954 $patron_1->extended_attributes(
956 { code => $attribute_type_1->code, attribute => 'a' },
957 { code => $attribute_type_2->code, attribute => 'a' }
963 $patron_2->extended_attributes(
965 { code => $attribute_type_1->code, attribute => 'a' },
966 { code => $attribute_type_2->code, attribute => 'a' }
970 'Koha::Exceptions::Patron::Attribute::UniqueIDConstraint',
971 'Exception thrown on unique attribute';
973 is( $patron_1->extended_attributes->count, 2, 'Extended attributes stored' );
974 is( $patron_2->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
976 $schema->storage->txn_rollback;
980 subtest 'invalid type attributes tests' => sub {
984 $schema->storage->txn_begin;
985 Koha::Patron::Attribute::Types->search->delete;
987 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
989 my $attribute_type_1 = $builder->build_object(
991 class => 'Koha::Patron::Attribute::Types',
992 value => { repeatable => 0 }
996 my $attribute_type_2 = $builder->build_object(
998 class => 'Koha::Patron::Attribute::Types'
1002 my $type_2 = $attribute_type_2->code;
1003 $attribute_type_2->delete;
1005 is( $patron->extended_attributes->count, 0, 'Patron has no extended attributes' );
1009 $patron->extended_attributes(
1011 { code => $attribute_type_1->code, attribute => 'a' },
1012 { code => $attribute_type_2->code, attribute => 'b' }
1016 'Koha::Exceptions::Patron::Attribute::InvalidType',
1017 'Exception thrown on invalid attribute type';
1019 is( $patron->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
1021 $schema->storage->txn_rollback;
1025 subtest 'globally mandatory attributes tests' => sub {
1029 $schema->storage->txn_begin;
1030 Koha::Patron::Attribute::Types->search->delete;
1032 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1034 my $attribute_type_1 = $builder->build_object(
1036 class => 'Koha::Patron::Attribute::Types',
1037 value => { mandatory => 1, class => 'a', category_code => undef }
1041 my $attribute_type_2 = $builder->build_object(
1043 class => 'Koha::Patron::Attribute::Types',
1044 value => { mandatory => 0, class => 'a', category_code => undef }
1048 is( $patron->extended_attributes->count, 0, 'Patron has no extended attributes' );
1052 $patron->extended_attributes(
1054 { code => $attribute_type_2->code, attribute => 'b' }
1058 'Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute',
1059 'Exception thrown on missing mandatory attribute type';
1061 is( $@->type, $attribute_type_1->code, 'Exception parameters are correct' );
1063 is( $patron->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
1065 $patron->extended_attributes(
1067 { code => $attribute_type_1->code, attribute => 'b' }
1071 is( $patron->extended_attributes->count, 1, 'Extended attributes succeeded' );
1073 $schema->storage->txn_rollback;
1077 subtest 'limited category mandatory attributes tests' => sub {
1081 $schema->storage->txn_begin;
1082 Koha::Patron::Attribute::Types->search->delete;
1084 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1086 my $attribute_type_1 = $builder->build_object(
1088 class => 'Koha::Patron::Attribute::Types',
1089 value => { mandatory => 1, class => 'a', category_code => $patron->categorycode }
1093 $patron->extended_attributes(
1095 { code => $attribute_type_1->code, attribute => 'a' }
1099 is( $patron->extended_attributes->count, 1, 'Extended attributes succeeded' );
1101 $patron = $builder->build_object({ class => 'Koha::Patrons' });
1102 # new patron, new category - they shouldn't be required to have any attributes
1105 ok( $patron->extended_attributes([]), "We can set no attributes, mandatory attribute for other category not required");
1114 subtest 'can_log_into() tests' => sub {
1118 $schema->storage->txn_begin;
1120 my $patron = $builder->build_object(
1122 class => 'Koha::Patrons',
1128 my $library = $builder->build_object({ class => 'Koha::Libraries' });
1130 t::lib::Mocks::mock_preference('IndependentBranches', 1);
1132 ok( $patron->can_log_into( $patron->library ), 'Patron can log into its own library' );
1133 ok( !$patron->can_log_into( $library ), 'Patron cannot log into different library, IndependentBranches on' );
1135 # make it a superlibrarian
1136 $patron->set({ flags => 1 })->store->discard_changes;
1137 ok( $patron->can_log_into( $library ), 'Superlibrarian can log into different library, IndependentBranches on' );
1139 t::lib::Mocks::mock_preference('IndependentBranches', 0);
1141 # No special permissions
1142 $patron->set({ flags => undef })->store->discard_changes;
1143 ok( $patron->can_log_into( $patron->library ), 'Patron can log into its own library' );
1144 ok( $patron->can_log_into( $library ), 'Patron can log into any library' );
1146 $schema->storage->txn_rollback;
1149 subtest 'can_request_article() tests' => sub {
1153 $schema->storage->txn_begin;
1155 t::lib::Mocks::mock_preference( 'ArticleRequests', 1 );
1157 my $item = $builder->build_sample_item;
1159 my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
1160 my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } );
1161 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1163 t::lib::Mocks::mock_userenv( { branchcode => $library_2->id } );
1165 Koha::CirculationRules->set_rule(
1167 categorycode => undef,
1168 branchcode => $library_1->id,
1169 rule_name => 'open_article_requests_limit',
1174 $builder->build_object(
1176 class => 'Koha::ArticleRequests',
1177 value => { status => 'REQUESTED', borrowernumber => $patron->id }
1180 $builder->build_object(
1182 class => 'Koha::ArticleRequests',
1183 value => { status => 'PENDING', borrowernumber => $patron->id }
1186 $builder->build_object(
1188 class => 'Koha::ArticleRequests',
1189 value => { status => 'PROCESSING', borrowernumber => $patron->id }
1192 $builder->build_object(
1194 class => 'Koha::ArticleRequests',
1195 value => { status => 'CANCELED', borrowernumber => $patron->id }
1200 $patron->can_request_article( $library_1->id ),
1201 '3 current requests, 4 is the limit: allowed'
1204 # Completed request, same day
1205 my $completed = $builder->build_object(
1207 class => 'Koha::ArticleRequests',
1209 status => 'COMPLETED',
1210 borrowernumber => $patron->id
1215 ok( !$patron->can_request_article( $library_1->id ),
1216 '3 current requests and a completed one the same day: denied' );
1218 $completed->updated_on(
1219 dt_from_string->add( days => -1 )->set(
1226 ok( $patron->can_request_article( $library_1->id ),
1227 '3 current requests and a completed one the day before: allowed' );
1229 Koha::CirculationRules->set_rule(
1231 categorycode => undef,
1232 branchcode => $library_2->id,
1233 rule_name => 'open_article_requests_limit',
1238 ok( !$patron->can_request_article,
1239 'Not passing the library_id param makes it fallback to userenv: denied'
1242 $schema->storage->txn_rollback;
1245 subtest 'article_requests() tests' => sub {
1249 $schema->storage->txn_begin;
1251 my $library = $builder->build_object({ class => 'Koha::Libraries' });
1252 t::lib::Mocks::mock_userenv( { branchcode => $library->id } );
1254 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1256 my $article_requests = $patron->article_requests;
1257 is( ref($article_requests), 'Koha::ArticleRequests',
1258 'In scalar context, type is correct' );
1259 is( $article_requests->count, 0, 'No article requests' );
1261 foreach my $i ( 0 .. 3 ) {
1263 my $item = $builder->build_sample_item;
1265 Koha::ArticleRequest->new(
1267 borrowernumber => $patron->id,
1268 biblionumber => $item->biblionumber,
1269 itemnumber => $item->id,
1275 $article_requests = $patron->article_requests;
1276 is( $article_requests->count, 4, '4 article requests' );
1278 $schema->storage->txn_rollback;
1282 subtest 'can_patron_change_staff_only_lists() tests' => sub {
1286 $schema->storage->txn_begin;
1288 # make a user with no special permissions
1289 my $patron = $builder->build_object(
1291 class => 'Koha::Patrons',
1297 is( $patron->can_patron_change_staff_only_lists(), 0, 'Patron without permissions cannot change staff only lists');
1299 # make it a 'Catalogue' permission
1300 $patron->set({ flags => 4 })->store->discard_changes;
1301 is( $patron->can_patron_change_staff_only_lists(), 1, 'Catalogue patron can change staff only lists');
1304 # make it a superlibrarian
1305 $patron->set({ flags => 1 })->store->discard_changes;
1306 is( $patron->can_patron_change_staff_only_lists(), 1, 'Superlibrarian patron can change staff only lists');
1308 $schema->storage->txn_rollback;
1311 subtest 'can_patron_change_permitted_staff_lists() tests' => sub {
1315 $schema->storage->txn_begin;
1317 # make a user with no special permissions
1318 my $patron = $builder->build_object(
1320 class => 'Koha::Patrons',
1326 is( $patron->can_patron_change_permitted_staff_lists(), 0, 'Patron without permissions cannot change permitted staff lists');
1328 # make it a 'Catalogue' permission
1329 $patron->set({ flags => 4 })->store->discard_changes;
1330 is( $patron->can_patron_change_permitted_staff_lists(), 0, 'Catalogue patron cannot change permitted staff lists');
1332 # make it a 'Catalogue' permission and 'edit_public_list_contents' sub-permission
1333 $patron->set({ flags => 4 })->store->discard_changes;
1336 source => 'UserPermission',
1338 borrowernumber => $patron->borrowernumber,
1339 module_bit => 20, # lists
1340 code => 'edit_public_list_contents',
1344 is( $patron->can_patron_change_permitted_staff_lists(), 1, 'Catalogue and "edit_public_list_contents" patron can change permitted staff lists');
1346 # make it a superlibrarian
1347 $patron->set({ flags => 1 })->store->discard_changes;
1348 is( $patron->can_patron_change_permitted_staff_lists(), 1, 'Superlibrarian patron can change permitted staff lists');
1350 $schema->storage->txn_rollback;
1353 subtest 'password expiration tests' => sub {
1357 $schema->storage->txn_begin;
1358 my $date = dt_from_string();
1359 my $category = $builder->build_object({ class => 'Koha::Patron::Categories', value => {
1360 password_expiry_days => 10,
1361 require_strong_password => 0,
1364 my $patron = $builder->build_object({ class=> 'Koha::Patrons', value => {
1365 categorycode => $category->categorycode,
1370 $patron->delete()->store()->discard_changes(); # Make sure we are storing a 'new' patron
1372 is( $patron->password_expiration_date(), $date->add( days => 10 )->ymd() , "Password expiration date set correctly on patron creation");
1374 $patron = $builder->build_object({ class => 'Koha::Patrons', value => {
1375 categorycode => $category->categorycode,
1379 $patron->delete()->store()->discard_changes();
1381 is( $patron->password_expiration_date(), undef, "Password expiration date is not set if patron does not have a password");
1383 $category->password_expiry_days(undef)->store();
1384 $patron = $builder->build_object({ class => 'Koha::Patrons', value => {
1385 categorycode => $category->categorycode
1388 $patron->delete()->store()->discard_changes();
1389 is( $patron->password_expiration_date(), undef, "Password expiration date is not set if category does not have expiry days set");
1391 $schema->storage->txn_rollback;
1393 subtest 'password_expired' => sub {
1397 $schema->storage->txn_begin;
1398 my $date = dt_from_string();
1399 $patron = $builder->build_object({ class => 'Koha::Patrons', value => {
1400 password_expiration_date => undef
1403 is( $patron->password_expired, 0, "Patron with no password expiration date, password not expired");
1404 $patron->password_expiration_date( $date )->store;
1405 $patron->discard_changes();
1406 is( $patron->password_expired, 1, "Patron with password expiration date of today, password expired");
1407 $date->subtract( days => 1 );
1408 $patron->password_expiration_date( $date )->store;
1409 $patron->discard_changes();
1410 is( $patron->password_expired, 1, "Patron with password expiration date in past, password expired");
1412 $schema->storage->txn_rollback;
1415 subtest 'set_password' => sub {
1419 $schema->storage->txn_begin;
1421 my $date = dt_from_string();
1422 my $category = $builder->build_object({ class => 'Koha::Patron::Categories', value => {
1423 password_expiry_days => 10
1426 my $patron = $builder->build_object({ class => 'Koha::Patrons', value => {
1427 categorycode => $category->categorycode,
1428 password_expiration_date => $date->subtract( days => 1 )
1431 is( $patron->password_expired, 1, "Patron password is expired");
1433 $date = dt_from_string();
1434 $patron->set_password({ password => "kitten", skip_validation => 1 })->discard_changes();
1435 is( $patron->password_expired, 0, "Patron password no longer expired when new password set");
1436 is( $patron->password_expiration_date(), $date->add( days => 10 )->ymd(), "Password expiration date set correctly on patron creation");
1439 $category->password_expiry_days( undef )->store();
1440 $patron->set_password({ password => "puppies", skip_validation => 1 })->discard_changes();
1441 is( $patron->password_expiration_date(), undef, "Password expiration date is unset if category does not have expiry days");
1443 $schema->storage->txn_rollback;
1448 subtest 'safe_to_delete() tests' => sub {
1452 $schema->storage->txn_begin;
1454 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1456 ## Make it the anonymous
1457 t::lib::Mocks::mock_preference( 'AnonymousPatron', $patron->id );
1459 ok( !$patron->safe_to_delete, 'Cannot delete, it is the anonymous patron' );
1460 my $message = $patron->safe_to_delete->messages->[0];
1461 is( $message->type, 'error', 'Type is error' );
1462 is( $message->message, 'is_anonymous_patron', 'Cannot delete, it is the anonymous patron' );
1464 t::lib::Mocks::mock_preference( 'AnonymousPatron', 0 );
1466 ## Make it have a checkout
1467 my $checkout = $builder->build_object(
1469 class => 'Koha::Checkouts',
1470 value => { borrowernumber => $patron->id }
1474 ok( !$patron->safe_to_delete, 'Cannot delete, has checkouts' );
1475 $message = $patron->safe_to_delete->messages->[0];
1476 is( $message->type, 'error', 'Type is error' );
1477 is( $message->message, 'has_checkouts', 'Cannot delete, has checkouts' );
1481 ## Make it have a guarantee
1482 t::lib::Mocks::mock_preference( 'borrowerRelationship', 'parent' );
1483 $builder->build_object({ class => 'Koha::Patrons' })
1484 ->add_guarantor({ guarantor_id => $patron->id, relationship => 'parent' });
1486 ok( !$patron->safe_to_delete, 'Cannot delete, has guarantees' );
1487 $message = $patron->safe_to_delete->messages->[0];
1488 is( $message->type, 'error', 'Type is error' );
1489 is( $message->message, 'has_guarantees', 'Cannot delete, has guarantees' );
1492 $patron->guarantee_relationships->delete;
1494 ## Make it have debt
1495 my $debit = $patron->account->add_debit({ amount => 10, interface => 'intranet', type => 'MANUAL' });
1497 ok( !$patron->safe_to_delete, 'Cannot delete, has debt' );
1498 $message = $patron->safe_to_delete->messages->[0];
1499 is( $message->type, 'error', 'Type is error' );
1500 is( $message->message, 'has_debt', 'Cannot delete, has debt' );
1502 my $manager = $builder->build_object( { class => 'Koha::Patrons' } );
1503 t::lib::Mocks::mock_userenv( { borrowernumber => $manager->id } );
1504 $patron->account->pay({ amount => 10, debits => [ $debit ] });
1506 ## Make it protected
1507 $patron->protected(1);
1508 ok( !$patron->safe_to_delete, 'Cannot delete, is protected' );
1509 $message = $patron->safe_to_delete->messages->[0];
1510 is( $message->type, 'error', 'Type is error' );
1511 is( $message->message, 'is_protected', 'Cannot delete, is protected' );
1512 $patron->protected(0);
1515 ok( $patron->safe_to_delete, 'Can delete, all conditions met' );
1516 my $messages = $patron->safe_to_delete->messages;
1517 is_deeply( $messages, [], 'Patron can be deleted, no messages' );
1519 $schema->storage->txn_rollback;
1522 subtest 'article_request_fee() tests' => sub {
1526 $schema->storage->txn_begin;
1528 # Cleanup, to avoid interference
1529 Koha::CirculationRules->search( { rule_name => 'article_request_fee' } )->delete;
1531 t::lib::Mocks::mock_preference( 'ArticleRequests', 1 );
1533 my $item = $builder->build_sample_item;
1535 my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
1536 my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } );
1537 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1539 # Rule that should never be picked, because the patron's category is always picked
1540 Koha::CirculationRules->set_rule(
1541 { categorycode => undef,
1542 branchcode => undef,
1543 rule_name => 'article_request_fee',
1548 is( $patron->article_request_fee( { library_id => $library_2->id } ), 1, 'library_id used correctly' );
1550 Koha::CirculationRules->set_rule(
1551 { categorycode => $patron->categorycode,
1552 branchcode => undef,
1553 rule_name => 'article_request_fee',
1558 Koha::CirculationRules->set_rule(
1559 { categorycode => $patron->categorycode,
1560 branchcode => $library_1->id,
1561 rule_name => 'article_request_fee',
1566 is( $patron->article_request_fee( { library_id => $library_2->id } ), 2, 'library_id used correctly' );
1568 t::lib::Mocks::mock_userenv( { branchcode => $library_1->id } );
1570 is( $patron->article_request_fee(), 3, 'env used correctly' );
1572 $schema->storage->txn_rollback;
1575 subtest 'add_article_request_fee_if_needed() tests' => sub {
1579 $schema->storage->txn_begin;
1583 my $patron_mock = Test::MockModule->new('Koha::Patron');
1584 $patron_mock->mock( 'article_request_fee', sub { return $amount; } );
1586 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1588 is( $patron->article_request_fee, $amount, 'article_request_fee mocked' );
1590 my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
1591 my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } );
1592 my $staff = $builder->build_object( { class => 'Koha::Patrons' } );
1593 my $item = $builder->build_sample_item;
1595 t::lib::Mocks::mock_userenv(
1596 { branchcode => $library_1->id, patron => $staff } );
1598 my $debit = $patron->add_article_request_fee_if_needed();
1599 is( $debit, undef, 'No fee, no debit line' );
1604 $debit = $patron->add_article_request_fee_if_needed({ item_id => $item->id });
1605 is( ref($debit), 'Koha::Account::Line', 'Debit object type correct' );
1606 is( $debit->amount, $amount,
1607 'amount set to $patron->article_request_fee value' );
1608 is( $debit->manager_id, $staff->id,
1609 'manager_id set to userenv session user' );
1610 is( $debit->branchcode, $library_1->id,
1611 'branchcode set to userenv session library' );
1612 is( $debit->debit_type_code, 'ARTICLE_REQUEST',
1613 'debit_type_code set correctly' );
1614 is( $debit->itemnumber, $item->id,
1615 'itemnumber set correctly' );
1619 $debit = $patron->add_article_request_fee_if_needed({ library_id => $library_2->id });
1620 is( ref($debit), 'Koha::Account::Line', 'Debit object type correct' );
1621 is( $debit->amount, $amount,
1622 'amount set to $patron->article_request_fee value' );
1623 is( $debit->branchcode, $library_2->id,
1624 'branchcode set to userenv session library' );
1625 is( $debit->itemnumber, undef,
1626 'itemnumber set correctly to undef' );
1628 $schema->storage->txn_rollback;
1631 subtest 'messages' => sub {
1634 $schema->storage->txn_begin;
1636 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1637 my $messages = $patron->messages;
1638 is( $messages->count, 0, "No message yet" );
1639 my $message_1 = $builder->build_object(
1641 class => 'Koha::Patron::Messages',
1642 value => { borrowernumber => $patron->borrowernumber }
1645 my $message_2 = $builder->build_object(
1647 class => 'Koha::Patron::Messages',
1648 value => { borrowernumber => $patron->borrowernumber }
1652 $messages = $patron->messages;
1653 is( $messages->count, 2, "There are two messages for this patron" );
1654 is( $messages->next->message, $message_1->message );
1655 is( $messages->next->message, $message_2->message );
1656 $schema->storage->txn_rollback;
1659 subtest 'recalls() tests' => sub {
1663 $schema->storage->txn_begin;
1665 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1666 my $biblio1 = $builder->build_object({ class => 'Koha::Biblios' });
1667 my $item1 = $builder->build_object({ class => 'Koha::Items' }, { value => { biblionumber => $biblio1->biblionumber } });
1668 my $biblio2 = $builder->build_object({ class => 'Koha::Biblios' });
1669 my $item2 = $builder->build_object({ class => 'Koha::Items' }, { value => { biblionumber => $biblio2->biblionumber } });
1672 { biblio_id => $biblio1->biblionumber,
1673 patron_id => $patron->borrowernumber,
1674 item_id => $item1->itemnumber,
1675 pickup_library_id => $patron->branchcode,
1676 created_date => \'NOW()',
1681 { biblio_id => $biblio2->biblionumber,
1682 patron_id => $patron->borrowernumber,
1683 item_id => $item2->itemnumber,
1684 pickup_library_id => $patron->branchcode,
1685 created_date => \'NOW()',
1690 { biblio_id => $biblio1->biblionumber,
1691 patron_id => $patron->borrowernumber,
1693 pickup_library_id => $patron->branchcode,
1694 created_date => \'NOW()',
1698 my $recall = Koha::Recall->new(
1699 { biblio_id => $biblio1->biblionumber,
1700 patron_id => $patron->borrowernumber,
1702 pickup_library_id => $patron->branchcode,
1703 created_date => \'NOW()',
1707 $recall->set_cancelled;
1709 is( $patron->recalls->count, 4, "Correctly gets this patron's recalls" );
1710 is( $patron->recalls->filter_by_current->count, 3, "Correctly gets this patron's active recalls" );
1711 is( $patron->recalls->filter_by_current->search( { biblio_id => $biblio1->biblionumber } )->count, 2, "Correctly gets this patron's active recalls on a specific biblio" );
1713 $schema->storage->txn_rollback;
1716 subtest 'encode_secret and decoded_secret' => sub {
1718 $schema->storage->txn_begin;
1720 t::lib::Mocks::mock_config('encryption_key', 't0P_secret');
1722 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1723 is( $patron->decoded_secret, undef, 'TestBuilder does not initialize it' );
1724 $patron->secret(q{});
1725 is( $patron->decoded_secret, q{}, 'Empty string case' );
1727 $patron->encode_secret('encrypt_me'); # Note: lazy testing; should be base32 string normally.
1728 is( length($patron->secret) > 0, 1, 'Secret length' );
1729 isnt( $patron->secret, 'encrypt_me', 'Encrypted column' );
1730 is( $patron->decoded_secret, 'encrypt_me', 'Decrypted column' );
1732 $schema->storage->txn_rollback;
1735 subtest 'notify_library_of_registration()' => sub {
1739 $schema->storage->txn_begin;
1740 my $dbh = C4::Context->dbh;
1742 my $library = $builder->build_object(
1744 class => 'Koha::Libraries',
1746 branchemail => 'from@mybranch.com',
1747 branchreplyto => 'to@mybranch.com'
1751 my $patron = $builder->build_object(
1753 class => 'Koha::Patrons',
1755 branchcode => $library->branchcode
1760 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'root@localhost' );
1761 t::lib::Mocks::mock_preference( 'EmailAddressForPatronRegistrations', 'library@localhost' );
1763 # Test when EmailPatronRegistrations equals BranchEmailAddress
1764 t::lib::Mocks::mock_preference( 'EmailPatronRegistrations', 'BranchEmailAddress' );
1765 is( $patron->notify_library_of_registration(C4::Context->preference('EmailPatronRegistrations')), 1, 'OPAC_REG email is queued if EmailPatronRegistration syspref equals BranchEmailAddress');
1766 my $sth = $dbh->prepare("SELECT to_address FROM message_queue where borrowernumber = ?");
1767 $sth->execute( $patron->borrowernumber );
1768 my $to_address = $sth->fetchrow_array;
1769 is( $to_address, 'to@mybranch.com', 'OPAC_REG email queued to go to branchreplyto address when EmailPatronRegistration equals BranchEmailAddress' );
1770 $dbh->do(q|DELETE FROM message_queue|);
1772 # Test when EmailPatronRegistrations equals EmailAddressForPatronRegistrations
1773 t::lib::Mocks::mock_preference( 'EmailPatronRegistrations', 'EmailAddressForPatronRegistrations' );
1774 is( $patron->notify_library_of_registration(C4::Context->preference('EmailPatronRegistrations')), 1, 'OPAC_REG email is queued if EmailPatronRegistration syspref equals EmailAddressForPatronRegistrations');
1775 $sth->execute( $patron->borrowernumber );
1776 $to_address = $sth->fetchrow_array;
1777 is( $to_address, 'library@localhost', 'OPAC_REG email queued to go to EmailAddressForPatronRegistrations syspref when EmailPatronRegistration equals EmailAddressForPatronRegistrations' );
1778 $dbh->do(q|DELETE FROM message_queue|);
1780 # Test when EmailPatronRegistrations equals KohaAdminEmailAddress
1781 t::lib::Mocks::mock_preference( 'EmailPatronRegistrations', 'KohaAdminEmailAddress' );
1782 t::lib::Mocks::mock_preference( 'ReplyToDefault', 'root@localhost' ); # FIXME Remove localhost
1783 is( $patron->notify_library_of_registration(C4::Context->preference('EmailPatronRegistrations')), 1, 'OPAC_REG email is queued if EmailPatronRegistration syspref equals KohaAdminEmailAddress');
1784 $sth->execute( $patron->borrowernumber );
1785 $to_address = $sth->fetchrow_array;
1786 is( $to_address, 'root@localhost', 'OPAC_REG email queued to go to KohaAdminEmailAddress syspref when EmailPatronRegistration equals KohaAdminEmailAddress' );
1787 $dbh->do(q|DELETE FROM message_queue|);
1789 $schema->storage->txn_rollback;
1792 subtest 'notice_email_address' => sub {
1794 $schema->storage->txn_begin;
1796 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
1798 t::lib::Mocks::mock_preference( 'EmailFieldPrecedence', 'email|emailpro' );
1799 t::lib::Mocks::mock_preference( 'EmailFieldPrimary', 'OFF' );
1800 is ($patron->notice_email_address, $patron->email, "Koha::Patron->notice_email_address returns correct value when EmailFieldPrimary is off");
1802 t::lib::Mocks::mock_preference( 'EmailFieldPrimary', 'emailpro' );
1803 is ($patron->notice_email_address, $patron->emailpro, "Koha::Patron->notice_email_address returns correct value when EmailFieldPrimary is emailpro");
1806 $schema->storage->txn_rollback;
1809 subtest 'first_valid_email_address' => sub {
1811 $schema->storage->txn_begin;
1813 my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { emailpro => ''}});
1815 t::lib::Mocks::mock_preference( 'EmailFieldPrecedence', 'emailpro|email' );
1816 is ($patron->first_valid_email_address, $patron->email, "Koha::Patron->first_valid_email_address returns correct value when EmailFieldPrecedence is 'emailpro|email' and emailpro is empty");
1819 $schema->storage->txn_rollback;
1822 subtest 'get_savings tests' => sub {
1826 $schema->storage->txn_begin;
1828 my $library = $builder->build_object({ class => 'Koha::Libraries' });
1829 my $patron = $builder->build_object({ class => 'Koha::Patrons' }, { value => { branchcode => $library->branchcode } });
1831 t::lib::Mocks::mock_userenv({ patron => $patron, branchcode => $library->branchcode });
1833 my $biblio = $builder->build_sample_biblio;
1834 my $item1 = $builder->build_sample_item(
1836 biblionumber => $biblio->biblionumber,
1837 library => $library->branchcode,
1838 replacementprice => rand(20),
1841 my $item2 = $builder->build_sample_item(
1843 biblionumber => $biblio->biblionumber,
1844 library => $library->branchcode,
1845 replacementprice => rand(20),
1849 is( $patron->get_savings, 0, 'No checkouts, no savings' );
1851 # Add an old checkout with deleted itemnumber
1852 $builder->build_object({ class => 'Koha::Old::Checkouts', value => { itemnumber => undef, borrowernumber => $patron->id } });
1854 is( $patron->get_savings, 0, 'No checkouts with itemnumber, no savings' );
1856 AddIssue( $patron, $item1->barcode );
1857 AddIssue( $patron, $item2->barcode );
1859 my $savings = $patron->get_savings;
1860 is( $savings + 0, $item1->replacementprice + $item2->replacementprice, "Savings correctly calculated from current issues" );
1862 AddReturn( $item2->barcode, $item2->homebranch );
1864 $savings = $patron->get_savings;
1865 is( $savings + 0, $item1->replacementprice + $item2->replacementprice, "Savings correctly calculated from current and old issues" );
1867 $schema->storage->txn_rollback;
1870 subtest 'update privacy tests' => sub {
1871 $schema->storage->txn_begin;
1875 $schema->storage->txn_begin;
1876 my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { privacy => 1 } });
1878 my $old_checkout = $builder->build_object({ class => 'Koha::Old::Checkouts', value => { borrowernumber => $patron->id } });
1880 t::lib::Mocks::mock_preference( 'AnonymousPatron', '0' );
1882 $patron->privacy(2); #set to never
1884 throws_ok{ $patron->store } 'Koha::Exceptions::Patron::FailedAnonymizing', 'We throw an exception when anonymizing fails';
1886 $old_checkout->discard_changes; #refresh from db
1887 $patron->discard_changes;
1889 is( $old_checkout->borrowernumber, $patron->id, "When anonymizing fails, we don't clear the checkouts");
1890 is( $patron->privacy(), 1, "When anonymizing fails, we don't chaneg the privacy");
1892 my $anon_patron = $builder->build_object({ class => 'Koha::Patrons'});
1893 t::lib::Mocks::mock_preference( 'AnonymousPatron', $anon_patron->id );
1895 $patron->privacy(2)->store(); #set to never
1897 $old_checkout->discard_changes; #refresh from db
1898 $patron->discard_changes;
1900 is( $old_checkout->borrowernumber, $anon_patron->id, "Checkout is successfully anonymized");
1901 is( $patron->privacy(), 2, "Patron privacy is successfully updated");
1903 $schema->storage->txn_rollback;
1906 subtest 'alert_subscriptions tests' => sub {
1909 $schema->storage->txn_begin;
1911 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1913 my $subscription1 = $builder->build_object( { class => 'Koha::Subscriptions' } );
1914 $subscription1->add_subscriber($patron);
1916 my $subscription2 = $builder->build_object( { class => 'Koha::Subscriptions' } );
1917 $subscription2->add_subscriber($patron);
1919 my @subscriptions = $patron->alert_subscriptions->as_list;
1921 is( @subscriptions, 2, "Number of patron's subscribed alerts successfully fetched" );
1922 is( $subscriptions[0]->subscriptionid, $subscription1->subscriptionid, "First subscribed alert is correct" );
1923 is( $subscriptions[1]->subscriptionid, $subscription2->subscriptionid, "Second subscribed alert is correct" );
1925 $patron->discard_changes;
1926 $schema->storage->txn_rollback;
1929 subtest 'test patron_consent' => sub {
1931 $schema->storage->txn_begin;
1933 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1934 throws_ok { $patron->consent } 'Koha::Exceptions::MissingParameter', 'missing type';
1936 my $consent = $patron->consent('GDPR_PROCESSING');
1937 is( ref $consent, 'Koha::Patron::Consent', 'return type check' );
1938 $consent->given_on('2021-02-03')->store;
1940 is( $patron->consent('GDPR_PROCESSING')->given_on, '2021-02-03 00:00:00', 'check date' );
1942 is( $patron->consent('NOT_EXIST')->refused_on, undef, 'New empty object for new type' );
1944 $schema->storage->txn_rollback;
1947 subtest 'update_lastseen tests' => sub {
1950 $schema->storage->txn_begin;
1952 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
1953 my $userid = $patron->userid;
1955 $patron->lastseen(undef);
1958 my $cache = Koha::Caches->get_instance();
1959 my $cache_key = "track_activity_" . $patron->borrowernumber;
1960 $cache->clear_from_cache($cache_key);
1962 t::lib::Mocks::mock_preference(
1963 'TrackLastPatronActivityTriggers',
1964 'login,connection,check_in,check_out,renewal,hold,article'
1967 is( $patron->lastseen, undef, 'Patron should have not last seen when newly created' );
1969 my $now = dt_from_string();
1970 Time::Fake->offset( $now->epoch );
1972 $patron->update_lastseen('login');
1973 $patron->_result()->discard_changes();
1974 isnt( $patron->lastseen, undef, 'Patron should have last seen set when TrackLastPatronActivityTriggers contains values' );
1975 my $last_seen = $patron->lastseen;
1977 Time::Fake->offset( $now->epoch + 5 );
1979 # Test that lastseen isn't updated more than once in a day (regardless of activity passed)
1980 $patron->update_lastseen('login');
1981 $patron->_result()->discard_changes();
1982 is( $patron->lastseen, $last_seen, 'Patron last seen should still be unchanged after a login' );
1983 $patron->update_lastseen('connection');
1984 $patron->_result()->discard_changes();
1985 is( $patron->lastseen, $last_seen, 'Patron last seen should still be unchanged after a SIP/ILSDI connection' );
1986 $patron->update_lastseen('check_out');
1987 $patron->_result()->discard_changes();
1988 is( $patron->lastseen, $last_seen, 'Patron last seen should still be unchanged after a check out' );
1989 $patron->update_lastseen('check_in');
1990 $patron->_result()->discard_changes();
1991 is( $patron->lastseen, $last_seen, 'Patron last seen should still be unchanged after a check in' );
1992 $patron->update_lastseen('renewal');
1993 $patron->_result()->discard_changes();
1994 is( $patron->lastseen, $last_seen, 'Patron last seen should still be unchanged after a renewal' );
1995 $patron->update_lastseen('hold');
1996 $patron->_result()->discard_changes();
1997 is( $patron->lastseen, $last_seen, 'Patron last seen should still be unchanged after a hold' );
1998 $patron->update_lastseen('article');
1999 $patron->_result()->discard_changes();
2000 is( $patron->lastseen, $last_seen, 'Patron last seen should still be unchanged after a article' );
2002 # Check that tracking is disabled when the activity isn't listed
2003 t::lib::Mocks::mock_preference( 'TrackLastPatronActivityTriggers', '' );
2004 $cache->clear_from_cache($cache_key); # Rule out the more than once day prevention above
2006 $patron->update_lastseen('login');
2007 $patron->_result()->discard_changes();
2009 $patron->lastseen, $last_seen,
2010 'Patron last seen should be unchanged after a login if login is not selected as an option and the cache has been cleared'
2013 $patron->update_lastseen('connection');
2014 $patron->_result()->discard_changes();
2016 $patron->lastseen, $last_seen,
2017 'Patron last seen should be unchanged after a connection if connection is not selected as an option and the cache has been cleared'
2020 $patron->update_lastseen('check_in');
2021 $patron->_result()->discard_changes();
2023 $patron->lastseen, $last_seen,
2024 'Patron last seen should be unchanged after a check_in if check_in is not selected as an option and the cache has been cleared'
2027 $patron->update_lastseen('check_out');
2028 $patron->_result()->discard_changes();
2030 $patron->lastseen, $last_seen,
2031 'Patron last seen should be unchanged after a check_out if check_out is not selected as an option and the cache has been cleared'
2034 $patron->update_lastseen('renewal');
2035 $patron->_result()->discard_changes();
2037 $patron->lastseen, $last_seen,
2038 'Patron last seen should be unchanged after a renewal if renewal is not selected as an option and the cache has been cleared'
2040 $patron->update_lastseen('hold');
2041 $patron->_result()->discard_changes();
2043 $patron->lastseen, $last_seen,
2044 'Patron last seen should be unchanged after a hold if hold is not selected as an option and the cache has been cleared'
2046 $patron->update_lastseen('article');
2047 $patron->_result()->discard_changes();
2049 $patron->lastseen, $last_seen,
2050 'Patron last seen should be unchanged after an article request if article is not selected as an option and the cache has been cleared'
2053 # Check tracking for each activity
2054 t::lib::Mocks::mock_preference(
2055 'TrackLastPatronActivityTriggers',
2056 'login,connection,check_in,check_out,renewal,hold,article'
2059 $cache->clear_from_cache($cache_key);
2060 $patron->update_lastseen('login');
2061 $patron->_result()->discard_changes();
2062 isnt( $patron->lastseen, $last_seen, 'Patron last seen should be changed after a login if we cleared the cache' );
2064 $cache->clear_from_cache($cache_key);
2065 $patron->update_lastseen('connection');
2066 $patron->_result()->discard_changes();
2068 $patron->lastseen, $last_seen,
2069 'Patron last seen should be changed after a connection if we cleared the cache'
2072 $cache->clear_from_cache($cache_key);
2073 $patron->update_lastseen('check_out');
2074 $patron->_result()->discard_changes();
2076 $patron->lastseen, $last_seen,
2077 'Patron last seen should be changed after a check_out if we cleared the cache'
2080 $cache->clear_from_cache($cache_key);
2081 $patron->update_lastseen('check_in');
2082 $patron->_result()->discard_changes();
2084 $patron->lastseen, $last_seen,
2085 'Patron last seen should be changed after a check_in if we cleared the cache'
2088 $cache->clear_from_cache($cache_key);
2089 $patron->update_lastseen('hold');
2090 $patron->_result()->discard_changes();
2092 $patron->lastseen, $last_seen,
2093 'Patron last seen should be changed after a hold if we cleared the cache'
2095 $patron->update_lastseen('article');
2096 $patron->_result()->discard_changes();
2098 $patron->lastseen, $last_seen,
2099 'Patron last seen should be changed after a article if we cleared the cache'
2102 $cache->clear_from_cache($cache_key);
2103 $patron->update_lastseen('renewal');
2104 $patron->_result()->discard_changes();
2105 isnt( $patron->lastseen, $last_seen, 'Patron last seen should be changed after a renewal if we cleared the cache' );
2107 # Check that the preference takes effect
2108 t::lib::Mocks::mock_preference( 'TrackLastPatronActivityTriggers', '' );
2109 $patron->lastseen(undef)->store;
2110 $cache->clear_from_cache($cache_key);
2111 $patron->update_lastseen('login');
2112 $patron->_result()->discard_changes();
2113 is( $patron->lastseen, undef, 'Patron should still have last seen unchanged when TrackLastPatronActivityTriggers is unset' );
2116 $schema->storage->txn_rollback;
2119 subtest 'get_lists_with_patron() tests' => sub {
2123 $schema->storage->txn_begin;
2125 my $owner = $builder->build_object( { class => 'Koha::Patrons' } );
2127 my $list_1 = AddPatronList( { name => ' Ya', owner => $owner->id } );
2128 my $list_2 = AddPatronList( { name => 'Hey!', owner => $owner->id } );
2130 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
2132 my @lists = $patron->get_lists_with_patron();
2134 is( scalar @lists, 0, 'Patron not included in any list' );
2136 AddPatronsToList( { list => $list_1, cardnumbers => [ $patron->cardnumber ] } );
2137 AddPatronsToList( { list => $list_2, cardnumbers => [ $patron->cardnumber ] } );
2139 @lists = $patron->get_lists_with_patron();
2140 foreach my $list (@lists) {
2141 is( ref($list), 'Koha::Schema::Result::PatronList', 'Type is correct' );
2144 is( join( ' ', map { $_->name } @lists ), ' Ya Hey!', 'Lists are the correct ones, and sorted alphabetically' );
2146 $schema->storage->txn_rollback;