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

(-)a/Koha/Patron.pm (+108 lines)
Lines 3019-3024 sub to_api { Link Here
3019
        ? Mojo::JSON->true
3019
        ? Mojo::JSON->true
3020
        : Mojo::JSON->false;
3020
        : Mojo::JSON->false;
3021
3021
3022
    $json_patron->{self_renewal_available} = $self->is_eligible_for_self_renewal();
3023
3022
    return $json_patron;
3024
    return $json_patron;
3023
}
3025
}
3024
3026
Lines 3592-3597 sub is_anonymous { Link Here
3592
    return ( $anonymous_patron && $self->borrowernumber eq $anonymous_patron ) ? 1 : 0;
3594
    return ( $anonymous_patron && $self->borrowernumber eq $anonymous_patron ) ? 1 : 0;
3593
}
3595
}
3594
3596
3597
=head3 is_eligible_for_self_renewal
3598
3599
my $eligible_for_self_renewal = $patron->is_eligible_for_self_renewal();
3600
3601
Returns a boolean value for whether self-renewal is available or not
3602
3603
=cut
3604
3605
sub is_eligible_for_self_renewal {
3606
    my ($self) = @_;
3607
3608
    my $category = $self->category;
3609
    return 0 if !$category->self_renewal_enabled;
3610
3611
    return 0 if $self->debarred;
3612
3613
    my $expiry_window =
3614
        $category->self_renewal_availability_start || C4::Context->preference('NotifyBorrowerDeparture');
3615
    my $post_expiry_window = $category->self_renewal_if_expired || 0;
3616
3617
    my $expiry_date  = dt_from_string( $self->dateexpiry, undef, 'floating' );
3618
    my $window_start = dt_from_string( $self->dateexpiry, undef, 'floating' )->subtract( days => $expiry_window );
3619
    my $window_end   = dt_from_string( $self->dateexpiry, undef, 'floating' )->add( days => $post_expiry_window );
3620
    my $today        = dt_from_string( undef,             undef, 'floating' );
3621
3622
    my $within_expiry_window =
3623
        $window_start < $today->truncate( to => 'day' ) && $today < $window_end->truncate( to => 'day' );
3624
    return 0 if !$within_expiry_window;
3625
3626
    my $charges_status            = $self->is_patron_inside_charge_limits();
3627
    my $self_renewal_charge_limit = $category->self_renewal_fines_block;
3628
    foreach my $key ( keys %$charges_status ) {
3629
        my $within_renewal_limit =
3630
            ( $self_renewal_charge_limit && $self_renewal_charge_limit > $charges_status->{$key}->{charge} ) ? 1 : 0;
3631
        return 0 if $charges_status->{$key}->{overlimit} && !$within_renewal_limit;
3632
    }
3633
3634
    return 1;
3635
}
3636
3637
=head3 request_modification
3638
3639
$patron->request_modification
3640
3641
Used in the OPAC and in the patron self-renewal workflow to request a modification to a patron's account
3642
Automatically approves the request based on the AutoApprovePatronProfileSettings syspref
3643
3644
=cut
3645
3646
sub request_modification {
3647
    my ( $self, $modification ) = @_;
3648
3649
    Koha::Patron::Modifications->search( { borrowernumber => $self->borrowernumber } )->delete;
3650
3651
    $modification->{verification_token} = q{}                   if !$modification->{verification_token};
3652
    $modification->{borrowernumber}     = $self->borrowernumber if !$modification->{borrowernumber};
3653
3654
    my $patron_modification = Koha::Patron::Modification->new($modification)->store()->discard_changes;
3655
3656
    #Automatically approve patron profile changes if AutoApprovePatronProfileSettings is enabled
3657
    $patron_modification->approve() if C4::Context->preference('AutoApprovePatronProfileSettings');
3658
}
3659
3660
=head3 create_expiry_notice_parameters
3661
3662
my $letter_params = $expiring_patron->create_expiry_notice_parameters(
3663
    { letter_code => $which_notice, forceprint => $forceprint, is_notice_mandatory => $is_notice_mandatory } );
3664
3665
Returns the parameters to send an expiry notice to a patron
3666
Used by both the membership_expiry.pl cron and the self-renewal workflow
3667
3668
=cut
3669
3670
sub create_expiry_notice_parameters {
3671
    my ( $self, $args ) = @_;
3672
3673
    my $letter_code         = $args->{letter_code};
3674
    my $forceprint          = $args->{forceprint} || 0;
3675
    my $is_notice_mandatory = $args->{is_notice_mandatory};
3676
3677
    my $letter_params = {
3678
        module         => 'members',
3679
        letter_code    => $letter_code,
3680
        branchcode     => $self->branchcode,
3681
        lang           => $self->lang,
3682
        borrowernumber => $self->borrowernumber,
3683
        tables         => {
3684
            borrowers => $self->borrowernumber,
3685
            branches  => $self->branchcode,
3686
        },
3687
    };
3688
3689
    my $sending_params = {
3690
        letter_params => $letter_params,
3691
        message_name  => 'Patron_Expiry',
3692
        forceprint    => $forceprint
3693
    };
3694
3695
    if ($is_notice_mandatory) {
3696
        $sending_params->{expiry_notice_mandatory} = 1;
3697
        $sending_params->{primary_contact_method}  = $forceprint ? 'print' : $self->primary_contact_method;
3698
    }
3699
3700
    return $sending_params;
3701
}
3702
3595
=head2 Internal methods
3703
=head2 Internal methods
3596
3704
3597
=head3 _type
3705
=head3 _type
(-)a/Koha/REST/V1/Patrons/SelfRenewal.pm (+151 lines)
Line 0 Link Here
1
package Koha::REST::V1::Patrons::SelfRenewal;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <https://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Mojo::Base 'Mojolicious::Controller';
21
22
use Koha::Patrons;
23
use Koha::Patron::Attribute::Types;
24
25
use Try::Tiny qw( catch try );
26
use JSON      qw( to_json );
27
28
=head1 NAME
29
30
Koha::REST::V1::Patrons::SelfRenewal
31
32
=head1 API
33
34
=head2 Methods
35
36
=head3 start
37
38
Controller function that retrieves the metadata required to begin a patron self renewal
39
40
=cut
41
42
sub start {
43
    my $c = shift->openapi->valid_input or return;
44
45
    my $patron = $c->stash('koha.user');
46
47
    return $c->render( status => 403, openapi => { error => "You are not eligible for self-renewal" } )
48
        if !$patron->is_eligible_for_self_renewal();
49
50
    return try {
51
        my $verification_checks = Koha::Patron::Attribute::Types->search_with_library_limits(
52
            {
53
                '-and' => {
54
                    self_renewal_verification_check => 1,
55
                    '-or'                           => [
56
                        "category_code" => undef,
57
                        "category_code" => $patron->categorycode,
58
                    ]
59
                }
60
            },
61
            {}
62
        );
63
64
        my $category              = $patron->category;
65
        my $self_renewal_settings = {
66
            self_renewal_failure_message => $category->self_renewal_failure_message,
67
            opac_patron_details          => C4::Context->preference('OPACPatronDetails')
68
        };
69
70
        return $c->render(
71
            status  => 200,
72
            openapi => {
73
                verification_checks   => $verification_checks,
74
                self_renewal_settings => $self_renewal_settings
75
            }
76
        );
77
    } catch {
78
        $c->unhandled_exception($_);
79
    };
80
}
81
82
=head3 submit
83
84
Controller function that receives the renewal request and process the renewal
85
86
=cut
87
88
sub submit {
89
    my $c = shift->openapi->valid_input or return;
90
91
    my $patron = $c->stash('koha.user');
92
93
    return try {
94
        Koha::Database->new->schema->txn_do(
95
            sub {
96
                my $body = $c->req->json;
97
98
                return $c->render( status => 403, openapi => { error => "You are not eligible for self-renewal" } )
99
                    if !$patron->is_eligible_for_self_renewal();
100
101
                my $OPACPatronDetails = C4::Context->preference("OPACPatronDetails");
102
                if ($OPACPatronDetails) {
103
                    my $extended_attributes = delete $body->{extended_attributes};
104
                    my $changed_fields      = {};
105
                    my $changes_detected;
106
                    foreach my $key ( keys %$body ) {
107
                        my $submitted_value = $body->{$key};
108
                        my $original_value  = $patron->$key;
109
110
                        if ( $submitted_value ne $original_value ) {
111
                            $changed_fields->{$key} = $submitted_value;
112
                            $changes_detected++;
113
                        }
114
                    }
115
                    if ($changes_detected) {
116
                        $changed_fields->{changed_fields}      = join ',', keys %$changed_fields;
117
                        $changed_fields->{extended_attributes} = to_json($extended_attributes) if $extended_attributes;
118
                        $patron->request_modification($changed_fields);
119
                    }
120
                }
121
122
                my $new_expiry_date = $patron->renew_account;
123
                my $response        = { expiry_date => $new_expiry_date };
124
125
                if ($new_expiry_date) {
126
                    my $is_notice_mandatory = $patron->category->enforce_expiry_notice;
127
                    my $letter_params       = $patron->create_expiry_notice_parameters(
128
                        {
129
                            letter_code => "MEMBERSHIP_RENEWED", is_notice_mandatory => $is_notice_mandatory,
130
                            forceprint  => 1
131
                        }
132
                    );
133
134
                    my $result = $patron->queue_notice($letter_params);
135
                    $response->{confirmation_sent} = 1 if $result->{sent};
136
                }
137
138
                return $c->render(
139
                    status  => 201,
140
                    openapi => $response
141
                );
142
            }
143
        );
144
145
    } catch {
146
        $c->unhandled_exception($_);
147
    };
148
149
}
150
151
1;
(-)a/api/v1/swagger/definitions/patron_self_renewal.yaml (+23 lines)
Line 0 Link Here
1
---
2
type: "object"
3
properties:
4
  verification_steps:
5
    type:
6
      - array
7
      - "null"
8
    description: Any additional verification steps set in patron attribute types
9
  self_renewal_settings:
10
    type:
11
      - object
12
      - "null"
13
    description: The object representing the self-renewal settings for the patron's category
14
  expiry_date:
15
    type:
16
      - string
17
      - "null"
18
    description: The new expiry date on successful self-renewal
19
  confirmation_sent:
20
    type:
21
      - array
22
      - "null"
23
    description: The method(s) through which confirmation of renewal has been sent
(-)a/api/v1/swagger/paths/public_patrons.yaml (+106 lines)
Lines 344-346 Link Here
344
        description: Under maintenance
344
        description: Under maintenance
345
        schema:
345
        schema:
346
          $ref: "../swagger.yaml#/definitions/error"
346
          $ref: "../swagger.yaml#/definitions/error"
347
/public/patrons/self_renewal:
348
  get:
349
    x-mojo-to: Patrons::SelfRenewal#start
350
    operationId: startSelfRenewal
351
    tags:
352
      - patrons
353
    summary: List patrons
354
    produces:
355
      - application/json
356
    parameters:
357
      - $ref: "../swagger.yaml#/parameters/match"
358
      - $ref: "../swagger.yaml#/parameters/order_by"
359
      - $ref: "../swagger.yaml#/parameters/page"
360
      - $ref: "../swagger.yaml#/parameters/per_page"
361
      - $ref: "../swagger.yaml#/parameters/q_param"
362
      - $ref: "../swagger.yaml#/parameters/q_body"
363
      - $ref: "../swagger.yaml#/parameters/request_id_header"
364
    responses:
365
      "200":
366
        description: The metadata required to start patron self-renewal
367
        schema:
368
          type: object
369
          $ref: "../swagger.yaml#/definitions/patron_self_renewal"
370
      "400":
371
        description: |
372
          Bad request. Possible `error_code` attribute values:
373
374
            * `invalid_query`
375
        schema:
376
          $ref: "../swagger.yaml#/definitions/error"
377
      "401":
378
        description: Authentication required
379
        schema:
380
          $ref: "../swagger.yaml#/definitions/error"
381
      "403":
382
        description: Access forbidden
383
        schema:
384
          $ref: "../swagger.yaml#/definitions/error"
385
      "500":
386
        description: |
387
          Internal server error. Possible `error_code` attribute values:
388
389
          * `internal_server_error`
390
        schema:
391
          $ref: "../swagger.yaml#/definitions/error"
392
      "503":
393
        description: Under maintenance
394
        schema:
395
          $ref: "../swagger.yaml#/definitions/error"
396
  post:
397
    x-mojo-to: Patrons::SelfRenewal#submit
398
    operationId: submitSelfRenewal
399
    tags:
400
      - patrons
401
    summary: Submit self-renewal form
402
    consumes:
403
      - application/json
404
    produces:
405
      - application/json
406
    parameters:
407
      - description: A JSON object containing information about the self-renewal
408
        in: body
409
        name: body
410
        required: true
411
        schema:
412
            $ref: "../swagger.yaml#/definitions/patron_self_renewal"
413
    responses:
414
      201:
415
        description: A successfully completed renewal
416
        schema:
417
          items:
418
            $ref: "../swagger.yaml#/definitions/patron_self_renewal"
419
      400:
420
        description: Bad request
421
        schema:
422
          $ref: "../swagger.yaml#/definitions/error"
423
      401:
424
        description: Authentication required
425
        schema:
426
          $ref: "../swagger.yaml#/definitions/error"
427
      403:
428
        description: Access forbidden
429
        schema:
430
          $ref: "../swagger.yaml#/definitions/error"
431
      404:
432
        description: Ressource not found
433
        schema:
434
          $ref: "../swagger.yaml#/definitions/error"
435
      409:
436
        description: Conflict in creating resource
437
        schema:
438
          $ref: "../swagger.yaml#/definitions/error"
439
      413:
440
        description: Payload too large
441
        schema:
442
          $ref: "../swagger.yaml#/definitions/error"
443
      500:
444
        description: |-
445
          Internal server error. Possible `error_code` attribute values:
446
          * `internal_server_error`
447
        schema:
448
          $ref: "../swagger.yaml#/definitions/error"
449
      503:
450
        description: Under maintenance
451
        schema:
452
          $ref: "../swagger.yaml#/definitions/error"
(-)a/koha-tmpl/intranet-tmpl/prog/js/fetch/patron-api-client.js (+17 lines)
Lines 25-30 export class PatronAPIClient { Link Here
25
                }),
25
                }),
26
        };
26
        };
27
    }
27
    }
28
29
    get self_renewal() {
30
        return {
31
            start: (query, params, headers) =>
32
                this.httpClient.getAll({
33
                    endpoint: "public/patrons/self_renewal",
34
                    query,
35
                    params,
36
                    headers,
37
                }),
38
            submit: renewal =>
39
                this.httpClient.post({
40
                    endpoint: "public/patrons/self_renewal",
41
                    body: renewal,
42
                }),
43
        };
44
    }
28
}
45
}
29
46
30
export default PatronAPIClient;
47
export default PatronAPIClient;
(-)a/misc/cronjobs/membership_expiry.pl (-25 / +3 lines)
Lines 285-316 while ( my $expiring_patron = $upcoming_mem_expires->next ) { Link Here
285
        $which_notice = $letter_expiry;
285
        $which_notice = $letter_expiry;
286
    }
286
    }
287
287
288
    my $from_address  = $expiring_patron->library->from_email_address;
289
    my $letter_params = {
290
        module         => 'members',
291
        letter_code    => $which_notice,
292
        branchcode     => $expiring_patron->branchcode,
293
        lang           => $expiring_patron->lang,
294
        borrowernumber => $expiring_patron->borrowernumber,
295
        tables         => {
296
            borrowers => $expiring_patron->borrowernumber,
297
            branches  => $expiring_patron->branchcode,
298
        },
299
    };
300
301
    my $sending_params = {
302
        letter_params => $letter_params,
303
        message_name  => 'Patron_Expiry',
304
        forceprint    => $forceprint
305
    };
306
307
    my $is_notice_mandatory = grep( $expiring_patron->categorycode, @mandatory_expiry_notice_categories );
288
    my $is_notice_mandatory = grep( $expiring_patron->categorycode, @mandatory_expiry_notice_categories );
308
    if ($is_notice_mandatory) {
289
    my $letter_params       = $expiring_patron->create_expiry_notice_parameters(
309
        $sending_params->{expiry_notice_mandatory} = 1;
290
        { letter_code => $which_notice, forceprint => $forceprint, is_notice_mandatory => $is_notice_mandatory } );
310
        $sending_params->{primary_contact_method}  = $forceprint ? 'print' : $expiring_patron->primary_contact_method;
311
    }
312
291
313
    my $result = $expiring_patron->queue_notice($sending_params);
292
    my $result = $expiring_patron->queue_notice($letter_params);
314
    $count_enqueued++ if $result->{sent};
293
    $count_enqueued++ if $result->{sent};
315
}
294
}
316
295
317
- 

Return to bug 26355