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 => 13;
26 use Koha::CirculationRules;
28 use Koha::DateUtils qw(dt_from_string);
29 use Koha::ArticleRequests;
31 use Koha::Patron::Relationships;
33 use t::lib::TestBuilder;
36 my $schema = Koha::Database->new->schema;
37 my $builder = t::lib::TestBuilder->new;
39 subtest 'add_guarantor() tests' => sub {
43 $schema->storage->txn_begin;
45 t::lib::Mocks::mock_preference( 'borrowerRelationship', 'father1|father2' );
47 my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' });
48 my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
51 { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber }); }
52 'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
53 'Exception is thrown as no relationship passed';
55 is( $patron_1->guarantee_relationships->count, 0, 'No guarantors added' );
58 { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father' }); }
59 'Koha::Exceptions::Patron::Relationship::InvalidRelationship',
60 'Exception is thrown as a wrong relationship was passed';
62 is( $patron_1->guarantee_relationships->count, 0, 'No guarantors added' );
64 $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father1' });
66 my $guarantors = $patron_1->guarantor_relationships;
68 is( $guarantors->count, 1, 'No guarantors added' );
72 open STDERR, '>', '/dev/null';
74 { $patron_1->add_guarantor({ guarantor_id => $patron_2->borrowernumber, relationship => 'father2' }); }
75 'Koha::Exceptions::Patron::Relationship::DuplicateRelationship',
76 'Exception is thrown for duplicated relationship';
80 $schema->storage->txn_rollback;
83 subtest 'relationships_debt() tests' => sub {
87 $schema->storage->txn_begin;
89 t::lib::Mocks::mock_preference( 'borrowerRelationship', 'parent' );
91 my $parent_1 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Parent 1" } });
92 my $parent_2 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Parent 2" } });
93 my $child_1 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Child 1" } });
94 my $child_2 = $builder->build_object({ class => 'Koha::Patrons', value => { firstname => "Child 2" } });
96 $child_1->add_guarantor({ guarantor_id => $parent_1->borrowernumber, relationship => 'parent' });
97 $child_1->add_guarantor({ guarantor_id => $parent_2->borrowernumber, relationship => 'parent' });
98 $child_2->add_guarantor({ guarantor_id => $parent_1->borrowernumber, relationship => 'parent' });
99 $child_2->add_guarantor({ guarantor_id => $parent_2->borrowernumber, relationship => 'parent' });
101 is( $child_1->guarantor_relationships->guarantors->count, 2, 'Child 1 has correct number of guarantors' );
102 is( $child_2->guarantor_relationships->guarantors->count, 2, 'Child 2 has correct number of guarantors' );
103 is( $parent_1->guarantee_relationships->guarantees->count, 2, 'Parent 1 has correct number of guarantees' );
104 is( $parent_2->guarantee_relationships->guarantees->count, 2, 'Parent 2 has correct number of guarantees' );
106 my $patrons = [ $parent_1, $parent_2, $child_1, $child_2 ];
108 # First test: No debt
109 my ($parent1_debt, $parent2_debt, $child1_debt, $child2_debt) = (0,0,0,0);
110 _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
112 # Add debt to child_2
114 $child_2->account->add_debit({ type => 'ACCOUNT', amount => $child2_debt, interface => 'commandline' });
115 is( $child_2->account->non_issues_charges, $child2_debt, 'Debt added to Child 2' );
116 _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
119 $parent_1->account->add_debit({ type => 'ACCOUNT', amount => $parent1_debt, interface => 'commandline' });
120 is( $parent_1->account->non_issues_charges, $parent1_debt, 'Debt added to Parent 1' );
121 _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
124 $parent_2->account->add_debit({ type => 'ACCOUNT', amount => $parent2_debt, interface => 'commandline' });
125 is( $parent_2->account->non_issues_charges, $parent2_debt, 'Parent 2 owes correct amount' );
126 _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
129 $child_1->account->add_debit({ type => 'ACCOUNT', amount => $child1_debt, interface => 'commandline' });
130 is( $child_1->account->non_issues_charges, $child1_debt, 'Child 1 owes correct amount' );
131 _test_combinations($patrons, $parent1_debt,$parent2_debt,$child1_debt,$child2_debt);
133 $schema->storage->txn_rollback;
136 sub _test_combinations {
137 my ( $patrons, $parent1_debt, $parent2_debt, $child1_debt, $child2_debt ) = @_;
140 # P1 => P1 + C1 + C2 ( - P1 ) ( + P2 )
141 # P2 => P2 + C1 + C2 ( - P2 ) ( + P1 )
142 # C1 => P1 + P2 + C1 + C2 ( - C1 )
143 # C2 => P1 + P2 + C1 + C2 ( - C2 )
145 # 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
146 for my $i ( 0 .. 7 ) {
147 my ( $only_this_guarantor, $include_guarantors, $include_this_patron )
148 = split '', sprintf( "%03b", $i );
149 for my $patron ( @$patrons ) {
150 if ( $only_this_guarantor
151 && !$patron->guarantee_relationships->count )
154 $patron->relationships_debt(
156 only_this_guarantor => $only_this_guarantor,
157 include_guarantors => $include_guarantors,
158 include_this_patron => $include_this_patron
162 'Koha::Exceptions::BadParameter',
163 'Exception is thrown as patron is not a guarantor';
169 if ( $patron->firstname eq 'Parent 1' ) {
170 $debt += $parent1_debt if ($include_this_patron && $include_guarantors);
171 $debt += $child1_debt + $child2_debt;
172 $debt += $parent2_debt unless ($only_this_guarantor || !$include_guarantors);
174 elsif ( $patron->firstname eq 'Parent 2' ) {
175 $debt += $parent2_debt if ($include_this_patron & $include_guarantors);
176 $debt += $child1_debt + $child2_debt;
177 $debt += $parent1_debt unless ($only_this_guarantor || !$include_guarantors);
179 elsif ( $patron->firstname eq 'Child 1' ) {
180 $debt += $child1_debt if ($include_this_patron);
181 $debt += $child2_debt;
182 $debt += $parent1_debt + $parent2_debt if ($include_guarantors);
185 $debt += $child2_debt if ($include_this_patron);
186 $debt += $child1_debt;
187 $debt += $parent1_debt + $parent2_debt if ($include_guarantors);
191 $patron->relationships_debt(
193 only_this_guarantor => $only_this_guarantor,
194 include_guarantors => $include_guarantors,
195 include_this_patron => $include_this_patron
200 . " debt of $debt calculated correctly for ( only_this_guarantor: $only_this_guarantor, include_guarantors: $include_guarantors, include_this_patron: $include_this_patron)"
207 subtest 'add_enrolment_fee_if_needed() tests' => sub {
211 subtest 'category has enrolment fee' => sub {
214 $schema->storage->txn_begin;
216 my $category = $builder->build_object(
218 class => 'Koha::Patron::Categories',
225 my $patron = $builder->build_object(
227 class => 'Koha::Patrons',
229 categorycode => $category->categorycode
234 my $enrollment_fee = $patron->add_enrolment_fee_if_needed();
235 is( $enrollment_fee * 1, 20, 'Enrolment fee amount is correct' );
236 my $account = $patron->account;
237 is( $patron->account->balance * 1, 20, 'Patron charged the enrolment fee' );
238 # second enrolment fee, new
239 $enrollment_fee = $patron->add_enrolment_fee_if_needed(0);
240 # third enrolment fee, renewal
241 $enrollment_fee = $patron->add_enrolment_fee_if_needed(1);
242 is( $patron->account->balance * 1, 60, 'Patron charged the enrolment fees' );
244 my @debits = $account->outstanding_debits->as_list;
245 is( scalar @debits, 3, '3 enrolment fees' );
246 is( $debits[0]->debit_type_code, 'ACCOUNT', 'Account type set correctly' );
247 is( $debits[1]->debit_type_code, 'ACCOUNT', 'Account type set correctly' );
248 is( $debits[2]->debit_type_code, 'ACCOUNT_RENEW', 'Account type set correctly' );
250 $schema->storage->txn_rollback;
253 subtest 'no enrolment fee' => sub {
257 $schema->storage->txn_begin;
259 my $category = $builder->build_object(
261 class => 'Koha::Patron::Categories',
268 my $patron = $builder->build_object(
270 class => 'Koha::Patrons',
272 categorycode => $category->categorycode
277 my $enrollment_fee = $patron->add_enrolment_fee_if_needed();
278 is( $enrollment_fee * 1, 0, 'No enrolment fee' );
279 my $account = $patron->account;
280 is( $patron->account->balance, 0, 'Patron not charged anything' );
282 my @debits = $account->outstanding_debits->as_list;
283 is( scalar @debits, 0, 'no debits' );
285 $schema->storage->txn_rollback;
289 subtest 'to_api() tests' => sub {
293 $schema->storage->txn_begin;
295 my $patron_class = Test::MockModule->new('Koha::Patron');
298 sub { return 'algo' }
301 my $patron = $builder->build_object(
303 class => 'Koha::Patrons',
310 my $restricted = $patron->to_api->{restricted};
311 ok( defined $restricted, 'restricted is defined' );
312 ok( !$restricted, 'debarred is undef, restricted evaluates to false' );
314 $patron->debarred( dt_from_string->add( days => 1 ) )->store->discard_changes;
315 $restricted = $patron->to_api->{restricted};
316 ok( defined $restricted, 'restricted is defined' );
317 ok( $restricted, 'debarred is defined, restricted evaluates to true' );
319 my $patron_json = $patron->to_api({ embed => { algo => {} } });
320 ok( exists $patron_json->{algo} );
321 is( $patron_json->{algo}, 'algo' );
323 $schema->storage->txn_rollback;
326 subtest 'login_attempts tests' => sub {
329 $schema->storage->txn_begin;
331 my $patron = $builder->build_object(
333 class => 'Koha::Patrons',
336 my $patron_info = $patron->unblessed;
338 delete $patron_info->{login_attempts};
339 my $new_patron = Koha::Patron->new($patron_info)->store;
340 is( $new_patron->discard_changes->login_attempts, 0, "login_attempts defaults to 0 as expected");
342 $schema->storage->txn_rollback;
345 subtest 'is_superlibrarian() tests' => sub {
349 $schema->storage->txn_begin;
351 my $patron = $builder->build_object(
353 class => 'Koha::Patrons',
361 is( $patron->is_superlibrarian, 0, 'Patron is not a superlibrarian and the method returns the correct value' );
363 $patron->flags(1)->store->discard_changes;
364 is( $patron->is_superlibrarian, 1, 'Patron is a superlibrarian and the method returns the correct value' );
366 $patron->flags(0)->store->discard_changes;
367 is( $patron->is_superlibrarian, 0, 'Patron is not a superlibrarian and the method returns the correct value' );
369 $schema->storage->txn_rollback;
372 subtest 'extended_attributes' => sub {
376 my $schema = Koha::Database->new->schema;
377 $schema->storage->txn_begin;
379 my $patron_1 = $builder->build_object({class=> 'Koha::Patrons'});
380 my $patron_2 = $builder->build_object({class=> 'Koha::Patrons'});
382 t::lib::Mocks::mock_userenv({ patron => $patron_1 });
384 my $attribute_type1 = Koha::Patron::Attribute::Type->new(
387 description => 'my description1',
391 my $attribute_type2 = Koha::Patron::Attribute::Type->new(
394 description => 'my description2',
396 staff_searchable => 1
400 my $new_library = $builder->build( { source => 'Branch' } );
401 my $attribute_type_limited = Koha::Patron::Attribute::Type->new(
402 { code => 'my code3', description => 'my description3' } )->store;
403 $attribute_type_limited->library_limits( [ $new_library->{branchcode} ] );
405 my $attributes_for_1 = [
407 attribute => 'my attribute1',
408 code => $attribute_type1->code(),
411 attribute => 'my attribute2',
412 code => $attribute_type2->code(),
415 attribute => 'my attribute limited',
416 code => $attribute_type_limited->code(),
420 my $attributes_for_2 = [
422 attribute => 'my attribute12',
423 code => $attribute_type1->code(),
426 attribute => 'my attribute limited 2',
427 code => $attribute_type_limited->code(),
431 my $extended_attributes = $patron_1->extended_attributes;
432 is( ref($extended_attributes), 'Koha::Patron::Attributes', 'Koha::Patron->extended_attributes must return a Koha::Patron::Attribute set' );
433 is( $extended_attributes->count, 0, 'There should not be attribute yet');
435 $patron_1->extended_attributes->filter_by_branch_limitations->delete;
436 $patron_2->extended_attributes->filter_by_branch_limitations->delete;
437 $patron_1->extended_attributes($attributes_for_1);
438 $patron_2->extended_attributes($attributes_for_2);
440 my $extended_attributes_for_1 = $patron_1->extended_attributes;
441 is( $extended_attributes_for_1->count, 3, 'There should be 3 attributes now for patron 1');
443 my $extended_attributes_for_2 = $patron_2->extended_attributes;
444 is( $extended_attributes_for_2->count, 2, 'There should be 2 attributes now for patron 2');
446 my $attribute_12 = $extended_attributes_for_2->search({ code => $attribute_type1->code })->next;
447 is( $attribute_12->attribute, 'my attribute12', 'search by code should return the correct attribute' );
449 $attribute_12 = $patron_2->get_extended_attribute( $attribute_type1->code );
450 is( $attribute_12->attribute, 'my attribute12', 'Koha::Patron->get_extended_attribute should return the correct attribute value' );
452 my $expected_attributes_for_2 = [
454 code => $attribute_type1->code(),
455 attribute => 'my attribute12',
458 code => $attribute_type_limited->code(),
459 attribute => 'my attribute limited 2',
462 # Sorting them by code
463 $expected_attributes_for_2 = [ sort { $a->{code} cmp $b->{code} } @$expected_attributes_for_2 ];
464 my @extended_attributes_for_2 = $extended_attributes_for_2->as_list;
469 code => $extended_attributes_for_2[0]->code,
470 attribute => $extended_attributes_for_2[0]->attribute
473 code => $extended_attributes_for_2[1]->code,
474 attribute => $extended_attributes_for_2[1]->attribute
477 $expected_attributes_for_2
480 # TODO - What about multiple? POD explains the problem
481 my $non_existent = $patron_2->get_extended_attribute( 'not_exist' );
482 is( $non_existent, undef, 'Koha::Patron->get_extended_attribute must return undef if the attribute does not exist' );
484 # Test branch limitations
485 t::lib::Mocks::mock_userenv({ patron => $patron_2 });
487 $extended_attributes_for_1 = $patron_1->extended_attributes;
488 is( $extended_attributes_for_1->count, 3, 'There should be 2 attributes for patron 1, the limited one should be returned');
491 $extended_attributes_for_1 = $patron_1->extended_attributes->filter_by_branch_limitations;
492 is( $extended_attributes_for_1->count, 2, 'There should be 2 attributes for patron 1, the limited one should be returned');
495 my $limited_value = $patron_1->get_extended_attribute( $attribute_type_limited->code );
496 is( $limited_value->attribute, 'my attribute limited', );
498 ## Do we need a filtered?
499 #$limited_value = $patron_1->get_extended_attribute( $attribute_type_limited->code );
500 #is( $limited_value, undef, );
502 $schema->storage->txn_rollback;
504 subtest 'non-repeatable attributes tests' => sub {
508 $schema->storage->txn_begin;
510 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
511 my $attribute_type = $builder->build_object(
513 class => 'Koha::Patron::Attribute::Types',
514 value => { repeatable => 0 }
518 is( $patron->extended_attributes->count, 0, 'Patron has no extended attributes' );
522 $patron->extended_attributes(
524 { code => $attribute_type->code, attribute => 'a' },
525 { code => $attribute_type->code, attribute => 'b' }
529 'Koha::Exceptions::Patron::Attribute::NonRepeatable',
530 'Exception thrown on non-repeatable attribute';
532 is( $patron->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
534 $schema->storage->txn_rollback;
538 subtest 'unique attributes tests' => sub {
542 $schema->storage->txn_begin;
544 my $patron_1 = $builder->build_object({ class => 'Koha::Patrons' });
545 my $patron_2 = $builder->build_object({ class => 'Koha::Patrons' });
547 my $attribute_type_1 = $builder->build_object(
549 class => 'Koha::Patron::Attribute::Types',
550 value => { unique => 1 }
554 my $attribute_type_2 = $builder->build_object(
556 class => 'Koha::Patron::Attribute::Types',
557 value => { unique => 0 }
561 is( $patron_1->extended_attributes->count, 0, 'patron_1 has no extended attributes' );
562 is( $patron_2->extended_attributes->count, 0, 'patron_2 has no extended attributes' );
564 $patron_1->extended_attributes(
566 { code => $attribute_type_1->code, attribute => 'a' },
567 { code => $attribute_type_2->code, attribute => 'a' }
573 $patron_2->extended_attributes(
575 { code => $attribute_type_1->code, attribute => 'a' },
576 { code => $attribute_type_2->code, attribute => 'a' }
580 'Koha::Exceptions::Patron::Attribute::UniqueIDConstraint',
581 'Exception thrown on unique attribute';
583 is( $patron_1->extended_attributes->count, 2, 'Extended attributes stored' );
584 is( $patron_2->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
586 $schema->storage->txn_rollback;
590 subtest 'invalid type attributes tests' => sub {
594 $schema->storage->txn_begin;
596 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
598 my $attribute_type_1 = $builder->build_object(
600 class => 'Koha::Patron::Attribute::Types',
601 value => { repeatable => 0 }
605 my $attribute_type_2 = $builder->build_object(
607 class => 'Koha::Patron::Attribute::Types'
611 my $type_2 = $attribute_type_2->code;
612 $attribute_type_2->delete;
614 is( $patron->extended_attributes->count, 0, 'Patron has no extended attributes' );
618 $patron->extended_attributes(
620 { code => $attribute_type_1->code, attribute => 'a' },
621 { code => $attribute_type_2->code, attribute => 'b' }
625 'Koha::Exceptions::Patron::Attribute::InvalidType',
626 'Exception thrown on invalid attribute type';
628 is( $patron->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
630 $schema->storage->txn_rollback;
634 subtest 'globally mandatory attributes tests' => sub {
638 $schema->storage->txn_begin;
640 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
642 my $attribute_type_1 = $builder->build_object(
644 class => 'Koha::Patron::Attribute::Types',
645 value => { mandatory => 1, class => 'a' }
649 my $attribute_type_2 = $builder->build_object(
651 class => 'Koha::Patron::Attribute::Types',
652 value => { mandatory => 0, class => 'a' }
656 is( $patron->extended_attributes->count, 0, 'Patron has no extended attributes' );
660 $patron->extended_attributes(
662 { code => $attribute_type_2->code, attribute => 'b' }
666 'Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute',
667 'Exception thrown on missing mandatory attribute type';
669 is( $@->type, $attribute_type_1->code, 'Exception parameters are correct' );
671 is( $patron->extended_attributes->count, 0, 'Extended attributes storing rolled back' );
673 $patron->extended_attributes(
675 { code => $attribute_type_1->code, attribute => 'b' }
679 is( $patron->extended_attributes->count, 1, 'Extended attributes succeeded' );
681 $schema->storage->txn_rollback;
687 subtest 'can_log_into() tests' => sub {
691 $schema->storage->txn_begin;
693 my $patron = $builder->build_object(
695 class => 'Koha::Patrons',
701 my $library = $builder->build_object({ class => 'Koha::Libraries' });
703 t::lib::Mocks::mock_preference('IndependentBranches', 1);
705 ok( $patron->can_log_into( $patron->library ), 'Patron can log into its own library' );
706 ok( !$patron->can_log_into( $library ), 'Patron cannot log into different library, IndependentBranches on' );
708 # make it a superlibrarian
709 $patron->set({ flags => 1 })->store->discard_changes;
710 ok( $patron->can_log_into( $library ), 'Superlibrarian can log into different library, IndependentBranches on' );
712 t::lib::Mocks::mock_preference('IndependentBranches', 0);
714 # No special permissions
715 $patron->set({ flags => undef })->store->discard_changes;
716 ok( $patron->can_log_into( $patron->library ), 'Patron can log into its own library' );
717 ok( $patron->can_log_into( $library ), 'Patron can log into any library' );
719 $schema->storage->txn_rollback;
722 subtest 'can_request_article() tests' => sub {
726 $schema->storage->txn_begin;
728 t::lib::Mocks::mock_preference( 'ArticleRequests', 1 );
730 my $item = $builder->build_sample_item;
732 my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
733 my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } );
734 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
736 t::lib::Mocks::mock_userenv( { branchcode => $library_2->id } );
738 Koha::CirculationRules->set_rule(
740 categorycode => undef,
741 branchcode => $library_1->id,
742 rule_name => 'open_article_requests_limit',
747 $builder->build_object(
749 class => 'Koha::ArticleRequests',
750 value => { status => 'REQUESTED', borrowernumber => $patron->id }
753 $builder->build_object(
755 class => 'Koha::ArticleRequests',
756 value => { status => 'PENDING', borrowernumber => $patron->id }
759 $builder->build_object(
761 class => 'Koha::ArticleRequests',
762 value => { status => 'PROCESSING', borrowernumber => $patron->id }
765 $builder->build_object(
767 class => 'Koha::ArticleRequests',
768 value => { status => 'CANCELED', borrowernumber => $patron->id }
773 $patron->can_request_article( $library_1->id ),
774 '3 current requests, 4 is the limit: allowed'
777 # Completed request, same day
778 my $completed = $builder->build_object(
780 class => 'Koha::ArticleRequests',
782 status => 'COMPLETED',
783 borrowernumber => $patron->id
788 ok( !$patron->can_request_article( $library_1->id ),
789 '3 current requests and a completed one the same day: denied' );
791 $completed->updated_on(
792 dt_from_string->add( days => -1 )->set(
799 ok( $patron->can_request_article( $library_1->id ),
800 '3 current requests and a completed one the day before: allowed' );
802 Koha::CirculationRules->set_rule(
804 categorycode => undef,
805 branchcode => $library_2->id,
806 rule_name => 'open_article_requests_limit',
811 ok( !$patron->can_request_article,
812 'Not passing the library_id param makes it fallback to userenv: denied'
815 $schema->storage->txn_rollback;
818 subtest 'article_requests() tests' => sub {
822 $schema->storage->txn_begin;
824 my $library = $builder->build_object({ class => 'Koha::Libraries' });
825 t::lib::Mocks::mock_userenv( { branchcode => $library->id } );
827 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
829 my $article_requests = $patron->article_requests;
830 is( ref($article_requests), 'Koha::ArticleRequests',
831 'In scalar context, type is correct' );
832 is( $article_requests->count, 0, 'No article requests' );
834 foreach my $i ( 0 .. 3 ) {
836 my $item = $builder->build_sample_item;
838 Koha::ArticleRequest->new(
840 borrowernumber => $patron->id,
841 biblionumber => $item->biblionumber,
842 itemnumber => $item->id,
848 $article_requests = $patron->article_requests;
849 is( $article_requests->count, 4, '4 article requests' );
851 $schema->storage->txn_rollback;
854 subtest 'safe_to_delete() tests' => sub {
858 $schema->storage->txn_begin;
860 my $patron = $builder->build_object({ class => 'Koha::Patrons' });
862 ## Make it the anonymous
863 t::lib::Mocks::mock_preference( 'AnonymousPatron', $patron->id );
865 ok( !$patron->safe_to_delete, 'Cannot delete, it is the anonymous patron' );
866 my $message = $patron->safe_to_delete->messages->[0];
867 is( $message->type, 'error', 'Type is error' );
868 is( $message->message, 'is_anonymous_patron', 'Cannot delete, it is the anonymous patron' );
870 t::lib::Mocks::mock_preference( 'AnonymousPatron', 0 );
872 ## Make it have a checkout
873 my $checkout = $builder->build_object(
875 class => 'Koha::Checkouts',
876 value => { borrowernumber => $patron->id }
880 ok( !$patron->safe_to_delete, 'Cannot delete, has checkouts' );
881 $message = $patron->safe_to_delete->messages->[0];
882 is( $message->type, 'error', 'Type is error' );
883 is( $message->message, 'has_checkouts', 'Cannot delete, has checkouts' );
887 ## Make it have a guarantee
888 t::lib::Mocks::mock_preference( 'borrowerRelationship', 'parent' );
889 $builder->build_object({ class => 'Koha::Patrons' })
890 ->add_guarantor({ guarantor_id => $patron->id, relationship => 'parent' });
892 ok( !$patron->safe_to_delete, 'Cannot delete, has guarantees' );
893 $message = $patron->safe_to_delete->messages->[0];
894 is( $message->type, 'error', 'Type is error' );
895 is( $message->message, 'has_guarantees', 'Cannot delete, has guarantees' );
898 $patron->guarantee_relationships->delete;
901 my $debit = $patron->account->add_debit({ amount => 10, interface => 'intranet', type => 'MANUAL' });
903 ok( !$patron->safe_to_delete, 'Cannot delete, has debt' );
904 $message = $patron->safe_to_delete->messages->[0];
905 is( $message->type, 'error', 'Type is error' );
906 is( $message->message, 'has_debt', 'Cannot delete, has debt' );
908 $patron->account->pay({ amount => 10, debits => [ $debit ] });
911 ok( $patron->safe_to_delete, 'Can delete, all conditions met' );
912 my $messages = $patron->safe_to_delete->messages;
913 is_deeply( $messages, [], 'Patron can be deleted, no messages' );
916 subtest 'article_request_fee() tests' => sub {
920 $schema->storage->txn_begin;
922 # Cleanup, to avoid interference
923 Koha::CirculationRules->search( { rule_name => 'article_request_fee' } )->delete;
925 t::lib::Mocks::mock_preference( 'ArticleRequests', 1 );
927 my $item = $builder->build_sample_item;
929 my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
930 my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } );
931 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
933 # Rule that should never be picked, because the patron's category is always picked
934 Koha::CirculationRules->set_rule(
935 { categorycode => undef,
937 rule_name => 'article_request_fee',
942 is( $patron->article_request_fee( { library_id => $library_2->id } ), 1, 'library_id used correctly' );
944 Koha::CirculationRules->set_rule(
945 { categorycode => $patron->categorycode,
947 rule_name => 'article_request_fee',
952 Koha::CirculationRules->set_rule(
953 { categorycode => $patron->categorycode,
954 branchcode => $library_1->id,
955 rule_name => 'article_request_fee',
960 is( $patron->article_request_fee( { library_id => $library_2->id } ), 2, 'library_id used correctly' );
962 t::lib::Mocks::mock_userenv( { branchcode => $library_1->id } );
964 is( $patron->article_request_fee(), 3, 'env used correctly' );
966 $schema->storage->txn_rollback;
969 subtest 'add_article_request_fee_if_needed() tests' => sub {
973 $schema->storage->txn_begin;
977 my $patron_mock = Test::MockModule->new('Koha::Patron');
978 $patron_mock->mock( 'article_request_fee', sub { return $amount; } );
980 my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
982 is( $patron->article_request_fee, $amount, 'article_request_fee mocked' );
984 my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } );
985 my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } );
986 my $staff = $builder->build_object( { class => 'Koha::Patrons' } );
987 my $item = $builder->build_sample_item;
989 t::lib::Mocks::mock_userenv(
990 { branchcode => $library_1->id, patron => $staff } );
992 my $debit = $patron->add_article_request_fee_if_needed();
993 is( $debit, undef, 'No fee, no debit line' );
998 $debit = $patron->add_article_request_fee_if_needed({ item_id => $item->id });
999 is( ref($debit), 'Koha::Account::Line', 'Debit object type correct' );
1000 is( $debit->amount, $amount,
1001 'amount set to $patron->article_request_fee value' );
1002 is( $debit->manager_id, $staff->id,
1003 'manager_id set to userenv session user' );
1004 is( $debit->branchcode, $library_1->id,
1005 'branchcode set to userenv session library' );
1006 is( $debit->debit_type_code, 'ARTICLE_REQUEST',
1007 'debit_type_code set correctly' );
1008 is( $debit->itemnumber, $item->id,
1009 'itemnumber set correctly' );
1013 $debit = $patron->add_article_request_fee_if_needed({ library_id => $library_2->id });
1014 is( ref($debit), 'Koha::Account::Line', 'Debit object type correct' );
1015 is( $debit->amount, $amount,
1016 'amount set to $patron->article_request_fee value' );
1017 is( $debit->branchcode, $library_2->id,
1018 'branchcode set to userenv session library' );
1019 is( $debit->itemnumber, undef,
1020 'itemnumber set correctly to undef' );
1022 $schema->storage->txn_rollback;