From 941bd674c0db8d612139693fd8b7fbd06ead465b Mon Sep 17 00:00:00 2001 From: Pedro Amorim Date: Fri, 25 Jul 2025 13:11:05 +0000 Subject: [PATCH] Bug 40529: Add hold group targets logic Hold group target hold: The hold that is set as the hold group's target. If a hold group has a hold target, subsequent check-ins that would 'trap' items to the remaining holds in the group are instead skipped. --- Koha/Hold.pm | 18 +++ Koha/HoldGroup.pm | 16 +++ Koha/Schema/Result/HoldGroup.pm | 19 ++- Koha/Schema/Result/HoldGroupTargetHold.pm | 126 ++++++++++++++++++ Koha/Schema/Result/Reserve.pm | 19 ++- api/v1/swagger/definitions/hold_group.yaml | 3 + .../bug_40529_-_hold_group_table_target.pl | 25 ++++ installer/data/mysql/kohastructure.sql | 14 ++ koha-tmpl/intranet-tmpl/prog/js/holds.js | 8 ++ svc/holds | 1 + 10 files changed, 245 insertions(+), 4 deletions(-) create mode 100644 Koha/Schema/Result/HoldGroupTargetHold.pm create mode 100755 installer/data/mysql/atomicupdate/bug_40529_-_hold_group_table_target.pl diff --git a/Koha/Hold.pm b/Koha/Hold.pm index e4ce7dae264..876036d0010 100644 --- a/Koha/Hold.pm +++ b/Koha/Hold.pm @@ -200,6 +200,24 @@ sub cleanup_hold_group { } } +=head3 is_hold_group_target + +$self->is_hold_group_target; + +Returns whether this hold is its hold group target + +=cut + +sub is_hold_group_target { + my ($self) = @_; + + return 1 + if $self->hold_group + && $self->hold_group->target_hold_id + && $self->hold_group->target_hold_id eq $self->reserve_id; + return 0; +} + =head3 set_transfer =cut diff --git a/Koha/HoldGroup.pm b/Koha/HoldGroup.pm index 7a7ae302408..028ae70516d 100644 --- a/Koha/HoldGroup.pm +++ b/Koha/HoldGroup.pm @@ -78,6 +78,22 @@ sub holds { return Koha::Holds->_new_from_dbic($holds_rs); } +=head3 target_hold_id + + $holds = $hold_group->target_hold_id + +Return the target_hold_id + +=cut + +sub target_hold_id { + my ($self) = @_; + + return unless $self->_result->hold_group_target_hold; + + return $self->_result->hold_group_target_hold->reserve_id; +} + =head3 _type =cut diff --git a/Koha/Schema/Result/HoldGroup.pm b/Koha/Schema/Result/HoldGroup.pm index 173e7443e2a..42bf74e4477 100644 --- a/Koha/Schema/Result/HoldGroup.pm +++ b/Koha/Schema/Result/HoldGroup.pm @@ -95,6 +95,21 @@ __PACKAGE__->belongs_to( }, ); +=head2 hold_group_target_hold + +Type: might_have + +Related object: L + +=cut + +__PACKAGE__->might_have( + "hold_group_target_hold", + "Koha::Schema::Result::HoldGroupTargetHold", + { "foreign.hold_group_id" => "self.hold_group_id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + =head2 old_reserves Type: has_many @@ -126,8 +141,8 @@ __PACKAGE__->has_many( ); -# Created by DBIx::Class::Schema::Loader v0.07051 @ 2025-07-25 13:02:35 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:hlAYDHvqpFZRpNU+d+y4RA +# Created by DBIx::Class::Schema::Loader v0.07051 @ 2025-07-25 13:10:48 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3CiPWMgltRazKxPlIb5x3g # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/Koha/Schema/Result/HoldGroupTargetHold.pm b/Koha/Schema/Result/HoldGroupTargetHold.pm new file mode 100644 index 00000000000..b23ea3146bc --- /dev/null +++ b/Koha/Schema/Result/HoldGroupTargetHold.pm @@ -0,0 +1,126 @@ +use utf8; +package Koha::Schema::Result::HoldGroupTargetHold; + +# Created by DBIx::Class::Schema::Loader +# DO NOT MODIFY THE FIRST PART OF THIS FILE + +=head1 NAME + +Koha::Schema::Result::HoldGroupTargetHold + +=cut + +use strict; +use warnings; + +use base 'DBIx::Class::Core'; + +=head1 TABLE: C + +=cut + +__PACKAGE__->table("hold_group_target_holds"); + +=head1 ACCESSORS + +=head2 hold_group_id + + data_type: 'integer' + extra: {unsigned => 1} + is_foreign_key: 1 + is_nullable: 0 + +foreign key, linking this to the hold_groups table + +=head2 reserve_id + + data_type: 'integer' + is_foreign_key: 1 + is_nullable: 1 + +foreign key, linking this to the reserves table + +=cut + +__PACKAGE__->add_columns( + "hold_group_id", + { + data_type => "integer", + extra => { unsigned => 1 }, + is_foreign_key => 1, + is_nullable => 0, + }, + "reserve_id", + { data_type => "integer", is_foreign_key => 1, is_nullable => 1 }, +); + +=head1 UNIQUE CONSTRAINTS + +=head2 C + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->add_unique_constraint("hold_group_id", ["hold_group_id"]); + +=head2 C + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->add_unique_constraint("reserve_id", ["reserve_id"]); + +=head1 RELATIONS + +=head2 hold_group + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "hold_group", + "Koha::Schema::Result::HoldGroup", + { hold_group_id => "hold_group_id" }, + { is_deferrable => 1, on_delete => "CASCADE", on_update => "RESTRICT" }, +); + +=head2 reserve + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "reserve", + "Koha::Schema::Result::Reserve", + { reserve_id => "reserve_id" }, + { + is_deferrable => 1, + join_type => "LEFT", + on_delete => "CASCADE", + on_update => "RESTRICT", + }, +); + + +# Created by DBIx::Class::Schema::Loader v0.07051 @ 2025-07-25 13:10:48 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:xgRnzTXH+Q+YxYLKQeGe+g + + +# You can replace this text with custom code or comments, and it will be preserved on regeneration +1; diff --git a/Koha/Schema/Result/Reserve.pm b/Koha/Schema/Result/Reserve.pm index 664df77b482..9de57b5451e 100644 --- a/Koha/Schema/Result/Reserve.pm +++ b/Koha/Schema/Result/Reserve.pm @@ -440,6 +440,21 @@ __PACKAGE__->belongs_to( }, ); +=head2 hold_group_target_hold + +Type: might_have + +Related object: L + +=cut + +__PACKAGE__->might_have( + "hold_group_target_hold", + "Koha::Schema::Result::HoldGroupTargetHold", + { "foreign.reserve_id" => "self.reserve_id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + =head2 item_group Type: belongs_to @@ -501,8 +516,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07051 @ 2025-07-25 13:02:35 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:x5AS7F7aOMb9ihb8gaxZvQ +# Created by DBIx::Class::Schema::Loader v0.07051 @ 2025-07-25 13:10:48 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:hipLVZ6zkbJZha+kYRMslQ __PACKAGE__->belongs_to( "item", diff --git a/api/v1/swagger/definitions/hold_group.yaml b/api/v1/swagger/definitions/hold_group.yaml index 271c262b58b..4f122579503 100644 --- a/api/v1/swagger/definitions/hold_group.yaml +++ b/api/v1/swagger/definitions/hold_group.yaml @@ -10,6 +10,9 @@ properties: visual_hold_group_id: type: integer description: Visual ID for this hold group, in the context of the related patron + target_hold_id: + type: integer + description: Target hold ID of this hold group holds: type: - array diff --git a/installer/data/mysql/atomicupdate/bug_40529_-_hold_group_table_target.pl b/installer/data/mysql/atomicupdate/bug_40529_-_hold_group_table_target.pl new file mode 100755 index 00000000000..4d064351362 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_40529_-_hold_group_table_target.pl @@ -0,0 +1,25 @@ +use Modern::Perl; +use Koha::Installer::Output qw(say_warning say_success say_info); + +return { + bug_number => "40529", + description => "Add 'hold_groups_target_hold_id' column to hold_groups", + up => sub { + my ($args) = @_; + my ( $dbh, $out ) = @$args{qw(dbh out)}; + + unless ( TableExists('hold_group_target_holds') ) { + $dbh->do( + q{ + CREATE TABLE hold_group_target_holds ( + `hold_group_id` int unsigned NOT NULL UNIQUE COMMENT 'foreign key, linking this to the hold_groups table', + `reserve_id` int(11) DEFAULT NULL UNIQUE COMMENT 'foreign key, linking this to the reserves table', + CONSTRAINT `hold_group_target_holds_ibfk_1` FOREIGN KEY (`hold_group_id`) REFERENCES `hold_groups` (`hold_group_id`) ON DELETE CASCADE, + CONSTRAINT `hold_group_target_holds_ibfk_2` FOREIGN KEY (`reserve_id`) REFERENCES `reserves` (`reserve_id`) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + } + ); + say_success( $out, "Added table 'hold_group_target_holds'" ); + } + }, +}; diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index 4ce20a82efe..49d8a28a635 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -5074,6 +5074,20 @@ CREATE TABLE hold_groups ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `hold_group_target_holds` +-- +DROP TABLE IF EXISTS `hold_group_target_holds`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE hold_group_target_holds ( + `hold_group_id` int unsigned NOT NULL UNIQUE COMMENT 'foreign key, linking this to the hold_groups table', + `reserve_id` int(11) DEFAULT NULL UNIQUE COMMENT 'foreign key, linking this to the reserves table', + CONSTRAINT `hold_group_target_holds_ibfk_1` FOREIGN KEY (`hold_group_id`) REFERENCES `hold_groups` (`hold_group_id`) ON DELETE CASCADE, + CONSTRAINT `hold_group_target_holds_ibfk_2` FOREIGN KEY (`reserve_id`) REFERENCES `reserves` (`reserve_id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `old_reserves` -- diff --git a/koha-tmpl/intranet-tmpl/prog/js/holds.js b/koha-tmpl/intranet-tmpl/prog/js/holds.js index 24883d040e7..bed19e2c90f 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/holds.js +++ b/koha-tmpl/intranet-tmpl/prog/js/holds.js @@ -259,6 +259,14 @@ $(document).ready(function () { ""; } + if (oObj.is_hold_group_target) { + var link = __("target of hold group"); + title += + '
(' + + link + + ")"; + } + return title; }, }, diff --git a/svc/holds b/svc/holds index a755a7f422a..80c7f4d25ab 100755 --- a/svc/holds +++ b/svc/holds @@ -115,6 +115,7 @@ while ( my $h = $holds_rs->next() ) { itemtype_limit => $itemtype_limit, hold_group_id => $h->hold_group_id, visual_hold_group_id => $h->hold_group ? $h->hold_group->visual_hold_group_id : q{}, + is_hold_group_target => $h->is_hold_group_target, reservedate_formatted => $h->reservedate() ? output_pref( { dt => dt_from_string( $h->reservedate() ), dateonly => 1 } ) : q{}, -- 2.39.5