Bug 32030: Link external packages with agreements
authorJonathan Druart <jonathan.druart@bugs.koha-community.org>
Thu, 30 Jun 2022 15:14:40 +0000 (17:14 +0200)
committerTomas Cohen Arazi <tomascohen@theke.io>
Tue, 8 Nov 2022 12:44:11 +0000 (09:44 -0300)
Signed-off-by: Jonathan Field <jonathan.field@ptfs-europe.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
14 files changed:
Koha/ERM/EHoldings/Package.pm
Koha/ERM/Providers/EBSCO.pm
Koha/REST/V1/ERM/EHoldings/Packages/Local.pm
api/v1/swagger/definitions/erm_eholdings_package.yaml
installer/data/mysql/atomicupdate/erm.pl
installer/data/mysql/kohastructure.sql
koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/AgreementsList.vue
koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOPackageAgreements.vue
koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsEBSCOPackagesShow.vue
koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsLocalPackagesFormAdd.vue
koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/EHoldingsLocalPackagesShow.vue
koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/Modal.vue [deleted file]
koha-tmpl/intranet-tmpl/prog/js/vue/fetch.js
koha-tmpl/intranet-tmpl/prog/js/vue/routes.js

index 6ee7975..a324887 100644 (file)
@@ -84,6 +84,26 @@ sub vendor {
     return Koha::Acquisition::Bookseller->_new_from_dbic($rs);
 }
 
+=head3 to_api_mapping
+
+=cut
+
+sub to_api_mapping {
+    my ( $self ) = @_;
+
+    return {
+        external_id => undef,
+        ( # Do we really need this?
+            $self->external_id
+            ? (
+                package_id       => $self->external_id,
+                koha_internal_id => $self->package_id
+              )
+            : ()
+        )
+    };
+}
+
 =head2 Internal methods
 
 =head3 _type
index 7df6569..1ed7956 100644 (file)
@@ -9,6 +9,8 @@ use List::Util qw( first );
 
 use Koha::Exceptions;
 
+use Koha::ERM::EHoldings::Packages;
+
 sub new {
     my $class = shift;
     my $self = {};
@@ -80,8 +82,13 @@ sub build_vendor {
 
 sub build_package {
     my ( $self, $result ) = @_;
+    my $local_package = $self->get_local_package(
+        $result->{vendorId} . '-' . $result->{packageId} );
     my $package = {
         package_id   => $result->{vendorId} . '-' . $result->{packageId},
+        ( $local_package
+            ? ( koha_internal_id => $local_package->package_id )
+            : () ),
         name         => $result->{packageName},
         content_type => $result->{contentType}, # This does not exist in /vendors/1/packages/2/titles/3
         created_on   => undef,
@@ -121,11 +128,18 @@ sub build_additional_params {
     return $additional_params;
 }
 
+sub get_local_package {
+    my ( $self, $package_id ) = @_;
+    return Koha::ERM::EHoldings::Packages->find(
+        { provider => 'ebsco', external_id => $package_id } );
+}
+
 sub embed {
     my ( $self, $object, $info, $embed_header ) = @_;
     $embed_header ||= q{};
 
     my @embed_resources;
+
     foreach my $embed_req ( split /\s*,\s*/, $embed_header ) {
         if ( $embed_req eq 'vendor.name' ) {
             $object->{vendor} = { name => $info->{vendorName}, };
@@ -145,6 +159,23 @@ sub embed {
         elsif ( $embed_req eq 'package.name' ) {
             $object->{package} = { name => $info->{packageName}, };
         }
+        elsif ( $embed_req eq 'package_agreements.agreement' ) {
+            # How to deal with 'package_agreements.agreement'?
+            $object->{package_agreements} = [];
+            my $package_id = $info->{vendorId} . '-' . $info->{packageId};
+            my $local_package = $self->get_local_package($package_id);
+            if ( $local_package ) {
+                for my $package_agreement (
+                    @{ $local_package->package_agreements->as_list } )
+                {
+                    push @{ $object->{package_agreements} },
+                      {
+                        %{ $package_agreement->unblessed },
+                        agreement => $package_agreement->agreement->unblessed,
+                      };
+                }
+            }
+        }
         if ( $embed_req eq 'resources' || $embed_req eq 'resources.package' ) {
             push @embed_resources, $embed_req;
         }
index 7bae58f..f9dfa70 100644 (file)
@@ -35,7 +35,8 @@ use Try::Tiny qw( catch try );
 sub list {
     my $c = shift->openapi->valid_input or return;
     return try {
-        my $packages_set = Koha::ERM::EHoldings::Packages->new;
+        my $packages_set =
+          Koha::ERM::EHoldings::Packages->search( { 'me.external_id' => undef } );
         my $packages     = $c->objects->search($packages_set);
         return $c->render( status => 200, openapi => $packages );
     }
@@ -89,6 +90,7 @@ sub add {
                 my $body = $c->validation->param('body');
 
                 my $package_agreements = delete $body->{package_agreements} // [];
+                delete $body->{external_id} unless $body->{external_id};
 
                 my $package = Koha::ERM::EHoldings::Package->new_from_api($body)->store;
                 $package->package_agreements($package_agreements);
@@ -164,9 +166,12 @@ sub update {
                 my $body = $c->validation->param('body');
 
                 my $package_agreements = delete $body->{package_agreements} // [];
-                use Data::Printer colored => 1; warn p $package_agreements;
+                delete $body->{external_id} unless $body->{external_id};
 
                 $package->set_from_api($body)->store;
+
+                # FIXME If there is no package_agreements and external_id is set, we could delete the row
+                # ie. It's coming from EBSCO and we don't have local data linked to it
                 $package->package_agreements($package_agreements);
 
                 $c->res->headers->location($c->req->url->to_string . '/' . $package->package_id);
index b2e055c..190e751 100644 (file)
@@ -13,11 +13,21 @@ properties:
   name:
     description: name of the package
     type: string
+  provider:
+    description: external id of the package
+    type:
+      - string
+      - "null"
   external_id:
     description: external id of the package
     type:
       - string
       - "null"
+  koha_internal_id:
+    description: internal id of the package
+    type:
+      - integer
+      - "null"
   package_type:
     description: type of the package
     type:
index e9bf90a..a67ca3b 100755 (executable)
@@ -191,6 +191,7 @@ return {
                     `vendor_id` INT(11) DEFAULT NULL COMMENT 'foreign key to aqbooksellers',
                     `name` VARCHAR(255) NOT NULL COMMENT 'name of the package',
                     `external_id` VARCHAR(255) DEFAULT NULL COMMENT 'External key',
+                    `provider` ENUM('ebsco') DEFAULT NULL COMMENT 'External provider',
                     `package_type` VARCHAR(80) DEFAULT NULL COMMENT 'type of the package',
                     `content_type` VARCHAR(80) DEFAULT NULL COMMENT 'type of the package',
                     `created_on` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'date of creation of the package',
index 8138f2e..7674de1 100644 (file)
@@ -2902,6 +2902,7 @@ CREATE TABLE `erm_eholdings_packages` (
     `vendor_id` INT(11) DEFAULT NULL COMMENT 'foreign key to aqbooksellers',
     `name` VARCHAR(255) NOT NULL COMMENT 'name of the package',
     `external_id` VARCHAR(255) DEFAULT NULL COMMENT 'External key',
+    `provider` ENUM('ebsco') DEFAULT NULL COMMENT 'External provider',
     `package_type` VARCHAR(80) DEFAULT NULL COMMENT 'type of the package',
     `content_type` VARCHAR(80) DEFAULT NULL COMMENT 'type of the package',
     `created_on` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'date of creation of the package',
index 38fa2c8..b337473 100644 (file)
@@ -1,7 +1,7 @@
 <template>
     <div v-if="!this.initialized">{{ $t("Loading") }}</div>
     <div v-else-if="this.agreements" id="agreements_list">
-        <Toolbar />
+        <Toolbar v-if="before_route_entered" />
         <table v-if="this.agreements.length" id="agreement_list"></table>
         <div v-else-if="this.initialized" class="dialog message">
             {{ $t("There are no agreements defined") }}
@@ -36,11 +36,17 @@ export default {
         return {
             agreements: [],
             initialized: false,
+            before_route_entered: false,
+            building_table: false,
         }
     },
     beforeRouteEnter(to, from, next) {
         next(vm => {
-            vm.getAgreements()
+            vm.before_route_entered = true // FIXME This is ugly, but we need to distinguish when it's used as main component or child component (from EHoldingsEBSCOPAckagesShow for instance)
+            if (!vm.building_table) {
+                vm.building_table = true
+                vm.getAgreements().then(() => vm.build_datatable())
+            }
         })
     },
     methods: {
@@ -58,193 +64,232 @@ export default {
         delete_agreement: function (agreement_id) {
             this.$router.push("/cgi-bin/koha/erm/agreements/delete/" + agreement_id)
         },
-    },
-    updated() {
-        let show_agreement = this.show_agreement
-        let edit_agreement = this.edit_agreement
-        let delete_agreement = this.delete_agreement
-        let default_search = this.$route.query.q
+        select_agreement: function (agreement_id) {
+            this.$emit('select-agreement', agreement_id)
+            this.$emit('close')
+        },
+        build_datatable: function () {
+            let show_agreement = this.show_agreement
+            let edit_agreement = this.edit_agreement
+            let delete_agreement = this.delete_agreement
+            let select_agreement = this.select_agreement
+            let default_search = this.$route.query.q
+            let actions = this.before_route_entered ? 'edit_delete' : 'select'
 
-        window['vendors'] = this.vendors.map(e => {
-            e['_id'] = e['id']
-            e['_str'] = e['name']
-            return e
-        })
-        let vendors_map = this.vendors.reduce((map, e) => {
-            map[e.id] = e
-            return map
-        }, {})
-        window['av_agreement_statuses'] = this.av_agreement_statuses.map(e => {
-            e['_id'] = e['authorised_value']
-            e['_str'] = e['lib']
-            return e
-        })
-        let av_agreement_statuses_map = this.av_agreement_statuses.reduce((map, e) => {
-            map[e.authorised_value] = e
-            return map
-        }, {})
-        window['av_agreement_closure_reasons'] = this.av_agreement_closure_reasons.map(e => {
-            e['_id'] = e['authorised_value']
-            e['_str'] = e['lib']
-            return e
-        })
-        let av_agreement_closure_reasons_map = this.av_agreement_closure_reasons.reduce((map, e) => {
-            map[e.authorised_value] = e
-            return map
-        }, {})
-        window['av_agreement_renewal_priorities'] = this.av_agreement_renewal_priorities.map(e => {
-            e['_id'] = e['authorised_value']
-            e['_str'] = e['lib']
-            return e
-        })
-        let av_agreement_renewal_priorities_map = this.av_agreement_renewal_priorities.reduce((map, e) => {
-            map[e.authorised_value] = e
-            return map
-        }, {})
-        window['av_agreement_is_perpetual'] = [{ _id: 0, _str: _('No') }, { _id: 1, _str: _("Yes") }]
+            window['vendors'] = this.vendors.map(e => {
+                e['_id'] = e['id']
+                e['_str'] = e['name']
+                return e
+            })
+            let vendors_map = this.vendors.reduce((map, e) => {
+                map[e.id] = e
+                return map
+            }, {})
+            window['av_agreement_statuses'] = this.av_agreement_statuses.map(e => {
+                e['_id'] = e['authorised_value']
+                e['_str'] = e['lib']
+                return e
+            })
+            let av_agreement_statuses_map = this.av_agreement_statuses.reduce((map, e) => {
+                map[e.authorised_value] = e
+                return map
+            }, {})
+            window['av_agreement_closure_reasons'] = this.av_agreement_closure_reasons.map(e => {
+                e['_id'] = e['authorised_value']
+                e['_str'] = e['lib']
+                return e
+            })
+            let av_agreement_closure_reasons_map = this.av_agreement_closure_reasons.reduce((map, e) => {
+                map[e.authorised_value] = e
+                return map
+            }, {})
+            window['av_agreement_renewal_priorities'] = this.av_agreement_renewal_priorities.map(e => {
+                e['_id'] = e['authorised_value']
+                e['_str'] = e['lib']
+                return e
+            })
+            let av_agreement_renewal_priorities_map = this.av_agreement_renewal_priorities.reduce((map, e) => {
+                map[e.authorised_value] = e
+                return map
+            }, {})
+            window['av_agreement_is_perpetual'] = [{ _id: 0, _str: _('No') }, { _id: 1, _str: _("Yes") }]
 
-        const table = $('#agreement_list').kohaTable({
-            "ajax": {
-                "url": "/api/v1/erm/agreements",
-            },
-            "order": [[0, "asc"]],
-            "search": { search: default_search },
-            "columnDefs": [{
-                "targets": [0, 2],
-                "render": function (data, type, row, meta) {
-                    if (type == 'display') {
-                        return escape_str(data)
-                    }
-                    return data
-                }
-            }],
-            "columns": [
-                {
-                    "title": __("Name"),
-                    "data": "me.agreement_id:me.name",
-                    "searchable": true,
-                    "orderable": true,
-                    "render": function (data, type, row, meta) {
-                        // Rendering done in drawCallback
-                        return ""
-                    }
-                },
-                {
-                    "title": __("Vendor"),
-                    "data": "vendor_id",
-                    "searchable": true,
-                    "orderable": true,
-                    "render": function (data, type, row, meta) {
-                        return row.vendor_id != undefined ? escape_str(vendors_map[row.vendor_id].name) : ""
-                    }
+            const table = $('#agreement_list').kohaTable({
+                "ajax": {
+                    "url": "/api/v1/erm/agreements",
                 },
-                {
-                    "title": __("Description"),
-                    "data": "description",
-                    "searchable": true,
-                    "orderable": true
-                },
-                {
-                    "title": __("Status"),
-                    "data": "status",
-                    "searchable": true,
-                    "orderable": true,
-                    "render": function (data, type, row, meta) {
-                        return escape_str(av_agreement_statuses_map[row.status].lib)
-                    }
-                },
-                {
-                    "title": __("Closure reason"),
-                    "data": "closure_reason",
-                    "searchable": true,
-                    "orderable": true,
-                    "render": function (data, type, row, meta) {
-                        return row.closure_reason != undefined && row.closure_reason != "" ? escape_str(av_agreement_closure_reasons_map[row.closure_reason].lib) : ""
-                    }
-                },
-                {
-                    "title": __("Is perpetual"),
-                    "data": "is_perpetual",
-                    "searchable": true,
-                    "orderable": true,
+                "order": [[0, "asc"]],
+                autoWidth: false,
+                "search": { search: default_search },
+                "columnDefs": [{
+                    "targets": [0, 2],
                     "render": function (data, type, row, meta) {
-                        return escape_str(row.is_perpetual ? _("Yes") : _("No"))
-                    }
-                },
-                {
-                    "title": __("Renewal priority"),
-                    "data": "renewal_priority",
-                    "searchable": true,
-                    "orderable": true,
-                    "render": function (data, type, row, meta) {
-                        return row.renewal_priority != undefined && row.renewal_priority != "" ? escape_str(av_agreement_renewal_priorities_map[row.renewal_priority].lib) : ""
+                        if (type == 'display') {
+                            return escape_str(data)
+                        }
+                        return data
                     }
-                },
-                {
-                    "title": __("Actions"),
-                    "data": function (row, type, val, meta) {
-                        return '<div class="actions"></div>'
+                }],
+                "columns": [
+                    {
+                        "title": __("Name"),
+                        "data": "me.agreement_id:me.name",
+                        "searchable": true,
+                        "orderable": true,
+                        "render": function (data, type, row, meta) {
+                            // Rendering done in drawCallback
+                            return ""
+                        }
                     },
-                    "className": "actions noExport",
-                    "searchable": false,
-                    "orderable": false
-                }
-            ],
-            drawCallback: function (settings) {
-
-                var api = new $.fn.dataTable.Api(settings)
-
-                $.each($(this).find("td .actions"), function (index, e) {
-                    let agreement_id = api.row(index).data().agreement_id
-                    let editButton = createVNode("a", {
-                        class: "btn btn-default btn-xs", role: "button", onClick: () => {
-                            edit_agreement(agreement_id)
+                    {
+                        "title": __("Vendor"),
+                        "data": "vendor_id",
+                        "searchable": true,
+                        "orderable": true,
+                        "render": function (data, type, row, meta) {
+                            return row.vendor_id != undefined ? escape_str(vendors_map[row.vendor_id].name) : ""
                         }
                     },
-                        [createVNode("i", { class: "fa fa-pencil", 'aria-hidden': "true" }), __("Edit")])
-
-                    let deleteButton = createVNode("a", {
-                        class: "btn btn-default btn-xs", role: "button", onClick: () => {
-                            delete_agreement(agreement_id)
+                    {
+                        "title": __("Description"),
+                        "data": "description",
+                        "searchable": true,
+                        "orderable": true
+                    },
+                    {
+                        "title": __("Status"),
+                        "data": "status",
+                        "searchable": true,
+                        "orderable": true,
+                        "render": function (data, type, row, meta) {
+                            return escape_str(av_agreement_statuses_map[row.status].lib)
                         }
                     },
-                        [createVNode("i", { class: "fa fa-trash", 'aria-hidden': "true" }), __("Delete")])
-
-                    let n = createVNode('span', {}, [editButton, " ", deleteButton])
-                    render(n, e)
-                })
-
-                $.each($(this).find("tbody tr td:first-child"), function (index, e) {
-                    let row = api.row(index).data()
-                    if (!row) return // Happen if the table is empty
-                    let n = createVNode("a", {
-                        role: "button",
-                        onClick: () => {
-                            show_agreement(row.agreement_id)
+                    {
+                        "title": __("Closure reason"),
+                        "data": "closure_reason",
+                        "searchable": true,
+                        "orderable": true,
+                        "render": function (data, type, row, meta) {
+                            return row.closure_reason != undefined && row.closure_reason != "" ? escape_str(av_agreement_closure_reasons_map[row.closure_reason].lib) : ""
                         }
                     },
-                        `${row.name} (#${row.agreement_id})`
-                    )
-                    render(n, e)
-                })
-            },
-            preDrawCallback: function (settings) {
-                var table_id = settings.nTable.id
-                $("#" + table_id).find("thead th").eq(1).attr('data-filter', 'vendors')
-                $("#" + table_id).find("thead th").eq(3).attr('data-filter', 'av_agreement_statuses')
-                $("#" + table_id).find("thead th").eq(4).attr('data-filter', 'av_agreement_closure_reasons')
-                $("#" + table_id).find("thead th").eq(5).attr('data-filter', 'av_agreement_is_perpetual')
-                $("#" + table_id).find("thead th").eq(6).attr('data-filter', 'av_agreement_renewal_priorities')
-            }
+                    {
+                        "title": __("Is perpetual"),
+                        "data": "is_perpetual",
+                        "searchable": true,
+                        "orderable": true,
+                        "render": function (data, type, row, meta) {
+                            return escape_str(row.is_perpetual ? _("Yes") : _("No"))
+                        }
+                    },
+                    {
+                        "title": __("Renewal priority"),
+                        "data": "renewal_priority",
+                        "searchable": true,
+                        "orderable": true,
+                        "render": function (data, type, row, meta) {
+                            return row.renewal_priority != undefined && row.renewal_priority != "" ? escape_str(av_agreement_renewal_priorities_map[row.renewal_priority].lib) : ""
+                        }
+                    },
+                    {
+                        "title": __("Actions"),
+                        "data": function (row, type, val, meta) {
+                            return '<div class="actions"></div>'
+                        },
+                        "className": "actions noExport",
+                        "searchable": false,
+                        "orderable": false
+                    }
+                ],
+                drawCallback: function (settings) {
+
+                    var api = new $.fn.dataTable.Api(settings)
+
+                    if (actions == 'edit_delete') {
+                        $.each($(this).find("td .actions"), function (index, e) {
+                            let agreement_id = api.row(index).data().agreement_id
+                            let editButton = createVNode("a", {
+                                class: "btn btn-default btn-xs", role: "button", onClick: () => {
+                                    edit_agreement(agreement_id)
+                                }
+                            },
+                                [createVNode("i", { class: "fa fa-pencil", 'aria-hidden': "true" }), __("Edit")])
 
-        }, agreement_table_settings, 1)
+                            let deleteButton = createVNode("a", {
+                                class: "btn btn-default btn-xs", role: "button", onClick: () => {
+                                    delete_agreement(agreement_id)
+                                }
+                            },
+                                [createVNode("i", { class: "fa fa-trash", 'aria-hidden': "true" }), __("Delete")])
+
+                            let n = createVNode('span', {}, [editButton, " ", deleteButton])
+                            render(n, e)
+                        })
+                    } else {
+                        $.each($(this).find("td .actions"), function (index, e) {
+                            let agreement_id = api.row(index).data().agreement_id
+                            let selectButton = createVNode("a", {
+                                class: "btn btn-default btn-xs", role: "button", onClick: () => {
+                                    select_agreement(agreement_id)
+                                }
+                            },
+                                [createVNode("i", { class: "fa fa-check", 'aria-hidden': "true" }), __("Select")])
+
+                            let n = createVNode('span', {}, [selectButton])
+                            render(n, e)
+                        })
+                    }
+
+                    $.each($(this).find("tbody tr td:first-child"), function (index, e) {
+                        let row = api.row(index).data()
+                        if (!row) return // Happen if the table is empty
+                        let n = createVNode("a", {
+                            role: "button",
+                            onClick: () => {
+                                show_agreement(row.agreement_id)
+                            }
+                        },
+                            `${row.name} (#${row.agreement_id})`
+                        )
+                        render(n, e)
+                    })
+                },
+                preDrawCallback: function (settings) {
+                    var table_id = settings.nTable.id
+                    $("#" + table_id).find("thead th").eq(1).attr('data-filter', 'vendors')
+                    $("#" + table_id).find("thead th").eq(3).attr('data-filter', 'av_agreement_statuses')
+                    $("#" + table_id).find("thead th").eq(4).attr('data-filter', 'av_agreement_closure_reasons')
+                    $("#" + table_id).find("thead th").eq(5).attr('data-filter', 'av_agreement_is_perpetual')
+                    $("#" + table_id).find("thead th").eq(6).attr('data-filter', 'av_agreement_renewal_priorities')
+                }
+
+            }, agreement_table_settings, 1)
+        },
+        destroy_table: function () {
+            $('#agreement_list')
+                .DataTable()
+                .destroy(true)
+        },
+    },
+    mounted() {
+        if (!this.building_table) {
+            this.building_table = true
+            this.getAgreements().then(() => this.build_datatable())
+        }
     },
     beforeUnmount() {
-        $('#agreement_list')
-            .DataTable()
-            .destroy(true)
+        // This delays the closing of the modal, do we really need it?
+        //this.destroy_table()
     },
     components: { Toolbar },
     name: "AgreementsList",
+    emits: ["select-agreement", "close"],
 }
 </script>
+
+<style scoped>
+#agreement_list {
+    display: table;
+}
+</style>
\ No newline at end of file
index 3426197..1c3f65a 100644 (file)
 <template>
-    <fieldset class="rows" id="package_agreements">
-        <legend>{{ $t("Agreements") }}</legend>
-        <fieldset
-            class="rows"
-            v-for="(package_agreement, counter) in package_agreements"
+    <transition name="modal">
+        <div v-if="showModal" class="modal">
+            <AgreementsList @select-agreement="addAgreement" />
+            <input
+                type="button"
+                @click="showModal = false"
+                :value="$t('Close')"
+            />
+        </div>
+    </transition>
+    <div id="package_agreements">
+        <div
+            v-for="(
+                package_agreement, counter
+            ) in erm_package.package_agreements"
             v-bind:key="counter"
         >
-            <legend>
-                {{ $t("Agreement .counter", { counter: counter + 1 }) }}
-                <a href="#" @click.prevent="deleteAgreement(counter)"
-                    ><i class="fa fa-trash"></i>
-                    {{ $t("Remove this agreement") }}</a
-                >
-            </legend>
-            <ol>
-                <li>
-                    <label :for="`agreement_id_${counter}`"
-                        >{{ $t("Agreement") }}:
-                    </label>
-                    <select
-                        v-model="package_agreement.agreement_id"
-                        :id="`agreement_id_${counter}`"
-                        required
-                    >
-                        <option value=""></option>
-                        <option
-                            v-for="agreement in agreements"
-                            :key="agreement.agreement_id"
-                            :value="agreement.agreement_id"
-                            :selected="
-                                agreement.agreement_id ==
-                                package_agreement.agreement_id
-                                    ? true
-                                    : false
-                            "
-                        >
-                            {{ agreement.name }}
-                        </option>
-                    </select>
-                    <span class="required">{{ $t("Required") }}</span>
-                </li>
-            </ol>
-        </fieldset>
-        <a
-            v-if="agreements.length"
-            class="btn btn-default"
-            @click="addAgreement"
+            <router-link
+                :to="`/cgi-bin/koha/erm/agreements/${package_agreement.agreement.agreement_id}`"
+                >{{ package_agreement.agreement.name }}</router-link
+            >
+            &nbsp;
+            <a
+                href="#"
+                @click.prevent="deleteAgreement(counter)"
+                :title="$t('Remove this agreement')"
+                ><i class="fa fa-trash"></i
+            ></a>
+        </div>
+        <a class="btn btn-default btn-xs" @click="showModal = true"
             ><font-awesome-icon icon="plus" /> {{ $t("Add new agreement") }}</a
         >
-        <span v-else>{{ $t("There are no agreements created yet") }}</span>
-    </fieldset>
+    </div>
 </template>
 
 <script>
-import { fetchAgreements } from "../../fetch"
+import AgreementsList from "./AgreementsList.vue"
+import { createPackage, editPackage } from "../../fetch"
 
 export default {
     data() {
-        return {
-            agreements: [],
-        }
+        return { showModal: false, }
     },
     beforeCreate() {
-        fetchAgreements().then((agreements) => {
-            this.agreements = agreements
-        })
     },
     methods: {
-        addAgreement() {
-            this.package_agreements.push({
-                agreement_id: null,
-            })
+        serializeAgreement() {
+            let erm_package = JSON.parse(JSON.stringify(this.erm_package)) // copy
+            delete erm_package.vendor_id // This is the EBSCO's vendor_id
+
+            // Remove remote data, we don't need to store them (don't we?)
+            // Keep the name, it's mandatory by the REST API specs
+            delete erm_package.package_type
+            delete erm_package.content_type
+            erm_package.external_id = erm_package.package_id
+            delete erm_package.package_id
+            erm_package.provider = 'ebsco'
+            erm_package.package_id = erm_package.koha_internal_id
+            delete erm_package.koha_internal_id
+            return erm_package
+        },
+        addAgreement(agreement_id) {
+            this.showModal = false
+            let erm_package = this.serializeAgreement()
+            // Only add if it does not exist
+            // TODO Add a warning?
+            if (!erm_package.package_agreements.find((a) => a.agreement_id == agreement_id)) {
+                erm_package.package_agreements.push({ agreement_id })
+                if (this.erm_package.koha_internal_id) {
+                    editPackage(erm_package).then(() => {
+                        this.$emit('refresh-agreements')
+                    })
+                } else {
+                    createPackage(erm_package).then(() => {
+                        this.$emit('refresh-agreements')
+                    })
+                }
+            }
         },
         deleteAgreement(counter) {
-            this.package_agreements.splice(counter, 1)
+            let erm_package = this.serializeAgreement()
+            erm_package.package_agreements.splice(counter, 1)
+            editPackage(erm_package).then(() => {
+                this.$emit('refresh-agreements')
+            })
         },
     },
     props: {
-        package_agreements: Array,
+        erm_package: Object,
     },
+    components: {
+        AgreementsList,
+    },
+    emits: ['refresh-agreements'],
     name: 'EHoldingsEBSCOPackageAgreements',
 }
 </script>
+<style scoped>
+#package_agreements {
+    padding-left: 26rem;
+}
+.modal {
+    position: fixed;
+    z-index: 9998;
+    top: 0;
+    left: 0;
+    width: 80%;
+    height: 80%;
+    background-color: rgba(0, 0, 0, 0.5);
+    display: table;
+    transition: opacity 0.3s ease;
+    margin: auto;
+    padding: 20px 30px;
+    background-color: #fff;
+    border-radius: 2px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
+    transition: all 0.3s ease;
+    font-family: Helvetica, Arial, sans-serif;
+}
+</style>
\ No newline at end of file
index 4b2b84a..30d0bc1 100644 (file)
                     </li>
 
                     <li>
+                        <label>Agreements</label>
+                        <EHoldingsPackageAgreements
+                            :erm_package="erm_package"
+                            @refresh-agreements="refreshAgreements"
+                        />
+                    </li>
+
+                    <li>
                         <label
                             >Titles ({{ erm_package.resources_count }})</label
                         >
@@ -78,6 +86,7 @@
 </template>
 
 <script>
+import EHoldingsPackageAgreements from "./EHoldingsEBSCOPackageAgreements.vue"
 import EHoldingsPackageTitlesList from "./EHoldingsEBSCOPackageTitlesList.vue"
 import { useAVStore } from "../../stores/authorised_values"
 import { fetchEBSCOPackage } from "../../fetch"
@@ -101,10 +110,12 @@ export default {
                 vendor_id: null,
                 name: '',
                 external_id: '',
+                provider: '',
                 package_type: '',
                 content_type: '',
                 created_on: null,
                 resources: null,
+                package_agreements: [],
             },
             initialized: false,
         }
@@ -123,8 +134,14 @@ export default {
             this.erm_package = erm_package
             this.initialized = true
         },
+        refreshAgreements() {
+            // FIXME We could GET /erm/eholdings/packages/$package_id/agreements instead
+            this.initialized = false
+            this.getPackage(this.erm_package.package_id)
+        },
     },
     components: {
+        EHoldingsPackageAgreements,
         EHoldingsPackageTitlesList,
     },
     name: "EHoldingsEBSCOPackagesShow",
@@ -134,4 +151,4 @@ export default {
 fieldset.rows label {
     width: 25rem;
 }
-</style>
+</style>
\ No newline at end of file
index 3cf50fe..79a2d0f 100644 (file)
@@ -115,7 +115,7 @@ import EHoldingsPackageAgreements from "./EHoldingsLocalPackageAgreements.vue"
 import { useVendorStore } from "../../stores/vendors"
 import { useAVStore } from "../../stores/authorised_values"
 import { setMessage, setError } from "../../messages"
-import { fetchLocalPackage } from '../../fetch'
+import { fetchLocalPackage, createPackage, editPackage } from '../../fetch'
 import { storeToRefs } from "pinia"
 
 export default {
@@ -138,10 +138,13 @@ export default {
         return {
             erm_package: {
                 package_id: null,
+                vendor_id: null,
                 name: '',
                 external_id: '',
                 package_type: '',
                 content_type: '',
+                created_on: null,
+                resources: null,
                 package_agreements: [],
             },
             initialized: false,
@@ -166,42 +169,26 @@ export default {
             e.preventDefault()
 
             let erm_package = JSON.parse(JSON.stringify(this.erm_package)) // copy
-            let apiUrl = '/api/v1/erm/eholdings/local/packages'
 
-            let method = 'POST'
             if (erm_package.package_id) {
-                method = 'PUT'
-                apiUrl += '/' + erm_package.package_id
-            }
-            delete erm_package.package_id
-            delete erm_package.resources
-            delete erm_package.vendor
-            delete erm_package.resources_count
-
-            erm_package.package_agreements = erm_package.package_agreements.map(({ package_id, agreement, ...keepAttrs }) => keepAttrs)
-
-            const options = {
-                method: method,
-                body: JSON.stringify(erm_package),
-                headers: {
-                    'Content-Type': 'application/json;charset=utf-8'
-                },
-            }
-
-            fetch(apiUrl, options)
-                .then(response => {
+                editPackage(erm_package).then(response => {
                     if (response.status == 200) {
                         this.$router.push("/cgi-bin/koha/erm/eholdings/local/packages")
                         setMessage(this.$t("Package updated"))
-                    } else if (response.status == 201) {
+                    } else {
+                        setError(response.message || response.statusText)
+                    }
+                })
+            } else {
+                createPackage(erm_package).then(response => {
+                    if (response.status == 201) {
                         this.$router.push("/cgi-bin/koha/erm/eholdings/local/packages")
                         setMessage(this.$t("Package created"))
                     } else {
                         setError(response.message || response.statusText)
                     }
-                }, (error) => {
-                    setError(error)
-                }).catch(e => { console.log(e) })
+                })
+            }
         },
     },
     components: {
index 174a222..8e6f92f 100644 (file)
                             >
                         </span>
                     </li>
-                    <li v-if="erm_package.external_id">
-                        <label>{{ $t("External ID") }}:</label>
-                        <span>
-                            <!-- FIXME Create a syspref to store the URL -->
-                            <a
-                                :href="`https://ptfs-europe-demo.folio.ebsco.com/eholdings/packages/${erm_package.vendor.external_id}-${erm_package.external_id}`"
-                            >
-                                {{ erm_package.external_id }}
-                            </a>
-                        </span>
-                    </li>
                     <li>
                         <label>{{ $t("Package type") }}:</label>
                         <span>{{
diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/Modal.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/Modal.vue
deleted file mode 100644 (file)
index 80ac259..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-<template>
-    <div class="modal-mask">
-      <div class="modal-wrapper">
-        <div class="modal-container">
-
-          <div class="modal-header">
-            <slot name="header">
-              default header
-            </slot>
-          </div>  
-
-          <div class="modal-body">
-            <slot name="body">
-              <AgreementsList />
-            </slot>
-          </div>
-
-          <div class="modal-footer">
-            <slot name="footer">
-              default footer
-              <button class="modal-default-button" @click="$emit('close')">
-                OK
-              </button>
-            </slot>
-          </div>
-        </div>
-      </div>
-    </div>
-</template>
-
-<script>
-import AgreementsList from "./AgreementsList.vue"
-export default {
-
-  setup() {
-    
-  },
-  components:{
-    AgreementsList
-  }
-}
-</script>
-
-export default 
-
-<style scoped>
-.modal-mask {
-  position: fixed;
-  z-index: 9998;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background-color: rgba(0, 0, 0, 0.5);
-  display: table;
-  transition: opacity 0.3s ease;
-}
-
-.modal-wrapper {
-  display: table-cell;
-  vertical-align: middle;
-}
-
-.modal-container {
-  width: 300px;
-  margin: 0px auto;
-  padding: 20px 30px;
-  background-color: #fff;
-  border-radius: 2px;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
-  transition: all 0.3s ease;
-  font-family: Helvetica, Arial, sans-serif;
-}
-
-.modal-header h3 {
-  margin-top: 0;
-  color: #42b983;
-}
-
-.modal-body {
-  margin: 20px 0;
-}
-
-.modal-default-button {
-  float: right;
-}
-
-/*
- * The following styles are auto-applied to elements with
- * transition="modal" when their visibility is toggled
- * by Vue.js.
- *
- * You can easily play with the modal transition by editing
- * these styles.
- */
-
-.modal-enter-from, .modal-leave-to {
-  opacity: 0;
-}
-
-.modal-enter-active .modal-container,
-.modal-leave-active .modal-container {
-  -webkit-transform: scale(1.1);
-  transform: scale(1.1);
-}
-
-</style>
\ No newline at end of file
index 712d34d..6818526 100644 (file)
@@ -104,6 +104,53 @@ export const fetchVendors = async function () {
     return vendors;
 };
 
+const _createEditPackage = async function (method, erm_package) {
+    let apiUrl = "/api/v1/erm/eholdings/local/packages";
+
+    if (method == "PUT") {
+        apiUrl += "/" + erm_package.package_id;
+    }
+    delete erm_package.package_id;
+    delete erm_package.resources;
+    delete erm_package.vendor;
+    delete erm_package.resources_count;
+    delete erm_package.is_selected;
+
+    erm_package.package_agreements = erm_package.package_agreements.map(
+        ({ package_id, agreement, ...keepAttrs }) => keepAttrs
+    );
+
+    const options = {
+        method: method,
+        body: JSON.stringify(erm_package),
+        headers: {
+            "Content-Type": "application/json;charset=utf-8",
+        },
+    };
+
+    let r;
+    await fetch(apiUrl, options)
+        .then(
+            (response) => {
+                r = response;
+            },
+            (error) => {
+                setError(error);
+            }
+        )
+        .catch((e) => {
+            console.log(e);
+        });
+    return r;
+};
+
+export const createPackage = function (erm_package) {
+    return _createEditPackage("POST", erm_package);
+};
+export const editPackage = function (erm_package) {
+    return _createEditPackage("PUT", erm_package);
+};
+
 const _fetchPackage = async function (apiUrl, package_id) {
     if (!package_id) return;
     let erm_package;
index be9a8cf..7cba547 100644 (file)
@@ -175,7 +175,7 @@ export const routes = [
                     breadcrumb: () =>
                         build_breadcrumb(
                             breadcrumb_paths.agreements,
-                            "Edit agreement" // $t("Edit agreemetn")
+                            "Edit agreement" // $t("Edit agreement")
                         ),
                 },
             },