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

(-)a/Koha/REST/V1/ImportSources.pm (-37 / +38 lines)
Lines 1-4 Link Here
1
package Koha::REST::V1::ImportSources;
1
package Koha::REST::V1::RecordSources;
2
2
3
# Copyright 2023 Theke Solutions
3
# Copyright 2023 Theke Solutions
4
#
4
#
Lines 21-27 use Modern::Perl; Link Here
21
21
22
use Mojo::Base 'Mojolicious::Controller';
22
use Mojo::Base 'Mojolicious::Controller';
23
23
24
use Koha::ImportSources;
24
use Koha::RecordSources;
25
25
26
use Try::Tiny qw( catch try );
26
use Try::Tiny qw( catch try );
27
27
Lines 37-47 sub list { Link Here
37
    my $c = shift->openapi->valid_input or return;
37
    my $c = shift->openapi->valid_input or return;
38
38
39
    return try {
39
    return try {
40
        my $import_sources_set = Koha::ImportSources->new;
40
        my $record_sources_set = Koha::RecordSources->new;
41
        my $import_sources = $c->objects->search( $import_sources_set );
41
        my $record_sources     = $c->objects->search($record_sources_set);
42
        return $c->render( status => 200, openapi => $import_sources );
42
        return $c->render( status => 200, openapi => $record_sources );
43
    }
43
    } catch {
44
    catch {
45
        $c->unhandled_exception($_);
44
        $c->unhandled_exception($_);
46
    };
45
    };
47
}
46
}
Lines 54-69 sub get { Link Here
54
    my $c = shift->openapi->valid_input or return;
53
    my $c = shift->openapi->valid_input or return;
55
54
56
    return try {
55
    return try {
57
        my $import_sources_set = Koha::ImportSources->new;
56
        my $record_sources_set = Koha::RecordSources->new;
58
        my $import_source = $c->objects->find( $import_sources_set, $c->validation->param('import_source_id') );
57
        my $record_source      = $c->objects->find( $record_sources_set, $c->validation->param('record_source_id') );
59
        unless ($import_source) {
58
        unless ($record_source) {
60
            return $c->render( status  => 404,
59
            return $c->render(
61
                            openapi => { error => "Import source not found" } );
60
                status  => 404,
61
                openapi => { error => "Record source not found" }
62
            );
62
        }
63
        }
63
64
64
        return $c->render( status => 200, openapi => $import_source );
65
        return $c->render( status => 200, openapi => $record_source );
65
    }
66
    } catch {
66
    catch {
67
        $c->unhandled_exception($_);
67
        $c->unhandled_exception($_);
68
    };
68
    };
69
}
69
}
Lines 76-90 sub add { Link Here
76
    my $c = shift->openapi->valid_input or return;
76
    my $c = shift->openapi->valid_input or return;
77
77
78
    return try {
78
    return try {
79
        my $import_source = Koha::ImportSource->new_from_api( $c->validation->param('body') );
79
        my $record_source = Koha::RecordSource->new_from_api( $c->validation->param('body') );
80
        $import_source->store;
80
        $record_source->store;
81
        $c->res->headers->location( $c->req->url->to_string . '/' . $import_source->import_source_id );
81
        $c->res->headers->location( $c->req->url->to_string . '/' . $record_source->record_source_id );
82
        return $c->render(
82
        return $c->render(
83
            status  => 201,
83
            status  => 201,
84
            openapi => $import_source->to_api
84
            openapi => $record_source->to_api
85
        );
85
        );
86
    }
86
    } catch {
87
    catch {
88
        $c->unhandled_exception($_);
87
        $c->unhandled_exception($_);
89
    };
88
    };
90
}
89
}
Lines 96-114 sub add { Link Here
96
sub update {
95
sub update {
97
    my $c = shift->openapi->valid_input or return;
96
    my $c = shift->openapi->valid_input or return;
98
97
99
    my $import_source = Koha::ImportSources->find( $c->validation->param('import_source_id') );
98
    my $record_source = Koha::RecordSources->find( $c->validation->param('record_source_id') );
100
99
101
    if ( not defined $import_source ) {
100
    if ( not defined $record_source ) {
102
        return $c->render( status  => 404,
101
        return $c->render(
103
                           openapi => { error => "Object not found" } );
102
            status  => 404,
103
            openapi => { error => "Object not found" }
104
        );
104
    }
105
    }
105
106
106
    return try {
107
    return try {
107
        $import_source->set_from_api( $c->validation->param('body') );
108
        $record_source->set_from_api( $c->validation->param('body') );
108
        $import_source->store();
109
        $record_source->store();
109
        return $c->render( status => 200, openapi => $import_source->to_api );
110
        return $c->render( status => 200, openapi => $record_source->to_api );
110
    }
111
    } catch {
111
    catch {
112
        $c->unhandled_exception($_);
112
        $c->unhandled_exception($_);
113
    };
113
    };
114
}
114
}
Lines 120-139 sub update { Link Here
120
sub delete {
120
sub delete {
121
    my $c = shift->openapi->valid_input or return;
121
    my $c = shift->openapi->valid_input or return;
122
122
123
    my $import_source = Koha::ImportSources->find( $c->validation->param('import_source_id') );
123
    my $record_source = Koha::RecordSources->find( $c->validation->param('record_source_id') );
124
    if ( not defined $import_source ) {
124
    if ( not defined $record_source ) {
125
        return $c->render( status  => 404,
125
        return $c->render(
126
                           openapi => { error => "Object not found" } );
126
            status  => 404,
127
            openapi => { error => "Object not found" }
128
        );
127
    }
129
    }
128
130
129
    return try {
131
    return try {
130
        $import_source->delete;
132
        $record_source->delete;
131
        return $c->render(
133
        return $c->render(
132
            status  => 204,
134
            status  => 204,
133
            openapi => q{}
135
            openapi => q{}
134
        );
136
        );
135
    }
137
    } catch {
136
    catch {
137
        $c->unhandled_exception($_);
138
        $c->unhandled_exception($_);
138
    };
139
    };
139
}
140
}
(-)a/Koha/ImportSource.pm (-6 / +6 lines)
Lines 1-4 Link Here
1
package Koha::ImportSource;
1
package Koha::RecordSource;
2
2
3
# This file is part of Koha.
3
# This file is part of Koha.
4
#
4
#
Lines 24-30 use base qw(Koha::Object); Link Here
24
24
25
=head1 NAME
25
=head1 NAME
26
26
27
Koha::ImportSource - Koha ImportSource Object class
27
Koha::RecordSource - Koha RecordSource Object class
28
28
29
=head1 API
29
=head1 API
30
30
Lines 32-40 Koha::ImportSource - Koha ImportSource Object class Link Here
32
32
33
=head3 patron
33
=head3 patron
34
34
35
my $patron = $import_source->patron
35
my $patron = $record_source->patron
36
36
37
Return the patron for this import source
37
Return the patron for this record source
38
38
39
=cut
39
=cut
40
40
Lines 50-56 sub patron { Link Here
50
=cut
50
=cut
51
51
52
sub _type {
52
sub _type {
53
    return 'ImportSource';
53
    return 'RecordSource';
54
}
54
}
55
55
56
1;
56
1;
(-)a/Koha/ImportSources.pm (-6 / +6 lines)
Lines 1-4 Link Here
1
package Koha::ImportSources;
1
package Koha::RecordSources;
2
2
3
# This file is part of Koha.
3
# This file is part of Koha.
4
#
4
#
Lines 22-32 use Modern::Perl; Link Here
22
22
23
use base qw(Koha::Objects);
23
use base qw(Koha::Objects);
24
24
25
use Koha::ImportSource;
25
use Koha::RecordSource;
26
26
27
=head1 NAME
27
=head1 NAME
28
28
29
Koha::ImportSources - Koha ImportSources Object class
29
Koha::RecordSources - Koha RecordSources Object class
30
30
31
=head1 API
31
=head1 API
32
32
Lines 37-43 Koha::ImportSources - Koha ImportSources Object class Link Here
37
=cut
37
=cut
38
38
39
sub _type {
39
sub _type {
40
    return 'ImportSource';
40
    return 'RecordSource';
41
}
41
}
42
42
43
=head3 object_class
43
=head3 object_class
Lines 45-51 sub _type { Link Here
45
=cut
45
=cut
46
46
47
sub object_class {
47
sub object_class {
48
    return 'Koha::ImportSource';
48
    return 'Koha::RecordSource';
49
}
49
}
50
50
51
1;
51
1;
(-)a/admin/import-sources.pl (-2 / +2 lines)
Lines 28-37 my $query = CGI->new; Link Here
28
28
29
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
29
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
30
    {
30
    {
31
        template_name   => "admin/import-sources.tt",
31
        template_name   => "admin/record-sources.tt",
32
        query           => $query,
32
        query           => $query,
33
        type            => "intranet",
33
        type            => "intranet",
34
        flagsrequired   => { parameters => 'manage_import_sources' },
34
        flagsrequired   => { parameters => 'manage_record_sources' },
35
    }
35
    }
36
);
36
);
37
37
(-)a/api/v1/swagger/definitions/import_source.yaml (-3 / +3 lines)
Lines 1-12 Link Here
1
---
1
---
2
type: object
2
type: object
3
properties:
3
properties:
4
  import_source_id:
4
  record_source_id:
5
    type: integer
5
    type: integer
6
    description: internally assigned import source identifier
6
    description: internally assigned record source identifier
7
    readOnly: true
7
    readOnly: true
8
  name:
8
  name:
9
    description: import source name
9
    description: record source name
10
    type: string
10
    type: string
11
  patron_id:
11
  patron_id:
12
    description: linked patron identifier
12
    description: linked patron identifier
(-)a/api/v1/swagger/paths/import_sources.yaml (-39 / +38 lines)
Lines 1-10 Link Here
1
/import_sources:
1
/record_sources:
2
  get:
2
  get:
3
    x-mojo-to: ImportSources#list
3
    x-mojo-to: RecordSources#list
4
    operationId: listImportSources
4
    operationId: listRecordSources
5
    summary: List import sources
5
    summary: List record sources
6
    tags:
6
    tags:
7
      - import_sources
7
      - record_sources
8
    parameters:
8
    parameters:
9
      - $ref: "../swagger.yaml#/parameters/match"
9
      - $ref: "../swagger.yaml#/parameters/match"
10
      - $ref: "../swagger.yaml#/parameters/order_by"
10
      - $ref: "../swagger.yaml#/parameters/order_by"
Lines 12-18 Link Here
12
      - $ref: "../swagger.yaml#/parameters/per_page"
12
      - $ref: "../swagger.yaml#/parameters/per_page"
13
      - $ref: "../swagger.yaml#/parameters/q_param"
13
      - $ref: "../swagger.yaml#/parameters/q_param"
14
      - $ref: "../swagger.yaml#/parameters/q_body"
14
      - $ref: "../swagger.yaml#/parameters/q_body"
15
      - $ref: "../swagger.yaml#/parameters/q_header"
16
      - $ref: "../swagger.yaml#/parameters/request_id_header"
15
      - $ref: "../swagger.yaml#/parameters/request_id_header"
17
      - name: x-koha-embed
16
      - name: x-koha-embed
18
        in: header
17
        in: header
Lines 30-36 Link Here
30
      - application/json
29
      - application/json
31
    responses:
30
    responses:
32
      "200":
31
      "200":
33
        description: A list of import sources
32
        description: A list of record sources
34
      "400":
33
      "400":
35
        description: Missing or wrong parameters
34
        description: Missing or wrong parameters
36
        schema:
35
        schema:
Lines 56-82 Link Here
56
          $ref: "../swagger.yaml#/definitions/error"
55
          $ref: "../swagger.yaml#/definitions/error"
57
    x-koha-authorization:
56
    x-koha-authorization:
58
      permissions:
57
      permissions:
59
        parameters: manage_import_sources
58
        parameters: manage_record_sources
60
  post:
59
  post:
61
    x-mojo-to: ImportSources#add
60
    x-mojo-to: RecordSources#add
62
    operationId: addImportSources
61
    operationId: addRecordSources
63
    summary: Add an import source
62
    summary: Add a record source
64
    tags:
63
    tags:
65
      - import_sources
64
      - record_sources
66
    parameters:
65
    parameters:
67
      - name: body
66
      - name: body
68
        in: body
67
        in: body
69
        description: A JSON object containing informations about the new import source
68
        description: A JSON object containing informations about the new record source
70
        required: true
69
        required: true
71
        schema:
70
        schema:
72
          $ref: "../swagger.yaml#/definitions/import_source"
71
          $ref: "../swagger.yaml#/definitions/record_source"
73
    consumes:
72
    consumes:
74
      - application/json
73
      - application/json
75
    produces:
74
    produces:
76
      - application/json
75
      - application/json
77
    responses:
76
    responses:
78
      "201":
77
      "201":
79
        description: An import source
78
        description: A record source
80
      "400":
79
      "400":
81
        description: Missing or wrong parameters
80
        description: Missing or wrong parameters
82
        schema:
81
        schema:
Lines 102-124 Link Here
102
          $ref: "../swagger.yaml#/definitions/error"
101
          $ref: "../swagger.yaml#/definitions/error"
103
    x-koha-authorization:
102
    x-koha-authorization:
104
      permissions:
103
      permissions:
105
        parameters: manage_import_sources
104
        parameters: manage_record_sources
106
"/import_sources/{import_source_id}":
105
"/record_sources/{record_source_id}":
107
  get:
106
  get:
108
    x-mojo-to: ImportSources#get
107
    x-mojo-to: RecordSources#get
109
    operationId: getImportSources
108
    operationId: getRecordSources
110
    summary: Get an import source
109
    summary: Get a record source
111
    tags:
110
    tags:
112
      - import_sources
111
      - record_sources
113
    parameters:
112
    parameters:
114
      - $ref: "../swagger.yaml#/parameters/import_source_id_pp"
113
      - $ref: "../swagger.yaml#/parameters/record_source_id_pp"
115
    consumes:
114
    consumes:
116
      - application/json
115
      - application/json
117
    produces:
116
    produces:
118
      - application/json
117
      - application/json
119
    responses:
118
    responses:
120
      "200":
119
      "200":
121
        description: An import source
120
        description: A record source
122
      "400":
121
      "400":
123
        description: Missing or wrong parameters
122
        description: Missing or wrong parameters
124
        schema:
123
        schema:
Lines 148-175 Link Here
148
          $ref: "../swagger.yaml#/definitions/error"
147
          $ref: "../swagger.yaml#/definitions/error"
149
    x-koha-authorization:
148
    x-koha-authorization:
150
      permissions:
149
      permissions:
151
        parameters: manage_import_sources
150
        parameters: manage_record_sources
152
  put:
151
  put:
153
    x-mojo-to: ImportSources#update
152
    x-mojo-to: RecordSources#update
154
    operationId: updateImportSources
153
    operationId: updateRecordSources
155
    summary: Update an import source
154
    summary: Update a record source
156
    tags:
155
    tags:
157
      - import_sources
156
      - record_sources
158
    parameters:
157
    parameters:
159
      - $ref: "../swagger.yaml#/parameters/import_source_id_pp"
158
      - $ref: "../swagger.yaml#/parameters/record_source_id_pp"
160
      - name: body
159
      - name: body
161
        in: body
160
        in: body
162
        description: A JSON object containing informations about the new import source
161
        description: A JSON object containing informations about the new record source
163
        required: true
162
        required: true
164
        schema:
163
        schema:
165
          $ref: "../swagger.yaml#/definitions/import_source"
164
          $ref: "../swagger.yaml#/definitions/record_source"
166
    consumes:
165
    consumes:
167
      - application/json
166
      - application/json
168
    produces:
167
    produces:
169
      - application/json
168
      - application/json
170
    responses:
169
    responses:
171
      "200":
170
      "200":
172
        description: An import source
171
        description: A record source
173
      "400":
172
      "400":
174
        description: Missing or wrong parameters
173
        description: Missing or wrong parameters
175
        schema:
174
        schema:
Lines 199-213 Link Here
199
          $ref: "../swagger.yaml#/definitions/error"
198
          $ref: "../swagger.yaml#/definitions/error"
200
    x-koha-authorization:
199
    x-koha-authorization:
201
      permissions:
200
      permissions:
202
        parameters: manage_import_sources
201
        parameters: manage_record_sources
203
  delete:
202
  delete:
204
    x-mojo-to: ImportSources#delete
203
    x-mojo-to: RecordSources#delete
205
    operationId: deleteImportSources
204
    operationId: deleteRecordSources
206
    summary: Delete an import source
205
    summary: Delete a record source
207
    tags:
206
    tags:
208
      - import_sources
207
      - record_sources
209
    parameters:
208
    parameters:
210
      - $ref: "../swagger.yaml#/parameters/import_source_id_pp"
209
      - $ref: "../swagger.yaml#/parameters/record_source_id_pp"
211
    consumes:
210
    consumes:
212
      - application/json
211
      - application/json
213
    produces:
212
    produces:
Lines 244-247 Link Here
244
          $ref: "../swagger.yaml#/definitions/error"
243
          $ref: "../swagger.yaml#/definitions/error"
245
    x-koha-authorization:
244
    x-koha-authorization:
246
      permissions:
245
      permissions:
247
        parameters: manage_import_sources
246
        parameters: manage_record_sources
(-)a/api/v1/swagger/swagger.yaml (-12 / +12 lines)
Lines 70-77 definitions: Link Here
70
    $ref: ./definitions/import_batch_profiles.yaml
70
    $ref: ./definitions/import_batch_profiles.yaml
71
  import_record_match:
71
  import_record_match:
72
    $ref: ./definitions/import_record_match.yaml
72
    $ref: ./definitions/import_record_match.yaml
73
  import_source:
73
  record_source:
74
    $ref: ./definitions/import_source.yaml
74
    $ref: ./definitions/record_source.yaml
75
  invoice:
75
  invoice:
76
    $ref: ./definitions/invoice.yaml
76
    $ref: ./definitions/invoice.yaml
77
  item:
77
  item:
Lines 277-286 paths: Link Here
277
    $ref: ./paths/import_batch_profiles.yaml#/~1import_batch_profiles
277
    $ref: ./paths/import_batch_profiles.yaml#/~1import_batch_profiles
278
  "/import_batch_profiles/{import_batch_profile_id}":
278
  "/import_batch_profiles/{import_batch_profile_id}":
279
    $ref: "./paths/import_batch_profiles.yaml#/~1import_batch_profiles~1{import_batch_profile_id}"
279
    $ref: "./paths/import_batch_profiles.yaml#/~1import_batch_profiles~1{import_batch_profile_id}"
280
  /import_sources:
280
  /record_sources:
281
    $ref: ./paths/import_sources.yaml#/~1import_sources
281
    $ref: ./paths/record_sources.yaml#/~1record_sources
282
  "/import_sources/{import_source_id}":
282
  "/record_sources/{record_source_id}":
283
    $ref: ./paths/import_sources.yaml#/~1import_sources~1{import_source_id}
283
    $ref: ./paths/record_sources.yaml#/~1record_sources~1{record_source_id}
284
  /items:
284
  /items:
285
    $ref: ./paths/items.yaml#/~1items
285
    $ref: ./paths/items.yaml#/~1items
286
  "/items/{item_id}":
286
  "/items/{item_id}":
Lines 539-548 parameters: Link Here
539
    name: import_record_id
539
    name: import_record_id
540
    required: true
540
    required: true
541
    type: integer
541
    type: integer
542
  import_source_id_pp:
542
  record_source_id_pp:
543
    description: Internal import source identifier
543
    description: Internal record source identifier
544
    in: path
544
    in: path
545
    name: import_source_id
545
    name: record_source_id
546
    required: true
546
    required: true
547
    type: integer
547
    type: integer
548
  item_id_pp:
548
  item_id_pp:
Lines 842-850 tags: Link Here
842
  - description: "Manage item groups\n"
842
  - description: "Manage item groups\n"
843
    name: item_groups
843
    name: item_groups
844
    x-displayName: Item groups
844
    x-displayName: Item groups
845
  - description: "Manage import sources\n"
845
  - description: "Manage record sources\n"
846
    name: import_sources
846
    name: record_sources
847
    x-displayName: Import source
847
    x-displayName: Record source
848
  - description: "Manage items\n"
848
  - description: "Manage items\n"
849
    name: items
849
    name: items
850
    x-displayName: Items
850
    x-displayName: Items
(-)a/debian/templates/apache-shared-intranet.conf (+2 lines)
Lines 13-18 ScriptAlias /search "/usr/share/koha/intranet/cgi-bin/catalogue/search.pl" Link Here
13
13
14
# Protect dev package install
14
# Protect dev package install
15
RewriteEngine on
15
RewriteEngine on
16
16
RewriteRule ^/cgi-bin/koha/(C4|debian|etc|installer/data|install_misc|Koha|misc|selenium|t|test|tmp|xt)/|\.PL$ /notfound [PT]
17
RewriteRule ^/cgi-bin/koha/(C4|debian|etc|installer/data|install_misc|Koha|misc|selenium|t|test|tmp|xt)/|\.PL$ /notfound [PT]
17
18
18
RewriteRule ^/bib/([^\/]*)/?$ /cgi-bin/koha/catalogue/detail.pl?biblionumber=$1 [PT]
19
RewriteRule ^/bib/([^\/]*)/?$ /cgi-bin/koha/catalogue/detail.pl?biblionumber=$1 [PT]
Lines 21-26 RewriteRule ^/issn/([^\/]*)/?$ /search?q=issn:$1 [PT] Link Here
21
RewriteRule ^(.*)_[0-9]{2}\.[0-9]{7}\.(js|css)$ $1.$2 [L]
22
RewriteRule ^(.*)_[0-9]{2}\.[0-9]{7}\.(js|css)$ $1.$2 [L]
22
23
23
RewriteRule ^/cgi-bin/koha/erm/.*$ /cgi-bin/koha/erm/erm.pl [PT]
24
RewriteRule ^/cgi-bin/koha/erm/.*$ /cgi-bin/koha/erm/erm.pl [PT]
25
RewriteRule ^/cgi-bin/koha/admin/record-sources(.*)?$ /cgi-bin/koha/admin/record-sources.pl$1 [PT]
24
26
25
Alias "/api" "/usr/share/koha/api"
27
Alias "/api" "/usr/share/koha/api"
26
<Directory "/usr/share/koha/api">
28
<Directory "/usr/share/koha/api">
(-)a/installer/data/mysql/atomicupdate/bug_32607.pl (-12 / +19 lines)
Lines 2-32 use Modern::Perl; Link Here
2
2
3
return {
3
return {
4
    bug_number => "32607",
4
    bug_number => "32607",
5
    description => "Add import_source table",
5
    description => "Add record_sources table",
6
    up => sub {
6
    up => sub {
7
        my ($args) = @_;
7
        my ($args) = @_;
8
        my ($dbh, $out) = @$args{qw(dbh out)};
8
        my ($dbh, $out) = @$args{qw(dbh out)};
9
        # Do you stuffs here
9
        # Do you stuffs here
10
        if(!TableExists('import_source')) {
10
        if(!TableExists('record_sources')) {
11
            $dbh->do(q{
11
            $dbh->do(q{
12
                CREATE TABLE `import_source` (
12
                --
13
                    `import_source_id` int(11) NOT NULL AUTO_INCREMENT,
13
                -- Table structure for table `record_sources`
14
                    `name` text NOT NULL,
14
                --
15
                    `patron_id` int(11) NOT NULL,
15
16
                    PRIMARY KEY (`import_source_id`),
16
                DROP TABLE IF EXISTS `record_sources`;
17
                    CONSTRAINT `import_source_fk_1` FOREIGN KEY (`patron_id`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE CASCADE ON UPDATE CASCADE
17
                /*!40101 SET @saved_cs_client     = @@character_set_client */;
18
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
18
                /*!40101 SET character_set_client = utf8 */;
19
                CREATE TABLE `record_sources` (
20
                `record_source_id` int(11) NOT NULL AUTO_INCREMENT,
21
                `name` text NOT NULL,
22
                `patron_id` int(11) NOT NULL,
23
                PRIMARY KEY (`record_source_id`),
24
                CONSTRAINT `record_source_fk_1` FOREIGN KEY (`patron_id`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE CASCADE ON UPDATE CASCADE
25
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
19
            });
26
            });
20
        }
27
        }
21
28
22
        say $out "Added new import_source table";
29
        say $out "Added new record_sources table";
23
30
24
        $dbh->do(q{
31
        $dbh->do(q{
25
            INSERT IGNORE INTO permissions (module_bit, code, description) VALUES
32
            INSERT IGNORE INTO permissions (module_bit, code, description) VALUES
26
            ( 3, 'manage_import_sources', 'Manage import sources')
33
            ( 3, 'manage_record_sources', 'Manage record sources')
27
        });
34
        });
28
35
29
        say $out "Added new manage_import_sources permission";
36
        say $out "Added new manage_record_sources permission";
30
37
31
    },
38
    },
32
};
39
};
(-)a/installer/data/mysql/kohastructure.sql (-6 / +6 lines)
Lines 3468-3485 CREATE TABLE `import_items` ( Link Here
3468
/*!40101 SET character_set_client = @saved_cs_client */;
3468
/*!40101 SET character_set_client = @saved_cs_client */;
3469
3469
3470
--
3470
--
3471
-- Table structure for table `import_source`
3471
-- Table structure for table `record_sources`
3472
--
3472
--
3473
3473
3474
DROP TABLE IF EXISTS `import_source`;
3474
DROP TABLE IF EXISTS `record_sources`;
3475
/*!40101 SET @saved_cs_client     = @@character_set_client */;
3475
/*!40101 SET @saved_cs_client     = @@character_set_client */;
3476
/*!40101 SET character_set_client = utf8 */;
3476
/*!40101 SET character_set_client = utf8 */;
3477
CREATE TABLE `import_source` (
3477
CREATE TABLE `record_sources` (
3478
  `import_source_id` int(11) NOT NULL AUTO_INCREMENT,
3478
  `record_source_id` int(11) NOT NULL AUTO_INCREMENT,
3479
  `name` text NOT NULL,
3479
  `name` text NOT NULL,
3480
  `patron_id` int(11) NOT NULL,
3480
  `patron_id` int(11) NOT NULL,
3481
  PRIMARY KEY (`import_source_id`),
3481
  PRIMARY KEY (`record_source_id`),
3482
  CONSTRAINT `import_source_fk_1` FOREIGN KEY (`patron_id`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE CASCADE ON UPDATE CASCADE
3482
  CONSTRAINT `record_source_fk_1` FOREIGN KEY (`patron_id`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE CASCADE ON UPDATE CASCADE
3483
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
3483
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
3484
--
3484
--
3485
-- Table structure for table `import_record_matches`
3485
-- Table structure for table `import_record_matches`
(-)a/installer/data/mysql/mandatory/userpermissions.sql (-1 / +1 lines)
Lines 42-48 INSERT INTO permissions (module_bit, code, description) VALUES Link Here
42
   ( 3, 'manage_curbside_pickups', 'Manage curbside pickups'),
42
   ( 3, 'manage_curbside_pickups', 'Manage curbside pickups'),
43
   ( 3, 'manage_search_filters', 'Manage custom search filters'),
43
   ( 3, 'manage_search_filters', 'Manage custom search filters'),
44
   ( 3, 'manage_identity_providers', 'Manage identity providers'),
44
   ( 3, 'manage_identity_providers', 'Manage identity providers'),
45
   ( 3, 'manage_import_sources', 'Manage import sources'),
45
   ( 3, 'manage_record_sources', 'Manage record sources'),
46
   ( 4, 'delete_borrowers', 'Delete patrons'),
46
   ( 4, 'delete_borrowers', 'Delete patrons'),
47
   ( 4, 'edit_borrowers', 'Add, modify and view patron information'),
47
   ( 4, 'edit_borrowers', 'Add, modify and view patron information'),
48
   ( 4, 'view_borrower_infos_from_any_libraries', 'View patron infos from any libraries'),
48
   ( 4, 'view_borrower_infos_from_any_libraries', 'View patron infos from any libraries'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt (-2 / +4 lines)
Lines 206-213 Link Here
206
                        <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration (Elasticsearch)</a></dt>
206
                        <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration (Elasticsearch)</a></dt>
207
                        <dd>Manage indexes, facets, and their mappings to MARC fields and subfields</dd>
207
                        <dd>Manage indexes, facets, and their mappings to MARC fields and subfields</dd>
208
                    [% END %]
208
                    [% END %]
209
                    <dt><a href="/cgi-bin/koha/admin/import-sources.pl">Import sources</a></dt>
209
                    [% IF ( CAN_user_parameters_manage_record_sources ) %]
210
                    <dd>Define sources to import from</dd>
210
                        <dt><a href="/cgi-bin/koha/admin/record-sources">Record sources</a></dt>
211
                        <dd>Define record sources to import from</dd>
212
                    [% END %]
211
                </dl>
213
                </dl>
212
            [% END %]
214
            [% END %]
213
215
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/import-sources.tt (-3 / +3 lines)
Lines 8-14 Link Here
8
[% PROCESS 'i18n.inc' %]
8
[% PROCESS 'i18n.inc' %]
9
[% INCLUDE 'doc-head-open.inc' %]
9
[% INCLUDE 'doc-head-open.inc' %]
10
<title>
10
<title>
11
    Import sources &rsaquo; Koha
11
    Record sources &rsaquo; Koha
12
</title>
12
</title>
13
[% INCLUDE 'doc-head-close.inc' %]
13
[% INCLUDE 'doc-head-close.inc' %]
14
</head>
14
</head>
Lines 18-24 Link Here
18
    [% INCLUDE 'erm-search.inc' %]
18
    [% INCLUDE 'erm-search.inc' %]
19
[% END %]
19
[% END %]
20
20
21
<div id="import-source"> <!-- this is closed in intranet-bottom.inc -->
21
<div id="record-source"> <!-- this is closed in intranet-bottom.inc -->
22
22
23
[% MACRO jsinclude BLOCK %]
23
[% MACRO jsinclude BLOCK %]
24
    [% INCLUDE 'calendar.inc' %]
24
    [% INCLUDE 'calendar.inc' %]
Lines 31-37 Link Here
31
      const RESTOAuth2ClientCredentials = [% IF Koha.Preference('RESTOAuth2ClientCredentials') %]true[% ELSE %]false[% END %];
31
      const RESTOAuth2ClientCredentials = [% IF Koha.Preference('RESTOAuth2ClientCredentials') %]true[% ELSE %]false[% END %];
32
    </script>
32
    </script>
33
33
34
    [% Asset.js("js/vue/dist/import-source.js") | $raw %]
34
    [% Asset.js("js/vue/dist/record-source.js") | $raw %]
35
35
36
[% END %]
36
[% END %]
37
[% INCLUDE 'intranet-bottom.inc' %]
37
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Form.vue (-144 lines)
Lines 1-144 Link Here
1
<template>
2
    <FormKit
3
        v-if="!loading"
4
        ref="form"
5
        @submit="doSubmit"
6
        type="form"
7
        :actions="false"
8
        :value="data"
9
    >
10
        <FormKitSchema :schema="formSchema" :data="schemaData"></FormKitSchema>
11
    </FormKit>
12
    <div v-else>Loading...</div>
13
</template>
14
<script>
15
import { FormKitSchema } from "@formkit/vue"
16
import { $__ } from "../i18n"
17
18
export default {
19
    name: "Form",
20
    data() {
21
        return {
22
            schemaData: {
23
                doCancel: () => {
24
                    this.$emit("cancel")
25
                },
26
            },
27
            formSchema: [
28
                {
29
                    $el: "fieldset",
30
                    attrs: {
31
                        class: "rows",
32
                    },
33
                    children: this.schema,
34
                },
35
                {
36
                    $el: "fieldset",
37
                    attrs: {
38
                        class: "action",
39
                    },
40
                    children: [
41
                        {
42
                            $el: "input",
43
                            attrs: {
44
                                type: "submit",
45
                                class: "btn btn-primary",
46
                            },
47
                            ignore: false,
48
                            children: this.submitMessage,
49
                        },
50
                        {
51
                            $el: "a",
52
                            attrs: {
53
                                class: "cancel",
54
                                onClick: "$doCancel",
55
                            },
56
                            children: this.cancelMessage,
57
                        },
58
                    ],
59
                },
60
            ],
61
        }
62
    },
63
    methods: {
64
        async doSubmit(event) {
65
            this.$emit("submit", event)
66
        },
67
    },
68
    components: {
69
        FormKitSchema,
70
    },
71
    emits: ["submit", "cancel"],
72
    props: {
73
        schema: Object,
74
        loading: {
75
            type: Boolean,
76
            default: false,
77
        },
78
        data: {
79
            type: Object,
80
            default: {},
81
        },
82
        submitMessage: {
83
            type: String,
84
            default: $__("Submit"),
85
        },
86
        cancelMessage: {
87
            type: String,
88
            default: $__("Cancel"),
89
        },
90
    },
91
}
92
</script>
93
94
<style>
95
.formkit-value {
96
    align-items: center;
97
    background-color: #f5f5f5;
98
    border-radius: 0.25em;
99
    box-sizing: border-box;
100
    color: black;
101
    display: flex;
102
    justify-content: space-between;
103
    padding: 0.55em 0.5em;
104
    text-decoration: none;
105
    width: 100%;
106
}
107
108
a.formkit-value:hover {
109
    text-decoration: none;
110
}
111
112
.formkit-value::after {
113
    content: "\00D7";
114
    margin-left: 0.5em;
115
    font-size: 1.1em;
116
}
117
118
.formkit-dropdown {
119
    position: absolute;
120
    top: 100%;
121
    left: 0;
122
    min-width: 15em;
123
    background-color: white;
124
    box-shadow: 0 0 0.5em rgb(0 0 0 / 10%);
125
    margin: 0;
126
    padding: 0;
127
    list-style-type: none;
128
    overflow: hidden;
129
    border-radius: 0.25em;
130
}
131
132
.formkit-dropdown-item[data-selected="true"] {
133
    background-color: #cfe8fc;
134
}
135
136
.formkit-dropdown-item {
137
    padding: 0.5em;
138
    border-bottom: 1px solid #e4e4e4;
139
}
140
141
a.cancel {
142
    cursor: pointer;
143
}
144
</style>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Page.vue (-1 / +3 lines)
Lines 16-22 Link Here
16
                    </div>
16
                    </div>
17
                </template>
17
                </template>
18
                <template v-else>
18
                <template v-else>
19
                    <div class="col-sm-12">
19
                    <div
20
                        class="col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2"
21
                    >
20
                        <Dialog></Dialog>
22
                        <Dialog></Dialog>
21
                        <slot></slot>
23
                        <slot></slot>
22
                    </div>
24
                    </div>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/import-sources/ISEdit.vue (-80 lines)
Lines 1-80 Link Here
1
<template>
2
    <h1>{{ title }}</h1>
3
    <Form
4
        :schema="schema"
5
        :data="data"
6
        @submit="processSubmit"
7
        @cancel="doCancel"
8
        :loading="loading"
9
    ></Form>
10
</template>
11
12
<script>
13
import Form from "../Form.vue"
14
import { inject } from "vue"
15
import { ImportSourcesClient } from "../../fetch/import-sources"
16
17
export default {
18
    name: "ISEdit",
19
    setup() {
20
        const { getFormSchema } = inject("modelStore")
21
        const api = new ImportSourcesClient()
22
        const { setMessage } = inject("mainStore")
23
        const schema = getFormSchema()
24
        return {
25
            schema,
26
            setMessage,
27
            api,
28
        }
29
    },
30
    data() {
31
        return {
32
            data: null,
33
            loading: true,
34
        }
35
    },
36
    methods: {
37
        async processSubmit(data) {
38
            let response
39
            let responseMessage
40
            if (data.import_source_id) {
41
                const { import_source_id: id, ...row } = data
42
                response = await this.api.update({ id, row })
43
                responseMessage = this.$__("Import source updated!")
44
            } else {
45
                response = await this.api.add({ row: data })
46
                responseMessage = this.$__("Import source created!")
47
            }
48
            if (response) {
49
                this.setMessage(responseMessage)
50
                return this.$router.push({ path: "../import-sources.pl" })
51
            }
52
        },
53
        doCancel() {
54
            this.$router.push({ path: "../import-sources.pl" })
55
        },
56
    },
57
    async created() {
58
        const { id } = this.$route.params
59
        if (id !== undefined) {
60
            const response = await this.api.getOne({
61
                id,
62
            })
63
            this.data = response
64
        }
65
        this.loading = false
66
    },
67
    computed: {
68
        title() {
69
            if (!this.data) return this.$__("Add import source")
70
            return this.$__("Edit %s").format(this.data.name)
71
        },
72
    },
73
    beforeMount() {
74
        this.$root.setTitle(this.title)
75
    },
76
    components: {
77
        Form,
78
    },
79
}
80
</script>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/import-sources/ISList.vue (-100 lines)
Lines 1-100 Link Here
1
<template>
2
    <div id="toolbar" class="btn-toolbar">
3
        <div class="btn-group">
4
            <button class="btn btn-default" @click="newImportSource">
5
                <i class="fa fa-plus"></i> New import source
6
            </button>
7
        </div>
8
    </div>
9
    <h1>{{ title }}</h1>
10
    <KohaTable
11
        v-bind="tableOptions"
12
        @edit="doEdit"
13
        @delete="doRemove"
14
    ></KohaTable>
15
</template>
16
17
<script>
18
import { useRouter } from "vue-router"
19
import { inject } from "vue"
20
import { ImportSourcesClient } from "../../fetch/import-sources"
21
import KohaTable from "../KohaTable.vue"
22
export default {
23
    name: "ISList",
24
    data() {
25
        return {
26
            title: this.$__("Import Sources"),
27
            tableOptions: {
28
                columns: this.columns,
29
                options: {
30
                    embed: "patron",
31
                },
32
                actions: {
33
                    "-1": ["edit", "delete"],
34
                },
35
                url: "/api/v1/import_sources",
36
            },
37
        }
38
    },
39
    setup() {
40
        const { getTableColumns } = inject("modelStore")
41
        const { setWarning, setMessage, setError, setConfirmationDialog } =
42
            inject("mainStore")
43
        const api = new ImportSourcesClient()
44
        const columns = getTableColumns()
45
        return {
46
            columns,
47
            setWarning,
48
            setMessage,
49
            setError,
50
            setConfirmationDialog,
51
            api,
52
        }
53
    },
54
    beforeMount() {
55
        this.$root.setTitle(this.title)
56
    },
57
    methods: {
58
        newImportSource() {
59
            this.$router.push({ path: "import-sources.pl/add" })
60
        },
61
        doEdit(data) {
62
            this.$router.push({
63
                path: `import-sources.pl/${data.import_source_id}`,
64
                props: {
65
                    data,
66
                },
67
            })
68
        },
69
        async doRemove(data, dt) {
70
            this.setConfirmationDialog(
71
                {
72
                    title: this.$__(
73
                        "Are you sure you want to remove this import source?"
74
                    ),
75
                    message: data.name,
76
                    accept_label: this.$__("Yes, remove"),
77
                    cancel_label: this.$__("No, do not remove"),
78
                },
79
                async () => {
80
                    const response = await this.api.remove({
81
                        id: data.import_source_id,
82
                    })
83
                    if (response) {
84
                        this.setMessage(
85
                            this.$__("Import source %s removed").format(
86
                                data.name
87
                            ),
88
                            true
89
                        )
90
                        dt.draw()
91
                    }
92
                }
93
            )
94
        },
95
    },
96
    components: {
97
        KohaTable,
98
    },
99
}
100
</script>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/record-sources/Edit.vue (+154 lines)
Line 0 Link Here
1
<template>
2
    <h1>{{ title }}</h1>
3
    <form @submit="processSubmit">
4
        <fieldset class="rows">
5
            <ol>
6
                <li>
7
                    <label class="required" for="name">
8
                        {{ $__("Name") }}:
9
                    </label>
10
                    <input
11
                        id="name"
12
                        v-model="row.name"
13
                        :placeholder="$__('Name')"
14
                        required
15
                    />
16
                    <span class="required">{{ $__("Required") }}</span>
17
                </li>
18
                <li>
19
                    <label :for="`user_id`" class="required"
20
                        >{{ $__("User") }}:</label
21
                    >
22
                    <span class="user">
23
                        {{ patron_str }}
24
                    </span>
25
                    (<a
26
                        href="#"
27
                        @click="selectUser()"
28
                        class="btn btn-default"
29
                        >{{ $__("Select user") }}</a
30
                    >)
31
                    <span class="required">{{ $__("Required") }}</span>
32
                </li>
33
                <input
34
                    type="hidden"
35
                    name="selected_patron_id"
36
                    id="selected_patron_id"
37
                />
38
            </ol>
39
        </fieldset>
40
        <fieldset class="action">
41
            <input type="submit" :value="$__('Submit')" />
42
            <a @click="doCancel($event)" class="router-link-active cancel">{{
43
                $__("Cancel")
44
            }}</a>
45
        </fieldset>
46
    </form>
47
</template>
48
49
<script>
50
import { inject } from "vue"
51
import { RecordSourcesClient } from "../../fetch/record-sources"
52
import { APIClient } from "../../fetch/api-client.js"
53
let {
54
    patron: { patrons },
55
} = APIClient
56
57
export default {
58
    name: "Edit",
59
    setup() {
60
        const { record_source } = new RecordSourcesClient()
61
        const { setMessage } = inject("mainStore")
62
        return {
63
            setMessage,
64
            api: record_source,
65
        }
66
    },
67
    data() {
68
        return {
69
            row: {
70
                name: "",
71
            },
72
            loading: true,
73
            patron_str: "",
74
        }
75
    },
76
    methods: {
77
        processSubmit(event) {
78
            event.preventDefault()
79
            let response
80
            if (this.row.record_source_id) {
81
                const { record_source_id: id, ...row } = this.row
82
                response = this.api
83
                    .update({ id, row })
84
                    .then(() => this.$__("Record source updated!"))
85
            } else {
86
                response = this.api
87
                    .create({ row: this.row })
88
                    .then(() => this.$__("Record source created!"))
89
            }
90
            return response.then(responseMessage => {
91
                this.setMessage(responseMessage)
92
                return this.$router.push({ path: "../record-sources" })
93
            })
94
        },
95
        selectUser() {
96
            let select_user_window = window.open(
97
                "/cgi-bin/koha/members/search.pl?columns=cardnumber,name,category,branch,action&selection_type=select",
98
                "PatronPopup",
99
                "width=740,height=450,location=yes,toolbar=no," +
100
                    "scrollbars=yes,resize=yes"
101
            )
102
            // This is a bit dirty, the "select user" window should be rewritten and be a Vue component
103
            // but that's not for now...
104
            select_user_window.addEventListener(
105
                "beforeunload",
106
                this.newUserSelected,
107
                false
108
            )
109
        },
110
        newUserSelected() {
111
            this.row.patron_id =
112
                document.getElementById("selected_patron_id").value
113
            this.getUserData()
114
        },
115
        getUserData() {
116
            patrons.get(this.row.patron_id).then(patron => {
117
                this.patron_str = $patron_to_html(patron)
118
            })
119
        },
120
        doCancel(event) {
121
            event.preventDefault()
122
            this.$router.push({ path: "../record-sources" })
123
        },
124
    },
125
    created() {
126
        const { id } = this.$route.params
127
        if (id !== undefined) {
128
            this.api
129
                .get({
130
                    id,
131
                })
132
                .then(response => {
133
                    Object.keys(response).forEach(key => {
134
                        this.row[key] = response[key]
135
                    })
136
                    this.getUserData()
137
                    this.loading = false
138
                })
139
        } else {
140
            this.loading = false
141
        }
142
    },
143
    computed: {
144
        title() {
145
            if (!this.row || !this.row.record_source_id)
146
                return this.$__("Add record source")
147
            return this.$__("Edit %s").format(this.row.name)
148
        },
149
    },
150
    beforeMount() {
151
        this.$root.setTitle(this.title)
152
    },
153
}
154
</script>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/record-sources/List.vue (+120 lines)
Line 0 Link Here
1
<template>
2
    <div id="toolbar" class="btn-toolbar">
3
        <div class="btn-group">
4
            <button class="btn btn-default" @click="newRecordSource">
5
                <i class="fa fa-plus"></i> New record source
6
            </button>
7
        </div>
8
    </div>
9
    <h1>{{ title }}</h1>
10
    <div class="page-section">
11
        <KohaTable
12
            v-bind="tableOptions"
13
            @edit="doEdit"
14
            @delete="doRemove"
15
        ></KohaTable>
16
    </div>
17
</template>
18
19
<script>
20
import { useRouter } from "vue-router"
21
import { inject } from "vue"
22
import { RecordSourcesClient } from "../../fetch/record-sources"
23
import KohaTable from "../KohaTable.vue"
24
export default {
25
    name: "List",
26
    data() {
27
        return {
28
            title: this.$__("Record Sources"),
29
            tableOptions: {
30
                columns: [
31
                    {
32
                        title: this.$__("Source name"),
33
                        data: "name",
34
                        searchable: true,
35
                    },
36
                    {
37
                        title: this.$__("Patron"),
38
                        searchable: true,
39
                        data: "patron.firstname:patron.surname:patron.middle_name",
40
                        render: (data, type, row) => {
41
                            const { firstname, surname, middle_name } =
42
                                row.patron
43
                            return [firstname, middle_name, surname]
44
                                .filter(part => (part || "").trim())
45
                                .join(" ")
46
                        },
47
                    },
48
                ],
49
                options: {
50
                    embed: "patron",
51
                },
52
                actions: {
53
                    "-1": ["edit", "delete"],
54
                },
55
                url: "/api/v1/record_sources",
56
            },
57
        }
58
    },
59
    setup() {
60
        const { setWarning, setMessage, setError, setConfirmationDialog } =
61
            inject("mainStore")
62
        const { record_source } = new RecordSourcesClient()
63
        return {
64
            setWarning,
65
            setMessage,
66
            setError,
67
            setConfirmationDialog,
68
            api: record_source,
69
        }
70
    },
71
    beforeMount() {
72
        this.$root.setTitle(this.title)
73
    },
74
    methods: {
75
        newRecordSource() {
76
            this.$router.push({ path: "record-sources/add" })
77
        },
78
        doEdit(data) {
79
            this.$router.push({
80
                path: `record-sources/${data.record_source_id}`,
81
                props: {
82
                    data,
83
                },
84
            })
85
        },
86
        doRemove(data, dt) {
87
            this.setConfirmationDialog(
88
                {
89
                    title: this.$__(
90
                        "Are you sure you want to remove this record source?"
91
                    ),
92
                    message: data.name,
93
                    accept_label: this.$__("Yes, remove"),
94
                    cancel_label: this.$__("No, do not remove"),
95
                },
96
                () => {
97
                    this.api
98
                        .delete({
99
                            id: data.record_source_id,
100
                        })
101
                        .then(response => {
102
                            if (response) {
103
                                this.setMessage(
104
                                    this.$__("Record source %s removed").format(
105
                                        data.name
106
                                    ),
107
                                    true
108
                                )
109
                                dt.draw()
110
                            }
111
                        })
112
                }
113
            )
114
        },
115
    },
116
    components: {
117
        KohaTable,
118
    },
119
}
120
</script>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/import-sources/ISMain.vue (-2 / +1 lines)
Lines 6-14 Link Here
6
6
7
<script>
7
<script>
8
import Page from "../Page.vue"
8
import Page from "../Page.vue"
9
// import { inject } from "vue"
10
export default {
9
export default {
11
    name: "ISMain",
10
    name: "Main",
12
    data() {
11
    data() {
13
        return {
12
        return {
14
            title: "",
13
            title: "",
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/import-sources.js (-40 lines)
Lines 1-40 Link Here
1
import HttpClient from "./http-client";
2
3
export class ImportSourcesClient extends HttpClient {
4
    constructor() {
5
        super({
6
            baseURL: '/api/v1/import_sources',
7
        });
8
    }
9
    async add({row, headers={}}) {
10
        const requestHeaders = {...headers, 'Content-Type': 'application/json'}
11
        return this.post({
12
            endpoint: '',
13
            headers: requestHeaders,
14
            body: JSON.stringify(row)
15
        })
16
    }
17
18
    async remove({id, headers={}}) {
19
        return this.delete({
20
            endpoint: '/'+id,
21
            headers,
22
        })
23
    }
24
25
    async update({id, headers={}, row}) {
26
        const requestHeaders = {...headers, 'Content-Type': 'application/json'}
27
        return this.put({
28
            endpoint: '/'+id,
29
            headers: requestHeaders,
30
            body: JSON.stringify(row)
31
        })
32
    }
33
34
    async getOne({id, headers={}}) {
35
        return this.get({
36
            endpoint: '/'+id,
37
            headers,
38
        })
39
    }
40
}
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/patron-api-client.js (-4 lines)
Lines 13-22 export class PatronAPIClient extends HttpClient { Link Here
13
                this.get({
13
                this.get({
14
                    endpoint: "patrons/" + id,
14
                    endpoint: "patrons/" + id,
15
                }),
15
                }),
16
            list: (query) =>
17
                this.get({
18
                    endpoint: "patrons" + (query ? '?'+query:''),
19
                })
20
        };
16
        };
21
    }
17
    }
22
}
18
}
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/record-sources.js (+48 lines)
Line 0 Link Here
1
import HttpClient from "./http-client";
2
3
export class RecordSourcesClient extends HttpClient {
4
    constructor() {
5
        super({
6
            baseURL: "/api/v1/record_sources",
7
        });
8
    }
9
10
    get record_source() {
11
        return {
12
            create: ({ row, headers = {} }) => {
13
                const requestHeaders = {
14
                    ...headers,
15
                    "Content-Type": "application/json",
16
                };
17
                return this.post({
18
                    endpoint: "",
19
                    headers: requestHeaders,
20
                    body: JSON.stringify(row),
21
                });
22
            },
23
            delete: ({ id, headers = {} }) => {
24
                return this.delete({
25
                    endpoint: "/" + id,
26
                    headers,
27
                });
28
            },
29
            update: ({ id, headers = {}, row }) => {
30
                const requestHeaders = {
31
                    ...headers,
32
                    "Content-Type": "application/json",
33
                };
34
                return this.put({
35
                    endpoint: "/" + id,
36
                    headers: requestHeaders,
37
                    body: JSON.stringify(row),
38
                });
39
            },
40
            get: ({ id, headers = {} }) => {
41
                return this.get({
42
                    endpoint: "/" + id,
43
                    headers,
44
                });
45
            },
46
        };
47
    }
48
}
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/i18n/index.js (-1 / +1 lines)
Lines 6-9 export default { Link Here
6
    install: (app, options) => {
6
    install: (app, options) => {
7
        app.config.globalProperties.$__ = $__;
7
        app.config.globalProperties.$__ = $__;
8
    },
8
    },
9
};
9
};
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/import-source/router.js (-12 lines)
Lines 1-12 Link Here
1
import { createWebHistory, createRouter } from "vue-router";
2
3
export default (menuStore) => {
4
5
  const { routes } = menuStore
6
  const router = createRouter({
7
      history: createWebHistory(),
8
      linkExactActiveClass: "current",
9
      routes,
10
  })
11
  return router
12
}
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/inputs/autocomplete.js (-199 lines)
Lines 1-199 Link Here
1
import { createInput } from '@formkit/vue'
2
3
/**
4
 * This is an input "feature" — a function that accepts a node and exposes
5
 * some additional functionality to an input. When using schemas, this can
6
 * take the place of a traditional "script" block in a Vue component. In this
7
 * example, we expose:
8
 *
9
 *   - An input handler `search`.
10
 *   - An input handler `selections`.
11
 *   - Commit middleware to place filtered options into the `matches` prop.
12
 *
13
 * Once written, input features are added via the input declaration.
14
 */
15
const searchFeature = (node) => {
16
17
  const _transform_options = (options) => {
18
    if(!Array.isArray(options)) {
19
      return Object.keys(options).map((key) => ({label: options[key], value: key}))
20
    }
21
    return options.map((option) => {
22
      if(typeof option === 'string') return {label: option, value: option}
23
      return option
24
    })
25
  }
26
27
  // We wait for our node to be fully  "created" before we start to add our
28
  // handlers to ensure the core Vue plugin has added its context object:
29
  node.on('created', async () => {
30
    // Ensure our matches prop starts as an array.
31
    node.props.matches = []
32
    node.props.selection = ''
33
34
    if(node.value !== undefined && node.props.attrs && typeof node.props.attrs.optionLoader === 'function') {
35
      node.props.selection = await node.props.attrs.optionLoader(node.value)
36
    }
37
38
    const clearValue = async (e) => {
39
      if (e && typeof e.preventDefault === 'function') e.preventDefault()
40
      node.input('')
41
      node.props.matches = []
42
      node.props.selection = ''
43
      node.props.searchValue = ''
44
      setTimeout(() => {
45
        if (document.querySelector('input#' + node.props.id)) {
46
          document.querySelector('input#' + node.props.id).focus()
47
        }
48
      }, 50)
49
    }
50
    // When we actually have an value to set:
51
    const setValue = async (e) => {
52
      if (e && typeof e.preventDefault === 'function') e.preventDefault()
53
      node.input(node.props.selection.value)
54
      node.props.display = node.props.selection.label
55
      node.props.searchValue = ''
56
      await new Promise((r) => setTimeout(r, 50)) // "next tick"
57
    }
58
59
    // Perform a soft selection, this is shown as a highlight in the dropdown
60
    const select = (delta) => {
61
      const available = node.props.matches
62
      let idx = available.indexOf(node.props.selection) + delta
63
      if (idx >= available.length) {
64
        idx = 0
65
      } else if (idx < 0) {
66
        idx = available.length - 1
67
      }
68
      node.props.selection = available[idx]
69
    }
70
71
    // Add some new "handlers" for our autocomplete. The handlers object is
72
    // just a conventionally good place to put event handlers. Auto complete
73
    // inputs always have to deal with lots of keyboard events, so that logic
74
    // is registered here.
75
    Object.assign(node.context.handlers, {
76
      setValue,
77
      clearValue,
78
      selection: (e) => {
79
        // This handler is called when entering data into the search input.
80
        switch (e.key) {
81
          case 'Enter':
82
            return setValue()
83
          case 'ArrowDown':
84
            e.preventDefault()
85
            return select(1)
86
          case 'ArrowUp':
87
            e.preventDefault()
88
            return select(-1)
89
          case 'Escape':
90
            return node.props.matches = []
91
        }
92
      },
93
      blur: (e) => {
94
        if(!node.props.hovering) node.props.matches = []
95
      },
96
      search(e) {
97
        node.props.searchValue = e.target.value
98
      },
99
      hover: (e) => {
100
        node.props.hovering = true
101
        node.props.selection = node.props.matches.find(match => String(match.value) === e.target.attributes.value.value)
102
        console.log(node.props.selection)
103
      },
104
      unhover: (e) => {
105
        if (node.props.matches.find(match => String(match.value) === e.target.attributes.value.value) === node.props.selection) {
106
          node.props.selection = ''
107
          node.props.hovering = false
108
        }
109
      },
110
    })
111
  })
112
113
  // Perform filtering when the search value changes
114
  node.on('prop:searchValue', async ({ payload: value }) => {
115
    let results
116
    if(typeof node.props.options === 'function') {
117
      results = _transform_options(await node.props.options(value))
118
    } else {
119
      results = _transform_options(node.props.options).filter((option) =>
120
        option.label.toLowerCase().split(/\W/).find((part).startsWith(value.toLowerCase()))
121
      )
122
    }
123
    //if (!results.length) results.push('No matches')
124
    node.props.matches = results
125
  })
126
}
127
128
/**
129
 * This is our input schema responsible for rendering the inner “input”
130
 * section. In our example, we render an text input which will be used
131
 * to filter search results, and an unordered list that shows all remaining
132
 * matches.
133
 */
134
const schema = {
135
  if: '$value',
136
  then: [
137
    {
138
      $el: 'a',
139
      attrs: {
140
        id: '$id',
141
        href: '#',
142
        class: '$classes.value',
143
        onClick: '$handlers.clearValue',
144
      },
145
      children: '$selection.label'
146
    }
147
  ],
148
  else: [
149
    {
150
      $el: 'input',
151
      bind: '$attrs',
152
      attrs: {
153
        id: '$id',
154
        class: '$classes.input',
155
        onKeydown: '$handlers.selection',
156
        onInput: '$handlers.search',
157
        onBlur: '$handlers.blur',
158
        value: '$searchValue',
159
      },
160
    },
161
    {
162
      $el: 'ul',
163
      if: '$matches.length',
164
      attrs: {
165
        class: '$classes.dropdown',
166
      },
167
      children: [
168
        {
169
          $el: 'li',
170
          for: ['match', '$matches'],
171
          attrs: {
172
            'data-selected': {
173
              if: '$selection === $match',
174
              then: 'true',
175
              else: 'false',
176
            },
177
            value: '$match.value',
178
            class: '$classes.dropdownItem',
179
            onClick: '$handlers.setValue',
180
            onMouseenter: '$handlers.hover',
181
            onMouseleave: '$handlers.unhover',
182
          },
183
          children: '$match.label',
184
        },
185
      ],
186
    },
187
  ],
188
}
189
190
/**
191
 * Finally we create our actual input declaration by using `createInput` this
192
 * places our schema into a "standard" FormKit schema feature set with slots,
193
 * labels, help, messages etc. The return value of this function is a proper
194
 * input declaration.
195
 */
196
export default createInput(schema, {
197
  props: ['options', 'matches', 'selection', 'searchValue', 'value'],
198
  features: [searchFeature],
199
})
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/inputs/index.js (-5 lines)
Lines 1-5 Link Here
1
import autocomplete from './autocomplete'
2
3
export default {
4
  autocomplete
5
}
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/models/import-sources.js (-85 lines)
Lines 1-85 Link Here
1
2
import PatronAPIClient from "../fetch/patron-api-client";
3
import { $__ } from "../i18n";
4
5
const api = new PatronAPIClient()
6
7
export default [
8
  {
9
    name: 'import_source_id',
10
    table: {
11
      data: 'import_source_id',
12
      visible: false
13
    },
14
    form: {
15
      type: 'hidden'
16
    }
17
  },
18
  {
19
    name: 'name',
20
    title: $__('Source name'),
21
    table: {
22
      data: 'name',
23
      searchable: true
24
    },
25
    form: {
26
      type: 'text',
27
      label: {
28
        $ref: 'title'
29
      },
30
      validation: 'required'
31
    }
32
  },
33
  {
34
    name: 'patron_id',
35
    title: $__('Patron'),
36
    table: {
37
      searchable: true,
38
      data: "patron.firstname:patron.surname:patron.middle_name",
39
      render: (data, type, row) => {
40
        const {firstname, surname, middle_name} = row.patron
41
        return [firstname, middle_name, surname].filter(part => (part||'').trim()).join(' ');
42
      }
43
    },
44
    form: {
45
      type: 'autocomplete',
46
      label: 'Choose a patron',
47
      validation: 'required',
48
      optionLoader: async (id) => {
49
        const {firstname, surname, middle_name, patron_id} = ( await api.patrons.get(id) || {} );
50
        return {value: patron_id, label: [firstname, middle_name, surname].filter(part => (part||'').trim()).join(' ')}
51
      },
52
      options: async (value) => {
53
        let query = '';
54
        if(value !== undefined) {
55
          query = new URLSearchParams({
56
            q: JSON.stringify([
57
              {
58
                firstname: {
59
                  like: '%'+value+'%'
60
                }
61
              },
62
              {
63
                middle_name: {
64
                  like: '%'+value+'%'
65
                }
66
              },
67
              {
68
                surname: {
69
                  like: '%'+value+'%'
70
                }
71
              }
72
            ])
73
          })
74
        }
75
76
        const response = await api.patrons.list(query)
77
        if(!response) return []
78
        return response.map(row => {
79
          const {firstname, surname, middle_name, patron_id} = row
80
          return {value: patron_id, label: [firstname, middle_name, surname].filter(part => (part||'').trim()).join(' ')}
81
        })
82
      }
83
    }
84
  }
85
]
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/modules/import-sources.ts (-21 / +9 lines)
Lines 1-8 Link Here
1
import { createApp } from "vue";
1
import { createApp } from "vue";
2
import { createPinia } from "pinia";
2
import { createPinia } from "pinia";
3
import { plugin as formkit, defaultConfig } from '@formkit/vue'
4
import { createWebHistory, createRouter } from "vue-router";
3
import { createWebHistory, createRouter } from "vue-router";
5
import '@formkit/themes/genesis'
6
4
7
import { library } from "@fortawesome/fontawesome-svg-core";
5
import { library } from "@fortawesome/fontawesome-svg-core";
8
import {
6
import {
Lines 14-44 import { Link Here
14
} from "@fortawesome/free-solid-svg-icons";
12
} from "@fortawesome/free-solid-svg-icons";
15
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
13
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
16
import vSelect from "vue-select";
14
import vSelect from "vue-select";
17
import { useNavigationStore } from '../stores/navigation';
15
import { useNavigationStore } from "../stores/navigation";
18
import { useMainStore } from '../stores/main';
16
import { useMainStore } from "../stores/main";
19
import { useModelStore } from '../stores/model';
17
import routesDef from "../routes/record-sources";
20
import routesDef from '../routes/import-sources';
21
import model from '../models/import-sources';
22
18
23
library.add(faPlus, faMinus, faPencil, faTrash, faSpinner);
19
library.add(faPlus, faMinus, faPencil, faTrash, faSpinner);
24
20
25
const pinia = createPinia();
21
const pinia = createPinia();
26
const navigationStore = useNavigationStore(pinia);
22
const navigationStore = useNavigationStore(pinia);
27
const mainStore = useMainStore(pinia);
23
const mainStore = useMainStore(pinia);
28
const modelStore = useModelStore(pinia);
29
const { removeMessages } = mainStore;
24
const { removeMessages } = mainStore;
30
const { setRoutes } = navigationStore;
25
const { setRoutes } = navigationStore;
31
const { setModel } = modelStore;
32
const routes = setRoutes(routesDef);
26
const routes = setRoutes(routesDef);
33
setModel(model);
34
27
35
const router = createRouter({
28
const router = createRouter({
36
    history: createWebHistory(),
29
    history: createWebHistory(),
37
    linkExactActiveClass: "current",
30
    linkExactActiveClass: "current",
38
    routes,
31
    routes,
39
})
32
});
40
33
41
import App from "../components/import-sources/ISMain.vue";
34
import App from "../components/record-sources/Main.vue";
42
35
43
// import { routes } from "./routes";
36
// import { routes } from "./routes";
44
37
Lines 50-76 import App from "../components/import-sources/ISMain.vue"; Link Here
50
43
51
// import { useAVStore } from "../stores/authorised_values";
44
// import { useAVStore } from "../stores/authorised_values";
52
45
53
import i18n from "../i18n"
46
import i18n from "../i18n";
54
import inputs from "../inputs"
55
const app = createApp(App);
47
const app = createApp(App);
56
48
57
const rootComponent = app
49
const rootComponent = app
58
    .use(i18n)
50
    .use(i18n)
59
    .use(pinia)
51
    .use(pinia)
60
    .use(router)
52
    .use(router)
61
    .use(formkit, defaultConfig({
62
        inputs
63
    }))
64
    .component("font-awesome-icon", FontAwesomeIcon)
53
    .component("font-awesome-icon", FontAwesomeIcon)
65
    .component("v-select", vSelect);
54
    .component("v-select", vSelect);
66
55
67
app.config.unwrapInjectedRef = true;
56
app.config.unwrapInjectedRef = true;
68
app.provide("mainStore", mainStore);
57
app.provide("mainStore", mainStore);
69
app.provide("navigationStore", navigationStore);
58
app.provide("navigationStore", navigationStore);
70
app.provide("modelStore", modelStore);
59
app.mount("#record-source");
71
app.mount("#import-source");
72
60
73
router.beforeEach((to, from) => {
61
router.beforeEach(to => {
74
    navigationStore.$patch({current: to.meta.self});
62
    navigationStore.$patch({ current: to.matched, params: to.params || {} });
75
    removeMessages(); // This will actually flag the messages as displayed already
63
    removeMessages(); // This will actually flag the messages as displayed already
76
});
64
});
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/routes/import-sources.js (-31 lines)
Lines 1-31 Link Here
1
import ISEdit from '../components/import-sources/ISEdit.vue'
2
import ISList from '../components/import-sources/ISList.vue'
3
4
export default {
5
  title: 'Administration',
6
  href: '/cgi-bin/koha/admin/admin-home.pl',
7
  children: [
8
    {
9
      title: 'Import sources',
10
      path: '/cgi-bin/koha/admin/import-sources.pl',
11
      component: {template: '<router-view></router-view>', name: 'ISBase'},
12
      children: [
13
        {
14
          title: 'List',
15
          path: '',
16
          component: ISList
17
        },
18
        {
19
          title: 'Add import source',
20
          path: 'add',
21
          component: ISEdit
22
        },
23
        {
24
          title: 'Edit import source',
25
          path: ':id',
26
          component: ISEdit
27
        }
28
      ]
29
    }
30
  ]
31
}
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/routes/record-sources.js (+34 lines)
Line 0 Link Here
1
import Edit from "../components/record-sources/Edit.vue";
2
import List from "../components/record-sources/List.vue";
3
4
export default {
5
    title: "Administration",
6
    href: "/cgi-bin/koha/admin/admin-home.pl",
7
    children: [
8
        {
9
            title: "Record sources",
10
            path: "/cgi-bin/koha/admin/record-sources",
11
            component: {
12
                template: "<router-view></router-view>",
13
                name: "Base",
14
            },
15
            children: [
16
                {
17
                    title: "List",
18
                    path: "",
19
                    component: List,
20
                },
21
                {
22
                    title: "Add record source",
23
                    path: "add",
24
                    component: Edit,
25
                },
26
                {
27
                    title: "Edit record source",
28
                    path: ":id",
29
                    component: Edit,
30
                },
31
            ],
32
        },
33
    ],
34
};
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/stores/model.js (-51 lines)
Lines 1-51 Link Here
1
import { defineStore } from "pinia";
2
3
export const useModelStore = defineStore("model", {
4
    state: () => ({
5
        model: []
6
    }),
7
    actions: {
8
        setModel(model) {
9
          this.model = model
10
        },
11
        getFormSchema() {
12
          return this.model.map(row => {
13
            const {table, form = {}, ...common} = row
14
            Object.keys(form).forEach(key => {
15
              if (key === 'type') {
16
                form.$formkit = form[key]
17
                key = '$formkit'
18
                delete form.type;
19
              }
20
              if(form[key].$ref) {
21
                const path = form[key].$ref.split('.')
22
                form[key] = path.reduce((obj, key, idx) => {
23
                  if(obj === undefined || obj[key] === undefined) return undefined
24
                  const value = obj[key]
25
                  if(idx === path.lenght - 1) delete obj[key]
26
                  return value
27
                }, common)
28
              }
29
            })
30
            return {...common, ...form}
31
          })
32
        },
33
        getTableColumns() {
34
          return this.model.map(row => {
35
            const {table = {}, form, ...common} = row
36
            Object.keys(table).forEach(key => {
37
              if(table[key].$ref) {
38
                const path = table[key].$ref.split('.')
39
                table[key] = path.reduce((obj, key, idx) => {
40
                  if(obj === undefined || obj[key] === undefined) return undefined
41
                  const value = obj[key]
42
                  if(idx === path.lenght - 1) delete obj[key]
43
                  return value
44
                }, common)
45
              }
46
            })
47
            return {...common, ...table}
48
          })
49
        }
50
    },
51
});
(-)a/t/db_dependent/Koha/ImportSources.t (-4 / +4 lines)
Lines 21-27 use Modern::Perl; Link Here
21
21
22
use Test::More tests => 1;
22
use Test::More tests => 1;
23
23
24
use Koha::ImportSources;
24
use Koha::RecordSources;
25
25
26
use t::lib::TestBuilder;
26
use t::lib::TestBuilder;
27
my $schema = Koha::Database->new->schema;
27
my $schema = Koha::Database->new->schema;
Lines 34-44 subtest 'patron' => sub { Link Here
34
34
35
  my $patron = $builder->build_object({class => 'Koha::Patrons', value => {firstname => 'Afirstname'}});
35
  my $patron = $builder->build_object({class => 'Koha::Patrons', value => {firstname => 'Afirstname'}});
36
36
37
  my $import_source = $builder->build_object({class => 'Koha::ImportSources', value => {name => 'import_source', patron_id => $patron->borrowernumber}});
37
  my $record_source = $builder->build_object({class => 'Koha::RecordSources', value => {name => 'record_source', patron_id => $patron->borrowernumber}});
38
38
39
  my $fetched_patron = $import_source->patron;
39
  my $fetched_patron = $record_source->patron;
40
40
41
  is($fetched_patron->firstname, 'Afirstname', 'Patron matches');
41
  is($fetched_patron->firstname, 'Afirstname', 'Patron matches');
42
42
43
  $schema->storage->txn_rollback;
43
  $schema->storage->txn_rollback;
44
};
44
};
(-)a/t/db_dependent/api/v1/import_sources.t (-32 / +32 lines)
Lines 27-33 use t::lib::TestBuilder; Link Here
27
use t::lib::Mocks;
27
use t::lib::Mocks;
28
28
29
use C4::Auth;
29
use C4::Auth;
30
use Koha::ImportSources;
30
use Koha::RecordSources;
31
use Koha::Database;
31
use Koha::Database;
32
32
33
my $schema  = Koha::Database->new->schema;
33
my $schema  = Koha::Database->new->schema;
Lines 45-51 subtest 'list() tests' => sub { Link Here
45
45
46
  my $source = $builder->build_object(
46
  my $source = $builder->build_object(
47
    {
47
    {
48
      class => 'Koha::ImportSources'
48
      class => 'Koha::RecordSources'
49
    }
49
    }
50
  );
50
  );
51
  my $patron = $builder->build_object(
51
  my $patron = $builder->build_object(
Lines 58-64 subtest 'list() tests' => sub { Link Here
58
  for ( 1..10 ) {
58
  for ( 1..10 ) {
59
      $builder->build_object(
59
      $builder->build_object(
60
        {
60
        {
61
          class => 'Koha::ImportSources'
61
          class => 'Koha::RecordSources'
62
        }
62
        }
63
      );
63
      );
64
  }
64
  }
Lines 76-82 subtest 'list() tests' => sub { Link Here
76
      { password => $password, skip_validation => 1 } );
76
      { password => $password, skip_validation => 1 } );
77
  my $userid = $nonprivilegedpatron->userid;
77
  my $userid = $nonprivilegedpatron->userid;
78
78
79
  $t->get_ok( "//$userid:$password@/api/v1/import_sources" )
79
  $t->get_ok( "//$userid:$password@/api/v1/record_sources" )
80
    ->status_is(403)
80
    ->status_is(403)
81
    ->json_is(
81
    ->json_is(
82
      '/error' => 'Authorization failure. Missing required permission(s).' );
82
      '/error' => 'Authorization failure. Missing required permission(s).' );
Lines 84-104 subtest 'list() tests' => sub { Link Here
84
  $patron->set_password( { password => $password, skip_validation => 1 } );
84
  $patron->set_password( { password => $password, skip_validation => 1 } );
85
  $userid = $patron->userid;
85
  $userid = $patron->userid;
86
86
87
  $t->get_ok( "//$userid:$password@/api/v1/import_sources?_per_page=10" )
87
  $t->get_ok( "//$userid:$password@/api/v1/record_sources?_per_page=10" )
88
    ->status_is( 200, 'SWAGGER3.2.2' );
88
    ->status_is( 200, 'SWAGGER3.2.2' );
89
89
90
  my $response_count = scalar @{ $t->tx->res->json };
90
  my $response_count = scalar @{ $t->tx->res->json };
91
91
92
  is( $response_count, 10, 'The API returns 10 sources' );
92
  is( $response_count, 10, 'The API returns 10 sources' );
93
93
94
  my $id = $source->import_source_id;
94
  my $id = $source->record_source_id;
95
  $t->get_ok( "//$userid:$password@/api/v1/import_sources?q={\"import_source_id\": $id}" )
95
  $t->get_ok( "//$userid:$password@/api/v1/record_sources?q={\"record_source_id\": $id}" )
96
    ->status_is(200)
96
    ->status_is(200)
97
    ->json_is( '' => [ $source->to_api ], 'SWAGGER3.3.2');
97
    ->json_is( '' => [ $source->to_api ], 'SWAGGER3.3.2');
98
98
99
  $source->delete;
99
  $source->delete;
100
100
101
  $t->get_ok( "//$userid:$password@/api/v1/import_sources?q={\"import_source_id\": $id}" )
101
  $t->get_ok( "//$userid:$password@/api/v1/record_sources?q={\"record_source_id\": $id}" )
102
    ->status_is(200)
102
    ->status_is(200)
103
    ->json_is( '' => [] );
103
    ->json_is( '' => [] );
104
104
Lines 114-120 subtest 'get() tests' => sub { Link Here
114
114
115
  my $source = $builder->build_object(
115
  my $source = $builder->build_object(
116
    {
116
    {
117
      class => 'Koha::ImportSources'
117
      class => 'Koha::RecordSources'
118
    }
118
    }
119
  );
119
  );
120
  my $patron = $builder->build_object(
120
  my $patron = $builder->build_object(
Lines 137-145 subtest 'get() tests' => sub { Link Here
137
      { password => $password, skip_validation => 1 } );
137
      { password => $password, skip_validation => 1 } );
138
  my $userid = $nonprivilegedpatron->userid;
138
  my $userid = $nonprivilegedpatron->userid;
139
139
140
  my $id = $source->import_source_id;
140
  my $id = $source->record_source_id;
141
141
142
  $t->get_ok( "//$userid:$password@/api/v1/import_sources/$id" )
142
  $t->get_ok( "//$userid:$password@/api/v1/record_sources/$id" )
143
    ->status_is(403)
143
    ->status_is(403)
144
    ->json_is(
144
    ->json_is(
145
      '/error' => 'Authorization failure. Missing required permission(s).' );
145
      '/error' => 'Authorization failure. Missing required permission(s).' );
Lines 147-161 subtest 'get() tests' => sub { Link Here
147
  $patron->set_password( { password => $password, skip_validation => 1 } );
147
  $patron->set_password( { password => $password, skip_validation => 1 } );
148
  $userid = $patron->userid;
148
  $userid = $patron->userid;
149
149
150
  $t->get_ok( "//$userid:$password@/api/v1/import_sources/$id" )
150
  $t->get_ok( "//$userid:$password@/api/v1/record_sources/$id" )
151
    ->status_is( 200, 'SWAGGER3.2.2' )
151
    ->status_is( 200, 'SWAGGER3.2.2' )
152
    ->json_is( '' => $source->to_api, 'SWAGGER3.3.2' );
152
    ->json_is( '' => $source->to_api, 'SWAGGER3.3.2' );
153
153
154
  $source->delete;
154
  $source->delete;
155
155
156
  $t->get_ok( "//$userid:$password@/api/v1/import_sources/$id" )
156
  $t->get_ok( "//$userid:$password@/api/v1/record_sources/$id" )
157
    ->status_is(404)
157
    ->status_is(404)
158
    ->json_is( '/error' => 'Import source not found' );
158
    ->json_is( '/error' => 'Record source not found' );
159
159
160
  $schema->storage->txn_rollback;
160
  $schema->storage->txn_rollback;
161
161
Lines 169-175 subtest 'delete() tests' => sub { Link Here
169
169
170
  my $source = $builder->build_object(
170
  my $source = $builder->build_object(
171
    {
171
    {
172
      class => 'Koha::ImportSources'
172
      class => 'Koha::RecordSources'
173
    }
173
    }
174
  );
174
  );
175
  my $patron = $builder->build_object(
175
  my $patron = $builder->build_object(
Lines 192-200 subtest 'delete() tests' => sub { Link Here
192
      { password => $password, skip_validation => 1 } );
192
      { password => $password, skip_validation => 1 } );
193
  my $userid = $nonprivilegedpatron->userid;
193
  my $userid = $nonprivilegedpatron->userid;
194
194
195
  my $id = $source->import_source_id;
195
  my $id = $source->record_source_id;
196
196
197
  $t->delete_ok( "//$userid:$password@/api/v1/import_sources/$id" )
197
  $t->delete_ok( "//$userid:$password@/api/v1/record_sources/$id" )
198
    ->status_is(403)
198
    ->status_is(403)
199
    ->json_is(
199
    ->json_is(
200
      '/error' => 'Authorization failure. Missing required permission(s).' );
200
      '/error' => 'Authorization failure. Missing required permission(s).' );
Lines 202-213 subtest 'delete() tests' => sub { Link Here
202
  $patron->set_password( { password => $password, skip_validation => 1 } );
202
  $patron->set_password( { password => $password, skip_validation => 1 } );
203
  $userid = $patron->userid;
203
  $userid = $patron->userid;
204
204
205
  $t->delete_ok( "//$userid:$password@/api/v1/import_sources/$id" )
205
  $t->delete_ok( "//$userid:$password@/api/v1/record_sources/$id" )
206
    ->status_is( 204, 'SWAGGER3.2.2' );
206
    ->status_is( 204, 'SWAGGER3.2.2' );
207
207
208
  my $deleted_source = Koha::ImportSources->search({import_source_id => $id});
208
  my $deleted_source = Koha::RecordSources->search({record_source_id => $id});
209
209
210
  is($deleted_source->count, 0, 'No import source found');
210
  is($deleted_source->count, 0, 'No record source found');
211
211
212
  $schema->storage->txn_rollback;
212
  $schema->storage->txn_rollback;
213
213
Lines 219-225 subtest 'add() tests' => sub { Link Here
219
219
220
  $schema->storage->txn_begin;
220
  $schema->storage->txn_begin;
221
221
222
  Koha::ImportSources->delete;
222
  Koha::RecordSources->delete;
223
223
224
  my $patron = $builder->build_object(
224
  my $patron = $builder->build_object(
225
      {
225
      {
Lines 242-248 subtest 'add() tests' => sub { Link Here
242
  my $userid = $nonprivilegedpatron->userid;
242
  my $userid = $nonprivilegedpatron->userid;
243
  my $patron_id = $nonprivilegedpatron->borrowernumber;
243
  my $patron_id = $nonprivilegedpatron->borrowernumber;
244
244
245
  $t->post_ok( "//$userid:$password@/api/v1/import_sources" => json => { name => 'test1', patron_id => $patron_id })
245
  $t->post_ok( "//$userid:$password@/api/v1/record_sources" => json => { name => 'test1', patron_id => $patron_id })
246
    ->status_is(403)
246
    ->status_is(403)
247
    ->json_is(
247
    ->json_is(
248
      '/error' => 'Authorization failure. Missing required permission(s).' );
248
      '/error' => 'Authorization failure. Missing required permission(s).' );
Lines 250-263 subtest 'add() tests' => sub { Link Here
250
  $patron->set_password( { password => $password, skip_validation => 1 } );
250
  $patron->set_password( { password => $password, skip_validation => 1 } );
251
  $userid = $patron->userid;
251
  $userid = $patron->userid;
252
252
253
  $t->post_ok( "//$userid:$password@/api/v1/import_sources" => json => { name => 'test1', patron_id => $patron_id })
253
  $t->post_ok( "//$userid:$password@/api/v1/record_sources" => json => { name => 'test1', patron_id => $patron_id })
254
    ->status_is( 201, 'SWAGGER3.2.2' )
254
    ->status_is( 201, 'SWAGGER3.2.2' )
255
    ->json_is('/name', 'test1')
255
    ->json_is('/name', 'test1')
256
    ->json_is('/patron_id', $patron_id);
256
    ->json_is('/patron_id', $patron_id);
257
257
258
  my $created_source = Koha::ImportSources->search->next;
258
  my $created_source = Koha::RecordSources->search->next;
259
259
260
  is($created_source->name, 'test1', 'Import source found');
260
  is($created_source->name, 'test1', 'Record source found');
261
261
262
  $schema->storage->txn_rollback;
262
  $schema->storage->txn_rollback;
263
263
Lines 271-277 subtest 'update() tests' => sub { Link Here
271
271
272
  my $source = $builder->build_object(
272
  my $source = $builder->build_object(
273
    {
273
    {
274
      class => 'Koha::ImportSources',
274
      class => 'Koha::RecordSources',
275
      value => { name => 'Oldname' }
275
      value => { name => 'Oldname' }
276
    }
276
    }
277
  );
277
  );
Lines 295-303 subtest 'update() tests' => sub { Link Here
295
      { password => $password, skip_validation => 1 } );
295
      { password => $password, skip_validation => 1 } );
296
  my $userid = $nonprivilegedpatron->userid;
296
  my $userid = $nonprivilegedpatron->userid;
297
297
298
  my $id = $source->import_source_id;
298
  my $id = $source->record_source_id;
299
299
300
  $t->put_ok( "//$userid:$password@/api/v1/import_sources/$id" => json => { name => 'Newname', patron_id => $source->patron_id } )
300
  $t->put_ok( "//$userid:$password@/api/v1/record_sources/$id" => json => { name => 'Newname', patron_id => $source->patron_id } )
301
    ->status_is(403)
301
    ->status_is(403)
302
    ->json_is(
302
    ->json_is(
303
      '/error' => 'Authorization failure. Missing required permission(s).' );
303
      '/error' => 'Authorization failure. Missing required permission(s).' );
Lines 305-318 subtest 'update() tests' => sub { Link Here
305
  $patron->set_password( { password => $password, skip_validation => 1 } );
305
  $patron->set_password( { password => $password, skip_validation => 1 } );
306
  $userid = $patron->userid;
306
  $userid = $patron->userid;
307
307
308
  $t->put_ok( "//$userid:$password@/api/v1/import_sources/$id" => json => { name => 'Newname', patron_id => $source->patron_id } )
308
  $t->put_ok( "//$userid:$password@/api/v1/record_sources/$id" => json => { name => 'Newname', patron_id => $source->patron_id } )
309
    ->status_is( 200, 'SWAGGER3.2.2' )
309
    ->status_is( 200, 'SWAGGER3.2.2' )
310
    ->json_is('/name', 'Newname');
310
    ->json_is('/name', 'Newname');
311
311
312
  my $updated_source = Koha::ImportSources->find($id);
312
  my $updated_source = Koha::RecordSources->find($id);
313
313
314
  is($updated_source->name, 'Newname', 'Import source updated');
314
  is($updated_source->name, 'Newname', 'Record source updated');
315
315
316
  $schema->storage->txn_rollback;
316
  $schema->storage->txn_rollback;
317
317
318
};
318
};
(-)a/webpack.config.js (-2 / +1 lines)
Lines 6-12 const webpack = require('webpack'); Link Here
6
module.exports = {
6
module.exports = {
7
  entry: {
7
  entry: {
8
    erm: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/erm.ts",
8
    erm: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/erm.ts",
9
    "import-source": "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/import-sources.ts"
9
    "record-source": "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/record-sources.ts"
10
  },
10
  },
11
  output: {
11
  output: {
12
    filename: "[name].js",
12
    filename: "[name].js",
13
- 

Return to bug 32607