From cdefd0bc785175a7aad7c1b51af128b31368c44c Mon Sep 17 00:00:00 2001
From: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Date: Fri, 1 Jul 2022 15:02:58 +0100
Subject: [PATCH] Bug 31080: Prevent bundles from being nested
Content-Type: text/plain; charset=utf-8

This is a follow-up for bug 28854 to prevent bundle type items from
being nested in to other bundles.

Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
---
 Koha/Exceptions/Item/Bundle.pm                | 49 +++++++++++++++++++
 Koha/Item.pm                                  |  6 +++
 Koha/REST/V1/Items.pm                         |  8 +++
 .../prog/en/modules/catalogue/detail.tt       |  7 +++
 4 files changed, 70 insertions(+)
 create mode 100644 Koha/Exceptions/Item/Bundle.pm

diff --git a/Koha/Exceptions/Item/Bundle.pm b/Koha/Exceptions/Item/Bundle.pm
new file mode 100644
index 0000000000..0ad0a2af00
--- /dev/null
+++ b/Koha/Exceptions/Item/Bundle.pm
@@ -0,0 +1,49 @@
+package Koha::Exceptions::Item::Bundle;
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+
+use Koha::Exception;
+
+use Exception::Class (
+
+    'Koha::Exceptions::Item::Bundle' => {
+        isa => 'Koha::Exception',
+    },
+    'Koha::Exceptions::Item::Bundle::IsBundle' => {
+        isa         => 'Koha::Exceptions::Item::Bundle',
+        description => "A bundle cannot be added to a bundle",
+    }
+);
+
+=head1 NAME
+
+Koha::Exceptions::Item::Bundle - Base class for Bundle exceptions
+
+=head1 Exceptions
+
+=head2 Koha::Exceptions::Item::Bundle
+
+Generic Item::Bundle exception
+
+=head2 Koha::Exceptions::Item::Bundle::IsBundle
+
+Exception to be used when attempting to add one bundle into another.
+
+=cut
+
+1;
diff --git a/Koha/Item.pm b/Koha/Item.pm
index 3ef1e04194..e508e7eb55 100644
--- a/Koha/Item.pm
+++ b/Koha/Item.pm
@@ -38,6 +38,7 @@ use Koha::CirculationRules;
 use Koha::CoverImages;
 use Koha::Exceptions::Item::Transfer;
 use Koha::Item::Attributes;
+use Koha::Exceptions::Item::Bundle;
 use Koha::Item::Transfer::Limits;
 use Koha::Item::Transfers;
 use Koha::ItemTypes;
@@ -1707,6 +1708,11 @@ Adds the bundle_item passed to this item
 sub add_to_bundle {
     my ( $self, $bundle_item ) = @_;
 
+    Koha::Exceptions::Item::Bundle::IsBundle->throw()
+      if ( $self->itemnumber eq $bundle_item->itemnumber
+        || $bundle_item->is_bundle
+        || $self->in_bundle );
+
     my $schema = Koha::Database->new->schema;
 
     my $BundleNotLoanValue = C4::Context->preference('BundleNotLoanValue');
diff --git a/Koha/REST/V1/Items.pm b/Koha/REST/V1/Items.pm
index 88a0675257..515fcd91bf 100644
--- a/Koha/REST/V1/Items.pm
+++ b/Koha/REST/V1/Items.pm
@@ -229,6 +229,14 @@ sub add_to_bundle {
                 }
             );
         }
+        elsif ( ref($_) eq 'Koha::Exceptions::Item::Bundle::IsBundle' ) {
+            return $c->render(
+                status => 400,
+                openapi => {
+                    error => 'Bundles cannot be nested'
+                }
+            );
+        }
         else {
             $c->unhandled_exception($_);
         }
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt
index 5db107ef27..e59ed096b9 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt
@@ -1794,6 +1794,13 @@ Note that permanent location is a code, and location may be an authval.
                           }
                       } else if ( data.status === 404 ) {
                           $('#addResult').replaceWith('<div id="addResult" class="alert alert-danger">'+_("Failure: Item '%s' not found").format(barcode)+'</div>');
+                      } else if ( data.status === 400 ) {
+                          var response = data.responseJSON;
+                          if ( response.error === "Bundles cannot be nested" ) {
+                              $('#addResult').replaceWith('<div id="addResult" class="alert alert-danger">'+_("Failure: Item '%s' is a bundle and bundles cannot be nested").format(barcode)+'</div>');
+                          } else {
+                              $('#addResult').replaceWith('<div id="addResult" class="alert alert-danger">'+_("Failure: Check the logs for details")+'</div>');
+                          }
                       } else {
                           $('#addResult').replaceWith('<div id="addResult" class="alert alert-danger">'+_("Failure: Check the logs for details")+'</div>');
                       }
-- 
2.30.2