Bug 24846: Add new tool to batch extend due dates
With events sometimes leading to unforeseen branch closures (think Coronavirus as an example), it would be helpful to have a tool that would allow librarians to update due dates in bulk based on branch and current due date of the material. It allows to select checkouts given the following parameters: * libraries * patron's categories * range of the due date You can set a hard due date, or define a number of days to extend the due date. Test plan: Check some items out Use the new tool to extend the due dates Test the different filters to make sure they all work Note: What about holidays? Signed-off-by: Bernardo Gonzalez Kriegel <bgkriegel@gmail.com> Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
This commit is contained in:
parent
18a56d0146
commit
c7084c4c27
2 changed files with 414 additions and 0 deletions
|
@ -0,0 +1,269 @@
|
|||
[% USE raw %]
|
||||
[% USE Asset %]
|
||||
[% SET footerjs = 1 %]
|
||||
[% USE Branches %]
|
||||
[% USE Categories %]
|
||||
[% USE KohaDates %]
|
||||
[% USE ItemTypes %]
|
||||
[% PROCESS 'html_helpers.inc' %]
|
||||
|
||||
[% INCLUDE 'doc-head-open.inc' %]
|
||||
<title>Koha › Tools › Batch extend due dates</title>
|
||||
[% INCLUDE 'doc-head-close.inc' %]
|
||||
[% Asset.css("css/humanmsg.css") | $raw %]
|
||||
</head>
|
||||
|
||||
<body id="tools_batch_extend_due_dates" class="tools">
|
||||
[% INCLUDE 'header.inc' %]
|
||||
[% INCLUDE 'cat-search.inc' %]
|
||||
|
||||
<div id="breadcrumbs">
|
||||
<a href="/cgi-bin/koha/mainpage.pl">Home</a> ›
|
||||
<a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> ›
|
||||
<a href="/cgi-bin/koha/tools/batch_extend_due_dates.pl">Batch extend due dates</a>
|
||||
</div>
|
||||
|
||||
<div class="main container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-10 col-sm-push-2">
|
||||
<main>
|
||||
|
||||
<h1>Batch extend due dates</h1>
|
||||
|
||||
[% IF ( messages ) %]
|
||||
<div class="dialog message">
|
||||
|
||||
[% FOREACH message IN messages %]
|
||||
[% IF message.type == 'success' %]
|
||||
<div><i class="fa fa-check success"></i>
|
||||
[% ELSIF message.type == 'warning' %]
|
||||
<div><i class="fa fa-warning warn"></i>
|
||||
[% ELSIF message.type == 'error' %]
|
||||
<div><i class="fa fa-exclamation error"></i>
|
||||
[% END %]
|
||||
[% IF message.error %]
|
||||
(The error was: [% message.error | html %]. See the Koha logfile for more information).
|
||||
[% END %]
|
||||
</div>
|
||||
[% END %]
|
||||
|
||||
</div> <!-- .dialog.message -->
|
||||
[% END %]
|
||||
|
||||
[% IF view == 'form' %]
|
||||
<form method="post" enctype="multipart/form-data" action="/cgi-bin/koha/tools/batch_extend_due_dates.pl" id="extend_due_dates_form">
|
||||
<fieldset class="rows">
|
||||
<legend>Checkout criteria:</legend>
|
||||
<ol>
|
||||
<li>
|
||||
<label for="categorycodes">Patrons' categories: </label>
|
||||
[% SET categories = Categories.all() %]
|
||||
<select id="categorycodes" name="categorycodes" multiple="multiple">
|
||||
[% FOREACH cat IN categories %]
|
||||
<option value="[% cat.categorycode | html %]">[% cat.description | html %]</option>
|
||||
[% END %]
|
||||
</select>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<label for="branchcodes">Libraries: </label>
|
||||
<select name="branchcodes" id="branchcodes" multiple="multiple">
|
||||
[% PROCESS options_for_libraries libraries => Branches.all() %]
|
||||
</select>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<label for="from_due_date">Due date from: </label>
|
||||
<input type="text" size="10" id="from" name="from_due_date" class="datepickerfrom" />
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<label for="to_due_date">Due date to:</label>
|
||||
<input type="text" size="10" id="to" name="to_due_date" class="datepickerto" />
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
<fieldset class="rows">
|
||||
<legend>New due date:</legend>
|
||||
<ol>
|
||||
<li>
|
||||
<label for="new_hard_due_date">Hard due date: </label>
|
||||
<input type="text" size="10" id="new_hard_due_date" name="new_hard_due_date" class="datepicker" />
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<label for="due_date_days">Or add number of days:</label>
|
||||
<input type="text" size="10" id="due_date_days" name="due_date_days"/>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
<fieldset class="action">
|
||||
<input type="hidden" name="op" value="list" />
|
||||
<input type="submit" value="Continue" class="button" />
|
||||
<a class="cancel" href="/cgi-bin/koha/tools/tools-home.pl">Cancel</a>
|
||||
</fieldset>
|
||||
</form> <!-- /#extend_due_dates_form -->
|
||||
[% ELSIF view == 'list' %]
|
||||
[% IF checkouts.count %]
|
||||
<form action="/cgi-bin/koha/tools/batch_extend_due_dates.pl" method="post" id="process">
|
||||
<div id="toolbar">
|
||||
<a id="selectall" href="#"><i class="fa fa-check"></i> Select all</a>
|
||||
| <a id="clearall" href="#"><i class="fa fa-remove"></i> Clear all</a>
|
||||
</div>
|
||||
<table id="checkouts">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Due date</th>
|
||||
<th>Title</th>
|
||||
<th>Item type</th>
|
||||
<th>Home library</th>
|
||||
<th>Checked out on</th>
|
||||
<th>Checked out from</th>
|
||||
<th>New due date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[% FOR checkout IN checkouts %]
|
||||
<tr>
|
||||
<td><input type="checkbox" name="issue_id" value="[% checkout.issue_id | html %]" /></td>
|
||||
<td>[% checkout.date_due | $KohaDates as_due_date => 1 %]</td>
|
||||
<td><a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% checkout.item.biblio.biblionumber | uri %]">[% checkout.item.biblio.title | html %]</a></td>
|
||||
<td>[% ItemTypes.GetDescription( checkout.item.effective_itemtype ) | html %]</td>
|
||||
<td>[% checkout.item.home_branch.branchname | html %]</td>
|
||||
<td>[% checkout.issuedate | $KohaDates %]</td>
|
||||
<td>[% Branches.GetName( checkout.branchcode ) | html %]</td>
|
||||
<td>
|
||||
[% IF new_hard_due_date %]
|
||||
[% new_hard_due_date | $KohaDates %]
|
||||
[% ELSE %]
|
||||
[% new_due_dates.shift | $KohaDates %]
|
||||
[% END %]
|
||||
</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
</tbody>
|
||||
</table> <!-- /#checkouts -->
|
||||
<div class="note"><i class="fa fa-exclamation"></i> Reminder: this action will modify all selected checkouts!</div>
|
||||
<fieldset class="action">
|
||||
<input type="hidden" name="op" value="modify" />
|
||||
<input type="hidden" name="new_hard_due_date" value="[% new_hard_due_date | $KohaDates %]" />
|
||||
<input type="hidden" name="due_date_days" value="[% due_date_days | html %]" />
|
||||
<input type="submit" value="Modify selected checkouts" class="button" />
|
||||
<a class="cancel" href="/cgi-bin/koha/tools/batch_extend_due_dates.pl">Cancel</a>
|
||||
</fieldset>
|
||||
</form> <!-- /#process -->
|
||||
[% ELSE %]
|
||||
<div class="dialog message">
|
||||
No checkouts for the selected filters.
|
||||
</div>
|
||||
[% END %]
|
||||
[% ELSIF view == 'report' %]
|
||||
<div class="dialog message">
|
||||
Due dates have been modified!
|
||||
</div>
|
||||
|
||||
<table id="checkouts_result">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Due date</th>
|
||||
<th>Title</th>
|
||||
<th>Item type</th>
|
||||
<th>Home library</th>
|
||||
<th>Checked out on</th>
|
||||
<th>Checked out from</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[% FOR checkout IN checkouts %]
|
||||
<tr>
|
||||
<td>[% checkout.date_due | $KohaDates as_due_date => 1 %]</td>
|
||||
<td><a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% checkout.item.biblio.biblionumber | uri %]">[% checkout.item.biblio.title | html %]</a></td>
|
||||
<td>[% ItemTypes.GetDescription( checkout.item.effective_itemtype ) | html %]</td>
|
||||
<td>[% checkout.item.home_branch.branchname | html %]</td>
|
||||
<td>[% checkout.issuedate | $KohaDates %]</td>
|
||||
<td>[% Branches.GetName( checkout.branchcode ) | html %]</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
</tbody>
|
||||
</table> <!-- /#checkouts_result -->
|
||||
[% END %]
|
||||
</main>
|
||||
</div> <!-- /.col-sm-10.col-sm-push-2 -->
|
||||
|
||||
<div class="col-sm-2 col-sm-pull-10">
|
||||
<aside>
|
||||
[% INCLUDE 'tools-menu.inc' %]
|
||||
</aside>
|
||||
</div> <!-- /.col-sm-2.col-sm-pull-10 -->
|
||||
</div> <!-- /.row -->
|
||||
|
||||
[% MACRO jsinclude BLOCK %]
|
||||
[% Asset.js("js/tools-menu.js") | $raw %]
|
||||
[% INCLUDE 'calendar.inc' %]
|
||||
[% INCLUDE 'datatables.inc' %]
|
||||
[% Asset.js("lib/jquery/plugins/jquery.checkboxes.min.js") | $raw %]
|
||||
[% Asset.js("lib/jquery/plugins/humanmsg.js") | $raw %]
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
$("#selectall").click(function(e) {
|
||||
e.preventDefault();
|
||||
$("#checkouts").checkCheckboxes();
|
||||
});
|
||||
$("#clearall").click(function(e) {
|
||||
e.preventDefault();
|
||||
$("#checkouts").unCheckCheckboxes();
|
||||
});
|
||||
$("#selectall").click();
|
||||
|
||||
$("table#checkouts").dataTable($.extend(true, {}, dataTablesDefaults, {
|
||||
"aoColumnDefs": [
|
||||
{ "aTargets": [0, 3], "bSortable": false, "bSearchable": false },
|
||||
{ "aTargets": [1], "sType": "num-html" }
|
||||
],
|
||||
"sDom": 't',
|
||||
"aaSorting": [],
|
||||
"bPaginate": false
|
||||
}));
|
||||
|
||||
$("table#checkouts_result").dataTable($.extend(true, {}, dataTablesDefaults, {
|
||||
"aoColumnDefs": [
|
||||
{ "aTargets": [0, 3], "bSortable": false, "bSearchable": false },
|
||||
{ "aTargets": [1], "sType": "num-html" }
|
||||
],
|
||||
"sDom": 't',
|
||||
"aaSorting": [],
|
||||
"bPaginate": false
|
||||
}));
|
||||
|
||||
$("#extend_due_dates_form").on('submit', function(e) {
|
||||
var new_hard_due_date = $("#new_hard_due_date").val();
|
||||
var due_date_days = $("#due_date_days").val();
|
||||
if (new_hard_due_date && due_date_days ) {
|
||||
e.preventDefault();
|
||||
alert(_("You must fill only one of the two due date options"));
|
||||
return false;
|
||||
} else if ( !new_hard_due_date && !due_date_days ) {
|
||||
e.preventDefault();
|
||||
alert(_("You must fill at least one of the two due date options"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$("#process").on('submit', function(e) {
|
||||
if ($("input[type=checkbox][name='issue_id']:checked").length == 0) {
|
||||
e.preventDefault();
|
||||
alert(_("Please select at least one checkout to process"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
[% END %]
|
||||
|
||||
[% INCLUDE 'intranet-bottom.inc' %]
|
145
tools/batch_extend_due_dates.pl
Executable file
145
tools/batch_extend_due_dates.pl
Executable file
|
@ -0,0 +1,145 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
# This file is part of Koha.
|
||||
#
|
||||
# Copyright 2020 Koha Development Team
|
||||
#
|
||||
# Koha is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# Koha is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General
|
||||
# Public License along with Koha; if not, see
|
||||
# <http://www.gnu.org/licenses>
|
||||
|
||||
use Modern::Perl;
|
||||
|
||||
use CGI;
|
||||
|
||||
use C4::Auth qw( get_template_and_user );
|
||||
use C4::Output qw( output_html_with_http_headers );
|
||||
use Koha::Checkouts;
|
||||
use Koha::DateUtils qw( dt_from_string );
|
||||
|
||||
my $input = new CGI;
|
||||
my $op = $input->param('op') // q|form|;
|
||||
|
||||
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
|
||||
{
|
||||
template_name => 'tools/batch_extend_due_dates.tt',
|
||||
query => $input,
|
||||
type => "intranet",
|
||||
authnotrequired => 0,
|
||||
flagsrequired => { tools => 'batch_extend_due_dates' },
|
||||
}
|
||||
);
|
||||
|
||||
if ( $op eq 'form' ) {
|
||||
$template->param( view => 'form', );
|
||||
}
|
||||
elsif ( $op eq 'list' ) {
|
||||
|
||||
my @categorycodes = $input->multi_param('categorycodes');
|
||||
my @branchcodes = $input->multi_param('branchcodes');
|
||||
my $from_due_date = $input->param('from_due_date');
|
||||
my $to_due_date = $input->param('to_due_date');
|
||||
my $new_hard_due_date = $input->param('new_hard_due_date');
|
||||
my $due_date_days = $input->param('due_date_days');
|
||||
|
||||
my $dtf = Koha::Database->new->schema->storage->datetime_parser;
|
||||
my $search_params;
|
||||
if (@categorycodes) {
|
||||
$search_params->{'borrower.categorycode'} = { -in => \@categorycodes };
|
||||
}
|
||||
if (@branchcodes) {
|
||||
$search_params->{'me.branchcode'} = { -in => \@branchcodes };
|
||||
}
|
||||
if ( $from_due_date and $to_due_date ) {
|
||||
my $to_due_date_endday = dt_from_string($to_due_date);
|
||||
$to_due_date_endday
|
||||
->set( # We set last second of day to see all checkouts from that day
|
||||
hour => 23,
|
||||
minute => 59,
|
||||
second => 59
|
||||
);
|
||||
$search_params->{'me.date_due'} = {
|
||||
-between => [
|
||||
$dtf->format_datetime( dt_from_string($from_due_date) ),
|
||||
$dtf->format_datetime($to_due_date_endday),
|
||||
]
|
||||
};
|
||||
}
|
||||
elsif ($from_due_date) {
|
||||
$search_params->{'me.date_due'} =
|
||||
{ '>=' => $dtf->format_datetime( dt_from_string($from_due_date) ) };
|
||||
}
|
||||
elsif ($to_due_date) {
|
||||
my $to_due_date_endday = dt_from_string($to_due_date);
|
||||
$to_due_date_endday
|
||||
->set( # We set last second of day to see all checkouts from that day
|
||||
hour => 23,
|
||||
minute => 59,
|
||||
second => 59
|
||||
);
|
||||
$search_params->{'me.date_due'} =
|
||||
{ '<=' => $dtf->format_datetime($to_due_date_endday) };
|
||||
}
|
||||
|
||||
my $checkouts = Koha::Checkouts->search(
|
||||
$search_params,
|
||||
{
|
||||
join => [ 'item', 'borrower' ]
|
||||
}
|
||||
);
|
||||
|
||||
my @new_due_dates;
|
||||
if ( not $new_hard_due_date && $due_date_days ) {
|
||||
while ( my $checkout = $checkouts->next ) {
|
||||
my $due_date = dt_from_string( $checkout->date_due );
|
||||
push @new_due_dates, $due_date->add( days => $due_date_days );
|
||||
}
|
||||
}
|
||||
$template->param(
|
||||
checkouts => $checkouts,
|
||||
new_hard_due_date => $new_hard_due_date
|
||||
? dt_from_string($new_hard_due_date)
|
||||
: undef,
|
||||
due_date_days => $due_date_days,
|
||||
new_due_dates => \@new_due_dates,
|
||||
view => 'list',
|
||||
);
|
||||
}
|
||||
elsif ( $op eq 'modify' ) {
|
||||
|
||||
# We want to modify selected checkouts!
|
||||
my @issue_ids = $input->multi_param('issue_id');
|
||||
my $new_hard_due_date = $input->param('new_hard_due_date');
|
||||
my $due_date_days = $input->param('due_date_days');
|
||||
|
||||
$new_hard_due_date &&= dt_from_string($new_hard_due_date);
|
||||
my $checkouts =
|
||||
Koha::Checkouts->search( { issue_id => { -in => \@issue_ids } } );
|
||||
while ( my $checkout = $checkouts->next ) {
|
||||
if ($new_hard_due_date) {
|
||||
$checkout->date_due($new_hard_due_date)->store;
|
||||
}
|
||||
else {
|
||||
my $dt = dt_from_string( $checkout->date_due )
|
||||
->add( days => $due_date_days );
|
||||
$checkout->date_due($dt)->store;
|
||||
}
|
||||
}
|
||||
|
||||
$template->param(
|
||||
view => 'report',
|
||||
checkouts => $checkouts,
|
||||
);
|
||||
}
|
||||
|
||||
output_html_with_http_headers $input, $cookie, $template->output;
|
Loading…
Reference in a new issue