Bugzilla – Attachment 194481 Details for
Bug 27208
Add a configurable time delay feature to hold notice templates
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 27208: Add the ability to delay pickup notices.
Bug-27208-Add-the-ability-to-delay-pickup-notices.patch (text/plain), 21.01 KB, created by
Kevin Carnes
on 2026-03-05 08:11:37 UTC
(
hide
)
Description:
Bug 27208: Add the ability to delay pickup notices.
Filename:
MIME Type:
Creator:
Kevin Carnes
Created:
2026-03-05 08:11:37 UTC
Size:
21.01 KB
patch
obsolete
>From 62ce194db6d0fcaa76f4b090b3ffb7af854a0691 Mon Sep 17 00:00:00 2001 >From: =?UTF-8?q?Bj=C3=B6rn=20Nyl=C3=A9n?= <bjorn.nylen@ub.lu.se> >Date: Tue, 14 Dec 2021 08:34:30 +0000 >Subject: [PATCH] Bug 27208: Add the ability to delay pickup notices. > >This bug adds the ability to delay sending a pickup notice. This can be done in two >ways, either by a fixed configurable delay of all pickup notices or a time selectable >by staff at checkin. This is controlled by two new system preferences, >PickupNoticeDelayMode and PickupNoticeDelayDuration. A delayed notice will also be >visible in the patron notice log. > >To test: > >1. Apply patch and run updatedatabase.pl to add new column to message_queue. >2. Check in items with holds with different delay modes ('no delay','fixed delay','selectable by staff'). >3. Check the patron notice logs to check notice delay. Delayed notices have status 'pending, delayed until...' >4. Expected behaviour is: > 'No delay': No notices are delayed. > 'Fixed delay': All pickup notices are delayed by the no of hours set in PickupNoticeDelayDuration. > 'Delay selectable by staff': Pickup notice is delayed until the time set in UI. If no delay is set, > notice will not be delayed. Also note the warning at the checkin page > when a delay is set. >5. Run process_message_queue.pl before and after the delay times above. Notices without delay are sent > immediatly, delayed notices are only sent after the set time. >6. Tests should pass. >--- > C4/Letters.pm | 5 ++- > C4/Reserves.pm | 27 ++++++++++--- > circ/returns.pl | 21 +++++++++- > installer/data/mysql/mandatory/sysprefs.sql | 2 + > .../prog/en/includes/calendar.inc | 39 +++++++++++++++++++ > .../en/modules/admin/preferences/patrons.pref | 12 ++++++ > .../prog/en/modules/circ/returns.tt | 30 ++++++++++++++ > .../prog/en/modules/members/notices.tt | 2 +- > 8 files changed, 129 insertions(+), 9 deletions(-) > >diff --git a/C4/Letters.pm b/C4/Letters.pm >index 1db40371776..4a18c8263d4 100644 >--- a/C4/Letters.pm >+++ b/C4/Letters.pm >@@ -1005,6 +1005,7 @@ sub EnqueueLetter { > reply_address => $params->{reply_address}, > content_type => $params->{letter}->{'content-type'}, > failure_code => $params->{failure_code} || q{}, >+ delay_until => $params->{'delay_until'} || undef, > } > )->store(); > return $message->id; >@@ -1078,6 +1079,7 @@ sub SendQueuedMessages { > : (), #TODO Existing inconsistency > !ref( $params->{type} ) > && $params->{type} ? ( message_transport_type => $params->{type} ) : (), #TODO Existing inconsistency >+ delay_until => [ '-or', { '<' => \'NOW()' }, undef ], > } > ); > $unsent_messages = $unsent_messages->search( \$where ) if $where; >@@ -1207,7 +1209,7 @@ sub GetQueuedMessages { > > my $dbh = C4::Context->dbh(); > my $statement = <<'ENDSQL'; >-SELECT message_id, borrowernumber, subject, content, message_transport_type, status, time_queued, updated_on, failure_code, from_address, to_address, cc_address >+SELECT message_id, borrowernumber, subject, content, message_transport_type, status, time_queued, updated_on, failure_code, from_address, to_address, cc_address, delay_until > FROM message_queue > ENDSQL > >@@ -1382,6 +1384,7 @@ sub _get_unsent_messages { > FROM message_queue mq > LEFT JOIN borrowers b ON b.borrowernumber = mq.borrowernumber > WHERE status = ? >+ AND ( delay_until < NOW() OR delay_until IS NULL) > }; > > my @query_params = ('pending'); >diff --git a/C4/Reserves.pm b/C4/Reserves.pm >index de50ce5c930..d49a1c1dede 100644 >--- a/C4/Reserves.pm >+++ b/C4/Reserves.pm >@@ -87,6 +87,7 @@ use Koha::ItemTypes; > use Koha::Items; > use Koha::Libraries; > use Koha::Library; >+use Koha::Logger; > use Koha::Patrons; > use Koha::Plugins; > use Koha::Policy::Holds; >@@ -1240,7 +1241,7 @@ sub ModReserveStatus { > > =head2 ModReserveAffect > >- &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id, $notify_library); >+ &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id, $notify_library, $delay_notice_until); > > This function affect an item and a status for a given reserve, either fetched directly > by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber >@@ -1256,7 +1257,8 @@ This function also removes any entry of the hold in holds queue table. > =cut > > sub ModReserveAffect { >- my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id, $notify_library ) = @_; >+ my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id, $notify_library, $delay_notice_until ) = >+ @_; > my $dbh = C4::Context->dbh; > > # we want to attach $itemnumber to $borrowernumber, find the biblionumber >@@ -1283,6 +1285,20 @@ sub ModReserveAffect { > > my $original = $hold->unblessed; > >+ # Handle notice delay >+ my $delay_notice_mode = C4::Context->preference('PickupNoticeDelayMode') || 'off'; >+ if ( $delay_notice_mode eq 'fixed' ) { >+ my $delay_notice_duration = C4::Context->preference('PickupNoticeDelayDuration') || 0; >+ $delay_notice_until = dt_from_string()->add( hours => $delay_notice_duration ); >+ } elsif ( $delay_notice_mode eq 'staff' ) { >+ if ( ref($delay_notice_until) ne 'DateTime' ) { >+ Koha::Logger->get->warn("${\( $delay_notice_until // 'undef' )} is not a DateTime object"); >+ $delay_notice_until = undef; >+ } >+ } else { >+ $delay_notice_until = undef; >+ } >+ > my $already_on_shelf = $hold->found && $hold->found eq 'W'; > > $hold->itemnumber($itemnumber); >@@ -1300,7 +1316,7 @@ sub ModReserveAffect { > $hold->set_processing(); > } else { > $hold->set_waiting($desk_id); >- _koha_notify_reserve( $hold->reserve_id ) unless $already_on_shelf; >+ _koha_notify_reserve( $hold->reserve_id, $delay_notice_until ) unless $already_on_shelf; > > # Complete transfer if one exists > my $transfer = $hold->item->get_transfer; >@@ -1873,7 +1889,7 @@ sub _Findgroupreserve { > > =head2 _koha_notify_reserve > >- _koha_notify_reserve( $hold->reserve_id ); >+ _koha_notify_reserve( $hold->reserve_id, $delay_until ); > > Sends a notification to the patron that their hold has been filled (through > ModReserveAffect) >@@ -1900,7 +1916,7 @@ The following tables are available within the notice: > =cut > > sub _koha_notify_reserve { >- my $reserve_id = shift; >+ my ( $reserve_id, $delay_until ) = @_; > > my $hold = Koha::Holds->find($reserve_id); > my $borrowernumber = $hold->borrowernumber; >@@ -1957,6 +1973,7 @@ sub _koha_notify_reserve { > from_address => $from_email_address, > reply_address => $reply_email_address, > message_transport_type => $mtt, >+ delay_until => $delay_until, > } > ); > } else { >diff --git a/circ/returns.pl b/circ/returns.pl >index 97b23534900..809505f7b4a 100755 >--- a/circ/returns.pl >+++ b/circ/returns.pl >@@ -93,6 +93,16 @@ my $userenv = C4::Context->userenv; > my $userenv_branch = $userenv->{'branch'} // ''; > my $forgivemanualholdsexpire = $query->param('forgivemanualholdsexpire'); > >+# Handle notice delay >+my $delay_notice_until = $query->param('delay_notice_until') || q{}; >+my $notice_delay_mode = C4::Context->preference('PickupNoticeDelayMode') || 'off'; >+my $delay_notice_until_dt; >+if ( $notice_delay_mode eq 'staff' && $delay_notice_until ) { >+ $delay_notice_until_dt = eval { dt_from_string($delay_notice_until) }; >+} else { >+ $delay_notice_until = q{}; >+} >+ > my $overduecharges = ( C4::Context->preference('finesMode') && C4::Context->preference('finesMode') eq 'production' ); > > #set up so only the last 8 returned items display (make for faster loading pages) >@@ -149,7 +159,10 @@ if ( $query->param('reserve_id') && $op eq 'cud-affect_reserve' ) { > > # diffBranchSend tells ModReserveAffect whether document is expected in this library or not, > # i.e., whether to apply waiting status >- ModReserveAffect( $itemnumber, $borrowernumber, $diffBranchSend, $reserve_id, $desk_id ); >+ ModReserveAffect( >+ $itemnumber, $borrowernumber, $diffBranchSend, $reserve_id, $desk_id, undef, >+ $delay_notice_until_dt >+ ); > > if ($diffBranchSend) { > my $tobranch = $hold->pickup_library(); >@@ -605,7 +618,10 @@ if ( $messages->{'ResFound'} ) { > my $biblio = $item->biblio; > > my $diffBranchSend = !$branchCheck ? $reserve->{branchcode} : undef; >- ModReserveAffect( $itemnumber, $reserve->{borrowernumber}, $diffBranchSend, $reserve->{reserve_id}, $desk_id ); >+ ModReserveAffect( >+ $itemnumber, $reserve->{borrowernumber}, $diffBranchSend, $reserve->{reserve_id}, $desk_id, >+ undef, $delay_notice_until_dt >+ ); > > if ($diffBranchSend) { > my $tobranch = Koha::Libraries->find( $reserve->{branchcode} ); >@@ -815,6 +831,7 @@ $template->param( > forgivemanualholdsexpire => $forgivemanualholdsexpire, > overduecharges => $overduecharges, > AudioAlerts => C4::Context->preference("AudioAlerts"), >+ delay_notice_until => $delay_notice_until, > ); > > if ($barcode) { >diff --git a/installer/data/mysql/mandatory/sysprefs.sql b/installer/data/mysql/mandatory/sysprefs.sql >index 585d7a5d8d8..29a99c2041b 100644 >--- a/installer/data/mysql/mandatory/sysprefs.sql >+++ b/installer/data/mysql/mandatory/sysprefs.sql >@@ -647,6 +647,8 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` > ('PatronSelfRegistrationVerifyByEmail','0',NULL,'If enabled, any patron attempting to register themselves via the OPAC will be required to verify themselves via email to activate their account.','YesNo'), > ('PatronsPerPage','20','20','Number of Patrons Per Page displayed by default','Integer'), > ('PhoneNotification','0',NULL,'If ON, enables generation of phone notifications to be sent by plugins','YesNo'), >+('PickupNoticeDelayMode','off','off,fixed,staff','Delay mode for pickup notices','Choice'), >+('PickupNoticeDelayDuration','',NULL,'Delay for pickup notices in hours if PickupNoticeDelayMode is set to "Fixed delay"','Integer'), > ('PlaceHoldsOnOrdersFromSuggestions','0',NULL,'If ON, enables generation of holds when orders are placed from suggestions','YesNo'), > ('PrefillGuaranteeField', 'phone,email,streetnumber,address,city,state,zipcode,country', NULL, 'Prefill these fields in guarantee member entry form from guarantor patron record', 'Multiple'), > ('PrefillItem','0',NULL,'When a new item is added, should it be prefilled with last created item values?','YesNo'), >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/calendar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/calendar.inc >index 607d72d929b..befa241d0c0 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/calendar.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/calendar.inc >@@ -272,6 +272,45 @@ > options['plugins'] = []; > } > >+ if ( $(input).data('flatpickr-futuredatetime') === true ) { >+ let min_date = new Date(); >+ options['minDate'] = min_date; >+ options['defaultHour'] = min_date.getHours(); >+ options['defaultMinute'] = min_date.getMinutes(); >+ options['plugins'] = [ >+ ShortcutButtonsPlugin({ >+ button: [ >+ { >+ label: _("1h") >+ }, >+ { >+ label: _("2h") >+ }, >+ { >+ label: _("4h") >+ } >+ ], >+ label: _("or"), >+ onClick: (index, fp) => { >+ let date = new Date(); >+ let hh = date.getHours(); >+ switch (index) { >+ case 0: >+ date.setHours(hh + 1); >+ break; >+ case 1: >+ date.setHours(hh + 2); >+ break; >+ case 2: >+ date.setHours(hh + 4); >+ break; >+ } >+ fp.setDate(date, true); >+ } >+ }) >+ ]; >+ } >+ > let fp = $(input).flatpickr(options); > > $(disable_buttons).each(function(index, value){ >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref >index d94bc0dd4bc..086ac991895 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref >@@ -280,6 +280,18 @@ Patrons: > never: never > preferences: according to patron messaging preferences > cron: (Deprecated) according to --send-notices cron switch >+ - >+ - "Delay pickup notices to allow time for processing. Send with: " >+ - pref: PickupNoticeDelayMode >+ default: 'off' >+ choices: >+ off: "no delay" >+ fixed: "fixed delay" >+ staff: "delay selectable by staff" >+ - "If 'Fixed delay' then delay the notice by " >+ - pref: PickupNoticeDelayDuration >+ default: 0 >+ - "hours" > Patron forms: > - > - "The following <a href='https://schema.koha-community.org/__VERSION__/tables/borrowers.html' target='_blank'>database columns</a> must be filled in on the patron entry screen:" >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt >index 872667c2825..3c3673d859b 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt >@@ -459,6 +459,7 @@ > <input type="hidden" name="return_date_override_remember" value="[% return_date_override_remember | html %]" /> > <input type="hidden" name="multiple_confirm" value="1" /> > <input type="hidden" name="dropboxmode" value="[% dropboxmode | html %]" /> >+ <input type="hidden" name="delay_notice_until" value="[% delay_notice_until | html %]" /> > <input type="hidden" name="exemptfine" value="[% exemptfine | html %]" /> > [% FOREACH checkin IN checkins %] > <input type="hidden" name="checkin_counter" value="[% loop.count | html %]" /> >@@ -614,6 +615,7 @@ > <input type="hidden" name="op" value="cud-transfer" /> > <input type="hidden" name="return_date_override" value="[% return_date_override | html %]" /> > <input type="hidden" name="return_date_override_remember" value="[% return_date_override_remember | html %]" /> >+ <input type="hidden" name="delay_notice_until" value="[% delay_notice_until | html %]" /> > <input type="hidden" name="itemnumber" value="[% itemnumber | html %]" /> > <input type="hidden" name="transit" value="[% NewTransfer | html %]" /> > <input type="hidden" name="dropboxmode" value="[% dropboxmode | html %]" /> >@@ -742,6 +744,7 @@ > > <input type="hidden" name="return_date_override" value="[% return_date_override | html %]" /> > <input type="hidden" name="return_date_override_remember" value="[% return_date_override_remember | html %]" /> >+ <input type="hidden" name="delay_notice_until" value="[% delay_notice_until | html %]" /> > [% INCLUDE all_checkin_messages %] > </div> > <!-- /.modal-body --> >@@ -845,6 +848,7 @@ > [% END %] > <input type="hidden" name="return_date_override" value="[% return_date_override | html %]" /> > <input type="hidden" name="return_date_override_remember" value="[% return_date_override_remember | html %]" /> >+ <input type="hidden" name="delay_notice_until" value="[% delay_notice_until | html %]" /> > <input type="hidden" name="exemptfine" value="[% exemptfine | html %]" /> > <input type="hidden" name="dropboxmode" value="[% dropboxmode | html %]" /> > <input type="hidden" name="forgivemanualholdsexpire" value="[% forgivemanualholdsexpire | html %]" /> >@@ -1213,6 +1217,13 @@ > <div id="return_date_remember" class="checkin-active-setting" [% UNLESS return_date_override_remember %]style="display:none;"[% END %]> > <p><i class="fa fa-check"></i> Saved check-in date: <span id="saved_return_date" class="single-line">[% return_date_override | html %]</span></p> > </div> >+ [% IF ( delay_notice_until ) %] >+ <div id="delay_notice" class="checkin-active-setting"> >+ [% ELSE %] >+ <div id="delay_notice" class="checkin-active-setting" style="display:none;"> >+ [% END %] >+ <p><i class="fa fa-check"></i> Delay pickup notice until: <span id="saved_delay_notice" class="single-line">[% delay_notice_until | html %]</span></p> >+ </div> > </div> > </div> > >@@ -1277,6 +1288,12 @@ > </div> > [% END %] > <!-- overduecharges --> >+ [% IF Koha.Preference('PickupNoticeDelayMode') == 'staff' %] >+ <div id="delay-notice" class="date-select" > >+ <div class="hint">Delay pickup notice until: </div> >+ <input type="text" size="20" id="delay_notice_until" name="delay_notice_until" value="[% delay_notice_until | html %]" class="flatpickr" data-flatpickr-enable-time="true" data-flatpickr-on-close-focus="#barcode" data-flatpickr-futuredatetime="true" /> >+ </div> >+ [% END %] > </div> > <!-- /.circ-settings --> > </fieldset> >@@ -1490,6 +1507,7 @@ > <input type="hidden" name="return_date_override_remember" value="[% return_date_override_remember | html %]" /> > <input type="hidden" name="dropboxmode" value="[% dropboxmode | html %]" /> > <input type="hidden" name="exemptfine" value="[% exemptfine | html %]" /> >+ <input type="hidden" name="delay_notice_until" value="[% delay_notice_until | html %]" /> > [% FOREACH checkin IN checkins %] > <input type="hidden" name="checkin_counter" value="[% loop.count | html %]" /> > <input type="hidden" name="checkin_barcode_[% loop.count | html %]" value="[% checkin.barcode | html %]" /> >@@ -1767,6 +1785,18 @@ > }); > [% END %] > [% END %] >+ >+ $("input#delay_notice_until").on("change", function () { >+ let delay_date = $(this).val(); >+ $("span#saved_delay_notice").text(delay_date); >+ if ( delay_date == '' ){ >+ $("div#delay_notice").hide(); >+ } >+ else { >+ $("div#delay_notice").show(); >+ } >+ }); >+ > }); > </script> > [% END %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/notices.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/notices.tt >index 4b59bec2721..dba28016bdc 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/notices.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/notices.tt >@@ -84,7 +84,7 @@ > [% IF ( QUEUED_MESSAGE.status == 'sent' ) %] > <span>sent</span> > [% ELSIF ( QUEUED_MESSAGE.status == 'pending' ) %] >- <span>pending</span> >+ <span>pending[% IF QUEUED_MESSAGE.delay_until %], delayed until [% QUEUED_MESSAGE.delay_until | $KohaDates with_hours => 1 %][% END %]</span> > [% ELSIF ( QUEUED_MESSAGE.status == 'failed' ) %] > <span>failed</span> > [% ELSIF ( QUEUED_MESSAGE.status == 'deleted' ) %] >-- >2.43.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 27208
:
128387
|
128388
|
128389
|
128390
|
128587
|
128588
|
128589
|
128590
|
128948
|
128949
|
128950
|
128951
|
194480
| 194481 |
194482
|
194483
|
194484