From 5de0993363a68c1042473ccc70f99d22d32c591c 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 | 8 +-- C4/Reserves.pm | 26 ++++++++-- circ/returns.pl | 15 +++++- installer/data/mysql/mandatory/sysprefs.sql | 2 + .../prog/en/includes/calendar.inc | 49 +++++++++++++++++++ .../en/modules/admin/preferences/patrons.pref | 12 +++++ .../prog/en/modules/circ/returns.tt | 31 +++++++++++- .../prog/en/modules/members/notices.tt | 2 +- 8 files changed, 133 insertions(+), 12 deletions(-) diff --git a/C4/Letters.pm b/C4/Letters.pm index c36553ac9a..5a2420652d 100644 --- a/C4/Letters.pm +++ b/C4/Letters.pm @@ -963,9 +963,9 @@ sub EnqueueLetter { my $dbh = C4::Context->dbh(); my $statement = << 'ENDSQL'; INSERT INTO message_queue -( borrowernumber, subject, content, metadata, letter_code, message_transport_type, status, time_queued, to_address, from_address, reply_address, content_type, failure_code ) +( borrowernumber, subject, content, metadata, letter_code, message_transport_type, status, time_queued, to_address, from_address, reply_address, content_type, failure_code, delay_until ) VALUES -( ?, ?, ?, ?, ?, ?, ?, CAST(NOW() AS DATETIME), ?, ?, ?, ?, ? ) +( ?, ?, ?, ?, ?, ?, ?, CAST(NOW() AS DATETIME), ?, ?, ?, ?, ?, ?) ENDSQL my $sth = $dbh->prepare($statement); @@ -982,6 +982,7 @@ ENDSQL $params->{'reply_address'}, # reply_address $params->{'letter'}->{'content-type'}, # content_type $params->{'failure_code'} || '', # failure_code + $params->{'delay_until'} || undef, # delay_until ); return $dbh->last_insert_id(undef,undef,'message_queue', undef); } @@ -1124,7 +1125,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 +SELECT message_id, borrowernumber, subject, content, message_transport_type, status, time_queued, updated_on, failure_code, delay_until FROM message_queue ENDSQL @@ -1292,6 +1293,7 @@ sub _get_unsent_messages { FROM message_queue mq LEFT JOIN borrowers b ON b.borrowernumber = mq.borrowernumber WHERE status = ? + AND ( delay_until < CURRENT_TIMESTAMP OR delay_until IS NULL) }; my @query_params = ('pending'); diff --git a/C4/Reserves.pm b/C4/Reserves.pm index a42168bc72..ec2127adc0 100644 --- a/C4/Reserves.pm +++ b/C4/Reserves.pm @@ -1173,7 +1173,7 @@ sub ModReserveStatus { =head2 ModReserveAffect - &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id); + &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id, $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 @@ -1189,7 +1189,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 ) = @_; + my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id, $delay_notice_until) = @_; + my $dbh = C4::Context->dbh; # we want to attach $itemnumber to $borrowernumber, find the biblionumber @@ -1211,6 +1212,20 @@ sub ModReserveAffect { return unless $hold; + # 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') { + $delay_notice_until = undef unless (ref($delay_notice_until) eq 'DateTime'); + warn "$delay_notice_until is no DateTime object" unless (ref($delay_notice_until) eq 'DateTime'); + } + else { + $delay_notice_until = undef; + } + my $already_on_shelf = $hold->found && $hold->found eq 'W'; $hold->itemnumber($itemnumber); @@ -1223,7 +1238,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; $transfer->receive if $transfer; @@ -1818,7 +1833,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, _not_ ModReserveFill) @@ -1845,7 +1860,7 @@ The following tables are availalbe witin 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; @@ -1894,6 +1909,7 @@ sub _koha_notify_reserve { borrowernumber => $borrowernumber, from_address => $admin_email_address, message_transport_type => $mtt, + delay_until => $delay_until, } ); }; diff --git a/circ/returns.pl b/circ/returns.pl index 8ab9986d91..3e58af357e 100755 --- a/circ/returns.pl +++ b/circ/returns.pl @@ -86,6 +86,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') || ''; +my $notice_delay_mode = C4::Context->preference('PickupNoticeDelayMode') || 'off'; +my $delay_notice_until_dt = undef; +if ( $notice_delay_mode eq 'staff' && $delay_notice_until ){ + $delay_notice_until_dt = eval { dt_from_string( $delay_notice_until ) }; +} else { + $delay_notice_until = ''; +} + my $overduecharges = (C4::Context->preference('finesMode') && C4::Context->preference('finesMode') eq 'production'); # Set up the item stack .... @@ -150,7 +160,7 @@ if ( $query->param('reserve_id') ) { my $diffBranchSend = ($userenv_branch ne $diffBranchReturned) ? $diffBranchReturned : undef; # 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, $delay_notice_until_dt ); } # check if we have other reserves for this document, if we have a return send the message of transfer my ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber); @@ -434,7 +444,7 @@ if ( $messages->{'ResFound'}) { my $biblio = $item->biblio; my $diffBranchSend = !$branchCheck ? $reserve->{branchcode} : undef; - ModReserveAffect( $reserve->{itemnumber}, $reserve->{borrowernumber}, $diffBranchSend, $reserve->{reserve_id}, $desk_id ); + ModReserveAffect( $reserve->{itemnumber}, $reserve->{borrowernumber}, $diffBranchSend, $reserve->{reserve_id}, $desk_id, $delay_notice_until_dt ); my ( $messages, $nextreservinfo ) = GetOtherReserves($reserve->{itemnumber}); $template->param( @@ -641,6 +651,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 68877bbbce..31c3ae0873 100644 --- a/installer/data/mysql/mandatory/sysprefs.sql +++ b/installer/data/mysql/mandatory/sysprefs.sql @@ -529,6 +529,8 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('PatronsPerPage','20','20','Number of Patrons Per Page displayed by default','Integer'), ('PatronQuickAddFields', '', NULL , 'A list of fields separated by "|" to be displayed along with mandatory fields in the patron quick add form if chosen at patron entry', 'Free' ), ('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' ), ('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','','When a new item is added, should it be prefilled with last created item values?','YesNo'), ('PreserveSerialNotes','1','','When a new "Expected" issue is generated, should it be prefilled with last created issue notes?','YesNo'), diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/calendar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/calendar.inc index 2805870d10..8e06466eed 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/calendar.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/calendar.inc @@ -155,6 +155,55 @@ options['enableTime'] = true; options['dateFormat'] = flatpickr_dateformat_string + " " + flatpickr_timeformat_string; } + if ( $(this).data('flatpickr-futuredatetime') === true ) { + let original_date = $(this).val(); + if ( original_date ) { + original_date = DateTime_from_syspref( original_date ).getTime(); + let now = new Date().getTime(); + + options['enable'] = [function(date){ + date = date.getTime(); + if ( date == original_date ) return true; + if ( date >= now) return true; + }]; + } + else { + options['minDate'] = new Date(); + options['defaultHour'] = new Date().getHours(); + options['defaultMinute'] = new Date().getMinutes(); + + } + options['plugins'] = [ ShortcutButtonsPlugin({ + button: [ + { + label: __("1h") + }, + { + label: __("2h") + }, + { + label: __("4h") + } + ], + label: __("or"), + onClick: (index, fp) => { + let date; + let hh = 23, mm = 59; + switch (index) { + case 0: + date = new Date(Date.now() + 1 * 60 * 60 * 1000); + break; + case 1: + date = new Date(Date.now() + 2 * 60 * 60 * 1000); + break; + case 2: + date = new Date(Date.now() + 4 * 60 * 60 * 1000); + break; + } + fp.setDate(date, true); + } + })]; + } let fp = $(this).flatpickr(options); if ( refresh_max_date ) { 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 5a6f1bd5a4..9e6234942c 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 @@ -206,6 +206,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 c119c2708e..1562815b49 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt @@ -367,6 +367,7 @@
+ [% FOREACH inputloo IN inputloop %] @@ -432,6 +433,7 @@ + [% FOREACH inputloo IN inputloop %] @@ -538,6 +540,7 @@ + [% INCLUDE all_checkin_messages %] @@ -609,6 +612,7 @@ [% END %] + @@ -708,6 +712,7 @@ + [% INCLUDE all_checkin_messages %] @@ -800,6 +805,13 @@ [% END %]

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

+ [% IF ( delay_notice_until ) %] +
+ [% ELSE %] +
@@ -851,7 +863,12 @@ [% END %] - + [% IF Koha.Preference('PickupNoticeDelayMode') == 'staff' %] +
+
Delay pickup notice until:
+ +
+ [% END %]
@@ -1038,6 +1055,7 @@ "dom": 'B<"clearfix">t', }, columns_settings); + $("#exemptcheck").change(function () { if (this.checked == true) { $("#barcode").addClass("input-warning"); @@ -1147,6 +1165,17 @@ window.open("/cgi-bin/koha/members/printslip.pl?borrowernumber=" + borrowernumber + "&print=checkinslip", "printwindow"); }); + $("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 acfa28cd72..c36c538203 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/notices.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/notices.tt @@ -72,7 +72,7 @@ [% IF ( QUEUED_MESSAGE.status == 'sent' ) %]sent - [% ELSIF ( QUEUED_MESSAGE.status == 'pending' ) %]pending + [% ELSIF ( QUEUED_MESSAGE.status == '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' ) %]deleted [% ELSE %][% QUEUED_MESSAGE.status | html %][% END %] -- 2.20.1