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

(-)a/Koha/ImportSource.pm (+56 lines)
Line 0 Link Here
1
package Koha::ImportSource;
2
3
# This file is part of Koha.
4
#
5
# Copyright 2020 Koha Development Team
6
#
7
# Koha is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as
9
# published by the Free Software Foundation; either version 3
10
# of the License, or (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General
18
# Public License along with Koha; if not, see
19
# <http://www.gnu.org/licenses>
20
21
use Modern::Perl;
22
23
use base qw(Koha::Object);
24
25
=head1 NAME
26
27
Koha::ImportSource - Koha ImportSource Object class
28
29
=head1 API
30
31
=head2 Class Methods
32
33
=head3 patron
34
35
my $patron = $import_source->patron
36
37
Return the patron for this import source
38
39
=cut
40
41
sub patron {
42
    my ($self) = @_;
43
44
    my $patron_rs = $self->_result->patron;
45
    return Koha::Patron->_new_from_dbic($patron_rs);
46
}
47
48
=head3 _type
49
50
=cut
51
52
sub _type {
53
    return 'ImportSource';
54
}
55
56
1;
(-)a/Koha/ImportSources.pm (+51 lines)
Line 0 Link Here
1
package Koha::ImportSources;
2
3
# This file is part of Koha.
4
#
5
# Copyright 2020 Koha Development Team
6
#
7
# Koha is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as
9
# published by the Free Software Foundation; either version 3
10
# of the License, or (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General
18
# Public License along with Koha; if not, see
19
# <http://www.gnu.org/licenses>
20
21
use Modern::Perl;
22
23
use base qw(Koha::Objects);
24
25
use Koha::ImportSource;
26
27
=head1 NAME
28
29
Koha::ImportSources - Koha ImportSources Object class
30
31
=head1 API
32
33
=head2 Class Methods
34
35
=head3 _type
36
37
=cut
38
39
sub _type {
40
    return 'ImportSource';
41
}
42
43
=head3 object_class
44
45
=cut
46
47
sub object_class {
48
    return 'Koha::ImportSource';
49
}
50
51
1;
(-)a/Koha/REST/V1/ImportSources.pm (+141 lines)
Line 0 Link Here
1
package Koha::REST::V1::ImportSources;
2
3
# Copyright 2023 Theke Solutions
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use Mojo::Base 'Mojolicious::Controller';
23
24
use Koha::ImportSources;
25
26
use Try::Tiny qw( catch try );
27
28
=head1 API
29
30
=head2 Methods
31
32
=head3 list
33
34
=cut
35
36
sub list {
37
    my $c = shift->openapi->valid_input or return;
38
39
    return try {
40
        my $import_sources_set = Koha::ImportSources->new;
41
        my $import_sources = $c->objects->search( $import_sources_set );
42
        return $c->render( status => 200, openapi => $import_sources );
43
    }
44
    catch {
45
        $c->unhandled_exception($_);
46
    };
47
}
48
49
=head3 get
50
51
=cut
52
53
sub get {
54
    my $c = shift->openapi->valid_input or return;
55
56
    return try {
57
        my $import_sources_set = Koha::ImportSources->new;
58
        my $import_source = $c->objects->find( $import_sources_set, $c->validation->param('import_source_id') );
59
        unless ($import_source) {
60
            return $c->render( status  => 404,
61
                            openapi => { error => "Import source not found" } );
62
        }
63
64
        return $c->render( status => 200, openapi => $import_source );
65
    }
66
    catch {
67
        $c->unhandled_exception($_);
68
    };
69
}
70
71
=head3 add
72
73
=cut
74
75
sub add {
76
    my $c = shift->openapi->valid_input or return;
77
78
    return try {
79
        my $import_source = Koha::ImportSource->new_from_api( $c->validation->param('body') );
80
        $import_source->store;
81
        $c->res->headers->location( $c->req->url->to_string . '/' . $import_source->import_source_id );
82
        return $c->render(
83
            status  => 201,
84
            openapi => $import_source->to_api
85
        );
86
    }
87
    catch {
88
        $c->unhandled_exception($_);
89
    };
90
}
91
92
=head3 update
93
94
=cut
95
96
sub update {
97
    my $c = shift->openapi->valid_input or return;
98
99
    my $import_source = Koha::ImportSources->find( $c->validation->param('import_source_id') );
100
101
    if ( not defined $import_source ) {
102
        return $c->render( status  => 404,
103
                           openapi => { error => "Object not found" } );
104
    }
105
106
    return try {
107
        $import_source->set_from_api( $c->validation->param('body') );
108
        $import_source->store();
109
        return $c->render( status => 200, openapi => $import_source->to_api );
110
    }
111
    catch {
112
        $c->unhandled_exception($_);
113
    };
114
}
115
116
=head3 delete
117
118
=cut
119
120
sub delete {
121
    my $c = shift->openapi->valid_input or return;
122
123
    my $import_source = Koha::ImportSources->find( $c->validation->param('import_source_id') );
124
    if ( not defined $import_source ) {
125
        return $c->render( status  => 404,
126
                           openapi => { error => "Object not found" } );
127
    }
128
129
    return try {
130
        $import_source->delete;
131
        return $c->render(
132
            status  => 204,
133
            openapi => q{}
134
        );
135
    }
136
    catch {
137
        $c->unhandled_exception($_);
138
    };
139
}
140
141
1;
(-)a/Koha/Schema/Result/ImportSource.pm (+23 lines)
Lines 86-89 __PACKAGE__->belongs_to( Link Here
86
86
87
87
88
# You can replace this text with custom code or comments, and it will be preserved on regeneration
88
# You can replace this text with custom code or comments, and it will be preserved on regeneration
89
90
=head1 CUSTOM CODE
91
92
=head2 koha_object_class
93
94
name of the corresponding Koha::Object
95
96
=cut
97
98
sub koha_object_class {
99
    'Koha::ImportSource';
100
}
101
102
=head2 koha_objects_class
103
104
name of the corresponding Koha::Objects
105
106
=cut
107
108
sub koha_objects_class {
109
    'Koha::ImportSources';
110
}
111
89
1;
112
1;
(-)a/admin/import-sources.pl (+38 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2023 Theke Solutions
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use CGI qw ( -utf8 );
23
use C4::Auth qw( get_template_and_user );
24
use C4::Output qw( output_html_with_http_headers );
25
use Koha::Plugins;
26
27
my $query = CGI->new;
28
29
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
30
    {
31
        template_name   => "admin/import-sources.tt",
32
        query           => $query,
33
        type            => "intranet",
34
        flagsrequired   => { parameters => 'manage_import_sources' },
35
    }
36
);
37
38
output_html_with_http_headers $query, $cookie, $template->output;
(-)a/api/v1/swagger/definitions/import_source.yaml (+13 lines)
Line 0 Link Here
1
---
2
type: object
3
properties:
4
  import_source_id:
5
    type: integer
6
    description: internally assigned import source identifier
7
    readOnly: true
8
  name:
9
    description: import source name
10
    type: string
11
  patron_id:
12
    description: linked patron identifier
13
    type: integer
(-)a/api/v1/swagger/paths/import_sources.yaml (+247 lines)
Line 0 Link Here
1
/import_sources:
2
  get:
3
    x-mojo-to: ImportSources#list
4
    operationId: listImportSources
5
    summary: List import sources
6
    tags:
7
      - import_sources
8
    parameters:
9
      - $ref: "../swagger.yaml#/parameters/match"
10
      - $ref: "../swagger.yaml#/parameters/order_by"
11
      - $ref: "../swagger.yaml#/parameters/page"
12
      - $ref: "../swagger.yaml#/parameters/per_page"
13
      - $ref: "../swagger.yaml#/parameters/q_param"
14
      - $ref: "../swagger.yaml#/parameters/q_body"
15
      - $ref: "../swagger.yaml#/parameters/q_header"
16
      - $ref: "../swagger.yaml#/parameters/request_id_header"
17
      - name: x-koha-embed
18
        in: header
19
        required: false
20
        description: Embed list sent as a request header
21
        type: array
22
        items:
23
          type: string
24
          enum:
25
            - patron
26
        collectionFormat: csv
27
    consumes:
28
      - application/json
29
    produces:
30
      - application/json
31
    responses:
32
      "200":
33
        description: A list of import sources
34
      "400":
35
        description: Missing or wrong parameters
36
        schema:
37
          $ref: "../swagger.yaml#/definitions/error"
38
      "401":
39
        description: Authentication required
40
        schema:
41
          $ref: "../swagger.yaml#/definitions/error"
42
      "403":
43
        description: Not allowed
44
        schema:
45
          $ref: "../swagger.yaml#/definitions/error"
46
      "500":
47
        description: |
48
          Internal server error. Possible `error_code` attribute values:
49
50
          * `internal_server_error`
51
        schema:
52
          $ref: "../swagger.yaml#/definitions/error"
53
      "503":
54
        description: Under maintenance
55
        schema:
56
          $ref: "../swagger.yaml#/definitions/error"
57
    x-koha-authorization:
58
      permissions:
59
        parameters: manage_import_sources
60
  post:
61
    x-mojo-to: ImportSources#add
62
    operationId: addImportSources
63
    summary: Add an import source
64
    tags:
65
      - import_sources
66
    parameters:
67
      - name: body
68
        in: body
69
        description: A JSON object containing informations about the new import source
70
        required: true
71
        schema:
72
          $ref: "../swagger.yaml#/definitions/import_source"
73
    consumes:
74
      - application/json
75
    produces:
76
      - application/json
77
    responses:
78
      "201":
79
        description: An import source
80
      "400":
81
        description: Missing or wrong parameters
82
        schema:
83
          $ref: "../swagger.yaml#/definitions/error"
84
      "401":
85
        description: Authentication required
86
        schema:
87
          $ref: "../swagger.yaml#/definitions/error"
88
      "403":
89
        description: Not allowed
90
        schema:
91
          $ref: "../swagger.yaml#/definitions/error"
92
      "500":
93
        description: |
94
          Internal server error. Possible `error_code` attribute values:
95
96
          * `internal_server_error`
97
        schema:
98
          $ref: "../swagger.yaml#/definitions/error"
99
      "503":
100
        description: Under maintenance
101
        schema:
102
          $ref: "../swagger.yaml#/definitions/error"
103
    x-koha-authorization:
104
      permissions:
105
        parameters: manage_import_sources
106
"/import_sources/{import_source_id}":
107
  get:
108
    x-mojo-to: ImportSources#get
109
    operationId: getImportSources
110
    summary: Get an import source
111
    tags:
112
      - import_sources
113
    parameters:
114
      - $ref: "../swagger.yaml#/parameters/import_source_id_pp"
115
    consumes:
116
      - application/json
117
    produces:
118
      - application/json
119
    responses:
120
      "200":
121
        description: An import source
122
      "400":
123
        description: Missing or wrong parameters
124
        schema:
125
          $ref: "../swagger.yaml#/definitions/error"
126
      "401":
127
        description: Authentication required
128
        schema:
129
          $ref: "../swagger.yaml#/definitions/error"
130
      "403":
131
        description: Not allowed
132
        schema:
133
          $ref: "../swagger.yaml#/definitions/error"
134
      "404":
135
        description: Not found
136
        schema:
137
          $ref: "../swagger.yaml#/definitions/error"
138
      "500":
139
        description: |
140
          Internal server error. Possible `error_code` attribute values:
141
142
          * `internal_server_error`
143
        schema:
144
          $ref: "../swagger.yaml#/definitions/error"
145
      "503":
146
        description: Under maintenance
147
        schema:
148
          $ref: "../swagger.yaml#/definitions/error"
149
    x-koha-authorization:
150
      permissions:
151
        parameters: manage_import_sources
152
  put:
153
    x-mojo-to: ImportSources#update
154
    operationId: updateImportSources
155
    summary: Update an import source
156
    tags:
157
      - import_sources
158
    parameters:
159
      - $ref: "../swagger.yaml#/parameters/import_source_id_pp"
160
      - name: body
161
        in: body
162
        description: A JSON object containing informations about the new import source
163
        required: true
164
        schema:
165
          $ref: "../swagger.yaml#/definitions/import_source"
166
    consumes:
167
      - application/json
168
    produces:
169
      - application/json
170
    responses:
171
      "200":
172
        description: An import source
173
      "400":
174
        description: Missing or wrong parameters
175
        schema:
176
          $ref: "../swagger.yaml#/definitions/error"
177
      "401":
178
        description: Authentication required
179
        schema:
180
          $ref: "../swagger.yaml#/definitions/error"
181
      "403":
182
        description: Not allowed
183
        schema:
184
          $ref: "../swagger.yaml#/definitions/error"
185
      "404":
186
        description: Not found
187
        schema:
188
          $ref: "../swagger.yaml#/definitions/error"
189
      "500":
190
        description: |
191
          Internal server error. Possible `error_code` attribute values:
192
193
          * `internal_server_error`
194
        schema:
195
          $ref: "../swagger.yaml#/definitions/error"
196
      "503":
197
        description: Under maintenance
198
        schema:
199
          $ref: "../swagger.yaml#/definitions/error"
200
    x-koha-authorization:
201
      permissions:
202
        parameters: manage_import_sources
203
  delete:
204
    x-mojo-to: ImportSources#delete
205
    operationId: deleteImportSources
206
    summary: Delete an import source
207
    tags:
208
      - import_sources
209
    parameters:
210
      - $ref: "../swagger.yaml#/parameters/import_source_id_pp"
211
    consumes:
212
      - application/json
213
    produces:
214
      - application/json
215
    responses:
216
      "204":
217
        description: Deleted
218
      "400":
219
        description: Missing or wrong parameters
220
        schema:
221
          $ref: "../swagger.yaml#/definitions/error"
222
      "401":
223
        description: Authentication required
224
        schema:
225
          $ref: "../swagger.yaml#/definitions/error"
226
      "403":
227
        description: Not allowed
228
        schema:
229
          $ref: "../swagger.yaml#/definitions/error"
230
      "404":
231
        description: Not found
232
        schema:
233
          $ref: "../swagger.yaml#/definitions/error"
234
      "500":
235
        description: |
236
          Internal server error. Possible `error_code` attribute values:
237
238
          * `internal_server_error`
239
        schema:
240
          $ref: "../swagger.yaml#/definitions/error"
241
      "503":
242
        description: Under maintenance
243
        schema:
244
          $ref: "../swagger.yaml#/definitions/error"
245
    x-koha-authorization:
246
      permissions:
247
        parameters: manage_import_sources
(-)a/api/v1/swagger/swagger.yaml (+15 lines)
Lines 70-75 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:
74
    $ref: ./definitions/import_source.yaml
73
  invoice:
75
  invoice:
74
    $ref: ./definitions/invoice.yaml
76
    $ref: ./definitions/invoice.yaml
75
  item:
77
  item:
Lines 275-280 paths: Link Here
275
    $ref: ./paths/import_batch_profiles.yaml#/~1import_batch_profiles
277
    $ref: ./paths/import_batch_profiles.yaml#/~1import_batch_profiles
276
  "/import_batch_profiles/{import_batch_profile_id}":
278
  "/import_batch_profiles/{import_batch_profile_id}":
277
    $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:
281
    $ref: ./paths/import_sources.yaml#/~1import_sources
282
  "/import_sources/{import_source_id}":
283
    $ref: ./paths/import_sources.yaml#/~1import_sources~1{import_source_id}
278
  /items:
284
  /items:
279
    $ref: ./paths/items.yaml#/~1items
285
    $ref: ./paths/items.yaml#/~1items
280
  "/items/{item_id}":
286
  "/items/{item_id}":
Lines 533-538 parameters: Link Here
533
    name: import_record_id
539
    name: import_record_id
534
    required: true
540
    required: true
535
    type: integer
541
    type: integer
542
  import_source_id_pp:
543
    description: Internal import source identifier
544
    in: path
545
    name: import_source_id
546
    required: true
547
    type: integer
536
  item_id_pp:
548
  item_id_pp:
537
    description: Internal item identifier
549
    description: Internal item identifier
538
    in: path
550
    in: path
Lines 830-835 tags: Link Here
830
  - description: "Manage item groups\n"
842
  - description: "Manage item groups\n"
831
    name: item_groups
843
    name: item_groups
832
    x-displayName: Item groups
844
    x-displayName: Item groups
845
  - description: "Manage import sources\n"
846
    name: import_sources
847
    x-displayName: Import source
833
  - description: "Manage items\n"
848
  - description: "Manage items\n"
834
    name: items
849
    name: items
835
    x-displayName: Items
850
    x-displayName: Items
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt (+2 lines)
Lines 206-211 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>
210
                    <dd>Define sources to import from</dd>
209
                </dl>
211
                </dl>
210
            [% END %]
212
            [% END %]
211
213
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/import-sources.tt (+37 lines)
Line 0 Link Here
1
[% USE raw %]
2
[% USE To %]
3
[% USE Asset %]
4
[% USE KohaDates %]
5
[% USE TablesSettings %]
6
[% USE AuthorisedValues %]
7
[% SET footerjs = 1 %]
8
[% PROCESS 'i18n.inc' %]
9
[% INCLUDE 'doc-head-open.inc' %]
10
<title>
11
    Import sources &rsaquo; Koha
12
</title>
13
[% INCLUDE 'doc-head-close.inc' %]
14
</head>
15
16
<body id="erm_agreements" class="erm">
17
[% WRAPPER 'header.inc' %]
18
    [% INCLUDE 'erm-search.inc' %]
19
[% END %]
20
21
<div id="import-source"> <!-- this is closed in intranet-bottom.inc -->
22
23
[% MACRO jsinclude BLOCK %]
24
    [% INCLUDE 'calendar.inc' %]
25
    [% INCLUDE 'datatables.inc' %]
26
    [% INCLUDE 'columns_settings.inc' %]
27
    [% INCLUDE 'js-patron-format.inc' %]
28
    [% INCLUDE 'js-date-format.inc' %]
29
30
    <script>
31
      const RESTOAuth2ClientCredentials = [% IF Koha.Preference('RESTOAuth2ClientCredentials') %]true[% ELSE %]false[% END %];
32
    </script>
33
34
    [% Asset.js("js/vue/dist/import-source.js") | $raw %]
35
36
[% END %]
37
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Form.vue (+144 lines)
Line 0 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 (+53 lines)
Line 0 Link Here
1
<template>
2
    <div :id="id">
3
        <div id="sub-header">
4
            <Breadcrumbs :title="title"></Breadcrumbs>
5
            <Help />
6
        </div>
7
        <div class="main container-fluid">
8
            <div class="row">
9
                <template v-if="leftMenu">
10
                    <div class="col-sm-10 col-sm-push-2">
11
                        <Dialog></Dialog>
12
                        <slot></slot>
13
                    </div>
14
                    <div class="col-sm-2 col-sm-pull-10">
15
                        <LeftMenu :title="title"></LeftMenu>
16
                    </div>
17
                </template>
18
                <template v-else>
19
                    <div class="col-sm-12">
20
                        <Dialog></Dialog>
21
                        <slot></slot>
22
                    </div>
23
                </template>
24
            </div>
25
        </div>
26
    </div>
27
</template>
28
29
<script>
30
import LeftMenu from "./LeftMenu.vue"
31
import Breadcrumbs from "./Breadcrumbs.vue"
32
import Help from "./Help.vue"
33
import Dialog from "./Dialog.vue"
34
export default {
35
    name: "Page",
36
    data: () => ({
37
        leftMenu: true,
38
    }),
39
    components: {
40
        LeftMenu,
41
        Dialog,
42
        Breadcrumbs,
43
        Help,
44
    },
45
    props: {
46
        id: String,
47
        title: String,
48
        leftMenu: Boolean,
49
    },
50
}
51
</script>
52
53
<style></style>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/import-sources/ISEdit.vue (+80 lines)
Line 0 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)
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="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/import-sources/ISMain.vue (+28 lines)
Line 0 Link Here
1
<template>
2
    <Page :left-menu="false" :title="title">
3
        <router-view></router-view>
4
    </Page>
5
</template>
6
7
<script>
8
import Page from "../Page.vue"
9
// import { inject } from "vue"
10
export default {
11
    name: "ISMain",
12
    data() {
13
        return {
14
            title: "",
15
        }
16
    },
17
    methods: {
18
        setTitle(title) {
19
            this.title = title
20
        },
21
    },
22
    components: {
23
        Page,
24
    },
25
}
26
</script>
27
28
<style></style>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/import-sources.js (+40 lines)
Line 0 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-18 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
                })
16
        };
20
        };
17
    }
21
    }
18
}
22
}
(-)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)
Line 0 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)
Line 0 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)
Line 0 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)
Line 0 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 (+76 lines)
Line 0 Link Here
1
import { createApp } from "vue";
2
import { createPinia } from "pinia";
3
import { plugin as formkit, defaultConfig } from '@formkit/vue'
4
import { createWebHistory, createRouter } from "vue-router";
5
import '@formkit/themes/genesis'
6
7
import { library } from "@fortawesome/fontawesome-svg-core";
8
import {
9
    faPlus,
10
    faMinus,
11
    faPencil,
12
    faTrash,
13
    faSpinner,
14
} from "@fortawesome/free-solid-svg-icons";
15
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
16
import vSelect from "vue-select";
17
import { useNavigationStore } from '../stores/navigation';
18
import { useMainStore } from '../stores/main';
19
import { useModelStore } from '../stores/model';
20
import routesDef from '../routes/import-sources';
21
import model from '../models/import-sources';
22
23
library.add(faPlus, faMinus, faPencil, faTrash, faSpinner);
24
25
const pinia = createPinia();
26
const navigationStore = useNavigationStore(pinia);
27
const mainStore = useMainStore(pinia);
28
const modelStore = useModelStore(pinia);
29
const { removeMessages } = mainStore;
30
const { setRoutes } = navigationStore;
31
const { setModel } = modelStore;
32
const routes = setRoutes(routesDef);
33
setModel(model);
34
35
const router = createRouter({
36
    history: createWebHistory(),
37
    linkExactActiveClass: "current",
38
    routes,
39
})
40
41
import App from "../components/import-sources/ISMain.vue";
42
43
// import { routes } from "./routes";
44
45
// const router = createRouter({
46
//     history: createWebHistory(),
47
//     linkExactActiveClass: "current",
48
//     routes,
49
// });
50
51
// import { useAVStore } from "../stores/authorised_values";
52
53
import i18n from "../i18n"
54
import inputs from "../inputs"
55
const app = createApp(App);
56
57
const rootComponent = app
58
    .use(i18n)
59
    .use(pinia)
60
    .use(router)
61
    .use(formkit, defaultConfig({
62
        inputs
63
    }))
64
    .component("font-awesome-icon", FontAwesomeIcon)
65
    .component("v-select", vSelect);
66
67
app.config.unwrapInjectedRef = true;
68
app.provide("mainStore", mainStore);
69
app.provide("navigationStore", navigationStore);
70
app.provide("modelStore", modelStore);
71
app.mount("#import-source");
72
73
router.beforeEach((to, from) => {
74
    navigationStore.$patch({current: to.meta.self});
75
    removeMessages(); // This will actually flag the messages as displayed already
76
});
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/routes/import-sources.js (+31 lines)
Line 0 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/stores/model.js (+51 lines)
Line 0 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/package.json (+2 lines)
Lines 9-14 Link Here
9
  "dependencies": {
9
  "dependencies": {
10
    "@cypress/vue": "^3.1.1",
10
    "@cypress/vue": "^3.1.1",
11
    "@cypress/webpack-dev-server": "^1.8.3",
11
    "@cypress/webpack-dev-server": "^1.8.3",
12
    "@formkit/themes": "^1.0.0-beta.14",
13
    "@formkit/vue": "^1.0.0-beta.14",
12
    "@fortawesome/fontawesome-svg-core": "^6.1.0",
14
    "@fortawesome/fontawesome-svg-core": "^6.1.0",
13
    "@fortawesome/free-solid-svg-icons": "^6.0.0",
15
    "@fortawesome/free-solid-svg-icons": "^6.0.0",
14
    "@fortawesome/vue-fontawesome": "^3.0.0-5",
16
    "@fortawesome/vue-fontawesome": "^3.0.0-5",
(-)a/t/db_dependent/Koha/ImportSources.t (+44 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2023 Theke Solutions
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use Test::More tests => 1;
23
24
use Koha::ImportSources;
25
26
use t::lib::TestBuilder;
27
my $schema = Koha::Database->new->schema;
28
my $builder = t::lib::TestBuilder->new;
29
30
subtest 'patron' => sub {
31
  plan tests => 1;
32
33
  $schema->storage->txn_begin;
34
35
  my $patron = $builder->build_object({class => 'Koha::Patrons', value => {firstname => 'Afirstname'}});
36
37
  my $import_source = $builder->build_object({class => 'Koha::ImportSources', value => {name => 'import_source', patron_id => $patron->borrowernumber}});
38
39
  my $fetched_patron = $import_source->patron;
40
41
  is($fetched_patron->firstname, 'Afirstname', 'Patron matches');
42
43
  $schema->storage->txn_rollback;
44
};
(-)a/t/db_dependent/api/v1/import_sources.t (+318 lines)
Line 0 Link Here
1
#!/usr/bin/env perl
2
3
# Copyright 2023 Theke Solutions
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use Test::More tests => 5;
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::ImportSources;
31
use Koha::Database;
32
33
my $schema  = Koha::Database->new->schema;
34
my $builder = t::lib::TestBuilder->new;
35
36
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
37
38
my $t = Test::Mojo->new('Koha::REST::V1');
39
40
subtest 'list() tests' => sub {
41
42
  plan tests => 12;
43
44
  $schema->storage->txn_begin;
45
46
  my $source = $builder->build_object(
47
    {
48
      class => 'Koha::ImportSources'
49
    }
50
  );
51
  my $patron = $builder->build_object(
52
      {
53
          class => 'Koha::Patrons',
54
          value => { flags => 3 }
55
      }
56
  );
57
58
  for ( 1..10 ) {
59
      $builder->build_object(
60
        {
61
          class => 'Koha::ImportSources'
62
        }
63
      );
64
  }
65
66
  my $nonprivilegedpatron = $builder->build_object(
67
      {
68
          class => 'Koha::Patrons',
69
          value => { flags => 0 }
70
      }
71
  );
72
73
  my $password = 'thePassword123';
74
75
  $nonprivilegedpatron->set_password(
76
      { password => $password, skip_validation => 1 } );
77
  my $userid = $nonprivilegedpatron->userid;
78
79
  $t->get_ok( "//$userid:$password@/api/v1/import_sources" )
80
    ->status_is(403)
81
    ->json_is(
82
      '/error' => 'Authorization failure. Missing required permission(s).' );
83
84
  $patron->set_password( { password => $password, skip_validation => 1 } );
85
  $userid = $patron->userid;
86
87
  $t->get_ok( "//$userid:$password@/api/v1/import_sources?_per_page=10" )
88
    ->status_is( 200, 'SWAGGER3.2.2' );
89
90
  my $response_count = scalar @{ $t->tx->res->json };
91
92
  is( $response_count, 10, 'The API returns 10 sources' );
93
94
  my $id = $source->import_source_id;
95
  $t->get_ok( "//$userid:$password@/api/v1/import_sources?q={\"import_source_id\": $id}" )
96
    ->status_is(200)
97
    ->json_is( '' => [ $source->to_api ], 'SWAGGER3.3.2');
98
99
  $source->delete;
100
101
  $t->get_ok( "//$userid:$password@/api/v1/import_sources?q={\"import_source_id\": $id}" )
102
    ->status_is(200)
103
    ->json_is( '' => [] );
104
105
  $schema->storage->txn_rollback;
106
107
};
108
109
subtest 'get() tests' => sub {
110
111
  plan tests => 9;
112
113
  $schema->storage->txn_begin;
114
115
  my $source = $builder->build_object(
116
    {
117
      class => 'Koha::ImportSources'
118
    }
119
  );
120
  my $patron = $builder->build_object(
121
      {
122
          class => 'Koha::Patrons',
123
          value => { flags => 3 }
124
      }
125
  );
126
127
  my $nonprivilegedpatron = $builder->build_object(
128
      {
129
          class => 'Koha::Patrons',
130
          value => { flags => 0 }
131
      }
132
  );
133
134
  my $password = 'thePassword123';
135
136
  $nonprivilegedpatron->set_password(
137
      { password => $password, skip_validation => 1 } );
138
  my $userid = $nonprivilegedpatron->userid;
139
140
  my $id = $source->import_source_id;
141
142
  $t->get_ok( "//$userid:$password@/api/v1/import_sources/$id" )
143
    ->status_is(403)
144
    ->json_is(
145
      '/error' => 'Authorization failure. Missing required permission(s).' );
146
147
  $patron->set_password( { password => $password, skip_validation => 1 } );
148
  $userid = $patron->userid;
149
150
  $t->get_ok( "//$userid:$password@/api/v1/import_sources/$id" )
151
    ->status_is( 200, 'SWAGGER3.2.2' )
152
    ->json_is( '' => $source->to_api, 'SWAGGER3.3.2' );
153
154
  $source->delete;
155
156
  $t->get_ok( "//$userid:$password@/api/v1/import_sources/$id" )
157
    ->status_is(404)
158
    ->json_is( '/error' => 'Import source not found' );
159
160
  $schema->storage->txn_rollback;
161
162
};
163
164
subtest 'delete() tests' => sub {
165
166
  plan tests => 6;
167
168
  $schema->storage->txn_begin;
169
170
  my $source = $builder->build_object(
171
    {
172
      class => 'Koha::ImportSources'
173
    }
174
  );
175
  my $patron = $builder->build_object(
176
      {
177
          class => 'Koha::Patrons',
178
          value => { flags => 3 }
179
      }
180
  );
181
182
  my $nonprivilegedpatron = $builder->build_object(
183
      {
184
          class => 'Koha::Patrons',
185
          value => { flags => 0 }
186
      }
187
  );
188
189
  my $password = 'thePassword123';
190
191
  $nonprivilegedpatron->set_password(
192
      { password => $password, skip_validation => 1 } );
193
  my $userid = $nonprivilegedpatron->userid;
194
195
  my $id = $source->import_source_id;
196
197
  $t->delete_ok( "//$userid:$password@/api/v1/import_sources/$id" )
198
    ->status_is(403)
199
    ->json_is(
200
      '/error' => 'Authorization failure. Missing required permission(s).' );
201
202
  $patron->set_password( { password => $password, skip_validation => 1 } );
203
  $userid = $patron->userid;
204
205
  $t->delete_ok( "//$userid:$password@/api/v1/import_sources/$id" )
206
    ->status_is( 204, 'SWAGGER3.2.2' );
207
208
  my $deleted_source = Koha::ImportSources->search({import_source_id => $id});
209
210
  is($deleted_source->count, 0, 'No import source found');
211
212
  $schema->storage->txn_rollback;
213
214
};
215
216
subtest 'add() tests' => sub {
217
218
  plan tests => 8;
219
220
  $schema->storage->txn_begin;
221
222
  Koha::ImportSources->delete;
223
224
  my $patron = $builder->build_object(
225
      {
226
          class => 'Koha::Patrons',
227
          value => { flags => 3 }
228
      }
229
  );
230
231
  my $nonprivilegedpatron = $builder->build_object(
232
      {
233
          class => 'Koha::Patrons',
234
          value => { flags => 0 }
235
      }
236
  );
237
238
  my $password = 'thePassword123';
239
240
  $nonprivilegedpatron->set_password(
241
      { password => $password, skip_validation => 1 } );
242
  my $userid = $nonprivilegedpatron->userid;
243
  my $patron_id = $nonprivilegedpatron->borrowernumber;
244
245
  $t->post_ok( "//$userid:$password@/api/v1/import_sources" => json => { name => 'test1', patron_id => $patron_id })
246
    ->status_is(403)
247
    ->json_is(
248
      '/error' => 'Authorization failure. Missing required permission(s).' );
249
250
  $patron->set_password( { password => $password, skip_validation => 1 } );
251
  $userid = $patron->userid;
252
253
  $t->post_ok( "//$userid:$password@/api/v1/import_sources" => json => { name => 'test1', patron_id => $patron_id })
254
    ->status_is( 201, 'SWAGGER3.2.2' )
255
    ->json_is('/name', 'test1')
256
    ->json_is('/patron_id', $patron_id);
257
258
  my $created_source = Koha::ImportSources->search->next;
259
260
  is($created_source->name, 'test1', 'Import source found');
261
262
  $schema->storage->txn_rollback;
263
264
};
265
266
subtest 'update() tests' => sub {
267
268
  plan tests => 7;
269
270
  $schema->storage->txn_begin;
271
272
  my $source = $builder->build_object(
273
    {
274
      class => 'Koha::ImportSources',
275
      value => { name => 'Oldname' }
276
    }
277
  );
278
  my $patron = $builder->build_object(
279
      {
280
          class => 'Koha::Patrons',
281
          value => { flags => 3 }
282
      }
283
  );
284
285
  my $nonprivilegedpatron = $builder->build_object(
286
      {
287
          class => 'Koha::Patrons',
288
          value => { flags => 0 }
289
      }
290
  );
291
292
  my $password = 'thePassword123';
293
294
  $nonprivilegedpatron->set_password(
295
      { password => $password, skip_validation => 1 } );
296
  my $userid = $nonprivilegedpatron->userid;
297
298
  my $id = $source->import_source_id;
299
300
  $t->put_ok( "//$userid:$password@/api/v1/import_sources/$id" => json => { name => 'Newname', patron_id => $source->patron_id } )
301
    ->status_is(403)
302
    ->json_is(
303
      '/error' => 'Authorization failure. Missing required permission(s).' );
304
305
  $patron->set_password( { password => $password, skip_validation => 1 } );
306
  $userid = $patron->userid;
307
308
  $t->put_ok( "//$userid:$password@/api/v1/import_sources/$id" => json => { name => 'Newname', patron_id => $source->patron_id } )
309
    ->status_is( 200, 'SWAGGER3.2.2' )
310
    ->json_is('/name', 'Newname');
311
312
  my $updated_source = Koha::ImportSources->find($id);
313
314
  is($updated_source->name, 'Newname', 'Import source updated');
315
316
  $schema->storage->txn_rollback;
317
318
};
(-)a/webpack.config.js (-1 / +1 lines)
Lines 6-11 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
  },
10
  },
10
  output: {
11
  output: {
11
    filename: "[name].js",
12
    filename: "[name].js",
12
- 

Return to bug 32607