From 62ce194db6d0fcaa76f4b090b3ffb7af854a0691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Nyl=C3=A9n?= 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 database columns 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 @@ + [% FOREACH checkin IN checkins %] @@ -614,6 +615,7 @@ + @@ -742,6 +744,7 @@ + [% INCLUDE all_checkin_messages %] @@ -845,6 +848,7 @@ [% END %] + @@ -1213,6 +1217,13 @@

Saved check-in date: [% return_date_override | html %]

+ [% IF ( delay_notice_until ) %] +
+ [% ELSE %] +
@@ -1277,6 +1288,12 @@ [% END %] + [% IF Koha.Preference('PickupNoticeDelayMode') == 'staff' %] +
+
Delay pickup notice until:
+ +
+ [% END %] @@ -1490,6 +1507,7 @@ + [% FOREACH checkin IN checkins %] @@ -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(); + } + }); + }); [% 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' ) %] sent [% ELSIF ( QUEUED_MESSAGE.status == 'pending' ) %] - pending + pending[% IF QUEUED_MESSAGE.delay_until %], delayed until [% QUEUED_MESSAGE.delay_until | $KohaDates with_hours => 1 %][% END %] [% ELSIF ( QUEUED_MESSAGE.status == 'failed' ) %] failed [% ELSIF ( QUEUED_MESSAGE.status == 'deleted' ) %] -- 2.43.0