View | Details | Raw Unified | Return to bug 26170
Collapse All | Expand All

(-)a/C4/Members.pm (+1 lines)
Lines 399-404 sub GetBorrowersToExpunge { Link Here
399
    $query .= q| WHERE  category_type <> 'S'
399
    $query .= q| WHERE  category_type <> 'S'
400
        AND ( borrowers.flags IS NULL OR borrowers.flags = 0 )
400
        AND ( borrowers.flags IS NULL OR borrowers.flags = 0 )
401
        AND tmp.guarantor_id IS NULL
401
        AND tmp.guarantor_id IS NULL
402
        AND borrowers.protected = 0
402
    |;
403
    |;
403
    my @query_params;
404
    my @query_params;
404
    if ( $filterbranch && $filterbranch ne "" ) {
405
    if ( $filterbranch && $filterbranch ne "" ) {
(-)a/Koha/Exceptions/Patron.pm (+4 lines)
Lines 23-28 use Exception::Class ( Link Here
23
        isa         => 'Koha::Exceptions::Patron',
23
        isa         => 'Koha::Exceptions::Patron',
24
        description => "Deleting patron failed, AnonymousPatron is not deleteable"
24
        description => "Deleting patron failed, AnonymousPatron is not deleteable"
25
    },
25
    },
26
    'Koha::Exceptions::Patron::FailedDeleteProtectedPatron' => {
27
        isa         => 'Koha::Exceptions::Patron',
28
        description => "Deleting patron failed, patron is protected"
29
    },
26
    'Koha::Exceptions::Patron::InvalidUserid' => {
30
    'Koha::Exceptions::Patron::InvalidUserid' => {
27
        isa         => 'Koha::Exceptions::Patron',
31
        isa         => 'Koha::Exceptions::Patron',
28
        description => 'Field userid is not valid (probably not unique)',
32
        description => 'Field userid is not valid (probably not unique)',
(-)a/Koha/Patron.pm (+14 lines)
Lines 395-400 sub delete { Link Here
395
    my $anonymous_patron = C4::Context->preference("AnonymousPatron");
395
    my $anonymous_patron = C4::Context->preference("AnonymousPatron");
396
    Koha::Exceptions::Patron::FailedDeleteAnonymousPatron->throw() if $anonymous_patron && $self->id eq $anonymous_patron;
396
    Koha::Exceptions::Patron::FailedDeleteAnonymousPatron->throw() if $anonymous_patron && $self->id eq $anonymous_patron;
397
397
398
    # Check if patron is protected
399
    Koha::Exceptions::Patron::FailedDeleteProtectedPatron->throw() if $self->protected == 1;
400
398
    $self->_result->result_source->schema->txn_do(
401
    $self->_result->result_source->schema->txn_do(
399
        sub {
402
        sub {
400
            # Cancel Patron's holds
403
            # Cancel Patron's holds
Lines 624-629 sub merge_with { Link Here
624
    my $anonymous_patron = C4::Context->preference("AnonymousPatron");
627
    my $anonymous_patron = C4::Context->preference("AnonymousPatron");
625
    return if $anonymous_patron && $self->id eq $anonymous_patron;
628
    return if $anonymous_patron && $self->id eq $anonymous_patron;
626
629
630
    # Do not merge other patrons into a protected patron
631
    return if $self->protected;
632
627
    my @patron_ids = @{ $patron_ids };
633
    my @patron_ids = @{ $patron_ids };
628
634
629
    # Ensure the keeper isn't in the list of patrons to merge
635
    # Ensure the keeper isn't in the list of patrons to merge
Lines 642-647 sub merge_with { Link Here
642
648
643
            next unless $patron;
649
            next unless $patron;
644
650
651
            # Do not merge protected patrons into other patrons
652
            next if $patron->protected;
653
645
            # Unbless for safety, the patron will end up being deleted
654
            # Unbless for safety, the patron will end up being deleted
646
            $results->{merged}->{$patron_id}->{patron} = $patron->unblessed;
655
            $results->{merged}->{$patron_id}->{patron} = $patron->unblessed;
647
656
Lines 2257-2262 This method tells if the Koha:Patron object can be deleted. Possible return valu Link Here
2257
2266
2258
=item 'is_anonymous_patron'
2267
=item 'is_anonymous_patron'
2259
2268
2269
=item 'is_protected'
2270
2260
=back
2271
=back
2261
2272
2262
=cut
2273
=cut
Lines 2280-2285 sub safe_to_delete { Link Here
2280
    elsif ( $self->guarantee_relationships->count ) {
2291
    elsif ( $self->guarantee_relationships->count ) {
2281
        $error = 'has_guarantees';
2292
        $error = 'has_guarantees';
2282
    }
2293
    }
2294
    elsif ( $self->protected ) {
2295
        $error = 'is_protected';
2296
    }
2283
2297
2284
    if ( $error ) {
2298
    if ( $error ) {
2285
        return Koha::Result::Boolean->new(0)->add_message({ message => $error });
2299
        return Koha::Result::Boolean->new(0)->add_message({ message => $error });
(-)a/Koha/REST/V1/Patrons.pm (+1 lines)
Lines 366-371 sub delete { Link Here
366
                has_debt       => 'Pending debts prevent deletion',
366
                has_debt       => 'Pending debts prevent deletion',
367
                has_guarantees => 'Patron is a guarantor and it prevents deletion',
367
                has_guarantees => 'Patron is a guarantor and it prevents deletion',
368
                is_anonymous_patron => 'Anonymous patron cannot be deleted',
368
                is_anonymous_patron => 'Anonymous patron cannot be deleted',
369
                is_protected => 'Protected patrons cannot be deleted',
369
            };
370
            };
370
371
371
            if ( any { $error->message eq $_ } keys %{$error_descriptions} ) {
372
            if ( any { $error->message eq $_ } keys %{$error_descriptions} ) {
(-)a/api/v1/swagger/definitions/patron.yaml (+4 lines)
Lines 374-379 properties: Link Here
374
      - object
374
      - object
375
      - "null"
375
      - "null"
376
    description: Library of the patron
376
    description: Library of the patron
377
  protected:
378
    type:
379
      - boolean
380
    description: Protected status of the patron
377
additionalProperties: false
381
additionalProperties: false
378
required:
382
required:
379
  - surname
383
  - surname
(-)a/api/v1/swagger/paths/patrons.yaml (+6 lines)
Lines 339-344 Link Here
339
        description: Search on login_attempts
339
        description: Search on login_attempts
340
        required: false
340
        required: false
341
        type: string
341
        type: string
342
      - name: protected
343
        in: query
344
        description: Search on protected status
345
        required: false
346
        type: boolean
342
      - $ref: "../swagger.yaml#/parameters/match"
347
      - $ref: "../swagger.yaml#/parameters/match"
343
      - $ref: "../swagger.yaml#/parameters/order_by"
348
      - $ref: "../swagger.yaml#/parameters/order_by"
344
      - $ref: "../swagger.yaml#/parameters/page"
349
      - $ref: "../swagger.yaml#/parameters/page"
Lines 593-598 Link Here
593
            * `has_debt`: The patron has pending debts
598
            * `has_debt`: The patron has pending debts
594
            * `has_guarantees`: The patron has guarantees
599
            * `has_guarantees`: The patron has guarantees
595
            * `is_anonymous_patron`: The system-wide anonymous patron cannot be deleted
600
            * `is_anonymous_patron`: The system-wide anonymous patron cannot be deleted
601
            * `is_protected`: Protected patrons cannot be deleted
596
        schema:
602
        schema:
597
          $ref: "../swagger.yaml#/definitions/error"
603
          $ref: "../swagger.yaml#/definitions/error"
598
      "500":
604
      "500":
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/members-toolbar.inc (-1 / +5 lines)
Lines 84-90 Link Here
84
                [% END %]
84
                [% END %]
85
85
86
                [% IF CAN_user_borrowers_delete_borrowers %]
86
                [% IF CAN_user_borrowers_delete_borrowers %]
87
                    <li><a id="deletepatron" href="#">Delete</a></li>
87
                    [% IF ( patron.protected == 1 ) %]
88
                        <li class="disabled"><a data-toggle="tooltip" data-placement="left" title="Patron is protected" id="deletepatron" href="#">Delete</a></li>
89
                    [% ELSE %]
90
                        <li><a id="deletepatron" href="#">Delete</a></li>
91
                    [% END %]
88
                [% ELSE %]
92
                [% ELSE %]
89
                    <li class="disabled"><a data-toggle="tooltip" data-placement="left" title="You are not authorized to delete patrons" id="deletepatron" href="#">Delete</a></li>
93
                    <li class="disabled"><a data-toggle="tooltip" data-placement="left" title="You are not authorized to delete patrons" id="deletepatron" href="#">Delete</a></li>
90
                [% END %]
94
                [% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt (+23 lines)
Lines 1025-1030 legend:hover { Link Here
1025
                                            </li>
1025
                                            </li>
1026
                                        [% END %]
1026
                                        [% END %]
1027
1027
1028
                                        <li class="radio">
1029
                                            <label for="protected">Protected:</label>
1030
                                            [% IF ( patron.protected == 1 ) %]
1031
                                                <label for="yes-protected">
1032
                                                    Yes
1033
                                                    <input type="radio" id="yes-protected" name="protected" value="1" checked="checked" />
1034
                                                </label>
1035
                                                <label for="no-protected">
1036
                                                    No
1037
                                                    <input type="radio" id="no-protected" name="protected" value="0" />
1038
                                                </label>
1039
                                            [% ELSE %]
1040
                                                <label for="yes-protected">
1041
                                                    Yes
1042
                                                    <input type="radio" id="yes-protected" name="protected" value="1" />
1043
                                                </label>
1044
                                                <label for="no-protected">
1045
                                                    No
1046
                                                    <input type="radio" id="no-protected" name="protected" value="0" checked="checked" />
1047
                                                </label>
1048
                                            [% END %]
1049
                                        </li>
1050
1028
                                        [% IF ( Koha.Preference('CheckPrevCheckout') == 'softyes' || Koha.Preference('CheckPrevCheckout') == 'softno' ) %]
1051
                                        [% IF ( Koha.Preference('CheckPrevCheckout') == 'softyes' || Koha.Preference('CheckPrevCheckout') == 'softno' ) %]
1029
                                            <li>
1052
                                            <li>
1030
                                                <label for="checkprevcheckout">Check for previous checkouts: </label>
1053
                                                <label for="checkprevcheckout">Check for previous checkouts: </label>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt (+8 lines)
Lines 468-473 Link Here
468
                                                [% END %]
468
                                                [% END %]
469
                                            </li>
469
                                            </li>
470
                                        [% END %]
470
                                        [% END %]
471
                                        <li id="patron-protected">
472
                                            <span class="label">Protected: </span>
473
                                            [% IF ( patron.protected == 1 ) %]
474
                                                Yes
475
                                            [% ELSE %]
476
                                                No
477
                                            [% END %]
478
                                        </li>
471
479
472
                                        [% IF ( patron.borrowernotes ) %]
480
                                        [% IF ( patron.borrowernotes ) %]
473
                                            <li id="patron-borrowernotes">
481
                                            <li id="patron-borrowernotes">
(-)a/t/db_dependent/Koha/Patron.t (-1 / +9 lines)
Lines 1063-1069 subtest 'password expiration tests' => sub { Link Here
1063
1063
1064
subtest 'safe_to_delete() tests' => sub {
1064
subtest 'safe_to_delete() tests' => sub {
1065
1065
1066
    plan tests => 14;
1066
    plan tests => 17;
1067
1067
1068
    $schema->storage->txn_begin;
1068
    $schema->storage->txn_begin;
1069
1069
Lines 1117-1122 subtest 'safe_to_delete() tests' => sub { Link Here
1117
    # cleanup
1117
    # cleanup
1118
    $patron->account->pay({ amount => 10, debits => [ $debit ] });
1118
    $patron->account->pay({ amount => 10, debits => [ $debit ] });
1119
1119
1120
    ## Make it protected
1121
    $patron->protected( 1 );
1122
    ok( !$patron->safe_to_delete, 'Cannot delete, is protected' );
1123
    $message = $patron->safe_to_delete->messages->[0];
1124
    is( $message->type, 'error', 'Type is error' );
1125
    is( $message->message, 'is_protected', 'Cannot delete, is protected' );
1126
    $patron->protected( 0 );
1127
1120
    ## Happy case :-D
1128
    ## Happy case :-D
1121
    ok( $patron->safe_to_delete, 'Can delete, all conditions met' );
1129
    ok( $patron->safe_to_delete, 'Can delete, all conditions met' );
1122
    my $messages = $patron->safe_to_delete->messages;
1130
    my $messages = $patron->safe_to_delete->messages;
(-)a/t/db_dependent/Koha/Patrons.t (-2 / +14 lines)
Lines 1614-1620 subtest 'BorrowersLog tests' => sub { Link Here
1614
$schema->storage->txn_rollback;
1614
$schema->storage->txn_rollback;
1615
1615
1616
subtest 'Test Koha::Patrons::merge' => sub {
1616
subtest 'Test Koha::Patrons::merge' => sub {
1617
    plan tests => 110;
1617
    plan tests => 113;
1618
1618
1619
    my $schema = Koha::Database->new()->schema();
1619
    my $schema = Koha::Database->new()->schema();
1620
1620
Lines 1626-1631 subtest 'Test Koha::Patrons::merge' => sub { Link Here
1626
    my $loser_1 = $builder->build({ source => 'Borrower' })->{borrowernumber};
1626
    my $loser_1 = $builder->build({ source => 'Borrower' })->{borrowernumber};
1627
    my $loser_2 = $builder->build({ source => 'Borrower' })->{borrowernumber};
1627
    my $loser_2 = $builder->build({ source => 'Borrower' })->{borrowernumber};
1628
1628
1629
    my $keeper_protected = $builder->build_object({ class => 'Koha::Patrons' });
1630
    $keeper_protected->protected( 1 )->store;
1631
1632
    my $loser_protected_obj = $builder->build_object({ class => 'Koha::Patrons' });
1633
    $loser_protected_obj->protected( 1 )->store;
1634
    my $loser_protected = $loser_protected_obj->borrowernumber;
1635
1629
    my $anonymous_patron_orig = C4::Context->preference('AnonymousPatron');
1636
    my $anonymous_patron_orig = C4::Context->preference('AnonymousPatron');
1630
    my $anonymous_patron = $builder->build({ source => 'Borrower' })->{borrowernumber};
1637
    my $anonymous_patron = $builder->build({ source => 'Borrower' })->{borrowernumber};
1631
    t::lib::Mocks::mock_preference( 'AnonymousPatron', $anonymous_patron );
1638
    t::lib::Mocks::mock_preference( 'AnonymousPatron', $anonymous_patron );
Lines 1648-1654 subtest 'Test Koha::Patrons::merge' => sub { Link Here
1648
        is( $loser_2_rs->count(), 1, "Found 1 $r rows for loser_2" );
1655
        is( $loser_2_rs->count(), 1, "Found 1 $r rows for loser_2" );
1649
    }
1656
    }
1650
1657
1651
    my $results = $keeper->merge_with([ $loser_1, $loser_2 ]);
1658
    my $results_protected = $keeper_protected->merge_with( [ $loser_1 ] );
1659
    is( $results_protected, undef, "Protected patrons cannot have other patrons merged into them" );
1660
    is( Koha::Patrons->search( { borrowernumber => $loser_1 } )->count, 1, "Patron from attempted merge with protected patron still exists" );
1661
1662
    my $results = $keeper->merge_with([ $loser_1, $loser_2, $loser_protected ]);
1652
1663
1653
    while (my ($r, $field) = each(%$resultsets)) {
1664
    while (my ($r, $field) = each(%$resultsets)) {
1654
        my $keeper_rs =
1665
        my $keeper_rs =
Lines 1659-1664 subtest 'Test Koha::Patrons::merge' => sub { Link Here
1659
    is( Koha::Patrons->find($loser_1), undef, 'Loser 1 has been deleted' );
1670
    is( Koha::Patrons->find($loser_1), undef, 'Loser 1 has been deleted' );
1660
    is( Koha::Patrons->find($loser_2), undef, 'Loser 2 has been deleted' );
1671
    is( Koha::Patrons->find($loser_2), undef, 'Loser 2 has been deleted' );
1661
    is( ref Koha::Patrons->find($anonymous_patron), 'Koha::Patron', 'Anonymous Patron was not deleted' );
1672
    is( ref Koha::Patrons->find($anonymous_patron), 'Koha::Patron', 'Anonymous Patron was not deleted' );
1673
    is( ref Koha::Patrons->find($loser_protected), 'Koha::Patron', 'Protected patron was not deleted' );
1662
1674
1663
    $anonymous_patron = Koha::Patrons->find($anonymous_patron);
1675
    $anonymous_patron = Koha::Patrons->find($anonymous_patron);
1664
    $results = $anonymous_patron->merge_with( [ $keeper->id ] );
1676
    $results = $anonymous_patron->merge_with( [ $keeper->id ] );
(-)a/t/db_dependent/Members.t (-1 / +21 lines)
Lines 17-23 Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Test::More tests => 53;
20
use Test::More tests => 55;
21
use Test::MockModule;
21
use Test::MockModule;
22
use Test::Exception;
22
use Test::Exception;
23
23
Lines 368-373 $patron->set({ flags => 4 })->store; Link Here
368
$patstodel = GetBorrowersToExpunge( {category_code => 'SMALLSTAFF' } );
368
$patstodel = GetBorrowersToExpunge( {category_code => 'SMALLSTAFF' } );
369
is( scalar @$patstodel, 0, 'Regular patron with flags>0 can not be deleted' );
369
is( scalar @$patstodel, 0, 'Regular patron with flags>0 can not be deleted' );
370
370
371
# Test GetBorrowersToExpunge and patrons with "protected" status (borrowers.protected = 1)
372
$builder->build({
373
        source => 'Category',
374
        value => {
375
            categorycode         => 'PROTECTED',
376
            description          => 'Protected',
377
            category_type        => 'A',
378
        },
379
});
380
$borrowernumber = Koha::Patron->new({
381
    categorycode => 'PROTECTED',
382
    branchcode => $library2->{branchcode},
383
})->store->borrowernumber;
384
$patron = Koha::Patrons->find( $borrowernumber );
385
$patstodel = GetBorrowersToExpunge( {category_code => 'PROTECTED' } );
386
is( scalar @$patstodel, 1, 'Patron with default protected status can be deleted' );
387
$patron->set({ protected => 1 })->store;
388
$patstodel = GetBorrowersToExpunge( {category_code => 'PROTECTED' } );
389
is( scalar @$patstodel, 0, 'Patron with protected status set can not be deleted' );
390
371
# Regression tests for BZ13502
391
# Regression tests for BZ13502
372
## Remove all entries with userid='' (should be only 1 max)
392
## Remove all entries with userid='' (should be only 1 max)
373
$dbh->do(q|DELETE FROM borrowers WHERE userid = ''|);
393
$dbh->do(q|DELETE FROM borrowers WHERE userid = ''|);
(-)a/t/lib/TestBuilder.pm (-1 / +1 lines)
Lines 580-585 sub _gen_default_values { Link Here
580
            borrowernotes  => '',
580
            borrowernotes  => '',
581
            secret         => undef,
581
            secret         => undef,
582
            password_expiration_date => undef,
582
            password_expiration_date => undef,
583
            protected      => 0,
583
        },
584
        },
584
        Item => {
585
        Item => {
585
            notforloan         => 0,
586
            notforloan         => 0,
586
- 

Return to bug 26170