diff --git a/C4/Reserves.pm b/C4/Reserves.pm index 3fbe9274f4..fc22959d70 100644 --- a/C4/Reserves.pm +++ b/C4/Reserves.pm @@ -143,18 +143,20 @@ This modules provides some functions to deal with reservations. AddReserve( { - branchcode => $branchcode, - borrowernumber => $borrowernumber, - biblionumber => $biblionumber, - priority => $priority, - reservation_date => $reservation_date, - expiration_date => $expiration_date, - notes => $notes, - title => $title, - itemnumber => $itemnumber, - found => $found, - itemtype => $itemtype, - item_group_id => $item_group_id + branchcode => $branchcode, + borrowernumber => $borrowernumber, + biblionumber => $biblionumber, + priority => $priority, + reservation_date => $reservation_date, + expiration_date => $expiration_date, + notes => $notes, + title => $title, + itemnumber => $itemnumber, + found => $found, + itemtype => $itemtype, + item_group_id => $item_group_id, + supplyill => $iso18626_request_id; + iso18626_payload => $payload_hash; } ); @@ -193,6 +195,8 @@ sub AddReserve { my $item_group_id = $params->{item_group_id}; my $confirmations = $params->{confirmations}; my $forced = $params->{forced}; + my $supplyill = $params->{supplyill}; + my $iso18626_payload = $params->{iso18626_payload}; $resdate ||= dt_from_string; @@ -257,6 +261,9 @@ sub AddReserve { non_priority => $non_priority ? 1 : 0, } )->store(); + + $hold->iso18626_attach_hold($supplyill); + $hold->set_waiting() if $found && $found eq 'W'; # record patron activity @@ -362,6 +369,8 @@ sub AddReserve { } } + $hold->progress_iso18626_request( 'hold_created', $iso18626_payload ); + Koha::Plugins->call( 'after_hold_create', $hold ); Koha::Plugins->call( 'after_hold_action', diff --git a/Koha/Controller/Catalogue.pm b/Koha/Controller/Catalogue.pm index a4793b4a55..33db7a8423 100644 --- a/Koha/Controller/Catalogue.pm +++ b/Koha/Controller/Catalogue.pm @@ -3,6 +3,7 @@ package Koha::Controller::Catalogue; use Modern::Perl; use C4::Auth qw( get_template_and_user ); +use Koha::ILL::ISO18626::Requests; use Koha::Patrons; =head1 NAME @@ -76,6 +77,15 @@ sub _prep_searchto_template_params { ); } + if ( $query->cookie("holdforsupplyill") ) { + my $holdfor_supply_ill = Koha::ILL::ISO18626::Requests->find( $query->cookie("holdforsupplyill") ); + if ($holdfor_supply_ill) { + $template->param( + holdforsupplyill => $query->cookie("holdforsupplyill"), + ); + } + } + return 1; } diff --git a/Koha/Hold.pm b/Koha/Hold.pm index ae4e156505..a88163ae35 100644 --- a/Koha/Hold.pm +++ b/Koha/Hold.pm @@ -36,6 +36,7 @@ use Koha::Items; use Koha::Libraries; use Koha::Calendar; use Koha::Plugins; +use Koha::ILL::ISO18626::Request; use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue; @@ -431,6 +432,8 @@ sub set_waiting { $self->set($values)->store(); + $self->progress_iso18626_request('hold_waiting'); + Koha::Plugins->call( 'after_hold_action', { @@ -692,6 +695,34 @@ sub patron { return Koha::Patron->_new_from_dbic($rs); } +=head3 iso18626_request + +Returns the related Koha::ILL::ISO18626::Request object for this hold + +=cut + +sub iso18626_request { + my ($self) = @_; + my $rs = $self->_result->iso18626_request; + return unless $rs; + return Koha::ILL::ISO18626::Request->_new_from_dbic($rs); +} + +=head3 iso18626_attach_hold + +Adds relationship to related this hold + +=cut + +sub iso18626_attach_hold { + my ( $self, $iso18626_request_id ) = @_; + + my $iso18626_request = Koha::ILL::ISO18626::Requests->find($iso18626_request_id); + return unless $iso18626_request; + + $iso18626_request->hold_id( $self->id )->store; +} + =head3 item Returns the related Koha::Item object for this Hold @@ -1001,6 +1032,8 @@ sub cancel { } ); + $self->progress_iso18626_request( 'hold_cancelled', undef ); #TODO: Add reasonUnfilled here + $self->cleanup_hold_group() unless $params->{skip_hold_group_cleanup}; if ($autofill_next) { @@ -1143,6 +1176,7 @@ sub revert_found { C4::Reserves::FixPriority( { biblionumber => $self->biblionumber } ); $self->remove_as_hold_group_target(); + $self->progress_iso18626_request('hold_waiting_reverted'); Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue( { biblio_ids => [ $self->biblionumber ] } ) if C4::Context->preference('RealTimeHoldsQueue'); @@ -1191,6 +1225,68 @@ sub change_type { return $self; } +=head3 progress_iso18626_request + + $hold->progress_iso18626_request( $action, $iso18626_payload ); + +Process and update the associated ISO18626 request based on a specific hold action. + +=over 4 + +=item C<$action> + +Required string. The hold-related event trigger (e.g., 'hold_created' or 'hold_cancelled'). + +=item C<$iso18626_payload> + +Required hashref. Contains data for the ISO18626 message, such as C, C, or C. + +=back + +=cut + +sub progress_iso18626_request { + my ( $self, $action, $iso18626_payload ) = @_; + + my $iso18626_request = $self->iso18626_request; + + return unless $iso18626_request; + + if ( $action eq 'hold_created' && $iso18626_request->status eq 'RequestReceived' ) { + $iso18626_request->progress_request( + 'supplyingAgency', + { + 'expectedDeliveryDate' => $iso18626_payload->{iso18626_statusInfo_expectedDeliveryDate}, + 'messageInfoNote' => $iso18626_payload->{iso18626_messageInfo_note}, + 'status' => 'ExpectToSupply' + } + ); + } + if ( $action eq 'hold_waiting' ) { + $iso18626_request->progress_request( + 'supplyingAgency', + { 'status' => 'WillSupply' } + + #TODO: Add messageInfoNote input on the checkin modal to pass a message here. + ); + } + if ( $action eq 'hold_waiting_reverted' && $iso18626_request->status eq 'WillSupply' ) { + $iso18626_request->progress_request( + 'supplyingAgency', + { 'status' => 'ExpectToSupply' } + ); + } + if ( $action eq 'hold_cancelled' ) { + $iso18626_request->progress_request( + 'supplyingAgency', + { + 'reasonUnfilled' => 'NotHeld', #Grab this from $iso18626_payload + 'status' => 'Unfilled' + } + ); + } +} + =head3 store Override base store method to set default diff --git a/Koha/ILL/ISO18626/Request.pm b/Koha/ILL/ISO18626/Request.pm index 37639e1028..ac8cf184aa 100644 --- a/Koha/ILL/ISO18626/Request.pm +++ b/Koha/ILL/ISO18626/Request.pm @@ -165,7 +165,7 @@ Method that returns the related I sub hold { my ($self) = @_; - my $hold_rs = $self->_result->reserve; + my $hold_rs = $self->_result->hold; return unless $hold_rs; return Koha::Hold->_new_from_dbic($hold_rs); } diff --git a/Koha/Schema/Result/Iso18626Request.pm b/Koha/Schema/Result/Iso18626Request.pm index aeb1715c0f..1a49f828a9 100644 --- a/Koha/Schema/Result/Iso18626Request.pm +++ b/Koha/Schema/Result/Iso18626Request.pm @@ -100,6 +100,14 @@ ISO18626 service type ISO18626 Requesting Agency action that requires a manual response (yes or no) +=head2 hold_id + + data_type: 'integer' + is_foreign_key: 1 + is_nullable: 1 + +ID of the hold related to this ISO18626 request + =cut __PACKAGE__->add_columns( @@ -171,6 +179,8 @@ __PACKAGE__->add_columns( extra => { list => ["Cancel", "Renew"] }, is_nullable => 1, }, + "hold_id", + { data_type => "integer", is_foreign_key => 1, is_nullable => 1 }, ); =head1 PRIMARY KEY @@ -185,8 +195,42 @@ __PACKAGE__->add_columns( __PACKAGE__->set_primary_key("iso18626_request_id"); +=head1 UNIQUE CONSTRAINTS + +=head2 C + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->add_unique_constraint("uniq_reserve_id", ["hold_id"]); + =head1 RELATIONS +=head2 hold + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "hold", + "Koha::Schema::Result::Reserve", + { reserve_id => "hold_id" }, + { + is_deferrable => 1, + join_type => "LEFT", + on_delete => "SET NULL", + on_update => "CASCADE", + }, +); + =head2 iso18626_messages Type: has_many @@ -219,39 +263,9 @@ __PACKAGE__->belongs_to( { is_deferrable => 1, on_delete => "RESTRICT", on_update => "CASCADE" }, ); -=head2 old_reserve -Type: might_have - -Related object: L - -=cut - -__PACKAGE__->might_have( - "old_reserve", - "Koha::Schema::Result::OldReserve", - { "foreign.iso18626_request_id" => "self.iso18626_request_id" }, - { cascade_copy => 0, cascade_delete => 0 }, -); - -=head2 reserve - -Type: might_have - -Related object: L - -=cut - -__PACKAGE__->might_have( - "reserve", - "Koha::Schema::Result::Reserve", - { "foreign.iso18626_request_id" => "self.iso18626_request_id" }, - { cascade_copy => 0, cascade_delete => 0 }, -); - - -# Created by DBIx::Class::Schema::Loader v0.07051 @ 2026-03-04 14:57:44 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:T0PCb3b3SH9O7/f3TYDnTw +# Created by DBIx::Class::Schema::Loader v0.07051 @ 2026-02-24 13:34:37 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7KqpCOYXvp6skCK/lTer0g =head2 koha_object_class diff --git a/Koha/Schema/Result/Reserve.pm b/Koha/Schema/Result/Reserve.pm index 749a672c8d..71cbd9661d 100644 --- a/Koha/Schema/Result/Reserve.pm +++ b/Koha/Schema/Result/Reserve.pm @@ -455,6 +455,21 @@ __PACKAGE__->might_have( { cascade_copy => 0, cascade_delete => 0 }, ); +=head2 iso18626_request + +Type: might_have + +Related object: L + +=cut + +__PACKAGE__->might_have( + "iso18626_request", + "Koha::Schema::Result::Iso18626Request", + { "foreign.hold_id" => "self.reserve_id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + =head2 item_group Type: belongs_to @@ -516,8 +531,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07051 @ 2025-11-03 19:54:07 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:B8/T/fmEf+RMPQsqOG+MiQ +# Created by DBIx::Class::Schema::Loader v0.07051 @ 2026-02-23 17:05:32 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:WQrlG+Rl+BYHA7poiEiwmA __PACKAGE__->belongs_to( "item", diff --git a/api/v1/swagger/definitions/hold.yaml b/api/v1/swagger/definitions/hold.yaml index a466e4eeec..2965be5e36 100644 --- a/api/v1/swagger/definitions/hold.yaml +++ b/api/v1/swagger/definitions/hold.yaml @@ -124,6 +124,11 @@ properties: - boolean - "null" description: Cancellation requests count for the hold (x-koha-embed) + iso18626_request: + type: + - object + - "null" + description: (x-koha-embed) biblio: type: - object diff --git a/api/v1/swagger/definitions/iso18626_request.yaml b/api/v1/swagger/definitions/iso18626_request.yaml index a958b637f7..f80447d657 100644 --- a/api/v1/swagger/definitions/iso18626_request.yaml +++ b/api/v1/swagger/definitions/iso18626_request.yaml @@ -47,6 +47,16 @@ properties: - object - "null" description: Embeded object + hold_id: + type: + - integer + - "null" + description: Associated hold (reserves) internal identifier + hold: + type: + - object + - "null" + description: Embeded object messages: type: - array diff --git a/api/v1/swagger/paths/holds.yaml b/api/v1/swagger/paths/holds.yaml index 782f392e15..eace433918 100644 --- a/api/v1/swagger/paths/holds.yaml +++ b/api/v1/swagger/paths/holds.yaml @@ -110,6 +110,7 @@ - patron - item_group - item_level_holds_count + - iso18626_request collectionFormat: csv produces: - application/json diff --git a/api/v1/swagger/paths/ill_iso18626_requests.yaml b/api/v1/swagger/paths/ill_iso18626_requests.yaml index 12a16a8358..9f11c7c22a 100644 --- a/api/v1/swagger/paths/ill_iso18626_requests.yaml +++ b/api/v1/swagger/paths/ill_iso18626_requests.yaml @@ -24,6 +24,7 @@ enum: - messages - requesting_agency + - hold collectionFormat: csv produces: - application/json @@ -83,6 +84,7 @@ enum: - messages - requesting_agency + - hold collectionFormat: csv produces: - application/json diff --git a/catalogue/search.pl b/catalogue/search.pl index 34d1c28d2d..a162737303 100755 --- a/catalogue/search.pl +++ b/catalogue/search.pl @@ -217,6 +217,14 @@ if ( $cgi->cookie("holdforclub") ) { } } +if ( my $searchcontext = $cgi->param('context') ) { + my ( $context, $data ) = split( /:/, $searchcontext, 2 ); + $template->param( + context => $context, + context_data => $data, + ); +} + # get biblionumbers stored in the cart my @cart_list; diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index cfc66aa774..59dce8ad91 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -3820,8 +3820,11 @@ CREATE TABLE `iso18626_requests` ( `status` enum('RequestReceived','ExpectToSupply','WillSupply','Loaned', 'Overdue', 'Recalled', 'RetryPossible', 'Unfilled', 'HoldReturn', 'ReleaseHoldReturn', 'CopyCompleted', 'LoanCompleted', 'CompletedWithoutReturn', 'Cancelled') DEFAULT 'RequestReceived' COMMENT 'Current ISO18626 status of request', `service_type` enum('Copy','Loan','CopyOrLoan') NOT NULL COMMENT 'ISO18626 service type', `pending_requesting_agency_action` enum('Cancel','Renew') DEFAULT NULL COMMENT 'ISO18626 Requesting Agency action that requires a manual response (yes or no)', - PRIMARY KEY (`iso18626_request_id`), + `hold_id` int(11) DEFAULT NULL COMMENT 'ID of the hold related to this ISO18626 request', +>>>>>>> 16e751e7126 (Bug 37762: Circulation: Hook to holds) + UNIQUE KEY `uniq_reserve_id` (`hold_id`), KEY `iso18626_rafk` (`iso18626_requesting_agency_id`), + CONSTRAINT `uniq_reserve_id` FOREIGN KEY (`hold_id`) REFERENCES `reserves` (`reserve_id`) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT `iso18626_rafk` FOREIGN KEY (`iso18626_requesting_agency_id`) REFERENCES `iso18626_requesting_agencies` (`iso18626_requesting_agency_id`) ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc index 68b4c24e45..f133ac33d7 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc @@ -259,7 +259,7 @@ [% IF ( CAN_user_reserveforothers ) %] [% IF biblio.items.filter_by_for_hold.count %] - [% IF ( holdfor ) %] + [% IF ( holdfor || holdforsupplyill ) %] [% ELSE %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc index 9e69dbf96a..5a555c8f6f 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc @@ -728,3 +728,18 @@ [% END %] + +[%- BLOCK search_context_dialog -%] + [% IF context == 'supplyill' %] +
+

You are searching to place a hold for supply ILL [% context_data | html %]

+
+ [% END %] +[%- END -%] + +[%- BLOCK search_context_form_input -%] + [% IF context %] + [% SET search_context_string = context_data ? (context _ ":" _ context_data) : context %] + + [% END %] +[%- END -%] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/advsearch.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/advsearch.tt index b8c9df3eb7..b732e9742a 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/advsearch.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/advsearch.tt @@ -43,6 +43,8 @@ [% WRAPPER 'main-container.inc' %]