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:
parent
ce2ea3e809
commit
8f77bee494
2 changed files with 498 additions and 0 deletions
|
@ -0,0 +1,237 @@
|
|||
[% PROCESS 'authorities-search-results.inc' %]
|
||||
[% INCLUDE 'doc-head-open.inc' %]
|
||||
<title>Koha › Tools › 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> ›
|
||||
<a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> ›
|
||||
<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' %]
|
261
tools/batch_record_modification.pl
Executable file
261
tools/batch_record_modification.pl
Executable 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;
|
Loading…
Reference in a new issue