Bug 11395: Add a batch record modification

This patch offers a new tool to modify records in a batch.

This feature adds:
- a new pl/tt files tools/batch_record_modification
- a new permission: tools > records_batchmod

Test plan for biblios:
0/ Create a new marc modification template with some actions.
1/ Generate a list of biblionumbers you want to modify.
There are two ways to generate a list of biblionumbers:
- using the basket: do a search, add some biblio to your basket, open
  the basket and click on the "Action" button > "Modify"
- generating a list from a report
2/ On the "Batch record modification" tool verify:
- information is correct.
- the preview link show you the needed record.
3/ After clicking on the "Modify selected recors" button, verify
the records have been modified as you wanted.

Test plan for authority:
0/ Create a new marc modification template with some actions.
1/ Generate a list of authid using a report:
2/ On the "Batch record modification" tool verify:
- authorities are display with the summary.
- the preview link show you the needed record.
3/ After clicking on the "Modify selected recors" button, verify
the records have been modified as you wanted.

Catch of errors:
if an error occurs during the modification process, the tool
displays an error message.

Signed-off-by: Brendan Gallagher <brendan@bywatersolutions.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@gmail.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
This commit is contained in:
Jonathan Druart 2013-12-12 21:13:53 +01:00 committed by Tomas Cohen Arazi
parent ce2ea3e809
commit 8f77bee494
2 changed files with 498 additions and 0 deletions

View file

@ -0,0 +1,237 @@
[% PROCESS 'authorities-search-results.inc' %]
[% INCLUDE 'doc-head-open.inc' %]
<title>Koha &rsaquo; Tools &rsaquo; Batch record modification</title>
[% INCLUDE 'doc-head-close.inc' %]
[% INCLUDE 'greybox.inc' %]
<link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
[% INCLUDE 'datatables.inc' %]
<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.checkboxes.min.js"></script>
<script type="text/javascript" src="[% themelang %]/js/background-job-progressbar.js"></script>
<script type="text/javascript">
//<![CDATA[
$(document).ready(function() {
$("#selectall").click(function(e){
e.preventDefault();
$(".records").checkCheckboxes();
});
$("#clearall").click(function(e){
e.preventDefault();
$(".records").unCheckCheckboxes();
});
$("#selectall").click();
$("table#biblios").dataTable($.extend(true, {}, dataTablesDefaults, {
"aoColumnDefs": [
{ "aTargets": [ 0, 3 ], "bSortable": false, "bSearchable": false },
{ "aTargets": [ 1 ], "sType": "num-html" }
],
"sDom": 't',
"aaSorting": [],
"bPaginate": false
}));
$("table#authorities").dataTable($.extend(true, {}, dataTablesDefaults, {
"aoColumnDefs": [
{ "aTargets": [ 0, 3 ], "bSortable": false, "bSearchable": false },
{ "aTargets": [ 1 ], "sType": "num-html" }
],
"sDom": 't',
"aaSorting": [],
"bPaginate": false
}));
$("#mainformsubmit").click(function(){
return submitBackgroundJob(document.getElementById("process"));
});
});
//]]>
</script>
</head>
<body id="tools_batch_record_modification" class="tools">
[% INCLUDE 'header.inc' %]
[% INCLUDE 'cat-search.inc' %]
<div id="breadcrumbs">
<a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo;
<a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> &rsaquo;
<a href="/cgi-bin/koha/tools/batch_record_modification.pl">Batch record modification</a>
</div>
<div id="doc3" class="yui-t2">
<div id="bd">
<div id="yui-main">
<div class="yui-b">
<h1>Batch record modification</h1>
[% FOREACH message IN messages %]
[% IF message.type == 'success' %]
<div class="dialog message">
[% ELSIF message.type == 'warning' %]
<div class="dialog alert">
[% ELSIF message.type == 'error' %]
<div class="dialog error" style="margin:auto;">
[% END %]
<td><a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblio.biblionumber %]">[% biblio.title %]</a></td>
[% IF message.code == 'no_action_defined_for_the_template' %]
The selected template (id=[% message.mmtid%]) does not exist or no action is defined.
[% ELSIF message.code == 'biblio_not_exists' %]
The biblionumber [% message.biblionumber %] does not exist in the database.
[% ELSIF message.code == 'authority_not_exists' %]
The authority id [% message.authid %] does not exist in the database.
[% ELSIF message.code == 'biblio_not_modified' %]
The biblio <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% message.biblionumber %]">[% message.biblionumber %]</a> has not been modified. An error occurred on modifying it.
[% ELSIF message.code == 'authority_not_modified' %]
The authority <a href="/cgi-bin/koha/authorities/detail.pl?authid=[% message.authid %]">[% message.authid %]</a> has not been modified. An error occurred on modifying it.
[% ELSIF message.code == 'biblio_modified' %]
The biblio <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% message.biblionumber %]">[% message.biblionumber %]</a> has successfully been modified.
[% ELSIF message.code == 'authority_modified' %]
The authority <a href="/cgi-bin/koha/authorities/detail.pl?authid=[% message.authid %]">[% message.authid %]</a> has successfully been modified.
[% END %]
[% IF message.error %]
(The error was: [% message.error%], see the Koha logfile for more information).
[% END %]
</div>
[% END %]
[% IF view == 'form' %]
<form method="post" enctype="multipart/form-data" action="/cgi-bin/koha/tools/batch_record_modification.pl">
<fieldset class="rows">
<legend>Record type</legend>
<ol>
<li><label for="biblio_type">Biblios: </label><input type="radio" name="recordtype" value="biblio" id="biblio_type" checked="checked" /></li>
<li><label for="authority_type">Authorities: </label><input type="radio" name="recordtype" value="authority" id="authority_type" /></li>
</ol>
</fieldset>
<fieldset class="rows">
<legend>Use a file</legend>
<ol>
<li><label for="uploadfile">File: </label> <input type="file" id="uploadfile" name="uploadfile" /></li>
</ol>
</fieldset>
<fieldset class="rows">
<legend>Or enter a list of record numbers</legend>
<ol>
<li>
<label for="recordnumber_list">Record number list (one per line): </label>
<textarea rows="10" cols="30" id="recordnumber_list" name="recordnumber_list"></textarea>
</li>
</ol>
</fieldset>
<fieldset class="rows">
<legend>Use MARC Modification Template:</legend>
<ol>
<li>
<label for="marc_modification_template_id" class="required">Modify record using the following template: </label>
<select name="marc_modification_template_id" id="marc_modification_template_id" required="required">
<option value="">Select a template</option>
[% FOREACH mmt IN MarcModificationTemplatesLoop %]
<option value="[% mmt.template_id %]">[% mmt.name %]</option>
[% END %]
</select>
</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>
[% ELSIF view == 'list' %]
[% IF records %]
[% IF recordtype == 'biblio' %]
<div id="toolbar">
<a id="selectall" href="#">Select All</a>
| <a id="clearall" href="#">Clear All</a>
</div>
<form action="/cgi-bin/koha/tools/batch_record_modification.pl" method="post" id="process">
<table id="biblios" class="records">
<thead>
<tr>
<th></th>
<th>Biblionumber</th>
<th>Title</th>
<th>Preview</th>
</tr>
</thead>
<tbody>
[% FOR biblio IN records %]
<tr>
<td><input type="checkbox" name="record_id" value="[% biblio.biblionumber %]" data-items="[% biblio.itemnumbers.size %]" data-issues="[% biblio.issues_count %]" data-reserves="[% biblio.reserves.size %]" /></td>
<td>[% biblio.biblionumber %]</td>
<td><a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblio.biblionumber %]">[% biblio.title %]</a></td>
<td><a href="/cgi-bin/koha/svc/records/preview?record_type=biblio&record_id=[% biblio.biblionumber %]&mmtid=[% mmtid %]" title="MARC preview" rel="gb_page_center[600,500]">Preview MARC</a>
</tr>
[% END %]
</tbody>
</table>
<div class="note">Reminder: this action will modify all selected biblios!</div>
[% ELSE %]
<div id="toolbar">
<a id="selectall" href="#">Select All</a>
| <a id="clearall" href="#">Clear All</a>
</div>
<form action="/cgi-bin/koha/tools/batch_record_modification.pl" method="post" id="process">
<table id="authorities" class="records">
<thead>
<tr>
<th></th>
<th>Authid</th>
<th>Summary</th>
<th>Preview</th>
</tr>
</thead>
<tbody>
[% FOR authority IN records %]
<tr>
<td><input type="checkbox" name="record_id" value="[% authority.authid %]" data-usage="[% authority.count_usage %]" /></td>
<td><a href="/cgi-bin/koha/authorities/detail.pl?authid=[% authority.authid %]">[% authority.authid %]</a></td>
<td>[% PROCESS authresult summary=authority.summary %]</td>
<td><a href="/cgi-bin/koha/svc/records/preview?record_type=authority&record_id=[% authority.authid %]&mmtid=[% mmtid %]" title="MARC preview" rel="gb_page_center[600,500]">Preview MARC</a>
</tr>
[% END %]
</tbody>
</table>
<div class="note">Reminder: this action will modify all selected authorities!</div>
[% END %]
<fieldset class="action">
<input type="hidden" name="op" value="modify" />
<input type="hidden" name="recordtype" value="[% recordtype %]" />
<input type="button" id="mainformsubmit" value="Modify selected records" class="button" />
<input type="hidden" name="runinbackground" id="runinbackground" value="" />
<input type="hidden" name="completedJobID" id="completedJobID" value="" />
<input type="hidden" name="marc_modification_template_id" value="[% mmtid %]" />
<a class="cancel" href="/cgi-bin/koha/tools/batch_record_modification.pl">Cancel</a>
</fieldset>
<div id="jobpanel">
<div id="jobstatus">Job progress: <div id="jobprogress"></div> <span id="jobprogresspercent">0</span>%</div>
<div id="jobfailed"></div>
</div>
</form>
[% ELSE %]
There is no record ids defined.
[% END %]
[% ELSIF view == 'report' %]
[% IF report.total_records == report.total_success %]
All records have successfully been modified!
[% ELSE %]
[% report.total_success %] / [% report.total_records %] records have successfully been modified.
Some errors occurred.
[% END %]
<p><a href="/cgi-bin/koha/tools/batch_record_modification.pl" title="New batch record modification">New batch record modification</a></p>
[% ELSIF view == 'errors' %]
[% FOR error IN errors %]
[% IF error == 'no_template_defined' %]
No MARC modification template is defined. You have <a href="/cgi-bin/koha/tools/marc_modification_templates.pl">to create</a> at least one template for using this tool.
[% END %]
[% END %]
[% ELSE %]
No action defined for the template.
[% END %]
</div>
</div>
<div class="yui-b">
[% INCLUDE 'tools-menu.inc' %]
</div>
</div>
[% INCLUDE 'intranet-bottom.inc' %]

View file

@ -0,0 +1,261 @@
#!/usr/bin/perl
# This file is part of Koha.
#
# Copyright 2013 BibLibre
#
# 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 List::MoreUtils qw( uniq );
use C4::Auth qw( get_template_and_user );
use C4::Output qw( output_html_with_http_headers );
use C4::AuthoritiesMarc qw( BuildSummary GetAuthTypeCode ModAuthority );
use C4::BackgroundJob;
use C4::Biblio qw( GetMarcBiblio ModBiblio );
use C4::MarcModificationTemplates qw( GetModificationTemplateActions GetModificationTemplates ModifyRecordWithTemplate );
use Koha::Authority;
my $input = new CGI;
our $dbh = C4::Context->dbh;
my $op = $input->param('op') // q|form|;
my $recordtype = $input->param('recordtype') // 'biblio';
my $mmtid = $input->param('marc_modification_template_id');
my ( @messages );
my ( $template, $loggedinuser, $cookie ) = get_template_and_user({
template_name => 'tools/batch_record_modification.tt',
query => $input,
type => "intranet",
authnotrequired => 0,
flagsrequired => { tools => 'biblio_batchmod' },
});
my $sessionID = $input->cookie("CGISESSID");
my $runinbackground = $input->param('runinbackground');
my $completedJobID = $input->param('completedJobID');
if ( $completedJobID ) {
my $job = C4::BackgroundJob->fetch($sessionID, $completedJobID);
my $report = $job->get('report');
my $messages = $job->get('messages');
$template->param(
report => $report,
messages => $messages,
view => 'report',
);
output_html_with_http_headers $input, $cookie, $template->output;
exit;
}
my @templates = GetModificationTemplates();
unless ( @templates ) {
$op = 'error';
$template->param(
view => 'errors',
errors => ['no_template_defined'],
);
output_html_with_http_headers $input, $cookie, $template->output;
}
if ( $mmtid ) {
my @actions = GetModificationTemplateActions( $mmtid );
unless ( @actions ) {
$op = 'form';
push @messages, {
type => 'error',
code => 'no_action_defined_for_the_template',
mmtid => $mmtid,
};
}
}
if ( $op eq 'form' ) {
# Display the form
$template->param(
view => 'form',
MarcModificationTemplatesLoop => \@templates,
);
} elsif ( $op eq 'list' ) {
# List all records to process
my ( @records, @record_ids );
if ( my $bib_list = $input->param('bib_list') ) {
# Come from the basket
@record_ids = split /\//, $bib_list;
$recordtype = 'biblio';
} elsif ( my $uploadfile = $input->param('uploadfile') ) {
# A file of id is given
while ( my $content = <$uploadfile> ) {
next unless $content;
$content =~ s/[\r\n]*$//;
push @record_ids, $content if $content;
}
} else {
# The user enters manually the list of id
push @record_ids, split( /\s\n/, $input->param('recordnumber_list') );
}
for my $record_id ( uniq @record_ids ) {
if ( $recordtype eq 'biblio' ) {
# Retrieve biblio information
my $biblio = C4::Biblio::GetBiblio( $record_id );
unless ( $biblio ) {
push @messages, {
type => 'warning',
code => 'biblio_not_exists',
biblionumber => $record_id,
};
next;
}
push @records, $biblio;
} else {
# Retrieve authority information
my $authority = Koha::Authority->get_from_authid( $record_id );
unless ( $authority ) {
push @messages, {
type => 'warning',
code => 'authority_not_exists',
authid => $record_id,
};
next;
}
push @records, {
authid => $record_id,
summary => C4::AuthoritiesMarc::BuildSummary( $authority->record, $record_id ),
};
}
}
$template->param(
records => \@records,
mmtid => $mmtid,
view => 'list',
);
} elsif ( $op eq 'modify' ) {
# We want to modify selected records!
my @record_ids = $input->param('record_id');
my ( $job );
if ( $runinbackground ) {
my $job_size = scalar( @record_ids );
$job = C4::BackgroundJob->new( $sessionID, "FIXME", $ENV{SCRIPT_NAME}, $job_size );
my $job_id = $job->id;
if (my $pid = fork) {
$dbh->{InactiveDestroy} = 1;
my $reply = CGI->new("");
print $reply->header(-type => 'text/html');
print '{"jobID":"' . $job_id . '"}';
exit 0;
} elsif (defined $pid) {
close STDOUT;
close STDERR;
} else {
warn "fork failed while attempting to run $ENV{'SCRIPT_NAME'} as a background job";
exit 0;
}
}
my $report = {
total_records => 0,
total_success => 0,
};
my $progress = 0;
$dbh->{RaiseError} = 1;
RECORD_IDS: for my $record_id ( sort { $a <=> $b } @record_ids ) {
$report->{total_records}++;
next unless $record_id;
if ( $recordtype eq 'biblio' ) {
# Biblios
my $biblionumber = $record_id;
# Finally, modify the biblio
my $error = eval {
my $record = GetMarcBiblio( $biblionumber );
ModifyRecordWithTemplate( $mmtid, $record );
ModBiblio( $record, $biblionumber );
};
if ( $error and $error != 1 or $@ ) { # ModBiblio returns 1 if everything as gone well
push @messages, {
type => 'error',
code => 'biblio_not_modified',
biblionumber => $biblionumber,
error => ($@ ? $@ : $error),
};
} else {
push @messages, {
type => 'success',
code => 'biblio_modified',
biblionumber => $biblionumber,
};
$report->{total_success}++;
}
} else {
# Authorities
my $authid = $record_id;
my $error = eval {
my $authority = Koha::Authority->get_from_authid( $authid );
my $record = $authority->record;
ModifyRecordWithTemplate( $mmtid, $record );
ModAuthority( $authid, $record, GetAuthTypeCode( $authid ) );
};
if ( $error and $error != $authid or $@ ) {
push @messages, {
type => 'error',
code => 'authority_not_modified',
authid => $authid,
error => ($@ ? $@ : 0),
};
} else {
push @messages, {
type => 'success',
code => 'authority_modified',
authid => $authid,
};
$report->{total_success}++;
}
}
$job->set({
view => 'report',
report => $report,
messages => \@messages,
});
$job->progress( ++$progress ) if $runinbackground;
}
if ($runinbackground) {
$job->finish if defined $job;
} else {
$template->param(
view => 'report',
report => $report,
messages => \@messages,
);
}
}
$template->param(
messages => \@messages,
recordtype => $recordtype,
);
output_html_with_http_headers $input, $cookie, $template->output;