Bug 37762: Circulation: Hook to holds

Signed-off-by: Jason Robb <jrobb@sekls.org>
Signed-off-by: Lisette Scheer <lisette@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Pedro Amorim 2026-01-30 11:17:03 -01:00 committed by Lucas Gass
parent 1d6fb7f57d
commit 950c9ccb96
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
25 changed files with 474 additions and 105 deletions

View File

@ -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',

View File

@ -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;
}

View File

@ -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<expectedDeliveryDate>, C<messageInfoNote>, or C<reasonUnfilled>.
=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

View File

@ -165,7 +165,7 @@ Method that returns the related I<Koha::Hold>
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);
}

View File

@ -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<uniq_reserve_id>
=over 4
=item * L</hold_id>
=back
=cut
__PACKAGE__->add_unique_constraint("uniq_reserve_id", ["hold_id"]);
=head1 RELATIONS
=head2 hold
Type: belongs_to
Related object: L<Koha::Schema::Result::Reserve>
=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<Koha::Schema::Result::OldReserve>
=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<Koha::Schema::Result::Reserve>
=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

View File

@ -455,6 +455,21 @@ __PACKAGE__->might_have(
{ cascade_copy => 0, cascade_delete => 0 },
);
=head2 iso18626_request
Type: might_have
Related object: L<Koha::Schema::Result::Iso18626Request>
=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",

View File

@ -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

View File

@ -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

View File

@ -110,6 +110,7 @@
- patron
- item_group
- item_level_holds_count
- iso18626_request
collectionFormat: csv
produces:
- application/json

View File

@ -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

View File

@ -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;

View File

@ -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 */;

View File

@ -259,7 +259,7 @@
[% IF ( CAN_user_reserveforothers ) %]
[% IF biblio.items.filter_by_for_hold.count %]
[% IF ( holdfor ) %]
[% IF ( holdfor || holdforsupplyill ) %]
<div class="btn-group">
<button class="btn btn-default dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-bookmark"></i>
@ -267,11 +267,19 @@
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblionumber | uri %]">Place hold</a></li>
<li
><a class="dropdown-item" href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblionumber | uri %]&amp;findborrower=[% holdfor_patron.cardnumber | uri %]"
>Place hold for [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 no_html => 1 %]</a
></li
>
[% IF holdforsupplyill %]
<li
><a class="dropdown-item" href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblionumber | uri %]&amp;findborrower=[% holdfor_patron.cardnumber | uri %]&amp;supplyill=[% holdforsupplyill | uri %]"
>Place supply ILL ([% holdforsupplyill | html %]) hold for [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 no_cardnumber => 1 no_html => 1 %]</a
></li
>
[% ELSIF holdfor %]
<li
><a class="dropdown-item" href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblionumber | uri %]&amp;findborrower=[% holdfor_patron.cardnumber | uri %]"
>Place hold for [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 no_html => 1 %]</a
></li
>
[% END %]
</ul>
</div>
[% ELSE %]

View File

@ -728,3 +728,18 @@
</div>
</div>
[% END %]
[%- BLOCK search_context_dialog -%]
[% IF context == 'supplyill' %]
<div class="alert alert-info">
<h4>You are searching to place a hold for <a target="_blank" href="/cgi-bin/koha/ill/iso18626_requests/[% context_data | url %]">supply ILL [% context_data | html %]</a></h4>
</div>
[% END %]
[%- END -%]
[%- BLOCK search_context_form_input -%]
[% IF context %]
[% SET search_context_string = context_data ? (context _ ":" _ context_data) : context %]
<input type="hidden" name="context" value="[% search_context_string | url %]" />
[% END %]
[%- END -%]

View File

@ -43,6 +43,8 @@
[% WRAPPER 'main-container.inc' %]
<form action="search.pl" method="get">
<div id="advanced-search">
[% PROCESS search_context_form_input %]
[% PROCESS search_context_dialog %]
[% IF edit_filter %]
<div id="toolbar" class="btn-toolbar">
<legend>Editing filter: [% edit_filter.name | html %]</legend>

View File

@ -68,6 +68,7 @@
[% END #/ WRAPPER sub-header.inc %]
[% WRAPPER 'main-container.inc' aside='facets' %]
[% PROCESS search_context_dialog %]
<h1>Search results</h1>
[% IF ( total ) %]
@ -158,12 +159,19 @@
[% END # /IF virtualshelves %]
[% IF ( CAN_user_reserveforothers_place_holds && DisplayMultiPlaceHold ) %]
[% IF ( holdfor or holdforclub ) %]
[% IF ( holdfor or holdforclub or holdforsupplyill ) %]
<div id="placeholdc" class="btn-group">
<button class="btn btn-default btn-xs placehold"><i class="fa-solid fa-bookmark"></i> Place hold</button>
<button class="btn btn-default btn-xs dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"> </button>
<ul class="dropdown-menu">
<li><a href="#" class="placehold dropdown-item">Place hold</a></li>
[% IF holdforsupplyill %]
<li
><a href="#" class="placeholdforsupplyill dropdown-item"
>Place supply ILL ([% holdforsupplyill | html %]) hold for [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 no_cardnumber => 1 no_html => 1 %]</a
></li
>
[% END %]
[% IF holdfor %]
<li><a href="#" class="placeholdfor dropdown-item">Place hold for [% INCLUDE 'patron-title.inc' patron => holdfor_patron no_title => 1 no_cardnumber => 1 no_html => 1 %]</a></li>
<li><hr class="dropdown-divider" /></li>
@ -955,6 +963,7 @@
<!-- Value will be set here by placeHold() -->
<input type="hidden" name="findborrower" id="holdFor" value="" />
<input type="hidden" name="club" id="holdForClub" value="" />
<input type="hidden" name="supplyill" id="holdForSupplyILL" value="" />
</form>
<form id="list_form" method="get" action="/cgi-bin/koha/reserve/request.pl">
@ -984,6 +993,7 @@
var biblionumber = "[% biblionumber | html %]";
var holdfor_cardnumber = "[% holdfor_patron.cardnumber | html %]";
var holdforclub = "[% holdforclub | html %]";
var holdforsupplyill = "[% holdforsupplyill | html %]";
var userbranch = "[% userbranch | html %]";
[% IF Koha.Preference('FilterSearchResultsByLoggedInBranch') %]
$(document).ready( function() {

View File

@ -643,6 +643,9 @@
[% FOREACH biblionumber IN biblionumbers %]
<input type="hidden" name="biblionumber" value="[% biblionumber | html %]" />
[% END %]
[% IF supplyill %]
<input type="hidden" name="supplyill" value="[% supplyill | html %]" />
[% END %]
<input type="hidden" name="holdable_bibs" id="holdable_bibs" value="[% biblio.biblionumber | html %]" />
<input type="hidden" name="title" value="[% biblio.title | html %]" />
<input type="hidden" name="rank-request" value="[% fixedRank | html %]" />
@ -698,6 +701,21 @@
</li>
</ol>
</fieldset>
[% IF supplyill %]
<fieldset class="rows">
<legend>ISO18626 ILL request details</legend>
<p>This hold will be linked to <a target="_blank" href="/cgi-bin/koha/ill/iso18626_requests/[% supplyill | url %]"> supply ILL [% supplyill | html %]</a>. </p
><p>This will update the ILL's status to "ExpectToSupply" and send an ISO18626 message to the requesting agency including the details below.</p>
<li>
<label for="iso18626_messageInfo_note">Message info note:</label>
<textarea id="iso18626_messageInfo_note" name="iso18626_messageInfo_note" cols="30" rows="1"></textarea>
</li>
<li>
<label for="iso18626_statusInfo_expectedDeliveryDate">Status info expected delivery date:</label>
<input id="iso18626_statusInfo_expectedDeliveryDate" name="iso18626_statusInfo_expectedDeliveryDate" size="10" type="text" class="flatpickr" data-flatpickr-enable-time="true" />
</li>
</fieldset>
[% END %]
<fieldset class="rows any_specific">
<legend>
[% IF force_hold_level == 'item' || force_hold_level == 'item_group' %]
@ -1540,7 +1558,7 @@
[% INCLUDE 'calendar.inc' %]
[% INCLUDE 'select2.inc' %]
[% SET url_biblio_params = "biblionumber=" _ biblionumbers.join("&amp;biblionumber=") %]
[% SET url_biblio_params = (supplyill ? "supplyill=" _ supplyill _ "&amp;" : "") _ "biblionumber=" _ biblionumbers.join("&amp;biblionumber=") %]
[% IF multi_hold %]
[% SET url_biblio_params = url_biblio_params _ "&amp;multi_hold=1" %]
[% END %]
@ -1726,6 +1744,7 @@
return false;
});
//Write handling of supply ILL params here ? before submit?
if (!multi_hold) {
$("#hold-request-form").on("submit", function (e) {
return check(e, $(this));
@ -1841,6 +1860,22 @@
msg = _("- Please select a pickup location for this hold") + "\n";
}
}
// var supplyillID = Cookies.get("holdforsupplyill") || null;
// if (supplyillID) {
// const client = APIClient.ill;
// client.supplying.patch({
// expectedDeliveryDate: null,
// messageInfoNote: "my message",
// status : "ExpectToSupply"
// }, supplyillID).then(
// success => {
// //alert(_("Successfully saved configuration"));
// },
// error => {
// console.warn("Something wrong happened: %s".format(error));
// }
// );
// }
if (msg == "") {
$("#hold-request-form").preventDoubleFormSubmit();

View File

@ -191,6 +191,7 @@ export const APIClient = {
circulation: createClientProxy(() => import("./circulation-api-client.js")),
club: createClientProxy(() => import("./club-api-client.js")),
cover_image: createClientProxy(() => import("./cover-image-api-client.js")),
ill: createClientProxy(() => import("./ill-api-client.js")),
localization: createClientProxy(
() => import("./localization-api-client.js")
),

View File

@ -261,6 +261,13 @@ $(document).ready(function () {
")</span>";
}
if (oObj.iso18626_request) {
title += create_iso18626_request_link(
oObj.iso18626_request.id,
oObj.iso18626_request.status
);
}
return title;
},
},
@ -1314,7 +1321,13 @@ async function load_patron_holds_table(biblio_id, split_data) {
ajax: {
url: url,
},
embed: ["patron", "item", "item_group", "item_level_holds_count"],
embed: [
"patron",
"item",
"item_group",
"item_level_holds_count",
"iso18626_request",
],
columnDefs: [
{
targets: [2, 3],
@ -1632,9 +1645,16 @@ async function load_patron_holds_table(biblio_id, split_data) {
? `<div>(<a href="/cgi-bin/koha/reserve/hold-group.pl?hold_group_id=${row.hold_group_id}" class="hold-group">${__("Hold group")} ${row.hold_group_id}</a>)</div>`
: "";
const iso_message = create_iso18626_request_link(
row.iso18626_request.iso18626_request_id,
row.iso18626_request.status
);
const extras = group_hold_message + iso_message;
// Handle status cases
if (row.status) {
return `<a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=${row.biblio_id}&itemnumber=${row.item_id}">${row.item.external_id}</a>${group_hold_message}`;
return `<a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=${row.biblio_id}&itemnumber=${row.item_id}">${row.item.external_id}</a>${extras}`;
}
// Handle item level holds
@ -1644,13 +1664,13 @@ async function load_patron_holds_table(biblio_id, split_data) {
const itemLink = `<a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=${row.biblio_id}&itemnumber=${row.item_id}">${barcode.escapeHtml ? barcode.escapeHtml() : barcode}</a>`;
if (row.item_level_holds_count >= 2) {
return `${__("Only item")} ${itemLink}${group_hold_message}`;
return `${__("Only item")} ${itemLink}${extras}`;
}
return `<select id="change_hold_type" class="change_hold_type ${table_class}" data-id="${row.hold_id}">
<option value="" selected>${__("Only item")} ${barcode}</option>
<option value="">${__("Next available")}</option>
</select>${group_hold_message}`;
</select>${extras}`;
}
// Handle item group
@ -1658,8 +1678,7 @@ async function load_patron_holds_table(biblio_id, split_data) {
return (
__(
"Next available item from group <strong>%s</strong>"
).format(row.item_group.description) +
group_hold_message
).format(row.item_group.description) + extras
);
}
@ -1668,7 +1687,7 @@ async function load_patron_holds_table(biblio_id, split_data) {
if (row.non_priority) {
message += `<br/><i>${__("Non priority hold")}</i>`;
}
return message + group_hold_message;
return message + extras;
},
},
{

View File

@ -1,4 +1,4 @@
/* global KOHA biblionumber new_results_browser addMultiple vShelfAdd openWindow search_result SEARCH_RESULTS PREF_IntranetCoce PREF_CoceProviders CoceHost CoceProviders addRecord delSingleRecord PREF_BrowseResultSelection resetSearchContext addBibToContext delBibToContext getContextBiblioNumbers holdfor_cardnumber holdforclub strQuery PREF_StaffHighlightedWords PREF_NotHighlightedWords __ */
/* global KOHA biblionumber new_results_browser addMultiple vShelfAdd openWindow search_result SEARCH_RESULTS PREF_IntranetCoce PREF_CoceProviders CoceHost CoceProviders addRecord delSingleRecord PREF_BrowseResultSelection resetSearchContext addBibToContext delBibToContext getContextBiblioNumbers holdfor_cardnumber holdforclub holdforsupplyill strQuery PREF_StaffHighlightedWords PREF_NotHighlightedWords __ */
function verify_cover_images() {
/* Loop over each container in the template which contains covers */
@ -203,6 +203,7 @@ $(document).ready(function () {
e.preventDefault();
$("#holdFor").val("");
$("#holdForClub").val("");
$("#holdForSupplyILL").val("");
placeHold();
});
@ -216,6 +217,11 @@ $(document).ready(function () {
holdForClub();
});
$(".placeholdforsupplyill").click(function (e) {
e.preventDefault();
holdForSupplyILL();
});
$("#forgetholdfor, #forgetholdforclub").click(function (e) {
e.preventDefault();
forgetPatronAndClub();
@ -416,6 +422,7 @@ function placeHold() {
function forgetPatronAndClub() {
Cookies.remove("holdfor", { path: "/", SameSite: "Lax" });
Cookies.remove("holdforclub", { path: "/", SameSite: "Lax" });
Cookies.remove("holdforsupplyill", { path: "/", SameSite: "Lax" }); //TODO: Make a its own button (?)
$(".holdforlink").remove();
$("#placeholdc").html(
'<a class="btn btn-default btn-xs placehold" href="#"><i class="fa-solid fa-bookmark"></i> ' +
@ -496,6 +503,12 @@ function holdForClub() {
placeHold();
}
function holdForSupplyILL() {
$("#holdFor").val(holdfor_cardnumber);
$("#holdForSupplyILL").val(holdforsupplyill);
placeHold();
}
function toggleBatchOp(b) {
var results_batch_ops = $("#results_batch_ops");
if (b) {

View File

@ -1093,6 +1093,27 @@ function holds_table_patron_page() {
return null;
}
/**
* Produces ISO 18626 request link, given an object
* @return {string} The HTML link
*/
function create_iso18626_request_link(id, status) {
var iso18626_request_text = __("Supply ILL #%s: %s").format(id, status);
var iso18626_request_link =
'<a href="/cgi-bin/koha/ill/iso18626_requests/' +
encodeURIComponent(id) +
'">' +
iso18626_request_text +
"</a>";
return (
'<br><span class="fw-bold fst-italic">(' +
iso18626_request_link +
")</span>"
);
}
/**
* Returns a roughly ideal position to scroll an element into view
* @param {string} target - The HTML id of the element to scroll into view

View File

@ -45,28 +45,29 @@ export default {
},
{
id: "ExpectToSupply",
confirm_message: $__(
"Supplying library expects to fill the request, based on e.g. information in the local OPAC. The message may include the ExpectedDeliveryDate"
),
button_label: $__("Expect to supply"),
icon: "fa-calendar-days",
dont_show: iso18626_request => true, //This is handled by creating a new hold
// confirm_message: $__(
// "Supplying library expects to fill the request, based on e.g. information in the local OPAC. The message may include the ExpectedDeliveryDate"
// ),
// button_label: $__("Expect to supply"),
// icon: "fa-calendar-days",
next_actions: [
"Loaned",
"WillSupply",
"RetryPossible",
"Unfilled",
],
action_inputs: [
{
name: "expectedDeliveryDate",
type: "date",
label: $__("Expected delivery date"),
toolTip: $__(
"Date and time the supplying library expects to deliver the item."
),
},
],
index: 0,
// action_inputs: [
// {
// name: "expectedDeliveryDate",
// type: "date",
// label: $__("Expected delivery date"),
// toolTip: $__(
// "Date and time the supplying library expects to deliver the item."
// ),
// },
// ],
// index: 0,
},
{
id: "WillSupply",
@ -638,6 +639,40 @@ export default {
});
}
if (resource.status == "RequestReceived") {
show_buttons.push({
cssClass: "btn btn-primary",
title: "Search to hold",
icon: "fa-calendar-days",
onClick: () => {
// !Copy pasted from member-menu.js
var date = new Date();
date.setTime(date.getTime() + 10 * 60 * 1000);
Cookies.set(
"holdfor",
resource.requesting_agency.patron_id,
{
path: "/",
expires: date,
sameSite: "Lax",
}
);
Cookies.set(
"holdforsupplyill",
resource.iso18626_request_id,
{
path: "/",
expires: date,
sameSite: "Lax",
}
);
location.href =
"/cgi-bin/koha/catalogue/search.pl?context=supplyill:" +
resource.iso18626_request_id;
},
});
}
return {
show: show_buttons,
};
@ -742,6 +777,13 @@ export default {
hideIn: ["List", "Form"],
group: $__("Request details"),
},
{
name: "hold_id",
label: $__("Hold on biblio"),
type: "boolean",
hideIn: ["List", "Form"],
group: $__("Request details"),
},
{
group: $__("ISO18626 Messages"),
name: "messages",

View File

@ -49,12 +49,23 @@ my $item_group_id = $input->param('item_group_id');
my $expirationdate = $input->param('expiration_date');
my $itemtype = $input->param('itemtype') || undef;
my $non_priority = $input->param('non_priority');
my $supplyill = $input->param('supplyill');
my $op = $input->param('op') || q{};
my $multi_holds = $input->param('multi_holds');
my $hold_group_param = $input->param('hold_group') || undef;
my $patron = Koha::Patrons->find($borrowernumber);
my @allowed_iso18626_fields = qw(
iso18626_statusInfo_expectedDeliveryDate
iso18626_messageInfo_note
);
my %iso18626_payload = map {
my $val = $input->param($_);
defined $val ? ( $_ => $val ) : ()
} @allowed_iso18626_fields;
my $holds_to_place_count = $input->param('holds_to_place_count') || 1;
my %bibinfos = ();
@ -106,6 +117,8 @@ if ( $op eq 'cud-placerequest' && $patron ) {
found => undef,
itemtype => $itemtype,
non_priority => $non_priority,
supplyill => $supplyill,
iso18626_payload => \%iso18626_payload,
}
);
@ -135,6 +148,9 @@ if ( $op eq 'cud-placerequest' && $patron ) {
found => undef,
itemtype => $itemtype,
non_priority => $non_priority,
# supplyill => $supplyill, FIXME: How does supplyill work with multi-hold?
# iso18626_payload => \%iso18626_payload,
}
);
push @successful_hold_ids, $reserve_id;
@ -159,6 +175,8 @@ if ( $op eq 'cud-placerequest' && $patron ) {
itemtype => $itemtype,
non_priority => $non_priority,
item_group_id => $item_group_id,
supplyill => $supplyill,
iso18626_payload => \%iso18626_payload,
}
);
push @successful_hold_ids, $reserve_id;

View File

@ -82,6 +82,7 @@ my $findclub = $input->param('findclub');
$findclub = '' unless defined $findclub && !$findborrower;
my $borrowernumber_hold = $input->param('borrowernumber') || '';
my $club_hold = $input->param('club') || '';
my $supplyill = $input->param('supplyill') || '';
my $messageborrower;
my $messageclub;
my $warnings;
@ -234,6 +235,10 @@ $template->param(
multi_hold => $multi_hold,
);
if ($supplyill) {
$template->param( supplyill => $supplyill );
}
# If we have the borrowernumber because we've performed an action, then we
# don't want to try to place another reserve.
if ( $borrowernumber_hold && !$op ) {

View File

@ -90,31 +90,38 @@ while ( my $h = $holds_rs->next() ) {
my $biblio = $h->biblio();
my @subtitles = split( / \| /, $biblio->subtitle() // '' );
my $hold = {
DT_RowId => $h->reserve_id(),
biblionumber => $biblionumber,
title => $biblio->title(),
subtitle => \@subtitles,
medium => $biblio->medium() // '',
part_number => $biblio->part_number() // '',
part_name => $biblio->part_name() // '',
author => $biblio->author(),
reserve_id => $h->reserve_id(),
branchcode => $h->branch()->branchname(),
branches => $libraries,
desk_name => $desk_name,
reservedate => $h->reservedate(),
expirationdate => $h->expirationdate(),
suspend => $h->suspend(),
suspend_until => $h->suspend_until(),
found => $h->found(),
waiting => $h->is_waiting(),
waiting_at => $h->branch()->branchname(),
waiting_here => $h->branch()->branchcode() eq $branch,
priority => $h->priority(),
reservenotes => $h->reservenotes(),
itemtype_limit => $itemtype_limit,
hold_group_id => $h->hold_group_id,
is_hold_group_target => $h->is_hold_group_target,
DT_RowId => $h->reserve_id(),
biblionumber => $biblionumber,
title => $biblio->title(),
subtitle => \@subtitles,
medium => $biblio->medium() // '',
part_number => $biblio->part_number() // '',
part_name => $biblio->part_name() // '',
author => $biblio->author(),
reserve_id => $h->reserve_id(),
branchcode => $h->branch()->branchname(),
branches => $libraries,
desk_name => $desk_name,
reservedate => $h->reservedate(),
expirationdate => $h->expirationdate(),
suspend => $h->suspend(),
suspend_until => $h->suspend_until(),
found => $h->found(),
waiting => $h->is_waiting(),
waiting_at => $h->branch()->branchname(),
waiting_here => $h->branch()->branchcode() eq $branch,
priority => $h->priority(),
reservenotes => $h->reservenotes(),
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,
iso18626_request => $h->iso18626_request
? {
id => $h->iso18626_request->iso18626_request_id,
status => $h->iso18626_request->status,
}
: undef,
reservedate_formatted => $h->reservedate()
? output_pref( { dt => dt_from_string( $h->reservedate() ), dateonly => 1 } )
: q{},