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

(-)a/Koha/REST/V1/Patrons/Renewals.pm (+162 lines)
Line 0 Link Here
1
package Koha::REST::V1::Patrons::Renewals;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 3 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
use Modern::Perl;
19
20
use Mojo::Base 'Mojolicious::Controller';
21
22
use Koha::ActionLogs;
23
use Koha::Patrons;
24
use Koha::DateUtils qw/ output_pref /;
25
26
=head1 NAME
27
28
Koha::REST::V1::Patrons::Renewals
29
30
=head1 API
31
32
=head2 Methods
33
34
=head3 get
35
36
Controller function that handles retrieving a patron's renewals, optionally
37
limiting by item id
38
39
=cut
40
41
sub get {
42
    my $c = shift->openapi->valid_input or return;
43
44
    my $patron_id = $c->validation->param('patron_id');
45
    unless ($patron_id) {
46
        return $c->render(
47
            status => 400,
48
            openapi => { error => "Patron ID must be specified." }
49
        );
50
    }
51
52
    my $item_id = $c->validation->param('item_id');
53
    # Create a hash where all keys are embedded values
54
    # Enables easy checking
55
    my $args = $c->req->params->to_hash // {};
56
    my %embed;
57
    my $args_arr = (ref $args->{embed} eq 'ARRAY') ? $args->{embed} : [ $args->{embed} ];
58
    if (defined $args->{embed}) {
59
        %embed = map { $_ => 1 }  @{$args_arr};
60
        delete $args->{embed};
61
    }
62
63
    # Fetch renewals
64
    my $where = {
65
        module => 'CIRCULATION',
66
        action => 'RENEWAL',
67
        object => $patron_id
68
    };
69
    $where->{info} = $item_id if $item_id;
70
    my $renewals = Koha::ActionLogs->search($where);
71
72
    # Now fetch all users associated with these renewals, so we can
73
    # embed their details in the response, if appropriate
74
    my $users = {};
75
    if ($embed{renewed_by} || $embed{patron}) {
76
        my $unique_bn = {};
77
        foreach my $renewal( @{ $renewals->as_list } ) {
78
            $unique_bn->{$renewal->user} = 1;
79
            $unique_bn->{$renewal->object} = 1;
80
        }
81
        my @borrowernumbers = keys %{$unique_bn};
82
        my $users_rs = Koha::Patrons->search({ 
83
            borrowernumber => { 'in' => \@borrowernumbers }
84
        });
85
        foreach my $user( @{ $users_rs->as_list } ) {
86
            $users->{$user->borrowernumber} = $user;
87
        }
88
    }
89
90
    # Prepare the response
91
    my @to_return = ();
92
    foreach my $return( @{$renewals->as_list} ) {
93
        # Map property names
94
        $return = _to_api( $return->TO_JSON );
95
        # Correctly format the timestamp
96
        $return->{timestamp} = output_pref({
97
            str => $return->{timestamp},
98
            dateonly => 1
99
        });
100
        # Embed as appropriate
101
        if ($embed{renewed_by}) {
102
            # If this was a system renewal, create a fake patron to
103
            # return containing basic details
104
            if ($return->{renewed_by_id} == 0) {
105
                $return->{renewed_by} = {
106
                    firstname => 'System',
107
                    surname  => ''
108
                };
109
            } else {
110
                $return->{renewed_by} = $users->{$return->{renewed_by_id}};
111
            }
112
        }
113
        if ($embed{patron}) {
114
            $return->{patron} = $users->{$return->{patron_id}};
115
        }
116
        push @to_return, $return;
117
    }
118
119
    return $c->render( status => 200, openapi => \@to_return );
120
}
121
122
=head3 _to_api
123
124
Helper function that maps Koha::ActionLog objects
125
into REST API attribute names.
126
127
=cut
128
129
sub _to_api {
130
    my $action_log = shift;
131
132
    # Rename attributes
133
    foreach my $column (
134
        keys %{ $Koha::REST::V1::Patrons::Renewals::to_api_mapping }
135
    ) {
136
        my $mapped_column =
137
            $Koha::REST::V1::Patrons::Renewals::to_api_mapping->{$column};
138
        if (exists $action_log->{ $column } && defined $mapped_column ) {
139
            $action_log->{ $mapped_column } = delete $action_log->{ $column };
140
        } elsif (exists $action_log->{ $column } && !defined $mapped_column ) {
141
            delete $action_log->{ $column };
142
        }
143
    }
144
145
    return $action_log;
146
}
147
148
=head2 Global variables
149
150
=head3 $to_api_mapping
151
152
=cut
153
154
our $to_api_mapping = {
155
    timestamp => 'timestamp',
156
    user      => 'renewed_by_id',
157
    object    => 'patron_id',
158
    info      => 'item_id',
159
    interface => 'interface'
160
};
161
162
1;
(-)a/api/v1/swagger/definitions.json (+3 lines)
Lines 35-40 Link Here
35
  "patron_balance": {
35
  "patron_balance": {
36
    "$ref": "definitions/patron_balance.json"
36
    "$ref": "definitions/patron_balance.json"
37
  },
37
  },
38
  "patron_renewals": {
39
    "$ref": "definitions/patron_renewals.json"
40
  },
38
  "allows_renewal": {
41
  "allows_renewal": {
39
    "$ref": "definitions/allows_renewal.json"
42
    "$ref": "definitions/allows_renewal.json"
40
  },
43
  },
(-)a/api/v1/swagger/definitions/patron_renewal.json (+23 lines)
Line 0 Link Here
1
{
2
  "type": "object",
3
  "properties": {
4
    "timestamp": {
5
      "type": "string",
6
      "description": "action timestamp"
7
    },
8
    "user": {
9
      "$ref": "../x-primitives.json#/patron_id"
10
    },
11
    "object": {
12
      "$ref": "../x-primitives.json#/patron_id"
13
    },
14
    "info": {
15
      "type": "integer",
16
      "description": "Item ID"
17
    },
18
    "interface": {
19
      "type": ["string"],
20
      "description": "The interface in which the renewal took place"
21
    }
22
  }
23
}
(-)a/api/v1/swagger/definitions/patron_renewals.json (+6 lines)
Line 0 Link Here
1
{
2
  "type": "array",
3
  "items": {
4
    "$ref": "patron_renewal.json"
5
  }
6
}
(-)a/api/v1/swagger/paths.json (+3 lines)
Lines 71-76 Link Here
71
  "/patrons/{patron_id}/password": {
71
  "/patrons/{patron_id}/password": {
72
    "$ref": "paths/patrons_password.json#/~1patrons~1{patron_id}~1password"
72
    "$ref": "paths/patrons_password.json#/~1patrons~1{patron_id}~1password"
73
  },
73
  },
74
  "/patrons/{patron_id}/renewals": {
75
    "$ref": "paths/patrons_renewals.json#/~1patrons~1{patron_id}~1renewals"
76
  },
74
  "/illrequests": {
77
  "/illrequests": {
75
    "$ref": "paths/illrequests.json#/~1illrequests"
78
    "$ref": "paths/illrequests.json#/~1illrequests"
76
  },
79
  },
(-)a/api/v1/swagger/paths/patrons_renewals.json (+90 lines)
Line 0 Link Here
1
{
2
  "/patrons/{patron_id}/renewals": {
3
    "get": {
4
      "x-mojo-to": "Patrons::Renewals#get",
5
      "operationId": "getPatronRenewals",
6
      "tags": [
7
        "patron"
8
      ],
9
      "parameters": [
10
        {
11
          "$ref": "../parameters.json#/patron_id_pp"
12
        },
13
        {
14
            "name": "item_id",
15
            "in": "query",
16
            "required": false,
17
            "description": "An optional item ID",
18
            "type": "integer"
19
        },
20
        {
21
            "name": "embed",
22
            "in": "query",
23
            "description": "Additional objects that should be embedded in the response",
24
            "required": false,
25
            "type": "array",
26
            "collectionFormat": "csv",
27
            "items": {
28
                "type": "string",
29
                "enum": [
30
                    "patron",
31
                    "renewed_by"
32
                ]
33
            }
34
        }
35
      ],
36
      "produces": [
37
        "application/json"
38
      ],
39
      "responses": {
40
        "200": {
41
          "description": "Patron's renewals",
42
          "schema": {
43
            "$ref": "../definitions.json#/patron_renewals"
44
          }
45
        },
46
        "400": {
47
          "description": "Bad request",
48
          "schema": {
49
            "$ref": "../definitions.json#/error"
50
          }
51
        },
52
        "401": {
53
          "description": "Authentication required",
54
          "schema": {
55
            "$ref": "../definitions.json#/error"
56
          }
57
        },
58
        "403": {
59
          "description": "Access forbidden",
60
          "schema": {
61
            "$ref": "../definitions.json#/error"
62
          }
63
        },
64
        "404": {
65
          "description": "Patron not found",
66
          "schema": {
67
            "$ref": "../definitions.json#/error"
68
          }
69
        },
70
        "500": {
71
          "description": "Internal server error",
72
          "schema": {
73
            "$ref": "../definitions.json#/error"
74
          }
75
        },
76
        "503": {
77
          "description": "Under maintenance",
78
          "schema": {
79
            "$ref": "../definitions.json#/error"
80
          }
81
        }
82
      },
83
      "x-koha-authorization": {
84
        "permissions": {
85
          "borrowers": "edit_borrowers"
86
        }
87
      }
88
    }
89
  }
90
}
(-)a/t/db_dependent/api/v1/patrons_renewals.t (-1 / +160 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/env perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 3 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
use Modern::Perl;
19
20
use Test::More tests => 1;
21
use Test::MockModule;
22
use Test::MockObject;
23
use Test::Mojo;
24
use Test::Warn;
25
26
use t::lib::TestBuilder;
27
use t::lib::Mocks;
28
29
use C4::Auth;
30
use Koha::ActionLogs;
31
use Koha::DateUtils qw( format_sqldatetime );
32
33
my $schema  = Koha::Database->new->schema;
34
my $builder = t::lib::TestBuilder->new;
35
36
t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
37
38
my $remote_address = '127.0.0.1';
39
my $t              = Test::Mojo->new('Koha::REST::V1');
40
41
subtest 'list() tests' => sub {
42
43
    plan tests => 24; 
44
45
    $schema->storage->txn_begin;
46
47
    Koha::ActionLogs->search->delete;
48
    # borrowers => 4 (userflags.sql)
49
    my ( $borrowernumber, $session_id ) = create_user_and_session({ authorized => 4 });
50
51
    ## Authorized user tests
52
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
53
    # No logs, so empty array should be returned
54
    my $tx = $t->ua->build_tx( GET => '/api/v1/patrons/999/renewals');
55
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
56
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
57
    $t->request_ok($tx)->status_is(200)->json_is( [] );
58
59
    # Create log entries
60
    my $log1 = $builder->build_object(
61
        {
62
            class => 'Koha::ActionLogs',
63
            value => {
64
                module    => 'CIRCULATION',
65
                action    => 'RENEWAL',
66
                user      => $borrowernumber, # Renewing staff member
67
                object    => $patron->borrowernumber, # Patron
68
                info      => 101, # Item ID
69
                interface => 'opac' # Renewing interface
70
            }
71
        }
72
    );
73
    my $log2 = $builder->build_object(
74
        {
75
            class => 'Koha::ActionLogs',
76
            value => {
77
                module    => 'CIRCULATION',
78
                action    => 'RENEWAL',
79
                user      => $borrowernumber, # Renewing staff member
80
                object    => $patron->borrowernumber, # Patron
81
                info      => 102, # Item ID
82
                interface => 'opac' # Renewing interface
83
            }
84
        }
85
    );
86
    # Getting all log entries for user
87
    $tx = $t->ua->build_tx( GET => '/api/v1/patrons/'.$patron->borrowernumber.'/renewals');
88
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
89
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
90
    $t->request_ok($tx)->status_is(200)->json_is( '/0/patron_id' => $patron->borrowernumber );
91
    $t->request_ok($tx)->status_is(200)->json_is( '/1/patron_id' => $patron->borrowernumber );
92
93
    # Get log entries filtered by item ID
94
    $tx = $t->ua->build_tx( GET => '/api/v1/patrons/'.$patron->borrowernumber.'/renewals?item_id=102');
95
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
96
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
97
    $t->request_ok($tx)->status_is(200)->json_is( '/0/item_id' => 102 );
98
99
    # Get logs with patron embedded
100
    Koha::ActionLogs->search->delete;
101
    my $embed_patron = $builder->build_object({ class => 'Koha::Patrons' });
102
    my $log_patron = $builder->build_object(
103
        {
104
            class => 'Koha::ActionLogs',
105
            value => {
106
                module    => 'CIRCULATION',
107
                action    => 'RENEWAL',
108
                object    => $embed_patron->borrowernumber, # Patron
109
                user      => $embed_patron->borrowernumber, # Renewing staff member
110
            }
111
        }
112
    );
113
    # Get log entry including patron embed
114
    $tx = $t->ua->build_tx( GET => '/api/v1/patrons/'.$embed_patron->borrowernumber.'/renewals?embed=patron');
115
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
116
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
117
    $t->request_ok($tx)->status_is(200)->json_is( '/0/patron/borrowernumber' => $embed_patron->borrowernumber );
118
    # Get log entry including renewed_by embed
119
    $tx = $t->ua->build_tx( GET => '/api/v1/patrons/'.$embed_patron->borrowernumber.'/renewals?embed=renewed_by');
120
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
121
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
122
    $t->request_ok($tx)->status_is(200)->json_is( '/0/renewed_by/borrowernumber' => $embed_patron->borrowernumber );
123
    # Get log entry including both embeds
124
    $tx = $t->ua->build_tx( GET => '/api/v1/patrons/'.$embed_patron->borrowernumber.'/renewals?embed=patron,renewed_by');
125
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
126
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
127
    $t->request_ok($tx)->status_is(200)->json_is( '/0/patron/borrowernumber' => $embed_patron->borrowernumber );
128
    $t->request_ok($tx)->status_is(200)->json_is( '/0/renewed_by/borrowernumber' => $embed_patron->borrowernumber );
129
130
    $schema->storage->txn_rollback;
131
};
132
133
sub create_user_and_session {
134
135
    my $args = shift;
136
    my $dbh  = C4::Context->dbh;
137
138
    my $flags = ( $args->{authorized} ) ? 2**$args->{authorized} : 0;
139
140
    my $user = $builder->build(
141
        {
142
            source => 'Borrower',
143
            value  => {
144
                flags => $flags
145
            }
146
        }
147
    );
148
149
    # Create a session for the authorized user
150
    my $session = C4::Auth::get_session('');
151
    $session->param( 'number',   $user->{borrowernumber} );
152
    $session->param( 'id',       $user->{userid} );
153
    $session->param( 'ip',       '127.0.0.1' );
154
    $session->param( 'lasttime', time() );
155
    $session->flush;
156
157
    return ( $user->{borrowernumber}, $session->id );
158
}
159
160
1;

Return to bug 23838